QByteArray AuroraMapWriter::toByteArray(const Map *map) { QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); MapWriter w; w.writeMap(map, &buffer); return bytes; }
QByteArray TmxMapWriter::toByteArray(const Map *map) { QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); MapWriter writer; writer.setLayerDataFormat(MapWriter::Base64Zlib); writer.writeMap(map, &buffer); return bytes; }
bool AuroraMapWriter::writeTileset(const Tileset *tileset, const QString &fileName) { MapWriter writer; writer.setDtdEnabled(false); bool result = writer.writeTileset(tileset, fileName); if (!result) { mError = writer.errorString(); } else { mError.clear(); } return result; }
bool TmxMapWriter::writeTileset(const Tileset *tileset, const QString &fileName) { Preferences *prefs = Preferences::instance(); MapWriter writer; writer.setDtdEnabled(prefs->dtdEnabled()); bool result = writer.writeTileset(tileset, fileName); if (!result) mError = writer.errorString(); else mError.clear(); return result; }
bool TmxMapWriter::write(const Map *map, const QString &fileName) { Preferences *prefs = Preferences::instance(); MapWriter writer; writer.setLayerDataFormat(prefs->layerDataFormat()); writer.setDtdEnabled(prefs->dtdEnabled()); bool result = writer.writeMap(map, fileName); if (!result) mError = writer.errorString(); else mError.clear(); return result; }
bool AuroraMapWriter::write(const Map *map, const QString &fileName) { Map::LayerDataFormat format = map->layerDataFormat(); if (format == Map::Default) format = Map::Base64Zlib; format = Map::Base64Zlib; MapWriter writer; writer.setLayerDataFormat(format); writer.setDtdEnabled(false); bool result = writer.writeMap(map, fileName); if (!result) { mError = writer.errorString(); } else { mError.clear(); } return result; }
int main(int argc, char *argv[]) { QGuiApplication a(argc, argv); a.setOrganizationDomain(QLatin1String("mapeditor.org")); a.setApplicationName(QLatin1String("TerrainGenerator")); a.setApplicationVersion(QLatin1String("1.0")); CommandLineOptions options; if (!parseCommandLineArguments(options)) { // Something went wrong, abort. return 1; } if (options.showVersion) showVersion(); if (options.showHelp) showHelp(); if (options.showVersion || options.showHelp) return 0; if (options.target.isEmpty()) { qWarning() << "Error: No target tileset provided"; showHelp(); return 1; } if (options.sources.isEmpty()) { qWarning() << "Error: No source tilesets provided"; showHelp(); return 1; } // Check terrain priorities. if (options.terrainPriority.isEmpty()) { qWarning("Error: No terrain priorities set (option -p)."); showHelp(); return 1; } MapReader reader; SharedTileset targetTileset; QList<SharedTileset> sources; if (!options.overwrite && QFile::exists(options.target)) { targetTileset = reader.readTileset(options.target); if (!targetTileset) { qCritical("Error reading target tileset:\n%s", qUtf8Printable(reader.errorString())); } // Remove empty tiles from the end of the tileset int nextTileId = targetTileset->nextTileId(); for (int id = nextTileId - 1; id >= 0; --id) { if (Tile *tile = targetTileset->findTile(id)) { if (isEmpty(tile->image().toImage())) { targetTileset->deleteTile(id); nextTileId = id; continue; } } break; } targetTileset->setNextTileId(nextTileId); // If the target tileset already exists, it is also a source tileset sources.append(targetTileset); } // Read source tilesets. for (const QString &sourceFileName : options.sources) { SharedTileset source = reader.readTileset(sourceFileName); if (!source) { qCritical("Error reading source tileset '%s':\n%s", qUtf8Printable(sourceFileName), qUtf8Printable(reader.errorString())); } sources.append(source); } // If the target tileset does not exist yet, create it if (!targetTileset) { QString name = QFileInfo(options.target).completeBaseName(); const SharedTileset &firstSource = sources.first(); int tileWidth = firstSource->tileWidth(); int tileHeight = firstSource->tileHeight(); targetTileset = Tileset::create(name, tileWidth, tileHeight); } // Set up a mapping from terrain to tile, for quick lookup QMap<TileTerrainNames, Tile*> terrainToTile; for (const SharedTileset &tileset : sources) for (Tile *tile : tileset->tiles()) if (tile->terrain() != 0xFFFFFFFF) if (!terrainToTile.contains(TileTerrainNames(tile))) // TODO: Optimize terrainToTile.insert(TileTerrainNames(tile), tile); // Set up the list of all terrains, mapped by name. QMap<QString, Terrain*> terrains; for (const SharedTileset &tileset : sources) for (Terrain *terrain : tileset->terrains()) if (!terrains.contains(terrain->name())) terrains.insert(terrain->name(), terrain); // Check if there is anything to combine. if (options.combineList.size() == 0) { qWarning() << "No terrain specified to combine (-c option)."; } else { // Dump the combine lists. qWarning() << "Terrains to combine:"; for (const QStringList &combine : options.combineList) { if (combine.isEmpty()) { qCritical("Empty combine set"); } qWarning() << combine; // Make sure every terrain from this set was defined. for (const QString &terrainName : combine) if (!terrains.contains(terrainName)) qCritical("Terrain %s is in combine list, however it wasn't defined by any tileset.", qUtf8Printable(terrainName)); } } // Setup terrain priorities. TerrainLessThan lessThan; int priority = 0; for (const QString &terrainName : options.terrainPriority) { lessThan.terrainPriority.insert(terrainName, priority); ++priority; } qDebug() << "Terrains found:" << terrains.keys(); // Check if all terrains from priority list were found and loaded. for (const QString &terrainName : lessThan.terrainPriority.keys()) if (!terrains.contains(terrainName)) qWarning() << "Terrain" << terrainName << "from priority list not found."; // Add terrain names not specified from command line. for (const QString &terrainName : terrains.keys()) { if (!lessThan.terrainPriority.contains(terrainName)) { qWarning() << "No priority set for" << terrainName; lessThan.terrainPriority.insert(terrainName, priority); ++priority; } } // Add terrains that are not defined in the target tileset yet // TODO: This step should be more configurable for (Terrain *terrain : terrains) { if (!hasTerrain(*targetTileset, terrain->name())) { Tile *terrainTile = terrain->imageTile(); QPixmap terrainImage = terrainTile->image(); Tile *newTerrainTile = targetTileset->addTile(terrainImage); newTerrainTile->setProperties(terrainTile->properties()); Terrain *newTerrain = targetTileset->addTerrain(terrain->name(), newTerrainTile->id()); // WARNING: This assumes the terrain tile has this terrain on all // its corners. newTerrainTile->setTerrain(makeTerrain(newTerrain->id())); terrainToTile.insert(TileTerrainNames(newTerrainTile), newTerrainTile); } } // Prepare a list of terrain combinations. QVector<TileTerrainNames> process; for (const QStringList &combine : options.combineList) { QList<Terrain*> terrainList; // Get the terrains to combine for (const QString &terrainName : combine) terrainList.append(terrains[terrainName]); // Construct a vector with all terrain combinations to process for (Terrain *topLeft : terrainList) { for (Terrain *topRight : terrainList) { for (Terrain *bottomLeft : terrainList) { for (Terrain *bottomRight : terrainList) { process.append(TileTerrainNames(topLeft->name(), topRight->name(), bottomLeft->name(), bottomRight->name())); } } } } } // Go through each combination of terrains and add the tile to the target // tileset if it's not in there yet. for (const TileTerrainNames &terrainNames : process) { Tile *tile = terrainToTile.value(terrainNames); if (tile && tile->tileset() == targetTileset) continue; QPixmap image; Properties properties; if (!tile) { qWarning() << "Generating" << terrainNames; // Start a new image QImage tileImage = QImage(targetTileset->tileWidth(), targetTileset->tileHeight(), QImage::Format_ARGB32); tileImage.fill(Qt::transparent); QPainter painter(&tileImage); QStringList terrainList = terrainNames.terrainList(); qSort(terrainList.begin(), terrainList.end(), lessThan); // Draw the lowest terrain to avoid pixel gaps QString baseTerrain = terrainList.first(); QPixmap baseImage = terrains[baseTerrain]->imageTile()->image(); painter.drawPixmap(0, 0, baseImage); for (const QString &terrainName : terrainList) { TileTerrainNames filtered = terrainNames.filter(terrainName); Tile *tile = terrainToTile.value(filtered); if (!tile) { qWarning() << "Missing" << filtered; continue; } painter.drawPixmap(0, 0, tile->image()); properties.merge(tile->properties()); } image = QPixmap::fromImage(tileImage); } else { qWarning() << "Copying" << terrainNames << "from" << QFileInfo(tile->tileset()->fileName()).fileName(); image = tile->image(); properties = tile->properties(); } Tile *newTile = targetTileset->addTile(image); newTile->setTerrain(terrainNames.toTerrain(*targetTileset)); newTile->setProperties(properties); terrainToTile.insert(terrainNames, newTile); } if (targetTileset->tileCount() == 0) qCritical("Target tileset is empty"); if (options.embedImage) { // Make sure there is no source name, this way the image will be saved in the TSX file. targetTileset->setImageSource(QString()); } else { // Save the target tileset image as separate file. int columns = qMin(options.columns, targetTileset->tileCount()); int rows = targetTileset->tileCount() / options.columns; if (targetTileset->tileCount() % options.columns > 0) ++rows; qWarning() << "Writing external tileset image."; // Save the target tileset image QImage image(targetTileset->tileWidth() * columns, targetTileset->tileHeight() * rows, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter(&image); for (Tile *tile : targetTileset->tiles()) { int x = (tile->id() % options.columns) * targetTileset->tileWidth(); int y = (tile->id() / options.columns) * targetTileset->tileHeight(); painter.drawPixmap(x, y, tile->image()); } QString imageFileName = QFileInfo(options.target).completeBaseName(); imageFileName += ".png"; image.save(imageFileName); targetTileset->setImageSource(imageFileName); targetTileset->setColumnCount(options.columns); } // Save the target tileset MapWriter writer; targetTileset->setFileName(QString()); writer.writeTileset(*targetTileset, options.target); return 0; }
void ProcessSolidBlock( unsigned short x_base, unsigned short y_base, MapWriter& mapwriter ) { unsigned int idx2_offset = 0; SOLIDX2_ELEM idx2_elem; memset( &idx2_elem, 0, sizeof idx2_elem ); idx2_elem.baseindex = mapwriter.NextSolidIndex(); unsigned short x_add_max = SOLIDX_X_SIZE, y_add_max = SOLIDX_Y_SIZE; if ( x_base + x_add_max > uo_map_width ) x_add_max = uo_map_width - x_base; if ( y_base + y_add_max > uo_map_height ) y_add_max = uo_map_height - y_base; for ( unsigned short x_add = 0; x_add < x_add_max; ++x_add ) { for ( unsigned short y_add = 0; y_add < y_add_max; ++y_add ) { unsigned short x = x_base + x_add; unsigned short y = y_base + y_add; StaticList statics; // read the map, and treat it like a static. short z; USTRUCT_MAPINFO mi; safe_getmapinfo( x, y, &z, &mi ); if ( mi.landtile > 0x3FFF ) INFO_PRINT.Format( "Tile 0x{:X} at ({},{},{}) is an invalid ID!\n" ) << mi.landtile << x << y << z; // for water, don't average with surrounding tiles. if ( landtile_uoflags( mi.landtile ) & USTRUCT_TILE::FLAG_LIQUID ) z = mi.z; short low_z = get_lowestadjacentz( x, y, z ); short lt_height = z - low_z; z = low_z; if ( mi.landtile > 0x3FFF ) INFO_PRINT.Format( "Tile 0x{:X} at ({},{},{}) is an invalid ID!\n" ) << mi.landtile << x << y << z; unsigned int lt_flags = landtile_uoflags( mi.landtile ); if ( ~lt_flags & USTRUCT_TILE::FLAG_BLOCKING ) { // this seems to be the default. lt_flags |= USTRUCT_TILE::FLAG_PLATFORM; } lt_flags |= USTRUCT_TILE::FLAG_NO_SHOOT; // added to make sure people using noshoot will have shapes // generated by this tile in future block LOS, shouldn't // affect people using old LOS method one way or another. lt_flags |= USTRUCT_TILE::FLAG_FLOOR; lt_flags |= USTRUCT_TILE::FLAG_HALF_HEIGHT; // the entire map is this way if ( lt_flags & USTRUCT_TILE::FLAG_WALL ) lt_height = 20; readstatics( statics, x, y, USTRUCT_TILE::FLAG_BLOCKING | USTRUCT_TILE::FLAG_PLATFORM | USTRUCT_TILE::FLAG_HALF_HEIGHT | USTRUCT_TILE::FLAG_LIQUID | USTRUCT_TILE::FLAG_HOVEROVER //USTRUCT_TILE::FLAG__WALK ); for ( unsigned i = 0; i < statics.size(); ++i ) { StaticRec srec = statics[i]; unsigned int polflags = polflags_from_tileflags( srec.graphic, srec.flags, cfg_use_no_shoot, cfg_LOS_through_windows ); if ( ( ~polflags & FLAG::MOVELAND ) && ( ~polflags & FLAG::MOVESEA ) && ( ~polflags & FLAG::BLOCKSIGHT ) && ( ~polflags & FLAG::BLOCKING ) && ( ~polflags & FLAG::OVERFLIGHT ) ) { // remove it. we'll re-sort later. statics.erase( statics.begin() + i ); --i; // do-over } if ( ( ~srec.flags & USTRUCT_TILE::FLAG_BLOCKING ) && ( ~srec.flags & USTRUCT_TILE::FLAG_PLATFORM ) && ( ~srec.flags & USTRUCT_TILE::FLAG_HALF_HEIGHT ) && ( ~srec.flags & USTRUCT_TILE::FLAG_LIQUID ) && ( ~srec.flags & USTRUCT_TILE::FLAG_HOVEROVER ) ) /*(~srec.flags & USTRUCT_TILE::FLAG__WALK)*/ { // remove it. we'll re-sort later. statics.erase( statics.begin() + i ); --i; // do-over } } bool addMap = true; for ( const auto &srec : statics ) { // Look for water tiles. If there are any, discard the map (which is usually at -15 anyway) if ( z + lt_height <= srec.z && ( ( srec.z - ( z + lt_height ) ) <= 10 ) && // only where the map is below or same Z as the static srec.graphic >= 0x1796 && srec.graphic <= 0x17B2 ) // FIXME hardcoded { // arr, there be water here addMap = false; } // if there's a static on top of one of these "wall" landtiles, make it override. if ( ( lt_flags & USTRUCT_TILE::FLAG_WALL ) && // wall? z <= srec.z && srec.z - z <= lt_height ) { lt_height = srec.z - z; } } // shadows above caves if ( is_cave_shadow( mi ) && !statics.empty() ) { addMap = false; } // If the map is a NODRAW tile, and there are statics, discard the map tile if ( mi.landtile == 2 && !statics.empty() ) addMap = false; if ( addMap ) statics.push_back( StaticRec( 0, static_cast<signed char>( z ), lt_flags, static_cast<char>( lt_height ) ) ); sort( statics.begin(), statics.end(), StaticsByZ() ); reverse( statics.begin(), statics.end() ); std::vector<MapShape> shapes; // try to consolidate like shapes, and discard ones we don't care about. while ( !statics.empty() ) { StaticRec srec = statics.back(); statics.pop_back(); unsigned int polflags = polflags_from_tileflags( srec.graphic, srec.flags, cfg_use_no_shoot, cfg_LOS_through_windows ); if ( ( ~polflags & FLAG::MOVELAND ) && ( ~polflags & FLAG::MOVESEA ) && ( ~polflags & FLAG::BLOCKSIGHT ) && ( ~polflags & FLAG::BLOCKING ) && ( ~polflags & FLAG::OVERFLIGHT ) ) { passert_always( 0 ); continue; } if ( ( ~srec.flags & USTRUCT_TILE::FLAG_BLOCKING ) && ( ~srec.flags & USTRUCT_TILE::FLAG_PLATFORM ) && ( ~srec.flags & USTRUCT_TILE::FLAG_HALF_HEIGHT ) && ( ~srec.flags & USTRUCT_TILE::FLAG_LIQUID ) && ( ~srec.flags & USTRUCT_TILE::FLAG_HOVEROVER ) ) /*(~srec.flags & USTRUCT_TILE::FLAG__WALK)*/ { passert_always( 0 ); continue; } if ( shapes.empty() ) { // this, whatever it is, is the map base. //TODO: look for water statics and use THOSE as the map. MapShape shape; shape.z = srec.z; //these will be converted below to shape.height = 0; //make the map "solid" shape.flags = static_cast<unsigned char>( polflags ); // no matter what, the lowest level is gradual shape.flags |= FLAG::GRADUAL; shapes.push_back( shape ); //for wall flag - map tile always height 0, at bottom. if map tile has height, add it as a static if ( srec.height != 0 ) { MapShape _shape; _shape.z = srec.z; _shape.height = srec.height; _shape.flags = polflags; shapes.push_back( _shape ); } continue; } MapShape& prev = shapes.back(); // we're adding it. MapShape shape; shape.z = srec.z; shape.height = srec.height; shape.flags = polflags; //always add the map shape seperately if ( shapes.size() == 1 ) { shapes.push_back( shape ); continue; } if ( shape.z < prev.z + prev.height ) { // things can't exist in the same place. // shrink the bottom part of this shape. // if that would give it negative height, then skip it. short height_remove = prev.z + prev.height - shape.z; if ( height_remove <= shape.height ) { shape.z += height_remove; shape.height -= height_remove; } else { // example: 5530, 14 continue; } } // sometimes water has "sand" a couple z-coords above it. // We'll try to detect this (really, anything that is up to 4 dist from water) // and extend the thing above downward. if ( ( prev.flags & FLAG::MOVESEA ) && ( shape.z > prev.z + prev.height ) && ( shape.z <= prev.z + prev.height + 4 ) ) { short height_add = shape.z - prev.z - prev.height; shape.z -= height_add; shape.height += height_add; } if ( ( prev.flags & FLAG::MOVESEA ) && ( prev.z + prev.height == -5 ) && ( shape.flags & FLAG::MOVESEA ) && ( shape.z == 25 ) ) { // oddly, there are some water tiles at z=25 in some places...I don't get it continue; } //string prevflags_s = flagstr(prev.flags); //const char* prevflags = prevflags_s.c_str(); //string shapeflags_s = flagstr(shape.flags); //const char* shapeflags = shapeflags_s.c_str(); if ( shape.z > prev.z + prev.height ) { // // elevated above what's below, must include separately // shapes.push_back( shape ); continue; } passert_always( shape.z == prev.z + prev.height ); if ( shape.z == prev.z + prev.height ) { // // sitting right on top of the previous solid // // standable atop non-standable: standable // nonstandable atop standable: nonstandable // etc bool can_combine = flags_match( prev.flags, shape.flags, FLAG::BLOCKSIGHT | FLAG::BLOCKING ); if ( prev.flags & FLAG::MOVELAND && ~shape.flags & FLAG::BLOCKING && ~shape.flags & FLAG::MOVELAND ) { can_combine = false; } if ( can_combine ) { prev.flags = shape.flags; prev.height += shape.height; } else // if one blocks LOS, but not the other, they can't be combined this way. { shapes.push_back( shape ); continue; } } } // the first StaticShape is the map base. MapShape base = shapes[0]; shapes.erase( shapes.begin() ); MAPCELL cell; passert_always( base.height == 0 ); cell.z = static_cast<signed char>( base.z ); //assume now map has height=1. a static was already added if it was >0 cell.flags = static_cast<u8>( base.flags ); if ( !shapes.empty() ) cell.flags |= FLAG::MORE_SOLIDS; mapwriter.SetMapCell( x, y, cell ); if ( !shapes.empty() ) { ++with_more_solids; total_statics += static_cast<unsigned int>( shapes.size() ); if ( idx2_offset == 0 ) idx2_offset = mapwriter.NextSolidx2Offset(); unsigned int addindex = mapwriter.NextSolidIndex() - idx2_elem.baseindex; if ( addindex > std::numeric_limits<unsigned short>::max() ) throw std::runtime_error("addoffset overflow"); idx2_elem.addindex[x_add][y_add] = static_cast<unsigned short>( addindex ); int count = static_cast<int>( shapes.size() ); for ( int j = 0; j < count; ++j ) { MapShape shape = shapes[j]; char _z, height, flags; _z = static_cast<char>( shapes[j].z ); height = static_cast<char>( shape.height ); flags = static_cast<u8>( shape.flags ); if ( !height )//make 0 height solid { --_z; ++height; } if ( j != count - 1 ) flags |= FLAG::MORE_SOLIDS; SOLIDS_ELEM solid; solid.z = _z; solid.height = height; solid.flags = flags; mapwriter.AppendSolid( solid ); } } } } if ( idx2_offset ) { ++nonempty; mapwriter.AppendSolidx2Elem( idx2_elem ); } else { ++empty; } mapwriter.SetSolidx2Offset( x_base, y_base, idx2_offset ); }