Merge branch 'general-devel' into region-mysql

This commit is contained in:
Lucas Nicodemus 2020-05-25 00:40:08 -07:00 committed by GitHub
commit 0e711da8cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 645 additions and 97 deletions

View file

@ -9,6 +9,8 @@ assignees: ''
<!-- Please provide the information requested below --> <!-- Please provide the information requested below -->
<!-- STOP! Please set DebugLogs to true in your config file, restart the server, and then produce the problem again. Please attach the new debug log to this report. -->
* TShock version: * TShock version:
* TShock build number (if known): * TShock build number (if known):
@ -31,8 +33,12 @@ PUT SUPER LONG ERROR MESSAGES IN THE TICK MARKS
<!-- If providing screenshots of client, send before and after pressing F8 to show network debug --> <!-- If providing screenshots of client, send before and after pressing F8 to show network debug -->
#### Any log messages from files that end in `.log` or `.txt`? #### Any log messages from files that end in `.log` or `.txt`? What are the last 100 log messages from the server console?
<!-- Please add context. Even if you don't have an error message, please show recent server logs at least. --> <!-- Please add context. Even if you don't have an error message, please show recent server logs at least. -->
<!-- STOP! If you do not provide any logs or screenshots, your issue may be closed or converted to a discussion without warning! -->
#### What plugins and what versions of those plugins are you running? #### What plugins and what versions of those plugins are you running?
If I didn't provide any logs this issue, please close my issue immediately. I'm sorry for the inconvenience.

View file

@ -2,8 +2,35 @@
This is the rolling changelog for TShock for Terraria. Use past tense when adding new entries; sign your name off when you add or change something. This should primarily be things like user changes, not necessarily codebase changes unless it's really relevant or large. This is the rolling changelog for TShock for Terraria. Use past tense when adding new entries; sign your name off when you add or change something. This should primarily be things like user changes, not necessarily codebase changes unless it's really relevant or large.
## Upcoming release ## Upcoming Release
* Fix pet licenses. (@Olink)
* Initial support for Journey mode in SSC worlds. (@Olink)
## TShock 4.4.0 (Pre-release 8)
* Update for OTAPI 2.0.0.36 and Terraria 1.4.0.4. (@hakusaro, @Patrikkk, @DeathCradle) * Update for OTAPI 2.0.0.36 and Terraria 1.4.0.4. (@hakusaro, @Patrikkk, @DeathCradle)
* Fixed /wind command. (@AxeelAnder)
* Fixed NPC debuff issue when attempting to fight bosses resulting in kicks. (@AxeelAnder)
* Fixed players are unable to remove an NPC. Change `byte NPCHomeChangeEventArgs.Homeless` to `HouseholdStatus NPCHomeChangeEventArgs.HouseholdStatus`. (@AxeelAnder)
* Fixed lava, wet, honey, and dry bombs;
and lava, wet, honey, and dry grenades;
and lava, wet, honey, and dry rockets;
and lava, wet, honey, and dry mines. (@Olink)
* Fix Bloody Tear displaying the wrong text when used. (@Olink)
* Fix the visibility toggle for the last two accessory slots. (@Olink)
* Adding Journey mode user account permissions. Journey mode must be enabled for these to have any effect. (@Patrikkk)
* `tshock.journey.time.freeze`
* `tshock.journey.time.set`
* `tshock.journey.time.setspeed`
* `tshock.journey.godmode`
* `tshock.journey.wind.strength`
* `tshock.journey.wind.freeze`
* `tshock.journey.rain.strength`
* `tshock.journey.rain.freeze`
* `tshock.journey.placementrange`
* `tshock.journey.setdifficulty`
* `tshock.journey.biomespreadfreeze`
* `tshock.journey.setspawnrate`
* Changed default thresholds for some changes in the config file to accommodate new items & changes to Terraria. (@hakusaro)
## TShock 4.4.0 (Pre-release 7 (Entangled)) ## TShock 4.4.0 (Pre-release 7 (Entangled))
* Fixed bed spawn issues when trying to remove spawn point in SSC. (@Olink) * Fixed bed spawn issues when trying to remove spawn point in SSC. (@Olink)

View file

