Files
openttd-cmclient/src/citymania/cm_export.cpp
T
2022-11-26 22:25:47 +04:00

524 lines
15 KiB
C++

#include "../stdafx.h"
#include "cm_export.hpp"
#include "../cargotype.h"
#include "../debug.h"
#include "../date_func.h"
#include "../house.h"
#include "../gfx_func.h"
#include "../gfx_type.h"
#include "../engine_base.h"
#include "../screenshot.h"
#include "../spritecache.h"
#include "../strings_func.h"
#include "../strings_type.h"
#include "../table/palettes.h"
#include "../table/sprites.h"
#include "../table/train_sprites.h"
#include "../viewport_sprite_sorter.h"
#include "../viewport_type.h"
#include "../window_func.h"
#include "../window_gui.h"
#include "../zoom_func.h"
#include <set>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include "../safeguards.h"
struct StringSpriteToDraw {
StringID string;
Colours colour;
int32 x;
int32 y;
uint64 params[2];
uint16 width;
};
struct TileSpriteToDraw {
SpriteID image;
PaletteID pal;
const SubSprite *sub; ///< only draw a rectangular part of the sprite
int32 x; ///< screen X coordinate of sprite
int32 y; ///< screen Y coordinate of sprite
};
struct ChildScreenSpriteToDraw {
SpriteID image;
PaletteID pal;
const SubSprite *sub; ///< only draw a rectangular part of the sprite
int32 x;
int32 y;
int next; ///< next child to draw (-1 at the end)
};
typedef std::vector<TileSpriteToDraw> TileSpriteToDrawVector;
typedef std::vector<ParentSpriteToDraw> ParentSpriteToDrawVector;
typedef std::vector<ChildScreenSpriteToDraw> ChildScreenSpriteToDrawVector;
namespace citymania {
extern SpriteID (*GetDefaultTrainSprite)(uint8, Direction); // train_cmd.cpp
namespace data_export {
class JsonWriter {
protected:
int i = 0;
bool no_comma = true;
char buffer[128];
bool js = false;
public:
std::ofstream f;
JsonWriter(const std::string &fname, bool js=false) {
this->js = js;
f.open(fname.c_str());
if (this->js) f << "OPENTTD = {";
no_comma = true;
}
~JsonWriter() {
this->ident(false);
if (this->js) f << "}" << std::endl;
f.close();
}
void ident(bool comma=true) {
if (comma && !no_comma) f << ",";
no_comma = false;
f << std::endl;
for(int j = 0; j < i; j++) f << " ";
}
void key(const char *k) {
const char *kn;
for (kn = k + strlen(k); kn >= k && *kn != '>' && *kn != '.'; kn--);
kn++;
this->ident();
f << "\"" << kn << "\": ";
}
void value(bool val) {
f << (val ? "true" : "false");
}
void value(unsigned int val) {
f << val;
}
void value(uint64 val) {
f << val;
}
void value(Money val) {
f << val;
}
void value(int val) {
f << val;
}
void value(const char *v) {
f << "\"" << v << "\"";
}
void value(const std::string &s) {
f << "\"" << s << "\"";
}
template<typename T>
void kv(const char *k, T v) {
key(k);
value(v);
}
void ks(const char *k, StringID s) {
GetString(buffer, s, lastof(buffer));
key(k);
value(buffer);
}
void begin_dict_with_key(const char *k) {
key(k);
f << "{";
no_comma = true;
i++;
}
void begin_dict() {
this->ident();
f << "{";
no_comma = true;
i++;
}
void end_dict() {
i--;
this->ident(false);
f << "}";
}
void begin_list_with_key(const char *k) {
key(k);
f << "[";
no_comma = true;
i++;
}
void end_list() {
i--;
this->ident(false);
f << "]";
}
};
#define JKV(j, field) j.kv(#field, field)
void WriteHouseSpecInfo(JsonWriter &j) {
j.begin_list_with_key("house_specs");
for (uint i = 0; i < NUM_HOUSES; i++) {
const HouseSpec *hs = HouseSpec::Get(i);
j.begin_dict();
j.kv("min_year", hs->min_year);
j.kv("max_year", hs->max_year);
j.kv("population", hs->population);
j.kv("removal_cost", hs->removal_cost);
j.kv("name", hs->building_name);
j.kv("mail_generation", hs->mail_generation);
j.kv("flags", hs->building_flags);
j.kv("availability", hs->building_availability);
j.kv("enabled", hs->enabled);
j.end_dict();
}
j.end_list();
}
void WriteCargoSpecInfo(JsonWriter &j) {
j.begin_list_with_key("cargo_specs");
const CargoSpec *cs;
char buffer[128];
char cargo_label[16];
bool first = true;
SetDParam(0, 123);
for (const CargoSpec *cs : CargoSpec::Iterate()) {
j.begin_dict();
JKV(j, cs->initial_payment);
j.kv("id", cs->bitnum);
j.kv("transit_days_1", cs->transit_days[0]);
j.kv("transit_days_2", cs->transit_days[1]);
JKV(j, cs->weight);
JKV(j, cs->multiplier);
JKV(j, cs->is_freight);
JKV(j, cs->legend_colour);
JKV(j, cs->rating_colour);
JKV(j, cs->sprite);
j.ks("name", cs->name);
j.ks("name_single", cs->name_single);
j.ks("units_volume", cs->units_volume);
j.ks("quantifier", cs->quantifier);
j.ks("abbrev", cs->abbrev);
for (uint i = 0; i < sizeof(cs->label); i++) {
cargo_label[i] = GB(cs->label, (uint8)(sizeof(cs->label) - i - 1) * 8, 8);
}
cargo_label[sizeof(cs->label)] = '\0';
JKV(j, cs->label);
j.kv("label_str", cargo_label);
j.end_dict();
}
j.end_list();
}
// extern const Palette _palette;
void WritePaletteInfo(JsonWriter &j) {
j.begin_list_with_key("palette");
for (uint i = 0; i < 256; i++) {
j.begin_dict();
auto &c = _palette.palette[i];
JKV(j, c.r);
JKV(j, c.g);
JKV(j, c.b);
std::ostringstream hex;
hex << "#" << std::hex << std::setfill('0');
hex << std::setw(2) << (int)c.r;
hex << std::setw(2) << (int)c.g;
hex << std::setw(2) << (int)c.b;
j.kv("hex", hex.str());
j.end_dict();
}
j.end_list();
j.begin_list_with_key("gradients");
for (auto i = 0; i < COLOUR_END; i++) {
if (i != 0) j.f << ",";
j.f << std::endl << "[";
for (auto k = 0; k < 8; k++) {
if (k != 0) j.f << ", ";
j.f << (int)_colour_gradient[i][k] << " ";
}
j.f << "]";
}
j.end_list();
const byte *remap = GetNonSprite(GB(PALETTE_TO_RED, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
}
void WriteEngineInfo(JsonWriter &j) {
j.begin_list_with_key("engines");
const Engine *e;
for (const Engine *e : Engine::Iterate()) {
if (e->type != VEH_TRAIN) continue;
j.begin_dict();
JKV(j, e->index);
j.kv("name", e->name);
j.kv("cost", e->GetCost());
j.kv("running_cost", e->GetRunningCost());
{
j.begin_dict_with_key("info");
JKV(j, e->info.cargo_type);
JKV(j, e->info.cargo_age_period);
JKV(j, e->info.climates);
JKV(j, e->info.base_intro);
JKV(j, e->info.lifelength);
JKV(j, e->info.base_life);
JKV(j, e->info.refit_mask);
JKV(j, e->info.refit_cost);
JKV(j, e->info.load_amount);
j.ks("name", e->info.string_id);
j.end_dict();
}
{
const RailVehicleInfo *rvi = &e->u.rail;
j.begin_dict_with_key("rail");
JKV(j, rvi->image_index);
JKV(j, rvi->railveh_type);
JKV(j, rvi->railtype);
JKV(j, rvi->max_speed);
JKV(j, rvi->power);
JKV(j, rvi->weight);
JKV(j, rvi->running_cost);
JKV(j, rvi->running_cost_class);
JKV(j, rvi->engclass);
JKV(j, rvi->tractive_effort);
JKV(j, rvi->air_drag);
JKV(j, rvi->capacity);
SpriteID sprite = GetDefaultTrainSprite(e->original_image_index, DIR_W);
JKV(j, sprite);
if (rvi->railveh_type == RAILVEH_MULTIHEAD) {
j.kv("rev_sprite",
GetDefaultTrainSprite(e->original_image_index + 1, DIR_W));
} else if (rvi->railveh_type == RAILVEH_WAGON) {
j.kv("full_sprite",
sprite + _wagon_full_adder[e->original_image_index]);
}
j.end_dict();
}
j.end_dict();
}
j.end_list();
}
} // namespace export
void ExportOpenttdData(const std::string &filename) {
data_export::JsonWriter j(filename, true);
data_export::WriteHouseSpecInfo(j);
data_export::WriteCargoSpecInfo(j);
data_export::WritePaletteInfo(j);
data_export::WriteEngineInfo(j);
}
extern void ViewportExportDrawBegin(const Viewport *vp, int left, int top, int right, int bottom);
extern void ViewportExportDrawEnd();
extern TileSpriteToDrawVector &ViewportExportGetTileSprites();
extern ParentSpriteToSortVector &ViewportExportGetSortedParentSprites();
extern ChildScreenSpriteToDrawVector &ViewportExportGetChildSprites();
void ViewportExportJson(const Viewport *vp, int left, int top, int right, int bottom) {
ViewportExportDrawBegin(vp, left, top, right, bottom);
auto fname = fmt::format("snaps/tick_{}.json", _tick_counter);
Debug(misc, 0, "Exporting tick {} into {} box ({},{})-({},{}) ", _tick_counter, fname, left, top, right, bottom);
data_export::JsonWriter j(fname);
j.begin_dict();
j.kv("tick", _tick_counter);
j.begin_list_with_key("tile_sprites");
for (auto &ts : ViewportExportGetTileSprites()) {
j.begin_dict();
j.kv("image", ts.image);
j.kv("pal", ts.pal);
j.kv("x", ts.x);
j.kv("y", ts.y);
if (ts.sub) {
j.begin_dict_with_key("sub");
j.kv("left", ts.sub->left);
j.kv("top", ts.sub->top);
j.kv("right", ts.sub->right);
j.kv("bottom", ts.sub->bottom);
j.end_dict();
}
j.end_dict();
}
j.end_list();
j.begin_list_with_key("parent_sprites");
auto &child_sprites = ViewportExportGetChildSprites();
for (const ParentSpriteToDraw *s : ViewportExportGetSortedParentSprites()) {
j.begin_dict();
j.kv("image", s->image);
j.kv("pal", s->pal);
j.kv("x", s->x);
j.kv("y", s->y);
j.kv("left", s->left);
j.kv("top", s->top);
j.kv("xmin", s->xmin);
j.kv("ymin", s->ymin);
j.kv("zmin", s->zmin);
j.kv("xmax", s->xmax);
j.kv("ymax", s->ymax);
j.kv("zmax", s->zmax);
if (s->sub) {
j.begin_dict_with_key("sub");
j.kv("left", s->sub->left);
j.kv("top", s->sub->top);
j.kv("right", s->sub->right);
j.kv("bottom", s->sub->bottom);
j.end_dict();
}
int child_idx = s->first_child;
if (child_idx >= 0) {
j.begin_list_with_key("children");
while (child_idx >= 0) {
const ChildScreenSpriteToDraw *cs = &child_sprites[child_idx];
child_idx = cs->next;
j.begin_dict();
j.kv("image", cs->image);
j.kv("pal", cs->pal);
j.kv("x", cs->x);
j.kv("y", cs->y);
if (cs->sub) {
j.begin_dict_with_key("sub");
j.kv("left", cs->sub->left);
j.kv("top", cs->sub->top);
j.kv("right", cs->sub->right);
j.kv("bottom", cs->sub->bottom);
j.end_dict();
}
j.end_dict();
}
j.end_dict();
}
j.end_dict();
}
j.end_list();
j.end_dict();
ViewportExportDrawEnd();
}
void ExportFrameSpritesJson() {
Viewport vp;
SetupScreenshotViewport(SC_VIEWPORT, &vp);
Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
vp.zoom = w->viewport->zoom;
ViewportExportJson(&vp,
vp.virtual_left,
vp.virtual_top,
vp.virtual_left + vp.virtual_width,
vp.virtual_top + vp.virtual_height
);
}
void ExportSprite(SpriteID sprite, SpriteType type) {
static std::set<SpriteID> exported;
if (exported.find(sprite) != exported.end()) return;
auto fname = fmt::format("snaps/sprite_{}.bin", sprite);
std::ofstream f(fname, std::ios::binary);
uint size;
void *raw = GetRawSprite(sprite, type);
if (type == ST_RECOLOUR) size = 257;
else size = *(((size_t *)raw) - 1);
f.write((char *)raw, size);
exported.insert(sprite);
}
void ExportSpriteAndPal(SpriteID img, SpriteID pal) {
SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
ExportSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR);
ExportSprite(real_sprite, ST_NORMAL);
} else if (pal != PAL_NONE) {
if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
//SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
} else {
ExportSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR);
}
ExportSprite(real_sprite, ST_NORMAL);
} else {
ExportSprite(real_sprite, ST_NORMAL);
}
}
void ViewportExport(const Viewport *vp, int left, int top, int right, int bottom) {
ViewportExportDrawBegin(vp, left, top, right, bottom);
auto fname = fmt::format("snaps/tick_{}.bin", _tick_counter);
Debug(misc, 0, "Exporting tick {} into {} box ({},{})-({},{}) ", _tick_counter, fname, left, top, right, bottom);
std::ofstream f(fname, std::ios::binary);
auto &tile_sprites = ViewportExportGetTileSprites();
uint64 n = tile_sprites.size();
f.write((char *)&n, 8);
f.write((const char *)tile_sprites.data(), n * sizeof(TileSpriteToDraw));
for (const auto &ts : tile_sprites) ExportSpriteAndPal(ts.image, ts.pal);
auto &parent_sprites = ViewportExportGetSortedParentSprites();
n = parent_sprites.size();
f.write((char *)&n, 8);
for (const ParentSpriteToDraw *s : ViewportExportGetSortedParentSprites()) {
f.write((const char *)s, sizeof(ParentSpriteToDraw) - 4);
ExportSpriteAndPal(s->image, s->pal);
}
auto &child_sprites = ViewportExportGetChildSprites();
n = child_sprites.size();
f.write((char *)&n, 8);
f.write((const char *)child_sprites.data(), n * sizeof(ChildScreenSpriteToDraw));
for (const auto &cs : child_sprites) ExportSpriteAndPal(cs.image, cs.pal);
ViewportExportDrawEnd();
}
bool _is_recording = false;
void ExportFrameSprites() {
if (!_is_recording) return;
Viewport vp;
SetupScreenshotViewport(SC_VIEWPORT, &vp);
Window *w = FindWindowById(WC_MAIN_WINDOW, 0);
vp.zoom = w->viewport->zoom;
ViewportExport(&vp,
vp.virtual_left,
vp.virtual_top,
vp.virtual_left + vp.virtual_width,
vp.virtual_top + vp.virtual_height
);
}
void StartRecording() {
_is_recording = true;
}
void StopRecording() {
_is_recording = false;
}
} // namespace citymania