diff --git a/.all-contributorsrc b/.all-contributorsrc
new file mode 100644
index 00000000..6f9e5960
--- /dev/null
+++ b/.all-contributorsrc
@@ -0,0 +1,57 @@
+{
+ "files": [
+ "README.md",
+ "README_cn.md"
+ ],
+ "imageSize": 100,
+ "commit": false,
+ "contributors": [
+ {
+ "login": "AviKav",
+ "name": "AviKav",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/18518861?v=4",
+ "profile": "https://avikav.net",
+ "contributions": [
+ "bug",
+ "test"
+ ]
+ },
+ {
+ "login": "AxisKriel",
+ "name": "Rodrigo Rente",
+ "avatar_url": "https://avatars0.githubusercontent.com/u/3332657?v=4",
+ "profile": "https://tshock.co",
+ "contributions": [
+ "code",
+ "projectManagement",
+ "test"
+ ]
+ },
+ {
+ "login": "sgkoishi",
+ "name": "Stargazing Koishi",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/9637711?v=4",
+ "profile": "https://sgkoi.dev",
+ "contributions": [
+ "code",
+ "infra"
+ ]
+ },
+ {
+ "login": "AxeelAnder",
+ "name": "Axeel",
+ "avatar_url": "https://avatars2.githubusercontent.com/u/25691207?v=4",
+ "profile": "https://github.com/AxeelAnder",
+ "contributions": [
+ "doc",
+ "projectManagement"
+ ]
+ }
+ ],
+ "contributorsPerLine": 7,
+ "projectName": "TShock",
+ "projectOwner": "Pryaxis",
+ "repoType": "github",
+ "repoHost": "https://github.com",
+ "skipCi": true
+}
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..990b9ed1
--- /dev/null
+++ b/.github/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+> By participating in the TShock for Terraria community, all members will adhere to maintaining decorum with respect to all humans, in and out of the community. Members will not engage in discussion that inappropriately disparages or marginalizes any group of people or any individual. Members will not attempt to further or advance an agenda to the point of being overbearing or close minded (such as through spreading FUD). Members will not abuse services provided to them and will follow the guidance of community leaders on a situational basis about what abuse consists of. Members will adhere to United States and international law. If members notice a violation of this code of conduct, they will not engage but will instead contact the leadership team on either the forums or Discord.
+
+> Do not attempt to circumvent or bypass the code of conduct by using clever logic or reasoning (e.g., insulting Facepunch members, because they weren't directly mentioned here).
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md
similarity index 75%
rename from CONTRIBUTING.md
rename to .github/CONTRIBUTING.md
index 6382cc88..b28f0c83 100644
--- a/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -8,38 +8,26 @@ Please follow these simple requirements before posting a bug report:
- How to reproduce the issue
- Screenshots of the issue (if applicable)
-### To build the source
-
-Note: This includes the API by default. If you need only the API, you need to cd into that folder and do the following with the .sln file for the API. For those new to C#, the .sln and .csproj files contain the necessary definitions to do a complete source build using Microsoft or Mono build tools.
-
-- Checkout the source.
-- Initialize the submodules: ```git submodule update --init```
-- Open the source in your favorite text editor that supports .NET building and press the build button OR
-- Run ```msbuild TShock.sln``` in the root of the cloned folder on Windows in a 'Developer Command Prompt' OR
-- Run ```xbuild TShock.sln``` in the root of the cloned folder on Unix.
-
-Need help? Drop by Slack and we'll be happy to explain it with more words, step by step.
-
### TShock Additions
If something is better suited to be a plugin for TShock, rather than a TShock core feature, it should not be added! Project scope is at times questionable, though, so create an issue on Github for discussion first. If an issue is completely outside of the scope of TShock, it will be made clear in that issue what it is.
_If you are confused, make a suggestion. We will determine scope and relevance for you._
-_If a person makes a suggestion in Slack, capture the suggestion as a Github issue. If a suggestion crops up on the forums, make a Github issue to capture it. If you want, direct the user to make a suggestion on Github, but set an alarm/timer/reminder so that if they don't know how to use Github or they don't have an account, an issue is still made and discussed. Make it clear that the issue is a surrogate issue for a suggestion from Slack/the forums too._
+_If a person makes a suggestion in Discord, capture the suggestion as a Github issue. If a suggestion crops up on the forums, make a Github issue to capture it. If you want, direct the user to make a suggestion on Github, but set an alarm/timer/reminder so that if they don't know how to use Github or they don't have an account, an issue is still made and discussed. Make it clear that the issue is a surrogate issue for a suggestion from Discord/the forums too._
### Pull Request Dev Guidelines
These guidelines are for all contributors.
* Create an issue first to suggest an improvement or feature addition to TShock.
-* Active developers will then give a go/no go for implementation. This is scope related: if an issue is within the scope of TShock, it will be tagged 'pr-wanted.'
-* After 'pr-wanted' has been added, an issue should be considered workable in a pull request fashion.
-* If you, as a developer, want to claim an issue for a PR, as soon as possible start work and note that in both the original issue and the new PR. The 'pr-wanted' tag will remain but the active PR will become the center for discussion for your implementation.
+* Active developers will then give a go/no go for implementation. This is scope related: if an issue is within the scope of TShock, it will be tagged 'Contribution Wanted.'
+* After 'Contribution Wanted' has been added, an issue should be considered workable in a pull request fashion.
+* If you, as a developer, want to claim an issue for a PR, as soon as possible start work and note that in both the original issue and the new PR. The 'Contribution Wanted' tag will remain but the active PR will become the center for discussion for your implementation.
* If a TShock core developer takes an issue, they'll be assigned to the issue. If your issue was taken by a TShock developer and you were actively developing it in a PR, you should _make it clear as soon as possible that a process error has been made_ so that the your development resources and our development resources aren't wasted.
* Please send a pull request with at least a sentence description and something meaningful as the title, not just the issue number you're fixing.
-_The pr-wanted tag indicates an issue should be implemented. If an issue has a developer assigned, it indicates that they're working on it. When in doubt, ask where an issue is before starting work (so you don't waste time)!_
+_The tag indicates an issue should be implemented. If an issue has a developer assigned, it indicates that they're working on it. When in doubt, ask where an issue is before starting work (so you don't waste time)!_
Even if you have write access to the repository, follow [Github flow](https://guides.github.com/introduction/flow/) when sending commits. Don't send commits directly to either ```master``` or ```general-devel``` unless those commits modify either the deploy scripts or non-code components. If it compiles, follow Github Flow.
@@ -56,6 +44,7 @@ Required:
- When using static methods on primitives, use the CLR type. E.g. ```String.Format``` instead of ```string.Format```.
- Always use properties, not public fields.
- Document deprecations and fail compilation if they're included with ```[Obsolete("Use blah instead of blahx...", true)]```.
+- Update the `CHANGELOG.md` file.
### Dev Team Guidelines
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..32f48df7
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+custom: https://www.givedirectly.org/
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..5c4cc69f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,23 @@
+!!!!!! PLEASE FILL IN THE TEMPLATE BELOW THANK YOU VERY MUCH !!!!!!
+
+If you don't need help, delete this template and just post an issue (feature requests and discussions and the like).
+
+* TShock version:
+* TShock build number (if known):
+
+#### Reproduction steps (if applicable)?
+
+1. Some step
+2. Some other step
+3. Some bigger step
+4. This is the problem
+
+#### Any stack traces or error messages (if known)?
+
+```
+PUT SUPER LONG ERROR MESSAGES IN THE TICK MARKS
+```
+
+#### Any screenshots?
+
+!!!!!! PLEASE FILL IN THE TEMPLATE ABOVE THANK YOU VERY MUCH !!!!!!
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..5f7e8ece
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,14 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Get help with TShock
+ url: https://github.com/Pryaxis/TShock/discussions/new
+ about: If you aren't sure if you have a bug, or if you're having any problems with TShock, post a question here.
+ - name: Chat with people on Discord
+ url: https://discord.gg/Cav9nYX
+ about: If you wanna chill or brainstorm, here's the place to do that.
+ - name: Don't talk about TShock
+ url: https://t.me/refugeecamp
+ about: Where the TShock developers chat about nothing related to TShock.
+ - name: 在QQ群提问
+ url: https://jq.qq.com/?_wv=1027&k=5GJZCe4
+ about: 如果你不会英语或想使用中文进行交流,可以加入我们的官方QQ群
diff --git a/.github/ISSUE_TEMPLATE/defect-report.md b/.github/ISSUE_TEMPLATE/defect-report.md
new file mode 100644
index 00000000..0ebbbf14
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/defect-report.md
@@ -0,0 +1,32 @@
+---
+name: Defect report
+about: Report a software defect in TShock (a problem you know is our fault)
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
+
+* TShock version:
+* TShock build number (if known):
+
+#### Reproduction steps (if applicable)?
+
+1. Some step
+2. Some other step
+3. Some bigger step
+4. This is the problem
+
+#### Any stack traces or error messages (if known)?
+
+```
+PUT SUPER LONG ERROR MESSAGES IN THE TICK MARKS
+```
+
+#### Any screenshots?
+
+#### Any log messages from files that end in `.log`?
+
+#### What plugins and what versions of those plugins are you running?
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..28865d5c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest a change to be made to TShock
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..4cf87d10
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,19 @@
+
+
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
+?????? HAVE YOU UPDATED THE CHANGELOG? ??????
\ No newline at end of file
diff --git a/.github/config.yml b/.github/config.yml
new file mode 100644
index 00000000..34b33fce
--- /dev/null
+++ b/.github/config.yml
@@ -0,0 +1,10 @@
+updateDocsComment: >
+ Thanks for the pull request! TShock's maintainers would like you to go ahead and give yourself credit by updating the `CHANGELOG.md` as soon as you can. Your pull request will likely not be accepted without this. This both helps us document changes to TShock, as well as give you credit for your work. You deserve it, so go take credit! :sparkles:
+
+updateDocsWhiteList:
+ - bug
+ - chore
+
+updateDocsTargetFiles:
+ - README
+ - CHANGELOG.md
diff --git a/.github/no-response.yml b/.github/no-response.yml
new file mode 100644
index 00000000..e85de284
--- /dev/null
+++ b/.github/no-response.yml
@@ -0,0 +1,13 @@
+# Configuration for probot-no-response - https://github.com/probot/no-response
+
+# Number of days of inactivity before an Issue is closed for lack of response
+daysUntilClose: 7
+# Label requiring a response
+responseRequiredLabel: followup-required
+# Comment to post when closing an Issue for lack of response. Set to `false` to disable
+closeComment: >
+ This issue has been automatically closed because there has been no response
+ to our request for more information from the original author. With only the
+ information that is currently in the issue, we don't have enough information
+ to take action. Please reach out if you have or find the answers we need so
+ that we can investigate further.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..8c96f6cb
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,63 @@
+name: Build Server
+
+on: [push]
+
+jobs:
+ build:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v1
+ with:
+ submodules: recursive
+ - name: Install nuget
+ run: choco install nuget.commandline
+ - name: OTAPI Debug
+ shell: cmd
+ run: |
+ nuget restore .\TerrariaServerAPI\TShock.4.OTAPI.sln
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe" .\TerrariaServerAPI\TShock.4.OTAPI.sln /p:Configuration=Debug
+ cd .\TerrariaServerAPI\TShock.Modifications.Bootstrapper\bin\Debug
+ TShock.Modifications.Bootstrapper.exe
+ - name: OTAPI Release
+ shell: cmd
+ run: |
+ nuget restore .\TerrariaServerAPI\TShock.4.OTAPI.sln
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe" .\TerrariaServerAPI\TShock.4.OTAPI.sln /p:Configuration=Release
+ cd .\TerrariaServerAPI\TShock.Modifications.Bootstrapper\bin\Release
+ TShock.Modifications.Bootstrapper.exe
+ - name: TerrariaServerAPI Debug
+ shell: cmd
+ run: |
+ cd .\TerrariaServerAPI
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe" .\TerrariaServerAPI\TerrariaServerAPI.csproj /p:Configuration=Debug
+ - name: TShock Debug
+ shell: cmd
+ run: |
+ nuget restore TShock.sln
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe" .\TShockAPI\TShockAPI.csproj /p:Configuration=Debug
+ - name: TerrariaServerAPI Release
+ shell: cmd
+ run: |
+ cd .\TerrariaServerAPI
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe" .\TerrariaServerAPI\TerrariaServerAPI.csproj /p:Configuration=Release
+ - name: TShock Release
+ shell: cmd
+ run: |
+ nuget restore TShock.sln
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\msbuild.exe" .\TShockAPI\TShockAPI.csproj /p:Configuration=Release
+ - uses: actions/upload-artifact@master
+ with:
+ name: Experimental TShock (not debug)
+ path: TShockAPI\bin\Release
+ - uses: actions/upload-artifact@master
+ with:
+ name: Experimental TShock (debug)
+ path: TShockAPI\bin\Debug
+ - uses: actions/upload-artifact@master
+ with:
+ name: Experimental (debug) OTAPI Bootstrapper
+ path: .\TerrariaServerAPI\TShock.Modifications.Bootstrapper\bin\Debug\TShock.Modifications.Bootstrapper.exe
+ - uses: actions/upload-artifact@master
+ with:
+ name: Experimental (not debug) OTAPI Bootstrapper
+ path: .\TerrariaServerAPI\TShock.Modifications.Bootstrapper\bin\Release\TShock.Modifications.Bootstrapper.exe
diff --git a/.gitignore b/.gitignore
index c7755a6d..5ac4b6aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,7 @@ Thumbs.db
*.csproj.user
*/_ReSharper*/*
*.user
+.vs/*
#Template Bat file#
###################
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 5e74512c..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-language: csharp
-solution: "./TShockAPI/TShockAPI.csproj"
-sudo: false
-install:
-- nuget restore
-script: python ./scripts/create_release.py
-notifications:
- slack:
- secure: O4Nibe2fdaUa2ZxuETUg6WEoQKvNM2CotnfaIVgm3fjfe61dfE1P+EgTpbwDG8646jSmpTqMDw8Z6I/WJwGTlXV/ZQsbwu63Cps4MgOTvPHZ0Lsye5azySlJZs1iI4ItYSj2czXfcnJ+qAl1SOOkXJrjB5uyTMWtDpCrSCFB3MA=
- webhooks:
- secure: dbTvcMtts5hSgV3DvlHPh36LTOvSPzQbVRUrgN9j0M/MlCm1QlBVt1vDLzN8VbkSYXiJYVWGMDpSHApL6SBu7sEQaXeC4zZyTMX76PeKw5a5xh0mIdDyg8Ls9WVA+QDVGes5DA1CZWbVRBDto3U0c+Ob8iza3o01sEFWpm7wQg4=
\ No newline at end of file
diff --git a/README.md b/README.md
index 9fa0710d..dfa0672c 100644
--- a/README.md
+++ b/README.md
@@ -1,46 +1,269 @@
-
-
+
+
+
+
+
+
+
+
+
+
+ 查看中文版
-TShock is a server modification for Terraria, written in C#, and based upon the [Terraria Server API](https://github.com/NyxStudios/TerrariaAPI-Server). It uses JSON for configuration management, and offers several features not present in the Terraria Server normally.
+TShock is a toolbox for Terraria servers and communities. That toolbox is jam packed with anti-cheat tools, server-side characters, groups, permissions, item bans, tons of commands, and limitless potential. It's one of a kind.
-## :star: Quick Start
+**We are currently updating for Terraria 1.4.0.1 (curRelease = 225). For updates, check our [twitter, @Pryaxis](https://twitter.com/Pryaxis).**
-https://tshock.readme.io/docs/getting-started
+* Download: [Stable](https://github.com/TShock/TShock/releases) or [Experimental](#experimental-downloads).
+* Read [the documentation](https://tshock.readme.io/) to quickly get up to speed.
+* Join [Discord](https://discord.gg/Cav9nYX).
+* Join [Telegram](https://t.me/pryaxis).
+* Download [other plugins](https://tshock.co/xf/index.php?resources/) to supercharge your server.
-## Features
+----
-* MySQL support
-* Permissions
-* Multiple administrators
-* Anti-cheat
-* User registration
-* Reserved slots
-* User punishment (kicking, banning, muting)
-* Server side characters
-* JSON based configuration management
+## Table of Contents
-## Community
+ * [New to TShock?](#new-to-tshock)
+ * [Experimental Downloads](#experimental-downloads)
+ * [Developer's Guide](#developers-guide)
+ * [Background](#background)
+ * [Building](#building)
+ * [On Windows](#on-windows)
+ * [The Terraria Server API](#the-terraria-server-api)
+ * [TShock](#tshock)
+ * [On macOS](#on-macos)
+ * [On Linux](#on-linux)
+ * [On Unix](#on-unix)
+ * [The Terraria Server API](#the-terraria-server-api-1)
+ * [TShock](#tshock-1)
+ * [Working with Terraria](#working-with-terraria)
+ * [Code of Conduct](#code-of-conduct)
-Feeling like helping out? Want to find an awesome server? Some awesome plugins?
+## New to TShock?
-* [Website & Forums](https://tshock.co/xf/)
-* [Contribute to our docs on readme.io](https://tshock.readme.io/)
-* [Join our Discord chat (supports Android, iOS, Web, Mac, and Windows)](https://discord.gg/XUJdH58)
+_These instructions assume Windows. If you're setting up on Linux or macOS, please refer to [the in-depth guide](https://tshock.readme.io/docs/getting-started) (and don't forget to install the *latest version* of `mono-complete` on Linux)._
-### Code of Conduct
+1. Download [the latest stable version](https://github.com/TShock/TShock/releases) and `unzip` the folder using your favorite unzip tool. Make sure that all of the files in the zip get into one folder. This is where your server will be stored. The file structure looks like this:
+
+
+ GeoIP.dat
+ Newtonsoft.Json.dll
+ OTAPI.dll
+ ServerPlugins\
+ |------BCrypt.Net.dll
+ |------HttpServer.dll
+ |------Mono.Data.Sqlite.dll
+ |------MySql.Data.dll
+ |------TShockAPI.dll
+ TerrariaServer.exe
+ sqlite3.dll
+
+
+1. Start `TerrariaServer.exe` and TShock will boot. Answer the startup questions, and you should be ready to roll. In the background, TShock made some folders for you. We'll come back to those later.
+
+1. Startup Terraria. Connect to a `multiplayer` server via IP and enter `localhost` if you're doing this on your local computer. If you're doing it on another computer, you need its IP address.
+
+1. Look at the server console for the _setup code_. Type `/setup [code]` (example: `/setup 12345`), then a space, then the code you see in the console in your game chat. Instead of chatting, you'll run a command on the server. This one makes you temporary admin. All commands are prefixed with `/` or `!` (to make them silent).
+
+1. Use the in-game command `/user add [account name] [password] owner` (example: `/user add shank lovely-ashes owner`) to create an account. This gives you owner rights on your server, which you can configure more to your liking later.
+
+1. Login to your newly created account with `/login [account name] [password]` (example: `/login shank lovely-ashes`). You should see a login success message.
+
+1. Turn off the setup system with `/setup` and your server is setup for initial use. TShock also created several files inside a new `tshock` folder. These files include `config.json` (our big configuration file), `sscconfig.json` (the server side characters configuration file), and `tshock.sqlite`. Don't lose your `tshock.sqlite` or you'll have to re-setup TShock.
+
+1. You can now [customize your configuration](https://tshock.readme.io/docs/config-settings), build groups, ban items, and install more plugins.
+
+## Experimental Downloads
+
+To download experimental versions of TShock, you have two real options: AppVeyor builds or GitHub builds. You can also get archived Travis CI builds. Fair warning though: experimental versions of TShock are point-in-time releases that are not technically supported by us. If you have to report an issue, please make it clear which commit or branch you downloaded your build from, which service, and the build number if applicable.
+
+On [AppVeyor](https://ci.appveyor.com/project/hakusaro/tshock/), click on history, find the build you want, click on the commit message, and then click on the artifacts tab. You can download either the debug or the release build. AppVeyor only keeps builds back 6 months though.
+
+On [GitHub](https://github.com/Pryaxis/TShock/), click on the actions tab, then click on "build server" on the commit or branch you want. If it was successful, you can download either the experimental release or debug artifacts.
+
+For old builds from Travis CI, you can still get them (for now) from us directly, on [our Travis CI artifact mirror](https://travis.tshock.co/). Please note that these builds should be considered legacy and for archival purposes only. If you need them in the long term, please raise an issue explaining why before they are removed.
+
+## Developer's Guide
+
+Whether you want to contribute to TShock by sending a pull request, customize it to suit your own elvish desires, or want to build your own plugin, this is the best starting point. By the end of this, you'll be able to build TShock from source, start to finish. More than that, though, you'll know how to start on the path of becoming an expert TShock developer.
+
+But first, you need some background.
+
+### Background
+
+Terraria is a C# application written on the .NET framework using the XNA game framework. TShock is a mod for Terraria's server, which is also written in C# on the .NET framework. Some might compare TShock to hMod in the Minecraft world (the precursor to Bukkit and its server, CraftBukkit). This is a good comparison to make in how the underlying build process works. When the project started, TShock was injected directly into the decompiled source code for Terraria. Unlike Minecraft, Terraria is not obfuscated, which means that many variable names and inner workings are sanely-named out of the box. Now, TShock uses advanced techniques to operate.
+
+TShock is, first and foremost, a plugin written for the server variant of the Terraria API, an unofficial construct originally built by `bladecoding`. `TShock` has been colloquially used to refer to both the plugin as well as the server and plugin together. Similarly, the Terraria API's client version was abandoned long ago, and development of the `Server` API led to the abbreviation `TSAPI`, for `Terraria Server API`. The plugin `TShock` is executed by the [Terraria Server API](https://github.com/Pryaxis/TerrariaAPI-Server), which is in turn bound to the `Open Terraria API`, more commonly `OTAPI`. The [Open Terraria API](https://github.com/DeathCradle/Open-Terraria-API) is maintained by [DeathCradle](https://github.com/DeathCradle).
+
+Now, the way that `TShock` runs on `TSAPI` through `OTAPI` can be summarized as the following:
+
+1. The Open Terraria API deeply integrates with Terraria by modifying the official server's binary directly. This is done through rewriting the Terraria bytecode, the [CIL code](https://en.wikipedia.org/wiki/Common_Intermediate_Language), using a patching tool designed by DeathCradle and tools from the Mono project. For `TSAPI`, additional modifications are done to support TSAPI specific features. This done through the `TShock Mintaka Patcher`.
+2. The `Terraria Server API` uses hooks provided by `OTAPI` to provide higher level hooks as well as legacy hooks for existing TSAPI applications.
+3. `TShock` is executed by `TSAPI`, uses hooks provided by both `TSAPI` and `OTAPI`, and provides even higher level hooks and support tools to other `TSAPI` plugins.
+
+With all of this in mind, the primary goal when compiling TShock is to remember that only the second and third layers are required to be interacted with. The first layer, `OTAPI`, is provided pre-compiled through NuGet. The second layer, `TSAPI`, is provided in the `TShock` repository through a git submodule. Its primary home is the [Terraria Server API repository](https://github.com/Pryaxis/TerrariaAPI-Server).
+
+Let's get started.
+
+### Building
+
+You need to get the source code. Using git, [clone this repository](https://help.github.com/articles/cloning-a-repository/).
+
+The next set of instructions are the technical details to setup both the Terraria Server API and TShock. More importantly, the Terraria API steps here are written under the assumption that you are building TShock primarily. Before you start, you need to **initialize the git submodules** and then **update them**. You need to use the following commands to do this.
+
+ $ git submodule init
+ $ git submodule update
+
+If you're using [GitHub Desktop](https://desktop.github.com), you need to perform additional steps. After cloning the TShock repository, go to the `Repository` menu and select `Open in Command Prompt`. If you don't have Git (not GitHub Desktop) installed, you can follow the prompts to to install Git for your command line. Once Git is installed, use this same process to get to the command prompt. Then, run the above commands.
+
+#### On Windows
+
+On Windows, you need to install [Visual Studio Community Edition](https://www.visualstudio.com/downloads/) or a better (more expensive) version of Visual Studio.
+
+##### The Terraria Server API
+
+1. Open the `TShock.4.OTAPI.sln` solution in the `TerrariaServerAPI` folder.
+
+1. Set the `TShock.Modifications.Bootstrapper` project as the StartUp project.
+
+1. Build the solution in either debug or release mode, depending on your preference. NuGet will automatically fetch the appropriate packages as a result of its magical powers.
+
+1. Hit the "Start" button in Visual Studio to run the `TShock Mintaka Bootstrapper`.
+
+1. Watch the output window and make sure that a non-zero number of modifications ran. When it completes, you have successfully bootstrapped `TShock Mintaka`.
+
+1. Set the `TerrariaServerAPI` project as the StartUp project.
+
+1. Build the solution in either debug or release mode, depending on your preference.
+
+1. Close `TShock.4.OTAPI.sln` in Visual Studio.
+
+You need to re-run the patcher any time `OTAPI` updates. You need to rebuild `TerrariaServerAPI` any time that the submodule in `TShock` gets changed, if you're doing this from inside the TShock repo. You also need to update the submodules (`git submodule update`) if they're out of date on a pull too.
+
+##### TShock
+
+1. Open the `TShock.sln` solution in the root of the repository.
+
+1. Build the solution. It should correctly download NuGet packages automatically and build against the aforementioned `TerrariaServerAPI` project you just built.
+
+#### On macOS
+
+1. Install [Homebrew](https://brew.sh) if you haven't already.
+
+1. Install mono:
+
+ $ brew install mono
+
+1. Verify that mono is available:
+
+ $ mono --version
+
+ Mono JIT compiler version 5.0.1.1 (2017-02/5077205 Sun Sep 17 18:29:46 BST 2017)
+ ...
+
+1. Proceed to the [unix build steps](#unix-build-steps) to continue.
+
+#### On Linux
+
+1. **DO NOT** just install mono from your package manager unless told to do so. If you do and it's out of date, you probably won't be able to successfully develop for TShock.
+
+1. Follow the [official install instructions for mono](http://www.mono-project.com/download/). **DO** install `mono-complete` or you're missing components.
+
+1. Proceed to the [unix build steps](#unix-build-steps) to continue.
+
+#### On Unix
+
+1. You need to get NuGet. Download the latest `nuget.exe` from [NuGet](https://www.nuget.org/downloads).
+
+1. Make a `~/bin` folder if you don't have one. Then, put `nuget.exe` inside it.
+
+ $ mkdir ~/bin/
+ $ cp ~/downloads/nuget.exe ~/bin/
+
+1. Set an environment variable to store if you plan to build in debug or release.
+
+ $ export BUILD_MODE=Debug
+
+ or
+
+ $ export BUILD_MODE=Release
+
+
+##### The Terraria Server API
+
+1. Perform a NuGet restore in the directory above `TerrariaServerAPI`.
+
+ $ mono ~/bin/nuget.exe restore ./TerrariaServerAPI/
+
+1. Build the `TShock.4.OTAPI.sln` solution the configuration you chose:
+
+ $ xbuild ./TerrariaServerAPI/TShock.4.OTAPI.sln /p:Configuration=$BUILD_MODE
+
+1. Run the `TShock Mintaka Bootstrapper` with the TShock modifications. If you don't use `/bin/bash` as your primary shell, you might want to temporarily switch to it, or the bootstrapper may fail.
+
+ $ cd ./TerrariaServerAPI/TShock.Modifications.Bootstrapper/bin/$BUILD_MODE/
+ $ mono TShock.Modifications.Bootstrapper.exe -in=OTAPI.dll \
+ -mod=../../../TShock.Modifications.**/bin/$BUILD_MODE/TShock.Modifications.*.dll \
+ -o=Output/OTAPI.dll
+
+1. Verify that non-zero modifications ran successfully. Then, build the Terraria Server API executable.
+
+ $ cd ./../../../
+ $ xbuild ./TerrariaServerAPI/TerrariaServerAPI/TerrariaServerAPI.csproj \
+ /p:Configuration=$BUILD_MODE
+
+You need to re-run the patcher any time `OTAPI` updates. You need to rebuild `TerrariaServerAPI` any time that the submodule in `TShock` gets changed, if you're doing this from inside the TShock repo. You also need to update the submodules (`git submodule update`) if they're out of date on a pull too.
+
+##### TShock
+
+1. Perform a NuGet restore in `TShockAPI` folder that contains `TShockAPI.sln`.
+
+ $ mono ~/bin/nuget.exe restore
+
+1. Build TShock in the `BUILD_MODE` you set earlier.
+
+ $ xbuild ./TShockAPI.sln /p:Configuration=$BUILD_MODE
+
+You're done!
+
+### Working with Terraria
+
+Working with Terraria in TShock and in other Terraria Server API plugins is different from most other APIs. Due to the nature of how OTAPI works, you have direct access to all public fields in the `Terraria` namespace. This means that you can access Terraria member methods directly. TShock and other plugins do this quite often, mostly to modify the game world, send data, and receive data. Calls to `Main` are one such example of direct access to Terraria. This is the equivalent to `net.minecraft.server` (NMS) calls in CraftBukkit.
+
+You might find yourself wondering where these fields are. Pryaxis provides the decompiled [Sources](https://github.com/pryaxis/Sources) to Terraria's server, updated with each release. Note that these decompiled servers do not re-compile. The process of fixing the decompiles has proven to be nearly impossible in a reasonable timeframe with the modern Terraria Server.
+
+Finally, you may be interested in developing other Terraria Server API plugins. The [TShockResources](https://github.com/TShockResources) organization has several plugins you can look at and build on. TShock is itself a plugin, and most plugins are open source. This gives you ample room to figure out where to go next.
+
+Need help? Join us on [Telegram](https://t.me/pryaxis) or [Discord](https://discord.gg/Cav9nYX).
+
+## Code of Conduct
> By participating in the TShock for Terraria community, all members will adhere to maintaining decorum with respect to all humans, in and out of the community. Members will not engage in discussion that inappropriately disparages or marginalizes any group of people or any individual. Members will not attempt to further or advance an agenda to the point of being overbearing or close minded (such as through spreading FUD). Members will not abuse services provided to them and will follow the guidance of community leaders on a situational basis about what abuse consists of. Members will adhere to United States and international law. If members notice a violation of this code of conduct, they will not engage but will instead contact the leadership team on either the forums or Discord.
> Do not attempt to circumvent or bypass the code of conduct by using clever logic or reasoning (e.g., insulting Facepunch members, because they weren't directly mentioned here).
-Please see the contributing file before sending pull requests.
+## Contributors
-## Download
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
-* [Github Releases](https://github.com/TShock/TShock/releases)
-* [Development Builds](https://travis.tshock.co/)
-* [Plugins](https://tshock.co/xf/index.php?resources/)
-* [Very, very old versions of TShock](https://github.com/TShock/TShock/downloads)
+
+
+
+
+
+
+
+
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
diff --git a/README_cn.md b/README_cn.md
new file mode 100644
index 00000000..ff900a06
--- /dev/null
+++ b/README_cn.md
@@ -0,0 +1,86 @@
+
+
+
+
+
+TShock是为泰拉瑞亚设计的多功能服务端。它拥有反作弊/强制开荒/用户组/权限管理/物品封禁/大量指令和无限的扩展性。
+
+* 下载: [稳定版](https://github.com/TShock/TShock/releases) or [测试版](#experimental-downloads)
+* 使用方法请阅读 [文档](https://tshock.readme.io/)
+* 你可以加入 [我们的官方QQ群](https://jq.qq.com/?_wv=1027&k=5GJZCe4) 交流
+* 也可以加入 [我们的Discord服务器](https://discord.gg/Cav9nYX) 提问
+* 如果想要深度技术支持,可以加入 [我们的Telegram群](https://t.me/pryaxis)
+* 你可以在 [这里](https://tshock.co/xf/index.php?resources/) 下载插件增强你的服务器
+
+----
+
+## 内容索引
+
+ * [第一次使用TShock?](#new-to-tshock)
+ * [下载测试版](#experimental-downloads)
+
+## 第一次使用TShock?
+
+_这篇指南基于Windows。如果你在使用Unix或者Linux,请参考 [深度指南](https://tshock.readme.io/docs/getting-started) (不要忘记在你的Linux系统上安装 **最新版** 的 `mono-complete` )._
+
+1. 下载 [最新稳定版](https://github.com/TShock/TShock/releases) 然后解压。解压后文件所在的文件夹就是你服务器的工作目录。文件夹结构大致如下:
+
+
+ GeoIP.dat
+ Newtonsoft.Json.dll
+ OTAPI.dll
+ ServerPlugins\
+ |------BCrypt.Net.dll
+ |------HttpServer.dll
+ |------Mono.Data.Sqlite.dll
+ |------MySql.Data.dll
+ |------TShockAPI.dll
+ TerrariaServer.exe
+ sqlite3.dll
+
+
+1. 运行 `TerrariaServer.exe` ,TShock就会启动了。 TShock会自动创建一些文件夹,具体用途稍后讨论。
+
+1. 启动你的游戏,选择 `多人模式` 并选择 `通过IP加入`。输入 `localhost` 或者 `127.0.0.1` 如果你的服务器和游戏运行在同一台电脑上。如果你在用其他设备开服,你需要输入它的IP地址。
+
+1. 查看服务器控制台上的 _验证码_。在游戏里打开聊天窗口输入 `/setup [验证码]` (举个例子: `/setup 12345`)然后回车。这条指令可以让你成为临时管理。 所有指令都需要以 `/` 或者 `!` 开头。
+
+1. 在游戏里输入指令 `/user add [账号名] [密码] owner` (举个例子: `/user add 鱼鱼 真可爱 owner`) 来创建一个账号并且给这个账号服主权限。
+
+1. 登录你刚刚创建的账号,方法是输入指令 `/login [账号名] [密码]` (举个例子: `/login 鱼鱼 真可爱`) 然后你就会看到登录成功的提示。
+
+1. 输入指令 `/setup` 关闭初始化设置功能,因为你已经搞定了。TShock会在 `tshock` 文件夹内创建数个文件。包括 `config.json` (服务器配置文件), `sscconfig.json` (强制开荒配置文件) 和 `tshock.sqlite` (服务器数据库)。不要把 `tshock.sqlite` 搞丢了,不然就白折腾了。
+
+1. 现在你可以 [调整配置](https://tshock.readme.io/docs/config-settings) ,创建用户组,封禁物品或者安装插件了。
+
+## 下载测试版
+
+想下载测试版的TShock,你有两个选择:AppVeyor或者GitHub。你也可以获取Travis CI上的旧版本。注意: 测试版的TShock理论上不受我们的支持。如果你遇到问题需要发Issue,请提前声明你的版本信息。
+
+在 [AppVeyor](https://ci.appveyor.com/project/hakusaro/tshock/) 上,点击History,找到需要的版本并点击, 然后点击Artifacts就可以下载它的发布版或者调试版。AppVeyor只会保留半年内的版本。
+
+在 [GitHub项目](https://github.com/Pryaxis/TShock/) 页面里,点击 `Actions`,然后点击你想要的branch的 `build server` 就可以下载它的发布版或者调试版。
+
+关于Travis CI上的旧版本,现在还可以在 [我们的Travis CI产物镜像](https://travis.tshock.co/) 上获取。但是请注意这些旧版本已经不再受支持。
+
+## Contributors
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+
+
+
+
+
+
+
+
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
diff --git a/TShockAPI/BackupManager.cs b/TShockAPI/BackupManager.cs
index 752ad7ac..f624c746 100644
--- a/TShockAPI/BackupManager.cs
+++ b/TShockAPI/BackupManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs
new file mode 100644
index 00000000..f55c7aaa
--- /dev/null
+++ b/TShockAPI/Bouncer.cs
@@ -0,0 +1,1933 @@
+/*
+TShock, a server mod for Terraria
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Terraria.ID;
+using TShockAPI.DB;
+using TShockAPI.Net;
+using Terraria;
+using Microsoft.Xna.Framework;
+using OTAPI.Tile;
+using TShockAPI.Localization;
+using static TShockAPI.GetDataHandlers;
+using TerrariaApi.Server;
+using Terraria.ObjectData;
+using Terraria.DataStructures;
+using Terraria.Localization;
+
+namespace TShockAPI
+{
+ /// Bouncer is the TShock anti-hack and anti-cheat system.
+ internal sealed class Bouncer
+ {
+ /// Constructor call initializes Bouncer and related functionality.
+ /// A new Bouncer.
+ internal Bouncer()
+ {
+ // Setup hooks
+ GetDataHandlers.GetSection += OnGetSection;
+ GetDataHandlers.PlayerUpdate += OnPlayerUpdate;
+ GetDataHandlers.TileEdit += OnTileEdit;
+ GetDataHandlers.SendTileSquare += OnSendTileSquare;
+ GetDataHandlers.ItemDrop += OnItemDrop;
+ GetDataHandlers.NewProjectile += OnNewProjectile;
+ GetDataHandlers.NPCStrike += OnNPCStrike;
+ GetDataHandlers.ProjectileKill += OnProjectileKill;
+ GetDataHandlers.ChestItemChange += OnChestItemChange;
+ GetDataHandlers.ChestOpen += OnChestOpen;
+ GetDataHandlers.PlaceChest += OnPlaceChest;
+ GetDataHandlers.PlayerZone += OnPlayerZone;
+ GetDataHandlers.PlayerAnimation += OnPlayerAnimation;
+ GetDataHandlers.LiquidSet += OnLiquidSet;
+ GetDataHandlers.PlayerBuff += OnPlayerBuff;
+ GetDataHandlers.NPCAddBuff += OnNPCAddBuff;
+ GetDataHandlers.NPCHome += OnUpdateNPCHome;
+ GetDataHandlers.HealOtherPlayer += OnHealOtherPlayer;
+ GetDataHandlers.PlaceObject += OnPlaceObject;
+ GetDataHandlers.PlaceTileEntity += OnPlaceTileEntity;
+ GetDataHandlers.PlaceItemFrame += OnPlaceItemFrame;
+ GetDataHandlers.PortalTeleport += OnPlayerPortalTeleport;
+ GetDataHandlers.GemLockToggle += OnGemLockToggle;
+ GetDataHandlers.MassWireOperation += OnMassWireOperation;
+ GetDataHandlers.PlayerDamage += OnPlayerDamage;
+ GetDataHandlers.KillMe += OnKillMe;
+ }
+
+ internal void OnGetSection(object sender, GetDataHandlers.GetSectionEventArgs args)
+ {
+ if (args.Player.RequestedSection)
+ {
+ args.Handled = true;
+ return;
+ }
+ args.Player.RequestedSection = true;
+
+ if (String.IsNullOrEmpty(args.Player.Name))
+ {
+ args.Player.Kick("Your client sent a blank character name.", true, true);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasPermission(Permissions.ignorestackhackdetection))
+ {
+ args.Player.IsDisabledForStackDetection = args.Player.HasHackedItemStacks(shouldWarnPlayer: true);
+ }
+ }
+
+ /// Handles disabling enforcement and minor anti-exploit stuff
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnPlayerUpdate(object sender, GetDataHandlers.PlayerUpdateEventArgs args)
+ {
+ byte plr = args.PlayerId;
+ BitsByte control = args.Control;
+ BitsByte pulley = args.Pulley;
+ byte item = args.Item;
+ var pos = args.Position;
+ var vel = args.Velocity;
+
+ if (pos.X < 0 || pos.Y < 0 || pos.X >= Main.maxTilesX * 16 - 16 || pos.Y >= Main.maxTilesY * 16 - 16)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (item < 0 || item >= args.Player.TPlayer.inventory.Length)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.LastNetPosition == Vector2.Zero)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!pos.Equals(args.Player.LastNetPosition))
+ {
+ float distance = Vector2.Distance(new Vector2(pos.X / 16f, pos.Y / 16f),
+ new Vector2(args.Player.LastNetPosition.X / 16f, args.Player.LastNetPosition.Y / 16f));
+
+ if (args.Player.IsBeingDisabled())
+ {
+ // If the player has moved outside the disabled zone...
+ if (distance > TShock.Config.MaxRangeForDisabled)
+ {
+ // We need to tell them they were disabled and why, then revert the change.
+ if (args.Player.IsDisabledForStackDetection)
+ {
+ args.Player.SendErrorMessage("Disabled. You went too far with hacked item stacks.");
+ }
+ else if (args.Player.IsDisabledForBannedWearable)
+ {
+ args.Player.SendErrorMessage("Disabled. You went too far with banned armor.");
+ }
+ else if (args.Player.IsDisabledForSSC)
+ {
+ args.Player.SendErrorMessage("Disabled. You need to {0}login to load your saved data.", TShock.Config.CommandSpecifier);
+ }
+ else if (TShock.Config.RequireLogin && !args.Player.IsLoggedIn)
+ {
+ args.Player.SendErrorMessage("Account needed! Please {0}register or {0}login to play!", TShock.Config.CommandSpecifier);
+ }
+ else if (args.Player.IsDisabledPendingTrashRemoval)
+ {
+ args.Player.SendErrorMessage("You need to rejoin to ensure your trash can is cleared!");
+ }
+
+ // ??
+ var lastTileX = args.Player.LastNetPosition.X;
+ var lastTileY = args.Player.LastNetPosition.Y - 48;
+ if (!args.Player.Teleport(lastTileX, lastTileY))
+ {
+ args.Player.Spawn();
+ }
+ args.Handled = true;
+ return;
+ }
+ args.Handled = true;
+ return;
+ }
+
+ // Corpses don't move
+ if (args.Player.Dead)
+ {
+ args.Handled = true;
+ return;
+ }
+ }
+
+ return;
+ }
+
+ /// Bouncer's TileEdit hook is used to revert malicious tile changes.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnTileEdit(object sender, GetDataHandlers.TileEditEventArgs args)
+ {
+ EditAction action = args.Action;
+ int tileX = args.X;
+ int tileY = args.Y;
+ short editData = args.EditData;
+ EditType type = args.editDetail;
+ byte style = args.Style;
+
+ try
+ {
+ if (editData < 0)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (!TShock.Utils.TilePlacementValid(tileX, tileY))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (action == EditAction.KillTile && Main.tile[tileX, tileY].type == TileID.MagicalIceBlock)
+ {
+ args.Handled = false;
+ return;
+ }
+
+ if (args.Player.Dead && TShock.Config.PreventDeadModification)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ Item selectedItem = args.Player.SelectedItem;
+ int lastKilledProj = args.Player.LastKilledProjectile;
+ ITile tile = Main.tile[tileX, tileY];
+
+ if (action == EditAction.PlaceTile)
+ {
+ if (TShock.TileBans.TileIsBanned(editData, args.Player))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Player.SendErrorMessage("You do not have permission to place this tile.");
+ args.Handled = true;
+ return;
+ }
+ }
+
+ if (action == EditAction.KillTile && !Main.tileCut[tile.type] && !breakableTiles.Contains(tile.type))
+ {
+ //TPlayer.mount.Type 8 => Drill Containment Unit.
+
+ // If the tile is an axe tile and they aren't selecting an axe, they're hacking.
+ if (Main.tileAxe[tile.type] && ((args.Player.TPlayer.mount.Type != 8 && selectedItem.axe == 0) && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+ // If the tile is a hammer tile and they aren't selecting a hammer, they're hacking.
+ else if (Main.tileHammer[tile.type] && ((args.Player.TPlayer.mount.Type != 8 && selectedItem.hammer == 0) && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+ // If the tile is a pickaxe tile and they aren't selecting a pickaxe, they're hacking.
+ // Item frames can be modified without pickaxe tile.
+ else if (tile.type != TileID.ItemFrame
+ && !Main.tileAxe[tile.type] && !Main.tileHammer[tile.type] && tile.wall == 0 && args.Player.TPlayer.mount.Type != 8 && selectedItem.pick == 0 && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+ }
+ else if (action == EditAction.KillWall)
+ {
+ // If they aren't selecting a hammer, they could be hacking.
+ if (selectedItem.hammer == 0 && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0 && selectedItem.createWall == 0)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+ }
+ else if (action == EditAction.PlaceTile && (projectileCreatesTile.ContainsKey(lastKilledProj) && editData == projectileCreatesTile[lastKilledProj]))
+ {
+ args.Player.LastKilledProjectile = 0;
+ }
+ else if (action == EditAction.PlaceTile || action == EditAction.PlaceWall)
+ {
+ if ((action == EditAction.PlaceTile && TShock.Config.PreventInvalidPlaceStyle) &&
+ (MaxPlaceStyles.ContainsKey(editData) && style > MaxPlaceStyles[editData]) &&
+ (ExtraneousPlaceStyles.ContainsKey(editData) && style > ExtraneousPlaceStyles[editData]))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ // If they aren't selecting the item which creates the tile or wall, they're hacking.
+ if (!(selectedItem.netID == ItemID.IceRod && editData == TileID.MagicalIceBlock) &&
+ (editData != (action == EditAction.PlaceTile ? selectedItem.createTile : selectedItem.createWall) &&
+ !(ropeCoilPlacements.ContainsKey(selectedItem.netID) && editData == ropeCoilPlacements[selectedItem.netID])))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ // Using the actuation accessory can lead to actuator hacking
+ if (TShock.Itembans.ItemIsBanned("Actuator", args.Player) && args.Player.TPlayer.autoActuator)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Player.SendErrorMessage("You do not have permission to place actuators.");
+ args.Handled = true;
+ return;
+ }
+ if (TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(selectedItem.netID), args.Player) || editData >= (action == EditAction.PlaceTile ? Main.maxTileSets : Main.maxWallTypes))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+ if (action == EditAction.PlaceTile && (editData == TileID.PiggyBank || editData == TileID.Safes) && Main.ServerSideCharacter)
+ {
+ args.Player.SendErrorMessage("You cannot place this tile because server side characters are enabled.");
+ args.Player.SendTileSquare(tileX, tileY, 3);
+ args.Handled = true;
+ return;
+ }
+ if (action == EditAction.PlaceTile && (editData == TileID.Containers || editData == TileID.Containers2))
+ {
+ if (TShock.Utils.HasWorldReachedMaxChests())
+ {
+ args.Player.SendErrorMessage("The world's chest limit has been reached - unable to place more.");
+ args.Player.SendTileSquare(tileX, tileY, 3);
+ args.Handled = true;
+ return;
+ }
+ if ((TShock.Utils.TilePlacementValid(tileX, tileY + 1) && Main.tile[tileX, tileY + 1].type == TileID.Boulder) ||
+ (TShock.Utils.TilePlacementValid(tileX + 1, tileY + 1) && Main.tile[tileX + 1, tileY + 1].type == TileID.Boulder))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 3);
+ args.Handled = true;
+ return;
+ }
+ }
+ }
+ else if (action == EditAction.PlaceWire || action == EditAction.PlaceWire2 || action == EditAction.PlaceWire3)
+ {
+ // If they aren't selecting a wrench, they're hacking.
+ // WireKite = The Grand Design
+ if (selectedItem.type != ItemID.Wrench
+ && selectedItem.type != ItemID.BlueWrench
+ && selectedItem.type != ItemID.GreenWrench
+ && selectedItem.type != ItemID.YellowWrench
+ && selectedItem.type != ItemID.MulticolorWrench
+ && selectedItem.type != ItemID.WireKite)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+ }
+ else if (action == EditAction.KillActuator || action == EditAction.KillWire ||
+ action == EditAction.KillWire2 || action == EditAction.KillWire3)
+ {
+ // If they aren't selecting the wire cutter, they're hacking.
+ if (selectedItem.type != ItemID.WireCutter
+ && selectedItem.type != ItemID.WireKite
+ && selectedItem.type != ItemID.MulticolorWrench)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+ }
+ else if (action == EditAction.PlaceActuator)
+ {
+ // If they aren't selecting the actuator and don't have the Presserator equipped, they're hacking.
+ if (selectedItem.type != ItemID.Actuator && !args.Player.TPlayer.autoActuator)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+ }
+ if (TShock.Config.AllowCutTilesAndBreakables && Main.tileCut[tile.type])
+ {
+ if (action == EditAction.KillWall)
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+ args.Handled = false;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasModifiedIceSuccessfully(tileX, tileY, editData, action)
+ && !args.Player.HasBuildPermission(tileX, tileY))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(tileX, tileY))
+ {
+ if (action == EditAction.PlaceTile && (editData == TileID.Rope || editData == TileID.SilkRope || editData == TileID.VineRope || editData == TileID.WebRope))
+ {
+ args.Handled = false;
+ return;
+ }
+
+ if (action == EditAction.KillTile || action == EditAction.KillWall && ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0)
+ {
+ args.Handled = false;
+ return;
+ }
+
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.TileKillThreshold >= TShock.Config.TileKillThreshold)
+ {
+ args.Player.Disable("Reached TileKill threshold.", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold)
+ {
+ args.Player.Disable("Reached TilePlace threshold.", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if ((action == EditAction.PlaceTile || action == EditAction.PlaceWall) && !args.Player.HasPermission(Permissions.ignoreplacetiledetection))
+ {
+ args.Player.TilePlaceThreshold++;
+ var coords = new Vector2(tileX, tileY);
+ lock (args.Player.TilesCreated)
+ if (!args.Player.TilesCreated.ContainsKey(coords))
+ args.Player.TilesCreated.Add(coords, Main.tile[tileX, tileY]);
+ }
+
+ if ((action == EditAction.KillTile || action == EditAction.KillTileNoItem || action == EditAction.KillWall) && Main.tileSolid[Main.tile[tileX, tileY].type] &&
+ !args.Player.HasPermission(Permissions.ignorekilltiledetection))
+ {
+ args.Player.TileKillThreshold++;
+ var coords = new Vector2(tileX, tileY);
+ lock (args.Player.TilesDestroyed)
+ if (!args.Player.TilesDestroyed.ContainsKey(coords))
+ args.Player.TilesDestroyed.Add(coords, Main.tile[tileX, tileY]);
+ }
+ args.Handled = false;
+ return;
+ }
+ catch
+ {
+ args.Player.SendTileSquare(tileX, tileY, 4);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Bouncer's SendTileSquare hook halts large scope world destruction.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnSendTileSquare(object sender, GetDataHandlers.SendTileSquareEventArgs args)
+ {
+ short size = args.Size;
+ int tileX = args.TileX;
+ int tileY = args.TileY;
+
+ if (args.Player.HasPermission(Permissions.allowclientsideworldedit))
+ {
+ args.Handled = false;
+ return;
+ }
+
+ // From White:
+ // IIRC it's because 5 means a 5x5 square which is normal for a tile square, and anything bigger is a non-vanilla tile modification attempt
+ if (size > 5)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.SendTileSquare(tileX, tileY, size);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendTileSquare(tileX, tileY, size);
+ args.Handled = true;
+ return;
+ }
+
+ try
+ {
+ var tiles = new NetTile[size, size];
+ for (int x = 0; x < size; x++)
+ {
+ for (int y = 0; y < size; y++)
+ {
+ tiles[x, y] = new NetTile(args.Data);
+ }
+ }
+
+ bool changed = false;
+ for (int x = 0; x < size; x++)
+ {
+ int realx = tileX + x;
+ if (realx < 0 || realx >= Main.maxTilesX)
+ continue;
+
+ for (int y = 0; y < size; y++)
+ {
+ int realy = tileY + y;
+ if (realy < 0 || realy >= Main.maxTilesY)
+ continue;
+
+ var tile = Main.tile[realx, realy];
+ var newtile = tiles[x, y];
+ if (!args.Player.HasBuildPermission(realx, realy) ||
+ !args.Player.IsInRange(realx, realy))
+ {
+ continue;
+ }
+
+ // Fixes the Flower Boots not creating flowers issue
+ if (size == 1 && args.Player.Accessories.Any(i => i.active && i.netID == ItemID.FlowerBoots))
+ {
+ if (Main.tile[realx, realy + 1].type == TileID.Grass && (newtile.Type == TileID.Plants || newtile.Type == TileID.Plants2))
+ {
+ args.Handled = false;
+ return;
+ }
+
+ if (Main.tile[realx, realy + 1].type == TileID.HallowedGrass && (newtile.Type == TileID.HallowedPlants || newtile.Type == TileID.HallowedPlants2))
+ {
+ args.Handled = false;
+ return;
+ }
+
+ if (Main.tile[realx, realy + 1].type == TileID.JungleGrass && newtile.Type == TileID.JunglePlants2)
+ {
+ args.Handled = false;
+ return;
+ }
+ }
+
+ // Junction Box
+ if (tile.type == TileID.WirePipe)
+ {
+ args.Handled = false;
+ return;
+ }
+
+ // Orientable tiles
+ if (tile.type == newtile.Type && orientableTiles.Contains(tile.type))
+ {
+ Main.tile[realx, realy].frameX = newtile.FrameX;
+ Main.tile[realx, realy].frameY = newtile.FrameY;
+ changed = true;
+ }
+
+ // Landmine
+ if (tile.type == TileID.LandMine && !newtile.Active)
+ {
+ Main.tile[realx, realy].active(false);
+ changed = true;
+ }
+
+ // Tile entities: sensors, item frames, training dummies
+ // here it handles all tile entities listed in `TileEntityID`
+ if ((newtile.Type == TileID.LogicSensor ||
+ newtile.Type == TileID.ItemFrame ||
+ newtile.Type == TileID.TargetDummy) &&
+ !Main.tile[realx, realy].active())
+ {
+ Main.tile[realx, realy].type = newtile.Type;
+ Main.tile[realx, realy].frameX = newtile.FrameX;
+ Main.tile[realx, realy].frameY = newtile.FrameY;
+ Main.tile[realx, realy].active(true);
+ changed = true;
+ }
+
+ if (tile.active() && newtile.Active && tile.type != newtile.Type)
+ {
+ // Grass <-> Grass
+ if ((TileID.Sets.Conversion.Grass[tile.type] && TileID.Sets.Conversion.Grass[newtile.Type]) ||
+ // Dirt <-> Dirt
+ ((tile.type == 0 || tile.type == 59) &&
+ (newtile.Type == 0 || newtile.Type == 59)) ||
+ // Ice <-> Ice
+ (TileID.Sets.Conversion.Ice[tile.type] && TileID.Sets.Conversion.Ice[newtile.Type]) ||
+ // Stone <-> Stone
+ ((TileID.Sets.Conversion.Stone[tile.type] || Main.tileMoss[tile.type]) &&
+ (TileID.Sets.Conversion.Stone[newtile.Type] || Main.tileMoss[newtile.Type])) ||
+ // Sand <-> Sand
+ (TileID.Sets.Conversion.Sand[tile.type] && TileID.Sets.Conversion.Sand[newtile.Type]) ||
+ // Sandstone <-> Sandstone
+ (TileID.Sets.Conversion.Sandstone[tile.type] && TileID.Sets.Conversion.Sandstone[newtile.Type]) ||
+ // Hardened Sand <-> Hardened Sand
+ (TileID.Sets.Conversion.HardenedSand[tile.type] && TileID.Sets.Conversion.HardenedSand[newtile.Type]))
+ {
+ Main.tile[realx, realy].type = newtile.Type;
+ changed = true;
+ }
+ }
+
+ // Stone wall <-> Stone wall
+ if (((tile.wall == 1 || tile.wall == 3 || tile.wall == 28 || tile.wall == 83) &&
+ (newtile.Wall == 1 || newtile.Wall == 3 || newtile.Wall == 28 || newtile.Wall == 83)) ||
+ // Leaf wall <-> Leaf wall
+ (((tile.wall >= 63 && tile.wall <= 70) || tile.wall == 81) &&
+ ((newtile.Wall >= 63 && newtile.Wall <= 70) || newtile.Wall == 81)))
+ {
+ Main.tile[realx, realy].wall = newtile.Wall;
+ changed = true;
+ }
+
+ if ((tile.type == TileID.TrapdoorClosed && (newtile.Type == TileID.TrapdoorOpen || !newtile.Active)) ||
+ (tile.type == TileID.TrapdoorOpen && (newtile.Type == TileID.TrapdoorClosed || !newtile.Active)) ||
+ (!tile.active() && newtile.Active && (newtile.Type == TileID.TrapdoorOpen || newtile.Type == TileID.TrapdoorClosed)))
+ {
+ Main.tile[realx, realy].type = newtile.Type;
+ Main.tile[realx, realy].frameX = newtile.FrameX;
+ Main.tile[realx, realy].frameY = newtile.FrameY;
+ Main.tile[realx, realy].active(newtile.Active);
+ changed = true;
+ }
+ }
+ }
+
+ if (changed)
+ {
+ TSPlayer.All.SendTileSquare(tileX, tileY, size + 1);
+ WorldGen.RangeFrame(tileX, tileY, tileX + size, tileY + size);
+ }
+ else
+ {
+ args.Player.SendTileSquare(tileX, tileY, size);
+ }
+ }
+ catch
+ {
+ args.Player.SendTileSquare(tileX, tileY, size);
+ }
+
+ args.Handled = true;
+ }
+
+ /// Registered when items fall to the ground to prevent cheating.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnItemDrop(object sender, GetDataHandlers.ItemDropEventArgs args)
+ {
+ short id = args.ID;
+ Vector2 pos = args.Position;
+ Vector2 vel = args.Velocity;
+ short stacks = args.Stacks;
+ short prefix = args.Prefix;
+ bool noDelay = args.NoDelay;
+ short type = args.Type;
+
+ // player is attempting to crash clients
+ if (type < -48 || type >= Main.maxItemTypes)
+ {
+ // Causes item duplications. Will be re added later if necessary
+ //args.Player.SendData(PacketTypes.ItemDrop, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ // make sure the prefix is a legit value
+ // Note: Not checking if prefix is less than 1 because if it is, this check
+ // will break item pickups on the client.
+ if (prefix > PrefixID.Count)
+ {
+ args.Player.SendData(PacketTypes.ItemDrop, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ //Item removed, let client do this to prevent item duplication
+ // client side (but only if it passed the range check) (i.e., return false)
+ if (type == 0)
+ {
+ if (!args.Player.IsInRange((int)(Main.item[id].position.X / 16f), (int)(Main.item[id].position.Y / 16f)))
+ {
+ // Causes item duplications. Will be re added if necessary
+ //args.Player.SendData(PacketTypes.ItemDrop, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ args.Handled = false;
+ return;
+ }
+
+ if (!args.Player.IsInRange((int)(pos.X / 16f), (int)(pos.Y / 16f)))
+ {
+ args.Player.SendData(PacketTypes.ItemDrop, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ // stop the client from changing the item type of a drop but
+ // only if the client isn't picking up the item
+ if (Main.item[id].active && Main.item[id].netID != type)
+ {
+ args.Player.SendData(PacketTypes.ItemDrop, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ Item item = new Item();
+ item.netDefaults(type);
+ if ((stacks > item.maxStack || stacks <= 0) || (TShock.Itembans.ItemIsBanned(EnglishLanguage.GetItemNameById(item.type), args.Player) && !args.Player.HasPermission(Permissions.allowdroppingbanneditems)))
+ {
+ args.Player.SendData(PacketTypes.ItemDrop, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ // TODO: Remove item ban part of this check
+ if ((Main.ServerSideCharacter) && (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond - args.Player.LoginMS < TShock.ServerSideCharacterConfig.LogonDiscardThreshold))
+ {
+ //Player is probably trying to sneak items onto the server in their hands!!!
+ TShock.Log.ConsoleInfo("Player {0} tried to sneak {1} onto the server!", args.Player.Name, item.Name);
+ args.Player.SendData(PacketTypes.ItemDrop, "", id);
+ args.Handled = true;
+ return;
+
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendData(PacketTypes.ItemDrop, "", id);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Bouncer's projectile trigger hook stops world damaging projectiles from destroying the world.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventArgs args)
+ {
+ short ident = args.Identity;
+ Vector2 pos = args.Position;
+ Vector2 vel = args.Velocity;
+ float knockback = args.Knockback;
+ short damage = args.Damage;
+ byte owner = args.Owner;
+ short type = args.Type;
+ int index = args.Index;
+
+ if (index > Main.maxProjectiles)
+ {
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ if (TShock.ProjectileBans.ProjectileIsBanned(type, args.Player))
+ {
+ args.Player.Disable(String.Format("Player does not have permission to create projectile {0}.", type), DisableFlags.WriteToLogAndConsole);
+ args.Player.SendErrorMessage("You do not have permission to create that projectile.");
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ if (damage > TShock.Config.MaxProjDamage && !args.Player.HasPermission(Permissions.ignoredamagecap))
+ {
+ args.Player.Disable(String.Format("Projectile damage is higher than {0}.", TShock.Config.MaxProjDamage), DisableFlags.WriteToLogAndConsole);
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ // Main.projHostile contains projectiles that can harm players
+ // without PvP enabled and belong to enemy mobs, so they shouldn't be
+ // possible for players to create. (Source: Ijwu, QuiCM)
+ if (Main.projHostile[type])
+ {
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ // Tombstones should never be permitted by players
+ if (type == ProjectileID.Tombstone)
+ {
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ if (!TShock.Config.IgnoreProjUpdate && !args.Player.HasPermission(Permissions.ignoreprojectiledetection))
+ {
+ if (type == ProjectileID.BlowupSmokeMoonlord
+ || type == ProjectileID.PhantasmalEye
+ || type == ProjectileID.CultistBossIceMist
+ || (type >= ProjectileID.MoonlordBullet && type <= ProjectileID.MoonlordTurretLaser)
+ || type == ProjectileID.DeathLaser || type == ProjectileID.Landmine
+ || type == ProjectileID.BulletDeadeye || type == ProjectileID.BoulderStaffOfEarth
+ || (type > ProjectileID.ConfettiMelee && type < ProjectileID.SpiritHeal)
+ || (type >= ProjectileID.FlamingWood && type <= ProjectileID.GreekFire3)
+ || (type >= ProjectileID.PineNeedleHostile && type <= ProjectileID.Spike)
+ || (type >= ProjectileID.MartianTurretBolt && type <= ProjectileID.RayGunnerLaser)
+ || type == ProjectileID.CultistBossLightningOrb)
+ {
+ TShock.Log.Debug("Certain projectiles have been ignored for cheat detection.");
+ }
+ else
+ {
+ args.Player.Disable(String.Format("Does not have projectile permission to update projectile. ({0})", type), DisableFlags.WriteToLogAndConsole);
+ args.Player.RemoveProjectile(ident, owner);
+ }
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.ProjectileThreshold >= TShock.Config.ProjectileThreshold)
+ {
+ args.Player.Disable("Reached projectile update threshold.", DisableFlags.WriteToLogAndConsole);
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.RemoveProjectile(ident, owner);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasPermission(Permissions.ignoreprojectiledetection))
+ {
+ if (type == ProjectileID.CrystalShard && TShock.Config.ProjIgnoreShrapnel) // Ignore crystal shards
+ {
+ TShock.Log.Debug("Ignoring shrapnel per config..");
+ }
+ else if (!Main.projectile[index].active)
+ {
+ args.Player.ProjectileThreshold++; // Creating new projectile
+ }
+ }
+
+ if ((type == ProjectileID.Bomb
+ || type == ProjectileID.Dynamite
+ || type == ProjectileID.StickyBomb
+ || type == ProjectileID.StickyDynamite))
+ {
+ // Denotes that the player has recently set a fuse - used for cheat detection.
+ args.Player.RecentFuse = 10;
+ }
+ }
+
+ /// Handles the NPC Strike event for Bouncer.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnNPCStrike(object sender, GetDataHandlers.NPCStrikeEventArgs args)
+ {
+ short id = args.ID;
+ byte direction = args.Direction;
+ short damage = args.Damage;
+ float knockback = args.Knockback;
+ byte crit = args.Critical;
+
+ if (Main.npc[id] == null)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (damage > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap))
+ {
+ if (TShock.Config.KickOnDamageThresholdBroken)
+ {
+ args.Player.Kick(string.Format("NPC damage exceeded {0}.", TShock.Config.MaxDamage));
+ args.Handled = true;
+ return;
+ }
+ else
+ {
+ args.Player.Disable(String.Format("NPC damage exceeded {0}.", TShock.Config.MaxDamage), DisableFlags.WriteToLogAndConsole);
+ }
+ args.Player.SendData(PacketTypes.NpcUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendData(PacketTypes.NpcUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (TShock.Config.RangeChecks &&
+ !args.Player.IsInRange((int)(Main.npc[id].position.X / 16f), (int)(Main.npc[id].position.Y / 16f), 128))
+ {
+ args.Player.SendData(PacketTypes.NpcUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.SendData(PacketTypes.NpcUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Handles ProjectileKill events for throttling and out of bounds projectiles.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnProjectileKill(object sender, GetDataHandlers.ProjectileKillEventArgs args)
+ {
+ if (args.ProjectileIndex < 0)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Handles when a chest item is changed.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnChestItemChange(object sender, GetDataHandlers.ChestItemEventArgs args)
+ {
+ short id = args.ID;
+ byte slot = args.Slot;
+ short stacks = args.Stacks;
+ byte prefix = args.Prefix;
+ short type = args.Type;
+
+ if (args.Player.TPlayer.chest != id)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendData(PacketTypes.ChestItem, "", id, slot);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasBuildPermission(Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(Main.chest[id].x, Main.chest[id].y))
+ {
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// The Bouncer handler for when chests are opened.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnChestOpen(object sender, GetDataHandlers.ChestOpenEventArgs args)
+ {
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(args.X, args.Y))
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasBuildPermission(args.X, args.Y) && TShock.Config.RegionProtectChests)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ int id = Chest.FindChest(args.X, args.Y);
+ args.Player.ActiveChest = id;
+ }
+
+ /// The place chest event that Bouncer hooks to prevent accidental damage.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnPlaceChest(object sender, GetDataHandlers.PlaceChestEventArgs args)
+ {
+ int tileX = args.TileX;
+ int tileY = args.TileY;
+ int flag = args.Flag;
+
+ if (!TShock.Utils.TilePlacementValid(tileX, tileY) || (args.Player.Dead && TShock.Config.PreventDeadModification))
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendTileSquare(tileX, tileY, 3);
+ args.Handled = true;
+ return;
+ }
+
+ if (flag != 0 && flag != 4 // if no container or container2 placement
+ && Main.tile[tileX, tileY].type != TileID.Containers
+ && Main.tile[tileX, tileY].type != TileID.Dressers
+ && Main.tile[tileX, tileY].type != TileID.Containers2
+ && (!TShock.Utils.HasWorldReachedMaxChests() && Main.tile[tileX, tileY].type != TileID.Dirt)) //Chest
+ {
+ args.Player.SendTileSquare(tileX, tileY, 3);
+ args.Handled = true;
+ return;
+ }
+
+ if (flag == 2) //place dresser
+ {
+ if ((TShock.Utils.TilePlacementValid(tileX, tileY + 1) && Main.tile[tileX, tileY + 1].type == TileID.Teleporter) ||
+ (TShock.Utils.TilePlacementValid(tileX + 1, tileY + 1) && Main.tile[tileX + 1, tileY + 1].type == TileID.Teleporter))
+ {
+ //Prevent a dresser from being placed on a teleporter, as this can cause client and server crashes.
+ args.Player.SendTileSquare(tileX, tileY, 3);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ if (!args.Player.HasBuildPermission(tileX, tileY))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 3);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(tileX, tileY))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 3);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Handles PlayerZone events for preventing spawning NPC maliciously.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnPlayerZone(object sender, GetDataHandlers.PlayerZoneEventArgs args)
+ {
+ if (args.Zone2[1] || args.Zone2[2] || args.Zone2[3] || args.Zone2[4])
+ {
+ bool hasSolarTower = false;
+ bool hasVortexTower = false;
+ bool hasNebulaTower = false;
+ bool hasStardustTower = false;
+
+ foreach (var npc in Main.npc)
+ {
+ if (npc.netID == NPCID.LunarTowerSolar)
+ hasSolarTower = true;
+ else if (npc.netID == NPCID.LunarTowerVortex)
+ hasVortexTower = true;
+ else if (npc.netID == NPCID.LunarTowerNebula)
+ hasNebulaTower = true;
+ else if (npc.netID == NPCID.LunarTowerStardust)
+ hasStardustTower = true;
+ }
+
+ if ((args.Zone2[1] && !hasSolarTower)
+ || (args.Zone2[2] && !hasVortexTower)
+ || (args.Zone2[3] && !hasNebulaTower)
+ || (args.Zone2[4] && !hasStardustTower)
+ )
+ {
+ args.Handled = true;
+ return;
+ }
+ }
+ }
+
+ /// Handles basic animation throttling for disabled players.
+ /// sender
+ /// args
+ internal void OnPlayerAnimation(object sender, GetDataHandlers.PlayerAnimationEventArgs args)
+ {
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Handles Bouncer's liquid set anti-cheat.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnLiquidSet(object sender, GetDataHandlers.LiquidSetEventArgs args)
+ {
+ int tileX = args.TileX;
+ int tileY = args.TileY;
+ byte amount = args.Amount;
+ byte type = args.Type;
+
+ if (!TShock.Utils.TilePlacementValid(tileX, tileY) || (args.Player.Dead && TShock.Config.PreventDeadModification))
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.TileLiquidThreshold >= TShock.Config.TileLiquidThreshold)
+ {
+ args.Player.Disable("Reached TileLiquid threshold.", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasPermission(Permissions.ignoreliquidsetdetection))
+ {
+ args.Player.TileLiquidThreshold++;
+ }
+
+ // Liquid anti-cheat
+ // Arguably the banned buckets bit should be in the item bans system
+ if (amount != 0)
+ {
+ int bucket = -1;
+ if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.EmptyBucket)
+ {
+ bucket = 0;
+ }
+ else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.WaterBucket)
+ {
+ bucket = 1;
+ }
+ else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.LavaBucket)
+ {
+ bucket = 2;
+ }
+ else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.HoneyBucket)
+ {
+ bucket = 3;
+ }
+ else if (args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.BottomlessBucket ||
+ args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type == ItemID.SuperAbsorbantSponge)
+ {
+ bucket = 4;
+ }
+
+ if (type == 1 && !(bucket == 2 || bucket == 0))
+ {
+ args.Player.SendErrorMessage("You do not have permission to perform this action.");
+ args.Player.Disable("Spreading lava without holding a lava bucket", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (type == 1 && TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player))
+ {
+ args.Player.SendErrorMessage("You do not have permission to perform this action.");
+ args.Player.Disable("Using banned lava bucket without permissions", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (type == 0 && !(bucket == 1 || bucket == 0 || bucket == 4))
+ {
+ args.Player.SendErrorMessage("You do not have permission to perform this action.");
+ args.Player.Disable("Spreading water without holding a water bucket", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (type == 0 && TShock.Itembans.ItemIsBanned("Water Bucket", args.Player))
+ {
+ args.Player.SendErrorMessage("You do not have permission to perform this action.");
+ args.Player.Disable("Using banned water bucket without permissions", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (type == 2 && !(bucket == 3 || bucket == 0))
+ {
+ args.Player.SendErrorMessage("You do not have permission to perform this action.");
+ args.Player.Disable("Spreading honey without holding a honey bucket", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (type == 2 && TShock.Itembans.ItemIsBanned("Honey Bucket", args.Player))
+ {
+ args.Player.SendErrorMessage("You do not have permission to perform this action.");
+ args.Player.Disable("Using banned honey bucket without permissions", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ if (!args.Player.HasBuildPermission(tileX, tileY))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(tileX, tileY, 16))
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.SendTileSquare(tileX, tileY, 1);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Handles Buff events.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnPlayerBuff(object sender, GetDataHandlers.PlayerBuffEventArgs args)
+ {
+ byte id = args.ID;
+ byte type = args.Type;
+ int time = args.Time;
+
+ if (TShock.Players[id] == null)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (id >= Main.maxPlayers)
+ {
+ args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (!TShock.Players[id].TPlayer.hostile || !Main.pvpBuff[type])
+ {
+ args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 50))
+ {
+ args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.SendData(PacketTypes.PlayerAddBuff, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (WhitelistBuffMaxTime[type] > 0 && time <= WhitelistBuffMaxTime[type])
+ {
+ args.Handled = false;
+ return;
+ }
+ }
+
+ /// Handles NPCAddBuff events.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnNPCAddBuff(object sender, GetDataHandlers.NPCAddBuffEventArgs args)
+ {
+ short id = args.ID;
+ byte type = args.Type;
+ short time = args.Time;
+
+ if (id >= Main.npc.Length)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ NPC npc = Main.npc[id];
+
+ if (npc == null)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Handled = true;
+ return;
+ }
+
+ bool detectedNPCBuffTimeCheat = false;
+
+ if (NPCAddBuffTimeMax.ContainsKey(type))
+ {
+ if (time > NPCAddBuffTimeMax[type])
+ {
+ detectedNPCBuffTimeCheat = true;
+ }
+
+ if (npc.townNPC && npc.netID != NPCID.Guide && npc.netID != NPCID.Clothier)
+ {
+ if (type != BuffID.Lovestruck && type != BuffID.Stinky && type != BuffID.DryadsWard &&
+ type != BuffID.Wet && type != BuffID.Slimed)
+ {
+ detectedNPCBuffTimeCheat = true;
+ }
+ }
+ }
+ else
+ {
+ detectedNPCBuffTimeCheat = true;
+ }
+
+ if (detectedNPCBuffTimeCheat)
+ {
+ args.Player.Kick("Added buff to NPC abnormally.", true);
+ args.Handled = true;
+ }
+ }
+
+ /// The Bouncer handler for when an NPC is rehomed.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnUpdateNPCHome(object sender, GetDataHandlers.NPCHomeChangeEventArgs args)
+ {
+ int id = args.ID;
+ short x = args.X;
+ short y = args.Y;
+ byte homeless = args.Homeless;
+
+ if (!args.Player.HasBuildPermission(x, y))
+ {
+ args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY,
+ Convert.ToByte(Main.npc[id].homeless));
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(x, y))
+ {
+ args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY,
+ Convert.ToByte(Main.npc[id].homeless));
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Bouncer's HealOther handler prevents gross misuse of HealOther packets by hackers.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnHealOtherPlayer(object sender, GetDataHandlers.HealOtherPlayerEventArgs args)
+ {
+ short amount = args.Amount;
+ byte plr = args.TargetPlayerIndex;
+
+ if (amount <= 0 || Main.player[plr] == null || !Main.player[plr].active)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ // Why 0.2?
+ // @bartico6: Because heal other player only happens when you are using the spectre armor with the hood,
+ // and the healing you can do with that is 20% of your damage.
+ if (amount > TShock.Config.MaxDamage * 0.2)
+ {
+ args.Player.Disable("HealOtherPlayer cheat attempt!", DisableFlags.WriteToLogAndConsole);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.HealOtherThreshold > TShock.Config.HealOtherThreshold)
+ {
+ args.Player.Disable("Reached HealOtherPlayer threshold.", DisableFlags.WriteToLogAndConsole);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled() || args.Player.IsBouncerThrottled())
+ {
+ args.Handled = true;
+ return;
+ }
+
+ args.Player.HealOtherThreshold++;
+ args.Handled = false;
+ return;
+ }
+
+ /// Bouncer's PlaceObject hook reverts malicious tile placement.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnPlaceObject(object sender, GetDataHandlers.PlaceObjectEventArgs args)
+ {
+ short x = args.X;
+ short y = args.Y;
+ short type = args.Type;
+ short style = args.Style;
+ byte alternate = args.Alternate;
+ bool direction = args.Direction;
+
+ if (type < 0 || type >= Main.maxTileSets)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (x < 0 || x >= Main.maxTilesX)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (y < 0 || y >= Main.maxTilesY)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ //style 52 and 53 are used by ItemID.Fake_newchest1 and ItemID.Fake_newchest2
+ //These two items cause localised lag and rendering issues
+ if (type == TileID.FakeContainers && (style == 52 || style == 53))
+ {
+ args.Player.SendTileSquare(x, y, 4);
+ args.Handled = true;
+ return;
+ }
+
+ // TODO: REMOVE. This does NOT look like Bouncer code.
+ if (TShock.TileBans.TileIsBanned(type, args.Player))
+ {
+ args.Player.SendTileSquare(x, y, 1);
+ args.Player.SendErrorMessage("You do not have permission to place this tile.");
+ args.Handled = true;
+ return;
+ }
+
+ if (!TShock.Utils.TilePlacementValid(x, y))
+ {
+ args.Player.SendTileSquare(x, y, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.Dead && TShock.Config.PreventDeadModification)
+ {
+ args.Player.SendTileSquare(x, y, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendTileSquare(x, y, 4);
+ args.Handled = true;
+ return;
+ }
+
+ // This is neccessary to check in order to prevent special tiles such as
+ // queen bee larva, paintings etc that use this packet from being placed
+ // without selecting the right item.
+ if (type != args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].createTile)
+ {
+ args.Player.SendTileSquare(x, y, 4);
+ args.Handled = true;
+ return;
+ }
+
+ TileObjectData tileData = TileObjectData.GetTileData(type, style, 0);
+ if (tileData == null)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ x -= tileData.Origin.X;
+ y -= tileData.Origin.Y;
+
+ for (int i = x; i < x + tileData.Width; i++)
+ {
+ for (int j = y; j < y + tileData.Height; j++)
+ {
+ if (!args.Player.HasModifiedIceSuccessfully(i, j, type, EditAction.PlaceTile)
+ && !args.Player.HasBuildPermission(i, j))
+ {
+ args.Player.SendTileSquare(i, j, 4);
+ args.Handled = true;
+ return;
+ }
+ }
+ }
+
+ // Ignore rope placement range
+ if ((type != TileID.Rope
+ || type != TileID.SilkRope
+ || type != TileID.VineRope
+ || type != TileID.WebRope)
+ && !args.Player.IsInRange(x, y))
+ {
+ args.Player.SendTileSquare(x, y, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold)
+ {
+ args.Player.Disable("Reached TilePlace threshold.", DisableFlags.WriteToLogAndConsole);
+ args.Player.SendTileSquare(x, y, 4);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasPermission(Permissions.ignoreplacetiledetection))
+ {
+ args.Player.TilePlaceThreshold++;
+ var coords = new Vector2(x, y);
+ lock (args.Player.TilesCreated)
+ if (!args.Player.TilesCreated.ContainsKey(coords))
+ args.Player.TilesCreated.Add(coords, Main.tile[x, y]);
+ }
+ }
+
+ /// Fired when a PlaceTileEntity occurs for basic anti-cheat on perms and range.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnPlaceTileEntity(object sender, GetDataHandlers.PlaceTileEntityEventArgs args)
+ {
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasBuildPermission(args.X, args.Y))
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(args.X, args.Y))
+ {
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Fired when an item frame is placed for anti-cheat detection.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnPlaceItemFrame(object sender, GetDataHandlers.PlaceItemFrameEventArgs args)
+ {
+ if (args.Player.IsBeingDisabled())
+ {
+ NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasBuildPermission(args.X, args.Y))
+ {
+ NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(args.X, args.Y))
+ {
+ NetMessage.SendData((int)PacketTypes.UpdateTileEntity, -1, -1, NetworkText.Empty, args.ItemFrame.ID, 0, 1);
+ args.Handled = true;
+ return;
+ }
+ }
+
+ internal void OnPlayerPortalTeleport(object sender, GetDataHandlers.TeleportThroughPortalEventArgs args)
+ {
+ //Packet 96 (player teleport through portal) has no validation on whether or not the player id provided
+ //belongs to the player who sent the packet.
+ if (args.Player.Index != args.TargetPlayerIndex)
+ {
+ //If the player who sent the packet is not the player being teleported, cancel this packet
+ args.Player.Disable("Malicious portal attempt.", DisableFlags.WriteToLogAndConsole); //Todo: this message is not particularly clear - suggestions wanted
+ args.Handled = true;
+ return;
+ }
+
+ //Generic bounds checking, though I'm not sure if anyone would willingly hack themselves outside the map?
+ if (args.NewPosition.X > Main.maxTilesX || args.NewPosition.X < 0
+ || args.NewPosition.Y > Main.maxTilesY || args.NewPosition.Y < 0)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ //May as well reject teleport attempts if the player is being throttled
+ if (args.Player.IsBeingDisabled() || args.Player.IsBouncerThrottled())
+ {
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Handles the anti-cheat components of gem lock toggles.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnGemLockToggle(object sender, GetDataHandlers.GemLockToggleEventArgs args)
+ {
+ if (args.X < 0 || args.Y < 0 || args.X >= Main.maxTilesX || args.Y >= Main.maxTilesY)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!TShock.Utils.TilePlacementValid(args.X, args.Y) || (args.Player.Dead && TShock.Config.PreventDeadModification))
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasBuildPermission(args.X, args.Y))
+ {
+ args.Handled = true;
+ return;
+ }
+ }
+
+ /// Handles validation of of basic anti-cheat on mass wire operations.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnMassWireOperation(object sender, GetDataHandlers.MassWireOperationEventArgs args)
+ {
+ short startX = args.StartX;
+ short startY = args.StartY;
+ short endX = args.EndX;
+ short endY = args.EndY;
+
+ List points = Utils.Instance.GetMassWireOperationRange(
+ new Point(startX, startY),
+ new Point(endX, endY),
+ args.Player.TPlayer.direction == 1);
+
+ int x;
+ int y;
+ foreach (Point p in points)
+ {
+ /* Perform similar checks to TileKill
+ * The server-side nature of this packet removes the need to use SendTileSquare
+ * Range checks are currently ignored here as the items that send this seem to have infinite range */
+
+ x = p.X;
+ y = p.Y;
+
+ if (!TShock.Utils.TilePlacementValid(x, y) || (args.Player.Dead && TShock.Config.PreventDeadModification))
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.HasBuildPermission(x, y))
+ {
+ args.Handled = true;
+ return;
+ }
+ }
+ }
+
+ /// Called when a player is damaged.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnPlayerDamage(object sender, GetDataHandlers.PlayerDamageEventArgs args)
+ {
+ byte id = args.ID;
+ short damage = args.Damage;
+ bool pvp = args.PVP;
+ bool crit = args.Critical;
+ byte direction = args.Direction;
+
+ if (id >= Main.maxPlayers || TShock.Players[id] == null)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ if (damage > TShock.Config.MaxDamage && !args.Player.HasPermission(Permissions.ignoredamagecap) && id != args.Player.Index)
+ {
+ if (TShock.Config.KickOnDamageThresholdBroken)
+ {
+ args.Player.Kick(string.Format("Player damage exceeded {0}.", TShock.Config.MaxDamage));
+ args.Handled = true;
+ return;
+ }
+ else
+ {
+ args.Player.Disable(String.Format("Player damage exceeded {0}.", TShock.Config.MaxDamage), DisableFlags.WriteToLogAndConsole);
+ }
+ args.Player.SendData(PacketTypes.PlayerHp, "", id);
+ args.Player.SendData(PacketTypes.PlayerUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (!TShock.Players[id].TPlayer.hostile && pvp && id != args.Player.Index)
+ {
+ args.Player.SendData(PacketTypes.PlayerHp, "", id);
+ args.Player.SendData(PacketTypes.PlayerUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBeingDisabled())
+ {
+ args.Player.SendData(PacketTypes.PlayerHp, "", id);
+ args.Player.SendData(PacketTypes.PlayerUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (!args.Player.IsInRange(TShock.Players[id].TileX, TShock.Players[id].TileY, 100))
+ {
+ args.Player.SendData(PacketTypes.PlayerHp, "", id);
+ args.Player.SendData(PacketTypes.PlayerUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ if (args.Player.IsBouncerThrottled())
+ {
+ args.Player.SendData(PacketTypes.PlayerHp, "", id);
+ args.Player.SendData(PacketTypes.PlayerUpdate, "", id);
+ args.Handled = true;
+ return;
+ }
+
+ }
+
+ /// Bouncer's KillMe hook stops crash exploits from out of bounds values.
+ /// The object that triggered the event.
+ /// The packet arguments that the event has.
+ internal void OnKillMe(object sender, GetDataHandlers.KillMeEventArgs args)
+ {
+ short damage = args.Damage;
+ short id = args.PlayerId;
+ PlayerDeathReason playerDeathReason = args.PlayerDeathReason;
+
+ if (damage > 20000) //Abnormal values have the potential to cause infinite loops in the server.
+ {
+ args.Player.Kick("Failed to shade polygon normals.", true, true);
+ TShock.Log.ConsoleError("Death Exploit Attempt: Damage {0}", damage);
+ args.Handled = true;
+ return;
+ }
+
+ if (id >= Main.maxPlayers)
+ {
+ args.Handled = true;
+ return;
+ }
+
+ // This was formerly marked as a crash check; does not actually crash on this specific packet.
+ if (playerDeathReason != null)
+ {
+ if (playerDeathReason.GetDeathText(TShock.Players[id].Name).ToString().Length > 500)
+ {
+ TShock.Players[id].Kick("Death reason outside of normal bounds.", true);
+ args.Handled = true;
+ return;
+ }
+ }
+ }
+
+
+ private static Dictionary NPCAddBuffTimeMax = new Dictionary()
+ {
+ { BuffID.Poisoned, 3600 },
+ { BuffID.OnFire, 1200 },
+ { BuffID.CursedInferno, 420 },
+ { BuffID.Frostburn, 900 },
+ { BuffID.Ichor, 1200 },
+ { BuffID.Venom, 1260 },
+ { BuffID.Midas, 120 },
+ { BuffID.Wet, 1500 },
+ { BuffID.Slimed, 1500 },
+ { BuffID.Lovestruck, 1800 },
+ { BuffID.Stinky, 1800 },
+ { BuffID.SoulDrain, 30 },
+ { BuffID.ShadowFlame, 660 },
+ { BuffID.DryadsWard, 120 },
+ { BuffID.BoneJavelin, 900 },
+ { BuffID.StardustMinionBleed, 900 },
+ { BuffID.DryadsWardDebuff, 120 },
+ { BuffID.BetsysCurse, 600 },
+ { BuffID.Oiled, 540 },
+ { BuffID.Confused, 450 }, // Brain of Confusion Internal Item ID: 3223
+ { BuffID.Daybreak, 300 } // Solar Eruption Item ID: 3473, Daybreak Item ID: 3543
+ };
+
+ ///
+ /// Tile IDs that can be oriented:
+ /// Cannon,
+ /// Chairs,
+ /// Beds,
+ /// Bathtubs,
+ /// Statues,
+ /// Mannequin,
+ /// Traps,
+ /// MusicBoxes,
+ /// ChristmasTree,
+ /// WaterFountain,
+ /// Womannequin,
+ /// MinecartTrack,
+ /// WeaponsRack,
+ /// LunarMonolith,
+ /// TargetDummy,
+ /// Campfire
+ ///
+ private static int[] orientableTiles = new int[]
+ {
+ TileID.Cannon,
+ TileID.Chairs,
+ TileID.Beds,
+ TileID.Bathtubs,
+ TileID.Statues,
+ TileID.Mannequin,
+ TileID.Traps,
+ TileID.MusicBoxes,
+ TileID.ChristmasTree,
+ TileID.WaterFountain,
+ TileID.Womannequin,
+ TileID.MinecartTrack,
+ TileID.WeaponsRack,
+ TileID.ItemFrame,
+ TileID.LunarMonolith,
+ TileID.TargetDummy,
+ TileID.Campfire
+ };
+
+ }
+}
diff --git a/TShockAPI/CLI/CommandLineParser.cs b/TShockAPI/CLI/CommandLineParser.cs
index 7106abbe..d62917fb 100644
--- a/TShockAPI/CLI/CommandLineParser.cs
+++ b/TShockAPI/CLI/CommandLineParser.cs
@@ -1,3 +1,21 @@
+/*
+TShock, a server mod for Terraria
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -165,10 +183,16 @@ namespace TShockAPI.CLI
{
_source = source;
- for (int i = 0; i < (source.Length - 1 == 0 ? 1 : source.Length - 1); i++)
+ for (int i = 0; i < (source.Length - 1 == 0 ? 1 : source.Length); i++)
{
string flag = source[i].ToLowerInvariant();
string argument = null;
+
+ if (string.IsNullOrWhiteSpace(flag))
+ {
+ continue;
+ }
+
if (i + 1 < source.Length)
{
argument = source[i + 1];
diff --git a/TShockAPI/CLI/FlagSet.cs b/TShockAPI/CLI/FlagSet.cs
index 71f4e07a..ed559f3d 100644
--- a/TShockAPI/CLI/FlagSet.cs
+++ b/TShockAPI/CLI/FlagSet.cs
@@ -1,3 +1,21 @@
+/*
+TShock, a server mod for Terraria
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs
index ee9106fc..3af965a8 100644
--- a/TShockAPI/Commands.cs
+++ b/TShockAPI/Commands.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@ using Terraria.GameContent.Events;
using Microsoft.Xna.Framework;
using OTAPI.Tile;
using TShockAPI.Localization;
+using System.Text.RegularExpressions;
namespace TShockAPI
{
@@ -192,11 +193,17 @@ namespace TShockAPI
public static List ChatCommands = new List();
public static ReadOnlyCollection TShockCommands = new ReadOnlyCollection(new List());
+ ///
+ /// The command specifier, defaults to "/"
+ ///
public static string Specifier
{
get { return string.IsNullOrWhiteSpace(TShock.Config.CommandSpecifier) ? "/" : TShock.Config.CommandSpecifier; }
}
+ ///
+ /// The silent command specifier, defaults to "."
+ ///
public static string SilentSpecifier
{
get { return string.IsNullOrWhiteSpace(TShock.Config.CommandSilentSpecifier) ? "." : TShock.Config.CommandSilentSpecifier; }
@@ -213,7 +220,7 @@ namespace TShockAPI
ChatCommands.Add(cmd);
};
- add(new Command(AuthToken, "auth")
+ add(new Command(SetupToken, "setup")
{
AllowServer = false,
HelpText = "Used to authenticate as superadmin when first setting up TShock."
@@ -311,6 +318,14 @@ namespace TShockAPI
{
HelpText = "Temporarily sets another player's group."
});
+ add(new Command(Permissions.su, SubstituteUser, "su")
+ {
+ HelpText = "Temporarily elevates you to Super Admin."
+ });
+ add(new Command(Permissions.su, SubstituteUserDo, "sudo")
+ {
+ HelpText = "Executes a command as the super admin."
+ });
add(new Command(Permissions.userinfo, GrabUserUserInfo, "userinfo", "ui")
{
HelpText = "Shows information about a player."
@@ -351,10 +366,6 @@ namespace TShockAPI
{
HelpText = "Reloads the server configuration file."
});
- add(new Command(Permissions.maintenance, Restart, "restart")
- {
- HelpText = "Restarts the server."
- });
add(new Command(Permissions.cfgpassword, ServerPassword, "serverpassword")
{
HelpText = "Changes the server password."
@@ -601,6 +612,10 @@ namespace TShockAPI
{
HelpText = "Sends a PM to a player."
});
+ add(new Command(Permissions.createdumps, CreateDumps, "dump-reference-data")
+ {
+ HelpText = "Creates a reference tables for Terraria data types and the TShock permission system in the server folder."
+ });
#endregion
add(new Command(Aliases, "aliases")
@@ -685,6 +700,10 @@ namespace TShockAPI
{
TShock.Utils.SendLogs(string.Format("{0} tried to execute {1}{2}.", player.Name, Specifier, cmdText), Color.PaleVioletRed, player);
player.SendErrorMessage("You do not have access to this command.");
+ if (player.HasPermission(Permissions.su))
+ {
+ player.SendInfoMessage("You can use '{0}sudo {0}{1}' to override this check.", Specifier, cmdText);
+ }
}
else if (!cmd.AllowServer && !player.RealPlayer)
{
@@ -764,7 +783,7 @@ namespace TShockAPI
{
TShock.Log.Warn(String.Format("{0} ({1}) had {2} or more invalid login attempts and was kicked automatically.",
args.Player.IP, args.Player.Name, TShock.Config.MaximumLoginAttempts));
- TShock.Utils.Kick(args.Player, "Too many invalid login attempts.");
+ args.Player.Kick("Too many invalid login attempts.");
return;
}
@@ -774,7 +793,7 @@ namespace TShockAPI
return;
}
- User user = TShock.Users.GetUserByName(args.Player.Name);
+ UserAccount account = TShock.UserAccounts.GetUserAccountByName(args.Player.Name);
string password = "";
bool usingUUID = false;
if (args.Parameters.Count == 0 && !TShock.Config.DisableUUIDLogin)
@@ -800,7 +819,7 @@ namespace TShockAPI
if (PlayerHooks.OnPlayerPreLogin(args.Player, args.Parameters[0], args.Parameters[1]))
return;
- user = TShock.Users.GetUserByName(args.Parameters[0]);
+ account = TShock.UserAccounts.GetUserAccountByName(args.Parameters[0]);
password = args.Parameters[1];
}
else
@@ -813,23 +832,23 @@ namespace TShockAPI
}
try
{
- if (user == null)
+ if (account == null)
{
- args.Player.SendErrorMessage("A user by that name does not exist.");
+ args.Player.SendErrorMessage("A user account by that name does not exist.");
}
- else if (user.VerifyPassword(password) ||
- (usingUUID && user.UUID == args.Player.UUID && !TShock.Config.DisableUUIDLogin &&
+ else if (account.VerifyPassword(password) ||
+ (usingUUID && account.UUID == args.Player.UUID && !TShock.Config.DisableUUIDLogin &&
!String.IsNullOrWhiteSpace(args.Player.UUID)))
{
- args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, user.ID);
+ args.Player.PlayerData = TShock.CharacterDB.GetPlayerData(args.Player, account.ID);
- var group = TShock.Utils.GetGroup(user.Group);
+ var group = TShock.Groups.GetGroupByName(account.Group);
args.Player.Group = group;
args.Player.tempGroup = null;
- args.Player.User = user;
+ args.Player.Account = account;
args.Player.IsLoggedIn = true;
- args.Player.IgnoreActionsForInventory = "none";
+ args.Player.IsDisabledForSSC = false;
if (Main.ServerSideCharacter)
{
@@ -843,14 +862,14 @@ namespace TShockAPI
args.Player.LoginFailsBySsi = false;
if (args.Player.HasPermission(Permissions.ignorestackhackdetection))
- args.Player.IgnoreActionsForCheating = "none";
+ args.Player.IsDisabledForStackDetection = false;
if (args.Player.HasPermission(Permissions.usebanneditem))
- args.Player.IgnoreActionsForDisabledArmor = "none";
+ args.Player.IsDisabledForBannedWearable = false;
- args.Player.SendSuccessMessage("Authenticated as " + user.Name + " successfully.");
+ args.Player.SendSuccessMessage("Authenticated as " + account.Name + " successfully.");
- TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + user.Name + ".");
+ TShock.Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + account.Name + ".");
if ((args.Player.LoginHarassed) && (TShock.Config.RememberLeavePos))
{
if (TShock.RememberedPos.GetLeavePos(args.Player.Name, args.Player.IP) != Vector2.Zero)
@@ -861,7 +880,7 @@ namespace TShockAPI
args.Player.LoginHarassed = false;
}
- TShock.Users.SetUserUUID(user, args.Player.UUID);
+ TShock.UserAccounts.SetUserAccountUUID(account, args.Player.UUID);
Hooks.PlayerHooks.OnPlayerPostLogin(args.Player);
}
@@ -875,7 +894,7 @@ namespace TShockAPI
{
args.Player.SendErrorMessage("Invalid password!");
}
- TShock.Log.Warn(args.Player.IP + " failed to authenticate as user: " + user.Name + ".");
+ TShock.Log.Warn(args.Player.IP + " failed to authenticate as user: " + account.Name + ".");
args.Player.LoginAttempts++;
}
}
@@ -909,14 +928,14 @@ namespace TShockAPI
if (args.Player.IsLoggedIn && args.Parameters.Count == 2)
{
string password = args.Parameters[0];
- if (args.Player.User.VerifyPassword(password))
+ if (args.Player.Account.VerifyPassword(password))
{
try
{
args.Player.SendSuccessMessage("You changed your password!");
- TShock.Users.SetUserPassword(args.Player.User, args.Parameters[1]); // SetUserPassword will hash it for you.
+ TShock.UserAccounts.SetUserAccountPassword(args.Player.Account, args.Parameters[1]); // SetUserPassword will hash it for you.
TShock.Log.ConsoleInfo(args.Player.IP + " named " + args.Player.Name + " changed the password of account " +
- args.Player.User.Name + ".");
+ args.Player.Account.Name + ".");
}
catch (ArgumentOutOfRangeException)
{
@@ -927,7 +946,7 @@ namespace TShockAPI
{
args.Player.SendErrorMessage("You failed to change your password!");
TShock.Log.ConsoleError(args.Player.IP + " named " + args.Player.Name + " failed to change password for account: " +
- args.Player.User.Name + ".");
+ args.Player.Account.Name + ".");
}
}
else
@@ -935,7 +954,7 @@ namespace TShockAPI
args.Player.SendErrorMessage("Not logged in or invalid syntax! Proper syntax: {0}password ", Specifier);
}
}
- catch (UserManagerException ex)
+ catch (UserAccountManagerException ex)
{
args.Player.SendErrorMessage("Sorry, an error occured: " + ex.Message + ".");
TShock.Log.ConsoleError("PasswordUser returned an error: " + ex);
@@ -946,15 +965,15 @@ namespace TShockAPI
{
try
{
- var user = new User();
+ var account = new UserAccount();
string echoPassword = "";
if (args.Parameters.Count == 1)
{
- user.Name = args.Player.Name;
+ account.Name = args.Player.Name;
echoPassword = args.Parameters[0];
try
{
- user.CreateBCryptHash(args.Parameters[0]);
+ account.CreateBCryptHash(args.Parameters[0]);
}
catch (ArgumentOutOfRangeException)
{
@@ -964,11 +983,11 @@ namespace TShockAPI
}
else if (args.Parameters.Count == 2 && TShock.Config.AllowRegisterAnyUsername)
{
- user.Name = args.Parameters[0];
+ account.Name = args.Parameters[0];
echoPassword = args.Parameters[1];
try
{
- user.CreateBCryptHash(args.Parameters[1]);
+ account.CreateBCryptHash(args.Parameters[1]);
}
catch (ArgumentOutOfRangeException)
{
@@ -982,24 +1001,24 @@ namespace TShockAPI
return;
}
- user.Group = TShock.Config.DefaultRegistrationGroupName; // FIXME -- we should get this from the DB. --Why?
- user.UUID = args.Player.UUID;
+ account.Group = TShock.Config.DefaultRegistrationGroupName; // FIXME -- we should get this from the DB. --Why?
+ account.UUID = args.Player.UUID;
- if (TShock.Users.GetUserByName(user.Name) == null && user.Name != TSServerPlayer.AccountName) // Cheap way of checking for existance of a user
+ if (TShock.UserAccounts.GetUserAccountByName(account.Name) == null && account.Name != TSServerPlayer.AccountName) // Cheap way of checking for existance of a user
{
- args.Player.SendSuccessMessage("Account \"{0}\" has been registered.", user.Name);
+ args.Player.SendSuccessMessage("Account \"{0}\" has been registered.", account.Name);
args.Player.SendSuccessMessage("Your password is {0}.", echoPassword);
- TShock.Users.AddUser(user);
- TShock.Log.ConsoleInfo("{0} registered an account: \"{1}\".", args.Player.Name, user.Name);
+ TShock.UserAccounts.AddUserAccount(account);
+ TShock.Log.ConsoleInfo("{0} registered an account: \"{1}\".", args.Player.Name, account.Name);
}
else
{
- args.Player.SendErrorMessage("Sorry, " + user.Name + " was already taken by another person.");
+ args.Player.SendErrorMessage("Sorry, " + account.Name + " was already taken by another person.");
args.Player.SendErrorMessage("Please try a different username.");
- TShock.Log.ConsoleInfo(args.Player.Name + " failed to register an existing account: " + user.Name);
+ TShock.Log.ConsoleInfo(args.Player.Name + " failed to register an existing account: " + account.Name);
}
}
- catch (UserManagerException ex)
+ catch (UserAccountManagerException ex)
{
args.Player.SendErrorMessage("Sorry, an error occured: " + ex.Message + ".");
TShock.Log.ConsoleError("RegisterUser returned an error: " + ex);
@@ -1020,57 +1039,57 @@ namespace TShockAPI
// Add requires a username, password, and a group specified.
if (subcmd == "add" && args.Parameters.Count == 4)
{
- var user = new User();
+ var account = new UserAccount();
- user.Name = args.Parameters[1];
+ account.Name = args.Parameters[1];
try
{
- user.CreateBCryptHash(args.Parameters[2]);
+ account.CreateBCryptHash(args.Parameters[2]);
}
catch (ArgumentOutOfRangeException)
{
args.Player.SendErrorMessage("Password must be greater than or equal to " + TShock.Config.MinimumPasswordLength + " characters.");
return;
}
- user.Group = args.Parameters[3];
+ account.Group = args.Parameters[3];
try
{
- TShock.Users.AddUser(user);
- args.Player.SendSuccessMessage("Account " + user.Name + " has been added to group " + user.Group + "!");
- TShock.Log.ConsoleInfo(args.Player.Name + " added Account " + user.Name + " to group " + user.Group);
+ TShock.UserAccounts.AddUserAccount(account);
+ args.Player.SendSuccessMessage("Account " + account.Name + " has been added to group " + account.Group + "!");
+ TShock.Log.ConsoleInfo(args.Player.Name + " added Account " + account.Name + " to group " + account.Group);
}
catch (GroupNotExistsException)
{
- args.Player.SendErrorMessage("Group " + user.Group + " does not exist!");
+ args.Player.SendErrorMessage("Group " + account.Group + " does not exist!");
}
- catch (UserExistsException)
+ catch (UserAccountExistsException)
{
- args.Player.SendErrorMessage("User " + user.Name + " already exists!");
+ args.Player.SendErrorMessage("User " + account.Name + " already exists!");
}
- catch (UserManagerException e)
+ catch (UserAccountManagerException e)
{
- args.Player.SendErrorMessage("User " + user.Name + " could not be added, check console for details.");
+ args.Player.SendErrorMessage("User " + account.Name + " could not be added, check console for details.");
TShock.Log.ConsoleError(e.ToString());
}
}
// User deletion requires a username
else if (subcmd == "del" && args.Parameters.Count == 2)
{
- var user = new User();
- user.Name = args.Parameters[1];
+ var account = new UserAccount();
+ account.Name = args.Parameters[1];
try
{
- TShock.Users.RemoveUser(user);
+ TShock.UserAccounts.RemoveUserAccount(account);
args.Player.SendSuccessMessage("Account removed successfully.");
TShock.Log.ConsoleInfo(args.Player.Name + " successfully deleted account: " + args.Parameters[1] + ".");
}
- catch (UserNotExistException)
+ catch (UserAccountNotExistException)
{
- args.Player.SendErrorMessage("The user " + user.Name + " does not exist! Deleted nobody!");
+ args.Player.SendErrorMessage("The user " + account.Name + " does not exist! Deleted nobody!");
}
- catch (UserManagerException ex)
+ catch (UserAccountManagerException ex)
{
args.Player.SendErrorMessage(ex.Message);
TShock.Log.ConsoleError(ex.ToString());
@@ -1080,22 +1099,22 @@ namespace TShockAPI
// Password changing requires a username, and a new password to set
else if (subcmd == "password" && args.Parameters.Count == 3)
{
- var user = new User();
- user.Name = args.Parameters[1];
+ var account = new UserAccount();
+ account.Name = args.Parameters[1];
try
{
- TShock.Users.SetUserPassword(user, args.Parameters[2]);
- TShock.Log.ConsoleInfo(args.Player.Name + " changed the password of account " + user.Name);
- args.Player.SendSuccessMessage("Password change succeeded for " + user.Name + ".");
+ TShock.UserAccounts.SetUserAccountPassword(account, args.Parameters[2]);
+ TShock.Log.ConsoleInfo(args.Player.Name + " changed the password of account " + account.Name);
+ args.Player.SendSuccessMessage("Password change succeeded for " + account.Name + ".");
}
- catch (UserNotExistException)
+ catch (UserAccountNotExistException)
{
- args.Player.SendErrorMessage("User " + user.Name + " does not exist!");
+ args.Player.SendErrorMessage("User " + account.Name + " does not exist!");
}
- catch (UserManagerException e)
+ catch (UserAccountManagerException e)
{
- args.Player.SendErrorMessage("Password change for " + user.Name + " failed! Check console!");
+ args.Player.SendErrorMessage("Password change for " + account.Name + " failed! Check console!");
TShock.Log.ConsoleError(e.ToString());
}
catch (ArgumentOutOfRangeException)
@@ -1106,26 +1125,26 @@ namespace TShockAPI
// Group changing requires a username or IP address, and a new group to set
else if (subcmd == "group" && args.Parameters.Count == 3)
{
- var user = new User();
- user.Name = args.Parameters[1];
+ var account = new UserAccount();
+ account.Name = args.Parameters[1];
try
{
- TShock.Users.SetUserGroup(user, args.Parameters[2]);
- TShock.Log.ConsoleInfo(args.Player.Name + " changed account " + user.Name + " to group " + args.Parameters[2] + ".");
- args.Player.SendSuccessMessage("Account " + user.Name + " has been changed to group " + args.Parameters[2] + "!");
+ TShock.UserAccounts.SetUserGroup(account, args.Parameters[2]);
+ TShock.Log.ConsoleInfo(args.Player.Name + " changed account " + account.Name + " to group " + args.Parameters[2] + ".");
+ args.Player.SendSuccessMessage("Account " + account.Name + " has been changed to group " + args.Parameters[2] + "!");
}
catch (GroupNotExistsException)
{
args.Player.SendErrorMessage("That group does not exist!");
}
- catch (UserNotExistException)
+ catch (UserAccountNotExistException)
{
- args.Player.SendErrorMessage("User " + user.Name + " does not exist!");
+ args.Player.SendErrorMessage("User " + account.Name + " does not exist!");
}
- catch (UserManagerException e)
+ catch (UserAccountManagerException e)
{
- args.Player.SendErrorMessage("User " + user.Name + " could not be added. Check console for details.");
+ args.Player.SendErrorMessage("User " + account.Name + " could not be added. Check console for details.");
TShock.Log.ConsoleError(e.ToString());
}
}
@@ -1176,17 +1195,17 @@ namespace TShockAPI
return;
}
- var players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count < 1)
args.Player.SendErrorMessage("Invalid player.");
else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var message = new StringBuilder();
message.Append("IP Address: ").Append(players[0].IP);
- if (players[0].User != null && players[0].IsLoggedIn)
- message.Append(" | Logged in as: ").Append(players[0].User.Name).Append(" | Group: ").Append(players[0].Group.Name);
+ if (players[0].Account != null && players[0].IsLoggedIn)
+ message.Append(" | Logged in as: ").Append(players[0].Account.Name).Append(" | Group: ").Append(players[0].Group.Name);
args.Player.SendSuccessMessage(message.ToString());
}
}
@@ -1202,28 +1221,28 @@ namespace TShockAPI
string username = String.Join(" ", args.Parameters);
if (!string.IsNullOrWhiteSpace(username))
{
- var user = TShock.Users.GetUserByName(username);
- if (user != null)
+ var account = TShock.UserAccounts.GetUserAccountByName(username);
+ if (account != null)
{
DateTime LastSeen;
string Timezone = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now).Hours.ToString("+#;-#");
- if (DateTime.TryParse(user.LastAccessed, out LastSeen))
+ if (DateTime.TryParse(account.LastAccessed, out LastSeen))
{
- LastSeen = DateTime.Parse(user.LastAccessed).ToLocalTime();
- args.Player.SendSuccessMessage("{0}'s last login occured {1} {2} UTC{3}.", user.Name, LastSeen.ToShortDateString(),
+ LastSeen = DateTime.Parse(account.LastAccessed).ToLocalTime();
+ args.Player.SendSuccessMessage("{0}'s last login occured {1} {2} UTC{3}.", account.Name, LastSeen.ToShortDateString(),
LastSeen.ToShortTimeString(), Timezone);
}
if (args.Player.Group.HasPermission(Permissions.advaccountinfo))
{
- List KnownIps = JsonConvert.DeserializeObject>(user.KnownIps?.ToString() ?? string.Empty);
+ List KnownIps = JsonConvert.DeserializeObject>(account.KnownIps?.ToString() ?? string.Empty);
string ip = KnownIps?[KnownIps.Count - 1] ?? "N/A";
- DateTime Registered = DateTime.Parse(user.Registered).ToLocalTime();
+ DateTime Registered = DateTime.Parse(account.Registered).ToLocalTime();
- args.Player.SendSuccessMessage("{0}'s group is {1}.", user.Name, user.Group);
- args.Player.SendSuccessMessage("{0}'s last known IP is {1}.", user.Name, ip);
- args.Player.SendSuccessMessage("{0}'s register date is {1} {2} UTC{3}.", user.Name, Registered.ToShortDateString(), Registered.ToShortTimeString(), Timezone);
+ args.Player.SendSuccessMessage("{0}'s group is {1}.", account.Name, account.Group);
+ args.Player.SendSuccessMessage("{0}'s last known IP is {1}.", account.Name, ip);
+ args.Player.SendSuccessMessage("{0}'s register date is {1} {2} UTC{3}.", account.Name, Registered.ToShortDateString(), Registered.ToShortTimeString(), Timezone);
}
}
else
@@ -1246,21 +1265,21 @@ namespace TShockAPI
}
string plStr = args.Parameters[0];
- var players = TShock.Utils.FindPlayer(plStr);
+ var players = TSPlayer.FindByNameOrID(plStr);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
}
else
{
string reason = args.Parameters.Count > 1
? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1))
: "Misbehaviour.";
- if (!TShock.Utils.Kick(players[0], reason, !args.Player.RealPlayer, false, args.Player.Name))
+ if (!players[0].Kick(reason, !args.Player.RealPlayer, false, args.Player.Name))
{
args.Player.SendErrorMessage("You can't kick another admin!");
}
@@ -1273,200 +1292,199 @@ namespace TShockAPI
switch (subcmd)
{
case "add":
- #region Add ban
+ #region Add Ban
{
if (args.Parameters.Count < 2)
{
- args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban add [reason]", Specifier);
+ args.Player.SendErrorMessage("Invalid command. Format: {0}ban add [time] [reason]", Specifier);
+ args.Player.SendErrorMessage("Example: {0}ban add Shank 10d Hacking and cheating", Specifier);
+ args.Player.SendErrorMessage("Example: {0}ban add Ash", Specifier);
+ args.Player.SendErrorMessage("Use the time 0 (zero) for a permanent ban.");
return;
}
- List players = TShock.Utils.FindPlayer(args.Parameters[1]);
- string reason = args.Parameters.Count > 2 ? String.Join(" ", args.Parameters.Skip(2)) : "Misbehavior.";
- if (players.Count == 0)
+ // Used only to notify if a ban was successful and who the ban was about
+ bool success = false;
+ string targetGeneralizedName = "";
+
+ // Effective ban target assignment
+ List players = TSPlayer.FindByNameOrID(args.Parameters[1]);
+
+ // Bad case: Players contains more than 1 person so we can't ban them
+ if (players.Count > 1)
{
- var user = TShock.Users.GetUserByName(args.Parameters[1]);
- if (user != null)
+ //Fail fast
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
+ return;
+ }
+
+ UserAccount offlineUserAccount = TShock.UserAccounts.GetUserAccountByName(args.Parameters[1]);
+
+ // Storage variable to determine if the command executor is the server console
+ // If it is, we assume they have full control and let them override permission checks
+ bool callerIsServerConsole = args.Player is TSServerPlayer;
+
+ // The ban reason the ban is going to have
+ string banReason = "Unknown.";
+
+ // The default ban length
+ // 0 is permanent ban, otherwise temp ban
+ int banLengthInSeconds = 0;
+
+ // Figure out if param 2 is a time or 0 or garbage
+ if (args.Parameters.Count >= 3)
+ {
+ bool parsedOkay = false;
+ if (args.Parameters[2] != "0")
{
- bool force = !args.Player.RealPlayer;
-
- if (user.Name == args.Player.Name && !force)
- {
- args.Player.SendErrorMessage("You can't ban yourself!");
- return;
- }
-
- if (TShock.Groups.GetGroupByName(user.Group).HasPermission(Permissions.immunetoban) && !force)
- args.Player.SendErrorMessage("You can't ban {0}!", user.Name);
- else
- {
- if (user.KnownIps == null)
- {
- args.Player.SendErrorMessage("Cannot ban {0} because they have no IPs to ban.", user.Name);
- return;
- }
- var knownIps = JsonConvert.DeserializeObject>(user.KnownIps);
- TShock.Bans.AddBan(knownIps.Last(), user.Name, user.UUID, reason, false, args.Player.User.Name);
- if (String.IsNullOrWhiteSpace(args.Player.User.Name))
- {
- if (args.Silent)
- {
- args.Player.SendInfoMessage("{0} was {1}banned for '{2}'.", user.Name, force ? "Force " : "", reason);
- }
- else
- {
- TSPlayer.All.SendInfoMessage("{0} was {1}banned for '{2}'.", user.Name, force ? "Force " : "", reason);
- }
- }
- else
- {
- if (args.Silent)
- {
- args.Player.SendInfoMessage("{0}banned {1} for '{2}'.", force ? "Force " : "", user.Name, reason);
- }
- else
- {
- TSPlayer.All.SendInfoMessage("{0} {1}banned {2} for '{3}'.", args.Player.Name, force ? "Force " : "", user.Name, reason);
- }
- }
- }
+ parsedOkay = TShock.Utils.TryParseTime(args.Parameters[2], out banLengthInSeconds);
+ }
+ else
+ {
+ parsedOkay = true;
}
- else
- args.Player.SendErrorMessage("Invalid player or account!");
- }
- else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
- else
- {
- if (!TShock.Utils.Ban(players[0], reason, !args.Player.RealPlayer, args.Player.User.Name))
- args.Player.SendErrorMessage("You can't ban {0}!", players[0].Name);
- }
- }
- #endregion
- return;
- case "addip":
- #region Add IP ban
- {
- if (args.Parameters.Count < 2)
- {
- args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban addip [reason]", Specifier);
- return;
- }
- string ip = args.Parameters[1];
- string reason = args.Parameters.Count > 2
- ? String.Join(" ", args.Parameters.GetRange(2, args.Parameters.Count - 2))
- : "Manually added IP address ban.";
- TShock.Bans.AddBan(ip, "", "", reason, false, args.Player.User.Name);
- args.Player.SendSuccessMessage("Banned IP {0}.", ip);
- }
- #endregion
- return;
- case "addtemp":
- #region Add temp ban
- {
- if (args.Parameters.Count < 3)
- {
- args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban addtemp [reason]", Specifier);
- return;
- }
-
- int time;
- if (!TShock.Utils.TryParseTime(args.Parameters[2], out time))
- {
- args.Player.SendErrorMessage("Invalid time string! Proper format: _d_h_m_s, with at least one time specifier.");
- args.Player.SendErrorMessage("For example, 1d and 10h-30m+2m are both valid time strings, but 2 is not.");
- return;
- }
-
- string reason = args.Parameters.Count > 3
- ? String.Join(" ", args.Parameters.Skip(3))
- : "Misbehavior.";
-
- List players = TShock.Utils.FindPlayer(args.Parameters[1]);
- if (players.Count == 0)
- {
- var user = TShock.Users.GetUserByName(args.Parameters[1]);
- if (user != null)
+ if (!parsedOkay)
{
- bool force = !args.Player.RealPlayer;
- if (TShock.Groups.GetGroupByName(user.Group).HasPermission(Permissions.immunetoban) && !force)
- args.Player.SendErrorMessage("You can't ban {0}!", user.Name);
- else
- {
- var knownIps = JsonConvert.DeserializeObject>(user.KnownIps);
- TShock.Bans.AddBan(knownIps.Last(), user.Name, user.UUID, reason, false, args.Player.User.Name, DateTime.UtcNow.AddSeconds(time).ToString("s"));
- if (String.IsNullOrWhiteSpace(args.Player.User.Name))
- {
- if (args.Silent)
- {
- args.Player.SendInfoMessage("{0} was {1}banned for '{2}'.", user.Name, force ? "force " : "", reason);
- }
- else
- {
- TSPlayer.All.SendInfoMessage("{0} was {1}banned for '{2}'.", user.Name, force ? "force " : "", reason);
- }
- }
- else
- {
- if (args.Silent)
- {
- args.Player.SendInfoMessage("{0} was {1}banned for '{2}'.", user.Name, force ? "force " : "", reason);
- }
- else
- {
- TSPlayer.All.SendInfoMessage("{0} {1}banned {2} for '{3}'.", args.Player.Name, force ? "force " : "", user.Name, reason);
- }
- }
- }
- }
- else
- {
- args.Player.SendErrorMessage("Invalid player or account!");
+ args.Player.SendErrorMessage("Invalid time format. Example: 10d 5h 3m 2s.");
+ args.Player.SendErrorMessage("Use 0 (zero) for a permanent ban.");
+ return;
}
}
- else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
- else
+
+ // If a reason exists, use the given reason.
+ if (args.Parameters.Count > 3)
{
- if (args.Player.RealPlayer && players[0].HasPermission(Permissions.immunetoban))
+ banReason = String.Join(" ", args.Parameters.Skip(3));
+ }
+
+ // Good case: Online ban for matching character.
+ if (players.Count == 1)
+ {
+ TSPlayer target = players[0];
+
+ if (target.HasPermission(Permissions.immunetoban) && !callerIsServerConsole)
{
- args.Player.SendErrorMessage("You can't ban {0}!", players[0].Name);
+ args.Player.SendErrorMessage("Permission denied. Target {0} is immune to ban.", target.Name);
return;
}
- if (TShock.Bans.AddBan(players[0].IP, players[0].Name, players[0].UUID, reason,
- false, args.Player.Name, DateTime.UtcNow.AddSeconds(time).ToString("s")))
+ targetGeneralizedName = target.Name;
+ success = TShock.Bans.AddBan(target.IP, target.Name, target.UUID, target.Account?.Name ?? "", banReason, false, args.Player.Account.Name,
+ banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
+
+ // Since this is an online ban, we need to dc the player and tell them now.
+ if (success)
{
- players[0].Disconnect(String.Format("Banned: {0}", reason));
- string verb = args.Player.RealPlayer ? "Force " : "";
- if (args.Player.RealPlayer)
- if (args.Silent)
- {
- args.Player.SendSuccessMessage("{0}banned {1} for '{2}'", verb, players[0].Name, reason);
- }
- else
- {
- TSPlayer.All.SendSuccessMessage("{0} {1}banned {2} for '{3}'", args.Player.Name, verb, players[0].Name, reason);
- }
+ if (banLengthInSeconds == 0)
+ {
+ target.Disconnect(String.Format("Permanently banned for {0}", banReason));
+ }
else
{
- if (args.Silent)
- {
- args.Player.SendSuccessMessage("{0}banned {1} for '{2}'", verb, players[0].Name, reason);
- }
- else
- {
- TSPlayer.All.SendSuccessMessage("{0} was {1}banned for '{2}'", players[0].Name, verb, reason);
- }
+ target.Disconnect(String.Format("Banned for {0} seconds for {1}", banLengthInSeconds, banReason));
}
}
- else
- args.Player.SendErrorMessage("Failed to ban {0}, check logs.", players[0].Name);
}
+
+ // Case: Players & user are invalid, could be IP?
+ // Note: Order matters. If this method is above the online player check,
+ // This enables you to ban an IP even if the player exists in the database as a player.
+ // You'll get two bans for the price of one, in theory, because both IP and user named IP will be banned.
+ // ??? edge cases are weird, but this is going to happen
+ // The only way around this is to either segregate off the IP code or do something else.
+ if (players.Count == 0)
+ {
+ // If the target is a valid IP...
+ string pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
+ Regex r = new Regex(pattern, RegexOptions.IgnoreCase);
+ if (r.IsMatch(args.Parameters[1]))
+ {
+ targetGeneralizedName = "IP: " + args.Parameters[1];
+ success = TShock.Bans.AddBan(args.Parameters[1], "", "", "", banReason,
+ false, args.Player.Account.Name, banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
+ if (success && offlineUserAccount != null)
+ {
+ args.Player.SendSuccessMessage("Target IP {0} was banned successfully.", targetGeneralizedName);
+ args.Player.SendErrorMessage("Note: An account named with this IP address also exists.");
+ args.Player.SendErrorMessage("Note: It will also be banned.");
+ }
+ }
+ else
+ {
+ // Apparently there is no way to not IP ban someone
+ // This means that where we would normally just ban a "character name" here
+ // We can't because it requires some IP as a primary key.
+ if (offlineUserAccount == null)
+ {
+ args.Player.SendErrorMessage("Unable to ban target {0}.", args.Parameters[1]);
+ args.Player.SendErrorMessage("Target is not a valid IP address, a valid online player, or a known offline user.");
+ return;
+ }
+ }
+
+ }
+
+ // Case: Offline ban
+ if (players.Count == 0 && offlineUserAccount != null)
+ {
+ // Catch: we don't know an offline player's last login character name
+ // This means that we're banning their *user name* on the assumption that
+ // user name == character name
+ // (which may not be true)
+ // This needs to be fixed in a future implementation.
+ targetGeneralizedName = offlineUserAccount.Name;
+
+ if (TShock.Groups.GetGroupByName(offlineUserAccount.Group).HasPermission(Permissions.immunetoban) &&
+ !callerIsServerConsole)
+ {
+ args.Player.SendErrorMessage("Permission denied. Target {0} is immune to ban.", targetGeneralizedName);
+ return;
+ }
+
+ if (offlineUserAccount.KnownIps == null)
+ {
+ args.Player.SendErrorMessage("Unable to ban target {0} because they have no valid IP to ban.", targetGeneralizedName);
+ return;
+ }
+
+ string lastIP = JsonConvert.DeserializeObject>(offlineUserAccount.KnownIps).Last();
+
+ success =
+ TShock.Bans.AddBan(lastIP,
+ "", offlineUserAccount.UUID, offlineUserAccount.Name, banReason, false, args.Player.Account.Name,
+ banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
+ }
+
+ if (success)
+ {
+ args.Player.SendSuccessMessage("{0} was successfully banned.", targetGeneralizedName);
+ args.Player.SendInfoMessage("Length: {0}", banLengthInSeconds == 0 ? "Permanent." : banLengthInSeconds + " seconds.");
+ args.Player.SendInfoMessage("Reason: {0}", banReason);
+ if (!args.Silent)
+ {
+ if (banLengthInSeconds == 0)
+ {
+ TSPlayer.All.SendErrorMessage("{0} was permanently banned by {1} for: {2}",
+ targetGeneralizedName, args.Player.Account.Name, banReason);
+ }
+ else
+ {
+ TSPlayer.All.SendErrorMessage("{0} was temp banned for {1} seconds by {2} for: {3}",
+ targetGeneralizedName, banLengthInSeconds, args.Player.Account.Name, banReason);
+ }
+ }
+ }
+ else
+ {
+ args.Player.SendErrorMessage("{0} was NOT banned due to a database error or other system problem.", targetGeneralizedName);
+ args.Player.SendErrorMessage("If this player is online, they have NOT been kicked.");
+ args.Player.SendErrorMessage("Check the system logs for details.");
+ }
+
+ return;
}
#endregion
- return;
case "del":
#region Delete ban
{
@@ -1522,9 +1540,7 @@ namespace TShockAPI
var lines = new List
{
- "add [reason] - Bans a player or user account if the player is not online.",
- "addip [reason] - Bans an IP.",
- "addtemp [reason] - Temporarily bans a player.",
+ "add [reason] - Bans a player or user account if the player is not online.",
"del - Unbans a player.",
"delip - Unbans an IP.",
"list [page] - Lists all player bans.",
@@ -1622,9 +1638,9 @@ namespace TShockAPI
args.Player.SendSuccessMessage("SSC has been saved.");
foreach (TSPlayer player in TShock.Players)
{
- if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
+ if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
{
- TShock.CharacterDB.InsertPlayerData(player);
+ TShock.CharacterDB.InsertPlayerData(player, true);
}
}
}
@@ -1644,7 +1660,7 @@ namespace TShockAPI
}
string playerNameToMatch = string.Join(" ", args.Parameters);
- var matchedPlayers = TShock.Utils.FindPlayer(playerNameToMatch);
+ var matchedPlayers = TSPlayer.FindByNameOrID(playerNameToMatch);
if (matchedPlayers.Count < 1)
{
args.Player.SendErrorMessage("No players matched \"{0}\".", playerNameToMatch);
@@ -1652,7 +1668,7 @@ namespace TShockAPI
}
else if (matchedPlayers.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, matchedPlayers.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(matchedPlayers.Select(p => p.Name));
return;
}
@@ -1667,7 +1683,7 @@ namespace TShockAPI
args.Player.SendErrorMessage("Player \"{0}\" has to perform a /login attempt first.", matchedPlayer.Name);
return;
}
- if (matchedPlayer.IgnoreActionsForClearingTrashCan)
+ if (matchedPlayer.IsDisabledPendingTrashRemoval)
{
args.Player.SendErrorMessage("Player \"{0}\" has to reconnect first.", matchedPlayer.Name);
return;
@@ -1682,10 +1698,10 @@ namespace TShockAPI
TSPlayer targetPlayer = args.Player;
if (args.Parameters.Count == 1 && args.Player.HasPermission(Permissions.uploadothersdata))
{
- List players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ List players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
return;
}
else if (players.Count == 0)
@@ -1763,7 +1779,7 @@ namespace TShockAPI
return;
}
- List ply = TShock.Utils.FindPlayer(args.Parameters[0]);
+ List ply = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (ply.Count < 1)
{
args.Player.SendErrorMessage("Could not find player {0}.", args.Parameters[0]);
@@ -1772,7 +1788,7 @@ namespace TShockAPI
if (ply.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, ply.Select(p => p.User.Name));
+ args.Player.SendMultipleMatchError(ply.Select(p => p.Account.Name));
}
if (!TShock.Groups.GroupExists(args.Parameters[1]))
@@ -1796,7 +1812,7 @@ namespace TShockAPI
ply[0].tempGroupTimer.Start();
}
- Group g = TShock.Utils.GetGroup(args.Parameters[1]);
+ Group g = TShock.Groups.GetGroupByName(args.Parameters[1]);
ply[0].tempGroup = g;
@@ -1814,10 +1830,48 @@ namespace TShockAPI
}
}
+ private static void SubstituteUser(CommandArgs args)
+ {
+
+ if (args.Player.tempGroup != null)
+ {
+ args.Player.tempGroup = null;
+ args.Player.tempGroupTimer.Stop();
+ args.Player.SendSuccessMessage("Your previous permission set has been restored.");
+ return;
+ }
+ else
+ {
+ args.Player.tempGroup = new SuperAdminGroup();
+ args.Player.tempGroupTimer = new System.Timers.Timer(600 * 1000);
+ args.Player.tempGroupTimer.Elapsed += args.Player.TempGroupTimerElapsed;
+ args.Player.tempGroupTimer.Start();
+ args.Player.SendSuccessMessage("Your account has been elevated to Super Admin for 10 minutes.");
+ return;
+ }
+ }
+
#endregion Player Management Commands
#region Server Maintenence Commands
+ // Executes a command as a superuser if you have sudo rights.
+ private static void SubstituteUserDo(CommandArgs args)
+ {
+ if (args.Parameters.Count == 0)
+ {
+ args.Player.SendErrorMessage("Usage: /sudo [command].");
+ args.Player.SendErrorMessage("Example: /sudo /ban add Shank 2d Hacking.");
+ return;
+ }
+
+ string replacementCommand = String.Join(" ", args.Parameters);
+ args.Player.tempGroup = new SuperAdminGroup();
+ HandleCommand(args.Player, replacementCommand);
+ args.Player.tempGroup = null;
+ return;
+ }
+
private static void Broadcast(CommandArgs args)
{
string message = string.Join(" ", args.Parameters);
@@ -1835,7 +1889,7 @@ namespace TShockAPI
{
foreach (TSPlayer player in TShock.Players)
{
- if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan)
+ if (player != null && player.IsLoggedIn && !player.IsDisabledPendingTrashRemoval)
{
player.SaveServerCharacter();
}
@@ -1846,25 +1900,6 @@ namespace TShockAPI
TShock.Utils.StopServer(true, reason);
}
- private static void Restart(CommandArgs args)
- {
- if (TShock.NoRestart)
- {
- args.Player.SendErrorMessage("This command has been disabled.");
- return;
- }
-
- if (ServerApi.RunningMono)
- {
- TShock.Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono.");
- }
- else
- {
- string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!");
- TShock.Utils.RestartServer(true, reason);
- }
- }
-
private static void OffNoSave(CommandArgs args)
{
string reason = ((args.Parameters.Count > 0) ? "Server shutting down: " + String.Join(" ", args.Parameters) : "Server shutting down!");
@@ -2011,19 +2046,19 @@ namespace TShockAPI
case "goblin":
case "goblins":
TSPlayer.All.SendInfoMessage("{0} has started a goblin army invasion.", args.Player.Name);
- TShock.StartInvasion(1);
+ TShock.Utils.StartInvasion(1);
break;
case "snowman":
case "snowmen":
TSPlayer.All.SendInfoMessage("{0} has started a snow legion invasion.", args.Player.Name);
- TShock.StartInvasion(2);
+ TShock.Utils.StartInvasion(2);
break;
case "pirate":
case "pirates":
TSPlayer.All.SendInfoMessage("{0} has started a pirate invasion.", args.Player.Name);
- TShock.StartInvasion(3);
+ TShock.Utils.StartInvasion(3);
break;
case "pumpkin":
@@ -2065,7 +2100,7 @@ namespace TShockAPI
case "martian":
case "martians":
TSPlayer.All.SendInfoMessage("{0} has started a martian invasion.", args.Player.Name);
- TShock.StartInvasion(4);
+ TShock.Utils.StartInvasion(4);
break;
}
}
@@ -2293,14 +2328,14 @@ namespace TShockAPI
}
else if (npcs.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, npcs.Select(n => $"{n.FullName}({n.type})"));
+ args.Player.SendMultipleMatchError(npcs.Select(n => $"{n.FullName}({n.type})"));
}
else
{
var npc = npcs[0];
if (npc.type >= 1 && npc.type < Main.maxNPCTypes && npc.type != 113)
{
- TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY, 50, 20);
+ TSPlayer.Server.SpawnNPC(npc.netID, npc.FullName, amount, args.Player.TileX, args.Player.TileY, 50, 20);
if (args.Silent)
{
args.Player.SendSuccessMessage("Spawned {0} {1} time(s).", npc.FullName, amount);
@@ -2363,11 +2398,11 @@ namespace TShockAPI
if (args.Parameters.Count == 1)
{
- var players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var target = players[0];
@@ -2392,13 +2427,13 @@ namespace TShockAPI
return;
}
- var players1 = TShock.Utils.FindPlayer(args.Parameters[0]);
- var players2 = TShock.Utils.FindPlayer(args.Parameters[1]);
+ var players1 = TSPlayer.FindByNameOrID(args.Parameters[0]);
+ var players2 = TSPlayer.FindByNameOrID(args.Parameters[1]);
if (players2.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
else if (players2.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players2.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players2.Select(p => p.Name));
else if (players1.Count == 0)
{
if (args.Parameters[0] == "*")
@@ -2438,7 +2473,7 @@ namespace TShockAPI
args.Player.SendErrorMessage("Invalid player!");
}
else if (players1.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players1.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players1.Select(p => p.Name));
else
{
var source = players1[0];
@@ -2487,7 +2522,7 @@ namespace TShockAPI
}
string playerName = String.Join(" ", args.Parameters);
- var players = TShock.Utils.FindPlayer(playerName);
+ var players = TSPlayer.FindByNameOrID(playerName);
if (players.Count == 0)
{
if (playerName == "*")
@@ -2511,7 +2546,7 @@ namespace TShockAPI
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var plr = players[0];
@@ -2550,7 +2585,7 @@ namespace TShockAPI
if (matches.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, matches.Select(n => $"{n.FullName}({n.whoAmI})"));
+ args.Player.SendMultipleMatchError(matches.Select(n => $"{n.FullName}({n.whoAmI})"));
return;
}
if (matches.Count == 0)
@@ -2572,14 +2607,14 @@ namespace TShockAPI
player = String.Join(" ", args.Parameters);
}
- var players = TShock.Utils.FindPlayer(player);
+ var players = TSPlayer.FindByNameOrID(player);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
}
else
{
@@ -2733,7 +2768,7 @@ namespace TShockAPI
return;
}
- var foundplr = TShock.Utils.FindPlayer(args.Parameters[1]);
+ var foundplr = TSPlayer.FindByNameOrID(args.Parameters[1]);
if (foundplr.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
@@ -2741,7 +2776,7 @@ namespace TShockAPI
}
else if (foundplr.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, foundplr.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(foundplr.Select(p => p.Name));
return;
}
@@ -2865,6 +2900,7 @@ namespace TShockAPI
"add - Adds a new group.",
"addperm - Adds permissions to a group.",
"color - Changes a group's chat color.",
+ "rename - Changes a group's name.",
"del - Deletes a group.",
"delperm - Removes permissions from a group.",
"list [page] - Lists groups.",
@@ -3074,6 +3110,29 @@ namespace TShockAPI
}
#endregion
return;
+ case "rename":
+ #region Rename group
+ {
+ if (args.Parameters.Count != 3)
+ {
+ args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}group rename ", Specifier);
+ return;
+ }
+
+ string group = args.Parameters[1];
+ string newName = args.Parameters[2];
+ try
+ {
+ string response = TShock.Groups.RenameGroup(group, newName);
+ args.Player.SendSuccessMessage(response);
+ }
+ catch (GroupManagerException ex)
+ {
+ args.Player.SendErrorMessage(ex.Message);
+ }
+ }
+ #endregion
+ return;
case "del":
#region Delete group
{
@@ -3168,7 +3227,7 @@ namespace TShockAPI
args.Player.SendErrorMessage("Invalid group.");
return;
}
- Group grp = TShock.Utils.GetGroup(args.Parameters[1]);
+ Group grp = TShock.Groups.GetGroupByName(args.Parameters[1]);
List permissions = grp.TotalPermissions;
PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(permissions),
@@ -3181,6 +3240,9 @@ namespace TShockAPI
}
#endregion
return;
+ default:
+ args.Player.SendErrorMessage("Invalid subcommand! Type {0}group help for more information on valid commands.", Specifier);
+ return;
}
}
#endregion Group Management
@@ -3208,11 +3270,36 @@ namespace TShockAPI
}
else if (items.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, items.Select(i => $"{i.Name}({i.netID})"));
+ args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})"));
}
else
{
- TShock.Itembans.AddNewBan(EnglishLanguage.GetItemNameById(items[0].type));
+ // Yes this is required because of localization
+ // User may have passed in localized name but itembans works on English names
+ string englishNameForStorage = EnglishLanguage.GetItemNameById(items[0].type);
+ TShock.Itembans.AddNewBan(englishNameForStorage);
+
+ // It was decided in Telegram that we would continue to ban
+ // projectiles based on whether or not their associated item was
+ // banned. However, it was also decided that we'd change the way
+ // this worked: in particular, we'd make it so that the item ban
+ // system just adds things to the projectile ban system at the
+ // command layer instead of inferring the state of projectile
+ // bans based on the state of the item ban system.
+
+ if (items[0].type == ItemID.DirtRod)
+ {
+ TShock.ProjectileBans.AddNewBan(ProjectileID.DirtBall);
+ }
+
+ if (items[0].type == ItemID.Sandgun)
+ {
+ TShock.ProjectileBans.AddNewBan(ProjectileID.SandBallGun);
+ TShock.ProjectileBans.AddNewBan(ProjectileID.EbonsandBallGun);
+ TShock.ProjectileBans.AddNewBan(ProjectileID.PearlSandBallGun);
+ }
+
+ // This returns the localized name to the player, not the item as it was stored.
args.Player.SendSuccessMessage("Banned " + items[0].Name + ".");
}
}
@@ -3234,7 +3321,7 @@ namespace TShockAPI
}
else if (items.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, items.Select(i => $"{i.Name}({i.netID})"));
+ args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})"));
}
else
{
@@ -3279,7 +3366,7 @@ namespace TShockAPI
}
else if (items.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, items.Select(i => $"{i.Name}({i.netID})"));
+ args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})"));
}
else
{
@@ -3305,7 +3392,7 @@ namespace TShockAPI
}
else if (items.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, items.Select(i => $"{i.Name}({i.netID})"));
+ args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})"));
}
else
{
@@ -3741,7 +3828,8 @@ namespace TShockAPI
private static void Reload(CommandArgs args)
{
- TShock.Utils.Reload(args.Player);
+ TShock.Utils.Reload();
+ Hooks.GeneralHooks.OnReloadEvent(args.Player);
args.Player.SendSuccessMessage(
"Configuration, permissions, and regions reload complete. Some changes may require a server restart.");
@@ -3872,7 +3960,7 @@ namespace TShockAPI
if (!Main.dayTime)
time += 15.0;
time = time % 24.0;
- args.Player.SendInfoMessage("The current time is {0}:{1:D2}.", (int)Math.Floor(time), (int)Math.Round((time % 1.0) * 60.0));
+ args.Player.SendInfoMessage("The current time is {0}:{1:D2}.", (int)Math.Floor(time), (int)Math.Floor((time % 1.0) * 60.0));
return;
}
@@ -4018,14 +4106,14 @@ namespace TShockAPI
}
string plStr = args.Parameters[0];
- var players = TShock.Utils.FindPlayer(plStr);
+ var players = TSPlayer.FindByNameOrID(plStr);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
}
else
{
@@ -4117,7 +4205,7 @@ namespace TShockAPI
var width = Math.Abs(args.Player.TempPoints[0].X - args.Player.TempPoints[1].X);
var height = Math.Abs(args.Player.TempPoints[0].Y - args.Player.TempPoints[1].Y);
- if (TShock.Regions.AddRegion(x, y, width, height, regionName, args.Player.User.Name,
+ if (TShock.Regions.AddRegion(x, y, width, height, regionName, args.Player.Account.Name,
Main.worldID.ToString()))
{
args.Player.TempPoints[0] = Point.Zero;
@@ -4206,7 +4294,7 @@ namespace TShockAPI
regionName = regionName + " " + args.Parameters[i];
}
}
- if (TShock.Users.GetUserByName(playerName) != null)
+ if (TShock.UserAccounts.GetUserAccountByName(playerName) != null)
{
if (TShock.Regions.AddNewUser(regionName, playerName))
{
@@ -4241,7 +4329,7 @@ namespace TShockAPI
regionName = regionName + " " + args.Parameters[i];
}
}
- if (TShock.Users.GetUserByName(playerName) != null)
+ if (TShock.UserAccounts.GetUserAccountByName(playerName) != null)
{
if (TShock.Regions.RemoveUser(regionName, playerName))
{
@@ -4382,9 +4470,9 @@ namespace TShockAPI
{
IEnumerable sharedUsersSelector = region.AllowedIDs.Select(userId =>
{
- User user = TShock.Users.GetUserByID(userId);
- if (user != null)
- return user.Name;
+ UserAccount account = TShock.UserAccounts.GetUserAccountByID(userId);
+ if (account != null)
+ return account.Name;
return string.Concat("{ID: ", userId, "}");
});
@@ -4530,6 +4618,51 @@ namespace TShockAPI
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}region resize ", Specifier);
break;
}
+ case "rename":
+ {
+ if (args.Parameters.Count != 3)
+ {
+ args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}region rename ", Specifier);
+ break;
+ }
+ else
+ {
+ string oldName = args.Parameters[1];
+ string newName = args.Parameters[2];
+
+ if (oldName == newName)
+ {
+ args.Player.SendErrorMessage("Error: both names are the same.");
+ break;
+ }
+
+ Region oldRegion = TShock.Regions.GetRegionByName(oldName);
+
+ if (oldRegion == null)
+ {
+ args.Player.SendErrorMessage("Invalid region \"{0}\".", oldName);
+ break;
+ }
+
+ Region newRegion = TShock.Regions.GetRegionByName(newName);
+
+ if (newRegion != null)
+ {
+ args.Player.SendErrorMessage("Region \"{0}\" already exists.", newName);
+ break;
+ }
+
+ if(TShock.Regions.RenameRegion(oldName, newName))
+ {
+ args.Player.SendInfoMessage("Region renamed successfully!");
+ }
+ else
+ {
+ args.Player.SendErrorMessage("Failed to rename the region.");
+ }
+ }
+ break;
+ }
case "tp":
{
if (!args.Player.HasPermission(Permissions.tp))
@@ -4570,6 +4703,7 @@ namespace TShockAPI
"define - Defines the region with the given name.",
"delete - Deletes the given region.",
"name [-u][-z][-p] - Shows the name of the region at the given point.",
+ "rename - Renames the given region.",
"list - Lists all regions.",
"resize - Resizes a region.",
"allow - Allows a user to a region.",
@@ -4633,7 +4767,7 @@ namespace TShockAPI
}
IEnumerable cmdNames = from cmd in ChatCommands
- where cmd.CanRun(args.Player) && (cmd.Name != "auth" || TShock.AuthToken != 0)
+ where cmd.CanRun(args.Player) && (cmd.Name != "auth" || TShock.SetupToken != 0)
select Specifier + cmd.Name;
PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(cmdNames),
@@ -4715,9 +4849,27 @@ namespace TShockAPI
return;
}
- args.Player.SendSuccessMessage("Online Players ({0}/{1})", TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots);
+ args.Player.SendSuccessMessage("Online Players ({0}/{1})", TShock.Utils.GetActivePlayerCount(), TShock.Config.MaxSlots);
+
+ var players = new List();
+
+ foreach (TSPlayer ply in TShock.Players)
+ {
+ if (ply != null && ply.Active)
+ {
+ if (displayIdsRequested)
+ {
+ players.Add(String.Format("{0} (ID: {1}{2})", ply.Name, ply.Index, ply.Account != null ? ", ID: " + ply.Account.ID : ""));
+ }
+ else
+ {
+ players.Add(ply.Name);
+ }
+ }
+ }
+
PaginationTools.SendPage(
- args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(TShock.Utils.GetPlayers(displayIdsRequested)),
+ args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(players),
new PaginationTools.Settings
{
IncludeHeader = false,
@@ -4726,58 +4878,57 @@ namespace TShockAPI
);
}
- private static void AuthToken(CommandArgs args)
+ private static void SetupToken(CommandArgs args)
{
- if (TShock.AuthToken == 0)
+ if (TShock.SetupToken == 0)
{
if (args.Player.Group.Name == new SuperAdminGroup().Name)
- args.Player.SendInfoMessage("The auth system is already disabled.");
+ args.Player.SendInfoMessage("The initial setup system is already disabled.");
else
{
- args.Player.SendWarningMessage("The auth system is disabled. This incident has been logged.");
- TShock.Utils.ForceKick(args.Player, "Auth system is disabled.", true, true);
- TShock.Log.Warn("{0} attempted to use {1}auth even though it's disabled.", args.Player.IP, Specifier);
+ args.Player.SendWarningMessage("The initial setup system is disabled. This incident has been logged.");
+ TShock.Log.Warn("{0} attempted to use the initial setup system even though it's disabled.", args.Player.IP);
return;
}
}
// If the user account is already a superadmin (permanent), disable the system
- if (args.Player.IsLoggedIn && args.Player.tempGroup == null && args.Player.Group.Name == new SuperAdminGroup().Name)
+ if (args.Player.IsLoggedIn && args.Player.tempGroup == null)
{
- args.Player.SendSuccessMessage("Your new account has been verified, and the {0}auth system has been turned off.", Specifier);
+ args.Player.SendSuccessMessage("Your new account has been verified, and the {0}setup system has been turned off.", Specifier);
args.Player.SendSuccessMessage("You can always use the {0}user command to manage players.", Specifier);
- args.Player.SendSuccessMessage("The auth system will remain disabled as long as a superadmin exists (even if you delete auth.lck).");
+ args.Player.SendSuccessMessage("The setup system will remain disabled as long as a superadmin exists (even if you delete setup.lock).");
args.Player.SendSuccessMessage("Share your server, talk with other admins, and more on our forums -- https://tshock.co/");
args.Player.SendSuccessMessage("Thank you for using TShock for Terraria!");
- FileTools.CreateFile(Path.Combine(TShock.SavePath, "auth.lck"));
- File.Delete(Path.Combine(TShock.SavePath, "authcode.txt"));
- TShock.AuthToken = 0;
+ FileTools.CreateFile(Path.Combine(TShock.SavePath, "setup.lock"));
+ File.Delete(Path.Combine(TShock.SavePath, "setup-code.txt"));
+ TShock.SetupToken = 0;
return;
}
if (args.Parameters.Count == 0)
{
- args.Player.SendErrorMessage("You must provide an auth code!");
+ args.Player.SendErrorMessage("You must provide a setup code!");
return;
}
int givenCode;
- if (!Int32.TryParse(args.Parameters[0], out givenCode) || givenCode != TShock.AuthToken)
+ if (!Int32.TryParse(args.Parameters[0], out givenCode) || givenCode != TShock.SetupToken)
{
- args.Player.SendErrorMessage("Incorrect auth code. This incident has been logged.");
- TShock.Log.Warn(args.Player.IP + " attempted to use an incorrect auth code.");
+ args.Player.SendErrorMessage("Incorrect setup code. This incident has been logged.");
+ TShock.Log.Warn(args.Player.IP + " attempted to use an incorrect setup code.");
return;
}
if (args.Player.Group.Name != "superadmin")
args.Player.tempGroup = new SuperAdminGroup();
- args.Player.SendInfoMessage("Superadmin has been temporarily given to you. It will be removed on logout.");
+ args.Player.SendInfoMessage("Temporary system access has been given to you, so you can run one command.");
args.Player.SendInfoMessage("Please use the following to create a permanent account for you.");
- args.Player.SendInfoMessage("{0}user add superadmin", Specifier);
- args.Player.SendInfoMessage("Creates: with the password as part of the superadmin group.");
+ args.Player.SendInfoMessage("{0}user add owner", Specifier);
+ args.Player.SendInfoMessage("Creates: with the password as part of the owner group.");
args.Player.SendInfoMessage("Please use {0}login after this process.", Specifier);
- args.Player.SendInfoMessage("If you understand, please {0}login now, and then type {0}auth.", Specifier);
+ args.Player.SendInfoMessage("If you understand, please {0}login now, and then type {0}setup.", Specifier);
return;
}
@@ -4826,14 +4977,14 @@ namespace TShockAPI
return;
}
- var players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
}
else if (players[0].HasPermission(Permissions.mute))
{
@@ -4858,12 +5009,12 @@ namespace TShockAPI
private static void Motd(CommandArgs args)
{
- TShock.Utils.ShowFileToUser(args.Player, FileTools.MotdPath);
+ args.Player.SendFileTextAsMessage(FileTools.MotdPath);
}
private static void Rules(CommandArgs args)
{
- TShock.Utils.ShowFileToUser(args.Player, FileTools.RulesPath);
+ args.Player.SendFileTextAsMessage(FileTools.RulesPath);
}
private static void Whisper(CommandArgs args)
@@ -4874,14 +5025,14 @@ namespace TShockAPI
return;
}
- var players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
}
else if (args.Player.mute)
{
@@ -4926,11 +5077,11 @@ namespace TShockAPI
int annoy = 5;
int.TryParse(args.Parameters[1], out annoy);
- var players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var ply = players[0];
@@ -4946,11 +5097,11 @@ namespace TShockAPI
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}confuse ", Specifier);
return;
}
- var players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var ply = players[0];
@@ -4966,11 +5117,11 @@ namespace TShockAPI
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}rocket ", Specifier);
return;
}
- var players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
var ply = players[0];
@@ -4995,11 +5146,11 @@ namespace TShockAPI
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}firework [red|green|blue|yellow]", Specifier);
return;
}
- var players = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var players = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (players.Count == 0)
args.Player.SendErrorMessage("Invalid player!");
else if (players.Count > 1)
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
else
{
int type = 167;
@@ -5056,6 +5207,14 @@ namespace TShockAPI
args.Player.SendErrorMessage("No command or command alias matching \"{0}\" found.", givenCommandName);
}
+ private static void CreateDumps(CommandArgs args)
+ {
+ TShock.Utils.DumpPermissionMatrix("PermissionMatrix.txt");
+ TShock.Utils.Dump(false);
+ args.Player.SendSuccessMessage("Your reference dumps have been created in the server folder.");
+ return;
+ }
+
#endregion General Commands
#region Cheat Commands
@@ -5155,14 +5314,14 @@ namespace TShockAPI
}
string plStr = String.Join(" ", args.Parameters);
- var players = TShock.Utils.FindPlayer(plStr);
+ var players = TSPlayer.FindByNameOrID(plStr);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
}
else
{
@@ -5193,7 +5352,7 @@ namespace TShockAPI
}
else if (npcs.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, npcs.Select(n => $"{n.FullName}({n.type})"));
+ args.Player.SendMultipleMatchError(npcs.Select(n => $"{n.FullName}({n.type})"));
return;
}
else
@@ -5248,7 +5407,7 @@ namespace TShockAPI
}
else if (matchedItems.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, matchedItems.Select(i => $"{i.Name}({i.netID})"));
+ args.Player.SendMultipleMatchError(matchedItems.Select(i => $"{i.Name}({i.netID})"));
return;
}
else
@@ -5278,7 +5437,7 @@ namespace TShockAPI
if (prefixIds.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, prefixIds.Select(p => p.ToString()));
+ args.Player.SendMultipleMatchError(prefixIds.Select(p => p.ToString()));
return;
}
else if (prefixIds.Count == 0)
@@ -5297,7 +5456,7 @@ namespace TShockAPI
if (itemAmount == 0 || itemAmount > item.maxStack)
itemAmount = item.maxStack;
- if (args.Player.GiveItemCheck(item.type, EnglishLanguage.GetItemNameById(item.type), item.width, item.height, itemAmount, prefixId))
+ if (args.Player.GiveItemCheck(item.type, EnglishLanguage.GetItemNameById(item.type), itemAmount, prefixId))
{
item.prefix = (byte)prefixId;
args.Player.SendSuccessMessage("Gave {0} {1}(s).", itemAmount, item.AffixName());
@@ -5331,7 +5490,7 @@ namespace TShockAPI
}
else if (npcs.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, npcs.Select(n => $"{n.FullName}({n.type})"));
+ args.Player.SendMultipleMatchError(npcs.Select(n => $"{n.FullName}({n.type})"));
return;
}
else if (args.Parameters[1].Length > 200)
@@ -5396,7 +5555,7 @@ namespace TShockAPI
}
else if (items.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, items.Select(i => $"{i.Name}({i.netID})"));
+ args.Player.SendMultipleMatchError(items.Select(i => $"{i.Name}({i.netID})"));
}
else
{
@@ -5420,14 +5579,14 @@ namespace TShockAPI
if (item.type >= 1 && item.type < Main.maxItemTypes)
{
- var players = TShock.Utils.FindPlayer(plStr);
+ var players = TSPlayer.FindByNameOrID(plStr);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
}
else
{
@@ -5436,7 +5595,7 @@ namespace TShockAPI
{
if (itemAmount == 0 || itemAmount > item.maxStack)
itemAmount = item.maxStack;
- if (plr.GiveItemCheck(item.type, EnglishLanguage.GetItemNameById(item.type), item.width, item.height, itemAmount, prefix))
+ if (plr.GiveItemCheck(item.type, EnglishLanguage.GetItemNameById(item.type), itemAmount, prefix))
{
args.Player.SendSuccessMessage(string.Format("Gave {0} {1} {2}(s).", plr.Name, itemAmount, item.Name));
plr.SendSuccessMessage(string.Format("{0} gave you {1} {2}(s).", args.Player.Name, itemAmount, item.Name));
@@ -5466,7 +5625,7 @@ namespace TShockAPI
if (args.Parameters.Count > 0)
{
string plStr = String.Join(" ", args.Parameters);
- var players = TShock.Utils.FindPlayer(plStr);
+ var players = TSPlayer.FindByNameOrID(plStr);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
@@ -5474,7 +5633,7 @@ namespace TShockAPI
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
return;
}
else
@@ -5523,7 +5682,7 @@ namespace TShockAPI
}
else if (found.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, found.Select(f => Lang.GetBuffName(f)));
+ args.Player.SendMultipleMatchError(found.Select(f => Lang.GetBuffName(f)));
return;
}
id = found[0];
@@ -5551,7 +5710,7 @@ namespace TShockAPI
}
int id = 0;
int time = 60;
- var foundplr = TShock.Utils.FindPlayer(args.Parameters[0]);
+ var foundplr = TSPlayer.FindByNameOrID(args.Parameters[0]);
if (foundplr.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
@@ -5559,7 +5718,7 @@ namespace TShockAPI
}
else if (foundplr.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, foundplr.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(foundplr.Select(p => p.Name));
return;
}
else
@@ -5574,7 +5733,7 @@ namespace TShockAPI
}
else if (found.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, found.Select(b => Lang.GetBuffName(b)));
+ args.Player.SendMultipleMatchError(found.Select(b => Lang.GetBuffName(b)));
return;
}
id = found[0];
@@ -5683,7 +5842,7 @@ namespace TShockAPI
return;
}
string plStr = String.Join(" ", args.Parameters);
- var players = TShock.Utils.FindPlayer(plStr);
+ var players = TSPlayer.FindByNameOrID(plStr);
if (players.Count == 0)
{
args.Player.SendErrorMessage("Invalid player!");
@@ -5691,7 +5850,7 @@ namespace TShockAPI
}
else if (players.Count > 1)
{
- TShock.Utils.SendMultipleMatchError(args.Player, players.Select(p => p.Name));
+ args.Player.SendMultipleMatchError(players.Select(p => p.Name));
return;
}
else
diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs
index e5fa3320..d9a5b559 100644
--- a/TShockAPI/ConfigFile.cs
+++ b/TShockAPI/ConfigFile.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -27,506 +27,497 @@ using Rests;
namespace TShockAPI
{
- /// ConfigFile - The config file class, which contains the configuration for a server that is serialized into JSON and deserialized on load.
+ /// The config file class, which contains the configuration for a server that is serialized into JSON and deserialized on load.
public class ConfigFile
{
- /// InvasionMultiplier - The equation for calculating invasion size = 100 + (multiplier * (number of active players > 200 hp)).
- [Description("The equation for calculating invasion size is 100 + (multiplier * (number of active players with greater than 200 health)).")]
+ /// Determines the size of invasion events. The equation for calculating invasion size = 100 + (multiplier * (number of active players > 200 hp)).
+ [Description("Determines the size of invasion events.\nThe equation for calculating invasion size is 100 + (multiplier * (number of active players with greater than 200 health)).")]
public int InvasionMultiplier = 1;
- /// DefaultMaximumSpawns - The default max spawns per wave.
- [Description("The default maximum mobs that will spawn per wave. Higher means more mobs in that wave.")]
+ /// The default maximum number of mobs that will spawn per wave. Higher means more mobs in that wave.
+ [Description("The default maximum number of mobs that will spawn per wave. Higher means more mobs in that wave.")]
public int DefaultMaximumSpawns = 5;
- /// DefaultSpawnRate - The default spawn rate.
+ /// The delay between waves. Lower values lead to more mobs.
[Description("The delay between waves. Lower values lead to more mobs.")]
public int DefaultSpawnRate = 600;
- /// ServerPort - The configured server port.
+ /// The port the server runs on.
[Description("The port the server runs on.")]
public int ServerPort = 7777;
- /// EnableWhitelist - boolean if the whitelist functionality should be turned on.
- [Description("Enable or disable the whitelist based on IP addresses in whitelist.txt.")]
+ /// Enable or disable the whitelist based on IP addresses in the whitelist.txt file.
+ [Description("Enable or disable the whitelist based on IP addresses in the whitelist.txt file.")]
public bool EnableWhitelist;
- /// InfiniteInvasion - Whether or not infinite invasion mode should be on.
- [Description("Enable the ability for invasion size to never decrease. Make sure to run /invade, and note that this adds 2 million+ goblins to the spawn queue for the map.")]
+ /// Enables never-ending invasion events. You still need to start the event.
+ [Description("Enables never ending invasion events. You still need to start the event, such as with the /invade command.")]
public bool InfiniteInvasion;
- /// PvPMode - The server PvP mode (normal, always, or disabled).
- [Description("Set the server pvp mode. Valid types are: \"normal\", \"always\" and \"disabled\".")]
+ /// Sets the PvP mode. Valid types are: "normal", "always", "disabled".
+ [Description("Sets the PvP mode. Valid types are: \"normal\", \"always\" and \"disabled\".")]
public string PvPMode = "normal";
- /// SpawnProtection - Enables the spawn protection system.
+ /// Prevents tiles from being placed within SpawnProtectionRadius of the default spawn.
[Description("Prevents tiles from being placed within SpawnProtectionRadius of the default spawn.")]
public bool SpawnProtection = true;
- /// SpawnProtectionRadius - The spawn protection tile radius.
- [Description("Radius from spawn tile for SpawnProtection.")]
+ /// The tile radius around the spawn tile that is protected by the SpawnProtection setting.
+ [Description("The tile radius around the spawn tile that is protected by the SpawnProtection setting.")]
public int SpawnProtectionRadius = 10;
- /// MaxSlots - The server's max slots.
- [Description("Max slots for the server. If you want people to be kicked with \"Server is full\" set this to how many players you want max and then set Terraria max players to 2 higher.")]
+ /// Maximum number of clients connected at once. If lower than Terraria's setting, the server will kick excess connections.
+ [Description("Maximum number of clients connected at once.\nIf you want people to be kicked with \"Server is full\" set this to how many players you want max and then set Terraria max players to 2 higher.")]
public int MaxSlots = 8;
- /// RangeChecks - Whether or not the anti-grief system based on range should be enabled.
- [Description("Global protection agent for any block distance based anti-grief check.")]
+ /// Enable or disable anti-cheat range checks based on distance between the player and their block placements.
+ [Description("Enable or disable anti-cheat range checks based on distance between the player and their block placements.")]
public bool RangeChecks = true;
- /// DisableBuild - Whether or not building should be enabled.
- [Description("Disables any building / placing of blocks.")]
+ /// Disables any placing, or removal of blocks.
+ [Description("Disables any placing, or removal of blocks.")]
public bool DisableBuild;
- /// SuperAdminChatRGB - The chat color for the superadmin group.
- [Description("#.#.# = Red/Blue/Green - RGB Colors for the Admin Chat Color. Max value: 255.")]
- public int[] SuperAdminChatRGB = { 255, 0, 0 };
+ /// The chat color for the superadmin group.
+ [Description("The chat color for the superadmin group.\n#.#.# = Red/Blue/Green\nMax value: 255")]
+ public int[] SuperAdminChatRGB = { 255, 255, 255 };
- /// SuperAdminChatPrefix - The superadmin chat prefix.
- [Description("Super admin group chat prefix.")]
- public string SuperAdminChatPrefix = "(Admin) ";
+ /// The superadmin chat prefix.
+ [Description("The superadmin chat prefix.")]
+ public string SuperAdminChatPrefix = "(Super Admin) ";
- /// SuperAdminChatSuffix - The superadmin chat suffix.
- [Description("Super admin group chat suffix.")]
+ /// The superadmin chat suffix.
+ [Description("The superadmin chat suffix.")]
public string SuperAdminChatSuffix = "";
- /// BackupInterval - The backup frequency in minutes.
- [Description("Backup frequency in minutes. So, a value of 60 = 60 minutes. Backups are stored in the \\tshock\\backups folder.")]
+ /// The interval between backups, in minutes. Backups are stored in the tshock/backups folder.
+ [Description("The interval between backups, in minutes. Backups are stored in the tshock/backups folder.")]
public int BackupInterval;
- /// BackupKeepFor - Backup max age in minutes.
- [Description("How long backups are kept in minutes. 2880 = 2 days.")]
+ /// For how long backups are kept in minutes.
+ [Description("For how long backups are kept in minutes.\neg. 2880 = 2 days.")]
public int BackupKeepFor = 60;
- /// RememberLeavePos - Whether or not to remember where an IP player was when they left.
- [Description("Remembers where a player left off. It works by remembering the IP, NOT the character.\neg. When you try to disconnect, and reconnect to be automatically placed at spawn, you'll be at your last location. Note: Won't save after server restarts.")]
+ /// Remembers where a player left off, based on their IP. Does not persist through server restarts.
+ [Description("Remembers where a player left off, based on their IP. Does not persist through server restarts.\neg. When you try to disconnect, and reconnect to be automatically placed at spawn, you'll be at your last location.")]
public bool RememberLeavePos;
- /// HardcoreOnly - Whether or not HardcoreOnly should be enabled.
- [Description("Hardcore players ONLY. This means softcore players cannot join.")]
+ /// Prevents non-hardcore players from connecting.
+ [Description("Prevents non-hardcore players from connecting.")]
public bool HardcoreOnly;
- /// MediumcoreOnly - Whether or not MediumCore only players should be enabled.
- [Description("Mediumcore players ONLY. This means softcore players cannot join.")]
+ /// Prevents softcore players from connecting.
+ [Description("Prevents softcore players from connecting.")]
public bool MediumcoreOnly;
- /// KickOnMediumcoreDeath - Whether or not to kick mediumcore players on death.
- [Description("Kicks a mediumcore player on death.")]
+ /// Whether or not to kick mediumcore players on death.
+ [Description("Whether or not to kick mediumcore players on death.")]
public bool KickOnMediumcoreDeath;
- /// BanOnMediumcoreDeath - Whether or not to ban mediumcore players on death.
- [Description("Bans a mediumcore player on death.")]
+ /// Whether or not to ban mediumcore players on death.
+ [Description("Whether or not to ban mediumcore players on death.")]
public bool BanOnMediumcoreDeath;
- /// AutoSave - Whether or not to use Terraria's built-in world auto save.
- [Description("Enable/disable Terraria's built in auto save.")]
+ /// Enable or disable Terraria's built-in world auto save.
+ [Description("Enable or disable Terraria's built-in world auto save.")]
public bool AutoSave = true;
- /// AnnounceSave - Whether or not to broadcast world saves.
- [Description("Enable/disable save announcements.")]
+ /// Enable or disable world save announcements.
+ [Description("Enable or disable world save announcements.")]
public bool AnnounceSave = true;
- /// MaximumLoginAttempts - Number of failed login attempts before kicking a player.
+ /// Number of failed login attempts before kicking the player.
[Description("Number of failed login attempts before kicking the player.")]
public int MaximumLoginAttempts = 3;
- /// ServerName - Used when replying to a REST /status request or sent to the client.
+ /// Replaces the world name during a session if UseServerName is true.
[Description("Replaces the world name during a session if UseServerName is true.")]
public string ServerName = "";
- /// UseServerName - Whether or not to use ServerName in place of the world name.
- [Description("Sends ServerName in place of the world name to clients.")]
+ /// Whether or not to use ServerName in place of the world name.
+ [Description("Whether or not to use ServerName in place of the world name.")]
public bool UseServerName = false;
- /// StorageType - The type of SQL database to use when storing data (either "sqlite" or "mysql").
- [Description("Valid types are \"sqlite\" and \"mysql\".")]
+ /// The type of database to use when storing data (either "sqlite" or "mysql").
+ [Description("The type of database to use when storing data (either \"sqlite\" or \"mysql\").")]
public string StorageType = "sqlite";
- /// MySqlHost - The hostname and port to to use when connecting to a MySQL database.
+ /// The MySQL hostname and port to direct connections to.
[Description("The MySQL hostname and port to direct connections to.")]
public string MySqlHost = "localhost:3306";
- /// MySqlDbName - The database name to use when connecting to a MySQL database.
- [Description("Database name to connect to.")]
+ /// The database name to connect to when using MySQL as the database type.
+ [Description("The database name to connect to when using MySQL as the database type.")]
public string MySqlDbName = "";
- /// MySqlUsername - The username for the login credentials used when connecting to a MySQL database.
- [Description("Database username to connect with.")]
+ /// The username used when connecting to a MySQL database.
+ [Description("The username used when connecting to a MySQL database.")]
public string MySqlUsername = "";
- /// MySqlPassword - The password for the login credentials used when connecting to a MySQL database.
- [Description("Database password to connect with.")]
+ /// The password used when connecting to a MySQL database.
+ [Description("The password used when connecting to a MySQL database.")]
public string MySqlPassword = "";
- /// MediumcoreBanReason - The reason given if banning mediumcore players on death.
- [Description("The reason given when banning a mediumcore player on death if BanOnMediumcoreDeath is set to true.")]
+ /// The reason given if banning a mediumcore player on death.
+ [Description("The reason given if banning a mediumcore player on death.")]
public string MediumcoreBanReason = "Death results in a ban";
- /// MediumcoreKickReason - The reason given if kicking mediumcore players on death.
- [Description("The reason given when kicking a mediumcore player on death if KickOnMediumcoreDeath is set to true.")]
+ /// The reason given if kicking a mediumcore players on death.
+ [Description("The reason given if kicking a mediumcore players on death.")]
public string MediumcoreKickReason = "Death results in a kick";
- /// EnableIPBans - Whether or not to kick players on join that match a banned IP address.
- [Description("Enables kicking of banned users by matching their IP Address.")]
+ /// Enables kicking banned users by matching their IP Address.
+ [Description("Enables kicking banned users by matching their IP Address.")]
public bool EnableIPBans = true;
- /// EnableUUIDBans - Whether or not to kick players on join that match a banned UUID.
- [Description("Enables kicking of banned users by matching their client UUID.")]
+ /// Enables kicking banned users by matching their client UUID.
+ [Description("Enables kicking banned users by matching their client UUID.")]
public bool EnableUUIDBans = true;
- /// EnableBanOnUsernames - Whether or not to kick players on join that match a banned character name.
- [Description("Enables kicking of banned users by matching their Character Name.")]
+ /// Enables kicking banned users by matching their Character Name.
+ [Description("Enables kicking banned users by matching their Character Name.")]
public bool EnableBanOnUsernames;
- /// DefaultRegistrationGroupName - The default group name to place newly registered users under.
- [Description("Selects the default group name to place new registrants under.")]
+ /// The default group name to place newly registered users under.
+ [Description("The default group name to place newly registered users under.")]
public string DefaultRegistrationGroupName = "default";
- /// DefaultGuestGroupName - The default group name to place unregistered players under.
- [Description("Selects the default group name to place unregistered players under.")]
+ /// The default group name to place unregistered players under.
+ [Description("The default group name to place unregistered players under.")]
public string DefaultGuestGroupName = "guest";
- /// DisableSpewLogs - Whether or not to send logs as messages to players with the log permission.
- [Description("Force-disable printing logs to players with the log permission.")]
+ /// Disables sending logs as messages to players with the log permission.
+ [Description("Disables sending logs as messages to players with the log permission.")]
public bool DisableSpewLogs = true;
- /// DisableSecondUpdateLogs - Prevents OnSecondUpdate() checks from writing to the log file.
+ /// Prevents OnSecondUpdate checks from writing to the log file.
[Description("Prevents OnSecondUpdate checks from writing to the log file.")]
public bool DisableSecondUpdateLogs = false;
- /// HashAlgorithm - The hash algorithm used to encrypt user passwords.
+ /// The hash algorithm used to encrypt user passwords.
/// Valid types: "sha512", "sha256" and "md5". Append with "-xp" for the xp supported algorithms.
[Description("The hash algorithm used to encrypt user passwords. Valid types: \"sha512\", \"sha256\" and \"md5\". Append with \"-xp\" for the xp supported algorithms.")]
public string HashAlgorithm = "sha512";
- /// ServerFullReason - The reason given when kicking players when the server is full.
- [Description("String that is used when kicking people when the server is full.")]
+ /// The reason given when kicking players that attempt to join while the server is full.
+ [Description("The reason given when kicking players that attempt to join while the server is full.")]
public string ServerFullReason = "Server is full";
- /// WhitelistKickReason - The reason given when kicking players for not being on the whitelist.
- [Description("String that is used when a user is kicked due to not being on the whitelist.")]
+ /// The reason given when kicking players for not being on the whitelist.
+ [Description("The reason given when kicking players for not being on the whitelist.")]
public string WhitelistKickReason = "You are not on the whitelist.";
- /// ServerFullNoReservedReason - The reason given when kicking players when the server is full and there are no reserved slots open.
- [Description("String that is used when kicking people when the server is full with no reserved slots.")]
+ /// The reason given when kicking players that attempt to join while the server is full with no reserved slots available.
+ [Description("The reason given when kicking players that attempt to join while the server is full with no reserved slots available.")]
public string ServerFullNoReservedReason = "Server is full. No reserved slots open.";
- /// SaveWorldOnCrash - Attempts to save world in the server crashes due to an unhandled exception.
- [Description("This will save the world if Terraria crashes from an unhandled exception.")]
+ /// Whether or not to save the world if the server crashes from an unhandled exception.
+ [Description("Whether or not to save the world if the server crashes from an unhandled exception.")]
public bool SaveWorldOnCrash = true;
- /// EnableGeoIP - Whether or not to announce a player's location on join.
- [Description("This will announce a player's location on join.")]
+ /// Whether or not to announce a player's geographic location on join, based on their IP.
+ [Description("Whether or not to announce a player's geographic location on join, based on their IP.")]
public bool EnableGeoIP;
- /// EnableTokenEndpointAuthentication - Whether or not to require token authentication for the public REST API endpoints.
- [Description("This will turn on token requirement for the public REST API endpoints.")]
+ /// Whether or not to require token authentication to use the public REST API endpoints.
+ [Description("Whether or not to require token authentication to use the public REST API endpoints.")]
public bool EnableTokenEndpointAuthentication;
- /// RestApiEnabled - Enable/disable the REST API.
- [Description("Enable/disable the REST API.")]
+ /// Enable or disable the REST API.
+ [Description("Enable or disable the REST API.")]
public bool RestApiEnabled;
- /// RestApiPort - The port used by the REST API.
- [Description("This is the port which the REST API will listen on.")]
+ /// The port used by the REST API.
+ [Description("The port used by the REST API.")]
public int RestApiPort = 7878;
- /// DisableTombstones - Disable tombstone dropping during death for all players.
- [Description("Disable tombstone dropping during death for all players.")]
+ /// Disables tombstone dropping during death for all players.
+ [Description("Disables tombstone dropping during death for all players.")]
public bool DisableTombstones = true;
- /// DisplayIPToAdmins - Displays a player's IP on join to everyone with the log permission.
- [Description("Displays a player's IP on join to everyone who has the log permission.")]
+ /// Displays a player's IP on join to users with the log permission.
+ [Description("Displays a player's IP on join to users with the log permission.")]
public bool DisplayIPToAdmins;
- /// KickProxyUsers - If the GeoIP service is running, this will kick users under a proxy.
- [Description("Kicks users using a proxy as identified with the GeoIP database.")]
+ /// If GeoIP is enabled, this will kick users identified as being under a proxy.
+ [Description("If GeoIP is enabled, this will kick users identified as being under a proxy.")]
public bool KickProxyUsers = true;
- /// DisableHardmode - If set to true, hardmode will not be activated by the Wall of Flesh or the /starthardmode command.
- [Description("Disables hardmode, can't never be activated. Overrides /starthardmode.")]
+ /// If enabled, hardmode will not be activated by the Wall of Flesh or the /starthardmode command.
+ [Description("If enabled, hardmode will not be activated by the Wall of Flesh or the /starthardmode command.")]
public bool DisableHardmode;
- /// DisableDungeonGuardian - Disables the dungeon guardian from being spawned while sending players to their spawn point instead.
- [Description("Disables the dungeon guardian from being spawned while sending players to their spawn point instead.")]
+ /// Prevents the dungeon guardian from being spawned while sending players to their spawn point instead.
+ [Description("Prevents the dungeon guardian from being spawned while sending players to their spawn point instead.")]
public bool DisableDungeonGuardian;
- /// DisableClownBombs - Disables clown bomb projectiles from spawning.
+ /// Disables clown bomb projectiles from spawning.
[Description("Disables clown bomb projectiles from spawning.")]
public bool DisableClownBombs;
- /// DisableSnowBalls - Disables snow ball projectiles from spawning.
+ /// Disables snow ball projectiles from spawning.
[Description("Disables snow ball projectiles from spawning.")]
public bool DisableSnowBalls;
- /// ChatFormat - Controls the in-game chat format. {0} = Group Name, {1} = Group Prefix, {2} = Player Name, {3} = Group Suffix, {4} = Chat Message.
+ /// Changes in-game chat format: {0} = Group Name, {1} = Group Prefix, {2} = Player Name, {3} = Group Suffix, {4} = Chat Message.
[Description("Changes in-game chat format: {0} = Group Name, {1} = Group Prefix, {2} = Player Name, {3} = Group Suffix, {4} = Chat Message.")]
public string ChatFormat = "{1}{2}{3}: {4}";
- /// ChatAboveHeadsFormat - Modifies the player name when using chat above heads. Same formatting options as ChatFormat.
- [Description("Change the player name when using chat above heads. This begins with a player name wrapped in brackets, as per Terraria's formatting. Same formatting as ChatFormat(minus the text aka {4}).")]
+ /// Changes the player name when using chat above heads. Starts with a player name wrapped in brackets, as per Terraria's formatting.\nSame formatting as ChatFormat without the message.
+ [Description("Changes the player name when using chat above heads. Starts with a player name wrapped in brackets, as per Terraria's formatting.\nSame formatting as ChatFormat without the message.")]
public string ChatAboveHeadsFormat = "{2}";
- /// ForceTime - Can be either "normal", "day" or "night". When set to one of the latter two, the blocks other.
- [Description("Force the world time to be normal, day, or night.")]
+ /// Forces the world time to be normal, day, or night.
+ [Description("Forces the world time to be normal, day, or night.")]
public string ForceTime = "normal";
- /// TileKillThreshold - Disables/reverts a player if this number of tile kills is exceeded within 1 second.
- [Description("Disables/reverts a player if this number of tile kills is exceeded within 1 second.")]
+ /// Disables a player and reverts their actions if this number of tile kills is exceeded within 1 second.
+ [Description("Disables a player and reverts their actions if this number of tile kills is exceeded within 1 second.")]
public int TileKillThreshold = 60;
- /// TilePlaceThreshold - Disables/reverts a player if this number of tile placements is exceeded within 1 second.
- [Description("Disables/reverts a player if this number of tile places is exceeded within 1 second.")]
+ /// Disables a player and reverts their actions if this number of tile places is exceeded within 1 second.
+ [Description("Disables a player and reverts their actions if this number of tile places is exceeded within 1 second.")]
public int TilePlaceThreshold = 20;
- /// TileLiquidThreshold - Disables a player if this number of liquid sets is exceeded within 1 second.
+ /// Disables a player if this number of liquid sets is exceeded within 1 second.
[Description("Disables a player if this number of liquid sets is exceeded within 1 second.")]
public int TileLiquidThreshold = 15;
- /// ProjectileThreshold - Disables a player if this number of projectiles is created within 1 second.
+ /// Disable a player if this number of projectiles is created within 1 second.
[Description("Disable a player if this number of projectiles is created within 1 second.")]
public int ProjectileThreshold = 50;
- /// HealOtherThreshold - Disables a player if this number of HealOtherPlayer packets is sent within 1 second.
+ /// Disables a player if this number of HealOtherPlayer packets is sent within 1 second.
[Description("Disables a player if this number of HealOtherPlayer packets is sent within 1 second.")]
public int HealOtherThreshold = 50;
- /// ProjIgnoreShrapnel - Whether or not to ignore shrapnel from crystal bullets for the projectile threshold count.
- [Description("Ignore shrapnel from crystal bullets for projectile threshold.")]
+ /// Whether or not to ignore shrapnel from crystal bullets for the projectile threshold count.
+ [Description("Whether or not to ignore shrapnel from crystal bullets for the projectile threshold count.")]
public bool ProjIgnoreShrapnel = true;
- /// RequireLogin - Requires all players to register or login before being allowed to play.
- [Description("Requires all players to register or login before being allowed to play.")]
+ /// Require all players to register or login before being allowed to play.
+ [Description("Require all players to register or login before being allowed to play.")]
public bool RequireLogin;
- /// DisableInvisPvP - Whether or not to turn a player invisible if using invisibility potions during PvP.
- [Description("Disables invisibility potions from being used in PvP (Note, can be used in the client, but the effect isn't sent to the rest of the server).")]
+ /// Disables the effect of invisibility potions while PvP is enabled by turning the player visible to the other clients.
+ [Description("Disables the effect of invisibility potions while PvP is enabled by turning the player visible to the other clients.")]
public bool DisableInvisPvP;
- /// MaxRangeForDisabled - The maximum distance, in tiles, that disabled players can move from.
- [Description("The maximum distance players disabled for various reasons can move from.")]
+ /// The maximum distance, in tiles, that disabled players can move from.
+ [Description("The maximum distance, in tiles, that disabled players can move from.")]
public int MaxRangeForDisabled = 10;
- /// ServerPassword - The server password required to join the server.
- [Description("Server password required to join the server.")]
+ /// The server password required to join the server.
+ [Description("The server password required to join the server.")]
public string ServerPassword = "";
- /// RegionProtectChests - Whether or not region protection should apply to chests.
- [Description("Protect chests with region and build permissions.")]
+ /// Whether or not region protection should apply to chests.
+ [Description("Whether or not region protection should apply to chests.")]
public bool RegionProtectChests;
- /// RegionProtectGemLocks - Whether or not region protection should apply to gem locks.
- [Description("Protect gem locks with region and build permissions.")]
+ /// Whether or not region protection should apply to gem locks.
+ [Description("Whether or not region protection should apply to gem locks.")]
public bool RegionProtectGemLocks = true;
- /// DisableLoginBeforeJoin - This will prevent users from being able to login before connecting.
- [Description("Disable users from being able to login with account password when joining.")]
+ /// Prevents users from being able to login before they finish connecting.
+ [Description("Prevents users from being able to login before they finish connecting.")]
public bool DisableLoginBeforeJoin;
- /// DisableUUIDLogin - This will disable automatic login through a saved client UUID.
- [Description("Disable users from being able to login with their client UUID.")]
+ /// Prevents users from being able to login with their client UUID.
+ [Description("Prevents users from being able to login with their client UUID.")]
public bool DisableUUIDLogin;
- /// KickEmptyUUID - Kick clients that don't send a UUID to the server.
- [Description("Kick clients that don't send a UUID to the server.")]
+ /// Kick clients that don't send their UUID to the server.
+ [Description("Kick clients that don't send their UUID to the server.")]
public bool KickEmptyUUID;
- /// AllowRegisterAnyUsername - Allows users to register a username that doesn't necessarily match their character name.
- [Description("Allows users to register any username with /register.")]
+ /// Allows users to register a username that doesn't necessarily match their character name.
+ [Description("Allows users to register a username that doesn't necessarily match their character name.")]
public bool AllowRegisterAnyUsername;
- /// AllowLoginAnyUsername - Allows users to login to any account even if the username doesn't match their character name.
- [Description("Allows users to login with any username with /login.")]
+ /// Allows users to login to any account even if the username doesn't match their character name.
+ [Description("Allows users to login to any account even if the username doesn't match their character name.")]
public bool AllowLoginAnyUsername = true;
/// The maximum damage a player/NPC can inflict.
- [Description("The maximum damage a player/npc can inflict.")]
+ [Description("The maximum damage a player/NPC can inflict.")]
public int MaxDamage = 1175;
/// The maximum damage a projectile can inflict.
[Description("The maximum damage a projectile can inflict.")]
public int MaxProjDamage = 1175;
- /// KickOnDamageThresholdBroken - Whether or not to kick users when they surpass the MaxDamage threshold.
- [Description("Kicks a user if set to true, if they inflict more damage then the max damage.")]
+ /// Whether or not to kick users when they surpass the MaxDamage threshold.
+ [Description("Whether or not to kick users when they surpass the MaxDamage threshold.")]
public bool KickOnDamageThresholdBroken = false;
- /// IgnoreProjUpdate - Ignores checking to see if player 'can' update a projectile.
- [Description("Ignores checking to see if player 'can' update a projectile.")]
+ /// Ignores checks to see if a player 'can' update a projectile.
+ [Description("Ignores checks to see if a player 'can' update a projectile.")]
public bool IgnoreProjUpdate = false;
- /// IgnoreProjKill - Ignores checking to see if player 'can' kill a projectile.
- [Description("Ignores checking to see if player 'can' kill a projectile.")]
+ /// Ignores checks to see if a player 'can' kill a projectile.
+ [Description("Ignores checks to see if a player 'can' kill a projectile.")]
public bool IgnoreProjKill = false;
- /// IgnoreNoClip - Ignores all no clip checks for players.
- [Description("Ignores all no clip checks for players.")]
- public bool IgnoreNoClip = false;
-
- /// AlllowIce - Allows ice placement even where a user cannot usually build.
- [Description("Allow ice placement even when user does not have canbuild.")]
+ /// Allows ice placement even where a user cannot usually build.
+ [Description("Allows ice placement even where a user cannot usually build.")]
public bool AllowIce = false;
- /// AllowCrimsonCreep - Enables or disables crimson to spread when a world is in hardmode.
- [Description("Allows crimson to spread when a world is hardmode.")]
+ /// Allows the crimson to spread when a world is in hardmode.
+ [Description("Allows the crimson to spread when a world is in hardmode.")]
public bool AllowCrimsonCreep = true;
- /// AllowCorruptionCreep - Enables or disables corruption to spread when a world is in hardmode.
- [Description("Allows corruption to spread when a world is hardmode.")]
+ /// Allows the corruption to spread when a world is in hardmode.
+ [Description("Allows the corruption to spread when a world is in hardmode.")]
public bool AllowCorruptionCreep = true;
- /// AllowHallowCreep - Enables or disables hallow to spread when a world is in hardmode.
- [Description("Allows hallow to spread when a world is hardmode.")]
+ /// Allows the hallow to spread when a world is in hardmode.
+ [Description("Allows the hallow to spread when a world is in hardmode.")]
public bool AllowHallowCreep = true;
- /// StatueSpawn200 - How many NPCs a statue can spawn within 200 pixels(?) before it stops spawning.
- [Description("How many things a statue can spawn within 200 pixels(?) before it stops spawning. Default = 3.")]
+ /// How many NPCs a statue can spawn within 200 pixels(?) before it stops spawning.
+ [Description("How many NPCs a statue can spawn within 200 pixels(?) before it stops spawning.\nDefault = 3.")]
public int StatueSpawn200 = 3;
- /// StatueSpawn600 - How many NPCs a statue can spawn within 600 pixels(?) before it stops spawning.
- [Description("How many things a statue can spawn within 600 pixels(?) before it stops spawning. Default = 6.")]
+ /// How many NPCs a statue can spawn within 600 pixels(?) before it stops spawning.
+ [Description("How many NPCs a statue can spawn within 600 pixels(?) before it stops spawning.\nDefault = 6.")]
public int StatueSpawn600 = 6;
- /// StatueSpawnWorld - How many NPCs a statue can spawn before it stops spawning.
- [Description("How many things a statue can spawn before it stops spawning. Default = 10.")]
+ /// How many NPCs a statue can spawn before it stops spawning.
+ [Description("How many NPCs a statue can spawn before it stops spawning.\nDefault = 10.")]
public int StatueSpawnWorld = 10;
- /// PreventBannedItemSpawn - Prevents banned items from being spawned with commands.
- [Description("Prevent banned items from being /i or /give.")]
+ /// Prevent banned items from being spawned or given with commands.
+ [Description("Prevent banned items from being spawned or given with commands.")]
public bool PreventBannedItemSpawn = false;
- /// PreventDeadModification - Prevent players from interacting with the world if dead.
- [Description("Prevent players from interacting with the world if dead.")]
+ /// Prevent players from interacting with the world while they are dead.
+ [Description("Prevent players from interacting with the world while they are dead.")]
public bool PreventDeadModification = true;
- /// EnableChatAboveHeads - Whether or not to display chat messages above players' heads.
- [Description("Displays chat messages above players' heads, but will disable chat prefixes to compensate.")]
+ /// Whether or not to display chat messages above players' heads.
+ [Description("Whether or not to display chat messages above players' heads.")]
public bool EnableChatAboveHeads = false;
- /// ForceXmas - Force Christmas-only events to occur all year.
- [Description("Force Christmas-only events to occur all year.")]
+ /// Forces Christmas-only events to occur all year.
+ [Description("Forces Christmas-only events to occur all year.")]
public bool ForceXmas = false;
- /// AllowAllowedGroupsToSpawnBannedItems - Allows groups on the banned item allowed list to spawn banned items even if is set to true.
- [Description("Allows groups on the banned item allowed list to spawn banned items.")]
+ /// Allows groups on the banned item allowed list to spawn banned items even if PreventBannedItemSpawn is set to true.
+ [Description("Allows groups on the banned item allowed list to spawn banned items even if PreventBannedItemSpawn is set to true.")]
public bool AllowAllowedGroupsToSpawnBannedItems = false;
- /// IgnoreChestStacksOnLoad - Allows stacks in chests to be beyond the stack limit during world load.
- [Description("Allows stacks in chests to be beyond the stack limit.")]
+ /// Allows stacks in chests to go beyond the stack limit during world loading.
+ [Description("Allows stacks in chests to go beyond the stack limit during world loading.")]
public bool IgnoreChestStacksOnLoad = false;
- /// LogPath - The path of the directory where logs should be written to.
- [Description("The path of the directory where logs should be written into.")]
+ /// The path to the directory where logs should be written to.
+ [Description("The path to the directory where logs should be written to.")]
public string LogPath = "tshock";
- /// UseSqlLogs - Whether or not to save logs to a SQL database instead of a text file.
- [Description("Save logs to an SQL database instead of a text file. Default = false.")]
+ /// Whether or not to save logs to the SQL database instead of a text file.
+ [Description("Whether or not to save logs to the SQL database instead of a text file.\nDefault = false.")]
public bool UseSqlLogs = false;
- /// RevertToTextLogsOnSqlFailures - Number of times the SQL log must fail to insert logs before falling back to the text log.
+ /// Number of times the SQL log must fail to insert logs before falling back to the text log.
[Description("Number of times the SQL log must fail to insert logs before falling back to the text log.")]
public int RevertToTextLogsOnSqlFailures = 10;
- /// PreventInvalidPlaceStyle - Prevents players from placing tiles with an invalid style.
+ /// Prevents players from placing tiles with an invalid style.
[Description("Prevents players from placing tiles with an invalid style.")]
public bool PreventInvalidPlaceStyle = true;
- /// BroadCastRGB - The RGB values used for the color of broadcast messages.
- [Description("#.#.# = Red/Blue/Green - RGB Colors for broadcasts. Max value: 255.")]
+ /// The RGB values used for the color of broadcast messages.
+ [Description("The RGB values used for the color of broadcast messages.\n#.#.# = Red/Blue/Green\nMax value: 255")]
public int[] BroadcastRGB = { 127, 255, 212 };
- /// ApplicationRestTokens - A dictionary of REST tokens that external applications may use to make queries to your server.
+ /// A dictionary of REST tokens that external applications may use to make queries to your server.
[Description("A dictionary of REST tokens that external applications may use to make queries to your server.")]
public Dictionary ApplicationRestTokens = new Dictionary();
- /// ReservedSlots - The number of reserved slots past your max server slot that can be joined by reserved players.
- [Description("The number of reserved slots past your max server slot that can be joined by reserved players.")]
+ /// The number of reserved slots past your max server slots that can be joined by reserved players.
+ [Description("The number of reserved slots past your max server slots that can be joined by reserved players.")]
public int ReservedSlots = 20;
- /// LogRest - Whether or not to log REST API connections.
- [Description("Enable/disable the REST API connection log.")]
+ /// Whether or not to log REST API connections.
+ [Description("Whether or not to log REST API connections.")]
public bool LogRest = false;
- /// RespawnSeconds - The number of seconds a player must wait before being respawned.
+ /// The number of seconds a player must wait before being respawned.
[Description("The number of seconds a player must wait before being respawned.")]
public int RespawnSeconds = 5;
- /// RespawnBossSeconds - "The number of seconds a player must wait before being respawned if there is a boss nearby.
+ /// The number of seconds a player must wait before being respawned if there is a boss nearby.
[Description("The number of seconds a player must wait before being respawned if there is a boss nearby.")]
public int RespawnBossSeconds = 10;
- /// TilePaintThreshold - Disables a player if this number of tiles is painted within 1 second.
+ /// Disables a player if this number of tiles is painted within 1 second.
[Description("Disables a player if this number of tiles is painted within 1 second.")]
public int TilePaintThreshold = 15;
- /// ForceHalloween - Forces Halloween-only events to occur all year.
- [Description("Forces your world to be in Halloween mode regardless of the data.")]
+ /// Forces Halloween-only events to occur all year.
+ [Description("Forces Halloween-only events to occur all year.")]
public bool ForceHalloween = false;
- /// AllowCutTilesAndBreakables - Allows players to break temporary tiles (grass, pots, etc) even if they cannot typically build in a region.
- [Description("Allows anyone to break grass, pots, etc.")]
+ /// Allows players to break temporary tiles (grass, pots, etc) where they cannot usually build.
+ [Description("Allows players to break temporary tiles (grass, pots, etc) where they cannot usually build.")]
public bool AllowCutTilesAndBreakables = false;
- /// CommandSpecifier - Specifies which string starts a command.
+ /// Specifies which string starts a command.
/// Note: Will not function properly if the string length is bigger than 1.
- [Description("Specifies which string starts a command.")]
+ [Description("Specifies which string starts a command.\nNote: Will not function properly if the string length is bigger than 1.")]
public string CommandSpecifier = "/";
- /// CommandSilentSpecifier - Specifies which string starts a command silently.
+ /// Specifies which string starts a command silently.
/// Note: Will not function properly if the string length is bigger than 1.
- [Description("Specifies which string starts a command silently.")]
+ [Description("Specifies which string starts a command silently.\nNote: Will not function properly if the string length is bigger than 1.")]
public string CommandSilentSpecifier = ".";
- /// KickOnHardcoreDeath - Whether or not to kick a hardcore player on death.
- [Description("Kicks a hardcore player on death.")]
+ /// Whether or not to kick hardcore players on death.
+ [Description("Whether or not to kick hardcore players on death.")]
public bool KickOnHardcoreDeath;
- /// BanOnHardcoreDeath - Whether or not to ban a hardcore player on death.
- [Description("Bans a hardcore player on death.")]
+ /// Whether or not to ban hardcore players on death.
+ [Description("Whether or not to ban hardcore players on death.")]
public bool BanOnHardcoreDeath;
- /// HardcoreBanReason - The reason given when banning a hardcore player on death.
- [Description("Bans a hardcore player on death.")]
+ /// The reason given when banning hardcore players on death.
+ [Description("The reason given when banning hardcore players on death.")]
public string HardcoreBanReason = "Death results in a ban";
- /// HardcoreKickReason - The reason given when kicking a hardcore player on death.
- [Description("Kicks a hardcore player on death.")]
+ /// The reason given when kicking hardcore players on death.
+ [Description("The reason given when kicking hardcore players on death.")]
public string HardcoreKickReason = "Death results in a kick";
- /// AnonymousBossInvasions - Whether or not to announce boss spawning or invasion starts.
- [Description("Whether bosses or invasions should be anonymously spawned.")]
+ /// Whether or not to announce boss spawning or invasion starts.
+ [Description("Whether or not to announce boss spawning or invasion starts.")]
public bool AnonymousBossInvasions = true;
- /// MaxHP - The maximum allowable HP, before equipment buffs.
- [Description("The maximum allowable HP, before equipment buffs.")]
+ /// The maximum HP a player can have, before equipment buffs.
+ [Description("The maximum HP a player can have, before equipment buffs.")]
public int MaxHP = 500;
- /// MaxMP - The maximum allowable MP, before equipment buffs.
- [Description("The maximum allowable MP, before equipment buffs.")]
+ /// The maximum MP a player can have, before equipment buffs.
+ [Description("The maximum MP a player can have, before equipment buffs.")]
public int MaxMP = 200;
- /// SaveWorldOnLastPlayerExit - Whether or not to save the world when the last player disconnects.
- [Description("Determines if the server should save the world if the last player exits.")]
+ /// Whether or not to save the world when the last player disconnects.
+ [Description("Whether or not to save the world when the last player disconnects.")]
public bool SaveWorldOnLastPlayerExit = true;
- /// BCryptWorkFactor - Determines the BCrypt work factor to use. If increased, all passwords will be upgraded to new work-factor on verify.
+ /// Determines the BCrypt work factor to use. If increased, all passwords will be upgraded to new work-factor on verify.
/// The number of computational rounds is 2^n. Increase with caution. Range: 5-31.
[Description("Determines the BCrypt work factor to use. If increased, all passwords will be upgraded to new work-factor on verify. The number of computational rounds is 2^n. Increase with caution. Range: 5-31.")]
public int BCryptWorkFactor = 7;
- /// MinimumPasswordLength - The minimum password length for new user accounts.
- [Description("The minimum password length for new user accounts. Minimum value is 4.")]
+ /// The minimum password length for new user accounts. Can never be lower than 4.
+ [Description("The minimum password length for new user accounts. Can never be lower than 4.")]
public int MinimumPasswordLength = 4;
- /// RESTMaximumRequestsPerInterval - The maximum REST requests in the bucket before denying requests.
+ /// The maximum REST requests in the bucket before denying requests. Minimum value is 5.
[Description("The maximum REST requests in the bucket before denying requests. Minimum value is 5.")]
public int RESTMaximumRequestsPerInterval = 5;
- /// RESTRequestBucketDecreaseIntervalMinutes - How often in minutes the REST requests bucket is decreased by one.
+ /// How often in minutes the REST requests bucket is decreased by one. Minimum value is 1 minute.
[Description("How often in minutes the REST requests bucket is decreased by one. Minimum value is 1 minute.")]
public int RESTRequestBucketDecreaseIntervalMinutes = 1;
- /// RESTLimitOnlyFailedLoginRequests - Whether or not to limit only the max failed login requests, or all login requests.
- [Obsolete("This value is no longer used and will be removed next version.")]
- [Description("Whether we should limit only the max failed login requests, or all login requests.")]
- public bool RESTLimitOnlyFailedLoginRequests = true;
-
- /// ShowBackupAutosaveMessages - Whether or not to show backup auto save messages.
- [Description("Show backup autosave messages.")]
+ /// Whether or not to show backup auto save messages.
+ [Description("Whether or not to show backup auto save messages.")]
public bool ShowBackupAutosaveMessages = true;
///
@@ -622,4 +613,4 @@ namespace TShockAPI
File.WriteAllText("ConfigDescriptions.txt", sb.ToString());
}
}
-}
\ No newline at end of file
+}
diff --git a/TShockAPI/DB/BanManager.cs b/TShockAPI/DB/BanManager.cs
index 8bfe9d86..64650b45 100644
--- a/TShockAPI/DB/BanManager.cs
+++ b/TShockAPI/DB/BanManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -46,7 +46,8 @@ namespace TShockAPI.DB
new SqlColumn("Reason", MySqlDbType.Text),
new SqlColumn("BanningUser", MySqlDbType.Text),
new SqlColumn("Date", MySqlDbType.Text),
- new SqlColumn("Expiration", MySqlDbType.Text)
+ new SqlColumn("Expiration", MySqlDbType.Text),
+ new SqlColumn("AccountName", MySqlDbType.Text)
);
var creator = new SqlTableCreator(db,
db.GetSqlType() == SqlType.Sqlite
@@ -75,7 +76,7 @@ namespace TShockAPI.DB
using (var reader = database.QueryReader("SELECT * FROM Bans WHERE IP=@0", ip))
{
if (reader.Read())
- return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration"));
+ return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("AccountName"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration"));
}
}
catch (Exception ex)
@@ -104,7 +105,7 @@ namespace TShockAPI.DB
{
while (reader.Read())
{
- banlist.Add(new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration")));
+ banlist.Add(new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("AccountName"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration")));
}
banlist.Sort(new BanComparer(sortMethod));
@@ -135,7 +136,33 @@ namespace TShockAPI.DB
using (var reader = database.QueryReader("SELECT * FROM Bans WHERE " + namecol + "=@0", name))
{
if (reader.Read())
- return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration"));
+ return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("AccountName"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration"));
+ }
+ }
+ catch (Exception ex)
+ {
+ TShock.Log.Error(ex.ToString());
+ }
+ return null;
+ }
+
+ ///
+ /// Gets a ban by account name (not player/character name).
+ ///
+ /// The name.
+ /// Whether to check with case sensitivity.
+ /// The ban.
+ public Ban GetBanByAccountName(string name, bool casesensitive = false)
+ {
+ try
+ {
+ var namecol = casesensitive ? "AccountName" : "UPPER(AccountName)";
+ if (!casesensitive)
+ name = name.ToUpper();
+ using (var reader = database.QueryReader("SELECT * FROM Bans WHERE " + namecol + "=@0", name))
+ {
+ if (reader.Read())
+ return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("AccountName"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration"));
}
}
catch (Exception ex)
@@ -157,7 +184,7 @@ namespace TShockAPI.DB
using (var reader = database.QueryReader("SELECT * FROM Bans WHERE UUID=@0", uuid))
{
if (reader.Read())
- return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration"));
+ return new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("UUID"), reader.Get("AccountName"), reader.Get("Reason"), reader.Get("BanningUser"), reader.Get("Date"), reader.Get("Expiration"));
}
}
catch (Exception ex)
@@ -178,7 +205,7 @@ namespace TShockAPI.DB
/// If set to true enable throwing exceptions.
/// Banner.
/// Expiration date.
- public bool AddBan(string ip, string name = "", string uuid = "", string reason = "", bool exceptions = false, string banner = "", string expiration = "")
+ public bool AddBan(string ip, string name = "", string uuid = "", string accountName = "", string reason = "", bool exceptions = false, string banner = "", string expiration = "")
{
try
{
@@ -192,7 +219,7 @@ namespace TShockAPI.DB
}
else
{
- return database.Query("INSERT INTO Bans (IP, Name, UUID, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4, @5, @6);", ip, name, uuid, reason, banner, DateTime.UtcNow.ToString("s"), expiration) != 0;
+ return database.Query("INSERT INTO Bans (IP, Name, UUID, Reason, BanningUser, Date, Expiration, AccountName) VALUES (@0, @1, @2, @3, @4, @5, @6, @7);", ip, name, uuid, reason, banner, DateTime.UtcNow.ToString("s"), expiration, accountName) != 0;
}
}
catch (Exception ex)
@@ -231,6 +258,26 @@ namespace TShockAPI.DB
return false;
}
+ ///
+ /// Removes a ban by account name (not character/player name).
+ ///
+ /// true , if ban was removed, false otherwise.
+ /// Match.
+ /// If set to true casesensitive.
+ public bool RemoveBanByAccountName(string match, bool casesensitive = true)
+ {
+ try
+ {
+ var namecol = casesensitive ? "AccountName" : "UPPER(AccountName)";
+ return database.Query("DELETE FROM Bans WHERE " + namecol + "=@0", casesensitive ? match : match.ToUpper()) != 0;
+ }
+ catch (Exception ex)
+ {
+ TShock.Log.Error(ex.ToString());
+ }
+ return false;
+ }
+
///
/// Clears bans.
///
@@ -247,6 +294,20 @@ namespace TShockAPI.DB
}
return false;
}
+
+ /// Removes a ban if it has expired.
+ /// The candidate ban to check.
+ /// If the ban has been removed.
+ public bool RemoveBanIfExpired(Ban ban)
+ {
+ if (!string.IsNullOrWhiteSpace(ban.Expiration) && (ban.ExpirationDateTime != null) && (DateTime.UtcNow >= ban.ExpirationDateTime))
+ {
+ RemoveBan(ban.IP, false, false, false);
+ return true;
+ }
+
+ return false;
+ }
}
///
@@ -380,6 +441,12 @@ namespace TShockAPI.DB
/// The UUID
public string UUID { get; set; }
+ ///
+ /// Gets or sets the account name of the ban
+ ///
+ /// The account name
+ public String AccountName { get; set; }
+
///
/// Gets or sets the ban reason.
///
@@ -424,11 +491,12 @@ namespace TShockAPI.DB
/// Banner.
/// UTC ban date.
/// Expiration time
- public Ban(string ip, string name, string uuid, string reason, string banner, string date, string exp)
+ public Ban(string ip, string name, string uuid, string accountName, string reason, string banner, string date, string exp)
{
IP = ip;
Name = name;
UUID = uuid;
+ AccountName = accountName;
Reason = reason;
BanningUser = banner;
Date = date;
@@ -454,10 +522,11 @@ namespace TShockAPI.DB
IP = string.Empty;
Name = string.Empty;
UUID = string.Empty;
+ AccountName = string.Empty;
Reason = string.Empty;
- BanningUser = "";
- Date = "";
- Expiration = "";
+ BanningUser = string.Empty;
+ Date = string.Empty;
+ Expiration = string.Empty;
}
}
}
diff --git a/TShockAPI/DB/CharacterManager.cs b/TShockAPI/DB/CharacterManager.cs
index ab663ef1..21380589 100644
--- a/TShockAPI/DB/CharacterManager.cs
+++ b/TShockAPI/DB/CharacterManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -120,7 +120,7 @@ namespace TShockAPI.DB
return playerData;
}
- public bool SeedInitialData(User user)
+ public bool SeedInitialData(UserAccount account)
{
var inventory = new StringBuilder();
@@ -132,7 +132,7 @@ namespace TShockAPI.DB
try
{
database.Query("INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, spawnX, spawnY, questsCompleted) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8);",
- user.ID,
+ account.ID,
TShock.ServerSideCharacterConfig.StartingHealth,
TShock.ServerSideCharacterConfig.StartingHealth,
TShock.ServerSideCharacterConfig.StartingMana,
@@ -156,26 +156,26 @@ namespace TShockAPI.DB
///
/// player to take data from
/// true if inserted successfully
- public bool InsertPlayerData(TSPlayer player)
+ public bool InsertPlayerData(TSPlayer player, bool fromCommand = false)
{
PlayerData playerData = player.PlayerData;
if (!player.IsLoggedIn)
return false;
- if (player.HasPermission(Permissions.bypassssc))
+ if (player.HasPermission(Permissions.bypassssc) && !fromCommand)
{
- TShock.Log.ConsoleInfo("Skipping SSC Backup for " + player.User.Name); // Debug code
- return true;
+ TShock.Log.ConsoleInfo("Skipping SSC Backup for " + player.Account.Name); // Debug code
+ return false;
}
- if (!GetPlayerData(player, player.User.ID).exists)
+ if (!GetPlayerData(player, player.Account.ID).exists)
{
try
{
database.Query(
"INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, extraSlot, spawnX, spawnY, skinVariant, hair, hairDye, hairColor, pantsColor, shirtColor, underShirtColor, shoeColor, hideVisuals, skinColor, eyeColor, questsCompleted) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20);",
- player.User.ID, playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, String.Join("~", playerData.inventory), playerData.extraSlot, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.skinVariant, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor),TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisual), TShock.Utils.EncodeColor(player.TPlayer.skinColor),TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished);
+ player.Account.ID, playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, String.Join("~", playerData.inventory), playerData.extraSlot, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.skinVariant, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor),TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisual), TShock.Utils.EncodeColor(player.TPlayer.skinColor),TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished);
return true;
}
catch (Exception ex)
@@ -189,7 +189,7 @@ namespace TShockAPI.DB
{
database.Query(
"UPDATE tsCharacter SET Health = @0, MaxHealth = @1, Mana = @2, MaxMana = @3, Inventory = @4, spawnX = @6, spawnY = @7, hair = @8, hairDye = @9, hairColor = @10, pantsColor = @11, shirtColor = @12, underShirtColor = @13, shoeColor = @14, hideVisuals = @15, skinColor = @16, eyeColor = @17, questsCompleted = @18, skinVariant = @19, extraSlot = @20 WHERE Account = @5;",
- playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, String.Join("~", playerData.inventory), player.User.ID, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor), TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisual), TShock.Utils.EncodeColor(player.TPlayer.skinColor), TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished, player.TPlayer.skinVariant, player.TPlayer.extraAccessory ? 1 : 0);
+ playerData.health, playerData.maxHealth, playerData.mana, playerData.maxMana, String.Join("~", playerData.inventory), player.Account.ID, player.TPlayer.SpawnX, player.TPlayer.SpawnY, player.TPlayer.hair, player.TPlayer.hairDye, TShock.Utils.EncodeColor(player.TPlayer.hairColor), TShock.Utils.EncodeColor(player.TPlayer.pantsColor), TShock.Utils.EncodeColor(player.TPlayer.shirtColor), TShock.Utils.EncodeColor(player.TPlayer.underShirtColor), TShock.Utils.EncodeColor(player.TPlayer.shoeColor), TShock.Utils.EncodeBoolArray(player.TPlayer.hideVisual), TShock.Utils.EncodeColor(player.TPlayer.skinColor), TShock.Utils.EncodeColor(player.TPlayer.eyeColor), player.TPlayer.anglerQuestsFinished, player.TPlayer.skinVariant, player.TPlayer.extraAccessory ? 1 : 0);
return true;
}
catch (Exception ex)
@@ -235,17 +235,17 @@ namespace TShockAPI.DB
if (player.HasPermission(Permissions.bypassssc))
{
- TShock.Log.ConsoleInfo("Skipping SSC Backup for " + player.User.Name); // Debug code
+ TShock.Log.ConsoleInfo("Skipping SSC Backup for " + player.Account.Name); // Debug code
return true;
}
- if (!GetPlayerData(player, player.User.ID).exists)
+ if (!GetPlayerData(player, player.Account.ID).exists)
{
try
{
database.Query(
"INSERT INTO tsCharacter (Account, Health, MaxHealth, Mana, MaxMana, Inventory, extraSlot, spawnX, spawnY, skinVariant, hair, hairDye, hairColor, pantsColor, shirtColor, underShirtColor, shoeColor, hideVisuals, skinColor, eyeColor, questsCompleted) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19, @20);",
- player.User.ID,
+ player.Account.ID,
playerData.health,
playerData.maxHealth,
playerData.mana,
@@ -284,7 +284,7 @@ namespace TShockAPI.DB
playerData.mana,
playerData.maxMana,
String.Join("~", playerData.inventory),
- player.User.ID,
+ player.Account.ID,
playerData.spawnX,
playerData.spawnX,
playerData.skinVariant,
diff --git a/TShockAPI/DB/DBTools.cs b/TShockAPI/DB/DBTools.cs
index 38d005d4..20300aef 100644
--- a/TShockAPI/DB/DBTools.cs
+++ b/TShockAPI/DB/DBTools.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2016 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs
index 62bad891..6405b5bc 100644
--- a/TShockAPI/DB/GroupManager.cs
+++ b/TShockAPI/DB/GroupManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -26,51 +26,153 @@ using MySql.Data.MySqlClient;
namespace TShockAPI.DB
{
+ ///
+ /// Represents the GroupManager, which is in charge of group management.
+ ///
public class GroupManager : IEnumerable
{
private IDbConnection database;
public readonly List groups = new List();
+ ///
+ /// Initializes a new instance of the class with the specified database connection.
+ ///
+ /// The connection.
public GroupManager(IDbConnection db)
{
database = db;
var table = new SqlTable("GroupList",
- new SqlColumn("GroupName", MySqlDbType.VarChar, 32) {Primary = true},
- new SqlColumn("Parent", MySqlDbType.VarChar, 32),
- new SqlColumn("Commands", MySqlDbType.Text),
- new SqlColumn("ChatColor", MySqlDbType.Text),
- new SqlColumn("Prefix", MySqlDbType.Text),
- new SqlColumn("Suffix", MySqlDbType.Text)
- );
+ new SqlColumn("GroupName", MySqlDbType.VarChar, 32) { Primary = true },
+ new SqlColumn("Parent", MySqlDbType.VarChar, 32),
+ new SqlColumn("Commands", MySqlDbType.Text),
+ new SqlColumn("ChatColor", MySqlDbType.Text),
+ new SqlColumn("Prefix", MySqlDbType.Text),
+ new SqlColumn("Suffix", MySqlDbType.Text)
+ );
var creator = new SqlTableCreator(db,
- db.GetSqlType() == SqlType.Sqlite
- ? (IQueryBuilder) new SqliteQueryCreator()
- : new MysqlQueryCreator());
+ db.GetSqlType() == SqlType.Sqlite
+ ? (IQueryBuilder)new SqliteQueryCreator()
+ : new MysqlQueryCreator());
if (creator.EnsureTableStructure(table))
{
// Add default groups if they don't exist
AddDefaultGroup("guest", "",
- string.Join(",", Permissions.canbuild, Permissions.canregister, Permissions.canlogin, Permissions.canpartychat,
- Permissions.cantalkinthird, Permissions.canchat));
+ string.Join(",",
+ Permissions.canbuild,
+ Permissions.canregister,
+ Permissions.canlogin,
+ Permissions.canpartychat,
+ Permissions.cantalkinthird,
+ Permissions.canchat));
AddDefaultGroup("default", "guest",
- string.Join(",", Permissions.warp, Permissions.canchangepassword, Permissions.canlogout));
+ string.Join(",",
+ Permissions.warp,
+ Permissions.canchangepassword,
+ Permissions.canlogout,
+ Permissions.summonboss,
+ Permissions.whisper,
+ Permissions.wormhole,
+ Permissions.canpaint));
- AddDefaultGroup("newadmin", "default",
- string.Join(",", Permissions.kick, Permissions.editspawn, Permissions.reservedslot));
+ AddDefaultGroup("vip", "default",
+ string.Join(",",
+ Permissions.reservedslot,
+ Permissions.renamenpc,
+ Permissions.startinvasion,
+ Permissions.summonboss,
+ Permissions.whisper,
+ Permissions.wormhole));
+
+ AddDefaultGroup("newadmin", "vip",
+ string.Join(",",
+ Permissions.kick,
+ Permissions.editspawn,
+ Permissions.reservedslot,
+ Permissions.annoy,
+ Permissions.checkaccountinfo,
+ Permissions.getpos,
+ Permissions.mute,
+ Permissions.rod,
+ Permissions.savessc,
+ Permissions.seeids,
+ "tshock.world.time.*"));
AddDefaultGroup("admin", "newadmin",
- string.Join(",", Permissions.ban, Permissions.whitelist, "tshock.world.time.*", Permissions.spawnboss,
- Permissions.spawnmob, Permissions.managewarp, Permissions.time, Permissions.tp, Permissions.slap,
- Permissions.kill, Permissions.logs,
- Permissions.immunetokick, Permissions.tpothers));
+ string.Join(",",
+ Permissions.ban,
+ Permissions.whitelist,
+ Permissions.spawnboss,
+ Permissions.spawnmob,
+ Permissions.managewarp,
+ Permissions.time,
+ Permissions.tp,
+ Permissions.slap,
+ Permissions.kill,
+ Permissions.logs,
+ Permissions.immunetokick,
+ Permissions.tpothers,
+ Permissions.advaccountinfo,
+ Permissions.broadcast,
+ Permissions.home,
+ Permissions.tpallothers,
+ Permissions.tpallow,
+ Permissions.tpnpc,
+ Permissions.tppos,
+ Permissions.tpsilent,
+ Permissions.userinfo));
AddDefaultGroup("trustedadmin", "admin",
- string.Join(",", Permissions.maintenance, "tshock.cfg.*", "tshock.world.*", Permissions.butcher, Permissions.item, Permissions.give,
- Permissions.heal, Permissions.immunetoban, Permissions.usebanneditem));
+ string.Join(",",
+ Permissions.maintenance,
+ "tshock.cfg.*",
+ "tshock.world.*",
+ Permissions.butcher,
+ Permissions.item,
+ Permissions.give,
+ Permissions.heal,
+ Permissions.immunetoban,
+ Permissions.usebanneditem,
+ Permissions.allowclientsideworldedit,
+ Permissions.buff,
+ Permissions.buffplayer,
+ Permissions.clear,
+ Permissions.clearangler,
+ Permissions.godmode,
+ Permissions.godmodeother,
+ Permissions.ignoredamagecap,
+ Permissions.ignorehp,
+ Permissions.ignorekilltiledetection,
+ Permissions.ignoreliquidsetdetection,
+ Permissions.ignoremp,
+ Permissions.ignorepaintdetection,
+ Permissions.ignoreplacetiledetection,
+ Permissions.ignoreprojectiledetection,
+ Permissions.ignorestackhackdetection,
+ Permissions.invade,
+ Permissions.startdd2,
+ Permissions.uploaddata,
+ Permissions.uploadothersdata));
- AddDefaultGroup("vip", "default", string.Join(",", Permissions.reservedslot));
+ AddDefaultGroup("owner", "trustedadmin",
+ string.Join(",",
+ Permissions.su,
+ Permissions.allowdroppingbanneditems,
+ Permissions.antibuild,
+ Permissions.canusebannedprojectiles,
+ Permissions.canusebannedtiles,
+ Permissions.managegroup,
+ Permissions.manageitem,
+ Permissions.manageprojectile,
+ Permissions.manageregion,
+ Permissions.managetile,
+ Permissions.maxspawns,
+ Permissions.serverinfo,
+ Permissions.settempgroup,
+ Permissions.spawnrate,
+ Permissions.tpoverride,
+ Permissions.createdumps));
}
// Load Permissions from the DB
@@ -85,7 +187,11 @@ namespace TShockAPI.DB
AddGroup(name, parent, permissions, Group.defaultChatColor);
}
-
+ ///
+ /// Determines whether the given group exists.
+ ///
+ /// The group.
+ /// true if it does; otherwise, false .
public bool GroupExists(string group)
{
if (group == "superadmin")
@@ -99,11 +205,20 @@ namespace TShockAPI.DB
return GetEnumerator();
}
+ ///
+ /// Gets the enumerator.
+ ///
+ /// The enumerator.
public IEnumerator GetEnumerator()
{
return groups.GetEnumerator();
}
+ ///
+ /// Gets the group matching the specified name.
+ ///
+ /// The name.
+ /// The group.
public Group GetGroupByName(string name)
{
var ret = groups.Where(g => g.Name == name);
@@ -139,8 +254,8 @@ namespace TShockAPI.DB
}
string query = (TShock.Config.StorageType.ToLower() == "sqlite")
- ? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);"
- : "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3";
+ ? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);"
+ : "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3";
if (database.Query(query, name, parentname, permissions, chatcolor) == 1)
{
groups.Add(group);
@@ -200,6 +315,114 @@ namespace TShockAPI.DB
group.Suffix = suffix;
}
+ ///
+ /// Renames the specified group.
+ ///
+ /// The group's name.
+ /// The new name.
+ /// The result from the operation to be sent back to the user.
+ public String RenameGroup(string name, string newName)
+ {
+ if (!GroupExists(name))
+ {
+ throw new GroupNotExistException(name);
+ }
+
+ if (GroupExists(newName))
+ {
+ throw new GroupExistsException(newName);
+ }
+
+ using (var db = database.CloneEx())
+ {
+ db.Open();
+ using (var transaction = db.BeginTransaction())
+ {
+ try
+ {
+ using (var command = db.CreateCommand())
+ {
+ command.CommandText = "UPDATE GroupList SET GroupName = @0 WHERE GroupName = @1";
+ command.AddParameter("@0", newName);
+ command.AddParameter("@1", name);
+ command.ExecuteNonQuery();
+ }
+
+ var oldGroup = GetGroupByName(name);
+ var newGroup = new Group(newName, oldGroup.Parent, oldGroup.ChatColor, oldGroup.Permissions)
+ {
+ Prefix = oldGroup.Prefix,
+ Suffix = oldGroup.Suffix
+ };
+ groups.Remove(oldGroup);
+ groups.Add(newGroup);
+
+ // We need to check if the old group has been referenced as a parent and update those references accordingly
+ using (var command = db.CreateCommand())
+ {
+ command.CommandText = "UPDATE GroupList SET Parent = @0 WHERE Parent = @1";
+ command.AddParameter("@0", newName);
+ command.AddParameter("@1", name);
+ command.ExecuteNonQuery();
+ }
+ foreach (var group in groups.Where(g => g.Parent != null && g.Parent == oldGroup))
+ {
+ group.Parent = newGroup;
+ }
+
+ // Read the config file to prevent the possible loss of any unsaved changes
+ TShock.Config = ConfigFile.Read(FileTools.ConfigPath);
+ if (TShock.Config.DefaultGuestGroupName == oldGroup.Name)
+ {
+ TShock.Config.DefaultGuestGroupName = newGroup.Name;
+ Group.DefaultGroup = newGroup;
+ }
+ if (TShock.Config.DefaultRegistrationGroupName == oldGroup.Name)
+ {
+ TShock.Config.DefaultRegistrationGroupName = newGroup.Name;
+ }
+ TShock.Config.Write(FileTools.ConfigPath);
+
+ // We also need to check if any users belong to the old group and automatically apply changes
+ using (var command = db.CreateCommand())
+ {
+ command.CommandText = "UPDATE Users SET Usergroup = @0 WHERE Usergroup = @1";
+ command.AddParameter("@0", newName);
+ command.AddParameter("@1", name);
+ command.ExecuteNonQuery();
+ }
+ foreach (var player in TShock.Players.Where(p => p?.Group == oldGroup))
+ {
+ player.Group = newGroup;
+ }
+
+ transaction.Commit();
+ return $"Group \"{name}\" has been renamed to \"{newName}\".";
+ }
+ catch (Exception ex)
+ {
+ TShock.Log.Error($"An exception has occured during database transaction: {ex.Message}");
+ try
+ {
+ transaction.Rollback();
+ }
+ catch (Exception rollbackEx)
+ {
+ TShock.Log.Error($"An exception has occured during database rollback: {rollbackEx.Message}");
+ }
+ }
+ }
+ }
+
+ throw new GroupManagerException($"Failed to rename group \"{name}\".");
+ }
+
+ ///
+ /// Deletes the specified group.
+ ///
+ /// The group's name.
+ /// Whether exceptions will be thrown in case something goes wrong.
+ /// The result from the operation to be sent back to the user.
public String DeleteGroup(String name, bool exceptions = false)
{
if (!GroupExists(name))
@@ -211,21 +434,27 @@ namespace TShockAPI.DB
if (database.Query("DELETE FROM GroupList WHERE GroupName=@0", name) == 1)
{
- groups.Remove(TShock.Utils.GetGroup(name));
+ groups.Remove(TShock.Groups.GetGroupByName(name));
return "Group " + name + " has been deleted successfully.";
}
- else if (exceptions)
- throw new GroupManagerException("Failed to delete group '" + name + ".'");
- return "";
+ if (exceptions)
+ throw new GroupManagerException("Failed to delete group '" + name + ".'");
+ return "Failed to delete group '" + name + ".'";
}
+ ///
+ /// Enumerates the given permission list and adds permissions for the specified group accordingly.
+ ///
+ /// The group name.
+ /// The permission list.
+ /// The result from the operation to be sent back to the user.
public String AddPermissions(String name, List permissions)
{
if (!GroupExists(name))
return "Error: Group doesn't exist.";
- var group = TShock.Utils.GetGroup(name);
+ var group = TShock.Groups.GetGroupByName(name);
var oldperms = group.Permissions; // Store old permissions in case of error
permissions.ForEach(p => group.AddPermission(p));
@@ -237,12 +466,18 @@ namespace TShockAPI.DB
return "";
}
+ ///
+ /// Enumerates the given permission list and removes valid permissions for the specified group accordingly.
+ ///
+ /// The group name.
+ /// The permission list.
+ /// The result from the operation to be sent back to the user.
public String DeletePermissions(String name, List permissions)
{
if (!GroupExists(name))
return "Error: Group doesn't exist.";
- var group = TShock.Utils.GetGroup(name);
+ var group = TShock.Groups.GetGroupByName(name);
var oldperms = group.Permissions; // Store old permissions in case of error
permissions.ForEach(p => group.RemovePermission(p));
@@ -254,12 +489,15 @@ namespace TShockAPI.DB
return "";
}
+ ///
+ /// Enumerates the group list and loads permissions for each group appropriately.
+ ///
public void LoadPermisions()
{
try
{
List newGroups = new List(groups.Count);
- Dictionary newGroupParents = new Dictionary(groups.Count);
+ Dictionary newGroupParents = new Dictionary(groups.Count);
using (var reader = database.QueryReader("SELECT * FROM GroupList"))
{
while (reader.Read())
@@ -271,7 +509,8 @@ namespace TShockAPI.DB
continue;
}
- newGroups.Add(new Group(groupName, null, reader.Get("ChatColor"), reader.Get("Commands")) {
+ newGroups.Add(new Group(groupName, null, reader.Get("ChatColor"), reader.Get("Commands"))
+ {
Prefix = reader.Get("Prefix"),
Suffix = reader.Get("Suffix"),
});
@@ -360,32 +599,60 @@ namespace TShockAPI.DB
}
}
+ ///
+ /// Represents the base GroupManager exception.
+ ///
[Serializable]
public class GroupManagerException : Exception
{
+ ///
+ /// Initializes a new instance of the with the specified message.
+ ///
+ /// The message.
public GroupManagerException(string message)
: base(message)
{
}
+ ///
+ /// Initializes a new instance of the with the specified message and inner exception.
+ ///
+ /// The message.
+ /// The inner exception.
public GroupManagerException(string message, Exception inner)
: base(message, inner)
{
}
}
+ ///
+ /// Represents the GroupExists exception.
+ /// This exception is thrown whenever an attempt to add an existing group into the database is made.
+ ///
[Serializable]
public class GroupExistsException : GroupManagerException
{
+ ///
+ /// Initializes a new instance of the with the specified group name.
+ ///
+ /// The group name.
public GroupExistsException(string name)
: base("Group '" + name + "' already exists")
{
}
}
+ ///
+ /// Represents the GroupNotExist exception.
+ /// This exception is thrown whenever we try to access a group that does not exist.
+ ///
[Serializable]
public class GroupNotExistException : GroupManagerException
{
+ ///
+ /// Initializes a new instance of the with the specified group name.
+ ///
+ /// The group name.
public GroupNotExistException(string name)
: base("Group '" + name + "' does not exist")
{
diff --git a/TShockAPI/DB/IQueryBuilder.cs b/TShockAPI/DB/IQueryBuilder.cs
index 7b202aaf..825a2e4a 100644
--- a/TShockAPI/DB/IQueryBuilder.cs
+++ b/TShockAPI/DB/IQueryBuilder.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -16,45 +16,120 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.Serialization;
using System.Text;
-using MySql.Data.MySqlClient;
using TShockAPI.Extensions;
namespace TShockAPI.DB
{
+ ///
+ /// Interface for various SQL related utilities.
+ ///
public interface IQueryBuilder
{
+ ///
+ /// Creates a table from a SqlTable object.
+ ///
+ /// The SqlTable to create the table from
+ /// The sql query for the table creation.
string CreateTable(SqlTable table);
+
+ ///
+ /// Alter a table from source to destination
+ ///
+ /// Must have name and column names. Column types are not required
+ /// Must have column names and column types.
+ /// The SQL Query
string AlterTable(SqlTable from, SqlTable to);
+
+ ///
+ /// Converts the MySqlDbType enum to it's string representation.
+ ///
+ /// The MySqlDbType type
+ /// The length of the datatype
+ /// The string representation
string DbTypeToString(MySqlDbType type, int? length);
+
+ ///
+ /// A UPDATE Query
+ ///
+ /// The table to update
+ /// The values to change
+ ///
+ /// The SQL query
string UpdateValue(string table, List values, List wheres);
+
+ ///
+ /// A INSERT query
+ ///
+ /// The table to insert to
+ ///
+ /// The SQL Query
string InsertValues(string table, List values);
+
+ ///
+ /// A SELECT query to get all columns
+ ///
+ /// The table to select from
+ ///
+ /// The SQL query
string ReadColumn(string table, List wheres);
+
+ ///
+ /// Deletes row(s).
+ ///
+ /// The table to delete the row from
+ ///
+ /// The SQL query
string DeleteRow(string table, List wheres);
+
+ ///
+ /// Renames the given table.
+ ///
+ /// Old name of the table
+ /// New name of the table
+ /// The sql query for renaming the table.
string RenameTable(string from, string to);
}
+ ///
+ /// Query Creator for Sqlite
+ ///
public class SqliteQueryCreator : GenericQueryCreator, IQueryBuilder
{
+ ///
+ /// Creates a table from a SqlTable object.
+ ///
+ /// The SqlTable to create the table from
+ /// The sql query for the table creation.
public override string CreateTable(SqlTable table)
{
+ ValidateSqlColumnType(table.Columns);
var columns =
table.Columns.Select(
c =>
- "'{0}' {1} {2} {3} {4}".SFormat(c.Name,
- DbTypeToString(c.Type, c.Length),
+ "'{0}' {1} {2} {3} {4} {5}".SFormat(c.Name,
+ DbTypeToString(c.Type, c.Length),
c.Primary ? "PRIMARY KEY" : "",
- c.AutoIncrement ? "AUTOINCREMENT" : "",
- c.NotNull ? "NOT NULL" : ""));
+ c.AutoIncrement ? "AUTOINCREMENT" : "",
+ c.NotNull ? "NOT NULL" : "",
+ c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : ""));
var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name);
- return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name),
+ return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name),
string.Join(", ", columns),
uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : "");
}
+ ///
+ /// Renames the given table.
+ ///
+ /// Old name of the table
+ /// New name of the table
+ /// The sql query for renaming the table.
public override string RenameTable(string from, string to)
{
return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to);
@@ -72,9 +147,16 @@ namespace TShockAPI.DB
{ MySqlDbType.Double, "REAL" },
{ MySqlDbType.Int32, "INTEGER" },
{ MySqlDbType.Blob, "BLOB" },
- { MySqlDbType.Int64, "BIGINT"},
+ { MySqlDbType.Int64, "BIGINT"},
+ { MySqlDbType.DateTime, "DATETIME"},
};
+ ///
+ /// Converts the MySqlDbType enum to it's string representation.
+ ///
+ /// The MySqlDbType type
+ /// The length of the datatype
+ /// The string representation
public string DbTypeToString(MySqlDbType type, int? length)
{
string ret;
@@ -83,21 +165,38 @@ namespace TShockAPI.DB
throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type));
}
+ ///
+ /// Escapes the table name
+ ///
+ /// The name of the table to be escaped
+ ///
protected override string EscapeTableName(string table)
{
return table.SFormat("'{0}'", table);
}
}
+ ///
+ /// Query Creator for MySQL
+ ///
public class MysqlQueryCreator : GenericQueryCreator, IQueryBuilder
{
+ ///
+ /// Creates a table from a SqlTable object.
+ ///
+ /// The SqlTable to create the table from
+ /// The sql query for the table creation.
public override string CreateTable(SqlTable table)
{
+ ValidateSqlColumnType(table.Columns);
var columns =
table.Columns.Select(
c =>
- "{0} {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "",
- c.AutoIncrement ? "AUTO_INCREMENT" : "", c.NotNull ? "NOT NULL" : ""));
+ "{0} {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length),
+ c.Primary ? "PRIMARY KEY" : "",
+ c.AutoIncrement ? "AUTO_INCREMENT" : "",
+ c.NotNull ? "NOT NULL" : "",
+ c.DefaultCurrentTimestamp ? "DEFAULT CURRENT_TIMESTAMP" : ""));
var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name);
return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns),
uniques.Count() > 0
@@ -105,6 +204,12 @@ namespace TShockAPI.DB
: "");
}
+ ///
+ /// Renames the given table.
+ ///
+ /// Old name of the table
+ /// New name of the table
+ /// The sql query for renaming the table.
public override string RenameTable(string from, string to)
{
return "RENAME TABLE {0} TO {1}".SFormat(from, to);
@@ -121,9 +226,16 @@ namespace TShockAPI.DB
{ MySqlDbType.Float, "FLOAT" },
{ MySqlDbType.Double, "DOUBLE" },
{ MySqlDbType.Int32, "INT" },
- { MySqlDbType.Int64, "BIGINT"},
+ { MySqlDbType.Int64, "BIGINT"},
+ { MySqlDbType.DateTime, "DATETIME"},
};
+ ///
+ /// Converts the MySqlDbType enum to it's string representation.
+ ///
+ /// The MySqlDbType type
+ /// The length of the datatype
+ /// The string representation
public string DbTypeToString(MySqlDbType type, int? length)
{
string ret;
@@ -132,17 +244,44 @@ namespace TShockAPI.DB
throw new NotImplementedException(Enum.GetName(typeof(MySqlDbType), type));
}
+ ///
+ /// Escapes the table name
+ ///
+ /// The name of the table to be escaped
+ ///
protected override string EscapeTableName(string table)
{
return table.SFormat("`{0}`", table);
}
}
+ ///
+ /// A Generic Query Creator (abstract)
+ ///
public abstract class GenericQueryCreator
{
protected static Random rand = new Random();
+
+ ///
+ /// Escapes the table name
+ ///
+ /// The name of the table to be escaped
+ ///
protected abstract string EscapeTableName(string table);
+
+ ///
+ /// Creates a table from a SqlTable object.
+ ///
+ /// The SqlTable to create the table from
+ /// The sql query for the table creation.
public abstract string CreateTable(SqlTable table);
+
+ ///
+ /// Renames the given table.
+ ///
+ /// Old name of the table
+ /// New name of the table
+ /// The sql query for renaming the table.
public abstract string RenameTable(string from, string to);
///
@@ -150,18 +289,9 @@ namespace TShockAPI.DB
///
/// Must have name and column names. Column types are not required
/// Must have column names and column types.
- ///
+ /// The SQL Query
public string AlterTable(SqlTable from, SqlTable to)
{
- /*
- * Any example outpuf from this looks like:-
- ALTER TABLE "main"."Bans" RENAME TO "oXHFcGcd04oXHFcGcd04_Bans"
- CREATE TABLE "main"."Bans" ("IP" TEXT PRIMARY KEY ,"Name" TEXT)
- INSERT INTO "main"."Bans" SELECT "IP","Name" FROM "main"."oXHFcGcd04oXHFcGcd04_Bans"
- DROP TABLE "main"."oXHFcGcd04oXHFcGcd04_Bans"
- *
- * Twitchy - Oh. I get it!
- */
var rstr = rand.NextString(20);
var escapedTable = EscapeTableName(from.Name);
var tmpTable = EscapeTableName("{0}_{1}".SFormat(rstr, from.Name));
@@ -175,11 +305,41 @@ namespace TShockAPI.DB
return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop);
}
+ ///
+ /// Check for errors in the columns.
+ ///
+ ///
+ ///
+ public void ValidateSqlColumnType(List columns)
+ {
+ columns.ForEach(x =>
+ {
+ if (x.DefaultCurrentTimestamp && x.Type != MySqlDbType.DateTime)
+ {
+ throw new SqlColumnException("Can't set to true SqlColumn.DefaultCurrentTimestamp " +
+ "when the MySqlDbType is not DateTime");
+ }
+ });
+ }
+
+ ///
+ /// Deletes row(s).
+ ///
+ /// The table to delete the row from
+ ///
+ /// The SQL query
public string DeleteRow(string table, List wheres)
{
return "DELETE FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
}
+ ///
+ /// A UPDATE Query
+ ///
+ /// The table to update
+ /// The values to change
+ ///
+ /// The SQL query
public string UpdateValue(string table, List values, List wheres)
{
if (0 == values.Count)
@@ -188,11 +348,23 @@ namespace TShockAPI.DB
return "UPDATE {0} SET {1} {2}".SFormat(EscapeTableName(table), string.Join(", ", values.Select(v => v.Name + " = " + v.Value)), BuildWhere(wheres));
}
+ ///
+ /// A SELECT query to get all columns
+ ///
+ /// The table to select from
+ ///
+ /// The SQL query
public string ReadColumn(string table, List wheres)
{
return "SELECT * FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres));
}
+ ///
+ /// A INSERT query
+ ///
+ /// The table to insert to
+ ///
+ /// The SQL Query
public string InsertValues(string table, List values)
{
var sbnames = new StringBuilder();
@@ -214,6 +386,11 @@ namespace TShockAPI.DB
return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(EscapeTableName(table), sbnames, sbvalues);
}
+ ///
+ /// Builds the SQL WHERE clause
+ ///
+ ///
+ ///
protected static string BuildWhere(List wheres)
{
if (0 == wheres.Count)
diff --git a/TShockAPI/DB/ItemManager.cs b/TShockAPI/DB/ItemManager.cs
index 4ce43066..012132dc 100644
--- a/TShockAPI/DB/ItemManager.cs
+++ b/TShockAPI/DB/ItemManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using MySql.Data.MySqlClient;
+using TShockAPI.Hooks;
namespace TShockAPI.DB
{
@@ -200,6 +201,10 @@ namespace TShockAPI.DB
if (ply.HasPermission(Permissions.usebanneditem))
return true;
+ PermissionHookResult hookResult = PlayerHooks.OnPlayerItembanPermission(ply, this);
+ if (hookResult != PermissionHookResult.Unhandled)
+ return hookResult == PermissionHookResult.Granted;
+
var cur = ply.Group;
var traversed = new List();
while (cur != null)
diff --git a/TShockAPI/DB/ProjectileManager.cs b/TShockAPI/DB/ProjectileManager.cs
index 3f736c9b..eb9314cd 100644
--- a/TShockAPI/DB/ProjectileManager.cs
+++ b/TShockAPI/DB/ProjectileManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using MySql.Data.MySqlClient;
+using TShockAPI.Hooks;
namespace TShockAPI.DB
{
@@ -205,6 +206,10 @@ namespace TShockAPI.DB
if (ply.HasPermission(Permissions.canusebannedprojectiles))
return true;
+ PermissionHookResult hookResult = PlayerHooks.OnPlayerProjbanPermission(ply, this);
+ if (hookResult != PermissionHookResult.Unhandled)
+ return hookResult == PermissionHookResult.Granted;
+
var cur = ply.Group;
var traversed = new List();
while (cur != null)
diff --git a/TShockAPI/DB/RegionManager.cs b/TShockAPI/DB/RegionManager.cs
index 661ec1c5..d776028b 100644
--- a/TShockAPI/DB/RegionManager.cs
+++ b/TShockAPI/DB/RegionManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -26,6 +26,9 @@ using Microsoft.Xna.Framework;
namespace TShockAPI.DB
{
+ ///
+ /// Represents the Region database manager.
+ ///
public class RegionManager
{
///
@@ -401,7 +404,41 @@ namespace TShockAPI.DB
}
return false;
}
+
+ ///
+ /// Renames a region
+ ///
+ /// Name of the region to rename
+ /// New name of the region
+ /// true if renamed successfully, false otherwise
+ public bool RenameRegion(string oldName, string newName)
+ {
+ Region region = null;
+ string worldID = Main.worldID.ToString();
+ bool result = false;
+
+ try
+ {
+ int q = database.Query("UPDATE Regions SET RegionName = @0 WHERE RegionName = @1 AND WorldID = @2",
+ newName, oldName, worldID);
+
+ if (q > 0)
+ {
+ region = Regions.First(r => r.Name == oldName && r.WorldID == worldID);
+ region.Name = newName;
+ Hooks.RegionHooks.OnRegionRenamed(region, oldName, newName);
+ result = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ TShock.Log.Error(ex.ToString());
+ }
+
+ return result;
+ }
+
///
/// Removes an allowed user from a region
///
@@ -413,7 +450,7 @@ namespace TShockAPI.DB
Region r = GetRegionByName(regionName);
if (r != null)
{
- if (!r.RemoveID(TShock.Users.GetUserID(userName)))
+ if (!r.RemoveID(TShock.UserAccounts.GetUserAccountID(userName)))
{
return false;
}
@@ -445,7 +482,7 @@ namespace TShockAPI.DB
mergedIDs = reader.Get("UserIds");
}
- string userIdToAdd = Convert.ToString(TShock.Users.GetUserID(userName));
+ string userIdToAdd = Convert.ToString(TShock.UserAccounts.GetUserAccountID(userName));
string[] ids = mergedIDs.Split(',');
// Is the user already allowed to the region?
if (ids.Contains(userIdToAdd))
@@ -754,7 +791,7 @@ namespace TShockAPI.DB
return false;
}
- return ply.HasPermission(Permissions.editregion) || AllowedIDs.Contains(ply.User.ID) || AllowedGroups.Contains(ply.Group.Name) || Owner == ply.User.Name;
+ return ply.HasPermission(Permissions.editregion) || AllowedIDs.Contains(ply.Account.ID) || AllowedGroups.Contains(ply.Group.Name) || Owner == ply.Account.Name;
}
///
diff --git a/TShockAPI/DB/RememberedPosManager.cs b/TShockAPI/DB/RememberedPosManager.cs
index 71207b7f..77fef847 100644
--- a/TShockAPI/DB/RememberedPosManager.cs
+++ b/TShockAPI/DB/RememberedPosManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/DB/SqlColumn.cs b/TShockAPI/DB/SqlColumn.cs
index 8a6c6bdf..27c45435 100644
--- a/TShockAPI/DB/SqlColumn.cs
+++ b/TShockAPI/DB/SqlColumn.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,6 +17,7 @@ along with this program. If not, see .
*/
using MySql.Data.MySqlClient;
+using System;
namespace TShockAPI.DB
{
@@ -28,11 +29,30 @@ namespace TShockAPI.DB
//Optional
+ ///
+ /// Sets/Gets if it's unique
+ ///
public bool Unique { get; set; }
+ ///
+ /// Sets/Gets if it's primary key
+ ///
public bool Primary { get; set; }
+ ///
+ /// Sets/Gets if it autoincrements
+ ///
public bool AutoIncrement { get; set; }
+ ///
+ /// Sets/Gets if it can be or not null
+ ///
public bool NotNull { get; set; }
+ ///
+ /// Sets the default value
+ ///
public string DefaultValue { get; set; }
+ ///
+ /// Use on DateTime only, if true, sets the default value to the current date when creating the row.
+ ///
+ public bool DefaultCurrentTimestamp { get; set; }
///
/// Length of the data type, null = default
@@ -51,4 +71,19 @@ namespace TShockAPI.DB
Length = length;
}
}
-}
\ No newline at end of file
+
+ ///
+ /// Used when a SqlColumn has validation errors.
+ ///
+ [Serializable]
+ public class SqlColumnException : Exception
+ {
+ ///
+ /// Creates a new SqlColumnException with the given message.
+ ///
+ ///
+ public SqlColumnException(string message) : base(message)
+ {
+ }
+ }
+}
diff --git a/TShockAPI/DB/SqlTable.cs b/TShockAPI/DB/SqlTable.cs
index 365b622d..1f0c4593 100644
--- a/TShockAPI/DB/SqlTable.cs
+++ b/TShockAPI/DB/SqlTable.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/DB/SqlValue.cs b/TShockAPI/DB/SqlValue.cs
index 918b7640..aa70e2d6 100644
--- a/TShockAPI/DB/SqlValue.cs
+++ b/TShockAPI/DB/SqlValue.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/DB/TileManager.cs b/TShockAPI/DB/TileManager.cs
index 911bf27b..586d4f47 100644
--- a/TShockAPI/DB/TileManager.cs
+++ b/TShockAPI/DB/TileManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using MySql.Data.MySqlClient;
+using TShockAPI.Hooks;
namespace TShockAPI.DB
{
@@ -205,6 +206,10 @@ namespace TShockAPI.DB
if (ply.HasPermission(Permissions.canusebannedtiles))
return true;
+ PermissionHookResult hookResult = PlayerHooks.OnPlayerTilebanPermission(ply, this);
+ if (hookResult != PermissionHookResult.Unhandled)
+ return hookResult == PermissionHookResult.Granted;
+
var cur = ply.Group;
var traversed = new List();
while (cur != null)
diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs
index e79fb933..0ea5c4f1 100644
--- a/TShockAPI/DB/UserManager.cs
+++ b/TShockAPI/DB/UserManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -28,16 +28,16 @@ using System.Security.Cryptography;
namespace TShockAPI.DB
{
- /// UserManager - Methods for dealing with database users and other user functionality within TShock.
- public class UserManager
+ /// UserAccountManager - Methods for dealing with database user accounts and other related functionality within TShock.
+ public class UserAccountManager
{
/// database - The database object to use for connections.
private IDbConnection _database;
- /// Creates a UserManager object. During instantiation, this method will verify the table structure against the format below.
+ /// Creates a UserAccountManager object. During instantiation, this method will verify the table structure against the format below.
/// The database to connect to.
- /// A UserManager object.
- public UserManager(IDbConnection db)
+ /// A UserAccountManager object.
+ public UserAccountManager(IDbConnection db)
{
_database = db;
@@ -59,145 +59,149 @@ namespace TShockAPI.DB
}
///
- /// Adds a given username to the database
+ /// Adds the given user account to the database
///
- /// User user
- public void AddUser(User user)
+ /// The user account to be added
+ public void AddUserAccount(UserAccount account)
{
- if (!TShock.Groups.GroupExists(user.Group))
- throw new GroupNotExistsException(user.Group);
+ if (!TShock.Groups.GroupExists(account.Group))
+ throw new GroupNotExistsException(account.Group);
int ret;
try
{
- ret = _database.Query("INSERT INTO Users (Username, Password, UUID, UserGroup, Registered) VALUES (@0, @1, @2, @3, @4);", user.Name,
- user.Password, user.UUID, user.Group, DateTime.UtcNow.ToString("s"));
+ ret = _database.Query("INSERT INTO Users (Username, Password, UUID, UserGroup, Registered) VALUES (@0, @1, @2, @3, @4);", account.Name,
+ account.Password, account.UUID, account.Group, DateTime.UtcNow.ToString("s"));
}
catch (Exception ex)
{
// Detect duplicate user using a regexp as Sqlite doesn't have well structured exceptions
- if (Regex.IsMatch(ex.Message, "Username.*not unique"))
- throw new UserExistsException(user.Name);
- throw new UserManagerException("AddUser SQL returned an error (" + ex.Message + ")", ex);
+ if (Regex.IsMatch(ex.Message, "Username.*not unique|UNIQUE constraint failed: Users\\.Username"))
+ throw new UserAccountExistsException(account.Name);
+ throw new UserAccountManagerException("AddUser SQL returned an error (" + ex.Message + ")", ex);
}
if (1 > ret)
- throw new UserExistsException(user.Name);
+ throw new UserAccountExistsException(account.Name);
- Hooks.AccountHooks.OnAccountCreate(user);
+ Hooks.AccountHooks.OnAccountCreate(account);
}
///
- /// Removes a given username from the database
+ /// Removes all user accounts from the database whose usernames match the given user account
///
- /// User user
- public void RemoveUser(User user)
+ /// The user account
+ public void RemoveUserAccount(UserAccount account)
{
try
{
- var tempuser = GetUser(user);
- int affected = _database.Query("DELETE FROM Users WHERE Username=@0", user.Name);
+ // Logout any player logged in as the account to be removed
+ TShock.Players.Where(p => p?.IsLoggedIn == true && p.Account.Name == account.Name).ForEach(p => p.Logout());
+
+ UserAccount tempuser = GetUserAccount(account);
+ int affected = _database.Query("DELETE FROM Users WHERE Username=@0", account.Name);
if (affected < 1)
- throw new UserNotExistException(user.Name);
+ throw new UserAccountNotExistException(account.Name);
Hooks.AccountHooks.OnAccountDelete(tempuser);
+
}
catch (Exception ex)
{
- throw new UserManagerException("RemoveUser SQL returned an error", ex);
+ throw new UserAccountManagerException("RemoveUser SQL returned an error", ex);
}
}
///
/// Sets the Hashed Password for a given username
///
- /// User user
- /// string password
- public void SetUserPassword(User user, string password)
+ /// The user account
+ /// The user account password to be set
+ public void SetUserAccountPassword(UserAccount account, string password)
{
try
{
- user.CreateBCryptHash(password);
+ account.CreateBCryptHash(password);
if (
- _database.Query("UPDATE Users SET Password = @0 WHERE Username = @1;", user.Password,
- user.Name) == 0)
- throw new UserNotExistException(user.Name);
+ _database.Query("UPDATE Users SET Password = @0 WHERE Username = @1;", account.Password,
+ account.Name) == 0)
+ throw new UserAccountNotExistException(account.Name);
}
catch (Exception ex)
{
- throw new UserManagerException("SetUserPassword SQL returned an error", ex);
+ throw new UserAccountManagerException("SetUserPassword SQL returned an error", ex);
}
}
///
/// Sets the UUID for a given username
///
- /// User user
- /// string uuid
- public void SetUserUUID(User user, string uuid)
+ /// The user account
+ /// The user account uuid to be set
+ public void SetUserAccountUUID(UserAccount account, string uuid)
{
try
{
if (
_database.Query("UPDATE Users SET UUID = @0 WHERE Username = @1;", uuid,
- user.Name) == 0)
- throw new UserNotExistException(user.Name);
+ account.Name) == 0)
+ throw new UserAccountNotExistException(account.Name);
}
catch (Exception ex)
{
- throw new UserManagerException("SetUserUUID SQL returned an error", ex);
+ throw new UserAccountManagerException("SetUserUUID SQL returned an error", ex);
}
}
///
/// Sets the group for a given username
///
- /// User user
- /// string group
- public void SetUserGroup(User user, string group)
+ /// The user account
+ /// The user account group to be set
+ public void SetUserGroup(UserAccount account, string group)
{
Group grp = TShock.Groups.GetGroupByName(group);
if (null == grp)
throw new GroupNotExistsException(group);
- if (_database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, user.Name) == 0)
- throw new UserNotExistException(user.Name);
+ if (_database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, account.Name) == 0)
+ throw new UserAccountNotExistException(account.Name);
try
{
// Update player group reference for any logged in player
- foreach (var player in TShock.Players.Where(p => p != null && p.User != null && p.User.Name == user.Name))
+ foreach (var player in TShock.Players.Where(p => p != null && p.Account != null && p.Account.Name == account.Name))
{
player.Group = grp;
}
}
catch (Exception ex)
{
- throw new UserManagerException("SetUserGroup SQL returned an error", ex);
+ throw new UserAccountManagerException("SetUserGroup SQL returned an error", ex);
}
}
- /// Updates the last accessed time for a database user to the current time.
- /// The user object to modify.
- public void UpdateLogin(User user)
+ /// Updates the last accessed time for a database user account to the current time.
+ /// The user account object to modify.
+ public void UpdateLogin(UserAccount account)
{
try
{
- if (_database.Query("UPDATE Users SET LastAccessed = @0, KnownIps = @1 WHERE Username = @2;", DateTime.UtcNow.ToString("s"), user.KnownIps, user.Name) == 0)
- throw new UserNotExistException(user.Name);
+ if (_database.Query("UPDATE Users SET LastAccessed = @0, KnownIps = @1 WHERE Username = @2;", DateTime.UtcNow.ToString("s"), account.KnownIps, account.Name) == 0)
+ throw new UserAccountNotExistException(account.Name);
}
catch (Exception ex)
{
- throw new UserManagerException("UpdateLogin SQL returned an error", ex);
+ throw new UserAccountManagerException("UpdateLogin SQL returned an error", ex);
}
}
- /// Gets the database ID of a given user object from the database.
- /// The username of the user to query for.
- /// The user's ID
- public int GetUserID(string username)
+ /// Gets the database ID of a given user account object from the database.
+ /// The username of the user account to query for.
+ /// The user account ID
+ public int GetUserAccountID(string username)
{
try
{
@@ -216,55 +220,55 @@ namespace TShockAPI.DB
return -1;
}
- /// Gets a user object by name.
+ /// Gets a user account object by name.
/// The user's name.
- /// The user object returned from the search.
- public User GetUserByName(string name)
+ /// The user account object returned from the search.
+ public UserAccount GetUserAccountByName(string name)
{
try
{
- return GetUser(new User {Name = name});
+ return GetUserAccount(new UserAccount {Name = name});
}
- catch (UserManagerException)
+ catch (UserAccountManagerException)
{
return null;
}
}
- /// Gets a user object by their user ID.
+ /// Gets a user account object by their user account ID.
/// The user's ID.
- /// The user object returned from the search.
- public User GetUserByID(int id)
+ /// The user account object returned from the search.
+ public UserAccount GetUserAccountByID(int id)
{
try
{
- return GetUser(new User {ID = id});
+ return GetUserAccount(new UserAccount {ID = id});
}
- catch (UserManagerException)
+ catch (UserAccountManagerException)
{
return null;
}
}
- /// Gets a user object by a user object.
- /// The user object to search by.
+ /// Gets a user account object by a user account object.
+ /// The user account object to search by.
/// The user object that is returned from the search.
- public User GetUser(User user)
+ public UserAccount GetUserAccount(UserAccount account)
{
bool multiple = false;
string query;
string type;
object arg;
- if (0 != user.ID)
+ if (account.ID != 0)
{
query = "SELECT * FROM Users WHERE ID=@0";
- arg = user.ID;
+ arg = account.ID;
type = "id";
}
else
{
query = "SELECT * FROM Users WHERE Username=@0";
- arg = user.Name;
+ arg = account.Name;
type = "name";
}
@@ -274,38 +278,38 @@ namespace TShockAPI.DB
{
if (result.Read())
{
- user = LoadUserFromResult(user, result);
+ account = LoadUserAccountFromResult(account, result);
// Check for multiple matches
if (!result.Read())
- return user;
+ return account;
multiple = true;
}
}
}
catch (Exception ex)
{
- throw new UserManagerException("GetUser SQL returned an error (" + ex.Message + ")", ex);
+ throw new UserAccountManagerException("GetUser SQL returned an error (" + ex.Message + ")", ex);
}
if (multiple)
- throw new UserManagerException(String.Format("Multiple users found for {0} '{1}'", type, arg));
+ throw new UserAccountManagerException(String.Format("Multiple user accounts found for {0} '{1}'", type, arg));
- throw new UserNotExistException(user.Name);
+ throw new UserAccountNotExistException(account.Name);
}
- /// Gets all users from the database.
- /// The users from the database.
- public List GetUsers()
+ /// Gets all the user accounts from the database.
+ /// The user accounts from the database.
+ public List GetUserAccounts()
{
try
{
- List users = new List();
+ List accounts = new List();
using (var reader = _database.QueryReader("SELECT * FROM Users"))
{
while (reader.Read())
{
- users.Add(LoadUserFromResult(new User(), reader));
+ accounts.Add(LoadUserAccountFromResult(new UserAccount(), reader));
}
- return users;
+ return accounts;
}
}
catch (Exception ex)
@@ -316,26 +320,26 @@ namespace TShockAPI.DB
}
///
- /// Gets all users from the database with a username that starts with or contains
+ /// Gets all user accounts from the database with a username that starts with or contains
///
/// Rough username search. "n" will match "n", "na", "nam", "name", etc
/// If is not the first part of the username. If true then "name" would match "name", "username", "wordsnamewords", etc
/// Matching users or null if exception is thrown
- public List GetUsersByName(string username, bool notAtStart = false)
+ public List GetUserAccountsByName(string username, bool notAtStart = false)
{
try
{
- List users = new List();
+ List accounts = new List();
string search = notAtStart ? string.Format("%{0}%", username) : string.Format("{0}%", username);
using (var reader = _database.QueryReader("SELECT * FROM Users WHERE Username LIKE @0",
search))
{
while (reader.Read())
{
- users.Add(LoadUserFromResult(new User(), reader));
+ accounts.Add(LoadUserAccountFromResult(new UserAccount(), reader));
}
}
- return users;
+ return accounts;
}
catch (Exception ex)
{
@@ -344,61 +348,61 @@ namespace TShockAPI.DB
return null;
}
- /// Fills out the fields of a User object with the results from a QueryResult object.
- /// The user to add data to.
+ /// Fills out the fields of a User account object with the results from a QueryResult object.
+ /// The user account to add data to.
/// The QueryResult object to add data from.
/// The 'filled out' user object.
- private User LoadUserFromResult(User user, QueryResult result)
+ private UserAccount LoadUserAccountFromResult(UserAccount account, QueryResult result)
{
- user.ID = result.Get("ID");
- user.Group = result.Get("Usergroup");
- user.Password = result.Get("Password");
- user.UUID = result.Get("UUID");
- user.Name = result.Get("Username");
- user.Registered = result.Get("Registered");
- user.LastAccessed = result.Get("LastAccessed");
- user.KnownIps = result.Get("KnownIps");
- return user;
+ account.ID = result.Get("ID");
+ account.Group = result.Get("Usergroup");
+ account.Password = result.Get("Password");
+ account.UUID = result.Get("UUID");
+ account.Name = result.Get("Username");
+ account.Registered = result.Get("Registered");
+ account.LastAccessed = result.Get("LastAccessed");
+ account.KnownIps = result.Get("KnownIps");
+ return account;
}
}
- /// A database user.
- public class User
+ /// A database user account.
+ public class UserAccount : IEquatable
{
- /// The database ID of the user.
+ /// The database ID of the user account.
public int ID { get; set; }
/// The user's name.
public string Name { get; set; }
- /// The hashed password for the user.
+ /// The hashed password for the user account.
public string Password { get; internal set; }
/// The user's saved Univerally Unique Identifier token.
public string UUID { get; set; }
-
- /// The group object that the user is a part of.
+
+ /// The group object that the user account is a part of.
public string Group { get; set; }
- /// The unix epoch corresponding to the registration date of the user.
+ /// The unix epoch corresponding to the registration date of the user account.
public string Registered { get; set; }
- /// The unix epoch corresponding to the last access date of the user.
+ /// The unix epoch corresponding to the last access date of the user account.
public string LastAccessed { get; set; }
- /// A JSON serialized list of known IP addresses for a user.
+ /// A JSON serialized list of known IP addresses for a user account.
public string KnownIps { get; set; }
- /// Constructor for the user object, assuming you define everything yourself.
+ /// Constructor for the user account object, assuming you define everything yourself.
/// The user's name.
/// The user's password hash.
/// The user's UUID.
/// The user's group name.
/// The unix epoch for the registration date.
/// The unix epoch for the last access date.
- /// The known IPs for the user, serialized as a JSON object
- /// A completed user object.
- public User(string name, string pass, string uuid, string group, string registered, string last, string known)
+ /// The known IPs for the user account, serialized as a JSON object
+ /// A completed user account object.
+ public UserAccount(string name, string pass, string uuid, string group, string registered, string last, string known)
{
Name = name;
Password = pass;
@@ -409,9 +413,9 @@ namespace TShockAPI.DB
KnownIps = known;
}
- /// Default constructor for a user object; holds no data.
- /// A user object.
- public User()
+ /// Default constructor for a user account object; holds no data.
+ /// A user account object.
+ public UserAccount()
{
Name = "";
Password = "";
@@ -428,7 +432,7 @@ namespace TShockAPI.DB
/// If the password is stored using BCrypt, it will be re-saved if the work factor in the config
/// is greater than the existing work factor with the new work factor.
///
- /// The password to check against the user object.
+ /// The password to check against the user account object.
/// bool true, if the password matched, or false, if it didn't.
public bool VerifyPassword(string password)
{
@@ -459,7 +463,7 @@ namespace TShockAPI.DB
}
/// Upgrades a password to BCrypt, from an insecure hashing algorithm.
- /// The raw user password (unhashed) to upgrade
+ /// The raw user account password (unhashed) to upgrade
protected void UpgradePasswordToBCrypt(string password)
{
// Save the old password, in the event that we have to revert changes.
@@ -467,9 +471,9 @@ namespace TShockAPI.DB
try
{
- TShock.Users.SetUserPassword(this, password);
+ TShock.UserAccounts.SetUserAccountPassword(this, password);
}
- catch (UserManagerException e)
+ catch (UserAccountManagerException e)
{
TShock.Log.ConsoleError(e.ToString());
Password = oldpassword; // Revert changes
@@ -477,7 +481,7 @@ namespace TShockAPI.DB
}
/// Upgrades a password to the highest work factor available in the config.
- /// The raw user password (unhashed) to upgrade
+ /// The raw user account password (unhashed) to upgrade
protected void UpgradePasswordWorkFactor(string password)
{
// If the destination work factor is not greater, we won't upgrade it or re-hash it
@@ -496,16 +500,16 @@ namespace TShockAPI.DB
{
try
{
- TShock.Users.SetUserPassword(this, password);
+ TShock.UserAccounts.SetUserAccountPassword(this, password);
}
- catch (UserManagerException e)
+ catch (UserAccountManagerException e)
{
TShock.Log.ConsoleError(e.ToString());
}
}
}
- /// Creates a BCrypt hash for a user and stores it in this object.
+ /// Creates a BCrypt hash for a user account and stores it in this object.
/// The plain text password to hash
public void CreateBCryptHash(string password)
{
@@ -524,7 +528,7 @@ namespace TShockAPI.DB
}
}
- /// Creates a BCrypt hash for a user and stores it in this object.
+ /// Creates a BCrypt hash for a user account and stores it in this object.
/// The plain text password to hash
/// The work factor to use in generating the password hash
public void CreateBCryptHash(string password, int workFactor)
@@ -581,59 +585,121 @@ namespace TShockAPI.DB
return HashPassword(Encoding.UTF8.GetBytes(password));
}
+ #region IEquatable
+
+ /// Indicates whether the current is equal to another .
+ /// true if the is equal to the parameter; otherwise, false.
+ /// An to compare with this .
+ public bool Equals(UserAccount other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return ID == other.ID && string.Equals(Name, other.Name);
+ }
+
+ /// Indicates whether the current is equal to another object.
+ /// true if the is equal to the parameter; otherwise, false.
+ /// An to compare with this .
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((UserAccount)obj);
+ }
+
+ /// Serves as the hash function.
+ /// A hash code for the current .
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (ID * 397) ^ (Name != null ? Name.GetHashCode() : 0);
+ }
+ }
+
+ ///
+ /// Compares equality of two objects.
+ ///
+ /// Left hand of the comparison.
+ /// Right hand of the comparison.
+ /// true if the objects are equal; otherwise, false.
+ public static bool operator ==(UserAccount left, UserAccount right)
+ {
+ return Equals(left, right);
+ }
+
+ ///
+ /// Compares equality of two objects.
+ ///
+ /// Left hand of the comparison.
+ /// Right hand of the comparison.
+ /// true if the objects aren't equal; otherwise, false.
+ public static bool operator !=(UserAccount left, UserAccount right)
+ {
+ return !Equals(left, right);
+ }
+
+ #endregion
+
+ ///
+ /// Converts the UserAccount to it's string representation
+ ///
+ /// Returns the UserAccount string representation
+ public override string ToString() => Name;
}
- /// UserManagerException - An exception generated by the user manager.
+ /// UserAccountManagerException - An exception generated by the user account manager.
[Serializable]
- public class UserManagerException : Exception
+ public class UserAccountManagerException : Exception
{
- /// Creates a new UserManagerException object.
+ /// Creates a new UserAccountManagerException object.
/// The message for the object.
- /// A new UserManagerException object.
- public UserManagerException(string message)
+ /// A new UserAccountManagerException object.
+ public UserAccountManagerException(string message)
: base(message)
{
}
- /// Creates a new UserManagerObject with an internal exception.
+ /// Creates a new UserAccountManager Object with an internal exception.
/// The message for the object.
/// The inner exception for the object.
- /// A new UserManagerException with a defined inner exception.
- public UserManagerException(string message, Exception inner)
+ /// A new UserAccountManagerException with a defined inner exception.
+ public UserAccountManagerException(string message, Exception inner)
: base(message, inner)
{
}
}
- /// A UserExistsException object, used when a user already exists when attempting to create a new one.
+ /// A UserExistsException object, used when a user account already exists when attempting to create a new one.
[Serializable]
- public class UserExistsException : UserManagerException
+ public class UserAccountExistsException : UserAccountManagerException
{
- /// Creates a new UserExistsException object.
- /// The name of the user that already exists.
- /// A UserExistsException object with the user's name passed in the message.
- public UserExistsException(string name)
- : base("User '" + name + "' already exists")
+ /// Creates a new UserAccountExistsException object.
+ /// The name of the user account that already exists.
+ /// A UserAccountExistsException object with the user's name passed in the message.
+ public UserAccountExistsException(string name)
+ : base("User account '" + name + "' already exists")
{
}
}
/// A UserNotExistException, used when a user does not exist and a query failed as a result of it.
[Serializable]
- public class UserNotExistException : UserManagerException
+ public class UserAccountNotExistException : UserAccountManagerException
{
- /// Creates a new UserNotExistException object, with the user's name in the message.
- /// The user's name to be pasesd in the message.
- /// A new UserNotExistException object with a message containing the user's name that does not exist.
- public UserNotExistException(string name)
- : base("User '" + name + "' does not exist")
+ /// Creates a new UserAccountNotExistException object, with the user account name in the message.
+ /// The user account name to be pasesd in the message.
+ /// A new UserAccountNotExistException object with a message containing the user account name that does not exist.
+ public UserAccountNotExistException(string name)
+ : base("User account '" + name + "' does not exist")
{
}
}
/// A GroupNotExistsException, used when a group does not exist.
[Serializable]
- public class GroupNotExistsException : UserManagerException
+ public class GroupNotExistsException : UserAccountManagerException
{
/// Creates a new GroupNotExistsException object with the group's name in the message.
/// The group name.
@@ -643,4 +709,4 @@ namespace TShockAPI.DB
{
}
}
-}
\ No newline at end of file
+}
diff --git a/TShockAPI/DB/WarpsManager.cs b/TShockAPI/DB/WarpsManager.cs
index d48ba40e..a409580e 100644
--- a/TShockAPI/DB/WarpsManager.cs
+++ b/TShockAPI/DB/WarpsManager.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -61,7 +61,7 @@ namespace TShockAPI.DB
/// The X position.
/// The Y position.
/// The name.
- /// Whether the opration succeeded.
+ /// Whether the operation succeeded.
public bool Add(int x, int y, string name)
{
try
diff --git a/TShockAPI/Extensions/DbExt.cs b/TShockAPI/Extensions/DbExt.cs
index 3db75ec6..b4e3e0f0 100644
--- a/TShockAPI/Extensions/DbExt.cs
+++ b/TShockAPI/Extensions/DbExt.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -23,6 +23,9 @@ using System.Diagnostics.CodeAnalysis;
namespace TShockAPI.DB
{
+ ///
+ /// Database extensions
+ ///
public static class DbExt
{
///
@@ -103,7 +106,7 @@ namespace TShockAPI.DB
public static IDbConnection CloneEx(this IDbConnection conn)
{
- var clone = (IDbConnection) Activator.CreateInstance(conn.GetType());
+ var clone = (IDbConnection)Activator.CreateInstance(conn.GetType());
clone.ConnectionString = conn.ConnectionString;
return clone;
}
@@ -120,80 +123,84 @@ namespace TShockAPI.DB
private static readonly Dictionary> ReadFuncs = new Dictionary
>
- {
- {
- typeof (bool),
- (s, i) => s.GetBoolean(i)
- },
- {
- typeof (bool?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetBoolean(i)
- },
- {
- typeof (byte),
- (s, i) => s.GetByte(i)
- },
- {
- typeof (byte?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetByte(i)
- },
- {
- typeof (Int16),
- (s, i) => s.GetInt16(i)
- },
- {
- typeof (Int16?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt16(i)
- },
- {
- typeof (Int32),
- (s, i) => s.GetInt32(i)
- },
- {
- typeof (Int32?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt32(i)
- },
- {
- typeof (Int64),
- (s, i) => s.GetInt64(i)
- },
- {
- typeof (Int64?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt64(i)
- },
- {
- typeof (string),
- (s, i) => s.GetString(i)
- },
- {
- typeof (decimal),
- (s, i) => s.GetDecimal(i)
- },
- {
- typeof (decimal?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetDecimal(i)
- },
- {
- typeof (float),
- (s, i) => s.GetFloat(i)
- },
- {
- typeof (float?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetFloat(i)
- },
- {
- typeof (double),
- (s, i) => s.GetDouble(i)
- },
- {
- typeof (double?),
- (s, i) => s.IsDBNull(i) ? null : (object)s.GetDouble(i)
- },
- {
- typeof (object),
- (s, i) => s.GetValue(i)
- },
- };
+ {
+ {
+ typeof (bool),
+ (s, i) => s.GetBoolean(i)
+ },
+ {
+ typeof (bool?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetBoolean(i)
+ },
+ {
+ typeof (byte),
+ (s, i) => s.GetByte(i)
+ },
+ {
+ typeof (byte?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetByte(i)
+ },
+ {
+ typeof (Int16),
+ (s, i) => s.GetInt16(i)
+ },
+ {
+ typeof (Int16?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt16(i)
+ },
+ {
+ typeof (Int32),
+ (s, i) => s.GetInt32(i)
+ },
+ {
+ typeof (Int32?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt32(i)
+ },
+ {
+ typeof (Int64),
+ (s, i) => s.GetInt64(i)
+ },
+ {
+ typeof (Int64?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetInt64(i)
+ },
+ {
+ typeof (string),
+ (s, i) => s.GetString(i)
+ },
+ {
+ typeof (decimal),
+ (s, i) => s.GetDecimal(i)
+ },
+ {
+ typeof (decimal?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetDecimal(i)
+ },
+ {
+ typeof (float),
+ (s, i) => s.GetFloat(i)
+ },
+ {
+ typeof (float?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetFloat(i)
+ },
+ {
+ typeof (double),
+ (s, i) => s.GetDouble(i)
+ },
+ {
+ typeof (double?),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetDouble(i)
+ },
+ {
+ typeof (DateTime),
+ (s, i) => s.IsDBNull(i) ? null : (object)s.GetDateTime(i)
+ },
+ {
+ typeof (object),
+ (s, i) => s.GetValue(i)
+ },
+ };
public static T Get(this IDataReader reader, string column)
{
@@ -205,8 +212,8 @@ namespace TShockAPI.DB
if (reader.IsDBNull(column))
return default(T);
- if (ReadFuncs.ContainsKey(typeof (T)))
- return (T) ReadFuncs[typeof (T)](reader, column);
+ if (ReadFuncs.ContainsKey(typeof(T)))
+ return (T)ReadFuncs[typeof(T)](reader, column);
throw new NotImplementedException();
}
@@ -272,4 +279,4 @@ namespace TShockAPI.DB
return Reader.Get(Reader.GetOrdinal(column));
}
}
-}
\ No newline at end of file
+}
diff --git a/TShockAPI/Extensions/ExceptionExt.cs b/TShockAPI/Extensions/ExceptionExt.cs
index fcae21e4..bd72f321 100644
--- a/TShockAPI/Extensions/ExceptionExt.cs
+++ b/TShockAPI/Extensions/ExceptionExt.cs
@@ -1,3 +1,21 @@
+/*
+TShock, a server mod for Terraria
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
using System;
namespace TShockAPI.Extensions
{
diff --git a/TShockAPI/Extensions/LinqExt.cs b/TShockAPI/Extensions/LinqExt.cs
index 80f8143f..54295985 100644
--- a/TShockAPI/Extensions/LinqExt.cs
+++ b/TShockAPI/Extensions/LinqExt.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/Extensions/RandomExt.cs b/TShockAPI/Extensions/RandomExt.cs
index da6de2de..6a14b149 100644
--- a/TShockAPI/Extensions/RandomExt.cs
+++ b/TShockAPI/Extensions/RandomExt.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/Extensions/StringExt.cs b/TShockAPI/Extensions/StringExt.cs
index 6c5323e2..2ba5aac3 100644
--- a/TShockAPI/Extensions/StringExt.cs
+++ b/TShockAPI/Extensions/StringExt.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/FileTools.cs b/TShockAPI/FileTools.cs
index 6938149c..ba6a9272 100644
--- a/TShockAPI/FileTools.cs
+++ b/TShockAPI/FileTools.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -26,7 +26,7 @@ namespace TShockAPI
public class FileTools
{
private const string MotdFormat =
- "This server is running TShock for Terraria.\n Type /help for a list of commands.\n%255,000,000%Current map: %map%\nCurrent players: %players%";
+ "This is [c/FF0000:%map%] on [c/00FFFF:TShock for Terraria].\n[c/00FF00:Current players:] [c/FFFF00:%players%]\nType [c/FF0000:/help] for a list of commands.\n";
///
/// Path to the file containing the rules.
///
@@ -154,7 +154,7 @@ namespace TShockAPI
{
if (string.IsNullOrWhiteSpace(line))
continue;
- contains = TShock.Utils.GetIPv4Address(line).Equals(ip);
+ contains = TShock.Utils.GetIPv4AddressFromHostname(line).Equals(ip);
if (contains)
return true;
}
diff --git a/TShockAPI/GeoIPCountry.cs b/TShockAPI/GeoIPCountry.cs
index d79cec83..6df90430 100644
--- a/TShockAPI/GeoIPCountry.cs
+++ b/TShockAPI/GeoIPCountry.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs
index 99264206..64242a07 100644
--- a/TShockAPI/GetDataHandlers.cs
+++ b/TShockAPI/GetDataHandlers.cs
@@ -1,6 +1,6 @@
/*
TShock, a server mod for Terraria
-Copyright (C) 2011-2017 Nyx Studios (fka. The TShock Team)
+Copyright (C) 2011-2019 Pryaxis & TShock Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -58,22 +58,337 @@ namespace TShockAPI
}
}
+ ///
+ /// A custom HandledEventArgs that contains TShock's TSPlayer for the triggering uesr and the Terraria MP data stream.
+ /// Differentiated by GetDataHandlerArgs because it can be handled and responds to being handled.
+ ///
+ public class GetDataHandledEventArgs : HandledEventArgs
+ {
+ /// The TSPlayer that triggered the event.
+ public TSPlayer Player { get; set; }
+
+ /// The raw MP packet data associated with the event.
+ public MemoryStream Data { get; set; }
+ }
+
public static class GetDataHandlers
{
private static Dictionary GetDataHandlerDelegates;
+
public static int[] WhitelistBuffMaxTime;
+
+ public static void InitGetDataHandler()
+ {
+ #region Blacklists
+
+ WhitelistBuffMaxTime = new int[Main.maxBuffTypes];
+ WhitelistBuffMaxTime[20] = 600;
+ WhitelistBuffMaxTime[0x18] = 1200;
+ WhitelistBuffMaxTime[0x1f] = 120;
+ WhitelistBuffMaxTime[0x27] = 420;
+
+ #endregion Blacklists
+
+ GetDataHandlerDelegates = new Dictionary
+ {
+ { PacketTypes.PlayerInfo, HandlePlayerInfo },
+ { PacketTypes.PlayerSlot, HandlePlayerSlot },
+ { PacketTypes.ContinueConnecting2, HandleConnecting },
+ { PacketTypes.TileGetSection, HandleGetSection },
+ { PacketTypes.PlayerSpawn, HandleSpawn },
+ { PacketTypes.PlayerUpdate, HandlePlayerUpdate },
+ { PacketTypes.PlayerHp, HandlePlayerHp },
+ { PacketTypes.Tile, HandleTile },
+ { PacketTypes.DoorUse, HandleDoorUse },
+ { PacketTypes.TileSendSquare, HandleSendTileSquare },
+ { PacketTypes.ItemDrop, HandleItemDrop },
+ { PacketTypes.ItemOwner, HandleItemOwner },
+ { PacketTypes.ProjectileNew, HandleProjectileNew },
+ { PacketTypes.NpcStrike, HandleNpcStrike },
+ { PacketTypes.ProjectileDestroy, HandleProjectileKill },
+ { PacketTypes.TogglePvp, HandleTogglePvp },
+ { PacketTypes.ChestGetContents, HandleChestOpen },
+ { PacketTypes.ChestItem, HandleChestItem },
+ { PacketTypes.ChestOpen, HandleChestActive },
+ { PacketTypes.PlaceChest, HandlePlaceChest },
+ { PacketTypes.Zones, HandlePlayerZone },
+ { PacketTypes.PasswordSend, HandlePassword },
+ { PacketTypes.PlayerAnimation, HandlePlayerAnimation },
+ { PacketTypes.PlayerMana, HandlePlayerMana },
+ { PacketTypes.PlayerTeam, HandlePlayerTeam },
+ { PacketTypes.SignNew, HandleSign },
+ { PacketTypes.LiquidSet, HandleLiquidSet },
+ { PacketTypes.PlayerBuff, HandlePlayerBuffList },
+ { PacketTypes.NpcSpecial, HandleSpecial },
+ { PacketTypes.NpcAddBuff, HandleNPCAddBuff },
+ { PacketTypes.PlayerAddBuff, HandlePlayerAddBuff },
+ { PacketTypes.UpdateNPCHome, UpdateNPCHome },
+ { PacketTypes.SpawnBossorInvasion, HandleSpawnBoss },
+ { PacketTypes.PaintTile, HandlePaintTile },
+ { PacketTypes.PaintWall, HandlePaintWall },
+ { PacketTypes.Teleport, HandleTeleport },
+ { PacketTypes.PlayerHealOther, HandleHealOther },
+ { PacketTypes.CatchNPC, HandleCatchNpc },
+ { PacketTypes.CompleteAnglerQuest, HandleCompleteAnglerQuest },
+ { PacketTypes.NumberOfAnglerQuestsCompleted, HandleNumberOfAnglerQuestsCompleted },
+ { PacketTypes.PlaceObject, HandlePlaceObject },
+ { PacketTypes.LoadNetModule, HandleLoadNetModule },
+ { PacketTypes.PlaceTileEntity, HandlePlaceTileEntity },
+ { PacketTypes.PlaceItemFrame, HandlePlaceItemFrame },
+ { PacketTypes.UpdateItemDrop, HandleItemDrop },
+ { PacketTypes.SyncExtraValue, HandleSyncExtraValue },
+ { PacketTypes.KillPortal, HandleKillPortal },
+ { PacketTypes.PlayerTeleportPortal, HandlePlayerPortalTeleport },
+ { PacketTypes.NpcTeleportPortal, HandleNpcTeleportPortal },
+ { PacketTypes.GemLockToggle, HandleGemLockToggle },
+ { PacketTypes.MassWireOperation, HandleMassWireOperation },
+ { PacketTypes.ToggleParty, HandleToggleParty },
+ { PacketTypes.CrystalInvasionStart, HandleOldOnesArmy },
+ { PacketTypes.PlayerHurtV2, HandlePlayerDamageV2 },
+ { PacketTypes.PlayerDeathV2, HandlePlayerKillMeV2 }
+ };
+ }
+
+ public static bool HandlerGetData(PacketTypes type, TSPlayer player, MemoryStream data)
+ {
+ GetDataHandlerDelegate handler;
+ if (GetDataHandlerDelegates.TryGetValue(type, out handler))
+ {
+ try
+ {
+ return handler(new GetDataHandlerArgs(player, data));
+ }
+ catch (Exception ex)
+ {
+ TShock.Log.Error(ex.ToString());
+ return true;
+ }
+ }
+ return false;
+ }
+
#region Events
+ public class PlayerInfoEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria playerID of the player
+ ///
+ public byte PlayerId { get; set; }
+ ///
+ /// Hair color
+ ///
+ public byte Hair { get; set; }
+ ///
+ /// Clothing style. 0-3 are for male characters, and 4-7 are for female characters.
+ ///
+ public int Style { get; set; }
+ ///
+ /// Character difficulty
+ ///
+ public byte Difficulty { get; set; }
+ ///
+ /// Player/character name
+ ///
+ public string Name { get; set; }
+ }
+ ///
+ /// PlayerInfo - called at a PlayerInfo event
+ /// If this is cancelled, the server will kick the player. If this should be changed in the future, let someone know.
+ ///
+ public static HandlerList PlayerInfo = new HandlerList();
+ private static bool OnPlayerInfo(TSPlayer player, MemoryStream data, byte _plrid, byte _hair, int _style, byte _difficulty, string _name)
+ {
+ if (PlayerInfo == null)
+ return false;
+
+ var args = new PlayerInfoEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = _plrid,
+ Hair = _hair,
+ Style = _style,
+ Difficulty = _difficulty,
+ Name = _name,
+ };
+ PlayerInfo.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a PlayerSlot event
+ ///
+ public class PlayerSlotEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria playerID
+ ///
+ public byte PlayerId { get; set; }
+ ///
+ /// The slot edited
+ ///
+ public byte Slot { get; set; }
+ ///
+ /// The stack edited
+ ///
+ public short Stack { get; set; }
+ ///
+ /// The item prefix
+ ///
+ public byte Prefix { get; set; }
+ ///
+ /// Item type
+ ///
+ public short Type { get; set; }
+ }
+ ///
+ /// PlayerSlot - called at a PlayerSlot event
+ ///
+ public static HandlerList PlayerSlot = new HandlerList();
+ private static bool OnPlayerSlot(TSPlayer player, MemoryStream data, byte _plr, byte _slot, short _stack, byte _prefix, short _type)
+ {
+ if (PlayerSlot == null)
+ return false;
+
+ var args = new PlayerSlotEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = _plr,
+ Slot = _slot,
+ Stack = _stack,
+ Prefix = _prefix,
+ Type = _type
+ };
+ PlayerSlot.Invoke(null, args);
+ return args.Handled;
+ }
+
+ /// The arguments to a GetSection packet.
+ public class GetSectionEventArgs : GetDataHandledEventArgs
+ {
+ /// The X position requested. Or -1 for spawn.
+ public int X { get; set; }
+
+ /// The Y position requested. Or -1 for spawn.
+ public int Y { get; set; }
+ }
+ /// The hook for a GetSection event.
+ public static HandlerList GetSection = new HandlerList();
+ private static bool OnGetSection(TSPlayer player, MemoryStream data, int x, int y)
+ {
+ if (GetSection == null)
+ return false;
+
+ var args = new GetSectionEventArgs
+ {
+ Player = player,
+ Data = data,
+ X = x,
+ Y = y,
+ };
+
+ GetSection.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a PlayerUpdate event
+ ///
+ public class PlayerUpdateEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria playerID of the player
+ ///
+ public byte PlayerId { get; set; }
+ ///
+ /// Control direction (BitFlags)
+ ///
+ public byte Control { get; set; }
+ ///
+ /// Selected item
+ ///
+ public byte Item { get; set; }
+ ///
+ /// Position of the player
+ ///
+ public Vector2 Position { get; set; }
+ ///
+ /// Velocity of the player
+ ///
+ public Vector2 Velocity { get; set; }
+ /// Pulley update (BitFlags)
+ public byte Pulley { get; set; }
+ }
+ ///
+ /// PlayerUpdate - When the player sends it's updated information to the server
+ ///
+ public static HandlerList PlayerUpdate = new HandlerList();
+ private static bool OnPlayerUpdate(TSPlayer player, MemoryStream data, byte plr, byte control, byte item, Vector2 position, Vector2 velocity, byte pulley)
+ {
+ if (PlayerUpdate == null)
+ return false;
+
+ var args = new PlayerUpdateEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = plr,
+ Control = control,
+ Item = item,
+ Position = position,
+ Velocity = velocity,
+ Pulley = pulley
+ };
+ PlayerUpdate.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a PlayerHP event
+ ///
+ public class PlayerHPEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria playerID of the player
+ ///
+ public byte PlayerId { get; set; }
+ ///
+ /// Current HP
+ ///
+ public short Current { get; set; }
+ ///
+ /// Maximum HP
+ ///
+ public short Max { get; set; }
+ }
+ ///
+ /// PlayerHP - called at a PlayerHP event
+ ///
+ public static HandlerList PlayerHP = new HandlerList();
+ private static bool OnPlayerHP(TSPlayer player, MemoryStream data, byte _plr, short _cur, short _max)
+ {
+ if (PlayerHP == null)
+ return false;
+
+ var args = new PlayerHPEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = _plr,
+ Current = _cur,
+ Max = _max,
+ };
+ PlayerHP.Invoke(null, args);
+ return args.Handled;
+ }
+
///
/// Used when a TileEdit event is called.
///
- public class TileEditEventArgs : HandledEventArgs
+ public class TileEditEventArgs : GetDataHandledEventArgs
{
- ///
- /// The TSPlayer who made the tile edit
- ///
- public TSPlayer Player { get; set; }
-
///
/// The tile coordinate on the X plane
///
@@ -104,12 +419,11 @@ namespace TShockAPI
///
public byte Style { get; set; }
}
-
///
/// TileEdit - called when a tile is placed or destroyed
///
- public static HandlerList TileEdit;
- private static bool OnTileEdit(TSPlayer ply, int x, int y, EditAction action, EditType editDetail, short editData, byte style)
+ public static HandlerList TileEdit = new HandlerList();
+ private static bool OnTileEdit(TSPlayer ply, MemoryStream data, int x, int y, EditAction action, EditType editDetail, short editData, byte style)
{
if (TileEdit == null)
return false;
@@ -117,6 +431,7 @@ namespace TShockAPI
var args = new TileEditEventArgs
{
Player = ply,
+ Data = data,
X = x,
Y = y,
Action = action,
@@ -127,452 +442,113 @@ namespace TShockAPI
TileEdit.Invoke(null, args);
return args.Handled;
}
- ///
- /// For use in a TogglePvp event
- ///
- public class TogglePvpEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria player ID of the player
- ///
- public byte PlayerId { get; set; }
- ///
- /// Enable/disable pvp?
- ///
- public bool Pvp { get; set; }
- }
- ///
- /// TogglePvp - called when a player toggles pvp
- ///
- public static HandlerList TogglePvp;
- private static bool OnPvpToggled(byte _id, bool _pvp)
- {
- if (TogglePvp == null)
- return false;
-
- var args = new TogglePvpEventArgs
- {
- PlayerId = _id,
- Pvp = _pvp,
- };
- TogglePvp.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a PlayerTeam event
- ///
- public class PlayerTeamEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria player ID of the player
- ///
- public byte PlayerId { get; set; }
- ///
- /// Enable/disable pvp?
- ///
- public byte Team { get; set; }
- }
- ///
- /// TogglePvp - called when a player toggles pvp
- ///
- public static HandlerList PlayerTeam;
- private static bool OnPlayerTeam(byte _id, byte _team)
- {
- if (PlayerTeam == null)
- return false;
-
- var args = new PlayerTeamEventArgs
- {
- PlayerId = _id,
- Team = _team,
- };
- PlayerTeam.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a PlayerSlot event
- ///
- public class PlayerSlotEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID
- ///
- public byte PlayerId { get; set; }
- ///
- /// The slot edited
- ///
- public byte Slot { get; set; }
- ///
- /// The stack edited
- ///
- public short Stack { get; set; }
- ///
- /// The item prefix
- ///
- public byte Prefix { get; set; }
- ///
- /// Item type
- ///
- public short Type { get; set; }
- }
- ///
- /// PlayerSlot - called at a PlayerSlot event
- ///
- public static HandlerList PlayerSlot;
- private static bool OnPlayerSlot(byte _plr, byte _slot, short _stack, byte _prefix, short _type)
- {
- if (PlayerSlot == null)
- return false;
-
- var args = new PlayerSlotEventArgs
- {
- PlayerId = _plr,
- Slot = _slot,
- Stack = _stack,
- Prefix = _prefix,
- Type = _type
- };
- PlayerSlot.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a PlayerHP event
- ///
- public class PlayerHPEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public byte PlayerId { get; set; }
- ///
- /// Current HP
- ///
- public short Current { get; set; }
- ///
- /// Maximum HP
- ///
- public short Max { get; set; }
- }
- ///
- /// PlayerHP - called at a PlayerHP event
- ///
- public static HandlerList PlayerHP;
-
- private static bool OnPlayerHP(byte _plr, short _cur, short _max)
- {
- if (PlayerHP == null)
- return false;
-
- var args = new PlayerHPEventArgs
- {
- PlayerId = _plr,
- Current = _cur,
- Max = _max,
- };
- PlayerHP.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a PlayerMana event
- ///
- public class PlayerManaEventArgs : HandledEventArgs
- {
- public byte PlayerId { get; set; }
- public short Current { get; set; }
- public short Max { get; set; }
- }
- ///
- /// PlayerMana - called at a PlayerMana event
- ///
- public static HandlerList PlayerMana;
-
- private static bool OnPlayerMana(byte _plr, short _cur, short _max)
- {
- if (PlayerMana == null)
- return false;
-
- var args = new PlayerManaEventArgs
- {
- PlayerId = _plr,
- Current = _cur,
- Max = _max,
- };
- PlayerMana.Invoke(null, args);
- return args.Handled;
- }
-
- public class PlayerInfoEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public byte PlayerId { get; set; }
- ///
- /// Hair color
- ///
- public byte Hair { get; set; }
- ///
- /// Clothing style. 0-3 are for male characters, and 4-7 are for female characters.
- ///
- public int Style { get; set; }
- ///
- /// Character difficulty
- ///
- public byte Difficulty { get; set; }
- ///
- /// Player/character name
- ///
- public string Name { get; set; }
- }
- ///
- /// PlayerInfo - called at a PlayerInfo event
- /// If this is cancelled, the server will ForceKick the player. If this should be changed in the future, let someone know.
- ///
- public static HandlerList PlayerInfo;
-
- private static bool OnPlayerInfo(byte _plrid, byte _hair, int _style, byte _difficulty, string _name)
- {
- if (PlayerInfo == null)
- return false;
-
- var args = new PlayerInfoEventArgs
- {
- PlayerId = _plrid,
- Hair = _hair,
- Style = _style,
- Difficulty = _difficulty,
- Name = _name,
- };
- PlayerInfo.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a TileKill event
- ///
- public class TileKillEventArgs : HandledEventArgs
- {
- ///
- /// The X coordinate that is being killed
- ///
- public int TileX { get; set; }
- ///
- /// The Y coordinate that is being killed
- ///
- public int TileY { get; set; }
- }
- ///
- /// TileKill - When a tile is removed from the world
- ///
- public static HandlerList TileKill;
-
- private static bool OnTileKill(int tilex, int tiley)
- {
- if (TileKill == null)
- return false;
-
- var args = new TileKillEventArgs
- {
- TileX = tilex,
- TileY = tiley,
- };
- TileKill.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a KillMe event
- ///
- public class KillMeEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public byte PlayerId { get; set; }
- ///
- /// The direction the damage is coming from (?)
- ///
- public byte Direction { get; set; }
- ///
- /// Amount of damage delt
- ///
- public short Damage { get; set; }
- ///
- /// Player's current pvp setting
- ///
- public bool Pvp { get; set; }
- }
- ///
- /// KillMe - Terraria's crappy way of handling damage from players
- ///
- public static HandlerList KillMe;
-
- private static bool OnKillMe(byte plr, byte direction, short damage, bool pvp)
- {
- if (KillMe == null)
- return false;
-
- var args = new KillMeEventArgs
- {
- PlayerId = plr,
- Direction = direction,
- Damage = damage,
- Pvp = pvp,
- };
- KillMe.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a PlayerUpdate event
- ///
- public class PlayerUpdateEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public byte PlayerId { get; set; }
- ///
- /// ???
- ///
- public byte Control { get; set; }
- ///
- /// Current item?
- ///
- public byte Item { get; set; }
- ///
- /// Position of the player
- ///
- public Vector2 Position { get; set; }
- ///
- /// Velocity of the player
- ///
- public Vector2 Velocity { get; set; }
-
- public byte Pulley { get; set; }
- }
- ///
- /// PlayerUpdate - When the player sends it's updated information to the server
- ///
- public static HandlerList PlayerUpdate;
-
- private static bool OnPlayerUpdate(byte player, byte control, byte item, Vector2 position, Vector2 velocity, byte pulley)
- {
- if (PlayerUpdate == null)
- return false;
-
- var args = new PlayerUpdateEventArgs
- {
- PlayerId = player,
- Control = control,
- Item = item,
- Position = position,
- Velocity = velocity,
- Pulley = pulley
- };
- PlayerUpdate.Invoke(null, args);
- return args.Handled;
- }
- public static bool TSCheckNoclip(Vector2 Position, int Width, int Height)
- {
- int num = (int)(Position.X / 16f);
- int num2 = (int)((Position.X + (float)Width) / 16f);
- int num3 = (int)(Position.Y / 16f);
- int num4 = (int)((Position.Y + (float)Height) / 16f);
- if (num < 0)
- {
- num = 0;
- }
- if (num2 > Main.maxTilesX)
- {
- num2 = Main.maxTilesX;
- }
- if (num3 < 0)
- {
- num3 = 0;
- }
- if (num4 > Main.maxTilesY)
- {
- num4 = Main.maxTilesY;
- }
- for (int c = num; c < num2; c++)
- {
- for (int d = num3; d < num4; d++)
- {
- if (Main.tile[c, d].liquid != 0)
- return false;
- }
- }
- for (int i = num; i < num2; i++)
- {
- for (int j = num3; j < num4; j++)
- {
- if (Main.tile[i, j] == null || Main.tileSand[Main.tile[i, j].type]
- || !TShock.Utils.TileSolid(i, j) || !TShock.Utils.TileSolid(i + 1, j) || !TShock.Utils.TileSolid(i - 1, j)
- || !TShock.Utils.TileSolid(i, j + 1) || !TShock.Utils.TileSolid(i + 1, j + 1) || !TShock.Utils.TileSolid(i - 1, j + 1)
- || !TShock.Utils.TileSolid(i, j - 1) || !TShock.Utils.TileSolid(i + 1, j - 1) || !TShock.Utils.TileSolid(i - 1, j - 1)
- || Main.tileSolidTop[(int)Main.tile[i, j].type])
- {
- continue;
- }
-
- Vector2 vector;
- vector.X = (float)(i * 16);
- vector.Y = (float)(j * 16);
- if (Position.X + (float)Width > vector.X && Position.X < vector.X + 16f && Position.Y + (float)Height > vector.Y && Position.Y < vector.Y + 16f)
- {
- return true;
- }
- }
- }
- return false;
- }
-
+
///
/// For use in a SendTileSquare event
///
- public class SendTileSquareEventArgs : HandledEventArgs
+ public class SendTileSquareEventArgs : GetDataHandledEventArgs
{
///
/// Size of the area
///
public short Size { get; set; }
+
///
/// A corner of the section
///
public int TileX { get; set; }
+
///
/// A corner of the section
///
public int TileY { get; set; }
}
///
- /// SendTileSquare - When the player sends a tile square
+ /// When the player sends a tile square
///
- public static HandlerList SendTileSquare;
-
- private static bool OnSendTileSquare(short size, int tilex, int tiley)
+ public static HandlerList SendTileSquare = new HandlerList();
+ private static bool OnSendTileSquare(TSPlayer player, MemoryStream data, short size, int tilex, int tiley)
{
if (SendTileSquare == null)
return false;
var args = new SendTileSquareEventArgs
{
+ Player = player,
+ Data = data,
Size = size,
TileX = tilex,
TileY = tiley,
};
+
SendTileSquare.Invoke(null, args);
return args.Handled;
}
+
+ ///
+ /// For use in an ItemDrop event
+ ///
+ public class ItemDropEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// ID of the item.
+ /// If below 400 and NetID(Type) is 0 Then Set Null. If ItemID is 400 Then New Item
+ ///
+ public short ID { get; set; }
+ ///
+ /// Position of the item
+ ///
+ public Vector2 Position { get; set; }
+ ///
+ /// Velocity at which the item is deployed
+ ///
+ public Vector2 Velocity { get; set; }
+ ///
+ /// Stacks
+ ///
+ public short Stacks { get; set; }
+ ///
+ /// Prefix of the item
+ ///
+ public byte Prefix { get; set; }
+ ///
+ /// No Delay on pickup
+ ///
+ public bool NoDelay { get; set; }
+ ///
+ /// Item type
+ ///
+ public short Type { get; set; }
+ }
+ ///
+ /// ItemDrop - Called when an item is dropped
+ ///
+ public static HandlerList ItemDrop = new HandlerList();
+ private static bool OnItemDrop(TSPlayer player, MemoryStream data, short id, Vector2 pos, Vector2 vel, short stacks, byte prefix, bool noDelay, short type)
+ {
+ if (ItemDrop == null)
+ return false;
+
+ var args = new ItemDropEventArgs
+ {
+ Player = player,
+ Data = data,
+ ID = id,
+ Position = pos,
+ Velocity = vel,
+ Stacks = stacks,
+ Prefix = prefix,
+ NoDelay = noDelay,
+ Type = type,
+ };
+ ItemDrop.Invoke(null, args);
+ return args.Handled;
+ }
+
///
/// For use in a NewProjectile event
///
- public class NewProjectileEventArgs : HandledEventArgs
+ public class NewProjectileEventArgs : GetDataHandledEventArgs
{
///
/// ???
@@ -610,15 +586,15 @@ namespace TShockAPI
///
/// NewProjectile - Called when a client creates a new projectile
///
- public static HandlerList NewProjectile;
-
- private static bool OnNewProjectile(short ident, Vector2 pos, Vector2 vel, float knockback, short dmg, byte owner, short type, int index)
+ public static HandlerList NewProjectile = new HandlerList();
+ private static bool OnNewProjectile(MemoryStream data, short ident, Vector2 pos, Vector2 vel, float knockback, short dmg, byte owner, short type, int index, TSPlayer player)
{
if (NewProjectile == null)
return false;
var args = new NewProjectileEventArgs
{
+ Data = data,
Identity = ident,
Position = pos,
Velocity = vel,
@@ -627,412 +603,16 @@ namespace TShockAPI
Owner = owner,
Type = type,
Index = index,
+ Player = player,
};
NewProjectile.Invoke(null, args);
return args.Handled;
}
-
- ///
- /// For use in a LiquidSet event
- ///
- public class LiquidSetEventArgs : HandledEventArgs
- {
- ///
- /// X location of the tile
- ///
- public int TileX { get; set; }
- ///
- /// Y location of the tile
- ///
- public int TileY { get; set; }
- ///
- /// Amount of liquid
- ///
- public byte Amount { get; set; }
- ///
- /// Type of Liquid: 0=water, 1=lave, 2=honey
- ///
- public byte Type { get; set; }
- }
- ///
- /// LiquidSet - When ever a liquid is set
- ///
- public static HandlerList LiquidSet;
-
- private static bool OnLiquidSet(int tilex, int tiley, byte amount, byte type)
- {
- if (LiquidSet == null)
- return false;
-
- var args = new LiquidSetEventArgs
- {
- TileX = tilex,
- TileY = tiley,
- Amount = amount,
- Type = type,
- };
- LiquidSet.Invoke(null, args);
- return args.Handled;
- }
- ///
- /// For use in a PlayerSpawn event
- ///
- public class SpawnEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public byte Player { get; set; }
- ///
- /// X location of the player's spawn
- ///
- public int SpawnX { get; set; }
- ///
- /// Y location of the player's spawn
- ///
- public int SpawnY { get; set; }
- }
- ///
- /// PlayerSpawn - When a player spawns
- ///
- public static HandlerList PlayerSpawn;
-
- private static bool OnPlayerSpawn(byte player, int spawnX, int spawnY)
- {
- if (PlayerSpawn == null)
- return false;
-
- var args = new SpawnEventArgs
- {
- Player = player,
- SpawnX = spawnX,
- SpawnY = spawnY,
- };
- PlayerSpawn.Invoke(null, args);
- return args.Handled;
- }
- ///
- /// For use with a ChestOpen event
- ///
- public class ChestOpenEventArgs : HandledEventArgs
- {
- ///
- /// X location of said chest
- ///
- public int X { get; set; }
- ///
- /// Y location of said chest
- ///
- public int Y { get; set; }
-
- ///
- /// The player opening the chest
- ///
- public TSPlayer Player { get; set; }
- }
- ///
- /// ChestOpen - Called when any chest is opened
- ///
- public static HandlerList ChestOpen;
-
- private static bool OnChestOpen(int x, int y, TSPlayer player)
- {
- if (ChestOpen == null)
- return false;
-
- var args = new ChestOpenEventArgs
- {
- X = x,
- Y = y,
- Player = player,
- };
- ChestOpen.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a ChestItemChange event
- ///
- public class ChestItemEventArgs : HandledEventArgs
- {
- ///
- /// ChestID
- ///
- public short ID { get; set; }
- ///
- /// Slot of the item
- ///
- public byte Slot { get; set; }
- ///
- /// How many?
- ///
- public short Stacks { get; set; }
- ///
- /// Item prefix
- ///
- public byte Prefix { get; set; }
- ///
- /// Item type
- ///
- public short Type { get; set; }
- }
- ///
- /// ChestItemChange - Called when an item in a chest changes
- ///
- public static HandlerList ChestItemChange;
-
- private static bool OnChestItemChange(short id, byte slot, short stacks, byte prefix, short type)
- {
- if (ChestItemChange == null)
- return false;
-
- var args = new ChestItemEventArgs
- {
- ID = id,
- Slot = slot,
- Stacks = stacks,
- Prefix = prefix,
- Type = type,
- };
- ChestItemChange.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a Sign event
- ///
- public class SignEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public short ID { get; set; }
- ///
- /// X location of the sign
- ///
- public int X { get; set; }
- ///
- /// Y location of the sign
- ///
- public int Y { get; set; }
- }
- ///
- /// Sign - Called when a sign is changed
- ///
- public static HandlerList Sign;
-
- private static bool OnSignEvent(short id, int x, int y)
- {
- if (Sign == null)
- return false;
-
- var args = new SignEventArgs
- {
- ID = id,
- X = x,
- Y = y,
- };
- Sign.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a NPCHome event
- ///
- public class NPCHomeChangeEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public short ID { get; set; }
- ///
- /// X location of the NPC home change
- ///
- public short X { get; set; }
- ///
- /// Y location of the NPC home change
- ///
- public short Y { get; set; }
- ///
- /// ByteBool homeless
- ///
- public byte Homeless { get; set; }
- }
- ///
- /// NPCHome - Called when an NPC's home is changed
- ///
- public static HandlerList NPCHome;
-
- private static bool OnUpdateNPCHome(short id, short x, short y, byte homeless)
- {
- if (NPCHome == null)
- return false;
-
- var args = new NPCHomeChangeEventArgs
- {
- ID = id,
- X = x,
- Y = y,
- Homeless = homeless,
- };
- NPCHome.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a PlayerBuff event
- ///
- public class PlayerBuffEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public byte ID { get; set; }
- ///
- /// Buff Type
- ///
- public byte Type { get; set; }
- ///
- /// Time the buff lasts
- ///
- public short Time { get; set; }
- }
- ///
- /// PlayerBuff - Called when a player is buffed
- ///
- public static HandlerList PlayerBuff;
-
- private static bool OnPlayerBuff(byte id, byte type, short time)
- {
- if (PlayerBuff == null)
- return false;
-
- var args = new PlayerBuffEventArgs
- {
- ID = id,
- Type = type,
- Time = time,
- };
- PlayerBuff.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in an ItemDrop event
- ///
- public class ItemDropEventArgs : HandledEventArgs
- {
- ///
- /// The player who sent message
- ///
- public TSPlayer Player { get; set; }
- ///
- /// ID of the item.
- /// If below 400 and NetID(Type) is 0 Then Set Null. If ItemID is 400 Then New Item
- ///
- public short ID { get; set; }
- ///
- /// Position of the item
- ///
- public Vector2 Position { get; set; }
- ///
- /// Velocity at which the item is deployed
- ///
- public Vector2 Velocity { get; set; }
- ///
- /// Stacks
- ///
- public short Stacks { get; set; }
- ///
- /// Prefix of the item
- ///
- public byte Prefix { get; set; }
- ///
- /// No Delay on pickup
- ///
- public bool NoDelay { get; set; }
- ///
- /// Item type
- ///
- public short Type { get; set; }
- }
- ///
- /// ItemDrop - Called when an item is dropped
- ///
- public static HandlerList ItemDrop;
-
- private static bool OnItemDrop(TSPlayer player, short id, Vector2 pos, Vector2 vel, short stacks, byte prefix, bool noDelay, short type)
- {
- if (ItemDrop == null)
- return false;
-
- var args = new ItemDropEventArgs
- {
- Player = player,
- ID = id,
- Position = pos,
- Velocity = vel,
- Stacks = stacks,
- Prefix = prefix,
- NoDelay = noDelay,
- Type = type,
- };
- ItemDrop.Invoke(null, args);
- return args.Handled;
- }
-
- ///
- /// For use in a PlayerDamage event
- ///
- public class PlayerDamageEventArgs : HandledEventArgs
- {
- ///
- /// The Terraria playerID of the player
- ///
- public byte ID { get; set; }
- ///
- /// The direction the damage is occuring from
- ///
- public byte Direction { get; set; }
- ///
- /// Amount of damage
- ///
- public short Damage { get; set; }
- ///
- /// If the player has PVP on
- ///
- public bool PVP { get; set; }
- ///
- /// Is the damage critical?
- ///
- public bool Critical { get; set; }
- }
- ///
- /// PlayerDamage - Called when a player is damaged
- ///
- public static HandlerList PlayerDamage;
-
- private static bool OnPlayerDamage(byte id, byte dir, short dmg, bool pvp, bool crit)
- {
- if (PlayerDamage == null)
- return false;
-
- var args = new PlayerDamageEventArgs
- {
- ID = id,
- Direction = dir,
- Damage = dmg,
- PVP = pvp,
- Critical = crit,
- };
- PlayerDamage.Invoke(null, args);
- return args.Handled;
- }
-
+
///
/// For use with a NPCStrike event
///
- public class NPCStrikeEventArgs : HandledEventArgs
+ public class NPCStrikeEventArgs : GetDataHandledEventArgs
{
///
/// ???
@@ -1058,15 +638,16 @@ namespace TShockAPI
///
/// NPCStrike - Called when an NPC is attacked
///
- public static HandlerList NPCStrike;
-
- private static bool OnNPCStrike(short id, byte dir, short dmg, float knockback, byte crit)
+ public static HandlerList NPCStrike = new HandlerList();
+ private static bool OnNPCStrike(TSPlayer player, MemoryStream data, short id, byte dir, short dmg, float knockback, byte crit)
{
if (NPCStrike == null)
return false;
var args = new NPCStrikeEventArgs
{
+ Player = player,
+ Data = data,
ID = id,
Direction = dir,
Damage = dmg,
@@ -1076,11 +657,488 @@ namespace TShockAPI
NPCStrike.Invoke(null, args);
return args.Handled;
}
+
+ /// The arguments to the ProjectileKill packet.
+ public class ProjectileKillEventArgs : GetDataHandledEventArgs
+ {
+ /// The projectile's identity...?
+ public int ProjectileIdentity;
+ /// The the player index of the projectile's owner (Main.players).
+ public byte ProjectileOwner;
+ /// The index of the projectile in Main.projectile.
+ public int ProjectileIndex;
+ }
+ /// The event fired when a projectile kill packet is received.
+ public static HandlerList ProjectileKill = new HandlerList();
+ /// Fires the ProjectileKill event.
+ /// The TSPlayer that caused the event.
+ /// The MemoryStream containing the raw event data.
+ /// The projectile identity (from the packet).
+ /// The projectile's owner (from the packet).
+ /// The projectile's index (from Main.projectiles).
+ /// bool
+ private static bool OnProjectileKill(TSPlayer player, MemoryStream data, int identity, byte owner, int index)
+ {
+ if (ProjectileKill == null)
+ return false;
+
+ var args = new ProjectileKillEventArgs
+ {
+ Player = player,
+ Data = data,
+ ProjectileIdentity = identity,
+ ProjectileOwner = owner,
+ ProjectileIndex = index,
+ };
+
+ ProjectileKill.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a TogglePvp event
+ ///
+ public class TogglePvpEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria player ID of the player
+ ///
+ public byte PlayerId { get; set; }
+ ///
+ /// Enable/disable pvp?
+ ///
+ public bool Pvp { get; set; }
+ }
+ ///
+ /// TogglePvp - called when a player toggles pvp
+ ///
+ public static HandlerList TogglePvp = new HandlerList();
+ private static bool OnPvpToggled(TSPlayer player, MemoryStream data, byte _id, bool _pvp)
+ {
+ if (TogglePvp == null)
+ return false;
+
+ var args = new TogglePvpEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = _id,
+ Pvp = _pvp,
+ };
+ TogglePvp.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a PlayerSpawn event
+ ///
+ public class SpawnEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria playerID of the player
+ ///
+ public byte PlayerId { get; set; }
+ ///
+ /// X location of the player's spawn
+ ///
+ public int SpawnX { get; set; }
+ ///
+ /// Y location of the player's spawn
+ ///
+ public int SpawnY { get; set; }
+ }
+ ///
+ /// PlayerSpawn - When a player spawns
+ ///
+ public static HandlerList PlayerSpawn = new HandlerList();
+ private static bool OnPlayerSpawn(TSPlayer player, MemoryStream data, byte pid, int spawnX, int spawnY)
+ {
+ if (PlayerSpawn == null)
+ return false;
+
+ var args = new SpawnEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = pid,
+ SpawnX = spawnX,
+ SpawnY = spawnY,
+ };
+ PlayerSpawn.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a ChestItemChange event
+ ///
+ public class ChestItemEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// ChestID
+ ///
+ public short ID { get; set; }
+ ///
+ /// Slot of the item
+ ///
+ public byte Slot { get; set; }
+ ///
+ /// How many?
+ ///
+ public short Stacks { get; set; }
+ ///
+ /// Item prefix
+ ///
+ public byte Prefix { get; set; }
+ ///
+ /// Item type
+ ///
+ public short Type { get; set; }
+ }
+ ///
+ /// ChestItemChange - Called when an item in a chest changes
+ ///
+ public static HandlerList ChestItemChange = new HandlerList();
+ private static bool OnChestItemChange(TSPlayer player, MemoryStream data, short id, byte slot, short stacks, byte prefix, short type)
+ {
+ if (ChestItemChange == null)
+ return false;
+
+ var args = new ChestItemEventArgs
+ {
+ Player = player,
+ Data = data,
+ ID = id,
+ Slot = slot,
+ Stacks = stacks,
+ Prefix = prefix,
+ Type = type,
+ };
+ ChestItemChange.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use with a ChestOpen event
+ ///
+ public class ChestOpenEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// X location of said chest
+ ///
+ public int X { get; set; }
+ ///
+ /// Y location of said chest
+ ///
+ public int Y { get; set; }
+ }
+ ///
+ /// ChestOpen - Called when any chest is opened
+ ///
+ public static HandlerList ChestOpen = new HandlerList();
+ private static bool OnChestOpen(MemoryStream data, int x, int y, TSPlayer player)
+ {
+ if (ChestOpen == null)
+ return false;
+
+ var args = new ChestOpenEventArgs
+ {
+ Data = data,
+ X = x,
+ Y = y,
+ Player = player,
+ };
+ ChestOpen.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a PlaceChest event
+ ///
+ public class PlaceChestEventArgs : GetDataHandledEventArgs
+ {
+ /// What the packet is doing (see MP packet docs).
+ public int Flag { get; set; }
+ ///
+ /// The X coordinate
+ ///
+ public int TileX { get; set; }
+ ///
+ /// The Y coordinate
+ ///
+ public int TileY { get; set; }
+ }
+ ///
+ /// When a chest is added or removed from the world.
+ ///
+ public static HandlerList PlaceChest = new HandlerList();
+ private static bool OnPlaceChest(TSPlayer player, MemoryStream data, int flag, int tilex, int tiley)
+ {
+ if (PlaceChest == null)
+ return false;
+
+ var args = new PlaceChestEventArgs
+ {
+ Player = player,
+ Data = data,
+ Flag = flag,
+ TileX = tilex,
+ TileY = tiley,
+ };
+ PlaceChest.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a PlayerZone event
+ ///
+ public class PlayerZoneEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria playerID of the player
+ ///
+ public byte PlayerId { get; set; }
+ ///
+ /// 0 = Dungeon, 1 = Corruption,2 =Holy, 3 = Meteor, 4 = Jungle, 5 = Snow, 6 = Crimson, 7 = Water Candle
+ ///
+ public BitsByte Zone1 { get; set; }
+ ///
+ /// 0 = Peace Candle, 1 = Solar Tower, 2 = Vortex Tower, 3 = Nebula Tower, 4 = Stardust Tower, 5 = Desert, 6 = Glowshroom, 7 = Underground Desert
+ ///
+ public BitsByte Zone2 { get; set; }
+ ///
+ /// 0 = Overworld, 1 = Dirt Layer, 2 = Rock Layer, 3 = Underworld, 4 = Beach, 5 = Rain, 6 = Sandstorm
+ ///
+ public BitsByte Zone3 { get; set; }
+ ///
+ /// 0 = Old One's Army
+ ///
+ public BitsByte Zone4 { get; set; }
+ }
+ ///
+ /// PlayerZone - When the player sends it's zone/biome information to the server
+ ///
+ public static HandlerList PlayerZone = new HandlerList();
+ private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, BitsByte zone1, BitsByte zone2, BitsByte zone3, BitsByte zone4)
+ {
+ if (PlayerZone == null)
+ return false;
+
+ var args = new PlayerZoneEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = plr,
+ Zone1 = zone1,
+ Zone2 = zone2,
+ Zone3 = zone3,
+ Zone4 = zone4
+ };
+ PlayerZone.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use with a PlayerAnimation event
+ ///
+ public class PlayerAnimationEventArgs : GetDataHandledEventArgs { }
+ ///
+ /// PlayerAnimation - Called when a player animates
+ ///
+ public static HandlerList PlayerAnimation = new HandlerList();
+ private static bool OnPlayerAnimation(TSPlayer player, MemoryStream data)
+ {
+ if (PlayerAnimation == null)
+ return false;
+
+ var args = new PlayerAnimationEventArgs
+ {
+ Player = player,
+ Data = data,
+ };
+ PlayerAnimation.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a PlayerMana event
+ ///
+ public class PlayerManaEventArgs : GetDataHandledEventArgs
+ {
+ public byte PlayerId { get; set; }
+ public short Current { get; set; }
+ public short Max { get; set; }
+ }
+ ///
+ /// PlayerMana - called at a PlayerMana event
+ ///
+ public static HandlerList PlayerMana = new HandlerList();
+ private static bool OnPlayerMana(TSPlayer player, MemoryStream data, byte _plr, short _cur, short _max)
+ {
+ if (PlayerMana == null)
+ return false;
+
+ var args = new PlayerManaEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = _plr,
+ Current = _cur,
+ Max = _max,
+ };
+ PlayerMana.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a PlayerTeam event
+ ///
+ public class PlayerTeamEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria player ID of the player
+ ///
+ public byte PlayerId { get; set; }
+ ///
+ /// Enable/disable pvp?
+ ///
+ public byte Team { get; set; }
+ }
+ ///
+ /// TogglePvp - called when a player toggles pvp
+ ///
+ public static HandlerList PlayerTeam = new HandlerList();
+ private static bool OnPlayerTeam(TSPlayer player, MemoryStream data, byte _id, byte _team)
+ {
+ if (PlayerTeam == null)
+ return false;
+
+ var args = new PlayerTeamEventArgs
+ {
+ Player = player,
+ Data = data,
+ PlayerId = _id,
+ Team = _team,
+ };
+ PlayerTeam.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a Sign event
+ ///
+ public class SignEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// The Terraria playerID of the player
+ ///
+ public short ID { get; set; }
+ ///
+ /// X location of the sign
+ ///
+ public int X { get; set; }
+ ///
+ /// Y location of the sign
+ ///
+ public int Y { get; set; }
+ }
+ ///
+ /// Sign - Called when a sign is changed
+ ///
+ public static HandlerList Sign = new HandlerList();
+ private static bool OnSignEvent(TSPlayer player, MemoryStream data, short id, int x, int y)
+ {
+ if (Sign == null)
+ return false;
+
+ var args = new SignEventArgs
+ {
+ Player = player,
+ Data = data,
+ ID = id,
+ X = x,
+ Y = y,
+ };
+ Sign.Invoke(null, args);
+ return args.Handled;
+ }
+
+ ///
+ /// For use in a LiquidSet event
+ ///
+ public class LiquidSetEventArgs : GetDataHandledEventArgs
+ {
+ ///
+ /// X location of the tile
+ ///
+ public int TileX { get; set; }
+ ///
+ /// Y location of the tile
+ ///
+ public int TileY { get; set; }
+ ///
+ /// Amount of liquid
+ ///
+ public byte Amount { get; set; }
+ ///
+ /// Type of Liquid: 0=water, 1=lave, 2=honey
+ ///
+ public byte Type { get; set; }
+ }
+ ///
+ /// LiquidSet - When ever a liquid is set
+ ///
+ public static HandlerList LiquidSet = new HandlerList