Merge branch 'general-devel' into otapi3
This commit is contained in:
commit
aa0bd7deb2
7 changed files with 263 additions and 77 deletions
|
|
@ -400,6 +400,18 @@ namespace TShockAPI
|
|||
return;
|
||||
}
|
||||
|
||||
// I do not understand the ice tile check enough to be able to modify it, however I do know that it can be used to completely bypass region protection
|
||||
// This check ensures that build permission is always checked no matter what
|
||||
if (!args.Player.HasBuildPermission(tileX, tileY))
|
||||
{
|
||||
TShock.Log.ConsoleDebug("Bouncer / OnTileEdit rejected from build from {0} {1} {2}", args.Player.Name, action, editData);
|
||||
|
||||
GetRollbackRectSize(tileX, tileY, out byte width, out byte length, out int offsetY);
|
||||
args.Player.SendTileRect((short)(tileX - width), (short)(tileY + offsetY), (byte)(width * 2), (byte)(length + 1));
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (editData < 0 ||
|
||||
((action == EditAction.PlaceTile || action == EditAction.ReplaceTile) && editData >= Main.maxTileSets) ||
|
||||
((action == EditAction.PlaceWall || action == EditAction.ReplaceWall) && editData >= Main.maxWallTypes))
|
||||
|
|
|
|||
|
|
@ -445,14 +445,6 @@ namespace TShockAPI.Configuration
|
|||
[Description("Whether or not to kick users when they surpass the HealOther threshold.")]
|
||||
public bool KickOnHealOtherThresholdBroken = false;
|
||||
|
||||
/// <summary>Disables a player if this number of tiles is present in a Tile Rectangle packet</summary>
|
||||
[Description("Disables a player if this number of tiles is present in a Tile Rectangle packet")]
|
||||
public int TileRectangleSizeThreshold = 50;
|
||||
|
||||
/// <summary>Whether or not to kick users when they surpass the TileRectangleSize threshold.</summary>
|
||||
[Description("Whether or not to kick users when they surpass the TileRectangleSize threshold.")]
|
||||
public bool KickOnTileRectangleSizeThresholdBroken = false;
|
||||
|
||||
/// <summary>Whether or not the server should suppress build permission failure warnings from regions, spawn point, or server edit failure.</summary>
|
||||
[Description("Whether or not the server should suppress build permission failure warnings from regions, spawn point, or server edit failure.")]
|
||||
public bool SuppressPermissionFailureNotices = false;
|
||||
|
|
|
|||
|
|
@ -18,13 +18,55 @@ namespace TShockAPI.Handlers
|
|||
public class SendTileRectHandler : IPacketHandler<GetDataHandlers.SendTileRectEventArgs>
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps grass-type blocks to flowers that can be grown on them with flower boots
|
||||
/// Maps plant tile types to their valid grass ground tiles when using flower boots
|
||||
/// </summary>
|
||||
public static Dictionary<ushort, List<ushort>> GrassToPlantMap = new Dictionary<ushort, List<ushort>>
|
||||
private static readonly Dictionary<ushort, HashSet<ushort>> FlowerBootPlantToGrassMap = new Dictionary<ushort, HashSet<ushort>>
|
||||
{
|
||||
{ TileID.Grass, new List<ushort> { TileID.Plants, TileID.Plants2 } },
|
||||
{ TileID.HallowedGrass, new List<ushort> { TileID.HallowedPlants, TileID.HallowedPlants2 } },
|
||||
{ TileID.JungleGrass, new List<ushort> { TileID.JunglePlants, TileID.JunglePlants2 } }
|
||||
{ TileID.Plants, new HashSet<ushort>()
|
||||
{
|
||||
TileID.Grass, TileID.GolfGrass
|
||||
} },
|
||||
{ TileID.HallowedPlants, new HashSet<ushort>()
|
||||
{
|
||||
TileID.HallowedGrass, TileID.GolfGrassHallowed
|
||||
} },
|
||||
{ TileID.HallowedPlants2, new HashSet<ushort>()
|
||||
{
|
||||
TileID.HallowedGrass, TileID.GolfGrassHallowed
|
||||
} },
|
||||
{ TileID.JunglePlants2, new HashSet<ushort>()
|
||||
{
|
||||
TileID.JungleGrass
|
||||
} },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps plant tile types to a list of valid styles, which are used to determine the FrameX value of the plant tile
|
||||
/// See `Player.DoBootsEffect_PlaceFlowersOnTile`
|
||||
/// </summary>
|
||||
private static readonly Dictionary<ushort, HashSet<ushort>> FlowerBootPlantToStyleMap = new Dictionary<ushort, HashSet<ushort>>()
|
||||
{
|
||||
{ TileID.Plants, new HashSet<ushort>()
|
||||
{
|
||||
// The upper line is from a `NextFromList` call
|
||||
// The lower line is from an additional switch which will add the listed options by adding a random value to a select set of styles
|
||||
6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 24, 27, 30, 33, 36, 39, 42,
|
||||
22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44,
|
||||
} },
|
||||
{ TileID.HallowedPlants, new HashSet<ushort>()
|
||||
{
|
||||
// 5 is intentionally missing here because it is being skipped by vanilla
|
||||
4, 6,
|
||||
} },
|
||||
{ TileID.HallowedPlants2, new HashSet<ushort>()
|
||||
{
|
||||
// 5 is intentionally missing here because it is being skipped by vanilla
|
||||
2, 3, 4, 6, 7,
|
||||
} },
|
||||
{ TileID.JunglePlants2, new HashSet<ushort>()
|
||||
{
|
||||
9, 10, 11, 12, 13, 14, 15, 16,
|
||||
} },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -38,7 +80,7 @@ namespace TShockAPI.Handlers
|
|||
|
||||
/// <summary>
|
||||
/// Maps TileIDs to Tile Entity IDs.
|
||||
/// Note: <see cref="Terraria.ID.TileEntityID"/> is empty at the time of writing, but entities are dynamically assigned their ID at initialize time
|
||||
/// Note: <see cref="TileEntityID"/> is empty at the time of writing, but entities are dynamically assigned their ID at initialize time
|
||||
/// which is why we can use the _myEntityId field on each entity type
|
||||
/// </summary>
|
||||
public static Dictionary<int, int> TileEntityIdToTileIdMap = new Dictionary<int, int>
|
||||
|
|
@ -137,24 +179,75 @@ namespace TShockAPI.Handlers
|
|||
// and process them as a tile object
|
||||
if (newTile.Type < TileObjectData._data.Count && TileObjectData._data[newTile.Type] != null)
|
||||
{
|
||||
// Verify that the changes are actually valid conceptually
|
||||
// Many tiles that are never placed or modified using this packet are valid TileObjectData entries, which is the main attack vector for most exploits using this packet
|
||||
if (Main.tile[realX, realY].type == newTile.Type)
|
||||
{
|
||||
switch (newTile.Type)
|
||||
{
|
||||
// Some individual cases might still allow crashing exploits, as the actual framing is not being checked here
|
||||
// Doing so requires hard-coding the individual valid framing values and is a lot of effort
|
||||
case TileID.ProjectilePressurePad:
|
||||
case TileID.WirePipe:
|
||||
case TileID.Traps:
|
||||
case TileID.Candles:
|
||||
case TileID.PeaceCandle:
|
||||
case TileID.WaterCandle:
|
||||
case TileID.PlatinumCandle:
|
||||
case TileID.Firework:
|
||||
case TileID.WaterFountain:
|
||||
case TileID.BloodMoonMonolith:
|
||||
case TileID.VoidMonolith:
|
||||
case TileID.LunarMonolith:
|
||||
case TileID.MusicBoxes:
|
||||
case TileID.ArrowSign:
|
||||
case TileID.PaintedArrowSign:
|
||||
case TileID.Cannon:
|
||||
case TileID.Campfire:
|
||||
case TileID.Plants:
|
||||
case TileID.MinecartTrack:
|
||||
case TileID.ChristmasTree:
|
||||
{
|
||||
// Allowed changes
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Together with Flower Boots and Land Mine destruction, these are the only cases where a tile type is allowed to be modified
|
||||
switch (newTile.Type)
|
||||
{
|
||||
case TileID.LogicSensor:
|
||||
case TileID.FoodPlatter:
|
||||
case TileID.WeaponsRack2:
|
||||
case TileID.ItemFrame:
|
||||
case TileID.HatRack:
|
||||
case TileID.DisplayDoll:
|
||||
case TileID.TeleportationPylon:
|
||||
case TileID.TargetDummy:
|
||||
{
|
||||
// Allowed placements
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data = TileObjectData._data[newTile.Type];
|
||||
NetTile[,] newTiles;
|
||||
int objWidth = data.Width;
|
||||
int objHeight = data.Height;
|
||||
int offsetY = 0;
|
||||
|
||||
if (newTile.Type == TileID.TrapdoorClosed)
|
||||
{
|
||||
// Trapdoors can modify a 2x3 space. When it closes it will have leftover tiles either on top or bottom.
|
||||
// If we don't update these tiles, the trapdoor gets confused and disappears.
|
||||
// So we capture all 6 possible tiles and offset ourselves 1 tile above the closed trapdoor to capture the entire 2x3 area
|
||||
objWidth = 2;
|
||||
objHeight = 3;
|
||||
offsetY = -1;
|
||||
}
|
||||
|
||||
// Ensure the tile object fits inside the rect before processing it
|
||||
if (!DoesTileObjectFitInTileRect(x, y, objWidth, objHeight, width, length, offsetY, processed))
|
||||
if (!DoesTileObjectFitInTileRect(x, y, objWidth, objHeight, width, length, processed))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -165,11 +258,11 @@ namespace TShockAPI.Handlers
|
|||
{
|
||||
for (int j = 0; j < objHeight; j++)
|
||||
{
|
||||
newTiles[i, j] = tiles[x + i, y + j + offsetY];
|
||||
processed[x + i, y + j + offsetY] = true;
|
||||
newTiles[i, j] = tiles[x + i, y + j];
|
||||
processed[x + i, y + j] = true;
|
||||
}
|
||||
}
|
||||
ProcessTileObject(newTile.Type, realX, realY + offsetY, objWidth, objHeight, newTiles, args);
|
||||
ProcessTileObject(newTile.Type, realX, realY, objWidth, objHeight, newTiles, args);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -229,25 +322,41 @@ namespace TShockAPI.Handlers
|
|||
{
|
||||
// Some boots allow growing flowers on grass. This process sends a 1x1 tile rect to grow the flowers
|
||||
// The rect size must be 1 and the player must have an accessory that allows growing flowers in order for this rect to be valid
|
||||
if (rectWidth == 1 && rectLength == 1 && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type)))
|
||||
if (rectWidth == 1 && rectLength == 1 && WorldGen.InWorld(realX, realY + 1) && args.Player.Accessories.Any(a => a != null && FlowerBootItems.Contains(a.type)))
|
||||
{
|
||||
ProcessFlowerBoots(realX, realY, newTile, args);
|
||||
ProcessFlowerBoots(realX, realY, newTile);
|
||||
return;
|
||||
}
|
||||
|
||||
ITile tile = Main.tile[realX, realY];
|
||||
|
||||
if (tile.type == TileID.LandMine && !newTile.Active)
|
||||
// Triggering a single land mine tile
|
||||
if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.LandMine && !newTile.Active)
|
||||
{
|
||||
UpdateServerTileState(tile, newTile, TileDataType.Tile);
|
||||
}
|
||||
|
||||
if (tile.type == TileID.WirePipe)
|
||||
// Hammering a single junction box
|
||||
if (rectWidth == 1 && rectLength == 1 && tile.type == TileID.WirePipe)
|
||||
{
|
||||
UpdateServerTileState(tile, newTile, TileDataType.Tile);
|
||||
}
|
||||
|
||||
ProcessConversionSpreads(Main.tile[realX, realY], newTile);
|
||||
// Mowing a single grass tile: Grass -> GolfGrass OR HallowedGrass -> GolfGrassHallowed
|
||||
if (rectWidth == 1 && rectLength == 1 &&
|
||||
(
|
||||
tile.type == TileID.Grass && newTile.Type == TileID.GolfGrass ||
|
||||
tile.type == TileID.HallowedGrass && newTile.Type == TileID.GolfGrassHallowed
|
||||
))
|
||||
{
|
||||
UpdateServerTileState(tile, newTile, TileDataType.Tile);
|
||||
}
|
||||
|
||||
// Conversion: only sends a 1x1 rect
|
||||
if (rectWidth == 1 && rectLength == 1)
|
||||
{
|
||||
ProcessConversionSpreads(tile, newTile);
|
||||
}
|
||||
|
||||
// All other single tile updates should not be processed.
|
||||
}
|
||||
|
|
@ -258,24 +367,22 @@ namespace TShockAPI.Handlers
|
|||
/// <param name="realX">The tile x position of the tile rect packet - this is where the flowers are intending to grow</param>
|
||||
/// <param name="realY">The tile y position of the tile rect packet - this is where the flowers are intending to grow</param>
|
||||
/// <param name="newTile">The NetTile containing information about the flowers that are being grown</param>
|
||||
/// <param name="args">SendTileRectEventArgs containing event information</param>
|
||||
internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile, GetDataHandlers.SendTileRectEventArgs args)
|
||||
internal void ProcessFlowerBoots(int realX, int realY, NetTile newTile)
|
||||
{
|
||||
// We need to get the tile below the tile rect to determine what grass types are allowed
|
||||
if (!WorldGen.InWorld(realX, realY + 1))
|
||||
ITile tile = Main.tile[realX, realY];
|
||||
// Ensure that:
|
||||
// - the placed plant is valid for the grass below
|
||||
// - the target tile is empty
|
||||
// - and the placed plant has valid framing (style * 18 = FrameX)
|
||||
if (
|
||||
FlowerBootPlantToGrassMap.TryGetValue(newTile.Type, out HashSet<ushort> grassTiles) &&
|
||||
!tile.active() &&
|
||||
grassTiles.Contains(Main.tile[realX, realY + 1].type) &&
|
||||
FlowerBootPlantToStyleMap[newTile.Type].Contains((ushort)(newTile.FrameX / 18))
|
||||
)
|
||||
{
|
||||
// If the tile below the tile rect isn't valid, we return here and don't update the server tile state
|
||||
return;
|
||||
UpdateServerTileState(tile, newTile, TileDataType.Tile);
|
||||
}
|
||||
|
||||
ITile tile = Main.tile[realX, realY + 1];
|
||||
if (!GrassToPlantMap.TryGetValue(tile.type, out List<ushort> plantTiles) && !plantTiles.Contains(newTile.Type))
|
||||
{
|
||||
// If the tile below the tile rect isn't a valid plant tile (eg grass) then we don't update the server tile state
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateServerTileState(Main.tile[realX, realY], newTile, TileDataType.Tile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -444,15 +551,9 @@ namespace TShockAPI.Handlers
|
|||
return true;
|
||||
}
|
||||
|
||||
var rectSize = args.Width * args.Length;
|
||||
if (rectSize > TShock.Config.Settings.TileRectangleSizeThreshold)
|
||||
if (args.Width > 4 || args.Length > 4) // as of 1.4.3.6 this is the biggest size the client will send in any case
|
||||
{
|
||||
TShock.Log.ConsoleDebug("Bouncer / SendTileRect rejected from non-vanilla tilemod from {0}", args.Player.Name);
|
||||
if (TShock.Config.Settings.KickOnTileRectangleSizeThresholdBroken)
|
||||
{
|
||||
args.Player.Kick("Unexpected tile threshold reached");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -482,24 +583,16 @@ namespace TShockAPI.Handlers
|
|||
/// <param name="height"></param>
|
||||
/// <param name="rectWidth"></param>
|
||||
/// <param name="rectLength"></param>
|
||||
/// <param name="offsetY"></param>
|
||||
/// <param name="processed"></param>
|
||||
/// <returns></returns>
|
||||
static bool DoesTileObjectFitInTileRect(int x, int y, int width, int height, short rectWidth, short rectLength, int offsetY, bool[,] processed)
|
||||
static bool DoesTileObjectFitInTileRect(int x, int y, int width, int height, short rectWidth, short rectLength, bool[,] processed)
|
||||
{
|
||||
// If the starting y position of this tile object is at (x, 0) and the y offset is negative, we'll be accessing tiles outside the rect
|
||||
if (y + offsetY < 0)
|
||||
{
|
||||
TShock.Log.ConsoleDebug("Bouncer / SendTileRectHandler - rejected tile object because object dimensions fall outside the tile rect (negative y value)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x + width > rectWidth || y + height + offsetY > rectLength)
|
||||
if (x + width > rectWidth || y + height > rectLength)
|
||||
{
|
||||
// This is ugly, but we want to mark all these tiles as processed so that we're not hitting this check multiple times for one dodgy tile object
|
||||
for (int i = x; i < rectWidth; i++)
|
||||
{
|
||||
for (int j = Math.Max(0, y + offsetY); j < rectLength; j++) // This is also ugly. Using Math.Max to make sure y + offsetY >= 0
|
||||
for (int j = y; j < rectLength; j++)
|
||||
{
|
||||
processed[i, j] = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,6 +272,32 @@ namespace TShockAPI.Hooks
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs used for the <see cref="PlayerHooks.PlayerHasBuildPermission"/> event.
|
||||
/// </summary>
|
||||
public class PlayerHasBuildPermissionEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The player who fired the event.
|
||||
/// </summary>
|
||||
public TSPlayer Player { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The X coordinate being checked.
|
||||
/// </summary>
|
||||
public int X { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Y coordinate being checked.
|
||||
/// </summary>
|
||||
public int Y { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="PermissionHookResult"/> of the hook.
|
||||
/// </summary>
|
||||
public PermissionHookResult Result { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A collection of events fired by players that can be hooked to.
|
||||
/// </summary>
|
||||
|
|
@ -368,6 +394,16 @@ namespace TShockAPI.Hooks
|
|||
/// </summary>
|
||||
public static event PlayerTilebanPermissionD PlayerTilebanPermission;
|
||||
|
||||
/// <summary>
|
||||
/// The delegate of the <see cref="PlayerHasBuildPermission"/> event.
|
||||
/// </summary>
|
||||
/// <param name="e">The EventArgs for this event.</param>
|
||||
public delegate void PlayerHasBuildPermissionD(PlayerHasBuildPermissionEventArgs e);
|
||||
/// <summary>
|
||||
/// Fired by players every time a build permission check occurs.
|
||||
/// </summary>
|
||||
public static event PlayerHasBuildPermissionD PlayerHasBuildPermission;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fires the <see cref="PlayerPostLogin"/> event.
|
||||
|
|
@ -525,6 +561,22 @@ namespace TShockAPI.Hooks
|
|||
return args.Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires the <see cref="PlayerHasBuildPermission"/> event.
|
||||
/// </summary>
|
||||
/// <param name="player">The player firing the event.</param>
|
||||
/// <returns>Event result if the event has been handled, otherwise <see cref="PermissionHookResult.Unhandled"/>.</returns>
|
||||
public static PermissionHookResult OnPlayerHasBuildPermission(TSPlayer player, int x, int y)
|
||||
{
|
||||
if (PlayerHasBuildPermission == null)
|
||||
return PermissionHookResult.Unhandled;
|
||||
|
||||
var args = new PlayerHasBuildPermissionEventArgs {Player = player, X = x, Y = y};
|
||||
PlayerHasBuildPermission(args);
|
||||
|
||||
return args.Result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -650,6 +650,12 @@ namespace TShockAPI
|
|||
/// <returns>True if the player can build at the given point from build, spawn, and region protection.</returns>
|
||||
public bool HasBuildPermission(int x, int y, bool shouldWarnPlayer = true)
|
||||
{
|
||||
PermissionHookResult hookResult = PlayerHooks.OnPlayerHasBuildPermission(this, x, y);
|
||||
if (hookResult != PermissionHookResult.Unhandled)
|
||||
{
|
||||
return hookResult == PermissionHookResult.Granted;
|
||||
}
|
||||
|
||||
BuildPermissionFailPoint failure = BuildPermissionFailPoint.GeneralBuild;
|
||||
// The goal is to short circuit on easy stuff as much as possible.
|
||||
// Don't compute permissions unless needed, and don't compute taxing stuff unless needed.
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ namespace TShockAPI
|
|||
/// <summary>VersionNum - The version number the TerrariaAPI will return back to the API. We just use the Assembly info.</summary>
|
||||
public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
/// <summary>VersionCodename - The version codename is displayed when the server starts. Inspired by software codenames conventions.</summary>
|
||||
public static readonly string VersionCodename = "Volodymyr Oleksandrovych Zelenskyy";
|
||||
public static readonly string VersionCodename = "Audaciously Artistic";
|
||||
|
||||
/// <summary>SavePath - This is the path TShock saves its data in. This path is relative to the TerrariaServer.exe (not in ServerPlugins).</summary>
|
||||
public static string SavePath = "tshock";
|
||||
|
|
@ -437,16 +437,15 @@ namespace TShockAPI
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Log is not null)
|
||||
// handle if Log was not initialised
|
||||
void SafeError(string message)
|
||||
{
|
||||
Log.ConsoleError("Fatal Startup Exception");
|
||||
Log.ConsoleError(ex.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Fatal Startup Exception");
|
||||
Console.WriteLine(ex.ToString());
|
||||
}
|
||||
if(Log is not null) Log.ConsoleError(message);
|
||||
else Console.WriteLine(message);
|
||||
};
|
||||
SafeError("TShock encountered a problem from which it cannot recover. The following message may help diagnose the problem.");
|
||||
SafeError("Until the problem is resolved, TShock will not be able to start (and will crash on startup).");
|
||||
SafeError(ex.ToString());
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -828,6 +827,28 @@ namespace TShockAPI
|
|||
}
|
||||
})
|
||||
|
||||
.AddFlag("-worldevil", (value) =>
|
||||
{
|
||||
|
||||
int worldEvil;
|
||||
switch (value.ToLower())
|
||||
{
|
||||
case "random":
|
||||
worldEvil = -1;
|
||||
break;
|
||||
case "corrupt":
|
||||
worldEvil = 0;
|
||||
break;
|
||||
case "crimson":
|
||||
worldEvil = 1;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid value given for command line argument \"-worldevil\".");
|
||||
}
|
||||
|
||||
ServerApi.LogWriter.PluginWriteLine(this, String.Format("New worlds will be generated with the {0} world evil type!", value), TraceLevel.Verbose);
|
||||
WorldGen.WorldGenParam_Evil = worldEvil;
|
||||
})
|
||||
|
||||
//Flags without arguments
|
||||
.AddFlag("-logclear", () => LogClear = true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue