mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-01-24 01:52:24 -05:00
Add background generating of singleplayer maps, show progress of generation.
This commit is contained in:
parent
f733c4b4e4
commit
ffafd8023b
9 changed files with 228 additions and 192 deletions
|
@ -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 );
|
||||
|
@ -47,6 +47,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;
|
||||
}
|
||||
|
|
|
@ -142,6 +142,8 @@
|
|||
<Compile Include="Entities\LocationUpdate.cs" />
|
||||
<Compile Include="Entities\NetPlayer.cs" />
|
||||
<Compile Include="Game\Game.Properties.cs" />
|
||||
<Compile Include="Generator\FlatGrassGenerator.cs" />
|
||||
<Compile Include="Generator\IMapGenerator.cs" />
|
||||
<Compile Include="Generator\Noise.cs" />
|
||||
<Compile Include="Generator\NotchyGenerator.cs" />
|
||||
<Compile Include="Generator\NotchyGenerator.Utils.cs" />
|
||||
|
|
41
ClassicalSharp/Generator/FlatGrassGenerator.cs
Normal file
41
ClassicalSharp/Generator/FlatGrassGenerator.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
ClassicalSharp/Generator/IMapGenerator.cs
Normal file
40
ClassicalSharp/Generator/IMapGenerator.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
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++];
|
||||
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;
|
||||
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++ ) {
|
||||
|
|
|
@ -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 ) );
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//#define TEST_VANILLA
|
||||
using System;
|
||||
using System.Net;
|
||||
using ClassicalSharp.Generator;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -91,7 +91,6 @@
|
|||
<Compile Include="WebService\IWebTask.cs" />
|
||||
<Compile Include="WebService\ServerListEntry.cs" />
|
||||
<Compile Include="WebService\UpdateCheckTask.cs" />
|
||||
<Compile Include="WebService\WebUtility.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ClassicalSharp\ClassicalSharp.csproj">
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Launcher2 {
|
||||
|
||||
public static class WebUtility {
|
||||
|
||||
static Dictionary<string, char> _lookupTable = new Dictionary<string, char>( 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( ';' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue