v0.1.0: software map renderer + SDL3 window viewer
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -14,6 +16,7 @@ namespace {
|
||||
std::puts("usage:");
|
||||
std::puts(" openra3 maps [--game-dir DIR]");
|
||||
std::puts(" openra3 skirmish [--game-dir DIR] [--map ID] [--frames N] [--seed N]");
|
||||
std::puts(" openra3 render [--game-dir DIR] [--map ID] [--frames N] [--seed N] [--world-size S] [--out FILE.bmp]");
|
||||
std::puts("env: RA3_GAME_DIR (default C:\\Red Alert 3); game data is never copied into the repo");
|
||||
}
|
||||
|
||||
@@ -98,6 +101,92 @@ namespace {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Entry name of a map's overview art (`<stem>_art.tga`). */
|
||||
auto art_entry_for(const ra3::map::map_info &info) -> std::string {
|
||||
auto entry = info.entry;
|
||||
if (entry.size() > 4U) entry.resize(entry.size() - 4U);
|
||||
entry += "_art.tga";
|
||||
return entry;
|
||||
}
|
||||
|
||||
auto command_render(const std::vector<std::string> &args, const std::optional<std::filesystem::path> &game_dir) -> int {
|
||||
const auto dir = ra3::fs::find_game_dir(game_dir);
|
||||
if (!dir) {
|
||||
std::puts("no Red Alert 3 install found; pass --game-dir or set RA3_GAME_DIR");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const auto archive = ra3::fs::big_archive::open(data_path(*dir, "MapsMultiplayer.big"));
|
||||
const auto catalog = ra3::map::map_catalog::from_archive(archive);
|
||||
const auto requested = option_value(args, "--map");
|
||||
const ra3::map::map_info *info = nullptr;
|
||||
if (requested) {
|
||||
info = catalog.find(*requested);
|
||||
} else if (catalog.size() > 0U) {
|
||||
info = &catalog.maps().front();
|
||||
}
|
||||
if (info == nullptr) {
|
||||
std::printf("map '%s' not found\n", requested ? requested->c_str() : "");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const auto tga = archive.read(art_entry_for(*info), true);
|
||||
const auto art = ra3::render::decode_tga(tga);
|
||||
|
||||
ra3::render::scene_options scene;
|
||||
scene.title = "OpenRA3 - " + info->id;
|
||||
if (const auto size = option_value(args, "--world-size")) scene.world_width = scene.world_height = std::stod(*size);
|
||||
|
||||
ra3::skirmish::match_config config;
|
||||
config.map_id = info->id;
|
||||
if (const auto seed = option_value(args, "--seed")) config.seed = static_cast<ra3::core::uint32>(std::stoul(*seed));
|
||||
if (const auto frames = option_value(args, "--frames")) config.max_frames = static_cast<ra3::core::uint32>(std::stoul(*frames));
|
||||
|
||||
auto starts = ra3::map::load_map(archive, *info).starts;
|
||||
if (starts.size() < 2U) starts = ra3::skirmish::builtin_start_positions();
|
||||
auto match = ra3::skirmish::skirmish_match::create(config, starts);
|
||||
const auto summary = match.run();
|
||||
|
||||
std::vector<ra3::render::marker> markers;
|
||||
for (const auto &player: match.players()) {
|
||||
markers.push_back({static_cast<double>(player.start.x), static_cast<double>(player.start.y), ra3::render::yellow, 6});
|
||||
}
|
||||
for (const auto &entry: match.units()) {
|
||||
if (!entry.alive) continue;
|
||||
const auto color = entry.owner == 0U ? ra3::render::blue : ra3::render::red;
|
||||
int radius = 2;
|
||||
if (entry.cls == ra3::skirmish::unit_class::tank) radius = 4;
|
||||
if (entry.cls == ra3::skirmish::unit_class::base) radius = 6;
|
||||
markers.push_back({static_cast<double>(entry.position.x), static_cast<double>(entry.position.y), color, radius});
|
||||
}
|
||||
|
||||
const auto composed = ra3::render::compose(art, markers, scene);
|
||||
|
||||
const auto out = option_value(args, "--out");
|
||||
const bool no_window = std::find(args.begin(), args.end(), "--no-window") != args.end();
|
||||
bool shown = false;
|
||||
if (!out && !no_window) {
|
||||
ra3::ui::viewer_options viewer;
|
||||
viewer.title = scene.title;
|
||||
shown = ra3::ui::run_viewer(composed, viewer);
|
||||
}
|
||||
if (!shown) {
|
||||
const auto path = out.value_or("openra3_view.bmp");
|
||||
const auto bmp = ra3::render::encode_bmp(composed);
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
if (!file) {
|
||||
std::printf("cannot write %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
file.write(reinterpret_cast<const char *>(bmp.data()), static_cast<std::streamsize>(bmp.size()));
|
||||
std::printf("wrote %s (%ux%u)\n", path.c_str(), composed.width(), composed.height());
|
||||
}
|
||||
|
||||
std::printf("map=%s units P0=%d P1=%d winner=%d frames=%u\n", info->id.c_str(), summary.units_left[0], summary.units_left[1], summary.winner,
|
||||
summary.frames);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto main(int argc, char **argv) -> int {
|
||||
@@ -115,6 +204,7 @@ auto main(int argc, char **argv) -> int {
|
||||
|
||||
if (command == "maps") return command_maps(game_dir);
|
||||
if (command == "skirmish") return command_skirmish(rest, game_dir);
|
||||
if (command == "render") return command_render(rest, game_dir);
|
||||
if (command == "help" || command == "--help" || command == "-h") {
|
||||
print_usage();
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user