/* (c) 2010 Perttu Ahola Minetest TODO: Check for obsolete todos TODO: Storing map on disk, preferably dynamically TODO: struct settings, with a mutex and get/set functions TODO: Implement torches and similar light sources (halfway done) TODO: A menu TODO: A cache class that can be used with lightNeighbors, unlightNeighbors and probably many others. Look for implementation in lightNeighbors TODO: Proper objects for random stuff in this file, like g_selected_material TODO: See if changing to 32-bit position variables doesn't raise memory consumption a lot. Now: TODO: Have to implement mutexes to MapSectors; otherwise initial lighting might fail TODO: Adding more heightmap points to MapSectors Network protocol: - Client map data is only updated from the server's, EXCEPT FOR lighting. Actions: - User places block -> Client sends PLACED_BLOCK(pos, node) -> Server validates and sends MAP_SINGLE_CHANGE(pos, node) -> Client applies change and recalculates lighting and face cache - User starts digging -> Client sends START_DIGGING(pos) -> Server starts timer - if user stops digging: -> Client sends STOP_DIGGING -> Server stops timer - if user continues: -> Server waits timer -> Server sends MAP_SINGLE_CHANGE(pos, node) -> Client applies change and recalculates lighting and face cache */ /* Setting this to 1 enables a special camera mode that forces the renderers to think that the camera statically points from the starting place to a static direction. This allows one to move around with the player and see what is actually drawn behind solid things etc. */ #define FIELD_OF_VIEW_TEST 0 // Enable unit tests #define ENABLE_TESTS 0 #ifdef _MSC_VER #pragma comment(lib, "Irrlicht.lib") #pragma comment(lib, "jthread.lib") // This would get rid of the console window //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") #endif #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #define sleep_ms(x) Sleep(x) #else #include #define sleep_ms(x) usleep(x*1000) #endif #include #include #include namespace jthread {} // JThread 1.2 support using namespace jthread; // JThread 1.3 support #include "common_irrlicht.h" #include "map.h" #include "player.h" #include "main.h" #include "test.h" #include "environment.h" #include "server.h" #include "client.h" #include const char *g_material_filenames[MATERIALS_COUNT] = { "../data/stone.png", "../data/grass.png", "../data/water.png", }; #define FPS_MIN 15 #define FPS_MAX 25 #define VIEWING_RANGE_NODES_MIN MAP_BLOCKSIZE #define VIEWING_RANGE_NODES_MAX 35 JMutex g_viewing_range_nodes_mutex; s16 g_viewing_range_nodes = MAP_BLOCKSIZE; /* Random stuff */ u16 g_selected_material = 0; /* Debug streams - use these to disable or enable outputs of parts of the program */ std::ofstream dfile("debug.txt"); //std::ofstream dfile2("debug2.txt"); // Connection //std::ostream dout_con(std::cout.rdbuf()); std::ostream dout_con(dfile.rdbuf()); // Server //std::ostream dout_server(std::cout.rdbuf()); std::ostream dout_server(dfile.rdbuf()); // Client //std::ostream dout_client(std::cout.rdbuf()); std::ostream dout_client(dfile.rdbuf()); /* TimeTaker */ class TimeTaker { public: TimeTaker(const char *name, IrrlichtDevice *dev) { m_name = name; m_dev = dev; m_time1 = m_dev->getTimer()->getRealTime(); } ~TimeTaker() { u32 time2 = m_dev->getTimer()->getRealTime(); u32 dtime = time2 - m_time1; std::cout< 0){ counter -= frametime; return; } counter = 5.0; //seconds g_viewing_range_nodes_mutex.Lock(); bool changed = false; if(frametime > 1.0/FPS_MIN || g_viewing_range_nodes > VIEWING_RANGE_NODES_MAX){ if(g_viewing_range_nodes > VIEWING_RANGE_NODES_MIN){ g_viewing_range_nodes -= MAP_BLOCKSIZE/2; changed = true; } } else if(frametime < 1.0/FPS_MAX || g_viewing_range_nodes < VIEWING_RANGE_NODES_MIN){ if(g_viewing_range_nodes < VIEWING_RANGE_NODES_MAX){ g_viewing_range_nodes += MAP_BLOCKSIZE/2; changed = true; } } if(changed){ std::cout<<"g_viewing_range_nodes = " <getTimer()->getRealTime(); tempf = 0.001; for(u32 i=0; i<10000000; i++){ temp16 += tempf; tempf += 0.001; } u32 time2 = device->getTimer()->getRealTime(); u32 fp_conversion_time = time2 - time1; std::cout<<"Done. "<getTimer()->getRealTime(); tempv3f1 = v3f(1,2,3); tempv3f2 = v3f(4,5,6); for(u32 i=0; i<40000000; i++){ tempf += tempv3f1.dotProduct(tempv3f2); tempv3f2 += v3f(7,8,9); } u32 time2 = device->getTimer()->getRealTime(); u32 dtime = time2 - time1; std::cout<<"Done. "<getTimer()->getRealTime(); core::map map1; tempf = -324; for(s16 y=0; y<500; y++){ for(s16 x=0; x<500; x++){ map1.insert(v2s16(x,y), tempf); tempf += 1; } } for(s16 y=500-1; y>=0; y--){ for(s16 x=0; x<500; x++){ tempf = map1[v2s16(x,y)]; } } u32 time2 = device->getTimer()->getRealTime(); u32 dtime = time2 - time1; std::cout<<"Done. "<getTimer()->getRealTime(); u32 time2 = time1; JMutex m; m.Init(); u32 n = 0; u32 i = 0; do{ n += 10000; for(; igetTimer()->getRealTime(); } // Do at least 10ms while(time2 < time1 + 10); u32 dtime = time2 - time1; u32 per_ms = n / dtime; std::cout<<"Done. "< "<>r0; if(r0 > res_count || r0 == 0) r0 = 0; u16 screenW = resolutions[r0-1][0]; u16 screenH = resolutions[r0-1][1]; */ // video::E_DRIVER_TYPE driverType; #ifdef _WIN32 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work driverType = video::EDT_OPENGL; #else driverType = video::EDT_OPENGL; #endif IrrlichtDevice *device; device = createDevice(driverType, core::dimension2d(screenW, screenH), 16, false, false, false, &receiver); if (device == 0) return 1; // could not create selected driver. /* Run some speed tests */ //SpeedTests(device); /* Continue initialization */ video::IVideoDriver* driver = device->getVideoDriver(); // These make the textures not to show at all //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT); //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED ); scene::ISceneManager* smgr = device->getSceneManager(); gui::IGUIEnvironment* guienv = device->getGUIEnvironment(); gui::IGUISkin* skin = guienv->getSkin(); gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png"); if(font) skin->setFont(font); //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0)); skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255)); //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0)); //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0)); skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0)); skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0)); const wchar_t *text = L"Loading..."; core::vector2d center(screenW/2, screenH/2); core::dimension2d textd = font->getDimension(text); std::cout<<"Text w="<getTexture(filename)); } //materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT); materials[i].setFlag(video::EMF_BILINEAR_FILTER, false); //materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false); } // Make a scope here for the client so that it gets removed // before the irrlicht device { std::cout<<"Creating server and client"<start(port); } Client client(smgr, materials); Address connect_address(0,0,0,0, port); try{ connect_address.Resolve(connect_name); } catch(ResolveError &e) { std::cout<<"Couldn't resolve address"<addCameraSceneNode( 0, // Camera parent v3f(BS*100, BS*2, BS*100), // Look from v3f(BS*100+1, BS*2, BS*100), // Look to -1 // Camera ID ); if(camera == NULL) return 1; camera->setFOV(FOV_ANGLE); // Just so big a value that everything rendered is visible camera->setFarValue(BS*1000); f32 camera_yaw = 0; // "right/left" f32 camera_pitch = 0; // "up/down" // Random constants #define WALK_ACCELERATION (4.0 * BS) #define WALKSPEED_MAX (4.0 * BS) //#define WALKSPEED_MAX (20.0 * BS) f32 walk_acceleration = WALK_ACCELERATION; f32 walkspeed_max = WALKSPEED_MAX; /* The mouse cursor needs not be visible, so we hide it via the irr::IrrlichtDevice::ICursorControl. */ device->getCursorControl()->setVisible(false); gui_loadingtext->remove(); gui::IGUIStaticText *guitext = guienv->addStaticText( L"Minetest-c55", core::rect(5, 5, 5+300, 5+textsize.Y), false, false); /* Main loop */ bool first_loop_after_window_activation = true; s32 lastFPS = -1; // Time is in milliseconds u32 lasttime = device->getTimer()->getTime(); while(device->run()) { /* Time difference calculation */ u32 time = device->getTimer()->getTime(); f32 dtime; // in seconds if(time > lasttime) dtime = (time - lasttime) / 1000.0; else dtime = 0; lasttime = time; updateViewingRange(dtime); // Collected during the loop and displayed core::list< core::aabbox3d > hilightboxes; /* Special keys */ if(receiver.IsKeyDown(irr::KEY_ESCAPE)) { break; } /* Player speed control */ v3f move_direction = v3f(0,0,1); move_direction.rotateXZBy(camera_yaw); v3f speed = v3f(0,0,0); if(receiver.IsKeyDown(irr::KEY_KEY_W)) { speed += move_direction; } if(receiver.IsKeyDown(irr::KEY_KEY_S)) { speed -= move_direction; } if(receiver.IsKeyDown(irr::KEY_KEY_A)) { speed += move_direction.crossProduct(v3f(0,1,0)); } if(receiver.IsKeyDown(irr::KEY_KEY_D)) { speed += move_direction.crossProduct(v3f(0,-1,0)); } if(receiver.IsKeyDown(irr::KEY_SPACE)) { if(player->touching_ground){ //player_speed.Y = 30*BS; //player.speed.Y = 5*BS; player->speed.Y = 6.5*BS; } } // The speed of the player (Y is ignored) speed = speed.normalize() * walkspeed_max; f32 inc = walk_acceleration * BS * dtime; if(player->speed.X < speed.X - inc) player->speed.X += inc; else if(player->speed.X > speed.X + inc) player->speed.X -= inc; else if(player->speed.X < speed.X) player->speed.X = speed.X; else if(player->speed.X > speed.X) player->speed.X = speed.X; if(player->speed.Z < speed.Z - inc) player->speed.Z += inc; else if(player->speed.Z > speed.Z + inc) player->speed.Z -= inc; else if(player->speed.Z < speed.Z) player->speed.Z = speed.Z; else if(player->speed.Z > speed.Z) player->speed.Z = speed.Z; /* Process environment */ { //TimeTaker("client.step(dtime)", device); client.step(dtime); } if(server != NULL){ //TimeTaker("server->step(dtime)", device); server->step(dtime); } /* Mouse and camera control */ if(device->isWindowActive()) { if(first_loop_after_window_activation){ first_loop_after_window_activation = false; } else{ s32 dx = device->getCursorControl()->getPosition().X - 320; s32 dy = device->getCursorControl()->getPosition().Y - 240; camera_yaw -= dx*0.2; camera_pitch += dy*0.2; if(camera_pitch < -89.9) camera_pitch = -89.9; if(camera_pitch > 89.9) camera_pitch = 89.9; } device->getCursorControl()->setPosition(320, 240); } else{ first_loop_after_window_activation = true; } v3f camera_direction = v3f(0,0,1); camera_direction.rotateYZBy(camera_pitch); camera_direction.rotateXZBy(camera_yaw); v3f camera_position = player->getPosition() + v3f(0, BS+BS/2, 0); camera->setPosition(camera_position); camera->setTarget(camera_position + camera_direction); if(FIELD_OF_VIEW_TEST){ //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1)); client.updateCamera(v3f(0,0,0), v3f(0,0,1)); } else{ //client.m_env.getMap().updateCamera(camera_position, camera_direction); client.updateCamera(camera_position, camera_direction); } /* Calculate what block is the crosshair pointing to */ //u32 t1 = device->getTimer()->getTime(); f32 d = 4; // max. distance core::line3d shootline(camera_position, camera_position + camera_direction * BS * (d+1)); bool nodefound = false; v3s16 nodepos; v3s16 neighbourpos; core::aabbox3d nodefacebox; f32 mindistance = BS * 1001; v3s16 pos_i = Map::floatToInt(player->getPosition()); s16 a = d; s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1); s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1); s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1); s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1); s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1); s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1); for(s16 y = ystart; y <= yend; y++){ for(s16 z = zstart; z <= zend; z++){ for(s16 x = xstart; x <= xend; x++) { try{ //if(client.m_env.getMap().getNode(x,y,z).d == MATERIAL_AIR){ if(client.getNode(v3s16(x,y,z)).d == MATERIAL_AIR){ continue; } }catch(InvalidPositionException &e){ continue; } v3s16 np(x,y,z); v3f npf = Map::intToFloat(np); f32 d = 0.01; v3s16 directions[6] = { v3s16(0,0,1), // back v3s16(0,1,0), // top v3s16(1,0,0), // right v3s16(0,0,-1), v3s16(0,-1,0), v3s16(-1,0,0), }; for(u16 i=0; i<6; i++){ //{u16 i=3; v3f dir_f = v3f(directions[i].X, directions[i].Y, directions[i].Z); v3f centerpoint = npf + dir_f * BS/2; f32 distance = (centerpoint - camera_position).getLength(); if(distance < mindistance){ //std::cout<<"for centerpoint=("< m; m.buildRotateFromTo(v3f(0,0,1), dir_f); // This is the back face v3f corners[2] = { v3f(BS/2, BS/2, BS/2), v3f(-BS/2, -BS/2, BS/2+d) }; for(u16 j=0; j<2; j++){ m.rotateVect(corners[j]); corners[j] += npf; //std::cout<<"box corners["< facebox(corners[0],corners[1]); core::aabbox3d facebox(corners[0]); facebox.addInternalPoint(corners[1]); if(facebox.intersectsWithLine(shootline)){ nodefound = true; nodepos = np; neighbourpos = np + directions[i]; mindistance = distance; nodefacebox = facebox; } } } }}} if(nodefound){ //std::cout<<"nodefound == true"<setText(positiontext);*/ } hilightboxes.push_back(nodefacebox); if(receiver.leftclicked){ std::cout<<"Removing block (MapNode)"<getTimer()->getRealTime(); //client.m_env.getMap().removeNodeAndUpdate(nodepos); client.removeNode(nodepos); u32 time2 = device->getTimer()->getRealTime(); u32 dtime = time2 - time1; std::cout<<"Took "<getTimer()->getRealTime(); /*f32 light = client.m_env.getMap().getNode(neighbourpos).light; MapNode n; n.d = g_selected_material; client.m_env.getMap().setNode(neighbourpos, n); client.m_env.getMap().nodeAddedUpdate(neighbourpos, light);*/ MapNode n; n.d = g_selected_material; client.addNode(neighbourpos, n); u32 time2 = device->getTimer()->getRealTime(); u32 dtime = time2 - time1; std::cout<<"Took "<setText(L""); } receiver.leftclicked = false; receiver.rightclicked = false; /* Update gui stuff */ static u8 old_selected_material = MATERIAL_AIR; if(g_selected_material != old_selected_material) { old_selected_material = g_selected_material; wchar_t temptext[50]; swprintf(temptext, 50, L"Minetest-c55 (F: material=%i)", g_selected_material); guitext->setText(temptext); } /* Drawing begins */ /* Background color is choosen based on whether the player is much beyond the initial ground level */ /*video::SColor bgcolor; v3s16 p0 = Map::floatToInt(player->position); s16 gy = client.m_env.getMap().getGroundHeight(v2s16(p0.X, p0.Z)); if(p0.Y > gy - MAP_BLOCKSIZE) bgcolor = video::SColor(255,90,140,200); else bgcolor = video::SColor(255,0,0,0);*/ video::SColor bgcolor = video::SColor(255,90,140,200); driver->beginScene(true, true, bgcolor); //std::cout<<"smgr->drawAll()"<drawAll(); core::vector2d displaycenter(screenW/2,screenH/2); driver->draw2DLine(displaycenter - core::vector2d(10,0), displaycenter + core::vector2d(10,0), video::SColor(255,255,255,255)); driver->draw2DLine(displaycenter - core::vector2d(0,10), displaycenter + core::vector2d(0,10), video::SColor(255,255,255,255)); video::SMaterial m; m.Thickness = 10; m.Lighting = false; driver->setMaterial(m); for(core::list< core::aabbox3d >::Iterator i=hilightboxes.begin(); i != hilightboxes.end(); i++){ driver->draw3DBox(*i, video::SColor(255,0,0,0)); } guienv->drawAll(); driver->endScene(); /* Drawing ends */ u16 fps = driver->getFPS(); if (lastFPS != fps) { core::stringw str = L"Minetest ["; str += driver->getName(); str += "] FPS:"; str += fps; device->setWindowCaption(str.c_str()); lastFPS = fps; } /*} else device->yield();*/ } if(server != NULL) delete server; } // client is deleted at this point /* In the end, delete the Irrlicht device. */ device->drop(); return 0; } //END