Implement proof of concept for scripting capabilities

This commit is contained in:
Marcus Huderle 2020-04-26 19:38:20 -05:00
parent 37ab54019b
commit 267cd5e2cb
12 changed files with 154 additions and 25 deletions

View file

@ -8,6 +8,7 @@ class Block
public:
Block();
Block(uint16_t);
Block(uint16_t tile, uint16_t collision, uint16_t elevation);
Block(const Block&);
bool operator ==(Block);
bool operator !=(Block);

View file

@ -74,8 +74,7 @@ public:
void cacheBlockdata();
void cacheCollision();
Block *getBlock(int x, int y);
void setBlock(int x, int y, Block block);
void _setBlock(int x, int y, Block block);
void setBlock(int x, int y, Block block, bool invokeCallback = false);
void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation);
void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation);
void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation);

View file

@ -37,6 +37,8 @@ public:
MainWindow(const MainWindow &) = delete;
MainWindow & operator = (const MainWindow &) = delete;
Q_INVOKABLE void scriptapi_setBlock(int x, int y, int tile, int collision, int elevation);
public slots:
void scaleMapView(int);

31
include/scripting.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef SCRIPTING_H
#define SCRIPTING_H
#include "mainwindow.h"
#include "block.h"
#include <QStringList>
#include <QJSEngine>
enum CallbackType {
OnBlockChanged,
};
class Scripting
{
public:
Scripting(MainWindow *mainWindow);
QJSValue newBlockObject(Block block);
static void init(MainWindow *mainWindow);
static void cb_MetatileChanged(int x, int y, Block prevBlock, Block newBlock);
private:
QJSEngine *engine;
QStringList filepaths;
QList<QJSValue> modules;
void loadModules(QStringList moduleFiles);
void invokeCallback(CallbackType type, QJSValueList args);
};
#endif // SCRIPTING_H

View file

@ -4,7 +4,7 @@
#
#-------------------------------------------------
QT += core gui
QT += core gui qml
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
@ -70,6 +70,7 @@ SOURCES += src/core/block.cpp \
src/main.cpp \
src/mainwindow.cpp \
src/project.cpp \
src/scripting.cpp \
src/settings.cpp \
src/log.cpp \
src/ui/newtilesetdialog.cpp
@ -94,7 +95,7 @@ HEADERS += include/core/block.h \
include/core/wildmoninfo.h \
include/lib/orderedmap.h \
include/lib/orderedjson.h \
include/ui/aboutporymap.h \
include/ui/aboutporymap.h \
include/ui/bordermetatilespixmapitem.h \
include/ui/collisionpixmapitem.h \
include/ui/connectionpixmapitem.h \
@ -132,6 +133,7 @@ HEADERS += include/core/block.h \
include/editor.h \
include/mainwindow.h \
include/project.h \
include/scripting.h \
include/settings.h \
include/log.h \
include/ui/newtilesetdialog.h

View file

