mirror of
https://github.com/birth1mark/orbis8.git
synced 2026-10-29 04:00:30 +01:00
585 lines
19 KiB
C++
585 lines
19 KiB
C++
#include <string>
|
|
|
|
#include "printHelper.h"
|
|
#include "nibblehelpers.h"
|
|
|
|
#include "graphics.h"
|
|
#include "vm.h"
|
|
#include "Audio.h"
|
|
|
|
PicoRam* _ph_mem;
|
|
Graphics* _ph_graphics;
|
|
Vm* _ph_vm;
|
|
Audio* _ph_audio;
|
|
|
|
void hexStrToBytes(std::string hex, uint8_t byteBuff[]) {
|
|
char buff[3];
|
|
buff[2] = 0;
|
|
for (unsigned int i = 0; i < hex.length(); i += 2) {
|
|
buff[0] = hex[i];
|
|
buff[1] = hex[i+1];
|
|
uint8_t byte = (uint8_t) strtol(buff, NULL, 16);
|
|
byteBuff[i/2] = byte;
|
|
}
|
|
}
|
|
|
|
void initPrintHelper(PicoRam* memory, Graphics* graphics, Vm* vm, Audio* audio) {
|
|
_ph_mem = memory;
|
|
_ph_graphics = graphics;
|
|
_ph_vm = vm;
|
|
_ph_audio = audio;
|
|
}
|
|
|
|
uint8_t p0CharToNum(uint8_t hexChar){
|
|
uint8_t num = 0;
|
|
if (hexChar > 47 && hexChar < 58){
|
|
num = hexChar - 48;
|
|
}
|
|
if (hexChar > 96) {
|
|
num = hexChar - 87;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
int pow2(int power){
|
|
switch(power){
|
|
case 0:
|
|
return 1;
|
|
break;
|
|
case 1:
|
|
return 2;
|
|
break;
|
|
case 2:
|
|
return 4;
|
|
break;
|
|
case 3:
|
|
return 8;
|
|
break;
|
|
case 4:
|
|
return 16;
|
|
break;
|
|
case 5:
|
|
return 32;
|
|
break;
|
|
case 6:
|
|
return 64;
|
|
break;
|
|
case 7:
|
|
return 128;
|
|
break;
|
|
case 8:
|
|
return 255;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int print(std::string str) {
|
|
//todo: default is not 0,0?
|
|
int x = _ph_mem->drawState.text_x;
|
|
int y = _ph_mem->drawState.text_y;
|
|
if (y >= 127) {
|
|
//Memcy screen to itself offset by how many lines needed (y + line height)
|
|
int lineHeight = 6; // possibly need to check if bigger font?
|
|
int linesToCopy = 127 - lineHeight;
|
|
int startY = y - linesToCopy;
|
|
|
|
memmove(
|
|
&_ph_mem->screenBuffer,
|
|
&_ph_mem->screenBuffer[COMBINED_IDX(0, startY)],
|
|
linesToCopy * 64);
|
|
|
|
//set the rest of the screen buffer to 0 (black)
|
|
memset(
|
|
&_ph_mem->screenBuffer[COMBINED_IDX(0, linesToCopy)],
|
|
0,
|
|
startY * 64);
|
|
|
|
y = _ph_mem->drawState.text_y = 127 - lineHeight;
|
|
//Memcpy buffy back to screen
|
|
|
|
}
|
|
int result = print(str, x, y);
|
|
|
|
if (_ph_mem->drawState.text_y >= 127) {
|
|
int lineHeight = 5; // possibly need to check if bigger font?
|
|
int linesToCopy = 127 - lineHeight;
|
|
int startY = _ph_mem->drawState.text_y - linesToCopy;
|
|
memmove(
|
|
&_ph_mem->screenBuffer,
|
|
&_ph_mem->screenBuffer[COMBINED_IDX(0, startY)],
|
|
linesToCopy * 64);
|
|
|
|
//set the rest of the screen buffer to 0 (black)
|
|
memset(
|
|
&_ph_mem->screenBuffer[COMBINED_IDX(0, linesToCopy)],
|
|
0,
|
|
startY * 64);
|
|
|
|
y = _ph_mem->drawState.text_y = 127 - lineHeight;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int print(std::string str, int x, int y) {
|
|
return print(str, x, y, _ph_mem->drawState.color);
|
|
}
|
|
|
|
int print(std::string str, int x, int y, uint8_t c) {
|
|
_ph_graphics->color(c);
|
|
|
|
_ph_mem->drawState.text_x = x;
|
|
_ph_mem->drawState.text_y = y;
|
|
int length = str.length();
|
|
|
|
int homeX = x;
|
|
int homeY = y;
|
|
int prevX = x;
|
|
int prevY = y;
|
|
int tabStopWidth = 4;
|
|
int charWidth = 4;
|
|
int charHeight = 6;
|
|
int lineHeight = 0;
|
|
int forceCharWidth = -1;
|
|
int forceCharHeight = -1;
|
|
uint8_t bgColor = 0xff;
|
|
uint8_t fgColor = _ph_mem->drawState.color;
|
|
uint8_t charBytes[8];
|
|
uint8_t audioStrBytes[32];
|
|
bool cancelWrap = false;
|
|
uint8_t outlineColor = 0;
|
|
uint8_t outlineNeighbors = 0;
|
|
bool underline = false;
|
|
|
|
uint8_t printMode = _ph_mem->hwState.printAttributes;
|
|
|
|
if ((printMode & 0x1) == 0) {
|
|
printMode = 0;
|
|
}
|
|
|
|
if ((printMode & PRINT_MODE_CUSTOM_FONT)) {
|
|
charWidth = _ph_mem->data[0x5600];
|
|
charHeight = _ph_mem->data[0x5602];
|
|
}
|
|
|
|
uint8_t* drawPal = _ph_mem->drawState.drawPaletteMap;
|
|
|
|
int framesBetweenChars = 0;
|
|
int framesToPause = 0;
|
|
int rhsWrap = -1;
|
|
|
|
|
|
for (size_t n = 0; n < length; n++) {
|
|
uint8_t ch = str[n];
|
|
framesToPause = framesBetweenChars;
|
|
if (ch == 0) { // null stop printing
|
|
_ph_mem->drawState.text_x=x;
|
|
_ph_mem->drawState.text_y=y;
|
|
|
|
return x;
|
|
}
|
|
else if (ch == 1) { // "\*{p0}" repeat the next character p0 times
|
|
uint8_t timesChr = str[++n];
|
|
int times = p0CharToNum(timesChr);
|
|
|
|
ch = str[++n];
|
|
for(int i = 0; i < times; i++) {
|
|
//TODO: combine with other draw character call - fix missing bg? lineheight?
|
|
x += charWidth + _ph_graphics->drawCharacter(
|
|
ch,
|
|
x,
|
|
y,
|
|
drawPal[fgColor & 0x0f],
|
|
bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f],
|
|
printMode,
|
|
forceCharWidth,
|
|
forceCharHeight);
|
|
}
|
|
}
|
|
else if (ch == 2) { // "\#{p0}" draw text on a solid background color
|
|
uint8_t bgColChar = str[++n];
|
|
bgColor = p0CharToNum(bgColChar);
|
|
printMode |= PRINT_MODE_ON;
|
|
printMode |= PRINT_MODE_SOLID_BG;
|
|
}
|
|
else if (ch == 3) { // "\-{p0}" move text cursor horizontally by 16-p0 pixels
|
|
uint8_t pixelCountChar = str[++n];
|
|
int deltaX = p0CharToNum(pixelCountChar) - 16;
|
|
|
|
x += deltaX;
|
|
//update memory cursor state?
|
|
}
|
|
else if (ch == 4) { // "\|{p0}" move text cursor vertically by 16-p0 pixels
|
|
uint8_t pixelCountChar = str[++n];
|
|
int deltaY = p0CharToNum(pixelCountChar) - 16;
|
|
|
|
y += deltaY;
|
|
//update memory cursor state?
|
|
}
|
|
else if (ch == 5) { // "\+{p0}" move text cursor vertically by 16-p0 pixels
|
|
uint8_t pixelCountXChar = str[++n];
|
|
uint8_t pixelCountYChar = str[++n];
|
|
int deltaX = p0CharToNum(pixelCountXChar) - 16;
|
|
int deltaY = p0CharToNum(pixelCountYChar) - 16;
|
|
|
|
x += deltaX;
|
|
y += deltaY;
|
|
//update memory cursor state?
|
|
}
|
|
else if (ch == 6) { // "\^" special command
|
|
uint8_t commandChar = str[++n];
|
|
// \^1 through \^9 skips a number of frames before continuing printing
|
|
if (commandChar > 48 && commandChar < 58){
|
|
//pause for x num frames
|
|
int frameCount = pow2(p0CharToNum(commandChar) - 1);
|
|
while (frameCount > 0){
|
|
//TODO: yield here? not sure how to handle this
|
|
//_ph_vm->vm_flip();
|
|
|
|
frameCount--;
|
|
}
|
|
}
|
|
// \^d P0 causes subsequent printing to pause P0 frames between each character,
|
|
else if (commandChar == 'd') {
|
|
uint8_t framesChar = str[++n];
|
|
framesBetweenChars = p0CharToNum(framesChar);
|
|
}
|
|
else if (commandChar == 'c'){
|
|
uint8_t colChar = str[++n];
|
|
uint8_t col = p0CharToNum(colChar);
|
|
|
|
_ph_graphics->cls(col);
|
|
}
|
|
else if (commandChar == 'g'){
|
|
x = homeX;
|
|
y = homeY;
|
|
}
|
|
else if (commandChar == 'h'){
|
|
homeX = x;
|
|
homeY = y;
|
|
}
|
|
else if (commandChar == 'j'){
|
|
uint8_t xChar = str[++n];
|
|
uint8_t yChar = str[++n];
|
|
x = p0CharToNum(xChar) * 4;
|
|
y = p0CharToNum(yChar) * 4;
|
|
}
|
|
else if (commandChar == 'r'){
|
|
uint8_t rhsWrapChar = str[++n];
|
|
rhsWrap = p0CharToNum(rhsWrapChar) * 4;
|
|
}
|
|
else if (commandChar == 's'){
|
|
uint8_t tswChar = str[++n];
|
|
tabStopWidth = p0CharToNum(tswChar);
|
|
}
|
|
else if (commandChar == 'x'){
|
|
uint8_t charWidthChar = str[++n];
|
|
forceCharWidth = p0CharToNum(charWidthChar);
|
|
charWidth = forceCharWidth;
|
|
}
|
|
else if (commandChar == 'y'){
|
|
//this behaves in a way I wouldn't expect in pico 8- it seems to apply to the next print statment
|
|
//but not if there is a \n in the string. Not sure if this is a bug or not
|
|
uint8_t charHeightChar = str[++n];
|
|
forceCharHeight = p0CharToNum(charHeightChar);
|
|
charHeight = forceCharHeight;
|
|
}
|
|
else if (commandChar == 'w'){
|
|
printMode |= PRINT_MODE_ON;
|
|
printMode |= PRINT_MODE_WIDE;
|
|
charWidth = 8;
|
|
}
|
|
else if (commandChar == 't'){
|
|
printMode |= PRINT_MODE_ON;
|
|
printMode |= PRINT_MODE_TALL;
|
|
charHeight = 12;
|
|
}
|
|
else if (commandChar == '='){
|
|
printMode |= PRINT_MODE_ON;
|
|
printMode |= PRINT_MODE_STRIPEY;
|
|
}
|
|
else if (commandChar == 'p'){
|
|
printMode |= PRINT_MODE_ON;
|
|
printMode |= PRINT_MODE_WIDE;
|
|
printMode |= PRINT_MODE_TALL;
|
|
printMode |= PRINT_MODE_STRIPEY;
|
|
charWidth = 8;
|
|
charHeight = 12;
|
|
}
|
|
else if (commandChar == 'i'){
|
|
printMode |= PRINT_MODE_ON;
|
|
printMode |= PRINT_MODE_INVERTED;
|
|
}
|
|
else if (commandChar == 'b'){
|
|
printMode |= PRINT_MODE_ON;
|
|
printMode |= PRINT_MODE_PADDING;
|
|
}
|
|
else if (commandChar == '#'){
|
|
printMode |= PRINT_MODE_ON;
|
|
printMode |= PRINT_MODE_SOLID_BG;
|
|
}
|
|
else if (commandChar == ':' || commandChar == ';' || commandChar == '.' || commandChar == ','){
|
|
//the docs say ';' and ',' should respect padding, but in my testing
|
|
//it looks like they behave exactly the same... /shrug
|
|
charHeight = forceCharHeight > 0 ? forceCharHeight : 8;
|
|
if (commandChar == ':' || commandChar == ';') {
|
|
std::string hexStr = str.substr(n+1, 16);
|
|
n+=16;
|
|
hexStrToBytes(hexStr, charBytes);
|
|
}
|
|
else {
|
|
std::string binStr = str.substr(n+1, 8);
|
|
n+=8;
|
|
for(size_t i = 0; i < 8; i++) {
|
|
charBytes[i] = binStr[i];
|
|
}
|
|
}
|
|
|
|
auto values = _ph_graphics->drawCharacterFromBytes(
|
|
charBytes,
|
|
x,
|
|
y,
|
|
drawPal[fgColor & 0x0f],
|
|
bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f],
|
|
printMode,
|
|
8,
|
|
charHeight);
|
|
|
|
x += 8 + get<0>(values);
|
|
charHeight = charHeight + get<1>(values);
|
|
lineHeight = charHeight > lineHeight ? charHeight : lineHeight;
|
|
}
|
|
else if (commandChar == '-'){
|
|
uint8_t turnOffModeChar = str[++n];
|
|
if (printMode) {
|
|
if (turnOffModeChar == 'w') {
|
|
printMode &= ~(PRINT_MODE_WIDE);
|
|
charWidth = 4;
|
|
}
|
|
else if (turnOffModeChar == 't') {
|
|
printMode &= ~(PRINT_MODE_TALL);
|
|
charHeight = 6;
|
|
}
|
|
else if (turnOffModeChar == '=') {
|
|
printMode &= ~(PRINT_MODE_STRIPEY);
|
|
}
|
|
else if (turnOffModeChar == 'p') {
|
|
printMode &= ~(PRINT_MODE_WIDE);
|
|
printMode &= ~(PRINT_MODE_TALL);
|
|
printMode &= ~(PRINT_MODE_STRIPEY);
|
|
charWidth = 4;
|
|
charHeight = 6;
|
|
}
|
|
else if (turnOffModeChar == 'i') {
|
|
printMode &= ~(PRINT_MODE_INVERTED);
|
|
}
|
|
else if (turnOffModeChar == 'b') {
|
|
printMode &= ~(PRINT_MODE_PADDING);
|
|
}
|
|
else if (turnOffModeChar == '#') {
|
|
printMode &= ~(PRINT_MODE_SOLID_BG);
|
|
}
|
|
}
|
|
}
|
|
else if (commandChar == '!'){
|
|
//no wraping after this print call (may be pico 8 bug?)
|
|
cancelWrap = true;
|
|
|
|
std::string addrHexStr = str.substr(n+1, 4);
|
|
n+=4;
|
|
int addr = (int)strtol(addrHexStr.c_str(), NULL, 16);
|
|
|
|
int size = length - n;
|
|
std::string binStr = str.substr(n + 1, size);
|
|
n+=size;
|
|
|
|
for(size_t i = 0; i < size; i++) {
|
|
_ph_mem->data[addr + i] = binStr[i];
|
|
}
|
|
}
|
|
else if (commandChar == '@'){
|
|
std::string addrHexStr = str.substr(n+1, 4);
|
|
n+=4;
|
|
int addr = (int)strtol(addrHexStr.c_str(), NULL, 16);
|
|
|
|
std::string sizeHexStr = str.substr(n+1, 4);
|
|
n+=4;
|
|
|
|
int size = (int)strtol(sizeHexStr.c_str(), NULL, 16);
|
|
|
|
std::string binStr = str.substr(n + 1, size);
|
|
n+= size;
|
|
|
|
for(size_t i = 0; i < size; i++) {
|
|
_ph_mem->data[addr + i] = binStr[i];
|
|
}
|
|
}
|
|
else if (commandChar == 'o'){
|
|
//Pico-8 docs for outline:
|
|
//The first character after the command "\^o" is the colour, and the following two
|
|
// characters are the neighbours bitfield in hexadecimal. For example, to draw a pixel up
|
|
// to the left of each foreground pixel, the value 0x01 can be used:
|
|
|
|
// > ?"\^o801hey"
|
|
|
|
uint8_t outlineColorChar = str[++n];
|
|
outlineColor = p0CharToNum(outlineColorChar);
|
|
|
|
std::string neighborsHexStr = str.substr(n+1, 2);
|
|
n+=2;
|
|
outlineNeighbors = (uint8_t)strtol(neighborsHexStr.c_str(), NULL, 16);
|
|
}
|
|
else if (commandChar == 'u'){
|
|
underline = true;
|
|
}
|
|
}
|
|
else if (ch == 7) { // "\a" audio command
|
|
uint8_t nextChar = str[++n];
|
|
int audioStrIdx = 0;
|
|
while (nextChar != 32 && n < length) {
|
|
audioStrBytes[audioStrIdx++] = nextChar;
|
|
nextChar = str[++n];
|
|
}
|
|
//todo: generate audio stuff and play
|
|
}
|
|
else if (ch == 11) { // "\v" decorate previous character
|
|
uint8_t offsetChar = str[++n];
|
|
uint8_t toPrint = str[++n];
|
|
int offset = p0CharToNum(offsetChar);
|
|
|
|
int xOffset = (offset%4)-2;
|
|
int yOffset = (offset/4)-8;
|
|
|
|
_ph_graphics->drawCharacter(
|
|
toPrint,
|
|
prevX + xOffset,
|
|
prevY + yOffset,
|
|
drawPal[fgColor & 0x0f],
|
|
bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f],
|
|
printMode,
|
|
forceCharWidth,
|
|
forceCharHeight);
|
|
}
|
|
else if (ch == 12) { //"\f{p0}" draw text with this foreground color
|
|
uint8_t fgColChar = str[++n];
|
|
fgColor = p0CharToNum(fgColChar);
|
|
_ph_graphics->color(fgColor);
|
|
}
|
|
else if (ch == 14) { //"\014" Turn on custom font stored at 0x5600
|
|
printMode |= PRINT_MODE_CUSTOM_FONT;
|
|
charWidth = _ph_mem->data[0x5600];
|
|
charHeight = _ph_mem->data[0x5602];
|
|
}
|
|
else if (ch == 15) { //"\015" Turn off custom font stored at 0x5600
|
|
printMode &= ~(PRINT_MODE_CUSTOM_FONT);
|
|
charWidth = 4;
|
|
charHeight = 6;
|
|
}
|
|
else if (ch == '\n') {
|
|
x = homeX;
|
|
lineHeight = lineHeight > 0 ? lineHeight : 6;
|
|
y += lineHeight;
|
|
lineHeight = 0;
|
|
}
|
|
else if (ch == '\t') {
|
|
while (x % (tabStopWidth*4) > 0) {
|
|
x++;
|
|
}
|
|
}
|
|
else if (ch == '\b') {
|
|
x -= charWidth;
|
|
}
|
|
else if (ch == '\r') {
|
|
x = homeX;
|
|
}
|
|
else if (ch >= 0x10) {
|
|
lineHeight = charHeight > lineHeight ? charHeight : lineHeight;
|
|
if (printMode & PRINT_MODE_SOLID_BG) {
|
|
uint8_t prevPenColor = _ph_mem->drawState.color;
|
|
_ph_graphics->rectfill(x-1, y-1, x + charWidth-1, y + lineHeight-1, bgColor);
|
|
_ph_mem->drawState.color = prevPenColor;
|
|
}
|
|
prevX = x;
|
|
prevY = y;
|
|
if (outlineColor != 0 && outlineNeighbors != 0) {
|
|
/*
|
|
0x01 0x02 0x04
|
|
0x08 -- 0x10
|
|
0x20 0x40 0x80
|
|
*/
|
|
int xOffsets[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
|
|
int yOffsets[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
int xOffset = xOffsets[i];
|
|
int yOffset = yOffsets[i];
|
|
|
|
if (xOffset == 0 && yOffset == 0) {
|
|
continue;
|
|
}
|
|
if (outlineNeighbors & (1 << i)) {
|
|
_ph_graphics->drawCharacter(
|
|
ch,
|
|
x + xOffset,
|
|
y + yOffset,
|
|
drawPal[outlineColor & 0x0f],
|
|
bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f],
|
|
printMode,
|
|
forceCharWidth,
|
|
forceCharHeight);
|
|
}
|
|
}
|
|
}
|
|
if (underline) {
|
|
_ph_graphics->rectfill(x-1, y + lineHeight, x + charWidth - 1, y + lineHeight, fgColor);
|
|
}
|
|
int extraWidth = _ph_graphics->drawCharacter(
|
|
ch,
|
|
x,
|
|
y,
|
|
drawPal[fgColor & 0x0f],
|
|
bgColor == 0xff ? 0 : drawPal[bgColor & 0x0f],
|
|
printMode,
|
|
forceCharWidth,
|
|
forceCharHeight);
|
|
|
|
// Apply per-character width adjustment if flag 0x1 is set at 0x5605
|
|
int perCharWidthAdj = 0;
|
|
if ((printMode & PRINT_MODE_CUSTOM_FONT) && (_ph_mem->data[0x5605] & 0x01)) {
|
|
int nibbleIdx = ch - 16;
|
|
if (nibbleIdx >= 0 && nibbleIdx < 240) {
|
|
uint8_t adjByte = _ph_mem->data[0x5608 + nibbleIdx/2];
|
|
uint8_t adjNibble = (nibbleIdx % 2 == 0) ? (adjByte & 0x0f) : (adjByte >> 4);
|
|
perCharWidthAdj = (adjNibble & 0x07);
|
|
if (perCharWidthAdj >= 4) perCharWidthAdj = perCharWidthAdj - 8; // convert 4,5,6,7 to -4,-3,-2,-1
|
|
}
|
|
}
|
|
|
|
x += charWidth + extraWidth + perCharWidthAdj;
|
|
|
|
while (framesToPause > 0){
|
|
//TODO: yield here? not sure how to handle this
|
|
//_ph_vm->vm_flip();
|
|
|
|
framesToPause--;
|
|
}
|
|
}
|
|
|
|
//soft wrap if enabled
|
|
if (rhsWrap > 0 && x >= rhsWrap) {
|
|
x = homeX;
|
|
lineHeight = lineHeight > 0 ? lineHeight : 6;
|
|
y += lineHeight;
|
|
lineHeight = 0;
|
|
}
|
|
}
|
|
|
|
lineHeight = lineHeight > 0 ? lineHeight : cancelWrap ? 0 : 6;
|
|
//todo: auto scrolling
|
|
_ph_mem->drawState.text_y = y + lineHeight;
|
|
|
|
return x;
|
|
}
|