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