Skip to content
Open
35 changes: 18 additions & 17 deletions TShockAPI/Bouncer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
TShock, a server mod for Terraria
Copyright (C) 2011-2019 Pryaxis & TShock Contributors

Expand Down Expand Up @@ -1312,6 +1312,7 @@
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
Expand All @@ -1332,7 +1333,7 @@
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;
}
Expand All @@ -1343,7 +1344,7 @@

// 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;
Expand All @@ -1355,7 +1356,7 @@

// 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;
Expand All @@ -1366,7 +1367,7 @@
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;
}
Expand All @@ -1375,7 +1376,7 @@
{
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;
}
Expand Down Expand Up @@ -1412,7 +1413,7 @@
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;
}
Expand All @@ -1423,7 +1424,7 @@
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;
}
Expand All @@ -1439,7 +1440,7 @@
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;
}
Expand All @@ -1449,7 +1450,7 @@
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;
}
Expand Down Expand Up @@ -1478,7 +1479,7 @@
{
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;
Expand All @@ -1493,7 +1494,7 @@
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));
Expand All @@ -1510,7 +1511,7 @@
)
{
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;
}
Expand All @@ -1526,7 +1527,7 @@
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;
}
Expand Down Expand Up @@ -1632,15 +1633,15 @@
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;
}

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;
}
Expand Down Expand Up @@ -2676,7 +2677,7 @@

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);

Check failure on line 2680 in TShockAPI/Bouncer.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm64)

The type or namespace name 'NewItemOwnership' does not exist in the namespace 'Terraria' (are you missing an assembly reference?)

Check failure on line 2680 in TShockAPI/Bouncer.cs

View workflow job for this annotation

GitHub Actions / build (linux-x64)

The type or namespace name 'NewItemOwnership' does not exist in the namespace 'Terraria' (are you missing an assembly reference?)

Check failure on line 2680 in TShockAPI/Bouncer.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm)

The type or namespace name 'NewItemOwnership' does not exist in the namespace 'Terraria' (are you missing an assembly reference?)

Check failure on line 2680 in TShockAPI/Bouncer.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

The type or namespace name 'NewItemOwnership' does not exist in the namespace 'Terraria' (are you missing an assembly reference?)

Check failure on line 2680 in TShockAPI/Bouncer.cs

View workflow job for this annotation

GitHub Actions / build (osx-x64)

The type or namespace name 'NewItemOwnership' does not exist in the namespace 'Terraria' (are you missing an assembly reference?)

Check failure on line 2680 in TShockAPI/Bouncer.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'NewItemOwnership' does not exist in the namespace 'Terraria' (are you missing an assembly reference?)
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);
Expand Down
2 changes: 1 addition & 1 deletion TShockAPI/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2537,7 +2537,7 @@
}
else
{
Main.StartRain(garenteeCoinRain: true);
Main.StartRain(guaranteeCoinRain: true);

Check failure on line 2540 in TShockAPI/Commands.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm64)

The best overload for 'StartRain' does not have a parameter named 'guaranteeCoinRain'

Check failure on line 2540 in TShockAPI/Commands.cs

View workflow job for this annotation

GitHub Actions / build (linux-x64)

The best overload for 'StartRain' does not have a parameter named 'guaranteeCoinRain'

Check failure on line 2540 in TShockAPI/Commands.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm)

The best overload for 'StartRain' does not have a parameter named 'guaranteeCoinRain'

Check failure on line 2540 in TShockAPI/Commands.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

The best overload for 'StartRain' does not have a parameter named 'guaranteeCoinRain'

Check failure on line 2540 in TShockAPI/Commands.cs

View workflow job for this annotation

GitHub Actions / build (osx-x64)

The best overload for 'StartRain' does not have a parameter named 'guaranteeCoinRain'

Check failure on line 2540 in TShockAPI/Commands.cs

View workflow job for this annotation

GitHub Actions / test

The best overload for 'StartRain' does not have a parameter named 'guaranteeCoinRain'
TSPlayer.All.SendData(PacketTypes.WorldInfo);
TSPlayer.All.SendInfoMessage(GetString("{0} caused it to coin rain.", args.Player.Name));
}
Expand Down
108 changes: 87 additions & 21 deletions TShockAPI/GetDataHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,11 @@
/// </summary>
public int Index { get; set; }

