Design initial event framework

This commit is contained in:
UnknownShadow200 2017-05-09 19:16:56 +10:00
parent 97de676d31
commit 68e815e6ed
11 changed files with 619 additions and 495 deletions

View file

@ -1,232 +1,231 @@
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using ClassicalSharp.Entities;
using ClassicalSharp.GraphicsAPI;
using ClassicalSharp.Physics;
using ClassicalSharp.Renderers;
using ClassicalSharp.Textures;
using OpenTK;
#if USE16_BIT
using BlockID = System.UInt16;
#else
using BlockID = System.Byte;
#endif
namespace ClassicalSharp.Model {
public class BlockModel : IModel {
BlockID block = Block.Air;
float height;
TerrainAtlas1D atlas;
bool bright;
Vector3 minBB, maxBB;
public bool SwitchOrder = false;
ModelCache cache;
public BlockModel(Game game) : base(game) {
cache = game.ModelCache;
Bobbing = false;
UsesSkin = false;
}
public override void CreateParts() { }
public override float NameYOffset { get { return height + 0.075f; } }
public override float GetEyeY(Entity entity) {
BlockID block = Byte.Parse(entity.ModelName);
float minY = game.BlockInfo.MinBB[block].Y;
float maxY = game.BlockInfo.MaxBB[block].Y;
return block == 0 ? 1 : (minY + maxY) / 2;
}
static Vector3 colShrink = new Vector3(0.75f/16, 0.75f/16, 0.75f/16);
public override Vector3 CollisionSize {
get { return (maxBB - minBB) - colShrink; } // to fit slightly inside
}
static Vector3 offset = new Vector3(-0.5f, 0.0f, -0.5f);
public override AABB PickingBounds {
get { return new AABB(minBB, maxBB).Offset(offset); }
}
public void CalcState(BlockID block) {
if (game.BlockInfo.Draw[block] == DrawType.Gas) {
minBB = Vector3.Zero;
maxBB = Vector3.One;
height = 1;
} else {
minBB = game.BlockInfo.MinBB[block];
maxBB = game.BlockInfo.MaxBB[block];
height = maxBB.Y - minBB.Y;
if (game.BlockInfo.Draw[block] == DrawType.Sprite)
height = 1;
}
}
public override float RenderDistance(Entity p) {
block = Utils.FastByte(p.ModelName);
CalcState(block);
return base.RenderDistance(p);
}
int lastTexId = -1;
public override void DrawModel(Entity p) {
// TODO: using 'is' is ugly, but means we can avoid creating
// a string every single time held block changes.
if (p is FakePlayer) {
block = ((FakePlayer)p).Block;
} else {
block = Utils.FastByte(p.ModelName);
}
CalcState(block);
if (game.BlockInfo.FullBright[block]) {
for (int i = 0; i < cols.Length; i++)
cols[i] = FastColour.WhitePacked;
}
if (game.BlockInfo.Draw[block] == DrawType.Gas) return;
lastTexId = -1;
atlas = game.TerrainAtlas1D;
bool sprite = game.BlockInfo.Draw[block] == DrawType.Sprite;
DrawParts(sprite);
if (index == 0) return;
IGraphicsApi gfx = game.Graphics;
gfx.BindTexture(lastTexId);
if (sprite) gfx.FaceCulling = true;
UpdateVB();
if (sprite) gfx.FaceCulling = false;
}
void FlushIfNotSame(int texIndex) {
int texId = game.TerrainAtlas1D.TexIds[texIndex];
if (texId == lastTexId) return;
if (lastTexId != -1) {
game.Graphics.BindTexture(lastTexId);
UpdateVB();
}
lastTexId = texId;
index = 0;
}
CuboidDrawer drawer = new CuboidDrawer();
void DrawParts(bool sprite) {
// SwitchOrder is needed for held block, which renders without depth testing
if (sprite) {
if (SwitchOrder) {
SpriteZQuad(Side.Back, false);
SpriteXQuad(Side.Right, false);
} else {
SpriteXQuad(Side.Right, false);
SpriteZQuad(Side.Back, false);
}
if (SwitchOrder) {
SpriteXQuad(Side.Right, true);
SpriteZQuad(Side.Back, true);
} else {
SpriteZQuad(Side.Back, true);
SpriteXQuad(Side.Right, true);
}
} else {
drawer.elementsPerAtlas1D = atlas.elementsPerAtlas1D;
drawer.invVerElementSize = atlas.invElementSize;
drawer.minBB = game.BlockInfo.MinBB[block]; drawer.minBB.Y = 1 - drawer.minBB.Y;
drawer.maxBB = game.BlockInfo.MaxBB[block]; drawer.maxBB.Y = 1 - drawer.maxBB.Y;
Vector3 min = game.BlockInfo.RenderMinBB[block];
Vector3 max = game.BlockInfo.RenderMaxBB[block];
drawer.x1 = min.X - 0.5f; drawer.y1 = min.Y; drawer.z1 = min.Z - 0.5f;
drawer.x2 = max.X - 0.5f; drawer.y2 = max.Y; drawer.z2 = max.Z - 0.5f;
drawer.Tinted = game.BlockInfo.Tinted[block];
drawer.TintColour = game.BlockInfo.FogColour[block];
drawer.Bottom(1, cols[1], GetTex(Side.Bottom), cache.vertices, ref index);
if (SwitchOrder) {
drawer.Right(1, cols[5], GetTex(Side.Right), cache.vertices, ref index);
drawer.Back(1, cols[2], GetTex(Side.Back), cache.vertices, ref index);
drawer.Left(1, cols[4], GetTex(Side.Left), cache.vertices, ref index);
drawer.Front(1, cols[3], GetTex(Side.Front), cache.vertices, ref index);
} else {
drawer.Front(1, cols[3], GetTex(Side.Front), cache.vertices, ref index);
drawer.Right(1, cols[5], GetTex(Side.Right), cache.vertices, ref index);
drawer.Back(1, cols[2], GetTex(Side.Back), cache.vertices, ref index);
drawer.Left(1, cols[4], GetTex(Side.Left), cache.vertices, ref index);
}
drawer.Top(1, cols[0], GetTex(Side.Top), cache.vertices, ref index);
}
}
int GetTex(int side) {
int texId = game.BlockInfo.GetTextureLoc(block, side);
texIndex = texId / atlas.elementsPerAtlas1D;
FlushIfNotSame(texIndex);
return texId;
}
void SpriteZQuad(int side, bool firstPart) {
SpriteZQuad(side, firstPart, false);
SpriteZQuad(side, firstPart, true);
}
void SpriteZQuad(int side, bool firstPart, bool mirror) {
int texId = game.BlockInfo.GetTextureLoc(block, side), texIndex = 0;
TextureRec rec = atlas.GetTexRec(texId, 1, out texIndex);
FlushIfNotSame(texIndex);
if (height != 1)
rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f);
int col = cols[0];
float p1 = 0, p2 = 0;
if (firstPart) { // Need to break into two quads for when drawing a sprite model in hand.
if (mirror) { rec.U1 = 0.5f; p1 = -5.5f/16; }
else { rec.U2 = 0.5f; p2 = -5.5f/16; }
} else {
if (mirror) { rec.U2 = 0.5f; p2 = 5.5f/16; }
else { rec.U1 = 0.5f; p1 = 5.5f/16; }
}
cache.vertices[index++] = new VertexP3fT2fC4b(p1, 0, p1, rec.U2, rec.V2, col);
cache.vertices[index++] = new VertexP3fT2fC4b(p1, 1, p1, rec.U2, rec.V1, col);
cache.vertices[index++] = new VertexP3fT2fC4b(p2, 1, p2, rec.U1, rec.V1, col);
cache.vertices[index++] = new VertexP3fT2fC4b(p2, 0, p2, rec.U1, rec.V2, col);
}
void SpriteXQuad(int side, bool firstPart) {
SpriteXQuad(side, firstPart, false);
SpriteXQuad(side, firstPart, true);
}
void SpriteXQuad(int side, bool firstPart, bool mirror) {
int texId = game.BlockInfo.GetTextureLoc(block, side), texIndex = 0;
TextureRec rec = atlas.GetTexRec(texId, 1, out texIndex);
FlushIfNotSame(texIndex);
if (height != 1)
rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f);
int col = cols[0];
float x1 = 0, x2 = 0, z1 = 0, z2 = 0;
if (firstPart) {
if (mirror) { rec.U2 = 0.5f; x2 = -5.5f/16; z2 = 5.5f/16; }
else { rec.U1 = 0.5f; x1 = -5.5f/16; z1 = 5.5f/16; }
} else {
if (mirror) { rec.U1 = 0.5f; x1 = 5.5f/16; z1 = -5.5f/16; }
else { rec.U2 = 0.5f; x2 = 5.5f/16; z2 = -5.5f/16; }
}
cache.vertices[index++] = new VertexP3fT2fC4b(x1, 0, z1, rec.U2, rec.V2, col);
cache.vertices[index++] = new VertexP3fT2fC4b(x1, 1, z1, rec.U2, rec.V1, col);
cache.vertices[index++] = new VertexP3fT2fC4b(x2, 1, z2, rec.U1, rec.V1, col);
cache.vertices[index++] = new VertexP3fT2fC4b(x2, 0, z2, rec.U1, rec.V2, col);
}
}
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using ClassicalSharp.Entities;
using ClassicalSharp.GraphicsAPI;
using ClassicalSharp.Physics;
using ClassicalSharp.Renderers;
using ClassicalSharp.Textures;
using OpenTK;
#if USE16_BIT
using BlockID = System.UInt16;
#else
using BlockID = System.Byte;
#endif
namespace ClassicalSharp.Model {
public class BlockModel : IModel {
BlockID block = Block.Air;
float height;
TerrainAtlas1D atlas;
Vector3 minBB, maxBB;
public bool SwitchOrder = false;
ModelCache cache;
public BlockModel(Game game) : base(game) {
cache = game.ModelCache;
Bobbing = false;
UsesSkin = false;
}
public override void CreateParts() { }
public override float NameYOffset { get { return height + 0.075f; } }
public override float GetEyeY(Entity entity) {
BlockID block = Byte.Parse(entity.ModelName);
float minY = game.BlockInfo.MinBB[block].Y;
float maxY = game.BlockInfo.MaxBB[block].Y;
return block == 0 ? 1 : (minY + maxY) / 2;
}
static Vector3 colShrink = new Vector3(0.75f/16, 0.75f/16, 0.75f/16);
public override Vector3 CollisionSize {
get { return (maxBB - minBB) - colShrink; } // to fit slightly inside
}
static Vector3 offset = new Vector3(-0.5f, 0.0f, -0.5f);
public override AABB PickingBounds {
get { return new AABB(minBB, maxBB).Offset(offset); }
}
public void CalcState(BlockID block) {
if (game.BlockInfo.Draw[block] == DrawType.Gas) {
minBB = Vector3.Zero;
maxBB = Vector3.One;
height = 1;
} else {
minBB = game.BlockInfo.MinBB[block];
maxBB = game.BlockInfo.MaxBB[block];
height = maxBB.Y - minBB.Y;
if (game.BlockInfo.Draw[block] == DrawType.Sprite)
height = 1;
}
}
public override float RenderDistance(Entity p) {
block = Utils.FastByte(p.ModelName);
CalcState(block);
return base.RenderDistance(p);
}
int lastTexId = -1;
public override void DrawModel(Entity p) {
// TODO: using 'is' is ugly, but means we can avoid creating
// a string every single time held block changes.
if (p is FakePlayer) {
block = ((FakePlayer)p).Block;
} else {
block = Utils.FastByte(p.ModelName);
}
CalcState(block);
if (game.BlockInfo.FullBright[block]) {
for (int i = 0; i < cols.Length; i++)
cols[i] = FastColour.WhitePacked;
}
if (game.BlockInfo.Draw[block] == DrawType.Gas) return;
lastTexId = -1;
atlas = game.TerrainAtlas1D;
bool sprite = game.BlockInfo.Draw[block] == DrawType.Sprite;
DrawParts(sprite);
if (index == 0) return;
IGraphicsApi gfx = game.Graphics;
gfx.BindTexture(lastTexId);
if (sprite) gfx.FaceCulling = true;
UpdateVB();
if (sprite) gfx.FaceCulling = false;
}
void FlushIfNotSame(int texIndex) {
int texId = game.TerrainAtlas1D.TexIds[texIndex];
if (texId == lastTexId) return;
if (lastTexId != -1) {
game.Graphics.BindTexture(lastTexId);
UpdateVB();
}
lastTexId = texId;
index = 0;
}
CuboidDrawer drawer = new CuboidDrawer();
void DrawParts(bool sprite) {
// SwitchOrder is needed for held block, which renders without depth testing
if (sprite) {
if (SwitchOrder) {
SpriteZQuad(Side.Back, false);
SpriteXQuad(Side.Right, false);
} else {
SpriteXQuad(Side.Right, false);
SpriteZQuad(Side.Back, false);
}
if (SwitchOrder) {
SpriteXQuad(Side.Right, true);
SpriteZQuad(Side.Back, true);
} else {
SpriteZQuad(Side.Back, true);
SpriteXQuad(Side.Right, true);
}
} else {
drawer.elementsPerAtlas1D = atlas.elementsPerAtlas1D;
drawer.invVerElementSize = atlas.invElementSize;
drawer.minBB = game.BlockInfo.MinBB[block]; drawer.minBB.Y = 1 - drawer.minBB.Y;
drawer.maxBB = game.BlockInfo.MaxBB[block]; drawer.maxBB.Y = 1 - drawer.maxBB.Y;
Vector3 min = game.BlockInfo.RenderMinBB[block];
Vector3 max = game.BlockInfo.RenderMaxBB[block];
drawer.x1 = min.X - 0.5f; drawer.y1 = min.Y; drawer.z1 = min.Z - 0.5f;
drawer.x2 = max.X - 0.5f; drawer.y2 = max.Y; drawer.z2 = max.Z - 0.5f;
drawer.Tinted = game.BlockInfo.Tinted[block];
drawer.TintColour = game.BlockInfo.FogColour[block];
drawer.Bottom(1, cols[1], GetTex(Side.Bottom), cache.vertices, ref index);
if (SwitchOrder) {
drawer.Right(1, cols[5], GetTex(Side.Right), cache.vertices, ref index);
drawer.Back(1, cols[2], GetTex(Side.Back), cache.vertices, ref index);
drawer.Left(1, cols[4], GetTex(Side.Left), cache.vertices, ref index);
drawer.Front(1, cols[3], GetTex(Side.Front), cache.vertices, ref index);
} else {
drawer.Front(1, cols[3], GetTex(Side.Front), cache.vertices, ref index);
drawer.Right(1, cols[5], GetTex(Side.Right), cache.vertices, ref index);
drawer.Back(1, cols[2], GetTex(Side.Back), cache.vertices, ref index);
drawer.Left(1, cols[4], GetTex(Side.Left), cache.vertices, ref index);
}
drawer.Top(1, cols[0], GetTex(Side.Top), cache.vertices, ref index);
}
}
int GetTex(int side) {
int texId = game.BlockInfo.GetTextureLoc(block, side);
texIndex = texId / atlas.elementsPerAtlas1D;
FlushIfNotSame(texIndex);
return texId;
}
void SpriteZQuad(int side, bool firstPart) {
SpriteZQuad(side, firstPart, false);
SpriteZQuad(side, firstPart, true);
}
void SpriteZQuad(int side, bool firstPart, bool mirror) {
int texId = game.BlockInfo.GetTextureLoc(block, side), texIndex = 0;
TextureRec rec = atlas.GetTexRec(texId, 1, out texIndex);
FlushIfNotSame(texIndex);
if (height != 1)
rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f);
int col = cols[0];
float p1 = 0, p2 = 0;
if (firstPart) { // Need to break into two quads for when drawing a sprite model in hand.
if (mirror) { rec.U1 = 0.5f; p1 = -5.5f/16; }
else { rec.U2 = 0.5f; p2 = -5.5f/16; }
} else {
if (mirror) { rec.U2 = 0.5f; p2 = 5.5f/16; }
else { rec.U1 = 0.5f; p1 = 5.5f/16; }
}
cache.vertices[index++] = new VertexP3fT2fC4b(p1, 0, p1, rec.U2, rec.V2, col);
cache.vertices[index++] = new VertexP3fT2fC4b(p1, 1, p1, rec.U2, rec.V1, col);
cache.vertices[index++] = new VertexP3fT2fC4b(p2, 1, p2, rec.U1, rec.V1, col);
cache.vertices[index++] = new VertexP3fT2fC4b(p2, 0, p2, rec.U1, rec.V2, col);
}
void SpriteXQuad(int side, bool firstPart) {
SpriteXQuad(side, firstPart, false);
SpriteXQuad(side, firstPart, true);
}
void SpriteXQuad(int side, bool firstPart, bool mirror) {
int texId = game.BlockInfo.GetTextureLoc(block, side), texIndex = 0;
TextureRec rec = atlas.GetTexRec(texId, 1, out texIndex);
FlushIfNotSame(texIndex);
if (height != 1)
rec.V2 = rec.V1 + height * atlas.invElementSize * (15.99f/16f);
int col = cols[0];
float x1 = 0, x2 = 0, z1 = 0, z2 = 0;
if (firstPart) {
if (mirror) { rec.U2 = 0.5f; x2 = -5.5f/16; z2 = 5.5f/16; }
else { rec.U1 = 0.5f; x1 = -5.5f/16; z1 = 5.5f/16; }
} else {
if (mirror) { rec.U1 = 0.5f; x1 = 5.5f/16; z1 = -5.5f/16; }
else { rec.U2 = 0.5f; x2 = 5.5f/16; z2 = -5.5f/16; }
}
cache.vertices[index++] = new VertexP3fT2fC4b(x1, 0, z1, rec.U2, rec.V2, col);
cache.vertices[index++] = new VertexP3fT2fC4b(x1, 1, z1, rec.U2, rec.V1, col);
cache.vertices[index++] = new VertexP3fT2fC4b(x2, 1, z2, rec.U1, rec.V1, col);
cache.vertices[index++] = new VertexP3fT2fC4b(x2, 0, z2, rec.U1, rec.V2, col);
}
}
}

