1882 lines
63 KiB
D
1882 lines
63 KiB
D
module otya.smilebasic.petitcomputer;
|
|
import derelict.sdl2.sdl;
|
|
import derelict.sdl2.image;
|
|
import derelict.opengl3.gl;
|
|
import std.net.curl;
|
|
import std.file;
|
|
import std.stdio;
|
|
import std.conv;
|
|
import std.string;
|
|
import std.range;
|
|
static import std.algorithm;
|
|
import core.stdc.stdio;
|
|
import core.sync.mutex;
|
|
import core.sync.condition;
|
|
static import core.stdc.string;
|
|
import otya.smilebasic.sprite;
|
|
import otya.smilebasic.error;
|
|
import otya.smilebasic.bg;
|
|
import otya.smilebasic.parser;
|
|
import otya.smilebasic.project;
|
|
import otya.smilebasic.console;
|
|
import otya.smilebasic.graphic;
|
|
import otya.smilebasic.dialog;
|
|
import otya.smilebasic.program;
|
|
import otya.smilebasic.random;
|
|
import otya.smilebasic.fade;
|
|
static import otya.smilebasic.token;
|
|
const static rot_test_deg = 45f;
|
|
const static rot_test_x = 0f;
|
|
const static rot_test_y = 1f;
|
|
const static rot_test_z = 1f;
|
|
enum Button
|
|
{
|
|
NONE = 0,
|
|
UP = 1,
|
|
DOWN = 2,
|
|
LEFT = 4,
|
|
RIGHT = 8,
|
|
A = 16,
|
|
B = 32,
|
|
X = 64,
|
|
Y = 128,
|
|
L = 256,
|
|
R = 512,
|
|
UNUSED = 1024,
|
|
ZR = 2048,
|
|
ZL = 4096,
|
|
START = 8192,
|
|
}
|
|
class GraphicPage
|
|
{
|
|
static GLenum textureScaleMode = GL_NEAREST;
|
|
SDL_Surface* surface;
|
|
this(string filename)
|
|
{
|
|
SDL_RWops* stream = SDL_RWFromFile(toStringz(filename), toStringz("rb"));
|
|
auto src = IMG_Load_RW(stream, 0);
|
|
scope(exit)
|
|
{
|
|
SDL_RWclose(stream);
|
|
}
|
|
this(src);
|
|
}
|
|
this(SDL_Surface* s)
|
|
{
|
|
surface = s;
|
|
|
|
GLenum texture_format;
|
|
GLint nOfColors;
|
|
nOfColors = surface.format.BytesPerPixel;
|
|
if (nOfColors == 4) // contains an alpha channel
|
|
{
|
|
if (surface.format.Rmask == 0x000000ff)
|
|
texture_format = GL_RGBA;
|
|
else
|
|
texture_format = GL_BGRA;
|
|
} else if (nOfColors == 3) // no alpha channel
|
|
{
|
|
if (surface.format.Rmask == 0x000000ff)
|
|
texture_format = GL_RGB;
|
|
else
|
|
texture_format = GL_BGR;
|
|
} else {
|
|
//printf("warning: the image is not truecolor.. this will probably break\n");
|
|
// this error should not go unhandled
|
|
}
|
|
textureFormat = texture_format;
|
|
}
|
|
GLuint glTexture;
|
|
GLenum textureFormat;
|
|
void createTexture(SDL_Renderer* renderer)
|
|
{
|
|
createTexture(renderer, GraphicPage.textureScaleMode);
|
|
}
|
|
void createTexture(SDL_Renderer* renderer, GLenum textureScaleMode)
|
|
{
|
|
ubyte r,g,b,a;
|
|
SDL_GetRGBA(*cast(uint*)surface.pixels, surface.format, &r, &g, &b, &a);
|
|
GLenum texture_format;
|
|
GLint nOfColors;
|
|
nOfColors = surface.format.BytesPerPixel;
|
|
if (nOfColors == 4) // contains an alpha channel
|
|
{
|
|
if (surface.format.Rmask == 0x000000ff)
|
|
texture_format = GL_RGBA;
|
|
else
|
|
texture_format = GL_BGRA;
|
|
} else if (nOfColors == 3) // no alpha channel
|
|
{
|
|
if (surface.format.Rmask == 0x000000ff)
|
|
texture_format = GL_RGB;
|
|
else
|
|
texture_format = GL_BGR;
|
|
} else {
|
|
//printf("warning: the image is not truecolor.. this will probably break\n");
|
|
// this error should not go unhandled
|
|
}
|
|
// Have OpenGL generate a texture object handle for us
|
|
glGenTextures( 1, &glTexture );
|
|
// Bind the texture object
|
|
glBindTexture( GL_TEXTURE_2D, glTexture );
|
|
// Set the texture's stretching properties
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, textureScaleMode );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, textureScaleMode );
|
|
|
|
// Edit the texture object's image data using the information SDL_Surface gives us
|
|
glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, surface.w, surface.h, 0,
|
|
texture_format, GL_UNSIGNED_BYTE, surface.pixels );
|
|
textureFormat = texture_format;
|
|
}
|
|
GLuint render;
|
|
GLuint buffer;
|
|
void createBuffer()
|
|
{
|
|
GLint old;
|
|
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old);
|
|
glBindTexture(GL_TEXTURE_2D, glTexture);
|
|
glGenRenderbuffersEXT(1, &render);
|
|
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render);
|
|
//glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, surface.w, surface.h);
|
|
glGenFramebuffersEXT(1, &buffer);
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, buffer);
|
|
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, glTexture, 0);
|
|
//glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render);
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
|
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
{
|
|
writeln("!?");
|
|
}
|
|
glBindFramebufferEXT(GL_FRAMEBUFFER, old);
|
|
}
|
|
void deleteGL()
|
|
{
|
|
if (buffer)
|
|
glDeleteFramebuffersEXT(1, &buffer);
|
|
if (render)
|
|
glDeleteRenderbuffersEXT(1, &render);
|
|
glDeleteTextures(1, &glTexture);
|
|
buffer = render = glTexture = 0;
|
|
}
|
|
void deleteSDL()
|
|
{
|
|
SDL_FreeSurface(surface);
|
|
surface = null;
|
|
}
|
|
|
|
}
|
|
|
|
version(Windows)
|
|
{
|
|
extern (Windows) static void* LoadLibraryA(in char*);
|
|
extern (Windows) static bool FreeLibrary(void*);
|
|
extern (Windows) static void* GetProcAddress(void*, in char*);
|
|
alias extern (Windows) void* function(void*, void*) ImmAssociateContext;
|
|
alias extern (Windows) int function(int) ImmDisableIME;
|
|
}
|
|
import std.container;
|
|
struct Size
|
|
{
|
|
int width, height;
|
|
}
|
|
struct Display
|
|
{
|
|
SDL_Rect[] rect;
|
|
Size windowSize;
|
|
int yoffset;
|
|
int count()
|
|
{
|
|
return cast(int)rect.length;
|
|
}
|
|
}
|
|
class RingBuffer(T)
|
|
{
|
|
T[] buffer;
|
|
this(size_t size)
|
|
{
|
|
start = -1;
|
|
end = -1;
|
|
buffer = new T[size];
|
|
}
|
|
sizediff_t end;
|
|
sizediff_t start;
|
|
void put(T val)
|
|
{
|
|
if (end < start)
|
|
{
|
|
start++;
|
|
if (start == buffer.length)
|
|
{
|
|
start = 0;
|
|
}
|
|
}
|
|
end++;
|
|
if (end == buffer.length)
|
|
{
|
|
end = 0;
|
|
start = 1;
|
|
}
|
|
buffer[end] = val;
|
|
return;
|
|
}
|
|
size_t length()
|
|
{
|
|
return start == -1 ? end - start : buffer.length;
|
|
}
|
|
T opIndex(size_t index)
|
|
{
|
|
return buffer[(start != -1 ? start + index : index) % buffer.length];
|
|
}
|
|
}
|
|
enum Hardware
|
|
{
|
|
threeDS = 0,
|
|
new3DS = 1,
|
|
wiiU = 2,
|
|
}
|
|
class PetitComputer
|
|
{
|
|
this()
|
|
{
|
|
//new Test();
|
|
}
|
|
static const string resourceDirName = "resources";
|
|
static const string resourcePath = "./resources";
|
|
static const string fontFile = resourcePath ~ "/font.png";
|
|
static const string spriteFile = resourcePath ~ "/defsp.png";
|
|
static const string BGFile = resourcePath ~ "/defbg.png";
|
|
static const string fontTableFile = "./fonttable.txt";
|
|
int screenWidth;
|
|
int screenHeight;
|
|
int screenWidthDisplay1;
|
|
int screenHeightDisplay1;
|
|
uint toGLColor(uint color)
|
|
{
|
|
//ARGB -> ABGR
|
|
return (color & 0xFF00FF00) | (color >> 16 & 0xFF) | ((color & 0xFF) << 16);
|
|
}
|
|
uint fromGLColor(uint color)
|
|
{
|
|
//ARGB <- ABGR
|
|
return (color & 0xFF00FF00) | (color >> 16 & 0xFF) | ((color & 0xFF) << 16);
|
|
}
|
|
struct ConsoleCharacter
|
|
{
|
|
wchar character;
|
|
int foreColor;
|
|
int backColor;
|
|
byte attr;
|
|
int z;
|
|
}
|
|
SDL_Color PetitColor(ubyte r, ubyte g, ubyte b, ubyte a)
|
|
{
|
|
return SDL_Color(r >> 5 << 5, g >> 5 << 5, b >> 5 << 5, a == 255 ? 255 : 0);
|
|
}
|
|
Button button;
|
|
struct Point
|
|
{
|
|
int x, y;
|
|
this(int x, int y)
|
|
{
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
}
|
|
int[2] bgpage;
|
|
Projects project;
|
|
bool redirectConsole;
|
|
int displayCount;
|
|
void init()
|
|
{
|
|
random = new Random();
|
|
project = new Projects(".");
|
|
// DerelictGL.load();
|
|
if(!exists(resourcePath))
|
|
{
|
|
writeln("create ./resources");
|
|
mkdir(resourceDirName);
|
|
}
|
|
if(!exists(fontFile))
|
|
{
|
|
writeln("download font");
|
|
download("http://smilebasic.com/wordpress/wp-content/themes/smilebasic/publishimage/appendix/res_font_table-320.png",
|
|
fontFile);
|
|
}
|
|
if(!exists(spriteFile))
|
|
{
|
|
writeln("download sprite");
|
|
download("http://dengekionline.com/elem/000/000/927/927124/petitcom_16_cs1w1_512x512.jpg",
|
|
spriteFile);
|
|
}
|
|
if(!exists(BGFile))
|
|
{
|
|
writeln("download BG");
|
|
download("http://dengekionline.com/elem/000/000/927/927125/petitcom_17_cs1w1_512x512.jpg",
|
|
BGFile);
|
|
}
|
|
screenWidth = 400;
|
|
screenHeight = 240;
|
|
screenWidthDisplay1 = 320;
|
|
screenHeightDisplay1 = 240;
|
|
currentDisplay = Display([SDL_Rect(0, 0, screenWidth, screenHeight)]);
|
|
displayCount = 2;
|
|
console = redirectConsole ? new NativeConsole(std.stdio.stdout, this) : new Console(this);
|
|
if(!exists(fontTableFile))
|
|
{
|
|
writeln("create font table");
|
|
//HTMLなんて解析したくないから適当
|
|
console.createFontTable();
|
|
}
|
|
else
|
|
{
|
|
console.loadFontTable();
|
|
}
|
|
functionKey = ["FILES", "LOAD\"", "SAVE\"", "LIST ERR", "RUN\n"];
|
|
writeln("OK");
|
|
}
|
|
int currentScreenWidth;
|
|
int currentScreenHeight;
|
|
void display(int number)
|
|
{
|
|
import std.exception : enforce;
|
|
enforce(number >= 0);
|
|
currentScreenHeight = currentDisplay.rect[number].h;
|
|
currentScreenWidth = currentDisplay.rect[number].w;
|
|
displaynum = number;
|
|
|
|
console.display(number);
|
|
graphic.display(number);
|
|
}
|
|
SDL_Renderer* renderer;
|
|
int vsyncFrame;
|
|
int vsyncCount;
|
|
int prevVSyncCount;
|
|
bool isVSync;
|
|
bool waitFlag;
|
|
void vsync(int f)
|
|
{
|
|
waitFlag = true;
|
|
isVSync = true;
|
|
vsyncFrame = f;
|
|
}
|
|
void wait(int f)
|
|
{
|
|
waitFlag = true;
|
|
isVSync = false;
|
|
vsyncFrame = f;
|
|
}
|
|
Graphic graphic;
|
|
Mutex keybuffermutex;
|
|
int keybufferpos;
|
|
int keybufferlen;
|
|
enum KeyOp
|
|
{
|
|
KEY,
|
|
COPY,
|
|
PASTE,
|
|
UNDO,
|
|
REDO,
|
|
}
|
|
struct Key
|
|
{
|
|
wchar key;
|
|
KeyOp op;
|
|
this(wchar k)
|
|
{
|
|
key = k;
|
|
op = KeyOp.KEY;
|
|
}
|
|
this(KeyOp o)
|
|
{
|
|
op = o;
|
|
}
|
|
}
|
|
//解析した結果キー入力のバッファは127くらい
|
|
Key[] keybuffer;// = new wchar[128];//Linuxだとこうやって確保した場合書き込むとSEGV
|
|
void sendKey(wchar key)
|
|
{
|
|
sendKey(Key(key));
|
|
}
|
|
void sendKey(Key key)
|
|
{
|
|
keybuffermutex.lock();
|
|
scope (exit)
|
|
keybuffermutex.unlock();
|
|
keybuffer[keybufferpos] = key;
|
|
keybufferlen++;
|
|
if(keybufferlen > keybuffer.length)
|
|
keybufferlen = cast(int)keybuffer.length;
|
|
keybufferpos = (keybufferpos + 1) % cast(int)keybuffer.length;
|
|
}
|
|
Mutex grpmutex;
|
|
int displaynum;
|
|
int renderstartpos;
|
|
//フォントの都合で縮小するとまともに見られないので非推奨
|
|
float scaleX = 1;
|
|
float scaleY = 1;
|
|
void glViewport2(int x, int y, int w, int h)
|
|
{
|
|
glViewport(cast(int)(x * scaleX), cast(int)(y * scaleY), cast(int)(w * scaleX), cast(int)(h * scaleY));
|
|
}
|
|
void chScreen2(int x, int y, int w, int h)
|
|
{
|
|
glViewport2(x, currentDisplay.yoffset - h - y, w, h);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glOrtho(0, w, h, 0, 1024, -2048);
|
|
}
|
|
void chRenderingDisplay(int i)
|
|
{
|
|
chScreen2(currentDisplay.rect[i].x, currentDisplay.rect[i].y, currentDisplay.rect[i].w, currentDisplay.rect[i].h);
|
|
}
|
|
void chRenderingDisplay(int i, int x, int y, int w, int h)
|
|
{
|
|
glViewport2(x + currentDisplay.rect[i].x, currentDisplay.yoffset - h - y - currentDisplay.rect[i].y, w, h);
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glOrtho(x, x + w, y + h, y, 1024, -2048);
|
|
}
|
|
Button[] buttonTable;
|
|
Button[] controllerButtonTable;
|
|
Sprite sprite;
|
|
Size[7] resolutionTable = [Size(256, 192), Size(320, 200), Size(320, 240), Size(400, 240), Size(640, 400), Size(640, 480), Size(854, 480)];
|
|
int xscreenmode = 0;
|
|
int bgmax;
|
|
void xscreen(int mode, int tv, int sprite, int bg)
|
|
{
|
|
xscreen(mode, tv, -1, sprite, bg);
|
|
}
|
|
void xscreen(int mode, int sprite, int bg)
|
|
{
|
|
xscreen(mode, -1, -1, sprite, bg);
|
|
}
|
|
Display currentDisplay;
|
|
void xscreen(int mode, int tv, int gamepad, int sprite, int bg)
|
|
{
|
|
int mode2 = mode / 2;
|
|
synchronized(renderSync)
|
|
{
|
|
this.sprite.spclr();
|
|
this.sprite.spmax = sprite;
|
|
this.bgmax = bg;
|
|
//BG
|
|
xscreenmode = mode2;
|
|
if(mode2 == 0)
|
|
{
|
|
currentDisplay = Display([SDL_Rect(0, 0, screenWidth, screenHeight)], Size(400, 240));
|
|
}
|
|
if(mode2 == 1)
|
|
{
|
|
currentDisplay = Display([SDL_Rect(0, 0, screenWidth, screenHeight), SDL_Rect((screenWidth - screenWidthDisplay1) / 2, screenHeight, screenWidthDisplay1, screenHeightDisplay1)], Size(400, 480));
|
|
}
|
|
if(mode == 4)
|
|
{
|
|
currentDisplay = Display([SDL_Rect(0, 0, screenWidthDisplay1, screenHeight + screenHeightDisplay1)], Size(320, 480));
|
|
}
|
|
if (mode == 5)
|
|
{
|
|
currentDisplay = Display([SDL_Rect(0, 0, resolutionTable[tv].width, resolutionTable[tv].height)], Size(resolutionTable[tv].width, resolutionTable[tv].height));
|
|
xscreenmode = 3;
|
|
}
|
|
if (mode == 6)
|
|
{
|
|
auto display0 = SDL_Rect(0, 0, resolutionTable[tv].width, resolutionTable[tv].height);
|
|
auto display1 = SDL_Rect(0, resolutionTable[tv].height, resolutionTable[gamepad].width, resolutionTable[gamepad].height);
|
|
if (resolutionTable[tv].width > resolutionTable[gamepad].width)
|
|
{
|
|
display1.x = (resolutionTable[tv].width - resolutionTable[gamepad].width) / 2;
|
|
}
|
|
else
|
|
{
|
|
display0.x = (resolutionTable[gamepad].width - resolutionTable[tv].width) / 2;
|
|
}
|
|
currentDisplay = Display([display0, display1], Size(std.algorithm.max(resolutionTable[gamepad].width, resolutionTable[tv].width), resolutionTable[gamepad].height + resolutionTable[tv].height));
|
|
xscreenmode = 4;
|
|
}
|
|
currentDisplay.yoffset = currentDisplay.windowSize.height;
|
|
int grpw, grph;
|
|
graphic.getSize(grpw, grph);
|
|
//1024*1024?
|
|
if (mode == 5 || mode == 6)
|
|
{
|
|
if (grpw != 1024 || grph != 1024)
|
|
{
|
|
graphic.setSize(1024, 1024);
|
|
graphic.initGraphicPages();
|
|
}
|
|
}
|
|
else if (grpw != 512 || grph != 512)
|
|
{
|
|
graphic.setSize(512, 512);
|
|
graphic.initGraphicPages();
|
|
}
|
|
displaynum = 0;
|
|
console.changeDisplay(currentDisplay);
|
|
for(int i = 0; i < bgmax; i++)
|
|
{
|
|
this.bg[i].display = 0;
|
|
this.bg[i].clip;
|
|
}
|
|
if (currentDisplay.rect.length > 1)
|
|
{
|
|
for(int i = bgmax; i < this.bg.length; i++)
|
|
{
|
|
this.bg[i].display = 1;
|
|
this.bg[i].clip;
|
|
}
|
|
}
|
|
for (int i = cast(int)currentDisplay.rect.length - 1; i >= 0; i--)
|
|
{
|
|
display(i);
|
|
graphic.clip(false);
|
|
graphic.clip(true);
|
|
this.sprite.spclip;
|
|
}
|
|
}
|
|
updateWindowSize();
|
|
}
|
|
|
|
//do not call in synchronized(renderSync)
|
|
void updateWindowSize()
|
|
{
|
|
SDL_SetWindowSize(window, cast(int)(currentDisplay.windowSize.width * scaleX), cast(int)(currentDisplay.windowSize.height * scaleY));
|
|
}
|
|
|
|
bool isValidLayer(int layer)
|
|
{
|
|
if (layer < 0)
|
|
return false;
|
|
if(displaynum)
|
|
{
|
|
return layer < bg.length - bgmax;
|
|
}
|
|
return layer < bgmax;
|
|
}
|
|
BG getBG(int layer)
|
|
{
|
|
if(displaynum)
|
|
{
|
|
return bg[layer + bgmax];
|
|
}
|
|
return bg[layer];
|
|
}
|
|
BG[] allBG()
|
|
{
|
|
return bg;
|
|
}
|
|
//TODO:extract class
|
|
bool[2] BGvisibles = [true, true];
|
|
bool BGvisible()
|
|
{
|
|
return BGvisibles[displaynum];
|
|
}
|
|
void BGvisible(bool value)
|
|
{
|
|
BGvisibles[displaynum] = value;
|
|
}
|
|
protected BG[4] bg;
|
|
bool quit;
|
|
Condition renderCondition;
|
|
struct Touch
|
|
{
|
|
int tm;
|
|
int x;
|
|
int y;
|
|
//WiiU
|
|
int gamepadX;
|
|
int gamepadY;
|
|
int display1X;
|
|
int display1Y;
|
|
};
|
|
Object touchSync = new Object();
|
|
Touch m_touch;
|
|
Touch touchPosition()
|
|
{
|
|
synchronized(touchSync)
|
|
{
|
|
return m_touch;
|
|
}
|
|
}
|
|
void touchPosition(Touch to)
|
|
{
|
|
synchronized(touchSync)
|
|
{
|
|
m_touch = to;
|
|
}
|
|
}
|
|
int petitcomBackcolor;
|
|
void backcolor(int color)
|
|
{
|
|
petitcomBackcolor = color;
|
|
}
|
|
int backcolor()
|
|
{
|
|
return petitcomBackcolor;
|
|
}
|
|
void updateTouch(int mousex, int mousey)
|
|
{
|
|
int ww, wh;
|
|
SDL_GetWindowSize(window, &ww, &wh);
|
|
mousex = cast(int)(mousex / scaleX);
|
|
mousey = cast(int)(mousey / scaleY) - (wh - currentDisplay.yoffset);
|
|
import std.algorithm.comparison : clamp;
|
|
auto old = touchPosition;
|
|
if (x.mode != XMode.WIIU)
|
|
{
|
|
if (mousey >= screenHeight)
|
|
{
|
|
mousey -= screenHeight;
|
|
mousex -= (screenWidth - screenWidthDisplay1) / 2;
|
|
}
|
|
mousex = mousex.clamp(5, 314);
|
|
mousey = mousey.clamp(5, 234);
|
|
touchPosition = Touch(old.tm + 1, mousex, mousey, 0, 0, mousex, mousey);
|
|
}
|
|
else
|
|
{
|
|
//!!WiiU
|
|
if (xscreenmode == 2 || xscreenmode == 0 || xscreenmode == 3)//XSCREEN 5
|
|
{
|
|
auto w = currentDisplay.rect[0].w;
|
|
auto h = currentDisplay.rect[0].h;
|
|
auto x3DS = cast(int)(mousex * (screenWidthDisplay1 / cast(float)w));
|
|
auto y3DS = cast(int)(mousey * (screenHeightDisplay1 / cast(float)h));
|
|
auto gamepadx = cast(int)(mousex * (854 / cast(float)w));
|
|
auto gamepady = cast(int)(mousey * (480 / cast(float)h));
|
|
touchPosition = Touch(old.tm + 1,
|
|
x3DS.clamp(5, 314), y3DS.clamp(5, 234),
|
|
gamepadx.clamp(8, 854 - 9), gamepady.clamp(8, 480 - 9),
|
|
mousex.clamp(8, w - 13), mousey.clamp(8, h - 9));
|
|
}
|
|
else
|
|
{
|
|
auto w = currentDisplay.rect[1].w;
|
|
auto h = currentDisplay.rect[1].h;
|
|
mousex -= currentDisplay.rect[1].x;
|
|
mousey -= currentDisplay.rect[1].y;
|
|
if (mousex >= 0 && mousey >= 0)
|
|
{
|
|
auto x3DS = cast(int)(mousex * (screenWidthDisplay1 / cast(float)w));
|
|
auto y3DS = cast(int)(mousey * (screenHeightDisplay1 / cast(float)h));
|
|
auto gamepadx = cast(int)(mousex * (854 / cast(float)w));
|
|
auto gamepady = cast(int)(mousey * (480 / cast(float)h));
|
|
touchPosition = Touch(old.tm + 1,
|
|
x3DS.clamp(5, 314), y3DS.clamp(5, 234),
|
|
gamepadx.clamp(8, 854 - 9), gamepady.clamp(8, 480 - 9),
|
|
mousex.clamp(12, w - 13), mousey.clamp(8, h - 9));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//https://miiverse.nintendo.net/replies/AYMHAAADAAB2V0f0v9-s2g
|
|
struct Stick
|
|
{
|
|
int internalX;//-105~105
|
|
int internalY;//-105~105
|
|
static const int max = 105;
|
|
double x()
|
|
{
|
|
return internalX / 120.0;
|
|
}
|
|
double y()
|
|
{
|
|
return internalY / 120.0;
|
|
}
|
|
}
|
|
private Object stickSync = new Object();
|
|
private Stick[2] sticks;//0:stick, 1:stickex
|
|
public Stick stick(int id)
|
|
{
|
|
synchronized (stickSync)
|
|
return sticks[id];
|
|
}
|
|
public void stick(int id, Stick stick)
|
|
{
|
|
synchronized (stickSync)
|
|
sticks[id] = stick;
|
|
}
|
|
private void updateStick(SDL_GameController* controller, int id, SDL_GameControllerAxis x, SDL_GameControllerAxis y)
|
|
{
|
|
Stick s;
|
|
if (!activeController)
|
|
{
|
|
stick(id, s);
|
|
return;
|
|
}
|
|
import std.algorithm : clamp, min, max;
|
|
short stickx = SDL_GameControllerGetAxis(controller, x).clamp(-short.max, short.max);
|
|
short sticky = SDL_GameControllerGetAxis(controller, y).clamp(-short.max, short.max);
|
|
s.internalX = stickx / (short.max / Stick.max);
|
|
s.internalY = -sticky / (short.max / Stick.max);
|
|
stick(id, s);
|
|
}
|
|
private void updateSticks()
|
|
{
|
|
updateStick(activeController, 0, SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY);
|
|
updateStick(activeController, 1, SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY);
|
|
}
|
|
SDL_GLContext context;
|
|
SDL_GLContext contextVM;
|
|
Object renderSync = new Object();
|
|
Button[int] controllerTable;
|
|
void render()
|
|
{
|
|
try
|
|
{
|
|
import derelict.sdl2.ttf;
|
|
DerelictSDL2.load();
|
|
DerelictSDL2Image.load();
|
|
DerelictSDL2ttf.load();
|
|
TTF_Init();
|
|
sprite.sppage[] = 4;
|
|
bgpage[] = 5;
|
|
SDL_Init(SDL_INIT_VIDEO);
|
|
SDL_Init(SDL_INIT_GAMECONTROLLER);
|
|
|
|
DerelictGL.load();
|
|
SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
|
|
for (int i = 0; i < SDL_NumJoysticks(); i++)
|
|
{
|
|
AddController(i);
|
|
}
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
writeln(t);
|
|
}
|
|
bool renderprofile = false;
|
|
try
|
|
{
|
|
/*version(Windows)
|
|
{
|
|
auto imm32 = LoadLibraryA("imm32.dll".toStringz);
|
|
ImmDisableIME ImmDisableIME = cast(ImmDisableIME)GetProcAddress(imm32, "ImmDisableIME".toStringz);
|
|
if(ImmDisableIME) ImmDisableIME(0);
|
|
}*/
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
window = SDL_CreateWindow("SMILEBASIC", SDL_WINDOWPOS_UNDEFINED,
|
|
SDL_WINDOWPOS_UNDEFINED, 400, 240,
|
|
SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
|
|
buttonTable = new Button[SDL_SCANCODE_SLEEP + 1];
|
|
buttonTable[SDL_SCANCODE_UP] = Button.UP;
|
|
buttonTable[SDL_SCANCODE_DOWN] = Button.DOWN;
|
|
buttonTable[SDL_SCANCODE_LEFT] = Button.LEFT;
|
|
buttonTable[SDL_SCANCODE_RIGHT] = Button.RIGHT;
|
|
buttonTable[SDL_SCANCODE_RETURN] = Button.A;
|
|
buttonTable[SDL_SCANCODE_X] = Button.A;
|
|
buttonTable[SDL_SCANCODE_ESCAPE] = Button.B;
|
|
buttonTable[SDL_SCANCODE_B] = Button.B;
|
|
buttonTable[SDL_SCANCODE_A] = Button.X;
|
|
buttonTable[SDL_SCANCODE_S] = Button.Y;
|
|
buttonTable[SDL_SCANCODE_Q] = Button.L;
|
|
buttonTable[SDL_SCANCODE_W] = Button.R;
|
|
controllerButtonTable = new Button[SDL_SCANCODE_SLEEP + 1];
|
|
controllerButtonTable[SDL_SCANCODE_UP] = Button.UP;
|
|
controllerButtonTable[SDL_SCANCODE_DOWN] = Button.DOWN;
|
|
controllerButtonTable[SDL_SCANCODE_LEFT] = Button.LEFT;
|
|
controllerButtonTable[SDL_SCANCODE_RIGHT] = Button.RIGHT;
|
|
|
|
controllerTable[SDL_CONTROLLER_BUTTON_DPAD_UP] = Button.UP;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = Button.DOWN;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = Button.LEFT;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = Button.RIGHT;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_A] = Button.A;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_B] = Button.B;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_X] = Button.X;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_Y] = Button.Y;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = Button.L;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER] = Button.R;
|
|
controllerTable[SDL_CONTROLLER_BUTTON_START] = Button.START;
|
|
if(!window)
|
|
{
|
|
write("can't create window: ");
|
|
writeln(SDL_GetError.to!string);
|
|
return;
|
|
}
|
|
bool fullscreen = false;
|
|
scope(exit)
|
|
{
|
|
quit = true;
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
}
|
|
renderer = SDL_CreateRenderer(window, -1, 0);
|
|
if(!renderer)
|
|
{
|
|
write("can't create renderer: ");
|
|
writeln(SDL_GetError.to!string);
|
|
return;
|
|
}
|
|
SDL_Event event;
|
|
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
|
|
contextVM = SDL_GL_CreateContext(window);
|
|
context = SDL_GL_CreateContext(window);
|
|
if(!context || !contextVM)
|
|
{
|
|
write("can't create OpenGL context: ");
|
|
writeln(SDL_GetError.to!string);
|
|
return;
|
|
}
|
|
SDL_GL_MakeCurrent(window, context);
|
|
console.initGRPF();
|
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
|
glEnable(GL_DEPTH_TEST);
|
|
/*version(Windows)
|
|
{
|
|
SDL_SysWMinfo wm;
|
|
if(SDL_GetWindowWMInfo(window, &wm))
|
|
{
|
|
ImmAssociateContext ImmAssociateContext = cast(ImmAssociateContext)GetProcAddress(imm32, "ImmAssociateContext".toStringz);
|
|
if(ImmAssociateContext)
|
|
{
|
|
auto c = ImmAssociateContext(wm.info.win.window, null);
|
|
}
|
|
}
|
|
}*/
|
|
int loopcnt;
|
|
graphic.initGraphicPages();
|
|
dialogResource = new DialogResources(renderer, textureScaleMode);
|
|
//3graphic.initGLGraphicPages();
|
|
//glEnable(GL_BLEND);
|
|
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
//glAlphaFunc(GL_GEQUAL, 0.5);
|
|
//glEnable(GL_ALPHA_TEST);
|
|
glAlphaFunc(GL_GEQUAL, 0.1f);
|
|
glEnable(GL_ALPHA_TEST);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
{
|
|
renderCondition.mutex.lock;
|
|
scope (exit)
|
|
renderCondition.mutex.unlock;
|
|
renderCondition.notify();
|
|
}
|
|
|
|
while(!quit)
|
|
{
|
|
auto profile = SDL_GetTicks();
|
|
if(console.showCursor)
|
|
{
|
|
loopcnt++;
|
|
//30フレームに一回
|
|
if(loopcnt >= 30)
|
|
{
|
|
console.animationCursor = !console.animationCursor;
|
|
loopcnt = 0;
|
|
}
|
|
}
|
|
synchronized(renderSync)
|
|
{
|
|
glLoadIdentity();
|
|
{
|
|
ubyte r, g, b, a;
|
|
RGBRead(petitcomBackcolor, r, g, b, a);
|
|
glClearColor(r / 255f, g / 255f, b / 255f, 1);
|
|
}
|
|
//描画の順位
|
|
//sprite>GRP>console>BG
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
version(test) glLoadIdentity();
|
|
version(test) glRotatef(rot_test_deg, rot_test_x, rot_test_y, rot_test_z);
|
|
console.render();
|
|
|
|
graphic.render();
|
|
version(test) glLoadIdentity();
|
|
version(test) glRotatef(rot_test_deg, rot_test_x, rot_test_y, rot_test_z);
|
|
|
|
if (BGvisibles[0])
|
|
{
|
|
chRenderingDisplay(0);
|
|
glBindTexture(GL_TEXTURE_2D, graphic.GRP[bgpage[0]].glTexture);
|
|
for(int i = 0; i < bgmax; i++)
|
|
{
|
|
bg[i].render(0, currentDisplay.rect[0].w, currentDisplay.rect[0].h);
|
|
}
|
|
}
|
|
if(currentDisplay.rect.length > 1 && BGvisibles[1])
|
|
{
|
|
chRenderingDisplay(1);
|
|
glBindTexture(GL_TEXTURE_2D, graphic.GRP[bgpage[1]].glTexture);
|
|
for(int i = bgmax; i < bg.length; i++)
|
|
{
|
|
bg[i].render(1, currentDisplay.rect[1].w, currentDisplay.rect[1].h);
|
|
}
|
|
}
|
|
glMatrixMode(GL_MODELVIEW);
|
|
version(test) glLoadIdentity();
|
|
version(test) glRotatef(rot_test_deg, rot_test_x, rot_test_y, rot_test_z);
|
|
|
|
//http://marina.sys.wakayama-u.ac.jp/~tokoi/?date=20081122 みたいな方法もあるけどとりあえず
|
|
//とりあえず一番楽な方法main
|
|
//これだと同一Zでスプライトが一番下に来てしまうのでZ値を補正する必要がある->やった
|
|
glEnable(GL_BLEND);
|
|
glDepthMask(GL_FALSE);//スプライト同士でのZバッファは半透明だと邪魔なので無効
|
|
sprite.render();//Zソートすべき->やった->プチコンの挙動的に安定ソートか非安定ソートか
|
|
glDepthMask(GL_TRUE);
|
|
glDisable(GL_BLEND);
|
|
for (int i = 0; i < currentDisplay.rect.length; i++)
|
|
{
|
|
fades[i].render();
|
|
}
|
|
if (dialog)
|
|
{
|
|
dialog.render();
|
|
}
|
|
SDL_GL_SwapWindow(window);
|
|
auto renderticks = (SDL_GetTicks() - profile);
|
|
if(renderprofile) writeln(renderticks);
|
|
int mousex, mousey;
|
|
if (SDL_GetMouseState(&mousex, &mousey) & SDL_BUTTON_LMASK)
|
|
{
|
|
updateTouch(mousex, mousey);
|
|
}
|
|
else
|
|
{
|
|
if (touchPosition.tm)
|
|
{
|
|
auto old = touchPosition;
|
|
old.tm = 0;
|
|
touchPosition = old;
|
|
}
|
|
}
|
|
}
|
|
maincntRender++;
|
|
updateSticks();
|
|
bool isReturnKeyPressed;//for IM
|
|
while (SDL_PollEvent(&event))
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case SDL_QUIT:
|
|
return;
|
|
case SDL_KEYUP:
|
|
synchronized (buttonLock)
|
|
{
|
|
auto key = event.key.keysym.sym;
|
|
if (controllers.length == 0)
|
|
button &= ~buttonTable[event.key.keysym.scancode];
|
|
else
|
|
button &= ~controllerButtonTable[event.key.keysym.scancode];
|
|
}
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
synchronized (buttonLock)
|
|
{
|
|
auto key = event.key.keysym.sym;
|
|
if (event.key.keysym.mod & KMOD_ALT)
|
|
{
|
|
if (key == SDLK_RETURN)
|
|
{
|
|
fullscreen = !fullscreen;
|
|
if (fullscreen)
|
|
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
|
|
else
|
|
SDL_SetWindowFullscreen(window, 0);
|
|
break;
|
|
}
|
|
}
|
|
if (controllers.length == 0)
|
|
button |= buttonTable[event.key.keysym.scancode];
|
|
else
|
|
button |= controllerButtonTable[event.key.keysym.scancode];
|
|
if(key == SDLK_v)
|
|
{
|
|
auto mod = event.key.keysym.mod;
|
|
if((mod & 0xC0))
|
|
{
|
|
sendKey(Key(KeyOp.PASTE));
|
|
}
|
|
}
|
|
//TODO:mod key support
|
|
if(event.key.keysym.scancode == SDL_SCANCODE_C && (event.key.keysym.mod & 0xC0) || Button.START & buttonTable[event.key.keysym.scancode])
|
|
{
|
|
stop();
|
|
}
|
|
if (event.key.keysym.scancode <= SDL_SCANCODE_F5 && event.key.keysym.scancode >= SDL_SCANCODE_F1)
|
|
{
|
|
foreach(wchar k; functionKey[event.key.keysym.scancode - SDL_SCANCODE_F1])
|
|
{
|
|
sendKey(k);
|
|
}
|
|
}
|
|
if(key == SDLK_BACKSPACE)
|
|
{
|
|
sendKey('\u0008');
|
|
}
|
|
if(key == SDLK_RETURN)
|
|
{
|
|
isReturnKeyPressed = true;
|
|
}
|
|
}
|
|
break;
|
|
case SDL_TEXTEDITING:
|
|
{
|
|
auto text = event.edit.text[0..event.text.text.indexOf('\0')].to!wstring;
|
|
console.setIMEditInfo(text, event.edit.start, event.edit.length);
|
|
break;
|
|
}
|
|
case SDL_TEXTINPUT:
|
|
{
|
|
console.setIMEditInfo("", 0, 0);
|
|
auto text = event.text.text[0..event.text.text.indexOf('\0')].to!wstring;
|
|
foreach(wchar key; text)
|
|
{
|
|
sendKey(key);
|
|
}
|
|
isReturnKeyPressed = false;
|
|
}
|
|
break;
|
|
case SDL_MOUSEWHEEL:
|
|
synchronized (renderSync)
|
|
{
|
|
currentDisplay.yoffset -= event.wheel.y * 8;
|
|
}
|
|
break;
|
|
case SDL_CONTROLLERDEVICEADDED:
|
|
{
|
|
AddController(event.cdevice.which);
|
|
}
|
|
break;
|
|
case SDL_CONTROLLERDEVICEREMOVED:
|
|
{
|
|
RemoveController(event.cdevice.which);
|
|
}
|
|
break;
|
|
case SDL_CONTROLLERBUTTONDOWN:
|
|
{
|
|
auto jbtn = event.jbutton.button;
|
|
if (jbtn in controllerTable)
|
|
{
|
|
button |= controllerTable[event.jbutton.button];
|
|
}
|
|
}
|
|
break;
|
|
case SDL_CONTROLLERBUTTONUP:
|
|
{
|
|
auto jbtn = event.jbutton.button;
|
|
if (jbtn in controllerTable)
|
|
{
|
|
button &= ~controllerTable[event.jbutton.button];
|
|
}
|
|
}
|
|
break;
|
|
case SDL_FINGERDOWN:
|
|
{
|
|
auto f = event.tfinger;
|
|
auto x = f.x * this.currentDisplay.windowSize.width;
|
|
auto y = f.y * this.currentDisplay.windowSize.height;
|
|
updateTouch(cast(int)x, cast(int)y);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (isReturnKeyPressed)
|
|
sendKey('\u000D');
|
|
long delay = (1000/60) - (cast(long)SDL_GetTicks() - profile);
|
|
if(delay > 0)
|
|
SDL_Delay(cast(uint)delay);
|
|
}
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
writeln(t);
|
|
readln();
|
|
}
|
|
}
|
|
SDL_GameController*[int] controllers;
|
|
SDL_GameController* activeController;
|
|
void AddController(int id)
|
|
{
|
|
if(SDL_IsGameController(id))
|
|
{
|
|
SDL_GameController* pad = SDL_GameControllerOpen(id);
|
|
if (pad)
|
|
{
|
|
controllers[id] = pad;
|
|
activeController = pad;
|
|
}
|
|
}
|
|
}
|
|
void RemoveController(int id)
|
|
{
|
|
if(SDL_IsGameController(id))
|
|
{
|
|
SDL_GameControllerClose(controllers.get(id, null));
|
|
controllers.remove(id);
|
|
if (controllers.length == 0)
|
|
activeController = null;
|
|
else
|
|
activeController = controllers.byValue.front;
|
|
}
|
|
}
|
|
SDL_Window* window;
|
|
otya.smilebasic.vm.VM vm;
|
|
int maincntRender;
|
|
bool running;
|
|
bool stopflg;
|
|
void stop()
|
|
{
|
|
running = false;
|
|
stopflg = true;
|
|
}
|
|
void printInformation()
|
|
{
|
|
console.print("otyaSMILEBASIC ver ", versionString, "\n");
|
|
console.print("(C)2015-2017 otya\n");
|
|
console.print("8327164 bytes free\n");
|
|
console.print("\n");
|
|
printPrompt();
|
|
}
|
|
void printPrompt()
|
|
{
|
|
if(project.currentProject.length) console.print("[", project.currentProject, "]");
|
|
console.print("OK\n");
|
|
}
|
|
Console console;
|
|
Object buttonLock;
|
|
bool isRunningDirectMode = false;
|
|
GLenum textureScaleMode;
|
|
int maincnt;
|
|
SmileBasicError lastError;
|
|
void run(bool nodirectmode = false, string inputfile = "", bool antialiasing = false, bool redirectConsole = false)
|
|
{
|
|
if (antialiasing)
|
|
{
|
|
textureScaleMode = GL_LINEAR;
|
|
}
|
|
else
|
|
{
|
|
textureScaleMode = GL_NEAREST;
|
|
}
|
|
this.redirectConsole = redirectConsole;
|
|
buttonLock = new Object();
|
|
program = new Program();
|
|
keybuffer = new Key[128];
|
|
init();
|
|
graphic = new Graphic2(this);
|
|
keybuffermutex = new Mutex();
|
|
grpmutex = new Mutex();
|
|
sprite = new Sprite(this);
|
|
for(int i = 0; i < bg.length; i++)
|
|
bg[i] = new BG(this);
|
|
fades = new Fade[displayCount];
|
|
for (int i = 0; i < displayCount; i++)
|
|
fades[i] = new Fade(this, i);
|
|
core.thread.Thread thread = new core.thread.Thread(&render);
|
|
renderCondition = new Condition(new Mutex());
|
|
thread.start();
|
|
{
|
|
renderCondition.mutex.lock;
|
|
scope (exit)
|
|
renderCondition.mutex.unlock;
|
|
renderCondition.wait();
|
|
}
|
|
graphic.initVM();
|
|
xscreen(0, 512, 4);
|
|
auto startTicks = SDL_GetTicks();
|
|
Parser parser;
|
|
otya.smilebasic.vm.VM vm;
|
|
bool directMode = false;
|
|
int oldpc;
|
|
void runSlot(int lot)
|
|
{
|
|
parser = new Parser(cast(wstring)program.slot[lot].text);
|
|
try
|
|
{
|
|
vm = parser.compile();
|
|
vm.init(this);
|
|
running = true;
|
|
}
|
|
catch(SmileBasicError sbe)
|
|
{
|
|
console.print(sbe.getErrorMessage, "\n");
|
|
if(sbe.getErrorMessage2.length) console.print(sbe.getErrorMessage2, "\n");
|
|
writeln(sbe.to!string);
|
|
writeln(sbe.getErrorMessage2);
|
|
lastError = sbe;
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
lastError = new InternalError(t);
|
|
writeln(t);
|
|
}
|
|
}
|
|
//デバッグ用
|
|
version(NDirectMode)
|
|
nodirectmode = true;
|
|
if (nodirectmode)
|
|
{
|
|
program.slot[0].load(inputfile.to!wstring, readText(inputfile).to!wstring);
|
|
runSlot(0);
|
|
}
|
|
else
|
|
{
|
|
printInformation();
|
|
directMode = true;
|
|
}
|
|
do
|
|
{
|
|
if(quit) return;
|
|
if(directMode)
|
|
{
|
|
if(!vm)
|
|
{
|
|
import otya.smilebasic.vm;
|
|
vm = (new Parser("")).compile;
|
|
vm.init(this);
|
|
}
|
|
else
|
|
{
|
|
printPrompt();
|
|
}
|
|
auto prg = input("", true, false);
|
|
if (!prg.empty && (inputHistory.length == 0 || inputHistory[inputHistory.length - 1] != prg))
|
|
{
|
|
inputHistory.put(prg);
|
|
}
|
|
auto lex = new Lexical(prg);
|
|
lex.popFront();
|
|
auto token = lex.front();
|
|
if(token.type == otya.smilebasic.token.TokenType.Iden && token.value.stringValue == "RUN")
|
|
{
|
|
bool empty = lex.empty();
|
|
int slot;
|
|
if(!empty)
|
|
{
|
|
lex.popFront();
|
|
token = lex.front();
|
|
if(token.type != otya.smilebasic.token.TokenType.Integer)
|
|
{
|
|
//TODO:error
|
|
console.print("Illegal function call", "\n");
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
slot = token.value.castInteger;
|
|
}
|
|
}
|
|
isRunningDirectMode = false;
|
|
runSlot(slot);
|
|
}
|
|
else if(token.type == otya.smilebasic.token.TokenType.Iden && token.value.stringValue == "CONT")
|
|
{
|
|
vm.pc = oldpc;
|
|
isRunningDirectMode = false;
|
|
directMode = false;
|
|
}
|
|
else
|
|
{
|
|
isRunningDirectMode = true;
|
|
parser = new Parser(prg);
|
|
try
|
|
{
|
|
auto cc = parser.compiler;
|
|
cc.compileDirectMode(vm);
|
|
}
|
|
catch(SmileBasicError sbe)
|
|
{
|
|
try
|
|
{
|
|
console.print(sbe.getErrorMessage, "\n");
|
|
if(sbe.getErrorMessage2.length) console.print(sbe.getErrorMessage2, "\n");
|
|
writeln(sbe.to!string);
|
|
writeln(sbe.getErrorMessage2);
|
|
auto loc = vm.currentLocation;
|
|
console.print(vm.currentSlotNumber, ":", loc.line, ":", loc.pos, ":", program.slot[vm.currentSlotNumber].getLine(loc));
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
writeln(t);
|
|
}
|
|
continue;
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
try
|
|
{
|
|
console.print(t.to!string);
|
|
console.print("\n");
|
|
writeln(t);
|
|
auto loc = vm.currentLocation;
|
|
console.print(vm.currentSlotNumber, ":", loc.line, ":", loc.pos, ":", program.slot[vm.currentSlotNumber].getLine(loc));
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
writeln(t);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
running = true;
|
|
}
|
|
version (dumpVM)
|
|
vm.dump;
|
|
this.vm = vm;
|
|
int startcnt = SDL_GetTicks();
|
|
float frame = 1000f / 60f;
|
|
typeof(vm.currentLocation()) loc;
|
|
debug bool trace = false;
|
|
while (true)
|
|
{
|
|
uint elapse;
|
|
startTicks = SDL_GetTicks();
|
|
int oldmaincnt = maincntRender;
|
|
//do
|
|
{
|
|
try
|
|
{
|
|
for(int i = 0; !quit && maincntRender == oldmaincnt && !waitFlag && running; i++)
|
|
{
|
|
version (traceVM)
|
|
std.stdio.stderr.writefln("%04X:%s", vm.pc, vm.getCurrent.toString(vm));
|
|
version (dumpStackVM)
|
|
vm.dumpStack();
|
|
running = vm.runStep();
|
|
debug if(trace && loc.line != vm.currentLocation.line)
|
|
{
|
|
loc = vm.currentLocation;
|
|
console.print(loc.line, ":", loc.pos, ":", parser.getLine(loc));
|
|
}
|
|
}
|
|
}
|
|
catch(SmileBasicError sbe)
|
|
{
|
|
running = false;
|
|
try
|
|
{
|
|
console.print(sbe.getErrorMessage, "\n");
|
|
if(sbe.getErrorMessage2.length) console.print(sbe.getErrorMessage2, "\n");
|
|
//print(sbe.to!string);
|
|
writeln(sbe.to!string);
|
|
writeln(sbe.getErrorMessage2);
|
|
writeln(sbe.func);
|
|
lastError = sbe;
|
|
loc = vm.currentLocation;
|
|
console.print(vm.currentSlotNumber, ":", loc.line, ":", loc.pos, ":", program.slot[vm.currentSlotNumber].getLine(loc));
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
lastError = new InternalError(t);
|
|
writeln(t);
|
|
}
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
lastError = new InternalError(t);
|
|
running = false;
|
|
try
|
|
{
|
|
console.print(t.to!string);
|
|
console.print("\n");
|
|
writeln(t);
|
|
loc = vm.currentLocation;
|
|
console.print(vm.currentSlotNumber, ":", loc.line, ":", loc.pos, ":", program.slot[vm.currentSlotNumber].getLine(loc));
|
|
}
|
|
catch(Throwable t)
|
|
{
|
|
writeln(t);
|
|
}
|
|
}
|
|
if(!running || stopflg)
|
|
{
|
|
if (nodirectmode)
|
|
quit = true;
|
|
if(stopflg)
|
|
{
|
|
loc = vm.currentLocation;
|
|
console.print("Break on ", vm.currentSlotNumber, ":", loc.line, "\n");
|
|
stopflg = false;
|
|
directMode = true;
|
|
if(!isRunningDirectMode)
|
|
oldpc = vm.pc;
|
|
}
|
|
if(directMode) break;
|
|
}
|
|
//elapse = SDL_GetTicks() - startTicks;
|
|
} //while(elapse <= 1000 / 60);
|
|
if (maincntRender != oldmaincnt)
|
|
{
|
|
maincnt++;
|
|
vsyncCount++;
|
|
}
|
|
if(waitFlag)
|
|
{
|
|
graphic.updateVM();
|
|
if (isVSync && prevVSyncCount)
|
|
{
|
|
vsyncFrame -= vsyncCount - prevVSyncCount;
|
|
}
|
|
auto endmaincnt = maincnt + vsyncFrame;
|
|
oldmaincnt = maincntRender;
|
|
while (maincnt < endmaincnt && !quit && running)
|
|
{
|
|
SDL_Delay(1);
|
|
if (maincntRender != oldmaincnt)
|
|
{
|
|
maincnt++;
|
|
oldmaincnt = maincntRender;
|
|
}
|
|
}
|
|
vsyncFrame = 0;
|
|
waitFlag = false;
|
|
isVSync = false;
|
|
prevVSyncCount = vsyncCount;
|
|
}
|
|
//maincnt = cast(int)((SDL_GetTicks() - startcnt) / frame);
|
|
graphic.updateVM();
|
|
if(quit)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
graphic.updateVM();
|
|
} while(directMode);
|
|
scope(exit)
|
|
{
|
|
writeln("quit");
|
|
}
|
|
}
|
|
void clearKeyBuffer()
|
|
{
|
|
keybuffermutex.lock();
|
|
scope(exit) keybuffermutex.unlock();
|
|
keybufferpos = 0;
|
|
keybufferlen = 0;
|
|
}
|
|
RingBuffer!wstring inputHistory = new RingBuffer!wstring(32/*ダイレクトモードでの履歴の数は32*/);
|
|
wstring input(wstring prompt, bool useClipBoard, bool isVM/*if this flag is true, enable start/select interrupt*/)
|
|
{
|
|
auto olddisplay = displaynum;
|
|
//displaynum = 0;
|
|
console.print(prompt);
|
|
clearKeyBuffer();
|
|
wstring buffer;
|
|
console.showCursor = true;
|
|
int oldCSRX = this.console.CSRX;
|
|
int oldCSRY = this.console.CSRY;
|
|
int pos;
|
|
int historyIndex = cast(int)inputHistory.length;
|
|
int oldmaincnt = maincntRender;
|
|
scope (exit)
|
|
{
|
|
maincnt += maincntRender - oldmaincnt;
|
|
vsyncCount += maincntRender - oldmaincnt;
|
|
}
|
|
void setText(wstring text)
|
|
{
|
|
console.CSRX = oldCSRX;
|
|
console.CSRY = oldCSRY;
|
|
for (int i = 0; i < buffer.length; i++)
|
|
{
|
|
console.print(" ");
|
|
}
|
|
buffer = text;
|
|
pos = cast(int)text.length;
|
|
console.CSRX = oldCSRX;
|
|
console.CSRY = oldCSRY;
|
|
console.print(text);
|
|
}
|
|
void left()
|
|
{
|
|
if (pos == 0)
|
|
{
|
|
return;
|
|
}
|
|
pos--;
|
|
if (console.CSRX == 0)
|
|
{
|
|
console.CSRY--;
|
|
console.CSRX = console.consoleWidthC - 1;
|
|
return;
|
|
}
|
|
console.CSRX--;
|
|
}
|
|
void right()
|
|
{
|
|
if (pos >= buffer.length)
|
|
{
|
|
return;
|
|
}
|
|
pos++;
|
|
console.CSRX++;
|
|
if (console.CSRX == console.consoleWidthC)
|
|
{
|
|
console.CSRY++;
|
|
console.CSRX = 0;
|
|
}
|
|
}
|
|
while(!quit && (!isVM || running))
|
|
{
|
|
auto oldpos = keybufferpos;
|
|
while(oldpos == keybufferpos && !quit && (!isVM || running))
|
|
{
|
|
Button old;
|
|
synchronized(buttonLock)
|
|
{
|
|
old = button;
|
|
}
|
|
SDL_Delay(16);//適当 ストレスを感じないくらい
|
|
Button button;
|
|
synchronized(buttonLock)
|
|
{
|
|
button = this.button;
|
|
}
|
|
button = button ^ old & button;
|
|
if (button & Button.UP)
|
|
{
|
|
if (historyIndex - 1 >= 0)
|
|
{
|
|
historyIndex--;
|
|
setText(inputHistory[historyIndex]);
|
|
}
|
|
}
|
|
if (button & Button.DOWN)
|
|
{
|
|
if (historyIndex + 1 < inputHistory.length)
|
|
{
|
|
historyIndex++;
|
|
setText(inputHistory[historyIndex]);
|
|
}
|
|
else
|
|
{
|
|
historyIndex++;
|
|
setText("");
|
|
}
|
|
}
|
|
if (button & Button.LEFT)
|
|
{
|
|
left();
|
|
}
|
|
if (button & Button.RIGHT)
|
|
{
|
|
right();
|
|
}
|
|
}
|
|
auto kbp = keybufferpos;
|
|
auto len = kbp - oldpos;
|
|
if(len < 0)
|
|
{
|
|
//arienai
|
|
kbp = oldpos;
|
|
}
|
|
wchar k;
|
|
//文字入力中はカーソルを表示する
|
|
console.animationCursor = true;
|
|
foreach(key1; keybuffer[oldpos..kbp])
|
|
{
|
|
wchar key = key1.key;
|
|
wstring ks;
|
|
wstring obf = buffer;
|
|
if(key1.op == KeyOp.KEY)
|
|
{
|
|
if(key == 8)
|
|
{
|
|
k = key;
|
|
if(!buffer.length) continue;
|
|
left();
|
|
auto odcsrx = console.CSRX;
|
|
auto odcsry = console.CSRY;
|
|
buffer = (pos ? buffer[0..pos] : "");
|
|
auto ocsrx = console.CSRX;
|
|
auto ocsry = console.CSRY;
|
|
if (obf.length > pos + 1)
|
|
{
|
|
buffer ~= obf[pos + 1..$];
|
|
console.print(obf[pos + 1..$]);
|
|
}
|
|
console.print(" ");
|
|
console.CSRX = odcsrx;
|
|
console.CSRY = odcsry;
|
|
continue;
|
|
}
|
|
if(key == '\r')
|
|
{
|
|
k = key;
|
|
console.print("\n");
|
|
break;
|
|
}
|
|
immutable(wchar[1]) a = key;
|
|
ks = a[];
|
|
}
|
|
else if(key1.op == KeyOp.PASTE)
|
|
{
|
|
if(SDL_HasClipboardText())
|
|
{
|
|
char* cl = SDL_GetClipboardText();
|
|
string an = cast(string)(cl[0..core.stdc.string.strlen(cl)]);
|
|
ks = an.to!wstring;
|
|
SDL_free(cl);
|
|
}
|
|
}
|
|
console.print(ks);
|
|
buffer = (pos ? buffer[0..pos] : "") ~ ks;
|
|
auto ocsrx = console.CSRX;
|
|
auto ocsry = console.CSRY;
|
|
if (obf.length > pos)
|
|
{
|
|
buffer ~= obf[pos..$];
|
|
console.print(obf[pos..$]);
|
|
}
|
|
console.CSRX = ocsrx;
|
|
console.CSRY = ocsry;
|
|
pos += ks.length;
|
|
}
|
|
clearKeyBuffer();
|
|
if(k == '\r')
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
console.showCursor = false;
|
|
//display = olddisplay;
|
|
return buffer;
|
|
}
|
|
wstring inkey()
|
|
{
|
|
keybuffermutex.lock();
|
|
scope (exit)
|
|
keybuffermutex.unlock();
|
|
if (!keybufferlen)
|
|
{
|
|
return "";
|
|
}
|
|
auto result = keybuffer[(keybufferpos - keybufferlen) % keybuffer.length];
|
|
keybufferlen--;
|
|
return result.key.to!wstring;
|
|
}
|
|
public bool isValidGraphicPage(int page)
|
|
{
|
|
return page >= 0 && page < 6;
|
|
}
|
|
enum XMode
|
|
{
|
|
_3DS,
|
|
COMPAT,
|
|
WIIU,
|
|
}
|
|
struct X
|
|
{
|
|
bool motion;
|
|
bool expad;
|
|
bool mic;
|
|
XMode mode;
|
|
}
|
|
X x;
|
|
public void xoff(wstring func)
|
|
{
|
|
if (func == "MIC")
|
|
{
|
|
x.mic = false;
|
|
}
|
|
else if (func == "EXPAD")
|
|
{
|
|
x.expad = false;
|
|
}
|
|
else if (func == "MOTION")
|
|
{
|
|
x.motion = false;
|
|
}
|
|
else
|
|
{
|
|
std.stdio.stderr.writefln("Unknown XOFF %s", func);
|
|
}
|
|
}
|
|
public void xon(wstring func)
|
|
{
|
|
if (func == "WIIU")
|
|
{
|
|
x.mode = XMode.WIIU;
|
|
}
|
|
else if (func == "3DS")
|
|
{
|
|
x.mode = XMode._3DS;
|
|
}
|
|
else if (func == "COMPAT")
|
|
{
|
|
x.mode = XMode.COMPAT;
|
|
}
|
|
else if (func == "MIC")
|
|
{
|
|
x.mic = true;
|
|
}
|
|
else if (func == "EXPAD")
|
|
{
|
|
x.expad = true;
|
|
}
|
|
else if (func == "MOTION")
|
|
{
|
|
x.motion = true;
|
|
}
|
|
else
|
|
{
|
|
std.stdio.stderr.writefln("Unknown XON %s", func);
|
|
}
|
|
}
|
|
Hardware hardware = Hardware.wiiU;
|
|
wstring versionString = "3.5.0";
|
|
int version_ = 0x3050000;
|
|
wstring[5] functionKey;
|
|
|
|
DialogBase dialog;
|
|
DialogResources dialogResource;
|
|
SDL_Rect showTouchScreen()
|
|
{
|
|
if (currentDisplay.rect.length != 1)
|
|
{
|
|
return currentDisplay.rect[1];
|
|
}
|
|
SDL_Rect rect;
|
|
if (xscreenmode == 0)
|
|
{
|
|
rect = SDL_Rect((screenWidth - screenWidthDisplay1) / 2, screenHeight, screenWidthDisplay1, screenHeightDisplay1);
|
|
}
|
|
else
|
|
{
|
|
rect = currentDisplay.rect[0];
|
|
rect.y += rect.h;
|
|
}
|
|
currentDisplay.windowSize.height += rect.h;
|
|
currentDisplay.yoffset = currentDisplay.windowSize.height;
|
|
updateWindowSize();
|
|
return rect;
|
|
}
|
|
void hideTouchScreen()
|
|
{
|
|
if (currentDisplay.rect.length != 1)
|
|
{
|
|
return;
|
|
}
|
|
auto rect = currentDisplay.rect[0];
|
|
currentDisplay.windowSize.height -= rect.h;
|
|
currentDisplay.yoffset = currentDisplay.windowSize.height;
|
|
updateWindowSize();
|
|
}
|
|
|
|
Program program;
|
|
|
|
void acls(bool gclsAndSpriteImageBGImageInit, bool spdef, bool fontdef)
|
|
{
|
|
console.initConsole();
|
|
xscreen(0, 512, 4);
|
|
console.cls();
|
|
backcolor = 0;
|
|
if (gclsAndSpriteImageBGImageInit)
|
|
{
|
|
//TODO:sprite,bg
|
|
for (int i = 0; i <= 3; i++)
|
|
{
|
|
this.graphic.useGRP = i;
|
|
graphic.gcls(0, 0);
|
|
}
|
|
}
|
|
this.graphic.usePage = [0, 1];
|
|
this.graphic.showPage = [0, 1];
|
|
this.graphic.gprios = [512, 512];/*reference:GPRIO 1024*/
|
|
this.graphic.visibles = [1, 1];
|
|
this.sprite.sppage = [4, 4];
|
|
this.sprite.allclear();
|
|
this.sprite.visibles = [true, true];
|
|
this.bgpage = [5, 5];
|
|
this.BGvisibles = [true, true];
|
|
foreach (b; bg)
|
|
{
|
|
b.screen(25, 15);
|
|
b.clip();
|
|
}
|
|
if (spdef)
|
|
{
|
|
this.sprite.spdef();
|
|
}
|
|
if (fontdef)
|
|
{
|
|
this.console.initGRPF();
|
|
}
|
|
}
|
|
|
|
int clipboardSize = 1048576;
|
|
wstring clipboard()
|
|
{
|
|
if(SDL_HasClipboardText())
|
|
{
|
|
char* cl = SDL_GetClipboardText();
|
|
string an = cast(string)(cl[0..core.stdc.string.strlen(cl)]);
|
|
auto ret = an.to!wstring;
|
|
if (ret.length > clipboardSize)
|
|
ret = ret[0..clipboardSize];
|
|
SDL_free(cl);
|
|
return ret;
|
|
}
|
|
return "";
|
|
}
|
|
void clipboard(wstring ws)
|
|
{
|
|
if (ws.length > clipboardSize)
|
|
{
|
|
throw new StringTooLong("CLIPBOARD");
|
|
}
|
|
SDL_SetClipboardText(ws.to!string.toStringz);
|
|
}
|
|
|
|
bool callback;
|
|
int callidx = 0;
|
|
|
|
//プチコン内部表現はRGB5_A1
|
|
static uint toGLColor(GLenum format, ubyte r, ubyte g, ubyte b, ubyte a)
|
|
{
|
|
if(format == GL_BGRA)
|
|
{
|
|
return a << 24 | r << 16 | g << 8 | b;
|
|
}
|
|
if(format == GL_RGBA)
|
|
{
|
|
return a << 24 | b << 16 | g << 8 | r;
|
|
}
|
|
throw new Exception("unsuport enviroment");
|
|
}
|
|
static uint toGLColor(GLenum format, uint petitcolor)
|
|
{
|
|
if(format == GL_BGRA)
|
|
{
|
|
//ARGB->BGRA
|
|
return (petitcolor & 0xFF00FF00) | (petitcolor & 0xFF) << 16 | petitcolor >> 16 & 0xFF;
|
|
}
|
|
if(format == GL_RGBA)
|
|
{
|
|
//return a << 24 | b << 16 | g << 8 | r;
|
|
}
|
|
throw new Exception("unsuport enviroment");
|
|
}
|
|
//プチコンと違って[A,]R,G,Bじゃない
|
|
static pure nothrow void RGBRead(T)(uint color, out T r, out T g, out T b, out T a)
|
|
{
|
|
//エンディアン関係ない
|
|
a = color >> 24;
|
|
r = color >> 16 & 0xFF;
|
|
g = color >> 8 & 0xFF;
|
|
b = color& 0xFF;
|
|
}
|
|
static pure nothrow uint RGB(ubyte r, ubyte g, ubyte b)
|
|
{
|
|
return 0xFF000000 | r << 16 | g << 8 | b;
|
|
}
|
|
static pure nothrow uint RGB(ubyte a, ubyte r, ubyte g, ubyte b)
|
|
{
|
|
return a << 24 | r << 16 | g << 8 | b;
|
|
}
|
|
public Random random;
|
|
Fade[] fades;
|
|
public Fade fade()
|
|
{
|
|
return fades[displaynum];
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
auto petitcomputer = new PetitComputer();
|
|
petitcomputer.run(true, "TEST.txt", false, true);
|
|
assert(petitcomputer.lastError is null);
|
|
}
|