first commit

This commit is contained in:
Beyhan Oğur
2026-09-03 04:45:36 +03:00
commit d5f52cc3d3
1038 changed files with 169579 additions and 0 deletions
+382
View File
@@ -0,0 +1,382 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using ClassicAssist.Misc;
namespace ClassicAssist.UO.Data
{
public static class Art
{
private static string _dataPath;
private static bool _isUOPFormat;
private static Lazy<Dictionary<int, Entry3D>> _lazyIndex;
public static bool Initialize( string dataPath )
{
_dataPath = dataPath;
if ( File.Exists( Path.Combine( dataPath, "artLegacyMUL.uop" ) ) )
{
_isUOPFormat = true;
}
_lazyIndex = new Lazy<Dictionary<int, Entry3D>>( () => _isUOPFormat ? LoadUOPIndex() : LoadMULIndex() );
return true;
}
private static Dictionary<int, Entry3D> LoadMULIndex()
{
int entrySize = Marshal.SizeOf( typeof( Entry3D ) );
byte[] buffer = new byte[entrySize];
GCHandle pinnedBuffer = GCHandle.Alloc( buffer, GCHandleType.Pinned );
int index = 0;
Dictionary<int, Entry3D> indexes = new Dictionary<int, Entry3D>();
using ( FileStream reader = File.Open( Path.Combine( _dataPath, "artidx.mul" ), FileMode.Open,
FileAccess.Read, FileShare.ReadWrite ) )
{
int bytesRead;
do
{
bytesRead = reader.Read( buffer, 0, entrySize );
indexes[index++] =
(Entry3D) Marshal.PtrToStructure( pinnedBuffer.AddrOfPinnedObject(), typeof( Entry3D ) );
}
while ( bytesRead > 0 );
}
return indexes;
}
private static Dictionary<int, Entry3D> LoadUOPIndex()
{
Dictionary<ulong, int> hashes = new Dictionary<ulong, int>();
Dictionary<int, Entry3D> indexes = new Dictionary<int, Entry3D>();
using ( FileStream reader = File.Open( Path.Combine( _dataPath, "artLegacyMUL.uop" ), FileMode.Open,
FileAccess.Read, FileShare.ReadWrite ) )
{
using ( BinaryReader bin = new BinaryReader( reader ) )
{
reader.Seek( 12, SeekOrigin.Current );
int firstAddress = bin.ReadInt32();
for ( int i = 0; i < 0x10000 /*formatHeader.NumberOfFiles*/; i++ )
{
string entryName = $"build/artlegacymul/{i:D8}.tga";
ulong hash = HashFileName( entryName );
if ( !hashes.ContainsKey( hash ) )
{
hashes.Add( hash, i );
}
}
long nextAddress = firstAddress;
do
{
reader.Seek( nextAddress, SeekOrigin.Begin );
UOPBlockHeader blockHeader = reader.ReadStruct<UOPBlockHeader>();
nextAddress = blockHeader.NextAddress;
for ( int i = 0; i < blockHeader.NumberOfFiles; i++ )
{
UOPFileHeader fileHeader = reader.ReadStruct<UOPFileHeader>();
if ( fileHeader.DataHeaderAddress == 0 )
{
continue;
}
if ( hashes.ContainsKey( fileHeader.Hash ) )
{
int index = hashes[fileHeader.Hash];
indexes[index] = new Entry3D( (int) fileHeader.DataHeaderAddress + fileHeader.Length,
fileHeader.IsCompressed == 1
? fileHeader.CompressedSize
: fileHeader.DecompressedSize, 0 );
}
}
}
while ( nextAddress > 0 );
}
}
return indexes;
}
public static Bitmap GetStatic( int itemID, int hue )
{
Bitmap bmp = GetStatic( itemID );
if ( hue == 0 || bmp == null )
{
return bmp;
}
StaticTile tileData = TileData.GetStaticTile( itemID );
Hues.ApplyHue( hue, bmp, tileData.Flags.HasFlag( TileFlags.PartialHue ) );
return bmp;
}
public static unsafe Bitmap GetStatic( int itemId )
{
itemId += 0x4000;
string fileName = Path.Combine( _dataPath, "art.mul" );
if ( _isUOPFormat )
{
fileName = Path.Combine( _dataPath, "artLegacyMUL.uop" );
}
FileStream artFile = File.Open( fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite );
Entry3D entry;
try
{
entry = _lazyIndex.Value[itemId];
}
catch ( KeyNotFoundException )
{
entry = _lazyIndex.Value[0x4000];
}
artFile.Seek( entry.Lookup, SeekOrigin.Begin );
using ( BinaryReader reader = new BinaryReader( artFile ) )
{
reader.ReadInt32();
int width = reader.ReadInt16();
int height = reader.ReadInt16();
if ( width <= 0 || height <= 0 )
{
return null;
}
int[] lookups = new int[height];
int start = (int) reader.BaseStream.Position + height * 2;
for ( int i = 0; i < height; i++ )
{
lookups[i] = start + reader.ReadUInt16() * 2;
}
Bitmap bmp = new Bitmap( width, height, PixelFormat.Format16bppArgb1555 );
BitmapData bd = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.WriteOnly,
PixelFormat.Format16bppArgb1555 );
ushort* line = (ushort*) bd.Scan0;
int delta = bd.Stride >> 1;
int largestX = 0;
int smallestX = width;
int largestY = 0;
int smallestY = height;
for ( int y = 0; y < height; y++, line += delta )
{
reader.BaseStream.Seek( lookups[y], SeekOrigin.Begin );
ushort* cur = line;
int xOffset, xRun;
int x = 0;
if ( y > largestY )
{
largestY = y + 1;
}
if ( y < smallestY )
{
smallestY = y;
}
while ( ( xOffset = reader.ReadUInt16() ) + ( xRun = reader.ReadUInt16() ) != 0 )
{
cur += xOffset;
ushort* end = cur + xRun;
x += xOffset;
if ( x < smallestX )
{
smallestX = x;
}
while ( cur < end )
{
*cur++ = (ushort) ( reader.ReadUInt16() ^ 0x8000 );
x++;
}
if ( x > largestX )
{
largestX = x;
}
}
}
bmp.UnlockBits( bd );
return bmp;
}
}
private static ulong HashFileName( string s )
{
uint ecx, edx, ebx, esi, edi;
uint eax = ecx = edx = ebx = esi = edi = 0;
ebx = edi = esi = (uint) s.Length + 0xDEADBEEF;
int i = 0;
for ( i = 0; i + 12 < s.Length; i += 12 )
{
edi = (uint) ( ( s[i + 7] << 24 ) | ( s[i + 6] << 16 ) | ( s[i + 5] << 8 ) | s[i + 4] ) + edi;
esi = (uint) ( ( s[i + 11] << 24 ) | ( s[i + 10] << 16 ) | ( s[i + 9] << 8 ) | s[i + 8] ) + esi;
edx = (uint) ( ( s[i + 3] << 24 ) | ( s[i + 2] << 16 ) | ( s[i + 1] << 8 ) | s[i] ) - esi;
edx = ( edx + ebx ) ^ ( esi >> 28 ) ^ ( esi << 4 );
esi += edi;
edi = ( edi - edx ) ^ ( edx >> 26 ) ^ ( edx << 6 );
edx += esi;
esi = ( esi - edi ) ^ ( edi >> 24 ) ^ ( edi << 8 );
edi += edx;
ebx = ( edx - esi ) ^ ( esi >> 16 ) ^ ( esi << 16 );
esi += edi;
edi = ( edi - ebx ) ^ ( ebx >> 13 ) ^ ( ebx << 19 );
ebx += esi;
esi = ( esi - edi ) ^ ( edi >> 28 ) ^ ( edi << 4 );
edi += ebx;
}
if ( s.Length - i > 0 )
{
switch ( s.Length - i )
{
case 12:
esi += (uint) s[i + 11] << 24;
goto case 11;
case 11:
esi += (uint) s[i + 10] << 16;
goto case 10;
case 10:
esi += (uint) s[i + 9] << 8;
goto case 9;
case 9:
esi += s[i + 8];
goto case 8;
case 8:
edi += (uint) s[i + 7] << 24;
goto case 7;
case 7:
edi += (uint) s[i + 6] << 16;
goto case 6;
case 6:
edi += (uint) s[i + 5] << 8;
goto case 5;
case 5:
edi += s[i + 4];
goto case 4;
case 4:
ebx += (uint) s[i + 3] << 24;
goto case 3;
case 3:
ebx += (uint) s[i + 2] << 16;
goto case 2;
case 2:
ebx += (uint) s[i + 1] << 8;
goto case 1;
case 1:
ebx += s[i];
break;
}
esi = ( esi ^ edi ) - ( ( edi >> 18 ) ^ ( edi << 14 ) );
ecx = ( esi ^ ebx ) - ( ( esi >> 21 ) ^ ( esi << 11 ) );
edi = ( edi ^ ecx ) - ( ( ecx >> 7 ) ^ ( ecx << 25 ) );
esi = ( esi ^ edi ) - ( ( edi >> 16 ) ^ ( edi << 16 ) );
edx = ( esi ^ ecx ) - ( ( esi >> 28 ) ^ ( esi << 4 ) );
edi = ( edi ^ edx ) - ( ( edx >> 18 ) ^ ( edx << 14 ) );
eax = ( esi ^ edi ) - ( ( edi >> 8 ) ^ ( edi << 24 ) );
return ( (ulong) edi << 32 ) | eax;
}
return ( (ulong) esi << 32 ) | eax;
}
#region Structures
[StructLayout( LayoutKind.Explicit )]
private struct Entry3D
{
[FieldOffset( 0 )]
public readonly int Lookup;
[FieldOffset( 4 )]
public readonly int Length;
[FieldOffset( 8 )]
public readonly int Extra;
public Entry3D( int lookup, int length, int extra )
{
Lookup = lookup;
Length = length;
Extra = extra;
}
}
[StructLayout( LayoutKind.Explicit, Size = 12 )]
private struct UOPBlockHeader
{
[FieldOffset( 0 )]
public readonly int NumberOfFiles;
[FieldOffset( 4 )]
public readonly long NextAddress;
}
[StructLayout( LayoutKind.Explicit, Pack = 0, Size = 34 )]
private struct UOPFileHeader
{
[FieldOffset( 0 )]
public readonly long DataHeaderAddress;
[FieldOffset( 8 )]
public readonly int Length;
[FieldOffset( 12 )]
public readonly int CompressedSize;
[FieldOffset( 16 )]
public readonly int DecompressedSize;
[FieldOffset( 20 )]
public readonly ulong Hash;
[FieldOffset( 28 )]
public readonly int Unknown;
[FieldOffset( 32 )]
public readonly short IsCompressed;
}
#endregion
}
}
+174
View File
@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ClassicAssist.UO.Data
{
public static class Cliloc
{
private static readonly Lazy<Dictionary<int, string>> _lazyClilocList =
new Lazy<Dictionary<int, string>>( LoadClilocs );
private static string _dataPath;
private static Dictionary<int, string> LoadClilocs()
{
string filename = Path.Combine( _dataPath, "Cliloc.enu" );
if ( !File.Exists( filename ) )
{
throw new FileNotFoundException( "File not found.", filename );
}
byte[] fileBytes = File.ReadAllBytes( filename );
Dictionary<int, string> clilocList = new Dictionary<int, string>( 100000 );
ushort len;
for ( int x = 6; x + 7 <= fileBytes.Length; x += 7 + len )
{
len = BitConverter.ToUInt16( fileBytes, x + 5 );
if ( x + 7 + len > fileBytes.Length )
{
break;
}
try
{
int key = BitConverter.ToInt32( fileBytes, x );
string value = Encoding.UTF8.GetString( fileBytes, x + 7, len );
clilocList[key] = value;
}
catch
{
break;
}
}
return clilocList;
}
public static string GetLocalString( string tokenizedString )
{
if ( tokenizedString.ToLower().Contains( "http://" ) || tokenizedString.ToLower().Contains( "https://" ) )
{
return tokenizedString;
}
while ( tokenizedString.Contains( "#" ) )
{
for ( int x = 0; x < tokenizedString.Length; x++ )
{
if ( tokenizedString[x] != '#' || x >= tokenizedString.Length - 1 )
{
continue;
}
if ( !char.IsNumber( tokenizedString[x + 1] ) )
{
return tokenizedString;
}
int y;
for ( y = x + 1; y < tokenizedString.Length; y++ )
{
if ( !char.IsNumber( tokenizedString[y] ) )
{
break;
}
}
string token = tokenizedString.Substring( x, y - x );
string tokenNum = tokenizedString.Substring( x + 1, y - x - 1 );
if ( tokenNum.Length <= 0 )
{
continue;
}
int propertyNum;
try
{
propertyNum = Convert.ToInt32( tokenNum );
}
catch
{
return tokenizedString;
}
string property = GetProperty( propertyNum );
tokenizedString = tokenizedString.Replace( token, property );
}
}
return tokenizedString;
}
public static string GetLocalString( int property, string[] arguments )
{
string propertyString = GetProperty( property );
if ( arguments == null )
{
return propertyString;
}
//foreach (string s in arguments)
for ( int x = 0; x < arguments.Length; x++ )
{
arguments[x] = GetLocalString( arguments[x] );
bool found = false;
int start = 0;
int index = 0;
foreach ( char c in propertyString )
{
if ( c == '~' )
{
if ( found )
{
string subString = propertyString.Substring( start, index - start + 1 );
propertyString = propertyString.Replace( subString, arguments[x] );
break;
}
start = index;
found = true;
}
index++;
}
if ( !found )
{
return propertyString;
}
}
return propertyString;
}
public static void Initialize( string dataPath )
{
_dataPath = dataPath;
}
public static string GetProperty( int property )
{
return _lazyClilocList.Value.TryGetValue( property, out string propertyString )
? propertyString
: $"Localized string {property} not found!";
}
public static Dictionary<int, string> GetItems()
{
return new Dictionary<int, string>( _lazyClilocList.Value );
}
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace ClassicAssist.UO.Data
{
public enum Direction : byte
{
North,
Northeast,
East,
Southeast,
South,
Southwest,
West,
Northwest,
Invalid
}
}
+139
View File
@@ -0,0 +1,139 @@
using System;
namespace ClassicAssist.UO.Data
{
[Flags]
public enum MessageAffixType
{
Append,
Prepend,
System
}
[Flags]
public enum HealthbarColour
{
None = 0x00,
Green = 0x01,
Yellow = 0x02,
Red = 0x04
}
public enum TargetFlags : byte
{
None,
Harmful,
Beneficial,
Cancel
}
public enum Virtues
{
None = '0',
Honor,
Sacrafice,
Valor,
Compassion,
Honesty,
Humility,
Justice,
Spirituality
}
public enum MobileRace : byte
{
Unknown,
Human,
Elf,
Gargoyle
}
public enum JournalSpeech : byte
{
Say,
System,
Emote,
Unknown1,
Unknown2,
Unknown3,
Label,
Focus,
Whisper,
Yell,
Spell,
Unknown4,
Unknown5,
Guild,
Alliance,
GM
}
public enum MobileQueryType : byte
{
StatsRequest = 4,
SkillsRequest = 5
}
public enum TargetType : byte
{
Object,
Tile
}
[Flags]
public enum FeatureFlags
{
None = 0x00000000,
T2A = 0x00000001,
UOR = 0x00000002,
UOTD = 0x00000004,
LBR = 0x00000008,
AOS = 0x00000010,
SixthCharacterSlot = 0x00000020,
SE = 0x00000040,
ML = 0x00000080,
EigthAge = 0x00000100,
NinthAge = 0x00000200, /* Crystal/Shadow Custom House Tiles */
TenthAge = 0x00000400,
IncreasedStorage = 0x00000800, /* Increased Housing/Bank Storage */
SeventhCharacterSlot = 0x00001000,
RoleplayFaces = 0x00002000,
TrialAccount = 0x00004000,
LiveAccount = 0x00008000,
SA = 0x00010000,
HS = 0x00020000,
Gothic = 0x00040000,
Rustic = 0x00080000,
Jungle = 0x00100000,
Shadowguard = 0x00200000,
TOL = 0x00400000,
EJ = 0x00800000
}
public enum Notoriety : byte
{
Invalid,
Innocent,
Ally,
Attackable,
Criminal,
Enemy,
Murderer,
Invulnerable,
Unknown
}
[Flags]
public enum MobileStatus : byte
{
None = 0x00,
Frozen = 0x01,
Female = 0x02,
Flying = 0x04,
Invulnerable = 0x08,
IgnoreMobiles = 0x10,
Unknown = 0x20,
WarMode = 0x40,
Hidden = 0x80
}
}
@@ -0,0 +1,106 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System.Linq;
using ClassicAssist.Data;
using ClassicAssist.Shared;
using ClassicAssist.UO.Network;
using ClassicAssist.UO.Network.Packets;
using ClassicAssist.UO.Objects.Gumps;
namespace ClassicAssist.UO.Data.Gumps
{
public class BoatGumpExtension : IExtension
{
public void Initialize()
{
IncomingPacketHandlers.JournalEntryAddedEvent += OnJournalEntryAddedEvent;
}
private static void OnJournalEntryAddedEvent( JournalEntry je )
{
switch ( je.Cliloc )
{
/* You are now piloting this vessel. */
case 1116727:
new BoatMovementGump().SendGump();
break;
/* You are no longer piloting this vessel. */
case 1149592:
Shared.UO.Commands.CloseClientGump( typeof( BoatMovementGump ) );
break;
}
}
}
public class BoatMovementGump : Gump
{
private static BoatSpeed _lastSpeed = BoatSpeed.OneTile;
public BoatMovementGump() : base( 100, 100 )
{
Movable = true;
Closable = true;
Resizable = false;
Disposable = false;
AddPage( 0 );
AddBackground( 0, 0, 150, 175, 9200 );
AddButton( 0, 0, 4507, 4507, (int) Direction.Northwest + 1, GumpButtonType.Reply, 0 );
AddButton( 50, 0, 4500, 4500, (int) Direction.North + 1, GumpButtonType.Reply, 0 );
AddButton( 100, 0, 4501, 4501, (int) Direction.Northeast + 1, GumpButtonType.Reply, 0 );
AddButton( 10, 50, 4506, 4506, (int) Direction.West + 1, GumpButtonType.Reply, 0 );
AddButton( 90, 50, 4502, 4502, (int) Direction.East + 1, GumpButtonType.Reply, 0 );
AddButton( 0, 100, 4505, 4505, (int) Direction.Southwest + 1, GumpButtonType.Reply, 0 );
AddButton( 50, 100, 4504, 4504, (int) Direction.South + 1, GumpButtonType.Reply, 0 );
AddButton( 100, 100, 4503, 4503, (int) Direction.Southeast + 1, GumpButtonType.Reply, 0 );
AddRadio( 10, 150, 208, 209, _lastSpeed == BoatSpeed.Normal, 1 );
AddLabel( 40, 150, 0, "Fast" );
AddButton( 60, 60, 2151, 2151, 10, GumpButtonType.Reply, 0 );
}
public override void OnResponse( int buttonID, int[] switches )
{
if ( buttonID == 0 )
{
return;
}
BoatSpeed speed = BoatSpeed.OneTile;
if ( switches?.Any( e => e == 1 ) ?? false )
{
speed = BoatSpeed.Normal;
}
_lastSpeed = speed;
if ( buttonID < 9 )
{
Engine.SendPacketToServer( new WheelBoatMoving( (Direction) buttonID - 1, speed ) );
}
if ( buttonID == 10 )
{
Engine.SendPacketToServer( new WheelBoatMoving( Direction.North, BoatSpeed.Stop ) );
}
new BoatMovementGump().SendGump();
}
}
}
@@ -0,0 +1,132 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System.Drawing;
using System.Threading;
using ClassicAssist.Shared;
using ClassicAssist.Helpers;
using ClassicAssist.Misc;
using ClassicAssist.UO.Network.PacketFilter;
using ClassicAssist.UO.Objects.Gumps;
namespace ClassicAssist.UO.Gumps
{
public class ConfirmPromptGump : Gump
{
public ConfirmPromptGump( string message, bool closable = false ) : base( 500, 500 )
{
const int width = 300;
const int height = 200;
SetCenterPosition( width, height );
Closable = closable;
Resizable = false;
Disposable = false;
Movable = true;
AddPage( 0 );
AddBackground( 0, 0, width, height, 9200 );
AddHtml( 10, 10, width - 20, height - 50, message, true, true );
AddButton( width - 140, height - 30, 247, 248, 1, GumpButtonType.Reply, 0 );
AddButton( width - 70, height - 30, 241, 242, 2, GumpButtonType.Reply, 0 );
}
public AutoResetEvent AutoResetEvent { get; set; } = new AutoResetEvent( false );
public bool Result { get; set; }
public override void OnResponse( int buttonID, int[] switches )
{
Result = buttonID == 1;
AutoResetEvent.Set();
}
public static bool ConfirmPrompt( string message, bool closable = false )
{
ConfirmPromptGump gump = new ConfirmPromptGump( message, closable );
PacketFilterInfo pfi = new PacketFilterInfo( 0xB1,
new[] { PacketFilterConditions.UIntAtPositionCondition( gump.ID, 7 ) } );
Engine.AddSendPostFilter( pfi );
gump.SendGump();
gump.AutoResetEvent.WaitOne();
Engine.RemoveSendPostFilter( pfi );
return gump.Result;
}
private static Point GetWindowSize()
{
if ( !NativeMethods.GetWindowRect( Engine.WindowHandle, out NativeMethods.RECT rect ) )
{
return Point.Empty;
}
int windowWidth = rect.Right - rect.Left;
int windowHeight = rect.Bottom - rect.Top;
return new Point( windowWidth, windowHeight );
}
public static Point GetGameWindowCenter()
{
dynamic currentProfile =
Reflection.GetTypePropertyValue<dynamic>( "ClassicUO.Configuration.ProfileManager", "Current", null );
if ( currentProfile == null )
{
return Point.Empty;
}
dynamic gameWindowSize =
Reflection.GetTypePropertyValue<dynamic>( currentProfile.GetType(), "GameWindowSize", currentProfile );
dynamic gameWindowPosition = Reflection.GetTypePropertyValue<dynamic>( currentProfile.GetType(),
"GameWindowPosition", currentProfile );
if ( gameWindowSize == null || gameWindowPosition == null )
{
return Point.Empty;
}
return new Point( gameWindowPosition.X + ( gameWindowSize.X >> 1 ),
gameWindowPosition.Y + ( gameWindowSize.Y >> 1 ) );
}
private void SetCenterPosition( int width, int height )
{
Point gameCenterPosition = GetGameWindowCenter();
if ( gameCenterPosition == Point.Empty )
{
Point size = GetWindowSize();
X = ( size.X - ( width ) ) >> 1;
Y = ( size.Y - ( height ) ) >> 1;
return;
}
X = gameCenterPosition.X - width / 2;
Y = gameCenterPosition.Y - height / 2;
}
}
}
@@ -0,0 +1,112 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using ClassicAssist.Shared;
using ClassicAssist.Data;
using ClassicAssist.Data.Macros;
using ClassicAssist.UO.Objects.Gumps;
namespace ClassicAssist.UO.Gumps
{
public class MacrosGump : RepositionableGump
{
private static Timer _timer;
private static string _lastList;
private static int _serial = 0x0fe00000;
public MacrosGump( string html ) : base( 190, 180, _serial++, (uint) _serial++ )
{
GumpX = Options.CurrentOptions.MacrosGumpX;
GumpY = Options.CurrentOptions.MacrosGumpY;
Movable = false;
Closable = false;
Resizable = false;
Disposable = false;
AddPage( 0 );
AddBackground( 0, 0, 190, 180, 3500 );
AddHtml( 20, 20, 150, 140, html, false, true );
}
public static void ResendGump( bool force = false )
{
try
{
MacroManager _macroManager = MacroManager.GetInstance();
IEnumerable<MacroEntry> macro = _macroManager.Items.Where( e => e.IsRunning );
string html = string.Empty;
foreach ( MacroEntry entry in macro )
{
if ( entry.IsBackground )
{
html += $"<BASEFONT COLOR=#000000><I>{entry.Name}</I></BASEFONT>\n";
}
else
{
html += $"<BASEFONT COLOR=#000000>{entry.Name}</BASEFONT>\n";
}
}
if ( html.Equals( _lastList ) && !force )
{
return;
}
if ( Engine.Gumps.GetGumps( out Gump[] gumps ) )
{
foreach ( Gump macrosGump in gumps.Where( g => g is MacrosGump ) )
{
Shared.UO.Commands.CloseClientGump( macrosGump.ID );
}
}
MacrosGump gump = new MacrosGump( html );
gump.SendGump();
_lastList = html;
}
catch ( InvalidOperationException e )
{
Console.WriteLine( e.ToString() );
}
}
public static void Initialize()
{
_timer = new Timer( o => ResendGump(), null, 1000, 250 );
}
public override void SetPosition( int x, int y )
{
base.SetPosition( x, y );
Options.CurrentOptions.MacrosGumpX = x;
Options.CurrentOptions.MacrosGumpY = y;
ResendGump( true );
}
}
}
@@ -0,0 +1,33 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using ClassicAssist.UO.Data;
namespace ClassicAssist.UO.Network.Packets
{
public class Pathfind : BasePacket
{
public Pathfind(int x, int y, int z)
{
_writer = new PacketWriter(7);
_writer.Write((byte)0x38);
_writer.Write((short)x);
_writer.Write((short)y);
_writer.Write((short)z);
}
}
}
@@ -0,0 +1,84 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System.Threading;
using ClassicAssist.Shared;
using ClassicAssist.UI.ViewModels;
using ClassicAssist.UO.Objects.Gumps;
namespace ClassicAssist.UO.Gumps
{
public abstract class RepositionableGump : Gump
{
private const int REPOSITION_BUTTON_ID = 10;
private readonly int _height;
private readonly int _width;
protected RepositionableGump( int width, int height, int serial, uint gumpID ) : base( 0, 0, serial,
gumpID )
{
_width = width;
_height = height;
}
public int GumpX { get; set; } = 100;
public int GumpY { get; set; } = 100;
public override void SendGump()
{
X = GumpX;
Y = GumpY;
AddButton( _width - 15, 5, 0x82C, 0x82C, REPOSITION_BUTTON_ID, GumpButtonType.Reply, 0 );
base.SendGump();
}
public override void OnResponse( int buttonID, int[] switches )
{
if ( buttonID == REPOSITION_BUTTON_ID )
{
Engine.Dispatcher.Invoke( () =>
{
Thread t = new Thread( () =>
{
SetPosition( GumpX, GumpY );
RepositionableGumpViewModel vm = new RepositionableGumpViewModel( this, GumpX, GumpY );
//TODO UI
//RepositionableGumpWindow window = new RepositionableGumpWindow { DataContext = vm };
//WindowInteropHelper helper = new WindowInteropHelper( window ) { Owner = Engine.WindowHandle };
//window.ShowInTaskbar = false;
//window.ShowDialog();
} )
{ IsBackground = true };
t.SetApartmentState( ApartmentState.STA );
t.Start();
} );
}
base.OnResponse( buttonID, switches );
}
public virtual void SetPosition( int x, int y )
{
GumpX = x;
GumpY = y;
}
}
}
@@ -0,0 +1,39 @@
using System;
using ClassicAssist.Data;
using ClassicAssist.Misc;
using ClassicAssist.UO.Objects.Gumps;
namespace ClassicAssist.UO.Gumps
{
public sealed class UpdateMessageGump : Gump
{
private readonly Version _version;
public UpdateMessageGump( IntPtr hWnd, string message, Version version ) : base( 500, 250, -1 )
{
if ( NativeMethods.GetWindowRect( hWnd, out NativeMethods.RECT rect ) )
{
int windowWidth = rect.Right - rect.Left;
int windowHeight = rect.Bottom - rect.Top;
X = ( windowWidth - 600 ) / 2;
Y = ( windowHeight - 400 ) / 2;
}
_version = version;
Closable = true;
Disposable = false;
AddPage( 0 );
AddBackground( 0, 0, 500, 400, 9270 );
AddHtml( 20, 20, 460, 330, message, true, true );
AddButton( 420, 360, 247, 248, 0, GumpButtonType.Reply, 0 );
}
public override void OnResponse( int buttonID, int[] switches )
{
AssistantOptions.UpdateGumpVersion = _version;
base.OnResponse( buttonID, switches );
}
}
}
@@ -0,0 +1,81 @@
using ClassicAssist.Shared;
using ClassicAssist.Data;
using ClassicAssist.Data.Macros.Commands;
using ClassicAssist.UO.Gumps;
using ClassicAssist.UO.Objects.Gumps;
namespace ClassicAssist.UO.Data.Gumps
{
public class WeaponAbilitiesGump : RepositionableGump
{
private readonly bool _primaryEnable;
private readonly int _primaryId;
private readonly bool _secondaryEnable;
private readonly int _secondaryId;
public WeaponAbilitiesGump( int primaryId, bool primaryEnable, int secondaryId, bool secondaryEnable ) : base(
90, 40, GumpSerial++, (uint) GumpSerial++ )
{
if ( Engine.Gumps.GetGumps( out Gump[] gumps ) )
{
foreach ( Gump gump in gumps )
{
if ( gump is WeaponAbilitiesGump )
{
Shared.UO.Commands.CloseClientGump( gump.ID );
}
}
}
GumpX = Options.CurrentOptions.AbilitiesGumpX;
GumpY = Options.CurrentOptions.AbilitiesGumpY;
_primaryId = primaryId;
_secondaryId = secondaryId;
_primaryEnable = primaryEnable;
_secondaryEnable = secondaryEnable;
Movable = false;
Closable = false;
AddPage( 0 );
AddImage( 0, 0, 0x5200 + ( primaryId - 1 ), primaryEnable ? 37 : 0 );
AddImage( 45, 0, 0x5200 + ( secondaryId - 1 ), secondaryEnable ? 37 : 0 );
AddButton( 15, 15, 1209, 1210, 1, GumpButtonType.Reply, 0 );
AddButton( 60, 15, 1209, 1210, 2, GumpButtonType.Reply, 0 );
}
public static int GumpSerial { get; set; } = 0x0fff0000;
public override void OnResponse( int buttonID, int[] switches )
{
switch ( buttonID )
{
case 1:
AbilitiesCommands.SetAbility( "primary" );
break;
case 2:
AbilitiesCommands.SetAbility( "secondary" );
break;
}
base.OnResponse( buttonID, switches );
}
private void ResendGump()
{
WeaponAbilitiesGump gump =
new WeaponAbilitiesGump( _primaryId, _primaryEnable, _secondaryId, _secondaryEnable );
gump.SendGump();
}
public override void SetPosition( int x, int y )
{
base.SetPosition( x, y );
Options.CurrentOptions.AbilitiesGumpX = x;
Options.CurrentOptions.AbilitiesGumpY = y;
ResendGump();
}
}
}
+152
View File
@@ -0,0 +1,152 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace ClassicAssist.UO.Data
{
public static class Hues
{
private const int BlockCount = 375;
private static string _dataPath;
public static Lazy<HueEntry[]> _lazyHueEntries = new Lazy<HueEntry[]>( LoadHueIndex );
public static bool Initialize( string dataPath )
{
_dataPath = dataPath;
return true;
}
private static HueEntry[] LoadHueIndex()
{
HueEntry[] entries = new HueEntry[3000];
if ( !File.Exists( Path.Combine( _dataPath, "hues.mul" ) ) )
{
throw new FileNotFoundException( "File not found", "hues.mul" );
}
using ( FileStream reader = File.Open( Path.Combine( _dataPath, "hues.mul" ), FileMode.Open,
FileAccess.Read, FileShare.ReadWrite ) )
{
BinaryReader binaryReader = new BinaryReader( reader );
int total = 0;
for ( int i = 0; i < BlockCount; i++ )
{
binaryReader.ReadInt32();
for ( int j = 0; j < 8; ++j, ++total )
{
entries[total] = HueEntry.Read( binaryReader );
}
}
}
return entries;
}
public static unsafe void ApplyHue( int hue, Bitmap bmp, bool onlyHueGrayPixels )
{
BitmapData bd = bmp.LockBits( new Rectangle( 0, 0, bmp.Width, bmp.Height ), ImageLockMode.ReadWrite,
PixelFormat.Format16bppArgb1555 );
hue = ( hue & 0x3FFF ) - 1;
int stride = bd.Stride >> 1;
int width = bd.Width;
int height = bd.Height;
int delta = stride - width;
ushort* pBuffer = (ushort*) bd.Scan0;
ushort* pLineEnd = pBuffer + width;
ushort* pImageEnd = pBuffer + stride * height;
if ( onlyHueGrayPixels )
{
while ( pBuffer < pImageEnd )
{
while ( pBuffer < pLineEnd )
{
int c = *pBuffer;
if ( c != 0 )
{
int r = ( c >> 10 ) & 0x1F;
int g = ( c >> 5 ) & 0x1F;
int b = c & 0x1F;
if ( r == g && r == b )
{
*pBuffer = (ushort) _lazyHueEntries.Value[hue].Colors[( c >> 10 ) & 0x1F];
}
}
++pBuffer;
}
pBuffer += delta;
pLineEnd += stride;
}
}
else
{
while ( pBuffer < pImageEnd )
{
while ( pBuffer < pLineEnd )
{
if ( *pBuffer != 0 )
{
int index = ( *pBuffer >> 10 ) & 0x1F;
HueEntry hueEntry = _lazyHueEntries.Value[hue];
*pBuffer = (ushort) hueEntry.Colors[index];
}
++pBuffer;
}
pBuffer += delta;
pLineEnd += stride;
}
}
bmp.UnlockBits( bd );
}
}
[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct HueEntry
{
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 32 )]
public short[] Colors;
public short TableStart;
public short TableEnd;
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 20 )]
public string Name;
public static HueEntry Read( BinaryReader reader )
{
// ReSharper disable once UseObjectOrCollectionInitializer
HueEntry entry = new HueEntry();
entry.Colors = new short[32];
for ( int i = 0; i < 32; ++i )
{
entry.Colors[i] = (short) ( reader.ReadUInt16() | 0x8000 );
}
entry.TableStart = (short) ( reader.ReadUInt16() | 0x8000 );
entry.TableEnd = (short) ( reader.ReadUInt16() | 0x8000 );
entry.Name = Encoding.ASCII.GetString( reader.ReadBytes( 20 ) ).TrimEnd( '\0' );
return entry;
}
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace ClassicAssist.UO.Data
{
public struct LandTile
{
public TileFlags Flags { get; set; }
public int ID { get; set; }
public string Name { get; set; }
public int X { get; set; }
public int Y { get; set; }
public sbyte Z { get; set; }
}
}
+37
View File
@@ -0,0 +1,37 @@
namespace ClassicAssist.UO.Data
{
public enum Layer : byte
{
Invalid = 0,
OneHanded = 1,
TwoHanded = 2,
Shoes = 3,
Pants = 4,
Shirt = 5,
Helm = 6,
Gloves = 7,
Ring = 8,
Talisman = 9,
Neck = 10, // 0x0A
Hair = 11, // 0x0B
Waist = 12, // 0x0C
InnerTorso = 13, // 0x0D
Bracelet = 14, // 0x0E
Unused_xF = 15, // 0x0F
FacialHair = 16, // 0x10
MiddleTorso = 17, // 0x11
Earrings = 18, // 0x12
Arms = 19, // 0x13
Cloak = 20, // 0x14
Backpack = 21, // 0x15
OuterTorso = 22, // 0x16
OuterLegs = 23, // 0x17
InnerLegs = 24, // 0x18
Mount = 25, // 0x19
ShopBuy = 26, // 0x1A
ShopResale = 27, // 0x1B
ShopSell = 28, // 0x1C
Bank = 29, // 0x1D
LastValid = 29 // 0x1D
}
}
+131
View File
@@ -0,0 +1,131 @@
using System;
using System.IO;
using System.Linq;
using ClassicAssist.Shared;
namespace ClassicAssist.UO.Data
{
public enum Map
{
Felucca,
Trammel,
Ilshenar,
Malas,
Tokuno,
Ter_Mur
}
public static class MapInfo
{
private static string _dataPath;
private static readonly UOPIndex[] _uopIndex = new UOPIndex[6];
private static int[,] _defaultMapSize { get; } =
{
{ 7168, 4096 }, { 7168, 4096 }, { 2304, 1600 }, { 2560, 2048 }, { 1448, 1448 }, { 1280, 4096 }
};
public static void Initialize( string dataPath )
{
_dataPath = dataPath;
}
public static LandTile GetLandTile( int map, int x, int y )
{
int blockIndex = x / 8 * ( _defaultMapSize[map, 1] / 8 ) + y / 8;
int cellX = x % 8;
int cellY = y % 8;
string fileName = GetMapFilename( map );
if ( string.IsNullOrEmpty( fileName ) )
{
return new LandTile();
}
using ( FileStream fileStream = File.OpenRead( fileName ) )
{
if ( fileName.EndsWith( ".uop", StringComparison.InvariantCultureIgnoreCase ) &&
_uopIndex[map] == null )
{
_uopIndex[map] = new UOPIndex( File.OpenRead( fileName ) );
}
int offset = blockIndex * 196 + 4 + cellY * 24 + cellX * 3;
if ( _uopIndex[map] != null )
{
offset = _uopIndex[map].Lookup( offset );
}
fileStream.Seek( offset, SeekOrigin.Begin );
using ( BinaryReader binaryReader = new BinaryReader( fileStream ) )
{
int id = binaryReader.ReadUInt16();
LandTile landTile = TileData.GetLandTile( id );
landTile.ID = id;
landTile.X = x;
landTile.Y = y;
landTile.Z = binaryReader.ReadSByte();
return landTile;
}
}
}
public static (int, int) GetMapSurface( int map, int x, int y )
{
LandTile landTile = GetLandTile( map, x, y );
StaticTile[] staticTiles = Statics.GetStatics( map, x, y );
if ( staticTiles == null || staticTiles.Length == 0 )
{
return ( landTile.Z, 0 );
}
StaticTile staticTile = staticTiles
.Where( i => i.Flags.HasFlag( TileFlags.Surface ) && Math.Abs( Engine.Player.Z - i.Z ) < 20 )
.OrderByDescending( i => i.Z ).FirstOrDefault();
if ( staticTile.ID == 0 && landTile.Flags.HasFlag( TileFlags.Impassable ) )
{
staticTile = staticTiles
.Where( i => i.Flags.HasFlag( TileFlags.Wet ) && Math.Abs( Engine.Player.Z - i.Z ) < 20 )
.OrderByDescending( i => i.Z ).FirstOrDefault();
}
if ( staticTile.ID == 0 )
{
return ( landTile.Z, 0 );
}
return ( staticTile.Z + staticTile.Height, staticTile.ID );
}
private static string GetMapFilename( int map )
{
if ( string.IsNullOrEmpty( _dataPath ) )
{
return null;
}
string uopFilename = Path.Combine( _dataPath, $"map{map}LegacyMUL.uop" );
if ( File.Exists( uopFilename ) )
{
return uopFilename;
}
string mulFilename = Path.Combine( _dataPath, $"map{map}.mul" );
if ( File.Exists( mulFilename ) )
{
return mulFilename;
}
return null;
}
}
}
@@ -0,0 +1,9 @@
namespace ClassicAssist.UO.Network.PacketFilter
{
public enum PacketDirection : byte
{
Incoming,
Outgoing,
Any
}
}
@@ -0,0 +1,197 @@
using System;
using System.IO;
using System.Text;
namespace ClassicAssist.UO.Data
{
public class PacketReader
{
private readonly MemoryStream _stream;
public PacketReader( byte[] data, int size, bool fixedSize )
{
_stream = new MemoryStream( data, 0, size, false );
_stream.Seek( fixedSize ? 1 : 3, SeekOrigin.Current );
}
public long Index => _stream.Position;
public long Size => _stream.Length;
public byte[] GetData()
{
return _stream.ToArray();
}
public static bool IsSafeChar( int c )
{
return c >= 0x20 && c < 0xFFFE;
}
public bool ReadBoolean()
{
return _stream.ReadByte() != 0;
}
public byte ReadByte()
{
return (byte) _stream.ReadByte();
}
public byte[] ReadByteArray( int length )
{
byte[] bytes = new byte[length];
for ( int i = 0; i < length; i++ )
{
bytes[i] = ReadByte();
}
return bytes;
}
public short ReadInt16()
{
byte[] buffer = new byte[2];
_stream.Read( buffer, 0, 2 );
return (short) ( ( buffer[0] << 8 ) | buffer[1] );
}
public int ReadInt32()
{
byte[] buffer = new byte[4];
_stream.Read( buffer, 0, 4 );
return ( buffer[0] << 24 ) | ( buffer[1] << 16 ) | ( buffer[2] << 8 ) | buffer[3];
}
public sbyte ReadSByte()
{
return (sbyte) ReadByte();
}
public string ReadString()
{
StringBuilder sb = new StringBuilder();
int c;
while ( Index + 1 < Size && ( c = ReadByte() ) != 0 )
{
sb.Append( (char) c );
}
return sb.ToString();
}
public string ReadString( int fixedLength )
{
byte[] buffer = new byte[fixedLength];
_stream.Read( buffer, 0, fixedLength );
return Encoding.ASCII.GetString( buffer ).TrimEnd( '\0' );
}
public string ReadStringSafe()
{
throw new NotImplementedException();
}
public string ReadStringSafe( int fixedLength )
{
StringBuilder output = new StringBuilder();
for ( int i = 0; i < fixedLength; i++ )
{
char c = (char) ReadByte();
if ( c == '\0' )
{
ReadByteArray( fixedLength - i - 1 );
break;
}
if ( IsSafeChar( c ) )
{
output.Append( c );
}
}
return output.ToString();
}
public ushort ReadUInt16()
{
return (ushort) ReadInt16();
}
public uint ReadUInt32()
{
return (uint) ReadInt32();
}
public string ReadUnicodeString()
{
StringBuilder sb = new StringBuilder();
int c;
while ( Index + 1 < Size && ( c = ( ReadByte() << 8 ) | ReadByte() ) != 0 )
{
sb.Append( (char) c );
}
return sb.ToString();
}
public string ReadUnicodeString( int fixedLength )
{
byte[] buffer = new byte[fixedLength];
_stream.Read( buffer, 0, fixedLength );
return Encoding.Unicode.GetString( buffer );
}
public string ReadUnicodeStringLE()
{
StringBuilder sb = new StringBuilder();
int c;
while ( Index + 1 < Size && ( c = ReadByte() | ( ReadByte() << 8 ) ) != 0 )
{
sb.Append( (char) c );
}
return sb.ToString();
}
public string ReadUnicodeStringLESafe( int fixedLength )
{
throw new NotImplementedException();
}
public string ReadUnicodeStringLESafe()
{
throw new NotImplementedException();
}
public string ReadUnicodeStringSafe()
{
throw new NotImplementedException();
}
public string ReadUnicodeStringSafe( int fixedLength )
{
throw new NotImplementedException();
}
public long Seek( int offset, SeekOrigin origin )
{
return _stream.Seek( offset, origin );
}
}
}
@@ -0,0 +1,480 @@
/***************************************************************************
* PacketWriter.cs
* -------------------
* begin : May 1, 2002
* copyright : (C) The RunUO Software Team
* email : info@runuo.com
*
* $Id$
*
***************************************************************************/
/***************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ClassicAssist.UO.Data
{
/// <summary>
/// Provides functionality for writing primitive binary data.
/// </summary>
public class PacketWriter : IDisposable
{
private static readonly Stack<PacketWriter> Pool = new Stack<PacketWriter>();
/// <summary>
/// Internal format buffer.
/// </summary>
private readonly byte[] _buffer = new byte[4];
/// <summary>
/// Internal stream which holds the entire packet.
/// </summary>
private readonly MemoryStream _stream;
private int m_Capacity;
/// <summary>
/// Instantiates a new PacketWriter instance with the default capacity of 4 bytes.
/// </summary>
public PacketWriter() : this( 32 )
{
}
/// <summary>
/// Instantiates a new PacketWriter instance with a given capacity.
/// </summary>
/// <param name="capacity">Initial capacity for the internal stream.</param>
public PacketWriter( int capacity )
{
_stream = new MemoryStream( capacity );
m_Capacity = capacity;
}
/// <summary>
/// Gets the total stream length.
/// </summary>
public long Length => _stream.Length;
/// <summary>
/// Gets or sets the current stream position.
/// </summary>
public long Position
{
get => _stream.Position;
set => _stream.Position = value;
}
/// <summary>
/// The internal stream used by this PacketWriter instance.
/// </summary>
public MemoryStream UnderlyingStream => _stream;
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
public static PacketWriter CreateInstance()
{
return CreateInstance( 32 );
}
public static PacketWriter CreateInstance( int capacity )
{
PacketWriter pw = null;
lock ( Pool )
{
if ( Pool.Count > 0 )
{
pw = Pool.Pop();
if ( pw != null )
{
pw.m_Capacity = capacity;
pw._stream.SetLength( 0 );
}
}
}
return pw ?? new PacketWriter( capacity );
}
public static void ReleaseInstance( PacketWriter pw )
{
lock ( Pool )
{
if ( !Pool.Contains( pw ) )
{
Pool.Push( pw );
}
else
{
try
{
using ( StreamWriter op = new StreamWriter( "neterr.log" ) )
{
op.WriteLine( "{0}\tInstance pool contains writer", DateTime.UtcNow );
}
}
catch
{
Console.WriteLine( @"net error" );
}
}
}
}
/// <summary>
/// Writes a 1-byte boolean value to the underlying stream. False is represented by 0, true by 1.
/// </summary>
public void Write( bool value )
{
_stream.WriteByte( (byte) ( value ? 1 : 0 ) );
}
/// <summary>
/// Writes a 1-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write( byte value )
{
_stream.WriteByte( value );
}
/// <summary>
/// Writes a 1-byte signed integer value to the underlying stream.
/// </summary>
public void Write( sbyte value )
{
_stream.WriteByte( (byte) value );
}
/// <summary>
/// Writes a 2-byte signed integer value to the underlying stream.
/// </summary>
public void Write( short value )
{
_buffer[0] = (byte) ( value >> 8 );
_buffer[1] = (byte) value;
_stream.Write( _buffer, 0, 2 );
}
/// <summary>
/// Writes a 2-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write( ushort value )
{
_buffer[0] = (byte) ( value >> 8 );
_buffer[1] = (byte) value;
_stream.Write( _buffer, 0, 2 );
}
/// <summary>
/// Writes a 4-byte signed integer value to the underlying stream.
/// </summary>
public void Write( int value )
{
_buffer[0] = (byte) ( value >> 24 );
_buffer[1] = (byte) ( value >> 16 );
_buffer[2] = (byte) ( value >> 8 );
_buffer[3] = (byte) value;
_stream.Write( _buffer, 0, 4 );
}
/// <summary>
/// Writes a 4-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write( uint value )
{
_buffer[0] = (byte) ( value >> 24 );
_buffer[1] = (byte) ( value >> 16 );
_buffer[2] = (byte) ( value >> 8 );
_buffer[3] = (byte) value;
_stream.Write( _buffer, 0, 4 );
}
/// <summary>
/// Writes a sequence of bytes to the underlying stream
/// </summary>
public void Write( byte[] buffer, int offset, int size )
{
_stream.Write( buffer, offset, size );
}
/// <summary>
/// Writes a fixed-length ASCII-encoded string value to the underlying stream. To fit (size), the string content is
/// either truncated or padded with null characters.
/// </summary>
public void WriteAsciiFixed( string value, int size )
{
if ( value == null )
{
Console.WriteLine( @"Network: Attempted to WriteAsciiFixed() with null value" );
value = string.Empty;
}
int length = value.Length;
_stream.SetLength( _stream.Length + size );
if ( length >= size )
{
_stream.Position +=
Encoding.ASCII.GetBytes( value, 0, size, _stream.GetBuffer(), (int) _stream.Position );
}
else
{
Encoding.ASCII.GetBytes( value, 0, length, _stream.GetBuffer(), (int) _stream.Position );
_stream.Position += size;
}
/*byte[] buffer = Encoding.ASCII.GetBytes( value );
if ( buffer.Length >= size )
{
m_Stream.Write( buffer, 0, size );
}
else
{
m_Stream.Write( buffer, 0, buffer.Length );
Fill( size - buffer.Length );
}*/
}
/// <summary>
/// Writes a dynamic-length ASCII-encoded string value to the underlying stream, followed by a 1-byte null character.
/// </summary>
public void WriteAsciiNull( string value )
{
if ( value == null )
{
Console.WriteLine( @"Network: Attempted to WriteAsciiNull() with null value" );
value = string.Empty;
}
int length = value.Length;
_stream.SetLength( _stream.Length + length + 1 );
Encoding.ASCII.GetBytes( value, 0, length, _stream.GetBuffer(), (int) _stream.Position );
_stream.Position += length + 1;
/*byte[] buffer = Encoding.ASCII.GetBytes( value );
m_Stream.Write( buffer, 0, buffer.Length );
m_Stream.WriteByte( 0 );*/
}
/// <summary>
/// Writes a dynamic-length little-endian unicode string value to the underlying stream, followed by a 2-byte null
/// character.
/// </summary>
public void WriteLittleUniNull( string value )
{
if ( value == null )
{
Console.WriteLine( @"Network: Attempted to WriteLittleUniNull() with null value" );
value = string.Empty;
}
int length = value.Length;
_stream.SetLength( _stream.Length + ( length + 1 ) * 2 );
_stream.Position +=
Encoding.Unicode.GetBytes( value, 0, length, _stream.GetBuffer(), (int) _stream.Position );
_stream.Position += 2;
/*byte[] buffer = Encoding.Unicode.GetBytes( value );
m_Stream.Write( buffer, 0, buffer.Length );
m_Buffer[0] = 0;
m_Buffer[1] = 0;
m_Stream.Write( m_Buffer, 0, 2 );*/
}
/// <summary>
/// Writes a fixed-length little-endian unicode string value to the underlying stream. To fit (size), the string
/// content is either truncated or padded with null characters.
/// </summary>
public void WriteLittleUniFixed( string value, int size )
{
if ( value == null )
{
Console.WriteLine( @"Network: Attempted to WriteLittleUniFixed() with null value" );
value = string.Empty;
}
size *= 2;
int length = value.Length;
_stream.SetLength( _stream.Length + size );
if ( length * 2 >= size )
{
_stream.Position +=
Encoding.Unicode.GetBytes( value, 0, length, _stream.GetBuffer(), (int) _stream.Position );
}
else
{
Encoding.Unicode.GetBytes( value, 0, length, _stream.GetBuffer(), (int) _stream.Position );
_stream.Position += size;
}
/*size *= 2;
byte[] buffer = Encoding.Unicode.GetBytes( value );
if ( buffer.Length >= size )
{
m_Stream.Write( buffer, 0, size );
}
else
{
m_Stream.Write( buffer, 0, buffer.Length );
Fill( size - buffer.Length );
}*/
}
/// <summary>
/// Writes a dynamic-length big-endian unicode string value to the underlying stream, followed by a 2-byte null
/// character.
/// </summary>
public void WriteBigUniNull( string value )
{
if ( value == null )
{
Console.WriteLine( @"Network: Attempted to WriteBigUniNull() with null value" );
value = string.Empty;
}
int length = value.Length;
_stream.SetLength( _stream.Length + ( length + 1 ) * 2 );
_stream.Position +=
Encoding.BigEndianUnicode.GetBytes( value, 0, length, _stream.GetBuffer(), (int) _stream.Position );
_stream.Position += 2;
/*byte[] buffer = Encoding.BigEndianUnicode.GetBytes( value );
m_Stream.Write( buffer, 0, buffer.Length );
m_Buffer[0] = 0;
m_Buffer[1] = 0;
m_Stream.Write( m_Buffer, 0, 2 );*/
}
/// <summary>
/// Writes a fixed-length big-endian unicode string value to the underlying stream. To fit (size), the string content
/// is either truncated or padded with null characters.
/// </summary>
public void WriteBigUniFixed( string value, int size )
{
if ( value == null )
{
Console.WriteLine( @"Network: Attempted to WriteBigUniFixed() with null value" );
value = string.Empty;
}
size *= 2;
int length = value.Length;
_stream.SetLength( _stream.Length + size );
if ( length * 2 >= size )
{
_stream.Position +=
Encoding.BigEndianUnicode.GetBytes( value, 0, length, _stream.GetBuffer(), (int) _stream.Position );
}
else
{
Encoding.BigEndianUnicode.GetBytes( value, 0, length, _stream.GetBuffer(), (int) _stream.Position );
_stream.Position += size;
}
/*size *= 2;
byte[] buffer = Encoding.BigEndianUnicode.GetBytes( value );
if ( buffer.Length >= size )
{
m_Stream.Write( buffer, 0, size );
}
else
{
m_Stream.Write( buffer, 0, buffer.Length );
Fill( size - buffer.Length );
}*/
}
/// <summary>
/// Fills the stream from the current position up to (capacity) with 0x00's
/// </summary>
public void Fill()
{
Fill( (int) ( m_Capacity - _stream.Length ) );
}
/// <summary>
/// Writes a number of 0x00 byte values to the underlying stream.
/// </summary>
public void Fill( int length )
{
if ( _stream.Position == _stream.Length )
{
_stream.SetLength( _stream.Length + length );
_stream.Seek( 0, SeekOrigin.End );
}
else
{
_stream.Write( new byte[length], 0, length );
}
}
/// <summary>
/// Offsets the current position from an origin.
/// </summary>
public long Seek( long offset, SeekOrigin origin )
{
return _stream.Seek( offset, origin );
}
/// <summary>
/// Gets the entire stream content as a byte array.
/// </summary>
public byte[] ToArray()
{
return _stream.ToArray();
}
protected virtual void Dispose( bool isDisposing )
{
if ( isDisposing )
{
_stream.Dispose();
}
GC.SuppressFinalize( this );
}
}
}
@@ -0,0 +1,48 @@
namespace ClassicAssist.UO.Data
{
public class PlayerStatus
{
public int ColdResist { get; set; }
public int DamageIncrease { get; set; }
public int DefenseChanceIncrease { get; set; }
public int Dex { get; set; }
public int EnergyResist { get; set; }
public int FasterCasting { get; set; }
public int FasterCastRecovery { get; set; }
public int Features { get; set; }
public int FireResist { get; set; }
public int Followers { get; set; }
public int Gold { get; set; }
public int Health { get; set; }
public int HitChanceIncrease { get; set; }
public int Int { get; set; }
public int LowerManaCost { get; set; }
public int LowerReagentCost { get; set; }
public int Luck { get; set; }
public int Mana { get; set; }
public int MaxColdResist { get; set; }
public int MaxDamage { get; set; }
public int MaxEnergyResist { get; set; }
public int MaxFireResist { get; set; }
public int MaxFollowers { get; set; }
public int MaxHealth { get; set; }
public int MaxMana { get; set; }
public int MaxPhysicalResist { get; set; }
public int MaxPoisonResist { get; set; }
public int MaxStamina { get; set; }
public int MaxWeight { get; set; }
public int MinDamage { get; set; }
public string Name { get; set; }
public int PhysicalResist { get; set; }
public int PoisonResist { get; set; }
public int Race { get; set; }
public int Sex { get; set; }
public int SpellDamageIncrease { get; set; }
public int Stamina { get; set; }
public int StatCap { get; set; }
public int Str { get; set; }
public int SwingSpeedIncrease { get; set; }
public int TithingPoints { get; set; }
public int Weight { get; set; }
}
}
@@ -0,0 +1,100 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ClassicAssist.Shared;
using ClassicAssist.UO.Network.Packets;
namespace ClassicAssist.UO.Data
{
public enum QuestPointerType
{
Resurrection,
Corpse,
Other
}
public class QuestPointerList : IEnumerable<QuestPointer>
{
private readonly object _lock = new object();
private readonly List<QuestPointer> _questPointers = new List<QuestPointer>();
public IEnumerator<QuestPointer> GetEnumerator()
{
return _questPointers.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add( QuestPointer questPointer )
{
lock ( _lock )
{
_questPointers.Add( questPointer );
}
Engine.SendPacketToClient( new DisplayQuestPointer( true, questPointer.X, questPointer.Y,
questPointer.Serial ) );
}
public void Remove( QuestPointer questPointer )
{
lock ( _lock )
{
_questPointers.Remove( questPointer );
}
Engine.SendPacketToClient( new DisplayQuestPointer( false, questPointer.X, questPointer.Y,
questPointer.Serial ) );
}
public void Clear()
{
Remove( _questPointers );
}
public void Remove( IEnumerable<QuestPointer> questPointers )
{
foreach ( QuestPointer questPointer in questPointers.ToArray() )
{
Remove( questPointer );
}
}
public void RemoveByType( QuestPointerType type )
{
IEnumerable<QuestPointer> pointers = _questPointers.Where( p => p.Type == type );
Remove( pointers );
}
}
public class QuestPointer
{
public int Serial { get; set; }
public QuestPointerType Type { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
}
+167
View File
@@ -0,0 +1,167 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using ClassicAssist.Shared;
using ClassicAssist.Data;
using ClassicAssist.Shared.UO;
using ClassicAssist.UO.Network.Packets;
using ClassicAssist.UO.Objects;
namespace ClassicAssist.UO.Data
{
public enum RehueType
{
Custom,
Friends,
Enemies
}
public class RehueEntry
{
public int Hue { get; set; }
public int Serial { get; set; }
public RehueType Type { get; set; }
}
public class RehueList
{
private readonly ConcurrentDictionary<int, RehueEntry> _rehueList = new ConcurrentDictionary<int, RehueEntry>();
public void Add( int serial, int hue, RehueType type = RehueType.Custom )
{
RehueEntry entry = new RehueEntry { Serial = serial, Hue = hue, Type = type };
_rehueList.AddOrUpdate( serial, i => entry, ( i, rehueEntry ) => entry );
}
public bool Remove( int serial )
{
return _rehueList.TryRemove( serial, out _ );
}
public bool Contains( int serial )
{
return _rehueList.ContainsKey( serial );
}
public void RemoveByType( RehueType type )
{
IEnumerable<int> keys = _rehueList.Where( kvp => kvp.Value.Type == type ).Select( kvp => kvp.Key );
foreach ( int key in keys )
{
Remove( key );
}
}
public void CheckItem( Item item )
{
if ( !_rehueList.TryGetValue( item.Serial, out RehueEntry entry ) )
{
return;
}
if ( item.Owner != 0 && !UOMath.IsMobile( item.Serial ) )
{
Engine.SendPacketToClient( new ContainerContentUpdate( item.Serial, item.ID, item.Direction, item.Count,
item.X, item.Y, item.Grid, item.Owner, entry.Hue ) );
}
else
{
// TODO
Commands.Resync();
}
}
public bool CheckSAWorldItem( ref byte[] packet, ref int length )
{
int serial = ( packet[4] << 24 ) | ( packet[5] << 16 ) | ( packet[6] << 8 ) | packet[7];
if ( !_rehueList.TryGetValue( serial, out RehueEntry entry ) )
{
return false;
}
Engine.SendPacketToClient( new SAWorldItem( packet, length, entry.Hue ) );
return true;
}
public bool CheckMobileIncoming( Mobile mobile, ItemCollection equipment )
{
bool result = _rehueList.TryGetValue( mobile.Serial, out RehueEntry entry );
if ( result )
{
Engine.SendPacketToClient( new MobileIncoming( mobile, equipment, entry.Hue ) );
return true;
}
if ( Options.CurrentOptions.RehueFriends &&
Options.CurrentOptions.Friends.Any( e => e.Serial == mobile.Serial ) )
{
Engine.SendPacketToClient( new MobileIncoming( mobile, equipment,
Options.CurrentOptions.RehueFriendsHue ) );
return true;
}
return false;
}
public bool CheckMobileUpdate( Mobile mobile )
{
bool result = _rehueList.TryGetValue( mobile.Serial, out RehueEntry entry );
if ( result )
{
Engine.SendPacketToClient( new MobileUpdate( mobile.Serial, mobile.ID,
entry.Hue > 0 ? entry.Hue : mobile.Hue, mobile.Status, mobile.X, mobile.Y, mobile.Z,
mobile.Direction ) );
return true;
}
if ( !Options.CurrentOptions.RehueFriends ||
Options.CurrentOptions.Friends.All( e => e.Serial != mobile.Serial ) )
{
return false;
}
Engine.SendPacketToClient( new MobileUpdate( mobile.Serial, mobile.ID,
Options.CurrentOptions.RehueFriendsHue, mobile.Status, mobile.X, mobile.Y, mobile.Z,
mobile.Direction ) );
return true;
}
public void CheckContainer( ItemCollection collection )
{
int backpack = Engine.Player?.Backpack?.Serial ?? 0;
foreach ( Item item in collection.GetItems() )
{
if ( item.IsDescendantOf( backpack ) )
{
CheckItem( item );
}
}
}
public bool CheckMobileMoving( Mobile mobile )
{
bool result = _rehueList.TryGetValue( mobile.Serial, out RehueEntry entry );
if ( result )
{
Engine.SendPacketToClient( new MobileMoving( mobile, entry.Hue ) );
return true;
}
if ( !Options.CurrentOptions.RehueFriends ||
Options.CurrentOptions.Friends.All( e => e.Serial != mobile.Serial ) )
{
return false;
}
Engine.SendPacketToClient( new MobileMoving( mobile, Options.CurrentOptions.RehueFriendsHue ) );
return true;
}
}
}
@@ -0,0 +1,30 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
namespace ClassicAssist.UO.Data
{
public class ShardEntry
{
public int Index { get; set; }
public int IP { get; set; }
public string Name { get; set; }
public int PercentFull { get; set; }
public int Timezone { get; set; }
}
}
+117
View File
@@ -0,0 +1,117 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace ClassicAssist.UO.Data
{
public enum LockStatus : byte
{
Up,
Down,
Locked
}
public enum StatType : byte
{
Str,
Dex,
Int
}
public class SkillData
{
public bool Invokable { get; internal set; }
public string Name { get; internal set; }
}
public static class Skills
{
private static Lazy<Dictionary<int, SkillData>> _lazySkillData;
private static string _dataPath;
internal static void Initialize( string dataPath )
{
_dataPath = dataPath;
_lazySkillData = new Lazy<Dictionary<int, SkillData>>( LoadSkills );
}
public static string GetSkillName( int skillID )
{
return _lazySkillData.Value.ContainsKey( skillID ) ? _lazySkillData.Value[skillID].Name : string.Empty;
}
public static bool IsInvokable( int skillID )
{
if ( skillID >= 0 && skillID <= _lazySkillData.Value.Count - 1 )
{
return _lazySkillData.Value[skillID].Invokable;
}
return false;
}
/// <summary>
/// Get string array of all skill names for UO installation defined in options.
/// Array returned will be indexed by skill ID.
/// </summary>
public static string[] GetSkillNames()
{
string[] skillNames = (string[]) _lazySkillData.Value.Values.Select( s => s.Name );
return skillNames;
}
public static SkillData[] GetSkillsArray()
{
return _lazySkillData?.Value.Values.ToArray();
}
internal static Dictionary<int, SkillData> GetSkills()
{
return _lazySkillData.Value;
}
internal static Dictionary<int, SkillData> LoadSkills()
{
string skillIndexFile = Path.Combine( _dataPath, "Skills.idx" );
string skillMulFile = Path.Combine( _dataPath, "skills.mul" );
Dictionary<int, SkillData> skills = new Dictionary<int, SkillData>();
if ( !File.Exists( skillIndexFile ) )
{
throw new FileNotFoundException( "File not found!", skillIndexFile );
}
if ( !File.Exists( skillMulFile ) )
{
throw new FileNotFoundException( "File not found!", skillMulFile );
}
byte[] indexBytes = File.ReadAllBytes( skillIndexFile );
byte[] mulBytes = File.ReadAllBytes( skillMulFile );
for ( int x = 0; x < indexBytes.Length / 12; x++ )
{
int offset = x * 12;
int start = BitConverter.ToInt32( indexBytes, offset );
int length = BitConverter.ToInt32( indexBytes, offset + 4 );
if ( length == 0 )
{
break;
}
skills.Add( x,
new SkillData
{
Invokable = mulBytes[start] == 1,
Name = Encoding.ASCII.GetString( mulBytes, start + 1, length - 2 )
} );
}
return skills;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ClassicAssist.UO.Data
{
public static class Speech
{
private static readonly Lazy<SpeechEntry[]> _entries = new Lazy<SpeechEntry[]>( LoadEntries );
private static string _dataPath;
public static int[] GetKeywords( string input )
{
IEnumerable<SpeechEntry> matches =
_entries.Value.Where( e => Regex.IsMatch( input, WildCardToRegular( e.Keywords ) ) );
return matches.Select( e => e.Id ).Distinct().ToArray();
}
public static void Initialize( string dataPath )
{
_dataPath = dataPath;
}
private static SpeechEntry[] LoadEntries()
{
string fullPath = Path.Combine( _dataPath, "speech.mul" );
using ( FileStream reader =
new FileStream( fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) )
{
using ( BinaryReader binaryReader = new BinaryReader( reader ) )
{
List<SpeechEntry> entries = new List<SpeechEntry>();
while ( reader.Position < reader.Length )
{
int id = ( binaryReader.ReadByte() << 8 ) | binaryReader.ReadByte();
int length = ( binaryReader.ReadByte() << 8 ) | binaryReader.ReadByte();
byte[] buffer = new byte[length];
binaryReader.Read( buffer, 0, length );
string text = Encoding.UTF8.GetString( buffer );
if ( text.Contains( "guards" ) )
{
}
entries.Add( new SpeechEntry { Id = id, Keywords = text } );
}
return entries.ToArray();
}
}
}
private static string WildCardToRegular( string value )
{
return "^" + Regex.Escape( value ).Replace( "\\*", ".*" ) + "$";
}
internal struct SpeechEntry
{
public int Id { get; set; }
public string Keywords { get; set; }
}
}
}
@@ -0,0 +1,17 @@
namespace ClassicAssist.UO.Data
{
public struct StaticTile
{
public TileFlags Flags { get; set; }
public byte Weight { get; set; }
public byte Quality { get; set; }
public byte Quantity { get; set; }
public ushort ID { get; set; }
public string Name { get; set; }
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
public int Hue { get; set; }
public int Height { get; set; }
}
}
+149
View File
@@ -0,0 +1,149 @@
#region License
// Copyright (C) 2020 Reetus
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ClassicAssist.UO.Data
{
public class Statics
{
private static string _dataPath;
private static readonly Lazy<StaticRecord[][]>[] _staticData = new Lazy<StaticRecord[][]>[6];
private static int[,] _defaultMapSize { get; } =
{
{ 7168, 4096 }, { 7168, 4096 }, { 2304, 1600 }, { 2560, 2048 }, { 1448, 1448 }, { 1280, 4096 }
};
internal static void Initialize( string dataPath )
{
_dataPath = dataPath;
for ( int i = 0; i < 6; i++ )
{
int map = i;
_staticData[i] = new Lazy<StaticRecord[][]>( () => LoadStatics( map ) );
}
}
private static StaticRecord[][] LoadStatics( int map )
{
string staticIndexFile = Path.Combine( _dataPath, $"staidx{map}.mul" );
string staticMulFile = Path.Combine( _dataPath, $"statics{map}.mul" );
if ( !File.Exists( staticIndexFile ) )
{
return null;
}
if ( !File.Exists( staticMulFile ) )
{
return null;
}
byte[] indexBytes = File.ReadAllBytes( staticIndexFile );
StaticRecord[][] staticItems = new StaticRecord[indexBytes.Length / 12][];
using ( FileStream fileStream =
new FileStream( staticMulFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) )
{
using ( BinaryReader binaryReader = new BinaryReader( fileStream ) )
{
for ( int x = 0; x < indexBytes.Length / 12; x++ )
{
int offset = x * 12;
int start = BitConverter.ToInt32( indexBytes, offset );
int length = BitConverter.ToInt32( indexBytes, offset + 4 );
if ( start == -1 )
{
continue;
}
fileStream.Seek( start, SeekOrigin.Begin );
staticItems[x] = new StaticRecord[length / 7];
for ( int y = 0; y < length / 7; y++ )
{
staticItems[x][y].ID = binaryReader.ReadUInt16();
staticItems[x][y].X = binaryReader.ReadByte();
staticItems[x][y].Y = binaryReader.ReadByte();
staticItems[x][y].Z = binaryReader.ReadSByte();
staticItems[x][y].Hue = binaryReader.ReadUInt16();
}
}
}
}
return staticItems;
}
public static StaticTile[] GetStatics( int map, int x, int y )
{
if ( _staticData?[map]?.Value == null )
{
return null;
}
int blockIndex = x / 8 * ( _defaultMapSize[map, 1] / 8 ) + y / 8;
int cellX = x % 8;
int cellY = y % 8;
StaticRecord[] blockStatics = _staticData[map].Value[blockIndex];
if ( blockStatics == null )
{
return null;
}
IEnumerable<StaticRecord> statics = blockStatics.Where( i => i.X == cellX && i.Y == cellY );
List<StaticTile> tiles = new List<StaticTile>();
foreach ( StaticRecord staticRecord in statics )
{
StaticTile tile = TileData.GetStaticTile( staticRecord.ID );
tile.X = x;
tile.Y = y;
tile.Z = staticRecord.Z;
tile.Hue = staticRecord.Hue;
tiles.Add( tile );
}
return tiles.ToArray();
}
private struct StaticRecord
{
public ushort ID { get; set; }
public byte X { get; set; }
public byte Y { get; set; }
public sbyte Z { get; set; }
public int Hue { get; set; }
}
}
}
+186
View File
@@ -0,0 +1,186 @@
using System;
using System.IO;
using System.Text;
namespace ClassicAssist.UO.Data
{
public static class TileData
{
private static string _dataPath;
private static readonly Lazy<LandTile[]> _landTiles = new Lazy<LandTile[]>( LoadLandTiles );
private static readonly Lazy<StaticTile[]> _staticTiles = new Lazy<StaticTile[]>( LoadStaticTiles );
private static bool _oldFormat;
public static void Initialize( string dataPath )
{
_dataPath = dataPath;
}
private static StaticTile[] LoadStaticTiles()
{
string fileName = Path.Combine( _dataPath, "tiledata.mul" );
if ( !File.Exists( fileName ) )
{
throw new FileNotFoundException( "File not found.", fileName );
}
byte[] fileBytes = File.ReadAllBytes( fileName );
StaticTile[] staticTiles = new StaticTile[( fileBytes.Length - 428032 ) / 1188 * 32];
MemoryStream ms = new MemoryStream( fileBytes, false );
using ( BinaryReader reader = new BinaryReader( ms ) )
{
ms.Seek( 36, SeekOrigin.Begin );
string name = Encoding.ASCII.GetString( reader.ReadBytes( 20 ) ).TrimEnd( '\0' );
if ( name == "VOID!!!!!!" )
{
_oldFormat = true;
}
if ( _oldFormat )
{
const int offset = 428032;
ms.Seek( offset, SeekOrigin.Begin );
for ( int i = 0; i < 0x4000; ++i )
{
if ( ( i & 0x1F ) == 0 )
{
reader.ReadInt32(); // header
}
staticTiles[i].ID = (ushort) i;
staticTiles[i].Flags = (TileFlags) reader.ReadInt32();
staticTiles[i].Weight = reader.ReadByte();
staticTiles[i].Quality = reader.ReadByte();
reader.ReadInt16();
reader.ReadByte();
staticTiles[i].Quantity = reader.ReadByte();
reader.ReadInt16();
reader.ReadByte();
reader.ReadByte();
reader.ReadInt16();
staticTiles[i].Height = reader.ReadByte();
staticTiles[i].Name = Encoding.ASCII.GetString( reader.ReadBytes( 20 ) ).TrimEnd( '\0' );
}
}
else
{
const int offset = 493568;
ms.Seek( offset, SeekOrigin.Begin );
for ( int i = 0; i < 0x10000; ++i )
{
if ( ( i & 0x1F ) == 0 )
{
reader.ReadInt32(); // header
}
staticTiles[i].ID = (ushort) i;
staticTiles[i].Flags = (TileFlags) reader.ReadInt64();
staticTiles[i].Weight = reader.ReadByte();
staticTiles[i].Quality = reader.ReadByte();
reader.ReadInt16();
reader.ReadByte();
staticTiles[i].Quantity = reader.ReadByte();
reader.ReadInt32();
reader.ReadByte();
/*int value = */
reader.ReadByte();
staticTiles[i].Height = reader.ReadByte();
staticTiles[i].Name = Encoding.ASCII.GetString( reader.ReadBytes( 20 ) ).TrimEnd( '\0' );
}
}
}
ms.Close();
return staticTiles;
}
private static LandTile[] LoadLandTiles()
{
string fileName = Path.Combine( _dataPath, "tiledata.mul" );
if ( !File.Exists( fileName ) )
{
throw new FileNotFoundException( "File not found.", fileName );
}
byte[] fileBytes = File.ReadAllBytes( fileName );
LandTile[] landTiles = new LandTile[16384];
MemoryStream ms = new MemoryStream( fileBytes, false );
using ( BinaryReader bin = new BinaryReader( ms ) )
{
ms.Seek( 36, SeekOrigin.Begin );
string name = Encoding.ASCII.GetString( bin.ReadBytes( 20 ) ).TrimEnd( '\0' );
if ( name == "VOID!!!!!!" )
{
_oldFormat = true;
}
ms.Seek( 0, SeekOrigin.Begin );
if ( _oldFormat )
{
for ( int i = 0; i < 0x4000; ++i )
{
if ( i == 0 || i > 0 && ( i & 0x1f ) == 0 )
{
bin.ReadInt32(); // block header
}
landTiles[i].Flags = (TileFlags) bin.ReadInt32();
landTiles[i].ID = bin.ReadInt16();
landTiles[i].Name = Encoding.ASCII.GetString( bin.ReadBytes( 20 ) ).TrimEnd( '\0' );
}
}
else
{
for ( int i = 0; i < 0x4000; ++i )
{
if ( i == 1 || i > 0 && ( i & 0x1f ) == 0 )
{
bin.ReadInt32(); // block header
}
landTiles[i].Flags = (TileFlags) bin.ReadInt64();
landTiles[i].ID = bin.ReadInt16();
landTiles[i].Name = Encoding.ASCII.GetString( bin.ReadBytes( 20 ) ).TrimEnd( '\0' );
}
}
}
ms.Close();
return landTiles;
}
public static LandTile GetLandTile( int index )
{
return _landTiles.Value[index];
}
public static StaticTile GetStaticTile( int index )
{
try
{
return _staticTiles.Value[index];
}
catch ( IndexOutOfRangeException )
{
return _staticTiles.Value[0];
}
}
}
}
+41
View File
@@ -0,0 +1,41 @@
using System;
namespace ClassicAssist.UO.Data
{
[Flags]
public enum TileFlags : ulong
{
Background = 0x00000001,
Weapon = 0x00000002,
Transparent = 0x00000004,
Translucent = 0x00000008,
Wall = 0x00000010,
Damaging = 0x00000020,
Impassable = 0x00000040,
Wet = 0x00000080,
Unknown = 0x00000100,
Surface = 0x00000200,
Bridge = 0x00000400,
Stackable = 0x00000800,
Window = 0x00001000,
NoShoot = 0x00002000,
PrefixA = 0x00004000,
PrefixAn = 0x00008000,
Internal = 0x00010000,
Foliage = 0x00020000,
PartialHue = 0x00040000,
Unknown1 = 0x00080000,
Map = 0x00100000,
Container = 0x00200000,
Wearable = 0x00400000,
LightSource = 0x00800000,
Animated = 0x01000000,
NoDiagonal = 0x02000000,
Unknown2 = 0x04000000,
Armor = 0x08000000,
Roof = 0x10000000,
Door = 0x20000000,
StairBack = 0x40000000,
StairRight = 0x80000000
}
}
+160
View File
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace ClassicAssist.UO.Data
{
public class UOPIndex : IDisposable
{
private readonly UOPEntry[] _entries;
private readonly int _length;
private readonly BinaryReader _reader;
public UOPIndex( Stream stream )
{
_reader = new BinaryReader( stream );
_length = (int) stream.Length;
if ( _reader.ReadInt32() != 0x50594D )
{
throw new ArgumentException( "Invalid UOP file." );
}
Version = _reader.ReadInt32();
_reader.ReadInt32();
int nextTable = _reader.ReadInt32();
List<UOPEntry> entries = new List<UOPEntry>();
do
{
stream.Seek( nextTable, SeekOrigin.Begin );
int count = _reader.ReadInt32();
nextTable = _reader.ReadInt32();
_reader.ReadInt32();
for ( int i = 0; i < count; ++i )
{
int offset = _reader.ReadInt32();
if ( offset == 0 )
{
stream.Seek( 30, SeekOrigin.Current );
continue;
}
_reader.ReadInt64();
int length = _reader.ReadInt32();
entries.Add( new UOPEntry( offset, length ) );
stream.Seek( 18, SeekOrigin.Current );
}
}
while ( nextTable != 0 && nextTable < _length );
entries.Sort( OffsetComparer.Instance );
foreach ( UOPEntry t in entries )
{
stream.Seek( t.Offset + 2, SeekOrigin.Begin );
int dataOffset = _reader.ReadInt16();
t.Offset += 4 + dataOffset;
stream.Seek( dataOffset, SeekOrigin.Current );
t.Order = _reader.ReadInt32();
}
entries.Sort();
_entries = entries.ToArray();
}
public int Version { get; }
public int Lookup( int offset )
{
int total = 0;
foreach ( UOPEntry t in _entries )
{
int newTotal = total + t.Length;
if ( offset < newTotal )
{
return t.Offset + ( offset - total );
}
total = newTotal;
}
return _length;
}
public void Close()
{
_reader.Close();
}
private class OffsetComparer : IComparer<UOPEntry>
{
public static readonly IComparer<UOPEntry> Instance = new OffsetComparer();
public int Compare( UOPEntry x, UOPEntry y )
{
if ( x == null || y == null )
{
return -1;
}
return x.Offset.CompareTo( y.Offset );
}
}
private class UOPEntry : IComparable<UOPEntry>
{
public readonly int Length;
public int Offset;
public int Order;
public UOPEntry( int offset, int length )
{
Offset = offset;
Length = length;
Order = 0;
}
public int CompareTo( UOPEntry other )
{
return Order.CompareTo( other.Order );
}
}
#region IDisposable Support
private bool disposedValue; // To detect redundant calls
protected virtual void Dispose( bool disposing )
{
if ( disposedValue )
{
return;
}
if ( disposing )
{
_reader.Dispose();
}
disposedValue = true;
}
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
#endregion
}
}