From 12e9f3d010f887c3fe0b32f3ddbf25f2ef6b7323 Mon Sep 17 00:00:00 2001 From: sapier Date: Wed, 16 Jan 2013 23:27:11 +0000 Subject: [PATCH 01/43] add limit for steps to avoid runaway memory consumption by collision handling --- src/collision.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/collision.cpp b/src/collision.cpp index 143b559fa..eb948e3c3 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -199,6 +199,10 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef, /* Calculate new velocity */ + if( dtime > 0.5 ) { + infostream<<"collisionMoveSimple: WARNING: maximum step interval exceeded, lost movement details!"< Date: Fri, 1 Feb 2013 15:50:33 +0100 Subject: [PATCH 02/43] Remove use of operator[] on a std::map, so no spurious elements get inserted. (fixes #464) All uses of operator[] in guiConfigureWorld.cpp have been replaced with .find() operations. In some places, operator[] had been called with mod names of "Add-Ons" or "N/A", which would insert a default-constructed ModSpec into the list of add-on mods. In other places, the use of operator[] was safe now, but it probably is safer for future changes to use .find() everywhere. --- src/guiConfigureWorld.cpp | 83 ++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/src/guiConfigureWorld.cpp b/src/guiConfigureWorld.cpp index 38f1a554d..fdcd0d3e8 100644 --- a/src/guiConfigureWorld.cpp +++ b/src/guiConfigureWorld.cpp @@ -414,8 +414,9 @@ bool GUIConfigureWorld::OnEvent(const SEvent& event) else if(selected_node != NULL && selected_node->getText() != NULL) { std::string modname = wide_to_narrow(selected_node->getText()); - ModSpec mod = m_addonmods[modname]; - enableAllMods(mod.modpack_content,true); + std::map::iterator mod_it = m_addonmods.find(modname); + if(mod_it != m_addonmods.end()) + enableAllMods(mod_it->second.modpack_content,true); } return true; } @@ -427,8 +428,9 @@ bool GUIConfigureWorld::OnEvent(const SEvent& event) if(selected_node != NULL && selected_node->getText() != NULL) { std::string modname = wide_to_narrow(selected_node->getText()); - ModSpec mod = m_addonmods[modname]; - enableAllMods(mod.modpack_content,false); + std::map::iterator mod_it = m_addonmods.find(modname); + if(mod_it != m_addonmods.end()) + enableAllMods(mod_it->second.modpack_content,false); } return true; } @@ -446,7 +448,7 @@ bool GUIConfigureWorld::OnEvent(const SEvent& event) return true; } if(event.GUIEvent.EventType==gui::EGET_TREEVIEW_NODE_SELECT && - event.GUIEvent.Caller->getID() == GUI_ID_MOD_TREEVIEW) + event.GUIEvent.Caller->getID() == GUI_ID_MOD_TREEVIEW) { selecting_dep = -1; selecting_rdep = -1; @@ -560,42 +562,49 @@ void GUIConfigureWorld::adjustSidebar() modname_w = L"N/A"; std::string modname = wide_to_narrow(modname_w); - // if no mods installed, don't show buttons or checkbox on the sidebar + ModSpec mspec; + std::map::iterator it = m_addonmods.find(modname); + if(it != m_addonmods.end()) + mspec = it->second; + + m_dependencies_listbox->clear(); + m_rdependencies_listbox->clear(); + + // if no mods installed, there is nothing to enable/disable, so we + // don't show buttons or checkbox on the sidebar if(node->getParent() == m_treeview->getRoot() && !node->hasChilds()) { m_disableall->setVisible(false); m_enableall->setVisible(false); m_enabled_checkbox->setVisible(false); + return; } - else + + // a modpack is not enabled/disabled by itself, only its cotnents + // are. so we show show enable/disable all buttons, but hide the + // checkbox + if(node->getParent() == m_treeview->getRoot() || + mspec.is_modpack) { - // if modpack, show enable/disable all buttons. otherwise, show - // enabled checkbox - if(node->getParent() == m_treeview->getRoot() || - m_addonmods[modname].is_modpack) - { - m_enabled_checkbox->setVisible(false); - m_disableall->setVisible(true); - m_enableall->setVisible(true); - m_modname_text->setText((L"Modpack: "+modname_w).c_str()); - } - else - { - m_disableall->setVisible(false); - m_enableall->setVisible(false); - m_enabled_checkbox->setVisible(true); - m_modname_text->setText((L"Mod: "+modname_w).c_str()); - } - } + m_enabled_checkbox->setVisible(false); + m_disableall->setVisible(true); + m_enableall->setVisible(true); + m_modname_text->setText((L"Modpack: "+modname_w).c_str()); + return; + } + + // for a normal mod, we hide the enable/disable all buttons, but show the checkbox. + m_disableall->setVisible(false); + m_enableall->setVisible(false); + m_enabled_checkbox->setVisible(true); + m_modname_text->setText((L"Mod: "+modname_w).c_str()); + // the mod is enabled unless it is disabled in the world.mt settings. bool mod_enabled = true; if(m_settings.exists("load_mod_"+modname)) mod_enabled = m_settings.getBool("load_mod_"+modname); m_enabled_checkbox->setChecked(mod_enabled); - // dependencies of this mod: - m_dependencies_listbox->clear(); - ModSpec mspec = m_addonmods[modname]; for(std::set::iterator it=mspec.depends.begin(); it != mspec.depends.end(); ++it) { @@ -611,9 +620,7 @@ void GUIConfigureWorld::adjustSidebar() m_dependencies_listbox->addItem(narrow_to_wide(dependency).c_str()); } - // reverse dependencies of this mod: - m_rdependencies_listbox->clear(); std::pair< std::multimap::iterator, std::multimap::iterator > rdep = m_reverse_depends.equal_range(modname); @@ -639,19 +646,25 @@ void GUIConfigureWorld::enableAllMods(std::map mods,bool e enableAllMods(mod.modpack_content,enable); else // not a modpack setEnabled(mod.name, enable); + } } void GUIConfigureWorld::enableMod(std::string modname) { + std::map::iterator mod_it = m_addonmods.find(modname); + if(mod_it == m_addonmods.end()){ + errorstream << "enableMod() called with invalid mod name \"" << modname << "\"" << std::endl; + return; + } + ModSpec mspec = mod_it->second; m_settings.setBool("load_mod_"+modname,true); std::map::iterator it = m_nodes.find(modname); - if(it != m_nodes.end()) + if(it != m_nodes.end()) (*it).second->setIcon(CHECKMARK_STR); m_new_mod_names.erase(modname); //also enable all dependencies - ModSpec mspec = m_addonmods[modname]; for(std::set::iterator it=mspec.depends.begin(); it != mspec.depends.end(); ++it) { @@ -664,6 +677,12 @@ void GUIConfigureWorld::enableMod(std::string modname) void GUIConfigureWorld::disableMod(std::string modname) { + std::map::iterator mod_it = m_addonmods.find(modname); + if(mod_it == m_addonmods.end()){ + errorstream << "disableMod() called with invalid mod name \"" << modname << "\"" << std::endl; + return; + } + m_settings.setBool("load_mod_"+modname,false); std::map::iterator it = m_nodes.find(modname); From 32552fede5b941332819be6e8768b6945d0dd86b Mon Sep 17 00:00:00 2001 From: ShadowNinja Date: Sat, 9 Feb 2013 16:32:59 -0500 Subject: [PATCH 03/43] Don't see through nodes without noclip privileges --- src/clientmap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clientmap.cpp b/src/clientmap.cpp index 800549a3b..0b30453b8 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -864,7 +864,7 @@ void ClientMap::renderPostFx() // - If the player is in liquid, draw a semi-transparent overlay. const ContentFeatures& features = nodemgr->get(n); video::SColor post_effect_color = features.post_effect_color; - if(features.solidness == 2 && g_settings->getBool("free_move") == false) + if(features.solidness == 2 && !(g_settings->getBool("noclip") && m_gamedef->checkLocalPrivilege("noclip"))) { post_effect_color = video::SColor(255, 0, 0, 0); } From 1cda39d5bb1da90116ec9fb3da913634312b62fa Mon Sep 17 00:00:00 2001 From: ShadowNinja Date: Sun, 3 Feb 2013 22:22:42 -0500 Subject: [PATCH 04/43] Don't fall off nodes if sneaking with free_move on but without fly privileges --- src/localplayer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/localplayer.cpp b/src/localplayer.cpp index b6dd0f42e..2d0d77140 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -159,7 +159,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, If sneaking, keep in range from the last walked node and don't fall off from it */ - if(control.sneak && m_sneak_node_exists && !g_settings->getBool("free_move")) + if(control.sneak && m_sneak_node_exists && !(fly_allowed && g_settings->getBool("free_move"))) { f32 maxd = 0.5*BS + sneak_max; v3f lwn_f = intToFloat(m_sneak_node, BS); From 6b1420a58af7472880bebb9d0f89be987bc25da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Doser?= Date: Fri, 25 Jan 2013 15:52:53 +0100 Subject: [PATCH 05/43] Disable backface culling for drawtype plantlike and only draw 2 faces instead of 4 This way, plants actually show the real backface on their back side, i.e., the front face mirrored around the vertical axis, instead of showing the front face on both sides. This looked weird when the texture was not symmetrical around the vertical axis. --- src/content_mapblock.cpp | 12 +----------- src/nodedef.cpp | 5 ++++- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index aa3c061d6..290890490 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -733,7 +733,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data, u16 l = getInteriorLight(n, 1, data); video::SColor c = MapBlock_LightColor(255, l, decode_light(f.light_source)); - for(u32 j=0; j<4; j++) + for(u32 j=0; j<2; j++) { video::S3DVertex vertices[4] = { @@ -759,16 +759,6 @@ void mapblock_mesh_generate_special(MeshMakeData *data, for(u16 i=0; i<4; i++) vertices[i].Pos.rotateXZBy(-45); } - else if(j == 2) - { - for(u16 i=0; i<4; i++) - vertices[i].Pos.rotateXZBy(135); - } - else if(j == 3) - { - for(u16 i=0; i<4; i++) - vertices[i].Pos.rotateXZBy(-135); - } for(u16 i=0; i<4; i++) { diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 560c6090d..729c69c92 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -604,9 +604,12 @@ public: } } break; + case NDT_PLANTLIKE: + f->solidness = 0; + f->backface_culling = false; + break; case NDT_TORCHLIKE: case NDT_SIGNLIKE: - case NDT_PLANTLIKE: case NDT_FENCELIKE: case NDT_RAILLIKE: case NDT_NODEBOX: From 2c362badd401dc16d32b543ed15bfcad44f9163b Mon Sep 17 00:00:00 2001 From: PilzAdam Date: Sun, 20 Jan 2013 16:21:09 +0100 Subject: [PATCH 06/43] Dont remove grass under unloaded blocks --- src/content_abm.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/content_abm.cpp b/src/content_abm.cpp index 9e65a7ab0..ce1751117 100644 --- a/src/content_abm.cpp +++ b/src/content_abm.cpp @@ -80,7 +80,8 @@ public: ServerMap *map = &env->getServerMap(); MapNode n_top = map->getNodeNoEx(p+v3s16(0,1,0)); - if(!ndef->get(n_top).light_propagates || + if((!ndef->get(n_top).light_propagates && + n_top.getContent() != CONTENT_IGNORE) || ndef->get(n_top).isLiquid()) { n.setContent(ndef->getId("mapgen_dirt")); From 9f1ae4084d945adb6281eaddc2ae56f488691d8b Mon Sep 17 00:00:00 2001 From: PilzAdam Date: Tue, 12 Feb 2013 19:37:25 +0100 Subject: [PATCH 07/43] Enable multiline signs --- src/game.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game.cpp b/src/game.cpp index e923cbbb0..5693c5c12 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1262,7 +1262,7 @@ void the_game( gui::IGUIStaticText *guitext_info = guienv->addStaticText( L"", core::rect(0,0,400,text_height*5+5) + v2s32(100,200), - false, false); + false, true); // Status text (displays info when showing and hiding GUI stuff, etc.) gui::IGUIStaticText *guitext_status = guienv->addStaticText( From 8e69b9cb261c503c5fb461086439aed28e434d19 Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Wed, 30 Jan 2013 04:07:00 +0400 Subject: [PATCH 08/43] Server requires gettext library as well --- src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8d0363548..2937a32ec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -347,6 +347,7 @@ if(BUILD_SERVER) ${ZLIB_LIBRARIES} ${JTHREAD_LIBRARY} ${SQLITE3_LIBRARY} + ${GETTEXT_LIBRARY} ${LUA_LIBRARY} ${PLATFORM_LIBS} ) From b1019a88ba9c0822d60d3f4a74d745c69be8a79b Mon Sep 17 00:00:00 2001 From: RealBadAngel Date: Tue, 12 Feb 2013 22:51:38 +0100 Subject: [PATCH 09/43] Fix to on_rightclick not able to change wielded_item --- builtin/item.lua | 3 +-- doc/lua_api.txt | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/builtin/item.lua b/builtin/item.lua index a28798d4d..acbdaa533 100644 --- a/builtin/item.lua +++ b/builtin/item.lua @@ -237,8 +237,7 @@ function minetest.item_place(itemstack, placer, pointed_thing) local n = minetest.env:get_node(pointed_thing.under) local nn = n.name if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].on_rightclick then - minetest.registered_nodes[nn].on_rightclick(pointed_thing.under, n, placer) - return + return minetest.registered_nodes[nn].on_rightclick(pointed_thing.under, n, placer, itemstack) end end diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 7ba2ed001..af70a1047 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1497,8 +1497,10 @@ Node definition (register_node) on_punch = func(pos, node, puncher), ^ default: minetest.node_punch ^ By default: does nothing - on_rightclick = func(pos, node, clicker), + on_rightclick = func(pos, node, clicker, itemstack), ^ default: nil + ^ if defined, itemstack will hold clicker's wielded item + Shall return the leftover itemstack on_dig = func(pos, node, digger), ^ default: minetest.node_dig ^ By default: checks privileges, wears out tool and removes node From 86b906d015ccde4e8f576a4e61d7c4ae8842b45d Mon Sep 17 00:00:00 2001 From: PilzAdam Date: Wed, 13 Feb 2013 18:06:25 +0100 Subject: [PATCH 10/43] Fix minetest.item_place_node() and minetest.item_drop() to always return an ItemStack --- builtin/item.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/item.lua b/builtin/item.lua index acbdaa533..2233b6c5b 100644 --- a/builtin/item.lua +++ b/builtin/item.lua @@ -141,7 +141,7 @@ function minetest.item_place_node(itemstack, placer, pointed_thing) minetest.log("info", placer:get_player_name() .. " tried to place" .. " node in invalid position " .. minetest.pos_to_string(above) .. ", replacing " .. oldnode_above.name) - return + return itemstack end -- Place above pointed node @@ -186,7 +186,7 @@ function minetest.item_place_node(itemstack, placer, pointed_thing) not check_attached_node(place_to, newnode) then minetest.log("action", "attached node " .. def.name .. " can not be placed at " .. minetest.pos_to_string(place_to)) - return + return itemstack end -- Add node and update @@ -262,7 +262,7 @@ function minetest.item_drop(itemstack, dropper, pos) else minetest.env:add_item(pos, itemstack) end - return "" + return ItemStack("") end function minetest.item_eat(hp_change, replace_with_item) From df3c925b3ccae3bdba125e6dc3ecc740739baeab Mon Sep 17 00:00:00 2001 From: MirceaKitsune Date: Fri, 8 Feb 2013 22:54:01 +0200 Subject: [PATCH 11/43] Improved Player Physics --- minetest.conf.example | 15 ++++ src/client.cpp | 20 +++++ src/clientserver.h | 17 ++++ src/defaultsettings.cpp | 14 +++ src/environment.cpp | 39 ++++++--- src/localplayer.cpp | 186 ++++++++++++++++++++++------------------ src/player.cpp | 58 +++++++++++-- src/player.h | 25 ++++-- src/server.cpp | 29 +++++++ src/server.h | 1 + 10 files changed, 295 insertions(+), 109 deletions(-) diff --git a/minetest.conf.example b/minetest.conf.example index 49fc95ba2..295725876 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -245,6 +245,21 @@ # Files that are not present would be fetched the usual way #remote_media = +# Physics stuff +#movement_acceleration_default = 2 +#movement_acceleration_air = 0.5 +#movement_acceleration_fast = 4 +#movement_speed_walk = 4 +#movement_speed_crouch = 1.35 +#movement_speed_fast = 20 +#movement_speed_climb = 2 +#movement_speed_jump = 6.5 +#movement_speed_descend = 6 +#movement_liquid_fluidity = 1 +#movement_liquid_fluidity_smooth = 0.5 +#movement_liquid_sink = 10 +#movement_gravity = 9.81 + # Mapgen stuff #mg_name = v6 #water_level = 1 diff --git a/src/client.cpp b/src/client.cpp index 9969ef538..415f07311 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1514,6 +1514,26 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) } } } + else if(command == TOCLIENT_MOVEMENT) + { + std::string datastring((char*)&data[2], datasize-2); + std::istringstream is(datastring, std::ios_base::binary); + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + player->movement_acceleration_default = readF1000(is) * BS; + player->movement_acceleration_air = readF1000(is) * BS; + player->movement_acceleration_fast = readF1000(is) * BS; + player->movement_speed_walk = readF1000(is) * BS; + player->movement_speed_crouch = readF1000(is) * BS; + player->movement_speed_fast = readF1000(is) * BS; + player->movement_speed_climb = readF1000(is) * BS; + player->movement_speed_jump = readF1000(is) * BS; + player->movement_liquid_fluidity = readF1000(is) * BS; + player->movement_liquid_fluidity_smooth = readF1000(is) * BS; + player->movement_liquid_sink = readF1000(is) * BS; + player->movement_gravity = readF1000(is) * BS; + } else if(command == TOCLIENT_HP) { std::string datastring((char*)&data[2], datasize-2); diff --git a/src/clientserver.h b/src/clientserver.h index 52b9dc7b0..7fb3e83d2 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -364,6 +364,23 @@ enum ToClientCommand u16 len u8[len] formname */ + + TOCLIENT_MOVEMENT = 0x45, + /* + u16 command + f1000 movement_acceleration_default + f1000 movement_acceleration_air + f1000 movement_acceleration_fast + f1000 movement_speed_walk + f1000 movement_speed_crouch + f1000 movement_speed_fast + f1000 movement_speed_climb + f1000 movement_speed_jump + f1000 movement_liquid_fluidity + f1000 movement_liquid_fluidity_smooth + f1000 movement_liquid_sink + f1000 movement_gravity + */ }; enum ToServerCommand diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index a164d0693..1c673f76c 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -172,6 +172,20 @@ void set_default_settings(Settings *settings) settings->setDefault("congestion_control_max_rate", "400"); settings->setDefault("congestion_control_min_rate", "10"); settings->setDefault("remote_media", ""); + + // physics stuff + settings->setDefault("movement_acceleration_default", "2"); + settings->setDefault("movement_acceleration_air", "0.5"); + settings->setDefault("movement_acceleration_fast", "8"); + settings->setDefault("movement_speed_walk", "4"); + settings->setDefault("movement_speed_crouch", "1.35"); + settings->setDefault("movement_speed_fast", "20"); + settings->setDefault("movement_speed_climb", "2"); + settings->setDefault("movement_speed_jump", "6.5"); + settings->setDefault("movement_liquid_fluidity", "1"); + settings->setDefault("movement_liquid_fluidity_smooth", "0.5"); + settings->setDefault("movement_liquid_sink", "10"); + settings->setDefault("movement_gravity", "9.81"); //mapgen related things settings->setDefault("mg_name", "v6"); diff --git a/src/environment.cpp b/src/environment.cpp index 51255b918..ebf5e9a63 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -2065,20 +2065,37 @@ void ClientEnvironment::step(float dtime) { // Gravity v3f speed = lplayer->getSpeed(); - if(lplayer->swimming_up == false) - speed.Y -= 9.81 * BS * dtime_part * 2; + if(lplayer->in_liquid == false) + speed.Y -= lplayer->movement_gravity * dtime_part * 2; - // Water resistance - if(lplayer->in_water_stable || lplayer->in_water) + // Liquid floating / sinking + if(lplayer->in_liquid && !lplayer->swimming_vertical) + speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2; + + // Liquid resistance + if(lplayer->in_liquid_stable || lplayer->in_liquid) { - f32 max_down = 2.0*BS; - if(speed.Y < -max_down) speed.Y = -max_down; + // How much the node's viscosity blocks movement, ranges between 0 and 1 + // Should match the scale at which viscosity increase affects other liquid attributes + const f32 viscosity_factor = 0.3; - f32 max = 2.5*BS; - if(speed.getLength() > max) - { - speed = speed / speed.getLength() * max; - } + v3f d_wanted = -speed / lplayer->movement_liquid_fluidity; + f32 dl = d_wanted.getLength(); + if(dl > lplayer->movement_liquid_fluidity_smooth) + dl = lplayer->movement_liquid_fluidity_smooth; + dl *= (lplayer->liquid_viscosity * viscosity_factor) + (1 - viscosity_factor); + + v3f d = d_wanted.normalize() * dl; + speed += d; + +#if 0 // old code + if(speed.X > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.X -= lplayer->movement_liquid_fluidity_smooth; + if(speed.X < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.X += lplayer->movement_liquid_fluidity_smooth; + if(speed.Y > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.Y -= lplayer->movement_liquid_fluidity_smooth; + if(speed.Y < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.Y += lplayer->movement_liquid_fluidity_smooth; + if(speed.Z > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.Z -= lplayer->movement_liquid_fluidity_smooth; + if(speed.Z < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.Z += lplayer->movement_liquid_fluidity_smooth; +#endif } lplayer->setSpeed(speed); diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 2d0d77140..8b6d7e2f6 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -90,37 +90,39 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, */ /* - Check if player is in water (the oscillating value) + Check if player is in liquid (the oscillating value) */ try{ - // If in water, the threshold of coming out is at higher y - if(in_water) + // If in liquid, the threshold of coming out is at higher y + if(in_liquid) { v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS); - in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + in_liquid = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(map.getNode(pp).getContent()).liquid_viscosity; } - // If not in water, the threshold of going in is at lower y + // If not in liquid, the threshold of going in is at lower y else { v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS); - in_water = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + in_liquid = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + liquid_viscosity = nodemgr->get(map.getNode(pp).getContent()).liquid_viscosity; } } catch(InvalidPositionException &e) { - in_water = false; + in_liquid = false; } /* - Check if player is in water (the stable value) + Check if player is in liquid (the stable value) */ try{ v3s16 pp = floatToInt(position + v3f(0,0,0), BS); - in_water_stable = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); + in_liquid_stable = nodemgr->get(map.getNode(pp).getContent()).isLiquid(); } catch(InvalidPositionException &e) { - in_water_stable = false; + in_liquid_stable = false; } /* @@ -159,7 +161,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, If sneaking, keep in range from the last walked node and don't fall off from it */ - if(control.sneak && m_sneak_node_exists && !(fly_allowed && g_settings->getBool("free_move"))) + if(control.sneak && m_sneak_node_exists && !(fly_allowed && g_settings->getBool("free_move")) && !in_liquid) { f32 maxd = 0.5*BS + sneak_max; v3f lwn_f = intToFloat(m_sneak_node, BS); @@ -315,7 +317,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, } if(bouncy_jump && control.jump){ - m_speed.Y += 6.5*BS; + m_speed.Y += movement_speed_jump*BS; touching_ground = false; MtEvent *e = new SimpleTriggerEvent("PlayerJump"); m_gamedef->event()->put(e); @@ -348,7 +350,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d, */ const ContentFeatures &f = nodemgr->get(map.getNodeNoEx(getStandingNodePos())); // Determine if jumping is possible - m_can_jump = touching_ground; + m_can_jump = touching_ground && !in_liquid; if(itemgroup_get(f.groups, "disable_jump")) m_can_jump = false; } @@ -361,12 +363,8 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d) void LocalPlayer::applyControl(float dtime) { // Clear stuff - swimming_up = false; + swimming_vertical = false; - // Random constants - f32 walk_acceleration = 4.0 * BS; - f32 walkspeed_max = 4.0 * BS; - setPitch(control.pitch); setYaw(control.yaw); @@ -380,22 +378,17 @@ void LocalPlayer::applyControl(float dtime) v3f move_direction = v3f(0,0,1); move_direction.rotateXZBy(getYaw()); - v3f speed = v3f(0,0,0); + v3f speedH = v3f(0,0,0); // Horizontal (X, Z) + v3f speedV = v3f(0,0,0); // Vertical (Y) bool fly_allowed = m_gamedef->checkLocalPrivilege("fly"); bool fast_allowed = m_gamedef->checkLocalPrivilege("fast"); bool free_move = fly_allowed && g_settings->getBool("free_move"); bool fast_move = fast_allowed && g_settings->getBool("fast_move"); + bool fast_or_aux1_descends = (fast_move && control.aux1) || (fast_move && g_settings->getBool("aux1_descends")); bool continuous_forward = g_settings->getBool("continuous_forward"); - if(free_move || is_climbing) - { - v3f speed = getSpeed(); - speed.Y = 0; - setSpeed(speed); - } - // Whether superspeed mode is used or not bool superspeed = false; @@ -415,18 +408,21 @@ void LocalPlayer::applyControl(float dtime) if(free_move) { // In free movement mode, aux1 descends - v3f speed = getSpeed(); if(fast_move) - speed.Y = -20*BS; + speedV.Y = -movement_speed_fast; else - speed.Y = -walkspeed_max; - setSpeed(speed); + speedV.Y = -movement_speed_walk; + } + else if(in_liquid || in_liquid_stable) + { + // Always use fast when aux1_descends & fast_move are enabled in liquid, since the aux1 button would mean both turbo and "swim down" causing a conflict + speedV.Y = -movement_speed_fast; + swimming_vertical = true; } else if(is_climbing) { - v3f speed = getSpeed(); - speed.Y = -3*BS; - setSpeed(speed); + // Always use fast when aux1_descends & fast_move are enabled when climbing, since the aux1 button would mean both turbo and "descend" causing a conflict + speedV.Y = -movement_speed_fast; } else { @@ -456,66 +452,69 @@ void LocalPlayer::applyControl(float dtime) if(free_move) { // In free movement mode, sneak descends - v3f speed = getSpeed(); - if(fast_move && (control.aux1 || - g_settings->getBool("always_fly_fast"))) - speed.Y = -20*BS; + if(fast_move && (control.aux1 || g_settings->getBool("always_fly_fast"))) + speedV.Y = -movement_speed_fast; else - speed.Y = -walkspeed_max; - setSpeed(speed); + speedV.Y = -movement_speed_walk; + } + else if(in_liquid || in_liquid_stable) + { + if(fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled in liquid, since the aux1 button would mean both turbo and "swim down" causing a conflict + speedV.Y = -movement_speed_fast; + else + speedV.Y = -movement_speed_walk; + swimming_vertical = true; } else if(is_climbing) { - v3f speed = getSpeed(); - speed.Y = -3*BS; - setSpeed(speed); + if(fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled when climbing, since the aux1 button would mean both turbo and "descend" causing a conflict + speedV.Y = -movement_speed_fast; + else + speedV.Y = -movement_speed_climb; } } } if(continuous_forward) - speed += move_direction; + speedH += move_direction; if(control.up) { if(continuous_forward) superspeed = true; else - speed += move_direction; + speedH += move_direction; } if(control.down) { - speed -= move_direction; + speedH -= move_direction; } if(control.left) { - speed += move_direction.crossProduct(v3f(0,1,0)); + speedH += move_direction.crossProduct(v3f(0,1,0)); } if(control.right) { - speed += move_direction.crossProduct(v3f(0,-1,0)); + speedH += move_direction.crossProduct(v3f(0,-1,0)); } if(control.jump) { if(free_move) - { - v3f speed = getSpeed(); - - if(g_settings->getBool("aux1_descends") || - g_settings->getBool("always_fly_fast")) + { + if(g_settings->getBool("aux1_descends") || g_settings->getBool("always_fly_fast")) { if(fast_move) - speed.Y = 20*BS; + speedV.Y = movement_speed_fast; else - speed.Y = walkspeed_max; + speedV.Y = movement_speed_walk; } else { if(fast_move && control.aux1) - speed.Y = 20*BS; + speedV.Y = movement_speed_fast; else - speed.Y = walkspeed_max; + speedV.Y = movement_speed_walk; } - - setSpeed(speed); } else if(m_can_jump) { @@ -524,49 +523,66 @@ void LocalPlayer::applyControl(float dtime) raising the height at which the jump speed is kept at its starting value */ - v3f speed = getSpeed(); - if(speed.Y >= -0.5*BS) + v3f speedJ = getSpeed(); + if(speedJ.Y >= -0.5 * BS) { - speed.Y = 6.5*BS; - setSpeed(speed); + speedJ.Y = movement_speed_jump; + setSpeed(speedJ); MtEvent *e = new SimpleTriggerEvent("PlayerJump"); m_gamedef->event()->put(e); } } - // Use the oscillating value for getting out of water - // (so that the player doesn't fly on the surface) - else if(in_water) + else if(in_liquid) { - v3f speed = getSpeed(); - speed.Y = 1.5*BS; - setSpeed(speed); - swimming_up = true; + if(fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled in liquid, since the aux1 button would mean both turbo and "swim down" causing a conflict + speedV.Y = movement_speed_fast; + else + speedV.Y = movement_speed_walk; + swimming_vertical = true; } else if(is_climbing) { - v3f speed = getSpeed(); - speed.Y = 3*BS; - setSpeed(speed); + if(fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled when climbing, since the aux1 button would mean both turbo and "descend" causing a conflict + speedV.Y = movement_speed_fast; + else + speedV.Y = movement_speed_climb; } } // The speed of the player (Y is ignored) - if(superspeed) - speed = speed.normalize() * walkspeed_max * 5.0; - else if(control.sneak && !free_move) - speed = speed.normalize() * walkspeed_max / 3.0; + if(superspeed || (is_climbing && fast_or_aux1_descends) || ((in_liquid || in_liquid_stable) && fast_or_aux1_descends)) + speedH = speedH.normalize() * movement_speed_fast; + else if(control.sneak && !free_move && !in_liquid && !in_liquid_stable) + speedH = speedH.normalize() * movement_speed_crouch; else - speed = speed.normalize() * walkspeed_max; - - f32 inc = walk_acceleration * BS * dtime; - - // Faster acceleration if fast and free movement - if(free_move && fast_move && superspeed) - inc = walk_acceleration * BS * dtime * 10; - + speedH = speedH.normalize() * movement_speed_walk; + + // Acceleration increase + f32 incH = 0; // Horizontal (X, Z) + f32 incV = 0; // Vertical (Y) + if((!touching_ground && !free_move && !is_climbing && !in_liquid) || (!free_move && m_can_jump && control.jump)) + { + // Jumping and falling + if(superspeed || (fast_move && control.aux1)) + incH = movement_acceleration_fast * BS * dtime; + else + incH = movement_acceleration_air * BS * dtime; + incV = 0; // No vertical acceleration in air + } + else if(superspeed || (fast_move && control.aux1)) + incH = incV = movement_acceleration_fast * BS * dtime; + else if ((in_liquid || in_liquid_stable) && fast_or_aux1_descends) + // Always use fast when aux1_descends & fast_move are enabled in liquid, since the aux1 button would mean both turbo and "swim down" causing a conflict + incH = incV = movement_acceleration_fast * BS * dtime; + else + incH = incV = movement_acceleration_default * BS * dtime; + // Accelerate to target speed with maximum increment - accelerate(speed, inc); + accelerateHorizontal(speedH, incH); + accelerateVertical(speedV, incV); } v3s16 LocalPlayer::getStandingNodePos() diff --git a/src/player.cpp b/src/player.cpp index 36f12c77b..0c34c4cdf 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -27,10 +27,11 @@ with this program; if not, write to the Free Software Foundation, Inc., Player::Player(IGameDef *gamedef): touching_ground(false), - in_water(false), - in_water_stable(false), + in_liquid(false), + in_liquid_stable(false), + liquid_viscosity(0), is_climbing(false), - swimming_up(false), + swimming_vertical(false), camera_barely_in_ceiling(false), inventory(gamedef->idef()), hp(PLAYER_MAX_HP), @@ -56,19 +57,35 @@ Player::Player(IGameDef *gamedef): "list[current_player;main;0,3.5;8,4;]" "list[current_player;craft;3,0;3,3;]" "list[current_player;craftpreview;7,1;1,1;]"; + + // Initialize movement settings at default values, so movement can work if the server fails to send them + movement_acceleration_default = 2 * BS; + movement_acceleration_air = 0.5 * BS; + movement_acceleration_fast = 8 * BS; + movement_speed_walk = 4 * BS; + movement_speed_crouch = 1.35 * BS; + movement_speed_fast = 20 * BS; + movement_speed_climb = 2 * BS; + movement_speed_jump = 6.5 * BS; + movement_liquid_fluidity = 1 * BS; + movement_liquid_fluidity_smooth = 0.5 * BS; + movement_liquid_sink = 10 * BS; + movement_gravity = 9.81 * BS; } Player::~Player() { } -// Y direction is ignored -void Player::accelerate(v3f target_speed, f32 max_increase) +// Horizontal acceleration (X and Z), Y direction is ignored +void Player::accelerateHorizontal(v3f target_speed, f32 max_increase) { + if(max_increase == 0) + return; + v3f d_wanted = target_speed - m_speed; d_wanted.Y = 0; - f32 dl_wanted = d_wanted.getLength(); - f32 dl = dl_wanted; + f32 dl = d_wanted.getLength(); if(dl > max_increase) dl = max_increase; @@ -76,7 +93,6 @@ void Player::accelerate(v3f target_speed, f32 max_increase) m_speed.X += d.X; m_speed.Z += d.Z; - //m_speed += d; #if 0 // old code if(m_speed.X < target_speed.X - max_increase) @@ -99,6 +115,32 @@ void Player::accelerate(v3f target_speed, f32 max_increase) #endif } +// Vertical acceleration (Y), X and Z directions are ignored +void Player::accelerateVertical(v3f target_speed, f32 max_increase) +{ + if(max_increase == 0) + return; + + f32 d_wanted = target_speed.Y - m_speed.Y; + if(d_wanted > max_increase) + d_wanted = max_increase; + else if(d_wanted < -max_increase) + d_wanted = -max_increase; + + m_speed.Y += d_wanted; + +#if 0 // old code + if(m_speed.Y < target_speed.Y - max_increase) + m_speed.Y += max_increase; + else if(m_speed.Y > target_speed.Y + max_increase) + m_speed.Y -= max_increase; + else if(m_speed.Y < target_speed.Y) + m_speed.Y = target_speed.Y; + else if(m_speed.Y > target_speed.Y) + m_speed.Y = target_speed.Y; +#endif +} + v3s16 Player::getLightPosition() const { return floatToInt(m_position + v3f(0,BS+BS/2,0), BS); diff --git a/src/player.h b/src/player.h index 67b02c344..770afdb37 100644 --- a/src/player.h +++ b/src/player.h @@ -108,8 +108,8 @@ public: m_speed = speed; } - // Y direction is ignored - void accelerate(v3f target_speed, f32 max_increase); + void accelerateHorizontal(v3f target_speed, f32 max_increase); + void accelerateVertical(v3f target_speed, f32 max_increase); v3f getPosition() { @@ -196,17 +196,32 @@ public: bool touching_ground; // This oscillates so that the player jumps a bit above the surface - bool in_water; + bool in_liquid; // This is more stable and defines the maximum speed of the player - bool in_water_stable; + bool in_liquid_stable; + // Gets the viscosity of water to calculate friction + u8 liquid_viscosity; bool is_climbing; - bool swimming_up; + bool swimming_vertical; bool camera_barely_in_ceiling; u8 light; Inventory inventory; + f32 movement_acceleration_default; + f32 movement_acceleration_air; + f32 movement_acceleration_fast; + f32 movement_speed_walk; + f32 movement_speed_crouch; + f32 movement_speed_fast; + f32 movement_speed_climb; + f32 movement_speed_jump; + f32 movement_liquid_fluidity; + f32 movement_liquid_fluidity_smooth; + f32 movement_liquid_sink; + f32 movement_gravity; + u16 hp; float hurt_tilt_timer; diff --git a/src/server.cpp b/src/server.cpp index a8640ad10..572ef4d82 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2344,6 +2344,9 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) infostream<<"Server: Sending content to " <getFloat("movement_acceleration_default")); + writeF1000(os, g_settings->getFloat("movement_acceleration_air")); + writeF1000(os, g_settings->getFloat("movement_acceleration_fast")); + writeF1000(os, g_settings->getFloat("movement_speed_walk")); + writeF1000(os, g_settings->getFloat("movement_speed_crouch")); + writeF1000(os, g_settings->getFloat("movement_speed_fast")); + writeF1000(os, g_settings->getFloat("movement_speed_climb")); + writeF1000(os, g_settings->getFloat("movement_speed_jump")); + writeF1000(os, g_settings->getFloat("movement_liquid_fluidity")); + writeF1000(os, g_settings->getFloat("movement_liquid_fluidity_smooth")); + writeF1000(os, g_settings->getFloat("movement_liquid_sink")); + writeF1000(os, g_settings->getFloat("movement_gravity")); + + // Make data buffer + std::string s = os.str(); + SharedBuffer data((u8*)s.c_str(), s.size()); + // Send as reliable + con.Send(peer_id, 0, data, true); +} + void Server::SendHP(con::Connection &con, u16 peer_id, u8 hp) { DSTACK(__FUNCTION_NAME); diff --git a/src/server.h b/src/server.h index 86d5513d8..29d47337d 100644 --- a/src/server.h +++ b/src/server.h @@ -602,6 +602,7 @@ private: Static send methods */ + static void SendMovement(con::Connection &con, u16 peer_id); static void SendHP(con::Connection &con, u16 peer_id, u8 hp); static void SendAccessDenied(con::Connection &con, u16 peer_id, const std::wstring &reason); From 30b9a4d6b479ecfcb84d4803f5d15ee9b6c7edd6 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Sun, 3 Feb 2013 16:19:09 +0400 Subject: [PATCH 12/43] Add Freetype support --- .gitignore | 4 + CMakeLists.txt | 2 + README.txt | 4 + fonts/liberationmono.ttf | Bin 0 -> 333636 bytes fonts/liberationsans.ttf | Bin 0 -> 133828 bytes minetest.conf.example | 5 + src/CMakeLists.txt | 45 + src/cguittfont/CGUITTFont.cpp | 1146 ++++++++++ src/cguittfont/CGUITTFont.h | 377 ++++ src/cguittfont/CMakeLists.txt | 17 + src/cguittfont/irrUString.h | 3877 ++++++++++++++++++++++++++++++++ src/cguittfont/xCGUITTFont.cpp | 5 + src/cguittfont/xCGUITTFont.h | 7 + src/cmake_config.h.in | 1 + src/config.h | 3 + src/defaultsettings.cpp | 6 + src/gettext.h | 41 +- src/guiChatConsole.cpp | 10 + src/guiMainMenu.cpp | 12 +- src/guiTextInputMenu.cpp | 12 +- src/intlGUIEditBox.cpp | 1508 +++++++++++++ src/intlGUIEditBox.h | 178 ++ src/main.cpp | 27 +- 23 files changed, 7271 insertions(+), 16 deletions(-) create mode 100644 fonts/liberationmono.ttf create mode 100644 fonts/liberationsans.ttf create mode 100644 src/cguittfont/CGUITTFont.cpp create mode 100644 src/cguittfont/CGUITTFont.h create mode 100644 src/cguittfont/CMakeLists.txt create mode 100644 src/cguittfont/irrUString.h create mode 100644 src/cguittfont/xCGUITTFont.cpp create mode 100644 src/cguittfont/xCGUITTFont.h create mode 100644 src/intlGUIEditBox.cpp create mode 100644 src/intlGUIEditBox.h diff --git a/.gitignore b/.gitignore index ee210c7c0..e282ec130 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,10 @@ src/jthread/cmake_install.cmake src/jthread/libjthread.a src/lua/build/ src/lua/CMakeFiles/ +src/cguittfont/CMakeFiles/ +src/cguittfont/libcguittfont.a +src/cguittfont/cmake_install.cmake +src/cguittfont/Makefile CMakeCache.txt CPackConfig.cmake CPackSourceConfig.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index fdb0347b7..fbf46d059 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,8 @@ if(RUN_IN_PLACE) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/textures/all/textures_here.txt" DESTINATION "${SHAREDIR}/textures/all") endif() +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/fonts" DESTINATION "${SHAREDIR}") + install(FILES "README.txt" DESTINATION "${DOCDIR}") install(FILES "doc/lua_api.txt" DESTINATION "${DOCDIR}") install(FILES "doc/mapformat.txt" DESTINATION "${DOCDIR}") diff --git a/README.txt b/README.txt index 8c5a46223..d6344bd2c 100644 --- a/README.txt +++ b/README.txt @@ -372,4 +372,8 @@ DejaVu Sans Mono: Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + Liberation Fonts Copyright: + + Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc. + diff --git a/fonts/liberationmono.ttf b/fonts/liberationmono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7260bd65e0dd2df5d0c398d579c126788603e5fd GIT binary patch literal 333636 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%0A#4WO;h)-5om@Q%c|Jj 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 +#include "CGUITTFont.h" + +namespace irr +{ +namespace gui +{ + +// Manages the FT_Face cache. +struct SGUITTFace : public virtual irr::IReferenceCounted +{ + SGUITTFace() : face_buffer(0), face_buffer_size(0) + { + memset((void*)&face, 0, sizeof(FT_Face)); + } + + ~SGUITTFace() + { + FT_Done_Face(face); + delete[] face_buffer; + } + + FT_Face face; + FT_Byte* face_buffer; + FT_Long face_buffer_size; +}; + +// Static variables. +FT_Library CGUITTFont::c_library; +core::map CGUITTFont::c_faces; +bool CGUITTFont::c_libraryLoaded = false; +scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0; +scene::SMesh CGUITTFont::shared_plane_; + +// + +video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const +{ + // Determine what our texture size should be. + // Add 1 because textures are inclusive-exclusive. + core::dimension2du d(bits.width + 1, bits.rows + 1); + core::dimension2du texture_size; + //core::dimension2du texture_size(bits.width + 1, bits.rows + 1); + + // Create and load our image now. + video::IImage* image = 0; + switch (bits.pixel_mode) + { + case FT_PIXEL_MODE_MONO: + { + // Create a blank image and fill it with transparent pixels. + texture_size = d.getOptimalSize(true, true); + image = driver->createImage(video::ECF_A1R5G5B5, texture_size); + image->fill(video::SColor(0, 255, 255, 255)); + + // Load the monochrome data in. + const u32 image_pitch = image->getPitch() / sizeof(u16); + u16* image_data = (u16*)image->lock(); + u8* glyph_data = bits.buffer; + for (s32 y = 0; y < bits.rows; ++y) + { + u16* row = image_data; + for (s32 x = 0; x < bits.width; ++x) + { + // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80. + // So, we go through the data each bit at a time. + if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0) + *row = 0xFFFF; + ++row; + } + image_data += image_pitch; + } + image->unlock(); + break; + } + + case FT_PIXEL_MODE_GRAY: + { + // Create our blank image. + texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0); + image = driver->createImage(video::ECF_A8R8G8B8, texture_size); + image->fill(video::SColor(0, 255, 255, 255)); + + // Load the grayscale data in. + const float gray_count = static_cast(bits.num_grays); + const u32 image_pitch = image->getPitch() / sizeof(u32); + u32* image_data = (u32*)image->lock(); + u8* glyph_data = bits.buffer; + for (s32 y = 0; y < bits.rows; ++y) + { + u8* row = glyph_data; + for (s32 x = 0; x < bits.width; ++x) + { + image_data[y * image_pitch + x] |= static_cast(255.0f * (static_cast(*row++) / gray_count)) << 24; + //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24); + } + glyph_data += bits.pitch; + } + image->unlock(); + break; + } + default: + // TODO: error message? + return 0; + } + return image; +} + +void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags) +{ + if (isLoaded) return; + + // Set the size of the glyph. + FT_Set_Pixel_Sizes(face, 0, font_size); + + // Attempt to load the glyph. + if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok) + // TODO: error message? + return; + + FT_GlyphSlot glyph = face->glyph; + FT_Bitmap bits = glyph->bitmap; + + // Setup the glyph information here: + advance = glyph->advance; + offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top); + + // Try to get the last page with available slots. + CGUITTGlyphPage* page = parent->getLastGlyphPage(); + + // If we need to make a new page, do that now. + if (!page) + { + page = parent->createGlyphPage(bits.pixel_mode); + if (!page) + // TODO: add error message? + return; + } + + glyph_page = parent->getLastGlyphPageIndex(); + u32 texture_side_length = page->texture->getOriginalSize().Width; + core::vector2di page_position( + (page->used_slots % (texture_side_length / font_size)) * font_size, + (page->used_slots / (texture_side_length / font_size)) * font_size + ); + source_rect.UpperLeftCorner = page_position; + source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows); + + page->dirty = true; + ++page->used_slots; + --page->available_slots; + + // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded. + surface = createGlyphImage(bits, driver); + + // Set our glyph as loaded. + isLoaded = true; +} + +void SGUITTGlyph::unload() +{ + if (surface) + { + surface->drop(); + surface = 0; + } + isLoaded = false; +} + +////////////////////// + +CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + if (!c_libraryLoaded) + { + if (FT_Init_FreeType(&c_library)) + return 0; + c_libraryLoaded = true; + } + + CGUITTFont* font = new CGUITTFont(env); + bool ret = font->load(filename, size, antialias, transparency); + if (!ret) + { + font->drop(); + return 0; + } + + return font; +} + +CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + if (!c_libraryLoaded) + { + if (FT_Init_FreeType(&c_library)) + return 0; + c_libraryLoaded = true; + } + + CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment()); + font->Device = device; + bool ret = font->load(filename, size, antialias, transparency); + if (!ret) + { + font->drop(); + return 0; + } + + return font; +} + +CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + return CGUITTFont::createTTFont(env, filename, size, antialias, transparency); +} + +CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + return CGUITTFont::createTTFont(device, filename, size, antialias, transparency); +} + +////////////////////// + +//! Constructor. +CGUITTFont::CGUITTFont(IGUIEnvironment *env) +: use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true), +batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0) +{ + #ifdef _DEBUG + setDebugName("CGUITTFont"); + #endif + + if (Environment) + { + // don't grab environment, to avoid circular references + Driver = Environment->getVideoDriver(); + } + + if (Driver) + Driver->grab(); + + setInvisibleCharacters(L" "); + + // Glyphs aren't reference counted, so don't try to delete them when we free the array. + Glyphs.set_free_when_destroyed(false); +} + +bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + // Some sanity checks. + if (Environment == 0 || Driver == 0) return false; + if (size == 0) return false; + if (filename.size() == 0) return false; + + io::IFileSystem* filesystem = Environment->getFileSystem(); + irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0); + this->size = size; + this->filename = filename; + + // Update the font loading flags when the font is first loaded. + this->use_monochrome = !antialias; + this->use_transparency = transparency; + update_load_flags(); + + // Log. + if (logger) + logger->log(L"CGUITTFont", core::stringw(core::stringw(L"Creating new font: ") + core::ustring(filename).toWCHAR_s() + L" " + core::stringc(size) + L"pt " + (antialias ? L"+antialias " : L"-antialias ") + (transparency ? L"+transparency" : L"-transparency")).c_str(), irr::ELL_INFORMATION); + + // Grab the face. + SGUITTFace* face = 0; + core::map::Node* node = c_faces.find(filename); + if (node == 0) + { + face = new SGUITTFace(); + c_faces.set(filename, face); + + if (filesystem) + { + // Read in the file data. + io::IReadFile* file = filesystem->createAndOpenFile(filename); + if (file == 0) + { + if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + face->face_buffer = new FT_Byte[file->getSize()]; + file->read(face->face_buffer, file->getSize()); + face->face_buffer_size = file->getSize(); + file->drop(); + + // Create the face. + if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face)) + { + if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + } + else + { + core::ustring converter(filename); + if (FT_New_Face(c_library, reinterpret_cast(converter.toUTF8_s().c_str()), 0, &face->face)) + { + if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + } + } + else + { + // Using another instance of this face. + face = node->getValue(); + face->grab(); + } + + // Store our face. + tt_face = face->face; + + // Store font metrics. + FT_Set_Pixel_Sizes(tt_face, size, 0); + font_metrics = tt_face->size->metrics; + + // Allocate our glyphs. + Glyphs.clear(); + Glyphs.reallocate(tt_face->num_glyphs); + Glyphs.set_used(tt_face->num_glyphs); + for (FT_Long i = 0; i < tt_face->num_glyphs; ++i) + { + Glyphs[i].isLoaded = false; + Glyphs[i].glyph_page = 0; + Glyphs[i].source_rect = core::recti(); + Glyphs[i].offset = core::vector2di(); + Glyphs[i].advance = FT_Vector(); + Glyphs[i].surface = 0; + Glyphs[i].parent = this; + } + + // Cache the first 127 ascii characters. + u32 old_size = batch_load_size; + batch_load_size = 127; + getGlyphIndexByChar((uchar32_t)0); + batch_load_size = old_size; + + return true; +} + +CGUITTFont::~CGUITTFont() +{ + // Delete the glyphs and glyph pages. + reset_images(); + CGUITTAssistDelete::Delete(Glyphs); + //Glyphs.clear(); + + // We aren't using this face anymore. + core::map::Node* n = c_faces.find(filename); + if (n) + { + SGUITTFace* f = n->getValue(); + + // Drop our face. If this was the last face, the destructor will clean up. + if (f->drop()) + c_faces.remove(filename); + + // If there are no more faces referenced by FreeType, clean up. + if (c_faces.size() == 0) + { + FT_Done_FreeType(c_library); + c_libraryLoaded = false; + } + } + + // Drop our driver now. + if (Driver) + Driver->drop(); +} + +void CGUITTFont::reset_images() +{ + // Delete the glyphs. + for (u32 i = 0; i != Glyphs.size(); ++i) + Glyphs[i].unload(); + + // Unload the glyph pages from video memory. + for (u32 i = 0; i != Glyph_Pages.size(); ++i) + delete Glyph_Pages[i]; + Glyph_Pages.clear(); + + // Always update the internal FreeType loading flags after resetting. + update_load_flags(); +} + +void CGUITTFont::update_glyph_pages() const +{ + for (u32 i = 0; i != Glyph_Pages.size(); ++i) + { + if (Glyph_Pages[i]->dirty) + Glyph_Pages[i]->updateTexture(); + } +} + +CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const +{ + CGUITTGlyphPage* page = 0; + if (Glyph_Pages.empty()) + return 0; + else + { + page = Glyph_Pages[getLastGlyphPageIndex()]; + if (page->available_slots == 0) + page = 0; + } + return page; +} + +CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) +{ + CGUITTGlyphPage* page = 0; + + // Name of our page. + io::path name("TTFontGlyphPage_"); + name += tt_face->family_name; + name += "."; + name += tt_face->style_name; + name += "."; + name += size; + name += "_"; + name += Glyph_Pages.size(); // The newly created page will be at the end of the collection. + + // Create the new page. + page = new CGUITTGlyphPage(Driver, name); + + // Determine our maximum texture size. + // If we keep getting 0, set it to 1024x1024, as that number is pretty safe. + core::dimension2du max_texture_size = max_page_texture_size; + if (max_texture_size.Width == 0 || max_texture_size.Height == 0) + max_texture_size = Driver->getMaxTextureSize(); + if (max_texture_size.Width == 0 || max_texture_size.Height == 0) + max_texture_size = core::dimension2du(1024, 1024); + + // We want to try to put at least 144 glyphs on a single texture. + core::dimension2du page_texture_size; + if (size <= 21) page_texture_size = core::dimension2du(256, 256); + else if (size <= 42) page_texture_size = core::dimension2du(512, 512); + else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024); + else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048); + else page_texture_size = core::dimension2du(4096, 4096); + + if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height) + page_texture_size = max_texture_size; + + if (!page->createPageTexture(pixel_mode, page_texture_size)) + // TODO: add error message? + return 0; + + if (page) + { + // Determine the number of glyph slots on the page and add it to the list of pages. + page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size); + Glyph_Pages.push_back(page); + } + return page; +} + +void CGUITTFont::setTransparency(const bool flag) +{ + use_transparency = flag; + reset_images(); +} + +void CGUITTFont::setMonochrome(const bool flag) +{ + use_monochrome = flag; + reset_images(); +} + +void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting) +{ + use_hinting = enable; + use_auto_hinting = enable_auto_hinting; + reset_images(); +} + +void CGUITTFont::draw(const core::stringw& text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) +{ + if (!Driver) + return; + + // Clear the glyph pages of their render information. + for (u32 i = 0; i < Glyph_Pages.size(); ++i) + { + Glyph_Pages[i]->render_positions.clear(); + Glyph_Pages[i]->render_source_rects.clear(); + } + + // Set up some variables. + core::dimension2d textDimension; + core::position2d offset = position.UpperLeftCorner; + + // Determine offset positions. + if (hcenter || vcenter) + { + textDimension = getDimension(text.c_str()); + + if (hcenter) + offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X; + + if (vcenter) + offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y; + } + + // Convert to a unicode string. + core::ustring utext(text); + + // Set up our render map. + core::map Render_Map; + + // Start parsing characters. + u32 n; + uchar32_t previousChar = 0; + core::ustring::const_iterator iter(utext); + while (!iter.atEnd()) + { + uchar32_t currentChar = *iter; + n = getGlyphIndexByChar(currentChar); + bool visible = (Invisible.findFirst(currentChar) == -1); + if (n > 0 && visible) + { + bool lineBreak=false; + if (currentChar == L'\r') // Mac or Windows breaks + { + lineBreak = true; + if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. + currentChar = *(++iter); + } + else if (currentChar == (uchar32_t)'\n') // Unix breaks + { + lineBreak = true; + } + + if (lineBreak) + { + previousChar = 0; + offset.Y += font_metrics.ascender / 64; + offset.X = position.UpperLeftCorner.X; + + if (hcenter) + offset.X += (position.getWidth() - textDimension.Width) >> 1; + ++iter; + continue; + } + + // Calculate the glyph offset. + s32 offx = Glyphs[n-1].offset.X; + s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y; + + // Apply kerning. + core::vector2di k = getKerning(currentChar, previousChar); + offset.X += k.X; + offset.Y += k.Y; + + // Determine rendering information. + SGUITTGlyph& glyph = Glyphs[n-1]; + CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page]; + page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy)); + page->render_source_rects.push_back(glyph.source_rect); + Render_Map.set(glyph.glyph_page, page); + } + offset.X += getWidthFromCharacter(currentChar); + + previousChar = currentChar; + ++iter; + } + + // Draw now. + update_glyph_pages(); + core::map::Iterator j = Render_Map.getIterator(); + while (!j.atEnd()) + { + core::map::Node* n = j.getNode(); + j++; + if (n == 0) continue; + + CGUITTGlyphPage* page = n->getValue(); + + if (!use_transparency) color.color |= 0xff000000; + Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true); + } +} + +core::dimension2d CGUITTFont::getCharDimension(const wchar_t ch) const +{ + return core::dimension2d(getWidthFromCharacter(ch), getHeightFromCharacter(ch)); +} + +core::dimension2d CGUITTFont::getDimension(const wchar_t* text) const +{ + return getDimension(core::ustring(text)); +} + +core::dimension2d CGUITTFont::getDimension(const core::ustring& text) const +{ + // Get the maximum font height. Unfortunately, we have to do this hack as + // Irrlicht will draw things wrong. In FreeType, the font size is the + // maximum size for a single glyph, but that glyph may hang "under" the + // draw line, increasing the total font height to beyond the set size. + // Irrlicht does not understand this concept when drawing fonts. Also, I + // add +1 to give it a 1 pixel blank border. This makes things like + // tooltips look nicer. + s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1; + s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1; + s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1; + s32 max_font_height = core::max_(test1, core::max_(test2, test3)); + + core::dimension2d text_dimension(0, max_font_height); + core::dimension2d line(0, max_font_height); + + uchar32_t previousChar = 0; + core::ustring::const_iterator iter = text.begin(); + for (; !iter.atEnd(); ++iter) + { + uchar32_t p = *iter; + bool lineBreak = false; + if (p == '\r') // Mac or Windows line breaks. + { + lineBreak = true; + if (*(iter + 1) == '\n') + { + ++iter; + p = *iter; + } + } + else if (p == '\n') // Unix line breaks. + { + lineBreak = true; + } + + // Kerning. + core::vector2di k = getKerning(p, previousChar); + line.Width += k.X; + previousChar = p; + + // Check for linebreak. + if (lineBreak) + { + previousChar = 0; + text_dimension.Height += line.Height; + if (text_dimension.Width < line.Width) + text_dimension.Width = line.Width; + line.Width = 0; + line.Height = max_font_height; + continue; + } + line.Width += getWidthFromCharacter(p); + } + if (text_dimension.Width < line.Width) + text_dimension.Width = line.Width; + + return text_dimension; +} + +inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const +{ + return getWidthFromCharacter((uchar32_t)c); +} + +inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const +{ + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + //FT_Set_Pixel_Sizes(tt_face, 0, size); + + u32 n = getGlyphIndexByChar(c); + if (n > 0) + { + int w = Glyphs[n-1].advance.x / 64; + return w; + } + if (c >= 0x2000) + return (font_metrics.ascender / 64); + else return (font_metrics.ascender / 64) / 2; +} + +inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const +{ + return getHeightFromCharacter((uchar32_t)c); +} + +inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const +{ + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + //FT_Set_Pixel_Sizes(tt_face, 0, size); + + u32 n = getGlyphIndexByChar(c); + if (n > 0) + { + // Grab the true height of the character, taking into account underhanging glyphs. + s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight(); + return height; + } + if (c >= 0x2000) + return (font_metrics.ascender / 64); + else return (font_metrics.ascender / 64) / 2; +} + +u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const +{ + return getGlyphIndexByChar((uchar32_t)c); +} + +u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const +{ + // Get the glyph. + u32 glyph = FT_Get_Char_Index(tt_face, c); + + // Check for a valid glyph. If it is invalid, attempt to use the replacement character. + if (glyph == 0) + glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER); + + // If our glyph is already loaded, don't bother doing any batch loading code. + if (glyph != 0 && Glyphs[glyph - 1].isLoaded) + return glyph; + + // Determine our batch loading positions. + u32 half_size = (batch_load_size / 2); + u32 start_pos = 0; + if (c > half_size) start_pos = c - half_size; + u32 end_pos = start_pos + batch_load_size; + + // Load all our characters. + do + { + // Get the character we are going to load. + u32 char_index = FT_Get_Char_Index(tt_face, start_pos); + + // If the glyph hasn't been loaded yet, do it now. + if (char_index) + { + SGUITTGlyph& glyph = Glyphs[char_index - 1]; + if (!glyph.isLoaded) + { + glyph.preload(char_index, tt_face, Driver, size, load_flags); + Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph); + } + } + } + while (++start_pos < end_pos); + + // Return our original character. + return glyph; +} + +s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const +{ + return getCharacterFromPos(core::ustring(text), pixel_x); +} + +s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const +{ + s32 x = 0; + //s32 idx = 0; + + u32 character = 0; + uchar32_t previousChar = 0; + core::ustring::const_iterator iter = text.begin(); + while (!iter.atEnd()) + { + uchar32_t c = *iter; + x += getWidthFromCharacter(c); + + // Kerning. + core::vector2di k = getKerning(c, previousChar); + x += k.X; + + if (x >= pixel_x) + return character; + + previousChar = c; + ++iter; + ++character; + } + + return -1; +} + +void CGUITTFont::setKerningWidth(s32 kerning) +{ + GlobalKerningWidth = kerning; +} + +void CGUITTFont::setKerningHeight(s32 kerning) +{ + GlobalKerningHeight = kerning; +} + +s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const +{ + if (tt_face == 0) + return GlobalKerningWidth; + if (thisLetter == 0 || previousLetter == 0) + return 0; + + return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter); +} + +s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const +{ + // Return only the kerning width. + return getKerning(thisLetter, previousLetter).X; +} + +s32 CGUITTFont::getKerningHeight() const +{ + // FreeType 2 currently doesn't return any height kerning information. + return GlobalKerningHeight; +} + +core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const +{ + return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter); +} + +core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const +{ + if (tt_face == 0 || thisLetter == 0 || previousLetter == 0) + return core::vector2di(); + + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + FT_Set_Pixel_Sizes(tt_face, 0, size); + + core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight); + + // If we don't have kerning, no point in continuing. + if (!FT_HAS_KERNING(tt_face)) + return ret; + + // Get the kerning information. + FT_Vector v; + FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v); + + // If we have a scalable font, the return value will be in font points. + if (FT_IS_SCALABLE(tt_face)) + { + // Font points, so divide by 64. + ret.X += (v.x / 64); + ret.Y += (v.y / 64); + } + else + { + // Pixel units. + ret.X += v.x; + ret.Y += v.y; + } + return ret; +} + +void CGUITTFont::setInvisibleCharacters(const wchar_t *s) +{ + core::ustring us(s); + Invisible = us; +} + +void CGUITTFont::setInvisibleCharacters(const core::ustring& s) +{ + Invisible = s; +} + +video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch) +{ + u32 n = getGlyphIndexByChar(ch); + const SGUITTGlyph& glyph = Glyphs[n-1]; + CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page]; + + if (page->dirty) + page->updateTexture(); + + video::ITexture* tex = page->texture; + + // Acquire a read-only lock of the corresponding page texture. + #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8 + void* ptr = tex->lock(video::ETLM_READ_ONLY); + #else + void* ptr = tex->lock(true); + #endif + + video::ECOLOR_FORMAT format = tex->getColorFormat(); + core::dimension2du tex_size = tex->getOriginalSize(); + video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false); + + // Copy the image data out of the page texture. + core::dimension2du glyph_size(glyph.source_rect.getSize()); + video::IImage* image = Driver->createImage(format, glyph_size); + pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect); + + tex->unlock(); + return image; +} + +video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const +{ + if (page_index < Glyph_Pages.size()) + return Glyph_Pages[page_index]->texture; + else + return 0; +} + +void CGUITTFont::createSharedPlane() +{ + /* + 2___3 + | /| + | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1) + |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1) + 0---1 + */ + + using namespace core; + using namespace video; + using namespace scene; + S3DVertex vertices[4]; + u16 indices[6] = {0,2,3,3,1,0}; + vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1)); + vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1)); + vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0)); + vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0)); + + SMeshBuffer* buf = new SMeshBuffer(); + buf->append(vertices, 4, indices, 6); + + shared_plane_.addMeshBuffer( buf ); + + shared_plane_ptr_ = &shared_plane_; + buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr. +} + +core::dimension2d CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const +{ + core::stringw s; + for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp ) + s.append(*temp); + + return getDimension(s.c_str()); +} + +core::array CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center) +{ + using namespace core; + using namespace video; + using namespace scene; + + array container; + + if (!Driver || !smgr) return container; + if (!parent) + parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1); + // if you don't specify parent, then we add a empty node attached to the root node + // this is generally undesirable. + + if (!shared_plane_ptr_) //this points to a static mesh that contains the plane + createSharedPlane(); //if it's not initialized, we create one. + + dimension2d text_size(getDimension(text)); //convert from unsigned to signed. + vector3df start_point(0, 0, 0), offset; + + /** NOTICE: + Because we are considering adding texts into 3D world, all Y axis vectors are inverted. + **/ + + // There's currently no "vertical center" concept when you apply text scene node to the 3D world. + if (center) + { + offset.X = start_point.X = -text_size.Width / 2.f; + offset.Y = start_point.Y = +text_size.Height/ 2.f; + offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1; + } + + // the default font material + SMaterial mat; + mat.setFlag(video::EMF_LIGHTING, true); + mat.setFlag(video::EMF_ZWRITE_ENABLE, false); + mat.setFlag(video::EMF_NORMALIZE_NORMALS, true); + mat.ColorMaterial = video::ECM_NONE; + mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID; + mat.MaterialTypeParam = 0.01f; + mat.DiffuseColor = color; + + wchar_t current_char = 0, previous_char = 0; + u32 n = 0; + + array glyph_indices; + + while (*text) + { + current_char = *text; + bool line_break=false; + if (current_char == L'\r') // Mac or Windows breaks + { + line_break = true; + if (*(text + 1) == L'\n') // Windows line breaks. + current_char = *(++text); + } + else if (current_char == L'\n') // Unix breaks + { + line_break = true; + } + + if (line_break) + { + previous_char = 0; + offset.Y -= tt_face->size->metrics.ascender / 64; + offset.X = start_point.X; + if (center) + offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1; + ++text; + } + else + { + n = getGlyphIndexByChar(current_char); + if (n > 0) + { + glyph_indices.push_back( n ); + + // Store glyph size and offset informations. + SGUITTGlyph const& glyph = Glyphs[n-1]; + u32 texw = glyph.source_rect.getWidth(); + u32 texh = glyph.source_rect.getHeight(); + s32 offx = glyph.offset.X; + s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y; + + // Apply kerning. + vector2di k = getKerning(current_char, previous_char); + offset.X += k.X; + offset.Y += k.Y; + + vector3df current_pos(offset.X + offx, offset.Y - offy, 0); + dimension2d letter_size = dimension2d(texw, texh); + + // Now we copy planes corresponding to the letter size. + IMeshManipulator* mani = smgr->getMeshManipulator(); + IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_); + #if IRRLICHT_VERSION_MAJOR==1 && IRRLICHT_VERSION_MINOR>=8 + mani->scale(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1)); + #else + mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1)); + #endif + + ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos); + meshcopy->drop(); + + current_node->getMaterial(0) = mat; + current_node->setAutomaticCulling(EAC_OFF); + current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter + //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging + + container.push_back(current_node); + } + offset.X += getWidthFromCharacter(current_char); + previous_char = current_char; + ++text; + } + } + + update_glyph_pages(); + //only after we update the textures can we use the glyph page textures. + + for (u32 i = 0; i < glyph_indices.size(); ++i) + { + u32 n = glyph_indices[i]; + SGUITTGlyph const& glyph = Glyphs[n-1]; + ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture; + f32 page_texture_size = (f32)current_tex->getSize().Width; + //Now we calculate the UV position according to the texture size and the source rect. + // + // 2___3 + // | /| + // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1) + // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1) + // 0---1 + // + f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size; + f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size); + f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size; + f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size); + + //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop. + IMeshSceneNode* node = static_cast(container[i]); + + S3DVertex* pv = static_cast(node->getMesh()->getMeshBuffer(0)->getVertices()); + //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast(letter_size.Height); + //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast(letter_size.Width); + pv[0].TCoords = vector2df(u1, v2); + pv[1].TCoords = vector2df(u2, v2); + pv[2].TCoords = vector2df(u1, v1); + pv[3].TCoords = vector2df(u2, v1); + + container[i]->getMaterial(0).setTexture(0, current_tex); + } + + return container; +} + +} // end namespace gui +} // end namespace irr diff --git a/src/cguittfont/CGUITTFont.h b/src/cguittfont/CGUITTFont.h new file mode 100644 index 000000000..12e25e0f3 --- /dev/null +++ b/src/cguittfont/CGUITTFont.h @@ -0,0 +1,377 @@ +/* + CGUITTFont FreeType class for Irrlicht + Copyright (c) 2009-2010 John Norman + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + The original version of this class can be located at: + http://irrlicht.suckerfreegames.com/ + + John Norman + john@suckerfreegames.com +*/ + +#ifndef __C_GUI_TTFONT_H_INCLUDED__ +#define __C_GUI_TTFONT_H_INCLUDED__ + +#include +#include +#include FT_FREETYPE_H + +namespace irr +{ +namespace gui +{ + struct SGUITTFace; + class CGUITTFont; + + //! Class to assist in deleting glyphs. + class CGUITTAssistDelete + { + public: + template + static void Delete(core::array& a) + { + TAlloc allocator; + allocator.deallocate(a.pointer()); + } + }; + + //! Structure representing a single TrueType glyph. + struct SGUITTGlyph + { + //! Constructor. + SGUITTGlyph() : isLoaded(false), glyph_page(0), surface(0), parent(0) {} + + //! Destructor. + ~SGUITTGlyph() { unload(); } + + //! Preload the glyph. + //! The preload process occurs when the program tries to cache the glyph from FT_Library. + //! However, it simply defines the SGUITTGlyph's properties and will only create the page + //! textures if necessary. The actual creation of the textures should only occur right + //! before the batch draw call. + void preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags); + + //! Unloads the glyph. + void unload(); + + //! Creates the IImage object from the FT_Bitmap. + video::IImage* createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const; + + //! If true, the glyph has been loaded. + bool isLoaded; + + //! The page the glyph is on. + u32 glyph_page; + + //! The source rectangle for the glyph. + core::recti source_rect; + + //! The offset of glyph when drawn. + core::vector2di offset; + + //! Glyph advance information. + FT_Vector advance; + + //! This is just the temporary image holder. After this glyph is paged, + //! it will be dropped. + mutable video::IImage* surface; + + //! The pointer pointing to the parent (CGUITTFont) + CGUITTFont* parent; + }; + + //! Holds a sheet of glyphs. + class CGUITTGlyphPage + { + public: + CGUITTGlyphPage(video::IVideoDriver* Driver, const io::path& texture_name) :texture(0), available_slots(0), used_slots(0), dirty(false), driver(Driver), name(texture_name) {} + ~CGUITTGlyphPage() + { + if (texture) + { + if (driver) + driver->removeTexture(texture); + else texture->drop(); + } + } + + //! Create the actual page texture, + bool createPageTexture(const u8& pixel_mode, const core::dimension2du& texture_size) + { + if( texture ) + return false; + + bool flgmip = driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); + + // Set the texture color format. + switch (pixel_mode) + { + case FT_PIXEL_MODE_MONO: + texture = driver->addTexture(texture_size, name, video::ECF_A1R5G5B5); + break; + case FT_PIXEL_MODE_GRAY: + default: + texture = driver->addTexture(texture_size, name, video::ECF_A8R8G8B8); + break; + } + + // Restore our texture creation flags. + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip); + return texture ? true : false; + } + + //! Add the glyph to a list of glyphs to be paged. + //! This collection will be cleared after updateTexture is called. + void pushGlyphToBePaged(const SGUITTGlyph* glyph) + { + glyph_to_be_paged.push_back(glyph); + } + + //! Updates the texture atlas with new glyphs. + void updateTexture() + { + if (!dirty) return; + + void* ptr = texture->lock(); + video::ECOLOR_FORMAT format = texture->getColorFormat(); + core::dimension2du size = texture->getOriginalSize(); + video::IImage* pageholder = driver->createImageFromData(format, size, ptr, true, false); + + for (u32 i = 0; i < glyph_to_be_paged.size(); ++i) + { + const SGUITTGlyph* glyph = glyph_to_be_paged[i]; + if (glyph && glyph->isLoaded) + { + if (glyph->surface) + { + glyph->surface->copyTo(pageholder, glyph->source_rect.UpperLeftCorner); + glyph->surface->drop(); + glyph->surface = 0; + } + else + { + ; // TODO: add error message? + //currently, if we failed to create the image, just ignore this operation. + } + } + } + + pageholder->drop(); + texture->unlock(); + glyph_to_be_paged.clear(); + dirty = false; + } + + video::ITexture* texture; + u32 available_slots; + u32 used_slots; + bool dirty; + + core::array render_positions; + core::array render_source_rects; + + private: + core::array glyph_to_be_paged; + video::IVideoDriver* driver; + io::path name; + }; + + //! Class representing a TrueType font. + class CGUITTFont : public IGUIFont + { + public: + //! Creates a new TrueType font and returns a pointer to it. The pointer must be drop()'ed when finished. + //! \param env The IGUIEnvironment the font loads out of. + //! \param filename The filename of the font. + //! \param size The size of the font glyphs in pixels. Since this is the size of the individual glyphs, the true height of the font may change depending on the characters used. + //! \param antialias set the use_monochrome (opposite to antialias) flag + //! \param transparency set the use_transparency flag + //! \return Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load. + static CGUITTFont* createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + static CGUITTFont* createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + static CGUITTFont* create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + static CGUITTFont* create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + + //! Destructor + virtual ~CGUITTFont(); + + //! Sets the amount of glyphs to batch load. + virtual void setBatchLoadSize(u32 batch_size) { batch_load_size = batch_size; } + + //! Sets the maximum texture size for a page of glyphs. + virtual void setMaxPageTextureSize(const core::dimension2du& texture_size) { max_page_texture_size = texture_size; } + + //! Get the font size. + virtual u32 getFontSize() const { return size; } + + //! Check the font's transparency. + virtual bool isTransparent() const { return use_transparency; } + + //! Check if the font auto-hinting is enabled. + //! Auto-hinting is FreeType's built-in font hinting engine. + virtual bool useAutoHinting() const { return use_auto_hinting; } + + //! Check if the font hinting is enabled. + virtual bool useHinting() const { return use_hinting; } + + //! Check if the font is being loaded as a monochrome font. + //! The font can either be a 256 color grayscale font, or a 2 color monochrome font. + virtual bool useMonochrome() const { return use_monochrome; } + + //! Tells the font to allow transparency when rendering. + //! Default: true. + //! \param flag If true, the font draws using transparency. + virtual void setTransparency(const bool flag); + + //! Tells the font to use monochrome rendering. + //! Default: false. + //! \param flag If true, the font draws using a monochrome image. If false, the font uses a grayscale image. + virtual void setMonochrome(const bool flag); + + //! Enables or disables font hinting. + //! Default: Hinting and auto-hinting true. + //! \param enable If false, font hinting is turned off. If true, font hinting is turned on. + //! \param enable_auto_hinting If true, FreeType uses its own auto-hinting algorithm. If false, it tries to use the algorithm specified by the font. + virtual void setFontHinting(const bool enable, const bool enable_auto_hinting = true); + + //! Draws some text and clips it to the specified rectangle if wanted. + virtual void draw(const core::stringw& text, const core::rect& position, + video::SColor color, bool hcenter=false, bool vcenter=false, + const core::rect* clip=0); + + //! Returns the dimension of a character produced by this font. + virtual core::dimension2d getCharDimension(const wchar_t ch) const; + + //! Returns the dimension of a text string. + virtual core::dimension2d getDimension(const wchar_t* text) const; + virtual core::dimension2d getDimension(const core::ustring& text) const; + + //! Calculates the index of the character in the text which is on a specific position. + virtual s32 getCharacterFromPos(const wchar_t* text, s32 pixel_x) const; + virtual s32 getCharacterFromPos(const core::ustring& text, s32 pixel_x) const; + + //! Sets global kerning width for the font. + virtual void setKerningWidth(s32 kerning); + + //! Sets global kerning height for the font. + virtual void setKerningHeight(s32 kerning); + + //! Gets kerning values (distance between letters) for the font. If no parameters are provided, + virtual s32 getKerningWidth(const wchar_t* thisLetter=0, const wchar_t* previousLetter=0) const; + virtual s32 getKerningWidth(const uchar32_t thisLetter=0, const uchar32_t previousLetter=0) const; + + //! Returns the distance between letters + virtual s32 getKerningHeight() const; + + //! Define which characters should not be drawn by the font. + virtual void setInvisibleCharacters(const wchar_t *s); + virtual void setInvisibleCharacters(const core::ustring& s); + + //! Get the last glyph page if there's still available slots. + //! If not, it will return zero. + CGUITTGlyphPage* getLastGlyphPage() const; + + //! Create a new glyph page texture. + //! \param pixel_mode the pixel mode defined by FT_Pixel_Mode + //should be better typed. fix later. + CGUITTGlyphPage* createGlyphPage(const u8& pixel_mode); + + //! Get the last glyph page's index. + u32 getLastGlyphPageIndex() const { return Glyph_Pages.size() - 1; } + + //! Create corresponding character's software image copy from the font, + //! so you can use this data just like any ordinary video::IImage. + //! \param ch The character you need + virtual video::IImage* createTextureFromChar(const uchar32_t& ch); + + //! This function is for debugging mostly. If the page doesn't exist it returns zero. + //! \param page_index Simply return the texture handle of a given page index. + virtual video::ITexture* getPageTextureByIndex(const u32& page_index) const; + + //! Add a list of scene nodes generated by putting font textures on the 3D planes. + virtual core::array addTextSceneNode + (const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent = 0, + const video::SColor& color = video::SColor(255, 0, 0, 0), bool center = false ); + + protected: + bool use_monochrome; + bool use_transparency; + bool use_hinting; + bool use_auto_hinting; + u32 size; + u32 batch_load_size; + core::dimension2du max_page_texture_size; + + private: + // Manages the FreeType library. + static FT_Library c_library; + static core::map c_faces; + static bool c_libraryLoaded; + static scene::IMesh* shared_plane_ptr_; + static scene::SMesh shared_plane_; + + CGUITTFont(IGUIEnvironment *env); + bool load(const io::path& filename, const u32 size, const bool antialias, const bool transparency); + void reset_images(); + void update_glyph_pages() const; + void update_load_flags() + { + // Set up our loading flags. + load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; + if (!useHinting()) load_flags |= FT_LOAD_NO_HINTING; + if (!useAutoHinting()) load_flags |= FT_LOAD_NO_AUTOHINT; + if (useMonochrome()) load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO | FT_RENDER_MODE_MONO; + else load_flags |= FT_LOAD_TARGET_NORMAL; + } + u32 getWidthFromCharacter(wchar_t c) const; + u32 getWidthFromCharacter(uchar32_t c) const; + u32 getHeightFromCharacter(wchar_t c) const; + u32 getHeightFromCharacter(uchar32_t c) const; + u32 getGlyphIndexByChar(wchar_t c) const; + u32 getGlyphIndexByChar(uchar32_t c) const; + core::vector2di getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const; + core::vector2di getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const; + core::dimension2d getDimensionUntilEndOfLine(const wchar_t* p) const; + + void createSharedPlane(); + + irr::IrrlichtDevice* Device; + gui::IGUIEnvironment* Environment; + video::IVideoDriver* Driver; + io::path filename; + FT_Face tt_face; + FT_Size_Metrics font_metrics; + FT_Int32 load_flags; + + mutable core::array Glyph_Pages; + mutable core::array Glyphs; + + s32 GlobalKerningWidth; + s32 GlobalKerningHeight; + core::ustring Invisible; + }; + +} // end namespace gui +} // end namespace irr + +#endif // __C_GUI_TTFONT_H_INCLUDED__ diff --git a/src/cguittfont/CMakeLists.txt b/src/cguittfont/CMakeLists.txt new file mode 100644 index 000000000..94d061462 --- /dev/null +++ b/src/cguittfont/CMakeLists.txt @@ -0,0 +1,17 @@ +include_directories( + ${IRRLICHT_INCLUDE_DIR} + ${FREETYPE_INCLUDE_DIRS} +) + +# CGUITTFont authors, y u no include headers you use? +# Do not add CGUITTFont.cpp to the line below. +# xCGUITTFont.cpp is a wrapper file that includes +# additional required headers. +add_library(cguittfont xCGUITTFont.cpp) + +target_link_libraries( + cguittfont + ${IRRLICHT_LIBRARY} + ${FREETYPE_LIBRARY} + ${ZLIB_LIBRARIES} # needed by freetype, repeated here for safety +) diff --git a/src/cguittfont/irrUString.h b/src/cguittfont/irrUString.h new file mode 100644 index 000000000..f41fa1f7b --- /dev/null +++ b/src/cguittfont/irrUString.h @@ -0,0 +1,3877 @@ +/* + Basic Unicode string class for Irrlicht. + Copyright (c) 2009-2011 John Norman + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + The original version of this class can be located at: + http://irrlicht.suckerfreegames.com/ + + John Norman + john@suckerfreegames.com +*/ + +#ifndef __IRR_USTRING_H_INCLUDED__ +#define __IRR_USTRING_H_INCLUDED__ + +#if (__cplusplus > 199711L) || (_MSC_VER >= 1600) || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define USTRING_CPP0X +# if defined(__GXX_EXPERIMENTAL_CXX0X__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5))) +# define USTRING_CPP0X_NEWLITERALS +# endif +#endif + +#include +#include +#include + +#ifdef USTRING_CPP0X +# include +#endif + +#ifndef USTRING_NO_STL +# include +# include +# include +#endif + +#include "irrTypes.h" +#include "irrAllocator.h" +#include "irrArray.h" +#include "irrMath.h" +#include "irrString.h" +#include "path.h" + +//! UTF-16 surrogate start values. +static const irr::u16 UTF16_HI_SURROGATE = 0xD800; +static const irr::u16 UTF16_LO_SURROGATE = 0xDC00; + +//! Is a UTF-16 code point a surrogate? +#define UTF16_IS_SURROGATE(c) (((c) & 0xF800) == 0xD800) +#define UTF16_IS_SURROGATE_HI(c) (((c) & 0xFC00) == 0xD800) +#define UTF16_IS_SURROGATE_LO(c) (((c) & 0xFC00) == 0xDC00) + + +namespace irr +{ + + // Define our character types. +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + typedef char32_t uchar32_t; + typedef char16_t uchar16_t; + typedef char uchar8_t; +#else + typedef u32 uchar32_t; + typedef u16 uchar16_t; + typedef u8 uchar8_t; +#endif + +namespace core +{ + +namespace unicode +{ + +//! The unicode replacement character. Used to replace invalid characters. +const irr::u16 UTF_REPLACEMENT_CHARACTER = 0xFFFD; + +//! Convert a UTF-16 surrogate pair into a UTF-32 character. +//! \param high The high value of the pair. +//! \param low The low value of the pair. +//! \return The UTF-32 character expressed by the surrogate pair. +inline uchar32_t toUTF32(uchar16_t high, uchar16_t low) +{ + // Convert the surrogate pair into a single UTF-32 character. + uchar32_t x = ((high & ((1 << 6) -1)) << 10) | (low & ((1 << 10) -1)); + uchar32_t wu = ((high >> 6) & ((1 << 5) - 1)) + 1; + return (wu << 16) | x; +} + +//! Swaps the endianness of a 16-bit value. +//! \return The new value. +inline uchar16_t swapEndian16(const uchar16_t& c) +{ + return ((c >> 8) & 0x00FF) | ((c << 8) & 0xFF00); +} + +//! Swaps the endianness of a 32-bit value. +//! \return The new value. +inline uchar32_t swapEndian32(const uchar32_t& c) +{ + return ((c >> 24) & 0x000000FF) | + ((c >> 8) & 0x0000FF00) | + ((c << 8) & 0x00FF0000) | + ((c << 24) & 0xFF000000); +} + +//! The Unicode byte order mark. +const u16 BOM = 0xFEFF; + +//! The size of the Unicode byte order mark in terms of the Unicode character size. +const u8 BOM_UTF8_LEN = 3; +const u8 BOM_UTF16_LEN = 1; +const u8 BOM_UTF32_LEN = 1; + +//! Unicode byte order marks for file operations. +const u8 BOM_ENCODE_UTF8[3] = { 0xEF, 0xBB, 0xBF }; +const u8 BOM_ENCODE_UTF16_BE[2] = { 0xFE, 0xFF }; +const u8 BOM_ENCODE_UTF16_LE[2] = { 0xFF, 0xFE }; +const u8 BOM_ENCODE_UTF32_BE[4] = { 0x00, 0x00, 0xFE, 0xFF }; +const u8 BOM_ENCODE_UTF32_LE[4] = { 0xFF, 0xFE, 0x00, 0x00 }; + +//! The size in bytes of the Unicode byte marks for file operations. +const u8 BOM_ENCODE_UTF8_LEN = 3; +const u8 BOM_ENCODE_UTF16_LEN = 2; +const u8 BOM_ENCODE_UTF32_LEN = 4; + +//! Unicode encoding type. +enum EUTF_ENCODE +{ + EUTFE_NONE = 0, + EUTFE_UTF8, + EUTFE_UTF16, + EUTFE_UTF16_LE, + EUTFE_UTF16_BE, + EUTFE_UTF32, + EUTFE_UTF32_LE, + EUTFE_UTF32_BE +}; + +//! Unicode endianness. +enum EUTF_ENDIAN +{ + EUTFEE_NATIVE = 0, + EUTFEE_LITTLE, + EUTFEE_BIG +}; + +//! Returns the specified unicode byte order mark in a byte array. +//! The byte order mark is the first few bytes in a text file that signifies its encoding. +/** \param mode The Unicode encoding method that we want to get the byte order mark for. + If EUTFE_UTF16 or EUTFE_UTF32 is passed, it uses the native system endianness. **/ +//! \return An array that contains a byte order mark. +inline core::array getUnicodeBOM(EUTF_ENCODE mode) +{ +#define COPY_ARRAY(source, size) \ + memcpy(ret.pointer(), source, size); \ + ret.set_used(size) + + core::array ret(4); + switch (mode) + { + case EUTFE_UTF8: + COPY_ARRAY(BOM_ENCODE_UTF8, BOM_ENCODE_UTF8_LEN); + break; + case EUTFE_UTF16: + #ifdef __BIG_ENDIAN__ + COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); + #else + COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); + #endif + break; + case EUTFE_UTF16_BE: + COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); + break; + case EUTFE_UTF16_LE: + COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); + break; + case EUTFE_UTF32: + #ifdef __BIG_ENDIAN__ + COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); + #else + COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); + #endif + break; + case EUTFE_UTF32_BE: + COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); + break; + case EUTFE_UTF32_LE: + COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); + break; + } + return ret; + +#undef COPY_ARRAY +} + +//! Detects if the given data stream starts with a unicode BOM. +//! \param data The data stream to check. +//! \return The unicode BOM associated with the data stream, or EUTFE_NONE if none was found. +inline EUTF_ENCODE determineUnicodeBOM(const char* data) +{ + if (memcmp(data, BOM_ENCODE_UTF8, 3) == 0) return EUTFE_UTF8; + if (memcmp(data, BOM_ENCODE_UTF16_BE, 2) == 0) return EUTFE_UTF16_BE; + if (memcmp(data, BOM_ENCODE_UTF16_LE, 2) == 0) return EUTFE_UTF16_LE; + if (memcmp(data, BOM_ENCODE_UTF32_BE, 4) == 0) return EUTFE_UTF32_BE; + if (memcmp(data, BOM_ENCODE_UTF32_LE, 4) == 0) return EUTFE_UTF32_LE; + return EUTFE_NONE; +} + +} // end namespace unicode + + +//! UTF-16 string class. +template > +class ustring16 +{ +public: + + ///------------------/// + /// iterator classes /// + ///------------------/// + + //! Access an element in a unicode string, allowing one to change it. + class _ustring16_iterator_access + { + public: + _ustring16_iterator_access(const ustring16* s, u32 p) : ref(s), pos(p) {} + + //! Allow the class to be interpreted as a single UTF-32 character. + operator uchar32_t() const + { + return _get(); + } + + //! Allow one to change the character in the unicode string. + //! \param c The new character to use. + //! \return Myself. + _ustring16_iterator_access& operator=(const uchar32_t c) + { + _set(c); + return *this; + } + + //! Increments the value by 1. + //! \return Myself. + _ustring16_iterator_access& operator++() + { + _set(_get() + 1); + return *this; + } + + //! Increments the value by 1, returning the old value. + //! \return A unicode character. + uchar32_t operator++(int) + { + uchar32_t old = _get(); + _set(old + 1); + return old; + } + + //! Decrements the value by 1. + //! \return Myself. + _ustring16_iterator_access& operator--() + { + _set(_get() - 1); + return *this; + } + + //! Decrements the value by 1, returning the old value. + //! \return A unicode character. + uchar32_t operator--(int) + { + uchar32_t old = _get(); + _set(old - 1); + return old; + } + + //! Adds to the value by a specified amount. + //! \param val The amount to add to this character. + //! \return Myself. + _ustring16_iterator_access& operator+=(int val) + { + _set(_get() + val); + return *this; + } + + //! Subtracts from the value by a specified amount. + //! \param val The amount to subtract from this character. + //! \return Myself. + _ustring16_iterator_access& operator-=(int val) + { + _set(_get() - val); + return *this; + } + + //! Multiples the value by a specified amount. + //! \param val The amount to multiply this character by. + //! \return Myself. + _ustring16_iterator_access& operator*=(int val) + { + _set(_get() * val); + return *this; + } + + //! Divides the value by a specified amount. + //! \param val The amount to divide this character by. + //! \return Myself. + _ustring16_iterator_access& operator/=(int val) + { + _set(_get() / val); + return *this; + } + + //! Modulos the value by a specified amount. + //! \param val The amount to modulo this character by. + //! \return Myself. + _ustring16_iterator_access& operator%=(int val) + { + _set(_get() % val); + return *this; + } + + //! Adds to the value by a specified amount. + //! \param val The amount to add to this character. + //! \return A unicode character. + uchar32_t operator+(int val) const + { + return _get() + val; + } + + //! Subtracts from the value by a specified amount. + //! \param val The amount to subtract from this character. + //! \return A unicode character. + uchar32_t operator-(int val) const + { + return _get() - val; + } + + //! Multiplies the value by a specified amount. + //! \param val The amount to multiply this character by. + //! \return A unicode character. + uchar32_t operator*(int val) const + { + return _get() * val; + } + + //! Divides the value by a specified amount. + //! \param val The amount to divide this character by. + //! \return A unicode character. + uchar32_t operator/(int val) const + { + return _get() / val; + } + + //! Modulos the value by a specified amount. + //! \param val The amount to modulo this character by. + //! \return A unicode character. + uchar32_t operator%(int val) const + { + return _get() % val; + } + + private: + //! Gets a uchar32_t from our current position. + uchar32_t _get() const + { + const uchar16_t* a = ref->c_str(); + if (!UTF16_IS_SURROGATE(a[pos])) + return static_cast(a[pos]); + else + { + if (pos + 1 >= ref->size_raw()) + return 0; + + return unicode::toUTF32(a[pos], a[pos + 1]); + } + } + + //! Sets a uchar32_t at our current position. + void _set(uchar32_t c) + { + ustring16* ref2 = const_cast*>(ref); + const uchar16_t* a = ref2->c_str(); + if (c > 0xFFFF) + { + // c will be multibyte, so split it up into the high and low surrogate pairs. + uchar16_t x = static_cast(c); + uchar16_t vh = UTF16_HI_SURROGATE | ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + + // If the previous position was a surrogate pair, just replace them. Else, insert the low pair. + if (UTF16_IS_SURROGATE_HI(a[pos]) && pos + 1 != ref2->size_raw()) + ref2->replace_raw(vl, static_cast(pos) + 1); + else ref2->insert_raw(vl, static_cast(pos) + 1); + + ref2->replace_raw(vh, static_cast(pos)); + } + else + { + // c will be a single byte. + uchar16_t vh = static_cast(c); + + // If the previous position was a surrogate pair, remove the extra byte. + if (UTF16_IS_SURROGATE_HI(a[pos])) + ref2->erase_raw(static_cast(pos) + 1); + + ref2->replace_raw(vh, static_cast(pos)); + } + } + + const ustring16* ref; + u32 pos; + }; + typedef typename ustring16::_ustring16_iterator_access access; + + + //! Iterator to iterate through a UTF-16 string. +#ifndef USTRING_NO_STL + class _ustring16_const_iterator : public std::iterator< + std::bidirectional_iterator_tag, // iterator_category + access, // value_type + ptrdiff_t, // difference_type + const access, // pointer + const access // reference + > +#else + class _ustring16_const_iterator +#endif + { + public: + typedef _ustring16_const_iterator _Iter; + typedef std::iterator _Base; + typedef const access const_pointer; + typedef const access const_reference; + +#ifndef USTRING_NO_STL + typedef typename _Base::value_type value_type; + typedef typename _Base::difference_type difference_type; + typedef typename _Base::difference_type distance_type; + typedef typename _Base::pointer pointer; + typedef const_reference reference; +#else + typedef access value_type; + typedef u32 difference_type; + typedef u32 distance_type; + typedef const_pointer pointer; + typedef const_reference reference; +#endif + + //! Constructors. + _ustring16_const_iterator(const _Iter& i) : ref(i.ref), pos(i.pos) {} + _ustring16_const_iterator(const ustring16& s) : ref(&s), pos(0) {} + _ustring16_const_iterator(const ustring16& s, const u32 p) : ref(&s), pos(0) + { + if (ref->size_raw() == 0 || p == 0) + return; + + // Go to the appropriate position. + u32 i = p; + u32 sr = ref->size_raw(); + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos < sr) + { + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; + else ++pos; + --i; + } + } + + //! Test for equalness. + bool operator==(const _Iter& iter) const + { + if (ref == iter.ref && pos == iter.pos) + return true; + return false; + } + + //! Test for unequalness. + bool operator!=(const _Iter& iter) const + { + if (ref != iter.ref || pos != iter.pos) + return true; + return false; + } + + //! Switch to the next full character in the string. + _Iter& operator++() + { // ++iterator + if (pos == ref->size_raw()) return *this; + const uchar16_t* a = ref->c_str(); + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; // TODO: check for valid low surrogate? + else ++pos; + if (pos > ref->size_raw()) pos = ref->size_raw(); + return *this; + } + + //! Switch to the next full character in the string, returning the previous position. + _Iter operator++(int) + { // iterator++ + _Iter _tmp(*this); + ++*this; + return _tmp; + } + + //! Switch to the previous full character in the string. + _Iter& operator--() + { // --iterator + if (pos == 0) return *this; + const uchar16_t* a = ref->c_str(); + --pos; + if (UTF16_IS_SURROGATE_LO(a[pos]) && pos != 0) // low surrogate, go back one more. + --pos; + return *this; + } + + //! Switch to the previous full character in the string, returning the previous position. + _Iter operator--(int) + { // iterator-- + _Iter _tmp(*this); + --*this; + return _tmp; + } + + //! Advance a specified number of full characters in the string. + //! \return Myself. + _Iter& operator+=(const difference_type v) + { + if (v == 0) return *this; + if (v < 0) return operator-=(v * -1); + + if (pos >= ref->size_raw()) + return *this; + + // Go to the appropriate position. + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + u32 i = (u32)v; + u32 sr = ref->size_raw(); + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos < sr) + { + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; + else ++pos; + --i; + } + if (pos > sr) + pos = sr; + + return *this; + } + + //! Go back a specified number of full characters in the string. + //! \return Myself. + _Iter& operator-=(const difference_type v) + { + if (v == 0) return *this; + if (v > 0) return operator+=(v * -1); + + if (pos == 0) + return *this; + + // Go to the appropriate position. + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + u32 i = (u32)v; + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos != 0) + { + --pos; + if (UTF16_IS_SURROGATE_LO(a[pos]) != 0 && pos != 0) + --pos; + --i; + } + + return *this; + } + + //! Return a new iterator that is a variable number of full characters forward from the current position. + _Iter operator+(const difference_type v) const + { + _Iter ret(*this); + ret += v; + return ret; + } + + //! Return a new iterator that is a variable number of full characters backward from the current position. + _Iter operator-(const difference_type v) const + { + _Iter ret(*this); + ret -= v; + return ret; + } + + //! Returns the distance between two iterators. + difference_type operator-(const _Iter& iter) const + { + // Make sure we reference the same object! + if (ref != iter.ref) + return difference_type(); + + _Iter i = iter; + difference_type ret; + + // Walk up. + if (pos > i.pos) + { + while (pos > i.pos) + { + ++i; + ++ret; + } + return ret; + } + + // Walk down. + while (pos < i.pos) + { + --i; + --ret; + } + return ret; + } + + //! Accesses the full character at the iterator's position. + const_reference operator*() const + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + const_reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + reference operator*() + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + const_pointer operator->() const + { + return operator*(); + } + + //! Accesses the full character at the iterator's position. + pointer operator->() + { + return operator*(); + } + + //! Is the iterator at the start of the string? + bool atStart() const + { + return pos == 0; + } + + //! Is the iterator at the end of the string? + bool atEnd() const + { + const uchar16_t* a = ref->c_str(); + if (UTF16_IS_SURROGATE(a[pos])) + return (pos + 1) >= ref->size_raw(); + else return pos >= ref->size_raw(); + } + + //! Moves the iterator to the start of the string. + void toStart() + { + pos = 0; + } + + //! Moves the iterator to the end of the string. + void toEnd() + { + const uchar16_t* a = ref->c_str(); + pos = ref->size_raw(); + } + + //! Returns the iterator's position. + //! \return The iterator's position. + u32 getPos() const + { + return pos; + } + + protected: + const ustring16* ref; + u32 pos; + }; + + //! Iterator to iterate through a UTF-16 string. + class _ustring16_iterator : public _ustring16_const_iterator + { + public: + typedef _ustring16_iterator _Iter; + typedef _ustring16_const_iterator _Base; + typedef typename _Base::const_pointer const_pointer; + typedef typename _Base::const_reference const_reference; + + typedef typename _Base::value_type value_type; + typedef typename _Base::difference_type difference_type; + typedef typename _Base::distance_type distance_type; + typedef access pointer; + typedef access reference; + + using _Base::pos; + using _Base::ref; + + //! Constructors. + _ustring16_iterator(const _Iter& i) : _ustring16_const_iterator(i) {} + _ustring16_iterator(const ustring16& s) : _ustring16_const_iterator(s) {} + _ustring16_iterator(const ustring16& s, const u32 p) : _ustring16_const_iterator(s, p) {} + + //! Accesses the full character at the iterator's position. + reference operator*() const + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + reference operator*() + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + pointer operator->() const + { + return operator*(); + } + + //! Accesses the full character at the iterator's position. + pointer operator->() + { + return operator*(); + } + }; + + typedef typename ustring16::_ustring16_iterator iterator; + typedef typename ustring16::_ustring16_const_iterator const_iterator; + + ///----------------------/// + /// end iterator classes /// + ///----------------------/// + + //! Default constructor + ustring16() + : array(0), allocated(1), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + array = allocator.allocate(1); // new u16[1]; + array[0] = 0x0; + } + + + //! Constructor + ustring16(const ustring16& other) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other; + } + + + //! Constructor from other string types + template + ustring16(const string& other) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other; + } + + +#ifndef USTRING_NO_STL + //! Constructor from std::string + template + ustring16(const std::basic_string& other) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other.c_str(); + } + + + //! Constructor from iterator. + template + ustring16(Itr first, Itr last) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + reserve(std::distance(first, last)); + array[used] = 0; + + for (; first != last; ++first) + append((uchar32_t)*first); + } +#endif + + +#ifndef USTRING_CPP0X_NEWLITERALS + //! Constructor for copying a character string from a pointer. + ustring16(const char* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + loadDataStream(c, strlen(c)); + //append((uchar8_t*)c); + } + + + //! Constructor for copying a character string from a pointer with a given length. + ustring16(const char* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + loadDataStream(c, length); + } +#endif + + + //! Constructor for copying a UTF-8 string from a pointer. + ustring16(const uchar8_t* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-8 string from a single char. + ustring16(const char c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append((uchar32_t)c); + } + + + //! Constructor for copying a UTF-8 string from a pointer with a given length. + ustring16(const uchar8_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a UTF-16 string from a pointer. + ustring16(const uchar16_t* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-16 string from a pointer with a given length + ustring16(const uchar16_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a UTF-32 string from a pointer. + ustring16(const uchar32_t* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-32 from a pointer with a given length. + ustring16(const uchar32_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a wchar_t string from a pointer. + ustring16(const wchar_t* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + if (sizeof(wchar_t) == 4) + append(reinterpret_cast(c)); + else if (sizeof(wchar_t) == 2) + append(reinterpret_cast(c)); + else if (sizeof(wchar_t) == 1) + append(reinterpret_cast(c)); + } + + + //! Constructor for copying a wchar_t string from a pointer with a given length. + ustring16(const wchar_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + if (sizeof(wchar_t) == 4) + append(reinterpret_cast(c), length); + else if (sizeof(wchar_t) == 2) + append(reinterpret_cast(c), length); + else if (sizeof(wchar_t) == 1) + append(reinterpret_cast(c), length); + } + + +#ifdef USTRING_CPP0X + //! Constructor for moving a ustring16 + ustring16(ustring16&& other) + : array(other.array), encoding(other.encoding), allocated(other.allocated), used(other.used) + { + //std::cout << "MOVE constructor" << std::endl; + other.array = 0; + other.allocated = 0; + other.used = 0; + } +#endif + + + //! Destructor + ~ustring16() + { + allocator.deallocate(array); // delete [] array; + } + + + //! Assignment operator + ustring16& operator=(const ustring16& other) + { + if (this == &other) + return *this; + + used = other.size_raw(); + if (used >= allocated) + { + allocator.deallocate(array); // delete [] array; + allocated = used + 1; + array = allocator.allocate(used + 1); //new u16[used]; + } + + const uchar16_t* p = other.c_str(); + for (u32 i=0; i<=used; ++i, ++p) + array[i] = *p; + + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + +#ifdef USTRING_CPP0X + //! Move assignment operator + ustring16& operator=(ustring16&& other) + { + if (this != &other) + { + //std::cout << "MOVE operator=" << std::endl; + allocator.deallocate(array); + + array = other.array; + allocated = other.allocated; + encoding = other.encoding; + used = other.used; + other.array = 0; + other.used = 0; + } + return *this; + } +#endif + + + //! Assignment operator for other string types + template + ustring16& operator=(const string& other) + { + *this = other.c_str(); + return *this; + } + + + //! Assignment operator for UTF-8 strings + ustring16& operator=(const uchar8_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for UTF-16 strings + ustring16& operator=(const uchar16_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for UTF-32 strings + ustring16& operator=(const uchar32_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for wchar_t strings. + /** Note that this assumes that a correct unicode string is stored in the wchar_t string. + Since wchar_t changes depending on its platform, it could either be a UTF-8, -16, or -32 string. + This function assumes you are storing the correct unicode encoding inside the wchar_t string. **/ + ustring16& operator=(const wchar_t* const c) + { + if (sizeof(wchar_t) == 4) + *this = reinterpret_cast(c); + else if (sizeof(wchar_t) == 2) + *this = reinterpret_cast(c); + else if (sizeof(wchar_t) == 1) + *this = reinterpret_cast(c); + + return *this; + } + + + //! Assignment operator for other strings. + /** Note that this assumes that a correct unicode string is stored in the string. **/ + template + ustring16& operator=(const B* const c) + { + if (sizeof(B) == 4) + *this = reinterpret_cast(c); + else if (sizeof(B) == 2) + *this = reinterpret_cast(c); + else if (sizeof(B) == 1) + *this = reinterpret_cast(c); + + return *this; + } + + + //! Direct access operator + access operator [](const u32 index) + { + _IRR_DEBUG_BREAK_IF(index>=size()) // bad index + iterator iter(*this, index); + return iter.operator*(); + } + + + //! Direct access operator + const access operator [](const u32 index) const + { + _IRR_DEBUG_BREAK_IF(index>=size()) // bad index + const_iterator iter(*this, index); + return iter.operator*(); + } + + + //! Equality operator + bool operator ==(const uchar16_t* const str) const + { + if (!str) + return false; + + u32 i; + for(i=0; array[i] && str[i]; ++i) + if (array[i] != str[i]) + return false; + + return !array[i] && !str[i]; + } + + + //! Equality operator + bool operator ==(const ustring16& other) const + { + for(u32 i=0; array[i] && other.array[i]; ++i) + if (array[i] != other.array[i]) + return false; + + return used == other.used; + } + + + //! Is smaller comparator + bool operator <(const ustring16& other) const + { + for(u32 i=0; array[i] && other.array[i]; ++i) + { + s32 diff = array[i] - other.array[i]; + if ( diff ) + return diff < 0; + } + + return used < other.used; + } + + + //! Inequality operator + bool operator !=(const uchar16_t* const str) const + { + return !(*this == str); + } + + + //! Inequality operator + bool operator !=(const ustring16& other) const + { + return !(*this == other); + } + + + //! Returns the length of a ustring16 in full characters. + //! \return Length of a ustring16 in full characters. + u32 size() const + { + const_iterator i(*this, 0); + u32 pos = 0; + while (!i.atEnd()) + { + ++i; + ++pos; + } + return pos; + } + + + //! Informs if the ustring is empty or not. + //! \return True if the ustring is empty, false if not. + bool empty() const + { + return (size_raw() == 0); + } + + + //! Returns a pointer to the raw UTF-16 string data. + //! \return pointer to C-style NUL terminated array of UTF-16 code points. + const uchar16_t* c_str() const + { + return array; + } + + + //! Compares the first n characters of this string with another. + //! \param other Other string to compare to. + //! \param n Number of characters to compare. + //! \return True if the n first characters of both strings are equal. + bool equalsn(const ustring16& other, u32 n) const + { + u32 i; + const uchar16_t* oa = other.c_str(); + for(i=0; array[i] && oa[i] && i < n; ++i) + if (array[i] != oa[i]) + return false; + + // if one (or both) of the strings was smaller then they + // are only equal if they have the same length + return (i == n) || (used == other.used); + } + + + //! Compares the first n characters of this string with another. + //! \param str Other string to compare to. + //! \param n Number of characters to compare. + //! \return True if the n first characters of both strings are equal. + bool equalsn(const uchar16_t* const str, u32 n) const + { + if (!str) + return false; + u32 i; + for(i=0; array[i] && str[i] && i < n; ++i) + if (array[i] != str[i]) + return false; + + // if one (or both) of the strings was smaller then they + // are only equal if they have the same length + return (i == n) || (array[i] == 0 && str[i] == 0); + } + + + //! Appends a character to this ustring16 + //! \param character The character to append. + //! \return A reference to our current string. + ustring16& append(uchar32_t character) + { + if (used + 2 >= allocated) + reallocate(used + 2); + + if (character > 0xFFFF) + { + used += 2; + + // character will be multibyte, so split it up into a surrogate pair. + uchar16_t x = static_cast(character); + uchar16_t vh = UTF16_HI_SURROGATE | ((((character >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[used-2] = vh; + array[used-1] = vl; + } + else + { + ++used; + array[used-1] = character; + } + array[used] = 0; + + return *this; + } + + + //! Appends a UTF-8 string to this ustring16 + //! \param other The UTF-8 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar8_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Determine if the string is long enough for a BOM. + u32 len = 0; + const uchar8_t* p = other; + do + { + ++len; + } while (*p++ && len < unicode::BOM_ENCODE_UTF8_LEN); + + // Check for BOM. + unicode::EUTF_ENCODE c_bom = unicode::EUTFE_NONE; + if (len == unicode::BOM_ENCODE_UTF8_LEN) + { + if (memcmp(other, unicode::BOM_ENCODE_UTF8, unicode::BOM_ENCODE_UTF8_LEN) == 0) + c_bom = unicode::EUTFE_UTF8; + } + + // If a BOM was found, don't include it in the string. + const uchar8_t* c2 = other; + if (c_bom != unicode::EUTFE_NONE) + { + c2 = other + unicode::BOM_UTF8_LEN; + length -= unicode::BOM_UTF8_LEN; + } + + // Calculate the size of the string to read in. + len = 0; + p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the array, do it now. + if (used + len >= allocated) + reallocate(used + (len * 2)); + u32 start = used; + + // Convert UTF-8 to UTF-16. + u32 pos = start; + for (u32 l = 0; l> 6) & 0x03) == 0x02) + { // Invalid continuation byte. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + } + else if (c2[l] == 0xC0 || c2[l] == 0xC1) + { // Invalid byte - overlong encoding. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + } + else if ((c2[l] & 0xF8) == 0xF0) + { // 4 bytes UTF-8, 2 bytes UTF-16. + // Check for a full string. + if ((l + 3) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 3; + break; + } + + // Validate. + bool valid = true; + u8 l2 = 0; + if (valid && (((c2[l+1] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+2] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+3] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (!valid) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += l2; + continue; + } + + // Decode. + uchar8_t b1 = ((c2[l] & 0x7) << 2) | ((c2[l+1] >> 4) & 0x3); + uchar8_t b2 = ((c2[l+1] & 0xF) << 4) | ((c2[l+2] >> 2) & 0xF); + uchar8_t b3 = ((c2[l+2] & 0x3) << 6) | (c2[l+3] & 0x3F); + uchar32_t v = b3 | ((uchar32_t)b2 << 8) | ((uchar32_t)b1 << 16); + + // Split v up into a surrogate pair. + uchar16_t x = static_cast(v); + uchar16_t vh = UTF16_HI_SURROGATE | ((((v >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + + array[pos++] = vh; + array[pos++] = vl; + l += 4; + ++used; // Using two shorts this time, so increase used by 1. + } + else if ((c2[l] & 0xF0) == 0xE0) + { // 3 bytes UTF-8, 1 byte UTF-16. + // Check for a full string. + if ((l + 2) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 2; + break; + } + + // Validate. + bool valid = true; + u8 l2 = 0; + if (valid && (((c2[l+1] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+2] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (!valid) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += l2; + continue; + } + + // Decode. + uchar8_t b1 = ((c2[l] & 0xF) << 4) | ((c2[l+1] >> 2) & 0xF); + uchar8_t b2 = ((c2[l+1] & 0x3) << 6) | (c2[l+2] & 0x3F); + uchar16_t ch = b2 | ((uchar16_t)b1 << 8); + array[pos++] = ch; + l += 3; + } + else if ((c2[l] & 0xE0) == 0xC0) + { // 2 bytes UTF-8, 1 byte UTF-16. + // Check for a full string. + if ((l + 1) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 1; + break; + } + + // Validate. + if (((c2[l+1] >> 6) & 0x03) != 0x02) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + continue; + } + + // Decode. + uchar8_t b1 = (c2[l] >> 2) & 0x7; + uchar8_t b2 = ((c2[l] & 0x3) << 6) | (c2[l+1] & 0x3F); + uchar16_t ch = b2 | ((uchar16_t)b1 << 8); + array[pos++] = ch; + l += 2; + } + else + { // 1 byte UTF-8, 1 byte UTF-16. + // Validate. + if (c2[l] > 0x7F) + { // Values above 0xF4 are restricted and aren't used. By now, anything above 0x7F is invalid. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + } + else array[pos++] = static_cast(c2[l]); + ++l; + } + } + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + + //! Appends a UTF-16 string to this ustring16 + //! \param other The UTF-16 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar16_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Determine if the string is long enough for a BOM. + u32 len = 0; + const uchar16_t* p = other; + do + { + ++len; + } while (*p++ && len < unicode::BOM_ENCODE_UTF16_LEN); + + // Check for the BOM to determine the string's endianness. + unicode::EUTF_ENDIAN c_end = unicode::EUTFEE_NATIVE; + if (memcmp(other, unicode::BOM_ENCODE_UTF16_LE, unicode::BOM_ENCODE_UTF16_LEN) == 0) + c_end = unicode::EUTFEE_LITTLE; + else if (memcmp(other, unicode::BOM_ENCODE_UTF16_BE, unicode::BOM_ENCODE_UTF16_LEN) == 0) + c_end = unicode::EUTFEE_BIG; + + // If a BOM was found, don't include it in the string. + const uchar16_t* c2 = other; + if (c_end != unicode::EUTFEE_NATIVE) + { + c2 = other + unicode::BOM_UTF16_LEN; + length -= unicode::BOM_UTF16_LEN; + } + + // Calculate the size of the string to read in. + len = 0; + p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the size of the array, do it now. + if (used + len >= allocated) + reallocate(used + (len * 2)); + u32 start = used; + used += len; + + // Copy the string now. + unicode::EUTF_ENDIAN m_end = getEndianness(); + for (u32 l = start; l < start + len; ++l) + { + array[l] = (uchar16_t)c2[l]; + if (c_end != unicode::EUTFEE_NATIVE && c_end != m_end) + array[l] = unicode::swapEndian16(array[l]); + } + + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + return *this; + } + + + //! Appends a UTF-32 string to this ustring16 + //! \param other The UTF-32 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar32_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Check for the BOM to determine the string's endianness. + unicode::EUTF_ENDIAN c_end = unicode::EUTFEE_NATIVE; + if (memcmp(other, unicode::BOM_ENCODE_UTF32_LE, unicode::BOM_ENCODE_UTF32_LEN) == 0) + c_end = unicode::EUTFEE_LITTLE; + else if (memcmp(other, unicode::BOM_ENCODE_UTF32_BE, unicode::BOM_ENCODE_UTF32_LEN) == 0) + c_end = unicode::EUTFEE_BIG; + + // If a BOM was found, don't include it in the string. + const uchar32_t* c2 = other; + if (c_end != unicode::EUTFEE_NATIVE) + { + c2 = other + unicode::BOM_UTF32_LEN; + length -= unicode::BOM_UTF32_LEN; + } + + // Calculate the size of the string to read in. + u32 len = 0; + const uchar32_t* p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the size of the array, do it now. + // In case all of the UTF-32 string is split into surrogate pairs, do len * 2. + if (used + (len * 2) >= allocated) + reallocate(used + ((len * 2) * 2)); + u32 start = used; + + // Convert UTF-32 to UTF-16. + unicode::EUTF_ENDIAN m_end = getEndianness(); + u32 pos = start; + for (u32 l = 0; l 0xFFFF) + { + // Split ch up into a surrogate pair as it is over 16 bits long. + uchar16_t x = static_cast(ch); + uchar16_t vh = UTF16_HI_SURROGATE | ((((ch >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[pos++] = vh; + array[pos++] = vl; + ++used; // Using two shorts, so increased used again. + } + else if (ch >= 0xD800 && ch <= 0xDFFF) + { + // Between possible UTF-16 surrogates (invalid!) + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + } + else array[pos++] = static_cast(ch); + } + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + + //! Appends a ustring16 to this ustring16 + //! \param other The string to append to this one. + //! \return A reference to our current string. + ustring16& append(const ustring16& other) + { + const uchar16_t* oa = other.c_str(); + + u32 len = other.size_raw(); + + if (used + len >= allocated) + reallocate(used + len); + + for (u32 l=0; l& append(const ustring16& other, u32 length) + { + if (other.size() == 0) + return *this; + + if (other.size() < length) + { + append(other); + return *this; + } + + if (used + length * 2 >= allocated) + reallocate(used + length * 2); + + const_iterator iter(other, 0); + u32 l = length; + while (!iter.atEnd() && l) + { + uchar32_t c = *iter; + append(c); + ++iter; + --l; + } + + return *this; + } + + + //! Reserves some memory. + //! \param count The amount of characters to reserve. + void reserve(u32 count) + { + if (count < allocated) + return; + + reallocate(count); + } + + + //! Finds first occurrence of character. + //! \param c The character to search for. + //! \return Position where the character has been found, or -1 if not found. + s32 findFirst(uchar32_t c) const + { + const_iterator i(*this, 0); + + s32 pos = 0; + while (!i.atEnd()) + { + uchar32_t t = *i; + if (c == t) + return pos; + ++pos; + ++i; + } + + return -1; + } + + //! Finds first occurrence of a character of a list. + //! \param c A list of characters to find. For example if the method should find the first occurrence of 'a' or 'b', this parameter should be "ab". + //! \param count The amount of characters in the list. Usually, this should be strlen(c). + //! \return Position where one of the characters has been found, or -1 if not found. + s32 findFirstChar(const uchar32_t* const c, u32 count=1) const + { + if (!c || !count) + return -1; + + const_iterator i(*this, 0); + + s32 pos = 0; + while (!i.atEnd()) + { + uchar32_t t = *i; + for (u32 j=0; j& str, const u32 start = 0) const + { + u32 my_size = size(); + u32 their_size = str.size(); + + if (their_size == 0 || my_size - start < their_size) + return -1; + + const_iterator i(*this, start); + + s32 pos = start; + while (!i.atEnd()) + { + const_iterator i2(i); + const_iterator j(str, 0); + uchar32_t t1 = (uchar32_t)*i2; + uchar32_t t2 = (uchar32_t)*j; + while (t1 == t2) + { + ++i2; + ++j; + if (j.atEnd()) + return pos; + t1 = (uchar32_t)*i2; + t2 = (uchar32_t)*j; + } + ++i; + ++pos; + } + + return -1; + } + + + //! Finds another ustring16 in this ustring16. + //! \param str The string to find. + //! \param start The start position of the search. + //! \return Positions where the string has been found, or -1 if not found. + s32 find_raw(const ustring16& str, const u32 start = 0) const + { + const uchar16_t* data = str.c_str(); + if (data && *data) + { + u32 len = 0; + + while (data[len]) + ++len; + + if (len > used) + return -1; + + for (u32 i=start; i<=used-len; ++i) + { + u32 j=0; + + while(data[j] && array[i+j] == data[j]) + ++j; + + if (!data[j]) + return i; + } + } + + return -1; + } + + + //! Returns a substring. + //! \param begin: Start of substring. + //! \param length: Length of substring. + //! \return A reference to our current string. + ustring16 subString(u32 begin, s32 length) const + { + u32 len = size(); + // if start after ustring16 + // or no proper substring length + if ((length <= 0) || (begin>=len)) + return ustring16(""); + // clamp length to maximal value + if ((length+begin) > len) + length = len-begin; + + ustring16 o; + o.reserve((length+1) * 2); + + const_iterator i(*this, begin); + while (!i.atEnd() && length) + { + o.append(*i); + ++i; + --length; + } + + return o; + } + + + //! Appends a character to this ustring16. + //! \param c Character to append. + //! \return A reference to our current string. + ustring16& operator += (char c) + { + append((uchar32_t)c); + return *this; + } + + + //! Appends a character to this ustring16. + //! \param c Character to append. + //! \return A reference to our current string. + ustring16& operator += (uchar32_t c) + { + append(c); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (short c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (unsigned short c) + { + append(core::stringc(c)); + return *this; + } + + +#ifdef USTRING_CPP0X_NEWLITERALS + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (int c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (unsigned int c) + { + append(core::stringc(c)); + return *this; + } +#endif + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (long c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (unsigned long c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a number to this ustring16. + //! \param c Number to append. + //! \return A reference to our current string. + ustring16& operator += (double c) + { + append(core::stringc(c)); + return *this; + } + + + //! Appends a char ustring16 to this ustring16. + //! \param c Char ustring16 to append. + //! \return A reference to our current string. + ustring16& operator += (const uchar16_t* const c) + { + append(c); + return *this; + } + + + //! Appends a ustring16 to this ustring16. + //! \param other ustring16 to append. + //! \return A reference to our current string. + ustring16& operator += (const ustring16& other) + { + append(other); + return *this; + } + + + //! Replaces all characters of a given type with another one. + //! \param toReplace Character to replace. + //! \param replaceWith Character replacing the old one. + //! \return A reference to our current string. + ustring16& replace(uchar32_t toReplace, uchar32_t replaceWith) + { + iterator i(*this, 0); + while (!i.atEnd()) + { + typename ustring16::access a = *i; + if ((uchar32_t)a == toReplace) + a = replaceWith; + ++i; + } + return *this; + } + + + //! Replaces all instances of a string with another one. + //! \param toReplace The string to replace. + //! \param replaceWith The string replacing the old one. + //! \return A reference to our current string. + ustring16& replace(const ustring16& toReplace, const ustring16& replaceWith) + { + if (toReplace.size() == 0) + return *this; + + const uchar16_t* other = toReplace.c_str(); + const uchar16_t* replace = replaceWith.c_str(); + const u32 other_size = toReplace.size_raw(); + const u32 replace_size = replaceWith.size_raw(); + + // Determine the delta. The algorithm will change depending on the delta. + s32 delta = replace_size - other_size; + + // A character for character replace. The string will not shrink or grow. + if (delta == 0) + { + s32 pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + for (u32 i = 0; i < replace_size; ++i) + array[pos + i] = replace[i]; + ++pos; + } + return *this; + } + + // We are going to be removing some characters. The string will shrink. + if (delta < 0) + { + u32 i = 0; + for (u32 pos = 0; pos <= used; ++i, ++pos) + { + // Is this potentially a match? + if (array[pos] == *other) + { + // Check to see if we have a match. + u32 j; + for (j = 0; j < other_size; ++j) + { + if (array[pos + j] != other[j]) + break; + } + + // If we have a match, replace characters. + if (j == other_size) + { + for (j = 0; j < replace_size; ++j) + array[i + j] = replace[j]; + i += replace_size - 1; + pos += other_size - 1; + continue; + } + } + + // No match found, just copy characters. + array[i - 1] = array[pos]; + } + array[i] = 0; + used = i; + + return *this; + } + + // We are going to be adding characters, so the string size will increase. + // Count the number of times toReplace exists in the string so we can allocate the new size. + u32 find_count = 0; + s32 pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + ++find_count; + ++pos; + } + + // Re-allocate the string now, if needed. + u32 len = delta * find_count; + if (used + len >= allocated) + reallocate(used + len); + + // Start replacing. + pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + uchar16_t* start = array + pos + other_size - 1; + uchar16_t* ptr = array + used; + uchar16_t* end = array + used + delta; + + // Shift characters to make room for the string. + while (ptr != start) + { + *end = *ptr; + --ptr; + --end; + } + + // Add the new string now. + for (u32 i = 0; i < replace_size; ++i) + array[pos + i] = replace[i]; + + pos += replace_size; + used += delta; + } + + // Terminate the string and return ourself. + array[used] = 0; + return *this; + } + + + //! Removes characters from a ustring16.. + //! \param c The character to remove. + //! \return A reference to our current string. + ustring16& remove(uchar32_t c) + { + u32 pos = 0; + u32 found = 0; + u32 len = (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of c as a UTF-16 character. + for (u32 i=0; i<=used; ++i) + { + uchar32_t uc32 = 0; + if (!UTF16_IS_SURROGATE_HI(array[i])) + uc32 |= array[i]; + else if (i + 1 <= used) + { + // Convert the surrogate pair into a single UTF-32 character. + uc32 = unicode::toUTF32(array[i], array[i + 1]); + } + u32 len2 = (uc32 > 0xFFFF ? 2 : 1); + + if (uc32 == c) + { + found += len; + continue; + } + + array[pos++] = array[i]; + if (len2 == 2) + array[pos++] = array[++i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Removes a ustring16 from the ustring16. + //! \param toRemove The string to remove. + //! \return A reference to our current string. + ustring16& remove(const ustring16& toRemove) + { + u32 size = toRemove.size_raw(); + if (size == 0) return *this; + + const uchar16_t* tra = toRemove.c_str(); + u32 pos = 0; + u32 found = 0; + for (u32 i=0; i<=used; ++i) + { + u32 j = 0; + while (j < size) + { + if (array[i + j] != tra[j]) + break; + ++j; + } + if (j == size) + { + found += size; + i += size - 1; + continue; + } + + array[pos++] = array[i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Removes characters from the ustring16. + //! \param characters The characters to remove. + //! \return A reference to our current string. + ustring16& removeChars(const ustring16& characters) + { + if (characters.size_raw() == 0) + return *this; + + u32 pos = 0; + u32 found = 0; + const_iterator iter(characters); + for (u32 i=0; i<=used; ++i) + { + uchar32_t uc32 = 0; + if (!UTF16_IS_SURROGATE_HI(array[i])) + uc32 |= array[i]; + else if (i + 1 <= used) + { + // Convert the surrogate pair into a single UTF-32 character. + uc32 = unicode::toUTF32(array[i], array[i+1]); + } + u32 len2 = (uc32 > 0xFFFF ? 2 : 1); + + bool cont = false; + iter.toStart(); + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (uc32 == c) + { + found += (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of c as a UTF-16 character. + ++i; + cont = true; + break; + } + ++iter; + } + if (cont) continue; + + array[pos++] = array[i]; + if (len2 == 2) + array[pos++] = array[++i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Trims the ustring16. + //! Removes the specified characters (by default, Latin-1 whitespace) from the begining and the end of the ustring16. + //! \param whitespace The characters that are to be considered as whitespace. + //! \return A reference to our current string. + ustring16& trim(const ustring16& whitespace = " \t\n\r") + { + core::array utf32white = whitespace.toUTF32(); + + // find start and end of the substring without the specified characters + const s32 begin = findFirstCharNotInList(utf32white.const_pointer(), whitespace.used + 1); + if (begin == -1) + return (*this=""); + + const s32 end = findLastCharNotInList(utf32white.const_pointer(), whitespace.used + 1); + + return (*this = subString(begin, (end +1) - begin)); + } + + + //! Erases a character from the ustring16. + //! May be slow, because all elements following after the erased element have to be copied. + //! \param index Index of element to be erased. + //! \return A reference to our current string. + ustring16& erase(u32 index) + { + _IRR_DEBUG_BREAK_IF(index>used) // access violation + + iterator i(*this, index); + + uchar32_t t = *i; + u32 len = (t > 0xFFFF ? 2 : 1); + + for (u32 j = static_cast(i.getPos()) + len; j <= used; ++j) + array[j - len] = array[j]; + + used -= len; + array[used] = 0; + + return *this; + } + + + //! Validate the existing ustring16, checking for valid surrogate pairs and checking for proper termination. + //! \return A reference to our current string. + ustring16& validate() + { + // Validate all unicode characters. + for (u32 i=0; i= allocated) || UTF16_IS_SURROGATE_LO(array[i])) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + else if (UTF16_IS_SURROGATE_HI(array[i]) && !UTF16_IS_SURROGATE_LO(array[i+1])) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + ++i; + } + if (array[i] >= 0xFDD0 && array[i] <= 0xFDEF) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + } + + // terminate + used = 0; + if (allocated > 0) + { + used = allocated - 1; + array[used] = 0; + } + return *this; + } + + + //! Gets the last char of the ustring16, or 0. + //! \return The last char of the ustring16, or 0. + uchar32_t lastChar() const + { + if (used < 1) + return 0; + + if (UTF16_IS_SURROGATE_LO(array[used-1])) + { + // Make sure we have a paired surrogate. + if (used < 2) + return 0; + + // Check for an invalid surrogate. + if (!UTF16_IS_SURROGATE_HI(array[used-2])) + return 0; + + // Convert the surrogate pair into a single UTF-32 character. + return unicode::toUTF32(array[used-2], array[used-1]); + } + else + { + return array[used-1]; + } + } + + + //! Split the ustring16 into parts. + /** This method will split a ustring16 at certain delimiter characters + into the container passed in as reference. The type of the container + has to be given as template parameter. It must provide a push_back and + a size method. + \param ret The result container + \param c C-style ustring16 of delimiter characters + \param count Number of delimiter characters + \param ignoreEmptyTokens Flag to avoid empty substrings in the result + container. If two delimiters occur without a character in between, an + empty substring would be placed in the result. If this flag is set, + only non-empty strings are stored. + \param keepSeparators Flag which allows to add the separator to the + result ustring16. If this flag is true, the concatenation of the + substrings results in the original ustring16. Otherwise, only the + characters between the delimiters are returned. + \return The number of resulting substrings + */ + template + u32 split(container& ret, const uchar32_t* const c, u32 count=1, bool ignoreEmptyTokens=true, bool keepSeparators=false) const + { + if (!c) + return 0; + + const_iterator i(*this); + const u32 oldSize=ret.size(); + u32 pos = 0; + u32 lastpos = 0; + u32 lastpospos = 0; + bool lastWasSeparator = false; + while (!i.atEnd()) + { + uchar32_t ch = *i; + bool foundSeparator = false; + for (u32 j=0; j(&array[lastpospos], pos - lastpos)); + foundSeparator = true; + lastpos = (keepSeparators ? pos : pos + 1); + lastpospos = (keepSeparators ? i.getPos() : i.getPos() + 1); + break; + } + } + lastWasSeparator = foundSeparator; + ++pos; + ++i; + } + u32 s = size() + 1; + if (s > lastpos) + ret.push_back(ustring16(&array[lastpospos], s - lastpos)); + return ret.size()-oldSize; + } + + + //! Split the ustring16 into parts. + /** This method will split a ustring16 at certain delimiter characters + into the container passed in as reference. The type of the container + has to be given as template parameter. It must provide a push_back and + a size method. + \param ret The result container + \param c A unicode string of delimiter characters + \param ignoreEmptyTokens Flag to avoid empty substrings in the result + container. If two delimiters occur without a character in between, an + empty substring would be placed in the result. If this flag is set, + only non-empty strings are stored. + \param keepSeparators Flag which allows to add the separator to the + result ustring16. If this flag is true, the concatenation of the + substrings results in the original ustring16. Otherwise, only the + characters between the delimiters are returned. + \return The number of resulting substrings + */ + template + u32 split(container& ret, const ustring16& c, bool ignoreEmptyTokens=true, bool keepSeparators=false) const + { + core::array v = c.toUTF32(); + return split(ret, v.pointer(), v.size(), ignoreEmptyTokens, keepSeparators); + } + + + //! Gets the size of the allocated memory buffer for the string. + //! \return The size of the allocated memory buffer. + u32 capacity() const + { + return allocated; + } + + + //! Returns the raw number of UTF-16 code points in the string which includes the individual surrogates. + //! \return The raw number of UTF-16 code points, excluding the trialing NUL. + u32 size_raw() const + { + return used; + } + + + //! Inserts a character into the string. + //! \param c The character to insert. + //! \param pos The position to insert the character. + //! \return A reference to our current string. + ustring16& insert(uchar32_t c, u32 pos) + { + u8 len = (c > 0xFFFF ? 2 : 1); + + if (used + len >= allocated) + reallocate(used + len); + + used += len; + + iterator iter(*this, pos); + for (u32 i = used - 2; i > iter.getPos(); --i) + array[i] = array[i - len]; + + if (c > 0xFFFF) + { + // c will be multibyte, so split it up into a surrogate pair. + uchar16_t x = static_cast(c); + uchar16_t vh = UTF16_HI_SURROGATE | ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[iter.getPos()] = vh; + array[iter.getPos()+1] = vl; + } + else + { + array[iter.getPos()] = static_cast(c); + } + array[used] = 0; + return *this; + } + + + //! Inserts a string into the string. + //! \param c The string to insert. + //! \param pos The position to insert the string. + //! \return A reference to our current string. + ustring16& insert(const ustring16& c, u32 pos) + { + u32 len = c.size_raw(); + if (len == 0) return *this; + + if (used + len >= allocated) + reallocate(used + len); + + used += len; + + iterator iter(*this, pos); + for (u32 i = used - 2; i > iter.getPos() + len; --i) + array[i] = array[i - len]; + + const uchar16_t* s = c.c_str(); + for (u32 i = 0; i < len; ++i) + { + array[pos++] = *s; + ++s; + } + + array[used] = 0; + return *this; + } + + + //! Inserts a character into the string. + //! \param c The character to insert. + //! \param pos The position to insert the character. + //! \return A reference to our current string. + ustring16& insert_raw(uchar16_t c, u32 pos) + { + if (used + 1 >= allocated) + reallocate(used + 1); + + ++used; + + for (u32 i = used - 1; i > pos; --i) + array[i] = array[i - 1]; + + array[pos] = c; + array[used] = 0; + return *this; + } + + + //! Removes a character from string. + //! \param pos Position of the character to remove. + //! \return A reference to our current string. + ustring16& erase_raw(u32 pos) + { + for (u32 i=pos; i<=used; ++i) + { + array[i] = array[i + 1]; + } + --used; + array[used] = 0; + return *this; + } + + + //! Replaces a character in the string. + //! \param c The new character. + //! \param pos The position of the character to replace. + //! \return A reference to our current string. + ustring16& replace_raw(uchar16_t c, u32 pos) + { + array[pos] = c; + return *this; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + iterator begin() + { + iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + const_iterator begin() const + { + const_iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + const_iterator cbegin() const + { + const_iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + iterator end() + { + iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + const_iterator end() const + { + const_iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + const_iterator cend() const + { + const_iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Converts the string to a UTF-8 encoded string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-8 encoded string. + core::string toUTF8_s(const bool addBOM = false) const + { + core::string ret; + ret.reserve(used * 4 + (addBOM ? unicode::BOM_UTF8_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the byte order mark if the user wants it. + if (addBOM) + { + ret.append(unicode::BOM_ENCODE_UTF8[0]); + ret.append(unicode::BOM_ENCODE_UTF8[1]); + ret.append(unicode::BOM_ENCODE_UTF8[2]); + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (c > 0xFFFF) + { // 4 bytes + uchar8_t b1 = (0x1E << 3) | ((c >> 18) & 0x7); + uchar8_t b2 = (0x2 << 6) | ((c >> 12) & 0x3F); + uchar8_t b3 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b4 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + ret.append(b3); + ret.append(b4); + } + else if (c > 0x7FF) + { // 3 bytes + uchar8_t b1 = (0xE << 4) | ((c >> 12) & 0xF); + uchar8_t b2 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b3 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + ret.append(b3); + } + else if (c > 0x7F) + { // 2 bytes + uchar8_t b1 = (0x6 << 5) | ((c >> 6) & 0x1F); + uchar8_t b2 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + } + else + { // 1 byte + ret.append(static_cast(c)); + } + ++iter; + } + return ret; + } + + + //! Converts the string to a UTF-8 encoded string array. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-8 encoded string. + core::array toUTF8(const bool addBOM = false) const + { + core::array ret(used * 4 + (addBOM ? unicode::BOM_UTF8_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the byte order mark if the user wants it. + if (addBOM) + { + ret.push_back(unicode::BOM_ENCODE_UTF8[0]); + ret.push_back(unicode::BOM_ENCODE_UTF8[1]); + ret.push_back(unicode::BOM_ENCODE_UTF8[2]); + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (c > 0xFFFF) + { // 4 bytes + uchar8_t b1 = (0x1E << 3) | ((c >> 18) & 0x7); + uchar8_t b2 = (0x2 << 6) | ((c >> 12) & 0x3F); + uchar8_t b3 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b4 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + ret.push_back(b3); + ret.push_back(b4); + } + else if (c > 0x7FF) + { // 3 bytes + uchar8_t b1 = (0xE << 4) | ((c >> 12) & 0xF); + uchar8_t b2 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b3 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + ret.push_back(b3); + } + else if (c > 0x7F) + { // 2 bytes + uchar8_t b1 = (0x6 << 5) | ((c >> 6) & 0x1F); + uchar8_t b2 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + } + else + { // 1 byte + ret.push_back(static_cast(c)); + } + ++iter; + } + ret.push_back(0); + return ret; + } + + +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + //! Converts the string to a UTF-16 encoded string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-16 encoded string. + core::string toUTF16_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::string ret; + ret.reserve(used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret[0] = unicode::BOM; + else if (endian == unicode::EUTFEE_LITTLE) + { + uchar8_t* ptr8 = reinterpret_cast(ret.c_str()); + *ptr8++ = unicode::BOM_ENCODE_UTF16_LE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_LE[1]; + } + else + { + uchar8_t* ptr8 = reinterpret_cast(ret.c_str()); + *ptr8++ = unicode::BOM_ENCODE_UTF16_BE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_BE[1]; + } + } + + ret.append(array); + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + { + char16_t* ptr = ret.c_str(); + for (u32 i = 0; i < ret.size(); ++i) + *ptr++ = unicode::swapEndian16(*ptr); + } + return ret; + } +#endif + + + //! Converts the string to a UTF-16 encoded string array. + //! Unfortunately, no toUTF16_s() version exists due to limitations with Irrlicht's string class. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-16 encoded string. + core::array toUTF16(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::array ret(used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); + uchar16_t* ptr = ret.pointer(); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + *ptr = unicode::BOM; + else if (endian == unicode::EUTFEE_LITTLE) + { + uchar8_t* ptr8 = reinterpret_cast(ptr); + *ptr8++ = unicode::BOM_ENCODE_UTF16_LE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_LE[1]; + } + else + { + uchar8_t* ptr8 = reinterpret_cast(ptr); + *ptr8++ = unicode::BOM_ENCODE_UTF16_BE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_BE[1]; + } + ++ptr; + } + + memcpy((void*)ptr, (void*)array, used * sizeof(uchar16_t)); + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + { + for (u32 i = 0; i <= used; ++i) + *ptr++ = unicode::swapEndian16(*ptr); + } + ret.set_used(used + (addBOM ? unicode::BOM_UTF16_LEN : 0)); + ret.push_back(0); + return ret; + } + + +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + //! Converts the string to a UTF-32 encoded string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-32 encoded string. + core::string toUTF32_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::string ret; + ret.reserve(size() + 1 + (addBOM ? unicode::BOM_UTF32_LEN : 0)); + const_iterator iter(*this, 0); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret.append(unicode::BOM); + else + { + union + { + uchar32_t full; + u8 chunk[4]; + } t; + + if (endian == unicode::EUTFEE_LITTLE) + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_LE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_LE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_LE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_LE[3]; + } + else + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_BE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_BE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_BE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_BE[3]; + } + ret.append(t.full); + } + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + c = unicode::swapEndian32(c); + ret.append(c); + ++iter; + } + return ret; + } +#endif + + + //! Converts the string to a UTF-32 encoded string array. + //! Unfortunately, no toUTF32_s() version exists due to limitations with Irrlicht's string class. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-32 encoded string. + core::array toUTF32(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::array ret(size() + (addBOM ? unicode::BOM_UTF32_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret.push_back(unicode::BOM); + else + { + union + { + uchar32_t full; + u8 chunk[4]; + } t; + + if (endian == unicode::EUTFEE_LITTLE) + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_LE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_LE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_LE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_LE[3]; + } + else + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_BE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_BE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_BE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_BE[3]; + } + ret.push_back(t.full); + } + } + ret.push_back(0); + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + c = unicode::swapEndian32(c); + ret.push_back(c); + ++iter; + } + return ret; + } + + + //! Converts the string to a wchar_t encoded string. + /** The size of a wchar_t changes depending on the platform. This function will store a + correct UTF-8, -16, or -32 encoded string depending on the size of a wchar_t. **/ + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the wchar_t encoded string. + core::string toWCHAR_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + if (sizeof(wchar_t) == 4) + { + core::array a(toUTF32(endian, addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + else if (sizeof(wchar_t) == 2) + { + if (endian == unicode::EUTFEE_NATIVE && addBOM == false) + { + core::stringw ret(array); + return ret; + } + else + { + core::array a(toUTF16(endian, addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + } + else if (sizeof(wchar_t) == 1) + { + core::array a(toUTF8(addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + + // Shouldn't happen. + return core::stringw(); + } + + + //! Converts the string to a wchar_t encoded string array. + /** The size of a wchar_t changes depending on the platform. This function will store a + correct UTF-8, -16, or -32 encoded string depending on the size of a wchar_t. **/ + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the wchar_t encoded string. + core::array toWCHAR(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + if (sizeof(wchar_t) == 4) + { + core::array a(toUTF32(endian, addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar32_t)); + return ret; + } + if (sizeof(wchar_t) == 2) + { + if (endian == unicode::EUTFEE_NATIVE && addBOM == false) + { + core::array ret(used); + ret.set_used(used); + memcpy((void*)ret.pointer(), (void*)array, used * sizeof(uchar16_t)); + return ret; + } + else + { + core::array a(toUTF16(endian, addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar16_t)); + return ret; + } + } + if (sizeof(wchar_t) == 1) + { + core::array a(toUTF8(addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar8_t)); + return ret; + } + + // Shouldn't happen. + return core::array(); + } + + //! Converts the string to a properly encoded io::path string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An io::path string containing the properly encoded string. + io::path toPATH_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { +#if defined(_IRR_WCHAR_FILESYSTEM) + return toWCHAR_s(endian, addBOM); +#else + return toUTF8_s(addBOM); +#endif + } + + //! Loads an unknown stream of data. + //! Will attempt to determine if the stream is unicode data. Useful for loading from files. + //! \param data The data stream to load from. + //! \param data_size The length of the data string. + //! \return A reference to our current string. + ustring16& loadDataStream(const char* data, size_t data_size) + { + // Clear our string. + *this = ""; + if (!data) + return *this; + + unicode::EUTF_ENCODE e = unicode::determineUnicodeBOM(data); + switch (e) + { + default: + case unicode::EUTFE_UTF8: + append((uchar8_t*)data, data_size); + break; + + case unicode::EUTFE_UTF16: + case unicode::EUTFE_UTF16_BE: + case unicode::EUTFE_UTF16_LE: + append((uchar16_t*)data, data_size / 2); + break; + + case unicode::EUTFE_UTF32: + case unicode::EUTFE_UTF32_BE: + case unicode::EUTFE_UTF32_LE: + append((uchar32_t*)data, data_size / 4); + break; + } + + return *this; + } + + //! Gets the encoding of the Unicode string this class contains. + //! \return An enum describing the current encoding of this string. + const unicode::EUTF_ENCODE getEncoding() const + { + return encoding; + } + + //! Gets the endianness of the Unicode string this class contains. + //! \return An enum describing the endianness of this string. + const unicode::EUTF_ENDIAN getEndianness() const + { + if (encoding == unicode::EUTFE_UTF16_LE || + encoding == unicode::EUTFE_UTF32_LE) + return unicode::EUTFEE_LITTLE; + else return unicode::EUTFEE_BIG; + } + +private: + + //! Reallocate the string, making it bigger or smaller. + //! \param new_size The new size of the string. + void reallocate(u32 new_size) + { + uchar16_t* old_array = array; + + array = allocator.allocate(new_size + 1); //new u16[new_size]; + allocated = new_size + 1; + if (old_array == 0) return; + + u32 amount = used < new_size ? used : new_size; + for (u32 i=0; i<=amount; ++i) + array[i] = old_array[i]; + + if (allocated <= used) + used = allocated - 1; + + array[used] = 0; + + allocator.deallocate(old_array); // delete [] old_array; + } + + //--- member variables + + uchar16_t* array; + unicode::EUTF_ENCODE encoding; + u32 allocated; + u32 used; + TAlloc allocator; + //irrAllocator allocator; +}; + +typedef ustring16 > ustring; + + +//! Appends two ustring16s. +template +inline ustring16 operator+(const ustring16& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16 operator+(const ustring16& left, const B* const right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16 operator+(const B* const left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16 operator+(const ustring16& left, const string& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16 operator+(const string& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16 operator+(const ustring16& left, const std::basic_string& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16 operator+(const std::basic_string& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a char. +template +inline ustring16 operator+(const ustring16& left, const char right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a char. +template +inline ustring16 operator+(const char left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +#ifdef USTRING_CPP0X_NEWLITERALS +//! Appends a ustring16 and a uchar32_t. +template +inline ustring16 operator+(const ustring16& left, const uchar32_t right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a uchar32_t. +template +inline ustring16 operator+(const uchar32_t left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} +#endif + + +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(const ustring16& left, const short right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(const short left, const ustring16& right) +{ + ustring16 ret(core::stringc(left)); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an unsigned short. +template +inline ustring16 operator+(const ustring16& left, const unsigned short right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and an unsigned short. +template +inline ustring16 operator+(const unsigned short left, const ustring16& right) +{ + ustring16 ret(core::stringc(left)); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an int. +template +inline ustring16 operator+(const ustring16& left, const int right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and an int. +template +inline ustring16 operator+(const int left, const ustring16& right) +{ + ustring16 ret(core::stringc(left)); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an unsigned int. +template +inline ustring16 operator+(const ustring16& left, const unsigned int right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and an unsigned int. +template +inline ustring16 operator+(const unsigned int left, const ustring16& right) +{ + ustring16 ret(core::stringc(left)); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a long. +template +inline ustring16 operator+(const ustring16& left, const long right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and a long. +template +inline ustring16 operator+(const long left, const ustring16& right) +{ + ustring16 ret(core::stringc(left)); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an unsigned long. +template +inline ustring16 operator+(const ustring16& left, const unsigned long right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and an unsigned long. +template +inline ustring16 operator+(const unsigned long left, const ustring16& right) +{ + ustring16 ret(core::stringc(left)); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a float. +template +inline ustring16 operator+(const ustring16& left, const float right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and a float. +template +inline ustring16 operator+(const float left, const ustring16& right) +{ + ustring16 ret(core::stringc(left)); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a double. +template +inline ustring16 operator+(const ustring16& left, const double right) +{ + ustring16 ret(left); + ret += core::stringc(right); + return ret; +} + + +//! Appends a ustring16 and a double. +template +inline ustring16 operator+(const double left, const ustring16& right) +{ + ustring16 ret(core::stringc(left)); + ret += right; + return ret; +} + + +#ifdef USTRING_CPP0X +//! Appends two ustring16s. +template +inline ustring16&& operator+(const ustring16& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends two ustring16s. +template +inline ustring16&& operator+(ustring16&& left, const ustring16& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends two ustring16s. +template +inline ustring16&& operator+(ustring16&& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&&, &&)" << std::endl; + if ((right.size_raw() <= left.capacity() - left.size_raw()) || + (right.capacity() - right.size_raw() < left.size_raw())) + { + left.append(right); + return std::move(left); + } + else + { + right.insert(left, 0); + return std::move(right); + } +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16&& operator+(ustring16&& left, const B* const right) +{ + //std::cout << "MOVE operator+(&&, B*)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16&& operator+(const B* const left, ustring16&& right) +{ + //std::cout << "MOVE operator+(B*, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16&& operator+(const string& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16&& operator+(ustring16&& left, const string& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16&& operator+(const std::basic_string& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(core::ustring16(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16&& operator+(ustring16&& left, const std::basic_string& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a char. +template +inline ustring16 operator+(ustring16&& left, const char right) +{ + left.append((uchar32_t)right); + return std::move(left); +} + + +//! Appends a ustring16 and a char. +template +inline ustring16 operator+(const char left, ustring16&& right) +{ + right.insert((uchar32_t)left, 0); + return std::move(right); +} + + +#ifdef USTRING_CPP0X_NEWLITERALS +//! Appends a ustring16 and a uchar32_t. +template +inline ustring16 operator+(ustring16&& left, const uchar32_t right) +{ + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a uchar32_t. +template +inline ustring16 operator+(const uchar32_t left, ustring16&& right) +{ + right.insert(left, 0); + return std::move(right); +} +#endif + + +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(ustring16&& left, const short right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and a short. +template +inline ustring16 operator+(const short left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and an unsigned short. +template +inline ustring16 operator+(ustring16&& left, const unsigned short right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and an unsigned short. +template +inline ustring16 operator+(const unsigned short left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and an int. +template +inline ustring16 operator+(ustring16&& left, const int right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and an int. +template +inline ustring16 operator+(const int left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and an unsigned int. +template +inline ustring16 operator+(ustring16&& left, const unsigned int right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and an unsigned int. +template +inline ustring16 operator+(const unsigned int left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a long. +template +inline ustring16 operator+(ustring16&& left, const long right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and a long. +template +inline ustring16 operator+(const long left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and an unsigned long. +template +inline ustring16 operator+(ustring16&& left, const unsigned long right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and an unsigned long. +template +inline ustring16 operator+(const unsigned long left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a float. +template +inline ustring16 operator+(ustring16&& left, const float right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and a float. +template +inline ustring16 operator+(const float left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a double. +template +inline ustring16 operator+(ustring16&& left, const double right) +{ + left.append(core::stringc(right)); + return std::move(left); +} + + +//! Appends a ustring16 and a double. +template +inline ustring16 operator+(const double left, ustring16&& right) +{ + right.insert(core::stringc(left), 0); + return std::move(right); +} +#endif + + +#ifndef USTRING_NO_STL +//! Writes a ustring16 to an ostream. +template +inline std::ostream& operator<<(std::ostream& out, const ustring16& in) +{ + out << in.toUTF8_s().c_str(); + return out; +} + +//! Writes a ustring16 to a wostream. +template +inline std::wostream& operator<<(std::wostream& out, const ustring16& in) +{ + out << in.toWCHAR_s().c_str(); + return out; +} +#endif + + +#ifndef USTRING_NO_STL + +namespace unicode +{ + +//! Hashing algorithm for hashing a ustring. Used for things like unordered_maps. +//! Algorithm taken from std::hash. +class hash : public std::unary_function +{ + public: + size_t operator()(const core::ustring& s) const + { + size_t ret = 2166136261U; + size_t index = 0; + size_t stride = 1 + s.size_raw() / 10; + + core::ustring::const_iterator i = s.begin(); + while (i != s.end()) + { + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + ret = 16777619U * ret ^ (size_t)s[(u32)index]; + index += stride; + i += stride; + } + return (ret); + } +}; + +} // end namespace unicode + +#endif + +} // end namespace core +} // end namespace irr + +#endif diff --git a/src/cguittfont/xCGUITTFont.cpp b/src/cguittfont/xCGUITTFont.cpp new file mode 100644 index 000000000..c51922e4c --- /dev/null +++ b/src/cguittfont/xCGUITTFont.cpp @@ -0,0 +1,5 @@ +// A wrapper source file to avoid hack with gcc and modifying +// the CGUITTFont files. + +#include "xCGUITTFont.h" +#include "CGUITTFont.cpp" diff --git a/src/cguittfont/xCGUITTFont.h b/src/cguittfont/xCGUITTFont.h new file mode 100644 index 000000000..c3efe7f6f --- /dev/null +++ b/src/cguittfont/xCGUITTFont.h @@ -0,0 +1,7 @@ +// A wrapper header to avoid hack with gcc and modifying +// the CGUITTFont files. + +#include +#include +#include "irrUString.h" +#include "CGUITTFont.h" diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index 4853d854f..51827cce6 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -9,6 +9,7 @@ #define CMAKE_USE_GETTEXT @USE_GETTEXT@ #define CMAKE_USE_CURL @USE_CURL@ #define CMAKE_USE_SOUND @USE_SOUND@ +#define CMAKE_USE_FREETYPE @USE_FREETYPE@ #define CMAKE_STATIC_SHAREDIR "@SHAREDIR@" #ifdef NDEBUG diff --git a/src/config.h b/src/config.h index f37ec0fed..37dc6e0ef 100644 --- a/src/config.h +++ b/src/config.h @@ -12,6 +12,7 @@ #define USE_GETTEXT 0 #define USE_SOUND 0 #define USE_CURL 0 +#define USE_FREETYPE 0 #define STATIC_SHAREDIR "" #define BUILD_INFO "non-cmake" @@ -29,6 +30,8 @@ #define USE_SOUND CMAKE_USE_SOUND #undef USE_CURL #define USE_CURL CMAKE_USE_CURL + #undef USE_FREETYPE + #define USE_FREETYPE CMAKE_USE_FREETYPE #undef STATIC_SHAREDIR #define STATIC_SHAREDIR CMAKE_STATIC_SHAREDIR #undef BUILD_INFO diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 1c673f76c..aebdc0aa8 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "settings.h" +#include "filesys.h" void set_default_settings(Settings *settings) { @@ -132,6 +133,11 @@ void set_default_settings(Settings *settings) settings->setDefault("serverlist_url", "servers.minetest.ru/server.list"); settings->setDefault("serverlist_file", "favoriteservers.txt"); + settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "liberationsans.ttf")); + settings->setDefault("font_size", "13"); + settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "liberationmono.ttf")); + settings->setDefault("mono_font_size", "13"); + // Server stuff // "map-dir" doesn't exist by default. settings->setDefault("default_game", "minetest"); diff --git a/src/gettext.h b/src/gettext.h index 54470cb0d..452787de4 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -12,6 +12,11 @@ #define gettext_noop(String) String #define N_(String) gettext_noop (String) +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#endif + inline void init_gettext(const char *path) { #if USE_GETTEXT // don't do this if MSVC compiler is used, it gives an assertion fail @@ -20,14 +25,44 @@ inline void init_gettext(const char *path) { #endif bindtextdomain(PROJECT_NAME, path); textdomain(PROJECT_NAME); +#if defined(_WIN32) + // As linux is successfully switched to UTF-8 completely at about year 2005 + // Windows still uses obsolete codepage based locales because you + // cannot recompile closed-source applications + + // Set character encoding for Win32 + char *tdomain = textdomain( (char *) NULL ); + if( tdomain == NULL ) + { + fprintf( stderr, "warning: domainname parameter is the null pointer, default domain is not set\n" ); + tdomain = (char *) "messages"; + } + /*char *codeset = */bind_textdomain_codeset( tdomain, "UTF-8" ); + //fprintf( stdout, "%s: debug: domainname = %s; codeset = %s\n", argv[0], tdomain, codeset ); +#endif // defined(_WIN32) #endif } inline wchar_t* chartowchar_t(const char *str) { + wchar_t* nstr = 0; +#if defined(_WIN32) + int nResult = MultiByteToWideChar( CP_UTF8, 0, (LPCSTR) str, -1, 0, 0 ); + if( nResult == 0 ) + { + fprintf( stderr, "error: MultiByteToWideChar returned null\n" ); + } + else + { + nstr = new wchar_t[nResult]; + MultiByteToWideChar( CP_UTF8, 0, (LPCSTR) str, -1, (WCHAR *) nstr, nResult ); + } +#else size_t l = strlen(str)+1; - wchar_t* nstr = new wchar_t[l]; + nstr = new wchar_t[l]; mbstowcs(nstr, str, l); +#endif + return nstr; } @@ -38,12 +73,12 @@ inline wchar_t* wgettext(const char *str) inline void changeCtype(const char *l) { - char *ret = NULL; + /*char *ret = NULL; ret = setlocale(LC_CTYPE, l); if(ret == NULL) infostream<<"locale could not be set"<get("mono_font_path"); + u16 font_size = g_settings->getU16("mono_font_size"); + m_font = gui::CGUITTFont::createTTFont(env, font_name.c_str(), font_size); + #else std::string font_name = "fontdejavusansmono.png"; m_font = env->getFont(getTexturePath(font_name).c_str()); + #endif if (m_font == NULL) { dstream << "Unable to load font: " << font_name << std::endl; diff --git a/src/guiMainMenu.cpp b/src/guiMainMenu.cpp index 9291bb4ec..343369643 100644 --- a/src/guiMainMenu.cpp +++ b/src/guiMainMenu.cpp @@ -260,7 +260,7 @@ void GUIMainMenu::regenerateGui(v2u32 screensize) //const wchar_t *text = L"H\nY\nB\nR\nI\nD"; const wchar_t *text = L"T\nA\nP\nE\n\nA\nN\nD\n\nG\nL\nU\nE"; gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, false, this, -1); + Environment->addStaticText(text, rect, false, true, this, -1); t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); } u32 bs = 5; @@ -359,7 +359,7 @@ void GUIMainMenu::regenerateGui(v2u32 screensize) rect += m_topleft_client + v2s32(15, 0); const wchar_t *text = L"C\nL\nI\nE\nN\nT"; gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, false, this, -1); + Environment->addStaticText(text, rect, false, true, this, -1); t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); } // Nickname + password @@ -469,7 +469,7 @@ void GUIMainMenu::regenerateGui(v2u32 screensize) rect += m_topleft_client + v2s32(15, 0); const wchar_t *text = L"C\nL\nI\nE\nN\nT"; gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, false, this, -1); + Environment->addStaticText(text, rect, false, true, this, -1); t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); } // Nickname + password @@ -546,7 +546,7 @@ void GUIMainMenu::regenerateGui(v2u32 screensize) rect += m_topleft_server + v2s32(15, 0); const wchar_t *text = L"S\nE\nR\nV\nE\nR"; gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, false, this, -1); + Environment->addStaticText(text, rect, false, true, this, -1); t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); } // Server parameters @@ -598,7 +598,7 @@ void GUIMainMenu::regenerateGui(v2u32 screensize) rect += m_topleft_client + v2s32(15, 0); const wchar_t *text = L"S\nE\nT\nT\nI\nN\nG\nS"; gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, false, this, -1); + Environment->addStaticText(text, rect, false, true, this, -1); t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); } s32 option_x = 70; @@ -701,7 +701,7 @@ void GUIMainMenu::regenerateGui(v2u32 screensize) rect += m_topleft_client + v2s32(15, 0); const wchar_t *text = L"C\nR\nE\nD\nI\nT\nS"; gui::IGUIStaticText *t = - Environment->addStaticText(text, rect, false, false, this, -1); + Environment->addStaticText(text, rect, false, true, this, -1); t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER); } { diff --git a/src/guiTextInputMenu.cpp b/src/guiTextInputMenu.cpp index 094532a62..857c26a45 100644 --- a/src/guiTextInputMenu.cpp +++ b/src/guiTextInputMenu.cpp @@ -29,6 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettext.h" +#if USE_FREETYPE +#include "intlGUIEditBox.h" +#endif + GUITextInputMenu::GUITextInputMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, @@ -105,8 +109,12 @@ void GUITextInputMenu::regenerateGui(v2u32 screensize) { core::rect rect(0, 0, 300, 30); rect = rect + v2s32(size.X/2-300/2, size.Y/2-30/2-25); - gui::IGUIElement *e = - Environment->addEditBox(text.c_str(), rect, true, this, 256); + #if USE_FREETYPE + gui::IGUIElement *e = (gui::IGUIElement *) new gui::intlGUIEditBox(text.c_str(), true, Environment, this, 256, rect); + e->drop(); + #else + gui::IGUIElement *e = Environment->addEditBox(text.c_str(), rect, true, this, 256); + #endif Environment->setFocus(e); irr::SEvent evt; diff --git a/src/intlGUIEditBox.cpp b/src/intlGUIEditBox.cpp new file mode 100644 index 000000000..4add61e20 --- /dev/null +++ b/src/intlGUIEditBox.cpp @@ -0,0 +1,1508 @@ +// 11.11.2011 11:11 ValkaTR +// +// This is a copy of intlGUIEditBox from the irrlicht, but with a +// fix in the OnEvent function, which doesn't allowed input of +// other keyboard layouts than latin-1 +// +// Characters like: ä ö ü õ ы й ю я ъ № € ° ... +// +// This fix is only needed for linux, because of a bug +// in the CIrrDeviceLinux.cpp:1014-1015 of the irrlicht +// +// Also locale in the programm should not be changed to +// a "C", "POSIX" or whatever, it should be set to "", +// or XLookupString will return nothing for the international +// characters. +// +// From the "man setlocale": +// +// On startup of the main program, the portable "C" locale +// is selected as default. A program may be made +// portable to all locales by calling: +// +// setlocale(LC_ALL, ""); +// +// after program initialization.... +// + +// Copyright (C) 2002-2010 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#include "intlGUIEditBox.h" + +#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUISkin.h" +#include "IGUIEnvironment.h" +#include "IGUIFont.h" +#include "IVideoDriver.h" +//#include "rect.h" +//#include "irrlicht/os.cpp" +#include "porting.h" +//#include "Keycodes.h" + +/* + todo: + optional scrollbars + ctrl+left/right to select word + double click/ctrl click: word select + drag to select whole words, triple click to select line + optional? dragging selected text + numerical +*/ + +namespace irr +{ +namespace gui +{ + +//! constructor +intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, + IGUIEnvironment* environment, IGUIElement* parent, s32 id, + const core::rect& rectangle) + : IGUIEditBox(environment, parent, id, rectangle), MouseMarking(false), + Border(border), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0), + OverrideColor(video::SColor(101,255,255,255)), OverrideFont(0), LastBreakFont(0), + Operator(0), BlinkStartTime(0), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0), + WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false), + PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), + CurrentTextRect(0,0,1,1), FrameRect(rectangle) +{ + #ifdef _DEBUG + setDebugName("intlintlGUIEditBox"); + #endif + + Text = text; + + if (Environment) + Operator = Environment->getOSOperator(); + + if (Operator) + Operator->grab(); + + // this element can be tabbed to + setTabStop(true); + setTabOrder(-1); + + IGUISkin *skin = 0; + if (Environment) + skin = Environment->getSkin(); + if (Border && skin) + { + FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + } + + breakText(); + + calculateScrollPos(); +} + + +//! destructor +intlGUIEditBox::~intlGUIEditBox() +{ + if (OverrideFont) + OverrideFont->drop(); + + if (Operator) + Operator->drop(); +} + + +//! Sets another skin independent font. +void intlGUIEditBox::setOverrideFont(IGUIFont* font) +{ + if (OverrideFont == font) + return; + + if (OverrideFont) + OverrideFont->drop(); + + OverrideFont = font; + + if (OverrideFont) + OverrideFont->grab(); + + breakText(); +} + +IGUIFont * intlGUIEditBox::getOverrideFont() const +{ + return OverrideFont; +} + +//! Get the font which is used right now for drawing +IGUIFont* intlGUIEditBox::getActiveFont() const +{ + if ( OverrideFont ) + return OverrideFont; + IGUISkin* skin = Environment->getSkin(); + if (skin) + return skin->getFont(); + return 0; +} + +//! Sets another color for the text. +void intlGUIEditBox::setOverrideColor(video::SColor color) +{ + OverrideColor = color; + OverrideColorEnabled = true; +} + +video::SColor intlGUIEditBox::getOverrideColor() const +{ + return OverrideColor; +} + +//! Turns the border on or off +void intlGUIEditBox::setDrawBorder(bool border) +{ + Border = border; +} + +//! Sets whether to draw the background +void intlGUIEditBox::setDrawBackground(bool draw) +{ +} + +//! Sets if the text should use the overide color or the color in the gui skin. +void intlGUIEditBox::enableOverrideColor(bool enable) +{ + OverrideColorEnabled = enable; +} + +bool intlGUIEditBox::isOverrideColorEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return OverrideColorEnabled; +} + +//! Enables or disables word wrap +void intlGUIEditBox::setWordWrap(bool enable) +{ + WordWrap = enable; + breakText(); +} + + +void intlGUIEditBox::updateAbsolutePosition() +{ + core::rect oldAbsoluteRect(AbsoluteRect); + IGUIElement::updateAbsolutePosition(); + if ( oldAbsoluteRect != AbsoluteRect ) + { + breakText(); + } +} + + +//! Checks if word wrap is enabled +bool intlGUIEditBox::isWordWrapEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return WordWrap; +} + + +//! Enables or disables newlines. +void intlGUIEditBox::setMultiLine(bool enable) +{ + MultiLine = enable; +} + + +//! Checks if multi line editing is enabled +bool intlGUIEditBox::isMultiLineEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return MultiLine; +} + + +void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar) +{ + PasswordBox = passwordBox; + if (PasswordBox) + { + PasswordChar = passwordChar; + setMultiLine(false); + setWordWrap(false); + BrokenText.clear(); + } +} + + +bool intlGUIEditBox::isPasswordBox() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return PasswordBox; +} + + +//! Sets text justification +void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) +{ + HAlign = horizontal; + VAlign = vertical; +} + + +//! called if an event happened. +bool intlGUIEditBox::OnEvent(const SEvent& event) +{ + if (IsEnabled) + { + + switch(event.EventType) + { + case EET_GUI_EVENT: + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) + { + if (event.GUIEvent.Caller == this) + { + MouseMarking = false; + setTextMarkers(0,0); + } + } + break; + case EET_KEY_INPUT_EVENT: + { +#if defined(linux) + // ################################################################ + // ValkaTR: + // This part is the difference from the original intlGUIEditBox + // It converts UTF-8 character into a UCS-2 (wchar_t) + wchar_t wc = L'_'; + mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); + + //printf( "char: %lc (%u) \r\n", wc, wc ); + + SEvent irrevent(event); + irrevent.KeyInput.Char = wc; + // ################################################################ + + if (processKey(irrevent)) + return true; +#else + if (processKey(event)) + return true; +#endif // defined(linux) + + break; + } + case EET_MOUSE_INPUT_EVENT: + if (processMouse(event)) + return true; + break; + default: + break; + } + } + + return IGUIElement::OnEvent(event); +} + + +bool intlGUIEditBox::processKey(const SEvent& event) +{ + if (!event.KeyInput.PressedDown) + return false; + + bool textChanged = false; + s32 newMarkBegin = MarkBegin; + s32 newMarkEnd = MarkEnd; + + // control shortcut handling + + if (event.KeyInput.Control) + { + // german backlash '\' entered with control + '?' + if ( event.KeyInput.Char == '\\' ) + { + inputChar(event.KeyInput.Char); + return true; + } + + switch(event.KeyInput.Key) + { + case KEY_KEY_A: + // select all + newMarkBegin = 0; + newMarkEnd = Text.size(); + break; + case KEY_KEY_C: + // copy to clipboard + if (!PasswordBox && Operator && MarkBegin != MarkEnd) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + core::stringc s; + s = Text.subString(realmbgn, realmend - realmbgn).c_str(); + Operator->copyToClipboard(s.c_str()); + } + break; + case KEY_KEY_X: + // cut to the clipboard + if (!PasswordBox && Operator && MarkBegin != MarkEnd) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + // copy + core::stringc sc; + sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); + Operator->copyToClipboard(sc.c_str()); + + if (IsEnabled) + { + // delete + core::stringw s; + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + } + break; + case KEY_KEY_V: + if ( !IsEnabled ) + break; + + // paste from the clipboard + if (Operator) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + // add new character + const c8* p = Operator->getTextFromClipboard(); + if (p) + { + if (MarkBegin == MarkEnd) + { + // insert text + core::stringw s = Text.subString(0, CursorPos); + s.append(p); + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + + if (!Max || s.size()<=Max) // thx to Fish FH for fix + { + Text = s; + s = p; + CursorPos += s.size(); + } + } + else + { + // replace text + + core::stringw s = Text.subString(0, realmbgn); + s.append(p); + s.append( Text.subString(realmend, Text.size()-realmend) ); + + if (!Max || s.size()<=Max) // thx to Fish FH for fix + { + Text = s; + s = p; + CursorPos = realmbgn + s.size(); + } + } + } + + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + case KEY_HOME: + // move/highlight to start of text + if (event.KeyInput.Shift) + { + newMarkEnd = CursorPos; + newMarkBegin = 0; + CursorPos = 0; + } + else + { + CursorPos = 0; + newMarkBegin = 0; + newMarkEnd = 0; + } + break; + case KEY_END: + // move/highlight to end of text + if (event.KeyInput.Shift) + { + newMarkBegin = CursorPos; + newMarkEnd = Text.size(); + CursorPos = 0; + } + else + { + CursorPos = Text.size(); + newMarkBegin = 0; + newMarkEnd = 0; + } + break; + default: + return false; + } + } + // default keyboard handling + else + switch(event.KeyInput.Key) + { + case KEY_END: + { + s32 p = Text.size(); + if (WordWrap || MultiLine) + { + p = getLineFromPos(CursorPos); + p = BrokenTextPositions[p] + (s32)BrokenText[p].size(); + if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' )) + p-=1; + } + + if (event.KeyInput.Shift) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = p; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + CursorPos = p; + BlinkStartTime = porting::getTimeMs(); + } + break; + case KEY_HOME: + { + + s32 p = 0; + if (WordWrap || MultiLine) + { + p = getLineFromPos(CursorPos); + p = BrokenTextPositions[p]; + } + + if (event.KeyInput.Shift) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + newMarkEnd = p; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + CursorPos = p; + BlinkStartTime = porting::getTimeMs(); + } + break; + case KEY_RETURN: + if (MultiLine) + { + inputChar(L'\n'); + return true; + } + else + { + sendGuiEvent( EGET_EDITBOX_ENTER ); + } + break; + case KEY_LEFT: + + if (event.KeyInput.Shift) + { + if (CursorPos > 0) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = CursorPos-1; + } + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + if (CursorPos > 0) CursorPos--; + BlinkStartTime = porting::getTimeMs(); + break; + + case KEY_RIGHT: + if (event.KeyInput.Shift) + { + if (Text.size() > (u32)CursorPos) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = CursorPos+1; + } + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + if (Text.size() > (u32)CursorPos) CursorPos++; + BlinkStartTime = porting::getTimeMs(); + break; + case KEY_UP: + if (MultiLine || (WordWrap && BrokenText.size() > 1) ) + { + s32 lineNo = getLineFromPos(CursorPos); + s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd); + if (lineNo > 0) + { + s32 cp = CursorPos - BrokenTextPositions[lineNo]; + if ((s32)BrokenText[lineNo-1].size() < cp) + CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1; + else + CursorPos = BrokenTextPositions[lineNo-1] + cp; + } + + if (event.KeyInput.Shift) + { + newMarkBegin = mb; + newMarkEnd = CursorPos; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + } + else + { + return false; + } + break; + case KEY_DOWN: + if (MultiLine || (WordWrap && BrokenText.size() > 1) ) + { + s32 lineNo = getLineFromPos(CursorPos); + s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd); + if (lineNo < (s32)BrokenText.size()-1) + { + s32 cp = CursorPos - BrokenTextPositions[lineNo]; + if ((s32)BrokenText[lineNo+1].size() < cp) + CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1; + else + CursorPos = BrokenTextPositions[lineNo+1] + cp; + } + + if (event.KeyInput.Shift) + { + newMarkBegin = mb; + newMarkEnd = CursorPos; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + } + else + { + return false; + } + break; + + case KEY_BACK: + if ( !this->IsEnabled ) + break; + + if (Text.size()) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // delete marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + } + else + { + // delete text behind cursor + if (CursorPos>0) + s = Text.subString(0, CursorPos-1); + else + s = L""; + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + Text = s; + --CursorPos; + } + + if (CursorPos < 0) + CursorPos = 0; + BlinkStartTime = porting::getTimeMs(); + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + case KEY_DELETE: + if ( !this->IsEnabled ) + break; + + if (Text.size() != 0) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // delete marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + } + else + { + // delete text before cursor + s = Text.subString(0, CursorPos); + s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) ); + Text = s; + } + + if (CursorPos > (s32)Text.size()) + CursorPos = (s32)Text.size(); + + BlinkStartTime = porting::getTimeMs(); + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + + case KEY_ESCAPE: + case KEY_TAB: + case KEY_SHIFT: + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + case KEY_F11: + case KEY_F12: + case KEY_F13: + case KEY_F14: + case KEY_F15: + case KEY_F16: + case KEY_F17: + case KEY_F18: + case KEY_F19: + case KEY_F20: + case KEY_F21: + case KEY_F22: + case KEY_F23: + case KEY_F24: + // ignore these keys + return false; + + default: + inputChar(event.KeyInput.Char); + return true; + } + + // Set new text markers + setTextMarkers( newMarkBegin, newMarkEnd ); + + // break the text if it has changed + if (textChanged) + { + breakText(); + sendGuiEvent(EGET_EDITBOX_CHANGED); + } + + calculateScrollPos(); + + return true; +} + + +//! draws the element and its children +void intlGUIEditBox::draw() +{ + if (!IsVisible) + return; + + const bool focus = Environment->hasFocus(this); + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + + FrameRect = AbsoluteRect; + + // draw the border + + if (Border) + { + skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW), + false, true, FrameRect, &AbsoluteClippingRect); + + FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + } + core::rect localClipRect = FrameRect; + localClipRect.clipAgainst(AbsoluteClippingRect); + + // draw the text + + IGUIFont* font = OverrideFont; + if (!OverrideFont) + font = skin->getFont(); + + s32 cursorLine = 0; + s32 charcursorpos = 0; + + if (font) + { + if (LastBreakFont != font) + { + breakText(); + } + + // calculate cursor pos + + core::stringw *txtLine = &Text; + s32 startPos = 0; + + core::stringw s, s2; + + // get mark position + const bool ml = (!PasswordBox && (WordWrap || MultiLine)); + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0; + const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1; + const s32 lineCount = ml ? BrokenText.size() : 1; + + // Save the override color information. + // Then, alter it if the edit box is disabled. + const bool prevOver = OverrideColorEnabled; + const video::SColor prevColor = OverrideColor; + + if (Text.size()) + { + if (!IsEnabled && !OverrideColorEnabled) + { + OverrideColorEnabled = true; + OverrideColor = skin->getColor(EGDC_GRAY_TEXT); + } + + for (s32 i=0; i < lineCount; ++i) + { + setTextRect(i); + + // clipping test - don't draw anything outside the visible area + core::rect c = localClipRect; + c.clipAgainst(CurrentTextRect); + if (!c.isValid()) + continue; + + // get current line + if (PasswordBox) + { + if (BrokenText.size() != 1) + { + BrokenText.clear(); + BrokenText.push_back(core::stringw()); + } + if (BrokenText[0].size() != Text.size()) + { + BrokenText[0] = Text; + for (u32 q = 0; q < Text.size(); ++q) + { + BrokenText[0] [q] = PasswordChar; + } + } + txtLine = &BrokenText[0]; + startPos = 0; + } + else + { + txtLine = ml ? &BrokenText[i] : &Text; + startPos = ml ? BrokenTextPositions[i] : 0; + } + + + // draw normal text + font->draw(txtLine->c_str(), CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); + + // draw mark and marked text + if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount) + { + + s32 mbegin = 0, mend = 0; + s32 lineStartPos = 0, lineEndPos = txtLine->size(); + + if (i == hlineStart) + { + // highlight start is on this line + s = txtLine->subString(0, realmbgn - startPos); + mbegin = font->getDimension(s.c_str()).Width; + + // deal with kerning + mbegin += font->getKerningWidth( + &((*txtLine)[realmbgn - startPos]), + realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0); + + lineStartPos = realmbgn - startPos; + } + if (i == hlineStart + hlineCount - 1) + { + // highlight end is on this line + s2 = txtLine->subString(0, realmend - startPos); + mend = font->getDimension(s2.c_str()).Width; + lineEndPos = (s32)s2.size(); + } + else + mend = font->getDimension(txtLine->c_str()).Width; + + CurrentTextRect.UpperLeftCorner.X += mbegin; + CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin; + + // draw mark + skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect); + + // draw marked text + s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos); + + if (s.size()) + font->draw(s.c_str(), CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT), + false, true, &localClipRect); + + } + } + + // Return the override color information to its previous settings. + OverrideColorEnabled = prevOver; + OverrideColor = prevColor; + } + + // draw cursor + + if (WordWrap || MultiLine) + { + cursorLine = getLineFromPos(CursorPos); + txtLine = &BrokenText[cursorLine]; + startPos = BrokenTextPositions[cursorLine]; + } + s = txtLine->subString(0,CursorPos-startPos); + charcursorpos = font->getDimension(s.c_str()).Width + + font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0); + + if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350) + { + setTextRect(cursorLine); + CurrentTextRect.UpperLeftCorner.X += charcursorpos; + + font->draw(L"_", CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); + } + } + + // draw children + IGUIElement::draw(); +} + + +//! Sets the new caption of this element. +void intlGUIEditBox::setText(const wchar_t* text) +{ + Text = text; + if (u32(CursorPos) > Text.size()) + CursorPos = Text.size(); + HScrollPos = 0; + breakText(); +} + + +//! Enables or disables automatic scrolling with cursor position +//! \param enable: If set to true, the text will move around with the cursor position +void intlGUIEditBox::setAutoScroll(bool enable) +{ + AutoScroll = enable; +} + + +//! Checks to see if automatic scrolling is enabled +//! \return true if automatic scrolling is enabled, false if not +bool intlGUIEditBox::isAutoScrollEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return AutoScroll; +} + + +//! Gets the area of the text in the edit box +//! \return Returns the size in pixels of the text +core::dimension2du intlGUIEditBox::getTextDimension() +{ + core::rect ret; + + setTextRect(0); + ret = CurrentTextRect; + + for (u32 i=1; i < BrokenText.size(); ++i) + { + setTextRect(i); + ret.addInternalPoint(CurrentTextRect.UpperLeftCorner); + ret.addInternalPoint(CurrentTextRect.LowerRightCorner); + } + + return core::dimension2du(ret.getSize()); +} + + +//! Sets the maximum amount of characters which may be entered in the box. +//! \param max: Maximum amount of characters. If 0, the character amount is +//! infinity. +void intlGUIEditBox::setMax(u32 max) +{ + Max = max; + + if (Text.size() > Max && Max != 0) + Text = Text.subString(0, Max); +} + + +//! Returns maximum amount of characters, previously set by setMax(); +u32 intlGUIEditBox::getMax() const +{ + return Max; +} + + +bool intlGUIEditBox::processMouse(const SEvent& event) +{ + switch(event.MouseInput.Event) + { + case irr::EMIE_LMOUSE_LEFT_UP: + if (Environment->hasFocus(this)) + { + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + if (MouseMarking) + { + setTextMarkers( MarkBegin, CursorPos ); + } + MouseMarking = false; + calculateScrollPos(); + return true; + } + break; + case irr::EMIE_MOUSE_MOVED: + { + if (MouseMarking) + { + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + setTextMarkers( MarkBegin, CursorPos ); + calculateScrollPos(); + return true; + } + } + break; + case EMIE_LMOUSE_PRESSED_DOWN: + if (!Environment->hasFocus(this)) + { + BlinkStartTime = porting::getTimeMs(); + MouseMarking = true; + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + setTextMarkers(CursorPos, CursorPos ); + calculateScrollPos(); + return true; + } + else + { + if (!AbsoluteClippingRect.isPointInside( + core::position2d(event.MouseInput.X, event.MouseInput.Y))) + { + return false; + } + else + { + // move cursor + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + + s32 newMarkBegin = MarkBegin; + if (!MouseMarking) + newMarkBegin = CursorPos; + + MouseMarking = true; + setTextMarkers( newMarkBegin, CursorPos); + calculateScrollPos(); + return true; + } + } + default: + break; + } + + return false; +} + + +s32 intlGUIEditBox::getCursorPos(s32 x, s32 y) +{ + IGUIFont* font = OverrideFont; + IGUISkin* skin = Environment->getSkin(); + if (!OverrideFont) + font = skin->getFont(); + + const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; + + core::stringw *txtLine=0; + s32 startPos=0; + x+=3; + + for (u32 i=0; i < lineCount; ++i) + { + setTextRect(i); + if (i == 0 && y < CurrentTextRect.UpperLeftCorner.Y) + y = CurrentTextRect.UpperLeftCorner.Y; + if (i == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y ) + y = CurrentTextRect.LowerRightCorner.Y; + + // is it inside this region? + if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) + { + // we've found the clicked line + txtLine = (WordWrap || MultiLine) ? &BrokenText[i] : &Text; + startPos = (WordWrap || MultiLine) ? BrokenTextPositions[i] : 0; + break; + } + } + + if (x < CurrentTextRect.UpperLeftCorner.X) + x = CurrentTextRect.UpperLeftCorner.X; + + s32 idx = font->getCharacterFromPos(Text.c_str(), x - CurrentTextRect.UpperLeftCorner.X); + + // click was on or left of the line + if (idx != -1) + return idx + startPos; + + // click was off the right edge of the line, go to end. + return txtLine->size() + startPos; +} + + +//! Breaks the single text line. +void intlGUIEditBox::breakText() +{ + IGUISkin* skin = Environment->getSkin(); + + if ((!WordWrap && !MultiLine) || !skin) + return; + + BrokenText.clear(); // need to reallocate :/ + BrokenTextPositions.set_used(0); + + IGUIFont* font = OverrideFont; + if (!OverrideFont) + font = skin->getFont(); + + if (!font) + return; + + LastBreakFont = font; + + core::stringw line; + core::stringw word; + core::stringw whitespace; + s32 lastLineStart = 0; + s32 size = Text.size(); + s32 length = 0; + s32 elWidth = RelativeRect.getWidth() - 6; + wchar_t c; + + for (s32 i=0; igetDimension(whitespace.c_str()).Width; + s32 worldlgth = font->getDimension(word.c_str()).Width; + + if (WordWrap && length + worldlgth + whitelgth > elWidth) + { + // break to next line + length = worldlgth; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); + lastLineStart = i - (s32)word.size(); + line = word; + } + else + { + // add word to line + line += whitespace; + line += word; + length += whitelgth + worldlgth; + } + + word = L""; + whitespace = L""; + } + + whitespace += c; + + // compute line break + if (lineBreak) + { + line += whitespace; + line += word; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); + lastLineStart = i+1; + line = L""; + word = L""; + whitespace = L""; + length = 0; + } + } + else + { + // yippee this is a word.. + word += c; + } + } + + line += whitespace; + line += word; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); +} + + +void intlGUIEditBox::setTextRect(s32 line) +{ + core::dimension2du d; + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + + IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + + if (!font) + return; + + // get text dimension + const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; + if (WordWrap || MultiLine) + { + d = font->getDimension(BrokenText[line].c_str()); + } + else + { + d = font->getDimension(Text.c_str()); + d.Height = AbsoluteRect.getHeight(); + } + d.Height += font->getKerningHeight(); + + // justification + switch (HAlign) + { + case EGUIA_CENTER: + // align to h centre + CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2); + CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2); + break; + case EGUIA_LOWERRIGHT: + // align to right edge + CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width; + CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth(); + break; + default: + // align to left edge + CurrentTextRect.UpperLeftCorner.X = 0; + CurrentTextRect.LowerRightCorner.X = d.Width; + + } + + switch (VAlign) + { + case EGUIA_CENTER: + // align to v centre + CurrentTextRect.UpperLeftCorner.Y = + (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line; + break; + case EGUIA_LOWERRIGHT: + // align to bottom edge + CurrentTextRect.UpperLeftCorner.Y = + FrameRect.getHeight() - lineCount*d.Height + d.Height*line; + break; + default: + // align to top edge + CurrentTextRect.UpperLeftCorner.Y = d.Height*line; + break; + } + + CurrentTextRect.UpperLeftCorner.X -= HScrollPos; + CurrentTextRect.LowerRightCorner.X -= HScrollPos; + CurrentTextRect.UpperLeftCorner.Y -= VScrollPos; + CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height; + + CurrentTextRect += FrameRect.UpperLeftCorner; + +} + + +s32 intlGUIEditBox::getLineFromPos(s32 pos) +{ + if (!WordWrap && !MultiLine) + return 0; + + s32 i=0; + while (i < (s32)BrokenTextPositions.size()) + { + if (BrokenTextPositions[i] > pos) + return i-1; + ++i; + } + return (s32)BrokenTextPositions.size() - 1; +} + + +void intlGUIEditBox::inputChar(wchar_t c) +{ + if (!IsEnabled) + return; + + if (c != 0) + { + if (Text.size() < Max || Max == 0) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // replace marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append(c); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + CursorPos = realmbgn+1; + } + else + { + // add new character + s = Text.subString(0, CursorPos); + s.append(c); + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + Text = s; + ++CursorPos; + } + + BlinkStartTime = porting::getTimeMs(); + setTextMarkers(0, 0); + } + } + breakText(); + sendGuiEvent(EGET_EDITBOX_CHANGED); + calculateScrollPos(); +} + + +void intlGUIEditBox::calculateScrollPos() +{ + if (!AutoScroll) + return; + + // calculate horizontal scroll position + s32 cursLine = getLineFromPos(CursorPos); + setTextRect(cursLine); + + // don't do horizontal scrolling when wordwrap is enabled. + if (!WordWrap) + { + // get cursor position + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + if (!font) + return; + + core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text; + s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos; + + s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos + + font->getDimension(txtLine->subString(0, cPos).c_str()).Width; + + s32 cEnd = cStart + font->getDimension(L"_ ").Width; + + if (FrameRect.LowerRightCorner.X < cEnd) + HScrollPos = cEnd - FrameRect.LowerRightCorner.X; + else if (FrameRect.UpperLeftCorner.X > cStart) + HScrollPos = cStart - FrameRect.UpperLeftCorner.X; + else + HScrollPos = 0; + + // todo: adjust scrollbar + } + + // vertical scroll position + if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos) + VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos; + + else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos) + VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos; + else + VScrollPos = 0; + + // todo: adjust scrollbar +} + +//! set text markers +void intlGUIEditBox::setTextMarkers(s32 begin, s32 end) +{ + if ( begin != MarkBegin || end != MarkEnd ) + { + MarkBegin = begin; + MarkEnd = end; + sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); + } +} + +//! send some gui event to parent +void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type) +{ + if ( Parent ) + { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = type; + + Parent->OnEvent(e); + } +} + +//! Writes attributes of the element. +void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +{ + // IGUIEditBox::serializeAttributes(out,options); + + out->addBool ("OverrideColorEnabled",OverrideColorEnabled ); + out->addColor ("OverrideColor", OverrideColor); + // out->addFont("OverrideFont",OverrideFont); + out->addInt ("MaxChars", Max); + out->addBool ("WordWrap", WordWrap); + out->addBool ("MultiLine", MultiLine); + out->addBool ("AutoScroll", AutoScroll); + out->addBool ("PasswordBox", PasswordBox); + core::stringw ch = L" "; + ch[0] = PasswordChar; + out->addString("PasswordChar", ch.c_str()); + out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); + out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); + + IGUIEditBox::serializeAttributes(out,options); +} + + +//! Reads attributes of the element +void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +{ + IGUIEditBox::deserializeAttributes(in,options); + + setOverrideColor(in->getAttributeAsColor("OverrideColor")); + enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); + setMax(in->getAttributeAsInt("MaxChars")); + setWordWrap(in->getAttributeAsBool("WordWrap")); + setMultiLine(in->getAttributeAsBool("MultiLine")); + setAutoScroll(in->getAttributeAsBool("AutoScroll")); + core::stringw ch = in->getAttributeAsStringW("PasswordChar"); + + if (!ch.size()) + setPasswordBox(in->getAttributeAsBool("PasswordBox")); + else + setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); + + setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); + + // setOverrideFont(in->getAttributeAsFont("OverrideFont")); +} + + +} // end namespace gui +} // end namespace irr + +#endif // _IRR_COMPILE_WITH_GUI_ diff --git a/src/intlGUIEditBox.h b/src/intlGUIEditBox.h new file mode 100644 index 000000000..f888fb620 --- /dev/null +++ b/src/intlGUIEditBox.h @@ -0,0 +1,178 @@ +// Copyright (C) 2002-2010 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __C_INTL_GUI_EDIT_BOX_H_INCLUDED__ +#define __C_INTL_GUI_EDIT_BOX_H_INCLUDED__ + +#include "IrrCompileConfig.h" +//#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUIEditBox.h" +#include "irrArray.h" +#include "IOSOperator.h" + +namespace irr +{ +namespace gui +{ + class intlGUIEditBox : public IGUIEditBox + { + public: + + //! constructor + intlGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment, + IGUIElement* parent, s32 id, const core::rect& rectangle); + + //! destructor + virtual ~intlGUIEditBox(); + + //! Sets another skin independent font. + virtual void setOverrideFont(IGUIFont* font=0); + + //! Gets the override font (if any) + /** \return The override font (may be 0) */ + virtual IGUIFont* getOverrideFont() const; + + //! Get the font which is used right now for drawing + /** Currently this is the override font when one is set and the + font of the active skin otherwise */ + virtual IGUIFont* getActiveFont() const; + + //! Sets another color for the text. + virtual void setOverrideColor(video::SColor color); + + //! Gets the override color + virtual video::SColor getOverrideColor() const; + + //! Sets if the text should use the overide color or the + //! color in the gui skin. + virtual void enableOverrideColor(bool enable); + + //! Checks if an override color is enabled + /** \return true if the override color is enabled, false otherwise */ + virtual bool isOverrideColorEnabled(void) const; + + //! Sets whether to draw the background + virtual void setDrawBackground(bool draw); + + //! Turns the border on or off + virtual void setDrawBorder(bool border); + + //! Enables or disables word wrap for using the edit box as multiline text editor. + virtual void setWordWrap(bool enable); + + //! Checks if word wrap is enabled + //! \return true if word wrap is enabled, false otherwise + virtual bool isWordWrapEnabled() const; + + //! Enables or disables newlines. + /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, + instead a newline character will be inserted. */ + virtual void setMultiLine(bool enable); + + //! Checks if multi line editing is enabled + //! \return true if mult-line is enabled, false otherwise + virtual bool isMultiLineEnabled() const; + + //! Enables or disables automatic scrolling with cursor position + //! \param enable: If set to true, the text will move around with the cursor position + virtual void setAutoScroll(bool enable); + + //! Checks to see if automatic scrolling is enabled + //! \return true if automatic scrolling is enabled, false if not + virtual bool isAutoScrollEnabled() const; + + //! Gets the size area of the text in the edit box + //! \return Returns the size in pixels of the text + virtual core::dimension2du getTextDimension(); + + //! Sets text justification + virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); + + //! called if an event happened. + virtual bool OnEvent(const SEvent& event); + + //! draws the element and its children + virtual void draw(); + + //! Sets the new caption of this element. + virtual void setText(const wchar_t* text); + + //! Sets the maximum amount of characters which may be entered in the box. + //! \param max: Maximum amount of characters. If 0, the character amount is + //! infinity. + virtual void setMax(u32 max); + + //! Returns maximum amount of characters, previously set by setMax(); + virtual u32 getMax() const; + + //! Sets whether the edit box is a password box. Setting this to true will + /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x + \param passwordBox: true to enable password, false to disable + \param passwordChar: the character that is displayed instead of letters */ + virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*'); + + //! Returns true if the edit box is currently a password box. + virtual bool isPasswordBox() const; + + //! Updates the absolute position, splits text if required + virtual void updateAbsolutePosition(); + + //! Writes attributes of the element. + virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + + //! Reads attributes of the element + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + + protected: + //! Breaks the single text line. + void breakText(); + //! sets the area of the given line + void setTextRect(s32 line); + //! returns the line number that the cursor is on + s32 getLineFromPos(s32 pos); + //! adds a letter to the edit box + void inputChar(wchar_t c); + //! calculates the current scroll position + void calculateScrollPos(); + //! send some gui event to parent + void sendGuiEvent(EGUI_EVENT_TYPE type); + //! set text markers + void setTextMarkers(s32 begin, s32 end); + + bool processKey(const SEvent& event); + bool processMouse(const SEvent& event); + s32 getCursorPos(s32 x, s32 y); + + bool MouseMarking; + bool Border; + bool OverrideColorEnabled; + s32 MarkBegin; + s32 MarkEnd; + + video::SColor OverrideColor; + gui::IGUIFont *OverrideFont, *LastBreakFont; + IOSOperator* Operator; + + u32 BlinkStartTime; + s32 CursorPos; + s32 HScrollPos, VScrollPos; // scroll position in characters + u32 Max; + + bool WordWrap, MultiLine, AutoScroll, PasswordBox; + wchar_t PasswordChar; + EGUI_ALIGNMENT HAlign, VAlign; + + core::array< core::stringw > BrokenText; + core::array< s32 > BrokenTextPositions; + + core::rect CurrentTextRect, FrameRect; // temporary values + }; + + +} // end namespace gui +} // end namespace irr + +//#endif // _IRR_COMPILE_WITH_GUI_ +#endif // __C_GUI_EDIT_BOX_H_INCLUDED__ diff --git a/src/main.cpp b/src/main.cpp index 6f4095148..968dc8d60 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,6 +68,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "profiler.h" #include "log.h" #include "mods.h" +#if USE_FREETYPE +#include "xCGUITTFont.h" +#endif #include "util/string.h" #include "subgame.h" #include "quicktune.h" @@ -767,11 +770,19 @@ int main(int argc, char *argv[]) log_register_thread("main"); - // Set locale. This is for forcing '.' as the decimal point. - std::locale::global(std::locale("C")); - // This enables printing all characters in bitmap font - setlocale(LC_CTYPE, "en_US"); + // This enables internatonal characters input + if( setlocale(LC_ALL, "") == NULL ) + { + fprintf( stderr, "%s: warning: could not set default locale\n", argv[0] ); + } + // Set locale. This is for forcing '.' as the decimal point. + try { + std::locale::global(std::locale(std::locale(""), "C", std::locale::numeric)); + setlocale(LC_NUMERIC, "C"); + } catch (const std::exception& ex) { + errorstream<<"Could not set numeric locale to C"<getGUIEnvironment(); gui::IGUISkin* skin = guienv->getSkin(); + #if USE_FREETYPE + std::string font_path = g_settings->get("font_path"); + u16 font_size = g_settings->getU16("font_size"); + gui::IGUIFont *font = gui::CGUITTFont::createTTFont(guienv, font_path.c_str(), font_size); + #else gui::IGUIFont* font = guienv->getFont(getTexturePath("fontlucida.png").c_str()); + #endif if(font) skin->setFont(font); else From 65cb318cd98de572c3423122d71a1478959d87b1 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 15 Feb 2013 22:21:23 +0200 Subject: [PATCH 13/43] Restore old acceleration behavior until something is actually agreed on --- src/defaultsettings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index aebdc0aa8..5786858fc 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -180,9 +180,9 @@ void set_default_settings(Settings *settings) settings->setDefault("remote_media", ""); // physics stuff - settings->setDefault("movement_acceleration_default", "2"); - settings->setDefault("movement_acceleration_air", "0.5"); - settings->setDefault("movement_acceleration_fast", "8"); + settings->setDefault("movement_acceleration_default", "4"); + settings->setDefault("movement_acceleration_air", "4"); + settings->setDefault("movement_acceleration_fast", "4"); settings->setDefault("movement_speed_walk", "4"); settings->setDefault("movement_speed_crouch", "1.35"); settings->setDefault("movement_speed_fast", "20"); From 4035bf325929db5875785d12118512ea828df717 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 15 Feb 2013 22:47:03 +0200 Subject: [PATCH 14/43] Restore old acceleration behavior until something is actually agreed on (part 2) --- src/player.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/player.cpp b/src/player.cpp index 0c34c4cdf..90aa4fe05 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -59,9 +59,9 @@ Player::Player(IGameDef *gamedef): "list[current_player;craftpreview;7,1;1,1;]"; // Initialize movement settings at default values, so movement can work if the server fails to send them - movement_acceleration_default = 2 * BS; - movement_acceleration_air = 0.5 * BS; - movement_acceleration_fast = 8 * BS; + movement_acceleration_default = 4 * BS; + movement_acceleration_air = 4 * BS; + movement_acceleration_fast = 4 * BS; movement_speed_walk = 4 * BS; movement_speed_crouch = 1.35 * BS; movement_speed_fast = 20 * BS; From c88d89d26edb0d7864feb1b8b1d8e79c765ae0ef Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Fri, 15 Feb 2013 23:18:19 +0200 Subject: [PATCH 15/43] Some kind of middleway values for player acceleration for now --- minetest.conf.example | 6 +++--- src/defaultsettings.cpp | 6 +++--- src/player.cpp | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/minetest.conf.example b/minetest.conf.example index 5afe6c5e2..30d0dbd5c 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -251,9 +251,9 @@ #remote_media = # Physics stuff -#movement_acceleration_default = 2 -#movement_acceleration_air = 0.5 -#movement_acceleration_fast = 4 +#movement_acceleration_default = 3 +#movement_acceleration_air = 2 +#movement_acceleration_fast = 10 #movement_speed_walk = 4 #movement_speed_crouch = 1.35 #movement_speed_fast = 20 diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 5786858fc..e3c6b2f7d 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -180,9 +180,9 @@ void set_default_settings(Settings *settings) settings->setDefault("remote_media", ""); // physics stuff - settings->setDefault("movement_acceleration_default", "4"); - settings->setDefault("movement_acceleration_air", "4"); - settings->setDefault("movement_acceleration_fast", "4"); + settings->setDefault("movement_acceleration_default", "3"); + settings->setDefault("movement_acceleration_air", "2"); + settings->setDefault("movement_acceleration_fast", "10"); settings->setDefault("movement_speed_walk", "4"); settings->setDefault("movement_speed_crouch", "1.35"); settings->setDefault("movement_speed_fast", "20"); diff --git a/src/player.cpp b/src/player.cpp index 90aa4fe05..86d3ae184 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -59,9 +59,9 @@ Player::Player(IGameDef *gamedef): "list[current_player;craftpreview;7,1;1,1;]"; // Initialize movement settings at default values, so movement can work if the server fails to send them - movement_acceleration_default = 4 * BS; - movement_acceleration_air = 4 * BS; - movement_acceleration_fast = 4 * BS; + movement_acceleration_default = 3 * BS; + movement_acceleration_air = 2 * BS; + movement_acceleration_fast = 10 * BS; movement_speed_walk = 4 * BS; movement_speed_crouch = 1.35 * BS; movement_speed_fast = 20 * BS; From 214a8b4597728213065f1bf880f4357b723b5bea Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Mon, 18 Feb 2013 13:58:55 +0400 Subject: [PATCH 16/43] Set numeric locale for Lua --- builtin/builtin.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/builtin.lua b/builtin/builtin.lua index f2811fa9c..bf33cbe6b 100644 --- a/builtin/builtin.lua +++ b/builtin/builtin.lua @@ -8,6 +8,7 @@ -- Initialize some very basic things print = minetest.debug math.randomseed(os.time()) +os.setlocale("C", "numeric") -- Load other files dofile(minetest.get_modpath("__builtin").."/serialize.lua") From 365c169b43f61b7f957bdd302b3cf9a837cabec1 Mon Sep 17 00:00:00 2001 From: Jeija Date: Sun, 27 Jan 2013 10:33:25 +0100 Subject: [PATCH 17/43] Disable placement prediction for nodes that use on_rightclick --- src/game.cpp | 3 ++- src/itemdef.cpp | 1 + src/itemdef.h | 2 ++ src/nodedef.cpp | 3 +++ src/nodedef.h | 2 ++ src/scriptapi.cpp | 6 +++++- 6 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 5693c5c12..c77754b5f 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2531,7 +2531,8 @@ void the_game( // make that happen const ItemDefinition &def = playeritem.getDefinition(itemdef); - if(def.node_placement_prediction != "") + if(def.node_placement_prediction != "" + && !nodedef->get(map.getNode(nodepos)).rightclickable) do{ // breakable verbosestream<<"Node placement prediction for " < Date: Tue, 19 Feb 2013 20:16:55 +0100 Subject: [PATCH 18/43] Dont drop tools on rightclick --- builtin/item.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin/item.lua b/builtin/item.lua index 2233b6c5b..69c4bd0bb 100644 --- a/builtin/item.lua +++ b/builtin/item.lua @@ -243,9 +243,8 @@ function minetest.item_place(itemstack, placer, pointed_thing) if itemstack:get_definition().type == "node" then return minetest.item_place_node(itemstack, placer, pointed_thing) - elseif itemstack:get_definition().type ~= "none" then - return minetest.item_place_object(itemstack, placer, pointed_thing) end + return itemstack end function minetest.item_drop(itemstack, dropper, pos) From 89b88917a89923b37b629557fdeb54fa0b442bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Doser?= Date: Wed, 20 Feb 2013 20:06:39 +0100 Subject: [PATCH 19/43] Print missing mod dependencies on server start i.e., not only the mod with missing dependencies, but also the missing dependencies itself. (This already used to be the case before the mod selection gui was added) Also, mods with unsatisfied dependencies are no longer reported as mods that could not be found. --- src/server.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/server.cpp b/src/server.cpp index 572ef4d82..ec8f8a5e8 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -998,17 +998,20 @@ Server::Server( ModConfiguration modconf(m_path_world); m_mods = modconf.getMods(); + std::list unsatisfied_mods = modconf.getUnsatisfiedMods(); // complain about mods with unsatisfied dependencies if(!modconf.isConsistent()) { - errorstream << "The following mods have unsatisfied dependencies: "; - std::list modlist = modconf.getUnsatisfiedMods(); - for(std::list::iterator it = modlist.begin(); - it != modlist.end(); ++it) + for(std::list::iterator it = unsatisfied_mods.begin(); + it != unsatisfied_mods.end(); ++it) { - errorstream << (*it).name << " "; + ModSpec mod = *it; + errorstream << "mod \"" << mod.name << "\" has unsatisfied dependencies: "; + for(std::set::iterator dep_it = mod.unsatisfied_depends.begin(); + dep_it != mod.unsatisfied_depends.end(); ++dep_it) + errorstream << " \"" << *dep_it << "\""; + errorstream << std::endl; } - errorstream << std::endl; } Settings worldmt_settings; @@ -1033,12 +1036,15 @@ Server::Server( for(std::vector::iterator it = m_mods.begin(); it != m_mods.end(); ++it) load_mod_names.erase((*it).name); + for(std::list::iterator it = unsatisfied_mods.begin(); + it != unsatisfied_mods.end(); ++it) + load_mod_names.erase((*it).name); if(!load_mod_names.empty()) { - errorstream << "The following mods could not be found: "; + errorstream << "The following mods could not be found:"; for(std::set::iterator it = load_mod_names.begin(); it != load_mod_names.end(); ++it) - errorstream << (*it) << " "; + errorstream << " \"" << (*it) << "\""; errorstream << std::endl; } From ef6b8bee07b9cefe7d463fabd199548d37ff2bee Mon Sep 17 00:00:00 2001 From: PilzAdam Date: Sat, 12 Jan 2013 20:18:43 +0100 Subject: [PATCH 20/43] Drop nodes as items when dugged and no room in inventory and dont remove dropped items when no room in inventory --- builtin/item.lua | 10 +++++++++- builtin/item_entity.lua | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/builtin/item.lua b/builtin/item.lua index 69c4bd0bb..1349fdf63 100644 --- a/builtin/item.lua +++ b/builtin/item.lua @@ -290,7 +290,15 @@ function minetest.handle_node_drops(pos, drops, digger) if digger:get_inventory() then local _, dropped_item for _, dropped_item in ipairs(drops) do - digger:get_inventory():add_item("main", dropped_item) + local left = digger:get_inventory():add_item("main", dropped_item) + if not left:is_empty() then + local p = { + x = pos.x + math.random()/2-0.25, + y = pos.y + math.random()/2-0.25, + z = pos.z + math.random()/2-0.25, + } + minetest.env:add_item(p, left) + end end end end diff --git a/builtin/item_entity.lua b/builtin/item_entity.lua index 46415e538..1699cb03c 100644 --- a/builtin/item_entity.lua +++ b/builtin/item_entity.lua @@ -109,7 +109,10 @@ minetest.register_entity("__builtin:item", { on_punch = function(self, hitter) if self.itemstring ~= '' then - hitter:get_inventory():add_item("main", self.itemstring) + local left = hitter:get_inventory():add_item("main", self.itemstring) + if not left:is_empty() then + return + end end self.object:remove() end, From ee07c3f7cf638e854518d2cfcb9c11a64412cc72 Mon Sep 17 00:00:00 2001 From: proller Date: Fri, 22 Feb 2013 02:00:44 +0400 Subject: [PATCH 21/43] new auto masterserver --- .gitignore | 2 + cmake/Modules/FindJson.cmake | 18 + minetest.conf.example | 12 +- src/CMakeLists.txt | 29 +- src/defaultsettings.cpp | 7 +- src/guiMainMenu.cpp | 64 +- src/guiMainMenu.h | 2 + src/json/CMakeLists.txt | 14 + src/json/UPDATING | 16 + src/json/json.h | 1914 +++++++++++++++ src/json/jsoncpp.cpp | 4367 ++++++++++++++++++++++++++++++++++ src/main.cpp | 10 +- src/server.cpp | 24 +- src/server.h | 2 + src/serverlist.cpp | 119 +- src/serverlist.h | 14 +- util/master/index.html | 11 + util/master/list.js | 72 + util/master/master.cgi | 257 ++ util/master/style.css | 14 + 20 files changed, 6919 insertions(+), 49 deletions(-) create mode 100644 cmake/Modules/FindJson.cmake create mode 100644 src/json/CMakeLists.txt create mode 100644 src/json/UPDATING create mode 100644 src/json/json.h create mode 100644 src/json/jsoncpp.cpp create mode 100644 util/master/index.html create mode 100644 util/master/list.js create mode 100755 util/master/master.cgi create mode 100644 util/master/style.css diff --git a/.gitignore b/.gitignore index e282ec130..353032637 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,8 @@ src/cguittfont/CMakeFiles/ src/cguittfont/libcguittfont.a src/cguittfont/cmake_install.cmake src/cguittfont/Makefile +src/json/CMakeFiles/ +src/json/libjson.a CMakeCache.txt CPackConfig.cmake CPackSourceConfig.cmake diff --git a/cmake/Modules/FindJson.cmake b/cmake/Modules/FindJson.cmake new file mode 100644 index 000000000..bc4e71a29 --- /dev/null +++ b/cmake/Modules/FindJson.cmake @@ -0,0 +1,18 @@ +# Look for json, use our own if not found + +#FIND_PATH(JSON_INCLUDE_DIR json.h) + +#FIND_LIBRARY(JSON_LIBRARY NAMES json) + +#IF(JSON_LIBRARY AND JSON_INCLUDE_DIR) +# SET( JSON_FOUND TRUE ) +#ENDIF(JSON_LIBRARY AND JSON_INCLUDE_DIR) + +#IF(JSON_FOUND) +# MESSAGE(STATUS "Found system json header file in ${JSON_INCLUDE_DIR}") +# MESSAGE(STATUS "Found system json library ${JSON_LIBRARY}") +#ELSE(JSON_FOUND) + SET(JSON_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/json) + SET(JSON_LIBRARY json) + MESSAGE(STATUS "Using project json library") +#ENDIF(JSON_FOUND) diff --git a/minetest.conf.example b/minetest.conf.example index 30d0dbd5c..c36fe05ff 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -159,7 +159,7 @@ #media_fetch_threads = 8 # Url to the server list displayed in the Multiplayer Tab -#serverlist_url = servers.minetest.ru/server.list +#serverlist_url = servers.minetest.net # File in client/serverlist/ that contains your favorite servers displayed in the Multiplayer Tab #serverlist_file = favoriteservers.txt @@ -172,6 +172,16 @@ # Server stuff # +# Name of server +#server_name = Minetest server +# Description of server +#server_description = mine here +# Domain name of server +#server_address = game.minetest.net +# Homepage of server +#server_url = http://minetest.net +# Automaticaly report to masterserver +#server_announce = 0 # Default game (default when creating a new world) #default_game = minetest # World directory (everything in the world is stored here) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b96f53fa..17102e848 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required( VERSION 2.6 ) mark_as_advanced(EXECUTABLE_OUTPUT_PATH LIBRARY_OUTPUT_PATH) mark_as_advanced(JTHREAD_INCLUDE_DIR JTHREAD_LIBRARY) mark_as_advanced(SQLITE3_INCLUDE_DIR SQLITE3_LIBRARY) +mark_as_advanced(JSON_INCLUDE_DIR JSON_LIBRARY) option(ENABLE_CURL "Enable cURL support for fetching media" 1) @@ -170,6 +171,7 @@ endif() find_package(Jthread REQUIRED) find_package(Sqlite3 REQUIRED) +find_package(Json REQUIRED) if(USE_FREETYPE) find_package(Freetype REQUIRED) @@ -242,6 +244,7 @@ set(common_SRCS biome.cpp clientserver.cpp staticobject.cpp + serverlist.cpp util/serialize.cpp util/directiontables.cpp util/numeric.cpp @@ -303,7 +306,6 @@ set(minetest_SRCS filecache.cpp tile.cpp shader.cpp - serverlist.cpp game.cpp main.cpp ) @@ -332,6 +334,7 @@ include_directories( ${JTHREAD_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} ${LUA_INCLUDE_DIR} + ${JSON_INCLUDE_DIR} ) if(USE_FREETYPE) @@ -341,6 +344,12 @@ if(USE_FREETYPE) ) endif(USE_FREETYPE) +if(USE_CURL) + include_directories( + ${CURL_INCLUDE_DIR} + ) +endif(USE_CURL) + set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin") if(BUILD_CLIENT) @@ -359,18 +368,15 @@ if(BUILD_CLIENT) ${JTHREAD_LIBRARY} ${SQLITE3_LIBRARY} ${LUA_LIBRARY} + ${JSON_LIBRARY} ${PLATFORM_LIBS} ${CLIENT_PLATFORM_LIBS} ) - if(USE_CURL) target_link_libraries( ${PROJECT_NAME} ${CURL_LIBRARY} ) - include_directories( - ${CURL_INCLUDE_DIR} - ) endif(USE_CURL) if(USE_FREETYPE) target_link_libraries( @@ -388,12 +394,20 @@ if(BUILD_SERVER) ${ZLIB_LIBRARIES} ${JTHREAD_LIBRARY} ${SQLITE3_LIBRARY} + ${JSON_LIBRARY} ${GETTEXT_LIBRARY} ${LUA_LIBRARY} ${PLATFORM_LIBS} ) + if(USE_CURL) + target_link_libraries( + ${PROJECT_NAME}server + ${CURL_LIBRARY} + ) + endif(USE_CURL) endif(BUILD_SERVER) + # # Set some optimizations and tweaks # @@ -569,4 +583,9 @@ else (LUA_FOUND) add_subdirectory(lua) endif (LUA_FOUND) +if (JSON_FOUND) +else (JSON_FOUND) + add_subdirectory(json) +endif (JSON_FOUND) + #end diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index e3c6b2f7d..d3a455ccb 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -130,8 +130,13 @@ void set_default_settings(Settings *settings) settings->setDefault("media_fetch_threads", "8"); - settings->setDefault("serverlist_url", "servers.minetest.ru/server.list"); + settings->setDefault("serverlist_url", "servers.minetest.net"); settings->setDefault("serverlist_file", "favoriteservers.txt"); + settings->setDefault("server_announce", "false"); + settings->setDefault("server_url", ""); + settings->setDefault("server_address", ""); + settings->setDefault("server_name", ""); + settings->setDefault("server_description", ""); settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "liberationsans.ttf")); settings->setDefault("font_size", "13"); diff --git a/src/guiMainMenu.cpp b/src/guiMainMenu.cpp index 343369643..ca334198f 100644 --- a/src/guiMainMenu.cpp +++ b/src/guiMainMenu.cpp @@ -108,6 +108,7 @@ enum GUI_ID_ENABLE_PARTICLES_CB, GUI_ID_DAMAGE_CB, GUI_ID_CREATIVE_CB, + GUI_ID_PUBLIC_CB, GUI_ID_JOIN_GAME_BUTTON, GUI_ID_CHANGE_KEYS_BUTTON, GUI_ID_DELETE_WORLD_BUTTON, @@ -562,6 +563,14 @@ void GUIMainMenu::regenerateGui(v2u32 screensize) Environment->addCheckBox(m_data->enable_damage, rect, this, GUI_ID_DAMAGE_CB, wgettext("Enable Damage")); } + #if USE_CURL + { + core::rect rect(0, 0, 250, 30); + rect += m_topleft_server + v2s32(30+20+250+20, 60); + Environment->addCheckBox(m_data->enable_public, rect, this, GUI_ID_PUBLIC_CB, + wgettext("Public")); + } + #endif // Delete world button { core::rect rect(0, 0, 130, 30); @@ -841,6 +850,11 @@ void GUIMainMenu::readInput(MainMenuData *dst) if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) dst->enable_damage = ((gui::IGUICheckBox*)e)->isChecked(); } + { + gui::IGUIElement *e = getElementFromId(GUI_ID_PUBLIC_CB); + if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) + dst->enable_public = ((gui::IGUICheckBox*)e)->isChecked(); + } { gui::IGUIElement *e = getElementFromId(GUI_ID_FANCYTREE_CB); if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) @@ -912,8 +926,8 @@ void GUIMainMenu::readInput(MainMenuData *dst) { ServerListSpec server = getServerListSpec(wide_to_narrow(dst->address), wide_to_narrow(dst->port)); - dst->servername = server.name; - dst->serverdescription = server.description; + dst->servername = server["name"].asString(); + dst->serverdescription = server["description"].asString(); } } @@ -1174,13 +1188,31 @@ void GUIMainMenu::updateGuiServerList() i != m_data->servers.end(); i++) { std::string text; - if (i->name != "" && i->description != "") - text = i->name + " (" + i->description + ")"; - else if (i->name !="") - text = i->name; - else - text = i->address + ":" + i->port; + if ((*i)["clients"].asString().size()) + text += (*i)["clients"].asString(); + if ((*i)["clients_max"].asString().size()) + text += "/" + (*i)["clients_max"].asString(); + text += " "; + if ((*i)["version"].asString().size()) + text += (*i)["version"].asString() + " "; + if ((*i)["password"].asString().size()) + text += "*"; + if ((*i)["creative"].asString().size()) + text += "C"; + if ((*i)["damage"].asString().size()) + text += "D"; + if ((*i)["pvp"].asString().size()) + text += "P"; + text += " "; + + if ((*i)["name"] != "" && (*i)["description"] != "") + text += (*i)["name"].asString() + " (" + (*i)["description"].asString() + ")"; + else if ((*i)["name"] !="") + text += (*i)["name"].asString(); + else + text += (*i)["address"].asString() + ":" + (*i)["port"].asString(); + serverlist->addItem(narrow_to_wide(text).c_str()); } } @@ -1191,26 +1223,26 @@ void GUIMainMenu::serverListOnSelected() { gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST); u16 id = serverlist->getSelected(); - if (id < 0) return; + //if (id < 0) return; // u16>0! ((gui::IGUIEditBox*)getElementFromId(GUI_ID_ADDRESS_INPUT)) - ->setText(narrow_to_wide(m_data->servers[id].address).c_str()); + ->setText(narrow_to_wide(m_data->servers[id]["address"].asString()).c_str()); ((gui::IGUIEditBox*)getElementFromId(GUI_ID_PORT_INPUT)) - ->setText(narrow_to_wide(m_data->servers[id].port).c_str()); + ->setText(narrow_to_wide(m_data->servers[id]["port"].asString()).c_str()); } } ServerListSpec GUIMainMenu::getServerListSpec(std::string address, std::string port) { ServerListSpec server; - server.address = address; - server.port = port; + server["address"] = address; + server["port"] = port; for(std::vector::iterator i = m_data->servers.begin(); i != m_data->servers.end(); i++) { - if (i->address == address && i->port == port) + if ((*i)["address"] == address && (*i)["port"] == port) { - server.description = i->description; - server.name = i->name; + server["description"] = (*i)["description"]; + server["name"] = (*i)["name"]; break; } } diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h index 2c657cb23..558a05d59 100644 --- a/src/guiMainMenu.h +++ b/src/guiMainMenu.h @@ -55,6 +55,7 @@ struct MainMenuData // Server options bool creative_mode; bool enable_damage; + bool enable_public; int selected_world; bool simple_singleplayer_mode; // Actions @@ -77,6 +78,7 @@ struct MainMenuData // Server opts creative_mode(false), enable_damage(false), + enable_public(false), selected_world(0), simple_singleplayer_mode(false), // Actions diff --git a/src/json/CMakeLists.txt b/src/json/CMakeLists.txt new file mode 100644 index 000000000..0957799aa --- /dev/null +++ b/src/json/CMakeLists.txt @@ -0,0 +1,14 @@ +if( UNIX ) + set(json_SRCS jsoncpp.cpp) + set(json_platform_LIBS "") +else( UNIX ) + set(json_SRCS jsoncpp.cpp) + set(json_platform_LIBS "") +endif( UNIX ) + +add_library(json ${json_SRCS}) + +target_link_libraries( + json + ${json_platform_LIBS} +) diff --git a/src/json/UPDATING b/src/json/UPDATING new file mode 100644 index 000000000..d00076601 --- /dev/null +++ b/src/json/UPDATING @@ -0,0 +1,16 @@ +#!/bin/sh +cd .. +svn co https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp jsoncpp +svn up jsoncpp +cd jsoncpp +python amalgamate.py +cp -R dist/json .. +cp dist/jsoncpp.cpp ../json + +# maybe you need to patch: +# src/json/jsoncpp.cpp: +# -#include +# +#include "json/json.h" + +#svn export --force https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp/src/lib_json json +#svn export --force https://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk/jsoncpp/include/json json diff --git a/src/json/json.h b/src/json/json.h new file mode 100644 index 000000000..396aafa82 --- /dev/null +++ b/src/json/json.h @@ -0,0 +1,1914 @@ +/// Json-cpp amalgated header (http://jsoncpp.sourceforge.net/). +/// It is intented to be used with #include + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +The author (Baptiste Lepilleur) explicitly disclaims copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is +released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + +#ifndef JSON_AMALGATED_H_INCLUDED +# define JSON_AMALGATED_H_INCLUDED +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +#define JSON_IS_AMALGAMATION + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +# define JSON_CONFIG_H_INCLUDED + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 +/// If defined, indicates that Json specific container should be used +/// (hash table & simple deque container with customizable allocator). +/// THIS FEATURE IS STILL EXPERIMENTAL! There is know bugs: See #3177332 +//# define JSON_VALUE_USE_INTERNAL_MAP 1 +/// Force usage of standard new/malloc based allocator instead of memory pool based allocator. +/// The memory pools allocator used optimization (initializing Value and ValueInternalLink +/// as if it was a POD) that may cause some validation tool to report errors. +/// Only has effects if JSON_VALUE_USE_INTERNAL_MAP is defined. +//# define JSON_USE_SIMPLE_INTERNAL_ALLOCATOR 1 + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +# ifndef JSON_USE_EXCEPTION +# define JSON_USE_EXCEPTION 1 +# endif + +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgated header. +// #define JSON_IS_AMALGAMATION + + +# ifdef JSON_IN_CPPTL +# include +# ifndef JSON_USE_CPPTL +# define JSON_USE_CPPTL 1 +# endif +# endif + +# ifdef JSON_IN_CPPTL +# define JSON_API CPPTL_API +# elif defined(JSON_DLL_BUILD) +# define JSON_API __declspec(dllexport) +# elif defined(JSON_DLL) +# define JSON_API __declspec(dllimport) +# else +# define JSON_API +# endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6 +// Microsoft Visual Studio 6 only support conversion from __int64 to double +// (no conversion from unsigned __int64). +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6 + +#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008 +/// Indicates that the following function is deprecated. +# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif + +#if !defined(JSONCPP_DEPRECATED) +# define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +namespace Json { + typedef int Int; + typedef unsigned int UInt; +# if defined(JSON_NO_INT64) + typedef int LargestInt; + typedef unsigned int LargestUInt; +# undef JSON_HAS_INT64 +# else // if defined(JSON_NO_INT64) + // For Microsoft Visual use specific types as long long is not supported +# if defined(_MSC_VER) // Microsoft Visual Studio + typedef __int64 Int64; + typedef unsigned __int64 UInt64; +# else // if defined(_MSC_VER) // Other platforms, use long long + typedef long long int Int64; + typedef unsigned long long int UInt64; +# endif // if defined(_MSC_VER) + typedef Int64 LargestInt; + typedef UInt64 LargestUInt; +# define JSON_HAS_INT64 +# endif // if defined(JSON_NO_INT64) +} // end namespace Json + + +#endif // JSON_CONFIG_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/config.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +# define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + + // writer.h + class FastWriter; + class StyledWriter; + + // reader.h + class Reader; + + // features.h + class Features; + + // value.h + typedef unsigned int ArrayIndex; + class StaticString; + class Path; + class PathArgument; + class Value; + class ValueIteratorBase; + class ValueIterator; + class ValueConstIterator; +#ifdef JSON_VALUE_USE_INTERNAL_MAP + class ValueMapAllocator; + class ValueInternalLink; + class ValueInternalArray; + class ValueInternalMap; +#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP + +} // namespace Json + + +#endif // JSON_FORWARDS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/forwards.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_FEATURES_H_INCLUDED +# define CPPTL_JSON_FEATURES_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + + /** \brief Configuration passed to reader and writer. + * This configuration object can be used to force the Reader or Writer + * to behave in a standard conforming way. + */ + class JSON_API Features + { + public: + /** \brief A configuration that allows all features and assumes all strings are UTF-8. + * - C & C++ comments are allowed + * - Root object can be any JSON value + * - Assumes Value strings are encoded in UTF-8 + */ + static Features all(); + + /** \brief A configuration that is strictly compatible with the JSON specification. + * - Comments are forbidden. + * - Root object must be either an array or an object value. + * - Assumes Value strings are encoded in UTF-8 + */ + static Features strictMode(); + + /** \brief Initialize the configuration like JsonConfig::allFeatures; + */ + Features(); + + /// \c true if comments are allowed. Default: \c true. + bool allowComments_; + + /// \c true if root must be either an array or an object value. Default: \c false. + bool strictRoot_; + }; + +} // namespace Json + +#endif // CPPTL_JSON_FEATURES_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/features.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_H_INCLUDED +# define CPPTL_JSON_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +# include +# include + +# ifndef JSON_USE_CPPTL_SMALLMAP +# include +# else +# include +# endif +# ifdef JSON_USE_CPPTL +# include +# endif + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + + /** \brief Type of the value held by a Value object. + */ + enum ValueType + { + nullValue = 0, ///< 'null' value + intValue, ///< signed integer value + uintValue, ///< unsigned integer value + realValue, ///< double value + stringValue, ///< UTF-8 string value + booleanValue, ///< bool value + arrayValue, ///< array value (ordered list) + objectValue ///< object value (collection of name/value pairs). + }; + + enum CommentPlacement + { + commentBefore = 0, ///< a comment placed on the line before a value + commentAfterOnSameLine, ///< a comment just after a value on the same line + commentAfter, ///< a comment on the line after a value (only make sense for root value) + numberOfCommentPlacement + }; + +//# ifdef JSON_USE_CPPTL +// typedef CppTL::AnyEnumerator EnumMemberNames; +// typedef CppTL::AnyEnumerator EnumValues; +//# endif + + /** \brief Lightweight wrapper to tag static string. + * + * Value constructor and objectValue member assignement takes advantage of the + * StaticString and avoid the cost of string duplication when storing the + * string or the member name. + * + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + class JSON_API StaticString + { + public: + explicit StaticString( const char *czstring ) + : str_( czstring ) + { + } + + operator const char *() const + { + return str_; + } + + const char *c_str() const + { + return str_; + } + + private: + const char *str_; + }; + + /** \brief Represents a JSON value. + * + * This class is a discriminated union wrapper that can represents a: + * - signed integer [range: Value::minInt - Value::maxInt] + * - unsigned integer (range: 0 - Value::maxUInt) + * - double + * - UTF-8 string + * - boolean + * - 'null' + * - an ordered list of Value + * - collection of name/value pairs (javascript object) + * + * The type of the held value is represented by a #ValueType and + * can be obtained using type(). + * + * values of an #objectValue or #arrayValue can be accessed using operator[]() methods. + * Non const methods will automatically create the a #nullValue element + * if it does not exist. + * The sequence of an #arrayValue will be automatically resize and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtanis default value in the case the required element + * does not exist. + * + * It is possible to iterate over the list of a #objectValue values using + * the getMemberNames() method. + */ + class JSON_API Value + { + friend class ValueIteratorBase; +# ifdef JSON_VALUE_USE_INTERNAL_MAP + friend class ValueInternalLink; + friend class ValueInternalMap; +# endif + public: + typedef std::vector Members; + typedef ValueIterator iterator; + typedef ValueConstIterator const_iterator; + typedef Json::UInt UInt; + typedef Json::Int Int; +# if defined(JSON_HAS_INT64) + typedef Json::UInt64 UInt64; + typedef Json::Int64 Int64; +#endif // defined(JSON_HAS_INT64) + typedef Json::LargestInt LargestInt; + typedef Json::LargestUInt LargestUInt; + typedef Json::ArrayIndex ArrayIndex; + + static const Value null; + /// Minimum signed integer value that can be stored in a Json::Value. + static const LargestInt minLargestInt; + /// Maximum signed integer value that can be stored in a Json::Value. + static const LargestInt maxLargestInt; + /// Maximum unsigned integer value that can be stored in a Json::Value. + static const LargestUInt maxLargestUInt; + + /// Minimum signed int value that can be stored in a Json::Value. + static const Int minInt; + /// Maximum signed int value that can be stored in a Json::Value. + static const Int maxInt; + /// Maximum unsigned int value that can be stored in a Json::Value. + static const UInt maxUInt; + +# if defined(JSON_HAS_INT64) + /// Minimum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 minInt64; + /// Maximum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 maxInt64; + /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. + static const UInt64 maxUInt64; +#endif // defined(JSON_HAS_INT64) + + private: +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION +# ifndef JSON_VALUE_USE_INTERNAL_MAP + class CZString + { + public: + enum DuplicationPolicy + { + noDuplication = 0, + duplicate, + duplicateOnCopy + }; + CZString( ArrayIndex index ); + CZString( const char *cstr, DuplicationPolicy allocate ); + CZString( const CZString &other ); + ~CZString(); + CZString &operator =( const CZString &other ); + bool operator<( const CZString &other ) const; + bool operator==( const CZString &other ) const; + ArrayIndex index() const; + const char *c_str() const; + bool isStaticString() const; + private: + void swap( CZString &other ); + const char *cstr_; + ArrayIndex index_; + }; + + public: +# ifndef JSON_USE_CPPTL_SMALLMAP + typedef std::map ObjectValues; +# else + typedef CppTL::SmallMap ObjectValues; +# endif // ifndef JSON_USE_CPPTL_SMALLMAP +# endif // ifndef JSON_VALUE_USE_INTERNAL_MAP +#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + + public: + /** \brief Create a default Value of the given type. + + This is a very useful constructor. + To create an empty array, pass arrayValue. + To create an empty object, pass objectValue. + Another Value can then be set to this one by assignment. + This is useful since clear() and resize() will not alter types. + + Examples: + \code + Json::Value null_value; // null + Json::Value arr_value(Json::arrayValue); // [] + Json::Value obj_value(Json::objectValue); // {} + \endcode + */ + Value( ValueType type = nullValue ); + Value( Int value ); + Value( UInt value ); +#if defined(JSON_HAS_INT64) + Value( Int64 value ); + Value( UInt64 value ); +#endif // if defined(JSON_HAS_INT64) + Value( double value ); + Value( const char *value ); + Value( const char *beginValue, const char *endValue ); + /** \brief Constructs a value from a static string. + + * Like other value string constructor but do not duplicate the string for + * internal storage. The given string must remain alive after the call to this + * constructor. + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * \endcode + */ + Value( const StaticString &value ); + Value( const std::string &value ); +# ifdef JSON_USE_CPPTL + Value( const CppTL::ConstString &value ); +# endif + Value( bool value ); + Value( const Value &other ); + ~Value(); + + Value &operator=( const Value &other ); + /// Swap values. + /// \note Currently, comments are intentionally not swapped, for + /// both logic and efficiency. + void swap( Value &other ); + + ValueType type() const; + + bool operator <( const Value &other ) const; + bool operator <=( const Value &other ) const; + bool operator >=( const Value &other ) const; + bool operator >( const Value &other ) const; + + bool operator ==( const Value &other ) const; + bool operator !=( const Value &other ) const; + + int compare( const Value &other ) const; + + const char *asCString() const; + std::string asString() const; +# ifdef JSON_USE_CPPTL + CppTL::ConstString asConstString() const; +# endif + Int asInt() const; + UInt asUInt() const; +#if defined(JSON_HAS_INT64) + Int64 asInt64() const; + UInt64 asUInt64() const; +#endif // if defined(JSON_HAS_INT64) + LargestInt asLargestInt() const; + LargestUInt asLargestUInt() const; + float asFloat() const; + double asDouble() const; + bool asBool() const; + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isInt64() const; + bool isUInt() const; + bool isUInt64() const; + bool isIntegral() const; + bool isDouble() const; + bool isNumeric() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + bool isConvertibleTo( ValueType other ) const; + + /// Number of values in array or object + ArrayIndex size() const; + + /// \brief Return true if empty array, empty object, or null; + /// otherwise, false. + bool empty() const; + + /// Return isNull() + bool operator!() const; + + /// Remove all object members and array elements. + /// \pre type() is arrayValue, objectValue, or nullValue + /// \post type() is unchanged + void clear(); + + /// Resize the array to size elements. + /// New elements are initialized to null. + /// May only be called on nullValue or arrayValue. + /// \pre type() is arrayValue or nullValue + /// \post type() is arrayValue + void resize( ArrayIndex size ); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value &operator[]( ArrayIndex index ); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value &operator[]( int index ); + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value &operator[]( ArrayIndex index ) const; + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value &operator[]( int index ) const; + + /// If the array contains at least index+1 elements, returns the element value, + /// otherwise returns defaultValue. + Value get( ArrayIndex index, + const Value &defaultValue ) const; + /// Return true if index < size(). + bool isValidIndex( ArrayIndex index ) const; + /// \brief Append value to array at the end. + /// + /// Equivalent to jsonvalue[jsonvalue.size()] = value; + Value &append( const Value &value ); + + /// Access an object value by name, create a null member if it does not exist. + Value &operator[]( const char *key ); + /// Access an object value by name, returns null if there is no member with that name. + const Value &operator[]( const char *key ) const; + /// Access an object value by name, create a null member if it does not exist. + Value &operator[]( const std::string &key ); + /// Access an object value by name, returns null if there is no member with that name. + const Value &operator[]( const std::string &key ) const; + /** \brief Access an object value by name, create a null member if it does not exist. + + * If the object as no entry for that name, then the member name used to store + * the new entry is not duplicated. + * Example of use: + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + Value &operator[]( const StaticString &key ); +# ifdef JSON_USE_CPPTL + /// Access an object value by name, create a null member if it does not exist. + Value &operator[]( const CppTL::ConstString &key ); + /// Access an object value by name, returns null if there is no member with that name. + const Value &operator[]( const CppTL::ConstString &key ) const; +# endif + /// Return the member named key if it exist, defaultValue otherwise. + Value get( const char *key, + const Value &defaultValue ) const; + /// Return the member named key if it exist, defaultValue otherwise. + Value get( const std::string &key, + const Value &defaultValue ) const; +# ifdef JSON_USE_CPPTL + /// Return the member named key if it exist, defaultValue otherwise. + Value get( const CppTL::ConstString &key, + const Value &defaultValue ) const; +# endif + /// \brief Remove and return the named member. + /// + /// Do nothing if it did not exist. + /// \return the removed Value, or null. + /// \pre type() is objectValue or nullValue + /// \post type() is unchanged + Value removeMember( const char* key ); + /// Same as removeMember(const char*) + Value removeMember( const std::string &key ); + + /// Return true if the object has a member named key. + bool isMember( const char *key ) const; + /// Return true if the object has a member named key. + bool isMember( const std::string &key ) const; +# ifdef JSON_USE_CPPTL + /// Return true if the object has a member named key. + bool isMember( const CppTL::ConstString &key ) const; +# endif + + /// \brief Return a list of the member names. + /// + /// If null, return an empty list. + /// \pre type() is objectValue or nullValue + /// \post if type() was nullValue, it remains nullValue + Members getMemberNames() const; + +//# ifdef JSON_USE_CPPTL +// EnumMemberNames enumMemberNames() const; +// EnumValues enumValues() const; +//# endif + + /// Comments must be //... or /* ... */ + void setComment( const char *comment, + CommentPlacement placement ); + /// Comments must be //... or /* ... */ + void setComment( const std::string &comment, + CommentPlacement placement ); + bool hasComment( CommentPlacement placement ) const; + /// Include delimiters and embedded newlines. + std::string getComment( CommentPlacement placement ) const; + + std::string toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + + private: + Value &resolveReference( const char *key, + bool isStatic ); + +# ifdef JSON_VALUE_USE_INTERNAL_MAP + inline bool isItemAvailable() const + { + return itemIsUsed_ == 0; + } + + inline void setItemUsed( bool isUsed = true ) + { + itemIsUsed_ = isUsed ? 1 : 0; + } + + inline bool isMemberNameStatic() const + { + return memberNameIsStatic_ == 0; + } + + inline void setMemberNameIsStatic( bool isStatic ) + { + memberNameIsStatic_ = isStatic ? 1 : 0; + } +# endif // # ifdef JSON_VALUE_USE_INTERNAL_MAP + + private: + struct CommentInfo + { + CommentInfo(); + ~CommentInfo(); + + void setComment( const char *text ); + + char *comment_; + }; + + //struct MemberNamesTransform + //{ + // typedef const char *result_type; + // const char *operator()( const CZString &name ) const + // { + // return name.c_str(); + // } + //}; + + union ValueHolder + { + LargestInt int_; + LargestUInt uint_; + double real_; + bool bool_; + char *string_; +# ifdef JSON_VALUE_USE_INTERNAL_MAP + ValueInternalArray *array_; + ValueInternalMap *map_; +#else + ObjectValues *map_; +# endif + } value_; + ValueType type_ : 8; + int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. +# ifdef JSON_VALUE_USE_INTERNAL_MAP + unsigned int itemIsUsed_ : 1; // used by the ValueInternalMap container. + int memberNameIsStatic_ : 1; // used by the ValueInternalMap container. +# endif + CommentInfo *comments_; + }; + + + /** \brief Experimental and untested: represents an element of the "path" to access a node. + */ + class PathArgument + { + public: + friend class Path; + + PathArgument(); + PathArgument( ArrayIndex index ); + PathArgument( const char *key ); + PathArgument( const std::string &key ); + + private: + enum Kind + { + kindNone = 0, + kindIndex, + kindKey + }; + std::string key_; + ArrayIndex index_; + Kind kind_; + }; + + /** \brief Experimental and untested: represents a "path" to access a node. + * + * Syntax: + * - "." => root node + * - ".[n]" => elements at index 'n' of root node (an array value) + * - ".name" => member named 'name' of root node (an object value) + * - ".name1.name2.name3" + * - ".[0][1][2].name1[3]" + * - ".%" => member name is provided as parameter + * - ".[%]" => index is provied as parameter + */ + class Path + { + public: + Path( const std::string &path, + const PathArgument &a1 = PathArgument(), + const PathArgument &a2 = PathArgument(), + const PathArgument &a3 = PathArgument(), + const PathArgument &a4 = PathArgument(), + const PathArgument &a5 = PathArgument() ); + + const Value &resolve( const Value &root ) const; + Value resolve( const Value &root, + const Value &defaultValue ) const; + /// Creates the "path" to access the specified node and returns a reference on the node. + Value &make( Value &root ) const; + + private: + typedef std::vector InArgs; + typedef std::vector Args; + + void makePath( const std::string &path, + const InArgs &in ); + void addPathInArg( const std::string &path, + const InArgs &in, + InArgs::const_iterator &itInArg, + PathArgument::Kind kind ); + void invalidPath( const std::string &path, + int location ); + + Args args_; + }; + + + +#ifdef JSON_VALUE_USE_INTERNAL_MAP + /** \brief Allocator to customize Value internal map. + * Below is an example of a simple implementation (default implementation actually + * use memory pool for speed). + * \code + class DefaultValueMapAllocator : public ValueMapAllocator + { + public: // overridden from ValueMapAllocator + virtual ValueInternalMap *newMap() + { + return new ValueInternalMap(); + } + + virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) + { + return new ValueInternalMap( other ); + } + + virtual void destructMap( ValueInternalMap *map ) + { + delete map; + } + + virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) + { + return new ValueInternalLink[size]; + } + + virtual void releaseMapBuckets( ValueInternalLink *links ) + { + delete [] links; + } + + virtual ValueInternalLink *allocateMapLink() + { + return new ValueInternalLink(); + } + + virtual void releaseMapLink( ValueInternalLink *link ) + { + delete link; + } + }; + * \endcode + */ + class JSON_API ValueMapAllocator + { + public: + virtual ~ValueMapAllocator(); + virtual ValueInternalMap *newMap() = 0; + virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) = 0; + virtual void destructMap( ValueInternalMap *map ) = 0; + virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) = 0; + virtual void releaseMapBuckets( ValueInternalLink *links ) = 0; + virtual ValueInternalLink *allocateMapLink() = 0; + virtual void releaseMapLink( ValueInternalLink *link ) = 0; + }; + + /** \brief ValueInternalMap hash-map bucket chain link (for internal use only). + * \internal previous_ & next_ allows for bidirectional traversal. + */ + class JSON_API ValueInternalLink + { + public: + enum { itemPerLink = 6 }; // sizeof(ValueInternalLink) = 128 on 32 bits architecture. + enum InternalFlags { + flagAvailable = 0, + flagUsed = 1 + }; + + ValueInternalLink(); + + ~ValueInternalLink(); + + Value items_[itemPerLink]; + char *keys_[itemPerLink]; + ValueInternalLink *previous_; + ValueInternalLink *next_; + }; + + + /** \brief A linked page based hash-table implementation used internally by Value. + * \internal ValueInternalMap is a tradional bucket based hash-table, with a linked + * list in each bucket to handle collision. There is an addional twist in that + * each node of the collision linked list is a page containing a fixed amount of + * value. This provides a better compromise between memory usage and speed. + * + * Each bucket is made up of a chained list of ValueInternalLink. The last + * link of a given bucket can be found in the 'previous_' field of the following bucket. + * The last link of the last bucket is stored in tailLink_ as it has no following bucket. + * Only the last link of a bucket may contains 'available' item. The last link always + * contains at least one element unless is it the bucket one very first link. + */ + class JSON_API ValueInternalMap + { + friend class ValueIteratorBase; + friend class Value; + public: + typedef unsigned int HashKey; + typedef unsigned int BucketIndex; + +# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + struct IteratorState + { + IteratorState() + : map_(0) + , link_(0) + , itemIndex_(0) + , bucketIndex_(0) + { + } + ValueInternalMap *map_; + ValueInternalLink *link_; + BucketIndex itemIndex_; + BucketIndex bucketIndex_; + }; +# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + + ValueInternalMap(); + ValueInternalMap( const ValueInternalMap &other ); + ValueInternalMap &operator =( const ValueInternalMap &other ); + ~ValueInternalMap(); + + void swap( ValueInternalMap &other ); + + BucketIndex size() const; + + void clear(); + + bool reserveDelta( BucketIndex growth ); + + bool reserve( BucketIndex newItemCount ); + + const Value *find( const char *key ) const; + + Value *find( const char *key ); + + Value &resolveReference( const char *key, + bool isStatic ); + + void remove( const char *key ); + + void doActualRemove( ValueInternalLink *link, + BucketIndex index, + BucketIndex bucketIndex ); + + ValueInternalLink *&getLastLinkInBucket( BucketIndex bucketIndex ); + + Value &setNewItem( const char *key, + bool isStatic, + ValueInternalLink *link, + BucketIndex index ); + + Value &unsafeAdd( const char *key, + bool isStatic, + HashKey hashedKey ); + + HashKey hash( const char *key ) const; + + int compare( const ValueInternalMap &other ) const; + + private: + void makeBeginIterator( IteratorState &it ) const; + void makeEndIterator( IteratorState &it ) const; + static bool equals( const IteratorState &x, const IteratorState &other ); + static void increment( IteratorState &iterator ); + static void incrementBucket( IteratorState &iterator ); + static void decrement( IteratorState &iterator ); + static const char *key( const IteratorState &iterator ); + static const char *key( const IteratorState &iterator, bool &isStatic ); + static Value &value( const IteratorState &iterator ); + static int distance( const IteratorState &x, const IteratorState &y ); + + private: + ValueInternalLink *buckets_; + ValueInternalLink *tailLink_; + BucketIndex bucketsSize_; + BucketIndex itemCount_; + }; + + /** \brief A simplified deque implementation used internally by Value. + * \internal + * It is based on a list of fixed "page", each page contains a fixed number of items. + * Instead of using a linked-list, a array of pointer is used for fast item look-up. + * Look-up for an element is as follow: + * - compute page index: pageIndex = itemIndex / itemsPerPage + * - look-up item in page: pages_[pageIndex][itemIndex % itemsPerPage] + * + * Insertion is amortized constant time (only the array containing the index of pointers + * need to be reallocated when items are appended). + */ + class JSON_API ValueInternalArray + { + friend class Value; + friend class ValueIteratorBase; + public: + enum { itemsPerPage = 8 }; // should be a power of 2 for fast divide and modulo. + typedef Value::ArrayIndex ArrayIndex; + typedef unsigned int PageIndex; + +# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + struct IteratorState // Must be a POD + { + IteratorState() + : array_(0) + , currentPageIndex_(0) + , currentItemIndex_(0) + { + } + ValueInternalArray *array_; + Value **currentPageIndex_; + unsigned int currentItemIndex_; + }; +# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + + ValueInternalArray(); + ValueInternalArray( const ValueInternalArray &other ); + ValueInternalArray &operator =( const ValueInternalArray &other ); + ~ValueInternalArray(); + void swap( ValueInternalArray &other ); + + void clear(); + void resize( ArrayIndex newSize ); + + Value &resolveReference( ArrayIndex index ); + + Value *find( ArrayIndex index ) const; + + ArrayIndex size() const; + + int compare( const ValueInternalArray &other ) const; + + private: + static bool equals( const IteratorState &x, const IteratorState &other ); + static void increment( IteratorState &iterator ); + static void decrement( IteratorState &iterator ); + static Value &dereference( const IteratorState &iterator ); + static Value &unsafeDereference( const IteratorState &iterator ); + static int distance( const IteratorState &x, const IteratorState &y ); + static ArrayIndex indexOf( const IteratorState &iterator ); + void makeBeginIterator( IteratorState &it ) const; + void makeEndIterator( IteratorState &it ) const; + void makeIterator( IteratorState &it, ArrayIndex index ) const; + + void makeIndexValid( ArrayIndex index ); + + Value **pages_; + ArrayIndex size_; + PageIndex pageCount_; + }; + + /** \brief Experimental: do not use. Allocator to customize Value internal array. + * Below is an example of a simple implementation (actual implementation use + * memory pool). + \code +class DefaultValueArrayAllocator : public ValueArrayAllocator +{ +public: // overridden from ValueArrayAllocator + virtual ~DefaultValueArrayAllocator() + { + } + + virtual ValueInternalArray *newArray() + { + return new ValueInternalArray(); + } + + virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) + { + return new ValueInternalArray( other ); + } + + virtual void destruct( ValueInternalArray *array ) + { + delete array; + } + + virtual void reallocateArrayPageIndex( Value **&indexes, + ValueInternalArray::PageIndex &indexCount, + ValueInternalArray::PageIndex minNewIndexCount ) + { + ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; + if ( minNewIndexCount > newIndexCount ) + newIndexCount = minNewIndexCount; + void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); + if ( !newIndexes ) + throw std::bad_alloc(); + indexCount = newIndexCount; + indexes = static_cast( newIndexes ); + } + virtual void releaseArrayPageIndex( Value **indexes, + ValueInternalArray::PageIndex indexCount ) + { + if ( indexes ) + free( indexes ); + } + + virtual Value *allocateArrayPage() + { + return static_cast( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); + } + + virtual void releaseArrayPage( Value *value ) + { + if ( value ) + free( value ); + } +}; + \endcode + */ + class JSON_API ValueArrayAllocator + { + public: + virtual ~ValueArrayAllocator(); + virtual ValueInternalArray *newArray() = 0; + virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) = 0; + virtual void destructArray( ValueInternalArray *array ) = 0; + /** \brief Reallocate array page index. + * Reallocates an array of pointer on each page. + * \param indexes [input] pointer on the current index. May be \c NULL. + * [output] pointer on the new index of at least + * \a minNewIndexCount pages. + * \param indexCount [input] current number of pages in the index. + * [output] number of page the reallocated index can handle. + * \b MUST be >= \a minNewIndexCount. + * \param minNewIndexCount Minimum number of page the new index must be able to + * handle. + */ + virtual void reallocateArrayPageIndex( Value **&indexes, + ValueInternalArray::PageIndex &indexCount, + ValueInternalArray::PageIndex minNewIndexCount ) = 0; + virtual void releaseArrayPageIndex( Value **indexes, + ValueInternalArray::PageIndex indexCount ) = 0; + virtual Value *allocateArrayPage() = 0; + virtual void releaseArrayPage( Value *value ) = 0; + }; +#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP + + + /** \brief base class for Value iterators. + * + */ + class ValueIteratorBase + { + public: + typedef unsigned int size_t; + typedef int difference_type; + typedef ValueIteratorBase SelfType; + + ValueIteratorBase(); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + explicit ValueIteratorBase( const Value::ObjectValues::iterator ¤t ); +#else + ValueIteratorBase( const ValueInternalArray::IteratorState &state ); + ValueIteratorBase( const ValueInternalMap::IteratorState &state ); +#endif + + bool operator ==( const SelfType &other ) const + { + return isEqual( other ); + } + + bool operator !=( const SelfType &other ) const + { + return !isEqual( other ); + } + + difference_type operator -( const SelfType &other ) const + { + return computeDistance( other ); + } + + /// Return either the index or the member name of the referenced value as a Value. + Value key() const; + + /// Return the index of the referenced Value. -1 if it is not an arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value. "" if it is not an objectValue. + const char *memberName() const; + + protected: + Value &deref() const; + + void increment(); + + void decrement(); + + difference_type computeDistance( const SelfType &other ) const; + + bool isEqual( const SelfType &other ) const; + + void copy( const SelfType &other ); + + private: +#ifndef JSON_VALUE_USE_INTERNAL_MAP + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_; +#else + union + { + ValueInternalArray::IteratorState array_; + ValueInternalMap::IteratorState map_; + } iterator_; + bool isArray_; +#endif + }; + + /** \brief const iterator for object and array value. + * + */ + class ValueConstIterator : public ValueIteratorBase + { + friend class Value; + public: + typedef unsigned int size_t; + typedef int difference_type; + typedef const Value &reference; + typedef const Value *pointer; + typedef ValueConstIterator SelfType; + + ValueConstIterator(); + private: + /*! \internal Use by Value to create an iterator. + */ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + explicit ValueConstIterator( const Value::ObjectValues::iterator ¤t ); +#else + ValueConstIterator( const ValueInternalArray::IteratorState &state ); + ValueConstIterator( const ValueInternalMap::IteratorState &state ); +#endif + public: + SelfType &operator =( const ValueIteratorBase &other ); + + SelfType operator++( int ) + { + SelfType temp( *this ); + ++*this; + return temp; + } + + SelfType operator--( int ) + { + SelfType temp( *this ); + --*this; + return temp; + } + + SelfType &operator--() + { + decrement(); + return *this; + } + + SelfType &operator++() + { + increment(); + return *this; + } + + reference operator *() const + { + return deref(); + } + }; + + + /** \brief Iterator for object and array value. + */ + class ValueIterator : public ValueIteratorBase + { + friend class Value; + public: + typedef unsigned int size_t; + typedef int difference_type; + typedef Value &reference; + typedef Value *pointer; + typedef ValueIterator SelfType; + + ValueIterator(); + ValueIterator( const ValueConstIterator &other ); + ValueIterator( const ValueIterator &other ); + private: + /*! \internal Use by Value to create an iterator. + */ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + explicit ValueIterator( const Value::ObjectValues::iterator ¤t ); +#else + ValueIterator( const ValueInternalArray::IteratorState &state ); + ValueIterator( const ValueInternalMap::IteratorState &state ); +#endif + public: + + SelfType &operator =( const SelfType &other ); + + SelfType operator++( int ) + { + SelfType temp( *this ); + ++*this; + return temp; + } + + SelfType operator--( int ) + { + SelfType temp( *this ); + --*this; + return temp; + } + + SelfType &operator--() + { + decrement(); + return *this; + } + + SelfType &operator++() + { + increment(); + return *this; + } + + reference operator *() const + { + return deref(); + } + }; + + +} // namespace Json + + +#endif // CPPTL_JSON_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/value.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_READER_H_INCLUDED +# define CPPTL_JSON_READER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "features.h" +# include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +# include +# include +# include + +namespace Json { + + /** \brief Unserialize a JSON document into a Value. + * + */ + class JSON_API Reader + { + public: + typedef char Char; + typedef const Char *Location; + + /** \brief Constructs a Reader allowing all features + * for parsing. + */ + Reader(); + + /** \brief Constructs a Reader allowing the specified feature set + * for parsing. + */ + Reader( const Features &features ); + + /** \brief Read a Value from a JSON document. + * \param document UTF-8 encoded string containing the document to read. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them back during + * serialization, \c false to discard comments. + * This parameter is ignored if Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an error occurred. + */ + bool parse( const std::string &document, + Value &root, + bool collectComments = true ); + + /** \brief Read a Value from a JSON document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the document to read. + \ Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them back during + * serialization, \c false to discard comments. + * This parameter is ignored if Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an error occurred. + */ + bool parse( const char *beginDoc, const char *endDoc, + Value &root, + bool collectComments = true ); + + /// \brief Parse from input stream. + /// \see Json::operator>>(std::istream&, Json::Value&). + bool parse( std::istream &is, + Value &root, + bool collectComments = true ); + + /** \brief Returns a user friendly string that list errors in the parsed document. + * \return Formatted error message with the list of errors with their location in + * the parsed document. An empty string is returned if no error occurred + * during parsing. + * \deprecated Use getFormattedErrorMessages() instead (typo fix). + */ + JSONCPP_DEPRECATED("Use getFormattedErrorMessages instead") + std::string getFormatedErrorMessages() const; + + /** \brief Returns a user friendly string that list errors in the parsed document. + * \return Formatted error message with the list of errors with their location in + * the parsed document. An empty string is returned if no error occurred + * during parsing. + */ + std::string getFormattedErrorMessages() const; + + private: + enum TokenType + { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token + { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo + { + public: + Token token_; + std::string message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool expectToken( TokenType type, Token &token, const char *message ); + bool readToken( Token &token ); + void skipSpaces(); + bool match( Location pattern, + int patternLength ); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + void readNumber(); + bool readValue(); + bool readObject( Token &token ); + bool readArray( Token &token ); + bool decodeNumber( Token &token ); + bool decodeString( Token &token ); + bool decodeString( Token &token, std::string &decoded ); + bool decodeDouble( Token &token ); + bool decodeUnicodeCodePoint( Token &token, + Location ¤t, + Location end, + unsigned int &unicode ); + bool decodeUnicodeEscapeSequence( Token &token, + Location ¤t, + Location end, + unsigned int &unicode ); + bool addError( const std::string &message, + Token &token, + Location extra = 0 ); + bool recoverFromError( TokenType skipUntilToken ); + bool addErrorAndRecover( const std::string &message, + Token &token, + TokenType skipUntilToken ); + void skipUntilSpace(); + Value ¤tValue(); + Char getNextChar(); + void getLocationLineAndColumn( Location location, + int &line, + int &column ) const; + std::string getLocationLineAndColumn( Location location ) const; + void addComment( Location begin, + Location end, + CommentPlacement placement ); + void skipCommentTokens( Token &token ); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + std::string document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value *lastValue_; + std::string commentsBefore_; + Features features_; + bool collectComments_; + }; + + /** \brief Read from 'sin' into 'root'. + + Always keep comments from the input JSON. + + This can be used to read a file into a particular sub-object. + For example: + \code + Json::Value root; + cin >> root["dir"]["file"]; + cout << root; + \endcode + Result: + \verbatim + { + "dir": { + "file": { + // The input stream JSON would be nested here. + } + } + } + \endverbatim + \throw std::exception on parse error. + \see Json::operator<<() + */ + std::istream& operator>>( std::istream&, Value& ); + +} // namespace Json + +#endif // CPPTL_JSON_READER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/reader.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_WRITER_H_INCLUDED +# define JSON_WRITER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +# include +# include + +namespace Json { + + class Value; + + /** \brief Abstract class for writers. + */ + class JSON_API Writer + { + public: + virtual ~Writer(); + + virtual std::string write( const Value &root ) = 0; + }; + + /** \brief Outputs a Value in JSON format without formatting (not human friendly). + * + * The JSON document is written in a single line. It is not intended for 'human' consumption, + * but may be usefull to support feature such as RPC where bandwith is limited. + * \sa Reader, Value + */ + class JSON_API FastWriter : public Writer + { + public: + FastWriter(); + virtual ~FastWriter(){} + + void enableYAMLCompatibility(); + + /** \brief Drop the "null" string from the writer's output for nullValues. + * Strictly speaking, this is not valid JSON. But when the output is being + * fed to a browser's Javascript, it makes for smaller output and the + * browser can handle the output just fine. + */ + void dropNullPlaceholders(); + + public: // overridden from Writer + virtual std::string write( const Value &root ); + + private: + void writeValue( const Value &value ); + + std::string document_; + bool yamlCompatiblityEnabled_; + bool dropNullPlaceholders_; + }; + + /** \brief Writes a Value in JSON format in a human friendly way. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value types, + * and all the values fit on one lines, then print the array on a single line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their #CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + */ + class JSON_API StyledWriter: public Writer + { + public: + StyledWriter(); + virtual ~StyledWriter(){} + + public: // overridden from Writer + /** \brief Serialize a Value in JSON format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + virtual std::string write( const Value &root ); + + private: + void writeValue( const Value &value ); + void writeArrayValue( const Value &value ); + bool isMultineArray( const Value &value ); + void pushValue( const std::string &value ); + void writeIndent(); + void writeWithIndent( const std::string &value ); + void indent(); + void unindent(); + void writeCommentBeforeValue( const Value &root ); + void writeCommentAfterValueOnSameLine( const Value &root ); + bool hasCommentForValue( const Value &value ); + static std::string normalizeEOL( const std::string &text ); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::string document_; + std::string indentString_; + int rightMargin_; + int indentSize_; + bool addChildValues_; + }; + + /** \brief Writes a Value in JSON format in a human friendly way, + to a stream rather than to a string. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value types, + * and all the values fit on one lines, then print the array on a single line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their #CommentPlacement. + * + * \param indentation Each level will be indented by this amount extra. + * \sa Reader, Value, Value::setComment() + */ + class JSON_API StyledStreamWriter + { + public: + StyledStreamWriter( std::string indentation="\t" ); + ~StyledStreamWriter(){} + + public: + /** \brief Serialize a Value in JSON format. + * \param out Stream to write to. (Can be ostringstream, e.g.) + * \param root Value to serialize. + * \note There is no point in deriving from Writer, since write() should not return a value. + */ + void write( std::ostream &out, const Value &root ); + + private: + void writeValue( const Value &value ); + void writeArrayValue( const Value &value ); + bool isMultineArray( const Value &value ); + void pushValue( const std::string &value ); + void writeIndent(); + void writeWithIndent( const std::string &value ); + void indent(); + void unindent(); + void writeCommentBeforeValue( const Value &root ); + void writeCommentAfterValueOnSameLine( const Value &root ); + bool hasCommentForValue( const Value &value ); + static std::string normalizeEOL( const std::string &text ); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::ostream* document_; + std::string indentString_; + int rightMargin_; + std::string indentation_; + bool addChildValues_; + }; + +# if defined(JSON_HAS_INT64) + std::string JSON_API valueToString( Int value ); + std::string JSON_API valueToString( UInt value ); +# endif // if defined(JSON_HAS_INT64) + std::string JSON_API valueToString( LargestInt value ); + std::string JSON_API valueToString( LargestUInt value ); + std::string JSON_API valueToString( double value ); + std::string JSON_API valueToString( bool value ); + std::string JSON_API valueToQuotedString( const char *value ); + + /// \brief Output using the StyledStreamWriter. + /// \see Json::operator>>() + std::ostream& operator<<( std::ostream&, const Value &root ); + +} // namespace Json + + + +#endif // JSON_WRITER_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/writer.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED +# define CPPTL_JSON_ASSERTIONS_H_INCLUDED + +#include + +#if !defined(JSON_IS_AMALGAMATION) +# include +#endif // if !defined(JSON_IS_AMALGAMATION) + +#if JSON_USE_EXCEPTION +#define JSON_ASSERT( condition ) assert( condition ); // @todo <= change this into an exception throw +#define JSON_FAIL_MESSAGE( message ) throw std::runtime_error( message ); +#else // JSON_USE_EXCEPTION +#define JSON_ASSERT( condition ) assert( condition ); + +// The call to assert() will show the failure message in debug builds. In +// release bugs we write to invalid memory in order to crash hard, so that a +// debugger or crash reporter gets the chance to take over. We still call exit() +// afterward in order to tell the compiler that this macro doesn't return. +#define JSON_FAIL_MESSAGE( message ) { assert(false && message); strcpy(reinterpret_cast(666), message); exit(123); } + +#endif + +#define JSON_ASSERT_MESSAGE( condition, message ) if (!( condition )) { JSON_FAIL_MESSAGE( message ) } + +#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: include/json/assertions.h +// ////////////////////////////////////////////////////////////////////// + + + + + +#endif //ifndef JSON_AMALGATED_H_INCLUDED diff --git a/src/json/jsoncpp.cpp b/src/json/jsoncpp.cpp new file mode 100644 index 000000000..dbe5a349f --- /dev/null +++ b/src/json/jsoncpp.cpp @@ -0,0 +1,4367 @@ +/// Json-cpp amalgated source (http://jsoncpp.sourceforge.net/). +/// It is intented to be used with #include + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + +/* +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +The author (Baptiste Lepilleur) explicitly disclaims copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is +released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + +*/ + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: LICENSE +// ////////////////////////////////////////////////////////////////////// + + + + + + +#include "json/json.h" + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED +# define LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +/* This header provides common string manipulation support, such as UTF-8, + * portable conversion from/to string... + * + * It is an internal header that must not be exposed. + */ + +namespace Json { + +/// Converts a unicode code-point to UTF-8. +static inline std::string +codePointToUTF8(unsigned int cp) +{ + std::string result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) + { + result.resize(1); + result[0] = static_cast(cp); + } + else if (cp <= 0x7FF) + { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } + else if (cp <= 0xFFFF) + { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = 0x80 | static_cast((0x3f & (cp >> 6))); + result[0] = 0xE0 | static_cast((0xf & (cp >> 12))); + } + else if (cp <= 0x10FFFF) + { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + return result; +} + + +/// Returns true if ch is a control character (in range [0,32[). +static inline bool +isControlCharacter(char ch) +{ + return ch > 0 && ch <= 0x1F; +} + + +enum { + /// Constant that specify the size of the buffer that must be passed to uintToString. + uintToStringBufferSize = 3*sizeof(LargestUInt)+1 +}; + +// Defines a char buffer for use with uintToString(). +typedef char UIntToStringBuffer[uintToStringBufferSize]; + + +/** Converts an unsigned integer to string. + * @param value Unsigned interger to convert to string + * @param current Input/Output string buffer. + * Must have at least uintToStringBufferSize chars free. + */ +static inline void +uintToString( LargestUInt value, + char *¤t ) +{ + *--current = 0; + do + { + *--current = char(value % 10) + '0'; + value /= 10; + } + while ( value != 0 ); +} + +} // namespace Json { + +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_tool.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +# include +# include +# include +# include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. +#endif + +namespace Json { + +// Implementation of class Features +// //////////////////////////////// + +Features::Features() + : allowComments_( true ) + , strictRoot_( false ) +{ +} + + +Features +Features::all() +{ + return Features(); +} + + +Features +Features::strictMode() +{ + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + return features; +} + +// Implementation of class Reader +// //////////////////////////////// + + +static inline bool +in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 ) +{ + return c == c1 || c == c2 || c == c3 || c == c4; +} + +static inline bool +in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5 ) +{ + return c == c1 || c == c2 || c == c3 || c == c4 || c == c5; +} + + +static bool +containsNewLine( Reader::Location begin, + Reader::Location end ) +{ + for ( ;begin < end; ++begin ) + if ( *begin == '\n' || *begin == '\r' ) + return true; + return false; +} + + +// Class Reader +// ////////////////////////////////////////////////////////////////// + +Reader::Reader() + : errors_(), + document_(), + begin_(), + end_(), + current_(), + lastValueEnd_(), + lastValue_(), + commentsBefore_(), + features_( Features::all() ), + collectComments_() +{ +} + + +Reader::Reader( const Features &features ) + : errors_(), + document_(), + begin_(), + end_(), + current_(), + lastValueEnd_(), + lastValue_(), + commentsBefore_(), + features_( features ), + collectComments_() +{ +} + + +bool +Reader::parse( const std::string &document, + Value &root, + bool collectComments ) +{ + document_ = document; + const char *begin = document_.c_str(); + const char *end = begin + document_.length(); + return parse( begin, end, root, collectComments ); +} + + +bool +Reader::parse( std::istream& sin, + Value &root, + bool collectComments ) +{ + //std::istream_iterator begin(sin); + //std::istream_iterator end; + // Those would allow streamed input from a file, if parse() were a + // template function. + + // Since std::string is reference-counted, this at least does not + // create an extra copy. + std::string doc; + std::getline(sin, doc, (char)EOF); + return parse( doc, root, collectComments ); +} + +bool +Reader::parse( const char *beginDoc, const char *endDoc, + Value &root, + bool collectComments ) +{ + if ( !features_.allowComments_ ) + { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = 0; + lastValue_ = 0; + commentsBefore_ = ""; + errors_.clear(); + while ( !nodes_.empty() ) + nodes_.pop(); + nodes_.push( &root ); + + bool successful = readValue(); + Token token; + skipCommentTokens( token ); + if ( collectComments_ && !commentsBefore_.empty() ) + root.setComment( commentsBefore_, commentAfter ); + if ( features_.strictRoot_ ) + { + if ( !root.isArray() && !root.isObject() ) + { + // Set error location to start of doc, ideally should be first token found in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( "A valid JSON document must be either an array or an object value.", + token ); + return false; + } + } + return successful; +} + + +bool +Reader::readValue() +{ + Token token; + skipCommentTokens( token ); + bool successful = true; + + if ( collectComments_ && !commentsBefore_.empty() ) + { + currentValue().setComment( commentsBefore_, commentBefore ); + commentsBefore_ = ""; + } + + + switch ( token.type_ ) + { + case tokenObjectBegin: + successful = readObject( token ); + break; + case tokenArrayBegin: + successful = readArray( token ); + break; + case tokenNumber: + successful = decodeNumber( token ); + break; + case tokenString: + successful = decodeString( token ); + break; + case tokenTrue: + currentValue() = true; + break; + case tokenFalse: + currentValue() = false; + break; + case tokenNull: + currentValue() = Value(); + break; + default: + return addError( "Syntax error: value, object or array expected.", token ); + } + + if ( collectComments_ ) + { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; +} + + +void +Reader::skipCommentTokens( Token &token ) +{ + if ( features_.allowComments_ ) + { + do + { + readToken( token ); + } + while ( token.type_ == tokenComment ); + } + else + { + readToken( token ); + } +} + + +bool +Reader::expectToken( TokenType type, Token &token, const char *message ) +{ + readToken( token ); + if ( token.type_ != type ) + return addError( message, token ); + return true; +} + + +bool +Reader::readToken( Token &token ) +{ + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch ( c ) + { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match( "rue", 3 ); + break; + case 'f': + token.type_ = tokenFalse; + ok = match( "alse", 4 ); + break; + case 'n': + token.type_ = tokenNull; + ok = match( "ull", 3 ); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if ( !ok ) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + + +void +Reader::skipSpaces() +{ + while ( current_ != end_ ) + { + Char c = *current_; + if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) + ++current_; + else + break; + } +} + + +bool +Reader::match( Location pattern, + int patternLength ) +{ + if ( end_ - current_ < patternLength ) + return false; + int index = patternLength; + while ( index-- ) + if ( current_[index] != pattern[index] ) + return false; + current_ += patternLength; + return true; +} + + +bool +Reader::readComment() +{ + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if ( c == '*' ) + successful = readCStyleComment(); + else if ( c == '/' ) + successful = readCppStyleComment(); + if ( !successful ) + return false; + + if ( collectComments_ ) + { + CommentPlacement placement = commentBefore; + if ( lastValueEnd_ && !containsNewLine( lastValueEnd_, commentBegin ) ) + { + if ( c != '*' || !containsNewLine( commentBegin, current_ ) ) + placement = commentAfterOnSameLine; + } + + addComment( commentBegin, current_, placement ); + } + return true; +} + + +void +Reader::addComment( Location begin, + Location end, + CommentPlacement placement ) +{ + assert( collectComments_ ); + if ( placement == commentAfterOnSameLine ) + { + assert( lastValue_ != 0 ); + lastValue_->setComment( std::string( begin, end ), placement ); + } + else + { + if ( !commentsBefore_.empty() ) + commentsBefore_ += "\n"; + commentsBefore_ += std::string( begin, end ); + } +} + + +bool +Reader::readCStyleComment() +{ + while ( current_ != end_ ) + { + Char c = getNextChar(); + if ( c == '*' && *current_ == '/' ) + break; + } + return getNextChar() == '/'; +} + + +bool +Reader::readCppStyleComment() +{ + while ( current_ != end_ ) + { + Char c = getNextChar(); + if ( c == '\r' || c == '\n' ) + break; + } + return true; +} + + +void +Reader::readNumber() +{ + while ( current_ != end_ ) + { + if ( !(*current_ >= '0' && *current_ <= '9') && + !in( *current_, '.', 'e', 'E', '+', '-' ) ) + break; + ++current_; + } +} + +bool +Reader::readString() +{ + Char c = 0; + while ( current_ != end_ ) + { + c = getNextChar(); + if ( c == '\\' ) + getNextChar(); + else if ( c == '"' ) + break; + } + return c == '"'; +} + + +bool +Reader::readObject( Token &/*tokenStart*/ ) +{ + Token tokenName; + std::string name; + currentValue() = Value( objectValue ); + while ( readToken( tokenName ) ) + { + bool initialTokenOk = true; + while ( tokenName.type_ == tokenComment && initialTokenOk ) + initialTokenOk = readToken( tokenName ); + if ( !initialTokenOk ) + break; + if ( tokenName.type_ == tokenObjectEnd && name.empty() ) // empty object + return true; + if ( tokenName.type_ != tokenString ) + break; + + name = ""; + if ( !decodeString( tokenName, name ) ) + return recoverFromError( tokenObjectEnd ); + + Token colon; + if ( !readToken( colon ) || colon.type_ != tokenMemberSeparator ) + { + return addErrorAndRecover( "Missing ':' after object member name", + colon, + tokenObjectEnd ); + } + Value &value = currentValue()[ name ]; + nodes_.push( &value ); + bool ok = readValue(); + nodes_.pop(); + if ( !ok ) // error already set + return recoverFromError( tokenObjectEnd ); + + Token comma; + if ( !readToken( comma ) + || ( comma.type_ != tokenObjectEnd && + comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment ) ) + { + return addErrorAndRecover( "Missing ',' or '}' in object declaration", + comma, + tokenObjectEnd ); + } + bool finalizeTokenOk = true; + while ( comma.type_ == tokenComment && + finalizeTokenOk ) + finalizeTokenOk = readToken( comma ); + if ( comma.type_ == tokenObjectEnd ) + return true; + } + return addErrorAndRecover( "Missing '}' or object member name", + tokenName, + tokenObjectEnd ); +} + + +bool +Reader::readArray( Token &/*tokenStart*/ ) +{ + currentValue() = Value( arrayValue ); + skipSpaces(); + if ( *current_ == ']' ) // empty array + { + Token endArray; + readToken( endArray ); + return true; + } + int index = 0; + for (;;) + { + Value &value = currentValue()[ index++ ]; + nodes_.push( &value ); + bool ok = readValue(); + nodes_.pop(); + if ( !ok ) // error already set + return recoverFromError( tokenArrayEnd ); + + Token token; + // Accept Comment after last item in the array. + ok = readToken( token ); + while ( token.type_ == tokenComment && ok ) + { + ok = readToken( token ); + } + bool badTokenType = ( token.type_ != tokenArraySeparator && + token.type_ != tokenArrayEnd ); + if ( !ok || badTokenType ) + { + return addErrorAndRecover( "Missing ',' or ']' in array declaration", + token, + tokenArrayEnd ); + } + if ( token.type_ == tokenArrayEnd ) + break; + } + return true; +} + + +bool +Reader::decodeNumber( Token &token ) +{ + bool isDouble = false; + for ( Location inspect = token.start_; inspect != token.end_; ++inspect ) + { + isDouble = isDouble + || in( *inspect, '.', 'e', 'E', '+' ) + || ( *inspect == '-' && inspect != token.start_ ); + } + if ( isDouble ) + return decodeDouble( token ); + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if ( isNegative ) + ++current; + Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(-Value::minLargestInt) + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::LargestUInt value = 0; + while ( current < token.end_ ) + { + Char c = *current++; + if ( c < '0' || c > '9' ) + return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); + Value::UInt digit(c - '0'); + if ( value >= threshold ) + { + // We've hit or exceeded the max value divided by 10 (rounded down). If + // a) we've only just touched the limit, b) this is the last digit, and + // c) it's small enough to fit in that rounding delta, we're okay. + // Otherwise treat this number as a double to avoid overflow. + if (value > threshold || + current != token.end_ || + digit > maxIntegerValue % 10) + { + return decodeDouble( token ); + } + } + value = value * 10 + digit; + } + if ( isNegative ) + currentValue() = -Value::LargestInt( value ); + else if ( value <= Value::LargestUInt(Value::maxInt) ) + currentValue() = Value::LargestInt( value ); + else + currentValue() = value; + return true; +} + + +bool +Reader::decodeDouble( Token &token ) +{ + double value = 0; + const int bufferSize = 32; + int count; + int length = int(token.end_ - token.start_); + + // Sanity check to avoid buffer overflow exploits. + if (length < 0) { + return addError( "Unable to parse token length", token ); + } + + // Avoid using a string constant for the format control string given to + // sscanf, as this can cause hard to debug crashes on OS X. See here for more + // info: + // + // http://developer.apple.com/library/mac/#DOCUMENTATION/DeveloperTools/gcc-4.0.1/gcc/Incompatibilities.html + char format[] = "%lf"; + + if ( length <= bufferSize ) + { + Char buffer[bufferSize+1]; + memcpy( buffer, token.start_, length ); + buffer[length] = 0; + count = sscanf( buffer, format, &value ); + } + else + { + std::string buffer( token.start_, token.end_ ); + count = sscanf( buffer.c_str(), format, &value ); + } + + if ( count != 1 ) + return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); + currentValue() = value; + return true; +} + + +bool +Reader::decodeString( Token &token ) +{ + std::string decoded; + if ( !decodeString( token, decoded ) ) + return false; + currentValue() = decoded; + return true; +} + + +bool +Reader::decodeString( Token &token, std::string &decoded ) +{ + decoded.reserve( token.end_ - token.start_ - 2 ); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while ( current != end ) + { + Char c = *current++; + if ( c == '"' ) + break; + else if ( c == '\\' ) + { + if ( current == end ) + return addError( "Empty escape sequence in string", token, current ); + Char escape = *current++; + switch ( escape ) + { + case '"': decoded += '"'; break; + case '/': decoded += '/'; break; + case '\\': decoded += '\\'; break; + case 'b': decoded += '\b'; break; + case 'f': decoded += '\f'; break; + case 'n': decoded += '\n'; break; + case 'r': decoded += '\r'; break; + case 't': decoded += '\t'; break; + case 'u': + { + unsigned int unicode; + if ( !decodeUnicodeCodePoint( token, current, end, unicode ) ) + return false; + decoded += codePointToUTF8(unicode); + } + break; + default: + return addError( "Bad escape sequence in string", token, current ); + } + } + else + { + decoded += c; + } + } + return true; +} + +bool +Reader::decodeUnicodeCodePoint( Token &token, + Location ¤t, + Location end, + unsigned int &unicode ) +{ + + if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) ) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) + { + // surrogate pairs + if (end - current < 6) + return addError( "additional six characters expected to parse unicode surrogate pair.", token, current ); + unsigned int surrogatePair; + if (*(current++) == '\\' && *(current++)== 'u') + { + if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair )) + { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } + else + return false; + } + else + return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current ); + } + return true; +} + +bool +Reader::decodeUnicodeEscapeSequence( Token &token, + Location ¤t, + Location end, + unsigned int &unicode ) +{ + if ( end - current < 4 ) + return addError( "Bad unicode escape sequence in string: four digits expected.", token, current ); + unicode = 0; + for ( int index =0; index < 4; ++index ) + { + Char c = *current++; + unicode *= 16; + if ( c >= '0' && c <= '9' ) + unicode += c - '0'; + else if ( c >= 'a' && c <= 'f' ) + unicode += c - 'a' + 10; + else if ( c >= 'A' && c <= 'F' ) + unicode += c - 'A' + 10; + else + return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current ); + } + return true; +} + + +bool +Reader::addError( const std::string &message, + Token &token, + Location extra ) +{ + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back( info ); + return false; +} + + +bool +Reader::recoverFromError( TokenType skipUntilToken ) +{ + int errorCount = int(errors_.size()); + Token skip; + for (;;) + { + if ( !readToken(skip) ) + errors_.resize( errorCount ); // discard errors caused by recovery + if ( skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream ) + break; + } + errors_.resize( errorCount ); + return false; +} + + +bool +Reader::addErrorAndRecover( const std::string &message, + Token &token, + TokenType skipUntilToken ) +{ + addError( message, token ); + return recoverFromError( skipUntilToken ); +} + + +Value & +Reader::currentValue() +{ + return *(nodes_.top()); +} + + +Reader::Char +Reader::getNextChar() +{ + if ( current_ == end_ ) + return 0; + return *current_++; +} + + +void +Reader::getLocationLineAndColumn( Location location, + int &line, + int &column ) const +{ + Location current = begin_; + Location lastLineStart = current; + line = 0; + while ( current < location && current != end_ ) + { + Char c = *current++; + if ( c == '\r' ) + { + if ( *current == '\n' ) + ++current; + lastLineStart = current; + ++line; + } + else if ( c == '\n' ) + { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + + +std::string +Reader::getLocationLineAndColumn( Location location ) const +{ + int line, column; + getLocationLineAndColumn( location, line, column ); + char buffer[18+16+16+1]; + sprintf( buffer, "Line %d, Column %d", line, column ); + return buffer; +} + + +// Deprecated. Preserved for backward compatibility +std::string +Reader::getFormatedErrorMessages() const +{ + return getFormattedErrorMessages(); +} + + +std::string +Reader::getFormattedErrorMessages() const +{ + std::string formattedMessage; + for ( Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError ) + { + const ErrorInfo &error = *itError; + formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if ( error.extra_ ) + formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n"; + } + return formattedMessage; +} + + +std::istream& operator>>( std::istream &sin, Value &root ) +{ + Json::Reader reader; + bool ok = reader.parse(sin, root, true); + if (!ok) { + fprintf( + stderr, + "Error from reader: %s", + reader.getFormattedErrorMessages().c_str()); + + JSON_FAIL_MESSAGE("reader error"); + } + return sin; +} + + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_reader.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_batchallocator.h +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSONCPP_BATCHALLOCATOR_H_INCLUDED +# define JSONCPP_BATCHALLOCATOR_H_INCLUDED + +# include +# include + +# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + +namespace Json { + +/* Fast memory allocator. + * + * This memory allocator allocates memory for a batch of object (specified by + * the page size, the number of object in each page). + * + * It does not allow the destruction of a single object. All the allocated objects + * can be destroyed at once. The memory can be either released or reused for future + * allocation. + * + * The in-place new operator must be used to construct the object using the pointer + * returned by allocate. + */ +template +class BatchAllocator +{ +public: + BatchAllocator( unsigned int objectsPerPage = 255 ) + : freeHead_( 0 ) + , objectsPerPage_( objectsPerPage ) + { +// printf( "Size: %d => %s\n", sizeof(AllocatedType), typeid(AllocatedType).name() ); + assert( sizeof(AllocatedType) * objectPerAllocation >= sizeof(AllocatedType *) ); // We must be able to store a slist in the object free space. + assert( objectsPerPage >= 16 ); + batches_ = allocateBatch( 0 ); // allocated a dummy page + currentBatch_ = batches_; + } + + ~BatchAllocator() + { + for ( BatchInfo *batch = batches_; batch; ) + { + BatchInfo *nextBatch = batch->next_; + free( batch ); + batch = nextBatch; + } + } + + /// allocate space for an array of objectPerAllocation object. + /// @warning it is the responsability of the caller to call objects constructors. + AllocatedType *allocate() + { + if ( freeHead_ ) // returns node from free list. + { + AllocatedType *object = freeHead_; + freeHead_ = *(AllocatedType **)object; + return object; + } + if ( currentBatch_->used_ == currentBatch_->end_ ) + { + currentBatch_ = currentBatch_->next_; + while ( currentBatch_ && currentBatch_->used_ == currentBatch_->end_ ) + currentBatch_ = currentBatch_->next_; + + if ( !currentBatch_ ) // no free batch found, allocate a new one + { + currentBatch_ = allocateBatch( objectsPerPage_ ); + currentBatch_->next_ = batches_; // insert at the head of the list + batches_ = currentBatch_; + } + } + AllocatedType *allocated = currentBatch_->used_; + currentBatch_->used_ += objectPerAllocation; + return allocated; + } + + /// Release the object. + /// @warning it is the responsability of the caller to actually destruct the object. + void release( AllocatedType *object ) + { + assert( object != 0 ); + *(AllocatedType **)object = freeHead_; + freeHead_ = object; + } + +private: + struct BatchInfo + { + BatchInfo *next_; + AllocatedType *used_; + AllocatedType *end_; + AllocatedType buffer_[objectPerAllocation]; + }; + + // disabled copy constructor and assignement operator. + BatchAllocator( const BatchAllocator & ); + void operator =( const BatchAllocator &); + + static BatchInfo *allocateBatch( unsigned int objectsPerPage ) + { + const unsigned int mallocSize = sizeof(BatchInfo) - sizeof(AllocatedType)* objectPerAllocation + + sizeof(AllocatedType) * objectPerAllocation * objectsPerPage; + BatchInfo *batch = static_cast( malloc( mallocSize ) ); + batch->next_ = 0; + batch->used_ = batch->buffer_; + batch->end_ = batch->buffer_ + objectsPerPage; + return batch; + } + + BatchInfo *batches_; + BatchInfo *currentBatch_; + /// Head of a single linked list within the allocated space of freeed object + AllocatedType *freeHead_; + unsigned int objectsPerPage_; +}; + + +} // namespace Json + +# endif // ifndef JSONCPP_DOC_INCLUDE_IMPLEMENTATION + +#endif // JSONCPP_BATCHALLOCATOR_H_INCLUDED + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_batchallocator.h +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIteratorBase +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIteratorBase::ValueIteratorBase() +#ifndef JSON_VALUE_USE_INTERNAL_MAP + : current_() + , isNull_( true ) +{ +} +#else + : isArray_( true ) + , isNull_( true ) +{ + iterator_.array_ = ValueInternalArray::IteratorState(); +} +#endif + + +#ifndef JSON_VALUE_USE_INTERNAL_MAP +ValueIteratorBase::ValueIteratorBase( const Value::ObjectValues::iterator ¤t ) + : current_( current ) + , isNull_( false ) +{ +} +#else +ValueIteratorBase::ValueIteratorBase( const ValueInternalArray::IteratorState &state ) + : isArray_( true ) +{ + iterator_.array_ = state; +} + + +ValueIteratorBase::ValueIteratorBase( const ValueInternalMap::IteratorState &state ) + : isArray_( false ) +{ + iterator_.map_ = state; +} +#endif + +Value & +ValueIteratorBase::deref() const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + return current_->second; +#else + if ( isArray_ ) + return ValueInternalArray::dereference( iterator_.array_ ); + return ValueInternalMap::value( iterator_.map_ ); +#endif +} + + +void +ValueIteratorBase::increment() +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + ++current_; +#else + if ( isArray_ ) + ValueInternalArray::increment( iterator_.array_ ); + ValueInternalMap::increment( iterator_.map_ ); +#endif +} + + +void +ValueIteratorBase::decrement() +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + --current_; +#else + if ( isArray_ ) + ValueInternalArray::decrement( iterator_.array_ ); + ValueInternalMap::decrement( iterator_.map_ ); +#endif +} + + +ValueIteratorBase::difference_type +ValueIteratorBase::computeDistance( const SelfType &other ) const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP +# ifdef JSON_USE_CPPTL_SMALLMAP + return current_ - other.current_; +# else + // Iterator for null value are initialized using the default + // constructor, which initialize current_ to the default + // std::map::iterator. As begin() and end() are two instance + // of the default std::map::iterator, they can not be compared. + // To allow this, we handle this comparison specifically. + if ( isNull_ && other.isNull_ ) + { + return 0; + } + + + // Usage of std::distance is not portable (does not compile with Sun Studio 12 RogueWave STL, + // which is the one used by default). + // Using a portable hand-made version for non random iterator instead: + // return difference_type( std::distance( current_, other.current_ ) ); + difference_type myDistance = 0; + for ( Value::ObjectValues::iterator it = current_; it != other.current_; ++it ) + { + ++myDistance; + } + return myDistance; +# endif +#else + if ( isArray_ ) + return ValueInternalArray::distance( iterator_.array_, other.iterator_.array_ ); + return ValueInternalMap::distance( iterator_.map_, other.iterator_.map_ ); +#endif +} + + +bool +ValueIteratorBase::isEqual( const SelfType &other ) const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + if ( isNull_ ) + { + return other.isNull_; + } + return current_ == other.current_; +#else + if ( isArray_ ) + return ValueInternalArray::equals( iterator_.array_, other.iterator_.array_ ); + return ValueInternalMap::equals( iterator_.map_, other.iterator_.map_ ); +#endif +} + + +void +ValueIteratorBase::copy( const SelfType &other ) +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + current_ = other.current_; +#else + if ( isArray_ ) + iterator_.array_ = other.iterator_.array_; + iterator_.map_ = other.iterator_.map_; +#endif +} + + +Value +ValueIteratorBase::key() const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + const Value::CZString czstring = (*current_).first; + if ( czstring.c_str() ) + { + if ( czstring.isStaticString() ) + return Value( StaticString( czstring.c_str() ) ); + return Value( czstring.c_str() ); + } + return Value( czstring.index() ); +#else + if ( isArray_ ) + return Value( ValueInternalArray::indexOf( iterator_.array_ ) ); + bool isStatic; + const char *memberName = ValueInternalMap::key( iterator_.map_, isStatic ); + if ( isStatic ) + return Value( StaticString( memberName ) ); + return Value( memberName ); +#endif +} + + +UInt +ValueIteratorBase::index() const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + const Value::CZString czstring = (*current_).first; + if ( !czstring.c_str() ) + return czstring.index(); + return Value::UInt( -1 ); +#else + if ( isArray_ ) + return Value::UInt( ValueInternalArray::indexOf( iterator_.array_ ) ); + return Value::UInt( -1 ); +#endif +} + + +const char * +ValueIteratorBase::memberName() const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + const char *name = (*current_).first.c_str(); + return name ? name : ""; +#else + if ( !isArray_ ) + return ValueInternalMap::key( iterator_.map_ ); + return ""; +#endif +} + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueConstIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueConstIterator::ValueConstIterator() +{ +} + + +#ifndef JSON_VALUE_USE_INTERNAL_MAP +ValueConstIterator::ValueConstIterator( const Value::ObjectValues::iterator ¤t ) + : ValueIteratorBase( current ) +{ +} +#else +ValueConstIterator::ValueConstIterator( const ValueInternalArray::IteratorState &state ) + : ValueIteratorBase( state ) +{ +} + +ValueConstIterator::ValueConstIterator( const ValueInternalMap::IteratorState &state ) + : ValueIteratorBase( state ) +{ +} +#endif + +ValueConstIterator & +ValueConstIterator::operator =( const ValueIteratorBase &other ) +{ + copy( other ); + return *this; +} + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIterator::ValueIterator() +{ +} + + +#ifndef JSON_VALUE_USE_INTERNAL_MAP +ValueIterator::ValueIterator( const Value::ObjectValues::iterator ¤t ) + : ValueIteratorBase( current ) +{ +} +#else +ValueIterator::ValueIterator( const ValueInternalArray::IteratorState &state ) + : ValueIteratorBase( state ) +{ +} + +ValueIterator::ValueIterator( const ValueInternalMap::IteratorState &state ) + : ValueIteratorBase( state ) +{ +} +#endif + +ValueIterator::ValueIterator( const ValueConstIterator &other ) + : ValueIteratorBase( other ) +{ +} + +ValueIterator::ValueIterator( const ValueIterator &other ) + : ValueIteratorBase( other ) +{ +} + +ValueIterator & +ValueIterator::operator =( const SelfType &other ) +{ + copy( other ); + return *this; +} + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_valueiterator.inl +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +# include +# include +# include +# ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR +# include "json_batchallocator.h" +# endif // #ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#ifdef JSON_USE_CPPTL +# include +#endif +#include // size_t + +#define JSON_ASSERT_UNREACHABLE assert( false ) + +namespace Json { + +const Value Value::null; +const Int Value::minInt = Int( ~(UInt(-1)/2) ); +const Int Value::maxInt = Int( UInt(-1)/2 ); +const UInt Value::maxUInt = UInt(-1); +# if defined(JSON_HAS_INT64) +const Int64 Value::minInt64 = Int64( ~(UInt64(-1)/2) ); +const Int64 Value::maxInt64 = Int64( UInt64(-1)/2 ); +const UInt64 Value::maxUInt64 = UInt64(-1); +// The constant is hard-coded because some compiler have trouble +// converting Value::maxUInt64 to a double correctly (AIX/xlC). +// Assumes that UInt64 is a 64 bits integer. +static const double maxUInt64AsDouble = 18446744073709551615.0; +#endif // defined(JSON_HAS_INT64) +const LargestInt Value::minLargestInt = LargestInt( ~(LargestUInt(-1)/2) ); +const LargestInt Value::maxLargestInt = LargestInt( LargestUInt(-1)/2 ); +const LargestUInt Value::maxLargestUInt = LargestUInt(-1); + + +/// Unknown size marker +static const unsigned int unknown = (unsigned)-1; + +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +template +static inline bool InRange(double d, T min, U max) { + return d >= min && d <= max; +} +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) +static inline double integerToDouble( Json::UInt64 value ) +{ + return static_cast( Int64(value/2) ) * 2.0 + Int64(value & 1); +} + +template +static inline double integerToDouble( T value ) +{ + return static_cast( value ); +} + +template +static inline bool InRange(double d, T min, U max) { + return d >= integerToDouble(min) && d <= integerToDouble(max); +} +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + + +/** Duplicates the specified string value. + * @param value Pointer to the string to duplicate. Must be zero-terminated if + * length is "unknown". + * @param length Length of the value. if equals to unknown, then it will be + * computed using strlen(value). + * @return Pointer on the duplicate instance of string. + */ +static inline char * +duplicateStringValue( const char *value, + unsigned int length = unknown ) +{ + if ( length == unknown ) + length = (unsigned int)strlen(value); + + // Avoid an integer overflow in the call to malloc below by limiting length + // to a sane value. + if (length >= (unsigned)Value::maxInt) + length = Value::maxInt - 1; + + char *newString = static_cast( malloc( length + 1 ) ); + JSON_ASSERT_MESSAGE( newString != 0, "Failed to allocate string value buffer" ); + memcpy( newString, value, length ); + newString[length] = 0; + return newString; +} + + +/** Free the string duplicated by duplicateStringValue(). + */ +static inline void +releaseStringValue( char *value ) +{ + if ( value ) + free( value ); +} + +} // namespace Json + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ValueInternals... +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +#if !defined(JSON_IS_AMALGAMATION) +# ifdef JSON_VALUE_USE_INTERNAL_MAP +# include "json_internalarray.inl" +# include "json_internalmap.inl" +# endif // JSON_VALUE_USE_INTERNAL_MAP + +# include "json_valueiterator.inl" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CommentInfo +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + + +Value::CommentInfo::CommentInfo() + : comment_( 0 ) +{ +} + +Value::CommentInfo::~CommentInfo() +{ + if ( comment_ ) + releaseStringValue( comment_ ); +} + + +void +Value::CommentInfo::setComment( const char *text ) +{ + if ( comment_ ) + releaseStringValue( comment_ ); + JSON_ASSERT( text != 0 ); + JSON_ASSERT_MESSAGE( text[0]=='\0' || text[0]=='/', "Comments must start with /"); + // It seems that /**/ style comments are acceptable as well. + comment_ = duplicateStringValue( text ); +} + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CZString +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +# ifndef JSON_VALUE_USE_INTERNAL_MAP + +// Notes: index_ indicates if the string was allocated when +// a string is stored. + +Value::CZString::CZString( ArrayIndex index ) + : cstr_( 0 ) + , index_( index ) +{ +} + +Value::CZString::CZString( const char *cstr, DuplicationPolicy allocate ) + : cstr_( allocate == duplicate ? duplicateStringValue(cstr) + : cstr ) + , index_( allocate ) +{ +} + +Value::CZString::CZString( const CZString &other ) +: cstr_( other.index_ != noDuplication && other.cstr_ != 0 + ? duplicateStringValue( other.cstr_ ) + : other.cstr_ ) + , index_( other.cstr_ ? (other.index_ == noDuplication ? noDuplication : duplicate) + : other.index_ ) +{ +} + +Value::CZString::~CZString() +{ + if ( cstr_ && index_ == duplicate ) + releaseStringValue( const_cast( cstr_ ) ); +} + +void +Value::CZString::swap( CZString &other ) +{ + std::swap( cstr_, other.cstr_ ); + std::swap( index_, other.index_ ); +} + +Value::CZString & +Value::CZString::operator =( const CZString &other ) +{ + CZString temp( other ); + swap( temp ); + return *this; +} + +bool +Value::CZString::operator<( const CZString &other ) const +{ + if ( cstr_ ) + return strcmp( cstr_, other.cstr_ ) < 0; + return index_ < other.index_; +} + +bool +Value::CZString::operator==( const CZString &other ) const +{ + if ( cstr_ ) + return strcmp( cstr_, other.cstr_ ) == 0; + return index_ == other.index_; +} + + +ArrayIndex +Value::CZString::index() const +{ + return index_; +} + + +const char * +Value::CZString::c_str() const +{ + return cstr_; +} + +bool +Value::CZString::isStaticString() const +{ + return index_ == noDuplication; +} + +#endif // ifndef JSON_VALUE_USE_INTERNAL_MAP + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::Value +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +/*! \internal Default constructor initialization must be equivalent to: + * memset( this, 0, sizeof(Value) ) + * This optimization is used in ValueInternalMap fast allocator. + */ +Value::Value( ValueType type ) + : type_( type ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + switch ( type ) + { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + value_.string_ = 0; + break; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; +#else + case arrayValue: + value_.array_ = arrayAllocator()->newArray(); + break; + case objectValue: + value_.map_ = mapAllocator()->newMap(); + break; +#endif + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + + +Value::Value( UInt value ) + : type_( uintValue ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.uint_ = value; +} + +Value::Value( Int value ) + : type_( intValue ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.int_ = value; +} + + +# if defined(JSON_HAS_INT64) +Value::Value( Int64 value ) + : type_( intValue ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.int_ = value; +} + + +Value::Value( UInt64 value ) + : type_( uintValue ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.uint_ = value; +} +#endif // defined(JSON_HAS_INT64) + +Value::Value( double value ) + : type_( realValue ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.real_ = value; +} + +Value::Value( const char *value ) + : type_( stringValue ) + , allocated_( true ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.string_ = duplicateStringValue( value ); +} + + +Value::Value( const char *beginValue, + const char *endValue ) + : type_( stringValue ) + , allocated_( true ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.string_ = duplicateStringValue( beginValue, + (unsigned int)(endValue - beginValue) ); +} + + +Value::Value( const std::string &value ) + : type_( stringValue ) + , allocated_( true ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.string_ = duplicateStringValue( value.c_str(), + (unsigned int)value.length() ); + +} + +Value::Value( const StaticString &value ) + : type_( stringValue ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.string_ = const_cast( value.c_str() ); +} + + +# ifdef JSON_USE_CPPTL +Value::Value( const CppTL::ConstString &value ) + : type_( stringValue ) + , allocated_( true ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.string_ = duplicateStringValue( value, value.length() ); +} +# endif + +Value::Value( bool value ) + : type_( booleanValue ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + value_.bool_ = value; +} + + +Value::Value( const Value &other ) + : type_( other.type_ ) + , allocated_( false ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif + , comments_( 0 ) +{ + switch ( type_ ) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if ( other.value_.string_ ) + { + value_.string_ = duplicateStringValue( other.value_.string_ ); + allocated_ = true; + } + else + value_.string_ = 0; + break; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues( *other.value_.map_ ); + break; +#else + case arrayValue: + value_.array_ = arrayAllocator()->newArrayCopy( *other.value_.array_ ); + break; + case objectValue: + value_.map_ = mapAllocator()->newMapCopy( *other.value_.map_ ); + break; +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + if ( other.comments_ ) + { + comments_ = new CommentInfo[numberOfCommentPlacement]; + for ( int comment =0; comment < numberOfCommentPlacement; ++comment ) + { + const CommentInfo &otherComment = other.comments_[comment]; + if ( otherComment.comment_ ) + comments_[comment].setComment( otherComment.comment_ ); + } + } +} + + +Value::~Value() +{ + switch ( type_ ) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if ( allocated_ ) + releaseStringValue( value_.string_ ); + break; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + delete value_.map_; + break; +#else + case arrayValue: + arrayAllocator()->destructArray( value_.array_ ); + break; + case objectValue: + mapAllocator()->destructMap( value_.map_ ); + break; +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + + if ( comments_ ) + delete[] comments_; +} + +Value & +Value::operator=( const Value &other ) +{ + Value temp( other ); + swap( temp ); + return *this; +} + +void +Value::swap( Value &other ) +{ + ValueType temp = type_; + type_ = other.type_; + other.type_ = temp; + std::swap( value_, other.value_ ); + int temp2 = allocated_; + allocated_ = other.allocated_; + other.allocated_ = temp2; +} + +ValueType +Value::type() const +{ + return type_; +} + + +int +Value::compare( const Value &other ) const +{ + if ( *this < other ) + return -1; + if ( *this > other ) + return 1; + return 0; +} + + +bool +Value::operator <( const Value &other ) const +{ + int typeDelta = type_ - other.type_; + if ( typeDelta ) + return typeDelta < 0 ? true : false; + switch ( type_ ) + { + case nullValue: + return false; + case intValue: + return value_.int_ < other.value_.int_; + case uintValue: + return value_.uint_ < other.value_.uint_; + case realValue: + return value_.real_ < other.value_.real_; + case booleanValue: + return value_.bool_ < other.value_.bool_; + case stringValue: + return ( value_.string_ == 0 && other.value_.string_ ) + || ( other.value_.string_ + && value_.string_ + && strcmp( value_.string_, other.value_.string_ ) < 0 ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + { + int delta = int( value_.map_->size() - other.value_.map_->size() ); + if ( delta ) + return delta < 0; + return (*value_.map_) < (*other.value_.map_); + } +#else + case arrayValue: + return value_.array_->compare( *(other.value_.array_) ) < 0; + case objectValue: + return value_.map_->compare( *(other.value_.map_) ) < 0; +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool +Value::operator <=( const Value &other ) const +{ + return !(other < *this); +} + +bool +Value::operator >=( const Value &other ) const +{ + return !(*this < other); +} + +bool +Value::operator >( const Value &other ) const +{ + return other < *this; +} + +bool +Value::operator ==( const Value &other ) const +{ + //if ( type_ != other.type_ ) + // GCC 2.95.3 says: + // attempt to take address of bit-field structure member `Json::Value::type_' + // Beats me, but a temp solves the problem. + int temp = other.type_; + if ( type_ != temp ) + return false; + switch ( type_ ) + { + case nullValue: + return true; + case intValue: + return value_.int_ == other.value_.int_; + case uintValue: + return value_.uint_ == other.value_.uint_; + case realValue: + return value_.real_ == other.value_.real_; + case booleanValue: + return value_.bool_ == other.value_.bool_; + case stringValue: + return ( value_.string_ == other.value_.string_ ) + || ( other.value_.string_ + && value_.string_ + && strcmp( value_.string_, other.value_.string_ ) == 0 ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() + && (*value_.map_) == (*other.value_.map_); +#else + case arrayValue: + return value_.array_->compare( *(other.value_.array_) ) == 0; + case objectValue: + return value_.map_->compare( *(other.value_.map_) ) == 0; +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool +Value::operator !=( const Value &other ) const +{ + return !( *this == other ); +} + +const char * +Value::asCString() const +{ + JSON_ASSERT( type_ == stringValue ); + return value_.string_; +} + + +std::string +Value::asString() const +{ + switch ( type_ ) + { + case nullValue: + return ""; + case stringValue: + return value_.string_ ? value_.string_ : ""; + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + return valueToString( value_.int_ ); + case uintValue: + return valueToString( value_.uint_ ); + case realValue: + return valueToString( value_.real_ ); + default: + JSON_FAIL_MESSAGE( "Type is not convertible to string" ); + } +} + +# ifdef JSON_USE_CPPTL +CppTL::ConstString +Value::asConstString() const +{ + return CppTL::ConstString( asString().c_str() ); +} +# endif + + +Value::Int +Value::asInt() const +{ + switch ( type_ ) + { + case intValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestInt out of Int range"); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt(), "LargestUInt out of Int range"); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt, maxInt), "double out of Int range"); + return Int(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int."); +} + + +Value::UInt +Value::asUInt() const +{ + switch ( type_ ) + { + case intValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestInt out of UInt range"); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isUInt(), "LargestUInt out of UInt range"); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt), "double out of UInt range"); + return UInt( value_.real_ ); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt."); +} + + +# if defined(JSON_HAS_INT64) + +Value::Int64 +Value::asInt64() const +{ + switch ( type_ ) + { + case intValue: + return Int64(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE(isInt64(), "LargestUInt out of Int64 range"); + return Int64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, minInt64, maxInt64), "double out of Int64 range"); + return Int64(value_.real_); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to Int64."); +} + + +Value::UInt64 +Value::asUInt64() const +{ + switch ( type_ ) + { + case intValue: + JSON_ASSERT_MESSAGE(isUInt64(), "LargestInt out of UInt64 range"); + return UInt64(value_.int_); + case uintValue: + return UInt64(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE(InRange(value_.real_, 0, maxUInt64), "double out of UInt64 range"); + return UInt64( value_.real_ ); + case nullValue: + return 0; + case booleanValue: + return value_.bool_ ? 1 : 0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to UInt64."); +} +# endif // if defined(JSON_HAS_INT64) + + +LargestInt +Value::asLargestInt() const +{ +#if defined(JSON_NO_INT64) + return asInt(); +#else + return asInt64(); +#endif +} + + +LargestUInt +Value::asLargestUInt() const +{ +#if defined(JSON_NO_INT64) + return asUInt(); +#else + return asUInt64(); +#endif +} + + +double +Value::asDouble() const +{ + switch ( type_ ) + { + case intValue: + return static_cast( value_.int_ ); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast( value_.uint_ ); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble( value_.uint_ ); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to double."); +} + +float +Value::asFloat() const +{ + switch ( type_ ) + { + case intValue: + return static_cast( value_.int_ ); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast( value_.uint_ ); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return integerToDouble( value_.uint_ ); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast( value_.real_ ); + case nullValue: + return 0.0; + case booleanValue: + return value_.bool_ ? 1.0f : 0.0f; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to float."); +} + +bool +Value::asBool() const +{ + switch ( type_ ) + { + case booleanValue: + return value_.bool_; + case nullValue: + return false; + case intValue: + return value_.int_ ? true : false; + case uintValue: + return value_.uint_ ? true : false; + case realValue: + return value_.real_ ? true : false; + default: + break; + } + JSON_FAIL_MESSAGE("Value is not convertible to bool."); +} + + +bool +Value::isConvertibleTo( ValueType other ) const +{ + switch ( other ) + { + case nullValue: + return ( isNumeric() && asDouble() == 0.0 ) + || ( type_ == booleanValue && value_.bool_ == false ) + || ( type_ == stringValue && asString() == "" ) + || ( type_ == arrayValue && value_.map_->size() == 0 ) + || ( type_ == objectValue && value_.map_->size() == 0 ) + || type_ == nullValue; + case intValue: + return isInt() + || (type_ == realValue && InRange(value_.real_, minInt, maxInt)) + || type_ == booleanValue + || type_ == nullValue; + case uintValue: + return isUInt() + || (type_ == realValue && InRange(value_.real_, 0, maxUInt)) + || type_ == booleanValue + || type_ == nullValue; + case realValue: + return isNumeric() + || type_ == booleanValue + || type_ == nullValue; + case booleanValue: + return isNumeric() + || type_ == booleanValue + || type_ == nullValue; + case stringValue: + return isNumeric() + || type_ == booleanValue + || type_ == stringValue + || type_ == nullValue; + case arrayValue: + return type_ == arrayValue + || type_ == nullValue; + case objectValue: + return type_ == objectValue + || type_ == nullValue; + } + JSON_ASSERT_UNREACHABLE; + return false; +} + + +/// Number of values in array or object +ArrayIndex +Value::size() const +{ + switch ( type_ ) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + case stringValue: + return 0; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: // size of the array is highest index + 1 + if ( !value_.map_->empty() ) + { + ObjectValues::const_iterator itLast = value_.map_->end(); + --itLast; + return (*itLast).first.index()+1; + } + return 0; + case objectValue: + return ArrayIndex( value_.map_->size() ); +#else + case arrayValue: + return Int( value_.array_->size() ); + case objectValue: + return Int( value_.map_->size() ); +#endif + } + JSON_ASSERT_UNREACHABLE; + return 0; // unreachable; +} + + +bool +Value::empty() const +{ + if ( isNull() || isArray() || isObject() ) + return size() == 0u; + else + return false; +} + + +bool +Value::operator!() const +{ + return isNull(); +} + + +void +Value::clear() +{ + JSON_ASSERT( type_ == nullValue || type_ == arrayValue || type_ == objectValue ); + + switch ( type_ ) + { +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + value_.map_->clear(); + break; +#else + case arrayValue: + value_.array_->clear(); + break; + case objectValue: + value_.map_->clear(); + break; +#endif + default: + break; + } +} + +void +Value::resize( ArrayIndex newSize ) +{ + JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); + if ( type_ == nullValue ) + *this = Value( arrayValue ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + ArrayIndex oldSize = size(); + if ( newSize == 0 ) + clear(); + else if ( newSize > oldSize ) + (*this)[ newSize - 1 ]; + else + { + for ( ArrayIndex index = newSize; index < oldSize; ++index ) + { + value_.map_->erase( index ); + } + assert( size() == newSize ); + } +#else + value_.array_->resize( newSize ); +#endif +} + + +Value & +Value::operator[]( ArrayIndex index ) +{ + JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); + if ( type_ == nullValue ) + *this = Value( arrayValue ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString key( index ); + ObjectValues::iterator it = value_.map_->lower_bound( key ); + if ( it != value_.map_->end() && (*it).first == key ) + return (*it).second; + + ObjectValues::value_type defaultValue( key, null ); + it = value_.map_->insert( it, defaultValue ); + return (*it).second; +#else + return value_.array_->resolveReference( index ); +#endif +} + + +Value & +Value::operator[]( int index ) +{ + JSON_ASSERT( index >= 0 ); + return (*this)[ ArrayIndex(index) ]; +} + + +const Value & +Value::operator[]( ArrayIndex index ) const +{ + JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); + if ( type_ == nullValue ) + return null; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString key( index ); + ObjectValues::const_iterator it = value_.map_->find( key ); + if ( it == value_.map_->end() ) + return null; + return (*it).second; +#else + Value *value = value_.array_->find( index ); + return value ? *value : null; +#endif +} + + +const Value & +Value::operator[]( int index ) const +{ + JSON_ASSERT( index >= 0 ); + return (*this)[ ArrayIndex(index) ]; +} + + +Value & +Value::operator[]( const char *key ) +{ + return resolveReference( key, false ); +} + + +Value & +Value::resolveReference( const char *key, + bool isStatic ) +{ + JSON_ASSERT( type_ == nullValue || type_ == objectValue ); + if ( type_ == nullValue ) + *this = Value( objectValue ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString actualKey( key, isStatic ? CZString::noDuplication + : CZString::duplicateOnCopy ); + ObjectValues::iterator it = value_.map_->lower_bound( actualKey ); + if ( it != value_.map_->end() && (*it).first == actualKey ) + return (*it).second; + + ObjectValues::value_type defaultValue( actualKey, null ); + it = value_.map_->insert( it, defaultValue ); + Value &value = (*it).second; + return value; +#else + return value_.map_->resolveReference( key, isStatic ); +#endif +} + + +Value +Value::get( ArrayIndex index, + const Value &defaultValue ) const +{ + const Value *value = &((*this)[index]); + return value == &null ? defaultValue : *value; +} + + +bool +Value::isValidIndex( ArrayIndex index ) const +{ + return index < size(); +} + + + +const Value & +Value::operator[]( const char *key ) const +{ + JSON_ASSERT( type_ == nullValue || type_ == objectValue ); + if ( type_ == nullValue ) + return null; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString actualKey( key, CZString::noDuplication ); + ObjectValues::const_iterator it = value_.map_->find( actualKey ); + if ( it == value_.map_->end() ) + return null; + return (*it).second; +#else + const Value *value = value_.map_->find( key ); + return value ? *value : null; +#endif +} + + +Value & +Value::operator[]( const std::string &key ) +{ + return (*this)[ key.c_str() ]; +} + + +const Value & +Value::operator[]( const std::string &key ) const +{ + return (*this)[ key.c_str() ]; +} + +Value & +Value::operator[]( const StaticString &key ) +{ + return resolveReference( key, true ); +} + + +# ifdef JSON_USE_CPPTL +Value & +Value::operator[]( const CppTL::ConstString &key ) +{ + return (*this)[ key.c_str() ]; +} + + +const Value & +Value::operator[]( const CppTL::ConstString &key ) const +{ + return (*this)[ key.c_str() ]; +} +# endif + + +Value & +Value::append( const Value &value ) +{ + return (*this)[size()] = value; +} + + +Value +Value::get( const char *key, + const Value &defaultValue ) const +{ + const Value *value = &((*this)[key]); + return value == &null ? defaultValue : *value; +} + + +Value +Value::get( const std::string &key, + const Value &defaultValue ) const +{ + return get( key.c_str(), defaultValue ); +} + +Value +Value::removeMember( const char* key ) +{ + JSON_ASSERT( type_ == nullValue || type_ == objectValue ); + if ( type_ == nullValue ) + return null; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString actualKey( key, CZString::noDuplication ); + ObjectValues::iterator it = value_.map_->find( actualKey ); + if ( it == value_.map_->end() ) + return null; + Value old(it->second); + value_.map_->erase(it); + return old; +#else + Value *value = value_.map_->find( key ); + if (value){ + Value old(*value); + value_.map_.remove( key ); + return old; + } else { + return null; + } +#endif +} + +Value +Value::removeMember( const std::string &key ) +{ + return removeMember( key.c_str() ); +} + +# ifdef JSON_USE_CPPTL +Value +Value::get( const CppTL::ConstString &key, + const Value &defaultValue ) const +{ + return get( key.c_str(), defaultValue ); +} +# endif + +bool +Value::isMember( const char *key ) const +{ + const Value *value = &((*this)[key]); + return value != &null; +} + + +bool +Value::isMember( const std::string &key ) const +{ + return isMember( key.c_str() ); +} + + +# ifdef JSON_USE_CPPTL +bool +Value::isMember( const CppTL::ConstString &key ) const +{ + return isMember( key.c_str() ); +} +#endif + +Value::Members +Value::getMemberNames() const +{ + JSON_ASSERT( type_ == nullValue || type_ == objectValue ); + if ( type_ == nullValue ) + return Value::Members(); + Members members; + members.reserve( value_.map_->size() ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for ( ; it != itEnd; ++it ) + members.push_back( std::string( (*it).first.c_str() ) ); +#else + ValueInternalMap::IteratorState it; + ValueInternalMap::IteratorState itEnd; + value_.map_->makeBeginIterator( it ); + value_.map_->makeEndIterator( itEnd ); + for ( ; !ValueInternalMap::equals( it, itEnd ); ValueInternalMap::increment(it) ) + members.push_back( std::string( ValueInternalMap::key( it ) ) ); +#endif + return members; +} +// +//# ifdef JSON_USE_CPPTL +//EnumMemberNames +//Value::enumMemberNames() const +//{ +// if ( type_ == objectValue ) +// { +// return CppTL::Enum::any( CppTL::Enum::transform( +// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), +// MemberNamesTransform() ) ); +// } +// return EnumMemberNames(); +//} +// +// +//EnumValues +//Value::enumValues() const +//{ +// if ( type_ == objectValue || type_ == arrayValue ) +// return CppTL::Enum::anyValues( *(value_.map_), +// CppTL::Type() ); +// return EnumValues(); +//} +// +//# endif + +static bool IsIntegral(double d) { + double integral_part; + return modf(d, &integral_part) == 0.0; +} + + +bool +Value::isNull() const +{ + return type_ == nullValue; +} + + +bool +Value::isBool() const +{ + return type_ == booleanValue; +} + + +bool +Value::isInt() const +{ + switch ( type_ ) + { + case intValue: + return value_.int_ >= minInt && value_.int_ <= maxInt; + case uintValue: + return value_.uint_ <= UInt(maxInt); + case realValue: + return value_.real_ >= minInt && + value_.real_ <= maxInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + + +bool +Value::isUInt() const +{ + switch ( type_ ) + { + case intValue: + return value_.int_ >= 0 && LargestUInt(value_.int_) <= LargestUInt(maxUInt); + case uintValue: + return value_.uint_ <= maxUInt; + case realValue: + return value_.real_ >= 0 && + value_.real_ <= maxUInt && + IsIntegral(value_.real_); + default: + break; + } + return false; +} + +bool +Value::isInt64() const +{ +# if defined(JSON_HAS_INT64) + switch ( type_ ) + { + case intValue: + return true; + case uintValue: + return value_.uint_ <= UInt64(maxInt64); + case realValue: + // Note that maxInt64 (= 2^63 - 1) is not exactly representable as a + // double, so double(maxInt64) will be rounded up to 2^63. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= double(minInt64) && + value_.real_ < double(maxInt64) && + IsIntegral(value_.real_); + default: + break; + } +# endif // JSON_HAS_INT64 + return false; +} + +bool +Value::isUInt64() const +{ +# if defined(JSON_HAS_INT64) + switch ( type_ ) + { + case intValue: + return value_.int_ >= 0; + case uintValue: + return true; + case realValue: + // Note that maxUInt64 (= 2^64 - 1) is not exactly representable as a + // double, so double(maxUInt64) will be rounded up to 2^64. Therefore we + // require the value to be strictly less than the limit. + return value_.real_ >= 0 && + value_.real_ < maxUInt64AsDouble && + IsIntegral(value_.real_); + default: + break; + } +# endif // JSON_HAS_INT64 + return false; +} + + +bool +Value::isIntegral() const +{ +#if defined(JSON_HAS_INT64) + return isInt64() || isUInt64(); +#else + return isInt() || isUInt(); +#endif +} + + +bool +Value::isDouble() const +{ + return type_ == realValue || isIntegral(); +} + + +bool +Value::isNumeric() const +{ + return isIntegral() || isDouble(); +} + + +bool +Value::isString() const +{ + return type_ == stringValue; +} + + +bool +Value::isArray() const +{ + return type_ == arrayValue; +} + + +bool +Value::isObject() const +{ + return type_ == objectValue; +} + + +void +Value::setComment( const char *comment, + CommentPlacement placement ) +{ + if ( !comments_ ) + comments_ = new CommentInfo[numberOfCommentPlacement]; + comments_[placement].setComment( comment ); +} + + +void +Value::setComment( const std::string &comment, + CommentPlacement placement ) +{ + setComment( comment.c_str(), placement ); +} + + +bool +Value::hasComment( CommentPlacement placement ) const +{ + return comments_ != 0 && comments_[placement].comment_ != 0; +} + +std::string +Value::getComment( CommentPlacement placement ) const +{ + if ( hasComment(placement) ) + return comments_[placement].comment_; + return ""; +} + + +std::string +Value::toStyledString() const +{ + StyledWriter writer; + return writer.write( *this ); +} + + +Value::const_iterator +Value::begin() const +{ + switch ( type_ ) + { +#ifdef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + if ( value_.array_ ) + { + ValueInternalArray::IteratorState it; + value_.array_->makeBeginIterator( it ); + return const_iterator( it ); + } + break; + case objectValue: + if ( value_.map_ ) + { + ValueInternalMap::IteratorState it; + value_.map_->makeBeginIterator( it ); + return const_iterator( it ); + } + break; +#else + case arrayValue: + case objectValue: + if ( value_.map_ ) + return const_iterator( value_.map_->begin() ); + break; +#endif + default: + break; + } + return const_iterator(); +} + +Value::const_iterator +Value::end() const +{ + switch ( type_ ) + { +#ifdef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + if ( value_.array_ ) + { + ValueInternalArray::IteratorState it; + value_.array_->makeEndIterator( it ); + return const_iterator( it ); + } + break; + case objectValue: + if ( value_.map_ ) + { + ValueInternalMap::IteratorState it; + value_.map_->makeEndIterator( it ); + return const_iterator( it ); + } + break; +#else + case arrayValue: + case objectValue: + if ( value_.map_ ) + return const_iterator( value_.map_->end() ); + break; +#endif + default: + break; + } + return const_iterator(); +} + + +Value::iterator +Value::begin() +{ + switch ( type_ ) + { +#ifdef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + if ( value_.array_ ) + { + ValueInternalArray::IteratorState it; + value_.array_->makeBeginIterator( it ); + return iterator( it ); + } + break; + case objectValue: + if ( value_.map_ ) + { + ValueInternalMap::IteratorState it; + value_.map_->makeBeginIterator( it ); + return iterator( it ); + } + break; +#else + case arrayValue: + case objectValue: + if ( value_.map_ ) + return iterator( value_.map_->begin() ); + break; +#endif + default: + break; + } + return iterator(); +} + +Value::iterator +Value::end() +{ + switch ( type_ ) + { +#ifdef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + if ( value_.array_ ) + { + ValueInternalArray::IteratorState it; + value_.array_->makeEndIterator( it ); + return iterator( it ); + } + break; + case objectValue: + if ( value_.map_ ) + { + ValueInternalMap::IteratorState it; + value_.map_->makeEndIterator( it ); + return iterator( it ); + } + break; +#else + case arrayValue: + case objectValue: + if ( value_.map_ ) + return iterator( value_.map_->end() ); + break; +#endif + default: + break; + } + return iterator(); +} + + +// class PathArgument +// ////////////////////////////////////////////////////////////////// + +PathArgument::PathArgument() + : key_() + , index_() + , kind_( kindNone ) +{ +} + + +PathArgument::PathArgument( ArrayIndex index ) + : key_() + , index_( index ) + , kind_( kindIndex ) +{ +} + + +PathArgument::PathArgument( const char *key ) + : key_( key ) + , index_() + , kind_( kindKey ) +{ +} + + +PathArgument::PathArgument( const std::string &key ) + : key_( key.c_str() ) + , index_() + , kind_( kindKey ) +{ +} + +// class Path +// ////////////////////////////////////////////////////////////////// + +Path::Path( const std::string &path, + const PathArgument &a1, + const PathArgument &a2, + const PathArgument &a3, + const PathArgument &a4, + const PathArgument &a5 ) +{ + InArgs in; + in.push_back( &a1 ); + in.push_back( &a2 ); + in.push_back( &a3 ); + in.push_back( &a4 ); + in.push_back( &a5 ); + makePath( path, in ); +} + + +void +Path::makePath( const std::string &path, + const InArgs &in ) +{ + const char *current = path.c_str(); + const char *end = current + path.length(); + InArgs::const_iterator itInArg = in.begin(); + while ( current != end ) + { + if ( *current == '[' ) + { + ++current; + if ( *current == '%' ) + addPathInArg( path, in, itInArg, PathArgument::kindIndex ); + else + { + ArrayIndex index = 0; + for ( ; current != end && *current >= '0' && *current <= '9'; ++current ) + index = index * 10 + ArrayIndex(*current - '0'); + args_.push_back( index ); + } + if ( current == end || *current++ != ']' ) + invalidPath( path, int(current - path.c_str()) ); + } + else if ( *current == '%' ) + { + addPathInArg( path, in, itInArg, PathArgument::kindKey ); + ++current; + } + else if ( *current == '.' ) + { + ++current; + } + else + { + const char *beginName = current; + while ( current != end && !strchr( "[.", *current ) ) + ++current; + args_.push_back( std::string( beginName, current ) ); + } + } +} + + +void +Path::addPathInArg( const std::string &path, + const InArgs &in, + InArgs::const_iterator &itInArg, + PathArgument::Kind kind ) +{ + if ( itInArg == in.end() ) + { + // Error: missing argument %d + } + else if ( (*itInArg)->kind_ != kind ) + { + // Error: bad argument type + } + else + { + args_.push_back( **itInArg ); + } +} + + +void +Path::invalidPath( const std::string &path, + int location ) +{ + // Error: invalid path. +} + + +const Value & +Path::resolve( const Value &root ) const +{ + const Value *node = &root; + for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) + { + const PathArgument &arg = *it; + if ( arg.kind_ == PathArgument::kindIndex ) + { + if ( !node->isArray() || !node->isValidIndex( arg.index_ ) ) + { + // Error: unable to resolve path (array value expected at position... + } + node = &((*node)[arg.index_]); + } + else if ( arg.kind_ == PathArgument::kindKey ) + { + if ( !node->isObject() ) + { + // Error: unable to resolve path (object value expected at position...) + } + node = &((*node)[arg.key_]); + if ( node == &Value::null ) + { + // Error: unable to resolve path (object has no member named '' at position...) + } + } + } + return *node; +} + + +Value +Path::resolve( const Value &root, + const Value &defaultValue ) const +{ + const Value *node = &root; + for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) + { + const PathArgument &arg = *it; + if ( arg.kind_ == PathArgument::kindIndex ) + { + if ( !node->isArray() || !node->isValidIndex( arg.index_ ) ) + return defaultValue; + node = &((*node)[arg.index_]); + } + else if ( arg.kind_ == PathArgument::kindKey ) + { + if ( !node->isObject() ) + return defaultValue; + node = &((*node)[arg.key_]); + if ( node == &Value::null ) + return defaultValue; + } + } + return *node; +} + + +Value & +Path::make( Value &root ) const +{ + Value *node = &root; + for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) + { + const PathArgument &arg = *it; + if ( arg.kind_ == PathArgument::kindIndex ) + { + if ( !node->isArray() ) + { + // Error: node is not an array at position ... + } + node = &((*node)[arg.index_]); + } + else if ( arg.kind_ == PathArgument::kindKey ) + { + if ( !node->isObject() ) + { + // Error: node is not an object at position... + } + node = &((*node)[arg.key_]); + } + } + return *node; +} + + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_value.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + + +// ////////////////////////////////////////////////////////////////////// +// Beginning of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + +// Copyright 2011 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +# include +# include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC++ 8.0 +#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. +#endif + +namespace Json { + +static bool containsControlCharacter( const char* str ) +{ + while ( *str ) + { + if ( isControlCharacter( *(str++) ) ) + return true; + } + return false; +} + + +std::string valueToString( LargestInt value ) +{ + UIntToStringBuffer buffer; + char *current = buffer + sizeof(buffer); + bool isNegative = value < 0; + if ( isNegative ) + value = -value; + uintToString( LargestUInt(value), current ); + if ( isNegative ) + *--current = '-'; + assert( current >= buffer ); + return current; +} + + +std::string valueToString( LargestUInt value ) +{ + UIntToStringBuffer buffer; + char *current = buffer + sizeof(buffer); + uintToString( value, current ); + assert( current >= buffer ); + return current; +} + +#if defined(JSON_HAS_INT64) + +std::string valueToString( Int value ) +{ + return valueToString( LargestInt(value) ); +} + + +std::string valueToString( UInt value ) +{ + return valueToString( LargestUInt(value) ); +} + +#endif // # if defined(JSON_HAS_INT64) + + +std::string valueToString( double value ) +{ + char buffer[32]; +#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. + sprintf_s(buffer, sizeof(buffer), "%#.16g", value); +#else + sprintf(buffer, "%#.16g", value); +#endif + char* ch = buffer + strlen(buffer) - 1; + if (*ch != '0') return buffer; // nothing to truncate, so save time + while(ch > buffer && *ch == '0'){ + --ch; + } + char* last_nonzero = ch; + while(ch >= buffer){ + switch(*ch){ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + --ch; + continue; + case '.': + // Truncate zeroes to save bytes in output, but keep one. + *(last_nonzero+2) = '\0'; + return buffer; + default: + return buffer; + } + } + return buffer; +} + + +std::string valueToString( bool value ) +{ + return value ? "true" : "false"; +} + +std::string valueToQuotedString( const char *value ) +{ + if (value == NULL) + return ""; + // Not sure how to handle unicode... + if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value )) + return std::string("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to std::string is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + std::string::size_type maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL + std::string result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + for (const char* c=value; *c != 0; ++c) + { + switch(*c) + { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + //case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something. + // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); + result += oss.str(); + } + else + { + result += *c; + } + break; + } + } + result += "\""; + return result; +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +Writer::~Writer() +{ +} + + +// Class FastWriter +// ////////////////////////////////////////////////////////////////// + +FastWriter::FastWriter() + : yamlCompatiblityEnabled_( false ), + dropNullPlaceholders_( false ) +{ +} + + +void +FastWriter::enableYAMLCompatibility() +{ + yamlCompatiblityEnabled_ = true; +} + + +void +FastWriter::dropNullPlaceholders() +{ + dropNullPlaceholders_ = true; +} + + +std::string +FastWriter::write( const Value &root ) +{ + document_ = ""; + writeValue( root ); + document_ += "\n"; + return document_; +} + + +void +FastWriter::writeValue( const Value &value ) +{ + switch ( value.type() ) + { + case nullValue: + if (!dropNullPlaceholders_) document_ += "null"; + break; + case intValue: + document_ += valueToString( value.asLargestInt() ); + break; + case uintValue: + document_ += valueToString( value.asLargestUInt() ); + break; + case realValue: + document_ += valueToString( value.asDouble() ); + break; + case stringValue: + document_ += valueToQuotedString( value.asCString() ); + break; + case booleanValue: + document_ += valueToString( value.asBool() ); + break; + case arrayValue: + { + document_ += "["; + int size = value.size(); + for ( int index =0; index < size; ++index ) + { + if ( index > 0 ) + document_ += ","; + writeValue( value[index] ); + } + document_ += "]"; + } + break; + case objectValue: + { + Value::Members members( value.getMemberNames() ); + document_ += "{"; + for ( Value::Members::iterator it = members.begin(); + it != members.end(); + ++it ) + { + const std::string &name = *it; + if ( it != members.begin() ) + document_ += ","; + document_ += valueToQuotedString( name.c_str() ); + document_ += yamlCompatiblityEnabled_ ? ": " + : ":"; + writeValue( value[name] ); + } + document_ += "}"; + } + break; + } +} + + +// Class StyledWriter +// ////////////////////////////////////////////////////////////////// + +StyledWriter::StyledWriter() + : rightMargin_( 74 ) + , indentSize_( 3 ) + , addChildValues_() +{ +} + + +std::string +StyledWriter::write( const Value &root ) +{ + document_ = ""; + addChildValues_ = false; + indentString_ = ""; + writeCommentBeforeValue( root ); + writeValue( root ); + writeCommentAfterValueOnSameLine( root ); + document_ += "\n"; + return document_; +} + + +void +StyledWriter::writeValue( const Value &value ) +{ + switch ( value.type() ) + { + case nullValue: + pushValue( "null" ); + break; + case intValue: + pushValue( valueToString( value.asLargestInt() ) ); + break; + case uintValue: + pushValue( valueToString( value.asLargestUInt() ) ); + break; + case realValue: + pushValue( valueToString( value.asDouble() ) ); + break; + case stringValue: + pushValue( valueToQuotedString( value.asCString() ) ); + break; + case booleanValue: + pushValue( valueToString( value.asBool() ) ); + break; + case arrayValue: + writeArrayValue( value); + break; + case objectValue: + { + Value::Members members( value.getMemberNames() ); + if ( members.empty() ) + pushValue( "{}" ); + else + { + writeWithIndent( "{" ); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) + { + const std::string &name = *it; + const Value &childValue = value[name]; + writeCommentBeforeValue( childValue ); + writeWithIndent( valueToQuotedString( name.c_str() ) ); + document_ += " : "; + writeValue( childValue ); + if ( ++it == members.end() ) + { + writeCommentAfterValueOnSameLine( childValue ); + break; + } + document_ += ","; + writeCommentAfterValueOnSameLine( childValue ); + } + unindent(); + writeWithIndent( "}" ); + } + } + break; + } +} + + +void +StyledWriter::writeArrayValue( const Value &value ) +{ + unsigned size = value.size(); + if ( size == 0 ) + pushValue( "[]" ); + else + { + bool isArrayMultiLine = isMultineArray( value ); + if ( isArrayMultiLine ) + { + writeWithIndent( "[" ); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index =0; + for (;;) + { + const Value &childValue = value[index]; + writeCommentBeforeValue( childValue ); + if ( hasChildValue ) + writeWithIndent( childValues_[index] ); + else + { + writeIndent(); + writeValue( childValue ); + } + if ( ++index == size ) + { + writeCommentAfterValueOnSameLine( childValue ); + break; + } + document_ += ","; + writeCommentAfterValueOnSameLine( childValue ); + } + unindent(); + writeWithIndent( "]" ); + } + else // output on a single line + { + assert( childValues_.size() == size ); + document_ += "[ "; + for ( unsigned index =0; index < size; ++index ) + { + if ( index > 0 ) + document_ += ", "; + document_ += childValues_[index]; + } + document_ += " ]"; + } + } +} + + +bool +StyledWriter::isMultineArray( const Value &value ) +{ + int size = value.size(); + bool isMultiLine = size*3 >= rightMargin_ ; + childValues_.clear(); + for ( int index =0; index < size && !isMultiLine; ++index ) + { + const Value &childValue = value[index]; + isMultiLine = isMultiLine || + ( (childValue.isArray() || childValue.isObject()) && + childValue.size() > 0 ); + } + if ( !isMultiLine ) // check if line length > max line length + { + childValues_.reserve( size ); + addChildValues_ = true; + int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' + for ( int index =0; index < size && !isMultiLine; ++index ) + { + writeValue( value[index] ); + lineLength += int( childValues_[index].length() ); + isMultiLine = isMultiLine && hasCommentForValue( value[index] ); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + + +void +StyledWriter::pushValue( const std::string &value ) +{ + if ( addChildValues_ ) + childValues_.push_back( value ); + else + document_ += value; +} + + +void +StyledWriter::writeIndent() +{ + if ( !document_.empty() ) + { + char last = document_[document_.length()-1]; + if ( last == ' ' ) // already indented + return; + if ( last != '\n' ) // Comments may add new-line + document_ += '\n'; + } + document_ += indentString_; +} + + +void +StyledWriter::writeWithIndent( const std::string &value ) +{ + writeIndent(); + document_ += value; +} + + +void +StyledWriter::indent() +{ + indentString_ += std::string( indentSize_, ' ' ); +} + + +void +StyledWriter::unindent() +{ + assert( int(indentString_.size()) >= indentSize_ ); + indentString_.resize( indentString_.size() - indentSize_ ); +} + + +void +StyledWriter::writeCommentBeforeValue( const Value &root ) +{ + if ( !root.hasComment( commentBefore ) ) + return; + document_ += normalizeEOL( root.getComment( commentBefore ) ); + document_ += "\n"; +} + + +void +StyledWriter::writeCommentAfterValueOnSameLine( const Value &root ) +{ + if ( root.hasComment( commentAfterOnSameLine ) ) + document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); + + if ( root.hasComment( commentAfter ) ) + { + document_ += "\n"; + document_ += normalizeEOL( root.getComment( commentAfter ) ); + document_ += "\n"; + } +} + + +bool +StyledWriter::hasCommentForValue( const Value &value ) +{ + return value.hasComment( commentBefore ) + || value.hasComment( commentAfterOnSameLine ) + || value.hasComment( commentAfter ); +} + + +std::string +StyledWriter::normalizeEOL( const std::string &text ) +{ + std::string normalized; + normalized.reserve( text.length() ); + const char *begin = text.c_str(); + const char *end = begin + text.length(); + const char *current = begin; + while ( current != end ) + { + char c = *current++; + if ( c == '\r' ) // mac or dos EOL + { + if ( *current == '\n' ) // convert dos EOL + ++current; + normalized += '\n'; + } + else // handle unix EOL & other char + normalized += c; + } + return normalized; +} + + +// Class StyledStreamWriter +// ////////////////////////////////////////////////////////////////// + +StyledStreamWriter::StyledStreamWriter( std::string indentation ) + : document_(NULL) + , rightMargin_( 74 ) + , indentation_( indentation ) + , addChildValues_() +{ +} + + +void +StyledStreamWriter::write( std::ostream &out, const Value &root ) +{ + document_ = &out; + addChildValues_ = false; + indentString_ = ""; + writeCommentBeforeValue( root ); + writeValue( root ); + writeCommentAfterValueOnSameLine( root ); + *document_ << "\n"; + document_ = NULL; // Forget the stream, for safety. +} + + +void +StyledStreamWriter::writeValue( const Value &value ) +{ + switch ( value.type() ) + { + case nullValue: + pushValue( "null" ); + break; + case intValue: + pushValue( valueToString( value.asLargestInt() ) ); + break; + case uintValue: + pushValue( valueToString( value.asLargestUInt() ) ); + break; + case realValue: + pushValue( valueToString( value.asDouble() ) ); + break; + case stringValue: + pushValue( valueToQuotedString( value.asCString() ) ); + break; + case booleanValue: + pushValue( valueToString( value.asBool() ) ); + break; + case arrayValue: + writeArrayValue( value); + break; + case objectValue: + { + Value::Members members( value.getMemberNames() ); + if ( members.empty() ) + pushValue( "{}" ); + else + { + writeWithIndent( "{" ); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) + { + const std::string &name = *it; + const Value &childValue = value[name]; + writeCommentBeforeValue( childValue ); + writeWithIndent( valueToQuotedString( name.c_str() ) ); + *document_ << " : "; + writeValue( childValue ); + if ( ++it == members.end() ) + { + writeCommentAfterValueOnSameLine( childValue ); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine( childValue ); + } + unindent(); + writeWithIndent( "}" ); + } + } + break; + } +} + + +void +StyledStreamWriter::writeArrayValue( const Value &value ) +{ + unsigned size = value.size(); + if ( size == 0 ) + pushValue( "[]" ); + else + { + bool isArrayMultiLine = isMultineArray( value ); + if ( isArrayMultiLine ) + { + writeWithIndent( "[" ); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index =0; + for (;;) + { + const Value &childValue = value[index]; + writeCommentBeforeValue( childValue ); + if ( hasChildValue ) + writeWithIndent( childValues_[index] ); + else + { + writeIndent(); + writeValue( childValue ); + } + if ( ++index == size ) + { + writeCommentAfterValueOnSameLine( childValue ); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine( childValue ); + } + unindent(); + writeWithIndent( "]" ); + } + else // output on a single line + { + assert( childValues_.size() == size ); + *document_ << "[ "; + for ( unsigned index =0; index < size; ++index ) + { + if ( index > 0 ) + *document_ << ", "; + *document_ << childValues_[index]; + } + *document_ << " ]"; + } + } +} + + +bool +StyledStreamWriter::isMultineArray( const Value &value ) +{ + int size = value.size(); + bool isMultiLine = size*3 >= rightMargin_ ; + childValues_.clear(); + for ( int index =0; index < size && !isMultiLine; ++index ) + { + const Value &childValue = value[index]; + isMultiLine = isMultiLine || + ( (childValue.isArray() || childValue.isObject()) && + childValue.size() > 0 ); + } + if ( !isMultiLine ) // check if line length > max line length + { + childValues_.reserve( size ); + addChildValues_ = true; + int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' + for ( int index =0; index < size && !isMultiLine; ++index ) + { + writeValue( value[index] ); + lineLength += int( childValues_[index].length() ); + isMultiLine = isMultiLine && hasCommentForValue( value[index] ); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + + +void +StyledStreamWriter::pushValue( const std::string &value ) +{ + if ( addChildValues_ ) + childValues_.push_back( value ); + else + *document_ << value; +} + + +void +StyledStreamWriter::writeIndent() +{ + /* + Some comments in this method would have been nice. ;-) + + if ( !document_.empty() ) + { + char last = document_[document_.length()-1]; + if ( last == ' ' ) // already indented + return; + if ( last != '\n' ) // Comments may add new-line + *document_ << '\n'; + } + */ + *document_ << '\n' << indentString_; +} + + +void +StyledStreamWriter::writeWithIndent( const std::string &value ) +{ + writeIndent(); + *document_ << value; +} + + +void +StyledStreamWriter::indent() +{ + indentString_ += indentation_; +} + + +void +StyledStreamWriter::unindent() +{ + assert( indentString_.size() >= indentation_.size() ); + indentString_.resize( indentString_.size() - indentation_.size() ); +} + + +void +StyledStreamWriter::writeCommentBeforeValue( const Value &root ) +{ + if ( !root.hasComment( commentBefore ) ) + return; + *document_ << normalizeEOL( root.getComment( commentBefore ) ); + *document_ << "\n"; +} + + +void +StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root ) +{ + if ( root.hasComment( commentAfterOnSameLine ) ) + *document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); + + if ( root.hasComment( commentAfter ) ) + { + *document_ << "\n"; + *document_ << normalizeEOL( root.getComment( commentAfter ) ); + *document_ << "\n"; + } +} + + +bool +StyledStreamWriter::hasCommentForValue( const Value &value ) +{ + return value.hasComment( commentBefore ) + || value.hasComment( commentAfterOnSameLine ) + || value.hasComment( commentAfter ); +} + + +std::string +StyledStreamWriter::normalizeEOL( const std::string &text ) +{ + std::string normalized; + normalized.reserve( text.length() ); + const char *begin = text.c_str(); + const char *end = begin + text.length(); + const char *current = begin; + while ( current != end ) + { + char c = *current++; + if ( c == '\r' ) // mac or dos EOL + { + if ( *current == '\n' ) // convert dos EOL + ++current; + normalized += '\n'; + } + else // handle unix EOL & other char + normalized += c; + } + return normalized; +} + + +std::ostream& operator<<( std::ostream &sout, const Value &root ) +{ + Json::StyledStreamWriter writer; + writer.write(sout, root); + return sout; +} + + +} // namespace Json + +// ////////////////////////////////////////////////////////////////////// +// End of content of file: src/lib_json/json_writer.cpp +// ////////////////////////////////////////////////////////////////////// + + + + + diff --git a/src/main.cpp b/src/main.cpp index 968dc8d60..fd3643e60 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1095,6 +1095,7 @@ int main(int argc, char *argv[]) #else bool run_dedicated_server = cmd_args.getFlag("server"); #endif + g_settings->set("server_dedicated", run_dedicated_server ? "true" : "false"); if(run_dedicated_server) { DSTACK("Dedicated server branch"); @@ -1593,6 +1594,7 @@ int main(int argc, char *argv[]) g_settings->set("creative_mode", itos(menudata.creative_mode)); g_settings->set("enable_damage", itos(menudata.enable_damage)); + g_settings->set("server_announce", itos(menudata.enable_public)); g_settings->set("name", playername); g_settings->set("address", address); g_settings->set("port", itos(port)); @@ -1619,10 +1621,10 @@ int main(int argc, char *argv[]) else if (address != "") { ServerListSpec server; - server.name = menudata.servername; - server.address = wide_to_narrow(menudata.address); - server.port = wide_to_narrow(menudata.port); - server.description = menudata.serverdescription; + server["name"] = menudata.servername; + server["address"] = wide_to_narrow(menudata.address); + server["port"] = wide_to_narrow(menudata.port); + server["description"] = menudata.serverdescription; ServerList::insert(server); } diff --git a/src/server.cpp b/src/server.cpp index ec8f8a5e8..94a4787f8 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -51,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "sound.h" // dummySoundManager #include "event_manager.h" #include "hex.h" +#include "serverlist.h" #include "util/string.h" #include "util/pointedthing.h" #include "util/mathconstants.h" @@ -961,9 +962,11 @@ Server::Server( { m_liquid_transform_timer = 0.0; m_print_info_timer = 0.0; + m_masterserver_timer = 0.0; m_objectdata_timer = 0.0; m_emergethread_trigger_timer = 0.0; m_savemap_timer = 0.0; + m_clients_number = 0; m_env_mutex.Init(); m_con_mutex.Init(); @@ -1505,7 +1508,7 @@ void Server::AsyncRunStep() counter = 0.0; JMutexAutoLock lock2(m_con_mutex); - + m_clients_number = 0; if(m_clients.size() != 0) infostream<<"Players:"<::Iterator @@ -1519,10 +1522,25 @@ void Server::AsyncRunStep() continue; infostream<<"* "<getName()<<"\t"; client->PrintInfo(infostream); + ++m_clients_number; } } } + +#if USE_CURL + // send masterserver announce + { + float &counter = m_masterserver_timer; + if((!counter || counter >= 300.0) && g_settings->getBool("server_announce") == true) + { + ServerList::sendAnnounce(!counter ? "start" : "update", m_clients_number); + counter = 0.01; + } + counter += dtime; + } +#endif + //if(g_settings->getBool("enable_experimental")) { @@ -5186,6 +5204,10 @@ void dedicated_server_loop(Server &server, bool &kill) if(server.getShutdownRequested() || kill) { infostream<<"Dedicated server quitting"<getBool("server_announce") == true) + ServerList::sendAnnounce("delete"); +#endif break; } diff --git a/src/server.h b/src/server.h index 29d47337d..22c7cf2bb 100644 --- a/src/server.h +++ b/src/server.h @@ -720,6 +720,7 @@ private: // Some timers float m_liquid_transform_timer; float m_print_info_timer; + float m_masterserver_timer; float m_objectdata_timer; float m_emergethread_trigger_timer; float m_savemap_timer; @@ -737,6 +738,7 @@ private: JMutex m_con_mutex; // Connected clients (behind the con mutex) core::map m_clients; + u16 m_clients_number; //for announcing masterserver // Bann checking BanManager m_banmanager; diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 88a213db1..b2f49ae72 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "porting.h" #include "log.h" +#include "json/json.h" #if USE_CURL #include #endif @@ -83,7 +84,7 @@ std::vector getOnline() { CURLcode res; - curl_easy_setopt(curl, CURLOPT_URL, g_settings->get("serverlist_url").c_str()); + curl_easy_setopt(curl, CURLOPT_URL, (g_settings->get("serverlist_url")+"/list").c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ServerList::WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring); @@ -92,8 +93,7 @@ std::vector getOnline() errorstream<<"Serverlist at url "<get("serverlist_url")<<" not found (internet connection?)"< serverlist = ServerList::getLocal(); for(unsigned i = 0; i < serverlist.size(); i++) { - if (serverlist[i].address == server.address - && serverlist[i].port == server.port) + if (serverlist[i]["address"] == server["address"] + && serverlist[i]["port"] == server["port"]) { serverlist.erase(serverlist.begin() + i); } @@ -150,17 +150,21 @@ std::vector deSerialize(std::string liststring) { std::vector serverlist; std::istringstream stream(liststring); - std::string line; + std::string line, tmp; while (std::getline(stream, line)) { std::transform(line.begin(), line.end(),line.begin(), ::toupper); if (line == "[SERVER]") { ServerListSpec thisserver; - std::getline(stream, thisserver.name); - std::getline(stream, thisserver.address); - std::getline(stream, thisserver.port); - std::getline(stream, thisserver.description); + std::getline(stream, tmp); + thisserver["name"] = tmp; + std::getline(stream, tmp); + thisserver["address"] = tmp; + std::getline(stream, tmp); + thisserver["port"] = tmp; + std::getline(stream, tmp); + thisserver["description"] = tmp; serverlist.push_back(thisserver); } } @@ -173,13 +177,100 @@ std::string serialize(std::vector serverlist) for(std::vector::iterator i = serverlist.begin(); i != serverlist.end(); i++) { liststring += "[server]\n"; - liststring += i->name + "\n"; - liststring += i->address + "\n"; - liststring += i->port + "\n"; - liststring += i->description + "\n"; + liststring += (*i)["name"].asString() + "\n"; + liststring += (*i)["address"].asString() + "\n"; + liststring += (*i)["port"].asString() + "\n"; + liststring += (*i)["description"].asString() + "\n"; liststring += "\n"; } return liststring; } +std::vector deSerializeJson(std::string liststring) +{ + std::vector serverlist; + Json::Value root; + Json::Reader reader; + std::istringstream stream(liststring); + if (!liststring.size()) { + return serverlist; + } + if (!reader.parse( stream, root ) ) + { + errorstream << "Failed to parse server list " << reader.getFormattedErrorMessages(); + return serverlist; + } + if (root["list"].isArray()) + for (unsigned int i = 0; i < root["list"].size(); i++) + { + if (root["list"][i].isObject()) { + serverlist.push_back(root["list"][i]); + } + } + return serverlist; +} + +std::string serializeJson(std::vector serverlist) +{ + Json::Value root; + Json::Value list(Json::arrayValue); + for(std::vector::iterator i = serverlist.begin(); i != serverlist.end(); i++) + { + list.append(*i); + } + root["list"] = list; + Json::StyledWriter writer; + return writer.write( root ); +} + + +#if USE_CURL +static size_t ServerAnnounceCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + return 0; + //((std::string*)userp)->append((char*)contents, size * nmemb); + //return size * nmemb; +} +void sendAnnounce(std::string action, u16 clients) { + Json::Value server; + if (action.size()) + server["action"] = action; + server["port"] = g_settings->get("port"); + if (action != "del") { + server["name"] = g_settings->get("server_name"); + server["description"] = g_settings->get("server_description"); + server["address"] = g_settings->get("server_address"); + server["version"] = VERSION_STRING; + server["url"] = g_settings->get("server_url"); + server["creative"] = g_settings->get("creative_mode"); + server["damage"] = g_settings->get("enable_damage"); + server["dedicated"] = g_settings->get("server_dedicated"); + server["password"] = g_settings->getBool("disallow_empty_password"); + server["pvp"] = g_settings->getBool("enable_pvp"); + server["clients"] = clients; + server["clients_max"] = g_settings->get("max_users"); + } + if(server["action"] == "start") + actionstream << "announcing to " << g_settings->get("serverlist_url") << std::endl; + Json::StyledWriter writer; + CURL *curl; + curl = curl_easy_init(); + if (curl) + { + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, (g_settings->get("serverlist_url")+std::string("/announce?json=")+curl_easy_escape(curl, writer.write( server ).c_str(), 0)).c_str()); + //curl_easy_setopt(curl, CURLOPT_USERAGENT, "minetest"); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ServerList::ServerAnnounceCallback); + //curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 1); + res = curl_easy_perform(curl); + //if (res != CURLE_OK) + // errorstream<<"Serverlist at url "<get("serverlist_url")<<" not found (internet connection?)"< #include "config.h" +#include "json/json.h" #ifndef SERVERLIST_HEADER #define SERVERLIST_HEADER -struct ServerListSpec -{ - std::string name; - std::string address; - std::string port; - std::string description; -}; +typedef Json::Value ServerListSpec; namespace ServerList { @@ -41,6 +36,11 @@ namespace ServerList bool insert(ServerListSpec server); std::vector deSerialize(std::string liststring); std::string serialize(std::vector); + std::vector deSerializeJson(std::string liststring); + std::string serializeJson(std::vector); + #if USE_CURL + void sendAnnounce(std::string action = "", u16 clients = 0); + #endif } //ServerList namespace #endif diff --git a/util/master/index.html b/util/master/index.html new file mode 100644 index 000000000..63ee5c70f --- /dev/null +++ b/util/master/index.html @@ -0,0 +1,11 @@ + + + + + Minetest server list + + +
+ + + diff --git a/util/master/list.js b/util/master/list.js new file mode 100644 index 000000000..dcc30e091 --- /dev/null +++ b/util/master/list.js @@ -0,0 +1,72 @@ +function e(s) { + if (typeof s === "undefined") s = ''; + return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); //mc" +} +function human_time(t) { + var n = 's'; + if (!t || t < 0) t = 0; + var f = 0; + var s = parseInt((new Date().getTime() / 1000 - (t || 0))); + if (!s || s <= 0) s = 0; + if (s == 0) return 'now'; + if (s >= 60) { + s /= 60; + n = 'm'; + if (s >= 60) { + s /= 60; + n = 'h'; + f = 1; + if (s >= 24) { + s /= 24; + n = 'd'; + f = 1; + if (s >= 30) { + s /= 30; + n = 'M'; + f = 1; + if (s >= 12) { + s /= 12; + n = 'y'; + f = 1; + } + } + } + } + } + return ((f ? parseFloat(s).toFixed(1) : parseInt(s)) + n); +} +function success(r) { + if (!r || !r.list) return; + var h = ''; + for (var i = 0; i < r.list.length; ++i) { + var s = r.list[i]; + if (!s) continue; + h += ''; + h += ''; + h += ''; + h += ''; + h += ''; + h += ''; + h += ''; + if (!s.time || s.time < 0) s.time = 0; + if (!s.start || s.start < 0) s.start = 0; + h += ''; + h += ''; + h += ''; + } + h += '
ip:portclients, maxversionnamedescflagsupdated/startedping
' + e(s.address) + ':' + e(s.port) + '' + e(s.clients) + (s.clients_max ? '/' + e(s.clients_max) : '') + (s.clients_top ? ', ' + s.clients_top : '') + '' + e(s.version) + ''; + if (s.url) h += ''; + h += e(s.name || s.url); + if (s.url) h += ''; + h += '' + e(s.description) + '' + e(s.password ? 'Pwd ' : '') + (s.creative ? 'Cre ' : '') + (s.damage ? 'Dmg ' : '') + (s.pvp ? 'Pvp ' : '') + (s.dedicated ? 'Ded ' : '') + '' + human_time(s.time) + (s.start ? '/' + human_time(s.start) : '') + '' + (s.ping ? parseFloat(s.ping).toFixed(3)*1000 : '') + '
' + jQuery('#table').html(h); +} +function get() { + jQuery.ajax({ + url: 'list', + dataType: 'json', + success: success + }); + setTimeout(get, 60000); +} +get(); \ No newline at end of file diff --git a/util/master/master.cgi b/util/master/master.cgi new file mode 100755 index 000000000..b918876bd --- /dev/null +++ b/util/master/master.cgi @@ -0,0 +1,257 @@ +#!/usr/bin/perl + +=info +install: + cpan JSON JSON::XS + touch list_full list + chmod a+rw list_full list + +freebsd: + www/fcgiwrap www/nginx + +rc.conf.local: +nginx_enable="YES" +fcgiwrap_enable="YES" +fcgiwrap_user="www" + +nginx: + + location / { + index index.html; + } + location /announce { + fastcgi_pass unix:/var/run/fcgiwrap/fcgiwrap.sock; + fastcgi_param SCRIPT_FILENAME $document_root/master.cgi; + include fastcgi_params; + } + + +apache .htaccess: + AddHandler cgi-script .cgi + DirectoryIndex index.html + Options +ExecCGI +FollowSymLinks + Order allow,deny + + Allow from all + + Deny from all + + +=cut + +use strict; +no strict qw(refs); +use warnings "NONFATAL" => "all"; +no warnings qw(uninitialized); +use utf8; +use Socket; +use Time::HiRes qw(time sleep); +use IO::Socket::INET; +use JSON; +use Net::Ping; +our $root_path; +($ENV{'SCRIPT_FILENAME'} || $0) =~ m|^(.+)[/\\].+?$|; #v0w +$root_path = $1 . '/' if $1; +$root_path =~ s|\\|/|g; + +our %config = ( + #debug => 1, + list_full => $root_path . 'list_full', + list_pub => $root_path . 'list', + time_purge => 86400 * 30, + time_alive => 650, + source_check => 1, + ping_timeout => 3, + ping => 1, + mineping => 1, + pingable => 1, + trusted => [qw( 176.9.122.10 )], #masterserver self ip - if server on same ip with masterserver doesnt announced + #blacklist => [], # [qw(2.3.4.5 4.5.6.7 8.9.0.1), '1.2.3.4', qr/^10\.20\.30\./, ], # list, or quoted, ips, or regex +); +do($root_path . 'config.pl'); +our $ping = Net::Ping->new("udp", $config{ping_timeout}); +$ping->hires(); + +sub get_params_one(@) { + local %_ = %{ref $_[0] eq 'HASH' ? shift : {}}; + for (@_) { + tr/+/ /, s/%([a-f\d]{2})/pack 'H*', $1/gei for my ($k, $v) = /^([^=]+=?)=(.+)$/ ? ($1, $2) : (/^([^=]*)=?$/, /^-/); + $_{$k} = $v; + } + wantarray ? %_ : \%_; +} + +sub get_params(;$$) { #v7 + my ($string, $delim) = @_; + $delim ||= '&'; + read(STDIN, local $_ = '', $ENV{'CONTENT_LENGTH'}) if !$string and $ENV{'CONTENT_LENGTH'}; + local %_ = + $string + ? get_params_one split $delim, $string + : (get_params_one(@ARGV), map { get_params_one split $delim, $_ } split(/;\s*/, $ENV{'HTTP_COOKIE'}), $ENV{'QUERY_STRING'}, $_); + wantarray ? %_ : \%_; +} + +sub get_params_utf8(;$$) { + local $_ = &get_params; + utf8::decode $_ for %$_; + wantarray ? %$_ : $_; +} + +sub file_rewrite(;$@) { + local $_ = shift; + return unless open my $fh, '>', $_; + print $fh @_; +} + +sub file_read ($) { + open my $f, '<', $_[0] or return; + local $/ = undef; + my $ret = <$f>; + close $f; + return \$ret; +} + +sub read_json { + my $ret = {}; + eval { $ret = JSON->new->utf8->relaxed(1)->decode(${ref $_[0] ? $_[0] : file_read($_[0]) or \''} || '{}'); }; #'mc + warn "json error [$@] on [", ${ref $_[0] ? $_[0] : \$_[0]}, "]" if $@; + $ret; +} + +sub printu (@) { + for (@_) { + print($_), next unless utf8::is_utf8($_); + my $s = $_; + utf8::encode($s); + print($s); + } +} + +sub name_to_ip_noc($) { + my ($name) = @_; + unless ($name =~ /^\d+\.\d+\.\d+\.\d+$/) { + local $_ = (gethostbyname($name))[4]; + return ($name, 1) unless length($_) == 4; + $name = inet_ntoa($_); + } + return $name; +} + +sub float { + return ($_[0] < 8 and $_[0] - int($_[0])) + ? sprintf('%.' . ($_[0] < 1 ? 3 : ($_[0] < 3 ? 2 : 1)) . 'f', $_[0]) + : int($_[0]); + +} + +sub mineping ($$) { + my ($addr, $port) = @_; + warn "mineping($addr, $port)" if $config{debug}; + my $data; + my $time = time; + eval { + my $socket = IO::Socket::INET->new( + 'PeerAddr' => $addr, + 'PeerPort' => $port, + 'Proto' => 'udp', + 'Timeout' => $config{ping_timeout}, + ); + $socket->send("\x4f\x45\x74\x03\x00\x00\x00\x01"); + local $SIG{ALRM} = sub { die "alarm time out"; }; + alarm $config{ping_timeout}; + $socket->recv($data, POSIX::BUFSIZ) or die "recv: $!"; + alarm 0; + 1; # return value from eval on normalcy + } or return 0; + return 0 unless length $data; + $time = float(time - $time); + warn "recvd: ", length $data, " [$time]" if $config{debug}; + return $time; +} + +sub request (;$) { + my ($r) = @_; + $r ||= \%ENV; + my $param = get_params_utf8; + my $after = sub { + if ($param->{json}) { + my $j = {}; + eval { $j = JSON->new->decode($param->{json}) || {} }; + $param->{$_} = $j->{$_} for keys %$j; + delete $param->{json}; + } + if (%$param) { + s/^false$// for values %$param; + $param->{ip} = $r->{REMOTE_ADDR}; + for (@{$config{blacklist}}) { + return if $param->{ip} ~~ $_; + } + $param->{address} ||= $param->{ip}; + if ($config{source_check} and name_to_ip_noc($param->{address}) ne $param->{ip} and !($param->{ip} ~~ $config{trusted})) { + warn("bad address [$param->{address}] ne [$param->{ip}]") if $config{debug}; + return; + } + $param->{port} ||= 30000; + $param->{key} = "$param->{ip}:$param->{port}"; + $param->{off} = time if $param->{action} ~~ 'delete'; + + if ($config{ping} and $param->{action} ne 'delete') { + if ($config{mineping}) { + $param->{ping} = mineping($param->{ip}, $param->{port}); + } else { + $ping->port_number($param->{port}); + $ping->service_check(0); + my ($pingret, $duration, $ip) = $ping->ping($param->{address}); + if ($ip ne $param->{ip} and !($param->{ip} ~~ $config{trusted})) { + warn "strange ping ip [$ip] != [$param->{ip}]" if $config{debug}; + return if $config{source_check} and !($param->{ip} ~~ $config{trusted}); + } + $param->{ping} = $duration if $pingret; + warn " PING t=$config{ping_timeout}, $param->{address}:$param->{port} = ( $pingret, $duration, $ip )" if $config{debug}; + } + } + my $list = read_json($config{list_full}) || {}; + warn "readed[$config{list_full}] list size=", scalar @{$list->{list}}; + my $listk = {map { $_->{key} => $_ } @{$list->{list}}}; + my $old = $listk->{$param->{key}}; + $param->{time} = $old->{time} if $param->{off}; + $param->{time} ||= int time; + $param->{start} = $param->{action} ~~ 'start' ? $param->{time} : $old->{start} || $param->{time}; + delete $param->{start} if $param->{off}; + $param->{first} ||= $old->{first} || $old->{time} || $param->{time}; + $param->{clients_top} = $old->{clients_top} if $old->{clients_top} > $param->{clients}; + $param->{clients_top} ||= $param->{clients} || 0; + delete $param->{action}; + $listk->{$param->{key}} = $param; + $list->{list} = [grep { $_->{time} > time - $config{time_purge} } values %$listk]; + file_rewrite($config{list_full}, JSON->new->encode($list)); + warn "writed[$config{list_full}] list size=", scalar @{$list->{list}}; + $list->{list} = [ + sort { $b->{clients} <=> $a->{clients} || $a->{start} <=> $b->{start} } + grep { $_->{time} > time - $config{time_alive} and !$_->{off} and (!$config{ping} or !$config{pingable} or $_->{ping}) } + @{$list->{list}} + ]; + file_rewrite($config{list_pub}, JSON->new->encode($list)); + warn "writed[$config{list_pub}] list size=", scalar @{$list->{list}}; + } + }; + return [200, ["Content-type", "application/json"], [JSON->new->encode({})]], $after; +} + +sub request_cgi { + my ($p, $after) = request(@_); + shift @$p; + printu join "\n", map { join ': ', @$_ } shift @$p; + printu "\n\n"; + printu join '', map { join '', @$_ } @$p; + if (fork) { + unless ($config{debug}) { + close STDOUT; + close STDERR; + } + } else { + $after->() if ref $after ~~ 'CODE'; + } +} +request_cgi() unless caller; diff --git a/util/master/style.css b/util/master/style.css new file mode 100644 index 000000000..638b51f80 --- /dev/null +++ b/util/master/style.css @@ -0,0 +1,14 @@ +table { + max-width: 100%; + background-color: transparent; + border-collapse: collapse; + border-spacing: 0; +} + +td, th { + border: 1px solid gray; +} + +div#table table { + width: 100%; +} \ No newline at end of file From c19fc00b044daba41fbdc3762851c6a1bae74b40 Mon Sep 17 00:00:00 2001 From: Ilya Zhuravlev Date: Fri, 22 Feb 2013 02:41:59 +0400 Subject: [PATCH 22/43] Fix json include --- src/json/jsoncpp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/json/jsoncpp.cpp b/src/json/jsoncpp.cpp index dbe5a349f..7a04736de 100644 --- a/src/json/jsoncpp.cpp +++ b/src/json/jsoncpp.cpp @@ -73,7 +73,7 @@ license you like. -#include "json/json.h" +#include "json.h" // ////////////////////////////////////////////////////////////////////// From 7f240aea1e55b0714e80ef6ab7172b58b0729f75 Mon Sep 17 00:00:00 2001 From: kwolekr Date: Fri, 8 Feb 2013 21:52:23 -0500 Subject: [PATCH 23/43] Added debug log level setting --- src/defaultsettings.cpp | 2 +- src/main.cpp | 40 +++++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index d3a455ccb..6cdaaa496 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -183,7 +183,7 @@ void set_default_settings(Settings *settings) settings->setDefault("congestion_control_max_rate", "400"); settings->setDefault("congestion_control_min_rate", "10"); settings->setDefault("remote_media", ""); - + settings->setDefault("debug_log_level", "0"); // physics stuff settings->setDefault("movement_acceleration_default", "3"); settings->setDefault("movement_acceleration_air", "2"); diff --git a/src/main.cpp b/src/main.cpp index fd3643e60..9287ec760 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -887,22 +887,7 @@ int main(int argc, char *argv[]) fs::CreateDir(porting::path_user); init_gettext((porting::path_share + DIR_DELIM + "locale").c_str()); - - // Initialize debug streams -#define DEBUGFILE "debug.txt" -#if RUN_IN_PLACE - std::string logfile = DEBUGFILE; -#else - std::string logfile = porting::path_user+DIR_DELIM+DEBUGFILE; -#endif - if(cmd_args.exists("logfile")) - logfile = cmd_args.get("logfile"); - if(logfile != "") - debugstreams_init(false, logfile.c_str()); - else - debugstreams_init(false, NULL); - infostream<<"logfile = "<getS32("debug_log_level"); + + if (loglevel == 0) //no logging + logfile = ""; + else if (loglevel > 0 && loglevel <= LMT_NUM_VALUES) + log_add_output_maxlev(&main_dstream_no_stderr_log_out, (LogMessageLevel)(loglevel - 1)); + + if(logfile != "") + debugstreams_init(false, logfile.c_str()); + else + debugstreams_init(false, NULL); + + infostream<<"logfile = "< Date: Sat, 23 Feb 2013 16:01:35 +0100 Subject: [PATCH 24/43] Add sound volume controls to ingame menu --- src/CMakeLists.txt | 1 + src/game.cpp | 8 ++ src/guiPauseMenu.cpp | 18 +++- src/guiPauseMenu.h | 1 + src/guiVolumeChange.cpp | 181 ++++++++++++++++++++++++++++++++++++++++ src/guiVolumeChange.h | 53 ++++++++++++ src/mainmenumanager.h | 7 ++ 7 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 src/guiVolumeChange.cpp create mode 100644 src/guiVolumeChange.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 17102e848..c06da20c4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -297,6 +297,7 @@ set(minetest_SRCS guiFormSpecMenu.cpp guiPauseMenu.cpp guiPasswordChange.cpp + guiVolumeChange.cpp guiDeathScreen.cpp guiChatConsole.cpp guiCreateWorld.cpp diff --git a/src/game.cpp b/src/game.cpp index c77754b5f..86909ccf0 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include "guiPauseMenu.h" #include "guiPasswordChange.h" +#include "guiVolumeChange.h" #include "guiFormSpecMenu.h" #include "guiTextInputMenu.h" #include "guiDeathScreen.h" @@ -1519,6 +1520,13 @@ void the_game( g_gamecallback->changepassword_requested = false; } + if(g_gamecallback->changevolume_requested) + { + (new GUIVolumeChange(guienv, guiroot, -1, + &g_menumgr, &client))->drop(); + g_gamecallback->changevolume_requested = false; + } + /* Process TextureSource's queue */ tsrc->processQueue(); diff --git a/src/guiPauseMenu.cpp b/src/guiPauseMenu.cpp index f6cbf248f..9d44b2e82 100644 --- a/src/guiPauseMenu.cpp +++ b/src/guiPauseMenu.cpp @@ -79,6 +79,11 @@ void GUIPauseMenu::removeChildren() if(e != NULL) e->remove(); } + { + gui::IGUIElement *e = getElementFromId(262); + if(e != NULL) + e->remove(); + } } void GUIPauseMenu::regenerateGui(v2u32 screensize) @@ -108,7 +113,7 @@ void GUIPauseMenu::regenerateGui(v2u32 screensize) */ const s32 btn_height = 30; const s32 btn_gap = 20; - const s32 btn_num = m_simple_singleplayer_mode ? 3 : 4; + const s32 btn_num = m_simple_singleplayer_mode ? 4 : 5; s32 btn_y = size.Y/2-((btn_num*btn_height+(btn_num-1)*btn_gap))/2; changeCtype(""); { @@ -128,6 +133,13 @@ void GUIPauseMenu::regenerateGui(v2u32 screensize) } btn_y += btn_height + btn_gap; } + { + core::rect rect(0, 0, 140, btn_height); + rect = rect + v2s32(size.X/2-140/2, btn_y); + Environment->addButton(rect, this, 262, + wgettext("Sound Volume")); + } + btn_y += btn_height + btn_gap; { core::rect rect(0, 0, 140, btn_height); rect = rect + v2s32(size.X/2-140/2, btn_y); @@ -236,6 +248,10 @@ bool GUIPauseMenu::OnEvent(const SEvent& event) quitMenu(); m_gamecallback->changePassword(); return true; + case 262: + quitMenu(); + m_gamecallback->changeVolume(); + return true; case 260: // disconnect m_gamecallback->disconnect(); quitMenu(); diff --git a/src/guiPauseMenu.h b/src/guiPauseMenu.h index 4b15fc74f..578f2a324 100644 --- a/src/guiPauseMenu.h +++ b/src/guiPauseMenu.h @@ -29,6 +29,7 @@ public: virtual void exitToOS() = 0; virtual void disconnect() = 0; virtual void changePassword() = 0; + virtual void changeVolume() = 0; }; class GUIPauseMenu : public GUIModalMenu diff --git a/src/guiVolumeChange.cpp b/src/guiVolumeChange.cpp new file mode 100644 index 000000000..606aa0196 --- /dev/null +++ b/src/guiVolumeChange.cpp @@ -0,0 +1,181 @@ +/* +Part of Minetest-c55 +Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2011 Ciaran Gultnieks +Copyright (C) 2013 RealBadAngel, Maciej Kasatkin + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "guiVolumeChange.h" +#include "debug.h" +#include "serialization.h" +#include +#include +#include +#include +#include +#include +#include "main.h" + +#include "gettext.h" + +const int ID_soundText1 = 263; +const int ID_soundText2 = 264; +const int ID_soundExitButton = 265; +const int ID_soundSlider = 266; + +GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, + Client* client +): + GUIModalMenu(env, parent, id, menumgr), + m_client(client) +{ +} + +GUIVolumeChange::~GUIVolumeChange() +{ + removeChildren(); +} + +void GUIVolumeChange::removeChildren() +{ + { + gui::IGUIElement *e = getElementFromId(ID_soundText1); + if(e != NULL) + e->remove(); + } + { + gui::IGUIElement *e = getElementFromId(ID_soundText2); + if(e != NULL) + e->remove(); + } + { + gui::IGUIElement *e = getElementFromId(ID_soundExitButton); + if(e != NULL) + e->remove(); + } + { + gui::IGUIElement *e = getElementFromId(ID_soundSlider); + if(e != NULL) + e->remove(); + } +} + +void GUIVolumeChange::regenerateGui(v2u32 screensize) +{ + /* + Remove stuff + */ + removeChildren(); + + /* + Calculate new sizes and positions + */ + core::rect rect( + screensize.X/2 - 380/2, + screensize.Y/2 - 200/2, + screensize.X/2 + 380/2, + screensize.Y/2 + 200/2 + ); + + DesiredRect = rect; + recalculateAbsolutePosition(false); + + v2s32 size = rect.getSize(); + v2s32 topleft_client(40, 0); + v2s32 size_client = size - v2s32(40, 0); + int volume=(int)(g_settings->getFloat("sound_volume")*100); + /* + Add stuff + */ + changeCtype(""); + { + core::rect rect(0, 0, 120, 20); + rect = rect + v2s32(size.X/2-60, size.Y/2-35); + Environment->addStaticText(wgettext("Sound Volume: "), rect, false, + true, this, ID_soundText1); + } + { + core::rect rect(0, 0, 30, 20); + rect = rect + v2s32(size.X/2+40, size.Y/2-35); + Environment->addStaticText(core::stringw(volume).c_str(), rect, false, + true, this, ID_soundText2); + } + { + core::rect rect(0, 0, 80, 30); + rect = rect + v2s32(size.X/2-80/2, size.Y/2+55); + Environment->addButton(rect, this, ID_soundExitButton, + wgettext("Exit")); + } + { + core::rect rect(0, 0, 300, 20); + rect = rect + v2s32(size.X/2-150, size.Y/2); + gui::IGUIScrollBar *e = Environment->addScrollBar(true, + rect, this, ID_soundSlider); + e->setMax(100); + e->setPos(volume); + } + changeCtype(""); +} + +void GUIVolumeChange::drawMenu() +{ + gui::IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + video::IVideoDriver* driver = Environment->getVideoDriver(); + video::SColor bgcolor(140,0,0,0); + driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); + gui::IGUIElement::draw(); +} + +bool GUIVolumeChange::OnEvent(const SEvent& event) +{ + if(event.EventType==EET_KEY_INPUT_EVENT) + { + if(event.KeyInput.Key==KEY_ESCAPE && event.KeyInput.PressedDown) + { + quitMenu(); + return true; + } + if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown) + { + quitMenu(); + return true; + } + } + if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED) + { + if (event.GUIEvent.Caller->getID() == ID_soundExitButton) + { + quitMenu(); + return true; + } + } + if(event.GUIEvent.EventType==gui::EGET_SCROLL_BAR_CHANGED) + { + if (event.GUIEvent.Caller->getID() == ID_soundSlider) + { + s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos(); + g_settings->setFloat("sound_volume",(float)pos/100); + gui::IGUIElement *e = getElementFromId(ID_soundText2); + e->setText( core::stringw(pos).c_str() ); + return true; + } + } + return Parent ? Parent->OnEvent(event) : false; +} + diff --git a/src/guiVolumeChange.h b/src/guiVolumeChange.h new file mode 100644 index 000000000..a0a50f393 --- /dev/null +++ b/src/guiVolumeChange.h @@ -0,0 +1,53 @@ +/* +Part of Minetest-c55 +Copyright (C) 2010-11 celeron55, Perttu Ahola +Copyright (C) 2011 Ciaran Gultnieks +Copyright (C) 2013 RealBadAngel, Maciej Kasatkin + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef GUIVOLUMECHANGE_HEADER +#define GUIVOLUMECHANGE_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "modalMenu.h" +#include "client.h" +#include + +class GUIVolumeChange : public GUIModalMenu +{ +public: + GUIVolumeChange(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, + Client* client); + ~GUIVolumeChange(); + + void removeChildren(); + /* + Remove and re-add (or reposition) stuff + */ + void regenerateGui(v2u32 screensize); + + void drawMenu(); + + bool OnEvent(const SEvent& event); + +private: + Client* m_client; + +}; + +#endif + diff --git a/src/mainmenumanager.h b/src/mainmenumanager.h index a37a8300f..dc2b287d4 100644 --- a/src/mainmenumanager.h +++ b/src/mainmenumanager.h @@ -94,6 +94,7 @@ public: MainGameCallback(IrrlichtDevice *a_device): disconnect_requested(false), changepassword_requested(false), + changevolume_requested(false), device(a_device) { } @@ -113,8 +114,14 @@ public: changepassword_requested = true; } + virtual void changeVolume() + { + changevolume_requested = true; + } + bool disconnect_requested; bool changepassword_requested; + bool changevolume_requested; IrrlichtDevice *device; }; From bc2e9dbaf2fe31a1aa8282c0ac7c87aee490c75c Mon Sep 17 00:00:00 2001 From: PilzAdam Date: Sat, 23 Feb 2013 22:48:02 +0100 Subject: [PATCH 25/43] Read server_announce from minetest.conf for GUI --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index 9287ec760..7bbbf082d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1481,6 +1481,7 @@ int main(int argc, char *argv[]) driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, menudata.mip_map); menudata.creative_mode = g_settings->getBool("creative_mode"); menudata.enable_damage = g_settings->getBool("enable_damage"); + menudata.enable_public = g_settings->getBool("server_announce"); // Default to selecting nothing menudata.selected_world = -1; // Get world listing for the menu From f0d70d4e86ddeae33939744535f0732434ad422e Mon Sep 17 00:00:00 2001 From: RealBadAngel Date: Sun, 24 Feb 2013 02:26:25 +0100 Subject: [PATCH 26/43] Add seed parameter for default and L-system trees --- doc/lua_api.txt | 1 + src/content_abm.cpp | 2 +- src/mapgen.cpp | 7 ++++--- src/scriptapi.cpp | 1 + src/treegen.cpp | 50 ++++++++++++++++++++++----------------------- src/treegen.h | 8 +++++--- 6 files changed, 37 insertions(+), 32 deletions(-) diff --git a/doc/lua_api.txt b/doc/lua_api.txt index af70a1047..005d7c010 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1093,6 +1093,7 @@ treedef={ thin_branches, - boolean true -> use thin (1 node) branches fruit, - string fruit node name fruit_chance, - num chance (0-100) to replace leaves with fruit node + seed, - num random seed } Key for Special L-System Symbols used in Axioms diff --git a/src/content_abm.cpp b/src/content_abm.cpp index ce1751117..0dcf862fc 100644 --- a/src/content_abm.cpp +++ b/src/content_abm.cpp @@ -119,7 +119,7 @@ public: v3s16 tree_blockp = getNodeBlockPos(tree_p); vmanip.initialEmerge(tree_blockp - v3s16(1,1,1), tree_blockp + v3s16(1,1,1)); bool is_apple_tree = myrand()%4 == 0; - treegen::make_tree(vmanip, tree_p, is_apple_tree, ndef); + treegen::make_tree(vmanip, tree_p, is_apple_tree, ndef, myrand()); vmanip.blitBackAll(&modified_blocks); // update lighting diff --git a/src/mapgen.cpp b/src/mapgen.cpp index b19073e90..a2d920bbc 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -2251,6 +2251,7 @@ void make_block(BlockMakeData *data) */ assert(central_area_size.X == central_area_size.Z); { + PseudoRandom ps (blockseed); // Divide area into parts s16 div = 8; s16 sidelen = central_area_size.X / div; @@ -2278,8 +2279,8 @@ void make_block(BlockMakeData *data) // Put trees in random places on part of division for(u32 i=0; igetId(fruit); getintfield(L, 3, "fruit_chance",tree_def.fruit_chance); } + getintfield(L, 3, "seed", tree_def.seed); } else return 0; diff --git a/src/treegen.cpp b/src/treegen.cpp index 7530843d3..948716dcf 100644 --- a/src/treegen.cpp +++ b/src/treegen.cpp @@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "util/numeric.h" #include "util/mathconstants.h" -#include "noise.h" #include "map.h" #include "environment.h" #include "nodedef.h" @@ -29,15 +28,15 @@ with this program; if not, write to the Free Software Foundation, Inc., namespace treegen { - void make_tree(ManualMapVoxelManipulator &vmanip, v3s16 p0, - bool is_apple_tree, INodeDefManager *ndef) + bool is_apple_tree, INodeDefManager *ndef,int seed) { MapNode treenode(ndef->getId("mapgen_tree")); MapNode leavesnode(ndef->getId("mapgen_leaves")); MapNode applenode(ndef->getId("mapgen_apple")); - s16 trunk_h = myrand_range(4, 5); + PseudoRandom ps(seed); + s16 trunk_h = ps.range(4, 5); v3s16 p1 = p0; for(s16 ii=0; iigetId("mapgen_dirt")); + PseudoRandom ps(tree_definition.seed+14002); // chance of inserting abcd rules double prop_a = 9; double prop_b = 8; @@ -159,13 +159,13 @@ void make_ltree(ManualMapVoxelManipulator &vmanip, v3s16 p0, INodeDefManager *nd //randomize tree growth level, minimum=2 s16 iterations = tree_definition.iterations; if (tree_definition.iterations_random_level>0) - iterations -= myrand_range(0,tree_definition.iterations_random_level); + iterations -= ps.range(0,tree_definition.iterations_random_level); if (iterations<2) iterations=2; s16 MAX_ANGLE_OFFSET = 5; double angle_in_radians = (double)tree_definition.angle*M_PI/180; - double angleOffset_in_radians = (s16)(myrand_range(0,1)%MAX_ANGLE_OFFSET)*M_PI/180; + double angleOffset_in_radians = (s16)(ps.range(0,1)%MAX_ANGLE_OFFSET)*M_PI/180; //initialize rotation matrix, position and stacks for branches core::matrix4 rotation; @@ -200,19 +200,19 @@ void make_ltree(ManualMapVoxelManipulator &vmanip, v3s16 p0, INodeDefManager *nd temp+=tree_definition.rules_d; break; case 'a': - if (prop_a >= myrand_range(1,10)) + if (prop_a >= ps.range(1,10)) temp+=tree_definition.rules_a; break; case 'b': - if (prop_b >= myrand_range(1,10)) + if (prop_b >= ps.range(1,10)) temp+=tree_definition.rules_b; break; case 'c': - if (prop_c >= myrand_range(1,10)) + if (prop_c >= ps.range(1,10)) temp+=tree_definition.rules_c; break; case 'd': - if (prop_d >= myrand_range(1,10)) + if (prop_d >= ps.range(1,10)) temp+=tree_definition.rules_d; break; default: @@ -324,10 +324,10 @@ void make_ltree(ManualMapVoxelManipulator &vmanip, v3s16 p0, INodeDefManager *nd for(z=-size; z<=size; z++) if (abs(x) == size && abs(y) == size && abs(z) == size) { - tree_leaves_placement(vmanip,v3f(position.X+x+1,position.Y+y,position.Z+z),tree_definition); - tree_leaves_placement(vmanip,v3f(position.X+x-1,position.Y+y,position.Z+z),tree_definition); - tree_leaves_placement(vmanip,v3f(position.X+x,position.Y+y,position.Z+z+1),tree_definition); - tree_leaves_placement(vmanip,v3f(position.X+x,position.Y+y,position.Z+z-1),tree_definition); + tree_leaves_placement(vmanip,v3f(position.X+x+1,position.Y+y,position.Z+z),ps.next(), tree_definition); + tree_leaves_placement(vmanip,v3f(position.X+x-1,position.Y+y,position.Z+z),ps.next(), tree_definition); + tree_leaves_placement(vmanip,v3f(position.X+x,position.Y+y,position.Z+z+1),ps.next(), tree_definition); + tree_leaves_placement(vmanip,v3f(position.X+x,position.Y+y,position.Z+z-1),ps.next(), tree_definition); } } dir = v3f(1,0,0); @@ -335,7 +335,7 @@ void make_ltree(ManualMapVoxelManipulator &vmanip, v3s16 p0, INodeDefManager *nd position+=dir; break; case 'f': - tree_single_leaves_placement(vmanip,v3f(position.X,position.Y,position.Z),tree_definition); + tree_single_leaves_placement(vmanip,v3f(position.X,position.Y,position.Z),ps.next() ,tree_definition); dir = v3f(1,0,0); dir = transposeMatrix(rotation,dir); position+=dir; @@ -421,10 +421,10 @@ void tree_trunk_placement(ManualMapVoxelManipulator &vmanip, v3f p0, } void tree_leaves_placement(ManualMapVoxelManipulator &vmanip, v3f p0, - TreeDef &tree_definition) + PseudoRandom ps ,TreeDef &tree_definition) { MapNode leavesnode=tree_definition.leavesnode; - if (myrand_range(1,100) > 100-tree_definition.leaves2_chance) + if (ps.range(1,100) > 100-tree_definition.leaves2_chance) leavesnode=tree_definition.leaves2node; v3s16 p1 = v3s16(myround(p0.X),myround(p0.Y),myround(p0.Z)); if(vmanip.m_area.contains(p1) == false) @@ -435,20 +435,20 @@ void tree_leaves_placement(ManualMapVoxelManipulator &vmanip, v3f p0, return; if (tree_definition.fruit_chance>0) { - if (myrand_range(1,100) > 100-tree_definition.fruit_chance) + if (ps.range(1,100) > 100-tree_definition.fruit_chance) vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode; else vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; } - else if (myrand_range(1,100) > 20) + else if (ps.range(1,100) > 20) vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; } void tree_single_leaves_placement(ManualMapVoxelManipulator &vmanip, v3f p0, - TreeDef &tree_definition) + PseudoRandom ps, TreeDef &tree_definition) { MapNode leavesnode=tree_definition.leavesnode; - if (myrand_range(1,100) > 100-tree_definition.leaves2_chance) + if (ps.range(1,100) > 100-tree_definition.leaves2_chance) leavesnode=tree_definition.leaves2node; v3s16 p1 = v3s16(myround(p0.X),myround(p0.Y),myround(p0.Z)); if(vmanip.m_area.contains(p1) == false) diff --git a/src/treegen.h b/src/treegen.h index cb365f4be..0068219dd 100644 --- a/src/treegen.h +++ b/src/treegen.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define TREEGEN_HEADER #include +#include "noise.h" class ManualMapVoxelManipulator; class INodeDefManager; @@ -47,11 +48,12 @@ std::string trunk_type; bool thin_branches; MapNode fruitnode; int fruit_chance; +int seed; }; // Add default tree void make_tree(ManualMapVoxelManipulator &vmanip, v3s16 p0, - bool is_apple_tree, INodeDefManager *ndef); + bool is_apple_tree, INodeDefManager *ndef,int seed); // Add L-Systems tree (used by engine) void make_ltree(ManualMapVoxelManipulator &vmanip, v3s16 p0, INodeDefManager *ndef, @@ -66,9 +68,9 @@ int fruit_chance; void tree_trunk_placement(ManualMapVoxelManipulator &vmanip, v3f p0, TreeDef &tree_definition); void tree_leaves_placement(ManualMapVoxelManipulator &vmanip, v3f p0, - TreeDef &tree_definition); + PseudoRandom ps, TreeDef &tree_definition); void tree_single_leaves_placement(ManualMapVoxelManipulator &vmanip, v3f p0, - TreeDef &tree_definition); + PseudoRandom ps, TreeDef &tree_definition); void tree_fruit_placement(ManualMapVoxelManipulator &vmanip, v3f p0, TreeDef &tree_definition); irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle,v3f axis); From b90e431fc785961e7913023999d1f570ad7ca151 Mon Sep 17 00:00:00 2001 From: proller Date: Sun, 24 Feb 2013 18:39:07 +0400 Subject: [PATCH 27/43] new adjustable finite liquid --- games/minimal/mods/default/mapgen.lua | 10 +- minetest.conf.example | 8 + src/content_abm.cpp | 36 ++- src/content_mapblock.cpp | 4 +- src/defaultsettings.cpp | 5 + src/map.cpp | 312 ++++++++++++++++++++++++++ src/map.h | 4 + src/mapnode.h | 5 + src/server.cpp | 7 +- src/server.h | 1 + 10 files changed, 384 insertions(+), 8 deletions(-) diff --git a/games/minimal/mods/default/mapgen.lua b/games/minimal/mods/default/mapgen.lua index af94505a0..115bb1458 100644 --- a/games/minimal/mods/default/mapgen.lua +++ b/games/minimal/mods/default/mapgen.lua @@ -27,7 +27,7 @@ minetest.register_alias("mapgen_mese", "default:mese") -- Ore generation -- -local function generate_ore(name, wherein, minp, maxp, seed, chunks_per_volume, ore_per_chunk, height_min, height_max) +local function generate_ore(name, wherein, minp, maxp, seed, chunks_per_volume, ore_per_chunk, height_min, height_max, param2) if maxp.y < height_min or minp.y > height_max then return end @@ -57,7 +57,7 @@ local function generate_ore(name, wherein, minp, maxp, seed, chunks_per_volume, local z2 = z0+z1 local p2 = {x=x2, y=y2, z=z2} if minetest.env:get_node(p2).name == wherein then - minetest.env:set_node(p2, {name=name}) + minetest.env:set_node(p2, {name=name, param2=param2}) end end end @@ -110,5 +110,11 @@ minetest.register_on_generated(function(minp, maxp, seed) end end end + if minetest.setting_get("liquid_finite") then + generate_ore("default:water_source", "default:stone", minp, maxp, seed+42, 1/24/24/24, 4, -100, -10, 128) + generate_ore("default:water_source", "default:stone", minp, maxp, seed+42, 1/28/28/28, 3, -10000, -101, 128) + generate_ore("default:lava_source", "default:stone", minp, maxp, seed+43, 1/38/38/38, 2, -500, -100, 128) + generate_ore("default:lava_source", "default:stone", minp, maxp, seed+43, 1/30/30/30, 4, -31000, -501, 128) + end end) diff --git a/minetest.conf.example b/minetest.conf.example index c36fe05ff..8e95d7e60 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -90,6 +90,14 @@ #enable_fog = true # Enable a bit lower water surface; disable for speed (not quite optimized) #new_style_water = false +# Constant volume liquids +#liquid_finite = false +# Update liquids every .. recommend for finite: 0.2 +#liquid_update = 1.0 +# When finite liquid: relax flowing blocks to source if level near max and N nearby source blocks, more realistic, but not true constant. values: 0,1,2,3,4 : 0 - disable, 1 - most aggresive +#liquid_relax = 1 +# optimization: faster cave flood (and not true constant) +#liquid_fast_flood = 1 # Enable nice leaves; disable for speed #new_style_leaves = true # Enable smooth lighting with simple ambient occlusion; diff --git a/src/content_abm.cpp b/src/content_abm.cpp index 0dcf862fc..971ac4f34 100644 --- a/src/content_abm.cpp +++ b/src/content_abm.cpp @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "mapblock.h" // For getNodeBlockPos #include "treegen.h" // For treegen::make_tree +#include "main.h" // for g_settings #include "map.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" @@ -146,11 +147,42 @@ public: } }; +class LiquidFlowABM : public ActiveBlockModifier +{ +private: + std::set contents; + +public: + LiquidFlowABM(ServerEnvironment *env, INodeDefManager *nodemgr) + { + std::set liquids; + nodemgr->getIds("group:liquid", liquids); + for(std::set::const_iterator k = liquids.begin(); k != liquids.end(); k++) + contents.insert(nodemgr->get(*k).liquid_alternative_flowing); + + } + virtual std::set getTriggerContents() + { + return contents; + } + virtual float getTriggerInterval() + { return 10.0; } + virtual u32 getTriggerChance() + { return 10; } + virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n) + { + ServerMap *map = &env->getServerMap(); + if (map->transforming_liquid_size() < 500) + map->transforming_liquid_add(p); + //if ((*map).m_transforming_liquid.size() < 500) (*map).m_transforming_liquid.push_back(p); + } +}; + void add_legacy_abms(ServerEnvironment *env, INodeDefManager *nodedef) { env->addActiveBlockModifier(new GrowGrassABM()); env->addActiveBlockModifier(new RemoveGrassABM()); env->addActiveBlockModifier(new MakeTreesFromSaplingsABM()); + if (g_settings->getBool("liquid_finite")) + env->addActiveBlockModifier(new LiquidFlowABM(env, nodedef)); } - - diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index 290890490..e224f0b06 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -261,7 +261,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data, level = (-0.5+node_liquid_level) * BS; else if(n2.getContent() == c_flowing) level = (-0.5 + ((float)(n2.param2&LIQUID_LEVEL_MASK) - + 0.5) / 8.0 * node_liquid_level) * BS; + + 0.5) / (float)LIQUID_LEVEL_SOURCE * node_liquid_level) * BS; // Check node above neighbor. // NOTE: This doesn't get executed if neighbor @@ -324,7 +324,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data, } } if(air_count >= 2) - cornerlevel = -0.5*BS; + cornerlevel = -0.5*BS+0.1; else if(valid_count > 0) cornerlevel /= valid_count; corner_levels[i] = cornerlevel; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6cdaaa496..f2c9886b4 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -201,6 +201,11 @@ void set_default_settings(Settings *settings) //mapgen related things settings->setDefault("mg_name", "v6"); settings->setDefault("water_level", "1"); + settings->setDefault("liquid_finite", "false"); + settings->setDefault("liquid_update", "1.0"); + settings->setDefault("liquid_relax", "1"); + settings->setDefault("liquid_fast_flood", "1"); + settings->setDefault("chunksize", "5"); settings->setDefault("mg_flags", "trees, caves, v6_biome_blend"); settings->setDefault("mgv6_freq_desert", "0.45"); diff --git a/src/map.cpp b/src/map.cpp index 717b0cf9b..7272451a4 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1613,10 +1613,322 @@ struct NodeNeighbor { MapNode n; NeighborType t; v3s16 p; + bool l; //can liquid + bool i; //infinity }; +void Map::transforming_liquid_add(v3s16 p) { + m_transforming_liquid.push_back(p); +} + +s32 Map::transforming_liquid_size() { + return m_transforming_liquid.size(); +} + +const v3s16 g_7dirs[7] = +{ + // +right, +top, +back + v3s16( 0,-1, 0), // bottom + v3s16( 0, 0, 0), // self + v3s16( 0, 0, 1), // back + v3s16( 0, 0,-1), // front + v3s16( 1, 0, 0), // right + v3s16(-1, 0, 0), // left + v3s16( 0, 1, 0) // top +}; + +#define D_BOTTOM 0 +#define D_TOP 6 +#define D_SELF 1 + +void Map::transformLiquidsFinite(core::map & modified_blocks) +{ + INodeDefManager *nodemgr = m_gamedef->ndef(); + + DSTACK(__FUNCTION_NAME); + //TimeTaker timer("transformLiquids()"); + + u32 loopcount = 0; + u32 initial_size = m_transforming_liquid.size(); + + u8 relax = g_settings->getS16("liquid_relax"); + bool fast_flood = g_settings->getS16("liquid_fast_flood"); + int water_level = g_settings->getS16("water_level"); + + /*if(initial_size != 0) + infostream<<"transformLiquids(): initial_size="< must_reflow, must_reflow_second; + + // List of MapBlocks that will require a lighting update (due to lava) + core::map lighting_modified_blocks; + + while(m_transforming_liquid.size() > 0) + { + // This should be done here so that it is done when continue is used + if(loopcount >= initial_size || loopcount >= 1000) + break; + loopcount++; + /* + Get a queued transforming liquid node + */ + v3s16 p0 = m_transforming_liquid.pop_front(); + u16 total_level = 0; + NodeNeighbor neighbors[7]; // surrounding flowing liquid nodes + s8 liquid_levels[7] = {-1, -1, -1, -1, -1, -1, -1}; // current level of every block + s8 liquid_levels_want[7] = {-1, -1, -1, -1, -1, -1, -1}; // target levels + s8 can_liquid_same_level = 0; + content_t liquid_kind = CONTENT_IGNORE; + content_t liquid_kind_flowing = CONTENT_IGNORE; + /* + Collect information about the environment + */ + const v3s16 *dirs = g_7dirs; + for (u16 i = 0; i < 7; i++) { + NeighborType nt = NEIGHBOR_SAME_LEVEL; + switch (i) { + case D_TOP: + nt = NEIGHBOR_UPPER; + break; + case D_BOTTOM: + nt = NEIGHBOR_LOWER; + break; + } + v3s16 npos = p0 + dirs[i]; + + neighbors[i].n = getNodeNoEx(npos); + neighbors[i].t = nt; + neighbors[i].p = npos; + neighbors[i].l = 0; + neighbors[i].i = 0; + NodeNeighbor & nb = neighbors[i]; + + switch (nodemgr->get(nb.n.getContent()).liquid_type) { + case LIQUID_NONE: + if (nb.n.getContent() == CONTENT_AIR) { + liquid_levels[i] = 0; + nb.l = 1; + } + break; + case LIQUID_SOURCE: + // if this node is not (yet) of a liquid type, choose the first liquid type we encounter + if (liquid_kind_flowing == CONTENT_IGNORE) + liquid_kind_flowing = nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_flowing); + if (liquid_kind == CONTENT_IGNORE) + liquid_kind = nb.n.getContent(); + if (nb.n.getContent() == liquid_kind) { + liquid_levels[i] = LIQUID_LEVEL_SOURCE; + nb.l = 1; + nb.i = (nb.n.param2 & LIQUID_INFINITY_MASK); + } + break; + case LIQUID_FLOWING: + // if this node is not (yet) of a liquid type, choose the first liquid type we encounter + if (liquid_kind_flowing == CONTENT_IGNORE) + liquid_kind_flowing = nb.n.getContent(); + if (liquid_kind == CONTENT_IGNORE) + liquid_kind = nodemgr->getId(nodemgr->get(nb.n).liquid_alternative_source); + if (nb.n.getContent() == liquid_kind_flowing) { + liquid_levels[i] = (nb.n.param2 & LIQUID_LEVEL_MASK); + nb.l = 1; + } + break; + } + if (nb.l && nb.t == NEIGHBOR_SAME_LEVEL) ++can_liquid_same_level; + if (liquid_levels[i] > 0) total_level += liquid_levels[i]; + + /* + infostream << "get node i=" <<(int)i<<" " << PP(npos) << " c="<ndef(); DSTACK(__FUNCTION_NAME); diff --git a/src/map.h b/src/map.h index 420fc29ca..a0305b790 100644 --- a/src/map.h +++ b/src/map.h @@ -300,6 +300,7 @@ public: virtual void PrintInfo(std::ostream &out); void transformLiquids(core::map & modified_blocks); + void transformLiquidsFinite(core::map & modified_blocks); /* Node metadata @@ -328,6 +329,9 @@ public: Variables */ + void transforming_liquid_add(v3s16 p); + s32 transforming_liquid_size(); + protected: std::ostream &m_dout; // A bit deprecated, could be removed diff --git a/src/mapnode.h b/src/mapnode.h index de2529e4d..56dfa6310 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -67,10 +67,15 @@ enum LightBank #define LIQUID_LEVEL_MASK 0x07 #define LIQUID_FLOW_DOWN_MASK 0x08 +//#define LIQUID_LEVEL_MASK 0x3f // better finite water +//#define LIQUID_FLOW_DOWN_MASK 0x40 // not used when finite water + /* maximum amount of liquid in a block */ #define LIQUID_LEVEL_MAX LIQUID_LEVEL_MASK #define LIQUID_LEVEL_SOURCE (LIQUID_LEVEL_MAX+1) +#define LIQUID_INFINITY_MASK 0x80 //0b10000000 + /* This is the stuff what the whole world consists of. */ diff --git a/src/server.cpp b/src/server.cpp index 94a4787f8..138f288c6 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -961,6 +961,7 @@ Server::Server( m_ignore_map_edit_events_peer_id(0) { m_liquid_transform_timer = 0.0; + m_liquid_transform_every = 1.0; m_print_info_timer = 0.0; m_masterserver_timer = 0.0; m_objectdata_timer = 0.0; @@ -1136,6 +1137,8 @@ Server::Server( Add some test ActiveBlockModifiers to environment */ add_legacy_abms(m_env, m_nodedef); + + m_liquid_transform_every = g_settings->getFloat("liquid_update"); } Server::~Server() @@ -1452,9 +1455,9 @@ void Server::AsyncRunStep() /* Transform liquids */ m_liquid_transform_timer += dtime; - if(m_liquid_transform_timer >= 1.00) + if(m_liquid_transform_timer >= m_liquid_transform_every) { - m_liquid_transform_timer -= 1.00; + m_liquid_transform_timer -= m_liquid_transform_every; JMutexAutoLock lock(m_env_mutex); diff --git a/src/server.h b/src/server.h index 22c7cf2bb..16bda4372 100644 --- a/src/server.h +++ b/src/server.h @@ -719,6 +719,7 @@ private: // Some timers float m_liquid_transform_timer; + float m_liquid_transform_every; float m_print_info_timer; float m_masterserver_timer; float m_objectdata_timer; From 22b0039ad411169024b8275a082732a7b0de4c03 Mon Sep 17 00:00:00 2001 From: PilzAdam Date: Sun, 24 Feb 2013 18:37:38 +0100 Subject: [PATCH 28/43] Update README --- README.txt | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.txt b/README.txt index d6344bd2c..edd11f3fe 100644 --- a/README.txt +++ b/README.txt @@ -4,22 +4,22 @@ Minetest An InfiniMiner/Minecraft inspired game. Copyright (c) 2010-2012 Perttu Ahola -and ther contributors (see source file comments and the version control log) +and contributors (see source file comments and the version control log) In case you downloaded the source code: --------------------------------------- If you downloaded the Minetest Engine source code in which this file is contained, you probably want to download the minetest_game project too: - https://github.com/celeron55/minetest_game/ + https://github.com/minetest/minetest_game/ See the README.txt in it. Further documentation ---------------------- - Website: http://minetest.net/ - Wiki: http://wiki.minetest.com/ -- Developer wiki: http://minetest.net/wiki/ -- Forum: http://minetest.net/forum/ -- Github: https://github.com/celeron55/minetest/ +- Developer wiki: http://dev.minetest.net/ +- Forum: http://forum.minetest.net/ +- Github: https://github.com/minetest/minetest/ - doc/ directory of source distribution This game is not finished @@ -41,7 +41,7 @@ Default Controls Paths ------ $bin - Compiled binaries -$share - Cistributed read-only data +$share - Distributed read-only data $user - User-created modifiable data Windows .zip / RUN_IN_PLACE source: @@ -83,15 +83,15 @@ Install dependencies. Here's an example for Debian/Ubuntu: $ apt-get install build-essential libirrlicht-dev cmake libbz2-dev libpng12-dev libjpeg8-dev libxxf86vm-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev Download source, extract (this is the URL to the latest of source repository, which might not work at all times): -$ wget https://github.com/celeron55/minetest/tarball/master -O master.tar.gz +$ wget https://github.com/minetest/minetest/tarball/master -O master.tar.gz $ tar xf master.tar.gz -$ cd celeron55-minetest-286edd4 (or similar) +$ cd minetest-minetest-286edd4 (or similar) Download minetest_game (otherwise only the "Minimal development test" game is available) $ cd games/ -$ wget https://github.com/celeron55/minetest_game/tarball/master -O master.tar.gz +$ wget https://github.com/minetest/minetest_game/tarball/master -O master.tar.gz $ tar xf master.tar.gz -$ mv celeron55-minetest_game-* minetest_game +$ mv minetest-minetest_game-* minetest_game $ cd .. Build a version that runs directly from the source directory: @@ -126,7 +126,7 @@ Compiling on Windows: http://www.winimage.com/zLibDll/index.html * Zlib library (zlibwapi.lib and zlibwapi.dll from zlib125dll.zip): http://www.winimage.com/zLibDll/index.html - * Optional: gettext bibrary and tools: + * Optional: gettext library and tools: http://gnuwin32.sourceforge.net/downlinks/gettext.php - This is used for other UI languages. Feel free to leave it out. * And, of course, Minetest: From 497ff1ecd64c8908f988e15ca879824f2781e3fd Mon Sep 17 00:00:00 2001 From: PilzAdam Date: Sun, 24 Feb 2013 18:40:43 +0100 Subject: [PATCH 29/43] Change Minetest-c55 to Minetest --- doc/protocol.txt | 2 +- old/changelog.txt | 2 +- src/activeobject.h | 2 +- src/ban.cpp | 2 +- src/ban.h | 2 +- src/biome.cpp | 2 +- src/biome.h | 2 +- src/camera.cpp | 2 +- src/camera.h | 2 +- src/chat.cpp | 2 +- src/chat.h | 2 +- src/client.cpp | 2 +- src/client.h | 2 +- src/clientmap.cpp | 2 +- src/clientmap.h | 2 +- src/clientobject.cpp | 2 +- src/clientobject.h | 2 +- src/clientserver.cpp | 2 +- src/clientserver.h | 2 +- src/clientsimpleobject.h | 2 +- src/clouds.cpp | 2 +- src/clouds.h | 2 +- src/collision.cpp | 2 +- src/collision.h | 2 +- src/connection.cpp | 2 +- src/connection.h | 2 +- src/constants.h | 2 +- src/content_abm.cpp | 2 +- src/content_abm.h | 2 +- src/content_cao.cpp | 2 +- src/content_cao.h | 2 +- src/content_cso.cpp | 2 +- src/content_cso.h | 2 +- src/content_mapblock.cpp | 2 +- src/content_mapblock.h | 2 +- src/content_mapnode.cpp | 2 +- src/content_mapnode.h | 2 +- src/content_nodemeta.cpp | 2 +- src/content_nodemeta.h | 2 +- src/content_object.h | 2 +- src/content_sao.cpp | 2 +- src/content_sao.h | 2 +- src/craftdef.cpp | 2 +- src/craftdef.h | 2 +- src/daynightratio.h | 2 +- src/debug.cpp | 2 +- src/debug.h | 2 +- src/defaultsettings.cpp | 2 +- src/defaultsettings.h | 2 +- src/environment.cpp | 2 +- src/environment.h | 2 +- src/event.h | 2 +- src/event_manager.h | 2 +- src/exceptions.h | 2 +- src/farmesh.cpp | 2 +- src/farmesh.h | 2 +- src/filecache.cpp | 2 +- src/filecache.h | 2 +- src/filesys.cpp | 2 +- src/filesys.h | 2 +- src/game.cpp | 2 +- src/game.h | 2 +- src/gamedef.h | 2 +- src/genericobject.cpp | 2 +- src/genericobject.h | 2 +- src/gettime.h | 2 +- src/guiChatConsole.cpp | 2 +- src/guiChatConsole.h | 2 +- src/guiConfigureWorld.cpp | 2 +- src/guiConfigureWorld.h | 2 +- src/guiConfirmMenu.cpp | 2 +- src/guiConfirmMenu.h | 2 +- src/guiCreateWorld.cpp | 2 +- src/guiCreateWorld.h | 2 +- src/guiDeathScreen.cpp | 2 +- src/guiDeathScreen.h | 2 +- src/guiFormSpecMenu.cpp | 2 +- src/guiFormSpecMenu.h | 2 +- src/guiKeyChangeMenu.cpp | 2 +- src/guiKeyChangeMenu.h | 2 +- src/guiMainMenu.cpp | 2 +- src/guiMainMenu.h | 2 +- src/guiMessageMenu.cpp | 2 +- src/guiMessageMenu.h | 2 +- src/guiPasswordChange.cpp | 2 +- src/guiPasswordChange.h | 2 +- src/guiPauseMenu.cpp | 2 +- src/guiPauseMenu.h | 2 +- src/guiTextInputMenu.cpp | 2 +- src/guiTextInputMenu.h | 2 +- src/guiVolumeChange.cpp | 2 +- src/guiVolumeChange.h | 2 +- src/hex.h | 2 +- src/inventory.cpp | 2 +- src/inventory.h | 2 +- src/inventorymanager.cpp | 2 +- src/inventorymanager.h | 2 +- src/irr_aabb3d.h | 2 +- src/irr_v2d.h | 2 +- src/irr_v3d.h | 2 +- src/irrlichttypes.h | 2 +- src/irrlichttypes_bloated.h | 2 +- src/irrlichttypes_extrabloated.h | 2 +- src/itemdef.cpp | 2 +- src/itemdef.h | 2 +- src/itemgroup.h | 2 +- src/keycode.cpp | 2 +- src/keycode.h | 2 +- src/light.cpp | 2 +- src/light.h | 2 +- src/localplayer.cpp | 2 +- src/localplayer.h | 2 +- src/log.cpp | 2 +- src/log.h | 2 +- src/logoutputbuffer.h | 2 +- src/luaentity_common.h | 2 +- src/main.cpp | 2 +- src/main.h | 2 +- src/mainmenumanager.h | 2 +- src/map.cpp | 2 +- src/map.h | 2 +- src/mapblock.cpp | 2 +- src/mapblock.h | 2 +- src/mapblock_mesh.cpp | 2 +- src/mapblock_mesh.h | 2 +- src/mapchunk.h | 2 +- src/mapgen.cpp | 2 +- src/mapgen.h | 2 +- src/mapgen_v6.cpp | 2 +- src/mapgen_v6.h | 2 +- src/mapnode.cpp | 2 +- src/mapnode.h | 2 +- src/mapsector.cpp | 2 +- src/mapsector.h | 2 +- src/mesh.cpp | 2 +- src/mesh.h | 2 +- src/modalMenu.h | 2 +- src/modifiedstate.h | 2 +- src/mods.cpp | 2 +- src/mods.h | 2 +- src/nameidmapping.cpp | 2 +- src/nameidmapping.h | 2 +- src/nodedef.cpp | 2 +- src/nodedef.h | 2 +- src/nodemetadata.cpp | 2 +- src/nodemetadata.h | 2 +- src/nodetimer.cpp | 2 +- src/nodetimer.h | 2 +- src/noise.cpp | 2 +- src/noise.h | 2 +- src/object_properties.cpp | 2 +- src/object_properties.h | 2 +- src/particles.cpp | 2 +- src/particles.h | 2 +- src/player.cpp | 2 +- src/player.h | 2 +- src/porting.cpp | 2 +- src/porting.h | 2 +- src/profiler.h | 2 +- src/quicktune.cpp | 2 +- src/quicktune.h | 2 +- src/quicktune_shortcutter.h | 2 +- src/rollback.cpp | 2 +- src/rollback.h | 2 +- src/rollback_interface.cpp | 2 +- src/rollback_interface.h | 2 +- src/script.cpp | 2 +- src/script.h | 2 +- src/scriptapi.cpp | 2 +- src/scriptapi.h | 2 +- src/serialization.cpp | 2 +- src/serialization.h | 2 +- src/server.cpp | 2 +- src/server.h | 2 +- src/serverlist.cpp | 2 +- src/serverlist.h | 2 +- src/serverobject.cpp | 2 +- src/serverobject.h | 2 +- src/settings.h | 2 +- src/shader.cpp | 2 +- src/shader.h | 2 +- src/sky.h | 2 +- src/socket.cpp | 2 +- src/socket.h | 2 +- src/sound.cpp | 2 +- src/sound.h | 2 +- src/sound_openal.cpp | 2 +- src/sound_openal.h | 2 +- src/staticobject.cpp | 2 +- src/staticobject.h | 2 +- src/strfnd.h | 2 +- src/subgame.cpp | 2 +- src/subgame.h | 2 +- src/test.cpp | 2 +- src/test.h | 2 +- src/threads.h | 2 +- src/tile.cpp | 2 +- src/tile.h | 2 +- src/tool.cpp | 2 +- src/tool.h | 2 +- src/treegen.cpp | 2 +- src/treegen.h | 2 +- src/util/container.h | 2 +- src/util/directiontables.cpp | 2 +- src/util/directiontables.h | 2 +- src/util/numeric.cpp | 2 +- src/util/numeric.h | 2 +- src/util/pointedthing.cpp | 2 +- src/util/pointedthing.h | 2 +- src/util/pointer.h | 2 +- src/util/serialize.cpp | 2 +- src/util/serialize.h | 2 +- src/util/string.cpp | 2 +- src/util/string.h | 2 +- src/util/thread.h | 2 +- src/util/timetaker.cpp | 2 +- src/util/timetaker.h | 2 +- src/voxel.cpp | 2 +- src/voxel.h | 2 +- src/voxelalgorithms.cpp | 2 +- src/voxelalgorithms.h | 2 +- util/wireshark/minetest.lua | 2 +- 222 files changed, 222 insertions(+), 222 deletions(-) diff --git a/doc/protocol.txt b/doc/protocol.txt index 82dca59bf..160f15226 100644 --- a/doc/protocol.txt +++ b/doc/protocol.txt @@ -1,4 +1,4 @@ -Minetest-c55 protocol (incomplete, early draft): +Minetest protocol (incomplete, early draft): Updated 2011-06-18 A custom protocol over UDP. diff --git a/old/changelog.txt b/old/changelog.txt index b27fc506b..1750d71d1 100644 --- a/old/changelog.txt +++ b/old/changelog.txt @@ -1,4 +1,4 @@ -Minetest-c55 changelog +Minetest changelog ---------------------- This should contain all the major changes. For minor stuff, refer to the commit log of the repository. diff --git a/src/activeobject.h b/src/activeobject.h index 1108a19de..d131bcc33 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/ban.cpp b/src/ban.cpp index e080586a3..6bc84a3e8 100644 --- a/src/ban.cpp +++ b/src/ban.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/ban.h b/src/ban.h index f5b9c4440..8132c3a38 100644 --- a/src/ban.h +++ b/src/ban.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/biome.cpp b/src/biome.cpp index 34d51839f..67e5a9de7 100644 --- a/src/biome.cpp +++ b/src/biome.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 kwolekr, Ryan Kwolek This program is free software; you can redistribute it and/or modify diff --git a/src/biome.h b/src/biome.h index 265f1df44..6b40b9f81 100644 --- a/src/biome.h +++ b/src/biome.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 kwolekr, Ryan Kwolek This program is free software; you can redistribute it and/or modify diff --git a/src/camera.cpp b/src/camera.cpp index 33e85d069..7916affbd 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/camera.h b/src/camera.h index 642b8d2d0..5b06da322 100644 --- a/src/camera.h +++ b/src/camera.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/chat.cpp b/src/chat.cpp index b9d115bd0..0efda7eac 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/chat.h b/src/chat.h index 49de52985..5ba4af800 100644 --- a/src/chat.h +++ b/src/chat.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/client.cpp b/src/client.cpp index 415f07311..f02c14bc8 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/client.h b/src/client.h index 43fac9c9a..e3cc98c5b 100644 --- a/src/client.h +++ b/src/client.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clientmap.cpp b/src/clientmap.cpp index 0b30453b8..e48aef190 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clientmap.h b/src/clientmap.h index f8a69630e..a9a365c05 100644 --- a/src/clientmap.h +++ b/src/clientmap.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clientobject.cpp b/src/clientobject.cpp index e1dbaf627..809357fc1 100644 --- a/src/clientobject.cpp +++ b/src/clientobject.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clientobject.h b/src/clientobject.h index 852d2c76b..1d88f991f 100644 --- a/src/clientobject.h +++ b/src/clientobject.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clientserver.cpp b/src/clientserver.cpp index bd0a8ede0..dee0a68e2 100644 --- a/src/clientserver.cpp +++ b/src/clientserver.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clientserver.h b/src/clientserver.h index 7fb3e83d2..6a135a139 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clientsimpleobject.h b/src/clientsimpleobject.h index 9dd8cc472..b91727492 100644 --- a/src/clientsimpleobject.h +++ b/src/clientsimpleobject.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clouds.cpp b/src/clouds.cpp index 5b980a5ba..67dd550f4 100644 --- a/src/clouds.cpp +++ b/src/clouds.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/clouds.h b/src/clouds.h index a7cde42bc..d65f19385 100644 --- a/src/clouds.h +++ b/src/clouds.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/collision.cpp b/src/collision.cpp index eb948e3c3..ccd91be94 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/collision.h b/src/collision.h index 52a7bbb7d..331ff8f9d 100644 --- a/src/collision.h +++ b/src/collision.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/connection.cpp b/src/connection.cpp index ed5a752be..b8bde1872 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/connection.h b/src/connection.h index f99cd1bf9..4ccbfdd16 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/constants.h b/src/constants.h index 97e94b361..39537c83b 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_abm.cpp b/src/content_abm.cpp index 0dcf862fc..a71dddfab 100644 --- a/src/content_abm.cpp +++ b/src/content_abm.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_abm.h b/src/content_abm.h index beb9b2e16..d837b7f3c 100644 --- a/src/content_abm.h +++ b/src/content_abm.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_cao.cpp b/src/content_cao.cpp index d71911b95..57cdbc912 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_cao.h b/src/content_cao.h index 566117716..81fd84f3d 100644 --- a/src/content_cao.h +++ b/src/content_cao.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_cso.cpp b/src/content_cso.cpp index 666f17734..f85c23ed2 100644 --- a/src/content_cso.cpp +++ b/src/content_cso.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_cso.h b/src/content_cso.h index 0b467f6c4..bb4ce6100 100644 --- a/src/content_cso.h +++ b/src/content_cso.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index 290890490..7a14d0bc8 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_mapblock.h b/src/content_mapblock.h index 142c00edf..7e87835f7 100644 --- a/src/content_mapblock.h +++ b/src/content_mapblock.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_mapnode.cpp b/src/content_mapnode.cpp index c3fdb4a42..49ce57c21 100644 --- a/src/content_mapnode.cpp +++ b/src/content_mapnode.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_mapnode.h b/src/content_mapnode.h index 45c2b0763..89c7a9a63 100644 --- a/src/content_mapnode.h +++ b/src/content_mapnode.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_nodemeta.cpp b/src/content_nodemeta.cpp index 0e4c3dd75..0a56db396 100644 --- a/src/content_nodemeta.cpp +++ b/src/content_nodemeta.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_nodemeta.h b/src/content_nodemeta.h index d206c35ad..fdd6524e8 100644 --- a/src/content_nodemeta.h +++ b/src/content_nodemeta.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_object.h b/src/content_object.h index e4690d295..913337eb4 100644 --- a/src/content_object.h +++ b/src/content_object.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 196c10e61..5eed17eaf 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/content_sao.h b/src/content_sao.h index 065c6a039..894fe9699 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/craftdef.cpp b/src/craftdef.cpp index b15443607..143c295f5 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/craftdef.h b/src/craftdef.h index a2258c6e8..aa71f1a1e 100644 --- a/src/craftdef.h +++ b/src/craftdef.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/daynightratio.h b/src/daynightratio.h index 96c6729eb..46acab60c 100644 --- a/src/daynightratio.h +++ b/src/daynightratio.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/debug.cpp b/src/debug.cpp index 8b25cc22d..9af7a5a9f 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/debug.h b/src/debug.h index 3e8066f4d..9b9385983 100644 --- a/src/debug.h +++ b/src/debug.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6cdaaa496..73a0893e1 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/defaultsettings.h b/src/defaultsettings.h index faac7a886..1fecf8cf7 100644 --- a/src/defaultsettings.h +++ b/src/defaultsettings.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/environment.cpp b/src/environment.cpp index ebf5e9a63..b8bcc5af8 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/environment.h b/src/environment.h index d1e61967f..1242506a3 100644 --- a/src/environment.h +++ b/src/environment.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/event.h b/src/event.h index 9e7a0dbf5..41be9622b 100644 --- a/src/event.h +++ b/src/event.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/event_manager.h b/src/event_manager.h index 9bc8e35eb..c2e9f792f 100644 --- a/src/event_manager.h +++ b/src/event_manager.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/exceptions.h b/src/exceptions.h index 35a57d070..476699ecb 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/farmesh.cpp b/src/farmesh.cpp index 23f3db5f6..ef1e6a0ae 100644 --- a/src/farmesh.cpp +++ b/src/farmesh.cpp @@ -1,5 +1,5 @@ /* -Part of Minetest-c55 +Part of Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/farmesh.h b/src/farmesh.h index 2eaf7d486..03b727d30 100644 --- a/src/farmesh.h +++ b/src/farmesh.h @@ -1,5 +1,5 @@ /* -Part of Minetest-c55 +Part of Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/filecache.cpp b/src/filecache.cpp index 716c769aa..ec076ce23 100644 --- a/src/filecache.cpp +++ b/src/filecache.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola Copyright (C) 2012 Jonathan Neuschäfer diff --git a/src/filecache.h b/src/filecache.h index da7e59f6a..96ea7a963 100644 --- a/src/filecache.h +++ b/src/filecache.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola Copyright (C) 2012 Jonathan Neuschäfer diff --git a/src/filesys.cpp b/src/filesys.cpp index bf26c8dac..b956412b5 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/filesys.h b/src/filesys.h index b7e6f3715..2d2ad9f3d 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/game.cpp b/src/game.cpp index 86909ccf0..d4f2a70af 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/game.h b/src/game.h index b74a7a8da..e69714486 100644 --- a/src/game.h +++ b/src/game.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/gamedef.h b/src/gamedef.h index 6fc99b9f1..76de9f777 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/genericobject.cpp b/src/genericobject.cpp index 398b07feb..6946dff71 100644 --- a/src/genericobject.cpp +++ b/src/genericobject.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/genericobject.h b/src/genericobject.h index b69c24b48..b4d5125c5 100644 --- a/src/genericobject.h +++ b/src/genericobject.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/gettime.h b/src/gettime.h index b5cb830cc..daeec0e46 100644 --- a/src/gettime.h +++ b/src/gettime.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp index 13883901e..24c302b24 100644 --- a/src/guiChatConsole.cpp +++ b/src/guiChatConsole.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiChatConsole.h b/src/guiChatConsole.h index 033a26244..6e73f1dc6 100644 --- a/src/guiChatConsole.h +++ b/src/guiChatConsole.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiConfigureWorld.cpp b/src/guiConfigureWorld.cpp index fdcd0d3e8..c97acabd2 100644 --- a/src/guiConfigureWorld.cpp +++ b/src/guiConfigureWorld.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiConfigureWorld.h b/src/guiConfigureWorld.h index 2280c6dbe..450aacc4c 100644 --- a/src/guiConfigureWorld.h +++ b/src/guiConfigureWorld.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiConfirmMenu.cpp b/src/guiConfirmMenu.cpp index 299110070..33ed93fb8 100644 --- a/src/guiConfirmMenu.cpp +++ b/src/guiConfirmMenu.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiConfirmMenu.h b/src/guiConfirmMenu.h index b9fb2cd53..a23bb7c55 100644 --- a/src/guiConfirmMenu.h +++ b/src/guiConfirmMenu.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiCreateWorld.cpp b/src/guiCreateWorld.cpp index a4c528b3e..d34850844 100644 --- a/src/guiCreateWorld.cpp +++ b/src/guiCreateWorld.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiCreateWorld.h b/src/guiCreateWorld.h index 492fcf565..60a8f7553 100644 --- a/src/guiCreateWorld.h +++ b/src/guiCreateWorld.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiDeathScreen.cpp b/src/guiDeathScreen.cpp index 4a767c43c..2a3e7e335 100644 --- a/src/guiDeathScreen.cpp +++ b/src/guiDeathScreen.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiDeathScreen.h b/src/guiDeathScreen.h index 3c0307634..82ee1eb46 100644 --- a/src/guiDeathScreen.h +++ b/src/guiDeathScreen.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index a1da09ee1..ae399a1b8 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index 86235900d..578dd6e01 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp index 3f6d03ebb..a5b82f612 100644 --- a/src/guiKeyChangeMenu.cpp +++ b/src/guiKeyChangeMenu.cpp @@ -1,5 +1,5 @@ /* - Minetest-c55 + Minetest Copyright (C) 2010-11 celeron55, Perttu Ahola Copyright (C) 2011 Ciaran Gultnieks Copyright (C) 2011 teddydestodes diff --git a/src/guiKeyChangeMenu.h b/src/guiKeyChangeMenu.h index beb4f0b6f..104af9502 100644 --- a/src/guiKeyChangeMenu.h +++ b/src/guiKeyChangeMenu.h @@ -1,5 +1,5 @@ /* - Minetest-c55 + Minetest Copyright (C) 2010-11 celeron55, Perttu Ahola Copyright (C) 2011 Ciaran Gultnieks Copyright (C) 2011 teddydestodes diff --git a/src/guiMainMenu.cpp b/src/guiMainMenu.cpp index ca334198f..eead3da92 100644 --- a/src/guiMainMenu.cpp +++ b/src/guiMainMenu.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-12 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h index 558a05d59..0a4292b06 100644 --- a/src/guiMainMenu.h +++ b/src/guiMainMenu.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiMessageMenu.cpp b/src/guiMessageMenu.cpp index 24d6f7f52..ce3bfe713 100644 --- a/src/guiMessageMenu.cpp +++ b/src/guiMessageMenu.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiMessageMenu.h b/src/guiMessageMenu.h index af2fc44a5..2eef1d8cd 100644 --- a/src/guiMessageMenu.h +++ b/src/guiMessageMenu.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiPasswordChange.cpp b/src/guiPasswordChange.cpp index ecae55e10..4ce1a3636 100644 --- a/src/guiPasswordChange.cpp +++ b/src/guiPasswordChange.cpp @@ -1,5 +1,5 @@ /* -Part of Minetest-c55 +Part of Minetest Copyright (C) 2011 celeron55, Perttu Ahola Copyright (C) 2011 Ciaran Gultnieks diff --git a/src/guiPasswordChange.h b/src/guiPasswordChange.h index f5f767b5e..05b145202 100644 --- a/src/guiPasswordChange.h +++ b/src/guiPasswordChange.h @@ -1,5 +1,5 @@ /* -Part of Minetest-c55 +Part of Minetest Copyright (C) 2010-11 celeron55, Perttu Ahola Copyright (C) 2011 Ciaran Gultnieks diff --git a/src/guiPauseMenu.cpp b/src/guiPauseMenu.cpp index 9d44b2e82..2ca9a80fb 100644 --- a/src/guiPauseMenu.cpp +++ b/src/guiPauseMenu.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiPauseMenu.h b/src/guiPauseMenu.h index 578f2a324..8a4ef931e 100644 --- a/src/guiPauseMenu.h +++ b/src/guiPauseMenu.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiTextInputMenu.cpp b/src/guiTextInputMenu.cpp index 857c26a45..56844228d 100644 --- a/src/guiTextInputMenu.cpp +++ b/src/guiTextInputMenu.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiTextInputMenu.h b/src/guiTextInputMenu.h index 1a55525c9..a532ec008 100644 --- a/src/guiTextInputMenu.h +++ b/src/guiTextInputMenu.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/guiVolumeChange.cpp b/src/guiVolumeChange.cpp index 606aa0196..99b302dd7 100644 --- a/src/guiVolumeChange.cpp +++ b/src/guiVolumeChange.cpp @@ -1,5 +1,5 @@ /* -Part of Minetest-c55 +Part of Minetest Copyright (C) 2011 celeron55, Perttu Ahola Copyright (C) 2011 Ciaran Gultnieks Copyright (C) 2013 RealBadAngel, Maciej Kasatkin diff --git a/src/guiVolumeChange.h b/src/guiVolumeChange.h index a0a50f393..6571640e9 100644 --- a/src/guiVolumeChange.h +++ b/src/guiVolumeChange.h @@ -1,5 +1,5 @@ /* -Part of Minetest-c55 +Part of Minetest Copyright (C) 2010-11 celeron55, Perttu Ahola Copyright (C) 2011 Ciaran Gultnieks Copyright (C) 2013 RealBadAngel, Maciej Kasatkin diff --git a/src/hex.h b/src/hex.h index e54ef9634..88703878e 100644 --- a/src/hex.h +++ b/src/hex.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 Jonathan Neuschäfer This program is free software; you can redistribute it and/or modify diff --git a/src/inventory.cpp b/src/inventory.cpp index 5e39a41a6..82896f3c0 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/inventory.h b/src/inventory.h index 5f90183d2..ffe944a13 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 4d862de66..5042376c6 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/inventorymanager.h b/src/inventorymanager.h index f81f5b972..eec461f2c 100644 --- a/src/inventorymanager.h +++ b/src/inventorymanager.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/irr_aabb3d.h b/src/irr_aabb3d.h index 3f04198ad..3cc8c33a2 100644 --- a/src/irr_aabb3d.h +++ b/src/irr_aabb3d.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/irr_v2d.h b/src/irr_v2d.h index 0710684ce..9a0492866 100644 --- a/src/irr_v2d.h +++ b/src/irr_v2d.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/irr_v3d.h b/src/irr_v3d.h index 21051f93c..f5c7a1bfb 100644 --- a/src/irr_v3d.h +++ b/src/irr_v3d.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/irrlichttypes.h b/src/irrlichttypes.h index 2db744a48..195c3cc0c 100644 --- a/src/irrlichttypes.h +++ b/src/irrlichttypes.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/irrlichttypes_bloated.h b/src/irrlichttypes_bloated.h index 528ee1f47..6576335ba 100644 --- a/src/irrlichttypes_bloated.h +++ b/src/irrlichttypes_bloated.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/irrlichttypes_extrabloated.h b/src/irrlichttypes_extrabloated.h index f85fb6318..17f2e298a 100644 --- a/src/irrlichttypes_extrabloated.h +++ b/src/irrlichttypes_extrabloated.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/itemdef.cpp b/src/itemdef.cpp index a4f3895e0..6c3bdb5e3 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola Copyright (C) 2011 Kahrl diff --git a/src/itemdef.h b/src/itemdef.h index ae3600868..a3054518d 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola Copyright (C) 2011 Kahrl diff --git a/src/itemgroup.h b/src/itemgroup.h index c6c36dcc4..3a07e9e02 100644 --- a/src/itemgroup.h +++ b/src/itemgroup.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/keycode.cpp b/src/keycode.cpp index 9aa9d300d..7b3c16332 100644 --- a/src/keycode.cpp +++ b/src/keycode.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/keycode.h b/src/keycode.h index a0b1e3553..7fe5f42d1 100644 --- a/src/keycode.h +++ b/src/keycode.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/light.cpp b/src/light.cpp index 66bceb72b..549603495 100644 --- a/src/light.cpp +++ b/src/light.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/light.h b/src/light.h index f1c458ad8..bafd10914 100644 --- a/src/light.h +++ b/src/light.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 8b6d7e2f6..ddb38fffa 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/localplayer.h b/src/localplayer.h index c9d010d7c..81b6eb808 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/log.cpp b/src/log.cpp index 78fa32026..14c49857b 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/log.h b/src/log.h index 012e7e938..5c9bb1b26 100644 --- a/src/log.h +++ b/src/log.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/logoutputbuffer.h b/src/logoutputbuffer.h index 1fb155aae..316da7856 100644 --- a/src/logoutputbuffer.h +++ b/src/logoutputbuffer.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/luaentity_common.h b/src/luaentity_common.h index 74dd046cb..ee6ef00a0 100644 --- a/src/luaentity_common.h +++ b/src/luaentity_common.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/main.cpp b/src/main.cpp index 7bbbf082d..1774641f1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/main.h b/src/main.h index a169a45dc..126445b03 100644 --- a/src/main.h +++ b/src/main.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mainmenumanager.h b/src/mainmenumanager.h index dc2b287d4..8915dd933 100644 --- a/src/mainmenumanager.h +++ b/src/mainmenumanager.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/map.cpp b/src/map.cpp index 717b0cf9b..f57aeee88 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/map.h b/src/map.h index 420fc29ca..7078dedc0 100644 --- a/src/map.h +++ b/src/map.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapblock.cpp b/src/mapblock.cpp index e9c8fadff..a84fdeb6a 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapblock.h b/src/mapblock.h index d56d93dda..2c8d6de66 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index daebbe217..1797db22e 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index c23b6cc5a..7d8abbb8d 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapchunk.h b/src/mapchunk.h index 6b7a411d7..a45dcdc3d 100644 --- a/src/mapchunk.h +++ b/src/mapchunk.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapgen.cpp b/src/mapgen.cpp index a2d920bbc..14981f12c 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapgen.h b/src/mapgen.h index 4f1ab4ebd..13d17bd58 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapgen_v6.cpp b/src/mapgen_v6.cpp index 3a5e10930..aad25c6da 100644 --- a/src/mapgen_v6.cpp +++ b/src/mapgen_v6.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapgen_v6.h b/src/mapgen_v6.h index 9a2a0287d..4171ec534 100644 --- a/src/mapgen_v6.h +++ b/src/mapgen_v6.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 5dab93754..d9bc67081 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapnode.h b/src/mapnode.h index de2529e4d..f89d8b0ed 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapsector.cpp b/src/mapsector.cpp index f152c38e7..2cba9444f 100644 --- a/src/mapsector.cpp +++ b/src/mapsector.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mapsector.h b/src/mapsector.h index 74539ab24..0f4bcdd3a 100644 --- a/src/mapsector.h +++ b/src/mapsector.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mesh.cpp b/src/mesh.cpp index 29b853143..4bfbc40c8 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mesh.h b/src/mesh.h index 5e1efcd52..062363bb7 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/modalMenu.h b/src/modalMenu.h index 04372167b..43f559062 100644 --- a/src/modalMenu.h +++ b/src/modalMenu.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/modifiedstate.h b/src/modifiedstate.h index 98004619f..0beebcfbf 100644 --- a/src/modifiedstate.h +++ b/src/modifiedstate.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mods.cpp b/src/mods.cpp index f6e3d58d7..cd3840edd 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/mods.h b/src/mods.h index 59dffdade..35894b339 100644 --- a/src/mods.h +++ b/src/mods.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/nameidmapping.cpp b/src/nameidmapping.cpp index 2a436f735..0e6090745 100644 --- a/src/nameidmapping.cpp +++ b/src/nameidmapping.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/nameidmapping.h b/src/nameidmapping.h index 90d8dbed8..3cb87987d 100644 --- a/src/nameidmapping.h +++ b/src/nameidmapping.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 12d9238ad..5b9c64cd4 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/nodedef.h b/src/nodedef.h index fa0c1f2e8..ade56dadf 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index 141c779f1..56391a39c 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/nodemetadata.h b/src/nodemetadata.h index 262b64d74..1602ef937 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/nodetimer.cpp b/src/nodetimer.cpp index bf1e7435d..d1043fa51 100644 --- a/src/nodetimer.cpp +++ b/src/nodetimer.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/nodetimer.h b/src/nodetimer.h index c643ab888..1f2f2e6f3 100644 --- a/src/nodetimer.h +++ b/src/nodetimer.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/noise.cpp b/src/noise.cpp index bfb1960c8..c0d3fe163 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/noise.h b/src/noise.h index e725b4e47..74e89a90f 100644 --- a/src/noise.h +++ b/src/noise.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/object_properties.cpp b/src/object_properties.cpp index ec988a37d..c02203a81 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/object_properties.h b/src/object_properties.h index d7d44625e..e1604b487 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/particles.cpp b/src/particles.cpp index d49e33322..a1f5739de 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/particles.h b/src/particles.h index 3ed9dfdc8..a9d189e4e 100644 --- a/src/particles.h +++ b/src/particles.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/player.cpp b/src/player.cpp index 86d3ae184..2a4f3747a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/player.h b/src/player.h index 770afdb37..8c9e7e725 100644 --- a/src/player.h +++ b/src/player.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/porting.cpp b/src/porting.cpp index f8a2cca5c..0f702810d 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/porting.h b/src/porting.h index 9ba3394bb..f98980460 100644 --- a/src/porting.h +++ b/src/porting.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/profiler.h b/src/profiler.h index b1e6abe58..a3ac41a25 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/quicktune.cpp b/src/quicktune.cpp index acd0d721f..ba0e0c654 100644 --- a/src/quicktune.cpp +++ b/src/quicktune.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/quicktune.h b/src/quicktune.h index a733ccda4..a0aa28d8e 100644 --- a/src/quicktune.h +++ b/src/quicktune.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/quicktune_shortcutter.h b/src/quicktune_shortcutter.h index 2e3b5310b..0ec80e48b 100644 --- a/src/quicktune_shortcutter.h +++ b/src/quicktune_shortcutter.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/rollback.cpp b/src/rollback.cpp index f0e3c40aa..fd60e0302 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/rollback.h b/src/rollback.h index 5e76042b5..8da03b588 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp index b2eb2093c..74f3e691c 100644 --- a/src/rollback_interface.cpp +++ b/src/rollback_interface.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/rollback_interface.h b/src/rollback_interface.h index 8dd429d76..c18af3076 100644 --- a/src/rollback_interface.h +++ b/src/rollback_interface.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/script.cpp b/src/script.cpp index b238e1be7..a27ac3c9f 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/script.h b/src/script.h index fea132b07..51ed4a02d 100644 --- a/src/script.h +++ b/src/script.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index b7a7f6e42..4d02fc965 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/scriptapi.h b/src/scriptapi.h index d71b8fe41..8c48c756f 100644 --- a/src/scriptapi.h +++ b/src/scriptapi.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/serialization.cpp b/src/serialization.cpp index c57e1642a..e79342432 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/serialization.h b/src/serialization.h index 533ddc8c4..b89997155 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/server.cpp b/src/server.cpp index 94a4787f8..b1d0b8d46 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/server.h b/src/server.h index 22c7cf2bb..157ade086 100644 --- a/src/server.h +++ b/src/server.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/serverlist.cpp b/src/serverlist.cpp index b2f49ae72..77b0ceef1 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/serverlist.h b/src/serverlist.h index 52549e97a..7aae32de1 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/serverobject.cpp b/src/serverobject.cpp index deaa94f2c..af77d466c 100644 --- a/src/serverobject.cpp +++ b/src/serverobject.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/serverobject.h b/src/serverobject.h index 14752878f..c5ae6aa8e 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/settings.h b/src/settings.h index addd9980c..db4f07eea 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/shader.cpp b/src/shader.cpp index 9e1a51f23..b05aad7a4 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola Copyright (C) 2012 Kahrl diff --git a/src/shader.h b/src/shader.h index 774a17b20..c39602cc0 100644 --- a/src/shader.h +++ b/src/shader.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola Copyright (C) 2012 Kahrl diff --git a/src/sky.h b/src/sky.h index 65170dad5..6b9168fc6 100644 --- a/src/sky.h +++ b/src/sky.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/socket.cpp b/src/socket.cpp index f8afed229..997a80c44 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/socket.h b/src/socket.h index 06ec2843a..2617c03bd 100644 --- a/src/socket.h +++ b/src/socket.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/sound.cpp b/src/sound.cpp index f5203bbb3..1e884e43a 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/sound.h b/src/sound.h index d74acd550..1f9b7d6e1 100644 --- a/src/sound.h +++ b/src/sound.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index c78f6288f..069641b74 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola OpenAL support based on work by: Copyright (C) 2011 Sebastian 'Bahamada' Rühl diff --git a/src/sound_openal.h b/src/sound_openal.h index 876c45e69..d3ddfb766 100644 --- a/src/sound_openal.h +++ b/src/sound_openal.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/staticobject.cpp b/src/staticobject.cpp index 2183f2ffe..00d846ba5 100644 --- a/src/staticobject.cpp +++ b/src/staticobject.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/staticobject.h b/src/staticobject.h index 6fccbdd4f..ca358236c 100644 --- a/src/staticobject.h +++ b/src/staticobject.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/strfnd.h b/src/strfnd.h index 71a9a8cd9..dcd65f6cd 100644 --- a/src/strfnd.h +++ b/src/strfnd.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/subgame.cpp b/src/subgame.cpp index 2f5340373..a22f03bc2 100644 --- a/src/subgame.cpp +++ b/src/subgame.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/subgame.h b/src/subgame.h index dd725caf7..fa6d70cee 100644 --- a/src/subgame.h +++ b/src/subgame.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/test.cpp b/src/test.cpp index bc0692a78..c7c97989f 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/test.h b/src/test.h index f69888214..606a1c289 100644 --- a/src/test.h +++ b/src/test.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/threads.h b/src/threads.h index e79404aa2..2ffc5eb1b 100644 --- a/src/threads.h +++ b/src/threads.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/tile.cpp b/src/tile.cpp index 7cad1b836..0936ba8e9 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/tile.h b/src/tile.h index b00c1c6c6..068defbb2 100644 --- a/src/tile.h +++ b/src/tile.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/tool.cpp b/src/tool.cpp index c1e268ff1..b452a00f6 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/tool.h b/src/tool.h index 18c09dca3..315a51bac 100644 --- a/src/tool.h +++ b/src/tool.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2011 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/treegen.cpp b/src/treegen.cpp index 948716dcf..400377838 100644 --- a/src/treegen.cpp +++ b/src/treegen.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola , 2012-2013 RealBadAngel, Maciej Kasatkin This program is free software; you can redistribute it and/or modify diff --git a/src/treegen.h b/src/treegen.h index 0068219dd..18adff859 100644 --- a/src/treegen.h +++ b/src/treegen.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola , 2012-2013 RealBadAngel, Maciej Kasatkin This program is free software; you can redistribute it and/or modify diff --git a/src/util/container.h b/src/util/container.h index 8c1ae02fb..15c877fde 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/directiontables.cpp b/src/util/directiontables.cpp index a5d2faa7c..54f8edf9e 100644 --- a/src/util/directiontables.cpp +++ b/src/util/directiontables.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/directiontables.h b/src/util/directiontables.h index 5bebd2fdb..4e26eae3e 100644 --- a/src/util/directiontables.h +++ b/src/util/directiontables.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 0ac812eb7..2d6afc3c6 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/numeric.h b/src/util/numeric.h index a028f1ff2..5ef014ff9 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/pointedthing.cpp b/src/util/pointedthing.cpp index d1be2d6b6..d5cc81003 100644 --- a/src/util/pointedthing.cpp +++ b/src/util/pointedthing.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/pointedthing.h b/src/util/pointedthing.h index e61e2f4f3..aab7a1043 100644 --- a/src/util/pointedthing.h +++ b/src/util/pointedthing.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/pointer.h b/src/util/pointer.h index 775f0a336..96f4c656b 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 16825b677..c1e99c7eb 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/serialize.h b/src/util/serialize.h index b356c484e..b0e93ad26 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/string.cpp b/src/util/string.cpp index 61b307c60..e151abefc 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/string.h b/src/util/string.h index a469a074a..b655ce25c 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/thread.h b/src/util/thread.h index 8e8c74ac5..db8e0b89e 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/timetaker.cpp b/src/util/timetaker.cpp index 52c618931..42a248dc6 100644 --- a/src/util/timetaker.cpp +++ b/src/util/timetaker.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/util/timetaker.h b/src/util/timetaker.h index 8aad97d0f..75542f433 100644 --- a/src/util/timetaker.h +++ b/src/util/timetaker.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/voxel.cpp b/src/voxel.cpp index 8fdae79e1..be504f983 100644 --- a/src/voxel.cpp +++ b/src/voxel.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/voxel.h b/src/voxel.h index c2a5efb4b..79155a0f6 100644 --- a/src/voxel.h +++ b/src/voxel.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index 795530d40..c9aff019e 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index 4360ef731..7a88caf2f 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -1,5 +1,5 @@ /* -Minetest-c55 +Minetest Copyright (C) 2010-2012 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify diff --git a/util/wireshark/minetest.lua b/util/wireshark/minetest.lua index acc95e5f4..908cc5633 100644 --- a/util/wireshark/minetest.lua +++ b/util/wireshark/minetest.lua @@ -4,7 +4,7 @@ -- --- Minetest-c55 +-- Minetest -- Copyright (C) 2011 celeron55, Perttu Ahola -- -- This program is free software; you can redistribute it and/or modify From 92c7bd02fb6afb26c82ec174d99d698107cd6fa5 Mon Sep 17 00:00:00 2001 From: PilzAdam Date: Sun, 24 Feb 2013 18:52:44 +0100 Subject: [PATCH 30/43] Update buildbot --- util/buildbot/buildwin32.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/util/buildbot/buildwin32.sh b/util/buildbot/buildwin32.sh index 52d2aa182..fc42db8a7 100755 --- a/util/buildbot/buildwin32.sh +++ b/util/buildbot/buildwin32.sh @@ -50,11 +50,11 @@ cd $builddir -c -O $packagedir/libvorbis-$vorbis_version-dll.7z || exit 1 [ -e $packagedir/libcurl-$curl_version-win32-msvc.zip ] || wget http://curl.haxx.se/download/libcurl-$curl_version-win32-msvc.zip \ -c -O $packagedir/libcurl-$curl_version-win32-msvc.zip || exit 1 -wget http://github.com/celeron55/minetest/zipball/master \ - -c -O $packagedir/minetest.zip --tries=3 || (echo "Please download http://github.com/celeron55/minetest/zipball/master manually and save it as $packagedir/minetest.zip"; read -s) +wget http://github.com/minetest/minetest/zipball/master \ + -c -O $packagedir/minetest.zip --tries=3 || (echo "Please download http://github.com/minetest/minetest/zipball/master manually and save it as $packagedir/minetest.zip"; read -s) [ -e $packagedir/minetest.zip ] || (echo "minetest.zip not found"; exit 1) -wget http://github.com/celeron55/minetest_game/zipball/master \ - -c -O $packagedir/minetest_game.zip --tries=3 || (echo "Please download http://github.com/celeron55/minetest_game/zipball/master manually and save it as $packagedir/minetest_game.zip"; read -s) +wget http://github.com/minetest/minetest_game/zipball/master \ + -c -O $packagedir/minetest_game.zip --tries=3 || (echo "Please download http://github.com/minetest/minetest_game/zipball/master manually and save it as $packagedir/minetest_game.zip"; read -s) [ -e $packagedir/minetest_game.zip ] || (echo "minetest_game.zip not found"; exit 1) [ -e $packagedir/openal_stripped.zip ] || wget http://minetest.ru/bin/openal_stripped.zip \ -c -O $packagedir/openal_stripped.zip || exit 1 @@ -63,10 +63,10 @@ wget http://github.com/celeron55/minetest_game/zipball/master \ # Figure out some path names from the packages -minetestdirname=`unzip -l $packagedir/minetest.zip | head -n 7 | tail -n 1 | sed -e 's/^[^c]*//' -e 's/\/.*$//'` +minetestdirname=`unzip -l $packagedir/minetest.zip | head -n 7 | tail -n 1 | sed -e 's/^[^m]*//' -e 's/\/.*$//'` minetestdir=$builddir/$minetestdirname || exit 1 -git_hash=`echo $minetestdirname | sed -e 's/celeron55-minetest-//'` -minetest_gamedirname=`unzip -l $packagedir/minetest_game.zip | head -n 7 | tail -n 1 | sed -e 's/^[^c]*//' -e 's/\/.*$//'` +git_hash=`echo $minetestdirname | sed -e 's/minetest-minetest-//'` +minetest_gamedirname=`unzip -l $packagedir/minetest_game.zip | head -n 7 | tail -n 1 | sed -e 's/^[^m]*//' -e 's/\/.*$//'` # Extract stuff cd $libdir || exit 1 From 6d0ea26c2d62c3774ff384cf1bfc2a3372b49a3b Mon Sep 17 00:00:00 2001 From: Sfan5 Date: Sun, 24 Feb 2013 19:38:45 +0100 Subject: [PATCH 31/43] Update Copyright Years --- README.txt | 4 ++-- src/activeobject.h | 2 +- src/ban.cpp | 2 +- src/ban.h | 2 +- src/biome.cpp | 2 +- src/biome.h | 2 +- src/camera.cpp | 2 +- src/camera.h | 2 +- src/chat.cpp | 2 +- src/chat.h | 2 +- src/client.cpp | 2 +- src/client.h | 2 +- src/clientmap.cpp | 2 +- src/clientmap.h | 2 +- src/clientobject.cpp | 2 +- src/clientobject.h | 2 +- src/clientserver.cpp | 2 +- src/clientserver.h | 2 +- src/clientsimpleobject.h | 2 +- src/clouds.cpp | 2 +- src/clouds.h | 2 +- src/collision.cpp | 2 +- src/collision.h | 2 +- src/connection.cpp | 2 +- src/connection.h | 2 +- src/constants.h | 2 +- src/content_abm.cpp | 2 +- src/content_abm.h | 2 +- src/content_cao.cpp | 2 +- src/content_cao.h | 2 +- src/content_cso.cpp | 2 +- src/content_cso.h | 2 +- src/content_mapblock.cpp | 2 +- src/content_mapblock.h | 2 +- src/content_mapnode.cpp | 2 +- src/content_mapnode.h | 2 +- src/content_nodemeta.cpp | 2 +- src/content_nodemeta.h | 2 +- src/content_object.h | 2 +- src/content_sao.cpp | 2 +- src/content_sao.h | 2 +- src/craftdef.cpp | 2 +- src/craftdef.h | 2 +- src/daynightratio.h | 2 +- src/debug.cpp | 2 +- src/debug.h | 2 +- src/defaultsettings.cpp | 2 +- src/defaultsettings.h | 2 +- src/environment.cpp | 2 +- src/environment.h | 2 +- src/event.h | 2 +- src/event_manager.h | 2 +- src/exceptions.h | 2 +- src/farmesh.cpp | 2 +- src/farmesh.h | 2 +- src/filecache.cpp | 4 ++-- src/filecache.h | 4 ++-- src/filesys.cpp | 2 +- src/filesys.h | 2 +- src/game.cpp | 2 +- src/game.h | 2 +- src/gamedef.h | 2 +- src/genericobject.cpp | 2 +- src/genericobject.h | 2 +- src/gettime.h | 2 +- src/guiChatConsole.cpp | 2 +- src/guiChatConsole.h | 2 +- src/guiConfigureWorld.cpp | 2 +- src/guiConfigureWorld.h | 2 +- src/guiConfirmMenu.cpp | 2 +- src/guiConfirmMenu.h | 2 +- src/guiCreateWorld.cpp | 2 +- src/guiCreateWorld.h | 2 +- src/guiDeathScreen.cpp | 2 +- src/guiDeathScreen.h | 2 +- src/guiFormSpecMenu.cpp | 2 +- src/guiFormSpecMenu.h | 2 +- src/guiKeyChangeMenu.cpp | 6 +++--- src/guiKeyChangeMenu.h | 6 +++--- src/guiMainMenu.cpp | 2 +- src/guiMainMenu.h | 2 +- src/guiMessageMenu.cpp | 2 +- src/guiMessageMenu.h | 2 +- src/guiPasswordChange.cpp | 4 ++-- src/guiPasswordChange.h | 4 ++-- src/guiPauseMenu.cpp | 2 +- src/guiPauseMenu.h | 2 +- src/guiTextInputMenu.cpp | 2 +- src/guiTextInputMenu.h | 2 +- src/guiVolumeChange.cpp | 4 ++-- src/guiVolumeChange.h | 4 ++-- src/hex.h | 2 +- src/intlGUIEditBox.cpp | 2 +- src/intlGUIEditBox.h | 2 +- src/inventory.cpp | 2 +- src/inventory.h | 2 +- src/inventorymanager.cpp | 2 +- src/inventorymanager.h | 2 +- src/irr_aabb3d.h | 2 +- src/irr_v2d.h | 2 +- src/irr_v3d.h | 2 +- src/irrlichttypes.h | 2 +- src/irrlichttypes_bloated.h | 2 +- src/irrlichttypes_extrabloated.h | 2 +- src/itemdef.cpp | 4 ++-- src/itemdef.h | 4 ++-- src/itemgroup.h | 2 +- src/keycode.cpp | 2 +- src/keycode.h | 2 +- src/light.cpp | 2 +- src/light.h | 2 +- src/localplayer.cpp | 2 +- src/localplayer.h | 2 +- src/log.cpp | 2 +- src/log.h | 2 +- src/logoutputbuffer.h | 2 +- src/luaentity_common.h | 2 +- src/main.cpp | 2 +- src/main.h | 2 +- src/mainmenumanager.h | 2 +- src/map.cpp | 2 +- src/map.h | 2 +- src/mapblock.cpp | 2 +- src/mapblock.h | 2 +- src/mapblock_mesh.cpp | 2 +- src/mapblock_mesh.h | 2 +- src/mapchunk.h | 2 +- src/mapgen.cpp | 2 +- src/mapgen.h | 2 +- src/mapgen_v6.cpp | 2 +- src/mapgen_v6.h | 2 +- src/mapnode.cpp | 2 +- src/mapnode.h | 2 +- src/mapsector.cpp | 2 +- src/mapsector.h | 2 +- src/mesh.cpp | 2 +- src/mesh.h | 2 +- src/modalMenu.h | 2 +- src/modifiedstate.h | 2 +- src/mods.cpp | 2 +- src/mods.h | 2 +- src/nameidmapping.cpp | 2 +- src/nameidmapping.h | 2 +- src/nodedef.cpp | 2 +- src/nodedef.h | 2 +- src/nodemetadata.cpp | 2 +- src/nodemetadata.h | 2 +- src/nodetimer.cpp | 2 +- src/nodetimer.h | 2 +- src/noise.cpp | 2 +- src/noise.h | 2 +- src/object_properties.cpp | 2 +- src/object_properties.h | 2 +- src/particles.cpp | 2 +- src/particles.h | 2 +- src/player.cpp | 2 +- src/player.h | 2 +- src/porting.cpp | 2 +- src/porting.h | 2 +- src/profiler.h | 2 +- src/quicktune.cpp | 2 +- src/quicktune.h | 2 +- src/quicktune_shortcutter.h | 2 +- src/rollback.cpp | 2 +- src/rollback.h | 2 +- src/rollback_interface.cpp | 2 +- src/rollback_interface.h | 2 +- src/script.cpp | 2 +- src/script.h | 2 +- src/scriptapi.cpp | 2 +- src/scriptapi.h | 2 +- src/serialization.cpp | 2 +- src/serialization.h | 2 +- src/server.cpp | 2 +- src/server.h | 2 +- src/serverlist.cpp | 2 +- src/serverlist.h | 2 +- src/serverobject.cpp | 2 +- src/serverobject.h | 2 +- src/settings.h | 2 +- src/shader.cpp | 4 ++-- src/shader.h | 4 ++-- src/sky.h | 2 +- src/socket.cpp | 2 +- src/socket.h | 2 +- src/sound.cpp | 2 +- src/sound.h | 2 +- src/sound_openal.cpp | 2 +- src/sound_openal.h | 2 +- src/staticobject.cpp | 2 +- src/staticobject.h | 2 +- src/strfnd.h | 2 +- src/subgame.cpp | 2 +- src/subgame.h | 2 +- src/test.cpp | 2 +- src/test.h | 2 +- src/threads.h | 2 +- src/tile.cpp | 2 +- src/tile.h | 2 +- src/tool.cpp | 2 +- src/tool.h | 2 +- src/treegen.cpp | 2 +- src/treegen.h | 2 +- src/util/container.h | 2 +- src/util/directiontables.cpp | 2 +- src/util/directiontables.h | 2 +- src/util/numeric.cpp | 2 +- src/util/numeric.h | 2 +- src/util/pointedthing.cpp | 2 +- src/util/pointedthing.h | 2 +- src/util/pointer.h | 2 +- src/util/serialize.cpp | 2 +- src/util/serialize.h | 2 +- src/util/string.cpp | 2 +- src/util/string.h | 2 +- src/util/thread.h | 2 +- src/util/timetaker.cpp | 2 +- src/util/timetaker.h | 2 +- src/voxel.cpp | 2 +- src/voxel.h | 2 +- src/voxelalgorithms.cpp | 2 +- src/voxelalgorithms.h | 2 +- 222 files changed, 237 insertions(+), 237 deletions(-) diff --git a/README.txt b/README.txt index edd11f3fe..20917d772 100644 --- a/README.txt +++ b/README.txt @@ -3,7 +3,7 @@ Minetest An InfiniMiner/Minecraft inspired game. -Copyright (c) 2010-2012 Perttu Ahola +Copyright (c) 2010-2013 Perttu Ahola and contributors (see source file comments and the version control log) In case you downloaded the source code: @@ -258,7 +258,7 @@ License of Minetest source code ------------------------------- Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/activeobject.h b/src/activeobject.h index d131bcc33..e454f2c8c 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/ban.cpp b/src/ban.cpp index 6bc84a3e8..75bae746f 100644 --- a/src/ban.cpp +++ b/src/ban.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/ban.h b/src/ban.h index 8132c3a38..529635c26 100644 --- a/src/ban.h +++ b/src/ban.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/biome.cpp b/src/biome.cpp index 67e5a9de7..86af75310 100644 --- a/src/biome.cpp +++ b/src/biome.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 kwolekr, Ryan Kwolek +Copyright (C) 2010-2013 kwolekr, Ryan Kwolek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/biome.h b/src/biome.h index 6b40b9f81..c30af46ad 100644 --- a/src/biome.h +++ b/src/biome.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 kwolekr, Ryan Kwolek +Copyright (C) 2010-2013 kwolekr, Ryan Kwolek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/camera.cpp b/src/camera.cpp index 7916affbd..b1e588415 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/camera.h b/src/camera.h index 5b06da322..fc43d1176 100644 --- a/src/camera.h +++ b/src/camera.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/chat.cpp b/src/chat.cpp index 0efda7eac..c3509ae49 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/chat.h b/src/chat.h index 5ba4af800..27863922c 100644 --- a/src/chat.h +++ b/src/chat.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/client.cpp b/src/client.cpp index f02c14bc8..be35db5de 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/client.h b/src/client.h index e3cc98c5b..809e98b81 100644 --- a/src/client.h +++ b/src/client.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clientmap.cpp b/src/clientmap.cpp index e48aef190..aa92dfdee 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clientmap.h b/src/clientmap.h index a9a365c05..786f35b77 100644 --- a/src/clientmap.h +++ b/src/clientmap.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clientobject.cpp b/src/clientobject.cpp index 809357fc1..e7c735dac 100644 --- a/src/clientobject.cpp +++ b/src/clientobject.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clientobject.h b/src/clientobject.h index 1d88f991f..d1ee366cf 100644 --- a/src/clientobject.h +++ b/src/clientobject.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clientserver.cpp b/src/clientserver.cpp index dee0a68e2..591a95542 100644 --- a/src/clientserver.cpp +++ b/src/clientserver.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clientserver.h b/src/clientserver.h index 6a135a139..769272a68 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clientsimpleobject.h b/src/clientsimpleobject.h index b91727492..c94db22f1 100644 --- a/src/clientsimpleobject.h +++ b/src/clientsimpleobject.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clouds.cpp b/src/clouds.cpp index 67dd550f4..9f0bc06d8 100644 --- a/src/clouds.cpp +++ b/src/clouds.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/clouds.h b/src/clouds.h index d65f19385..72923c2c5 100644 --- a/src/clouds.h +++ b/src/clouds.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/collision.cpp b/src/collision.cpp index ccd91be94..58517b779 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/collision.h b/src/collision.h index 331ff8f9d..38cc3efb3 100644 --- a/src/collision.h +++ b/src/collision.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/connection.cpp b/src/connection.cpp index b8bde1872..7a3018bfd 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/connection.h b/src/connection.h index 4ccbfdd16..05b1ca2e8 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/constants.h b/src/constants.h index 39537c83b..8246c8c21 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_abm.cpp b/src/content_abm.cpp index a71dddfab..cbfdd2e9f 100644 --- a/src/content_abm.cpp +++ b/src/content_abm.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_abm.h b/src/content_abm.h index d837b7f3c..0a91a96a2 100644 --- a/src/content_abm.h +++ b/src/content_abm.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 57cdbc912..5e5cb38ae 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_cao.h b/src/content_cao.h index 81fd84f3d..441c74922 100644 --- a/src/content_cao.h +++ b/src/content_cao.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_cso.cpp b/src/content_cso.cpp index f85c23ed2..20eb88b7d 100644 --- a/src/content_cso.cpp +++ b/src/content_cso.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_cso.h b/src/content_cso.h index bb4ce6100..5007d2526 100644 --- a/src/content_cso.h +++ b/src/content_cso.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index 7a14d0bc8..4a5c001d5 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_mapblock.h b/src/content_mapblock.h index 7e87835f7..bb1e129da 100644 --- a/src/content_mapblock.h +++ b/src/content_mapblock.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_mapnode.cpp b/src/content_mapnode.cpp index 49ce57c21..44d0b8e38 100644 --- a/src/content_mapnode.cpp +++ b/src/content_mapnode.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_mapnode.h b/src/content_mapnode.h index 89c7a9a63..5c9c0b66d 100644 --- a/src/content_mapnode.h +++ b/src/content_mapnode.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_nodemeta.cpp b/src/content_nodemeta.cpp index 0a56db396..a9a8cc60c 100644 --- a/src/content_nodemeta.cpp +++ b/src/content_nodemeta.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_nodemeta.h b/src/content_nodemeta.h index fdd6524e8..907649b5f 100644 --- a/src/content_nodemeta.h +++ b/src/content_nodemeta.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_object.h b/src/content_object.h index 913337eb4..65a829773 100644 --- a/src/content_object.h +++ b/src/content_object.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 5eed17eaf..718a42dff 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/content_sao.h b/src/content_sao.h index 894fe9699..2fd1034eb 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/craftdef.cpp b/src/craftdef.cpp index 143c295f5..99e3fcc3d 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/craftdef.h b/src/craftdef.h index aa71f1a1e..eb3cd7e39 100644 --- a/src/craftdef.h +++ b/src/craftdef.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/daynightratio.h b/src/daynightratio.h index 46acab60c..3375133ef 100644 --- a/src/daynightratio.h +++ b/src/daynightratio.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/debug.cpp b/src/debug.cpp index 9af7a5a9f..e32cceb86 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/debug.h b/src/debug.h index 9b9385983..69a215a42 100644 --- a/src/debug.h +++ b/src/debug.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 73a0893e1..610dd65a4 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/defaultsettings.h b/src/defaultsettings.h index 1fecf8cf7..37e3f717f 100644 --- a/src/defaultsettings.h +++ b/src/defaultsettings.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/environment.cpp b/src/environment.cpp index b8bcc5af8..e939672e7 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/environment.h b/src/environment.h index 1242506a3..a79ccc63d 100644 --- a/src/environment.h +++ b/src/environment.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/event.h b/src/event.h index 41be9622b..cfc222d5d 100644 --- a/src/event.h +++ b/src/event.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/event_manager.h b/src/event_manager.h index c2e9f792f..33d99b28c 100644 --- a/src/event_manager.h +++ b/src/event_manager.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/exceptions.h b/src/exceptions.h index 476699ecb..458fb50b1 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/farmesh.cpp b/src/farmesh.cpp index ef1e6a0ae..443e2b3bf 100644 --- a/src/farmesh.cpp +++ b/src/farmesh.cpp @@ -1,6 +1,6 @@ /* Part of Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/farmesh.h b/src/farmesh.h index 03b727d30..47147fa8f 100644 --- a/src/farmesh.h +++ b/src/farmesh.h @@ -1,6 +1,6 @@ /* Part of Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/filecache.cpp b/src/filecache.cpp index ec076ce23..23df1d7d0 100644 --- a/src/filecache.cpp +++ b/src/filecache.cpp @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola -Copyright (C) 2012 Jonathan Neuschäfer +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Jonathan Neuschäfer This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/filecache.h b/src/filecache.h index 96ea7a963..cf59a8e5b 100644 --- a/src/filecache.h +++ b/src/filecache.h @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola -Copyright (C) 2012 Jonathan Neuschäfer +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Jonathan Neuschäfer This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/filesys.cpp b/src/filesys.cpp index b956412b5..256c8f16a 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/filesys.h b/src/filesys.h index 2d2ad9f3d..263eb796f 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/game.cpp b/src/game.cpp index d4f2a70af..8ab0f3fe3 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/game.h b/src/game.h index e69714486..fef777fea 100644 --- a/src/game.h +++ b/src/game.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/gamedef.h b/src/gamedef.h index 76de9f777..1d46b028e 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/genericobject.cpp b/src/genericobject.cpp index 6946dff71..f7b272b00 100644 --- a/src/genericobject.cpp +++ b/src/genericobject.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/genericobject.h b/src/genericobject.h index b4d5125c5..9a21baa67 100644 --- a/src/genericobject.h +++ b/src/genericobject.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/gettime.h b/src/gettime.h index daeec0e46..611906559 100644 --- a/src/gettime.h +++ b/src/gettime.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp index 24c302b24..f522af01f 100644 --- a/src/guiChatConsole.cpp +++ b/src/guiChatConsole.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiChatConsole.h b/src/guiChatConsole.h index 6e73f1dc6..c896aae28 100644 --- a/src/guiChatConsole.h +++ b/src/guiChatConsole.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiConfigureWorld.cpp b/src/guiConfigureWorld.cpp index c97acabd2..080691222 100644 --- a/src/guiConfigureWorld.cpp +++ b/src/guiConfigureWorld.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiConfigureWorld.h b/src/guiConfigureWorld.h index 450aacc4c..8a77c5f89 100644 --- a/src/guiConfigureWorld.h +++ b/src/guiConfigureWorld.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiConfirmMenu.cpp b/src/guiConfirmMenu.cpp index 33ed93fb8..70e33c760 100644 --- a/src/guiConfirmMenu.cpp +++ b/src/guiConfirmMenu.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiConfirmMenu.h b/src/guiConfirmMenu.h index a23bb7c55..7d217d6a6 100644 --- a/src/guiConfirmMenu.h +++ b/src/guiConfirmMenu.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiCreateWorld.cpp b/src/guiCreateWorld.cpp index d34850844..8ac3fb17c 100644 --- a/src/guiCreateWorld.cpp +++ b/src/guiCreateWorld.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiCreateWorld.h b/src/guiCreateWorld.h index 60a8f7553..d9bc3638a 100644 --- a/src/guiCreateWorld.h +++ b/src/guiCreateWorld.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiDeathScreen.cpp b/src/guiDeathScreen.cpp index 2a3e7e335..ba329e3be 100644 --- a/src/guiDeathScreen.cpp +++ b/src/guiDeathScreen.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiDeathScreen.h b/src/guiDeathScreen.h index 82ee1eb46..261053a51 100644 --- a/src/guiDeathScreen.h +++ b/src/guiDeathScreen.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index ae399a1b8..120d6629a 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index 578dd6e01..aee16736e 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp index a5b82f612..4569f3d9d 100644 --- a/src/guiKeyChangeMenu.cpp +++ b/src/guiKeyChangeMenu.cpp @@ -1,8 +1,8 @@ /* Minetest - Copyright (C) 2010-11 celeron55, Perttu Ahola - Copyright (C) 2011 Ciaran Gultnieks - Copyright (C) 2011 teddydestodes + Copyright (C) 2010-2013 celeron55, Perttu Ahola + Copyright (C) 2013 Ciaran Gultnieks + Copyright (C) 2013 teddydestodes This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiKeyChangeMenu.h b/src/guiKeyChangeMenu.h index 104af9502..2b498676b 100644 --- a/src/guiKeyChangeMenu.h +++ b/src/guiKeyChangeMenu.h @@ -1,8 +1,8 @@ /* Minetest - Copyright (C) 2010-11 celeron55, Perttu Ahola - Copyright (C) 2011 Ciaran Gultnieks - Copyright (C) 2011 teddydestodes + Copyright (C) 2010-2013 celeron55, Perttu Ahola + Copyright (C) 2013 Ciaran Gultnieks + Copyright (C) 2013 teddydestodes This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiMainMenu.cpp b/src/guiMainMenu.cpp index eead3da92..c2e68579e 100644 --- a/src/guiMainMenu.cpp +++ b/src/guiMainMenu.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-12 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h index 0a4292b06..a21f3b32a 100644 --- a/src/guiMainMenu.h +++ b/src/guiMainMenu.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiMessageMenu.cpp b/src/guiMessageMenu.cpp index ce3bfe713..abb31cf2d 100644 --- a/src/guiMessageMenu.cpp +++ b/src/guiMessageMenu.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiMessageMenu.h b/src/guiMessageMenu.h index 2eef1d8cd..8ec8e4a41 100644 --- a/src/guiMessageMenu.h +++ b/src/guiMessageMenu.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiPasswordChange.cpp b/src/guiPasswordChange.cpp index 4ce1a3636..e4b77b611 100644 --- a/src/guiPasswordChange.cpp +++ b/src/guiPasswordChange.cpp @@ -1,7 +1,7 @@ /* Part of Minetest -Copyright (C) 2011 celeron55, Perttu Ahola -Copyright (C) 2011 Ciaran Gultnieks +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Ciaran Gultnieks Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/src/guiPasswordChange.h b/src/guiPasswordChange.h index 05b145202..aecc7076f 100644 --- a/src/guiPasswordChange.h +++ b/src/guiPasswordChange.h @@ -1,7 +1,7 @@ /* Part of Minetest -Copyright (C) 2010-11 celeron55, Perttu Ahola -Copyright (C) 2011 Ciaran Gultnieks +Copyright (C) 2010-2013 celeron55, Perttu Ahola +Copyright (C) 2013 Ciaran Gultnieks Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/src/guiPauseMenu.cpp b/src/guiPauseMenu.cpp index 2ca9a80fb..dc95eef5b 100644 --- a/src/guiPauseMenu.cpp +++ b/src/guiPauseMenu.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiPauseMenu.h b/src/guiPauseMenu.h index 8a4ef931e..25011a34a 100644 --- a/src/guiPauseMenu.h +++ b/src/guiPauseMenu.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiTextInputMenu.cpp b/src/guiTextInputMenu.cpp index 56844228d..f4e3bb5ba 100644 --- a/src/guiTextInputMenu.cpp +++ b/src/guiTextInputMenu.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiTextInputMenu.h b/src/guiTextInputMenu.h index a532ec008..2fa7ead9e 100644 --- a/src/guiTextInputMenu.h +++ b/src/guiTextInputMenu.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/guiVolumeChange.cpp b/src/guiVolumeChange.cpp index 99b302dd7..cfb89da1c 100644 --- a/src/guiVolumeChange.cpp +++ b/src/guiVolumeChange.cpp @@ -1,7 +1,7 @@ /* Part of Minetest -Copyright (C) 2011 celeron55, Perttu Ahola -Copyright (C) 2011 Ciaran Gultnieks +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Ciaran Gultnieks Copyright (C) 2013 RealBadAngel, Maciej Kasatkin Permission to use, copy, modify, and distribute this software for any diff --git a/src/guiVolumeChange.h b/src/guiVolumeChange.h index 6571640e9..1dcc7fbde 100644 --- a/src/guiVolumeChange.h +++ b/src/guiVolumeChange.h @@ -1,7 +1,7 @@ /* Part of Minetest -Copyright (C) 2010-11 celeron55, Perttu Ahola -Copyright (C) 2011 Ciaran Gultnieks +Copyright (C) 2010-2013 celeron55, Perttu Ahola +Copyright (C) 2013 Ciaran Gultnieks Copyright (C) 2013 RealBadAngel, Maciej Kasatkin Permission to use, copy, modify, and distribute this software for any diff --git a/src/hex.h b/src/hex.h index 88703878e..87a6aecb4 100644 --- a/src/hex.h +++ b/src/hex.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 Jonathan Neuschäfer +Copyright (C) 2013 Jonathan Neuschäfer This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/intlGUIEditBox.cpp b/src/intlGUIEditBox.cpp index 4add61e20..32cca4866 100644 --- a/src/intlGUIEditBox.cpp +++ b/src/intlGUIEditBox.cpp @@ -25,7 +25,7 @@ // after program initialization.... // -// Copyright (C) 2002-2010 Nikolaus Gebhardt +// Copyright (C) 2002-2013 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h diff --git a/src/intlGUIEditBox.h b/src/intlGUIEditBox.h index f888fb620..e3ee15a30 100644 --- a/src/intlGUIEditBox.h +++ b/src/intlGUIEditBox.h @@ -1,4 +1,4 @@ -// Copyright (C) 2002-2010 Nikolaus Gebhardt +// Copyright (C) 2002-2013 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h diff --git a/src/inventory.cpp b/src/inventory.cpp index 82896f3c0..7051b611f 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/inventory.h b/src/inventory.h index ffe944a13..676088b94 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index 5042376c6..08cae6d40 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/inventorymanager.h b/src/inventorymanager.h index eec461f2c..8e2abc961 100644 --- a/src/inventorymanager.h +++ b/src/inventorymanager.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/irr_aabb3d.h b/src/irr_aabb3d.h index 3cc8c33a2..7ac401837 100644 --- a/src/irr_aabb3d.h +++ b/src/irr_aabb3d.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/irr_v2d.h b/src/irr_v2d.h index 9a0492866..5c0d65a30 100644 --- a/src/irr_v2d.h +++ b/src/irr_v2d.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/irr_v3d.h b/src/irr_v3d.h index f5c7a1bfb..7bc73ad10 100644 --- a/src/irr_v3d.h +++ b/src/irr_v3d.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/irrlichttypes.h b/src/irrlichttypes.h index 195c3cc0c..7da1a4bd2 100644 --- a/src/irrlichttypes.h +++ b/src/irrlichttypes.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/irrlichttypes_bloated.h b/src/irrlichttypes_bloated.h index 6576335ba..2ce19999e 100644 --- a/src/irrlichttypes_bloated.h +++ b/src/irrlichttypes_bloated.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/irrlichttypes_extrabloated.h b/src/irrlichttypes_extrabloated.h index 17f2e298a..a541b1a02 100644 --- a/src/irrlichttypes_extrabloated.h +++ b/src/irrlichttypes_extrabloated.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 6c3bdb5e3..c1ecf762f 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola -Copyright (C) 2011 Kahrl +Copyright (C) 2010-2013 celeron55, Perttu Ahola +Copyright (C) 2013 Kahrl This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/itemdef.h b/src/itemdef.h index a3054518d..4a9e16966 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola -Copyright (C) 2011 Kahrl +Copyright (C) 2010-2013 celeron55, Perttu Ahola +Copyright (C) 2013 Kahrl This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/itemgroup.h b/src/itemgroup.h index 3a07e9e02..69678064f 100644 --- a/src/itemgroup.h +++ b/src/itemgroup.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/keycode.cpp b/src/keycode.cpp index 7b3c16332..8aadab2f1 100644 --- a/src/keycode.cpp +++ b/src/keycode.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/keycode.h b/src/keycode.h index 7fe5f42d1..65f04d8d7 100644 --- a/src/keycode.h +++ b/src/keycode.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/light.cpp b/src/light.cpp index 549603495..765c6303a 100644 --- a/src/light.cpp +++ b/src/light.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/light.h b/src/light.h index bafd10914..e847e1ce9 100644 --- a/src/light.h +++ b/src/light.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/localplayer.cpp b/src/localplayer.cpp index ddb38fffa..0554302e0 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/localplayer.h b/src/localplayer.h index 81b6eb808..f372c787d 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/log.cpp b/src/log.cpp index 14c49857b..366d83b64 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/log.h b/src/log.h index 5c9bb1b26..7d8b60b71 100644 --- a/src/log.h +++ b/src/log.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/logoutputbuffer.h b/src/logoutputbuffer.h index 316da7856..69f06c444 100644 --- a/src/logoutputbuffer.h +++ b/src/logoutputbuffer.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/luaentity_common.h b/src/luaentity_common.h index ee6ef00a0..35b079ade 100644 --- a/src/luaentity_common.h +++ b/src/luaentity_common.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/main.cpp b/src/main.cpp index 1774641f1..cfd643ac7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/main.h b/src/main.h index 126445b03..daa8c70d2 100644 --- a/src/main.h +++ b/src/main.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mainmenumanager.h b/src/mainmenumanager.h index 8915dd933..ce7684f11 100644 --- a/src/mainmenumanager.h +++ b/src/mainmenumanager.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/map.cpp b/src/map.cpp index f57aeee88..696d73182 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/map.h b/src/map.h index 7078dedc0..1c91745c0 100644 --- a/src/map.h +++ b/src/map.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapblock.cpp b/src/mapblock.cpp index a84fdeb6a..a6e9b3951 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapblock.h b/src/mapblock.h index 2c8d6de66..692b54318 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index 1797db22e..f4d57922a 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index 7d8abbb8d..5b33990c6 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapchunk.h b/src/mapchunk.h index a45dcdc3d..a70de8711 100644 --- a/src/mapchunk.h +++ b/src/mapchunk.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 14981f12c..73fe63318 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapgen.h b/src/mapgen.h index 13d17bd58..765ac3bb2 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapgen_v6.cpp b/src/mapgen_v6.cpp index aad25c6da..dd4452928 100644 --- a/src/mapgen_v6.cpp +++ b/src/mapgen_v6.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapgen_v6.h b/src/mapgen_v6.h index 4171ec534..d2f05252b 100644 --- a/src/mapgen_v6.h +++ b/src/mapgen_v6.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapnode.cpp b/src/mapnode.cpp index d9bc67081..0513e688c 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapnode.h b/src/mapnode.h index f89d8b0ed..d70764e0e 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapsector.cpp b/src/mapsector.cpp index 2cba9444f..108effa79 100644 --- a/src/mapsector.cpp +++ b/src/mapsector.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mapsector.h b/src/mapsector.h index 0f4bcdd3a..88fc76b57 100644 --- a/src/mapsector.h +++ b/src/mapsector.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mesh.cpp b/src/mesh.cpp index 4bfbc40c8..c2e6bcaa4 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mesh.h b/src/mesh.h index 062363bb7..cf46e7f8f 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/modalMenu.h b/src/modalMenu.h index 43f559062..d19b4e27c 100644 --- a/src/modalMenu.h +++ b/src/modalMenu.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/modifiedstate.h b/src/modifiedstate.h index 0beebcfbf..75518f2f5 100644 --- a/src/modifiedstate.h +++ b/src/modifiedstate.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mods.cpp b/src/mods.cpp index cd3840edd..ac2d9b17d 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/mods.h b/src/mods.h index 35894b339..9761a9103 100644 --- a/src/mods.h +++ b/src/mods.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/nameidmapping.cpp b/src/nameidmapping.cpp index 0e6090745..bcddb4515 100644 --- a/src/nameidmapping.cpp +++ b/src/nameidmapping.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/nameidmapping.h b/src/nameidmapping.h index 3cb87987d..417c441d2 100644 --- a/src/nameidmapping.h +++ b/src/nameidmapping.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 5b9c64cd4..9a1145a8e 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/nodedef.h b/src/nodedef.h index ade56dadf..b3f972b9b 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index 56391a39c..911d86d22 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/nodemetadata.h b/src/nodemetadata.h index 1602ef937..5af10d0f0 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/nodetimer.cpp b/src/nodetimer.cpp index d1043fa51..db5fb08b6 100644 --- a/src/nodetimer.cpp +++ b/src/nodetimer.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/nodetimer.h b/src/nodetimer.h index 1f2f2e6f3..bdbd32293 100644 --- a/src/nodetimer.h +++ b/src/nodetimer.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/noise.cpp b/src/noise.cpp index c0d3fe163..d250882e8 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/noise.h b/src/noise.h index 74e89a90f..c2a85771c 100644 --- a/src/noise.h +++ b/src/noise.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/object_properties.cpp b/src/object_properties.cpp index c02203a81..6086bf09f 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/object_properties.h b/src/object_properties.h index e1604b487..bde38bd66 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/particles.cpp b/src/particles.cpp index a1f5739de..fef21d91b 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/particles.h b/src/particles.h index a9d189e4e..b317549e7 100644 --- a/src/particles.h +++ b/src/particles.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/player.cpp b/src/player.cpp index 2a4f3747a..4c81887be 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/player.h b/src/player.h index 8c9e7e725..496c99532 100644 --- a/src/player.h +++ b/src/player.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/porting.cpp b/src/porting.cpp index 0f702810d..7ad557833 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/porting.h b/src/porting.h index f98980460..13b715557 100644 --- a/src/porting.h +++ b/src/porting.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/profiler.h b/src/profiler.h index a3ac41a25..b9fa22485 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/quicktune.cpp b/src/quicktune.cpp index ba0e0c654..d3a7fcae2 100644 --- a/src/quicktune.cpp +++ b/src/quicktune.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/quicktune.h b/src/quicktune.h index a0aa28d8e..566fbfc4d 100644 --- a/src/quicktune.h +++ b/src/quicktune.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/quicktune_shortcutter.h b/src/quicktune_shortcutter.h index 0ec80e48b..16bcc07e8 100644 --- a/src/quicktune_shortcutter.h +++ b/src/quicktune_shortcutter.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/rollback.cpp b/src/rollback.cpp index fd60e0302..fe1d59d0c 100644 --- a/src/rollback.cpp +++ b/src/rollback.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/rollback.h b/src/rollback.h index 8da03b588..46a76f583 100644 --- a/src/rollback.h +++ b/src/rollback.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp index 74f3e691c..70a9e9457 100644 --- a/src/rollback_interface.cpp +++ b/src/rollback_interface.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/rollback_interface.h b/src/rollback_interface.h index c18af3076..b2e8f3428 100644 --- a/src/rollback_interface.h +++ b/src/rollback_interface.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/script.cpp b/src/script.cpp index a27ac3c9f..4619fa636 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/script.h b/src/script.h index 51ed4a02d..dbacae5ae 100644 --- a/src/script.h +++ b/src/script.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp index 4d02fc965..7fddcfc68 100644 --- a/src/scriptapi.cpp +++ b/src/scriptapi.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/scriptapi.h b/src/scriptapi.h index 8c48c756f..e94ca781f 100644 --- a/src/scriptapi.h +++ b/src/scriptapi.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/serialization.cpp b/src/serialization.cpp index e79342432..118bad467 100644 --- a/src/serialization.cpp +++ b/src/serialization.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/serialization.h b/src/serialization.h index b89997155..defead31e 100644 --- a/src/serialization.h +++ b/src/serialization.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/server.cpp b/src/server.cpp index b1d0b8d46..686a3fea1 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/server.h b/src/server.h index 157ade086..26973643a 100644 --- a/src/server.h +++ b/src/server.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 77b0ceef1..b5e6aad4e 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/serverlist.h b/src/serverlist.h index 7aae32de1..e81e64c5b 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/serverobject.cpp b/src/serverobject.cpp index af77d466c..beb17d31f 100644 --- a/src/serverobject.cpp +++ b/src/serverobject.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/serverobject.h b/src/serverobject.h index c5ae6aa8e..6525270f6 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/settings.h b/src/settings.h index db4f07eea..7ac308cc0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/shader.cpp b/src/shader.cpp index b05aad7a4..7e3d16e8b 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola -Copyright (C) 2012 Kahrl +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Kahrl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/shader.h b/src/shader.h index c39602cc0..a7367eaff 100644 --- a/src/shader.h +++ b/src/shader.h @@ -1,7 +1,7 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola -Copyright (C) 2012 Kahrl +Copyright (C) 2013 celeron55, Perttu Ahola +Copyright (C) 2013 Kahrl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/sky.h b/src/sky.h index 6b9168fc6..7b7295e4e 100644 --- a/src/sky.h +++ b/src/sky.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/socket.cpp b/src/socket.cpp index 997a80c44..9e289baa2 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/socket.h b/src/socket.h index 2617c03bd..994a07e45 100644 --- a/src/socket.h +++ b/src/socket.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/sound.cpp b/src/sound.cpp index 1e884e43a..707065043 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/sound.h b/src/sound.h index 1f9b7d6e1..c21401e8b 100644 --- a/src/sound.h +++ b/src/sound.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index 069641b74..cc5261eae 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola OpenAL support based on work by: Copyright (C) 2011 Sebastian 'Bahamada' Rühl Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits diff --git a/src/sound_openal.h b/src/sound_openal.h index d3ddfb766..13d01a5d6 100644 --- a/src/sound_openal.h +++ b/src/sound_openal.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/staticobject.cpp b/src/staticobject.cpp index 00d846ba5..48fadaf06 100644 --- a/src/staticobject.cpp +++ b/src/staticobject.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/staticobject.h b/src/staticobject.h index ca358236c..c8427fe47 100644 --- a/src/staticobject.h +++ b/src/staticobject.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/strfnd.h b/src/strfnd.h index dcd65f6cd..d28aa73b2 100644 --- a/src/strfnd.h +++ b/src/strfnd.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/subgame.cpp b/src/subgame.cpp index a22f03bc2..3c8bf53c5 100644 --- a/src/subgame.cpp +++ b/src/subgame.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2012 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/subgame.h b/src/subgame.h index fa6d70cee..8561c1a52 100644 --- a/src/subgame.h +++ b/src/subgame.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/test.cpp b/src/test.cpp index c7c97989f..f988b34f7 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/test.h b/src/test.h index 606a1c289..547e9a986 100644 --- a/src/test.h +++ b/src/test.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/threads.h b/src/threads.h index 2ffc5eb1b..a3717a1de 100644 --- a/src/threads.h +++ b/src/threads.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/tile.cpp b/src/tile.cpp index 0936ba8e9..7286293d8 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/tile.h b/src/tile.h index 068defbb2..07e5bcb56 100644 --- a/src/tile.h +++ b/src/tile.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/tool.cpp b/src/tool.cpp index b452a00f6..04f19749c 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2011 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/tool.h b/src/tool.h index 315a51bac..e812a9e36 100644 --- a/src/tool.h +++ b/src/tool.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/treegen.cpp b/src/treegen.cpp index 400377838..7b6152329 100644 --- a/src/treegen.cpp +++ b/src/treegen.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola , +Copyright (C) 2010-2013 celeron55, Perttu Ahola , 2012-2013 RealBadAngel, Maciej Kasatkin This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/treegen.h b/src/treegen.h index 18adff859..ca4d3e23d 100644 --- a/src/treegen.h +++ b/src/treegen.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola , +Copyright (C) 2010-2013 celeron55, Perttu Ahola , 2012-2013 RealBadAngel, Maciej Kasatkin This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/container.h b/src/util/container.h index 15c877fde..775372649 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/directiontables.cpp b/src/util/directiontables.cpp index 54f8edf9e..d6c154842 100644 --- a/src/util/directiontables.cpp +++ b/src/util/directiontables.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/directiontables.h b/src/util/directiontables.h index 4e26eae3e..0dd3aab09 100644 --- a/src/util/directiontables.h +++ b/src/util/directiontables.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 2d6afc3c6..a79454628 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/numeric.h b/src/util/numeric.h index 5ef014ff9..450a98e40 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/pointedthing.cpp b/src/util/pointedthing.cpp index d5cc81003..dc39e313a 100644 --- a/src/util/pointedthing.cpp +++ b/src/util/pointedthing.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/pointedthing.h b/src/util/pointedthing.h index aab7a1043..2b2703e98 100644 --- a/src/util/pointedthing.h +++ b/src/util/pointedthing.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/pointer.h b/src/util/pointer.h index 96f4c656b..f65683332 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index c1e99c7eb..f32813551 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/serialize.h b/src/util/serialize.h index b0e93ad26..b297786e6 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/string.cpp b/src/util/string.cpp index e151abefc..3bd8b7364 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/string.h b/src/util/string.h index b655ce25c..2f0264bd4 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/thread.h b/src/util/thread.h index db8e0b89e..949bb4204 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/timetaker.cpp b/src/util/timetaker.cpp index 42a248dc6..910fea822 100644 --- a/src/util/timetaker.cpp +++ b/src/util/timetaker.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/util/timetaker.h b/src/util/timetaker.h index 75542f433..0b9d9ca04 100644 --- a/src/util/timetaker.h +++ b/src/util/timetaker.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/voxel.cpp b/src/voxel.cpp index be504f983..0ff5305fa 100644 --- a/src/voxel.cpp +++ b/src/voxel.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/voxel.h b/src/voxel.h index 79155a0f6..b48943624 100644 --- a/src/voxel.h +++ b/src/voxel.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010 celeron55, Perttu Ahola +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp index c9aff019e..bd8f816b8 100644 --- a/src/voxelalgorithms.cpp +++ b/src/voxelalgorithms.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/voxelalgorithms.h b/src/voxelalgorithms.h index 7a88caf2f..2a5fc394e 100644 --- a/src/voxelalgorithms.h +++ b/src/voxelalgorithms.h @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2010-2012 celeron55, Perttu Ahola +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by From b9d8e59bbf727fcc1a073bbf27e5d1703b9490ef Mon Sep 17 00:00:00 2001 From: kwolekr Date: Wed, 13 Feb 2013 22:43:15 -0500 Subject: [PATCH 32/43] Add emerge.cpp, initial EmergeThread changes - Neatly placed all emerge related code into a new file, emerge.cpp - Greatly cleaned up the code in EmergeThread::Thread() - Reworked Emerge queue. Now an actual std::queue of v3s16 block positions - Removed the completely unnecessary map of peer ids requesting blocks --- src/CMakeLists.txt | 1 + src/emerge.cpp | 677 +++++++++++++++++++++++++++++++++++++++++++ src/emerge.h | 122 ++++++++ src/environment.h | 1 - src/jthread/jmutex.h | 50 ++++ src/map.cpp | 1 + src/map.h | 4 +- src/mapgen.cpp | 153 ---------- src/mapgen.h | 52 +--- src/mapgen_v6.cpp | 1 + src/server.cpp | 345 ++-------------------- src/server.h | 39 +-- 12 files changed, 886 insertions(+), 560 deletions(-) create mode 100644 src/emerge.cpp create mode 100644 src/emerge.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c06da20c4..d2f080c90 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -209,6 +209,7 @@ set(common_SRCS script.cpp log.cpp content_sao.cpp + emerge.cpp mapgen.cpp mapgen_v6.cpp treegen.cpp diff --git a/src/emerge.cpp b/src/emerge.cpp new file mode 100644 index 000000000..728ea7196 --- /dev/null +++ b/src/emerge.cpp @@ -0,0 +1,677 @@ +/* +Minetest-c55 +Copyright (C) 2010-2011 celeron55, Perttu Ahola + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include "server.h" +#include +#include +#include "clientserver.h" +#include "map.h" +#include "jmutexautolock.h" +#include "main.h" +#include "constants.h" +#include "voxel.h" +#include "config.h" +#include "mapblock.h" +#include "serverobject.h" +#include "settings.h" +#include "script.h" +#include "scriptapi.h" +#include "profiler.h" +#include "log.h" +#include "nodedef.h" +#include "biome.h" +#include "emerge.h" +#include "mapgen_v6.h" + + +EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { + //register built-in mapgens + registerMapgen("v6", new MapgenFactoryV6()); + + this->biomedef = bdef ? bdef : new BiomeDefManager(gamedef); + this->params = NULL; + this->mapgen = NULL; + + queuemutex.Init(); + emergethread = new EmergeThread((Server *)gamedef); +} + + +EmergeManager::~EmergeManager() { + emergethread->setRun(false); + emergethread->stop(); + + delete emergethread; + delete biomedef; + delete mapgen; + delete params; +} + + +void EmergeManager::initMapgens(MapgenParams *mgparams) { + if (mapgen) + return; + + this->params = mgparams; + this->mapgen = getMapgen(); //only one mapgen for now! +} + + +Mapgen *EmergeManager::getMapgen() { + if (!mapgen) { + mapgen = createMapgen(params->mg_name, 0, params, this); + if (!mapgen) { + infostream << "EmergeManager: falling back to mapgen v6" << std::endl; + delete params; + params = createMapgenParams("v6"); + mapgen = createMapgen("v6", 0, params, this); + } + } + return mapgen; +} + + +bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate) { ///return false if adding failed, or queue full! + u8 flags = 0; + + if (allow_generate) + flags |= BLOCK_EMERGE_ALLOWGEN; + + //TODO: + // add logic to select which emergethread to add it to + // - one with the least queue contents? + // - if a queue is too full, move onto another one + // - use the peer id sometime + + { + JMutexAutoLock queuelock(queuemutex); + + std::map::const_iterator iter = blocks_enqueued.find(p); + if (iter != blocks_enqueued.end()) { + flags |= iter->second; + blocks_enqueued[p] = flags; + return true; + } + + blocks_enqueued.insert(std::make_pair(p, flags)); + emergethread->blockqueue.push(p); + } + emergethread->qevent.signal(); + + return true; +} + + +bool EmergeManager::popBlockEmerge(v3s16 *pos, u8 *flags) { + JMutexAutoLock queuelock(queuemutex); + + if (emergethread->blockqueue.empty()) + return false; + v3s16 p = emergethread->blockqueue.front(); + emergethread->blockqueue.pop(); + + *pos = p; + + std::map::iterator iter = blocks_enqueued.find(p); + if (iter == blocks_enqueued.end()) //uh oh, this isn't right!!!!!!!!!!!!!!!!!! + return false; + + *flags = iter->second; + blocks_enqueued.erase(iter); + + return true; +} + + +int EmergeManager::getGroundLevelAtPoint(v2s16 p) { + if (!mapgen) + return 0; + return mapgen->getGroundLevelAtPoint(p); +} + + +bool EmergeManager::isBlockUnderground(v3s16 blockpos) { + /* + v2s16 p = v2s16((blockpos.X * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2, + (blockpos.Y * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2); + int ground_level = getGroundLevelAtPoint(p); + return blockpos.Y * (MAP_BLOCKSIZE + 1) <= min(water_level, ground_level); + */ + + //yuck, but then again, should i bother being accurate? + //the height of the nodes in a single block is quite variable + return blockpos.Y * (MAP_BLOCKSIZE + 1) <= params->water_level; +} + + +u32 EmergeManager::getBlockSeed(v3s16 p) { + return (u32)(params->seed & 0xFFFFFFFF) + + p.Z * 38134234 + + p.Y * 42123 + + p.Y * 23; +} + + +Mapgen *EmergeManager::createMapgen(std::string mgname, int mgid, + MapgenParams *mgparams, EmergeManager *emerge) { + std::map::const_iterator iter = mglist.find(mgname); + if (iter == mglist.end()) { + errorstream << "EmergeManager; mapgen " << mgname << + " not registered" << std::endl; + return NULL; + } + + MapgenFactory *mgfactory = iter->second; + return mgfactory->createMapgen(mgid, mgparams, emerge); +} + + +MapgenParams *EmergeManager::createMapgenParams(std::string mgname) { + std::map::const_iterator iter = mglist.find(mgname); + if (iter == mglist.end()) { + errorstream << "EmergeManager: mapgen " << mgname << + " not registered" << std::endl; + return NULL; + } + + MapgenFactory *mgfactory = iter->second; + return mgfactory->createMapgenParams(); +} + + +MapgenParams *EmergeManager::getParamsFromSettings(Settings *settings) { + std::string mg_name = settings->get("mg_name"); + MapgenParams *mgparams = createMapgenParams(mg_name); + + mgparams->mg_name = mg_name; + mgparams->seed = settings->getU64(settings == g_settings ? "fixed_map_seed" : "seed"); + mgparams->water_level = settings->getS16("water_level"); + mgparams->chunksize = settings->getS16("chunksize"); + mgparams->flags = settings->getS32("mg_flags"); + + if (!mgparams->readParams(settings)) { + delete mgparams; + return NULL; + } + return mgparams; +} + + +void EmergeManager::setParamsToSettings(Settings *settings) { + settings->set("mg_name", params->mg_name); + settings->setU64("seed", params->seed); + settings->setS16("water_level", params->water_level); + settings->setS16("chunksize", params->chunksize); + settings->setFlagStr("mg_flags", params->flags, flagdesc_mapgen); + + params->writeParams(settings); +} + + +bool EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) { + mglist.insert(std::make_pair(mgname, mgfactory)); + infostream << "EmergeManager: registered mapgen " << mgname << std::endl; +} + + + +class MapEditEventIgnorer +{ +public: + MapEditEventIgnorer(bool *flag): + m_flag(flag) + { + if(*m_flag == false) + *m_flag = true; + else + m_flag = NULL; + } + + ~MapEditEventIgnorer() + { + if(m_flag) + { + assert(*m_flag); + *m_flag = false; + } + } + +private: + bool *m_flag; +}; + +class MapEditEventAreaIgnorer +{ +public: + MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): + m_ignorevariable(ignorevariable) + { + if(m_ignorevariable->getVolume() == 0) + *m_ignorevariable = a; + else + m_ignorevariable = NULL; + } + + ~MapEditEventAreaIgnorer() + { + if(m_ignorevariable) + { + assert(m_ignorevariable->getVolume() != 0); + *m_ignorevariable = VoxelArea(); + } + } + +private: + VoxelArea *m_ignorevariable; +}; + + +#if 1 + +#define EMERGE_DBG_OUT(x) \ + { if (enable_mapgen_debug_info) \ + infostream << "EmergeThread: " x << std::endl; } + +bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b, + BlockMakeData *data, bool allow_gen) { + v2s16 p2d(p.X, p.Z); + //envlock: usually takes <=1ms, sometimes 90ms or ~400ms to acquire + JMutexAutoLock envlock(m_server->m_env_mutex); + + // Load sector if it isn't loaded + if (map->getSectorNoGenerateNoEx(p2d) == NULL) + map->loadSectorMeta(p2d); + + // Attempt to load block + MapBlock *block = map->getBlockNoCreateNoEx(p); + if (!block || block->isDummy() || !block->isGenerated()) { + EMERGE_DBG_OUT("not in memory, attempting to load from disk"); + block = map->loadBlock(p); + } + + // If could not load and allowed to generate, + // start generation inside this same envlock + if (allow_gen && (block == NULL || !block->isGenerated())) { + EMERGE_DBG_OUT("generating"); + map->initBlockMake(data, p); + return true; + } + + *b = block; + return false; +} + + +void *EmergeThread::Thread() { + ThreadStarted(); + log_register_thread("EmergeThread"); + DSTACK(__FUNCTION_NAME); + BEGIN_DEBUG_EXCEPTION_HANDLER + + v3s16 last_tried_pos(-32768,-32768,-32768); // For error output + v3s16 p; + u8 flags; + + map = (ServerMap *)&(m_server->m_env->getMap()); + emerge = m_server->m_emerge; + mapgen = emerge->getMapgen(); + + while (getRun()) + try { + while (!emerge->popBlockEmerge(&p, &flags)) + qevent.wait(); + + last_tried_pos = p; + if (blockpos_over_limit(p)) + continue; + + bool allow_generate = flags & BLOCK_EMERGE_ALLOWGEN; + EMERGE_DBG_OUT("p=" PP(p) " allow_generate=" << allow_generate); + + /* + Try to fetch block from memory or disk. + If not found and asked to generate, initialize generator. + */ + BlockMakeData data; + MapBlock *block = NULL; + core::map modified_blocks; + + if (getBlockOrStartGen(p, &block, &data, allow_generate)) { + { + ScopeProfiler sp(g_profiler, "EmergeThread: Mapgen::makeChunk", SPT_AVG); + TimeTaker t("mapgen::make_block()"); + + mapgen->makeChunk(&data); + + if (enable_mapgen_debug_info == false) + t.stop(true); // Hide output + } + + { + //envlock: usually 0ms, but can take either 30 or 400ms to acquire + JMutexAutoLock envlock(m_server->m_env_mutex); + ScopeProfiler sp(g_profiler, "EmergeThread: after " + "mapgen::make_block (envlock)", SPT_AVG); + + map->finishBlockMake(&data, modified_blocks); + + block = map->getBlockNoCreateNoEx(p); + if (block) { + /* + Do some post-generate stuff + */ + v3s16 minp = data.blockpos_min * MAP_BLOCKSIZE; + v3s16 maxp = data.blockpos_max * MAP_BLOCKSIZE + + v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); + + // Ignore map edit events, they will not need to be sent + // to anybody because the block hasn't been sent to anybody + MapEditEventAreaIgnorer + ign(&m_server->m_ignore_map_edit_events_area, + VoxelArea(minp, maxp)); + { // takes about 90ms with -O1 on an e3-1230v2 + scriptapi_environment_on_generated(m_server->m_lua, + minp, maxp, emerge->getBlockSeed(minp)); + } + + EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); + + m_server->m_env->activateBlock(block, 0); + } + } + } + + /* + Set sent status of modified blocks on clients + */ + + // NOTE: Server's clients are also behind the connection mutex + //conlock: consistently takes 30-40ms to acquire + JMutexAutoLock lock(m_server->m_con_mutex); + // Add the originally fetched block to the modified list + if (block) + modified_blocks.insert(p, block); + + // Set the modified blocks unsent for all the clients + for (core::map::Iterator + i = m_server->m_clients.getIterator(); + i.atEnd() == false; i++) { + RemoteClient *client = i.getNode()->getValue(); + if (modified_blocks.size() > 0) { + // Remove block from sent history + client->SetBlocksNotSent(modified_blocks); + } + } + } + catch (VersionMismatchException &e) { + std::ostringstream err; + err << "World data version mismatch in MapBlock "<setAsyncFatalError(err.str()); + } + catch (SerializationError &e) { + std::ostringstream err; + err << "Invalid data in MapBlock "<setAsyncFatalError(err.str()); + } + + END_DEBUG_EXCEPTION_HANDLER(errorstream) + log_deregister_thread(); + return NULL; +} + +#else + +void *EmergeThread::Thread() { + ThreadStarted(); + log_register_thread("EmergeThread"); + DSTACK(__FUNCTION_NAME); + BEGIN_DEBUG_EXCEPTION_HANDLER + + bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); + + v3s16 last_tried_pos(-32768,-32768,-32768); // For error output + ServerMap &map = ((ServerMap&)m_server->m_env->getMap()); + EmergeManager *emerge = m_server->m_emerge; + Mapgen *mapgen = emerge->getMapgen(); + + while(getRun()) + try { + QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop(); + if(qptr == NULL) + break; + SharedPtr q(qptr); + + v3s16 &p = q->pos; + v2s16 p2d(p.X,p.Z); + + last_tried_pos = p; + + /* + Do not generate over-limit + */ + if (blockpos_over_limit(p)) + continue; + + //infostream<<"EmergeThread::Thread(): running"<::Iterator i; + for (i=q->s.getIterator(); !i.atEnd(); i++) { + u8 flags = i.getNode()->getValue(); + if (!(flags & BLOCK_EMERGE_FLAG_FROMDISK)) { + only_from_disk = false; + break; + } + } + } + + if (enable_mapgen_debug_info) + infostream<<"EmergeThread: p=" + <<"("<isGenerated() == false)){ + if(enable_mapgen_debug_info) + infostream<<"EmergeThread: generating"<makeChunk(&data); + + if (enable_mapgen_debug_info == false) + t.stop(true); // Hide output + } + + do{ // enable break + // Lock environment again to access the map + JMutexAutoLock envlock(m_server->m_env_mutex); + + ScopeProfiler sp(g_profiler, "EmergeThread: after " + "mapgen::make_block (envlock)", SPT_AVG); + + // Blit data back on map, update lighting, add mobs and + // whatever this does + map.finishBlockMake(&data, modified_blocks); + + // Get central block + block = map.getBlockNoCreateNoEx(p); + + // If block doesn't exist, don't try doing anything with it + // This happens if the block is not in generation boundaries + if(!block) + break; + + /* + Do some post-generate stuff + */ + v3s16 minp = data.blockpos_min * MAP_BLOCKSIZE; + v3s16 maxp = data.blockpos_max * MAP_BLOCKSIZE + + v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); + + /* + Ignore map edit events, they will not need to be + sent to anybody because the block hasn't been sent + to anybody + */ + MapEditEventAreaIgnorer ign( + &m_server->m_ignore_map_edit_events_area, + VoxelArea(minp, maxp)); + { + TimeTaker timer("on_generated"); + scriptapi_environment_on_generated(m_server->m_lua, + minp, maxp, emerge->getBlockSeed(minp)); + //int t = timer.stop(true); + //dstream<<"on_generated took "<m_env->activateBlock(block, 0); + }while(false); + } + + if(block == NULL) + got_block = false; + + /* + Set sent status of modified blocks on clients + */ + + // NOTE: Server's clients are also behind the connection mutex + JMutexAutoLock lock(m_server->m_con_mutex); + + /* + Add the originally fetched block to the modified list + */ + if(got_block) + modified_blocks.insert(p, block); + + /* + Set the modified blocks unsent for all the clients + */ + for(core::map::Iterator + i = m_server->m_clients.getIterator(); + i.atEnd() == false; i++) { + RemoteClient *client = i.getNode()->getValue(); + if(modified_blocks.size() > 0) { + // Remove block from sent history + client->SetBlocksNotSent(modified_blocks); + } + } + + +niters++; + } + catch (VersionMismatchException &e) { + std::ostringstream err; + err << "World data version mismatch in MapBlock "<setAsyncFatalError(err.str()); + } + catch (SerializationError &e) { + std::ostringstream err; + err << "Invalid data in MapBlock "<setAsyncFatalError(err.str()); + } +printf("emergethread iterated %d times\n", niters); + END_DEBUG_EXCEPTION_HANDLER(errorstream) + log_deregister_thread(); + return NULL; +} + +#endif diff --git a/src/emerge.h b/src/emerge.h new file mode 100644 index 000000000..0acc89a6d --- /dev/null +++ b/src/emerge.h @@ -0,0 +1,122 @@ +#ifndef EMERGE_HEADER +#define EMERGE_HEADER + +#include +#include +#include "util/thread.h" + +#define BLOCK_EMERGE_ALLOWGEN (1<<0) + +class Mapgen; +class MapgenParams; +class MapgenFactory; +class Biome; +class BiomeDefManager; +class EmergeThread; +class ManualMapVoxelManipulator; +//class ServerMap; +//class MapBlock; + +#include "server.h" + +struct BlockMakeData { + bool no_op; + ManualMapVoxelManipulator *vmanip; + u64 seed; + v3s16 blockpos_min; + v3s16 blockpos_max; + v3s16 blockpos_requested; + UniqueQueue transforming_liquid; + INodeDefManager *nodedef; + +// BlockMakeData(); +// ~BlockMakeData(); + +BlockMakeData(): + no_op(false), + vmanip(NULL), + seed(0), + nodedef(NULL) +{} + +~BlockMakeData() +{ + delete vmanip; +} +}; + +class EmergeManager { +public: + std::map mglist; + + //settings + MapgenParams *params; + + JMutex queuemutex; + std::map blocks_enqueued; //change to a hashtable later + Mapgen *mapgen; + EmergeThread *emergethread; + + //biome manager + BiomeDefManager *biomedef; + + EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef); + ~EmergeManager(); + + void initMapgens(MapgenParams *mgparams); + Mapgen *createMapgen(std::string mgname, int mgid, + MapgenParams *mgparams, EmergeManager *emerge); + MapgenParams *createMapgenParams(std::string mgname); + Mapgen *getMapgen(); + bool enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate); + bool popBlockEmerge(v3s16 *pos, u8 *flags); + + bool registerMapgen(std::string name, MapgenFactory *mgfactory); + MapgenParams *getParamsFromSettings(Settings *settings); + void setParamsToSettings(Settings *settings); + + //mapgen helper methods + Biome *getBiomeAtPoint(v3s16 p); + int getGroundLevelAtPoint(v2s16 p); + bool isBlockUnderground(v3s16 blockpos); + u32 getBlockSeed(v3s16 p); +}; + +class EmergeThread : public SimpleThread +{ + Server *m_server; + ServerMap *map; + EmergeManager *emerge; + Mapgen *mapgen; + bool enable_mapgen_debug_info; + +public: + Event qevent; + std::queue blockqueue; + + EmergeThread(Server *server): + SimpleThread(), + m_server(server), + map(NULL), + emerge(NULL), + mapgen(NULL) + { + enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); + } + + void *Thread(); + + void trigger() + { + setRun(true); + if(IsRunning() == false) + { + Start(); + } + } + + bool getBlockOrStartGen(v3s16 p, MapBlock **b, + BlockMakeData *data, bool allow_generate); +}; + +#endif diff --git a/src/environment.h b/src/environment.h index a79ccc63d..07a4d7635 100644 --- a/src/environment.h +++ b/src/environment.h @@ -40,7 +40,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapnode.h" #include "mapblock.h" -class Server; class ServerEnvironment; class ActiveBlockModifier; class ServerActiveObject; diff --git a/src/jthread/jmutex.h b/src/jthread/jmutex.h index 9ce013096..6675162a5 100644 --- a/src/jthread/jmutex.h +++ b/src/jthread/jmutex.h @@ -67,4 +67,54 @@ private: bool initialized; }; +#ifdef _WIN32 + +class Event { + HANDLE hEvent; + +public: + Event() { + hEvent = CreateEvent(NULL, 0, 0, NULL); + } + + ~Event() { + CloseHandle(hEvent); + } + + void wait() { + WaitForSingleObject(hEvent, INFINITE); + } + + void signal() { + SetEvent(hEvent); + } +} + +#else + +#include + +class Event { + sem_t sem; + +public: + Event() { + sem_init(&sem, 0, 0); + } + + ~Event() { + sem_destroy(&sem); + } + + void wait() { + sem_wait(&sem); + } + + void signal() { + sem_post(&sem); + } +}; + +#endif + #endif // JMUTEX_H diff --git a/src/map.cpp b/src/map.cpp index 696d73182..7eb45463f 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "util/directiontables.h" #include "rollback_interface.h" +#include "emerge.h" #include "mapgen_v6.h" #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" diff --git a/src/map.h b/src/map.h index 1c91745c0..0b2311f39 100644 --- a/src/map.h +++ b/src/map.h @@ -46,6 +46,8 @@ class MapBlock; class NodeMetadata; class IGameDef; class IRollbackReportSink; +class EmergeManager; +class BlockMakeData; /* @@ -378,7 +380,7 @@ public: Blocks are generated by using these and makeBlock(). */ void initBlockMake(BlockMakeData *data, v3s16 blockpos); - MapBlock* finishBlockMake(BlockMakeData *data, + MapBlock *finishBlockMake(BlockMakeData *data, core::map &changed_blocks); // A non-threaded wrapper to the above - DEFUNCT diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 73fe63318..ef5da6bf1 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -46,144 +46,6 @@ FlagDesc flagdesc_mapgen[] = { }; /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////// Emerge Manager //////////////////////////////// - - -EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { - //register built-in mapgens - registerMapgen("v6", new MapgenFactoryV6()); - - //the order of these assignments is pretty important - this->biomedef = bdef ? bdef : new BiomeDefManager(gamedef); - this->params = NULL; - this->mapgen = NULL; -} - - -EmergeManager::~EmergeManager() { - delete biomedef; - delete mapgen; - delete params; -} - - -void EmergeManager::initMapgens(MapgenParams *mgparams) { - if (mapgen) - return; - - this->params = mgparams; - this->mapgen = getMapgen(); //only one mapgen for now! -} - - -Mapgen *EmergeManager::getMapgen() { - if (!mapgen) { - mapgen = createMapgen(params->mg_name, 0, params, this); - if (!mapgen) { - infostream << "EmergeManager: falling back to mapgen v6" << std::endl; - delete params; - params = createMapgenParams("v6"); - mapgen = createMapgen("v6", 0, params, this); - } - } - return mapgen; -} - -void EmergeManager::addBlockToQueue() { - //STUB -} - - -int EmergeManager::getGroundLevelAtPoint(v2s16 p) { - if (!mapgen) - return 0; - return mapgen->getGroundLevelAtPoint(p); -} - - -bool EmergeManager::isBlockUnderground(v3s16 blockpos) { - /* - v2s16 p = v2s16((blockpos.X * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2, - (blockpos.Y * MAP_BLOCKSIZE) + MAP_BLOCKSIZE / 2); - int ground_level = getGroundLevelAtPoint(p); - return blockpos.Y * (MAP_BLOCKSIZE + 1) <= min(water_level, ground_level); - */ - - //yuck, but then again, should i bother being accurate? - //the height of the nodes in a single block is quite variable - return blockpos.Y * (MAP_BLOCKSIZE + 1) <= params->water_level; -} - - -u32 EmergeManager::getBlockSeed(v3s16 p) { - return (u32)(params->seed & 0xFFFFFFFF) + - p.Z * 38134234 + - p.Y * 42123 + - p.Y * 23; -} - - -Mapgen *EmergeManager::createMapgen(std::string mgname, int mgid, - MapgenParams *mgparams, EmergeManager *emerge) { - std::map::const_iterator iter = mglist.find(mgname); - if (iter == mglist.end()) { - errorstream << "EmergeManager; mapgen " << mgname << - " not registered" << std::endl; - return NULL; - } - - MapgenFactory *mgfactory = iter->second; - return mgfactory->createMapgen(mgid, mgparams, emerge); -} - - -MapgenParams *EmergeManager::createMapgenParams(std::string mgname) { - std::map::const_iterator iter = mglist.find(mgname); - if (iter == mglist.end()) { - errorstream << "EmergeManager: mapgen " << mgname << - " not registered" << std::endl; - return NULL; - } - - MapgenFactory *mgfactory = iter->second; - return mgfactory->createMapgenParams(); -} - - -MapgenParams *EmergeManager::getParamsFromSettings(Settings *settings) { - std::string mg_name = settings->get("mg_name"); - MapgenParams *mgparams = createMapgenParams(mg_name); - - mgparams->mg_name = mg_name; - mgparams->seed = settings->getU64(settings == g_settings ? "fixed_map_seed" : "seed"); - mgparams->water_level = settings->getS16("water_level"); - mgparams->chunksize = settings->getS16("chunksize"); - mgparams->flags = settings->getFlagStr("mg_flags", flagdesc_mapgen); - - if (!mgparams->readParams(settings)) { - delete mgparams; - return NULL; - } - return mgparams; -} - - -void EmergeManager::setParamsToSettings(Settings *settings) { - settings->set("mg_name", params->mg_name); - settings->setU64("seed", params->seed); - settings->setS16("water_level", params->water_level); - settings->setS16("chunksize", params->chunksize); - settings->setFlagStr("mg_flags", params->flags, flagdesc_mapgen); - - params->writeParams(settings); -} - - -void EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) { - mglist.insert(std::make_pair(mgname, mgfactory)); - infostream << "EmergeManager: registered mapgen " << mgname << std::endl; -} - ///////////////////// @@ -2986,18 +2848,3 @@ void make_block(BlockMakeData *data) #endif ///BIG COMMENT -BlockMakeData::BlockMakeData(): - no_op(false), - vmanip(NULL), - seed(0), - nodedef(NULL) -{} - -BlockMakeData::~BlockMakeData() -{ - delete vmanip; -} - -//}; // namespace mapgen - - diff --git a/src/mapgen.h b/src/mapgen.h index 765ac3bb2..c3c209ad1 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapnode.h" #include "noise.h" #include "settings.h" +//#include "emerge.h" #include /////////////////// Mapgen flags @@ -36,6 +37,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MGV6_BIOME_BLEND 0x10 #define MG_FLAT 0x20 +extern FlagDesc flagdesc_mapgen[]; + class BiomeDefManager; class Biome; class EmergeManager; @@ -43,20 +46,7 @@ class MapBlock; class ManualMapVoxelManipulator; class VoxelManipulator; class INodeDefManager; - -struct BlockMakeData { - bool no_op; - ManualMapVoxelManipulator *vmanip; - u64 seed; - v3s16 blockpos_min; - v3s16 blockpos_max; - v3s16 blockpos_requested; - UniqueQueue transforming_liquid; - INodeDefManager *nodedef; - - BlockMakeData(); - ~BlockMakeData(); -}; +class BlockMakeData; struct MapgenParams { std::string mg_name; @@ -99,39 +89,5 @@ struct MapgenFactory { virtual MapgenParams *createMapgenParams() = 0; }; -class EmergeManager { -public: - std::map mglist; - - //settings - MapgenParams *params; - - //mapgen objects here - Mapgen *mapgen; - - //biome manager - BiomeDefManager *biomedef; - - EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef); - ~EmergeManager(); - - void initMapgens(MapgenParams *mgparams); - Mapgen *createMapgen(std::string mgname, int mgid, - MapgenParams *mgparams, EmergeManager *emerge); - MapgenParams *createMapgenParams(std::string mgname); - Mapgen *getMapgen(); - void addBlockToQueue(); - - void registerMapgen(std::string name, MapgenFactory *mgfactory); - MapgenParams *getParamsFromSettings(Settings *settings); - void setParamsToSettings(Settings *settings); - - //mapgen helper methods - Biome *getBiomeAtPoint(v3s16 p); - int getGroundLevelAtPoint(v2s16 p); - bool isBlockUnderground(v3s16 blockpos); - u32 getBlockSeed(v3s16 p); -}; - #endif diff --git a/src/mapgen_v6.cpp b/src/mapgen_v6.cpp index dd4452928..ef2cf5f52 100644 --- a/src/mapgen_v6.cpp +++ b/src/mapgen_v6.cpp @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "profiler.h" #include "settings.h" // For g_settings #include "main.h" // For g_profiler +#include "emerge.h" #include "mapgen_v6.h" /////////////////// Mapgen V6 perlin noise default values diff --git a/src/server.cpp b/src/server.cpp index 686a3fea1..f2897d46d 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "itemdef.h" #include "craftdef.h" +#include "emerge.h" #include "mapgen.h" #include "biome.h" #include "content_mapnode.h" @@ -58,60 +59,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "rollback.h" #include "util/serialize.h" -#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" - -#define BLOCK_EMERGE_FLAG_FROMDISK (1<<0) - -class MapEditEventIgnorer -{ -public: - MapEditEventIgnorer(bool *flag): - m_flag(flag) - { - if(*m_flag == false) - *m_flag = true; - else - m_flag = NULL; - } - - ~MapEditEventIgnorer() - { - if(m_flag) - { - assert(*m_flag); - *m_flag = false; - } - } - -private: - bool *m_flag; -}; - -class MapEditEventAreaIgnorer -{ -public: - MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): - m_ignorevariable(ignorevariable) - { - if(m_ignorevariable->getVolume() == 0) - *m_ignorevariable = a; - else - m_ignorevariable = NULL; - } - - ~MapEditEventAreaIgnorer() - { - if(m_ignorevariable) - { - assert(m_ignorevariable->getVolume() != 0); - *m_ignorevariable = VoxelArea(); - } - } - -private: - VoxelArea *m_ignorevariable; -}; - void * ServerThread::Thread() { ThreadStarted(); @@ -157,265 +104,6 @@ void * ServerThread::Thread() return NULL; } -void * EmergeThread::Thread() -{ - ThreadStarted(); - - log_register_thread("EmergeThread"); - - DSTACK(__FUNCTION_NAME); - - BEGIN_DEBUG_EXCEPTION_HANDLER - - bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); - - v3s16 last_tried_pos(-32768,-32768,-32768); // For error output - - ServerMap &map = ((ServerMap&)m_server->m_env->getMap()); - EmergeManager *emerge = m_server->m_emerge; - Mapgen *mapgen = emerge->getMapgen(); - - /* - Get block info from queue, emerge them and send them - to clients. - - After queue is empty, exit. - */ - while(getRun()) - try{ - QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop(); - if(qptr == NULL) - break; - - SharedPtr q(qptr); - - v3s16 &p = q->pos; - v2s16 p2d(p.X,p.Z); - - last_tried_pos = p; - - /* - Do not generate over-limit - */ - if(blockpos_over_limit(p)) - continue; - - //infostream<<"EmergeThread::Thread(): running"<::Iterator i; - for(i=q->peer_ids.getIterator(); i.atEnd()==false; i++) - { - //u16 peer_id = i.getNode()->getKey(); - - // Check flags - u8 flags = i.getNode()->getValue(); - if((flags & BLOCK_EMERGE_FLAG_FROMDISK) == false) - only_from_disk = false; - - } - } - - if(enable_mapgen_debug_info) - infostream<<"EmergeThread: p=" - <<"("<isGenerated() == false)){ - if(enable_mapgen_debug_info) - infostream<<"EmergeThread: generating"<makeChunk(&data); - //mapgen::make_block(&data); - - if(enable_mapgen_debug_info == false) - t.stop(true); // Hide output - } - - do{ // enable break - // Lock environment again to access the map - JMutexAutoLock envlock(m_server->m_env_mutex); - - ScopeProfiler sp(g_profiler, "EmergeThread: after " - "mapgen::make_block (envlock)", SPT_AVG); - - // Blit data back on map, update lighting, add mobs and - // whatever this does - map.finishBlockMake(&data, modified_blocks); - - // Get central block - block = map.getBlockNoCreateNoEx(p); - - // If block doesn't exist, don't try doing anything with it - // This happens if the block is not in generation boundaries - if(!block) - break; - - /* - Do some post-generate stuff - */ - - v3s16 minp = data.blockpos_min*MAP_BLOCKSIZE; - v3s16 maxp = data.blockpos_max*MAP_BLOCKSIZE + - v3s16(1,1,1)*(MAP_BLOCKSIZE-1); - - /* - Ignore map edit events, they will not need to be - sent to anybody because the block hasn't been sent - to anybody - */ - //MapEditEventIgnorer ign(&m_server->m_ignore_map_edit_events); - MapEditEventAreaIgnorer ign( - &m_server->m_ignore_map_edit_events_area, - VoxelArea(minp, maxp)); - { - TimeTaker timer("on_generated"); - scriptapi_environment_on_generated(m_server->m_lua, - minp, maxp, emerge->getBlockSeed(minp)); - /*int t = timer.stop(true); - dstream<<"on_generated took "<m_env->activateBlock(block, 0); - }while(false); - } - - if(block == NULL) - got_block = false; - - /* - Set sent status of modified blocks on clients - */ - - // NOTE: Server's clients are also behind the connection mutex - JMutexAutoLock lock(m_server->m_con_mutex); - - /* - Add the originally fetched block to the modified list - */ - if(got_block) - { - modified_blocks.insert(p, block); - } - - /* - Set the modified blocks unsent for all the clients - */ - - for(core::map::Iterator - i = m_server->m_clients.getIterator(); - i.atEnd() == false; i++) - { - RemoteClient *client = i.getNode()->getValue(); - - if(modified_blocks.size() > 0) - { - // Remove block from sent history - client->SetBlocksNotSent(modified_blocks); - } - } - } - catch(VersionMismatchException &e) - { - std::ostringstream err; - err<<"World data version mismatch in MapBlock "<setAsyncFatalError(err.str()); - } - catch(SerializationError &e) - { - std::ostringstream err; - err<<"Invalid data in MapBlock "<setAsyncFatalError(err.str()); - } - - END_DEBUG_EXCEPTION_HANDLER(errorstream) - - log_deregister_thread(); - - return NULL; -} - v3f ServerSoundParams::getPos(ServerEnvironment *env, bool *pos_exists) const { if(pos_exists) *pos_exists = false; @@ -770,7 +458,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, */ if(block == NULL || surely_not_found_on_disk || block_is_invalid) { - //TODO: Get value from somewhere + /* //TODO: Get value from somewhere // Allow only one block in emerge queue //if(server->m_emerge_queue.peerItemCount(peer_id) < 1) // Allow two blocks in queue per client @@ -799,7 +487,17 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, nearest_emergefull_d = d; goto queue_full_break; } + */ + if (server->m_emerge->enqueueBlockEmerge(peer_id, p, generate)) { + if (nearest_emerged_d == -1) + nearest_emerged_d = d; + } else { + if (nearest_emergefull_d == -1) + nearest_emergefull_d = d; + goto queue_full_break; + } + // get next one. continue; } @@ -953,7 +651,7 @@ Server::Server( m_craftdef(createCraftDefManager()), m_event(new EventManager()), m_thread(this), - m_emergethread(this), + //m_emergethread(this), m_time_of_day_send_timer(0), m_uptime(0), m_shutdown_requested(false), @@ -1278,9 +976,9 @@ void Server::stop() // Stop threads (set run=false first so both start stopping) m_thread.setRun(false); - m_emergethread.setRun(false); + //m_emergethread.setRun(false); m_thread.stop(); - m_emergethread.stop(); + //m_emergethread.stop(); infostream<<"Server: Threads stopped"<emergethread->trigger(); // Update m_enable_rollback_recording here too m_enable_rollback_recording = @@ -3115,8 +2813,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) infostream<<"Server: Not punching: Node not found." <<" Adding block to emerge queue." <enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), false); } if(n.getContent() != CONTENT_IGNORE) scriptapi_node_on_punch(m_lua, p_under, n, playersao); @@ -3172,8 +2869,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) infostream<<"Server: Not finishing digging: Node not found." <<" Adding block to emerge queue." <enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), false); } /* Cheat prevention */ @@ -4728,10 +4424,7 @@ void Server::notifyPlayers(const std::wstring msg) void Server::queueBlockEmerge(v3s16 blockpos, bool allow_generate) { - u8 flags = 0; - if(!allow_generate) - flags |= BLOCK_EMERGE_FLAG_FROMDISK; - m_emerge_queue.addBlock(PEER_ID_INEXISTENT, blockpos, flags); + m_emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, blockpos, allow_generate); } Inventory* Server::createDetachedInventory(const std::string &name) diff --git a/src/server.h b/src/server.h index 26973643a..e92cbb564 100644 --- a/src/server.h +++ b/src/server.h @@ -39,6 +39,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "rollback_interface.h" // Needed for rollbackRevertActions() #include // Needed for rollbackRevertActions() +#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" + struct LuaState; typedef struct lua_State lua_State; class IWritableItemDefManager; @@ -47,6 +49,7 @@ class IWritableCraftDefManager; class EventManager; class PlayerSAO; class IRollbackManager; +class EmergeManager; class ServerError : public std::exception { @@ -120,11 +123,9 @@ public: If it is, update the peer to it and quit. */ core::list::Iterator i; - for(i=m_queue.begin(); i!=m_queue.end(); i++) - { + for(i=m_queue.begin(); i!=m_queue.end(); i++) { QueuedBlockEmerge *q = *i; - if(q->pos == pos) - { + if (q->pos == pos) { q->peer_ids[peer_id] = flags; return; } @@ -136,7 +137,7 @@ public: */ QueuedBlockEmerge *q = new QueuedBlockEmerge; q->pos = pos; - if(peer_id != 0) + if (peer_id != 0) q->peer_ids[peer_id] = flags; m_queue.push_back(q); } @@ -200,30 +201,6 @@ public: void * Thread(); }; -class EmergeThread : public SimpleThread -{ - Server *m_server; - -public: - - EmergeThread(Server *server): - SimpleThread(), - m_server(server) - { - } - - void * Thread(); - - void trigger() - { - setRun(true); - if(IsRunning() == false) - { - Start(); - } - } -}; - struct PlayerInfo { u16 id; @@ -785,9 +762,9 @@ private: // The server mainly operates in this thread ServerThread m_thread; // This thread fetches and generates map - EmergeThread m_emergethread; + //EmergeThread m_emergethread; // Queue of block coordinates to be processed by the emerge thread - BlockEmergeQueue m_emerge_queue; + //BlockEmergeQueue m_emerge_queue; /* Time related stuff From 3b7a445cd78c6b5ab301b5bf44e1ec54a00427f1 Mon Sep 17 00:00:00 2001 From: kwolekr Date: Thu, 14 Feb 2013 18:32:56 -0500 Subject: [PATCH 33/43] Add global and per-peer queue limits --- src/emerge.cpp | 43 ++++++++++++++++++++++++++++++++++--------- src/emerge.h | 30 ++++++++++++++---------------- src/mapgen.h | 1 - 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index 728ea7196..82867be50 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -88,7 +88,10 @@ Mapgen *EmergeManager::getMapgen() { } -bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate) { ///return false if adding failed, or queue full! +bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate) { + std::map::const_iterator iter; + BlockEmergeData *bedata; + u16 count; u8 flags = 0; if (allow_generate) @@ -103,14 +106,30 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate { JMutexAutoLock queuelock(queuemutex); - std::map::const_iterator iter = blocks_enqueued.find(p); + count = blocks_enqueued.size(); + u16 queuelimit_total = 256; + if (count >= queuelimit_total) + return false; + + count = peer_queue_count[peer_id]; + u16 queuelimit_peer = allow_generate ? 1 : 5; + if (count >= queuelimit_peer) + return false; + + iter = blocks_enqueued.find(p); if (iter != blocks_enqueued.end()) { - flags |= iter->second; - blocks_enqueued[p] = flags; + bedata = iter->second; + bedata->flags |= flags; return true; } + + bedata = new BlockEmergeData; + bedata->flags = flags; + bedata->peer_requested = peer_id; + blocks_enqueued.insert(std::make_pair(p, bedata)); + + peer_queue_count[peer_id] = count + 1; - blocks_enqueued.insert(std::make_pair(p, flags)); emergethread->blockqueue.push(p); } emergethread->qevent.signal(); @@ -120,6 +139,7 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate bool EmergeManager::popBlockEmerge(v3s16 *pos, u8 *flags) { + std::map::iterator iter; JMutexAutoLock queuelock(queuemutex); if (emergethread->blockqueue.empty()) @@ -128,12 +148,17 @@ bool EmergeManager::popBlockEmerge(v3s16 *pos, u8 *flags) { emergethread->blockqueue.pop(); *pos = p; + + iter = blocks_enqueued.find(p); + if (iter == blocks_enqueued.end()) + return false; //uh oh, queue and map out of sync!! - std::map::iterator iter = blocks_enqueued.find(p); - if (iter == blocks_enqueued.end()) //uh oh, this isn't right!!!!!!!!!!!!!!!!!! - return false; + BlockEmergeData *bedata = iter->second; + *flags = bedata->flags; + + peer_queue_count[bedata->peer_requested]--; - *flags = iter->second; + delete bedata; blocks_enqueued.erase(iter); return true; diff --git a/src/emerge.h b/src/emerge.h index 0acc89a6d..5f73dcd86 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -14,8 +14,6 @@ class Biome; class BiomeDefManager; class EmergeThread; class ManualMapVoxelManipulator; -//class ServerMap; -//class MapBlock; #include "server.h" @@ -29,20 +27,19 @@ struct BlockMakeData { UniqueQueue transforming_liquid; INodeDefManager *nodedef; -// BlockMakeData(); -// ~BlockMakeData(); - -BlockMakeData(): - no_op(false), - vmanip(NULL), - seed(0), - nodedef(NULL) -{} + BlockMakeData(): + no_op(false), + vmanip(NULL), + seed(0), + nodedef(NULL) + {} -~BlockMakeData() -{ - delete vmanip; -} + ~BlockMakeData() { delete vmanip; } +}; + +struct BlockEmergeData { + u16 peer_requested; + u8 flags; }; class EmergeManager { @@ -53,7 +50,8 @@ public: MapgenParams *params; JMutex queuemutex; - std::map blocks_enqueued; //change to a hashtable later + std::map blocks_enqueued; + std::map peer_queue_count; Mapgen *mapgen; EmergeThread *emergethread; diff --git a/src/mapgen.h b/src/mapgen.h index c3c209ad1..911e87537 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -26,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapnode.h" #include "noise.h" #include "settings.h" -//#include "emerge.h" #include /////////////////// Mapgen flags From 206ec36c8e50a017636a70345b95f79feffb2446 Mon Sep 17 00:00:00 2001 From: kwolekr Date: Thu, 14 Feb 2013 19:22:43 -0500 Subject: [PATCH 34/43] Fix EmergeThread hang on exit --- src/emerge.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index 82867be50..e2b9e5a6c 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -56,6 +56,7 @@ EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { EmergeManager::~EmergeManager() { emergethread->setRun(false); + emergethread->qevent.signal(); emergethread->stop(); delete emergethread; @@ -360,8 +361,11 @@ void *EmergeThread::Thread() { while (getRun()) try { - while (!emerge->popBlockEmerge(&p, &flags)) + while (!emerge->popBlockEmerge(&p, &flags)) { qevent.wait(); + if (!getRun()) + goto exit_emerge_loop; + } last_tried_pos = p; if (blockpos_over_limit(p)) @@ -393,7 +397,7 @@ void *EmergeThread::Thread() { //envlock: usually 0ms, but can take either 30 or 400ms to acquire JMutexAutoLock envlock(m_server->m_env_mutex); ScopeProfiler sp(g_profiler, "EmergeThread: after " - "mapgen::make_block (envlock)", SPT_AVG); + "Mapgen::makeChunk (envlock)", SPT_AVG); map->finishBlockMake(&data, modified_blocks); @@ -465,6 +469,7 @@ void *EmergeThread::Thread() { } END_DEBUG_EXCEPTION_HANDLER(errorstream) +exit_emerge_loop: log_deregister_thread(); return NULL; } From 76217939e05bdd8d06fa7113902a74b02deeb915 Mon Sep 17 00:00:00 2001 From: kwolekr Date: Thu, 14 Feb 2013 20:36:58 -0500 Subject: [PATCH 35/43] Add emerge queue limit configuration --- src/defaultsettings.cpp | 4 ++++ src/emerge.cpp | 17 +++++++---------- src/emerge.h | 13 +++++++++---- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 610dd65a4..f18e9b1e0 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -184,6 +184,10 @@ void set_default_settings(Settings *settings) settings->setDefault("congestion_control_min_rate", "10"); settings->setDefault("remote_media", ""); settings->setDefault("debug_log_level", "0"); + settings->setDefault("emergequeue_limit_total", "256"); + settings->setDefault("emergequeue_limit_diskonly", "5"); + settings->setDefault("emergequeue_limit_generate", "1"); + // physics stuff settings->setDefault("movement_acceleration_default", "3"); settings->setDefault("movement_acceleration_air", "2"); diff --git a/src/emerge.cpp b/src/emerge.cpp index e2b9e5a6c..b785c8688 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -49,6 +49,10 @@ EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { this->params = NULL; this->mapgen = NULL; + qlimit_total = g_settings->getU16("emergequeue_limit_total"); + qlimit_diskonly = g_settings->getU16("emergequeue_limit_diskonly"); + qlimit_generate = g_settings->getU16("emergequeue_limit_generate"); + queuemutex.Init(); emergethread = new EmergeThread((Server *)gamedef); } @@ -98,23 +102,16 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate if (allow_generate) flags |= BLOCK_EMERGE_ALLOWGEN; - //TODO: - // add logic to select which emergethread to add it to - // - one with the least queue contents? - // - if a queue is too full, move onto another one - // - use the peer id sometime - { JMutexAutoLock queuelock(queuemutex); count = blocks_enqueued.size(); - u16 queuelimit_total = 256; - if (count >= queuelimit_total) + if (count >= qlimit_total) return false; count = peer_queue_count[peer_id]; - u16 queuelimit_peer = allow_generate ? 1 : 5; - if (count >= queuelimit_peer) + u16 qlimit_peer = allow_generate ? qlimit_generate : qlimit_diskonly; + if (count >= qlimit_peer) return false; iter = blocks_enqueued.find(p); diff --git a/src/emerge.h b/src/emerge.h index 5f73dcd86..b4461ae61 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -45,15 +45,20 @@ struct BlockEmergeData { class EmergeManager { public: std::map mglist; - + + Mapgen *mapgen; + EmergeThread *emergethread; + //settings MapgenParams *params; - + u16 qlimit_total; + u16 qlimit_diskonly; + u16 qlimit_generate; + + //block emerge queue data structures JMutex queuemutex; std::map blocks_enqueued; std::map peer_queue_count; - Mapgen *mapgen; - EmergeThread *emergethread; //biome manager BiomeDefManager *biomedef; From 5ec5b1cbd64a22e628be2cf03391883c44074811 Mon Sep 17 00:00:00 2001 From: kwolekr Date: Sun, 17 Feb 2013 01:47:49 -0500 Subject: [PATCH 36/43] Add multi-Emerge thread support --- src/defaultsettings.cpp | 1 + src/emerge.cpp | 99 +++++++++++++++++++++++++---------------- src/emerge.h | 15 ++++--- src/map.cpp | 65 +++++++++++++++++++++------ src/map.h | 13 +++--- src/porting.cpp | 23 ++++++++++ src/porting.h | 5 +++ src/server.cpp | 3 +- 8 files changed, 160 insertions(+), 64 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index f18e9b1e0..219cda9e7 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -187,6 +187,7 @@ void set_default_settings(Settings *settings) settings->setDefault("emergequeue_limit_total", "256"); settings->setDefault("emergequeue_limit_diskonly", "5"); settings->setDefault("emergequeue_limit_generate", "1"); + settings->setDefault("num_emerge_threads", ""); // physics stuff settings->setDefault("movement_acceleration_default", "3"); diff --git a/src/emerge.cpp b/src/emerge.cpp index b785c8688..ee6650f9c 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -47,49 +47,56 @@ EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { this->biomedef = bdef ? bdef : new BiomeDefManager(gamedef); this->params = NULL; - this->mapgen = NULL; qlimit_total = g_settings->getU16("emergequeue_limit_total"); qlimit_diskonly = g_settings->getU16("emergequeue_limit_diskonly"); qlimit_generate = g_settings->getU16("emergequeue_limit_generate"); queuemutex.Init(); - emergethread = new EmergeThread((Server *)gamedef); + int nthreads = g_settings->get("num_emerge_threads").empty() ? + porting::getNumberOfProcessors() : + g_settings->getU16("num_emerge_threads"); + if (nthreads < 1) + nthreads = 1; + + for (int i = 0; i != nthreads; i++) + emergethread.push_back(new EmergeThread((Server *)gamedef, i)); + + infostream << "EmergeManager: using " << nthreads << " threads" << std::endl; } EmergeManager::~EmergeManager() { - emergethread->setRun(false); - emergethread->qevent.signal(); - emergethread->stop(); + for (int i = 0; i != emergethread.size(); i++) { + emergethread[i]->setRun(false); + emergethread[i]->qevent.signal(); + emergethread[i]->stop(); + delete emergethread[i]; + delete mapgen[i]; + } - delete emergethread; delete biomedef; - delete mapgen; delete params; } void EmergeManager::initMapgens(MapgenParams *mgparams) { - if (mapgen) + Mapgen *mg; + + if (mapgen.size()) return; this->params = mgparams; - this->mapgen = getMapgen(); //only one mapgen for now! -} - - -Mapgen *EmergeManager::getMapgen() { - if (!mapgen) { - mapgen = createMapgen(params->mg_name, 0, params, this); - if (!mapgen) { + for (int i = 0; i != emergethread.size(); i++) { + mg = createMapgen(params->mg_name, 0, params); + if (!mg) { infostream << "EmergeManager: falling back to mapgen v6" << std::endl; delete params; params = createMapgenParams("v6"); - mapgen = createMapgen("v6", 0, params, this); + mg = createMapgen("v6", 0, params); } + mapgen.push_back(mg); } - return mapgen; } @@ -98,6 +105,7 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate BlockEmergeData *bedata; u16 count; u8 flags = 0; + int idx = 0; if (allow_generate) flags |= BLOCK_EMERGE_ALLOWGEN; @@ -128,45 +136,58 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate peer_queue_count[peer_id] = count + 1; - emergethread->blockqueue.push(p); + int lowestitems = emergethread[0]->blockqueue.size(); + for (int i = 1; i != emergethread.size(); i++) { + int nitems = emergethread[i]->blockqueue.size(); + if (nitems < lowestitems) { + idx = i; + lowestitems = nitems; + } + } + + emergethread[idx]->blockqueue.push(p); } - emergethread->qevent.signal(); + emergethread[idx]->qevent.signal(); return true; } -bool EmergeManager::popBlockEmerge(v3s16 *pos, u8 *flags) { +bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) { std::map::iterator iter; - JMutexAutoLock queuelock(queuemutex); + JMutexAutoLock queuelock(emerge->queuemutex); - if (emergethread->blockqueue.empty()) + if (blockqueue.empty()) return false; - v3s16 p = emergethread->blockqueue.front(); - emergethread->blockqueue.pop(); + v3s16 p = blockqueue.front(); + blockqueue.pop(); *pos = p; - iter = blocks_enqueued.find(p); - if (iter == blocks_enqueued.end()) + iter = emerge->blocks_enqueued.find(p); + if (iter == emerge->blocks_enqueued.end()) return false; //uh oh, queue and map out of sync!! BlockEmergeData *bedata = iter->second; *flags = bedata->flags; - peer_queue_count[bedata->peer_requested]--; + emerge->peer_queue_count[bedata->peer_requested]--; delete bedata; - blocks_enqueued.erase(iter); + emerge->blocks_enqueued.erase(iter); return true; } int EmergeManager::getGroundLevelAtPoint(v2s16 p) { - if (!mapgen) + if (!mapgen[0]) { + errorstream << "EmergeManager: getGroundLevelAtPoint() called" + " before mapgen initialized" << std::endl; return 0; - return mapgen->getGroundLevelAtPoint(p); + } + + return mapgen[0]->getGroundLevelAtPoint(p); } @@ -193,8 +214,9 @@ u32 EmergeManager::getBlockSeed(v3s16 p) { Mapgen *EmergeManager::createMapgen(std::string mgname, int mgid, - MapgenParams *mgparams, EmergeManager *emerge) { - std::map::const_iterator iter = mglist.find(mgname); + MapgenParams *mgparams) { + std::map::const_iterator iter; + iter = mglist.find(mgname); if (iter == mglist.end()) { errorstream << "EmergeManager; mapgen " << mgname << " not registered" << std::endl; @@ -202,12 +224,13 @@ Mapgen *EmergeManager::createMapgen(std::string mgname, int mgid, } MapgenFactory *mgfactory = iter->second; - return mgfactory->createMapgen(mgid, mgparams, emerge); + return mgfactory->createMapgen(mgid, mgparams, this); } MapgenParams *EmergeManager::createMapgenParams(std::string mgname) { - std::map::const_iterator iter = mglist.find(mgname); + std::map::const_iterator iter; + iter = mglist.find(mgname); if (iter == mglist.end()) { errorstream << "EmergeManager: mapgen " << mgname << " not registered" << std::endl; @@ -227,7 +250,7 @@ MapgenParams *EmergeManager::getParamsFromSettings(Settings *settings) { mgparams->seed = settings->getU64(settings == g_settings ? "fixed_map_seed" : "seed"); mgparams->water_level = settings->getS16("water_level"); mgparams->chunksize = settings->getS16("chunksize"); - mgparams->flags = settings->getS32("mg_flags"); + mgparams->flags = settings->getFlagStr("mg_flags", flagdesc_mapgen); if (!mgparams->readParams(settings)) { delete mgparams; @@ -354,11 +377,11 @@ void *EmergeThread::Thread() { map = (ServerMap *)&(m_server->m_env->getMap()); emerge = m_server->m_emerge; - mapgen = emerge->getMapgen(); + mapgen = emerge->mapgen[id]; //emerge->getMapgen(); while (getRun()) try { - while (!emerge->popBlockEmerge(&p, &flags)) { + while (!popBlockEmerge(&p, &flags)) { qevent.wait(); if (!getRun()) goto exit_emerge_loop; diff --git a/src/emerge.h b/src/emerge.h index b4461ae61..7e0cc4850 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -46,8 +46,8 @@ class EmergeManager { public: std::map mglist; - Mapgen *mapgen; - EmergeThread *emergethread; + std::vector mapgen; + std::vector emergethread; //settings MapgenParams *params; @@ -68,11 +68,9 @@ public: void initMapgens(MapgenParams *mgparams); Mapgen *createMapgen(std::string mgname, int mgid, - MapgenParams *mgparams, EmergeManager *emerge); + MapgenParams *mgparams); MapgenParams *createMapgenParams(std::string mgname); - Mapgen *getMapgen(); bool enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate); - bool popBlockEmerge(v3s16 *pos, u8 *flags); bool registerMapgen(std::string name, MapgenFactory *mgfactory); MapgenParams *getParamsFromSettings(Settings *settings); @@ -92,17 +90,19 @@ class EmergeThread : public SimpleThread EmergeManager *emerge; Mapgen *mapgen; bool enable_mapgen_debug_info; + int id; public: Event qevent; std::queue blockqueue; - EmergeThread(Server *server): + EmergeThread(Server *server, int ethreadid): SimpleThread(), m_server(server), map(NULL), emerge(NULL), - mapgen(NULL) + mapgen(NULL), + id(ethreadid) { enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); } @@ -118,6 +118,7 @@ public: } } + bool popBlockEmerge(v3s16 *pos, u8 *flags); bool getBlockOrStartGen(v3s16 p, MapBlock **b, BlockMakeData *data, bool allow_generate); }; diff --git a/src/map.cpp b/src/map.cpp index 7eb45463f..a8928d864 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2009,7 +2009,7 @@ ServerMap::ServerMap(std::string savedir, IGameDef *gamedef, EmergeManager *emer m_mgparams = m_emerge->getParamsFromSettings(g_settings); if (!m_mgparams) m_mgparams = new MapgenV6Params(); - + m_seed = m_mgparams->seed; if (g_settings->get("fixed_map_seed").empty()) @@ -2246,6 +2246,21 @@ void ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos) //TimeTaker timer("initBlockMake() initialEmerge"); data->vmanip->initialEmerge(bigarea_blocks_min, bigarea_blocks_max); } + + // Ensure none of the blocks to be generated were marked as containing CONTENT_IGNORE + for (s16 z = blockpos_min.Z; z <= blockpos_max.Z; z++) { + for (s16 y = blockpos_min.Y; y <= blockpos_max.Y; y++) { + for (s16 x = blockpos_min.X; x <= blockpos_max.X; x++) { + core::map::Node *n; + n = data->vmanip->m_loaded_blocks.find(v3s16(x, y, z)); + if (n == NULL) + continue; + u8 flags = n->getValue(); + flags &= ~VMANIP_BLOCK_CONTAINS_CIGNORE; + n->setValue(flags); + } + } + } // Data is ready now. } @@ -3672,8 +3687,10 @@ void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id) for(s32 y=p_min.Y; y<=p_max.Y; y++) for(s32 x=p_min.X; x<=p_max.X; x++) { + u8 flags = 0; + MapBlock *block; v3s16 p(x,y,z); - core::map::Node *n; + core::map::Node *n; n = m_loaded_blocks.find(p); if(n != NULL) continue; @@ -3689,7 +3706,7 @@ void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id) a.print(infostream); infostream<getBlockNoCreate(p); + block = m_map->getBlockNoCreate(p); if(block->isDummy()) block_data_inexistent = true; else @@ -3702,6 +3719,8 @@ void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id) if(block_data_inexistent) { + flags |= VMANIP_BLOCK_DATA_INEXIST; + VoxelArea a(p*MAP_BLOCKSIZE, (p+1)*MAP_BLOCKSIZE-v3s16(1,1,1)); // Fill with VOXELFLAG_INEXISTENT for(s32 z=a.MinEdge.Z; z<=a.MaxEdge.Z; z++) @@ -3711,8 +3730,13 @@ void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id) memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE); } } + else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE) + { + // Mark that block was loaded as blank + flags |= VMANIP_BLOCK_CONTAINS_CIGNORE; + } - m_loaded_blocks.insert(p, !block_data_inexistent); + m_loaded_blocks.insert(p, flags); } //infostream<<"emerge done"<::Node *n; + core::map::Node *n; n = m_loaded_blocks.find(p); if(n != NULL) continue; @@ -3843,7 +3869,7 @@ void ManualMapVoxelManipulator::initialEmerge( { TimeTaker timer1("emerge load", &emerge_load_time); - MapBlock *block = m_map->getBlockNoCreate(p); + block = m_map->getBlockNoCreate(p); if(block->isDummy()) block_data_inexistent = true; else @@ -3856,6 +3882,8 @@ void ManualMapVoxelManipulator::initialEmerge( if(block_data_inexistent) { + flags |= VMANIP_BLOCK_DATA_INEXIST; + /* Mark area inexistent */ @@ -3868,8 +3896,13 @@ void ManualMapVoxelManipulator::initialEmerge( memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE); } } + else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE) + { + // Mark that block was loaded as blank + flags |= VMANIP_BLOCK_CONTAINS_CIGNORE; + } - m_loaded_blocks.insert(p, !block_data_inexistent); + m_loaded_blocks.insert(p, flags); } } @@ -3882,12 +3915,14 @@ void ManualMapVoxelManipulator::blitBackAll( /* Copy data of all blocks */ - for(core::map::Iterator + for(core::map::Iterator i = m_loaded_blocks.getIterator(); i.atEnd() == false; i++) { v3s16 p = i.getNode()->getKey(); - bool existed = i.getNode()->getValue(); + u8 flags = i.getNode()->getValue(); + + bool existed = !(flags & VMANIP_BLOCK_DATA_INEXIST); if(existed == false) { // The Great Bug was found using this @@ -3896,6 +3931,7 @@ void ManualMapVoxelManipulator::blitBackAll( <getBlockNoCreateNoEx(p); if(block == NULL) { @@ -3906,10 +3942,13 @@ void ManualMapVoxelManipulator::blitBackAll( continue; } - block->copyFrom(*this); - - if(modified_blocks) - modified_blocks->insert(p, block); + bool no_content_ignore = !(flags & VMANIP_BLOCK_CONTAINS_CIGNORE); + if (no_content_ignore) + { + block->copyFrom(*this); + if(modified_blocks) + modified_blocks->insert(p, block); + } } } diff --git a/src/map.h b/src/map.h index 0b2311f39..1062f8301 100644 --- a/src/map.h +++ b/src/map.h @@ -517,6 +517,9 @@ private: sqlite3_stmt *m_database_list; }; +#define VMANIP_BLOCK_DATA_INEXIST 1 +#define VMANIP_BLOCK_CONTAINS_CIGNORE 2 + class MapVoxelManipulator : public VoxelManipulator { public: @@ -532,14 +535,14 @@ public: virtual void emerge(VoxelArea a, s32 caller_id=-1); void blitBack(core::map & modified_blocks); - -protected: - Map *m_map; + /* key = blockpos - value = block existed when loaded + value = flags describing the block */ - core::map m_loaded_blocks; + core::map m_loaded_blocks; +protected: + Map *m_map; }; class ManualMapVoxelManipulator : public MapVoxelManipulator diff --git a/src/porting.cpp b/src/porting.cpp index 7ad557833..58d71e4aa 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -131,6 +131,29 @@ void signal_handler_init(void) #endif +/* + Multithreading support +*/ +int getNumberOfProcessors() { + #if defined(_SC_NPROCESSORS_ONLN) + return sysconf(_SC_NPROCESSORS_ONLN); + #elif defined(__FreeBSD__) || defined(__APPLE__) + unsigned int len, count; + len = sizeof(count); + return sysctlbyname("hw.ncpu", &count, &len, NULL, 0); + #elif defined(_GNU_SOURCE) + return get_nprocs(); + #elif defined(_WIN32) + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; + #elif defined(PTW32_VERSION) || defined(__hpux) + return pthread_num_processors_np(); + #else + return 1; + #endif +} + /* Path mangler */ diff --git a/src/porting.h b/src/porting.h index 13b715557..53aad6171 100644 --- a/src/porting.h +++ b/src/porting.h @@ -103,6 +103,11 @@ std::string getDataPath(const char *subpath); */ void initializePaths(); +/* + Get number of online processors in the system. +*/ +int getNumberOfProcessors(); + /* Resolution is 10-20ms. Remember to check for overflows. diff --git a/src/server.cpp b/src/server.cpp index f2897d46d..5021718a3 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1649,7 +1649,8 @@ void Server::AsyncRunStep() { counter = 0.0; - m_emerge->emergethread->trigger(); + for (int i = 0; i != m_emerge->emergethread.size(); i++) + m_emerge->emergethread[i]->trigger(); // Update m_enable_rollback_recording here too m_enable_rollback_recording = From 67c59645ecd76cbf7a8186d0850ab5950e9ea22a Mon Sep 17 00:00:00 2001 From: kwolekr Date: Sun, 17 Feb 2013 02:46:08 -0500 Subject: [PATCH 37/43] Misc. cleanup and minor fixes --- src/emerge.cpp | 21 +++++++++------------ src/emerge.h | 8 +++++--- src/map.cpp | 29 +++++++---------------------- src/map.h | 2 +- src/mapgen_v6.cpp | 7 +------ 5 files changed, 23 insertions(+), 44 deletions(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index ee6650f9c..ac654f368 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -48,6 +48,8 @@ EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { this->biomedef = bdef ? bdef : new BiomeDefManager(gamedef); this->params = NULL; + mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); + qlimit_total = g_settings->getU16("emergequeue_limit_total"); qlimit_diskonly = g_settings->getU16("emergequeue_limit_diskonly"); qlimit_generate = g_settings->getU16("emergequeue_limit_generate"); @@ -209,7 +211,7 @@ u32 EmergeManager::getBlockSeed(v3s16 p) { return (u32)(params->seed & 0xFFFFFFFF) + p.Z * 38134234 + p.Y * 42123 + - p.Y * 23; + p.X * 23; } @@ -331,10 +333,6 @@ private: #if 1 -#define EMERGE_DBG_OUT(x) \ - { if (enable_mapgen_debug_info) \ - infostream << "EmergeThread: " x << std::endl; } - bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b, BlockMakeData *data, bool allow_gen) { v2s16 p2d(p.X, p.Z); @@ -356,8 +354,8 @@ bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b, // start generation inside this same envlock if (allow_gen && (block == NULL || !block->isGenerated())) { EMERGE_DBG_OUT("generating"); - map->initBlockMake(data, p); - return true; + *b = block; + return map->initBlockMake(data, p); } *b = block; @@ -377,14 +375,14 @@ void *EmergeThread::Thread() { map = (ServerMap *)&(m_server->m_env->getMap()); emerge = m_server->m_emerge; - mapgen = emerge->mapgen[id]; //emerge->getMapgen(); + mapgen = emerge->mapgen[id]; + enable_mapgen_debug_info = emerge->mapgen_debug_info; while (getRun()) try { - while (!popBlockEmerge(&p, &flags)) { + if (!popBlockEmerge(&p, &flags)) { qevent.wait(); - if (!getRun()) - goto exit_emerge_loop; + continue; } last_tried_pos = p; @@ -489,7 +487,6 @@ void *EmergeThread::Thread() { } END_DEBUG_EXCEPTION_HANDLER(errorstream) -exit_emerge_loop: log_deregister_thread(); return NULL; } diff --git a/src/emerge.h b/src/emerge.h index 7e0cc4850..e5014df58 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -7,6 +7,10 @@ #define BLOCK_EMERGE_ALLOWGEN (1<<0) +#define EMERGE_DBG_OUT(x) \ + { if (enable_mapgen_debug_info) \ + infostream << "EmergeThread: " x << std::endl; } + class Mapgen; class MapgenParams; class MapgenFactory; @@ -18,7 +22,6 @@ class ManualMapVoxelManipulator; #include "server.h" struct BlockMakeData { - bool no_op; ManualMapVoxelManipulator *vmanip; u64 seed; v3s16 blockpos_min; @@ -28,7 +31,6 @@ struct BlockMakeData { INodeDefManager *nodedef; BlockMakeData(): - no_op(false), vmanip(NULL), seed(0), nodedef(NULL) @@ -51,6 +53,7 @@ public: //settings MapgenParams *params; + bool mapgen_debug_info; u16 qlimit_total; u16 qlimit_diskonly; u16 qlimit_generate; @@ -104,7 +107,6 @@ public: mapgen(NULL), id(ethreadid) { - enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); } void *Thread(); diff --git a/src/map.cpp b/src/map.cpp index a8928d864..3f2f8d1d3 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2143,14 +2143,10 @@ ServerMap::~ServerMap() #endif } -void ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos) +bool ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos) { - bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); - if(enable_mapgen_debug_info) - infostream<<"initBlockMake(): " - <<"("<mapgen_debug_info; + EMERGE_DBG_OUT("initBlockMake(): " PP(blockpos) " - " PP(blockpos)); //s16 chunksize = 3; //v3s16 chunk_offset(-1,-1,-1); @@ -2170,12 +2166,8 @@ void ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos) // Do nothing if not inside limits (+-1 because of neighbors) if(blockpos_over_limit(blockpos_min - extra_borders) || blockpos_over_limit(blockpos_max + extra_borders)) - { - data->no_op = true; - return; - } + return false; - data->no_op = false; data->seed = m_seed; data->blockpos_min = blockpos_min; data->blockpos_max = blockpos_max; @@ -2263,6 +2255,7 @@ void ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos) } // Data is ready now. + return true; } MapBlock* ServerMap::finishBlockMake(BlockMakeData *data, @@ -2277,13 +2270,7 @@ MapBlock* ServerMap::finishBlockMake(BlockMakeData *data, v3s16 extra_borders(1,1,1); - if(data->no_op) - { - //infostream<<"finishBlockMake(): no-op"<getBool("enable_mapgen_debug_info"); + bool enable_mapgen_debug_info = m_emerge->mapgen_debug_info; /*infostream<<"Resulting vmanip:"<vmanip.print(infostream);*/ @@ -2311,9 +2298,7 @@ MapBlock* ServerMap::finishBlockMake(BlockMakeData *data, data->vmanip->blitBackAll(&changed_blocks); } - if(enable_mapgen_debug_info) - infostream<<"finishBlockMake: changed_blocks.size()=" - <generating = true; assert(data->vmanip); @@ -1436,4 +1430,5 @@ void MapgenV6::makeChunk(BlockMakeData *data) vmanip.spreadLight(bank, light_sources, ndef); } } + this->generating = false; } From 60e6284f30d31e11c1a464d9a8b3c5c392ceb3f0 Mon Sep 17 00:00:00 2001 From: kwolekr Date: Thu, 21 Feb 2013 17:44:14 -0500 Subject: [PATCH 38/43] Tune queue limits, some other adjustments --- src/defaultsettings.cpp | 4 +- src/emerge.cpp | 30 ++++++--- src/porting.cpp | 132 ++++++++++++++++++++++++++++++++++------ src/porting.h | 37 ++++++++++- 4 files changed, 174 insertions(+), 29 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 219cda9e7..0e82c4e2f 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -185,8 +185,8 @@ void set_default_settings(Settings *settings) settings->setDefault("remote_media", ""); settings->setDefault("debug_log_level", "0"); settings->setDefault("emergequeue_limit_total", "256"); - settings->setDefault("emergequeue_limit_diskonly", "5"); - settings->setDefault("emergequeue_limit_generate", "1"); + settings->setDefault("emergequeue_limit_diskonly", ""); + settings->setDefault("emergequeue_limit_generate", ""); settings->setDefault("num_emerge_threads", ""); // physics stuff diff --git a/src/emerge.cpp b/src/emerge.cpp index ac654f368..dd97734c5 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -49,18 +49,29 @@ EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { this->params = NULL; mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); - - qlimit_total = g_settings->getU16("emergequeue_limit_total"); - qlimit_diskonly = g_settings->getU16("emergequeue_limit_diskonly"); - qlimit_generate = g_settings->getU16("emergequeue_limit_generate"); queuemutex.Init(); - int nthreads = g_settings->get("num_emerge_threads").empty() ? - porting::getNumberOfProcessors() : - g_settings->getU16("num_emerge_threads"); + + int nthreads; + if (g_settings->get("num_emerge_threads").empty()) { + int nprocs = porting::getNumberOfProcessors(); + // leave a proc for the main thread and one for some other misc threads + if (nprocs > 2) + nthreads = nprocs - 2; + } else { + nthreads = g_settings->getU16("num_emerge_threads"); + } if (nthreads < 1) nthreads = 1; + qlimit_total = g_settings->getU16("emergequeue_limit_total"); + qlimit_diskonly = g_settings->get("emergequeue_limit_diskonly").empty() ? + nthreads * 5 + 1 : + g_settings->getU16("emergequeue_limit_diskonly"); + qlimit_generate = g_settings->get("emergequeue_limit_generate").empty() ? + nthreads + 1 : + g_settings->getU16("emergequeue_limit_generate"); + for (int i = 0; i != nthreads; i++) emergethread.push_back(new EmergeThread((Server *)gamedef, i)); @@ -138,6 +149,7 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate peer_queue_count[peer_id] = count + 1; + // insert into the EmergeThread queue with the least items int lowestitems = emergethread[0]->blockqueue.size(); for (int i = 1; i != emergethread.size(); i++) { int nitems = emergethread[i]->blockqueue.size(); @@ -183,7 +195,7 @@ bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) { int EmergeManager::getGroundLevelAtPoint(v2s16 p) { - if (!mapgen[0]) { + if (mapgen.size() == 0 || !mapgen[0]) { errorstream << "EmergeManager: getGroundLevelAtPoint() called" " before mapgen initialized" << std::endl; return 0; @@ -365,7 +377,7 @@ bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b, void *EmergeThread::Thread() { ThreadStarted(); - log_register_thread("EmergeThread"); + log_register_thread("EmergeThread" + id); DSTACK(__FUNCTION_NAME); BEGIN_DEBUG_EXCEPTION_HANDLER diff --git a/src/porting.cpp b/src/porting.cpp index 58d71e4aa..84df15b30 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -131,29 +131,127 @@ void signal_handler_init(void) #endif + /* Multithreading support */ int getNumberOfProcessors() { - #if defined(_SC_NPROCESSORS_ONLN) - return sysconf(_SC_NPROCESSORS_ONLN); - #elif defined(__FreeBSD__) || defined(__APPLE__) - unsigned int len, count; - len = sizeof(count); - return sysctlbyname("hw.ncpu", &count, &len, NULL, 0); - #elif defined(_GNU_SOURCE) - return get_nprocs(); - #elif defined(_WIN32) - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - return sysinfo.dwNumberOfProcessors; - #elif defined(PTW32_VERSION) || defined(__hpux) - return pthread_num_processors_np(); - #else - return 1; - #endif +#if defined(_SC_NPROCESSORS_ONLN) + + return sysconf(_SC_NPROCESSORS_ONLN); + +#elif defined(__FreeBSD__) || defined(__APPLE__) + + unsigned int len, count; + len = sizeof(count); + return sysctlbyname("hw.ncpu", &count, &len, NULL, 0); + +#elif defined(_GNU_SOURCE) + + return get_nprocs(); + +#elif defined(_WIN32) + + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; + +#elif defined(PTW32_VERSION) || defined(__hpux) + + return pthread_num_processors_np(); + +#else + + return 1; + +#endif } + +bool threadBindToProcessor(threadid_t tid, int pnumber) { +#if defined(_WIN32) + + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, tid); + if (!hThread) + return false; + + bool success = SetThreadAffinityMask(hThread, 1 << pnumber) != 0; + + CloseHandle(hThread); + return success; + +#elif (defined(__FreeBSD__) && (__FreeBSD_version >= 702106)) \ + || defined(__linux) || defined(linux) + + cpu_set_t cpuset; + + CPU_ZERO(&cpuset); + CPU_SET(pnumber, &cpuset); + return pthread_setaffinity_np(tid, sizeof(cpuset), &cpuset) == 0; + +#elif defined(__sun) || defined(sun) + + return processor_bind(P_LWPID, MAKE_LWPID_PTHREAD(tid), + pnumber, NULL) == 0; + +#elif defined(_AIX) + + return bindprocessor(BINDTHREAD, (tid_t)tid, pnumber) == 0; + +#elif defined(__hpux) || defined(hpux) + + pthread_spu_t answer; + + return pthread_processor_bind_np(PTHREAD_BIND_ADVISORY_NP, + &answer, pnumber, tid) == 0; + +#elif defined(__APPLE__) + + struct thread_affinity_policy tapol; + + thread_port_t threadport = pthread_mach_thread_np(tid); + tapol.affinity_tag = pnumber + 1; + return thread_policy_set(threadport, THREAD_AFFINITY_POLICY, + (thread_policy_t)&tapol, THREAD_AFFINITY_POLICY_COUNT) == KERN_SUCCESS; + +#else + + return false; + +#endif +} + + +bool threadSetPriority(threadid_t tid, int prio) { +#if defined(_WIN32) + + HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, tid); + if (!hThread) + return false; + + bool success = SetThreadPriority(hThread, prio) != 0; + + CloseHandle(hThread); + return success; + +#else + + struct sched_param sparam; + int policy; + + if (pthread_getschedparam(tid, &policy, &sparam) != 0) + return false; + + int min = sched_get_priority_min(policy); + int max = sched_get_priority_max(policy); + + sparam.sched_priority = min + prio * (max - min) / THREAD_PRIORITY_HIGHEST; + return pthread_setschedparam(tid, policy, &sparam) == 0; + +#endif +} + + /* Path mangler */ diff --git a/src/porting.h b/src/porting.h index 53aad6171..74ee97f88 100644 --- a/src/porting.h +++ b/src/porting.h @@ -46,8 +46,33 @@ with this program; if not, write to the Free Software Foundation, Inc., #else #include #include //for uintptr_t - + + #if defined(linux) || defined(__linux) + #define _GNU_SOURCE + #endif + + #include + + #ifdef __FreeBSD__ + #include + typedef cpuset_t cpu_set_t; + #elif defined(__sun) || defined(sun) + #include + #include + #elif defined(_AIX) + #include + #elif __APPLE__ + #include + #include + #endif + #define sleep_ms(x) usleep(x*1000) + + #define THREAD_PRIORITY_LOWEST 0 + #define THREAD_PRIORITY_BELOW_NORMAL 1 + #define THREAD_PRIORITY_NORMAL 2 + #define THREAD_PRIORITY_ABOVE_NORMAL 3 + #define THREAD_PRIORITY_HIGHEST 4 #endif #ifdef _MSC_VER @@ -108,6 +133,16 @@ void initializePaths(); */ int getNumberOfProcessors(); +/* + Set a thread's affinity to a particular processor. +*/ +bool threadBindToProcessor(threadid_t tid, int pnumber); + +/* + Set a thread's priority. +*/ +bool threadSetPriority(threadid_t tid, int prio); + /* Resolution is 10-20ms. Remember to check for overflows. From 5bd50a2d9060f265d4c8d2fff062cac106ccab7c Mon Sep 17 00:00:00 2001 From: kwolekr Date: Thu, 21 Feb 2013 22:26:21 -0500 Subject: [PATCH 39/43] Fix most walled-off caves --- src/map.cpp | 23 ++++++++++------------- src/porting.h | 2 +- src/voxel.cpp | 8 +++++++- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index 3f2f8d1d3..74bea3dac 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -2240,7 +2240,7 @@ bool ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos) } // Ensure none of the blocks to be generated were marked as containing CONTENT_IGNORE - for (s16 z = blockpos_min.Z; z <= blockpos_max.Z; z++) { +/* for (s16 z = blockpos_min.Z; z <= blockpos_max.Z; z++) { for (s16 y = blockpos_min.Y; y <= blockpos_max.Y; y++) { for (s16 x = blockpos_min.X; x <= blockpos_max.X; x++) { core::map::Node *n; @@ -2252,7 +2252,7 @@ bool ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos) n->setValue(flags); } } - } + }*/ // Data is ready now. return true; @@ -3715,11 +3715,11 @@ void MapVoxelManipulator::emerge(VoxelArea a, s32 caller_id) memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE); } } - else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE) + /*else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE) { // Mark that block was loaded as blank flags |= VMANIP_BLOCK_CONTAINS_CIGNORE; - } + }*/ m_loaded_blocks.insert(p, flags); } @@ -3881,11 +3881,11 @@ void ManualMapVoxelManipulator::initialEmerge( memset(&m_flags[i], VOXELFLAG_INEXISTENT, MAP_BLOCKSIZE); } } - else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE) + /*else if (block->getNode(0, 0, 0).getContent() == CONTENT_IGNORE) { // Mark that block was loaded as blank flags |= VMANIP_BLOCK_CONTAINS_CIGNORE; - } + }*/ m_loaded_blocks.insert(p, flags); } @@ -3927,13 +3927,10 @@ void ManualMapVoxelManipulator::blitBackAll( continue; } - bool no_content_ignore = !(flags & VMANIP_BLOCK_CONTAINS_CIGNORE); - if (no_content_ignore) - { - block->copyFrom(*this); - if(modified_blocks) - modified_blocks->insert(p, block); - } + block->copyFrom(*this); + + if(modified_blocks) + modified_blocks->insert(p, block); } } diff --git a/src/porting.h b/src/porting.h index 74ee97f88..03d2fcbd3 100644 --- a/src/porting.h +++ b/src/porting.h @@ -72,7 +72,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define THREAD_PRIORITY_BELOW_NORMAL 1 #define THREAD_PRIORITY_NORMAL 2 #define THREAD_PRIORITY_ABOVE_NORMAL 3 - #define THREAD_PRIORITY_HIGHEST 4 + #define THREAD_PRIORITY_HIGHEST 4 #endif #ifdef _MSC_VER diff --git a/src/voxel.cpp b/src/voxel.cpp index 0ff5305fa..c55f3f539 100644 --- a/src/voxel.cpp +++ b/src/voxel.cpp @@ -244,7 +244,13 @@ void VoxelManipulator::copyTo(MapNode *dst, VoxelArea dst_area, { s32 i_dst = dst_area.index(dst_pos.X, dst_pos.Y+y, dst_pos.Z+z); s32 i_local = m_area.index(from_pos.X, from_pos.Y+y, from_pos.Z+z); - memcpy(&dst[i_dst], &m_data[i_local], size.X*sizeof(MapNode)); + for (s16 x = 0; x < size.X; x++) { + if (m_data[i_local].getContent() != CONTENT_IGNORE) + dst[i_dst] = m_data[i_local]; + i_dst++; + i_local++; + } + //memcpy(&dst[i_dst], &m_data[i_local], size.X*sizeof(MapNode)); } } From 28c50c16cdeda7de39ed15c17094f3de01287d4c Mon Sep 17 00:00:00 2001 From: kwolekr Date: Sat, 23 Feb 2013 12:30:13 -0500 Subject: [PATCH 40/43] Fix build on Windows --- src/debug.h | 3 +++ src/jthread/jmutex.h | 5 ++++- src/porting.h | 7 +++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/debug.h b/src/debug.h index 69a215a42..56952427c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -32,6 +32,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0500 + #endif #include #ifdef _MSC_VER #include diff --git a/src/jthread/jmutex.h b/src/jthread/jmutex.h index 6675162a5..e528aeb4a 100644 --- a/src/jthread/jmutex.h +++ b/src/jthread/jmutex.h @@ -30,6 +30,9 @@ #define JMUTEX_H #if (defined(WIN32) || defined(_WIN32_WCE)) + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0500 + #endif #ifndef _WIN32_WCE #include #endif // _WIN32_WCE @@ -88,7 +91,7 @@ public: void signal() { SetEvent(hEvent); } -} +}; #else diff --git a/src/porting.h b/src/porting.h index 03d2fcbd3..d7d107340 100644 --- a/src/porting.h +++ b/src/porting.h @@ -40,14 +40,17 @@ with this program; if not, write to the Free Software Foundation, Inc., //#define ALIGNOF(type) offsetof (alignment_trick, member) #ifdef _WIN32 + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0500 + #endif #include #define sleep_ms(x) Sleep(x) #else #include #include //for uintptr_t - - #if defined(linux) || defined(__linux) + + #if (defined(linux) || defined(__linux)) && !defined(_GNU_SOURCE) #define _GNU_SOURCE #endif From f00d136ab1523297fdf4707bbc5d40ec9d4f7afb Mon Sep 17 00:00:00 2001 From: kwolekr Date: Sat, 23 Feb 2013 22:06:16 -0500 Subject: [PATCH 41/43] Fix selection of # threads, set default to 1 thread --- src/defaultsettings.cpp | 2 +- src/emerge.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 0e82c4e2f..4307f7610 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -187,7 +187,7 @@ void set_default_settings(Settings *settings) settings->setDefault("emergequeue_limit_total", "256"); settings->setDefault("emergequeue_limit_diskonly", ""); settings->setDefault("emergequeue_limit_generate", ""); - settings->setDefault("num_emerge_threads", ""); + settings->setDefault("num_emerge_threads", "1"); // physics stuff settings->setDefault("movement_acceleration_default", "3"); diff --git a/src/emerge.cpp b/src/emerge.cpp index dd97734c5..dc2bb3e99 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -56,8 +56,7 @@ EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { if (g_settings->get("num_emerge_threads").empty()) { int nprocs = porting::getNumberOfProcessors(); // leave a proc for the main thread and one for some other misc threads - if (nprocs > 2) - nthreads = nprocs - 2; + nthreads = (nprocs > 2) ? nthreads = nprocs - 2 : 1; } else { nthreads = g_settings->getU16("num_emerge_threads"); } From 9b5f182642416add39aab7782b1d155dafead160 Mon Sep 17 00:00:00 2001 From: kwolekr Date: Sat, 23 Feb 2013 22:45:12 -0500 Subject: [PATCH 42/43] Remove dead code, relocate some code --- src/emerge.cpp | 332 ++++--------------------------------------------- src/server.h | 138 ++++++-------------- 2 files changed, 62 insertions(+), 408 deletions(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index dc2bb3e99..250c44fbb 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -41,6 +41,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen_v6.h" +/////////////////////////////// Emerge Manager //////////////////////////////// + EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { //register built-in mapgens registerMapgen("v6", new MapgenFactoryV6()); @@ -166,33 +168,6 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate } -bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) { - std::map::iterator iter; - JMutexAutoLock queuelock(emerge->queuemutex); - - if (blockqueue.empty()) - return false; - v3s16 p = blockqueue.front(); - blockqueue.pop(); - - *pos = p; - - iter = emerge->blocks_enqueued.find(p); - if (iter == emerge->blocks_enqueued.end()) - return false; //uh oh, queue and map out of sync!! - - BlockEmergeData *bedata = iter->second; - *flags = bedata->flags; - - emerge->peer_queue_count[bedata->peer_requested]--; - - delete bedata; - emerge->blocks_enqueued.erase(iter); - - return true; -} - - int EmergeManager::getGroundLevelAtPoint(v2s16 p) { if (mapgen.size() == 0 || !mapgen[0]) { errorstream << "EmergeManager: getGroundLevelAtPoint() called" @@ -290,59 +265,34 @@ bool EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) } +////////////////////////////// Emerge Thread ////////////////////////////////// -class MapEditEventIgnorer -{ -public: - MapEditEventIgnorer(bool *flag): - m_flag(flag) - { - if(*m_flag == false) - *m_flag = true; - else - m_flag = NULL; - } +bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) { + std::map::iterator iter; + JMutexAutoLock queuelock(emerge->queuemutex); - ~MapEditEventIgnorer() - { - if(m_flag) - { - assert(*m_flag); - *m_flag = false; - } - } + if (blockqueue.empty()) + return false; + v3s16 p = blockqueue.front(); + blockqueue.pop(); + + *pos = p; + + iter = emerge->blocks_enqueued.find(p); + if (iter == emerge->blocks_enqueued.end()) + return false; //uh oh, queue and map out of sync!! -private: - bool *m_flag; -}; + BlockEmergeData *bedata = iter->second; + *flags = bedata->flags; + + emerge->peer_queue_count[bedata->peer_requested]--; -class MapEditEventAreaIgnorer -{ -public: - MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): - m_ignorevariable(ignorevariable) - { - if(m_ignorevariable->getVolume() == 0) - *m_ignorevariable = a; - else - m_ignorevariable = NULL; - } + delete bedata; + emerge->blocks_enqueued.erase(iter); + + return true; +} - ~MapEditEventAreaIgnorer() - { - if(m_ignorevariable) - { - assert(m_ignorevariable->getVolume() != 0); - *m_ignorevariable = VoxelArea(); - } - } - -private: - VoxelArea *m_ignorevariable; -}; - - -#if 1 bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b, BlockMakeData *data, bool allow_gen) { @@ -501,235 +451,3 @@ void *EmergeThread::Thread() { log_deregister_thread(); return NULL; } - -#else - -void *EmergeThread::Thread() { - ThreadStarted(); - log_register_thread("EmergeThread"); - DSTACK(__FUNCTION_NAME); - BEGIN_DEBUG_EXCEPTION_HANDLER - - bool enable_mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info"); - - v3s16 last_tried_pos(-32768,-32768,-32768); // For error output - ServerMap &map = ((ServerMap&)m_server->m_env->getMap()); - EmergeManager *emerge = m_server->m_emerge; - Mapgen *mapgen = emerge->getMapgen(); - - while(getRun()) - try { - QueuedBlockEmerge *qptr = m_server->m_emerge_queue.pop(); - if(qptr == NULL) - break; - SharedPtr q(qptr); - - v3s16 &p = q->pos; - v2s16 p2d(p.X,p.Z); - - last_tried_pos = p; - - /* - Do not generate over-limit - */ - if (blockpos_over_limit(p)) - continue; - - //infostream<<"EmergeThread::Thread(): running"<::Iterator i; - for (i=q->s.getIterator(); !i.atEnd(); i++) { - u8 flags = i.getNode()->getValue(); - if (!(flags & BLOCK_EMERGE_FLAG_FROMDISK)) { - only_from_disk = false; - break; - } - } - } - - if (enable_mapgen_debug_info) - infostream<<"EmergeThread: p=" - <<"("<isGenerated() == false)){ - if(enable_mapgen_debug_info) - infostream<<"EmergeThread: generating"<makeChunk(&data); - - if (enable_mapgen_debug_info == false) - t.stop(true); // Hide output - } - - do{ // enable break - // Lock environment again to access the map - JMutexAutoLock envlock(m_server->m_env_mutex); - - ScopeProfiler sp(g_profiler, "EmergeThread: after " - "mapgen::make_block (envlock)", SPT_AVG); - - // Blit data back on map, update lighting, add mobs and - // whatever this does - map.finishBlockMake(&data, modified_blocks); - - // Get central block - block = map.getBlockNoCreateNoEx(p); - - // If block doesn't exist, don't try doing anything with it - // This happens if the block is not in generation boundaries - if(!block) - break; - - /* - Do some post-generate stuff - */ - v3s16 minp = data.blockpos_min * MAP_BLOCKSIZE; - v3s16 maxp = data.blockpos_max * MAP_BLOCKSIZE + - v3s16(1,1,1) * (MAP_BLOCKSIZE - 1); - - /* - Ignore map edit events, they will not need to be - sent to anybody because the block hasn't been sent - to anybody - */ - MapEditEventAreaIgnorer ign( - &m_server->m_ignore_map_edit_events_area, - VoxelArea(minp, maxp)); - { - TimeTaker timer("on_generated"); - scriptapi_environment_on_generated(m_server->m_lua, - minp, maxp, emerge->getBlockSeed(minp)); - //int t = timer.stop(true); - //dstream<<"on_generated took "<m_env->activateBlock(block, 0); - }while(false); - } - - if(block == NULL) - got_block = false; - - /* - Set sent status of modified blocks on clients - */ - - // NOTE: Server's clients are also behind the connection mutex - JMutexAutoLock lock(m_server->m_con_mutex); - - /* - Add the originally fetched block to the modified list - */ - if(got_block) - modified_blocks.insert(p, block); - - /* - Set the modified blocks unsent for all the clients - */ - for(core::map::Iterator - i = m_server->m_clients.getIterator(); - i.atEnd() == false; i++) { - RemoteClient *client = i.getNode()->getValue(); - if(modified_blocks.size() > 0) { - // Remove block from sent history - client->SetBlocksNotSent(modified_blocks); - } - } - - -niters++; - } - catch (VersionMismatchException &e) { - std::ostringstream err; - err << "World data version mismatch in MapBlock "<setAsyncFatalError(err.str()); - } - catch (SerializationError &e) { - std::ostringstream err; - err << "Invalid data in MapBlock "<setAsyncFatalError(err.str()); - } -printf("emergethread iterated %d times\n", niters); - END_DEBUG_EXCEPTION_HANDLER(errorstream) - log_deregister_thread(); - return NULL; -} - -#endif diff --git a/src/server.h b/src/server.h index e92cbb564..907be485d 100644 --- a/src/server.h +++ b/src/server.h @@ -73,115 +73,55 @@ public: */ v3f findSpawnPos(ServerMap &map); -/* - A structure containing the data needed for queueing the fetching - of blocks. -*/ -struct QueuedBlockEmerge -{ - v3s16 pos; - // key = peer_id, value = flags - core::map peer_ids; -}; -/* - This is a thread-safe class. -*/ -class BlockEmergeQueue +class MapEditEventIgnorer { public: - BlockEmergeQueue() + MapEditEventIgnorer(bool *flag): + m_flag(flag) { - m_mutex.Init(); + if(*m_flag == false) + *m_flag = true; + else + m_flag = NULL; } - ~BlockEmergeQueue() + ~MapEditEventIgnorer() { - JMutexAutoLock lock(m_mutex); - - core::list::Iterator i; - for(i=m_queue.begin(); i!=m_queue.end(); i++) + if(m_flag) { - QueuedBlockEmerge *q = *i; - delete q; + assert(*m_flag); + *m_flag = false; } } - /* - peer_id=0 adds with nobody to send to - */ - void addBlock(u16 peer_id, v3s16 pos, u8 flags) - { - DSTACK(__FUNCTION_NAME); - - JMutexAutoLock lock(m_mutex); - - if(peer_id != 0) - { - /* - Find if block is already in queue. - If it is, update the peer to it and quit. - */ - core::list::Iterator i; - for(i=m_queue.begin(); i!=m_queue.end(); i++) { - QueuedBlockEmerge *q = *i; - if (q->pos == pos) { - q->peer_ids[peer_id] = flags; - return; - } - } - } - - /* - Add the block - */ - QueuedBlockEmerge *q = new QueuedBlockEmerge; - q->pos = pos; - if (peer_id != 0) - q->peer_ids[peer_id] = flags; - m_queue.push_back(q); - } - - // Returned pointer must be deleted - // Returns NULL if queue is empty - QueuedBlockEmerge * pop() - { - JMutexAutoLock lock(m_mutex); - - core::list::Iterator i = m_queue.begin(); - if(i == m_queue.end()) - return NULL; - QueuedBlockEmerge *q = *i; - m_queue.erase(i); - return q; - } - - u32 size() - { - JMutexAutoLock lock(m_mutex); - return m_queue.size(); - } - - u32 peerItemCount(u16 peer_id) - { - JMutexAutoLock lock(m_mutex); - - u32 count = 0; - - core::list::Iterator i; - for(i=m_queue.begin(); i!=m_queue.end(); i++) - { - QueuedBlockEmerge *q = *i; - if(q->peer_ids.find(peer_id) != NULL) - count++; - } - - return count; - } - private: - core::list m_queue; - JMutex m_mutex; + bool *m_flag; +}; + +class MapEditEventAreaIgnorer +{ +public: + MapEditEventAreaIgnorer(VoxelArea *ignorevariable, const VoxelArea &a): + m_ignorevariable(ignorevariable) + { + if(m_ignorevariable->getVolume() == 0) + *m_ignorevariable = a; + else + m_ignorevariable = NULL; + } + + ~MapEditEventAreaIgnorer() + { + if(m_ignorevariable) + { + assert(m_ignorevariable->getVolume() != 0); + *m_ignorevariable = VoxelArea(); + } + } + +private: + VoxelArea *m_ignorevariable; }; class Server; @@ -761,10 +701,6 @@ private: // The server mainly operates in this thread ServerThread m_thread; - // This thread fetches and generates map - //EmergeThread m_emergethread; - // Queue of block coordinates to be processed by the emerge thread - //BlockEmergeQueue m_emerge_queue; /* Time related stuff From d31f07bd4b83f858cce589faac56922e12ba670f Mon Sep 17 00:00:00 2001 From: kwolekr Date: Tue, 26 Feb 2013 01:57:59 -0500 Subject: [PATCH 43/43] Fix most warnings, re-fix MSVC compile error --- src/base64.h | 2 +- src/content_cso.cpp | 2 ++ src/emerge.cpp | 10 +++++----- src/emerge.h | 2 +- src/game.cpp | 4 ++-- src/map.cpp | 2 +- src/noise.cpp | 4 ++-- src/server.cpp | 2 +- src/test.cpp | 2 +- src/util/serialize.h | 11 ++++++----- 10 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/base64.h b/src/base64.h index 5f2d6743d..1cb175518 100644 --- a/src/base64.h +++ b/src/base64.h @@ -7,4 +7,4 @@ bool base64_is_valid(std::string const& s); std::string base64_encode(unsigned char const* , unsigned int len); std::string base64_decode(std::string const& s); -#endif // BASE64_HEADER \ No newline at end of file +#endif // BASE64_HEADER diff --git a/src/content_cso.cpp b/src/content_cso.cpp index 20eb88b7d..73d5f2b48 100644 --- a/src/content_cso.cpp +++ b/src/content_cso.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "map.h" +/* static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, float txs, float tys, int col, int row) { @@ -33,6 +34,7 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill, matrix.setTextureTranslate(txs*col, tys*row); matrix.setTextureScale(txs, tys); } +*/ class SmokePuffCSO: public ClientSimpleObject { diff --git a/src/emerge.cpp b/src/emerge.cpp index 250c44fbb..6c6863eff 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -58,7 +58,7 @@ EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { if (g_settings->get("num_emerge_threads").empty()) { int nprocs = porting::getNumberOfProcessors(); // leave a proc for the main thread and one for some other misc threads - nthreads = (nprocs > 2) ? nthreads = nprocs - 2 : 1; + nthreads = (nprocs > 2) ? nprocs - 2 : 1; } else { nthreads = g_settings->getU16("num_emerge_threads"); } @@ -81,7 +81,7 @@ EmergeManager::EmergeManager(IGameDef *gamedef, BiomeDefManager *bdef) { EmergeManager::~EmergeManager() { - for (int i = 0; i != emergethread.size(); i++) { + for (unsigned int i = 0; i != emergethread.size(); i++) { emergethread[i]->setRun(false); emergethread[i]->qevent.signal(); emergethread[i]->stop(); @@ -101,7 +101,7 @@ void EmergeManager::initMapgens(MapgenParams *mgparams) { return; this->params = mgparams; - for (int i = 0; i != emergethread.size(); i++) { + for (unsigned int i = 0; i != emergethread.size(); i++) { mg = createMapgen(params->mg_name, 0, params); if (!mg) { infostream << "EmergeManager: falling back to mapgen v6" << std::endl; @@ -152,7 +152,7 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate // insert into the EmergeThread queue with the least items int lowestitems = emergethread[0]->blockqueue.size(); - for (int i = 1; i != emergethread.size(); i++) { + for (unsigned int i = 1; i != emergethread.size(); i++) { int nitems = emergethread[i]->blockqueue.size(); if (nitems < lowestitems) { idx = i; @@ -259,7 +259,7 @@ void EmergeManager::setParamsToSettings(Settings *settings) { } -bool EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) { +void EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory) { mglist.insert(std::make_pair(mgname, mgfactory)); infostream << "EmergeManager: registered mapgen " << mgname << std::endl; } diff --git a/src/emerge.h b/src/emerge.h index e5014df58..70b67e731 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -75,7 +75,7 @@ public: MapgenParams *createMapgenParams(std::string mgname); bool enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate); - bool registerMapgen(std::string name, MapgenFactory *mgfactory); + void registerMapgen(std::string name, MapgenFactory *mgfactory); MapgenParams *getParamsFromSettings(Settings *settings); void setParamsToSettings(Settings *settings); diff --git a/src/game.cpp b/src/game.cpp index 8ab0f3fe3..1ae29b13b 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2816,12 +2816,12 @@ void the_game( char temptext[300]; snprintf(temptext, 300, "(% .1f, % .1f, % .1f)" - " (yaw = %.1f) (seed = %lli)", + " (yaw = %.1f) (seed = %llu)", player_position.X/BS, player_position.Y/BS, player_position.Z/BS, wrapDegrees_0_360(camera_yaw), - client.getMapSeed()); + (unsigned long long)client.getMapSeed()); guitext2->setText(narrow_to_wide(temptext).c_str()); guitext2->setVisible(true); diff --git a/src/map.cpp b/src/map.cpp index 07af2676b..4be094326 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -3283,7 +3283,7 @@ void ServerMap::save(ModifiedState save_level) block_count_all++; - if(block->getModified() >= save_level) + if(block->getModified() >= (u32)save_level) { // Lazy beginSave() if(!save_started){ diff --git a/src/noise.cpp b/src/noise.cpp index d250882e8..49b5f7e58 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -507,7 +507,7 @@ void Noise::gradientMap3D(float x, float y, float z, float *Noise::perlinMap2D(float x, float y) { - float a = 0.0, f = 1.0, g = 1.0; + float f = 1.0, g = 1.0; int i, j, index, oct; x /= np->spread.X; @@ -537,7 +537,7 @@ float *Noise::perlinMap2D(float x, float y) { float *Noise::perlinMap3D(float x, float y, float z) { - float a = 0.0, f = 1.0, g = 1.0; + float f = 1.0, g = 1.0; int i, j, k, index, oct; x /= np->spread.X; diff --git a/src/server.cpp b/src/server.cpp index 2d00cf4ac..41a7a4289 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1652,7 +1652,7 @@ void Server::AsyncRunStep() { counter = 0.0; - for (int i = 0; i != m_emerge->emergethread.size(); i++) + for (unsigned int i = 0; i != m_emerge->emergethread.size(); i++) m_emerge->emergethread[i]->trigger(); // Update m_enable_rollback_recording here too diff --git a/src/test.cpp b/src/test.cpp index f988b34f7..d86868118 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -462,7 +462,7 @@ struct TestCompress: public TestBase <