载入中...
搜索中...
未找到
RpgMakerTileImporter.inl
浏览该文件的文档.
1// RPG Maker MV/MZ adapter implementation split from TileConfig.cpp.
2// Included inside namespace eve::map so it can reuse the import parser helpers.
3namespace {
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}}}};
36
37TileLayer::Tileset::Visual rpgMakerVisual(int gid) {
38 TileLayer::Tileset::Visual visual;
39 visual.gid = gid;
40 constexpr int tile = 48, quarter = 24, a1 = 2048, a2 = 2816, a3 = 4352, a4 = 5888;
41 if (gid < a1) {
42 visual.x = ((gid / 128 % 2) * 8 + gid % 8) * tile;
43 visual.y = ((gid % 256 / 8) % 16) * tile;
44 visual.width = tile;
45 visual.height = tile;
46 return visual;
47 }
48 const int kind = (gid - a1) / 48;
49 const int shape = (gid - a1) % 48;
50 const int tx = kind % 8, ty = kind / 8;
51 int bx = 0, by = 0;
52 const Quarter *table = nullptr;
53 int frameCount = 1;
54 bool waterSurface = false, waterfall = false;
55 if (gid < a2) {
56 if (kind == 0) {
57 bx = 0;
58 by = 0;
59 waterSurface = true;
60 } else if (kind == 1) {
61 bx = 0;
62 by = 3;
63 waterSurface = true;
64 } else if (kind == 2) {
65 bx = 6;
66 by = 0;
67 } else if (kind == 3) {
68 bx = 6;
69 by = 3;
70 } else {
71 bx = (tx / 4) * 8;
72 by = ty * 6 + (tx / 2 % 2) * 3;
73 if (kind % 2 == 0)
74 waterSurface = true;
75 else {
76 bx += 6;
77 waterfall = true;
78 }
79 }
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) {
84 bx = tx * 2;
85 by = (ty - 2) * 3;
86 table = &kRpgFloor[size_t(shape)];
87 } else if (gid < a4) {
88 bx = tx * 2;
89 by = (ty - 6) * 2;
90 table = &kRpgWall[size_t(shape % 16)];
91 } else {
92 bx = tx * 2;
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)];
95 }
96 const int waterIndices[4] = {0, 1, 2, 1};
97 for (int frame = 0; frame < frameCount; ++frame) {
98 TileLayer::Tileset::Visual::SubtileFrame output;
99 output.durationMs = 500;
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)});
107 }
108 visual.subtileFrames.push_back(std::move(output));
109 }
110 return visual;
111}
112} // namespace
113
114TileLayer::Tileset::Visual decodeRpgMakerTileVisual(int tileId) { return rpgMakerVisual(tileId); }
115
116EVENGINE_API_WORLD eve::Result<RpgMakerImportReceipt> importRpgMakerMap(const std::string &mapPath, const std::string &tilesetsPath,
117 const std::string &sourceEngine) {
118 auto fail = [](const std::string &code, const std::string &message, const std::string &path) {
120 eve::DiagnosticCode::ParseError, message, path, {{"domain_code", code}}, "map.rpgmaker"));
121 };
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);
129
130 std::string decodeError;
131 auto mapDocument = eve::json::Document::parse(mapText, &decodeError);
132 if (!mapDocument.valid() || !mapDocument.root().isObject())
133 return fail("map.import.rpgmaker.map-invalid", decodeError, mapPath);
134 auto tilesetsDocument = eve::json::Document::parse(tilesetsText, &decodeError);
135 if (!tilesetsDocument.valid() || !tilesetsDocument.root().isArray())
136 return fail("map.import.rpgmaker.tilesets-invalid", decodeError, tilesetsPath);
137
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);
146
147 const Json tilesets = tilesetsDocument.root();
148 Json tileset;
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);
152
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",
157 tilesetsPath);
158
159 std::array<std::vector<uint32_t>, 4> planes;
160 for (int z = 0; z < 4; ++z) {
161 planes[size_t(z)].resize(planeSize);
162 for (size_t cell = 0; cell < planeSize; ++cell)
163 planes[size_t(z)][cell] = asUInt32(rawData.at(size_t(z) * planeSize + cell), 0);
164 }
165
166 struct SheetSpec {
167 int nameIndex;
168 int firstGid;
169 int columns;
170 };
171 constexpr std::array<SheetSpec, 9> sheets = {{{5, 1, 16},
172 {6, 256, 16},
173 {7, 512, 16},
174 {8, 768, 16},
175 {4, 1536, 8},
176 {0, 2048, 8},
177 {1, 2816, 8},
178 {2, 4352, 8},
179 {3, 5888, 8}}};
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;
185 TilesetInfo info;
186 info.firstGid = sheet.firstGid;
187 info.columns = sheet.columns;
188 info.tileW = 48;
189 info.tileH = 48;
190 info.image = (projectRoot / "img" / "tilesets" / (name + ".png")).lexically_normal().generic_string();
191 infos.push_back(std::move(info));
192 }
193 if (infos.empty())
194 return fail("map.import.rpgmaker.tileset-images-empty", "Tileset has no named sheets", tilesetsPath);
195
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) {
201 TileLayer *layer = TileLayer::createLayer(width, height, 48.f, 48.f);
202 layer->tiles()->gids = planes[size_t(z)];
203 applyTilesets(layer, infos);
204 layer->setLayer(z);
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);
217 uint8_t directions = 0x0f;
218 if (flag & 0x08) directions &= uint8_t(~0x01); // up / north
219 if (flag & 0x04) directions &= uint8_t(~0x02); // right / east
220 if (flag & 0x01) directions &= uint8_t(~0x04); // down / south
221 if (flag & 0x02) directions &= uint8_t(~0x08); // left / west
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;
231 layer->setTileNavigationProfile(gid, walkable, 1.f, directions, directions, !star && directions == 0,
232 int(semantics));
233 }
234 layer->rebuildSpatialIndex();
235 receipt.layers.push_back(layer);
236 }
237
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";
243 struct ProfileKey {
244 uint8_t directions = 0x0f;
245 uint32_t semantics = 0;
246 };
247 std::vector<std::pair<ProfileKey, int>> profiles;
248 auto &navigationGids = receipt.navigationLayer->tiles()->gids;
249 for (size_t cell = 0; cell < planeSize; ++cell) {
250 ProfileKey profile;
251 profile.directions = 0;
252 constexpr int rpgBits[4] = {0x08, 0x04, 0x01, 0x02};
253 constexpr uint8_t evBits[4] = {0x01, 0x02, 0x04, 0x08};
254 for (int direction = 0; direction < 4; ++direction) {
255 bool allowed = true;
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;
262 break;
263 }
264 if (allowed) profile.directions |= evBits[direction];
265 }
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;
276 }
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;
279 });
280 int profileGid = 0;
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));
287 } else {
288 profileGid = found->second;
289 }
290 navigationGids[cell] = uint32_t(profileGid);
291 }
292 receipt.navigationLayer->rebuildSpatialIndex();
293
294 if (rawData.size() >= planeSize * 5) {
295 bool hasShadow = false;
296 for (size_t cell = 0; cell < planeSize; ++cell)
297 hasShadow = hasShadow || (rawData.at(planeSize * 4 + cell).asInt(0) & 0x0f);
298 if (hasShadow) {
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);
302 for (size_t cell = 0; cell < planeSize; ++cell) {
303 const int bits = rawData.at(planeSize * 4 + cell).asInt(0) & 0x0f;
304 const int x = int(cell % size_t(width)), y = int(cell / size_t(width));
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);
307 }
308 receipt.shadowLayer->resource()->path = mapPath;
309 receipt.shadowLayer->resource()->sourceEngine = sourceEngine;
310 receipt.shadowLayer->resource()->sourceVersion = "MV/MZ shadow projection";
311 }
312 }
313 return eve::Result<RpgMakerImportReceipt>::success(std::move(receipt),
315}
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
int bx
Definition CaveMesh.cpp:114
int by
Definition CaveMesh.cpp:114
float planes[6][4]
#define EVENGINE_API_WORLD
Definition Export.h:109
std::string message
DiagnosticCode code
ShaderImageInput shape
std::uint32_t height
std::uint32_t width
TokenKind kind
std::string name
size_t directions
Definition OnnxLstm.cpp:29
TileLayer * layer
std::string path
Definition PlayHost.cpp:110
RoadLaneDirection direction
bool found
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)
Cell cell
int firstGid
int columns
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.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
Definition Json.cpp:581
std::unordered_map< std::string, SkillDefinition > & table()
Definition Skill.cpp:65
glm::uvec4 info