summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--README.txt20
-rw-r--r--builtin/features.lua10
-rw-r--r--doc/lua_api.txt11
-rw-r--r--doc/minetest.63
-rw-r--r--games/minimal/menu/background.pngbin0 -> 1387 bytes
-rw-r--r--games/minimal/menu/icon.pngbin0 -> 397 bytes
-rw-r--r--minetest.conf.example2
-rw-r--r--src/client.cpp19
-rw-r--r--src/client.h2
-rw-r--r--src/clientserver.h8
-rw-r--r--src/defaultsettings.cpp1
-rw-r--r--src/environment.cpp17
-rw-r--r--src/game.cpp308
-rw-r--r--src/guiChatConsole.cpp1
-rw-r--r--src/guiConfigureWorld.cpp22
-rw-r--r--src/guiCreateWorld.cpp15
-rw-r--r--src/guiCreateWorld.h4
-rw-r--r--src/guiMainMenu.cpp115
-rw-r--r--src/guiMainMenu.h15
-rw-r--r--src/guiPauseMenu.cpp16
-rw-r--r--src/itemdef.cpp14
-rw-r--r--src/main.cpp459
-rw-r--r--src/main.h8
-rw-r--r--src/mapblock_mesh.cpp13
-rw-r--r--src/mesh.cpp1
-rw-r--r--src/mods.cpp373
-rw-r--r--src/mods.h76
-rw-r--r--src/scriptapi.cpp3
-rw-r--r--src/server.cpp17
-rw-r--r--src/serverlist.cpp2
-rw-r--r--src/shader.cpp8
-rw-r--r--src/shader.h8
-rw-r--r--src/subgame.cpp10
-rw-r--r--src/subgame.h7
-rw-r--r--src/tile.cpp2
-rw-r--r--src/tile.h11
37 files changed, 1133 insertions, 469 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff44e9c26..ba273a0dc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -143,6 +143,7 @@ if(EXISTS ${MINETEST_GAME_SOURCE} AND IS_DIRECTORY ${MINETEST_GAME_SOURCE})
install(FILES ${MINETEST_GAME_SOURCE}/game.conf DESTINATION "${SHAREDIR}/games/minetest_game/")
install(FILES ${MINETEST_GAME_SOURCE}/README.txt DESTINATION "${SHAREDIR}/games/minetest_game/")
install(DIRECTORY ${MINETEST_GAME_SOURCE}/mods DESTINATION "${SHAREDIR}/games/minetest_game")
+ install(DIRECTORY ${MINETEST_GAME_SOURCE}/menu DESTINATION "${SHAREDIR}/games/minetest_game")
endif()
set(MINETEST_BUILD_GAME_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/games/build")
if(EXISTS ${MINETEST_BUILD_GAME_SOURCE} AND IS_DIRECTORY ${MINETEST_BUILD_GAME_SOURCE})
diff --git a/README.txt b/README.txt
index 74940a147..ae5003f77 100644
--- a/README.txt
+++ b/README.txt
@@ -30,13 +30,17 @@ This game is not finished
Default Controls
-----------------
-- WASD: Move
-- Space: Jump
-- E: Go down
-- Shift: Sneak
-- Q: Drop item
-- I: Open inventory
-- Mouse: Turn/look
+- WASD: move
+- Space: jump/climb
+- Shift: sneak/go down
+- Q: drop item
+- I: inventory
+- Mouse: turn/look
+- Mouse left: dig/punch
+- Mouse right: place/use
+- Mouse wheel: select item
+- Esc: pause menu
+- T: chat
- Settable in the configuration file, see the section below.
Paths
@@ -277,7 +281,7 @@ the Free Software Foundation; either version 2.1 of the License, or
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 General Public License for more details.
+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.,
diff --git a/builtin/features.lua b/builtin/features.lua
index 0eef2519d..9d00cfd99 100644
--- a/builtin/features.lua
+++ b/builtin/features.lua
@@ -1,11 +1,11 @@
-- Minetest: builtin/features.lua
minetest.features = {
- "glasslike_framed" = true,
- "nodebox_as_selectionbox" = true,
- "chat_send_player_param3" = true,
- "get_all_craft_recipes_works" = true,
- "use_texture_alpha" = true,
+ glasslike_framed = true,
+ nodebox_as_selectionbox = true,
+ chat_send_player_param3 = true,
+ get_all_craft_recipes_works = true,
+ use_texture_alpha = true,
}
function minetest.has_feature(arg)
diff --git a/doc/lua_api.txt b/doc/lua_api.txt
index 597f98c2c..02ca7cba3 100644
--- a/doc/lua_api.txt
+++ b/doc/lua_api.txt
@@ -120,6 +120,17 @@ depends.txt:
List of mods that have to be loaded before loading this mod.
A single line contains a single modname.
+ Optional dependencies can be defined by appending a question mark
+ to a single modname. Their meaning is that if the specified mod
+ is missing, that does not prevent this mod from being loaded.
+
+optdepends.txt:
+ An alternative way of specifying optional dependencies.
+ Like depends.txt, a single line contains a single modname.
+
+ NOTE: This file exists for compatibility purposes only and
+ support for it will be removed from the engine by the end of 2013.
+
init.lua:
The main Lua script. Running this script should register everything it
wants to register. Subsequent execution depends on minetest calling the
diff --git a/doc/minetest.6 b/doc/minetest.6
index b3fdd94d9..64dfdd149 100644
--- a/doc/minetest.6
+++ b/doc/minetest.6
@@ -61,6 +61,9 @@ Run dedicated server
\-\-speedtests
Run speed tests
.TP
+\-\-videomodes
+List available video modes
+.TP
\-\-info
Print more information to console
.TP
diff --git a/games/minimal/menu/background.png b/games/minimal/menu/background.png
new file mode 100644
index 000000000..e020f909d
--- /dev/null
+++ b/games/minimal/menu/background.png
Binary files differ
diff --git a/games/minimal/menu/icon.png b/games/minimal/menu/icon.png
new file mode 100644
index 000000000..fa8d7cd7f
--- /dev/null
+++ b/games/minimal/menu/icon.png
Binary files differ
diff --git a/minetest.conf.example b/minetest.conf.example
index 36ef31459..7d56f99d8 100644
--- a/minetest.conf.example
+++ b/minetest.conf.example
@@ -151,6 +151,8 @@
#crosshair_color = (255,255,255)
# Cross alpha (opaqueness, between 0 and 255)
#crosshair_alpha = 255
+# Sensitivity multiplier
+#mouse_sensitivity = 0.2
# Sound settings
#enable_sound = true
#sound_volume = 0.7
diff --git a/src/client.cpp b/src/client.cpp
index 56505c66c..929ed0eab 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h"
#include "settings.h"
#include "profiler.h"
+#include "gettext.h"
#include "log.h"
#include "nodemetadata.h"
#include "nodedef.h"
@@ -2555,6 +2556,9 @@ void Client::inventoryAction(InventoryAction *a)
Predict some local inventory changes
*/
a->clientApply(this, this);
+
+ // Remove it
+ delete a;
}
ClientActiveObject * Client::getSelectedActiveObject(
@@ -2801,7 +2805,10 @@ ClientEvent Client::getClientEvent()
return m_client_event_queue.pop_front();
}
-void Client::afterContentReceived()
+void draw_load_screen(const std::wstring &text,
+ IrrlichtDevice* device, gui::IGUIFont* font,
+ float dtime=0 ,int percent=0, bool clouds=true);
+void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font)
{
infostream<<"Client::afterContentReceived() started"<<std::endl;
assert(m_itemdef_received);
@@ -2836,13 +2843,23 @@ void Client::afterContentReceived()
if(g_settings->getBool("preload_item_visuals"))
{
verbosestream<<"Updating item textures and meshes"<<std::endl;
+ wchar_t* text = wgettext("Item textures...");
+ draw_load_screen(text,device,font,0,0);
std::set<std::string> names = m_itemdef->getAll();
+ size_t size = names.size();
+ size_t count = 0;
+ int percent = 0;
for(std::set<std::string>::const_iterator
i = names.begin(); i != names.end(); ++i){
// Asking for these caches the result
m_itemdef->getInventoryTexture(*i, this);
m_itemdef->getWieldMesh(*i, this);
+ count++;
+ percent = count*100/size;
+ if (count%50 == 0) // only update every 50 item
+ draw_load_screen(text,device,font,0,percent);
}
+ delete[] text;
}
// Start mesh update thread after setting up content definitions
diff --git a/src/client.h b/src/client.h
index 67ba6c565..f0cc55868 100644
--- a/src/client.h
+++ b/src/client.h
@@ -385,7 +385,7 @@ public:
bool nodedefReceived()
{ return m_nodedef_received; }
- void afterContentReceived();
+ void afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font);
float getRTT(void);
diff --git a/src/clientserver.h b/src/clientserver.h
index cfa87ada7..ee9271bbc 100644
--- a/src/clientserver.h
+++ b/src/clientserver.h
@@ -91,10 +91,10 @@ SharedBuffer<u8> makePacket_TOCLIENT_TIME_OF_DAY(u16 time, float time_speed);
PROTOCOL_VERSION 19:
GENERIC_CMD_SET_PHYSICS_OVERRIDE
PROTOCOL_VERSION 20:
- TOCLIENT_HUD_ADD
- TOCLIENT_HUD_RM
- TOCLIENT_HUD_CHANGE
- TOCLIENT_HUD_BUILTIN_ENABLE
+ TOCLIENT_HUDADD
+ TOCLIENT_HUDRM
+ TOCLIENT_HUDCHANGE
+ TOCLIENT_HUD_SET_FLAGS
*/
#define LATEST_PROTOCOL_VERSION 20
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 09ec45697..f270a47aa 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -117,6 +117,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("selectionbox_color", "(0,0,0)");
settings->setDefault("crosshair_color", "(255,255,255)");
settings->setDefault("crosshair_alpha", "255");
+ settings->setDefault("mouse_sensitivity", "0.2");
settings->setDefault("enable_sound", "true");
settings->setDefault("sound_volume", "0.8");
settings->setDefault("desynchronize_mapblock_texture_animation", "true");
diff --git a/src/environment.cpp b/src/environment.cpp
index 438c9ef4f..e06b032f2 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -1084,8 +1084,8 @@ void ServerEnvironment::step(float dtime)
{
v3s16 p = *i;
- /*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
- <<") became inactive"<<std::endl;*/
+ /* infostream<<"Server: Block " << PP(p)
+ << " became inactive"<<std::endl; */
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
if(block==NULL)
@@ -1104,9 +1104,6 @@ void ServerEnvironment::step(float dtime)
i != blocks_added.end(); ++i)
{
v3s16 p = *i;
-
- /*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z
- <<") became active"<<std::endl;*/
MapBlock *block = m_map->getBlockNoCreateNoEx(p);
if(block==NULL){
@@ -1117,6 +1114,8 @@ void ServerEnvironment::step(float dtime)
}
activateBlock(block);
+ /* infostream<<"Server: Block " << PP(p)
+ << " became active"<<std::endl; */
}
}
@@ -1850,17 +1849,17 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete)
<<" Forcing delete."<<std::endl;
force_delete = true;
} else {
- u16 new_id = pending_delete ? id : 0;
// If static counterpart already exists, remove it first.
// This shouldn't happen, but happens rarely for some
// unknown reason. Unsuccessful attempts have been made to
// find said reason.
- if(new_id && block->m_static_objects.m_active.find(new_id) != block->m_static_objects.m_active.end()){
+ if(id && block->m_static_objects.m_active.find(id) != block->m_static_objects.m_active.end()){
infostream<<"ServerEnv: WARNING: Performing hack #83274"
<<std::endl;
- block->m_static_objects.remove(new_id);
+ block->m_static_objects.remove(id);
}
- block->m_static_objects.insert(new_id, s_obj);
+ //store static data
+ block->m_static_objects.insert(0, s_obj);
// Only mark block as modified if data changed considerably
if(shall_be_written)
diff --git a/src/game.cpp b/src/game.cpp
index 189003e4c..f63a4a400 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -395,11 +395,14 @@ PointedThing getPointedThing(Client *client, v3f player_position,
/*
Draws a screen with a single text on it.
Text will be removed when the screen is drawn the next time.
+ Additionally, a progressbar can be drawn when percent is set between 0 and 100.
*/
/*gui::IGUIStaticText **/
void draw_load_screen(const std::wstring &text,
- video::IVideoDriver* driver, gui::IGUIFont* font)
+ IrrlichtDevice* device, gui::IGUIFont* font,
+ float dtime=0 ,int percent=0, bool clouds=true)
{
+ video::IVideoDriver* driver = device->getVideoDriver();
v2u32 screensize = driver->getScreenSize();
const wchar_t *loadingtext = text.c_str();
core::vector2d<u32> textsize_u = font->getDimension(loadingtext);
@@ -411,7 +414,30 @@ void draw_load_screen(const std::wstring &text,
loadingtext, textrect, false, false);
guitext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
- driver->beginScene(true, true, video::SColor(255,0,0,0));
+ bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds");
+ if (cloud_menu_background)
+ {
+ g_menuclouds->step(dtime*3);
+ g_menuclouds->render();
+ driver->beginScene(true, true, video::SColor(255,140,186,250));
+ g_menucloudsmgr->drawAll();
+ }
+ else
+ driver->beginScene(true, true, video::SColor(255,0,0,0));
+ if (percent >= 0 && percent <= 100) // draw progress bar
+ {
+ core::vector2d<s32> barsize(256,32);
+ core::rect<s32> barrect(center-barsize/2, center+barsize/2);
+ driver->draw2DRectangle(video::SColor(255,255,255,255),barrect, NULL); // border
+ driver->draw2DRectangle(video::SColor(255,64,64,64), core::rect<s32> (
+ barrect.UpperLeftCorner+1,
+ barrect.LowerRightCorner-1), NULL); // black inside the bar
+ driver->draw2DRectangle(video::SColor(255,128,128,128), core::rect<s32> (
+ barrect.UpperLeftCorner+1,
+ core::vector2d<s32>(
+ barrect.LowerRightCorner.X-(barsize.X-1)+percent*(barsize.X-2)/100,
+ barrect.LowerRightCorner.Y-1)), NULL); // the actual progress
+ }
guienv->drawAll();
driver->endScene();
@@ -789,6 +815,67 @@ public:
}
};
+void nodePlacementPrediction(Client &client,
+ const ItemDefinition &playeritem_def,
+ v3s16 nodepos, v3s16 neighbourpos)
+{
+ std::string prediction = playeritem_def.node_placement_prediction;
+ INodeDefManager *nodedef = client.ndef();
+ ClientMap &map = client.getEnv().getClientMap();
+
+ if(prediction != "" && !nodedef->get(map.getNode(nodepos)).rightclickable)
+ {
+ verbosestream<<"Node placement prediction for "
+ <<playeritem_def.name<<" is "
+ <<prediction<<std::endl;
+ v3s16 p = neighbourpos;
+ // Place inside node itself if buildable_to
+ try{
+ MapNode n_under = map.getNode(nodepos);
+ if(nodedef->get(n_under).buildable_to)
+ p = nodepos;
+ else if (!nodedef->get(map.getNode(p)).buildable_to)
+ return;
+ }catch(InvalidPositionException &e){}
+ // Find id of predicted node
+ content_t id;
+ bool found = nodedef->getId(prediction, id);
+ if(!found){
+ errorstream<<"Node placement prediction failed for "
+ <<playeritem_def.name<<" (places "
+ <<prediction
+ <<") - Name not known"<<std::endl;
+ return;
+ }
+ // Predict param2
+ u8 param2 = 0;
+ if(nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED){
+ v3s16 dir = nodepos - neighbourpos;
+ if(abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))){
+ param2 = dir.Y < 0 ? 1 : 0;
+ } else if(abs(dir.X) > abs(dir.Z)){
+ param2 = dir.X < 0 ? 3 : 2;
+ } else {
+ param2 = dir.Z < 0 ? 5 : 4;
+ }
+ }
+ // TODO: Facedir prediction
+ // TODO: If predicted node is in attached_node group, check attachment
+ // Add node to client map
+ MapNode n(id, 0, param2);
+ try{
+ // This triggers the required mesh update too
+ client.addNode(p, n);
+ }catch(InvalidPositionException &e){
+ errorstream<<"Node placement prediction failed for "
+ <<playeritem_def.name<<" (places "
+ <<prediction
+ <<") - Position not loaded"<<std::endl;
+ }
+ }
+}
+
+
void the_game(
bool &kill,
bool random_input,
@@ -821,7 +908,11 @@ void the_game(
Draw "Loading" screen
*/
- draw_load_screen(L"Loading...", driver, font);
+ {
+ wchar_t* text = wgettext("Loading...");
+ draw_load_screen(text, device, font,0,0);
+ delete[] text;
+ }
// Create texture source
IWritableTextureSource *tsrc = createTextureSource(device);
@@ -878,7 +969,9 @@ void the_game(
*/
if(address == ""){
- draw_load_screen(L"Creating server...", driver, font);
+ wchar_t* text = wgettext("Creating server....");
+ draw_load_screen(text, device, font,0,25);
+ delete[] text;
infostream<<"Creating server"<<std::endl;
server = new Server(map_dir, configpath, gamespec,
simple_singleplayer_mode);
@@ -891,7 +984,11 @@ void the_game(
Create client
*/
- draw_load_screen(L"Creating client...", driver, font);
+ {
+ wchar_t* text = wgettext("Creating client...");
+ draw_load_screen(text, device, font,0,50);
+ delete[] text;
+ }
infostream<<"Creating client"<<std::endl;
MapDrawControl draw_control;
@@ -901,8 +998,12 @@ void the_game(
// Client acts as our GameDef
IGameDef *gamedef = &client;
-
- draw_load_screen(L"Resolving address...", driver, font);
+
+ {
+ wchar_t* text = wgettext("Resolving address...");
+ draw_load_screen(text, device, font,0,75);
+ delete[] text;
+ }
Address connect_address(0,0,0,0, port);
try{
if(address == "")
@@ -934,15 +1035,26 @@ void the_game(
bool could_connect = false;
bool connect_aborted = false;
try{
- float frametime = 0.033;
float time_counter = 0.0;
input->clear();
+ float fps_max = g_settings->getFloat("fps_max");
+ bool cloud_menu_background = g_settings->getBool("menu_clouds");
+ u32 lasttime = device->getTimer()->getTime();
while(device->run())
{
+ f32 dtime=0; // in seconds
+ if (cloud_menu_background) {
+ u32 time = device->getTimer()->getTime();
+ if(time > lasttime)
+ dtime = (time - lasttime) / 1000.0;
+ else
+ dtime = 0;
+ lasttime = time;
+ }
// Update client and server
- client.step(frametime);
+ client.step(dtime);
if(server != NULL)
- server->step(frametime);
+ server->step(dtime);
// End condition
if(client.connectedAndInitialized()){
@@ -963,15 +1075,37 @@ void the_game(
}
// Display status
- std::wostringstream ss;
- ss<<L"Connecting to server... (press Escape to cancel)\n";
- std::wstring animation = L"/-\\|";
- ss<<animation[(int)(time_counter/0.2)%4];
- draw_load_screen(ss.str(), driver, font);
+ {
+ wchar_t* text = wgettext("Connecting to server...");
+ draw_load_screen(text, device, font, dtime, 100);
+ delete[] text;
+ }
- // Delay a bit
- sleep_ms(1000*frametime);
- time_counter += frametime;
+ // On some computers framerate doesn't seem to be
+ // automatically limited
+ if (cloud_menu_background) {
+ // Time of frame without fps limit
+ float busytime;
+ u32 busytime_u32;
+ // not using getRealTime is necessary for wine
+ u32 time = device->getTimer()->getTime();
+ if(time > lasttime)
+ busytime_u32 = time - lasttime;
+ else
+ busytime_u32 = 0;
+ busytime = busytime_u32 / 1000.0;
+
+ // FPS limiter
+ u32 frametime_min = 1000./fps_max;
+
+ if(busytime_u32 < frametime_min) {
+ u32 sleeptime = frametime_min - busytime_u32;
+ device->sleep(sleeptime);
+ }
+ } else {
+ sleep_ms(25);
+ }
+ time_counter += dtime;
}
}
catch(con::PeerNotFoundException &e)
@@ -995,15 +1129,26 @@ void the_game(
bool got_content = false;
bool content_aborted = false;
{
- float frametime = 0.033;
float time_counter = 0.0;
input->clear();
+ float fps_max = g_settings->getFloat("fps_max");
+ bool cloud_menu_background = g_settings->getBool("menu_clouds");
+ u32 lasttime = device->getTimer()->getTime();
while(device->run())
{
+ f32 dtime=0; // in seconds
+ if (cloud_menu_background) {
+ u32 time = device->getTimer()->getTime();
+ if(time > lasttime)
+ dtime = (time - lasttime) / 1000.0;
+ else
+ dtime = 0;
+ lasttime = time;
+ }
// Update client and server
- client.step(frametime);
+ client.step(dtime);
if(server != NULL)
- server->step(frametime);
+ server->step(dtime);
// End condition
if(client.texturesReceived() &&
@@ -1025,21 +1170,52 @@ void the_game(
}
// Display status
- std::wostringstream ss;
- ss<<L"Waiting content... (press Escape to cancel)\n";
+ std::ostringstream ss;
+ int progress=0;
+ if (!client.itemdefReceived())
+ {
+ ss << "Item definitions...";
+ progress = 0;
+ }
+ else if (!client.nodedefReceived())
+ {
+ ss << "Node definitions...";
+ progress = 25;
+ }
+ else
+ {
+ ss << "Media...";
+ progress = 50+client.mediaReceiveProgress()*50+0.5;
+ }
+ wchar_t* text = wgettext(ss.str().c_str());
+ draw_load_screen(text, device, font, dtime, progress);
+ delete[] text;
+
+ // On some computers framerate doesn't seem to be
+ // automatically limited
+ if (cloud_menu_background) {
+ // Time of frame without fps limit
+ float busytime;
+ u32 busytime_u32;
+ // not using getRealTime is necessary for wine
+ u32 time = device->getTimer()->getTime();
+ if(time > lasttime)
+ busytime_u32 = time - lasttime;
+ else
+ busytime_u32 = 0;
+ busytime = busytime_u32 / 1000.0;
- ss<<(client.itemdefReceived()?L"[X]":L"[ ]");
- ss<<L" Item definitions\n";
- ss<<(client.nodedefReceived()?L"[X]":L"[ ]");
- ss<<L" Node definitions\n";
- ss<<L"["<<(int)(client.mediaReceiveProgress()*100+0.5)<<L"%] ";
- ss<<L" Media\n";
+ // FPS limiter
+ u32 frametime_min = 1000./fps_max;
- draw_load_screen(ss.str(), driver, font);
-
- // Delay a bit
- sleep_ms(1000*frametime);
- time_counter += frametime;
+ if(busytime_u32 < frametime_min) {
+ u32 sleeptime = frametime_min - busytime_u32;
+ device->sleep(sleeptime);
+ }
+ } else {
+ sleep_ms(25);
+ }
+ time_counter += dtime;
}
}
@@ -1056,7 +1232,7 @@ void the_game(
After all content has been received:
Update cached textures, meshes and materials
*/
- client.afterContentReceived();
+ client.afterContentReceived(device,font);
/*
Create the camera node
@@ -1885,7 +2061,8 @@ void the_game(
if(input->isKeyDown(irr::KEY_RIGHT))
dx += dtime * keyspeed;*/
- float d = 0.2;
+ float d = g_settings->getFloat("mouse_sensitivity");
+ d = rangelim(d, 0.01, 100.0);
camera_yaw -= dx*d;
camera_pitch += dy*d;
if(camera_pitch < -89.5) camera_pitch = -89.5;
@@ -2198,17 +2375,15 @@ void the_game(
- Can it point to liquids?
*/
ItemStack playeritem;
- bool playeritem_usable = false;
- bool playeritem_liquids_pointable = false;
{
InventoryList *mlist = local_inventory.getList("main");
if(mlist != NULL)
{
playeritem = mlist->getItem(client.getPlayerItem());
- playeritem_usable = playeritem.getDefinition(itemdef).usable;
- playeritem_liquids_pointable = playeritem.getDefinition(itemdef).liquids_pointable;
}
}
+ const ItemDefinition &playeritem_def =
+ playeritem.getDefinition(itemdef);
ToolCapabilities playeritem_toolcap =
playeritem.getToolCapabilities(itemdef);
@@ -2267,7 +2442,7 @@ void the_game(
// input
&client, player_position, camera_direction,
camera_position, shootline, d,
- playeritem_liquids_pointable, !ldown_for_dig,
+ playeritem_def.liquids_pointable, !ldown_for_dig,
// output
hilightboxes,
selected_object);
@@ -2327,7 +2502,7 @@ void the_game(
else
repeat_rightclick_timer = 0;
- if(playeritem_usable && input->getLeftState())
+ if(playeritem_def.usable && input->getLeftState())
{
if(input->getLeftClicked())
client.interact(4, pointed);
@@ -2534,46 +2709,13 @@ void the_game(
// If the wielded item has node placement prediction,
// make that happen
- const ItemDefinition &def =
- playeritem.getDefinition(itemdef);
- if(def.node_placement_prediction != ""
- && !nodedef->get(map.getNode(nodepos)).rightclickable)
- do{ // breakable
- verbosestream<<"Node placement prediction for "
- <<playeritem.name<<" is "
- <<def.node_placement_prediction<<std::endl;
- v3s16 p = neighbourpos;
- // Place inside node itself if buildable_to
- try{
- MapNode n_under = map.getNode(nodepos);
- if(nodedef->get(n_under).buildable_to)
- p = nodepos;
- }catch(InvalidPositionException &e){}
- // Find id of predicted node
- content_t id;
- bool found =
- nodedef->getId(def.node_placement_prediction, id);
- if(!found){
- errorstream<<"Node placement prediction failed for "
- <<playeritem.name<<" (places "
- <<def.node_placement_prediction
- <<") - Name not known"<<std::endl;
- break;
- }
- MapNode n(id);
- try{
- // This triggers the required mesh update too
- client.addNode(p, n);
- }catch(InvalidPositionException &e){
- errorstream<<"Node placement prediction failed for "
- <<playeritem.name<<" (places "
- <<def.node_placement_prediction
- <<") - Position not loaded"<<std::endl;
- }
- }while(0);
+ nodePlacementPrediction(client,
+ playeritem_def,
+ nodepos, neighbourpos);
// Read the sound
- soundmaker.m_player_rightpunch_sound = def.sound_place;
+ soundmaker.m_player_rightpunch_sound =
+ playeritem_def.sound_place;
}
}
}
@@ -3193,6 +3335,8 @@ void the_game(
clouds->drop();
if (gui_chat_console)
gui_chat_console->drop();
+ if (sky)
+ sky->drop();
clear_particles();
/*
@@ -3201,7 +3345,9 @@ void the_game(
*/
{
/*gui::IGUIStaticText *gui_shuttingdowntext = */
- draw_load_screen(L"Shutting down stuff...", driver, font);
+ wchar_t* text = wgettext("Shutting down stuff...");
+ draw_load_screen(text, device, font, 0, -1, false);
+ delete[] text;
/*driver->beginScene(true, true, video::SColor(255,0,0,0));
guienv->drawAll();
driver->endScene();
diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp
index ec23648f8..f31e599dc 100644
--- a/src/guiChatConsole.cpp
+++ b/src/guiChatConsole.cpp
@@ -121,6 +121,7 @@ GUIChatConsole::GUIChatConsole(
GUIChatConsole::~GUIChatConsole()
{
+ delete m_font;
}
void GUIChatConsole::openConsole(f32 height)
diff --git a/src/guiConfigureWorld.cpp b/src/guiConfigureWorld.cpp
index b2debfbd2..f94ed7d17 100644
--- a/src/guiConfigureWorld.cpp
+++ b/src/guiConfigureWorld.cpp
@@ -407,14 +407,26 @@ bool GUIConfigureWorld::OnEvent(const SEvent& event)
delete[] text;
menu->drop();
- ModConfiguration modconf(m_wspec.path);
- if(!modconf.isConsistent())
+ try
{
- wchar_t* text = wgettext("Warning: Configuration not consistent. ");
+ ModConfiguration modconf(m_wspec.path);
+ if(!modconf.isConsistent())
+ {
+ wchar_t* text = wgettext("Warning: Configuration not consistent. ");
+ GUIMessageMenu *menu =
+ new GUIMessageMenu(Environment, Parent, -1, m_menumgr,
+ text );
+ delete[] text;
+ menu->drop();
+ }
+ }
+ catch(ModError &err)
+ {
+ errorstream<<err.what()<<std::endl;
+ std::wstring text = narrow_to_wide(err.what()) + wgettext("\nCheck debug.txt for details.");
GUIMessageMenu *menu =
new GUIMessageMenu(Environment, Parent, -1, m_menumgr,
- text );
- delete[] text;
+ text );
menu->drop();
}
diff --git a/src/guiCreateWorld.cpp b/src/guiCreateWorld.cpp
index 09b18fb3c..caa884bc0 100644
--- a/src/guiCreateWorld.cpp
+++ b/src/guiCreateWorld.cpp
@@ -42,13 +42,22 @@ GUICreateWorld::GUICreateWorld(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id,
IMenuManager *menumgr,
CreateWorldDest *dest,
- const std::vector<SubgameSpec> &games
+ const std::vector<SubgameSpec> &games,
+ const std::string &initial_game
):
GUIModalMenu(env, parent, id, menumgr),
m_dest(dest),
- m_games(games)
+ m_games(games),
+ m_initial_game_i(0)
{
assert(games.size() > 0);
+
+ for(size_t i=0; i<games.size(); i++){
+ if(games[i].id == initial_game){
+ m_initial_game_i = i;
+ break;
+ }
+ }
}
GUICreateWorld::~GUICreateWorld()
@@ -151,7 +160,7 @@ void GUICreateWorld::regenerateGui(v2u32 screensize)
os<<L"]";
e->addItem(os.str().c_str());
}
- e->setSelected(0);
+ e->setSelected(m_initial_game_i);
}
changeCtype("");
{
diff --git a/src/guiCreateWorld.h b/src/guiCreateWorld.h
index d9bc3638a..2765dc2bd 100644
--- a/src/guiCreateWorld.h
+++ b/src/guiCreateWorld.h
@@ -38,7 +38,8 @@ public:
gui::IGUIElement* parent, s32 id,
IMenuManager *menumgr,
CreateWorldDest *dest,
- const std::vector<SubgameSpec> &games);
+ const std::vector<SubgameSpec> &games,
+ const std::string &initial_game);
~GUICreateWorld();
void removeChildren();
@@ -56,6 +57,7 @@ public:
private:
CreateWorldDest *m_dest;
std::vector<SubgameSpec> m_games;
+ int m_initial_game_i;
};
#endif
diff --git a/src/guiMainMenu.cpp b/src/guiMainMenu.cpp
index 06e7cdff7..36685db84 100644
--- a/src/guiMainMenu.cpp
+++ b/src/guiMainMenu.cpp
@@ -53,12 +53,12 @@ const wchar_t *contrib_core_strs[] = {
L"Ilya Zhuravlev (thexyz) <xyz@minetest.net>",
L"Lisa Milne (darkrose) <lisa@ltmnet.com>",
L"Maciej Kasatkin (RealBadAngel) <mk@realbadangel.pl>",
- L"proller <proler@gmail.com>"
+ L"proller <proler@gmail.com>",
+ L"sfan5 <sfan5@live.de>"
};
const wchar_t *contrib_active_strs[] = {
L"kahrl <kahrl@gmx.net>",
- L"sfan5 <sfan5@live.de>",
L"sapier <sapier@gmx.net>",
L"Vanessa Ezekowitz (VanessaE) <vanessaezekowitz@gmail.com>",
L"Jurgen Doser (doserj) <jurgen.doser@gmail.com>",
@@ -162,15 +162,8 @@ enum
GUI_ID_SERVERLIST_TOGGLE,
GUI_ID_SERVERLIST_DELETE,
GUI_ID_SERVERLIST_TITLE,
-};
-
-enum
-{
- TAB_SINGLEPLAYER=0,
- TAB_MULTIPLAYER,
- TAB_ADVANCED,
- TAB_SETTINGS,
- TAB_CREDITS
+ GUI_ID_GAME_BUTTON_FIRST = 130,
+ GUI_ID_GAME_BUTTON_MAX = 150,
};
GUIMainMenu::GUIMainMenu(gui::IGUIEnvironment* env,
@@ -255,8 +248,13 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
{
core::rect<s32> rect(0, 0, size.X, 40);
rect += v2s32(4, 0);
- Environment->addStaticText(narrow_to_wide(
- "Minetest " VERSION_STRING).c_str(),
+ std::string t = "Minetest " VERSION_STRING;
+ if(m_data->selected_game_name != "" &&
+ m_data->selected_tab == TAB_SINGLEPLAYER){
+ t += "/";
+ t += m_data->selected_game_name;
+ }
+ Environment->addStaticText(narrow_to_wide(t).c_str(),
rect, false, true, this, -1);
}
@@ -342,11 +340,17 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
gui::IGUIListBox *e = Environment->addListBox(rect, this,
GUI_ID_WORLD_LISTBOX);
e->setDrawBackground(true);
- for(std::vector<WorldSpec>::const_iterator i = m_data->worlds.begin();
- i != m_data->worlds.end(); i++){
- e->addItem(narrow_to_wide(i->name+" ["+i->gameid+"]").c_str());
+ m_world_indices.clear();
+ for(size_t wi = 0; wi < m_data->worlds.size(); wi++){
+ const WorldSpec &spec = m_data->worlds[wi];
+ if(spec.gameid == m_data->selected_game){
+ //e->addItem(narrow_to_wide(spec.name+" ["+spec.gameid+"]").c_str());
+ e->addItem(narrow_to_wide(spec.name).c_str());
+ m_world_indices.push_back(wi);
+ if(m_data->selected_world == (int)wi)
+ e->setSelected(m_world_indices.size()-1);
+ }
}
- e->setSelected(m_data->selected_world);
Environment->setFocus(e);
}
// Delete world button
@@ -416,6 +420,26 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
delete[] text;
}
changeCtype("C");
+
+ /* Add game selection buttons */
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ for(size_t i=0; i<m_data->games.size(); i++){
+ const SubgameSpec *spec = &m_data->games[i];
+ v2s32 p(8 + i*(48+8), screensize.Y - (48+8));
+ core::rect<s32> rect(0, 0, 48, 48);
+ rect += p;
+ video::ITexture *bgtexture = NULL;
+ if(spec->menuicon_path != "")
+ bgtexture = driver->getTexture(spec->menuicon_path.c_str());
+ gui::IGUIButton *b = Environment->addButton(rect, this,
+ GUI_ID_GAME_BUTTON_FIRST+i, narrow_to_wide(wrap_rows(spec->id, 4)).c_str());
+ if(bgtexture){
+ b->setImage(bgtexture);
+ b->setText(L"");
+ b->setDrawBorder(false);
+ b->setUseAlphaChannel(true);
+ }
+ }
}
else if(m_data->selected_tab == TAB_MULTIPLAYER)
{
@@ -471,16 +495,20 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
{
core::rect<s32> rect(0, 0, 390, 20);
rect += m_topleft_client + v2s32(50, 10);
- Environment->addStaticText(wgettext("Favorites:"),
+ wchar_t* text = wgettext("Favorites:");
+ Environment->addStaticText(text,
rect, false, true, this, GUI_ID_SERVERLIST_TITLE);
+ delete[] text;
}
} else {
m_data->servers = ServerList::getOnline();
{
core::rect<s32> rect(0, 0, 390, 20);
rect += m_topleft_client + v2s32(50, 10);
- Environment->addStaticText(wgettext("Public Server List:"),
+ wchar_t* text = wgettext("Public Server List:");
+ Environment->addStaticText(text,
rect, false, true, this, GUI_ID_SERVERLIST_TITLE);
+ delete[] text;
}
}
#else
@@ -488,8 +516,10 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
{
core::rect<s32> rect(0, 0, 390, 20);
rect += m_topleft_client + v2s32(50, 10);
- Environment->addStaticText(wgettext("Favorites:"),
+ wchar_t* text = wgettext("Favorites:");
+ Environment->addStaticText(text,
rect, false, true, this, GUI_ID_SERVERLIST_TITLE);
+ delete[] text;
}
#endif
updateGuiServerList();
@@ -714,9 +744,11 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
gui::IGUIListBox *e = Environment->addListBox(rect, this,
GUI_ID_WORLD_LISTBOX);
e->setDrawBackground(true);
- for(std::vector<WorldSpec>::const_iterator i = m_data->worlds.begin();
- i != m_data->worlds.end(); i++){
- e->addItem(narrow_to_wide(i->name+" ["+i->gameid+"]").c_str());
+ m_world_indices.clear();
+ for(size_t wi = 0; wi < m_data->worlds.size(); wi++){
+ const WorldSpec &spec = m_data->worlds[wi];
+ e->addItem(narrow_to_wide(spec.name+" ["+spec.gameid+"]").c_str());
+ m_world_indices.push_back(wi);
}
e->setSelected(m_data->selected_world);
}
@@ -909,7 +941,9 @@ void GUIMainMenu::drawMenu()
if (!skin)
return;
video::IVideoDriver* driver = Environment->getVideoDriver();
-
+
+ /* Draw menu background */
+
/*video::SColor bgcolor(140,0,0,0);
driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);*/
@@ -976,6 +1010,8 @@ void GUIMainMenu::drawMenu()
}
}
+ /* Draw UI elements */
+
gui::IGUIElement::draw();
}
@@ -1100,8 +1136,13 @@ void GUIMainMenu::readInput(MainMenuData *dst)
{
gui::IGUIElement *e = getElementFromId(GUI_ID_WORLD_LISTBOX);
- if(e != NULL && e->getType() == gui::EGUIET_LIST_BOX)
- dst->selected_world = ((gui::IGUIListBox*)e)->getSelected();
+ if(e != NULL && e->getType() == gui::EGUIET_LIST_BOX){
+ int list_i = ((gui::IGUIListBox*)e)->getSelected();
+ if(list_i == -1)
+ dst->selected_world = -1;
+ else
+ dst->selected_world = m_world_indices[list_i];
+ }
}
{
ServerListSpec server =
@@ -1221,7 +1262,7 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
return true;
}
case GUI_ID_CREATE_WORLD_BUTTON: {
- std::vector<SubgameSpec> games = getAvailableGames();
+ const std::vector<SubgameSpec> &games = m_data->games;
if(games.size() == 0){
wchar_t* text = wgettext("Cannot create world: No games found");
GUIMessageMenu *menu = new GUIMessageMenu(env, parent,
@@ -1232,7 +1273,7 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
} else {
CreateWorldDest *dest = new CreateWorldDestMainMenu(this);
GUICreateWorld *menu = new GUICreateWorld(env, parent, -1,
- menumgr, dest, games);
+ menumgr, dest, games, m_data->selected_game);
menu->drop();
}
return true;
@@ -1308,6 +1349,16 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
}
#endif
}
+ /* Game buttons */
+ int eid = event.GUIEvent.Caller->getID();
+ if(eid >= GUI_ID_GAME_BUTTON_FIRST &&
+ eid <= GUI_ID_GAME_BUTTON_MAX){
+ m_data->selected_game =
+ m_data->games[eid - GUI_ID_GAME_BUTTON_FIRST].id;
+ m_data->selected_game_name =
+ m_data->games[eid - GUI_ID_GAME_BUTTON_FIRST].name;
+ regenerateGui(m_screensize_old);
+ }
}
if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER)
{
@@ -1318,9 +1369,11 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
readInput(&cur);
if (getTab() == TAB_MULTIPLAYER && cur.address == L"")
{
+ wchar_t* text = wgettext("Address required.");
(new GUIMessageMenu(env, parent, -1, menumgr,
- wgettext("Address required."))
+ text)
)->drop();
+ delete[] text;
return true;
}
acceptInput();
@@ -1328,6 +1381,10 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
return true;
}
}
+ if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED)
+ {
+ readInput(m_data);
+ }
if(event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN)
{
switch(event.GUIEvent.Caller->getID())
diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h
index a594ccd41..8697344c8 100644
--- a/src/guiMainMenu.h
+++ b/src/guiMainMenu.h
@@ -34,11 +34,22 @@ enum {
SERVERLIST_PUBLIC,
};
+enum
+{
+ TAB_SINGLEPLAYER=0,
+ TAB_MULTIPLAYER,
+ TAB_ADVANCED,
+ TAB_SETTINGS,
+ TAB_CREDITS
+};
+
struct MainMenuData
{
// These are in the native format of the gui elements
// Generic
int selected_tab;
+ std::string selected_game;
+ std::string selected_game_name;
// Client options
std::string servername;
std::string serverdescription;
@@ -78,6 +89,8 @@ struct MainMenuData
MainMenuData():
// Generic
selected_tab(0),
+ selected_game("minetest"),
+ selected_game_name("Minetest"),
// Client opts
fancy_trees(false),
smooth_lighting(false),
@@ -128,6 +141,8 @@ private:
s32 id;
IMenuManager *menumgr;
+ std::vector<int> m_world_indices;
+
bool m_is_regenerating;
v2s32 m_topleft_client;
v2s32 m_size_client;
diff --git a/src/guiPauseMenu.cpp b/src/guiPauseMenu.cpp
index b57b4a1d1..f5d323a9b 100644
--- a/src/guiPauseMenu.cpp
+++ b/src/guiPauseMenu.cpp
@@ -168,16 +168,16 @@ void GUIPauseMenu::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 180, 240);
rect = rect + v2s32(size.X/2 + 90, size.Y/2-rect.getHeight()/2);
wchar_t* text = wgettext("Default Controls:\n"
- "- WASD: Walk\n"
- "- Mouse left: dig/hit\n"
+ "- WASD: move\n"
+ "- Space: jump/climb\n"
+ "- Shift: sneak/go down\n"
+ "- Q: drop item\n"
+ "- I: inventory\n"
+ "- Mouse: turn/look\n"
+ "- Mouse left: dig/punch\n"
"- Mouse right: place/use\n"
"- Mouse wheel: select item\n"
- "- 0...9: select item\n"
- "- Shift: sneak\n"
- "- R: Toggle viewing all loaded chunks\n"
- "- I: Inventory menu\n"
- "- ESC: This menu\n"
- "- T: Chat\n"
+ "- T: chat\n"
);
Environment->addStaticText(text, rect, false, true, this, 258);
delete[] text;
diff --git a/src/itemdef.cpp b/src/itemdef.cpp
index 05328ea48..d660db77f 100644
--- a/src/itemdef.cpp
+++ b/src/itemdef.cpp
@@ -226,17 +226,11 @@ class CItemDefManager: public IWritableItemDefManager
public:
CItemDefManager()
{
- for (std::map<std::string, ItemDefinition*>::iterator iter =
- m_item_definitions.begin(); iter != m_item_definitions.end();
- iter ++) {
- delete iter->second;
- }
- m_item_definitions.clear();
+
#ifndef SERVER
m_main_thread = get_current_thread_id();
m_driver = NULL;
#endif
-
clear();
}
virtual ~CItemDefManager()
@@ -260,6 +254,12 @@ public:
}
m_driver = NULL;
#endif
+ for (std::map<std::string, ItemDefinition*>::iterator iter =
+ m_item_definitions.begin(); iter != m_item_definitions.end();
+ iter ++) {
+ delete iter->second;
+ }
+ m_item_definitions.clear();
}
virtual const ItemDefinition& get(const std::string &name_) const
{
diff --git a/src/main.cpp b/src/main.cpp
index eef65cdb2..7f9ec1ace 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -76,6 +76,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "subgame.h"
#include "quicktune.h"
#include "serverlist.h"
+#include "sound.h"
+#include "sound_openal.h"
/*
Settings.
@@ -88,6 +90,10 @@ Settings *g_settings = &main_settings;
Profiler main_profiler;
Profiler *g_profiler = &main_profiler;
+// Menu clouds are created later
+Clouds *g_menuclouds = 0;
+irr::scene::ISceneManager *g_menucloudsmgr = 0;
+
/*
Debug streams
*/
@@ -196,7 +202,49 @@ u32 getTime(TimePrecision prec) {
return 0;
return g_timegetter->getTime(prec);
}
+#endif
+
+//Client side main menu music fetcher
+#ifndef SERVER
+class MenuMusicFetcher: public OnDemandSoundFetcher
+{
+ std::set<std::string> m_fetched;
+public:
+ void fetchSounds(const std::string &name,
+ std::set<std::string> &dst_paths,
+ std::set<std::string> &dst_datas)
+ {
+ if(m_fetched.count(name))
+ return;
+ m_fetched.insert(name);
+ std::string base;
+ base = porting::path_share + DIR_DELIM + "sounds";
+ dst_paths.insert(base + DIR_DELIM + name + ".ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".0.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".1.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".2.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".3.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".4.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".5.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".6.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".7.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".8.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".9.ogg");
+ base = porting::path_user + DIR_DELIM + "sounds";
+ dst_paths.insert(base + DIR_DELIM + name + ".ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".0.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".1.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".2.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".3.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".4.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".5.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".6.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".7.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".8.ogg");
+ dst_paths.insert(base + DIR_DELIM + name + ".9.ogg");
+ }
+};
#endif
class StderrLogOutput: public ILogOutput
@@ -612,122 +660,181 @@ private:
bool rightreleased;
};
-//Draw the tiled menu background
-void drawMenuBackground(video::IVideoDriver* driver) {
- core::dimension2d<u32> screensize = driver->getScreenSize();
-
- std::string path = getTexturePath("menubg.png");
- if (path[0]) {
- static const video::ITexture *bgtexture =
- driver->getTexture(path.c_str());
+struct MenuTextures
+{
+ std::string current_gameid;
+ bool global_textures;
+ video::ITexture *background;
+ video::ITexture *overlay;
+ video::ITexture *header;
+ video::ITexture *footer;
+
+ MenuTextures():
+ global_textures(false),
+ background(NULL),
+ overlay(NULL),
+ header(NULL),
+ footer(NULL)
+ {}
- if (bgtexture) {
- s32 scaledsize = 128;
-
- // The important difference between destsize and screensize is
- // that destsize is rounded to whole scaled pixels.
- // These formulas use component-wise multiplication and division of v2u32.
- v2u32 texturesize = bgtexture->getSize();
- v2u32 sourcesize = texturesize * screensize / scaledsize + v2u32(1,1);
- v2u32 destsize = scaledsize * sourcesize / texturesize;
-
- // Default texture wrapping mode in Irrlicht is ETC_REPEAT.
- driver->draw2DImage(bgtexture,
- core::rect<s32>(0, 0, destsize.X, destsize.Y),
- core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
- NULL, NULL, true);
+ static video::ITexture* getMenuTexture(const std::string &tname,
+ video::IVideoDriver* driver, const SubgameSpec *spec)
+ {
+ if(spec){
+ std::string path;
+ // eg. minetest_menu_background.png (for texture packs)
+ std::string pack_tname = spec->id + "_menu_" + tname + ".png";
+ path = getTexturePath(pack_tname);
+ if(path != "")
+ return driver->getTexture(path.c_str());
+ // eg. games/minetest_game/menu/background.png
+ path = getImagePath(spec->path + DIR_DELIM + "menu" + DIR_DELIM + tname + ".png");
+ if(path != "")
+ return driver->getTexture(path.c_str());
+ } else {
+ std::string path;
+ // eg. menu_background.png
+ std::string pack_tname = "menu_" + tname + ".png";
+ path = getTexturePath(pack_tname);
+ if(path != "")
+ return driver->getTexture(path.c_str());
}
+ return NULL;
}
-}
-//Draw the footer at the bottom of the window
-void drawMenuFooter(video::IVideoDriver* driver, bool clouds) {
- core::dimension2d<u32> screensize = driver->getScreenSize();
- std::string path = getTexturePath(clouds ?
- "menufooter_clouds.png" : "menufooter.png");
- if (path[0]) {
- static const video::ITexture *footertexture =
- driver->getTexture(path.c_str());
-
- if (footertexture) {
- f32 mult = (((f32)screensize.Width)) /
- ((f32)footertexture->getOriginalSize().Width);
-
- v2s32 footersize(((f32)footertexture->getOriginalSize().Width) * mult,
- ((f32)footertexture->getOriginalSize().Height) * mult);
-
- // Don't draw the footer if there isn't enough room
- s32 free_space = (((s32)screensize.Height)-320)/2;
- if (free_space > footersize.Y) {
- core::rect<s32> rect(0,0,footersize.X,footersize.Y);
- rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
- rect -= v2s32(footersize.X/2, 0);
-
- driver->draw2DImage(footertexture, rect,
- core::rect<s32>(core::position2d<s32>(0,0),
- core::dimension2di(footertexture->getSize())),
- NULL, NULL, true);
- }
+ void update(video::IVideoDriver* driver, const SubgameSpec *spec, int tab)
+ {
+ if(tab == TAB_SINGLEPLAYER){
+ if(spec->id == current_gameid)
+ return;
+ current_gameid = spec->id;
+ global_textures = false;
+ background = getMenuTexture("background", driver, spec);
+ overlay = getMenuTexture("overlay", driver, spec);
+ header = getMenuTexture("header", driver, spec);
+ footer = getMenuTexture("footer", driver, spec);
+ } else {
+ if(global_textures)
+ return;
+ current_gameid = "";
+ global_textures = true;
+ background = getMenuTexture("background", driver, NULL);
+ overlay = getMenuTexture("overlay", driver, NULL);
+ header = getMenuTexture("header", driver, NULL);
+ footer = getMenuTexture("footer", driver, NULL);
}
}
+};
+
+void drawMenuBackground(video::IVideoDriver* driver, const MenuTextures &menutextures)
+{
+ v2u32 screensize = driver->getScreenSize();
+ video::ITexture *texture = menutextures.background;
+
+ /* If no texture, draw background of solid color */
+ if(!texture){
+ video::SColor color(255,80,58,37);
+ core::rect<s32> rect(0, 0, screensize.X, screensize.Y);
+ driver->draw2DRectangle(color, rect, NULL);
+ return;
+ }
+
+ /* Draw background texture */
+ v2u32 sourcesize = texture->getSize();
+ driver->draw2DImage(texture,
+ core::rect<s32>(0, 0, screensize.X, screensize.Y),
+ core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+ NULL, NULL, true);
+}
+
+void drawMenuOverlay(video::IVideoDriver* driver, const MenuTextures &menutextures)
+{
+ v2u32 screensize = driver->getScreenSize();
+ video::ITexture *texture = menutextures.overlay;
+
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
+
+ /* Draw overlay texture */
+ v2u32 sourcesize = texture->getSize();
+ driver->draw2DImage(texture,
+ core::rect<s32>(0, 0, screensize.X, screensize.Y),
+ core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+ NULL, NULL, true);
}
-// Draw the Header over the main menu
-void drawMenuHeader(video::IVideoDriver* driver) {
+void drawMenuHeader(video::IVideoDriver* driver, const MenuTextures &menutextures)
+{
core::dimension2d<u32> screensize = driver->getScreenSize();
+ video::ITexture *texture = menutextures.header;
- std::string path = getTexturePath("menuheader.png");
- if (path[0]) {
- static const video::ITexture *splashtexture =
- driver->getTexture(path.c_str());
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
- if(splashtexture) {
- f32 mult = (((f32)screensize.Width / 2)) /
- ((f32)splashtexture->getOriginalSize().Width);
+ f32 mult = (((f32)screensize.Width / 2)) /
+ ((f32)texture->getOriginalSize().Width);
- v2s32 splashsize(((f32)splashtexture->getOriginalSize().Width) * mult,
- ((f32)splashtexture->getOriginalSize().Height) * mult);
+ v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult,
+ ((f32)texture->getOriginalSize().Height) * mult);
- // Don't draw the header is there isn't enough room
- s32 free_space = (((s32)screensize.Height)-320)/2;
- if (free_space > splashsize.Y) {
- core::rect<s32> splashrect(0, 0, splashsize.X, splashsize.Y);
- splashrect += v2s32((screensize.Width/2)-(splashsize.X/2),
- ((free_space/2)-splashsize.Y/2)+10);
+ // Don't draw the header is there isn't enough room
+ s32 free_space = (((s32)screensize.Height)-320)/2;
+ if (free_space > splashsize.Y) {
+ core::rect<s32> splashrect(0, 0, splashsize.X, splashsize.Y);
+ splashrect += v2s32((screensize.Width/2)-(splashsize.X/2),
+ ((free_space/2)-splashsize.Y/2)+10);
- video::SColor bgcolor(255,50,50,50);
+ video::SColor bgcolor(255,50,50,50);
- driver->draw2DImage(splashtexture, splashrect,
- core::rect<s32>(core::position2d<s32>(0,0),
- core::dimension2di(splashtexture->getSize())),
- NULL, NULL, true);
- }
- }
+ driver->draw2DImage(texture, splashrect,
+ core::rect<s32>(core::position2d<s32>(0,0),
+ core::dimension2di(texture->getSize())),
+ NULL, NULL, true);
}
}
-// Draw the Splash over the clouds and under the main menu
-void drawMenuSplash(video::IVideoDriver* driver) {
+void drawMenuFooter(video::IVideoDriver* driver, const MenuTextures &menutextures)
+{
core::dimension2d<u32> screensize = driver->getScreenSize();
- std::string path = getTexturePath("menusplash.png");
- if (path[0]) {
- static const video::ITexture *splashtexture =
- driver->getTexture(path.c_str());
+ video::ITexture *texture = menutextures.footer;
+
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
+
+ f32 mult = (((f32)screensize.Width)) /
+ ((f32)texture->getOriginalSize().Width);
- if(splashtexture) {
- core::rect<s32> splashrect(0, 0, screensize.Width, screensize.Height);
+ v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult,
+ ((f32)texture->getOriginalSize().Height) * mult);
- video::SColor bgcolor(255,50,50,50);
+ // Don't draw the footer if there isn't enough room
+ s32 free_space = (((s32)screensize.Height)-320)/2;
+ if (free_space > footersize.Y) {
+ core::rect<s32> rect(0,0,footersize.X,footersize.Y);
+ rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
+ rect -= v2s32(footersize.X/2, 0);
+
+ driver->draw2DImage(texture, rect,
+ core::rect<s32>(core::position2d<s32>(0,0),
+ core::dimension2di(texture->getSize())),
+ NULL, NULL, true);
+ }
+}
- driver->draw2DImage(splashtexture, splashrect,
- core::rect<s32>(core::position2d<s32>(0,0),
- core::dimension2di(splashtexture->getSize())),
- NULL, NULL, true);
+static const SubgameSpec* getMenuGame(const MainMenuData &menudata)
+{
+ for(size_t i=0; i<menudata.games.size(); i++){
+ if(menudata.games[i].id == menudata.selected_game){
+ return &menudata.games[i];
}
}
+ return NULL;
}
-#endif
+#endif // !SERVER
// These are defined global so that they're not optimized too much.
// Can't change them to volatile.
@@ -900,6 +1007,8 @@ int main(int argc, char *argv[])
allowed_options.insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING,
_("Set gameid (\"--gameid list\" prints available ones)"))));
#ifndef SERVER
+ allowed_options.insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG,
+ _("Show available video modes"))));
allowed_options.insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG,
_("Run speed tests"))));
allowed_options.insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING,
@@ -999,7 +1108,7 @@ int main(int argc, char *argv[])
print_worldspecs(worldspecs, dstream);
return 0;
}
-
+
// Print startup message
infostream<<PROJECT_NAME<<
" "<<_("with")<<" SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
@@ -1379,11 +1488,64 @@ int main(int argc, char *argv[])
}
/*
- Create device and exit if creation failed
+ List video modes if requested
*/
MyEventReceiver receiver;
+ if(cmd_args.getFlag("videomodes")){
+ IrrlichtDevice *nulldevice;
+
+ SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
+ params.DriverType = video::EDT_NULL;
+ params.WindowSize = core::dimension2d<u32>(640, 480);
+ params.Bits = 24;
+ params.AntiAlias = fsaa;
+ params.Fullscreen = false;
+ params.Stencilbuffer = false;
+ params.Vsync = vsync;
+ params.EventReceiver = &receiver;
+
+ nulldevice = createDeviceEx(params);
+
+ if(nulldevice == 0)
+ return 1;
+
+ dstream<<_("Available video modes (WxHxD):")<<std::endl;
+
+ video::IVideoModeList *videomode_list =
+ nulldevice->getVideoModeList();
+
+ if(videomode_list == 0){
+ nulldevice->drop();
+ return 1;
+ }
+
+ s32 videomode_count = videomode_list->getVideoModeCount();
+ core::dimension2d<u32> videomode_res;
+ s32 videomode_depth;
+ for (s32 i = 0; i < videomode_count; ++i){
+ videomode_res = videomode_list->getVideoModeResolution(i);
+ videomode_depth = videomode_list->getVideoModeDepth(i);
+ dstream<<videomode_res.Width<<"x"<<videomode_res.Height
+ <<"x"<<videomode_depth<<std::endl;
+ }
+
+ dstream<<_("Active video mode (WxHxD):")<<std::endl;
+ videomode_res = videomode_list->getDesktopResolution();
+ videomode_depth = videomode_list->getDesktopDepth();
+ dstream<<videomode_res.Width<<"x"<<videomode_res.Height
+ <<"x"<<videomode_depth<<std::endl;
+
+ nulldevice->drop();
+
+ return 0;
+ }
+
+ /*
+ Create device and exit if creation failed
+ */
+
IrrlichtDevice *device;
SIrrlichtCreationParameters params = SIrrlichtCreationParameters();
@@ -1426,6 +1588,7 @@ int main(int argc, char *argv[])
{
dstream<<"Running speed tests"<<std::endl;
SpeedTests();
+ device->drop();
return 0;
}
@@ -1477,6 +1640,19 @@ int main(int argc, char *argv[])
skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255,96,134,49));
#endif
+
+ // Create the menu clouds
+ if (!g_menucloudsmgr)
+ g_menucloudsmgr = smgr->createNewSceneManager();
+ if (!g_menuclouds)
+ g_menuclouds = new Clouds(g_menucloudsmgr->getRootSceneNode(),
+ g_menucloudsmgr, -1, rand(), 100);
+ g_menuclouds->update(v2f(0, 0), video::SColor(255,200,200,255));
+ scene::ICameraSceneNode* camera;
+ camera = g_menucloudsmgr->addCameraSceneNode(0,
+ v3f(0,0,0), v3f(0, 60, 100));
+ camera->setFarValue(10000);
+
/*
GUI stuff
*/
@@ -1558,6 +1734,10 @@ int main(int argc, char *argv[])
menudata.selected_tab = g_settings->getS32("selected_mainmenu_tab");
if(g_settings->exists("selected_serverlist"))
menudata.selected_serverlist = g_settings->getS32("selected_serverlist");
+ if(g_settings->exists("selected_mainmenu_game")){
+ menudata.selected_game = g_settings->get("selected_mainmenu_game");
+ menudata.selected_game_name = findSubgame(menudata.selected_game).name;
+ }
menudata.address = narrow_to_wide(address);
menudata.name = narrow_to_wide(playername);
menudata.port = narrow_to_wide(itos(port));
@@ -1611,6 +1791,17 @@ int main(int argc, char *argv[])
}
// Copy worldspecs to menu
menudata.worlds = worldspecs;
+ // Get game listing
+ menudata.games = getAvailableGames();
+ // If selected game doesn't exist, take first from list
+ if(findSubgame(menudata.selected_game).id == "" &&
+ !menudata.games.empty()){
+ menudata.selected_game = menudata.games[0].id;
+ }
+ const SubgameSpec *menugame = getMenuGame(menudata);
+
+ MenuTextures menutextures;
+ menutextures.update(driver, menugame, menudata.selected_tab);
if(skip_main_menu == false)
{
@@ -1623,7 +1814,7 @@ int main(int argc, char *argv[])
break;
driver->beginScene(true, true,
video::SColor(255,128,128,128));
- drawMenuBackground(driver);
+ drawMenuBackground(driver, menutextures);
guienv->drawAll();
driver->endScene();
// On some computers framerate doesn't seem to be
@@ -1637,22 +1828,6 @@ int main(int argc, char *argv[])
&g_menumgr, &menudata, g_gamecallback);
menu->allowFocusRemoval(true);
- // Clouds for the main menu
- bool cloud_menu_background = false;
- Clouds *clouds = NULL;
- if (g_settings->getBool("menu_clouds")) {
- cloud_menu_background = true;
- clouds = new Clouds(smgr->getRootSceneNode(),
- smgr, -1, rand(), 100);
- clouds->update(v2f(0, 0), video::SColor(255,200,200,255));
-
- // A camera to see the clouds
- scene::ICameraSceneNode* camera;
- camera = smgr->addCameraSceneNode(0,
- v3f(0,0,0), v3f(0, 60, 100));
- camera->setFarValue(10000);
- }
-
if(error_message != L"")
{
verbosestream<<"error_message = "
@@ -1668,6 +1843,16 @@ int main(int argc, char *argv[])
// Time is in milliseconds, for clouds
u32 lasttime = device->getTimer()->getTime();
+ MenuMusicFetcher soundfetcher;
+ ISoundManager *sound = NULL;
+ sound = createOpenALSoundManager(&soundfetcher);
+ if(!sound)
+ sound = &dummySoundManager;
+ SimpleSoundSpec spec;
+ spec.name = "main_menu";
+ spec.gain = 1;
+ s32 handle = sound->playSound(spec, true);
+
infostream<<"Created main menu"<<std::endl;
while(device->run() && kill == false)
@@ -1675,8 +1860,25 @@ int main(int argc, char *argv[])
if(menu->getStatus() == true)
break;
+ // Game can be selected in the menu
+ menugame = getMenuGame(menudata);
+ menutextures.update(driver, menugame, menu->getTab());
+ // Clouds for the main menu
+ bool cloud_menu_background = g_settings->getBool("menu_clouds");
+ if(menugame){
+ // If game has regular background and no overlay, don't use clouds
+ if(cloud_menu_background && menutextures.background &&
+ !menutextures.overlay){
+ cloud_menu_background = false;
+ }
+ // If game game has overlay and no regular background, always draw clouds
+ else if(menutextures.overlay && !menutextures.background){
+ cloud_menu_background = true;
+ }
+ }
+
// Time calc for the clouds
- f32 dtime; // in seconds
+ f32 dtime=0; // in seconds
if (cloud_menu_background) {
u32 time = device->getTimer()->getTime();
if(time > lasttime)
@@ -1691,15 +1893,16 @@ int main(int argc, char *argv[])
if (cloud_menu_background) {
// *3 otherwise the clouds would move very slowly
- clouds->step(dtime*3);
- clouds->render();
- smgr->drawAll();
- drawMenuSplash(driver);
- drawMenuFooter(driver, true);
- drawMenuHeader(driver);
+ g_menuclouds->step(dtime*3);
+ g_menuclouds->render();
+ g_menucloudsmgr->drawAll();
+ drawMenuOverlay(driver, menutextures);
+ drawMenuHeader(driver, menutextures);
+ drawMenuFooter(driver, menutextures);
} else {
- drawMenuBackground(driver);
- drawMenuFooter(driver, false);
+ drawMenuBackground(driver, menutextures);
+ drawMenuHeader(driver, menutextures);
+ drawMenuFooter(driver, menutextures);
}
guienv->drawAll();
@@ -1731,14 +1934,15 @@ int main(int argc, char *argv[])
sleep_ms(25);
}
}
-
+ sound->stopSound(handle);
+ if(sound != &dummySoundManager){
+ delete sound;
+ sound = NULL;
+ }
+
infostream<<"Dropping main menu"<<std::endl;
menu->drop();
- if (cloud_menu_background) {
- clouds->drop();
- smgr->clear();
- }
}
playername = wide_to_narrow(menudata.name);
@@ -1755,6 +1959,7 @@ int main(int argc, char *argv[])
// Save settings
g_settings->setS32("selected_mainmenu_tab", menudata.selected_tab);
g_settings->setS32("selected_serverlist", menudata.selected_serverlist);
+ g_settings->set("selected_mainmenu_game", menudata.selected_game);
g_settings->set("new_style_leaves", itos(menudata.fancy_trees));
g_settings->set("smooth_lighting", itos(menudata.smooth_lighting));
g_settings->set("enable_3d_clouds", itos(menudata.clouds_3d));
@@ -1832,6 +2037,7 @@ int main(int argc, char *argv[])
continue;
}
g_settings->set("selected_world_path", path);
+ g_settings->set("selected_mainmenu_game", menudata.create_world_gameid);
continue;
}
@@ -1897,6 +2103,7 @@ int main(int argc, char *argv[])
gamespec,
simple_singleplayer_mode
);
+ smgr->clear();
} //try
catch(con::PeerNotFoundException &e)
@@ -1927,6 +2134,10 @@ int main(int argc, char *argv[])
}
} // Menu-game loop
+
+ g_menuclouds->drop();
+ g_menucloudsmgr->drop();
+
delete input;
/*
@@ -1934,6 +2145,8 @@ int main(int argc, char *argv[])
*/
device->drop();
+ delete font;
+
#endif // !SERVER
// Update configuration file
diff --git a/src/main.h b/src/main.h
index daa8c70d2..df67a6348 100644
--- a/src/main.h
+++ b/src/main.h
@@ -28,6 +28,14 @@ extern Settings *g_settings;
class Profiler;
extern Profiler *g_profiler;
+// Menu clouds
+class Clouds;
+extern Clouds *g_menuclouds;
+
+// Scene manager used for menu clouds
+namespace irr{namespace scene{class ISceneManager;}}
+extern irr::scene::ISceneManager *g_menucloudsmgr;
+
// Debug streams
#include <fstream>
diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp
index f8a0b5f06..0f83e863c 100644
--- a/src/mapblock_mesh.cpp
+++ b/src/mapblock_mesh.cpp
@@ -1338,12 +1338,20 @@ void MeshCollector::append(const TileSpec &tile,
const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices)
{
+ if(numIndices > 65535)
+ {
+ dstream<<"FIXME: MeshCollector::append() called with numIndices="<<numIndices<<" (limit 65535)"<<std::endl;
+ return;
+ }
+
PreMeshBuffer *p = NULL;
for(u32 i=0; i<prebuffers.size(); i++)
{
PreMeshBuffer &pp = prebuffers[i];
if(pp.tile != tile)
continue;
+ if(pp.indices.size() + numIndices > 65535)
+ continue;
p = &pp;
break;
@@ -1361,11 +1369,6 @@ void MeshCollector::append(const TileSpec &tile,
for(u32 i=0; i<numIndices; i++)
{
u32 j = indices[i] + vertex_count;
- if(j > 65535)
- {
- dstream<<"FIXME: Meshbuffer ran out of indices"<<std::endl;
- // NOTE: Fix is to just add an another MeshBuffer
- }
p->indices.push_back(j);
}
for(u32 i=0; i<numVertices; i++)
diff --git a/src/mesh.cpp b/src/mesh.cpp
index da0dbe442..fd35a3a06 100644
--- a/src/mesh.cpp
+++ b/src/mesh.cpp
@@ -284,7 +284,6 @@ scene::IAnimatedMesh* createExtrudedMesh(video::ITexture *texture,
if (img2 != NULL)
{
img1->copyTo(img2);
- img1->drop();
mesh = extrudeARGB(size.Width, size.Height, (u8*) img2->lock());
img2->unlock();
diff --git a/src/mods.cpp b/src/mods.cpp
index 6a7ab79aa..64c319992 100644
--- a/src/mods.cpp
+++ b/src/mods.cpp
@@ -24,9 +24,74 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "subgame.h"
#include "settings.h"
#include "strfnd.h"
+#include <cctype>
-std::map<std::string, ModSpec> getModsInPath(std::string path)
+static bool parseDependsLine(std::istream &is,
+ std::string &dep, std::set<char> &symbols)
{
+ std::getline(is, dep);
+ dep = trim(dep);
+ symbols.clear();
+ size_t pos = dep.size();
+ while(pos > 0 && !string_allowed(dep.substr(pos-1, 1), MODNAME_ALLOWED_CHARS)){
+ // last character is a symbol, not part of the modname
+ symbols.insert(dep[pos-1]);
+ --pos;
+ }
+ dep = trim(dep.substr(0, pos));
+ return dep != "";
+}
+
+void parseModContents(ModSpec &spec)
+{
+ // NOTE: this function works in mutual recursion with getModsInPath
+
+ spec.depends.clear();
+ spec.optdepends.clear();
+ spec.is_modpack = false;
+ spec.modpack_content.clear();
+
+ // Handle modpacks (defined by containing modpack.txt)
+ std::ifstream modpack_is((spec.path+DIR_DELIM+"modpack.txt").c_str());
+ if(modpack_is.good()){ //a modpack, recursively get the mods in it
+ modpack_is.close(); // We don't actually need the file
+ spec.is_modpack = true;
+ spec.modpack_content = getModsInPath(spec.path, true);
+
+ // modpacks have no dependencies; they are defined and
+ // tracked separately for each mod in the modpack
+ }
+ else{ // not a modpack, parse the dependencies
+ std::ifstream is((spec.path+DIR_DELIM+"depends.txt").c_str());
+ while(is.good()){
+ std::string dep;
+ std::set<char> symbols;
+ if(parseDependsLine(is, dep, symbols)){
+ if(symbols.count('?') != 0){
+ spec.optdepends.insert(dep);
+ }
+ else{
+ spec.depends.insert(dep);
+ }
+ }
+ }
+
+ // FIXME: optdepends.txt is deprecated
+ // remove this code at some point in the future
+ std::ifstream is2((spec.path+DIR_DELIM+"optdepends.txt").c_str());
+ while(is2.good()){
+ std::string dep;
+ std::set<char> symbols;
+ if(parseDependsLine(is2, dep, symbols))
+ spec.optdepends.insert(dep);
+ }
+ }
+}
+
+std::map<std::string, ModSpec> getModsInPath(std::string path, bool part_of_modpack)
+{
+ // NOTE: this function works in mutual recursion with parseModContents
+
std::map<std::string, ModSpec> result;
std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
for(u32 j=0; j<dirlist.size(); j++){
@@ -39,36 +104,32 @@ std::map<std::string, ModSpec> getModsInPath(std::string path)
continue;
std::string modpath = path + DIR_DELIM + modname;
- // Handle modpacks (defined by containing modpack.txt)
- std::ifstream modpack_is((modpath+DIR_DELIM+"modpack.txt").c_str(),
- std::ios_base::binary);
- if(modpack_is.good()) //a modpack, recursively get the mods in it
- {
- modpack_is.close(); // We don't actually need the file
- ModSpec spec(modname,modpath);
- spec.modpack_content = getModsInPath(modpath);
- spec.is_modpack = true;
- result.insert(std::make_pair(modname,spec));
- }
- else // not a modpack, add the modspec
- {
- std::set<std::string> depends;
- std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(),
- std::ios_base::binary);
- while(is.good())
- {
- std::string dep;
- std::getline(is, dep);
- dep = trim(dep);
- if(dep != "")
- depends.insert(dep);
- }
+ ModSpec spec(modname, modpath);
+ spec.part_of_modpack = part_of_modpack;
+ parseModContents(spec);
+ result.insert(std::make_pair(modname, spec));
+ }
+ return result;
+}
- ModSpec spec(modname, modpath, depends);
- result.insert(std::make_pair(modname,spec));
+ModSpec findCommonMod(const std::string &modname)
+{
+ // Try to find in {$user,$share}/games/common/$modname
+ std::vector<std::string> find_paths;
+ find_paths.push_back(porting::path_user + DIR_DELIM + "games" +
+ DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
+ find_paths.push_back(porting::path_share + DIR_DELIM + "games" +
+ DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
+ for(u32 i=0; i<find_paths.size(); i++){
+ const std::string &try_path = find_paths[i];
+ if(fs::PathExists(try_path)){
+ ModSpec spec(modname, try_path);
+ parseModContents(spec);
+ return spec;
}
}
- return result;
+ // Failed to find mod
+ return ModSpec();
}
std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods)
@@ -109,109 +170,18 @@ std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
}
else //not a modpack
{
- // infostream << "inserting mod " << mod.name << std::endl;
result.push_back(mod);
}
}
return result;
}
-std::vector<ModSpec> filterMods(std::vector<ModSpec> mods,
- std::set<std::string> exclude_mod_names)
-{
- std::vector<ModSpec> result;
- for(std::vector<ModSpec>::iterator it = mods.begin();
- it != mods.end(); ++it)
- {
- ModSpec& mod = *it;
- if(exclude_mod_names.count(mod.name) == 0)
- result.push_back(mod);
- }
- return result;
-}
-
-void ModConfiguration::addModsInPathFiltered(std::string path, std::set<std::string> exclude_mods)
-{
- addMods(filterMods(flattenMods(getModsInPath(path)),exclude_mods));
-}
-
-
-void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
-{
- // Step 1: remove mods in sorted_mods from unmet dependencies
- // of new_mods. new mods without unmet dependencies are
- // temporarily stored in satisfied_mods
- std::vector<ModSpec> satisfied_mods;
- for(std::vector<ModSpec>::iterator it = m_sorted_mods.begin();
- it != m_sorted_mods.end(); ++it)
- {
- ModSpec mod = *it;
- for(std::vector<ModSpec>::iterator it_new = new_mods.begin();
- it_new != new_mods.end(); ++it_new)
- {
- ModSpec& mod_new = *it_new;
- //infostream << "erasing dependency " << mod.name << " from " << mod_new.name << std::endl;
- mod_new.unsatisfied_depends.erase(mod.name);
- }
- }
-
- // split new mods into satisfied and unsatisfied
- for(std::vector<ModSpec>::iterator it = new_mods.begin();
- it != new_mods.end(); ++it)
- {
- ModSpec mod_new = *it;
- if(mod_new.unsatisfied_depends.empty())
- satisfied_mods.push_back(mod_new);
- else
- m_unsatisfied_mods.push_back(mod_new);
- }
-
- // Step 2: mods without unmet dependencies can be appended to
- // the sorted list.
- while(!satisfied_mods.empty())
- {
- ModSpec mod = satisfied_mods.back();
- m_sorted_mods.push_back(mod);
- satisfied_mods.pop_back();
- for(std::list<ModSpec>::iterator it = m_unsatisfied_mods.begin();
- it != m_unsatisfied_mods.end(); )
- {
- ModSpec& mod2 = *it;
- mod2.unsatisfied_depends.erase(mod.name);
- if(mod2.unsatisfied_depends.empty())
- {
- satisfied_mods.push_back(mod2);
- it = m_unsatisfied_mods.erase(it);
- }
- else
- ++it;
- }
- }
-}
-
-// If failed, returned modspec has name==""
-static ModSpec findCommonMod(const std::string &modname)
-{
- // Try to find in {$user,$share}/games/common/$modname
- std::vector<std::string> find_paths;
- find_paths.push_back(porting::path_user + DIR_DELIM + "games" +
- DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
- find_paths.push_back(porting::path_share + DIR_DELIM + "games" +
- DIR_DELIM + "common" + DIR_DELIM + "mods" + DIR_DELIM + modname);
- for(u32 i=0; i<find_paths.size(); i++){
- const std::string &try_path = find_paths[i];
- if(fs::PathExists(try_path))
- return ModSpec(modname, try_path);
- }
- // Failed to find mod
- return ModSpec();
-}
-
ModConfiguration::ModConfiguration(std::string worldpath)
{
SubgameSpec gamespec = findWorldSubgame(worldpath);
- // Add common mods without dependency handling
+ // Add common mods
+ std::map<std::string, ModSpec> common_mods;
std::vector<std::string> inexistent_common_mods;
Settings gameconf;
if(getGameConfig(gamespec.path, gameconf)){
@@ -225,7 +195,7 @@ ModConfiguration::ModConfiguration(std::string worldpath)
if(spec.name.empty())
inexistent_common_mods.push_back(modname);
else
- m_sorted_mods.push_back(spec);
+ common_mods.insert(std::make_pair(modname, spec));
}
}
}
@@ -238,10 +208,11 @@ ModConfiguration::ModConfiguration(std::string worldpath)
s += " could not be found.";
throw ModError(s);
}
+ addMods(flattenMods(common_mods));
- // Add all world mods and all game mods
- addModsInPath(worldpath + DIR_DELIM + "worldmods");
+ // Add all game mods and all world mods
addModsInPath(gamespec.gamemods_path);
+ addModsInPath(worldpath + DIR_DELIM + "worldmods");
// check world.mt file for mods explicitely declared to be
// loaded or not by a load_mod_<modname> = ... line.
@@ -264,7 +235,155 @@ ModConfiguration::ModConfiguration(std::string worldpath)
}
}
- for(std::set<std::string>::const_iterator i = gamespec.addon_mods_paths.begin();
- i != gamespec.addon_mods_paths.end(); ++i)
- addModsInPathFiltered((*i),exclude_mod_names);
+ // Collect all mods in gamespec.addon_mods_paths,
+ // excluding those in the set exclude_mod_names
+ std::vector<ModSpec> addon_mods;
+ for(std::set<std::string>::const_iterator it_path = gamespec.addon_mods_paths.begin();
+ it_path != gamespec.addon_mods_paths.end(); ++it_path)
+ {
+ std::vector<ModSpec> addon_mods_in_path = flattenMods(getModsInPath(*it_path));
+ for(std::vector<ModSpec>::iterator it = addon_mods_in_path.begin();
+ it != addon_mods_in_path.end(); ++it)
+ {
+ ModSpec& mod = *it;
+ if(exclude_mod_names.count(mod.name) == 0)
+ addon_mods.push_back(mod);
+ }
+ }
+
+ addMods(addon_mods);
+
+ // report on name conflicts
+ if(!m_name_conflicts.empty()){
+ std::string s = "Unresolved name conflicts for mods ";
+ for(std::set<std::string>::const_iterator it = m_name_conflicts.begin();
+ it != m_name_conflicts.end(); ++it)
+ {
+ if(it != m_name_conflicts.begin()) s += ", ";
+ s += std::string("\"") + (*it) + "\"";
+ }
+ s += ".";
+ throw ModError(s);
+ }
+
+ // get the mods in order
+ resolveDependencies();
+}
+
+void ModConfiguration::addModsInPath(std::string path)
+{
+ addMods(flattenMods(getModsInPath(path)));
+}
+
+void ModConfiguration::addMods(std::vector<ModSpec> new_mods)
+{
+ // Maintain a map of all existing m_unsatisfied_mods.
+ // Keys are mod names and values are indices into m_unsatisfied_mods.
+ std::map<std::string, u32> existing_mods;
+ for(u32 i = 0; i < m_unsatisfied_mods.size(); ++i){
+ existing_mods[m_unsatisfied_mods[i].name] = i;
+ }
+
+ // Add new mods
+ for(int want_from_modpack = 1; want_from_modpack >= 0; --want_from_modpack){
+ // First iteration:
+ // Add all the mods that come from modpacks
+ // Second iteration:
+ // Add all the mods that didn't come from modpacks
+
+ std::set<std::string> seen_this_iteration;
+
+ for(std::vector<ModSpec>::const_iterator it = new_mods.begin();
+ it != new_mods.end(); ++it){
+ const ModSpec &mod = *it;
+ if(mod.part_of_modpack != want_from_modpack)
+ continue;
+ if(existing_mods.count(mod.name) == 0){
+ // GOOD CASE: completely new mod.
+ m_unsatisfied_mods.push_back(mod);
+ existing_mods[mod.name] = m_unsatisfied_mods.size() - 1;
+ }
+ else if(seen_this_iteration.count(mod.name) == 0){
+ // BAD CASE: name conflict in different levels.
+ u32 oldindex = existing_mods[mod.name];
+ const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
+ errorstream<<"WARNING: Mod name conflict detected: \""
+ <<mod.name<<"\""<<std::endl
+ <<"Will not load: "<<oldmod.path<<std::endl
+ <<"Overridden by: "<<mod.path<<std::endl;
+ m_unsatisfied_mods[oldindex] = mod;
+
+ // If there was a "VERY BAD CASE" name conflict
+ // in an earlier level, ignore it.
+ m_name_conflicts.erase(mod.name);
+ }
+ else{
+ // VERY BAD CASE: name conflict in the same level.
+ u32 oldindex = existing_mods[mod.name];
+ const ModSpec &oldmod = m_unsatisfied_mods[oldindex];
+ errorstream<<"WARNING: Mod name conflict detected: \""
+ <<mod.name<<"\""<<std::endl
+ <<"Will not load: "<<oldmod.path<<std::endl
+ <<"Will not load: "<<mod.path<<std::endl;
+ m_unsatisfied_mods[oldindex] = mod;
+ m_name_conflicts.insert(mod.name);
+ }
+ seen_this_iteration.insert(mod.name);
+ }
+ }
+}
+
+void ModConfiguration::resolveDependencies()
+{
+ // Step 1: Compile a list of the mod names we're working with
+ std::set<std::string> modnames;
+ for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
+ it != m_unsatisfied_mods.end(); ++it){
+ modnames.insert((*it).name);
+ }
+
+ // Step 2: get dependencies (including optional dependencies)
+ // of each mod, split mods into satisfied and unsatisfied
+ std::list<ModSpec> satisfied;
+ std::list<ModSpec> unsatisfied;
+ for(std::vector<ModSpec>::iterator it = m_unsatisfied_mods.begin();
+ it != m_unsatisfied_mods.end(); ++it){
+ ModSpec mod = *it;
+ mod.unsatisfied_depends = mod.depends;
+ // check which optional dependencies actually exist
+ for(std::set<std::string>::iterator it_optdep = mod.optdepends.begin();
+ it_optdep != mod.optdepends.end(); ++it_optdep){
+ std::string optdep = *it_optdep;
+ if(modnames.count(optdep) != 0)
+ mod.unsatisfied_depends.insert(optdep);
+ }
+ // if a mod has no depends it is initially satisfied
+ if(mod.unsatisfied_depends.empty())
+ satisfied.push_back(mod);
+ else
+ unsatisfied.push_back(mod);
+ }
+
+ // Step 3: mods without unmet dependencies can be appended to
+ // the sorted list.
+ while(!satisfied.empty()){
+ ModSpec mod = satisfied.back();
+ m_sorted_mods.push_back(mod);
+ satisfied.pop_back();
+ for(std::list<ModSpec>::iterator it = unsatisfied.begin();
+ it != unsatisfied.end(); ){
+ ModSpec& mod2 = *it;
+ mod2.unsatisfied_depends.erase(mod.name);
+ if(mod2.unsatisfied_depends.empty()){
+ satisfied.push_back(mod2);
+ it = unsatisfied.erase(it);
+ }
+ else{
+ ++it;
+ }
+ }
+ }
+
+ // Step 4: write back list of unsatisfied mods
+ m_unsatisfied_mods.assign(unsatisfied.begin(), unsatisfied.end());
}
diff --git a/src/mods.h b/src/mods.h
index 32bcfb471..a8100fcfd 100644
--- a/src/mods.h
+++ b/src/mods.h
@@ -30,6 +30,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <exception>
#include <list>
+#define MODNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_"
+
class ModError : public std::exception
{
public:
@@ -53,23 +55,32 @@ struct ModSpec
std::string path;
//if normal mod:
std::set<std::string> depends;
+ std::set<std::string> optdepends;
std::set<std::string> unsatisfied_depends;
+ bool part_of_modpack;
bool is_modpack;
// if modpack:
std::map<std::string,ModSpec> modpack_content;
- ModSpec(const std::string name_="", const std::string path_="",
- const std::set<std::string> depends_=std::set<std::string>()):
+ ModSpec(const std::string name_="", const std::string path_=""):
name(name_),
path(path_),
- depends(depends_),
- unsatisfied_depends(depends_),
- is_modpack(false),
- modpack_content()
+ depends(),
+ optdepends(),
+ unsatisfied_depends(),
+ part_of_modpack(false),
+ is_modpack(false),
+ modpack_content()
{}
};
-std::map<std::string,ModSpec> getModsInPath(std::string path);
+// Retrieves depends, optdepends, is_modpack and modpack_content
+void parseModContents(ModSpec &mod);
+
+std::map<std::string,ModSpec> getModsInPath(std::string path, bool part_of_modpack = false);
+
+// If failed, returned modspec has name==""
+ModSpec findCommonMod(const std::string &modname);
// expands modpack contents, but does not replace them.
std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods);
@@ -77,10 +88,6 @@ std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mod
// replaces modpack Modspecs with their content
std::vector<ModSpec> flattenMods(std::map<std::string,ModSpec> mods);
-// removes Mods mentioned in exclude_mod_names
-std::vector<ModSpec> filterMods(std::vector<ModSpec> mods,
- std::set<std::string> exclude_mod_names);
-
// a ModConfiguration is a subset of installed mods, expected to have
// all dependencies fullfilled, so it can be used as a list of mods to
// load when the game starts.
@@ -89,26 +96,13 @@ class ModConfiguration
public:
ModConfiguration():
m_unsatisfied_mods(),
- m_sorted_mods()
+ m_sorted_mods(),
+ m_name_conflicts()
{}
ModConfiguration(std::string worldpath);
- // adds all mods in the given path. used for games, modpacks
- // and world-specific mods (worldmods-folders)
- void addModsInPath(std::string path)
- {
- addMods(flattenMods(getModsInPath(path)));
- }
-
- // adds all mods in the given path whose name does not appear
- // in the exclude_mods set.
- void addModsInPathFiltered(std::string path, std::set<std::string> exclude_mods);
-
- // adds all mods in the set.
- void addMods(std::vector<ModSpec> mods);
-
// checks if all dependencies are fullfilled.
bool isConsistent()
{
@@ -120,17 +114,27 @@ public:
return m_sorted_mods;
}
- std::list<ModSpec> getUnsatisfiedMods()
+ std::vector<ModSpec> getUnsatisfiedMods()
{
return m_unsatisfied_mods;
}
private:
+ // adds all mods in the given path. used for games, modpacks
+ // and world-specific mods (worldmods-folders)
+ void addModsInPath(std::string path);
- // mods with unmet dependencies. This is a list and not a
- // vector because we want easy removal of elements at every
- // position.
- std::list<ModSpec> m_unsatisfied_mods;
+ // adds all mods in the set.
+ void addMods(std::vector<ModSpec> new_mods);
+
+ // move mods from m_unsatisfied_mods to m_sorted_mods
+ // in an order that satisfies dependencies
+ void resolveDependencies();
+
+ // mods with unmet dependencies. Before dependencies are resolved,
+ // this is where all mods are stored. Afterwards this contains
+ // only the ones with really unsatisfied dependencies.
+ std::vector<ModSpec> m_unsatisfied_mods;
// list of mods sorted such that they can be loaded in the
// given order with all dependencies being fullfilled. I.e.,
@@ -138,6 +142,16 @@ private:
// appear earlier in the vector.
std::vector<ModSpec> m_sorted_mods;
+ // set of mod names for which an unresolved name conflict
+ // exists. A name conflict happens when two or more mods
+ // at the same level have the same name but different paths.
+ // Levels (mods in higher levels override mods in lower levels):
+ // 1. common mod in modpack; 2. common mod;
+ // 3. game mod in modpack; 4. game mod;
+ // 5. world mod in modpack; 6. world mod;
+ // 7. addon mod in modpack; 8. addon mod.
+ std::set<std::string> m_name_conflicts;
+
};
#endif
diff --git a/src/scriptapi.cpp b/src/scriptapi.cpp
index f0fe1950e..26759a059 100644
--- a/src/scriptapi.cpp
+++ b/src/scriptapi.cpp
@@ -76,8 +76,7 @@ bool scriptapi_loadmod(lua_State *L, const std::string &scriptpath,
{
ModNameStorer modnamestorer(L, modname);
- if(!string_allowed(modname, "abcdefghijklmnopqrstuvwxyz"
- "0123456789_")){
+ if(!string_allowed(modname, MODNAME_ALLOWED_CHARS)){
errorstream<<"Error loading mod \""<<modname
<<"\": modname does not follow naming conventions: "
<<"Only chararacters [a-z0-9_] are allowed."<<std::endl;
diff --git a/src/server.cpp b/src/server.cpp
index d5e505190..40a4f8a02 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -707,11 +707,11 @@ Server::Server(
ModConfiguration modconf(m_path_world);
m_mods = modconf.getMods();
- std::list<ModSpec> unsatisfied_mods = modconf.getUnsatisfiedMods();
+ std::vector<ModSpec> unsatisfied_mods = modconf.getUnsatisfiedMods();
// complain about mods with unsatisfied dependencies
if(!modconf.isConsistent())
{
- for(std::list<ModSpec>::iterator it = unsatisfied_mods.begin();
+ for(std::vector<ModSpec>::iterator it = unsatisfied_mods.begin();
it != unsatisfied_mods.end(); ++it)
{
ModSpec mod = *it;
@@ -745,7 +745,7 @@ Server::Server(
for(std::vector<ModSpec>::iterator it = m_mods.begin();
it != m_mods.end(); ++it)
load_mod_names.erase((*it).name);
- for(std::list<ModSpec>::iterator it = unsatisfied_mods.begin();
+ for(std::vector<ModSpec>::iterator it = unsatisfied_mods.begin();
it != unsatisfied_mods.end(); ++it)
load_mod_names.erase((*it).name);
if(!load_mod_names.empty())
@@ -2980,12 +2980,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
playersao->setWieldedItem(item);
}
- // If item has node placement prediction, always send the above
- // node to make sure the client knows what exactly happened
+ // If item has node placement prediction, always send the
+ // blocks to make sure the client knows what exactly happened
if(item.getDefinition(m_itemdef).node_placement_prediction != ""){
RemoteClient *client = getClient(peer_id);
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
client->SetBlockNotSent(blockpos);
+ v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
+ if(blockpos2 != blockpos){
+ client->SetBlockNotSent(blockpos2);
+ }
}
} // action == 3
@@ -5073,6 +5077,9 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id)
getPlayerEffectivePrivs(player->getName()),
isSingleplayer());
+ /* Clean up old HUD elements from previous sessions */
+ player->hud.clear();
+
/* Add object to environment */
m_env->addActiveObject(playersao);
diff --git a/src/serverlist.cpp b/src/serverlist.cpp
index 7053436d0..ea5a616c2 100644
--- a/src/serverlist.cpp
+++ b/src/serverlist.cpp
@@ -53,7 +53,7 @@ std::vector<ServerListSpec> getLocal()
std::string liststring;
if(fs::PathExists(path))
{
- std::ifstream istream(path.c_str(), std::ios::binary);
+ std::ifstream istream(path.c_str());
if(istream.is_open())
{
std::ostringstream ostream;
diff --git a/src/shader.cpp b/src/shader.cpp
index 62b7c99a9..9cef7f353 100644
--- a/src/shader.cpp
+++ b/src/shader.cpp
@@ -4,16 +4,16 @@ Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Kahrl <kahrl@gmx.net>
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
-the Free Software Foundation; either version 2 of the License, or
+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 General Public License for more details.
+GNU Lesser General Public License for more details.
-You should have received a copy of the GNU General Public License along
+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.
*/
diff --git a/src/shader.h b/src/shader.h
index a62569602..579cef635 100644
--- a/src/shader.h
+++ b/src/shader.h
@@ -4,16 +4,16 @@ Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Kahrl <kahrl@gmx.net>
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
-the Free Software Foundation; either version 2 of the License, or
+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 General Public License for more details.
+GNU Lesser General Public License for more details.
-You should have received a copy of the GNU General Public License along
+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.
*/
diff --git a/src/subgame.cpp b/src/subgame.cpp
index 19ad4e636..cdb546619 100644
--- a/src/subgame.cpp
+++ b/src/subgame.cpp
@@ -22,6 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "filesys.h"
#include "settings.h"
#include "log.h"
+#ifndef SERVER
+#include "tile.h" // getImagePath
+#endif
#include "util/string.h"
bool getGameMinetestConfig(const std::string &game_path, Settings &conf)
@@ -94,7 +97,12 @@ SubgameSpec findSubgame(const std::string &id)
std::string game_name = getGameName(game_path);
if(game_name == "")
game_name = id;
- return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name);
+ std::string menuicon_path;
+#ifndef SERVER
+ menuicon_path = getImagePath(game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png");
+#endif
+ return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name,
+ menuicon_path);
}
SubgameSpec findWorldSubgame(const std::string &world_path)
diff --git a/src/subgame.h b/src/subgame.h
index b120b7542..4b15faa8d 100644
--- a/src/subgame.h
+++ b/src/subgame.h
@@ -35,17 +35,20 @@ struct SubgameSpec
std::string gamemods_path; //path to mods of the game
std::set<std::string> addon_mods_paths; //paths to addon mods for this game
std::string name;
+ std::string menuicon_path;
SubgameSpec(const std::string &id_="",
const std::string &path_="",
const std::string &gamemods_path_="",
const std::set<std::string> &addon_mods_paths_=std::set<std::string>(),
- const std::string &name_=""):
+ const std::string &name_="",
+ const std::string &menuicon_path_=""):
id(id_),
path(path_),
gamemods_path(gamemods_path_),
addon_mods_paths(addon_mods_paths_),
- name(name_)
+ name(name_),
+ menuicon_path(menuicon_path_)
{}
bool isValid() const
diff --git a/src/tile.cpp b/src/tile.cpp
index 39f47962e..5f25e123b 100644
--- a/src/tile.cpp
+++ b/src/tile.cpp
@@ -77,7 +77,7 @@ static bool replace_ext(std::string &path, const char *ext)
If failed, return "".
*/
-static std::string getImagePath(std::string path)
+std::string getImagePath(std::string path)
{
// A NULL-ended list of possible image extensions
const char *extensions[] = {
diff --git a/src/tile.h b/src/tile.h
index ea5c4be54..531a93172 100644
--- a/src/tile.h
+++ b/src/tile.h
@@ -35,6 +35,17 @@ class IGameDef;
*/
/*
+ Find out the full path of an image by trying different filename
+ extensions.
+
+ If failed, return "".
+
+ TODO: Should probably be moved out from here, because things needing
+ this function do not need anything else from this header
+*/
+std::string getImagePath(std::string path);
+
+/*
Gets the path to a texture by first checking if the texture exists
in texture_path and if not, using the data path.