View file

@ -1,246 +1,243 @@
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using System.Drawing;
using System.Globalization;
using ClassicalSharp.Model;
using OpenTK;
using OpenTK.Input;
#if ANDROID
using Android.Graphics;
using AndroidColor = Android.Graphics.Color;
#endif
namespace ClassicalSharp {
// NOTE: These delegates should be removed when using versions later than NET 2.0.
// ################################################################
public delegate void Action();
public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3);
public delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
public delegate TResult Func<TResult>();
public delegate TResult Func<T1, TResult>(T1 arg1);
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
// ################################################################
public static partial class Utils {
public const int StringLength = 64;
/// <summary> Returns a string with all the colour codes stripped from it. </summary>
public static string StripColours(string value) {
if (value.IndexOf('&') == -1) return value;
char[] output = new char[value.Length];
int usedChars = 0;
for (int i = 0; i < value.Length; i++) {
char token = value[i];
if (token == '&') {
i++; // Skip over the following colour code.
} else {
output[usedChars++] = token;
}
}
return new String(output, 0, usedChars);
}
/// <summary> Returns a string with a + removed if it is the last character in the string. </summary>
public static string RemoveEndPlus(string value) {
// Workaround for MCDzienny (and others) use a '+' at the end to distinguish classicube.net accounts
// from minecraft.net accounts. Unfortunately they also send this ending + to the client.
if (String.IsNullOrEmpty(value)) return value;
return value[value.Length - 1] == '+' ?
value.Substring(0, value.Length - 1) : value;
}
const StringComparison comp = StringComparison.OrdinalIgnoreCase;
/// <summary> Returns whether a equals b, ignoring any case differences. </summary>
public static bool CaselessEquals(string a, string b) { return a.Equals(b, comp); }
/// <summary> Returns whether a starts with b, ignoring any case differences. </summary>
public static bool CaselessStarts(string a, string b) { return a.StartsWith(b, comp); }
/// <summary> Returns whether a ends with b, ignoring any case differences. </summary>
public static bool CaselessEnds(string a, string b) { return a.EndsWith(b, comp); }
/// <summary> Converts the given byte array of length N to a hex string of length 2N. </summary>
public static string ToHexString(byte[] array) {
int len = array.Length;
char[] hex = new char[len * 2];
for (int i = 0; i < array.Length; i++) {
int value = array[i];
int hi = value >> 4, lo = value & 0x0F;
// 48 = index of 0, 55 = index of (A - 10).
hex[i * 2 + 0] = hi < 10 ? (char)(hi + 48) : (char)(hi + 55);
hex[i * 2 + 1] = lo < 10 ? (char)(lo + 48) : (char)(lo + 55);
}
return new String(hex);
}
/// <summary> Returns the hex code represented by the given character.
/// Throws FormatException if the input character isn't a hex code. </summary>
public static int ParseHex(char value) {
int hex;
if (!TryParseHex(value, out hex))
throw new FormatException("Invalid hex code given: " + value);
return hex;
}
/// <summary> Attempts to return the hex code represented by the given character. </summary>
public static bool TryParseHex(char value, out int hex) {
hex = 0;
if (value >= '0' && value <= '9') {
hex = (int)(value - '0');
} else if (value >= 'a' && value <= 'f') {
hex = (int)(value - 'a') + 10;
} else if (value >= 'A' && value <= 'F') {
hex = (int)(value - 'A') + 10;
} else {
return false;
}
return true;
}
/// <summary> Attempts to caselessly parse the given string as a Key enum member,
/// returning defValue if there was an error parsing. </summary>
public static bool TryParseEnum<T>(string value, T defValue, out T result) {
T mapping;
try {
mapping = (T)Enum.Parse(typeof(T), value, true);
} catch (ArgumentException) {
result = defValue;
return false;
}
result = mapping;
return true;
}
public static void LogDebug(string text) {
Console.WriteLine(text);
}
public static void LogDebug(string text, params object[] args) {
Console.WriteLine(String.Format(text, args));
}
public static int AdjViewDist(float value) {
return (int)(1.4142135 * value);
}
/// <summary> Returns the number of vertices needed to subdivide a quad. </summary>
public static int CountVertices(int axis1Len, int axis2Len, int axisSize) {
return CeilDiv(axis1Len, axisSize) * CeilDiv(axis2Len, axisSize) * 4;
}
public static int Tint(int col, FastColour tint) {
FastColour adjCol = FastColour.Unpack(col);
adjCol *= tint;
return adjCol.Pack();
}
public static byte FastByte(string s) {
int sum = 0;
switch (s.Length) {
case 1: sum = (s[0] - '0'); break;
case 2: sum = (s[0] - '0') * 10 + (s[1] - '0'); break;
case 3: sum = (s[0] - '0') * 100 + (s[1] - '0') * 10 + (s[2] - '0'); break;
}
return (byte)sum;
}
/// <summary> Determines the skin type of the specified bitmap. </summary>
public static SkinType GetSkinType(Bitmap bmp) {
if (bmp.Width == bmp.Height * 2) {
return SkinType.Type64x32;
} else if (bmp.Width == bmp.Height) {
// Minecraft alex skins have this particular pixel with alpha of 0.
int scale = bmp.Width / 64;
#if !ANDROID
int alpha = bmp.GetPixel(54 * scale, 20 * scale).A;
#else
int alpha = AndroidColor.GetAlphaComponent(bmp.GetPixel(54 * scale, 20 * scale));
#endif
return alpha >= 127 ? SkinType.Type64x64 : SkinType.Type64x64Slim;
}
return SkinType.Invalid;
}
/// <summary> Returns whether the specified string starts with http:// or https:// </summary>
public static bool IsUrlPrefix(string value, int index) {
int http = value.IndexOf("http://", index);
int https = value.IndexOf("https://", index);
return http == index || https == index;
}
/// <summary> Conversion for code page 437 characters from index 0 to 31 to unicode. </summary>
public const string ControlCharReplacements = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼";
/// <summary> Conversion for code page 437 characters from index 127 to 255 to unicode. </summary>
public const string ExtendedCharReplacements = "⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»" +
"░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌" +
"█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u00a0";
public static bool IsValidInputChar(char c, Game game) {
if (c >= ' ' && c <= '~') return true; // ascii
bool isCP437 = Utils.ControlCharReplacements.IndexOf(c) >= 0 ||
Utils.ExtendedCharReplacements.IndexOf(c) >= 0;
bool supportsCP437 = game.Server.SupportsFullCP437;
return supportsCP437 && isCP437;
}
public unsafe static string ToLower(string value) {
fixed(char* ptr = value) {
for (int i = 0; i < value.Length; i++) {
char c = ptr[i];
if (c < 'A' || c > 'Z') continue;
c += ' '; ptr[i] = c;
}
}
return value;
}
// Not all languages use . as their decimal point separator
public static bool TryParseDecimal(string s, out float result) {
if (s.IndexOf(',') >= 0)
s = s.Replace(',', '.');
float temp;
result = 0;
if (!Single.TryParse(s, style, NumberFormatInfo.InvariantInfo, out temp)) return false;
if (Single.IsInfinity(temp) || Single.IsNaN(temp)) return false;
result = temp;
return true;
}
public static float ParseDecimal(string s) {
if (s.IndexOf(',') >= 0)
s = s.Replace(',', '.');
return Single.Parse(s, style, NumberFormatInfo.InvariantInfo);
}
const NumberStyles style = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
| NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint;
#if USE16_BIT
public static ushort[] UInt8sToUInt16s(byte[] src) {
ushort[] dst = new ushort[src.Length];
for (int i = 0; i < dst.Length; i++)
dst[i] = src[i];
return dst;
}
public static byte[] UInt16sToUInt8s(ushort[] src) {
byte[] dst = new byte[src.Length];
for (int i = 0; i < dst.Length; i++)
dst[i] = (byte)src[i];
return dst;
}
#endif
}
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using System.Drawing;
using System.Globalization;
using ClassicalSharp.Model;
using OpenTK;
using OpenTK.Input;
#if ANDROID
using Android.Graphics;
using AndroidColor = Android.Graphics.Color;
#endif
namespace ClassicalSharp {
// NOTE: These delegates should be removed when using versions later than NET 2.0.
// ################################################################
public delegate void Action();
public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3);
public delegate TResult Func<TResult>();
public delegate TResult Func<T1, TResult>(T1 arg1);
// ################################################################
public static partial class Utils {
public const int StringLength = 64;
/// <summary> Returns a string with all the colour codes stripped from it. </summary>
public static string StripColours(string value) {
if (value.IndexOf('&') == -1) return value;
char[] output = new char[value.Length];
int usedChars = 0;
for (int i = 0; i < value.Length; i++) {
char token = value[i];
if (token == '&') {
i++; // Skip over the following colour code.
} else {
output[usedChars++] = token;
}
}
return new String(output, 0, usedChars);
}
/// <summary> Returns a string with a + removed if it is the last character in the string. </summary>
public static string RemoveEndPlus(string value) {
// Workaround for MCDzienny (and others) use a '+' at the end to distinguish classicube.net accounts
// from minecraft.net accounts. Unfortunately they also send this ending + to the client.
if (String.IsNullOrEmpty(value)) return value;
return value[value.Length - 1] == '+' ?
value.Substring(0, value.Length - 1) : value;
}
const StringComparison comp = StringComparison.OrdinalIgnoreCase;
/// <summary> Returns whether a equals b, ignoring any case differences. </summary>
public static bool CaselessEquals(string a, string b) { return a.Equals(b, comp); }
/// <summary> Returns whether a starts with b, ignoring any case differences. </summary>
public static bool CaselessStarts(string a, string b) { return a.StartsWith(b, comp); }
/// <summary> Returns whether a ends with b, ignoring any case differences. </summary>
public static bool CaselessEnds(string a, string b) { return a.EndsWith(b, comp); }
/// <summary> Converts the given byte array of length N to a hex string of length 2N. </summary>
public static string ToHexString(byte[] array) {
int len = array.Length;
char[] hex = new char[len * 2];
for (int i = 0; i < array.Length; i++) {
int value = array[i];
int hi = value >> 4, lo = value & 0x0F;
// 48 = index of 0, 55 = index of (A - 10).
hex[i * 2 + 0] = hi < 10 ? (char)(hi + 48) : (char)(hi + 55);
hex[i * 2 + 1] = lo < 10 ? (char)(lo + 48) : (char)(lo + 55);
}
return new String(hex);
}
/// <summary> Returns the hex code represented by the given character.
/// Throws FormatException if the input character isn't a hex code. </summary>
public static int ParseHex(char value) {
int hex;
if (!TryParseHex(value, out hex))
throw new FormatException("Invalid hex code given: " + value);
return hex;
}
/// <summary> Attempts to return the hex code represented by the given character. </summary>
public static bool TryParseHex(char value, out int hex) {
hex = 0;
if (value >= '0' && value <= '9') {
hex = (int)(value - '0');
} else if (value >= 'a' && value <= 'f') {
hex = (int)(value - 'a') + 10;
} else if (value >= 'A' && value <= 'F') {
hex = (int)(value - 'A') + 10;
} else {
return false;
}
return true;
}
/// <summary> Attempts to caselessly parse the given string as a Key enum member,
/// returning defValue if there was an error parsing. </summary>
public static bool TryParseEnum<T>(string value, T defValue, out T result) {
T mapping;
try {
mapping = (T)Enum.Parse(typeof(T), value, true);
} catch (ArgumentException) {
result = defValue;
return false;
}
result = mapping;
return true;
}
public static void LogDebug(string text) {
Console.WriteLine(text);
}
public static void LogDebug(string text, params object[] args) {
Console.WriteLine(String.Format(text, args));
}
public static int AdjViewDist(float value) {
return (int)(1.4142135 * value);
}
/// <summary> Returns the number of vertices needed to subdivide a quad. </summary>
public static int CountVertices(int axis1Len, int axis2Len, int axisSize) {
return CeilDiv(axis1Len, axisSize) * CeilDiv(axis2Len, axisSize) * 4;
}
public static int Tint(int col, FastColour tint) {
FastColour adjCol = FastColour.Unpack(col);
adjCol *= tint;
return adjCol.Pack();
}
public static byte FastByte(string s) {
int sum = 0;
switch (s.Length) {
case 1: sum = (s[0] - '0'); break;
case 2: sum = (s[0] - '0') * 10 + (s[1] - '0'); break;
case 3: sum = (s[0] - '0') * 100 + (s[1] - '0') * 10 + (s[2] - '0'); break;
}
return (byte)sum;
}
/// <summary> Determines the skin type of the specified bitmap. </summary>
public static SkinType GetSkinType(Bitmap bmp) {
if (bmp.Width == bmp.Height * 2) {
return SkinType.Type64x32;
} else if (bmp.Width == bmp.Height) {
// Minecraft alex skins have this particular pixel with alpha of 0.
int scale = bmp.Width / 64;
#if !ANDROID
int alpha = bmp.GetPixel(54 * scale, 20 * scale).A;
#else
int alpha = AndroidColor.GetAlphaComponent(bmp.GetPixel(54 * scale, 20 * scale));
#endif
return alpha >= 127 ? SkinType.Type64x64 : SkinType.Type64x64Slim;
}
return SkinType.Invalid;
}
/// <summary> Returns whether the specified string starts with http:// or https:// </summary>
public static bool IsUrlPrefix(string value, int index) {
int http = value.IndexOf("http://", index);
int https = value.IndexOf("https://", index);
return http == index || https == index;
}
/// <summary> Conversion for code page 437 characters from index 0 to 31 to unicode. </summary>
public const string ControlCharReplacements = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼";
/// <summary> Conversion for code page 437 characters from index 127 to 255 to unicode. </summary>
public const string ExtendedCharReplacements = "⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»" +
"░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌" +
"█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u00a0";
public static bool IsValidInputChar(char c, Game game) {
if (c >= ' ' && c <= '~') return true; // ascii
bool isCP437 = Utils.ControlCharReplacements.IndexOf(c) >= 0 ||
Utils.ExtendedCharReplacements.IndexOf(c) >= 0;
bool supportsCP437 = game.Server.SupportsFullCP437;
return supportsCP437 && isCP437;
}
public unsafe static string ToLower(string value) {
fixed(char* ptr = value) {
for (int i = 0; i < value.Length; i++) {
char c = ptr[i];
if (c < 'A' || c > 'Z') continue;
c += ' '; ptr[i] = c;
}
}
return value;
}
// Not all languages use . as their decimal point separator
public static bool TryParseDecimal(string s, out float result) {
if (s.IndexOf(',') >= 0)
s = s.Replace(',', '.');
float temp;
result = 0;
if (!Single.TryParse(s, style, NumberFormatInfo.InvariantInfo, out temp)) return false;
if (Single.IsInfinity(temp) || Single.IsNaN(temp)) return false;
result = temp;
return true;
}
public static float ParseDecimal(string s) {
if (s.IndexOf(',') >= 0)
s = s.Replace(',', '.');
return Single.Parse(s, style, NumberFormatInfo.InvariantInfo);
}
const NumberStyles style = NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
| NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint;
#if USE16_BIT
public static ushort[] UInt8sToUInt16s(byte[] src) {
ushort[] dst = new ushort[src.Length];
for (int i = 0; i < dst.Length; i++)
dst[i] = src[i];
return dst;
}
public static byte[] UInt16sToUInt8s(ushort[] src) {
byte[] dst = new byte[src.Length];
for (int i = 0; i < dst.Length; i++)
dst[i] = (byte)src[i];
return dst;
}
#endif
}
}