@ -19,18 +19,17 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Terraria.ID; using Terraria.ID;
using TShockAPI.DB;
using TShockAPI.Net; using TShockAPI.Net;
using Terraria; using Terraria;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using OTAPI.Tile; using OTAPI.Tile;
using TShockAPI.Localization; using TShockAPI.Localization;
using static TShockAPI.GetDataHandlers; using static TShockAPI.GetDataHandlers;
using TerrariaApi.Server;
using Terraria.ObjectData; using Terraria.ObjectData;
using Terraria.DataStructures; using Terraria.DataStructures;
using Terraria.Localization; using Terraria.Localization;
using TShockAPI.Models.PlayerUpdate; using TShockAPI.Models.PlayerUpdate;
using System.Threading.Tasks;
namespace TShockAPI namespace TShockAPI
{ {
@ -528,7 +527,7 @@ namespace TShockAPI
if (args.Player.HasPermission(Permissions.allowclientsideworldedit)) if (args.Player.HasPermission(Permissions.allowclientsideworldedit))
{ {
TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected clientside world edit from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / SendTileSquare accepted clientside world edit from {0}", args.Player.Name);
args.Handled = false; args.Handled = false;
return; return;
} }
@ -711,7 +710,7 @@ namespace TShockAPI
args.Player.SendTileSquare(tileX, tileY, size); args.Player.SendTileSquare(tileX, tileY, size);
} }
TShock.Log.ConsoleDebug("Bouncer / SendTileSquare rejected from spaghetti from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / SendTileSquare reimplemented from spaghetti from {0}", args.Player.Name);
args.Handled = true; args.Handled = true;
} }
@ -865,7 +864,6 @@ namespace TShockAPI
return; return;
} }
if (stabProjectile.ContainsKey(type)) if (stabProjectile.ContainsKey(type))
{ {
if (stabProjectile[type] == args.Player.TPlayer.HeldItem.type) if (stabProjectile[type] == args.Player.TPlayer.HeldItem.type)
@ -875,7 +873,6 @@ namespace TShockAPI
} }
} }
// Main.projHostile contains projectiles that can harm players // Main.projHostile contains projectiles that can harm players
// without PvP enabled and belong to enemy mobs, so they shouldn't be // without PvP enabled and belong to enemy mobs, so they shouldn't be
// possible for players to create. (Source: Ijwu, QuiCM) // possible for players to create. (Source: Ijwu, QuiCM)
@ -888,6 +885,8 @@ namespace TShockAPI
} }
// Tombstones should never be permitted by players // Tombstones should never be permitted by players
// This check means like, invalid or hacked tombstones (sent from hacked clients)
// Death does not create a tombstone projectile by default
if (type == ProjectileID.Tombstone) if (type == ProjectileID.Tombstone)
{ {
TShock.Log.ConsoleDebug("Bouncer / OnNewProjectile rejected from tombstones from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / OnNewProjectile rejected from tombstones from {0}", args.Player.Name);
@ -1290,6 +1289,24 @@ namespace TShockAPI
args.Player.TileLiquidThreshold++; args.Player.TileLiquidThreshold++;
} }
bool wasThereABombNearby = false;
lock (args.Player.RecentlyCreatedProjectiles)
{
IEnumerable<int> projectileTypesThatPerformThisOperation;
if (amount > 0) //handle the projectiles that create fluid.
{
projectileTypesThatPerformThisOperation = projectileCreatesLiquid.Where(k => k.Value == type).Select(k => k.Key);
}
else //handle the scenario where we are removing liquid
{
projectileTypesThatPerformThisOperation = projectileCreatesLiquid.Where(k => k.Value == LiquidType.Removal).Select(k => k.Key);
}
var recentBombs = args.Player.RecentlyCreatedProjectiles.Where(p => projectileTypesThatPerformThisOperation.Contains(Main.projectile[p.Index].type));
wasThereABombNearby = recentBombs.Any(r => Math.Abs(args.TileX - (Main.projectile[r.Index].position.X / 16.0f)) < TShock.Config.BombExplosionRadius
&& Math.Abs(args.TileY - (Main.projectile[r.Index].position.Y / 16.0f)) < TShock.Config.BombExplosionRadius);
}
// Liquid anti-cheat // Liquid anti-cheat
// Arguably the banned buckets bit should be in the item bans system // Arguably the banned buckets bit should be in the item bans system
if (amount != 0) if (amount != 0)
@ -1326,7 +1343,7 @@ namespace TShockAPI
bucket = 6; bucket = 6;
} }
if (type == LiquidType.Lava && !(bucket == 2 || bucket == 0 || bucket == 5 || bucket == 6)) if (!wasThereABombNearby && type == LiquidType.Lava && !(bucket == 2 || bucket == 0 || bucket == 5 || bucket == 6))
{ {
TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 1 from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 1 from {0}", args.Player.Name);
args.Player.SendErrorMessage("You do not have permission to perform this action."); args.Player.SendErrorMessage("You do not have permission to perform this action.");
@ -1336,7 +1353,7 @@ namespace TShockAPI
return; return;
} }
if (type == LiquidType.Lava && TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player)) if (!wasThereABombNearby && type == LiquidType.Lava && TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player))
{ {
TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected lava bucket from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected lava bucket from {0}", args.Player.Name);
args.Player.SendErrorMessage("You do not have permission to perform this action."); args.Player.SendErrorMessage("You do not have permission to perform this action.");
@ -1346,7 +1363,7 @@ namespace TShockAPI
return; return;
} }
if (type == LiquidType.Water && !(bucket == 1 || bucket == 0 || bucket == 4)) if (!wasThereABombNearby && type == LiquidType.Water && !(bucket == 1 || bucket == 0 || bucket == 4))
{ {
TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 2 from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 2 from {0}", args.Player.Name);
args.Player.SendErrorMessage("You do not have permission to perform this action."); args.Player.SendErrorMessage("You do not have permission to perform this action.");
@ -1356,7 +1373,7 @@ namespace TShockAPI
return; return;
} }
if (type == LiquidType.Water && TShock.Itembans.ItemIsBanned("Water Bucket", args.Player)) if (!wasThereABombNearby && type == LiquidType.Water && TShock.Itembans.ItemIsBanned("Water Bucket", args.Player))
{ {
TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 3 from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 3 from {0}", args.Player.Name);
args.Player.SendErrorMessage("You do not have permission to perform this action."); args.Player.SendErrorMessage("You do not have permission to perform this action.");
@ -1366,7 +1383,7 @@ namespace TShockAPI
return; return;
} }
if (type == LiquidType.Honey && !(bucket == 3 || bucket == 0)) if (!wasThereABombNearby && type == LiquidType.Honey && !(bucket == 3 || bucket == 0))
{ {
TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 4 from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 4 from {0}", args.Player.Name);
args.Player.SendErrorMessage("You do not have permission to perform this action."); args.Player.SendErrorMessage("You do not have permission to perform this action.");
@ -1376,7 +1393,7 @@ namespace TShockAPI
return; return;
} }
if (type == LiquidType.Honey && TShock.Itembans.ItemIsBanned("Honey Bucket", args.Player)) if (!wasThereABombNearby && type == LiquidType.Honey && TShock.Itembans.ItemIsBanned("Honey Bucket", args.Player))
{ {
TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 5 from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected bucket check 5 from {0}", args.Player.Name);
args.Player.SendErrorMessage("You do not have permission to perform this action."); args.Player.SendErrorMessage("You do not have permission to perform this action.");
@ -1395,7 +1412,7 @@ namespace TShockAPI
return; return;
} }
if (!args.Player.IsInRange(tileX, tileY, 16)) if (!wasThereABombNearby && !args.Player.IsInRange(tileX, tileY, 16))
{ {
TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected range checks from {0}", args.Player.Name); TShock.Log.ConsoleDebug("Bouncer / OnLiquidSet rejected range checks from {0}", args.Player.Name);
args.Player.SendTileSquare(tileX, tileY, 1); args.Player.SendTileSquare(tileX, tileY, 1);
@ -1547,7 +1564,6 @@ namespace TShockAPI
int id = args.ID; int id = args.ID;
short x = args.X; short x = args.X;
short y = args.Y; short y = args.Y;
byte homeless = args.Homeless;
if (!args.Player.HasBuildPermission(x, y)) if (!args.Player.HasBuildPermission(x, y))
{ {
@ -1558,7 +1574,8 @@ namespace TShockAPI
return; return;
} }
if (!args.Player.IsInRange(x, y)) // When kicking out an npc, x and y in args are 0, we shouldn't check range at this case
if (args.HouseholdStatus != HouseholdStatus.Homeless && !args.Player.IsInRange(x, y))
{ {
args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY,
Convert.ToByte(Main.npc[id].homeless)); Convert.ToByte(Main.npc[id].homeless));
@ -2045,39 +2062,56 @@ namespace TShockAPI
} }
} }
internal void OnSecondUpdate()
{
Task.Run(() =>
{
foreach (var player in TShock.Players)
{
if (player != null && player.TPlayer.whoAmI >= 0)
{
var threshold = DateTime.Now.AddSeconds(-5);
lock (player.RecentlyCreatedProjectiles)
{
player.RecentlyCreatedProjectiles = player.RecentlyCreatedProjectiles.Where(s => s.CreatedAt > threshold).ToList();
}
}
}
});
}
// These time values are references from Projectile.cs, at npc.AddBuff() calls. // These time values are references from Projectile.cs, at npc.AddBuff() calls.
private static Dictionary<int, short> NPCAddBuffTimeMax = new Dictionary<int, short>() private static Dictionary<int, short> NPCAddBuffTimeMax = new Dictionary<int, short>()
{ {
{ BuffID.Poisoned, 3600 }, { BuffID.Poisoned, 3600 }, // BuffID: 20
{ BuffID.OnFire, 1200 }, { BuffID.OnFire, 1200 }, // BuffID: 24
{ BuffID.CursedInferno, 420 }, { BuffID.Confused, short.MaxValue }, // BuffID: 31 Brain of Confusion Internal Item ID: 3223
{ BuffID.Frostburn, 900 }, { BuffID.CursedInferno, 420 }, // BuffID: 39
{ BuffID.Ichor, 1200 }, { BuffID.Frostburn, 900 }, // BuffID: 44
{ BuffID.Venom, 1260 }, { BuffID.Ichor, 1200 }, // BuffID: 69
{ BuffID.Midas, 120 }, { BuffID.Venom, 1800 }, // BuffID: 70
{ BuffID.Wet, 1500 }, { BuffID.Midas, 120 }, // BuffID: 72
{ BuffID.Slimed, 1500 }, { BuffID.Wet, 1500 }, // BuffID: 103
{ BuffID.Lovestruck, 1800 }, { BuffID.Lovestruck, 1800 }, // BuffID: 119
{ BuffID.Stinky, 1800 }, { BuffID.Stinky, 1800 }, // BuffID: 120
{ BuffID.SoulDrain, 30 }, { BuffID.Slimed, 1500 }, // BuffID: 137
{ BuffID.ShadowFlame, 660 }, { BuffID.SoulDrain, 30 }, // BuffID: 151
{ BuffID.DryadsWard, 120 }, { BuffID.ShadowFlame, 660 }, // BuffID: 153
{ BuffID.BoneJavelin, 900 }, { BuffID.DryadsWard, 120 }, // BuffID: 165
{ BuffID.StardustMinionBleed, 900 }, { BuffID.BoneJavelin, 900 }, // BuffID: 169
{ BuffID.DryadsWardDebuff, 120 }, { BuffID.StardustMinionBleed, 900 }, // BuffID: 183
{ BuffID.BetsysCurse, 600 }, { BuffID.DryadsWardDebuff, 120 }, // BuffID: 186
{ BuffID.Oiled, 540 }, { BuffID.Daybreak, 300 }, // BuffID: 189 Solar Eruption Item ID: 3473, Daybreak Item ID: 3543
{ BuffID.Confused, 450 }, // Brain of Confusion Internal Item ID: 3223 { BuffID.BetsysCurse, 600 }, // BuffID: 203
{ BuffID.Daybreak, 300 }, // Solar Eruption Item ID: 3473, Daybreak Item ID: 3543 { BuffID.Oiled, 540 }, // BuffID: 204
{ BuffID.BlandWhipEnemyDebuff, 240 }, { BuffID.BlandWhipEnemyDebuff, 240 }, // BuffID: 307
{ BuffID.SwordWhipNPCDebuff, 240 }, { BuffID.SwordWhipNPCDebuff, 240 }, // BuffID: 309
{ BuffID.ScytheWhipEnemyDebuff, 240 }, { BuffID.ScytheWhipEnemyDebuff, 240 }, // BuffID: 310
{ BuffID.FlameWhipEnemyDebuff, 240 }, { BuffID.FlameWhipEnemyDebuff, 240 }, // BuffID: 313
{ BuffID.ThornWhipNPCDebuff, 240 }, { BuffID.ThornWhipNPCDebuff, 240 }, // BuffID: 315
{ BuffID.RainbowWhipNPCDebuff, 240 }, { BuffID.RainbowWhipNPCDebuff, 240 }, // BuffID: 316
{ BuffID.MaceWhipNPCDebuff, 240 }, { BuffID.MaceWhipNPCDebuff, 240 }, // BuffID: 319
{ BuffID.GelBalloonBuff, 1800 } { BuffID.GelBalloonBuff, 1800 } // BuffID: 320
}; };
/// <summary> /// <summary>