@ -3,6 +3,12 @@
Block::Block() : tile(0), collision(0), elevation(0) {
}
Block::Block(uint16_t tile, uint16_t collision, uint16_t elevation) {
this->tile = tile;
this->collision = collision;
this->elevation = elevation;
}
Block::Block(uint16_t word)
{
tile = word & 0x3ff;

View file

@ -2,6 +2,7 @@
#include "historyitem.h"
#include "map.h"
#include "imageproviders.h"
#include "scripting.h"
#include <QTime>
#include <QPainter>
@ -363,10 +364,14 @@ Block* Map::getBlock(int x, int y) {
return nullptr;
}
void Map::_setBlock(int x, int y, Block block) {
void Map::setBlock(int x, int y, Block block, bool invokeCallback) {
int i = y * getWidth() + x;
if (layout->blockdata && layout->blockdata->blocks) {
if (layout->blockdata && layout->blockdata->blocks && i < layout->blockdata->blocks->size()) {
Block prevBlock = layout->blockdata->blocks->value(i);
layout->blockdata->blocks->replace(i, block);
if (invokeCallback) {
Scripting::cb_MetatileChanged(x, y, prevBlock, block);
}
}
}
@ -390,7 +395,7 @@ void Map::_floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_
block->collision = collision;
block->elevation = elevation;
_setBlock(x, y, *block);
setBlock(x, y, *block);
if ((block = getBlock(x + 1, y)) && block->collision == old_coll && block->elevation == old_elev) {
todo.append(QPoint(x + 1, y));
}
@ -495,14 +500,6 @@ void Map::commit() {
}
}
void Map::setBlock(int x, int y, Block block) {
Block *old_block = getBlock(x, y);
if (old_block && (*old_block) != block) {
_setBlock(x, y, block);
commit();
}
}
void Map::floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation) {
Block *block = getBlock(x, y);
if (block && (block->collision != collision || block->elevation != elevation)) {
@ -523,7 +520,7 @@ void Map::magicFillCollisionElevation(int initialX, int initialY, uint16_t colli
if (block && block->collision == old_coll && block->elevation == old_elev) {
block->collision = collision;
block->elevation = elevation;
_setBlock(x, y, *block);
setBlock(x, y, *block);
}
}
}

View file

@ -8,6 +8,8 @@
#include "ui_eventpropertiesframe.h"
#include "bordermetatilespixmapitem.h"
#include "currentselectedmetatilespixmapitem.h"
#include "customattributestable.h"
#include "scripting.h"
#include <QFileDialog>
#include <QDirIterator>
@ -44,6 +46,7 @@ MainWindow::MainWindow(QWidget *parent) :
QCoreApplication::setApplicationName("porymap");
QApplication::setApplicationDisplayName("porymap");
QApplication::setWindowIcon(QIcon(":/icons/porymap-icon-2.ico"));
Scripting::init(this);
ui->setupUi(this);
this->initWindow();
@ -2540,3 +2543,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
QMainWindow::closeEvent(event);
}
void MainWindow::scriptapi_setBlock(int x, int y, int tile, int collision, int elevation) {
this->editor->map->setBlock(x, y, Block(tile, collision, elevation));
}

69
src/scripting.cpp Normal file
View file

@ -0,0 +1,69 @@
#include "scripting.h"
#include "log.h"
QMap<CallbackType, QString> callbackFunctions = {
{OnBlockChanged, "on_block_changed"},
};
Scripting *instance = nullptr;
void Scripting::init(MainWindow *mainWindow) {
instance = new Scripting(mainWindow);
}
Scripting::Scripting(MainWindow *mainWindow) {
this->engine = new QJSEngine(mainWindow);
this->engine->installExtensions(QJSEngine::ConsoleExtension);
this->engine->globalObject().setProperty("api", this->engine->newQObject(mainWindow));
this->filepaths.append("D:\\devkitProOld\\msys\\home\\huder\\pretmap\\test_script.js");
this->loadModules(this->filepaths);
}
void Scripting::loadModules(QStringList moduleFiles) {
for (QString filepath : moduleFiles) {
QJSValue module = this->engine->importModule(filepath);
if (module.isError()) {
logError(QString("Failed to load custom script file '%1'").arg(filepath));
continue;
}
this->modules.append(module);
}
}
void Scripting::invokeCallback(CallbackType type, QJSValueList args) {
for (QJSValue module : this->modules) {
QString functionName = callbackFunctions[type];
QJSValue callbackFunction = module.property(functionName);
if (callbackFunction.isError()) {
continue;
}
QJSValue result = callbackFunction.call(args);
if (result.isError()) {
logError(QString("Module %1 encountered an error when calling '%2'").arg(module.toString()).arg(functionName));
continue;
}
}
}
void Scripting::cb_MetatileChanged(int x, int y, Block prevBlock, Block newBlock) {
if (!instance) return;
QJSValueList args {
x,
y,
instance->newBlockObject(prevBlock),
instance->newBlockObject(newBlock),
};
instance->invokeCallback(OnBlockChanged, args);
}
QJSValue Scripting::newBlockObject(Block block) {
QJSValue obj = this->engine->newObject();
obj.setProperty("tile", block.tile);
obj.setProperty("collision", block.collision);
obj.setProperty("elevation", block.elevation);
obj.setProperty("rawValue", block.rawValue());
return obj;
}

View file

@ -39,7 +39,7 @@ void CollisionPixmapItem::paint(QGraphicsSceneMouseEvent *event) {
if (block) {
block->collision = this->movementPermissionsSelector->getSelectedCollision();
block->elevation = this->movementPermissionsSelector->getSelectedElevation();
map->_setBlock(x, y, *block);
map->setBlock(x, y, *block);
}
if (event->type() == QEvent::GraphicsSceneMouseRelease) {
map->commit();

View file

@ -56,7 +56,7 @@ void MapPixmapItem::shift(QGraphicsSceneMouseEvent *event) {
int blockIndex = j * map->getWidth() + i;
Block srcBlock = backupBlockdata->blocks->at(blockIndex);
map->_setBlock(destX, destY, srcBlock);
map->setBlock(destX, destY, srcBlock);
}
delete backupBlockdata;
@ -96,7 +96,7 @@ void MapPixmapItem::paintNormal(int x, int y) {
block->collision = selectedCollisions->at(index).first;
block->elevation = selectedCollisions->at(index).second;
}
map->_setBlock(actualX, actualY, *block);
map->setBlock(actualX, actualY, *block, true);
}
}
}
@ -159,7 +159,7 @@ void MapPixmapItem::paintSmartPath(int x, int y) {
block->collision = openTileCollision;
block->elevation = openTileElevation;
}
map->_setBlock(actualX, actualY, *block);
map->setBlock(actualX, actualY, *block);
}
}
@ -203,7 +203,7 @@ void MapPixmapItem::paintSmartPath(int x, int y) {
block->collision = selectedCollisions->at(smartPathTable[id]).first;
block->elevation = selectedCollisions->at(smartPathTable[id]).second;
}
map->_setBlock(actualX, actualY, *block);
map->setBlock(actualX, actualY, *block);
}
}
@ -312,7 +312,7 @@ void MapPixmapItem::magicFill(QGraphicsSceneMouseEvent *event) {
block->collision = selectedCollisions->at(index).first;
block->elevation = selectedCollisions->at(index).second;
}
map->_setBlock(x, y, *block);
map->setBlock(x, y, *block);
}
}
}
@ -362,7 +362,7 @@ void MapPixmapItem::_floodFill(int initialX, int initialY) {
block->collision = selectedCollisions->at(index).first;
block->elevation = selectedCollisions->at(index).second;
}
map->_setBlock(x, y, *block);
map->setBlock(x, y, *block);
}
if (!visited[x + 1 + y * map->getWidth()] && (block = map->getBlock(x + 1, y)) && block->tile == old_tile) {
todo.append(QPoint(x + 1, y));
@ -427,7 +427,7 @@ void MapPixmapItem::_floodFillSmartPath(int initialX, int initialY) {
block->collision = openTileCollision;
block->elevation = openTileElevation;
}
map->_setBlock(x, y, *block);
map->setBlock(x, y, *block);
if ((block = map->getBlock(x + 1, y)) && block->tile == old_tile) {
todo.append(QPoint(x + 1, y));
}
@ -482,7 +482,7 @@ void MapPixmapItem::_floodFillSmartPath(int initialX, int initialY) {
block->collision = selectedCollisions->at(smartPathTable[id]).first;
block->elevation = selectedCollisions->at(smartPathTable[id]).second;
}
map->_setBlock(x, y, *block);
map->setBlock(x, y, *block);
// Visit neighbors if they are smart-path tiles, and don't revisit any.
if (!visited[x + 1 + y * map->getWidth()] && (block = map->getBlock(x + 1, y)) && IS_SMART_PATH_TILE(block)) {

15
test_script.js Normal file
View file

@ -0,0 +1,15 @@
function randInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
const grassTiles = [0x8, 0x9, 0x10, 0x11];
// Porymap callback when a block is painted.
export function on_block_changed(x, y, prevBlock, newBlock) {
if (grassTiles.indexOf(newBlock.tile) != -1) {
const i = randInt(0, grassTiles.length);
api.scriptapi_setBlock(x, y, grassTiles[i], newBlock.collision, newBlock.elevation);
}
}