Compare commits
No commits in common. "old/sdl" and "main" have entirely different histories.
30
.eslintrc.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: [
|
||||||
|
'react-hooks',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'prettier/@typescript-eslint',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2018,
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'react/prop-types': [0],
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
'prettier/prettier': 'warn',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
*~
|
/node_modules
|
||||||
|
/.webpack
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 2.8.3)
|
|
||||||
project(RPGEDIT CXX)
|
|
||||||
|
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
|
||||||
pkg_check_modules(SDL2 REQUIRED sdl2 SDL2_image)
|
|
||||||
|
|
||||||
find_package(Lua 5.3 EXACT REQUIRED)
|
|
||||||
|
|
||||||
add_subdirectory(src)
|
|
15
build/webpack.main.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/main/index.ts',
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ts$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
},
|
||||||
|
};
|
30
build/webpack.renderer.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
const CopyPlugin = require('copy-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(woff|woff2|eot|ttf|otf)$/,
|
||||||
|
use: 'file-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(vs|fs)$/,
|
||||||
|
use: 'raw-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [new CopyPlugin([{ from: 'static' }])],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js', '.tsx', '.jsx'],
|
||||||
|
},
|
||||||
|
};
|
67
package.json
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": ".webpack/main",
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron-forge start",
|
||||||
|
"lint": "eslint 'src/**/*.ts' 'src/**/*.tsx'"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"forge": {
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"@electron-forge/plugin-webpack",
|
||||||
|
{
|
||||||
|
"mainConfig": "./build/webpack.main.js",
|
||||||
|
"renderer": {
|
||||||
|
"config": "./build/webpack.renderer.js",
|
||||||
|
"entryPoints": [
|
||||||
|
{
|
||||||
|
"html": "./src/renderer/index.html",
|
||||||
|
"js": "./src/renderer/index.tsx",
|
||||||
|
"name": "main_window"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@electron-forge/cli": "^6.0.0-beta.50",
|
||||||
|
"@electron-forge/plugin-webpack": "^6.0.0-beta.50",
|
||||||
|
"@types/color": "^3.0.1",
|
||||||
|
"@types/electron-devtools-installer": "^2.2.0",
|
||||||
|
"@types/react": "^16.9.25",
|
||||||
|
"@types/react-dom": "^16.9.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
||||||
|
"@typescript-eslint/parser": "^2.24.0",
|
||||||
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
|
"css-loader": "^3.4.2",
|
||||||
|
"electron": "^8.1.1",
|
||||||
|
"electron-devtools-installer": "^2.2.4",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-config-prettier": "^6.10.0",
|
||||||
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
|
"eslint-plugin-react": "^7.19.0",
|
||||||
|
"eslint-plugin-react-hooks": "^2.5.1",
|
||||||
|
"file-loader": "^6.0.0",
|
||||||
|
"prettier": "^2.0.1",
|
||||||
|
"raw-loader": "^4.0.0",
|
||||||
|
"style-loader": "^1.1.3",
|
||||||
|
"ts-loader": "^6.2.1",
|
||||||
|
"typescript": "^3.8.3",
|
||||||
|
"webpack": "^4.42.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@material-ui/core": "^4.9.7",
|
||||||
|
"color": "^3.1.2",
|
||||||
|
"fast-glob": "^3.2.2",
|
||||||
|
"gl-matrix": "^3.2.1",
|
||||||
|
"immutable": "^4.0.0-rc.12",
|
||||||
|
"react": "^16.13.1",
|
||||||
|
"react-dom": "^16.13.1",
|
||||||
|
"typeface-roboto": "^0.0.75"
|
||||||
|
}
|
||||||
|
}
|
10
prettier.config.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module.exports = {
|
||||||
|
semi: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
arrowParens: 'always',
|
||||||
|
singleQuote: true,
|
||||||
|
jsxSingleQuote: true,
|
||||||
|
printWidth: 120,
|
||||||
|
useTabs: true,
|
||||||
|
tabWidth: 8,
|
||||||
|
};
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
5
project/tiling/dirt/meta.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Dirt",
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
}
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
5
project/tiling/grass/meta.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Grass",
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
}
|
|
@ -1,104 +0,0 @@
|
||||||
32 32 2
|
|
||||||
|
|
||||||
G 0 grass
|
|
||||||
< 0 road_left
|
|
||||||
> 0 road_right
|
|
||||||
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
11111111111111111111111111111111
|
|
||||||
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
GGGGGGGGGGG<>GGGGGGGGGGGGGGGGGGG
|
|
||||||
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
||||||
................................
|
|
|
@ -1,3 +0,0 @@
|
||||||
function interact(entity, time)
|
|
||||||
print('Interact! ' .. time)
|
|
||||||
end
|
|
|
@ -1,20 +0,0 @@
|
||||||
local function print_table(foo, bar)
|
|
||||||
for k, v in pairs(foo) do
|
|
||||||
print(bar .. k, v)
|
|
||||||
|
|
||||||
--if type(v) == 'table' and bar .. k ~= '_G' then
|
|
||||||
-- print_table(v, bar .. k .. '.')
|
|
||||||
--end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--print(getmetatable(_G))
|
|
||||||
--setmetatable(_G, {})
|
|
||||||
--print_table(_G, '')
|
|
||||||
|
|
||||||
print(bar)
|
|
||||||
print(getmetatable(bar))
|
|
||||||
|
|
||||||
--print(getmetatable('').bar)
|
|
||||||
--getmetatable('').bar = 'bar'
|
|
||||||
--print(getmetatable('').bar)
|
|
Before Width: | Height: | Size: 808 B |
Before Width: | Height: | Size: 118 B |
Before Width: | Height: | Size: 245 B |
Before Width: | Height: | Size: 242 B |
|
@ -1,16 +0,0 @@
|
||||||
include_directories(${SDL2_INCLUDE_DIRS} ${LUA_INCLUDE_DIR})
|
|
||||||
link_directories(${SDL2_LIBRARY_DIRS})
|
|
||||||
|
|
||||||
add_executable(rpgedit
|
|
||||||
rpgedit.cpp
|
|
||||||
control/MapContext.cpp
|
|
||||||
control/RPGEdit.cpp
|
|
||||||
control/ScriptContext.cpp
|
|
||||||
model/Map.cpp
|
|
||||||
model/Scriptable.cpp
|
|
||||||
view/MapView.cpp
|
|
||||||
view/SpriteCache.cpp
|
|
||||||
)
|
|
||||||
set_target_properties(rpgedit PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall ${SDL2_CFLAGS_OTHER}")
|
|
||||||
set_target_properties(rpgedit PROPERTIES LINK_FLAGS "${SDL2_LDFLAGS_OTHER}")
|
|
||||||
target_link_libraries(rpgedit ${SDL2_LIBRARIES} ${LUA_LIBRARIES})
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
#include "TimeProvider.hpp"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <deque>
|
|
||||||
#include <functional>
|
|
||||||
#include <limits>
|
|
||||||
#include <queue>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
class EventBus {
|
|
||||||
public:
|
|
||||||
typedef std::function<void ()> Event;
|
|
||||||
typedef std::pair<uint64_t, Event> EventEntry;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static bool compare_events(const EventEntry &e1, const EventEntry &e2) {
|
|
||||||
return e1.first > e2.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::priority_queue<EventEntry, std::deque<EventEntry>, bool (*)(const EventEntry &, const EventEntry &)> events;
|
|
||||||
|
|
||||||
std::mutex mutex;
|
|
||||||
std::condition_variable cond;
|
|
||||||
|
|
||||||
public:
|
|
||||||
EventBus() : events(compare_events) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void enqueue(const Event &event, uint64_t time) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
|
||||||
events.push(EventEntry(time, event));
|
|
||||||
cond.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
EventEntry get(TimeProvider *timeProvider) {
|
|
||||||
std::unique_lock<std::mutex> lock(mutex);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (events.empty()) {
|
|
||||||
cond.wait(lock);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventEntry top = events.top();
|
|
||||||
uint64_t time = timeProvider->now();
|
|
||||||
|
|
||||||
if (top.first > time) {
|
|
||||||
cond.wait_for(lock, std::chrono::milliseconds(top.first - time));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
events.pop();
|
|
||||||
return top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t peek() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
|
||||||
|
|
||||||
if (events.empty())
|
|
||||||
return std::numeric_limits<uint64_t>::max();
|
|
||||||
|
|
||||||
return events.top().first;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <functional>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
class InputHandler {
|
|
||||||
public:
|
|
||||||
typedef std::function<void (uint16_t, bool, uint64_t)> Listener;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<Listener> listeners;
|
|
||||||
|
|
||||||
std::unordered_set<uint16_t> pressedKeys;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void registerListener(const Listener &listener) {
|
|
||||||
listeners.push_back(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
void keyPressed(uint16_t key, uint64_t time) {
|
|
||||||
if (pressedKeys.insert(key).second) {
|
|
||||||
for (auto &listener : listeners)
|
|
||||||
listener(key, true, time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void keyReleased(uint16_t key, uint64_t time) {
|
|
||||||
if (pressedKeys.erase(key)) {
|
|
||||||
for (auto &listener : listeners)
|
|
||||||
listener(key, false, time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isKeyPressed(uint16_t key) {
|
|
||||||
return pressedKeys.count(key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "MapContext.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
MapContext::MapContext(EventBus *eventBus0, InputHandler *inputHandler0, ScriptContext *scriptContext0, const std::shared_ptr<View::Window> &window, const Model::Map &map0)
|
|
||||||
: eventBus(eventBus0), inputHandler(inputHandler0), scriptContext(scriptContext0), map(map0) {
|
|
||||||
view = std::unique_ptr<View::MapView>(new View::MapView(window, map.getTileset()));
|
|
||||||
|
|
||||||
Model::Entity *square = map.addEntity("square", Model::Position<int>{10, 10});
|
|
||||||
square->setScriptInteract("interact", "interact");
|
|
||||||
|
|
||||||
playerEntity = map.addEntity("square", Model::Position<int>{8, 8});
|
|
||||||
|
|
||||||
view->updateEntities(map.getEntities());
|
|
||||||
|
|
||||||
inputHandler->registerListener(
|
|
||||||
[this] (uint16_t key, bool pressed, uint64_t time) {
|
|
||||||
if (pressed)
|
|
||||||
keyPressed(key, time);
|
|
||||||
else
|
|
||||||
movePlayerContinue(time);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapContext::movePlayer(Model::Direction dir, uint64_t time) {
|
|
||||||
if (!map.moveEntity(playerEntity, dir, time, time+250))
|
|
||||||
return;
|
|
||||||
|
|
||||||
eventBus->enqueue(
|
|
||||||
[=] {
|
|
||||||
map.finishEntityTransition(playerEntity);
|
|
||||||
movePlayerContinue(time+250);
|
|
||||||
},
|
|
||||||
time+250
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapContext::movePlayerContinue(uint64_t time) {
|
|
||||||
if (inputHandler->isKeyPressed(SDL_SCANCODE_UP))
|
|
||||||
movePlayer(Model::Direction::NORTH, time);
|
|
||||||
else if (inputHandler->isKeyPressed(SDL_SCANCODE_RIGHT))
|
|
||||||
movePlayer(Model::Direction::EAST, time);
|
|
||||||
else if (inputHandler->isKeyPressed(SDL_SCANCODE_DOWN))
|
|
||||||
movePlayer(Model::Direction::SOUTH, time);
|
|
||||||
else if (inputHandler->isKeyPressed(SDL_SCANCODE_LEFT))
|
|
||||||
movePlayer(Model::Direction::WEST, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapContext::interact(uint64_t time) {
|
|
||||||
Model::Position<int> p = map.getEntityPosition(playerEntity) + playerEntity->getDirection();
|
|
||||||
Model::Entity *target = map.getEntityAt(p);
|
|
||||||
|
|
||||||
if (!target)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const std::pair<std::string, std::string> &interactScript = target->getScriptInteract();
|
|
||||||
if (interactScript.first.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
scriptContext->run(interactScript.first, interactScript.second, nullptr, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapContext::keyPressed(uint16_t key, uint64_t time) {
|
|
||||||
switch (key) {
|
|
||||||
case SDL_SCANCODE_UP:
|
|
||||||
movePlayer(Model::Direction::NORTH, time);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_RIGHT:
|
|
||||||
movePlayer(Model::Direction::EAST, time);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_DOWN:
|
|
||||||
movePlayer(Model::Direction::SOUTH, time);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_LEFT:
|
|
||||||
movePlayer(Model::Direction::WEST, time);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_SPACE:
|
|
||||||
interact(time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "EventBus.hpp"
|
|
||||||
#include "InputHandler.hpp"
|
|
||||||
#include "ScriptContext.hpp"
|
|
||||||
#include "../model/Map.hpp"
|
|
||||||
#include "../view/MapView.hpp"
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
class MapContext {
|
|
||||||
private:
|
|
||||||
EventBus *const eventBus;
|
|
||||||
InputHandler *const inputHandler;
|
|
||||||
ScriptContext *const scriptContext;
|
|
||||||
|
|
||||||
std::unique_ptr<View::MapView> view;
|
|
||||||
|
|
||||||
Model::Map map;
|
|
||||||
Model::Entity *playerEntity;
|
|
||||||
|
|
||||||
void movePlayer(Model::Direction dir, uint64_t time);
|
|
||||||
void movePlayerContinue(uint64_t time);
|
|
||||||
void keyPressed(uint16_t key, uint64_t time);
|
|
||||||
void interact(uint64_t time);
|
|
||||||
|
|
||||||
Model::Position<float> getViewPosition(uint64_t time) {
|
|
||||||
return map.getEntityPosition(playerEntity, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
MapContext(EventBus *eventBus0, InputHandler *inputHandler0, ScriptContext *scriptContext0, const std::shared_ptr<View::Window> &window, const Model::Map &map0);
|
|
||||||
|
|
||||||
void render(uint64_t time) {
|
|
||||||
view->render(&map, getViewPosition(time), time);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "RPGEdit.hpp"
|
|
||||||
#include "MapContext.hpp"
|
|
||||||
#include "../view/MapView.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
bool RPGEdit::handleSystemEvent(const SDL_Event &event) {
|
|
||||||
uint64_t time = timeProvider.now();
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_KEYDOWN:
|
|
||||||
eventBus.enqueue([=] { inputHandler.keyPressed(event.key.keysym.scancode, time); }, time);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_KEYUP:
|
|
||||||
eventBus.enqueue([=] { inputHandler.keyReleased(event.key.keysym.scancode, time); }, time);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SDL_QUIT:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RPGEdit::systemLoop() {
|
|
||||||
const int MIN_FRAME_DELAY = 10;
|
|
||||||
|
|
||||||
uint32_t lastFrameTicks = SDL_GetTicks();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
uint32_t diff = SDL_GetTicks() - lastFrameTicks;
|
|
||||||
int timeout = std::max(MIN_FRAME_DELAY - int(diff), 0);
|
|
||||||
|
|
||||||
SDL_Event event;
|
|
||||||
if (SDL_WaitEventTimeout(&event, timeout)) {
|
|
||||||
if (!handleSystemEvent(event))
|
|
||||||
return;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastFrameTicks = SDL_GetTicks();
|
|
||||||
|
|
||||||
SDL_RenderClear(window->getRenderer());
|
|
||||||
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(modelMutex);
|
|
||||||
|
|
||||||
ctx->render(std::min(timeProvider.now(), handledTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_RenderPresent(window->getRenderer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RPGEdit::eventLoop() {
|
|
||||||
while (true) {
|
|
||||||
EventBus::EventEntry event = eventBus.get(&timeProvider);
|
|
||||||
|
|
||||||
if (!event.second)
|
|
||||||
return;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(modelMutex);
|
|
||||||
|
|
||||||
event.second();
|
|
||||||
|
|
||||||
handledTime = eventBus.peek();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RPGEdit::run() {
|
|
||||||
std::unique_ptr<Model::Map> map = Model::Map::load("test");
|
|
||||||
|
|
||||||
window = std::make_shared<View::Window>();
|
|
||||||
|
|
||||||
ctx = std::make_shared<MapContext>(&eventBus, &inputHandler, &scriptContext, window, *map);
|
|
||||||
|
|
||||||
eventThread = std::thread([this] { eventLoop(); });
|
|
||||||
|
|
||||||
systemLoop();
|
|
||||||
|
|
||||||
eventBus.enqueue(EventBus::Event(), timeProvider.now());
|
|
||||||
eventThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "EventBus.hpp"
|
|
||||||
#include "InputHandler.hpp"
|
|
||||||
#include "MapContext.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
class RPGEdit {
|
|
||||||
private:
|
|
||||||
TimeProvider timeProvider;
|
|
||||||
|
|
||||||
EventBus eventBus;
|
|
||||||
InputHandler inputHandler;
|
|
||||||
|
|
||||||
ScriptContext scriptContext;
|
|
||||||
|
|
||||||
std::shared_ptr<View::Window> window;
|
|
||||||
|
|
||||||
std::shared_ptr<MapContext> ctx;
|
|
||||||
|
|
||||||
std::thread eventThread;
|
|
||||||
std::mutex modelMutex;
|
|
||||||
uint64_t handledTime = std::numeric_limits<uint64_t>::max();
|
|
||||||
|
|
||||||
bool handleSystemEvent(const SDL_Event &event);
|
|
||||||
void systemLoop();
|
|
||||||
|
|
||||||
void eventLoop();
|
|
||||||
|
|
||||||
public:
|
|
||||||
void run();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "ScriptContext.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
void ScriptContext::setupEnv() {
|
|
||||||
const std::pair<const char *, lua_CFunction> libs[] = {
|
|
||||||
{"_G", luaopen_base},
|
|
||||||
{"math", luaopen_math},
|
|
||||||
{"string", luaopen_string},
|
|
||||||
{"table", luaopen_table},
|
|
||||||
{"utf8", luaopen_utf8},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (auto &lib : libs) {
|
|
||||||
lua_pushcfunction(L, lib.second);
|
|
||||||
lua_pushstring(L, lib.first);
|
|
||||||
lua_call(L, 1, 1);
|
|
||||||
lua_setglobal(L, lib.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const char *f : {"dofile", "loadfile", "require"}) {
|
|
||||||
lua_pushnil(L);
|
|
||||||
lua_setglobal(L, f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptContext::cleanupEnv() {
|
|
||||||
lua_pushglobaltable(L);
|
|
||||||
|
|
||||||
lua_pushnil(L);
|
|
||||||
lua_setmetatable(L, -2);
|
|
||||||
|
|
||||||
lua_pushnil(L);
|
|
||||||
while (lua_next(L, -2) != 0) {
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
lua_pushvalue(L, -1);
|
|
||||||
lua_pushnil(L);
|
|
||||||
|
|
||||||
lua_rawset(L, -4);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptContext::load(const std::string &script) {
|
|
||||||
if (loadedScripts.count(script))
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string filename = "../resources/script/" + script + ".lua";
|
|
||||||
|
|
||||||
lua_rawgetp(L, LUA_REGISTRYINDEX, this);
|
|
||||||
lua_pushstring(L, script.c_str());
|
|
||||||
luaL_loadfile(L, filename.c_str());
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
loadedScripts.insert(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../model/ScriptValue.hpp"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <lua.h>
|
|
||||||
#include <lualib.h>
|
|
||||||
#include <lauxlib.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
class ScriptContext {
|
|
||||||
private:
|
|
||||||
lua_State *L;
|
|
||||||
|
|
||||||
std::unordered_set<std::string> loadedScripts;
|
|
||||||
|
|
||||||
void setupEnv();
|
|
||||||
void cleanupEnv();
|
|
||||||
|
|
||||||
void load(const std::string &script);
|
|
||||||
|
|
||||||
|
|
||||||
void pushArg(std::nullptr_t __attribute__((unused)) v) {
|
|
||||||
lua_pushnil(L);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pushArg(const std::string &v) {
|
|
||||||
lua_pushstring(L, v.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, class = typename std::enable_if<std::is_arithmetic<T>::value>::type>
|
|
||||||
void pushArg(T v) {
|
|
||||||
lua_pushnumber(L, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pushArg(bool v) {
|
|
||||||
lua_pushboolean(L, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pushArg(Model::ScriptValue &v) {
|
|
||||||
v.push(L);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t pushArgs() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename... Args>
|
|
||||||
size_t pushArgs(T v, Args ...args) {
|
|
||||||
pushArg(v);
|
|
||||||
return pushArgs(args...) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
ScriptContext(const ScriptContext &other) = delete;
|
|
||||||
ScriptContext(ScriptContext &&other) = delete;
|
|
||||||
ScriptContext & operator=(const ScriptContext &other) = delete;
|
|
||||||
ScriptContext & operator=(ScriptContext &&other) = delete;
|
|
||||||
|
|
||||||
ScriptContext() {
|
|
||||||
L = luaL_newstate();
|
|
||||||
|
|
||||||
lua_newtable(L);
|
|
||||||
lua_rawsetp(L, LUA_REGISTRYINDEX, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
~ScriptContext() {
|
|
||||||
lua_close(L);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setGlobal(const std::string &key, Model::ScriptValue *value) {
|
|
||||||
value->push(L);
|
|
||||||
lua_setglobal(L, key.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
void run(const std::string &script, const std::string &name, Args ...args) {
|
|
||||||
load(script);
|
|
||||||
|
|
||||||
setupEnv();
|
|
||||||
|
|
||||||
lua_rawgetp(L, LUA_REGISTRYINDEX, this);
|
|
||||||
lua_getfield(L, -1, script.c_str());
|
|
||||||
lua_remove(L, -2);
|
|
||||||
|
|
||||||
lua_call(L, 0, 0);
|
|
||||||
|
|
||||||
lua_getglobal(L, name.c_str());
|
|
||||||
lua_call(L, pushArgs(args...), 0);
|
|
||||||
|
|
||||||
cleanupEnv();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Control {
|
|
||||||
|
|
||||||
class TimeProvider {
|
|
||||||
private:
|
|
||||||
uint64_t time;
|
|
||||||
std::mutex mutex;
|
|
||||||
|
|
||||||
public:
|
|
||||||
TimeProvider() {
|
|
||||||
time = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t now() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex);
|
|
||||||
|
|
||||||
uint32_t ticks = SDL_GetTicks() - uint32_t(time);
|
|
||||||
time += ticks;
|
|
||||||
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
55
src/main/index.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { app, BrowserWindow } from 'electron';
|
||||||
|
|
||||||
|
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
|
||||||
|
|
||||||
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
app.allowRendererProcessReuse = true;
|
||||||
|
|
||||||
|
async function installReactDevTools(): Promise<void> {
|
||||||
|
const { default: installExtension, REACT_DEVELOPER_TOOLS } = await import('electron-devtools-installer');
|
||||||
|
const name = await installExtension(REACT_DEVELOPER_TOOLS);
|
||||||
|
console.log(`Added Extension: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow(): void {
|
||||||
|
const window = new BrowserWindow({ webPreferences: { nodeIntegration: true } });
|
||||||
|
|
||||||
|
window.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
|
||||||
|
|
||||||
|
if (isDevelopment) {
|
||||||
|
window.webContents.openDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.webContents.on('devtools-opened', () => {
|
||||||
|
window.webContents.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// quit application when all windows are closed
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
// On macOS it is common for applications and their menu bar
|
||||||
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function initialize(): Promise<void> {
|
||||||
|
if (isDevelopment) {
|
||||||
|
await installReactDevTools();
|
||||||
|
}
|
||||||
|
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create main BrowserWindow when electron is ready
|
||||||
|
app.on('ready', initialize);
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
enum class CollisionType {
|
|
||||||
BLOCKED = 0,
|
|
||||||
EMPTY,
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
enum class Direction {
|
|
||||||
NORTH,
|
|
||||||
EAST,
|
|
||||||
SOUTH,
|
|
||||||
WEST
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline Direction operator-(Direction dir) {
|
|
||||||
switch (dir) {
|
|
||||||
case Direction::NORTH:
|
|
||||||
return Direction::SOUTH;
|
|
||||||
|
|
||||||
case Direction::EAST:
|
|
||||||
return Direction::WEST;
|
|
||||||
|
|
||||||
case Direction::SOUTH:
|
|
||||||
return Direction::NORTH;
|
|
||||||
|
|
||||||
case Direction::WEST:
|
|
||||||
return Direction::EAST;
|
|
||||||
|
|
||||||
default:
|
|
||||||
__builtin_unreachable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
#include "Direction.hpp"
|
|
||||||
#include "Scriptable.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
class Entity : public Scriptable {
|
|
||||||
private:
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
std::pair<std::string, std::string> scriptInteract;
|
|
||||||
|
|
||||||
Direction direction;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Entity(const std::string &name0)
|
|
||||||
: name(name0), direction(Direction::NORTH) {
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string & getName() const {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
Direction getDirection() const {
|
|
||||||
return direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDirection(Direction dir) {
|
|
||||||
direction = dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::pair<std::string, std::string> & getScriptInteract() const {
|
|
||||||
return scriptInteract;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setScriptInteract(const std::string &script, const std::string &name) {
|
|
||||||
scriptInteract.first = script;
|
|
||||||
scriptInteract.second = name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "Map.hpp"
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
Position<float> Map::getEntityPosition(const Entity *entity, uint64_t time) const {
|
|
||||||
const EntityState &state = entityStates.at(entity);
|
|
||||||
|
|
||||||
Position<float> p(state.position);
|
|
||||||
|
|
||||||
if (state.transitionEnd)
|
|
||||||
if (time <= state.transitionStart)
|
|
||||||
return p;
|
|
||||||
else if (time >= state.transitionEnd)
|
|
||||||
return p + state.direction;
|
|
||||||
else
|
|
||||||
return p.translate(state.direction,
|
|
||||||
float(time-state.transitionStart)/
|
|
||||||
float(state.transitionEnd-state.transitionStart));
|
|
||||||
else
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Map::moveEntity(Entity *entity, Direction dir, uint64_t start, uint64_t end) {
|
|
||||||
EntityState &state = entityStates.at(entity);
|
|
||||||
|
|
||||||
if (state.transitionEnd)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
entity->setDirection(dir);
|
|
||||||
|
|
||||||
if (isBlocked(state.position + dir))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
state.transitionStart = start;
|
|
||||||
state.transitionEnd = end;
|
|
||||||
state.direction = dir;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Map::moveEntityTo(Entity *entity, Position<int> pos) {
|
|
||||||
EntityState &state = entityStates.at(entity);
|
|
||||||
|
|
||||||
removeEntityPosition(state.position, entity);
|
|
||||||
addEntityPosition(pos, entity);
|
|
||||||
|
|
||||||
state.position = pos;
|
|
||||||
state.transitionStart = state.transitionEnd = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Map::finishEntityTransition(Entity *entity) {
|
|
||||||
EntityState &state = entityStates.at(entity);
|
|
||||||
|
|
||||||
if (state.transitionEnd)
|
|
||||||
moveEntityTo(entity, state.position + state.direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Map> Map::load(const std::string &name) {
|
|
||||||
std::string filename = "../resources/map/" + name + ".map";
|
|
||||||
|
|
||||||
std::ifstream file;
|
|
||||||
|
|
||||||
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
|
|
||||||
file.open(filename.c_str());
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
|
|
||||||
std::getline(file, line);
|
|
||||||
size_t w, h, layers;
|
|
||||||
if (std::sscanf(line.c_str(), "%zu %zu %zu", &w, &h, &layers) != 3)
|
|
||||||
throw std::invalid_argument("invalid map file");
|
|
||||||
|
|
||||||
std::unique_ptr<Map> map(new Map(w, h, layers));
|
|
||||||
|
|
||||||
std::getline(file, line);
|
|
||||||
if (line.length())
|
|
||||||
throw std::invalid_argument("invalid map file");
|
|
||||||
|
|
||||||
size_t n = 1;
|
|
||||||
std::unordered_map<char, size_t> tileset;
|
|
||||||
|
|
||||||
for (std::getline(file, line); line.length(); std::getline(file, line)) {
|
|
||||||
char c;
|
|
||||||
unsigned rot;
|
|
||||||
char *tile = nullptr;
|
|
||||||
|
|
||||||
if (std::sscanf(line.c_str(), "%c %u %ms", &c, &rot, &tile) != 3 || rot >= 4) {
|
|
||||||
std::free(tile);
|
|
||||||
throw std::invalid_argument("invalid map file");
|
|
||||||
}
|
|
||||||
|
|
||||||
tileset.emplace(c, n++);
|
|
||||||
map->tileset.emplace_back(tile, rot);
|
|
||||||
std::free(tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < h; i++) {
|
|
||||||
std::getline(file, line);
|
|
||||||
if (line.length() != w)
|
|
||||||
throw std::invalid_argument("invalid map file");
|
|
||||||
|
|
||||||
for (size_t j = 0; j < w; j++) {
|
|
||||||
if (line[j] == '1')
|
|
||||||
map->setCollisionAt(Position<int>{int(j), int(i)}, CollisionType::EMPTY);
|
|
||||||
else if (line[j] != '0')
|
|
||||||
throw std::invalid_argument("invalid map file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t layer = 0; layer < layers; layer++) {
|
|
||||||
std::getline(file, line);
|
|
||||||
if (line.length())
|
|
||||||
throw std::invalid_argument("invalid map file");
|
|
||||||
|
|
||||||
for (size_t i = 0; i < h; i++) {
|
|
||||||
std::getline(file, line);
|
|
||||||
if (line.length() != w)
|
|
||||||
throw std::invalid_argument("invalid map file");
|
|
||||||
|
|
||||||
for (size_t j = 0; j < w; j++) {
|
|
||||||
auto it = tileset.find(line[j]);
|
|
||||||
if (it != tileset.end())
|
|
||||||
map->setTileAt(layer, Position<int>{int(j), int(i)}, it->second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,251 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "CollisionType.hpp"
|
|
||||||
#include "Entity.hpp"
|
|
||||||
#include "Position.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
class _Map {
|
|
||||||
protected:
|
|
||||||
std::vector<std::pair<std::string, int>> tileset;
|
|
||||||
|
|
||||||
size_t width, height;
|
|
||||||
std::vector<CollisionType> collision;
|
|
||||||
std::vector<std::vector<uint32_t>> tiles;
|
|
||||||
|
|
||||||
_Map(size_t width0, size_t height0, size_t layers)
|
|
||||||
: width(width0), height(height0), collision(width*height) {
|
|
||||||
for (size_t i = 0; i < layers; i++)
|
|
||||||
tiles.emplace_back(width*height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Map : private _Map {
|
|
||||||
private:
|
|
||||||
struct EntityState {
|
|
||||||
Position<int> position;
|
|
||||||
|
|
||||||
Direction direction;
|
|
||||||
uint64_t transitionStart = 0;
|
|
||||||
uint64_t transitionEnd = 0;
|
|
||||||
|
|
||||||
EntityState(const Position<int> &position0) : position(position0) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Entity>> entities;
|
|
||||||
std::unordered_map<Position<int>, std::unordered_set<Entity *>> positions;
|
|
||||||
std::unordered_map<const Entity *, EntityState> entityStates;
|
|
||||||
|
|
||||||
|
|
||||||
void addEntityPosition(const Position<int> &position, Entity *entity) {
|
|
||||||
positions[position].insert(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeEntityPosition(const Position<int> &position, Entity *entity) {
|
|
||||||
auto it = positions.find(position);
|
|
||||||
it->second.erase(entity);
|
|
||||||
|
|
||||||
if (it->second.empty())
|
|
||||||
positions.erase(it);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void pushEntity(Entity *entity, EntityState &&state) {
|
|
||||||
entities.push_back(std::unique_ptr<Entity>(entity));
|
|
||||||
addEntityPosition(state.position, entity);
|
|
||||||
entityStates.emplace(entity, std::move(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
void copyEntities(const Map &other) {
|
|
||||||
for (auto &e : other.entities)
|
|
||||||
pushEntity(new Entity(*e), EntityState(other.entityStates.at(e.get())));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasTransitionTo(const Position<int> &p, Direction dir) const {
|
|
||||||
auto it = positions.find(p+dir);
|
|
||||||
if (it == positions.end())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (const Entity *entity : it->second) {
|
|
||||||
const EntityState &state = entityStates.at(entity);
|
|
||||||
|
|
||||||
if (state.transitionEnd && (state.direction == -dir))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasTransitionTo(const Position<int> &p) const {
|
|
||||||
if (hasTransitionTo(p, Direction::NORTH))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (hasTransitionTo(p, Direction::EAST))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (hasTransitionTo(p, Direction::SOUTH))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (hasTransitionTo(p, Direction::WEST))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Map(size_t width0, size_t height0, size_t layers) : _Map(width0, height0, layers) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Map & operator=(const Map &other) {
|
|
||||||
static_cast<_Map>(*this) = other;
|
|
||||||
copyEntities(other);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map(const Map &other) : _Map(other) {
|
|
||||||
copyEntities(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map & operator=(Map &&other) {
|
|
||||||
static_cast<_Map>(*this) = std::move(other);
|
|
||||||
|
|
||||||
entities = std::move(other.entities);
|
|
||||||
positions = std::move(other.positions);
|
|
||||||
entityStates = std::move(other.entityStates);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map(Map &&other) : _Map(std::move(other)) {
|
|
||||||
entities = std::move(other.entities);
|
|
||||||
positions = std::move(other.positions);
|
|
||||||
entityStates = std::move(other.entityStates);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<std::string, int>> & getTileset() {
|
|
||||||
return tileset;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<std::pair<std::string, int>> & getTileset() const {
|
|
||||||
return tileset;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<std::unique_ptr<Entity>> & getEntities() const {
|
|
||||||
return entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity * addEntity(const std::string &name, const Position<int> &pos) {
|
|
||||||
Entity *e = new Entity(name);
|
|
||||||
pushEntity(e, EntityState(pos));
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t getWidth() const {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t getHeight() const {
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t getLayerCount() const {
|
|
||||||
return tiles.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCollisionAt(const Position<int> &p, CollisionType value) {
|
|
||||||
if (p.x < 0 || size_t(p.x) >= width || p.y < 0 || size_t(p.y) >= height)
|
|
||||||
throw std::range_error("Map::setCollisionAt: bad coordinates");
|
|
||||||
|
|
||||||
collision[p.y*width + p.x] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isBlocked(const Position<int> &p) const {
|
|
||||||
if (p.x < 0 || size_t(p.x) >= width || p.y < 0 || size_t(p.y) >= height)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (positions.find(p) != positions.end())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (hasTransitionTo(p))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return collision[p.y*width + p.x] == CollisionType::BLOCKED;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void setTileAt(size_t layer, const Position<int> &p, uint32_t value) {
|
|
||||||
if (layer >= tiles.size() || p.x < 0 || size_t(p.x) >= width || p.y < 0 || size_t(p.y) >= height)
|
|
||||||
throw std::range_error("Map::setTileAt: bad coordinates");
|
|
||||||
|
|
||||||
tiles[layer][p.y*width + p.x] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getTileAt(size_t layer, const Position<int> &p) const {
|
|
||||||
if (layer >= tiles.size() || p.x < 0 || size_t(p.x) >= width || p.y < 0 || size_t(p.y) >= height)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return tiles[layer][p.y*width + p.x];
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity * getEntityAt(const Position<int> &p) {
|
|
||||||
auto it = positions.find(p);
|
|
||||||
if (it == positions.end())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return *it->second.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
Position<int> getEntityPosition(const Entity *entity) const {
|
|
||||||
return entityStates.at(entity).position;
|
|
||||||
}
|
|
||||||
|
|
||||||
Position<float> getEntityPosition(const Entity *entity, uint64_t time) const;
|
|
||||||
bool moveEntity(Entity *entity, Direction dir, uint64_t start, uint64_t end);
|
|
||||||
void moveEntityTo(Entity *entity, Position<int> pos);
|
|
||||||
void finishEntityTransition(Entity *entity);
|
|
||||||
|
|
||||||
static std::unique_ptr<Map> load(const std::string &name);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Direction.hpp"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct Position {
|
|
||||||
T x, y;
|
|
||||||
|
|
||||||
Position translate(Direction dir, T amount) const {
|
|
||||||
Position p = *this;
|
|
||||||
|
|
||||||
switch (dir) {
|
|
||||||
case Direction::NORTH:
|
|
||||||
p.y -= amount;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Direction::EAST:
|
|
||||||
p.x += amount;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Direction::SOUTH:
|
|
||||||
p.y += amount;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Direction::WEST:
|
|
||||||
p.x -= amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
Position<T> operator+(Direction dir) const {
|
|
||||||
return translate(dir, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Position<T> &p) const {
|
|
||||||
return (x == p.x) && (y == p.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T2>
|
|
||||||
explicit operator Position<T2>() const {
|
|
||||||
return Position<T2>{T2(x), T2(y)};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
template<typename T> struct hash<RPGEdit::Model::Position<T>> {
|
|
||||||
typedef size_t result_type;
|
|
||||||
typedef RPGEdit::Model::Position<T> argument_type;
|
|
||||||
|
|
||||||
size_t operator()(const RPGEdit::Model::Position<T> &pos) const noexcept {
|
|
||||||
const int shift = 4*sizeof(size_t); // half the bit count of size_t
|
|
||||||
|
|
||||||
std::hash<T> hash;
|
|
||||||
size_t hash1 = hash(pos.x), hash2 = hash(pos.y);
|
|
||||||
|
|
||||||
return hash1 ^ ((hash2 << shift) | (hash2 >> shift));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <lua.h>
|
|
||||||
#include <lualib.h>
|
|
||||||
#include <lauxlib.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
class ScriptValue {
|
|
||||||
public:
|
|
||||||
virtual void push(lua_State *L) = 0;
|
|
||||||
|
|
||||||
virtual ~ScriptValue() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class ScriptBoolean : public ScriptValue {
|
|
||||||
private:
|
|
||||||
bool value;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ScriptBoolean(bool value0) : value(value0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptBoolean & operator=(bool newValue) {
|
|
||||||
value = newValue;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator bool() const {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void push(lua_State *L) {
|
|
||||||
lua_pushboolean(L, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ScriptNumber : public ScriptValue {
|
|
||||||
private:
|
|
||||||
lua_Number value;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ScriptNumber(lua_Number value0) : value(value0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptNumber & operator=(lua_Number newValue) {
|
|
||||||
value = newValue;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator lua_Number() const {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void push(lua_State *L) {
|
|
||||||
lua_pushnumber(L, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ScriptString : public ScriptValue {
|
|
||||||
private:
|
|
||||||
std::string value;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ScriptString(std::string &value0) : value(value0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ScriptString & operator=(std::string &newValue) {
|
|
||||||
value = newValue;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator std::string &() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void push(lua_State *L) {
|
|
||||||
lua_pushstring(L, value.c_str());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ScriptTable : public ScriptValue {
|
|
||||||
public:
|
|
||||||
typedef std::unordered_map<std::shared_ptr<ScriptValue>, std::shared_ptr<ScriptValue>> MapType;
|
|
||||||
|
|
||||||
private:
|
|
||||||
MapType value;
|
|
||||||
|
|
||||||
public:
|
|
||||||
std::shared_ptr<ScriptValue> & operator[](const std::shared_ptr<ScriptValue> &key) {
|
|
||||||
return value[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void push(lua_State *L) {
|
|
||||||
lua_createtable(L, 0, value.size());
|
|
||||||
|
|
||||||
for (const auto &entry : value) {
|
|
||||||
entry.first->push(L);
|
|
||||||
entry.second->push(L);
|
|
||||||
|
|
||||||
lua_settable(L, -3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "Scriptable.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
int Scriptable::gc(lua_State *L) {
|
|
||||||
std::shared_ptr<ScriptValue> *value = static_cast<std::shared_ptr<ScriptValue> *>(lua_touserdata(L, -1));
|
|
||||||
|
|
||||||
value->~shared_ptr<ScriptValue>();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Scriptable::setupMetatable(lua_State *L) {
|
|
||||||
if (luaL_newmetatable(L, "RPGEdit::Model::Scriptable")) {
|
|
||||||
lua_pushstring(L, "__metatable");
|
|
||||||
lua_pushstring(L, "protected");
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
|
|
||||||
lua_pushstring(L, "__gc");
|
|
||||||
lua_pushcfunction(L, &gc);
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_setmetatable(L, -2);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
#include "ScriptValue.hpp"
|
|
||||||
|
|
||||||
#include <new>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace Model {
|
|
||||||
|
|
||||||
class Scriptable : public ScriptValue, public std::enable_shared_from_this<Scriptable> {
|
|
||||||
private:
|
|
||||||
static int gc(lua_State *L);
|
|
||||||
|
|
||||||
void setupMetatable(lua_State *L);
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual void push(lua_State *L) {
|
|
||||||
|
|
||||||
void *ptr = lua_newuserdata(L, sizeof(std::shared_ptr<ScriptValue>));
|
|
||||||
setupMetatable(L);
|
|
||||||
|
|
||||||
new (ptr) std::shared_ptr<ScriptValue>(shared_from_this());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
16
src/renderer/custom.d.ts
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
declare namespace NodeJS {
|
||||||
|
interface Module {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
hot: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.vs' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.fs' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
64
src/renderer/editor/editor.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import 'typeface-roboto';
|
||||||
|
|
||||||
|
import { createMuiTheme, makeStyles, Theme, ThemeProvider } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||||
|
import Drawer from '@material-ui/core/Drawer';
|
||||||
|
|
||||||
|
import { Library } from './library';
|
||||||
|
|
||||||
|
const drawerWidth = 240;
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
drawer: {
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
drawerPaper: {
|
||||||
|
width: drawerWidth,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flexGrow: 1,
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
function EditorLayout(): JSX.Element {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<CssBaseline />
|
||||||
|
<Drawer
|
||||||
|
className={classes.drawer}
|
||||||
|
variant='permanent'
|
||||||
|
classes={{
|
||||||
|
paper: classes.drawerPaper,
|
||||||
|
}}
|
||||||
|
anchor='left'
|
||||||
|
></Drawer>
|
||||||
|
<main className={classes.content}>
|
||||||
|
<Library />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = createMuiTheme({
|
||||||
|
palette: {
|
||||||
|
type: 'dark',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function Editor(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<EditorLayout />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
118
src/renderer/editor/library.tsx
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
const { useCallback, useMemo } = React;
|
||||||
|
|
||||||
|
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
import Box from '@material-ui/core/Box';
|
||||||
|
import Button from '@material-ui/core/Button';
|
||||||
|
import Container from '@material-ui/core/Container';
|
||||||
|
|
||||||
|
import * as Color from 'color';
|
||||||
|
import * as glob from 'fast-glob';
|
||||||
|
|
||||||
|
import { Tiling, TilingMeta } from './types';
|
||||||
|
import { usePromise, useReadFile, readJSON } from './util';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
grid: {
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
margin: theme.spacing(-0.5),
|
||||||
|
},
|
||||||
|
tile: {
|
||||||
|
imageRendering: 'pixelated',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: theme.palette.divider,
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
position: 'relative',
|
||||||
|
width: 130,
|
||||||
|
height: 130,
|
||||||
|
margin: theme.spacing(0.5),
|
||||||
|
// cursor: 'pointer',
|
||||||
|
// '&:hover': {
|
||||||
|
// borderColor: theme.palette.text.secondary,
|
||||||
|
// boxShadow: `0 0 2px 1px ${theme.palette.text.secondary}`,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
img: {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 1,
|
||||||
|
width: 128,
|
||||||
|
height: 128,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 2,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
height: '37.5%',
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
background: Color(theme.palette.background.default).fade(0.3).string(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
function tilingSprite(tiling: Tiling): string {
|
||||||
|
const x = (tiling.meta.width - 1) / 2;
|
||||||
|
const y = (tiling.meta.height - 1) / 2;
|
||||||
|
return `project/tiling/${tiling.id}/${x}_${y}.png`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TilingDisplayProps {
|
||||||
|
tiling: Tiling;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TilingDisplay({ tiling }: TilingDisplayProps): JSX.Element | null {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const path = tilingSprite(tiling);
|
||||||
|
const image = useReadFile(path);
|
||||||
|
const src = useMemo(() => (image ? `data:image/png;base64,${image.toString('base64')}` : undefined), [image]);
|
||||||
|
return (
|
||||||
|
<div className={classes.tile}>
|
||||||
|
<img className={classes.img} src={src} />
|
||||||
|
<div className={classes.label}>{tiling.meta.name}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listTilings(): Promise<string[]> {
|
||||||
|
const matches = await glob('project/tiling/*/meta.json');
|
||||||
|
return matches.map((m) => m.split('/')[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTilingMeta(id: string): Promise<TilingMeta> {
|
||||||
|
const path = `project/tiling/${id}/meta.json`;
|
||||||
|
const meta = await readJSON(path);
|
||||||
|
return meta as TilingMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTilings(): Promise<Tiling[]> {
|
||||||
|
const tilings = await listTilings();
|
||||||
|
return Promise.all(
|
||||||
|
tilings.map((id) => [id, loadTilingMeta(id)] as const).map(async ([id, p]) => ({ id, meta: await p })),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Library(): JSX.Element {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const mkLoadTilings = useCallback(() => loadTilings(), []);
|
||||||
|
const tilings = usePromise(mkLoadTilings) ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Box mb={2}>
|
||||||
|
<div className={classes.grid}>
|
||||||
|
{tilings.map((tiling, i) => (
|
||||||
|
<TilingDisplay key={i} tiling={tiling} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
<Button variant='contained' color='primary'>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
11
src/renderer/editor/types.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export interface TilingMeta {
|
||||||
|
name: string;
|
||||||
|
desc?: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tiling {
|
||||||
|
id: string;
|
||||||
|
meta: TilingMeta;
|
||||||
|
}
|
36
src/renderer/editor/util.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
export function usePromise<T>(f: () => Promise<T>): T | null {
|
||||||
|
const [value, setValue] = useState<T | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(null);
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
(async (): Promise<void> => {
|
||||||
|
const v = await f();
|
||||||
|
if (!cancelled) {
|
||||||
|
setValue(v);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [f]);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReadFile(path: string): Buffer | null {
|
||||||
|
const readFile = useCallback(() => fs.promises.readFile(path), [path]);
|
||||||
|
return usePromise(readFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readJSON(path: string): Promise<unknown> {
|
||||||
|
const content = await fs.promises.readFile(path, 'utf8');
|
||||||
|
return JSON.parse(content);
|
||||||
|
}
|
18
src/renderer/index.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||||
|
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
|
||||||
|
<title>RPGedit</title>
|
||||||
|
<style>
|
||||||
|
html, body, #app {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
8
src/renderer/index.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import { Editor } from './editor/editor';
|
||||||
|
|
||||||
|
ReactDOM.render(<Editor />, document.querySelector('#app'));
|
||||||
|
|
||||||
|
module.hot?.accept();
|
60
src/renderer/runtime/controller/collision.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { Collision } from '../model/data/collision';
|
||||||
|
|
||||||
|
import { Circle } from '../math/circle';
|
||||||
|
import { Collidable } from '../math/collision';
|
||||||
|
import { LineSegment, Movement } from '../math/line';
|
||||||
|
import { Point } from '../math/point';
|
||||||
|
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export function mkCollision(collision: Collision[]): Collidable[] {
|
||||||
|
const ret: Collidable[] = [];
|
||||||
|
|
||||||
|
for (const c of collision) {
|
||||||
|
switch (c.type) {
|
||||||
|
case 'polygon':
|
||||||
|
if (!c.vertices.length) continue;
|
||||||
|
|
||||||
|
let prev = c.vertices[c.vertices.length - 1];
|
||||||
|
|
||||||
|
for (const v of c.vertices) {
|
||||||
|
ret.push(LineSegment.fromPoints(vec2.clone(prev), vec2.clone(v)));
|
||||||
|
prev = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const v of c.vertices) {
|
||||||
|
ret.push(new Point(vec2.clone(v)));
|
||||||
|
prev = v;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'circle':
|
||||||
|
ret.push(new Circle(vec2.clone(c.center), c.radius));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollidableGroup {
|
||||||
|
getTranslation(): vec2 | null;
|
||||||
|
getCollidables(): Collidable[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collide(collision: CollidableGroup, out: vec2, move: Movement, radius: number): boolean {
|
||||||
|
const t = collision.getTranslation();
|
||||||
|
if (t) move = move.translate(vec2.negate(vec2.create(), t));
|
||||||
|
|
||||||
|
for (const c of collision.getCollidables()) {
|
||||||
|
if (!c.collide(out, move, radius)) continue;
|
||||||
|
|
||||||
|
if (vec2.squaredDistance(move.src, out) >= vec2.squaredDistance(move.src, move.dest)) continue;
|
||||||
|
|
||||||
|
if (t) vec2.add(out, out, t);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
43
src/renderer/runtime/controller/entitycontext.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { EntityView } from '../view/entity';
|
||||||
|
import { Renderer } from '../view/renderer/renderer';
|
||||||
|
|
||||||
|
import { Collidable } from '../math/collision';
|
||||||
|
|
||||||
|
import { CollidableGroup, mkCollision } from './collision';
|
||||||
|
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export class EntityContext implements CollidableGroup {
|
||||||
|
public static async load(renderer: Renderer, name: string): Promise<EntityContext> {
|
||||||
|
return new EntityContext(renderer, name, await EntityView.load(renderer, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly pos: vec2 = vec2.create();
|
||||||
|
|
||||||
|
private readonly collision: Collidable[];
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
private readonly renderer: Renderer,
|
||||||
|
private readonly name: string,
|
||||||
|
private readonly view: EntityView,
|
||||||
|
) {
|
||||||
|
this.collision = mkCollision(view.data.collision);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(time: number): void {
|
||||||
|
this.renderer.setTranslation(this.pos);
|
||||||
|
this.view.renderByTime(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTranslation(): vec2 {
|
||||||
|
return this.pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCollidables(): Collidable[] {
|
||||||
|
return this.collision;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interact(): void {
|
||||||
|
alert(`You've interacted with ${this.name}!`);
|
||||||
|
}
|
||||||
|
}
|
163
src/renderer/runtime/controller/gamecontext.ts
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import { CollidableGroup, collide, mkCollision } from './collision';
|
||||||
|
import { EntityContext } from './entitycontext';
|
||||||
|
|
||||||
|
import { MapData, MapDataInput } from '../model/data/map';
|
||||||
|
|
||||||
|
import { ButtonCode, GameInputHandler } from '../view/input/gameinput';
|
||||||
|
import { MapView } from '../view/map';
|
||||||
|
import { Renderer } from '../view/renderer/renderer';
|
||||||
|
|
||||||
|
import { Collidable } from '../math/collision';
|
||||||
|
import { Movement } from '../math/line';
|
||||||
|
import { getJSON, nextAnimationFrame } from '../util';
|
||||||
|
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export class GameContext implements CollidableGroup {
|
||||||
|
public static async load(renderer: Renderer): Promise<GameContext> {
|
||||||
|
const map = this.loadMap(renderer, 'test');
|
||||||
|
const loadPlayer = EntityContext.load(renderer, 'green_circle');
|
||||||
|
const loadEntity = EntityContext.load(renderer, 'red_circle');
|
||||||
|
|
||||||
|
const [mapView, mapCollision] = await map;
|
||||||
|
const player = await loadPlayer;
|
||||||
|
const entity = await loadEntity;
|
||||||
|
|
||||||
|
vec2.set(player.pos, 7, 6);
|
||||||
|
vec2.set(entity.pos, 4, 3);
|
||||||
|
|
||||||
|
return new GameContext(renderer, mapView, player, [entity], mapCollision);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async loadMap(renderer: Renderer, name: string): Promise<[MapView, Collidable[]]> {
|
||||||
|
const map = new MapData((await getJSON(`resources/map/${name}.json`)) as MapDataInput);
|
||||||
|
return [await MapView.load(renderer, map), mkCollision(map.collision)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly initTime: number = performance.now();
|
||||||
|
private time = 0;
|
||||||
|
|
||||||
|
private readonly tick = 10; // ms per tick
|
||||||
|
private readonly maxSpeed = 0.04; // movement per tick
|
||||||
|
private readonly maxSkip = 20; // maximum ticks to process in a single render step
|
||||||
|
|
||||||
|
private readonly input: GameInputHandler;
|
||||||
|
|
||||||
|
private readonly playerDir: vec2 = vec2.fromValues(0, 1);
|
||||||
|
private speed = 0;
|
||||||
|
|
||||||
|
private readonly collisionRadius = 15 / 32;
|
||||||
|
private readonly interactLength = 1 / 32;
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
private readonly renderer: Renderer,
|
||||||
|
private readonly mapView: MapView,
|
||||||
|
private readonly player: EntityContext,
|
||||||
|
private readonly entities: EntityContext[],
|
||||||
|
private readonly collision: Collidable[],
|
||||||
|
) {
|
||||||
|
this.input = new GameInputHandler();
|
||||||
|
this.input.addListener((input) => {
|
||||||
|
switch (input.type) {
|
||||||
|
case 'button':
|
||||||
|
if (input.button === ButtonCode.Action) this.interact();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'direction':
|
||||||
|
if (vec2.sqrLen(input.direction) > 0) {
|
||||||
|
vec2.copy(this.playerDir, input.direction);
|
||||||
|
this.speed = this.maxSpeed;
|
||||||
|
} else {
|
||||||
|
this.speed = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.renderLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTranslation(): null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCollidables(): Collidable[] {
|
||||||
|
return this.collision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateTime(time: number): number {
|
||||||
|
const diff = Math.round(time / this.tick) - Math.round(this.time / this.tick);
|
||||||
|
this.time = time;
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private canInteract(c: CollidableGroup): boolean {
|
||||||
|
const dest = vec2.scaleAndAdd(vec2.create(), this.player.pos, this.playerDir, this.interactLength);
|
||||||
|
const move = new Movement(this.player.pos, dest);
|
||||||
|
|
||||||
|
return collide(c, vec2.create(), move, this.collisionRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interact(): void {
|
||||||
|
for (const e of this.entities) {
|
||||||
|
if (!this.canInteract(e)) continue;
|
||||||
|
|
||||||
|
e.interact();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateStepCollide(out: vec2, dest: vec2): boolean {
|
||||||
|
const move = new Movement(this.player.pos, dest);
|
||||||
|
|
||||||
|
for (const c of [this, ...this.entities]) {
|
||||||
|
if (collide(c, out, move, this.collisionRadius)) {
|
||||||
|
if (vec2.squaredDistance(move.src, out) >= vec2.squaredDistance(move.src, move.dest))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateStep(): void {
|
||||||
|
const dest = vec2.scaleAndAdd(vec2.create(), this.player.pos, this.playerDir, this.speed);
|
||||||
|
const newDest = vec2.create();
|
||||||
|
|
||||||
|
while (this.updateStepCollide(newDest, dest)) {
|
||||||
|
if (vec2.equals(newDest, this.player.pos)) return;
|
||||||
|
|
||||||
|
vec2.copy(dest, newDest);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2.copy(this.player.pos, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private update(time: number): void {
|
||||||
|
const diff = Math.min(this.maxSkip, this.updateTime(time));
|
||||||
|
|
||||||
|
if (!this.speed) {
|
||||||
|
this.renderer.snapToGrid(this.player.pos, this.player.pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < diff; i++) this.updateStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
private render(): void {
|
||||||
|
this.renderer.setCenter(this.player.pos);
|
||||||
|
this.renderer.clear();
|
||||||
|
|
||||||
|
for (const r of [this.mapView, ...this.entities, this.player]) r.render(this.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async renderLoop(): Promise<void> {
|
||||||
|
while (true) {
|
||||||
|
this.update((await nextAnimationFrame()) - this.initTime);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/renderer/runtime/index.css
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
html, body, div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
border: 0px;
|
||||||
|
background: #223;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
15
src/renderer/runtime/math/circle.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Collidable } from './collision';
|
||||||
|
import { Movement } from './line';
|
||||||
|
import { Point } from './point';
|
||||||
|
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export class Circle implements Collidable, Point {
|
||||||
|
private pointCollide = Point.prototype.collide;
|
||||||
|
|
||||||
|
constructor(public readonly p: vec2, public readonly r: number) {}
|
||||||
|
|
||||||
|
public collide(out: vec2, move: Movement, r: number): boolean {
|
||||||
|
return this.pointCollide(out, move, r + this.r);
|
||||||
|
}
|
||||||
|
}
|
7
src/renderer/runtime/math/collision.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { Movement } from './line';
|
||||||
|
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export interface Collidable {
|
||||||
|
collide(out: vec2, move: Movement, r: number): boolean;
|
||||||
|
}
|
124
src/renderer/runtime/math/line.ts
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import { mat2, vec2 } from 'gl-matrix';
|
||||||
|
import { Collidable } from './collision';
|
||||||
|
|
||||||
|
const rot90 = mat2.fromValues(0, 1, -1, 0);
|
||||||
|
|
||||||
|
export function normal(out: vec2, a: vec2): vec2 {
|
||||||
|
return vec2.transformMat2(out, a, rot90);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function crossz(a: vec2, b: vec2): number {
|
||||||
|
return a[0] * b[1] - a[1] * b[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Line {
|
||||||
|
constructor(public readonly p: vec2, public readonly v: vec2) {}
|
||||||
|
|
||||||
|
public getNormal(out: vec2): vec2 {
|
||||||
|
return normal(out, this.v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public projectPointDistance(p2: vec2): number {
|
||||||
|
const v2 = vec2.sub(vec2.create(), p2, this.p);
|
||||||
|
return vec2.dot(this.v, v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public projectPoint(out: vec2, p2: vec2): vec2 {
|
||||||
|
const d = this.projectPointDistance(p2);
|
||||||
|
return vec2.scaleAndAdd(out, this.p, this.v, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public distancePoint(p2: vec2): number {
|
||||||
|
const v2 = vec2.sub(vec2.create(), p2, this.p);
|
||||||
|
return crossz(this.v, v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public intersectLine(out: vec2, l2: Line): vec2 {
|
||||||
|
const vp = vec2.sub(vec2.create(), l2.p, this.p);
|
||||||
|
const d = crossz(vp, this.v);
|
||||||
|
const d2 = d / crossz(this.v, l2.v);
|
||||||
|
return vec2.scaleAndAdd(out, l2.p, l2.v, d2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Movement {
|
||||||
|
public readonly v: vec2;
|
||||||
|
|
||||||
|
constructor(public readonly src: vec2, public readonly dest: vec2) {
|
||||||
|
this.v = vec2.sub(vec2.create(), dest, src);
|
||||||
|
}
|
||||||
|
|
||||||
|
public intersectLine(out: vec2, l: Line): vec2 {
|
||||||
|
const vp = vec2.sub(vec2.create(), l.p, this.src);
|
||||||
|
const d = crossz(vp, this.v);
|
||||||
|
const d2 = d / crossz(this.v, l.v);
|
||||||
|
return vec2.scaleAndAdd(out, l.p, l.v, d2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public passes(p: vec2): boolean {
|
||||||
|
const vp = vec2.sub(vec2.create(), p, this.src);
|
||||||
|
const d = vec2.dot(this.v, vp);
|
||||||
|
return d >= 0 && d <= vec2.sqrLen(this.v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toLineSegment(): LineSegment {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
|
return LineSegment.fromPoints(this.src, this.dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public translate(t: vec2): Movement {
|
||||||
|
const src = vec2.add(vec2.create(), this.src, t);
|
||||||
|
const dest = vec2.add(vec2.create(), this.dest, t);
|
||||||
|
return new Movement(src, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LineSegment extends Line implements Collidable {
|
||||||
|
public static fromPoints(p1: vec2, p2: vec2): LineSegment {
|
||||||
|
const d = vec2.dist(p1, p2);
|
||||||
|
const v = vec2.sub(vec2.create(), p2, p1);
|
||||||
|
vec2.scale(v, v, 1 / d);
|
||||||
|
|
||||||
|
return new LineSegment(p1, v, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(p: vec2, v: vec2, public readonly l: number) {
|
||||||
|
super(p, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getP2(out: vec2): vec2 {
|
||||||
|
return vec2.scaleAndAdd(out, this.p, this.v, this.l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public containsPoint(p2: vec2): boolean {
|
||||||
|
const d = this.projectPointDistance(p2);
|
||||||
|
return d >= 0 && d <= this.l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public collide(out: vec2, move: Movement, r: number): boolean {
|
||||||
|
if (this.distancePoint(move.src) < 0) return false;
|
||||||
|
|
||||||
|
if (crossz(move.v, this.v) < 0) return false;
|
||||||
|
|
||||||
|
const t = this.getNormal(vec2.create());
|
||||||
|
vec2.scale(t, t, -r);
|
||||||
|
|
||||||
|
const refMove = move.translate(t);
|
||||||
|
|
||||||
|
if (!this.collideRef(out, refMove)) return false;
|
||||||
|
|
||||||
|
vec2.sub(out, out, t);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private collideRef(out: vec2, move: Movement): boolean {
|
||||||
|
if (this.distancePoint(move.dest) >= 0) return false;
|
||||||
|
|
||||||
|
const x = move.intersectLine(vec2.create(), this);
|
||||||
|
if (!this.containsPoint(x)) return false;
|
||||||
|
|
||||||
|
this.projectPoint(out, move.dest);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
37
src/renderer/runtime/math/point.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { Collidable } from './collision';
|
||||||
|
import { Line, Movement, normal } from './line';
|
||||||
|
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export class Point implements Collidable {
|
||||||
|
constructor(public readonly p: vec2) {}
|
||||||
|
|
||||||
|
public collide(out: vec2, move: Movement, r: number): boolean {
|
||||||
|
const moveLine = move.toLineSegment();
|
||||||
|
|
||||||
|
if (moveLine.projectPointDistance(this.p) < 0) return false;
|
||||||
|
|
||||||
|
const d = moveLine.distancePoint(this.p) / r;
|
||||||
|
if (Math.abs(d) >= 1) return false;
|
||||||
|
|
||||||
|
const e = Math.sqrt(1 - d * d);
|
||||||
|
|
||||||
|
const t = moveLine.getNormal(vec2.create());
|
||||||
|
vec2.scale(t, t, d);
|
||||||
|
vec2.scaleAndAdd(t, t, moveLine.v, e);
|
||||||
|
|
||||||
|
const tr = vec2.scale(vec2.create(), t, r);
|
||||||
|
|
||||||
|
const refMove = move.translate(tr);
|
||||||
|
|
||||||
|
if (vec2.sqrDist(this.p, move.src) > r * r && !refMove.passes(this.p)) return false;
|
||||||
|
|
||||||
|
normal(t, t);
|
||||||
|
|
||||||
|
const tang = new Line(this.p, t);
|
||||||
|
tang.projectPoint(out, refMove.dest);
|
||||||
|
vec2.sub(out, out, tr);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
12
src/renderer/runtime/model/data/collision.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export interface PolygonCollision {
|
||||||
|
readonly type: 'polygon';
|
||||||
|
readonly vertices: Array<[number, number]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CircleCollision {
|
||||||
|
readonly type: 'circle';
|
||||||
|
readonly center: [number, number];
|
||||||
|
readonly radius: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Collision = PolygonCollision | CircleCollision;
|
29
src/renderer/runtime/model/data/entity.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Collision } from './collision';
|
||||||
|
|
||||||
|
export interface EntityAnimation {
|
||||||
|
readonly sequence: ReadonlyArray<[number, number]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntityDataInput {
|
||||||
|
readonly sprite: string;
|
||||||
|
readonly anchor?: [number, number];
|
||||||
|
readonly collision?: Collision[];
|
||||||
|
readonly frames?: number;
|
||||||
|
readonly animation?: EntityAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EntityData {
|
||||||
|
public readonly sprite: string;
|
||||||
|
public readonly anchor: [number, number];
|
||||||
|
public readonly collision: Collision[];
|
||||||
|
public readonly frames: number;
|
||||||
|
public readonly animation?: EntityAnimation;
|
||||||
|
|
||||||
|
constructor(input: EntityDataInput) {
|
||||||
|
this.sprite = input.sprite;
|
||||||
|
this.anchor = input.anchor || [0.5, 0.5];
|
||||||
|
this.collision = input.collision || [];
|
||||||
|
this.frames = input.frames || 1;
|
||||||
|
this.animation = input.animation;
|
||||||
|
}
|
||||||
|
}
|
29
src/renderer/runtime/model/data/map.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Collision } from './collision';
|
||||||
|
|
||||||
|
export interface MapLayer {
|
||||||
|
readonly tiles: number[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MapDataInput {
|
||||||
|
readonly tiles: string[];
|
||||||
|
readonly layers: MapLayer[];
|
||||||
|
readonly collision: Collision[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MapData {
|
||||||
|
public readonly tiles: string[];
|
||||||
|
public readonly layers: MapLayer[];
|
||||||
|
public readonly collision: Collision[];
|
||||||
|
|
||||||
|
public readonly width: number;
|
||||||
|
public readonly height: number;
|
||||||
|
|
||||||
|
constructor(data: MapDataInput) {
|
||||||
|
this.tiles = data.tiles;
|
||||||
|
this.layers = data.layers;
|
||||||
|
this.collision = data.collision;
|
||||||
|
|
||||||
|
this.height = this.layers[0].tiles.length;
|
||||||
|
this.width = this.layers[0].tiles[0].length;
|
||||||
|
}
|
||||||
|
}
|
55
src/renderer/runtime/util.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
export function recordToMap<T>(r: Record<string, T>): Map<string, T> {
|
||||||
|
const ret = new Map();
|
||||||
|
|
||||||
|
for (const k of Object.keys(r)) ret.set(k, r[k]);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapValues<K, V1, V2>(f: (v: V1) => V2, map: Map<K, V1>): Map<K, V2> {
|
||||||
|
const ret: Map<K, V2> = new Map();
|
||||||
|
|
||||||
|
for (const [k, v] of map) ret.set(k, f(v));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mapValuesAsync<K, V1, V2>(f: (v: V1) => Promise<V2>, map: Map<K, V1>): Promise<Map<K, V2>> {
|
||||||
|
const ret: Map<K, V2> = new Map();
|
||||||
|
|
||||||
|
for (const [k, v] of mapValues(f, map)) ret.set(k, await v);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextPowerOf2(n: number): number {
|
||||||
|
let i = 1;
|
||||||
|
|
||||||
|
while (i < n) i *= 2;
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export class Listenable<T extends any[]> {
|
||||||
|
private readonly listeners: Array<(...args: T) => void> = [];
|
||||||
|
|
||||||
|
public addListener(listener: (...args: T) => void): void {
|
||||||
|
this.listeners.push(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected runListeners(...args: T): void {
|
||||||
|
this.listeners.forEach((l) => l(...args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getJSON(url: string): Promise<unknown> {
|
||||||
|
const res = await window.fetch(url);
|
||||||
|
if (res.status < 200 || res.status >= 300) throw new Error(res.statusText);
|
||||||
|
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextAnimationFrame(): Promise<DOMHighResTimeStamp> {
|
||||||
|
return new Promise((resolve) => window.requestAnimationFrame(resolve));
|
||||||
|
}
|
62
src/renderer/runtime/view/entity.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { EntityData, EntityDataInput } from '../model/data/entity';
|
||||||
|
import { Renderer } from './renderer/renderer';
|
||||||
|
import { SpriteCoords, SpriteView, SpriteViewBuilder } from './sprite';
|
||||||
|
import { loadImage, mkTexture } from './util/image';
|
||||||
|
|
||||||
|
import { getJSON } from '../util';
|
||||||
|
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export class EntityView {
|
||||||
|
public static async load(r: Renderer, name: string): Promise<EntityView> {
|
||||||
|
const data = new EntityData((await getJSON(`resources/entity/${name}.json`)) as EntityDataInput);
|
||||||
|
const tile = await loadImage(`resources/sprite/entity/${data.sprite}.png`);
|
||||||
|
|
||||||
|
const [texture, size] = mkTexture(r, tile);
|
||||||
|
const frameSize: [number, number] = [size[0], size[1] / data.frames];
|
||||||
|
|
||||||
|
const offset = vec2.mul(vec2.create(), frameSize, data.anchor);
|
||||||
|
r.snapToGrid(offset, offset);
|
||||||
|
|
||||||
|
const coords: SpriteCoords = [
|
||||||
|
-offset[0],
|
||||||
|
-offset[1],
|
||||||
|
-offset[0] + frameSize[0],
|
||||||
|
-offset[1] + frameSize[1],
|
||||||
|
];
|
||||||
|
|
||||||
|
const sprites: SpriteView[] = [];
|
||||||
|
|
||||||
|
for (let frame = 0; frame < data.frames; frame++) {
|
||||||
|
const builder = new SpriteViewBuilder(r, texture);
|
||||||
|
builder.addSprite(coords, [0, frame / data.frames, 1, (frame + 1) / data.frames]);
|
||||||
|
sprites.push(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EntityView(data, sprites);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly totalTime: number;
|
||||||
|
|
||||||
|
private constructor(public readonly data: EntityData, public readonly sprites: SpriteView[]) {
|
||||||
|
if (data.animation) this.totalTime = data.animation.sequence.reduce((a, s) => a + s[0], 0);
|
||||||
|
else this.totalTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSpriteByTime(time: number): SpriteView {
|
||||||
|
time %= this.totalTime;
|
||||||
|
|
||||||
|
if (this.data.animation) {
|
||||||
|
for (const [len, sprite] of this.data.animation.sequence) {
|
||||||
|
time -= len;
|
||||||
|
if (time < 0) return this.sprites[sprite];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sprites[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderByTime(time: number): void {
|
||||||
|
this.getSpriteByTime(time).render();
|
||||||
|
}
|
||||||
|
}
|
68
src/renderer/runtime/view/input/gameinput.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { InputHandler } from './inputhandler';
|
||||||
|
|
||||||
|
import { Listenable } from '../../util';
|
||||||
|
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export enum ButtonCode {
|
||||||
|
Action,
|
||||||
|
Back,
|
||||||
|
Menu,
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonMapping: Record<string, ButtonCode> = {
|
||||||
|
KeyZ: ButtonCode.Action,
|
||||||
|
KeyX: ButtonCode.Back,
|
||||||
|
KeyC: ButtonCode.Menu,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DirectionInput {
|
||||||
|
type: 'direction';
|
||||||
|
direction: vec2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ButtonInput {
|
||||||
|
type: 'button';
|
||||||
|
button: ButtonCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GameInput = DirectionInput | ButtonInput;
|
||||||
|
|
||||||
|
export class GameInputHandler extends Listenable<[GameInput]> {
|
||||||
|
private readonly input: InputHandler;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.input = new InputHandler(
|
||||||
|
new Set(['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', ...Object.keys(buttonMapping)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.input.addListener((key: string, pressed: boolean) => {
|
||||||
|
const button = buttonMapping[key];
|
||||||
|
if (button !== undefined) {
|
||||||
|
if (pressed)
|
||||||
|
this.runListeners({
|
||||||
|
type: 'button',
|
||||||
|
button,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dir = vec2.create();
|
||||||
|
|
||||||
|
if (this.input.has('ArrowLeft')) vec2.add(dir, dir, [-1, 0]);
|
||||||
|
if (this.input.has('ArrowUp')) vec2.add(dir, dir, [0, -1]);
|
||||||
|
if (this.input.has('ArrowRight')) vec2.add(dir, dir, [1, 0]);
|
||||||
|
if (this.input.has('ArrowDown')) vec2.add(dir, dir, [0, 1]);
|
||||||
|
|
||||||
|
if (vec2.sqrLen(dir) > 0) vec2.normalize(dir, dir);
|
||||||
|
|
||||||
|
this.runListeners({
|
||||||
|
type: 'direction',
|
||||||
|
direction: dir,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
40
src/renderer/runtime/view/input/inputhandler.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Listenable } from '../../util';
|
||||||
|
|
||||||
|
export class InputHandler extends Listenable<[string, boolean]> {
|
||||||
|
private readonly keys: Set<string> = new Set();
|
||||||
|
|
||||||
|
constructor(relevantKeys: Set<string>) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
window.addEventListener('keydown', (ev) => {
|
||||||
|
if (!relevantKeys.has(ev.code)) return;
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (ev.repeat) return;
|
||||||
|
|
||||||
|
this.keys.add(ev.code);
|
||||||
|
this.runListeners(ev.code, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('keyup', (ev) => {
|
||||||
|
if (!relevantKeys.has(ev.code)) return;
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (!this.keys.has(ev.code)) return;
|
||||||
|
|
||||||
|
this.keys.delete(ev.code);
|
||||||
|
this.runListeners(ev.code, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('blur', () => {
|
||||||
|
this.keys.clear();
|
||||||
|
this.runListeners('', false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public has(key: string): boolean {
|
||||||
|
return this.keys.has(key);
|
||||||
|
}
|
||||||
|
}
|
172
src/renderer/runtime/view/map.ts
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
import { EntityView } from './entity';
|
||||||
|
import { Renderer } from './renderer/renderer';
|
||||||
|
import { SpriteCoords, SpriteView, SpriteViewBuilder } from './sprite';
|
||||||
|
import { loadImage, mkTexture } from './util/image';
|
||||||
|
|
||||||
|
import { MapData } from '../model/data/map';
|
||||||
|
|
||||||
|
import { nextPowerOf2 } from '../util';
|
||||||
|
|
||||||
|
interface StaticMapTile {
|
||||||
|
type: 'static';
|
||||||
|
image: HTMLImageElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EntityTile {
|
||||||
|
type: 'entity';
|
||||||
|
entity: EntityView;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MapTile = StaticMapTile | EntityTile;
|
||||||
|
|
||||||
|
interface StaticTilesetTile {
|
||||||
|
type: 'static';
|
||||||
|
coords: SpriteCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TilesetTile = StaticTilesetTile | EntityTile;
|
||||||
|
|
||||||
|
interface Tileset {
|
||||||
|
texture: WebGLTexture;
|
||||||
|
tiles: TilesetTile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTile(r: Renderer, tile: string): Promise<MapTile> {
|
||||||
|
const name = tile.substr(1);
|
||||||
|
switch (tile[0]) {
|
||||||
|
case '-':
|
||||||
|
return {
|
||||||
|
type: 'static',
|
||||||
|
image: await loadImage(`resources/sprite/tile/${name}.png`),
|
||||||
|
};
|
||||||
|
|
||||||
|
case '@':
|
||||||
|
return {
|
||||||
|
type: 'entity',
|
||||||
|
entity: await EntityView.load(r, name),
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error('invalid tile specifier');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTiles(r: Renderer, tiles: string[]): Promise<MapTile[]> {
|
||||||
|
return Promise.all(tiles.map((tile) => loadTile(r, tile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mkTileset(r: Renderer, mapTiles: MapTile[]): Tileset {
|
||||||
|
const tileSize = 32;
|
||||||
|
|
||||||
|
const canvasDim = nextPowerOf2(Math.sqrt(mapTiles.length));
|
||||||
|
const canvasSize = canvasDim * tileSize;
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = canvas.height = canvasSize;
|
||||||
|
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
const tiles: TilesetTile[] = [];
|
||||||
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
for (const tile of mapTiles) {
|
||||||
|
switch (tile.type) {
|
||||||
|
case 'static':
|
||||||
|
ctx.drawImage(tile.image, x * tileSize, y * tileSize);
|
||||||
|
tiles.push({
|
||||||
|
type: 'static',
|
||||||
|
coords: [
|
||||||
|
x / canvasDim,
|
||||||
|
y / canvasDim,
|
||||||
|
(x + 1) / canvasDim,
|
||||||
|
(y + 1) / canvasDim,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
x++;
|
||||||
|
if (x === canvasDim) {
|
||||||
|
x = 0;
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'entity':
|
||||||
|
tiles.push(tile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [texture] = mkTexture(r, canvas);
|
||||||
|
|
||||||
|
return {
|
||||||
|
texture,
|
||||||
|
tiles,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSprite(
|
||||||
|
builder: SpriteViewBuilder,
|
||||||
|
entityTiles: Array<[[number, number], EntityView]>,
|
||||||
|
tileset: Tileset,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
tile: number,
|
||||||
|
): void {
|
||||||
|
if (!tile) return;
|
||||||
|
|
||||||
|
const tilesetTile = tileset.tiles[tile - 1];
|
||||||
|
|
||||||
|
switch (tilesetTile.type) {
|
||||||
|
case 'static':
|
||||||
|
builder.addSprite([x, y, x + 1, y + 1], tilesetTile.coords);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'entity':
|
||||||
|
entityTiles.push([[x + 0.5, y + 0.5], tilesetTile.entity]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MapLayerView {
|
||||||
|
public constructor(
|
||||||
|
private r: Renderer,
|
||||||
|
private staticTiles: SpriteView,
|
||||||
|
private entityTiles: Array<[[number, number], EntityView]>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public render(time: number): void {
|
||||||
|
this.r.setTranslation([0, 0]);
|
||||||
|
this.staticTiles.render();
|
||||||
|
|
||||||
|
for (const [coords, entity] of this.entityTiles) {
|
||||||
|
this.r.setTranslation(coords);
|
||||||
|
entity.renderByTime(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMapLayer(r: Renderer, tileset: Tileset, layer: number[][]): MapLayerView {
|
||||||
|
const builder = new SpriteViewBuilder(r, tileset.texture);
|
||||||
|
const entityTiles: Array<[[number, number], EntityView]> = [];
|
||||||
|
|
||||||
|
for (let x = 0; x < layer[0].length; x++)
|
||||||
|
for (let y = 0; y < layer.length; y++) addSprite(builder, entityTiles, tileset, x, y, layer[y][x]);
|
||||||
|
|
||||||
|
return new MapLayerView(r, builder.build(), entityTiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MapView {
|
||||||
|
public static async load(r: Renderer, map: MapData): Promise<MapView> {
|
||||||
|
const tiles = await loadTiles(r, map.tiles);
|
||||||
|
const tileset = mkTileset(r, tiles);
|
||||||
|
|
||||||
|
const layers = map.layers.map((layer) => buildMapLayer(r, tileset, layer.tiles));
|
||||||
|
return new MapView(layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(private layers: MapLayerView[]) {}
|
||||||
|
|
||||||
|
public render(time: number): void {
|
||||||
|
for (const layer of this.layers) layer.render(time);
|
||||||
|
}
|
||||||
|
}
|
96
src/renderer/runtime/view/renderer/renderer.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import { nextPowerOf2 } from '../../util';
|
||||||
|
import { Shaders } from './shaders';
|
||||||
|
|
||||||
|
import { mat4, vec2 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export class Renderer {
|
||||||
|
public readonly coordScale = 32;
|
||||||
|
private readonly viewScale = 2;
|
||||||
|
|
||||||
|
private readonly gl: WebGLRenderingContext;
|
||||||
|
private readonly shaders: Shaders;
|
||||||
|
|
||||||
|
private readonly center: vec2 = vec2.create();
|
||||||
|
private readonly translation: vec2 = vec2.create();
|
||||||
|
private readonly viewport: mat4 = mat4.create();
|
||||||
|
|
||||||
|
constructor(private readonly canvas: HTMLCanvasElement) {
|
||||||
|
this.gl = this.mkContext();
|
||||||
|
|
||||||
|
this.shaders = new Shaders(this.gl);
|
||||||
|
|
||||||
|
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
this.gl.enable(this.gl.BLEND);
|
||||||
|
this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createBuffer(): WebGLBuffer {
|
||||||
|
const ret = this.gl.createBuffer();
|
||||||
|
if (!ret) throw new Error('unable to create buffer');
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getContext(): WebGLRenderingContext {
|
||||||
|
return this.gl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVertexPosLoc(): number {
|
||||||
|
return this.shaders.vertexPosLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTextureCoordLoc(): number {
|
||||||
|
return this.shaders.textureCoordLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSamplerLoc(): WebGLUniformLocation {
|
||||||
|
return this.shaders.samplerLoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCenter(v: vec2): void {
|
||||||
|
this.snapToGrid(this.center, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setTranslation(v: vec2): void {
|
||||||
|
vec2.sub(this.translation, v, this.center);
|
||||||
|
this.snapToGrid(this.translation, this.translation);
|
||||||
|
this.gl.uniform2fv(this.shaders.translateLoc, this.translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
this.setTranslation([0, 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public snapToGrid(out: vec2, a: vec2): void {
|
||||||
|
vec2.scale(out, a, this.coordScale);
|
||||||
|
vec2.round(out, out);
|
||||||
|
vec2.scale(out, out, 1 / this.coordScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public resize(): void {
|
||||||
|
const w = this.canvas.width;
|
||||||
|
const h = this.canvas.height;
|
||||||
|
const ws = nextPowerOf2(w);
|
||||||
|
const hs = nextPowerOf2(h);
|
||||||
|
|
||||||
|
this.gl.viewport((w - ws) / 2, (h - hs) / 2, ws, hs);
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
const scale = this.viewScale * this.coordScale;
|
||||||
|
|
||||||
|
mat4.identity(this.viewport);
|
||||||
|
mat4.scale(this.viewport, this.viewport, [(2 * scale) / ws, (-2 * scale) / hs, 1.0]);
|
||||||
|
this.gl.uniformMatrix4fv(this.shaders.viewportLoc, false, this.viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
private mkContext(): WebGLRenderingContext {
|
||||||
|
const gl = this.canvas.getContext('webgl');
|
||||||
|
if (!gl) throw new Error('unable to initialize WebGL context');
|
||||||
|
|
||||||
|
return gl;
|
||||||
|
}
|
||||||
|
}
|
75
src/renderer/runtime/view/renderer/shaders.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import fragmentShaderSrc from './shaders/default.fs';
|
||||||
|
import vertexShaderSrc from './shaders/default.vs';
|
||||||
|
|
||||||
|
export class Shaders {
|
||||||
|
public readonly viewportLoc: WebGLUniformLocation;
|
||||||
|
public readonly translateLoc: WebGLUniformLocation;
|
||||||
|
|
||||||
|
public readonly vertexPosLoc: number;
|
||||||
|
public readonly textureCoordLoc: number;
|
||||||
|
public readonly samplerLoc: WebGLUniformLocation;
|
||||||
|
|
||||||
|
constructor(private readonly gl: WebGLRenderingContext) {
|
||||||
|
const shaderProgram = this.gl.createProgram();
|
||||||
|
if (!shaderProgram) throw new Error('Unable to create shader program');
|
||||||
|
|
||||||
|
const vertexShader = this.compileShader(this.gl.VERTEX_SHADER, vertexShaderSrc);
|
||||||
|
const fragmentShader = this.compileShader(this.gl.FRAGMENT_SHADER, fragmentShaderSrc);
|
||||||
|
|
||||||
|
this.gl.attachShader(shaderProgram, vertexShader);
|
||||||
|
this.gl.attachShader(shaderProgram, fragmentShader);
|
||||||
|
|
||||||
|
this.gl.linkProgram(shaderProgram);
|
||||||
|
if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
|
||||||
|
const err = this.gl.getProgramInfoLog(shaderProgram);
|
||||||
|
|
||||||
|
this.gl.deleteShader(vertexShader);
|
||||||
|
this.gl.deleteShader(fragmentShader);
|
||||||
|
this.gl.deleteProgram(shaderProgram);
|
||||||
|
|
||||||
|
throw new Error('Unable to link shader: ' + err);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
|
this.vertexPosLoc = this.getAttribLocation(shaderProgram, 'aVertexPos');
|
||||||
|
this.gl.enableVertexAttribArray(this.vertexPosLoc);
|
||||||
|
|
||||||
|
this.textureCoordLoc = this.getAttribLocation(shaderProgram, 'aTextureCoord');
|
||||||
|
this.gl.enableVertexAttribArray(this.textureCoordLoc);
|
||||||
|
|
||||||
|
this.viewportLoc = this.getUniformLocation(shaderProgram, 'uViewport');
|
||||||
|
this.translateLoc = this.getUniformLocation(shaderProgram, 'uTranslate');
|
||||||
|
this.samplerLoc = this.getUniformLocation(shaderProgram, 'uSampler');
|
||||||
|
}
|
||||||
|
|
||||||
|
private compileShader(type: number, src: string): WebGLShader {
|
||||||
|
const shader = this.gl.createShader(type);
|
||||||
|
if (!shader) throw new Error('Unable to create shader');
|
||||||
|
|
||||||
|
this.gl.shaderSource(shader, src);
|
||||||
|
this.gl.compileShader(shader);
|
||||||
|
|
||||||
|
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
|
||||||
|
const err = this.gl.getShaderInfoLog(shader);
|
||||||
|
this.gl.deleteShader(shader);
|
||||||
|
throw new Error('Unable to compile shader: ' + err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAttribLocation(program: WebGLProgram, name: string): number {
|
||||||
|
const ret = this.gl.getAttribLocation(program, name);
|
||||||
|
if (ret < 0) throw new Error(`unable to get location of attribute '${name}'`);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUniformLocation(program: WebGLProgram, name: string): WebGLUniformLocation {
|
||||||
|
const ret = this.gl.getUniformLocation(program, name);
|
||||||
|
if (!ret) throw new Error(`unable to get location of uniform '${name}'`);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
10
src/renderer/runtime/view/renderer/shaders/default.fs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
varying vec2 vTextureCoord;
|
||||||
|
|
||||||
|
uniform sampler2D uSampler;
|
||||||
|
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = texture2D(uSampler, vTextureCoord);
|
||||||
|
}
|
13
src/renderer/runtime/view/renderer/shaders/default.vs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
attribute vec2 aVertexPos;
|
||||||
|
attribute vec2 aTextureCoord;
|
||||||
|
|
||||||
|
uniform mat4 uViewport;
|
||||||
|
uniform vec2 uTranslate;
|
||||||
|
|
||||||
|
varying highp vec2 vTextureCoord;
|
||||||
|
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_Position = uViewport * vec4(aVertexPos + uTranslate, 0.0, 1.0);
|
||||||
|
vTextureCoord = aTextureCoord;
|
||||||
|
}
|
78
src/renderer/runtime/view/sprite.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import { Renderer } from './renderer/renderer';
|
||||||
|
|
||||||
|
export type SpriteCoords = [number, number, number, number];
|
||||||
|
|
||||||
|
export class SpriteView {
|
||||||
|
private readonly vertexCount: number;
|
||||||
|
private readonly vertexBuffer: WebGLBuffer;
|
||||||
|
private readonly textureBuffer: WebGLBuffer;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly r: Renderer,
|
||||||
|
private readonly texture: WebGLTexture,
|
||||||
|
vertexData: number[],
|
||||||
|
textureData: number[],
|
||||||
|
) {
|
||||||
|
const gl = r.getContext();
|
||||||
|
|
||||||
|
this.vertexBuffer = r.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
this.textureBuffer = r.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureData), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
this.vertexCount = vertexData.length / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
const gl = this.r.getContext();
|
||||||
|
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||||
|
gl.uniform1i(this.r.getSamplerLoc(), 0);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
|
||||||
|
gl.vertexAttribPointer(this.r.getVertexPosLoc(), 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer);
|
||||||
|
gl.vertexAttribPointer(this.r.getTextureCoordLoc(), 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SpriteViewBuilder {
|
||||||
|
private static pushSprite(buf: number[], coords: SpriteCoords): void {
|
||||||
|
const [x1, y1, x2, y2] = coords;
|
||||||
|
|
||||||
|
buf.push(x1);
|
||||||
|
buf.push(y1);
|
||||||
|
buf.push(x2);
|
||||||
|
buf.push(y1);
|
||||||
|
buf.push(x1);
|
||||||
|
buf.push(y2);
|
||||||
|
|
||||||
|
buf.push(x1);
|
||||||
|
buf.push(y2);
|
||||||
|
buf.push(x2);
|
||||||
|
buf.push(y1);
|
||||||
|
buf.push(x2);
|
||||||
|
buf.push(y2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly vertexData: number[] = [];
|
||||||
|
private readonly textureData: number[] = [];
|
||||||
|
|
||||||
|
constructor(private readonly r: Renderer, private readonly texture: WebGLTexture) {}
|
||||||
|
|
||||||
|
public addSprite(vertexCoords: SpriteCoords, texCoords: SpriteCoords): void {
|
||||||
|
SpriteViewBuilder.pushSprite(this.vertexData, vertexCoords);
|
||||||
|
SpriteViewBuilder.pushSprite(this.textureData, texCoords);
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(): SpriteView {
|
||||||
|
return new SpriteView(this.r, this.texture, this.vertexData, this.textureData);
|
||||||
|
}
|
||||||
|
}
|
31
src/renderer/runtime/view/util/image.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { Renderer } from '../renderer/renderer';
|
||||||
|
|
||||||
|
export function loadImage(url: string): Promise<HTMLImageElement> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.addEventListener('load', () => {
|
||||||
|
resolve(img);
|
||||||
|
});
|
||||||
|
img.addEventListener('error', () => {
|
||||||
|
reject(new Error('failed to load ' + url));
|
||||||
|
});
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mkTexture(r: Renderer, src: HTMLCanvasElement | HTMLImageElement): [WebGLTexture, [number, number]] {
|
||||||
|
const gl = r.getContext();
|
||||||
|
const texture = gl.createTexture();
|
||||||
|
if (!texture) throw new Error('unable to create texture');
|
||||||
|
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
|
||||||
|
const size: [number, number] = [src.width / r.coordScale, src.height / r.coordScale];
|
||||||
|
|
||||||
|
return [texture, size];
|
||||||
|
}
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "control/RPGEdit.hpp"
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
#include <SDL_image.h>
|
|
||||||
#include <SDL_main.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
int main(__attribute__((unused)) int argc, __attribute__((unused)) char *argv[]) {
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
|
||||||
std::cerr << "Unable to initialize SDL" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
|
|
||||||
std::cerr << "Unable to initialize PNG loader" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
RPGEdit::Control::RPGEdit().run();
|
|
||||||
|
|
||||||
IMG_Quit();
|
|
||||||
SDL_Quit();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "MapView.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace View {
|
|
||||||
|
|
||||||
MapView::MapView(const std::shared_ptr<Window> &window0, const std::vector<std::pair<std::string, int>> &tileset)
|
|
||||||
: window(window0) {
|
|
||||||
uint32_t rmask, gmask, bmask, amask;
|
|
||||||
|
|
||||||
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
||||||
rmask = 0xff000000;
|
|
||||||
gmask = 0x00ff0000;
|
|
||||||
bmask = 0x0000ff00;
|
|
||||||
amask = 0x000000ff;
|
|
||||||
#else
|
|
||||||
rmask = 0x000000ff;
|
|
||||||
gmask = 0x0000ff00;
|
|
||||||
bmask = 0x00ff0000;
|
|
||||||
amask = 0xff000000;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SpriteCache *spriteCache = window->getSpriteCache();
|
|
||||||
|
|
||||||
SDL_Surface *surface = SDL_CreateRGBSurface(0, getTileSize()*tileset.size(), getTileSize(), 32, rmask, gmask, bmask, amask);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < tileset.size(); i++) {
|
|
||||||
SDL_Rect rect = {
|
|
||||||
.x = int(getTileSize()*i),
|
|
||||||
.y = 0,
|
|
||||||
.w = 0,
|
|
||||||
.h = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_Surface *sprite = spriteCache->get("tile", tileset[i].first, tileset[i].second);
|
|
||||||
|
|
||||||
SDL_SetSurfaceBlendMode(sprite, SDL_BLENDMODE_NONE);
|
|
||||||
SDL_BlitSurface(sprite, nullptr, surface, &rect);
|
|
||||||
SDL_SetSurfaceBlendMode(sprite, SDL_BLENDMODE_BLEND);
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles = SDL_CreateTextureFromSurface(window->getRenderer(), surface);
|
|
||||||
SDL_FreeSurface(surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
MapView::~MapView() {
|
|
||||||
SDL_DestroyTexture(tiles);
|
|
||||||
|
|
||||||
clearEntities();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapView::updateEntities(const std::vector<std::unique_ptr<Model::Entity>> &entities) {
|
|
||||||
SpriteCache *spriteCache = window->getSpriteCache();
|
|
||||||
|
|
||||||
for (auto &entity : entities) {
|
|
||||||
const std::string &name = entity->getName();
|
|
||||||
|
|
||||||
if (!entitySprites[name])
|
|
||||||
entitySprites[name] = SDL_CreateTextureFromSurface(window->getRenderer(), spriteCache->get("entity", name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapView::clearEntities() {
|
|
||||||
for (auto &entity : entitySprites)
|
|
||||||
SDL_DestroyTexture(entity.second);
|
|
||||||
|
|
||||||
entitySprites.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MapView::render(const Model::Map *map, Model::Position<float> center, uint64_t time) {
|
|
||||||
std::pair<int, int> viewport = window->getViewport();
|
|
||||||
|
|
||||||
float pixels = std::max(viewport.first/20.0f, viewport.second/15.0f);
|
|
||||||
int tilePixels = getTileSize() * std::ceil(pixels / getTileSize());
|
|
||||||
|
|
||||||
float tilesW = viewport.first / tilePixels;
|
|
||||||
float tilesH = viewport.second / tilePixels;
|
|
||||||
|
|
||||||
int minX = std::floor(center.x - tilesW/2 - 0.5f), maxX = std::ceil(center.x + tilesW/2 + 0.5f);
|
|
||||||
int minY = std::floor(center.y - tilesH/2 - 0.5f), maxY = std::ceil(center.y + tilesH/2 + 0.5f);
|
|
||||||
|
|
||||||
int baseX = viewport.first/2 - int(center.x * tilePixels) - tilePixels/2, baseY = viewport.second/2 - int(center.y * tilePixels) - tilePixels/2;
|
|
||||||
|
|
||||||
for (size_t layer = 0; layer < map->getLayerCount(); layer++) {
|
|
||||||
for (int x = minX; x <= maxX; x++) {
|
|
||||||
for (int y = minY; y <= maxY; y++) {
|
|
||||||
uint32_t tile = map->getTileAt(layer, Model::Position<int>{x, y});
|
|
||||||
if (!tile)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
SDL_Rect src = {
|
|
||||||
.x = int(getTileSize()*(tile-1)),
|
|
||||||
.y = 0,
|
|
||||||
.w = getTileSize(),
|
|
||||||
.h = getTileSize(),
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_Rect dst = {
|
|
||||||
.x = baseX + x*tilePixels,
|
|
||||||
.y = baseY + y*tilePixels,
|
|
||||||
.w = tilePixels,
|
|
||||||
.h = tilePixels,
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_RenderCopy(window->getRenderer(), tiles, &src, &dst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &entity : map->getEntities()) {
|
|
||||||
Model::Position<float> pos = map->getEntityPosition(entity.get(), time);
|
|
||||||
Model::Direction dir = entity->getDirection();
|
|
||||||
|
|
||||||
SDL_Rect src = {
|
|
||||||
.x = getTileSize()*int(dir),
|
|
||||||
.y = 0,
|
|
||||||
.w = getTileSize(),
|
|
||||||
.h = getTileSize(),
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_Rect dst = {
|
|
||||||
.x = baseX + int(pos.x * tilePixels),
|
|
||||||
.y = baseY + int(pos.y * tilePixels),
|
|
||||||
.w = tilePixels,
|
|
||||||
.h = tilePixels,
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_RenderCopy(window->getRenderer(), entitySprites[entity->getName()], &src, &dst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Window.hpp"
|
|
||||||
#include "../model/Map.hpp"
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <map>
|
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace View {
|
|
||||||
|
|
||||||
class MapView {
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Window> window;
|
|
||||||
|
|
||||||
SDL_Texture *tiles;
|
|
||||||
std::map<std::string, SDL_Texture *> entitySprites;
|
|
||||||
|
|
||||||
int getTileSize() {
|
|
||||||
return 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
MapView(const std::shared_ptr<Window> &window0, const std::vector<std::pair<std::string, int>> &tileset);
|
|
||||||
~MapView();
|
|
||||||
|
|
||||||
void updateEntities(const std::vector<std::unique_ptr<Model::Entity>> &entities);
|
|
||||||
void clearEntities();
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
void render(const Model::Map *map, Model::Position<float> center, uint64_t time);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "SpriteCache.hpp"
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include <SDL_image.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace View {
|
|
||||||
|
|
||||||
SpriteCache::sprite_value SpriteCache::load(const std::string &id) {
|
|
||||||
std::string filename = "../resources/sprite/" + id + ".png";
|
|
||||||
|
|
||||||
SDL_Surface *surface = IMG_Load(filename.c_str());
|
|
||||||
|
|
||||||
return sprite_value(surface, SDL_Surface_deleter());
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteCache::sprite_value SpriteCache::load(const std::string &id, unsigned rotation) {
|
|
||||||
if (!rotation)
|
|
||||||
return load(id);
|
|
||||||
|
|
||||||
sprite_value &base = get(id, 0);
|
|
||||||
SDL_Surface *surface;
|
|
||||||
|
|
||||||
SDL_LockSurface(base.get());
|
|
||||||
|
|
||||||
int w = base->w, h = base->h, d = base->format->BytesPerPixel, w2, h2;
|
|
||||||
|
|
||||||
if (rotation == 2) {
|
|
||||||
w2 = w;
|
|
||||||
h2 = h;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
w2 = h;
|
|
||||||
h2 = w;
|
|
||||||
}
|
|
||||||
|
|
||||||
surface = SDL_CreateRGBSurface(0, w2, h2, base->format->BitsPerPixel,
|
|
||||||
base->format->Rmask, base->format->Gmask, base->format->Bmask, base->format->Amask);
|
|
||||||
|
|
||||||
SDL_LockSurface(surface);
|
|
||||||
|
|
||||||
uint8_t *src = reinterpret_cast<uint8_t *>(base->pixels);
|
|
||||||
uint8_t *dst = reinterpret_cast<uint8_t *>(surface->pixels);
|
|
||||||
int pitch = base->pitch;
|
|
||||||
int pitch2 = surface->pitch;
|
|
||||||
|
|
||||||
for (int x = 0; x < w; x++) {
|
|
||||||
for (int y = 0; y < h; y++) {
|
|
||||||
int x2, y2;
|
|
||||||
|
|
||||||
switch (rotation) {
|
|
||||||
case 1:
|
|
||||||
x2 = y;
|
|
||||||
y2 = w - x - 1;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
x2 = w - x - 1;
|
|
||||||
y2 = h - y - 1;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
x2 = h - y - 1;
|
|
||||||
y2 = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(dst + y2*pitch2 + d*x2, src + y*pitch + d*x, d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_UnlockSurface(surface);
|
|
||||||
SDL_UnlockSurface(base.get());
|
|
||||||
|
|
||||||
return sprite_value(surface, SDL_Surface_deleter());
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteCache::sprite_value & SpriteCache::get(const std::string &id, unsigned rotation) {
|
|
||||||
sprite_key key(id, rotation);
|
|
||||||
|
|
||||||
sprite_value &surface = sprites[key];
|
|
||||||
|
|
||||||
if (!surface)
|
|
||||||
surface = load(id, rotation);
|
|
||||||
|
|
||||||
if (!surface)
|
|
||||||
sprites.erase(sprites.find(key));
|
|
||||||
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Surface * SpriteCache::get(const std::string &type, const std::string &name, unsigned rotation) {
|
|
||||||
if (rotation >= 4)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return get(type + "/" + name, rotation).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace View {
|
|
||||||
|
|
||||||
class SpriteCache {
|
|
||||||
private:
|
|
||||||
struct SDL_Surface_deleter {
|
|
||||||
void operator()(SDL_Surface *surface) const {
|
|
||||||
SDL_FreeSurface(surface);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::pair<std::string, int> sprite_key;
|
|
||||||
typedef std::unique_ptr<SDL_Surface, SDL_Surface_deleter> sprite_value;
|
|
||||||
|
|
||||||
struct sprite_key_hash {
|
|
||||||
size_t operator()(const sprite_key &k) const {
|
|
||||||
std::hash<std::string> string_hash;
|
|
||||||
std::hash<int> int_hash;
|
|
||||||
|
|
||||||
return string_hash(k.first) ^ int_hash(k.second);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unordered_map<sprite_key, sprite_value, sprite_key_hash> sprites;
|
|
||||||
|
|
||||||
sprite_value load(const std::string &id);
|
|
||||||
sprite_value load(const std::string &id, unsigned rotation);
|
|
||||||
sprite_value & get(const std::string &id, unsigned rotation);
|
|
||||||
|
|
||||||
public:
|
|
||||||
SDL_Surface * get(const std::string &type, const std::string &name, unsigned rotation = 0);
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
sprites.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (c) 2014-2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "SpriteCache.hpp"
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace RPGEdit {
|
|
||||||
|
|
||||||
namespace View {
|
|
||||||
|
|
||||||
class Window {
|
|
||||||
private:
|
|
||||||
SpriteCache spriteCache;
|
|
||||||
|
|
||||||
SDL_Window *window;
|
|
||||||
SDL_Renderer *renderer;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Window() {
|
|
||||||
window = SDL_CreateWindow("RPGedit", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
|
|
||||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC);
|
|
||||||
}
|
|
||||||
|
|
||||||
~Window() {
|
|
||||||
SDL_DestroyRenderer(renderer);
|
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteCache * getSpriteCache() {
|
|
||||||
return &spriteCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Renderer * getRenderer() {
|
|
||||||
return renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<int, int> getViewport() {
|
|
||||||
int w, h;
|
|
||||||
SDL_GetWindowSize(window, &w, &h);
|
|
||||||
|
|
||||||
return std::pair<int, int>(w, h);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
3
static/resources/entity/green_circle.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"sprite": "green_circle"
|
||||||
|
}
|
10
static/resources/entity/red_circle.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"sprite": "red_circle",
|
||||||
|
"collision": [
|
||||||
|
{
|
||||||
|
"type": "circle",
|
||||||
|
"center": [0, 0],
|
||||||
|
"radius": 0.46875
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
static/resources/entity/red_ellipse.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"sprite": "red_ellipse"
|
||||||
|
}
|
23
static/resources/entity/square.json
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"sprite": "square",
|
||||||
|
"collision": [
|
||||||
|
{
|
||||||
|
"type": "polygon",
|
||||||
|
"vertices": [
|
||||||
|
[-0.46875, -0.46875],
|
||||||
|
[-0.46875, 0.46875],
|
||||||
|
[0.46875, 0.46875],
|
||||||
|
[0.46875, -0.46875]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"frames": 4,
|
||||||
|
"animation": {
|
||||||
|
"sequence": [
|
||||||
|
[500, 0],
|
||||||
|
[500, 1],
|
||||||
|
[500, 2],
|
||||||
|
[500, 3]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
12
static/resources/entity/water.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"sprite": "water",
|
||||||
|
"frames": 4,
|
||||||
|
"animation": {
|
||||||
|
"sequence": [
|
||||||
|
[500, 0],
|
||||||
|
[500, 1],
|
||||||
|
[500, 2],
|
||||||
|
[500, 3]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
84
static/resources/map/test.json
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
{
|
||||||
|
"tiles": [
|
||||||
|
"-stone/floor",
|
||||||
|
"-stone/plate",
|
||||||
|
"-stone/wall/top",
|
||||||
|
"-stone/wall/right",
|
||||||
|
"-stone/wall/bottom",
|
||||||
|
"-stone/wall/left",
|
||||||
|
"-stone/wall/top_left",
|
||||||
|
"-stone/wall/top_right",
|
||||||
|
"-stone/wall/bottom_right",
|
||||||
|
"-stone/wall/bottom_left",
|
||||||
|
"-stone/wall/top_left_inner",
|
||||||
|
"-stone/wall/top_right_inner",
|
||||||
|
"-stone/wall/bottom_right_inner",
|
||||||
|
"-stone/wall/bottom_left_inner",
|
||||||
|
"@water",
|
||||||
|
"-stone/border/bottom_right",
|
||||||
|
"-stone/border/bottom_left"
|
||||||
|
],
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"tiles": [
|
||||||
|
[0, 7, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 0],
|
||||||
|
[0, 6, 1, 1, 1, 1, 15, 15, 1, 1, 1, 1, 4, 0],
|
||||||
|
[0, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0],
|
||||||
|
[0, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0],
|
||||||
|
[3, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 3],
|
||||||
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
[5, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 5],
|
||||||
|
[0, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0],
|
||||||
|
[0, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0],
|
||||||
|
[0, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0],
|
||||||
|
[0, 10, 5, 5, 5, 14, 1, 1, 13, 5, 5, 5, 9, 0],
|
||||||
|
[0, 0, 0, 0, 0, 6, 1, 1, 4, 0, 0, 0, 0, 0]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tiles": [
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 17, 16, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
|
||||||
|
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
|
||||||
|
[0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"collision": [
|
||||||
|
{
|
||||||
|
"type": "polygon",
|
||||||
|
"vertices": [
|
||||||
|
[ 2, 1],
|
||||||
|
[ 6, 1],
|
||||||
|
[ 6, 2],
|
||||||
|
[ 8, 2],
|
||||||
|
[ 8, 1],
|
||||||
|
[12, 1],
|
||||||
|
[12, 5],
|
||||||
|
[14, 5],
|
||||||
|
[14, 7],
|
||||||
|
[12, 7],
|
||||||
|
[12, 11],
|
||||||
|
[ 8, 11],
|
||||||
|
[ 8, 13],
|
||||||
|
[ 6, 13],
|
||||||
|
[ 6, 11],
|
||||||
|
[ 2, 11],
|
||||||
|
[ 2, 7],
|
||||||
|
[ 0, 7],
|
||||||
|
[ 0, 5],
|
||||||
|
[ 2, 5]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
static/resources/sprite/entity/green_circle.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
static/resources/sprite/entity/red_circle.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
static/resources/sprite/entity/red_ellipse.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
static/resources/sprite/entity/simple_square.png
Normal file
After Width: | Height: | Size: 155 B |
BIN
static/resources/sprite/entity/square.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
static/resources/sprite/entity/water.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
static/resources/sprite/tile/dirt.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
static/resources/sprite/tile/grass.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
static/resources/sprite/tile/road_left.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/resources/sprite/tile/road_right.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/resources/sprite/tile/stone/border/bottom_left.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
static/resources/sprite/tile/stone/border/bottom_right.png
Normal file
After Width: | Height: | Size: 927 B |
BIN
static/resources/sprite/tile/stone/floor.png
Normal file
After Width: | Height: | Size: 765 B |
BIN
static/resources/sprite/tile/stone/plate.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/resources/sprite/tile/stone/wall/bottom.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/resources/sprite/tile/stone/wall/bottom_left.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
static/resources/sprite/tile/stone/wall/bottom_left_inner.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/resources/sprite/tile/stone/wall/bottom_right.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
static/resources/sprite/tile/stone/wall/bottom_right_inner.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/resources/sprite/tile/stone/wall/left.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
static/resources/sprite/tile/stone/wall/right.png
Normal file
After Width: | Height: | Size: 1.5 KiB |