]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/hud.cpp
Moved Killaura to Lua; Added ForceField; Added Friendlist; Added ClientObjectRef...
[dragonfireclient.git] / src / client / hud.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2010-2013 blue42u, Jonathon Anderson <anderjon@umail.iu.edu>
5 Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include "client/hud.h"
23 #include <cmath>
24 #include "settings.h"
25 #include "util/numeric.h"
26 #include "log.h"
27 #include "client.h"
28 #include "inventory.h"
29 #include "shader.h"
30 #include "client/tile.h"
31 #include "localplayer.h"
32 #include "camera.h"
33 #include "porting.h"
34 #include "fontengine.h"
35 #include "guiscalingfilter.h"
36 #include "mesh.h"
37 #include "wieldmesh.h"
38 #include "client/renderingengine.h"
39
40 #ifdef HAVE_TOUCHSCREENGUI
41 #include "gui/touchscreengui.h"
42 #endif
43
44 #define OBJECT_CROSSHAIR_LINE_SIZE 8
45 #define CROSSHAIR_LINE_SIZE 10
46
47 Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player,
48                 Inventory *inventory)
49 {
50         driver            = RenderingEngine::get_video_driver();
51         this->guienv      = guienv;
52         this->client      = client;
53         this->player      = player;
54         this->inventory   = inventory;
55
56         m_hud_scaling      = g_settings->getFloat("hud_scaling");
57         m_scale_factor     = m_hud_scaling * RenderingEngine::getDisplayDensity();
58         m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
59                 RenderingEngine::getDisplayDensity() + 0.5f);
60         m_hotbar_imagesize *= m_hud_scaling;
61         m_padding = m_hotbar_imagesize / 12;
62
63         for (auto &hbar_color : hbar_colors)
64                 hbar_color = video::SColor(255, 255, 255, 255);
65
66         tsrc = client->getTextureSource();
67
68         v3f crosshair_color = g_settings->getV3F("crosshair_color");
69         u32 cross_r = rangelim(myround(crosshair_color.X), 0, 255);
70         u32 cross_g = rangelim(myround(crosshair_color.Y), 0, 255);
71         u32 cross_b = rangelim(myround(crosshair_color.Z), 0, 255);
72         u32 cross_a = rangelim(g_settings->getS32("crosshair_alpha"), 0, 255);
73         crosshair_argb = video::SColor(cross_a, cross_r, cross_g, cross_b);
74
75         v3f selectionbox_color = g_settings->getV3F("selectionbox_color");
76         u32 sbox_r = rangelim(myround(selectionbox_color.X), 0, 255);
77         u32 sbox_g = rangelim(myround(selectionbox_color.Y), 0, 255);
78         u32 sbox_b = rangelim(myround(selectionbox_color.Z), 0, 255);
79         selectionbox_argb = video::SColor(255, sbox_r, sbox_g, sbox_b);
80
81         use_crosshair_image = tsrc->isKnownSourceImage("crosshair.png");
82         use_object_crosshair_image = tsrc->isKnownSourceImage("object_crosshair.png");
83
84         m_selection_boxes.clear();
85         m_halo_boxes.clear();
86
87         std::string mode_setting = g_settings->get("node_highlighting");
88
89         if (mode_setting == "halo") {
90                 m_mode = HIGHLIGHT_HALO;
91         } else if (mode_setting == "none") {
92                 m_mode = HIGHLIGHT_NONE;
93         } else {
94                 m_mode = HIGHLIGHT_BOX;
95         }
96
97         m_selection_material.Lighting = false;
98
99         if (g_settings->getBool("enable_shaders")) {
100                 IShaderSource *shdrsrc = client->getShaderSource();
101                 u16 shader_id = shdrsrc->getShader(
102                         m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", 1, 1);
103                 m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
104         } else {
105                 m_selection_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
106         }
107
108         if (m_mode == HIGHLIGHT_BOX) {
109                 m_selection_material.Thickness =
110                         rangelim(g_settings->getS16("selectionbox_width"), 1, 5);
111         } else if (m_mode == HIGHLIGHT_HALO) {
112                 m_selection_material.setTexture(0, tsrc->getTextureForMesh("halo.png"));
113                 m_selection_material.setFlag(video::EMF_BACK_FACE_CULLING, true);
114         } else {
115                 m_selection_material.MaterialType = video::EMT_SOLID;
116         }
117 }
118
119 Hud::~Hud()
120 {
121         if (m_selection_mesh)
122                 m_selection_mesh->drop();
123 }
124
125 void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect,
126                 bool selected)
127 {
128         if (selected) {
129                 /* draw hihlighting around selected item */
130                 if (use_hotbar_selected_image) {
131                         core::rect<s32> imgrect2 = rect;
132                         imgrect2.UpperLeftCorner.X  -= (m_padding*2);
133                         imgrect2.UpperLeftCorner.Y  -= (m_padding*2);
134                         imgrect2.LowerRightCorner.X += (m_padding*2);
135                         imgrect2.LowerRightCorner.Y += (m_padding*2);
136                                 video::ITexture *texture = tsrc->getTexture(hotbar_selected_image);
137                                 core::dimension2di imgsize(texture->getOriginalSize());
138                         draw2DImageFilterScaled(driver, texture, imgrect2,
139                                         core::rect<s32>(core::position2d<s32>(0,0), imgsize),
140                                         NULL, hbar_colors, true);
141                 } else {
142                         video::SColor c_outside(255,255,0,0);
143                         //video::SColor c_outside(255,0,0,0);
144                         //video::SColor c_inside(255,192,192,192);
145                         s32 x1 = rect.UpperLeftCorner.X;
146                         s32 y1 = rect.UpperLeftCorner.Y;
147                         s32 x2 = rect.LowerRightCorner.X;
148                         s32 y2 = rect.LowerRightCorner.Y;
149                         // Black base borders
150                         driver->draw2DRectangle(c_outside,
151                                 core::rect<s32>(
152                                 v2s32(x1 - m_padding, y1 - m_padding),
153                                 v2s32(x2 + m_padding, y1)
154                                 ), NULL);
155                         driver->draw2DRectangle(c_outside,
156                                 core::rect<s32>(
157                                 v2s32(x1 - m_padding, y2),
158                                 v2s32(x2 + m_padding, y2 + m_padding)
159                                 ), NULL);
160                         driver->draw2DRectangle(c_outside,
161                                 core::rect<s32>(
162                                 v2s32(x1 - m_padding, y1),
163                                         v2s32(x1, y2)
164                                 ), NULL);
165                         driver->draw2DRectangle(c_outside,
166                                 core::rect<s32>(
167                                         v2s32(x2, y1),
168                                 v2s32(x2 + m_padding, y2)
169                                 ), NULL);
170                         /*// Light inside borders
171                         driver->draw2DRectangle(c_inside,
172                                 core::rect<s32>(
173                                         v2s32(x1 - padding/2, y1 - padding/2),
174                                         v2s32(x2 + padding/2, y1)
175                                 ), NULL);
176                         driver->draw2DRectangle(c_inside,
177                                 core::rect<s32>(
178                                         v2s32(x1 - padding/2, y2),
179                                         v2s32(x2 + padding/2, y2 + padding/2)
180                                 ), NULL);
181                         driver->draw2DRectangle(c_inside,
182                                 core::rect<s32>(
183                                         v2s32(x1 - padding/2, y1),
184                                         v2s32(x1, y2)
185                                 ), NULL);
186                         driver->draw2DRectangle(c_inside,
187                                 core::rect<s32>(
188                                         v2s32(x2, y1),
189                                         v2s32(x2 + padding/2, y2)
190                                 ), NULL);
191                         */
192                 }
193         }
194
195         video::SColor bgcolor2(128, 0, 0, 0);
196         if (!use_hotbar_image)
197                 driver->draw2DRectangle(bgcolor2, rect, NULL);
198         drawItemStack(driver, g_fontengine->getFont(), item, rect, NULL,
199                 client, selected ? IT_ROT_SELECTED : IT_ROT_NONE);
200 }
201
202 //NOTE: selectitem = 0 -> no selected; selectitem 1-based
203 void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
204                 s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction)
205 {
206 #ifdef HAVE_TOUCHSCREENGUI
207         if (g_touchscreengui && inv_offset == 0)
208                 g_touchscreengui->resetHud();
209 #endif
210
211         s32 height  = m_hotbar_imagesize + m_padding * 2;
212         s32 width   = (itemcount - inv_offset) * (m_hotbar_imagesize + m_padding * 2);
213
214         if (direction == HUD_DIR_TOP_BOTTOM || direction == HUD_DIR_BOTTOM_TOP) {
215                 s32 tmp = height;
216                 height = width;
217                 width = tmp;
218         }
219
220         // Position of upper left corner of bar
221         v2s32 pos = screen_offset * m_scale_factor;
222         pos += upperleftpos;
223
224         // Store hotbar_image in member variable, used by drawItem()
225         if (hotbar_image != player->hotbar_image) {
226                 hotbar_image = player->hotbar_image;
227                 use_hotbar_image = !hotbar_image.empty();
228         }
229
230         // Store hotbar_selected_image in member variable, used by drawItem()
231         if (hotbar_selected_image != player->hotbar_selected_image) {
232                 hotbar_selected_image = player->hotbar_selected_image;
233                 use_hotbar_selected_image = !hotbar_selected_image.empty();
234         }
235
236         // draw customized item background
237         if (use_hotbar_image) {
238                 core::rect<s32> imgrect2(-m_padding/2, -m_padding/2,
239                         width+m_padding/2, height+m_padding/2);
240                 core::rect<s32> rect2 = imgrect2 + pos;
241                 video::ITexture *texture = tsrc->getTexture(hotbar_image);
242                 core::dimension2di imgsize(texture->getOriginalSize());
243                 draw2DImageFilterScaled(driver, texture, rect2,
244                         core::rect<s32>(core::position2d<s32>(0,0), imgsize),
245                         NULL, hbar_colors, true);
246         }
247
248         // Draw items
249         core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize);
250         for (s32 i = inv_offset; i < itemcount && (size_t)i < mainlist->getSize(); i++) {
251                 s32 fullimglen = m_hotbar_imagesize + m_padding * 2;
252
253                 v2s32 steppos;
254                 switch (direction) {
255                 case HUD_DIR_RIGHT_LEFT:
256                         steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), m_padding);
257                         break;
258                 case HUD_DIR_TOP_BOTTOM:
259                         steppos = v2s32(m_padding, m_padding + (i - inv_offset) * fullimglen);
260                         break;
261                 case HUD_DIR_BOTTOM_TOP:
262                         steppos = v2s32(m_padding, -(m_padding + (i - inv_offset) * fullimglen));
263                         break;
264                 default:
265                         steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, m_padding);
266                         break;
267                 }
268
269                 drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i + 1) == selectitem);
270
271 #ifdef HAVE_TOUCHSCREENGUI
272                 if (g_touchscreengui)
273                         g_touchscreengui->registerHudItem(i, (imgrect + pos + steppos));
274 #endif
275         }
276 }
277
278 // Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false.
279 bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos)
280 {
281         v3f w_pos = e->world_pos * BS;
282         scene::ICameraSceneNode* camera =
283                 RenderingEngine::get_scene_manager()->getActiveCamera();
284         w_pos -= intToFloat(camera_offset, BS);
285         core::matrix4 trans = camera->getProjectionMatrix();
286         trans *= camera->getViewMatrix();
287         f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f };
288         trans.multiplyWith1x4Matrix(transformed_pos);
289         if (transformed_pos[3] < 0)
290                 return false;
291         f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
292                 core::reciprocal(transformed_pos[3]);
293         pos->X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5);
294         pos->Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5);
295         return true;
296 }
297
298 void Hud::drawLuaElements(const v3s16 &camera_offset)
299 {
300         u32 text_height = g_fontengine->getTextHeight();
301         irr::gui::IGUIFont* font = g_fontengine->getFont();
302
303         // Reorder elements by z_index
304         std::vector<size_t> ids;
305
306         for (size_t i = 0; i != player->maxHudId(); i++) {
307                 HudElement *e = player->getHud(i);
308                 if (!e)
309                         continue;
310
311                 auto it = ids.begin();
312                 while (it != ids.end() && player->getHud(*it)->z_index <= e->z_index)
313                         ++it;
314
315                 ids.insert(it, i);
316         }
317
318         for (size_t i : ids) {
319                 HudElement *e = player->getHud(i);
320
321                 v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5),
322                                 floor(e->pos.Y * (float) m_screensize.Y + 0.5));
323                 switch (e->type) {
324                         case HUD_ELEM_TEXT: {
325                                 irr::gui::IGUIFont *textfont = font;
326                                 unsigned int font_size = g_fontengine->getDefaultFontSize();
327
328                                 if (e->size.X > 0)
329                                         font_size *= e->size.X;
330
331                                 if (font_size != g_fontengine->getDefaultFontSize())
332                                         textfont = g_fontengine->getFont(font_size);
333
334                                 video::SColor color(255, (e->number >> 16) & 0xFF,
335                                                                                  (e->number >> 8)  & 0xFF,
336                                                                                  (e->number >> 0)  & 0xFF);
337                                 std::wstring text = unescape_translate(utf8_to_wide(e->text));
338                                 core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
339 #ifdef __ANDROID__
340                                 // The text size on Android is not proportional with the actual scaling
341                                 irr::gui::IGUIFont *font_scaled = font_size <= 3 ?
342                                         textfont : g_fontengine->getFont(font_size - 3);
343                                 if (e->offset.X < -20)
344                                         textsize = font_scaled->getDimension(text.c_str());
345 #endif
346                                 v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2),
347                                              (e->align.Y - 1.0) * (textsize.Height / 2));
348                                 core::rect<s32> size(0, 0, e->scale.X * m_scale_factor,
349                                                      text_height * e->scale.Y * m_scale_factor);
350                                 v2s32 offs(e->offset.X * m_scale_factor,
351                                            e->offset.Y * m_scale_factor);
352 #ifdef __ANDROID__
353                                 if (e->offset.X < -20)
354                                         font_scaled->draw(text.c_str(), size + pos + offset + offs, color);
355                                 else
356 #endif
357                                 {
358                                         textfont->draw(text.c_str(), size + pos + offset + offs, color);
359                                 }
360                                 break; }
361                         case HUD_ELEM_STATBAR: {
362                                 v2s32 offs(e->offset.X, e->offset.Y);
363                                 drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2,
364                                         e->number, e->item, offs, e->size);
365                                 break; }
366                         case HUD_ELEM_INVENTORY: {
367                                 InventoryList *inv = inventory->getList(e->text);
368                                 drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
369                                         inv, e->item, e->dir);
370                                 break; }
371                         case HUD_ELEM_WAYPOINT: {
372                                 if (!calculateScreenPos(camera_offset, e, &pos))
373                                         break;
374                                 v3f p_pos = player->getPosition() / BS;
375                                 pos += v2s32(e->offset.X, e->offset.Y);
376                                 video::SColor color(255, (e->number >> 16) & 0xFF,
377                                                                                  (e->number >> 8)  & 0xFF,
378                                                                                  (e->number >> 0)  & 0xFF);
379                                 std::wstring text = unescape_translate(utf8_to_wide(e->name));
380                                 const std::string &unit = e->text;
381                                 // waypoints reuse the item field to store precision, item = precision + 1
382                                 u32 item = e->item;
383                                 float precision = (item == 0) ? 10.0f : (item - 1.f);
384                                 bool draw_precision = precision > 0;
385
386                                 core::rect<s32> bounds(0, 0, font->getDimension(text.c_str()).Width, (draw_precision ? 2:1) * text_height);
387                                 pos.Y += (e->align.Y - 1.0) * bounds.getHeight() / 2;
388                                 bounds += pos;
389                                 font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0) * bounds.getWidth() / 2, 0), color);
390                                 if (draw_precision) {
391                                         std::ostringstream os;
392                                         float distance = std::floor(precision * p_pos.getDistanceFrom(e->world_pos)) / precision;
393                                         os << distance << unit;
394                                         text = unescape_translate(utf8_to_wide(os.str()));
395                                         bounds.LowerRightCorner.X = bounds.UpperLeftCorner.X + font->getDimension(text.c_str()).Width;
396                                         font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0f) * bounds.getWidth() / 2, text_height), color);
397                                 }
398                                 break; }
399                         case HUD_ELEM_IMAGE_WAYPOINT: {
400                                 if (!calculateScreenPos(camera_offset, e, &pos))
401                                         break;
402                         }
403                         case HUD_ELEM_IMAGE: {
404                                 video::ITexture *texture = tsrc->getTexture(e->text);
405                                 if (!texture)
406                                         continue;
407
408                                 const video::SColor color(255, 255, 255, 255);
409                                 const video::SColor colors[] = {color, color, color, color};
410                                 core::dimension2di imgsize(texture->getOriginalSize());
411                                 v2s32 dstsize(imgsize.Width * e->scale.X * m_scale_factor,
412                                               imgsize.Height * e->scale.Y * m_scale_factor);
413                                 if (e->scale.X < 0)
414                                         dstsize.X = m_screensize.X * (e->scale.X * -0.01);
415                                 if (e->scale.Y < 0)
416                                         dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01);
417                                 v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
418                                              (e->align.Y - 1.0) * dstsize.Y / 2);
419                                 core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
420                                 rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
421                                                              e->offset.Y * m_scale_factor);
422                                 draw2DImageFilterScaled(driver, texture, rect,
423                                         core::rect<s32>(core::position2d<s32>(0,0), imgsize),
424                                         NULL, colors, true);
425                                 break; }
426                         default:
427                                 infostream << "Hud::drawLuaElements: ignoring drawform " << e->type <<
428                                         " of hud element ID " << i << " due to unrecognized type" << std::endl;
429                 }
430         }
431 }
432
433
434 void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
435                 const std::string &texture, const std::string &bgtexture,
436                 s32 count, s32 maxcount, v2s32 offset, v2s32 size)
437 {
438         const video::SColor color(255, 255, 255, 255);
439         const video::SColor colors[] = {color, color, color, color};
440
441         video::ITexture *stat_texture = tsrc->getTexture(texture);
442         if (!stat_texture)
443                 return;
444
445         video::ITexture *stat_texture_bg = nullptr;
446         if (!bgtexture.empty()) {
447                 stat_texture_bg = tsrc->getTexture(bgtexture);
448         }
449
450         core::dimension2di srcd(stat_texture->getOriginalSize());
451         core::dimension2di dstd;
452         if (size == v2s32()) {
453                 dstd = srcd;
454                 dstd.Height *= m_scale_factor;
455                 dstd.Width  *= m_scale_factor;
456                 offset.X *= m_scale_factor;
457                 offset.Y *= m_scale_factor;
458         } else {
459                 dstd.Height = size.Y * m_scale_factor;
460                 dstd.Width  = size.X * m_scale_factor;
461                 offset.X *= m_scale_factor;
462                 offset.Y *= m_scale_factor;
463         }
464
465         v2s32 p = pos;
466         if (corner & HUD_CORNER_LOWER)
467                 p -= dstd.Height;
468
469         p += offset;
470
471         v2s32 steppos;
472         switch (drawdir) {
473                 case HUD_DIR_RIGHT_LEFT:
474                         steppos = v2s32(-1, 0);
475                         break;
476                 case HUD_DIR_TOP_BOTTOM:
477                         steppos = v2s32(0, 1);
478                         break;
479                 case HUD_DIR_BOTTOM_TOP:
480                         steppos = v2s32(0, -1);
481                         break;
482                 default:
483                         // From left to right
484                         steppos = v2s32(1, 0);
485                         break;
486         }
487
488         auto calculate_clipping_rect = [] (core::dimension2di src,
489                         v2s32 steppos) -> core::rect<s32> {
490
491                 // Create basic rectangle
492                 core::rect<s32> rect(0, 0,
493                         src.Width  - std::abs(steppos.X) * src.Width / 2,
494                         src.Height - std::abs(steppos.Y) * src.Height / 2
495                 );
496                 // Move rectangle left or down
497                 if (steppos.X == -1)
498                         rect += v2s32(src.Width / 2, 0);
499                 if (steppos.Y == -1)
500                         rect += v2s32(0, src.Height / 2);
501                 return rect;
502         };
503         // Rectangles for 1/2 the actual value to display
504         core::rect<s32> srchalfrect, dsthalfrect;
505         // Rectangles for 1/2 the "off state" texture
506         core::rect<s32> srchalfrect2, dsthalfrect2;
507
508         if (count % 2 == 1) {
509                 // Need to draw halves: Calculate rectangles
510                 srchalfrect  = calculate_clipping_rect(srcd, steppos);
511                 dsthalfrect  = calculate_clipping_rect(dstd, steppos);
512                 srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1);
513                 dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1);
514         }
515
516         steppos.X *= dstd.Width;
517         steppos.Y *= dstd.Height;
518
519         // Draw full textures
520         for (s32 i = 0; i < count / 2; i++) {
521                 core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
522                 core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
523
524                 dstrect += p;
525                 draw2DImageFilterScaled(driver, stat_texture,
526                         dstrect, srcrect, NULL, colors, true);
527                 p += steppos;
528         }
529
530         if (count % 2 == 1) {
531                 // Draw half a texture
532                 draw2DImageFilterScaled(driver, stat_texture,
533                         dsthalfrect + p, srchalfrect, NULL, colors, true);
534
535                 if (stat_texture_bg && maxcount > count) {
536                         draw2DImageFilterScaled(driver, stat_texture_bg,
537                                         dsthalfrect2 + p, srchalfrect2,
538                                         NULL, colors, true);
539                         p += steppos;
540                 }
541         }
542
543         if (stat_texture_bg && maxcount > count / 2) {
544                 // Draw "off state" textures
545                 s32 start_offset;
546                 if (count % 2 == 1)
547                         start_offset = count / 2 + 1;
548                 else
549                         start_offset = count / 2;
550                 for (s32 i = start_offset; i < maxcount / 2; i++) {
551                         core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
552                         core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
553
554                         dstrect += p;
555                         draw2DImageFilterScaled(driver, stat_texture_bg,
556                                         dstrect, srcrect,
557                                         NULL, colors, true);
558                         p += steppos;
559                 }
560
561                 if (maxcount % 2 == 1) {
562                         draw2DImageFilterScaled(driver, stat_texture_bg,
563                                         dsthalfrect + p, srchalfrect,
564                                         NULL, colors, true);
565                 }
566         }
567 }
568
569
570 void Hud::drawHotbar(u16 playeritem) {
571
572         v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y);
573
574         InventoryList *mainlist = inventory->getList("main");
575         if (mainlist == NULL) {
576                 //silently ignore this we may not be initialized completely
577                 return;
578         }
579
580         s32 hotbar_itemcount = player->hud_hotbar_itemcount;
581         s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2);
582         v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);
583
584         const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
585         if ((float) width / (float) window_size.X <=
586                         g_settings->getFloat("hud_hotbar_max_width")) {
587                 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
588                         drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0);
589                 }
590         } else {
591                 pos.X += width/4;
592
593                 v2s32 secondpos = pos;
594                 pos = pos - v2s32(0, m_hotbar_imagesize + m_padding);
595
596                 if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
597                         drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0,
598                                 mainlist, playeritem + 1, 0);
599                         drawItems(secondpos, v2s32(0, 0), hotbar_itemcount,
600                                 hotbar_itemcount / 2, mainlist, playeritem + 1, 0);
601                 }
602         }
603 }
604
605
606 void Hud::drawCrosshair()
607 {
608         if (pointing_at_object) {
609                 if (use_object_crosshair_image) {
610                         video::ITexture *object_crosshair = tsrc->getTexture("object_crosshair.png");
611                         v2u32 size  = object_crosshair->getOriginalSize();
612                         v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
613                                         m_displaycenter.Y - (size.Y / 2));
614                         driver->draw2DImage(object_crosshair, lsize,
615                                         core::rect<s32>(0, 0, size.X, size.Y),
616                                         nullptr, crosshair_argb, true);
617                 } else {
618                         driver->draw2DLine(
619                                         m_displaycenter - v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
620                                         OBJECT_CROSSHAIR_LINE_SIZE),
621                                         m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
622                                         OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
623                         driver->draw2DLine(
624                                         m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE,
625                                         -OBJECT_CROSSHAIR_LINE_SIZE),
626                                         m_displaycenter + v2s32(-OBJECT_CROSSHAIR_LINE_SIZE,
627                                         OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb);
628                 }
629
630                 return;
631         }
632
633         if (use_crosshair_image) {
634                 video::ITexture *crosshair = tsrc->getTexture("crosshair.png");
635                 v2u32 size  = crosshair->getOriginalSize();
636                 v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
637                                 m_displaycenter.Y - (size.Y / 2));
638                 driver->draw2DImage(crosshair, lsize,
639                                 core::rect<s32>(0, 0, size.X, size.Y),
640                                 nullptr, crosshair_argb, true);
641         } else {
642                 driver->draw2DLine(m_displaycenter - v2s32(CROSSHAIR_LINE_SIZE, 0),
643                                 m_displaycenter + v2s32(CROSSHAIR_LINE_SIZE, 0), crosshair_argb);
644                 driver->draw2DLine(m_displaycenter - v2s32(0, CROSSHAIR_LINE_SIZE),
645                                 m_displaycenter + v2s32(0, CROSSHAIR_LINE_SIZE), crosshair_argb);
646         }
647 }
648
649 void Hud::setSelectionPos(const v3f &pos, const v3s16 &camera_offset)
650 {
651         m_camera_offset = camera_offset;
652         m_selection_pos = pos;
653         m_selection_pos_with_offset = pos - intToFloat(camera_offset, BS);
654 }
655
656 void Hud::drawSelectionMesh()
657 {
658         if (m_mode == HIGHLIGHT_BOX) {
659                 // Draw 3D selection boxes
660                 video::SMaterial oldmaterial = driver->getMaterial2D();
661                 driver->setMaterial(m_selection_material);
662                 for (auto & selection_box : m_selection_boxes) {
663                         aabb3f box = aabb3f(
664                                 selection_box.MinEdge + m_selection_pos_with_offset,
665                                 selection_box.MaxEdge + m_selection_pos_with_offset);
666
667                         u32 r = (selectionbox_argb.getRed() *
668                                         m_selection_mesh_color.getRed() / 255);
669                         u32 g = (selectionbox_argb.getGreen() *
670                                         m_selection_mesh_color.getGreen() / 255);
671                         u32 b = (selectionbox_argb.getBlue() *
672                                         m_selection_mesh_color.getBlue() / 255);
673                         driver->draw3DBox(box, video::SColor(255, r, g, b));
674                 }
675                 driver->setMaterial(oldmaterial);
676         } else if (m_mode == HIGHLIGHT_HALO && m_selection_mesh) {
677                 // Draw selection mesh
678                 video::SMaterial oldmaterial = driver->getMaterial2D();
679                 driver->setMaterial(m_selection_material);
680                 setMeshColor(m_selection_mesh, m_selection_mesh_color);
681                 video::SColor face_color(0,
682                         MYMIN(255, m_selection_mesh_color.getRed() * 1.5),
683                         MYMIN(255, m_selection_mesh_color.getGreen() * 1.5),
684                         MYMIN(255, m_selection_mesh_color.getBlue() * 1.5));
685                 setMeshColorByNormal(m_selection_mesh, m_selected_face_normal,
686                         face_color);
687                 scene::IMesh* mesh = cloneMesh(m_selection_mesh);
688                 translateMesh(mesh, m_selection_pos_with_offset);
689                 u32 mc = m_selection_mesh->getMeshBufferCount();
690                 for (u32 i = 0; i < mc; i++) {
691                         scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
692                         driver->drawMeshBuffer(buf);
693                 }
694                 mesh->drop();
695                 driver->setMaterial(oldmaterial);
696         }
697 }
698
699 void Hud::updateSelectionMesh(const v3s16 &camera_offset)
700 {
701         m_camera_offset = camera_offset;
702         if (m_mode != HIGHLIGHT_HALO)
703                 return;
704
705         if (m_selection_mesh) {
706                 m_selection_mesh->drop();
707                 m_selection_mesh = NULL;
708         }
709
710         if (m_selection_boxes.empty()) {
711                 // No pointed object
712                 return;
713         }
714
715         // New pointed object, create new mesh.
716
717         // Texture UV coordinates for selection boxes
718         static f32 texture_uv[24] = {
719                 0,0,1,1,
720                 0,0,1,1,
721                 0,0,1,1,
722                 0,0,1,1,
723                 0,0,1,1,
724                 0,0,1,1
725         };
726
727         // Use single halo box instead of multiple overlapping boxes.
728         // Temporary solution - problem can be solved with multiple
729         // rendering targets, or some method to remove inner surfaces.
730         // Thats because of halo transparency.
731
732         aabb3f halo_box(100.0, 100.0, 100.0, -100.0, -100.0, -100.0);
733         m_halo_boxes.clear();
734
735         for (const auto &selection_box : m_selection_boxes) {
736                 halo_box.addInternalBox(selection_box);
737         }
738
739         m_halo_boxes.push_back(halo_box);
740         m_selection_mesh = convertNodeboxesToMesh(
741                 m_halo_boxes, texture_uv, 0.5);
742 }
743
744 void Hud::resizeHotbar() {
745         const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
746
747         if (m_screensize != window_size) {
748                 m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE *
749                         RenderingEngine::getDisplayDensity() + 0.5);
750                 m_hotbar_imagesize *= m_hud_scaling;
751                 m_padding = m_hotbar_imagesize / 12;
752                 m_screensize = window_size;
753                 m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2);
754         }
755 }
756
757 struct MeshTimeInfo {
758         u64 time;
759         scene::IMesh *mesh = nullptr;
760 };
761
762 void drawItemStack(
763                 video::IVideoDriver *driver,
764                 gui::IGUIFont *font,
765                 const ItemStack &item,
766                 const core::rect<s32> &rect,
767                 const core::rect<s32> *clip,
768                 Client *client,
769                 ItemRotationKind rotation_kind,
770                 const v3s16 &angle,
771                 const v3s16 &rotation_speed)
772 {
773         static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
774
775         if (item.empty()) {
776                 if (rotation_kind < IT_ROT_NONE && rotation_kind != IT_ROT_OTHER) {
777                         rotation_time_infos[rotation_kind].mesh = NULL;
778                 }
779                 return;
780         }
781
782         const ItemDefinition &def = item.getDefinition(client->idef());
783         ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);
784
785         if (imesh && imesh->mesh) {
786                 scene::IMesh *mesh = imesh->mesh;
787                 driver->clearZBuffer();
788                 s32 delta = 0;
789                 if (rotation_kind < IT_ROT_NONE) {
790                         MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
791                         if (mesh != ti.mesh && rotation_kind != IT_ROT_OTHER) {
792                                 ti.mesh = mesh;
793                                 ti.time = porting::getTimeMs();
794                         } else {
795                                 delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
796                         }
797                 }
798                 core::rect<s32> oldViewPort = driver->getViewPort();
799                 core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
800                 core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
801                 core::rect<s32> viewrect = rect;
802                 if (clip)
803                         viewrect.clipAgainst(*clip);
804
805                 core::matrix4 ProjMatrix;
806                 ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
807
808                 core::matrix4 ViewMatrix;
809                 ViewMatrix.buildProjectionMatrixOrthoLH(
810                         2.0f * viewrect.getWidth() / rect.getWidth(),
811                         2.0f * viewrect.getHeight() / rect.getHeight(),
812                         -1.0f,
813                         100.0f);
814                 ViewMatrix.setTranslation(core::vector3df(
815                         1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
816                                         viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
817                                         viewrect.getWidth(),
818                         1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
819                                         rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
820                                         viewrect.getHeight(),
821                         0.0f));
822
823                 driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
824                 driver->setTransform(video::ETS_VIEW, ViewMatrix);
825
826                 core::matrix4 matrix;
827                 matrix.makeIdentity();
828
829                 static thread_local bool enable_animations =
830                         g_settings->getBool("inventory_items_animations");
831
832                 if (enable_animations) {
833                         float timer_f = (float) delta / 5000.f;
834                         matrix.setRotationDegrees(v3f(
835                                 angle.X + rotation_speed.X * 3.60f * timer_f,
836                                 angle.Y + rotation_speed.Y * 3.60f * timer_f,
837                                 angle.Z + rotation_speed.Z * 3.60f * timer_f)
838                         );
839                 }
840
841                 driver->setTransform(video::ETS_WORLD, matrix);
842                 driver->setViewPort(viewrect);
843
844                 video::SColor basecolor =
845                         client->idef()->getItemstackColor(item, client);
846
847                 u32 mc = mesh->getMeshBufferCount();
848                 for (u32 j = 0; j < mc; ++j) {
849                         scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
850                         // we can modify vertices relatively fast,
851                         // because these meshes are not buffered.
852                         assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
853                         video::SColor c = basecolor;
854
855                         if (imesh->buffer_colors.size() > j) {
856                                 ItemPartColor *p = &imesh->buffer_colors[j];
857                                 if (p->override_base)
858                                         c = p->color;
859                         }
860
861                         if (imesh->needs_shading)
862                                 colorizeMeshBuffer(buf, &c);
863                         else
864                                 setMeshBufferColor(buf, c);
865
866                         video::SMaterial &material = buf->getMaterial();
867                         material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
868                         material.Lighting = false;
869                         driver->setMaterial(material);
870                         driver->drawMeshBuffer(buf);
871                 }
872
873                 driver->setTransform(video::ETS_VIEW, oldViewMat);
874                 driver->setTransform(video::ETS_PROJECTION, oldProjMat);
875                 driver->setViewPort(oldViewPort);
876
877                 // draw the inventory_overlay
878                 if (def.type == ITEM_NODE && def.inventory_image.empty() &&
879                                 !def.inventory_overlay.empty()) {
880                         ITextureSource *tsrc = client->getTextureSource();
881                         video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
882                         core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
883                         core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
884                         draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
885                 }
886         }
887
888         if (def.type == ITEM_TOOL && item.wear != 0) {
889                 // Draw a progressbar
890                 float barheight = rect.getHeight() / 16;
891                 float barpad_x = rect.getWidth() / 16;
892                 float barpad_y = rect.getHeight() / 16;
893
894                 core::rect<s32> progressrect(
895                         rect.UpperLeftCorner.X + barpad_x,
896                         rect.LowerRightCorner.Y - barpad_y - barheight,
897                         rect.LowerRightCorner.X - barpad_x,
898                         rect.LowerRightCorner.Y - barpad_y);
899
900                 // Shrink progressrect by amount of tool damage
901                 float wear = item.wear / 65535.0f;
902                 int progressmid =
903                         wear * progressrect.UpperLeftCorner.X +
904                         (1 - wear) * progressrect.LowerRightCorner.X;
905
906                 // Compute progressbar color
907                 //   wear = 0.0: green
908                 //   wear = 0.5: yellow
909                 //   wear = 1.0: red
910                 video::SColor color(255, 255, 255, 255);
911                 int wear_i = MYMIN(std::floor(wear * 600), 511);
912                 wear_i = MYMIN(wear_i + 10, 511);
913
914                 if (wear_i <= 255)
915                         color.set(255, wear_i, 255, 0);
916                 else
917                         color.set(255, 255, 511 - wear_i, 0);
918
919                 core::rect<s32> progressrect2 = progressrect;
920                 progressrect2.LowerRightCorner.X = progressmid;
921                 driver->draw2DRectangle(color, progressrect2, clip);
922
923                 color = video::SColor(255, 0, 0, 0);
924                 progressrect2 = progressrect;
925                 progressrect2.UpperLeftCorner.X = progressmid;
926                 driver->draw2DRectangle(color, progressrect2, clip);
927         }
928
929         if (font != NULL && item.count >= 2) {
930                 // Get the item count as a string
931                 std::string text = itos(item.count);
932                 v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
933                 v2s32 sdim(dim.X, dim.Y);
934
935                 core::rect<s32> rect2(
936                         /*rect.UpperLeftCorner,
937                         core::dimension2d<u32>(rect.getWidth(), 15)*/
938                         rect.LowerRightCorner - sdim,
939                         sdim
940                 );
941
942                 video::SColor bgcolor(128, 0, 0, 0);
943                 driver->draw2DRectangle(bgcolor, rect2, clip);
944
945                 video::SColor color(255, 255, 255, 255);
946                 font->draw(text.c_str(), rect2, color, false, false, clip);
947         }
948 }
949
950 void drawItemStack(
951                 video::IVideoDriver *driver,
952                 gui::IGUIFont *font,
953                 const ItemStack &item,
954                 const core::rect<s32> &rect,
955                 const core::rect<s32> *clip,
956                 Client *client,
957                 ItemRotationKind rotation_kind)
958 {
959         drawItemStack(driver, font, item, rect, clip, client, rotation_kind,
960                 v3s16(0, 0, 0), v3s16(0, 100, 0));
961 }