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-12-08 22:21:07 +03:00
# include "Helpers/NetworkingObjects/NetObjManager.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 ) :
2025-12-09 13:00:38 +03:00
netObjMan ( initMain . netObjectTypeList , worldInfo . conType ! = CONNECTIONTYPE_CLIENT ) ,
2025-07-06 20:43:53 +03:00
main ( initMain ) ,
rMan ( * this ) ,
drawProg ( * this ) ,
2025-09-06 19:06:30 +03:00
bMan ( * this ) ,
2025-12-09 13:00:38 +03:00
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 ;
2025-12-09 13:00:38 +03:00
con . connect_p2p ( * this , netSource ) ;
2025-07-06 20:43:53 +03:00
set_name ( " " ) ;
break ;
}
case CONNECTIONTYPE_LOCAL : {
2025-11-21 08:35:47 +03:00
if ( worldInfo . filePathSource . has_value ( ) )
load_from_file ( worldInfo . filePathSource . value ( ) , worldInfo . fileDataBuffer ) ;
2025-07-06 20:43:53 +03:00
set_name ( name ) ;
break ;
}
case CONNECTIONTYPE_SERVER : {
break ;
}
}
drawProg . init_client_callbacks ( ) ;
rMan . 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 ;
2025-11-30 11:47:57 +03:00
CoordSpaceHelper camCoordsToMoveTo ;
Vector2f windowSizeToMoveTo ;
message ( displayName , camCoordsToMoveTo , windowSizeToMoveTo , fileDisplayName , clients ) ;
2025-07-08 12:18:55 +03:00
set_name ( fileDisplayName ) ;
2025-11-30 11:47:57 +03:00
drawData . cam . smooth_move_to ( * main . world , camCoordsToMoveTo , windowSizeToMoveTo , true ) ;
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-12-09 19:29:58 +03:00
message ( gridMan ) ;
bMan . bookmarks = netObjMan . read_create_message < NetworkingObjects : : NetObjOrderedList < Bookmark > > ( message , nullptr ) ;
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
} ) ;
2025-12-09 13:00:38 +03:00
con . client_add_recv_callback ( CLIENT_UPDATE_NETWORK_OBJECT , [ & ] ( cereal : : PortableBinaryInputArchive & message ) {
2025-12-09 19:29:58 +03:00
if ( ! con . host_exists ( ) & & ! clientStillConnecting ) // Dont update a direct connected client (which is just the server), and dont process these until the CLIENT_INITIAL_DATA message is received first
2025-12-09 13:00:38 +03:00
netObjMan . read_update_message ( message , nullptr ) ;
} ) ;
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 ) ;
}
} ) ;
2025-11-30 11:47:57 +03:00
con . client_add_recv_callback ( CLIENT_MOVE_CAMERA , [ & ] ( cereal : : PortableBinaryInputArchive & message ) {
2025-07-06 20:43:53 +03:00
ServerPortionID id ;
message ( id ) ;
auto & c = clients [ id ] ;
2025-11-30 11:47:57 +03:00
message ( c . camCoords , c . windowSize ) ;
} ) ;
con . client_add_recv_callback ( CLIENT_MOVE_SCREEN_MOUSE , [ & ] ( cereal : : PortableBinaryInputArchive & message ) {
ServerPortionID id ;
message ( id ) ;
auto & c = clients [ id ] ;
message ( c . cursorPos ) ;
2025-07-06 20:43:53 +03:00
} ) ;
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 " ) ;
2025-12-09 17:20:27 +03:00
setToDestroy = true ;
return ;
2025-07-06 20:43:53 +03:00
}
if ( ! clientStillConnecting ) {
2025-11-30 11:47:57 +03:00
constexpr float SECONDS_TO_SEND_CAMERA_DATA = 0.5f ;
timeToSendCameraData . update_time_since ( ) ;
if ( timeToSendCameraData . get_time_since ( ) > SECONDS_TO_SEND_CAMERA_DATA ) {
timeToSendCameraData . update_time_point ( ) ;
if ( ! sentCameraData . has_value ( ) | | ( sentCameraData . value ( ) . windowSize ! = main . window . size . cast < float > ( ) . eval ( ) | | sentCameraData . value ( ) . camTransform ! = drawData . cam . c ) ) {
con . client_send_items_to_server ( RELIABLE_COMMAND_CHANNEL , SERVER_MOVE_CAMERA , drawData . cam . c , main . window . size . cast < float > ( ) . eval ( ) ) ;
sentCameraData = {
. camTransform = drawData . cam . c ,
. windowSize = main . window . size . cast < float > ( )
} ;
}
}
con . client_send_items_to_server ( UNRELIABLE_COMMAND_CHANNEL , SERVER_MOVE_SCREEN_MOUSE , main . input . mouse . pos ) ;
2025-07-06 20:43:53 +03:00
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-12-09 17:20:27 +03:00
if ( con . is_client_disconnected ( ) ) {
Logger : : get ( ) . log ( " USERINFO " , " Client connection failed " ) ;
setToDestroy = true ;
}
2025-07-06 20:43:53 +03:00
}
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 ( ) ;
2025-09-24 16:18:20 +03:00
gridMan . init_client_callbacks ( ) ;
2025-07-06 20:43:53 +03:00
init_client_callbacks ( ) ;
2025-11-30 11:47:57 +03:00
con . client_send_items_to_server ( RELIABLE_COMMAND_CHANNEL , SERVER_INITIAL_DATA , displayName , true , drawData . cam . c , main . window . size . cast < float > ( ) . eval ( ) ) ;
2025-07-06 20:43:53 +03:00
conType = CONNECTIONTYPE_SERVER ;
netSource = initNetSource ;
drawProg . components . set_server_from_client_list ( ) ;
clientStillConnecting = true ;
}
2025-11-21 08:35:47 +03:00
void World : : save_to_file ( const std : : filesystem : : path & filePathToSaveAt ) {
2025-07-06 20:43:53 +03:00
try {
2025-11-21 08:35:47 +03:00
filePath = force_extension_on_path ( filePathToSaveAt , FILE_EXTENSION ) ;
2025-07-06 20:43:53 +03:00
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 ( ) ) ;
}
}
2025-11-21 08:35:47 +03:00
void World : : load_from_file ( const std : : filesystem : : path & filePathToLoadFrom , std : : string_view buffer ) {
filePath = force_extension_on_path ( filePathToLoadFrom , FILE_EXTENSION ) ;
2025-07-06 20:43:53 +03:00
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-11-21 08:35:47 +03:00
byteDataFromFile = read_file_to_string ( filePath ) ;
2025-07-28 18:21:09 +03:00
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 ) ;
}