/*
Minetest-c55
Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
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
(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.
You should have received a copy of the GNU 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 "common_irrlicht.h"
#include "game.h"
#include "client.h"
#include "server.h"
#include "guiPauseMenu.h"
#include "guiPasswordChange.h"
#include "guiInventoryMenu.h"
#include "guiTextInputMenu.h"
#include "materials.h"
#include "config.h"
#include "clouds.h"
#include "keycode.h"
#include "farmesh.h"
#include "mapblock.h"
/*
TODO: Move content-aware stuff to separate file by adding properties
and virtual interfaces
*/
#include "content_mapnode.h"
#include "content_nodemeta.h"
/*
Setting this to 1 enables a special camera mode that forces
the renderers to think that the camera statically points from
the starting place to a static direction.
This allows one to move around with the player and see what
is actually drawn behind solid things and behind the player.
*/
#define FIELD_OF_VIEW_TEST 0
MapDrawControl draw_control;
// Chat data
struct ChatLine
{
ChatLine():
age(0.0)
{
}
ChatLine(const std::wstring &a_text):
age(0.0),
text(a_text)
{
}
float age;
std::wstring text;
};
/*
Inventory stuff
*/
// Inventory actions from the menu are buffered here before sending
Queue<InventoryAction*> inventory_action_queue;
// This is a copy of the inventory that the client's environment has
Inventory local_inventory;
u16 g_selected_item = 0;
/*
Text input system
*/
struct TextDestSign : public TextDest
{
TextDestSign(v3s16 blockpos, s16 id, Client *client)
{
m_blockpos = blockpos;
m_id = id;
m_client = client;
}
void gotText(std::wstring text)
{
std::string ntext = wide_to_narrow(text);
dstream<<"Changing text of a sign object: "
<<ntext<<std::endl;
m_client->sendSignText(m_blockpos, m_id, ntext);
}
v3s16 m_blockpos;
s16 m_id;
Client *m_client;
};
struct TextDestChat : public TextDest
{
TextDestChat(Client *client)
{
m_client = client;
}
void gotText(std::wstring text)
{
// Discard empty line
if(text == L"")
return;
// Parse command (server command starts with "/#")
if(text[0] == L'/' && text[1] != L'#')
{
std::wstring reply = L"Local: ";
reply += L"Local commands not yet supported. "
L"Server prefix is \"/#\".";
m_client->addChatMessage(reply);
return;
}
// Send to others
m_client->sendChatMessage(text);
// Show locally
m_client->addChatMessage(text);
}
Client *m_client;
};
struct TextDestSignNode : public TextDest
{
TextDestSignNode(v3s16 p, Client *client)
{
m_p = p;
m_client = client;
}
void gotText(std::wstring text)
{
std::string ntext = wide_to_narrow(text);
dstream<<"Changing text of a sign node: "
<<ntext<<std::endl;
m_client->sendSignNodeText(m_p, ntext);
}
v3s16 m_p;
Client *m_client;
};
/*
Render distance feedback loop
*/
void updateViewingRange(f32 frametime_in, Client *client)
{
if(draw_control.range_all == true)
return;
static f32 added_frametime = 0;
static s16 added_frames = 0;
added_frametime += frametime_in;
added_frames += 1;
// Actually this counter kind of sucks because frametime is busytime
static f32 counter = 0;
counter -= frametime_in;
if(counter > 0)
return;
//counter = 0.1;
counter = 0.2;
/*dstream<<__FUNCTION_NAME
<<": Collected "<<added_frames<<" frames, total of "
<<added_frametime<<"s."<<std::endl;*/
/*dstream<<"draw_control.blocks_drawn="
<<draw_control.blocks_drawn
<<", draw_control.blocks_would_have_drawn="
<<draw_control.blocks_would_have_drawn
<<std::endl;*/
float range_min = g_settings.getS16("viewing_range_nodes_min");
float range_max = g_settings.getS16("viewing_range_nodes_max");
// Limit minimum to keep the feedback loop stable
if(range_min < 5)
range_min = 5;
draw_control.wanted_min_range = range_min;
//draw_control.wanted_max_blocks = (1.5*draw_control.blocks_drawn)+1;
draw_control.wanted_max_blocks = (1.5*draw_control.blocks_would_have_drawn)+1;
if(draw_control.wanted_max_blocks < 10)
draw_control.wanted_max_blocks = 10;
float block_draw_ratio = 1.0;
if(draw_control.blocks_would_have_drawn != 0)
{
block_draw_ratio = (float)draw_control.blocks_drawn
/ (float)draw_control.blocks_would_have_drawn;
}
// Calculate the average frametime in the case that all wanted
// blocks had been drawn
f32 frametime = added_frametime / added_frames / block_draw_ratio;
added_frametime = 0.0;
added_frames = 0;
float wanted_fps = g_settings.getFloat("wanted_fps");
float wanted_frametime = 1.0 / wanted_fps;
f32 wanted_frametime_change = wanted_frametime - frametime;
//dstream<<"wanted_frametime_change="<<wanted_frametime_change<<std::endl;
// If needed frametime change is small, just return
if(fabs(wanted_frametime_change) < wanted_frametime*0.4)
{
//dstream<<"ignoring small wanted_frametime_change"<<std::endl;
return;
}
float range = draw_control.wanted_range;
float new_range = range;
static s16 range_old = 0;
static f32 frametime_old = 0;
float d_range = range - range_old;
f32 d_frametime = frametime - frametime_old;
// A sane default of 30ms per 50 nodes of range
static f32 time_per_range = 30. / 50;
if(d_range != 0)
{
time_per_range = d_frametime / d_range;
}
// The minimum allowed calculated frametime-range derivative:
// Practically this sets the maximum speed of changing the range.
// The lower this value, the higher the maximum changing speed.
// A low value here results in wobbly range (0.001)
// A high value here results in slow changing range (0.0025)
// SUGG: This could be dynamically adjusted so that when
// the camera is turning, this is lower
//float min_time_per_range = 0.0015;
float min_time_per_range = 0.0010;
//float min_time_per_range = 0.05 / range;
if(time_per_range < min_time_per_range)
{
time_per_range = min_time_per_range;
//dstream<<"time_per_range="<<time_per_range<<" (min)"<<std::endl;
}
else
{
//dstream<<"time_per_range="<<time_per_range<<std::endl;
}
f32 wanted_range_change = wanted_frametime_change / time_per_range;
// Dampen the change a bit to kill oscillations
//wanted_range_change *= 0.9;
//wanted_range_change *= 0.75;
wanted_range_change *= 0.5;
//dstream<<"wanted_range_change="<<wanted_range_change<<std::endl;
// If needed range change is very small, just return
if(fabs(wanted_range_change) < 0.001)
{
//dstream<<"ignoring small wanted_range_change"<<std::endl;
return;
}
new_range += wanted_range_change;
//float new_range_unclamped = new_range;
if(new_range < range_min)
new_range = range_min;
if(new_range > range_max)
new_range = range_max;
/*dstream<<"new_range="<<new_range_unclamped
<<", clamped to "<<new_range<<std::endl;*/
draw_control.wanted_range = new_range;
range_old = new_range;
frametime_old = frametime;
}
/*
Hotbar draw routine
*/
void draw_hotbar(video::IVideoDriver *driver, gui::IGUIFont *font,
v2s32 centerlowerpos, s32 imgsize, s32 itemcount,
Inventory *inventory, s32 halfheartcount)
{
InventoryList *mainlist = inventory->getList("main");
if(mainlist == NULL)
{
dstream<<"WARNING: draw_hotbar(): mainlist == NULL"<<std::endl;
return;
}
s32 padding = imgsize/12;
//s32 height = imgsize + padding*2;
s32 width = itemcount*(imgsize+padding*2);
// Position of upper left corner of bar
v2s32 pos = centerlowerpos - v2s32(width/2, imgsize+padding*2);
// Draw background color
/*core::rect<s32> barrect(0,0,width,height);
barrect += pos;
video::SColor bgcolor(255,128,128,128);
driver->draw2DRectangle(bgcolor, barrect, NULL);*/
core::rect<s32> imgrect(0,0,imgsize,imgsize);
for(s32 i=0; i<itemcount; i++)
{
InventoryItem *item = mainlist->getItem(i);
core::rect<s32> rect = imgrect + pos
+ v2s32(padding+i*(imgsize+padding*2), padding);
if(g_selected_item == i)
{
driver->draw2DRectangle(video::SColor(255,255,0,0),
core::rect<s32>(rect.UpperLeftCorner - v2s32(1,1)*padding,
rect.LowerRightCorner + v2s32(1,1)*padding),
NULL);
}
else
{
video::SColor bgcolor2(128,0,0,0);
driver->draw2DRectangle(bgcolor2, rect, NULL);
}
if(item != NULL)
{
drawInventoryItem(driver, font, item, rect, NULL);
}
}
/*
Draw hearts
*/
{
video::ITexture *heart_texture =
driver->getTexture(getTexturePath("heart.png").c_str());
v2s32 p = pos + v2s32(0, -20);
for(s32 i=0; i<halfheartcount/2; i++)
{
const video::SColor color(255,255,255,255);
const video::SColor colors[] = {color,color,color,color};
core::rect<s32> rect(0,0,16,16);
rect += p;
driver->draw2DImage(heart_texture, rect,
core::rect<s32>(core::position2d<s32>(0,0),
core::dimension2di(heart_texture->getOriginalSize())),
NULL, colors, true);
p += v2s32(16,0);
}
if(halfheartcount % 2 == 1)
{
const video::SColor color(255,255,255,255);
const video::SColor colors[] = {color,color,color,color};
core::rect<s32> rect(0,0,16/2,16);
rect += p;
core::dimension2di srcd(heart_texture->getOriginalSize());
srcd.Width /= 2;
driver->draw2DImage(heart_texture, rect,
core::rect<s32>(core::position2d<s32>(0,0), srcd),
NULL, colors, true);
p += v2s32(16,0);
}
}
}
/*
Find what the player is pointing at
*/
void getPointedNode(Client *client, v3f player_position,
v3f camera_direction, v3f camera_position,
bool &nodefound, core::line3d<f32> shootline,
v3s16 &nodepos, v3s16 &neighbourpos,
core::aabbox3d<f32> &nodehilightbox,
f32 d)
{
f32 mindistance = BS * 1001;
v3s16 pos_i = floatToInt(player_position, BS);
/*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
<<std::endl;*/
s16 a = d;
s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
for(s16 y = ystart; y <= yend; y++)
for(s16 z = zstart; z <= zend; z++)
for(s16 x = xstart; x <= xend; x++)
{
MapNode n;
try
{
n = client->getNode(v3s16(x,y,z));
if(content_pointable(n.d) == false)
continue;
}
catch(InvalidPositionException &e)
{
continue;
}
v3s16 np(x,y,z);
v3f npf = intToFloat(np, BS);
f32 d = 0.01;
v3s16 dirs[6] = {
v3s16(0,0,1), // back
v3s16(0,1,0), // top
v3s16(1,0,0), // right
v3s16(0,0,-1), // front
v3s16(0,-1,0), // bottom
v3s16(-1,0,0), // left
};
/*
Meta-objects
*/
if(n.d == CONTENT_TORCH)
{
v3s16 dir = unpackDir(n.dir);
v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
dir_f *= BS/2 - BS/6 - BS/20;
v3f cpf = npf + dir_f;
f32 distance = (cpf - camera_position).getLength();
core::aabbox3d<f32> box;
// bottom
if(dir == v3s16(0,-1,0))
{
box = core::aabbox3d<f32>(
npf - v3f(BS/6, BS/2, BS/6),
npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
);
}
// top
else if(dir == v3s16(0,1,0))
{
box = core::aabbox3d<f32>(
npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
npf + v3f(BS/6, BS/2, BS/6)
);
}
// side
else
{
box = core::aabbox3d<f32>(
cpf - v3f(BS/6, BS/3, BS/6),
cpf + v3f(BS/6, BS/3, BS/6)
);
}
if(distance < mindistance)
{
if(box.intersectsWithLine(shootline))
{
nodefound = true;
nodepos = np;
neighbourpos = np;
mindistance = distance;
nodehilightbox = box;
}
}
}
else if(n.d == CONTENT_SIGN_WALL)
{
v3s16 dir = unpackDir(n.dir);
v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
dir_f *= BS/2 - BS/6 - BS/20;
v3f cpf = npf + dir_f;
f32 distance = (cpf - camera_position).getLength();
v3f vertices[4] =
{
v3f(BS*0.42,-BS*0.35,-BS*0.4),
v3f(BS*0.49, BS*0.35, BS*0.4),
};
for(s32 i=0; i<2; i++)
{
if(dir == v3s16(1,0,0))
vertices[i].rotateXZBy(0);
if(dir == v3s16(-1,0,0))
vertices[i].rotateXZBy(180);
if(dir == v3s16(0,0,1))
vertices[i].rotateXZBy(90);
if(dir == v3s16(0,0,-1))
vertices[i].rotateXZBy(-90);
if(dir == v3s16(0,-1,0))
vertices[i].rotateXYBy(-90);
if(dir == v3s16(0,1,0))
vertices[i].rotateXYBy(90);
vertices[i] += npf;
}
core::aabbox3d<f32> box;
box = core::aabbox3d<f32>(vertices[0]);
box.addInternalPoint(vertices[1]);
if(distance < mindistance)
|