View file

@ -4044,7 +4044,6 @@ namespace TShockAPI
{ {
tsply.SaveServerCharacter(); tsply.SaveServerCharacter();
} }
args.Player.SendSuccessMessage("Save succeeded.");
} }
private static void Settle(CommandArgs args) private static void Settle(CommandArgs args)
@ -4264,7 +4263,7 @@ namespace TShockAPI
} }
Main.windSpeedCurrent = speed; Main.windSpeedCurrent = speed;
Main.windSpeedTarget = 0f; Main.windSpeedTarget = speed;
TSPlayer.All.SendData(PacketTypes.WorldInfo); TSPlayer.All.SendData(PacketTypes.WorldInfo);
TSPlayer.All.SendInfoMessage("{0} changed the wind speed to {1}.", args.Player.Name, speed); TSPlayer.All.SendInfoMessage("{0} changed the wind speed to {1}.", args.Player.Name, speed);
} }

View file

@ -275,11 +275,11 @@ namespace TShockAPI
/// <summary>Disables a player and reverts their actions if this number of tile places is exceeded within 1 second.</summary> /// <summary>Disables a player and reverts their actions if this number of tile places is exceeded within 1 second.</summary>
[Description("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; public int TilePlaceThreshold = 32;
/// <summary>Disables a player if this number of liquid sets is exceeded within 1 second.</summary> /// <summary>Disables a player if this number of liquid sets is exceeded within 1 second.</summary>
[Description("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; public int TileLiquidThreshold = 50;
/// <summary>Disable a player if this number of projectiles is created within 1 second.</summary> /// <summary>Disable a player if this number of projectiles is created within 1 second.</summary>
[Description("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.")]
@ -527,6 +527,9 @@ namespace TShockAPI
[Description("Whether or not the server should output debug level messages related to system operation.")] [Description("Whether or not the server should output debug level messages related to system operation.")]
public bool DebugLogs = false; public bool DebugLogs = false;
[Description("Determines the range in tiles that a bomb can affect tiles from detonation point.")]
public int BombExplosionRadius = 5;
/// <summary> /// <summary>
/// Reads a configuration file from a given path /// Reads a configuration file from a given path
/// </summary> /// </summary>

View file

@ -0,0 +1,138 @@
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Terraria;
using Terraria.ID;
namespace TShockAPI.DB
{
/// <summary>
/// This class is used as the data interface for Journey mode research.
/// This information is maintained such that SSC characters will be properly set up with
/// the world's current research.
/// </summary>
public class ResearchDatastore
{
private IDbConnection database;
/// <summary>
/// In-memory cache of what items have been sacrificed.
/// The first call to GetSacrificedItems will load this with data from the database.
/// </summary>
private Dictionary<int, int> _itemsSacrificed;
/// <summary>
/// Initializes a new instance of the <see cref="TShockAPI.DB.ResearchDatastore"/> class.
/// </summary>
/// <param name="db">A valid connection to the TShock database</param>
public ResearchDatastore(IDbConnection db)
{
database = db;
var table = new SqlTable("Research",
new SqlColumn("WorldId", MySqlDbType.Int32),
new SqlColumn("PlayerId", MySqlDbType.Int32),
new SqlColumn("ItemId", MySqlDbType.Int32),
new SqlColumn("AmountSacrificed", MySqlDbType.Int32),
new SqlColumn("TimeSacrificed", MySqlDbType.DateTime)
);
var creator = new SqlTableCreator(db,
db.GetSqlType() == SqlType.Sqlite
? (IQueryBuilder)new SqliteQueryCreator()
: new MysqlQueryCreator());
try
{
creator.EnsureTableStructure(table);
}
catch (DllNotFoundException)
{
Console.WriteLine("Possible problem with your database - is Sqlite3.dll present?");
throw new Exception("Could not find a database library (probably Sqlite3.dll)");
}
}
/// <summary>
/// This call will return the memory-cached list of items sacrificed.
/// If the cache is not initialized, it will be initialized from the database.
/// </summary>
/// <returns></returns>
public Dictionary<int, int> GetSacrificedItems()
{
if (_itemsSacrificed == null)
{
_itemsSacrificed = ReadFromDatabase();
}
return _itemsSacrificed;
}
/// <summary>
/// This function will return a Dictionary&lt;ItemId, AmountSacrificed&gt; representing
/// what the progress of research on items is for this world.
/// </summary>
/// <returns>A dictionary of ItemID keys and Amount Sacrificed values.</returns>
private Dictionary<int, int> ReadFromDatabase()
{
Dictionary<int, int> sacrificedItems = new Dictionary<int, int>();
var sql = @"select itemId, sum(AmountSacrificed) totalSacrificed
from Research
where WorldId = @0
group by itemId";
try {
using (var reader = database.QueryReader(sql, Main.worldID))
{
while (reader.Read())
{
var itemId = reader.Get<Int32>("itemId");
var amount = reader.Get<Int32>("totalSacrificed");
sacrificedItems[itemId] = amount;
}
}
}
catch (Exception ex)
{
TShock.Log.Error(ex.ToString());
}
return sacrificedItems;
}
/// <summary>
/// This method will sacrifice an amount of an item for research.
/// </summary>
/// <param name="itemId">The net ItemId that is being researched.</param>
/// <param name="amount">The amount of items being sacrificed.</param>
/// <param name="player">The player who sacrificed the item for research.</param>
/// <returns>The cumulative total sacrifices for this item.</returns>
public int SacrificeItem(int itemId, int amount, TSPlayer player)
{
var itemsSacrificed = GetSacrificedItems();
if (!(itemsSacrificed.ContainsKey(itemId)))
itemsSacrificed[itemId] = 0;
var sql = @"insert into Research (WorldId, PlayerId, ItemId, AmountSacrificed, TimeSacrificed) values (@0, @1, @2, @3, @4)";
var result = 0;
try
{
result = database.Query(sql, Main.worldID, player.Account.ID, itemId, amount, DateTime.Now);
}
catch (Exception ex)
{
TShock.Log.Error(ex.ToString());
}
if (result == 1)
{
itemsSacrificed[itemId] += amount;
}
return itemsSacrificed[itemId];
}
}
}

View file

@ -39,6 +39,8 @@ using TShockAPI.Localization;
using TShockAPI.Models; using TShockAPI.Models;
using TShockAPI.Models.PlayerUpdate; using TShockAPI.Models.PlayerUpdate;
using TShockAPI.Models.Projectiles; using TShockAPI.Models.Projectiles;
using Terraria.Net;
using Terraria.GameContent.NetModules;
namespace TShockAPI namespace TShockAPI
{ {
@ -125,7 +127,7 @@ namespace TShockAPI
{ PacketTypes.NpcSpecial, HandleSpecial }, { PacketTypes.NpcSpecial, HandleSpecial },
{ PacketTypes.NpcAddBuff, HandleNPCAddBuff }, { PacketTypes.NpcAddBuff, HandleNPCAddBuff },
{ PacketTypes.PlayerAddBuff, HandlePlayerAddBuff }, { PacketTypes.PlayerAddBuff, HandlePlayerAddBuff },
{ PacketTypes.UpdateNPCHome, UpdateNPCHome }, { PacketTypes.UpdateNPCHome, HandleUpdateNPCHome },
{ PacketTypes.SpawnBossorInvasion, HandleSpawnBoss }, { PacketTypes.SpawnBossorInvasion, HandleSpawnBoss },
{ PacketTypes.PaintTile, HandlePaintTile }, { PacketTypes.PaintTile, HandlePaintTile },
{ PacketTypes.PaintWall, HandlePaintWall }, { PacketTypes.PaintWall, HandlePaintWall },
@ -1141,7 +1143,8 @@ namespace TShockAPI
{ {
Water = 0, Water = 0,
Lava = 1, Lava = 1,
Honey = 2 Honey = 2,
Removal = 255 //@Olink: lets hope they never invent 255 fluids or decide to also use this :(
} }
/// <summary> /// <summary>
@ -1307,6 +1310,13 @@ namespace TShockAPI
return args.Handled; return args.Handled;
} }
public enum HouseholdStatus : byte
{
None = 0,
Homeless = 1,
HasRoom = 2,
}
/// <summary> /// <summary>
/// For use in a NPCHome event /// For use in a NPCHome event
/// </summary> /// </summary>
@ -1325,15 +1335,15 @@ namespace TShockAPI
/// </summary> /// </summary>
public short Y { get; set; } public short Y { get; set; }
/// <summary> /// <summary>
/// ByteBool homeless /// HouseholdStatus of the NPC
/// </summary> /// </summary>
public byte Homeless { get; set; } public HouseholdStatus HouseholdStatus { get; set; }
} }
/// <summary> /// <summary>
/// NPCHome - Called when an NPC's home is changed /// NPCHome - Called when an NPC's home is changed
/// </summary> /// </summary>
public static HandlerList<NPCHomeChangeEventArgs> NPCHome = new HandlerList<NPCHomeChangeEventArgs>(); public static HandlerList<NPCHomeChangeEventArgs> NPCHome = new HandlerList<NPCHomeChangeEventArgs>();
private static bool OnUpdateNPCHome(TSPlayer player, MemoryStream data, short id, short x, short y, byte homeless) private static bool OnUpdateNPCHome(TSPlayer player, MemoryStream data, short id, short x, short y, byte houseHoldStatus)
{ {
if (NPCHome == null) if (NPCHome == null)
return false; return false;
@ -1345,7 +1355,7 @@ namespace TShockAPI
ID = id, ID = id,
X = x, X = x,
Y = y, Y = y,
Homeless = homeless, HouseholdStatus = (HouseholdStatus) houseHoldStatus,
}; };
NPCHome.Invoke(null, args); NPCHome.Invoke(null, args);
return args.Handled; return args.Handled;
@ -1915,11 +1925,14 @@ namespace TShockAPI
args.Player.TPlayer.shirtColor = shirtColor; args.Player.TPlayer.shirtColor = shirtColor;
args.Player.TPlayer.underShirtColor = underShirtColor; args.Player.TPlayer.underShirtColor = underShirtColor;
args.Player.TPlayer.shoeColor = shoeColor; args.Player.TPlayer.shoeColor = shoeColor;
//@Olink: If you need to change bool[10], please make sure you also update the for loops below to account for it.
//There are two arrays from terraria that we only have a single array for. You will need to make sure that you are looking
//at the correct terraria array (hideVisual or hideVisual2).
args.Player.TPlayer.hideVisibleAccessory = new bool[10]; args.Player.TPlayer.hideVisibleAccessory = new bool[10];
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
args.Player.TPlayer.hideVisibleAccessory[i] = hideVisual[i]; args.Player.TPlayer.hideVisibleAccessory[i] = hideVisual[i];
for (int i = 8; i < 10; i++) for (int i = 0; i < 2; i++)
args.Player.TPlayer.hideVisibleAccessory[i] = hideVisual2[i]; args.Player.TPlayer.hideVisibleAccessory[i+8] = hideVisual2[i];
args.Player.TPlayer.hideMisc = hideMisc; args.Player.TPlayer.hideMisc = hideMisc;
args.Player.TPlayer.extraAccessory = extraSlot; args.Player.TPlayer.extraAccessory = extraSlot;
NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, args.Player.Index, NetworkText.FromLiteral(args.Player.Name), args.Player.Index); NetMessage.SendData((int)PacketTypes.PlayerInfo, -1, args.Player.Index, NetworkText.FromLiteral(args.Player.Name), args.Player.Index);
@ -2322,6 +2335,17 @@ namespace TShockAPI
if (OnNewProjectile(args.Data, ident, pos, vel, knockback, dmg, owner, type, index, args.Player)) if (OnNewProjectile(args.Data, ident, pos, vel, knockback, dmg, owner, type, index, args.Player))
return true; return true;
lock (args.Player.RecentlyCreatedProjectiles)
{
if (!args.Player.RecentlyCreatedProjectiles.Any(p => p.Index == index))
{
args.Player.RecentlyCreatedProjectiles.Add(new GetDataHandlers.ProjectileStruct()
{
Index = index,
CreatedAt = DateTime.Now
});
}
}
return false; return false;
} }
@ -2386,6 +2410,10 @@ namespace TShockAPI
} }
args.Player.LastKilledProjectile = type; args.Player.LastKilledProjectile = type;
lock (args.Player.RecentlyCreatedProjectiles)
{
args.Player.RecentlyCreatedProjectiles.ForEach(s => { if (s.Index == index) { s.Killed = true; } });
}
return false; return false;
} }
@ -2763,14 +2791,14 @@ namespace TShockAPI
return true; return true;
} }
private static bool UpdateNPCHome(GetDataHandlerArgs args) private static bool HandleUpdateNPCHome(GetDataHandlerArgs args)
{ {
var id = args.Data.ReadInt16(); var id = args.Data.ReadInt16();
var x = args.Data.ReadInt16(); var x = args.Data.ReadInt16();
var y = args.Data.ReadInt16(); var y = args.Data.ReadInt16();
var homeless = args.Data.ReadInt8(); var householdStatus = args.Data.ReadInt8();
if (OnUpdateNPCHome(args.Player, args.Data, id, x, y, homeless)) if (OnUpdateNPCHome(args.Player, args.Data, id, x, y, householdStatus))
return true; return true;
if (!args.Player.HasPermission(Permissions.movenpc)) if (!args.Player.HasPermission(Permissions.movenpc))
@ -2827,38 +2855,53 @@ namespace TShockAPI
string thing; string thing;
switch (thingType) switch (thingType)
{ {
case -14:
thing = "has sent a request to the bunny delivery service";
break;
case -13:
thing = "has sent a request to the dog delivery service";
break;
case -12:
thing = "has sent a request to the cat delivery service";
break;
case -11:
thing = "applied advanced combat techniques";
break;
case -10:
thing = "summoned a Blood Moon";
break;
case -8: case -8:
thing = "a Moon Lord"; thing = "summoned a Moon Lord";
break; break;
case -7: case -7:
thing = "a Martian invasion"; thing = "summoned a Martian invasion";
break; break;
case -6: case -6:
thing = "an eclipse"; thing = "summoned an eclipse";
break; break;
case -5: case -5:
thing = "a frost moon"; thing = "summoned a frost moon";
break; break;
case -4: case -4:
thing = "a pumpkin moon"; thing = "summoned a pumpkin moon";
break; break;
case -3: case -3:
thing = "the Pirates"; thing = "summoned the Pirates";
break; break;
case -2: case -2:
thing = "the Snow Legion"; thing = "summoned the Snow Legion";
break; break;
case -1: case -1:
thing = "a Goblin Invasion"; thing = "summoned a Goblin Invasion";
break; break;
default: default:
thing = String.Format("the {0}", npc.FullName); thing = String.Format("summoned the {0}", npc.FullName);
break; break;
} }
if (TShock.Config.AnonymousBossInvasions) if (TShock.Config.AnonymousBossInvasions)
TShock.Utils.SendLogs(string.Format("{0} summoned {1}!", args.Player.Name, thing), Color.PaleVioletRed, args.Player); TShock.Utils.SendLogs(string.Format("{0} {1}!", args.Player.Name, thing), Color.PaleVioletRed, args.Player);
else else
TShock.Utils.Broadcast(String.Format("{0} summoned {1}!", args.Player.Name, thing), 175, 75, 255); TShock.Utils.Broadcast(String.Format("{0} {1}!", args.Player.Name, thing), 175, 75, 255);
return false; return false;
} }
@ -3084,6 +3127,144 @@ namespace TShockAPI
private static bool HandleLoadNetModule(GetDataHandlerArgs args) private static bool HandleLoadNetModule(GetDataHandlerArgs args)
{ {
short moduleId = args.Data.ReadInt16();
if (moduleId == (int)NetModulesTypes.CreativePowers)
{
CreativePowerTypes powerId = (CreativePowerTypes)args.Data.ReadInt16();
switch (powerId)
{
case CreativePowerTypes.FreezeTime:
{
if (!args.Player.HasPermission(Permissions.journey_timefreeze))
{
args.Player.SendErrorMessage("You don't have permission to freeze the time of the server!");
return true;
}
break;
}
case CreativePowerTypes.SetDawn:
case CreativePowerTypes.SetNoon:
case CreativePowerTypes.SetDusk:
case CreativePowerTypes.SetMidnight:
{
if (!args.Player.HasPermission(Permissions.journey_timeset))
{
args.Player.SendErrorMessage("You don't have permission to modify the time of the server!");
return true;
}
break;
}
case CreativePowerTypes.Godmode:
{
if (!args.Player.HasPermission(Permissions.journey_godmode))
{
args.Player.SendErrorMessage("You don't have permission to toggle godmode!");
return true;
}
break;
}
case CreativePowerTypes.WindStrength:
{
if (!args.Player.HasPermission(Permissions.journey_windstrength))
{
args.Player.SendErrorMessage("You don't have permission to modify the wind strength of the server!");
return true;
}
break;
}
case CreativePowerTypes.RainStrength:
{
if (!args.Player.HasPermission(Permissions.journey_rainstrength))
{
args.Player.SendErrorMessage("You don't have permission to modify the rain strength of the server!");
return true;
}
break;
}
case CreativePowerTypes.TimeSpeed:
{
if (!args.Player.HasPermission(Permissions.journey_timespeed))
{
args.Player.SendErrorMessage("You don't have permission to modify the time speed of the server!");
return true;
}
break;
}
case CreativePowerTypes.RainFreeze:
{
if (!args.Player.HasPermission(Permissions.journey_rainfreeze))
{
args.Player.SendErrorMessage("You don't have permission to freeze the rain strength of the server!");
return true;
}
break;
}
case CreativePowerTypes.WindFreeze:
{
if (!args.Player.HasPermission(Permissions.journey_windfreeze))
{
args.Player.SendErrorMessage("You don't have permission to freeze the wind strength of the server!");
return true;
}
break;
}
case CreativePowerTypes.IncreasePlacementRange:
{
if (!args.Player.HasPermission(Permissions.journey_placementrange))
{
args.Player.SendErrorMessage("You don't have permission to modify the tile placement range of your character!");
return true;
}
break;
}
case CreativePowerTypes.WorldDifficulty:
{
if (!args.Player.HasPermission(Permissions.journey_setdifficulty))
{
args.Player.SendErrorMessage("You don't have permission to modify the world dificulty of the server!");
return true;
}
break;
}
case CreativePowerTypes.BiomeSpreadFreeze:
{
if (!args.Player.HasPermission(Permissions.journey_biomespreadfreeze))
{
args.Player.SendErrorMessage("You don't have permission to freeze the biome spread of server!");
return true;
}
break;
}
case CreativePowerTypes.SetSpawnRate:
{
if (!args.Player.HasPermission(Permissions.journey_setspawnrate))
{
args.Player.SendErrorMessage("You don't have permission to modify the NPC spawn rate of server!");
return true;
}
break;
}
default:
{
return true;
}
}
} else if (moduleId == (int)NetModulesTypes.CreativeUnlocksPlayerReport)
{
var unknownField = args.Data.ReadByte();
if (unknownField == 0) //this is required or something???
{
var itemId = args.Data.ReadUInt16();
var amount = args.Data.ReadUInt16();
var totalSacrificed = TShock.ResearchDatastore.SacrificeItem(itemId, amount, args.Player);
var response = NetCreativeUnlocksModule.SerializeItemSacrifice(itemId, totalSacrificed);
NetManager.Instance.Broadcast(response);
}
}
// As of 1.4.x.x, this is now used for more things: // As of 1.4.x.x, this is now used for more things:
// NetCreativePowersModule // NetCreativePowersModule
// NetCreativePowerPermissionsModule // NetCreativePowerPermissionsModule
@ -3446,6 +3627,30 @@ namespace TShockAPI
{ ProjectileID.MysticSnakeCoil, TileID.MysticSnakeRope } { ProjectileID.MysticSnakeCoil, TileID.MysticSnakeRope }
}; };
internal static Dictionary<int, LiquidType> projectileCreatesLiquid = new Dictionary<int, LiquidType>
{
{ProjectileID.LavaBomb, LiquidType.Lava},
{ProjectileID.LavaRocket, LiquidType.Lava },
{ProjectileID.LavaGrenade, LiquidType.Lava },
{ProjectileID.LavaMine, LiquidType.Lava },
//{ProjectileID.LavaSnowmanRocket, LiquidType.Lava }, //these require additional checks.
{ProjectileID.WetBomb, LiquidType.Water},
{ProjectileID.WetRocket, LiquidType.Water },
{ProjectileID.WetGrenade, LiquidType.Water},
{ProjectileID.WetMine, LiquidType.Water},
//{ProjectileID.WetSnowmanRocket, LiquidType.Water}, //these require additional checks.
{ProjectileID.HoneyBomb, LiquidType.Honey},
{ProjectileID.HoneyRocket, LiquidType.Honey },
{ProjectileID.HoneyGrenade, LiquidType.Honey },
{ProjectileID.HoneyMine, LiquidType.Honey },
//{ProjectileID.HoneySnowmanRocket, LiquidType.Honey }, //these require additional checks.
{ProjectileID.DryBomb, LiquidType.Removal },
{ProjectileID.DryRocket, LiquidType.Removal },
{ProjectileID.DryGrenade, LiquidType.Removal },
{ProjectileID.DryMine, LiquidType.Removal },
//{ProjectileID.DrySnowmanRocket, LiquidType.Removal } //these require additional checks.
};
internal static Dictionary<int, int> ropeCoilPlacements = new Dictionary<int, int> internal static Dictionary<int, int> ropeCoilPlacements = new Dictionary<int, int>
{ {
{ItemID.RopeCoil, TileID.Rope}, {ItemID.RopeCoil, TileID.Rope},
@ -3461,5 +3666,46 @@ namespace TShockAPI
{ {
{TileID.MinecartTrack, 3} {TileID.MinecartTrack, 3}
}; };
internal struct ProjectileStruct
{
public int Index { get; set; }
public DateTime CreatedAt { get; set; }
public bool Killed { get; internal set; }
}
public enum NetModulesTypes
{
Liquid,
Text,
Ping,
Ambience,
Bestiary,
CreativeUnlocks,
CreativePowers,
CreativeUnlocksPlayerReport,
TeleportPylon,
Particles,
CreativePowerPermissions
}
public enum CreativePowerTypes
{
FreezeTime,
SetDawn,
SetNoon,
SetDusk,
SetMidnight,
Godmode,
WindStrength,
RainStrength,
TimeSpeed,
RainFreeze,
WindFreeze,
IncreasePlacementRange,
WorldDifficulty,
BiomeSpreadFreeze,
SetSpawnRate
}
} }
} }

