From ffafd8023b0c699e5c7ee94b140059bdb1e816d9 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Thu, 10 Dec 2015 20:28:38 +1100 Subject: [PATCH] Add background generating of singleplayer maps, show progress of generation. --- ClassicalSharp/2D/Screens/LoadingMapScreen.cs | 20 ++- ClassicalSharp/ClassicalSharp.csproj | 2 + .../Generator/FlatGrassGenerator.cs | 41 ++++++ ClassicalSharp/Generator/IMapGenerator.cs | 40 ++++++ ClassicalSharp/Generator/NotchyGenerator.cs | 122 +++++++++++------ ClassicalSharp/Singleplayer/Physics.cs | 5 +- ClassicalSharp/Singleplayer/Server.cs | 61 ++++++--- Launcher2/Launcher2.csproj | 1 - Launcher2/WebService/WebUtility.cs | 128 ------------------ 9 files changed, 228 insertions(+), 192 deletions(-) create mode 100644 ClassicalSharp/Generator/FlatGrassGenerator.cs create mode 100644 ClassicalSharp/Generator/IMapGenerator.cs delete mode 100644 Launcher2/WebService/WebUtility.cs diff --git a/ClassicalSharp/2D/Screens/LoadingMapScreen.cs b/ClassicalSharp/2D/Screens/LoadingMapScreen.cs index a4e1f232b..bf931942c 100644 --- a/ClassicalSharp/2D/Screens/LoadingMapScreen.cs +++ b/ClassicalSharp/2D/Screens/LoadingMapScreen.cs @@ -32,8 +32,8 @@ namespace ClassicalSharp { public override void Init() { graphicsApi.Fog = false; - titleWidget = TextWidget.Create( game, 0, 30, serverName, Anchor.Centre, Anchor.LeftOrTop, font ); - messageWidget = TextWidget.Create( game, 0, 60, serverMotd, Anchor.Centre, Anchor.LeftOrTop, font ); + SetTitle( serverName ); + SetMessage( serverMotd ); progX = game.Width / 2f - progWidth / 2f; Size size = new Size( progWidth, progHeight ); @@ -46,6 +46,22 @@ namespace ClassicalSharp { } game.Events.MapLoading += MapLoading; } + + public void SetTitle( string title ) { + if( titleWidget != null ) + titleWidget.Dispose(); + titleWidget = TextWidget.Create( game, 0, 30, title, Anchor.Centre, Anchor.LeftOrTop, font ); + } + + public void SetMessage( string message ) { + if( messageWidget != null ) + messageWidget.Dispose(); + messageWidget = TextWidget.Create( game, 0, 60, message, Anchor.Centre, Anchor.LeftOrTop, font ); + } + + public void SetProgress( float progress ) { + this.progress = (int)(progress * 100); + } void MapLoading( object sender, MapLoadingEventArgs e ) { progress = e.Progress; diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index ecb063d20..dde097b16 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -142,6 +142,8 @@ + + diff --git a/ClassicalSharp/Generator/FlatGrassGenerator.cs b/ClassicalSharp/Generator/FlatGrassGenerator.cs new file mode 100644 index 000000000..e5d758142 --- /dev/null +++ b/ClassicalSharp/Generator/FlatGrassGenerator.cs @@ -0,0 +1,41 @@ +using System; +using OpenTK; + +namespace ClassicalSharp.Generator { + + public unsafe sealed class FlatGrassGenerator : IMapGenerator { + + public override string GeneratorName { + get { return "Flatgrass generator"; } + } + + int width, length; + public override byte[] Generate( int width, int height, int length, int seed ) { + byte[] map = new byte[width * height * length]; + this.width = width; + this.length = length; + + fixed( byte* ptr = map ) { + CurrentState = "Setting dirt blocks"; + MapSet( ptr, 0, height / 2 - 2, (byte)Block.Dirt ); + + CurrentState = "Setting grass blocks"; + MapSet( ptr, height / 2 - 1, height / 2 - 1, (byte)Block.Grass ); + } + return map; + } + + unsafe void MapSet( byte* ptr, int yStart, int yEnd, byte block ) { + int startIndex = yStart * length * width; + int endIndex = (yEnd * length + (length - 1)) * width + (width - 1); + int count = (endIndex - startIndex) + 1, offset = 0; + + while( offset < count ) { + int bytes = Math.Min( count - offset, width * length ); + MemUtils.memset( (IntPtr)ptr, block, startIndex + offset, bytes ); + offset += bytes; + CurrentProgress = (float)offset / count; + } + } + } +} diff --git a/ClassicalSharp/Generator/IMapGenerator.cs b/ClassicalSharp/Generator/IMapGenerator.cs new file mode 100644 index 000000000..5bc225eb2 --- /dev/null +++ b/ClassicalSharp/Generator/IMapGenerator.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading; +using ClassicalSharp.Singleplayer; + +namespace ClassicalSharp.Generator { + + public abstract class IMapGenerator { + + public abstract string GeneratorName { get; } + + public abstract byte[] Generate( int width, int height, int length, int seed ); + + public string CurrentState; + + public float CurrentProgress; + + public bool Done = false; + + public int Width, Height, Length; + + public void GenerateAsync( Game game, int width, int height, int length, int seed ) { + Width = width; Height = height; Length = length; + Thread thread = new Thread( + () => { + SinglePlayerServer server = (SinglePlayerServer)game.Network; + try { + server.generatedMap = Generate( width, height, length, seed ); + } catch( Exception ex ) { + ErrorHandler.LogError( "IMapGenerator.RunAsync", ex ); + } + Done = true; + } + ); + + thread.IsBackground = true; + thread.Name = "IMapGenerator.RunAsync"; + thread.Start(); + } + } +} diff --git a/ClassicalSharp/Generator/NotchyGenerator.cs b/ClassicalSharp/Generator/NotchyGenerator.cs index c601d1bec..89331042a 100644 --- a/ClassicalSharp/Generator/NotchyGenerator.cs +++ b/ClassicalSharp/Generator/NotchyGenerator.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; namespace ClassicalSharp.Generator { // TODO: figure out how noise functions differ, probably based on rnd. - public sealed partial class NotchyGenerator { + public sealed partial class NotchyGenerator : IMapGenerator { int width, height, length; int waterLevel, oneY; @@ -16,14 +16,18 @@ namespace ClassicalSharp.Generator { short[] heightmap; Random rnd; - public byte[] GenerateMap( int width, int height, int length ) { + public override string GeneratorName { + get { return "Vanilla classic generator"; } + } + + public override byte[] Generate( int width, int height, int length, int seed ) { this.width = width; this.height = height; this.length = length; oneY = width * length; waterLevel = height / 2; blocks = new byte[width * height * length]; - rnd = new Random( 0x5553200 ); // 0x5553201 is flatter. + rnd = new Random( seed ); CreateHeightmap(); CreateStrata(); @@ -51,50 +55,57 @@ namespace ClassicalSharp.Generator { Noise n3 = new OctaveNoise( 6, rnd ); int index = 0; short[] hMap = new short[width * length]; + CurrentState = "Building heightmap"; - for( int z = 0; z < length; z++ ) - for( int x = 0; x < width; x++ ) - { - double hLow = n1.Compute( x * 1.3f, z * 1.3f ) / 6 - 4; - double hHigh = n2.Compute( x * 1.3f, z * 1.3f ) / 5 + 6; - - double height = n3.Compute( x, z ) > 0 ? hLow : Math.Max( hLow, hHigh ); - height *= 0.5; - if( height < 0 ) height *= 0.8f; - hMap[index++] = (short)(height + waterLevel); + for( int z = 0; z < length; z++ ) { + CurrentProgress = (float)z / length; + for( int x = 0; x < width; x++ ) { + double hLow = n1.Compute( x * 1.3f, z * 1.3f ) / 6 - 4; + double hHigh = n2.Compute( x * 1.3f, z * 1.3f ) / 5 + 6; + + double height = n3.Compute( x, z ) > 0 ? hLow : Math.Max( hLow, hHigh ); + height *= 0.5; + if( height < 0 ) height *= 0.8f; + hMap[index++] = (short)(height + waterLevel); + } } heightmap = hMap; } void CreateStrata() { Noise n = new OctaveNoise( 8, rnd ); - //Noise n = new ImprovedNoise( rnd ); + CurrentState = "Creating strata"; int hMapIndex = 0; - for( int z = 0; z < length; z++ ) - for( int x = 0; x < width; x++ ) - { - int dirtThickness = (int)(n.Compute( x, z ) / 24 - 4); - int dirtHeight = heightmap[hMapIndex++]; + for( int z = 0; z < length; z++ ) { + CurrentProgress = (float)z / length; + for( int x = 0; x < width; x++ ) { + int dirtThickness = (int)(n.Compute( x, z ) / 24 - 4); + int dirtHeight = heightmap[hMapIndex++]; - int stoneHeight = dirtHeight + dirtThickness; - int mapIndex = z * width + x; - - blocks[mapIndex] = (byte)Block.Lava; - mapIndex += oneY; - for( int y = 1; y < height; y++ ) { - byte type = 0; - if( y <= stoneHeight ) type = (byte)Block.Stone; - else if( y <= dirtHeight ) type = (byte)Block.Dirt; - blocks[mapIndex] = type; + int stoneHeight = dirtHeight + dirtThickness; + int mapIndex = z * width + x; + + blocks[mapIndex] = (byte)Block.Lava; mapIndex += oneY; + for( int y = 1; y < height; y++ ) { + byte type = 0; + if( y <= stoneHeight ) type = (byte)Block.Stone; + else if( y <= dirtHeight ) type = (byte)Block.Dirt; + + blocks[mapIndex] = type; + mapIndex += oneY; + } } } } void CarveCaves() { int cavesCount = blocks.Length / 8192; + CurrentState = "Carving caves"; + for( int i = 0; i < cavesCount; i++ ) { + CurrentProgress = (float)i / cavesCount; double caveX = rnd.Next( width ); double caveY = rnd.Next( height ); double caveZ = rnd.Next( length ); @@ -128,7 +139,10 @@ namespace ClassicalSharp.Generator { void CarveOreVeins( float abundance, byte block ) { int numVeins = (int)(blocks.Length * abundance / 16384); + CurrentState = "Carving " + (Block)block; + for( int i = 0; i < numVeins; i++ ) { + CurrentProgress = (float)i / numVeins; double veinX = rnd.Next( width ); double veinY = rnd.Next( height ); double veinZ = rnd.Next( length ); @@ -157,7 +171,10 @@ namespace ClassicalSharp.Generator { int waterY = waterLevel - 1; int index1 = (waterY * length + 0) * width + 0; int index2 = (waterY * length + (length - 1)) * width + 0; + CurrentState = "Flooding edge water"; + for( int x = 0; x < width; x++ ) { + CurrentProgress = 0 + ((float)x / width) * 0.5f; FloodFill( index1, (byte)Block.Water ); FloodFill( index2, (byte)Block.Water ); index1++; index2++; @@ -166,6 +183,7 @@ namespace ClassicalSharp.Generator { index1 = (waterY * length + 0) * width + 0; index2 = (waterY * length + 0) * width + (width - 1); for( int z = 0; z < length; z++ ) { + CurrentProgress = 0.5f + ((float)z / length) * 0.5f; FloodFill( index1, (byte)Block.Water ); FloodFill( index2, (byte)Block.Water ); index1 += width; index2 += width; @@ -174,7 +192,10 @@ namespace ClassicalSharp.Generator { void FloodFillWater() { int numSources = width * length / 800; + CurrentState = "Flooding water"; + for( int i = 0; i < numSources; i++ ) { + CurrentProgress = (float)i / numSources; int x = rnd.Next( width ), z = rnd.Next( length ); int y = waterLevel - rnd.Next( 1, 3 ); FloodFill( (y * length + z) * width + x, (byte)Block.Water ); @@ -183,7 +204,10 @@ namespace ClassicalSharp.Generator { void FloodFillLava() { int numSources = width * length / 20000; + CurrentState = "Flooding lava"; + for( int i = 0; i < numSources; i++ ) { + CurrentProgress = (float)i / numSources; int x = rnd.Next( width ), z = rnd.Next( length ); int y = (int)((waterLevel - 3) * rnd.NextDouble() * rnd.NextDouble()); FloodFill( (y * length + z) * width + x, (byte)Block.Lava ); @@ -192,31 +216,35 @@ namespace ClassicalSharp.Generator { void CreateSurfaceLayer() { Noise n1 = new OctaveNoise( 8, rnd ), n2 = new OctaveNoise( 8, rnd ); + CurrentState = "Creating surface"; // TODO: update heightmap int hMapIndex = 0; - for( int z = 0; z < length; z++ ) - for( int x = 0; x < width; x++ ) - { - bool sand = n1.Compute( x, z ) > 8; - bool gravel = n2.Compute( x, z ) > 12; - int y = heightmap[hMapIndex++]; - - int index = (y * length + z) * width + x; - byte blockAbove = y >= (height - 1) ? (byte)0 : blocks[index + oneY]; - if( blockAbove == (byte)Block.Water && gravel ) { - blocks[index] = (byte)Block.Gravel; - } else if( blockAbove == 0 ) { - blocks[index] = (y <= waterLevel && sand) ? - (byte)Block.Sand : (byte)Block.Grass; + for( int z = 0; z < length; z++ ) { + CurrentProgress = (float)z / length; + for( int x = 0; x < width; x++ ) { + bool sand = n1.Compute( x, z ) > 8; + bool gravel = n2.Compute( x, z ) > 12; + int y = heightmap[hMapIndex++]; + + int index = (y * length + z) * width + x; + byte blockAbove = y >= (height - 1) ? (byte)0 : blocks[index + oneY]; + if( blockAbove == (byte)Block.Water && gravel ) { + blocks[index] = (byte)Block.Gravel; + } else if( blockAbove == 0 ) { + blocks[index] = (y <= waterLevel && sand) ? + (byte)Block.Sand : (byte)Block.Grass; + } } - } } void PlantFlowers() { int numPatches = width * length / 3000; + CurrentState = "Planting flowers"; + for( int i = 0; i < numPatches; i++ ) { + CurrentProgress = (float)i / numPatches; byte type = (byte)((byte)Block.Dandelion + rnd.Next( 0, 2 ) ); int patchX = rnd.Next( width ), patchZ = rnd.Next( length ); for( int j = 0; j < 10; j++ ) { @@ -238,7 +266,10 @@ namespace ClassicalSharp.Generator { void PlantMushrooms() { int numPatches = width * length / 2000; + CurrentState = "Planting mushrooms"; + for( int i = 0; i < numPatches; i++ ) { + CurrentProgress = (float)i / numPatches; byte type = (byte)((byte)Block.BrownMushroom + rnd.Next( 0, 2 ) ); int patchX = rnd.Next( width ); int patchY = rnd.Next( height ); @@ -265,7 +296,10 @@ namespace ClassicalSharp.Generator { void PlantTrees() { int numPatches = width * length / 4000; + CurrentState = "Planting tress"; + for( int i = 0; i < numPatches; i++ ) { + CurrentProgress = (float)i / numPatches; int patchX = rnd.Next( width ), patchZ = rnd.Next( length ); for( int j = 0; j < 20; j++ ) { diff --git a/ClassicalSharp/Singleplayer/Physics.cs b/ClassicalSharp/Singleplayer/Physics.cs index ad80ee08d..de6754f9a 100644 --- a/ClassicalSharp/Singleplayer/Physics.cs +++ b/ClassicalSharp/Singleplayer/Physics.cs @@ -113,11 +113,14 @@ namespace ClassicalSharp.Singleplayer { #region General void TickRandomBlocks() { + int xMax = width - 1, yMax = height - 1, zMax = length - 1; for( int y = 0; y < height; y += 16 ) { for( int z = 0; z < length; z += 16 ) { for( int x = 0; x < width; x += 16 ) { int lo = (y * length + z) * width + x; - int hi = ((y + 15) * length + (z + 15)) * width + (x + 15 ); + int hi = (Math.Min( yMax, y + 15 ) * length + Math.Min( zMax, z + 15 )) + * width + Math.Min( xMax, x + 15 ); + HandleBlock( rnd.Next( lo, hi ) ); HandleBlock( rnd.Next( lo, hi ) ); HandleBlock( rnd.Next( lo, hi ) ); diff --git a/ClassicalSharp/Singleplayer/Server.cs b/ClassicalSharp/Singleplayer/Server.cs index d581b0acc..81113bba0 100644 --- a/ClassicalSharp/Singleplayer/Server.cs +++ b/ClassicalSharp/Singleplayer/Server.cs @@ -1,6 +1,7 @@ //#define TEST_VANILLA using System; using System.Net; +using ClassicalSharp.Generator; using OpenTK; using OpenTK.Input; @@ -47,7 +48,7 @@ namespace ClassicalSharp.Singleplayer { } public override void SendSetBlock( int x, int y, int z, bool place, byte block ) { - if( place ) + if( place ) physics.OnBlockPlaced( x, y, z, block ); } @@ -58,11 +59,29 @@ namespace ClassicalSharp.Singleplayer { physics.Dispose(); } + string lastState; public override void Tick( double delta ) { if( Disconnected ) return; physics.Tick(); + + if( generator == null ) + return; + if( generator.Done ) { + EndGeneration(); + } else { + string state = generator.CurrentState; + float progress = generator.CurrentProgress; + LoadingMapScreen screen = ((LoadingMapScreen)game.GetActiveScreen); + + screen.SetProgress( progress ); + if( state != lastState ) { + lastState = state; + screen.SetMessage( state ); + } + } } + IMapGenerator generator; internal void NewMap() { ServerName = "Single player"; ServerMotd = "Generating a map.."; @@ -72,26 +91,36 @@ namespace ClassicalSharp.Singleplayer { game.SetNewScreen( new LoadingMapScreen( game, ServerName, ServerMotd ) ); } - internal unsafe void MakeMap( int width, int height, int length ) { - #if TEST_VANILLA - byte[] map = new ClassicalSharp.Generator.NotchyGenerator() - .GenerateMap( width, height, length ); - #else - byte[] map = new byte[width * height * length]; - fixed( byte* ptr = map ) { - MapSet( width, length, ptr, 0, height / 2 - 2, (byte)Block.Dirt ); - MapSet( width, length, ptr, height / 2 - 1, height / 2 - 1, (byte)Block.Grass ); - } - #endif - - game.Map.SetData( map, width, height, length ); - game.Events.RaiseOnNewMapLoaded(); + void EndGeneration() { game.SetNewScreen( null ); - ResetPlayerPosition(); + if( generatedMap == null ) { + game.Chat.Add( "&cFailed to generate the map." ); + } else { + IMapGenerator gen = generator; + game.Map.SetData( generatedMap, generator.Width, + generator.Height, generator.Length ); + generatedMap = null; + + game.Events.RaiseOnNewMapLoaded(); + ResetPlayerPosition(); + } + + generator = null; game.Chat.Add( "&ePlaying single player", CpeMessage.Status1 ); GC.Collect(); } + public byte[] generatedMap; + internal void MakeMap( int width, int height, int length ) { + #if TEST_VANILLA + generator = new NotchyGenerator(); + generator.GenerateAsync( game, width, height, length, 0x5553200 ); + #else + generator = new FlatGrassGenerator(); + generator.GenerateAsync( game, width, height, length, 0 ); + #endif + } + unsafe void MapSet( int width, int length, byte* ptr, int yStart, int yEnd, byte block ) { int startIndex = yStart * length * width; int endIndex = ( yEnd * length + (length - 1) ) * width + (width - 1); diff --git a/Launcher2/Launcher2.csproj b/Launcher2/Launcher2.csproj index a215b6f48..6c984cf3e 100644 --- a/Launcher2/Launcher2.csproj +++ b/Launcher2/Launcher2.csproj @@ -91,7 +91,6 @@ - diff --git a/Launcher2/WebService/WebUtility.cs b/Launcher2/WebService/WebUtility.cs deleted file mode 100644 index 74519f097..000000000 --- a/Launcher2/WebService/WebUtility.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text; - -namespace Launcher2 { - - public static class WebUtility { - - static Dictionary _lookupTable = new Dictionary( 253, StringComparer.Ordinal ) { - { "quot", '\x0022' }, { "amp", '\x0026' }, { "apos", '\x0027' }, { "lt", '\x003C' }, - { "gt", '\x003E' }, { "nbsp", '\x00A0' }, { "iexcl", '\x00A1' }, { "cent", '\x00A2' }, - { "pound", '\x00A3' }, { "curren", '\x00A4' }, { "yen", '\x00A5' }, { "brvbar", '\x00A6' }, - { "sect", '\x00A7' }, { "uml", '\x00A8' }, { "copy", '\x00A9' }, { "ordf", '\x00AA' }, - { "laquo", '\x00AB' }, { "not", '\x00AC' }, { "shy", '\x00AD' }, { "reg", '\x00AE' }, - { "macr", '\x00AF' }, { "deg", '\x00B0' }, { "plusmn", '\x00B1' }, { "sup2", '\x00B2' }, - { "sup3", '\x00B3' }, { "acute", '\x00B4' }, { "micro", '\x00B5' }, { "para", '\x00B6' }, - { "middot", '\x00B7' }, { "cedil", '\x00B8' }, { "sup1", '\x00B9' }, { "ordm", '\x00BA' }, - { "raquo", '\x00BB' }, { "frac14", '\x00BC' }, { "frac12", '\x00BD' }, { "frac34", '\x00BE' }, - { "iquest", '\x00BF' }, { "Agrave", '\x00C0' }, { "Aacute", '\x00C1' }, { "Acirc", '\x00C2' }, - { "Atilde", '\x00C3' }, { "Auml", '\x00C4' }, { "Aring", '\x00C5' }, { "AElig", '\x00C6' }, - { "Ccedil", '\x00C7' }, { "Egrave", '\x00C8' }, { "Eacute", '\x00C9' }, { "Ecirc", '\x00CA' }, - { "Euml", '\x00CB' }, { "Igrave", '\x00CC' }, { "Iacute", '\x00CD' }, { "Icirc", '\x00CE' }, - { "Iuml", '\x00CF' }, { "ETH", '\x00D0' }, { "Ntilde", '\x00D1' }, { "Ograve", '\x00D2' }, - { "Oacute", '\x00D3' }, { "Ocirc", '\x00D4' }, { "Otilde", '\x00D5' }, { "Ouml", '\x00D6' }, - { "times", '\x00D7' }, { "Oslash", '\x00D8' }, { "Ugrave", '\x00D9' }, { "Uacute", '\x00DA' }, - { "Ucirc", '\x00DB' }, { "Uuml", '\x00DC' }, { "Yacute", '\x00DD' }, { "THORN", '\x00DE' }, - { "szlig", '\x00DF' }, { "agrave", '\x00E0' }, { "aacute", '\x00E1' }, { "acirc", '\x00E2' }, - { "atilde", '\x00E3' }, { "auml", '\x00E4' }, { "aring", '\x00E5' }, { "aelig", '\x00E6' }, - { "ccedil", '\x00E7' }, { "egrave", '\x00E8' }, { "eacute", '\x00E9' }, { "ecirc", '\x00EA' }, - { "euml", '\x00EB' }, { "igrave", '\x00EC' }, { "iacute", '\x00ED' }, { "icirc", '\x00EE' }, - { "iuml", '\x00EF' }, { "eth", '\x00F0' }, { "ntilde", '\x00F1' }, { "ograve", '\x00F2' }, - { "oacute", '\x00F3' }, { "ocirc", '\x00F4' }, { "otilde", '\x00F5' }, { "ouml", '\x00F6' }, - { "divide", '\x00F7' }, { "oslash", '\x00F8' }, { "ugrave", '\x00F9' }, { "uacute", '\x00FA' }, - { "ucirc", '\x00FB' }, { "uuml", '\x00FC' }, { "yacute", '\x00FD' }, { "thorn", '\x00FE' }, - { "yuml", '\x00FF' }, { "OElig", '\x0152' }, { "oelig", '\x0153' }, { "Scaron", '\x0160' }, - { "scaron", '\x0161' }, { "Yuml", '\x0178' }, { "fnof", '\x0192' }, { "circ", '\x02C6' }, - { "tilde", '\x02DC' }, { "Alpha", '\x0391' }, { "Beta", '\x0392' }, { "Gamma", '\x0393' }, - { "Delta", '\x0394' }, { "Epsilon", '\x0395' }, { "Zeta", '\x0396' }, { "Eta", '\x0397' }, - { "Theta", '\x0398' }, { "Iota", '\x0399' }, { "Kappa", '\x039A' }, { "Lambda", '\x039B' }, - { "Mu", '\x039C' }, { "Nu", '\x039D' }, { "Xi", '\x039E' }, { "Omicron", '\x039F' }, - { "Pi", '\x03A0' }, { "Rho", '\x03A1' }, { "Sigma", '\x03A3' }, { "Tau", '\x03A4' }, - { "Upsilon", '\x03A5' }, { "Phi", '\x03A6' }, { "Chi", '\x03A7' }, { "Psi", '\x03A8' }, - { "Omega", '\x03A9' }, { "alpha", '\x03B1' }, { "beta", '\x03B2' }, { "gamma", '\x03B3' }, - { "delta", '\x03B4' }, { "epsilon", '\x03B5' }, { "zeta", '\x03B6' }, { "eta", '\x03B7' }, - { "theta", '\x03B8' }, { "iota", '\x03B9' }, { "kappa", '\x03BA' }, { "lambda", '\x03BB' }, - { "mu", '\x03BC' }, { "nu", '\x03BD' }, { "xi", '\x03BE' }, { "omicron", '\x03BF' }, - { "pi", '\x03C0' }, { "rho", '\x03C1' }, { "sigmaf", '\x03C2' }, { "sigma", '\x03C3' }, - { "tau", '\x03C4' }, { "upsilon", '\x03C5' }, { "phi", '\x03C6' }, { "chi", '\x03C7' }, - { "psi", '\x03C8' }, { "omega", '\x03C9' }, { "thetasym", '\x03D1' }, { "upsih", '\x03D2' }, - { "piv", '\x03D6' }, { "ensp", '\x2002' }, { "emsp", '\x2003' }, { "thinsp", '\x2009' }, - { "zwnj", '\x200C' }, { "zwj", '\x200D' }, { "lrm", '\x200E' }, { "rlm", '\x200F' }, - { "ndash", '\x2013' }, { "mdash", '\x2014' }, { "lsquo", '\x2018' }, { "rsquo", '\x2019' }, - { "sbquo", '\x201A' }, { "ldquo", '\x201C' }, { "rdquo", '\x201D' }, { "bdquo", '\x201E' }, - { "dagger", '\x2020' }, { "Dagger", '\x2021' }, { "bull", '\x2022' }, { "hellip", '\x2026' }, - { "permil", '\x2030' }, { "prime", '\x2032' }, { "Prime", '\x2033' }, { "lsaquo", '\x2039' }, - { "rsaquo", '\x203A' }, { "oline", '\x203E' }, { "frasl", '\x2044' }, { "euro", '\x20AC' }, - { "image", '\x2111' }, { "weierp", '\x2118' }, { "real", '\x211C' }, { "trade", '\x2122' }, - { "alefsym", '\x2135' }, { "larr", '\x2190' }, { "uarr", '\x2191' }, { "rarr", '\x2192' }, - { "darr", '\x2193' }, { "harr", '\x2194' }, { "crarr", '\x21B5' }, { "lArr", '\x21D0' }, - { "uArr", '\x21D1' }, { "rArr", '\x21D2' }, { "dArr", '\x21D3' }, { "hArr", '\x21D4' }, - { "forall", '\x2200' }, { "part", '\x2202' }, { "exist", '\x2203' }, { "empty", '\x2205' }, - { "nabla", '\x2207' }, { "isin", '\x2208' }, { "notin", '\x2209' }, { "ni", '\x220B' }, - { "prod", '\x220F' }, { "sum", '\x2211' }, { "minus", '\x2212' }, { "lowast", '\x2217' }, - { "radic", '\x221A' }, { "prop", '\x221D' }, { "infin", '\x221E' }, { "ang", '\x2220' }, - { "and", '\x2227' }, { "or", '\x2228' }, { "cap", '\x2229' }, { "cup", '\x222A' }, - { "int", '\x222B' }, { "there4", '\x2234' }, { "sim", '\x223C' }, { "cong", '\x2245' }, - { "asymp", '\x2248' }, { "ne", '\x2260' }, { "equiv", '\x2261' }, { "le", '\x2264' }, - { "ge", '\x2265' }, { "sub", '\x2282' }, { "sup", '\x2283' }, { "nsub", '\x2284' }, - { "sube", '\x2286' }, { "supe", '\x2287' }, { "oplus", '\x2295' }, { "otimes", '\x2297' }, - { "perp", '\x22A5' }, { "sdot", '\x22C5' }, { "lceil", '\x2308' }, { "rceil", '\x2309' }, - { "lfloor", '\x230A' }, { "rfloor", '\x230B' }, { "lang", '\x2329' }, { "rang", '\x232A' }, - { "loz", '\x25CA' }, { "spades", '\x2660' }, { "clubs", '\x2663' }, { "hearts", '\x2665' }, - { "diams", '\x2666' }, - }; - - public static string HtmlDecode( string value ) { - value = value.Replace( "hellip;", "\x2026" ); // minecraft.net doesn't escape this at the end properly. - if( String.IsNullOrEmpty( value ) || value.IndexOf( '&' ) < 0 ) { - return value; - } - - StringBuilder sb = new StringBuilder(); - WebUtility.HtmlDecode( value, sb ); - return sb.ToString(); - } - - static void HtmlDecode( string value, StringBuilder output ) { - for( int i = 0; i < value.Length; i++ ) { - char token = value[i]; - if( token != '&' ) { - output.Append( token ); - continue; - } - - int entityEnd = value.IndexOf( ';', i + 1 ); - if( entityEnd <= 0 ) { - output.Append( token ); - continue; - } - - string entity = value.Substring( i + 1, entityEnd - i - 1 ); - if( entity.Length > 1 && entity[0] == '#' ) { - ushort encodedNumber; - if( entity[1] == 'x' || entity[1] == 'X' ) { - ushort.TryParse( entity.Substring( 2 ), NumberStyles.AllowHexSpecifier, NumberFormatInfo.InvariantInfo, out encodedNumber ); - } else { - ushort.TryParse( entity.Substring( 1 ), NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out encodedNumber ); - } - if( encodedNumber != 0 ) { - output.Append( (char)encodedNumber ); - i = entityEnd; - } - } else { - i = entityEnd; - char decodedEntity; - if( _lookupTable.TryGetValue( entity, out decodedEntity ) ) { - output.Append( decodedEntity ); - } else { // Invalid token. - output.Append( '&' ); - output.Append( entity ); - output.Append( ';' ); - } - } - } - } - } -}