2018-11-28 19:28:41 -05:00
using System ;
2018-11-22 00:18:42 -05:00
using System.Drawing ;
2018-11-28 19:28:41 -05:00
using System.Linq ;
2018-11-23 20:51:32 -05:00
using System.Runtime.InteropServices ;
2018-11-28 19:28:41 -05:00
using OpenDiablo2.Common.Interfaces ;
using OpenDiablo2.Common.Models ;
using SDL2 ;
2018-11-22 00:18:42 -05:00
namespace OpenDiablo2.SDL2_
{
2018-11-23 20:51:32 -05:00
public sealed class SDL2RenderWindow : IRenderWindow , IRenderTarget , IMouseInfoProvider , IKeyboardInfoProvider
2018-11-22 00:18:42 -05:00
{
private static readonly log4net . ILog log = log4net . LogManager . GetLogger ( System . Reflection . MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
2018-11-22 14:30:37 -05:00
2018-11-22 00:18:42 -05:00
private IntPtr window , renderer ;
2018-11-22 18:46:24 -05:00
public bool IsRunning { get ; private set ; }
2018-11-22 00:56:36 -05:00
public int MouseX { get ; internal set ; } = 0 ;
public int MouseY { get ; internal set ; } = 0 ;
public bool LeftMouseDown { get ; internal set ; } = false ;
public bool RightMouseDown { get ; internal set ; } = false ;
2018-11-22 18:46:24 -05:00
public bool ReserveMouse { get ; set ; } = false ;
2018-11-22 00:56:36 -05:00
2018-11-23 20:51:32 -05:00
public OnKeyPressed KeyPressCallback { get ; set ; }
2018-11-22 14:30:37 -05:00
private readonly IMPQProvider mpqProvider ;
private readonly IPaletteProvider paletteProvider ;
2018-11-22 22:53:05 -05:00
private readonly IResourceManager resourceManager ;
2018-11-27 20:02:18 -05:00
private readonly GlobalConfiguration globalConfig ;
2018-11-28 19:28:41 -05:00
private readonly Func < IGameState > getGameState ;
2018-11-27 00:56:54 -05:00
private readonly Func < IMapEngine > getMapEngine ;
2018-11-22 00:18:42 -05:00
2018-11-22 14:30:37 -05:00
public SDL2RenderWindow (
2018-11-27 20:02:18 -05:00
GlobalConfiguration globalConfig ,
2018-11-22 14:30:37 -05:00
IMPQProvider mpqProvider ,
2018-11-22 22:53:05 -05:00
IPaletteProvider paletteProvider ,
2018-11-25 14:15:13 -05:00
IResourceManager resourceManager ,
2018-11-28 19:28:41 -05:00
Func < IGameState > getGameState ,
2018-11-27 00:56:54 -05:00
Func < IMapEngine > getMapEngine
2018-11-22 14:30:37 -05:00
)
2018-11-22 00:18:42 -05:00
{
2018-11-27 20:02:18 -05:00
this . globalConfig = globalConfig ;
2018-11-22 14:30:37 -05:00
this . mpqProvider = mpqProvider ;
this . paletteProvider = paletteProvider ;
2018-11-22 22:53:05 -05:00
this . resourceManager = resourceManager ;
2018-11-28 19:28:41 -05:00
this . getGameState = getGameState ;
2018-11-27 00:56:54 -05:00
this . getMapEngine = getMapEngine ;
2018-11-22 00:18:42 -05:00
SDL . SDL_Init ( SDL . SDL_INIT_EVERYTHING ) ;
if ( SDL . SDL_SetHint ( SDL . SDL_HINT_RENDER_SCALE_QUALITY , "0" ) = = SDL . SDL_bool . SDL_FALSE )
throw new ApplicationException ( $"Unable to Init hinting: {SDL.SDL_GetError()}" ) ;
2018-11-28 19:28:41 -05:00
window = SDL . SDL_CreateWindow ( "OpenDiablo2" , SDL . SDL_WINDOWPOS_UNDEFINED , SDL . SDL_WINDOWPOS_UNDEFINED , 800 , 600 , SDL . SDL_WindowFlags . SDL_WINDOW_SHOWN ) ;
2018-11-22 00:18:42 -05:00
if ( window = = IntPtr . Zero )
throw new ApplicationException ( $"Unable to create SDL Window: {SDL.SDL_GetError()}" ) ;
2018-11-25 10:03:30 -05:00
renderer = SDL . SDL_CreateRenderer ( window , - 1 , SDL . SDL_RendererFlags . SDL_RENDERER_SOFTWARE ) ;
2018-11-22 00:18:42 -05:00
if ( renderer = = IntPtr . Zero )
throw new ApplicationException ( $"Unable to create SDL Window: {SDL.SDL_GetError()}" ) ;
2018-11-23 20:51:32 -05:00
SDL . SDL_SetRenderDrawBlendMode ( renderer , SDL . SDL_BlendMode . SDL_BLENDMODE_BLEND ) ;
2018-11-28 19:28:41 -05:00
SDL . SDL_GL_SetSwapInterval ( 1 ) ;
2018-11-27 20:02:18 -05:00
SDL . SDL_ShowCursor ( globalConfig . MouseMode = = eMouseMode . Hardware ? 1 : 0 ) ;
2018-11-22 00:56:36 -05:00
2018-11-22 18:46:24 -05:00
IsRunning = true ;
2018-11-22 00:18:42 -05:00
}
2018-11-22 18:46:24 -05:00
public void Quit ( ) = > IsRunning = false ;
2018-11-22 00:18:42 -05:00
public void Dispose ( )
{
SDL . SDL_DestroyRenderer ( renderer ) ;
SDL . SDL_DestroyWindow ( window ) ;
SDL . SDL_Quit ( ) ;
}
2018-11-23 20:51:32 -05:00
public unsafe bool KeyIsPressed ( int scancode )
{
int numKeys ;
byte * keys = ( byte * ) SDL . SDL_GetKeyboardState ( out numKeys ) ;
return keys [ scancode ] > 0 ;
}
2018-11-22 00:18:42 -05:00
public void Clear ( )
{
2018-11-23 15:22:35 -05:00
SDL . SDL_SetRenderTarget ( renderer , IntPtr . Zero ) ;
SDL . SDL_SetRenderDrawColor ( renderer , 0 , 0 , 0 , 255 ) ;
2018-11-22 00:18:42 -05:00
SDL . SDL_RenderClear ( renderer ) ;
}
public void Sync ( )
{
SDL . SDL_RenderPresent ( renderer ) ;
}
2018-11-23 20:51:32 -05:00
public unsafe void Update ( )
2018-11-22 00:18:42 -05:00
{
while ( SDL . SDL_PollEvent ( out SDL . SDL_Event evt ) ! = 0 )
{
2018-11-22 00:56:36 -05:00
if ( evt . type = = SDL . SDL_EventType . SDL_MOUSEMOTION )
{
MouseX = evt . motion . x ;
MouseY = evt . motion . y ;
continue ;
}
2018-11-22 18:46:24 -05:00
else if ( evt . type = = SDL . SDL_EventType . SDL_MOUSEBUTTONDOWN )
{
2018-11-24 23:49:56 -05:00
switch ( ( uint ) evt . button . button )
2018-11-22 18:46:24 -05:00
{
case SDL . SDL_BUTTON_LEFT :
LeftMouseDown = true ;
break ;
case SDL . SDL_BUTTON_RIGHT :
RightMouseDown = true ;
break ;
}
}
else if ( evt . type = = SDL . SDL_EventType . SDL_MOUSEBUTTONUP )
{
switch ( ( uint ) evt . button . button )
{
case SDL . SDL_BUTTON_LEFT :
LeftMouseDown = false ;
break ;
case SDL . SDL_BUTTON_RIGHT :
RightMouseDown = false ;
break ;
}
}
2018-11-23 20:51:32 -05:00
else if ( evt . type = = SDL . SDL_EventType . SDL_KEYDOWN )
{
if ( evt . key . keysym . sym = = SDL . SDL_Keycode . SDLK_BACKSPACE & & KeyPressCallback ! = null )
KeyPressCallback ( '\b' ) ;
}
else if ( evt . type = = SDL . SDL_EventType . SDL_TEXTINPUT )
{
KeyPressCallback ? . Invoke ( Marshal . PtrToStringAnsi ( ( IntPtr ) evt . text . text ) [ 0 ] ) ;
continue ;
}
2018-11-22 18:46:24 -05:00
else if ( evt . type = = SDL . SDL_EventType . SDL_QUIT )
2018-11-22 00:56:36 -05:00
{
2018-11-22 18:46:24 -05:00
IsRunning = false ;
2018-11-22 00:56:36 -05:00
continue ;
}
2018-11-22 00:18:42 -05:00
}
}
2018-11-22 16:22:39 -05:00
public void Draw ( ISprite sprite , Point location )
{
sprite . Location = location ;
Draw ( sprite ) ;
}
2018-11-22 00:18:42 -05:00
2018-11-23 21:56:30 -05:00
public void Draw ( ISprite sprite , int frame , Point location )
{
sprite . Location = location ;
sprite . Frame = frame ;
Draw ( sprite ) ;
}
2018-11-22 14:30:37 -05:00
public void Draw ( ISprite sprite , int frame )
{
sprite . Frame = frame ;
Draw ( sprite ) ;
}
2018-11-22 00:18:42 -05:00
public void Draw ( ISprite sprite )
{
var spr = sprite as SDL2Sprite ;
2018-11-24 17:54:15 -05:00
if ( spr . texture = = IntPtr . Zero )
return ;
2018-11-22 00:18:42 -05:00
var loc = spr . GetRenderPoint ( ) ;
var destRect = new SDL . SDL_Rect
{
x = loc . X ,
y = loc . Y ,
w = spr . FrameSize . Width ,
h = spr . FrameSize . Height
} ;
2018-11-23 15:22:35 -05:00
SDL . SDL_RenderCopy ( renderer , spr . texture , IntPtr . Zero , ref destRect ) ;
2018-11-22 00:18:42 -05:00
}
2018-11-22 12:35:21 -05:00
public void Draw ( ISprite sprite , int xSegments , int ySegments , int offset )
{
var spr = sprite as SDL2Sprite ;
var segSize = xSegments * ySegments ;
for ( var y = 0 ; y < ySegments ; y + + )
{
for ( var x = 0 ; x < xSegments ; x + + )
{
var textureIndex = x + ( y * xSegments ) + ( offset * xSegments * ySegments ) ;
2018-11-23 15:22:35 -05:00
spr . Frame = Math . Min ( spr . TotalFrames - 1 , Math . Max ( 0 , textureIndex ) ) ;
2018-11-22 12:35:21 -05:00
var destRect = new SDL . SDL_Rect
{
x = sprite . Location . X + ( x * 256 ) ,
y = sprite . Location . Y + ( y * 256 ) - ( int ) ( spr . FrameSize . Height - spr . source . Frames [ textureIndex ] . Height ) ,
w = spr . FrameSize . Width ,
h = spr . FrameSize . Height
} ;
2018-11-23 15:22:35 -05:00
SDL . SDL_RenderCopy ( renderer , spr . texture , IntPtr . Zero , ref destRect ) ;
2018-11-22 12:35:21 -05:00
}
}
}
2018-11-22 14:30:37 -05:00
public ISprite LoadSprite ( string resourcePath , string palette ) = > LoadSprite ( resourcePath , palette , Point . Empty ) ;
public ISprite LoadSprite ( string resourcePath , string palette , Point location )
{
2018-11-22 22:53:05 -05:00
var result = new SDL2Sprite ( resourceManager . GetImageSet ( resourcePath ) , renderer )
2018-11-22 16:22:39 -05:00
{
CurrentPalette = paletteProvider . PaletteTable [ palette ] ,
Location = location
} ;
return result ;
}
public IFont LoadFont ( string resourcePath , string palette )
{
2018-11-22 22:53:05 -05:00
var result = new SDL2Font ( resourceManager . GetMPQFont ( resourcePath ) , renderer )
2018-11-22 16:22:39 -05:00
{
CurrentPalette = paletteProvider . PaletteTable [ palette ]
} ;
return result ;
}
public ILabel CreateLabel ( IFont font )
{
var result = new SDL2Label ( font , renderer ) ;
return result ;
}
public ILabel CreateLabel ( IFont font , string text )
{
var result = CreateLabel ( font ) ;
result . Text = text ;
return result ;
}
public ILabel CreateLabel ( IFont font , Point position , string text )
{
var result = new SDL2Label ( font , renderer )
{
Text = text ,
2018-11-22 18:06:15 -05:00
Location = position
2018-11-22 16:22:39 -05:00
} ;
2018-11-22 14:30:37 -05:00
return result ;
}
2018-11-22 16:22:39 -05:00
public void Draw ( ILabel label )
{
var lbl = label as SDL2Label ;
2018-11-22 18:06:15 -05:00
var loc = lbl . Location ;
2018-11-22 16:22:39 -05:00
var destRect = new SDL . SDL_Rect
{
x = loc . X ,
y = loc . Y ,
w = lbl . textureSize . Width ,
h = lbl . textureSize . Height
} ;
2018-11-22 18:06:15 -05:00
2018-11-22 16:22:39 -05:00
SDL . SDL_RenderCopy ( renderer , lbl . texture , IntPtr . Zero , ref destRect ) ;
}
2018-11-24 10:54:02 -05:00
2018-11-27 23:09:10 -05:00
public unsafe MapCellInfo CacheMapCell ( MPQDT1Tile mapCell )
2018-11-24 23:49:56 -05:00
{
2018-11-28 19:28:41 -05:00
log . Debug ( $"Caching map cell {mapCell.Id}" ) ;
2018-11-27 23:09:10 -05:00
var minX = mapCell . Blocks . Min ( x = > x . PositionX ) ;
var minY = mapCell . Blocks . Min ( x = > x . PositionY ) ;
var maxX = mapCell . Blocks . Max ( x = > x . PositionX + 32 ) ;
var maxY = mapCell . Blocks . Max ( x = > x . PositionY + 32 ) ;
2018-11-25 18:37:53 -05:00
var diffX = maxX - minX ;
var diffY = maxY - minY ;
var offX = - minX ;
2018-11-28 19:28:41 -05:00
var offY = - minY ;
2018-11-25 18:37:53 -05:00
var frameSize = new Size ( diffX , Math . Abs ( diffY ) ) ;
var srcRect = new SDL . SDL_Rect { x = 0 , y = 0 , w = frameSize . Width , h = Math . Abs ( frameSize . Height ) } ;
var frameSizeMax = diffX * Math . Abs ( diffY ) ;
var texId = SDL . SDL_CreateTexture ( renderer , SDL . SDL_PIXELFORMAT_ARGB8888 , ( int ) SDL . SDL_TextureAccess . SDL_TEXTUREACCESS_STREAMING , frameSize . Width , frameSize . Height ) ;
SDL . SDL_SetTextureBlendMode ( texId , SDL . SDL_BlendMode . SDL_BLENDMODE_BLEND ) ;
if ( SDL . SDL_LockTexture ( texId , IntPtr . Zero , out IntPtr pixels , out int pitch ) ! = 0 )
2018-11-27 23:09:10 -05:00
throw new ApplicationException ( "Could not lock texture for map rendering" ) ;
2018-11-24 23:49:56 -05:00
try
{
UInt32 * data = ( UInt32 * ) pixels ;
var pitchChange = ( pitch / 4 ) ;
2018-11-27 00:56:54 -05:00
2018-11-25 18:37:53 -05:00
for ( var i = 0 ; i < frameSize . Height * pitchChange ; i + + )
data [ i ] = 0x0 ;
2018-11-27 00:56:54 -05:00
2018-11-24 23:49:56 -05:00
2018-11-27 23:09:10 -05:00
foreach ( var block in mapCell . Blocks )
2018-11-24 23:49:56 -05:00
{
2018-11-28 19:28:41 -05:00
var index = block . PositionX + offX + ( ( block . PositionY + offY ) * pitchChange ) ;
2018-11-25 09:42:39 -05:00
var xx = 0 ;
2018-11-25 14:15:13 -05:00
var yy = 0 ;
2018-11-25 09:42:39 -05:00
foreach ( var colorIndex in block . PixelData )
2018-11-24 23:49:56 -05:00
{
2018-11-25 09:42:39 -05:00
try
2018-11-24 23:49:56 -05:00
{
2018-11-25 14:15:13 -05:00
if ( colorIndex = = 0 )
continue ;
2018-11-28 19:28:41 -05:00
var color = getGameState ( ) . CurrentPalette . Colors [ colorIndex ] ;
2018-11-27 00:56:54 -05:00
2018-11-25 18:37:53 -05:00
if ( color > 0 )
2018-11-24 23:49:56 -05:00
data [ index ] = color ;
2018-11-25 14:15:13 -05:00
}
finally
2018-11-25 09:42:39 -05:00
{
index + + ;
xx + + ;
if ( xx = = 32 )
{
index - = 32 ;
index + = pitchChange ;
xx = 0 ;
2018-11-25 14:15:13 -05:00
yy + + ;
2018-11-25 09:42:39 -05:00
}
2018-11-24 23:49:56 -05:00
}
}
}
}
finally
{
2018-11-25 18:37:53 -05:00
SDL . SDL_UnlockTexture ( texId ) ;
2018-11-24 23:49:56 -05:00
}
2018-11-27 23:09:10 -05:00
return new MapCellInfo
2018-11-25 18:37:53 -05:00
{
2018-11-28 19:28:41 -05:00
Tile = mapCell ,
2018-11-25 18:37:53 -05:00
FrameHeight = frameSize . Height ,
FrameWidth = frameSize . Width ,
OffX = offX ,
2018-11-28 19:28:41 -05:00
OffY = offY ,
2018-11-27 00:56:54 -05:00
Rect = srcRect . ToRectangle ( ) ,
Texture = new SDL2Texture { Pointer = texId }
2018-11-25 18:37:53 -05:00
} ;
2018-11-27 23:09:10 -05:00
}
2018-11-25 18:37:53 -05:00
2018-11-27 23:09:10 -05:00
public void DrawMapCell ( MapCellInfo mapCellInfo , int xPixel , int yPixel )
{
var srcRect = new SDL . SDL_Rect { x = 0 , y = 0 , w = mapCellInfo . FrameWidth , h = Math . Abs ( mapCellInfo . FrameHeight ) } ;
2018-11-28 19:28:41 -05:00
var destRect = new SDL . SDL_Rect { x = xPixel - mapCellInfo . OffX , y = yPixel - mapCellInfo . OffY , w = mapCellInfo . FrameWidth , h = mapCellInfo . FrameHeight } ;
2018-11-27 23:09:10 -05:00
SDL . SDL_RenderCopy ( renderer , ( mapCellInfo . Texture as SDL2Texture ) . Pointer , ref srcRect , ref destRect ) ;
2018-11-24 23:49:56 -05:00
}
2018-11-27 20:02:18 -05:00
public unsafe IMouseCursor LoadCursor ( ISprite sprite , int frame , Point hotspot )
{
if ( globalConfig . MouseMode ! = eMouseMode . Hardware )
throw new ApplicationException ( "Tried to set a hardware cursor, but we are using software cursors!" ) ;
var multiple = globalConfig . HardwareMouseScale ;
var spr = sprite as SDL2Sprite ;
var surface = SDL . SDL_CreateRGBSurface ( 0 , spr . FrameSize . Width * multiple , spr . FrameSize . Height * multiple , 32 , 0xFF0000 , 0xFF00 , 0xFF , 0xFF000000 ) ;
var pixels = ( UInt32 * ) ( ( SDL . SDL_Surface * ) surface ) - > pixels ;
2018-11-28 19:28:41 -05:00
for ( var y = 0 ; y < ( spr . FrameSize . Height * multiple ) - 1 ; y + + )
for ( var x = 0 ; x < ( spr . FrameSize . Width * multiple ) - 1 ; x + + )
2018-11-27 20:02:18 -05:00
{
pixels [ x + ( y * spr . FrameSize . Width * multiple ) ] = spr . source . Frames [ frame ] . GetColor ( x / multiple , y / multiple , sprite . CurrentPalette ) ;
}
var cursor = SDL . SDL_CreateColorCursor ( surface , hotspot . X , hotspot . Y ) ;
if ( cursor = = IntPtr . Zero )
throw new ApplicationException ( $"Unable to set the cursor cursor: {SDL.SDL_GetError()}" ) ; // TODO: Is this supported everywhere? May need to still support software cursors.
return new SDL2MouseCursor { Surface = cursor } ;
}
public void SetCursor ( IMouseCursor mouseCursor )
{
if ( globalConfig . MouseMode ! = eMouseMode . Hardware )
throw new ApplicationException ( "Tried to set a hardware cursor, but we are using software cursors!" ) ;
SDL . SDL_SetCursor ( ( mouseCursor as SDL2MouseCursor ) . Surface ) ;
}
2018-11-22 00:18:42 -05:00
}
}