View file

@ -31,8 +31,7 @@ namespace TShockAPI
/// <summary>Contains the permission nodes used in TShock.</summary> /// <summary>Contains the permission nodes used in TShock.</summary>
public static class Permissions public static class Permissions
{ {
// tshock.account nodes #region tshock.account nodes
[Description("User can register account in game.")] [Description("User can register account in game.")]
public static readonly string canregister = "tshock.account.register"; public static readonly string canregister = "tshock.account.register";
@ -44,9 +43,9 @@ namespace TShockAPI
[Description("User can change password in game.")] [Description("User can change password in game.")]
public static readonly string canchangepassword = "tshock.account.changepassword"; public static readonly string canchangepassword = "tshock.account.changepassword";
#endregion
// tshock.admin nodes #region tshock.admin nodes
[Description("User can set build protection status.")] [Description("User can set build protection status.")]
public static readonly string antibuild = "tshock.admin.antibuild"; public static readonly string antibuild = "tshock.admin.antibuild";
@ -106,17 +105,17 @@ namespace TShockAPI
[Description("User can get other users' info.")] [Description("User can get other users' info.")]
public static readonly string userinfo = "tshock.admin.userinfo"; public static readonly string userinfo = "tshock.admin.userinfo";
#endregion
// tshock.buff nodes #region tshock.buff nodes
[Description("User can buff self.")] [Description("User can buff self.")]
public static readonly string buff = "tshock.buff.self"; public static readonly string buff = "tshock.buff.self";
[Description("User can buff other players.")] [Description("User can buff other players.")]
public static readonly string buffplayer = "tshock.buff.others"; public static readonly string buffplayer = "tshock.buff.others";
#endregion
// tshock.cfg nodes #region tshock.cfg nodes
[Description("User is notified when an update is available, user can turn off / restart the server.")] [Description("User is notified when an update is available, user can turn off / restart the server.")]
public static readonly string maintenance = "tshock.cfg.maintenance"; public static readonly string maintenance = "tshock.cfg.maintenance";
@ -131,9 +130,9 @@ namespace TShockAPI
[Description("User can create reference files of Terraria IDs and the permission matrix in the server folder.")] [Description("User can create reference files of Terraria IDs and the permission matrix in the server folder.")]
public static readonly string createdumps = "tshock.cfg.createdumps"; public static readonly string createdumps = "tshock.cfg.createdumps";
#endregion
// tshock.ignore nodes #region tshock.ignore nodes
[Description("Prevents you from being reverted by kill tile abuse detection.")] [Description("Prevents you from being reverted by kill tile abuse detection.")]
public static readonly string ignorekilltiledetection = "tshock.ignore.removetile"; public static readonly string ignorekilltiledetection = "tshock.ignore.removetile";
@ -169,9 +168,9 @@ namespace TShockAPI
[Description("Prevents you from being disabled by abnormal MP.")] [Description("Prevents you from being disabled by abnormal MP.")]
public static readonly string ignoremp = "tshock.ignore.mp"; public static readonly string ignoremp = "tshock.ignore.mp";
#endregion
// tshock.item nodes #region tshock.item nodes
[Description("User can give items.")] [Description("User can give items.")]
public static readonly string give = "tshock.item.give"; public static readonly string give = "tshock.item.give";
@ -180,9 +179,9 @@ namespace TShockAPI
[Description("Allows you to use banned items.")] [Description("Allows you to use banned items.")]
public static readonly string usebanneditem = "tshock.item.usebanned"; public static readonly string usebanneditem = "tshock.item.usebanned";
#endregion
// tshock.npc nodes #region tshock.npc nodes
[Description("User can edit the max spawns.")] [Description("User can edit the max spawns.")]
public static readonly string maxspawns = "tshock.npc.maxspawns"; public static readonly string maxspawns = "tshock.npc.maxspawns";
@ -227,9 +226,9 @@ namespace TShockAPI
[Description("Allows a user to elevate to superadmin for 10 minutes.")] [Description("Allows a user to elevate to superadmin for 10 minutes.")]
public static readonly string su = "tshock.su"; public static readonly string su = "tshock.su";
#endregion
// tshock.tp nodes #region tshock.tp nodes
[Description("User can teleport *everyone* to them.")] [Description("User can teleport *everyone* to them.")]
public static readonly string tpallothers = "tshock.tp.allothers"; public static readonly string tpallothers = "tshock.tp.allothers";
@ -268,9 +267,9 @@ namespace TShockAPI
[Description("User can use wormhole potions.")] [Description("User can use wormhole potions.")]
public static readonly string wormhole = "tshock.tp.wormhole"; public static readonly string wormhole = "tshock.tp.wormhole";
#endregion
// tshock.world nodes #region tshock.world nodes
[Description("User can use the 'worldevent' command")] [Description("User can use the 'worldevent' command")]
public static readonly string manageevents = "tshock.world.events"; public static readonly string manageevents = "tshock.world.events";
@ -372,9 +371,47 @@ namespace TShockAPI
[Description("Player can toggle party event.")] [Description("Player can toggle party event.")]
public static readonly string toggleparty = "tshock.world.toggleparty"; public static readonly string toggleparty = "tshock.world.toggleparty";
#endregion
// Non-grouped #region tshock.journey nodes
[Description("User can use Creative UI freeze time.")]
public static readonly string journey_timefreeze = "tshock.journey.time.freeze";
[Description("User can use Creative UI to set world time.")]
public static readonly string journey_timeset = "tshock.journey.time.set";
[Description("User can use Creative UI to set world time speed.")]
public static readonly string journey_timespeed = "tshock.journey.time.setspeed";
[Description("User can use Creative UI to to toggle character godmode.")]
public static readonly string journey_godmode = "tshock.journey.godmode";
[Description("User can use Creative UI to set world wind strength/seed.")]
public static readonly string journey_windstrength = "tshock.journey.wind.strength";
[Description("User can use Creative UI to stop the world wind strength from changing.")]
public static readonly string journey_windfreeze = "tshock.journey.wind.freeze";
[Description("User can use Creative UI to set world rain strength/seed.")]
public static readonly string journey_rainstrength = "tshock.journey.rain.strength";
[Description("User can use Creative UI to stop the world rain strength from changing.")]
public static readonly string journey_rainfreeze = "tshock.journey.rain.freeze";
[Description("User can use Creative UI to toggle increased placement range.")]
public static readonly string journey_placementrange = "tshock.journey.placementrange";
[Description("User can use Creative UI to set world difficulty/mode.")]
public static readonly string journey_setdifficulty = "tshock.journey.setdifficulty";
[Description("User can use Creative UI to stop the biome spread of the world.")]
public static readonly string journey_biomespreadfreeze = "tshock.journey.biomespreadfreeze";
[Description("User can use Creative UI to set the NPC spawn rate of the world.")]
public static readonly string journey_setspawnrate = "tshock.journey.setspawnrate";
#endregion
#region Non-grouped
[Description("User can clear items or projectiles.")] [Description("User can clear items or projectiles.")]
public static readonly string clear = "tshock.clear"; public static readonly string clear = "tshock.clear";
@ -428,7 +465,7 @@ namespace TShockAPI
[Description("Player can see advanced information about any user account.")] [Description("Player can see advanced information about any user account.")]
public static readonly string advaccountinfo = "tshock.accountinfo.details"; public static readonly string advaccountinfo = "tshock.accountinfo.details";
#endregion
/// <summary> /// <summary>
/// Lists all commands associated with a given permission /// Lists all commands associated with a given permission
/// </summary> /// </summary>

View file

@ -20,6 +20,9 @@ using Microsoft.Xna.Framework;
using Terraria; using Terraria;
using TShockAPI; using TShockAPI;
using Terraria.Localization; using Terraria.Localization;
using Terraria.GameContent.NetModules;
using Terraria.Net;
using Terraria.ID;
namespace TShockAPI namespace TShockAPI
{ {
@ -480,6 +483,20 @@ namespace TShockAPI
NetMessage.SendData(76, -1, -1, NetworkText.Empty, player.Index); NetMessage.SendData(76, -1, -1, NetworkText.Empty, player.Index);
NetMessage.SendData(39, player.Index, -1, NetworkText.Empty, 400); NetMessage.SendData(39, player.Index, -1, NetworkText.Empty, 400);
var sacrificedItems = TShock.ResearchDatastore.GetSacrificedItems();
for(int i = 0; i < ItemID.Count; i++)
{
var amount = 0;
if (sacrificedItems.ContainsKey(i))
{
amount = sacrificedItems[i];
}
var response = NetCreativeUnlocksModule.SerializeItemSacrifice(i, amount);
NetManager.Instance.SendToClient(response, player.TPlayer.whoAmI);
}
} }
} }
} }

