4using Quarter = std::array<uint8_t, 8>;
5constexpr std::array<Quarter, 48> kRpgFloor = {
6 {{{2, 4, 1, 4, 2, 3, 1, 3}}, {{2, 0, 1, 4, 2, 3, 1, 3}}, {{2, 4, 3, 0, 2, 3, 1, 3}}, {{2, 0, 3, 0, 2, 3, 1, 3}},
7 {{2, 4, 1, 4, 2, 3, 3, 1}}, {{2, 0, 1, 4, 2, 3, 3, 1}}, {{2, 4, 3, 0, 2, 3, 3, 1}}, {{2, 0, 3, 0, 2, 3, 3, 1}},
8 {{2, 4, 1, 4, 2, 1, 1, 3}}, {{2, 0, 1, 4, 2, 1, 1, 3}}, {{2, 4, 3, 0, 2, 1, 1, 3}}, {{2, 0, 3, 0, 2, 1, 1, 3}},
9 {{2, 4, 1, 4, 2, 1, 3, 1}}, {{2, 0, 1, 4, 2, 1, 3, 1}}, {{2, 4, 3, 0, 2, 1, 3, 1}}, {{2, 0, 3, 0, 2, 1, 3, 1}},
10 {{0, 4, 1, 4, 0, 3, 1, 3}}, {{0, 4, 3, 0, 0, 3, 1, 3}}, {{0, 4, 1, 4, 0, 3, 3, 1}}, {{0, 4, 3, 0, 0, 3, 3, 1}},
11 {{2, 2, 1, 2, 2, 3, 1, 3}}, {{2, 2, 1, 2, 2, 3, 3, 1}}, {{2, 2, 1, 2, 2, 1, 1, 3}}, {{2, 2, 1, 2, 2, 1, 3, 1}},
12 {{2, 4, 3, 4, 2, 3, 3, 3}}, {{2, 4, 3, 4, 2, 1, 3, 3}}, {{2, 0, 3, 4, 2, 3, 3, 3}}, {{2, 0, 3, 4, 2, 1, 3, 3}},
13 {{2, 4, 1, 4, 2, 5, 1, 5}}, {{2, 0, 1, 4, 2, 5, 1, 5}}, {{2, 4, 3, 0, 2, 5, 1, 5}}, {{2, 0, 3, 0, 2, 5, 1, 5}},
14 {{0, 4, 3, 4, 0, 3, 3, 3}}, {{2, 2, 1, 2, 2, 5, 1, 5}}, {{0, 2, 1, 2, 0, 3, 1, 3}}, {{0, 2, 1, 2, 0, 3, 3, 1}},
15 {{2, 2, 3, 2, 2, 3, 3, 3}}, {{2, 2, 3, 2, 2, 1, 3, 3}}, {{2, 4, 3, 4, 2, 5, 3, 5}}, {{2, 0, 3, 4, 2, 5, 3, 5}},
16 {{0, 4, 1, 4, 0, 5, 1, 5}}, {{0, 4, 3, 0, 0, 5, 1, 5}}, {{0, 2, 3, 2, 0, 3, 3, 3}}, {{0, 2, 1, 2, 0, 5, 1, 5}},
17 {{0, 4, 3, 4, 0, 5, 3, 5}}, {{2, 2, 3, 2, 2, 5, 3, 5}}, {{0, 2, 3, 2, 0, 5, 3, 5}}, {{0, 0, 1, 0, 0, 1, 1, 1}}}};
18constexpr std::array<Quarter, 16> kRpgWall = {{{{2, 2, 1, 2, 2, 1, 1, 1}},
19 {{0, 2, 1, 2, 0, 1, 1, 1}},
20 {{2, 0, 1, 0, 2, 1, 1, 1}},
21 {{0, 0, 1, 0, 0, 1, 1, 1}},
22 {{2, 2, 3, 2, 2, 1, 3, 1}},
23 {{0, 2, 3, 2, 0, 1, 3, 1}},
24 {{2, 0, 3, 0, 2, 1, 3, 1}},
25 {{0, 0, 3, 0, 0, 1, 3, 1}},
26 {{2, 2, 1, 2, 2, 3, 1, 3}},
27 {{0, 2, 1, 2, 0, 3, 1, 3}},
28 {{2, 0, 1, 0, 2, 3, 1, 3}},
29 {{0, 0, 1, 0, 0, 3, 1, 3}},
30 {{2, 2, 3, 2, 2, 3, 3, 3}},
31 {{0, 2, 3, 2, 0, 3, 3, 3}},
32 {{2, 0, 3, 0, 2, 3, 3, 3}},
33 {{0, 0, 3, 0, 0, 3, 3, 3}}}};
34constexpr std::array<Quarter, 4> kRpgWaterfall = {
35 {{{2, 0, 1, 0, 2, 1, 1, 1}}, {{0, 0, 1, 0, 0, 1, 1, 1}}, {{2, 0, 3, 0, 2, 1, 3, 1}}, {{0, 0, 3, 0, 0, 1, 3, 1}}}};
37TileLayer::Tileset::Visual rpgMakerVisual(
int gid) {
38 TileLayer::Tileset::Visual visual;
40 constexpr int tile = 48, quarter = 24, a1 = 2048, a2 = 2816, a3 = 4352, a4 = 5888;
42 visual.x = ((gid / 128 % 2) * 8 + gid % 8) * tile;
43 visual.y = ((gid % 256 / 8) % 16) * tile;
48 const int kind = (gid - a1) / 48;
49 const int shape = (gid - a1) % 48;
50 const int tx =
kind % 8, ty =
kind / 8;
54 bool waterSurface =
false, waterfall =
false;
60 }
else if (
kind == 1) {
64 }
else if (
kind == 2) {
67 }
else if (
kind == 3) {
72 by = ty * 6 + (tx / 2 % 2) * 3;
80 if (waterSurface) frameCount = 4;
81 if (waterfall) frameCount = 3;
82 table = waterfall ? &kRpgWaterfall[size_t(
shape % 4)] : &kRpgFloor[size_t(
shape)];
83 }
else if (gid < a3) {
87 }
else if (gid < a4) {
93 by = int((ty - 10) * 2.5f + (ty % 2 == 1 ? 0.5f : 0.f));
94 table = ty % 2 == 1 ? &kRpgWall[size_t(
shape % 16)] : &kRpgFloor[size_t(
shape)];
96 const int waterIndices[4] = {0, 1, 2, 1};
97 for (
int frame = 0; frame < frameCount; ++frame) {
98 TileLayer::Tileset::Visual::SubtileFrame
output;
100 int frameBx =
bx, frameBy =
by;
101 if (waterSurface) frameBx += waterIndices[frame] * 2;
102 if (waterfall) frameBy += frame;
103 for (
int part = 0; part < 4; ++part) {
104 output.parts.push_back({(frameBx * 2 + (*table)[size_t(part * 2)]) * quarter,
105 (frameBy * 2 + (*table)[size_t(part * 2 + 1)]) * quarter, quarter, quarter,
106 float((part % 2) * quarter),
float((part / 2) * quarter)});
108 visual.subtileFrames.push_back(std::move(
output));
117 const std::string &sourceEngine) {
118 auto fail = [](
const std::string &
code,
const std::string &
message,
const std::string &
path) {
122 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>(
"Filesystem");
123 if (!fs) fs = eve::filesystem::Filesystem::create();
124 std::string mapText, tilesetsText;
125 if (!readImportText(fs, mapPath, mapText))
126 return fail(
"map.import.rpgmaker.map-read-failed",
"Could not read RPG Maker map", mapPath);
127 if (!readImportText(fs, tilesetsPath, tilesetsText))
128 return fail(
"map.import.rpgmaker.tilesets-read-failed",
"Could not read Tilesets.json", tilesetsPath);
130 std::string decodeError;
132 if (!mapDocument.valid() || !mapDocument.root().isObject())
133 return fail(
"map.import.rpgmaker.map-invalid", decodeError, mapPath);
135 if (!tilesetsDocument.valid() || !tilesetsDocument.root().isArray())
136 return fail(
"map.import.rpgmaker.tilesets-invalid", decodeError, tilesetsPath);
138 const Json map = mapDocument.root();
139 const int width = map.has(
"width") ? map.getInt(
"width", 0) : 0;
140 const int height = map.has(
"height") ? map.getInt(
"height", 0) : 0;
141 const int tilesetId = map.has(
"tilesetId") ? map.getInt(
"tilesetId", 0) : 0;
142 const Json rawData = map.get(
"data");
143 const size_t planeSize = size_t(std::max(0,
width) * std::max(0,
height));
144 if (
width <= 0 ||
height <= 0 || !rawData.isArray() || rawData.size() < planeSize * 4)
145 return fail(
"map.import.rpgmaker.map-shape-invalid",
"Map data must contain four tile planes", mapPath);
147 const Json tilesets = tilesetsDocument.root();
149 if (tilesetId > 0 &&
size_t(tilesetId) < tilesets.size()) tileset = tilesets.at(
size_t(tilesetId));
150 if (!tileset.isObject())
151 return fail(
"map.import.rpgmaker.tileset-not-found",
"Map tilesetId is not present", tilesetsPath);
153 const Json names = tileset.get(
"tilesetNames");
154 const Json flags = tileset.get(
"flags");
155 if (!names.isArray() || names.size() < 9 || !flags.isArray())
156 return fail(
"map.import.rpgmaker.tileset-shape-invalid",
"Tileset requires nine sheet names and passage flags",
159 std::array<std::vector<uint32_t>, 4>
planes;
160 for (
int z = 0;
z < 4; ++
z) {
161 planes[size_t(
z)].resize(planeSize);
163 planes[
size_t(
z)][
cell] = asUInt32(rawData.at(
size_t(
z) * planeSize +
cell), 0);
171 constexpr std::array<SheetSpec, 9> sheets = {{{5, 1, 16},
180 const std::filesystem::path projectRoot = std::filesystem::path(tilesetsPath).parent_path().parent_path();
181 std::vector<TilesetInfo> infos;
182 for (
const auto &sheet : sheets) {
183 const std::string
name = names.at(
size_t(sheet.nameIndex)).asString();
184 if (
name.empty())
continue;
186 info.firstGid = sheet.firstGid;
187 info.columns = sheet.columns;
190 info.image = (projectRoot /
"img" /
"tilesets" / (
name +
".png")).lexically_normal().generic_string();
191 infos.push_back(std::move(
info));
194 return fail(
"map.import.rpgmaker.tileset-images-empty",
"Tileset has no named sheets", tilesetsPath);
196 RpgMakerImportReceipt receipt;
197 receipt.sourceEngine = sourceEngine;
198 receipt.tilesetId = tilesetId;
199 constexpr uint32_t kStar = 1u << 0, kLadder = 1u << 1, kBush = 1u << 2, kCounter = 1u << 3, kDamage = 1u << 4;
200 for (
int z = 0;
z < 4; ++
z) {
203 applyTilesets(
layer, infos);
205 layer->resource()->path = mapPath;
206 layer->resource()->sourceEngine = sourceEngine;
207 layer->resource()->sourceVersion =
"MV/MZ";
208 layer->resource()->dependencyPaths = {tilesetsPath};
209 std::vector<int> configuredGids;
210 for (uint32_t rawGid :
planes[size_t(
z)]) {
211 const int gid = int(tileGid(rawGid));
212 if (gid <= 0 ||
size_t(gid) >= flags.size())
continue;
213 if (std::find(configuredGids.begin(), configuredGids.end(), gid) != configuredGids.end())
continue;
214 configuredGids.push_back(gid);
215 layer->tileset()->visuals.push_back(rpgMakerVisual(gid));
216 const int flag = flags.at(
size_t(gid)).asInt(0);
218 if (flag & 0x08)
directions &= uint8_t(~0x01);
219 if (flag & 0x04)
directions &= uint8_t(~0x02);
220 if (flag & 0x01)
directions &= uint8_t(~0x04);
221 if (flag & 0x02)
directions &= uint8_t(~0x08);
222 const bool star = (flag & 0x10) != 0;
223 const bool walkable = star ||
directions != 0;
224 uint32_t semantics = 0;
225 if (star) semantics |= kStar;
226 if (flag & 0x20) semantics |= kLadder;
227 if (flag & 0x40) semantics |= kBush;
228 if (flag & 0x80) semantics |= kCounter;
229 if (flag & 0x100) semantics |= kDamage;
230 semantics |= uint32_t((flag >> 12) & 0x0f) << 8;
234 layer->rebuildSpatialIndex();
235 receipt.layers.push_back(
layer);
238 receipt.navigationLayer = TileLayer::createLayer(
width,
height, 48.f, 48.f);
239 receipt.navigationLayer->setVisible(
false);
240 receipt.navigationLayer->resource()->path = mapPath;
241 receipt.navigationLayer->resource()->sourceEngine = sourceEngine;
242 receipt.navigationLayer->resource()->sourceVersion =
"MV/MZ navigation projection";
245 uint32_t semantics = 0;
247 std::vector<std::pair<ProfileKey, int>> profiles;
248 auto &navigationGids = receipt.navigationLayer->tiles()->gids;
251 profile.directions = 0;
252 constexpr int rpgBits[4] = {0x08, 0x04, 0x01, 0x02};
253 constexpr uint8_t evBits[4] = {0x01, 0x02, 0x04, 0x08};
256 for (
int z = 3;
z >= 0; --
z) {
257 const int gid = int(tileGid(
planes[
size_t(
z)][
cell]));
258 if (gid <= 0 ||
size_t(gid) >= flags.size())
continue;
259 const int flag = flags.at(
size_t(gid)).asInt(0);
260 if (flag & 0x10)
continue;
261 allowed = (flag & rpgBits[
direction]) == 0;
264 if (allowed) profile.directions |= evBits[
direction];
266 for (
int z = 0;
z < 4; ++
z) {
267 const int gid = int(tileGid(
planes[
size_t(
z)][
cell]));
268 if (gid <= 0 ||
size_t(gid) >= flags.size())
continue;
269 const int flag = flags.at(
size_t(gid)).asInt(0);
270 if (flag & 0x10) profile.semantics |= kStar;
271 if (flag & 0x20) profile.semantics |= kLadder;
272 if (flag & 0x40) profile.semantics |= kBush;
273 if (flag & 0x80) profile.semantics |= kCounter;
274 if (flag & 0x100) profile.semantics |= kDamage;
275 profile.semantics |= uint32_t((flag >> 12) & 0x0f) << 8;
277 auto found = std::find_if(profiles.begin(), profiles.end(), [&](
const auto &entry) {
278 return entry.first.directions == profile.directions && entry.first.semantics == profile.semantics;
281 if (
found == profiles.end()) {
282 profileGid = 9000 + int(profiles.size());
283 profiles.push_back({profile, profileGid});
284 receipt.navigationLayer->setTileNavigationProfile(profileGid, profile.directions != 0, 1.f,
285 profile.directions, profile.directions,
286 profile.directions == 0,
int(profile.semantics));
288 profileGid =
found->second;
290 navigationGids[
cell] = uint32_t(profileGid);
292 receipt.navigationLayer->rebuildSpatialIndex();
294 if (rawData.size() >= planeSize * 5) {
295 bool hasShadow =
false;
297 hasShadow = hasShadow || (rawData.at(planeSize * 4 +
cell).asInt(0) & 0x0f);
299 receipt.shadowLayer = TileLayer::createLayer(
width * 2,
height * 2, 24.f, 24.f);
300 receipt.shadowLayer->setLayer(2);
301 receipt.shadowLayer->setTint(0.f, 0.f, 0.f, 0.5f);
303 const int bits = rawData.at(planeSize * 4 +
cell).asInt(0) & 0x0f;
305 for (
int part = 0; part < 4; ++part)
306 if (bits & (1 << part)) receipt.shadowLayer->setTile(
x * 2 + part % 2,
y * 2 + part / 2, 1);
308 receipt.shadowLayer->resource()->path = mapPath;
309 receipt.shadowLayer->resource()->sourceEngine = sourceEngine;
310 receipt.shadowLayer->resource()->sourceVersion =
"MV/MZ shadow projection";
#define EVENGINE_API_WORLD
RoadLaneDirection direction
TileLayer::Tileset::Visual decodeRpgMakerTileVisual(int tileId)
EVENGINE_API_WORLD eve::Result< RpgMakerImportReceipt > importRpgMakerMap(const std::string &mapPath, const std::string &tilesetsPath, const std::string &sourceEngine)
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
std::unordered_map< std::string, SkillDefinition > & table()