diff --git a/TShockAPI/Bouncer.cs b/TShockAPI/Bouncer.cs index 909a84d68..2dbde82a5 100644 --- a/TShockAPI/Bouncer.cs +++ b/TShockAPI/Bouncer.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011-2019 Pryaxis & TShock Contributors @@ -1312,6 +1312,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA short type = args.Type; int index = args.Index; float[] ai = args.Ai; + int generation = args.Generation; // Clients do send NaN values so we can't just kick them // See https://github.com/Pryaxis/TShock/issues/3076 @@ -1332,7 +1333,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA if (index > Main.maxProjectiles) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from above projectile limit from {0}", args.Player.Name)); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1343,7 +1344,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA // Client will fight the server if we remove pets, silently reject instead if (!Main.projPet[type] && !ProjectileID.Sets.LightPet[type]) - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; @@ -1355,7 +1356,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA // Client will fight the server if we remove pets, silently reject instead if (!Main.projPet[type] && !ProjectileID.Sets.LightPet[type]) - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; @@ -1366,7 +1367,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA args.Player.Disable(GetString("Player does not have permission to create projectile {0}.", type), DisableFlags.WriteToLogAndConsole); TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from permission check from {0} {1}", args.Player.Name, type)); args.Player.SendErrorMessage(GetString("You do not have permission to create that projectile.")); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1375,7 +1376,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA { args.Player.Disable(GetString("Projectile damage is higher than {0}.", TShock.Config.Settings.MaxProjDamage), DisableFlags.WriteToLogAndConsole); TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from projectile damage limit from {0} {1}/{2}", args.Player.Name, damage, TShock.Config.Settings.MaxProjDamage)); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1412,7 +1413,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA if (Main.projHostile[type]) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from hostile projectile from {0}", args.Player.Name)); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1423,7 +1424,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA if (type == ProjectileID.Tombstone) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from tombstones from {0}", args.Player.Name)); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1439,7 +1440,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA if (discreteDirection is < -3 or > 4) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gate from {0} (invalid angle: {1})", args.Player.Name, discreteDirection)); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1449,7 +1450,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA if (boltProjectileData.Type == 0 || boltProjectileData.Killed) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from portal gate from {0} (missing active Portal Gun bolt)", args.Player.Name, discreteDirection)); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1478,7 +1479,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA { TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile please report to tshock about this! normally this is a reject from {0} {1}", args.Player.Name, type)); // args.Player.Disable(String.Format("Does not have projectile permission to update projectile. ({0})", type), DisableFlags.WriteToLogAndConsole); - // args.Player.RemoveProjectile(ident, owner); + // args.Player.RemoveProjectile(ident, owner, generation); } // args.Handled = false; // return; @@ -1493,7 +1494,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA else { args.Player.Disable(GetString("Reached projectile create threshold."), DisableFlags.WriteToLogAndConsole); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); } TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from projectile create threshold from {0} {1}/{2}", args.Player.Name, args.Player.ProjectileThreshold, TShock.Config.Settings.ProjectileThreshold)); @@ -1510,7 +1511,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA ) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from bouncer modified AI from {0}.", args.Player.Name)); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1526,7 +1527,7 @@ internal void OnNewProjectile(object sender, GetDataHandlers.NewProjectileEventA if (TShock.Config.Settings.DisableModifiedZenith && type == ProjectileID.FinalFractal && (ai[0] < -100 || ai[0] > 101) && !Terraria.Graphics.FinalFractalHelper._fractalProfiles.ContainsKey((int)ai[1])) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnNewProjectile rejected from bouncer modified Zenith projectile from {0}.", args.Player.Name)); - args.Player.RemoveProjectile(ident, owner); + args.Player.RemoveProjectile(ident, owner, generation); args.Handled = true; return; } @@ -1632,7 +1633,7 @@ internal void OnProjectileKill(object sender, GetDataHandlers.ProjectileKillEven if (args.Player.IsBeingDisabled()) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnProjectileKill rejected from disabled from {0}", args.Player.Name)); - args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner); + args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner, args.ProjectileGeneration); args.Handled = true; return; } @@ -1640,7 +1641,7 @@ internal void OnProjectileKill(object sender, GetDataHandlers.ProjectileKillEven if (args.Player.IsBouncerThrottled()) { TShock.Log.ConsoleDebug(GetString("Bouncer / OnProjectileKill rejected from bouncer throttle from {0}", args.Player.Name)); - args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner); + args.Player.RemoveProjectile(args.ProjectileIdentity, args.ProjectileOwner, args.ProjectileGeneration); args.Handled = true; return; } @@ -2676,7 +2677,7 @@ internal void OnPlaceItemFrame(object sender, GetDataHandlers.PlaceItemFrameEven if (!args.Player.HasBuildPermission(args.X, args.Y)) { - int num = Item.NewItem(null, (args.X * 16) + 8, (args.Y * 16) + 8, args.Player.TPlayer.width, args.Player.TPlayer.height, args.ItemID, args.Stack, noBroadcast: true, args.Prefix, noGrabDelay: true); + int num = Item.NewItem(null, (args.X * 16) + 8, (args.Y * 16) + 8, args.Player.TPlayer.width, args.Player.TPlayer.height, args.ItemID, args.Stack, noBroadcast: true, args.Prefix, Terraria.NewItemOwnership.None); Main.item[num].playerIndexTheItemIsReservedFor = args.Player.Index; NetMessage.SendData((int)PacketTypes.ItemDrop, args.Player.Index, -1, NetworkText.Empty, num, 1f); NetMessage.SendData((int)PacketTypes.ItemOwner, args.Player.Index, -1, NetworkText.Empty, num); diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs index 0ce138f14..9c1d2a0b2 100644 --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -2537,7 +2537,7 @@ private static void Rain(CommandArgs args) } else { - Main.StartRain(garenteeCoinRain: true); + Main.StartRain(guaranteeCoinRain: true); TSPlayer.All.SendData(PacketTypes.WorldInfo); TSPlayer.All.SendInfoMessage(GetString("{0} caused it to coin rain.", args.Player.Name)); } diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index ef4b0c2e9..f3424e6dc 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -700,6 +700,11 @@ public class NewProjectileEventArgs : GetDataHandledEventArgs /// public int Index { get; set; } + /// + /// Slot-reuse counter from the sender's ProjectileKey. + /// + public int Generation { get; set; } + /// /// The special meaning of the projectile. /// @@ -709,7 +714,7 @@ public class NewProjectileEventArgs : GetDataHandledEventArgs /// NewProjectile - Called when a client creates a new projectile /// 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, float[] ai) + private static bool OnNewProjectile(MemoryStream data, short ident, Vector2 pos, Vector2 vel, float knockback, short dmg, byte owner, short type, int index, TSPlayer player, float[] ai, int generation) { if (NewProjectile == null) return false; @@ -726,7 +731,8 @@ private static bool OnNewProjectile(MemoryStream data, short ident, Vector2 pos, Type = type, Index = index, Player = player, - Ai = ai + Ai = ai, + Generation = generation }; NewProjectile.Invoke(null, args); return args.Handled; @@ -790,6 +796,8 @@ public class ProjectileKillEventArgs : GetDataHandledEventArgs public byte ProjectileOwner; /// The index of the projectile in Main.projectile. public int ProjectileIndex; + /// Slot-reuse counter from the sender's ProjectileKey. + public int ProjectileGeneration; } /// The event fired when a projectile kill packet is received. public static HandlerList ProjectileKill = new HandlerList(); @@ -800,7 +808,7 @@ public class ProjectileKillEventArgs : GetDataHandledEventArgs /// 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) + private static bool OnProjectileKill(TSPlayer player, MemoryStream data, int identity, byte owner, int index, int generation) { if (ProjectileKill == null) return false; @@ -812,6 +820,7 @@ private static bool OnProjectileKill(TSPlayer player, MemoryStream data, int ide ProjectileIdentity = identity, ProjectileOwner = owner, ProjectileIndex = index, + ProjectileGeneration = generation, }; ProjectileKill.Invoke(null, args); @@ -3260,8 +3269,19 @@ private static bool HandleItemDrop(GetDataHandlerArgs args) var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); var stacks = args.Data.ReadInt16(); var prefix = args.Data.ReadInt8(); - var noDelay = args.Data.ReadInt8() == 1; + BitsByte flags = args.Data.ReadInt8(); + var ownership = (byte)((flags[0] ? 1 : 0) | (flags[1] ? 2 : 0)); + var noDelay = ownership <= 1; var type = args.Data.ReadInt16(); + if (flags[2]) + { + args.Data.ReadBoolean(); // shimmered + args.Data.ReadSingle(); // shimmerTime + } + if (flags[3]) + { + args.Data.ReadInt8(); // enemyGrabDelayTime + } if (OnItemDrop(args.Player, args.Data, id, pos, vel, stacks, prefix, noDelay, type)) return true; @@ -3271,6 +3291,7 @@ private static bool HandleItemDrop(GetDataHandlerArgs args) private static bool HandleItemOwner(GetDataHandlerArgs args) { + // vanilla never runs case 22 on a server; kept for old clients var id = args.Data.ReadInt16(); var owner = args.Data.ReadInt8(); @@ -3295,10 +3316,11 @@ private static bool HandleNpcItemStrike(GetDataHandlerArgs args) private static bool HandleProjectileNew(GetDataHandlerArgs args) { - short ident = args.Data.ReadInt16(); + var key = (ProjectileKey)args.Data.ReadInt32(); + byte owner = (byte)key.Spawner; + short ident = (short)key.Index; Vector2 pos = args.Data.ReadVector2(); Vector2 vel = args.Data.ReadVector2(); - byte owner = args.Data.ReadInt8(); short type = args.Data.ReadInt16(); BitsByte bitsByte = (BitsByte)args.Data.ReadByte(); BitsByte bitsByte2 = (BitsByte)(bitsByte[2] ? args.Data.ReadByte() : 0); @@ -3310,11 +3332,21 @@ private static bool HandleProjectileNew(GetDataHandlerArgs args) short dmg = (short)(bitsByte[4] ? args.Data.ReadInt16() : 0); float knockback = bitsByte[5] ? args.Data.ReadSingle() : 0f; short origDmg = (short)(bitsByte[6] ? args.Data.ReadInt16() : 0); - short projUUID = (short)(bitsByte[7] ? args.Data.ReadInt16() : -1); - if (projUUID >= 1000) projUUID = -1; ai[2] = (bitsByte2[0] ? args.Data.ReadSingle() : 0f); - var index = TShock.Utils.SearchProjectile(ident, owner); + if (type < 0 || type >= Main.projHostile.Length || Main.projHostile[type]) + { + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleProjectileNew rejected hostile projectile type {0}", args.Player.Name)); + return true; + } + + if (owner != args.Player.Index) + { + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleProjectileNew rejected key spawner mismatch {0}", args.Player.Name)); + return true; + } + + var index = TShock.Utils.SearchProjectile(ident, owner, key.Generation); // Cattiva's dig ability can bypass build permissions via vanilla exploit in Terraria v1.4.5 // Block ai[0] == 3 (dig state) @@ -3324,7 +3356,7 @@ private static bool HandleProjectileNew(GetDataHandlerArgs args) return true; } - if (OnNewProjectile(args.Data, ident, pos, vel, knockback, dmg, owner, type, index, args.Player, ai)) + if (OnNewProjectile(args.Data, ident, pos, vel, knockback, dmg, owner, type, index, args.Player, ai, key.Generation)) return true; lock (args.Player.RecentlyCreatedProjectiles) @@ -3344,12 +3376,22 @@ private static bool HandleProjectileNew(GetDataHandlerArgs args) private static bool HandleNpcStrike(GetDataHandlerArgs args) { - var id = args.Data.ReadInt16(); + short id = args.Data.ReadInt8(); + var generation = args.Data.ReadInt8(); var dmg = args.Data.ReadInt16(); var knockback = args.Data.ReadSingle(); var direction = (byte)(args.Data.ReadInt8() - 1); var crit = args.Data.ReadInt8(); + if (id >= Main.npc.Length) + return true; + + if (Main.npc[id].generation != generation) + { + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleNpcStrike rejected npc generation mismatch {0}", args.Player.Name)); + return true; + } + if (OnNPCStrike(args.Player, args.Data, id, direction, dmg, knockback, crit)) return true; @@ -3393,12 +3435,33 @@ private static bool HandleNpcStrike(GetDataHandlerArgs args) private static bool HandleProjectileKill(GetDataHandlerArgs args) { - var ident = args.Data.ReadInt16(); - var owner = args.Data.ReadInt8(); - owner = (byte)args.Player.Index; - var index = TShock.Utils.SearchProjectile(ident, owner); + var key = (ProjectileKey)args.Data.ReadInt32(); + var killPos = args.Data.ReadVector2(); + var ident = (short)key.Index; + var owner = (byte)args.Player.Index; + + // TryGet does not bounds check, and Index is wider than keyToIndex + if (key.Index > Main.maxProjectiles) + { + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleProjectileKill rejected out of range projectile index {0}", args.Player.Name)); + return true; + } + + if (!key.TryGet(out var killed) || !killed.active) + { + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleProjectileKill rejected stale projectile key {0}", args.Player.Name)); + return true; + } + + var index = killed.whoAmI; + + if (killed.owner != args.Player.Index) + { + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleProjectileKill rejected owner mismatch {0}", args.Player.Name)); + return true; + } - if (OnProjectileKill(args.Player, args.Data, ident, owner, index)) + if (OnProjectileKill(args.Player, args.Data, ident, owner, index, key.Generation)) { return true; } @@ -4281,7 +4344,12 @@ private static bool HandleHealOther(GetDataHandlerArgs args) private static bool HandleCatchNpc(GetDataHandlerArgs args) { var npcID = args.Data.ReadInt16(); - var who = args.Data.ReadByte(); + + if (npcID < 0 || npcID >= Main.maxNPCs) + { + TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleCatchNpc rejected out of range npc {0}", args.Player.Name)); + return true; + } if (Main.npc[npcID]?.catchItem == 0) { @@ -5193,6 +5261,7 @@ public enum NetModuleType Ping, Ambience, Bestiary, + CreativeUnlocks, CreativePowers, CreativeUnlocksPlayerReport, TeleportPylon, @@ -5200,11 +5269,8 @@ public enum NetModuleType CreativePowerPermissions, Banners, CraftingRequests, - TagEffectState, LeashedEntity, - UnbreakableWallScan, - [Obsolete("Removed in 1.4.5")] - CreativeUnlocks + UnbreakableWallScan } public enum CreativePowerTypes diff --git a/TShockAPI/Net/ProjectileRemoveMsg.cs b/TShockAPI/Net/ProjectileRemoveMsg.cs index ed4d57ffa..21fa8c4fd 100644 --- a/TShockAPI/Net/ProjectileRemoveMsg.cs +++ b/TShockAPI/Net/ProjectileRemoveMsg.cs @@ -30,20 +30,20 @@ public override PacketTypes ID public short Index { get; set; } public byte Owner { get; set; } + /// Slot-reuse counter from the sender's ProjectileKey. + public int Generation { get; set; } public override void Pack(Stream stream) { - stream.WriteInt16(Index); + // ProjectileKey: spawner:8 | index:10 | generation:14 + int key = (Owner & 255) | (Index & 1023) << 8 | (Generation & 16383) << 18; + stream.WriteInt32(key); stream.WriteSingle(-1); stream.WriteSingle(-1); stream.WriteSingle(0); stream.WriteSingle(0); - stream.WriteSingle(0); - stream.WriteInt16(0); - stream.WriteByte(Owner); stream.WriteInt16(0); - stream.WriteSingle(0); - stream.WriteSingle(0); + stream.WriteByte(0); } } } \ No newline at end of file diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index 977d2533b..6d4cdf7ed 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -976,9 +976,9 @@ private object PlayerReadV3(RestRequestArgs args) return ret; TSPlayer player = (TSPlayer)ret; - var inventory = player.TPlayer.inventory.Where(p => p.active).ToList(); - var equipment = player.TPlayer.armor.Where(p => p.active).ToList(); - var dyes = player.TPlayer.dye.Where(p => p.active).ToList(); + var inventory = player.TPlayer.inventory.Where(p => !p.IsAir).ToList(); + var equipment = player.TPlayer.armor.Where(p => !p.IsAir).ToList(); + var dyes = player.TPlayer.dye.Where(p => !p.IsAir).ToList(); return new RestObject() { {"nickname", player.Name}, @@ -1012,12 +1012,12 @@ private object PlayerReadV4(RestRequestArgs args) object items = new { - inventory = player.TPlayer.inventory.Where(i => i.active).Select(item => (NetItem)item), - equipment = player.TPlayer.armor.Where(i => i.active).Select(item => (NetItem)item), - dyes = player.TPlayer.dye.Where(i => i.active).Select(item => (NetItem)item), - piggy = player.TPlayer.bank.item.Where(i => i.active).Select(item => (NetItem)item), - safe = player.TPlayer.bank2.item.Where(i => i.active).Select(item => (NetItem)item), - forge = player.TPlayer.bank3.item.Where(i => i.active).Select(item => (NetItem)item) + inventory = player.TPlayer.inventory.Where(i => !i.IsAir).Select(item => (NetItem)item), + equipment = player.TPlayer.armor.Where(i => !i.IsAir).Select(item => (NetItem)item), + dyes = player.TPlayer.dye.Where(i => !i.IsAir).Select(item => (NetItem)item), + piggy = player.TPlayer.bank.item.Where(i => !i.IsAir).Select(item => (NetItem)item), + safe = player.TPlayer.bank2.item.Where(i => !i.IsAir).Select(item => (NetItem)item), + forge = player.TPlayer.bank3.item.Where(i => !i.IsAir).Select(item => (NetItem)item) }; return new RestObject diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 78969f89b..a3e41d9b4 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -1420,7 +1420,7 @@ public bool InventorySlotAvailable { for (int i = 0; i < 50; i++) //51 is trash can, 52-55 is coins, 56-59 is ammo { - if (TPlayer.inventory[i] == null || !TPlayer.inventory[i].active || TPlayer.inventory[i].Name == "") + if (TPlayer.inventory[i] == null || TPlayer.inventory[i].IsAir || TPlayer.inventory[i].Name == "") { flag = true; break; @@ -1739,13 +1739,31 @@ public void Spawn(int tilex, int tiley, PlayerSpawnContext context, int? respawn /// The projectile's index. /// The projectile's owner. public void RemoveProjectile(int index, int owner) + { + // index is the identity, not the slot + int generation = 0; + int slot = TShock.Utils.SearchProjectile((short)index, owner); + if (slot >= 0 && slot < Main.maxProjectiles) + generation = Main.projectile[slot].key.Generation; + + RemoveProjectile(index, owner, generation); + } + + /// + /// Removes a projectile whose generation is already known, typically taken from the packet. + /// + /// The projectile's identity. + /// The player index of the projectile's owner. + /// Slot-reuse counter from the sender's ProjectileKey. + public void RemoveProjectile(int index, int owner, int generation) { using (var ms = new MemoryStream()) { var msg = new ProjectileRemoveMsg { Index = (short)index, - Owner = (byte)owner + Owner = (byte)owner, + Generation = generation }; msg.PackFull(ms); SendRawData(ms.ToArray()); @@ -1984,7 +2002,7 @@ private Item GiveItemDirectly_FillEmptyInventorySlot(Item item, int slot) private void GiveItemByDrop(int type, int stack, int prefix) { - int itemIndex = Item.NewItem(new EntitySource_DebugCommand(), (int)X, (int)Y, TPlayer.width, TPlayer.height, type, stack, true, prefix, true); + int itemIndex = Item.NewItem(new EntitySource_DebugCommand(), (int)X, (int)Y, TPlayer.width, TPlayer.height, type, stack, true, prefix, Terraria.NewItemOwnership.None); Main.item[itemIndex].playerIndexTheItemIsReservedFor = this.Index; SendData(PacketTypes.ItemDrop, "", itemIndex, 1); SendData(PacketTypes.ItemOwner, null, itemIndex); diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs index 450c66e52..09e29a907 100644 --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011-2019 Pryaxis & TShock Contributors @@ -1066,7 +1066,7 @@ private void OnUpdate(EventArgs args) // even if there are no clients connected if (ServerApi.ForceUpdate) { - Netplay.HasClients = true; + Netplay.HasFullyConnectedClients = true; } if (Backups.IsBackupTime) diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs index 8cc3be19f..26d9144c0 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011-2019 Pryaxis & TShock Contributors @@ -779,14 +779,40 @@ public bool TryParseTime(string str, out ulong seconds) /// projectile ID public int SearchProjectile(short identity, int owner) { - for (int i = 0; i < Main.maxProjectiles; i++) - { - if (Main.projectile[i].identity == identity && Main.projectile[i].owner == owner) - return i; - } + if (identity < 0 || identity > 1000 || owner < 0 || owner > 255) + return 1000; + + int index = Projectile.keyToIndex[owner, identity]; + if (index < 0 || index >= Main.maxProjectiles) + return 1000; + + // keyToIndex is never cleared, so verify the slot still holds this key + var key = Main.projectile[index].key; + if (key.Spawner == owner && key.Index == identity) + return index; + return 1000; } + /// + /// Searches for a projectile by identity, owner and generation. + /// + /// identity + /// owner + /// slot-reuse counter from the sender's ProjectileKey + /// projectile ID + public int SearchProjectile(short identity, int owner, int generation) + { + int index = SearchProjectile(identity, owner); + if (index < 0 || index >= Main.maxProjectiles) + return 1000; + + if (Main.projectile[index].key.Generation != generation) + return 1000; + + return index; + } + /// /// Enumerates boundary points of the given region's rectangle. /// diff --git a/TShockLauncher/TShockLauncher.csproj b/TShockLauncher/TShockLauncher.csproj index ae741c24f..07006bc0c 100644 --- a/TShockLauncher/TShockLauncher.csproj +++ b/TShockLauncher/TShockLauncher.csproj @@ -38,7 +38,7 @@ - + PreserveNewest true