Merge branch 'general-devel' into tileconv

This commit is contained in:
Stargazing Koishi 2022-12-10 21:37:52 +00:00 committed by GitHub
commit 104598394b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 36285 additions and 21712 deletions

View file

@ -400,7 +400,7 @@ namespace TShockAPI
};
PlayerAddBuffWhitelist[BuffID.OnFire3] = new BuffLimit
{
MaxTicks = 60 * 5,
MaxTicks = 60 * 6,
CanBeAddedWithoutHostile = false,
CanOnlyBeAppliedToSender = false
};
@ -416,6 +416,24 @@ namespace TShockAPI
CanBeAddedWithoutHostile = false,
CanOnlyBeAppliedToSender = false
};
PlayerAddBuffWhitelist[BuffID.ShadowCandle] = new BuffLimit
{
MaxTicks = 2,
CanBeAddedWithoutHostile = true,
CanOnlyBeAppliedToSender = true
};
PlayerAddBuffWhitelist[BuffID.BrainOfConfusionBuff] = new BuffLimit
{
MaxTicks = 240,
CanBeAddedWithoutHostile = true,
CanOnlyBeAppliedToSender = true
};
PlayerAddBuffWhitelist[BuffID.WindPushed] = new BuffLimit
{
MaxTicks = 2,
CanBeAddedWithoutHostile = true,
CanOnlyBeAppliedToSender = true
};
#endregion Whitelist
}
@ -674,12 +692,10 @@ namespace TShockAPI
}
}
if (action == EditAction.KillTile && !Main.tileCut[tile.type] && !breakableTiles.Contains(tile.type))
if (action == EditAction.KillTile && !Main.tileCut[tile.type] && !breakableTiles.Contains(tile.type) && args.Player.RecentFuse == 0)
{
// TPlayer.mount.Type 8 => Drill Containment Unit.
// If the tile is an axe tile and they aren't selecting an axe, they're hacking.
if (Main.tileAxe[tile.type] && ((args.Player.TPlayer.mount.Type != 8 && selectedItem.axe == 0) && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0))
if (Main.tileAxe[tile.type] && ((args.Player.TPlayer.mount.Type != MountID.Drill && selectedItem.axe == 0) && !ItemID.Sets.Explosives[selectedItem.netID]))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (axe) {0} {1} {2}", args.Player.Name, action, editData));
args.Player.SendTileSquareCentered(tileX, tileY, 4);
@ -687,7 +703,7 @@ namespace TShockAPI
return;
}
// If the tile is a hammer tile and they aren't selecting a hammer, they're hacking.
else if (Main.tileHammer[tile.type] && ((args.Player.TPlayer.mount.Type != 8 && selectedItem.hammer == 0) && !ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0))
else if (Main.tileHammer[tile.type] && ((args.Player.TPlayer.mount.Type != MountID.Drill && selectedItem.hammer == 0) && !ItemID.Sets.Explosives[selectedItem.netID]))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (hammer) {0} {1} {2}", args.Player.Name, action, editData));
args.Player.SendTileSquareCentered(tileX, tileY, 4);
@ -699,11 +715,12 @@ namespace TShockAPI
// also add an exception for snake coils, they can be removed when the player places a new one or after x amount of time
// If the tile is part of the breakable when placing set, it might be getting broken by a placement.
else if (tile.type != TileID.ItemFrame && tile.type != TileID.MysticSnakeRope
&& !Main.tileAxe[tile.type] && !Main.tileHammer[tile.type] && tile.wall == 0 &&
args.Player.TPlayer.mount.Type != MountID.Drill && selectedItem.pick == 0 &&
selectedItem.type != ItemID.GravediggerShovel &&
!ItemID.Sets.Explosives[selectedItem.netID] && args.Player.RecentFuse == 0
&& !TileID.Sets.BreakableWhenPlacing[tile.type])
&& !ItemID.Sets.Explosives[selectedItem.netID]
&& !TileID.Sets.BreakableWhenPlacing[tile.type]
&& !Main.tileAxe[tile.type] && !Main.tileHammer[tile.type] && tile.wall == 0
&& selectedItem.pick == 0 && selectedItem.type != ItemID.GravediggerShovel
&& args.Player.TPlayer.mount.Type != MountID.Drill
&& args.Player.TPlayer.mount.Type != MountID.DiggingMoleMinecart)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from (pick) {0} {1} {2}", args.Player.Name, action,
editData));
@ -764,7 +781,7 @@ namespace TShockAPI
if ((action == EditAction.PlaceTile || action == EditAction.ReplaceTile) && editData != selectedItem.createTile)
{
/// These would get caught up in the below check because Terraria does not set their createTile field.
if (selectedItem.netID != ItemID.IceRod && selectedItem.netID != ItemID.DirtBomb && selectedItem.netID != ItemID.StickyBomb)
if (selectedItem.netID != ItemID.IceRod && selectedItem.netID != ItemID.DirtBomb && selectedItem.netID != ItemID.StickyBomb && (args.Player.TPlayer.mount.Type != MountID.DiggingMoleMinecart || editData != TileID.MinecartTrack))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnTileEdit rejected from tile placement not matching selected item createTile {0} {1} {2} selectedItemID:{3} createTile:{4}", args.Player.Name, action, editData, selectedItem.netID, selectedItem.createTile));
args.Player.SendTileSquareCentered(tileX, tileY, 4);
@ -1333,6 +1350,7 @@ namespace TShockAPI
|| type == ProjectileID.Dynamite
|| type == ProjectileID.StickyBomb
|| type == ProjectileID.StickyDynamite
|| type == ProjectileID.BombFish
|| type == ProjectileID.ScarabBomb
|| type == ProjectileID.DirtBomb))
{
@ -1704,132 +1722,108 @@ namespace TShockAPI
// Liquid anti-cheat
// Arguably the banned buckets bit should be in the item bans system
if (amount != 0)
if (amount != 0 && !wasThereABombNearby)
{
int bucket = -1;
int selectedItemType = args.Player.TPlayer.inventory[args.Player.TPlayer.selectedItem].type;
if (selectedItemType == ItemID.EmptyBucket)
{
bucket = 0;
}
else if (selectedItemType == ItemID.WaterBucket)
{
bucket = 1;
}
else if (selectedItemType == ItemID.LavaBucket)
{
bucket = 2;
}
else if (selectedItemType == ItemID.HoneyBucket)
{
bucket = 3;
}
else if (selectedItemType == ItemID.BottomlessBucket ||
selectedItemType == ItemID.SuperAbsorbantSponge)
{
bucket = 4;
}
else if (selectedItemType == ItemID.LavaAbsorbantSponge)
{
bucket = 5;
}
else if (selectedItemType == ItemID.BottomlessLavaBucket)
{
bucket = 6;
}
else if (selectedItemType == ItemID.BottomlessHoneyBucket
|| selectedItemType == ItemID.HoneyAbsorbantSponge)
{
bucket = 7;
}
else if (selectedItemType == ItemID.BottomlessShimmerBucket)
{
bucket = 8;
}
else if (selectedItemType == ItemID.UltraAbsorbantSponge)
{
bucket = 9;
}
if (!wasThereABombNearby && type == LiquidType.Lava && !(bucket == 2 || bucket == 0 || bucket == 5 || bucket == 6 || bucket == 9))
void Reject(string reason)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected bucket check 1 from {0}", args.Player.Name));
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected liquid type {0} from {1} holding {2}", type, args.Player.Name, selectedItemType));
args.Player.SendErrorMessage(GetString("You do not have permission to perform this action."));
args.Player.Disable(GetString("Spreading lava without holding a lava bucket"), DisableFlags.WriteToLogAndConsole);
args.Player.Disable(reason, DisableFlags.WriteToLogAndConsole);
args.Player.SendTileSquareCentered(tileX, tileY, 1);
args.Handled = true;
}
if (TShock.ItemBans.DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(selectedItemType), args.Player))
{
Reject(GetString("Using banned {0} to manipulate liquid", Lang.GetItemNameValue(selectedItemType)));
return;
}
if (!wasThereABombNearby && type == LiquidType.Lava && TShock.ItemBans.DataModel.ItemIsBanned("Lava Bucket", args.Player))
switch (type)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected lava bucket from {0}", args.Player.Name));
args.Player.SendErrorMessage(GetString("You do not have permission to perform this action."));
args.Player.Disable(GetString("Using banned lava bucket without permissions"), DisableFlags.WriteToLogAndConsole);
args.Player.SendTileSquareCentered(tileX, tileY, 1);
args.Handled = true;
return;
case LiquidType.Water:
if (TShock.ItemBans.DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(ItemID.WaterBucket), args.Player))
{
Reject(GetString("Using banned water bucket without permissions"));
return;
}
break;
case LiquidType.Lava:
if (TShock.ItemBans.DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(ItemID.LavaBucket), args.Player))
{
Reject(GetString("Using banned lava bucket without permissions"));
return;
}
break;
case LiquidType.Honey:
if (TShock.ItemBans.DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(ItemID.HoneyBucket), args.Player))
{
Reject(GetString("Using banned honey bucket without permissions"));
return;
}
break;
case LiquidType.Shimmer:
if (TShock.ItemBans.DataModel.ItemIsBanned(EnglishLanguage.GetItemNameById(ItemID.BottomlessShimmerBucket), args.Player))
{
Reject(GetString("Using banned shimmering water bucket without permissions"));
return;
}
break;
default:
Reject(GetString("Manipulating unknown liquid type"));
return;
}
if (!wasThereABombNearby && type == LiquidType.Water && !(bucket == 1 || bucket == 0 || bucket == 4 || bucket == 9))
switch (selectedItemType)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected bucket check 2 from {0}", args.Player.Name));
args.Player.SendErrorMessage(GetString("You do not have permission to perform this action."));
args.Player.Disable(GetString("Spreading water without holding a water bucket"), DisableFlags.WriteToLogAndConsole);
args.Player.SendTileSquareCentered(tileX, tileY, 1);
args.Handled = true;
return;
}
if (!wasThereABombNearby && type == LiquidType.Water && TShock.ItemBans.DataModel.ItemIsBanned("Water Bucket", args.Player))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected bucket check 3 from {0}", args.Player.Name));
args.Player.SendErrorMessage(GetString("You do not have permission to perform this action."));
args.Player.Disable(GetString("Using banned water bucket without permissions"), DisableFlags.WriteToLogAndConsole);
args.Player.SendTileSquareCentered(tileX, tileY, 1);
args.Handled = true;
return;
}
if (!wasThereABombNearby && type == LiquidType.Honey && !(bucket == 3 || bucket == 0 || bucket == 7 || bucket == 9))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected bucket check 4 from {0}", args.Player.Name));
args.Player.SendErrorMessage(GetString("You do not have permission to perform this action."));
args.Player.Disable(GetString("Spreading honey without holding a honey bucket"), DisableFlags.WriteToLogAndConsole);
args.Player.SendTileSquareCentered(tileX, tileY, 1);
args.Handled = true;
return;
}
if (!wasThereABombNearby && type == LiquidType.Honey && TShock.ItemBans.DataModel.ItemIsBanned("Honey Bucket", args.Player))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected bucket check 5 from {0}", args.Player.Name));
args.Player.SendErrorMessage(GetString("You do not have permission to perform this action."));
args.Player.Disable(GetString("Using banned honey bucket without permissions"), DisableFlags.WriteToLogAndConsole);
args.Player.SendTileSquareCentered(tileX, tileY, 1);
args.Handled = true;
return;
}
if (!wasThereABombNearby && type == LiquidType.Shimmer && !(bucket == 8 || bucket == 9))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected bucket check 6 from {0}", args.Player.Name));
args.Player.SendErrorMessage(GetString("You do not have permission to perform this action."));
args.Player.Disable(GetString("Spreading shimmer without holding a shimmer bucket"), DisableFlags.WriteToLogAndConsole);
args.Player.SendTileSquareCentered(tileX, tileY, 1);
args.Handled = true;
return;
}
if (!wasThereABombNearby && type == LiquidType.Shimmer &&
TShock.ItemBans.DataModel.ItemIsBanned("Bottomless Shimmer Bucket", args.Player))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnLiquidSet rejected bucket check 7 from {0}", args.Player.Name));
args.Player.SendErrorMessage(GetString("You do not have permission to perform this action."));
args.Player.Disable(GetString("Using banned bottomless shimmer bucket without permissions"), DisableFlags.WriteToLogAndConsole);
args.Player.SendTileSquareCentered(tileX, tileY, 1);
args.Handled = true;
return;
case ItemID.WaterBucket:
case ItemID.BottomlessBucket:
if (type != LiquidType.Water)
{
Reject(GetString("Using {0} on non-water", Lang.GetItemNameValue(selectedItemType)));
return;
}
break;
case ItemID.HoneyBucket:
case ItemID.HoneyAbsorbantSponge:
case ItemID.BottomlessHoneyBucket:
if (type != LiquidType.Honey)
{
Reject(GetString("Using {0} on non-honey", Lang.GetItemNameValue(selectedItemType)));
return;
}
break;
case ItemID.LavaAbsorbantSponge:
case ItemID.BottomlessLavaBucket:
case ItemID.LavaBucket:
if (type != LiquidType.Lava)
{
Reject(GetString("Using {0} on non-lava", Lang.GetItemNameValue(selectedItemType)));
return;
}
break;
case ItemID.BottomlessShimmerBucket:
if (type != LiquidType.Shimmer)
{
Reject(GetString("Using {0} on non-shimmer", Lang.GetItemNameValue(selectedItemType)));
return;
}
break;
case ItemID.SuperAbsorbantSponge:
if (type != LiquidType.Water && type != LiquidType.Shimmer)
{
Reject(GetString("Using {0} on non-water or shimmer", Lang.GetItemNameValue(selectedItemType)));
return;
}
break;
case ItemID.EmptyBucket:
case ItemID.UltraAbsorbantSponge:
break;
default:
Reject(GetString("Using {0} to manipulate unknown liquid {1}", Lang.GetItemNameValue(selectedItemType), type));
return;
}
}
@ -1867,41 +1861,57 @@ namespace TShockAPI
int type = args.Type;
int time = args.Time;
void Reject(bool shouldResync = true)
{
args.Handled = true;
if (shouldResync)
args.Player.SendData(PacketTypes.PlayerBuff, number: id);
}
if (id >= Main.maxPlayers)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected player cap from {0}", args.Player.Name));
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: target ID out of bounds",
args.Player.Name, args.Player.Index, type, id, time));
Reject(false);
return;
}
if (TShock.Players[id] == null)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected null check from {0}", args.Player.Name));
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: target is null", args.Player.Name,
args.Player.Index, type, id, time));
Reject(false);
return;
}
if (type >= Terraria.ID.BuffID.Count)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected invalid buff type {0}", args.Player.Name));
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: invalid buff type", args.Player.Name,
args.Player.Index, type, id, time));
Reject(false);
return;
}
if (args.Player.IsBeingDisabled())
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected disabled from {0}", args.Player.Name));
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: sender is being disabled",
args.Player.Name, args.Player.Index, type, id, time));
Reject();
return;
}
if (args.Player.IsBouncerThrottled())
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected throttled from {0}", args.Player.Name));
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: sender is being throttled",
args.Player.Name, args.Player.Index, type, id, time));
Reject();
return;
}
@ -1910,41 +1920,46 @@ namespace TShockAPI
if (!args.Player.IsInRange(targetPlayer.TileX, targetPlayer.TileY, 50))
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected range check from {0}", args.Player.Name));
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: sender is not in range of target",
args.Player.Name, args.Player.Index, type, id, time));
Reject();
return;
}
if (buffLimit == null)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected non-whitelisted buff {0}", args.Player.Name));
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: buff is not whitelisted",
args.Player.Name, args.Player.Index, type, id, time));
Reject();
return;
}
if (buffLimit.CanOnlyBeAppliedToSender && id != args.Player.Index)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected applied to non-sender from {0}", args.Player.Name));
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: buff cannot be applied to non-senders",
args.Player.Name, args.Player.Index, type, id, time));
Reject();
return;
}
if (!buffLimit.CanBeAddedWithoutHostile && !targetPlayer.TPlayer.hostile)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected hostile/pvp from {0}", args.Player.Name));
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: buff cannot be applied without pvp",
args.Player.Name, args.Player.Index, type, id, time));
Reject();
return;
}
if (time <= 0 || time > buffLimit.MaxTicks)
{
TShock.Log.ConsoleDebug(GetString("Bouncer / OnPlayerBuff rejected time too long from {0}", args.Player.Name));
args.Player.SendData(PacketTypes.PlayerBuff, "", id);
args.Handled = true;
TShock.Log.ConsoleDebug(GetString(
"Bouncer / OnPlayerBuff rejected {0} ({1}) applying buff {2} to {3} for {4} ticks: buff cannot be applied for that long",
args.Player.Name, args.Player.Index, type, id, time));
Reject();
return;
}
}