/// <summary>
/// Slot-reuse counter from the sender's ProjectileKey.
/// </summary>
public int Generation { get; set; }

/// <summary>
/// The special meaning of the projectile.
/// </summary>
Expand All @@ -709,7 +714,7 @@
/// NewProjectile - Called when a client creates a new projectile
/// </summary>
public static HandlerList<NewProjectileEventArgs> NewProjectile = new HandlerList<NewProjectileEventArgs>();
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;
Expand All @@ -726,7 +731,8 @@
Type = type,
Index = index,
Player = player,
Ai = ai
Ai = ai,
Generation = generation
};
NewProjectile.Invoke(null, args);
return args.Handled;
Expand Down Expand Up @@ -790,6 +796,8 @@
public byte ProjectileOwner;
/// <summary>The index of the projectile in Main.projectile.</summary>
public int ProjectileIndex;
/// <summary>Slot-reuse counter from the sender's ProjectileKey.</summary>
public int ProjectileGeneration;
}
/// <summary>The event fired when a projectile kill packet is received.</summary>
public static HandlerList<ProjectileKillEventArgs> ProjectileKill = new HandlerList<ProjectileKillEventArgs>();
Expand All @@ -800,7 +808,7 @@
/// <param name="owner">The projectile's owner (from the packet).</param>
/// <param name="index">The projectile's index (from Main.projectiles).</param>
/// <returns>bool</returns>
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;
Expand All @@ -812,6 +820,7 @@
ProjectileIdentity = identity,
ProjectileOwner = owner,
ProjectileIndex = index,
ProjectileGeneration = generation,
};

ProjectileKill.Invoke(null, args);
Expand Down Expand Up @@ -3260,8 +3269,19 @@
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;
Expand All @@ -3271,6 +3291,7 @@

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();

Expand All @@ -3295,10 +3316,11 @@

private static bool HandleProjectileNew(GetDataHandlerArgs args)
{
short ident = args.Data.ReadInt16();
var key = (ProjectileKey)args.Data.ReadInt32();

Check failure on line 3319 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm64)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3319 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-x64)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3319 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3319 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3319 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (osx-x64)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3319 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)
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);
Expand All @@ -3310,11 +3332,21 @@
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)
Expand All @@ -3324,7 +3356,7 @@
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)
Expand All @@ -3344,12 +3376,22 @@

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)

Check failure on line 3389 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm64)

'NPC' does not contain a definition for 'generation' and no accessible extension method 'generation' accepting a first argument of type 'NPC' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 3389 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-x64)

'NPC' does not contain a definition for 'generation' and no accessible extension method 'generation' accepting a first argument of type 'NPC' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 3389 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm)

'NPC' does not contain a definition for 'generation' and no accessible extension method 'generation' accepting a first argument of type 'NPC' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 3389 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

'NPC' does not contain a definition for 'generation' and no accessible extension method 'generation' accepting a first argument of type 'NPC' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 3389 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (osx-x64)

'NPC' does not contain a definition for 'generation' and no accessible extension method 'generation' accepting a first argument of type 'NPC' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 3389 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / test

'NPC' does not contain a definition for 'generation' and no accessible extension method 'generation' accepting a first argument of type 'NPC' could be found (are you missing a using directive or an assembly reference?)
{
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;

Expand Down Expand Up @@ -3393,12 +3435,33 @@

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();

Check failure on line 3438 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm64)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3438 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-x64)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3438 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (linux-arm)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3438 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 3438 in TShockAPI/GetDataHandlers.cs

View workflow job for this annotation

GitHub Actions / test

The type or namespace name 'ProjectileKey' could not be found (are you missing a using directive or an assembly reference?)
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;
}
Expand Down Expand Up @@ -4281,7 +4344,12 @@
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)
{
Expand Down Expand Up @@ -5193,18 +5261,16 @@
Ping,
Ambience,
Bestiary,
CreativeUnlocks,
CreativePowers,
CreativeUnlocksPlayerReport,
TeleportPylon,
Particles,
CreativePowerPermissions,
Banners,
CraftingRequests,
TagEffectState,
LeashedEntity,
UnbreakableWallScan,
[Obsolete("Removed in 1.4.5")]
CreativeUnlocks
UnbreakableWallScan
}

public enum CreativePowerTypes
Expand Down
Loading
Loading