diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f34631c..743141af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,28 @@ This is the rolling changelog for TShock for Terraria. Use past tense when addin * If there is no section called "Upcoming changes" below this line, please add one with `## Upcoming changes` as the first line, and then a bulleted item directly after with the first change. ## Upcoming changes -* This could be you! +* Fixed server crash from `/v2/players/list` & other parameterised REST endpoints. (@QuiCM, reported by @ATFGK) +* Added handling to the PlayerChat hook event. (@QuiCM - Thanks for the suggestion @Arthri) +* Changed the spawnboss command to support silent command specifiers. (@QuiCM, suggested by @nojomyth-dev) + +## TShock 4.5.0.1 +* Fixed conversion from old to new ban system for MySQL hosted ban databases. (@DeathCradle, @ATFGK) +* Fixed wrong identifier used for UUID bans. (@DeathCradle, @ATFGK) +* Fixed conversion from sqlite bans due to locking issue. (@DeathCradle, @Kojirremer) + +## TShock 4.5.0 +* Updated OTAPI and TSAPI to Terraria 1.4.2.1. (@Stealownz, @DeathCradle) +* Updated TShock with preliminary protocol support for Terraria 1.4.2.1. (@Stealownz) ## TShock 4.4.0 (Pre-release 16) * Patched protocol issue. Thanks to Off (@tlworks) and @bartico6 for contributions, including packet captures, packet analysis, exploit proof-of-concept testing, patch testing, and detailed reproduction steps. (@hakusaro) * Disabled debug by default. (@hakusaro) * Changed "WinVer" field in `/serverinfo` to "Operating System". (@Terrabade) +* Rewritten `/grow`, added every default tree type & changed the default help response. (@Nova4334) + * Added a new permission: `tshock.world.growevil` to prevent players to grow evil biome trees, these trees spawn with evil biome blocks below them. +* Introduced `/wallow` to disable or enable recieving whispers from other players. (@Nova4334) +* Removed stoned & webbed from disabled status (@QuiCM) +* Fix -forceupdate flag not forcing updates (@Quake) ## TShock 4.4.0 (Pre-release 15) * Overhauled Bans system. Bans are now based on 'identifiers'. (@QuiCM) diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 8d1217cf..20c21f26 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -585,6 +585,11 @@ namespace TShockAPI { HelpText = "Sends a PM to a player." }); + add(new Command(Permissions.whisper, Wallow, "wallow") + { + AllowServer = false, + HelpText = "Toggles to either ignore or recieve whispers from other players." + }); 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." @@ -2421,6 +2426,8 @@ namespace TShockAPI return; } + string message = "{0} spawned {1} {2} time(s)"; + string spawnName; NPC npc = new NPC(); switch (args.Parameters[0].ToLower()) { @@ -2433,87 +2440,89 @@ namespace TShockAPI npc.SetDefaults(i); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); } - TSPlayer.All.SendSuccessMessage("{0} has spawned all bosses {1} time(s).", args.Player.Name, amount); - return; + spawnName = "all bosses"; + break; + case "brain": case "brain of cthulhu": case "boc": npc.SetDefaults(266); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Brain of Cthulhu {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Brain of Cthulhu"; + break; + case "destroyer": npc.SetDefaults(134); TSPlayer.Server.SetTime(false, 0.0); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Destroyer {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Destroyer"; + break; case "duke": case "duke fishron": case "fishron": npc.SetDefaults(370); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Duke Fishron {1} time(s).", args.Player.Name, amount); - return; + spawnName = "Duke Fishron"; + break; case "eater": case "eater of worlds": case "eow": npc.SetDefaults(13); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Eater of Worlds {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Eater of Worlds"; + break; case "eye": case "eye of cthulhu": case "eoc": npc.SetDefaults(4); TSPlayer.Server.SetTime(false, 0.0); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Eye of Cthulhu {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Eye of Cthulhu"; + break; case "golem": npc.SetDefaults(245); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Golem {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Golem"; + break; case "king": case "king slime": case "ks": npc.SetDefaults(50); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned King Slime {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the King Slime"; + break; case "plantera": npc.SetDefaults(262); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Plantera {1} time(s).", args.Player.Name, amount); - return; + spawnName = "Plantera"; + break; case "prime": case "skeletron prime": npc.SetDefaults(127); TSPlayer.Server.SetTime(false, 0.0); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Skeletron Prime {1} time(s).", args.Player.Name, amount); - return; + spawnName = "Skeletron Prime"; + break; case "queen bee": case "qb": npc.SetDefaults(222); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Queen Bee {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Queen Bee"; + break; case "skeletron": npc.SetDefaults(35); TSPlayer.Server.SetTime(false, 0.0); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Skeletron {1} time(s).", args.Player.Name, amount); - return; + spawnName = "Skeletron"; + break; case "twins": TSPlayer.Server.SetTime(false, 0.0); npc.SetDefaults(125); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); npc.SetDefaults(126); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Twins {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Twins"; + break; case "wof": case "wall of flesh": if (Main.wofNPCIndex != -1) @@ -2527,103 +2536,114 @@ namespace TShockAPI return; } NPC.SpawnWOF(new Vector2(args.Player.X, args.Player.Y)); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Wall of Flesh.", args.Player.Name); - return; + spawnName = "the Wall of Flesh"; + break; case "moon": case "moon lord": case "ml": npc.SetDefaults(398); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Moon Lord {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Moon Lord"; + break; case "empress": case "empress of light": case "eol": npc.SetDefaults(636); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Empress of Light {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Empress of Light"; + break; case "queen slime": case "qs": npc.SetDefaults(657); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Queen Slime {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Queen Slime"; + break; case "lunatic": case "lunatic cultist": case "cultist": case "lc": npc.SetDefaults(439); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Lunatic Cultist {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Lunatic Cultist"; + break; case "betsy": npc.SetDefaults(551); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Betsy {1} time(s).", args.Player.Name, amount); - return; + spawnName = "Betsy"; + break; case "flying dutchman": case "flying": case "dutchman": npc.SetDefaults(491); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Flying Dutchman {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Flying Dutchman"; + break; case "mourning wood": npc.SetDefaults(325); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Mourning Wood {1} time(s).", args.Player.Name, amount); - return; + spawnName = "Mourning Wood"; + break; case "pumpking": npc.SetDefaults(327); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Pumpking {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Pumpking"; + break; case "everscream": npc.SetDefaults(344); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Everscream {1} time(s).", args.Player.Name, amount); - return; + spawnName = "Everscream"; + break; case "santa-nk1": case "santa": npc.SetDefaults(346); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned Santa-NK1 {1} time(s).", args.Player.Name, amount); - return; + spawnName = "Santa-NK1"; + break; case "ice queen": npc.SetDefaults(345); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Ice Queen {1} time(s).", args.Player.Name, amount); - return; + spawnName = "the Ice Queen"; + break; case "martian saucer": npc.SetDefaults(392); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Martian Saucer {1} time(s).", args.Player.Name, amount); - return; + spawnName = "a Martian Saucer"; + break; case "solar pillar": npc.SetDefaults(517); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Solar Pillar {1} time(s).", args.Player.Name, amount); - return; + spawnName = "a Solar Pillar"; + break; case "nebula pillar": npc.SetDefaults(507); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Nebula Pillar {1} time(s).", args.Player.Name, amount); - return; + spawnName = "a Nebula Pillar"; + break; case "vortex pillar": npc.SetDefaults(422); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Vortex Pillar {1} time(s).", args.Player.Name, amount); - return; + spawnName = "a Vortex Pillar"; + break; case "stardust pillar": npc.SetDefaults(493); TSPlayer.Server.SpawnNPC(npc.type, npc.FullName, amount, args.Player.TileX, args.Player.TileY); - TSPlayer.All.SendSuccessMessage("{0} has spawned the Stardust Pillar {1} time(s).", args.Player.Name, amount); - return; + spawnName = "a Stardust Pillar"; + break; default: args.Player.SendErrorMessage("Invalid boss type!"); return; } + + if (args.Silent) + { + //"You spawned time(s)" + args.Player.SendSuccessMessage(message, "You", spawnName, amount); + } + else + { + //" spawned time(s)" + TSPlayer.All.SendSuccessMessage(message, args.Player.Name, spawnName, amount); + } } private static void SpawnMob(CommandArgs args) @@ -5283,14 +5303,15 @@ namespace TShockAPI args.Player.SendFileTextAsMessage(FileTools.RulesPath); } - private static void Whisper(CommandArgs args) + public static bool[] WDisabled { get; set; } = new bool[256]; + + public static void Whisper(CommandArgs args) { if (args.Parameters.Count < 2) { - args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}whisper ", Specifier); + args.Player.SendErrorMessage("Invalid syntax! Proper usage: /whisper "); return; } - var players = TSPlayer.FindByNameOrID(args.Parameters[0]); if (players.Count == 0) { @@ -5308,6 +5329,11 @@ namespace TShockAPI { var plr = players[0]; var msg = string.Join(" ", args.Parameters.ToArray(), 1, args.Parameters.Count - 1); + if (WDisabled[players[0].Index]) + { + args.Player.SendErrorMessage("This player has disabled people from sending whispers!"); + return; + } plr.SendMessage(String.Format(" {1}", args.Player.Name, msg), Color.MediumPurple); args.Player.SendMessage(String.Format(" {1}", plr.Name, msg), Color.MediumPurple); plr.LastWhisper = args.Player; @@ -5315,6 +5341,19 @@ namespace TShockAPI } } + public static void Wallow(CommandArgs args) + { + int index = args.Player.Index; + if (WDisabled[index]) + { + args.Player.SendSuccessMessage("You will now recieve whispers from other players!"); + WDisabled[index] = !WDisabled[index]; + return; + } + WDisabled[index] = !WDisabled[index]; + args.Player.SendSuccessMessage("You will now not recieve whispers from other players, type '/wallow' to recieve them again!"); + } + private static void Reply(CommandArgs args) { if (args.Player.mute) @@ -5410,7 +5449,7 @@ namespace TShockAPI type = 170; } var ply = players[0]; - int p = Projectile.NewProjectile(ply.TPlayer.position.X, ply.TPlayer.position.Y - 64f, 0f, -8f, type, 0, (float)0); + int p = Projectile.NewProjectile(Projectile.GetNoneSource(), ply.TPlayer.position.X, ply.TPlayer.position.Y - 64f, 0f, -8f, type, 0, (float)0); Main.projectile[p].Kill(); args.Player.SendSuccessMessage("Launched Firework on {0}.", ply.Name); } @@ -6010,13 +6049,11 @@ namespace TShockAPI } } - private static void Grow(CommandArgs args) + public static void Grow(CommandArgs args) { - if (args.Parameters.Count != 1) - { - args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}grow ", Specifier); - return; - } + bool growevilAmb = args.Player.HasPermission(Permissions.growevil); + string subcmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower(); + var name = "Fail"; var x = args.Player.TileX; var y = args.Player.TileY + 3; @@ -6027,10 +6064,37 @@ namespace TShockAPI return; } - switch (args.Parameters[0].ToLower()) + switch (subcmd) { - case "tree": - for (int i = x - 1; i < x + 2; i++) + case "help": + { + if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out int pageNumber)) + return; + + var lines = new List + { + "- Default trees :", + " 'basic', 'sakura', 'willow', 'boreal', 'mahogany', 'ebonwood', 'shadewood', 'pearlwood'.", + "- Palm trees :", + " 'palm', 'corruptpalm', 'crimsonpalm', 'hallowpalm'.", + "- Gem trees :", + " 'topaz', 'amethyst', 'sapphire', 'emerald', 'ruby', 'diamond', 'amber'.", + "- Misc :", + " 'cactus', 'herb', 'mushroom'." + }; + + PaginationTools.SendPage(args.Player, pageNumber, lines, + new PaginationTools.Settings + { + HeaderFormat = "Trees types & misc available to use. ({0}/{1}):", + FooterFormat = "Type {0}grow help {{0}} for more sub-commands.".SFormat(Commands.Specifier) + } + ); + } + break; + + case "basic": + for (int i = x - 2; i < x + 3; i++) { Main.tile[i, y].active(true); Main.tile[i, y].type = 2; @@ -6038,37 +6102,279 @@ namespace TShockAPI } Main.tile[x, y - 1].wall = 0; WorldGen.GrowTree(x, y); - name = "Tree"; + name = "Basic Tree"; break; - case "epictree": - for (int i = x - 1; i < x + 2; i++) + + case "boreal": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 147; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowTree(x, y); + name = "Boreal Tree"; + break; + + case "mahogany": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 60; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowTree(x, y); + name = "Rich Mahogany"; + break; + + case "sakura": + for (int i = x - 2; i < x + 3; i++) { Main.tile[i, y].active(true); Main.tile[i, y].type = 2; Main.tile[i, y].wall = 0; } Main.tile[x, y - 1].wall = 0; - Main.tile[x, y - 1].liquid = 0; - Main.tile[x, y - 1].active(true); - WorldGen.GrowEpicTree(x, y); - name = "Epic Tree"; + WorldGen.TryGrowingTreeByType(596, x, y); + name = "Sakura Tree"; break; - case "mushroom": - for (int i = x - 1; i < x + 2; i++) + + case "willow": + for (int i = x - 2; i < x + 3; i++) { Main.tile[i, y].active(true); - Main.tile[i, y].type = 70; + Main.tile[i, y].type = 2; Main.tile[i, y].wall = 0; } Main.tile[x, y - 1].wall = 0; - WorldGen.GrowShroom(x, y); - name = "Mushroom"; + WorldGen.TryGrowingTreeByType(616, x, y); + name = "Willow Tree"; break; + + case "shadewood": + if (growevilAmb) + { + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 199; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowTree(x, y); + name = "Shadewood tree"; + } + else args.Player.SendErrorMessage("You do not have permission to grow this tree type"); + break; + + case "ebonwood": + if (growevilAmb) + { + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 23; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowTree(x, y); + name = "Ebonwood Tree"; + } + else args.Player.SendErrorMessage("You do not have permission to grow this tree type"); + break; + + case "pearlwood": + if (growevilAmb) + { + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 109; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowTree(x, y); + name = "Pearlwood Tree"; + } + else args.Player.SendErrorMessage("You do not have permission to grow this tree type"); + break; + + case "palm": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 53; + Main.tile[i, y].wall = 0; + } + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y + 1].active(true); + Main.tile[i, y + 1].type = 397; + Main.tile[i, y + 1].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowPalmTree(x, y); + name = "Desert Palm"; + break; + + case "hallowpalm": + if (growevilAmb) + { + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 116; + Main.tile[i, y].wall = 0; + } + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y + 1].active(true); + Main.tile[i, y + 1].type = 402; + Main.tile[i, y + 1].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowPalmTree(x, y); + name = "Hallow Palm"; + } + else args.Player.SendErrorMessage("You do not have permission to grow this tree type"); + break; + + case "crimsonpalm": + if (growevilAmb) + { + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 234; + Main.tile[i, y].wall = 0; + } + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y + 1].active(true); + Main.tile[i, y + 1].type = 399; + Main.tile[i, y + 1].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowPalmTree(x, y); + name = "Crimson Palm"; + } + else args.Player.SendErrorMessage("You do not have permission to grow this tree type"); + break; + + case "corruptpalm": + if (growevilAmb) + { + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 112; + Main.tile[i, y].wall = 0; + } + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y + 1].active(true); + Main.tile[i, y + 1].type = 398; + Main.tile[i, y + 1].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowPalmTree(x, y); + name = "Corruption Palm"; + } + else args.Player.SendErrorMessage("You do not have permission to grow this tree type"); + break; + + case "topaz": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 1; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.TryGrowingTreeByType(583, x, y); + name = "Topaz Gemtree"; + break; + + case "amethyst": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 1; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.TryGrowingTreeByType(584, x, y); + name = "Amethust Gemtree"; + break; + + case "sapphire": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 1; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.TryGrowingTreeByType(585, x, y); + name = "Sapphire Gemtree"; + break; + + case "emerald": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 1; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.TryGrowingTreeByType(586, x, y); + name = "Emerald Gemtree"; + break; + + case "ruby": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 1; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.TryGrowingTreeByType(587, x, y); + name = "Ruby Gemtree"; + break; + + case "diamond": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 1; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.TryGrowingTreeByType(588, x, y); + name = "Diamond Gemtree"; + break; + + case "amber": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 1; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.TryGrowingTreeByType(589, x, y); + name = "Amber Gemtree"; + break; + case "cactus": Main.tile[x, y].type = 53; WorldGen.GrowCactus(x, y); name = "Cactus"; break; + case "herb": Main.tile[x, y].active(true); Main.tile[x, y].frameX = 36; @@ -6076,12 +6382,28 @@ namespace TShockAPI WorldGen.GrowAlch(x, y); name = "Herb"; break; + + case "mushroom": + for (int i = x - 2; i < x + 3; i++) + { + Main.tile[i, y].active(true); + Main.tile[i, y].type = 70; + Main.tile[i, y].wall = 0; + } + Main.tile[x, y - 1].wall = 0; + WorldGen.GrowShroom(x, y); + name = "Glowing Mushroom Tree"; + break; + default: args.Player.SendErrorMessage("Unknown plant!"); return; } - args.Player.SendTileSquare(x, y); - args.Player.SendSuccessMessage("Tried to grow a " + name + "."); + if (args.Parameters.Count == 1) + { + args.Player.SendTileSquare(x - 2, y - 20, 25); + args.Player.SendSuccessMessage("Tried to grow a " + name + "."); + } } private static void ToggleGodMode(CommandArgs args) diff --git a/TShockAPI/DB/BanManager.cs b/TShockAPI/DB/BanManager.cs index 0edea2d0..5777e84c 100644 --- a/TShockAPI/DB/BanManager.cs +++ b/TShockAPI/DB/BanManager.cs @@ -37,18 +37,7 @@ namespace TShockAPI.DB /// /// Readonly dictionary of Bans, keyed on ban ticket number. /// - public ReadOnlyDictionary Bans - { - get - { - if (_bans == null) - { - _bans = RetrieveAllBans().ToDictionary(b => b.TicketNumber); - } - - return new ReadOnlyDictionary(_bans); - } - } + public ReadOnlyDictionary Bans => new ReadOnlyDictionary(_bans); /// /// Event invoked when a ban is checked for validity @@ -93,12 +82,24 @@ namespace TShockAPI.DB throw new Exception("Could not find a database library (probably Sqlite3.dll)"); } + EnsureBansCollection(); TryConvertBans(); OnBanValidate += BanValidateCheck; OnBanPreAdd += BanAddedCheck; } + /// + /// Ensures the collection is ready to use. + /// + private void EnsureBansCollection() + { + if (_bans == null) + { + _bans = RetrieveAllBans().ToDictionary(b => b.TicketNumber); + } + } + /// /// Converts bans from the old ban system to the new. /// @@ -107,7 +108,7 @@ namespace TShockAPI.DB int res; if (database.GetSqlType() == SqlType.Mysql) { - res = database.QueryScalar("SELECT COUNT(name) FROM information_schema.tables WHERE table_schema = @0 and table_name = 'Bans'", TShock.Config.Settings.MySqlDbName); + res = database.QueryScalar("SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = @0 and table_name = 'Bans'", TShock.Config.Settings.MySqlDbName); } else { @@ -116,6 +117,7 @@ namespace TShockAPI.DB if (res != 0) { + var bans = new List(); using (var reader = database.QueryReader("SELECT * FROM Bans")) { while (reader.Read()) @@ -140,22 +142,46 @@ namespace TShockAPI.DB if (!string.IsNullOrWhiteSpace(ip)) { - InsertBan($"{Identifier.IP}{ip}", reason, banningUser, start, end); + bans.Add(new BanPreAddEventArgs + { + Identifier = $"{Identifier.IP}{ip}", + Reason = reason, + BanningUser = banningUser, + BanDateTime = start, + ExpirationDateTime = end + }); } if (!string.IsNullOrWhiteSpace(account)) { - InsertBan($"{Identifier.Account}{account}", reason, banningUser, start, end); + bans.Add(new BanPreAddEventArgs + { + Identifier = $"{Identifier.Account}{account}", + Reason = reason, + BanningUser = banningUser, + BanDateTime = start, + ExpirationDateTime = end + }); } if (!string.IsNullOrWhiteSpace(uuid)) { - InsertBan($"{Identifier.UUID}{uuid}", reason, banningUser, start, end); + bans.Add(new BanPreAddEventArgs + { + Identifier = $"{Identifier.UUID}{uuid}", + Reason = reason, + BanningUser = banningUser, + BanDateTime = start, + ExpirationDateTime = end + }); } } } - database.Query("DROP TABLE 'Bans'"); + foreach (var ban in bans) + InsertBan(ban); + + database.Query("DROP TABLE Bans"); } } @@ -232,7 +258,7 @@ namespace TShockAPI.DB args.Message = args.Valid ? null : "a current ban for this identifier already exists."; } } - + /// /// Adds a new ban for the given identifier. Returns a Ban object if the ban was added, else null /// @@ -252,7 +278,16 @@ namespace TShockAPI.DB BanDateTime = fromDate, ExpirationDateTime = toDate }; + return InsertBan(args); + } + /// + /// Adds a new ban for the given data. Returns a Ban object if the ban was added, else null + /// + /// A predefined instance of + /// + public AddBanResult InsertBan(BanPreAddEventArgs args) + { OnBanPreAdd?.Invoke(this, args); if (!args.Valid) @@ -265,21 +300,21 @@ namespace TShockAPI.DB if (database.GetSqlType() == SqlType.Mysql) { - query += "SELECT CAST(LAST_INSERT_ID() as INT);"; + query += "SELECT LAST_INSERT_ID();"; } else { query += "SELECT CAST(last_insert_rowid() as INT);"; } - int ticketId = database.QueryScalar(query, identifier, reason, banningUser, fromDate.Ticks, toDate.Ticks); + int ticketId = database.QueryScalar(query, args.Identifier, args.Reason, args.BanningUser, args.BanDateTime.Ticks, args.ExpirationDateTime.Ticks); if (ticketId == 0) { return new AddBanResult { Message = "Database insert failed." }; } - Ban b = new Ban(ticketId, identifier, reason, banningUser, fromDate, toDate); + Ban b = new Ban(ticketId, args.Identifier, args.Reason, args.BanningUser, args.BanDateTime, args.ExpirationDateTime); _bans.Add(ticketId, b); OnBanPostAdd?.Invoke(this, new BanEventArgs { Ban = b }); diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index ccb16468..67eb764d 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -2704,6 +2704,7 @@ namespace TShockAPI float[] ai = new float[Projectile.maxAI]; for (int i = 0; i < Projectile.maxAI; ++i) ai[i] = !bits.AI[i] ? 0.0f : args.Data.ReadSingle(); + ushort bannerId = bits.HasBannerIdToRespondTo ? args.Data.ReadUInt16() : (ushort)0; short dmg = bits.HasDamage ? args.Data.ReadInt16() : (short)0; float knockback = bits.HasKnockback ? args.Data.ReadSingle() : 0.0f; short origDmg = bits.HasOriginalDamage ? args.Data.ReadInt16() : (short)0; diff --git a/TShockAPI/Hooks/PlayerHooks.cs b/TShockAPI/Hooks/PlayerHooks.cs index 9056fe55..3114020b 100644 --- a/TShockAPI/Hooks/PlayerHooks.cs +++ b/TShockAPI/Hooks/PlayerHooks.cs @@ -449,14 +449,16 @@ namespace TShockAPI.Hooks /// The player firing the event. /// The raw chat text sent by the player. /// The chat text after being formatted. - public static void OnPlayerChat(TSPlayer ply, string rawtext, ref string tshockText) + public static bool OnPlayerChat(TSPlayer ply, string rawtext, ref string tshockText) { if (PlayerChat == null) - return; + return false; var args = new PlayerChatEventArgs {Player = ply, RawText = rawtext, TShockFormattedText = tshockText}; PlayerChat(args); tshockText = args.TShockFormattedText; + + return args.Handled; } /// diff --git a/TShockAPI/Models/Projectiles/NewProjectileData.cs b/TShockAPI/Models/Projectiles/NewProjectileData.cs index 7caa057e..c76dbc65 100644 --- a/TShockAPI/Models/Projectiles/NewProjectileData.cs +++ b/TShockAPI/Models/Projectiles/NewProjectileData.cs @@ -38,6 +38,12 @@ namespace TShockAPI.Models.Projectiles } } + public bool HasBannerIdToRespondTo + { + get => bitsbyte[3]; + set => bitsbyte[3] = value; + } + /// /// Gets or Sets the Damage flag on the backing field /// diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index 600974a1..eb6b6925 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -319,6 +319,9 @@ namespace TShockAPI [Description("User can grow plants.")] public static readonly string grow = "tshock.world.grow"; + [Description("User can grow evil biome plants.")] + public static readonly string growevil = "tshock.world.growevil"; + [Description("User can change hardmode state.")] public static readonly string hardmode = "tshock.world.hardmode"; diff --git a/TShockAPI/Properties/AssemblyInfo.cs b/TShockAPI/Properties/AssemblyInfo.cs index bf337820..1e1ba3db 100644 --- a/TShockAPI/Properties/AssemblyInfo.cs +++ b/TShockAPI/Properties/AssemblyInfo.cs @@ -53,5 +53,5 @@ using System.Runtime.InteropServices; // Also, be sure to release on github with the exact assembly version tag as below // so that the update manager works correctly (via the Github releases api and mimic) -[assembly: AssemblyVersion("4.4.0")] -[assembly: AssemblyFileVersion("4.4.0")] +[assembly: AssemblyVersion("4.5.0.1")] +[assembly: AssemblyFileVersion("4.5.0.1")] diff --git a/TShockAPI/Rest/Rest.cs b/TShockAPI/Rest/Rest.cs index 51d783f5..40a908ef 100644 --- a/TShockAPI/Rest/Rest.cs +++ b/TShockAPI/Rest/Rest.cs @@ -94,17 +94,17 @@ namespace Rests /// /// public IEnumerator GetEnumerator() - { - return GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() { foreach (IParameter param in _collection) { yield return new EscapedParameter(param); } } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } } /// diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 20977c8e..4c24468b 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -1557,8 +1557,6 @@ namespace TShockAPI public virtual void Disable(string reason = "", DisableFlags flags = DisableFlags.WriteToLog) { LastThreat = DateTime.UtcNow; - SetBuff(BuffID.Frozen, 330, true); - SetBuff(BuffID.Stoned, 330, true); SetBuff(BuffID.Webbed, 330, true); if (ActiveChest != -1) @@ -1639,7 +1637,7 @@ namespace TShockAPI if (force) { TShock.Bans.InsertBan($"{Identifier.IP}{IP}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue); - TShock.Bans.InsertBan($"{Identifier.IP}{UUID}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue); + TShock.Bans.InsertBan($"{Identifier.UUID}{UUID}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue); if (Account != null) { TShock.Bans.InsertBan($"{Identifier.Account}{Account.Name}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue); diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 5f774f95..52bef011 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -57,7 +57,7 @@ namespace TShockAPI /// VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info. public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version; /// VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions. - public static readonly string VersionCodename = "Now with less velocity, thanks to Off + Quake. Usual thanks to Chris/White <3"; + public static readonly string VersionCodename = "Stealownz + DeathCradle edition"; /// SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins). public static string SavePath = "tshock"; @@ -890,6 +890,13 @@ namespace TShockAPI /// args - EventArgs args private void OnUpdate(EventArgs args) { + // This forces Terraria to actually continue to update + // even if there are no clients connected + if (ServerApi.ForceUpdate) + { + Netplay.HasClients = true; + } + if (Backups.IsBackupTime) Backups.Backup(); //call these every second, not every update @@ -1323,9 +1330,17 @@ namespace TShockAPI { text = String.Format(Config.Settings.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix, args.Text); - Hooks.PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text); - Utils.Broadcast(text, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B); + + //Invoke the PlayerChat hook. If this hook event is handled then we need to prevent sending the chat message + bool cancelChat = PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text); args.Handled = true; + + if (cancelChat) + { + return; + } + + Utils.Broadcast(text, tsplr.Group.R, tsplr.Group.G, tsplr.Group.B); } else { @@ -1337,7 +1352,13 @@ namespace TShockAPI //Give that poor player their name back :'c ply.name = name; - PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text); + + bool cancelChat = PlayerHooks.OnPlayerChat(tsplr, args.Text, ref text); + if (cancelChat) + { + args.Handled = true; + return; + } //This netpacket is used to send chat text from the server to clients, in this case on behalf of a client Terraria.Net.NetPacket packet = Terraria.GameContent.NetModules.NetTextModule.SerializeServerMessage( diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index cde1f86e..add34f1e 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -70,7 +70,7 @@ ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - + False ..\TerrariaServerAPI\TerrariaServerAPI\bin\$(ConfigurationName)\OTAPI.dll diff --git a/TerrariaServerAPI b/TerrariaServerAPI index 2a0b338a..e0302513 160000 --- a/TerrariaServerAPI +++ b/TerrariaServerAPI @@ -1 +1 @@ -Subproject commit 2a0b338a3c4fb82e05946082ab31def771b783fb +Subproject commit e0302513009220a8d92ba12c867a401e7731d28a