View file

@ -55,6 +55,10 @@ namespace TShockAPI.Configuration
[Description("Allows stacks in chests to go beyond the stack limit during world loading.")]
public bool IgnoreChestStacksOnLoad = false;
/// <summary>Allows changing of the default world tile provider.</summary>
[Description("Allows changing of the default world tile provider.")]
public string WorldTileProvider = "default";
#endregion
@ -109,8 +113,8 @@ namespace TShockAPI.Configuration
[Description("Enables never ending invasion events. You still need to start the event, such as with the /invade command.")]
public bool InfiniteInvasion;
/// <summary>Sets the PvP mode. Valid types are: "normal", "always", "disabled".</summary>
[Description("Sets the PvP mode. Valid types are: \"normal\", \"always\" and \"disabled\".")]
/// <summary>Sets the PvP mode. Valid types are: "normal", "always", "pvpwithnoteam", "disabled".</summary>
[Description("Sets the PvP mode. Valid types are: \"normal\", \"always\", \"pvpwithnoteam\" and \"disabled\".")]
public string PvPMode = "normal";
/// <summary>Prevents tiles from being placed within SpawnProtectionRadius of the default spawn.</summary>

View file

@ -856,6 +856,14 @@ namespace TShockAPI
/// </summary>
public int RespawnTimer { get; set; }
/// <summary>
/// Number Of Deaths PVE
/// </summary>
public int NumberOfDeathsPVE { get; set; }
/// <summary>
/// Number Of Deaths PVP
/// </summary>
public int NumberOfDeathsPVP { get; set; }
/// <summary>
/// Context of where the player is spawning from.
/// </summary>
public PlayerSpawnContext SpawnContext { get; set; }
@ -864,7 +872,7 @@ namespace TShockAPI
/// PlayerSpawn - When a player spawns
/// </summary>
public static HandlerList<SpawnEventArgs> PlayerSpawn = new HandlerList<SpawnEventArgs>();
private static bool OnPlayerSpawn(TSPlayer player, MemoryStream data, byte pid, int spawnX, int spawnY, int respawnTimer, PlayerSpawnContext spawnContext)
private static bool OnPlayerSpawn(TSPlayer player, MemoryStream data, byte pid, int spawnX, int spawnY, int respawnTimer, int numberOfDeathsPVE, int numberOfDeathsPVP, PlayerSpawnContext spawnContext)
{
if (PlayerSpawn == null)
return false;
@ -877,6 +885,8 @@ namespace TShockAPI
SpawnX = spawnX,
SpawnY = spawnY,
RespawnTimer = respawnTimer,
NumberOfDeathsPVE = numberOfDeathsPVE,
NumberOfDeathsPVP = numberOfDeathsPVP,
SpawnContext = spawnContext
};
PlayerSpawn.Invoke(null, args);
@ -1033,12 +1043,16 @@ namespace TShockAPI
/// 0 = Old One's Army, 1 = Granite, 2 = Marble, 3 = Hive, 4 = Gem Cave, 5 = Lihzhard Temple, 6 = Graveyard
/// </summary>
public BitsByte Zone4 { get; set; }
/// <summary>
/// 0 = The Aether
/// </summary>
public BitsByte Zone5 { get; set; }
}
/// <summary>
/// PlayerZone - When the player sends it's zone/biome information to the server
/// </summary>
public static HandlerList<PlayerZoneEventArgs> PlayerZone = new HandlerList<PlayerZoneEventArgs>();
private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, BitsByte zone1, BitsByte zone2, BitsByte zone3, BitsByte zone4)
private static bool OnPlayerZone(TSPlayer player, MemoryStream data, byte plr, BitsByte zone1, BitsByte zone2, BitsByte zone3, BitsByte zone4, BitsByte zone5)
{
if (PlayerZone == null)
return false;
@ -1051,7 +1065,8 @@ namespace TShockAPI
Zone1 = zone1,
Zone2 = zone2,
Zone3 = zone3,
Zone4 = zone4
Zone4 = zone4,
Zone5 = zone5
};
PlayerZone.Invoke(null, args);
return args.Handled;
@ -1519,12 +1534,16 @@ namespace TShockAPI
/// Type
/// </summary>
public byte type { get; set; }
/// <summary>
/// Paint Coat Tile
/// </summary>
public byte coatTile { get; set; }
}
/// <summary>
/// NPCStrike - Called when an NPC is attacked
/// </summary>
public static HandlerList<PaintTileEventArgs> PaintTile = new HandlerList<PaintTileEventArgs>();
private static bool OnPaintTile(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t)
private static bool OnPaintTile(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t, byte ct)
{
if (PaintTile == null)
return false;
@ -1535,7 +1554,8 @@ namespace TShockAPI
Data = data,
X = x,
Y = y,
type = t
type = t,
coatTile = ct
};
PaintTile.Invoke(null, args);
return args.Handled;
@ -1558,12 +1578,16 @@ namespace TShockAPI
/// Type
/// </summary>
public byte type { get; set; }
/// <summary>
/// Paint Coat Wall
/// </summary>
public byte coatWall { get; set; }
}
/// <summary>
/// Called When a wall is painted
/// </summary>
public static HandlerList<PaintWallEventArgs> PaintWall = new HandlerList<PaintWallEventArgs>();
private static bool OnPaintWall(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t)
private static bool OnPaintWall(TSPlayer player, MemoryStream data, Int32 x, Int32 y, byte t, byte cw)
{
if (PaintWall == null)
return false;
@ -1574,7 +1598,8 @@ namespace TShockAPI
Data = data,
X = x,
Y = y,
type = t
type = t,
coatWall = cw
};
PaintWall.Invoke(null, args);
return args.Handled;
@ -1734,12 +1759,15 @@ namespace TShockAPI
/// <summary>Alternate variation of the object placed.</summary>
public byte Alternate { get; set; }
/// <summary>Related to Rubblemaker.</summary>
public sbyte Random { get; set; }
/// <summary>The direction the object was placed.</summary>
public bool Direction { get; set; }
}
/// <summary>Fired when an object is placed in the world.</summary>
public static HandlerList<PlaceObjectEventArgs> PlaceObject = new HandlerList<PlaceObjectEventArgs>();
private static bool OnPlaceObject(TSPlayer player, MemoryStream data, short x, short y, short type, short style, byte alternate, bool direction)
private static bool OnPlaceObject(TSPlayer player, MemoryStream data, short x, short y, short type, short style, byte alternate, sbyte random, bool direction)
{
if (PlaceObject == null)
return false;
@ -1753,6 +1781,7 @@ namespace TShockAPI
Type = type,
Style = style,
Alternate = alternate,
Random = random,
Direction = direction
};
@ -1980,6 +2009,10 @@ namespace TShockAPI
/// Is the damage critical?
/// </summary>
public bool Critical { get; set; }
/// <summary>
/// Cooldown Counter
/// </summary>
public sbyte CooldownCounter { get; set; }
/// <summary>The reason the player took damage and/or died.</summary>
public PlayerDeathReason PlayerDeathReason { get; set; }
}
@ -1987,7 +2020,7 @@ namespace TShockAPI
/// PlayerDamage - Called when a player is damaged
/// </summary>
public static HandlerList<PlayerDamageEventArgs> PlayerDamage = new HandlerList<PlayerDamageEventArgs>();
private static bool OnPlayerDamage(TSPlayer player, MemoryStream data, byte id, byte dir, short dmg, bool pvp, bool crit, PlayerDeathReason playerDeathReason)
private static bool OnPlayerDamage(TSPlayer player, MemoryStream data, byte id, byte dir, short dmg, bool pvp, bool crit, sbyte cooldownCounter, PlayerDeathReason playerDeathReason)
{
if (PlayerDamage == null)
return false;
@ -2001,6 +2034,7 @@ namespace TShockAPI
Damage = dmg,
PVP = pvp,
Critical = crit,
CooldownCounter = cooldownCounter,
PlayerDeathReason = playerDeathReason,
};
PlayerDamage.Invoke(null, args);
@ -2687,9 +2721,11 @@ namespace TShockAPI
short spawnx = args.Data.ReadInt16();
short spawny = args.Data.ReadInt16();
int respawnTimer = args.Data.ReadInt32();
short numberOfDeathsPVE = args.Data.ReadInt16();
short numberOfDeathsPVP = args.Data.ReadInt16();
PlayerSpawnContext context = (PlayerSpawnContext)args.Data.ReadByte();
if (OnPlayerSpawn(args.Player, args.Data, player, spawnx, spawny, respawnTimer, context))
if (OnPlayerSpawn(args.Player, args.Data, player, spawnx, spawny, respawnTimer, numberOfDeathsPVE, numberOfDeathsPVP, context))
return true;
if ((Main.ServerSideCharacter) && (spawnx == -1 && spawny == -1)) //this means they want to spawn to vanilla spawn
@ -2912,17 +2948,19 @@ namespace TShockAPI
Vector2 vel = args.Data.ReadVector2();
byte owner = args.Data.ReadInt8();
short type = args.Data.ReadInt16();
NewProjectileData bits = new NewProjectileData((BitsByte)args.Data.ReadByte());
BitsByte bitsByte = (BitsByte)args.Data.ReadByte();
BitsByte bitsByte2 = (BitsByte)(bitsByte[2] ? args.Data.ReadByte() : 0);
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;
short projUUID = bits.HasUUUID ? args.Data.ReadInt16() : (short)-1;
if (projUUID >= 1000)
projUUID = -1;
for (int i = 0; i < Projectile.maxAI; ++i) ai[i] = 0f;
ai[0] = bitsByte[0] ? args.Data.ReadSingle() : 0f;
ai[1] = bitsByte[1] ? args.Data.ReadSingle() : 0f;
ushort bannerId = (ushort)(bitsByte[3] ? args.Data.ReadUInt16() : 0);
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);
@ -3054,7 +3092,7 @@ namespace TShockAPI
}
string pvpMode = TShock.Config.Settings.PvPMode.ToLowerInvariant();
if (pvpMode == "disabled" || pvpMode == "always" || (DateTime.UtcNow - args.Player.LastPvPTeamChange).TotalSeconds < 5)
if (pvpMode == "disabled" || pvpMode == "always" || pvpMode == "pvpwithnoteam" || (DateTime.UtcNow - args.Player.LastPvPTeamChange).TotalSeconds < 5)
{
TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandleTogglePvp rejected fastswitch {0}", args.Player.Name));
args.Player.SendData(PacketTypes.TogglePvp, "", id);
@ -3150,8 +3188,9 @@ namespace TShockAPI
BitsByte zone2 = args.Data.ReadInt8();
BitsByte zone3 = args.Data.ReadInt8();
BitsByte zone4 = args.Data.ReadInt8();
BitsByte zone5 = args.Data.ReadInt8();
if (OnPlayerZone(args.Player, args.Data, plr, zone1, zone2, zone3, zone4))
if (OnPlayerZone(args.Player, args.Data, plr, zone1, zone2, zone3, zone4, zone5))
return true;
return false;
@ -3310,7 +3349,8 @@ namespace TShockAPI
if (id != args.Player.Index)
return true;
if ((DateTime.UtcNow - args.Player.LastPvPTeamChange).TotalSeconds < 5)
string pvpMode = TShock.Config.Settings.PvPMode.ToLowerInvariant();
if (pvpMode == "pvpwithnoteam" || (DateTime.UtcNow - args.Player.LastPvPTeamChange).TotalSeconds < 5)
{
args.Player.SendData(PacketTypes.PlayerTeam, "", id);
TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandlePlayerTeam rejected team fastswitch {0}", args.Player.Name));
@ -3616,13 +3656,14 @@ namespace TShockAPI
var x = args.Data.ReadInt16();
var y = args.Data.ReadInt16();
var t = args.Data.ReadInt8();
var ct = args.Data.ReadInt8();//PaintCoatTile
if (x < 0 || y < 0 || x >= Main.maxTilesX || y >= Main.maxTilesY || t > Main.numTileColors)
{
TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandlePaintTile rejected range check {0}", args.Player.Name));
return true;
}
if (OnPaintTile(args.Player, args.Data, x, y, t))
if (OnPaintTile(args.Player, args.Data, x, y, t, ct))
{
return true;
}
@ -3663,13 +3704,14 @@ namespace TShockAPI
var x = args.Data.ReadInt16();
var y = args.Data.ReadInt16();
var t = args.Data.ReadInt8();
var cw = args.Data.ReadInt8();//PaintCoatWall
if (x < 0 || y < 0 || x >= Main.maxTilesX || y >= Main.maxTilesY || t > Main.numTileColors)
{
TShock.Log.ConsoleDebug(GetString("GetDataHandlers / HandlePaintWall rejected range check {0}", args.Player.Name));
return true;
}
if (OnPaintWall(args.Player, args.Data, x, y, t))
if (OnPaintWall(args.Player, args.Data, x, y, t, cw))
{
return true;
}
@ -3928,9 +3970,10 @@ namespace TShockAPI
short type = args.Data.ReadInt16();
short style = args.Data.ReadInt16();
byte alternate = args.Data.ReadInt8();
sbyte random = (sbyte)args.Data.ReadInt8();
bool direction = args.Data.ReadBoolean();
if (OnPlaceObject(args.Player, args.Data, x, y, type, style, alternate, direction))
if (OnPlaceObject(args.Player, args.Data, x, y, type, style, alternate, random, direction))
return true;
return false;
@ -4068,7 +4111,7 @@ namespace TShockAPI
private static bool HandleNpcTeleportPortal(GetDataHandlerArgs args)
{
var npcIndex = args.Data.ReadByte();
var npcIndex = args.Data.ReadUInt16();
var portalColorIndex = args.Data.ReadInt16();
var newPosition = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle());
var velocity = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle());
@ -4162,8 +4205,9 @@ namespace TShockAPI
var bits = (BitsByte)(args.Data.ReadByte());
var crit = bits[0];
var pvp = bits[1];
var cooldownCounter = (sbyte)args.Data.ReadInt8();
if (OnPlayerDamage(args.Player, args.Data, id, direction, dmg, pvp, crit, playerDeathReason))
if (OnPlayerDamage(args.Player, args.Data, id, direction, dmg, pvp, crit, cooldownCounter, playerDeathReason))
return true;
return false;

View file

@ -33,6 +33,8 @@ namespace TShockAPI.Net
public short TileX { get; set; }
public short TileY { get; set; }
public int RespawnTimer { get; set; }
public short NumberOfDeathsPVE { get; set; }
public short NumberOfDeathsPVP { get; set; }
public PlayerSpawnContext PlayerSpawnContext { get; set; }
public override void Pack(Stream stream)
@ -41,6 +43,8 @@ namespace TShockAPI.Net
stream.WriteInt16(TileX);
stream.WriteInt16(TileY);
stream.WriteInt32(RespawnTimer);
stream.WriteInt16(NumberOfDeathsPVE);
stream.WriteInt16(NumberOfDeathsPVP);
stream.WriteByte((byte) PlayerSpawnContext);
}
}