View file

@ -55,7 +55,7 @@ namespace TShockAPI
// These can be caused by an unexpected error such as a bad or out of date plugin // These can be caused by an unexpected error such as a bad or out of date plugin
try try
{ {
TShock.Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red); TShock.Utils.Broadcast("Saving world...", Color.Yellow);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -130,8 +130,11 @@ namespace TShockAPI
} }
else else
WorldFile.SaveWorld(task.resetTime); WorldFile.SaveWorld(task.resetTime);
if (TShock.Config.AnnounceSave)
TShock.Utils.Broadcast("World saved.", Color.Yellow); TShock.Utils.Broadcast("World saved.", Color.Yellow);
TShock.Log.Info(string.Format("World saved at ({0})", Main.worldPathName));
TShock.Log.Info(string.Format("World saved at ({0})", Main.worldPathName));
} }
catch (Exception e) catch (Exception e)
{ {

View file

@ -764,6 +764,12 @@ namespace TShockAPI
/// </summary> /// </summary>
public int LastKilledProjectile = 0; public int LastKilledProjectile = 0;
/// <summary>
/// Keeps track of recently created projectiles by this player. TShock.cs OnSecondUpdate() removes from this in an async task.
/// Projectiles older than 5 seconds are purged from this collection as they are no longer "recent."
/// </summary>
internal List<TShockAPI.GetDataHandlers.ProjectileStruct> RecentlyCreatedProjectiles = new List<TShockAPI.GetDataHandlers.ProjectileStruct>();
/// <summary> /// <summary>
/// The current region this player is in, or null if none. /// The current region this player is in, or null if none.
/// </summary> /// </summary>

View file

@ -57,7 +57,7 @@ namespace TShockAPI
/// <summary>VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.</summary> /// <summary>VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.</summary>
public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version; public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
/// <summary>VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.</summary> /// <summary>VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.</summary>
public static readonly string VersionCodename = "Go to sleep Patrikkk, Icy, Chris, Death, Axeel, Zaicon, hakusaro, and Yoraiz0r <3"; public static readonly string VersionCodename = "Go to sleep Patrikkk, Icy, Chris, Death, Axeel, Zaicon, hakusaro, Zack, and Yoraiz0r <3";
/// <summary>SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).</summary> /// <summary>SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).</summary>
public static string SavePath = "tshock"; public static string SavePath = "tshock";
@ -100,6 +100,8 @@ namespace TShockAPI
public static RememberedPosManager RememberedPos; public static RememberedPosManager RememberedPos;
/// <summary>CharacterDB - Static reference to the SSC character manager.</summary> /// <summary>CharacterDB - Static reference to the SSC character manager.</summary>
public static CharacterManager CharacterDB; public static CharacterManager CharacterDB;
/// <summary>Contains the information about what research has been performed in Journey mode.</summary>
public static ResearchDatastore ResearchDatastore;
/// <summary>Config - Static reference to the config system, for accessing values set in users' config files.</summary> /// <summary>Config - Static reference to the config system, for accessing values set in users' config files.</summary>
public static ConfigFile Config { get; set; } public static ConfigFile Config { get; set; }
/// <summary>ServerSideCharacterConfig - Static reference to the server side character config, for accessing values set by users to modify SSC.</summary> /// <summary>ServerSideCharacterConfig - Static reference to the server side character config, for accessing values set by users to modify SSC.</summary>
@ -324,6 +326,7 @@ namespace TShockAPI
TileBans = new TileManager(DB); TileBans = new TileManager(DB);
RememberedPos = new RememberedPosManager(DB); RememberedPos = new RememberedPosManager(DB);
CharacterDB = new CharacterManager(DB); CharacterDB = new CharacterManager(DB);
ResearchDatastore = new ResearchDatastore(DB);
RestApi = new SecureRest(Netplay.ServerIP, Config.RestApiPort); RestApi = new SecureRest(Netplay.ServerIP, Config.RestApiPort);
RestManager = new RestManager(RestApi); RestManager = new RestManager(RestApi);
RestManager.RegisterRestfulCommands(); RestManager.RegisterRestfulCommands();
@ -1065,6 +1068,8 @@ namespace TShockAPI
} }
} }
} }
Bouncer.OnSecondUpdate();
Utils.SetConsoleTitle(false); Utils.SetConsoleTitle(false);
} }
@ -1620,6 +1625,32 @@ namespace TShockAPI
e.Handled = true; e.Handled = true;
return; return;
} }
} else if (e.MsgId == PacketTypes.ProjectileNew)
{
if (e.number >= 0 && e.number < Main.projectile.Length)
{
var projectile = Main.projectile[e.number];
if (projectile.active && projectile.owner >= 0 && GetDataHandlers.projectileCreatesLiquid.ContainsKey(projectile.type))
{
var player = Players[projectile.owner];
if (player != null)
{
if (player.RecentlyCreatedProjectiles.Any(p => p.Index == e.number && p.Killed))
{
player.RecentlyCreatedProjectiles.RemoveAll(p => p.Index == e.number && p.Killed);
}
if (!player.RecentlyCreatedProjectiles.Any(p => p.Index == e.number)) {
player.RecentlyCreatedProjectiles.Add(new GetDataHandlers.ProjectileStruct()
{
Index = e.number,
CreatedAt = DateTime.Now
});
}
}
}
}
} }
} }

View file

@ -85,6 +85,7 @@
<Compile Include="CLI\FlagSet.cs" /> <Compile Include="CLI\FlagSet.cs" />
<Compile Include="DB\ProjectileManager.cs" /> <Compile Include="DB\ProjectileManager.cs" />
<Compile Include="DB\RegionManager.cs" /> <Compile Include="DB\RegionManager.cs" />
<Compile Include="DB\ResearchDatastore.cs" />
<Compile Include="DB\TileManager.cs" /> <Compile Include="DB\TileManager.cs" />
<Compile Include="Extensions\ExceptionExt.cs" /> <Compile Include="Extensions\ExceptionExt.cs" />
<Compile Include="Hooks\AccountHooks.cs" /> <Compile Include="Hooks\AccountHooks.cs" />
@ -220,4 +221,4 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>