2025-07-06 20:43:53 +03:00
|
|
|
#include "World.hpp"
|
|
|
|
|
#include "DrawComponents/DrawComponent.hpp"
|
2025-08-01 10:58:45 +03:00
|
|
|
#include "Helpers/HsvRgb.hpp"
|
2025-07-06 20:43:53 +03:00
|
|
|
#include "Helpers/MathExtras.hpp"
|
|
|
|
|
#include "Helpers/Networking/ByteStream.hpp"
|
2025-11-17 16:08:42 +03:00
|
|
|
#include "Helpers/VersionNumber.hpp"
|
2025-07-06 20:43:53 +03:00
|
|
|
#include "Server/CommandList.hpp"
|
|
|
|
|
#include "MainProgram.hpp"
|
2025-09-24 16:18:20 +03:00
|
|
|
#include "SharedTypes.hpp"
|
2025-10-16 09:38:58 +03:00
|
|
|
#include "Toolbar.hpp"
|
2025-11-16 16:39:12 +03:00
|
|
|
#include "VersionConstants.hpp"
|
2025-09-12 09:54:49 +03:00
|
|
|
#include "WorldGrid.hpp"
|
2025-07-06 20:43:53 +03:00
|
|
|
#include "cereal/archives/portable_binary.hpp"
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include "FileHelpers.hpp"
|
|
|
|
|
#include "FontData.hpp"
|
|
|
|
|
#include <include/core/SkFontMetrics.h>
|
|
|
|
|
#include <Helpers/Networking/NetLibrary.hpp>
|
|
|
|
|
#include <Helpers/Logger.hpp>
|
|
|
|
|
#include <cereal/types/vector.hpp>
|
|
|
|
|
#include <Helpers/Networking/NetLibrary.hpp>
|
2025-07-28 18:21:09 +03:00
|
|
|
#include <zstd.h>
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
|
#include <EmscriptenHelpers/emscripten_browser_file.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
World::World(MainProgram& initMain, OpenWorldInfo& worldInfo):
|
|
|
|
|
main(initMain),
|
|
|
|
|
rMan(*this),
|
|
|
|
|
drawProg(*this),
|
2025-09-06 19:06:30 +03:00
|
|
|
bMan(*this),
|
|
|
|
|
gridMan(*this)
|
2025-07-06 20:43:53 +03:00
|
|
|
{
|
2025-08-01 20:07:47 +03:00
|
|
|
set_canvas_background_color(main.defaultCanvasBackgroundColor);
|
2025-07-06 20:43:53 +03:00
|
|
|
displayName = main.displayName;
|
|
|
|
|
|
|
|
|
|
netSource = worldInfo.netSource;
|
|
|
|
|
|
|
|
|
|
if(worldInfo.conType == CONNECTIONTYPE_CLIENT || worldInfo.conType == CONNECTIONTYPE_SERVER)
|
2025-10-27 20:19:47 +03:00
|
|
|
main.init_net_library();
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
drawData.rMan = &rMan;
|
|
|
|
|
drawData.main = &main;
|
|
|
|
|
conType = worldInfo.conType;
|
|
|
|
|
|
|
|
|
|
switch(conType) {
|
|
|
|
|
case CONNECTIONTYPE_CLIENT: {
|
|
|
|
|
clientStillConnecting = true;
|
|
|
|
|
con.connect_p2p(netSource);
|
|
|
|
|
set_name("");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case CONNECTIONTYPE_LOCAL: {
|
|
|
|
|
if(!worldInfo.fileSource.empty())
|
|
|
|
|
load_from_file(worldInfo.fileSource, worldInfo.fileDataBuffer);
|
|
|
|
|
set_name(name);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case CONNECTIONTYPE_SERVER: {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drawProg.init_client_callbacks();
|
|
|
|
|
rMan.init_client_callbacks();
|
|
|
|
|
bMan.init_client_callbacks();
|
2025-09-24 16:18:20 +03:00
|
|
|
gridMan.init_client_callbacks();
|
2025-07-06 20:43:53 +03:00
|
|
|
init_client_callbacks();
|
|
|
|
|
con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_INITIAL_DATA, displayName, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::init_client_callbacks() {
|
|
|
|
|
con.client_add_recv_callback(CLIENT_INITIAL_DATA, [&](cereal::PortableBinaryInputArchive& message) {
|
|
|
|
|
bool isDirectConnect;
|
|
|
|
|
message(isDirectConnect, ownID, userColor);
|
|
|
|
|
if(!isDirectConnect) {
|
2025-07-08 12:18:55 +03:00
|
|
|
std::string fileDisplayName;
|
|
|
|
|
message(displayName, fileDisplayName, clients);
|
|
|
|
|
set_name(fileDisplayName);
|
2025-08-01 20:07:47 +03:00
|
|
|
Vector3f newBackColor;
|
|
|
|
|
message(newBackColor);
|
|
|
|
|
set_canvas_background_color(newBackColor, false);
|
2025-09-30 16:36:09 +03:00
|
|
|
message(canvasScale);
|
2025-07-06 20:43:53 +03:00
|
|
|
drawProg.initialize_draw_data(message);
|
2025-09-24 16:18:20 +03:00
|
|
|
message(bMan, gridMan);
|
2025-10-02 11:55:50 +03:00
|
|
|
if(!clients.empty()) {
|
|
|
|
|
auto& [randomClientServerID, randomClient] = *clients.begin();
|
|
|
|
|
drawData.cam.smooth_move_to(*main.world, randomClient.camCoords, randomClient.windowSize, true);
|
|
|
|
|
}
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
2025-09-24 16:18:20 +03:00
|
|
|
nextClientID = get_max_id(ownID);
|
2025-07-06 20:43:53 +03:00
|
|
|
clientStillConnecting = false;
|
|
|
|
|
});
|
|
|
|
|
con.client_add_recv_callback(CLIENT_USER_CONNECT, [&](cereal::PortableBinaryInputArchive& message) {
|
|
|
|
|
ServerPortionID id;
|
|
|
|
|
std::string displayName;
|
|
|
|
|
Vector3f cursorColor;
|
|
|
|
|
message(id, displayName, cursorColor);
|
|
|
|
|
clients[id].displayName = displayName;
|
|
|
|
|
clients[id].cursorColor = cursorColor;
|
2025-10-16 09:38:58 +03:00
|
|
|
add_chat_message(clients[id].displayName, "joined", Toolbar::ChatMessage::Type::JOIN);
|
2025-07-06 20:43:53 +03:00
|
|
|
});
|
|
|
|
|
con.client_add_recv_callback(CLIENT_USER_DISCONNECT, [&](cereal::PortableBinaryInputArchive& message) {
|
|
|
|
|
ServerPortionID id;
|
|
|
|
|
message(id);
|
|
|
|
|
auto it = clients.find(id);
|
|
|
|
|
if(it != clients.end()) {
|
2025-10-16 09:38:58 +03:00
|
|
|
add_chat_message(clients[id].displayName, "left", Toolbar::ChatMessage::Type::JOIN);
|
2025-07-06 20:43:53 +03:00
|
|
|
clients.erase(id);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
con.client_add_recv_callback(CLIENT_MOVE_MOUSE, [&](cereal::PortableBinaryInputArchive& message) {
|
|
|
|
|
ServerPortionID id;
|
|
|
|
|
message(id);
|
|
|
|
|
auto& c = clients[id];
|
|
|
|
|
message(c.camCoords, c.windowSize, c.cursorPos);
|
|
|
|
|
});
|
|
|
|
|
con.client_add_recv_callback(CLIENT_CHAT_MESSAGE, [&](cereal::PortableBinaryInputArchive& message) {
|
2025-10-16 09:38:58 +03:00
|
|
|
ServerPortionID senderID;
|
2025-07-06 20:43:53 +03:00
|
|
|
std::string chatMessage;
|
2025-10-16 09:38:58 +03:00
|
|
|
message(senderID, chatMessage);
|
|
|
|
|
auto it = clients.find(senderID);
|
|
|
|
|
if(it != clients.end())
|
|
|
|
|
add_chat_message(clients[senderID].displayName, chatMessage, Toolbar::ChatMessage::Type::NORMAL);
|
2025-07-06 20:43:53 +03:00
|
|
|
});
|
2025-08-01 20:07:47 +03:00
|
|
|
con.client_add_recv_callback(CLIENT_CANVAS_COLOR, [&](cereal::PortableBinaryInputArchive& message) {
|
|
|
|
|
Vector3f newBackColor;
|
|
|
|
|
message(newBackColor);
|
|
|
|
|
set_canvas_background_color(newBackColor, false);
|
|
|
|
|
});
|
2025-09-30 16:36:09 +03:00
|
|
|
con.client_add_recv_callback(CLIENT_CANVAS_SCALE, [&](cereal::PortableBinaryInputArchive& message) {
|
|
|
|
|
uint64_t newCanvasScale;
|
|
|
|
|
message(newCanvasScale);
|
2025-09-30 16:48:12 +03:00
|
|
|
if(newCanvasScale > canvasScale) {
|
|
|
|
|
con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_CANVAS_SCALE, newCanvasScale);
|
|
|
|
|
scale_up(FixedPoint::pow_int(CANVAS_SCALE_UP_STEP, newCanvasScale - canvasScale));
|
|
|
|
|
canvasScale = newCanvasScale;
|
|
|
|
|
}
|
2025-09-30 16:36:09 +03:00
|
|
|
});
|
2025-07-06 20:43:53 +03:00
|
|
|
con.client_add_recv_callback(CLIENT_KEEP_ALIVE, [&](cereal::PortableBinaryInputArchive& message) {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::focus_update() {
|
|
|
|
|
con.update();
|
|
|
|
|
|
|
|
|
|
if(con.is_host_disconnected()) {
|
|
|
|
|
Logger::get().log("USERINFO", "Host connection failed");
|
|
|
|
|
clientStillConnecting = false;
|
|
|
|
|
clients.clear();
|
|
|
|
|
netSource.clear();
|
|
|
|
|
conType = CONNECTIONTYPE_LOCAL;
|
|
|
|
|
con = ConnectionManager();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(con.is_client_disconnected()) {
|
|
|
|
|
Logger::get().log("USERINFO", "Client connection failed");
|
|
|
|
|
clientStillConnecting = false;
|
|
|
|
|
clients.clear();
|
|
|
|
|
netSource.clear();
|
|
|
|
|
conType = CONNECTIONTYPE_LOCAL;
|
|
|
|
|
con = ConnectionManager();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!clientStillConnecting) {
|
|
|
|
|
con.client_send_items_to_server(UNRELIABLE_COMMAND_CHANNEL, SERVER_MOVE_MOUSE, drawData.cam.c, main.window.size.cast<float>().eval(), main.input.mouse.pos);
|
|
|
|
|
drawProg.update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
drawData.cam.update_main(*this);
|
|
|
|
|
|
|
|
|
|
rMan.update();
|
|
|
|
|
|
|
|
|
|
mousePreviousWorldVec = drawData.cam.c.from_space(main.input.mouse.pos);
|
|
|
|
|
mouseWorldMove = drawData.cam.c.from_space(main.input.mouse.move);
|
|
|
|
|
|
2025-07-27 20:08:46 +03:00
|
|
|
if(main.input.key(InputManager::KEY_UNDO).repeat)
|
|
|
|
|
undo_with_checks();
|
|
|
|
|
else if(main.input.key(InputManager::KEY_REDO).repeat)
|
|
|
|
|
redo_with_checks();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::undo_with_checks() {
|
|
|
|
|
if(!clientStillConnecting && !drawProg.prevent_undo_or_redo())
|
|
|
|
|
undo.undo();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::redo_with_checks() {
|
|
|
|
|
if(!clientStillConnecting && !drawProg.prevent_undo_or_redo())
|
|
|
|
|
undo.redo();
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::unfocus_update() {
|
|
|
|
|
con.update();
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-01 20:07:47 +03:00
|
|
|
void World::set_canvas_background_color(const Vector3f& newBackColor, bool sendChangeOverNetwork) {
|
2025-08-01 10:58:45 +03:00
|
|
|
if(newBackColor != convert_vec3<Vector3f>(canvasTheme.backColor)) {
|
|
|
|
|
canvasTheme.backColor = SkColor4f{newBackColor.x(), newBackColor.y(), newBackColor.z(), 1.0f};
|
|
|
|
|
Vector3f newHSV = rgb_to_hsv<Vector3f>(newBackColor);
|
2025-08-18 08:16:29 +03:00
|
|
|
SkColor4f oldToolFrontColor = canvasTheme.toolFrontColor;
|
2025-08-01 20:07:47 +03:00
|
|
|
if(newHSV.z() >= 0.6f)
|
2025-08-01 10:58:45 +03:00
|
|
|
canvasTheme.toolFrontColor = SkColor4f{0.0f, 0.0f, 0.0f, 1.0f};
|
2025-08-01 20:07:47 +03:00
|
|
|
else
|
2025-08-01 10:58:45 +03:00
|
|
|
canvasTheme.toolFrontColor = SkColor4f{1.0f, 1.0f, 1.0f, 1.0f};
|
2025-08-18 08:16:29 +03:00
|
|
|
if(oldToolFrontColor != canvasTheme.toolFrontColor)
|
|
|
|
|
drawProg.clear_draw_cache();
|
2025-08-01 20:07:47 +03:00
|
|
|
if(sendChangeOverNetwork)
|
|
|
|
|
con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_CANVAS_COLOR, newBackColor);
|
2025-08-01 10:58:45 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
void World::send_chat_message(const std::string& message) {
|
|
|
|
|
if(!clientStillConnecting) {
|
2025-10-16 09:38:58 +03:00
|
|
|
con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_CHAT_MESSAGE, message);
|
|
|
|
|
add_chat_message(displayName, message, Toolbar::ChatMessage::Type::NORMAL);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 09:38:58 +03:00
|
|
|
void World::add_chat_message(const std::string& name, const std::string& message, Toolbar::ChatMessage::Type type) {
|
|
|
|
|
Logger::get().log("CHAT", type == Toolbar::ChatMessage::JOIN ? (name + " " + message) : ("[" + name + "] " + message));
|
|
|
|
|
chatMessages.emplace_front(Toolbar::ChatMessage{name, message, type});
|
2025-07-06 20:43:53 +03:00
|
|
|
if(chatMessages.size() == CHAT_SIZE)
|
|
|
|
|
chatMessages.pop_back();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ServerClientID World::get_new_id() {
|
|
|
|
|
nextClientID++;
|
|
|
|
|
return {ownID, nextClientID};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WorldVec World::get_mouse_world_pos() {
|
|
|
|
|
return mousePreviousWorldVec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WorldVec World::get_mouse_world_move() {
|
|
|
|
|
return mouseWorldMove;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::early_destroy() {
|
|
|
|
|
//con.early_destroy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool World::network_being_used() {
|
|
|
|
|
return conType != CONNECTIONTYPE_LOCAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::set_name(const std::string& n) {
|
|
|
|
|
if(n.empty())
|
|
|
|
|
name = "New File";
|
|
|
|
|
else
|
|
|
|
|
name = n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::start_hosting(const std::string& initNetSource, const std::string& serverLocalID) {
|
|
|
|
|
if(conType != CONNECTIONTYPE_LOCAL)
|
|
|
|
|
return;
|
2025-10-27 20:19:47 +03:00
|
|
|
main.init_net_library();
|
2025-07-06 20:43:53 +03:00
|
|
|
con.init_local_p2p(*this, serverLocalID);
|
|
|
|
|
drawProg.init_client_callbacks();
|
|
|
|
|
rMan.init_client_callbacks();
|
|
|
|
|
bMan.init_client_callbacks();
|
2025-09-24 16:18:20 +03:00
|
|
|
gridMan.init_client_callbacks();
|
2025-07-06 20:43:53 +03:00
|
|
|
init_client_callbacks();
|
|
|
|
|
con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_INITIAL_DATA, displayName, true);
|
|
|
|
|
conType = CONNECTIONTYPE_SERVER;
|
|
|
|
|
netSource = initNetSource;
|
|
|
|
|
drawProg.components.set_server_from_client_list();
|
|
|
|
|
clientStillConnecting = true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-28 18:21:09 +03:00
|
|
|
void World::save_to_file(const std::filesystem::path& fileName) {
|
2025-07-06 20:43:53 +03:00
|
|
|
try {
|
|
|
|
|
filePath = force_extension_on_path(fileName, FILE_EXTENSION);
|
|
|
|
|
|
|
|
|
|
std::stringstream f;
|
2025-11-16 16:39:12 +03:00
|
|
|
f.write(VersionConstants::CURRENT_SAVEFILE_HEADER.c_str(), VersionConstants::SAVEFILE_HEADER_LEN);
|
2025-07-28 18:21:09 +03:00
|
|
|
|
|
|
|
|
{
|
|
|
|
|
std::stringstream fWorldDataToCompress;
|
|
|
|
|
{
|
|
|
|
|
cereal::PortableBinaryOutputArchive a(fWorldDataToCompress);
|
2025-11-16 16:39:12 +03:00
|
|
|
save_file(a);
|
2025-07-28 18:21:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<char> compressedData(ZSTD_compressBound(fWorldDataToCompress.view().size()));
|
|
|
|
|
size_t trueCompressedSize = ZSTD_compress(compressedData.data(), compressedData.size(), fWorldDataToCompress.view().data(), fWorldDataToCompress.view().size(), ZSTD_CLEVEL_DEFAULT);
|
|
|
|
|
std::string_view compressedF(compressedData.data(), trueCompressedSize);
|
|
|
|
|
|
|
|
|
|
f << compressedF;
|
|
|
|
|
}
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
set_name(filePath.stem().string());
|
|
|
|
|
|
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
|
|
|
emscripten_browser_file::download(
|
|
|
|
|
filePath.string(),
|
|
|
|
|
"application/octet-stream",
|
|
|
|
|
f.view()
|
|
|
|
|
);
|
2025-07-28 18:21:09 +03:00
|
|
|
#else
|
|
|
|
|
std::ofstream fi(filePath, std::ios::out | std::ios::binary);
|
|
|
|
|
fi << f.view();
|
|
|
|
|
fi.close();
|
2025-07-06 20:43:53 +03:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
Logger::get().log("USERINFO", "File saved");
|
|
|
|
|
}
|
|
|
|
|
catch(const std::exception& e) {
|
|
|
|
|
Logger::get().log("WORLDFATAL", std::string("Save error: ") + e.what());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void World::load_from_file(const std::filesystem::path& fileName, std::string_view buffer) {
|
|
|
|
|
filePath = force_extension_on_path(fileName, FILE_EXTENSION);
|
|
|
|
|
|
2025-07-28 18:21:09 +03:00
|
|
|
std::string byteDataFromFile;
|
2025-07-20 15:24:44 +03:00
|
|
|
|
2025-07-06 20:43:53 +03:00
|
|
|
if(buffer.empty()) {
|
2025-07-28 18:21:09 +03:00
|
|
|
byteDataFromFile = read_file_to_string(fileName);
|
|
|
|
|
buffer = byteDataFromFile;
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
2025-07-28 18:21:09 +03:00
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
if(buffer.size() < VersionConstants::SAVEFILE_HEADER_LEN)
|
2025-09-24 16:18:20 +03:00
|
|
|
throw std::runtime_error("[World::load_from_file] File is not an InfiniPaint canvas (File too small)");
|
2025-07-28 18:21:09 +03:00
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
std::string_view fileHeader = buffer.substr(0, VersionConstants::SAVEFILE_HEADER_LEN);
|
|
|
|
|
VersionNumber fileVersion = VersionConstants::header_to_version_number(std::string(fileHeader));
|
2025-07-28 18:21:09 +03:00
|
|
|
std::string_view uncompressedDataView;
|
|
|
|
|
std::vector<char> uncompressedDataVector;
|
|
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
if(fileVersion < VersionNumber(0, 1, 0))
|
|
|
|
|
uncompressedDataView = std::string_view(buffer.data() + VersionConstants::SAVEFILE_HEADER_LEN, buffer.size() - VersionConstants::SAVEFILE_HEADER_LEN);
|
|
|
|
|
else {
|
|
|
|
|
uncompressedDataVector.resize(ZSTD_getFrameContentSize(buffer.data() + VersionConstants::SAVEFILE_HEADER_LEN, buffer.size() - VersionConstants::SAVEFILE_HEADER_LEN));
|
|
|
|
|
size_t trueUncompressedSize = ZSTD_decompress(uncompressedDataVector.data(), uncompressedDataVector.size(), buffer.data() + VersionConstants::SAVEFILE_HEADER_LEN, buffer.size() - VersionConstants::SAVEFILE_HEADER_LEN);
|
2025-07-28 18:21:09 +03:00
|
|
|
uncompressedDataView = std::string_view(uncompressedDataVector.data(), trueUncompressedSize);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
2025-07-28 18:21:09 +03:00
|
|
|
|
|
|
|
|
ByteMemStream f((char*)uncompressedDataView.data(), uncompressedDataView.size());
|
|
|
|
|
|
2025-11-17 16:08:42 +03:00
|
|
|
Logger::get().log("INFO", "Loading file from version " + version_numbers_to_version_str(fileVersion));
|
|
|
|
|
|
2025-07-28 18:21:09 +03:00
|
|
|
cereal::PortableBinaryInputArchive a(f);
|
2025-11-16 16:39:12 +03:00
|
|
|
load_file(a, fileVersion);
|
2025-09-24 16:18:20 +03:00
|
|
|
nextClientID = get_max_id(ownID);
|
2025-07-06 20:43:53 +03:00
|
|
|
|
|
|
|
|
Logger::get().log("USERINFO", "File loaded");
|
|
|
|
|
set_name(filePath.stem().string());
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 16:18:20 +03:00
|
|
|
ClientPortionID World::get_max_id(ServerPortionID serverID) {
|
|
|
|
|
return std::max(std::max(rMan.get_max_id(serverID), drawProg.get_max_id(serverID)), gridMan.get_max_id(serverID));
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
void World::save_file(cereal::PortableBinaryOutputArchive& a) const {
|
|
|
|
|
a(drawData.cam.c, main.window.size.cast<float>().eval());
|
|
|
|
|
a(convert_vec3<Vector3f>(canvasTheme.backColor));
|
|
|
|
|
drawProg.save_file(a);
|
|
|
|
|
a(bMan);
|
|
|
|
|
a(gridMan);
|
|
|
|
|
rMan.save_strip_unused_resources(a, drawProg.get_used_resources());
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
void World::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
|
2025-07-06 20:43:53 +03:00
|
|
|
CoordSpaceHelper coordsToJumpTo;
|
|
|
|
|
Vector2f windowSizeToJumpTo;
|
|
|
|
|
a(coordsToJumpTo, windowSizeToJumpTo);
|
2025-09-24 16:18:20 +03:00
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
if(version < VersionNumber(0, 1, 0))
|
|
|
|
|
set_canvas_background_color(DEFAULT_CANVAS_BACKGROUND_COLOR);
|
2025-09-24 16:18:20 +03:00
|
|
|
else {
|
2025-08-01 20:07:47 +03:00
|
|
|
Vector3f canvasBackColor;
|
|
|
|
|
a(canvasBackColor);
|
2025-11-17 17:11:12 +03:00
|
|
|
const Vector3f OLD_DEFAULT_CANVAS_BACKGROUND_COLOR{0.12f, 0.12f, 0.12f};
|
|
|
|
|
if(version < VersionNumber(0, 3, 0) && canvasBackColor == OLD_DEFAULT_CANVAS_BACKGROUND_COLOR)
|
|
|
|
|
set_canvas_background_color(DEFAULT_CANVAS_BACKGROUND_COLOR);
|
|
|
|
|
else
|
|
|
|
|
set_canvas_background_color(canvasBackColor);
|
2025-08-01 20:07:47 +03:00
|
|
|
}
|
2025-09-24 16:18:20 +03:00
|
|
|
|
2025-11-16 16:39:12 +03:00
|
|
|
drawData.cam.smooth_move_to(*this, coordsToJumpTo, windowSizeToJumpTo, true);
|
|
|
|
|
drawProg.load_file(a, version);
|
|
|
|
|
a(bMan);
|
|
|
|
|
if(version >= VersionNumber(0, 2, 0))
|
|
|
|
|
a(gridMan);
|
|
|
|
|
a(rMan);
|
2025-07-06 20:43:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WorldScalar World::calculate_zoom_from_uniform_zoom(WorldScalar uniformZoom, WorldVec oldWindowSize) {
|
|
|
|
|
WorldScalar a1(main.window.size.x() / static_cast<double>(oldWindowSize.x()));
|
|
|
|
|
WorldScalar a2(main.window.size.y() / static_cast<double>(oldWindowSize.y()));
|
|
|
|
|
return uniformZoom * ((a1 < a2) ? a1 : a2);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 19:06:30 +03:00
|
|
|
void World::draw_other_player_cursors(SkCanvas* canvas, const DrawData& drawData) {
|
2025-07-06 20:43:53 +03:00
|
|
|
for(auto& [id, data] : clients) {
|
2025-09-30 11:45:03 +03:00
|
|
|
if((drawData.cam.c.inverseScale << 10) > data.camCoords.inverseScale && drawData.clampDrawMinimum < data.camCoords.inverseScale) {
|
2025-07-06 20:43:53 +03:00
|
|
|
canvas->save();
|
|
|
|
|
data.camCoords.transform_sk_canvas(canvas, drawData);
|
|
|
|
|
|
|
|
|
|
canvas->translate(data.cursorPos.x(), data.cursorPos.y());
|
|
|
|
|
|
|
|
|
|
SkPaint p(SkColor4f{data.cursorColor.x(), data.cursorColor.y(), data.cursorColor.z(), 0.2f});
|
|
|
|
|
canvas->drawCircle(0.0f, 0.0f, 4.5f, p);
|
|
|
|
|
p.setStroke(true);
|
|
|
|
|
p.setStrokeWidth(1.0f);
|
|
|
|
|
p.setColor4f({data.cursorColor.x(), data.cursorColor.y(), data.cursorColor.z(), 0.7f});
|
|
|
|
|
canvas->drawCircle(0.0f, 0.0f, 5.0f, p);
|
|
|
|
|
|
|
|
|
|
SkFont f = main.toolbar.io->get_font(18.0f);
|
|
|
|
|
SkFontMetrics metrics;
|
|
|
|
|
f.getMetrics(&metrics);
|
|
|
|
|
|
|
|
|
|
float nextText = f.measureText(data.displayName.c_str(), data.displayName.length(), SkTextEncoding::kUTF8, nullptr);
|
|
|
|
|
Vector2f bounds{nextText, - metrics.fAscent + metrics.fDescent};
|
|
|
|
|
|
|
|
|
|
SkPaint p2(SkColor4f{data.cursorColor.x(), data.cursorColor.y(), data.cursorColor.z(), 0.5f});
|
|
|
|
|
canvas->drawSimpleText(data.displayName.c_str(), data.displayName.length(), SkTextEncoding::kUTF8, 6.0f, -metrics.fDescent, f, p2);
|
|
|
|
|
|
|
|
|
|
SkPaint p3(color_mul_alpha(main.toolbar.io->theme->backColor1, 0.5f));
|
|
|
|
|
canvas->drawRoundRect(SkRect::MakeXYWH(6.0f, -bounds.y(), bounds.x(), bounds.y()), 3.0f, 3.0f, p3);
|
|
|
|
|
|
|
|
|
|
canvas->restore();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-06 19:06:30 +03:00
|
|
|
|
2025-09-30 16:36:09 +03:00
|
|
|
void World::scale_up_step() {
|
|
|
|
|
canvasScale++;
|
|
|
|
|
con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_CANVAS_SCALE, canvasScale);
|
|
|
|
|
scale_up(WorldScalar(CANVAS_SCALE_UP_STEP));
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-30 11:45:03 +03:00
|
|
|
void World::scale_up(const WorldScalar& scaleUpAmount) {
|
|
|
|
|
Logger::get().log("USERINFO", "Canvas scaled up");
|
|
|
|
|
bMan.scale_up(scaleUpAmount);
|
|
|
|
|
gridMan.scale_up(scaleUpAmount);
|
|
|
|
|
drawData.cam.scale_up(*this, scaleUpAmount);
|
2025-09-30 16:36:09 +03:00
|
|
|
// drawProg will be sending info on committed objects/modified grids. These objects will be scaled up already.
|
|
|
|
|
// This means that the scale up message must be send BEFORE the World::scale_up function is called on our end
|
|
|
|
|
// if this client is the one responsible for the scale up
|
|
|
|
|
drawProg.scale_up(scaleUpAmount);
|
|
|
|
|
|
|
|
|
|
undo.clear(); // Do last to make sure that any undo generated while scaling up is removed, as it contains outdated scale info
|
2025-09-30 11:45:03 +03:00
|
|
|
}
|
|
|
|
|
|
2025-09-06 19:06:30 +03:00
|
|
|
void World::draw(SkCanvas* canvas) {
|
|
|
|
|
drawData.refresh_draw_optimizing_values();
|
|
|
|
|
if(drawData.drawGrids)
|
2025-10-01 21:31:16 +03:00
|
|
|
gridMan.draw_back(canvas, drawData);
|
2025-09-06 19:06:30 +03:00
|
|
|
drawProg.draw(canvas, drawData);
|
2025-10-01 21:31:16 +03:00
|
|
|
if(drawData.drawGrids) {
|
|
|
|
|
gridMan.draw_front(canvas, drawData);
|
2025-09-25 10:21:47 +03:00
|
|
|
gridMan.draw_coordinates(canvas, drawData);
|
2025-10-01 21:31:16 +03:00
|
|
|
}
|
2025-09-06 19:06:30 +03:00
|
|
|
draw_other_player_cursors(canvas, drawData);
|
|
|
|
|
}
|