View file

@ -1394,7 +1394,9 @@ namespace TShockAPI
/// <param name="tiley">The Y coordinate.</param>
/// <param name="context">The PlayerSpawnContext.</param>
/// <param name="respawnTimer">The respawn timer, will be Player.respawnTimer if parameter is null.</param>
public void Spawn(int tilex, int tiley, PlayerSpawnContext context, int? respawnTimer = null)
/// <param name="numberOfDeathsPVE">The number of deaths PVE, will be TPlayer.numberOfDeathsPVE if parameter is null.</param>
/// <param name="numberOfDeathsPVP">The number of deaths PVP, will be TPlayer.numberOfDeathsPVP if parameter is null.</param>
public void Spawn(int tilex, int tiley, PlayerSpawnContext context, int? respawnTimer = null, short? numberOfDeathsPVE = null, short? numberOfDeathsPVP = null)
{
using (var ms = new MemoryStream())
{
@ -1404,6 +1406,8 @@ namespace TShockAPI
TileX = (short)tilex,
TileY = (short)tiley,
RespawnTimer = respawnTimer ?? TShock.Players[Index].RespawnTimer * 60,
NumberOfDeathsPVE = numberOfDeathsPVE ?? (short)TPlayer.numberOfDeathsPVE,
NumberOfDeathsPVP = numberOfDeathsPVP ?? (short)TPlayer.numberOfDeathsPVP,
PlayerSpawnContext = context,
};
msg.PackFull(ms);

View file

@ -45,6 +45,10 @@ using TShockAPI.Localization;
using TShockAPI.Configuration;
using Terraria.GameContent.Creative;
using System.Runtime.InteropServices;
using MonoMod.Cil;
using Terraria.Achievements;
using Terraria.Initializers;
using Terraria.UI.Chat;
using TShockAPI.Modules;
namespace TShockAPI
@ -383,6 +387,19 @@ namespace TShockAPI
if (Config.Settings.EnableGeoIP && File.Exists(geoippath))
Geo = new GeoIPCountry(geoippath);
// check if a custom tile provider is to be used
switch(Config.Settings.WorldTileProvider?.ToLower())
{
case "heaptile":
Log.ConsoleInfo(GetString($"Using {nameof(HeapTile)} for tile implementation"), TraceLevel.Info);
Main.tile = new TileProvider();
break;
case "constileation":
Log.ConsoleInfo(GetString($"Using {nameof(ConstileationProvider)} for tile implementation"), TraceLevel.Info);
Main.tile = new ConstileationProvider();
break;
}
Log.ConsoleInfo(GetString("TShock {0} ({1}) now running.", Version, VersionCodename));
ServerApi.Hooks.GamePostInitialize.Register(this, OnPostInit);
@ -416,6 +433,33 @@ namespace TShockAPI
EnglishLanguage.Initialize();
// The AchievementTagHandler expects Main.Achievements to be non-null, which is not normally the case on dedicated servers.
// When trying to parse an achievement chat tag, it will instead throw.
// The tag is parsed when calling ChatManager.ParseMessage, which is used in TShock when writing chat messages to the
// console. Our OnChat handler uses Utils.Broadcast, which will send the message to all connected clients, write the message
// to the console and the log. Due to the order of execution, the message ends up being sent to all connected clients, but
// throws whilst trying to write to the console, and never gets written to the log.
// To solve the issue, we make achievements available on the server, allowing the tag handler to work as expected, and
// even allowing the localization of achievement names to appear in the console.
if (Game != null)
{
// Initialize the AchievementManager, which is normally only done on clients.
Game._achievements = new AchievementManager();
IL.Terraria.Initializers.AchievementInitializer.Load += OnAchievementInitializerLoad;
// Actually call AchievementInitializer.Load, which is also normally only done on clients.
AchievementInitializer.Load();
}
else
{
// If we don't have a Game instance, then we'll just remove the achievement tag handler entirely. This will cause the
// raw tag to just be used instead (and not be localized), but still avoid all the issues outlined above.
ChatManager._handlers.Remove("a", out _);
ChatManager._handlers.Remove("achievement", out _);
}
ModuleManager.Initialise(new object[] { this });
if (Config.Settings.RestApiEnabled)
@ -452,6 +496,13 @@ namespace TShockAPI
}
}
private static void OnAchievementInitializerLoad(ILContext il)
{
// Modify AchievementInitializer.Load to remove the Main.netMode == 2 check (occupies the first 4 IL instructions)
for (var i = 0; i < 4; i++)
il.Body.Instructions.RemoveAt(0);
}
protected void CrashReporter_HeapshotRequesting(object sender, EventArgs e)
{
foreach (TSPlayer player in TShock.Players)
@ -473,6 +524,8 @@ namespace TShockAPI
}
SaveManager.Instance.Dispose();
IL.Terraria.Initializers.AchievementInitializer.Load -= OnAchievementInitializerLoad;
ModuleManager.Dispose();
ServerApi.Hooks.GamePostInitialize.Deregister(this, OnPostInit);
@ -1663,7 +1716,7 @@ namespace TShockAPI
player.SendFileTextAsMessage(FileTools.MotdPath);
string pvpMode = Config.Settings.PvPMode.ToLowerInvariant();
if (pvpMode == "always")
if (pvpMode == "always" || pvpMode == "pvpwithnoteam")
{
player.TPlayer.hostile = true;
player.SendData(PacketTypes.TogglePvp, "", player.Index);

View file

@ -290,9 +290,15 @@ namespace TShockAPI
if (currentName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return new List<Item> { GetItemById(i) };
if (currentName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
{
startswith.Add(i);
else if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
continue;
}
if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
{
contains.Add(i);
continue;
}
}
currentName = EnglishLanguage.GetItemNameById(i);
if (!string.IsNullOrEmpty(currentName))
@ -300,9 +306,15 @@ namespace TShockAPI
if (currentName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return new List<Item> { GetItemById(i) };
if (currentName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
{
startswith.Add(i);
else if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
continue;
}
if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
{
contains.Add(i);
continue;
}
}
}
@ -377,9 +389,15 @@ namespace TShockAPI
if (currentName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return new List<NPC> { GetNPCById(i) };
if (currentName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
{
startswith.Add(i);
else if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
continue;
}
if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
{
contains.Add(i);
continue;
}
}
currentName = EnglishLanguage.GetNpcNameById(i);
if (!string.IsNullOrEmpty(currentName))
@ -387,9 +405,15 @@ namespace TShockAPI
if (currentName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return new List<NPC> { GetNPCById(i) };
if (currentName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
{
startswith.Add(i);
else if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
continue;
}
if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
{
contains.Add(i);
continue;
}
}
}
@ -435,9 +459,15 @@ namespace TShockAPI
if (currentName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return new List<int> { i };
if (currentName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
{
startswith.Add(i);
else if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
continue;
}
if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
{
contains.Add(i);
continue;
}
}
currentName = EnglishLanguage.GetBuffNameById(i);
if (!string.IsNullOrWhiteSpace(currentName))
@ -445,9 +475,15 @@ namespace TShockAPI
if (currentName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return new List<int> { i };
if (currentName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
{
startswith.Add(i);
else if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
continue;
}
if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
{
contains.Add(i);
continue;
}
}
}
@ -483,9 +519,15 @@ namespace TShockAPI
if (currentName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return new List<int> { i };
if (currentName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
{
startswith.Add(i);
else if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
continue;
}
if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
{
contains.Add(i);
continue;
}
}
currentName = EnglishLanguage.GetPrefixById(i);
if (!string.IsNullOrWhiteSpace(currentName))
@ -493,9 +535,15 @@ namespace TShockAPI
if (currentName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
return new List<int> { i };
if (currentName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
{
startswith.Add(i);
else if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
continue;
}
if (currentName.Contains(name, StringComparison.InvariantCultureIgnoreCase))
{
contains.Add(i);
continue;
}
}
}