View file

@ -173,9 +173,12 @@
<ClInclude Include="BlockID.h" />
<ClInclude Include="Block.h" />
<ClInclude Include="Compiler.h" />
<ClInclude Include="D3D9Api.h" />
<ClInclude Include="DefaultSet.h" />
<ClInclude Include="Bitmap.h" />
<ClInclude Include="ErrorHandler.h" />
<ClInclude Include="WorldEvents.h" />
<ClInclude Include="EventHandler.h" />
<ClInclude Include="FastColour.h" />
<ClInclude Include="Funcs.h" />
<ClInclude Include="Game.h" />
@ -201,9 +204,9 @@
<ItemGroup>
<ClCompile Include="Block.c" />
<ClCompile Include="Compiler.c" />
<ClCompile Include="D3D9Api.c" />
<ClCompile Include="DefaultSet.c" />
<ClCompile Include="Bitmap.c" />
<ClCompile Include="Direct3D9Api.c" />
<ClCompile Include="ExtMath.c" />
<ClCompile Include="FastColour.c" />
<ClCompile Include="GraphicsCommon.c" />

View file

@ -70,6 +70,9 @@
<Filter Include="Source Files\Map">
<UniqueIdentifier>{cb26e83f-9153-4964-9dc2-13502fbb1d1b}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Events">
<UniqueIdentifier>{a912f0d5-3ceb-4e7e-ba4e-28170c634047}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="NotchyGenerator.h">
@ -156,6 +159,15 @@
<ClInclude Include="WorldEnv.h">
<Filter>Header Files\Map</Filter>
</ClInclude>
<ClInclude Include="D3D9Api.h">
<Filter>Header Files\Graphics</Filter>
</ClInclude>
<ClInclude Include="EventHandler.h">
<Filter>Header Files\Events</Filter>
</ClInclude>
<ClInclude Include="WorldEvents.h">
<Filter>Header Files\Events</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="NotchyGenerator.c">
@ -203,9 +215,6 @@
<ClCompile Include="GraphicsCommon.c">
<Filter>Source Files\Graphics</Filter>
</ClCompile>
<ClCompile Include="Direct3D9Api.c">
<Filter>Source Files\Graphics</Filter>
</ClCompile>
<ClCompile Include="Vector3I.c">
<Filter>Source Files\Math</Filter>
</ClCompile>
@ -218,5 +227,8 @@
<ClCompile Include="WorldEnv.c">
<Filter>Source Files\Map</Filter>
</ClCompile>
<ClCompile Include="D3D9Api.c">
<Filter>Source Files\Graphics</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -22,6 +22,13 @@ void Gfx_Init(Game* game) {
}
bool Gfx_SetTexturing(bool enabled) {
if (enabled) return;
ReturnCode hresult = IDirect3DDevice9_SetTexture(device, 0, NULL);
ErrorHandler_CheckOrFail(hresult, "D3D9_SetTexturing");
}
bool d3d9_fogEnable = false;
void Gfx_SetFog(bool enabled) {
if (d3d9_fogEnable == enabled) return;

37
src/Client/EventHandler.h Normal file
View file

@ -0,0 +1,37 @@
#ifndef CS_EVENTHANDLER_H
#define CS_EVENTHANDLER_H
#include "Typedefs.h"
/* Helper method for managing events.
Copyright 2017 ClassicalSharp | Licensed under BSD-3
*/
/* Event handler that takes no arguments. */
typedef void (*Event_Void)(void);
/* Event handler that takes single 32 bit signed integer argument. */
typedef void (*Event_Int32)(Int32 argument);
/* Event handler that takes single floating-point argument. */
typedef void(*Event_Float32)(Real32 argument);
/* Maximum number of event handlers that can be registered. */
#define EventHandler_Size 32
/* Adds given event handler to handlers list for the given event. */
void EventHandler_Register(void** handlers, Int32* count, void* handler);
/* Removes given event handler from handlers list of the given event. */
void EventHandler_Unregister(void** handlers, Int32* count, void* handler);
/* Calls handlers for an event that has no arguments.*/
void EventHandler_Call_Void(Event_Void* handlers, Int32 handlersCount);
/* Calls handlers for an event that has no arguments.*/
void EventHandler_Call_Int32(Event_Int32* handlers, Int32 handlersCount);
/* Calls handlers for an event that has no arguments.*/
void EventHandler_Call_Float32(Event_Float32* handlers, Int32 handlersCount);
#endif

View file

@ -1,6 +1,7 @@
#ifndef CS_GFXAPI_H
#define CS_GFXAPI_H
#include "Typedefs.h"
#include "EventHandler.h"
#include "Bitmap.h"
#include "FastColour.h"
#include "String.h"
@ -16,26 +17,28 @@
void Gfx_Init(Game* game);
/* Maximum supported length of a dimension (width and height) of a 2D texture. */
Int32 Gfx_MaxTextureDimensions;
Int32 Gfx_MaxTextureDimensions = 0;
/* Minimum near plane value supported by the graphics API. */
float Gfx_MinZNear;
float Gfx_MinZNear = 0.0f;
/* Returns whether this graphics api had a valid context. */
bool Gfx_LostContext;
bool Gfx_LostContext = false;
/* Maximum number of vertices that can be indexed. */
#define Gfx_MaxIndices (65536 / 4 * 6)
// TODO: define these, we need an action interface
/*/// <summary> Event raised when a context is destroyed after having been previously lost. </summary>
public event Action ContextLost;
/* Event raised when a context is destroyed after having been previously lost. */
Event_Void Gfx_ContextLost[EventHandler_Size];
Int32 Gfx_ContextLostCount = 0;
/// <summary> Event raised when a context is recreated after having been previously lost. </summary>
public event Action ContextRecreated;
/* Event raised when a context is recreated after having been previously lost. */
Event_Void Gfx_ContextRecreated[EventHandler_Size];
Int32 Gfx_ContextRecreatedCount = 0;
/// <summary> Delegate that is invoked when the current context is lost,
// TODO: IMPLEMENT THIS
/* /// <summary> Delegate that is invoked when the current context is lost,
/// and is repeatedly invoked until the context can be retrieved. </summary>
public Action<ScheduledTask> LostContextFunction;*/

View file

@ -19,7 +19,7 @@ void GfxCommon_LoseContext(String reason) {
Platform_Log(String_FromConstant("Lost graphics context:"));
Platform_Log(reason);
if (ContextLost != null) ContextLost();
EventHandler_Call_Void(Gfx_ContextLost, Gfx_ContextLostCount);
GfxCommon_Free();
}
@ -27,7 +27,7 @@ void GfxCommon_RecreateContext() {
Gfx_LostContext = false;
Platform_Log(String_FromConstant("Recreating graphics context"));
if (ContextRecreated != null) ContextRecreated();
EventHandler_Call_Void(Gfx_ContextRecreated, Gfx_ContextRecreatedCount);
GfxCommon_Init();
}

View file

@ -2,11 +2,12 @@
#include "BlockID.h"
#include "ErrorHandler.h"
#include "String.h"
#include "WorldEnv.h"
void World_Reset() {
World_Width = 0; World_Height = 0; World_Length = 0;
World_Blocks = NULL; World_BlocksSize = 0;
Uuid = Guid.NewGuid();
World_Uuid = Guid.NewGuid();
}
void World_SetNewMap(BlockID* blocks, Int32 blocksSize, Int32 width, Int32 height, Int32 length) {
@ -18,8 +19,12 @@ void World_SetNewMap(BlockID* blocks, Int32 blocksSize, Int32 width, Int32 heigh
ErrorHandler_Fail(String_FromConstant("Blocks array size does not match volume of map."));
}
if (Env.EdgeHeight == -1) Env.EdgeHeight = height / 2;
if (Env.CloudHeight == -1) Env.CloudHeight = height + 2;
if (WorldEnv_EdgeHeight == -1) {
WorldEnv_EdgeHeight = height / 2;
}
if (WorldEnv_CloudHeight == -1) {
WorldEnv_CloudHeight = height + 2;
}
}
BlockID World_GetPhysicsBlock(Int32 x, Int32 y, Int32 z) {

View file

@ -66,5 +66,5 @@ bool World_IsValidPos(Int32 x, Int32 y, Int32 z);
bool World_IsValidPos_3I(Vector3I p);
/* Unpacks the given index into the map's block array into its original world coordinates. */
Vector3I World_GetCoords(int index);
Vector3I World_GetCoords(Int32 index);
#endif

61
src/Client/WorldEvents.h Normal file
View file

@ -0,0 +1,61 @@
#ifndef CS_WORLDEVENTS_H
#define CS_WORLDEVENTS_H
#include "EventHandler.h"
#include "Typedefs.h"
/* World related events.
Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
*/
/* Raised when the player joins and begins loading a new world. */
Event_Void WorldEvents_NewMap[EventHandler_Size];
Int32 WorldEvents_NewMapCount = 0;
/* Raises NewMap event. */
void WorldEvents_RaiseNewMap();
/* Raised when a portion of the world is read and decompressed, or generated.
The floating point argument is progress (from 0 to 1). */
Event_Float32 WorldEvents_MapLoading[EventHandler_Size];
Int32 WorldEvents_MapLoadingCount = 0;
/* Raises MapLoading event. */
void WorldEvents_RaiseMapLoading(Real32 progress);
/* Raised when new world has finished loading and the player can now interact with it. */
Event_Void WorldEvents_NewMapLoaded[EventHandler_Size];
Int32 WorldEvents_NewMapLoadedCount = 0;
/* Raises NewMapLoaded event. */
void WorldEvents_RaiseNewMapLoaded();
/* Raised when an environment variable of the world is changed by the user, CPE, or WoM config. */
Event_Int32 WorldEvents_EnvVariableChanged[EventHandler_Size];
Int32 WorldEvents_EnvVariableChangedCount = 0;
/* Raises EnvVariableChanged event. */
void WorldEvents_RaiseEnvVariableChanged(Int32 envVar);
/* Environment variable identifiers*/
#define EnvVar_SidesBlock 0
#define EnvVar_EdgeBlock 1
#define EnvVar_EdgeLevel 2
#define EnvVar_CloudsLevel 3
#define EnvVar_CloudsSpeed 4
#define EnvVar_Weather 5
#define EnvVar_SkyCol 6
#define EnvVar_CloudsCol 7
#define EnvVar_FogCol 8
#define EnvVar_SunCol 9
#define EnvVar_ShadowCol 10
#define EnvVar_WeatherSpeed 11
#define EnvVar_WeatherFade 12
#define EnvVar_ExpFog 13
#define EnvVar_SidesOffset 14
#endif