LOCAL_SRC_FILES := \
jni/src/ban.cpp \
jni/src/camera.cpp \
- jni/src/cavegen.cpp \
+ jni/src/mapgen/cavegen.cpp \
jni/src/chat.cpp \
jni/src/client.cpp \
jni/src/clientenvironment.cpp \
jni/src/content_sao.cpp \
jni/src/convert_json.cpp \
jni/src/craftdef.cpp \
- jni/src/database-dummy.cpp \
- jni/src/database-files.cpp \
- jni/src/database-sqlite3.cpp \
- jni/src/database.cpp \
+ jni/src/database/database-dummy.cpp \
+ jni/src/database/database-files.cpp \
+ jni/src/database/database-sqlite3.cpp \
+ jni/src/database/database.cpp \
jni/src/debug.cpp \
jni/src/defaultsettings.cpp \
- jni/src/dungeongen.cpp \
+ jni/src/mapgen/dungeongen.cpp \
jni/src/emerge.cpp \
jni/src/environment.cpp \
jni/src/face_position_cache.cpp \
jni/src/game.cpp \
jni/src/genericobject.cpp \
jni/src/gettext.cpp \
- jni/src/guiChatConsole.cpp \
- jni/src/guiEditBoxWithScrollbar.cpp \
- jni/src/guiEngine.cpp \
- jni/src/guiPathSelectMenu.cpp \
- jni/src/guiFormSpecMenu.cpp \
- jni/src/guiKeyChangeMenu.cpp \
- jni/src/guiPasswordChange.cpp \
- jni/src/guiTable.cpp \
+ jni/src/gui/guiChatConsole.cpp \
+ jni/src/gui/guiEditBoxWithScrollbar.cpp \
+ jni/src/gui/guiEngine.cpp \
+ jni/src/gui/guiPathSelectMenu.cpp \
+ jni/src/gui/guiFormSpecMenu.cpp \
+ jni/src/gui/guiKeyChangeMenu.cpp \
+ jni/src/gui/guiPasswordChange.cpp \
+ jni/src/gui/guiTable.cpp \
jni/src/guiscalingfilter.cpp \
- jni/src/guiVolumeChange.cpp \
+ jni/src/gui/guiVolumeChange.cpp \
jni/src/httpfetch.cpp \
jni/src/hud.cpp \
jni/src/imagefilters.cpp \
- jni/src/intlGUIEditBox.cpp \
+ jni/src/gui/intlGUIEditBox.cpp \
jni/src/inventory.cpp \
jni/src/inventorymanager.cpp \
jni/src/itemdef.cpp \
jni/src/map_settings_manager.cpp \
jni/src/mapblock.cpp \
jni/src/mapblock_mesh.cpp \
- jni/src/mapgen.cpp \
- jni/src/mapgen_carpathian.cpp \
- jni/src/mapgen_flat.cpp \
- jni/src/mapgen_fractal.cpp \
- jni/src/mapgen_singlenode.cpp \
- jni/src/mapgen_v5.cpp \
- jni/src/mapgen_v6.cpp \
- jni/src/mapgen_v7.cpp \
- jni/src/mapgen_valleys.cpp \
+ jni/src/mapgen/mapgen.cpp \
+ jni/src/mapgen/mapgen_carpathian.cpp \
+ jni/src/mapgen/mapgen_flat.cpp \
+ jni/src/mapgen/mapgen_fractal.cpp \
+ jni/src/mapgen/mapgen_singlenode.cpp \
+ jni/src/mapgen/mapgen_v5.cpp \
+ jni/src/mapgen/mapgen_v6.cpp \
+ jni/src/mapgen/mapgen_v7.cpp \
+ jni/src/mapgen/mapgen_valleys.cpp \
jni/src/mapnode.cpp \
jni/src/mapsector.cpp \
jni/src/mesh.cpp \
jni/src/mesh_generator_thread.cpp \
jni/src/metadata.cpp \
- jni/src/mg_biome.cpp \
- jni/src/mg_decoration.cpp \
- jni/src/mg_ore.cpp \
- jni/src/mg_schematic.cpp \
+ jni/src/mapgen/mg_biome.cpp \
+ jni/src/mapgen/mg_decoration.cpp \
+ jni/src/mapgen/mg_ore.cpp \
+ jni/src/mapgen/mg_schematic.cpp \
jni/src/minimap.cpp \
jni/src/mods.cpp \
jni/src/nameidmapping.cpp \
jni/src/subgame.cpp \
jni/src/tileanimation.cpp \
jni/src/tool.cpp \
- jni/src/treegen.cpp \
+ jni/src/mapgen/treegen.cpp \
jni/src/version.cpp \
jni/src/voxel.cpp \
jni/src/voxelalgorithms.cpp \
add_subdirectory(threading)
+add_subdirectory(database)
+add_subdirectory(gui)
+add_subdirectory(mapgen)
add_subdirectory(network)
add_subdirectory(script)
add_subdirectory(unittest)
add_subdirectory(irrlicht_changes)
set(common_SRCS
+ ${database_SRCS}
+ ${mapgen_SRCS}
ban.cpp
- cavegen.cpp
chat.cpp
clientiface.cpp
collision.cpp
content_sao.cpp
convert_json.cpp
craftdef.cpp
- database-dummy.cpp
- database-files.cpp
- database-leveldb.cpp
- database-postgresql.cpp
- database-redis.cpp
- database-sqlite3.cpp
- database.cpp
debug.cpp
defaultsettings.cpp
- dungeongen.cpp
emerge.cpp
environment.cpp
face_position_cache.cpp
map.cpp
map_settings_manager.cpp
mapblock.cpp
- mapgen.cpp
- mapgen_carpathian.cpp
- mapgen_flat.cpp
- mapgen_fractal.cpp
- mapgen_singlenode.cpp
- mapgen_v5.cpp
- mapgen_v6.cpp
- mapgen_v7.cpp
- mapgen_valleys.cpp
mapnode.cpp
mapsector.cpp
metadata.cpp
- mg_biome.cpp
- mg_decoration.cpp
- mg_ore.cpp
- mg_schematic.cpp
modchannels.cpp
mods.cpp
nameidmapping.cpp
tileanimation.cpp
tool.cpp
translation.cpp
- treegen.cpp
version.cpp
voxel.cpp
voxelalgorithms.cpp
set(client_SRCS
${client_SRCS}
${common_SRCS}
+ ${gui_SRCS}
${sound_SRCS}
${client_network_SRCS}
${client_irrlicht_changes_SRCS}
filecache.cpp
fontengine.cpp
game.cpp
- guiChatConsole.cpp
- guiEditBoxWithScrollbar.cpp
- guiEngine.cpp
- guiPathSelectMenu.cpp
- guiFormSpecMenu.cpp
- guiKeyChangeMenu.cpp
- guiPasswordChange.cpp
guiscalingfilter.cpp
- guiTable.cpp
- guiVolumeChange.cpp
hud.cpp
imagefilters.cpp
- intlGUIEditBox.cpp
keycode.cpp
localplayer.cpp
main.cpp
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "util/numeric.h"
-#include "map.h"
-#include "mapgen.h"
-#include "mapgen_v5.h"
-#include "mapgen_v6.h"
-#include "mapgen_v7.h"
-#include "mg_biome.h"
-#include "cavegen.h"
-
-static NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0);
-
-
-////
-//// CavesNoiseIntersection
-////
-
-CavesNoiseIntersection::CavesNoiseIntersection(
- INodeDefManager *nodedef, BiomeManager *biomemgr, v3s16 chunksize,
- NoiseParams *np_cave1, NoiseParams *np_cave2, s32 seed, float cave_width)
-{
- assert(nodedef);
- assert(biomemgr);
-
- m_ndef = nodedef;
- m_bmgr = biomemgr;
-
- m_csize = chunksize;
- m_cave_width = cave_width;
-
- m_ystride = m_csize.X;
- m_zstride_1d = m_csize.X * (m_csize.Y + 1);
-
- // Noises are created using 1-down overgeneration
- // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for
- // re-carving the solid overtop placed for blocking sunlight
- noise_cave1 = new Noise(np_cave1, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
- noise_cave2 = new Noise(np_cave2, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
-}
-
-
-CavesNoiseIntersection::~CavesNoiseIntersection()
-{
- delete noise_cave1;
- delete noise_cave2;
-}
-
-
-void CavesNoiseIntersection::generateCaves(MMVManip *vm,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
-{
- assert(vm);
- assert(biomemap);
-
- noise_cave1->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
- noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
-
- const v3s16 &em = vm->m_area.getExtent();
- u32 index2d = 0; // Biomemap index
-
- for (s16 z = nmin.Z; z <= nmax.Z; z++)
- for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) {
- bool column_is_open = false; // Is column open to overground
- bool is_under_river = false; // Is column under river water
- bool is_under_tunnel = false; // Is tunnel or is under tunnel
- bool is_top_filler_above = false; // Is top or filler above node
- // Indexes at column top
- u32 vi = vm->m_area.index(x, nmax.Y, z);
- u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride +
- (x - nmin.X); // 3D noise index
- // Biome of column
- Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index2d]);
- u16 depth_top = biome->depth_top;
- u16 base_filler = depth_top + biome->depth_filler;
- u16 depth_riverbed = biome->depth_riverbed;
- u16 nplaced = 0;
- // Don't excavate the overgenerated stone at nmax.Y + 1,
- // this creates a 'roof' over the tunnel, preventing light in
- // tunnels at mapchunk borders when generating mapchunks upwards.
- // This 'roof' is removed when the mapchunk above is generated.
- for (s16 y = nmax.Y; y >= nmin.Y - 1; y--,
- index3d -= m_ystride,
- vm->m_area.add_y(em, vi, -1)) {
- content_t c = vm->m_data[vi].getContent();
-
- if (c == CONTENT_AIR || c == biome->c_water_top ||
- c == biome->c_water) {
- column_is_open = true;
- is_top_filler_above = false;
- continue;
- }
-
- if (c == biome->c_river_water) {
- column_is_open = true;
- is_under_river = true;
- is_top_filler_above = false;
- continue;
- }
-
- // Ground
- float d1 = contour(noise_cave1->result[index3d]);
- float d2 = contour(noise_cave2->result[index3d]);
-
- if (d1 * d2 > m_cave_width && m_ndef->get(c).is_ground_content) {
- // In tunnel and ground content, excavate
- vm->m_data[vi] = MapNode(CONTENT_AIR);
- is_under_tunnel = true;
- // If tunnel roof is top or filler, replace with stone
- if (is_top_filler_above)
- vm->m_data[vi + em.X] = MapNode(biome->c_stone);
- is_top_filler_above = false;
- } else if (column_is_open && is_under_tunnel &&
- (c == biome->c_stone || c == biome->c_filler)) {
- // Tunnel entrance floor, place biome surface nodes
- if (is_under_river) {
- if (nplaced < depth_riverbed) {
- vm->m_data[vi] = MapNode(biome->c_riverbed);
- is_top_filler_above = true;
- nplaced++;
- } else {
- // Disable top/filler placement
- column_is_open = false;
- is_under_river = false;
- is_under_tunnel = false;
- }
- } else if (nplaced < depth_top) {
- vm->m_data[vi] = MapNode(biome->c_top);
- is_top_filler_above = true;
- nplaced++;
- } else if (nplaced < base_filler) {
- vm->m_data[vi] = MapNode(biome->c_filler);
- is_top_filler_above = true;
- nplaced++;
- } else {
- // Disable top/filler placement
- column_is_open = false;
- is_under_tunnel = false;
- }
- } else {
- // Not tunnel or tunnel entrance floor
- // Check node for possible replacing with stone for tunnel roof
- if (c == biome->c_top || c == biome->c_filler)
- is_top_filler_above = true;
-
- column_is_open = false;
- }
- }
- }
-}
-
-
-////
-//// CavernsNoise
-////
-
-CavernsNoise::CavernsNoise(
- INodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern,
- s32 seed, float cavern_limit, float cavern_taper, float cavern_threshold)
-{
- assert(nodedef);
-
- m_ndef = nodedef;
-
- m_csize = chunksize;
- m_cavern_limit = cavern_limit;
- m_cavern_taper = cavern_taper;
- m_cavern_threshold = cavern_threshold;
-
- m_ystride = m_csize.X;
- m_zstride_1d = m_csize.X * (m_csize.Y + 1);
-
- // Noise is created using 1-down overgeneration
- // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for
- // re-carving the solid overtop placed for blocking sunlight
- noise_cavern = new Noise(np_cavern, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
-
- c_water_source = m_ndef->getId("mapgen_water_source");
- if (c_water_source == CONTENT_IGNORE)
- c_water_source = CONTENT_AIR;
-
- c_lava_source = m_ndef->getId("mapgen_lava_source");
- if (c_lava_source == CONTENT_IGNORE)
- c_lava_source = CONTENT_AIR;
-}
-
-
-CavernsNoise::~CavernsNoise()
-{
- delete noise_cavern;
-}
-
-
-bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax)
-{
- assert(vm);
-
- // Calculate noise
- noise_cavern->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
-
- // Cache cavern_amp values
- float *cavern_amp = new float[m_csize.Y + 1];
- u8 cavern_amp_index = 0; // Index zero at column top
- for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, cavern_amp_index++) {
- cavern_amp[cavern_amp_index] =
- MYMIN((m_cavern_limit - y) / (float)m_cavern_taper, 1.0f);
- }
-
- //// Place nodes
- bool near_cavern = false;
- const v3s16 &em = vm->m_area.getExtent();
- u32 index2d = 0;
-
- for (s16 z = nmin.Z; z <= nmax.Z; z++)
- for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) {
- // Reset cave_amp index to column top
- cavern_amp_index = 0;
- // Initial voxelmanip index at column top
- u32 vi = vm->m_area.index(x, nmax.Y, z);
- // Initial 3D noise index at column top
- u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride +
- (x - nmin.X);
- // Don't excavate the overgenerated stone at node_max.Y + 1,
- // this creates a 'roof' over the cavern, preventing light in
- // caverns at mapchunk borders when generating mapchunks upwards.
- // This 'roof' is excavated when the mapchunk above is generated.
- for (s16 y = nmax.Y; y >= nmin.Y - 1; y--,
- index3d -= m_ystride,
- vm->m_area.add_y(em, vi, -1),
- cavern_amp_index++) {
- content_t c = vm->m_data[vi].getContent();
- float n_absamp_cavern = fabs(noise_cavern->result[index3d]) *
- cavern_amp[cavern_amp_index];
- // Disable CavesRandomWalk at a safe distance from caverns
- // to avoid excessively spreading liquids in caverns.
- if (n_absamp_cavern > m_cavern_threshold - 0.1f) {
- near_cavern = true;
- if (n_absamp_cavern > m_cavern_threshold &&
- m_ndef->get(c).is_ground_content)
- vm->m_data[vi] = MapNode(CONTENT_AIR);
- }
- }
- }
-
- delete[] cavern_amp;
-
- return near_cavern;
-}
-
-
-////
-//// CavesRandomWalk
-////
-
-CavesRandomWalk::CavesRandomWalk(
- INodeDefManager *ndef,
- GenerateNotifier *gennotify,
- s32 seed,
- int water_level,
- content_t water_source,
- content_t lava_source,
- int lava_depth)
-{
- assert(ndef);
-
- this->ndef = ndef;
- this->gennotify = gennotify;
- this->seed = seed;
- this->water_level = water_level;
- this->np_caveliquids = &nparams_caveliquids;
- this->lava_depth = lava_depth;
-
- c_water_source = water_source;
- if (c_water_source == CONTENT_IGNORE)
- c_water_source = ndef->getId("mapgen_water_source");
- if (c_water_source == CONTENT_IGNORE)
- c_water_source = CONTENT_AIR;
-
- c_lava_source = lava_source;
- if (c_lava_source == CONTENT_IGNORE)
- c_lava_source = ndef->getId("mapgen_lava_source");
- if (c_lava_source == CONTENT_IGNORE)
- c_lava_source = CONTENT_AIR;
-}
-
-
-void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax,
- PseudoRandom *ps, bool is_large_cave, int max_stone_height, s16 *heightmap)
-{
- assert(vm);
- assert(ps);
-
- this->vm = vm;
- this->ps = ps;
- this->node_min = nmin;
- this->node_max = nmax;
- this->heightmap = heightmap;
- this->large_cave = is_large_cave;
-
- this->ystride = nmax.X - nmin.X + 1;
-
- // Set initial parameters from randomness
- int dswitchint = ps->range(1, 14);
- flooded = ps->range(1, 2) == 2;
-
- if (large_cave) {
- part_max_length_rs = ps->range(2, 4);
- tunnel_routepoints = ps->range(5, ps->range(15, 30));
- min_tunnel_diameter = 5;
- max_tunnel_diameter = ps->range(7, ps->range(8, 24));
- } else {
- part_max_length_rs = ps->range(2, 9);
- tunnel_routepoints = ps->range(10, ps->range(15, 30));
- min_tunnel_diameter = 2;
- max_tunnel_diameter = ps->range(2, 6);
- }
-
- large_cave_is_flat = (ps->range(0, 1) == 0);
-
- main_direction = v3f(0, 0, 0);
-
- // Allowed route area size in nodes
- ar = node_max - node_min + v3s16(1, 1, 1);
- // Area starting point in nodes
- of = node_min;
-
- // Allow a bit more
- //(this should be more than the maximum radius of the tunnel)
- const s16 insure = 10;
- s16 more = MYMAX(MAP_BLOCKSIZE - max_tunnel_diameter / 2 - insure, 1);
- ar += v3s16(1, 0, 1) * more * 2;
- of -= v3s16(1, 0, 1) * more;
-
- route_y_min = 0;
- // Allow half a diameter + 7 over stone surface
- route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7;
-
- // Limit maximum to area
- route_y_max = rangelim(route_y_max, 0, ar.Y - 1);
-
- if (large_cave) {
- s16 minpos = 0;
- if (node_min.Y < water_level && node_max.Y > water_level) {
- minpos = water_level - max_tunnel_diameter / 3 - of.Y;
- route_y_max = water_level + max_tunnel_diameter / 3 - of.Y;
- }
- route_y_min = ps->range(minpos, minpos + max_tunnel_diameter);
- route_y_min = rangelim(route_y_min, 0, route_y_max);
- }
-
- s16 route_start_y_min = route_y_min;
- s16 route_start_y_max = route_y_max;
-
- route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1);
- route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1);
-
- // Randomize starting position
- orp.Z = (float)(ps->next() % ar.Z) + 0.5f;
- orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f;
- orp.X = (float)(ps->next() % ar.X) + 0.5f;
-
- // Add generation notify begin event
- if (gennotify) {
- v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
- GenNotifyType notifytype = large_cave ?
- GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN;
- gennotify->addEvent(notifytype, abs_pos);
- }
-
- // Generate some tunnel starting from orp
- for (u16 j = 0; j < tunnel_routepoints; j++)
- makeTunnel(j % dswitchint == 0);
-
- // Add generation notify end event
- if (gennotify) {
- v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
- GenNotifyType notifytype = large_cave ?
- GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END;
- gennotify->addEvent(notifytype, abs_pos);
- }
-}
-
-
-void CavesRandomWalk::makeTunnel(bool dirswitch)
-{
- if (dirswitch && !large_cave) {
- main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10;
- main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30;
- main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10;
-
- main_direction *= (float)ps->range(0, 10) / 10;
- }
-
- // Randomize size
- s16 min_d = min_tunnel_diameter;
- s16 max_d = max_tunnel_diameter;
- rs = ps->range(min_d, max_d);
- s16 rs_part_max_length_rs = rs * part_max_length_rs;
-
- v3s16 maxlen;
- if (large_cave) {
- maxlen = v3s16(
- rs_part_max_length_rs,
- rs_part_max_length_rs / 2,
- rs_part_max_length_rs
- );
- } else {
- maxlen = v3s16(
- rs_part_max_length_rs,
- ps->range(1, rs_part_max_length_rs),
- rs_part_max_length_rs
- );
- }
-
- v3f vec;
- // Jump downward sometimes
- if (!large_cave && ps->range(0, 12) == 0) {
- vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2;
- vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y;
- vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2;
- } else {
- vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2;
- vec.Y = (float)(ps->next() % (maxlen.Y * 1)) - (float)maxlen.Y / 2;
- vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2;
- }
-
- // Do not make caves that are above ground.
- // It is only necessary to check the startpoint and endpoint.
- v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2;
- v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1;
- if (isPosAboveSurface(p1) || isPosAboveSurface(p2))
- return;
-
- vec += main_direction;
-
- v3f rp = orp + vec;
- if (rp.X < 0)
- rp.X = 0;
- else if (rp.X >= ar.X)
- rp.X = ar.X - 1;
-
- if (rp.Y < route_y_min)
- rp.Y = route_y_min;
- else if (rp.Y >= route_y_max)
- rp.Y = route_y_max - 1;
-
- if (rp.Z < 0)
- rp.Z = 0;
- else if (rp.Z >= ar.Z)
- rp.Z = ar.Z - 1;
-
- vec = rp - orp;
-
- float veclen = vec.getLength();
- if (veclen < 0.05f)
- veclen = 1.0f;
-
- // Every second section is rough
- bool randomize_xz = (ps->range(1, 2) == 1);
-
- // Carve routes
- for (float f = 0.f; f < 1.0f; f += 1.0f / veclen)
- carveRoute(vec, f, randomize_xz);
-
- orp = rp;
-}
-
-
-void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz)
-{
- MapNode airnode(CONTENT_AIR);
- MapNode waternode(c_water_source);
- MapNode lavanode(c_lava_source);
-
- v3s16 startp(orp.X, orp.Y, orp.Z);
- startp += of;
-
- float nval = NoisePerlin3D(np_caveliquids, startp.X,
- startp.Y, startp.Z, seed);
- MapNode liquidnode = (nval < 0.40f && node_max.Y < lava_depth) ?
- lavanode : waternode;
-
- v3f fp = orp + vec * f;
- fp.X += 0.1f * ps->range(-10, 10);
- fp.Z += 0.1f * ps->range(-10, 10);
- v3s16 cp(fp.X, fp.Y, fp.Z);
-
- s16 d0 = -rs / 2;
- s16 d1 = d0 + rs;
- if (randomize_xz) {
- d0 += ps->range(-1, 1);
- d1 += ps->range(-1, 1);
- }
-
- bool flat_cave_floor = !large_cave && ps->range(0, 2) == 2;
-
- for (s16 z0 = d0; z0 <= d1; z0++) {
- s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1);
- for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) {
- s16 maxabsxz = MYMAX(abs(x0), abs(z0));
-
- s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1);
-
- for (s16 y0 = -si2; y0 <= si2; y0++) {
- // Make better floors in small caves
- if (flat_cave_floor && y0 <= -rs / 2 && rs <= 7)
- continue;
-
- if (large_cave_is_flat) {
- // Make large caves not so tall
- if (rs > 7 && abs(y0) >= rs / 3)
- continue;
- }
-
- v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0);
- p += of;
-
- if (!vm->m_area.contains(p))
- continue;
-
- u32 i = vm->m_area.index(p);
- content_t c = vm->m_data[i].getContent();
- if (!ndef->get(c).is_ground_content)
- continue;
-
- if (large_cave) {
- int full_ymin = node_min.Y - MAP_BLOCKSIZE;
- int full_ymax = node_max.Y + MAP_BLOCKSIZE;
-
- if (flooded && full_ymin < water_level && full_ymax > water_level)
- vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode;
- else if (flooded && full_ymax < water_level)
- vm->m_data[i] = (p.Y < startp.Y - 4) ? liquidnode : airnode;
- else
- vm->m_data[i] = airnode;
- } else {
- if (c == CONTENT_IGNORE)
- continue;
-
- vm->m_data[i] = airnode;
- vm->m_flags[i] |= VMANIP_FLAG_CAVE;
- }
- }
- }
- }
-}
-
-
-inline bool CavesRandomWalk::isPosAboveSurface(v3s16 p)
-{
- if (heightmap != NULL &&
- p.Z >= node_min.Z && p.Z <= node_max.Z &&
- p.X >= node_min.X && p.X <= node_max.X) {
- u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X);
- if (heightmap[index] < p.Y)
- return true;
- } else if (p.Y > water_level) {
- return true;
- }
-
- return false;
-}
-
-
-////
-//// CavesV6
-////
-
-CavesV6::CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify,
- int water_level, content_t water_source, content_t lava_source)
-{
- assert(ndef);
-
- this->ndef = ndef;
- this->gennotify = gennotify;
- this->water_level = water_level;
-
- c_water_source = water_source;
- if (c_water_source == CONTENT_IGNORE)
- c_water_source = ndef->getId("mapgen_water_source");
- if (c_water_source == CONTENT_IGNORE)
- c_water_source = CONTENT_AIR;
-
- c_lava_source = lava_source;
- if (c_lava_source == CONTENT_IGNORE)
- c_lava_source = ndef->getId("mapgen_lava_source");
- if (c_lava_source == CONTENT_IGNORE)
- c_lava_source = CONTENT_AIR;
-}
-
-
-void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax,
- PseudoRandom *ps, PseudoRandom *ps2,
- bool is_large_cave, int max_stone_height, s16 *heightmap)
-{
- assert(vm);
- assert(ps);
- assert(ps2);
-
- this->vm = vm;
- this->ps = ps;
- this->ps2 = ps2;
- this->node_min = nmin;
- this->node_max = nmax;
- this->heightmap = heightmap;
- this->large_cave = is_large_cave;
-
- this->ystride = nmax.X - nmin.X + 1;
-
- // Set initial parameters from randomness
- min_tunnel_diameter = 2;
- max_tunnel_diameter = ps->range(2, 6);
- int dswitchint = ps->range(1, 14);
- if (large_cave) {
- part_max_length_rs = ps->range(2, 4);
- tunnel_routepoints = ps->range(5, ps->range(15, 30));
- min_tunnel_diameter = 5;
- max_tunnel_diameter = ps->range(7, ps->range(8, 24));
- } else {
- part_max_length_rs = ps->range(2, 9);
- tunnel_routepoints = ps->range(10, ps->range(15, 30));
- }
- large_cave_is_flat = (ps->range(0, 1) == 0);
-
- main_direction = v3f(0, 0, 0);
-
- // Allowed route area size in nodes
- ar = node_max - node_min + v3s16(1, 1, 1);
- // Area starting point in nodes
- of = node_min;
-
- // Allow a bit more
- //(this should be more than the maximum radius of the tunnel)
- const s16 max_spread_amount = MAP_BLOCKSIZE;
- const s16 insure = 10;
- s16 more = MYMAX(max_spread_amount - max_tunnel_diameter / 2 - insure, 1);
- ar += v3s16(1, 0, 1) * more * 2;
- of -= v3s16(1, 0, 1) * more;
-
- route_y_min = 0;
- // Allow half a diameter + 7 over stone surface
- route_y_max = -of.Y + max_stone_height + max_tunnel_diameter / 2 + 7;
-
- // Limit maximum to area
- route_y_max = rangelim(route_y_max, 0, ar.Y - 1);
-
- if (large_cave) {
- s16 minpos = 0;
- if (node_min.Y < water_level && node_max.Y > water_level) {
- minpos = water_level - max_tunnel_diameter / 3 - of.Y;
- route_y_max = water_level + max_tunnel_diameter / 3 - of.Y;
- }
- route_y_min = ps->range(minpos, minpos + max_tunnel_diameter);
- route_y_min = rangelim(route_y_min, 0, route_y_max);
- }
-
- s16 route_start_y_min = route_y_min;
- s16 route_start_y_max = route_y_max;
-
- route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1);
- route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1);
-
- // Randomize starting position
- orp.Z = (float)(ps->next() % ar.Z) + 0.5f;
- orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f;
- orp.X = (float)(ps->next() % ar.X) + 0.5f;
-
- // Add generation notify begin event
- if (gennotify != NULL) {
- v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
- GenNotifyType notifytype = large_cave ?
- GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN;
- gennotify->addEvent(notifytype, abs_pos);
- }
-
- // Generate some tunnel starting from orp
- for (u16 j = 0; j < tunnel_routepoints; j++)
- makeTunnel(j % dswitchint == 0);
-
- // Add generation notify end event
- if (gennotify != NULL) {
- v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
- GenNotifyType notifytype = large_cave ?
- GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END;
- gennotify->addEvent(notifytype, abs_pos);
- }
-}
-
-
-void CavesV6::makeTunnel(bool dirswitch)
-{
- if (dirswitch && !large_cave) {
- main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10;
- main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30;
- main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10;
-
- main_direction *= (float)ps->range(0, 10) / 10;
- }
-
- // Randomize size
- s16 min_d = min_tunnel_diameter;
- s16 max_d = max_tunnel_diameter;
- rs = ps->range(min_d, max_d);
- s16 rs_part_max_length_rs = rs * part_max_length_rs;
-
- v3s16 maxlen;
- if (large_cave) {
- maxlen = v3s16(
- rs_part_max_length_rs,
- rs_part_max_length_rs / 2,
- rs_part_max_length_rs
- );
- } else {
- maxlen = v3s16(
- rs_part_max_length_rs,
- ps->range(1, rs_part_max_length_rs),
- rs_part_max_length_rs
- );
- }
-
- v3f vec;
- vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2;
- vec.Y = (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2;
- vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2;
-
- // Jump downward sometimes
- if (!large_cave && ps->range(0, 12) == 0) {
- vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2;
- vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y;
- vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2;
- }
-
- // Do not make caves that are entirely above ground, to fix shadow bugs
- // caused by overgenerated large caves.
- // It is only necessary to check the startpoint and endpoint.
- v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2;
- v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1;
-
- // If startpoint and endpoint are above ground, disable placement of nodes
- // in carveRoute while still running all PseudoRandom calls to ensure caves
- // are consistent with existing worlds.
- bool tunnel_above_ground =
- p1.Y > getSurfaceFromHeightmap(p1) &&
- p2.Y > getSurfaceFromHeightmap(p2);
-
- vec += main_direction;
-
- v3f rp = orp + vec;
- if (rp.X < 0)
- rp.X = 0;
- else if (rp.X >= ar.X)
- rp.X = ar.X - 1;
-
- if (rp.Y < route_y_min)
- rp.Y = route_y_min;
- else if (rp.Y >= route_y_max)
- rp.Y = route_y_max - 1;
-
- if (rp.Z < 0)
- rp.Z = 0;
- else if (rp.Z >= ar.Z)
- rp.Z = ar.Z - 1;
-
- vec = rp - orp;
-
- float veclen = vec.getLength();
- // As odd as it sounds, veclen is *exactly* 0.0 sometimes, causing a FPE
- if (veclen < 0.05f)
- veclen = 1.0f;
-
- // Every second section is rough
- bool randomize_xz = (ps2->range(1, 2) == 1);
-
- // Carve routes
- for (float f = 0.f; f < 1.0f; f += 1.0f / veclen)
- carveRoute(vec, f, randomize_xz, tunnel_above_ground);
-
- orp = rp;
-}
-
-
-void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz,
- bool tunnel_above_ground)
-{
- MapNode airnode(CONTENT_AIR);
- MapNode waternode(c_water_source);
- MapNode lavanode(c_lava_source);
-
- v3s16 startp(orp.X, orp.Y, orp.Z);
- startp += of;
-
- v3f fp = orp + vec * f;
- fp.X += 0.1f * ps->range(-10, 10);
- fp.Z += 0.1f * ps->range(-10, 10);
- v3s16 cp(fp.X, fp.Y, fp.Z);
-
- s16 d0 = -rs / 2;
- s16 d1 = d0 + rs;
- if (randomize_xz) {
- d0 += ps->range(-1, 1);
- d1 += ps->range(-1, 1);
- }
-
- for (s16 z0 = d0; z0 <= d1; z0++) {
- s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1);
- for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) {
- if (tunnel_above_ground)
- continue;
-
- s16 maxabsxz = MYMAX(abs(x0), abs(z0));
- s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1);
- for (s16 y0 = -si2; y0 <= si2; y0++) {
- if (large_cave_is_flat) {
- // Make large caves not so tall
- if (rs > 7 && abs(y0) >= rs / 3)
- continue;
- }
-
- v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0);
- p += of;
-
- if (!vm->m_area.contains(p))
- continue;
-
- u32 i = vm->m_area.index(p);
- content_t c = vm->m_data[i].getContent();
- if (!ndef->get(c).is_ground_content)
- continue;
-
- if (large_cave) {
- int full_ymin = node_min.Y - MAP_BLOCKSIZE;
- int full_ymax = node_max.Y + MAP_BLOCKSIZE;
-
- if (full_ymin < water_level && full_ymax > water_level) {
- vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode;
- } else if (full_ymax < water_level) {
- vm->m_data[i] = (p.Y < startp.Y - 2) ? lavanode : airnode;
- } else {
- vm->m_data[i] = airnode;
- }
- } else {
- if (c == CONTENT_IGNORE || c == CONTENT_AIR)
- continue;
-
- vm->m_data[i] = airnode;
- vm->m_flags[i] |= VMANIP_FLAG_CAVE;
- }
- }
- }
- }
-}
-
-
-inline s16 CavesV6::getSurfaceFromHeightmap(v3s16 p)
-{
- if (heightmap != NULL &&
- p.Z >= node_min.Z && p.Z <= node_max.Z &&
- p.X >= node_min.X && p.X <= node_max.X) {
- u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X);
- return heightmap[index];
- }
-
- return water_level;
-
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1
-
-class GenerateNotifier;
-
-/*
- CavesNoiseIntersection is a cave digging algorithm that carves smooth,
- web-like, continuous tunnels at points where the density of the intersection
- between two separate 3d noises is above a certain value. This value,
- cave_width, can be modified to set the effective width of these tunnels.
-
- This algorithm is relatively heavyweight, taking ~80ms to generate an
- 80x80x80 chunk of map on a modern processor. Use sparingly!
-
- TODO(hmmmm): Remove dependency on biomes
- TODO(hmmmm): Find alternative to overgeneration as solution for sunlight issue
-*/
-class CavesNoiseIntersection
-{
-public:
- CavesNoiseIntersection(INodeDefManager *nodedef, BiomeManager *biomemgr,
- v3s16 chunksize, NoiseParams *np_cave1, NoiseParams *np_cave2,
- s32 seed, float cave_width);
- ~CavesNoiseIntersection();
-
- void generateCaves(MMVManip *vm, v3s16 nmin, v3s16 nmax, u8 *biomemap);
-
-private:
- INodeDefManager *m_ndef;
- BiomeManager *m_bmgr;
-
- // configurable parameters
- v3s16 m_csize;
- float m_cave_width;
-
- // intermediate state variables
- u16 m_ystride;
- u16 m_zstride_1d;
-
- Noise *noise_cave1;
- Noise *noise_cave2;
-};
-
-/*
- CavernsNoise is a cave digging algorithm
-*/
-class CavernsNoise
-{
-public:
- CavernsNoise(INodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern,
- s32 seed, float cavern_limit, float cavern_taper,
- float cavern_threshold);
- ~CavernsNoise();
-
- bool generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax);
-
-private:
- INodeDefManager *m_ndef;
-
- // configurable parameters
- v3s16 m_csize;
- float m_cavern_limit;
- float m_cavern_taper;
- float m_cavern_threshold;
-
- // intermediate state variables
- u16 m_ystride;
- u16 m_zstride_1d;
-
- Noise *noise_cavern;
-
- content_t c_water_source;
- content_t c_lava_source;
-};
-
-/*
- CavesRandomWalk is an implementation of a cave-digging algorithm that
- operates on the principle of a "random walk" to approximate the stochiastic
- activity of cavern development.
-
- In summary, this algorithm works by carving a randomly sized tunnel in a
- random direction a random amount of times, randomly varying in width.
- All randomness here is uniformly distributed; alternative distributions have
- not yet been implemented.
-
- This algorithm is very fast, executing in less than 1ms on average for an
- 80x80x80 chunk of map on a modern processor.
-*/
-class CavesRandomWalk
-{
-public:
- MMVManip *vm;
- INodeDefManager *ndef;
- GenerateNotifier *gennotify;
- s16 *heightmap;
-
- // configurable parameters
- s32 seed;
- int water_level;
- int lava_depth;
- NoiseParams *np_caveliquids;
-
- // intermediate state variables
- u16 ystride;
-
- s16 min_tunnel_diameter;
- s16 max_tunnel_diameter;
- u16 tunnel_routepoints;
- int part_max_length_rs;
-
- bool large_cave;
- bool large_cave_is_flat;
- bool flooded;
-
- s16 max_stone_y;
- v3s16 node_min;
- v3s16 node_max;
-
- v3f orp; // starting point, relative to caved space
- v3s16 of; // absolute coordinates of caved space
- v3s16 ar; // allowed route area
- s16 rs; // tunnel radius size
- v3f main_direction;
-
- s16 route_y_min;
- s16 route_y_max;
-
- PseudoRandom *ps;
-
- content_t c_water_source;
- content_t c_lava_source;
-
- // ndef is a mandatory parameter.
- // If gennotify is NULL, generation events are not logged.
- CavesRandomWalk(INodeDefManager *ndef, GenerateNotifier *gennotify = NULL,
- s32 seed = 0, int water_level = 1,
- content_t water_source = CONTENT_IGNORE,
- content_t lava_source = CONTENT_IGNORE, int lava_depth = -256);
-
- // vm and ps are mandatory parameters.
- // If heightmap is NULL, the surface level at all points is assumed to
- // be water_level.
- void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps,
- bool is_large_cave, int max_stone_height, s16 *heightmap);
-
-private:
- void makeTunnel(bool dirswitch);
- void carveRoute(v3f vec, float f, bool randomize_xz);
-
- inline bool isPosAboveSurface(v3s16 p);
-};
-
-/*
- CavesV6 is the original version of caves used with Mapgen V6.
-
- Though it uses the same fundamental algorithm as CavesRandomWalk, it is made
- separate to preserve the exact sequence of PseudoRandom calls - any change
- to this ordering results in the output being radically different.
- Because caves in Mapgen V6 are responsible for a large portion of the basic
- terrain shape, modifying this will break our contract of reverse
- compatibility for a 'stable' mapgen such as V6.
-
- tl;dr,
- *** DO NOT TOUCH THIS CLASS UNLESS YOU KNOW WHAT YOU ARE DOING ***
-*/
-class CavesV6
-{
-public:
- MMVManip *vm;
- INodeDefManager *ndef;
- GenerateNotifier *gennotify;
- PseudoRandom *ps;
- PseudoRandom *ps2;
-
- // configurable parameters
- s16 *heightmap;
- content_t c_water_source;
- content_t c_lava_source;
- int water_level;
-
- // intermediate state variables
- u16 ystride;
-
- s16 min_tunnel_diameter;
- s16 max_tunnel_diameter;
- u16 tunnel_routepoints;
- int part_max_length_rs;
-
- bool large_cave;
- bool large_cave_is_flat;
-
- v3s16 node_min;
- v3s16 node_max;
-
- v3f orp; // starting point, relative to caved space
- v3s16 of; // absolute coordinates of caved space
- v3s16 ar; // allowed route area
- s16 rs; // tunnel radius size
- v3f main_direction;
-
- s16 route_y_min;
- s16 route_y_max;
-
- // ndef is a mandatory parameter.
- // If gennotify is NULL, generation events are not logged.
- CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify = NULL,
- int water_level = 1, content_t water_source = CONTENT_IGNORE,
- content_t lava_source = CONTENT_IGNORE);
-
- // vm, ps, and ps2 are mandatory parameters.
- // If heightmap is NULL, the surface level at all points is assumed to
- // be water_level.
- void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps,
- PseudoRandom *ps2, bool is_large_cave, int max_stone_height,
- s16 *heightmap = NULL);
-
-private:
- void makeTunnel(bool dirswitch);
- void carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground);
-
- inline s16 getSurfaceFromHeightmap(v3s16 p);
-};
#include "clientmap.h"
#include "clientmedia.h"
#include "version.h"
-#include "database-sqlite3.h"
+#include "database/database-sqlite3.h"
#include "serialization.h"
#include "guiscalingfilter.h"
#include "script/scripting_client.h"
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "mainmenumanager.h"
+#include "gui/mainmenumanager.h"
#include "clouds.h"
#include "server.h"
#include "filesys.h"
-#include "guiMainMenu.h"
+#include "gui/guiMainMenu.h"
#include "game.h"
#include "player.h"
#include "chat.h"
#include "gettext.h"
#include "profiler.h"
#include "serverlist.h"
-#include "guiEngine.h"
+#include "gui/guiEngine.h"
#include "fontengine.h"
#include "clientlauncher.h"
#include "version.h"
#include "util/numeric.h"
#include "inputhandler.h"
-#include "mainmenumanager.h"
+#include "gui/mainmenumanager.h"
bool MyEventReceiver::OnEvent(const SEvent &event)
{
#include "renderingengine.h"
#ifdef HAVE_TOUCHSCREENGUI
-#include "touchscreengui.h"
+#include "gui/touchscreengui.h"
#endif
class KeyList : private std::list<KeyPress>
#include "settings.h"
#include "gettime.h"
#include "porting.h"
-#include "../util/string.h"
+#include "util/string.h"
bool JoystickButtonCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const
{
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-/*
-Dummy database class
-*/
-
-#include "database-dummy.h"
-
-
-bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data)
-{
- m_database[getBlockAsInteger(pos)] = data;
- return true;
-}
-
-void Database_Dummy::loadBlock(const v3s16 &pos, std::string *block)
-{
- s64 i = getBlockAsInteger(pos);
- auto it = m_database.find(i);
- if (it == m_database.end()) {
- *block = "";
- return;
- }
-
- *block = it->second;
-}
-
-bool Database_Dummy::deleteBlock(const v3s16 &pos)
-{
- m_database.erase(getBlockAsInteger(pos));
- return true;
-}
-
-void Database_Dummy::listAllLoadableBlocks(std::vector<v3s16> &dst)
-{
- dst.reserve(m_database.size());
- for (std::map<s64, std::string>::const_iterator x = m_database.begin();
- x != m_database.end(); ++x) {
- dst.push_back(getIntegerAsBlock(x->first));
- }
-}
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <map>
-#include <string>
-#include "database.h"
-#include "irrlichttypes.h"
-
-class Database_Dummy : public MapDatabase, public PlayerDatabase
-{
-public:
- bool saveBlock(const v3s16 &pos, const std::string &data);
- void loadBlock(const v3s16 &pos, std::string *block);
- bool deleteBlock(const v3s16 &pos);
- void listAllLoadableBlocks(std::vector<v3s16> &dst);
-
- void savePlayer(RemotePlayer *player) {}
- bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) { return true; }
- bool removePlayer(const std::string &name) { return true; }
- void listPlayers(std::vector<std::string> &res) {}
-
- void beginSave() {}
- void endSave() {}
-
-private:
- std::map<s64, std::string> m_database;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include <cassert>
-#include <json/json.h>
-#include "database-files.h"
-#include "content_sao.h"
-#include "remoteplayer.h"
-#include "settings.h"
-#include "porting.h"
-#include "filesys.h"
-
-// !!! WARNING !!!
-// This backend is intended to be used on Minetest 0.4.16 only for the transition backend
-// for player files
-
-void PlayerDatabaseFiles::serialize(std::ostringstream &os, RemotePlayer *player)
-{
- // Utilize a Settings object for storing values
- Settings args;
- args.setS32("version", 1);
- args.set("name", player->getName());
-
- sanity_check(player->getPlayerSAO());
- args.setS32("hp", player->getPlayerSAO()->getHP());
- args.setV3F("position", player->getPlayerSAO()->getBasePosition());
- args.setFloat("pitch", player->getPlayerSAO()->getPitch());
- args.setFloat("yaw", player->getPlayerSAO()->getYaw());
- args.setS32("breath", player->getPlayerSAO()->getBreath());
-
- std::string extended_attrs;
- player->serializeExtraAttributes(extended_attrs);
- args.set("extended_attributes", extended_attrs);
-
- args.writeLines(os);
-
- os << "PlayerArgsEnd\n";
-
- player->inventory.serialize(os);
-}
-
-void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
-{
- std::string savedir = m_savedir + DIR_DELIM;
- std::string path = savedir + player->getName();
- bool path_found = false;
- RemotePlayer testplayer("", NULL);
-
- for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) {
- if (!fs::PathExists(path)) {
- path_found = true;
- continue;
- }
-
- // Open and deserialize file to check player name
- std::ifstream is(path.c_str(), std::ios_base::binary);
- if (!is.good()) {
- errorstream << "Failed to open " << path << std::endl;
- return;
- }
-
- testplayer.deSerialize(is, path, NULL);
- is.close();
- if (strcmp(testplayer.getName(), player->getName()) == 0) {
- path_found = true;
- continue;
- }
-
- path = savedir + player->getName() + itos(i);
- }
-
- if (!path_found) {
- errorstream << "Didn't find free file for player " << player->getName()
- << std::endl;
- return;
- }
-
- // Open and serialize file
- std::ostringstream ss(std::ios_base::binary);
- serialize(ss, player);
- if (!fs::safeWriteToFile(path, ss.str())) {
- infostream << "Failed to write " << path << std::endl;
- }
- player->setModified(false);
-}
-
-bool PlayerDatabaseFiles::removePlayer(const std::string &name)
-{
- std::string players_path = m_savedir + DIR_DELIM;
- std::string path = players_path + name;
-
- RemotePlayer temp_player("", NULL);
- for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
- // Open file and deserialize
- std::ifstream is(path.c_str(), std::ios_base::binary);
- if (!is.good())
- continue;
-
- temp_player.deSerialize(is, path, NULL);
- is.close();
-
- if (temp_player.getName() == name) {
- fs::DeleteSingleFileOrEmptyDirectory(path);
- return true;
- }
-
- path = players_path + name + itos(i);
- }
-
- return false;
-}
-
-bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
-{
- std::string players_path = m_savedir + DIR_DELIM;
- std::string path = players_path + player->getName();
-
- const std::string player_to_load = player->getName();
- for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
- // Open file and deserialize
- std::ifstream is(path.c_str(), std::ios_base::binary);
- if (!is.good())
- continue;
-
- player->deSerialize(is, path, sao);
- is.close();
-
- if (player->getName() == player_to_load)
- return true;
-
- path = players_path + player_to_load + itos(i);
- }
-
- infostream << "Player file for player " << player_to_load << " not found" << std::endl;
- return false;
-}
-
-void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
-{
- std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
- // list files into players directory
- for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
- files.end(); ++it) {
- // Ignore directories
- if (it->dir)
- continue;
-
- const std::string &filename = it->name;
- std::string full_path = m_savedir + DIR_DELIM + filename;
- std::ifstream is(full_path.c_str(), std::ios_base::binary);
- if (!is.good())
- continue;
-
- RemotePlayer player(filename.c_str(), NULL);
- // Null env & dummy peer_id
- PlayerSAO playerSAO(NULL, &player, 15789, false);
-
- player.deSerialize(is, "", &playerSAO);
- is.close();
-
- res.emplace_back(player.getName());
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-// !!! WARNING !!!
-// This backend is intended to be used on Minetest 0.4.16 only for the transition backend
-// for player files
-
-#include "database.h"
-
-class PlayerDatabaseFiles : public PlayerDatabase
-{
-public:
- PlayerDatabaseFiles(const std::string &savedir) : m_savedir(savedir) {}
- virtual ~PlayerDatabaseFiles() = default;
-
- void savePlayer(RemotePlayer *player);
- bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
- bool removePlayer(const std::string &name);
- void listPlayers(std::vector<std::string> &res);
-
-private:
- void serialize(std::ostringstream &os, RemotePlayer *player);
-
- std::string m_savedir;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "config.h"
-
-#if USE_LEVELDB
-
-#include "database-leveldb.h"
-
-#include "log.h"
-#include "filesys.h"
-#include "exceptions.h"
-#include "util/string.h"
-
-#include "leveldb/db.h"
-
-
-#define ENSURE_STATUS_OK(s) \
- if (!(s).ok()) { \
- throw DatabaseException(std::string("LevelDB error: ") + \
- (s).ToString()); \
- }
-
-
-Database_LevelDB::Database_LevelDB(const std::string &savedir)
-{
- leveldb::Options options;
- options.create_if_missing = true;
- leveldb::Status status = leveldb::DB::Open(options,
- savedir + DIR_DELIM + "map.db", &m_database);
- ENSURE_STATUS_OK(status);
-}
-
-Database_LevelDB::~Database_LevelDB()
-{
- delete m_database;
-}
-
-bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data)
-{
- leveldb::Status status = m_database->Put(leveldb::WriteOptions(),
- i64tos(getBlockAsInteger(pos)), data);
- if (!status.ok()) {
- warningstream << "saveBlock: LevelDB error saving block "
- << PP(pos) << ": " << status.ToString() << std::endl;
- return false;
- }
-
- return true;
-}
-
-void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block)
-{
- std::string datastr;
- leveldb::Status status = m_database->Get(leveldb::ReadOptions(),
- i64tos(getBlockAsInteger(pos)), &datastr);
-
- *block = (status.ok()) ? datastr : "";
-}
-
-bool Database_LevelDB::deleteBlock(const v3s16 &pos)
-{
- leveldb::Status status = m_database->Delete(leveldb::WriteOptions(),
- i64tos(getBlockAsInteger(pos)));
- if (!status.ok()) {
- warningstream << "deleteBlock: LevelDB error deleting block "
- << PP(pos) << ": " << status.ToString() << std::endl;
- return false;
- }
-
- return true;
-}
-
-void Database_LevelDB::listAllLoadableBlocks(std::vector<v3s16> &dst)
-{
- leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions());
- for (it->SeekToFirst(); it->Valid(); it->Next()) {
- dst.push_back(getIntegerAsBlock(stoi64(it->key().ToString())));
- }
- ENSURE_STATUS_OK(it->status()); // Check for any errors found during the scan
- delete it;
-}
-
-#endif // USE_LEVELDB
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "config.h"
-
-#if USE_LEVELDB
-
-#include <string>
-#include "database.h"
-#include "leveldb/db.h"
-
-class Database_LevelDB : public MapDatabase
-{
-public:
- Database_LevelDB(const std::string &savedir);
- ~Database_LevelDB();
-
- bool saveBlock(const v3s16 &pos, const std::string &data);
- void loadBlock(const v3s16 &pos, std::string *block);
- bool deleteBlock(const v3s16 &pos);
- void listAllLoadableBlocks(std::vector<v3s16> &dst);
-
- void beginSave() {}
- void endSave() {}
-
-private:
- leveldb::DB *m_database;
-};
-
-#endif // USE_LEVELDB
+++ /dev/null
-/*
-Copyright (C) 2016 Loic Blot <loic.blot@unix-experience.fr>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "config.h"
-
-#if USE_POSTGRESQL
-
-#include "database-postgresql.h"
-
-#ifdef _WIN32
- // Without this some of the network functions are not found on mingw
- #ifndef _WIN32_WINNT
- #define _WIN32_WINNT 0x0501
- #endif
- #include <windows.h>
- #include <winsock2.h>
-#else
-#include <netinet/in.h>
-#endif
-
-#include "debug.h"
-#include "exceptions.h"
-#include "settings.h"
-#include "content_sao.h"
-#include "remoteplayer.h"
-
-Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) :
- m_connect_string(connect_string)
-{
- if (m_connect_string.empty()) {
- throw SettingNotFoundException(
- "Set pgsql_connection string in world.mt to "
- "use the postgresql backend\n"
- "Notes:\n"
- "pgsql_connection has the following form: \n"
- "\tpgsql_connection = host=127.0.0.1 port=5432 user=mt_user "
- "password=mt_password dbname=minetest_world\n"
- "mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and "
- "DELETE rights on the database.\n"
- "Don't create mt_user as a SUPERUSER!");
- }
-}
-
-Database_PostgreSQL::~Database_PostgreSQL()
-{
- PQfinish(m_conn);
-}
-
-void Database_PostgreSQL::connectToDatabase()
-{
- m_conn = PQconnectdb(m_connect_string.c_str());
-
- if (PQstatus(m_conn) != CONNECTION_OK) {
- throw DatabaseException(std::string(
- "PostgreSQL database error: ") +
- PQerrorMessage(m_conn));
- }
-
- m_pgversion = PQserverVersion(m_conn);
-
- /*
- * We are using UPSERT feature from PostgreSQL 9.5
- * to have the better performance where possible.
- */
- if (m_pgversion < 90500) {
- warningstream << "Your PostgreSQL server lacks UPSERT "
- << "support. Use version 9.5 or better if possible."
- << std::endl;
- }
-
- infostream << "PostgreSQL Database: Version " << m_pgversion
- << " Connection made." << std::endl;
-
- createDatabase();
- initStatements();
-}
-
-void Database_PostgreSQL::verifyDatabase()
-{
- if (PQstatus(m_conn) == CONNECTION_OK)
- return;
-
- PQreset(m_conn);
- ping();
-}
-
-void Database_PostgreSQL::ping()
-{
- if (PQping(m_connect_string.c_str()) != PQPING_OK) {
- throw DatabaseException(std::string(
- "PostgreSQL database error: ") +
- PQerrorMessage(m_conn));
- }
-}
-
-bool Database_PostgreSQL::initialized() const
-{
- return (PQstatus(m_conn) == CONNECTION_OK);
-}
-
-PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
-{
- ExecStatusType statusType = PQresultStatus(result);
-
- switch (statusType) {
- case PGRES_COMMAND_OK:
- case PGRES_TUPLES_OK:
- break;
- case PGRES_FATAL_ERROR:
- default:
- throw DatabaseException(
- std::string("PostgreSQL database error: ") +
- PQresultErrorMessage(result));
- }
-
- if (clear)
- PQclear(result);
-
- return result;
-}
-
-void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name,
- const std::string &definition)
-{
- std::string sql_check_table = "SELECT relname FROM pg_class WHERE relname='" +
- table_name + "';";
- PGresult *result = checkResults(PQexec(m_conn, sql_check_table.c_str()), false);
-
- // If table doesn't exist, create it
- if (!PQntuples(result)) {
- checkResults(PQexec(m_conn, definition.c_str()));
- }
-
- PQclear(result);
-}
-
-void Database_PostgreSQL::beginSave()
-{
- verifyDatabase();
- checkResults(PQexec(m_conn, "BEGIN;"));
-}
-
-void Database_PostgreSQL::endSave()
-{
- checkResults(PQexec(m_conn, "COMMIT;"));
-}
-
-MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string):
- Database_PostgreSQL(connect_string),
- MapDatabase()
-{
- connectToDatabase();
-}
-
-
-void MapDatabasePostgreSQL::createDatabase()
-{
- createTableIfNotExists("blocks",
- "CREATE TABLE blocks ("
- "posX INT NOT NULL,"
- "posY INT NOT NULL,"
- "posZ INT NOT NULL,"
- "data BYTEA,"
- "PRIMARY KEY (posX,posY,posZ)"
- ");"
- );
-
- infostream << "PostgreSQL: Map Database was initialized." << std::endl;
-}
-
-void MapDatabasePostgreSQL::initStatements()
-{
- prepareStatement("read_block",
- "SELECT data FROM blocks "
- "WHERE posX = $1::int4 AND posY = $2::int4 AND "
- "posZ = $3::int4");
-
- if (getPGVersion() < 90500) {
- prepareStatement("write_block_insert",
- "INSERT INTO blocks (posX, posY, posZ, data) SELECT "
- "$1::int4, $2::int4, $3::int4, $4::bytea "
- "WHERE NOT EXISTS (SELECT true FROM blocks "
- "WHERE posX = $1::int4 AND posY = $2::int4 AND "
- "posZ = $3::int4)");
-
- prepareStatement("write_block_update",
- "UPDATE blocks SET data = $4::bytea "
- "WHERE posX = $1::int4 AND posY = $2::int4 AND "
- "posZ = $3::int4");
- } else {
- prepareStatement("write_block",
- "INSERT INTO blocks (posX, posY, posZ, data) VALUES "
- "($1::int4, $2::int4, $3::int4, $4::bytea) "
- "ON CONFLICT ON CONSTRAINT blocks_pkey DO "
- "UPDATE SET data = $4::bytea");
- }
-
- prepareStatement("delete_block", "DELETE FROM blocks WHERE "
- "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4");
-
- prepareStatement("list_all_loadable_blocks",
- "SELECT posX, posY, posZ FROM blocks");
-}
-
-bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data)
-{
- // Verify if we don't overflow the platform integer with the mapblock size
- if (data.size() > INT_MAX) {
- errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
- << "data.size() over 0xFFFFFFFF (== " << data.size()
- << ")" << std::endl;
- return false;
- }
-
- verifyDatabase();
-
- s32 x, y, z;
- x = htonl(pos.X);
- y = htonl(pos.Y);
- z = htonl(pos.Z);
-
- const void *args[] = { &x, &y, &z, data.c_str() };
- const int argLen[] = {
- sizeof(x), sizeof(y), sizeof(z), (int)data.size()
- };
- const int argFmt[] = { 1, 1, 1, 1 };
-
- if (getPGVersion() < 90500) {
- execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt);
- execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt);
- } else {
- execPrepared("write_block", ARRLEN(args), args, argLen, argFmt);
- }
- return true;
-}
-
-void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block)
-{
- verifyDatabase();
-
- s32 x, y, z;
- x = htonl(pos.X);
- y = htonl(pos.Y);
- z = htonl(pos.Z);
-
- const void *args[] = { &x, &y, &z };
- const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
- const int argFmt[] = { 1, 1, 1 };
-
- PGresult *results = execPrepared("read_block", ARRLEN(args), args,
- argLen, argFmt, false);
-
- *block = "";
-
- if (PQntuples(results))
- *block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
-
- PQclear(results);
-}
-
-bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos)
-{
- verifyDatabase();
-
- s32 x, y, z;
- x = htonl(pos.X);
- y = htonl(pos.Y);
- z = htonl(pos.Z);
-
- const void *args[] = { &x, &y, &z };
- const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
- const int argFmt[] = { 1, 1, 1 };
-
- execPrepared("delete_block", ARRLEN(args), args, argLen, argFmt);
-
- return true;
-}
-
-void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
-{
- verifyDatabase();
-
- PGresult *results = execPrepared("list_all_loadable_blocks", 0,
- NULL, NULL, NULL, false, false);
-
- int numrows = PQntuples(results);
-
- for (int row = 0; row < numrows; ++row)
- dst.push_back(pg_to_v3s16(results, 0, 0));
-
- PQclear(results);
-}
-
-/*
- * Player Database
- */
-PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string):
- Database_PostgreSQL(connect_string),
- PlayerDatabase()
-{
- connectToDatabase();
-}
-
-
-void PlayerDatabasePostgreSQL::createDatabase()
-{
- createTableIfNotExists("player",
- "CREATE TABLE player ("
- "name VARCHAR(60) NOT NULL,"
- "pitch NUMERIC(15, 7) NOT NULL,"
- "yaw NUMERIC(15, 7) NOT NULL,"
- "posX NUMERIC(15, 7) NOT NULL,"
- "posY NUMERIC(15, 7) NOT NULL,"
- "posZ NUMERIC(15, 7) NOT NULL,"
- "hp INT NOT NULL,"
- "breath INT NOT NULL,"
- "creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
- "modification_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
- "PRIMARY KEY (name)"
- ");"
- );
-
- createTableIfNotExists("player_inventories",
- "CREATE TABLE player_inventories ("
- "player VARCHAR(60) NOT NULL,"
- "inv_id INT NOT NULL,"
- "inv_width INT NOT NULL,"
- "inv_name TEXT NOT NULL DEFAULT '',"
- "inv_size INT NOT NULL,"
- "PRIMARY KEY(player, inv_id),"
- "CONSTRAINT player_inventories_fkey FOREIGN KEY (player) REFERENCES "
- "player (name) ON DELETE CASCADE"
- ");"
- );
-
- createTableIfNotExists("player_inventory_items",
- "CREATE TABLE player_inventory_items ("
- "player VARCHAR(60) NOT NULL,"
- "inv_id INT NOT NULL,"
- "slot_id INT NOT NULL,"
- "item TEXT NOT NULL DEFAULT '',"
- "PRIMARY KEY(player, inv_id, slot_id),"
- "CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) REFERENCES "
- "player (name) ON DELETE CASCADE"
- ");"
- );
-
- createTableIfNotExists("player_metadata",
- "CREATE TABLE player_metadata ("
- "player VARCHAR(60) NOT NULL,"
- "attr VARCHAR(256) NOT NULL,"
- "value TEXT,"
- "PRIMARY KEY(player, attr),"
- "CONSTRAINT player_metadata_fkey FOREIGN KEY (player) REFERENCES "
- "player (name) ON DELETE CASCADE"
- ");"
- );
-
- infostream << "PostgreSQL: Player Database was inited." << std::endl;
-}
-
-void PlayerDatabasePostgreSQL::initStatements()
-{
- if (getPGVersion() < 90500) {
- prepareStatement("create_player",
- "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
- "($1, $2, $3, $4, $5, $6, $7::int, $8::int)");
-
- prepareStatement("update_player",
- "UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, posZ = $6, hp = $7::int, "
- "breath = $8::int, modification_date = NOW() WHERE name = $1");
- } else {
- prepareStatement("save_player",
- "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
- "($1, $2, $3, $4, $5, $6, $7::int, $8::int)"
- "ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET pitch = $2, yaw = $3, "
- "posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = $8::int, "
- "modification_date = NOW()");
- }
-
- prepareStatement("remove_player", "DELETE FROM player WHERE name = $1");
-
- prepareStatement("load_player_list", "SELECT name FROM player");
-
- prepareStatement("remove_player_inventories",
- "DELETE FROM player_inventories WHERE player = $1");
-
- prepareStatement("remove_player_inventory_items",
- "DELETE FROM player_inventory_items WHERE player = $1");
-
- prepareStatement("add_player_inventory",
- "INSERT INTO player_inventories (player, inv_id, inv_width, inv_name, inv_size) VALUES "
- "($1, $2::int, $3::int, $4, $5::int)");
-
- prepareStatement("add_player_inventory_item",
- "INSERT INTO player_inventory_items (player, inv_id, slot_id, item) VALUES "
- "($1, $2::int, $3::int, $4)");
-
- prepareStatement("load_player_inventories",
- "SELECT inv_id, inv_width, inv_name, inv_size FROM player_inventories "
- "WHERE player = $1 ORDER BY inv_id");
-
- prepareStatement("load_player_inventory_items",
- "SELECT slot_id, item FROM player_inventory_items WHERE "
- "player = $1 AND inv_id = $2::int");
-
- prepareStatement("load_player",
- "SELECT pitch, yaw, posX, posY, posZ, hp, breath FROM player WHERE name = $1");
-
- prepareStatement("remove_player_metadata",
- "DELETE FROM player_metadata WHERE player = $1");
-
- prepareStatement("save_player_metadata",
- "INSERT INTO player_metadata (player, attr, value) VALUES ($1, $2, $3)");
-
- prepareStatement("load_player_metadata",
- "SELECT attr, value FROM player_metadata WHERE player = $1");
-
-}
-
-bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername)
-{
- verifyDatabase();
-
- const char *values[] = { playername.c_str() };
- PGresult *results = execPrepared("load_player", 1, values, false);
-
- bool res = (PQntuples(results) > 0);
- PQclear(results);
- return res;
-}
-
-void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player)
-{
- PlayerSAO* sao = player->getPlayerSAO();
- if (!sao)
- return;
-
- verifyDatabase();
-
- v3f pos = sao->getBasePosition();
- std::string pitch = ftos(sao->getPitch());
- std::string yaw = ftos(sao->getYaw());
- std::string posx = ftos(pos.X);
- std::string posy = ftos(pos.Y);
- std::string posz = ftos(pos.Z);
- std::string hp = itos(sao->getHP());
- std::string breath = itos(sao->getBreath());
- const char *values[] = {
- player->getName(),
- pitch.c_str(),
- yaw.c_str(),
- posx.c_str(), posy.c_str(), posz.c_str(),
- hp.c_str(),
- breath.c_str()
- };
-
- const char* rmvalues[] = { player->getName() };
- beginSave();
-
- if (getPGVersion() < 90500) {
- if (!playerDataExists(player->getName()))
- execPrepared("create_player", 8, values, true, false);
- else
- execPrepared("update_player", 8, values, true, false);
- }
- else
- execPrepared("save_player", 8, values, true, false);
-
- // Write player inventories
- execPrepared("remove_player_inventories", 1, rmvalues);
- execPrepared("remove_player_inventory_items", 1, rmvalues);
-
- std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists();
- for (u16 i = 0; i < inventory_lists.size(); i++) {
- const InventoryList* list = inventory_lists[i];
- const std::string &name = list->getName();
- std::string width = itos(list->getWidth()),
- inv_id = itos(i), lsize = itos(list->getSize());
-
- const char* inv_values[] = {
- player->getName(),
- inv_id.c_str(),
- width.c_str(),
- name.c_str(),
- lsize.c_str()
- };
- execPrepared("add_player_inventory", 5, inv_values);
-
- for (u32 j = 0; j < list->getSize(); j++) {
- std::ostringstream os;
- list->getItem(j).serialize(os);
- std::string itemStr = os.str(), slotId = itos(j);
-
- const char* invitem_values[] = {
- player->getName(),
- inv_id.c_str(),
- slotId.c_str(),
- itemStr.c_str()
- };
- execPrepared("add_player_inventory_item", 4, invitem_values);
- }
- }
-
- execPrepared("remove_player_metadata", 1, rmvalues);
- const PlayerAttributes &attrs = sao->getExtendedAttributes();
- for (const auto &attr : attrs) {
- const char *meta_values[] = {
- player->getName(),
- attr.first.c_str(),
- attr.second.c_str()
- };
- execPrepared("save_player_metadata", 3, meta_values);
- }
- endSave();
-}
-
-bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
-{
- sanity_check(sao);
- verifyDatabase();
-
- const char *values[] = { player->getName() };
- PGresult *results = execPrepared("load_player", 1, values, false, false);
-
- // Player not found, return not found
- if (!PQntuples(results)) {
- PQclear(results);
- return false;
- }
-
- sao->setPitch(pg_to_float(results, 0, 0));
- sao->setYaw(pg_to_float(results, 0, 1));
- sao->setBasePosition(v3f(
- pg_to_float(results, 0, 2),
- pg_to_float(results, 0, 3),
- pg_to_float(results, 0, 4))
- );
- sao->setHPRaw((s16) pg_to_int(results, 0, 5));
- sao->setBreath((u16) pg_to_int(results, 0, 6), false);
-
- PQclear(results);
-
- // Load inventory
- results = execPrepared("load_player_inventories", 1, values, false, false);
-
- int resultCount = PQntuples(results);
-
- for (int row = 0; row < resultCount; ++row) {
- InventoryList* invList = player->inventory.
- addList(PQgetvalue(results, row, 2), pg_to_uint(results, row, 3));
- invList->setWidth(pg_to_uint(results, row, 1));
-
- u32 invId = pg_to_uint(results, row, 0);
- std::string invIdStr = itos(invId);
-
- const char* values2[] = {
- player->getName(),
- invIdStr.c_str()
- };
- PGresult *results2 = execPrepared("load_player_inventory_items", 2,
- values2, false, false);
-
- int resultCount2 = PQntuples(results2);
- for (int row2 = 0; row2 < resultCount2; row2++) {
- const std::string itemStr = PQgetvalue(results2, row2, 1);
- if (itemStr.length() > 0) {
- ItemStack stack;
- stack.deSerialize(itemStr);
- invList->changeItem(pg_to_uint(results2, row2, 0), stack);
- }
- }
- PQclear(results2);
- }
-
- PQclear(results);
-
- results = execPrepared("load_player_metadata", 1, values, false);
-
- int numrows = PQntuples(results);
- for (int row = 0; row < numrows; row++) {
- sao->setExtendedAttribute(PQgetvalue(results, row, 0),PQgetvalue(results, row, 1));
- }
-
- PQclear(results);
-
- return true;
-}
-
-bool PlayerDatabasePostgreSQL::removePlayer(const std::string &name)
-{
- if (!playerDataExists(name))
- return false;
-
- verifyDatabase();
-
- const char *values[] = { name.c_str() };
- execPrepared("remove_player", 1, values);
-
- return true;
-}
-
-void PlayerDatabasePostgreSQL::listPlayers(std::vector<std::string> &res)
-{
- verifyDatabase();
-
- PGresult *results = execPrepared("load_player_list", 0, NULL, false);
-
- int numrows = PQntuples(results);
- for (int row = 0; row < numrows; row++)
- res.emplace_back(PQgetvalue(results, row, 0));
-
- PQclear(results);
-}
-
-#endif // USE_POSTGRESQL
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <string>
-#include <libpq-fe.h>
-#include "database.h"
-#include "util/basic_macros.h"
-
-class Settings;
-
-class Database_PostgreSQL: public Database
-{
-public:
- Database_PostgreSQL(const std::string &connect_string);
- ~Database_PostgreSQL();
-
- void beginSave();
- void endSave();
-
- bool initialized() const;
-
-
-protected:
- // Conversion helpers
- inline int pg_to_int(PGresult *res, int row, int col)
- {
- return atoi(PQgetvalue(res, row, col));
- }
-
- inline u32 pg_to_uint(PGresult *res, int row, int col)
- {
- return (u32) atoi(PQgetvalue(res, row, col));
- }
-
- inline float pg_to_float(PGresult *res, int row, int col)
- {
- return (float) atof(PQgetvalue(res, row, col));
- }
-
- inline v3s16 pg_to_v3s16(PGresult *res, int row, int col)
- {
- return v3s16(
- pg_to_int(res, row, col),
- pg_to_int(res, row, col + 1),
- pg_to_int(res, row, col + 2)
- );
- }
-
- inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
- const void **params,
- const int *paramsLengths = NULL, const int *paramsFormats = NULL,
- bool clear = true, bool nobinary = true)
- {
- return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber,
- (const char* const*) params, paramsLengths, paramsFormats,
- nobinary ? 1 : 0), clear);
- }
-
- inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
- const char **params, bool clear = true, bool nobinary = true)
- {
- return execPrepared(stmtName, paramsNumber,
- (const void **)params, NULL, NULL, clear, nobinary);
- }
-
- void createTableIfNotExists(const std::string &table_name, const std::string &definition);
- void verifyDatabase();
-
- // Database initialization
- void connectToDatabase();
- virtual void createDatabase() = 0;
- virtual void initStatements() = 0;
- inline void prepareStatement(const std::string &name, const std::string &sql)
- {
- checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL));
- }
-
- const int getPGVersion() const { return m_pgversion; }
-private:
- // Database connectivity checks
- void ping();
-
- // Database usage
- PGresult *checkResults(PGresult *res, bool clear = true);
-
- // Attributes
- std::string m_connect_string;
- PGconn *m_conn = nullptr;
- int m_pgversion = 0;
-};
-
-class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase
-{
-public:
- MapDatabasePostgreSQL(const std::string &connect_string);
- virtual ~MapDatabasePostgreSQL() = default;
-
- bool saveBlock(const v3s16 &pos, const std::string &data);
- void loadBlock(const v3s16 &pos, std::string *block);
- bool deleteBlock(const v3s16 &pos);
- void listAllLoadableBlocks(std::vector<v3s16> &dst);
-
- void beginSave() { Database_PostgreSQL::beginSave(); }
- void endSave() { Database_PostgreSQL::endSave(); }
-
-protected:
- virtual void createDatabase();
- virtual void initStatements();
-};
-
-class PlayerDatabasePostgreSQL : private Database_PostgreSQL, public PlayerDatabase
-{
-public:
- PlayerDatabasePostgreSQL(const std::string &connect_string);
- virtual ~PlayerDatabasePostgreSQL() = default;
-
- void savePlayer(RemotePlayer *player);
- bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
- bool removePlayer(const std::string &name);
- void listPlayers(std::vector<std::string> &res);
-
-protected:
- virtual void createDatabase();
- virtual void initStatements();
-
-private:
- bool playerDataExists(const std::string &playername);
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "config.h"
-
-#if USE_REDIS
-
-#include "database-redis.h"
-
-#include "settings.h"
-#include "log.h"
-#include "exceptions.h"
-#include "util/string.h"
-
-#include <hiredis.h>
-#include <cassert>
-
-
-Database_Redis::Database_Redis(Settings &conf)
-{
- std::string tmp;
- try {
- tmp = conf.get("redis_address");
- hash = conf.get("redis_hash");
- } catch (SettingNotFoundException &) {
- throw SettingNotFoundException("Set redis_address and "
- "redis_hash in world.mt to use the redis backend");
- }
- const char *addr = tmp.c_str();
- int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379;
- // if redis_address contains '/' assume unix socket, else hostname/ip
- ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port);
- if (!ctx) {
- throw DatabaseException("Cannot allocate redis context");
- } else if (ctx->err) {
- std::string err = std::string("Connection error: ") + ctx->errstr;
- redisFree(ctx);
- throw DatabaseException(err);
- }
- if (conf.exists("redis_password")) {
- tmp = conf.get("redis_password");
- redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "AUTH %s", tmp.c_str()));
- if (!reply)
- throw DatabaseException("Redis authentication failed");
- if (reply->type == REDIS_REPLY_ERROR) {
- std::string err = "Redis authentication failed: " + std::string(reply->str, reply->len);
- freeReplyObject(reply);
- throw DatabaseException(err);
- }
- freeReplyObject(reply);
- }
-}
-
-Database_Redis::~Database_Redis()
-{
- redisFree(ctx);
-}
-
-void Database_Redis::beginSave() {
- redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI"));
- if (!reply) {
- throw DatabaseException(std::string(
- "Redis command 'MULTI' failed: ") + ctx->errstr);
- }
- freeReplyObject(reply);
-}
-
-void Database_Redis::endSave() {
- redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC"));
- if (!reply) {
- throw DatabaseException(std::string(
- "Redis command 'EXEC' failed: ") + ctx->errstr);
- }
- freeReplyObject(reply);
-}
-
-bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data)
-{
- std::string tmp = i64tos(getBlockAsInteger(pos));
-
- redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HSET %s %s %b",
- hash.c_str(), tmp.c_str(), data.c_str(), data.size()));
- if (!reply) {
- warningstream << "saveBlock: redis command 'HSET' failed on "
- "block " << PP(pos) << ": " << ctx->errstr << std::endl;
- freeReplyObject(reply);
- return false;
- }
-
- if (reply->type == REDIS_REPLY_ERROR) {
- warningstream << "saveBlock: saving block " << PP(pos)
- << " failed: " << std::string(reply->str, reply->len) << std::endl;
- freeReplyObject(reply);
- return false;
- }
-
- freeReplyObject(reply);
- return true;
-}
-
-void Database_Redis::loadBlock(const v3s16 &pos, std::string *block)
-{
- std::string tmp = i64tos(getBlockAsInteger(pos));
- redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
- "HGET %s %s", hash.c_str(), tmp.c_str()));
-
- if (!reply) {
- throw DatabaseException(std::string(
- "Redis command 'HGET %s %s' failed: ") + ctx->errstr);
- }
-
- switch (reply->type) {
- case REDIS_REPLY_STRING: {
- *block = std::string(reply->str, reply->len);
- // std::string copies the memory so this won't cause any problems
- freeReplyObject(reply);
- return;
- }
- case REDIS_REPLY_ERROR: {
- std::string errstr(reply->str, reply->len);
- freeReplyObject(reply);
- errorstream << "loadBlock: loading block " << PP(pos)
- << " failed: " << errstr << std::endl;
- throw DatabaseException(std::string(
- "Redis command 'HGET %s %s' errored: ") + errstr);
- }
- case REDIS_REPLY_NIL: {
- *block = "";
- // block not found in database
- freeReplyObject(reply);
- return;
- }
- }
-
- errorstream << "loadBlock: loading block " << PP(pos)
- << " returned invalid reply type " << reply->type
- << ": " << std::string(reply->str, reply->len) << std::endl;
- freeReplyObject(reply);
- throw DatabaseException(std::string(
- "Redis command 'HGET %s %s' gave invalid reply."));
-}
-
-bool Database_Redis::deleteBlock(const v3s16 &pos)
-{
- std::string tmp = i64tos(getBlockAsInteger(pos));
-
- redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
- "HDEL %s %s", hash.c_str(), tmp.c_str()));
- if (!reply) {
- throw DatabaseException(std::string(
- "Redis command 'HDEL %s %s' failed: ") + ctx->errstr);
- } else if (reply->type == REDIS_REPLY_ERROR) {
- warningstream << "deleteBlock: deleting block " << PP(pos)
- << " failed: " << std::string(reply->str, reply->len) << std::endl;
- freeReplyObject(reply);
- return false;
- }
-
- freeReplyObject(reply);
- return true;
-}
-
-void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst)
-{
- redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str()));
- if (!reply) {
- throw DatabaseException(std::string(
- "Redis command 'HKEYS %s' failed: ") + ctx->errstr);
- }
- switch (reply->type) {
- case REDIS_REPLY_ARRAY:
- dst.reserve(reply->elements);
- for (size_t i = 0; i < reply->elements; i++) {
- assert(reply->element[i]->type == REDIS_REPLY_STRING);
- dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str)));
- }
- break;
- case REDIS_REPLY_ERROR:
- throw DatabaseException(std::string(
- "Failed to get keys from database: ") +
- std::string(reply->str, reply->len));
- }
- freeReplyObject(reply);
-}
-
-#endif // USE_REDIS
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "config.h"
-
-#if USE_REDIS
-
-#include <hiredis.h>
-#include <string>
-#include "database.h"
-
-class Settings;
-
-class Database_Redis : public MapDatabase
-{
-public:
- Database_Redis(Settings &conf);
- ~Database_Redis();
-
- void beginSave();
- void endSave();
-
- bool saveBlock(const v3s16 &pos, const std::string &data);
- void loadBlock(const v3s16 &pos, std::string *block);
- bool deleteBlock(const v3s16 &pos);
- void listAllLoadableBlocks(std::vector<v3s16> &dst);
-
-private:
- redisContext *ctx = nullptr;
- std::string hash = "";
-};
-
-#endif // USE_REDIS
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-/*
-SQLite format specification:
- blocks:
- (PK) INT id
- BLOB data
-*/
-
-
-#include "database-sqlite3.h"
-
-#include "log.h"
-#include "filesys.h"
-#include "exceptions.h"
-#include "settings.h"
-#include "porting.h"
-#include "util/string.h"
-#include "content_sao.h"
-#include "remoteplayer.h"
-
-#include <cassert>
-
-// When to print messages when the database is being held locked by another process
-// Note: I've seen occasional delays of over 250ms while running minetestmapper.
-#define BUSY_INFO_TRESHOLD 100 // Print first informational message after 100ms.
-#define BUSY_WARNING_TRESHOLD 250 // Print warning message after 250ms. Lag is increased.
-#define BUSY_ERROR_TRESHOLD 1000 // Print error message after 1000ms. Significant lag.
-#define BUSY_FATAL_TRESHOLD 3000 // Allow SQLITE_BUSY to be returned, which will cause a minetest crash.
-#define BUSY_ERROR_INTERVAL 10000 // Safety net: report again every 10 seconds
-
-
-#define SQLRES(s, r, m) \
- if ((s) != (r)) { \
- throw DatabaseException(std::string(m) + ": " +\
- sqlite3_errmsg(m_database)); \
- }
-#define SQLOK(s, m) SQLRES(s, SQLITE_OK, m)
-
-#define PREPARE_STATEMENT(name, query) \
- SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL),\
- "Failed to prepare query '" query "'")
-
-#define SQLOK_ERRSTREAM(s, m) \
- if ((s) != SQLITE_OK) { \
- errorstream << (m) << ": " \
- << sqlite3_errmsg(m_database) << std::endl; \
- }
-
-#define FINALIZE_STATEMENT(statement) SQLOK_ERRSTREAM(sqlite3_finalize(statement), \
- "Failed to finalize " #statement)
-
-int Database_SQLite3::busyHandler(void *data, int count)
-{
- s64 &first_time = reinterpret_cast<s64 *>(data)[0];
- s64 &prev_time = reinterpret_cast<s64 *>(data)[1];
- s64 cur_time = porting::getTimeMs();
-
- if (count == 0) {
- first_time = cur_time;
- prev_time = first_time;
- } else {
- while (cur_time < prev_time)
- cur_time += s64(1)<<32;
- }
-
- if (cur_time - first_time < BUSY_INFO_TRESHOLD) {
- ; // do nothing
- } else if (cur_time - first_time >= BUSY_INFO_TRESHOLD &&
- prev_time - first_time < BUSY_INFO_TRESHOLD) {
- infostream << "SQLite3 database has been locked for "
- << cur_time - first_time << " ms." << std::endl;
- } else if (cur_time - first_time >= BUSY_WARNING_TRESHOLD &&
- prev_time - first_time < BUSY_WARNING_TRESHOLD) {
- warningstream << "SQLite3 database has been locked for "
- << cur_time - first_time << " ms." << std::endl;
- } else if (cur_time - first_time >= BUSY_ERROR_TRESHOLD &&
- prev_time - first_time < BUSY_ERROR_TRESHOLD) {
- errorstream << "SQLite3 database has been locked for "
- << cur_time - first_time << " ms; this causes lag." << std::endl;
- } else if (cur_time - first_time >= BUSY_FATAL_TRESHOLD &&
- prev_time - first_time < BUSY_FATAL_TRESHOLD) {
- errorstream << "SQLite3 database has been locked for "
- << cur_time - first_time << " ms - giving up!" << std::endl;
- } else if ((cur_time - first_time) / BUSY_ERROR_INTERVAL !=
- (prev_time - first_time) / BUSY_ERROR_INTERVAL) {
- // Safety net: keep reporting every BUSY_ERROR_INTERVAL
- errorstream << "SQLite3 database has been locked for "
- << (cur_time - first_time) / 1000 << " seconds!" << std::endl;
- }
-
- prev_time = cur_time;
-
- // Make sqlite transaction fail if delay exceeds BUSY_FATAL_TRESHOLD
- return cur_time - first_time < BUSY_FATAL_TRESHOLD;
-}
-
-
-Database_SQLite3::Database_SQLite3(const std::string &savedir, const std::string &dbname) :
- m_savedir(savedir),
- m_dbname(dbname)
-{
-}
-
-void Database_SQLite3::beginSave()
-{
- verifyDatabase();
- SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE,
- "Failed to start SQLite3 transaction");
- sqlite3_reset(m_stmt_begin);
-}
-
-void Database_SQLite3::endSave()
-{
- verifyDatabase();
- SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE,
- "Failed to commit SQLite3 transaction");
- sqlite3_reset(m_stmt_end);
-}
-
-void Database_SQLite3::openDatabase()
-{
- if (m_database) return;
-
- std::string dbp = m_savedir + DIR_DELIM + m_dbname + ".sqlite";
-
- // Open the database connection
-
- if (!fs::CreateAllDirs(m_savedir)) {
- infostream << "Database_SQLite3: Failed to create directory \""
- << m_savedir << "\"" << std::endl;
- throw FileNotGoodException("Failed to create database "
- "save directory");
- }
-
- bool needs_create = !fs::PathExists(dbp);
-
- SQLOK(sqlite3_open_v2(dbp.c_str(), &m_database,
- SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL),
- std::string("Failed to open SQLite3 database file ") + dbp);
-
- SQLOK(sqlite3_busy_handler(m_database, Database_SQLite3::busyHandler,
- m_busy_handler_data), "Failed to set SQLite3 busy handler");
-
- if (needs_create) {
- createDatabase();
- }
-
- std::string query_str = std::string("PRAGMA synchronous = ")
- + itos(g_settings->getU16("sqlite_synchronous"));
- SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL),
- "Failed to modify sqlite3 synchronous mode");
- SQLOK(sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL),
- "Failed to enable sqlite3 foreign key support");
-}
-
-void Database_SQLite3::verifyDatabase()
-{
- if (m_initialized) return;
-
- openDatabase();
-
- PREPARE_STATEMENT(begin, "BEGIN;");
- PREPARE_STATEMENT(end, "COMMIT;");
-
- initStatements();
-
- m_initialized = true;
-}
-
-Database_SQLite3::~Database_SQLite3()
-{
- FINALIZE_STATEMENT(m_stmt_begin)
- FINALIZE_STATEMENT(m_stmt_end)
-
- SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database");
-}
-
-/*
- * Map database
- */
-
-MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir):
- Database_SQLite3(savedir, "map"),
- MapDatabase()
-{
-}
-
-MapDatabaseSQLite3::~MapDatabaseSQLite3()
-{
- FINALIZE_STATEMENT(m_stmt_read)
- FINALIZE_STATEMENT(m_stmt_write)
- FINALIZE_STATEMENT(m_stmt_list)
- FINALIZE_STATEMENT(m_stmt_delete)
-}
-
-
-void MapDatabaseSQLite3::createDatabase()
-{
- assert(m_database); // Pre-condition
-
- SQLOK(sqlite3_exec(m_database,
- "CREATE TABLE IF NOT EXISTS `blocks` (\n"
- " `pos` INT PRIMARY KEY,\n"
- " `data` BLOB\n"
- ");\n",
- NULL, NULL, NULL),
- "Failed to create database table");
-}
-
-void MapDatabaseSQLite3::initStatements()
-{
- PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
-#ifdef __ANDROID__
- PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
-#else
- PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
-#endif
- PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
- PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
-
- verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
-}
-
-inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
-{
- SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)),
- "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
-}
-
-bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
-{
- verifyDatabase();
-
- bindPos(m_stmt_delete, pos);
-
- bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE;
- sqlite3_reset(m_stmt_delete);
-
- if (!good) {
- warningstream << "deleteBlock: Block failed to delete "
- << PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl;
- }
- return good;
-}
-
-bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data)
-{
- verifyDatabase();
-
-#ifdef __ANDROID__
- /**
- * Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android,
- * deleting them and then inserting works.
- */
- bindPos(m_stmt_read, pos);
-
- if (sqlite3_step(m_stmt_read) == SQLITE_ROW) {
- deleteBlock(pos);
- }
- sqlite3_reset(m_stmt_read);
-#endif
-
- bindPos(m_stmt_write, pos);
- SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL),
- "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
-
- SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE, "Failed to save block")
- sqlite3_reset(m_stmt_write);
-
- return true;
-}
-
-void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
-{
- verifyDatabase();
-
- bindPos(m_stmt_read, pos);
-
- if (sqlite3_step(m_stmt_read) != SQLITE_ROW) {
- sqlite3_reset(m_stmt_read);
- return;
- }
-
- const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0);
- size_t len = sqlite3_column_bytes(m_stmt_read, 0);
-
- *block = (data) ? std::string(data, len) : "";
-
- sqlite3_step(m_stmt_read);
- // We should never get more than 1 row, so ok to reset
- sqlite3_reset(m_stmt_read);
-}
-
-void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
-{
- verifyDatabase();
-
- while (sqlite3_step(m_stmt_list) == SQLITE_ROW)
- dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
-
- sqlite3_reset(m_stmt_list);
-}
-
-/*
- * Player Database
- */
-
-PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir):
- Database_SQLite3(savedir, "players"),
- PlayerDatabase()
-{
-}
-
-PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3()
-{
- FINALIZE_STATEMENT(m_stmt_player_load)
- FINALIZE_STATEMENT(m_stmt_player_add)
- FINALIZE_STATEMENT(m_stmt_player_update)
- FINALIZE_STATEMENT(m_stmt_player_remove)
- FINALIZE_STATEMENT(m_stmt_player_list)
- FINALIZE_STATEMENT(m_stmt_player_add_inventory)
- FINALIZE_STATEMENT(m_stmt_player_add_inventory_items)
- FINALIZE_STATEMENT(m_stmt_player_remove_inventory)
- FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items)
- FINALIZE_STATEMENT(m_stmt_player_load_inventory)
- FINALIZE_STATEMENT(m_stmt_player_load_inventory_items)
- FINALIZE_STATEMENT(m_stmt_player_metadata_load)
- FINALIZE_STATEMENT(m_stmt_player_metadata_add)
- FINALIZE_STATEMENT(m_stmt_player_metadata_remove)
-};
-
-
-void PlayerDatabaseSQLite3::createDatabase()
-{
- assert(m_database); // Pre-condition
-
- SQLOK(sqlite3_exec(m_database,
- "CREATE TABLE IF NOT EXISTS `player` ("
- "`name` VARCHAR(50) NOT NULL,"
- "`pitch` NUMERIC(11, 4) NOT NULL,"
- "`yaw` NUMERIC(11, 4) NOT NULL,"
- "`posX` NUMERIC(11, 4) NOT NULL,"
- "`posY` NUMERIC(11, 4) NOT NULL,"
- "`posZ` NUMERIC(11, 4) NOT NULL,"
- "`hp` INT NOT NULL,"
- "`breath` INT NOT NULL,"
- "`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
- "`modification_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
- "PRIMARY KEY (`name`));",
- NULL, NULL, NULL),
- "Failed to create player table");
-
- SQLOK(sqlite3_exec(m_database,
- "CREATE TABLE IF NOT EXISTS `player_metadata` ("
- " `player` VARCHAR(50) NOT NULL,"
- " `metadata` VARCHAR(256) NOT NULL,"
- " `value` TEXT,"
- " PRIMARY KEY(`player`, `metadata`),"
- " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
- NULL, NULL, NULL),
- "Failed to create player metadata table");
-
- SQLOK(sqlite3_exec(m_database,
- "CREATE TABLE IF NOT EXISTS `player_inventories` ("
- " `player` VARCHAR(50) NOT NULL,"
- " `inv_id` INT NOT NULL,"
- " `inv_width` INT NOT NULL,"
- " `inv_name` TEXT NOT NULL DEFAULT '',"
- " `inv_size` INT NOT NULL,"
- " PRIMARY KEY(player, inv_id),"
- " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
- NULL, NULL, NULL),
- "Failed to create player inventory table");
-
- SQLOK(sqlite3_exec(m_database,
- "CREATE TABLE `player_inventory_items` ("
- " `player` VARCHAR(50) NOT NULL,"
- " `inv_id` INT NOT NULL,"
- " `slot_id` INT NOT NULL,"
- " `item` TEXT NOT NULL DEFAULT '',"
- " PRIMARY KEY(player, inv_id, slot_id),"
- " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
- NULL, NULL, NULL),
- "Failed to create player inventory items table");
-}
-
-void PlayerDatabaseSQLite3::initStatements()
-{
- PREPARE_STATEMENT(player_load, "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, "
- "`breath`"
- "FROM `player` WHERE `name` = ?")
- PREPARE_STATEMENT(player_add, "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, "
- "`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
- PREPARE_STATEMENT(player_update, "UPDATE `player` SET `pitch` = ?, `yaw` = ?, "
- "`posX` = ?, `posY` = ?, `posZ` = ?, `hp` = ?, `breath` = ?, "
- "`modification_date` = CURRENT_TIMESTAMP WHERE `name` = ?")
- PREPARE_STATEMENT(player_remove, "DELETE FROM `player` WHERE `name` = ?")
- PREPARE_STATEMENT(player_list, "SELECT `name` FROM `player`")
-
- PREPARE_STATEMENT(player_add_inventory, "INSERT INTO `player_inventories` "
- "(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) VALUES (?, ?, ?, ?, ?)")
- PREPARE_STATEMENT(player_add_inventory_items, "INSERT INTO `player_inventory_items` "
- "(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)")
- PREPARE_STATEMENT(player_remove_inventory, "DELETE FROM `player_inventories` "
- "WHERE `player` = ?")
- PREPARE_STATEMENT(player_remove_inventory_items, "DELETE FROM `player_inventory_items` "
- "WHERE `player` = ?")
- PREPARE_STATEMENT(player_load_inventory, "SELECT `inv_id`, `inv_width`, `inv_name`, "
- "`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER BY inv_id")
- PREPARE_STATEMENT(player_load_inventory_items, "SELECT `slot_id`, `item` "
- "FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = ?")
-
- PREPARE_STATEMENT(player_metadata_load, "SELECT `metadata`, `value` FROM "
- "`player_metadata` WHERE `player` = ?")
- PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadata` "
- "(`player`, `metadata`, `value`) VALUES (?, ?, ?)")
- PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` "
- "WHERE `player` = ?")
- verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl;
-}
-
-bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name)
-{
- verifyDatabase();
- str_to_sqlite(m_stmt_player_load, 1, name);
- bool res = (sqlite3_step(m_stmt_player_load) == SQLITE_ROW);
- sqlite3_reset(m_stmt_player_load);
- return res;
-}
-
-void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player)
-{
- PlayerSAO* sao = player->getPlayerSAO();
- sanity_check(sao);
-
- const v3f &pos = sao->getBasePosition();
- // Begin save in brace is mandatory
- if (!playerDataExists(player->getName())) {
- beginSave();
- str_to_sqlite(m_stmt_player_add, 1, player->getName());
- double_to_sqlite(m_stmt_player_add, 2, sao->getPitch());
- double_to_sqlite(m_stmt_player_add, 3, sao->getYaw());
- double_to_sqlite(m_stmt_player_add, 4, pos.X);
- double_to_sqlite(m_stmt_player_add, 5, pos.Y);
- double_to_sqlite(m_stmt_player_add, 6, pos.Z);
- int64_to_sqlite(m_stmt_player_add, 7, sao->getHP());
- int64_to_sqlite(m_stmt_player_add, 8, sao->getBreath());
-
- sqlite3_vrfy(sqlite3_step(m_stmt_player_add), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_add);
- } else {
- beginSave();
- double_to_sqlite(m_stmt_player_update, 1, sao->getPitch());
- double_to_sqlite(m_stmt_player_update, 2, sao->getYaw());
- double_to_sqlite(m_stmt_player_update, 3, pos.X);
- double_to_sqlite(m_stmt_player_update, 4, pos.Y);
- double_to_sqlite(m_stmt_player_update, 5, pos.Z);
- int64_to_sqlite(m_stmt_player_update, 6, sao->getHP());
- int64_to_sqlite(m_stmt_player_update, 7, sao->getBreath());
- str_to_sqlite(m_stmt_player_update, 8, player->getName());
-
- sqlite3_vrfy(sqlite3_step(m_stmt_player_update), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_update);
- }
-
- // Write player inventories
- str_to_sqlite(m_stmt_player_remove_inventory, 1, player->getName());
- sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_remove_inventory);
-
- str_to_sqlite(m_stmt_player_remove_inventory_items, 1, player->getName());
- sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_remove_inventory_items);
-
- std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists();
- for (u16 i = 0; i < inventory_lists.size(); i++) {
- const InventoryList* list = inventory_lists[i];
-
- str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName());
- int_to_sqlite(m_stmt_player_add_inventory, 2, i);
- int_to_sqlite(m_stmt_player_add_inventory, 3, list->getWidth());
- str_to_sqlite(m_stmt_player_add_inventory, 4, list->getName());
- int_to_sqlite(m_stmt_player_add_inventory, 5, list->getSize());
- sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_add_inventory);
-
- for (u32 j = 0; j < list->getSize(); j++) {
- std::ostringstream os;
- list->getItem(j).serialize(os);
- std::string itemStr = os.str();
-
- str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName());
- int_to_sqlite(m_stmt_player_add_inventory_items, 2, i);
- int_to_sqlite(m_stmt_player_add_inventory_items, 3, j);
- str_to_sqlite(m_stmt_player_add_inventory_items, 4, itemStr);
- sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_add_inventory_items);
- }
- }
-
- str_to_sqlite(m_stmt_player_metadata_remove, 1, player->getName());
- sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_remove), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_metadata_remove);
-
- const PlayerAttributes &attrs = sao->getExtendedAttributes();
- for (const auto &attr : attrs) {
- str_to_sqlite(m_stmt_player_metadata_add, 1, player->getName());
- str_to_sqlite(m_stmt_player_metadata_add, 2, attr.first);
- str_to_sqlite(m_stmt_player_metadata_add, 3, attr.second);
- sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_add), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_metadata_add);
- }
-
- endSave();
-}
-
-bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
-{
- verifyDatabase();
-
- str_to_sqlite(m_stmt_player_load, 1, player->getName());
- if (sqlite3_step(m_stmt_player_load) != SQLITE_ROW) {
- sqlite3_reset(m_stmt_player_load);
- return false;
- }
- sao->setPitch(sqlite_to_float(m_stmt_player_load, 0));
- sao->setYaw(sqlite_to_float(m_stmt_player_load, 1));
- sao->setBasePosition(sqlite_to_v3f(m_stmt_player_load, 2));
- sao->setHPRaw((s16) MYMIN(sqlite_to_int(m_stmt_player_load, 5), S16_MAX));
- sao->setBreath((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false);
- sqlite3_reset(m_stmt_player_load);
-
- // Load inventory
- str_to_sqlite(m_stmt_player_load_inventory, 1, player->getName());
- while (sqlite3_step(m_stmt_player_load_inventory) == SQLITE_ROW) {
- InventoryList *invList = player->inventory.addList(
- sqlite_to_string(m_stmt_player_load_inventory, 2),
- sqlite_to_uint(m_stmt_player_load_inventory, 3));
- invList->setWidth(sqlite_to_uint(m_stmt_player_load_inventory, 1));
-
- u32 invId = sqlite_to_uint(m_stmt_player_load_inventory, 0);
-
- str_to_sqlite(m_stmt_player_load_inventory_items, 1, player->getName());
- int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId);
- while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) {
- const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1);
- if (itemStr.length() > 0) {
- ItemStack stack;
- stack.deSerialize(itemStr);
- invList->changeItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack);
- }
- }
- sqlite3_reset(m_stmt_player_load_inventory_items);
- }
-
- sqlite3_reset(m_stmt_player_load_inventory);
-
- str_to_sqlite(m_stmt_player_metadata_load, 1, sao->getPlayer()->getName());
- while (sqlite3_step(m_stmt_player_metadata_load) == SQLITE_ROW) {
- std::string attr = sqlite_to_string(m_stmt_player_metadata_load, 0);
- std::string value = sqlite_to_string(m_stmt_player_metadata_load, 1);
-
- sao->setExtendedAttribute(attr, value);
- }
- sqlite3_reset(m_stmt_player_metadata_load);
- return true;
-}
-
-bool PlayerDatabaseSQLite3::removePlayer(const std::string &name)
-{
- if (!playerDataExists(name))
- return false;
-
- str_to_sqlite(m_stmt_player_remove, 1, name);
- sqlite3_vrfy(sqlite3_step(m_stmt_player_remove), SQLITE_DONE);
- sqlite3_reset(m_stmt_player_remove);
- return true;
-}
-
-void PlayerDatabaseSQLite3::listPlayers(std::vector<std::string> &res)
-{
- verifyDatabase();
-
- while (sqlite3_step(m_stmt_player_list) == SQLITE_ROW)
- res.push_back(sqlite_to_string(m_stmt_player_list, 0));
-
- sqlite3_reset(m_stmt_player_list);
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <cstring>
-#include <string>
-#include "database.h"
-#include "exceptions.h"
-
-extern "C" {
-#include "sqlite3.h"
-}
-
-class Database_SQLite3 : public Database
-{
-public:
- virtual ~Database_SQLite3();
-
- void beginSave();
- void endSave();
-
- bool initialized() const { return m_initialized; }
-protected:
- Database_SQLite3(const std::string &savedir, const std::string &dbname);
-
- // Open and initialize the database if needed
- void verifyDatabase();
-
- // Convertors
- inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const std::string &str) const
- {
- sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.c_str(), str.size(), NULL));
- }
-
- inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const char *str) const
- {
- sqlite3_vrfy(sqlite3_bind_text(s, iCol, str, strlen(str), NULL));
- }
-
- inline void int_to_sqlite(sqlite3_stmt *s, int iCol, int val) const
- {
- sqlite3_vrfy(sqlite3_bind_int(s, iCol, val));
- }
-
- inline void int64_to_sqlite(sqlite3_stmt *s, int iCol, s64 val) const
- {
- sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_int64) val));
- }
-
- inline void double_to_sqlite(sqlite3_stmt *s, int iCol, double val) const
- {
- sqlite3_vrfy(sqlite3_bind_double(s, iCol, val));
- }
-
- inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol)
- {
- const char* text = reinterpret_cast<const char*>(sqlite3_column_text(s, iCol));
- return std::string(text ? text : "");
- }
-
- inline s32 sqlite_to_int(sqlite3_stmt *s, int iCol)
- {
- return sqlite3_column_int(s, iCol);
- }
-
- inline u32 sqlite_to_uint(sqlite3_stmt *s, int iCol)
- {
- return (u32) sqlite3_column_int(s, iCol);
- }
-
- inline float sqlite_to_float(sqlite3_stmt *s, int iCol)
- {
- return (float) sqlite3_column_double(s, iCol);
- }
-
- inline const v3f sqlite_to_v3f(sqlite3_stmt *s, int iCol)
- {
- return v3f(sqlite_to_float(s, iCol), sqlite_to_float(s, iCol + 1),
- sqlite_to_float(s, iCol + 2));
- }
-
- // Query verifiers helpers
- inline void sqlite3_vrfy(int s, const std::string &m = "", int r = SQLITE_OK) const
- {
- if (s != r)
- throw DatabaseException(m + ": " + sqlite3_errmsg(m_database));
- }
-
- inline void sqlite3_vrfy(const int s, const int r, const std::string &m = "") const
- {
- sqlite3_vrfy(s, m, r);
- }
-
- // Create the database structure
- virtual void createDatabase() = 0;
- virtual void initStatements() = 0;
-
- sqlite3 *m_database = nullptr;
-private:
- // Open the database
- void openDatabase();
-
- bool m_initialized = false;
-
- std::string m_savedir = "";
- std::string m_dbname = "";
-
- sqlite3_stmt *m_stmt_begin = nullptr;
- sqlite3_stmt *m_stmt_end = nullptr;
-
- s64 m_busy_handler_data[2];
-
- static int busyHandler(void *data, int count);
-};
-
-class MapDatabaseSQLite3 : private Database_SQLite3, public MapDatabase
-{
-public:
- MapDatabaseSQLite3(const std::string &savedir);
- virtual ~MapDatabaseSQLite3();
-
- bool saveBlock(const v3s16 &pos, const std::string &data);
- void loadBlock(const v3s16 &pos, std::string *block);
- bool deleteBlock(const v3s16 &pos);
- void listAllLoadableBlocks(std::vector<v3s16> &dst);
-
- void beginSave() { Database_SQLite3::beginSave(); }
- void endSave() { Database_SQLite3::endSave(); }
-protected:
- virtual void createDatabase();
- virtual void initStatements();
-
-private:
- void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1);
-
- // Map
- sqlite3_stmt *m_stmt_read = nullptr;
- sqlite3_stmt *m_stmt_write = nullptr;
- sqlite3_stmt *m_stmt_list = nullptr;
- sqlite3_stmt *m_stmt_delete = nullptr;
-};
-
-class PlayerDatabaseSQLite3 : private Database_SQLite3, public PlayerDatabase
-{
-public:
- PlayerDatabaseSQLite3(const std::string &savedir);
- virtual ~PlayerDatabaseSQLite3();
-
- void savePlayer(RemotePlayer *player);
- bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
- bool removePlayer(const std::string &name);
- void listPlayers(std::vector<std::string> &res);
-
-protected:
- virtual void createDatabase();
- virtual void initStatements();
-
-private:
- bool playerDataExists(const std::string &name);
-
- // Players
- sqlite3_stmt *m_stmt_player_load = nullptr;
- sqlite3_stmt *m_stmt_player_add = nullptr;
- sqlite3_stmt *m_stmt_player_update = nullptr;
- sqlite3_stmt *m_stmt_player_remove = nullptr;
- sqlite3_stmt *m_stmt_player_list = nullptr;
- sqlite3_stmt *m_stmt_player_load_inventory = nullptr;
- sqlite3_stmt *m_stmt_player_load_inventory_items = nullptr;
- sqlite3_stmt *m_stmt_player_add_inventory = nullptr;
- sqlite3_stmt *m_stmt_player_add_inventory_items = nullptr;
- sqlite3_stmt *m_stmt_player_remove_inventory = nullptr;
- sqlite3_stmt *m_stmt_player_remove_inventory_items = nullptr;
- sqlite3_stmt *m_stmt_player_metadata_load = nullptr;
- sqlite3_stmt *m_stmt_player_metadata_remove = nullptr;
- sqlite3_stmt *m_stmt_player_metadata_add = nullptr;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "database.h"
-#include "irrlichttypes.h"
-
-
-/****************
- * Black magic! *
- ****************
- * The position hashing is very messed up.
- * It's a lot more complicated than it looks.
- */
-
-static inline s16 unsigned_to_signed(u16 i, u16 max_positive)
-{
- if (i < max_positive) {
- return i;
- }
-
- return i - (max_positive * 2);
-}
-
-
-// Modulo of a negative number does not work consistently in C
-static inline s64 pythonmodulo(s64 i, s16 mod)
-{
- if (i >= 0) {
- return i % mod;
- }
- return mod - ((-i) % mod);
-}
-
-
-s64 MapDatabase::getBlockAsInteger(const v3s16 &pos)
-{
- return (u64) pos.Z * 0x1000000 +
- (u64) pos.Y * 0x1000 +
- (u64) pos.X;
-}
-
-
-v3s16 MapDatabase::getIntegerAsBlock(s64 i)
-{
- v3s16 pos;
- pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
- i = (i - pos.X) / 4096;
- pos.Y = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
- i = (i - pos.Y) / 4096;
- pos.Z = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
- return pos;
-}
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <string>
-#include <vector>
-#include "irr_v3d.h"
-#include "irrlichttypes.h"
-#include "util/basic_macros.h"
-
-class Database
-{
-public:
- virtual void beginSave() = 0;
- virtual void endSave() = 0;
- virtual bool initialized() const { return true; }
-};
-
-class MapDatabase : public Database
-{
-public:
- virtual ~MapDatabase() = default;
-
- virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
- virtual void loadBlock(const v3s16 &pos, std::string *block) = 0;
- virtual bool deleteBlock(const v3s16 &pos) = 0;
-
- static s64 getBlockAsInteger(const v3s16 &pos);
- static v3s16 getIntegerAsBlock(s64 i);
-
- virtual void listAllLoadableBlocks(std::vector<v3s16> &dst) = 0;
-};
-
-class PlayerSAO;
-class RemotePlayer;
-
-class PlayerDatabase
-{
-public:
- virtual ~PlayerDatabase() = default;
-
- virtual void savePlayer(RemotePlayer *player) = 0;
- virtual bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) = 0;
- virtual bool removePlayer(const std::string &name) = 0;
- virtual void listPlayers(std::vector<std::string> &res) = 0;
-};
--- /dev/null
+set(database_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/database.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/database-dummy.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/database-files.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/database-leveldb.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/database-postgresql.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/database-redis.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/database-sqlite3.cpp
+ PARENT_SCOPE
+)
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+/*
+Dummy database class
+*/
+
+#include "database-dummy.h"
+
+
+bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data)
+{
+ m_database[getBlockAsInteger(pos)] = data;
+ return true;
+}
+
+void Database_Dummy::loadBlock(const v3s16 &pos, std::string *block)
+{
+ s64 i = getBlockAsInteger(pos);
+ auto it = m_database.find(i);
+ if (it == m_database.end()) {
+ *block = "";
+ return;
+ }
+
+ *block = it->second;
+}
+
+bool Database_Dummy::deleteBlock(const v3s16 &pos)
+{
+ m_database.erase(getBlockAsInteger(pos));
+ return true;
+}
+
+void Database_Dummy::listAllLoadableBlocks(std::vector<v3s16> &dst)
+{
+ dst.reserve(m_database.size());
+ for (std::map<s64, std::string>::const_iterator x = m_database.begin();
+ x != m_database.end(); ++x) {
+ dst.push_back(getIntegerAsBlock(x->first));
+ }
+}
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <map>
+#include <string>
+#include "database.h"
+#include "irrlichttypes.h"
+
+class Database_Dummy : public MapDatabase, public PlayerDatabase
+{
+public:
+ bool saveBlock(const v3s16 &pos, const std::string &data);
+ void loadBlock(const v3s16 &pos, std::string *block);
+ bool deleteBlock(const v3s16 &pos);
+ void listAllLoadableBlocks(std::vector<v3s16> &dst);
+
+ void savePlayer(RemotePlayer *player) {}
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) { return true; }
+ bool removePlayer(const std::string &name) { return true; }
+ void listPlayers(std::vector<std::string> &res) {}
+
+ void beginSave() {}
+ void endSave() {}
+
+private:
+ std::map<s64, std::string> m_database;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <cassert>
+#include <json/json.h>
+#include "database-files.h"
+#include "content_sao.h"
+#include "remoteplayer.h"
+#include "settings.h"
+#include "porting.h"
+#include "filesys.h"
+
+// !!! WARNING !!!
+// This backend is intended to be used on Minetest 0.4.16 only for the transition backend
+// for player files
+
+void PlayerDatabaseFiles::serialize(std::ostringstream &os, RemotePlayer *player)
+{
+ // Utilize a Settings object for storing values
+ Settings args;
+ args.setS32("version", 1);
+ args.set("name", player->getName());
+
+ sanity_check(player->getPlayerSAO());
+ args.setS32("hp", player->getPlayerSAO()->getHP());
+ args.setV3F("position", player->getPlayerSAO()->getBasePosition());
+ args.setFloat("pitch", player->getPlayerSAO()->getPitch());
+ args.setFloat("yaw", player->getPlayerSAO()->getYaw());
+ args.setS32("breath", player->getPlayerSAO()->getBreath());
+
+ std::string extended_attrs;
+ player->serializeExtraAttributes(extended_attrs);
+ args.set("extended_attributes", extended_attrs);
+
+ args.writeLines(os);
+
+ os << "PlayerArgsEnd\n";
+
+ player->inventory.serialize(os);
+}
+
+void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
+{
+ std::string savedir = m_savedir + DIR_DELIM;
+ std::string path = savedir + player->getName();
+ bool path_found = false;
+ RemotePlayer testplayer("", NULL);
+
+ for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES && !path_found; i++) {
+ if (!fs::PathExists(path)) {
+ path_found = true;
+ continue;
+ }
+
+ // Open and deserialize file to check player name
+ std::ifstream is(path.c_str(), std::ios_base::binary);
+ if (!is.good()) {
+ errorstream << "Failed to open " << path << std::endl;
+ return;
+ }
+
+ testplayer.deSerialize(is, path, NULL);
+ is.close();
+ if (strcmp(testplayer.getName(), player->getName()) == 0) {
+ path_found = true;
+ continue;
+ }
+
+ path = savedir + player->getName() + itos(i);
+ }
+
+ if (!path_found) {
+ errorstream << "Didn't find free file for player " << player->getName()
+ << std::endl;
+ return;
+ }
+
+ // Open and serialize file
+ std::ostringstream ss(std::ios_base::binary);
+ serialize(ss, player);
+ if (!fs::safeWriteToFile(path, ss.str())) {
+ infostream << "Failed to write " << path << std::endl;
+ }
+ player->setModified(false);
+}
+
+bool PlayerDatabaseFiles::removePlayer(const std::string &name)
+{
+ std::string players_path = m_savedir + DIR_DELIM;
+ std::string path = players_path + name;
+
+ RemotePlayer temp_player("", NULL);
+ for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
+ // Open file and deserialize
+ std::ifstream is(path.c_str(), std::ios_base::binary);
+ if (!is.good())
+ continue;
+
+ temp_player.deSerialize(is, path, NULL);
+ is.close();
+
+ if (temp_player.getName() == name) {
+ fs::DeleteSingleFileOrEmptyDirectory(path);
+ return true;
+ }
+
+ path = players_path + name + itos(i);
+ }
+
+ return false;
+}
+
+bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
+{
+ std::string players_path = m_savedir + DIR_DELIM;
+ std::string path = players_path + player->getName();
+
+ const std::string player_to_load = player->getName();
+ for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
+ // Open file and deserialize
+ std::ifstream is(path.c_str(), std::ios_base::binary);
+ if (!is.good())
+ continue;
+
+ player->deSerialize(is, path, sao);
+ is.close();
+
+ if (player->getName() == player_to_load)
+ return true;
+
+ path = players_path + player_to_load + itos(i);
+ }
+
+ infostream << "Player file for player " << player_to_load << " not found" << std::endl;
+ return false;
+}
+
+void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
+{
+ std::vector<fs::DirListNode> files = fs::GetDirListing(m_savedir);
+ // list files into players directory
+ for (std::vector<fs::DirListNode>::const_iterator it = files.begin(); it !=
+ files.end(); ++it) {
+ // Ignore directories
+ if (it->dir)
+ continue;
+
+ const std::string &filename = it->name;
+ std::string full_path = m_savedir + DIR_DELIM + filename;
+ std::ifstream is(full_path.c_str(), std::ios_base::binary);
+ if (!is.good())
+ continue;
+
+ RemotePlayer player(filename.c_str(), NULL);
+ // Null env & dummy peer_id
+ PlayerSAO playerSAO(NULL, &player, 15789, false);
+
+ player.deSerialize(is, "", &playerSAO);
+ is.close();
+
+ res.emplace_back(player.getName());
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2017 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+// !!! WARNING !!!
+// This backend is intended to be used on Minetest 0.4.16 only for the transition backend
+// for player files
+
+#include "database.h"
+
+class PlayerDatabaseFiles : public PlayerDatabase
+{
+public:
+ PlayerDatabaseFiles(const std::string &savedir) : m_savedir(savedir) {}
+ virtual ~PlayerDatabaseFiles() = default;
+
+ void savePlayer(RemotePlayer *player);
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
+ bool removePlayer(const std::string &name);
+ void listPlayers(std::vector<std::string> &res);
+
+private:
+ void serialize(std::ostringstream &os, RemotePlayer *player);
+
+ std::string m_savedir;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "config.h"
+
+#if USE_LEVELDB
+
+#include "database-leveldb.h"
+
+#include "log.h"
+#include "filesys.h"
+#include "exceptions.h"
+#include "util/string.h"
+
+#include "leveldb/db.h"
+
+
+#define ENSURE_STATUS_OK(s) \
+ if (!(s).ok()) { \
+ throw DatabaseException(std::string("LevelDB error: ") + \
+ (s).ToString()); \
+ }
+
+
+Database_LevelDB::Database_LevelDB(const std::string &savedir)
+{
+ leveldb::Options options;
+ options.create_if_missing = true;
+ leveldb::Status status = leveldb::DB::Open(options,
+ savedir + DIR_DELIM + "map.db", &m_database);
+ ENSURE_STATUS_OK(status);
+}
+
+Database_LevelDB::~Database_LevelDB()
+{
+ delete m_database;
+}
+
+bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data)
+{
+ leveldb::Status status = m_database->Put(leveldb::WriteOptions(),
+ i64tos(getBlockAsInteger(pos)), data);
+ if (!status.ok()) {
+ warningstream << "saveBlock: LevelDB error saving block "
+ << PP(pos) << ": " << status.ToString() << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+void Database_LevelDB::loadBlock(const v3s16 &pos, std::string *block)
+{
+ std::string datastr;
+ leveldb::Status status = m_database->Get(leveldb::ReadOptions(),
+ i64tos(getBlockAsInteger(pos)), &datastr);
+
+ *block = (status.ok()) ? datastr : "";
+}
+
+bool Database_LevelDB::deleteBlock(const v3s16 &pos)
+{
+ leveldb::Status status = m_database->Delete(leveldb::WriteOptions(),
+ i64tos(getBlockAsInteger(pos)));
+ if (!status.ok()) {
+ warningstream << "deleteBlock: LevelDB error deleting block "
+ << PP(pos) << ": " << status.ToString() << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+void Database_LevelDB::listAllLoadableBlocks(std::vector<v3s16> &dst)
+{
+ leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions());
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ dst.push_back(getIntegerAsBlock(stoi64(it->key().ToString())));
+ }
+ ENSURE_STATUS_OK(it->status()); // Check for any errors found during the scan
+ delete it;
+}
+
+#endif // USE_LEVELDB
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "config.h"
+
+#if USE_LEVELDB
+
+#include <string>
+#include "database.h"
+#include "leveldb/db.h"
+
+class Database_LevelDB : public MapDatabase
+{
+public:
+ Database_LevelDB(const std::string &savedir);
+ ~Database_LevelDB();
+
+ bool saveBlock(const v3s16 &pos, const std::string &data);
+ void loadBlock(const v3s16 &pos, std::string *block);
+ bool deleteBlock(const v3s16 &pos);
+ void listAllLoadableBlocks(std::vector<v3s16> &dst);
+
+ void beginSave() {}
+ void endSave() {}
+
+private:
+ leveldb::DB *m_database;
+};
+
+#endif // USE_LEVELDB
--- /dev/null
+/*
+Copyright (C) 2016 Loic Blot <loic.blot@unix-experience.fr>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "config.h"
+
+#if USE_POSTGRESQL
+
+#include "database-postgresql.h"
+
+#ifdef _WIN32
+ // Without this some of the network functions are not found on mingw
+ #ifndef _WIN32_WINNT
+ #define _WIN32_WINNT 0x0501
+ #endif
+ #include <windows.h>
+ #include <winsock2.h>
+#else
+#include <netinet/in.h>
+#endif
+
+#include "debug.h"
+#include "exceptions.h"
+#include "settings.h"
+#include "content_sao.h"
+#include "remoteplayer.h"
+
+Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) :
+ m_connect_string(connect_string)
+{
+ if (m_connect_string.empty()) {
+ throw SettingNotFoundException(
+ "Set pgsql_connection string in world.mt to "
+ "use the postgresql backend\n"
+ "Notes:\n"
+ "pgsql_connection has the following form: \n"
+ "\tpgsql_connection = host=127.0.0.1 port=5432 user=mt_user "
+ "password=mt_password dbname=minetest_world\n"
+ "mt_user should have CREATE TABLE, INSERT, SELECT, UPDATE and "
+ "DELETE rights on the database.\n"
+ "Don't create mt_user as a SUPERUSER!");
+ }
+}
+
+Database_PostgreSQL::~Database_PostgreSQL()
+{
+ PQfinish(m_conn);
+}
+
+void Database_PostgreSQL::connectToDatabase()
+{
+ m_conn = PQconnectdb(m_connect_string.c_str());
+
+ if (PQstatus(m_conn) != CONNECTION_OK) {
+ throw DatabaseException(std::string(
+ "PostgreSQL database error: ") +
+ PQerrorMessage(m_conn));
+ }
+
+ m_pgversion = PQserverVersion(m_conn);
+
+ /*
+ * We are using UPSERT feature from PostgreSQL 9.5
+ * to have the better performance where possible.
+ */
+ if (m_pgversion < 90500) {
+ warningstream << "Your PostgreSQL server lacks UPSERT "
+ << "support. Use version 9.5 or better if possible."
+ << std::endl;
+ }
+
+ infostream << "PostgreSQL Database: Version " << m_pgversion
+ << " Connection made." << std::endl;
+
+ createDatabase();
+ initStatements();
+}
+
+void Database_PostgreSQL::verifyDatabase()
+{
+ if (PQstatus(m_conn) == CONNECTION_OK)
+ return;
+
+ PQreset(m_conn);
+ ping();
+}
+
+void Database_PostgreSQL::ping()
+{
+ if (PQping(m_connect_string.c_str()) != PQPING_OK) {
+ throw DatabaseException(std::string(
+ "PostgreSQL database error: ") +
+ PQerrorMessage(m_conn));
+ }
+}
+
+bool Database_PostgreSQL::initialized() const
+{
+ return (PQstatus(m_conn) == CONNECTION_OK);
+}
+
+PGresult *Database_PostgreSQL::checkResults(PGresult *result, bool clear)
+{
+ ExecStatusType statusType = PQresultStatus(result);
+
+ switch (statusType) {
+ case PGRES_COMMAND_OK:
+ case PGRES_TUPLES_OK:
+ break;
+ case PGRES_FATAL_ERROR:
+ default:
+ throw DatabaseException(
+ std::string("PostgreSQL database error: ") +
+ PQresultErrorMessage(result));
+ }
+
+ if (clear)
+ PQclear(result);
+
+ return result;
+}
+
+void Database_PostgreSQL::createTableIfNotExists(const std::string &table_name,
+ const std::string &definition)
+{
+ std::string sql_check_table = "SELECT relname FROM pg_class WHERE relname='" +
+ table_name + "';";
+ PGresult *result = checkResults(PQexec(m_conn, sql_check_table.c_str()), false);
+
+ // If table doesn't exist, create it
+ if (!PQntuples(result)) {
+ checkResults(PQexec(m_conn, definition.c_str()));
+ }
+
+ PQclear(result);
+}
+
+void Database_PostgreSQL::beginSave()
+{
+ verifyDatabase();
+ checkResults(PQexec(m_conn, "BEGIN;"));
+}
+
+void Database_PostgreSQL::endSave()
+{
+ checkResults(PQexec(m_conn, "COMMIT;"));
+}
+
+MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string):
+ Database_PostgreSQL(connect_string),
+ MapDatabase()
+{
+ connectToDatabase();
+}
+
+
+void MapDatabasePostgreSQL::createDatabase()
+{
+ createTableIfNotExists("blocks",
+ "CREATE TABLE blocks ("
+ "posX INT NOT NULL,"
+ "posY INT NOT NULL,"
+ "posZ INT NOT NULL,"
+ "data BYTEA,"
+ "PRIMARY KEY (posX,posY,posZ)"
+ ");"
+ );
+
+ infostream << "PostgreSQL: Map Database was initialized." << std::endl;
+}
+
+void MapDatabasePostgreSQL::initStatements()
+{
+ prepareStatement("read_block",
+ "SELECT data FROM blocks "
+ "WHERE posX = $1::int4 AND posY = $2::int4 AND "
+ "posZ = $3::int4");
+
+ if (getPGVersion() < 90500) {
+ prepareStatement("write_block_insert",
+ "INSERT INTO blocks (posX, posY, posZ, data) SELECT "
+ "$1::int4, $2::int4, $3::int4, $4::bytea "
+ "WHERE NOT EXISTS (SELECT true FROM blocks "
+ "WHERE posX = $1::int4 AND posY = $2::int4 AND "
+ "posZ = $3::int4)");
+
+ prepareStatement("write_block_update",
+ "UPDATE blocks SET data = $4::bytea "
+ "WHERE posX = $1::int4 AND posY = $2::int4 AND "
+ "posZ = $3::int4");
+ } else {
+ prepareStatement("write_block",
+ "INSERT INTO blocks (posX, posY, posZ, data) VALUES "
+ "($1::int4, $2::int4, $3::int4, $4::bytea) "
+ "ON CONFLICT ON CONSTRAINT blocks_pkey DO "
+ "UPDATE SET data = $4::bytea");
+ }
+
+ prepareStatement("delete_block", "DELETE FROM blocks WHERE "
+ "posX = $1::int4 AND posY = $2::int4 AND posZ = $3::int4");
+
+ prepareStatement("list_all_loadable_blocks",
+ "SELECT posX, posY, posZ FROM blocks");
+}
+
+bool MapDatabasePostgreSQL::saveBlock(const v3s16 &pos, const std::string &data)
+{
+ // Verify if we don't overflow the platform integer with the mapblock size
+ if (data.size() > INT_MAX) {
+ errorstream << "Database_PostgreSQL::saveBlock: Data truncation! "
+ << "data.size() over 0xFFFFFFFF (== " << data.size()
+ << ")" << std::endl;
+ return false;
+ }
+
+ verifyDatabase();
+
+ s32 x, y, z;
+ x = htonl(pos.X);
+ y = htonl(pos.Y);
+ z = htonl(pos.Z);
+
+ const void *args[] = { &x, &y, &z, data.c_str() };
+ const int argLen[] = {
+ sizeof(x), sizeof(y), sizeof(z), (int)data.size()
+ };
+ const int argFmt[] = { 1, 1, 1, 1 };
+
+ if (getPGVersion() < 90500) {
+ execPrepared("write_block_update", ARRLEN(args), args, argLen, argFmt);
+ execPrepared("write_block_insert", ARRLEN(args), args, argLen, argFmt);
+ } else {
+ execPrepared("write_block", ARRLEN(args), args, argLen, argFmt);
+ }
+ return true;
+}
+
+void MapDatabasePostgreSQL::loadBlock(const v3s16 &pos, std::string *block)
+{
+ verifyDatabase();
+
+ s32 x, y, z;
+ x = htonl(pos.X);
+ y = htonl(pos.Y);
+ z = htonl(pos.Z);
+
+ const void *args[] = { &x, &y, &z };
+ const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
+ const int argFmt[] = { 1, 1, 1 };
+
+ PGresult *results = execPrepared("read_block", ARRLEN(args), args,
+ argLen, argFmt, false);
+
+ *block = "";
+
+ if (PQntuples(results))
+ *block = std::string(PQgetvalue(results, 0, 0), PQgetlength(results, 0, 0));
+
+ PQclear(results);
+}
+
+bool MapDatabasePostgreSQL::deleteBlock(const v3s16 &pos)
+{
+ verifyDatabase();
+
+ s32 x, y, z;
+ x = htonl(pos.X);
+ y = htonl(pos.Y);
+ z = htonl(pos.Z);
+
+ const void *args[] = { &x, &y, &z };
+ const int argLen[] = { sizeof(x), sizeof(y), sizeof(z) };
+ const int argFmt[] = { 1, 1, 1 };
+
+ execPrepared("delete_block", ARRLEN(args), args, argLen, argFmt);
+
+ return true;
+}
+
+void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
+{
+ verifyDatabase();
+
+ PGresult *results = execPrepared("list_all_loadable_blocks", 0,
+ NULL, NULL, NULL, false, false);
+
+ int numrows = PQntuples(results);
+
+ for (int row = 0; row < numrows; ++row)
+ dst.push_back(pg_to_v3s16(results, 0, 0));
+
+ PQclear(results);
+}
+
+/*
+ * Player Database
+ */
+PlayerDatabasePostgreSQL::PlayerDatabasePostgreSQL(const std::string &connect_string):
+ Database_PostgreSQL(connect_string),
+ PlayerDatabase()
+{
+ connectToDatabase();
+}
+
+
+void PlayerDatabasePostgreSQL::createDatabase()
+{
+ createTableIfNotExists("player",
+ "CREATE TABLE player ("
+ "name VARCHAR(60) NOT NULL,"
+ "pitch NUMERIC(15, 7) NOT NULL,"
+ "yaw NUMERIC(15, 7) NOT NULL,"
+ "posX NUMERIC(15, 7) NOT NULL,"
+ "posY NUMERIC(15, 7) NOT NULL,"
+ "posZ NUMERIC(15, 7) NOT NULL,"
+ "hp INT NOT NULL,"
+ "breath INT NOT NULL,"
+ "creation_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
+ "modification_date TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),"
+ "PRIMARY KEY (name)"
+ ");"
+ );
+
+ createTableIfNotExists("player_inventories",
+ "CREATE TABLE player_inventories ("
+ "player VARCHAR(60) NOT NULL,"
+ "inv_id INT NOT NULL,"
+ "inv_width INT NOT NULL,"
+ "inv_name TEXT NOT NULL DEFAULT '',"
+ "inv_size INT NOT NULL,"
+ "PRIMARY KEY(player, inv_id),"
+ "CONSTRAINT player_inventories_fkey FOREIGN KEY (player) REFERENCES "
+ "player (name) ON DELETE CASCADE"
+ ");"
+ );
+
+ createTableIfNotExists("player_inventory_items",
+ "CREATE TABLE player_inventory_items ("
+ "player VARCHAR(60) NOT NULL,"
+ "inv_id INT NOT NULL,"
+ "slot_id INT NOT NULL,"
+ "item TEXT NOT NULL DEFAULT '',"
+ "PRIMARY KEY(player, inv_id, slot_id),"
+ "CONSTRAINT player_inventory_items_fkey FOREIGN KEY (player) REFERENCES "
+ "player (name) ON DELETE CASCADE"
+ ");"
+ );
+
+ createTableIfNotExists("player_metadata",
+ "CREATE TABLE player_metadata ("
+ "player VARCHAR(60) NOT NULL,"
+ "attr VARCHAR(256) NOT NULL,"
+ "value TEXT,"
+ "PRIMARY KEY(player, attr),"
+ "CONSTRAINT player_metadata_fkey FOREIGN KEY (player) REFERENCES "
+ "player (name) ON DELETE CASCADE"
+ ");"
+ );
+
+ infostream << "PostgreSQL: Player Database was inited." << std::endl;
+}
+
+void PlayerDatabasePostgreSQL::initStatements()
+{
+ if (getPGVersion() < 90500) {
+ prepareStatement("create_player",
+ "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
+ "($1, $2, $3, $4, $5, $6, $7::int, $8::int)");
+
+ prepareStatement("update_player",
+ "UPDATE SET pitch = $2, yaw = $3, posX = $4, posY = $5, posZ = $6, hp = $7::int, "
+ "breath = $8::int, modification_date = NOW() WHERE name = $1");
+ } else {
+ prepareStatement("save_player",
+ "INSERT INTO player(name, pitch, yaw, posX, posY, posZ, hp, breath) VALUES "
+ "($1, $2, $3, $4, $5, $6, $7::int, $8::int)"
+ "ON CONFLICT ON CONSTRAINT player_pkey DO UPDATE SET pitch = $2, yaw = $3, "
+ "posX = $4, posY = $5, posZ = $6, hp = $7::int, breath = $8::int, "
+ "modification_date = NOW()");
+ }
+
+ prepareStatement("remove_player", "DELETE FROM player WHERE name = $1");
+
+ prepareStatement("load_player_list", "SELECT name FROM player");
+
+ prepareStatement("remove_player_inventories",
+ "DELETE FROM player_inventories WHERE player = $1");
+
+ prepareStatement("remove_player_inventory_items",
+ "DELETE FROM player_inventory_items WHERE player = $1");
+
+ prepareStatement("add_player_inventory",
+ "INSERT INTO player_inventories (player, inv_id, inv_width, inv_name, inv_size) VALUES "
+ "($1, $2::int, $3::int, $4, $5::int)");
+
+ prepareStatement("add_player_inventory_item",
+ "INSERT INTO player_inventory_items (player, inv_id, slot_id, item) VALUES "
+ "($1, $2::int, $3::int, $4)");
+
+ prepareStatement("load_player_inventories",
+ "SELECT inv_id, inv_width, inv_name, inv_size FROM player_inventories "
+ "WHERE player = $1 ORDER BY inv_id");
+
+ prepareStatement("load_player_inventory_items",
+ "SELECT slot_id, item FROM player_inventory_items WHERE "
+ "player = $1 AND inv_id = $2::int");
+
+ prepareStatement("load_player",
+ "SELECT pitch, yaw, posX, posY, posZ, hp, breath FROM player WHERE name = $1");
+
+ prepareStatement("remove_player_metadata",
+ "DELETE FROM player_metadata WHERE player = $1");
+
+ prepareStatement("save_player_metadata",
+ "INSERT INTO player_metadata (player, attr, value) VALUES ($1, $2, $3)");
+
+ prepareStatement("load_player_metadata",
+ "SELECT attr, value FROM player_metadata WHERE player = $1");
+
+}
+
+bool PlayerDatabasePostgreSQL::playerDataExists(const std::string &playername)
+{
+ verifyDatabase();
+
+ const char *values[] = { playername.c_str() };
+ PGresult *results = execPrepared("load_player", 1, values, false);
+
+ bool res = (PQntuples(results) > 0);
+ PQclear(results);
+ return res;
+}
+
+void PlayerDatabasePostgreSQL::savePlayer(RemotePlayer *player)
+{
+ PlayerSAO* sao = player->getPlayerSAO();
+ if (!sao)
+ return;
+
+ verifyDatabase();
+
+ v3f pos = sao->getBasePosition();
+ std::string pitch = ftos(sao->getPitch());
+ std::string yaw = ftos(sao->getYaw());
+ std::string posx = ftos(pos.X);
+ std::string posy = ftos(pos.Y);
+ std::string posz = ftos(pos.Z);
+ std::string hp = itos(sao->getHP());
+ std::string breath = itos(sao->getBreath());
+ const char *values[] = {
+ player->getName(),
+ pitch.c_str(),
+ yaw.c_str(),
+ posx.c_str(), posy.c_str(), posz.c_str(),
+ hp.c_str(),
+ breath.c_str()
+ };
+
+ const char* rmvalues[] = { player->getName() };
+ beginSave();
+
+ if (getPGVersion() < 90500) {
+ if (!playerDataExists(player->getName()))
+ execPrepared("create_player", 8, values, true, false);
+ else
+ execPrepared("update_player", 8, values, true, false);
+ }
+ else
+ execPrepared("save_player", 8, values, true, false);
+
+ // Write player inventories
+ execPrepared("remove_player_inventories", 1, rmvalues);
+ execPrepared("remove_player_inventory_items", 1, rmvalues);
+
+ std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists();
+ for (u16 i = 0; i < inventory_lists.size(); i++) {
+ const InventoryList* list = inventory_lists[i];
+ const std::string &name = list->getName();
+ std::string width = itos(list->getWidth()),
+ inv_id = itos(i), lsize = itos(list->getSize());
+
+ const char* inv_values[] = {
+ player->getName(),
+ inv_id.c_str(),
+ width.c_str(),
+ name.c_str(),
+ lsize.c_str()
+ };
+ execPrepared("add_player_inventory", 5, inv_values);
+
+ for (u32 j = 0; j < list->getSize(); j++) {
+ std::ostringstream os;
+ list->getItem(j).serialize(os);
+ std::string itemStr = os.str(), slotId = itos(j);
+
+ const char* invitem_values[] = {
+ player->getName(),
+ inv_id.c_str(),
+ slotId.c_str(),
+ itemStr.c_str()
+ };
+ execPrepared("add_player_inventory_item", 4, invitem_values);
+ }
+ }
+
+ execPrepared("remove_player_metadata", 1, rmvalues);
+ const PlayerAttributes &attrs = sao->getExtendedAttributes();
+ for (const auto &attr : attrs) {
+ const char *meta_values[] = {
+ player->getName(),
+ attr.first.c_str(),
+ attr.second.c_str()
+ };
+ execPrepared("save_player_metadata", 3, meta_values);
+ }
+ endSave();
+}
+
+bool PlayerDatabasePostgreSQL::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
+{
+ sanity_check(sao);
+ verifyDatabase();
+
+ const char *values[] = { player->getName() };
+ PGresult *results = execPrepared("load_player", 1, values, false, false);
+
+ // Player not found, return not found
+ if (!PQntuples(results)) {
+ PQclear(results);
+ return false;
+ }
+
+ sao->setPitch(pg_to_float(results, 0, 0));
+ sao->setYaw(pg_to_float(results, 0, 1));
+ sao->setBasePosition(v3f(
+ pg_to_float(results, 0, 2),
+ pg_to_float(results, 0, 3),
+ pg_to_float(results, 0, 4))
+ );
+ sao->setHPRaw((s16) pg_to_int(results, 0, 5));
+ sao->setBreath((u16) pg_to_int(results, 0, 6), false);
+
+ PQclear(results);
+
+ // Load inventory
+ results = execPrepared("load_player_inventories", 1, values, false, false);
+
+ int resultCount = PQntuples(results);
+
+ for (int row = 0; row < resultCount; ++row) {
+ InventoryList* invList = player->inventory.
+ addList(PQgetvalue(results, row, 2), pg_to_uint(results, row, 3));
+ invList->setWidth(pg_to_uint(results, row, 1));
+
+ u32 invId = pg_to_uint(results, row, 0);
+ std::string invIdStr = itos(invId);
+
+ const char* values2[] = {
+ player->getName(),
+ invIdStr.c_str()
+ };
+ PGresult *results2 = execPrepared("load_player_inventory_items", 2,
+ values2, false, false);
+
+ int resultCount2 = PQntuples(results2);
+ for (int row2 = 0; row2 < resultCount2; row2++) {
+ const std::string itemStr = PQgetvalue(results2, row2, 1);
+ if (itemStr.length() > 0) {
+ ItemStack stack;
+ stack.deSerialize(itemStr);
+ invList->changeItem(pg_to_uint(results2, row2, 0), stack);
+ }
+ }
+ PQclear(results2);
+ }
+
+ PQclear(results);
+
+ results = execPrepared("load_player_metadata", 1, values, false);
+
+ int numrows = PQntuples(results);
+ for (int row = 0; row < numrows; row++) {
+ sao->setExtendedAttribute(PQgetvalue(results, row, 0),PQgetvalue(results, row, 1));
+ }
+
+ PQclear(results);
+
+ return true;
+}
+
+bool PlayerDatabasePostgreSQL::removePlayer(const std::string &name)
+{
+ if (!playerDataExists(name))
+ return false;
+
+ verifyDatabase();
+
+ const char *values[] = { name.c_str() };
+ execPrepared("remove_player", 1, values);
+
+ return true;
+}
+
+void PlayerDatabasePostgreSQL::listPlayers(std::vector<std::string> &res)
+{
+ verifyDatabase();
+
+ PGresult *results = execPrepared("load_player_list", 0, NULL, false);
+
+ int numrows = PQntuples(results);
+ for (int row = 0; row < numrows; row++)
+ res.emplace_back(PQgetvalue(results, row, 0));
+
+ PQclear(results);
+}
+
+#endif // USE_POSTGRESQL
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <string>
+#include <libpq-fe.h>
+#include "database.h"
+#include "util/basic_macros.h"
+
+class Settings;
+
+class Database_PostgreSQL: public Database
+{
+public:
+ Database_PostgreSQL(const std::string &connect_string);
+ ~Database_PostgreSQL();
+
+ void beginSave();
+ void endSave();
+
+ bool initialized() const;
+
+
+protected:
+ // Conversion helpers
+ inline int pg_to_int(PGresult *res, int row, int col)
+ {
+ return atoi(PQgetvalue(res, row, col));
+ }
+
+ inline u32 pg_to_uint(PGresult *res, int row, int col)
+ {
+ return (u32) atoi(PQgetvalue(res, row, col));
+ }
+
+ inline float pg_to_float(PGresult *res, int row, int col)
+ {
+ return (float) atof(PQgetvalue(res, row, col));
+ }
+
+ inline v3s16 pg_to_v3s16(PGresult *res, int row, int col)
+ {
+ return v3s16(
+ pg_to_int(res, row, col),
+ pg_to_int(res, row, col + 1),
+ pg_to_int(res, row, col + 2)
+ );
+ }
+
+ inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
+ const void **params,
+ const int *paramsLengths = NULL, const int *paramsFormats = NULL,
+ bool clear = true, bool nobinary = true)
+ {
+ return checkResults(PQexecPrepared(m_conn, stmtName, paramsNumber,
+ (const char* const*) params, paramsLengths, paramsFormats,
+ nobinary ? 1 : 0), clear);
+ }
+
+ inline PGresult *execPrepared(const char *stmtName, const int paramsNumber,
+ const char **params, bool clear = true, bool nobinary = true)
+ {
+ return execPrepared(stmtName, paramsNumber,
+ (const void **)params, NULL, NULL, clear, nobinary);
+ }
+
+ void createTableIfNotExists(const std::string &table_name, const std::string &definition);
+ void verifyDatabase();
+
+ // Database initialization
+ void connectToDatabase();
+ virtual void createDatabase() = 0;
+ virtual void initStatements() = 0;
+ inline void prepareStatement(const std::string &name, const std::string &sql)
+ {
+ checkResults(PQprepare(m_conn, name.c_str(), sql.c_str(), 0, NULL));
+ }
+
+ const int getPGVersion() const { return m_pgversion; }
+private:
+ // Database connectivity checks
+ void ping();
+
+ // Database usage
+ PGresult *checkResults(PGresult *res, bool clear = true);
+
+ // Attributes
+ std::string m_connect_string;
+ PGconn *m_conn = nullptr;
+ int m_pgversion = 0;
+};
+
+class MapDatabasePostgreSQL : private Database_PostgreSQL, public MapDatabase
+{
+public:
+ MapDatabasePostgreSQL(const std::string &connect_string);
+ virtual ~MapDatabasePostgreSQL() = default;
+
+ bool saveBlock(const v3s16 &pos, const std::string &data);
+ void loadBlock(const v3s16 &pos, std::string *block);
+ bool deleteBlock(const v3s16 &pos);
+ void listAllLoadableBlocks(std::vector<v3s16> &dst);
+
+ void beginSave() { Database_PostgreSQL::beginSave(); }
+ void endSave() { Database_PostgreSQL::endSave(); }
+
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+};
+
+class PlayerDatabasePostgreSQL : private Database_PostgreSQL, public PlayerDatabase
+{
+public:
+ PlayerDatabasePostgreSQL(const std::string &connect_string);
+ virtual ~PlayerDatabasePostgreSQL() = default;
+
+ void savePlayer(RemotePlayer *player);
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
+ bool removePlayer(const std::string &name);
+ void listPlayers(std::vector<std::string> &res);
+
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+
+private:
+ bool playerDataExists(const std::string &playername);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "config.h"
+
+#if USE_REDIS
+
+#include "database-redis.h"
+
+#include "settings.h"
+#include "log.h"
+#include "exceptions.h"
+#include "util/string.h"
+
+#include <hiredis.h>
+#include <cassert>
+
+
+Database_Redis::Database_Redis(Settings &conf)
+{
+ std::string tmp;
+ try {
+ tmp = conf.get("redis_address");
+ hash = conf.get("redis_hash");
+ } catch (SettingNotFoundException &) {
+ throw SettingNotFoundException("Set redis_address and "
+ "redis_hash in world.mt to use the redis backend");
+ }
+ const char *addr = tmp.c_str();
+ int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379;
+ // if redis_address contains '/' assume unix socket, else hostname/ip
+ ctx = tmp.find('/') != std::string::npos ? redisConnectUnix(addr) : redisConnect(addr, port);
+ if (!ctx) {
+ throw DatabaseException("Cannot allocate redis context");
+ } else if (ctx->err) {
+ std::string err = std::string("Connection error: ") + ctx->errstr;
+ redisFree(ctx);
+ throw DatabaseException(err);
+ }
+ if (conf.exists("redis_password")) {
+ tmp = conf.get("redis_password");
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "AUTH %s", tmp.c_str()));
+ if (!reply)
+ throw DatabaseException("Redis authentication failed");
+ if (reply->type == REDIS_REPLY_ERROR) {
+ std::string err = "Redis authentication failed: " + std::string(reply->str, reply->len);
+ freeReplyObject(reply);
+ throw DatabaseException(err);
+ }
+ freeReplyObject(reply);
+ }
+}
+
+Database_Redis::~Database_Redis()
+{
+ redisFree(ctx);
+}
+
+void Database_Redis::beginSave() {
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI"));
+ if (!reply) {
+ throw DatabaseException(std::string(
+ "Redis command 'MULTI' failed: ") + ctx->errstr);
+ }
+ freeReplyObject(reply);
+}
+
+void Database_Redis::endSave() {
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC"));
+ if (!reply) {
+ throw DatabaseException(std::string(
+ "Redis command 'EXEC' failed: ") + ctx->errstr);
+ }
+ freeReplyObject(reply);
+}
+
+bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data)
+{
+ std::string tmp = i64tos(getBlockAsInteger(pos));
+
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HSET %s %s %b",
+ hash.c_str(), tmp.c_str(), data.c_str(), data.size()));
+ if (!reply) {
+ warningstream << "saveBlock: redis command 'HSET' failed on "
+ "block " << PP(pos) << ": " << ctx->errstr << std::endl;
+ freeReplyObject(reply);
+ return false;
+ }
+
+ if (reply->type == REDIS_REPLY_ERROR) {
+ warningstream << "saveBlock: saving block " << PP(pos)
+ << " failed: " << std::string(reply->str, reply->len) << std::endl;
+ freeReplyObject(reply);
+ return false;
+ }
+
+ freeReplyObject(reply);
+ return true;
+}
+
+void Database_Redis::loadBlock(const v3s16 &pos, std::string *block)
+{
+ std::string tmp = i64tos(getBlockAsInteger(pos));
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
+ "HGET %s %s", hash.c_str(), tmp.c_str()));
+
+ if (!reply) {
+ throw DatabaseException(std::string(
+ "Redis command 'HGET %s %s' failed: ") + ctx->errstr);
+ }
+
+ switch (reply->type) {
+ case REDIS_REPLY_STRING: {
+ *block = std::string(reply->str, reply->len);
+ // std::string copies the memory so this won't cause any problems
+ freeReplyObject(reply);
+ return;
+ }
+ case REDIS_REPLY_ERROR: {
+ std::string errstr(reply->str, reply->len);
+ freeReplyObject(reply);
+ errorstream << "loadBlock: loading block " << PP(pos)
+ << " failed: " << errstr << std::endl;
+ throw DatabaseException(std::string(
+ "Redis command 'HGET %s %s' errored: ") + errstr);
+ }
+ case REDIS_REPLY_NIL: {
+ *block = "";
+ // block not found in database
+ freeReplyObject(reply);
+ return;
+ }
+ }
+
+ errorstream << "loadBlock: loading block " << PP(pos)
+ << " returned invalid reply type " << reply->type
+ << ": " << std::string(reply->str, reply->len) << std::endl;
+ freeReplyObject(reply);
+ throw DatabaseException(std::string(
+ "Redis command 'HGET %s %s' gave invalid reply."));
+}
+
+bool Database_Redis::deleteBlock(const v3s16 &pos)
+{
+ std::string tmp = i64tos(getBlockAsInteger(pos));
+
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx,
+ "HDEL %s %s", hash.c_str(), tmp.c_str()));
+ if (!reply) {
+ throw DatabaseException(std::string(
+ "Redis command 'HDEL %s %s' failed: ") + ctx->errstr);
+ } else if (reply->type == REDIS_REPLY_ERROR) {
+ warningstream << "deleteBlock: deleting block " << PP(pos)
+ << " failed: " << std::string(reply->str, reply->len) << std::endl;
+ freeReplyObject(reply);
+ return false;
+ }
+
+ freeReplyObject(reply);
+ return true;
+}
+
+void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst)
+{
+ redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str()));
+ if (!reply) {
+ throw DatabaseException(std::string(
+ "Redis command 'HKEYS %s' failed: ") + ctx->errstr);
+ }
+ switch (reply->type) {
+ case REDIS_REPLY_ARRAY:
+ dst.reserve(reply->elements);
+ for (size_t i = 0; i < reply->elements; i++) {
+ assert(reply->element[i]->type == REDIS_REPLY_STRING);
+ dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str)));
+ }
+ break;
+ case REDIS_REPLY_ERROR:
+ throw DatabaseException(std::string(
+ "Failed to get keys from database: ") +
+ std::string(reply->str, reply->len));
+ }
+ freeReplyObject(reply);
+}
+
+#endif // USE_REDIS
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "config.h"
+
+#if USE_REDIS
+
+#include <hiredis.h>
+#include <string>
+#include "database.h"
+
+class Settings;
+
+class Database_Redis : public MapDatabase
+{
+public:
+ Database_Redis(Settings &conf);
+ ~Database_Redis();
+
+ void beginSave();
+ void endSave();
+
+ bool saveBlock(const v3s16 &pos, const std::string &data);
+ void loadBlock(const v3s16 &pos, std::string *block);
+ bool deleteBlock(const v3s16 &pos);
+ void listAllLoadableBlocks(std::vector<v3s16> &dst);
+
+private:
+ redisContext *ctx = nullptr;
+ std::string hash = "";
+};
+
+#endif // USE_REDIS
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+/*
+SQLite format specification:
+ blocks:
+ (PK) INT id
+ BLOB data
+*/
+
+
+#include "database-sqlite3.h"
+
+#include "log.h"
+#include "filesys.h"
+#include "exceptions.h"
+#include "settings.h"
+#include "porting.h"
+#include "util/string.h"
+#include "content_sao.h"
+#include "remoteplayer.h"
+
+#include <cassert>
+
+// When to print messages when the database is being held locked by another process
+// Note: I've seen occasional delays of over 250ms while running minetestmapper.
+#define BUSY_INFO_TRESHOLD 100 // Print first informational message after 100ms.
+#define BUSY_WARNING_TRESHOLD 250 // Print warning message after 250ms. Lag is increased.
+#define BUSY_ERROR_TRESHOLD 1000 // Print error message after 1000ms. Significant lag.
+#define BUSY_FATAL_TRESHOLD 3000 // Allow SQLITE_BUSY to be returned, which will cause a minetest crash.
+#define BUSY_ERROR_INTERVAL 10000 // Safety net: report again every 10 seconds
+
+
+#define SQLRES(s, r, m) \
+ if ((s) != (r)) { \
+ throw DatabaseException(std::string(m) + ": " +\
+ sqlite3_errmsg(m_database)); \
+ }
+#define SQLOK(s, m) SQLRES(s, SQLITE_OK, m)
+
+#define PREPARE_STATEMENT(name, query) \
+ SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL),\
+ "Failed to prepare query '" query "'")
+
+#define SQLOK_ERRSTREAM(s, m) \
+ if ((s) != SQLITE_OK) { \
+ errorstream << (m) << ": " \
+ << sqlite3_errmsg(m_database) << std::endl; \
+ }
+
+#define FINALIZE_STATEMENT(statement) SQLOK_ERRSTREAM(sqlite3_finalize(statement), \
+ "Failed to finalize " #statement)
+
+int Database_SQLite3::busyHandler(void *data, int count)
+{
+ s64 &first_time = reinterpret_cast<s64 *>(data)[0];
+ s64 &prev_time = reinterpret_cast<s64 *>(data)[1];
+ s64 cur_time = porting::getTimeMs();
+
+ if (count == 0) {
+ first_time = cur_time;
+ prev_time = first_time;
+ } else {
+ while (cur_time < prev_time)
+ cur_time += s64(1)<<32;
+ }
+
+ if (cur_time - first_time < BUSY_INFO_TRESHOLD) {
+ ; // do nothing
+ } else if (cur_time - first_time >= BUSY_INFO_TRESHOLD &&
+ prev_time - first_time < BUSY_INFO_TRESHOLD) {
+ infostream << "SQLite3 database has been locked for "
+ << cur_time - first_time << " ms." << std::endl;
+ } else if (cur_time - first_time >= BUSY_WARNING_TRESHOLD &&
+ prev_time - first_time < BUSY_WARNING_TRESHOLD) {
+ warningstream << "SQLite3 database has been locked for "
+ << cur_time - first_time << " ms." << std::endl;
+ } else if (cur_time - first_time >= BUSY_ERROR_TRESHOLD &&
+ prev_time - first_time < BUSY_ERROR_TRESHOLD) {
+ errorstream << "SQLite3 database has been locked for "
+ << cur_time - first_time << " ms; this causes lag." << std::endl;
+ } else if (cur_time - first_time >= BUSY_FATAL_TRESHOLD &&
+ prev_time - first_time < BUSY_FATAL_TRESHOLD) {
+ errorstream << "SQLite3 database has been locked for "
+ << cur_time - first_time << " ms - giving up!" << std::endl;
+ } else if ((cur_time - first_time) / BUSY_ERROR_INTERVAL !=
+ (prev_time - first_time) / BUSY_ERROR_INTERVAL) {
+ // Safety net: keep reporting every BUSY_ERROR_INTERVAL
+ errorstream << "SQLite3 database has been locked for "
+ << (cur_time - first_time) / 1000 << " seconds!" << std::endl;
+ }
+
+ prev_time = cur_time;
+
+ // Make sqlite transaction fail if delay exceeds BUSY_FATAL_TRESHOLD
+ return cur_time - first_time < BUSY_FATAL_TRESHOLD;
+}
+
+
+Database_SQLite3::Database_SQLite3(const std::string &savedir, const std::string &dbname) :
+ m_savedir(savedir),
+ m_dbname(dbname)
+{
+}
+
+void Database_SQLite3::beginSave()
+{
+ verifyDatabase();
+ SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE,
+ "Failed to start SQLite3 transaction");
+ sqlite3_reset(m_stmt_begin);
+}
+
+void Database_SQLite3::endSave()
+{
+ verifyDatabase();
+ SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE,
+ "Failed to commit SQLite3 transaction");
+ sqlite3_reset(m_stmt_end);
+}
+
+void Database_SQLite3::openDatabase()
+{
+ if (m_database) return;
+
+ std::string dbp = m_savedir + DIR_DELIM + m_dbname + ".sqlite";
+
+ // Open the database connection
+
+ if (!fs::CreateAllDirs(m_savedir)) {
+ infostream << "Database_SQLite3: Failed to create directory \""
+ << m_savedir << "\"" << std::endl;
+ throw FileNotGoodException("Failed to create database "
+ "save directory");
+ }
+
+ bool needs_create = !fs::PathExists(dbp);
+
+ SQLOK(sqlite3_open_v2(dbp.c_str(), &m_database,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL),
+ std::string("Failed to open SQLite3 database file ") + dbp);
+
+ SQLOK(sqlite3_busy_handler(m_database, Database_SQLite3::busyHandler,
+ m_busy_handler_data), "Failed to set SQLite3 busy handler");
+
+ if (needs_create) {
+ createDatabase();
+ }
+
+ std::string query_str = std::string("PRAGMA synchronous = ")
+ + itos(g_settings->getU16("sqlite_synchronous"));
+ SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL),
+ "Failed to modify sqlite3 synchronous mode");
+ SQLOK(sqlite3_exec(m_database, "PRAGMA foreign_keys = ON", NULL, NULL, NULL),
+ "Failed to enable sqlite3 foreign key support");
+}
+
+void Database_SQLite3::verifyDatabase()
+{
+ if (m_initialized) return;
+
+ openDatabase();
+
+ PREPARE_STATEMENT(begin, "BEGIN;");
+ PREPARE_STATEMENT(end, "COMMIT;");
+
+ initStatements();
+
+ m_initialized = true;
+}
+
+Database_SQLite3::~Database_SQLite3()
+{
+ FINALIZE_STATEMENT(m_stmt_begin)
+ FINALIZE_STATEMENT(m_stmt_end)
+
+ SQLOK_ERRSTREAM(sqlite3_close(m_database), "Failed to close database");
+}
+
+/*
+ * Map database
+ */
+
+MapDatabaseSQLite3::MapDatabaseSQLite3(const std::string &savedir):
+ Database_SQLite3(savedir, "map"),
+ MapDatabase()
+{
+}
+
+MapDatabaseSQLite3::~MapDatabaseSQLite3()
+{
+ FINALIZE_STATEMENT(m_stmt_read)
+ FINALIZE_STATEMENT(m_stmt_write)
+ FINALIZE_STATEMENT(m_stmt_list)
+ FINALIZE_STATEMENT(m_stmt_delete)
+}
+
+
+void MapDatabaseSQLite3::createDatabase()
+{
+ assert(m_database); // Pre-condition
+
+ SQLOK(sqlite3_exec(m_database,
+ "CREATE TABLE IF NOT EXISTS `blocks` (\n"
+ " `pos` INT PRIMARY KEY,\n"
+ " `data` BLOB\n"
+ ");\n",
+ NULL, NULL, NULL),
+ "Failed to create database table");
+}
+
+void MapDatabaseSQLite3::initStatements()
+{
+ PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1");
+#ifdef __ANDROID__
+ PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
+#else
+ PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)");
+#endif
+ PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?");
+ PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`");
+
+ verbosestream << "ServerMap: SQLite3 database opened." << std::endl;
+}
+
+inline void MapDatabaseSQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index)
+{
+ SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos)),
+ "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
+}
+
+bool MapDatabaseSQLite3::deleteBlock(const v3s16 &pos)
+{
+ verifyDatabase();
+
+ bindPos(m_stmt_delete, pos);
+
+ bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE;
+ sqlite3_reset(m_stmt_delete);
+
+ if (!good) {
+ warningstream << "deleteBlock: Block failed to delete "
+ << PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl;
+ }
+ return good;
+}
+
+bool MapDatabaseSQLite3::saveBlock(const v3s16 &pos, const std::string &data)
+{
+ verifyDatabase();
+
+#ifdef __ANDROID__
+ /**
+ * Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android,
+ * deleting them and then inserting works.
+ */
+ bindPos(m_stmt_read, pos);
+
+ if (sqlite3_step(m_stmt_read) == SQLITE_ROW) {
+ deleteBlock(pos);
+ }
+ sqlite3_reset(m_stmt_read);
+#endif
+
+ bindPos(m_stmt_write, pos);
+ SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL),
+ "Internal error: failed to bind query at " __FILE__ ":" TOSTRING(__LINE__));
+
+ SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE, "Failed to save block")
+ sqlite3_reset(m_stmt_write);
+
+ return true;
+}
+
+void MapDatabaseSQLite3::loadBlock(const v3s16 &pos, std::string *block)
+{
+ verifyDatabase();
+
+ bindPos(m_stmt_read, pos);
+
+ if (sqlite3_step(m_stmt_read) != SQLITE_ROW) {
+ sqlite3_reset(m_stmt_read);
+ return;
+ }
+
+ const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0);
+ size_t len = sqlite3_column_bytes(m_stmt_read, 0);
+
+ *block = (data) ? std::string(data, len) : "";
+
+ sqlite3_step(m_stmt_read);
+ // We should never get more than 1 row, so ok to reset
+ sqlite3_reset(m_stmt_read);
+}
+
+void MapDatabaseSQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst)
+{
+ verifyDatabase();
+
+ while (sqlite3_step(m_stmt_list) == SQLITE_ROW)
+ dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0)));
+
+ sqlite3_reset(m_stmt_list);
+}
+
+/*
+ * Player Database
+ */
+
+PlayerDatabaseSQLite3::PlayerDatabaseSQLite3(const std::string &savedir):
+ Database_SQLite3(savedir, "players"),
+ PlayerDatabase()
+{
+}
+
+PlayerDatabaseSQLite3::~PlayerDatabaseSQLite3()
+{
+ FINALIZE_STATEMENT(m_stmt_player_load)
+ FINALIZE_STATEMENT(m_stmt_player_add)
+ FINALIZE_STATEMENT(m_stmt_player_update)
+ FINALIZE_STATEMENT(m_stmt_player_remove)
+ FINALIZE_STATEMENT(m_stmt_player_list)
+ FINALIZE_STATEMENT(m_stmt_player_add_inventory)
+ FINALIZE_STATEMENT(m_stmt_player_add_inventory_items)
+ FINALIZE_STATEMENT(m_stmt_player_remove_inventory)
+ FINALIZE_STATEMENT(m_stmt_player_remove_inventory_items)
+ FINALIZE_STATEMENT(m_stmt_player_load_inventory)
+ FINALIZE_STATEMENT(m_stmt_player_load_inventory_items)
+ FINALIZE_STATEMENT(m_stmt_player_metadata_load)
+ FINALIZE_STATEMENT(m_stmt_player_metadata_add)
+ FINALIZE_STATEMENT(m_stmt_player_metadata_remove)
+};
+
+
+void PlayerDatabaseSQLite3::createDatabase()
+{
+ assert(m_database); // Pre-condition
+
+ SQLOK(sqlite3_exec(m_database,
+ "CREATE TABLE IF NOT EXISTS `player` ("
+ "`name` VARCHAR(50) NOT NULL,"
+ "`pitch` NUMERIC(11, 4) NOT NULL,"
+ "`yaw` NUMERIC(11, 4) NOT NULL,"
+ "`posX` NUMERIC(11, 4) NOT NULL,"
+ "`posY` NUMERIC(11, 4) NOT NULL,"
+ "`posZ` NUMERIC(11, 4) NOT NULL,"
+ "`hp` INT NOT NULL,"
+ "`breath` INT NOT NULL,"
+ "`creation_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ "`modification_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
+ "PRIMARY KEY (`name`));",
+ NULL, NULL, NULL),
+ "Failed to create player table");
+
+ SQLOK(sqlite3_exec(m_database,
+ "CREATE TABLE IF NOT EXISTS `player_metadata` ("
+ " `player` VARCHAR(50) NOT NULL,"
+ " `metadata` VARCHAR(256) NOT NULL,"
+ " `value` TEXT,"
+ " PRIMARY KEY(`player`, `metadata`),"
+ " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
+ NULL, NULL, NULL),
+ "Failed to create player metadata table");
+
+ SQLOK(sqlite3_exec(m_database,
+ "CREATE TABLE IF NOT EXISTS `player_inventories` ("
+ " `player` VARCHAR(50) NOT NULL,"
+ " `inv_id` INT NOT NULL,"
+ " `inv_width` INT NOT NULL,"
+ " `inv_name` TEXT NOT NULL DEFAULT '',"
+ " `inv_size` INT NOT NULL,"
+ " PRIMARY KEY(player, inv_id),"
+ " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
+ NULL, NULL, NULL),
+ "Failed to create player inventory table");
+
+ SQLOK(sqlite3_exec(m_database,
+ "CREATE TABLE `player_inventory_items` ("
+ " `player` VARCHAR(50) NOT NULL,"
+ " `inv_id` INT NOT NULL,"
+ " `slot_id` INT NOT NULL,"
+ " `item` TEXT NOT NULL DEFAULT '',"
+ " PRIMARY KEY(player, inv_id, slot_id),"
+ " FOREIGN KEY (`player`) REFERENCES player (`name`) ON DELETE CASCADE );",
+ NULL, NULL, NULL),
+ "Failed to create player inventory items table");
+}
+
+void PlayerDatabaseSQLite3::initStatements()
+{
+ PREPARE_STATEMENT(player_load, "SELECT `pitch`, `yaw`, `posX`, `posY`, `posZ`, `hp`, "
+ "`breath`"
+ "FROM `player` WHERE `name` = ?")
+ PREPARE_STATEMENT(player_add, "INSERT INTO `player` (`name`, `pitch`, `yaw`, `posX`, "
+ "`posY`, `posZ`, `hp`, `breath`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
+ PREPARE_STATEMENT(player_update, "UPDATE `player` SET `pitch` = ?, `yaw` = ?, "
+ "`posX` = ?, `posY` = ?, `posZ` = ?, `hp` = ?, `breath` = ?, "
+ "`modification_date` = CURRENT_TIMESTAMP WHERE `name` = ?")
+ PREPARE_STATEMENT(player_remove, "DELETE FROM `player` WHERE `name` = ?")
+ PREPARE_STATEMENT(player_list, "SELECT `name` FROM `player`")
+
+ PREPARE_STATEMENT(player_add_inventory, "INSERT INTO `player_inventories` "
+ "(`player`, `inv_id`, `inv_width`, `inv_name`, `inv_size`) VALUES (?, ?, ?, ?, ?)")
+ PREPARE_STATEMENT(player_add_inventory_items, "INSERT INTO `player_inventory_items` "
+ "(`player`, `inv_id`, `slot_id`, `item`) VALUES (?, ?, ?, ?)")
+ PREPARE_STATEMENT(player_remove_inventory, "DELETE FROM `player_inventories` "
+ "WHERE `player` = ?")
+ PREPARE_STATEMENT(player_remove_inventory_items, "DELETE FROM `player_inventory_items` "
+ "WHERE `player` = ?")
+ PREPARE_STATEMENT(player_load_inventory, "SELECT `inv_id`, `inv_width`, `inv_name`, "
+ "`inv_size` FROM `player_inventories` WHERE `player` = ? ORDER BY inv_id")
+ PREPARE_STATEMENT(player_load_inventory_items, "SELECT `slot_id`, `item` "
+ "FROM `player_inventory_items` WHERE `player` = ? AND `inv_id` = ?")
+
+ PREPARE_STATEMENT(player_metadata_load, "SELECT `metadata`, `value` FROM "
+ "`player_metadata` WHERE `player` = ?")
+ PREPARE_STATEMENT(player_metadata_add, "INSERT INTO `player_metadata` "
+ "(`player`, `metadata`, `value`) VALUES (?, ?, ?)")
+ PREPARE_STATEMENT(player_metadata_remove, "DELETE FROM `player_metadata` "
+ "WHERE `player` = ?")
+ verbosestream << "ServerEnvironment: SQLite3 database opened (players)." << std::endl;
+}
+
+bool PlayerDatabaseSQLite3::playerDataExists(const std::string &name)
+{
+ verifyDatabase();
+ str_to_sqlite(m_stmt_player_load, 1, name);
+ bool res = (sqlite3_step(m_stmt_player_load) == SQLITE_ROW);
+ sqlite3_reset(m_stmt_player_load);
+ return res;
+}
+
+void PlayerDatabaseSQLite3::savePlayer(RemotePlayer *player)
+{
+ PlayerSAO* sao = player->getPlayerSAO();
+ sanity_check(sao);
+
+ const v3f &pos = sao->getBasePosition();
+ // Begin save in brace is mandatory
+ if (!playerDataExists(player->getName())) {
+ beginSave();
+ str_to_sqlite(m_stmt_player_add, 1, player->getName());
+ double_to_sqlite(m_stmt_player_add, 2, sao->getPitch());
+ double_to_sqlite(m_stmt_player_add, 3, sao->getYaw());
+ double_to_sqlite(m_stmt_player_add, 4, pos.X);
+ double_to_sqlite(m_stmt_player_add, 5, pos.Y);
+ double_to_sqlite(m_stmt_player_add, 6, pos.Z);
+ int64_to_sqlite(m_stmt_player_add, 7, sao->getHP());
+ int64_to_sqlite(m_stmt_player_add, 8, sao->getBreath());
+
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_add), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_add);
+ } else {
+ beginSave();
+ double_to_sqlite(m_stmt_player_update, 1, sao->getPitch());
+ double_to_sqlite(m_stmt_player_update, 2, sao->getYaw());
+ double_to_sqlite(m_stmt_player_update, 3, pos.X);
+ double_to_sqlite(m_stmt_player_update, 4, pos.Y);
+ double_to_sqlite(m_stmt_player_update, 5, pos.Z);
+ int64_to_sqlite(m_stmt_player_update, 6, sao->getHP());
+ int64_to_sqlite(m_stmt_player_update, 7, sao->getBreath());
+ str_to_sqlite(m_stmt_player_update, 8, player->getName());
+
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_update), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_update);
+ }
+
+ // Write player inventories
+ str_to_sqlite(m_stmt_player_remove_inventory, 1, player->getName());
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_remove_inventory);
+
+ str_to_sqlite(m_stmt_player_remove_inventory_items, 1, player->getName());
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_remove_inventory_items), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_remove_inventory_items);
+
+ std::vector<const InventoryList*> inventory_lists = sao->getInventory()->getLists();
+ for (u16 i = 0; i < inventory_lists.size(); i++) {
+ const InventoryList* list = inventory_lists[i];
+
+ str_to_sqlite(m_stmt_player_add_inventory, 1, player->getName());
+ int_to_sqlite(m_stmt_player_add_inventory, 2, i);
+ int_to_sqlite(m_stmt_player_add_inventory, 3, list->getWidth());
+ str_to_sqlite(m_stmt_player_add_inventory, 4, list->getName());
+ int_to_sqlite(m_stmt_player_add_inventory, 5, list->getSize());
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_add_inventory);
+
+ for (u32 j = 0; j < list->getSize(); j++) {
+ std::ostringstream os;
+ list->getItem(j).serialize(os);
+ std::string itemStr = os.str();
+
+ str_to_sqlite(m_stmt_player_add_inventory_items, 1, player->getName());
+ int_to_sqlite(m_stmt_player_add_inventory_items, 2, i);
+ int_to_sqlite(m_stmt_player_add_inventory_items, 3, j);
+ str_to_sqlite(m_stmt_player_add_inventory_items, 4, itemStr);
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_add_inventory_items), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_add_inventory_items);
+ }
+ }
+
+ str_to_sqlite(m_stmt_player_metadata_remove, 1, player->getName());
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_remove), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_metadata_remove);
+
+ const PlayerAttributes &attrs = sao->getExtendedAttributes();
+ for (const auto &attr : attrs) {
+ str_to_sqlite(m_stmt_player_metadata_add, 1, player->getName());
+ str_to_sqlite(m_stmt_player_metadata_add, 2, attr.first);
+ str_to_sqlite(m_stmt_player_metadata_add, 3, attr.second);
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_metadata_add), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_metadata_add);
+ }
+
+ endSave();
+}
+
+bool PlayerDatabaseSQLite3::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
+{
+ verifyDatabase();
+
+ str_to_sqlite(m_stmt_player_load, 1, player->getName());
+ if (sqlite3_step(m_stmt_player_load) != SQLITE_ROW) {
+ sqlite3_reset(m_stmt_player_load);
+ return false;
+ }
+ sao->setPitch(sqlite_to_float(m_stmt_player_load, 0));
+ sao->setYaw(sqlite_to_float(m_stmt_player_load, 1));
+ sao->setBasePosition(sqlite_to_v3f(m_stmt_player_load, 2));
+ sao->setHPRaw((s16) MYMIN(sqlite_to_int(m_stmt_player_load, 5), S16_MAX));
+ sao->setBreath((u16) MYMIN(sqlite_to_int(m_stmt_player_load, 6), U16_MAX), false);
+ sqlite3_reset(m_stmt_player_load);
+
+ // Load inventory
+ str_to_sqlite(m_stmt_player_load_inventory, 1, player->getName());
+ while (sqlite3_step(m_stmt_player_load_inventory) == SQLITE_ROW) {
+ InventoryList *invList = player->inventory.addList(
+ sqlite_to_string(m_stmt_player_load_inventory, 2),
+ sqlite_to_uint(m_stmt_player_load_inventory, 3));
+ invList->setWidth(sqlite_to_uint(m_stmt_player_load_inventory, 1));
+
+ u32 invId = sqlite_to_uint(m_stmt_player_load_inventory, 0);
+
+ str_to_sqlite(m_stmt_player_load_inventory_items, 1, player->getName());
+ int_to_sqlite(m_stmt_player_load_inventory_items, 2, invId);
+ while (sqlite3_step(m_stmt_player_load_inventory_items) == SQLITE_ROW) {
+ const std::string itemStr = sqlite_to_string(m_stmt_player_load_inventory_items, 1);
+ if (itemStr.length() > 0) {
+ ItemStack stack;
+ stack.deSerialize(itemStr);
+ invList->changeItem(sqlite_to_uint(m_stmt_player_load_inventory_items, 0), stack);
+ }
+ }
+ sqlite3_reset(m_stmt_player_load_inventory_items);
+ }
+
+ sqlite3_reset(m_stmt_player_load_inventory);
+
+ str_to_sqlite(m_stmt_player_metadata_load, 1, sao->getPlayer()->getName());
+ while (sqlite3_step(m_stmt_player_metadata_load) == SQLITE_ROW) {
+ std::string attr = sqlite_to_string(m_stmt_player_metadata_load, 0);
+ std::string value = sqlite_to_string(m_stmt_player_metadata_load, 1);
+
+ sao->setExtendedAttribute(attr, value);
+ }
+ sqlite3_reset(m_stmt_player_metadata_load);
+ return true;
+}
+
+bool PlayerDatabaseSQLite3::removePlayer(const std::string &name)
+{
+ if (!playerDataExists(name))
+ return false;
+
+ str_to_sqlite(m_stmt_player_remove, 1, name);
+ sqlite3_vrfy(sqlite3_step(m_stmt_player_remove), SQLITE_DONE);
+ sqlite3_reset(m_stmt_player_remove);
+ return true;
+}
+
+void PlayerDatabaseSQLite3::listPlayers(std::vector<std::string> &res)
+{
+ verifyDatabase();
+
+ while (sqlite3_step(m_stmt_player_list) == SQLITE_ROW)
+ res.push_back(sqlite_to_string(m_stmt_player_list, 0));
+
+ sqlite3_reset(m_stmt_player_list);
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <cstring>
+#include <string>
+#include "database.h"
+#include "exceptions.h"
+
+extern "C" {
+#include "sqlite3.h"
+}
+
+class Database_SQLite3 : public Database
+{
+public:
+ virtual ~Database_SQLite3();
+
+ void beginSave();
+ void endSave();
+
+ bool initialized() const { return m_initialized; }
+protected:
+ Database_SQLite3(const std::string &savedir, const std::string &dbname);
+
+ // Open and initialize the database if needed
+ void verifyDatabase();
+
+ // Convertors
+ inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const std::string &str) const
+ {
+ sqlite3_vrfy(sqlite3_bind_text(s, iCol, str.c_str(), str.size(), NULL));
+ }
+
+ inline void str_to_sqlite(sqlite3_stmt *s, int iCol, const char *str) const
+ {
+ sqlite3_vrfy(sqlite3_bind_text(s, iCol, str, strlen(str), NULL));
+ }
+
+ inline void int_to_sqlite(sqlite3_stmt *s, int iCol, int val) const
+ {
+ sqlite3_vrfy(sqlite3_bind_int(s, iCol, val));
+ }
+
+ inline void int64_to_sqlite(sqlite3_stmt *s, int iCol, s64 val) const
+ {
+ sqlite3_vrfy(sqlite3_bind_int64(s, iCol, (sqlite3_int64) val));
+ }
+
+ inline void double_to_sqlite(sqlite3_stmt *s, int iCol, double val) const
+ {
+ sqlite3_vrfy(sqlite3_bind_double(s, iCol, val));
+ }
+
+ inline std::string sqlite_to_string(sqlite3_stmt *s, int iCol)
+ {
+ const char* text = reinterpret_cast<const char*>(sqlite3_column_text(s, iCol));
+ return std::string(text ? text : "");
+ }
+
+ inline s32 sqlite_to_int(sqlite3_stmt *s, int iCol)
+ {
+ return sqlite3_column_int(s, iCol);
+ }
+
+ inline u32 sqlite_to_uint(sqlite3_stmt *s, int iCol)
+ {
+ return (u32) sqlite3_column_int(s, iCol);
+ }
+
+ inline float sqlite_to_float(sqlite3_stmt *s, int iCol)
+ {
+ return (float) sqlite3_column_double(s, iCol);
+ }
+
+ inline const v3f sqlite_to_v3f(sqlite3_stmt *s, int iCol)
+ {
+ return v3f(sqlite_to_float(s, iCol), sqlite_to_float(s, iCol + 1),
+ sqlite_to_float(s, iCol + 2));
+ }
+
+ // Query verifiers helpers
+ inline void sqlite3_vrfy(int s, const std::string &m = "", int r = SQLITE_OK) const
+ {
+ if (s != r)
+ throw DatabaseException(m + ": " + sqlite3_errmsg(m_database));
+ }
+
+ inline void sqlite3_vrfy(const int s, const int r, const std::string &m = "") const
+ {
+ sqlite3_vrfy(s, m, r);
+ }
+
+ // Create the database structure
+ virtual void createDatabase() = 0;
+ virtual void initStatements() = 0;
+
+ sqlite3 *m_database = nullptr;
+private:
+ // Open the database
+ void openDatabase();
+
+ bool m_initialized = false;
+
+ std::string m_savedir = "";
+ std::string m_dbname = "";
+
+ sqlite3_stmt *m_stmt_begin = nullptr;
+ sqlite3_stmt *m_stmt_end = nullptr;
+
+ s64 m_busy_handler_data[2];
+
+ static int busyHandler(void *data, int count);
+};
+
+class MapDatabaseSQLite3 : private Database_SQLite3, public MapDatabase
+{
+public:
+ MapDatabaseSQLite3(const std::string &savedir);
+ virtual ~MapDatabaseSQLite3();
+
+ bool saveBlock(const v3s16 &pos, const std::string &data);
+ void loadBlock(const v3s16 &pos, std::string *block);
+ bool deleteBlock(const v3s16 &pos);
+ void listAllLoadableBlocks(std::vector<v3s16> &dst);
+
+ void beginSave() { Database_SQLite3::beginSave(); }
+ void endSave() { Database_SQLite3::endSave(); }
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+
+private:
+ void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index = 1);
+
+ // Map
+ sqlite3_stmt *m_stmt_read = nullptr;
+ sqlite3_stmt *m_stmt_write = nullptr;
+ sqlite3_stmt *m_stmt_list = nullptr;
+ sqlite3_stmt *m_stmt_delete = nullptr;
+};
+
+class PlayerDatabaseSQLite3 : private Database_SQLite3, public PlayerDatabase
+{
+public:
+ PlayerDatabaseSQLite3(const std::string &savedir);
+ virtual ~PlayerDatabaseSQLite3();
+
+ void savePlayer(RemotePlayer *player);
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
+ bool removePlayer(const std::string &name);
+ void listPlayers(std::vector<std::string> &res);
+
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+
+private:
+ bool playerDataExists(const std::string &name);
+
+ // Players
+ sqlite3_stmt *m_stmt_player_load = nullptr;
+ sqlite3_stmt *m_stmt_player_add = nullptr;
+ sqlite3_stmt *m_stmt_player_update = nullptr;
+ sqlite3_stmt *m_stmt_player_remove = nullptr;
+ sqlite3_stmt *m_stmt_player_list = nullptr;
+ sqlite3_stmt *m_stmt_player_load_inventory = nullptr;
+ sqlite3_stmt *m_stmt_player_load_inventory_items = nullptr;
+ sqlite3_stmt *m_stmt_player_add_inventory = nullptr;
+ sqlite3_stmt *m_stmt_player_add_inventory_items = nullptr;
+ sqlite3_stmt *m_stmt_player_remove_inventory = nullptr;
+ sqlite3_stmt *m_stmt_player_remove_inventory_items = nullptr;
+ sqlite3_stmt *m_stmt_player_metadata_load = nullptr;
+ sqlite3_stmt *m_stmt_player_metadata_remove = nullptr;
+ sqlite3_stmt *m_stmt_player_metadata_add = nullptr;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "database.h"
+#include "irrlichttypes.h"
+
+
+/****************
+ * Black magic! *
+ ****************
+ * The position hashing is very messed up.
+ * It's a lot more complicated than it looks.
+ */
+
+static inline s16 unsigned_to_signed(u16 i, u16 max_positive)
+{
+ if (i < max_positive) {
+ return i;
+ }
+
+ return i - (max_positive * 2);
+}
+
+
+// Modulo of a negative number does not work consistently in C
+static inline s64 pythonmodulo(s64 i, s16 mod)
+{
+ if (i >= 0) {
+ return i % mod;
+ }
+ return mod - ((-i) % mod);
+}
+
+
+s64 MapDatabase::getBlockAsInteger(const v3s16 &pos)
+{
+ return (u64) pos.Z * 0x1000000 +
+ (u64) pos.Y * 0x1000 +
+ (u64) pos.X;
+}
+
+
+v3s16 MapDatabase::getIntegerAsBlock(s64 i)
+{
+ v3s16 pos;
+ pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
+ i = (i - pos.X) / 4096;
+ pos.Y = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
+ i = (i - pos.Y) / 4096;
+ pos.Z = unsigned_to_signed(pythonmodulo(i, 4096), 2048);
+ return pos;
+}
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "irr_v3d.h"
+#include "irrlichttypes.h"
+#include "util/basic_macros.h"
+
+class Database
+{
+public:
+ virtual void beginSave() = 0;
+ virtual void endSave() = 0;
+ virtual bool initialized() const { return true; }
+};
+
+class MapDatabase : public Database
+{
+public:
+ virtual ~MapDatabase() = default;
+
+ virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0;
+ virtual void loadBlock(const v3s16 &pos, std::string *block) = 0;
+ virtual bool deleteBlock(const v3s16 &pos) = 0;
+
+ static s64 getBlockAsInteger(const v3s16 &pos);
+ static v3s16 getIntegerAsBlock(s64 i);
+
+ virtual void listAllLoadableBlocks(std::vector<v3s16> &dst) = 0;
+};
+
+class PlayerSAO;
+class RemotePlayer;
+
+class PlayerDatabase
+{
+public:
+ virtual ~PlayerDatabase() = default;
+
+ virtual void savePlayer(RemotePlayer *player) = 0;
+ virtual bool loadPlayer(RemotePlayer *player, PlayerSAO *sao) = 0;
+ virtual bool removePlayer(const std::string &name) = 0;
+ virtual void listPlayers(std::vector<std::string> &res) = 0;
+};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "dungeongen.h"
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "nodedef.h"
-#include "settings.h"
-
-//#define DGEN_USE_TORCHES
-
-NoiseParams nparams_dungeon_density(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
-NoiseParams nparams_dungeon_alt_wall(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-DungeonGen::DungeonGen(INodeDefManager *ndef,
- GenerateNotifier *gennotify, DungeonParams *dparams)
-{
- assert(ndef);
-
- this->ndef = ndef;
- this->gennotify = gennotify;
-
-#ifdef DGEN_USE_TORCHES
- c_torch = ndef->getId("default:torch");
-#endif
-
- if (dparams) {
- memcpy(&dp, dparams, sizeof(dp));
- } else {
- // Default dungeon parameters
- dp.seed = 0;
-
- dp.c_water = ndef->getId("mapgen_water_source");
- dp.c_river_water = ndef->getId("mapgen_river_water_source");
- dp.c_wall = ndef->getId("mapgen_cobble");
- dp.c_alt_wall = ndef->getId("mapgen_mossycobble");
- dp.c_stair = ndef->getId("mapgen_stair_cobble");
-
- if (dp.c_river_water == CONTENT_IGNORE)
- dp.c_river_water = ndef->getId("mapgen_water_source");
-
- dp.diagonal_dirs = false;
- dp.only_in_ground = true;
- dp.holesize = v3s16(1, 2, 1);
- dp.corridor_len_min = 1;
- dp.corridor_len_max = 13;
- dp.room_size_min = v3s16(4, 4, 4);
- dp.room_size_max = v3s16(8, 6, 8);
- dp.room_size_large_min = v3s16(8, 8, 8);
- dp.room_size_large_max = v3s16(16, 16, 16);
- dp.rooms_min = 2;
- dp.rooms_max = 16;
- dp.y_min = -MAX_MAP_GENERATION_LIMIT;
- dp.y_max = MAX_MAP_GENERATION_LIMIT;
- dp.notifytype = GENNOTIFY_DUNGEON;
-
- dp.np_density = nparams_dungeon_density;
- dp.np_alt_wall = nparams_dungeon_alt_wall;
- }
-}
-
-
-void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax)
-{
- assert(vm);
-
- //TimeTaker t("gen dungeons");
- if (nmin.Y < dp.y_min || nmax.Y > dp.y_max)
- return;
-
- float nval_density = NoisePerlin3D(&dp.np_density, nmin.X, nmin.Y, nmin.Z, dp.seed);
- if (nval_density < 1.0f)
- return;
-
- static const bool preserve_ignore = !g_settings->getBool("projecting_dungeons");
-
- this->vm = vm;
- this->blockseed = bseed;
- random.seed(bseed + 2);
-
- // Dungeon generator doesn't modify places which have this set
- vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE);
-
- if (dp.only_in_ground) {
- // Set all air and water to be untouchable to make dungeons open to
- // caves and open air. Optionally set ignore to be untouchable to
- // prevent protruding dungeons.
- for (s16 z = nmin.Z; z <= nmax.Z; z++) {
- for (s16 y = nmin.Y; y <= nmax.Y; y++) {
- u32 i = vm->m_area.index(nmin.X, y, z);
- for (s16 x = nmin.X; x <= nmax.X; x++) {
- content_t c = vm->m_data[i].getContent();
- if (c == CONTENT_AIR || c == dp.c_water ||
- (preserve_ignore && c == CONTENT_IGNORE) ||
- c == dp.c_river_water)
- vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
- i++;
- }
- }
- }
- }
-
- // Add them
- for (u32 i = 0; i < floor(nval_density); i++)
- makeDungeon(v3s16(1, 1, 1) * MAP_BLOCKSIZE);
-
- // Optionally convert some structure to alternative structure
- if (dp.c_alt_wall == CONTENT_IGNORE)
- return;
-
- for (s16 z = nmin.Z; z <= nmax.Z; z++)
- for (s16 y = nmin.Y; y <= nmax.Y; y++) {
- u32 i = vm->m_area.index(nmin.X, y, z);
- for (s16 x = nmin.X; x <= nmax.X; x++) {
- if (vm->m_data[i].getContent() == dp.c_wall) {
- if (NoisePerlin3D(&dp.np_alt_wall, x, y, z, blockseed) > 0.0f)
- vm->m_data[i].setContent(dp.c_alt_wall);
- }
- i++;
- }
- }
-
- //printf("== gen dungeons: %dms\n", t.stop());
-}
-
-
-void DungeonGen::makeDungeon(v3s16 start_padding)
-{
- const v3s16 &areasize = vm->m_area.getExtent();
- v3s16 roomsize;
- v3s16 roomplace;
-
- /*
- Find place for first room.
- There is a 1 in 4 chance of the first room being 'large',
- all other rooms are not 'large'.
- */
- bool fits = false;
- for (u32 i = 0; i < 100 && !fits; i++) {
- bool is_large_room = ((random.next() & 3) == 1);
- if (is_large_room) {
- roomsize.Z = random.range(
- dp.room_size_large_min.Z, dp.room_size_large_max.Z);
- roomsize.Y = random.range(
- dp.room_size_large_min.Y, dp.room_size_large_max.Y);
- roomsize.X = random.range(
- dp.room_size_large_min.X, dp.room_size_large_max.X);
- } else {
- roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
- roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
- roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X);
- }
-
- // start_padding is used to disallow starting the generation of
- // a dungeon in a neighboring generation chunk
- roomplace = vm->m_area.MinEdge + start_padding;
- roomplace.Z += random.range(0, areasize.Z - roomsize.Z - start_padding.Z);
- roomplace.Y += random.range(0, areasize.Y - roomsize.Y - start_padding.Y);
- roomplace.X += random.range(0, areasize.X - roomsize.X - start_padding.X);
-
- /*
- Check that we're not putting the room to an unknown place,
- otherwise it might end up floating in the air
- */
- fits = true;
- for (s16 z = 0; z < roomsize.Z; z++)
- for (s16 y = 0; y < roomsize.Y; y++)
- for (s16 x = 0; x < roomsize.X; x++) {
- v3s16 p = roomplace + v3s16(x, y, z);
- u32 vi = vm->m_area.index(p);
- if ((vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) ||
- vm->m_data[vi].getContent() == CONTENT_IGNORE) {
- fits = false;
- break;
- }
- }
- }
- // No place found
- if (!fits)
- return;
-
- /*
- Stores the center position of the last room made, so that
- a new corridor can be started from the last room instead of
- the new room, if chosen so.
- */
- v3s16 last_room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
-
- u32 room_count = random.range(dp.rooms_min, dp.rooms_max);
- for (u32 i = 0; i < room_count; i++) {
- // Make a room to the determined place
- makeRoom(roomsize, roomplace);
-
- v3s16 room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
- if (gennotify)
- gennotify->addEvent(dp.notifytype, room_center);
-
-#ifdef DGEN_USE_TORCHES
- // Place torch at room center (for testing)
- vm->m_data[vm->m_area.index(room_center)] = MapNode(c_torch);
-#endif
-
- // Quit if last room
- if (i == room_count - 1)
- break;
-
- // Determine walker start position
-
- bool start_in_last_room = (random.range(0, 2) != 0);
-
- v3s16 walker_start_place;
-
- if (start_in_last_room) {
- walker_start_place = last_room_center;
- } else {
- walker_start_place = room_center;
- // Store center of current room as the last one
- last_room_center = room_center;
- }
-
- // Create walker and find a place for a door
- v3s16 doorplace;
- v3s16 doordir;
-
- m_pos = walker_start_place;
- if (!findPlaceForDoor(doorplace, doordir))
- return;
-
- if (random.range(0, 1) == 0)
- // Make the door
- makeDoor(doorplace, doordir);
- else
- // Don't actually make a door
- doorplace -= doordir;
-
- // Make a random corridor starting from the door
- v3s16 corridor_end;
- v3s16 corridor_end_dir;
- makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir);
-
- // Find a place for a random sized room
- roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
- roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
- roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X);
-
- m_pos = corridor_end;
- m_dir = corridor_end_dir;
- if (!findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace))
- return;
-
- if (random.range(0, 1) == 0)
- // Make the door
- makeDoor(doorplace, doordir);
- else
- // Don't actually make a door
- roomplace -= doordir;
-
- }
-}
-
-
-void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
-{
- MapNode n_wall(dp.c_wall);
- MapNode n_air(CONTENT_AIR);
-
- // Make +-X walls
- for (s16 z = 0; z < roomsize.Z; z++)
- for (s16 y = 0; y < roomsize.Y; y++) {
- {
- v3s16 p = roomplace + v3s16(0, y, z);
- if (!vm->m_area.contains(p))
- continue;
- u32 vi = vm->m_area.index(p);
- if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
- continue;
- vm->m_data[vi] = n_wall;
- }
- {
- v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z);
- if (!vm->m_area.contains(p))
- continue;
- u32 vi = vm->m_area.index(p);
- if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
- continue;
- vm->m_data[vi] = n_wall;
- }
- }
-
- // Make +-Z walls
- for (s16 x = 0; x < roomsize.X; x++)
- for (s16 y = 0; y < roomsize.Y; y++) {
- {
- v3s16 p = roomplace + v3s16(x, y, 0);
- if (!vm->m_area.contains(p))
- continue;
- u32 vi = vm->m_area.index(p);
- if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
- continue;
- vm->m_data[vi] = n_wall;
- }
- {
- v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1);
- if (!vm->m_area.contains(p))
- continue;
- u32 vi = vm->m_area.index(p);
- if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
- continue;
- vm->m_data[vi] = n_wall;
- }
- }
-
- // Make +-Y walls (floor and ceiling)
- for (s16 z = 0; z < roomsize.Z; z++)
- for (s16 x = 0; x < roomsize.X; x++) {
- {
- v3s16 p = roomplace + v3s16(x, 0, z);
- if (!vm->m_area.contains(p))
- continue;
- u32 vi = vm->m_area.index(p);
- if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
- continue;
- vm->m_data[vi] = n_wall;
- }
- {
- v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z);
- if (!vm->m_area.contains(p))
- continue;
- u32 vi = vm->m_area.index(p);
- if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
- continue;
- vm->m_data[vi] = n_wall;
- }
- }
-
- // Fill with air
- for (s16 z = 1; z < roomsize.Z - 1; z++)
- for (s16 y = 1; y < roomsize.Y - 1; y++)
- for (s16 x = 1; x < roomsize.X - 1; x++) {
- v3s16 p = roomplace + v3s16(x, y, z);
- if (!vm->m_area.contains(p))
- continue;
- u32 vi = vm->m_area.index(p);
- vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE;
- vm->m_data[vi] = n_air;
- }
-}
-
-
-void DungeonGen::makeFill(v3s16 place, v3s16 size,
- u8 avoid_flags, MapNode n, u8 or_flags)
-{
- for (s16 z = 0; z < size.Z; z++)
- for (s16 y = 0; y < size.Y; y++)
- for (s16 x = 0; x < size.X; x++) {
- v3s16 p = place + v3s16(x, y, z);
- if (!vm->m_area.contains(p))
- continue;
- u32 vi = vm->m_area.index(p);
- if (vm->m_flags[vi] & avoid_flags)
- continue;
- vm->m_flags[vi] |= or_flags;
- vm->m_data[vi] = n;
- }
-}
-
-
-void DungeonGen::makeHole(v3s16 place)
-{
- makeFill(place, dp.holesize, 0, MapNode(CONTENT_AIR),
- VMANIP_FLAG_DUNGEON_INSIDE);
-}
-
-
-void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir)
-{
- makeHole(doorplace);
-
-#ifdef DGEN_USE_TORCHES
- // Place torch (for testing)
- vm->m_data[vm->m_area.index(doorplace)] = MapNode(c_torch);
-#endif
-}
-
-
-void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir,
- v3s16 &result_place, v3s16 &result_dir)
-{
- makeHole(doorplace);
- v3s16 p0 = doorplace;
- v3s16 dir = doordir;
- u32 length = random.range(dp.corridor_len_min, dp.corridor_len_max);
- u32 partlength = random.range(dp.corridor_len_min, dp.corridor_len_max);
- u32 partcount = 0;
- s16 make_stairs = 0;
-
- if (random.next() % 2 == 0 && partlength >= 3)
- make_stairs = random.next() % 2 ? 1 : -1;
-
- for (u32 i = 0; i < length; i++) {
- v3s16 p = p0 + dir;
- if (partcount != 0)
- p.Y += make_stairs;
-
- // Check segment of minimum size corridor is in voxelmanip
- if (vm->m_area.contains(p) && vm->m_area.contains(p + v3s16(0, 1, 0))) {
- if (make_stairs) {
- makeFill(p + v3s16(-1, -1, -1),
- dp.holesize + v3s16(2, 3, 2),
- VMANIP_FLAG_DUNGEON_UNTOUCHABLE,
- MapNode(dp.c_wall),
- 0);
- makeHole(p);
- makeHole(p - dir);
-
- // TODO: fix stairs code so it works 100%
- // (quite difficult)
-
- // exclude stairs from the bottom step
- // exclude stairs from diagonal steps
- if (((dir.X ^ dir.Z) & 1) &&
- (((make_stairs == 1) && i != 0) ||
- ((make_stairs == -1) && i != length - 1))) {
- // rotate face 180 deg if
- // making stairs backwards
- int facedir = dir_to_facedir(dir * make_stairs);
- v3s16 ps = p;
- u16 stair_width = (dir.Z != 0) ? dp.holesize.X : dp.holesize.Z;
- // Stair width direction vector
- v3s16 swv = (dir.Z != 0) ? v3s16(1, 0, 0) : v3s16(0, 0, 1);
-
- for (u16 st = 0; st < stair_width; st++) {
- u32 vi = vm->m_area.index(ps.X - dir.X, ps.Y - 1, ps.Z - dir.Z);
- if (vm->m_area.contains(ps + v3s16(-dir.X, -1, -dir.Z)) &&
- vm->m_data[vi].getContent() == dp.c_wall)
- vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
-
- vi = vm->m_area.index(ps.X, ps.Y, ps.Z);
- if (vm->m_area.contains(ps) &&
- vm->m_data[vi].getContent() == dp.c_wall)
- vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
-
- ps += swv;
- }
- }
- } else {
- makeFill(p + v3s16(-1, -1, -1),
- dp.holesize + v3s16(2, 2, 2),
- VMANIP_FLAG_DUNGEON_UNTOUCHABLE,
- MapNode(dp.c_wall),
- 0);
- makeHole(p);
- }
-
- p0 = p;
- } else {
- // Can't go here, turn away
- dir = turn_xz(dir, random.range(0, 1));
- make_stairs = -make_stairs;
- partcount = 0;
- partlength = random.range(1, length);
- continue;
- }
-
- partcount++;
- if (partcount >= partlength) {
- partcount = 0;
-
- dir = random_turn(random, dir);
-
- partlength = random.range(1, length);
-
- make_stairs = 0;
- if (random.next() % 2 == 0 && partlength >= 3)
- make_stairs = random.next() % 2 ? 1 : -1;
- }
- }
- result_place = p0;
- result_dir = dir;
-}
-
-
-bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir)
-{
- for (u32 i = 0; i < 100; i++) {
- v3s16 p = m_pos + m_dir;
- v3s16 p1 = p + v3s16(0, 1, 0);
- if (!vm->m_area.contains(p) || !vm->m_area.contains(p1) || i % 4 == 0) {
- randomizeDir();
- continue;
- }
- if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_wall &&
- vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_wall) {
- // Found wall, this is a good place!
- result_place = p;
- result_dir = m_dir;
- // Randomize next direction
- randomizeDir();
- return true;
- }
- /*
- Determine where to move next
- */
- // Jump one up if the actual space is there
- if (vm->getNodeNoExNoEmerge(p +
- v3s16(0, 0, 0)).getContent() == dp.c_wall &&
- vm->getNodeNoExNoEmerge(p +
- v3s16(0, 1, 0)).getContent() == CONTENT_AIR &&
- vm->getNodeNoExNoEmerge(p +
- v3s16(0, 2, 0)).getContent() == CONTENT_AIR)
- p += v3s16(0,1,0);
- // Jump one down if the actual space is there
- if (vm->getNodeNoExNoEmerge(p +
- v3s16(0, 1, 0)).getContent() == dp.c_wall &&
- vm->getNodeNoExNoEmerge(p +
- v3s16(0, 0, 0)).getContent() == CONTENT_AIR &&
- vm->getNodeNoExNoEmerge(p +
- v3s16(0, -1, 0)).getContent() == CONTENT_AIR)
- p += v3s16(0, -1, 0);
- // Check if walking is now possible
- if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR ||
- vm->getNodeNoExNoEmerge(p +
- v3s16(0, 1, 0)).getContent() != CONTENT_AIR) {
- // Cannot continue walking here
- randomizeDir();
- continue;
- }
- // Move there
- m_pos = p;
- }
- return false;
-}
-
-
-bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
- v3s16 &result_doordir, v3s16 &result_roomplace)
-{
- for (s16 trycount = 0; trycount < 30; trycount++) {
- v3s16 doorplace;
- v3s16 doordir;
- bool r = findPlaceForDoor(doorplace, doordir);
- if (!r)
- continue;
- v3s16 roomplace;
- // X east, Z north, Y up
- if (doordir == v3s16(1, 0, 0)) // X+
- roomplace = doorplace +
- v3s16(0, -1, random.range(-roomsize.Z + 2, -2));
- if (doordir == v3s16(-1, 0, 0)) // X-
- roomplace = doorplace +
- v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2));
- if (doordir == v3s16(0, 0, 1)) // Z+
- roomplace = doorplace +
- v3s16(random.range(-roomsize.X + 2, -2), -1, 0);
- if (doordir == v3s16(0, 0, -1)) // Z-
- roomplace = doorplace +
- v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1);
-
- // Check fit
- bool fits = true;
- for (s16 z = 1; z < roomsize.Z - 1; z++)
- for (s16 y = 1; y < roomsize.Y - 1; y++)
- for (s16 x = 1; x < roomsize.X - 1; x++) {
- v3s16 p = roomplace + v3s16(x, y, z);
- if (!vm->m_area.contains(p)) {
- fits = false;
- break;
- }
- if (vm->m_flags[vm->m_area.index(p)] & VMANIP_FLAG_DUNGEON_INSIDE) {
- fits = false;
- break;
- }
- }
- if (!fits) {
- // Find new place
- continue;
- }
- result_doorplace = doorplace;
- result_doordir = doordir;
- result_roomplace = roomplace;
- return true;
- }
- return false;
-}
-
-
-v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs)
-{
- // Make diagonal directions somewhat rare
- if (diagonal_dirs && (random.next() % 4 == 0)) {
- v3s16 dir;
- int trycount = 0;
-
- do {
- trycount++;
-
- dir.Z = random.next() % 3 - 1;
- dir.Y = 0;
- dir.X = random.next() % 3 - 1;
- } while ((dir.X == 0 || dir.Z == 0) && trycount < 10);
-
- return dir;
- }
-
- if (random.next() % 2 == 0)
- return random.next() % 2 ? v3s16(-1, 0, 0) : v3s16(1, 0, 0);
-
- return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1);
-}
-
-
-v3s16 turn_xz(v3s16 olddir, int t)
-{
- v3s16 dir;
- if (t == 0) {
- // Turn right
- dir.X = olddir.Z;
- dir.Z = -olddir.X;
- dir.Y = olddir.Y;
- } else {
- // Turn left
- dir.X = -olddir.Z;
- dir.Z = olddir.X;
- dir.Y = olddir.Y;
- }
- return dir;
-}
-
-
-v3s16 random_turn(PseudoRandom &random, v3s16 olddir)
-{
- int turn = random.range(0, 2);
- v3s16 dir;
- if (turn == 0)
- // Go straight
- dir = olddir;
- else if (turn == 1)
- // Turn right
- dir = turn_xz(olddir, 0);
- else
- // Turn left
- dir = turn_xz(olddir, 1);
- return dir;
-}
-
-
-int dir_to_facedir(v3s16 d)
-{
- if (abs(d.X) > abs(d.Z))
- return d.X < 0 ? 3 : 1;
-
- return d.Z < 0 ? 2 : 0;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "voxel.h"
-#include "noise.h"
-#include "mapgen.h"
-
-#define VMANIP_FLAG_DUNGEON_INSIDE VOXELFLAG_CHECKED1
-#define VMANIP_FLAG_DUNGEON_PRESERVE VOXELFLAG_CHECKED2
-#define VMANIP_FLAG_DUNGEON_UNTOUCHABLE (\
- VMANIP_FLAG_DUNGEON_INSIDE|VMANIP_FLAG_DUNGEON_PRESERVE)
-
-class MMVManip;
-class INodeDefManager;
-
-v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs);
-v3s16 turn_xz(v3s16 olddir, int t);
-v3s16 random_turn(PseudoRandom &random, v3s16 olddir);
-int dir_to_facedir(v3s16 d);
-
-
-struct DungeonParams {
- s32 seed;
-
- content_t c_water;
- content_t c_river_water;
- content_t c_wall;
- content_t c_alt_wall;
- content_t c_stair;
-
- bool diagonal_dirs;
- bool only_in_ground;
- v3s16 holesize;
- u16 corridor_len_min;
- u16 corridor_len_max;
- v3s16 room_size_min;
- v3s16 room_size_max;
- v3s16 room_size_large_min;
- v3s16 room_size_large_max;
- u16 rooms_min;
- u16 rooms_max;
- s16 y_min;
- s16 y_max;
- GenNotifyType notifytype;
-
- NoiseParams np_density;
- NoiseParams np_alt_wall;
-};
-
-class DungeonGen {
-public:
- MMVManip *vm;
- INodeDefManager *ndef;
- GenerateNotifier *gennotify;
-
- u32 blockseed;
- PseudoRandom random;
- v3s16 csize;
-
- content_t c_torch;
- DungeonParams dp;
-
- // RoomWalker
- v3s16 m_pos;
- v3s16 m_dir;
-
- DungeonGen(INodeDefManager *ndef,
- GenerateNotifier *gennotify, DungeonParams *dparams);
-
- void generate(MMVManip *vm, u32 bseed,
- v3s16 full_node_min, v3s16 full_node_max);
-
- void makeDungeon(v3s16 start_padding);
- void makeRoom(v3s16 roomsize, v3s16 roomplace);
- void makeCorridor(v3s16 doorplace, v3s16 doordir,
- v3s16 &result_place, v3s16 &result_dir);
- void makeDoor(v3s16 doorplace, v3s16 doordir);
- void makeFill(v3s16 place, v3s16 size, u8 avoid_flags, MapNode n, u8 or_flags);
- void makeHole(v3s16 place);
-
- bool findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir);
- bool findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
- v3s16 &result_doordir, v3s16 &result_roomplace);
-
- inline void randomizeDir()
- {
- m_dir = rand_ortho_dir(random, dp.diagonal_dirs);
- }
-};
-
-extern NoiseParams nparams_dungeon_density;
-extern NoiseParams nparams_dungeon_alt_wall;
#include "log.h"
#include "map.h"
#include "mapblock.h"
-#include "mg_biome.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mg_schematic.h"
+#include "mapgen/mg_biome.h"
+#include "mapgen/mg_ore.h"
+#include "mapgen/mg_decoration.h"
+#include "mapgen/mg_schematic.h"
#include "nodedef.h"
#include "profiler.h"
#include "scripting_server.h"
#include "network/networkprotocol.h"
#include "irr_v3d.h"
#include "util/container.h"
-#include "mapgen.h" // for MapgenParams
+#include "mapgen/mapgen.h" // for MapgenParams
#include "map.h"
#define BLOCK_EMERGE_ALLOW_GEN (1 << 0)
#include "log.h"
#include "filesys.h"
#include "gettext.h"
-#include "guiChatConsole.h"
-#include "guiFormSpecMenu.h"
-#include "guiKeyChangeMenu.h"
-#include "guiPasswordChange.h"
-#include "guiVolumeChange.h"
-#include "mainmenumanager.h"
+#include "gui/guiChatConsole.h"
+#include "gui/guiFormSpecMenu.h"
+#include "gui/guiKeyChangeMenu.h"
+#include "gui/guiPasswordChange.h"
+#include "gui/guiVolumeChange.h"
+#include "gui/mainmenumanager.h"
#include "mapblock.h"
#include "minimap.h"
#include "nodedef.h" // Needed for determining pointing to nodes
--- /dev/null
+set(gui_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiEngine.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiFormSpecMenu.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/intlGUIEditBox.cpp
+ PARENT_SCOPE
+)
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiChatConsole.h"
+#include "chat.h"
+#include "client.h"
+#include "debug.h"
+#include "gettime.h"
+#include "keycode.h"
+#include "settings.h"
+#include "porting.h"
+#include "client/tile.h"
+#include "fontengine.h"
+#include "log.h"
+#include "gettext.h"
+#include <string>
+
+#if USE_FREETYPE
+ #include "irrlicht_changes/CGUITTFont.h"
+#endif
+
+inline u32 clamp_u8(s32 value)
+{
+ return (u32) MYMIN(MYMAX(value, 0), 255);
+}
+
+
+GUIChatConsole::GUIChatConsole(
+ gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent,
+ s32 id,
+ ChatBackend* backend,
+ Client* client,
+ IMenuManager* menumgr
+):
+ IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
+ core::rect<s32>(0,0,100,100)),
+ m_chat_backend(backend),
+ m_client(client),
+ m_menumgr(menumgr),
+ m_animate_time_old(porting::getTimeMs())
+{
+ // load background settings
+ s32 console_alpha = g_settings->getS32("console_alpha");
+ m_background_color.setAlpha(clamp_u8(console_alpha));
+
+ // load the background texture depending on settings
+ ITextureSource *tsrc = client->getTextureSource();
+ if (tsrc->isKnownSourceImage("background_chat.jpg")) {
+ m_background = tsrc->getTexture("background_chat.jpg");
+ m_background_color.setRed(255);
+ m_background_color.setGreen(255);
+ m_background_color.setBlue(255);
+ } else {
+ v3f console_color = g_settings->getV3F("console_color");
+ m_background_color.setRed(clamp_u8(myround(console_color.X)));
+ m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
+ m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
+ }
+
+ m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono);
+
+ if (!m_font) {
+ errorstream << "GUIChatConsole: Unable to load mono font ";
+ } else {
+ core::dimension2d<u32> dim = m_font->getDimension(L"M");
+ m_fontsize = v2u32(dim.Width, dim.Height);
+ m_font->grab();
+ }
+ m_fontsize.X = MYMAX(m_fontsize.X, 1);
+ m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
+
+ // set default cursor options
+ setCursor(true, true, 2.0, 0.1);
+}
+
+GUIChatConsole::~GUIChatConsole()
+{
+ if (m_font)
+ m_font->drop();
+}
+
+void GUIChatConsole::openConsole(f32 scale)
+{
+ assert(scale > 0.0f && scale <= 1.0f);
+
+ m_open = true;
+ m_desired_height_fraction = scale;
+ m_desired_height = scale * m_screensize.Y;
+ reformatConsole();
+ m_animate_time_old = porting::getTimeMs();
+ IGUIElement::setVisible(true);
+ Environment->setFocus(this);
+ m_menumgr->createdMenu(this);
+}
+
+bool GUIChatConsole::isOpen() const
+{
+ return m_open;
+}
+
+bool GUIChatConsole::isOpenInhibited() const
+{
+ return m_open_inhibited > 0;
+}
+
+void GUIChatConsole::closeConsole()
+{
+ m_open = false;
+ Environment->removeFocus(this);
+ m_menumgr->deletingMenu(this);
+}
+
+void GUIChatConsole::closeConsoleAtOnce()
+{
+ closeConsole();
+ m_height = 0;
+ recalculateConsolePosition();
+}
+
+f32 GUIChatConsole::getDesiredHeight() const
+{
+ return m_desired_height_fraction;
+}
+
+void GUIChatConsole::replaceAndAddToHistory(std::wstring line)
+{
+ ChatPrompt& prompt = m_chat_backend->getPrompt();
+ prompt.addToHistory(prompt.getLine());
+ prompt.replace(line);
+}
+
+
+void GUIChatConsole::setCursor(
+ bool visible, bool blinking, f32 blink_speed, f32 relative_height)
+{
+ if (visible)
+ {
+ if (blinking)
+ {
+ // leave m_cursor_blink unchanged
+ m_cursor_blink_speed = blink_speed;
+ }
+ else
+ {
+ m_cursor_blink = 0x8000; // on
+ m_cursor_blink_speed = 0.0;
+ }
+ }
+ else
+ {
+ m_cursor_blink = 0; // off
+ m_cursor_blink_speed = 0.0;
+ }
+ m_cursor_height = relative_height;
+}
+
+void GUIChatConsole::draw()
+{
+ if(!IsVisible)
+ return;
+
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ // Check screen size
+ v2u32 screensize = driver->getScreenSize();
+ if (screensize != m_screensize)
+ {
+ // screen size has changed
+ // scale current console height to new window size
+ if (m_screensize.Y != 0)
+ m_height = m_height * screensize.Y / m_screensize.Y;
+ m_screensize = screensize;
+ m_desired_height = m_desired_height_fraction * m_screensize.Y;
+ reformatConsole();
+ }
+
+ // Animation
+ u64 now = porting::getTimeMs();
+ animate(now - m_animate_time_old);
+ m_animate_time_old = now;
+
+ // Draw console elements if visible
+ if (m_height > 0)
+ {
+ drawBackground();
+ drawText();
+ drawPrompt();
+ }
+
+ gui::IGUIElement::draw();
+}
+
+void GUIChatConsole::reformatConsole()
+{
+ s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
+ s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
+ if (cols <= 0 || rows <= 0)
+ cols = rows = 0;
+ recalculateConsolePosition();
+ m_chat_backend->reformat(cols, rows);
+}
+
+void GUIChatConsole::recalculateConsolePosition()
+{
+ core::rect<s32> rect(0, 0, m_screensize.X, m_height);
+ DesiredRect = rect;
+ recalculateAbsolutePosition(false);
+}
+
+void GUIChatConsole::animate(u32 msec)
+{
+ // animate the console height
+ s32 goal = m_open ? m_desired_height : 0;
+
+ // Set invisible if close animation finished (reset by openConsole)
+ // This function (animate()) is never called once its visibility becomes false so do not
+ // actually set visible to false before the inhibited period is over
+ if (!m_open && m_height == 0 && m_open_inhibited == 0)
+ IGUIElement::setVisible(false);
+
+ if (m_height != goal)
+ {
+ s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
+ if (max_change == 0)
+ max_change = 1;
+
+ if (m_height < goal)
+ {
+ // increase height
+ if (m_height + max_change < goal)
+ m_height += max_change;
+ else
+ m_height = goal;
+ }
+ else
+ {
+ // decrease height
+ if (m_height > goal + max_change)
+ m_height -= max_change;
+ else
+ m_height = goal;
+ }
+
+ recalculateConsolePosition();
+ }
+
+ // blink the cursor
+ if (m_cursor_blink_speed != 0.0)
+ {
+ u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
+ if (blink_increase == 0)
+ blink_increase = 1;
+ m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
+ }
+
+ // decrease open inhibit counter
+ if (m_open_inhibited > msec)
+ m_open_inhibited -= msec;
+ else
+ m_open_inhibited = 0;
+}
+
+void GUIChatConsole::drawBackground()
+{
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ if (m_background != NULL)
+ {
+ core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
+ driver->draw2DImage(
+ m_background,
+ v2s32(0, 0),
+ sourcerect,
+ &AbsoluteClippingRect,
+ m_background_color,
+ false);
+ }
+ else
+ {
+ driver->draw2DRectangle(
+ m_background_color,
+ core::rect<s32>(0, 0, m_screensize.X, m_height),
+ &AbsoluteClippingRect);
+ }
+}
+
+void GUIChatConsole::drawText()
+{
+ if (m_font == NULL)
+ return;
+
+ ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
+ for (u32 row = 0; row < buf.getRows(); ++row)
+ {
+ const ChatFormattedLine& line = buf.getFormattedLine(row);
+ if (line.fragments.empty())
+ continue;
+
+ s32 line_height = m_fontsize.Y;
+ s32 y = row * line_height + m_height - m_desired_height;
+ if (y + line_height < 0)
+ continue;
+
+ for (const ChatFormattedFragment &fragment : line.fragments) {
+ s32 x = (fragment.column + 1) * m_fontsize.X;
+ core::rect<s32> destrect(
+ x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
+
+
+ #if USE_FREETYPE
+ // Draw colored text if FreeType is enabled
+ irr::gui::CGUITTFont *tmp = dynamic_cast<irr::gui::CGUITTFont *>(m_font);
+ tmp->draw(
+ fragment.text,
+ destrect,
+ video::SColor(255, 255, 255, 255),
+ false,
+ false,
+ &AbsoluteClippingRect);
+ #else
+ // Otherwise use standard text
+ m_font->draw(
+ fragment.text.c_str(),
+ destrect,
+ video::SColor(255, 255, 255, 255),
+ false,
+ false,
+ &AbsoluteClippingRect);
+ #endif
+ }
+ }
+}
+
+void GUIChatConsole::drawPrompt()
+{
+ if (!m_font)
+ return;
+
+ u32 row = m_chat_backend->getConsoleBuffer().getRows();
+ s32 line_height = m_fontsize.Y;
+ s32 y = row * line_height + m_height - m_desired_height;
+
+ ChatPrompt& prompt = m_chat_backend->getPrompt();
+ std::wstring prompt_text = prompt.getVisiblePortion();
+
+ // FIXME Draw string at once, not character by character
+ // That will only work with the cursor once we have a monospace font
+ for (u32 i = 0; i < prompt_text.size(); ++i)
+ {
+ wchar_t ws[2] = {prompt_text[i], 0};
+ s32 x = (1 + i) * m_fontsize.X;
+ core::rect<s32> destrect(
+ x, y, x + m_fontsize.X, y + m_fontsize.Y);
+ m_font->draw(
+ ws,
+ destrect,
+ video::SColor(255, 255, 255, 255),
+ false,
+ false,
+ &AbsoluteClippingRect);
+ }
+
+ // Draw the cursor during on periods
+ if ((m_cursor_blink & 0x8000) != 0)
+ {
+ s32 cursor_pos = prompt.getVisibleCursorPosition();
+ if (cursor_pos >= 0)
+ {
+ s32 cursor_len = prompt.getCursorLength();
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ s32 x = (1 + cursor_pos) * m_fontsize.X;
+ core::rect<s32> destrect(
+ x,
+ y + m_fontsize.Y * (1.0 - m_cursor_height),
+ x + m_fontsize.X * MYMAX(cursor_len, 1),
+ y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1)
+ );
+ video::SColor cursor_color(255,255,255,255);
+ driver->draw2DRectangle(
+ cursor_color,
+ destrect,
+ &AbsoluteClippingRect);
+ }
+ }
+
+}
+
+bool GUIChatConsole::OnEvent(const SEvent& event)
+{
+
+ ChatPrompt &prompt = m_chat_backend->getPrompt();
+
+ if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
+ {
+ // Key input
+ if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) {
+ closeConsole();
+
+ // inhibit open so the_game doesn't reopen immediately
+ m_open_inhibited = 50;
+ m_close_on_enter = false;
+ return true;
+ }
+
+ if (event.KeyInput.Key == KEY_ESCAPE) {
+ closeConsoleAtOnce();
+ m_close_on_enter = false;
+ // inhibit open so the_game doesn't reopen immediately
+ m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_PRIOR)
+ {
+ m_chat_backend->scrollPageUp();
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_NEXT)
+ {
+ m_chat_backend->scrollPageDown();
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_RETURN)
+ {
+ prompt.addToHistory(prompt.getLine());
+ std::wstring text = prompt.replace(L"");
+ m_client->typeChatMessage(text);
+ if (m_close_on_enter) {
+ closeConsoleAtOnce();
+ m_close_on_enter = false;
+ }
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_UP)
+ {
+ // Up pressed
+ // Move back in history
+ prompt.historyPrev();
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_DOWN)
+ {
+ // Down pressed
+ // Move forward in history
+ prompt.historyNext();
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
+ {
+ // Left/right pressed
+ // Move/select character/word to the left depending on control and shift keys
+ ChatPrompt::CursorOp op = event.KeyInput.Shift ?
+ ChatPrompt::CURSOROP_SELECT :
+ ChatPrompt::CURSOROP_MOVE;
+ ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
+ ChatPrompt::CURSOROP_DIR_LEFT :
+ ChatPrompt::CURSOROP_DIR_RIGHT;
+ ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
+ ChatPrompt::CURSOROP_SCOPE_WORD :
+ ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+ prompt.cursorOperation(op, dir, scope);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_HOME)
+ {
+ // Home pressed
+ // move to beginning of line
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_MOVE,
+ ChatPrompt::CURSOROP_DIR_LEFT,
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_END)
+ {
+ // End pressed
+ // move to end of line
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_MOVE,
+ ChatPrompt::CURSOROP_DIR_RIGHT,
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_BACK)
+ {
+ // Backspace or Ctrl-Backspace pressed
+ // delete character / word to the left
+ ChatPrompt::CursorOpScope scope =
+ event.KeyInput.Control ?
+ ChatPrompt::CURSOROP_SCOPE_WORD :
+ ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_LEFT,
+ scope);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_DELETE)
+ {
+ // Delete or Ctrl-Delete pressed
+ // delete character / word to the right
+ ChatPrompt::CursorOpScope scope =
+ event.KeyInput.Control ?
+ ChatPrompt::CURSOROP_SCOPE_WORD :
+ ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_RIGHT,
+ scope);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
+ {
+ // Ctrl-A pressed
+ // Select all text
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_SELECT,
+ ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
+ {
+ // Ctrl-C pressed
+ // Copy text to clipboard
+ if (prompt.getCursorLength() <= 0)
+ return true;
+ std::wstring wselected = prompt.getSelection();
+ std::string selected(wselected.begin(), wselected.end());
+ Environment->getOSOperator()->copyToClipboard(selected.c_str());
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
+ {
+ // Ctrl-V pressed
+ // paste text from clipboard
+ if (prompt.getCursorLength() > 0) {
+ // Delete selected section of text
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+ ChatPrompt::CURSOROP_SCOPE_SELECTION);
+ }
+ IOSOperator *os_operator = Environment->getOSOperator();
+ const c8 *text = os_operator->getTextFromClipboard();
+ if (!text)
+ return true;
+ std::basic_string<unsigned char> str((const unsigned char*)text);
+ prompt.input(std::wstring(str.begin(), str.end()));
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
+ {
+ // Ctrl-X pressed
+ // Cut text to clipboard
+ if (prompt.getCursorLength() <= 0)
+ return true;
+ std::wstring wselected = prompt.getSelection();
+ std::string selected(wselected.begin(), wselected.end());
+ Environment->getOSOperator()->copyToClipboard(selected.c_str());
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+ ChatPrompt::CURSOROP_SCOPE_SELECTION);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
+ {
+ // Ctrl-U pressed
+ // kill line to left end
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_LEFT,
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
+ {
+ // Ctrl-K pressed
+ // kill line to right end
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_RIGHT,
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_TAB)
+ {
+ // Tab or Shift-Tab pressed
+ // Nick completion
+ std::list<std::string> names = m_client->getConnectedPlayerNames();
+ bool backwards = event.KeyInput.Shift;
+ prompt.nickCompletion(names, backwards);
+ return true;
+ } else if (!iswcntrl(event.KeyInput.Char) && !event.KeyInput.Control) {
+ #if defined(__linux__) && (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9)
+ wchar_t wc = L'_';
+ mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
+ prompt.input(wc);
+ #else
+ prompt.input(event.KeyInput.Char);
+ #endif
+ return true;
+ }
+ }
+ else if(event.EventType == EET_MOUSE_INPUT_EVENT)
+ {
+ if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
+ {
+ s32 rows = myround(-3.0 * event.MouseInput.Wheel);
+ m_chat_backend->scroll(rows);
+ }
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
+void GUIChatConsole::setVisible(bool visible)
+{
+ m_open = visible;
+ IGUIElement::setVisible(visible);
+ if (!visible) {
+ m_height = 0;
+ recalculateConsolePosition();
+ }
+}
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include "chat.h"
+#include "config.h"
+
+class Client;
+
+class GUIChatConsole : public gui::IGUIElement
+{
+public:
+ GUIChatConsole(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent,
+ s32 id,
+ ChatBackend* backend,
+ Client* client,
+ IMenuManager* menumgr);
+ virtual ~GUIChatConsole();
+
+ // Open the console (height = desired fraction of screen size)
+ // This doesn't open immediately but initiates an animation.
+ // You should call isOpenInhibited() before this.
+ void openConsole(f32 scale);
+
+ bool isOpen() const;
+
+ // Check if the console should not be opened at the moment
+ // This is to avoid reopening the console immediately after closing
+ bool isOpenInhibited() const;
+ // Close the console, equivalent to openConsole(0).
+ // This doesn't close immediately but initiates an animation.
+ void closeConsole();
+ // Close the console immediately, without animation.
+ void closeConsoleAtOnce();
+ // Set whether to close the console after the user presses enter.
+ void setCloseOnEnter(bool close) { m_close_on_enter = close; }
+
+ // Return the desired height (fraction of screen size)
+ // Zero if the console is closed or getting closed
+ f32 getDesiredHeight() const;
+
+ // Replace actual line when adding the actual to the history (if there is any)
+ void replaceAndAddToHistory(std::wstring line);
+
+ // Change how the cursor looks
+ void setCursor(
+ bool visible,
+ bool blinking = false,
+ f32 blink_speed = 1.0,
+ f32 relative_height = 1.0);
+
+ // Irrlicht draw method
+ virtual void draw();
+
+ bool canTakeFocus(gui::IGUIElement* element) { return false; }
+
+ virtual bool OnEvent(const SEvent& event);
+
+ virtual void setVisible(bool visible);
+
+private:
+ void reformatConsole();
+ void recalculateConsolePosition();
+
+ // These methods are called by draw
+ void animate(u32 msec);
+ void drawBackground();
+ void drawText();
+ void drawPrompt();
+
+private:
+ ChatBackend* m_chat_backend;
+ Client* m_client;
+ IMenuManager* m_menumgr;
+
+ // current screen size
+ v2u32 m_screensize;
+
+ // used to compute how much time passed since last animate()
+ u64 m_animate_time_old;
+
+ // should the console be opened or closed?
+ bool m_open = false;
+ // should it close after you press enter?
+ bool m_close_on_enter = false;
+ // current console height [pixels]
+ s32 m_height = 0;
+ // desired height [pixels]
+ f32 m_desired_height = 0.0f;
+ // desired height [screen height fraction]
+ f32 m_desired_height_fraction = 0.0f;
+ // console open/close animation speed [screen height fraction / second]
+ f32 m_height_speed = 5.0f;
+ // if nonzero, opening the console is inhibited [milliseconds]
+ u32 m_open_inhibited = 0;
+
+ // cursor blink frame (16-bit value)
+ // cursor is off during [0,32767] and on during [32768,65535]
+ u32 m_cursor_blink = 0;
+ // cursor blink speed [on/off toggles / second]
+ f32 m_cursor_blink_speed = 0.0f;
+ // cursor height [line height]
+ f32 m_cursor_height = 0.0f;
+
+ // background texture
+ video::ITexture *m_background = nullptr;
+ // background color (including alpha)
+ video::SColor m_background_color = video::SColor(255, 0, 0, 0);
+
+ // font
+ gui::IGUIFont *m_font = nullptr;
+ v2u32 m_fontsize;
+};
--- /dev/null
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// Modified by Mustapha T.
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include "guiEditBoxWithScrollbar.h"
+
+#include "IGUISkin.h"
+#include "IGUIEnvironment.h"
+#include "IGUIFont.h"
+#include "IVideoDriver.h"
+#include "rect.h"
+#include "porting.h"
+#include "Keycodes.h"
+
+
+/*
+todo:
+optional scrollbars [done]
+ctrl+left/right to select word
+double click/ctrl click: word select + drag to select whole words, triple click to select line
+optional? dragging selected text
+numerical
+*/
+
+
+//! constructor
+GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool border,
+ IGUIEnvironment* environment, IGUIElement* parent, s32 id,
+ const core::rect<s32>& rectangle, bool writable, bool has_vscrollbar)
+ : IGUIEditBox(environment, parent, id, rectangle), m_mouse_marking(false),
+ m_border(border), m_background(true), m_override_color_enabled(false), m_mark_begin(0), m_mark_end(0),
+ m_override_color(video::SColor(101, 255, 255, 255)), m_override_font(0), m_last_break_font(0),
+ m_operator(0), m_blink_start_time(0), m_cursor_pos(0), m_hscroll_pos(0), m_vscroll_pos(0), m_max(0),
+ m_word_wrap(false), m_multiline(false), m_autoscroll(true), m_passwordbox(false),
+ m_passwordchar(L'*'), m_halign(EGUIA_UPPERLEFT), m_valign(EGUIA_CENTER),
+ m_current_text_rect(0, 0, 1, 1), m_frame_rect(rectangle),
+ m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable),
+ m_bg_color_used(false)
+{
+#ifdef _DEBUG
+ setDebugName("GUIEditBoxWithScrollBar");
+#endif
+
+
+ Text = text;
+
+ if (Environment)
+ m_operator = Environment->getOSOperator();
+
+ if (m_operator)
+ m_operator->grab();
+
+ // this element can be tabbed to
+ setTabStop(true);
+ setTabOrder(-1);
+
+ if (has_vscrollbar) {
+ createVScrollBar();
+ }
+
+ calculateFrameRect();
+ breakText();
+
+ calculateScrollPos();
+ setWritable(writable);
+}
+
+
+//! destructor
+GUIEditBoxWithScrollBar::~GUIEditBoxWithScrollBar()
+{
+ if (m_override_font)
+ m_override_font->drop();
+
+ if (m_operator)
+ m_operator->drop();
+
+ m_vscrollbar->remove();
+}
+
+
+//! Sets another skin independent font.
+void GUIEditBoxWithScrollBar::setOverrideFont(IGUIFont* font)
+{
+ if (m_override_font == font)
+ return;
+
+ if (m_override_font)
+ m_override_font->drop();
+
+ m_override_font = font;
+
+ if (m_override_font)
+ m_override_font->grab();
+
+ breakText();
+}
+
+//! Gets the override font (if any)
+IGUIFont * GUIEditBoxWithScrollBar::getOverrideFont() const
+{
+ return m_override_font;
+}
+
+//! Get the font which is used right now for drawing
+IGUIFont* GUIEditBoxWithScrollBar::getActiveFont() const
+{
+ if (m_override_font)
+ return m_override_font;
+ IGUISkin* skin = Environment->getSkin();
+ if (skin)
+ return skin->getFont();
+ return 0;
+}
+
+//! Sets another color for the text.
+void GUIEditBoxWithScrollBar::setOverrideColor(video::SColor color)
+{
+ m_override_color = color;
+ m_override_color_enabled = true;
+}
+
+
+video::SColor GUIEditBoxWithScrollBar::getOverrideColor() const
+{
+ return m_override_color;
+}
+
+
+//! Turns the border on or off
+void GUIEditBoxWithScrollBar::setDrawBorder(bool border)
+{
+ m_border = border;
+}
+
+//! Sets whether to draw the background
+void GUIEditBoxWithScrollBar::setDrawBackground(bool draw)
+{
+ m_background = draw;
+}
+
+//! Sets if the text should use the overide color or the color in the gui skin.
+void GUIEditBoxWithScrollBar::enableOverrideColor(bool enable)
+{
+ m_override_color_enabled = enable;
+}
+
+bool GUIEditBoxWithScrollBar::isOverrideColorEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_override_color_enabled;
+}
+
+//! Enables or disables word wrap
+void GUIEditBoxWithScrollBar::setWordWrap(bool enable)
+{
+ m_word_wrap = enable;
+ breakText();
+}
+
+
+void GUIEditBoxWithScrollBar::updateAbsolutePosition()
+{
+ core::rect<s32> old_absolute_rect(AbsoluteRect);
+ IGUIElement::updateAbsolutePosition();
+ if (old_absolute_rect != AbsoluteRect) {
+ calculateFrameRect();
+ breakText();
+ calculateScrollPos();
+ }
+}
+
+//! Checks if word wrap is enabled
+bool GUIEditBoxWithScrollBar::isWordWrapEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_word_wrap;
+}
+
+
+//! Enables or disables newlines.
+void GUIEditBoxWithScrollBar::setMultiLine(bool enable)
+{
+ m_multiline = enable;
+}
+
+
+//! Checks if multi line editing is enabled
+bool GUIEditBoxWithScrollBar::isMultiLineEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_multiline;
+}
+
+
+void GUIEditBoxWithScrollBar::setPasswordBox(bool password_box, wchar_t password_char)
+{
+ m_passwordbox = password_box;
+ if (m_passwordbox) {
+ m_passwordchar = password_char;
+ setMultiLine(false);
+ setWordWrap(false);
+ m_broken_text.clear();
+ }
+}
+
+
+bool GUIEditBoxWithScrollBar::isPasswordBox() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_passwordbox;
+}
+
+
+//! Sets text justification
+void GUIEditBoxWithScrollBar::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
+{
+ m_halign = horizontal;
+ m_valign = vertical;
+}
+
+
+//! called if an event happened.
+bool GUIEditBoxWithScrollBar::OnEvent(const SEvent& event)
+{
+ if (isEnabled()) {
+ switch (event.EventType)
+ {
+ case EET_GUI_EVENT:
+ if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) {
+ if (event.GUIEvent.Caller == this) {
+ m_mouse_marking = false;
+ setTextMarkers(0, 0);
+ }
+ }
+ break;
+ case EET_KEY_INPUT_EVENT:
+ if (processKey(event))
+ return true;
+ break;
+ case EET_MOUSE_INPUT_EVENT:
+ if (processMouse(event))
+ return true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return IGUIElement::OnEvent(event);
+}
+
+
+bool GUIEditBoxWithScrollBar::processKey(const SEvent& event)
+{
+ if (!m_writable) {
+ return false;
+ }
+
+ if (!event.KeyInput.PressedDown)
+ return false;
+
+ bool text_changed = false;
+ s32 new_mark_begin = m_mark_begin;
+ s32 new_mark_end = m_mark_end;
+
+ // control shortcut handling
+
+ if (event.KeyInput.Control) {
+
+ // german backlash '\' entered with control + '?'
+ if (event.KeyInput.Char == '\\') {
+ inputChar(event.KeyInput.Char);
+ return true;
+ }
+
+ switch (event.KeyInput.Key) {
+ case KEY_KEY_A:
+ // select all
+ new_mark_begin = 0;
+ new_mark_end = Text.size();
+ break;
+ case KEY_KEY_C:
+ // copy to clipboard
+ if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end)
+ {
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ core::stringc s;
+ s = Text.subString(realmbgn, realmend - realmbgn).c_str();
+ m_operator->copyToClipboard(s.c_str());
+ }
+ break;
+ case KEY_KEY_X:
+ // cut to the clipboard
+ if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end) {
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ // copy
+ core::stringc sc;
+ sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
+ m_operator->copyToClipboard(sc.c_str());
+
+ if (isEnabled())
+ {
+ // delete
+ core::stringw s;
+ s = Text.subString(0, realmbgn);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+ Text = s;
+
+ m_cursor_pos = realmbgn;
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ text_changed = true;
+ }
+ }
+ break;
+ case KEY_KEY_V:
+ if (!isEnabled())
+ break;
+
+ // paste from the clipboard
+ if (m_operator) {
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ // add new character
+ const c8* p = m_operator->getTextFromClipboard();
+ if (p) {
+ if (m_mark_begin == m_mark_end) {
+ // insert text
+ core::stringw s = Text.subString(0, m_cursor_pos);
+ s.append(p);
+ s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
+
+ if (!m_max || s.size() <= m_max) // thx to Fish FH for fix
+ {
+ Text = s;
+ s = p;
+ m_cursor_pos += s.size();
+ }
+ } else {
+ // replace text
+
+ core::stringw s = Text.subString(0, realmbgn);
+ s.append(p);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+
+ if (!m_max || s.size() <= m_max) // thx to Fish FH for fix
+ {
+ Text = s;
+ s = p;
+ m_cursor_pos = realmbgn + s.size();
+ }
+ }
+ }
+
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ text_changed = true;
+ }
+ break;
+ case KEY_HOME:
+ // move/highlight to start of text
+ if (event.KeyInput.Shift) {
+ new_mark_end = m_cursor_pos;
+ new_mark_begin = 0;
+ m_cursor_pos = 0;
+ } else {
+ m_cursor_pos = 0;
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ break;
+ case KEY_END:
+ // move/highlight to end of text
+ if (event.KeyInput.Shift) {
+ new_mark_begin = m_cursor_pos;
+ new_mark_end = Text.size();
+ m_cursor_pos = 0;
+ } else {
+ m_cursor_pos = Text.size();
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ // default keyboard handling
+ else
+ switch (event.KeyInput.Key) {
+ case KEY_END:
+ {
+ s32 p = Text.size();
+ if (m_word_wrap || m_multiline) {
+ p = getLineFromPos(m_cursor_pos);
+ p = m_broken_text_positions[p] + (s32)m_broken_text[p].size();
+ if (p > 0 && (Text[p - 1] == L'\r' || Text[p - 1] == L'\n'))
+ p -= 1;
+ }
+
+ if (event.KeyInput.Shift) {
+ if (m_mark_begin == m_mark_end)
+ new_mark_begin = m_cursor_pos;
+
+ new_mark_end = p;
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ m_cursor_pos = p;
+ m_blink_start_time = porting::getTimeMs();
+ }
+ break;
+ case KEY_HOME:
+ {
+
+ s32 p = 0;
+ if (m_word_wrap || m_multiline) {
+ p = getLineFromPos(m_cursor_pos);
+ p = m_broken_text_positions[p];
+ }
+
+ if (event.KeyInput.Shift) {
+ if (m_mark_begin == m_mark_end)
+ new_mark_begin = m_cursor_pos;
+ new_mark_end = p;
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ m_cursor_pos = p;
+ m_blink_start_time = porting::getTimeMs();
+ }
+ break;
+ case KEY_RETURN:
+ if (m_multiline) {
+ inputChar(L'\n');
+ } else {
+ calculateScrollPos();
+ sendGuiEvent(EGET_EDITBOX_ENTER);
+ }
+ return true;
+ case KEY_LEFT:
+
+ if (event.KeyInput.Shift) {
+ if (m_cursor_pos > 0) {
+ if (m_mark_begin == m_mark_end)
+ new_mark_begin = m_cursor_pos;
+
+ new_mark_end = m_cursor_pos - 1;
+ }
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+
+ if (m_cursor_pos > 0)
+ m_cursor_pos--;
+ m_blink_start_time = porting::getTimeMs();
+ break;
+
+ case KEY_RIGHT:
+ if (event.KeyInput.Shift) {
+ if (Text.size() > (u32)m_cursor_pos) {
+ if (m_mark_begin == m_mark_end)
+ new_mark_begin = m_cursor_pos;
+
+ new_mark_end = m_cursor_pos + 1;
+ }
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+
+ if (Text.size() > (u32)m_cursor_pos)
+ m_cursor_pos++;
+ m_blink_start_time = porting::getTimeMs();
+ break;
+ case KEY_UP:
+ if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
+ s32 lineNo = getLineFromPos(m_cursor_pos);
+ s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin > m_mark_end ? m_mark_begin : m_mark_end);
+ if (lineNo > 0) {
+ s32 cp = m_cursor_pos - m_broken_text_positions[lineNo];
+ if ((s32)m_broken_text[lineNo - 1].size() < cp)
+ m_cursor_pos = m_broken_text_positions[lineNo - 1] + core::max_((u32)1, m_broken_text[lineNo - 1].size()) - 1;
+ else
+ m_cursor_pos = m_broken_text_positions[lineNo - 1] + cp;
+ }
+
+ if (event.KeyInput.Shift) {
+ new_mark_begin = mb;
+ new_mark_end = m_cursor_pos;
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ } else {
+ return false;
+ }
+ break;
+ case KEY_DOWN:
+ if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
+ s32 lineNo = getLineFromPos(m_cursor_pos);
+ s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end);
+ if (lineNo < (s32)m_broken_text.size() - 1)
+ {
+ s32 cp = m_cursor_pos - m_broken_text_positions[lineNo];
+ if ((s32)m_broken_text[lineNo + 1].size() < cp)
+ m_cursor_pos = m_broken_text_positions[lineNo + 1] + core::max_((u32)1, m_broken_text[lineNo + 1].size()) - 1;
+ else
+ m_cursor_pos = m_broken_text_positions[lineNo + 1] + cp;
+ }
+
+ if (event.KeyInput.Shift) {
+ new_mark_begin = mb;
+ new_mark_end = m_cursor_pos;
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+
+ } else {
+ return false;
+ }
+ break;
+
+ case KEY_BACK:
+ if (!isEnabled())
+ break;
+
+ if (Text.size()) {
+ core::stringw s;
+
+ if (m_mark_begin != m_mark_end) {
+ // delete marked text
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ s = Text.subString(0, realmbgn);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+ Text = s;
+
+ m_cursor_pos = realmbgn;
+ } else {
+ // delete text behind cursor
+ if (m_cursor_pos > 0)
+ s = Text.subString(0, m_cursor_pos - 1);
+ else
+ s = L"";
+ s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
+ Text = s;
+ --m_cursor_pos;
+ }
+
+ if (m_cursor_pos < 0)
+ m_cursor_pos = 0;
+ m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime();
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ text_changed = true;
+ }
+ break;
+ case KEY_DELETE:
+ if (!isEnabled())
+ break;
+
+ if (Text.size() != 0) {
+ core::stringw s;
+
+ if (m_mark_begin != m_mark_end) {
+ // delete marked text
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ s = Text.subString(0, realmbgn);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+ Text = s;
+
+ m_cursor_pos = realmbgn;
+ } else {
+ // delete text before cursor
+ s = Text.subString(0, m_cursor_pos);
+ s.append(Text.subString(m_cursor_pos + 1, Text.size() - m_cursor_pos - 1));
+ Text = s;
+ }
+
+ if (m_cursor_pos > (s32)Text.size())
+ m_cursor_pos = (s32)Text.size();
+
+ m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime();
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ text_changed = true;
+ }
+ break;
+
+ case KEY_ESCAPE:
+ case KEY_TAB:
+ case KEY_SHIFT:
+ case KEY_F1:
+ case KEY_F2:
+ case KEY_F3:
+ case KEY_F4:
+ case KEY_F5:
+ case KEY_F6:
+ case KEY_F7:
+ case KEY_F8:
+ case KEY_F9:
+ case KEY_F10:
+ case KEY_F11:
+ case KEY_F12:
+ case KEY_F13:
+ case KEY_F14:
+ case KEY_F15:
+ case KEY_F16:
+ case KEY_F17:
+ case KEY_F18:
+ case KEY_F19:
+ case KEY_F20:
+ case KEY_F21:
+ case KEY_F22:
+ case KEY_F23:
+ case KEY_F24:
+ // ignore these keys
+ return false;
+
+ default:
+ inputChar(event.KeyInput.Char);
+ return true;
+ }
+
+ // Set new text markers
+ setTextMarkers(new_mark_begin, new_mark_end);
+
+ // break the text if it has changed
+ if (text_changed) {
+ breakText();
+ calculateScrollPos();
+ sendGuiEvent(EGET_EDITBOX_CHANGED);
+ }
+ else
+ {
+ calculateScrollPos();
+ }
+
+ return true;
+}
+
+
+//! draws the element and its children
+void GUIEditBoxWithScrollBar::draw()
+{
+ if (!IsVisible)
+ return;
+
+ const bool focus = Environment->hasFocus(this);
+
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ video::SColor default_bg_color;
+ video::SColor bg_color;
+
+ default_bg_color = m_writable ? skin->getColor(EGDC_WINDOW) : video::SColor(0);
+ bg_color = m_bg_color_used ? m_bg_color : default_bg_color;
+
+ if (!m_border && m_background) {
+ skin->draw2DRectangle(this, bg_color, AbsoluteRect, &AbsoluteClippingRect);
+ }
+
+ // draw the border
+
+ if (m_border) {
+
+ if (m_writable) {
+ skin->draw3DSunkenPane(this, bg_color, false, m_background,
+ AbsoluteRect, &AbsoluteClippingRect);
+ }
+
+ calculateFrameRect();
+ }
+
+ core::rect<s32> local_clip_rect = m_frame_rect;
+ local_clip_rect.clipAgainst(AbsoluteClippingRect);
+
+ // draw the text
+
+ IGUIFont* font = getActiveFont();
+
+ s32 cursor_line = 0;
+ s32 charcursorpos = 0;
+
+ if (font) {
+ if (m_last_break_font != font) {
+ breakText();
+ }
+
+ // calculate cursor pos
+
+ core::stringw *txt_line = &Text;
+ s32 start_pos = 0;
+
+ core::stringw s, s2;
+
+ // get mark position
+ const bool ml = (!m_passwordbox && (m_word_wrap || m_multiline));
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+ const s32 hline_start = ml ? getLineFromPos(realmbgn) : 0;
+ const s32 hline_count = ml ? getLineFromPos(realmend) - hline_start + 1 : 1;
+ const s32 line_count = ml ? m_broken_text.size() : 1;
+
+ // Save the override color information.
+ // Then, alter it if the edit box is disabled.
+ const bool prevOver = m_override_color_enabled;
+ const video::SColor prevColor = m_override_color;
+
+ if (Text.size()) {
+ if (!isEnabled() && !m_override_color_enabled) {
+ m_override_color_enabled = true;
+ m_override_color = skin->getColor(EGDC_GRAY_TEXT);
+ }
+
+ for (s32 i = 0; i < line_count; ++i) {
+ setTextRect(i);
+
+ // clipping test - don't draw anything outside the visible area
+ core::rect<s32> c = local_clip_rect;
+ c.clipAgainst(m_current_text_rect);
+ if (!c.isValid())
+ continue;
+
+ // get current line
+ if (m_passwordbox) {
+ if (m_broken_text.size() != 1) {
+ m_broken_text.clear();
+ m_broken_text.push_back(core::stringw());
+ }
+ if (m_broken_text[0].size() != Text.size()){
+ m_broken_text[0] = Text;
+ for (u32 q = 0; q < Text.size(); ++q)
+ {
+ m_broken_text[0][q] = m_passwordchar;
+ }
+ }
+ txt_line = &m_broken_text[0];
+ start_pos = 0;
+ } else {
+ txt_line = ml ? &m_broken_text[i] : &Text;
+ start_pos = ml ? m_broken_text_positions[i] : 0;
+ }
+
+
+ // draw normal text
+ font->draw(txt_line->c_str(), m_current_text_rect,
+ m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT),
+ false, true, &local_clip_rect);
+
+ // draw mark and marked text
+ if (focus && m_mark_begin != m_mark_end && i >= hline_start && i < hline_start + hline_count) {
+
+ s32 mbegin = 0, mend = 0;
+ s32 lineStartPos = 0, lineEndPos = txt_line->size();
+
+ if (i == hline_start) {
+ // highlight start is on this line
+ s = txt_line->subString(0, realmbgn - start_pos);
+ mbegin = font->getDimension(s.c_str()).Width;
+
+ // deal with kerning
+ mbegin += font->getKerningWidth(
+ &((*txt_line)[realmbgn - start_pos]),
+ realmbgn - start_pos > 0 ? &((*txt_line)[realmbgn - start_pos - 1]) : 0);
+
+ lineStartPos = realmbgn - start_pos;
+ }
+ if (i == hline_start + hline_count - 1) {
+ // highlight end is on this line
+ s2 = txt_line->subString(0, realmend - start_pos);
+ mend = font->getDimension(s2.c_str()).Width;
+ lineEndPos = (s32)s2.size();
+ } else {
+ mend = font->getDimension(txt_line->c_str()).Width;
+ }
+
+
+ m_current_text_rect.UpperLeftCorner.X += mbegin;
+ m_current_text_rect.LowerRightCorner.X = m_current_text_rect.UpperLeftCorner.X + mend - mbegin;
+
+
+ // draw mark
+ skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), m_current_text_rect, &local_clip_rect);
+
+ // draw marked text
+ s = txt_line->subString(lineStartPos, lineEndPos - lineStartPos);
+
+ if (s.size())
+ font->draw(s.c_str(), m_current_text_rect,
+ m_override_color_enabled ? m_override_color : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
+ false, true, &local_clip_rect);
+
+ }
+ }
+
+ // Return the override color information to its previous settings.
+ m_override_color_enabled = prevOver;
+ m_override_color = prevColor;
+ }
+
+ // draw cursor
+ if (IsEnabled && m_writable) {
+ if (m_word_wrap || m_multiline) {
+ cursor_line = getLineFromPos(m_cursor_pos);
+ txt_line = &m_broken_text[cursor_line];
+ start_pos = m_broken_text_positions[cursor_line];
+ }
+ s = txt_line->subString(0, m_cursor_pos - start_pos);
+ charcursorpos = font->getDimension(s.c_str()).Width +
+ font->getKerningWidth(L"_", m_cursor_pos - start_pos > 0 ? &((*txt_line)[m_cursor_pos - start_pos - 1]) : 0);
+
+ if (focus && (porting::getTimeMs() - m_blink_start_time) % 700 < 350) {
+ setTextRect(cursor_line);
+ m_current_text_rect.UpperLeftCorner.X += charcursorpos;
+
+ font->draw(L"_", m_current_text_rect,
+ m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT),
+ false, true, &local_clip_rect);
+ }
+ }
+ }
+
+ // draw children
+ IGUIElement::draw();
+}
+
+
+//! Sets the new caption of this element.
+void GUIEditBoxWithScrollBar::setText(const wchar_t* text)
+{
+ Text = text;
+ if (u32(m_cursor_pos) > Text.size())
+ m_cursor_pos = Text.size();
+ m_hscroll_pos = 0;
+ breakText();
+}
+
+
+//! Enables or disables automatic scrolling with cursor position
+//! \param enable: If set to true, the text will move around with the cursor position
+void GUIEditBoxWithScrollBar::setAutoScroll(bool enable)
+{
+ m_autoscroll = enable;
+}
+
+
+//! Checks to see if automatic scrolling is enabled
+//! \return true if automatic scrolling is enabled, false if not
+bool GUIEditBoxWithScrollBar::isAutoScrollEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_autoscroll;
+}
+
+
+//! Gets the area of the text in the edit box
+//! \return Returns the size in pixels of the text
+core::dimension2du GUIEditBoxWithScrollBar::getTextDimension()
+{
+ core::rect<s32> ret;
+
+ setTextRect(0);
+ ret = m_current_text_rect;
+
+ for (u32 i = 1; i < m_broken_text.size(); ++i) {
+ setTextRect(i);
+ ret.addInternalPoint(m_current_text_rect.UpperLeftCorner);
+ ret.addInternalPoint(m_current_text_rect.LowerRightCorner);
+ }
+
+ return core::dimension2du(ret.getSize());
+}
+
+
+//! Sets the maximum amount of characters which may be entered in the box.
+//! \param max: Maximum amount of characters. If 0, the character amount is
+//! infinity.
+void GUIEditBoxWithScrollBar::setMax(u32 max)
+{
+ m_max = max;
+
+ if (Text.size() > m_max && m_max != 0)
+ Text = Text.subString(0, m_max);
+}
+
+
+//! Returns maximum amount of characters, previously set by setMax();
+u32 GUIEditBoxWithScrollBar::getMax() const
+{
+ return m_max;
+}
+
+
+bool GUIEditBoxWithScrollBar::processMouse(const SEvent& event)
+{
+ switch (event.MouseInput.Event)
+ {
+ case irr::EMIE_LMOUSE_LEFT_UP:
+ if (Environment->hasFocus(this)) {
+ m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ if (m_mouse_marking) {
+ setTextMarkers(m_mark_begin, m_cursor_pos);
+ }
+ m_mouse_marking = false;
+ calculateScrollPos();
+ return true;
+ }
+ break;
+ case irr::EMIE_MOUSE_MOVED:
+ {
+ if (m_mouse_marking) {
+ m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ setTextMarkers(m_mark_begin, m_cursor_pos);
+ calculateScrollPos();
+ return true;
+ }
+ }
+ break;
+ case EMIE_LMOUSE_PRESSED_DOWN:
+
+ if (!Environment->hasFocus(this)) {
+ m_blink_start_time = porting::getTimeMs();
+ m_mouse_marking = true;
+ m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ setTextMarkers(m_cursor_pos, m_cursor_pos);
+ calculateScrollPos();
+ return true;
+ } else {
+ if (!AbsoluteClippingRect.isPointInside(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) {
+ return false;
+ } else {
+ // move cursor
+ m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+
+ s32 newMarkBegin = m_mark_begin;
+ if (!m_mouse_marking)
+ newMarkBegin = m_cursor_pos;
+
+ m_mouse_marking = true;
+ setTextMarkers(newMarkBegin, m_cursor_pos);
+ calculateScrollPos();
+ return true;
+ }
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+s32 GUIEditBoxWithScrollBar::getCursorPos(s32 x, s32 y)
+{
+ IGUIFont* font = getActiveFont();
+
+ const u32 line_count = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1;
+
+ core::stringw *txt_line = 0;
+ s32 start_pos = 0;
+ x += 3;
+
+ for (u32 i = 0; i < line_count; ++i) {
+ setTextRect(i);
+ if (i == 0 && y < m_current_text_rect.UpperLeftCorner.Y)
+ y = m_current_text_rect.UpperLeftCorner.Y;
+ if (i == line_count - 1 && y > m_current_text_rect.LowerRightCorner.Y)
+ y = m_current_text_rect.LowerRightCorner.Y;
+
+ // is it inside this region?
+ if (y >= m_current_text_rect.UpperLeftCorner.Y && y <= m_current_text_rect.LowerRightCorner.Y) {
+ // we've found the clicked line
+ txt_line = (m_word_wrap || m_multiline) ? &m_broken_text[i] : &Text;
+ start_pos = (m_word_wrap || m_multiline) ? m_broken_text_positions[i] : 0;
+ break;
+ }
+ }
+
+ if (x < m_current_text_rect.UpperLeftCorner.X)
+ x = m_current_text_rect.UpperLeftCorner.X;
+
+ if (!txt_line)
+ return 0;
+
+ s32 idx = font->getCharacterFromPos(txt_line->c_str(), x - m_current_text_rect.UpperLeftCorner.X);
+
+ // click was on or left of the line
+ if (idx != -1)
+ return idx + start_pos;
+
+ // click was off the right edge of the line, go to end.
+ return txt_line->size() + start_pos;
+}
+
+
+//! Breaks the single text line.
+void GUIEditBoxWithScrollBar::breakText()
+{
+ if ((!m_word_wrap && !m_multiline))
+ return;
+
+ m_broken_text.clear(); // need to reallocate :/
+ m_broken_text_positions.clear();
+
+ IGUIFont* font = getActiveFont();
+ if (!font)
+ return;
+
+ m_last_break_font = font;
+
+ core::stringw line;
+ core::stringw word;
+ core::stringw whitespace;
+ s32 last_line_start = 0;
+ s32 size = Text.size();
+ s32 length = 0;
+ s32 el_width = RelativeRect.getWidth() - 6;
+ wchar_t c;
+
+ for (s32 i = 0; i < size; ++i) {
+ c = Text[i];
+ bool line_break = false;
+
+ if (c == L'\r') { // Mac or Windows breaks
+
+ line_break = true;
+ c = 0;
+ if (Text[i + 1] == L'\n') { // Windows breaks
+ // TODO: I (Michael) think that we shouldn't change the text given by the user for whatever reason.
+ // Instead rework the cursor positioning to be able to handle this (but not in stable release
+ // branch as users might already expect this behavior).
+ Text.erase(i + 1);
+ --size;
+ if (m_cursor_pos > i)
+ --m_cursor_pos;
+ }
+ } else if (c == L'\n') { // Unix breaks
+ line_break = true;
+ c = 0;
+ }
+
+ // don't break if we're not a multi-line edit box
+ if (!m_multiline)
+ line_break = false;
+
+ if (c == L' ' || c == 0 || i == (size - 1)) {
+ // here comes the next whitespace, look if
+ // we can break the last word to the next line
+ // We also break whitespace, otherwise cursor would vanish beside the right border.
+ s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
+ s32 worldlgth = font->getDimension(word.c_str()).Width;
+
+ if (m_word_wrap && length + worldlgth + whitelgth > el_width && line.size() > 0) {
+ // break to next line
+ length = worldlgth;
+ m_broken_text.push_back(line);
+ m_broken_text_positions.push_back(last_line_start);
+ last_line_start = i - (s32)word.size();
+ line = word;
+ } else {
+ // add word to line
+ line += whitespace;
+ line += word;
+ length += whitelgth + worldlgth;
+ }
+
+ word = L"";
+ whitespace = L"";
+
+
+ if (c)
+ whitespace += c;
+
+ // compute line break
+ if (line_break) {
+ line += whitespace;
+ line += word;
+ m_broken_text.push_back(line);
+ m_broken_text_positions.push_back(last_line_start);
+ last_line_start = i + 1;
+ line = L"";
+ word = L"";
+ whitespace = L"";
+ length = 0;
+ }
+ } else {
+ // yippee this is a word..
+ word += c;
+ }
+ }
+
+ line += whitespace;
+ line += word;
+ m_broken_text.push_back(line);
+ m_broken_text_positions.push_back(last_line_start);
+}
+
+// TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom)
+// but HAlign according to line-width (pixels) and not by row.
+// Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling.
+// But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling).
+void GUIEditBoxWithScrollBar::setTextRect(s32 line)
+{
+ if (line < 0)
+ return;
+
+ IGUIFont* font = getActiveFont();
+ if (!font)
+ return;
+
+ core::dimension2du d;
+
+ // get text dimension
+ const u32 line_count = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1;
+ if (m_word_wrap || m_multiline) {
+ d = font->getDimension(m_broken_text[line].c_str());
+ } else {
+ d = font->getDimension(Text.c_str());
+ d.Height = AbsoluteRect.getHeight();
+ }
+ d.Height += font->getKerningHeight();
+
+ // justification
+ switch (m_halign) {
+ case EGUIA_CENTER:
+ // align to h centre
+ m_current_text_rect.UpperLeftCorner.X = (m_frame_rect.getWidth() / 2) - (d.Width / 2);
+ m_current_text_rect.LowerRightCorner.X = (m_frame_rect.getWidth() / 2) + (d.Width / 2);
+ break;
+ case EGUIA_LOWERRIGHT:
+ // align to right edge
+ m_current_text_rect.UpperLeftCorner.X = m_frame_rect.getWidth() - d.Width;
+ m_current_text_rect.LowerRightCorner.X = m_frame_rect.getWidth();
+ break;
+ default:
+ // align to left edge
+ m_current_text_rect.UpperLeftCorner.X = 0;
+ m_current_text_rect.LowerRightCorner.X = d.Width;
+
+ }
+
+ switch (m_valign) {
+ case EGUIA_CENTER:
+ // align to v centre
+ m_current_text_rect.UpperLeftCorner.Y =
+ (m_frame_rect.getHeight() / 2) - (line_count*d.Height) / 2 + d.Height*line;
+ break;
+ case EGUIA_LOWERRIGHT:
+ // align to bottom edge
+ m_current_text_rect.UpperLeftCorner.Y =
+ m_frame_rect.getHeight() - line_count*d.Height + d.Height*line;
+ break;
+ default:
+ // align to top edge
+ m_current_text_rect.UpperLeftCorner.Y = d.Height*line;
+ break;
+ }
+
+ m_current_text_rect.UpperLeftCorner.X -= m_hscroll_pos;
+ m_current_text_rect.LowerRightCorner.X -= m_hscroll_pos;
+ m_current_text_rect.UpperLeftCorner.Y -= m_vscroll_pos;
+ m_current_text_rect.LowerRightCorner.Y = m_current_text_rect.UpperLeftCorner.Y + d.Height;
+
+ m_current_text_rect += m_frame_rect.UpperLeftCorner;
+}
+
+
+s32 GUIEditBoxWithScrollBar::getLineFromPos(s32 pos)
+{
+ if (!m_word_wrap && !m_multiline)
+ return 0;
+
+ s32 i = 0;
+ while (i < (s32)m_broken_text_positions.size()) {
+ if (m_broken_text_positions[i] > pos)
+ return i - 1;
+ ++i;
+ }
+ return (s32)m_broken_text_positions.size() - 1;
+}
+
+
+void GUIEditBoxWithScrollBar::inputChar(wchar_t c)
+{
+ if (!isEnabled())
+ return;
+
+ if (c != 0) {
+ if (Text.size() < m_max || m_max == 0) {
+ core::stringw s;
+
+ if (m_mark_begin != m_mark_end) {
+ // replace marked text
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ s = Text.subString(0, realmbgn);
+ s.append(c);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+ Text = s;
+ m_cursor_pos = realmbgn + 1;
+ } else {
+ // add new character
+ s = Text.subString(0, m_cursor_pos);
+ s.append(c);
+ s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
+ Text = s;
+ ++m_cursor_pos;
+ }
+
+ m_blink_start_time = porting::getTimeMs();
+ setTextMarkers(0, 0);
+ }
+ }
+ breakText();
+ calculateScrollPos();
+ sendGuiEvent(EGET_EDITBOX_CHANGED);
+}
+
+// calculate autoscroll
+void GUIEditBoxWithScrollBar::calculateScrollPos()
+{
+ if (!m_autoscroll)
+ return;
+
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+ IGUIFont* font = m_override_font ? m_override_font : skin->getFont();
+ if (!font)
+ return;
+
+ s32 curs_line = getLineFromPos(m_cursor_pos);
+ if (curs_line < 0)
+ return;
+ setTextRect(curs_line);
+ const bool has_broken_text = m_multiline || m_word_wrap;
+
+ // Check horizonal scrolling
+ // NOTE: Calculations different to vertical scrolling because setTextRect interprets VAlign relative to line but HAlign not relative to row
+ {
+ // get cursor position
+ IGUIFont* font = getActiveFont();
+ if (!font)
+ return;
+
+ // get cursor area
+ irr::u32 cursor_width = font->getDimension(L"_").Width;
+ core::stringw *txt_line = has_broken_text ? &m_broken_text[curs_line] : &Text;
+ s32 cpos = has_broken_text ? m_cursor_pos - m_broken_text_positions[curs_line] : m_cursor_pos; // column
+ s32 cstart = font->getDimension(txt_line->subString(0, cpos).c_str()).Width; // pixels from text-start
+ s32 cend = cstart + cursor_width;
+ s32 txt_width = font->getDimension(txt_line->c_str()).Width;
+
+ if (txt_width < m_frame_rect.getWidth()) {
+ // TODO: Needs a clean left and right gap removal depending on HAlign, similar to vertical scrolling tests for top/bottom.
+ // This check just fixes the case where it was most noticable (text smaller than clipping area).
+
+ m_hscroll_pos = 0;
+ setTextRect(curs_line);
+ }
+
+ if (m_current_text_rect.UpperLeftCorner.X + cstart < m_frame_rect.UpperLeftCorner.X) {
+ // cursor to the left of the clipping area
+ m_hscroll_pos -= m_frame_rect.UpperLeftCorner.X - (m_current_text_rect.UpperLeftCorner.X + cstart);
+ setTextRect(curs_line);
+
+ // TODO: should show more characters to the left when we're scrolling left
+ // and the cursor reaches the border.
+ } else if (m_current_text_rect.UpperLeftCorner.X + cend > m_frame_rect.LowerRightCorner.X) {
+ // cursor to the right of the clipping area
+ m_hscroll_pos += (m_current_text_rect.UpperLeftCorner.X + cend) - m_frame_rect.LowerRightCorner.X;
+ setTextRect(curs_line);
+ }
+ }
+
+ // calculate vertical scrolling
+ if (has_broken_text) {
+ irr::u32 line_height = font->getDimension(L"A").Height + font->getKerningHeight();
+ // only up to 1 line fits?
+ if (line_height >= (irr::u32)m_frame_rect.getHeight()) {
+ m_vscroll_pos = 0;
+ setTextRect(curs_line);
+ s32 unscrolledPos = m_current_text_rect.UpperLeftCorner.Y;
+ s32 pivot = m_frame_rect.UpperLeftCorner.Y;
+ switch (m_valign) {
+ case EGUIA_CENTER:
+ pivot += m_frame_rect.getHeight() / 2;
+ unscrolledPos += line_height / 2;
+ break;
+ case EGUIA_LOWERRIGHT:
+ pivot += m_frame_rect.getHeight();
+ unscrolledPos += line_height;
+ break;
+ default:
+ break;
+ }
+ m_vscroll_pos = unscrolledPos - pivot;
+ setTextRect(curs_line);
+ } else {
+ // First 2 checks are necessary when people delete lines
+ setTextRect(0);
+ if (m_current_text_rect.UpperLeftCorner.Y > m_frame_rect.UpperLeftCorner.Y && m_valign != EGUIA_LOWERRIGHT) {
+ // first line is leaving a gap on top
+ m_vscroll_pos = 0;
+ } else if (m_valign != EGUIA_UPPERLEFT) {
+ u32 lastLine = m_broken_text_positions.empty() ? 0 : m_broken_text_positions.size() - 1;
+ setTextRect(lastLine);
+ if (m_current_text_rect.LowerRightCorner.Y < m_frame_rect.LowerRightCorner.Y)
+ {
+ // last line is leaving a gap on bottom
+ m_vscroll_pos -= m_frame_rect.LowerRightCorner.Y - m_current_text_rect.LowerRightCorner.Y;
+ }
+ }
+
+ setTextRect(curs_line);
+ if (m_current_text_rect.UpperLeftCorner.Y < m_frame_rect.UpperLeftCorner.Y) {
+ // text above valid area
+ m_vscroll_pos -= m_frame_rect.UpperLeftCorner.Y - m_current_text_rect.UpperLeftCorner.Y;
+ setTextRect(curs_line);
+ } else if (m_current_text_rect.LowerRightCorner.Y > m_frame_rect.LowerRightCorner.Y){
+ // text below valid area
+ m_vscroll_pos += m_current_text_rect.LowerRightCorner.Y - m_frame_rect.LowerRightCorner.Y;
+ setTextRect(curs_line);
+ }
+ }
+ }
+
+ if (m_vscrollbar) {
+ m_vscrollbar->setPos(m_vscroll_pos);
+ }
+}
+
+void GUIEditBoxWithScrollBar::calculateFrameRect()
+{
+ m_frame_rect = AbsoluteRect;
+
+
+ IGUISkin *skin = 0;
+ if (Environment)
+ skin = Environment->getSkin();
+ if (m_border && skin) {
+ m_frame_rect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X) + 1;
+ m_frame_rect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1;
+ m_frame_rect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X) + 1;
+ m_frame_rect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1;
+ }
+
+ updateVScrollBar();
+}
+
+//! set text markers
+void GUIEditBoxWithScrollBar::setTextMarkers(s32 begin, s32 end)
+{
+ if (begin != m_mark_begin || end != m_mark_end) {
+ m_mark_begin = begin;
+ m_mark_end = end;
+ sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
+ }
+}
+
+//! send some gui event to parent
+void GUIEditBoxWithScrollBar::sendGuiEvent(EGUI_EVENT_TYPE type)
+{
+ if (Parent) {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = 0;
+ e.GUIEvent.EventType = type;
+
+ Parent->OnEvent(e);
+ }
+}
+
+//! create a vertical scroll bar
+void GUIEditBoxWithScrollBar::createVScrollBar()
+{
+ IGUISkin *skin = 0;
+ if (Environment)
+ skin = Environment->getSkin();
+
+ m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16;
+
+ irr::core::rect<s32> scrollbarrect = m_frame_rect;
+ scrollbarrect.UpperLeftCorner.X += m_frame_rect.getWidth() - m_scrollbar_width;
+ m_vscrollbar = Environment->addScrollBar(false, scrollbarrect, getParent(), getID());
+ m_vscrollbar->setVisible(false);
+ m_vscrollbar->setSmallStep(1);
+ m_vscrollbar->setLargeStep(1);
+}
+
+void GUIEditBoxWithScrollBar::updateVScrollBar()
+{
+ if (!m_vscrollbar) {
+ return;
+ }
+
+ // OnScrollBarChanged(...)
+ if (m_vscrollbar->getPos() != m_vscroll_pos) {
+ s32 deltaScrollY = m_vscrollbar->getPos() - m_vscroll_pos;
+ m_current_text_rect.UpperLeftCorner.Y -= deltaScrollY;
+ m_current_text_rect.LowerRightCorner.Y -= deltaScrollY;
+
+ s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
+ if (scrollymax != m_vscrollbar->getMax()) {
+ // manage a newline or a deleted line
+ m_vscrollbar->setMax(scrollymax);
+ calculateScrollPos();
+ } else {
+ // manage a newline or a deleted line
+ m_vscroll_pos = m_vscrollbar->getPos();
+ }
+ }
+
+ // check if a vertical scrollbar is needed ?
+ if (getTextDimension().Height > (u32) m_frame_rect.getHeight()) {
+ m_frame_rect.LowerRightCorner.X -= m_scrollbar_width;
+
+ s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
+ if (scrollymax != m_vscrollbar->getMax()) {
+ m_vscrollbar->setMax(scrollymax);
+ }
+
+ if (!m_vscrollbar->isVisible()) {
+ m_vscrollbar->setVisible(true);
+ }
+ } else {
+ if (m_vscrollbar->isVisible())
+ {
+ m_vscrollbar->setVisible(false);
+ m_vscroll_pos = 0;
+ m_vscrollbar->setPos(0);
+ m_vscrollbar->setMax(1);
+ }
+ }
+
+
+}
+
+//! set true if this editbox is writable
+void GUIEditBoxWithScrollBar::setWritable(bool writable)
+{
+ m_writable = writable;
+}
+
+//! Change the background color
+void GUIEditBoxWithScrollBar::setBackgroundColor(const video::SColor &bg_color)
+{
+ m_bg_color = bg_color;
+ m_bg_color_used = true;
+}
+
+//! Writes attributes of the element.
+void GUIEditBoxWithScrollBar::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options = 0) const
+{
+ // IGUIEditBox::serializeAttributes(out,options);
+
+ out->addBool("Border", m_border);
+ out->addBool("Background", m_background);
+ out->addBool("OverrideColorEnabled", m_override_color_enabled);
+ out->addColor("OverrideColor", m_override_color);
+ // out->addFont("OverrideFont", OverrideFont);
+ out->addInt("MaxChars", m_max);
+ out->addBool("WordWrap", m_word_wrap);
+ out->addBool("MultiLine", m_multiline);
+ out->addBool("AutoScroll", m_autoscroll);
+ out->addBool("PasswordBox", m_passwordbox);
+ core::stringw ch = L" ";
+ ch[0] = m_passwordchar;
+ out->addString("PasswordChar", ch.c_str());
+ out->addEnum("HTextAlign", m_halign, GUIAlignmentNames);
+ out->addEnum("VTextAlign", m_valign, GUIAlignmentNames);
+ out->addBool("Writable", m_writable);
+
+ IGUIEditBox::serializeAttributes(out, options);
+}
+
+
+//! Reads attributes of the element
+void GUIEditBoxWithScrollBar::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options = 0)
+{
+ IGUIEditBox::deserializeAttributes(in, options);
+
+ setDrawBorder(in->getAttributeAsBool("Border"));
+ setDrawBackground(in->getAttributeAsBool("Background"));
+ setOverrideColor(in->getAttributeAsColor("OverrideColor"));
+ enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
+ setMax(in->getAttributeAsInt("MaxChars"));
+ setWordWrap(in->getAttributeAsBool("WordWrap"));
+ setMultiLine(in->getAttributeAsBool("MultiLine"));
+ setAutoScroll(in->getAttributeAsBool("AutoScroll"));
+ core::stringw ch = in->getAttributeAsStringW("PasswordChar");
+
+ if (!ch.size())
+ setPasswordBox(in->getAttributeAsBool("PasswordBox"));
+ else
+ setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]);
+
+ setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
+ (EGUI_ALIGNMENT)in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
+
+ // setOverrideFont(in->getAttributeAsFont("OverrideFont"));
+ setWritable(in->getAttributeAsBool("Writable"));
+}
--- /dev/null
+// Copyright (C) 2002-2012 Nikolaus Gebhardt, Modified by Mustapha Tachouct
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#ifndef GUIEDITBOXWITHSCROLLBAR_HEADER
+#define GUIEDITBOXWITHSCROLLBAR_HEADER
+
+#include "IGUIEditBox.h"
+#include "IOSOperator.h"
+#include "IGUIScrollBar.h"
+#include <vector>
+
+using namespace irr;
+using namespace irr::gui;
+
+class GUIEditBoxWithScrollBar : public IGUIEditBox
+{
+public:
+
+ //! constructor
+ GUIEditBoxWithScrollBar(const wchar_t* text, bool border, IGUIEnvironment* environment,
+ IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
+ bool writable = true, bool has_vscrollbar = true);
+
+ //! destructor
+ virtual ~GUIEditBoxWithScrollBar();
+
+ //! Sets another skin independent font.
+ virtual void setOverrideFont(IGUIFont* font = 0);
+
+ //! Gets the override font (if any)
+ /** \return The override font (may be 0) */
+ virtual IGUIFont* getOverrideFont() const;
+
+ //! Get the font which is used right now for drawing
+ /** Currently this is the override font when one is set and the
+ font of the active skin otherwise */
+ virtual IGUIFont* getActiveFont() const;
+
+ //! Sets another color for the text.
+ virtual void setOverrideColor(video::SColor color);
+
+ //! Gets the override color
+ virtual video::SColor getOverrideColor() const;
+
+ //! Sets if the text should use the overide color or the
+ //! color in the gui skin.
+ virtual void enableOverrideColor(bool enable);
+
+ //! Checks if an override color is enabled
+ /** \return true if the override color is enabled, false otherwise */
+ virtual bool isOverrideColorEnabled(void) const;
+
+ //! Sets whether to draw the background
+ virtual void setDrawBackground(bool draw);
+
+ //! Turns the border on or off
+ virtual void setDrawBorder(bool border);
+
+ //! Enables or disables word wrap for using the edit box as multiline text editor.
+ virtual void setWordWrap(bool enable);
+
+ //! Checks if word wrap is enabled
+ //! \return true if word wrap is enabled, false otherwise
+ virtual bool isWordWrapEnabled() const;
+
+ //! Enables or disables newlines.
+ /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired,
+ instead a newline character will be inserted. */
+ virtual void setMultiLine(bool enable);
+
+ //! Checks if multi line editing is enabled
+ //! \return true if mult-line is enabled, false otherwise
+ virtual bool isMultiLineEnabled() const;
+
+ //! Enables or disables automatic scrolling with cursor position
+ //! \param enable: If set to true, the text will move around with the cursor position
+ virtual void setAutoScroll(bool enable);
+
+ //! Checks to see if automatic scrolling is enabled
+ //! \return true if automatic scrolling is enabled, false if not
+ virtual bool isAutoScrollEnabled() const;
+
+ //! Gets the size area of the text in the edit box
+ //! \return Returns the size in pixels of the text
+ virtual core::dimension2du getTextDimension();
+
+ //! Sets text justification
+ virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical);
+
+ //! called if an event happened.
+ virtual bool OnEvent(const SEvent& event);
+
+ //! draws the element and its children
+ virtual void draw();
+
+ //! Sets the new caption of this element.
+ virtual void setText(const wchar_t* text);
+
+ //! Sets the maximum amount of characters which may be entered in the box.
+ //! \param max: Maximum amount of characters. If 0, the character amount is
+ //! infinity.
+ virtual void setMax(u32 max);
+
+ //! Returns maximum amount of characters, previously set by setMax();
+ virtual u32 getMax() const;
+
+ //! Sets whether the edit box is a password box. Setting this to true will
+ /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x
+ \param passwordBox: true to enable password, false to disable
+ \param passwordChar: the character that is displayed instead of letters */
+ virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*');
+
+ //! Returns true if the edit box is currently a password box.
+ virtual bool isPasswordBox() const;
+
+ //! Updates the absolute position, splits text if required
+ virtual void updateAbsolutePosition();
+
+ virtual void setWritable(bool writable);
+
+ //! Change the background color
+ virtual void setBackgroundColor(const video::SColor &bg_color);
+
+ //! Writes attributes of the element.
+ virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;
+
+ //! Reads attributes of the element
+ virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
+
+protected:
+ //! Breaks the single text line.
+ void breakText();
+ //! sets the area of the given line
+ void setTextRect(s32 line);
+ //! returns the line number that the cursor is on
+ s32 getLineFromPos(s32 pos);
+ //! adds a letter to the edit box
+ void inputChar(wchar_t c);
+ //! calculates the current scroll position
+ void calculateScrollPos();
+ //! calculated the FrameRect
+ void calculateFrameRect();
+ //! send some gui event to parent
+ void sendGuiEvent(EGUI_EVENT_TYPE type);
+ //! set text markers
+ void setTextMarkers(s32 begin, s32 end);
+ //! create a Vertical ScrollBar
+ void createVScrollBar();
+ //! update the vertical scrollBar (visibilty & position)
+ void updateVScrollBar();
+
+ bool processKey(const SEvent& event);
+ bool processMouse(const SEvent& event);
+ s32 getCursorPos(s32 x, s32 y);
+
+ bool m_mouse_marking;
+ bool m_border;
+ bool m_background;
+ bool m_override_color_enabled;
+ s32 m_mark_begin;
+ s32 m_mark_end;
+
+ video::SColor m_override_color;
+ gui::IGUIFont *m_override_font, *m_last_break_font;
+ IOSOperator* m_operator;
+
+ u32 m_blink_start_time;
+ s32 m_cursor_pos;
+ s32 m_hscroll_pos, m_vscroll_pos; // scroll position in characters
+ u32 m_max;
+
+ bool m_word_wrap, m_multiline, m_autoscroll, m_passwordbox;
+ wchar_t m_passwordchar;
+ EGUI_ALIGNMENT m_halign, m_valign;
+
+ std::vector<core::stringw> m_broken_text;
+ std::vector<s32> m_broken_text_positions;
+
+ core::rect<s32> m_current_text_rect, m_frame_rect; // temporary values
+
+ u32 m_scrollbar_width;
+ IGUIScrollBar *m_vscrollbar;
+ bool m_writable;
+
+ bool m_bg_color_used;
+ video::SColor m_bg_color;
+};
+
+
+#endif // GUIEDITBOXWITHSCROLLBAR_HEADER
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 sapier
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiEngine.h"
+
+#include <IGUIStaticText.h>
+#include <ICameraSceneNode.h>
+#include "client/renderingengine.h"
+#include "scripting_mainmenu.h"
+#include "util/numeric.h"
+#include "config.h"
+#include "version.h"
+#include "porting.h"
+#include "filesys.h"
+#include "settings.h"
+#include "guiMainMenu.h"
+#include "sound.h"
+#include "sound_openal.h"
+#include "clouds.h"
+#include "httpfetch.h"
+#include "log.h"
+#include "fontengine.h"
+#include "guiscalingfilter.h"
+#include "irrlicht_changes/static_text.h"
+
+#ifdef __ANDROID__
+#include "client/tile.h"
+#include <GLES/gl.h>
+#endif
+
+
+/******************************************************************************/
+void TextDestGuiEngine::gotText(const StringMap &fields)
+{
+ m_engine->getScriptIface()->handleMainMenuButtons(fields);
+}
+
+/******************************************************************************/
+void TextDestGuiEngine::gotText(const std::wstring &text)
+{
+ m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text));
+}
+
+/******************************************************************************/
+MenuTextureSource::~MenuTextureSource()
+{
+ for (const std::string &texture_to_delete : m_to_delete) {
+ const char *tname = texture_to_delete.c_str();
+ video::ITexture *texture = m_driver->getTexture(tname);
+ m_driver->removeTexture(texture);
+ }
+}
+
+/******************************************************************************/
+video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
+{
+ if(id)
+ *id = 0;
+ if(name.empty())
+ return NULL;
+ m_to_delete.insert(name);
+
+#ifdef __ANDROID__
+ video::IImage *image = m_driver->createImageFromFile(name.c_str());
+ if (image) {
+ image = Align2Npot2(image, m_driver);
+ video::ITexture* retval = m_driver->addTexture(name.c_str(), image);
+ image->drop();
+ return retval;
+ }
+#endif
+ return m_driver->getTexture(name.c_str());
+}
+
+/******************************************************************************/
+/** MenuMusicFetcher */
+/******************************************************************************/
+void MenuMusicFetcher::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");
+ int i;
+ for(i=0; i<10; i++)
+ dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg");
+ base = porting::path_user + DIR_DELIM + "sounds";
+ dst_paths.insert(base + DIR_DELIM + name + ".ogg");
+ for(i=0; i<10; i++)
+ dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg");
+}
+
+/******************************************************************************/
+/** GUIEngine */
+/******************************************************************************/
+GUIEngine::GUIEngine(JoystickController *joystick,
+ gui::IGUIElement *parent,
+ IMenuManager *menumgr,
+ MainMenuData *data,
+ bool &kill) :
+ m_parent(parent),
+ m_menumanager(menumgr),
+ m_smgr(RenderingEngine::get_scene_manager()),
+ m_data(data),
+ m_kill(kill)
+{
+ //initialize texture pointers
+ for (image_definition &texture : m_textures) {
+ texture.texture = NULL;
+ }
+ // is deleted by guiformspec!
+ m_buttonhandler = new TextDestGuiEngine(this);
+
+ //create texture source
+ m_texture_source = new MenuTextureSource(RenderingEngine::get_video_driver());
+
+ //create soundmanager
+ MenuMusicFetcher soundfetcher;
+#if USE_SOUND
+ m_sound_manager = createOpenALSoundManager(&soundfetcher);
+#endif
+ if(!m_sound_manager)
+ m_sound_manager = &dummySoundManager;
+
+ //create topleft header
+ m_toplefttext = L"";
+
+ core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
+ g_fontengine->getTextHeight());
+ rect += v2s32(4, 0);
+
+ m_irr_toplefttext =
+ addStaticText(RenderingEngine::get_gui_env(), m_toplefttext,
+ rect, false, true, 0, -1);
+
+ //create formspecsource
+ m_formspecgui = new FormspecFormSource("");
+
+ /* Create menu */
+ m_menu = new GUIFormSpecMenu(joystick,
+ m_parent,
+ -1,
+ m_menumanager,
+ NULL /* &client */,
+ m_texture_source,
+ m_formspecgui,
+ m_buttonhandler,
+ false);
+
+ m_menu->allowClose(false);
+ m_menu->lockSize(true,v2u32(800,600));
+
+ // Initialize scripting
+
+ infostream << "GUIEngine: Initializing Lua" << std::endl;
+
+ m_script = new MainMenuScripting(this);
+
+ try {
+ m_script->setMainMenuData(&m_data->script_data);
+ m_data->script_data.errormessage = "";
+
+ if (!loadMainMenuScript()) {
+ errorstream << "No future without main menu!" << std::endl;
+ abort();
+ }
+
+ run();
+ } catch (LuaError &e) {
+ errorstream << "Main menu error: " << e.what() << std::endl;
+ m_data->script_data.errormessage = e.what();
+ }
+
+ m_menu->quitMenu();
+ m_menu->drop();
+ m_menu = NULL;
+}
+
+/******************************************************************************/
+bool GUIEngine::loadMainMenuScript()
+{
+ // Set main menu path (for core.get_mainmenu_path())
+ m_scriptdir = g_settings->get("main_menu_path");
+ if (m_scriptdir.empty()) {
+ m_scriptdir = porting::path_share + DIR_DELIM + "builtin" + DIR_DELIM + "mainmenu";
+ }
+
+ // Load builtin (which will load the main menu script)
+ std::string script = porting::path_share + DIR_DELIM "builtin" + DIR_DELIM "init.lua";
+ try {
+ m_script->loadScript(script);
+ // Menu script loaded
+ return true;
+ } catch (const ModError &e) {
+ errorstream << "GUIEngine: execution of menu script failed: "
+ << e.what() << std::endl;
+ }
+
+ return false;
+}
+
+/******************************************************************************/
+void GUIEngine::run()
+{
+ // Always create clouds because they may or may not be
+ // needed based on the game selected
+ video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+
+ cloudInit();
+
+ unsigned int text_height = g_fontengine->getTextHeight();
+
+ irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
+ g_settings->getU16("screen_h"));
+
+ while (RenderingEngine::run() && (!m_startgame) && (!m_kill)) {
+
+ const irr::core::dimension2d<u32> ¤t_screen_size =
+ RenderingEngine::get_video_driver()->getScreenSize();
+ // Verify if window size has changed and save it if it's the case
+ // Ensure evaluating settings->getBool after verifying screensize
+ // First condition is cheaper
+ if (previous_screen_size != current_screen_size &&
+ current_screen_size != irr::core::dimension2d<u32>(0,0) &&
+ g_settings->getBool("autosave_screensize")) {
+ g_settings->setU16("screen_w", current_screen_size.Width);
+ g_settings->setU16("screen_h", current_screen_size.Height);
+ previous_screen_size = current_screen_size;
+ }
+
+ //check if we need to update the "upper left corner"-text
+ if (text_height != g_fontengine->getTextHeight()) {
+ updateTopLeftTextSize();
+ text_height = g_fontengine->getTextHeight();
+ }
+
+ driver->beginScene(true, true, video::SColor(255,140,186,250));
+
+ if (m_clouds_enabled)
+ {
+ cloudPreProcess();
+ drawOverlay(driver);
+ }
+ else
+ drawBackground(driver);
+
+ drawHeader(driver);
+ drawFooter(driver);
+
+ RenderingEngine::get_gui_env()->drawAll();
+
+ driver->endScene();
+
+ if (m_clouds_enabled)
+ cloudPostProcess();
+ else
+ sleep_ms(25);
+
+ m_script->step();
+
+#ifdef __ANDROID__
+ m_menu->getAndroidUIInput();
+#endif
+ }
+}
+
+/******************************************************************************/
+GUIEngine::~GUIEngine()
+{
+ if (m_sound_manager != &dummySoundManager){
+ delete m_sound_manager;
+ m_sound_manager = NULL;
+ }
+
+ infostream<<"GUIEngine: Deinitializing scripting"<<std::endl;
+ delete m_script;
+
+ m_irr_toplefttext->setText(L"");
+
+ //clean up texture pointers
+ for (image_definition &texture : m_textures) {
+ if (texture.texture)
+ RenderingEngine::get_video_driver()->removeTexture(texture.texture);
+ }
+
+ delete m_texture_source;
+
+ if (m_cloud.clouds)
+ m_cloud.clouds->drop();
+}
+
+/******************************************************************************/
+void GUIEngine::cloudInit()
+{
+ m_cloud.clouds = new Clouds(m_smgr, -1, rand());
+ m_cloud.clouds->setHeight(100.0f);
+ m_cloud.clouds->update(v3f(0, 0, 0), video::SColor(255,200,200,255));
+
+ m_cloud.camera = m_smgr->addCameraSceneNode(0,
+ v3f(0,0,0), v3f(0, 60, 100));
+ m_cloud.camera->setFarValue(10000);
+
+ m_cloud.lasttime = RenderingEngine::get_timer_time();
+}
+
+/******************************************************************************/
+void GUIEngine::cloudPreProcess()
+{
+ u32 time = RenderingEngine::get_timer_time();
+
+ if(time > m_cloud.lasttime)
+ m_cloud.dtime = (time - m_cloud.lasttime) / 1000.0;
+ else
+ m_cloud.dtime = 0;
+
+ m_cloud.lasttime = time;
+
+ m_cloud.clouds->step(m_cloud.dtime*3);
+ m_cloud.clouds->render();
+ m_smgr->drawAll();
+}
+
+/******************************************************************************/
+void GUIEngine::cloudPostProcess()
+{
+ float fps_max = g_settings->getFloat("pause_fps_max");
+ // Time of frame without fps limit
+ u32 busytime_u32;
+
+ // not using getRealTime is necessary for wine
+ u32 time = RenderingEngine::get_timer_time();
+ if(time > m_cloud.lasttime)
+ busytime_u32 = time - m_cloud.lasttime;
+ else
+ busytime_u32 = 0;
+
+ // FPS limiter
+ u32 frametime_min = 1000./fps_max;
+
+ if (busytime_u32 < frametime_min) {
+ u32 sleeptime = frametime_min - busytime_u32;
+ RenderingEngine::get_raw_device()->sleep(sleeptime);
+ }
+}
+
+/******************************************************************************/
+void GUIEngine::drawBackground(video::IVideoDriver *driver)
+{
+ v2u32 screensize = driver->getScreenSize();
+
+ video::ITexture* texture = m_textures[TEX_LAYER_BACKGROUND].texture;
+
+ /* 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;
+ }
+
+ v2u32 sourcesize = texture->getOriginalSize();
+
+ if (m_textures[TEX_LAYER_BACKGROUND].tile)
+ {
+ v2u32 tilesize(
+ MYMAX(sourcesize.X,m_textures[TEX_LAYER_BACKGROUND].minsize),
+ MYMAX(sourcesize.Y,m_textures[TEX_LAYER_BACKGROUND].minsize));
+ for (unsigned int x = 0; x < screensize.X; x += tilesize.X )
+ {
+ for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y )
+ {
+ draw2DImageFilterScaled(driver, texture,
+ core::rect<s32>(x, y, x+tilesize.X, y+tilesize.Y),
+ core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+ NULL, NULL, true);
+ }
+ }
+ return;
+ }
+
+ /* Draw background texture */
+ draw2DImageFilterScaled(driver, texture,
+ core::rect<s32>(0, 0, screensize.X, screensize.Y),
+ core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+ NULL, NULL, true);
+}
+
+/******************************************************************************/
+void GUIEngine::drawOverlay(video::IVideoDriver *driver)
+{
+ v2u32 screensize = driver->getScreenSize();
+
+ video::ITexture* texture = m_textures[TEX_LAYER_OVERLAY].texture;
+
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
+
+ /* Draw background texture */
+ v2u32 sourcesize = texture->getOriginalSize();
+ draw2DImageFilterScaled(driver, texture,
+ core::rect<s32>(0, 0, screensize.X, screensize.Y),
+ core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+ NULL, NULL, true);
+}
+
+/******************************************************************************/
+void GUIEngine::drawHeader(video::IVideoDriver *driver)
+{
+ core::dimension2d<u32> screensize = driver->getScreenSize();
+
+ video::ITexture* texture = m_textures[TEX_LAYER_HEADER].texture;
+
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
+
+ f32 mult = (((f32)screensize.Width / 2.0)) /
+ ((f32)texture->getOriginalSize().Width);
+
+ v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult,
+ ((f32)texture->getOriginalSize().Height) * mult);
+
+ // Don't draw the header if 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);
+
+ draw2DImageFilterScaled(driver, texture, splashrect,
+ core::rect<s32>(core::position2d<s32>(0,0),
+ core::dimension2di(texture->getOriginalSize())),
+ NULL, NULL, true);
+ }
+}
+
+/******************************************************************************/
+void GUIEngine::drawFooter(video::IVideoDriver *driver)
+{
+ core::dimension2d<u32> screensize = driver->getScreenSize();
+
+ video::ITexture* texture = m_textures[TEX_LAYER_FOOTER].texture;
+
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
+
+ f32 mult = (((f32)screensize.Width)) /
+ ((f32)texture->getOriginalSize().Width);
+
+ v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult,
+ ((f32)texture->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);
+
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0,0),
+ core::dimension2di(texture->getOriginalSize())),
+ NULL, NULL, true);
+ }
+}
+
+/******************************************************************************/
+bool GUIEngine::setTexture(texture_layer layer, std::string texturepath,
+ bool tile_image, unsigned int minsize)
+{
+ video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+
+ if (m_textures[layer].texture) {
+ driver->removeTexture(m_textures[layer].texture);
+ m_textures[layer].texture = NULL;
+ }
+
+ if (texturepath.empty() || !fs::PathExists(texturepath)) {
+ return false;
+ }
+
+ m_textures[layer].texture = driver->getTexture(texturepath.c_str());
+ m_textures[layer].tile = tile_image;
+ m_textures[layer].minsize = minsize;
+
+ if (!m_textures[layer].texture) {
+ return false;
+ }
+
+ return true;
+}
+
+/******************************************************************************/
+bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
+{
+#if USE_CURL
+ std::ofstream target_file(target.c_str(), std::ios::out | std::ios::binary);
+
+ if (!target_file.good()) {
+ return false;
+ }
+
+ HTTPFetchRequest fetch_request;
+ HTTPFetchResult fetch_result;
+ fetch_request.url = url;
+ fetch_request.caller = HTTPFETCH_SYNC;
+ fetch_request.timeout = g_settings->getS32("curl_file_download_timeout");
+ httpfetch_sync(fetch_request, fetch_result);
+
+ if (!fetch_result.succeeded) {
+ return false;
+ }
+ target_file << fetch_result.data;
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+/******************************************************************************/
+void GUIEngine::setTopleftText(const std::string &text)
+{
+ m_toplefttext = translate_string(utf8_to_wide(text));
+
+ updateTopLeftTextSize();
+}
+
+/******************************************************************************/
+void GUIEngine::updateTopLeftTextSize()
+{
+ core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
+ g_fontengine->getTextHeight());
+ rect += v2s32(4, 0);
+
+ m_irr_toplefttext->remove();
+ m_irr_toplefttext =
+ addStaticText(RenderingEngine::get_gui_env(), m_toplefttext,
+ rect, false, true, 0, -1);
+}
+
+/******************************************************************************/
+s32 GUIEngine::playSound(SimpleSoundSpec spec, bool looped)
+{
+ s32 handle = m_sound_manager->playSound(spec, looped);
+ return handle;
+}
+
+/******************************************************************************/
+void GUIEngine::stopSound(s32 handle)
+{
+ m_sound_manager->stopSound(handle);
+}
+
+/******************************************************************************/
+unsigned int GUIEngine::queueAsync(const std::string &serialized_func,
+ const std::string &serialized_params)
+{
+ return m_script->queueAsync(serialized_func, serialized_params);
+}
+
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 sapier
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+/******************************************************************************/
+/* Includes */
+/******************************************************************************/
+#include "irrlichttypes.h"
+#include "modalMenu.h"
+#include "guiFormSpecMenu.h"
+#include "sound.h"
+#include "client/tile.h"
+#include "util/enriched_string.h"
+
+/******************************************************************************/
+/* Typedefs and macros */
+/******************************************************************************/
+/** texture layer ids */
+typedef enum {
+ TEX_LAYER_BACKGROUND = 0,
+ TEX_LAYER_OVERLAY,
+ TEX_LAYER_HEADER,
+ TEX_LAYER_FOOTER,
+ TEX_LAYER_MAX
+} texture_layer;
+
+typedef struct {
+ video::ITexture *texture = nullptr;
+ bool tile;
+ unsigned int minsize;
+} image_definition;
+
+/******************************************************************************/
+/* forward declarations */
+/******************************************************************************/
+class GUIEngine;
+class MainMenuScripting;
+class Clouds;
+struct MainMenuData;
+
+/******************************************************************************/
+/* declarations */
+/******************************************************************************/
+
+/** GUIEngine specific implementation of TextDest used within guiFormSpecMenu */
+class TextDestGuiEngine : public TextDest
+{
+public:
+ /**
+ * default constructor
+ * @param engine the engine data is transmitted for further processing
+ */
+ TextDestGuiEngine(GUIEngine* engine) : m_engine(engine) {};
+
+ /**
+ * receive fields transmitted by guiFormSpecMenu
+ * @param fields map containing formspec field elements currently active
+ */
+ void gotText(const StringMap &fields);
+
+ /**
+ * receive text/events transmitted by guiFormSpecMenu
+ * @param text textual representation of event
+ */
+ void gotText(const std::wstring &text);
+
+private:
+ /** target to transmit data to */
+ GUIEngine *m_engine = nullptr;
+};
+
+/** GUIEngine specific implementation of ISimpleTextureSource */
+class MenuTextureSource : public ISimpleTextureSource
+{
+public:
+ /**
+ * default constructor
+ * @param driver the video driver to load textures from
+ */
+ MenuTextureSource(video::IVideoDriver *driver) : m_driver(driver) {};
+
+ /**
+ * destructor, removes all loaded textures
+ */
+ virtual ~MenuTextureSource();
+
+ /**
+ * get a texture, loading it if required
+ * @param name path to the texture
+ * @param id receives the texture ID, always 0 in this implementation
+ */
+ video::ITexture *getTexture(const std::string &name, u32 *id = NULL);
+
+private:
+ /** driver to get textures from */
+ video::IVideoDriver *m_driver = nullptr;
+ /** set of texture names to delete */
+ std::set<std::string> m_to_delete;
+};
+
+/** GUIEngine specific implementation of OnDemandSoundFetcher */
+class MenuMusicFetcher: public OnDemandSoundFetcher
+{
+public:
+ /**
+ * get sound file paths according to sound name
+ * @param name sound name
+ * @param dst_paths receives possible paths to sound files
+ * @param dst_datas receives binary sound data (not used here)
+ */
+ void fetchSounds(const std::string &name,
+ std::set<std::string> &dst_paths,
+ std::set<std::string> &dst_datas);
+
+private:
+ /** set of fetched sound names */
+ std::set<std::string> m_fetched;
+};
+
+/** implementation of main menu based uppon formspecs */
+class GUIEngine {
+ /** grant ModApiMainMenu access to private members */
+ friend class ModApiMainMenu;
+ friend class ModApiSound;
+
+public:
+ /**
+ * default constructor
+ * @param dev device to draw at
+ * @param parent parent gui element
+ * @param menumgr manager to add menus to
+ * @param smgr scene manager to add scene elements to
+ * @param data struct to transfer data to main game handling
+ */
+ GUIEngine(JoystickController *joystick,
+ gui::IGUIElement *parent,
+ IMenuManager *menumgr,
+ MainMenuData *data,
+ bool &kill);
+
+ /** default destructor */
+ virtual ~GUIEngine();
+
+ /**
+ * return MainMenuScripting interface
+ */
+ MainMenuScripting *getScriptIface()
+ {
+ return m_script;
+ }
+
+ /**
+ * return dir of current menuscript
+ */
+ std::string getScriptDir()
+ {
+ return m_scriptdir;
+ }
+
+ /** pass async callback to scriptengine **/
+ unsigned int queueAsync(const std::string &serialized_fct,
+ const std::string &serialized_params);
+
+private:
+
+ /** find and run the main menu script */
+ bool loadMainMenuScript();
+
+ /** run main menu loop */
+ void run();
+
+ /** update size of topleftext element */
+ void updateTopLeftTextSize();
+
+ /** parent gui element */
+ gui::IGUIElement *m_parent = nullptr;
+ /** manager to add menus to */
+ IMenuManager *m_menumanager = nullptr;
+ /** scene manager to add scene elements to */
+ scene::ISceneManager *m_smgr = nullptr;
+ /** pointer to data beeing transfered back to main game handling */
+ MainMenuData *m_data = nullptr;
+ /** pointer to texture source */
+ ISimpleTextureSource *m_texture_source = nullptr;
+ /** pointer to soundmanager*/
+ ISoundManager *m_sound_manager = nullptr;
+
+ /** representation of form source to be used in mainmenu formspec */
+ FormspecFormSource *m_formspecgui = nullptr;
+ /** formspec input receiver */
+ TextDestGuiEngine *m_buttonhandler = nullptr;
+ /** the formspec menu */
+ GUIFormSpecMenu *m_menu = nullptr;
+
+ /** reference to kill variable managed by SIGINT handler */
+ bool &m_kill;
+
+ /** variable used to abort menu and return back to main game handling */
+ bool m_startgame = false;
+
+ /** scripting interface */
+ MainMenuScripting *m_script = nullptr;
+
+ /** script basefolder */
+ std::string m_scriptdir = "";
+
+ /**
+ * draw background layer
+ * @param driver to use for drawing
+ */
+ void drawBackground(video::IVideoDriver *driver);
+ /**
+ * draw overlay layer
+ * @param driver to use for drawing
+ */
+ void drawOverlay(video::IVideoDriver *driver);
+ /**
+ * draw header layer
+ * @param driver to use for drawing
+ */
+ void drawHeader(video::IVideoDriver *driver);
+ /**
+ * draw footer layer
+ * @param driver to use for drawing
+ */
+ void drawFooter(video::IVideoDriver *driver);
+
+ /**
+ * load a texture for a specified layer
+ * @param layer draw layer to specify texture
+ * @param texturepath full path of texture to load
+ */
+ bool setTexture(texture_layer layer, std::string texturepath,
+ bool tile_image, unsigned int minsize);
+
+ /**
+ * download a file using curl
+ * @param url url to download
+ * @param target file to store to
+ */
+ static bool downloadFile(const std::string &url, const std::string &target);
+
+ /** array containing pointers to current specified texture layers */
+ image_definition m_textures[TEX_LAYER_MAX];
+
+ /**
+ * specify text to appear as top left string
+ * @param text to set
+ */
+ void setTopleftText(const std::string &text);
+
+ /** pointer to gui element shown at topleft corner */
+ irr::gui::IGUIStaticText *m_irr_toplefttext = nullptr;
+ /** and text that is in it */
+ EnrichedString m_toplefttext;
+
+ /** initialize cloud subsystem */
+ void cloudInit();
+ /** do preprocessing for cloud subsystem */
+ void cloudPreProcess();
+ /** do postprocessing for cloud subsystem */
+ void cloudPostProcess();
+
+ /** internam data required for drawing clouds */
+ struct clouddata {
+ /** delta time since last cloud processing */
+ f32 dtime;
+ /** absolute time of last cloud processing */
+ u32 lasttime;
+ /** pointer to cloud class */
+ Clouds *clouds = nullptr;
+ /** camera required for drawing clouds */
+ scene::ICameraSceneNode *camera = nullptr;
+ };
+
+ /** is drawing of clouds enabled atm */
+ bool m_clouds_enabled = true;
+ /** data used to draw clouds */
+ clouddata m_cloud;
+
+ /** start playing a sound and return handle */
+ s32 playSound(SimpleSoundSpec spec, bool looped);
+ /** stop playing a sound started with playSound() */
+ void stopSound(s32 handle);
+
+
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include <cstdlib>
+#include <algorithm>
+#include <iterator>
+#include <sstream>
+#include <limits>
+#include "guiFormSpecMenu.h"
+#include "guiTable.h"
+#include "constants.h"
+#include "gamedef.h"
+#include "keycode.h"
+#include "util/strfnd.h"
+#include <IGUICheckBox.h>
+#include <IGUIEditBox.h>
+#include <IGUIButton.h>
+#include <IGUIStaticText.h>
+#include <IGUIFont.h>
+#include <IGUITabControl.h>
+#include <IGUIComboBox.h>
+#include "client/renderingengine.h"
+#include "log.h"
+#include "client/tile.h" // ITextureSource
+#include "hud.h" // drawItemStack
+#include "filesys.h"
+#include "gettime.h"
+#include "gettext.h"
+#include "scripting_server.h"
+#include "porting.h"
+#include "settings.h"
+#include "client.h"
+#include "fontengine.h"
+#include "util/hex.h"
+#include "util/numeric.h"
+#include "util/string.h" // for parseColorString()
+#include "irrlicht_changes/static_text.h"
+#include "guiscalingfilter.h"
+#include "guiEditBoxWithScrollbar.h"
+
+#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
+#include "intlGUIEditBox.h"
+#endif
+
+#define MY_CHECKPOS(a,b) \
+ if (v_pos.size() != 2) { \
+ errorstream<< "Invalid pos for element " << a << "specified: \"" \
+ << parts[b] << "\"" << std::endl; \
+ return; \
+ }
+
+#define MY_CHECKGEOM(a,b) \
+ if (v_geom.size() != 2) { \
+ errorstream<< "Invalid pos for element " << a << "specified: \"" \
+ << parts[b] << "\"" << std::endl; \
+ return; \
+ }
+/*
+ GUIFormSpecMenu
+*/
+static unsigned int font_line_height(gui::IGUIFont *font)
+{
+ return font->getDimension(L"Ay").Height + font->getKerningHeight();
+}
+
+inline u32 clamp_u8(s32 value)
+{
+ return (u32) MYMIN(MYMAX(value, 0), 255);
+}
+
+GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
+ gui::IGUIElement *parent, s32 id, IMenuManager *menumgr,
+ Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst,
+ bool remap_dbl_click) :
+ GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr),
+ m_invmgr(client),
+ m_tsrc(tsrc),
+ m_client(client),
+ m_form_src(fsrc),
+ m_text_dst(tdst),
+ m_joystick(joystick),
+ m_remap_dbl_click(remap_dbl_click)
+#ifdef __ANDROID__
+ , m_JavaDialogFieldName("")
+#endif
+{
+ current_keys_pending.key_down = false;
+ current_keys_pending.key_up = false;
+ current_keys_pending.key_enter = false;
+ current_keys_pending.key_escape = false;
+
+ m_doubleclickdetect[0].time = 0;
+ m_doubleclickdetect[1].time = 0;
+
+ m_doubleclickdetect[0].pos = v2s32(0, 0);
+ m_doubleclickdetect[1].pos = v2s32(0, 0);
+
+ m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
+ m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname");
+}
+
+GUIFormSpecMenu::~GUIFormSpecMenu()
+{
+ removeChildren();
+
+ for (auto &table_it : m_tables) {
+ table_it.second->drop();
+ }
+
+ delete m_selected_item;
+ delete m_form_src;
+ delete m_text_dst;
+}
+
+void GUIFormSpecMenu::removeChildren()
+{
+ const core::list<gui::IGUIElement*> &children = getChildren();
+
+ while(!children.empty()) {
+ (*children.getLast())->remove();
+ }
+
+ if(m_tooltip_element) {
+ m_tooltip_element->remove();
+ m_tooltip_element->drop();
+ m_tooltip_element = NULL;
+ }
+
+}
+
+void GUIFormSpecMenu::setInitialFocus()
+{
+ // Set initial focus according to following order of precedence:
+ // 1. first empty editbox
+ // 2. first editbox
+ // 3. first table
+ // 4. last button
+ // 5. first focusable (not statictext, not tabheader)
+ // 6. first child element
+
+ core::list<gui::IGUIElement*> children = getChildren();
+
+ // in case "children" contains any NULL elements, remove them
+ for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
+ it != children.end();) {
+ if (*it)
+ ++it;
+ else
+ it = children.erase(it);
+ }
+
+ // 1. first empty editbox
+ for (gui::IGUIElement *it : children) {
+ if (it->getType() == gui::EGUIET_EDIT_BOX
+ && it->getText()[0] == 0) {
+ Environment->setFocus(it);
+ return;
+ }
+ }
+
+ // 2. first editbox
+ for (gui::IGUIElement *it : children) {
+ if (it->getType() == gui::EGUIET_EDIT_BOX) {
+ Environment->setFocus(it);
+ return;
+ }
+ }
+
+ // 3. first table
+ for (gui::IGUIElement *it : children) {
+ if (it->getTypeName() == std::string("GUITable")) {
+ Environment->setFocus(it);
+ return;
+ }
+ }
+
+ // 4. last button
+ for (core::list<gui::IGUIElement*>::Iterator it = children.getLast();
+ it != children.end(); --it) {
+ if ((*it)->getType() == gui::EGUIET_BUTTON) {
+ Environment->setFocus(*it);
+ return;
+ }
+ }
+
+ // 5. first focusable (not statictext, not tabheader)
+ for (gui::IGUIElement *it : children) {
+ if (it->getType() != gui::EGUIET_STATIC_TEXT &&
+ it->getType() != gui::EGUIET_TAB_CONTROL) {
+ Environment->setFocus(it);
+ return;
+ }
+ }
+
+ // 6. first child element
+ if (children.empty())
+ Environment->setFocus(this);
+ else
+ Environment->setFocus(*(children.begin()));
+}
+
+GUITable* GUIFormSpecMenu::getTable(const std::string &tablename)
+{
+ for (auto &table : m_tables) {
+ if (tablename == table.first.fname)
+ return table.second;
+ }
+ return 0;
+}
+
+std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string &name)
+{
+ for (auto &dropdown : m_dropdowns) {
+ if (name == dropdown.first.fname)
+ return &dropdown.second;
+ }
+ return NULL;
+}
+
+void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,',');
+
+ if (((parts.size() == 2) || parts.size() == 3) ||
+ ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ if (parts[1].find(';') != std::string::npos)
+ parts[1] = parts[1].substr(0,parts[1].find(';'));
+
+ data->invsize.X = MYMAX(0, stof(parts[0]));
+ data->invsize.Y = MYMAX(0, stof(parts[1]));
+
+ lockSize(false);
+ if (parts.size() == 3) {
+ if (parts[2] == "true") {
+ lockSize(true,v2u32(800,600));
+ }
+ }
+
+ data->explicit_size = true;
+ return;
+ }
+ errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ',');
+
+ if (parts.size() >= 2) {
+ if (parts[1].find(';') != std::string::npos)
+ parts[1] = parts[1].substr(0, parts[1].find(';'));
+
+ container_stack.push(pos_offset);
+ pos_offset.X += MYMAX(0, stof(parts[0]));
+ pos_offset.Y += MYMAX(0, stof(parts[1]));
+ return;
+ }
+ errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseContainerEnd(parserData* data)
+{
+ if (container_stack.empty()) {
+ errorstream<< "Invalid container end element, no matching container start element" << std::endl;
+ } else {
+ pos_offset = container_stack.top();
+ container_stack.pop();
+ }
+}
+
+void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
+{
+ if (m_client == 0) {
+ warningstream<<"invalid use of 'list' with m_client==0"<<std::endl;
+ return;
+ }
+
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 4) || (parts.size() == 5)) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::string location = parts[0];
+ std::string listname = parts[1];
+ std::vector<std::string> v_pos = split(parts[2],',');
+ std::vector<std::string> v_geom = split(parts[3],',');
+ std::string startindex;
+ if (parts.size() == 5)
+ startindex = parts[4];
+
+ MY_CHECKPOS("list",2);
+ MY_CHECKGEOM("list",3);
+
+ InventoryLocation loc;
+
+ if(location == "context" || location == "current_name")
+ loc = m_current_inventory_location;
+ else
+ loc.deSerialize(location);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = stoi(v_geom[0]);
+ geom.Y = stoi(v_geom[1]);
+
+ s32 start_i = 0;
+ if (!startindex.empty())
+ start_i = stoi(startindex);
+
+ if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
+ errorstream<< "Invalid list element: '" << element << "'" << std::endl;
+ return;
+ }
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of list without a size[] element"<<std::endl;
+ m_inventorylists.emplace_back(loc, listname, pos, geom, start_i);
+ return;
+ }
+ errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element)
+{
+ if (m_client == 0) {
+ errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl;
+ return;
+ }
+
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() == 2) {
+ std::string location = parts[0];
+ std::string listname = parts[1];
+
+ InventoryLocation loc;
+
+ if (location == "context" || location == "current_name")
+ loc = m_current_inventory_location;
+ else
+ loc.deSerialize(location);
+
+ m_inventory_rings.emplace_back(loc, listname);
+ return;
+ }
+
+ if (element.empty() && m_inventorylists.size() > 1) {
+ size_t siz = m_inventorylists.size();
+ // insert the last two inv list elements into the list ring
+ const ListDrawSpec &spa = m_inventorylists[siz - 2];
+ const ListDrawSpec &spb = m_inventorylists[siz - 1];
+ m_inventory_rings.emplace_back(spa.inventoryloc, spa.listname);
+ m_inventory_rings.emplace_back(spb.inventoryloc, spb.listname);
+ return;
+ }
+
+ errorstream<< "Invalid list ring element(" << parts.size() << ", "
+ << m_inventorylists.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() >= 3) && (parts.size() <= 4)) ||
+ ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string name = parts[1];
+ std::string label = parts[2];
+ std::string selected;
+
+ if (parts.size() >= 4)
+ selected = parts[3];
+
+ MY_CHECKPOS("checkbox",0);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ bool fselected = false;
+
+ if (selected == "true")
+ fselected = true;
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ core::rect<s32> rect = core::rect<s32>(
+ pos.X, pos.Y + ((imgsize.Y/2) - m_btn_height),
+ pos.X + m_font->getDimension(wlabel.c_str()).Width + 25, // text size + size of checkbox
+ pos.Y + ((imgsize.Y/2) + m_btn_height));
+
+ FieldSpec spec(
+ name,
+ wlabel, //Needed for displaying text on MSVC
+ wlabel,
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_CheckBox;
+
+ gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
+ spec.fid, spec.flabel.c_str());
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ m_checkboxes.emplace_back(spec,e);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (parts.size() >= 5) {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_dim = split(parts[1],',');
+ std::string name = parts[3];
+ std::string value = parts[4];
+
+ MY_CHECKPOS("scrollbar",0);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ if (v_dim.size() != 2) {
+ errorstream<< "Invalid size for element " << "scrollbar"
+ << "specified: \"" << parts[1] << "\"" << std::endl;
+ return;
+ }
+
+ v2s32 dim;
+ dim.X = stof(v_dim[0]) * (float) spacing.X;
+ dim.Y = stof(v_dim[1]) * (float) spacing.Y;
+
+ core::rect<s32> rect =
+ core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ bool is_horizontal = true;
+
+ if (parts[2] == "vertical")
+ is_horizontal = false;
+
+ spec.ftype = f_ScrollBar;
+ spec.send = true;
+ gui::IGUIScrollBar* e =
+ Environment->addScrollBar(is_horizontal,rect,this,spec.fid);
+
+ e->setMax(1000);
+ e->setMin(0);
+ e->setPos(stoi(parts[4]));
+ e->setSmallStep(10);
+ e->setLargeStep(100);
+
+ m_scrollbars.emplace_back(spec,e);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 3) ||
+ ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = unescape_string(parts[2]);
+
+ MY_CHECKPOS("image", 0);
+ MY_CHECKGEOM("image", 1);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)imgsize.X;
+ geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+
+ if (!data->explicit_size)
+ warningstream<<"invalid use of image without a size[] element"<<std::endl;
+ m_images.emplace_back(name, pos, geom);
+ return;
+ }
+
+ if (parts.size() == 2) {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string name = unescape_string(parts[1]);
+
+ MY_CHECKPOS("image", 0);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ if (!data->explicit_size)
+ warningstream<<"invalid use of image without a size[] element"<<std::endl;
+ m_images.emplace_back(name, pos);
+ return;
+ }
+ errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 3) ||
+ ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+
+ MY_CHECKPOS("itemimage",0);
+ MY_CHECKGEOM("itemimage",1);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)imgsize.X;
+ geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of item_image without a size[] element"<<std::endl;
+ m_itemimages.emplace_back("", name, pos, geom);
+ return;
+ }
+ errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
+ const std::string &type)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 4) ||
+ ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::string label = parts[3];
+
+ MY_CHECKPOS("button",0);
+ MY_CHECKGEOM("button",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+
+ core::rect<s32> rect =
+ core::rect<s32>(pos.X, pos.Y - m_btn_height,
+ pos.X + geom.X, pos.Y + m_btn_height);
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of button without a size[] element"<<std::endl;
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ L"",
+ 258+m_fields.size()
+ );
+ spec.ftype = f_Button;
+ if(type == "button_exit")
+ spec.is_exit = true;
+ gui::IGUIButton* e = Environment->addButton(rect, this, spec.fid,
+ spec.flabel.c_str());
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 3) || (parts.size() == 4)) ||
+ ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = unescape_string(parts[2]);
+
+ MY_CHECKPOS("background",0);
+ MY_CHECKGEOM("background",1);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X - (float)imgsize.X)/2;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y - (float)imgsize.Y)/2;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)spacing.X;
+ geom.Y = stof(v_geom[1]) * (float)spacing.Y;
+
+ if (!data->explicit_size)
+ warningstream<<"invalid use of background without a size[] element"<<std::endl;
+
+ bool clip = false;
+ if (parts.size() == 4 && is_yes(parts[3])) {
+ pos.X = stoi(v_pos[0]); //acts as offset
+ pos.Y = stoi(v_pos[1]); //acts as offset
+ clip = true;
+ }
+ m_backgrounds.emplace_back(name, pos, geom, clip);
+
+ return;
+ }
+ errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ data->table_options.clear();
+ for (const std::string &part : parts) {
+ // Parse table option
+ std::string opt = unescape_string(part);
+ data->table_options.push_back(GUITable::splitOption(opt));
+ }
+}
+
+void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ data->table_columns.clear();
+ for (const std::string &part : parts) {
+ std::vector<std::string> col_parts = split(part,',');
+ GUITable::TableColumn column;
+ // Parse column type
+ if (!col_parts.empty())
+ column.type = col_parts[0];
+ // Parse column options
+ for (size_t j = 1; j < col_parts.size(); ++j) {
+ std::string opt = unescape_string(col_parts[j]);
+ column.options.push_back(GUITable::splitOption(opt));
+ }
+ data->table_columns.push_back(column);
+ }
+}
+
+void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 4) || (parts.size() == 5)) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::vector<std::string> items = split(parts[3],',');
+ std::string str_initial_selection;
+ std::string str_transparent = "false";
+
+ if (parts.size() >= 5)
+ str_initial_selection = parts[4];
+
+ MY_CHECKPOS("table",0);
+ MY_CHECKGEOM("table",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)spacing.X;
+ geom.Y = stof(v_geom[1]) * (float)spacing.Y;
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_Table;
+
+ for (std::string &item : items) {
+ item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
+ }
+
+ //now really show table
+ GUITable *e = new GUITable(Environment, this, spec.fid, rect,
+ m_tsrc);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ e->setTable(data->table_options, data->table_columns, items);
+
+ if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
+ e->setDynamicData(data->table_dyndata[name]);
+ }
+
+ if (!str_initial_selection.empty() && str_initial_selection != "0")
+ e->setSelected(stoi(str_initial_selection));
+
+ m_tables.emplace_back(spec, e);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) ||
+ ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::vector<std::string> items = split(parts[3],',');
+ std::string str_initial_selection;
+ std::string str_transparent = "false";
+
+ if (parts.size() >= 5)
+ str_initial_selection = parts[4];
+
+ if (parts.size() >= 6)
+ str_transparent = parts[5];
+
+ MY_CHECKPOS("textlist",0);
+ MY_CHECKGEOM("textlist",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)spacing.X;
+ geom.Y = stof(v_geom[1]) * (float)spacing.Y;
+
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_Table;
+
+ for (std::string &item : items) {
+ item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
+ }
+
+ //now really show list
+ GUITable *e = new GUITable(Environment, this, spec.fid, rect,
+ m_tsrc);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ e->setTextList(items, is_yes(str_transparent));
+
+ if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
+ e->setDynamicData(data->table_dyndata[name]);
+ }
+
+ if (!str_initial_selection.empty() && str_initial_selection != "0")
+ e->setSelected(stoi(str_initial_selection));
+
+ m_tables.emplace_back(spec, e);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+
+void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 5) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string name = parts[2];
+ std::vector<std::string> items = split(parts[3],',');
+ std::string str_initial_selection;
+ str_initial_selection = parts[4];
+
+ MY_CHECKPOS("dropdown",0);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ s32 width = stof(parts[1]) * (float)spacing.Y;
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
+ pos.X + width, pos.Y + (m_btn_height * 2));
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_DropDown;
+ spec.send = true;
+
+ //now really show list
+ gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ for (const std::string &item : items) {
+ e->addItem(unescape_translate(unescape_string(
+ utf8_to_wide(item))).c_str());
+ }
+
+ if (!str_initial_selection.empty())
+ e->setSelected(stoi(str_initial_selection)-1);
+
+ m_fields.push_back(spec);
+
+ m_dropdowns.emplace_back(spec, std::vector<std::string>());
+ std::vector<std::string> &values = m_dropdowns.back().second;
+ for (const std::string &item : items) {
+ values.push_back(unescape_string(item));
+ }
+
+ return;
+ }
+ errorstream << "Invalid dropdown element(" << parts.size() << "): '"
+ << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+ if (parts.size() == 2 ||
+ (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) {
+ field_close_on_enter[parts[0]] = is_yes(parts[1]);
+ }
+}
+
+void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 4) || (parts.size() == 5) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::string label = parts[3];
+
+ MY_CHECKPOS("pwdfield",0);
+ MY_CHECKGEOM("pwdfield",1);
+
+ v2s32 pos = pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+ pos.Y -= m_btn_height;
+ geom.Y = m_btn_height*2;
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.send = true;
+ gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ if (label.length() >= 1)
+ {
+ int font_height = g_fontengine->getTextHeight();
+ rect.UpperLeftCorner.Y -= font_height;
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
+ }
+
+ e->setPasswordBox(true,L'*');
+
+ irr::SEvent evt;
+ evt.EventType = EET_KEY_INPUT_EVENT;
+ evt.KeyInput.Key = KEY_END;
+ evt.KeyInput.Char = 0;
+ evt.KeyInput.Control = false;
+ evt.KeyInput.Shift = false;
+ evt.KeyInput.PressedDown = true;
+ e->OnEvent(evt);
+
+ if (parts.size() >= 5) {
+ // TODO: remove after 2016-11-03
+ warningstream << "pwdfield: use field_close_on_enter[name, enabled]" <<
+ " instead of the 5th param" << std::endl;
+ field_close_on_enter[name] = is_yes(parts[4]);
+ }
+
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseSimpleField(parserData* data,
+ std::vector<std::string> &parts)
+{
+ std::string name = parts[0];
+ std::string label = parts[1];
+ std::string default_val = parts[2];
+
+ core::rect<s32> rect;
+
+ if(data->explicit_size)
+ warningstream<<"invalid use of unpositioned \"field\" in inventory"<<std::endl;
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.Y = ((m_fields.size()+2)*60);
+ v2s32 size = DesiredRect.getSize();
+
+ rect = core::rect<s32>(size.X / 2 - 150, pos.Y,
+ (size.X / 2 - 150) + 300, pos.Y + (m_btn_height*2));
+
+
+ if(m_form_src)
+ default_val = m_form_src->resolveText(default_val);
+
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ utf8_to_wide(unescape_string(default_val)),
+ 258+m_fields.size()
+ );
+
+ if (name.empty()) {
+ // spec field id to 0, this stops submit searching for a value that isn't there
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid);
+ } else {
+ spec.send = true;
+ gui::IGUIElement *e;
+#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
+ if (g_settings->getBool("freetype")) {
+ e = (gui::IGUIElement *) new gui::intlGUIEditBox(spec.fdefault.c_str(),
+ true, Environment, this, spec.fid, rect);
+ e->drop();
+ } else {
+#else
+ {
+#endif
+ e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
+ }
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ irr::SEvent evt;
+ evt.EventType = EET_KEY_INPUT_EVENT;
+ evt.KeyInput.Key = KEY_END;
+ evt.KeyInput.Char = 0;
+ evt.KeyInput.Control = 0;
+ evt.KeyInput.Shift = 0;
+ evt.KeyInput.PressedDown = true;
+ e->OnEvent(evt);
+
+ if (label.length() >= 1)
+ {
+ int font_height = g_fontengine->getTextHeight();
+ rect.UpperLeftCorner.Y -= font_height;
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
+ }
+ }
+
+ if (parts.size() >= 4) {
+ // TODO: remove after 2016-11-03
+ warningstream << "field/simple: use field_close_on_enter[name, enabled]" <<
+ " instead of the 4th param" << std::endl;
+ field_close_on_enter[name] = is_yes(parts[3]);
+ }
+
+ m_fields.push_back(spec);
+}
+
+void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
+ const std::string &type)
+{
+
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::string label = parts[3];
+ std::string default_val = parts[4];
+
+ MY_CHECKPOS(type,0);
+ MY_CHECKGEOM(type,1);
+
+ v2s32 pos = pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ v2s32 geom;
+
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+
+ if (type == "textarea")
+ {
+ geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
+ pos.Y += m_btn_height;
+ }
+ else
+ {
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+ pos.Y -= m_btn_height;
+ geom.Y = m_btn_height*2;
+ }
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of positioned "<<type<<" without a size[] element"<<std::endl;
+
+ if(m_form_src)
+ default_val = m_form_src->resolveText(default_val);
+
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ utf8_to_wide(unescape_string(default_val)),
+ 258+m_fields.size()
+ );
+
+ bool is_editable = !name.empty();
+
+ if (is_editable)
+ spec.send = true;
+
+ gui::IGUIEditBox *e = nullptr;
+ const wchar_t *text = spec.fdefault.empty() ?
+ wlabel.c_str() : spec.fdefault.c_str();
+
+#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
+ if (g_settings->getBool("freetype")) {
+ e = (gui::IGUIEditBox *) new gui::intlGUIEditBox(text,
+ true, Environment, this, spec.fid, rect, is_editable, true);
+ e->drop();
+ } else {
+#else
+ {
+#endif
+ e = new GUIEditBoxWithScrollBar(text, true,
+ Environment, this, spec.fid, rect, is_editable, true);
+ }
+
+ if (is_editable && spec.fname == data->focused_fieldname)
+ Environment->setFocus(e);
+
+ if (e) {
+ if (type == "textarea")
+ {
+ e->setMultiLine(true);
+ e->setWordWrap(true);
+ e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
+ } else {
+ irr::SEvent evt;
+ evt.EventType = EET_KEY_INPUT_EVENT;
+ evt.KeyInput.Key = KEY_END;
+ evt.KeyInput.Char = 0;
+ evt.KeyInput.Control = 0;
+ evt.KeyInput.Shift = 0;
+ evt.KeyInput.PressedDown = true;
+ e->OnEvent(evt);
+ }
+ }
+
+ if (is_editable && !label.empty()) {
+ int font_height = g_fontengine->getTextHeight();
+ rect.UpperLeftCorner.Y -= font_height;
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
+ }
+
+ if (parts.size() >= 6) {
+ // TODO: remove after 2016-11-03
+ warningstream << "field/textarea: use field_close_on_enter[name, enabled]" <<
+ " instead of the 6th param" << std::endl;
+ field_close_on_enter[name] = is_yes(parts[5]);
+ }
+
+ m_fields.push_back(spec);
+}
+
+void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
+ const std::string &type)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (parts.size() == 3 || parts.size() == 4) {
+ parseSimpleField(data,parts);
+ return;
+ }
+
+ if ((parts.size() == 5) || (parts.size() == 6) ||
+ ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ parseTextArea(data,parts,type);
+ return;
+ }
+ errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 2) ||
+ ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string text = parts[1];
+
+ MY_CHECKPOS("label",0);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += (stof(v_pos[1]) + 7.0/30.0) * (float)spacing.Y;
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of label without a size[] element"<<std::endl;
+
+ std::vector<std::string> lines = split(text, '\n');
+
+ for (unsigned int i = 0; i != lines.size(); i++) {
+ // Lines are spaced at the nominal distance of
+ // 2/5 inventory slot, even if the font doesn't
+ // quite match that. This provides consistent
+ // form layout, at the expense of sometimes
+ // having sub-optimal spacing for the font.
+ // We multiply by 2 and then divide by 5, rather
+ // than multiply by 0.4, to get exact results
+ // in the integer cases: 0.4 is not exactly
+ // representable in binary floating point.
+ s32 posy = pos.Y + ((float)i) * spacing.Y * 2.0 / 5.0;
+ std::wstring wlabel = utf8_to_wide(unescape_string(lines[i]));
+ core::rect<s32> rect = core::rect<s32>(
+ pos.X, posy - m_btn_height,
+ pos.X + m_font->getDimension(wlabel.c_str()).Width,
+ posy + m_btn_height);
+ FieldSpec spec(
+ "",
+ wlabel,
+ L"",
+ 258+m_fields.size()
+ );
+ gui::IGUIStaticText *e =
+ addStaticText(Environment, spec.flabel.c_str(),
+ rect, false, false, this, spec.fid);
+ e->setTextAlignment(gui::EGUIA_UPPERLEFT,
+ gui::EGUIA_CENTER);
+ m_fields.push_back(spec);
+ }
+
+ return;
+ }
+ errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 2) ||
+ ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::wstring text = unescape_translate(
+ unescape_string(utf8_to_wide(parts[1])));
+
+ MY_CHECKPOS("vertlabel",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ core::rect<s32> rect = core::rect<s32>(
+ pos.X, pos.Y+((imgsize.Y/2)- m_btn_height),
+ pos.X+15, pos.Y +
+ font_line_height(m_font)
+ * (text.length()+1)
+ +((imgsize.Y/2)- m_btn_height));
+ //actually text.length() would be correct but adding +1 avoids to break all mods
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of label without a size[] element"<<std::endl;
+
+ std::wstring label;
+
+ for (wchar_t i : text) {
+ label += i;
+ label += L"\n";
+ }
+
+ FieldSpec spec(
+ "",
+ label,
+ L"",
+ 258+m_fields.size()
+ );
+ gui::IGUIStaticText *t =
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, false, this, spec.fid);
+ t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element,
+ const std::string &type)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) ||
+ ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string image_name = parts[2];
+ std::string name = parts[3];
+ std::string label = parts[4];
+
+ MY_CHECKPOS("imagebutton",0);
+ MY_CHECKGEOM("imagebutton",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+ v2s32 geom;
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+ geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
+
+ bool noclip = false;
+ bool drawborder = true;
+ std::string pressed_image_name;
+
+ if (parts.size() >= 7) {
+ if (parts[5] == "true")
+ noclip = true;
+ if (parts[6] == "false")
+ drawborder = false;
+ }
+
+ if (parts.size() >= 8) {
+ pressed_image_name = parts[7];
+ }
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of image_button without a size[] element"<<std::endl;
+
+ image_name = unescape_string(image_name);
+ pressed_image_name = unescape_string(pressed_image_name);
+
+ std::wstring wlabel = utf8_to_wide(unescape_string(label));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ utf8_to_wide(image_name),
+ 258+m_fields.size()
+ );
+ spec.ftype = f_Button;
+ if(type == "image_button_exit")
+ spec.is_exit = true;
+
+ video::ITexture *texture = 0;
+ video::ITexture *pressed_texture = 0;
+ texture = m_tsrc->getTexture(image_name);
+ if (!pressed_image_name.empty())
+ pressed_texture = m_tsrc->getTexture(pressed_image_name);
+ else
+ pressed_texture = texture;
+
+ gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ e->setUseAlphaChannel(true);
+ e->setImage(guiScalingImageButton(
+ Environment->getVideoDriver(), texture, geom.X, geom.Y));
+ e->setPressedImage(guiScalingImageButton(
+ Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
+ e->setScaleImage(true);
+ e->setNotClipped(noclip);
+ e->setDrawBorder(drawborder);
+
+ m_fields.push_back(spec);
+ return;
+ }
+
+ errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 4) || (parts.size() == 6)) ||
+ ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string name = parts[1];
+ std::vector<std::string> buttons = split(parts[2],',');
+ std::string str_index = parts[3];
+ bool show_background = true;
+ bool show_border = true;
+ int tab_index = stoi(str_index) -1;
+
+ MY_CHECKPOS("tabheader",0);
+
+ if (parts.size() == 6) {
+ if (parts[4] == "true")
+ show_background = false;
+ if (parts[5] == "false")
+ show_border = false;
+ }
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_TabHeader;
+
+ v2s32 pos = pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y - m_btn_height * 2;
+ v2s32 geom;
+ geom.X = DesiredRect.getWidth();
+ geom.Y = m_btn_height*2;
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
+ pos.Y+geom.Y);
+
+ gui::IGUITabControl *e = Environment->addTabControl(rect, this,
+ show_background, show_border, spec.fid);
+ e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
+ irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
+ e->setTabHeight(m_btn_height*2);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ e->setNotClipped(true);
+
+ for (const std::string &button : buttons) {
+ e->addTab(unescape_translate(unescape_string(
+ utf8_to_wide(button))).c_str(), -1);
+ }
+
+ if ((tab_index >= 0) &&
+ (buttons.size() < INT_MAX) &&
+ (tab_index < (int) buttons.size()))
+ e->setActiveTab(tab_index);
+
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream << "Invalid TabHeader element(" << parts.size() << "): '"
+ << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
+{
+
+ if (m_client == 0) {
+ warningstream << "invalid use of item_image_button with m_client==0"
+ << std::endl;
+ return;
+ }
+
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 5) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string item_name = parts[2];
+ std::string name = parts[3];
+ std::string label = parts[4];
+
+ label = unescape_string(label);
+ item_name = unescape_string(item_name);
+
+ MY_CHECKPOS("itemimagebutton",0);
+ MY_CHECKGEOM("itemimagebutton",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+ v2s32 geom;
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+ geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of item_image_button without a size[] element"<<std::endl;
+
+ IItemDefManager *idef = m_client->idef();
+ ItemStack item;
+ item.deSerialize(item_name, idef);
+
+ m_tooltips[name] =
+ TooltipSpec(utf8_to_wide(item.getDefinition(idef).description),
+ m_default_tooltip_bgcolor,
+ m_default_tooltip_color);
+
+ FieldSpec spec(
+ name,
+ utf8_to_wide(label),
+ utf8_to_wide(item_name),
+ 258 + m_fields.size()
+ );
+
+ gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, L"");
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ spec.ftype = f_Button;
+ rect+=data->basepos-padding;
+ spec.rect=rect;
+ m_fields.push_back(spec);
+ pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+ m_itemimages.emplace_back("", item_name, e, pos, geom);
+ m_static_texts.emplace_back(utf8_to_wide(label), rect, e);
+ return;
+ }
+ errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 3) ||
+ ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+
+ MY_CHECKPOS("box",0);
+ MY_CHECKGEOM("box",1);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)spacing.X;
+ geom.Y = stof(v_geom[1]) * (float)spacing.Y;
+
+ video::SColor tmp_color;
+
+ if (parseColorString(parts[2], tmp_color, false)) {
+ BoxDrawSpec spec(pos, geom, tmp_color);
+
+ m_boxes.push_back(spec);
+ }
+ else {
+ errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl;
+ }
+ return;
+ }
+ errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 1) || (parts.size() == 2)) ||
+ ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) {
+ parseColorString(parts[0], m_bgcolor, false);
+
+ if (parts.size() == 2) {
+ std::string fullscreen = parts[1];
+ m_bgfullscreen = is_yes(fullscreen);
+ }
+
+ return;
+ }
+
+ errorstream << "Invalid bgcolor element(" << parts.size() << "): '" << element << "'"
+ << std::endl;
+}
+
+void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ parseColorString(parts[0], m_slotbg_n, false);
+ parseColorString(parts[1], m_slotbg_h, false);
+
+ if (parts.size() >= 3) {
+ if (parseColorString(parts[2], m_slotbordercolor, false)) {
+ m_slotborder = true;
+ }
+ }
+ if (parts.size() == 5) {
+ video::SColor tmp_color;
+
+ if (parseColorString(parts[3], tmp_color, false))
+ m_default_tooltip_bgcolor = tmp_color;
+ if (parseColorString(parts[4], tmp_color, false))
+ m_default_tooltip_color = tmp_color;
+ }
+ return;
+ }
+ errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+ if (parts.size() == 2) {
+ std::string name = parts[0];
+ m_tooltips[name] = TooltipSpec(utf8_to_wide(unescape_string(parts[1])),
+ m_default_tooltip_bgcolor, m_default_tooltip_color);
+ return;
+ }
+
+ if (parts.size() == 4) {
+ std::string name = parts[0];
+ video::SColor tmp_color1, tmp_color2;
+ if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) {
+ m_tooltips[name] = TooltipSpec(utf8_to_wide(unescape_string(parts[1])),
+ tmp_color1, tmp_color2);
+ return;
+ }
+ }
+ errorstream<< "Invalid tooltip element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+bool GUIFormSpecMenu::parseVersionDirect(const std::string &data)
+{
+ //some prechecks
+ if (data.empty())
+ return false;
+
+ std::vector<std::string> parts = split(data,'[');
+
+ if (parts.size() < 2) {
+ return false;
+ }
+
+ if (parts[0] != "formspec_version") {
+ return false;
+ }
+
+ if (is_number(parts[1])) {
+ m_formspec_version = mystoi(parts[1]);
+ return true;
+ }
+
+ return false;
+}
+
+bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &element)
+{
+ if (element.empty())
+ return false;
+
+ std::vector<std::string> parts = split(element,'[');
+
+ if (parts.size() < 2)
+ return false;
+
+ std::string type = trim(parts[0]);
+ std::string description = trim(parts[1]);
+
+ if (type != "size" && type != "invsize")
+ return false;
+
+ if (type == "invsize")
+ log_deprecated("Deprecated formspec element \"invsize\" is used");
+
+ parseSize(data, description);
+
+ return true;
+}
+
+bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &element)
+{
+ if (element.empty())
+ return false;
+
+ std::vector<std::string> parts = split(element, '[');
+
+ if (parts.size() != 2)
+ return false;
+
+ std::string type = trim(parts[0]);
+ std::string description = trim(parts[1]);
+
+ if (type != "position")
+ return false;
+
+ parsePosition(data, description);
+
+ return true;
+}
+
+void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ',');
+
+ if (parts.size() == 2) {
+ data->offset.X = stof(parts[0]);
+ data->offset.Y = stof(parts[1]);
+ return;
+ }
+
+ errorstream << "Invalid position element (" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &element)
+{
+ if (element.empty())
+ return false;
+
+ std::vector<std::string> parts = split(element, '[');
+
+ if (parts.size() != 2)
+ return false;
+
+ std::string type = trim(parts[0]);
+ std::string description = trim(parts[1]);
+
+ if (type != "anchor")
+ return false;
+
+ parseAnchor(data, description);
+
+ return true;
+}
+
+void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ',');
+
+ if (parts.size() == 2) {
+ data->anchor.X = stof(parts[0]);
+ data->anchor.Y = stof(parts[1]);
+ return;
+ }
+
+ errorstream << "Invalid anchor element (" << parts.size() << "): '" << element
+ << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
+{
+ //some prechecks
+ if (element.empty())
+ return;
+
+ std::vector<std::string> parts = split(element,'[');
+
+ // ugly workaround to keep compatibility
+ if (parts.size() > 2) {
+ if (trim(parts[0]) == "image") {
+ for (unsigned int i=2;i< parts.size(); i++) {
+ parts[1] += "[" + parts[i];
+ }
+ }
+ else { return; }
+ }
+
+ if (parts.size() < 2) {
+ return;
+ }
+
+ std::string type = trim(parts[0]);
+ std::string description = trim(parts[1]);
+
+ if (type == "container") {
+ parseContainer(data, description);
+ return;
+ }
+
+ if (type == "container_end") {
+ parseContainerEnd(data);
+ return;
+ }
+
+ if (type == "list") {
+ parseList(data, description);
+ return;
+ }
+
+ if (type == "listring") {
+ parseListRing(data, description);
+ return;
+ }
+
+ if (type == "checkbox") {
+ parseCheckbox(data, description);
+ return;
+ }
+
+ if (type == "image") {
+ parseImage(data, description);
+ return;
+ }
+
+ if (type == "item_image") {
+ parseItemImage(data, description);
+ return;
+ }
+
+ if (type == "button" || type == "button_exit") {
+ parseButton(data, description, type);
+ return;
+ }
+
+ if (type == "background") {
+ parseBackground(data,description);
+ return;
+ }
+
+ if (type == "tableoptions"){
+ parseTableOptions(data,description);
+ return;
+ }
+
+ if (type == "tablecolumns"){
+ parseTableColumns(data,description);
+ return;
+ }
+
+ if (type == "table"){
+ parseTable(data,description);
+ return;
+ }
+
+ if (type == "textlist"){
+ parseTextList(data,description);
+ return;
+ }
+
+ if (type == "dropdown"){
+ parseDropDown(data,description);
+ return;
+ }
+
+ if (type == "field_close_on_enter") {
+ parseFieldCloseOnEnter(data, description);
+ return;
+ }
+
+ if (type == "pwdfield") {
+ parsePwdField(data,description);
+ return;
+ }
+
+ if ((type == "field") || (type == "textarea")){
+ parseField(data,description,type);
+ return;
+ }
+
+ if (type == "label") {
+ parseLabel(data,description);
+ return;
+ }
+
+ if (type == "vertlabel") {
+ parseVertLabel(data,description);
+ return;
+ }
+
+ if (type == "item_image_button") {
+ parseItemImageButton(data,description);
+ return;
+ }
+
+ if ((type == "image_button") || (type == "image_button_exit")) {
+ parseImageButton(data,description,type);
+ return;
+ }
+
+ if (type == "tabheader") {
+ parseTabHeader(data,description);
+ return;
+ }
+
+ if (type == "box") {
+ parseBox(data,description);
+ return;
+ }
+
+ if (type == "bgcolor") {
+ parseBackgroundColor(data,description);
+ return;
+ }
+
+ if (type == "listcolors") {
+ parseListColors(data,description);
+ return;
+ }
+
+ if (type == "tooltip") {
+ parseTooltip(data,description);
+ return;
+ }
+
+ if (type == "scrollbar") {
+ parseScrollBar(data, description);
+ return;
+ }
+
+ // Ignore others
+ infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
+ << std::endl;
+}
+
+void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
+{
+ /* useless to regenerate without a screensize */
+ if ((screensize.X <= 0) || (screensize.Y <= 0)) {
+ return;
+ }
+
+ parserData mydata;
+
+ //preserve tables
+ for (auto &m_table : m_tables) {
+ std::string tablename = m_table.first.fname;
+ GUITable *table = m_table.second;
+ mydata.table_dyndata[tablename] = table->getDynamicData();
+ }
+
+ //set focus
+ if (!m_focused_element.empty())
+ mydata.focused_fieldname = m_focused_element;
+
+ //preserve focus
+ gui::IGUIElement *focused_element = Environment->getFocus();
+ if (focused_element && focused_element->getParent() == this) {
+ s32 focused_id = focused_element->getID();
+ if (focused_id > 257) {
+ for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
+ if (field.fid == focused_id) {
+ mydata.focused_fieldname = field.fname;
+ break;
+ }
+ }
+ }
+ }
+
+ // Remove children
+ removeChildren();
+
+ for (auto &table_it : m_tables) {
+ table_it.second->drop();
+ }
+
+ mydata.size= v2s32(100,100);
+ mydata.screensize = screensize;
+ mydata.offset = v2f32(0.5f, 0.5f);
+ mydata.anchor = v2f32(0.5f, 0.5f);
+
+ // Base position of contents of form
+ mydata.basepos = getBasePos();
+
+ /* Convert m_init_draw_spec to m_inventorylists */
+
+ m_inventorylists.clear();
+ m_images.clear();
+ m_backgrounds.clear();
+ m_itemimages.clear();
+ m_tables.clear();
+ m_checkboxes.clear();
+ m_scrollbars.clear();
+ m_fields.clear();
+ m_boxes.clear();
+ m_tooltips.clear();
+ m_inventory_rings.clear();
+ m_static_texts.clear();
+ m_dropdowns.clear();
+
+ m_bgfullscreen = false;
+
+ {
+ v3f formspec_bgcolor = g_settings->getV3F("formspec_default_bg_color");
+ m_bgcolor = video::SColor(
+ (u8) clamp_u8(g_settings->getS32("formspec_default_bg_opacity")),
+ clamp_u8(myround(formspec_bgcolor.X)),
+ clamp_u8(myround(formspec_bgcolor.Y)),
+ clamp_u8(myround(formspec_bgcolor.Z))
+ );
+ }
+
+ {
+ v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color");
+ m_fullscreen_bgcolor = video::SColor(
+ (u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")),
+ clamp_u8(myround(formspec_bgcolor.X)),
+ clamp_u8(myround(formspec_bgcolor.Y)),
+ clamp_u8(myround(formspec_bgcolor.Z))
+ );
+ }
+
+
+ m_slotbg_n = video::SColor(255,128,128,128);
+ m_slotbg_h = video::SColor(255,192,192,192);
+
+ m_default_tooltip_bgcolor = video::SColor(255,110,130,60);
+ m_default_tooltip_color = video::SColor(255,255,255,255);
+
+ m_slotbordercolor = video::SColor(200,0,0,0);
+ m_slotborder = false;
+
+ // Add tooltip
+ {
+ assert(!m_tooltip_element);
+ // Note: parent != this so that the tooltip isn't clipped by the menu rectangle
+ m_tooltip_element = addStaticText(Environment, L"",core::rect<s32>(0,0,110,18));
+ m_tooltip_element->enableOverrideColor(true);
+ m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
+ m_tooltip_element->setDrawBackground(true);
+ m_tooltip_element->setDrawBorder(true);
+ m_tooltip_element->setOverrideColor(m_default_tooltip_color);
+ m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
+ m_tooltip_element->setWordWrap(false);
+ //we're not parent so no autograb for this one!
+ m_tooltip_element->grab();
+ }
+
+ std::vector<std::string> elements = split(m_formspec_string,']');
+ unsigned int i = 0;
+
+ /* try to read version from first element only */
+ if (!elements.empty()) {
+ if ( parseVersionDirect(elements[0]) ) {
+ i++;
+ }
+ }
+
+ /* we need size first in order to calculate image scale */
+ mydata.explicit_size = false;
+ for (; i< elements.size(); i++) {
+ if (!parseSizeDirect(&mydata, elements[i])) {
+ break;
+ }
+ }
+
+ /* "position" element is always after "size" element if it used */
+ for (; i< elements.size(); i++) {
+ if (!parsePositionDirect(&mydata, elements[i])) {
+ break;
+ }
+ }
+
+ /* "anchor" element is always after "position" (or "size" element) if it used */
+ for (; i< elements.size(); i++) {
+ if (!parseAnchorDirect(&mydata, elements[i])) {
+ break;
+ }
+ }
+
+
+ if (mydata.explicit_size) {
+ // compute scaling for specified form size
+ if (m_lock) {
+ v2u32 current_screensize = RenderingEngine::get_video_driver()->getScreenSize();
+ v2u32 delta = current_screensize - m_lockscreensize;
+
+ if (current_screensize.Y > m_lockscreensize.Y)
+ delta.Y /= 2;
+ else
+ delta.Y = 0;
+
+ if (current_screensize.X > m_lockscreensize.X)
+ delta.X /= 2;
+ else
+ delta.X = 0;
+
+ offset = v2s32(delta.X,delta.Y);
+
+ mydata.screensize = m_lockscreensize;
+ } else {
+ offset = v2s32(0,0);
+ }
+
+ double gui_scaling = g_settings->getFloat("gui_scaling");
+ double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
+
+ double use_imgsize;
+ if (m_lock) {
+ // In fixed-size mode, inventory image size
+ // is 0.53 inch multiplied by the gui_scaling
+ // config parameter. This magic size is chosen
+ // to make the main menu (15.5 inventory images
+ // wide, including border) just fit into the
+ // default window (800 pixels wide) at 96 DPI
+ // and default scaling (1.00).
+ use_imgsize = 0.5555 * screen_dpi * gui_scaling;
+ } else {
+ // In variable-size mode, we prefer to make the
+ // inventory image size 1/15 of screen height,
+ // multiplied by the gui_scaling config parameter.
+ // If the preferred size won't fit the whole
+ // form on the screen, either horizontally or
+ // vertically, then we scale it down to fit.
+ // (The magic numbers in the computation of what
+ // fits arise from the scaling factors in the
+ // following stanza, including the form border,
+ // help text space, and 0.1 inventory slot spare.)
+ // However, a minimum size is also set, that
+ // the image size can't be less than 0.3 inch
+ // multiplied by gui_scaling, even if this means
+ // the form doesn't fit the screen.
+ double prefer_imgsize = mydata.screensize.Y / 15 *
+ gui_scaling;
+ double fitx_imgsize = mydata.screensize.X /
+ ((5.0/4.0) * (0.5 + mydata.invsize.X));
+ double fity_imgsize = mydata.screensize.Y /
+ ((15.0/13.0) * (0.85 * mydata.invsize.Y));
+ double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
+ double min_imgsize = 0.3 * screen_dpi * gui_scaling;
+ use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize,
+ MYMIN(fitx_imgsize, fity_imgsize)));
+ }
+
+ // Everything else is scaled in proportion to the
+ // inventory image size. The inventory slot spacing
+ // is 5/4 image size horizontally and 15/13 image size
+ // vertically. The padding around the form (incorporating
+ // the border of the outer inventory slots) is 3/8
+ // image size. Font height (baseline to baseline)
+ // is 2/5 vertical inventory slot spacing, and button
+ // half-height is 7/8 of font height.
+ imgsize = v2s32(use_imgsize, use_imgsize);
+ spacing = v2s32(use_imgsize*5.0/4, use_imgsize*15.0/13);
+ padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8);
+ m_btn_height = use_imgsize*15.0/13 * 0.35;
+
+ m_font = g_fontengine->getFont();
+
+ mydata.size = v2s32(
+ padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
+ padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
+ );
+ DesiredRect = mydata.rect = core::rect<s32>(
+ (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X,
+ (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y,
+ (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * (f32)mydata.size.X) + offset.X,
+ (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * (f32)mydata.size.Y) + offset.Y
+ );
+ } else {
+ // Non-size[] form must consist only of text fields and
+ // implicit "Proceed" button. Use default font, and
+ // temporary form size which will be recalculated below.
+ m_font = g_fontengine->getFont();
+ m_btn_height = font_line_height(m_font) * 0.875;
+ DesiredRect = core::rect<s32>(
+ (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * 580.0),
+ (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * 300.0),
+ (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * 580.0),
+ (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * 300.0)
+ );
+ }
+ recalculateAbsolutePosition(false);
+ mydata.basepos = getBasePos();
+ m_tooltip_element->setOverrideFont(m_font);
+
+ gui::IGUISkin* skin = Environment->getSkin();
+ sanity_check(skin);
+ gui::IGUIFont *old_font = skin->getFont();
+ skin->setFont(m_font);
+
+ pos_offset = v2s32();
+ for (; i< elements.size(); i++) {
+ parseElement(&mydata, elements[i]);
+ }
+
+ if (!container_stack.empty()) {
+ errorstream << "Invalid formspec string: container was never closed!"
+ << std::endl;
+ }
+
+ // If there are fields without explicit size[], add a "Proceed"
+ // button and adjust size to fit all the fields.
+ if (!m_fields.empty() && !mydata.explicit_size) {
+ mydata.rect = core::rect<s32>(
+ mydata.screensize.X/2 - 580/2,
+ mydata.screensize.Y/2 - 300/2,
+ mydata.screensize.X/2 + 580/2,
+ mydata.screensize.Y/2 + 240/2+(m_fields.size()*60)
+ );
+ DesiredRect = mydata.rect;
+ recalculateAbsolutePosition(false);
+ mydata.basepos = getBasePos();
+
+ {
+ v2s32 pos = mydata.basepos;
+ pos.Y = ((m_fields.size()+2)*60);
+
+ v2s32 size = DesiredRect.getSize();
+ mydata.rect =
+ core::rect<s32>(size.X/2-70, pos.Y,
+ (size.X/2-70)+140, pos.Y + (m_btn_height*2));
+ const wchar_t *text = wgettext("Proceed");
+ Environment->addButton(mydata.rect, this, 257, text);
+ delete[] text;
+ }
+
+ }
+
+ //set initial focus if parser didn't set it
+ focused_element = Environment->getFocus();
+ if (!focused_element
+ || !isMyChild(focused_element)
+ || focused_element->getType() == gui::EGUIET_TAB_CONTROL)
+ setInitialFocus();
+
+ skin->setFont(old_font);
+}
+
+#ifdef __ANDROID__
+bool GUIFormSpecMenu::getAndroidUIInput()
+{
+ /* no dialog shown */
+ if (m_JavaDialogFieldName == "") {
+ return false;
+ }
+
+ /* still waiting */
+ if (porting::getInputDialogState() == -1) {
+ return true;
+ }
+
+ std::string fieldname = m_JavaDialogFieldName;
+ m_JavaDialogFieldName = "";
+
+ /* no value abort dialog processing */
+ if (porting::getInputDialogState() != 0) {
+ return false;
+ }
+
+ for(std::vector<FieldSpec>::iterator iter = m_fields.begin();
+ iter != m_fields.end(); ++iter) {
+
+ if (iter->fname != fieldname) {
+ continue;
+ }
+ IGUIElement* tochange = getElementFromId(iter->fid);
+
+ if (tochange == 0) {
+ return false;
+ }
+
+ if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) {
+ return false;
+ }
+
+ std::string text = porting::getInputDialogValue();
+
+ ((gui::IGUIEditBox*) tochange)->
+ setText(utf8_to_wide(text).c_str());
+ }
+ return false;
+}
+#endif
+
+GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
+{
+ core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
+
+ for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
+ for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
+ s32 item_i = i + s.start_item_i;
+ s32 x = (i%s.geom.X) * spacing.X;
+ s32 y = (i/s.geom.X) * spacing.Y;
+ v2s32 p0(x,y);
+ core::rect<s32> rect = imgrect + s.pos + p0;
+ if(rect.isPointInside(p))
+ {
+ return ItemSpec(s.inventoryloc, s.listname, item_i);
+ }
+ }
+ }
+
+ return ItemSpec(InventoryLocation(), "", -1);
+}
+
+void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase,
+ bool &item_hovered)
+{
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
+ if(!inv){
+ warningstream<<"GUIFormSpecMenu::drawList(): "
+ <<"The inventory location "
+ <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
+ <<std::endl;
+ return;
+ }
+ InventoryList *ilist = inv->getList(s.listname);
+ if(!ilist){
+ warningstream<<"GUIFormSpecMenu::drawList(): "
+ <<"The inventory list \""<<s.listname<<"\" @ \""
+ <<s.inventoryloc.dump()<<"\" doesn't exist"
+ <<std::endl;
+ return;
+ }
+
+ core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
+
+ for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
+ {
+ s32 item_i = i + s.start_item_i;
+ if(item_i >= (s32) ilist->getSize())
+ break;
+ s32 x = (i%s.geom.X) * spacing.X;
+ s32 y = (i/s.geom.X) * spacing.Y;
+ v2s32 p(x,y);
+ core::rect<s32> rect = imgrect + s.pos + p;
+ ItemStack item;
+ if(ilist)
+ item = ilist->getItem(item_i);
+
+ bool selected = m_selected_item
+ && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
+ && m_selected_item->listname == s.listname
+ && m_selected_item->i == item_i;
+ bool hovering = rect.isPointInside(m_pointer);
+ ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED :
+ (hovering ? IT_ROT_HOVERED : IT_ROT_NONE);
+
+ if (phase == 0) {
+ if (hovering) {
+ item_hovered = true;
+ driver->draw2DRectangle(m_slotbg_h, rect, &AbsoluteClippingRect);
+ } else {
+ driver->draw2DRectangle(m_slotbg_n, rect, &AbsoluteClippingRect);
+ }
+ }
+
+ //Draw inv slot borders
+ if (m_slotborder) {
+ s32 x1 = rect.UpperLeftCorner.X;
+ s32 y1 = rect.UpperLeftCorner.Y;
+ s32 x2 = rect.LowerRightCorner.X;
+ s32 y2 = rect.LowerRightCorner.Y;
+ s32 border = 1;
+ driver->draw2DRectangle(m_slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y1 - border),
+ v2s32(x2 + border, y1)), NULL);
+ driver->draw2DRectangle(m_slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y2),
+ v2s32(x2 + border, y2 + border)), NULL);
+ driver->draw2DRectangle(m_slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y1),
+ v2s32(x1, y2)), NULL);
+ driver->draw2DRectangle(m_slotbordercolor,
+ core::rect<s32>(v2s32(x2, y1),
+ v2s32(x2 + border, y2)), NULL);
+ }
+
+ if(phase == 1)
+ {
+ // Draw item stack
+ if(selected)
+ {
+ item.takeItem(m_selected_amount);
+ }
+ if(!item.empty())
+ {
+ drawItemStack(driver, m_font, item,
+ rect, &AbsoluteClippingRect, m_client,
+ rotation_kind);
+ }
+
+ // Draw tooltip
+ std::wstring tooltip_text;
+ if (hovering && !m_selected_item) {
+ const std::string &desc = item.metadata.getString("description");
+ if (desc.empty())
+ tooltip_text =
+ utf8_to_wide(item.getDefinition(m_client->idef()).description);
+ else
+ tooltip_text = utf8_to_wide(desc);
+
+ if (!item.name.empty()) {
+ if (tooltip_text.empty())
+ tooltip_text = utf8_to_wide(item.name);
+ if (m_tooltip_append_itemname)
+ tooltip_text += utf8_to_wide(" [" + item.name + "]");
+ }
+ }
+ if (!tooltip_text.empty()) {
+ showTooltip(tooltip_text, m_default_tooltip_color,
+ m_default_tooltip_bgcolor);
+ }
+ }
+ }
+}
+
+void GUIFormSpecMenu::drawSelectedItem()
+{
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ if (!m_selected_item) {
+ drawItemStack(driver, m_font, ItemStack(),
+ core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
+ NULL, m_client, IT_ROT_DRAGGED);
+ return;
+ }
+
+ Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
+ sanity_check(inv);
+ InventoryList *list = inv->getList(m_selected_item->listname);
+ sanity_check(list);
+ ItemStack stack = list->getItem(m_selected_item->i);
+ stack.count = m_selected_amount;
+
+ core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
+ core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
+ rect.constrainTo(driver->getViewPort());
+ drawItemStack(driver, m_font, stack, rect, NULL, m_client, IT_ROT_DRAGGED);
+}
+
+void GUIFormSpecMenu::drawMenu()
+{
+ if (m_form_src) {
+ const std::string &newform = m_form_src->getForm();
+ if (newform != m_formspec_string) {
+ m_formspec_string = newform;
+ regenerateGui(m_screensize_old);
+ }
+ }
+
+ gui::IGUISkin* skin = Environment->getSkin();
+ sanity_check(skin != NULL);
+ gui::IGUIFont *old_font = skin->getFont();
+ skin->setFont(m_font);
+
+ updateSelectedItem();
+
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ v2u32 screenSize = driver->getScreenSize();
+ core::rect<s32> allbg(0, 0, screenSize.X, screenSize.Y);
+
+ if (m_bgfullscreen)
+ driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg);
+ else
+ driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);
+
+ m_tooltip_element->setVisible(false);
+
+ /*
+ Draw backgrounds
+ */
+ for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_backgrounds) {
+ video::ITexture *texture = m_tsrc->getTexture(spec.name);
+
+ if (texture != 0) {
+ // Image size on screen
+ core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
+ // Image rectangle on screen
+ core::rect<s32> rect = imgrect + spec.pos;
+
+ if (spec.clip) {
+ core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
+ rect = core::rect<s32>(AbsoluteRect.UpperLeftCorner.X - spec.pos.X,
+ AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y,
+ AbsoluteRect.UpperLeftCorner.X + absrec_size.Width + spec.pos.X,
+ AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
+ }
+
+ const video::SColor color(255,255,255,255);
+ const video::SColor colors[] = {color,color,color,color};
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0,0),
+ core::dimension2di(texture->getOriginalSize())),
+ NULL/*&AbsoluteClippingRect*/, colors, true);
+ } else {
+ errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
+ errorstream << "\t" << spec.name << std::endl;
+ }
+ }
+
+ /*
+ Draw Boxes
+ */
+ for (const GUIFormSpecMenu::BoxDrawSpec &spec : m_boxes) {
+ irr::video::SColor todraw = spec.color;
+
+ todraw.setAlpha(140);
+
+ core::rect<s32> rect(spec.pos.X,spec.pos.Y,
+ spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y);
+
+ driver->draw2DRectangle(todraw, rect, 0);
+ }
+
+ /*
+ Call base class
+ */
+ gui::IGUIElement::draw();
+
+ /*
+ Draw images
+ */
+ for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_images) {
+ video::ITexture *texture = m_tsrc->getTexture(spec.name);
+
+ if (texture != 0) {
+ const core::dimension2d<u32>& img_origsize = texture->getOriginalSize();
+ // Image size on screen
+ core::rect<s32> imgrect;
+
+ if (spec.scale)
+ imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y);
+ else {
+
+ imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height);
+ }
+ // Image rectangle on screen
+ core::rect<s32> rect = imgrect + spec.pos;
+ const video::SColor color(255,255,255,255);
+ const video::SColor colors[] = {color,color,color,color};
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
+ NULL/*&AbsoluteClippingRect*/, colors, true);
+ }
+ else {
+ errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl;
+ errorstream << "\t" << spec.name << std::endl;
+ }
+ }
+
+ /*
+ Draw item images
+ */
+ for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_itemimages) {
+ if (m_client == 0)
+ break;
+
+ IItemDefManager *idef = m_client->idef();
+ ItemStack item;
+ item.deSerialize(spec.item_name, idef);
+ core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
+ // Viewport rectangle on screen
+ core::rect<s32> rect = imgrect + spec.pos;
+ if (spec.parent_button && spec.parent_button->isPressed()) {
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+ rect += core::dimension2d<s32>(
+ 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
+#else
+ rect += core::dimension2d<s32>(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
+#endif
+ }
+ drawItemStack(driver, m_font, item, rect, &AbsoluteClippingRect,
+ m_client, IT_ROT_NONE);
+ }
+
+ /*
+ Draw items
+ Phase 0: Item slot rectangles
+ Phase 1: Item images; prepare tooltip
+ */
+ bool item_hovered = false;
+ int start_phase = 0;
+ for (int phase = start_phase; phase <= 1; phase++) {
+ for (const GUIFormSpecMenu::ListDrawSpec &spec : m_inventorylists) {
+ drawList(spec, phase, item_hovered);
+ }
+ }
+ if (!item_hovered) {
+ drawItemStack(driver, m_font, ItemStack(),
+ core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
+ NULL, m_client, IT_ROT_HOVERED);
+ }
+
+/* TODO find way to show tooltips on touchscreen */
+#ifndef HAVE_TOUCHSCREENGUI
+ m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition();
+#endif
+
+ /*
+ Draw static text elements
+ */
+ for (const GUIFormSpecMenu::StaticTextSpec &spec : m_static_texts) {
+ core::rect<s32> rect = spec.rect;
+ if (spec.parent_button && spec.parent_button->isPressed()) {
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+ rect += core::dimension2d<s32>(
+ 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
+#else
+ // Use image offset instead of text's because its a bit smaller
+ // and fits better, also TEXT_OFFSET_X is always 0
+ rect += core::dimension2d<s32>(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
+#endif
+ }
+ video::SColor color(255, 255, 255, 255);
+ m_font->draw(spec.text.c_str(), rect, color, true, true, &rect);
+ }
+
+ /*
+ Draw fields/buttons tooltips
+ */
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
+
+ if (hovered != NULL) {
+ s32 id = hovered->getID();
+
+ u64 delta = 0;
+ if (id == -1) {
+ m_old_tooltip_id = id;
+ } else {
+ if (id == m_old_tooltip_id) {
+ delta = porting::getDeltaMs(m_hovered_time, porting::getTimeMs());
+ } else {
+ m_hovered_time = porting::getTimeMs();
+ m_old_tooltip_id = id;
+ }
+ }
+
+ // Find and update the current tooltip
+ if (id != -1 && delta >= m_tooltip_show_delay) {
+ for (const FieldSpec &field : m_fields) {
+
+ if (field.fid != id)
+ continue;
+
+ const std::wstring &text = m_tooltips[field.fname].tooltip;
+ if (!text.empty())
+ showTooltip(text, m_tooltips[field.fname].color,
+ m_tooltips[field.fname].bgcolor);
+
+ break;
+ }
+ }
+ }
+
+ m_tooltip_element->draw();
+
+ /*
+ Draw dragged item stack
+ */
+ drawSelectedItem();
+
+ skin->setFont(old_font);
+}
+
+
+void GUIFormSpecMenu::showTooltip(const std::wstring &text,
+ const irr::video::SColor &color, const irr::video::SColor &bgcolor)
+{
+ const std::wstring ntext = translate_string(text);
+ m_tooltip_element->setOverrideColor(color);
+ m_tooltip_element->setBackgroundColor(bgcolor);
+ setStaticText(m_tooltip_element, ntext.c_str());
+
+ // Tooltip size and offset
+ s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
+#if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1
+ std::vector<std::wstring> text_rows = str_split(ntext, L'\n');
+ s32 tooltip_height = m_tooltip_element->getTextHeight() * text_rows.size() + 5;
+#else
+ s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
+#endif
+ v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
+ int tooltip_offset_x = m_btn_height;
+ int tooltip_offset_y = m_btn_height;
+#ifdef __ANDROID__
+ tooltip_offset_x *= 3;
+ tooltip_offset_y = 0;
+ if (m_pointer.X > (s32)screenSize.X / 2)
+ tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
+#endif
+
+ // Calculate and set the tooltip position
+ s32 tooltip_x = m_pointer.X + tooltip_offset_x;
+ s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
+ if (tooltip_x + tooltip_width > (s32)screenSize.X)
+ tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height;
+ if (tooltip_y + tooltip_height > (s32)screenSize.Y)
+ tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
+
+ m_tooltip_element->setRelativePosition(
+ core::rect<s32>(
+ core::position2d<s32>(tooltip_x, tooltip_y),
+ core::dimension2d<s32>(tooltip_width, tooltip_height)
+ )
+ );
+
+ // Display the tooltip
+ m_tooltip_element->setVisible(true);
+ bringToFront(m_tooltip_element);
+}
+
+void GUIFormSpecMenu::updateSelectedItem()
+{
+ // If the selected stack has become empty for some reason, deselect it.
+ // If the selected stack has become inaccessible, deselect it.
+ // If the selected stack has become smaller, adjust m_selected_amount.
+ ItemStack selected = verifySelectedItem();
+
+ // WARNING: BLACK MAGIC
+ // See if there is a stack suited for our current guess.
+ // If such stack does not exist, clear the guess.
+ if (!m_selected_content_guess.name.empty() &&
+ selected.name == m_selected_content_guess.name &&
+ selected.count == m_selected_content_guess.count){
+ // Selected item fits the guess. Skip the black magic.
+ } else if (!m_selected_content_guess.name.empty()) {
+ bool found = false;
+ for(u32 i=0; i<m_inventorylists.size() && !found; i++){
+ const ListDrawSpec &s = m_inventorylists[i];
+ Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
+ if(!inv)
+ continue;
+ InventoryList *list = inv->getList(s.listname);
+ if(!list)
+ continue;
+ for(s32 i=0; i<s.geom.X*s.geom.Y && !found; i++){
+ u32 item_i = i + s.start_item_i;
+ if(item_i >= list->getSize())
+ continue;
+ ItemStack stack = list->getItem(item_i);
+ if(stack.name == m_selected_content_guess.name &&
+ stack.count == m_selected_content_guess.count){
+ found = true;
+ infostream<<"Client: Changing selected content guess to "
+ <<s.inventoryloc.dump()<<" "<<s.listname
+ <<" "<<item_i<<std::endl;
+ delete m_selected_item;
+ m_selected_item = new ItemSpec(s.inventoryloc, s.listname, item_i);
+ m_selected_amount = stack.count;
+ }
+ }
+ }
+ if(!found){
+ infostream<<"Client: Discarding selected content guess: "
+ <<m_selected_content_guess.getItemString()<<std::endl;
+ m_selected_content_guess.name = "";
+ }
+ }
+
+ // If craftresult is nonempty and nothing else is selected, select it now.
+ if(!m_selected_item)
+ {
+ for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
+ if(s.listname == "craftpreview")
+ {
+ Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
+ InventoryList *list = inv->getList("craftresult");
+ if(list && list->getSize() >= 1 && !list->getItem(0).empty())
+ {
+ m_selected_item = new ItemSpec;
+ m_selected_item->inventoryloc = s.inventoryloc;
+ m_selected_item->listname = "craftresult";
+ m_selected_item->i = 0;
+ m_selected_amount = 0;
+ m_selected_dragging = false;
+ break;
+ }
+ }
+ }
+ }
+
+ // If craftresult is selected, keep the whole stack selected
+ if(m_selected_item && m_selected_item->listname == "craftresult")
+ {
+ m_selected_amount = verifySelectedItem().count;
+ }
+}
+
+ItemStack GUIFormSpecMenu::verifySelectedItem()
+{
+ // If the selected stack has become empty for some reason, deselect it.
+ // If the selected stack has become inaccessible, deselect it.
+ // If the selected stack has become smaller, adjust m_selected_amount.
+ // Return the selected stack.
+
+ if(m_selected_item)
+ {
+ if(m_selected_item->isValid())
+ {
+ Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
+ if(inv)
+ {
+ InventoryList *list = inv->getList(m_selected_item->listname);
+ if(list && (u32) m_selected_item->i < list->getSize())
+ {
+ ItemStack stack = list->getItem(m_selected_item->i);
+ if(m_selected_amount > stack.count)
+ m_selected_amount = stack.count;
+ if(!stack.empty())
+ return stack;
+ }
+ }
+ }
+
+ // selection was not valid
+ delete m_selected_item;
+ m_selected_item = NULL;
+ m_selected_amount = 0;
+ m_selected_dragging = false;
+ }
+ return ItemStack();
+}
+
+void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
+{
+ if(m_text_dst)
+ {
+ StringMap fields;
+
+ if (quitmode == quit_mode_accept) {
+ fields["quit"] = "true";
+ }
+
+ if (quitmode == quit_mode_cancel) {
+ fields["quit"] = "true";
+ m_text_dst->gotText(fields);
+ return;
+ }
+
+ if (current_keys_pending.key_down) {
+ fields["key_down"] = "true";
+ current_keys_pending.key_down = false;
+ }
+
+ if (current_keys_pending.key_up) {
+ fields["key_up"] = "true";
+ current_keys_pending.key_up = false;
+ }
+
+ if (current_keys_pending.key_enter) {
+ fields["key_enter"] = "true";
+ current_keys_pending.key_enter = false;
+ }
+
+ if (!current_field_enter_pending.empty()) {
+ fields["key_enter_field"] = current_field_enter_pending;
+ current_field_enter_pending = "";
+ }
+
+ if (current_keys_pending.key_escape) {
+ fields["key_escape"] = "true";
+ current_keys_pending.key_escape = false;
+ }
+
+ for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ if(s.send) {
+ std::string name = s.fname;
+ if (s.ftype == f_Button) {
+ fields[name] = wide_to_utf8(s.flabel);
+ } else if (s.ftype == f_Table) {
+ GUITable *table = getTable(s.fname);
+ if (table) {
+ fields[name] = table->checkEvent();
+ }
+ }
+ else if(s.ftype == f_DropDown) {
+ // no dynamic cast possible due to some distributions shipped
+ // without rtti support in irrlicht
+ IGUIElement * element = getElementFromId(s.fid);
+ gui::IGUIComboBox *e = NULL;
+ if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
+ e = static_cast<gui::IGUIComboBox*>(element);
+ }
+ s32 selected = e->getSelected();
+ if (selected >= 0) {
+ std::vector<std::string> *dropdown_values =
+ getDropDownValues(s.fname);
+ if (dropdown_values && selected < (s32)dropdown_values->size()) {
+ fields[name] = (*dropdown_values)[selected];
+ }
+ }
+ }
+ else if (s.ftype == f_TabHeader) {
+ // no dynamic cast possible due to some distributions shipped
+ // without rttzi support in irrlicht
+ IGUIElement * element = getElementFromId(s.fid);
+ gui::IGUITabControl *e = NULL;
+ if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
+ e = static_cast<gui::IGUITabControl *>(element);
+ }
+
+ if (e != 0) {
+ std::stringstream ss;
+ ss << (e->getActiveTab() +1);
+ fields[name] = ss.str();
+ }
+ }
+ else if (s.ftype == f_CheckBox) {
+ // no dynamic cast possible due to some distributions shipped
+ // without rtti support in irrlicht
+ IGUIElement * element = getElementFromId(s.fid);
+ gui::IGUICheckBox *e = NULL;
+ if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
+ e = static_cast<gui::IGUICheckBox*>(element);
+ }
+
+ if (e != 0) {
+ if (e->isChecked())
+ fields[name] = "true";
+ else
+ fields[name] = "false";
+ }
+ }
+ else if (s.ftype == f_ScrollBar) {
+ // no dynamic cast possible due to some distributions shipped
+ // without rtti support in irrlicht
+ IGUIElement * element = getElementFromId(s.fid);
+ gui::IGUIScrollBar *e = NULL;
+ if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) {
+ e = static_cast<gui::IGUIScrollBar*>(element);
+ }
+
+ if (e != 0) {
+ std::stringstream os;
+ os << e->getPos();
+ if (s.fdefault == L"Changed")
+ fields[name] = "CHG:" + os.str();
+ else
+ fields[name] = "VAL:" + os.str();
+ }
+ }
+ else
+ {
+ IGUIElement* e = getElementFromId(s.fid);
+ if(e != NULL) {
+ fields[name] = wide_to_utf8(e->getText());
+ }
+ }
+ }
+ }
+
+ m_text_dst->gotText(fields);
+ }
+}
+
+static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent)
+{
+ while(tocheck != NULL) {
+ if (tocheck == parent) {
+ return true;
+ }
+ tocheck = tocheck->getParent();
+ }
+ return false;
+}
+
+bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
+{
+ // The IGUITabControl renders visually using the skin's selected
+ // font, which we override for the duration of form drawing,
+ // but computes tab hotspots based on how it would have rendered
+ // using the font that is selected at the time of button release.
+ // To make these two consistent, temporarily override the skin's
+ // font while the IGUITabControl is processing the event.
+ if (event.EventType == EET_MOUSE_INPUT_EVENT &&
+ event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
+ s32 x = event.MouseInput.X;
+ s32 y = event.MouseInput.Y;
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(x, y));
+ if (hovered && isMyChild(hovered) &&
+ hovered->getType() == gui::EGUIET_TAB_CONTROL) {
+ gui::IGUISkin* skin = Environment->getSkin();
+ sanity_check(skin != NULL);
+ gui::IGUIFont *old_font = skin->getFont();
+ skin->setFont(m_font);
+ bool retval = hovered->OnEvent(event);
+ skin->setFont(old_font);
+ return retval;
+ }
+ }
+
+ // Fix Esc/Return key being eaten by checkboxen and tables
+ if(event.EventType==EET_KEY_INPUT_EVENT) {
+ KeyPress kp(event.KeyInput);
+ if (kp == EscapeKey || kp == CancelKey
+ || kp == getKeySetting("keymap_inventory")
+ || event.KeyInput.Key==KEY_RETURN) {
+ gui::IGUIElement *focused = Environment->getFocus();
+ if (focused && isMyChild(focused) &&
+ (focused->getType() == gui::EGUIET_LIST_BOX ||
+ focused->getType() == gui::EGUIET_CHECK_BOX)) {
+ OnEvent(event);
+ return true;
+ }
+ }
+ }
+ // Mouse wheel events: send to hovered element instead of focused
+ if(event.EventType==EET_MOUSE_INPUT_EVENT
+ && event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
+ s32 x = event.MouseInput.X;
+ s32 y = event.MouseInput.Y;
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(x, y));
+ if (hovered && isMyChild(hovered)) {
+ hovered->OnEvent(event);
+ return true;
+ }
+ }
+
+ if (event.EventType == EET_MOUSE_INPUT_EVENT) {
+ s32 x = event.MouseInput.X;
+ s32 y = event.MouseInput.Y;
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(x, y));
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ m_old_tooltip_id = -1;
+ }
+ if (!isChild(hovered,this)) {
+ if (DoubleClickDetection(event)) {
+ return true;
+ }
+ }
+ }
+
+ #ifdef __ANDROID__
+ // display software keyboard when clicking edit boxes
+ if (event.EventType == EET_MOUSE_INPUT_EVENT
+ && event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
+ if ((hovered) && (hovered->getType() == irr::gui::EGUIET_EDIT_BOX)) {
+ bool retval = hovered->OnEvent(event);
+ if (retval) {
+ Environment->setFocus(hovered);
+ }
+ m_JavaDialogFieldName = getNameByID(hovered->getID());
+ std::string message = gettext("Enter ");
+ std::string label = wide_to_utf8(getLabelByID(hovered->getID()));
+ if (label == "") {
+ label = "text";
+ }
+ message += gettext(label) + ":";
+
+ /* single line text input */
+ int type = 2;
+
+ /* multi line text input */
+ if (((gui::IGUIEditBox*) hovered)->isMultiLineEnabled()) {
+ type = 1;
+ }
+
+ /* passwords are always single line */
+ if (((gui::IGUIEditBox*) hovered)->isPasswordBox()) {
+ type = 3;
+ }
+
+ porting::showInputDialog(gettext("ok"), "",
+ wide_to_utf8(((gui::IGUIEditBox*) hovered)->getText()),
+ type);
+ return retval;
+ }
+ }
+
+ if (event.EventType == EET_TOUCH_INPUT_EVENT)
+ {
+ SEvent translated;
+ memset(&translated, 0, sizeof(SEvent));
+ translated.EventType = EET_MOUSE_INPUT_EVENT;
+ gui::IGUIElement* root = Environment->getRootGUIElement();
+
+ if (!root) {
+ errorstream
+ << "GUIFormSpecMenu::preprocessEvent unable to get root element"
+ << std::endl;
+ return false;
+ }
+ gui::IGUIElement* hovered = root->getElementFromPoint(
+ core::position2d<s32>(
+ event.TouchInput.X,
+ event.TouchInput.Y));
+
+ translated.MouseInput.X = event.TouchInput.X;
+ translated.MouseInput.Y = event.TouchInput.Y;
+ translated.MouseInput.Control = false;
+
+ bool dont_send_event = false;
+
+ if (event.TouchInput.touchedCount == 1) {
+ switch (event.TouchInput.Event) {
+ case ETIE_PRESSED_DOWN:
+ m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
+ translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT;
+ m_down_pos = m_pointer;
+ break;
+ case ETIE_MOVED:
+ m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
+ translated.MouseInput.Event = EMIE_MOUSE_MOVED;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT;
+ break;
+ case ETIE_LEFT_UP:
+ translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
+ translated.MouseInput.ButtonStates = 0;
+ hovered = root->getElementFromPoint(m_down_pos);
+ /* we don't have a valid pointer element use last
+ * known pointer pos */
+ translated.MouseInput.X = m_pointer.X;
+ translated.MouseInput.Y = m_pointer.Y;
+
+ /* reset down pos */
+ m_down_pos = v2s32(0,0);
+ break;
+ default:
+ dont_send_event = true;
+ //this is not supposed to happen
+ errorstream
+ << "GUIFormSpecMenu::preprocessEvent unexpected usecase Event="
+ << event.TouchInput.Event << std::endl;
+ }
+ } else if ( (event.TouchInput.touchedCount == 2) &&
+ (event.TouchInput.Event == ETIE_PRESSED_DOWN) ) {
+ hovered = root->getElementFromPoint(m_down_pos);
+
+ translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT;
+ translated.MouseInput.X = m_pointer.X;
+ translated.MouseInput.Y = m_pointer.Y;
+
+ if (hovered) {
+ hovered->OnEvent(translated);
+ }
+
+ translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT;
+
+
+ if (hovered) {
+ hovered->OnEvent(translated);
+ }
+ dont_send_event = true;
+ }
+ /* ignore unhandled 2 touch events ... accidental moving for example */
+ else if (event.TouchInput.touchedCount == 2) {
+ dont_send_event = true;
+ }
+ else if (event.TouchInput.touchedCount > 2) {
+ errorstream
+ << "GUIFormSpecMenu::preprocessEvent to many multitouch events "
+ << event.TouchInput.touchedCount << " ignoring them" << std::endl;
+ }
+
+ if (dont_send_event) {
+ return true;
+ }
+
+ /* check if translated event needs to be preprocessed again */
+ if (preprocessEvent(translated)) {
+ return true;
+ }
+ if (hovered) {
+ grab();
+ bool retval = hovered->OnEvent(translated);
+
+ if (event.TouchInput.Event == ETIE_LEFT_UP) {
+ /* reset pointer */
+ m_pointer = v2s32(0,0);
+ }
+ drop();
+ return retval;
+ }
+ }
+ #endif
+
+ if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
+ /* TODO add a check like:
+ if (event.JoystickEvent != joystick_we_listen_for)
+ return false;
+ */
+ bool handled = m_joystick->handleEvent(event.JoystickEvent);
+ if (handled) {
+ if (m_joystick->wasKeyDown(KeyType::ESC)) {
+ tryClose();
+ } else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
+ if (m_allowclose) {
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ }
+ }
+ }
+ return handled;
+ }
+
+ return false;
+}
+
+/******************************************************************************/
+bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event)
+{
+ /* The following code is for capturing double-clicks of the mouse button
+ * and translating the double-click into an EET_KEY_INPUT_EVENT event
+ * -- which closes the form -- under some circumstances.
+ *
+ * There have been many github issues reporting this as a bug even though it
+ * was an intended feature. For this reason, remapping the double-click as
+ * an ESC must be explicitly set when creating this class via the
+ * /p remap_dbl_click parameter of the constructor.
+ */
+
+ if (!m_remap_dbl_click)
+ return false;
+
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos;
+ m_doubleclickdetect[0].time = m_doubleclickdetect[1].time;
+
+ m_doubleclickdetect[1].pos = m_pointer;
+ m_doubleclickdetect[1].time = porting::getTimeMs();
+ }
+ else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
+ u64 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, porting::getTimeMs());
+ if (delta > 400) {
+ return false;
+ }
+
+ double squaredistance =
+ m_doubleclickdetect[0].pos
+ .getDistanceFromSQ(m_doubleclickdetect[1].pos);
+
+ if (squaredistance > (30*30)) {
+ return false;
+ }
+
+ SEvent* translated = new SEvent();
+ assert(translated != 0);
+ //translate doubleclick to escape
+ memset(translated, 0, sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = KEY_ESCAPE;
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.PressedDown = true;
+ translated->KeyInput.Char = 0;
+ OnEvent(*translated);
+
+ // no need to send the key up event as we're already deleted
+ // and no one else did notice this event
+ delete translated;
+ return true;
+ }
+
+ return false;
+}
+
+void GUIFormSpecMenu::tryClose()
+{
+ if (m_allowclose) {
+ doPause = false;
+ acceptInput(quit_mode_cancel);
+ quitMenu();
+ } else {
+ m_text_dst->gotText(L"MenuQuit");
+ }
+}
+
+bool GUIFormSpecMenu::OnEvent(const SEvent& event)
+{
+ if (event.EventType==EET_KEY_INPUT_EVENT) {
+ KeyPress kp(event.KeyInput);
+ if (event.KeyInput.PressedDown && (
+ (kp == EscapeKey) || (kp == CancelKey) ||
+ ((m_client != NULL) && (kp == getKeySetting("keymap_inventory"))))) {
+ tryClose();
+ return true;
+ }
+
+ if (m_client != NULL && event.KeyInput.PressedDown &&
+ (kp == getKeySetting("keymap_screenshot"))) {
+ m_client->makeScreenshot();
+ }
+ if (event.KeyInput.PressedDown &&
+ (event.KeyInput.Key==KEY_RETURN ||
+ event.KeyInput.Key==KEY_UP ||
+ event.KeyInput.Key==KEY_DOWN)
+ ) {
+ switch (event.KeyInput.Key) {
+ case KEY_RETURN:
+ current_keys_pending.key_enter = true;
+ break;
+ case KEY_UP:
+ current_keys_pending.key_up = true;
+ break;
+ case KEY_DOWN:
+ current_keys_pending.key_down = true;
+ break;
+ break;
+ default:
+ //can't happen at all!
+ FATAL_ERROR("Reached a source line that can't ever been reached");
+ break;
+ }
+ if (current_keys_pending.key_enter && m_allowclose) {
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ } else {
+ acceptInput();
+ }
+ return true;
+ }
+
+ }
+
+ /* Mouse event other than movement, or crossing the border of inventory
+ field while holding right mouse button
+ */
+ if (event.EventType == EET_MOUSE_INPUT_EVENT &&
+ (event.MouseInput.Event != EMIE_MOUSE_MOVED ||
+ (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
+ event.MouseInput.isRightPressed() &&
+ getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) {
+
+ // Get selected item and hovered/clicked item (s)
+
+ m_old_tooltip_id = -1;
+ updateSelectedItem();
+ ItemSpec s = getItemAtPos(m_pointer);
+
+ Inventory *inv_selected = NULL;
+ Inventory *inv_s = NULL;
+ InventoryList *list_s = NULL;
+
+ if (m_selected_item) {
+ inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
+ sanity_check(inv_selected);
+ sanity_check(inv_selected->getList(m_selected_item->listname) != NULL);
+ }
+
+ u32 s_count = 0;
+
+ if (s.isValid())
+ do { // breakable
+ inv_s = m_invmgr->getInventory(s.inventoryloc);
+
+ if (!inv_s) {
+ errorstream << "InventoryMenu: The selected inventory location "
+ << "\"" << s.inventoryloc.dump() << "\" doesn't exist"
+ << std::endl;
+ s.i = -1; // make it invalid again
+ break;
+ }
+
+ list_s = inv_s->getList(s.listname);
+ if (list_s == NULL) {
+ verbosestream << "InventoryMenu: The selected inventory list \""
+ << s.listname << "\" does not exist" << std::endl;
+ s.i = -1; // make it invalid again
+ break;
+ }
+
+ if ((u32)s.i >= list_s->getSize()) {
+ infostream << "InventoryMenu: The selected inventory list \""
+ << s.listname << "\" is too small (i=" << s.i << ", size="
+ << list_s->getSize() << ")" << std::endl;
+ s.i = -1; // make it invalid again
+ break;
+ }
+
+ s_count = list_s->getItem(s.i).count;
+ } while(0);
+
+ bool identical = (m_selected_item != NULL) && s.isValid() &&
+ (inv_selected == inv_s) &&
+ (m_selected_item->listname == s.listname) &&
+ (m_selected_item->i == s.i);
+
+ // buttons: 0 = left, 1 = right, 2 = middle
+ // up/down: 0 = down (press), 1 = up (release), 2 = unknown event, -1 movement
+ int button = 0;
+ int updown = 2;
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
+ { button = 0; updown = 0; }
+ else if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
+ { button = 1; updown = 0; }
+ else if (event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
+ { button = 2; updown = 0; }
+ else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
+ { button = 0; updown = 1; }
+ else if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
+ { button = 1; updown = 1; }
+ else if (event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP)
+ { button = 2; updown = 1; }
+ else if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
+ { updown = -1;}
+
+ // Set this number to a positive value to generate a move action
+ // from m_selected_item to s.
+ u32 move_amount = 0;
+
+ // Set this number to a positive value to generate a move action
+ // from s to the next inventory ring.
+ u32 shift_move_amount = 0;
+
+ // Set this number to a positive value to generate a drop action
+ // from m_selected_item.
+ u32 drop_amount = 0;
+
+ // Set this number to a positive value to generate a craft action at s.
+ u32 craft_amount = 0;
+
+ if (updown == 0) {
+ // Some mouse button has been pressed
+
+ //infostream<<"Mouse button "<<button<<" pressed at p=("
+ // <<p.X<<","<<p.Y<<")"<<std::endl;
+
+ m_selected_dragging = false;
+
+ if (s.isValid() && s.listname == "craftpreview") {
+ // Craft preview has been clicked: craft
+ craft_amount = (button == 2 ? 10 : 1);
+ } else if (m_selected_item == NULL) {
+ if (s_count != 0) {
+ // Non-empty stack has been clicked: select or shift-move it
+ m_selected_item = new ItemSpec(s);
+
+ u32 count;
+ if (button == 1) // right
+ count = (s_count + 1) / 2;
+ else if (button == 2) // middle
+ count = MYMIN(s_count, 10);
+ else // left
+ count = s_count;
+
+ if (!event.MouseInput.Shift) {
+ // no shift: select item
+ m_selected_amount = count;
+ m_selected_dragging = true;
+ m_auto_place = false;
+ } else {
+ // shift pressed: move item
+ if (button != 1)
+ shift_move_amount = count;
+ else // count of 1 at left click like after drag & drop
+ shift_move_amount = 1;
+ }
+ }
+ } else { // m_selected_item != NULL
+ assert(m_selected_amount >= 1);
+
+ if (s.isValid()) {
+ // Clicked a slot: move
+ if (button == 1) // right
+ move_amount = 1;
+ else if (button == 2) // middle
+ move_amount = MYMIN(m_selected_amount, 10);
+ else // left
+ move_amount = m_selected_amount;
+
+ if (identical) {
+ if (move_amount >= m_selected_amount)
+ m_selected_amount = 0;
+ else
+ m_selected_amount -= move_amount;
+ move_amount = 0;
+ }
+ }
+ else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) {
+ // Clicked outside of the window: drop
+ if (button == 1) // right
+ drop_amount = 1;
+ else if (button == 2) // middle
+ drop_amount = MYMIN(m_selected_amount, 10);
+ else // left
+ drop_amount = m_selected_amount;
+ }
+ }
+ }
+ else if (updown == 1) {
+ // Some mouse button has been released
+
+ //infostream<<"Mouse button "<<button<<" released at p=("
+ // <<p.X<<","<<p.Y<<")"<<std::endl;
+
+ if (m_selected_item != NULL && m_selected_dragging && s.isValid()) {
+ if (!identical) {
+ // Dragged to different slot: move all selected
+ move_amount = m_selected_amount;
+ }
+ } else if (m_selected_item != NULL && m_selected_dragging &&
+ !(getAbsoluteClippingRect().isPointInside(m_pointer))) {
+ // Dragged outside of window: drop all selected
+ drop_amount = m_selected_amount;
+ }
+
+ m_selected_dragging = false;
+ // Keep track of whether the mouse button be released
+ // One click is drag without dropping. Click + release
+ // + click changes to drop item when moved mode
+ if (m_selected_item)
+ m_auto_place = true;
+ } else if (updown == -1) {
+ // Mouse has been moved and rmb is down and mouse pointer just
+ // entered a new inventory field (checked in the entry-if, this
+ // is the only action here that is generated by mouse movement)
+ if (m_selected_item != NULL && s.isValid()) {
+ // Move 1 item
+ // TODO: middle mouse to move 10 items might be handy
+ if (m_auto_place) {
+ // Only move an item if the destination slot is empty
+ // or contains the same item type as what is going to be
+ // moved
+ InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
+ InventoryList *list_to = list_s;
+ assert(list_from && list_to);
+ ItemStack stack_from = list_from->getItem(m_selected_item->i);
+ ItemStack stack_to = list_to->getItem(s.i);
+ if (stack_to.empty() || stack_to.name == stack_from.name)
+ move_amount = 1;
+ }
+ }
+ }
+
+ // Possibly send inventory action to server
+ if (move_amount > 0) {
+ // Send IAction::Move
+
+ assert(m_selected_item && m_selected_item->isValid());
+ assert(s.isValid());
+
+ assert(inv_selected && inv_s);
+ InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
+ InventoryList *list_to = list_s;
+ assert(list_from && list_to);
+ ItemStack stack_from = list_from->getItem(m_selected_item->i);
+ ItemStack stack_to = list_to->getItem(s.i);
+
+ // Check how many items can be moved
+ move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
+ ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
+ // If source stack cannot be added to destination stack at all,
+ // they are swapped
+ if ((leftover.count == stack_from.count) &&
+ (leftover.name == stack_from.name)) {
+ m_selected_amount = stack_to.count;
+ // In case the server doesn't directly swap them but instead
+ // moves stack_to somewhere else, set this
+ m_selected_content_guess = stack_to;
+ m_selected_content_guess_inventory = s.inventoryloc;
+ }
+ // Source stack goes fully into destination stack
+ else if (leftover.empty()) {
+ m_selected_amount -= move_amount;
+ m_selected_content_guess = ItemStack(); // Clear
+ }
+ // Source stack goes partly into destination stack
+ else {
+ move_amount -= leftover.count;
+ m_selected_amount -= move_amount;
+ m_selected_content_guess = ItemStack(); // Clear
+ }
+
+ infostream << "Handing IAction::Move to manager" << std::endl;
+ IMoveAction *a = new IMoveAction();
+ a->count = move_amount;
+ a->from_inv = m_selected_item->inventoryloc;
+ a->from_list = m_selected_item->listname;
+ a->from_i = m_selected_item->i;
+ a->to_inv = s.inventoryloc;
+ a->to_list = s.listname;
+ a->to_i = s.i;
+ m_invmgr->inventoryAction(a);
+ } else if (shift_move_amount > 0) {
+ u32 mis = m_inventory_rings.size();
+ u32 i = 0;
+ for (; i < mis; i++) {
+ const ListRingSpec &sp = m_inventory_rings[i];
+ if (sp.inventoryloc == s.inventoryloc
+ && sp.listname == s.listname)
+ break;
+ }
+ do {
+ if (i >= mis) // if not found
+ break;
+ u32 to_inv_ind = (i + 1) % mis;
+ const ListRingSpec &to_inv_sp = m_inventory_rings[to_inv_ind];
+ InventoryList *list_from = list_s;
+ if (!s.isValid())
+ break;
+ Inventory *inv_to = m_invmgr->getInventory(to_inv_sp.inventoryloc);
+ if (!inv_to)
+ break;
+ InventoryList *list_to = inv_to->getList(to_inv_sp.listname);
+ if (!list_to)
+ break;
+ ItemStack stack_from = list_from->getItem(s.i);
+ assert(shift_move_amount <= stack_from.count);
+ if (m_client->getProtoVersion() >= 25) {
+ infostream << "Handing IAction::Move to manager" << std::endl;
+ IMoveAction *a = new IMoveAction();
+ a->count = shift_move_amount;
+ a->from_inv = s.inventoryloc;
+ a->from_list = s.listname;
+ a->from_i = s.i;
+ a->to_inv = to_inv_sp.inventoryloc;
+ a->to_list = to_inv_sp.listname;
+ a->move_somewhere = true;
+ m_invmgr->inventoryAction(a);
+ } else {
+ // find a place (or more than one) to add the new item
+ u32 ilt_size = list_to->getSize();
+ ItemStack leftover;
+ for (u32 slot_to = 0; slot_to < ilt_size
+ && shift_move_amount > 0; slot_to++) {
+ list_to->itemFits(slot_to, stack_from, &leftover);
+ if (leftover.count < stack_from.count) {
+ infostream << "Handing IAction::Move to manager" << std::endl;
+ IMoveAction *a = new IMoveAction();
+ a->count = MYMIN(shift_move_amount,
+ (u32) (stack_from.count - leftover.count));
+ shift_move_amount -= a->count;
+ a->from_inv = s.inventoryloc;
+ a->from_list = s.listname;
+ a->from_i = s.i;
+ a->to_inv = to_inv_sp.inventoryloc;
+ a->to_list = to_inv_sp.listname;
+ a->to_i = slot_to;
+ m_invmgr->inventoryAction(a);
+ stack_from = leftover;
+ }
+ }
+ }
+ } while (0);
+ } else if (drop_amount > 0) {
+ m_selected_content_guess = ItemStack(); // Clear
+
+ // Send IAction::Drop
+
+ assert(m_selected_item && m_selected_item->isValid());
+ assert(inv_selected);
+ InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
+ assert(list_from);
+ ItemStack stack_from = list_from->getItem(m_selected_item->i);
+
+ // Check how many items can be dropped
+ drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
+ assert(drop_amount > 0 && drop_amount <= m_selected_amount);
+ m_selected_amount -= drop_amount;
+
+ infostream << "Handing IAction::Drop to manager" << std::endl;
+ IDropAction *a = new IDropAction();
+ a->count = drop_amount;
+ a->from_inv = m_selected_item->inventoryloc;
+ a->from_list = m_selected_item->listname;
+ a->from_i = m_selected_item->i;
+ m_invmgr->inventoryAction(a);
+ } else if (craft_amount > 0) {
+ m_selected_content_guess = ItemStack(); // Clear
+
+ // Send IAction::Craft
+
+ assert(s.isValid());
+ assert(inv_s);
+
+ infostream << "Handing IAction::Craft to manager" << std::endl;
+ ICraftAction *a = new ICraftAction();
+ a->count = craft_amount;
+ a->craft_inv = s.inventoryloc;
+ m_invmgr->inventoryAction(a);
+ }
+
+ // If m_selected_amount has been decreased to zero, deselect
+ if (m_selected_amount == 0) {
+ delete m_selected_item;
+ m_selected_item = NULL;
+ m_selected_amount = 0;
+ m_selected_dragging = false;
+ m_selected_content_guess = ItemStack();
+ }
+ m_old_pointer = m_pointer;
+ }
+ if (event.EventType == EET_GUI_EVENT) {
+
+ if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
+ && isVisible()) {
+ // find the element that was clicked
+ for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ if ((s.ftype == f_TabHeader) &&
+ (s.fid == event.GUIEvent.Caller->getID())) {
+ s.send = true;
+ acceptInput();
+ s.send = false;
+ return true;
+ }
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
+ && isVisible()) {
+ if (!canTakeFocus(event.GUIEvent.Element)) {
+ infostream<<"GUIFormSpecMenu: Not allowing focus change."
+ <<std::endl;
+ // Returning true disables focus change
+ return true;
+ }
+ }
+ if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
+ (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
+ (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
+ (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
+ unsigned int btn_id = event.GUIEvent.Caller->getID();
+
+ if (btn_id == 257) {
+ if (m_allowclose) {
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ } else {
+ acceptInput();
+ m_text_dst->gotText(L"ExitButton");
+ }
+ // quitMenu deallocates menu
+ return true;
+ }
+
+ // find the element that was clicked
+ for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ // if its a button, set the send field so
+ // lua knows which button was pressed
+ if ((s.ftype == f_Button || s.ftype == f_CheckBox) &&
+ s.fid == event.GUIEvent.Caller->getID()) {
+ s.send = true;
+ if (s.is_exit) {
+ if (m_allowclose) {
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ } else {
+ m_text_dst->gotText(L"ExitButton");
+ }
+ return true;
+ }
+
+ acceptInput(quit_mode_no);
+ s.send = false;
+ return true;
+
+ } else if ((s.ftype == f_DropDown) &&
+ (s.fid == event.GUIEvent.Caller->getID())) {
+ // only send the changed dropdown
+ for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
+ if (s2.ftype == f_DropDown) {
+ s2.send = false;
+ }
+ }
+ s.send = true;
+ acceptInput(quit_mode_no);
+
+ // revert configuration to make sure dropdowns are sent on
+ // regular button click
+ for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
+ if (s2.ftype == f_DropDown) {
+ s2.send = true;
+ }
+ }
+ return true;
+ } else if ((s.ftype == f_ScrollBar) &&
+ (s.fid == event.GUIEvent.Caller->getID())) {
+ s.fdefault = L"Changed";
+ acceptInput(quit_mode_no);
+ s.fdefault = L"";
+ }
+ }
+ }
+
+ if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
+ if (event.GUIEvent.Caller->getID() > 257) {
+ bool close_on_enter = true;
+ for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ if (s.ftype == f_Unknown &&
+ s.fid == event.GUIEvent.Caller->getID()) {
+ current_field_enter_pending = s.fname;
+ std::unordered_map<std::string, bool>::const_iterator it =
+ field_close_on_enter.find(s.fname);
+ if (it != field_close_on_enter.end())
+ close_on_enter = (*it).second;
+
+ break;
+ }
+ }
+
+ if (m_allowclose && close_on_enter) {
+ current_keys_pending.key_enter = true;
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ } else {
+ current_keys_pending.key_enter = true;
+ acceptInput();
+ }
+ // quitMenu deallocates menu
+ return true;
+ }
+ }
+
+ if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
+ int current_id = event.GUIEvent.Caller->getID();
+ if (current_id > 257) {
+ // find the element that was clicked
+ for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ // if it's a table, set the send field
+ // so lua knows which table was changed
+ if ((s.ftype == f_Table) && (s.fid == current_id)) {
+ s.send = true;
+ acceptInput();
+ s.send=false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
+/**
+ * get name of element by element id
+ * @param id of element
+ * @return name string or empty string
+ */
+std::string GUIFormSpecMenu::getNameByID(s32 id)
+{
+ for (FieldSpec &spec : m_fields) {
+ if (spec.fid == id) {
+ return spec.fname;
+ }
+ }
+ return "";
+}
+
+/**
+ * get label of element by id
+ * @param id of element
+ * @return label string or empty string
+ */
+std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
+{
+ for (FieldSpec &spec : m_fields) {
+ if (spec.fid == id) {
+ return spec.flabel;
+ }
+ }
+ return L"";
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <utility>
+#include <stack>
+
+#include "irrlichttypes_extrabloated.h"
+#include "inventorymanager.h"
+#include "modalMenu.h"
+#include "guiTable.h"
+#include "network/networkprotocol.h"
+#include "client/joystick_controller.h"
+#include "util/string.h"
+#include "util/enriched_string.h"
+
+class InventoryManager;
+class ISimpleTextureSource;
+class Client;
+
+typedef enum {
+ f_Button,
+ f_Table,
+ f_TabHeader,
+ f_CheckBox,
+ f_DropDown,
+ f_ScrollBar,
+ f_Unknown
+} FormspecFieldType;
+
+typedef enum {
+ quit_mode_no,
+ quit_mode_accept,
+ quit_mode_cancel
+} FormspecQuitMode;
+
+struct TextDest
+{
+ virtual ~TextDest() = default;
+
+ // This is deprecated I guess? -celeron55
+ virtual void gotText(const std::wstring &text) {}
+ virtual void gotText(const StringMap &fields) = 0;
+
+ std::string m_formname;
+};
+
+class IFormSource
+{
+public:
+ virtual ~IFormSource() = default;
+ virtual const std::string &getForm() const = 0;
+ // Fill in variables in field text
+ virtual std::string resolveText(const std::string &str) { return str; }
+};
+
+class GUIFormSpecMenu : public GUIModalMenu
+{
+ struct ItemSpec
+ {
+ ItemSpec() = default;
+
+ ItemSpec(const InventoryLocation &a_inventoryloc,
+ const std::string &a_listname,
+ s32 a_i) :
+ inventoryloc(a_inventoryloc),
+ listname(a_listname),
+ i(a_i)
+ {
+ }
+
+ bool isValid() const { return i != -1; }
+
+ InventoryLocation inventoryloc;
+ std::string listname;
+ s32 i = -1;
+ };
+
+ struct ListDrawSpec
+ {
+ ListDrawSpec() = default;
+
+ ListDrawSpec(const InventoryLocation &a_inventoryloc,
+ const std::string &a_listname,
+ v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i):
+ inventoryloc(a_inventoryloc),
+ listname(a_listname),
+ pos(a_pos),
+ geom(a_geom),
+ start_item_i(a_start_item_i)
+ {
+ }
+
+ InventoryLocation inventoryloc;
+ std::string listname;
+ v2s32 pos;
+ v2s32 geom;
+ s32 start_item_i;
+ };
+
+ struct ListRingSpec
+ {
+ ListRingSpec() = default;
+
+ ListRingSpec(const InventoryLocation &a_inventoryloc,
+ const std::string &a_listname):
+ inventoryloc(a_inventoryloc),
+ listname(a_listname)
+ {
+ }
+
+ InventoryLocation inventoryloc;
+ std::string listname;
+ };
+
+ struct ImageDrawSpec
+ {
+ ImageDrawSpec():
+ parent_button(NULL),
+ clip(false)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
+ const std::string &a_item_name,
+ gui::IGUIButton *a_parent_button,
+ const v2s32 &a_pos, const v2s32 &a_geom):
+ name(a_name),
+ item_name(a_item_name),
+ parent_button(a_parent_button),
+ pos(a_pos),
+ geom(a_geom),
+ scale(true),
+ clip(false)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
+ const std::string &a_item_name,
+ const v2s32 &a_pos, const v2s32 &a_geom):
+ name(a_name),
+ item_name(a_item_name),
+ parent_button(NULL),
+ pos(a_pos),
+ geom(a_geom),
+ scale(true),
+ clip(false)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
+ const v2s32 &a_pos, const v2s32 &a_geom, bool clip=false):
+ name(a_name),
+ parent_button(NULL),
+ pos(a_pos),
+ geom(a_geom),
+ scale(true),
+ clip(clip)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
+ const v2s32 &a_pos):
+ name(a_name),
+ parent_button(NULL),
+ pos(a_pos),
+ scale(false),
+ clip(false)
+ {
+ }
+
+ std::string name;
+ std::string item_name;
+ gui::IGUIButton *parent_button;
+ v2s32 pos;
+ v2s32 geom;
+ bool scale;
+ bool clip;
+ };
+
+ struct FieldSpec
+ {
+ FieldSpec() = default;
+
+ FieldSpec(const std::string &name, const std::wstring &label,
+ const std::wstring &default_text, int id) :
+ fname(name),
+ flabel(label),
+ fdefault(unescape_enriched(translate_string(default_text))),
+ fid(id),
+ send(false),
+ ftype(f_Unknown),
+ is_exit(false)
+ {
+ }
+
+ std::string fname;
+ std::wstring flabel;
+ std::wstring fdefault;
+ int fid;
+ bool send;
+ FormspecFieldType ftype;
+ bool is_exit;
+ core::rect<s32> rect;
+ };
+
+ struct BoxDrawSpec
+ {
+ BoxDrawSpec(v2s32 a_pos, v2s32 a_geom,irr::video::SColor a_color):
+ pos(a_pos),
+ geom(a_geom),
+ color(a_color)
+ {
+ }
+ v2s32 pos;
+ v2s32 geom;
+ irr::video::SColor color;
+ };
+
+ struct TooltipSpec
+ {
+ TooltipSpec() = default;
+ TooltipSpec(const std::wstring &a_tooltip, irr::video::SColor a_bgcolor,
+ irr::video::SColor a_color):
+ tooltip(translate_string(a_tooltip)),
+ bgcolor(a_bgcolor),
+ color(a_color)
+ {
+ }
+
+ std::wstring tooltip;
+ irr::video::SColor bgcolor;
+ irr::video::SColor color;
+ };
+
+ struct StaticTextSpec
+ {
+ StaticTextSpec():
+ parent_button(NULL)
+ {
+ }
+
+ StaticTextSpec(const std::wstring &a_text,
+ const core::rect<s32> &a_rect):
+ text(a_text),
+ rect(a_rect),
+ parent_button(NULL)
+ {
+ }
+
+ StaticTextSpec(const std::wstring &a_text,
+ const core::rect<s32> &a_rect,
+ gui::IGUIButton *a_parent_button):
+ text(a_text),
+ rect(a_rect),
+ parent_button(a_parent_button)
+ {
+ }
+
+ std::wstring text;
+ core::rect<s32> rect;
+ gui::IGUIButton *parent_button;
+ };
+
+public:
+ GUIFormSpecMenu(JoystickController *joystick,
+ gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr,
+ Client *client,
+ ISimpleTextureSource *tsrc,
+ IFormSource* fs_src,
+ TextDest* txt_dst,
+ bool remap_dbl_click = true);
+
+ ~GUIFormSpecMenu();
+
+ void setFormSpec(const std::string &formspec_string,
+ const InventoryLocation ¤t_inventory_location)
+ {
+ m_formspec_string = formspec_string;
+ m_current_inventory_location = current_inventory_location;
+ regenerateGui(m_screensize_old);
+ }
+
+ // form_src is deleted by this GUIFormSpecMenu
+ void setFormSource(IFormSource *form_src)
+ {
+ delete m_form_src;
+ m_form_src = form_src;
+ }
+
+ // text_dst is deleted by this GUIFormSpecMenu
+ void setTextDest(TextDest *text_dst)
+ {
+ delete m_text_dst;
+ m_text_dst = text_dst;
+ }
+
+ void allowClose(bool value)
+ {
+ m_allowclose = value;
+ }
+
+ void lockSize(bool lock,v2u32 basescreensize=v2u32(0,0))
+ {
+ m_lock = lock;
+ m_lockscreensize = basescreensize;
+ }
+
+ void removeChildren();
+ void setInitialFocus();
+
+ void setFocus(const std::string &elementname)
+ {
+ m_focused_element = elementname;
+ }
+
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ ItemSpec getItemAtPos(v2s32 p) const;
+ void drawList(const ListDrawSpec &s, int phase, bool &item_hovered);
+ void drawSelectedItem();
+ void drawMenu();
+ void updateSelectedItem();
+ ItemStack verifySelectedItem();
+
+ void acceptInput(FormspecQuitMode quitmode);
+ bool preprocessEvent(const SEvent& event);
+ bool OnEvent(const SEvent& event);
+ bool doPause;
+ bool pausesGame() { return doPause; }
+
+ GUITable* getTable(const std::string &tablename);
+ std::vector<std::string>* getDropDownValues(const std::string &name);
+
+#ifdef __ANDROID__
+ bool getAndroidUIInput();
+#endif
+
+protected:
+ v2s32 getBasePos() const
+ {
+ return padding + offset + AbsoluteRect.UpperLeftCorner;
+ }
+
+ v2s32 padding;
+ v2s32 spacing;
+ v2s32 imgsize;
+ v2s32 offset;
+ v2s32 pos_offset;
+ std::stack<v2s32> container_stack;
+
+ InventoryManager *m_invmgr;
+ ISimpleTextureSource *m_tsrc;
+ Client *m_client;
+
+ std::string m_formspec_string;
+ InventoryLocation m_current_inventory_location;
+
+ std::vector<ListDrawSpec> m_inventorylists;
+ std::vector<ListRingSpec> m_inventory_rings;
+ std::vector<ImageDrawSpec> m_backgrounds;
+ std::vector<ImageDrawSpec> m_images;
+ std::vector<ImageDrawSpec> m_itemimages;
+ std::vector<BoxDrawSpec> m_boxes;
+ std::unordered_map<std::string, bool> field_close_on_enter;
+ std::vector<FieldSpec> m_fields;
+ std::vector<StaticTextSpec> m_static_texts;
+ std::vector<std::pair<FieldSpec,GUITable*> > m_tables;
+ std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes;
+ std::map<std::string, TooltipSpec> m_tooltips;
+ std::vector<std::pair<FieldSpec,gui::IGUIScrollBar*> > m_scrollbars;
+ std::vector<std::pair<FieldSpec, std::vector<std::string> > > m_dropdowns;
+
+ ItemSpec *m_selected_item = nullptr;
+ u32 m_selected_amount = 0;
+ bool m_selected_dragging = false;
+
+ // WARNING: BLACK MAGIC
+ // Used to guess and keep up with some special things the server can do.
+ // If name is "", no guess exists.
+ ItemStack m_selected_content_guess;
+ InventoryLocation m_selected_content_guess_inventory;
+
+ v2s32 m_pointer;
+ v2s32 m_old_pointer; // Mouse position after previous mouse event
+ gui::IGUIStaticText *m_tooltip_element = nullptr;
+
+ u64 m_tooltip_show_delay;
+ bool m_tooltip_append_itemname;
+ u64 m_hovered_time = 0;
+ s32 m_old_tooltip_id = -1;
+
+ bool m_auto_place = false;
+
+ bool m_allowclose = true;
+ bool m_lock = false;
+ v2u32 m_lockscreensize;
+
+ bool m_bgfullscreen;
+ bool m_slotborder;
+ video::SColor m_bgcolor;
+ video::SColor m_fullscreen_bgcolor;
+ video::SColor m_slotbg_n;
+ video::SColor m_slotbg_h;
+ video::SColor m_slotbordercolor;
+ video::SColor m_default_tooltip_bgcolor;
+ video::SColor m_default_tooltip_color;
+
+private:
+ IFormSource *m_form_src;
+ TextDest *m_text_dst;
+ u32 m_formspec_version = 0;
+ std::string m_focused_element = "";
+ JoystickController *m_joystick;
+
+ typedef struct {
+ bool explicit_size;
+ v2f invsize;
+ v2s32 size;
+ v2f32 offset;
+ v2f32 anchor;
+ core::rect<s32> rect;
+ v2s32 basepos;
+ v2u32 screensize;
+ std::string focused_fieldname;
+ GUITable::TableOptions table_options;
+ GUITable::TableColumns table_columns;
+ // used to restore table selection/scroll/treeview state
+ std::unordered_map<std::string, GUITable::DynamicData> table_dyndata;
+ } parserData;
+
+ typedef struct {
+ bool key_up;
+ bool key_down;
+ bool key_enter;
+ bool key_escape;
+ } fs_key_pendig;
+
+ fs_key_pendig current_keys_pending;
+ std::string current_field_enter_pending = "";
+
+ void parseElement(parserData* data, const std::string &element);
+
+ void parseSize(parserData* data, const std::string &element);
+ void parseContainer(parserData* data, const std::string &element);
+ void parseContainerEnd(parserData* data);
+ void parseList(parserData* data, const std::string &element);
+ void parseListRing(parserData* data, const std::string &element);
+ void parseCheckbox(parserData* data, const std::string &element);
+ void parseImage(parserData* data, const std::string &element);
+ void parseItemImage(parserData* data, const std::string &element);
+ void parseButton(parserData* data, const std::string &element,
+ const std::string &typ);
+ void parseBackground(parserData* data, const std::string &element);
+ void parseTableOptions(parserData* data, const std::string &element);
+ void parseTableColumns(parserData* data, const std::string &element);
+ void parseTable(parserData* data, const std::string &element);
+ void parseTextList(parserData* data, const std::string &element);
+ void parseDropDown(parserData* data, const std::string &element);
+ void parseFieldCloseOnEnter(parserData *data, const std::string &element);
+ void parsePwdField(parserData* data, const std::string &element);
+ void parseField(parserData* data, const std::string &element, const std::string &type);
+ void parseSimpleField(parserData* data,std::vector<std::string> &parts);
+ void parseTextArea(parserData* data,std::vector<std::string>& parts,
+ const std::string &type);
+ void parseLabel(parserData* data, const std::string &element);
+ void parseVertLabel(parserData* data, const std::string &element);
+ void parseImageButton(parserData* data, const std::string &element,
+ const std::string &type);
+ void parseItemImageButton(parserData* data, const std::string &element);
+ void parseTabHeader(parserData* data, const std::string &element);
+ void parseBox(parserData* data, const std::string &element);
+ void parseBackgroundColor(parserData* data, const std::string &element);
+ void parseListColors(parserData* data, const std::string &element);
+ void parseTooltip(parserData* data, const std::string &element);
+ bool parseVersionDirect(const std::string &data);
+ bool parseSizeDirect(parserData* data, const std::string &element);
+ void parseScrollBar(parserData* data, const std::string &element);
+ bool parsePositionDirect(parserData *data, const std::string &element);
+ void parsePosition(parserData *data, const std::string &element);
+ bool parseAnchorDirect(parserData *data, const std::string &element);
+ void parseAnchor(parserData *data, const std::string &element);
+
+ void tryClose();
+
+ void showTooltip(const std::wstring &text, const irr::video::SColor &color,
+ const irr::video::SColor &bgcolor);
+
+ /**
+ * check if event is part of a double click
+ * @param event event to evaluate
+ * @return true/false if a doubleclick was detected
+ */
+ bool DoubleClickDetection(const SEvent event);
+
+ struct clickpos
+ {
+ v2s32 pos;
+ s64 time;
+ };
+ clickpos m_doubleclickdetect[2];
+
+ int m_btn_height;
+ gui::IGUIFont *m_font = nullptr;
+
+ std::wstring getLabelByID(s32 id);
+ std::string getNameByID(s32 id);
+#ifdef __ANDROID__
+ v2s32 m_down_pos;
+ std::string m_JavaDialogFieldName;
+#endif
+
+ /* If true, remap a double-click (or double-tap) action to ESC. This is so
+ * that, for example, Android users can double-tap to close a formspec.
+ *
+ * This value can (currently) only be set by the class constructor
+ * and the default value for the setting is true.
+ */
+ bool m_remap_dbl_click;
+
+};
+
+class FormspecFormSource: public IFormSource
+{
+public:
+ FormspecFormSource(const std::string &formspec):
+ m_formspec(formspec)
+ {
+ }
+
+ ~FormspecFormSource() = default;
+
+ void setForm(const std::string &formspec)
+ {
+ m_formspec = FORMSPEC_VERSION_STRING + formspec;
+ }
+
+ const std::string &getForm() const
+ {
+ return m_formspec;
+ }
+
+ std::string m_formspec;
+};
--- /dev/null
+/*
+ Minetest
+ Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+ Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+ Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "guiKeyChangeMenu.h"
+#include "debug.h"
+#include "serialization.h"
+#include <string>
+#include <IGUICheckBox.h>
+#include <IGUIEditBox.h>
+#include <IGUIButton.h>
+#include <IGUIStaticText.h>
+#include <IGUIFont.h>
+#include "settings.h"
+#include <algorithm>
+
+#include "mainmenumanager.h" // for g_gamecallback
+
+#define KMaxButtonPerColumns 12
+
+extern MainGameCallback *g_gamecallback;
+
+enum
+{
+ GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR,
+ // buttons
+ GUI_ID_KEY_FORWARD_BUTTON,
+ GUI_ID_KEY_BACKWARD_BUTTON,
+ GUI_ID_KEY_LEFT_BUTTON,
+ GUI_ID_KEY_RIGHT_BUTTON,
+ GUI_ID_KEY_USE_BUTTON,
+ GUI_ID_KEY_FLY_BUTTON,
+ GUI_ID_KEY_FAST_BUTTON,
+ GUI_ID_KEY_JUMP_BUTTON,
+ GUI_ID_KEY_NOCLIP_BUTTON,
+ GUI_ID_KEY_CINEMATIC_BUTTON,
+ GUI_ID_KEY_CHAT_BUTTON,
+ GUI_ID_KEY_CMD_BUTTON,
+ GUI_ID_KEY_CMD_LOCAL_BUTTON,
+ GUI_ID_KEY_CONSOLE_BUTTON,
+ GUI_ID_KEY_SNEAK_BUTTON,
+ GUI_ID_KEY_DROP_BUTTON,
+ GUI_ID_KEY_INVENTORY_BUTTON,
+ GUI_ID_KEY_HOTBAR_PREV_BUTTON,
+ GUI_ID_KEY_HOTBAR_NEXT_BUTTON,
+ GUI_ID_KEY_MUTE_BUTTON,
+ GUI_ID_KEY_DEC_VOLUME_BUTTON,
+ GUI_ID_KEY_INC_VOLUME_BUTTON,
+ GUI_ID_KEY_RANGE_BUTTON,
+ GUI_ID_KEY_ZOOM_BUTTON,
+ GUI_ID_KEY_CAMERA_BUTTON,
+ GUI_ID_KEY_MINIMAP_BUTTON,
+ GUI_ID_KEY_SCREENSHOT_BUTTON,
+ GUI_ID_KEY_CHATLOG_BUTTON,
+ GUI_ID_KEY_HUD_BUTTON,
+ GUI_ID_KEY_FOG_BUTTON,
+ GUI_ID_KEY_DEC_RANGE_BUTTON,
+ GUI_ID_KEY_INC_RANGE_BUTTON,
+ GUI_ID_KEY_AUTOFWD_BUTTON,
+ // other
+ GUI_ID_CB_AUX1_DESCENDS,
+ GUI_ID_CB_DOUBLETAP_JUMP,
+};
+
+GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) :
+GUIModalMenu(env, parent, id, menumgr)
+{
+ init_keys();
+ for (key_setting *ks : key_settings)
+ key_used.push_back(ks->key);
+}
+
+GUIKeyChangeMenu::~GUIKeyChangeMenu()
+{
+ removeChildren();
+
+ for (key_setting *ks : key_settings) {
+ delete[] ks->button_name;
+ delete ks;
+ }
+ key_settings.clear();
+}
+
+void GUIKeyChangeMenu::removeChildren()
+{
+ const core::list<gui::IGUIElement*> &children = getChildren();
+ core::list<gui::IGUIElement*> children_copy;
+ for (gui::IGUIElement*i : children) {
+ children_copy.push_back(i);
+ }
+
+ for (gui::IGUIElement *i : children_copy) {
+ i->remove();
+ }
+}
+
+void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
+{
+ removeChildren();
+ v2s32 size(745, 430);
+
+ core::rect < s32 > rect(screensize.X / 2 - size.X / 2,
+ screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2,
+ screensize.Y / 2 + size.Y / 2);
+
+ DesiredRect = rect;
+ recalculateAbsolutePosition(false);
+
+ v2s32 topleft(0, 0);
+
+ {
+ core::rect < s32 > rect(0, 0, 600, 40);
+ rect += topleft + v2s32(25, 3);
+ //gui::IGUIStaticText *t =
+ const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)");
+ Environment->addStaticText(text,
+ rect, false, true, this, -1);
+ delete[] text;
+ //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
+ }
+
+ // Build buttons
+
+ v2s32 offset(25, 60);
+
+ for(size_t i = 0; i < key_settings.size(); i++)
+ {
+ key_setting *k = key_settings.at(i);
+ {
+ core::rect < s32 > rect(0, 0, 150, 20);
+ rect += topleft + v2s32(offset.X, offset.Y);
+ Environment->addStaticText(k->button_name, rect, false, true, this, -1);
+ }
+
+ {
+ core::rect < s32 > rect(0, 0, 100, 30);
+ rect += topleft + v2s32(offset.X + 120, offset.Y - 5);
+ const wchar_t *text = wgettext(k->key.name());
+ k->button = Environment->addButton(rect, this, k->id, text);
+ delete[] text;
+ }
+ if ((i + 1) % KMaxButtonPerColumns == 0) {
+ offset.X += 230;
+ offset.Y = 60;
+ } else {
+ offset += v2s32(0, 25);
+ }
+ }
+
+ {
+ s32 option_x = offset.X;
+ s32 option_y = offset.Y + 5;
+ u32 option_w = 180;
+ {
+ core::rect<s32> rect(0, 0, option_w, 30);
+ rect += topleft + v2s32(option_x, option_y);
+ const wchar_t *text = wgettext("\"Special\" = climb down");
+ Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this,
+ GUI_ID_CB_AUX1_DESCENDS, text);
+ delete[] text;
+ }
+ offset += v2s32(0, 25);
+ }
+
+ {
+ s32 option_x = offset.X;
+ s32 option_y = offset.Y + 5;
+ u32 option_w = 280;
+ {
+ core::rect<s32> rect(0, 0, option_w, 30);
+ rect += topleft + v2s32(option_x, option_y);
+ const wchar_t *text = wgettext("Double tap \"jump\" to toggle fly");
+ Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this,
+ GUI_ID_CB_DOUBLETAP_JUMP, text);
+ delete[] text;
+ }
+ offset += v2s32(0, 25);
+ }
+
+ {
+ core::rect < s32 > rect(0, 0, 100, 30);
+ rect += topleft + v2s32(size.X / 2 - 105, size.Y - 40);
+ const wchar_t *text = wgettext("Save");
+ Environment->addButton(rect, this, GUI_ID_BACK_BUTTON,
+ text);
+ delete[] text;
+ }
+ {
+ core::rect < s32 > rect(0, 0, 100, 30);
+ rect += topleft + v2s32(size.X / 2 + 5, size.Y - 40);
+ const wchar_t *text = wgettext("Cancel");
+ Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON,
+ text);
+ delete[] text;
+ }
+}
+
+void GUIKeyChangeMenu::drawMenu()
+{
+ gui::IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ video::SColor bgcolor(140, 0, 0, 0);
+
+ {
+ core::rect < s32 > rect(0, 0, 745, 620);
+ rect += AbsoluteRect.UpperLeftCorner;
+ driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
+ }
+
+ gui::IGUIElement::draw();
+}
+
+bool GUIKeyChangeMenu::acceptInput()
+{
+ for (key_setting *k : key_settings) {
+ g_settings->set(k->setting_name, k->key.sym());
+ }
+
+ {
+ gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
+ if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
+ g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
+ }
+ {
+ gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP);
+ if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
+ g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked());
+ }
+
+ clearKeyCache();
+
+ g_gamecallback->signalKeyConfigChange();
+
+ return true;
+}
+
+bool GUIKeyChangeMenu::resetMenu()
+{
+ if (activeKey >= 0)
+ {
+ for (key_setting *k : key_settings) {
+ if (k->id == activeKey) {
+ const wchar_t *text = wgettext(k->key.name());
+ k->button->setText(text);
+ delete[] text;
+ break;
+ }
+ }
+ activeKey = -1;
+ return false;
+ }
+ return true;
+}
+bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
+{
+ if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0
+ && event.KeyInput.PressedDown) {
+
+ bool prefer_character = shift_down;
+ KeyPress kp(event.KeyInput, prefer_character);
+
+ bool shift_went_down = false;
+ if(!shift_down &&
+ (event.KeyInput.Key == irr::KEY_SHIFT ||
+ event.KeyInput.Key == irr::KEY_LSHIFT ||
+ event.KeyInput.Key == irr::KEY_RSHIFT))
+ shift_went_down = true;
+
+ // Remove Key already in use message
+ if(this->key_used_text)
+ {
+ this->key_used_text->remove();
+ this->key_used_text = NULL;
+ }
+ // Display Key already in use message
+ if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end())
+ {
+ core::rect < s32 > rect(0, 0, 600, 40);
+ rect += v2s32(0, 0) + v2s32(25, 30);
+ const wchar_t *text = wgettext("Key already in use");
+ this->key_used_text = Environment->addStaticText(text,
+ rect, false, true, this, -1);
+ delete[] text;
+ //infostream << "Key already in use" << std::endl;
+ }
+
+ // But go on
+ {
+ key_setting *k = NULL;
+ for (key_setting *ks : key_settings) {
+ if (ks->id == activeKey) {
+ k = ks;
+ break;
+ }
+ }
+ FATAL_ERROR_IF(k == NULL, "Key setting not found");
+ k->key = kp;
+ const wchar_t *text = wgettext(k->key.name());
+ k->button->setText(text);
+ delete[] text;
+
+ this->key_used.push_back(kp);
+
+ // Allow characters made with shift
+ if(shift_went_down){
+ shift_down = true;
+ return false;
+ }
+
+ activeKey = -1;
+ return true;
+ }
+ } else if (event.EventType == EET_KEY_INPUT_EVENT && activeKey < 0
+ && event.KeyInput.PressedDown
+ && event.KeyInput.Key == irr::KEY_ESCAPE) {
+ quitMenu();
+ return true;
+ } else if (event.EventType == EET_GUI_EVENT) {
+ if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
+ && isVisible())
+ {
+ if (!canTakeFocus(event.GUIEvent.Element))
+ {
+ dstream << "GUIMainMenu: Not allowing focus change."
+ << std::endl;
+ // Returning true disables focus change
+ return true;
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED)
+ {
+ switch (event.GUIEvent.Caller->getID())
+ {
+ case GUI_ID_BACK_BUTTON: //back
+ acceptInput();
+ quitMenu();
+ return true;
+ case GUI_ID_ABORT_BUTTON: //abort
+ quitMenu();
+ return true;
+ default:
+ key_setting *k = NULL;
+
+ for (key_setting *ks : key_settings) {
+ if (ks->id == event.GUIEvent.Caller->getID()) {
+ k = ks;
+ break;
+ }
+ }
+ FATAL_ERROR_IF(k == NULL, "Key setting not found");
+
+ resetMenu();
+ shift_down = false;
+ activeKey = event.GUIEvent.Caller->getID();
+ const wchar_t *text = wgettext("press key");
+ k->button->setText(text);
+ delete[] text;
+ this->key_used.erase(std::remove(this->key_used.begin(),
+ this->key_used.end(), k->key), this->key_used.end());
+ break;
+ }
+ Environment->setFocus(this);
+ }
+ }
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
+void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::string &setting_name)
+{
+ key_setting *k = new key_setting;
+ k->id = id;
+
+ k->button_name = button_name;
+ k->setting_name = setting_name;
+ k->key = getKeySetting(k->setting_name.c_str());
+ key_settings.push_back(k);
+}
+
+void GUIKeyChangeMenu::init_keys()
+{
+ this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wgettext("Forward"), "keymap_forward");
+ this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), "keymap_backward");
+ this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left");
+ this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right");
+ this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Special"), "keymap_special1");
+ this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump");
+ this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak");
+ this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop");
+ this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), "keymap_inventory");
+ this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON,wgettext("Prev. item"), "keymap_hotbar_previous");
+ this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,wgettext("Next item"), "keymap_hotbar_next");
+ this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom");
+ this->add_key(GUI_ID_KEY_CAMERA_BUTTON, wgettext("Change camera"), "keymap_camera_mode");
+ this->add_key(GUI_ID_KEY_CINEMATIC_BUTTON, wgettext("Toggle Cinematic"), "keymap_cinematic");
+ this->add_key(GUI_ID_KEY_MINIMAP_BUTTON, wgettext("Toggle minimap"), "keymap_minimap");
+ this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove");
+ this->add_key(GUI_ID_KEY_FAST_BUTTON, wgettext("Toggle fast"), "keymap_fastmove");
+ this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), "keymap_noclip");
+ this->add_key(GUI_ID_KEY_MUTE_BUTTON, wgettext("Mute"), "keymap_mute");
+ this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON,wgettext("Dec. volume"), "keymap_decrease_volume");
+ this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON,wgettext("Inc. volume"), "keymap_increase_volume");
+ this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON, wgettext("Autoforward"), "keymap_autoforward");
+ this->add_key(GUI_ID_KEY_CHAT_BUTTON, wgettext("Chat"), "keymap_chat");
+ this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON,wgettext("Screenshot"), "keymap_screenshot");
+ this->add_key(GUI_ID_KEY_RANGE_BUTTON, wgettext("Range select"), "keymap_rangeselect");
+ this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON, wgettext("Dec. range"), "keymap_decrease_viewing_range_min");
+ this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON, wgettext("Inc. range"), "keymap_increase_viewing_range_min");
+ this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wgettext("Console"), "keymap_console");
+ this->add_key(GUI_ID_KEY_CMD_BUTTON, wgettext("Command"), "keymap_cmd");
+ this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wgettext("Local command"), "keymap_cmd_local");
+ this->add_key(GUI_ID_KEY_HUD_BUTTON, wgettext("Toggle HUD"), "keymap_toggle_hud");
+ this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wgettext("Toggle chat log"), "keymap_toggle_chat");
+ this->add_key(GUI_ID_KEY_FOG_BUTTON, wgettext("Toggle fog"), "keymap_toggle_force_fog_off");
+}
+
--- /dev/null
+/*
+ Minetest
+ Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+ Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+ Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include "gettext.h"
+#include "keycode.h"
+#include <string>
+#include <vector>
+
+struct key_setting
+{
+ int id;
+ const wchar_t *button_name;
+ KeyPress key;
+ std::string setting_name;
+ gui::IGUIButton *button;
+};
+
+class GUIKeyChangeMenu : public GUIModalMenu
+{
+public:
+ GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ IMenuManager *menumgr);
+ ~GUIKeyChangeMenu();
+
+ void removeChildren();
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ void drawMenu();
+
+ bool acceptInput();
+
+ bool OnEvent(const SEvent &event);
+
+ bool pausesGame() { return true; }
+
+private:
+ void init_keys();
+
+ bool resetMenu();
+
+ void add_key(int id, const wchar_t *button_name, const std::string &setting_name);
+
+ bool shift_down = false;
+ s32 activeKey = -1;
+
+ std::vector<KeyPress> key_used;
+ gui::IGUIStaticText *key_used_text = nullptr;
+ std::vector<key_setting *> key_settings;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include <string>
+#include <list>
+
+struct MainMenuDataForScript {
+
+ MainMenuDataForScript() = default;
+
+ // Whether the server has requested a reconnect
+ bool reconnect_requested = false;
+ std::string errormessage = "";
+};
+
+struct MainMenuData {
+ // Client options
+ std::string servername;
+ std::string serverdescription;
+ std::string address;
+ std::string port;
+ std::string name;
+ std::string password;
+ // Whether to reconnect
+ bool do_reconnect = false;
+
+ // Server options
+ int selected_world = 0;
+ bool simple_singleplayer_mode = false;
+
+ // Data to be passed to the script
+ MainMenuDataForScript script_data;
+
+ MainMenuData() = default;
+};
--- /dev/null
+/*
+Part of Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "guiPasswordChange.h"
+#include "client.h"
+#include <IGUICheckBox.h>
+#include <IGUIEditBox.h>
+#include <IGUIButton.h>
+#include <IGUIStaticText.h>
+#include <IGUIFont.h>
+
+#include "gettext.h"
+
+const int ID_oldPassword = 256;
+const int ID_newPassword1 = 257;
+const int ID_newPassword2 = 258;
+const int ID_change = 259;
+const int ID_message = 260;
+const int ID_cancel = 261;
+
+GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr,
+ Client* client
+):
+ GUIModalMenu(env, parent, id, menumgr),
+ m_client(client)
+{
+}
+
+GUIPasswordChange::~GUIPasswordChange()
+{
+ removeChildren();
+}
+
+void GUIPasswordChange::removeChildren()
+{
+ const core::list<gui::IGUIElement *> &children = getChildren();
+ core::list<gui::IGUIElement *> children_copy;
+ for (gui::IGUIElement *i : children) {
+ children_copy.push_back(i);
+ }
+
+ for (gui::IGUIElement *i : children_copy) {
+ i->remove();
+ }
+}
+void GUIPasswordChange::regenerateGui(v2u32 screensize)
+{
+ /*
+ save current input
+ */
+ acceptInput();
+
+ /*
+ Remove stuff
+ */
+ removeChildren();
+
+ /*
+ Calculate new sizes and positions
+ */
+ core::rect<s32> rect(
+ screensize.X/2 - 580/2,
+ screensize.Y/2 - 300/2,
+ screensize.X/2 + 580/2,
+ screensize.Y/2 + 300/2
+ );
+
+ DesiredRect = rect;
+ recalculateAbsolutePosition(false);
+
+ v2s32 size = rect.getSize();
+ v2s32 topleft_client(40, 0);
+
+ const wchar_t *text;
+
+ /*
+ Add stuff
+ */
+ s32 ypos = 50;
+ {
+ core::rect<s32> rect(0, 0, 150, 20);
+ rect += topleft_client + v2s32(25, ypos + 6);
+ text = wgettext("Old Password");
+ Environment->addStaticText(text, rect, false, true, this, -1);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 230, 30);
+ rect += topleft_client + v2s32(160, ypos);
+ gui::IGUIEditBox *e = Environment->addEditBox(
+ m_oldpass.c_str(), rect, true, this, ID_oldPassword);
+ Environment->setFocus(e);
+ e->setPasswordBox(true);
+ }
+ ypos += 50;
+ {
+ core::rect<s32> rect(0, 0, 150, 20);
+ rect += topleft_client + v2s32(25, ypos + 6);
+ text = wgettext("New Password");
+ Environment->addStaticText(text, rect, false, true, this, -1);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 230, 30);
+ rect += topleft_client + v2s32(160, ypos);
+ gui::IGUIEditBox *e = Environment->addEditBox(
+ m_newpass.c_str(), rect, true, this, ID_newPassword1);
+ e->setPasswordBox(true);
+ }
+ ypos += 50;
+ {
+ core::rect<s32> rect(0, 0, 150, 20);
+ rect += topleft_client + v2s32(25, ypos + 6);
+ text = wgettext("Confirm Password");
+ Environment->addStaticText(text, rect, false, true, this, -1);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 230, 30);
+ rect += topleft_client + v2s32(160, ypos);
+ gui::IGUIEditBox *e = Environment->addEditBox(
+ m_newpass_confirm.c_str(), rect, true, this, ID_newPassword2);
+ e->setPasswordBox(true);
+ }
+
+ ypos += 50;
+ {
+ core::rect<s32> rect(0, 0, 100, 30);
+ rect = rect + v2s32(size.X / 4 + 56, ypos);
+ text = wgettext("Change");
+ Environment->addButton(rect, this, ID_change, text);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 100, 30);
+ rect = rect + v2s32(size.X / 4 + 185, ypos);
+ text = wgettext("Cancel");
+ Environment->addButton(rect, this, ID_cancel, text);
+ delete[] text;
+ }
+
+ ypos += 50;
+ {
+ core::rect<s32> rect(0, 0, 300, 20);
+ rect += topleft_client + v2s32(35, ypos);
+ text = wgettext("Passwords do not match!");
+ IGUIElement *e =
+ Environment->addStaticText(
+ text, rect, false, true, this, ID_message);
+ e->setVisible(false);
+ delete[] text;
+ }
+}
+
+void GUIPasswordChange::drawMenu()
+{
+ gui::IGUISkin *skin = Environment->getSkin();
+ if (!skin)
+ return;
+ video::IVideoDriver *driver = Environment->getVideoDriver();
+
+ video::SColor bgcolor(140, 0, 0, 0);
+ driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
+
+ gui::IGUIElement::draw();
+}
+
+void GUIPasswordChange::acceptInput()
+{
+ gui::IGUIElement *e;
+ e = getElementFromId(ID_oldPassword);
+ if (e != NULL)
+ m_oldpass = e->getText();
+ e = getElementFromId(ID_newPassword1);
+ if (e != NULL)
+ m_newpass = e->getText();
+ e = getElementFromId(ID_newPassword2);
+ if (e != NULL)
+ m_newpass_confirm = e->getText();
+}
+
+bool GUIPasswordChange::processInput()
+{
+ if (m_newpass != m_newpass_confirm) {
+ gui::IGUIElement *e = getElementFromId(ID_message);
+ if (e != NULL)
+ e->setVisible(true);
+ return false;
+ }
+ m_client->sendChangePassword(wide_to_utf8(m_oldpass), wide_to_utf8(m_newpass));
+ return true;
+}
+
+bool GUIPasswordChange::OnEvent(const SEvent &event)
+{
+ if (event.EventType == EET_KEY_INPUT_EVENT) {
+ if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
+ quitMenu();
+ return true;
+ }
+ if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
+ acceptInput();
+ if (processInput())
+ quitMenu();
+ return true;
+ }
+ }
+ if (event.EventType == EET_GUI_EVENT) {
+ if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST &&
+ isVisible()) {
+ if (!canTakeFocus(event.GUIEvent.Element)) {
+ dstream << "GUIPasswordChange: Not allowing focus change."
+ << std::endl;
+ // Returning true disables focus change
+ return true;
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
+ switch (event.GUIEvent.Caller->getID()) {
+ case ID_change:
+ acceptInput();
+ if (processInput())
+ quitMenu();
+ return true;
+ case ID_cancel:
+ quitMenu();
+ return true;
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
+ switch (event.GUIEvent.Caller->getID()) {
+ case ID_oldPassword:
+ case ID_newPassword1:
+ case ID_newPassword2:
+ acceptInput();
+ if (processInput())
+ quitMenu();
+ return true;
+ }
+ }
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
--- /dev/null
+/*
+Part of Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include <string>
+
+class Client;
+
+class GUIPasswordChange : public GUIModalMenu
+{
+public:
+ GUIPasswordChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ IMenuManager *menumgr, Client *client);
+ ~GUIPasswordChange();
+
+ void removeChildren();
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ void drawMenu();
+
+ void acceptInput();
+
+ bool processInput();
+
+ bool OnEvent(const SEvent &event);
+
+private:
+ Client *m_client;
+ std::wstring m_oldpass = L"";
+ std::wstring m_newpass = L"";
+ std::wstring m_newpass_confirm = L"";
+};
--- /dev/null
+/*
+ Minetest
+ Copyright (C) 2013 sapier
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "guiPathSelectMenu.h"
+
+GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id, IMenuManager *menumgr,
+ const std::string &title, const std::string &formname,
+ bool is_file_select) :
+ GUIModalMenu(env, parent, id, menumgr),
+ m_title(utf8_to_wide(title)),
+ m_formname(formname),
+ m_file_select_dialog(is_file_select)
+{
+}
+
+GUIFileSelectMenu::~GUIFileSelectMenu()
+{
+ removeChildren();
+ setlocale(LC_NUMERIC, "C");
+}
+
+void GUIFileSelectMenu::regenerateGui(v2u32 screensize)
+{
+ removeChildren();
+ m_fileOpenDialog = 0;
+
+ core::dimension2du size(600, 400);
+ core::rect<s32> rect(0, 0, screensize.X, screensize.Y);
+
+ DesiredRect = rect;
+ recalculateAbsolutePosition(false);
+
+ m_fileOpenDialog =
+ Environment->addFileOpenDialog(m_title.c_str(), false, this, -1);
+
+ core::position2di pos = core::position2di(screensize.X / 2 - size.Width / 2,
+ screensize.Y / 2 - size.Height / 2);
+ m_fileOpenDialog->setRelativePosition(pos);
+ m_fileOpenDialog->setMinSize(size);
+}
+
+void GUIFileSelectMenu::drawMenu()
+{
+ gui::IGUISkin *skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ gui::IGUIElement::draw();
+}
+
+void GUIFileSelectMenu::acceptInput()
+{
+ if (m_text_dst && !m_formname.empty()) {
+ StringMap fields;
+ if (m_accepted) {
+ std::string path;
+ if (!m_file_select_dialog) {
+ core::string<fschar_t> string =
+ m_fileOpenDialog->getDirectoryName();
+ path = std::string(string.c_str());
+ } else {
+ path = wide_to_utf8(m_fileOpenDialog->getFileName());
+ }
+ fields[m_formname + "_accepted"] = path;
+ } else {
+ fields[m_formname + "_canceled"] = m_formname;
+ }
+ m_text_dst->gotText(fields);
+ }
+ quitMenu();
+}
+
+bool GUIFileSelectMenu::OnEvent(const SEvent &event)
+{
+ if (event.EventType == irr::EET_GUI_EVENT) {
+ switch (event.GUIEvent.EventType) {
+ case gui::EGET_ELEMENT_CLOSED:
+ case gui::EGET_FILE_CHOOSE_DIALOG_CANCELLED:
+ m_accepted = false;
+ acceptInput();
+ return true;
+ case gui::EGET_DIRECTORY_SELECTED:
+ m_accepted = !m_file_select_dialog;
+ acceptInput();
+ return true;
+ case gui::EGET_FILE_SELECTED:
+ m_accepted = m_file_select_dialog;
+ acceptInput();
+ return true;
+ default:
+ // ignore this event
+ break;
+ }
+ }
+ return Parent ? Parent->OnEvent(event) : false;
+}
--- /dev/null
+/*
+ Minetest
+ Copyright (C) 2013 sapier
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "modalMenu.h"
+#include "IGUIFileOpenDialog.h"
+#include "guiFormSpecMenu.h" //required because of TextDest only !!!
+
+class GUIFileSelectMenu : public GUIModalMenu
+{
+public:
+ GUIFileSelectMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ IMenuManager *menumgr, const std::string &title,
+ const std::string &formid, bool is_file_select);
+ ~GUIFileSelectMenu();
+
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ void drawMenu();
+
+ bool OnEvent(const SEvent &event);
+
+ void setTextDest(TextDest *dest) { m_text_dst = dest; }
+
+private:
+ void acceptInput();
+
+ std::wstring m_title;
+ bool m_accepted = false;
+
+ gui::IGUIFileOpenDialog *m_fileOpenDialog = nullptr;
+
+ TextDest *m_text_dst = nullptr;
+
+ std::string m_formname;
+ bool m_file_select_dialog;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "guiTable.h"
+#include <queue>
+#include <sstream>
+#include <utility>
+#include <cstring>
+#include <IGUISkin.h>
+#include <IGUIFont.h>
+#include <IGUIScrollBar.h>
+#include "client/renderingengine.h"
+#include "debug.h"
+#include "log.h"
+#include "client/tile.h"
+#include "gettime.h"
+#include "util/string.h"
+#include "util/numeric.h"
+#include "util/string.h" // for parseColorString()
+#include "settings.h" // for settings
+#include "porting.h" // for dpi
+#include "guiscalingfilter.h"
+
+/*
+ GUITable
+*/
+
+GUITable::GUITable(gui::IGUIEnvironment *env,
+ gui::IGUIElement* parent, s32 id,
+ core::rect<s32> rectangle,
+ ISimpleTextureSource *tsrc
+):
+ gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
+ m_tsrc(tsrc)
+{
+ assert(tsrc != NULL);
+
+ gui::IGUISkin* skin = Environment->getSkin();
+
+ m_font = skin->getFont();
+ if (m_font) {
+ m_font->grab();
+ m_rowheight = m_font->getDimension(L"A").Height + 4;
+ m_rowheight = MYMAX(m_rowheight, 1);
+ }
+
+ const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
+ m_scrollbar = Environment->addScrollBar(false,
+ core::rect<s32>(RelativeRect.getWidth() - s,
+ 0,
+ RelativeRect.getWidth(),
+ RelativeRect.getHeight()),
+ this, -1);
+ m_scrollbar->setSubElement(true);
+ m_scrollbar->setTabStop(false);
+ m_scrollbar->setAlignment(gui::EGUIA_LOWERRIGHT, gui::EGUIA_LOWERRIGHT,
+ gui::EGUIA_UPPERLEFT, gui::EGUIA_LOWERRIGHT);
+ m_scrollbar->setVisible(false);
+ m_scrollbar->setPos(0);
+
+ setTabStop(true);
+ setTabOrder(-1);
+ updateAbsolutePosition();
+
+ core::rect<s32> relative_rect = m_scrollbar->getRelativePosition();
+ s32 width = (relative_rect.getWidth()/(2.0/3.0)) *
+ RenderingEngine::getDisplayDensity() *
+ g_settings->getFloat("gui_scaling");
+ m_scrollbar->setRelativePosition(core::rect<s32>(
+ relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y,
+ relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y
+ ));
+}
+
+GUITable::~GUITable()
+{
+ for (GUITable::Row &row : m_rows)
+ delete[] row.cells;
+
+ if (m_font)
+ m_font->drop();
+
+ m_scrollbar->remove();
+}
+
+GUITable::Option GUITable::splitOption(const std::string &str)
+{
+ size_t equal_pos = str.find('=');
+ if (equal_pos == std::string::npos)
+ return GUITable::Option(str, "");
+
+ return GUITable::Option(str.substr(0, equal_pos),
+ str.substr(equal_pos + 1));
+}
+
+void GUITable::setTextList(const std::vector<std::string> &content,
+ bool transparent)
+{
+ clear();
+
+ if (transparent) {
+ m_background.setAlpha(0);
+ m_border = false;
+ }
+
+ m_is_textlist = true;
+
+ s32 empty_string_index = allocString("");
+
+ m_rows.resize(content.size());
+ for (s32 i = 0; i < (s32) content.size(); ++i) {
+ Row *row = &m_rows[i];
+ row->cells = new Cell[1];
+ row->cellcount = 1;
+ row->indent = 0;
+ row->visible_index = i;
+ m_visible_rows.push_back(i);
+
+ Cell *cell = row->cells;
+ cell->xmin = 0;
+ cell->xmax = 0x7fff; // something large enough
+ cell->xpos = 6;
+ cell->content_type = COLUMN_TYPE_TEXT;
+ cell->content_index = empty_string_index;
+ cell->tooltip_index = empty_string_index;
+ cell->color.set(255, 255, 255, 255);
+ cell->color_defined = false;
+ cell->reported_column = 1;
+
+ // parse row content (color)
+ const std::string &s = content[i];
+ if (s[0] == '#' && s[1] == '#') {
+ // double # to escape
+ cell->content_index = allocString(s.substr(2));
+ }
+ else if (s[0] == '#' && s.size() >= 7 &&
+ parseColorString(
+ s.substr(0,7), cell->color, false)) {
+ // single # for color
+ cell->color_defined = true;
+ cell->content_index = allocString(s.substr(7));
+ }
+ else {
+ // no #, just text
+ cell->content_index = allocString(s);
+ }
+
+ }
+
+ allocationComplete();
+
+ // Clamp scroll bar position
+ updateScrollBar();
+}
+
+void GUITable::setTable(const TableOptions &options,
+ const TableColumns &columns,
+ std::vector<std::string> &content)
+{
+ clear();
+
+ // Naming conventions:
+ // i is always a row index, 0-based
+ // j is always a column index, 0-based
+ // k is another index, for example an option index
+
+ // Handle a stupid error case... (issue #1187)
+ if (columns.empty()) {
+ TableColumn text_column;
+ text_column.type = "text";
+ TableColumns new_columns;
+ new_columns.push_back(text_column);
+ setTable(options, new_columns, content);
+ return;
+ }
+
+ // Handle table options
+ video::SColor default_color(255, 255, 255, 255);
+ s32 opendepth = 0;
+ for (const Option &option : options) {
+ const std::string &name = option.name;
+ const std::string &value = option.value;
+ if (name == "color")
+ parseColorString(value, m_color, false);
+ else if (name == "background")
+ parseColorString(value, m_background, false);
+ else if (name == "border")
+ m_border = is_yes(value);
+ else if (name == "highlight")
+ parseColorString(value, m_highlight, false);
+ else if (name == "highlight_text")
+ parseColorString(value, m_highlight_text, false);
+ else if (name == "opendepth")
+ opendepth = stoi(value);
+ else
+ errorstream<<"Invalid table option: \""<<name<<"\""
+ <<" (value=\""<<value<<"\")"<<std::endl;
+ }
+
+ // Get number of columns and rows
+ // note: error case columns.size() == 0 was handled above
+ s32 colcount = columns.size();
+ assert(colcount >= 1);
+ // rowcount = ceil(cellcount / colcount) but use integer arithmetic
+ s32 rowcount = (content.size() + colcount - 1) / colcount;
+ assert(rowcount >= 0);
+ // Append empty strings to content if there is an incomplete row
+ s32 cellcount = rowcount * colcount;
+ while (content.size() < (u32) cellcount)
+ content.emplace_back("");
+
+ // Create temporary rows (for processing columns)
+ struct TempRow {
+ // Current horizontal position (may different between rows due
+ // to indent/tree columns, or text/image columns with width<0)
+ s32 x;
+ // Tree indentation level
+ s32 indent;
+ // Next cell: Index into m_strings or m_images
+ s32 content_index;
+ // Next cell: Width in pixels
+ s32 content_width;
+ // Vector of completed cells in this row
+ std::vector<Cell> cells;
+ // Stores colors and how long they last (maximum column index)
+ std::vector<std::pair<video::SColor, s32> > colors;
+
+ TempRow(): x(0), indent(0), content_index(0), content_width(0) {}
+ };
+ TempRow *rows = new TempRow[rowcount];
+
+ // Get em width. Pedantically speaking, the width of "M" is not
+ // necessarily the same as the em width, but whatever, close enough.
+ s32 em = 6;
+ if (m_font)
+ em = m_font->getDimension(L"M").Width;
+
+ s32 default_tooltip_index = allocString("");
+
+ std::map<s32, s32> active_image_indices;
+
+ // Process content in column-major order
+ for (s32 j = 0; j < colcount; ++j) {
+ // Check column type
+ ColumnType columntype = COLUMN_TYPE_TEXT;
+ if (columns[j].type == "text")
+ columntype = COLUMN_TYPE_TEXT;
+ else if (columns[j].type == "image")
+ columntype = COLUMN_TYPE_IMAGE;
+ else if (columns[j].type == "color")
+ columntype = COLUMN_TYPE_COLOR;
+ else if (columns[j].type == "indent")
+ columntype = COLUMN_TYPE_INDENT;
+ else if (columns[j].type == "tree")
+ columntype = COLUMN_TYPE_TREE;
+ else
+ errorstream<<"Invalid table column type: \""
+ <<columns[j].type<<"\""<<std::endl;
+
+ // Process column options
+ s32 padding = myround(0.5 * em);
+ s32 tooltip_index = default_tooltip_index;
+ s32 align = 0;
+ s32 width = 0;
+ s32 span = colcount;
+
+ if (columntype == COLUMN_TYPE_INDENT) {
+ padding = 0; // default indent padding
+ }
+ if (columntype == COLUMN_TYPE_INDENT ||
+ columntype == COLUMN_TYPE_TREE) {
+ width = myround(em * 1.5); // default indent width
+ }
+
+ for (const Option &option : columns[j].options) {
+ const std::string &name = option.name;
+ const std::string &value = option.value;
+ if (name == "padding")
+ padding = myround(stof(value) * em);
+ else if (name == "tooltip")
+ tooltip_index = allocString(value);
+ else if (name == "align" && value == "left")
+ align = 0;
+ else if (name == "align" && value == "center")
+ align = 1;
+ else if (name == "align" && value == "right")
+ align = 2;
+ else if (name == "align" && value == "inline")
+ align = 3;
+ else if (name == "width")
+ width = myround(stof(value) * em);
+ else if (name == "span" && columntype == COLUMN_TYPE_COLOR)
+ span = stoi(value);
+ else if (columntype == COLUMN_TYPE_IMAGE &&
+ !name.empty() &&
+ string_allowed(name, "0123456789")) {
+ s32 content_index = allocImage(value);
+ active_image_indices.insert(std::make_pair(
+ stoi(name),
+ content_index));
+ }
+ else {
+ errorstream<<"Invalid table column option: \""<<name<<"\""
+ <<" (value=\""<<value<<"\")"<<std::endl;
+ }
+ }
+
+ // If current column type can use information from "color" columns,
+ // find out which of those is currently active
+ if (columntype == COLUMN_TYPE_TEXT) {
+ for (s32 i = 0; i < rowcount; ++i) {
+ TempRow *row = &rows[i];
+ while (!row->colors.empty() && row->colors.back().second < j)
+ row->colors.pop_back();
+ }
+ }
+
+ // Make template for new cells
+ Cell newcell;
+ memset(&newcell, 0, sizeof newcell);
+ newcell.content_type = columntype;
+ newcell.tooltip_index = tooltip_index;
+ newcell.reported_column = j+1;
+
+ if (columntype == COLUMN_TYPE_TEXT) {
+ // Find right edge of column
+ s32 xmax = 0;
+ for (s32 i = 0; i < rowcount; ++i) {
+ TempRow *row = &rows[i];
+ row->content_index = allocString(content[i * colcount + j]);
+ const core::stringw &text = m_strings[row->content_index];
+ row->content_width = m_font ?
+ m_font->getDimension(text.c_str()).Width : 0;
+ row->content_width = MYMAX(row->content_width, width);
+ s32 row_xmax = row->x + padding + row->content_width;
+ xmax = MYMAX(xmax, row_xmax);
+ }
+ // Add a new cell (of text type) to each row
+ for (s32 i = 0; i < rowcount; ++i) {
+ newcell.xmin = rows[i].x + padding;
+ alignContent(&newcell, xmax, rows[i].content_width, align);
+ newcell.content_index = rows[i].content_index;
+ newcell.color_defined = !rows[i].colors.empty();
+ if (newcell.color_defined)
+ newcell.color = rows[i].colors.back().first;
+ rows[i].cells.push_back(newcell);
+ rows[i].x = newcell.xmax;
+ }
+ }
+ else if (columntype == COLUMN_TYPE_IMAGE) {
+ // Find right edge of column
+ s32 xmax = 0;
+ for (s32 i = 0; i < rowcount; ++i) {
+ TempRow *row = &rows[i];
+ row->content_index = -1;
+
+ // Find content_index. Image indices are defined in
+ // column options so check active_image_indices.
+ s32 image_index = stoi(content[i * colcount + j]);
+ std::map<s32, s32>::iterator image_iter =
+ active_image_indices.find(image_index);
+ if (image_iter != active_image_indices.end())
+ row->content_index = image_iter->second;
+
+ // Get texture object (might be NULL)
+ video::ITexture *image = NULL;
+ if (row->content_index >= 0)
+ image = m_images[row->content_index];
+
+ // Get content width and update xmax
+ row->content_width = image ? image->getOriginalSize().Width : 0;
+ row->content_width = MYMAX(row->content_width, width);
+ s32 row_xmax = row->x + padding + row->content_width;
+ xmax = MYMAX(xmax, row_xmax);
+ }
+ // Add a new cell (of image type) to each row
+ for (s32 i = 0; i < rowcount; ++i) {
+ newcell.xmin = rows[i].x + padding;
+ alignContent(&newcell, xmax, rows[i].content_width, align);
+ newcell.content_index = rows[i].content_index;
+ rows[i].cells.push_back(newcell);
+ rows[i].x = newcell.xmax;
+ }
+ active_image_indices.clear();
+ }
+ else if (columntype == COLUMN_TYPE_COLOR) {
+ for (s32 i = 0; i < rowcount; ++i) {
+ video::SColor cellcolor(255, 255, 255, 255);
+ if (parseColorString(content[i * colcount + j], cellcolor, true))
+ rows[i].colors.emplace_back(cellcolor, j+span);
+ }
+ }
+ else if (columntype == COLUMN_TYPE_INDENT ||
+ columntype == COLUMN_TYPE_TREE) {
+ // For column type "tree", reserve additional space for +/-
+ // Also enable special processing for treeview-type tables
+ s32 content_width = 0;
+ if (columntype == COLUMN_TYPE_TREE) {
+ content_width = m_font ? m_font->getDimension(L"+").Width : 0;
+ m_has_tree_column = true;
+ }
+ // Add a new cell (of indent or tree type) to each row
+ for (s32 i = 0; i < rowcount; ++i) {
+ TempRow *row = &rows[i];
+
+ s32 indentlevel = stoi(content[i * colcount + j]);
+ indentlevel = MYMAX(indentlevel, 0);
+ if (columntype == COLUMN_TYPE_TREE)
+ row->indent = indentlevel;
+
+ newcell.xmin = row->x + padding;
+ newcell.xpos = newcell.xmin + indentlevel * width;
+ newcell.xmax = newcell.xpos + content_width;
+ newcell.content_index = 0;
+ newcell.color_defined = !rows[i].colors.empty();
+ if (newcell.color_defined)
+ newcell.color = rows[i].colors.back().first;
+ row->cells.push_back(newcell);
+ row->x = newcell.xmax;
+ }
+ }
+ }
+
+ // Copy temporary rows to not so temporary rows
+ if (rowcount >= 1) {
+ m_rows.resize(rowcount);
+ for (s32 i = 0; i < rowcount; ++i) {
+ Row *row = &m_rows[i];
+ row->cellcount = rows[i].cells.size();
+ row->cells = new Cell[row->cellcount];
+ memcpy((void*) row->cells, (void*) &rows[i].cells[0],
+ row->cellcount * sizeof(Cell));
+ row->indent = rows[i].indent;
+ row->visible_index = i;
+ m_visible_rows.push_back(i);
+ }
+ }
+
+ if (m_has_tree_column) {
+ // Treeview: convert tree to indent cells on leaf rows
+ for (s32 i = 0; i < rowcount; ++i) {
+ if (i == rowcount-1 || m_rows[i].indent >= m_rows[i+1].indent)
+ for (s32 j = 0; j < m_rows[i].cellcount; ++j)
+ if (m_rows[i].cells[j].content_type == COLUMN_TYPE_TREE)
+ m_rows[i].cells[j].content_type = COLUMN_TYPE_INDENT;
+ }
+
+ // Treeview: close rows according to opendepth option
+ std::set<s32> opened_trees;
+ for (s32 i = 0; i < rowcount; ++i)
+ if (m_rows[i].indent < opendepth)
+ opened_trees.insert(i);
+ setOpenedTrees(opened_trees);
+ }
+
+ // Delete temporary information used only during setTable()
+ delete[] rows;
+ allocationComplete();
+
+ // Clamp scroll bar position
+ updateScrollBar();
+}
+
+void GUITable::clear()
+{
+ // Clean up cells and rows
+ for (GUITable::Row &row : m_rows)
+ delete[] row.cells;
+ m_rows.clear();
+ m_visible_rows.clear();
+
+ // Get colors from skin
+ gui::IGUISkin *skin = Environment->getSkin();
+ m_color = skin->getColor(gui::EGDC_BUTTON_TEXT);
+ m_background = skin->getColor(gui::EGDC_3D_HIGH_LIGHT);
+ m_highlight = skin->getColor(gui::EGDC_HIGH_LIGHT);
+ m_highlight_text = skin->getColor(gui::EGDC_HIGH_LIGHT_TEXT);
+
+ // Reset members
+ m_is_textlist = false;
+ m_has_tree_column = false;
+ m_selected = -1;
+ m_sel_column = 0;
+ m_sel_doubleclick = false;
+ m_keynav_time = 0;
+ m_keynav_buffer = L"";
+ m_border = true;
+ m_strings.clear();
+ m_images.clear();
+ m_alloc_strings.clear();
+ m_alloc_images.clear();
+}
+
+std::string GUITable::checkEvent()
+{
+ s32 sel = getSelected();
+ assert(sel >= 0);
+
+ if (sel == 0) {
+ return "INV";
+ }
+
+ std::ostringstream os(std::ios::binary);
+ if (m_sel_doubleclick) {
+ os<<"DCL:";
+ m_sel_doubleclick = false;
+ }
+ else {
+ os<<"CHG:";
+ }
+ os<<sel;
+ if (!m_is_textlist) {
+ os<<":"<<m_sel_column;
+ }
+ return os.str();
+}
+
+s32 GUITable::getSelected() const
+{
+ if (m_selected < 0)
+ return 0;
+
+ assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
+ return m_visible_rows[m_selected] + 1;
+}
+
+void GUITable::setSelected(s32 index)
+{
+ s32 old_selected = m_selected;
+
+ m_selected = -1;
+ m_sel_column = 0;
+ m_sel_doubleclick = false;
+
+ --index; // Switch from 1-based indexing to 0-based indexing
+
+ s32 rowcount = m_rows.size();
+ if (rowcount == 0 || index < 0) {
+ return;
+ }
+
+ if (index >= rowcount) {
+ index = rowcount - 1;
+ }
+
+ // If the selected row is not visible, open its ancestors to make it visible
+ bool selection_invisible = m_rows[index].visible_index < 0;
+ if (selection_invisible) {
+ std::set<s32> opened_trees;
+ getOpenedTrees(opened_trees);
+ s32 indent = m_rows[index].indent;
+ for (s32 j = index - 1; j >= 0; --j) {
+ if (m_rows[j].indent < indent) {
+ opened_trees.insert(j);
+ indent = m_rows[j].indent;
+ }
+ }
+ setOpenedTrees(opened_trees);
+ }
+
+ if (index >= 0) {
+ m_selected = m_rows[index].visible_index;
+ assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
+ }
+
+ if (m_selected != old_selected || selection_invisible) {
+ autoScroll();
+ }
+}
+
+GUITable::DynamicData GUITable::getDynamicData() const
+{
+ DynamicData dyndata;
+ dyndata.selected = getSelected();
+ dyndata.scrollpos = m_scrollbar->getPos();
+ dyndata.keynav_time = m_keynav_time;
+ dyndata.keynav_buffer = m_keynav_buffer;
+ if (m_has_tree_column)
+ getOpenedTrees(dyndata.opened_trees);
+ return dyndata;
+}
+
+void GUITable::setDynamicData(const DynamicData &dyndata)
+{
+ if (m_has_tree_column)
+ setOpenedTrees(dyndata.opened_trees);
+
+ m_keynav_time = dyndata.keynav_time;
+ m_keynav_buffer = dyndata.keynav_buffer;
+
+ setSelected(dyndata.selected);
+ m_sel_column = 0;
+ m_sel_doubleclick = false;
+
+ m_scrollbar->setPos(dyndata.scrollpos);
+}
+
+const c8* GUITable::getTypeName() const
+{
+ return "GUITable";
+}
+
+void GUITable::updateAbsolutePosition()
+{
+ IGUIElement::updateAbsolutePosition();
+ updateScrollBar();
+}
+
+void GUITable::draw()
+{
+ if (!IsVisible)
+ return;
+
+ gui::IGUISkin *skin = Environment->getSkin();
+
+ // draw background
+
+ bool draw_background = m_background.getAlpha() > 0;
+ if (m_border)
+ skin->draw3DSunkenPane(this, m_background,
+ true, draw_background,
+ AbsoluteRect, &AbsoluteClippingRect);
+ else if (draw_background)
+ skin->draw2DRectangle(this, m_background,
+ AbsoluteRect, &AbsoluteClippingRect);
+
+ // get clipping rect
+
+ core::rect<s32> client_clip(AbsoluteRect);
+ client_clip.UpperLeftCorner.Y += 1;
+ client_clip.UpperLeftCorner.X += 1;
+ client_clip.LowerRightCorner.Y -= 1;
+ client_clip.LowerRightCorner.X -= 1;
+ if (m_scrollbar->isVisible()) {
+ client_clip.LowerRightCorner.X =
+ m_scrollbar->getAbsolutePosition().UpperLeftCorner.X;
+ }
+ client_clip.clipAgainst(AbsoluteClippingRect);
+
+ // draw visible rows
+
+ s32 scrollpos = m_scrollbar->getPos();
+ s32 row_min = scrollpos / m_rowheight;
+ s32 row_max = (scrollpos + AbsoluteRect.getHeight() - 1)
+ / m_rowheight + 1;
+ row_max = MYMIN(row_max, (s32) m_visible_rows.size());
+
+ core::rect<s32> row_rect(AbsoluteRect);
+ if (m_scrollbar->isVisible())
+ row_rect.LowerRightCorner.X -=
+ skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
+ row_rect.UpperLeftCorner.Y += row_min * m_rowheight - scrollpos;
+ row_rect.LowerRightCorner.Y = row_rect.UpperLeftCorner.Y + m_rowheight;
+
+ for (s32 i = row_min; i < row_max; ++i) {
+ Row *row = &m_rows[m_visible_rows[i]];
+ bool is_sel = i == m_selected;
+ video::SColor color = m_color;
+
+ if (is_sel) {
+ skin->draw2DRectangle(this, m_highlight, row_rect, &client_clip);
+ color = m_highlight_text;
+ }
+
+ for (s32 j = 0; j < row->cellcount; ++j)
+ drawCell(&row->cells[j], color, row_rect, client_clip);
+
+ row_rect.UpperLeftCorner.Y += m_rowheight;
+ row_rect.LowerRightCorner.Y += m_rowheight;
+ }
+
+ // Draw children
+ IGUIElement::draw();
+}
+
+void GUITable::drawCell(const Cell *cell, video::SColor color,
+ const core::rect<s32> &row_rect,
+ const core::rect<s32> &client_clip)
+{
+ if ((cell->content_type == COLUMN_TYPE_TEXT)
+ || (cell->content_type == COLUMN_TYPE_TREE)) {
+
+ core::rect<s32> text_rect = row_rect;
+ text_rect.UpperLeftCorner.X = row_rect.UpperLeftCorner.X
+ + cell->xpos;
+ text_rect.LowerRightCorner.X = row_rect.UpperLeftCorner.X
+ + cell->xmax;
+
+ if (cell->color_defined)
+ color = cell->color;
+
+ if (m_font) {
+ if (cell->content_type == COLUMN_TYPE_TEXT)
+ m_font->draw(m_strings[cell->content_index],
+ text_rect, color,
+ false, true, &client_clip);
+ else // tree
+ m_font->draw(cell->content_index ? L"+" : L"-",
+ text_rect, color,
+ false, true, &client_clip);
+ }
+ }
+ else if (cell->content_type == COLUMN_TYPE_IMAGE) {
+
+ if (cell->content_index < 0)
+ return;
+
+ video::IVideoDriver *driver = Environment->getVideoDriver();
+ video::ITexture *image = m_images[cell->content_index];
+
+ if (image) {
+ core::position2d<s32> dest_pos =
+ row_rect.UpperLeftCorner;
+ dest_pos.X += cell->xpos;
+ core::rect<s32> source_rect(
+ core::position2d<s32>(0, 0),
+ image->getOriginalSize());
+ s32 imgh = source_rect.LowerRightCorner.Y;
+ s32 rowh = row_rect.getHeight();
+ if (imgh < rowh)
+ dest_pos.Y += (rowh - imgh) / 2;
+ else
+ source_rect.LowerRightCorner.Y = rowh;
+
+ video::SColor color(255, 255, 255, 255);
+
+ driver->draw2DImage(image, dest_pos, source_rect,
+ &client_clip, color, true);
+ }
+ }
+}
+
+bool GUITable::OnEvent(const SEvent &event)
+{
+ if (!isEnabled())
+ return IGUIElement::OnEvent(event);
+
+ if (event.EventType == EET_KEY_INPUT_EVENT) {
+ if (event.KeyInput.PressedDown && (
+ event.KeyInput.Key == KEY_DOWN ||
+ event.KeyInput.Key == KEY_UP ||
+ event.KeyInput.Key == KEY_HOME ||
+ event.KeyInput.Key == KEY_END ||
+ event.KeyInput.Key == KEY_NEXT ||
+ event.KeyInput.Key == KEY_PRIOR)) {
+ s32 offset = 0;
+ switch (event.KeyInput.Key) {
+ case KEY_DOWN:
+ offset = 1;
+ break;
+ case KEY_UP:
+ offset = -1;
+ break;
+ case KEY_HOME:
+ offset = - (s32) m_visible_rows.size();
+ break;
+ case KEY_END:
+ offset = m_visible_rows.size();
+ break;
+ case KEY_NEXT:
+ offset = AbsoluteRect.getHeight() / m_rowheight;
+ break;
+ case KEY_PRIOR:
+ offset = - (s32) (AbsoluteRect.getHeight() / m_rowheight);
+ break;
+ default:
+ break;
+ }
+ s32 old_selected = m_selected;
+ s32 rowcount = m_visible_rows.size();
+ if (rowcount != 0) {
+ m_selected = rangelim(m_selected + offset, 0, rowcount-1);
+ autoScroll();
+ }
+
+ if (m_selected != old_selected)
+ sendTableEvent(0, false);
+
+ return true;
+ }
+
+ if (event.KeyInput.PressedDown && (
+ event.KeyInput.Key == KEY_LEFT ||
+ event.KeyInput.Key == KEY_RIGHT)) {
+ // Open/close subtree via keyboard
+ if (m_selected >= 0) {
+ int dir = event.KeyInput.Key == KEY_LEFT ? -1 : 1;
+ toggleVisibleTree(m_selected, dir, true);
+ }
+ return true;
+ }
+ else if (!event.KeyInput.PressedDown && (
+ event.KeyInput.Key == KEY_RETURN ||
+ event.KeyInput.Key == KEY_SPACE)) {
+ sendTableEvent(0, true);
+ return true;
+ }
+ else if (event.KeyInput.Key == KEY_ESCAPE ||
+ event.KeyInput.Key == KEY_SPACE) {
+ // pass to parent
+ }
+ else if (event.KeyInput.PressedDown && event.KeyInput.Char) {
+ // change selection based on text as it is typed
+ u64 now = porting::getTimeMs();
+ if (now - m_keynav_time >= 500)
+ m_keynav_buffer = L"";
+ m_keynav_time = now;
+
+ // add to key buffer if not a key repeat
+ if (!(m_keynav_buffer.size() == 1 &&
+ m_keynav_buffer[0] == event.KeyInput.Char)) {
+ m_keynav_buffer.append(event.KeyInput.Char);
+ }
+
+ // find the selected item, starting at the current selection
+ // don't change selection if the key buffer matches the current item
+ s32 old_selected = m_selected;
+ s32 start = MYMAX(m_selected, 0);
+ s32 rowcount = m_visible_rows.size();
+ for (s32 k = 1; k < rowcount; ++k) {
+ s32 current = start + k;
+ if (current >= rowcount)
+ current -= rowcount;
+ if (doesRowStartWith(getRow(current), m_keynav_buffer)) {
+ m_selected = current;
+ break;
+ }
+ }
+ autoScroll();
+ if (m_selected != old_selected)
+ sendTableEvent(0, false);
+
+ return true;
+ }
+ }
+ if (event.EventType == EET_MOUSE_INPUT_EVENT) {
+ core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
+
+ if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
+ m_scrollbar->setPos(m_scrollbar->getPos() +
+ (event.MouseInput.Wheel < 0 ? -3 : 3) *
+ - (s32) m_rowheight / 2);
+ return true;
+ }
+
+ // Find hovered row and cell
+ bool really_hovering = false;
+ s32 row_i = getRowAt(p.Y, really_hovering);
+ const Cell *cell = NULL;
+ if (really_hovering) {
+ s32 cell_j = getCellAt(p.X, row_i);
+ if (cell_j >= 0)
+ cell = &(getRow(row_i)->cells[cell_j]);
+ }
+
+ // Update tooltip
+ setToolTipText(cell ? m_strings[cell->tooltip_index].c_str() : L"");
+
+ // Fix for #1567/#1806:
+ // IGUIScrollBar passes double click events to its parent,
+ // which we don't want. Detect this case and discard the event
+ if (event.MouseInput.Event != EMIE_MOUSE_MOVED &&
+ m_scrollbar->isVisible() &&
+ m_scrollbar->isPointInside(p))
+ return true;
+
+ if (event.MouseInput.isLeftPressed() &&
+ (isPointInside(p) ||
+ event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
+ s32 sel_column = 0;
+ bool sel_doubleclick = (event.MouseInput.Event
+ == EMIE_LMOUSE_DOUBLE_CLICK);
+ bool plusminus_clicked = false;
+
+ // For certain events (left click), report column
+ // Also open/close subtrees when the +/- is clicked
+ if (cell && (
+ event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN ||
+ event.MouseInput.Event == EMIE_LMOUSE_DOUBLE_CLICK ||
+ event.MouseInput.Event == EMIE_LMOUSE_TRIPLE_CLICK)) {
+ sel_column = cell->reported_column;
+ if (cell->content_type == COLUMN_TYPE_TREE)
+ plusminus_clicked = true;
+ }
+
+ if (plusminus_clicked) {
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ toggleVisibleTree(row_i, 0, false);
+ }
+ }
+ else {
+ // Normal selection
+ s32 old_selected = m_selected;
+ m_selected = row_i;
+ autoScroll();
+
+ if (m_selected != old_selected ||
+ sel_column >= 1 ||
+ sel_doubleclick) {
+ sendTableEvent(sel_column, sel_doubleclick);
+ }
+
+ // Treeview: double click opens/closes trees
+ if (m_has_tree_column && sel_doubleclick) {
+ toggleVisibleTree(m_selected, 0, false);
+ }
+ }
+ }
+ return true;
+ }
+ if (event.EventType == EET_GUI_EVENT &&
+ event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED &&
+ event.GUIEvent.Caller == m_scrollbar) {
+ // Don't pass events from our scrollbar to the parent
+ return true;
+ }
+
+ return IGUIElement::OnEvent(event);
+}
+
+/******************************************************************************/
+/* GUITable helper functions */
+/******************************************************************************/
+
+s32 GUITable::allocString(const std::string &text)
+{
+ std::map<std::string, s32>::iterator it = m_alloc_strings.find(text);
+ if (it == m_alloc_strings.end()) {
+ s32 id = m_strings.size();
+ std::wstring wtext = utf8_to_wide(text);
+ m_strings.emplace_back(wtext.c_str());
+ m_alloc_strings.insert(std::make_pair(text, id));
+ return id;
+ }
+
+ return it->second;
+}
+
+s32 GUITable::allocImage(const std::string &imagename)
+{
+ std::map<std::string, s32>::iterator it = m_alloc_images.find(imagename);
+ if (it == m_alloc_images.end()) {
+ s32 id = m_images.size();
+ m_images.push_back(m_tsrc->getTexture(imagename));
+ m_alloc_images.insert(std::make_pair(imagename, id));
+ return id;
+ }
+
+ return it->second;
+}
+
+void GUITable::allocationComplete()
+{
+ // Called when done with creating rows and cells from table data,
+ // i.e. when allocString and allocImage won't be called anymore
+ m_alloc_strings.clear();
+ m_alloc_images.clear();
+}
+
+const GUITable::Row* GUITable::getRow(s32 i) const
+{
+ if (i >= 0 && i < (s32) m_visible_rows.size())
+ return &m_rows[m_visible_rows[i]];
+
+ return NULL;
+}
+
+bool GUITable::doesRowStartWith(const Row *row, const core::stringw &str) const
+{
+ if (row == NULL)
+ return false;
+
+ for (s32 j = 0; j < row->cellcount; ++j) {
+ Cell *cell = &row->cells[j];
+ if (cell->content_type == COLUMN_TYPE_TEXT) {
+ const core::stringw &cellstr = m_strings[cell->content_index];
+ if (cellstr.size() >= str.size() &&
+ str.equals_ignore_case(cellstr.subString(0, str.size())))
+ return true;
+ }
+ }
+ return false;
+}
+
+s32 GUITable::getRowAt(s32 y, bool &really_hovering) const
+{
+ really_hovering = false;
+
+ s32 rowcount = m_visible_rows.size();
+ if (rowcount == 0)
+ return -1;
+
+ // Use arithmetic to find row
+ s32 rel_y = y - AbsoluteRect.UpperLeftCorner.Y - 1;
+ s32 i = (rel_y + m_scrollbar->getPos()) / m_rowheight;
+
+ if (i >= 0 && i < rowcount) {
+ really_hovering = true;
+ return i;
+ }
+ if (i < 0)
+ return 0;
+
+ return rowcount - 1;
+}
+
+s32 GUITable::getCellAt(s32 x, s32 row_i) const
+{
+ const Row *row = getRow(row_i);
+ if (row == NULL)
+ return -1;
+
+ // Use binary search to find cell in row
+ s32 rel_x = x - AbsoluteRect.UpperLeftCorner.X - 1;
+ s32 jmin = 0;
+ s32 jmax = row->cellcount - 1;
+ while (jmin < jmax) {
+ s32 pivot = jmin + (jmax - jmin) / 2;
+ assert(pivot >= 0 && pivot < row->cellcount);
+ const Cell *cell = &row->cells[pivot];
+
+ if (rel_x >= cell->xmin && rel_x <= cell->xmax)
+ return pivot;
+
+ if (rel_x < cell->xmin)
+ jmax = pivot - 1;
+ else
+ jmin = pivot + 1;
+ }
+
+ if (jmin >= 0 && jmin < row->cellcount &&
+ rel_x >= row->cells[jmin].xmin &&
+ rel_x <= row->cells[jmin].xmax)
+ return jmin;
+
+ return -1;
+}
+
+void GUITable::autoScroll()
+{
+ if (m_selected >= 0) {
+ s32 pos = m_scrollbar->getPos();
+ s32 maxpos = m_selected * m_rowheight;
+ s32 minpos = maxpos - (AbsoluteRect.getHeight() - m_rowheight);
+ if (pos > maxpos)
+ m_scrollbar->setPos(maxpos);
+ else if (pos < minpos)
+ m_scrollbar->setPos(minpos);
+ }
+}
+
+void GUITable::updateScrollBar()
+{
+ s32 totalheight = m_rowheight * m_visible_rows.size();
+ s32 scrollmax = MYMAX(0, totalheight - AbsoluteRect.getHeight());
+ m_scrollbar->setVisible(scrollmax > 0);
+ m_scrollbar->setMax(scrollmax);
+ m_scrollbar->setSmallStep(m_rowheight);
+ m_scrollbar->setLargeStep(2 * m_rowheight);
+}
+
+void GUITable::sendTableEvent(s32 column, bool doubleclick)
+{
+ m_sel_column = column;
+ m_sel_doubleclick = doubleclick;
+ if (Parent) {
+ SEvent e;
+ memset(&e, 0, sizeof e);
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = 0;
+ e.GUIEvent.EventType = gui::EGET_TABLE_CHANGED;
+ Parent->OnEvent(e);
+ }
+}
+
+void GUITable::getOpenedTrees(std::set<s32> &opened_trees) const
+{
+ opened_trees.clear();
+ s32 rowcount = m_rows.size();
+ for (s32 i = 0; i < rowcount - 1; ++i) {
+ if (m_rows[i].indent < m_rows[i+1].indent &&
+ m_rows[i+1].visible_index != -2)
+ opened_trees.insert(i);
+ }
+}
+
+void GUITable::setOpenedTrees(const std::set<s32> &opened_trees)
+{
+ s32 old_selected = -1;
+ if (m_selected >= 0)
+ old_selected = m_visible_rows[m_selected];
+
+ std::vector<s32> parents;
+ std::vector<s32> closed_parents;
+
+ m_visible_rows.clear();
+
+ for (size_t i = 0; i < m_rows.size(); ++i) {
+ Row *row = &m_rows[i];
+
+ // Update list of ancestors
+ while (!parents.empty() && m_rows[parents.back()].indent >= row->indent)
+ parents.pop_back();
+ while (!closed_parents.empty() &&
+ m_rows[closed_parents.back()].indent >= row->indent)
+ closed_parents.pop_back();
+
+ assert(closed_parents.size() <= parents.size());
+
+ if (closed_parents.empty()) {
+ // Visible row
+ row->visible_index = m_visible_rows.size();
+ m_visible_rows.push_back(i);
+ }
+ else if (parents.back() == closed_parents.back()) {
+ // Invisible row, direct parent is closed
+ row->visible_index = -2;
+ }
+ else {
+ // Invisible row, direct parent is open, some ancestor is closed
+ row->visible_index = -1;
+ }
+
+ // If not a leaf, add to parents list
+ if (i < m_rows.size()-1 && row->indent < m_rows[i+1].indent) {
+ parents.push_back(i);
+
+ s32 content_index = 0; // "-", open
+ if (opened_trees.count(i) == 0) {
+ closed_parents.push_back(i);
+ content_index = 1; // "+", closed
+ }
+
+ // Update all cells of type "tree"
+ for (s32 j = 0; j < row->cellcount; ++j)
+ if (row->cells[j].content_type == COLUMN_TYPE_TREE)
+ row->cells[j].content_index = content_index;
+ }
+ }
+
+ updateScrollBar();
+
+ // m_selected must be updated since it is a visible row index
+ if (old_selected >= 0)
+ m_selected = m_rows[old_selected].visible_index;
+}
+
+void GUITable::openTree(s32 to_open)
+{
+ std::set<s32> opened_trees;
+ getOpenedTrees(opened_trees);
+ opened_trees.insert(to_open);
+ setOpenedTrees(opened_trees);
+}
+
+void GUITable::closeTree(s32 to_close)
+{
+ std::set<s32> opened_trees;
+ getOpenedTrees(opened_trees);
+ opened_trees.erase(to_close);
+ setOpenedTrees(opened_trees);
+}
+
+// The following function takes a visible row index (hidden rows skipped)
+// dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
+void GUITable::toggleVisibleTree(s32 row_i, int dir, bool move_selection)
+{
+ // Check if the chosen tree is currently open
+ const Row *row = getRow(row_i);
+ if (row == NULL)
+ return;
+
+ bool was_open = false;
+ for (s32 j = 0; j < row->cellcount; ++j) {
+ if (row->cells[j].content_type == COLUMN_TYPE_TREE) {
+ was_open = row->cells[j].content_index == 0;
+ break;
+ }
+ }
+
+ // Check if the chosen tree should be opened
+ bool do_open = !was_open;
+ if (dir < 0)
+ do_open = false;
+ else if (dir > 0)
+ do_open = true;
+
+ // Close or open the tree; the heavy lifting is done by setOpenedTrees
+ if (was_open && !do_open)
+ closeTree(m_visible_rows[row_i]);
+ else if (!was_open && do_open)
+ openTree(m_visible_rows[row_i]);
+
+ // Change selected row if requested by caller,
+ // this is useful for keyboard navigation
+ if (move_selection) {
+ s32 sel = row_i;
+ if (was_open && do_open) {
+ // Move selection to first child
+ const Row *maybe_child = getRow(sel + 1);
+ if (maybe_child && maybe_child->indent > row->indent)
+ sel++;
+ }
+ else if (!was_open && !do_open) {
+ // Move selection to parent
+ assert(getRow(sel) != NULL);
+ while (sel > 0 && getRow(sel - 1)->indent >= row->indent)
+ sel--;
+ sel--;
+ if (sel < 0) // was root already selected?
+ sel = row_i;
+ }
+ if (sel != m_selected) {
+ m_selected = sel;
+ autoScroll();
+ sendTableEvent(0, false);
+ }
+ }
+}
+
+void GUITable::alignContent(Cell *cell, s32 xmax, s32 content_width, s32 align)
+{
+ // requires that cell.xmin, cell.xmax are properly set
+ // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
+ if (align == 0) {
+ cell->xpos = cell->xmin;
+ cell->xmax = xmax;
+ }
+ else if (align == 1) {
+ cell->xpos = (cell->xmin + xmax - content_width) / 2;
+ cell->xmax = xmax;
+ }
+ else if (align == 2) {
+ cell->xpos = xmax - content_width;
+ cell->xmax = xmax;
+ }
+ else {
+ // inline alignment: the cells of the column don't have an aligned
+ // right border, the right border of each cell depends on the content
+ cell->xpos = cell->xmin;
+ cell->xmax = cell->xmin + content_width;
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+#include <iostream>
+
+#include "irrlichttypes_extrabloated.h"
+
+class ISimpleTextureSource;
+
+/*
+ A table GUI element for GUIFormSpecMenu.
+
+ Sends a EGET_TABLE_CHANGED event to the parent when
+ an item is selected or double-clicked.
+ Call checkEvent() to get info.
+
+ Credits: The interface and implementation of this class are (very)
+ loosely based on the Irrlicht classes CGUITable and CGUIListBox.
+ CGUITable and CGUIListBox are licensed under the Irrlicht license;
+ they are Copyright (C) 2002-2012 Nikolaus Gebhardt
+*/
+class GUITable : public gui::IGUIElement
+{
+public:
+ /*
+ Stores dynamic data that should be preserved
+ when updating a formspec
+ */
+ struct DynamicData
+ {
+ s32 selected = 0;
+ s32 scrollpos = 0;
+ s32 keynav_time = 0;
+ core::stringw keynav_buffer;
+ std::set<s32> opened_trees;
+ };
+
+ /*
+ An option of the form <name>=<value>
+ */
+ struct Option
+ {
+ std::string name;
+ std::string value;
+
+ Option(const std::string &name_, const std::string &value_) :
+ name(name_),
+ value(value_)
+ {}
+ };
+
+ /*
+ A list of options that concern the entire table
+ */
+ typedef std::vector<Option> TableOptions;
+
+ /*
+ A column with options
+ */
+ struct TableColumn
+ {
+ std::string type;
+ std::vector<Option> options;
+ };
+ typedef std::vector<TableColumn> TableColumns;
+
+
+ GUITable(gui::IGUIEnvironment *env,
+ gui::IGUIElement *parent, s32 id,
+ core::rect<s32> rectangle,
+ ISimpleTextureSource *tsrc);
+
+ virtual ~GUITable();
+
+ /* Split a string of the form "name=value" into name and value */
+ static Option splitOption(const std::string &str);
+
+ /* Set textlist-like options, columns and data */
+ void setTextList(const std::vector<std::string> &content,
+ bool transparent);
+
+ /* Set generic table options, columns and content */
+ // Adds empty strings to end of content if there is an incomplete row
+ void setTable(const TableOptions &options,
+ const TableColumns &columns,
+ std::vector<std::string> &content);
+
+ /* Clear the table */
+ void clear();
+
+ /* Get info about last event (string such as "CHG:1:2") */
+ // Call this after EGET_TABLE_CHANGED
+ std::string checkEvent();
+
+ /* Get index of currently selected row (first=1; 0 if none selected) */
+ s32 getSelected() const;
+
+ /* Set currently selected row (first=1; 0 if none selected) */
+ // If given index is not visible at the moment, select its parent
+ // Autoscroll to make the selected row fully visible
+ void setSelected(s32 index);
+
+ /* Get selection, scroll position and opened (sub)trees */
+ DynamicData getDynamicData() const;
+
+ /* Set selection, scroll position and opened (sub)trees */
+ void setDynamicData(const DynamicData &dyndata);
+
+ /* Returns "GUITable" */
+ virtual const c8* getTypeName() const;
+
+ /* Must be called when position or size changes */
+ virtual void updateAbsolutePosition();
+
+ /* Irrlicht draw method */
+ virtual void draw();
+
+ /* Irrlicht event handler */
+ virtual bool OnEvent(const SEvent &event);
+
+protected:
+ enum ColumnType {
+ COLUMN_TYPE_TEXT,
+ COLUMN_TYPE_IMAGE,
+ COLUMN_TYPE_COLOR,
+ COLUMN_TYPE_INDENT,
+ COLUMN_TYPE_TREE,
+ };
+
+ struct Cell {
+ s32 xmin;
+ s32 xmax;
+ s32 xpos;
+ ColumnType content_type;
+ s32 content_index;
+ s32 tooltip_index;
+ video::SColor color;
+ bool color_defined;
+ s32 reported_column;
+ };
+
+ struct Row {
+ Cell *cells;
+ s32 cellcount;
+ s32 indent;
+ // visible_index >= 0: is index of row in m_visible_rows
+ // visible_index == -1: parent open but other ancestor closed
+ // visible_index == -2: parent closed
+ s32 visible_index;
+ };
+
+ // Texture source
+ ISimpleTextureSource *m_tsrc;
+
+ // Table content (including hidden rows)
+ std::vector<Row> m_rows;
+ // Table content (only visible; indices into m_rows)
+ std::vector<s32> m_visible_rows;
+ bool m_is_textlist = false;
+ bool m_has_tree_column = false;
+
+ // Selection status
+ s32 m_selected = -1; // index of row (1...n), or 0 if none selected
+ s32 m_sel_column = 0;
+ bool m_sel_doubleclick = false;
+
+ // Keyboard navigation stuff
+ u64 m_keynav_time = 0;
+ core::stringw m_keynav_buffer = L"";
+
+ // Drawing and geometry information
+ bool m_border = true;
+ video::SColor m_color = video::SColor(255, 255, 255, 255);
+ video::SColor m_background = video::SColor(255, 0, 0, 0);
+ video::SColor m_highlight = video::SColor(255, 70, 100, 50);
+ video::SColor m_highlight_text = video::SColor(255, 255, 255, 255);
+ s32 m_rowheight = 1;
+ gui::IGUIFont *m_font = nullptr;
+ gui::IGUIScrollBar *m_scrollbar = nullptr;
+
+ // Allocated strings and images
+ std::vector<core::stringw> m_strings;
+ std::vector<video::ITexture*> m_images;
+ std::map<std::string, s32> m_alloc_strings;
+ std::map<std::string, s32> m_alloc_images;
+
+ s32 allocString(const std::string &text);
+ s32 allocImage(const std::string &imagename);
+ void allocationComplete();
+
+ // Helper for draw() that draws a single cell
+ void drawCell(const Cell *cell, video::SColor color,
+ const core::rect<s32> &rowrect,
+ const core::rect<s32> &client_clip);
+
+ // Returns the i-th visible row (NULL if i is invalid)
+ const Row *getRow(s32 i) const;
+
+ // Key navigation helper
+ bool doesRowStartWith(const Row *row, const core::stringw &str) const;
+
+ // Returns the row at a given screen Y coordinate
+ // Returns index i such that m_rows[i] is valid (or -1 on error)
+ s32 getRowAt(s32 y, bool &really_hovering) const;
+
+ // Returns the cell at a given screen X coordinate within m_rows[row_i]
+ // Returns index j such that m_rows[row_i].cells[j] is valid
+ // (or -1 on error)
+ s32 getCellAt(s32 x, s32 row_i) const;
+
+ // Make the selected row fully visible
+ void autoScroll();
+
+ // Should be called when m_rowcount or m_rowheight changes
+ void updateScrollBar();
+
+ // Sends EET_GUI_EVENT / EGET_TABLE_CHANGED to parent
+ void sendTableEvent(s32 column, bool doubleclick);
+
+ // Functions that help deal with hidden rows
+ // The following functions take raw row indices (hidden rows not skipped)
+ void getOpenedTrees(std::set<s32> &opened_trees) const;
+ void setOpenedTrees(const std::set<s32> &opened_trees);
+ void openTree(s32 to_open);
+ void closeTree(s32 to_close);
+ // The following function takes a visible row index (hidden rows skipped)
+ // dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
+ void toggleVisibleTree(s32 row_i, int dir, bool move_selection);
+
+ // Aligns cell content in column according to alignment specification
+ // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
+ static void alignContent(Cell *cell, s32 xmax, s32 content_width,
+ s32 align);
+};
--- /dev/null
+/*
+Part of Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "guiVolumeChange.h"
+#include "debug.h"
+#include "serialization.h"
+#include <string>
+#include <IGUICheckBox.h>
+#include <IGUIButton.h>
+#include <IGUIScrollBar.h>
+#include <IGUIStaticText.h>
+#include <IGUIFont.h>
+#include "settings.h"
+
+#include "gettext.h"
+
+const int ID_soundText = 263;
+const int ID_soundExitButton = 264;
+const int ID_soundSlider = 265;
+const int ID_soundMuteButton = 266;
+
+GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr
+):
+ GUIModalMenu(env, parent, id, menumgr)
+{
+}
+
+GUIVolumeChange::~GUIVolumeChange()
+{
+ removeChildren();
+}
+
+void GUIVolumeChange::removeChildren()
+{
+ if (gui::IGUIElement *e = getElementFromId(ID_soundText))
+ e->remove();
+
+ if (gui::IGUIElement *e = getElementFromId(ID_soundExitButton))
+ e->remove();
+
+ if (gui::IGUIElement *e = getElementFromId(ID_soundSlider))
+ e->remove();
+}
+
+void GUIVolumeChange::regenerateGui(v2u32 screensize)
+{
+ /*
+ Remove stuff
+ */
+ removeChildren();
+
+ /*
+ Calculate new sizes and positions
+ */
+ DesiredRect = core::rect<s32>(
+ screensize.X/2 - 380/2,
+ screensize.Y/2 - 200/2,
+ screensize.X/2 + 380/2,
+ screensize.Y/2 + 200/2
+ );
+ recalculateAbsolutePosition(false);
+
+ v2s32 size = DesiredRect.getSize();
+ int volume = (int)(g_settings->getFloat("sound_volume") * 100);
+
+ /*
+ Add stuff
+ */
+ {
+ core::rect<s32> rect(0, 0, 160, 20);
+ rect = rect + v2s32(size.X / 2 - 80, size.Y / 2 - 70);
+
+ const wchar_t *text = wgettext("Sound Volume: ");
+ core::stringw volume_text = text;
+ delete [] text;
+
+ volume_text += core::stringw(volume) + core::stringw("%");
+ Environment->addStaticText(volume_text.c_str(), rect, false,
+ true, this, ID_soundText);
+ }
+ {
+ core::rect<s32> rect(0, 0, 80, 30);
+ rect = rect + v2s32(size.X/2-80/2, size.Y/2+55);
+ const wchar_t *text = wgettext("Exit");
+ Environment->addButton(rect, this, ID_soundExitButton,
+ text);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 300, 20);
+ rect = rect + v2s32(size.X / 2 - 150, size.Y / 2);
+ gui::IGUIScrollBar *e = Environment->addScrollBar(true,
+ rect, this, ID_soundSlider);
+ e->setMax(100);
+ e->setPos(volume);
+ }
+ {
+ core::rect<s32> rect(0, 0, 160, 20);
+ rect = rect + v2s32(size.X / 2 - 80, size.Y / 2 - 35);
+ const wchar_t *text = wgettext("Muted");
+ Environment->addCheckBox(g_settings->getBool("mute_sound"), rect, this,
+ ID_soundMuteButton, text);
+ delete[] text;
+ }
+}
+
+void GUIVolumeChange::drawMenu()
+{
+ gui::IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ video::SColor bgcolor(140, 0, 0, 0);
+ driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
+ gui::IGUIElement::draw();
+}
+
+bool GUIVolumeChange::OnEvent(const SEvent& event)
+{
+ if (event.EventType == EET_KEY_INPUT_EVENT) {
+ if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
+ quitMenu();
+ return true;
+ }
+
+ if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
+ quitMenu();
+ return true;
+ }
+ } else if (event.EventType == EET_GUI_EVENT) {
+ if (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) {
+ gui::IGUIElement *e = getElementFromId(ID_soundMuteButton);
+ if (e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) {
+ g_settings->setBool("mute_sound", ((gui::IGUICheckBox*)e)->isChecked());
+ }
+
+ Environment->setFocus(this);
+ return true;
+ }
+
+ if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
+ if (event.GUIEvent.Caller->getID() == ID_soundExitButton) {
+ quitMenu();
+ return true;
+ }
+ Environment->setFocus(this);
+ }
+
+ if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
+ && isVisible()) {
+ if (!canTakeFocus(event.GUIEvent.Element)) {
+ dstream << "GUIMainMenu: Not allowing focus change."
+ << std::endl;
+ // Returning true disables focus change
+ return true;
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
+ if (event.GUIEvent.Caller->getID() == ID_soundSlider) {
+ s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
+ g_settings->setFloat("sound_volume", (float) pos / 100);
+
+ gui::IGUIElement *e = getElementFromId(ID_soundText);
+ const wchar_t *text = wgettext("Sound Volume: ");
+ core::stringw volume_text = text;
+ delete [] text;
+
+ volume_text += core::stringw(pos) + core::stringw("%");
+ e->setText(volume_text.c_str());
+ return true;
+ }
+ }
+
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
--- /dev/null
+/*
+Part of Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include <string>
+
+class GUIVolumeChange : public GUIModalMenu
+{
+public:
+ GUIVolumeChange(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr);
+ ~GUIVolumeChange();
+
+ void removeChildren();
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ void drawMenu();
+
+ bool OnEvent(const SEvent& event);
+
+ bool pausesGame() { return true; }
+};
--- /dev/null
+// 11.11.2011 11:11 ValkaTR
+//
+// This is a copy of intlGUIEditBox from the irrlicht, but with a
+// fix in the OnEvent function, which doesn't allowed input of
+// other keyboard layouts than latin-1
+//
+// Characters like: ä ö ü õ ы й ю я ъ № € ° ...
+//
+// This fix is only needed for linux, because of a bug
+// in the CIrrDeviceLinux.cpp:1014-1015 of the irrlicht
+//
+// Also locale in the programm should not be changed to
+// a "C", "POSIX" or whatever, it should be set to "",
+// or XLookupString will return nothing for the international
+// characters.
+//
+// From the "man setlocale":
+//
+// On startup of the main program, the portable "C" locale
+// is selected as default. A program may be made
+// portable to all locales by calling:
+//
+// setlocale(LC_ALL, "");
+//
+// after program initialization....
+//
+
+// Copyright (C) 2002-2013 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include <util/numeric.h>
+#include "intlGUIEditBox.h"
+
+#if defined(_IRR_COMPILE_WITH_GUI_) && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
+
+#include "IGUISkin.h"
+#include "IGUIEnvironment.h"
+#include "IGUIFont.h"
+#include "IVideoDriver.h"
+//#include "rect.h"
+//#include "irrlicht/os.cpp"
+#include "porting.h"
+//#include "Keycodes.h"
+#include "log.h"
+
+/*
+ todo:
+ optional scrollbars
+ ctrl+left/right to select word
+ double click/ctrl click: word select + drag to select whole words, triple click to select line
+ optional? dragging selected text
+ numerical
+*/
+
+namespace irr
+{
+namespace gui
+{
+
+//! constructor
+intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border,
+ IGUIEnvironment* environment, IGUIElement* parent, s32 id,
+ const core::rect<s32>& rectangle, bool writable, bool has_vscrollbar)
+ : IGUIEditBox(environment, parent, id, rectangle),
+ Border(border), FrameRect(rectangle),
+ m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable)
+{
+ #ifdef _DEBUG
+ setDebugName("intlintlGUIEditBox");
+ #endif
+
+ Text = text;
+
+ if (Environment)
+ Operator = Environment->getOSOperator();
+
+ if (Operator)
+ Operator->grab();
+
+ // this element can be tabbed to
+ setTabStop(true);
+ setTabOrder(-1);
+
+ IGUISkin *skin = 0;
+ if (Environment)
+ skin = Environment->getSkin();
+ if (Border && skin)
+ {
+ FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
+ FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
+ FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
+ FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
+ }
+
+ if (skin && has_vscrollbar) {
+ m_scrollbar_width = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
+
+ if (m_scrollbar_width > 0) {
+ createVScrollBar();
+ }
+ }
+
+ breakText();
+
+ calculateScrollPos();
+ setWritable(writable);
+}
+
+
+//! destructor
+intlGUIEditBox::~intlGUIEditBox()
+{
+ if (OverrideFont)
+ OverrideFont->drop();
+
+ if (Operator)
+ Operator->drop();
+}
+
+
+//! Sets another skin independent font.
+void intlGUIEditBox::setOverrideFont(IGUIFont* font)
+{
+ if (OverrideFont == font)
+ return;
+
+ if (OverrideFont)
+ OverrideFont->drop();
+
+ OverrideFont = font;
+
+ if (OverrideFont)
+ OverrideFont->grab();
+
+ breakText();
+}
+
+IGUIFont * intlGUIEditBox::getOverrideFont() const
+{
+ return OverrideFont;
+}
+
+//! Get the font which is used right now for drawing
+IGUIFont* intlGUIEditBox::getActiveFont() const
+{
+ if ( OverrideFont )
+ return OverrideFont;
+ IGUISkin* skin = Environment->getSkin();
+ if (skin)
+ return skin->getFont();
+ return 0;
+}
+
+//! Sets another color for the text.
+void intlGUIEditBox::setOverrideColor(video::SColor color)
+{
+ OverrideColor = color;
+ OverrideColorEnabled = true;
+}
+
+video::SColor intlGUIEditBox::getOverrideColor() const
+{
+ return OverrideColor;
+}
+
+//! Turns the border on or off
+void intlGUIEditBox::setDrawBorder(bool border)
+{
+ Border = border;
+}
+
+//! Sets whether to draw the background
+void intlGUIEditBox::setDrawBackground(bool draw)
+{
+}
+
+//! Sets if the text should use the overide color or the color in the gui skin.
+void intlGUIEditBox::enableOverrideColor(bool enable)
+{
+ OverrideColorEnabled = enable;
+}
+
+bool intlGUIEditBox::isOverrideColorEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return OverrideColorEnabled;
+}
+
+//! Enables or disables word wrap
+void intlGUIEditBox::setWordWrap(bool enable)
+{
+ WordWrap = enable;
+ breakText();
+}
+
+
+void intlGUIEditBox::updateAbsolutePosition()
+{
+ core::rect<s32> oldAbsoluteRect(AbsoluteRect);
+ IGUIElement::updateAbsolutePosition();
+ if ( oldAbsoluteRect != AbsoluteRect )
+ {
+ breakText();
+ }
+}
+
+
+//! Checks if word wrap is enabled
+bool intlGUIEditBox::isWordWrapEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return WordWrap;
+}
+
+
+//! Enables or disables newlines.
+void intlGUIEditBox::setMultiLine(bool enable)
+{
+ MultiLine = enable;
+}
+
+
+//! Checks if multi line editing is enabled
+bool intlGUIEditBox::isMultiLineEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return MultiLine;
+}
+
+
+void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar)
+{
+ PasswordBox = passwordBox;
+ if (PasswordBox)
+ {
+ PasswordChar = passwordChar;
+ setMultiLine(false);
+ setWordWrap(false);
+ BrokenText.clear();
+ }
+}
+
+
+bool intlGUIEditBox::isPasswordBox() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return PasswordBox;
+}
+
+
+//! Sets text justification
+void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
+{
+ HAlign = horizontal;
+ VAlign = vertical;
+}
+
+
+//! called if an event happened.
+bool intlGUIEditBox::OnEvent(const SEvent& event)
+{
+ if (IsEnabled)
+ {
+
+ switch(event.EventType)
+ {
+ case EET_GUI_EVENT:
+ if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
+ {
+ if (event.GUIEvent.Caller == this)
+ {
+ MouseMarking = false;
+ setTextMarkers(0,0);
+ }
+ }
+ break;
+ case EET_KEY_INPUT_EVENT:
+ {
+#if (defined(__linux__) || defined(__FreeBSD__))
+ // ################################################################
+ // ValkaTR:
+ // This part is the difference from the original intlGUIEditBox
+ // It converts UTF-8 character into a UCS-2 (wchar_t)
+ wchar_t wc = L'_';
+ mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
+
+ //printf( "char: %lc (%u) \r\n", wc, wc );
+
+ SEvent irrevent(event);
+ irrevent.KeyInput.Char = wc;
+ // ################################################################
+
+ if (processKey(irrevent))
+ return true;
+#else
+ if (processKey(event))
+ return true;
+#endif // defined(linux)
+
+ break;
+ }
+ case EET_MOUSE_INPUT_EVENT:
+ if (processMouse(event))
+ return true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return IGUIElement::OnEvent(event);
+}
+
+
+bool intlGUIEditBox::processKey(const SEvent& event)
+{
+ if (!event.KeyInput.PressedDown)
+ return false;
+
+ bool textChanged = false;
+ s32 newMarkBegin = MarkBegin;
+ s32 newMarkEnd = MarkEnd;
+
+ // control shortcut handling
+
+ if (event.KeyInput.Control)
+ {
+ // german backlash '\' entered with control + '?'
+ if ( event.KeyInput.Char == '\\' )
+ {
+ inputChar(event.KeyInput.Char);
+ return true;
+ }
+
+ switch(event.KeyInput.Key)
+ {
+ case KEY_KEY_A:
+ // select all
+ newMarkBegin = 0;
+ newMarkEnd = Text.size();
+ break;
+ case KEY_KEY_C:
+ // copy to clipboard
+ if (!PasswordBox && Operator && MarkBegin != MarkEnd)
+ {
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ core::stringc s;
+ s = Text.subString(realmbgn, realmend - realmbgn).c_str();
+ Operator->copyToClipboard(s.c_str());
+ }
+ break;
+ case KEY_KEY_X:
+ // cut to the clipboard
+ if (!PasswordBox && Operator && MarkBegin != MarkEnd)
+ {
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ // copy
+ core::stringc sc;
+ sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
+ Operator->copyToClipboard(sc.c_str());
+
+ if (IsEnabled)
+ {
+ // delete
+ core::stringw s;
+ s = Text.subString(0, realmbgn);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+ Text = s;
+
+ CursorPos = realmbgn;
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ textChanged = true;
+ }
+ }
+ break;
+ case KEY_KEY_V:
+ if ( !IsEnabled )
+ break;
+
+ // paste from the clipboard
+ if (Operator)
+ {
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ // add new character
+ const c8* p = Operator->getTextFromClipboard();
+ if (p)
+ {
+ if (MarkBegin == MarkEnd)
+ {
+ // insert text
+ core::stringw s = Text.subString(0, CursorPos);
+ s.append(p);
+ s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
+
+ if (!Max || s.size()<=Max) // thx to Fish FH for fix
+ {
+ Text = s;
+ s = p;
+ CursorPos += s.size();
+ }
+ }
+ else
+ {
+ // replace text
+
+ core::stringw s = Text.subString(0, realmbgn);
+ s.append(p);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+
+ if (!Max || s.size()<=Max) // thx to Fish FH for fix
+ {
+ Text = s;
+ s = p;
+ CursorPos = realmbgn + s.size();
+ }
+ }
+ }
+
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ textChanged = true;
+ }
+ break;
+ case KEY_HOME:
+ // move/highlight to start of text
+ if (event.KeyInput.Shift)
+ {
+ newMarkEnd = CursorPos;
+ newMarkBegin = 0;
+ CursorPos = 0;
+ }
+ else
+ {
+ CursorPos = 0;
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+ break;
+ case KEY_END:
+ // move/highlight to end of text
+ if (event.KeyInput.Shift)
+ {
+ newMarkBegin = CursorPos;
+ newMarkEnd = Text.size();
+ CursorPos = 0;
+ }
+ else
+ {
+ CursorPos = Text.size();
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ // default keyboard handling
+ else
+ switch(event.KeyInput.Key)
+ {
+ case KEY_END:
+ {
+ s32 p = Text.size();
+ if (WordWrap || MultiLine)
+ {
+ p = getLineFromPos(CursorPos);
+ p = BrokenTextPositions[p] + (s32)BrokenText[p].size();
+ if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' ))
+ p-=1;
+ }
+
+ if (event.KeyInput.Shift)
+ {
+ if (MarkBegin == MarkEnd)
+ newMarkBegin = CursorPos;
+
+ newMarkEnd = p;
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+ CursorPos = p;
+ BlinkStartTime = porting::getTimeMs();
+ }
+ break;
+ case KEY_HOME:
+ {
+
+ s32 p = 0;
+ if (WordWrap || MultiLine)
+ {
+ p = getLineFromPos(CursorPos);
+ p = BrokenTextPositions[p];
+ }
+
+ if (event.KeyInput.Shift)
+ {
+ if (MarkBegin == MarkEnd)
+ newMarkBegin = CursorPos;
+ newMarkEnd = p;
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+ CursorPos = p;
+ BlinkStartTime = porting::getTimeMs();
+ }
+ break;
+ case KEY_RETURN:
+ if (MultiLine)
+ {
+ inputChar(L'\n');
+ return true;
+ }
+ else
+ {
+ sendGuiEvent( EGET_EDITBOX_ENTER );
+ }
+ break;
+ case KEY_LEFT:
+
+ if (event.KeyInput.Shift)
+ {
+ if (CursorPos > 0)
+ {
+ if (MarkBegin == MarkEnd)
+ newMarkBegin = CursorPos;
+
+ newMarkEnd = CursorPos-1;
+ }
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+
+ if (CursorPos > 0) CursorPos--;
+ BlinkStartTime = porting::getTimeMs();
+ break;
+
+ case KEY_RIGHT:
+ if (event.KeyInput.Shift)
+ {
+ if (Text.size() > (u32)CursorPos)
+ {
+ if (MarkBegin == MarkEnd)
+ newMarkBegin = CursorPos;
+
+ newMarkEnd = CursorPos+1;
+ }
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+
+ if (Text.size() > (u32)CursorPos) CursorPos++;
+ BlinkStartTime = porting::getTimeMs();
+ break;
+ case KEY_UP:
+ if (MultiLine || (WordWrap && BrokenText.size() > 1) )
+ {
+ s32 lineNo = getLineFromPos(CursorPos);
+ s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd);
+ if (lineNo > 0)
+ {
+ s32 cp = CursorPos - BrokenTextPositions[lineNo];
+ if ((s32)BrokenText[lineNo-1].size() < cp)
+ CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1;
+ else
+ CursorPos = BrokenTextPositions[lineNo-1] + cp;
+ }
+
+ if (event.KeyInput.Shift)
+ {
+ newMarkBegin = mb;
+ newMarkEnd = CursorPos;
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ case KEY_DOWN:
+ if (MultiLine || (WordWrap && BrokenText.size() > 1) )
+ {
+ s32 lineNo = getLineFromPos(CursorPos);
+ s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd);
+ if (lineNo < (s32)BrokenText.size()-1)
+ {
+ s32 cp = CursorPos - BrokenTextPositions[lineNo];
+ if ((s32)BrokenText[lineNo+1].size() < cp)
+ CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1;
+ else
+ CursorPos = BrokenTextPositions[lineNo+1] + cp;
+ }
+
+ if (event.KeyInput.Shift)
+ {
+ newMarkBegin = mb;
+ newMarkEnd = CursorPos;
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+
+ }
+ else
+ {
+ return false;
+ }
+ break;
+
+ case KEY_BACK:
+ if ( !this->IsEnabled )
+ break;
+
+ if (!Text.empty()) {
+ core::stringw s;
+
+ if (MarkBegin != MarkEnd)
+ {
+ // delete marked text
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ s = Text.subString(0, realmbgn);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+ Text = s;
+
+ CursorPos = realmbgn;
+ }
+ else
+ {
+ // delete text behind cursor
+ if (CursorPos>0)
+ s = Text.subString(0, CursorPos-1);
+ else
+ s = L"";
+ s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
+ Text = s;
+ --CursorPos;
+ }
+
+ if (CursorPos < 0)
+ CursorPos = 0;
+ BlinkStartTime = porting::getTimeMs();
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ textChanged = true;
+ }
+ break;
+ case KEY_DELETE:
+ if ( !this->IsEnabled )
+ break;
+
+ if (!Text.empty()) {
+ core::stringw s;
+
+ if (MarkBegin != MarkEnd)
+ {
+ // delete marked text
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ s = Text.subString(0, realmbgn);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+ Text = s;
+
+ CursorPos = realmbgn;
+ }
+ else
+ {
+ // delete text before cursor
+ s = Text.subString(0, CursorPos);
+ s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) );
+ Text = s;
+ }
+
+ if (CursorPos > (s32)Text.size())
+ CursorPos = (s32)Text.size();
+
+ BlinkStartTime = porting::getTimeMs();
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ textChanged = true;
+ }
+ break;
+
+ case KEY_ESCAPE:
+ case KEY_TAB:
+ case KEY_SHIFT:
+ case KEY_F1:
+ case KEY_F2:
+ case KEY_F3:
+ case KEY_F4:
+ case KEY_F5:
+ case KEY_F6:
+ case KEY_F7:
+ case KEY_F8:
+ case KEY_F9:
+ case KEY_F10:
+ case KEY_F11:
+ case KEY_F12:
+ case KEY_F13:
+ case KEY_F14:
+ case KEY_F15:
+ case KEY_F16:
+ case KEY_F17:
+ case KEY_F18:
+ case KEY_F19:
+ case KEY_F20:
+ case KEY_F21:
+ case KEY_F22:
+ case KEY_F23:
+ case KEY_F24:
+ // ignore these keys
+ return false;
+
+ default:
+ inputChar(event.KeyInput.Char);
+ return true;
+ }
+
+ // Set new text markers
+ setTextMarkers( newMarkBegin, newMarkEnd );
+
+ // break the text if it has changed
+ if (textChanged)
+ {
+ breakText();
+ sendGuiEvent(EGET_EDITBOX_CHANGED);
+ }
+
+ calculateScrollPos();
+
+ return true;
+}
+
+
+//! draws the element and its children
+void intlGUIEditBox::draw()
+{
+ if (!IsVisible)
+ return;
+
+ const bool focus = Environment->hasFocus(this);
+
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ FrameRect = AbsoluteRect;
+
+ // draw the border
+
+ if (Border)
+ {
+ if (m_writable) {
+ skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW),
+ false, true, FrameRect, &AbsoluteClippingRect);
+ }
+
+ FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
+ FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
+ FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
+ FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
+ }
+
+ updateVScrollBar();
+ core::rect<s32> localClipRect = FrameRect;
+ localClipRect.clipAgainst(AbsoluteClippingRect);
+
+ // draw the text
+
+ IGUIFont* font = OverrideFont;
+ if (!OverrideFont)
+ font = skin->getFont();
+
+ s32 cursorLine = 0;
+ s32 charcursorpos = 0;
+
+ if (font)
+ {
+ if (LastBreakFont != font)
+ {
+ breakText();
+ }
+
+ // calculate cursor pos
+
+ core::stringw *txtLine = &Text;
+ s32 startPos = 0;
+
+ core::stringw s, s2;
+
+ // get mark position
+ const bool ml = (!PasswordBox && (WordWrap || MultiLine));
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+ const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0;
+ const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1;
+ const s32 lineCount = ml ? BrokenText.size() : 1;
+
+ // Save the override color information.
+ // Then, alter it if the edit box is disabled.
+ const bool prevOver = OverrideColorEnabled;
+ const video::SColor prevColor = OverrideColor;
+
+ if (!Text.empty()) {
+ if (!IsEnabled && !OverrideColorEnabled)
+ {
+ OverrideColorEnabled = true;
+ OverrideColor = skin->getColor(EGDC_GRAY_TEXT);
+ }
+
+ for (s32 i=0; i < lineCount; ++i)
+ {
+ setTextRect(i);
+
+ // clipping test - don't draw anything outside the visible area
+ core::rect<s32> c = localClipRect;
+ c.clipAgainst(CurrentTextRect);
+ if (!c.isValid())
+ continue;
+
+ // get current line
+ if (PasswordBox)
+ {
+ if (BrokenText.size() != 1)
+ {
+ BrokenText.clear();
+ BrokenText.push_back(core::stringw());
+ }
+ if (BrokenText[0].size() != Text.size())
+ {
+ BrokenText[0] = Text;
+ for (u32 q = 0; q < Text.size(); ++q)
+ {
+ BrokenText[0] [q] = PasswordChar;
+ }
+ }
+ txtLine = &BrokenText[0];
+ startPos = 0;
+ }
+ else
+ {
+ txtLine = ml ? &BrokenText[i] : &Text;
+ startPos = ml ? BrokenTextPositions[i] : 0;
+ }
+
+
+ // draw normal text
+ font->draw(txtLine->c_str(), CurrentTextRect,
+ OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
+ false, true, &localClipRect);
+
+ // draw mark and marked text
+ if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount)
+ {
+
+ s32 mbegin = 0, mend = 0;
+ s32 lineStartPos = 0, lineEndPos = txtLine->size();
+
+ if (i == hlineStart)
+ {
+ // highlight start is on this line
+ s = txtLine->subString(0, realmbgn - startPos);
+ mbegin = font->getDimension(s.c_str()).Width;
+
+ // deal with kerning
+ mbegin += font->getKerningWidth(
+ &((*txtLine)[realmbgn - startPos]),
+ realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0);
+
+ lineStartPos = realmbgn - startPos;
+ }
+ if (i == hlineStart + hlineCount - 1)
+ {
+ // highlight end is on this line
+ s2 = txtLine->subString(0, realmend - startPos);
+ mend = font->getDimension(s2.c_str()).Width;
+ lineEndPos = (s32)s2.size();
+ }
+ else
+ mend = font->getDimension(txtLine->c_str()).Width;
+
+ CurrentTextRect.UpperLeftCorner.X += mbegin;
+ CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin;
+
+ // draw mark
+ skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect);
+
+ // draw marked text
+ s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos);
+
+ if (!s.empty())
+ font->draw(s.c_str(), CurrentTextRect,
+ OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
+ false, true, &localClipRect);
+
+ }
+ }
+
+ // Return the override color information to its previous settings.
+ OverrideColorEnabled = prevOver;
+ OverrideColor = prevColor;
+ }
+
+ // draw cursor
+
+ if (WordWrap || MultiLine)
+ {
+ cursorLine = getLineFromPos(CursorPos);
+ txtLine = &BrokenText[cursorLine];
+ startPos = BrokenTextPositions[cursorLine];
+ }
+ s = txtLine->subString(0,CursorPos-startPos);
+ charcursorpos = font->getDimension(s.c_str()).Width +
+ font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0);
+
+ if (m_writable) {
+ if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350) {
+ setTextRect(cursorLine);
+ CurrentTextRect.UpperLeftCorner.X += charcursorpos;
+
+ font->draw(L"_", CurrentTextRect,
+ OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
+ false, true, &localClipRect);
+ }
+ }
+ }
+
+ // draw children
+ IGUIElement::draw();
+}
+
+
+//! Sets the new caption of this element.
+void intlGUIEditBox::setText(const wchar_t* text)
+{
+ Text = text;
+ if (u32(CursorPos) > Text.size())
+ CursorPos = Text.size();
+ HScrollPos = 0;
+ breakText();
+}
+
+
+//! Enables or disables automatic scrolling with cursor position
+//! \param enable: If set to true, the text will move around with the cursor position
+void intlGUIEditBox::setAutoScroll(bool enable)
+{
+ AutoScroll = enable;
+}
+
+
+//! Checks to see if automatic scrolling is enabled
+//! \return true if automatic scrolling is enabled, false if not
+bool intlGUIEditBox::isAutoScrollEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return AutoScroll;
+}
+
+
+//! Gets the area of the text in the edit box
+//! \return Returns the size in pixels of the text
+core::dimension2du intlGUIEditBox::getTextDimension()
+{
+ core::rect<s32> ret;
+
+ setTextRect(0);
+ ret = CurrentTextRect;
+
+ for (u32 i=1; i < BrokenText.size(); ++i)
+ {
+ setTextRect(i);
+ ret.addInternalPoint(CurrentTextRect.UpperLeftCorner);
+ ret.addInternalPoint(CurrentTextRect.LowerRightCorner);
+ }
+
+ return core::dimension2du(ret.getSize());
+}
+
+
+//! Sets the maximum amount of characters which may be entered in the box.
+//! \param max: Maximum amount of characters. If 0, the character amount is
+//! infinity.
+void intlGUIEditBox::setMax(u32 max)
+{
+ Max = max;
+
+ if (Text.size() > Max && Max != 0)
+ Text = Text.subString(0, Max);
+}
+
+
+//! Returns maximum amount of characters, previously set by setMax();
+u32 intlGUIEditBox::getMax() const
+{
+ return Max;
+}
+
+
+bool intlGUIEditBox::processMouse(const SEvent& event)
+{
+ switch(event.MouseInput.Event)
+ {
+ case irr::EMIE_LMOUSE_LEFT_UP:
+ if (Environment->hasFocus(this))
+ {
+ CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ if (MouseMarking)
+ {
+ setTextMarkers( MarkBegin, CursorPos );
+ }
+ MouseMarking = false;
+ calculateScrollPos();
+ return true;
+ }
+ break;
+ case irr::EMIE_MOUSE_MOVED:
+ {
+ if (MouseMarking)
+ {
+ CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ setTextMarkers( MarkBegin, CursorPos );
+ calculateScrollPos();
+ return true;
+ }
+ }
+ break;
+ case EMIE_LMOUSE_PRESSED_DOWN:
+ if (!Environment->hasFocus(this))
+ {
+ BlinkStartTime = porting::getTimeMs();
+ MouseMarking = true;
+ CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ setTextMarkers(CursorPos, CursorPos );
+ calculateScrollPos();
+ return true;
+ }
+ else
+ {
+ if (!AbsoluteClippingRect.isPointInside(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) {
+ return false;
+ }
+
+
+ // move cursor
+ CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+
+ s32 newMarkBegin = MarkBegin;
+ if (!MouseMarking)
+ newMarkBegin = CursorPos;
+
+ MouseMarking = true;
+ setTextMarkers( newMarkBegin, CursorPos);
+ calculateScrollPos();
+ return true;
+ }
+ break;
+ case EMIE_MOUSE_WHEEL:
+ if (m_vscrollbar) {
+ s32 pos = m_vscrollbar->getPos();
+ s32 step = m_vscrollbar->getSmallStep();
+ m_vscrollbar->setPos(pos - event.MouseInput.Wheel * step);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+s32 intlGUIEditBox::getCursorPos(s32 x, s32 y)
+{
+ IGUIFont* font = OverrideFont;
+ IGUISkin* skin = Environment->getSkin();
+ if (!OverrideFont)
+ font = skin->getFont();
+
+ const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
+
+ core::stringw *txtLine = NULL;
+ s32 startPos = 0;
+ u32 curr_line_idx = 0;
+ x += 3;
+
+ for (; curr_line_idx < lineCount; ++curr_line_idx) {
+ setTextRect(curr_line_idx);
+ if (curr_line_idx == 0 && y < CurrentTextRect.UpperLeftCorner.Y)
+ y = CurrentTextRect.UpperLeftCorner.Y;
+ if (curr_line_idx == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y)
+ y = CurrentTextRect.LowerRightCorner.Y;
+
+ // is it inside this region?
+ if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) {
+ // we've found the clicked line
+ txtLine = (WordWrap || MultiLine) ? &BrokenText[curr_line_idx] : &Text;
+ startPos = (WordWrap || MultiLine) ? BrokenTextPositions[curr_line_idx] : 0;
+ break;
+ }
+ }
+
+ if (x < CurrentTextRect.UpperLeftCorner.X)
+ x = CurrentTextRect.UpperLeftCorner.X;
+ else if (x > CurrentTextRect.LowerRightCorner.X)
+ x = CurrentTextRect.LowerRightCorner.X;
+
+ s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X);
+ // Special handling for last line, if we are on limits, add 1 extra shift because idx
+ // will be the last char, not null char of the wstring
+ if (curr_line_idx == lineCount - 1 && x == CurrentTextRect.LowerRightCorner.X)
+ idx++;
+
+ return rangelim(idx + startPos, 0, S32_MAX);
+}
+
+
+//! Breaks the single text line.
+void intlGUIEditBox::breakText()
+{
+ IGUISkin* skin = Environment->getSkin();
+
+ if ((!WordWrap && !MultiLine) || !skin)
+ return;
+
+ BrokenText.clear(); // need to reallocate :/
+ BrokenTextPositions.set_used(0);
+
+ IGUIFont* font = OverrideFont;
+ if (!OverrideFont)
+ font = skin->getFont();
+
+ if (!font)
+ return;
+
+ LastBreakFont = font;
+
+ core::stringw line;
+ core::stringw word;
+ core::stringw whitespace;
+ s32 lastLineStart = 0;
+ s32 size = Text.size();
+ s32 length = 0;
+ s32 elWidth = RelativeRect.getWidth() - 6;
+ wchar_t c;
+
+ for (s32 i=0; i<size; ++i)
+ {
+ c = Text[i];
+ bool lineBreak = false;
+
+ if (c == L'\r') // Mac or Windows breaks
+ {
+ lineBreak = true;
+ c = ' ';
+ if (Text[i+1] == L'\n') // Windows breaks
+ {
+ Text.erase(i+1);
+ --size;
+ }
+ }
+ else if (c == L'\n') // Unix breaks
+ {
+ lineBreak = true;
+ c = ' ';
+ }
+
+ // don't break if we're not a multi-line edit box
+ if (!MultiLine)
+ lineBreak = false;
+
+ if (c == L' ' || c == 0 || i == (size-1))
+ {
+ if (!word.empty()) {
+ // here comes the next whitespace, look if
+ // we can break the last word to the next line.
+ s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
+ s32 worldlgth = font->getDimension(word.c_str()).Width;
+
+ if (WordWrap && length + worldlgth + whitelgth > elWidth)
+ {
+ // break to next line
+ length = worldlgth;
+ BrokenText.push_back(line);
+ BrokenTextPositions.push_back(lastLineStart);
+ lastLineStart = i - (s32)word.size();
+ line = word;
+ }
+ else
+ {
+ // add word to line
+ line += whitespace;
+ line += word;
+ length += whitelgth + worldlgth;
+ }
+
+ word = L"";
+ whitespace = L"";
+ }
+
+ whitespace += c;
+
+ // compute line break
+ if (lineBreak)
+ {
+ line += whitespace;
+ line += word;
+ BrokenText.push_back(line);
+ BrokenTextPositions.push_back(lastLineStart);
+ lastLineStart = i+1;
+ line = L"";
+ word = L"";
+ whitespace = L"";
+ length = 0;
+ }
+ }
+ else
+ {
+ // yippee this is a word..
+ word += c;
+ }
+ }
+
+ line += whitespace;
+ line += word;
+ BrokenText.push_back(line);
+ BrokenTextPositions.push_back(lastLineStart);
+}
+
+
+void intlGUIEditBox::setTextRect(s32 line)
+{
+ core::dimension2du d;
+
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont();
+
+ if (!font)
+ return;
+
+ // get text dimension
+ const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
+ if (WordWrap || MultiLine)
+ {
+ d = font->getDimension(BrokenText[line].c_str());
+ }
+ else
+ {
+ d = font->getDimension(Text.c_str());
+ d.Height = AbsoluteRect.getHeight();
+ }
+ d.Height += font->getKerningHeight();
+
+ // justification
+ switch (HAlign)
+ {
+ case EGUIA_CENTER:
+ // align to h centre
+ CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2);
+ CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2);
+ break;
+ case EGUIA_LOWERRIGHT:
+ // align to right edge
+ CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width;
+ CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth();
+ break;
+ default:
+ // align to left edge
+ CurrentTextRect.UpperLeftCorner.X = 0;
+ CurrentTextRect.LowerRightCorner.X = d.Width;
+
+ }
+
+ switch (VAlign)
+ {
+ case EGUIA_CENTER:
+ // align to v centre
+ CurrentTextRect.UpperLeftCorner.Y =
+ (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line;
+ break;
+ case EGUIA_LOWERRIGHT:
+ // align to bottom edge
+ CurrentTextRect.UpperLeftCorner.Y =
+ FrameRect.getHeight() - lineCount*d.Height + d.Height*line;
+ break;
+ default:
+ // align to top edge
+ CurrentTextRect.UpperLeftCorner.Y = d.Height*line;
+ break;
+ }
+
+ CurrentTextRect.UpperLeftCorner.X -= HScrollPos;
+ CurrentTextRect.LowerRightCorner.X -= HScrollPos;
+ CurrentTextRect.UpperLeftCorner.Y -= VScrollPos;
+ CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height;
+
+ CurrentTextRect += FrameRect.UpperLeftCorner;
+
+}
+
+
+s32 intlGUIEditBox::getLineFromPos(s32 pos)
+{
+ if (!WordWrap && !MultiLine)
+ return 0;
+
+ s32 i=0;
+ while (i < (s32)BrokenTextPositions.size())
+ {
+ if (BrokenTextPositions[i] > pos)
+ return i-1;
+ ++i;
+ }
+ return (s32)BrokenTextPositions.size() - 1;
+}
+
+
+void intlGUIEditBox::inputChar(wchar_t c)
+{
+ if (!IsEnabled)
+ return;
+
+ if (c != 0)
+ {
+ if (Text.size() < Max || Max == 0)
+ {
+ core::stringw s;
+
+ if (MarkBegin != MarkEnd)
+ {
+ // replace marked text
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ s = Text.subString(0, realmbgn);
+ s.append(c);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+ Text = s;
+ CursorPos = realmbgn+1;
+ }
+ else
+ {
+ // add new character
+ s = Text.subString(0, CursorPos);
+ s.append(c);
+ s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
+ Text = s;
+ ++CursorPos;
+ }
+
+ BlinkStartTime = porting::getTimeMs();
+ setTextMarkers(0, 0);
+ }
+ }
+ breakText();
+ sendGuiEvent(EGET_EDITBOX_CHANGED);
+ calculateScrollPos();
+}
+
+
+void intlGUIEditBox::calculateScrollPos()
+{
+ if (!AutoScroll)
+ return;
+
+ // calculate horizontal scroll position
+ s32 cursLine = getLineFromPos(CursorPos);
+ setTextRect(cursLine);
+
+ // don't do horizontal scrolling when wordwrap is enabled.
+ if (!WordWrap)
+ {
+ // get cursor position
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+ IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont();
+ if (!font)
+ return;
+
+ core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text;
+ s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos;
+
+ s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos +
+ font->getDimension(txtLine->subString(0, cPos).c_str()).Width;
+
+ s32 cEnd = cStart + font->getDimension(L"_ ").Width;
+
+ if (FrameRect.LowerRightCorner.X < cEnd)
+ HScrollPos = cEnd - FrameRect.LowerRightCorner.X;
+ else if (FrameRect.UpperLeftCorner.X > cStart)
+ HScrollPos = cStart - FrameRect.UpperLeftCorner.X;
+ else
+ HScrollPos = 0;
+
+ // todo: adjust scrollbar
+ }
+
+ // vertical scroll position
+ if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos)
+ VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos;
+
+ else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos)
+ VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos;
+ else
+ VScrollPos = 0;
+
+ // todo: adjust scrollbar
+ if (m_vscrollbar)
+ m_vscrollbar->setPos(VScrollPos);
+}
+
+//! set text markers
+void intlGUIEditBox::setTextMarkers(s32 begin, s32 end)
+{
+ if ( begin != MarkBegin || end != MarkEnd )
+ {
+ MarkBegin = begin;
+ MarkEnd = end;
+ sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
+ }
+}
+
+//! send some gui event to parent
+void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type)
+{
+ if ( Parent )
+ {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = 0;
+ e.GUIEvent.EventType = type;
+
+ Parent->OnEvent(e);
+ }
+}
+
+//! Create a vertical scrollbar
+void intlGUIEditBox::createVScrollBar()
+{
+ s32 fontHeight = 1;
+
+ if (OverrideFont) {
+ fontHeight = OverrideFont->getDimension(L"").Height;
+ } else {
+ if (IGUISkin* skin = Environment->getSkin()) {
+ if (IGUIFont* font = skin->getFont()) {
+ fontHeight = font->getDimension(L"").Height;
+ }
+ }
+ }
+
+ irr::core::rect<s32> scrollbarrect = FrameRect;
+ scrollbarrect.UpperLeftCorner.X += FrameRect.getWidth() - m_scrollbar_width;
+ m_vscrollbar = Environment->addScrollBar(false, scrollbarrect, getParent(), getID());
+ m_vscrollbar->setVisible(false);
+ m_vscrollbar->setSmallStep(3 * fontHeight);
+ m_vscrollbar->setLargeStep(10 * fontHeight);
+}
+
+//! Update the vertical scrollbar (visibilty & scroll position)
+void intlGUIEditBox::updateVScrollBar()
+{
+ if (!m_vscrollbar)
+ return;
+
+ // OnScrollBarChanged(...)
+ if (m_vscrollbar->getPos() != VScrollPos) {
+ s32 deltaScrollY = m_vscrollbar->getPos() - VScrollPos;
+ CurrentTextRect.UpperLeftCorner.Y -= deltaScrollY;
+ CurrentTextRect.LowerRightCorner.Y -= deltaScrollY;
+
+ s32 scrollymax = getTextDimension().Height - FrameRect.getHeight();
+ if (scrollymax != m_vscrollbar->getMax()) {
+ // manage a newline or a deleted line
+ m_vscrollbar->setMax(scrollymax);
+ calculateScrollPos();
+ } else {
+ // manage a newline or a deleted line
+ VScrollPos = m_vscrollbar->getPos();
+ }
+ }
+
+ // check if a vertical scrollbar is needed ?
+ if (getTextDimension().Height > (u32) FrameRect.getHeight()) {
+ s32 scrollymax = getTextDimension().Height - FrameRect.getHeight();
+ if (scrollymax != m_vscrollbar->getMax()) {
+ m_vscrollbar->setMax(scrollymax);
+ }
+
+ if (!m_vscrollbar->isVisible() && MultiLine) {
+ AbsoluteRect.LowerRightCorner.X -= m_scrollbar_width;
+
+ m_vscrollbar->setVisible(true);
+ }
+ } else {
+ if (m_vscrollbar->isVisible()) {
+ AbsoluteRect.LowerRightCorner.X += m_scrollbar_width;
+
+ VScrollPos = 0;
+ m_vscrollbar->setPos(0);
+ m_vscrollbar->setMax(1);
+ m_vscrollbar->setVisible(false);
+ }
+ }
+}
+
+void intlGUIEditBox::setWritable(bool can_write_text)
+{
+ m_writable = can_write_text;
+}
+
+//! Writes attributes of the element.
+void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
+{
+ // IGUIEditBox::serializeAttributes(out,options);
+
+ out->addBool ("OverrideColorEnabled",OverrideColorEnabled );
+ out->addColor ("OverrideColor", OverrideColor);
+ // out->addFont("OverrideFont",OverrideFont);
+ out->addInt ("MaxChars", Max);
+ out->addBool ("WordWrap", WordWrap);
+ out->addBool ("MultiLine", MultiLine);
+ out->addBool ("AutoScroll", AutoScroll);
+ out->addBool ("PasswordBox", PasswordBox);
+ core::stringw ch = L" ";
+ ch[0] = PasswordChar;
+ out->addString("PasswordChar", ch.c_str());
+ out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames);
+ out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames);
+ out->addBool ("Writable", m_writable);
+
+ IGUIEditBox::serializeAttributes(out,options);
+}
+
+
+//! Reads attributes of the element
+void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
+{
+ IGUIEditBox::deserializeAttributes(in,options);
+
+ setOverrideColor(in->getAttributeAsColor("OverrideColor"));
+ enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
+ setMax(in->getAttributeAsInt("MaxChars"));
+ setWordWrap(in->getAttributeAsBool("WordWrap"));
+ setMultiLine(in->getAttributeAsBool("MultiLine"));
+ setAutoScroll(in->getAttributeAsBool("AutoScroll"));
+ core::stringw ch = in->getAttributeAsStringW("PasswordChar");
+
+ if (ch.empty())
+ setPasswordBox(in->getAttributeAsBool("PasswordBox"));
+ else
+ setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]);
+
+ setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
+ (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
+
+ setWritable(in->getAttributeAsBool("Writable"));
+ // setOverrideFont(in->getAttributeAsFont("OverrideFont"));
+}
+
+
+} // end namespace gui
+} // end namespace irr
+
+#endif // _IRR_COMPILE_WITH_GUI_
--- /dev/null
+// Copyright (C) 2002-2013 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#pragma once
+
+#include "IrrCompileConfig.h"
+//#ifdef _IRR_COMPILE_WITH_GUI_
+
+#include "IGUIEditBox.h"
+#include "irrArray.h"
+#include "IOSOperator.h"
+#include "IGUIScrollBar.h"
+
+namespace irr
+{
+namespace gui
+{
+ class intlGUIEditBox : public IGUIEditBox
+ {
+ public:
+
+ //! constructor
+ intlGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment,
+ IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
+ bool writable = true, bool has_vscrollbar = false);
+
+ //! destructor
+ virtual ~intlGUIEditBox();
+
+ //! Sets another skin independent font.
+ virtual void setOverrideFont(IGUIFont* font=0);
+
+ //! Gets the override font (if any)
+ /** \return The override font (may be 0) */
+ virtual IGUIFont* getOverrideFont() const;
+
+ //! Get the font which is used right now for drawing
+ /** Currently this is the override font when one is set and the
+ font of the active skin otherwise */
+ virtual IGUIFont* getActiveFont() const;
+
+ //! Sets another color for the text.
+ virtual void setOverrideColor(video::SColor color);
+
+ //! Gets the override color
+ virtual video::SColor getOverrideColor() const;
+
+ //! Sets if the text should use the overide color or the
+ //! color in the gui skin.
+ virtual void enableOverrideColor(bool enable);
+
+ //! Checks if an override color is enabled
+ /** \return true if the override color is enabled, false otherwise */
+ virtual bool isOverrideColorEnabled(void) const;
+
+ //! Sets whether to draw the background
+ virtual void setDrawBackground(bool draw);
+
+ //! Turns the border on or off
+ virtual void setDrawBorder(bool border);
+
+ //! Enables or disables word wrap for using the edit box as multiline text editor.
+ virtual void setWordWrap(bool enable);
+
+ //! Checks if word wrap is enabled
+ //! \return true if word wrap is enabled, false otherwise
+ virtual bool isWordWrapEnabled() const;
+
+ //! Enables or disables newlines.
+ /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired,
+ instead a newline character will be inserted. */
+ virtual void setMultiLine(bool enable);
+
+ //! Checks if multi line editing is enabled
+ //! \return true if mult-line is enabled, false otherwise
+ virtual bool isMultiLineEnabled() const;
+
+ //! Enables or disables automatic scrolling with cursor position
+ //! \param enable: If set to true, the text will move around with the cursor position
+ virtual void setAutoScroll(bool enable);
+
+ //! Checks to see if automatic scrolling is enabled
+ //! \return true if automatic scrolling is enabled, false if not
+ virtual bool isAutoScrollEnabled() const;
+
+ //! Gets the size area of the text in the edit box
+ //! \return Returns the size in pixels of the text
+ virtual core::dimension2du getTextDimension();
+
+ //! Sets text justification
+ virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical);
+
+ //! called if an event happened.
+ virtual bool OnEvent(const SEvent& event);
+
+ //! draws the element and its children
+ virtual void draw();
+
+ //! Sets the new caption of this element.
+ virtual void setText(const wchar_t* text);
+
+ //! Sets the maximum amount of characters which may be entered in the box.
+ //! \param max: Maximum amount of characters. If 0, the character amount is
+ //! infinity.
+ virtual void setMax(u32 max);
+
+ //! Returns maximum amount of characters, previously set by setMax();
+ virtual u32 getMax() const;
+
+ //! Sets whether the edit box is a password box. Setting this to true will
+ /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x
+ \param passwordBox: true to enable password, false to disable
+ \param passwordChar: the character that is displayed instead of letters */
+ virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*');
+
+ //! Returns true if the edit box is currently a password box.
+ virtual bool isPasswordBox() const;
+
+ //! Updates the absolute position, splits text if required
+ virtual void updateAbsolutePosition();
+
+ //! set true if this EditBox is writable
+ virtual void setWritable(bool can_write_text);
+
+ //! Writes attributes of the element.
+ virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;
+
+ //! Reads attributes of the element
+ virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
+
+ protected:
+ //! Breaks the single text line.
+ void breakText();
+ //! sets the area of the given line
+ void setTextRect(s32 line);
+ //! returns the line number that the cursor is on
+ s32 getLineFromPos(s32 pos);
+ //! adds a letter to the edit box
+ void inputChar(wchar_t c);
+ //! calculates the current scroll position
+ void calculateScrollPos();
+ //! send some gui event to parent
+ void sendGuiEvent(EGUI_EVENT_TYPE type);
+ //! set text markers
+ void setTextMarkers(s32 begin, s32 end);
+
+ bool processKey(const SEvent& event);
+ bool processMouse(const SEvent& event);
+ s32 getCursorPos(s32 x, s32 y);
+
+ //! Create a vertical scrollbar
+ void createVScrollBar();
+
+ //! Update the vertical scrollbar (visibilty & scroll position)
+ void updateVScrollBar();
+
+ bool MouseMarking = false;
+ bool Border;
+ bool OverrideColorEnabled = false;
+ s32 MarkBegin = 0;
+ s32 MarkEnd = 0;
+
+ video::SColor OverrideColor = video::SColor(101,255,255,255);
+ gui::IGUIFont *OverrideFont = nullptr;
+ gui::IGUIFont *LastBreakFont = nullptr;
+ IOSOperator *Operator = nullptr;
+
+ u64 BlinkStartTime = 0;
+ s32 CursorPos = 0;
+ s32 HScrollPos = 0;
+ s32 VScrollPos = 0; // scroll position in characters
+ u32 Max = 0;
+
+ bool WordWrap = false;
+ bool MultiLine = false;
+ bool AutoScroll = true;
+ bool PasswordBox = false;
+ wchar_t PasswordChar = L'*';
+ EGUI_ALIGNMENT HAlign = EGUIA_UPPERLEFT;
+ EGUI_ALIGNMENT VAlign = EGUIA_CENTER;
+
+ core::array<core::stringw> BrokenText;
+ core::array<s32> BrokenTextPositions;
+
+ core::rect<s32> CurrentTextRect = core::rect<s32>(0,0,1,1);
+ core::rect<s32> FrameRect; // temporary values
+ u32 m_scrollbar_width;
+ IGUIScrollBar *m_vscrollbar;
+ bool m_writable;
+
+ };
+
+
+} // end namespace gui
+} // end namespace irr
+
+//#endif // _IRR_COMPILE_WITH_GUI_
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+/*
+ All kinds of stuff that needs to be exposed from main.cpp
+*/
+#include "modalMenu.h"
+#include <cassert>
+#include <list>
+
+class IGameCallback
+{
+public:
+ virtual void exitToOS() = 0;
+ virtual void keyConfig() = 0;
+ virtual void disconnect() = 0;
+ virtual void changePassword() = 0;
+ virtual void changeVolume() = 0;
+
+ virtual void signalKeyConfigChange() = 0;
+};
+
+extern gui::IGUIEnvironment *guienv;
+extern gui::IGUIStaticText *guiroot;
+
+// Handler for the modal menus
+
+class MainMenuManager : public IMenuManager
+{
+public:
+ virtual void createdMenu(gui::IGUIElement *menu)
+ {
+#ifndef NDEBUG
+ for (gui::IGUIElement *i : m_stack) {
+ assert(i != menu);
+ }
+#endif
+
+ if(!m_stack.empty())
+ m_stack.back()->setVisible(false);
+ m_stack.push_back(menu);
+ }
+
+ virtual void deletingMenu(gui::IGUIElement *menu)
+ {
+ // Remove all entries if there are duplicates
+ bool removed_entry;
+ do{
+ removed_entry = false;
+ for(std::list<gui::IGUIElement*>::iterator
+ i = m_stack.begin();
+ i != m_stack.end(); ++i)
+ {
+ if(*i == menu)
+ {
+ m_stack.erase(i);
+ removed_entry = true;
+ break;
+ }
+ }
+ }while(removed_entry);
+
+ /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
+ assert(*i == menu);
+ m_stack.erase(i);*/
+
+ if(!m_stack.empty())
+ m_stack.back()->setVisible(true);
+ }
+
+ // Returns true to prevent further processing
+ virtual bool preprocessEvent(const SEvent& event)
+ {
+ if (m_stack.empty())
+ return false;
+ GUIModalMenu *mm = dynamic_cast<GUIModalMenu*>(m_stack.back());
+ return mm && mm->preprocessEvent(event);
+ }
+
+ u32 menuCount()
+ {
+ return m_stack.size();
+ }
+
+ bool pausesGame()
+ {
+ for (gui::IGUIElement *i : m_stack) {
+ GUIModalMenu *mm = dynamic_cast<GUIModalMenu*>(i);
+ if (mm && mm->pausesGame())
+ return true;
+ }
+ return false;
+ }
+
+ std::list<gui::IGUIElement*> m_stack;
+};
+
+extern MainMenuManager g_menumgr;
+
+extern bool isMenuActive();
+
+class MainGameCallback : public IGameCallback
+{
+public:
+ MainGameCallback() = default;
+ virtual ~MainGameCallback() = default;
+
+ virtual void exitToOS()
+ {
+ shutdown_requested = true;
+ }
+
+ virtual void disconnect()
+ {
+ disconnect_requested = true;
+ }
+
+ virtual void changePassword()
+ {
+ changepassword_requested = true;
+ }
+
+ virtual void changeVolume()
+ {
+ changevolume_requested = true;
+ }
+
+ virtual void keyConfig()
+ {
+ keyconfig_requested = true;
+ }
+
+ virtual void signalKeyConfigChange()
+ {
+ keyconfig_changed = true;
+ }
+
+
+ bool disconnect_requested = false;
+ bool changepassword_requested = false;
+ bool changevolume_requested = false;
+ bool keyconfig_requested = false;
+ bool shutdown_requested = false;
+
+ bool keyconfig_changed = false;
+};
+
+extern MainGameCallback *g_gamecallback;
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#ifdef HAVE_TOUCHSCREENGUI
+#include "touchscreengui.h"
+#endif
+
+class GUIModalMenu;
+
+class IMenuManager
+{
+public:
+ // A GUIModalMenu calls these when this class is passed as a parameter
+ virtual void createdMenu(gui::IGUIElement *menu) = 0;
+ virtual void deletingMenu(gui::IGUIElement *menu) = 0;
+};
+
+/*
+ Remember to drop() the menu after creating, so that it can
+ remove itself when it wants to.
+*/
+
+class GUIModalMenu : public gui::IGUIElement
+{
+public:
+ GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr):
+ IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
+ core::rect<s32>(0,0,100,100))
+ {
+ m_menumgr = menumgr;
+
+ setVisible(true);
+ Environment->setFocus(this);
+ m_menumgr->createdMenu(this);
+ }
+
+ virtual ~GUIModalMenu()
+ {
+ m_menumgr->deletingMenu(this);
+ }
+
+ void allowFocusRemoval(bool allow)
+ {
+ m_allow_focus_removal = allow;
+ }
+
+ bool canTakeFocus(gui::IGUIElement *e)
+ {
+ return (e && (e == this || isMyChild(e))) || m_allow_focus_removal;
+ }
+
+ void draw()
+ {
+ if(!IsVisible)
+ return;
+
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ v2u32 screensize = driver->getScreenSize();
+ if(screensize != m_screensize_old /*|| m_force_regenerate_gui*/)
+ {
+ m_screensize_old = screensize;
+ regenerateGui(screensize);
+ //m_force_regenerate_gui = false;
+ }
+
+ drawMenu();
+ }
+
+ /*
+ This should be called when the menu wants to quit.
+
+ WARNING: THIS DEALLOCATES THE MENU FROM MEMORY. Return
+ immediately if you call this from the menu itself.
+
+ (More precisely, this decrements the reference count.)
+ */
+ void quitMenu()
+ {
+ allowFocusRemoval(true);
+ // This removes Environment's grab on us
+ Environment->removeFocus(this);
+ m_menumgr->deletingMenu(this);
+ this->remove();
+#ifdef HAVE_TOUCHSCREENGUI
+ if (g_touchscreengui)
+ g_touchscreengui->show();
+#endif
+ }
+
+ void removeChildren()
+ {
+ const core::list<gui::IGUIElement*> &children = getChildren();
+ core::list<gui::IGUIElement*> children_copy;
+ for (gui::IGUIElement *i : children) {
+ children_copy.push_back(i);
+ }
+
+ for (gui::IGUIElement *i : children_copy) {
+ i->remove();
+ }
+ }
+
+ virtual void regenerateGui(v2u32 screensize) = 0;
+ virtual void drawMenu() = 0;
+ virtual bool preprocessEvent(const SEvent& event) { return false; };
+ virtual bool OnEvent(const SEvent& event) { return false; };
+ virtual bool pausesGame(){ return false; } // Used for pause menu
+
+protected:
+ //bool m_force_regenerate_gui;
+ v2u32 m_screensize_old;
+private:
+ IMenuManager *m_menumgr;
+ // This might be necessary to expose to the implementation if it
+ // wants to launch other menus
+ bool m_allow_focus_removal = false;
+};
--- /dev/null
+/*
+Copyright (C) 2014 sapier
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "touchscreengui.h"
+#include "irrlichttypes.h"
+#include "irr_v2d.h"
+#include "log.h"
+#include "keycode.h"
+#include "settings.h"
+#include "gettime.h"
+#include "util/numeric.h"
+#include "porting.h"
+#include "guiscalingfilter.h"
+
+#include <iostream>
+#include <algorithm>
+
+#include <ISceneCollisionManager.h>
+
+// Very slow button repeat frequency (in seconds)
+#define SLOW_BUTTON_REPEAT (1.0f)
+
+using namespace irr::core;
+
+const char** touchgui_button_imagenames = (const char*[]) {
+ "up_arrow.png",
+ "down_arrow.png",
+ "left_arrow.png",
+ "right_arrow.png",
+ "jump_btn.png",
+ "down.png"
+};
+
+static irr::EKEY_CODE id2keycode(touch_gui_button_id id)
+{
+ std::string key = "";
+ switch (id) {
+ case forward_id:
+ key = "forward";
+ break;
+ case left_id:
+ key = "left";
+ break;
+ case right_id:
+ key = "right";
+ break;
+ case backward_id:
+ key = "backward";
+ break;
+ case inventory_id:
+ key = "inventory";
+ break;
+ case drop_id:
+ key = "drop";
+ break;
+ case jump_id:
+ key = "jump";
+ break;
+ case crunch_id:
+ key = "sneak";
+ break;
+ case fly_id:
+ key = "freemove";
+ break;
+ case noclip_id:
+ key = "noclip";
+ break;
+ case fast_id:
+ key = "fastmove";
+ break;
+ case debug_id:
+ key = "toggle_debug";
+ break;
+ case chat_id:
+ key = "chat";
+ break;
+ case camera_id:
+ key = "camera_mode";
+ break;
+ case range_id:
+ key = "rangeselect";
+ break;
+ }
+ assert(key != "");
+ return keyname_to_keycode(g_settings->get("keymap_" + key).c_str());
+}
+
+TouchScreenGUI *g_touchscreengui;
+
+static void load_button_texture(button_info* btn, const char* path,
+ rect<s32> button_rect, ISimpleTextureSource* tsrc, video::IVideoDriver *driver)
+{
+ unsigned int tid;
+ video::ITexture *texture = guiScalingImageButton(driver,
+ tsrc->getTexture(path, &tid), button_rect.getWidth(),
+ button_rect.getHeight());
+ if (texture) {
+ btn->guibutton->setUseAlphaChannel(true);
+ if (g_settings->getBool("gui_scaling_filter")) {
+ rect<s32> txr_rect = rect<s32>(0, 0, button_rect.getWidth(), button_rect.getHeight());
+ btn->guibutton->setImage(texture, txr_rect);
+ btn->guibutton->setPressedImage(texture, txr_rect);
+ btn->guibutton->setScaleImage(false);
+ } else {
+ btn->guibutton->setImage(texture);
+ btn->guibutton->setPressedImage(texture);
+ btn->guibutton->setScaleImage(true);
+ }
+ btn->guibutton->setDrawBorder(false);
+ btn->guibutton->setText(L"");
+ }
+}
+
+AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device,
+ IEventReceiver* receiver) :
+ m_driver(device->getVideoDriver()),
+ m_guienv(device->getGUIEnvironment()),
+ m_receiver(receiver)
+{
+}
+
+void AutoHideButtonBar::init(ISimpleTextureSource* tsrc,
+ const char* starter_img, int button_id, v2s32 UpperLeft,
+ v2s32 LowerRight, autohide_button_bar_dir dir, float timeout)
+{
+ m_texturesource = tsrc;
+
+ m_upper_left = UpperLeft;
+ m_lower_right = LowerRight;
+
+ /* init settings bar */
+
+ irr::core::rect<int> current_button = rect<s32>(UpperLeft.X, UpperLeft.Y,
+ LowerRight.X, LowerRight.Y);
+
+ m_starter.guibutton = m_guienv->addButton(current_button, 0, button_id, L"", 0);
+ m_starter.guibutton->grab();
+ m_starter.repeatcounter = -1;
+ m_starter.keycode = KEY_OEM_8; // use invalid keycode as it's not relevant
+ m_starter.immediate_release = true;
+ m_starter.ids.clear();
+
+ load_button_texture(&m_starter, starter_img, current_button,
+ m_texturesource, m_driver);
+
+ m_dir = dir;
+ m_timeout_value = timeout;
+
+ m_initialized = true;
+}
+
+AutoHideButtonBar::~AutoHideButtonBar()
+{
+ if (m_starter.guibutton) {
+ m_starter.guibutton->setVisible(false);
+ m_starter.guibutton->drop();
+ }
+}
+
+void AutoHideButtonBar::addButton(touch_gui_button_id button_id,
+ const wchar_t* caption, const char* btn_image)
+{
+
+ if (!m_initialized) {
+ errorstream << "AutoHideButtonBar::addButton not yet initialized!"
+ << std::endl;
+ return;
+ }
+ int button_size = 0;
+
+ if ((m_dir == AHBB_Dir_Top_Bottom) || (m_dir == AHBB_Dir_Bottom_Top)) {
+ button_size = m_lower_right.X - m_upper_left.X;
+ } else {
+ button_size = m_lower_right.Y - m_upper_left.Y;
+ }
+
+ irr::core::rect<int> current_button;
+
+ if ((m_dir == AHBB_Dir_Right_Left) || (m_dir == AHBB_Dir_Left_Right)) {
+
+ int x_start = 0;
+ int x_end = 0;
+
+ if (m_dir == AHBB_Dir_Left_Right) {
+ x_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
+ + (button_size * 0.25);
+ x_end = x_start + button_size;
+ } else {
+ x_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size())
+ - (button_size * 0.25);
+ x_start = x_end - button_size;
+ }
+
+ current_button = rect<s32>(x_start, m_upper_left.Y, x_end,
+ m_lower_right.Y);
+ } else {
+ int y_start = 0;
+ int y_end = 0;
+
+ if (m_dir == AHBB_Dir_Top_Bottom) {
+ y_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
+ + (button_size * 0.25);
+ y_end = y_start + button_size;
+ } else {
+ y_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size())
+ - (button_size * 0.25);
+ y_start = y_end - button_size;
+ }
+
+ current_button = rect<s32>(m_upper_left.X, y_start, m_lower_right.Y,
+ y_end);
+ }
+
+ button_info* btn = new button_info();
+ btn->guibutton = m_guienv->addButton(current_button, 0, button_id, caption, 0);
+ btn->guibutton->grab();
+ btn->guibutton->setVisible(false);
+ btn->guibutton->setEnabled(false);
+ btn->repeatcounter = -1;
+ btn->keycode = id2keycode(button_id);
+ btn->immediate_release = true;
+ btn->ids.clear();
+
+ load_button_texture(btn, btn_image, current_button, m_texturesource,
+ m_driver);
+
+ m_buttons.push_back(btn);
+}
+
+bool AutoHideButtonBar::isButton(const SEvent &event)
+{
+ IGUIElement* rootguielement = m_guienv->getRootGUIElement();
+
+ if (rootguielement == NULL) {
+ return false;
+ }
+
+ gui::IGUIElement *element = rootguielement->getElementFromPoint(
+ core::position2d<s32>(event.TouchInput.X, event.TouchInput.Y));
+
+ if (element == NULL) {
+ return false;
+ }
+
+ if (m_active) {
+ /* check for all buttons in vector */
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ if ((*iter)->guibutton == element) {
+
+ SEvent* translated = new SEvent();
+ memset(translated, 0, sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = (*iter)->keycode;
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.Char = 0;
+
+ /* add this event */
+ translated->KeyInput.PressedDown = true;
+ m_receiver->OnEvent(*translated);
+
+ /* remove this event */
+ translated->KeyInput.PressedDown = false;
+ m_receiver->OnEvent(*translated);
+
+ delete translated;
+
+ (*iter)->ids.push_back(event.TouchInput.ID);
+
+ m_timeout = 0;
+
+ return true;
+ }
+ ++iter;
+ }
+ } else {
+ /* check for starter button only */
+ if (element == m_starter.guibutton) {
+ m_starter.ids.push_back(event.TouchInput.ID);
+ m_starter.guibutton->setVisible(false);
+ m_starter.guibutton->setEnabled(false);
+ m_active = true;
+ m_timeout = 0;
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ (*iter)->guibutton->setVisible(true);
+ (*iter)->guibutton->setEnabled(true);
+ ++iter;
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+bool AutoHideButtonBar::isReleaseButton(int eventID)
+{
+ std::vector<int>::iterator id = std::find(m_starter.ids.begin(),
+ m_starter.ids.end(), eventID);
+
+ if (id != m_starter.ids.end()) {
+ m_starter.ids.erase(id);
+ return true;
+ }
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ std::vector<int>::iterator id = std::find((*iter)->ids.begin(),
+ (*iter)->ids.end(), eventID);
+
+ if (id != (*iter)->ids.end()) {
+ (*iter)->ids.erase(id);
+ // TODO handle settings button release
+ return true;
+ }
+ ++iter;
+ }
+
+ return false;
+}
+
+void AutoHideButtonBar::step(float dtime)
+{
+ if (m_active) {
+ m_timeout += dtime;
+
+ if (m_timeout > m_timeout_value) {
+ deactivate();
+ }
+ }
+}
+
+void AutoHideButtonBar::deactivate()
+{
+ if (m_visible) {
+ m_starter.guibutton->setVisible(true);
+ m_starter.guibutton->setEnabled(true);
+ }
+ m_active = false;
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ (*iter)->guibutton->setVisible(false);
+ (*iter)->guibutton->setEnabled(false);
+ ++iter;
+ }
+}
+
+void AutoHideButtonBar::hide()
+{
+ m_visible = false;
+ m_starter.guibutton->setVisible(false);
+ m_starter.guibutton->setEnabled(false);
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ (*iter)->guibutton->setVisible(false);
+ (*iter)->guibutton->setEnabled(false);
+ ++iter;
+ }
+}
+
+void AutoHideButtonBar::show()
+{
+ m_visible = true;
+
+ if (m_active) {
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ (*iter)->guibutton->setVisible(true);
+ (*iter)->guibutton->setEnabled(true);
+ ++iter;
+ }
+ } else {
+ m_starter.guibutton->setVisible(true);
+ m_starter.guibutton->setEnabled(true);
+ }
+}
+
+TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver):
+ m_device(device),
+ m_guienv(device->getGUIEnvironment()),
+ m_receiver(receiver),
+ m_settingsbar(device, receiver),
+ m_rarecontrolsbar(device, receiver)
+{
+ for (unsigned int i=0; i < after_last_element_id; i++) {
+ m_buttons[i].guibutton = 0;
+ m_buttons[i].repeatcounter = -1;
+ m_buttons[i].repeatdelay = BUTTON_REPEAT_DELAY;
+ }
+
+ m_screensize = m_device->getVideoDriver()->getScreenSize();
+}
+
+void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
+ std::wstring caption, bool immediate_release, float repeat_delay)
+{
+
+ button_info* btn = &m_buttons[id];
+ btn->guibutton = m_guienv->addButton(button_rect, 0, id, caption.c_str());
+ btn->guibutton->grab();
+ btn->repeatcounter = -1;
+ btn->repeatdelay = repeat_delay;
+ btn->keycode = id2keycode(id);
+ btn->immediate_release = immediate_release;
+ btn->ids.clear();
+
+ load_button_texture(btn,touchgui_button_imagenames[id],button_rect,
+ m_texturesource, m_device->getVideoDriver());
+}
+
+static int getMaxControlPadSize(float density) {
+ return 200 * density * g_settings->getFloat("hud_scaling");
+}
+
+int TouchScreenGUI::getGuiButtonSize()
+{
+ u32 control_pad_size = MYMIN((2 * m_screensize.Y) / 3,
+ getMaxControlPadSize(porting::getDisplayDensity()));
+
+ return control_pad_size / 3;
+}
+
+void TouchScreenGUI::init(ISimpleTextureSource* tsrc)
+{
+ assert(tsrc != 0);
+
+ u32 button_size = getGuiButtonSize();
+ m_visible = true;
+ m_texturesource = tsrc;
+ /*
+ draw control pad
+ 0 1 2
+ 3 4 5
+ for now only 0, 1, 2, and 4 are used
+ */
+ int number = 0;
+ for (int y = 0; y < 2; ++y)
+ for (int x = 0; x < 3; ++x, ++number) {
+ rect<s32> button_rect(
+ x * button_size, m_screensize.Y - button_size * (2 - y),
+ (x + 1) * button_size, m_screensize.Y - button_size * (1 - y)
+ );
+ touch_gui_button_id id = after_last_element_id;
+ std::wstring caption;
+ switch (number) {
+ case 0:
+ id = left_id;
+ caption = L"<";
+ break;
+ case 1:
+ id = forward_id;
+ caption = L"^";
+ break;
+ case 2:
+ id = right_id;
+ caption = L">";
+ break;
+ case 4:
+ id = backward_id;
+ caption = L"v";
+ break;
+ }
+ if (id != after_last_element_id) {
+ initButton(id, button_rect, caption, false);
+ }
+ }
+
+ /* init jump button */
+ initButton(jump_id,
+ rect<s32>(m_screensize.X-(1.75*button_size),
+ m_screensize.Y - (0.5*button_size),
+ m_screensize.X-(0.25*button_size),
+ m_screensize.Y),
+ L"x",false);
+
+ /* init crunch button */
+ initButton(crunch_id,
+ rect<s32>(m_screensize.X-(3.25*button_size),
+ m_screensize.Y - (0.5*button_size),
+ m_screensize.X-(1.75*button_size),
+ m_screensize.Y),
+ L"H",false);
+
+ m_settingsbar.init(m_texturesource, "gear_icon.png", settings_starter_id,
+ v2s32(m_screensize.X - (button_size / 2),
+ m_screensize.Y - ((SETTINGS_BAR_Y_OFFSET + 1) * button_size)
+ + (button_size * 0.5)),
+ v2s32(m_screensize.X,
+ m_screensize.Y - (SETTINGS_BAR_Y_OFFSET * button_size)
+ + (button_size * 0.5)), AHBB_Dir_Right_Left,
+ 3.0);
+
+ m_settingsbar.addButton(fly_id, L"fly", "fly_btn.png");
+ m_settingsbar.addButton(noclip_id, L"noclip", "noclip_btn.png");
+ m_settingsbar.addButton(fast_id, L"fast", "fast_btn.png");
+ m_settingsbar.addButton(debug_id, L"debug", "debug_btn.png");
+ m_settingsbar.addButton(camera_id, L"camera", "camera_btn.png");
+ m_settingsbar.addButton(range_id, L"rangeview", "rangeview_btn.png");
+
+ m_rarecontrolsbar.init(m_texturesource, "rare_controls.png",
+ rare_controls_starter_id,
+ v2s32(0,
+ m_screensize.Y
+ - ((RARE_CONTROLS_BAR_Y_OFFSET + 1) * button_size)
+ + (button_size * 0.5)),
+ v2s32(button_size / 2,
+ m_screensize.Y - (RARE_CONTROLS_BAR_Y_OFFSET * button_size)
+ + (button_size * 0.5)), AHBB_Dir_Left_Right,
+ 2);
+
+ m_rarecontrolsbar.addButton(chat_id, L"Chat", "chat_btn.png");
+ m_rarecontrolsbar.addButton(inventory_id, L"inv", "inventory_btn.png");
+ m_rarecontrolsbar.addButton(drop_id, L"drop", "drop_btn.png");
+
+}
+
+touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y)
+{
+ IGUIElement* rootguielement = m_guienv->getRootGUIElement();
+
+ if (rootguielement != NULL) {
+ gui::IGUIElement *element =
+ rootguielement->getElementFromPoint(core::position2d<s32>(x,y));
+
+ if (element) {
+ for (unsigned int i=0; i < after_last_element_id; i++) {
+ if (element == m_buttons[i].guibutton) {
+ return (touch_gui_button_id) i;
+ }
+ }
+ }
+ }
+ return after_last_element_id;
+}
+
+touch_gui_button_id TouchScreenGUI::getButtonID(int eventID)
+{
+ for (unsigned int i=0; i < after_last_element_id; i++) {
+ button_info* btn = &m_buttons[i];
+
+ std::vector<int>::iterator id =
+ std::find(btn->ids.begin(),btn->ids.end(), eventID);
+
+ if (id != btn->ids.end())
+ return (touch_gui_button_id) i;
+ }
+
+ return after_last_element_id;
+}
+
+bool TouchScreenGUI::isHUDButton(const SEvent &event)
+{
+ // check if hud item is pressed
+ for (std::map<int,rect<s32> >::iterator iter = m_hud_rects.begin();
+ iter != m_hud_rects.end(); ++iter) {
+ if (iter->second.isPointInside(
+ v2s32(event.TouchInput.X,
+ event.TouchInput.Y)
+ )) {
+ if ( iter->first < 8) {
+ SEvent* translated = new SEvent();
+ memset(translated,0,sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + iter->first);
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.PressedDown = true;
+ m_receiver->OnEvent(*translated);
+ m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key;
+ delete translated;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool TouchScreenGUI::isReleaseHUDButton(int eventID)
+{
+ std::map<int,irr::EKEY_CODE>::iterator iter = m_hud_ids.find(eventID);
+
+ if (iter != m_hud_ids.end()) {
+ SEvent* translated = new SEvent();
+ memset(translated,0,sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = iter->second;
+ translated->KeyInput.PressedDown = false;
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ m_receiver->OnEvent(*translated);
+ m_hud_ids.erase(iter);
+ delete translated;
+ return true;
+ }
+ return false;
+}
+
+void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button,
+ int eventID, bool action)
+{
+ button_info* btn = &m_buttons[button];
+ SEvent* translated = new SEvent();
+ memset(translated,0,sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = btn->keycode;
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.Char = 0;
+
+ /* add this event */
+ if (action) {
+ assert(std::find(btn->ids.begin(),btn->ids.end(), eventID) == btn->ids.end());
+
+ btn->ids.push_back(eventID);
+
+ if (btn->ids.size() > 1) return;
+
+ btn->repeatcounter = 0;
+ translated->KeyInput.PressedDown = true;
+ translated->KeyInput.Key = btn->keycode;
+ m_receiver->OnEvent(*translated);
+ }
+ /* remove event */
+ if ((!action) || (btn->immediate_release)) {
+
+ std::vector<int>::iterator pos =
+ std::find(btn->ids.begin(),btn->ids.end(), eventID);
+ /* has to be in touch list */
+ assert(pos != btn->ids.end());
+ btn->ids.erase(pos);
+
+ if (btn->ids.size() > 0) { return; }
+
+ translated->KeyInput.PressedDown = false;
+ btn->repeatcounter = -1;
+ m_receiver->OnEvent(*translated);
+ }
+ delete translated;
+}
+
+
+void TouchScreenGUI::handleReleaseEvent(int evt_id)
+{
+ touch_gui_button_id button = getButtonID(evt_id);
+
+ /* handle button events */
+ if (button != after_last_element_id) {
+ handleButtonEvent(button, evt_id, false);
+ }
+ /* handle hud button events */
+ else if (isReleaseHUDButton(evt_id)) {
+ /* nothing to do here */
+ } else if (m_settingsbar.isReleaseButton(evt_id)) {
+ /* nothing to do here */
+ } else if (m_rarecontrolsbar.isReleaseButton(evt_id)) {
+ /* nothing to do here */
+ }
+ /* handle the point used for moving view */
+ else if (evt_id == m_move_id) {
+ m_move_id = -1;
+
+ /* if this pointer issued a mouse event issue symmetric release here */
+ if (m_move_sent_as_mouse_event) {
+ SEvent* translated = new SEvent;
+ memset(translated,0,sizeof(SEvent));
+ translated->EventType = EET_MOUSE_INPUT_EVENT;
+ translated->MouseInput.X = m_move_downlocation.X;
+ translated->MouseInput.Y = m_move_downlocation.Y;
+ translated->MouseInput.Shift = false;
+ translated->MouseInput.Control = false;
+ translated->MouseInput.ButtonStates = 0;
+ translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
+ m_receiver->OnEvent(*translated);
+ delete translated;
+ }
+ else {
+ /* do double tap detection */
+ doubleTapDetection();
+ }
+ }
+ else {
+ infostream
+ << "TouchScreenGUI::translateEvent released unknown button: "
+ << evt_id << std::endl;
+ }
+
+ for (std::vector<id_status>::iterator iter = m_known_ids.begin();
+ iter != m_known_ids.end(); ++iter) {
+ if (iter->id == evt_id) {
+ m_known_ids.erase(iter);
+ break;
+ }
+ }
+}
+
+void TouchScreenGUI::translateEvent(const SEvent &event)
+{
+ if (!m_visible) {
+ infostream << "TouchScreenGUI::translateEvent got event but not visible?!" << std::endl;
+ return;
+ }
+
+ if (event.EventType != EET_TOUCH_INPUT_EVENT) {
+ return;
+ }
+
+ if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
+
+ /* add to own copy of eventlist ...
+ * android would provide this information but irrlicht guys don't
+ * wanna design a efficient interface
+ */
+ id_status toadd;
+ toadd.id = event.TouchInput.ID;
+ toadd.X = event.TouchInput.X;
+ toadd.Y = event.TouchInput.Y;
+ m_known_ids.push_back(toadd);
+
+ int eventID = event.TouchInput.ID;
+
+ touch_gui_button_id button =
+ getButtonID(event.TouchInput.X, event.TouchInput.Y);
+
+ /* handle button events */
+ if (button != after_last_element_id) {
+ handleButtonEvent(button, eventID, true);
+ m_settingsbar.deactivate();
+ m_rarecontrolsbar.deactivate();
+ } else if (isHUDButton(event)) {
+ m_settingsbar.deactivate();
+ m_rarecontrolsbar.deactivate();
+ /* already handled in isHUDButton() */
+ } else if (m_settingsbar.isButton(event)) {
+ m_rarecontrolsbar.deactivate();
+ /* already handled in isSettingsBarButton() */
+ } else if (m_rarecontrolsbar.isButton(event)) {
+ m_settingsbar.deactivate();
+ /* already handled in isSettingsBarButton() */
+ }
+ /* handle non button events */
+ else {
+ m_settingsbar.deactivate();
+ m_rarecontrolsbar.deactivate();
+ /* if we don't already have a moving point make this the moving one */
+ if (m_move_id == -1) {
+ m_move_id = event.TouchInput.ID;
+ m_move_has_really_moved = false;
+ m_move_downtime = porting::getTimeMs();
+ m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y);
+ m_move_sent_as_mouse_event = false;
+ }
+ }
+
+ m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y);
+ }
+ else if (event.TouchInput.Event == ETIE_LEFT_UP) {
+ verbosestream << "Up event for pointerid: " << event.TouchInput.ID << std::endl;
+ handleReleaseEvent(event.TouchInput.ID);
+ }
+ else {
+ assert(event.TouchInput.Event == ETIE_MOVED);
+ int move_idx = event.TouchInput.ID;
+
+ if (m_pointerpos[event.TouchInput.ID] ==
+ v2s32(event.TouchInput.X, event.TouchInput.Y)) {
+ return;
+ }
+
+ if (m_move_id != -1) {
+ if ((event.TouchInput.ID == m_move_id) &&
+ (!m_move_sent_as_mouse_event)) {
+
+ double distance = sqrt(
+ (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) *
+ (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) +
+ (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) *
+ (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y));
+
+ if ((distance > g_settings->getU16("touchscreen_threshold")) ||
+ (m_move_has_really_moved)) {
+ m_move_has_really_moved = true;
+ s32 X = event.TouchInput.X;
+ s32 Y = event.TouchInput.Y;
+
+ // update camera_yaw and camera_pitch
+ s32 dx = X - m_pointerpos[event.TouchInput.ID].X;
+ s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y;
+
+ /* adapt to similar behaviour as pc screen */
+ double d = g_settings->getFloat("mouse_sensitivity") *4;
+ double old_yaw = m_camera_yaw_change;
+ double old_pitch = m_camera_pitch;
+
+ m_camera_yaw_change -= dx * d;
+ m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * d), -180), 180);
+
+ // update shootline
+ m_shootline = m_device
+ ->getSceneManager()
+ ->getSceneCollisionManager()
+ ->getRayFromScreenCoordinates(v2s32(X, Y));
+ m_pointerpos[event.TouchInput.ID] = v2s32(X, Y);
+ }
+ }
+ else if ((event.TouchInput.ID == m_move_id) &&
+ (m_move_sent_as_mouse_event)) {
+ m_shootline = m_device
+ ->getSceneManager()
+ ->getSceneCollisionManager()
+ ->getRayFromScreenCoordinates(
+ v2s32(event.TouchInput.X,event.TouchInput.Y));
+ }
+ } else {
+ handleChangedButton(event);
+ }
+ }
+}
+
+void TouchScreenGUI::handleChangedButton(const SEvent &event)
+{
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
+
+ if (m_buttons[i].ids.empty()) {
+ continue;
+ }
+ for (std::vector<int>::iterator iter = m_buttons[i].ids.begin();
+ iter != m_buttons[i].ids.end(); ++iter) {
+
+ if (event.TouchInput.ID == *iter) {
+
+ int current_button_id =
+ getButtonID(event.TouchInput.X, event.TouchInput.Y);
+
+ if (current_button_id == i) {
+ continue;
+ }
+
+ /* remove old button */
+ handleButtonEvent((touch_gui_button_id) i,*iter,false);
+
+ if (current_button_id == after_last_element_id) {
+ return;
+ }
+ handleButtonEvent((touch_gui_button_id) current_button_id,*iter,true);
+ return;
+
+ }
+ }
+ }
+
+ int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y);
+
+ if (current_button_id == after_last_element_id) {
+ return;
+ }
+
+ button_info* btn = &m_buttons[current_button_id];
+ if (std::find(btn->ids.begin(),btn->ids.end(), event.TouchInput.ID)
+ == btn->ids.end())
+ {
+ handleButtonEvent((touch_gui_button_id) current_button_id,
+ event.TouchInput.ID, true);
+ }
+
+}
+
+bool TouchScreenGUI::doubleTapDetection()
+{
+ m_key_events[0].down_time = m_key_events[1].down_time;
+ m_key_events[0].x = m_key_events[1].x;
+ m_key_events[0].y = m_key_events[1].y;
+ m_key_events[1].down_time = m_move_downtime;
+ m_key_events[1].x = m_move_downlocation.X;
+ m_key_events[1].y = m_move_downlocation.Y;
+
+ u64 delta = porting::getDeltaMs(m_key_events[0].down_time, porting::getTimeMs());
+ if (delta > 400)
+ return false;
+
+ double distance = sqrt(
+ (m_key_events[0].x - m_key_events[1].x) * (m_key_events[0].x - m_key_events[1].x) +
+ (m_key_events[0].y - m_key_events[1].y) * (m_key_events[0].y - m_key_events[1].y));
+
+
+ if (distance > (20 + g_settings->getU16("touchscreen_threshold")))
+ return false;
+
+ SEvent* translated = new SEvent();
+ memset(translated, 0, sizeof(SEvent));
+ translated->EventType = EET_MOUSE_INPUT_EVENT;
+ translated->MouseInput.X = m_key_events[0].x;
+ translated->MouseInput.Y = m_key_events[0].y;
+ translated->MouseInput.Shift = false;
+ translated->MouseInput.Control = false;
+ translated->MouseInput.ButtonStates = EMBSM_RIGHT;
+
+ // update shootline
+ m_shootline = m_device
+ ->getSceneManager()
+ ->getSceneCollisionManager()
+ ->getRayFromScreenCoordinates(v2s32(m_key_events[0].x, m_key_events[0].y));
+
+ translated->MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
+ verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl;
+ m_receiver->OnEvent(*translated);
+
+ translated->MouseInput.ButtonStates = 0;
+ translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
+ verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
+ m_receiver->OnEvent(*translated);
+ delete translated;
+ return true;
+
+}
+
+TouchScreenGUI::~TouchScreenGUI()
+{
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
+ button_info* btn = &m_buttons[i];
+ if (btn->guibutton != 0) {
+ btn->guibutton->drop();
+ btn->guibutton = NULL;
+ }
+ }
+}
+
+void TouchScreenGUI::step(float dtime)
+{
+ /* simulate keyboard repeats */
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
+ button_info* btn = &m_buttons[i];
+
+ if (btn->ids.size() > 0) {
+ btn->repeatcounter += dtime;
+
+ /* in case we're moving around digging does not happen */
+ if (m_move_id != -1)
+ m_move_has_really_moved = true;
+
+ if (btn->repeatcounter < btn->repeatdelay) continue;
+
+ btn->repeatcounter = 0;
+ SEvent translated;
+ memset(&translated, 0, sizeof(SEvent));
+ translated.EventType = irr::EET_KEY_INPUT_EVENT;
+ translated.KeyInput.Key = btn->keycode;
+ translated.KeyInput.PressedDown = false;
+ m_receiver->OnEvent(translated);
+
+ translated.KeyInput.PressedDown = true;
+ m_receiver->OnEvent(translated);
+ }
+ }
+
+ /* if a new placed pointer isn't moved for some time start digging */
+ if ((m_move_id != -1) &&
+ (!m_move_has_really_moved) &&
+ (!m_move_sent_as_mouse_event)) {
+
+ u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
+
+ if (delta > MIN_DIG_TIME_MS) {
+ m_shootline = m_device
+ ->getSceneManager()
+ ->getSceneCollisionManager()
+ ->getRayFromScreenCoordinates(
+ v2s32(m_move_downlocation.X,m_move_downlocation.Y));
+
+ SEvent translated;
+ memset(&translated, 0, sizeof(SEvent));
+ translated.EventType = EET_MOUSE_INPUT_EVENT;
+ translated.MouseInput.X = m_move_downlocation.X;
+ translated.MouseInput.Y = m_move_downlocation.Y;
+ translated.MouseInput.Shift = false;
+ translated.MouseInput.Control = false;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT;
+ translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
+ verbosestream << "TouchScreenGUI::step left click press" << std::endl;
+ m_receiver->OnEvent(translated);
+ m_move_sent_as_mouse_event = true;
+ }
+ }
+
+ m_settingsbar.step(dtime);
+ m_rarecontrolsbar.step(dtime);
+}
+
+void TouchScreenGUI::resetHud()
+{
+ m_hud_rects.clear();
+}
+
+void TouchScreenGUI::registerHudItem(int index, const rect<s32> &rect)
+{
+ m_hud_rects[index] = rect;
+}
+
+void TouchScreenGUI::Toggle(bool visible)
+{
+ m_visible = visible;
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
+ button_info* btn = &m_buttons[i];
+ if (btn->guibutton != 0) {
+ btn->guibutton->setVisible(visible);
+ }
+ }
+
+ /* clear all active buttons */
+ if (!visible) {
+ while (m_known_ids.size() > 0) {
+ handleReleaseEvent(m_known_ids.begin()->id);
+ }
+
+ m_settingsbar.hide();
+ m_rarecontrolsbar.hide();
+ } else {
+ m_settingsbar.show();
+ m_rarecontrolsbar.show();
+ }
+}
+
+void TouchScreenGUI::hide()
+{
+ if (!m_visible)
+ return;
+
+ Toggle(false);
+}
+
+void TouchScreenGUI::show()
+{
+ if (m_visible)
+ return;
+
+ Toggle(true);
+}
--- /dev/null
+/*
+Copyright (C) 2014 sapier
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <IEventReceiver.h>
+#include <IGUIButton.h>
+#include <IGUIEnvironment.h>
+
+#include <map>
+#include <vector>
+
+#include "client/tile.h"
+#include "game.h"
+
+using namespace irr;
+using namespace irr::core;
+using namespace irr::gui;
+
+typedef enum {
+ forward_id = 0,
+ backward_id,
+ left_id,
+ right_id,
+ jump_id,
+ crunch_id,
+ after_last_element_id,
+ settings_starter_id,
+ rare_controls_starter_id,
+ fly_id,
+ noclip_id,
+ fast_id,
+ debug_id,
+ camera_id,
+ range_id,
+ chat_id,
+ inventory_id,
+ drop_id
+} touch_gui_button_id;
+
+typedef enum {
+ AHBB_Dir_Top_Bottom,
+ AHBB_Dir_Bottom_Top,
+ AHBB_Dir_Left_Right,
+ AHBB_Dir_Right_Left
+} autohide_button_bar_dir;
+
+#define MIN_DIG_TIME_MS 500
+#define MAX_TOUCH_COUNT 64
+#define BUTTON_REPEAT_DELAY 0.2f
+
+#define SETTINGS_BAR_Y_OFFSET 6.5
+#define RARE_CONTROLS_BAR_Y_OFFSET 4
+
+extern const char **touchgui_button_imagenames;
+
+struct button_info
+{
+ float repeatcounter;
+ float repeatdelay;
+ irr::EKEY_CODE keycode;
+ std::vector<int> ids;
+ IGUIButton *guibutton = nullptr;
+ bool immediate_release;
+};
+
+class AutoHideButtonBar
+{
+public:
+ AutoHideButtonBar(IrrlichtDevice *device, IEventReceiver *receiver);
+
+ void init(ISimpleTextureSource *tsrc, const char *starter_img, int button_id,
+ v2s32 UpperLeft, v2s32 LowerRight, autohide_button_bar_dir dir,
+ float timeout);
+
+ ~AutoHideButtonBar();
+
+ /* add button to be shown */
+ void addButton(touch_gui_button_id id, const wchar_t *caption,
+ const char *btn_image);
+
+ /* detect settings bar button events */
+ bool isButton(const SEvent &event);
+
+ /* handle released hud buttons */
+ bool isReleaseButton(int eventID);
+
+ /* step handler */
+ void step(float dtime);
+
+ /* deactivate button bar */
+ void deactivate();
+
+ /* hide the whole buttonbar */
+ void hide();
+
+ /* unhide the buttonbar */
+ void show();
+
+private:
+ ISimpleTextureSource *m_texturesource = nullptr;
+ irr::video::IVideoDriver *m_driver;
+ IGUIEnvironment *m_guienv;
+ IEventReceiver *m_receiver;
+ button_info m_starter;
+ std::vector<button_info *> m_buttons;
+
+ v2s32 m_upper_left;
+ v2s32 m_lower_right;
+
+ /* show settings bar */
+ bool m_active = false;
+
+ bool m_visible = true;
+
+ /* settings bar timeout */
+ float m_timeout = 0.0f;
+ float m_timeout_value = 3.0f;
+ bool m_initialized = false;
+ autohide_button_bar_dir m_dir = AHBB_Dir_Right_Left;
+};
+
+class TouchScreenGUI
+{
+public:
+ TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver);
+ ~TouchScreenGUI();
+
+ void translateEvent(const SEvent &event);
+
+ void init(ISimpleTextureSource *tsrc);
+
+ double getYawChange()
+ {
+ double res = m_camera_yaw_change;
+ m_camera_yaw_change = 0;
+ return res;
+ }
+
+ double getPitch() { return m_camera_pitch; }
+
+ /*!
+ * Returns a line which describes what the player is pointing at.
+ * The starting point and looking direction are significant,
+ * the line should be scaled to match its length to the actual distance
+ * the player can reach.
+ * The line starts at the camera and ends on the camera's far plane.
+ * The coordinates do not contain the camera offset.
+ */
+ line3d<f32> getShootline() { return m_shootline; }
+
+ void step(float dtime);
+ void resetHud();
+ void registerHudItem(int index, const rect<s32> &rect);
+ void Toggle(bool visible);
+
+ void hide();
+ void show();
+
+private:
+ IrrlichtDevice *m_device;
+ IGUIEnvironment *m_guienv;
+ IEventReceiver *m_receiver;
+ ISimpleTextureSource *m_texturesource;
+ v2u32 m_screensize;
+ std::map<int, rect<s32>> m_hud_rects;
+ std::map<int, irr::EKEY_CODE> m_hud_ids;
+ bool m_visible; // is the gui visible
+
+ /* value in degree */
+ double m_camera_yaw_change = 0.0;
+ double m_camera_pitch = 0.0;
+
+ /*!
+ * A line starting at the camera and pointing towards the
+ * selected object.
+ * The line ends on the camera's far plane.
+ * The coordinates do not contain the camera offset.
+ */
+ line3d<f32> m_shootline;
+
+ int m_move_id = -1;
+ bool m_move_has_really_moved = false;
+ s64 m_move_downtime = 0;
+ bool m_move_sent_as_mouse_event = false;
+ v2s32 m_move_downlocation = v2s32(-10000, -10000);
+
+ button_info m_buttons[after_last_element_id];
+
+ /* gui button detection */
+ touch_gui_button_id getButtonID(s32 x, s32 y);
+
+ /* gui button by eventID */
+ touch_gui_button_id getButtonID(int eventID);
+
+ /* check if a button has changed */
+ void handleChangedButton(const SEvent &event);
+
+ /* initialize a button */
+ void initButton(touch_gui_button_id id, rect<s32> button_rect,
+ std::wstring caption, bool immediate_release,
+ float repeat_delay = BUTTON_REPEAT_DELAY);
+
+ struct id_status
+ {
+ int id;
+ int X;
+ int Y;
+ };
+
+ /* vector to store known ids and their initial touch positions*/
+ std::vector<id_status> m_known_ids;
+
+ /* handle a button event */
+ void handleButtonEvent(touch_gui_button_id bID, int eventID, bool action);
+
+ /* handle pressed hud buttons */
+ bool isHUDButton(const SEvent &event);
+
+ /* handle released hud buttons */
+ bool isReleaseHUDButton(int eventID);
+
+ /* handle double taps */
+ bool doubleTapDetection();
+
+ /* handle release event */
+ void handleReleaseEvent(int evt_id);
+
+ /* get size of regular gui control button */
+ int getGuiButtonSize();
+
+ /* doubleclick detection variables */
+ struct key_event
+ {
+ unsigned int down_time;
+ s32 x;
+ s32 y;
+ };
+
+ /* array for saving last known position of a pointer */
+ v2s32 m_pointerpos[MAX_TOUCH_COUNT];
+
+ /* array for doubletap detection */
+ key_event m_key_events[2];
+
+ /* settings bar */
+ AutoHideButtonBar m_settingsbar;
+
+ /* rare controls bar */
+ AutoHideButtonBar m_rarecontrolsbar;
+};
+extern TouchScreenGUI *g_touchscreengui;
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "guiChatConsole.h"
-#include "chat.h"
-#include "client.h"
-#include "debug.h"
-#include "gettime.h"
-#include "keycode.h"
-#include "settings.h"
-#include "porting.h"
-#include "client/tile.h"
-#include "fontengine.h"
-#include "log.h"
-#include "gettext.h"
-#include <string>
-
-#if USE_FREETYPE
- #include "irrlicht_changes/CGUITTFont.h"
-#endif
-
-inline u32 clamp_u8(s32 value)
-{
- return (u32) MYMIN(MYMAX(value, 0), 255);
-}
-
-
-GUIChatConsole::GUIChatConsole(
- gui::IGUIEnvironment* env,
- gui::IGUIElement* parent,
- s32 id,
- ChatBackend* backend,
- Client* client,
- IMenuManager* menumgr
-):
- IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
- core::rect<s32>(0,0,100,100)),
- m_chat_backend(backend),
- m_client(client),
- m_menumgr(menumgr),
- m_animate_time_old(porting::getTimeMs())
-{
- // load background settings
- s32 console_alpha = g_settings->getS32("console_alpha");
- m_background_color.setAlpha(clamp_u8(console_alpha));
-
- // load the background texture depending on settings
- ITextureSource *tsrc = client->getTextureSource();
- if (tsrc->isKnownSourceImage("background_chat.jpg")) {
- m_background = tsrc->getTexture("background_chat.jpg");
- m_background_color.setRed(255);
- m_background_color.setGreen(255);
- m_background_color.setBlue(255);
- } else {
- v3f console_color = g_settings->getV3F("console_color");
- m_background_color.setRed(clamp_u8(myround(console_color.X)));
- m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
- m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
- }
-
- m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono);
-
- if (!m_font) {
- errorstream << "GUIChatConsole: Unable to load mono font ";
- } else {
- core::dimension2d<u32> dim = m_font->getDimension(L"M");
- m_fontsize = v2u32(dim.Width, dim.Height);
- m_font->grab();
- }
- m_fontsize.X = MYMAX(m_fontsize.X, 1);
- m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
-
- // set default cursor options
- setCursor(true, true, 2.0, 0.1);
-}
-
-GUIChatConsole::~GUIChatConsole()
-{
- if (m_font)
- m_font->drop();
-}
-
-void GUIChatConsole::openConsole(f32 scale)
-{
- assert(scale > 0.0f && scale <= 1.0f);
-
- m_open = true;
- m_desired_height_fraction = scale;
- m_desired_height = scale * m_screensize.Y;
- reformatConsole();
- m_animate_time_old = porting::getTimeMs();
- IGUIElement::setVisible(true);
- Environment->setFocus(this);
- m_menumgr->createdMenu(this);
-}
-
-bool GUIChatConsole::isOpen() const
-{
- return m_open;
-}
-
-bool GUIChatConsole::isOpenInhibited() const
-{
- return m_open_inhibited > 0;
-}
-
-void GUIChatConsole::closeConsole()
-{
- m_open = false;
- Environment->removeFocus(this);
- m_menumgr->deletingMenu(this);
-}
-
-void GUIChatConsole::closeConsoleAtOnce()
-{
- closeConsole();
- m_height = 0;
- recalculateConsolePosition();
-}
-
-f32 GUIChatConsole::getDesiredHeight() const
-{
- return m_desired_height_fraction;
-}
-
-void GUIChatConsole::replaceAndAddToHistory(std::wstring line)
-{
- ChatPrompt& prompt = m_chat_backend->getPrompt();
- prompt.addToHistory(prompt.getLine());
- prompt.replace(line);
-}
-
-
-void GUIChatConsole::setCursor(
- bool visible, bool blinking, f32 blink_speed, f32 relative_height)
-{
- if (visible)
- {
- if (blinking)
- {
- // leave m_cursor_blink unchanged
- m_cursor_blink_speed = blink_speed;
- }
- else
- {
- m_cursor_blink = 0x8000; // on
- m_cursor_blink_speed = 0.0;
- }
- }
- else
- {
- m_cursor_blink = 0; // off
- m_cursor_blink_speed = 0.0;
- }
- m_cursor_height = relative_height;
-}
-
-void GUIChatConsole::draw()
-{
- if(!IsVisible)
- return;
-
- video::IVideoDriver* driver = Environment->getVideoDriver();
-
- // Check screen size
- v2u32 screensize = driver->getScreenSize();
- if (screensize != m_screensize)
- {
- // screen size has changed
- // scale current console height to new window size
- if (m_screensize.Y != 0)
- m_height = m_height * screensize.Y / m_screensize.Y;
- m_screensize = screensize;
- m_desired_height = m_desired_height_fraction * m_screensize.Y;
- reformatConsole();
- }
-
- // Animation
- u64 now = porting::getTimeMs();
- animate(now - m_animate_time_old);
- m_animate_time_old = now;
-
- // Draw console elements if visible
- if (m_height > 0)
- {
- drawBackground();
- drawText();
- drawPrompt();
- }
-
- gui::IGUIElement::draw();
-}
-
-void GUIChatConsole::reformatConsole()
-{
- s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
- s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
- if (cols <= 0 || rows <= 0)
- cols = rows = 0;
- recalculateConsolePosition();
- m_chat_backend->reformat(cols, rows);
-}
-
-void GUIChatConsole::recalculateConsolePosition()
-{
- core::rect<s32> rect(0, 0, m_screensize.X, m_height);
- DesiredRect = rect;
- recalculateAbsolutePosition(false);
-}
-
-void GUIChatConsole::animate(u32 msec)
-{
- // animate the console height
- s32 goal = m_open ? m_desired_height : 0;
-
- // Set invisible if close animation finished (reset by openConsole)
- // This function (animate()) is never called once its visibility becomes false so do not
- // actually set visible to false before the inhibited period is over
- if (!m_open && m_height == 0 && m_open_inhibited == 0)
- IGUIElement::setVisible(false);
-
- if (m_height != goal)
- {
- s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
- if (max_change == 0)
- max_change = 1;
-
- if (m_height < goal)
- {
- // increase height
- if (m_height + max_change < goal)
- m_height += max_change;
- else
- m_height = goal;
- }
- else
- {
- // decrease height
- if (m_height > goal + max_change)
- m_height -= max_change;
- else
- m_height = goal;
- }
-
- recalculateConsolePosition();
- }
-
- // blink the cursor
- if (m_cursor_blink_speed != 0.0)
- {
- u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
- if (blink_increase == 0)
- blink_increase = 1;
- m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
- }
-
- // decrease open inhibit counter
- if (m_open_inhibited > msec)
- m_open_inhibited -= msec;
- else
- m_open_inhibited = 0;
-}
-
-void GUIChatConsole::drawBackground()
-{
- video::IVideoDriver* driver = Environment->getVideoDriver();
- if (m_background != NULL)
- {
- core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
- driver->draw2DImage(
- m_background,
- v2s32(0, 0),
- sourcerect,
- &AbsoluteClippingRect,
- m_background_color,
- false);
- }
- else
- {
- driver->draw2DRectangle(
- m_background_color,
- core::rect<s32>(0, 0, m_screensize.X, m_height),
- &AbsoluteClippingRect);
- }
-}
-
-void GUIChatConsole::drawText()
-{
- if (m_font == NULL)
- return;
-
- ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
- for (u32 row = 0; row < buf.getRows(); ++row)
- {
- const ChatFormattedLine& line = buf.getFormattedLine(row);
- if (line.fragments.empty())
- continue;
-
- s32 line_height = m_fontsize.Y;
- s32 y = row * line_height + m_height - m_desired_height;
- if (y + line_height < 0)
- continue;
-
- for (const ChatFormattedFragment &fragment : line.fragments) {
- s32 x = (fragment.column + 1) * m_fontsize.X;
- core::rect<s32> destrect(
- x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
-
-
- #if USE_FREETYPE
- // Draw colored text if FreeType is enabled
- irr::gui::CGUITTFont *tmp = dynamic_cast<irr::gui::CGUITTFont *>(m_font);
- tmp->draw(
- fragment.text,
- destrect,
- video::SColor(255, 255, 255, 255),
- false,
- false,
- &AbsoluteClippingRect);
- #else
- // Otherwise use standard text
- m_font->draw(
- fragment.text.c_str(),
- destrect,
- video::SColor(255, 255, 255, 255),
- false,
- false,
- &AbsoluteClippingRect);
- #endif
- }
- }
-}
-
-void GUIChatConsole::drawPrompt()
-{
- if (!m_font)
- return;
-
- u32 row = m_chat_backend->getConsoleBuffer().getRows();
- s32 line_height = m_fontsize.Y;
- s32 y = row * line_height + m_height - m_desired_height;
-
- ChatPrompt& prompt = m_chat_backend->getPrompt();
- std::wstring prompt_text = prompt.getVisiblePortion();
-
- // FIXME Draw string at once, not character by character
- // That will only work with the cursor once we have a monospace font
- for (u32 i = 0; i < prompt_text.size(); ++i)
- {
- wchar_t ws[2] = {prompt_text[i], 0};
- s32 x = (1 + i) * m_fontsize.X;
- core::rect<s32> destrect(
- x, y, x + m_fontsize.X, y + m_fontsize.Y);
- m_font->draw(
- ws,
- destrect,
- video::SColor(255, 255, 255, 255),
- false,
- false,
- &AbsoluteClippingRect);
- }
-
- // Draw the cursor during on periods
- if ((m_cursor_blink & 0x8000) != 0)
- {
- s32 cursor_pos = prompt.getVisibleCursorPosition();
- if (cursor_pos >= 0)
- {
- s32 cursor_len = prompt.getCursorLength();
- video::IVideoDriver* driver = Environment->getVideoDriver();
- s32 x = (1 + cursor_pos) * m_fontsize.X;
- core::rect<s32> destrect(
- x,
- y + m_fontsize.Y * (1.0 - m_cursor_height),
- x + m_fontsize.X * MYMAX(cursor_len, 1),
- y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1)
- );
- video::SColor cursor_color(255,255,255,255);
- driver->draw2DRectangle(
- cursor_color,
- destrect,
- &AbsoluteClippingRect);
- }
- }
-
-}
-
-bool GUIChatConsole::OnEvent(const SEvent& event)
-{
-
- ChatPrompt &prompt = m_chat_backend->getPrompt();
-
- if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
- {
- // Key input
- if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) {
- closeConsole();
-
- // inhibit open so the_game doesn't reopen immediately
- m_open_inhibited = 50;
- m_close_on_enter = false;
- return true;
- }
-
- if (event.KeyInput.Key == KEY_ESCAPE) {
- closeConsoleAtOnce();
- m_close_on_enter = false;
- // inhibit open so the_game doesn't reopen immediately
- m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
- return true;
- }
- else if(event.KeyInput.Key == KEY_PRIOR)
- {
- m_chat_backend->scrollPageUp();
- return true;
- }
- else if(event.KeyInput.Key == KEY_NEXT)
- {
- m_chat_backend->scrollPageDown();
- return true;
- }
- else if(event.KeyInput.Key == KEY_RETURN)
- {
- prompt.addToHistory(prompt.getLine());
- std::wstring text = prompt.replace(L"");
- m_client->typeChatMessage(text);
- if (m_close_on_enter) {
- closeConsoleAtOnce();
- m_close_on_enter = false;
- }
- return true;
- }
- else if(event.KeyInput.Key == KEY_UP)
- {
- // Up pressed
- // Move back in history
- prompt.historyPrev();
- return true;
- }
- else if(event.KeyInput.Key == KEY_DOWN)
- {
- // Down pressed
- // Move forward in history
- prompt.historyNext();
- return true;
- }
- else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
- {
- // Left/right pressed
- // Move/select character/word to the left depending on control and shift keys
- ChatPrompt::CursorOp op = event.KeyInput.Shift ?
- ChatPrompt::CURSOROP_SELECT :
- ChatPrompt::CURSOROP_MOVE;
- ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
- ChatPrompt::CURSOROP_DIR_LEFT :
- ChatPrompt::CURSOROP_DIR_RIGHT;
- ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
- ChatPrompt::CURSOROP_SCOPE_WORD :
- ChatPrompt::CURSOROP_SCOPE_CHARACTER;
- prompt.cursorOperation(op, dir, scope);
- return true;
- }
- else if(event.KeyInput.Key == KEY_HOME)
- {
- // Home pressed
- // move to beginning of line
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_MOVE,
- ChatPrompt::CURSOROP_DIR_LEFT,
- ChatPrompt::CURSOROP_SCOPE_LINE);
- return true;
- }
- else if(event.KeyInput.Key == KEY_END)
- {
- // End pressed
- // move to end of line
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_MOVE,
- ChatPrompt::CURSOROP_DIR_RIGHT,
- ChatPrompt::CURSOROP_SCOPE_LINE);
- return true;
- }
- else if(event.KeyInput.Key == KEY_BACK)
- {
- // Backspace or Ctrl-Backspace pressed
- // delete character / word to the left
- ChatPrompt::CursorOpScope scope =
- event.KeyInput.Control ?
- ChatPrompt::CURSOROP_SCOPE_WORD :
- ChatPrompt::CURSOROP_SCOPE_CHARACTER;
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_DELETE,
- ChatPrompt::CURSOROP_DIR_LEFT,
- scope);
- return true;
- }
- else if(event.KeyInput.Key == KEY_DELETE)
- {
- // Delete or Ctrl-Delete pressed
- // delete character / word to the right
- ChatPrompt::CursorOpScope scope =
- event.KeyInput.Control ?
- ChatPrompt::CURSOROP_SCOPE_WORD :
- ChatPrompt::CURSOROP_SCOPE_CHARACTER;
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_DELETE,
- ChatPrompt::CURSOROP_DIR_RIGHT,
- scope);
- return true;
- }
- else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
- {
- // Ctrl-A pressed
- // Select all text
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_SELECT,
- ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
- ChatPrompt::CURSOROP_SCOPE_LINE);
- return true;
- }
- else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
- {
- // Ctrl-C pressed
- // Copy text to clipboard
- if (prompt.getCursorLength() <= 0)
- return true;
- std::wstring wselected = prompt.getSelection();
- std::string selected(wselected.begin(), wselected.end());
- Environment->getOSOperator()->copyToClipboard(selected.c_str());
- return true;
- }
- else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
- {
- // Ctrl-V pressed
- // paste text from clipboard
- if (prompt.getCursorLength() > 0) {
- // Delete selected section of text
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_DELETE,
- ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
- ChatPrompt::CURSOROP_SCOPE_SELECTION);
- }
- IOSOperator *os_operator = Environment->getOSOperator();
- const c8 *text = os_operator->getTextFromClipboard();
- if (!text)
- return true;
- std::basic_string<unsigned char> str((const unsigned char*)text);
- prompt.input(std::wstring(str.begin(), str.end()));
- return true;
- }
- else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
- {
- // Ctrl-X pressed
- // Cut text to clipboard
- if (prompt.getCursorLength() <= 0)
- return true;
- std::wstring wselected = prompt.getSelection();
- std::string selected(wselected.begin(), wselected.end());
- Environment->getOSOperator()->copyToClipboard(selected.c_str());
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_DELETE,
- ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
- ChatPrompt::CURSOROP_SCOPE_SELECTION);
- return true;
- }
- else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
- {
- // Ctrl-U pressed
- // kill line to left end
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_DELETE,
- ChatPrompt::CURSOROP_DIR_LEFT,
- ChatPrompt::CURSOROP_SCOPE_LINE);
- return true;
- }
- else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
- {
- // Ctrl-K pressed
- // kill line to right end
- prompt.cursorOperation(
- ChatPrompt::CURSOROP_DELETE,
- ChatPrompt::CURSOROP_DIR_RIGHT,
- ChatPrompt::CURSOROP_SCOPE_LINE);
- return true;
- }
- else if(event.KeyInput.Key == KEY_TAB)
- {
- // Tab or Shift-Tab pressed
- // Nick completion
- std::list<std::string> names = m_client->getConnectedPlayerNames();
- bool backwards = event.KeyInput.Shift;
- prompt.nickCompletion(names, backwards);
- return true;
- } else if (!iswcntrl(event.KeyInput.Char) && !event.KeyInput.Control) {
- #if defined(__linux__) && (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9)
- wchar_t wc = L'_';
- mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
- prompt.input(wc);
- #else
- prompt.input(event.KeyInput.Char);
- #endif
- return true;
- }
- }
- else if(event.EventType == EET_MOUSE_INPUT_EVENT)
- {
- if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
- {
- s32 rows = myround(-3.0 * event.MouseInput.Wheel);
- m_chat_backend->scroll(rows);
- }
- }
-
- return Parent ? Parent->OnEvent(event) : false;
-}
-
-void GUIChatConsole::setVisible(bool visible)
-{
- m_open = visible;
- IGUIElement::setVisible(visible);
- if (!visible) {
- m_height = 0;
- recalculateConsolePosition();
- }
-}
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "modalMenu.h"
-#include "chat.h"
-#include "config.h"
-
-class Client;
-
-class GUIChatConsole : public gui::IGUIElement
-{
-public:
- GUIChatConsole(gui::IGUIEnvironment* env,
- gui::IGUIElement* parent,
- s32 id,
- ChatBackend* backend,
- Client* client,
- IMenuManager* menumgr);
- virtual ~GUIChatConsole();
-
- // Open the console (height = desired fraction of screen size)
- // This doesn't open immediately but initiates an animation.
- // You should call isOpenInhibited() before this.
- void openConsole(f32 scale);
-
- bool isOpen() const;
-
- // Check if the console should not be opened at the moment
- // This is to avoid reopening the console immediately after closing
- bool isOpenInhibited() const;
- // Close the console, equivalent to openConsole(0).
- // This doesn't close immediately but initiates an animation.
- void closeConsole();
- // Close the console immediately, without animation.
- void closeConsoleAtOnce();
- // Set whether to close the console after the user presses enter.
- void setCloseOnEnter(bool close) { m_close_on_enter = close; }
-
- // Return the desired height (fraction of screen size)
- // Zero if the console is closed or getting closed
- f32 getDesiredHeight() const;
-
- // Replace actual line when adding the actual to the history (if there is any)
- void replaceAndAddToHistory(std::wstring line);
-
- // Change how the cursor looks
- void setCursor(
- bool visible,
- bool blinking = false,
- f32 blink_speed = 1.0,
- f32 relative_height = 1.0);
-
- // Irrlicht draw method
- virtual void draw();
-
- bool canTakeFocus(gui::IGUIElement* element) { return false; }
-
- virtual bool OnEvent(const SEvent& event);
-
- virtual void setVisible(bool visible);
-
-private:
- void reformatConsole();
- void recalculateConsolePosition();
-
- // These methods are called by draw
- void animate(u32 msec);
- void drawBackground();
- void drawText();
- void drawPrompt();
-
-private:
- ChatBackend* m_chat_backend;
- Client* m_client;
- IMenuManager* m_menumgr;
-
- // current screen size
- v2u32 m_screensize;
-
- // used to compute how much time passed since last animate()
- u64 m_animate_time_old;
-
- // should the console be opened or closed?
- bool m_open = false;
- // should it close after you press enter?
- bool m_close_on_enter = false;
- // current console height [pixels]
- s32 m_height = 0;
- // desired height [pixels]
- f32 m_desired_height = 0.0f;
- // desired height [screen height fraction]
- f32 m_desired_height_fraction = 0.0f;
- // console open/close animation speed [screen height fraction / second]
- f32 m_height_speed = 5.0f;
- // if nonzero, opening the console is inhibited [milliseconds]
- u32 m_open_inhibited = 0;
-
- // cursor blink frame (16-bit value)
- // cursor is off during [0,32767] and on during [32768,65535]
- u32 m_cursor_blink = 0;
- // cursor blink speed [on/off toggles / second]
- f32 m_cursor_blink_speed = 0.0f;
- // cursor height [line height]
- f32 m_cursor_height = 0.0f;
-
- // background texture
- video::ITexture *m_background = nullptr;
- // background color (including alpha)
- video::SColor m_background_color = video::SColor(255, 0, 0, 0);
-
- // font
- gui::IGUIFont *m_font = nullptr;
- v2u32 m_fontsize;
-};
+++ /dev/null
-// Copyright (C) 2002-2012 Nikolaus Gebhardt
-// Modified by Mustapha T.
-// This file is part of the "Irrlicht Engine".
-// For conditions of distribution and use, see copyright notice in irrlicht.h
-
-#include "guiEditBoxWithScrollbar.h"
-
-#include "IGUISkin.h"
-#include "IGUIEnvironment.h"
-#include "IGUIFont.h"
-#include "IVideoDriver.h"
-#include "rect.h"
-#include "porting.h"
-#include "Keycodes.h"
-
-
-/*
-todo:
-optional scrollbars [done]
-ctrl+left/right to select word
-double click/ctrl click: word select + drag to select whole words, triple click to select line
-optional? dragging selected text
-numerical
-*/
-
-
-//! constructor
-GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool border,
- IGUIEnvironment* environment, IGUIElement* parent, s32 id,
- const core::rect<s32>& rectangle, bool writable, bool has_vscrollbar)
- : IGUIEditBox(environment, parent, id, rectangle), m_mouse_marking(false),
- m_border(border), m_background(true), m_override_color_enabled(false), m_mark_begin(0), m_mark_end(0),
- m_override_color(video::SColor(101, 255, 255, 255)), m_override_font(0), m_last_break_font(0),
- m_operator(0), m_blink_start_time(0), m_cursor_pos(0), m_hscroll_pos(0), m_vscroll_pos(0), m_max(0),
- m_word_wrap(false), m_multiline(false), m_autoscroll(true), m_passwordbox(false),
- m_passwordchar(L'*'), m_halign(EGUIA_UPPERLEFT), m_valign(EGUIA_CENTER),
- m_current_text_rect(0, 0, 1, 1), m_frame_rect(rectangle),
- m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable),
- m_bg_color_used(false)
-{
-#ifdef _DEBUG
- setDebugName("GUIEditBoxWithScrollBar");
-#endif
-
-
- Text = text;
-
- if (Environment)
- m_operator = Environment->getOSOperator();
-
- if (m_operator)
- m_operator->grab();
-
- // this element can be tabbed to
- setTabStop(true);
- setTabOrder(-1);
-
- if (has_vscrollbar) {
- createVScrollBar();
- }
-
- calculateFrameRect();
- breakText();
-
- calculateScrollPos();
- setWritable(writable);
-}
-
-
-//! destructor
-GUIEditBoxWithScrollBar::~GUIEditBoxWithScrollBar()
-{
- if (m_override_font)
- m_override_font->drop();
-
- if (m_operator)
- m_operator->drop();
-
- m_vscrollbar->remove();
-}
-
-
-//! Sets another skin independent font.
-void GUIEditBoxWithScrollBar::setOverrideFont(IGUIFont* font)
-{
- if (m_override_font == font)
- return;
-
- if (m_override_font)
- m_override_font->drop();
-
- m_override_font = font;
-
- if (m_override_font)
- m_override_font->grab();
-
- breakText();
-}
-
-//! Gets the override font (if any)
-IGUIFont * GUIEditBoxWithScrollBar::getOverrideFont() const
-{
- return m_override_font;
-}
-
-//! Get the font which is used right now for drawing
-IGUIFont* GUIEditBoxWithScrollBar::getActiveFont() const
-{
- if (m_override_font)
- return m_override_font;
- IGUISkin* skin = Environment->getSkin();
- if (skin)
- return skin->getFont();
- return 0;
-}
-
-//! Sets another color for the text.
-void GUIEditBoxWithScrollBar::setOverrideColor(video::SColor color)
-{
- m_override_color = color;
- m_override_color_enabled = true;
-}
-
-
-video::SColor GUIEditBoxWithScrollBar::getOverrideColor() const
-{
- return m_override_color;
-}
-
-
-//! Turns the border on or off
-void GUIEditBoxWithScrollBar::setDrawBorder(bool border)
-{
- m_border = border;
-}
-
-//! Sets whether to draw the background
-void GUIEditBoxWithScrollBar::setDrawBackground(bool draw)
-{
- m_background = draw;
-}
-
-//! Sets if the text should use the overide color or the color in the gui skin.
-void GUIEditBoxWithScrollBar::enableOverrideColor(bool enable)
-{
- m_override_color_enabled = enable;
-}
-
-bool GUIEditBoxWithScrollBar::isOverrideColorEnabled() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return m_override_color_enabled;
-}
-
-//! Enables or disables word wrap
-void GUIEditBoxWithScrollBar::setWordWrap(bool enable)
-{
- m_word_wrap = enable;
- breakText();
-}
-
-
-void GUIEditBoxWithScrollBar::updateAbsolutePosition()
-{
- core::rect<s32> old_absolute_rect(AbsoluteRect);
- IGUIElement::updateAbsolutePosition();
- if (old_absolute_rect != AbsoluteRect) {
- calculateFrameRect();
- breakText();
- calculateScrollPos();
- }
-}
-
-//! Checks if word wrap is enabled
-bool GUIEditBoxWithScrollBar::isWordWrapEnabled() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return m_word_wrap;
-}
-
-
-//! Enables or disables newlines.
-void GUIEditBoxWithScrollBar::setMultiLine(bool enable)
-{
- m_multiline = enable;
-}
-
-
-//! Checks if multi line editing is enabled
-bool GUIEditBoxWithScrollBar::isMultiLineEnabled() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return m_multiline;
-}
-
-
-void GUIEditBoxWithScrollBar::setPasswordBox(bool password_box, wchar_t password_char)
-{
- m_passwordbox = password_box;
- if (m_passwordbox) {
- m_passwordchar = password_char;
- setMultiLine(false);
- setWordWrap(false);
- m_broken_text.clear();
- }
-}
-
-
-bool GUIEditBoxWithScrollBar::isPasswordBox() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return m_passwordbox;
-}
-
-
-//! Sets text justification
-void GUIEditBoxWithScrollBar::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
-{
- m_halign = horizontal;
- m_valign = vertical;
-}
-
-
-//! called if an event happened.
-bool GUIEditBoxWithScrollBar::OnEvent(const SEvent& event)
-{
- if (isEnabled()) {
- switch (event.EventType)
- {
- case EET_GUI_EVENT:
- if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) {
- if (event.GUIEvent.Caller == this) {
- m_mouse_marking = false;
- setTextMarkers(0, 0);
- }
- }
- break;
- case EET_KEY_INPUT_EVENT:
- if (processKey(event))
- return true;
- break;
- case EET_MOUSE_INPUT_EVENT:
- if (processMouse(event))
- return true;
- break;
- default:
- break;
- }
- }
-
- return IGUIElement::OnEvent(event);
-}
-
-
-bool GUIEditBoxWithScrollBar::processKey(const SEvent& event)
-{
- if (!m_writable) {
- return false;
- }
-
- if (!event.KeyInput.PressedDown)
- return false;
-
- bool text_changed = false;
- s32 new_mark_begin = m_mark_begin;
- s32 new_mark_end = m_mark_end;
-
- // control shortcut handling
-
- if (event.KeyInput.Control) {
-
- // german backlash '\' entered with control + '?'
- if (event.KeyInput.Char == '\\') {
- inputChar(event.KeyInput.Char);
- return true;
- }
-
- switch (event.KeyInput.Key) {
- case KEY_KEY_A:
- // select all
- new_mark_begin = 0;
- new_mark_end = Text.size();
- break;
- case KEY_KEY_C:
- // copy to clipboard
- if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end)
- {
- const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
- const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
-
- core::stringc s;
- s = Text.subString(realmbgn, realmend - realmbgn).c_str();
- m_operator->copyToClipboard(s.c_str());
- }
- break;
- case KEY_KEY_X:
- // cut to the clipboard
- if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end) {
- const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
- const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
-
- // copy
- core::stringc sc;
- sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
- m_operator->copyToClipboard(sc.c_str());
-
- if (isEnabled())
- {
- // delete
- core::stringw s;
- s = Text.subString(0, realmbgn);
- s.append(Text.subString(realmend, Text.size() - realmend));
- Text = s;
-
- m_cursor_pos = realmbgn;
- new_mark_begin = 0;
- new_mark_end = 0;
- text_changed = true;
- }
- }
- break;
- case KEY_KEY_V:
- if (!isEnabled())
- break;
-
- // paste from the clipboard
- if (m_operator) {
- const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
- const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
-
- // add new character
- const c8* p = m_operator->getTextFromClipboard();
- if (p) {
- if (m_mark_begin == m_mark_end) {
- // insert text
- core::stringw s = Text.subString(0, m_cursor_pos);
- s.append(p);
- s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
-
- if (!m_max || s.size() <= m_max) // thx to Fish FH for fix
- {
- Text = s;
- s = p;
- m_cursor_pos += s.size();
- }
- } else {
- // replace text
-
- core::stringw s = Text.subString(0, realmbgn);
- s.append(p);
- s.append(Text.subString(realmend, Text.size() - realmend));
-
- if (!m_max || s.size() <= m_max) // thx to Fish FH for fix
- {
- Text = s;
- s = p;
- m_cursor_pos = realmbgn + s.size();
- }
- }
- }
-
- new_mark_begin = 0;
- new_mark_end = 0;
- text_changed = true;
- }
- break;
- case KEY_HOME:
- // move/highlight to start of text
- if (event.KeyInput.Shift) {
- new_mark_end = m_cursor_pos;
- new_mark_begin = 0;
- m_cursor_pos = 0;
- } else {
- m_cursor_pos = 0;
- new_mark_begin = 0;
- new_mark_end = 0;
- }
- break;
- case KEY_END:
- // move/highlight to end of text
- if (event.KeyInput.Shift) {
- new_mark_begin = m_cursor_pos;
- new_mark_end = Text.size();
- m_cursor_pos = 0;
- } else {
- m_cursor_pos = Text.size();
- new_mark_begin = 0;
- new_mark_end = 0;
- }
- break;
- default:
- return false;
- }
- }
- // default keyboard handling
- else
- switch (event.KeyInput.Key) {
- case KEY_END:
- {
- s32 p = Text.size();
- if (m_word_wrap || m_multiline) {
- p = getLineFromPos(m_cursor_pos);
- p = m_broken_text_positions[p] + (s32)m_broken_text[p].size();
- if (p > 0 && (Text[p - 1] == L'\r' || Text[p - 1] == L'\n'))
- p -= 1;
- }
-
- if (event.KeyInput.Shift) {
- if (m_mark_begin == m_mark_end)
- new_mark_begin = m_cursor_pos;
-
- new_mark_end = p;
- } else {
- new_mark_begin = 0;
- new_mark_end = 0;
- }
- m_cursor_pos = p;
- m_blink_start_time = porting::getTimeMs();
- }
- break;
- case KEY_HOME:
- {
-
- s32 p = 0;
- if (m_word_wrap || m_multiline) {
- p = getLineFromPos(m_cursor_pos);
- p = m_broken_text_positions[p];
- }
-
- if (event.KeyInput.Shift) {
- if (m_mark_begin == m_mark_end)
- new_mark_begin = m_cursor_pos;
- new_mark_end = p;
- } else {
- new_mark_begin = 0;
- new_mark_end = 0;
- }
- m_cursor_pos = p;
- m_blink_start_time = porting::getTimeMs();
- }
- break;
- case KEY_RETURN:
- if (m_multiline) {
- inputChar(L'\n');
- } else {
- calculateScrollPos();
- sendGuiEvent(EGET_EDITBOX_ENTER);
- }
- return true;
- case KEY_LEFT:
-
- if (event.KeyInput.Shift) {
- if (m_cursor_pos > 0) {
- if (m_mark_begin == m_mark_end)
- new_mark_begin = m_cursor_pos;
-
- new_mark_end = m_cursor_pos - 1;
- }
- } else {
- new_mark_begin = 0;
- new_mark_end = 0;
- }
-
- if (m_cursor_pos > 0)
- m_cursor_pos--;
- m_blink_start_time = porting::getTimeMs();
- break;
-
- case KEY_RIGHT:
- if (event.KeyInput.Shift) {
- if (Text.size() > (u32)m_cursor_pos) {
- if (m_mark_begin == m_mark_end)
- new_mark_begin = m_cursor_pos;
-
- new_mark_end = m_cursor_pos + 1;
- }
- } else {
- new_mark_begin = 0;
- new_mark_end = 0;
- }
-
- if (Text.size() > (u32)m_cursor_pos)
- m_cursor_pos++;
- m_blink_start_time = porting::getTimeMs();
- break;
- case KEY_UP:
- if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
- s32 lineNo = getLineFromPos(m_cursor_pos);
- s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin > m_mark_end ? m_mark_begin : m_mark_end);
- if (lineNo > 0) {
- s32 cp = m_cursor_pos - m_broken_text_positions[lineNo];
- if ((s32)m_broken_text[lineNo - 1].size() < cp)
- m_cursor_pos = m_broken_text_positions[lineNo - 1] + core::max_((u32)1, m_broken_text[lineNo - 1].size()) - 1;
- else
- m_cursor_pos = m_broken_text_positions[lineNo - 1] + cp;
- }
-
- if (event.KeyInput.Shift) {
- new_mark_begin = mb;
- new_mark_end = m_cursor_pos;
- } else {
- new_mark_begin = 0;
- new_mark_end = 0;
- }
- } else {
- return false;
- }
- break;
- case KEY_DOWN:
- if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
- s32 lineNo = getLineFromPos(m_cursor_pos);
- s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end);
- if (lineNo < (s32)m_broken_text.size() - 1)
- {
- s32 cp = m_cursor_pos - m_broken_text_positions[lineNo];
- if ((s32)m_broken_text[lineNo + 1].size() < cp)
- m_cursor_pos = m_broken_text_positions[lineNo + 1] + core::max_((u32)1, m_broken_text[lineNo + 1].size()) - 1;
- else
- m_cursor_pos = m_broken_text_positions[lineNo + 1] + cp;
- }
-
- if (event.KeyInput.Shift) {
- new_mark_begin = mb;
- new_mark_end = m_cursor_pos;
- } else {
- new_mark_begin = 0;
- new_mark_end = 0;
- }
-
- } else {
- return false;
- }
- break;
-
- case KEY_BACK:
- if (!isEnabled())
- break;
-
- if (Text.size()) {
- core::stringw s;
-
- if (m_mark_begin != m_mark_end) {
- // delete marked text
- const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
- const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
-
- s = Text.subString(0, realmbgn);
- s.append(Text.subString(realmend, Text.size() - realmend));
- Text = s;
-
- m_cursor_pos = realmbgn;
- } else {
- // delete text behind cursor
- if (m_cursor_pos > 0)
- s = Text.subString(0, m_cursor_pos - 1);
- else
- s = L"";
- s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
- Text = s;
- --m_cursor_pos;
- }
-
- if (m_cursor_pos < 0)
- m_cursor_pos = 0;
- m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime();
- new_mark_begin = 0;
- new_mark_end = 0;
- text_changed = true;
- }
- break;
- case KEY_DELETE:
- if (!isEnabled())
- break;
-
- if (Text.size() != 0) {
- core::stringw s;
-
- if (m_mark_begin != m_mark_end) {
- // delete marked text
- const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
- const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
-
- s = Text.subString(0, realmbgn);
- s.append(Text.subString(realmend, Text.size() - realmend));
- Text = s;
-
- m_cursor_pos = realmbgn;
- } else {
- // delete text before cursor
- s = Text.subString(0, m_cursor_pos);
- s.append(Text.subString(m_cursor_pos + 1, Text.size() - m_cursor_pos - 1));
- Text = s;
- }
-
- if (m_cursor_pos > (s32)Text.size())
- m_cursor_pos = (s32)Text.size();
-
- m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime();
- new_mark_begin = 0;
- new_mark_end = 0;
- text_changed = true;
- }
- break;
-
- case KEY_ESCAPE:
- case KEY_TAB:
- case KEY_SHIFT:
- case KEY_F1:
- case KEY_F2:
- case KEY_F3:
- case KEY_F4:
- case KEY_F5:
- case KEY_F6:
- case KEY_F7:
- case KEY_F8:
- case KEY_F9:
- case KEY_F10:
- case KEY_F11:
- case KEY_F12:
- case KEY_F13:
- case KEY_F14:
- case KEY_F15:
- case KEY_F16:
- case KEY_F17:
- case KEY_F18:
- case KEY_F19:
- case KEY_F20:
- case KEY_F21:
- case KEY_F22:
- case KEY_F23:
- case KEY_F24:
- // ignore these keys
- return false;
-
- default:
- inputChar(event.KeyInput.Char);
- return true;
- }
-
- // Set new text markers
- setTextMarkers(new_mark_begin, new_mark_end);
-
- // break the text if it has changed
- if (text_changed) {
- breakText();
- calculateScrollPos();
- sendGuiEvent(EGET_EDITBOX_CHANGED);
- }
- else
- {
- calculateScrollPos();
- }
-
- return true;
-}
-
-
-//! draws the element and its children
-void GUIEditBoxWithScrollBar::draw()
-{
- if (!IsVisible)
- return;
-
- const bool focus = Environment->hasFocus(this);
-
- IGUISkin* skin = Environment->getSkin();
- if (!skin)
- return;
-
- video::SColor default_bg_color;
- video::SColor bg_color;
-
- default_bg_color = m_writable ? skin->getColor(EGDC_WINDOW) : video::SColor(0);
- bg_color = m_bg_color_used ? m_bg_color : default_bg_color;
-
- if (!m_border && m_background) {
- skin->draw2DRectangle(this, bg_color, AbsoluteRect, &AbsoluteClippingRect);
- }
-
- // draw the border
-
- if (m_border) {
-
- if (m_writable) {
- skin->draw3DSunkenPane(this, bg_color, false, m_background,
- AbsoluteRect, &AbsoluteClippingRect);
- }
-
- calculateFrameRect();
- }
-
- core::rect<s32> local_clip_rect = m_frame_rect;
- local_clip_rect.clipAgainst(AbsoluteClippingRect);
-
- // draw the text
-
- IGUIFont* font = getActiveFont();
-
- s32 cursor_line = 0;
- s32 charcursorpos = 0;
-
- if (font) {
- if (m_last_break_font != font) {
- breakText();
- }
-
- // calculate cursor pos
-
- core::stringw *txt_line = &Text;
- s32 start_pos = 0;
-
- core::stringw s, s2;
-
- // get mark position
- const bool ml = (!m_passwordbox && (m_word_wrap || m_multiline));
- const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
- const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
- const s32 hline_start = ml ? getLineFromPos(realmbgn) : 0;
- const s32 hline_count = ml ? getLineFromPos(realmend) - hline_start + 1 : 1;
- const s32 line_count = ml ? m_broken_text.size() : 1;
-
- // Save the override color information.
- // Then, alter it if the edit box is disabled.
- const bool prevOver = m_override_color_enabled;
- const video::SColor prevColor = m_override_color;
-
- if (Text.size()) {
- if (!isEnabled() && !m_override_color_enabled) {
- m_override_color_enabled = true;
- m_override_color = skin->getColor(EGDC_GRAY_TEXT);
- }
-
- for (s32 i = 0; i < line_count; ++i) {
- setTextRect(i);
-
- // clipping test - don't draw anything outside the visible area
- core::rect<s32> c = local_clip_rect;
- c.clipAgainst(m_current_text_rect);
- if (!c.isValid())
- continue;
-
- // get current line
- if (m_passwordbox) {
- if (m_broken_text.size() != 1) {
- m_broken_text.clear();
- m_broken_text.push_back(core::stringw());
- }
- if (m_broken_text[0].size() != Text.size()){
- m_broken_text[0] = Text;
- for (u32 q = 0; q < Text.size(); ++q)
- {
- m_broken_text[0][q] = m_passwordchar;
- }
- }
- txt_line = &m_broken_text[0];
- start_pos = 0;
- } else {
- txt_line = ml ? &m_broken_text[i] : &Text;
- start_pos = ml ? m_broken_text_positions[i] : 0;
- }
-
-
- // draw normal text
- font->draw(txt_line->c_str(), m_current_text_rect,
- m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT),
- false, true, &local_clip_rect);
-
- // draw mark and marked text
- if (focus && m_mark_begin != m_mark_end && i >= hline_start && i < hline_start + hline_count) {
-
- s32 mbegin = 0, mend = 0;
- s32 lineStartPos = 0, lineEndPos = txt_line->size();
-
- if (i == hline_start) {
- // highlight start is on this line
- s = txt_line->subString(0, realmbgn - start_pos);
- mbegin = font->getDimension(s.c_str()).Width;
-
- // deal with kerning
- mbegin += font->getKerningWidth(
- &((*txt_line)[realmbgn - start_pos]),
- realmbgn - start_pos > 0 ? &((*txt_line)[realmbgn - start_pos - 1]) : 0);
-
- lineStartPos = realmbgn - start_pos;
- }
- if (i == hline_start + hline_count - 1) {
- // highlight end is on this line
- s2 = txt_line->subString(0, realmend - start_pos);
- mend = font->getDimension(s2.c_str()).Width;
- lineEndPos = (s32)s2.size();
- } else {
- mend = font->getDimension(txt_line->c_str()).Width;
- }
-
-
- m_current_text_rect.UpperLeftCorner.X += mbegin;
- m_current_text_rect.LowerRightCorner.X = m_current_text_rect.UpperLeftCorner.X + mend - mbegin;
-
-
- // draw mark
- skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), m_current_text_rect, &local_clip_rect);
-
- // draw marked text
- s = txt_line->subString(lineStartPos, lineEndPos - lineStartPos);
-
- if (s.size())
- font->draw(s.c_str(), m_current_text_rect,
- m_override_color_enabled ? m_override_color : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
- false, true, &local_clip_rect);
-
- }
- }
-
- // Return the override color information to its previous settings.
- m_override_color_enabled = prevOver;
- m_override_color = prevColor;
- }
-
- // draw cursor
- if (IsEnabled && m_writable) {
- if (m_word_wrap || m_multiline) {
- cursor_line = getLineFromPos(m_cursor_pos);
- txt_line = &m_broken_text[cursor_line];
- start_pos = m_broken_text_positions[cursor_line];
- }
- s = txt_line->subString(0, m_cursor_pos - start_pos);
- charcursorpos = font->getDimension(s.c_str()).Width +
- font->getKerningWidth(L"_", m_cursor_pos - start_pos > 0 ? &((*txt_line)[m_cursor_pos - start_pos - 1]) : 0);
-
- if (focus && (porting::getTimeMs() - m_blink_start_time) % 700 < 350) {
- setTextRect(cursor_line);
- m_current_text_rect.UpperLeftCorner.X += charcursorpos;
-
- font->draw(L"_", m_current_text_rect,
- m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT),
- false, true, &local_clip_rect);
- }
- }
- }
-
- // draw children
- IGUIElement::draw();
-}
-
-
-//! Sets the new caption of this element.
-void GUIEditBoxWithScrollBar::setText(const wchar_t* text)
-{
- Text = text;
- if (u32(m_cursor_pos) > Text.size())
- m_cursor_pos = Text.size();
- m_hscroll_pos = 0;
- breakText();
-}
-
-
-//! Enables or disables automatic scrolling with cursor position
-//! \param enable: If set to true, the text will move around with the cursor position
-void GUIEditBoxWithScrollBar::setAutoScroll(bool enable)
-{
- m_autoscroll = enable;
-}
-
-
-//! Checks to see if automatic scrolling is enabled
-//! \return true if automatic scrolling is enabled, false if not
-bool GUIEditBoxWithScrollBar::isAutoScrollEnabled() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return m_autoscroll;
-}
-
-
-//! Gets the area of the text in the edit box
-//! \return Returns the size in pixels of the text
-core::dimension2du GUIEditBoxWithScrollBar::getTextDimension()
-{
- core::rect<s32> ret;
-
- setTextRect(0);
- ret = m_current_text_rect;
-
- for (u32 i = 1; i < m_broken_text.size(); ++i) {
- setTextRect(i);
- ret.addInternalPoint(m_current_text_rect.UpperLeftCorner);
- ret.addInternalPoint(m_current_text_rect.LowerRightCorner);
- }
-
- return core::dimension2du(ret.getSize());
-}
-
-
-//! Sets the maximum amount of characters which may be entered in the box.
-//! \param max: Maximum amount of characters. If 0, the character amount is
-//! infinity.
-void GUIEditBoxWithScrollBar::setMax(u32 max)
-{
- m_max = max;
-
- if (Text.size() > m_max && m_max != 0)
- Text = Text.subString(0, m_max);
-}
-
-
-//! Returns maximum amount of characters, previously set by setMax();
-u32 GUIEditBoxWithScrollBar::getMax() const
-{
- return m_max;
-}
-
-
-bool GUIEditBoxWithScrollBar::processMouse(const SEvent& event)
-{
- switch (event.MouseInput.Event)
- {
- case irr::EMIE_LMOUSE_LEFT_UP:
- if (Environment->hasFocus(this)) {
- m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
- if (m_mouse_marking) {
- setTextMarkers(m_mark_begin, m_cursor_pos);
- }
- m_mouse_marking = false;
- calculateScrollPos();
- return true;
- }
- break;
- case irr::EMIE_MOUSE_MOVED:
- {
- if (m_mouse_marking) {
- m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
- setTextMarkers(m_mark_begin, m_cursor_pos);
- calculateScrollPos();
- return true;
- }
- }
- break;
- case EMIE_LMOUSE_PRESSED_DOWN:
-
- if (!Environment->hasFocus(this)) {
- m_blink_start_time = porting::getTimeMs();
- m_mouse_marking = true;
- m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
- setTextMarkers(m_cursor_pos, m_cursor_pos);
- calculateScrollPos();
- return true;
- } else {
- if (!AbsoluteClippingRect.isPointInside(
- core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) {
- return false;
- } else {
- // move cursor
- m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
-
- s32 newMarkBegin = m_mark_begin;
- if (!m_mouse_marking)
- newMarkBegin = m_cursor_pos;
-
- m_mouse_marking = true;
- setTextMarkers(newMarkBegin, m_cursor_pos);
- calculateScrollPos();
- return true;
- }
- }
- default:
- break;
- }
-
- return false;
-}
-
-
-s32 GUIEditBoxWithScrollBar::getCursorPos(s32 x, s32 y)
-{
- IGUIFont* font = getActiveFont();
-
- const u32 line_count = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1;
-
- core::stringw *txt_line = 0;
- s32 start_pos = 0;
- x += 3;
-
- for (u32 i = 0; i < line_count; ++i) {
- setTextRect(i);
- if (i == 0 && y < m_current_text_rect.UpperLeftCorner.Y)
- y = m_current_text_rect.UpperLeftCorner.Y;
- if (i == line_count - 1 && y > m_current_text_rect.LowerRightCorner.Y)
- y = m_current_text_rect.LowerRightCorner.Y;
-
- // is it inside this region?
- if (y >= m_current_text_rect.UpperLeftCorner.Y && y <= m_current_text_rect.LowerRightCorner.Y) {
- // we've found the clicked line
- txt_line = (m_word_wrap || m_multiline) ? &m_broken_text[i] : &Text;
- start_pos = (m_word_wrap || m_multiline) ? m_broken_text_positions[i] : 0;
- break;
- }
- }
-
- if (x < m_current_text_rect.UpperLeftCorner.X)
- x = m_current_text_rect.UpperLeftCorner.X;
-
- if (!txt_line)
- return 0;
-
- s32 idx = font->getCharacterFromPos(txt_line->c_str(), x - m_current_text_rect.UpperLeftCorner.X);
-
- // click was on or left of the line
- if (idx != -1)
- return idx + start_pos;
-
- // click was off the right edge of the line, go to end.
- return txt_line->size() + start_pos;
-}
-
-
-//! Breaks the single text line.
-void GUIEditBoxWithScrollBar::breakText()
-{
- if ((!m_word_wrap && !m_multiline))
- return;
-
- m_broken_text.clear(); // need to reallocate :/
- m_broken_text_positions.clear();
-
- IGUIFont* font = getActiveFont();
- if (!font)
- return;
-
- m_last_break_font = font;
-
- core::stringw line;
- core::stringw word;
- core::stringw whitespace;
- s32 last_line_start = 0;
- s32 size = Text.size();
- s32 length = 0;
- s32 el_width = RelativeRect.getWidth() - 6;
- wchar_t c;
-
- for (s32 i = 0; i < size; ++i) {
- c = Text[i];
- bool line_break = false;
-
- if (c == L'\r') { // Mac or Windows breaks
-
- line_break = true;
- c = 0;
- if (Text[i + 1] == L'\n') { // Windows breaks
- // TODO: I (Michael) think that we shouldn't change the text given by the user for whatever reason.
- // Instead rework the cursor positioning to be able to handle this (but not in stable release
- // branch as users might already expect this behavior).
- Text.erase(i + 1);
- --size;
- if (m_cursor_pos > i)
- --m_cursor_pos;
- }
- } else if (c == L'\n') { // Unix breaks
- line_break = true;
- c = 0;
- }
-
- // don't break if we're not a multi-line edit box
- if (!m_multiline)
- line_break = false;
-
- if (c == L' ' || c == 0 || i == (size - 1)) {
- // here comes the next whitespace, look if
- // we can break the last word to the next line
- // We also break whitespace, otherwise cursor would vanish beside the right border.
- s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
- s32 worldlgth = font->getDimension(word.c_str()).Width;
-
- if (m_word_wrap && length + worldlgth + whitelgth > el_width && line.size() > 0) {
- // break to next line
- length = worldlgth;
- m_broken_text.push_back(line);
- m_broken_text_positions.push_back(last_line_start);
- last_line_start = i - (s32)word.size();
- line = word;
- } else {
- // add word to line
- line += whitespace;
- line += word;
- length += whitelgth + worldlgth;
- }
-
- word = L"";
- whitespace = L"";
-
-
- if (c)
- whitespace += c;
-
- // compute line break
- if (line_break) {
- line += whitespace;
- line += word;
- m_broken_text.push_back(line);
- m_broken_text_positions.push_back(last_line_start);
- last_line_start = i + 1;
- line = L"";
- word = L"";
- whitespace = L"";
- length = 0;
- }
- } else {
- // yippee this is a word..
- word += c;
- }
- }
-
- line += whitespace;
- line += word;
- m_broken_text.push_back(line);
- m_broken_text_positions.push_back(last_line_start);
-}
-
-// TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom)
-// but HAlign according to line-width (pixels) and not by row.
-// Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling.
-// But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling).
-void GUIEditBoxWithScrollBar::setTextRect(s32 line)
-{
- if (line < 0)
- return;
-
- IGUIFont* font = getActiveFont();
- if (!font)
- return;
-
- core::dimension2du d;
-
- // get text dimension
- const u32 line_count = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1;
- if (m_word_wrap || m_multiline) {
- d = font->getDimension(m_broken_text[line].c_str());
- } else {
- d = font->getDimension(Text.c_str());
- d.Height = AbsoluteRect.getHeight();
- }
- d.Height += font->getKerningHeight();
-
- // justification
- switch (m_halign) {
- case EGUIA_CENTER:
- // align to h centre
- m_current_text_rect.UpperLeftCorner.X = (m_frame_rect.getWidth() / 2) - (d.Width / 2);
- m_current_text_rect.LowerRightCorner.X = (m_frame_rect.getWidth() / 2) + (d.Width / 2);
- break;
- case EGUIA_LOWERRIGHT:
- // align to right edge
- m_current_text_rect.UpperLeftCorner.X = m_frame_rect.getWidth() - d.Width;
- m_current_text_rect.LowerRightCorner.X = m_frame_rect.getWidth();
- break;
- default:
- // align to left edge
- m_current_text_rect.UpperLeftCorner.X = 0;
- m_current_text_rect.LowerRightCorner.X = d.Width;
-
- }
-
- switch (m_valign) {
- case EGUIA_CENTER:
- // align to v centre
- m_current_text_rect.UpperLeftCorner.Y =
- (m_frame_rect.getHeight() / 2) - (line_count*d.Height) / 2 + d.Height*line;
- break;
- case EGUIA_LOWERRIGHT:
- // align to bottom edge
- m_current_text_rect.UpperLeftCorner.Y =
- m_frame_rect.getHeight() - line_count*d.Height + d.Height*line;
- break;
- default:
- // align to top edge
- m_current_text_rect.UpperLeftCorner.Y = d.Height*line;
- break;
- }
-
- m_current_text_rect.UpperLeftCorner.X -= m_hscroll_pos;
- m_current_text_rect.LowerRightCorner.X -= m_hscroll_pos;
- m_current_text_rect.UpperLeftCorner.Y -= m_vscroll_pos;
- m_current_text_rect.LowerRightCorner.Y = m_current_text_rect.UpperLeftCorner.Y + d.Height;
-
- m_current_text_rect += m_frame_rect.UpperLeftCorner;
-}
-
-
-s32 GUIEditBoxWithScrollBar::getLineFromPos(s32 pos)
-{
- if (!m_word_wrap && !m_multiline)
- return 0;
-
- s32 i = 0;
- while (i < (s32)m_broken_text_positions.size()) {
- if (m_broken_text_positions[i] > pos)
- return i - 1;
- ++i;
- }
- return (s32)m_broken_text_positions.size() - 1;
-}
-
-
-void GUIEditBoxWithScrollBar::inputChar(wchar_t c)
-{
- if (!isEnabled())
- return;
-
- if (c != 0) {
- if (Text.size() < m_max || m_max == 0) {
- core::stringw s;
-
- if (m_mark_begin != m_mark_end) {
- // replace marked text
- const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
- const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
-
- s = Text.subString(0, realmbgn);
- s.append(c);
- s.append(Text.subString(realmend, Text.size() - realmend));
- Text = s;
- m_cursor_pos = realmbgn + 1;
- } else {
- // add new character
- s = Text.subString(0, m_cursor_pos);
- s.append(c);
- s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
- Text = s;
- ++m_cursor_pos;
- }
-
- m_blink_start_time = porting::getTimeMs();
- setTextMarkers(0, 0);
- }
- }
- breakText();
- calculateScrollPos();
- sendGuiEvent(EGET_EDITBOX_CHANGED);
-}
-
-// calculate autoscroll
-void GUIEditBoxWithScrollBar::calculateScrollPos()
-{
- if (!m_autoscroll)
- return;
-
- IGUISkin* skin = Environment->getSkin();
- if (!skin)
- return;
- IGUIFont* font = m_override_font ? m_override_font : skin->getFont();
- if (!font)
- return;
-
- s32 curs_line = getLineFromPos(m_cursor_pos);
- if (curs_line < 0)
- return;
- setTextRect(curs_line);
- const bool has_broken_text = m_multiline || m_word_wrap;
-
- // Check horizonal scrolling
- // NOTE: Calculations different to vertical scrolling because setTextRect interprets VAlign relative to line but HAlign not relative to row
- {
- // get cursor position
- IGUIFont* font = getActiveFont();
- if (!font)
- return;
-
- // get cursor area
- irr::u32 cursor_width = font->getDimension(L"_").Width;
- core::stringw *txt_line = has_broken_text ? &m_broken_text[curs_line] : &Text;
- s32 cpos = has_broken_text ? m_cursor_pos - m_broken_text_positions[curs_line] : m_cursor_pos; // column
- s32 cstart = font->getDimension(txt_line->subString(0, cpos).c_str()).Width; // pixels from text-start
- s32 cend = cstart + cursor_width;
- s32 txt_width = font->getDimension(txt_line->c_str()).Width;
-
- if (txt_width < m_frame_rect.getWidth()) {
- // TODO: Needs a clean left and right gap removal depending on HAlign, similar to vertical scrolling tests for top/bottom.
- // This check just fixes the case where it was most noticable (text smaller than clipping area).
-
- m_hscroll_pos = 0;
- setTextRect(curs_line);
- }
-
- if (m_current_text_rect.UpperLeftCorner.X + cstart < m_frame_rect.UpperLeftCorner.X) {
- // cursor to the left of the clipping area
- m_hscroll_pos -= m_frame_rect.UpperLeftCorner.X - (m_current_text_rect.UpperLeftCorner.X + cstart);
- setTextRect(curs_line);
-
- // TODO: should show more characters to the left when we're scrolling left
- // and the cursor reaches the border.
- } else if (m_current_text_rect.UpperLeftCorner.X + cend > m_frame_rect.LowerRightCorner.X) {
- // cursor to the right of the clipping area
- m_hscroll_pos += (m_current_text_rect.UpperLeftCorner.X + cend) - m_frame_rect.LowerRightCorner.X;
- setTextRect(curs_line);
- }
- }
-
- // calculate vertical scrolling
- if (has_broken_text) {
- irr::u32 line_height = font->getDimension(L"A").Height + font->getKerningHeight();
- // only up to 1 line fits?
- if (line_height >= (irr::u32)m_frame_rect.getHeight()) {
- m_vscroll_pos = 0;
- setTextRect(curs_line);
- s32 unscrolledPos = m_current_text_rect.UpperLeftCorner.Y;
- s32 pivot = m_frame_rect.UpperLeftCorner.Y;
- switch (m_valign) {
- case EGUIA_CENTER:
- pivot += m_frame_rect.getHeight() / 2;
- unscrolledPos += line_height / 2;
- break;
- case EGUIA_LOWERRIGHT:
- pivot += m_frame_rect.getHeight();
- unscrolledPos += line_height;
- break;
- default:
- break;
- }
- m_vscroll_pos = unscrolledPos - pivot;
- setTextRect(curs_line);
- } else {
- // First 2 checks are necessary when people delete lines
- setTextRect(0);
- if (m_current_text_rect.UpperLeftCorner.Y > m_frame_rect.UpperLeftCorner.Y && m_valign != EGUIA_LOWERRIGHT) {
- // first line is leaving a gap on top
- m_vscroll_pos = 0;
- } else if (m_valign != EGUIA_UPPERLEFT) {
- u32 lastLine = m_broken_text_positions.empty() ? 0 : m_broken_text_positions.size() - 1;
- setTextRect(lastLine);
- if (m_current_text_rect.LowerRightCorner.Y < m_frame_rect.LowerRightCorner.Y)
- {
- // last line is leaving a gap on bottom
- m_vscroll_pos -= m_frame_rect.LowerRightCorner.Y - m_current_text_rect.LowerRightCorner.Y;
- }
- }
-
- setTextRect(curs_line);
- if (m_current_text_rect.UpperLeftCorner.Y < m_frame_rect.UpperLeftCorner.Y) {
- // text above valid area
- m_vscroll_pos -= m_frame_rect.UpperLeftCorner.Y - m_current_text_rect.UpperLeftCorner.Y;
- setTextRect(curs_line);
- } else if (m_current_text_rect.LowerRightCorner.Y > m_frame_rect.LowerRightCorner.Y){
- // text below valid area
- m_vscroll_pos += m_current_text_rect.LowerRightCorner.Y - m_frame_rect.LowerRightCorner.Y;
- setTextRect(curs_line);
- }
- }
- }
-
- if (m_vscrollbar) {
- m_vscrollbar->setPos(m_vscroll_pos);
- }
-}
-
-void GUIEditBoxWithScrollBar::calculateFrameRect()
-{
- m_frame_rect = AbsoluteRect;
-
-
- IGUISkin *skin = 0;
- if (Environment)
- skin = Environment->getSkin();
- if (m_border && skin) {
- m_frame_rect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X) + 1;
- m_frame_rect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1;
- m_frame_rect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X) + 1;
- m_frame_rect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1;
- }
-
- updateVScrollBar();
-}
-
-//! set text markers
-void GUIEditBoxWithScrollBar::setTextMarkers(s32 begin, s32 end)
-{
- if (begin != m_mark_begin || end != m_mark_end) {
- m_mark_begin = begin;
- m_mark_end = end;
- sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
- }
-}
-
-//! send some gui event to parent
-void GUIEditBoxWithScrollBar::sendGuiEvent(EGUI_EVENT_TYPE type)
-{
- if (Parent) {
- SEvent e;
- e.EventType = EET_GUI_EVENT;
- e.GUIEvent.Caller = this;
- e.GUIEvent.Element = 0;
- e.GUIEvent.EventType = type;
-
- Parent->OnEvent(e);
- }
-}
-
-//! create a vertical scroll bar
-void GUIEditBoxWithScrollBar::createVScrollBar()
-{
- IGUISkin *skin = 0;
- if (Environment)
- skin = Environment->getSkin();
-
- m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16;
-
- irr::core::rect<s32> scrollbarrect = m_frame_rect;
- scrollbarrect.UpperLeftCorner.X += m_frame_rect.getWidth() - m_scrollbar_width;
- m_vscrollbar = Environment->addScrollBar(false, scrollbarrect, getParent(), getID());
- m_vscrollbar->setVisible(false);
- m_vscrollbar->setSmallStep(1);
- m_vscrollbar->setLargeStep(1);
-}
-
-void GUIEditBoxWithScrollBar::updateVScrollBar()
-{
- if (!m_vscrollbar) {
- return;
- }
-
- // OnScrollBarChanged(...)
- if (m_vscrollbar->getPos() != m_vscroll_pos) {
- s32 deltaScrollY = m_vscrollbar->getPos() - m_vscroll_pos;
- m_current_text_rect.UpperLeftCorner.Y -= deltaScrollY;
- m_current_text_rect.LowerRightCorner.Y -= deltaScrollY;
-
- s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
- if (scrollymax != m_vscrollbar->getMax()) {
- // manage a newline or a deleted line
- m_vscrollbar->setMax(scrollymax);
- calculateScrollPos();
- } else {
- // manage a newline or a deleted line
- m_vscroll_pos = m_vscrollbar->getPos();
- }
- }
-
- // check if a vertical scrollbar is needed ?
- if (getTextDimension().Height > (u32) m_frame_rect.getHeight()) {
- m_frame_rect.LowerRightCorner.X -= m_scrollbar_width;
-
- s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
- if (scrollymax != m_vscrollbar->getMax()) {
- m_vscrollbar->setMax(scrollymax);
- }
-
- if (!m_vscrollbar->isVisible()) {
- m_vscrollbar->setVisible(true);
- }
- } else {
- if (m_vscrollbar->isVisible())
- {
- m_vscrollbar->setVisible(false);
- m_vscroll_pos = 0;
- m_vscrollbar->setPos(0);
- m_vscrollbar->setMax(1);
- }
- }
-
-
-}
-
-//! set true if this editbox is writable
-void GUIEditBoxWithScrollBar::setWritable(bool writable)
-{
- m_writable = writable;
-}
-
-//! Change the background color
-void GUIEditBoxWithScrollBar::setBackgroundColor(const video::SColor &bg_color)
-{
- m_bg_color = bg_color;
- m_bg_color_used = true;
-}
-
-//! Writes attributes of the element.
-void GUIEditBoxWithScrollBar::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options = 0) const
-{
- // IGUIEditBox::serializeAttributes(out,options);
-
- out->addBool("Border", m_border);
- out->addBool("Background", m_background);
- out->addBool("OverrideColorEnabled", m_override_color_enabled);
- out->addColor("OverrideColor", m_override_color);
- // out->addFont("OverrideFont", OverrideFont);
- out->addInt("MaxChars", m_max);
- out->addBool("WordWrap", m_word_wrap);
- out->addBool("MultiLine", m_multiline);
- out->addBool("AutoScroll", m_autoscroll);
- out->addBool("PasswordBox", m_passwordbox);
- core::stringw ch = L" ";
- ch[0] = m_passwordchar;
- out->addString("PasswordChar", ch.c_str());
- out->addEnum("HTextAlign", m_halign, GUIAlignmentNames);
- out->addEnum("VTextAlign", m_valign, GUIAlignmentNames);
- out->addBool("Writable", m_writable);
-
- IGUIEditBox::serializeAttributes(out, options);
-}
-
-
-//! Reads attributes of the element
-void GUIEditBoxWithScrollBar::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options = 0)
-{
- IGUIEditBox::deserializeAttributes(in, options);
-
- setDrawBorder(in->getAttributeAsBool("Border"));
- setDrawBackground(in->getAttributeAsBool("Background"));
- setOverrideColor(in->getAttributeAsColor("OverrideColor"));
- enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
- setMax(in->getAttributeAsInt("MaxChars"));
- setWordWrap(in->getAttributeAsBool("WordWrap"));
- setMultiLine(in->getAttributeAsBool("MultiLine"));
- setAutoScroll(in->getAttributeAsBool("AutoScroll"));
- core::stringw ch = in->getAttributeAsStringW("PasswordChar");
-
- if (!ch.size())
- setPasswordBox(in->getAttributeAsBool("PasswordBox"));
- else
- setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]);
-
- setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
- (EGUI_ALIGNMENT)in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
-
- // setOverrideFont(in->getAttributeAsFont("OverrideFont"));
- setWritable(in->getAttributeAsBool("Writable"));
-}
+++ /dev/null
-// Copyright (C) 2002-2012 Nikolaus Gebhardt, Modified by Mustapha Tachouct
-// This file is part of the "Irrlicht Engine".
-// For conditions of distribution and use, see copyright notice in irrlicht.h
-
-#ifndef GUIEDITBOXWITHSCROLLBAR_HEADER
-#define GUIEDITBOXWITHSCROLLBAR_HEADER
-
-#include "IGUIEditBox.h"
-#include "IOSOperator.h"
-#include "IGUIScrollBar.h"
-#include <vector>
-
-using namespace irr;
-using namespace irr::gui;
-
-class GUIEditBoxWithScrollBar : public IGUIEditBox
-{
-public:
-
- //! constructor
- GUIEditBoxWithScrollBar(const wchar_t* text, bool border, IGUIEnvironment* environment,
- IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
- bool writable = true, bool has_vscrollbar = true);
-
- //! destructor
- virtual ~GUIEditBoxWithScrollBar();
-
- //! Sets another skin independent font.
- virtual void setOverrideFont(IGUIFont* font = 0);
-
- //! Gets the override font (if any)
- /** \return The override font (may be 0) */
- virtual IGUIFont* getOverrideFont() const;
-
- //! Get the font which is used right now for drawing
- /** Currently this is the override font when one is set and the
- font of the active skin otherwise */
- virtual IGUIFont* getActiveFont() const;
-
- //! Sets another color for the text.
- virtual void setOverrideColor(video::SColor color);
-
- //! Gets the override color
- virtual video::SColor getOverrideColor() const;
-
- //! Sets if the text should use the overide color or the
- //! color in the gui skin.
- virtual void enableOverrideColor(bool enable);
-
- //! Checks if an override color is enabled
- /** \return true if the override color is enabled, false otherwise */
- virtual bool isOverrideColorEnabled(void) const;
-
- //! Sets whether to draw the background
- virtual void setDrawBackground(bool draw);
-
- //! Turns the border on or off
- virtual void setDrawBorder(bool border);
-
- //! Enables or disables word wrap for using the edit box as multiline text editor.
- virtual void setWordWrap(bool enable);
-
- //! Checks if word wrap is enabled
- //! \return true if word wrap is enabled, false otherwise
- virtual bool isWordWrapEnabled() const;
-
- //! Enables or disables newlines.
- /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired,
- instead a newline character will be inserted. */
- virtual void setMultiLine(bool enable);
-
- //! Checks if multi line editing is enabled
- //! \return true if mult-line is enabled, false otherwise
- virtual bool isMultiLineEnabled() const;
-
- //! Enables or disables automatic scrolling with cursor position
- //! \param enable: If set to true, the text will move around with the cursor position
- virtual void setAutoScroll(bool enable);
-
- //! Checks to see if automatic scrolling is enabled
- //! \return true if automatic scrolling is enabled, false if not
- virtual bool isAutoScrollEnabled() const;
-
- //! Gets the size area of the text in the edit box
- //! \return Returns the size in pixels of the text
- virtual core::dimension2du getTextDimension();
-
- //! Sets text justification
- virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical);
-
- //! called if an event happened.
- virtual bool OnEvent(const SEvent& event);
-
- //! draws the element and its children
- virtual void draw();
-
- //! Sets the new caption of this element.
- virtual void setText(const wchar_t* text);
-
- //! Sets the maximum amount of characters which may be entered in the box.
- //! \param max: Maximum amount of characters. If 0, the character amount is
- //! infinity.
- virtual void setMax(u32 max);
-
- //! Returns maximum amount of characters, previously set by setMax();
- virtual u32 getMax() const;
-
- //! Sets whether the edit box is a password box. Setting this to true will
- /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x
- \param passwordBox: true to enable password, false to disable
- \param passwordChar: the character that is displayed instead of letters */
- virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*');
-
- //! Returns true if the edit box is currently a password box.
- virtual bool isPasswordBox() const;
-
- //! Updates the absolute position, splits text if required
- virtual void updateAbsolutePosition();
-
- virtual void setWritable(bool writable);
-
- //! Change the background color
- virtual void setBackgroundColor(const video::SColor &bg_color);
-
- //! Writes attributes of the element.
- virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;
-
- //! Reads attributes of the element
- virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
-
-protected:
- //! Breaks the single text line.
- void breakText();
- //! sets the area of the given line
- void setTextRect(s32 line);
- //! returns the line number that the cursor is on
- s32 getLineFromPos(s32 pos);
- //! adds a letter to the edit box
- void inputChar(wchar_t c);
- //! calculates the current scroll position
- void calculateScrollPos();
- //! calculated the FrameRect
- void calculateFrameRect();
- //! send some gui event to parent
- void sendGuiEvent(EGUI_EVENT_TYPE type);
- //! set text markers
- void setTextMarkers(s32 begin, s32 end);
- //! create a Vertical ScrollBar
- void createVScrollBar();
- //! update the vertical scrollBar (visibilty & position)
- void updateVScrollBar();
-
- bool processKey(const SEvent& event);
- bool processMouse(const SEvent& event);
- s32 getCursorPos(s32 x, s32 y);
-
- bool m_mouse_marking;
- bool m_border;
- bool m_background;
- bool m_override_color_enabled;
- s32 m_mark_begin;
- s32 m_mark_end;
-
- video::SColor m_override_color;
- gui::IGUIFont *m_override_font, *m_last_break_font;
- IOSOperator* m_operator;
-
- u32 m_blink_start_time;
- s32 m_cursor_pos;
- s32 m_hscroll_pos, m_vscroll_pos; // scroll position in characters
- u32 m_max;
-
- bool m_word_wrap, m_multiline, m_autoscroll, m_passwordbox;
- wchar_t m_passwordchar;
- EGUI_ALIGNMENT m_halign, m_valign;
-
- std::vector<core::stringw> m_broken_text;
- std::vector<s32> m_broken_text_positions;
-
- core::rect<s32> m_current_text_rect, m_frame_rect; // temporary values
-
- u32 m_scrollbar_width;
- IGUIScrollBar *m_vscrollbar;
- bool m_writable;
-
- bool m_bg_color_used;
- video::SColor m_bg_color;
-};
-
-
-#endif // GUIEDITBOXWITHSCROLLBAR_HEADER
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 sapier
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "guiEngine.h"
-
-#include <IGUIStaticText.h>
-#include <ICameraSceneNode.h>
-#include "client/renderingengine.h"
-#include "scripting_mainmenu.h"
-#include "util/numeric.h"
-#include "config.h"
-#include "version.h"
-#include "porting.h"
-#include "filesys.h"
-#include "settings.h"
-#include "guiMainMenu.h"
-#include "sound.h"
-#include "sound_openal.h"
-#include "clouds.h"
-#include "httpfetch.h"
-#include "log.h"
-#include "fontengine.h"
-#include "guiscalingfilter.h"
-#include "irrlicht_changes/static_text.h"
-
-#ifdef __ANDROID__
-#include "client/tile.h"
-#include <GLES/gl.h>
-#endif
-
-
-/******************************************************************************/
-void TextDestGuiEngine::gotText(const StringMap &fields)
-{
- m_engine->getScriptIface()->handleMainMenuButtons(fields);
-}
-
-/******************************************************************************/
-void TextDestGuiEngine::gotText(const std::wstring &text)
-{
- m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text));
-}
-
-/******************************************************************************/
-MenuTextureSource::~MenuTextureSource()
-{
- for (const std::string &texture_to_delete : m_to_delete) {
- const char *tname = texture_to_delete.c_str();
- video::ITexture *texture = m_driver->getTexture(tname);
- m_driver->removeTexture(texture);
- }
-}
-
-/******************************************************************************/
-video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
-{
- if(id)
- *id = 0;
- if(name.empty())
- return NULL;
- m_to_delete.insert(name);
-
-#ifdef __ANDROID__
- video::IImage *image = m_driver->createImageFromFile(name.c_str());
- if (image) {
- image = Align2Npot2(image, m_driver);
- video::ITexture* retval = m_driver->addTexture(name.c_str(), image);
- image->drop();
- return retval;
- }
-#endif
- return m_driver->getTexture(name.c_str());
-}
-
-/******************************************************************************/
-/** MenuMusicFetcher */
-/******************************************************************************/
-void MenuMusicFetcher::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");
- int i;
- for(i=0; i<10; i++)
- dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg");
- base = porting::path_user + DIR_DELIM + "sounds";
- dst_paths.insert(base + DIR_DELIM + name + ".ogg");
- for(i=0; i<10; i++)
- dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg");
-}
-
-/******************************************************************************/
-/** GUIEngine */
-/******************************************************************************/
-GUIEngine::GUIEngine(JoystickController *joystick,
- gui::IGUIElement *parent,
- IMenuManager *menumgr,
- MainMenuData *data,
- bool &kill) :
- m_parent(parent),
- m_menumanager(menumgr),
- m_smgr(RenderingEngine::get_scene_manager()),
- m_data(data),
- m_kill(kill)
-{
- //initialize texture pointers
- for (image_definition &texture : m_textures) {
- texture.texture = NULL;
- }
- // is deleted by guiformspec!
- m_buttonhandler = new TextDestGuiEngine(this);
-
- //create texture source
- m_texture_source = new MenuTextureSource(RenderingEngine::get_video_driver());
-
- //create soundmanager
- MenuMusicFetcher soundfetcher;
-#if USE_SOUND
- m_sound_manager = createOpenALSoundManager(&soundfetcher);
-#endif
- if(!m_sound_manager)
- m_sound_manager = &dummySoundManager;
-
- //create topleft header
- m_toplefttext = L"";
-
- core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
- g_fontengine->getTextHeight());
- rect += v2s32(4, 0);
-
- m_irr_toplefttext =
- addStaticText(RenderingEngine::get_gui_env(), m_toplefttext,
- rect, false, true, 0, -1);
-
- //create formspecsource
- m_formspecgui = new FormspecFormSource("");
-
- /* Create menu */
- m_menu = new GUIFormSpecMenu(joystick,
- m_parent,
- -1,
- m_menumanager,
- NULL /* &client */,
- m_texture_source,
- m_formspecgui,
- m_buttonhandler,
- false);
-
- m_menu->allowClose(false);
- m_menu->lockSize(true,v2u32(800,600));
-
- // Initialize scripting
-
- infostream << "GUIEngine: Initializing Lua" << std::endl;
-
- m_script = new MainMenuScripting(this);
-
- try {
- m_script->setMainMenuData(&m_data->script_data);
- m_data->script_data.errormessage = "";
-
- if (!loadMainMenuScript()) {
- errorstream << "No future without main menu!" << std::endl;
- abort();
- }
-
- run();
- } catch (LuaError &e) {
- errorstream << "Main menu error: " << e.what() << std::endl;
- m_data->script_data.errormessage = e.what();
- }
-
- m_menu->quitMenu();
- m_menu->drop();
- m_menu = NULL;
-}
-
-/******************************************************************************/
-bool GUIEngine::loadMainMenuScript()
-{
- // Set main menu path (for core.get_mainmenu_path())
- m_scriptdir = g_settings->get("main_menu_path");
- if (m_scriptdir.empty()) {
- m_scriptdir = porting::path_share + DIR_DELIM + "builtin" + DIR_DELIM + "mainmenu";
- }
-
- // Load builtin (which will load the main menu script)
- std::string script = porting::path_share + DIR_DELIM "builtin" + DIR_DELIM "init.lua";
- try {
- m_script->loadScript(script);
- // Menu script loaded
- return true;
- } catch (const ModError &e) {
- errorstream << "GUIEngine: execution of menu script failed: "
- << e.what() << std::endl;
- }
-
- return false;
-}
-
-/******************************************************************************/
-void GUIEngine::run()
-{
- // Always create clouds because they may or may not be
- // needed based on the game selected
- video::IVideoDriver *driver = RenderingEngine::get_video_driver();
-
- cloudInit();
-
- unsigned int text_height = g_fontengine->getTextHeight();
-
- irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
- g_settings->getU16("screen_h"));
-
- while (RenderingEngine::run() && (!m_startgame) && (!m_kill)) {
-
- const irr::core::dimension2d<u32> ¤t_screen_size =
- RenderingEngine::get_video_driver()->getScreenSize();
- // Verify if window size has changed and save it if it's the case
- // Ensure evaluating settings->getBool after verifying screensize
- // First condition is cheaper
- if (previous_screen_size != current_screen_size &&
- current_screen_size != irr::core::dimension2d<u32>(0,0) &&
- g_settings->getBool("autosave_screensize")) {
- g_settings->setU16("screen_w", current_screen_size.Width);
- g_settings->setU16("screen_h", current_screen_size.Height);
- previous_screen_size = current_screen_size;
- }
-
- //check if we need to update the "upper left corner"-text
- if (text_height != g_fontengine->getTextHeight()) {
- updateTopLeftTextSize();
- text_height = g_fontengine->getTextHeight();
- }
-
- driver->beginScene(true, true, video::SColor(255,140,186,250));
-
- if (m_clouds_enabled)
- {
- cloudPreProcess();
- drawOverlay(driver);
- }
- else
- drawBackground(driver);
-
- drawHeader(driver);
- drawFooter(driver);
-
- RenderingEngine::get_gui_env()->drawAll();
-
- driver->endScene();
-
- if (m_clouds_enabled)
- cloudPostProcess();
- else
- sleep_ms(25);
-
- m_script->step();
-
-#ifdef __ANDROID__
- m_menu->getAndroidUIInput();
-#endif
- }
-}
-
-/******************************************************************************/
-GUIEngine::~GUIEngine()
-{
- if (m_sound_manager != &dummySoundManager){
- delete m_sound_manager;
- m_sound_manager = NULL;
- }
-
- infostream<<"GUIEngine: Deinitializing scripting"<<std::endl;
- delete m_script;
-
- m_irr_toplefttext->setText(L"");
-
- //clean up texture pointers
- for (image_definition &texture : m_textures) {
- if (texture.texture)
- RenderingEngine::get_video_driver()->removeTexture(texture.texture);
- }
-
- delete m_texture_source;
-
- if (m_cloud.clouds)
- m_cloud.clouds->drop();
-}
-
-/******************************************************************************/
-void GUIEngine::cloudInit()
-{
- m_cloud.clouds = new Clouds(m_smgr, -1, rand());
- m_cloud.clouds->setHeight(100.0f);
- m_cloud.clouds->update(v3f(0, 0, 0), video::SColor(255,200,200,255));
-
- m_cloud.camera = m_smgr->addCameraSceneNode(0,
- v3f(0,0,0), v3f(0, 60, 100));
- m_cloud.camera->setFarValue(10000);
-
- m_cloud.lasttime = RenderingEngine::get_timer_time();
-}
-
-/******************************************************************************/
-void GUIEngine::cloudPreProcess()
-{
- u32 time = RenderingEngine::get_timer_time();
-
- if(time > m_cloud.lasttime)
- m_cloud.dtime = (time - m_cloud.lasttime) / 1000.0;
- else
- m_cloud.dtime = 0;
-
- m_cloud.lasttime = time;
-
- m_cloud.clouds->step(m_cloud.dtime*3);
- m_cloud.clouds->render();
- m_smgr->drawAll();
-}
-
-/******************************************************************************/
-void GUIEngine::cloudPostProcess()
-{
- float fps_max = g_settings->getFloat("pause_fps_max");
- // Time of frame without fps limit
- u32 busytime_u32;
-
- // not using getRealTime is necessary for wine
- u32 time = RenderingEngine::get_timer_time();
- if(time > m_cloud.lasttime)
- busytime_u32 = time - m_cloud.lasttime;
- else
- busytime_u32 = 0;
-
- // FPS limiter
- u32 frametime_min = 1000./fps_max;
-
- if (busytime_u32 < frametime_min) {
- u32 sleeptime = frametime_min - busytime_u32;
- RenderingEngine::get_raw_device()->sleep(sleeptime);
- }
-}
-
-/******************************************************************************/
-void GUIEngine::drawBackground(video::IVideoDriver *driver)
-{
- v2u32 screensize = driver->getScreenSize();
-
- video::ITexture* texture = m_textures[TEX_LAYER_BACKGROUND].texture;
-
- /* 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;
- }
-
- v2u32 sourcesize = texture->getOriginalSize();
-
- if (m_textures[TEX_LAYER_BACKGROUND].tile)
- {
- v2u32 tilesize(
- MYMAX(sourcesize.X,m_textures[TEX_LAYER_BACKGROUND].minsize),
- MYMAX(sourcesize.Y,m_textures[TEX_LAYER_BACKGROUND].minsize));
- for (unsigned int x = 0; x < screensize.X; x += tilesize.X )
- {
- for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y )
- {
- draw2DImageFilterScaled(driver, texture,
- core::rect<s32>(x, y, x+tilesize.X, y+tilesize.Y),
- core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
- NULL, NULL, true);
- }
- }
- return;
- }
-
- /* Draw background texture */
- draw2DImageFilterScaled(driver, texture,
- core::rect<s32>(0, 0, screensize.X, screensize.Y),
- core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
- NULL, NULL, true);
-}
-
-/******************************************************************************/
-void GUIEngine::drawOverlay(video::IVideoDriver *driver)
-{
- v2u32 screensize = driver->getScreenSize();
-
- video::ITexture* texture = m_textures[TEX_LAYER_OVERLAY].texture;
-
- /* If no texture, draw nothing */
- if(!texture)
- return;
-
- /* Draw background texture */
- v2u32 sourcesize = texture->getOriginalSize();
- draw2DImageFilterScaled(driver, texture,
- core::rect<s32>(0, 0, screensize.X, screensize.Y),
- core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
- NULL, NULL, true);
-}
-
-/******************************************************************************/
-void GUIEngine::drawHeader(video::IVideoDriver *driver)
-{
- core::dimension2d<u32> screensize = driver->getScreenSize();
-
- video::ITexture* texture = m_textures[TEX_LAYER_HEADER].texture;
-
- /* If no texture, draw nothing */
- if(!texture)
- return;
-
- f32 mult = (((f32)screensize.Width / 2.0)) /
- ((f32)texture->getOriginalSize().Width);
-
- v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult,
- ((f32)texture->getOriginalSize().Height) * mult);
-
- // Don't draw the header if 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);
-
- draw2DImageFilterScaled(driver, texture, splashrect,
- core::rect<s32>(core::position2d<s32>(0,0),
- core::dimension2di(texture->getOriginalSize())),
- NULL, NULL, true);
- }
-}
-
-/******************************************************************************/
-void GUIEngine::drawFooter(video::IVideoDriver *driver)
-{
- core::dimension2d<u32> screensize = driver->getScreenSize();
-
- video::ITexture* texture = m_textures[TEX_LAYER_FOOTER].texture;
-
- /* If no texture, draw nothing */
- if(!texture)
- return;
-
- f32 mult = (((f32)screensize.Width)) /
- ((f32)texture->getOriginalSize().Width);
-
- v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult,
- ((f32)texture->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);
-
- draw2DImageFilterScaled(driver, texture, rect,
- core::rect<s32>(core::position2d<s32>(0,0),
- core::dimension2di(texture->getOriginalSize())),
- NULL, NULL, true);
- }
-}
-
-/******************************************************************************/
-bool GUIEngine::setTexture(texture_layer layer, std::string texturepath,
- bool tile_image, unsigned int minsize)
-{
- video::IVideoDriver *driver = RenderingEngine::get_video_driver();
-
- if (m_textures[layer].texture) {
- driver->removeTexture(m_textures[layer].texture);
- m_textures[layer].texture = NULL;
- }
-
- if (texturepath.empty() || !fs::PathExists(texturepath)) {
- return false;
- }
-
- m_textures[layer].texture = driver->getTexture(texturepath.c_str());
- m_textures[layer].tile = tile_image;
- m_textures[layer].minsize = minsize;
-
- if (!m_textures[layer].texture) {
- return false;
- }
-
- return true;
-}
-
-/******************************************************************************/
-bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
-{
-#if USE_CURL
- std::ofstream target_file(target.c_str(), std::ios::out | std::ios::binary);
-
- if (!target_file.good()) {
- return false;
- }
-
- HTTPFetchRequest fetch_request;
- HTTPFetchResult fetch_result;
- fetch_request.url = url;
- fetch_request.caller = HTTPFETCH_SYNC;
- fetch_request.timeout = g_settings->getS32("curl_file_download_timeout");
- httpfetch_sync(fetch_request, fetch_result);
-
- if (!fetch_result.succeeded) {
- return false;
- }
- target_file << fetch_result.data;
-
- return true;
-#else
- return false;
-#endif
-}
-
-/******************************************************************************/
-void GUIEngine::setTopleftText(const std::string &text)
-{
- m_toplefttext = translate_string(utf8_to_wide(text));
-
- updateTopLeftTextSize();
-}
-
-/******************************************************************************/
-void GUIEngine::updateTopLeftTextSize()
-{
- core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
- g_fontengine->getTextHeight());
- rect += v2s32(4, 0);
-
- m_irr_toplefttext->remove();
- m_irr_toplefttext =
- addStaticText(RenderingEngine::get_gui_env(), m_toplefttext,
- rect, false, true, 0, -1);
-}
-
-/******************************************************************************/
-s32 GUIEngine::playSound(SimpleSoundSpec spec, bool looped)
-{
- s32 handle = m_sound_manager->playSound(spec, looped);
- return handle;
-}
-
-/******************************************************************************/
-void GUIEngine::stopSound(s32 handle)
-{
- m_sound_manager->stopSound(handle);
-}
-
-/******************************************************************************/
-unsigned int GUIEngine::queueAsync(const std::string &serialized_func,
- const std::string &serialized_params)
-{
- return m_script->queueAsync(serialized_func, serialized_params);
-}
-
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 sapier
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-/******************************************************************************/
-/* Includes */
-/******************************************************************************/
-#include "irrlichttypes.h"
-#include "modalMenu.h"
-#include "guiFormSpecMenu.h"
-#include "sound.h"
-#include "client/tile.h"
-#include "util/enriched_string.h"
-
-/******************************************************************************/
-/* Typedefs and macros */
-/******************************************************************************/
-/** texture layer ids */
-typedef enum {
- TEX_LAYER_BACKGROUND = 0,
- TEX_LAYER_OVERLAY,
- TEX_LAYER_HEADER,
- TEX_LAYER_FOOTER,
- TEX_LAYER_MAX
-} texture_layer;
-
-typedef struct {
- video::ITexture *texture = nullptr;
- bool tile;
- unsigned int minsize;
-} image_definition;
-
-/******************************************************************************/
-/* forward declarations */
-/******************************************************************************/
-class GUIEngine;
-class MainMenuScripting;
-class Clouds;
-struct MainMenuData;
-
-/******************************************************************************/
-/* declarations */
-/******************************************************************************/
-
-/** GUIEngine specific implementation of TextDest used within guiFormSpecMenu */
-class TextDestGuiEngine : public TextDest
-{
-public:
- /**
- * default constructor
- * @param engine the engine data is transmitted for further processing
- */
- TextDestGuiEngine(GUIEngine* engine) : m_engine(engine) {};
-
- /**
- * receive fields transmitted by guiFormSpecMenu
- * @param fields map containing formspec field elements currently active
- */
- void gotText(const StringMap &fields);
-
- /**
- * receive text/events transmitted by guiFormSpecMenu
- * @param text textual representation of event
- */
- void gotText(const std::wstring &text);
-
-private:
- /** target to transmit data to */
- GUIEngine *m_engine = nullptr;
-};
-
-/** GUIEngine specific implementation of ISimpleTextureSource */
-class MenuTextureSource : public ISimpleTextureSource
-{
-public:
- /**
- * default constructor
- * @param driver the video driver to load textures from
- */
- MenuTextureSource(video::IVideoDriver *driver) : m_driver(driver) {};
-
- /**
- * destructor, removes all loaded textures
- */
- virtual ~MenuTextureSource();
-
- /**
- * get a texture, loading it if required
- * @param name path to the texture
- * @param id receives the texture ID, always 0 in this implementation
- */
- video::ITexture *getTexture(const std::string &name, u32 *id = NULL);
-
-private:
- /** driver to get textures from */
- video::IVideoDriver *m_driver = nullptr;
- /** set of texture names to delete */
- std::set<std::string> m_to_delete;
-};
-
-/** GUIEngine specific implementation of OnDemandSoundFetcher */
-class MenuMusicFetcher: public OnDemandSoundFetcher
-{
-public:
- /**
- * get sound file paths according to sound name
- * @param name sound name
- * @param dst_paths receives possible paths to sound files
- * @param dst_datas receives binary sound data (not used here)
- */
- void fetchSounds(const std::string &name,
- std::set<std::string> &dst_paths,
- std::set<std::string> &dst_datas);
-
-private:
- /** set of fetched sound names */
- std::set<std::string> m_fetched;
-};
-
-/** implementation of main menu based uppon formspecs */
-class GUIEngine {
- /** grant ModApiMainMenu access to private members */
- friend class ModApiMainMenu;
- friend class ModApiSound;
-
-public:
- /**
- * default constructor
- * @param dev device to draw at
- * @param parent parent gui element
- * @param menumgr manager to add menus to
- * @param smgr scene manager to add scene elements to
- * @param data struct to transfer data to main game handling
- */
- GUIEngine(JoystickController *joystick,
- gui::IGUIElement *parent,
- IMenuManager *menumgr,
- MainMenuData *data,
- bool &kill);
-
- /** default destructor */
- virtual ~GUIEngine();
-
- /**
- * return MainMenuScripting interface
- */
- MainMenuScripting *getScriptIface()
- {
- return m_script;
- }
-
- /**
- * return dir of current menuscript
- */
- std::string getScriptDir()
- {
- return m_scriptdir;
- }
-
- /** pass async callback to scriptengine **/
- unsigned int queueAsync(const std::string &serialized_fct,
- const std::string &serialized_params);
-
-private:
-
- /** find and run the main menu script */
- bool loadMainMenuScript();
-
- /** run main menu loop */
- void run();
-
- /** update size of topleftext element */
- void updateTopLeftTextSize();
-
- /** parent gui element */
- gui::IGUIElement *m_parent = nullptr;
- /** manager to add menus to */
- IMenuManager *m_menumanager = nullptr;
- /** scene manager to add scene elements to */
- scene::ISceneManager *m_smgr = nullptr;
- /** pointer to data beeing transfered back to main game handling */
- MainMenuData *m_data = nullptr;
- /** pointer to texture source */
- ISimpleTextureSource *m_texture_source = nullptr;
- /** pointer to soundmanager*/
- ISoundManager *m_sound_manager = nullptr;
-
- /** representation of form source to be used in mainmenu formspec */
- FormspecFormSource *m_formspecgui = nullptr;
- /** formspec input receiver */
- TextDestGuiEngine *m_buttonhandler = nullptr;
- /** the formspec menu */
- GUIFormSpecMenu *m_menu = nullptr;
-
- /** reference to kill variable managed by SIGINT handler */
- bool &m_kill;
-
- /** variable used to abort menu and return back to main game handling */
- bool m_startgame = false;
-
- /** scripting interface */
- MainMenuScripting *m_script = nullptr;
-
- /** script basefolder */
- std::string m_scriptdir = "";
-
- /**
- * draw background layer
- * @param driver to use for drawing
- */
- void drawBackground(video::IVideoDriver *driver);
- /**
- * draw overlay layer
- * @param driver to use for drawing
- */
- void drawOverlay(video::IVideoDriver *driver);
- /**
- * draw header layer
- * @param driver to use for drawing
- */
- void drawHeader(video::IVideoDriver *driver);
- /**
- * draw footer layer
- * @param driver to use for drawing
- */
- void drawFooter(video::IVideoDriver *driver);
-
- /**
- * load a texture for a specified layer
- * @param layer draw layer to specify texture
- * @param texturepath full path of texture to load
- */
- bool setTexture(texture_layer layer, std::string texturepath,
- bool tile_image, unsigned int minsize);
-
- /**
- * download a file using curl
- * @param url url to download
- * @param target file to store to
- */
- static bool downloadFile(const std::string &url, const std::string &target);
-
- /** array containing pointers to current specified texture layers */
- image_definition m_textures[TEX_LAYER_MAX];
-
- /**
- * specify text to appear as top left string
- * @param text to set
- */
- void setTopleftText(const std::string &text);
-
- /** pointer to gui element shown at topleft corner */
- irr::gui::IGUIStaticText *m_irr_toplefttext = nullptr;
- /** and text that is in it */
- EnrichedString m_toplefttext;
-
- /** initialize cloud subsystem */
- void cloudInit();
- /** do preprocessing for cloud subsystem */
- void cloudPreProcess();
- /** do postprocessing for cloud subsystem */
- void cloudPostProcess();
-
- /** internam data required for drawing clouds */
- struct clouddata {
- /** delta time since last cloud processing */
- f32 dtime;
- /** absolute time of last cloud processing */
- u32 lasttime;
- /** pointer to cloud class */
- Clouds *clouds = nullptr;
- /** camera required for drawing clouds */
- scene::ICameraSceneNode *camera = nullptr;
- };
-
- /** is drawing of clouds enabled atm */
- bool m_clouds_enabled = true;
- /** data used to draw clouds */
- clouddata m_cloud;
-
- /** start playing a sound and return handle */
- s32 playSound(SimpleSoundSpec spec, bool looped);
- /** stop playing a sound started with playSound() */
- void stopSound(s32 handle);
-
-
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-
-#include <cstdlib>
-#include <algorithm>
-#include <iterator>
-#include <sstream>
-#include <limits>
-#include "guiFormSpecMenu.h"
-#include "guiTable.h"
-#include "constants.h"
-#include "gamedef.h"
-#include "keycode.h"
-#include "util/strfnd.h"
-#include <IGUICheckBox.h>
-#include <IGUIEditBox.h>
-#include <IGUIButton.h>
-#include <IGUIStaticText.h>
-#include <IGUIFont.h>
-#include <IGUITabControl.h>
-#include <IGUIComboBox.h>
-#include "client/renderingengine.h"
-#include "log.h"
-#include "client/tile.h" // ITextureSource
-#include "hud.h" // drawItemStack
-#include "filesys.h"
-#include "gettime.h"
-#include "gettext.h"
-#include "scripting_server.h"
-#include "porting.h"
-#include "settings.h"
-#include "client.h"
-#include "fontengine.h"
-#include "util/hex.h"
-#include "util/numeric.h"
-#include "util/string.h" // for parseColorString()
-#include "irrlicht_changes/static_text.h"
-#include "guiscalingfilter.h"
-#include "guiEditBoxWithScrollbar.h"
-
-#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
-#include "intlGUIEditBox.h"
-#endif
-
-#define MY_CHECKPOS(a,b) \
- if (v_pos.size() != 2) { \
- errorstream<< "Invalid pos for element " << a << "specified: \"" \
- << parts[b] << "\"" << std::endl; \
- return; \
- }
-
-#define MY_CHECKGEOM(a,b) \
- if (v_geom.size() != 2) { \
- errorstream<< "Invalid pos for element " << a << "specified: \"" \
- << parts[b] << "\"" << std::endl; \
- return; \
- }
-/*
- GUIFormSpecMenu
-*/
-static unsigned int font_line_height(gui::IGUIFont *font)
-{
- return font->getDimension(L"Ay").Height + font->getKerningHeight();
-}
-
-inline u32 clamp_u8(s32 value)
-{
- return (u32) MYMIN(MYMAX(value, 0), 255);
-}
-
-GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
- gui::IGUIElement *parent, s32 id, IMenuManager *menumgr,
- Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst,
- bool remap_dbl_click) :
- GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr),
- m_invmgr(client),
- m_tsrc(tsrc),
- m_client(client),
- m_form_src(fsrc),
- m_text_dst(tdst),
- m_joystick(joystick),
- m_remap_dbl_click(remap_dbl_click)
-#ifdef __ANDROID__
- , m_JavaDialogFieldName("")
-#endif
-{
- current_keys_pending.key_down = false;
- current_keys_pending.key_up = false;
- current_keys_pending.key_enter = false;
- current_keys_pending.key_escape = false;
-
- m_doubleclickdetect[0].time = 0;
- m_doubleclickdetect[1].time = 0;
-
- m_doubleclickdetect[0].pos = v2s32(0, 0);
- m_doubleclickdetect[1].pos = v2s32(0, 0);
-
- m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
- m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname");
-}
-
-GUIFormSpecMenu::~GUIFormSpecMenu()
-{
- removeChildren();
-
- for (auto &table_it : m_tables) {
- table_it.second->drop();
- }
-
- delete m_selected_item;
- delete m_form_src;
- delete m_text_dst;
-}
-
-void GUIFormSpecMenu::removeChildren()
-{
- const core::list<gui::IGUIElement*> &children = getChildren();
-
- while(!children.empty()) {
- (*children.getLast())->remove();
- }
-
- if(m_tooltip_element) {
- m_tooltip_element->remove();
- m_tooltip_element->drop();
- m_tooltip_element = NULL;
- }
-
-}
-
-void GUIFormSpecMenu::setInitialFocus()
-{
- // Set initial focus according to following order of precedence:
- // 1. first empty editbox
- // 2. first editbox
- // 3. first table
- // 4. last button
- // 5. first focusable (not statictext, not tabheader)
- // 6. first child element
-
- core::list<gui::IGUIElement*> children = getChildren();
-
- // in case "children" contains any NULL elements, remove them
- for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
- it != children.end();) {
- if (*it)
- ++it;
- else
- it = children.erase(it);
- }
-
- // 1. first empty editbox
- for (gui::IGUIElement *it : children) {
- if (it->getType() == gui::EGUIET_EDIT_BOX
- && it->getText()[0] == 0) {
- Environment->setFocus(it);
- return;
- }
- }
-
- // 2. first editbox
- for (gui::IGUIElement *it : children) {
- if (it->getType() == gui::EGUIET_EDIT_BOX) {
- Environment->setFocus(it);
- return;
- }
- }
-
- // 3. first table
- for (gui::IGUIElement *it : children) {
- if (it->getTypeName() == std::string("GUITable")) {
- Environment->setFocus(it);
- return;
- }
- }
-
- // 4. last button
- for (core::list<gui::IGUIElement*>::Iterator it = children.getLast();
- it != children.end(); --it) {
- if ((*it)->getType() == gui::EGUIET_BUTTON) {
- Environment->setFocus(*it);
- return;
- }
- }
-
- // 5. first focusable (not statictext, not tabheader)
- for (gui::IGUIElement *it : children) {
- if (it->getType() != gui::EGUIET_STATIC_TEXT &&
- it->getType() != gui::EGUIET_TAB_CONTROL) {
- Environment->setFocus(it);
- return;
- }
- }
-
- // 6. first child element
- if (children.empty())
- Environment->setFocus(this);
- else
- Environment->setFocus(*(children.begin()));
-}
-
-GUITable* GUIFormSpecMenu::getTable(const std::string &tablename)
-{
- for (auto &table : m_tables) {
- if (tablename == table.first.fname)
- return table.second;
- }
- return 0;
-}
-
-std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string &name)
-{
- for (auto &dropdown : m_dropdowns) {
- if (name == dropdown.first.fname)
- return &dropdown.second;
- }
- return NULL;
-}
-
-void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,',');
-
- if (((parts.size() == 2) || parts.size() == 3) ||
- ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- if (parts[1].find(';') != std::string::npos)
- parts[1] = parts[1].substr(0,parts[1].find(';'));
-
- data->invsize.X = MYMAX(0, stof(parts[0]));
- data->invsize.Y = MYMAX(0, stof(parts[1]));
-
- lockSize(false);
- if (parts.size() == 3) {
- if (parts[2] == "true") {
- lockSize(true,v2u32(800,600));
- }
- }
-
- data->explicit_size = true;
- return;
- }
- errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element, ',');
-
- if (parts.size() >= 2) {
- if (parts[1].find(';') != std::string::npos)
- parts[1] = parts[1].substr(0, parts[1].find(';'));
-
- container_stack.push(pos_offset);
- pos_offset.X += MYMAX(0, stof(parts[0]));
- pos_offset.Y += MYMAX(0, stof(parts[1]));
- return;
- }
- errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseContainerEnd(parserData* data)
-{
- if (container_stack.empty()) {
- errorstream<< "Invalid container end element, no matching container start element" << std::endl;
- } else {
- pos_offset = container_stack.top();
- container_stack.pop();
- }
-}
-
-void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
-{
- if (m_client == 0) {
- warningstream<<"invalid use of 'list' with m_client==0"<<std::endl;
- return;
- }
-
- std::vector<std::string> parts = split(element,';');
-
- if (((parts.size() == 4) || (parts.size() == 5)) ||
- ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::string location = parts[0];
- std::string listname = parts[1];
- std::vector<std::string> v_pos = split(parts[2],',');
- std::vector<std::string> v_geom = split(parts[3],',');
- std::string startindex;
- if (parts.size() == 5)
- startindex = parts[4];
-
- MY_CHECKPOS("list",2);
- MY_CHECKGEOM("list",3);
-
- InventoryLocation loc;
-
- if(location == "context" || location == "current_name")
- loc = m_current_inventory_location;
- else
- loc.deSerialize(location);
-
- v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
-
- v2s32 geom;
- geom.X = stoi(v_geom[0]);
- geom.Y = stoi(v_geom[1]);
-
- s32 start_i = 0;
- if (!startindex.empty())
- start_i = stoi(startindex);
-
- if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
- errorstream<< "Invalid list element: '" << element << "'" << std::endl;
- return;
- }
-
- if(!data->explicit_size)
- warningstream<<"invalid use of list without a size[] element"<<std::endl;
- m_inventorylists.emplace_back(loc, listname, pos, geom, start_i);
- return;
- }
- errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element)
-{
- if (m_client == 0) {
- errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl;
- return;
- }
-
- std::vector<std::string> parts = split(element, ';');
-
- if (parts.size() == 2) {
- std::string location = parts[0];
- std::string listname = parts[1];
-
- InventoryLocation loc;
-
- if (location == "context" || location == "current_name")
- loc = m_current_inventory_location;
- else
- loc.deSerialize(location);
-
- m_inventory_rings.emplace_back(loc, listname);
- return;
- }
-
- if (element.empty() && m_inventorylists.size() > 1) {
- size_t siz = m_inventorylists.size();
- // insert the last two inv list elements into the list ring
- const ListDrawSpec &spa = m_inventorylists[siz - 2];
- const ListDrawSpec &spb = m_inventorylists[siz - 1];
- m_inventory_rings.emplace_back(spa.inventoryloc, spa.listname);
- m_inventory_rings.emplace_back(spb.inventoryloc, spb.listname);
- return;
- }
-
- errorstream<< "Invalid list ring element(" << parts.size() << ", "
- << m_inventorylists.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (((parts.size() >= 3) && (parts.size() <= 4)) ||
- ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::string name = parts[1];
- std::string label = parts[2];
- std::string selected;
-
- if (parts.size() >= 4)
- selected = parts[3];
-
- MY_CHECKPOS("checkbox",0);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float) spacing.X;
- pos.Y += stof(v_pos[1]) * (float) spacing.Y;
-
- bool fselected = false;
-
- if (selected == "true")
- fselected = true;
-
- std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
-
- core::rect<s32> rect = core::rect<s32>(
- pos.X, pos.Y + ((imgsize.Y/2) - m_btn_height),
- pos.X + m_font->getDimension(wlabel.c_str()).Width + 25, // text size + size of checkbox
- pos.Y + ((imgsize.Y/2) + m_btn_height));
-
- FieldSpec spec(
- name,
- wlabel, //Needed for displaying text on MSVC
- wlabel,
- 258+m_fields.size()
- );
-
- spec.ftype = f_CheckBox;
-
- gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
- spec.fid, spec.flabel.c_str());
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- m_checkboxes.emplace_back(spec,e);
- m_fields.push_back(spec);
- return;
- }
- errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (parts.size() >= 5) {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_dim = split(parts[1],',');
- std::string name = parts[3];
- std::string value = parts[4];
-
- MY_CHECKPOS("scrollbar",0);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float) spacing.X;
- pos.Y += stof(v_pos[1]) * (float) spacing.Y;
-
- if (v_dim.size() != 2) {
- errorstream<< "Invalid size for element " << "scrollbar"
- << "specified: \"" << parts[1] << "\"" << std::endl;
- return;
- }
-
- v2s32 dim;
- dim.X = stof(v_dim[0]) * (float) spacing.X;
- dim.Y = stof(v_dim[1]) * (float) spacing.Y;
-
- core::rect<s32> rect =
- core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
-
- FieldSpec spec(
- name,
- L"",
- L"",
- 258+m_fields.size()
- );
-
- bool is_horizontal = true;
-
- if (parts[2] == "vertical")
- is_horizontal = false;
-
- spec.ftype = f_ScrollBar;
- spec.send = true;
- gui::IGUIScrollBar* e =
- Environment->addScrollBar(is_horizontal,rect,this,spec.fid);
-
- e->setMax(1000);
- e->setMin(0);
- e->setPos(stoi(parts[4]));
- e->setSmallStep(10);
- e->setLargeStep(100);
-
- m_scrollbars.emplace_back(spec,e);
- m_fields.push_back(spec);
- return;
- }
- errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 3) ||
- ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string name = unescape_string(parts[2]);
-
- MY_CHECKPOS("image", 0);
- MY_CHECKGEOM("image", 1);
-
- v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float) spacing.X;
- pos.Y += stof(v_pos[1]) * (float) spacing.Y;
-
- v2s32 geom;
- geom.X = stof(v_geom[0]) * (float)imgsize.X;
- geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
-
- if (!data->explicit_size)
- warningstream<<"invalid use of image without a size[] element"<<std::endl;
- m_images.emplace_back(name, pos, geom);
- return;
- }
-
- if (parts.size() == 2) {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::string name = unescape_string(parts[1]);
-
- MY_CHECKPOS("image", 0);
-
- v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float) spacing.X;
- pos.Y += stof(v_pos[1]) * (float) spacing.Y;
-
- if (!data->explicit_size)
- warningstream<<"invalid use of image without a size[] element"<<std::endl;
- m_images.emplace_back(name, pos);
- return;
- }
- errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 3) ||
- ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string name = parts[2];
-
- MY_CHECKPOS("itemimage",0);
- MY_CHECKGEOM("itemimage",1);
-
- v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float) spacing.X;
- pos.Y += stof(v_pos[1]) * (float) spacing.Y;
-
- v2s32 geom;
- geom.X = stof(v_geom[0]) * (float)imgsize.X;
- geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
-
- if(!data->explicit_size)
- warningstream<<"invalid use of item_image without a size[] element"<<std::endl;
- m_itemimages.emplace_back("", name, pos, geom);
- return;
- }
- errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
- const std::string &type)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 4) ||
- ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string name = parts[2];
- std::string label = parts[3];
-
- MY_CHECKPOS("button",0);
- MY_CHECKGEOM("button",1);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
-
- v2s32 geom;
- geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
- pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
-
- core::rect<s32> rect =
- core::rect<s32>(pos.X, pos.Y - m_btn_height,
- pos.X + geom.X, pos.Y + m_btn_height);
-
- if(!data->explicit_size)
- warningstream<<"invalid use of button without a size[] element"<<std::endl;
-
- std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
-
- FieldSpec spec(
- name,
- wlabel,
- L"",
- 258+m_fields.size()
- );
- spec.ftype = f_Button;
- if(type == "button_exit")
- spec.is_exit = true;
- gui::IGUIButton* e = Environment->addButton(rect, this, spec.fid,
- spec.flabel.c_str());
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- m_fields.push_back(spec);
- return;
- }
- errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (((parts.size() == 3) || (parts.size() == 4)) ||
- ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string name = unescape_string(parts[2]);
-
- MY_CHECKPOS("background",0);
- MY_CHECKGEOM("background",1);
-
- v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X - (float)imgsize.X)/2;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y - (float)imgsize.Y)/2;
-
- v2s32 geom;
- geom.X = stof(v_geom[0]) * (float)spacing.X;
- geom.Y = stof(v_geom[1]) * (float)spacing.Y;
-
- if (!data->explicit_size)
- warningstream<<"invalid use of background without a size[] element"<<std::endl;
-
- bool clip = false;
- if (parts.size() == 4 && is_yes(parts[3])) {
- pos.X = stoi(v_pos[0]); //acts as offset
- pos.Y = stoi(v_pos[1]); //acts as offset
- clip = true;
- }
- m_backgrounds.emplace_back(name, pos, geom, clip);
-
- return;
- }
- errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- data->table_options.clear();
- for (const std::string &part : parts) {
- // Parse table option
- std::string opt = unescape_string(part);
- data->table_options.push_back(GUITable::splitOption(opt));
- }
-}
-
-void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- data->table_columns.clear();
- for (const std::string &part : parts) {
- std::vector<std::string> col_parts = split(part,',');
- GUITable::TableColumn column;
- // Parse column type
- if (!col_parts.empty())
- column.type = col_parts[0];
- // Parse column options
- for (size_t j = 1; j < col_parts.size(); ++j) {
- std::string opt = unescape_string(col_parts[j]);
- column.options.push_back(GUITable::splitOption(opt));
- }
- data->table_columns.push_back(column);
- }
-}
-
-void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (((parts.size() == 4) || (parts.size() == 5)) ||
- ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string name = parts[2];
- std::vector<std::string> items = split(parts[3],',');
- std::string str_initial_selection;
- std::string str_transparent = "false";
-
- if (parts.size() >= 5)
- str_initial_selection = parts[4];
-
- MY_CHECKPOS("table",0);
- MY_CHECKGEOM("table",1);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
-
- v2s32 geom;
- geom.X = stof(v_geom[0]) * (float)spacing.X;
- geom.Y = stof(v_geom[1]) * (float)spacing.Y;
-
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
-
- FieldSpec spec(
- name,
- L"",
- L"",
- 258+m_fields.size()
- );
-
- spec.ftype = f_Table;
-
- for (std::string &item : items) {
- item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
- }
-
- //now really show table
- GUITable *e = new GUITable(Environment, this, spec.fid, rect,
- m_tsrc);
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- e->setTable(data->table_options, data->table_columns, items);
-
- if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
- e->setDynamicData(data->table_dyndata[name]);
- }
-
- if (!str_initial_selection.empty() && str_initial_selection != "0")
- e->setSelected(stoi(str_initial_selection));
-
- m_tables.emplace_back(spec, e);
- m_fields.push_back(spec);
- return;
- }
- errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) ||
- ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string name = parts[2];
- std::vector<std::string> items = split(parts[3],',');
- std::string str_initial_selection;
- std::string str_transparent = "false";
-
- if (parts.size() >= 5)
- str_initial_selection = parts[4];
-
- if (parts.size() >= 6)
- str_transparent = parts[5];
-
- MY_CHECKPOS("textlist",0);
- MY_CHECKGEOM("textlist",1);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
-
- v2s32 geom;
- geom.X = stof(v_geom[0]) * (float)spacing.X;
- geom.Y = stof(v_geom[1]) * (float)spacing.Y;
-
-
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
-
- FieldSpec spec(
- name,
- L"",
- L"",
- 258+m_fields.size()
- );
-
- spec.ftype = f_Table;
-
- for (std::string &item : items) {
- item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
- }
-
- //now really show list
- GUITable *e = new GUITable(Environment, this, spec.fid, rect,
- m_tsrc);
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- e->setTextList(items, is_yes(str_transparent));
-
- if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
- e->setDynamicData(data->table_dyndata[name]);
- }
-
- if (!str_initial_selection.empty() && str_initial_selection != "0")
- e->setSelected(stoi(str_initial_selection));
-
- m_tables.emplace_back(spec, e);
- m_fields.push_back(spec);
- return;
- }
- errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-
-void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 5) ||
- ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::string name = parts[2];
- std::vector<std::string> items = split(parts[3],',');
- std::string str_initial_selection;
- str_initial_selection = parts[4];
-
- MY_CHECKPOS("dropdown",0);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
-
- s32 width = stof(parts[1]) * (float)spacing.Y;
-
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
- pos.X + width, pos.Y + (m_btn_height * 2));
-
- FieldSpec spec(
- name,
- L"",
- L"",
- 258+m_fields.size()
- );
-
- spec.ftype = f_DropDown;
- spec.send = true;
-
- //now really show list
- gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid);
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- for (const std::string &item : items) {
- e->addItem(unescape_translate(unescape_string(
- utf8_to_wide(item))).c_str());
- }
-
- if (!str_initial_selection.empty())
- e->setSelected(stoi(str_initial_selection)-1);
-
- m_fields.push_back(spec);
-
- m_dropdowns.emplace_back(spec, std::vector<std::string>());
- std::vector<std::string> &values = m_dropdowns.back().second;
- for (const std::string &item : items) {
- values.push_back(unescape_string(item));
- }
-
- return;
- }
- errorstream << "Invalid dropdown element(" << parts.size() << "): '"
- << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
- if (parts.size() == 2 ||
- (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) {
- field_close_on_enter[parts[0]] = is_yes(parts[1]);
- }
-}
-
-void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 4) || (parts.size() == 5) ||
- ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string name = parts[2];
- std::string label = parts[3];
-
- MY_CHECKPOS("pwdfield",0);
- MY_CHECKGEOM("pwdfield",1);
-
- v2s32 pos = pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
-
- v2s32 geom;
- geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
-
- pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
- pos.Y -= m_btn_height;
- geom.Y = m_btn_height*2;
-
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
-
- std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
-
- FieldSpec spec(
- name,
- wlabel,
- L"",
- 258+m_fields.size()
- );
-
- spec.send = true;
- gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- if (label.length() >= 1)
- {
- int font_height = g_fontengine->getTextHeight();
- rect.UpperLeftCorner.Y -= font_height;
- rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
- addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
- }
-
- e->setPasswordBox(true,L'*');
-
- irr::SEvent evt;
- evt.EventType = EET_KEY_INPUT_EVENT;
- evt.KeyInput.Key = KEY_END;
- evt.KeyInput.Char = 0;
- evt.KeyInput.Control = false;
- evt.KeyInput.Shift = false;
- evt.KeyInput.PressedDown = true;
- e->OnEvent(evt);
-
- if (parts.size() >= 5) {
- // TODO: remove after 2016-11-03
- warningstream << "pwdfield: use field_close_on_enter[name, enabled]" <<
- " instead of the 5th param" << std::endl;
- field_close_on_enter[name] = is_yes(parts[4]);
- }
-
- m_fields.push_back(spec);
- return;
- }
- errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseSimpleField(parserData* data,
- std::vector<std::string> &parts)
-{
- std::string name = parts[0];
- std::string label = parts[1];
- std::string default_val = parts[2];
-
- core::rect<s32> rect;
-
- if(data->explicit_size)
- warningstream<<"invalid use of unpositioned \"field\" in inventory"<<std::endl;
-
- v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
- pos.Y = ((m_fields.size()+2)*60);
- v2s32 size = DesiredRect.getSize();
-
- rect = core::rect<s32>(size.X / 2 - 150, pos.Y,
- (size.X / 2 - 150) + 300, pos.Y + (m_btn_height*2));
-
-
- if(m_form_src)
- default_val = m_form_src->resolveText(default_val);
-
-
- std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
-
- FieldSpec spec(
- name,
- wlabel,
- utf8_to_wide(unescape_string(default_val)),
- 258+m_fields.size()
- );
-
- if (name.empty()) {
- // spec field id to 0, this stops submit searching for a value that isn't there
- addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid);
- } else {
- spec.send = true;
- gui::IGUIElement *e;
-#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
- if (g_settings->getBool("freetype")) {
- e = (gui::IGUIElement *) new gui::intlGUIEditBox(spec.fdefault.c_str(),
- true, Environment, this, spec.fid, rect);
- e->drop();
- } else {
-#else
- {
-#endif
- e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
- }
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- irr::SEvent evt;
- evt.EventType = EET_KEY_INPUT_EVENT;
- evt.KeyInput.Key = KEY_END;
- evt.KeyInput.Char = 0;
- evt.KeyInput.Control = 0;
- evt.KeyInput.Shift = 0;
- evt.KeyInput.PressedDown = true;
- e->OnEvent(evt);
-
- if (label.length() >= 1)
- {
- int font_height = g_fontengine->getTextHeight();
- rect.UpperLeftCorner.Y -= font_height;
- rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
- addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
- }
- }
-
- if (parts.size() >= 4) {
- // TODO: remove after 2016-11-03
- warningstream << "field/simple: use field_close_on_enter[name, enabled]" <<
- " instead of the 4th param" << std::endl;
- field_close_on_enter[name] = is_yes(parts[3]);
- }
-
- m_fields.push_back(spec);
-}
-
-void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
- const std::string &type)
-{
-
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string name = parts[2];
- std::string label = parts[3];
- std::string default_val = parts[4];
-
- MY_CHECKPOS(type,0);
- MY_CHECKGEOM(type,1);
-
- v2s32 pos = pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float) spacing.X;
- pos.Y += stof(v_pos[1]) * (float) spacing.Y;
-
- v2s32 geom;
-
- geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
-
- if (type == "textarea")
- {
- geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
- pos.Y += m_btn_height;
- }
- else
- {
- pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
- pos.Y -= m_btn_height;
- geom.Y = m_btn_height*2;
- }
-
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
-
- if(!data->explicit_size)
- warningstream<<"invalid use of positioned "<<type<<" without a size[] element"<<std::endl;
-
- if(m_form_src)
- default_val = m_form_src->resolveText(default_val);
-
-
- std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
-
- FieldSpec spec(
- name,
- wlabel,
- utf8_to_wide(unescape_string(default_val)),
- 258+m_fields.size()
- );
-
- bool is_editable = !name.empty();
-
- if (is_editable)
- spec.send = true;
-
- gui::IGUIEditBox *e = nullptr;
- const wchar_t *text = spec.fdefault.empty() ?
- wlabel.c_str() : spec.fdefault.c_str();
-
-#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
- if (g_settings->getBool("freetype")) {
- e = (gui::IGUIEditBox *) new gui::intlGUIEditBox(text,
- true, Environment, this, spec.fid, rect, is_editable, true);
- e->drop();
- } else {
-#else
- {
-#endif
- e = new GUIEditBoxWithScrollBar(text, true,
- Environment, this, spec.fid, rect, is_editable, true);
- }
-
- if (is_editable && spec.fname == data->focused_fieldname)
- Environment->setFocus(e);
-
- if (e) {
- if (type == "textarea")
- {
- e->setMultiLine(true);
- e->setWordWrap(true);
- e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
- } else {
- irr::SEvent evt;
- evt.EventType = EET_KEY_INPUT_EVENT;
- evt.KeyInput.Key = KEY_END;
- evt.KeyInput.Char = 0;
- evt.KeyInput.Control = 0;
- evt.KeyInput.Shift = 0;
- evt.KeyInput.PressedDown = true;
- e->OnEvent(evt);
- }
- }
-
- if (is_editable && !label.empty()) {
- int font_height = g_fontengine->getTextHeight();
- rect.UpperLeftCorner.Y -= font_height;
- rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
- addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
- }
-
- if (parts.size() >= 6) {
- // TODO: remove after 2016-11-03
- warningstream << "field/textarea: use field_close_on_enter[name, enabled]" <<
- " instead of the 6th param" << std::endl;
- field_close_on_enter[name] = is_yes(parts[5]);
- }
-
- m_fields.push_back(spec);
-}
-
-void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
- const std::string &type)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (parts.size() == 3 || parts.size() == 4) {
- parseSimpleField(data,parts);
- return;
- }
-
- if ((parts.size() == 5) || (parts.size() == 6) ||
- ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- parseTextArea(data,parts,type);
- return;
- }
- errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 2) ||
- ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::string text = parts[1];
-
- MY_CHECKPOS("label",0);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += (stof(v_pos[1]) + 7.0/30.0) * (float)spacing.Y;
-
- if(!data->explicit_size)
- warningstream<<"invalid use of label without a size[] element"<<std::endl;
-
- std::vector<std::string> lines = split(text, '\n');
-
- for (unsigned int i = 0; i != lines.size(); i++) {
- // Lines are spaced at the nominal distance of
- // 2/5 inventory slot, even if the font doesn't
- // quite match that. This provides consistent
- // form layout, at the expense of sometimes
- // having sub-optimal spacing for the font.
- // We multiply by 2 and then divide by 5, rather
- // than multiply by 0.4, to get exact results
- // in the integer cases: 0.4 is not exactly
- // representable in binary floating point.
- s32 posy = pos.Y + ((float)i) * spacing.Y * 2.0 / 5.0;
- std::wstring wlabel = utf8_to_wide(unescape_string(lines[i]));
- core::rect<s32> rect = core::rect<s32>(
- pos.X, posy - m_btn_height,
- pos.X + m_font->getDimension(wlabel.c_str()).Width,
- posy + m_btn_height);
- FieldSpec spec(
- "",
- wlabel,
- L"",
- 258+m_fields.size()
- );
- gui::IGUIStaticText *e =
- addStaticText(Environment, spec.flabel.c_str(),
- rect, false, false, this, spec.fid);
- e->setTextAlignment(gui::EGUIA_UPPERLEFT,
- gui::EGUIA_CENTER);
- m_fields.push_back(spec);
- }
-
- return;
- }
- errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 2) ||
- ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::wstring text = unescape_translate(
- unescape_string(utf8_to_wide(parts[1])));
-
- MY_CHECKPOS("vertlabel",1);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
-
- core::rect<s32> rect = core::rect<s32>(
- pos.X, pos.Y+((imgsize.Y/2)- m_btn_height),
- pos.X+15, pos.Y +
- font_line_height(m_font)
- * (text.length()+1)
- +((imgsize.Y/2)- m_btn_height));
- //actually text.length() would be correct but adding +1 avoids to break all mods
-
- if(!data->explicit_size)
- warningstream<<"invalid use of label without a size[] element"<<std::endl;
-
- std::wstring label;
-
- for (wchar_t i : text) {
- label += i;
- label += L"\n";
- }
-
- FieldSpec spec(
- "",
- label,
- L"",
- 258+m_fields.size()
- );
- gui::IGUIStaticText *t =
- addStaticText(Environment, spec.flabel.c_str(), rect, false, false, this, spec.fid);
- t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
- m_fields.push_back(spec);
- return;
- }
- errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element,
- const std::string &type)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) ||
- ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string image_name = parts[2];
- std::string name = parts[3];
- std::string label = parts[4];
-
- MY_CHECKPOS("imagebutton",0);
- MY_CHECKGEOM("imagebutton",1);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
- v2s32 geom;
- geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
- geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
-
- bool noclip = false;
- bool drawborder = true;
- std::string pressed_image_name;
-
- if (parts.size() >= 7) {
- if (parts[5] == "true")
- noclip = true;
- if (parts[6] == "false")
- drawborder = false;
- }
-
- if (parts.size() >= 8) {
- pressed_image_name = parts[7];
- }
-
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
-
- if(!data->explicit_size)
- warningstream<<"invalid use of image_button without a size[] element"<<std::endl;
-
- image_name = unescape_string(image_name);
- pressed_image_name = unescape_string(pressed_image_name);
-
- std::wstring wlabel = utf8_to_wide(unescape_string(label));
-
- FieldSpec spec(
- name,
- wlabel,
- utf8_to_wide(image_name),
- 258+m_fields.size()
- );
- spec.ftype = f_Button;
- if(type == "image_button_exit")
- spec.is_exit = true;
-
- video::ITexture *texture = 0;
- video::ITexture *pressed_texture = 0;
- texture = m_tsrc->getTexture(image_name);
- if (!pressed_image_name.empty())
- pressed_texture = m_tsrc->getTexture(pressed_image_name);
- else
- pressed_texture = texture;
-
- gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- e->setUseAlphaChannel(true);
- e->setImage(guiScalingImageButton(
- Environment->getVideoDriver(), texture, geom.X, geom.Y));
- e->setPressedImage(guiScalingImageButton(
- Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
- e->setScaleImage(true);
- e->setNotClipped(noclip);
- e->setDrawBorder(drawborder);
-
- m_fields.push_back(spec);
- return;
- }
-
- errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (((parts.size() == 4) || (parts.size() == 6)) ||
- ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::string name = parts[1];
- std::vector<std::string> buttons = split(parts[2],',');
- std::string str_index = parts[3];
- bool show_background = true;
- bool show_border = true;
- int tab_index = stoi(str_index) -1;
-
- MY_CHECKPOS("tabheader",0);
-
- if (parts.size() == 6) {
- if (parts[4] == "true")
- show_background = false;
- if (parts[5] == "false")
- show_border = false;
- }
-
- FieldSpec spec(
- name,
- L"",
- L"",
- 258+m_fields.size()
- );
-
- spec.ftype = f_TabHeader;
-
- v2s32 pos = pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y - m_btn_height * 2;
- v2s32 geom;
- geom.X = DesiredRect.getWidth();
- geom.Y = m_btn_height*2;
-
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
- pos.Y+geom.Y);
-
- gui::IGUITabControl *e = Environment->addTabControl(rect, this,
- show_background, show_border, spec.fid);
- e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
- irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
- e->setTabHeight(m_btn_height*2);
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- e->setNotClipped(true);
-
- for (const std::string &button : buttons) {
- e->addTab(unescape_translate(unescape_string(
- utf8_to_wide(button))).c_str(), -1);
- }
-
- if ((tab_index >= 0) &&
- (buttons.size() < INT_MAX) &&
- (tab_index < (int) buttons.size()))
- e->setActiveTab(tab_index);
-
- m_fields.push_back(spec);
- return;
- }
- errorstream << "Invalid TabHeader element(" << parts.size() << "): '"
- << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
-{
-
- if (m_client == 0) {
- warningstream << "invalid use of item_image_button with m_client==0"
- << std::endl;
- return;
- }
-
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 5) ||
- ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
- std::string item_name = parts[2];
- std::string name = parts[3];
- std::string label = parts[4];
-
- label = unescape_string(label);
- item_name = unescape_string(item_name);
-
- MY_CHECKPOS("itemimagebutton",0);
- MY_CHECKGEOM("itemimagebutton",1);
-
- v2s32 pos = padding + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float)spacing.X;
- pos.Y += stof(v_pos[1]) * (float)spacing.Y;
- v2s32 geom;
- geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
- geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
-
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
-
- if(!data->explicit_size)
- warningstream<<"invalid use of item_image_button without a size[] element"<<std::endl;
-
- IItemDefManager *idef = m_client->idef();
- ItemStack item;
- item.deSerialize(item_name, idef);
-
- m_tooltips[name] =
- TooltipSpec(utf8_to_wide(item.getDefinition(idef).description),
- m_default_tooltip_bgcolor,
- m_default_tooltip_color);
-
- FieldSpec spec(
- name,
- utf8_to_wide(label),
- utf8_to_wide(item_name),
- 258 + m_fields.size()
- );
-
- gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, L"");
-
- if (spec.fname == data->focused_fieldname) {
- Environment->setFocus(e);
- }
-
- spec.ftype = f_Button;
- rect+=data->basepos-padding;
- spec.rect=rect;
- m_fields.push_back(spec);
- pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float) spacing.X;
- pos.Y += stof(v_pos[1]) * (float) spacing.Y;
- m_itemimages.emplace_back("", item_name, e, pos, geom);
- m_static_texts.emplace_back(utf8_to_wide(label), rect, e);
- return;
- }
- errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if ((parts.size() == 3) ||
- ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_geom = split(parts[1],',');
-
- MY_CHECKPOS("box",0);
- MY_CHECKGEOM("box",1);
-
- v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
- pos.X += stof(v_pos[0]) * (float) spacing.X;
- pos.Y += stof(v_pos[1]) * (float) spacing.Y;
-
- v2s32 geom;
- geom.X = stof(v_geom[0]) * (float)spacing.X;
- geom.Y = stof(v_geom[1]) * (float)spacing.Y;
-
- video::SColor tmp_color;
-
- if (parseColorString(parts[2], tmp_color, false)) {
- BoxDrawSpec spec(pos, geom, tmp_color);
-
- m_boxes.push_back(spec);
- }
- else {
- errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl;
- }
- return;
- }
- errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (((parts.size() == 1) || (parts.size() == 2)) ||
- ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) {
- parseColorString(parts[0], m_bgcolor, false);
-
- if (parts.size() == 2) {
- std::string fullscreen = parts[1];
- m_bgfullscreen = is_yes(fullscreen);
- }
-
- return;
- }
-
- errorstream << "Invalid bgcolor element(" << parts.size() << "): '" << element << "'"
- << std::endl;
-}
-
-void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
-
- if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) ||
- ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
- parseColorString(parts[0], m_slotbg_n, false);
- parseColorString(parts[1], m_slotbg_h, false);
-
- if (parts.size() >= 3) {
- if (parseColorString(parts[2], m_slotbordercolor, false)) {
- m_slotborder = true;
- }
- }
- if (parts.size() == 5) {
- video::SColor tmp_color;
-
- if (parseColorString(parts[3], tmp_color, false))
- m_default_tooltip_bgcolor = tmp_color;
- if (parseColorString(parts[4], tmp_color, false))
- m_default_tooltip_color = tmp_color;
- }
- return;
- }
- errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
-{
- std::vector<std::string> parts = split(element,';');
- if (parts.size() == 2) {
- std::string name = parts[0];
- m_tooltips[name] = TooltipSpec(utf8_to_wide(unescape_string(parts[1])),
- m_default_tooltip_bgcolor, m_default_tooltip_color);
- return;
- }
-
- if (parts.size() == 4) {
- std::string name = parts[0];
- video::SColor tmp_color1, tmp_color2;
- if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) {
- m_tooltips[name] = TooltipSpec(utf8_to_wide(unescape_string(parts[1])),
- tmp_color1, tmp_color2);
- return;
- }
- }
- errorstream<< "Invalid tooltip element(" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-bool GUIFormSpecMenu::parseVersionDirect(const std::string &data)
-{
- //some prechecks
- if (data.empty())
- return false;
-
- std::vector<std::string> parts = split(data,'[');
-
- if (parts.size() < 2) {
- return false;
- }
-
- if (parts[0] != "formspec_version") {
- return false;
- }
-
- if (is_number(parts[1])) {
- m_formspec_version = mystoi(parts[1]);
- return true;
- }
-
- return false;
-}
-
-bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &element)
-{
- if (element.empty())
- return false;
-
- std::vector<std::string> parts = split(element,'[');
-
- if (parts.size() < 2)
- return false;
-
- std::string type = trim(parts[0]);
- std::string description = trim(parts[1]);
-
- if (type != "size" && type != "invsize")
- return false;
-
- if (type == "invsize")
- log_deprecated("Deprecated formspec element \"invsize\" is used");
-
- parseSize(data, description);
-
- return true;
-}
-
-bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &element)
-{
- if (element.empty())
- return false;
-
- std::vector<std::string> parts = split(element, '[');
-
- if (parts.size() != 2)
- return false;
-
- std::string type = trim(parts[0]);
- std::string description = trim(parts[1]);
-
- if (type != "position")
- return false;
-
- parsePosition(data, description);
-
- return true;
-}
-
-void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element)
-{
- std::vector<std::string> parts = split(element, ',');
-
- if (parts.size() == 2) {
- data->offset.X = stof(parts[0]);
- data->offset.Y = stof(parts[1]);
- return;
- }
-
- errorstream << "Invalid position element (" << parts.size() << "): '" << element << "'" << std::endl;
-}
-
-bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &element)
-{
- if (element.empty())
- return false;
-
- std::vector<std::string> parts = split(element, '[');
-
- if (parts.size() != 2)
- return false;
-
- std::string type = trim(parts[0]);
- std::string description = trim(parts[1]);
-
- if (type != "anchor")
- return false;
-
- parseAnchor(data, description);
-
- return true;
-}
-
-void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element)
-{
- std::vector<std::string> parts = split(element, ',');
-
- if (parts.size() == 2) {
- data->anchor.X = stof(parts[0]);
- data->anchor.Y = stof(parts[1]);
- return;
- }
-
- errorstream << "Invalid anchor element (" << parts.size() << "): '" << element
- << "'" << std::endl;
-}
-
-void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
-{
- //some prechecks
- if (element.empty())
- return;
-
- std::vector<std::string> parts = split(element,'[');
-
- // ugly workaround to keep compatibility
- if (parts.size() > 2) {
- if (trim(parts[0]) == "image") {
- for (unsigned int i=2;i< parts.size(); i++) {
- parts[1] += "[" + parts[i];
- }
- }
- else { return; }
- }
-
- if (parts.size() < 2) {
- return;
- }
-
- std::string type = trim(parts[0]);
- std::string description = trim(parts[1]);
-
- if (type == "container") {
- parseContainer(data, description);
- return;
- }
-
- if (type == "container_end") {
- parseContainerEnd(data);
- return;
- }
-
- if (type == "list") {
- parseList(data, description);
- return;
- }
-
- if (type == "listring") {
- parseListRing(data, description);
- return;
- }
-
- if (type == "checkbox") {
- parseCheckbox(data, description);
- return;
- }
-
- if (type == "image") {
- parseImage(data, description);
- return;
- }
-
- if (type == "item_image") {
- parseItemImage(data, description);
- return;
- }
-
- if (type == "button" || type == "button_exit") {
- parseButton(data, description, type);
- return;
- }
-
- if (type == "background") {
- parseBackground(data,description);
- return;
- }
-
- if (type == "tableoptions"){
- parseTableOptions(data,description);
- return;
- }
-
- if (type == "tablecolumns"){
- parseTableColumns(data,description);
- return;
- }
-
- if (type == "table"){
- parseTable(data,description);
- return;
- }
-
- if (type == "textlist"){
- parseTextList(data,description);
- return;
- }
-
- if (type == "dropdown"){
- parseDropDown(data,description);
- return;
- }
-
- if (type == "field_close_on_enter") {
- parseFieldCloseOnEnter(data, description);
- return;
- }
-
- if (type == "pwdfield") {
- parsePwdField(data,description);
- return;
- }
-
- if ((type == "field") || (type == "textarea")){
- parseField(data,description,type);
- return;
- }
-
- if (type == "label") {
- parseLabel(data,description);
- return;
- }
-
- if (type == "vertlabel") {
- parseVertLabel(data,description);
- return;
- }
-
- if (type == "item_image_button") {
- parseItemImageButton(data,description);
- return;
- }
-
- if ((type == "image_button") || (type == "image_button_exit")) {
- parseImageButton(data,description,type);
- return;
- }
-
- if (type == "tabheader") {
- parseTabHeader(data,description);
- return;
- }
-
- if (type == "box") {
- parseBox(data,description);
- return;
- }
-
- if (type == "bgcolor") {
- parseBackgroundColor(data,description);
- return;
- }
-
- if (type == "listcolors") {
- parseListColors(data,description);
- return;
- }
-
- if (type == "tooltip") {
- parseTooltip(data,description);
- return;
- }
-
- if (type == "scrollbar") {
- parseScrollBar(data, description);
- return;
- }
-
- // Ignore others
- infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
- << std::endl;
-}
-
-void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
-{
- /* useless to regenerate without a screensize */
- if ((screensize.X <= 0) || (screensize.Y <= 0)) {
- return;
- }
-
- parserData mydata;
-
- //preserve tables
- for (auto &m_table : m_tables) {
- std::string tablename = m_table.first.fname;
- GUITable *table = m_table.second;
- mydata.table_dyndata[tablename] = table->getDynamicData();
- }
-
- //set focus
- if (!m_focused_element.empty())
- mydata.focused_fieldname = m_focused_element;
-
- //preserve focus
- gui::IGUIElement *focused_element = Environment->getFocus();
- if (focused_element && focused_element->getParent() == this) {
- s32 focused_id = focused_element->getID();
- if (focused_id > 257) {
- for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
- if (field.fid == focused_id) {
- mydata.focused_fieldname = field.fname;
- break;
- }
- }
- }
- }
-
- // Remove children
- removeChildren();
-
- for (auto &table_it : m_tables) {
- table_it.second->drop();
- }
-
- mydata.size= v2s32(100,100);
- mydata.screensize = screensize;
- mydata.offset = v2f32(0.5f, 0.5f);
- mydata.anchor = v2f32(0.5f, 0.5f);
-
- // Base position of contents of form
- mydata.basepos = getBasePos();
-
- /* Convert m_init_draw_spec to m_inventorylists */
-
- m_inventorylists.clear();
- m_images.clear();
- m_backgrounds.clear();
- m_itemimages.clear();
- m_tables.clear();
- m_checkboxes.clear();
- m_scrollbars.clear();
- m_fields.clear();
- m_boxes.clear();
- m_tooltips.clear();
- m_inventory_rings.clear();
- m_static_texts.clear();
- m_dropdowns.clear();
-
- m_bgfullscreen = false;
-
- {
- v3f formspec_bgcolor = g_settings->getV3F("formspec_default_bg_color");
- m_bgcolor = video::SColor(
- (u8) clamp_u8(g_settings->getS32("formspec_default_bg_opacity")),
- clamp_u8(myround(formspec_bgcolor.X)),
- clamp_u8(myround(formspec_bgcolor.Y)),
- clamp_u8(myround(formspec_bgcolor.Z))
- );
- }
-
- {
- v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color");
- m_fullscreen_bgcolor = video::SColor(
- (u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")),
- clamp_u8(myround(formspec_bgcolor.X)),
- clamp_u8(myround(formspec_bgcolor.Y)),
- clamp_u8(myround(formspec_bgcolor.Z))
- );
- }
-
-
- m_slotbg_n = video::SColor(255,128,128,128);
- m_slotbg_h = video::SColor(255,192,192,192);
-
- m_default_tooltip_bgcolor = video::SColor(255,110,130,60);
- m_default_tooltip_color = video::SColor(255,255,255,255);
-
- m_slotbordercolor = video::SColor(200,0,0,0);
- m_slotborder = false;
-
- // Add tooltip
- {
- assert(!m_tooltip_element);
- // Note: parent != this so that the tooltip isn't clipped by the menu rectangle
- m_tooltip_element = addStaticText(Environment, L"",core::rect<s32>(0,0,110,18));
- m_tooltip_element->enableOverrideColor(true);
- m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
- m_tooltip_element->setDrawBackground(true);
- m_tooltip_element->setDrawBorder(true);
- m_tooltip_element->setOverrideColor(m_default_tooltip_color);
- m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
- m_tooltip_element->setWordWrap(false);
- //we're not parent so no autograb for this one!
- m_tooltip_element->grab();
- }
-
- std::vector<std::string> elements = split(m_formspec_string,']');
- unsigned int i = 0;
-
- /* try to read version from first element only */
- if (!elements.empty()) {
- if ( parseVersionDirect(elements[0]) ) {
- i++;
- }
- }
-
- /* we need size first in order to calculate image scale */
- mydata.explicit_size = false;
- for (; i< elements.size(); i++) {
- if (!parseSizeDirect(&mydata, elements[i])) {
- break;
- }
- }
-
- /* "position" element is always after "size" element if it used */
- for (; i< elements.size(); i++) {
- if (!parsePositionDirect(&mydata, elements[i])) {
- break;
- }
- }
-
- /* "anchor" element is always after "position" (or "size" element) if it used */
- for (; i< elements.size(); i++) {
- if (!parseAnchorDirect(&mydata, elements[i])) {
- break;
- }
- }
-
-
- if (mydata.explicit_size) {
- // compute scaling for specified form size
- if (m_lock) {
- v2u32 current_screensize = RenderingEngine::get_video_driver()->getScreenSize();
- v2u32 delta = current_screensize - m_lockscreensize;
-
- if (current_screensize.Y > m_lockscreensize.Y)
- delta.Y /= 2;
- else
- delta.Y = 0;
-
- if (current_screensize.X > m_lockscreensize.X)
- delta.X /= 2;
- else
- delta.X = 0;
-
- offset = v2s32(delta.X,delta.Y);
-
- mydata.screensize = m_lockscreensize;
- } else {
- offset = v2s32(0,0);
- }
-
- double gui_scaling = g_settings->getFloat("gui_scaling");
- double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
-
- double use_imgsize;
- if (m_lock) {
- // In fixed-size mode, inventory image size
- // is 0.53 inch multiplied by the gui_scaling
- // config parameter. This magic size is chosen
- // to make the main menu (15.5 inventory images
- // wide, including border) just fit into the
- // default window (800 pixels wide) at 96 DPI
- // and default scaling (1.00).
- use_imgsize = 0.5555 * screen_dpi * gui_scaling;
- } else {
- // In variable-size mode, we prefer to make the
- // inventory image size 1/15 of screen height,
- // multiplied by the gui_scaling config parameter.
- // If the preferred size won't fit the whole
- // form on the screen, either horizontally or
- // vertically, then we scale it down to fit.
- // (The magic numbers in the computation of what
- // fits arise from the scaling factors in the
- // following stanza, including the form border,
- // help text space, and 0.1 inventory slot spare.)
- // However, a minimum size is also set, that
- // the image size can't be less than 0.3 inch
- // multiplied by gui_scaling, even if this means
- // the form doesn't fit the screen.
- double prefer_imgsize = mydata.screensize.Y / 15 *
- gui_scaling;
- double fitx_imgsize = mydata.screensize.X /
- ((5.0/4.0) * (0.5 + mydata.invsize.X));
- double fity_imgsize = mydata.screensize.Y /
- ((15.0/13.0) * (0.85 * mydata.invsize.Y));
- double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
- double min_imgsize = 0.3 * screen_dpi * gui_scaling;
- use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize,
- MYMIN(fitx_imgsize, fity_imgsize)));
- }
-
- // Everything else is scaled in proportion to the
- // inventory image size. The inventory slot spacing
- // is 5/4 image size horizontally and 15/13 image size
- // vertically. The padding around the form (incorporating
- // the border of the outer inventory slots) is 3/8
- // image size. Font height (baseline to baseline)
- // is 2/5 vertical inventory slot spacing, and button
- // half-height is 7/8 of font height.
- imgsize = v2s32(use_imgsize, use_imgsize);
- spacing = v2s32(use_imgsize*5.0/4, use_imgsize*15.0/13);
- padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8);
- m_btn_height = use_imgsize*15.0/13 * 0.35;
-
- m_font = g_fontengine->getFont();
-
- mydata.size = v2s32(
- padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
- padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
- );
- DesiredRect = mydata.rect = core::rect<s32>(
- (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X,
- (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y,
- (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * (f32)mydata.size.X) + offset.X,
- (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * (f32)mydata.size.Y) + offset.Y
- );
- } else {
- // Non-size[] form must consist only of text fields and
- // implicit "Proceed" button. Use default font, and
- // temporary form size which will be recalculated below.
- m_font = g_fontengine->getFont();
- m_btn_height = font_line_height(m_font) * 0.875;
- DesiredRect = core::rect<s32>(
- (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * 580.0),
- (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * 300.0),
- (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * 580.0),
- (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * 300.0)
- );
- }
- recalculateAbsolutePosition(false);
- mydata.basepos = getBasePos();
- m_tooltip_element->setOverrideFont(m_font);
-
- gui::IGUISkin* skin = Environment->getSkin();
- sanity_check(skin);
- gui::IGUIFont *old_font = skin->getFont();
- skin->setFont(m_font);
-
- pos_offset = v2s32();
- for (; i< elements.size(); i++) {
- parseElement(&mydata, elements[i]);
- }
-
- if (!container_stack.empty()) {
- errorstream << "Invalid formspec string: container was never closed!"
- << std::endl;
- }
-
- // If there are fields without explicit size[], add a "Proceed"
- // button and adjust size to fit all the fields.
- if (!m_fields.empty() && !mydata.explicit_size) {
- mydata.rect = core::rect<s32>(
- mydata.screensize.X/2 - 580/2,
- mydata.screensize.Y/2 - 300/2,
- mydata.screensize.X/2 + 580/2,
- mydata.screensize.Y/2 + 240/2+(m_fields.size()*60)
- );
- DesiredRect = mydata.rect;
- recalculateAbsolutePosition(false);
- mydata.basepos = getBasePos();
-
- {
- v2s32 pos = mydata.basepos;
- pos.Y = ((m_fields.size()+2)*60);
-
- v2s32 size = DesiredRect.getSize();
- mydata.rect =
- core::rect<s32>(size.X/2-70, pos.Y,
- (size.X/2-70)+140, pos.Y + (m_btn_height*2));
- const wchar_t *text = wgettext("Proceed");
- Environment->addButton(mydata.rect, this, 257, text);
- delete[] text;
- }
-
- }
-
- //set initial focus if parser didn't set it
- focused_element = Environment->getFocus();
- if (!focused_element
- || !isMyChild(focused_element)
- || focused_element->getType() == gui::EGUIET_TAB_CONTROL)
- setInitialFocus();
-
- skin->setFont(old_font);
-}
-
-#ifdef __ANDROID__
-bool GUIFormSpecMenu::getAndroidUIInput()
-{
- /* no dialog shown */
- if (m_JavaDialogFieldName == "") {
- return false;
- }
-
- /* still waiting */
- if (porting::getInputDialogState() == -1) {
- return true;
- }
-
- std::string fieldname = m_JavaDialogFieldName;
- m_JavaDialogFieldName = "";
-
- /* no value abort dialog processing */
- if (porting::getInputDialogState() != 0) {
- return false;
- }
-
- for(std::vector<FieldSpec>::iterator iter = m_fields.begin();
- iter != m_fields.end(); ++iter) {
-
- if (iter->fname != fieldname) {
- continue;
- }
- IGUIElement* tochange = getElementFromId(iter->fid);
-
- if (tochange == 0) {
- return false;
- }
-
- if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) {
- return false;
- }
-
- std::string text = porting::getInputDialogValue();
-
- ((gui::IGUIEditBox*) tochange)->
- setText(utf8_to_wide(text).c_str());
- }
- return false;
-}
-#endif
-
-GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
-{
- core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
-
- for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
- for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
- s32 item_i = i + s.start_item_i;
- s32 x = (i%s.geom.X) * spacing.X;
- s32 y = (i/s.geom.X) * spacing.Y;
- v2s32 p0(x,y);
- core::rect<s32> rect = imgrect + s.pos + p0;
- if(rect.isPointInside(p))
- {
- return ItemSpec(s.inventoryloc, s.listname, item_i);
- }
- }
- }
-
- return ItemSpec(InventoryLocation(), "", -1);
-}
-
-void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase,
- bool &item_hovered)
-{
- video::IVideoDriver* driver = Environment->getVideoDriver();
-
- Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
- if(!inv){
- warningstream<<"GUIFormSpecMenu::drawList(): "
- <<"The inventory location "
- <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
- <<std::endl;
- return;
- }
- InventoryList *ilist = inv->getList(s.listname);
- if(!ilist){
- warningstream<<"GUIFormSpecMenu::drawList(): "
- <<"The inventory list \""<<s.listname<<"\" @ \""
- <<s.inventoryloc.dump()<<"\" doesn't exist"
- <<std::endl;
- return;
- }
-
- core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
-
- for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
- {
- s32 item_i = i + s.start_item_i;
- if(item_i >= (s32) ilist->getSize())
- break;
- s32 x = (i%s.geom.X) * spacing.X;
- s32 y = (i/s.geom.X) * spacing.Y;
- v2s32 p(x,y);
- core::rect<s32> rect = imgrect + s.pos + p;
- ItemStack item;
- if(ilist)
- item = ilist->getItem(item_i);
-
- bool selected = m_selected_item
- && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
- && m_selected_item->listname == s.listname
- && m_selected_item->i == item_i;
- bool hovering = rect.isPointInside(m_pointer);
- ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED :
- (hovering ? IT_ROT_HOVERED : IT_ROT_NONE);
-
- if (phase == 0) {
- if (hovering) {
- item_hovered = true;
- driver->draw2DRectangle(m_slotbg_h, rect, &AbsoluteClippingRect);
- } else {
- driver->draw2DRectangle(m_slotbg_n, rect, &AbsoluteClippingRect);
- }
- }
-
- //Draw inv slot borders
- if (m_slotborder) {
- s32 x1 = rect.UpperLeftCorner.X;
- s32 y1 = rect.UpperLeftCorner.Y;
- s32 x2 = rect.LowerRightCorner.X;
- s32 y2 = rect.LowerRightCorner.Y;
- s32 border = 1;
- driver->draw2DRectangle(m_slotbordercolor,
- core::rect<s32>(v2s32(x1 - border, y1 - border),
- v2s32(x2 + border, y1)), NULL);
- driver->draw2DRectangle(m_slotbordercolor,
- core::rect<s32>(v2s32(x1 - border, y2),
- v2s32(x2 + border, y2 + border)), NULL);
- driver->draw2DRectangle(m_slotbordercolor,
- core::rect<s32>(v2s32(x1 - border, y1),
- v2s32(x1, y2)), NULL);
- driver->draw2DRectangle(m_slotbordercolor,
- core::rect<s32>(v2s32(x2, y1),
- v2s32(x2 + border, y2)), NULL);
- }
-
- if(phase == 1)
- {
- // Draw item stack
- if(selected)
- {
- item.takeItem(m_selected_amount);
- }
- if(!item.empty())
- {
- drawItemStack(driver, m_font, item,
- rect, &AbsoluteClippingRect, m_client,
- rotation_kind);
- }
-
- // Draw tooltip
- std::wstring tooltip_text;
- if (hovering && !m_selected_item) {
- const std::string &desc = item.metadata.getString("description");
- if (desc.empty())
- tooltip_text =
- utf8_to_wide(item.getDefinition(m_client->idef()).description);
- else
- tooltip_text = utf8_to_wide(desc);
-
- if (!item.name.empty()) {
- if (tooltip_text.empty())
- tooltip_text = utf8_to_wide(item.name);
- if (m_tooltip_append_itemname)
- tooltip_text += utf8_to_wide(" [" + item.name + "]");
- }
- }
- if (!tooltip_text.empty()) {
- showTooltip(tooltip_text, m_default_tooltip_color,
- m_default_tooltip_bgcolor);
- }
- }
- }
-}
-
-void GUIFormSpecMenu::drawSelectedItem()
-{
- video::IVideoDriver* driver = Environment->getVideoDriver();
-
- if (!m_selected_item) {
- drawItemStack(driver, m_font, ItemStack(),
- core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
- NULL, m_client, IT_ROT_DRAGGED);
- return;
- }
-
- Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
- sanity_check(inv);
- InventoryList *list = inv->getList(m_selected_item->listname);
- sanity_check(list);
- ItemStack stack = list->getItem(m_selected_item->i);
- stack.count = m_selected_amount;
-
- core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
- core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
- rect.constrainTo(driver->getViewPort());
- drawItemStack(driver, m_font, stack, rect, NULL, m_client, IT_ROT_DRAGGED);
-}
-
-void GUIFormSpecMenu::drawMenu()
-{
- if (m_form_src) {
- const std::string &newform = m_form_src->getForm();
- if (newform != m_formspec_string) {
- m_formspec_string = newform;
- regenerateGui(m_screensize_old);
- }
- }
-
- gui::IGUISkin* skin = Environment->getSkin();
- sanity_check(skin != NULL);
- gui::IGUIFont *old_font = skin->getFont();
- skin->setFont(m_font);
-
- updateSelectedItem();
-
- video::IVideoDriver* driver = Environment->getVideoDriver();
-
- v2u32 screenSize = driver->getScreenSize();
- core::rect<s32> allbg(0, 0, screenSize.X, screenSize.Y);
-
- if (m_bgfullscreen)
- driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg);
- else
- driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);
-
- m_tooltip_element->setVisible(false);
-
- /*
- Draw backgrounds
- */
- for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_backgrounds) {
- video::ITexture *texture = m_tsrc->getTexture(spec.name);
-
- if (texture != 0) {
- // Image size on screen
- core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
- // Image rectangle on screen
- core::rect<s32> rect = imgrect + spec.pos;
-
- if (spec.clip) {
- core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
- rect = core::rect<s32>(AbsoluteRect.UpperLeftCorner.X - spec.pos.X,
- AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y,
- AbsoluteRect.UpperLeftCorner.X + absrec_size.Width + spec.pos.X,
- AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
- }
-
- const video::SColor color(255,255,255,255);
- const video::SColor colors[] = {color,color,color,color};
- draw2DImageFilterScaled(driver, texture, rect,
- core::rect<s32>(core::position2d<s32>(0,0),
- core::dimension2di(texture->getOriginalSize())),
- NULL/*&AbsoluteClippingRect*/, colors, true);
- } else {
- errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
- errorstream << "\t" << spec.name << std::endl;
- }
- }
-
- /*
- Draw Boxes
- */
- for (const GUIFormSpecMenu::BoxDrawSpec &spec : m_boxes) {
- irr::video::SColor todraw = spec.color;
-
- todraw.setAlpha(140);
-
- core::rect<s32> rect(spec.pos.X,spec.pos.Y,
- spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y);
-
- driver->draw2DRectangle(todraw, rect, 0);
- }
-
- /*
- Call base class
- */
- gui::IGUIElement::draw();
-
- /*
- Draw images
- */
- for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_images) {
- video::ITexture *texture = m_tsrc->getTexture(spec.name);
-
- if (texture != 0) {
- const core::dimension2d<u32>& img_origsize = texture->getOriginalSize();
- // Image size on screen
- core::rect<s32> imgrect;
-
- if (spec.scale)
- imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y);
- else {
-
- imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height);
- }
- // Image rectangle on screen
- core::rect<s32> rect = imgrect + spec.pos;
- const video::SColor color(255,255,255,255);
- const video::SColor colors[] = {color,color,color,color};
- draw2DImageFilterScaled(driver, texture, rect,
- core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
- NULL/*&AbsoluteClippingRect*/, colors, true);
- }
- else {
- errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl;
- errorstream << "\t" << spec.name << std::endl;
- }
- }
-
- /*
- Draw item images
- */
- for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_itemimages) {
- if (m_client == 0)
- break;
-
- IItemDefManager *idef = m_client->idef();
- ItemStack item;
- item.deSerialize(spec.item_name, idef);
- core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
- // Viewport rectangle on screen
- core::rect<s32> rect = imgrect + spec.pos;
- if (spec.parent_button && spec.parent_button->isPressed()) {
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- rect += core::dimension2d<s32>(
- 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
-#else
- rect += core::dimension2d<s32>(
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
-#endif
- }
- drawItemStack(driver, m_font, item, rect, &AbsoluteClippingRect,
- m_client, IT_ROT_NONE);
- }
-
- /*
- Draw items
- Phase 0: Item slot rectangles
- Phase 1: Item images; prepare tooltip
- */
- bool item_hovered = false;
- int start_phase = 0;
- for (int phase = start_phase; phase <= 1; phase++) {
- for (const GUIFormSpecMenu::ListDrawSpec &spec : m_inventorylists) {
- drawList(spec, phase, item_hovered);
- }
- }
- if (!item_hovered) {
- drawItemStack(driver, m_font, ItemStack(),
- core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
- NULL, m_client, IT_ROT_HOVERED);
- }
-
-/* TODO find way to show tooltips on touchscreen */
-#ifndef HAVE_TOUCHSCREENGUI
- m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition();
-#endif
-
- /*
- Draw static text elements
- */
- for (const GUIFormSpecMenu::StaticTextSpec &spec : m_static_texts) {
- core::rect<s32> rect = spec.rect;
- if (spec.parent_button && spec.parent_button->isPressed()) {
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- rect += core::dimension2d<s32>(
- 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
-#else
- // Use image offset instead of text's because its a bit smaller
- // and fits better, also TEXT_OFFSET_X is always 0
- rect += core::dimension2d<s32>(
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
-#endif
- }
- video::SColor color(255, 255, 255, 255);
- m_font->draw(spec.text.c_str(), rect, color, true, true, &rect);
- }
-
- /*
- Draw fields/buttons tooltips
- */
- gui::IGUIElement *hovered =
- Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
-
- if (hovered != NULL) {
- s32 id = hovered->getID();
-
- u64 delta = 0;
- if (id == -1) {
- m_old_tooltip_id = id;
- } else {
- if (id == m_old_tooltip_id) {
- delta = porting::getDeltaMs(m_hovered_time, porting::getTimeMs());
- } else {
- m_hovered_time = porting::getTimeMs();
- m_old_tooltip_id = id;
- }
- }
-
- // Find and update the current tooltip
- if (id != -1 && delta >= m_tooltip_show_delay) {
- for (const FieldSpec &field : m_fields) {
-
- if (field.fid != id)
- continue;
-
- const std::wstring &text = m_tooltips[field.fname].tooltip;
- if (!text.empty())
- showTooltip(text, m_tooltips[field.fname].color,
- m_tooltips[field.fname].bgcolor);
-
- break;
- }
- }
- }
-
- m_tooltip_element->draw();
-
- /*
- Draw dragged item stack
- */
- drawSelectedItem();
-
- skin->setFont(old_font);
-}
-
-
-void GUIFormSpecMenu::showTooltip(const std::wstring &text,
- const irr::video::SColor &color, const irr::video::SColor &bgcolor)
-{
- const std::wstring ntext = translate_string(text);
- m_tooltip_element->setOverrideColor(color);
- m_tooltip_element->setBackgroundColor(bgcolor);
- setStaticText(m_tooltip_element, ntext.c_str());
-
- // Tooltip size and offset
- s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
-#if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1
- std::vector<std::wstring> text_rows = str_split(ntext, L'\n');
- s32 tooltip_height = m_tooltip_element->getTextHeight() * text_rows.size() + 5;
-#else
- s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
-#endif
- v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
- int tooltip_offset_x = m_btn_height;
- int tooltip_offset_y = m_btn_height;
-#ifdef __ANDROID__
- tooltip_offset_x *= 3;
- tooltip_offset_y = 0;
- if (m_pointer.X > (s32)screenSize.X / 2)
- tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
-#endif
-
- // Calculate and set the tooltip position
- s32 tooltip_x = m_pointer.X + tooltip_offset_x;
- s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
- if (tooltip_x + tooltip_width > (s32)screenSize.X)
- tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height;
- if (tooltip_y + tooltip_height > (s32)screenSize.Y)
- tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
-
- m_tooltip_element->setRelativePosition(
- core::rect<s32>(
- core::position2d<s32>(tooltip_x, tooltip_y),
- core::dimension2d<s32>(tooltip_width, tooltip_height)
- )
- );
-
- // Display the tooltip
- m_tooltip_element->setVisible(true);
- bringToFront(m_tooltip_element);
-}
-
-void GUIFormSpecMenu::updateSelectedItem()
-{
- // If the selected stack has become empty for some reason, deselect it.
- // If the selected stack has become inaccessible, deselect it.
- // If the selected stack has become smaller, adjust m_selected_amount.
- ItemStack selected = verifySelectedItem();
-
- // WARNING: BLACK MAGIC
- // See if there is a stack suited for our current guess.
- // If such stack does not exist, clear the guess.
- if (!m_selected_content_guess.name.empty() &&
- selected.name == m_selected_content_guess.name &&
- selected.count == m_selected_content_guess.count){
- // Selected item fits the guess. Skip the black magic.
- } else if (!m_selected_content_guess.name.empty()) {
- bool found = false;
- for(u32 i=0; i<m_inventorylists.size() && !found; i++){
- const ListDrawSpec &s = m_inventorylists[i];
- Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
- if(!inv)
- continue;
- InventoryList *list = inv->getList(s.listname);
- if(!list)
- continue;
- for(s32 i=0; i<s.geom.X*s.geom.Y && !found; i++){
- u32 item_i = i + s.start_item_i;
- if(item_i >= list->getSize())
- continue;
- ItemStack stack = list->getItem(item_i);
- if(stack.name == m_selected_content_guess.name &&
- stack.count == m_selected_content_guess.count){
- found = true;
- infostream<<"Client: Changing selected content guess to "
- <<s.inventoryloc.dump()<<" "<<s.listname
- <<" "<<item_i<<std::endl;
- delete m_selected_item;
- m_selected_item = new ItemSpec(s.inventoryloc, s.listname, item_i);
- m_selected_amount = stack.count;
- }
- }
- }
- if(!found){
- infostream<<"Client: Discarding selected content guess: "
- <<m_selected_content_guess.getItemString()<<std::endl;
- m_selected_content_guess.name = "";
- }
- }
-
- // If craftresult is nonempty and nothing else is selected, select it now.
- if(!m_selected_item)
- {
- for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
- if(s.listname == "craftpreview")
- {
- Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
- InventoryList *list = inv->getList("craftresult");
- if(list && list->getSize() >= 1 && !list->getItem(0).empty())
- {
- m_selected_item = new ItemSpec;
- m_selected_item->inventoryloc = s.inventoryloc;
- m_selected_item->listname = "craftresult";
- m_selected_item->i = 0;
- m_selected_amount = 0;
- m_selected_dragging = false;
- break;
- }
- }
- }
- }
-
- // If craftresult is selected, keep the whole stack selected
- if(m_selected_item && m_selected_item->listname == "craftresult")
- {
- m_selected_amount = verifySelectedItem().count;
- }
-}
-
-ItemStack GUIFormSpecMenu::verifySelectedItem()
-{
- // If the selected stack has become empty for some reason, deselect it.
- // If the selected stack has become inaccessible, deselect it.
- // If the selected stack has become smaller, adjust m_selected_amount.
- // Return the selected stack.
-
- if(m_selected_item)
- {
- if(m_selected_item->isValid())
- {
- Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
- if(inv)
- {
- InventoryList *list = inv->getList(m_selected_item->listname);
- if(list && (u32) m_selected_item->i < list->getSize())
- {
- ItemStack stack = list->getItem(m_selected_item->i);
- if(m_selected_amount > stack.count)
- m_selected_amount = stack.count;
- if(!stack.empty())
- return stack;
- }
- }
- }
-
- // selection was not valid
- delete m_selected_item;
- m_selected_item = NULL;
- m_selected_amount = 0;
- m_selected_dragging = false;
- }
- return ItemStack();
-}
-
-void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
-{
- if(m_text_dst)
- {
- StringMap fields;
-
- if (quitmode == quit_mode_accept) {
- fields["quit"] = "true";
- }
-
- if (quitmode == quit_mode_cancel) {
- fields["quit"] = "true";
- m_text_dst->gotText(fields);
- return;
- }
-
- if (current_keys_pending.key_down) {
- fields["key_down"] = "true";
- current_keys_pending.key_down = false;
- }
-
- if (current_keys_pending.key_up) {
- fields["key_up"] = "true";
- current_keys_pending.key_up = false;
- }
-
- if (current_keys_pending.key_enter) {
- fields["key_enter"] = "true";
- current_keys_pending.key_enter = false;
- }
-
- if (!current_field_enter_pending.empty()) {
- fields["key_enter_field"] = current_field_enter_pending;
- current_field_enter_pending = "";
- }
-
- if (current_keys_pending.key_escape) {
- fields["key_escape"] = "true";
- current_keys_pending.key_escape = false;
- }
-
- for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
- if(s.send) {
- std::string name = s.fname;
- if (s.ftype == f_Button) {
- fields[name] = wide_to_utf8(s.flabel);
- } else if (s.ftype == f_Table) {
- GUITable *table = getTable(s.fname);
- if (table) {
- fields[name] = table->checkEvent();
- }
- }
- else if(s.ftype == f_DropDown) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
- IGUIElement * element = getElementFromId(s.fid);
- gui::IGUIComboBox *e = NULL;
- if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
- e = static_cast<gui::IGUIComboBox*>(element);
- }
- s32 selected = e->getSelected();
- if (selected >= 0) {
- std::vector<std::string> *dropdown_values =
- getDropDownValues(s.fname);
- if (dropdown_values && selected < (s32)dropdown_values->size()) {
- fields[name] = (*dropdown_values)[selected];
- }
- }
- }
- else if (s.ftype == f_TabHeader) {
- // no dynamic cast possible due to some distributions shipped
- // without rttzi support in irrlicht
- IGUIElement * element = getElementFromId(s.fid);
- gui::IGUITabControl *e = NULL;
- if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
- e = static_cast<gui::IGUITabControl *>(element);
- }
-
- if (e != 0) {
- std::stringstream ss;
- ss << (e->getActiveTab() +1);
- fields[name] = ss.str();
- }
- }
- else if (s.ftype == f_CheckBox) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
- IGUIElement * element = getElementFromId(s.fid);
- gui::IGUICheckBox *e = NULL;
- if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
- e = static_cast<gui::IGUICheckBox*>(element);
- }
-
- if (e != 0) {
- if (e->isChecked())
- fields[name] = "true";
- else
- fields[name] = "false";
- }
- }
- else if (s.ftype == f_ScrollBar) {
- // no dynamic cast possible due to some distributions shipped
- // without rtti support in irrlicht
- IGUIElement * element = getElementFromId(s.fid);
- gui::IGUIScrollBar *e = NULL;
- if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) {
- e = static_cast<gui::IGUIScrollBar*>(element);
- }
-
- if (e != 0) {
- std::stringstream os;
- os << e->getPos();
- if (s.fdefault == L"Changed")
- fields[name] = "CHG:" + os.str();
- else
- fields[name] = "VAL:" + os.str();
- }
- }
- else
- {
- IGUIElement* e = getElementFromId(s.fid);
- if(e != NULL) {
- fields[name] = wide_to_utf8(e->getText());
- }
- }
- }
- }
-
- m_text_dst->gotText(fields);
- }
-}
-
-static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent)
-{
- while(tocheck != NULL) {
- if (tocheck == parent) {
- return true;
- }
- tocheck = tocheck->getParent();
- }
- return false;
-}
-
-bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
-{
- // The IGUITabControl renders visually using the skin's selected
- // font, which we override for the duration of form drawing,
- // but computes tab hotspots based on how it would have rendered
- // using the font that is selected at the time of button release.
- // To make these two consistent, temporarily override the skin's
- // font while the IGUITabControl is processing the event.
- if (event.EventType == EET_MOUSE_INPUT_EVENT &&
- event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
- s32 x = event.MouseInput.X;
- s32 y = event.MouseInput.Y;
- gui::IGUIElement *hovered =
- Environment->getRootGUIElement()->getElementFromPoint(
- core::position2d<s32>(x, y));
- if (hovered && isMyChild(hovered) &&
- hovered->getType() == gui::EGUIET_TAB_CONTROL) {
- gui::IGUISkin* skin = Environment->getSkin();
- sanity_check(skin != NULL);
- gui::IGUIFont *old_font = skin->getFont();
- skin->setFont(m_font);
- bool retval = hovered->OnEvent(event);
- skin->setFont(old_font);
- return retval;
- }
- }
-
- // Fix Esc/Return key being eaten by checkboxen and tables
- if(event.EventType==EET_KEY_INPUT_EVENT) {
- KeyPress kp(event.KeyInput);
- if (kp == EscapeKey || kp == CancelKey
- || kp == getKeySetting("keymap_inventory")
- || event.KeyInput.Key==KEY_RETURN) {
- gui::IGUIElement *focused = Environment->getFocus();
- if (focused && isMyChild(focused) &&
- (focused->getType() == gui::EGUIET_LIST_BOX ||
- focused->getType() == gui::EGUIET_CHECK_BOX)) {
- OnEvent(event);
- return true;
- }
- }
- }
- // Mouse wheel events: send to hovered element instead of focused
- if(event.EventType==EET_MOUSE_INPUT_EVENT
- && event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
- s32 x = event.MouseInput.X;
- s32 y = event.MouseInput.Y;
- gui::IGUIElement *hovered =
- Environment->getRootGUIElement()->getElementFromPoint(
- core::position2d<s32>(x, y));
- if (hovered && isMyChild(hovered)) {
- hovered->OnEvent(event);
- return true;
- }
- }
-
- if (event.EventType == EET_MOUSE_INPUT_EVENT) {
- s32 x = event.MouseInput.X;
- s32 y = event.MouseInput.Y;
- gui::IGUIElement *hovered =
- Environment->getRootGUIElement()->getElementFromPoint(
- core::position2d<s32>(x, y));
- if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
- m_old_tooltip_id = -1;
- }
- if (!isChild(hovered,this)) {
- if (DoubleClickDetection(event)) {
- return true;
- }
- }
- }
-
- #ifdef __ANDROID__
- // display software keyboard when clicking edit boxes
- if (event.EventType == EET_MOUSE_INPUT_EVENT
- && event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
- gui::IGUIElement *hovered =
- Environment->getRootGUIElement()->getElementFromPoint(
- core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
- if ((hovered) && (hovered->getType() == irr::gui::EGUIET_EDIT_BOX)) {
- bool retval = hovered->OnEvent(event);
- if (retval) {
- Environment->setFocus(hovered);
- }
- m_JavaDialogFieldName = getNameByID(hovered->getID());
- std::string message = gettext("Enter ");
- std::string label = wide_to_utf8(getLabelByID(hovered->getID()));
- if (label == "") {
- label = "text";
- }
- message += gettext(label) + ":";
-
- /* single line text input */
- int type = 2;
-
- /* multi line text input */
- if (((gui::IGUIEditBox*) hovered)->isMultiLineEnabled()) {
- type = 1;
- }
-
- /* passwords are always single line */
- if (((gui::IGUIEditBox*) hovered)->isPasswordBox()) {
- type = 3;
- }
-
- porting::showInputDialog(gettext("ok"), "",
- wide_to_utf8(((gui::IGUIEditBox*) hovered)->getText()),
- type);
- return retval;
- }
- }
-
- if (event.EventType == EET_TOUCH_INPUT_EVENT)
- {
- SEvent translated;
- memset(&translated, 0, sizeof(SEvent));
- translated.EventType = EET_MOUSE_INPUT_EVENT;
- gui::IGUIElement* root = Environment->getRootGUIElement();
-
- if (!root) {
- errorstream
- << "GUIFormSpecMenu::preprocessEvent unable to get root element"
- << std::endl;
- return false;
- }
- gui::IGUIElement* hovered = root->getElementFromPoint(
- core::position2d<s32>(
- event.TouchInput.X,
- event.TouchInput.Y));
-
- translated.MouseInput.X = event.TouchInput.X;
- translated.MouseInput.Y = event.TouchInput.Y;
- translated.MouseInput.Control = false;
-
- bool dont_send_event = false;
-
- if (event.TouchInput.touchedCount == 1) {
- switch (event.TouchInput.Event) {
- case ETIE_PRESSED_DOWN:
- m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
- translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
- translated.MouseInput.ButtonStates = EMBSM_LEFT;
- m_down_pos = m_pointer;
- break;
- case ETIE_MOVED:
- m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
- translated.MouseInput.Event = EMIE_MOUSE_MOVED;
- translated.MouseInput.ButtonStates = EMBSM_LEFT;
- break;
- case ETIE_LEFT_UP:
- translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
- translated.MouseInput.ButtonStates = 0;
- hovered = root->getElementFromPoint(m_down_pos);
- /* we don't have a valid pointer element use last
- * known pointer pos */
- translated.MouseInput.X = m_pointer.X;
- translated.MouseInput.Y = m_pointer.Y;
-
- /* reset down pos */
- m_down_pos = v2s32(0,0);
- break;
- default:
- dont_send_event = true;
- //this is not supposed to happen
- errorstream
- << "GUIFormSpecMenu::preprocessEvent unexpected usecase Event="
- << event.TouchInput.Event << std::endl;
- }
- } else if ( (event.TouchInput.touchedCount == 2) &&
- (event.TouchInput.Event == ETIE_PRESSED_DOWN) ) {
- hovered = root->getElementFromPoint(m_down_pos);
-
- translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
- translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT;
- translated.MouseInput.X = m_pointer.X;
- translated.MouseInput.Y = m_pointer.Y;
-
- if (hovered) {
- hovered->OnEvent(translated);
- }
-
- translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
- translated.MouseInput.ButtonStates = EMBSM_LEFT;
-
-
- if (hovered) {
- hovered->OnEvent(translated);
- }
- dont_send_event = true;
- }
- /* ignore unhandled 2 touch events ... accidental moving for example */
- else if (event.TouchInput.touchedCount == 2) {
- dont_send_event = true;
- }
- else if (event.TouchInput.touchedCount > 2) {
- errorstream
- << "GUIFormSpecMenu::preprocessEvent to many multitouch events "
- << event.TouchInput.touchedCount << " ignoring them" << std::endl;
- }
-
- if (dont_send_event) {
- return true;
- }
-
- /* check if translated event needs to be preprocessed again */
- if (preprocessEvent(translated)) {
- return true;
- }
- if (hovered) {
- grab();
- bool retval = hovered->OnEvent(translated);
-
- if (event.TouchInput.Event == ETIE_LEFT_UP) {
- /* reset pointer */
- m_pointer = v2s32(0,0);
- }
- drop();
- return retval;
- }
- }
- #endif
-
- if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
- /* TODO add a check like:
- if (event.JoystickEvent != joystick_we_listen_for)
- return false;
- */
- bool handled = m_joystick->handleEvent(event.JoystickEvent);
- if (handled) {
- if (m_joystick->wasKeyDown(KeyType::ESC)) {
- tryClose();
- } else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
- if (m_allowclose) {
- acceptInput(quit_mode_accept);
- quitMenu();
- }
- }
- }
- return handled;
- }
-
- return false;
-}
-
-/******************************************************************************/
-bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event)
-{
- /* The following code is for capturing double-clicks of the mouse button
- * and translating the double-click into an EET_KEY_INPUT_EVENT event
- * -- which closes the form -- under some circumstances.
- *
- * There have been many github issues reporting this as a bug even though it
- * was an intended feature. For this reason, remapping the double-click as
- * an ESC must be explicitly set when creating this class via the
- * /p remap_dbl_click parameter of the constructor.
- */
-
- if (!m_remap_dbl_click)
- return false;
-
- if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
- m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos;
- m_doubleclickdetect[0].time = m_doubleclickdetect[1].time;
-
- m_doubleclickdetect[1].pos = m_pointer;
- m_doubleclickdetect[1].time = porting::getTimeMs();
- }
- else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
- u64 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, porting::getTimeMs());
- if (delta > 400) {
- return false;
- }
-
- double squaredistance =
- m_doubleclickdetect[0].pos
- .getDistanceFromSQ(m_doubleclickdetect[1].pos);
-
- if (squaredistance > (30*30)) {
- return false;
- }
-
- SEvent* translated = new SEvent();
- assert(translated != 0);
- //translate doubleclick to escape
- memset(translated, 0, sizeof(SEvent));
- translated->EventType = irr::EET_KEY_INPUT_EVENT;
- translated->KeyInput.Key = KEY_ESCAPE;
- translated->KeyInput.Control = false;
- translated->KeyInput.Shift = false;
- translated->KeyInput.PressedDown = true;
- translated->KeyInput.Char = 0;
- OnEvent(*translated);
-
- // no need to send the key up event as we're already deleted
- // and no one else did notice this event
- delete translated;
- return true;
- }
-
- return false;
-}
-
-void GUIFormSpecMenu::tryClose()
-{
- if (m_allowclose) {
- doPause = false;
- acceptInput(quit_mode_cancel);
- quitMenu();
- } else {
- m_text_dst->gotText(L"MenuQuit");
- }
-}
-
-bool GUIFormSpecMenu::OnEvent(const SEvent& event)
-{
- if (event.EventType==EET_KEY_INPUT_EVENT) {
- KeyPress kp(event.KeyInput);
- if (event.KeyInput.PressedDown && (
- (kp == EscapeKey) || (kp == CancelKey) ||
- ((m_client != NULL) && (kp == getKeySetting("keymap_inventory"))))) {
- tryClose();
- return true;
- }
-
- if (m_client != NULL && event.KeyInput.PressedDown &&
- (kp == getKeySetting("keymap_screenshot"))) {
- m_client->makeScreenshot();
- }
- if (event.KeyInput.PressedDown &&
- (event.KeyInput.Key==KEY_RETURN ||
- event.KeyInput.Key==KEY_UP ||
- event.KeyInput.Key==KEY_DOWN)
- ) {
- switch (event.KeyInput.Key) {
- case KEY_RETURN:
- current_keys_pending.key_enter = true;
- break;
- case KEY_UP:
- current_keys_pending.key_up = true;
- break;
- case KEY_DOWN:
- current_keys_pending.key_down = true;
- break;
- break;
- default:
- //can't happen at all!
- FATAL_ERROR("Reached a source line that can't ever been reached");
- break;
- }
- if (current_keys_pending.key_enter && m_allowclose) {
- acceptInput(quit_mode_accept);
- quitMenu();
- } else {
- acceptInput();
- }
- return true;
- }
-
- }
-
- /* Mouse event other than movement, or crossing the border of inventory
- field while holding right mouse button
- */
- if (event.EventType == EET_MOUSE_INPUT_EVENT &&
- (event.MouseInput.Event != EMIE_MOUSE_MOVED ||
- (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
- event.MouseInput.isRightPressed() &&
- getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) {
-
- // Get selected item and hovered/clicked item (s)
-
- m_old_tooltip_id = -1;
- updateSelectedItem();
- ItemSpec s = getItemAtPos(m_pointer);
-
- Inventory *inv_selected = NULL;
- Inventory *inv_s = NULL;
- InventoryList *list_s = NULL;
-
- if (m_selected_item) {
- inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
- sanity_check(inv_selected);
- sanity_check(inv_selected->getList(m_selected_item->listname) != NULL);
- }
-
- u32 s_count = 0;
-
- if (s.isValid())
- do { // breakable
- inv_s = m_invmgr->getInventory(s.inventoryloc);
-
- if (!inv_s) {
- errorstream << "InventoryMenu: The selected inventory location "
- << "\"" << s.inventoryloc.dump() << "\" doesn't exist"
- << std::endl;
- s.i = -1; // make it invalid again
- break;
- }
-
- list_s = inv_s->getList(s.listname);
- if (list_s == NULL) {
- verbosestream << "InventoryMenu: The selected inventory list \""
- << s.listname << "\" does not exist" << std::endl;
- s.i = -1; // make it invalid again
- break;
- }
-
- if ((u32)s.i >= list_s->getSize()) {
- infostream << "InventoryMenu: The selected inventory list \""
- << s.listname << "\" is too small (i=" << s.i << ", size="
- << list_s->getSize() << ")" << std::endl;
- s.i = -1; // make it invalid again
- break;
- }
-
- s_count = list_s->getItem(s.i).count;
- } while(0);
-
- bool identical = (m_selected_item != NULL) && s.isValid() &&
- (inv_selected == inv_s) &&
- (m_selected_item->listname == s.listname) &&
- (m_selected_item->i == s.i);
-
- // buttons: 0 = left, 1 = right, 2 = middle
- // up/down: 0 = down (press), 1 = up (release), 2 = unknown event, -1 movement
- int button = 0;
- int updown = 2;
- if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
- { button = 0; updown = 0; }
- else if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
- { button = 1; updown = 0; }
- else if (event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
- { button = 2; updown = 0; }
- else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
- { button = 0; updown = 1; }
- else if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
- { button = 1; updown = 1; }
- else if (event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP)
- { button = 2; updown = 1; }
- else if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
- { updown = -1;}
-
- // Set this number to a positive value to generate a move action
- // from m_selected_item to s.
- u32 move_amount = 0;
-
- // Set this number to a positive value to generate a move action
- // from s to the next inventory ring.
- u32 shift_move_amount = 0;
-
- // Set this number to a positive value to generate a drop action
- // from m_selected_item.
- u32 drop_amount = 0;
-
- // Set this number to a positive value to generate a craft action at s.
- u32 craft_amount = 0;
-
- if (updown == 0) {
- // Some mouse button has been pressed
-
- //infostream<<"Mouse button "<<button<<" pressed at p=("
- // <<p.X<<","<<p.Y<<")"<<std::endl;
-
- m_selected_dragging = false;
-
- if (s.isValid() && s.listname == "craftpreview") {
- // Craft preview has been clicked: craft
- craft_amount = (button == 2 ? 10 : 1);
- } else if (m_selected_item == NULL) {
- if (s_count != 0) {
- // Non-empty stack has been clicked: select or shift-move it
- m_selected_item = new ItemSpec(s);
-
- u32 count;
- if (button == 1) // right
- count = (s_count + 1) / 2;
- else if (button == 2) // middle
- count = MYMIN(s_count, 10);
- else // left
- count = s_count;
-
- if (!event.MouseInput.Shift) {
- // no shift: select item
- m_selected_amount = count;
- m_selected_dragging = true;
- m_auto_place = false;
- } else {
- // shift pressed: move item
- if (button != 1)
- shift_move_amount = count;
- else // count of 1 at left click like after drag & drop
- shift_move_amount = 1;
- }
- }
- } else { // m_selected_item != NULL
- assert(m_selected_amount >= 1);
-
- if (s.isValid()) {
- // Clicked a slot: move
- if (button == 1) // right
- move_amount = 1;
- else if (button == 2) // middle
- move_amount = MYMIN(m_selected_amount, 10);
- else // left
- move_amount = m_selected_amount;
-
- if (identical) {
- if (move_amount >= m_selected_amount)
- m_selected_amount = 0;
- else
- m_selected_amount -= move_amount;
- move_amount = 0;
- }
- }
- else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) {
- // Clicked outside of the window: drop
- if (button == 1) // right
- drop_amount = 1;
- else if (button == 2) // middle
- drop_amount = MYMIN(m_selected_amount, 10);
- else // left
- drop_amount = m_selected_amount;
- }
- }
- }
- else if (updown == 1) {
- // Some mouse button has been released
-
- //infostream<<"Mouse button "<<button<<" released at p=("
- // <<p.X<<","<<p.Y<<")"<<std::endl;
-
- if (m_selected_item != NULL && m_selected_dragging && s.isValid()) {
- if (!identical) {
- // Dragged to different slot: move all selected
- move_amount = m_selected_amount;
- }
- } else if (m_selected_item != NULL && m_selected_dragging &&
- !(getAbsoluteClippingRect().isPointInside(m_pointer))) {
- // Dragged outside of window: drop all selected
- drop_amount = m_selected_amount;
- }
-
- m_selected_dragging = false;
- // Keep track of whether the mouse button be released
- // One click is drag without dropping. Click + release
- // + click changes to drop item when moved mode
- if (m_selected_item)
- m_auto_place = true;
- } else if (updown == -1) {
- // Mouse has been moved and rmb is down and mouse pointer just
- // entered a new inventory field (checked in the entry-if, this
- // is the only action here that is generated by mouse movement)
- if (m_selected_item != NULL && s.isValid()) {
- // Move 1 item
- // TODO: middle mouse to move 10 items might be handy
- if (m_auto_place) {
- // Only move an item if the destination slot is empty
- // or contains the same item type as what is going to be
- // moved
- InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
- InventoryList *list_to = list_s;
- assert(list_from && list_to);
- ItemStack stack_from = list_from->getItem(m_selected_item->i);
- ItemStack stack_to = list_to->getItem(s.i);
- if (stack_to.empty() || stack_to.name == stack_from.name)
- move_amount = 1;
- }
- }
- }
-
- // Possibly send inventory action to server
- if (move_amount > 0) {
- // Send IAction::Move
-
- assert(m_selected_item && m_selected_item->isValid());
- assert(s.isValid());
-
- assert(inv_selected && inv_s);
- InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
- InventoryList *list_to = list_s;
- assert(list_from && list_to);
- ItemStack stack_from = list_from->getItem(m_selected_item->i);
- ItemStack stack_to = list_to->getItem(s.i);
-
- // Check how many items can be moved
- move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
- ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
- // If source stack cannot be added to destination stack at all,
- // they are swapped
- if ((leftover.count == stack_from.count) &&
- (leftover.name == stack_from.name)) {
- m_selected_amount = stack_to.count;
- // In case the server doesn't directly swap them but instead
- // moves stack_to somewhere else, set this
- m_selected_content_guess = stack_to;
- m_selected_content_guess_inventory = s.inventoryloc;
- }
- // Source stack goes fully into destination stack
- else if (leftover.empty()) {
- m_selected_amount -= move_amount;
- m_selected_content_guess = ItemStack(); // Clear
- }
- // Source stack goes partly into destination stack
- else {
- move_amount -= leftover.count;
- m_selected_amount -= move_amount;
- m_selected_content_guess = ItemStack(); // Clear
- }
-
- infostream << "Handing IAction::Move to manager" << std::endl;
- IMoveAction *a = new IMoveAction();
- a->count = move_amount;
- a->from_inv = m_selected_item->inventoryloc;
- a->from_list = m_selected_item->listname;
- a->from_i = m_selected_item->i;
- a->to_inv = s.inventoryloc;
- a->to_list = s.listname;
- a->to_i = s.i;
- m_invmgr->inventoryAction(a);
- } else if (shift_move_amount > 0) {
- u32 mis = m_inventory_rings.size();
- u32 i = 0;
- for (; i < mis; i++) {
- const ListRingSpec &sp = m_inventory_rings[i];
- if (sp.inventoryloc == s.inventoryloc
- && sp.listname == s.listname)
- break;
- }
- do {
- if (i >= mis) // if not found
- break;
- u32 to_inv_ind = (i + 1) % mis;
- const ListRingSpec &to_inv_sp = m_inventory_rings[to_inv_ind];
- InventoryList *list_from = list_s;
- if (!s.isValid())
- break;
- Inventory *inv_to = m_invmgr->getInventory(to_inv_sp.inventoryloc);
- if (!inv_to)
- break;
- InventoryList *list_to = inv_to->getList(to_inv_sp.listname);
- if (!list_to)
- break;
- ItemStack stack_from = list_from->getItem(s.i);
- assert(shift_move_amount <= stack_from.count);
- if (m_client->getProtoVersion() >= 25) {
- infostream << "Handing IAction::Move to manager" << std::endl;
- IMoveAction *a = new IMoveAction();
- a->count = shift_move_amount;
- a->from_inv = s.inventoryloc;
- a->from_list = s.listname;
- a->from_i = s.i;
- a->to_inv = to_inv_sp.inventoryloc;
- a->to_list = to_inv_sp.listname;
- a->move_somewhere = true;
- m_invmgr->inventoryAction(a);
- } else {
- // find a place (or more than one) to add the new item
- u32 ilt_size = list_to->getSize();
- ItemStack leftover;
- for (u32 slot_to = 0; slot_to < ilt_size
- && shift_move_amount > 0; slot_to++) {
- list_to->itemFits(slot_to, stack_from, &leftover);
- if (leftover.count < stack_from.count) {
- infostream << "Handing IAction::Move to manager" << std::endl;
- IMoveAction *a = new IMoveAction();
- a->count = MYMIN(shift_move_amount,
- (u32) (stack_from.count - leftover.count));
- shift_move_amount -= a->count;
- a->from_inv = s.inventoryloc;
- a->from_list = s.listname;
- a->from_i = s.i;
- a->to_inv = to_inv_sp.inventoryloc;
- a->to_list = to_inv_sp.listname;
- a->to_i = slot_to;
- m_invmgr->inventoryAction(a);
- stack_from = leftover;
- }
- }
- }
- } while (0);
- } else if (drop_amount > 0) {
- m_selected_content_guess = ItemStack(); // Clear
-
- // Send IAction::Drop
-
- assert(m_selected_item && m_selected_item->isValid());
- assert(inv_selected);
- InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
- assert(list_from);
- ItemStack stack_from = list_from->getItem(m_selected_item->i);
-
- // Check how many items can be dropped
- drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
- assert(drop_amount > 0 && drop_amount <= m_selected_amount);
- m_selected_amount -= drop_amount;
-
- infostream << "Handing IAction::Drop to manager" << std::endl;
- IDropAction *a = new IDropAction();
- a->count = drop_amount;
- a->from_inv = m_selected_item->inventoryloc;
- a->from_list = m_selected_item->listname;
- a->from_i = m_selected_item->i;
- m_invmgr->inventoryAction(a);
- } else if (craft_amount > 0) {
- m_selected_content_guess = ItemStack(); // Clear
-
- // Send IAction::Craft
-
- assert(s.isValid());
- assert(inv_s);
-
- infostream << "Handing IAction::Craft to manager" << std::endl;
- ICraftAction *a = new ICraftAction();
- a->count = craft_amount;
- a->craft_inv = s.inventoryloc;
- m_invmgr->inventoryAction(a);
- }
-
- // If m_selected_amount has been decreased to zero, deselect
- if (m_selected_amount == 0) {
- delete m_selected_item;
- m_selected_item = NULL;
- m_selected_amount = 0;
- m_selected_dragging = false;
- m_selected_content_guess = ItemStack();
- }
- m_old_pointer = m_pointer;
- }
- if (event.EventType == EET_GUI_EVENT) {
-
- if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
- && isVisible()) {
- // find the element that was clicked
- for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
- if ((s.ftype == f_TabHeader) &&
- (s.fid == event.GUIEvent.Caller->getID())) {
- s.send = true;
- acceptInput();
- s.send = false;
- return true;
- }
- }
- }
- if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
- && isVisible()) {
- if (!canTakeFocus(event.GUIEvent.Element)) {
- infostream<<"GUIFormSpecMenu: Not allowing focus change."
- <<std::endl;
- // Returning true disables focus change
- return true;
- }
- }
- if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
- (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
- (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
- (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
- unsigned int btn_id = event.GUIEvent.Caller->getID();
-
- if (btn_id == 257) {
- if (m_allowclose) {
- acceptInput(quit_mode_accept);
- quitMenu();
- } else {
- acceptInput();
- m_text_dst->gotText(L"ExitButton");
- }
- // quitMenu deallocates menu
- return true;
- }
-
- // find the element that was clicked
- for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
- // if its a button, set the send field so
- // lua knows which button was pressed
- if ((s.ftype == f_Button || s.ftype == f_CheckBox) &&
- s.fid == event.GUIEvent.Caller->getID()) {
- s.send = true;
- if (s.is_exit) {
- if (m_allowclose) {
- acceptInput(quit_mode_accept);
- quitMenu();
- } else {
- m_text_dst->gotText(L"ExitButton");
- }
- return true;
- }
-
- acceptInput(quit_mode_no);
- s.send = false;
- return true;
-
- } else if ((s.ftype == f_DropDown) &&
- (s.fid == event.GUIEvent.Caller->getID())) {
- // only send the changed dropdown
- for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
- if (s2.ftype == f_DropDown) {
- s2.send = false;
- }
- }
- s.send = true;
- acceptInput(quit_mode_no);
-
- // revert configuration to make sure dropdowns are sent on
- // regular button click
- for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
- if (s2.ftype == f_DropDown) {
- s2.send = true;
- }
- }
- return true;
- } else if ((s.ftype == f_ScrollBar) &&
- (s.fid == event.GUIEvent.Caller->getID())) {
- s.fdefault = L"Changed";
- acceptInput(quit_mode_no);
- s.fdefault = L"";
- }
- }
- }
-
- if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
- if (event.GUIEvent.Caller->getID() > 257) {
- bool close_on_enter = true;
- for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
- if (s.ftype == f_Unknown &&
- s.fid == event.GUIEvent.Caller->getID()) {
- current_field_enter_pending = s.fname;
- std::unordered_map<std::string, bool>::const_iterator it =
- field_close_on_enter.find(s.fname);
- if (it != field_close_on_enter.end())
- close_on_enter = (*it).second;
-
- break;
- }
- }
-
- if (m_allowclose && close_on_enter) {
- current_keys_pending.key_enter = true;
- acceptInput(quit_mode_accept);
- quitMenu();
- } else {
- current_keys_pending.key_enter = true;
- acceptInput();
- }
- // quitMenu deallocates menu
- return true;
- }
- }
-
- if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
- int current_id = event.GUIEvent.Caller->getID();
- if (current_id > 257) {
- // find the element that was clicked
- for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
- // if it's a table, set the send field
- // so lua knows which table was changed
- if ((s.ftype == f_Table) && (s.fid == current_id)) {
- s.send = true;
- acceptInput();
- s.send=false;
- }
- }
- return true;
- }
- }
- }
-
- return Parent ? Parent->OnEvent(event) : false;
-}
-
-/**
- * get name of element by element id
- * @param id of element
- * @return name string or empty string
- */
-std::string GUIFormSpecMenu::getNameByID(s32 id)
-{
- for (FieldSpec &spec : m_fields) {
- if (spec.fid == id) {
- return spec.fname;
- }
- }
- return "";
-}
-
-/**
- * get label of element by id
- * @param id of element
- * @return label string or empty string
- */
-std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
-{
- for (FieldSpec &spec : m_fields) {
- if (spec.fid == id) {
- return spec.flabel;
- }
- }
- return L"";
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <utility>
-#include <stack>
-
-#include "irrlichttypes_extrabloated.h"
-#include "inventorymanager.h"
-#include "modalMenu.h"
-#include "guiTable.h"
-#include "network/networkprotocol.h"
-#include "client/joystick_controller.h"
-#include "util/string.h"
-#include "util/enriched_string.h"
-
-class InventoryManager;
-class ISimpleTextureSource;
-class Client;
-
-typedef enum {
- f_Button,
- f_Table,
- f_TabHeader,
- f_CheckBox,
- f_DropDown,
- f_ScrollBar,
- f_Unknown
-} FormspecFieldType;
-
-typedef enum {
- quit_mode_no,
- quit_mode_accept,
- quit_mode_cancel
-} FormspecQuitMode;
-
-struct TextDest
-{
- virtual ~TextDest() = default;
-
- // This is deprecated I guess? -celeron55
- virtual void gotText(const std::wstring &text) {}
- virtual void gotText(const StringMap &fields) = 0;
-
- std::string m_formname;
-};
-
-class IFormSource
-{
-public:
- virtual ~IFormSource() = default;
- virtual const std::string &getForm() const = 0;
- // Fill in variables in field text
- virtual std::string resolveText(const std::string &str) { return str; }
-};
-
-class GUIFormSpecMenu : public GUIModalMenu
-{
- struct ItemSpec
- {
- ItemSpec() = default;
-
- ItemSpec(const InventoryLocation &a_inventoryloc,
- const std::string &a_listname,
- s32 a_i) :
- inventoryloc(a_inventoryloc),
- listname(a_listname),
- i(a_i)
- {
- }
-
- bool isValid() const { return i != -1; }
-
- InventoryLocation inventoryloc;
- std::string listname;
- s32 i = -1;
- };
-
- struct ListDrawSpec
- {
- ListDrawSpec() = default;
-
- ListDrawSpec(const InventoryLocation &a_inventoryloc,
- const std::string &a_listname,
- v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i):
- inventoryloc(a_inventoryloc),
- listname(a_listname),
- pos(a_pos),
- geom(a_geom),
- start_item_i(a_start_item_i)
- {
- }
-
- InventoryLocation inventoryloc;
- std::string listname;
- v2s32 pos;
- v2s32 geom;
- s32 start_item_i;
- };
-
- struct ListRingSpec
- {
- ListRingSpec() = default;
-
- ListRingSpec(const InventoryLocation &a_inventoryloc,
- const std::string &a_listname):
- inventoryloc(a_inventoryloc),
- listname(a_listname)
- {
- }
-
- InventoryLocation inventoryloc;
- std::string listname;
- };
-
- struct ImageDrawSpec
- {
- ImageDrawSpec():
- parent_button(NULL),
- clip(false)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const std::string &a_item_name,
- gui::IGUIButton *a_parent_button,
- const v2s32 &a_pos, const v2s32 &a_geom):
- name(a_name),
- item_name(a_item_name),
- parent_button(a_parent_button),
- pos(a_pos),
- geom(a_geom),
- scale(true),
- clip(false)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const std::string &a_item_name,
- const v2s32 &a_pos, const v2s32 &a_geom):
- name(a_name),
- item_name(a_item_name),
- parent_button(NULL),
- pos(a_pos),
- geom(a_geom),
- scale(true),
- clip(false)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const v2s32 &a_pos, const v2s32 &a_geom, bool clip=false):
- name(a_name),
- parent_button(NULL),
- pos(a_pos),
- geom(a_geom),
- scale(true),
- clip(clip)
- {
- }
-
- ImageDrawSpec(const std::string &a_name,
- const v2s32 &a_pos):
- name(a_name),
- parent_button(NULL),
- pos(a_pos),
- scale(false),
- clip(false)
- {
- }
-
- std::string name;
- std::string item_name;
- gui::IGUIButton *parent_button;
- v2s32 pos;
- v2s32 geom;
- bool scale;
- bool clip;
- };
-
- struct FieldSpec
- {
- FieldSpec() = default;
-
- FieldSpec(const std::string &name, const std::wstring &label,
- const std::wstring &default_text, int id) :
- fname(name),
- flabel(label),
- fdefault(unescape_enriched(translate_string(default_text))),
- fid(id),
- send(false),
- ftype(f_Unknown),
- is_exit(false)
- {
- }
-
- std::string fname;
- std::wstring flabel;
- std::wstring fdefault;
- int fid;
- bool send;
- FormspecFieldType ftype;
- bool is_exit;
- core::rect<s32> rect;
- };
-
- struct BoxDrawSpec
- {
- BoxDrawSpec(v2s32 a_pos, v2s32 a_geom,irr::video::SColor a_color):
- pos(a_pos),
- geom(a_geom),
- color(a_color)
- {
- }
- v2s32 pos;
- v2s32 geom;
- irr::video::SColor color;
- };
-
- struct TooltipSpec
- {
- TooltipSpec() = default;
- TooltipSpec(const std::wstring &a_tooltip, irr::video::SColor a_bgcolor,
- irr::video::SColor a_color):
- tooltip(translate_string(a_tooltip)),
- bgcolor(a_bgcolor),
- color(a_color)
- {
- }
-
- std::wstring tooltip;
- irr::video::SColor bgcolor;
- irr::video::SColor color;
- };
-
- struct StaticTextSpec
- {
- StaticTextSpec():
- parent_button(NULL)
- {
- }
-
- StaticTextSpec(const std::wstring &a_text,
- const core::rect<s32> &a_rect):
- text(a_text),
- rect(a_rect),
- parent_button(NULL)
- {
- }
-
- StaticTextSpec(const std::wstring &a_text,
- const core::rect<s32> &a_rect,
- gui::IGUIButton *a_parent_button):
- text(a_text),
- rect(a_rect),
- parent_button(a_parent_button)
- {
- }
-
- std::wstring text;
- core::rect<s32> rect;
- gui::IGUIButton *parent_button;
- };
-
-public:
- GUIFormSpecMenu(JoystickController *joystick,
- gui::IGUIElement* parent, s32 id,
- IMenuManager *menumgr,
- Client *client,
- ISimpleTextureSource *tsrc,
- IFormSource* fs_src,
- TextDest* txt_dst,
- bool remap_dbl_click = true);
-
- ~GUIFormSpecMenu();
-
- void setFormSpec(const std::string &formspec_string,
- const InventoryLocation ¤t_inventory_location)
- {
- m_formspec_string = formspec_string;
- m_current_inventory_location = current_inventory_location;
- regenerateGui(m_screensize_old);
- }
-
- // form_src is deleted by this GUIFormSpecMenu
- void setFormSource(IFormSource *form_src)
- {
- delete m_form_src;
- m_form_src = form_src;
- }
-
- // text_dst is deleted by this GUIFormSpecMenu
- void setTextDest(TextDest *text_dst)
- {
- delete m_text_dst;
- m_text_dst = text_dst;
- }
-
- void allowClose(bool value)
- {
- m_allowclose = value;
- }
-
- void lockSize(bool lock,v2u32 basescreensize=v2u32(0,0))
- {
- m_lock = lock;
- m_lockscreensize = basescreensize;
- }
-
- void removeChildren();
- void setInitialFocus();
-
- void setFocus(const std::string &elementname)
- {
- m_focused_element = elementname;
- }
-
- /*
- Remove and re-add (or reposition) stuff
- */
- void regenerateGui(v2u32 screensize);
-
- ItemSpec getItemAtPos(v2s32 p) const;
- void drawList(const ListDrawSpec &s, int phase, bool &item_hovered);
- void drawSelectedItem();
- void drawMenu();
- void updateSelectedItem();
- ItemStack verifySelectedItem();
-
- void acceptInput(FormspecQuitMode quitmode);
- bool preprocessEvent(const SEvent& event);
- bool OnEvent(const SEvent& event);
- bool doPause;
- bool pausesGame() { return doPause; }
-
- GUITable* getTable(const std::string &tablename);
- std::vector<std::string>* getDropDownValues(const std::string &name);
-
-#ifdef __ANDROID__
- bool getAndroidUIInput();
-#endif
-
-protected:
- v2s32 getBasePos() const
- {
- return padding + offset + AbsoluteRect.UpperLeftCorner;
- }
-
- v2s32 padding;
- v2s32 spacing;
- v2s32 imgsize;
- v2s32 offset;
- v2s32 pos_offset;
- std::stack<v2s32> container_stack;
-
- InventoryManager *m_invmgr;
- ISimpleTextureSource *m_tsrc;
- Client *m_client;
-
- std::string m_formspec_string;
- InventoryLocation m_current_inventory_location;
-
- std::vector<ListDrawSpec> m_inventorylists;
- std::vector<ListRingSpec> m_inventory_rings;
- std::vector<ImageDrawSpec> m_backgrounds;
- std::vector<ImageDrawSpec> m_images;
- std::vector<ImageDrawSpec> m_itemimages;
- std::vector<BoxDrawSpec> m_boxes;
- std::unordered_map<std::string, bool> field_close_on_enter;
- std::vector<FieldSpec> m_fields;
- std::vector<StaticTextSpec> m_static_texts;
- std::vector<std::pair<FieldSpec,GUITable*> > m_tables;
- std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes;
- std::map<std::string, TooltipSpec> m_tooltips;
- std::vector<std::pair<FieldSpec,gui::IGUIScrollBar*> > m_scrollbars;
- std::vector<std::pair<FieldSpec, std::vector<std::string> > > m_dropdowns;
-
- ItemSpec *m_selected_item = nullptr;
- u32 m_selected_amount = 0;
- bool m_selected_dragging = false;
-
- // WARNING: BLACK MAGIC
- // Used to guess and keep up with some special things the server can do.
- // If name is "", no guess exists.
- ItemStack m_selected_content_guess;
- InventoryLocation m_selected_content_guess_inventory;
-
- v2s32 m_pointer;
- v2s32 m_old_pointer; // Mouse position after previous mouse event
- gui::IGUIStaticText *m_tooltip_element = nullptr;
-
- u64 m_tooltip_show_delay;
- bool m_tooltip_append_itemname;
- u64 m_hovered_time = 0;
- s32 m_old_tooltip_id = -1;
-
- bool m_auto_place = false;
-
- bool m_allowclose = true;
- bool m_lock = false;
- v2u32 m_lockscreensize;
-
- bool m_bgfullscreen;
- bool m_slotborder;
- video::SColor m_bgcolor;
- video::SColor m_fullscreen_bgcolor;
- video::SColor m_slotbg_n;
- video::SColor m_slotbg_h;
- video::SColor m_slotbordercolor;
- video::SColor m_default_tooltip_bgcolor;
- video::SColor m_default_tooltip_color;
-
-private:
- IFormSource *m_form_src;
- TextDest *m_text_dst;
- u32 m_formspec_version = 0;
- std::string m_focused_element = "";
- JoystickController *m_joystick;
-
- typedef struct {
- bool explicit_size;
- v2f invsize;
- v2s32 size;
- v2f32 offset;
- v2f32 anchor;
- core::rect<s32> rect;
- v2s32 basepos;
- v2u32 screensize;
- std::string focused_fieldname;
- GUITable::TableOptions table_options;
- GUITable::TableColumns table_columns;
- // used to restore table selection/scroll/treeview state
- std::unordered_map<std::string, GUITable::DynamicData> table_dyndata;
- } parserData;
-
- typedef struct {
- bool key_up;
- bool key_down;
- bool key_enter;
- bool key_escape;
- } fs_key_pendig;
-
- fs_key_pendig current_keys_pending;
- std::string current_field_enter_pending = "";
-
- void parseElement(parserData* data, const std::string &element);
-
- void parseSize(parserData* data, const std::string &element);
- void parseContainer(parserData* data, const std::string &element);
- void parseContainerEnd(parserData* data);
- void parseList(parserData* data, const std::string &element);
- void parseListRing(parserData* data, const std::string &element);
- void parseCheckbox(parserData* data, const std::string &element);
- void parseImage(parserData* data, const std::string &element);
- void parseItemImage(parserData* data, const std::string &element);
- void parseButton(parserData* data, const std::string &element,
- const std::string &typ);
- void parseBackground(parserData* data, const std::string &element);
- void parseTableOptions(parserData* data, const std::string &element);
- void parseTableColumns(parserData* data, const std::string &element);
- void parseTable(parserData* data, const std::string &element);
- void parseTextList(parserData* data, const std::string &element);
- void parseDropDown(parserData* data, const std::string &element);
- void parseFieldCloseOnEnter(parserData *data, const std::string &element);
- void parsePwdField(parserData* data, const std::string &element);
- void parseField(parserData* data, const std::string &element, const std::string &type);
- void parseSimpleField(parserData* data,std::vector<std::string> &parts);
- void parseTextArea(parserData* data,std::vector<std::string>& parts,
- const std::string &type);
- void parseLabel(parserData* data, const std::string &element);
- void parseVertLabel(parserData* data, const std::string &element);
- void parseImageButton(parserData* data, const std::string &element,
- const std::string &type);
- void parseItemImageButton(parserData* data, const std::string &element);
- void parseTabHeader(parserData* data, const std::string &element);
- void parseBox(parserData* data, const std::string &element);
- void parseBackgroundColor(parserData* data, const std::string &element);
- void parseListColors(parserData* data, const std::string &element);
- void parseTooltip(parserData* data, const std::string &element);
- bool parseVersionDirect(const std::string &data);
- bool parseSizeDirect(parserData* data, const std::string &element);
- void parseScrollBar(parserData* data, const std::string &element);
- bool parsePositionDirect(parserData *data, const std::string &element);
- void parsePosition(parserData *data, const std::string &element);
- bool parseAnchorDirect(parserData *data, const std::string &element);
- void parseAnchor(parserData *data, const std::string &element);
-
- void tryClose();
-
- void showTooltip(const std::wstring &text, const irr::video::SColor &color,
- const irr::video::SColor &bgcolor);
-
- /**
- * check if event is part of a double click
- * @param event event to evaluate
- * @return true/false if a doubleclick was detected
- */
- bool DoubleClickDetection(const SEvent event);
-
- struct clickpos
- {
- v2s32 pos;
- s64 time;
- };
- clickpos m_doubleclickdetect[2];
-
- int m_btn_height;
- gui::IGUIFont *m_font = nullptr;
-
- std::wstring getLabelByID(s32 id);
- std::string getNameByID(s32 id);
-#ifdef __ANDROID__
- v2s32 m_down_pos;
- std::string m_JavaDialogFieldName;
-#endif
-
- /* If true, remap a double-click (or double-tap) action to ESC. This is so
- * that, for example, Android users can double-tap to close a formspec.
- *
- * This value can (currently) only be set by the class constructor
- * and the default value for the setting is true.
- */
- bool m_remap_dbl_click;
-
-};
-
-class FormspecFormSource: public IFormSource
-{
-public:
- FormspecFormSource(const std::string &formspec):
- m_formspec(formspec)
- {
- }
-
- ~FormspecFormSource() = default;
-
- void setForm(const std::string &formspec)
- {
- m_formspec = FORMSPEC_VERSION_STRING + formspec;
- }
-
- const std::string &getForm() const
- {
- return m_formspec;
- }
-
- std::string m_formspec;
-};
+++ /dev/null
-/*
- Minetest
- Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
- Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
- Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "guiKeyChangeMenu.h"
-#include "debug.h"
-#include "serialization.h"
-#include <string>
-#include <IGUICheckBox.h>
-#include <IGUIEditBox.h>
-#include <IGUIButton.h>
-#include <IGUIStaticText.h>
-#include <IGUIFont.h>
-#include "settings.h"
-#include <algorithm>
-
-#include "mainmenumanager.h" // for g_gamecallback
-
-#define KMaxButtonPerColumns 12
-
-extern MainGameCallback *g_gamecallback;
-
-enum
-{
- GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR,
- // buttons
- GUI_ID_KEY_FORWARD_BUTTON,
- GUI_ID_KEY_BACKWARD_BUTTON,
- GUI_ID_KEY_LEFT_BUTTON,
- GUI_ID_KEY_RIGHT_BUTTON,
- GUI_ID_KEY_USE_BUTTON,
- GUI_ID_KEY_FLY_BUTTON,
- GUI_ID_KEY_FAST_BUTTON,
- GUI_ID_KEY_JUMP_BUTTON,
- GUI_ID_KEY_NOCLIP_BUTTON,
- GUI_ID_KEY_CINEMATIC_BUTTON,
- GUI_ID_KEY_CHAT_BUTTON,
- GUI_ID_KEY_CMD_BUTTON,
- GUI_ID_KEY_CMD_LOCAL_BUTTON,
- GUI_ID_KEY_CONSOLE_BUTTON,
- GUI_ID_KEY_SNEAK_BUTTON,
- GUI_ID_KEY_DROP_BUTTON,
- GUI_ID_KEY_INVENTORY_BUTTON,
- GUI_ID_KEY_HOTBAR_PREV_BUTTON,
- GUI_ID_KEY_HOTBAR_NEXT_BUTTON,
- GUI_ID_KEY_MUTE_BUTTON,
- GUI_ID_KEY_DEC_VOLUME_BUTTON,
- GUI_ID_KEY_INC_VOLUME_BUTTON,
- GUI_ID_KEY_RANGE_BUTTON,
- GUI_ID_KEY_ZOOM_BUTTON,
- GUI_ID_KEY_CAMERA_BUTTON,
- GUI_ID_KEY_MINIMAP_BUTTON,
- GUI_ID_KEY_SCREENSHOT_BUTTON,
- GUI_ID_KEY_CHATLOG_BUTTON,
- GUI_ID_KEY_HUD_BUTTON,
- GUI_ID_KEY_FOG_BUTTON,
- GUI_ID_KEY_DEC_RANGE_BUTTON,
- GUI_ID_KEY_INC_RANGE_BUTTON,
- GUI_ID_KEY_AUTOFWD_BUTTON,
- // other
- GUI_ID_CB_AUX1_DESCENDS,
- GUI_ID_CB_DOUBLETAP_JUMP,
-};
-
-GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
- gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) :
-GUIModalMenu(env, parent, id, menumgr)
-{
- init_keys();
- for (key_setting *ks : key_settings)
- key_used.push_back(ks->key);
-}
-
-GUIKeyChangeMenu::~GUIKeyChangeMenu()
-{
- removeChildren();
-
- for (key_setting *ks : key_settings) {
- delete[] ks->button_name;
- delete ks;
- }
- key_settings.clear();
-}
-
-void GUIKeyChangeMenu::removeChildren()
-{
- const core::list<gui::IGUIElement*> &children = getChildren();
- core::list<gui::IGUIElement*> children_copy;
- for (gui::IGUIElement*i : children) {
- children_copy.push_back(i);
- }
-
- for (gui::IGUIElement *i : children_copy) {
- i->remove();
- }
-}
-
-void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
-{
- removeChildren();
- v2s32 size(745, 430);
-
- core::rect < s32 > rect(screensize.X / 2 - size.X / 2,
- screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2,
- screensize.Y / 2 + size.Y / 2);
-
- DesiredRect = rect;
- recalculateAbsolutePosition(false);
-
- v2s32 topleft(0, 0);
-
- {
- core::rect < s32 > rect(0, 0, 600, 40);
- rect += topleft + v2s32(25, 3);
- //gui::IGUIStaticText *t =
- const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)");
- Environment->addStaticText(text,
- rect, false, true, this, -1);
- delete[] text;
- //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
- }
-
- // Build buttons
-
- v2s32 offset(25, 60);
-
- for(size_t i = 0; i < key_settings.size(); i++)
- {
- key_setting *k = key_settings.at(i);
- {
- core::rect < s32 > rect(0, 0, 150, 20);
- rect += topleft + v2s32(offset.X, offset.Y);
- Environment->addStaticText(k->button_name, rect, false, true, this, -1);
- }
-
- {
- core::rect < s32 > rect(0, 0, 100, 30);
- rect += topleft + v2s32(offset.X + 120, offset.Y - 5);
- const wchar_t *text = wgettext(k->key.name());
- k->button = Environment->addButton(rect, this, k->id, text);
- delete[] text;
- }
- if ((i + 1) % KMaxButtonPerColumns == 0) {
- offset.X += 230;
- offset.Y = 60;
- } else {
- offset += v2s32(0, 25);
- }
- }
-
- {
- s32 option_x = offset.X;
- s32 option_y = offset.Y + 5;
- u32 option_w = 180;
- {
- core::rect<s32> rect(0, 0, option_w, 30);
- rect += topleft + v2s32(option_x, option_y);
- const wchar_t *text = wgettext("\"Special\" = climb down");
- Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this,
- GUI_ID_CB_AUX1_DESCENDS, text);
- delete[] text;
- }
- offset += v2s32(0, 25);
- }
-
- {
- s32 option_x = offset.X;
- s32 option_y = offset.Y + 5;
- u32 option_w = 280;
- {
- core::rect<s32> rect(0, 0, option_w, 30);
- rect += topleft + v2s32(option_x, option_y);
- const wchar_t *text = wgettext("Double tap \"jump\" to toggle fly");
- Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this,
- GUI_ID_CB_DOUBLETAP_JUMP, text);
- delete[] text;
- }
- offset += v2s32(0, 25);
- }
-
- {
- core::rect < s32 > rect(0, 0, 100, 30);
- rect += topleft + v2s32(size.X / 2 - 105, size.Y - 40);
- const wchar_t *text = wgettext("Save");
- Environment->addButton(rect, this, GUI_ID_BACK_BUTTON,
- text);
- delete[] text;
- }
- {
- core::rect < s32 > rect(0, 0, 100, 30);
- rect += topleft + v2s32(size.X / 2 + 5, size.Y - 40);
- const wchar_t *text = wgettext("Cancel");
- Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON,
- text);
- delete[] text;
- }
-}
-
-void GUIKeyChangeMenu::drawMenu()
-{
- gui::IGUISkin* skin = Environment->getSkin();
- if (!skin)
- return;
- video::IVideoDriver* driver = Environment->getVideoDriver();
-
- video::SColor bgcolor(140, 0, 0, 0);
-
- {
- core::rect < s32 > rect(0, 0, 745, 620);
- rect += AbsoluteRect.UpperLeftCorner;
- driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
- }
-
- gui::IGUIElement::draw();
-}
-
-bool GUIKeyChangeMenu::acceptInput()
-{
- for (key_setting *k : key_settings) {
- g_settings->set(k->setting_name, k->key.sym());
- }
-
- {
- gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
- if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
- g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
- }
- {
- gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP);
- if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
- g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked());
- }
-
- clearKeyCache();
-
- g_gamecallback->signalKeyConfigChange();
-
- return true;
-}
-
-bool GUIKeyChangeMenu::resetMenu()
-{
- if (activeKey >= 0)
- {
- for (key_setting *k : key_settings) {
- if (k->id == activeKey) {
- const wchar_t *text = wgettext(k->key.name());
- k->button->setText(text);
- delete[] text;
- break;
- }
- }
- activeKey = -1;
- return false;
- }
- return true;
-}
-bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
-{
- if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0
- && event.KeyInput.PressedDown) {
-
- bool prefer_character = shift_down;
- KeyPress kp(event.KeyInput, prefer_character);
-
- bool shift_went_down = false;
- if(!shift_down &&
- (event.KeyInput.Key == irr::KEY_SHIFT ||
- event.KeyInput.Key == irr::KEY_LSHIFT ||
- event.KeyInput.Key == irr::KEY_RSHIFT))
- shift_went_down = true;
-
- // Remove Key already in use message
- if(this->key_used_text)
- {
- this->key_used_text->remove();
- this->key_used_text = NULL;
- }
- // Display Key already in use message
- if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end())
- {
- core::rect < s32 > rect(0, 0, 600, 40);
- rect += v2s32(0, 0) + v2s32(25, 30);
- const wchar_t *text = wgettext("Key already in use");
- this->key_used_text = Environment->addStaticText(text,
- rect, false, true, this, -1);
- delete[] text;
- //infostream << "Key already in use" << std::endl;
- }
-
- // But go on
- {
- key_setting *k = NULL;
- for (key_setting *ks : key_settings) {
- if (ks->id == activeKey) {
- k = ks;
- break;
- }
- }
- FATAL_ERROR_IF(k == NULL, "Key setting not found");
- k->key = kp;
- const wchar_t *text = wgettext(k->key.name());
- k->button->setText(text);
- delete[] text;
-
- this->key_used.push_back(kp);
-
- // Allow characters made with shift
- if(shift_went_down){
- shift_down = true;
- return false;
- }
-
- activeKey = -1;
- return true;
- }
- } else if (event.EventType == EET_KEY_INPUT_EVENT && activeKey < 0
- && event.KeyInput.PressedDown
- && event.KeyInput.Key == irr::KEY_ESCAPE) {
- quitMenu();
- return true;
- } else if (event.EventType == EET_GUI_EVENT) {
- if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
- && isVisible())
- {
- if (!canTakeFocus(event.GUIEvent.Element))
- {
- dstream << "GUIMainMenu: Not allowing focus change."
- << std::endl;
- // Returning true disables focus change
- return true;
- }
- }
- if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED)
- {
- switch (event.GUIEvent.Caller->getID())
- {
- case GUI_ID_BACK_BUTTON: //back
- acceptInput();
- quitMenu();
- return true;
- case GUI_ID_ABORT_BUTTON: //abort
- quitMenu();
- return true;
- default:
- key_setting *k = NULL;
-
- for (key_setting *ks : key_settings) {
- if (ks->id == event.GUIEvent.Caller->getID()) {
- k = ks;
- break;
- }
- }
- FATAL_ERROR_IF(k == NULL, "Key setting not found");
-
- resetMenu();
- shift_down = false;
- activeKey = event.GUIEvent.Caller->getID();
- const wchar_t *text = wgettext("press key");
- k->button->setText(text);
- delete[] text;
- this->key_used.erase(std::remove(this->key_used.begin(),
- this->key_used.end(), k->key), this->key_used.end());
- break;
- }
- Environment->setFocus(this);
- }
- }
- return Parent ? Parent->OnEvent(event) : false;
-}
-
-void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::string &setting_name)
-{
- key_setting *k = new key_setting;
- k->id = id;
-
- k->button_name = button_name;
- k->setting_name = setting_name;
- k->key = getKeySetting(k->setting_name.c_str());
- key_settings.push_back(k);
-}
-
-void GUIKeyChangeMenu::init_keys()
-{
- this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wgettext("Forward"), "keymap_forward");
- this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), "keymap_backward");
- this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left");
- this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right");
- this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Special"), "keymap_special1");
- this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump");
- this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak");
- this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop");
- this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), "keymap_inventory");
- this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON,wgettext("Prev. item"), "keymap_hotbar_previous");
- this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,wgettext("Next item"), "keymap_hotbar_next");
- this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom");
- this->add_key(GUI_ID_KEY_CAMERA_BUTTON, wgettext("Change camera"), "keymap_camera_mode");
- this->add_key(GUI_ID_KEY_CINEMATIC_BUTTON, wgettext("Toggle Cinematic"), "keymap_cinematic");
- this->add_key(GUI_ID_KEY_MINIMAP_BUTTON, wgettext("Toggle minimap"), "keymap_minimap");
- this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove");
- this->add_key(GUI_ID_KEY_FAST_BUTTON, wgettext("Toggle fast"), "keymap_fastmove");
- this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), "keymap_noclip");
- this->add_key(GUI_ID_KEY_MUTE_BUTTON, wgettext("Mute"), "keymap_mute");
- this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON,wgettext("Dec. volume"), "keymap_decrease_volume");
- this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON,wgettext("Inc. volume"), "keymap_increase_volume");
- this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON, wgettext("Autoforward"), "keymap_autoforward");
- this->add_key(GUI_ID_KEY_CHAT_BUTTON, wgettext("Chat"), "keymap_chat");
- this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON,wgettext("Screenshot"), "keymap_screenshot");
- this->add_key(GUI_ID_KEY_RANGE_BUTTON, wgettext("Range select"), "keymap_rangeselect");
- this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON, wgettext("Dec. range"), "keymap_decrease_viewing_range_min");
- this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON, wgettext("Inc. range"), "keymap_increase_viewing_range_min");
- this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wgettext("Console"), "keymap_console");
- this->add_key(GUI_ID_KEY_CMD_BUTTON, wgettext("Command"), "keymap_cmd");
- this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wgettext("Local command"), "keymap_cmd_local");
- this->add_key(GUI_ID_KEY_HUD_BUTTON, wgettext("Toggle HUD"), "keymap_toggle_hud");
- this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wgettext("Toggle chat log"), "keymap_toggle_chat");
- this->add_key(GUI_ID_KEY_FOG_BUTTON, wgettext("Toggle fog"), "keymap_toggle_force_fog_off");
-}
-
+++ /dev/null
-/*
- Minetest
- Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
- Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
- Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "modalMenu.h"
-#include "gettext.h"
-#include "keycode.h"
-#include <string>
-#include <vector>
-
-struct key_setting
-{
- int id;
- const wchar_t *button_name;
- KeyPress key;
- std::string setting_name;
- gui::IGUIButton *button;
-};
-
-class GUIKeyChangeMenu : public GUIModalMenu
-{
-public:
- GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
- IMenuManager *menumgr);
- ~GUIKeyChangeMenu();
-
- void removeChildren();
- /*
- Remove and re-add (or reposition) stuff
- */
- void regenerateGui(v2u32 screensize);
-
- void drawMenu();
-
- bool acceptInput();
-
- bool OnEvent(const SEvent &event);
-
- bool pausesGame() { return true; }
-
-private:
- void init_keys();
-
- bool resetMenu();
-
- void add_key(int id, const wchar_t *button_name, const std::string &setting_name);
-
- bool shift_down = false;
- s32 activeKey = -1;
-
- std::vector<KeyPress> key_used;
- gui::IGUIStaticText *key_used_text = nullptr;
- std::vector<key_setting *> key_settings;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "modalMenu.h"
-#include <string>
-#include <list>
-
-struct MainMenuDataForScript {
-
- MainMenuDataForScript() = default;
-
- // Whether the server has requested a reconnect
- bool reconnect_requested = false;
- std::string errormessage = "";
-};
-
-struct MainMenuData {
- // Client options
- std::string servername;
- std::string serverdescription;
- std::string address;
- std::string port;
- std::string name;
- std::string password;
- // Whether to reconnect
- bool do_reconnect = false;
-
- // Server options
- int selected_world = 0;
- bool simple_singleplayer_mode = false;
-
- // Data to be passed to the script
- MainMenuDataForScript script_data;
-
- MainMenuData() = default;
-};
+++ /dev/null
-/*
-Part of Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
-
-Permission to use, copy, modify, and distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-#include "guiPasswordChange.h"
-#include "client.h"
-#include <IGUICheckBox.h>
-#include <IGUIEditBox.h>
-#include <IGUIButton.h>
-#include <IGUIStaticText.h>
-#include <IGUIFont.h>
-
-#include "gettext.h"
-
-const int ID_oldPassword = 256;
-const int ID_newPassword1 = 257;
-const int ID_newPassword2 = 258;
-const int ID_change = 259;
-const int ID_message = 260;
-const int ID_cancel = 261;
-
-GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env,
- gui::IGUIElement* parent, s32 id,
- IMenuManager *menumgr,
- Client* client
-):
- GUIModalMenu(env, parent, id, menumgr),
- m_client(client)
-{
-}
-
-GUIPasswordChange::~GUIPasswordChange()
-{
- removeChildren();
-}
-
-void GUIPasswordChange::removeChildren()
-{
- const core::list<gui::IGUIElement *> &children = getChildren();
- core::list<gui::IGUIElement *> children_copy;
- for (gui::IGUIElement *i : children) {
- children_copy.push_back(i);
- }
-
- for (gui::IGUIElement *i : children_copy) {
- i->remove();
- }
-}
-void GUIPasswordChange::regenerateGui(v2u32 screensize)
-{
- /*
- save current input
- */
- acceptInput();
-
- /*
- Remove stuff
- */
- removeChildren();
-
- /*
- Calculate new sizes and positions
- */
- core::rect<s32> rect(
- screensize.X/2 - 580/2,
- screensize.Y/2 - 300/2,
- screensize.X/2 + 580/2,
- screensize.Y/2 + 300/2
- );
-
- DesiredRect = rect;
- recalculateAbsolutePosition(false);
-
- v2s32 size = rect.getSize();
- v2s32 topleft_client(40, 0);
-
- const wchar_t *text;
-
- /*
- Add stuff
- */
- s32 ypos = 50;
- {
- core::rect<s32> rect(0, 0, 150, 20);
- rect += topleft_client + v2s32(25, ypos + 6);
- text = wgettext("Old Password");
- Environment->addStaticText(text, rect, false, true, this, -1);
- delete[] text;
- }
- {
- core::rect<s32> rect(0, 0, 230, 30);
- rect += topleft_client + v2s32(160, ypos);
- gui::IGUIEditBox *e = Environment->addEditBox(
- m_oldpass.c_str(), rect, true, this, ID_oldPassword);
- Environment->setFocus(e);
- e->setPasswordBox(true);
- }
- ypos += 50;
- {
- core::rect<s32> rect(0, 0, 150, 20);
- rect += topleft_client + v2s32(25, ypos + 6);
- text = wgettext("New Password");
- Environment->addStaticText(text, rect, false, true, this, -1);
- delete[] text;
- }
- {
- core::rect<s32> rect(0, 0, 230, 30);
- rect += topleft_client + v2s32(160, ypos);
- gui::IGUIEditBox *e = Environment->addEditBox(
- m_newpass.c_str(), rect, true, this, ID_newPassword1);
- e->setPasswordBox(true);
- }
- ypos += 50;
- {
- core::rect<s32> rect(0, 0, 150, 20);
- rect += topleft_client + v2s32(25, ypos + 6);
- text = wgettext("Confirm Password");
- Environment->addStaticText(text, rect, false, true, this, -1);
- delete[] text;
- }
- {
- core::rect<s32> rect(0, 0, 230, 30);
- rect += topleft_client + v2s32(160, ypos);
- gui::IGUIEditBox *e = Environment->addEditBox(
- m_newpass_confirm.c_str(), rect, true, this, ID_newPassword2);
- e->setPasswordBox(true);
- }
-
- ypos += 50;
- {
- core::rect<s32> rect(0, 0, 100, 30);
- rect = rect + v2s32(size.X / 4 + 56, ypos);
- text = wgettext("Change");
- Environment->addButton(rect, this, ID_change, text);
- delete[] text;
- }
- {
- core::rect<s32> rect(0, 0, 100, 30);
- rect = rect + v2s32(size.X / 4 + 185, ypos);
- text = wgettext("Cancel");
- Environment->addButton(rect, this, ID_cancel, text);
- delete[] text;
- }
-
- ypos += 50;
- {
- core::rect<s32> rect(0, 0, 300, 20);
- rect += topleft_client + v2s32(35, ypos);
- text = wgettext("Passwords do not match!");
- IGUIElement *e =
- Environment->addStaticText(
- text, rect, false, true, this, ID_message);
- e->setVisible(false);
- delete[] text;
- }
-}
-
-void GUIPasswordChange::drawMenu()
-{
- gui::IGUISkin *skin = Environment->getSkin();
- if (!skin)
- return;
- video::IVideoDriver *driver = Environment->getVideoDriver();
-
- video::SColor bgcolor(140, 0, 0, 0);
- driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
-
- gui::IGUIElement::draw();
-}
-
-void GUIPasswordChange::acceptInput()
-{
- gui::IGUIElement *e;
- e = getElementFromId(ID_oldPassword);
- if (e != NULL)
- m_oldpass = e->getText();
- e = getElementFromId(ID_newPassword1);
- if (e != NULL)
- m_newpass = e->getText();
- e = getElementFromId(ID_newPassword2);
- if (e != NULL)
- m_newpass_confirm = e->getText();
-}
-
-bool GUIPasswordChange::processInput()
-{
- if (m_newpass != m_newpass_confirm) {
- gui::IGUIElement *e = getElementFromId(ID_message);
- if (e != NULL)
- e->setVisible(true);
- return false;
- }
- m_client->sendChangePassword(wide_to_utf8(m_oldpass), wide_to_utf8(m_newpass));
- return true;
-}
-
-bool GUIPasswordChange::OnEvent(const SEvent &event)
-{
- if (event.EventType == EET_KEY_INPUT_EVENT) {
- if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
- quitMenu();
- return true;
- }
- if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
- acceptInput();
- if (processInput())
- quitMenu();
- return true;
- }
- }
- if (event.EventType == EET_GUI_EVENT) {
- if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST &&
- isVisible()) {
- if (!canTakeFocus(event.GUIEvent.Element)) {
- dstream << "GUIPasswordChange: Not allowing focus change."
- << std::endl;
- // Returning true disables focus change
- return true;
- }
- }
- if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
- switch (event.GUIEvent.Caller->getID()) {
- case ID_change:
- acceptInput();
- if (processInput())
- quitMenu();
- return true;
- case ID_cancel:
- quitMenu();
- return true;
- }
- }
- if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
- switch (event.GUIEvent.Caller->getID()) {
- case ID_oldPassword:
- case ID_newPassword1:
- case ID_newPassword2:
- acceptInput();
- if (processInput())
- quitMenu();
- return true;
- }
- }
- }
-
- return Parent ? Parent->OnEvent(event) : false;
-}
+++ /dev/null
-/*
-Part of Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
-
-Permission to use, copy, modify, and distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "modalMenu.h"
-#include <string>
-
-class Client;
-
-class GUIPasswordChange : public GUIModalMenu
-{
-public:
- GUIPasswordChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
- IMenuManager *menumgr, Client *client);
- ~GUIPasswordChange();
-
- void removeChildren();
- /*
- Remove and re-add (or reposition) stuff
- */
- void regenerateGui(v2u32 screensize);
-
- void drawMenu();
-
- void acceptInput();
-
- bool processInput();
-
- bool OnEvent(const SEvent &event);
-
-private:
- Client *m_client;
- std::wstring m_oldpass = L"";
- std::wstring m_newpass = L"";
- std::wstring m_newpass_confirm = L"";
-};
+++ /dev/null
-/*
- Minetest
- Copyright (C) 2013 sapier
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "guiPathSelectMenu.h"
-
-GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env,
- gui::IGUIElement* parent, s32 id, IMenuManager *menumgr,
- const std::string &title, const std::string &formname,
- bool is_file_select) :
- GUIModalMenu(env, parent, id, menumgr),
- m_title(utf8_to_wide(title)),
- m_formname(formname),
- m_file_select_dialog(is_file_select)
-{
-}
-
-GUIFileSelectMenu::~GUIFileSelectMenu()
-{
- removeChildren();
- setlocale(LC_NUMERIC, "C");
-}
-
-void GUIFileSelectMenu::regenerateGui(v2u32 screensize)
-{
- removeChildren();
- m_fileOpenDialog = 0;
-
- core::dimension2du size(600, 400);
- core::rect<s32> rect(0, 0, screensize.X, screensize.Y);
-
- DesiredRect = rect;
- recalculateAbsolutePosition(false);
-
- m_fileOpenDialog =
- Environment->addFileOpenDialog(m_title.c_str(), false, this, -1);
-
- core::position2di pos = core::position2di(screensize.X / 2 - size.Width / 2,
- screensize.Y / 2 - size.Height / 2);
- m_fileOpenDialog->setRelativePosition(pos);
- m_fileOpenDialog->setMinSize(size);
-}
-
-void GUIFileSelectMenu::drawMenu()
-{
- gui::IGUISkin *skin = Environment->getSkin();
- if (!skin)
- return;
-
- gui::IGUIElement::draw();
-}
-
-void GUIFileSelectMenu::acceptInput()
-{
- if (m_text_dst && !m_formname.empty()) {
- StringMap fields;
- if (m_accepted) {
- std::string path;
- if (!m_file_select_dialog) {
- core::string<fschar_t> string =
- m_fileOpenDialog->getDirectoryName();
- path = std::string(string.c_str());
- } else {
- path = wide_to_utf8(m_fileOpenDialog->getFileName());
- }
- fields[m_formname + "_accepted"] = path;
- } else {
- fields[m_formname + "_canceled"] = m_formname;
- }
- m_text_dst->gotText(fields);
- }
- quitMenu();
-}
-
-bool GUIFileSelectMenu::OnEvent(const SEvent &event)
-{
- if (event.EventType == irr::EET_GUI_EVENT) {
- switch (event.GUIEvent.EventType) {
- case gui::EGET_ELEMENT_CLOSED:
- case gui::EGET_FILE_CHOOSE_DIALOG_CANCELLED:
- m_accepted = false;
- acceptInput();
- return true;
- case gui::EGET_DIRECTORY_SELECTED:
- m_accepted = !m_file_select_dialog;
- acceptInput();
- return true;
- case gui::EGET_FILE_SELECTED:
- m_accepted = m_file_select_dialog;
- acceptInput();
- return true;
- default:
- // ignore this event
- break;
- }
- }
- return Parent ? Parent->OnEvent(event) : false;
-}
+++ /dev/null
-/*
- Minetest
- Copyright (C) 2013 sapier
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#pragma once
-
-#include <string>
-
-#include "modalMenu.h"
-#include "IGUIFileOpenDialog.h"
-#include "guiFormSpecMenu.h" //required because of TextDest only !!!
-
-class GUIFileSelectMenu : public GUIModalMenu
-{
-public:
- GUIFileSelectMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
- IMenuManager *menumgr, const std::string &title,
- const std::string &formid, bool is_file_select);
- ~GUIFileSelectMenu();
-
- /*
- Remove and re-add (or reposition) stuff
- */
- void regenerateGui(v2u32 screensize);
-
- void drawMenu();
-
- bool OnEvent(const SEvent &event);
-
- void setTextDest(TextDest *dest) { m_text_dst = dest; }
-
-private:
- void acceptInput();
-
- std::wstring m_title;
- bool m_accepted = false;
-
- gui::IGUIFileOpenDialog *m_fileOpenDialog = nullptr;
-
- TextDest *m_text_dst = nullptr;
-
- std::string m_formname;
- bool m_file_select_dialog;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-
-#include "guiTable.h"
-#include <queue>
-#include <sstream>
-#include <utility>
-#include <cstring>
-#include <IGUISkin.h>
-#include <IGUIFont.h>
-#include <IGUIScrollBar.h>
-#include "client/renderingengine.h"
-#include "debug.h"
-#include "log.h"
-#include "client/tile.h"
-#include "gettime.h"
-#include "util/string.h"
-#include "util/numeric.h"
-#include "util/string.h" // for parseColorString()
-#include "settings.h" // for settings
-#include "porting.h" // for dpi
-#include "guiscalingfilter.h"
-
-/*
- GUITable
-*/
-
-GUITable::GUITable(gui::IGUIEnvironment *env,
- gui::IGUIElement* parent, s32 id,
- core::rect<s32> rectangle,
- ISimpleTextureSource *tsrc
-):
- gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
- m_tsrc(tsrc)
-{
- assert(tsrc != NULL);
-
- gui::IGUISkin* skin = Environment->getSkin();
-
- m_font = skin->getFont();
- if (m_font) {
- m_font->grab();
- m_rowheight = m_font->getDimension(L"A").Height + 4;
- m_rowheight = MYMAX(m_rowheight, 1);
- }
-
- const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
- m_scrollbar = Environment->addScrollBar(false,
- core::rect<s32>(RelativeRect.getWidth() - s,
- 0,
- RelativeRect.getWidth(),
- RelativeRect.getHeight()),
- this, -1);
- m_scrollbar->setSubElement(true);
- m_scrollbar->setTabStop(false);
- m_scrollbar->setAlignment(gui::EGUIA_LOWERRIGHT, gui::EGUIA_LOWERRIGHT,
- gui::EGUIA_UPPERLEFT, gui::EGUIA_LOWERRIGHT);
- m_scrollbar->setVisible(false);
- m_scrollbar->setPos(0);
-
- setTabStop(true);
- setTabOrder(-1);
- updateAbsolutePosition();
-
- core::rect<s32> relative_rect = m_scrollbar->getRelativePosition();
- s32 width = (relative_rect.getWidth()/(2.0/3.0)) *
- RenderingEngine::getDisplayDensity() *
- g_settings->getFloat("gui_scaling");
- m_scrollbar->setRelativePosition(core::rect<s32>(
- relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y,
- relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y
- ));
-}
-
-GUITable::~GUITable()
-{
- for (GUITable::Row &row : m_rows)
- delete[] row.cells;
-
- if (m_font)
- m_font->drop();
-
- m_scrollbar->remove();
-}
-
-GUITable::Option GUITable::splitOption(const std::string &str)
-{
- size_t equal_pos = str.find('=');
- if (equal_pos == std::string::npos)
- return GUITable::Option(str, "");
-
- return GUITable::Option(str.substr(0, equal_pos),
- str.substr(equal_pos + 1));
-}
-
-void GUITable::setTextList(const std::vector<std::string> &content,
- bool transparent)
-{
- clear();
-
- if (transparent) {
- m_background.setAlpha(0);
- m_border = false;
- }
-
- m_is_textlist = true;
-
- s32 empty_string_index = allocString("");
-
- m_rows.resize(content.size());
- for (s32 i = 0; i < (s32) content.size(); ++i) {
- Row *row = &m_rows[i];
- row->cells = new Cell[1];
- row->cellcount = 1;
- row->indent = 0;
- row->visible_index = i;
- m_visible_rows.push_back(i);
-
- Cell *cell = row->cells;
- cell->xmin = 0;
- cell->xmax = 0x7fff; // something large enough
- cell->xpos = 6;
- cell->content_type = COLUMN_TYPE_TEXT;
- cell->content_index = empty_string_index;
- cell->tooltip_index = empty_string_index;
- cell->color.set(255, 255, 255, 255);
- cell->color_defined = false;
- cell->reported_column = 1;
-
- // parse row content (color)
- const std::string &s = content[i];
- if (s[0] == '#' && s[1] == '#') {
- // double # to escape
- cell->content_index = allocString(s.substr(2));
- }
- else if (s[0] == '#' && s.size() >= 7 &&
- parseColorString(
- s.substr(0,7), cell->color, false)) {
- // single # for color
- cell->color_defined = true;
- cell->content_index = allocString(s.substr(7));
- }
- else {
- // no #, just text
- cell->content_index = allocString(s);
- }
-
- }
-
- allocationComplete();
-
- // Clamp scroll bar position
- updateScrollBar();
-}
-
-void GUITable::setTable(const TableOptions &options,
- const TableColumns &columns,
- std::vector<std::string> &content)
-{
- clear();
-
- // Naming conventions:
- // i is always a row index, 0-based
- // j is always a column index, 0-based
- // k is another index, for example an option index
-
- // Handle a stupid error case... (issue #1187)
- if (columns.empty()) {
- TableColumn text_column;
- text_column.type = "text";
- TableColumns new_columns;
- new_columns.push_back(text_column);
- setTable(options, new_columns, content);
- return;
- }
-
- // Handle table options
- video::SColor default_color(255, 255, 255, 255);
- s32 opendepth = 0;
- for (const Option &option : options) {
- const std::string &name = option.name;
- const std::string &value = option.value;
- if (name == "color")
- parseColorString(value, m_color, false);
- else if (name == "background")
- parseColorString(value, m_background, false);
- else if (name == "border")
- m_border = is_yes(value);
- else if (name == "highlight")
- parseColorString(value, m_highlight, false);
- else if (name == "highlight_text")
- parseColorString(value, m_highlight_text, false);
- else if (name == "opendepth")
- opendepth = stoi(value);
- else
- errorstream<<"Invalid table option: \""<<name<<"\""
- <<" (value=\""<<value<<"\")"<<std::endl;
- }
-
- // Get number of columns and rows
- // note: error case columns.size() == 0 was handled above
- s32 colcount = columns.size();
- assert(colcount >= 1);
- // rowcount = ceil(cellcount / colcount) but use integer arithmetic
- s32 rowcount = (content.size() + colcount - 1) / colcount;
- assert(rowcount >= 0);
- // Append empty strings to content if there is an incomplete row
- s32 cellcount = rowcount * colcount;
- while (content.size() < (u32) cellcount)
- content.emplace_back("");
-
- // Create temporary rows (for processing columns)
- struct TempRow {
- // Current horizontal position (may different between rows due
- // to indent/tree columns, or text/image columns with width<0)
- s32 x;
- // Tree indentation level
- s32 indent;
- // Next cell: Index into m_strings or m_images
- s32 content_index;
- // Next cell: Width in pixels
- s32 content_width;
- // Vector of completed cells in this row
- std::vector<Cell> cells;
- // Stores colors and how long they last (maximum column index)
- std::vector<std::pair<video::SColor, s32> > colors;
-
- TempRow(): x(0), indent(0), content_index(0), content_width(0) {}
- };
- TempRow *rows = new TempRow[rowcount];
-
- // Get em width. Pedantically speaking, the width of "M" is not
- // necessarily the same as the em width, but whatever, close enough.
- s32 em = 6;
- if (m_font)
- em = m_font->getDimension(L"M").Width;
-
- s32 default_tooltip_index = allocString("");
-
- std::map<s32, s32> active_image_indices;
-
- // Process content in column-major order
- for (s32 j = 0; j < colcount; ++j) {
- // Check column type
- ColumnType columntype = COLUMN_TYPE_TEXT;
- if (columns[j].type == "text")
- columntype = COLUMN_TYPE_TEXT;
- else if (columns[j].type == "image")
- columntype = COLUMN_TYPE_IMAGE;
- else if (columns[j].type == "color")
- columntype = COLUMN_TYPE_COLOR;
- else if (columns[j].type == "indent")
- columntype = COLUMN_TYPE_INDENT;
- else if (columns[j].type == "tree")
- columntype = COLUMN_TYPE_TREE;
- else
- errorstream<<"Invalid table column type: \""
- <<columns[j].type<<"\""<<std::endl;
-
- // Process column options
- s32 padding = myround(0.5 * em);
- s32 tooltip_index = default_tooltip_index;
- s32 align = 0;
- s32 width = 0;
- s32 span = colcount;
-
- if (columntype == COLUMN_TYPE_INDENT) {
- padding = 0; // default indent padding
- }
- if (columntype == COLUMN_TYPE_INDENT ||
- columntype == COLUMN_TYPE_TREE) {
- width = myround(em * 1.5); // default indent width
- }
-
- for (const Option &option : columns[j].options) {
- const std::string &name = option.name;
- const std::string &value = option.value;
- if (name == "padding")
- padding = myround(stof(value) * em);
- else if (name == "tooltip")
- tooltip_index = allocString(value);
- else if (name == "align" && value == "left")
- align = 0;
- else if (name == "align" && value == "center")
- align = 1;
- else if (name == "align" && value == "right")
- align = 2;
- else if (name == "align" && value == "inline")
- align = 3;
- else if (name == "width")
- width = myround(stof(value) * em);
- else if (name == "span" && columntype == COLUMN_TYPE_COLOR)
- span = stoi(value);
- else if (columntype == COLUMN_TYPE_IMAGE &&
- !name.empty() &&
- string_allowed(name, "0123456789")) {
- s32 content_index = allocImage(value);
- active_image_indices.insert(std::make_pair(
- stoi(name),
- content_index));
- }
- else {
- errorstream<<"Invalid table column option: \""<<name<<"\""
- <<" (value=\""<<value<<"\")"<<std::endl;
- }
- }
-
- // If current column type can use information from "color" columns,
- // find out which of those is currently active
- if (columntype == COLUMN_TYPE_TEXT) {
- for (s32 i = 0; i < rowcount; ++i) {
- TempRow *row = &rows[i];
- while (!row->colors.empty() && row->colors.back().second < j)
- row->colors.pop_back();
- }
- }
-
- // Make template for new cells
- Cell newcell;
- memset(&newcell, 0, sizeof newcell);
- newcell.content_type = columntype;
- newcell.tooltip_index = tooltip_index;
- newcell.reported_column = j+1;
-
- if (columntype == COLUMN_TYPE_TEXT) {
- // Find right edge of column
- s32 xmax = 0;
- for (s32 i = 0; i < rowcount; ++i) {
- TempRow *row = &rows[i];
- row->content_index = allocString(content[i * colcount + j]);
- const core::stringw &text = m_strings[row->content_index];
- row->content_width = m_font ?
- m_font->getDimension(text.c_str()).Width : 0;
- row->content_width = MYMAX(row->content_width, width);
- s32 row_xmax = row->x + padding + row->content_width;
- xmax = MYMAX(xmax, row_xmax);
- }
- // Add a new cell (of text type) to each row
- for (s32 i = 0; i < rowcount; ++i) {
- newcell.xmin = rows[i].x + padding;
- alignContent(&newcell, xmax, rows[i].content_width, align);
- newcell.content_index = rows[i].content_index;
- newcell.color_defined = !rows[i].colors.empty();
- if (newcell.color_defined)
- newcell.color = rows[i].colors.back().first;
- rows[i].cells.push_back(newcell);
- rows[i].x = newcell.xmax;
- }
- }
- else if (columntype == COLUMN_TYPE_IMAGE) {
- // Find right edge of column
- s32 xmax = 0;
- for (s32 i = 0; i < rowcount; ++i) {
- TempRow *row = &rows[i];
- row->content_index = -1;
-
- // Find content_index. Image indices are defined in
- // column options so check active_image_indices.
- s32 image_index = stoi(content[i * colcount + j]);
- std::map<s32, s32>::iterator image_iter =
- active_image_indices.find(image_index);
- if (image_iter != active_image_indices.end())
- row->content_index = image_iter->second;
-
- // Get texture object (might be NULL)
- video::ITexture *image = NULL;
- if (row->content_index >= 0)
- image = m_images[row->content_index];
-
- // Get content width and update xmax
- row->content_width = image ? image->getOriginalSize().Width : 0;
- row->content_width = MYMAX(row->content_width, width);
- s32 row_xmax = row->x + padding + row->content_width;
- xmax = MYMAX(xmax, row_xmax);
- }
- // Add a new cell (of image type) to each row
- for (s32 i = 0; i < rowcount; ++i) {
- newcell.xmin = rows[i].x + padding;
- alignContent(&newcell, xmax, rows[i].content_width, align);
- newcell.content_index = rows[i].content_index;
- rows[i].cells.push_back(newcell);
- rows[i].x = newcell.xmax;
- }
- active_image_indices.clear();
- }
- else if (columntype == COLUMN_TYPE_COLOR) {
- for (s32 i = 0; i < rowcount; ++i) {
- video::SColor cellcolor(255, 255, 255, 255);
- if (parseColorString(content[i * colcount + j], cellcolor, true))
- rows[i].colors.emplace_back(cellcolor, j+span);
- }
- }
- else if (columntype == COLUMN_TYPE_INDENT ||
- columntype == COLUMN_TYPE_TREE) {
- // For column type "tree", reserve additional space for +/-
- // Also enable special processing for treeview-type tables
- s32 content_width = 0;
- if (columntype == COLUMN_TYPE_TREE) {
- content_width = m_font ? m_font->getDimension(L"+").Width : 0;
- m_has_tree_column = true;
- }
- // Add a new cell (of indent or tree type) to each row
- for (s32 i = 0; i < rowcount; ++i) {
- TempRow *row = &rows[i];
-
- s32 indentlevel = stoi(content[i * colcount + j]);
- indentlevel = MYMAX(indentlevel, 0);
- if (columntype == COLUMN_TYPE_TREE)
- row->indent = indentlevel;
-
- newcell.xmin = row->x + padding;
- newcell.xpos = newcell.xmin + indentlevel * width;
- newcell.xmax = newcell.xpos + content_width;
- newcell.content_index = 0;
- newcell.color_defined = !rows[i].colors.empty();
- if (newcell.color_defined)
- newcell.color = rows[i].colors.back().first;
- row->cells.push_back(newcell);
- row->x = newcell.xmax;
- }
- }
- }
-
- // Copy temporary rows to not so temporary rows
- if (rowcount >= 1) {
- m_rows.resize(rowcount);
- for (s32 i = 0; i < rowcount; ++i) {
- Row *row = &m_rows[i];
- row->cellcount = rows[i].cells.size();
- row->cells = new Cell[row->cellcount];
- memcpy((void*) row->cells, (void*) &rows[i].cells[0],
- row->cellcount * sizeof(Cell));
- row->indent = rows[i].indent;
- row->visible_index = i;
- m_visible_rows.push_back(i);
- }
- }
-
- if (m_has_tree_column) {
- // Treeview: convert tree to indent cells on leaf rows
- for (s32 i = 0; i < rowcount; ++i) {
- if (i == rowcount-1 || m_rows[i].indent >= m_rows[i+1].indent)
- for (s32 j = 0; j < m_rows[i].cellcount; ++j)
- if (m_rows[i].cells[j].content_type == COLUMN_TYPE_TREE)
- m_rows[i].cells[j].content_type = COLUMN_TYPE_INDENT;
- }
-
- // Treeview: close rows according to opendepth option
- std::set<s32> opened_trees;
- for (s32 i = 0; i < rowcount; ++i)
- if (m_rows[i].indent < opendepth)
- opened_trees.insert(i);
- setOpenedTrees(opened_trees);
- }
-
- // Delete temporary information used only during setTable()
- delete[] rows;
- allocationComplete();
-
- // Clamp scroll bar position
- updateScrollBar();
-}
-
-void GUITable::clear()
-{
- // Clean up cells and rows
- for (GUITable::Row &row : m_rows)
- delete[] row.cells;
- m_rows.clear();
- m_visible_rows.clear();
-
- // Get colors from skin
- gui::IGUISkin *skin = Environment->getSkin();
- m_color = skin->getColor(gui::EGDC_BUTTON_TEXT);
- m_background = skin->getColor(gui::EGDC_3D_HIGH_LIGHT);
- m_highlight = skin->getColor(gui::EGDC_HIGH_LIGHT);
- m_highlight_text = skin->getColor(gui::EGDC_HIGH_LIGHT_TEXT);
-
- // Reset members
- m_is_textlist = false;
- m_has_tree_column = false;
- m_selected = -1;
- m_sel_column = 0;
- m_sel_doubleclick = false;
- m_keynav_time = 0;
- m_keynav_buffer = L"";
- m_border = true;
- m_strings.clear();
- m_images.clear();
- m_alloc_strings.clear();
- m_alloc_images.clear();
-}
-
-std::string GUITable::checkEvent()
-{
- s32 sel = getSelected();
- assert(sel >= 0);
-
- if (sel == 0) {
- return "INV";
- }
-
- std::ostringstream os(std::ios::binary);
- if (m_sel_doubleclick) {
- os<<"DCL:";
- m_sel_doubleclick = false;
- }
- else {
- os<<"CHG:";
- }
- os<<sel;
- if (!m_is_textlist) {
- os<<":"<<m_sel_column;
- }
- return os.str();
-}
-
-s32 GUITable::getSelected() const
-{
- if (m_selected < 0)
- return 0;
-
- assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
- return m_visible_rows[m_selected] + 1;
-}
-
-void GUITable::setSelected(s32 index)
-{
- s32 old_selected = m_selected;
-
- m_selected = -1;
- m_sel_column = 0;
- m_sel_doubleclick = false;
-
- --index; // Switch from 1-based indexing to 0-based indexing
-
- s32 rowcount = m_rows.size();
- if (rowcount == 0 || index < 0) {
- return;
- }
-
- if (index >= rowcount) {
- index = rowcount - 1;
- }
-
- // If the selected row is not visible, open its ancestors to make it visible
- bool selection_invisible = m_rows[index].visible_index < 0;
- if (selection_invisible) {
- std::set<s32> opened_trees;
- getOpenedTrees(opened_trees);
- s32 indent = m_rows[index].indent;
- for (s32 j = index - 1; j >= 0; --j) {
- if (m_rows[j].indent < indent) {
- opened_trees.insert(j);
- indent = m_rows[j].indent;
- }
- }
- setOpenedTrees(opened_trees);
- }
-
- if (index >= 0) {
- m_selected = m_rows[index].visible_index;
- assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
- }
-
- if (m_selected != old_selected || selection_invisible) {
- autoScroll();
- }
-}
-
-GUITable::DynamicData GUITable::getDynamicData() const
-{
- DynamicData dyndata;
- dyndata.selected = getSelected();
- dyndata.scrollpos = m_scrollbar->getPos();
- dyndata.keynav_time = m_keynav_time;
- dyndata.keynav_buffer = m_keynav_buffer;
- if (m_has_tree_column)
- getOpenedTrees(dyndata.opened_trees);
- return dyndata;
-}
-
-void GUITable::setDynamicData(const DynamicData &dyndata)
-{
- if (m_has_tree_column)
- setOpenedTrees(dyndata.opened_trees);
-
- m_keynav_time = dyndata.keynav_time;
- m_keynav_buffer = dyndata.keynav_buffer;
-
- setSelected(dyndata.selected);
- m_sel_column = 0;
- m_sel_doubleclick = false;
-
- m_scrollbar->setPos(dyndata.scrollpos);
-}
-
-const c8* GUITable::getTypeName() const
-{
- return "GUITable";
-}
-
-void GUITable::updateAbsolutePosition()
-{
- IGUIElement::updateAbsolutePosition();
- updateScrollBar();
-}
-
-void GUITable::draw()
-{
- if (!IsVisible)
- return;
-
- gui::IGUISkin *skin = Environment->getSkin();
-
- // draw background
-
- bool draw_background = m_background.getAlpha() > 0;
- if (m_border)
- skin->draw3DSunkenPane(this, m_background,
- true, draw_background,
- AbsoluteRect, &AbsoluteClippingRect);
- else if (draw_background)
- skin->draw2DRectangle(this, m_background,
- AbsoluteRect, &AbsoluteClippingRect);
-
- // get clipping rect
-
- core::rect<s32> client_clip(AbsoluteRect);
- client_clip.UpperLeftCorner.Y += 1;
- client_clip.UpperLeftCorner.X += 1;
- client_clip.LowerRightCorner.Y -= 1;
- client_clip.LowerRightCorner.X -= 1;
- if (m_scrollbar->isVisible()) {
- client_clip.LowerRightCorner.X =
- m_scrollbar->getAbsolutePosition().UpperLeftCorner.X;
- }
- client_clip.clipAgainst(AbsoluteClippingRect);
-
- // draw visible rows
-
- s32 scrollpos = m_scrollbar->getPos();
- s32 row_min = scrollpos / m_rowheight;
- s32 row_max = (scrollpos + AbsoluteRect.getHeight() - 1)
- / m_rowheight + 1;
- row_max = MYMIN(row_max, (s32) m_visible_rows.size());
-
- core::rect<s32> row_rect(AbsoluteRect);
- if (m_scrollbar->isVisible())
- row_rect.LowerRightCorner.X -=
- skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
- row_rect.UpperLeftCorner.Y += row_min * m_rowheight - scrollpos;
- row_rect.LowerRightCorner.Y = row_rect.UpperLeftCorner.Y + m_rowheight;
-
- for (s32 i = row_min; i < row_max; ++i) {
- Row *row = &m_rows[m_visible_rows[i]];
- bool is_sel = i == m_selected;
- video::SColor color = m_color;
-
- if (is_sel) {
- skin->draw2DRectangle(this, m_highlight, row_rect, &client_clip);
- color = m_highlight_text;
- }
-
- for (s32 j = 0; j < row->cellcount; ++j)
- drawCell(&row->cells[j], color, row_rect, client_clip);
-
- row_rect.UpperLeftCorner.Y += m_rowheight;
- row_rect.LowerRightCorner.Y += m_rowheight;
- }
-
- // Draw children
- IGUIElement::draw();
-}
-
-void GUITable::drawCell(const Cell *cell, video::SColor color,
- const core::rect<s32> &row_rect,
- const core::rect<s32> &client_clip)
-{
- if ((cell->content_type == COLUMN_TYPE_TEXT)
- || (cell->content_type == COLUMN_TYPE_TREE)) {
-
- core::rect<s32> text_rect = row_rect;
- text_rect.UpperLeftCorner.X = row_rect.UpperLeftCorner.X
- + cell->xpos;
- text_rect.LowerRightCorner.X = row_rect.UpperLeftCorner.X
- + cell->xmax;
-
- if (cell->color_defined)
- color = cell->color;
-
- if (m_font) {
- if (cell->content_type == COLUMN_TYPE_TEXT)
- m_font->draw(m_strings[cell->content_index],
- text_rect, color,
- false, true, &client_clip);
- else // tree
- m_font->draw(cell->content_index ? L"+" : L"-",
- text_rect, color,
- false, true, &client_clip);
- }
- }
- else if (cell->content_type == COLUMN_TYPE_IMAGE) {
-
- if (cell->content_index < 0)
- return;
-
- video::IVideoDriver *driver = Environment->getVideoDriver();
- video::ITexture *image = m_images[cell->content_index];
-
- if (image) {
- core::position2d<s32> dest_pos =
- row_rect.UpperLeftCorner;
- dest_pos.X += cell->xpos;
- core::rect<s32> source_rect(
- core::position2d<s32>(0, 0),
- image->getOriginalSize());
- s32 imgh = source_rect.LowerRightCorner.Y;
- s32 rowh = row_rect.getHeight();
- if (imgh < rowh)
- dest_pos.Y += (rowh - imgh) / 2;
- else
- source_rect.LowerRightCorner.Y = rowh;
-
- video::SColor color(255, 255, 255, 255);
-
- driver->draw2DImage(image, dest_pos, source_rect,
- &client_clip, color, true);
- }
- }
-}
-
-bool GUITable::OnEvent(const SEvent &event)
-{
- if (!isEnabled())
- return IGUIElement::OnEvent(event);
-
- if (event.EventType == EET_KEY_INPUT_EVENT) {
- if (event.KeyInput.PressedDown && (
- event.KeyInput.Key == KEY_DOWN ||
- event.KeyInput.Key == KEY_UP ||
- event.KeyInput.Key == KEY_HOME ||
- event.KeyInput.Key == KEY_END ||
- event.KeyInput.Key == KEY_NEXT ||
- event.KeyInput.Key == KEY_PRIOR)) {
- s32 offset = 0;
- switch (event.KeyInput.Key) {
- case KEY_DOWN:
- offset = 1;
- break;
- case KEY_UP:
- offset = -1;
- break;
- case KEY_HOME:
- offset = - (s32) m_visible_rows.size();
- break;
- case KEY_END:
- offset = m_visible_rows.size();
- break;
- case KEY_NEXT:
- offset = AbsoluteRect.getHeight() / m_rowheight;
- break;
- case KEY_PRIOR:
- offset = - (s32) (AbsoluteRect.getHeight() / m_rowheight);
- break;
- default:
- break;
- }
- s32 old_selected = m_selected;
- s32 rowcount = m_visible_rows.size();
- if (rowcount != 0) {
- m_selected = rangelim(m_selected + offset, 0, rowcount-1);
- autoScroll();
- }
-
- if (m_selected != old_selected)
- sendTableEvent(0, false);
-
- return true;
- }
-
- if (event.KeyInput.PressedDown && (
- event.KeyInput.Key == KEY_LEFT ||
- event.KeyInput.Key == KEY_RIGHT)) {
- // Open/close subtree via keyboard
- if (m_selected >= 0) {
- int dir = event.KeyInput.Key == KEY_LEFT ? -1 : 1;
- toggleVisibleTree(m_selected, dir, true);
- }
- return true;
- }
- else if (!event.KeyInput.PressedDown && (
- event.KeyInput.Key == KEY_RETURN ||
- event.KeyInput.Key == KEY_SPACE)) {
- sendTableEvent(0, true);
- return true;
- }
- else if (event.KeyInput.Key == KEY_ESCAPE ||
- event.KeyInput.Key == KEY_SPACE) {
- // pass to parent
- }
- else if (event.KeyInput.PressedDown && event.KeyInput.Char) {
- // change selection based on text as it is typed
- u64 now = porting::getTimeMs();
- if (now - m_keynav_time >= 500)
- m_keynav_buffer = L"";
- m_keynav_time = now;
-
- // add to key buffer if not a key repeat
- if (!(m_keynav_buffer.size() == 1 &&
- m_keynav_buffer[0] == event.KeyInput.Char)) {
- m_keynav_buffer.append(event.KeyInput.Char);
- }
-
- // find the selected item, starting at the current selection
- // don't change selection if the key buffer matches the current item
- s32 old_selected = m_selected;
- s32 start = MYMAX(m_selected, 0);
- s32 rowcount = m_visible_rows.size();
- for (s32 k = 1; k < rowcount; ++k) {
- s32 current = start + k;
- if (current >= rowcount)
- current -= rowcount;
- if (doesRowStartWith(getRow(current), m_keynav_buffer)) {
- m_selected = current;
- break;
- }
- }
- autoScroll();
- if (m_selected != old_selected)
- sendTableEvent(0, false);
-
- return true;
- }
- }
- if (event.EventType == EET_MOUSE_INPUT_EVENT) {
- core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
-
- if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
- m_scrollbar->setPos(m_scrollbar->getPos() +
- (event.MouseInput.Wheel < 0 ? -3 : 3) *
- - (s32) m_rowheight / 2);
- return true;
- }
-
- // Find hovered row and cell
- bool really_hovering = false;
- s32 row_i = getRowAt(p.Y, really_hovering);
- const Cell *cell = NULL;
- if (really_hovering) {
- s32 cell_j = getCellAt(p.X, row_i);
- if (cell_j >= 0)
- cell = &(getRow(row_i)->cells[cell_j]);
- }
-
- // Update tooltip
- setToolTipText(cell ? m_strings[cell->tooltip_index].c_str() : L"");
-
- // Fix for #1567/#1806:
- // IGUIScrollBar passes double click events to its parent,
- // which we don't want. Detect this case and discard the event
- if (event.MouseInput.Event != EMIE_MOUSE_MOVED &&
- m_scrollbar->isVisible() &&
- m_scrollbar->isPointInside(p))
- return true;
-
- if (event.MouseInput.isLeftPressed() &&
- (isPointInside(p) ||
- event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
- s32 sel_column = 0;
- bool sel_doubleclick = (event.MouseInput.Event
- == EMIE_LMOUSE_DOUBLE_CLICK);
- bool plusminus_clicked = false;
-
- // For certain events (left click), report column
- // Also open/close subtrees when the +/- is clicked
- if (cell && (
- event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN ||
- event.MouseInput.Event == EMIE_LMOUSE_DOUBLE_CLICK ||
- event.MouseInput.Event == EMIE_LMOUSE_TRIPLE_CLICK)) {
- sel_column = cell->reported_column;
- if (cell->content_type == COLUMN_TYPE_TREE)
- plusminus_clicked = true;
- }
-
- if (plusminus_clicked) {
- if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
- toggleVisibleTree(row_i, 0, false);
- }
- }
- else {
- // Normal selection
- s32 old_selected = m_selected;
- m_selected = row_i;
- autoScroll();
-
- if (m_selected != old_selected ||
- sel_column >= 1 ||
- sel_doubleclick) {
- sendTableEvent(sel_column, sel_doubleclick);
- }
-
- // Treeview: double click opens/closes trees
- if (m_has_tree_column && sel_doubleclick) {
- toggleVisibleTree(m_selected, 0, false);
- }
- }
- }
- return true;
- }
- if (event.EventType == EET_GUI_EVENT &&
- event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED &&
- event.GUIEvent.Caller == m_scrollbar) {
- // Don't pass events from our scrollbar to the parent
- return true;
- }
-
- return IGUIElement::OnEvent(event);
-}
-
-/******************************************************************************/
-/* GUITable helper functions */
-/******************************************************************************/
-
-s32 GUITable::allocString(const std::string &text)
-{
- std::map<std::string, s32>::iterator it = m_alloc_strings.find(text);
- if (it == m_alloc_strings.end()) {
- s32 id = m_strings.size();
- std::wstring wtext = utf8_to_wide(text);
- m_strings.emplace_back(wtext.c_str());
- m_alloc_strings.insert(std::make_pair(text, id));
- return id;
- }
-
- return it->second;
-}
-
-s32 GUITable::allocImage(const std::string &imagename)
-{
- std::map<std::string, s32>::iterator it = m_alloc_images.find(imagename);
- if (it == m_alloc_images.end()) {
- s32 id = m_images.size();
- m_images.push_back(m_tsrc->getTexture(imagename));
- m_alloc_images.insert(std::make_pair(imagename, id));
- return id;
- }
-
- return it->second;
-}
-
-void GUITable::allocationComplete()
-{
- // Called when done with creating rows and cells from table data,
- // i.e. when allocString and allocImage won't be called anymore
- m_alloc_strings.clear();
- m_alloc_images.clear();
-}
-
-const GUITable::Row* GUITable::getRow(s32 i) const
-{
- if (i >= 0 && i < (s32) m_visible_rows.size())
- return &m_rows[m_visible_rows[i]];
-
- return NULL;
-}
-
-bool GUITable::doesRowStartWith(const Row *row, const core::stringw &str) const
-{
- if (row == NULL)
- return false;
-
- for (s32 j = 0; j < row->cellcount; ++j) {
- Cell *cell = &row->cells[j];
- if (cell->content_type == COLUMN_TYPE_TEXT) {
- const core::stringw &cellstr = m_strings[cell->content_index];
- if (cellstr.size() >= str.size() &&
- str.equals_ignore_case(cellstr.subString(0, str.size())))
- return true;
- }
- }
- return false;
-}
-
-s32 GUITable::getRowAt(s32 y, bool &really_hovering) const
-{
- really_hovering = false;
-
- s32 rowcount = m_visible_rows.size();
- if (rowcount == 0)
- return -1;
-
- // Use arithmetic to find row
- s32 rel_y = y - AbsoluteRect.UpperLeftCorner.Y - 1;
- s32 i = (rel_y + m_scrollbar->getPos()) / m_rowheight;
-
- if (i >= 0 && i < rowcount) {
- really_hovering = true;
- return i;
- }
- if (i < 0)
- return 0;
-
- return rowcount - 1;
-}
-
-s32 GUITable::getCellAt(s32 x, s32 row_i) const
-{
- const Row *row = getRow(row_i);
- if (row == NULL)
- return -1;
-
- // Use binary search to find cell in row
- s32 rel_x = x - AbsoluteRect.UpperLeftCorner.X - 1;
- s32 jmin = 0;
- s32 jmax = row->cellcount - 1;
- while (jmin < jmax) {
- s32 pivot = jmin + (jmax - jmin) / 2;
- assert(pivot >= 0 && pivot < row->cellcount);
- const Cell *cell = &row->cells[pivot];
-
- if (rel_x >= cell->xmin && rel_x <= cell->xmax)
- return pivot;
-
- if (rel_x < cell->xmin)
- jmax = pivot - 1;
- else
- jmin = pivot + 1;
- }
-
- if (jmin >= 0 && jmin < row->cellcount &&
- rel_x >= row->cells[jmin].xmin &&
- rel_x <= row->cells[jmin].xmax)
- return jmin;
-
- return -1;
-}
-
-void GUITable::autoScroll()
-{
- if (m_selected >= 0) {
- s32 pos = m_scrollbar->getPos();
- s32 maxpos = m_selected * m_rowheight;
- s32 minpos = maxpos - (AbsoluteRect.getHeight() - m_rowheight);
- if (pos > maxpos)
- m_scrollbar->setPos(maxpos);
- else if (pos < minpos)
- m_scrollbar->setPos(minpos);
- }
-}
-
-void GUITable::updateScrollBar()
-{
- s32 totalheight = m_rowheight * m_visible_rows.size();
- s32 scrollmax = MYMAX(0, totalheight - AbsoluteRect.getHeight());
- m_scrollbar->setVisible(scrollmax > 0);
- m_scrollbar->setMax(scrollmax);
- m_scrollbar->setSmallStep(m_rowheight);
- m_scrollbar->setLargeStep(2 * m_rowheight);
-}
-
-void GUITable::sendTableEvent(s32 column, bool doubleclick)
-{
- m_sel_column = column;
- m_sel_doubleclick = doubleclick;
- if (Parent) {
- SEvent e;
- memset(&e, 0, sizeof e);
- e.EventType = EET_GUI_EVENT;
- e.GUIEvent.Caller = this;
- e.GUIEvent.Element = 0;
- e.GUIEvent.EventType = gui::EGET_TABLE_CHANGED;
- Parent->OnEvent(e);
- }
-}
-
-void GUITable::getOpenedTrees(std::set<s32> &opened_trees) const
-{
- opened_trees.clear();
- s32 rowcount = m_rows.size();
- for (s32 i = 0; i < rowcount - 1; ++i) {
- if (m_rows[i].indent < m_rows[i+1].indent &&
- m_rows[i+1].visible_index != -2)
- opened_trees.insert(i);
- }
-}
-
-void GUITable::setOpenedTrees(const std::set<s32> &opened_trees)
-{
- s32 old_selected = -1;
- if (m_selected >= 0)
- old_selected = m_visible_rows[m_selected];
-
- std::vector<s32> parents;
- std::vector<s32> closed_parents;
-
- m_visible_rows.clear();
-
- for (size_t i = 0; i < m_rows.size(); ++i) {
- Row *row = &m_rows[i];
-
- // Update list of ancestors
- while (!parents.empty() && m_rows[parents.back()].indent >= row->indent)
- parents.pop_back();
- while (!closed_parents.empty() &&
- m_rows[closed_parents.back()].indent >= row->indent)
- closed_parents.pop_back();
-
- assert(closed_parents.size() <= parents.size());
-
- if (closed_parents.empty()) {
- // Visible row
- row->visible_index = m_visible_rows.size();
- m_visible_rows.push_back(i);
- }
- else if (parents.back() == closed_parents.back()) {
- // Invisible row, direct parent is closed
- row->visible_index = -2;
- }
- else {
- // Invisible row, direct parent is open, some ancestor is closed
- row->visible_index = -1;
- }
-
- // If not a leaf, add to parents list
- if (i < m_rows.size()-1 && row->indent < m_rows[i+1].indent) {
- parents.push_back(i);
-
- s32 content_index = 0; // "-", open
- if (opened_trees.count(i) == 0) {
- closed_parents.push_back(i);
- content_index = 1; // "+", closed
- }
-
- // Update all cells of type "tree"
- for (s32 j = 0; j < row->cellcount; ++j)
- if (row->cells[j].content_type == COLUMN_TYPE_TREE)
- row->cells[j].content_index = content_index;
- }
- }
-
- updateScrollBar();
-
- // m_selected must be updated since it is a visible row index
- if (old_selected >= 0)
- m_selected = m_rows[old_selected].visible_index;
-}
-
-void GUITable::openTree(s32 to_open)
-{
- std::set<s32> opened_trees;
- getOpenedTrees(opened_trees);
- opened_trees.insert(to_open);
- setOpenedTrees(opened_trees);
-}
-
-void GUITable::closeTree(s32 to_close)
-{
- std::set<s32> opened_trees;
- getOpenedTrees(opened_trees);
- opened_trees.erase(to_close);
- setOpenedTrees(opened_trees);
-}
-
-// The following function takes a visible row index (hidden rows skipped)
-// dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
-void GUITable::toggleVisibleTree(s32 row_i, int dir, bool move_selection)
-{
- // Check if the chosen tree is currently open
- const Row *row = getRow(row_i);
- if (row == NULL)
- return;
-
- bool was_open = false;
- for (s32 j = 0; j < row->cellcount; ++j) {
- if (row->cells[j].content_type == COLUMN_TYPE_TREE) {
- was_open = row->cells[j].content_index == 0;
- break;
- }
- }
-
- // Check if the chosen tree should be opened
- bool do_open = !was_open;
- if (dir < 0)
- do_open = false;
- else if (dir > 0)
- do_open = true;
-
- // Close or open the tree; the heavy lifting is done by setOpenedTrees
- if (was_open && !do_open)
- closeTree(m_visible_rows[row_i]);
- else if (!was_open && do_open)
- openTree(m_visible_rows[row_i]);
-
- // Change selected row if requested by caller,
- // this is useful for keyboard navigation
- if (move_selection) {
- s32 sel = row_i;
- if (was_open && do_open) {
- // Move selection to first child
- const Row *maybe_child = getRow(sel + 1);
- if (maybe_child && maybe_child->indent > row->indent)
- sel++;
- }
- else if (!was_open && !do_open) {
- // Move selection to parent
- assert(getRow(sel) != NULL);
- while (sel > 0 && getRow(sel - 1)->indent >= row->indent)
- sel--;
- sel--;
- if (sel < 0) // was root already selected?
- sel = row_i;
- }
- if (sel != m_selected) {
- m_selected = sel;
- autoScroll();
- sendTableEvent(0, false);
- }
- }
-}
-
-void GUITable::alignContent(Cell *cell, s32 xmax, s32 content_width, s32 align)
-{
- // requires that cell.xmin, cell.xmax are properly set
- // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
- if (align == 0) {
- cell->xpos = cell->xmin;
- cell->xmax = xmax;
- }
- else if (align == 1) {
- cell->xpos = (cell->xmin + xmax - content_width) / 2;
- cell->xmax = xmax;
- }
- else if (align == 2) {
- cell->xpos = xmax - content_width;
- cell->xmax = xmax;
- }
- else {
- // inline alignment: the cells of the column don't have an aligned
- // right border, the right border of each cell depends on the content
- cell->xpos = cell->xmin;
- cell->xmax = cell->xmin + content_width;
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <map>
-#include <set>
-#include <string>
-#include <vector>
-#include <iostream>
-
-#include "irrlichttypes_extrabloated.h"
-
-class ISimpleTextureSource;
-
-/*
- A table GUI element for GUIFormSpecMenu.
-
- Sends a EGET_TABLE_CHANGED event to the parent when
- an item is selected or double-clicked.
- Call checkEvent() to get info.
-
- Credits: The interface and implementation of this class are (very)
- loosely based on the Irrlicht classes CGUITable and CGUIListBox.
- CGUITable and CGUIListBox are licensed under the Irrlicht license;
- they are Copyright (C) 2002-2012 Nikolaus Gebhardt
-*/
-class GUITable : public gui::IGUIElement
-{
-public:
- /*
- Stores dynamic data that should be preserved
- when updating a formspec
- */
- struct DynamicData
- {
- s32 selected = 0;
- s32 scrollpos = 0;
- s32 keynav_time = 0;
- core::stringw keynav_buffer;
- std::set<s32> opened_trees;
- };
-
- /*
- An option of the form <name>=<value>
- */
- struct Option
- {
- std::string name;
- std::string value;
-
- Option(const std::string &name_, const std::string &value_) :
- name(name_),
- value(value_)
- {}
- };
-
- /*
- A list of options that concern the entire table
- */
- typedef std::vector<Option> TableOptions;
-
- /*
- A column with options
- */
- struct TableColumn
- {
- std::string type;
- std::vector<Option> options;
- };
- typedef std::vector<TableColumn> TableColumns;
-
-
- GUITable(gui::IGUIEnvironment *env,
- gui::IGUIElement *parent, s32 id,
- core::rect<s32> rectangle,
- ISimpleTextureSource *tsrc);
-
- virtual ~GUITable();
-
- /* Split a string of the form "name=value" into name and value */
- static Option splitOption(const std::string &str);
-
- /* Set textlist-like options, columns and data */
- void setTextList(const std::vector<std::string> &content,
- bool transparent);
-
- /* Set generic table options, columns and content */
- // Adds empty strings to end of content if there is an incomplete row
- void setTable(const TableOptions &options,
- const TableColumns &columns,
- std::vector<std::string> &content);
-
- /* Clear the table */
- void clear();
-
- /* Get info about last event (string such as "CHG:1:2") */
- // Call this after EGET_TABLE_CHANGED
- std::string checkEvent();
-
- /* Get index of currently selected row (first=1; 0 if none selected) */
- s32 getSelected() const;
-
- /* Set currently selected row (first=1; 0 if none selected) */
- // If given index is not visible at the moment, select its parent
- // Autoscroll to make the selected row fully visible
- void setSelected(s32 index);
-
- /* Get selection, scroll position and opened (sub)trees */
- DynamicData getDynamicData() const;
-
- /* Set selection, scroll position and opened (sub)trees */
- void setDynamicData(const DynamicData &dyndata);
-
- /* Returns "GUITable" */
- virtual const c8* getTypeName() const;
-
- /* Must be called when position or size changes */
- virtual void updateAbsolutePosition();
-
- /* Irrlicht draw method */
- virtual void draw();
-
- /* Irrlicht event handler */
- virtual bool OnEvent(const SEvent &event);
-
-protected:
- enum ColumnType {
- COLUMN_TYPE_TEXT,
- COLUMN_TYPE_IMAGE,
- COLUMN_TYPE_COLOR,
- COLUMN_TYPE_INDENT,
- COLUMN_TYPE_TREE,
- };
-
- struct Cell {
- s32 xmin;
- s32 xmax;
- s32 xpos;
- ColumnType content_type;
- s32 content_index;
- s32 tooltip_index;
- video::SColor color;
- bool color_defined;
- s32 reported_column;
- };
-
- struct Row {
- Cell *cells;
- s32 cellcount;
- s32 indent;
- // visible_index >= 0: is index of row in m_visible_rows
- // visible_index == -1: parent open but other ancestor closed
- // visible_index == -2: parent closed
- s32 visible_index;
- };
-
- // Texture source
- ISimpleTextureSource *m_tsrc;
-
- // Table content (including hidden rows)
- std::vector<Row> m_rows;
- // Table content (only visible; indices into m_rows)
- std::vector<s32> m_visible_rows;
- bool m_is_textlist = false;
- bool m_has_tree_column = false;
-
- // Selection status
- s32 m_selected = -1; // index of row (1...n), or 0 if none selected
- s32 m_sel_column = 0;
- bool m_sel_doubleclick = false;
-
- // Keyboard navigation stuff
- u64 m_keynav_time = 0;
- core::stringw m_keynav_buffer = L"";
-
- // Drawing and geometry information
- bool m_border = true;
- video::SColor m_color = video::SColor(255, 255, 255, 255);
- video::SColor m_background = video::SColor(255, 0, 0, 0);
- video::SColor m_highlight = video::SColor(255, 70, 100, 50);
- video::SColor m_highlight_text = video::SColor(255, 255, 255, 255);
- s32 m_rowheight = 1;
- gui::IGUIFont *m_font = nullptr;
- gui::IGUIScrollBar *m_scrollbar = nullptr;
-
- // Allocated strings and images
- std::vector<core::stringw> m_strings;
- std::vector<video::ITexture*> m_images;
- std::map<std::string, s32> m_alloc_strings;
- std::map<std::string, s32> m_alloc_images;
-
- s32 allocString(const std::string &text);
- s32 allocImage(const std::string &imagename);
- void allocationComplete();
-
- // Helper for draw() that draws a single cell
- void drawCell(const Cell *cell, video::SColor color,
- const core::rect<s32> &rowrect,
- const core::rect<s32> &client_clip);
-
- // Returns the i-th visible row (NULL if i is invalid)
- const Row *getRow(s32 i) const;
-
- // Key navigation helper
- bool doesRowStartWith(const Row *row, const core::stringw &str) const;
-
- // Returns the row at a given screen Y coordinate
- // Returns index i such that m_rows[i] is valid (or -1 on error)
- s32 getRowAt(s32 y, bool &really_hovering) const;
-
- // Returns the cell at a given screen X coordinate within m_rows[row_i]
- // Returns index j such that m_rows[row_i].cells[j] is valid
- // (or -1 on error)
- s32 getCellAt(s32 x, s32 row_i) const;
-
- // Make the selected row fully visible
- void autoScroll();
-
- // Should be called when m_rowcount or m_rowheight changes
- void updateScrollBar();
-
- // Sends EET_GUI_EVENT / EGET_TABLE_CHANGED to parent
- void sendTableEvent(s32 column, bool doubleclick);
-
- // Functions that help deal with hidden rows
- // The following functions take raw row indices (hidden rows not skipped)
- void getOpenedTrees(std::set<s32> &opened_trees) const;
- void setOpenedTrees(const std::set<s32> &opened_trees);
- void openTree(s32 to_open);
- void closeTree(s32 to_close);
- // The following function takes a visible row index (hidden rows skipped)
- // dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
- void toggleVisibleTree(s32 row_i, int dir, bool move_selection);
-
- // Aligns cell content in column according to alignment specification
- // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
- static void alignContent(Cell *cell, s32 xmax, s32 content_width,
- s32 align);
-};
+++ /dev/null
-/*
-Part of Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
-Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
-
-Permission to use, copy, modify, and distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-#include "guiVolumeChange.h"
-#include "debug.h"
-#include "serialization.h"
-#include <string>
-#include <IGUICheckBox.h>
-#include <IGUIButton.h>
-#include <IGUIScrollBar.h>
-#include <IGUIStaticText.h>
-#include <IGUIFont.h>
-#include "settings.h"
-
-#include "gettext.h"
-
-const int ID_soundText = 263;
-const int ID_soundExitButton = 264;
-const int ID_soundSlider = 265;
-const int ID_soundMuteButton = 266;
-
-GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env,
- gui::IGUIElement* parent, s32 id,
- IMenuManager *menumgr
-):
- GUIModalMenu(env, parent, id, menumgr)
-{
-}
-
-GUIVolumeChange::~GUIVolumeChange()
-{
- removeChildren();
-}
-
-void GUIVolumeChange::removeChildren()
-{
- if (gui::IGUIElement *e = getElementFromId(ID_soundText))
- e->remove();
-
- if (gui::IGUIElement *e = getElementFromId(ID_soundExitButton))
- e->remove();
-
- if (gui::IGUIElement *e = getElementFromId(ID_soundSlider))
- e->remove();
-}
-
-void GUIVolumeChange::regenerateGui(v2u32 screensize)
-{
- /*
- Remove stuff
- */
- removeChildren();
-
- /*
- Calculate new sizes and positions
- */
- DesiredRect = core::rect<s32>(
- screensize.X/2 - 380/2,
- screensize.Y/2 - 200/2,
- screensize.X/2 + 380/2,
- screensize.Y/2 + 200/2
- );
- recalculateAbsolutePosition(false);
-
- v2s32 size = DesiredRect.getSize();
- int volume = (int)(g_settings->getFloat("sound_volume") * 100);
-
- /*
- Add stuff
- */
- {
- core::rect<s32> rect(0, 0, 160, 20);
- rect = rect + v2s32(size.X / 2 - 80, size.Y / 2 - 70);
-
- const wchar_t *text = wgettext("Sound Volume: ");
- core::stringw volume_text = text;
- delete [] text;
-
- volume_text += core::stringw(volume) + core::stringw("%");
- Environment->addStaticText(volume_text.c_str(), rect, false,
- true, this, ID_soundText);
- }
- {
- core::rect<s32> rect(0, 0, 80, 30);
- rect = rect + v2s32(size.X/2-80/2, size.Y/2+55);
- const wchar_t *text = wgettext("Exit");
- Environment->addButton(rect, this, ID_soundExitButton,
- text);
- delete[] text;
- }
- {
- core::rect<s32> rect(0, 0, 300, 20);
- rect = rect + v2s32(size.X / 2 - 150, size.Y / 2);
- gui::IGUIScrollBar *e = Environment->addScrollBar(true,
- rect, this, ID_soundSlider);
- e->setMax(100);
- e->setPos(volume);
- }
- {
- core::rect<s32> rect(0, 0, 160, 20);
- rect = rect + v2s32(size.X / 2 - 80, size.Y / 2 - 35);
- const wchar_t *text = wgettext("Muted");
- Environment->addCheckBox(g_settings->getBool("mute_sound"), rect, this,
- ID_soundMuteButton, text);
- delete[] text;
- }
-}
-
-void GUIVolumeChange::drawMenu()
-{
- gui::IGUISkin* skin = Environment->getSkin();
- if (!skin)
- return;
- video::IVideoDriver* driver = Environment->getVideoDriver();
- video::SColor bgcolor(140, 0, 0, 0);
- driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
- gui::IGUIElement::draw();
-}
-
-bool GUIVolumeChange::OnEvent(const SEvent& event)
-{
- if (event.EventType == EET_KEY_INPUT_EVENT) {
- if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
- quitMenu();
- return true;
- }
-
- if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
- quitMenu();
- return true;
- }
- } else if (event.EventType == EET_GUI_EVENT) {
- if (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) {
- gui::IGUIElement *e = getElementFromId(ID_soundMuteButton);
- if (e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) {
- g_settings->setBool("mute_sound", ((gui::IGUICheckBox*)e)->isChecked());
- }
-
- Environment->setFocus(this);
- return true;
- }
-
- if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
- if (event.GUIEvent.Caller->getID() == ID_soundExitButton) {
- quitMenu();
- return true;
- }
- Environment->setFocus(this);
- }
-
- if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
- && isVisible()) {
- if (!canTakeFocus(event.GUIEvent.Element)) {
- dstream << "GUIMainMenu: Not allowing focus change."
- << std::endl;
- // Returning true disables focus change
- return true;
- }
- }
- if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
- if (event.GUIEvent.Caller->getID() == ID_soundSlider) {
- s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
- g_settings->setFloat("sound_volume", (float) pos / 100);
-
- gui::IGUIElement *e = getElementFromId(ID_soundText);
- const wchar_t *text = wgettext("Sound Volume: ");
- core::stringw volume_text = text;
- delete [] text;
-
- volume_text += core::stringw(pos) + core::stringw("%");
- e->setText(volume_text.c_str());
- return true;
- }
- }
-
- }
-
- return Parent ? Parent->OnEvent(event) : false;
-}
-
+++ /dev/null
-/*
-Part of Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
-Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
-
-Permission to use, copy, modify, and distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "modalMenu.h"
-#include <string>
-
-class GUIVolumeChange : public GUIModalMenu
-{
-public:
- GUIVolumeChange(gui::IGUIEnvironment* env,
- gui::IGUIElement* parent, s32 id,
- IMenuManager *menumgr);
- ~GUIVolumeChange();
-
- void removeChildren();
- /*
- Remove and re-add (or reposition) stuff
- */
- void regenerateGui(v2u32 screensize);
-
- void drawMenu();
-
- bool OnEvent(const SEvent& event);
-
- bool pausesGame() { return true; }
-};
#include "client/renderingengine.h"
#ifdef HAVE_TOUCHSCREENGUI
-#include "touchscreengui.h"
+#include "gui/touchscreengui.h"
#endif
Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player,
+++ /dev/null
-// 11.11.2011 11:11 ValkaTR
-//
-// This is a copy of intlGUIEditBox from the irrlicht, but with a
-// fix in the OnEvent function, which doesn't allowed input of
-// other keyboard layouts than latin-1
-//
-// Characters like: ä ö ü õ ы й ю я ъ № € ° ...
-//
-// This fix is only needed for linux, because of a bug
-// in the CIrrDeviceLinux.cpp:1014-1015 of the irrlicht
-//
-// Also locale in the programm should not be changed to
-// a "C", "POSIX" or whatever, it should be set to "",
-// or XLookupString will return nothing for the international
-// characters.
-//
-// From the "man setlocale":
-//
-// On startup of the main program, the portable "C" locale
-// is selected as default. A program may be made
-// portable to all locales by calling:
-//
-// setlocale(LC_ALL, "");
-//
-// after program initialization....
-//
-
-// Copyright (C) 2002-2013 Nikolaus Gebhardt
-// This file is part of the "Irrlicht Engine".
-// For conditions of distribution and use, see copyright notice in irrlicht.h
-
-#include <util/numeric.h>
-#include "intlGUIEditBox.h"
-
-#if defined(_IRR_COMPILE_WITH_GUI_) && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
-
-#include "IGUISkin.h"
-#include "IGUIEnvironment.h"
-#include "IGUIFont.h"
-#include "IVideoDriver.h"
-//#include "rect.h"
-//#include "irrlicht/os.cpp"
-#include "porting.h"
-//#include "Keycodes.h"
-#include "log.h"
-
-/*
- todo:
- optional scrollbars
- ctrl+left/right to select word
- double click/ctrl click: word select + drag to select whole words, triple click to select line
- optional? dragging selected text
- numerical
-*/
-
-namespace irr
-{
-namespace gui
-{
-
-//! constructor
-intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border,
- IGUIEnvironment* environment, IGUIElement* parent, s32 id,
- const core::rect<s32>& rectangle, bool writable, bool has_vscrollbar)
- : IGUIEditBox(environment, parent, id, rectangle),
- Border(border), FrameRect(rectangle),
- m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable)
-{
- #ifdef _DEBUG
- setDebugName("intlintlGUIEditBox");
- #endif
-
- Text = text;
-
- if (Environment)
- Operator = Environment->getOSOperator();
-
- if (Operator)
- Operator->grab();
-
- // this element can be tabbed to
- setTabStop(true);
- setTabOrder(-1);
-
- IGUISkin *skin = 0;
- if (Environment)
- skin = Environment->getSkin();
- if (Border && skin)
- {
- FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
- FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
- FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
- FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
- }
-
- if (skin && has_vscrollbar) {
- m_scrollbar_width = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
-
- if (m_scrollbar_width > 0) {
- createVScrollBar();
- }
- }
-
- breakText();
-
- calculateScrollPos();
- setWritable(writable);
-}
-
-
-//! destructor
-intlGUIEditBox::~intlGUIEditBox()
-{
- if (OverrideFont)
- OverrideFont->drop();
-
- if (Operator)
- Operator->drop();
-}
-
-
-//! Sets another skin independent font.
-void intlGUIEditBox::setOverrideFont(IGUIFont* font)
-{
- if (OverrideFont == font)
- return;
-
- if (OverrideFont)
- OverrideFont->drop();
-
- OverrideFont = font;
-
- if (OverrideFont)
- OverrideFont->grab();
-
- breakText();
-}
-
-IGUIFont * intlGUIEditBox::getOverrideFont() const
-{
- return OverrideFont;
-}
-
-//! Get the font which is used right now for drawing
-IGUIFont* intlGUIEditBox::getActiveFont() const
-{
- if ( OverrideFont )
- return OverrideFont;
- IGUISkin* skin = Environment->getSkin();
- if (skin)
- return skin->getFont();
- return 0;
-}
-
-//! Sets another color for the text.
-void intlGUIEditBox::setOverrideColor(video::SColor color)
-{
- OverrideColor = color;
- OverrideColorEnabled = true;
-}
-
-video::SColor intlGUIEditBox::getOverrideColor() const
-{
- return OverrideColor;
-}
-
-//! Turns the border on or off
-void intlGUIEditBox::setDrawBorder(bool border)
-{
- Border = border;
-}
-
-//! Sets whether to draw the background
-void intlGUIEditBox::setDrawBackground(bool draw)
-{
-}
-
-//! Sets if the text should use the overide color or the color in the gui skin.
-void intlGUIEditBox::enableOverrideColor(bool enable)
-{
- OverrideColorEnabled = enable;
-}
-
-bool intlGUIEditBox::isOverrideColorEnabled() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return OverrideColorEnabled;
-}
-
-//! Enables or disables word wrap
-void intlGUIEditBox::setWordWrap(bool enable)
-{
- WordWrap = enable;
- breakText();
-}
-
-
-void intlGUIEditBox::updateAbsolutePosition()
-{
- core::rect<s32> oldAbsoluteRect(AbsoluteRect);
- IGUIElement::updateAbsolutePosition();
- if ( oldAbsoluteRect != AbsoluteRect )
- {
- breakText();
- }
-}
-
-
-//! Checks if word wrap is enabled
-bool intlGUIEditBox::isWordWrapEnabled() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return WordWrap;
-}
-
-
-//! Enables or disables newlines.
-void intlGUIEditBox::setMultiLine(bool enable)
-{
- MultiLine = enable;
-}
-
-
-//! Checks if multi line editing is enabled
-bool intlGUIEditBox::isMultiLineEnabled() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return MultiLine;
-}
-
-
-void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar)
-{
- PasswordBox = passwordBox;
- if (PasswordBox)
- {
- PasswordChar = passwordChar;
- setMultiLine(false);
- setWordWrap(false);
- BrokenText.clear();
- }
-}
-
-
-bool intlGUIEditBox::isPasswordBox() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return PasswordBox;
-}
-
-
-//! Sets text justification
-void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
-{
- HAlign = horizontal;
- VAlign = vertical;
-}
-
-
-//! called if an event happened.
-bool intlGUIEditBox::OnEvent(const SEvent& event)
-{
- if (IsEnabled)
- {
-
- switch(event.EventType)
- {
- case EET_GUI_EVENT:
- if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
- {
- if (event.GUIEvent.Caller == this)
- {
- MouseMarking = false;
- setTextMarkers(0,0);
- }
- }
- break;
- case EET_KEY_INPUT_EVENT:
- {
-#if (defined(__linux__) || defined(__FreeBSD__))
- // ################################################################
- // ValkaTR:
- // This part is the difference from the original intlGUIEditBox
- // It converts UTF-8 character into a UCS-2 (wchar_t)
- wchar_t wc = L'_';
- mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
-
- //printf( "char: %lc (%u) \r\n", wc, wc );
-
- SEvent irrevent(event);
- irrevent.KeyInput.Char = wc;
- // ################################################################
-
- if (processKey(irrevent))
- return true;
-#else
- if (processKey(event))
- return true;
-#endif // defined(linux)
-
- break;
- }
- case EET_MOUSE_INPUT_EVENT:
- if (processMouse(event))
- return true;
- break;
- default:
- break;
- }
- }
-
- return IGUIElement::OnEvent(event);
-}
-
-
-bool intlGUIEditBox::processKey(const SEvent& event)
-{
- if (!event.KeyInput.PressedDown)
- return false;
-
- bool textChanged = false;
- s32 newMarkBegin = MarkBegin;
- s32 newMarkEnd = MarkEnd;
-
- // control shortcut handling
-
- if (event.KeyInput.Control)
- {
- // german backlash '\' entered with control + '?'
- if ( event.KeyInput.Char == '\\' )
- {
- inputChar(event.KeyInput.Char);
- return true;
- }
-
- switch(event.KeyInput.Key)
- {
- case KEY_KEY_A:
- // select all
- newMarkBegin = 0;
- newMarkEnd = Text.size();
- break;
- case KEY_KEY_C:
- // copy to clipboard
- if (!PasswordBox && Operator && MarkBegin != MarkEnd)
- {
- const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
- const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
-
- core::stringc s;
- s = Text.subString(realmbgn, realmend - realmbgn).c_str();
- Operator->copyToClipboard(s.c_str());
- }
- break;
- case KEY_KEY_X:
- // cut to the clipboard
- if (!PasswordBox && Operator && MarkBegin != MarkEnd)
- {
- const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
- const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
-
- // copy
- core::stringc sc;
- sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
- Operator->copyToClipboard(sc.c_str());
-
- if (IsEnabled)
- {
- // delete
- core::stringw s;
- s = Text.subString(0, realmbgn);
- s.append( Text.subString(realmend, Text.size()-realmend) );
- Text = s;
-
- CursorPos = realmbgn;
- newMarkBegin = 0;
- newMarkEnd = 0;
- textChanged = true;
- }
- }
- break;
- case KEY_KEY_V:
- if ( !IsEnabled )
- break;
-
- // paste from the clipboard
- if (Operator)
- {
- const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
- const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
-
- // add new character
- const c8* p = Operator->getTextFromClipboard();
- if (p)
- {
- if (MarkBegin == MarkEnd)
- {
- // insert text
- core::stringw s = Text.subString(0, CursorPos);
- s.append(p);
- s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
-
- if (!Max || s.size()<=Max) // thx to Fish FH for fix
- {
- Text = s;
- s = p;
- CursorPos += s.size();
- }
- }
- else
- {
- // replace text
-
- core::stringw s = Text.subString(0, realmbgn);
- s.append(p);
- s.append( Text.subString(realmend, Text.size()-realmend) );
-
- if (!Max || s.size()<=Max) // thx to Fish FH for fix
- {
- Text = s;
- s = p;
- CursorPos = realmbgn + s.size();
- }
- }
- }
-
- newMarkBegin = 0;
- newMarkEnd = 0;
- textChanged = true;
- }
- break;
- case KEY_HOME:
- // move/highlight to start of text
- if (event.KeyInput.Shift)
- {
- newMarkEnd = CursorPos;
- newMarkBegin = 0;
- CursorPos = 0;
- }
- else
- {
- CursorPos = 0;
- newMarkBegin = 0;
- newMarkEnd = 0;
- }
- break;
- case KEY_END:
- // move/highlight to end of text
- if (event.KeyInput.Shift)
- {
- newMarkBegin = CursorPos;
- newMarkEnd = Text.size();
- CursorPos = 0;
- }
- else
- {
- CursorPos = Text.size();
- newMarkBegin = 0;
- newMarkEnd = 0;
- }
- break;
- default:
- return false;
- }
- }
- // default keyboard handling
- else
- switch(event.KeyInput.Key)
- {
- case KEY_END:
- {
- s32 p = Text.size();
- if (WordWrap || MultiLine)
- {
- p = getLineFromPos(CursorPos);
- p = BrokenTextPositions[p] + (s32)BrokenText[p].size();
- if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' ))
- p-=1;
- }
-
- if (event.KeyInput.Shift)
- {
- if (MarkBegin == MarkEnd)
- newMarkBegin = CursorPos;
-
- newMarkEnd = p;
- }
- else
- {
- newMarkBegin = 0;
- newMarkEnd = 0;
- }
- CursorPos = p;
- BlinkStartTime = porting::getTimeMs();
- }
- break;
- case KEY_HOME:
- {
-
- s32 p = 0;
- if (WordWrap || MultiLine)
- {
- p = getLineFromPos(CursorPos);
- p = BrokenTextPositions[p];
- }
-
- if (event.KeyInput.Shift)
- {
- if (MarkBegin == MarkEnd)
- newMarkBegin = CursorPos;
- newMarkEnd = p;
- }
- else
- {
- newMarkBegin = 0;
- newMarkEnd = 0;
- }
- CursorPos = p;
- BlinkStartTime = porting::getTimeMs();
- }
- break;
- case KEY_RETURN:
- if (MultiLine)
- {
- inputChar(L'\n');
- return true;
- }
- else
- {
- sendGuiEvent( EGET_EDITBOX_ENTER );
- }
- break;
- case KEY_LEFT:
-
- if (event.KeyInput.Shift)
- {
- if (CursorPos > 0)
- {
- if (MarkBegin == MarkEnd)
- newMarkBegin = CursorPos;
-
- newMarkEnd = CursorPos-1;
- }
- }
- else
- {
- newMarkBegin = 0;
- newMarkEnd = 0;
- }
-
- if (CursorPos > 0) CursorPos--;
- BlinkStartTime = porting::getTimeMs();
- break;
-
- case KEY_RIGHT:
- if (event.KeyInput.Shift)
- {
- if (Text.size() > (u32)CursorPos)
- {
- if (MarkBegin == MarkEnd)
- newMarkBegin = CursorPos;
-
- newMarkEnd = CursorPos+1;
- }
- }
- else
- {
- newMarkBegin = 0;
- newMarkEnd = 0;
- }
-
- if (Text.size() > (u32)CursorPos) CursorPos++;
- BlinkStartTime = porting::getTimeMs();
- break;
- case KEY_UP:
- if (MultiLine || (WordWrap && BrokenText.size() > 1) )
- {
- s32 lineNo = getLineFromPos(CursorPos);
- s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd);
- if (lineNo > 0)
- {
- s32 cp = CursorPos - BrokenTextPositions[lineNo];
- if ((s32)BrokenText[lineNo-1].size() < cp)
- CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1;
- else
- CursorPos = BrokenTextPositions[lineNo-1] + cp;
- }
-
- if (event.KeyInput.Shift)
- {
- newMarkBegin = mb;
- newMarkEnd = CursorPos;
- }
- else
- {
- newMarkBegin = 0;
- newMarkEnd = 0;
- }
-
- }
- else
- {
- return false;
- }
- break;
- case KEY_DOWN:
- if (MultiLine || (WordWrap && BrokenText.size() > 1) )
- {
- s32 lineNo = getLineFromPos(CursorPos);
- s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd);
- if (lineNo < (s32)BrokenText.size()-1)
- {
- s32 cp = CursorPos - BrokenTextPositions[lineNo];
- if ((s32)BrokenText[lineNo+1].size() < cp)
- CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1;
- else
- CursorPos = BrokenTextPositions[lineNo+1] + cp;
- }
-
- if (event.KeyInput.Shift)
- {
- newMarkBegin = mb;
- newMarkEnd = CursorPos;
- }
- else
- {
- newMarkBegin = 0;
- newMarkEnd = 0;
- }
-
- }
- else
- {
- return false;
- }
- break;
-
- case KEY_BACK:
- if ( !this->IsEnabled )
- break;
-
- if (!Text.empty()) {
- core::stringw s;
-
- if (MarkBegin != MarkEnd)
- {
- // delete marked text
- const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
- const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
-
- s = Text.subString(0, realmbgn);
- s.append( Text.subString(realmend, Text.size()-realmend) );
- Text = s;
-
- CursorPos = realmbgn;
- }
- else
- {
- // delete text behind cursor
- if (CursorPos>0)
- s = Text.subString(0, CursorPos-1);
- else
- s = L"";
- s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
- Text = s;
- --CursorPos;
- }
-
- if (CursorPos < 0)
- CursorPos = 0;
- BlinkStartTime = porting::getTimeMs();
- newMarkBegin = 0;
- newMarkEnd = 0;
- textChanged = true;
- }
- break;
- case KEY_DELETE:
- if ( !this->IsEnabled )
- break;
-
- if (!Text.empty()) {
- core::stringw s;
-
- if (MarkBegin != MarkEnd)
- {
- // delete marked text
- const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
- const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
-
- s = Text.subString(0, realmbgn);
- s.append( Text.subString(realmend, Text.size()-realmend) );
- Text = s;
-
- CursorPos = realmbgn;
- }
- else
- {
- // delete text before cursor
- s = Text.subString(0, CursorPos);
- s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) );
- Text = s;
- }
-
- if (CursorPos > (s32)Text.size())
- CursorPos = (s32)Text.size();
-
- BlinkStartTime = porting::getTimeMs();
- newMarkBegin = 0;
- newMarkEnd = 0;
- textChanged = true;
- }
- break;
-
- case KEY_ESCAPE:
- case KEY_TAB:
- case KEY_SHIFT:
- case KEY_F1:
- case KEY_F2:
- case KEY_F3:
- case KEY_F4:
- case KEY_F5:
- case KEY_F6:
- case KEY_F7:
- case KEY_F8:
- case KEY_F9:
- case KEY_F10:
- case KEY_F11:
- case KEY_F12:
- case KEY_F13:
- case KEY_F14:
- case KEY_F15:
- case KEY_F16:
- case KEY_F17:
- case KEY_F18:
- case KEY_F19:
- case KEY_F20:
- case KEY_F21:
- case KEY_F22:
- case KEY_F23:
- case KEY_F24:
- // ignore these keys
- return false;
-
- default:
- inputChar(event.KeyInput.Char);
- return true;
- }
-
- // Set new text markers
- setTextMarkers( newMarkBegin, newMarkEnd );
-
- // break the text if it has changed
- if (textChanged)
- {
- breakText();
- sendGuiEvent(EGET_EDITBOX_CHANGED);
- }
-
- calculateScrollPos();
-
- return true;
-}
-
-
-//! draws the element and its children
-void intlGUIEditBox::draw()
-{
- if (!IsVisible)
- return;
-
- const bool focus = Environment->hasFocus(this);
-
- IGUISkin* skin = Environment->getSkin();
- if (!skin)
- return;
-
- FrameRect = AbsoluteRect;
-
- // draw the border
-
- if (Border)
- {
- if (m_writable) {
- skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW),
- false, true, FrameRect, &AbsoluteClippingRect);
- }
-
- FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
- FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
- FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
- FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
- }
-
- updateVScrollBar();
- core::rect<s32> localClipRect = FrameRect;
- localClipRect.clipAgainst(AbsoluteClippingRect);
-
- // draw the text
-
- IGUIFont* font = OverrideFont;
- if (!OverrideFont)
- font = skin->getFont();
-
- s32 cursorLine = 0;
- s32 charcursorpos = 0;
-
- if (font)
- {
- if (LastBreakFont != font)
- {
- breakText();
- }
-
- // calculate cursor pos
-
- core::stringw *txtLine = &Text;
- s32 startPos = 0;
-
- core::stringw s, s2;
-
- // get mark position
- const bool ml = (!PasswordBox && (WordWrap || MultiLine));
- const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
- const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
- const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0;
- const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1;
- const s32 lineCount = ml ? BrokenText.size() : 1;
-
- // Save the override color information.
- // Then, alter it if the edit box is disabled.
- const bool prevOver = OverrideColorEnabled;
- const video::SColor prevColor = OverrideColor;
-
- if (!Text.empty()) {
- if (!IsEnabled && !OverrideColorEnabled)
- {
- OverrideColorEnabled = true;
- OverrideColor = skin->getColor(EGDC_GRAY_TEXT);
- }
-
- for (s32 i=0; i < lineCount; ++i)
- {
- setTextRect(i);
-
- // clipping test - don't draw anything outside the visible area
- core::rect<s32> c = localClipRect;
- c.clipAgainst(CurrentTextRect);
- if (!c.isValid())
- continue;
-
- // get current line
- if (PasswordBox)
- {
- if (BrokenText.size() != 1)
- {
- BrokenText.clear();
- BrokenText.push_back(core::stringw());
- }
- if (BrokenText[0].size() != Text.size())
- {
- BrokenText[0] = Text;
- for (u32 q = 0; q < Text.size(); ++q)
- {
- BrokenText[0] [q] = PasswordChar;
- }
- }
- txtLine = &BrokenText[0];
- startPos = 0;
- }
- else
- {
- txtLine = ml ? &BrokenText[i] : &Text;
- startPos = ml ? BrokenTextPositions[i] : 0;
- }
-
-
- // draw normal text
- font->draw(txtLine->c_str(), CurrentTextRect,
- OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
- false, true, &localClipRect);
-
- // draw mark and marked text
- if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount)
- {
-
- s32 mbegin = 0, mend = 0;
- s32 lineStartPos = 0, lineEndPos = txtLine->size();
-
- if (i == hlineStart)
- {
- // highlight start is on this line
- s = txtLine->subString(0, realmbgn - startPos);
- mbegin = font->getDimension(s.c_str()).Width;
-
- // deal with kerning
- mbegin += font->getKerningWidth(
- &((*txtLine)[realmbgn - startPos]),
- realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0);
-
- lineStartPos = realmbgn - startPos;
- }
- if (i == hlineStart + hlineCount - 1)
- {
- // highlight end is on this line
- s2 = txtLine->subString(0, realmend - startPos);
- mend = font->getDimension(s2.c_str()).Width;
- lineEndPos = (s32)s2.size();
- }
- else
- mend = font->getDimension(txtLine->c_str()).Width;
-
- CurrentTextRect.UpperLeftCorner.X += mbegin;
- CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin;
-
- // draw mark
- skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect);
-
- // draw marked text
- s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos);
-
- if (!s.empty())
- font->draw(s.c_str(), CurrentTextRect,
- OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
- false, true, &localClipRect);
-
- }
- }
-
- // Return the override color information to its previous settings.
- OverrideColorEnabled = prevOver;
- OverrideColor = prevColor;
- }
-
- // draw cursor
-
- if (WordWrap || MultiLine)
- {
- cursorLine = getLineFromPos(CursorPos);
- txtLine = &BrokenText[cursorLine];
- startPos = BrokenTextPositions[cursorLine];
- }
- s = txtLine->subString(0,CursorPos-startPos);
- charcursorpos = font->getDimension(s.c_str()).Width +
- font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0);
-
- if (m_writable) {
- if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350) {
- setTextRect(cursorLine);
- CurrentTextRect.UpperLeftCorner.X += charcursorpos;
-
- font->draw(L"_", CurrentTextRect,
- OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
- false, true, &localClipRect);
- }
- }
- }
-
- // draw children
- IGUIElement::draw();
-}
-
-
-//! Sets the new caption of this element.
-void intlGUIEditBox::setText(const wchar_t* text)
-{
- Text = text;
- if (u32(CursorPos) > Text.size())
- CursorPos = Text.size();
- HScrollPos = 0;
- breakText();
-}
-
-
-//! Enables or disables automatic scrolling with cursor position
-//! \param enable: If set to true, the text will move around with the cursor position
-void intlGUIEditBox::setAutoScroll(bool enable)
-{
- AutoScroll = enable;
-}
-
-
-//! Checks to see if automatic scrolling is enabled
-//! \return true if automatic scrolling is enabled, false if not
-bool intlGUIEditBox::isAutoScrollEnabled() const
-{
- _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
- return AutoScroll;
-}
-
-
-//! Gets the area of the text in the edit box
-//! \return Returns the size in pixels of the text
-core::dimension2du intlGUIEditBox::getTextDimension()
-{
- core::rect<s32> ret;
-
- setTextRect(0);
- ret = CurrentTextRect;
-
- for (u32 i=1; i < BrokenText.size(); ++i)
- {
- setTextRect(i);
- ret.addInternalPoint(CurrentTextRect.UpperLeftCorner);
- ret.addInternalPoint(CurrentTextRect.LowerRightCorner);
- }
-
- return core::dimension2du(ret.getSize());
-}
-
-
-//! Sets the maximum amount of characters which may be entered in the box.
-//! \param max: Maximum amount of characters. If 0, the character amount is
-//! infinity.
-void intlGUIEditBox::setMax(u32 max)
-{
- Max = max;
-
- if (Text.size() > Max && Max != 0)
- Text = Text.subString(0, Max);
-}
-
-
-//! Returns maximum amount of characters, previously set by setMax();
-u32 intlGUIEditBox::getMax() const
-{
- return Max;
-}
-
-
-bool intlGUIEditBox::processMouse(const SEvent& event)
-{
- switch(event.MouseInput.Event)
- {
- case irr::EMIE_LMOUSE_LEFT_UP:
- if (Environment->hasFocus(this))
- {
- CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
- if (MouseMarking)
- {
- setTextMarkers( MarkBegin, CursorPos );
- }
- MouseMarking = false;
- calculateScrollPos();
- return true;
- }
- break;
- case irr::EMIE_MOUSE_MOVED:
- {
- if (MouseMarking)
- {
- CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
- setTextMarkers( MarkBegin, CursorPos );
- calculateScrollPos();
- return true;
- }
- }
- break;
- case EMIE_LMOUSE_PRESSED_DOWN:
- if (!Environment->hasFocus(this))
- {
- BlinkStartTime = porting::getTimeMs();
- MouseMarking = true;
- CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
- setTextMarkers(CursorPos, CursorPos );
- calculateScrollPos();
- return true;
- }
- else
- {
- if (!AbsoluteClippingRect.isPointInside(
- core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) {
- return false;
- }
-
-
- // move cursor
- CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
-
- s32 newMarkBegin = MarkBegin;
- if (!MouseMarking)
- newMarkBegin = CursorPos;
-
- MouseMarking = true;
- setTextMarkers( newMarkBegin, CursorPos);
- calculateScrollPos();
- return true;
- }
- break;
- case EMIE_MOUSE_WHEEL:
- if (m_vscrollbar) {
- s32 pos = m_vscrollbar->getPos();
- s32 step = m_vscrollbar->getSmallStep();
- m_vscrollbar->setPos(pos - event.MouseInput.Wheel * step);
- }
- break;
- default:
- break;
- }
-
- return false;
-}
-
-
-s32 intlGUIEditBox::getCursorPos(s32 x, s32 y)
-{
- IGUIFont* font = OverrideFont;
- IGUISkin* skin = Environment->getSkin();
- if (!OverrideFont)
- font = skin->getFont();
-
- const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
-
- core::stringw *txtLine = NULL;
- s32 startPos = 0;
- u32 curr_line_idx = 0;
- x += 3;
-
- for (; curr_line_idx < lineCount; ++curr_line_idx) {
- setTextRect(curr_line_idx);
- if (curr_line_idx == 0 && y < CurrentTextRect.UpperLeftCorner.Y)
- y = CurrentTextRect.UpperLeftCorner.Y;
- if (curr_line_idx == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y)
- y = CurrentTextRect.LowerRightCorner.Y;
-
- // is it inside this region?
- if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) {
- // we've found the clicked line
- txtLine = (WordWrap || MultiLine) ? &BrokenText[curr_line_idx] : &Text;
- startPos = (WordWrap || MultiLine) ? BrokenTextPositions[curr_line_idx] : 0;
- break;
- }
- }
-
- if (x < CurrentTextRect.UpperLeftCorner.X)
- x = CurrentTextRect.UpperLeftCorner.X;
- else if (x > CurrentTextRect.LowerRightCorner.X)
- x = CurrentTextRect.LowerRightCorner.X;
-
- s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X);
- // Special handling for last line, if we are on limits, add 1 extra shift because idx
- // will be the last char, not null char of the wstring
- if (curr_line_idx == lineCount - 1 && x == CurrentTextRect.LowerRightCorner.X)
- idx++;
-
- return rangelim(idx + startPos, 0, S32_MAX);
-}
-
-
-//! Breaks the single text line.
-void intlGUIEditBox::breakText()
-{
- IGUISkin* skin = Environment->getSkin();
-
- if ((!WordWrap && !MultiLine) || !skin)
- return;
-
- BrokenText.clear(); // need to reallocate :/
- BrokenTextPositions.set_used(0);
-
- IGUIFont* font = OverrideFont;
- if (!OverrideFont)
- font = skin->getFont();
-
- if (!font)
- return;
-
- LastBreakFont = font;
-
- core::stringw line;
- core::stringw word;
- core::stringw whitespace;
- s32 lastLineStart = 0;
- s32 size = Text.size();
- s32 length = 0;
- s32 elWidth = RelativeRect.getWidth() - 6;
- wchar_t c;
-
- for (s32 i=0; i<size; ++i)
- {
- c = Text[i];
- bool lineBreak = false;
-
- if (c == L'\r') // Mac or Windows breaks
- {
- lineBreak = true;
- c = ' ';
- if (Text[i+1] == L'\n') // Windows breaks
- {
- Text.erase(i+1);
- --size;
- }
- }
- else if (c == L'\n') // Unix breaks
- {
- lineBreak = true;
- c = ' ';
- }
-
- // don't break if we're not a multi-line edit box
- if (!MultiLine)
- lineBreak = false;
-
- if (c == L' ' || c == 0 || i == (size-1))
- {
- if (!word.empty()) {
- // here comes the next whitespace, look if
- // we can break the last word to the next line.
- s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
- s32 worldlgth = font->getDimension(word.c_str()).Width;
-
- if (WordWrap && length + worldlgth + whitelgth > elWidth)
- {
- // break to next line
- length = worldlgth;
- BrokenText.push_back(line);
- BrokenTextPositions.push_back(lastLineStart);
- lastLineStart = i - (s32)word.size();
- line = word;
- }
- else
- {
- // add word to line
- line += whitespace;
- line += word;
- length += whitelgth + worldlgth;
- }
-
- word = L"";
- whitespace = L"";
- }
-
- whitespace += c;
-
- // compute line break
- if (lineBreak)
- {
- line += whitespace;
- line += word;
- BrokenText.push_back(line);
- BrokenTextPositions.push_back(lastLineStart);
- lastLineStart = i+1;
- line = L"";
- word = L"";
- whitespace = L"";
- length = 0;
- }
- }
- else
- {
- // yippee this is a word..
- word += c;
- }
- }
-
- line += whitespace;
- line += word;
- BrokenText.push_back(line);
- BrokenTextPositions.push_back(lastLineStart);
-}
-
-
-void intlGUIEditBox::setTextRect(s32 line)
-{
- core::dimension2du d;
-
- IGUISkin* skin = Environment->getSkin();
- if (!skin)
- return;
-
- IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont();
-
- if (!font)
- return;
-
- // get text dimension
- const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
- if (WordWrap || MultiLine)
- {
- d = font->getDimension(BrokenText[line].c_str());
- }
- else
- {
- d = font->getDimension(Text.c_str());
- d.Height = AbsoluteRect.getHeight();
- }
- d.Height += font->getKerningHeight();
-
- // justification
- switch (HAlign)
- {
- case EGUIA_CENTER:
- // align to h centre
- CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2);
- CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2);
- break;
- case EGUIA_LOWERRIGHT:
- // align to right edge
- CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width;
- CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth();
- break;
- default:
- // align to left edge
- CurrentTextRect.UpperLeftCorner.X = 0;
- CurrentTextRect.LowerRightCorner.X = d.Width;
-
- }
-
- switch (VAlign)
- {
- case EGUIA_CENTER:
- // align to v centre
- CurrentTextRect.UpperLeftCorner.Y =
- (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line;
- break;
- case EGUIA_LOWERRIGHT:
- // align to bottom edge
- CurrentTextRect.UpperLeftCorner.Y =
- FrameRect.getHeight() - lineCount*d.Height + d.Height*line;
- break;
- default:
- // align to top edge
- CurrentTextRect.UpperLeftCorner.Y = d.Height*line;
- break;
- }
-
- CurrentTextRect.UpperLeftCorner.X -= HScrollPos;
- CurrentTextRect.LowerRightCorner.X -= HScrollPos;
- CurrentTextRect.UpperLeftCorner.Y -= VScrollPos;
- CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height;
-
- CurrentTextRect += FrameRect.UpperLeftCorner;
-
-}
-
-
-s32 intlGUIEditBox::getLineFromPos(s32 pos)
-{
- if (!WordWrap && !MultiLine)
- return 0;
-
- s32 i=0;
- while (i < (s32)BrokenTextPositions.size())
- {
- if (BrokenTextPositions[i] > pos)
- return i-1;
- ++i;
- }
- return (s32)BrokenTextPositions.size() - 1;
-}
-
-
-void intlGUIEditBox::inputChar(wchar_t c)
-{
- if (!IsEnabled)
- return;
-
- if (c != 0)
- {
- if (Text.size() < Max || Max == 0)
- {
- core::stringw s;
-
- if (MarkBegin != MarkEnd)
- {
- // replace marked text
- const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
- const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
-
- s = Text.subString(0, realmbgn);
- s.append(c);
- s.append( Text.subString(realmend, Text.size()-realmend) );
- Text = s;
- CursorPos = realmbgn+1;
- }
- else
- {
- // add new character
- s = Text.subString(0, CursorPos);
- s.append(c);
- s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
- Text = s;
- ++CursorPos;
- }
-
- BlinkStartTime = porting::getTimeMs();
- setTextMarkers(0, 0);
- }
- }
- breakText();
- sendGuiEvent(EGET_EDITBOX_CHANGED);
- calculateScrollPos();
-}
-
-
-void intlGUIEditBox::calculateScrollPos()
-{
- if (!AutoScroll)
- return;
-
- // calculate horizontal scroll position
- s32 cursLine = getLineFromPos(CursorPos);
- setTextRect(cursLine);
-
- // don't do horizontal scrolling when wordwrap is enabled.
- if (!WordWrap)
- {
- // get cursor position
- IGUISkin* skin = Environment->getSkin();
- if (!skin)
- return;
- IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont();
- if (!font)
- return;
-
- core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text;
- s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos;
-
- s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos +
- font->getDimension(txtLine->subString(0, cPos).c_str()).Width;
-
- s32 cEnd = cStart + font->getDimension(L"_ ").Width;
-
- if (FrameRect.LowerRightCorner.X < cEnd)
- HScrollPos = cEnd - FrameRect.LowerRightCorner.X;
- else if (FrameRect.UpperLeftCorner.X > cStart)
- HScrollPos = cStart - FrameRect.UpperLeftCorner.X;
- else
- HScrollPos = 0;
-
- // todo: adjust scrollbar
- }
-
- // vertical scroll position
- if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos)
- VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos;
-
- else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos)
- VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos;
- else
- VScrollPos = 0;
-
- // todo: adjust scrollbar
- if (m_vscrollbar)
- m_vscrollbar->setPos(VScrollPos);
-}
-
-//! set text markers
-void intlGUIEditBox::setTextMarkers(s32 begin, s32 end)
-{
- if ( begin != MarkBegin || end != MarkEnd )
- {
- MarkBegin = begin;
- MarkEnd = end;
- sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
- }
-}
-
-//! send some gui event to parent
-void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type)
-{
- if ( Parent )
- {
- SEvent e;
- e.EventType = EET_GUI_EVENT;
- e.GUIEvent.Caller = this;
- e.GUIEvent.Element = 0;
- e.GUIEvent.EventType = type;
-
- Parent->OnEvent(e);
- }
-}
-
-//! Create a vertical scrollbar
-void intlGUIEditBox::createVScrollBar()
-{
- s32 fontHeight = 1;
-
- if (OverrideFont) {
- fontHeight = OverrideFont->getDimension(L"").Height;
- } else {
- if (IGUISkin* skin = Environment->getSkin()) {
- if (IGUIFont* font = skin->getFont()) {
- fontHeight = font->getDimension(L"").Height;
- }
- }
- }
-
- irr::core::rect<s32> scrollbarrect = FrameRect;
- scrollbarrect.UpperLeftCorner.X += FrameRect.getWidth() - m_scrollbar_width;
- m_vscrollbar = Environment->addScrollBar(false, scrollbarrect, getParent(), getID());
- m_vscrollbar->setVisible(false);
- m_vscrollbar->setSmallStep(3 * fontHeight);
- m_vscrollbar->setLargeStep(10 * fontHeight);
-}
-
-//! Update the vertical scrollbar (visibilty & scroll position)
-void intlGUIEditBox::updateVScrollBar()
-{
- if (!m_vscrollbar)
- return;
-
- // OnScrollBarChanged(...)
- if (m_vscrollbar->getPos() != VScrollPos) {
- s32 deltaScrollY = m_vscrollbar->getPos() - VScrollPos;
- CurrentTextRect.UpperLeftCorner.Y -= deltaScrollY;
- CurrentTextRect.LowerRightCorner.Y -= deltaScrollY;
-
- s32 scrollymax = getTextDimension().Height - FrameRect.getHeight();
- if (scrollymax != m_vscrollbar->getMax()) {
- // manage a newline or a deleted line
- m_vscrollbar->setMax(scrollymax);
- calculateScrollPos();
- } else {
- // manage a newline or a deleted line
- VScrollPos = m_vscrollbar->getPos();
- }
- }
-
- // check if a vertical scrollbar is needed ?
- if (getTextDimension().Height > (u32) FrameRect.getHeight()) {
- s32 scrollymax = getTextDimension().Height - FrameRect.getHeight();
- if (scrollymax != m_vscrollbar->getMax()) {
- m_vscrollbar->setMax(scrollymax);
- }
-
- if (!m_vscrollbar->isVisible() && MultiLine) {
- AbsoluteRect.LowerRightCorner.X -= m_scrollbar_width;
-
- m_vscrollbar->setVisible(true);
- }
- } else {
- if (m_vscrollbar->isVisible()) {
- AbsoluteRect.LowerRightCorner.X += m_scrollbar_width;
-
- VScrollPos = 0;
- m_vscrollbar->setPos(0);
- m_vscrollbar->setMax(1);
- m_vscrollbar->setVisible(false);
- }
- }
-}
-
-void intlGUIEditBox::setWritable(bool can_write_text)
-{
- m_writable = can_write_text;
-}
-
-//! Writes attributes of the element.
-void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
-{
- // IGUIEditBox::serializeAttributes(out,options);
-
- out->addBool ("OverrideColorEnabled",OverrideColorEnabled );
- out->addColor ("OverrideColor", OverrideColor);
- // out->addFont("OverrideFont",OverrideFont);
- out->addInt ("MaxChars", Max);
- out->addBool ("WordWrap", WordWrap);
- out->addBool ("MultiLine", MultiLine);
- out->addBool ("AutoScroll", AutoScroll);
- out->addBool ("PasswordBox", PasswordBox);
- core::stringw ch = L" ";
- ch[0] = PasswordChar;
- out->addString("PasswordChar", ch.c_str());
- out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames);
- out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames);
- out->addBool ("Writable", m_writable);
-
- IGUIEditBox::serializeAttributes(out,options);
-}
-
-
-//! Reads attributes of the element
-void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
-{
- IGUIEditBox::deserializeAttributes(in,options);
-
- setOverrideColor(in->getAttributeAsColor("OverrideColor"));
- enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
- setMax(in->getAttributeAsInt("MaxChars"));
- setWordWrap(in->getAttributeAsBool("WordWrap"));
- setMultiLine(in->getAttributeAsBool("MultiLine"));
- setAutoScroll(in->getAttributeAsBool("AutoScroll"));
- core::stringw ch = in->getAttributeAsStringW("PasswordChar");
-
- if (ch.empty())
- setPasswordBox(in->getAttributeAsBool("PasswordBox"));
- else
- setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]);
-
- setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
- (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
-
- setWritable(in->getAttributeAsBool("Writable"));
- // setOverrideFont(in->getAttributeAsFont("OverrideFont"));
-}
-
-
-} // end namespace gui
-} // end namespace irr
-
-#endif // _IRR_COMPILE_WITH_GUI_
+++ /dev/null
-// Copyright (C) 2002-2013 Nikolaus Gebhardt
-// This file is part of the "Irrlicht Engine".
-// For conditions of distribution and use, see copyright notice in irrlicht.h
-
-#pragma once
-
-#include "IrrCompileConfig.h"
-//#ifdef _IRR_COMPILE_WITH_GUI_
-
-#include "IGUIEditBox.h"
-#include "irrArray.h"
-#include "IOSOperator.h"
-#include "IGUIScrollBar.h"
-
-namespace irr
-{
-namespace gui
-{
- class intlGUIEditBox : public IGUIEditBox
- {
- public:
-
- //! constructor
- intlGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment,
- IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
- bool writable = true, bool has_vscrollbar = false);
-
- //! destructor
- virtual ~intlGUIEditBox();
-
- //! Sets another skin independent font.
- virtual void setOverrideFont(IGUIFont* font=0);
-
- //! Gets the override font (if any)
- /** \return The override font (may be 0) */
- virtual IGUIFont* getOverrideFont() const;
-
- //! Get the font which is used right now for drawing
- /** Currently this is the override font when one is set and the
- font of the active skin otherwise */
- virtual IGUIFont* getActiveFont() const;
-
- //! Sets another color for the text.
- virtual void setOverrideColor(video::SColor color);
-
- //! Gets the override color
- virtual video::SColor getOverrideColor() const;
-
- //! Sets if the text should use the overide color or the
- //! color in the gui skin.
- virtual void enableOverrideColor(bool enable);
-
- //! Checks if an override color is enabled
- /** \return true if the override color is enabled, false otherwise */
- virtual bool isOverrideColorEnabled(void) const;
-
- //! Sets whether to draw the background
- virtual void setDrawBackground(bool draw);
-
- //! Turns the border on or off
- virtual void setDrawBorder(bool border);
-
- //! Enables or disables word wrap for using the edit box as multiline text editor.
- virtual void setWordWrap(bool enable);
-
- //! Checks if word wrap is enabled
- //! \return true if word wrap is enabled, false otherwise
- virtual bool isWordWrapEnabled() const;
-
- //! Enables or disables newlines.
- /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired,
- instead a newline character will be inserted. */
- virtual void setMultiLine(bool enable);
-
- //! Checks if multi line editing is enabled
- //! \return true if mult-line is enabled, false otherwise
- virtual bool isMultiLineEnabled() const;
-
- //! Enables or disables automatic scrolling with cursor position
- //! \param enable: If set to true, the text will move around with the cursor position
- virtual void setAutoScroll(bool enable);
-
- //! Checks to see if automatic scrolling is enabled
- //! \return true if automatic scrolling is enabled, false if not
- virtual bool isAutoScrollEnabled() const;
-
- //! Gets the size area of the text in the edit box
- //! \return Returns the size in pixels of the text
- virtual core::dimension2du getTextDimension();
-
- //! Sets text justification
- virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical);
-
- //! called if an event happened.
- virtual bool OnEvent(const SEvent& event);
-
- //! draws the element and its children
- virtual void draw();
-
- //! Sets the new caption of this element.
- virtual void setText(const wchar_t* text);
-
- //! Sets the maximum amount of characters which may be entered in the box.
- //! \param max: Maximum amount of characters. If 0, the character amount is
- //! infinity.
- virtual void setMax(u32 max);
-
- //! Returns maximum amount of characters, previously set by setMax();
- virtual u32 getMax() const;
-
- //! Sets whether the edit box is a password box. Setting this to true will
- /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x
- \param passwordBox: true to enable password, false to disable
- \param passwordChar: the character that is displayed instead of letters */
- virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*');
-
- //! Returns true if the edit box is currently a password box.
- virtual bool isPasswordBox() const;
-
- //! Updates the absolute position, splits text if required
- virtual void updateAbsolutePosition();
-
- //! set true if this EditBox is writable
- virtual void setWritable(bool can_write_text);
-
- //! Writes attributes of the element.
- virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;
-
- //! Reads attributes of the element
- virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
-
- protected:
- //! Breaks the single text line.
- void breakText();
- //! sets the area of the given line
- void setTextRect(s32 line);
- //! returns the line number that the cursor is on
- s32 getLineFromPos(s32 pos);
- //! adds a letter to the edit box
- void inputChar(wchar_t c);
- //! calculates the current scroll position
- void calculateScrollPos();
- //! send some gui event to parent
- void sendGuiEvent(EGUI_EVENT_TYPE type);
- //! set text markers
- void setTextMarkers(s32 begin, s32 end);
-
- bool processKey(const SEvent& event);
- bool processMouse(const SEvent& event);
- s32 getCursorPos(s32 x, s32 y);
-
- //! Create a vertical scrollbar
- void createVScrollBar();
-
- //! Update the vertical scrollbar (visibilty & scroll position)
- void updateVScrollBar();
-
- bool MouseMarking = false;
- bool Border;
- bool OverrideColorEnabled = false;
- s32 MarkBegin = 0;
- s32 MarkEnd = 0;
-
- video::SColor OverrideColor = video::SColor(101,255,255,255);
- gui::IGUIFont *OverrideFont = nullptr;
- gui::IGUIFont *LastBreakFont = nullptr;
- IOSOperator *Operator = nullptr;
-
- u64 BlinkStartTime = 0;
- s32 CursorPos = 0;
- s32 HScrollPos = 0;
- s32 VScrollPos = 0; // scroll position in characters
- u32 Max = 0;
-
- bool WordWrap = false;
- bool MultiLine = false;
- bool AutoScroll = true;
- bool PasswordBox = false;
- wchar_t PasswordChar = L'*';
- EGUI_ALIGNMENT HAlign = EGUIA_UPPERLEFT;
- EGUI_ALIGNMENT VAlign = EGUIA_CENTER;
-
- core::array<core::stringw> BrokenText;
- core::array<s32> BrokenTextPositions;
-
- core::rect<s32> CurrentTextRect = core::rect<s32>(0,0,1,1);
- core::rect<s32> FrameRect; // temporary values
- u32 m_scrollbar_width;
- IGUIScrollBar *m_vscrollbar;
- bool m_writable;
-
- };
-
-
-} // end namespace gui
-} // end namespace irr
-
-//#endif // _IRR_COMPILE_WITH_GUI_
#include "quicktune.h"
#include "httpfetch.h"
#include "gameparams.h"
-#include "database.h"
+#include "database/database.h"
#include "config.h"
#include "player.h"
#include "porting.h"
#include "terminal_chat_console.h"
#endif
#ifndef SERVER
-#include "guiMainMenu.h"
+#include "gui/guiMainMenu.h"
#include "client/clientlauncher.h"
-#include "guiEngine.h"
-#include "mainmenumanager.h"
+#include "gui/guiEngine.h"
+#include "gui/mainmenumanager.h"
#endif
#ifdef HAVE_TOUCHSCREENGUI
- #include "touchscreengui.h"
+ #include "gui/touchscreengui.h"
#endif
#if !defined(SERVER) && \
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-/*
- All kinds of stuff that needs to be exposed from main.cpp
-*/
-#include "modalMenu.h"
-#include <cassert>
-#include <list>
-
-class IGameCallback
-{
-public:
- virtual void exitToOS() = 0;
- virtual void keyConfig() = 0;
- virtual void disconnect() = 0;
- virtual void changePassword() = 0;
- virtual void changeVolume() = 0;
-
- virtual void signalKeyConfigChange() = 0;
-};
-
-extern gui::IGUIEnvironment *guienv;
-extern gui::IGUIStaticText *guiroot;
-
-// Handler for the modal menus
-
-class MainMenuManager : public IMenuManager
-{
-public:
- virtual void createdMenu(gui::IGUIElement *menu)
- {
-#ifndef NDEBUG
- for (gui::IGUIElement *i : m_stack) {
- assert(i != menu);
- }
-#endif
-
- if(!m_stack.empty())
- m_stack.back()->setVisible(false);
- m_stack.push_back(menu);
- }
-
- virtual void deletingMenu(gui::IGUIElement *menu)
- {
- // Remove all entries if there are duplicates
- bool removed_entry;
- do{
- removed_entry = false;
- for(std::list<gui::IGUIElement*>::iterator
- i = m_stack.begin();
- i != m_stack.end(); ++i)
- {
- if(*i == menu)
- {
- m_stack.erase(i);
- removed_entry = true;
- break;
- }
- }
- }while(removed_entry);
-
- /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
- assert(*i == menu);
- m_stack.erase(i);*/
-
- if(!m_stack.empty())
- m_stack.back()->setVisible(true);
- }
-
- // Returns true to prevent further processing
- virtual bool preprocessEvent(const SEvent& event)
- {
- if (m_stack.empty())
- return false;
- GUIModalMenu *mm = dynamic_cast<GUIModalMenu*>(m_stack.back());
- return mm && mm->preprocessEvent(event);
- }
-
- u32 menuCount()
- {
- return m_stack.size();
- }
-
- bool pausesGame()
- {
- for (gui::IGUIElement *i : m_stack) {
- GUIModalMenu *mm = dynamic_cast<GUIModalMenu*>(i);
- if (mm && mm->pausesGame())
- return true;
- }
- return false;
- }
-
- std::list<gui::IGUIElement*> m_stack;
-};
-
-extern MainMenuManager g_menumgr;
-
-extern bool isMenuActive();
-
-class MainGameCallback : public IGameCallback
-{
-public:
- MainGameCallback() = default;
- virtual ~MainGameCallback() = default;
-
- virtual void exitToOS()
- {
- shutdown_requested = true;
- }
-
- virtual void disconnect()
- {
- disconnect_requested = true;
- }
-
- virtual void changePassword()
- {
- changepassword_requested = true;
- }
-
- virtual void changeVolume()
- {
- changevolume_requested = true;
- }
-
- virtual void keyConfig()
- {
- keyconfig_requested = true;
- }
-
- virtual void signalKeyConfigChange()
- {
- keyconfig_changed = true;
- }
-
-
- bool disconnect_requested = false;
- bool changepassword_requested = false;
- bool changevolume_requested = false;
- bool keyconfig_requested = false;
- bool shutdown_requested = false;
-
- bool keyconfig_changed = false;
-};
-
-extern MainGameCallback *g_gamecallback;
#include "environment.h"
#include "reflowscan.h"
#include "emerge.h"
-#include "mapgen_v6.h"
-#include "mg_biome.h"
+#include "mapgen/mapgen_v6.h"
+#include "mapgen/mg_biome.h"
#include "config.h"
#include "server.h"
-#include "database.h"
-#include "database-dummy.h"
-#include "database-sqlite3.h"
+#include "database/database.h"
+#include "database/database-dummy.h"
+#include "database/database-sqlite3.h"
#include "script/scripting_server.h"
#include <deque>
#include <queue>
#if USE_LEVELDB
-#include "database-leveldb.h"
+#include "database/database-leveldb.h"
#endif
#if USE_REDIS
-#include "database-redis.h"
+#include "database/database-redis.h"
#endif
#if USE_POSTGRESQL
-#include "database-postgresql.h"
+#include "database/database-postgresql.h"
#endif
#include "debug.h"
#include "filesys.h"
#include "log.h"
-#include "mapgen.h"
+#include "mapgen/mapgen.h"
#include "settings.h"
#include "map_settings_manager.h"
#include "modifiedstate.h"
#include "util/numeric.h" // getContainerPos
#include "settings.h"
-#include "mapgen.h"
+#include "mapgen/mapgen.h"
class Map;
class NodeMetadataList;
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "gamedef.h"
-#include "mg_biome.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "content_sao.h"
-#include "nodedef.h"
-#include "emerge.h"
-#include "voxelalgorithms.h"
-#include "porting.h"
-#include "profiler.h"
-#include "settings.h"
-#include "treegen.h"
-#include "serialization.h"
-#include "util/serialize.h"
-#include "util/numeric.h"
-#include "filesys.h"
-#include "log.h"
-#include "mapgen_carpathian.h"
-#include "mapgen_flat.h"
-#include "mapgen_fractal.h"
-#include "mapgen_v5.h"
-#include "mapgen_v6.h"
-#include "mapgen_v7.h"
-#include "mapgen_valleys.h"
-#include "mapgen_singlenode.h"
-#include "cavegen.h"
-#include "dungeongen.h"
-
-FlagDesc flagdesc_mapgen[] = {
- {"caves", MG_CAVES},
- {"dungeons", MG_DUNGEONS},
- {"light", MG_LIGHT},
- {"decorations", MG_DECORATIONS},
- {NULL, 0}
-};
-
-FlagDesc flagdesc_gennotify[] = {
- {"dungeon", 1 << GENNOTIFY_DUNGEON},
- {"temple", 1 << GENNOTIFY_TEMPLE},
- {"cave_begin", 1 << GENNOTIFY_CAVE_BEGIN},
- {"cave_end", 1 << GENNOTIFY_CAVE_END},
- {"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN},
- {"large_cave_end", 1 << GENNOTIFY_LARGECAVE_END},
- {"decoration", 1 << GENNOTIFY_DECORATION},
- {NULL, 0}
-};
-
-struct MapgenDesc {
- const char *name;
- bool is_user_visible;
-};
-
-////
-//// Built-in mapgens
-////
-
-static MapgenDesc g_reg_mapgens[] = {
- {"v5", true},
- {"v6", true},
- {"v7", true},
- {"flat", true},
- {"fractal", true},
- {"valleys", true},
- {"singlenode", true},
- {"carpathian", true},
-};
-
-STATIC_ASSERT(
- ARRLEN(g_reg_mapgens) == MAPGEN_INVALID,
- registered_mapgens_is_wrong_size);
-
-////
-//// Mapgen
-////
-
-Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge) :
- gennotify(emerge->gen_notify_on, &emerge->gen_notify_on_deco_ids)
-{
- id = mapgenid;
- water_level = params->water_level;
- mapgen_limit = params->mapgen_limit;
- flags = params->flags;
- csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE);
-
- /*
- We are losing half our entropy by doing this, but it is necessary to
- preserve reverse compatibility. If the top half of our current 64 bit
- seeds ever starts getting used, existing worlds will break due to a
- different hash outcome and no way to differentiate between versions.
-
- A solution could be to add a new bit to designate that the top half of
- the seed value should be used, essentially a 1-bit version code, but
- this would require increasing the total size of a seed to 9 bytes (yuck)
-
- It's probably okay if this never gets fixed. 4.2 billion possibilities
- ought to be enough for anyone.
- */
- seed = (s32)params->seed;
-
- ndef = emerge->ndef;
-}
-
-
-MapgenType Mapgen::getMapgenType(const std::string &mgname)
-{
- for (size_t i = 0; i != ARRLEN(g_reg_mapgens); i++) {
- if (mgname == g_reg_mapgens[i].name)
- return (MapgenType)i;
- }
-
- return MAPGEN_INVALID;
-}
-
-
-const char *Mapgen::getMapgenName(MapgenType mgtype)
-{
- size_t index = (size_t)mgtype;
- if (index == MAPGEN_INVALID || index >= ARRLEN(g_reg_mapgens))
- return "invalid";
-
- return g_reg_mapgens[index].name;
-}
-
-
-Mapgen *Mapgen::createMapgen(MapgenType mgtype, int mgid,
- MapgenParams *params, EmergeManager *emerge)
-{
- switch (mgtype) {
- case MAPGEN_CARPATHIAN:
- return new MapgenCarpathian(mgid, (MapgenCarpathianParams *)params, emerge);
- case MAPGEN_FLAT:
- return new MapgenFlat(mgid, (MapgenFlatParams *)params, emerge);
- case MAPGEN_FRACTAL:
- return new MapgenFractal(mgid, (MapgenFractalParams *)params, emerge);
- case MAPGEN_SINGLENODE:
- return new MapgenSinglenode(mgid, (MapgenSinglenodeParams *)params, emerge);
- case MAPGEN_V5:
- return new MapgenV5(mgid, (MapgenV5Params *)params, emerge);
- case MAPGEN_V6:
- return new MapgenV6(mgid, (MapgenV6Params *)params, emerge);
- case MAPGEN_V7:
- return new MapgenV7(mgid, (MapgenV7Params *)params, emerge);
- case MAPGEN_VALLEYS:
- return new MapgenValleys(mgid, (MapgenValleysParams *)params, emerge);
- default:
- return NULL;
- }
-}
-
-
-MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype)
-{
- switch (mgtype) {
- case MAPGEN_CARPATHIAN:
- return new MapgenCarpathianParams;
- case MAPGEN_FLAT:
- return new MapgenFlatParams;
- case MAPGEN_FRACTAL:
- return new MapgenFractalParams;
- case MAPGEN_SINGLENODE:
- return new MapgenSinglenodeParams;
- case MAPGEN_V5:
- return new MapgenV5Params;
- case MAPGEN_V6:
- return new MapgenV6Params;
- case MAPGEN_V7:
- return new MapgenV7Params;
- case MAPGEN_VALLEYS:
- return new MapgenValleysParams;
- default:
- return NULL;
- }
-}
-
-
-void Mapgen::getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden)
-{
- for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) {
- if (include_hidden || g_reg_mapgens[i].is_user_visible)
- mgnames->push_back(g_reg_mapgens[i].name);
- }
-}
-
-
-u32 Mapgen::getBlockSeed(v3s16 p, s32 seed)
-{
- return (u32)seed +
- p.Z * 38134234 +
- p.Y * 42123 +
- p.X * 23;
-}
-
-
-u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed)
-{
- u32 n = 1619 * p.X + 31337 * p.Y + 52591 * p.Z + 1013 * seed;
- n = (n >> 13) ^ n;
- return (n * (n * n * 60493 + 19990303) + 1376312589);
-}
-
-
-// Returns Y one under area minimum if not found
-s16 Mapgen::findGroundLevelFull(v2s16 p2d)
-{
- const v3s16 &em = vm->m_area.getExtent();
- s16 y_nodes_max = vm->m_area.MaxEdge.Y;
- s16 y_nodes_min = vm->m_area.MinEdge.Y;
- u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
- s16 y;
-
- for (y = y_nodes_max; y >= y_nodes_min; y--) {
- MapNode &n = vm->m_data[i];
- if (ndef->get(n).walkable)
- break;
-
- vm->m_area.add_y(em, i, -1);
- }
- return (y >= y_nodes_min) ? y : y_nodes_min - 1;
-}
-
-
-// Returns -MAX_MAP_GENERATION_LIMIT if not found
-s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax)
-{
- const v3s16 &em = vm->m_area.getExtent();
- u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
- s16 y;
-
- for (y = ymax; y >= ymin; y--) {
- MapNode &n = vm->m_data[i];
- if (ndef->get(n).walkable)
- break;
-
- vm->m_area.add_y(em, i, -1);
- }
- return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT;
-}
-
-
-// Returns -MAX_MAP_GENERATION_LIMIT if not found or if ground is found first
-s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax)
-{
- const v3s16 &em = vm->m_area.getExtent();
- u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
- s16 y;
-
- for (y = ymax; y >= ymin; y--) {
- MapNode &n = vm->m_data[i];
- if (ndef->get(n).walkable)
- return -MAX_MAP_GENERATION_LIMIT;
-
- if (ndef->get(n).isLiquid())
- break;
-
- vm->m_area.add_y(em, i, -1);
- }
- return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT;
-}
-
-
-void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax)
-{
- if (!heightmap)
- return;
-
- //TimeTaker t("Mapgen::updateHeightmap", NULL, PRECISION_MICRO);
- int index = 0;
- for (s16 z = nmin.Z; z <= nmax.Z; z++) {
- for (s16 x = nmin.X; x <= nmax.X; x++, index++) {
- s16 y = findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y);
-
- heightmap[index] = y;
- }
- }
-}
-
-
-void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
- s16 *floors, s16 *ceilings, u16 *num_floors, u16 *num_ceilings)
-{
- u16 floor_i = 0;
- u16 ceiling_i = 0;
- const v3s16 &em = vm->m_area.getExtent();
-
- bool is_walkable = false;
- u32 vi = vm->m_area.index(p2d.X, ymax, p2d.Y);
- MapNode mn_max = vm->m_data[vi];
- bool walkable_above = ndef->get(mn_max).walkable;
- vm->m_area.add_y(em, vi, -1);
-
- for (s16 y = ymax - 1; y >= ymin; y--) {
- MapNode mn = vm->m_data[vi];
- is_walkable = ndef->get(mn).walkable;
-
- if (is_walkable && !walkable_above) {
- floors[floor_i] = y;
- floor_i++;
- } else if (!is_walkable && walkable_above) {
- ceilings[ceiling_i] = y + 1;
- ceiling_i++;
- }
-
- vm->m_area.add_y(em, vi, -1);
- walkable_above = is_walkable;
- }
-
- *num_floors = floor_i;
- *num_ceilings = ceiling_i;
-}
-
-
-inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em)
-{
- u32 vi_neg_x = vi;
- vm->m_area.add_x(em, vi_neg_x, -1);
- if (vm->m_data[vi_neg_x].getContent() != CONTENT_IGNORE) {
- const ContentFeatures &c_nx = ndef->get(vm->m_data[vi_neg_x]);
- if (c_nx.floodable && !c_nx.isLiquid())
- return true;
- }
- u32 vi_pos_x = vi;
- vm->m_area.add_x(em, vi_pos_x, +1);
- if (vm->m_data[vi_pos_x].getContent() != CONTENT_IGNORE) {
- const ContentFeatures &c_px = ndef->get(vm->m_data[vi_pos_x]);
- if (c_px.floodable && !c_px.isLiquid())
- return true;
- }
- u32 vi_neg_z = vi;
- vm->m_area.add_z(em, vi_neg_z, -1);
- if (vm->m_data[vi_neg_z].getContent() != CONTENT_IGNORE) {
- const ContentFeatures &c_nz = ndef->get(vm->m_data[vi_neg_z]);
- if (c_nz.floodable && !c_nz.isLiquid())
- return true;
- }
- u32 vi_pos_z = vi;
- vm->m_area.add_z(em, vi_pos_z, +1);
- if (vm->m_data[vi_pos_z].getContent() != CONTENT_IGNORE) {
- const ContentFeatures &c_pz = ndef->get(vm->m_data[vi_pos_z]);
- if (c_pz.floodable && !c_pz.isLiquid())
- return true;
- }
- return false;
-}
-
-void Mapgen::updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax)
-{
- bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed;
- const v3s16 &em = vm->m_area.getExtent();
-
- for (s16 z = nmin.Z + 1; z <= nmax.Z - 1; z++)
- for (s16 x = nmin.X + 1; x <= nmax.X - 1; x++) {
- wasignored = true;
- wasliquid = false;
- waschecked = false;
- waspushed = false;
-
- u32 vi = vm->m_area.index(x, nmax.Y, z);
- for (s16 y = nmax.Y; y >= nmin.Y; y--) {
- isignored = vm->m_data[vi].getContent() == CONTENT_IGNORE;
- isliquid = ndef->get(vm->m_data[vi]).isLiquid();
-
- if (isignored || wasignored || isliquid == wasliquid) {
- // Neither topmost node of liquid column nor topmost node below column
- waschecked = false;
- waspushed = false;
- } else if (isliquid) {
- // This is the topmost node in the column
- bool ispushed = false;
- if (isLiquidHorizontallyFlowable(vi, em)) {
- trans_liquid->push_back(v3s16(x, y, z));
- ispushed = true;
- }
- // Remember waschecked and waspushed to avoid repeated
- // checks/pushes in case the column consists of only this node
- waschecked = true;
- waspushed = ispushed;
- } else {
- // This is the topmost node below a liquid column
- u32 vi_above = vi;
- vm->m_area.add_y(em, vi_above, 1);
- if (!waspushed && (ndef->get(vm->m_data[vi]).floodable ||
- (!waschecked && isLiquidHorizontallyFlowable(vi_above, em)))) {
- // Push back the lowest node in the column which is one
- // node above this one
- trans_liquid->push_back(v3s16(x, y + 1, z));
- }
- }
-
- wasliquid = isliquid;
- wasignored = isignored;
- vm->m_area.add_y(em, vi, -1);
- }
- }
-}
-
-
-void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax)
-{
- ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
- VoxelArea a(nmin, nmax);
-
- for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
- for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
- u32 i = vm->m_area.index(a.MinEdge.X, y, z);
- for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++)
- vm->m_data[i].param1 = light;
- }
- }
-}
-
-
-void Mapgen::lightSpread(VoxelArea &a, v3s16 p, u8 light)
-{
- if (light <= 1 || !a.contains(p))
- return;
-
- u32 vi = vm->m_area.index(p);
- MapNode &n = vm->m_data[vi];
-
- // Decay light in each of the banks separately
- u8 light_day = light & 0x0F;
- if (light_day > 0)
- light_day -= 0x01;
-
- u8 light_night = light & 0xF0;
- if (light_night > 0)
- light_night -= 0x10;
-
- // Bail out only if we have no more light from either bank to propogate, or
- // we hit a solid block that light cannot pass through.
- if ((light_day <= (n.param1 & 0x0F) &&
- light_night <= (n.param1 & 0xF0)) ||
- !ndef->get(n).light_propagates)
- return;
-
- // Since this recursive function only terminates when there is no light from
- // either bank left, we need to take the max of both banks into account for
- // the case where spreading has stopped for one light bank but not the other.
- light = MYMAX(light_day, n.param1 & 0x0F) |
- MYMAX(light_night, n.param1 & 0xF0);
-
- n.param1 = light;
-
- lightSpread(a, p + v3s16(0, 0, 1), light);
- lightSpread(a, p + v3s16(0, 1, 0), light);
- lightSpread(a, p + v3s16(1, 0, 0), light);
- lightSpread(a, p - v3s16(0, 0, 1), light);
- lightSpread(a, p - v3s16(0, 1, 0), light);
- lightSpread(a, p - v3s16(1, 0, 0), light);
-}
-
-
-void Mapgen::calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
- bool propagate_shadow)
-{
- ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
- //TimeTaker t("updateLighting");
-
- propagateSunlight(nmin, nmax, propagate_shadow);
- spreadLight(full_nmin, full_nmax);
-
- //printf("updateLighting: %dms\n", t.stop());
-}
-
-
-void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow)
-{
- //TimeTaker t("propagateSunlight");
- VoxelArea a(nmin, nmax);
- bool block_is_underground = (water_level >= nmax.Y);
- const v3s16 &em = vm->m_area.getExtent();
-
- // NOTE: Direct access to the low 4 bits of param1 is okay here because,
- // by definition, sunlight will never be in the night lightbank.
-
- for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
- for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++) {
- // see if we can get a light value from the overtop
- u32 i = vm->m_area.index(x, a.MaxEdge.Y + 1, z);
- if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
- if (block_is_underground)
- continue;
- } else if ((vm->m_data[i].param1 & 0x0F) != LIGHT_SUN &&
- propagate_shadow) {
- continue;
- }
- vm->m_area.add_y(em, i, -1);
-
- for (int y = a.MaxEdge.Y; y >= a.MinEdge.Y; y--) {
- MapNode &n = vm->m_data[i];
- if (!ndef->get(n).sunlight_propagates)
- break;
- n.param1 = LIGHT_SUN;
- vm->m_area.add_y(em, i, -1);
- }
- }
- }
- //printf("propagateSunlight: %dms\n", t.stop());
-}
-
-
-void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax)
-{
- //TimeTaker t("spreadLight");
- VoxelArea a(nmin, nmax);
-
- for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
- for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
- u32 i = vm->m_area.index(a.MinEdge.X, y, z);
- for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++) {
- MapNode &n = vm->m_data[i];
- if (n.getContent() == CONTENT_IGNORE)
- continue;
-
- const ContentFeatures &cf = ndef->get(n);
- if (!cf.light_propagates)
- continue;
-
- // TODO(hmmmmm): Abstract away direct param1 accesses with a
- // wrapper, but something lighter than MapNode::get/setLight
-
- u8 light_produced = cf.light_source;
- if (light_produced)
- n.param1 = light_produced | (light_produced << 4);
-
- u8 light = n.param1;
- if (light) {
- lightSpread(a, v3s16(x, y, z + 1), light);
- lightSpread(a, v3s16(x, y + 1, z ), light);
- lightSpread(a, v3s16(x + 1, y, z ), light);
- lightSpread(a, v3s16(x, y, z - 1), light);
- lightSpread(a, v3s16(x, y - 1, z ), light);
- lightSpread(a, v3s16(x - 1, y, z ), light);
- }
- }
- }
- }
-
- //printf("spreadLight: %dms\n", t.stop());
-}
-
-
-////
-//// MapgenBasic
-////
-
-MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge)
- : Mapgen(mapgenid, params, emerge)
-{
- this->m_emerge = emerge;
- this->m_bmgr = emerge->biomemgr;
-
- //// Here, 'stride' refers to the number of elements needed to skip to index
- //// an adjacent element for that coordinate in noise/height/biome maps
- //// (*not* vmanip content map!)
-
- // Note there is no X stride explicitly defined. Items adjacent in the X
- // coordinate are assumed to be adjacent in memory as well (i.e. stride of 1).
-
- // Number of elements to skip to get to the next Y coordinate
- this->ystride = csize.X;
-
- // Number of elements to skip to get to the next Z coordinate
- this->zstride = csize.X * csize.Y;
-
- // Z-stride value for maps oversized for 1-down overgeneration
- this->zstride_1d = csize.X * (csize.Y + 1);
-
- // Z-stride value for maps oversized for 1-up 1-down overgeneration
- this->zstride_1u1d = csize.X * (csize.Y + 2);
-
- //// Allocate heightmap
- this->heightmap = new s16[csize.X * csize.Z];
-
- //// Initialize biome generator
- // TODO(hmmmm): should we have a way to disable biomemanager biomes?
- biomegen = m_bmgr->createBiomeGen(BIOMEGEN_ORIGINAL, params->bparams, csize);
- biomemap = biomegen->biomemap;
-
- //// Look up some commonly used content
- c_stone = ndef->getId("mapgen_stone");
- c_desert_stone = ndef->getId("mapgen_desert_stone");
- c_sandstone = ndef->getId("mapgen_sandstone");
- c_water_source = ndef->getId("mapgen_water_source");
- c_river_water_source = ndef->getId("mapgen_river_water_source");
- c_lava_source = ndef->getId("mapgen_lava_source");
-
- // Fall back to more basic content if not defined
- // river_water_source cannot fallback to water_source because river water
- // needs to be non-renewable and have a short flow range.
- if (c_desert_stone == CONTENT_IGNORE)
- c_desert_stone = c_stone;
- if (c_sandstone == CONTENT_IGNORE)
- c_sandstone = c_stone;
-
- //// Content used for dungeon generation
- c_cobble = ndef->getId("mapgen_cobble");
- c_mossycobble = ndef->getId("mapgen_mossycobble");
- c_stair_cobble = ndef->getId("mapgen_stair_cobble");
- c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone");
- c_sandstonebrick = ndef->getId("mapgen_sandstonebrick");
- c_stair_sandstone_block = ndef->getId("mapgen_stair_sandstone_block");
-
- // Fall back to more basic content if not defined
- if (c_mossycobble == CONTENT_IGNORE)
- c_mossycobble = c_cobble;
- if (c_stair_cobble == CONTENT_IGNORE)
- c_stair_cobble = c_cobble;
- if (c_stair_desert_stone == CONTENT_IGNORE)
- c_stair_desert_stone = c_desert_stone;
- if (c_sandstonebrick == CONTENT_IGNORE)
- c_sandstonebrick = c_sandstone;
- if (c_stair_sandstone_block == CONTENT_IGNORE)
- c_stair_sandstone_block = c_sandstonebrick;
-}
-
-
-MapgenBasic::~MapgenBasic()
-{
- delete biomegen;
- delete []heightmap;
-}
-
-
-void MapgenBasic::generateBiomes(MgStoneType *mgstone_type,
- content_t *biome_stone)
-{
- // can't generate biomes without a biome generator!
- assert(biomegen);
- assert(biomemap);
-
- const v3s16 &em = vm->m_area.getExtent();
- u32 index = 0;
- MgStoneType stone_type = MGSTONE_OTHER;
- content_t c_biome_stone = c_stone;
-
- noise_filler_depth->perlinMap2D(node_min.X, node_min.Z);
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
- Biome *biome = NULL;
- u16 depth_top = 0;
- u16 base_filler = 0;
- u16 depth_water_top = 0;
- u16 depth_riverbed = 0;
- s16 biome_y_min = -MAX_MAP_GENERATION_LIMIT;
- u32 vi = vm->m_area.index(x, node_max.Y, z);
-
- // Check node at base of mapchunk above, either a node of a previously
- // generated mapchunk or if not, a node of overgenerated base terrain.
- content_t c_above = vm->m_data[vi + em.X].getContent();
- bool air_above = c_above == CONTENT_AIR;
- bool river_water_above = c_above == c_river_water_source;
- bool water_above = c_above == c_water_source || river_water_above;
-
- biomemap[index] = BIOME_NONE;
-
- // If there is air or water above enable top/filler placement, otherwise force
- // nplaced to stone level by setting a number exceeding any possible filler depth.
- u16 nplaced = (air_above || water_above) ? 0 : U16_MAX;
-
- for (s16 y = node_max.Y; y >= node_min.Y; y--) {
- content_t c = vm->m_data[vi].getContent();
- // Biome is (re)calculated:
- // 1. At the surface of stone below air or water.
- // 2. At the surface of water below air.
- // 3. When stone or water is detected but biome has not yet been calculated.
- // 4. When stone or water is detected just below a biome's lower limit.
- bool is_stone_surface = (c == c_stone) &&
- (air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4
-
- bool is_water_surface =
- (c == c_water_source || c == c_river_water_source) &&
- (air_above || !biome || y < biome_y_min); // 2, 3, 4
-
- if (is_stone_surface || is_water_surface) {
- // (Re)calculate biome
- biome = biomegen->getBiomeAtIndex(index, y);
-
- if (biomemap[index] == BIOME_NONE && is_stone_surface)
- biomemap[index] = biome->index;
-
- depth_top = biome->depth_top;
- base_filler = MYMAX(depth_top +
- biome->depth_filler +
- noise_filler_depth->result[index], 0.0f);
- depth_water_top = biome->depth_water_top;
- depth_riverbed = biome->depth_riverbed;
- biome_y_min = biome->y_min;
-
- // Detect stone type for dungeons during every biome calculation.
- // If none detected the last selected biome stone is chosen.
- if (biome->c_stone == c_stone)
- stone_type = MGSTONE_STONE;
- else if (biome->c_stone == c_desert_stone)
- stone_type = MGSTONE_DESERT_STONE;
- else if (biome->c_stone == c_sandstone)
- stone_type = MGSTONE_SANDSTONE;
-
- c_biome_stone = biome->c_stone;
- }
-
- if (c == c_stone) {
- content_t c_below = vm->m_data[vi - em.X].getContent();
-
- // If the node below isn't solid, make this node stone, so that
- // any top/filler nodes above are structurally supported.
- // This is done by aborting the cycle of top/filler placement
- // immediately by forcing nplaced to stone level.
- if (c_below == CONTENT_AIR
- || c_below == c_water_source
- || c_below == c_river_water_source)
- nplaced = U16_MAX;
-
- if (river_water_above) {
- if (nplaced < depth_riverbed) {
- vm->m_data[vi] = MapNode(biome->c_riverbed);
- nplaced++;
- } else {
- nplaced = U16_MAX; // Disable top/filler placement
- river_water_above = false;
- }
- } else if (nplaced < depth_top) {
- vm->m_data[vi] = MapNode(biome->c_top);
- nplaced++;
- } else if (nplaced < base_filler) {
- vm->m_data[vi] = MapNode(biome->c_filler);
- nplaced++;
- } else {
- vm->m_data[vi] = MapNode(biome->c_stone);
- nplaced = U16_MAX; // Disable top/filler placement
- }
-
- air_above = false;
- water_above = false;
- } else if (c == c_water_source) {
- vm->m_data[vi] = MapNode((y > (s32)(water_level - depth_water_top))
- ? biome->c_water_top : biome->c_water);
- nplaced = 0; // Enable top/filler placement for next surface
- air_above = false;
- water_above = true;
- } else if (c == c_river_water_source) {
- vm->m_data[vi] = MapNode(biome->c_river_water);
- nplaced = 0; // Enable riverbed placement for next surface
- air_above = false;
- water_above = true;
- river_water_above = true;
- } else if (c == CONTENT_AIR) {
- nplaced = 0; // Enable top/filler placement for next surface
- air_above = true;
- water_above = false;
- } else { // Possible various nodes overgenerated from neighbouring mapchunks
- nplaced = U16_MAX; // Disable top/filler placement
- air_above = false;
- water_above = false;
- }
-
- vm->m_area.add_y(em, vi, -1);
- }
- }
-
- *mgstone_type = stone_type;
- *biome_stone = c_biome_stone;
-}
-
-
-void MapgenBasic::dustTopNodes()
-{
- if (node_max.Y < water_level)
- return;
-
- const v3s16 &em = vm->m_area.getExtent();
- u32 index = 0;
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
- Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index]);
-
- if (biome->c_dust == CONTENT_IGNORE)
- continue;
-
- u32 vi = vm->m_area.index(x, full_node_max.Y, z);
- content_t c_full_max = vm->m_data[vi].getContent();
- s16 y_start;
-
- if (c_full_max == CONTENT_AIR) {
- y_start = full_node_max.Y - 1;
- } else if (c_full_max == CONTENT_IGNORE) {
- vi = vm->m_area.index(x, node_max.Y + 1, z);
- content_t c_max = vm->m_data[vi].getContent();
-
- if (c_max == CONTENT_AIR)
- y_start = node_max.Y;
- else
- continue;
- } else {
- continue;
- }
-
- vi = vm->m_area.index(x, y_start, z);
- for (s16 y = y_start; y >= node_min.Y - 1; y--) {
- if (vm->m_data[vi].getContent() != CONTENT_AIR)
- break;
-
- vm->m_area.add_y(em, vi, -1);
- }
-
- content_t c = vm->m_data[vi].getContent();
- if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE && c != biome->c_dust) {
- vm->m_area.add_y(em, vi, 1);
- vm->m_data[vi] = MapNode(biome->c_dust);
- }
- }
-}
-
-
-void MapgenBasic::generateCaves(s16 max_stone_y, s16 large_cave_depth)
-{
- if (max_stone_y < node_min.Y)
- return;
-
- CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize,
- &np_cave1, &np_cave2, seed, cave_width);
-
- caves_noise.generateCaves(vm, node_min, node_max, biomemap);
-
- if (node_max.Y > large_cave_depth)
- return;
-
- PseudoRandom ps(blockseed + 21343);
- u32 bruises_count = ps.range(0, 2);
- for (u32 i = 0; i < bruises_count; i++) {
- CavesRandomWalk cave(ndef, &gennotify, seed, water_level,
- c_water_source, CONTENT_IGNORE, lava_depth);
-
- cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap);
- }
-}
-
-
-bool MapgenBasic::generateCaverns(s16 max_stone_y)
-{
- if (node_min.Y > max_stone_y || node_min.Y > cavern_limit)
- return false;
-
- CavernsNoise caverns_noise(ndef, csize, &np_cavern,
- seed, cavern_limit, cavern_taper, cavern_threshold);
-
- return caverns_noise.generateCaverns(vm, node_min, node_max);
-}
-
-
-void MapgenBasic::generateDungeons(s16 max_stone_y,
- MgStoneType stone_type, content_t biome_stone)
-{
- if (max_stone_y < node_min.Y)
- return;
-
- DungeonParams dp;
-
- dp.seed = seed;
- dp.c_water = c_water_source;
- dp.c_river_water = c_river_water_source;
-
- dp.only_in_ground = true;
- dp.corridor_len_min = 1;
- dp.corridor_len_max = 13;
- dp.rooms_min = 2;
- dp.rooms_max = 16;
- dp.y_min = -MAX_MAP_GENERATION_LIMIT;
- dp.y_max = MAX_MAP_GENERATION_LIMIT;
-
- dp.np_density = nparams_dungeon_density;
- dp.np_alt_wall = nparams_dungeon_alt_wall;
-
- switch (stone_type) {
- default:
- case MGSTONE_STONE:
- dp.c_wall = c_cobble;
- dp.c_alt_wall = c_mossycobble;
- dp.c_stair = c_stair_cobble;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(1, 2, 1);
- dp.room_size_min = v3s16(4, 4, 4);
- dp.room_size_max = v3s16(8, 6, 8);
- dp.room_size_large_min = v3s16(8, 8, 8);
- dp.room_size_large_max = v3s16(16, 16, 16);
- dp.notifytype = GENNOTIFY_DUNGEON;
- break;
- case MGSTONE_DESERT_STONE:
- dp.c_wall = c_desert_stone;
- dp.c_alt_wall = CONTENT_IGNORE;
- dp.c_stair = c_stair_desert_stone;
-
- dp.diagonal_dirs = true;
- dp.holesize = v3s16(2, 3, 2);
- dp.room_size_min = v3s16(6, 9, 6);
- dp.room_size_max = v3s16(10, 11, 10);
- dp.room_size_large_min = v3s16(10, 13, 10);
- dp.room_size_large_max = v3s16(18, 21, 18);
- dp.notifytype = GENNOTIFY_TEMPLE;
- break;
- case MGSTONE_SANDSTONE:
- dp.c_wall = c_sandstonebrick;
- dp.c_alt_wall = CONTENT_IGNORE;
- dp.c_stair = c_stair_sandstone_block;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(2, 2, 2);
- dp.room_size_min = v3s16(6, 4, 6);
- dp.room_size_max = v3s16(10, 6, 10);
- dp.room_size_large_min = v3s16(10, 8, 10);
- dp.room_size_large_max = v3s16(18, 16, 18);
- dp.notifytype = GENNOTIFY_DUNGEON;
- break;
- case MGSTONE_OTHER:
- dp.c_wall = biome_stone;
- dp.c_alt_wall = biome_stone;
- dp.c_stair = biome_stone;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(1, 2, 1);
- dp.room_size_min = v3s16(4, 4, 4);
- dp.room_size_max = v3s16(8, 6, 8);
- dp.room_size_large_min = v3s16(8, 8, 8);
- dp.room_size_large_max = v3s16(16, 16, 16);
- dp.notifytype = GENNOTIFY_DUNGEON;
- break;
- }
-
- DungeonGen dgen(ndef, &gennotify, &dp);
- dgen.generate(vm, blockseed, full_node_min, full_node_max);
-}
-
-
-////
-//// GenerateNotifier
-////
-
-GenerateNotifier::GenerateNotifier(u32 notify_on,
- std::set<u32> *notify_on_deco_ids)
-{
- m_notify_on = notify_on;
- m_notify_on_deco_ids = notify_on_deco_ids;
-}
-
-
-void GenerateNotifier::setNotifyOn(u32 notify_on)
-{
- m_notify_on = notify_on;
-}
-
-
-void GenerateNotifier::setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids)
-{
- m_notify_on_deco_ids = notify_on_deco_ids;
-}
-
-
-bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id)
-{
- if (!(m_notify_on & (1 << type)))
- return false;
-
- if (type == GENNOTIFY_DECORATION &&
- m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->end())
- return false;
-
- GenNotifyEvent gne;
- gne.type = type;
- gne.pos = pos;
- gne.id = id;
- m_notify_events.push_back(gne);
-
- return true;
-}
-
-
-void GenerateNotifier::getEvents(
- std::map<std::string, std::vector<v3s16> > &event_map,
- bool peek_events)
-{
- std::list<GenNotifyEvent>::iterator it;
-
- for (it = m_notify_events.begin(); it != m_notify_events.end(); ++it) {
- GenNotifyEvent &gn = *it;
- std::string name = (gn.type == GENNOTIFY_DECORATION) ?
- "decoration#"+ itos(gn.id) :
- flagdesc_gennotify[gn.type].name;
-
- event_map[name].push_back(gn.pos);
- }
-
- if (!peek_events)
- m_notify_events.clear();
-}
-
-
-////
-//// MapgenParams
-////
-
-
-MapgenParams::~MapgenParams()
-{
- delete bparams;
-}
-
-
-void MapgenParams::readParams(const Settings *settings)
-{
- std::string seed_str;
- const char *seed_name = (settings == g_settings) ? "fixed_map_seed" : "seed";
-
- if (settings->getNoEx(seed_name, seed_str)) {
- if (!seed_str.empty())
- seed = read_seed(seed_str.c_str());
- else
- myrand_bytes(&seed, sizeof(seed));
- }
-
- std::string mg_name;
- if (settings->getNoEx("mg_name", mg_name)) {
- mgtype = Mapgen::getMapgenType(mg_name);
- if (mgtype == MAPGEN_INVALID)
- mgtype = MAPGEN_DEFAULT;
- }
-
- settings->getS16NoEx("water_level", water_level);
- settings->getS16NoEx("mapgen_limit", mapgen_limit);
- settings->getS16NoEx("chunksize", chunksize);
- settings->getFlagStrNoEx("mg_flags", flags, flagdesc_mapgen);
-
- delete bparams;
- bparams = BiomeManager::createBiomeParams(BIOMEGEN_ORIGINAL);
- if (bparams) {
- bparams->readParams(settings);
- bparams->seed = seed;
- }
-}
-
-
-void MapgenParams::writeParams(Settings *settings) const
-{
- settings->set("mg_name", Mapgen::getMapgenName(mgtype));
- settings->setU64("seed", seed);
- settings->setS16("water_level", water_level);
- settings->setS16("mapgen_limit", mapgen_limit);
- settings->setS16("chunksize", chunksize);
- settings->setFlagStr("mg_flags", flags, flagdesc_mapgen, U32_MAX);
-
- if (bparams)
- bparams->writeParams(settings);
-}
-
-// Calculate edges of outermost generated mapchunks (less than
-// 'mapgen_limit'), and corresponding exact limits for SAO entities.
-void MapgenParams::calcMapgenEdges()
-{
- if (m_mapgen_edges_calculated)
- return;
-
- // Central chunk offset, in blocks
- s16 ccoff_b = -chunksize / 2;
- // Chunksize, in nodes
- s32 csize_n = chunksize * MAP_BLOCKSIZE;
- // Minp/maxp of central chunk, in nodes
- s16 ccmin = ccoff_b * MAP_BLOCKSIZE;
- s16 ccmax = ccmin + csize_n - 1;
- // Fullminp/fullmaxp of central chunk, in nodes
- s16 ccfmin = ccmin - MAP_BLOCKSIZE;
- s16 ccfmax = ccmax + MAP_BLOCKSIZE;
- // Effective mapgen limit, in blocks
- // Uses same calculation as ServerMap::blockpos_over_mapgen_limit(v3s16 p)
- s16 mapgen_limit_b = rangelim(mapgen_limit,
- 0, MAX_MAP_GENERATION_LIMIT) / MAP_BLOCKSIZE;
- // Effective mapgen limits, in nodes
- s16 mapgen_limit_min = -mapgen_limit_b * MAP_BLOCKSIZE;
- s16 mapgen_limit_max = (mapgen_limit_b + 1) * MAP_BLOCKSIZE - 1;
- // Number of complete chunks from central chunk fullminp/fullmaxp
- // to effective mapgen limits.
- s16 numcmin = MYMAX((ccfmin - mapgen_limit_min) / csize_n, 0);
- s16 numcmax = MYMAX((mapgen_limit_max - ccfmax) / csize_n, 0);
- // Mapgen edges, in nodes
- mapgen_edge_min = ccmin - numcmin * csize_n;
- mapgen_edge_max = ccmax + numcmax * csize_n;
- // SAO position limits, in Irrlicht units
- m_sao_limit_min = mapgen_edge_min * BS - 3.0f;
- m_sao_limit_max = mapgen_edge_max * BS + 3.0f;
-
- m_mapgen_edges_calculated = true;
-}
-
-
-bool MapgenParams::saoPosOverLimit(const v3f &p)
-{
- if (!m_mapgen_edges_calculated)
- calcMapgenEdges();
-
- return p.X < m_sao_limit_min ||
- p.X > m_sao_limit_max ||
- p.Y < m_sao_limit_min ||
- p.Y > m_sao_limit_max ||
- p.Z < m_sao_limit_min ||
- p.Z > m_sao_limit_max;
-}
-
-
-s32 MapgenParams::getSpawnRangeMax()
-{
- calcMapgenEdges();
-
- return MYMIN(-mapgen_edge_min, mapgen_edge_max);
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "noise.h"
-#include "nodedef.h"
-#include "util/string.h"
-#include "util/container.h"
-
-#define MAPGEN_DEFAULT MAPGEN_V7
-#define MAPGEN_DEFAULT_NAME "v7"
-
-/////////////////// Mapgen flags
-#define MG_TREES 0x01 // Deprecated. Moved into mgv6 flags
-#define MG_CAVES 0x02
-#define MG_DUNGEONS 0x04
-#define MG_FLAT 0x08 // Deprecated. Moved into mgv6 flags
-#define MG_LIGHT 0x10
-#define MG_DECORATIONS 0x20
-
-typedef u8 biome_t; // copy from mg_biome.h to avoid an unnecessary include
-
-class Settings;
-class MMVManip;
-class INodeDefManager;
-
-extern FlagDesc flagdesc_mapgen[];
-extern FlagDesc flagdesc_gennotify[];
-
-class Biome;
-class BiomeGen;
-struct BiomeParams;
-class BiomeManager;
-class EmergeManager;
-class MapBlock;
-class VoxelManipulator;
-struct BlockMakeData;
-class VoxelArea;
-class Map;
-
-enum MapgenObject {
- MGOBJ_VMANIP,
- MGOBJ_HEIGHTMAP,
- MGOBJ_BIOMEMAP,
- MGOBJ_HEATMAP,
- MGOBJ_HUMIDMAP,
- MGOBJ_GENNOTIFY
-};
-
-enum GenNotifyType {
- GENNOTIFY_DUNGEON,
- GENNOTIFY_TEMPLE,
- GENNOTIFY_CAVE_BEGIN,
- GENNOTIFY_CAVE_END,
- GENNOTIFY_LARGECAVE_BEGIN,
- GENNOTIFY_LARGECAVE_END,
- GENNOTIFY_DECORATION,
- NUM_GENNOTIFY_TYPES
-};
-
-enum MgStoneType {
- MGSTONE_STONE,
- MGSTONE_DESERT_STONE,
- MGSTONE_SANDSTONE,
- MGSTONE_OTHER,
-};
-
-struct GenNotifyEvent {
- GenNotifyType type;
- v3s16 pos;
- u32 id;
-};
-
-class GenerateNotifier {
-public:
- GenerateNotifier() = default;
- GenerateNotifier(u32 notify_on, std::set<u32> *notify_on_deco_ids);
-
- void setNotifyOn(u32 notify_on);
- void setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids);
-
- bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0);
- void getEvents(std::map<std::string, std::vector<v3s16> > &event_map,
- bool peek_events=false);
-
-private:
- u32 m_notify_on = 0;
- std::set<u32> *m_notify_on_deco_ids;
- std::list<GenNotifyEvent> m_notify_events;
-};
-
-enum MapgenType {
- MAPGEN_V5,
- MAPGEN_V6,
- MAPGEN_V7,
- MAPGEN_FLAT,
- MAPGEN_FRACTAL,
- MAPGEN_VALLEYS,
- MAPGEN_SINGLENODE,
- MAPGEN_CARPATHIAN,
- MAPGEN_INVALID,
-};
-
-struct MapgenParams {
- MapgenParams() = default;
- virtual ~MapgenParams();
-
- MapgenType mgtype = MAPGEN_DEFAULT;
- s16 chunksize = 5;
- u64 seed = 0;
- s16 water_level = 1;
- s16 mapgen_limit = MAX_MAP_GENERATION_LIMIT;
- u32 flags = MG_CAVES | MG_LIGHT | MG_DECORATIONS;
-
- BiomeParams *bparams = nullptr;
-
- s16 mapgen_edge_min = -MAX_MAP_GENERATION_LIMIT;
- s16 mapgen_edge_max = MAX_MAP_GENERATION_LIMIT;
-
- virtual void readParams(const Settings *settings);
- virtual void writeParams(Settings *settings) const;
-
- bool saoPosOverLimit(const v3f &p);
- s32 getSpawnRangeMax();
-
-private:
- void calcMapgenEdges();
-
- float m_sao_limit_min = -MAX_MAP_GENERATION_LIMIT * BS;
- float m_sao_limit_max = MAX_MAP_GENERATION_LIMIT * BS;
- bool m_mapgen_edges_calculated = false;
-};
-
-
-/*
- Generic interface for map generators. All mapgens must inherit this class.
- If a feature exposed by a public member pointer is not supported by a
- certain mapgen, it must be set to NULL.
-
- Apart from makeChunk, getGroundLevelAtPoint, and getSpawnLevelAtPoint, all
- methods can be used by constructing a Mapgen base class and setting the
- appropriate public members (e.g. vm, ndef, and so on).
-*/
-class Mapgen {
-public:
- s32 seed = 0;
- int water_level = 0;
- int mapgen_limit = 0;
- u32 flags = 0;
- bool generating = false;
- int id = -1;
-
- MMVManip *vm = nullptr;
- INodeDefManager *ndef = nullptr;
-
- u32 blockseed;
- s16 *heightmap = nullptr;
- biome_t *biomemap = nullptr;
- v3s16 csize;
-
- BiomeGen *biomegen = nullptr;
- GenerateNotifier gennotify;
-
- Mapgen() = default;
- Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge);
- virtual ~Mapgen() = default;
- DISABLE_CLASS_COPY(Mapgen);
-
- virtual MapgenType getType() const { return MAPGEN_INVALID; }
-
- static u32 getBlockSeed(v3s16 p, s32 seed);
- static u32 getBlockSeed2(v3s16 p, s32 seed);
- s16 findGroundLevelFull(v2s16 p2d);
- s16 findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax);
- s16 findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax);
- void updateHeightmap(v3s16 nmin, v3s16 nmax);
- void getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
- s16 *floors, s16 *ceilings, u16 *num_floors, u16 *num_ceilings);
-
- void updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax);
-
- void setLighting(u8 light, v3s16 nmin, v3s16 nmax);
- void lightSpread(VoxelArea &a, v3s16 p, u8 light);
- void calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
- bool propagate_shadow = true);
- void propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow);
- void spreadLight(v3s16 nmin, v3s16 nmax);
-
- virtual void makeChunk(BlockMakeData *data) {}
- virtual int getGroundLevelAtPoint(v2s16 p) { return 0; }
-
- // getSpawnLevelAtPoint() is a function within each mapgen that returns a
- // suitable y co-ordinate for player spawn ('suitable' usually meaning
- // within 16 nodes of water_level). If a suitable spawn level cannot be
- // found at the specified (X, Z) 'MAX_MAP_GENERATION_LIMIT' is returned to
- // signify this and to cause Server::findSpawnPos() to try another (X, Z).
- virtual int getSpawnLevelAtPoint(v2s16 p) { return 0; }
-
- // Mapgen management functions
- static MapgenType getMapgenType(const std::string &mgname);
- static const char *getMapgenName(MapgenType mgtype);
- static Mapgen *createMapgen(MapgenType mgtype, int mgid,
- MapgenParams *params, EmergeManager *emerge);
- static MapgenParams *createMapgenParams(MapgenType mgtype);
- static void getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden);
-
-private:
- // isLiquidHorizontallyFlowable() is a helper function for updateLiquid()
- // that checks whether there are floodable nodes without liquid beneath
- // the node at index vi.
- inline bool isLiquidHorizontallyFlowable(u32 vi, v3s16 em);
-};
-
-/*
- MapgenBasic is a Mapgen implementation that handles basic functionality
- the majority of conventional mapgens will probably want to use, but isn't
- generic enough to be included as part of the base Mapgen class (such as
- generating biome terrain over terrain node skeletons, generating caves,
- dungeons, etc.)
-
- Inherit MapgenBasic instead of Mapgen to add this basic functionality to
- your mapgen without having to reimplement it. Feel free to override any of
- these methods if you desire different or more advanced behavior.
-
- Note that you must still create your own generateTerrain implementation when
- inheriting MapgenBasic.
-*/
-class MapgenBasic : public Mapgen {
-public:
- MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge);
- virtual ~MapgenBasic();
-
- virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth);
- virtual bool generateCaverns(s16 max_stone_y);
- virtual void generateDungeons(s16 max_stone_y,
- MgStoneType stone_type, content_t biome_stone);
- virtual void generateBiomes(MgStoneType *mgstone_type,
- content_t *biome_stone);
- virtual void dustTopNodes();
-
-protected:
- EmergeManager *m_emerge;
- BiomeManager *m_bmgr;
-
- Noise *noise_filler_depth;
-
- v3s16 node_min;
- v3s16 node_max;
- v3s16 full_node_min;
- v3s16 full_node_max;
-
- // Content required for generateBiomes
- content_t c_stone;
- content_t c_desert_stone;
- content_t c_sandstone;
- content_t c_water_source;
- content_t c_river_water_source;
- content_t c_lava_source;
-
- // Content required for generateDungeons
- content_t c_cobble;
- content_t c_stair_cobble;
- content_t c_mossycobble;
- content_t c_stair_desert_stone;
- content_t c_sandstonebrick;
- content_t c_stair_sandstone_block;
-
- int ystride;
- int zstride;
- int zstride_1d;
- int zstride_1u1d;
-
- u32 spflags;
-
- NoiseParams np_cave1;
- NoiseParams np_cave2;
- NoiseParams np_cavern;
- float cave_width;
- float cavern_limit;
- float cavern_taper;
- float cavern_threshold;
- int lava_depth;
-};
--- /dev/null
+set(mapgen_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/cavegen.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/dungeongen.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_carpathian.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_flat.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_fractal.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_singlenode.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v5.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v6.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v7.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_valleys.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_biome.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_decoration.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_ore.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_schematic.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/treegen.cpp
+ PARENT_SCOPE
+)
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "util/numeric.h"
+#include "map.h"
+#include "mapgen.h"
+#include "mapgen_v5.h"
+#include "mapgen_v6.h"
+#include "mapgen_v7.h"
+#include "mg_biome.h"
+#include "cavegen.h"
+
+static NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0);
+
+
+////
+//// CavesNoiseIntersection
+////
+
+CavesNoiseIntersection::CavesNoiseIntersection(
+ INodeDefManager *nodedef, BiomeManager *biomemgr, v3s16 chunksize,
+ NoiseParams *np_cave1, NoiseParams *np_cave2, s32 seed, float cave_width)
+{
+ assert(nodedef);
+ assert(biomemgr);
+
+ m_ndef = nodedef;
+ m_bmgr = biomemgr;
+
+ m_csize = chunksize;
+ m_cave_width = cave_width;
+
+ m_ystride = m_csize.X;
+ m_zstride_1d = m_csize.X * (m_csize.Y + 1);
+
+ // Noises are created using 1-down overgeneration
+ // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for
+ // re-carving the solid overtop placed for blocking sunlight
+ noise_cave1 = new Noise(np_cave1, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
+ noise_cave2 = new Noise(np_cave2, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
+}
+
+
+CavesNoiseIntersection::~CavesNoiseIntersection()
+{
+ delete noise_cave1;
+ delete noise_cave2;
+}
+
+
+void CavesNoiseIntersection::generateCaves(MMVManip *vm,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ assert(vm);
+ assert(biomemap);
+
+ noise_cave1->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
+ noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 index2d = 0; // Biomemap index
+
+ for (s16 z = nmin.Z; z <= nmax.Z; z++)
+ for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) {
+ bool column_is_open = false; // Is column open to overground
+ bool is_under_river = false; // Is column under river water
+ bool is_under_tunnel = false; // Is tunnel or is under tunnel
+ bool is_top_filler_above = false; // Is top or filler above node
+ // Indexes at column top
+ u32 vi = vm->m_area.index(x, nmax.Y, z);
+ u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride +
+ (x - nmin.X); // 3D noise index
+ // Biome of column
+ Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index2d]);
+ u16 depth_top = biome->depth_top;
+ u16 base_filler = depth_top + biome->depth_filler;
+ u16 depth_riverbed = biome->depth_riverbed;
+ u16 nplaced = 0;
+ // Don't excavate the overgenerated stone at nmax.Y + 1,
+ // this creates a 'roof' over the tunnel, preventing light in
+ // tunnels at mapchunk borders when generating mapchunks upwards.
+ // This 'roof' is removed when the mapchunk above is generated.
+ for (s16 y = nmax.Y; y >= nmin.Y - 1; y--,
+ index3d -= m_ystride,
+ vm->m_area.add_y(em, vi, -1)) {
+ content_t c = vm->m_data[vi].getContent();
+
+ if (c == CONTENT_AIR || c == biome->c_water_top ||
+ c == biome->c_water) {
+ column_is_open = true;
+ is_top_filler_above = false;
+ continue;
+ }
+
+ if (c == biome->c_river_water) {
+ column_is_open = true;
+ is_under_river = true;
+ is_top_filler_above = false;
+ continue;
+ }
+
+ // Ground
+ float d1 = contour(noise_cave1->result[index3d]);
+ float d2 = contour(noise_cave2->result[index3d]);
+
+ if (d1 * d2 > m_cave_width && m_ndef->get(c).is_ground_content) {
+ // In tunnel and ground content, excavate
+ vm->m_data[vi] = MapNode(CONTENT_AIR);
+ is_under_tunnel = true;
+ // If tunnel roof is top or filler, replace with stone
+ if (is_top_filler_above)
+ vm->m_data[vi + em.X] = MapNode(biome->c_stone);
+ is_top_filler_above = false;
+ } else if (column_is_open && is_under_tunnel &&
+ (c == biome->c_stone || c == biome->c_filler)) {
+ // Tunnel entrance floor, place biome surface nodes
+ if (is_under_river) {
+ if (nplaced < depth_riverbed) {
+ vm->m_data[vi] = MapNode(biome->c_riverbed);
+ is_top_filler_above = true;
+ nplaced++;
+ } else {
+ // Disable top/filler placement
+ column_is_open = false;
+ is_under_river = false;
+ is_under_tunnel = false;
+ }
+ } else if (nplaced < depth_top) {
+ vm->m_data[vi] = MapNode(biome->c_top);
+ is_top_filler_above = true;
+ nplaced++;
+ } else if (nplaced < base_filler) {
+ vm->m_data[vi] = MapNode(biome->c_filler);
+ is_top_filler_above = true;
+ nplaced++;
+ } else {
+ // Disable top/filler placement
+ column_is_open = false;
+ is_under_tunnel = false;
+ }
+ } else {
+ // Not tunnel or tunnel entrance floor
+ // Check node for possible replacing with stone for tunnel roof
+ if (c == biome->c_top || c == biome->c_filler)
+ is_top_filler_above = true;
+
+ column_is_open = false;
+ }
+ }
+ }
+}
+
+
+////
+//// CavernsNoise
+////
+
+CavernsNoise::CavernsNoise(
+ INodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern,
+ s32 seed, float cavern_limit, float cavern_taper, float cavern_threshold)
+{
+ assert(nodedef);
+
+ m_ndef = nodedef;
+
+ m_csize = chunksize;
+ m_cavern_limit = cavern_limit;
+ m_cavern_taper = cavern_taper;
+ m_cavern_threshold = cavern_threshold;
+
+ m_ystride = m_csize.X;
+ m_zstride_1d = m_csize.X * (m_csize.Y + 1);
+
+ // Noise is created using 1-down overgeneration
+ // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for
+ // re-carving the solid overtop placed for blocking sunlight
+ noise_cavern = new Noise(np_cavern, seed, m_csize.X, m_csize.Y + 1, m_csize.Z);
+
+ c_water_source = m_ndef->getId("mapgen_water_source");
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = CONTENT_AIR;
+
+ c_lava_source = m_ndef->getId("mapgen_lava_source");
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = CONTENT_AIR;
+}
+
+
+CavernsNoise::~CavernsNoise()
+{
+ delete noise_cavern;
+}
+
+
+bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax)
+{
+ assert(vm);
+
+ // Calculate noise
+ noise_cavern->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z);
+
+ // Cache cavern_amp values
+ float *cavern_amp = new float[m_csize.Y + 1];
+ u8 cavern_amp_index = 0; // Index zero at column top
+ for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, cavern_amp_index++) {
+ cavern_amp[cavern_amp_index] =
+ MYMIN((m_cavern_limit - y) / (float)m_cavern_taper, 1.0f);
+ }
+
+ //// Place nodes
+ bool near_cavern = false;
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 index2d = 0;
+
+ for (s16 z = nmin.Z; z <= nmax.Z; z++)
+ for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) {
+ // Reset cave_amp index to column top
+ cavern_amp_index = 0;
+ // Initial voxelmanip index at column top
+ u32 vi = vm->m_area.index(x, nmax.Y, z);
+ // Initial 3D noise index at column top
+ u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride +
+ (x - nmin.X);
+ // Don't excavate the overgenerated stone at node_max.Y + 1,
+ // this creates a 'roof' over the cavern, preventing light in
+ // caverns at mapchunk borders when generating mapchunks upwards.
+ // This 'roof' is excavated when the mapchunk above is generated.
+ for (s16 y = nmax.Y; y >= nmin.Y - 1; y--,
+ index3d -= m_ystride,
+ vm->m_area.add_y(em, vi, -1),
+ cavern_amp_index++) {
+ content_t c = vm->m_data[vi].getContent();
+ float n_absamp_cavern = fabs(noise_cavern->result[index3d]) *
+ cavern_amp[cavern_amp_index];
+ // Disable CavesRandomWalk at a safe distance from caverns
+ // to avoid excessively spreading liquids in caverns.
+ if (n_absamp_cavern > m_cavern_threshold - 0.1f) {
+ near_cavern = true;
+ if (n_absamp_cavern > m_cavern_threshold &&
+ m_ndef->get(c).is_ground_content)
+ vm->m_data[vi] = MapNode(CONTENT_AIR);
+ }
+ }
+ }
+
+ delete[] cavern_amp;
+
+ return near_cavern;
+}
+
+
+////
+//// CavesRandomWalk
+////
+
+CavesRandomWalk::CavesRandomWalk(
+ INodeDefManager *ndef,
+ GenerateNotifier *gennotify,
+ s32 seed,
+ int water_level,
+ content_t water_source,
+ content_t lava_source,
+ int lava_depth)
+{
+ assert(ndef);
+
+ this->ndef = ndef;
+ this->gennotify = gennotify;
+ this->seed = seed;
+ this->water_level = water_level;
+ this->np_caveliquids = &nparams_caveliquids;
+ this->lava_depth = lava_depth;
+
+ c_water_source = water_source;
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = ndef->getId("mapgen_water_source");
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = CONTENT_AIR;
+
+ c_lava_source = lava_source;
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = ndef->getId("mapgen_lava_source");
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = CONTENT_AIR;
+}
+
+
+void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax,
+ PseudoRandom *ps, bool is_large_cave, int max_stone_height, s16 *heightmap)
+{
+ assert(vm);
+ assert(ps);
+
+ this->vm = vm;
+ this->ps = ps;
+ this->node_min = nmin;
+ this->node_max = nmax;
+ this->heightmap = heightmap;
+ this->large_cave = is_large_cave;
+
+ this->ystride = nmax.X - nmin.X + 1;
+
+ // Set initial parameters from randomness
+ int dswitchint = ps->range(1, 14);
+ flooded = ps->range(1, 2) == 2;
+
+ if (large_cave) {
+ part_max_length_rs = ps->range(2, 4);
+ tunnel_routepoints = ps->range(5, ps->range(15, 30));
+ min_tunnel_diameter = 5;
+ max_tunnel_diameter = ps->range(7, ps->range(8, 24));
+ } else {
+ part_max_length_rs = ps->range(2, 9);
+ tunnel_routepoints = ps->range(10, ps->range(15, 30));
+ min_tunnel_diameter = 2;
+ max_tunnel_diameter = ps->range(2, 6);
+ }
+
+ large_cave_is_flat = (ps->range(0, 1) == 0);
+
+ main_direction = v3f(0, 0, 0);
+
+ // Allowed route area size in nodes
+ ar = node_max - node_min + v3s16(1, 1, 1);
+ // Area starting point in nodes
+ of = node_min;
+
+ // Allow a bit more
+ //(this should be more than the maximum radius of the tunnel)
+ const s16 insure = 10;
+ s16 more = MYMAX(MAP_BLOCKSIZE - max_tunnel_diameter / 2 - insure, 1);
+ ar += v3s16(1, 0, 1) * more * 2;
+ of -= v3s16(1, 0, 1) * more;
+
+ route_y_min = 0;
+ // Allow half a diameter + 7 over stone surface
+ route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7;
+
+ // Limit maximum to area
+ route_y_max = rangelim(route_y_max, 0, ar.Y - 1);
+
+ if (large_cave) {
+ s16 minpos = 0;
+ if (node_min.Y < water_level && node_max.Y > water_level) {
+ minpos = water_level - max_tunnel_diameter / 3 - of.Y;
+ route_y_max = water_level + max_tunnel_diameter / 3 - of.Y;
+ }
+ route_y_min = ps->range(minpos, minpos + max_tunnel_diameter);
+ route_y_min = rangelim(route_y_min, 0, route_y_max);
+ }
+
+ s16 route_start_y_min = route_y_min;
+ s16 route_start_y_max = route_y_max;
+
+ route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1);
+ route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1);
+
+ // Randomize starting position
+ orp.Z = (float)(ps->next() % ar.Z) + 0.5f;
+ orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f;
+ orp.X = (float)(ps->next() % ar.X) + 0.5f;
+
+ // Add generation notify begin event
+ if (gennotify) {
+ v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
+ GenNotifyType notifytype = large_cave ?
+ GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN;
+ gennotify->addEvent(notifytype, abs_pos);
+ }
+
+ // Generate some tunnel starting from orp
+ for (u16 j = 0; j < tunnel_routepoints; j++)
+ makeTunnel(j % dswitchint == 0);
+
+ // Add generation notify end event
+ if (gennotify) {
+ v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
+ GenNotifyType notifytype = large_cave ?
+ GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END;
+ gennotify->addEvent(notifytype, abs_pos);
+ }
+}
+
+
+void CavesRandomWalk::makeTunnel(bool dirswitch)
+{
+ if (dirswitch && !large_cave) {
+ main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10;
+ main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30;
+ main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10;
+
+ main_direction *= (float)ps->range(0, 10) / 10;
+ }
+
+ // Randomize size
+ s16 min_d = min_tunnel_diameter;
+ s16 max_d = max_tunnel_diameter;
+ rs = ps->range(min_d, max_d);
+ s16 rs_part_max_length_rs = rs * part_max_length_rs;
+
+ v3s16 maxlen;
+ if (large_cave) {
+ maxlen = v3s16(
+ rs_part_max_length_rs,
+ rs_part_max_length_rs / 2,
+ rs_part_max_length_rs
+ );
+ } else {
+ maxlen = v3s16(
+ rs_part_max_length_rs,
+ ps->range(1, rs_part_max_length_rs),
+ rs_part_max_length_rs
+ );
+ }
+
+ v3f vec;
+ // Jump downward sometimes
+ if (!large_cave && ps->range(0, 12) == 0) {
+ vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2;
+ vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y;
+ vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2;
+ } else {
+ vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2;
+ vec.Y = (float)(ps->next() % (maxlen.Y * 1)) - (float)maxlen.Y / 2;
+ vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2;
+ }
+
+ // Do not make caves that are above ground.
+ // It is only necessary to check the startpoint and endpoint.
+ v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2;
+ v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1;
+ if (isPosAboveSurface(p1) || isPosAboveSurface(p2))
+ return;
+
+ vec += main_direction;
+
+ v3f rp = orp + vec;
+ if (rp.X < 0)
+ rp.X = 0;
+ else if (rp.X >= ar.X)
+ rp.X = ar.X - 1;
+
+ if (rp.Y < route_y_min)
+ rp.Y = route_y_min;
+ else if (rp.Y >= route_y_max)
+ rp.Y = route_y_max - 1;
+
+ if (rp.Z < 0)
+ rp.Z = 0;
+ else if (rp.Z >= ar.Z)
+ rp.Z = ar.Z - 1;
+
+ vec = rp - orp;
+
+ float veclen = vec.getLength();
+ if (veclen < 0.05f)
+ veclen = 1.0f;
+
+ // Every second section is rough
+ bool randomize_xz = (ps->range(1, 2) == 1);
+
+ // Carve routes
+ for (float f = 0.f; f < 1.0f; f += 1.0f / veclen)
+ carveRoute(vec, f, randomize_xz);
+
+ orp = rp;
+}
+
+
+void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz)
+{
+ MapNode airnode(CONTENT_AIR);
+ MapNode waternode(c_water_source);
+ MapNode lavanode(c_lava_source);
+
+ v3s16 startp(orp.X, orp.Y, orp.Z);
+ startp += of;
+
+ float nval = NoisePerlin3D(np_caveliquids, startp.X,
+ startp.Y, startp.Z, seed);
+ MapNode liquidnode = (nval < 0.40f && node_max.Y < lava_depth) ?
+ lavanode : waternode;
+
+ v3f fp = orp + vec * f;
+ fp.X += 0.1f * ps->range(-10, 10);
+ fp.Z += 0.1f * ps->range(-10, 10);
+ v3s16 cp(fp.X, fp.Y, fp.Z);
+
+ s16 d0 = -rs / 2;
+ s16 d1 = d0 + rs;
+ if (randomize_xz) {
+ d0 += ps->range(-1, 1);
+ d1 += ps->range(-1, 1);
+ }
+
+ bool flat_cave_floor = !large_cave && ps->range(0, 2) == 2;
+
+ for (s16 z0 = d0; z0 <= d1; z0++) {
+ s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1);
+ for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) {
+ s16 maxabsxz = MYMAX(abs(x0), abs(z0));
+
+ s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1);
+
+ for (s16 y0 = -si2; y0 <= si2; y0++) {
+ // Make better floors in small caves
+ if (flat_cave_floor && y0 <= -rs / 2 && rs <= 7)
+ continue;
+
+ if (large_cave_is_flat) {
+ // Make large caves not so tall
+ if (rs > 7 && abs(y0) >= rs / 3)
+ continue;
+ }
+
+ v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0);
+ p += of;
+
+ if (!vm->m_area.contains(p))
+ continue;
+
+ u32 i = vm->m_area.index(p);
+ content_t c = vm->m_data[i].getContent();
+ if (!ndef->get(c).is_ground_content)
+ continue;
+
+ if (large_cave) {
+ int full_ymin = node_min.Y - MAP_BLOCKSIZE;
+ int full_ymax = node_max.Y + MAP_BLOCKSIZE;
+
+ if (flooded && full_ymin < water_level && full_ymax > water_level)
+ vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode;
+ else if (flooded && full_ymax < water_level)
+ vm->m_data[i] = (p.Y < startp.Y - 4) ? liquidnode : airnode;
+ else
+ vm->m_data[i] = airnode;
+ } else {
+ if (c == CONTENT_IGNORE)
+ continue;
+
+ vm->m_data[i] = airnode;
+ vm->m_flags[i] |= VMANIP_FLAG_CAVE;
+ }
+ }
+ }
+ }
+}
+
+
+inline bool CavesRandomWalk::isPosAboveSurface(v3s16 p)
+{
+ if (heightmap != NULL &&
+ p.Z >= node_min.Z && p.Z <= node_max.Z &&
+ p.X >= node_min.X && p.X <= node_max.X) {
+ u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X);
+ if (heightmap[index] < p.Y)
+ return true;
+ } else if (p.Y > water_level) {
+ return true;
+ }
+
+ return false;
+}
+
+
+////
+//// CavesV6
+////
+
+CavesV6::CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify,
+ int water_level, content_t water_source, content_t lava_source)
+{
+ assert(ndef);
+
+ this->ndef = ndef;
+ this->gennotify = gennotify;
+ this->water_level = water_level;
+
+ c_water_source = water_source;
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = ndef->getId("mapgen_water_source");
+ if (c_water_source == CONTENT_IGNORE)
+ c_water_source = CONTENT_AIR;
+
+ c_lava_source = lava_source;
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = ndef->getId("mapgen_lava_source");
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = CONTENT_AIR;
+}
+
+
+void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax,
+ PseudoRandom *ps, PseudoRandom *ps2,
+ bool is_large_cave, int max_stone_height, s16 *heightmap)
+{
+ assert(vm);
+ assert(ps);
+ assert(ps2);
+
+ this->vm = vm;
+ this->ps = ps;
+ this->ps2 = ps2;
+ this->node_min = nmin;
+ this->node_max = nmax;
+ this->heightmap = heightmap;
+ this->large_cave = is_large_cave;
+
+ this->ystride = nmax.X - nmin.X + 1;
+
+ // Set initial parameters from randomness
+ min_tunnel_diameter = 2;
+ max_tunnel_diameter = ps->range(2, 6);
+ int dswitchint = ps->range(1, 14);
+ if (large_cave) {
+ part_max_length_rs = ps->range(2, 4);
+ tunnel_routepoints = ps->range(5, ps->range(15, 30));
+ min_tunnel_diameter = 5;
+ max_tunnel_diameter = ps->range(7, ps->range(8, 24));
+ } else {
+ part_max_length_rs = ps->range(2, 9);
+ tunnel_routepoints = ps->range(10, ps->range(15, 30));
+ }
+ large_cave_is_flat = (ps->range(0, 1) == 0);
+
+ main_direction = v3f(0, 0, 0);
+
+ // Allowed route area size in nodes
+ ar = node_max - node_min + v3s16(1, 1, 1);
+ // Area starting point in nodes
+ of = node_min;
+
+ // Allow a bit more
+ //(this should be more than the maximum radius of the tunnel)
+ const s16 max_spread_amount = MAP_BLOCKSIZE;
+ const s16 insure = 10;
+ s16 more = MYMAX(max_spread_amount - max_tunnel_diameter / 2 - insure, 1);
+ ar += v3s16(1, 0, 1) * more * 2;
+ of -= v3s16(1, 0, 1) * more;
+
+ route_y_min = 0;
+ // Allow half a diameter + 7 over stone surface
+ route_y_max = -of.Y + max_stone_height + max_tunnel_diameter / 2 + 7;
+
+ // Limit maximum to area
+ route_y_max = rangelim(route_y_max, 0, ar.Y - 1);
+
+ if (large_cave) {
+ s16 minpos = 0;
+ if (node_min.Y < water_level && node_max.Y > water_level) {
+ minpos = water_level - max_tunnel_diameter / 3 - of.Y;
+ route_y_max = water_level + max_tunnel_diameter / 3 - of.Y;
+ }
+ route_y_min = ps->range(minpos, minpos + max_tunnel_diameter);
+ route_y_min = rangelim(route_y_min, 0, route_y_max);
+ }
+
+ s16 route_start_y_min = route_y_min;
+ s16 route_start_y_max = route_y_max;
+
+ route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1);
+ route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1);
+
+ // Randomize starting position
+ orp.Z = (float)(ps->next() % ar.Z) + 0.5f;
+ orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f;
+ orp.X = (float)(ps->next() % ar.X) + 0.5f;
+
+ // Add generation notify begin event
+ if (gennotify != NULL) {
+ v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
+ GenNotifyType notifytype = large_cave ?
+ GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN;
+ gennotify->addEvent(notifytype, abs_pos);
+ }
+
+ // Generate some tunnel starting from orp
+ for (u16 j = 0; j < tunnel_routepoints; j++)
+ makeTunnel(j % dswitchint == 0);
+
+ // Add generation notify end event
+ if (gennotify != NULL) {
+ v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z);
+ GenNotifyType notifytype = large_cave ?
+ GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END;
+ gennotify->addEvent(notifytype, abs_pos);
+ }
+}
+
+
+void CavesV6::makeTunnel(bool dirswitch)
+{
+ if (dirswitch && !large_cave) {
+ main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10;
+ main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30;
+ main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10;
+
+ main_direction *= (float)ps->range(0, 10) / 10;
+ }
+
+ // Randomize size
+ s16 min_d = min_tunnel_diameter;
+ s16 max_d = max_tunnel_diameter;
+ rs = ps->range(min_d, max_d);
+ s16 rs_part_max_length_rs = rs * part_max_length_rs;
+
+ v3s16 maxlen;
+ if (large_cave) {
+ maxlen = v3s16(
+ rs_part_max_length_rs,
+ rs_part_max_length_rs / 2,
+ rs_part_max_length_rs
+ );
+ } else {
+ maxlen = v3s16(
+ rs_part_max_length_rs,
+ ps->range(1, rs_part_max_length_rs),
+ rs_part_max_length_rs
+ );
+ }
+
+ v3f vec;
+ vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2;
+ vec.Y = (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2;
+ vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2;
+
+ // Jump downward sometimes
+ if (!large_cave && ps->range(0, 12) == 0) {
+ vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2;
+ vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y;
+ vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2;
+ }
+
+ // Do not make caves that are entirely above ground, to fix shadow bugs
+ // caused by overgenerated large caves.
+ // It is only necessary to check the startpoint and endpoint.
+ v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2;
+ v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1;
+
+ // If startpoint and endpoint are above ground, disable placement of nodes
+ // in carveRoute while still running all PseudoRandom calls to ensure caves
+ // are consistent with existing worlds.
+ bool tunnel_above_ground =
+ p1.Y > getSurfaceFromHeightmap(p1) &&
+ p2.Y > getSurfaceFromHeightmap(p2);
+
+ vec += main_direction;
+
+ v3f rp = orp + vec;
+ if (rp.X < 0)
+ rp.X = 0;
+ else if (rp.X >= ar.X)
+ rp.X = ar.X - 1;
+
+ if (rp.Y < route_y_min)
+ rp.Y = route_y_min;
+ else if (rp.Y >= route_y_max)
+ rp.Y = route_y_max - 1;
+
+ if (rp.Z < 0)
+ rp.Z = 0;
+ else if (rp.Z >= ar.Z)
+ rp.Z = ar.Z - 1;
+
+ vec = rp - orp;
+
+ float veclen = vec.getLength();
+ // As odd as it sounds, veclen is *exactly* 0.0 sometimes, causing a FPE
+ if (veclen < 0.05f)
+ veclen = 1.0f;
+
+ // Every second section is rough
+ bool randomize_xz = (ps2->range(1, 2) == 1);
+
+ // Carve routes
+ for (float f = 0.f; f < 1.0f; f += 1.0f / veclen)
+ carveRoute(vec, f, randomize_xz, tunnel_above_ground);
+
+ orp = rp;
+}
+
+
+void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz,
+ bool tunnel_above_ground)
+{
+ MapNode airnode(CONTENT_AIR);
+ MapNode waternode(c_water_source);
+ MapNode lavanode(c_lava_source);
+
+ v3s16 startp(orp.X, orp.Y, orp.Z);
+ startp += of;
+
+ v3f fp = orp + vec * f;
+ fp.X += 0.1f * ps->range(-10, 10);
+ fp.Z += 0.1f * ps->range(-10, 10);
+ v3s16 cp(fp.X, fp.Y, fp.Z);
+
+ s16 d0 = -rs / 2;
+ s16 d1 = d0 + rs;
+ if (randomize_xz) {
+ d0 += ps->range(-1, 1);
+ d1 += ps->range(-1, 1);
+ }
+
+ for (s16 z0 = d0; z0 <= d1; z0++) {
+ s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1);
+ for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) {
+ if (tunnel_above_ground)
+ continue;
+
+ s16 maxabsxz = MYMAX(abs(x0), abs(z0));
+ s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1);
+ for (s16 y0 = -si2; y0 <= si2; y0++) {
+ if (large_cave_is_flat) {
+ // Make large caves not so tall
+ if (rs > 7 && abs(y0) >= rs / 3)
+ continue;
+ }
+
+ v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0);
+ p += of;
+
+ if (!vm->m_area.contains(p))
+ continue;
+
+ u32 i = vm->m_area.index(p);
+ content_t c = vm->m_data[i].getContent();
+ if (!ndef->get(c).is_ground_content)
+ continue;
+
+ if (large_cave) {
+ int full_ymin = node_min.Y - MAP_BLOCKSIZE;
+ int full_ymax = node_max.Y + MAP_BLOCKSIZE;
+
+ if (full_ymin < water_level && full_ymax > water_level) {
+ vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode;
+ } else if (full_ymax < water_level) {
+ vm->m_data[i] = (p.Y < startp.Y - 2) ? lavanode : airnode;
+ } else {
+ vm->m_data[i] = airnode;
+ }
+ } else {
+ if (c == CONTENT_IGNORE || c == CONTENT_AIR)
+ continue;
+
+ vm->m_data[i] = airnode;
+ vm->m_flags[i] |= VMANIP_FLAG_CAVE;
+ }
+ }
+ }
+ }
+}
+
+
+inline s16 CavesV6::getSurfaceFromHeightmap(v3s16 p)
+{
+ if (heightmap != NULL &&
+ p.Z >= node_min.Z && p.Z <= node_max.Z &&
+ p.X >= node_min.X && p.X <= node_max.X) {
+ u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X);
+ return heightmap[index];
+ }
+
+ return water_level;
+
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1
+
+class GenerateNotifier;
+
+/*
+ CavesNoiseIntersection is a cave digging algorithm that carves smooth,
+ web-like, continuous tunnels at points where the density of the intersection
+ between two separate 3d noises is above a certain value. This value,
+ cave_width, can be modified to set the effective width of these tunnels.
+
+ This algorithm is relatively heavyweight, taking ~80ms to generate an
+ 80x80x80 chunk of map on a modern processor. Use sparingly!
+
+ TODO(hmmmm): Remove dependency on biomes
+ TODO(hmmmm): Find alternative to overgeneration as solution for sunlight issue
+*/
+class CavesNoiseIntersection
+{
+public:
+ CavesNoiseIntersection(INodeDefManager *nodedef, BiomeManager *biomemgr,
+ v3s16 chunksize, NoiseParams *np_cave1, NoiseParams *np_cave2,
+ s32 seed, float cave_width);
+ ~CavesNoiseIntersection();
+
+ void generateCaves(MMVManip *vm, v3s16 nmin, v3s16 nmax, u8 *biomemap);
+
+private:
+ INodeDefManager *m_ndef;
+ BiomeManager *m_bmgr;
+
+ // configurable parameters
+ v3s16 m_csize;
+ float m_cave_width;
+
+ // intermediate state variables
+ u16 m_ystride;
+ u16 m_zstride_1d;
+
+ Noise *noise_cave1;
+ Noise *noise_cave2;
+};
+
+/*
+ CavernsNoise is a cave digging algorithm
+*/
+class CavernsNoise
+{
+public:
+ CavernsNoise(INodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern,
+ s32 seed, float cavern_limit, float cavern_taper,
+ float cavern_threshold);
+ ~CavernsNoise();
+
+ bool generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax);
+
+private:
+ INodeDefManager *m_ndef;
+
+ // configurable parameters
+ v3s16 m_csize;
+ float m_cavern_limit;
+ float m_cavern_taper;
+ float m_cavern_threshold;
+
+ // intermediate state variables
+ u16 m_ystride;
+ u16 m_zstride_1d;
+
+ Noise *noise_cavern;
+
+ content_t c_water_source;
+ content_t c_lava_source;
+};
+
+/*
+ CavesRandomWalk is an implementation of a cave-digging algorithm that
+ operates on the principle of a "random walk" to approximate the stochiastic
+ activity of cavern development.
+
+ In summary, this algorithm works by carving a randomly sized tunnel in a
+ random direction a random amount of times, randomly varying in width.
+ All randomness here is uniformly distributed; alternative distributions have
+ not yet been implemented.
+
+ This algorithm is very fast, executing in less than 1ms on average for an
+ 80x80x80 chunk of map on a modern processor.
+*/
+class CavesRandomWalk
+{
+public:
+ MMVManip *vm;
+ INodeDefManager *ndef;
+ GenerateNotifier *gennotify;
+ s16 *heightmap;
+
+ // configurable parameters
+ s32 seed;
+ int water_level;
+ int lava_depth;
+ NoiseParams *np_caveliquids;
+
+ // intermediate state variables
+ u16 ystride;
+
+ s16 min_tunnel_diameter;
+ s16 max_tunnel_diameter;
+ u16 tunnel_routepoints;
+ int part_max_length_rs;
+
+ bool large_cave;
+ bool large_cave_is_flat;
+ bool flooded;
+
+ s16 max_stone_y;
+ v3s16 node_min;
+ v3s16 node_max;
+
+ v3f orp; // starting point, relative to caved space
+ v3s16 of; // absolute coordinates of caved space
+ v3s16 ar; // allowed route area
+ s16 rs; // tunnel radius size
+ v3f main_direction;
+
+ s16 route_y_min;
+ s16 route_y_max;
+
+ PseudoRandom *ps;
+
+ content_t c_water_source;
+ content_t c_lava_source;
+
+ // ndef is a mandatory parameter.
+ // If gennotify is NULL, generation events are not logged.
+ CavesRandomWalk(INodeDefManager *ndef, GenerateNotifier *gennotify = NULL,
+ s32 seed = 0, int water_level = 1,
+ content_t water_source = CONTENT_IGNORE,
+ content_t lava_source = CONTENT_IGNORE, int lava_depth = -256);
+
+ // vm and ps are mandatory parameters.
+ // If heightmap is NULL, the surface level at all points is assumed to
+ // be water_level.
+ void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps,
+ bool is_large_cave, int max_stone_height, s16 *heightmap);
+
+private:
+ void makeTunnel(bool dirswitch);
+ void carveRoute(v3f vec, float f, bool randomize_xz);
+
+ inline bool isPosAboveSurface(v3s16 p);
+};
+
+/*
+ CavesV6 is the original version of caves used with Mapgen V6.
+
+ Though it uses the same fundamental algorithm as CavesRandomWalk, it is made
+ separate to preserve the exact sequence of PseudoRandom calls - any change
+ to this ordering results in the output being radically different.
+ Because caves in Mapgen V6 are responsible for a large portion of the basic
+ terrain shape, modifying this will break our contract of reverse
+ compatibility for a 'stable' mapgen such as V6.
+
+ tl;dr,
+ *** DO NOT TOUCH THIS CLASS UNLESS YOU KNOW WHAT YOU ARE DOING ***
+*/
+class CavesV6
+{
+public:
+ MMVManip *vm;
+ INodeDefManager *ndef;
+ GenerateNotifier *gennotify;
+ PseudoRandom *ps;
+ PseudoRandom *ps2;
+
+ // configurable parameters
+ s16 *heightmap;
+ content_t c_water_source;
+ content_t c_lava_source;
+ int water_level;
+
+ // intermediate state variables
+ u16 ystride;
+
+ s16 min_tunnel_diameter;
+ s16 max_tunnel_diameter;
+ u16 tunnel_routepoints;
+ int part_max_length_rs;
+
+ bool large_cave;
+ bool large_cave_is_flat;
+
+ v3s16 node_min;
+ v3s16 node_max;
+
+ v3f orp; // starting point, relative to caved space
+ v3s16 of; // absolute coordinates of caved space
+ v3s16 ar; // allowed route area
+ s16 rs; // tunnel radius size
+ v3f main_direction;
+
+ s16 route_y_min;
+ s16 route_y_max;
+
+ // ndef is a mandatory parameter.
+ // If gennotify is NULL, generation events are not logged.
+ CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify = NULL,
+ int water_level = 1, content_t water_source = CONTENT_IGNORE,
+ content_t lava_source = CONTENT_IGNORE);
+
+ // vm, ps, and ps2 are mandatory parameters.
+ // If heightmap is NULL, the surface level at all points is assumed to
+ // be water_level.
+ void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps,
+ PseudoRandom *ps2, bool is_large_cave, int max_stone_height,
+ s16 *heightmap = NULL);
+
+private:
+ void makeTunnel(bool dirswitch);
+ void carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground);
+
+ inline s16 getSurfaceFromHeightmap(v3s16 p);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "dungeongen.h"
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "nodedef.h"
+#include "settings.h"
+
+//#define DGEN_USE_TORCHES
+
+NoiseParams nparams_dungeon_density(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
+NoiseParams nparams_dungeon_alt_wall(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+DungeonGen::DungeonGen(INodeDefManager *ndef,
+ GenerateNotifier *gennotify, DungeonParams *dparams)
+{
+ assert(ndef);
+
+ this->ndef = ndef;
+ this->gennotify = gennotify;
+
+#ifdef DGEN_USE_TORCHES
+ c_torch = ndef->getId("default:torch");
+#endif
+
+ if (dparams) {
+ memcpy(&dp, dparams, sizeof(dp));
+ } else {
+ // Default dungeon parameters
+ dp.seed = 0;
+
+ dp.c_water = ndef->getId("mapgen_water_source");
+ dp.c_river_water = ndef->getId("mapgen_river_water_source");
+ dp.c_wall = ndef->getId("mapgen_cobble");
+ dp.c_alt_wall = ndef->getId("mapgen_mossycobble");
+ dp.c_stair = ndef->getId("mapgen_stair_cobble");
+
+ if (dp.c_river_water == CONTENT_IGNORE)
+ dp.c_river_water = ndef->getId("mapgen_water_source");
+
+ dp.diagonal_dirs = false;
+ dp.only_in_ground = true;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.corridor_len_min = 1;
+ dp.corridor_len_max = 13;
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.rooms_min = 2;
+ dp.rooms_max = 16;
+ dp.y_min = -MAX_MAP_GENERATION_LIMIT;
+ dp.y_max = MAX_MAP_GENERATION_LIMIT;
+ dp.notifytype = GENNOTIFY_DUNGEON;
+
+ dp.np_density = nparams_dungeon_density;
+ dp.np_alt_wall = nparams_dungeon_alt_wall;
+ }
+}
+
+
+void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax)
+{
+ assert(vm);
+
+ //TimeTaker t("gen dungeons");
+ if (nmin.Y < dp.y_min || nmax.Y > dp.y_max)
+ return;
+
+ float nval_density = NoisePerlin3D(&dp.np_density, nmin.X, nmin.Y, nmin.Z, dp.seed);
+ if (nval_density < 1.0f)
+ return;
+
+ static const bool preserve_ignore = !g_settings->getBool("projecting_dungeons");
+
+ this->vm = vm;
+ this->blockseed = bseed;
+ random.seed(bseed + 2);
+
+ // Dungeon generator doesn't modify places which have this set
+ vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE);
+
+ if (dp.only_in_ground) {
+ // Set all air and water to be untouchable to make dungeons open to
+ // caves and open air. Optionally set ignore to be untouchable to
+ // prevent protruding dungeons.
+ for (s16 z = nmin.Z; z <= nmax.Z; z++) {
+ for (s16 y = nmin.Y; y <= nmax.Y; y++) {
+ u32 i = vm->m_area.index(nmin.X, y, z);
+ for (s16 x = nmin.X; x <= nmax.X; x++) {
+ content_t c = vm->m_data[i].getContent();
+ if (c == CONTENT_AIR || c == dp.c_water ||
+ (preserve_ignore && c == CONTENT_IGNORE) ||
+ c == dp.c_river_water)
+ vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
+ i++;
+ }
+ }
+ }
+ }
+
+ // Add them
+ for (u32 i = 0; i < floor(nval_density); i++)
+ makeDungeon(v3s16(1, 1, 1) * MAP_BLOCKSIZE);
+
+ // Optionally convert some structure to alternative structure
+ if (dp.c_alt_wall == CONTENT_IGNORE)
+ return;
+
+ for (s16 z = nmin.Z; z <= nmax.Z; z++)
+ for (s16 y = nmin.Y; y <= nmax.Y; y++) {
+ u32 i = vm->m_area.index(nmin.X, y, z);
+ for (s16 x = nmin.X; x <= nmax.X; x++) {
+ if (vm->m_data[i].getContent() == dp.c_wall) {
+ if (NoisePerlin3D(&dp.np_alt_wall, x, y, z, blockseed) > 0.0f)
+ vm->m_data[i].setContent(dp.c_alt_wall);
+ }
+ i++;
+ }
+ }
+
+ //printf("== gen dungeons: %dms\n", t.stop());
+}
+
+
+void DungeonGen::makeDungeon(v3s16 start_padding)
+{
+ const v3s16 &areasize = vm->m_area.getExtent();
+ v3s16 roomsize;
+ v3s16 roomplace;
+
+ /*
+ Find place for first room.
+ There is a 1 in 4 chance of the first room being 'large',
+ all other rooms are not 'large'.
+ */
+ bool fits = false;
+ for (u32 i = 0; i < 100 && !fits; i++) {
+ bool is_large_room = ((random.next() & 3) == 1);
+ if (is_large_room) {
+ roomsize.Z = random.range(
+ dp.room_size_large_min.Z, dp.room_size_large_max.Z);
+ roomsize.Y = random.range(
+ dp.room_size_large_min.Y, dp.room_size_large_max.Y);
+ roomsize.X = random.range(
+ dp.room_size_large_min.X, dp.room_size_large_max.X);
+ } else {
+ roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
+ roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
+ roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X);
+ }
+
+ // start_padding is used to disallow starting the generation of
+ // a dungeon in a neighboring generation chunk
+ roomplace = vm->m_area.MinEdge + start_padding;
+ roomplace.Z += random.range(0, areasize.Z - roomsize.Z - start_padding.Z);
+ roomplace.Y += random.range(0, areasize.Y - roomsize.Y - start_padding.Y);
+ roomplace.X += random.range(0, areasize.X - roomsize.X - start_padding.X);
+
+ /*
+ Check that we're not putting the room to an unknown place,
+ otherwise it might end up floating in the air
+ */
+ fits = true;
+ for (s16 z = 0; z < roomsize.Z; z++)
+ for (s16 y = 0; y < roomsize.Y; y++)
+ for (s16 x = 0; x < roomsize.X; x++) {
+ v3s16 p = roomplace + v3s16(x, y, z);
+ u32 vi = vm->m_area.index(p);
+ if ((vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) ||
+ vm->m_data[vi].getContent() == CONTENT_IGNORE) {
+ fits = false;
+ break;
+ }
+ }
+ }
+ // No place found
+ if (!fits)
+ return;
+
+ /*
+ Stores the center position of the last room made, so that
+ a new corridor can be started from the last room instead of
+ the new room, if chosen so.
+ */
+ v3s16 last_room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
+
+ u32 room_count = random.range(dp.rooms_min, dp.rooms_max);
+ for (u32 i = 0; i < room_count; i++) {
+ // Make a room to the determined place
+ makeRoom(roomsize, roomplace);
+
+ v3s16 room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
+ if (gennotify)
+ gennotify->addEvent(dp.notifytype, room_center);
+
+#ifdef DGEN_USE_TORCHES
+ // Place torch at room center (for testing)
+ vm->m_data[vm->m_area.index(room_center)] = MapNode(c_torch);
+#endif
+
+ // Quit if last room
+ if (i == room_count - 1)
+ break;
+
+ // Determine walker start position
+
+ bool start_in_last_room = (random.range(0, 2) != 0);
+
+ v3s16 walker_start_place;
+
+ if (start_in_last_room) {
+ walker_start_place = last_room_center;
+ } else {
+ walker_start_place = room_center;
+ // Store center of current room as the last one
+ last_room_center = room_center;
+ }
+
+ // Create walker and find a place for a door
+ v3s16 doorplace;
+ v3s16 doordir;
+
+ m_pos = walker_start_place;
+ if (!findPlaceForDoor(doorplace, doordir))
+ return;
+
+ if (random.range(0, 1) == 0)
+ // Make the door
+ makeDoor(doorplace, doordir);
+ else
+ // Don't actually make a door
+ doorplace -= doordir;
+
+ // Make a random corridor starting from the door
+ v3s16 corridor_end;
+ v3s16 corridor_end_dir;
+ makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir);
+
+ // Find a place for a random sized room
+ roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
+ roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
+ roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X);
+
+ m_pos = corridor_end;
+ m_dir = corridor_end_dir;
+ if (!findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace))
+ return;
+
+ if (random.range(0, 1) == 0)
+ // Make the door
+ makeDoor(doorplace, doordir);
+ else
+ // Don't actually make a door
+ roomplace -= doordir;
+
+ }
+}
+
+
+void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
+{
+ MapNode n_wall(dp.c_wall);
+ MapNode n_air(CONTENT_AIR);
+
+ // Make +-X walls
+ for (s16 z = 0; z < roomsize.Z; z++)
+ for (s16 y = 0; y < roomsize.Y; y++) {
+ {
+ v3s16 p = roomplace + v3s16(0, y, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ {
+ v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ }
+
+ // Make +-Z walls
+ for (s16 x = 0; x < roomsize.X; x++)
+ for (s16 y = 0; y < roomsize.Y; y++) {
+ {
+ v3s16 p = roomplace + v3s16(x, y, 0);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ {
+ v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ }
+
+ // Make +-Y walls (floor and ceiling)
+ for (s16 z = 0; z < roomsize.Z; z++)
+ for (s16 x = 0; x < roomsize.X; x++) {
+ {
+ v3s16 p = roomplace + v3s16(x, 0, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ {
+ v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
+ continue;
+ vm->m_data[vi] = n_wall;
+ }
+ }
+
+ // Fill with air
+ for (s16 z = 1; z < roomsize.Z - 1; z++)
+ for (s16 y = 1; y < roomsize.Y - 1; y++)
+ for (s16 x = 1; x < roomsize.X - 1; x++) {
+ v3s16 p = roomplace + v3s16(x, y, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE;
+ vm->m_data[vi] = n_air;
+ }
+}
+
+
+void DungeonGen::makeFill(v3s16 place, v3s16 size,
+ u8 avoid_flags, MapNode n, u8 or_flags)
+{
+ for (s16 z = 0; z < size.Z; z++)
+ for (s16 y = 0; y < size.Y; y++)
+ for (s16 x = 0; x < size.X; x++) {
+ v3s16 p = place + v3s16(x, y, z);
+ if (!vm->m_area.contains(p))
+ continue;
+ u32 vi = vm->m_area.index(p);
+ if (vm->m_flags[vi] & avoid_flags)
+ continue;
+ vm->m_flags[vi] |= or_flags;
+ vm->m_data[vi] = n;
+ }
+}
+
+
+void DungeonGen::makeHole(v3s16 place)
+{
+ makeFill(place, dp.holesize, 0, MapNode(CONTENT_AIR),
+ VMANIP_FLAG_DUNGEON_INSIDE);
+}
+
+
+void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir)
+{
+ makeHole(doorplace);
+
+#ifdef DGEN_USE_TORCHES
+ // Place torch (for testing)
+ vm->m_data[vm->m_area.index(doorplace)] = MapNode(c_torch);
+#endif
+}
+
+
+void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir,
+ v3s16 &result_place, v3s16 &result_dir)
+{
+ makeHole(doorplace);
+ v3s16 p0 = doorplace;
+ v3s16 dir = doordir;
+ u32 length = random.range(dp.corridor_len_min, dp.corridor_len_max);
+ u32 partlength = random.range(dp.corridor_len_min, dp.corridor_len_max);
+ u32 partcount = 0;
+ s16 make_stairs = 0;
+
+ if (random.next() % 2 == 0 && partlength >= 3)
+ make_stairs = random.next() % 2 ? 1 : -1;
+
+ for (u32 i = 0; i < length; i++) {
+ v3s16 p = p0 + dir;
+ if (partcount != 0)
+ p.Y += make_stairs;
+
+ // Check segment of minimum size corridor is in voxelmanip
+ if (vm->m_area.contains(p) && vm->m_area.contains(p + v3s16(0, 1, 0))) {
+ if (make_stairs) {
+ makeFill(p + v3s16(-1, -1, -1),
+ dp.holesize + v3s16(2, 3, 2),
+ VMANIP_FLAG_DUNGEON_UNTOUCHABLE,
+ MapNode(dp.c_wall),
+ 0);
+ makeHole(p);
+ makeHole(p - dir);
+
+ // TODO: fix stairs code so it works 100%
+ // (quite difficult)
+
+ // exclude stairs from the bottom step
+ // exclude stairs from diagonal steps
+ if (((dir.X ^ dir.Z) & 1) &&
+ (((make_stairs == 1) && i != 0) ||
+ ((make_stairs == -1) && i != length - 1))) {
+ // rotate face 180 deg if
+ // making stairs backwards
+ int facedir = dir_to_facedir(dir * make_stairs);
+ v3s16 ps = p;
+ u16 stair_width = (dir.Z != 0) ? dp.holesize.X : dp.holesize.Z;
+ // Stair width direction vector
+ v3s16 swv = (dir.Z != 0) ? v3s16(1, 0, 0) : v3s16(0, 0, 1);
+
+ for (u16 st = 0; st < stair_width; st++) {
+ u32 vi = vm->m_area.index(ps.X - dir.X, ps.Y - 1, ps.Z - dir.Z);
+ if (vm->m_area.contains(ps + v3s16(-dir.X, -1, -dir.Z)) &&
+ vm->m_data[vi].getContent() == dp.c_wall)
+ vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
+
+ vi = vm->m_area.index(ps.X, ps.Y, ps.Z);
+ if (vm->m_area.contains(ps) &&
+ vm->m_data[vi].getContent() == dp.c_wall)
+ vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
+
+ ps += swv;
+ }
+ }
+ } else {
+ makeFill(p + v3s16(-1, -1, -1),
+ dp.holesize + v3s16(2, 2, 2),
+ VMANIP_FLAG_DUNGEON_UNTOUCHABLE,
+ MapNode(dp.c_wall),
+ 0);
+ makeHole(p);
+ }
+
+ p0 = p;
+ } else {
+ // Can't go here, turn away
+ dir = turn_xz(dir, random.range(0, 1));
+ make_stairs = -make_stairs;
+ partcount = 0;
+ partlength = random.range(1, length);
+ continue;
+ }
+
+ partcount++;
+ if (partcount >= partlength) {
+ partcount = 0;
+
+ dir = random_turn(random, dir);
+
+ partlength = random.range(1, length);
+
+ make_stairs = 0;
+ if (random.next() % 2 == 0 && partlength >= 3)
+ make_stairs = random.next() % 2 ? 1 : -1;
+ }
+ }
+ result_place = p0;
+ result_dir = dir;
+}
+
+
+bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir)
+{
+ for (u32 i = 0; i < 100; i++) {
+ v3s16 p = m_pos + m_dir;
+ v3s16 p1 = p + v3s16(0, 1, 0);
+ if (!vm->m_area.contains(p) || !vm->m_area.contains(p1) || i % 4 == 0) {
+ randomizeDir();
+ continue;
+ }
+ if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_wall &&
+ vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_wall) {
+ // Found wall, this is a good place!
+ result_place = p;
+ result_dir = m_dir;
+ // Randomize next direction
+ randomizeDir();
+ return true;
+ }
+ /*
+ Determine where to move next
+ */
+ // Jump one up if the actual space is there
+ if (vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 0, 0)).getContent() == dp.c_wall &&
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 1, 0)).getContent() == CONTENT_AIR &&
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 2, 0)).getContent() == CONTENT_AIR)
+ p += v3s16(0,1,0);
+ // Jump one down if the actual space is there
+ if (vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 1, 0)).getContent() == dp.c_wall &&
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 0, 0)).getContent() == CONTENT_AIR &&
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, -1, 0)).getContent() == CONTENT_AIR)
+ p += v3s16(0, -1, 0);
+ // Check if walking is now possible
+ if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR ||
+ vm->getNodeNoExNoEmerge(p +
+ v3s16(0, 1, 0)).getContent() != CONTENT_AIR) {
+ // Cannot continue walking here
+ randomizeDir();
+ continue;
+ }
+ // Move there
+ m_pos = p;
+ }
+ return false;
+}
+
+
+bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
+ v3s16 &result_doordir, v3s16 &result_roomplace)
+{
+ for (s16 trycount = 0; trycount < 30; trycount++) {
+ v3s16 doorplace;
+ v3s16 doordir;
+ bool r = findPlaceForDoor(doorplace, doordir);
+ if (!r)
+ continue;
+ v3s16 roomplace;
+ // X east, Z north, Y up
+ if (doordir == v3s16(1, 0, 0)) // X+
+ roomplace = doorplace +
+ v3s16(0, -1, random.range(-roomsize.Z + 2, -2));
+ if (doordir == v3s16(-1, 0, 0)) // X-
+ roomplace = doorplace +
+ v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2));
+ if (doordir == v3s16(0, 0, 1)) // Z+
+ roomplace = doorplace +
+ v3s16(random.range(-roomsize.X + 2, -2), -1, 0);
+ if (doordir == v3s16(0, 0, -1)) // Z-
+ roomplace = doorplace +
+ v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1);
+
+ // Check fit
+ bool fits = true;
+ for (s16 z = 1; z < roomsize.Z - 1; z++)
+ for (s16 y = 1; y < roomsize.Y - 1; y++)
+ for (s16 x = 1; x < roomsize.X - 1; x++) {
+ v3s16 p = roomplace + v3s16(x, y, z);
+ if (!vm->m_area.contains(p)) {
+ fits = false;
+ break;
+ }
+ if (vm->m_flags[vm->m_area.index(p)] & VMANIP_FLAG_DUNGEON_INSIDE) {
+ fits = false;
+ break;
+ }
+ }
+ if (!fits) {
+ // Find new place
+ continue;
+ }
+ result_doorplace = doorplace;
+ result_doordir = doordir;
+ result_roomplace = roomplace;
+ return true;
+ }
+ return false;
+}
+
+
+v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs)
+{
+ // Make diagonal directions somewhat rare
+ if (diagonal_dirs && (random.next() % 4 == 0)) {
+ v3s16 dir;
+ int trycount = 0;
+
+ do {
+ trycount++;
+
+ dir.Z = random.next() % 3 - 1;
+ dir.Y = 0;
+ dir.X = random.next() % 3 - 1;
+ } while ((dir.X == 0 || dir.Z == 0) && trycount < 10);
+
+ return dir;
+ }
+
+ if (random.next() % 2 == 0)
+ return random.next() % 2 ? v3s16(-1, 0, 0) : v3s16(1, 0, 0);
+
+ return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1);
+}
+
+
+v3s16 turn_xz(v3s16 olddir, int t)
+{
+ v3s16 dir;
+ if (t == 0) {
+ // Turn right
+ dir.X = olddir.Z;
+ dir.Z = -olddir.X;
+ dir.Y = olddir.Y;
+ } else {
+ // Turn left
+ dir.X = -olddir.Z;
+ dir.Z = olddir.X;
+ dir.Y = olddir.Y;
+ }
+ return dir;
+}
+
+
+v3s16 random_turn(PseudoRandom &random, v3s16 olddir)
+{
+ int turn = random.range(0, 2);
+ v3s16 dir;
+ if (turn == 0)
+ // Go straight
+ dir = olddir;
+ else if (turn == 1)
+ // Turn right
+ dir = turn_xz(olddir, 0);
+ else
+ // Turn left
+ dir = turn_xz(olddir, 1);
+ return dir;
+}
+
+
+int dir_to_facedir(v3s16 d)
+{
+ if (abs(d.X) > abs(d.Z))
+ return d.X < 0 ? 3 : 1;
+
+ return d.Z < 0 ? 2 : 0;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 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 Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "voxel.h"
+#include "noise.h"
+#include "mapgen.h"
+
+#define VMANIP_FLAG_DUNGEON_INSIDE VOXELFLAG_CHECKED1
+#define VMANIP_FLAG_DUNGEON_PRESERVE VOXELFLAG_CHECKED2
+#define VMANIP_FLAG_DUNGEON_UNTOUCHABLE (\
+ VMANIP_FLAG_DUNGEON_INSIDE|VMANIP_FLAG_DUNGEON_PRESERVE)
+
+class MMVManip;
+class INodeDefManager;
+
+v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs);
+v3s16 turn_xz(v3s16 olddir, int t);
+v3s16 random_turn(PseudoRandom &random, v3s16 olddir);
+int dir_to_facedir(v3s16 d);
+
+
+struct DungeonParams {
+ s32 seed;
+
+ content_t c_water;
+ content_t c_river_water;
+ content_t c_wall;
+ content_t c_alt_wall;
+ content_t c_stair;
+
+ bool diagonal_dirs;
+ bool only_in_ground;
+ v3s16 holesize;
+ u16 corridor_len_min;
+ u16 corridor_len_max;
+ v3s16 room_size_min;
+ v3s16 room_size_max;
+ v3s16 room_size_large_min;
+ v3s16 room_size_large_max;
+ u16 rooms_min;
+ u16 rooms_max;
+ s16 y_min;
+ s16 y_max;
+ GenNotifyType notifytype;
+
+ NoiseParams np_density;
+ NoiseParams np_alt_wall;
+};
+
+class DungeonGen {
+public:
+ MMVManip *vm;
+ INodeDefManager *ndef;
+ GenerateNotifier *gennotify;
+
+ u32 blockseed;
+ PseudoRandom random;
+ v3s16 csize;
+
+ content_t c_torch;
+ DungeonParams dp;
+
+ // RoomWalker
+ v3s16 m_pos;
+ v3s16 m_dir;
+
+ DungeonGen(INodeDefManager *ndef,
+ GenerateNotifier *gennotify, DungeonParams *dparams);
+
+ void generate(MMVManip *vm, u32 bseed,
+ v3s16 full_node_min, v3s16 full_node_max);
+
+ void makeDungeon(v3s16 start_padding);
+ void makeRoom(v3s16 roomsize, v3s16 roomplace);
+ void makeCorridor(v3s16 doorplace, v3s16 doordir,
+ v3s16 &result_place, v3s16 &result_dir);
+ void makeDoor(v3s16 doorplace, v3s16 doordir);
+ void makeFill(v3s16 place, v3s16 size, u8 avoid_flags, MapNode n, u8 or_flags);
+ void makeHole(v3s16 place);
+
+ bool findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir);
+ bool findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
+ v3s16 &result_doordir, v3s16 &result_roomplace);
+
+ inline void randomizeDir()
+ {
+ m_dir = rand_ortho_dir(random, dp.diagonal_dirs);
+ }
+};
+
+extern NoiseParams nparams_dungeon_density;
+extern NoiseParams nparams_dungeon_alt_wall;
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "gamedef.h"
+#include "mg_biome.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "emerge.h"
+#include "voxelalgorithms.h"
+#include "porting.h"
+#include "profiler.h"
+#include "settings.h"
+#include "treegen.h"
+#include "serialization.h"
+#include "util/serialize.h"
+#include "util/numeric.h"
+#include "filesys.h"
+#include "log.h"
+#include "mapgen_carpathian.h"
+#include "mapgen_flat.h"
+#include "mapgen_fractal.h"
+#include "mapgen_v5.h"
+#include "mapgen_v6.h"
+#include "mapgen_v7.h"
+#include "mapgen_valleys.h"
+#include "mapgen_singlenode.h"
+#include "cavegen.h"
+#include "dungeongen.h"
+
+FlagDesc flagdesc_mapgen[] = {
+ {"caves", MG_CAVES},
+ {"dungeons", MG_DUNGEONS},
+ {"light", MG_LIGHT},
+ {"decorations", MG_DECORATIONS},
+ {NULL, 0}
+};
+
+FlagDesc flagdesc_gennotify[] = {
+ {"dungeon", 1 << GENNOTIFY_DUNGEON},
+ {"temple", 1 << GENNOTIFY_TEMPLE},
+ {"cave_begin", 1 << GENNOTIFY_CAVE_BEGIN},
+ {"cave_end", 1 << GENNOTIFY_CAVE_END},
+ {"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN},
+ {"large_cave_end", 1 << GENNOTIFY_LARGECAVE_END},
+ {"decoration", 1 << GENNOTIFY_DECORATION},
+ {NULL, 0}
+};
+
+struct MapgenDesc {
+ const char *name;
+ bool is_user_visible;
+};
+
+////
+//// Built-in mapgens
+////
+
+static MapgenDesc g_reg_mapgens[] = {
+ {"v5", true},
+ {"v6", true},
+ {"v7", true},
+ {"flat", true},
+ {"fractal", true},
+ {"valleys", true},
+ {"singlenode", true},
+ {"carpathian", true},
+};
+
+STATIC_ASSERT(
+ ARRLEN(g_reg_mapgens) == MAPGEN_INVALID,
+ registered_mapgens_is_wrong_size);
+
+////
+//// Mapgen
+////
+
+Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge) :
+ gennotify(emerge->gen_notify_on, &emerge->gen_notify_on_deco_ids)
+{
+ id = mapgenid;
+ water_level = params->water_level;
+ mapgen_limit = params->mapgen_limit;
+ flags = params->flags;
+ csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE);
+
+ /*
+ We are losing half our entropy by doing this, but it is necessary to
+ preserve reverse compatibility. If the top half of our current 64 bit
+ seeds ever starts getting used, existing worlds will break due to a
+ different hash outcome and no way to differentiate between versions.
+
+ A solution could be to add a new bit to designate that the top half of
+ the seed value should be used, essentially a 1-bit version code, but
+ this would require increasing the total size of a seed to 9 bytes (yuck)
+
+ It's probably okay if this never gets fixed. 4.2 billion possibilities
+ ought to be enough for anyone.
+ */
+ seed = (s32)params->seed;
+
+ ndef = emerge->ndef;
+}
+
+
+MapgenType Mapgen::getMapgenType(const std::string &mgname)
+{
+ for (size_t i = 0; i != ARRLEN(g_reg_mapgens); i++) {
+ if (mgname == g_reg_mapgens[i].name)
+ return (MapgenType)i;
+ }
+
+ return MAPGEN_INVALID;
+}
+
+
+const char *Mapgen::getMapgenName(MapgenType mgtype)
+{
+ size_t index = (size_t)mgtype;
+ if (index == MAPGEN_INVALID || index >= ARRLEN(g_reg_mapgens))
+ return "invalid";
+
+ return g_reg_mapgens[index].name;
+}
+
+
+Mapgen *Mapgen::createMapgen(MapgenType mgtype, int mgid,
+ MapgenParams *params, EmergeManager *emerge)
+{
+ switch (mgtype) {
+ case MAPGEN_CARPATHIAN:
+ return new MapgenCarpathian(mgid, (MapgenCarpathianParams *)params, emerge);
+ case MAPGEN_FLAT:
+ return new MapgenFlat(mgid, (MapgenFlatParams *)params, emerge);
+ case MAPGEN_FRACTAL:
+ return new MapgenFractal(mgid, (MapgenFractalParams *)params, emerge);
+ case MAPGEN_SINGLENODE:
+ return new MapgenSinglenode(mgid, (MapgenSinglenodeParams *)params, emerge);
+ case MAPGEN_V5:
+ return new MapgenV5(mgid, (MapgenV5Params *)params, emerge);
+ case MAPGEN_V6:
+ return new MapgenV6(mgid, (MapgenV6Params *)params, emerge);
+ case MAPGEN_V7:
+ return new MapgenV7(mgid, (MapgenV7Params *)params, emerge);
+ case MAPGEN_VALLEYS:
+ return new MapgenValleys(mgid, (MapgenValleysParams *)params, emerge);
+ default:
+ return NULL;
+ }
+}
+
+
+MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype)
+{
+ switch (mgtype) {
+ case MAPGEN_CARPATHIAN:
+ return new MapgenCarpathianParams;
+ case MAPGEN_FLAT:
+ return new MapgenFlatParams;
+ case MAPGEN_FRACTAL:
+ return new MapgenFractalParams;
+ case MAPGEN_SINGLENODE:
+ return new MapgenSinglenodeParams;
+ case MAPGEN_V5:
+ return new MapgenV5Params;
+ case MAPGEN_V6:
+ return new MapgenV6Params;
+ case MAPGEN_V7:
+ return new MapgenV7Params;
+ case MAPGEN_VALLEYS:
+ return new MapgenValleysParams;
+ default:
+ return NULL;
+ }
+}
+
+
+void Mapgen::getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden)
+{
+ for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) {
+ if (include_hidden || g_reg_mapgens[i].is_user_visible)
+ mgnames->push_back(g_reg_mapgens[i].name);
+ }
+}
+
+
+u32 Mapgen::getBlockSeed(v3s16 p, s32 seed)
+{
+ return (u32)seed +
+ p.Z * 38134234 +
+ p.Y * 42123 +
+ p.X * 23;
+}
+
+
+u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed)
+{
+ u32 n = 1619 * p.X + 31337 * p.Y + 52591 * p.Z + 1013 * seed;
+ n = (n >> 13) ^ n;
+ return (n * (n * n * 60493 + 19990303) + 1376312589);
+}
+
+
+// Returns Y one under area minimum if not found
+s16 Mapgen::findGroundLevelFull(v2s16 p2d)
+{
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 y_nodes_max = vm->m_area.MaxEdge.Y;
+ s16 y_nodes_min = vm->m_area.MinEdge.Y;
+ u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
+ s16 y;
+
+ for (y = y_nodes_max; y >= y_nodes_min; y--) {
+ MapNode &n = vm->m_data[i];
+ if (ndef->get(n).walkable)
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+ return (y >= y_nodes_min) ? y : y_nodes_min - 1;
+}
+
+
+// Returns -MAX_MAP_GENERATION_LIMIT if not found
+s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax)
+{
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
+ s16 y;
+
+ for (y = ymax; y >= ymin; y--) {
+ MapNode &n = vm->m_data[i];
+ if (ndef->get(n).walkable)
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+ return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT;
+}
+
+
+// Returns -MAX_MAP_GENERATION_LIMIT if not found or if ground is found first
+s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax)
+{
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y);
+ s16 y;
+
+ for (y = ymax; y >= ymin; y--) {
+ MapNode &n = vm->m_data[i];
+ if (ndef->get(n).walkable)
+ return -MAX_MAP_GENERATION_LIMIT;
+
+ if (ndef->get(n).isLiquid())
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+ return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT;
+}
+
+
+void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax)
+{
+ if (!heightmap)
+ return;
+
+ //TimeTaker t("Mapgen::updateHeightmap", NULL, PRECISION_MICRO);
+ int index = 0;
+ for (s16 z = nmin.Z; z <= nmax.Z; z++) {
+ for (s16 x = nmin.X; x <= nmax.X; x++, index++) {
+ s16 y = findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y);
+
+ heightmap[index] = y;
+ }
+ }
+}
+
+
+void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
+ s16 *floors, s16 *ceilings, u16 *num_floors, u16 *num_ceilings)
+{
+ u16 floor_i = 0;
+ u16 ceiling_i = 0;
+ const v3s16 &em = vm->m_area.getExtent();
+
+ bool is_walkable = false;
+ u32 vi = vm->m_area.index(p2d.X, ymax, p2d.Y);
+ MapNode mn_max = vm->m_data[vi];
+ bool walkable_above = ndef->get(mn_max).walkable;
+ vm->m_area.add_y(em, vi, -1);
+
+ for (s16 y = ymax - 1; y >= ymin; y--) {
+ MapNode mn = vm->m_data[vi];
+ is_walkable = ndef->get(mn).walkable;
+
+ if (is_walkable && !walkable_above) {
+ floors[floor_i] = y;
+ floor_i++;
+ } else if (!is_walkable && walkable_above) {
+ ceilings[ceiling_i] = y + 1;
+ ceiling_i++;
+ }
+
+ vm->m_area.add_y(em, vi, -1);
+ walkable_above = is_walkable;
+ }
+
+ *num_floors = floor_i;
+ *num_ceilings = ceiling_i;
+}
+
+
+inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em)
+{
+ u32 vi_neg_x = vi;
+ vm->m_area.add_x(em, vi_neg_x, -1);
+ if (vm->m_data[vi_neg_x].getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &c_nx = ndef->get(vm->m_data[vi_neg_x]);
+ if (c_nx.floodable && !c_nx.isLiquid())
+ return true;
+ }
+ u32 vi_pos_x = vi;
+ vm->m_area.add_x(em, vi_pos_x, +1);
+ if (vm->m_data[vi_pos_x].getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &c_px = ndef->get(vm->m_data[vi_pos_x]);
+ if (c_px.floodable && !c_px.isLiquid())
+ return true;
+ }
+ u32 vi_neg_z = vi;
+ vm->m_area.add_z(em, vi_neg_z, -1);
+ if (vm->m_data[vi_neg_z].getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &c_nz = ndef->get(vm->m_data[vi_neg_z]);
+ if (c_nz.floodable && !c_nz.isLiquid())
+ return true;
+ }
+ u32 vi_pos_z = vi;
+ vm->m_area.add_z(em, vi_pos_z, +1);
+ if (vm->m_data[vi_pos_z].getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &c_pz = ndef->get(vm->m_data[vi_pos_z]);
+ if (c_pz.floodable && !c_pz.isLiquid())
+ return true;
+ }
+ return false;
+}
+
+void Mapgen::updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax)
+{
+ bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed;
+ const v3s16 &em = vm->m_area.getExtent();
+
+ for (s16 z = nmin.Z + 1; z <= nmax.Z - 1; z++)
+ for (s16 x = nmin.X + 1; x <= nmax.X - 1; x++) {
+ wasignored = true;
+ wasliquid = false;
+ waschecked = false;
+ waspushed = false;
+
+ u32 vi = vm->m_area.index(x, nmax.Y, z);
+ for (s16 y = nmax.Y; y >= nmin.Y; y--) {
+ isignored = vm->m_data[vi].getContent() == CONTENT_IGNORE;
+ isliquid = ndef->get(vm->m_data[vi]).isLiquid();
+
+ if (isignored || wasignored || isliquid == wasliquid) {
+ // Neither topmost node of liquid column nor topmost node below column
+ waschecked = false;
+ waspushed = false;
+ } else if (isliquid) {
+ // This is the topmost node in the column
+ bool ispushed = false;
+ if (isLiquidHorizontallyFlowable(vi, em)) {
+ trans_liquid->push_back(v3s16(x, y, z));
+ ispushed = true;
+ }
+ // Remember waschecked and waspushed to avoid repeated
+ // checks/pushes in case the column consists of only this node
+ waschecked = true;
+ waspushed = ispushed;
+ } else {
+ // This is the topmost node below a liquid column
+ u32 vi_above = vi;
+ vm->m_area.add_y(em, vi_above, 1);
+ if (!waspushed && (ndef->get(vm->m_data[vi]).floodable ||
+ (!waschecked && isLiquidHorizontallyFlowable(vi_above, em)))) {
+ // Push back the lowest node in the column which is one
+ // node above this one
+ trans_liquid->push_back(v3s16(x, y + 1, z));
+ }
+ }
+
+ wasliquid = isliquid;
+ wasignored = isignored;
+ vm->m_area.add_y(em, vi, -1);
+ }
+ }
+}
+
+
+void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax)
+{
+ ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
+ VoxelArea a(nmin, nmax);
+
+ for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
+ for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
+ u32 i = vm->m_area.index(a.MinEdge.X, y, z);
+ for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++)
+ vm->m_data[i].param1 = light;
+ }
+ }
+}
+
+
+void Mapgen::lightSpread(VoxelArea &a, v3s16 p, u8 light)
+{
+ if (light <= 1 || !a.contains(p))
+ return;
+
+ u32 vi = vm->m_area.index(p);
+ MapNode &n = vm->m_data[vi];
+
+ // Decay light in each of the banks separately
+ u8 light_day = light & 0x0F;
+ if (light_day > 0)
+ light_day -= 0x01;
+
+ u8 light_night = light & 0xF0;
+ if (light_night > 0)
+ light_night -= 0x10;
+
+ // Bail out only if we have no more light from either bank to propogate, or
+ // we hit a solid block that light cannot pass through.
+ if ((light_day <= (n.param1 & 0x0F) &&
+ light_night <= (n.param1 & 0xF0)) ||
+ !ndef->get(n).light_propagates)
+ return;
+
+ // Since this recursive function only terminates when there is no light from
+ // either bank left, we need to take the max of both banks into account for
+ // the case where spreading has stopped for one light bank but not the other.
+ light = MYMAX(light_day, n.param1 & 0x0F) |
+ MYMAX(light_night, n.param1 & 0xF0);
+
+ n.param1 = light;
+
+ lightSpread(a, p + v3s16(0, 0, 1), light);
+ lightSpread(a, p + v3s16(0, 1, 0), light);
+ lightSpread(a, p + v3s16(1, 0, 0), light);
+ lightSpread(a, p - v3s16(0, 0, 1), light);
+ lightSpread(a, p - v3s16(0, 1, 0), light);
+ lightSpread(a, p - v3s16(1, 0, 0), light);
+}
+
+
+void Mapgen::calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
+ bool propagate_shadow)
+{
+ ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
+ //TimeTaker t("updateLighting");
+
+ propagateSunlight(nmin, nmax, propagate_shadow);
+ spreadLight(full_nmin, full_nmax);
+
+ //printf("updateLighting: %dms\n", t.stop());
+}
+
+
+void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow)
+{
+ //TimeTaker t("propagateSunlight");
+ VoxelArea a(nmin, nmax);
+ bool block_is_underground = (water_level >= nmax.Y);
+ const v3s16 &em = vm->m_area.getExtent();
+
+ // NOTE: Direct access to the low 4 bits of param1 is okay here because,
+ // by definition, sunlight will never be in the night lightbank.
+
+ for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
+ for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++) {
+ // see if we can get a light value from the overtop
+ u32 i = vm->m_area.index(x, a.MaxEdge.Y + 1, z);
+ if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
+ if (block_is_underground)
+ continue;
+ } else if ((vm->m_data[i].param1 & 0x0F) != LIGHT_SUN &&
+ propagate_shadow) {
+ continue;
+ }
+ vm->m_area.add_y(em, i, -1);
+
+ for (int y = a.MaxEdge.Y; y >= a.MinEdge.Y; y--) {
+ MapNode &n = vm->m_data[i];
+ if (!ndef->get(n).sunlight_propagates)
+ break;
+ n.param1 = LIGHT_SUN;
+ vm->m_area.add_y(em, i, -1);
+ }
+ }
+ }
+ //printf("propagateSunlight: %dms\n", t.stop());
+}
+
+
+void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax)
+{
+ //TimeTaker t("spreadLight");
+ VoxelArea a(nmin, nmax);
+
+ for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
+ for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) {
+ u32 i = vm->m_area.index(a.MinEdge.X, y, z);
+ for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++) {
+ MapNode &n = vm->m_data[i];
+ if (n.getContent() == CONTENT_IGNORE)
+ continue;
+
+ const ContentFeatures &cf = ndef->get(n);
+ if (!cf.light_propagates)
+ continue;
+
+ // TODO(hmmmmm): Abstract away direct param1 accesses with a
+ // wrapper, but something lighter than MapNode::get/setLight
+
+ u8 light_produced = cf.light_source;
+ if (light_produced)
+ n.param1 = light_produced | (light_produced << 4);
+
+ u8 light = n.param1;
+ if (light) {
+ lightSpread(a, v3s16(x, y, z + 1), light);
+ lightSpread(a, v3s16(x, y + 1, z ), light);
+ lightSpread(a, v3s16(x + 1, y, z ), light);
+ lightSpread(a, v3s16(x, y, z - 1), light);
+ lightSpread(a, v3s16(x, y - 1, z ), light);
+ lightSpread(a, v3s16(x - 1, y, z ), light);
+ }
+ }
+ }
+ }
+
+ //printf("spreadLight: %dms\n", t.stop());
+}
+
+
+////
+//// MapgenBasic
+////
+
+MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge)
+ : Mapgen(mapgenid, params, emerge)
+{
+ this->m_emerge = emerge;
+ this->m_bmgr = emerge->biomemgr;
+
+ //// Here, 'stride' refers to the number of elements needed to skip to index
+ //// an adjacent element for that coordinate in noise/height/biome maps
+ //// (*not* vmanip content map!)
+
+ // Note there is no X stride explicitly defined. Items adjacent in the X
+ // coordinate are assumed to be adjacent in memory as well (i.e. stride of 1).
+
+ // Number of elements to skip to get to the next Y coordinate
+ this->ystride = csize.X;
+
+ // Number of elements to skip to get to the next Z coordinate
+ this->zstride = csize.X * csize.Y;
+
+ // Z-stride value for maps oversized for 1-down overgeneration
+ this->zstride_1d = csize.X * (csize.Y + 1);
+
+ // Z-stride value for maps oversized for 1-up 1-down overgeneration
+ this->zstride_1u1d = csize.X * (csize.Y + 2);
+
+ //// Allocate heightmap
+ this->heightmap = new s16[csize.X * csize.Z];
+
+ //// Initialize biome generator
+ // TODO(hmmmm): should we have a way to disable biomemanager biomes?
+ biomegen = m_bmgr->createBiomeGen(BIOMEGEN_ORIGINAL, params->bparams, csize);
+ biomemap = biomegen->biomemap;
+
+ //// Look up some commonly used content
+ c_stone = ndef->getId("mapgen_stone");
+ c_desert_stone = ndef->getId("mapgen_desert_stone");
+ c_sandstone = ndef->getId("mapgen_sandstone");
+ c_water_source = ndef->getId("mapgen_water_source");
+ c_river_water_source = ndef->getId("mapgen_river_water_source");
+ c_lava_source = ndef->getId("mapgen_lava_source");
+
+ // Fall back to more basic content if not defined
+ // river_water_source cannot fallback to water_source because river water
+ // needs to be non-renewable and have a short flow range.
+ if (c_desert_stone == CONTENT_IGNORE)
+ c_desert_stone = c_stone;
+ if (c_sandstone == CONTENT_IGNORE)
+ c_sandstone = c_stone;
+
+ //// Content used for dungeon generation
+ c_cobble = ndef->getId("mapgen_cobble");
+ c_mossycobble = ndef->getId("mapgen_mossycobble");
+ c_stair_cobble = ndef->getId("mapgen_stair_cobble");
+ c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone");
+ c_sandstonebrick = ndef->getId("mapgen_sandstonebrick");
+ c_stair_sandstone_block = ndef->getId("mapgen_stair_sandstone_block");
+
+ // Fall back to more basic content if not defined
+ if (c_mossycobble == CONTENT_IGNORE)
+ c_mossycobble = c_cobble;
+ if (c_stair_cobble == CONTENT_IGNORE)
+ c_stair_cobble = c_cobble;
+ if (c_stair_desert_stone == CONTENT_IGNORE)
+ c_stair_desert_stone = c_desert_stone;
+ if (c_sandstonebrick == CONTENT_IGNORE)
+ c_sandstonebrick = c_sandstone;
+ if (c_stair_sandstone_block == CONTENT_IGNORE)
+ c_stair_sandstone_block = c_sandstonebrick;
+}
+
+
+MapgenBasic::~MapgenBasic()
+{
+ delete biomegen;
+ delete []heightmap;
+}
+
+
+void MapgenBasic::generateBiomes(MgStoneType *mgstone_type,
+ content_t *biome_stone)
+{
+ // can't generate biomes without a biome generator!
+ assert(biomegen);
+ assert(biomemap);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 index = 0;
+ MgStoneType stone_type = MGSTONE_OTHER;
+ content_t c_biome_stone = c_stone;
+
+ noise_filler_depth->perlinMap2D(node_min.X, node_min.Z);
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ Biome *biome = NULL;
+ u16 depth_top = 0;
+ u16 base_filler = 0;
+ u16 depth_water_top = 0;
+ u16 depth_riverbed = 0;
+ s16 biome_y_min = -MAX_MAP_GENERATION_LIMIT;
+ u32 vi = vm->m_area.index(x, node_max.Y, z);
+
+ // Check node at base of mapchunk above, either a node of a previously
+ // generated mapchunk or if not, a node of overgenerated base terrain.
+ content_t c_above = vm->m_data[vi + em.X].getContent();
+ bool air_above = c_above == CONTENT_AIR;
+ bool river_water_above = c_above == c_river_water_source;
+ bool water_above = c_above == c_water_source || river_water_above;
+
+ biomemap[index] = BIOME_NONE;
+
+ // If there is air or water above enable top/filler placement, otherwise force
+ // nplaced to stone level by setting a number exceeding any possible filler depth.
+ u16 nplaced = (air_above || water_above) ? 0 : U16_MAX;
+
+ for (s16 y = node_max.Y; y >= node_min.Y; y--) {
+ content_t c = vm->m_data[vi].getContent();
+ // Biome is (re)calculated:
+ // 1. At the surface of stone below air or water.
+ // 2. At the surface of water below air.
+ // 3. When stone or water is detected but biome has not yet been calculated.
+ // 4. When stone or water is detected just below a biome's lower limit.
+ bool is_stone_surface = (c == c_stone) &&
+ (air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4
+
+ bool is_water_surface =
+ (c == c_water_source || c == c_river_water_source) &&
+ (air_above || !biome || y < biome_y_min); // 2, 3, 4
+
+ if (is_stone_surface || is_water_surface) {
+ // (Re)calculate biome
+ biome = biomegen->getBiomeAtIndex(index, y);
+
+ if (biomemap[index] == BIOME_NONE && is_stone_surface)
+ biomemap[index] = biome->index;
+
+ depth_top = biome->depth_top;
+ base_filler = MYMAX(depth_top +
+ biome->depth_filler +
+ noise_filler_depth->result[index], 0.0f);
+ depth_water_top = biome->depth_water_top;
+ depth_riverbed = biome->depth_riverbed;
+ biome_y_min = biome->y_min;
+
+ // Detect stone type for dungeons during every biome calculation.
+ // If none detected the last selected biome stone is chosen.
+ if (biome->c_stone == c_stone)
+ stone_type = MGSTONE_STONE;
+ else if (biome->c_stone == c_desert_stone)
+ stone_type = MGSTONE_DESERT_STONE;
+ else if (biome->c_stone == c_sandstone)
+ stone_type = MGSTONE_SANDSTONE;
+
+ c_biome_stone = biome->c_stone;
+ }
+
+ if (c == c_stone) {
+ content_t c_below = vm->m_data[vi - em.X].getContent();
+
+ // If the node below isn't solid, make this node stone, so that
+ // any top/filler nodes above are structurally supported.
+ // This is done by aborting the cycle of top/filler placement
+ // immediately by forcing nplaced to stone level.
+ if (c_below == CONTENT_AIR
+ || c_below == c_water_source
+ || c_below == c_river_water_source)
+ nplaced = U16_MAX;
+
+ if (river_water_above) {
+ if (nplaced < depth_riverbed) {
+ vm->m_data[vi] = MapNode(biome->c_riverbed);
+ nplaced++;
+ } else {
+ nplaced = U16_MAX; // Disable top/filler placement
+ river_water_above = false;
+ }
+ } else if (nplaced < depth_top) {
+ vm->m_data[vi] = MapNode(biome->c_top);
+ nplaced++;
+ } else if (nplaced < base_filler) {
+ vm->m_data[vi] = MapNode(biome->c_filler);
+ nplaced++;
+ } else {
+ vm->m_data[vi] = MapNode(biome->c_stone);
+ nplaced = U16_MAX; // Disable top/filler placement
+ }
+
+ air_above = false;
+ water_above = false;
+ } else if (c == c_water_source) {
+ vm->m_data[vi] = MapNode((y > (s32)(water_level - depth_water_top))
+ ? biome->c_water_top : biome->c_water);
+ nplaced = 0; // Enable top/filler placement for next surface
+ air_above = false;
+ water_above = true;
+ } else if (c == c_river_water_source) {
+ vm->m_data[vi] = MapNode(biome->c_river_water);
+ nplaced = 0; // Enable riverbed placement for next surface
+ air_above = false;
+ water_above = true;
+ river_water_above = true;
+ } else if (c == CONTENT_AIR) {
+ nplaced = 0; // Enable top/filler placement for next surface
+ air_above = true;
+ water_above = false;
+ } else { // Possible various nodes overgenerated from neighbouring mapchunks
+ nplaced = U16_MAX; // Disable top/filler placement
+ air_above = false;
+ water_above = false;
+ }
+
+ vm->m_area.add_y(em, vi, -1);
+ }
+ }
+
+ *mgstone_type = stone_type;
+ *biome_stone = c_biome_stone;
+}
+
+
+void MapgenBasic::dustTopNodes()
+{
+ if (node_max.Y < water_level)
+ return;
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 index = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index]);
+
+ if (biome->c_dust == CONTENT_IGNORE)
+ continue;
+
+ u32 vi = vm->m_area.index(x, full_node_max.Y, z);
+ content_t c_full_max = vm->m_data[vi].getContent();
+ s16 y_start;
+
+ if (c_full_max == CONTENT_AIR) {
+ y_start = full_node_max.Y - 1;
+ } else if (c_full_max == CONTENT_IGNORE) {
+ vi = vm->m_area.index(x, node_max.Y + 1, z);
+ content_t c_max = vm->m_data[vi].getContent();
+
+ if (c_max == CONTENT_AIR)
+ y_start = node_max.Y;
+ else
+ continue;
+ } else {
+ continue;
+ }
+
+ vi = vm->m_area.index(x, y_start, z);
+ for (s16 y = y_start; y >= node_min.Y - 1; y--) {
+ if (vm->m_data[vi].getContent() != CONTENT_AIR)
+ break;
+
+ vm->m_area.add_y(em, vi, -1);
+ }
+
+ content_t c = vm->m_data[vi].getContent();
+ if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE && c != biome->c_dust) {
+ vm->m_area.add_y(em, vi, 1);
+ vm->m_data[vi] = MapNode(biome->c_dust);
+ }
+ }
+}
+
+
+void MapgenBasic::generateCaves(s16 max_stone_y, s16 large_cave_depth)
+{
+ if (max_stone_y < node_min.Y)
+ return;
+
+ CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize,
+ &np_cave1, &np_cave2, seed, cave_width);
+
+ caves_noise.generateCaves(vm, node_min, node_max, biomemap);
+
+ if (node_max.Y > large_cave_depth)
+ return;
+
+ PseudoRandom ps(blockseed + 21343);
+ u32 bruises_count = ps.range(0, 2);
+ for (u32 i = 0; i < bruises_count; i++) {
+ CavesRandomWalk cave(ndef, &gennotify, seed, water_level,
+ c_water_source, CONTENT_IGNORE, lava_depth);
+
+ cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap);
+ }
+}
+
+
+bool MapgenBasic::generateCaverns(s16 max_stone_y)
+{
+ if (node_min.Y > max_stone_y || node_min.Y > cavern_limit)
+ return false;
+
+ CavernsNoise caverns_noise(ndef, csize, &np_cavern,
+ seed, cavern_limit, cavern_taper, cavern_threshold);
+
+ return caverns_noise.generateCaverns(vm, node_min, node_max);
+}
+
+
+void MapgenBasic::generateDungeons(s16 max_stone_y,
+ MgStoneType stone_type, content_t biome_stone)
+{
+ if (max_stone_y < node_min.Y)
+ return;
+
+ DungeonParams dp;
+
+ dp.seed = seed;
+ dp.c_water = c_water_source;
+ dp.c_river_water = c_river_water_source;
+
+ dp.only_in_ground = true;
+ dp.corridor_len_min = 1;
+ dp.corridor_len_max = 13;
+ dp.rooms_min = 2;
+ dp.rooms_max = 16;
+ dp.y_min = -MAX_MAP_GENERATION_LIMIT;
+ dp.y_max = MAX_MAP_GENERATION_LIMIT;
+
+ dp.np_density = nparams_dungeon_density;
+ dp.np_alt_wall = nparams_dungeon_alt_wall;
+
+ switch (stone_type) {
+ default:
+ case MGSTONE_STONE:
+ dp.c_wall = c_cobble;
+ dp.c_alt_wall = c_mossycobble;
+ dp.c_stair = c_stair_cobble;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ break;
+ case MGSTONE_DESERT_STONE:
+ dp.c_wall = c_desert_stone;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = c_stair_desert_stone;
+
+ dp.diagonal_dirs = true;
+ dp.holesize = v3s16(2, 3, 2);
+ dp.room_size_min = v3s16(6, 9, 6);
+ dp.room_size_max = v3s16(10, 11, 10);
+ dp.room_size_large_min = v3s16(10, 13, 10);
+ dp.room_size_large_max = v3s16(18, 21, 18);
+ dp.notifytype = GENNOTIFY_TEMPLE;
+ break;
+ case MGSTONE_SANDSTONE:
+ dp.c_wall = c_sandstonebrick;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = c_stair_sandstone_block;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(2, 2, 2);
+ dp.room_size_min = v3s16(6, 4, 6);
+ dp.room_size_max = v3s16(10, 6, 10);
+ dp.room_size_large_min = v3s16(10, 8, 10);
+ dp.room_size_large_max = v3s16(18, 16, 18);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ break;
+ case MGSTONE_OTHER:
+ dp.c_wall = biome_stone;
+ dp.c_alt_wall = biome_stone;
+ dp.c_stair = biome_stone;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ break;
+ }
+
+ DungeonGen dgen(ndef, &gennotify, &dp);
+ dgen.generate(vm, blockseed, full_node_min, full_node_max);
+}
+
+
+////
+//// GenerateNotifier
+////
+
+GenerateNotifier::GenerateNotifier(u32 notify_on,
+ std::set<u32> *notify_on_deco_ids)
+{
+ m_notify_on = notify_on;
+ m_notify_on_deco_ids = notify_on_deco_ids;
+}
+
+
+void GenerateNotifier::setNotifyOn(u32 notify_on)
+{
+ m_notify_on = notify_on;
+}
+
+
+void GenerateNotifier::setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids)
+{
+ m_notify_on_deco_ids = notify_on_deco_ids;
+}
+
+
+bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id)
+{
+ if (!(m_notify_on & (1 << type)))
+ return false;
+
+ if (type == GENNOTIFY_DECORATION &&
+ m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->end())
+ return false;
+
+ GenNotifyEvent gne;
+ gne.type = type;
+ gne.pos = pos;
+ gne.id = id;
+ m_notify_events.push_back(gne);
+
+ return true;
+}
+
+
+void GenerateNotifier::getEvents(
+ std::map<std::string, std::vector<v3s16> > &event_map,
+ bool peek_events)
+{
+ std::list<GenNotifyEvent>::iterator it;
+
+ for (it = m_notify_events.begin(); it != m_notify_events.end(); ++it) {
+ GenNotifyEvent &gn = *it;
+ std::string name = (gn.type == GENNOTIFY_DECORATION) ?
+ "decoration#"+ itos(gn.id) :
+ flagdesc_gennotify[gn.type].name;
+
+ event_map[name].push_back(gn.pos);
+ }
+
+ if (!peek_events)
+ m_notify_events.clear();
+}
+
+
+////
+//// MapgenParams
+////
+
+
+MapgenParams::~MapgenParams()
+{
+ delete bparams;
+}
+
+
+void MapgenParams::readParams(const Settings *settings)
+{
+ std::string seed_str;
+ const char *seed_name = (settings == g_settings) ? "fixed_map_seed" : "seed";
+
+ if (settings->getNoEx(seed_name, seed_str)) {
+ if (!seed_str.empty())
+ seed = read_seed(seed_str.c_str());
+ else
+ myrand_bytes(&seed, sizeof(seed));
+ }
+
+ std::string mg_name;
+ if (settings->getNoEx("mg_name", mg_name)) {
+ mgtype = Mapgen::getMapgenType(mg_name);
+ if (mgtype == MAPGEN_INVALID)
+ mgtype = MAPGEN_DEFAULT;
+ }
+
+ settings->getS16NoEx("water_level", water_level);
+ settings->getS16NoEx("mapgen_limit", mapgen_limit);
+ settings->getS16NoEx("chunksize", chunksize);
+ settings->getFlagStrNoEx("mg_flags", flags, flagdesc_mapgen);
+
+ delete bparams;
+ bparams = BiomeManager::createBiomeParams(BIOMEGEN_ORIGINAL);
+ if (bparams) {
+ bparams->readParams(settings);
+ bparams->seed = seed;
+ }
+}
+
+
+void MapgenParams::writeParams(Settings *settings) const
+{
+ settings->set("mg_name", Mapgen::getMapgenName(mgtype));
+ settings->setU64("seed", seed);
+ settings->setS16("water_level", water_level);
+ settings->setS16("mapgen_limit", mapgen_limit);
+ settings->setS16("chunksize", chunksize);
+ settings->setFlagStr("mg_flags", flags, flagdesc_mapgen, U32_MAX);
+
+ if (bparams)
+ bparams->writeParams(settings);
+}
+
+// Calculate edges of outermost generated mapchunks (less than
+// 'mapgen_limit'), and corresponding exact limits for SAO entities.
+void MapgenParams::calcMapgenEdges()
+{
+ if (m_mapgen_edges_calculated)
+ return;
+
+ // Central chunk offset, in blocks
+ s16 ccoff_b = -chunksize / 2;
+ // Chunksize, in nodes
+ s32 csize_n = chunksize * MAP_BLOCKSIZE;
+ // Minp/maxp of central chunk, in nodes
+ s16 ccmin = ccoff_b * MAP_BLOCKSIZE;
+ s16 ccmax = ccmin + csize_n - 1;
+ // Fullminp/fullmaxp of central chunk, in nodes
+ s16 ccfmin = ccmin - MAP_BLOCKSIZE;
+ s16 ccfmax = ccmax + MAP_BLOCKSIZE;
+ // Effective mapgen limit, in blocks
+ // Uses same calculation as ServerMap::blockpos_over_mapgen_limit(v3s16 p)
+ s16 mapgen_limit_b = rangelim(mapgen_limit,
+ 0, MAX_MAP_GENERATION_LIMIT) / MAP_BLOCKSIZE;
+ // Effective mapgen limits, in nodes
+ s16 mapgen_limit_min = -mapgen_limit_b * MAP_BLOCKSIZE;
+ s16 mapgen_limit_max = (mapgen_limit_b + 1) * MAP_BLOCKSIZE - 1;
+ // Number of complete chunks from central chunk fullminp/fullmaxp
+ // to effective mapgen limits.
+ s16 numcmin = MYMAX((ccfmin - mapgen_limit_min) / csize_n, 0);
+ s16 numcmax = MYMAX((mapgen_limit_max - ccfmax) / csize_n, 0);
+ // Mapgen edges, in nodes
+ mapgen_edge_min = ccmin - numcmin * csize_n;
+ mapgen_edge_max = ccmax + numcmax * csize_n;
+ // SAO position limits, in Irrlicht units
+ m_sao_limit_min = mapgen_edge_min * BS - 3.0f;
+ m_sao_limit_max = mapgen_edge_max * BS + 3.0f;
+
+ m_mapgen_edges_calculated = true;
+}
+
+
+bool MapgenParams::saoPosOverLimit(const v3f &p)
+{
+ if (!m_mapgen_edges_calculated)
+ calcMapgenEdges();
+
+ return p.X < m_sao_limit_min ||
+ p.X > m_sao_limit_max ||
+ p.Y < m_sao_limit_min ||
+ p.Y > m_sao_limit_max ||
+ p.Z < m_sao_limit_min ||
+ p.Z > m_sao_limit_max;
+}
+
+
+s32 MapgenParams::getSpawnRangeMax()
+{
+ calcMapgenEdges();
+
+ return MYMIN(-mapgen_edge_min, mapgen_edge_max);
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "noise.h"
+#include "nodedef.h"
+#include "util/string.h"
+#include "util/container.h"
+
+#define MAPGEN_DEFAULT MAPGEN_V7
+#define MAPGEN_DEFAULT_NAME "v7"
+
+/////////////////// Mapgen flags
+#define MG_TREES 0x01 // Deprecated. Moved into mgv6 flags
+#define MG_CAVES 0x02
+#define MG_DUNGEONS 0x04
+#define MG_FLAT 0x08 // Deprecated. Moved into mgv6 flags
+#define MG_LIGHT 0x10
+#define MG_DECORATIONS 0x20
+
+typedef u8 biome_t; // copy from mg_biome.h to avoid an unnecessary include
+
+class Settings;
+class MMVManip;
+class INodeDefManager;
+
+extern FlagDesc flagdesc_mapgen[];
+extern FlagDesc flagdesc_gennotify[];
+
+class Biome;
+class BiomeGen;
+struct BiomeParams;
+class BiomeManager;
+class EmergeManager;
+class MapBlock;
+class VoxelManipulator;
+struct BlockMakeData;
+class VoxelArea;
+class Map;
+
+enum MapgenObject {
+ MGOBJ_VMANIP,
+ MGOBJ_HEIGHTMAP,
+ MGOBJ_BIOMEMAP,
+ MGOBJ_HEATMAP,
+ MGOBJ_HUMIDMAP,
+ MGOBJ_GENNOTIFY
+};
+
+enum GenNotifyType {
+ GENNOTIFY_DUNGEON,
+ GENNOTIFY_TEMPLE,
+ GENNOTIFY_CAVE_BEGIN,
+ GENNOTIFY_CAVE_END,
+ GENNOTIFY_LARGECAVE_BEGIN,
+ GENNOTIFY_LARGECAVE_END,
+ GENNOTIFY_DECORATION,
+ NUM_GENNOTIFY_TYPES
+};
+
+enum MgStoneType {
+ MGSTONE_STONE,
+ MGSTONE_DESERT_STONE,
+ MGSTONE_SANDSTONE,
+ MGSTONE_OTHER,
+};
+
+struct GenNotifyEvent {
+ GenNotifyType type;
+ v3s16 pos;
+ u32 id;
+};
+
+class GenerateNotifier {
+public:
+ GenerateNotifier() = default;
+ GenerateNotifier(u32 notify_on, std::set<u32> *notify_on_deco_ids);
+
+ void setNotifyOn(u32 notify_on);
+ void setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids);
+
+ bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0);
+ void getEvents(std::map<std::string, std::vector<v3s16> > &event_map,
+ bool peek_events=false);
+
+private:
+ u32 m_notify_on = 0;
+ std::set<u32> *m_notify_on_deco_ids;
+ std::list<GenNotifyEvent> m_notify_events;
+};
+
+enum MapgenType {
+ MAPGEN_V5,
+ MAPGEN_V6,
+ MAPGEN_V7,
+ MAPGEN_FLAT,
+ MAPGEN_FRACTAL,
+ MAPGEN_VALLEYS,
+ MAPGEN_SINGLENODE,
+ MAPGEN_CARPATHIAN,
+ MAPGEN_INVALID,
+};
+
+struct MapgenParams {
+ MapgenParams() = default;
+ virtual ~MapgenParams();
+
+ MapgenType mgtype = MAPGEN_DEFAULT;
+ s16 chunksize = 5;
+ u64 seed = 0;
+ s16 water_level = 1;
+ s16 mapgen_limit = MAX_MAP_GENERATION_LIMIT;
+ u32 flags = MG_CAVES | MG_LIGHT | MG_DECORATIONS;
+
+ BiomeParams *bparams = nullptr;
+
+ s16 mapgen_edge_min = -MAX_MAP_GENERATION_LIMIT;
+ s16 mapgen_edge_max = MAX_MAP_GENERATION_LIMIT;
+
+ virtual void readParams(const Settings *settings);
+ virtual void writeParams(Settings *settings) const;
+
+ bool saoPosOverLimit(const v3f &p);
+ s32 getSpawnRangeMax();
+
+private:
+ void calcMapgenEdges();
+
+ float m_sao_limit_min = -MAX_MAP_GENERATION_LIMIT * BS;
+ float m_sao_limit_max = MAX_MAP_GENERATION_LIMIT * BS;
+ bool m_mapgen_edges_calculated = false;
+};
+
+
+/*
+ Generic interface for map generators. All mapgens must inherit this class.
+ If a feature exposed by a public member pointer is not supported by a
+ certain mapgen, it must be set to NULL.
+
+ Apart from makeChunk, getGroundLevelAtPoint, and getSpawnLevelAtPoint, all
+ methods can be used by constructing a Mapgen base class and setting the
+ appropriate public members (e.g. vm, ndef, and so on).
+*/
+class Mapgen {
+public:
+ s32 seed = 0;
+ int water_level = 0;
+ int mapgen_limit = 0;
+ u32 flags = 0;
+ bool generating = false;
+ int id = -1;
+
+ MMVManip *vm = nullptr;
+ INodeDefManager *ndef = nullptr;
+
+ u32 blockseed;
+ s16 *heightmap = nullptr;
+ biome_t *biomemap = nullptr;
+ v3s16 csize;
+
+ BiomeGen *biomegen = nullptr;
+ GenerateNotifier gennotify;
+
+ Mapgen() = default;
+ Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ virtual ~Mapgen() = default;
+ DISABLE_CLASS_COPY(Mapgen);
+
+ virtual MapgenType getType() const { return MAPGEN_INVALID; }
+
+ static u32 getBlockSeed(v3s16 p, s32 seed);
+ static u32 getBlockSeed2(v3s16 p, s32 seed);
+ s16 findGroundLevelFull(v2s16 p2d);
+ s16 findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax);
+ s16 findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax);
+ void updateHeightmap(v3s16 nmin, v3s16 nmax);
+ void getSurfaces(v2s16 p2d, s16 ymin, s16 ymax,
+ s16 *floors, s16 *ceilings, u16 *num_floors, u16 *num_ceilings);
+
+ void updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax);
+
+ void setLighting(u8 light, v3s16 nmin, v3s16 nmax);
+ void lightSpread(VoxelArea &a, v3s16 p, u8 light);
+ void calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
+ bool propagate_shadow = true);
+ void propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow);
+ void spreadLight(v3s16 nmin, v3s16 nmax);
+
+ virtual void makeChunk(BlockMakeData *data) {}
+ virtual int getGroundLevelAtPoint(v2s16 p) { return 0; }
+
+ // getSpawnLevelAtPoint() is a function within each mapgen that returns a
+ // suitable y co-ordinate for player spawn ('suitable' usually meaning
+ // within 16 nodes of water_level). If a suitable spawn level cannot be
+ // found at the specified (X, Z) 'MAX_MAP_GENERATION_LIMIT' is returned to
+ // signify this and to cause Server::findSpawnPos() to try another (X, Z).
+ virtual int getSpawnLevelAtPoint(v2s16 p) { return 0; }
+
+ // Mapgen management functions
+ static MapgenType getMapgenType(const std::string &mgname);
+ static const char *getMapgenName(MapgenType mgtype);
+ static Mapgen *createMapgen(MapgenType mgtype, int mgid,
+ MapgenParams *params, EmergeManager *emerge);
+ static MapgenParams *createMapgenParams(MapgenType mgtype);
+ static void getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden);
+
+private:
+ // isLiquidHorizontallyFlowable() is a helper function for updateLiquid()
+ // that checks whether there are floodable nodes without liquid beneath
+ // the node at index vi.
+ inline bool isLiquidHorizontallyFlowable(u32 vi, v3s16 em);
+};
+
+/*
+ MapgenBasic is a Mapgen implementation that handles basic functionality
+ the majority of conventional mapgens will probably want to use, but isn't
+ generic enough to be included as part of the base Mapgen class (such as
+ generating biome terrain over terrain node skeletons, generating caves,
+ dungeons, etc.)
+
+ Inherit MapgenBasic instead of Mapgen to add this basic functionality to
+ your mapgen without having to reimplement it. Feel free to override any of
+ these methods if you desire different or more advanced behavior.
+
+ Note that you must still create your own generateTerrain implementation when
+ inheriting MapgenBasic.
+*/
+class MapgenBasic : public Mapgen {
+public:
+ MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ virtual ~MapgenBasic();
+
+ virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth);
+ virtual bool generateCaverns(s16 max_stone_y);
+ virtual void generateDungeons(s16 max_stone_y,
+ MgStoneType stone_type, content_t biome_stone);
+ virtual void generateBiomes(MgStoneType *mgstone_type,
+ content_t *biome_stone);
+ virtual void dustTopNodes();
+
+protected:
+ EmergeManager *m_emerge;
+ BiomeManager *m_bmgr;
+
+ Noise *noise_filler_depth;
+
+ v3s16 node_min;
+ v3s16 node_max;
+ v3s16 full_node_min;
+ v3s16 full_node_max;
+
+ // Content required for generateBiomes
+ content_t c_stone;
+ content_t c_desert_stone;
+ content_t c_sandstone;
+ content_t c_water_source;
+ content_t c_river_water_source;
+ content_t c_lava_source;
+
+ // Content required for generateDungeons
+ content_t c_cobble;
+ content_t c_stair_cobble;
+ content_t c_mossycobble;
+ content_t c_stair_desert_stone;
+ content_t c_sandstonebrick;
+ content_t c_stair_sandstone_block;
+
+ int ystride;
+ int zstride;
+ int zstride_1d;
+ int zstride_1u1d;
+
+ u32 spflags;
+
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+ NoiseParams np_cavern;
+ float cave_width;
+ float cavern_limit;
+ float cavern_taper;
+ float cavern_threshold;
+ int lava_depth;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2016 paramat, Matt Gregory
+Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2017 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include <cmath>
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_carpathian.h"
+
+
+FlagDesc flagdesc_mapgen_carpathian[] = {
+ {"caverns", MGCARPATHIAN_CAVERNS},
+ {NULL, 0}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+MapgenCarpathian::MapgenCarpathian(
+ int mapgenid, MapgenCarpathianParams *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ cave_width = params->cave_width;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ cavern_limit = params->cavern_limit;
+ cavern_taper = params->cavern_taper;
+ cavern_threshold = params->cavern_threshold;
+ grad_wl = 1 - water_level;
+
+ //// 2D Terrain noise
+ noise_base = new Noise(¶ms->np_base, seed, csize.X, csize.Z);
+ noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
+ noise_height1 = new Noise(¶ms->np_height1, seed, csize.X, csize.Z);
+ noise_height2 = new Noise(¶ms->np_height2, seed, csize.X, csize.Z);
+ noise_height3 = new Noise(¶ms->np_height3, seed, csize.X, csize.Z);
+ noise_height4 = new Noise(¶ms->np_height4, seed, csize.X, csize.Z);
+ noise_hills_terrain = new Noise(¶ms->np_hills_terrain, seed, csize.X, csize.Z);
+ noise_ridge_terrain = new Noise(¶ms->np_ridge_terrain, seed, csize.X, csize.Z);
+ noise_step_terrain = new Noise(¶ms->np_step_terrain, seed, csize.X, csize.Z);
+ noise_hills = new Noise(¶ms->np_hills, seed, csize.X, csize.Z);
+ noise_ridge_mnt = new Noise(¶ms->np_ridge_mnt, seed, csize.X, csize.Z);
+ noise_step_mnt = new Noise(¶ms->np_step_mnt, seed, csize.X, csize.Z);
+
+ //// 3D terrain noise
+ // 1 up 1 down overgeneration
+ noise_mnt_var = new Noise(¶ms->np_mnt_var, seed, csize.X, csize.Y + 2, csize.Z);
+
+ //// Cave noise
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+}
+
+
+MapgenCarpathian::~MapgenCarpathian()
+{
+ delete noise_base;
+ delete noise_filler_depth;
+ delete noise_height1;
+ delete noise_height2;
+ delete noise_height3;
+ delete noise_height4;
+ delete noise_hills_terrain;
+ delete noise_ridge_terrain;
+ delete noise_step_terrain;
+ delete noise_hills;
+ delete noise_ridge_mnt;
+ delete noise_step_mnt;
+ delete noise_mnt_var;
+}
+
+
+MapgenCarpathianParams::MapgenCarpathianParams():
+ np_base (12, 1, v3f(2557, 2557, 2557), 6538, 4, 0.8, 0.5),
+ np_filler_depth (0, 1, v3f(128, 128, 128), 261, 3, 0.7, 2.0),
+ np_height1 (0, 5, v3f(251, 251, 251), 9613, 5, 0.5, 2.0),
+ np_height2 (0, 5, v3f(383, 383, 383), 1949, 5, 0.5, 2.0),
+ np_height3 (0, 5, v3f(509, 509, 509), 3211, 5, 0.5, 2.0),
+ np_height4 (0, 5, v3f(631, 631, 631), 1583, 5, 0.5, 2.0),
+ np_hills_terrain (1, 1, v3f(1301, 1301, 1301), 1692, 5, 0.5, 2.0),
+ np_ridge_terrain (1, 1, v3f(1889, 1889, 1889), 3568, 5, 0.5, 2.0),
+ np_step_terrain (1, 1, v3f(1889, 1889, 1889), 4157, 5, 0.5, 2.0),
+ np_hills (0, 3, v3f(257, 257, 257), 6604, 6, 0.5, 2.0),
+ np_ridge_mnt (0, 12, v3f(743, 743, 743), 5520, 6, 0.7, 2.0),
+ np_step_mnt (0, 8, v3f(509, 509, 509), 2590, 6, 0.6, 2.0),
+ np_mnt_var (0, 1, v3f(499, 499, 499), 2490, 5, 0.55, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0)
+{
+}
+
+
+void MapgenCarpathianParams::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian);
+ settings->getFloatNoEx("mgcarpathian_cave_width", cave_width);
+ settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgcarpathian_lava_depth", lava_depth);
+ settings->getS16NoEx("mgcarpathian_cavern_limit", cavern_limit);
+ settings->getS16NoEx("mgcarpathian_cavern_taper", cavern_taper);
+ settings->getFloatNoEx("mgcarpathian_cavern_threshold", cavern_threshold);
+
+ settings->getNoiseParams("mgcarpathian_np_base", np_base);
+ settings->getNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgcarpathian_np_height1", np_height1);
+ settings->getNoiseParams("mgcarpathian_np_height2", np_height2);
+ settings->getNoiseParams("mgcarpathian_np_height3", np_height3);
+ settings->getNoiseParams("mgcarpathian_np_height4", np_height4);
+ settings->getNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain);
+ settings->getNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain);
+ settings->getNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain);
+ settings->getNoiseParams("mgcarpathian_np_hills", np_hills);
+ settings->getNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt);
+ settings->getNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt);
+ settings->getNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var);
+ settings->getNoiseParams("mgcarpathian_np_cave1", np_cave1);
+ settings->getNoiseParams("mgcarpathian_np_cave2", np_cave2);
+ settings->getNoiseParams("mgcarpathian_np_cavern", np_cavern);
+}
+
+
+void MapgenCarpathianParams::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian, U32_MAX);
+ settings->setFloat("mgcarpathian_cave_width", cave_width);
+ settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth);
+ settings->setS16("mgcarpathian_lava_depth", lava_depth);
+ settings->setS16("mgcarpathian_cavern_limit", cavern_limit);
+ settings->setS16("mgcarpathian_cavern_taper", cavern_taper);
+ settings->setFloat("mgcarpathian_cavern_threshold", cavern_threshold);
+
+ settings->setNoiseParams("mgcarpathian_np_base", np_base);
+ settings->setNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgcarpathian_np_height1", np_height1);
+ settings->setNoiseParams("mgcarpathian_np_height2", np_height2);
+ settings->setNoiseParams("mgcarpathian_np_height3", np_height3);
+ settings->setNoiseParams("mgcarpathian_np_height4", np_height4);
+ settings->setNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain);
+ settings->setNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain);
+ settings->setNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain);
+ settings->setNoiseParams("mgcarpathian_np_hills", np_hills);
+ settings->setNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt);
+ settings->setNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt);
+ settings->setNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var);
+ settings->setNoiseParams("mgcarpathian_np_cave1", np_cave1);
+ settings->setNoiseParams("mgcarpathian_np_cave2", np_cave2);
+ settings->setNoiseParams("mgcarpathian_np_cavern", np_cavern);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+// Lerp function
+inline float MapgenCarpathian::getLerp(float noise1, float noise2, float mod)
+{
+ return noise1 + mod * (noise2 - noise1);
+}
+
+// Steps function
+float MapgenCarpathian::getSteps(float noise)
+{
+ float w = 0.5f;
+ float k = floor(noise / w);
+ float f = (noise - k * w) / w;
+ float s = std::fmin(2.f * f, 1.f);
+ return (k + s) * w;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void MapgenCarpathian::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ // Create a block-specific seed
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate terrain
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ // Generate caverns, tunnels and classic caves
+ if (flags & MG_CAVES) {
+ bool has_cavern = false;
+ // Generate caverns
+ if (spflags & MGCARPATHIAN_CAVERNS)
+ has_cavern = generateCaverns(stone_surface_max_y);
+ // Generate tunnels and classic caves
+ if (has_cavern)
+ // Disable classic caves in this mapchunk by setting
+ // 'large cave depth' to world base. Avoids excessive liquid in
+ // large caverns and floating blobs of overgenerated liquid.
+ generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
+ else
+ generateCaves(stone_surface_max_y, large_cave_depth);
+ }
+
+ // Generate dungeons
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ // Update liquids
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ // Calculate lighting
+ if (flags & MG_LIGHT) {
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max);
+ }
+
+ this->generating = false;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p)
+{
+ s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
+ if (level_at_point <= water_level || level_at_point > water_level + 32)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ return level_at_point;
+}
+
+
+float MapgenCarpathian::terrainLevelAtPoint(s16 x, s16 z)
+{
+ float ground = NoisePerlin2D(&noise_base->np, x, z, seed);
+ float height1 = NoisePerlin2D(&noise_height1->np, x, z, seed);
+ float height2 = NoisePerlin2D(&noise_height2->np, x, z, seed);
+ float height3 = NoisePerlin2D(&noise_height3->np, x, z, seed);
+ float height4 = NoisePerlin2D(&noise_height4->np, x, z, seed);
+ float hter = NoisePerlin2D(&noise_hills_terrain->np, x, z, seed);
+ float rter = NoisePerlin2D(&noise_ridge_terrain->np, x, z, seed);
+ float ster = NoisePerlin2D(&noise_step_terrain->np, x, z, seed);
+ float n_hills = NoisePerlin2D(&noise_hills->np, x, z, seed);
+ float n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, x, z, seed);
+ float n_step_mnt = NoisePerlin2D(&noise_step_mnt->np, x, z, seed);
+
+ int height = -MAX_MAP_GENERATION_LIMIT;
+
+ for (s16 y = 1; y <= 30; y++) {
+ float mnt_var = NoisePerlin3D(&noise_mnt_var->np, x, y, z, seed);
+
+ // Gradient & shallow seabed
+ s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y;
+
+ // Hill/Mountain height (hilliness)
+ float hill1 = getLerp(height1, height2, mnt_var);
+ float hill2 = getLerp(height3, height4, mnt_var);
+ float hill3 = getLerp(height3, height2, mnt_var);
+ float hill4 = getLerp(height1, height4, mnt_var);
+ float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
+
+ // Rolling hills
+ float hill_mnt = hilliness * pow(n_hills, 2.f);
+ float hills = pow(hter, 3.f) * hill_mnt;
+
+ // Ridged mountains
+ float ridge_mnt = hilliness * (1.f - fabs(n_ridge_mnt));
+ float ridged_mountains = pow(rter, 3.f) * ridge_mnt;
+
+ // Step (terraced) mountains
+ float step_mnt = hilliness * getSteps(n_step_mnt);
+ float step_mountains = pow(ster, 3.f) * step_mnt;
+
+ // Final terrain level
+ float mountains = hills + ridged_mountains + step_mountains;
+ float surface_level = ground + mountains + grad;
+
+ if (y > surface_level && height < 0)
+ height = y;
+ }
+
+ return height;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+int MapgenCarpathian::generateTerrain()
+{
+ MapNode mn_air(CONTENT_AIR);
+ MapNode mn_stone(c_stone);
+ MapNode mn_water(c_water_source);
+
+ s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 index2d = 0;
+ u32 index3d = 0;
+
+ // Calculate noise for terrain generation
+ noise_base->perlinMap2D(node_min.X, node_min.Z);
+ noise_height1->perlinMap2D(node_min.X, node_min.Z);
+ noise_height2->perlinMap2D(node_min.X, node_min.Z);
+ noise_height3->perlinMap2D(node_min.X, node_min.Z);
+ noise_height4->perlinMap2D(node_min.X, node_min.Z);
+ noise_hills_terrain->perlinMap2D(node_min.X, node_min.Z);
+ noise_ridge_terrain->perlinMap2D(node_min.X, node_min.Z);
+ noise_step_terrain->perlinMap2D(node_min.X, node_min.Z);
+ noise_hills->perlinMap2D(node_min.X, node_min.Z);
+ noise_ridge_mnt->perlinMap2D(node_min.X, node_min.Z);
+ noise_step_mnt->perlinMap2D(node_min.X, node_min.Z);
+ noise_mnt_var->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ //// Place nodes
+ for (s16 z = node_min.Z; z <= node_max.Z; z++) {
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X;
+ x++, vi++, index2d++, index3d++) {
+ if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
+ continue;
+
+ // Base terrain
+ float ground = noise_base->result[index2d];
+
+ // Gradient & shallow seabed
+ s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y;
+
+ // Hill/Mountain height (hilliness)
+ float height1 = noise_height1->result[index2d];
+ float height2 = noise_height2->result[index2d];
+ float height3 = noise_height3->result[index2d];
+ float height4 = noise_height4->result[index2d];
+ float mnt_var = noise_mnt_var->result[index3d];
+ // Combine height noises and apply 3D variation
+ float hill1 = getLerp(height1, height2, mnt_var);
+ float hill2 = getLerp(height3, height4, mnt_var);
+ float hill3 = getLerp(height3, height2, mnt_var);
+ float hill4 = getLerp(height1, height4, mnt_var);
+ // 'hilliness' determines whether hills/mountains are
+ // small or large
+ float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
+
+ // Rolling hills
+ float hter = noise_hills_terrain->result[index2d];
+ float n_hills = noise_hills->result[index2d];
+ float hill_mnt = hilliness * pow(n_hills, 2.f);
+ float hills = pow(fabs(hter), 3.f) * hill_mnt;
+
+ // Ridged mountains
+ float rter = noise_ridge_terrain->result[index2d];
+ float n_ridge_mnt = noise_ridge_mnt->result[index2d];
+ float ridge_mnt = hilliness * (1.f - fabs(n_ridge_mnt));
+ float ridged_mountains = pow(fabs(rter), 3.f) * ridge_mnt;
+
+ // Step (terraced) mountains
+ float ster = noise_step_terrain->result[index2d];
+ float n_step_mnt = noise_step_mnt->result[index2d];
+ float step_mnt = hilliness * getSteps(n_step_mnt);
+ float step_mountains = pow(fabs(ster), 3.f) * step_mnt;
+
+ // Final terrain level
+ float mountains = hills + ridged_mountains + step_mountains;
+ float surface_level = ground + mountains + grad;
+
+ if (y < surface_level) {
+ vm->m_data[vi] = mn_stone; // Stone
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if (y <= water_level) {
+ vm->m_data[vi] = mn_water; // Sea water
+ } else {
+ vm->m_data[vi] = mn_air; // Air
+ }
+ }
+ index2d -= ystride;
+ }
+ index2d += ystride;
+ }
+
+ return stone_surface_max_y;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2016 paramat, Matt Gregory
+Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2017 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+///////// Mapgen Carpathian flags
+#define MGCARPATHIAN_CAVERNS 0x01
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_carpathian[];
+
+
+struct MapgenCarpathianParams : public MapgenParams
+{
+ u32 spflags = MGCARPATHIAN_CAVERNS;
+ float cave_width = 0.09f;
+ s16 large_cave_depth = -33;
+ s16 lava_depth = -256;
+ s16 cavern_limit = -256;
+ s16 cavern_taper = 256;
+ float cavern_threshold = 0.7f;
+
+ NoiseParams np_base;
+ NoiseParams np_filler_depth;
+ NoiseParams np_height1;
+ NoiseParams np_height2;
+ NoiseParams np_height3;
+ NoiseParams np_height4;
+ NoiseParams np_hills_terrain;
+ NoiseParams np_ridge_terrain;
+ NoiseParams np_step_terrain;
+ NoiseParams np_hills;
+ NoiseParams np_ridge_mnt;
+ NoiseParams np_step_mnt;
+ NoiseParams np_mnt_var;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+ NoiseParams np_cavern;
+
+ MapgenCarpathianParams();
+ ~MapgenCarpathianParams() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenCarpathian : public MapgenBasic
+{
+public:
+ MapgenCarpathian(int mapgenid, MapgenCarpathianParams *params,
+ EmergeManager *emerge);
+ ~MapgenCarpathian();
+
+ virtual MapgenType getType() const { return MAPGEN_CARPATHIAN; }
+
+ float getSteps(float noise);
+ inline float getLerp(float noise1, float noise2, float mod);
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+
+private:
+ s16 large_cave_depth;
+ s32 grad_wl;
+
+ Noise *noise_base;
+ Noise *noise_height1;
+ Noise *noise_height2;
+ Noise *noise_height3;
+ Noise *noise_height4;
+ Noise *noise_hills_terrain;
+ Noise *noise_ridge_terrain;
+ Noise *noise_step_terrain;
+ Noise *noise_hills;
+ Noise *noise_ridge_mnt;
+ Noise *noise_step_mnt;
+ Noise *noise_mnt_var;
+
+ float terrainLevelAtPoint(s16 x, s16 z);
+ int generateTerrain();
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2015-2017 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_flat.h"
+
+
+FlagDesc flagdesc_mapgen_flat[] = {
+ {"lakes", MGFLAT_LAKES},
+ {"hills", MGFLAT_HILLS},
+ {NULL, 0}
+};
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+
+MapgenFlat::MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ ground_level = params->ground_level;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ cave_width = params->cave_width;
+ lake_threshold = params->lake_threshold;
+ lake_steepness = params->lake_steepness;
+ hill_threshold = params->hill_threshold;
+ hill_steepness = params->hill_steepness;
+
+ // 2D noise
+ noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
+
+ if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
+ noise_terrain = new Noise(¶ms->np_terrain, seed, csize.X, csize.Z);
+ // 3D noise
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+}
+
+
+MapgenFlat::~MapgenFlat()
+{
+ delete noise_filler_depth;
+
+ if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
+ delete noise_terrain;
+}
+
+
+MapgenFlatParams::MapgenFlatParams():
+ np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0),
+ np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
+{
+}
+
+
+void MapgenFlatParams::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgflat_spflags", spflags, flagdesc_mapgen_flat);
+ settings->getS16NoEx("mgflat_ground_level", ground_level);
+ settings->getS16NoEx("mgflat_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgflat_lava_depth", lava_depth);
+ settings->getFloatNoEx("mgflat_cave_width", cave_width);
+ settings->getFloatNoEx("mgflat_lake_threshold", lake_threshold);
+ settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness);
+ settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold);
+ settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness);
+
+ settings->getNoiseParams("mgflat_np_terrain", np_terrain);
+ settings->getNoiseParams("mgflat_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgflat_np_cave1", np_cave1);
+ settings->getNoiseParams("mgflat_np_cave2", np_cave2);
+}
+
+
+void MapgenFlatParams::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgflat_spflags", spflags, flagdesc_mapgen_flat, U32_MAX);
+ settings->setS16("mgflat_ground_level", ground_level);
+ settings->setS16("mgflat_large_cave_depth", large_cave_depth);
+ settings->setS16("mgflat_lava_depth", lava_depth);
+ settings->setFloat("mgflat_cave_width", cave_width);
+ settings->setFloat("mgflat_lake_threshold", lake_threshold);
+ settings->setFloat("mgflat_lake_steepness", lake_steepness);
+ settings->setFloat("mgflat_hill_threshold", hill_threshold);
+ settings->setFloat("mgflat_hill_steepness", hill_steepness);
+
+ settings->setNoiseParams("mgflat_np_terrain", np_terrain);
+ settings->setNoiseParams("mgflat_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgflat_np_cave1", np_cave1);
+ settings->setNoiseParams("mgflat_np_cave2", np_cave2);
+}
+
+
+/////////////////////////////////////////////////////////////////
+
+
+int MapgenFlat::getSpawnLevelAtPoint(v2s16 p)
+{
+ s16 level_at_point = ground_level;
+ float n_terrain = 0.0f;
+ if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
+ n_terrain = NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed);
+
+ if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
+ level_at_point = ground_level -
+ (lake_threshold - n_terrain) * lake_steepness;
+ } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
+ level_at_point = ground_level +
+ (n_terrain - hill_threshold) * hill_steepness;
+ }
+
+ if (ground_level < water_level) // Ocean world, allow spawn in water
+ return MYMAX(level_at_point, water_level);
+
+ if (level_at_point > water_level)
+ return level_at_point; // Spawn on land
+
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+}
+
+
+void MapgenFlat::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate base terrain, mountains, and ridges with initial heightmaps
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ if (flags & MG_CAVES)
+ generateCaves(stone_surface_max_y, large_cave_depth);
+
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //printf("makeChunk: %dms\n", t.stop());
+
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ if (flags & MG_LIGHT)
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max);
+
+ //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
+ // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
+
+ this->generating = false;
+}
+
+
+s16 MapgenFlat::generateTerrain()
+{
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_stone(c_stone);
+ MapNode n_water(c_water_source);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 ni2d = 0;
+
+ bool use_noise = (spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS);
+ if (use_noise)
+ noise_terrain->perlinMap2D(node_min.X, node_min.Z);
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, ni2d++) {
+ s16 stone_level = ground_level;
+ float n_terrain = use_noise ? noise_terrain->result[ni2d] : 0.0f;
+
+ if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
+ s16 depress = (lake_threshold - n_terrain) * lake_steepness;
+ stone_level = ground_level - depress;
+ } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
+ s16 rise = (n_terrain - hill_threshold) * hill_steepness;
+ stone_level = ground_level + rise;
+ }
+
+ u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
+ if (y <= stone_level) {
+ vm->m_data[vi] = n_stone;
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if (y <= water_level) {
+ vm->m_data[vi] = n_water;
+ } else {
+ vm->m_data[vi] = n_air;
+ }
+ }
+ vm->m_area.add_y(em, vi, 1);
+ }
+ }
+
+ return stone_surface_max_y;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2015-2017 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+/////// Mapgen Flat flags
+#define MGFLAT_LAKES 0x01
+#define MGFLAT_HILLS 0x02
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_flat[];
+
+struct MapgenFlatParams : public MapgenParams
+{
+ u32 spflags = 0;
+ s16 ground_level = 8;
+ s16 large_cave_depth = -33;
+ s16 lava_depth = -256;
+ float cave_width = 0.09f;
+ float lake_threshold = -0.45f;
+ float lake_steepness = 48.0f;
+ float hill_threshold = 0.45f;
+ float hill_steepness = 64.0f;
+ NoiseParams np_terrain;
+ NoiseParams np_filler_depth;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+
+ MapgenFlatParams();
+ ~MapgenFlatParams() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenFlat : public MapgenBasic
+{
+public:
+ MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge);
+ ~MapgenFlat();
+
+ virtual MapgenType getType() const { return MAPGEN_FLAT; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+ s16 generateTerrain();
+
+private:
+ s16 ground_level;
+ s16 large_cave_depth;
+ float lake_threshold;
+ float lake_steepness;
+ float hill_threshold;
+ float hill_steepness;
+ Noise *noise_terrain;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2015-2017 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_fractal.h"
+
+
+FlagDesc flagdesc_mapgen_fractal[] = {
+ {NULL, 0}
+};
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+
+MapgenFractal::MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ cave_width = params->cave_width;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ fractal = params->fractal;
+ iterations = params->iterations;
+ scale = params->scale;
+ offset = params->offset;
+ slice_w = params->slice_w;
+ julia_x = params->julia_x;
+ julia_y = params->julia_y;
+ julia_z = params->julia_z;
+ julia_w = params->julia_w;
+
+ //// 2D terrain noise
+ noise_seabed = new Noise(¶ms->np_seabed, seed, csize.X, csize.Z);
+ noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
+
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+
+ formula = fractal / 2 + fractal % 2;
+ julia = fractal % 2 == 0;
+}
+
+
+MapgenFractal::~MapgenFractal()
+{
+ delete noise_seabed;
+ delete noise_filler_depth;
+}
+
+
+MapgenFractalParams::MapgenFractalParams():
+ np_seabed (-14, 9, v3f(600, 600, 600), 41900, 5, 0.6, 2.0),
+ np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
+{
+}
+
+
+void MapgenFractalParams::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgfractal_spflags", spflags, flagdesc_mapgen_fractal);
+ settings->getFloatNoEx("mgfractal_cave_width", cave_width);
+ settings->getS16NoEx("mgfractal_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgfractal_lava_depth", lava_depth);
+ settings->getU16NoEx("mgfractal_fractal", fractal);
+ settings->getU16NoEx("mgfractal_iterations", iterations);
+ settings->getV3FNoEx("mgfractal_scale", scale);
+ settings->getV3FNoEx("mgfractal_offset", offset);
+ settings->getFloatNoEx("mgfractal_slice_w", slice_w);
+ settings->getFloatNoEx("mgfractal_julia_x", julia_x);
+ settings->getFloatNoEx("mgfractal_julia_y", julia_y);
+ settings->getFloatNoEx("mgfractal_julia_z", julia_z);
+ settings->getFloatNoEx("mgfractal_julia_w", julia_w);
+
+ settings->getNoiseParams("mgfractal_np_seabed", np_seabed);
+ settings->getNoiseParams("mgfractal_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgfractal_np_cave1", np_cave1);
+ settings->getNoiseParams("mgfractal_np_cave2", np_cave2);
+}
+
+
+void MapgenFractalParams::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgfractal_spflags", spflags, flagdesc_mapgen_fractal, U32_MAX);
+ settings->setFloat("mgfractal_cave_width", cave_width);
+ settings->setS16("mgfractal_large_cave_depth", large_cave_depth);
+ settings->setS16("mgfractal_lava_depth", lava_depth);
+ settings->setU16("mgfractal_fractal", fractal);
+ settings->setU16("mgfractal_iterations", iterations);
+ settings->setV3F("mgfractal_scale", scale);
+ settings->setV3F("mgfractal_offset", offset);
+ settings->setFloat("mgfractal_slice_w", slice_w);
+ settings->setFloat("mgfractal_julia_x", julia_x);
+ settings->setFloat("mgfractal_julia_y", julia_y);
+ settings->setFloat("mgfractal_julia_z", julia_z);
+ settings->setFloat("mgfractal_julia_w", julia_w);
+
+ settings->setNoiseParams("mgfractal_np_seabed", np_seabed);
+ settings->setNoiseParams("mgfractal_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgfractal_np_cave1", np_cave1);
+ settings->setNoiseParams("mgfractal_np_cave2", np_cave2);
+}
+
+
+/////////////////////////////////////////////////////////////////
+
+
+int MapgenFractal::getSpawnLevelAtPoint(v2s16 p)
+{
+ bool solid_below = false; // Dry solid node is present below to spawn on
+ u8 air_count = 0; // Consecutive air nodes above the dry solid node
+ s16 seabed_level = NoisePerlin2D(&noise_seabed->np, p.X, p.Y, seed);
+ // Seabed can rise above water_level or might be raised to create dry land
+ s16 search_start = MYMAX(seabed_level, water_level + 1);
+ if (seabed_level > water_level)
+ solid_below = true;
+
+ for (s16 y = search_start; y <= search_start + 128; y++) {
+ if (getFractalAtPoint(p.X, y, p.Y)) { // Fractal node
+ solid_below = true;
+ air_count = 0;
+ } else if (solid_below) { // Air above solid node
+ air_count++;
+ // 3 to account for snowblock dust
+ if (air_count == 3)
+ return y - 2;
+ }
+ }
+
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+}
+
+
+void MapgenFractal::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate base terrain, mountains, and ridges with initial heightmaps
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ if (flags & MG_CAVES)
+ generateCaves(stone_surface_max_y, large_cave_depth);
+
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //printf("makeChunk: %dms\n", t.stop());
+
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ if (flags & MG_LIGHT)
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max);
+
+ //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
+ // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
+
+ this->generating = false;
+}
+
+
+bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z)
+{
+ float cx, cy, cz, cw, ox, oy, oz, ow;
+
+ if (julia) { // Julia set
+ cx = julia_x;
+ cy = julia_y;
+ cz = julia_z;
+ cw = julia_w;
+ ox = (float)x / scale.X - offset.X;
+ oy = (float)y / scale.Y - offset.Y;
+ oz = (float)z / scale.Z - offset.Z;
+ ow = slice_w;
+ } else { // Mandelbrot set
+ cx = (float)x / scale.X - offset.X;
+ cy = (float)y / scale.Y - offset.Y;
+ cz = (float)z / scale.Z - offset.Z;
+ cw = slice_w;
+ ox = 0.0f;
+ oy = 0.0f;
+ oz = 0.0f;
+ ow = 0.0f;
+ }
+
+ float nx = 0.0f;
+ float ny = 0.0f;
+ float nz = 0.0f;
+ float nw = 0.0f;
+
+ for (u16 iter = 0; iter < iterations; iter++) {
+ switch (formula) {
+ default:
+ case 1: // 4D "Roundy"
+ nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
+ ny = 2.0f * (ox * oy + oz * ow) + cy;
+ nz = 2.0f * (ox * oz + oy * ow) + cz;
+ nw = 2.0f * (ox * ow + oy * oz) + cw;
+ break;
+ case 2: // 4D "Squarry"
+ nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
+ ny = 2.0f * (ox * oy + oz * ow) + cy;
+ nz = 2.0f * (ox * oz + oy * ow) + cz;
+ nw = 2.0f * (ox * ow - oy * oz) + cw;
+ break;
+ case 3: // 4D "Mandy Cousin"
+ nx = ox * ox - oy * oy - oz * oz + ow * ow + cx;
+ ny = 2.0f * (ox * oy + oz * ow) + cy;
+ nz = 2.0f * (ox * oz + oy * ow) + cz;
+ nw = 2.0f * (ox * ow + oy * oz) + cw;
+ break;
+ case 4: // 4D "Variation"
+ nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
+ ny = 2.0f * (ox * oy + oz * ow) + cy;
+ nz = 2.0f * (ox * oz - oy * ow) + cz;
+ nw = 2.0f * (ox * ow + oy * oz) + cw;
+ break;
+ case 5: // 3D "Mandelbrot/Mandelbar"
+ nx = ox * ox - oy * oy - oz * oz + cx;
+ ny = 2.0f * ox * oy + cy;
+ nz = -2.0f * ox * oz + cz;
+ break;
+ case 6: // 3D "Christmas Tree"
+ // Altering the formula here is necessary to avoid division by zero
+ if (fabs(oz) < 0.000000001f) {
+ nx = ox * ox - oy * oy - oz * oz + cx;
+ ny = 2.0f * oy * ox + cy;
+ nz = 4.0f * oz * ox + cz;
+ } else {
+ float a = (2.0f * ox) / (sqrt(oy * oy + oz * oz));
+ nx = ox * ox - oy * oy - oz * oz + cx;
+ ny = a * (oy * oy - oz * oz) + cy;
+ nz = a * 2.0f * oy * oz + cz;
+ }
+ break;
+ case 7: // 3D "Mandelbulb"
+ if (fabs(oy) < 0.000000001f) {
+ nx = ox * ox - oz * oz + cx;
+ ny = cy;
+ nz = -2.0f * oz * sqrt(ox * ox) + cz;
+ } else {
+ float a = 1.0f - (oz * oz) / (ox * ox + oy * oy);
+ nx = (ox * ox - oy * oy) * a + cx;
+ ny = 2.0f * ox * oy * a + cy;
+ nz = -2.0f * oz * sqrt(ox * ox + oy * oy) + cz;
+ }
+ break;
+ case 8: // 3D "Cosine Mandelbulb"
+ if (fabs(oy) < 0.000000001f) {
+ nx = 2.0f * ox * oz + cx;
+ ny = 4.0f * oy * oz + cy;
+ nz = oz * oz - ox * ox - oy * oy + cz;
+ } else {
+ float a = (2.0f * oz) / sqrt(ox * ox + oy * oy);
+ nx = (ox * ox - oy * oy) * a + cx;
+ ny = 2.0f * ox * oy * a + cy;
+ nz = oz * oz - ox * ox - oy * oy + cz;
+ }
+ break;
+ case 9: // 4D "Mandelbulb"
+ float rxy = sqrt(ox * ox + oy * oy);
+ float rxyz = sqrt(ox * ox + oy * oy + oz * oz);
+ if (fabs(ow) < 0.000000001f && fabs(oz) < 0.000000001f) {
+ nx = (ox * ox - oy * oy) + cx;
+ ny = 2.0f * ox * oy + cy;
+ nz = -2.0f * rxy * oz + cz;
+ nw = 2.0f * rxyz * ow + cw;
+ } else {
+ float a = 1.0f - (ow * ow) / (rxyz * rxyz);
+ float b = a * (1.0f - (oz * oz) / (rxy * rxy));
+ nx = (ox * ox - oy * oy) * b + cx;
+ ny = 2.0f * ox * oy * b + cy;
+ nz = -2.0f * rxy * oz * a + cz;
+ nw = 2.0f * rxyz * ow + cw;
+ }
+ break;
+ }
+
+ if (nx * nx + ny * ny + nz * nz + nw * nw > 4.0f)
+ return false;
+
+ ox = nx;
+ oy = ny;
+ oz = nz;
+ ow = nw;
+ }
+
+ return true;
+}
+
+
+s16 MapgenFractal::generateTerrain()
+{
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_stone(c_stone);
+ MapNode n_water(c_water_source);
+
+ s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 index2d = 0;
+
+ noise_seabed->perlinMap2D(node_min.X, node_min.Z);
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++) {
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X; x++, vi++, index2d++) {
+ if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
+ s16 seabed_height = noise_seabed->result[index2d];
+
+ if (y <= seabed_height || getFractalAtPoint(x, y, z)) {
+ vm->m_data[vi] = n_stone;
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if (y <= water_level) {
+ vm->m_data[vi] = n_water;
+ } else {
+ vm->m_data[vi] = n_air;
+ }
+ }
+ }
+ index2d -= ystride;
+ }
+ index2d += ystride;
+ }
+
+ return stone_surface_max_y;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2015-2017 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+Fractal formulas from http://www.bugman123.com/Hypercomplex/index.html
+by Paul Nylander, and from http://www.fractalforums.com, thank you.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_fractal[];
+
+struct MapgenFractalParams : public MapgenParams
+{
+ u32 spflags = 0;
+ float cave_width = 0.09f;
+ s16 large_cave_depth = -33;
+ s16 lava_depth = -256;
+ u16 fractal = 1;
+ u16 iterations = 11;
+ v3f scale = v3f(4096.0, 1024.0, 4096.0);
+ v3f offset = v3f(1.52, 0.0, 0.0);
+ float slice_w = 0.0f;
+ float julia_x = 0.267f;
+ float julia_y = 0.2f;
+ float julia_z = 0.133f;
+ float julia_w = 0.067f;
+ NoiseParams np_seabed;
+ NoiseParams np_filler_depth;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+
+ MapgenFractalParams();
+ ~MapgenFractalParams() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenFractal : public MapgenBasic
+{
+public:
+ MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge);
+ ~MapgenFractal();
+
+ virtual MapgenType getType() const { return MAPGEN_FRACTAL; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+ bool getFractalAtPoint(s16 x, s16 y, s16 z);
+ s16 generateTerrain();
+
+private:
+ u16 formula;
+ bool julia;
+
+ s16 large_cave_depth;
+ u16 fractal;
+ u16 iterations;
+ v3f scale;
+ v3f offset;
+ float slice_w;
+ float julia_x;
+ float julia_y;
+ float julia_z;
+ float julia_w;
+ Noise *noise_seabed;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mapgen_singlenode.h"
+#include "voxel.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+#include "emerge.h"
+
+
+MapgenSinglenode::MapgenSinglenode(int mapgenid,
+ MapgenParams *params, EmergeManager *emerge)
+ : Mapgen(mapgenid, params, emerge)
+{
+ flags = params->flags;
+
+ INodeDefManager *ndef = emerge->ndef;
+
+ c_node = ndef->getId("mapgen_singlenode");
+ if (c_node == CONTENT_IGNORE)
+ c_node = CONTENT_AIR;
+
+ MapNode n_node(c_node);
+ set_light = (ndef->get(n_node).sunlight_propagates) ? LIGHT_SUN : 0x00;
+}
+
+
+//////////////////////// Map generator
+
+void MapgenSinglenode::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+
+ // Area of central chunk
+ v3s16 node_min = blockpos_min * MAP_BLOCKSIZE;
+ v3s16 node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(node_min, data->seed);
+
+ MapNode n_node(c_node);
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 y = node_min.Y; y <= node_max.Y; y++) {
+ u32 i = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X; x++) {
+ if (vm->m_data[i].getContent() == CONTENT_IGNORE)
+ vm->m_data[i] = n_node;
+ i++;
+ }
+ }
+
+ // Add top and bottom side of water to transforming_liquid queue
+ updateLiquid(&data->transforming_liquid, node_min, node_max);
+
+ // Set lighting
+ if ((flags & MG_LIGHT) && set_light == LIGHT_SUN)
+ setLighting(LIGHT_SUN, node_min, node_max);
+
+ this->generating = false;
+}
+
+
+int MapgenSinglenode::getSpawnLevelAtPoint(v2s16 p)
+{
+ return 0;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+struct MapgenSinglenodeParams : public MapgenParams
+{
+ MapgenSinglenodeParams() = default;
+ ~MapgenSinglenodeParams() = default;
+
+ void readParams(const Settings *settings) {}
+ void writeParams(Settings *settings) const {}
+};
+
+class MapgenSinglenode : public Mapgen
+{
+public:
+ u32 flags;
+ content_t c_node;
+ u8 set_light;
+
+ MapgenSinglenode(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ ~MapgenSinglenode() = default;
+
+ virtual MapgenType getType() const { return MAPGEN_SINGLENODE; }
+
+ void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2017 paramat
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_v5.h"
+
+
+FlagDesc flagdesc_mapgen_v5[] = {
+ {"caverns", MGV5_CAVERNS},
+ {NULL, 0}
+};
+
+
+MapgenV5::MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ cave_width = params->cave_width;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ cavern_limit = params->cavern_limit;
+ cavern_taper = params->cavern_taper;
+ cavern_threshold = params->cavern_threshold;
+
+ // Terrain noise
+ noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
+ noise_factor = new Noise(¶ms->np_factor, seed, csize.X, csize.Z);
+ noise_height = new Noise(¶ms->np_height, seed, csize.X, csize.Z);
+
+ // 3D terrain noise
+ // 1-up 1-down overgeneration
+ noise_ground = new Noise(¶ms->np_ground, seed, csize.X, csize.Y + 2, csize.Z);
+ // 1 down overgeneration
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+}
+
+
+MapgenV5::~MapgenV5()
+{
+ delete noise_filler_depth;
+ delete noise_factor;
+ delete noise_height;
+ delete noise_ground;
+}
+
+
+MapgenV5Params::MapgenV5Params():
+ np_filler_depth (0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0),
+ np_factor (0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0),
+ np_height (0, 10, v3f(250, 250, 250), 84174, 4, 0.5, 2.0),
+ np_ground (0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED),
+ np_cave1 (0, 12, v3f(50, 50, 50), 52534, 4, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(50, 50, 50), 10325, 4, 0.5, 2.0),
+ np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0)
+{
+}
+
+
+void MapgenV5Params::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgv5_spflags", spflags, flagdesc_mapgen_v5);
+ settings->getFloatNoEx("mgv5_cave_width", cave_width);
+ settings->getS16NoEx("mgv5_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgv5_lava_depth", lava_depth);
+ settings->getS16NoEx("mgv5_cavern_limit", cavern_limit);
+ settings->getS16NoEx("mgv5_cavern_taper", cavern_taper);
+ settings->getFloatNoEx("mgv5_cavern_threshold", cavern_threshold);
+
+ settings->getNoiseParams("mgv5_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgv5_np_factor", np_factor);
+ settings->getNoiseParams("mgv5_np_height", np_height);
+ settings->getNoiseParams("mgv5_np_ground", np_ground);
+ settings->getNoiseParams("mgv5_np_cave1", np_cave1);
+ settings->getNoiseParams("mgv5_np_cave2", np_cave2);
+ settings->getNoiseParams("mgv5_np_cavern", np_cavern);
+}
+
+
+void MapgenV5Params::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgv5_spflags", spflags, flagdesc_mapgen_v5, U32_MAX);
+ settings->setFloat("mgv5_cave_width", cave_width);
+ settings->setS16("mgv5_large_cave_depth", large_cave_depth);
+ settings->setS16("mgv5_lava_depth", lava_depth);
+ settings->setS16("mgv5_cavern_limit", cavern_limit);
+ settings->setS16("mgv5_cavern_taper", cavern_taper);
+ settings->setFloat("mgv5_cavern_threshold", cavern_threshold);
+
+ settings->setNoiseParams("mgv5_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgv5_np_factor", np_factor);
+ settings->setNoiseParams("mgv5_np_height", np_height);
+ settings->setNoiseParams("mgv5_np_ground", np_ground);
+ settings->setNoiseParams("mgv5_np_cave1", np_cave1);
+ settings->setNoiseParams("mgv5_np_cave2", np_cave2);
+ settings->setNoiseParams("mgv5_np_cavern", np_cavern);
+}
+
+
+int MapgenV5::getSpawnLevelAtPoint(v2s16 p)
+{
+
+ float f = 0.55 + NoisePerlin2D(&noise_factor->np, p.X, p.Y, seed);
+ if (f < 0.01)
+ f = 0.01;
+ else if (f >= 1.0)
+ f *= 1.6;
+ float h = NoisePerlin2D(&noise_height->np, p.X, p.Y, seed);
+
+ // noise_height 'offset' is the average level of terrain. At least 50% of
+ // terrain will be below this.
+ // Raising the maximum spawn level above 'water_level + 16' is necessary
+ // for when noise_height 'offset' is set much higher than water_level.
+ s16 max_spawn_y = MYMAX(noise_height->np.offset, water_level + 16);
+
+ // Starting spawn search at max_spawn_y + 128 ensures 128 nodes of open
+ // space above spawn position. Avoids spawning in possibly sealed voids.
+ for (s16 y = max_spawn_y + 128; y >= water_level; y--) {
+ float n_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed);
+
+ if (n_ground * f > y - h) { // If solid
+ if (y < water_level || y > max_spawn_y)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ // y + 2 because y is surface and due to biome 'dust' nodes.
+ return y + 2;
+ }
+ }
+ // Unsuitable spawn position, no ground found
+ return MAX_MAP_GENERATION_LIMIT;
+}
+
+
+void MapgenV5::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ // Create a block-specific seed
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate base terrain
+ s16 stone_surface_max_y = generateBaseTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ // Generate caverns, tunnels and classic caves
+ if (flags & MG_CAVES) {
+ bool near_cavern = false;
+ // Generate caverns
+ if (spflags & MGV5_CAVERNS)
+ near_cavern = generateCaverns(stone_surface_max_y);
+ // Generate tunnels and classic caves
+ if (near_cavern)
+ // Disable classic caves in this mapchunk by setting
+ // 'large cave depth' to world base. Avoids excessive liquid in
+ // large caverns and floating blobs of overgenerated liquid.
+ generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
+ else
+ generateCaves(stone_surface_max_y, large_cave_depth);
+ }
+
+ // Generate dungeons and desert temples
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //printf("makeChunk: %dms\n", t.stop());
+
+ // Add top and bottom side of water to transforming_liquid queue
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ // Calculate lighting
+ if (flags & MG_LIGHT) {
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max);
+ }
+
+ this->generating = false;
+}
+
+
+int MapgenV5::generateBaseTerrain()
+{
+ u32 index = 0;
+ u32 index2d = 0;
+ int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+
+ noise_factor->perlinMap2D(node_min.X, node_min.Z);
+ noise_height->perlinMap2D(node_min.X, node_min.Z);
+ noise_ground->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ for (s16 z=node_min.Z; z<=node_max.Z; z++) {
+ for (s16 y=node_min.Y - 1; y<=node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x=node_min.X; x<=node_max.X; x++, vi++, index++, index2d++) {
+ if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
+ continue;
+
+ float f = 0.55 + noise_factor->result[index2d];
+ if (f < 0.01)
+ f = 0.01;
+ else if (f >= 1.0)
+ f *= 1.6;
+ float h = noise_height->result[index2d];
+
+ if (noise_ground->result[index] * f < y - h) {
+ if (y <= water_level)
+ vm->m_data[vi] = MapNode(c_water_source);
+ else
+ vm->m_data[vi] = MapNode(CONTENT_AIR);
+ } else {
+ vm->m_data[vi] = MapNode(c_stone);
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ }
+ }
+ index2d -= ystride;
+ }
+ index2d += ystride;
+ }
+
+ return stone_surface_max_y;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2017 paramat
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+///////// Mapgen V5 flags
+#define MGV5_CAVERNS 0x01
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_v5[];
+
+struct MapgenV5Params : public MapgenParams
+{
+ u32 spflags = MGV5_CAVERNS;
+ float cave_width = 0.125f;
+ s16 large_cave_depth = -256;
+ s16 lava_depth = -256;
+ s16 cavern_limit = -256;
+ s16 cavern_taper = 256;
+ float cavern_threshold = 0.7f;
+
+ NoiseParams np_filler_depth;
+ NoiseParams np_factor;
+ NoiseParams np_height;
+ NoiseParams np_ground;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+ NoiseParams np_cavern;
+
+ MapgenV5Params();
+ ~MapgenV5Params() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenV5 : public MapgenBasic
+{
+public:
+ MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge);
+ ~MapgenV5();
+
+ virtual MapgenType getType() const { return MAPGEN_V5; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+ int generateBaseTerrain();
+
+private:
+ s16 large_cave_depth;
+ Noise *noise_factor;
+ Noise *noise_height;
+ Noise *noise_ground;
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+//#include "serverobject.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "treegen.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_v6.h"
+
+
+FlagDesc flagdesc_mapgen_v6[] = {
+ {"jungles", MGV6_JUNGLES},
+ {"biomeblend", MGV6_BIOMEBLEND},
+ {"mudflow", MGV6_MUDFLOW},
+ {"snowbiomes", MGV6_SNOWBIOMES},
+ {"flat", MGV6_FLAT},
+ {"trees", MGV6_TREES},
+ {NULL, 0}
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+
+MapgenV6::MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge)
+ : Mapgen(mapgenid, params, emerge)
+{
+ m_emerge = emerge;
+ ystride = csize.X; //////fix this
+
+ heightmap = new s16[csize.X * csize.Z];
+
+ spflags = params->spflags;
+ freq_desert = params->freq_desert;
+ freq_beach = params->freq_beach;
+
+ np_cave = ¶ms->np_cave;
+ np_humidity = ¶ms->np_humidity;
+ np_trees = ¶ms->np_trees;
+ np_apple_trees = ¶ms->np_apple_trees;
+
+ //// Create noise objects
+ noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Y);
+ noise_terrain_higher = new Noise(¶ms->np_terrain_higher, seed, csize.X, csize.Y);
+ noise_steepness = new Noise(¶ms->np_steepness, seed, csize.X, csize.Y);
+ noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Y);
+ noise_mud = new Noise(¶ms->np_mud, seed, csize.X, csize.Y);
+ noise_beach = new Noise(¶ms->np_beach, seed, csize.X, csize.Y);
+ noise_biome = new Noise(¶ms->np_biome, seed,
+ csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE);
+ noise_humidity = new Noise(¶ms->np_humidity, seed,
+ csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE);
+
+ //// Resolve nodes to be used
+ INodeDefManager *ndef = emerge->ndef;
+
+ c_stone = ndef->getId("mapgen_stone");
+ c_dirt = ndef->getId("mapgen_dirt");
+ c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass");
+ c_sand = ndef->getId("mapgen_sand");
+ c_water_source = ndef->getId("mapgen_water_source");
+ c_lava_source = ndef->getId("mapgen_lava_source");
+ c_gravel = ndef->getId("mapgen_gravel");
+ c_desert_stone = ndef->getId("mapgen_desert_stone");
+ c_desert_sand = ndef->getId("mapgen_desert_sand");
+ c_dirt_with_snow = ndef->getId("mapgen_dirt_with_snow");
+ c_snow = ndef->getId("mapgen_snow");
+ c_snowblock = ndef->getId("mapgen_snowblock");
+ c_ice = ndef->getId("mapgen_ice");
+
+ if (c_gravel == CONTENT_IGNORE)
+ c_gravel = c_stone;
+ if (c_desert_stone == CONTENT_IGNORE)
+ c_desert_stone = c_stone;
+ if (c_desert_sand == CONTENT_IGNORE)
+ c_desert_sand = c_sand;
+ if (c_dirt_with_snow == CONTENT_IGNORE)
+ c_dirt_with_snow = c_dirt_with_grass;
+ if (c_snow == CONTENT_IGNORE)
+ c_snow = CONTENT_AIR;
+ if (c_snowblock == CONTENT_IGNORE)
+ c_snowblock = c_dirt_with_grass;
+ if (c_ice == CONTENT_IGNORE)
+ c_ice = c_water_source;
+
+ c_cobble = ndef->getId("mapgen_cobble");
+ c_mossycobble = ndef->getId("mapgen_mossycobble");
+ c_stair_cobble = ndef->getId("mapgen_stair_cobble");
+ c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone");
+
+ if (c_mossycobble == CONTENT_IGNORE)
+ c_mossycobble = c_cobble;
+ if (c_stair_cobble == CONTENT_IGNORE)
+ c_stair_cobble = c_cobble;
+ if (c_stair_desert_stone == CONTENT_IGNORE)
+ c_stair_desert_stone = c_desert_stone;
+}
+
+
+MapgenV6::~MapgenV6()
+{
+ delete noise_terrain_base;
+ delete noise_terrain_higher;
+ delete noise_steepness;
+ delete noise_height_select;
+ delete noise_mud;
+ delete noise_beach;
+ delete noise_biome;
+ delete noise_humidity;
+
+ delete[] heightmap;
+}
+
+
+MapgenV6Params::MapgenV6Params():
+ np_terrain_base (-4, 20.0, v3f(250.0, 250.0, 250.0), 82341, 5, 0.6, 2.0),
+ np_terrain_higher (20, 16.0, v3f(500.0, 500.0, 500.0), 85039, 5, 0.6, 2.0),
+ np_steepness (0.85, 0.5, v3f(125.0, 125.0, 125.0), -932, 5, 0.7, 2.0),
+ np_height_select (0, 1.0, v3f(250.0, 250.0, 250.0), 4213, 5, 0.69, 2.0),
+ np_mud (4, 2.0, v3f(200.0, 200.0, 200.0), 91013, 3, 0.55, 2.0),
+ np_beach (0, 1.0, v3f(250.0, 250.0, 250.0), 59420, 3, 0.50, 2.0),
+ np_biome (0, 1.0, v3f(500.0, 500.0, 500.0), 9130, 3, 0.50, 2.0),
+ np_cave (6, 6.0, v3f(250.0, 250.0, 250.0), 34329, 3, 0.50, 2.0),
+ np_humidity (0.5, 0.5, v3f(500.0, 500.0, 500.0), 72384, 3, 0.50, 2.0),
+ np_trees (0, 1.0, v3f(125.0, 125.0, 125.0), 2, 4, 0.66, 2.0),
+ np_apple_trees (0, 1.0, v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0)
+{
+}
+
+
+void MapgenV6Params::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgv6_spflags", spflags, flagdesc_mapgen_v6);
+ settings->getFloatNoEx("mgv6_freq_desert", freq_desert);
+ settings->getFloatNoEx("mgv6_freq_beach", freq_beach);
+
+ settings->getNoiseParams("mgv6_np_terrain_base", np_terrain_base);
+ settings->getNoiseParams("mgv6_np_terrain_higher", np_terrain_higher);
+ settings->getNoiseParams("mgv6_np_steepness", np_steepness);
+ settings->getNoiseParams("mgv6_np_height_select", np_height_select);
+ settings->getNoiseParams("mgv6_np_mud", np_mud);
+ settings->getNoiseParams("mgv6_np_beach", np_beach);
+ settings->getNoiseParams("mgv6_np_biome", np_biome);
+ settings->getNoiseParams("mgv6_np_cave", np_cave);
+ settings->getNoiseParams("mgv6_np_humidity", np_humidity);
+ settings->getNoiseParams("mgv6_np_trees", np_trees);
+ settings->getNoiseParams("mgv6_np_apple_trees", np_apple_trees);
+}
+
+
+void MapgenV6Params::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6, U32_MAX);
+ settings->setFloat("mgv6_freq_desert", freq_desert);
+ settings->setFloat("mgv6_freq_beach", freq_beach);
+
+ settings->setNoiseParams("mgv6_np_terrain_base", np_terrain_base);
+ settings->setNoiseParams("mgv6_np_terrain_higher", np_terrain_higher);
+ settings->setNoiseParams("mgv6_np_steepness", np_steepness);
+ settings->setNoiseParams("mgv6_np_height_select", np_height_select);
+ settings->setNoiseParams("mgv6_np_mud", np_mud);
+ settings->setNoiseParams("mgv6_np_beach", np_beach);
+ settings->setNoiseParams("mgv6_np_biome", np_biome);
+ settings->setNoiseParams("mgv6_np_cave", np_cave);
+ settings->setNoiseParams("mgv6_np_humidity", np_humidity);
+ settings->setNoiseParams("mgv6_np_trees", np_trees);
+ settings->setNoiseParams("mgv6_np_apple_trees", np_apple_trees);
+}
+
+
+//////////////////////// Some helper functions for the map generator
+
+// Returns Y one under area minimum if not found
+s16 MapgenV6::find_stone_level(v2s16 p2d)
+{
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 y_nodes_max = vm->m_area.MaxEdge.Y;
+ s16 y_nodes_min = vm->m_area.MinEdge.Y;
+ u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
+ s16 y;
+
+ for (y = y_nodes_max; y >= y_nodes_min; y--) {
+ content_t c = vm->m_data[i].getContent();
+ if (c != CONTENT_IGNORE && (c == c_stone || c == c_desert_stone))
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+ return (y >= y_nodes_min) ? y : y_nodes_min - 1;
+}
+
+
+// Required by mapgen.h
+bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos)
+{
+ /*s16 minimum_groundlevel = (s16)get_sector_minimum_ground_level(
+ seed, v2s16(blockpos.X, blockpos.Z));*/
+ // Nah, this is just a heuristic, just return something
+ s16 minimum_groundlevel = water_level;
+
+ if(blockpos.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel)
+ return true;
+
+ return false;
+}
+
+
+//////////////////////// Base terrain height functions
+
+float MapgenV6::baseTerrainLevel(float terrain_base, float terrain_higher,
+ float steepness, float height_select)
+{
+ float base = 1 + terrain_base;
+ float higher = 1 + terrain_higher;
+
+ // Limit higher ground level to at least base
+ if(higher < base)
+ higher = base;
+
+ // Steepness factor of cliffs
+ float b = steepness;
+ b = rangelim(b, 0.0, 1000.0);
+ b = 5 * b * b * b * b * b * b * b;
+ b = rangelim(b, 0.5, 1000.0);
+
+ // Values 1.5...100 give quite horrible looking slopes
+ if (b > 1.5 && b < 100.0)
+ b = (b < 10.0) ? 1.5 : 100.0;
+
+ float a_off = -0.20; // Offset to more low
+ float a = 0.5 + b * (a_off + height_select);
+ a = rangelim(a, 0.0, 1.0); // Limit
+
+ return base * (1.0 - a) + higher * a;
+}
+
+
+float MapgenV6::baseTerrainLevelFromNoise(v2s16 p)
+{
+ if (spflags & MGV6_FLAT)
+ return water_level;
+
+ float terrain_base = NoisePerlin2D_PO(&noise_terrain_base->np,
+ p.X, 0.5, p.Y, 0.5, seed);
+ float terrain_higher = NoisePerlin2D_PO(&noise_terrain_higher->np,
+ p.X, 0.5, p.Y, 0.5, seed);
+ float steepness = NoisePerlin2D_PO(&noise_steepness->np,
+ p.X, 0.5, p.Y, 0.5, seed);
+ float height_select = NoisePerlin2D_PO(&noise_height_select->np,
+ p.X, 0.5, p.Y, 0.5, seed);
+
+ return baseTerrainLevel(terrain_base, terrain_higher,
+ steepness, height_select);
+}
+
+
+float MapgenV6::baseTerrainLevelFromMap(v2s16 p)
+{
+ int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
+ return baseTerrainLevelFromMap(index);
+}
+
+
+float MapgenV6::baseTerrainLevelFromMap(int index)
+{
+ if (spflags & MGV6_FLAT)
+ return water_level;
+
+ float terrain_base = noise_terrain_base->result[index];
+ float terrain_higher = noise_terrain_higher->result[index];
+ float steepness = noise_steepness->result[index];
+ float height_select = noise_height_select->result[index];
+
+ return baseTerrainLevel(terrain_base, terrain_higher,
+ steepness, height_select);
+}
+
+
+s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision)
+{
+ return baseTerrainLevelFromNoise(p2d) + MGV6_AVERAGE_MUD_AMOUNT;
+}
+
+
+int MapgenV6::getGroundLevelAtPoint(v2s16 p)
+{
+ return baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
+}
+
+
+int MapgenV6::getSpawnLevelAtPoint(v2s16 p)
+{
+ s16 level_at_point = baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
+ if (level_at_point <= water_level ||
+ level_at_point > water_level + 16)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ return level_at_point;
+}
+
+
+//////////////////////// Noise functions
+
+float MapgenV6::getMudAmount(v2s16 p)
+{
+ int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
+ return getMudAmount(index);
+}
+
+
+bool MapgenV6::getHaveBeach(v2s16 p)
+{
+ int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
+ return getHaveBeach(index);
+}
+
+
+BiomeV6Type MapgenV6::getBiome(v2s16 p)
+{
+ int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE)
+ + (p.X - full_node_min.X);
+ return getBiome(index, p);
+}
+
+
+float MapgenV6::getHumidity(v2s16 p)
+{
+ /*double noise = noise2d_perlin(
+ 0.5+(float)p.X/500, 0.5+(float)p.Y/500,
+ seed+72384, 4, 0.66);
+ noise = (noise + 1.0)/2.0;*/
+
+ int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE)
+ + (p.X - full_node_min.X);
+ float noise = noise_humidity->result[index];
+
+ if (noise < 0.0)
+ noise = 0.0;
+ if (noise > 1.0)
+ noise = 1.0;
+ return noise;
+}
+
+
+float MapgenV6::getTreeAmount(v2s16 p)
+{
+ /*double noise = noise2d_perlin(
+ 0.5+(float)p.X/125, 0.5+(float)p.Y/125,
+ seed+2, 4, 0.66);*/
+
+ float noise = NoisePerlin2D(np_trees, p.X, p.Y, seed);
+ float zeroval = -0.39;
+ if (noise < zeroval)
+ return 0;
+
+ return 0.04 * (noise - zeroval) / (1.0 - zeroval);
+}
+
+
+bool MapgenV6::getHaveAppleTree(v2s16 p)
+{
+ /*is_apple_tree = noise2d_perlin(
+ 0.5+(float)p.X/100, 0.5+(float)p.Z/100,
+ data->seed+342902, 3, 0.45) > 0.2;*/
+
+ float noise = NoisePerlin2D(np_apple_trees, p.X, p.Y, seed);
+
+ return noise > 0.2;
+}
+
+
+float MapgenV6::getMudAmount(int index)
+{
+ if (spflags & MGV6_FLAT)
+ return MGV6_AVERAGE_MUD_AMOUNT;
+
+ /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin(
+ 0.5+(float)p.X/200, 0.5+(float)p.Y/200,
+ seed+91013, 3, 0.55));*/
+
+ return noise_mud->result[index];
+}
+
+
+bool MapgenV6::getHaveBeach(int index)
+{
+ // Determine whether to have sand here
+ /*double sandnoise = noise2d_perlin(
+ 0.2+(float)p2d.X/250, 0.7+(float)p2d.Y/250,
+ seed+59420, 3, 0.50);*/
+
+ float sandnoise = noise_beach->result[index];
+ return (sandnoise > freq_beach);
+}
+
+
+BiomeV6Type MapgenV6::getBiome(int index, v2s16 p)
+{
+ // Just do something very simple as for now
+ /*double d = noise2d_perlin(
+ 0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250,
+ seed+9130, 3, 0.50);*/
+
+ float d = noise_biome->result[index];
+ float h = noise_humidity->result[index];
+
+ if (spflags & MGV6_SNOWBIOMES) {
+ float blend = (spflags & MGV6_BIOMEBLEND) ? noise2d(p.X, p.Y, seed) / 40 : 0;
+
+ if (d > MGV6_FREQ_HOT + blend) {
+ if (h > MGV6_FREQ_JUNGLE + blend)
+ return BT_JUNGLE;
+
+ return BT_DESERT;
+ }
+
+ if (d < MGV6_FREQ_SNOW + blend) {
+ if (h > MGV6_FREQ_TAIGA + blend)
+ return BT_TAIGA;
+
+ return BT_TUNDRA;
+ }
+
+ return BT_NORMAL;
+ }
+
+ if (d > freq_desert)
+ return BT_DESERT;
+
+ if ((spflags & MGV6_BIOMEBLEND) && (d > freq_desert - 0.10) &&
+ ((noise2d(p.X, p.Y, seed) + 1.0) > (freq_desert - d) * 20.0))
+ return BT_DESERT;
+
+ if ((spflags & MGV6_JUNGLES) && h > 0.75)
+ return BT_JUNGLE;
+
+ return BT_NORMAL;
+
+}
+
+
+u32 MapgenV6::get_blockseed(u64 seed, v3s16 p)
+{
+ s32 x = p.X, y = p.Y, z = p.Z;
+ return (u32)(seed % 0x100000000ULL) + z * 38134234 + y * 42123 + x * 23;
+}
+
+
+//////////////////////// Map generator
+
+void MapgenV6::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+
+ // Hack: use minimum block coords for old code that assumes a single block
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+
+ // Area of central chunk
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ // Full allocated area
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ central_area_size = node_max - node_min + v3s16(1, 1, 1);
+ assert(central_area_size.X == central_area_size.Z);
+
+ // Create a block-specific seed
+ blockseed = get_blockseed(data->seed, full_node_min);
+
+ // Make some noise
+ calculateNoise();
+
+ // Maximum height of the stone surface and obstacles.
+ // This is used to guide the cave generation
+ s16 stone_surface_max_y;
+
+ // Generate general ground level to full area
+ stone_surface_max_y = generateGround();
+
+ // Create initial heightmap to limit caves
+ updateHeightmap(node_min, node_max);
+
+ const s16 max_spread_amount = MAP_BLOCKSIZE;
+ // Limit dirt flow area by 1 because mud is flown into neighbors.
+ s16 mudflow_minpos = -max_spread_amount + 1;
+ s16 mudflow_maxpos = central_area_size.X + max_spread_amount - 2;
+
+ // Loop this part, it will make stuff look older and newer nicely
+ const u32 age_loops = 2;
+ for (u32 i_age = 0; i_age < age_loops; i_age++) { // Aging loop
+ // Make caves (this code is relatively horrible)
+ if (flags & MG_CAVES)
+ generateCaves(stone_surface_max_y);
+
+ // Add mud to the central chunk
+ addMud();
+
+ // Flow mud away from steep edges
+ if (spflags & MGV6_MUDFLOW)
+ flowMud(mudflow_minpos, mudflow_maxpos);
+
+ }
+
+ // Update heightmap after mudflow
+ updateHeightmap(node_min, node_max);
+
+ // Add dungeons
+ if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) {
+ DungeonParams dp;
+
+ dp.seed = seed;
+ dp.c_water = c_water_source;
+ dp.c_river_water = c_water_source;
+
+ dp.only_in_ground = true;
+ dp.corridor_len_min = 1;
+ dp.corridor_len_max = 13;
+ dp.rooms_min = 2;
+ dp.rooms_max = 16;
+ dp.y_min = -MAX_MAP_GENERATION_LIMIT;
+ dp.y_max = MAX_MAP_GENERATION_LIMIT;
+
+ dp.np_density
+ = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
+ dp.np_alt_wall
+ = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
+
+ if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
+ dp.c_wall = c_desert_stone;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = c_stair_desert_stone;
+
+ dp.diagonal_dirs = true;
+ dp.holesize = v3s16(2, 3, 2);
+ dp.room_size_min = v3s16(6, 9, 6);
+ dp.room_size_max = v3s16(10, 11, 10);
+ dp.room_size_large_min = v3s16(10, 13, 10);
+ dp.room_size_large_max = v3s16(18, 21, 18);
+ dp.notifytype = GENNOTIFY_TEMPLE;
+ } else {
+ dp.c_wall = c_cobble;
+ dp.c_alt_wall = c_mossycobble;
+ dp.c_stair = c_stair_cobble;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ }
+
+ DungeonGen dgen(ndef, &gennotify, &dp);
+ dgen.generate(vm, blockseed, full_node_min, full_node_max);
+ }
+
+ // Add top and bottom side of water to transforming_liquid queue
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ // Add surface nodes
+ growGrass();
+
+ // Generate some trees, and add grass, if a jungle
+ if (spflags & MGV6_TREES)
+ placeTreesAndJungleGrass();
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Calculate lighting
+ if (flags & MG_LIGHT)
+ calcLighting(node_min - v3s16(1, 1, 1) * MAP_BLOCKSIZE,
+ node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE,
+ full_node_min, full_node_max);
+
+ this->generating = false;
+}
+
+
+void MapgenV6::calculateNoise()
+{
+ int x = node_min.X;
+ int z = node_min.Z;
+ int fx = full_node_min.X;
+ int fz = full_node_min.Z;
+
+ if (!(spflags & MGV6_FLAT)) {
+ noise_terrain_base->perlinMap2D_PO(x, 0.5, z, 0.5);
+ noise_terrain_higher->perlinMap2D_PO(x, 0.5, z, 0.5);
+ noise_steepness->perlinMap2D_PO(x, 0.5, z, 0.5);
+ noise_height_select->perlinMap2D_PO(x, 0.5, z, 0.5);
+ noise_mud->perlinMap2D_PO(x, 0.5, z, 0.5);
+ }
+
+ noise_beach->perlinMap2D_PO(x, 0.2, z, 0.7);
+
+ noise_biome->perlinMap2D_PO(fx, 0.6, fz, 0.2);
+ noise_humidity->perlinMap2D_PO(fx, 0.0, fz, 0.0);
+ // Humidity map does not need range limiting 0 to 1,
+ // only humidity at point does
+}
+
+
+int MapgenV6::generateGround()
+{
+ //TimeTaker timer1("Generating ground level");
+ MapNode n_air(CONTENT_AIR), n_water_source(c_water_source);
+ MapNode n_stone(c_stone), n_desert_stone(c_desert_stone);
+ MapNode n_ice(c_ice);
+ int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+
+ u32 index = 0;
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ // Surface height
+ s16 surface_y = (s16)baseTerrainLevelFromMap(index);
+
+ // Log it
+ if (surface_y > stone_surface_max_y)
+ stone_surface_max_y = surface_y;
+
+ BiomeV6Type bt = getBiome(v2s16(x, z));
+
+ // Fill ground with stone
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 i = vm->m_area.index(x, node_min.Y, z);
+ for (s16 y = node_min.Y; y <= node_max.Y; y++) {
+ if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
+ if (y <= surface_y) {
+ vm->m_data[i] = (y >= MGV6_DESERT_STONE_BASE
+ && bt == BT_DESERT) ?
+ n_desert_stone : n_stone;
+ } else if (y <= water_level) {
+ vm->m_data[i] = (y >= MGV6_ICE_BASE
+ && bt == BT_TUNDRA) ?
+ n_ice : n_water_source;
+ } else {
+ vm->m_data[i] = n_air;
+ }
+ }
+ vm->m_area.add_y(em, i, 1);
+ }
+ }
+
+ return stone_surface_max_y;
+}
+
+
+void MapgenV6::addMud()
+{
+ // 15ms @cs=8
+ //TimeTaker timer1("add mud");
+ MapNode n_dirt(c_dirt), n_gravel(c_gravel);
+ MapNode n_sand(c_sand), n_desert_sand(c_desert_sand);
+ MapNode addnode;
+
+ u32 index = 0;
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ // Randomize mud amount
+ s16 mud_add_amount = getMudAmount(index) / 2.0 + 0.5;
+
+ // Find ground level
+ s16 surface_y = find_stone_level(v2s16(x, z)); /////////////////optimize this!
+
+ // Handle area not found
+ if (surface_y == vm->m_area.MinEdge.Y - 1)
+ continue;
+
+ BiomeV6Type bt = getBiome(v2s16(x, z));
+ addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt;
+
+ if (bt == BT_DESERT && surface_y + mud_add_amount <= water_level + 1) {
+ addnode = n_sand;
+ } else if (mud_add_amount <= 0) {
+ mud_add_amount = 1 - mud_add_amount;
+ addnode = n_gravel;
+ } else if (bt != BT_DESERT && getHaveBeach(index) &&
+ surface_y + mud_add_amount <= water_level + 2) {
+ addnode = n_sand;
+ }
+
+ if ((bt == BT_DESERT || bt == BT_TUNDRA) && surface_y > 20)
+ mud_add_amount = MYMAX(0, mud_add_amount - (surface_y - 20) / 5);
+
+ /* If topmost node is grass, change it to mud. It might be if it was
+ // flown to there from a neighboring chunk and then converted.
+ u32 i = vm->m_area.index(x, surface_y, z);
+ if (vm->m_data[i].getContent() == c_dirt_with_grass)
+ vm->m_data[i] = n_dirt;*/
+
+ // Add mud on ground
+ s16 mudcount = 0;
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 y_start = surface_y + 1;
+ u32 i = vm->m_area.index(x, y_start, z);
+ for (s16 y = y_start; y <= node_max.Y; y++) {
+ if (mudcount >= mud_add_amount)
+ break;
+
+ vm->m_data[i] = addnode;
+ mudcount++;
+
+ vm->m_area.add_y(em, i, 1);
+ }
+ }
+}
+
+
+void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos)
+{
+ // 340ms @cs=8
+ //TimeTaker timer1("flow mud");
+
+ // Iterate a few times
+ for (s16 k = 0; k < 3; k++) {
+ for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++)
+ for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) {
+ // Invert coordinates every 2nd iteration
+ if (k % 2 == 0) {
+ x = mudflow_maxpos - (x - mudflow_minpos);
+ z = mudflow_maxpos - (z - mudflow_minpos);
+ }
+
+ // Node position in 2d
+ v2s16 p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 i = vm->m_area.index(p2d.X, node_max.Y, p2d.Y);
+ s16 y = node_max.Y;
+
+ while (y >= node_min.Y) {
+
+ for (;; y--) {
+ MapNode *n = NULL;
+ // Find mud
+ for (; y >= node_min.Y; y--) {
+ n = &vm->m_data[i];
+ if (n->getContent() == c_dirt ||
+ n->getContent() == c_dirt_with_grass ||
+ n->getContent() == c_gravel)
+ break;
+
+ vm->m_area.add_y(em, i, -1);
+ }
+
+ // Stop if out of area
+ //if(vmanip.m_area.contains(i) == false)
+ if (y < node_min.Y)
+ break;
+
+ if (n->getContent() == c_dirt ||
+ n->getContent() == c_dirt_with_grass) {
+ // Make it exactly mud
+ n->setContent(c_dirt);
+
+ // Don't flow it if the stuff under it is not mud
+ {
+ u32 i2 = i;
+ vm->m_area.add_y(em, i2, -1);
+ // Cancel if out of area
+ if (!vm->m_area.contains(i2))
+ continue;
+ MapNode *n2 = &vm->m_data[i2];
+ if (n2->getContent() != c_dirt &&
+ n2->getContent() != c_dirt_with_grass)
+ continue;
+ }
+ }
+
+ v3s16 dirs4[4] = {
+ v3s16(0, 0, 1), // back
+ v3s16(1, 0, 0), // right
+ v3s16(0, 0, -1), // front
+ v3s16(-1, 0, 0), // left
+ };
+
+ // Check that upper is walkable. Cancel
+ // dropping if upper keeps it in place.
+ u32 i3 = i;
+ vm->m_area.add_y(em, i3, 1);
+ MapNode *n3 = NULL;
+
+ if (vm->m_area.contains(i3)) {
+ n3 = &vm->m_data[i3];
+ if (ndef->get(*n3).walkable)
+ continue;
+ }
+
+ // Drop mud on side
+ for (const v3s16 &dirp : dirs4) {
+ u32 i2 = i;
+ // Move to side
+ vm->m_area.add_p(em, i2, dirp);
+ // Fail if out of area
+ if (!vm->m_area.contains(i2))
+ continue;
+ // Check that side is air
+ MapNode *n2 = &vm->m_data[i2];
+ if (ndef->get(*n2).walkable)
+ continue;
+ // Check that under side is air
+ vm->m_area.add_y(em, i2, -1);
+ if (!vm->m_area.contains(i2))
+ continue;
+ n2 = &vm->m_data[i2];
+ if (ndef->get(*n2).walkable)
+ continue;
+ // Loop further down until not air
+ bool dropped_to_unknown = false;
+ do {
+ vm->m_area.add_y(em, i2, -1);
+ n2 = &vm->m_data[i2];
+ // if out of known area
+ if (!vm->m_area.contains(i2) ||
+ n2->getContent() == CONTENT_IGNORE) {
+ dropped_to_unknown = true;
+ break;
+ }
+ } while (!ndef->get(*n2).walkable);
+ // Loop one up so that we're in air
+ vm->m_area.add_y(em, i2, 1);
+
+ // Move mud to new place. Outside mapchunk remove
+ // any decorations above removed or placed mud.
+ if (!dropped_to_unknown)
+ moveMud(i, i2, i3, p2d, em);
+
+ // Done
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void MapgenV6::moveMud(u32 remove_index, u32 place_index,
+ u32 above_remove_index, v2s16 pos, v3s16 em)
+{
+ MapNode n_air(CONTENT_AIR);
+ // Copy mud from old place to new place
+ vm->m_data[place_index] = vm->m_data[remove_index];
+ // Set old place to be air
+ vm->m_data[remove_index] = n_air;
+ // Outside the mapchunk decorations may need to be removed if above removed
+ // mud or if half-buried in placed mud. Placed mud is to the side of pos so
+ // use 'pos.X >= node_max.X' etc.
+ if (pos.X >= node_max.X || pos.X <= node_min.X ||
+ pos.Y >= node_max.Z || pos.Y <= node_min.Z) {
+ // 'above remove' node is above removed mud. If it is not air, water or
+ // 'ignore' it is a decoration that needs removing. Also search upwards
+ // to remove a possible stacked decoration.
+ // Check for 'ignore' because stacked decorations can penetrate into
+ // 'ignore' nodes above the mapchunk.
+ while (vm->m_area.contains(above_remove_index) &&
+ vm->m_data[above_remove_index].getContent() != CONTENT_AIR &&
+ vm->m_data[above_remove_index].getContent() != c_water_source &&
+ vm->m_data[above_remove_index].getContent() != CONTENT_IGNORE) {
+ vm->m_data[above_remove_index] = n_air;
+ vm->m_area.add_y(em, above_remove_index, 1);
+ }
+ // Mud placed may have partially-buried a stacked decoration, search
+ // above and remove.
+ vm->m_area.add_y(em, place_index, 1);
+ while (vm->m_area.contains(place_index) &&
+ vm->m_data[place_index].getContent() != CONTENT_AIR &&
+ vm->m_data[place_index].getContent() != c_water_source &&
+ vm->m_data[place_index].getContent() != CONTENT_IGNORE) {
+ vm->m_data[place_index] = n_air;
+ vm->m_area.add_y(em, place_index, 1);
+ }
+ }
+}
+
+
+void MapgenV6::placeTreesAndJungleGrass()
+{
+ //TimeTaker t("placeTrees");
+ if (node_max.Y < water_level)
+ return;
+
+ PseudoRandom grassrandom(blockseed + 53);
+ content_t c_junglegrass = ndef->getId("mapgen_junglegrass");
+ // if we don't have junglegrass, don't place cignore... that's bad
+ if (c_junglegrass == CONTENT_IGNORE)
+ c_junglegrass = CONTENT_AIR;
+ MapNode n_junglegrass(c_junglegrass);
+ const v3s16 &em = vm->m_area.getExtent();
+
+ // Divide area into parts
+ s16 div = 8;
+ s16 sidelen = central_area_size.X / div;
+ double area = sidelen * sidelen;
+
+ // N.B. We must add jungle grass first, since tree leaves will
+ // obstruct the ground, giving us a false ground level
+ for (s16 z0 = 0; z0 < div; z0++)
+ for (s16 x0 = 0; x0 < div; x0++) {
+ // Center position of part of division
+ v2s16 p2d_center(
+ node_min.X + sidelen / 2 + sidelen * x0,
+ node_min.Z + sidelen / 2 + sidelen * z0
+ );
+ // Minimum edge of part of division
+ v2s16 p2d_min(
+ node_min.X + sidelen * x0,
+ node_min.Z + sidelen * z0
+ );
+ // Maximum edge of part of division
+ v2s16 p2d_max(
+ node_min.X + sidelen + sidelen * x0 - 1,
+ node_min.Z + sidelen + sidelen * z0 - 1
+ );
+
+ // Get biome at center position of part of division
+ BiomeV6Type bt = getBiome(p2d_center);
+
+ // Amount of trees
+ u32 tree_count;
+ if (bt == BT_JUNGLE || bt == BT_TAIGA || bt == BT_NORMAL) {
+ tree_count = area * getTreeAmount(p2d_center);
+ if (bt == BT_JUNGLE)
+ tree_count *= 4;
+ } else {
+ tree_count = 0;
+ }
+
+ // Add jungle grass
+ if (bt == BT_JUNGLE) {
+ float humidity = getHumidity(p2d_center);
+ u32 grass_count = 5 * humidity * tree_count;
+ for (u32 i = 0; i < grass_count; i++) {
+ s16 x = grassrandom.range(p2d_min.X, p2d_max.X);
+ s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y);
+ int mapindex = central_area_size.X * (z - node_min.Z)
+ + (x - node_min.X);
+ s16 y = heightmap[mapindex];
+ if (y < water_level)
+ continue;
+
+ u32 vi = vm->m_area.index(x, y, z);
+ // place on dirt_with_grass, since we know it is exposed to sunlight
+ if (vm->m_data[vi].getContent() == c_dirt_with_grass) {
+ vm->m_area.add_y(em, vi, 1);
+ vm->m_data[vi] = n_junglegrass;
+ }
+ }
+ }
+
+ // Put trees in random places on part of division
+ for (u32 i = 0; i < tree_count; i++) {
+ s16 x = myrand_range(p2d_min.X, p2d_max.X);
+ s16 z = myrand_range(p2d_min.Y, p2d_max.Y);
+ int mapindex = central_area_size.X * (z - node_min.Z)
+ + (x - node_min.X);
+ s16 y = heightmap[mapindex];
+ // Don't make a tree under water level
+ // Don't make a tree so high that it doesn't fit
+ if (y < water_level || y > node_max.Y - 6)
+ continue;
+
+ v3s16 p(x, y, z);
+ // Trees grow only on mud and grass
+ {
+ u32 i = vm->m_area.index(p);
+ content_t c = vm->m_data[i].getContent();
+ if (c != c_dirt &&
+ c != c_dirt_with_grass &&
+ c != c_dirt_with_snow)
+ continue;
+ }
+ p.Y++;
+
+ // Make a tree
+ if (bt == BT_JUNGLE) {
+ treegen::make_jungletree(*vm, p, ndef, myrand());
+ } else if (bt == BT_TAIGA) {
+ treegen::make_pine_tree(*vm, p - v3s16(0, 1, 0), ndef, myrand());
+ } else if (bt == BT_NORMAL) {
+ bool is_apple_tree = (myrand_range(0, 3) == 0) &&
+ getHaveAppleTree(v2s16(x, z));
+ treegen::make_tree(*vm, p, is_apple_tree, ndef, myrand());
+ }
+ }
+ }
+ //printf("placeTreesAndJungleGrass: %dms\n", t.stop());
+}
+
+
+void MapgenV6::growGrass() // Add surface nodes
+{
+ MapNode n_dirt_with_grass(c_dirt_with_grass);
+ MapNode n_dirt_with_snow(c_dirt_with_snow);
+ MapNode n_snowblock(c_snowblock);
+ MapNode n_snow(c_snow);
+ const v3s16 &em = vm->m_area.getExtent();
+
+ u32 index = 0;
+ for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++)
+ for (s16 x = full_node_min.X; x <= full_node_max.X; x++, index++) {
+ // Find the lowest surface to which enough light ends up to make
+ // grass grow. Basically just wait until not air and not leaves.
+ s16 surface_y = 0;
+ {
+ u32 i = vm->m_area.index(x, node_max.Y, z);
+ s16 y;
+ // Go to ground level
+ for (y = node_max.Y; y >= full_node_min.Y; y--) {
+ MapNode &n = vm->m_data[i];
+ if (ndef->get(n).param_type != CPT_LIGHT ||
+ ndef->get(n).liquid_type != LIQUID_NONE ||
+ n.getContent() == c_ice)
+ break;
+ vm->m_area.add_y(em, i, -1);
+ }
+ surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y;
+ }
+
+ BiomeV6Type bt = getBiome(index, v2s16(x, z));
+ u32 i = vm->m_area.index(x, surface_y, z);
+ content_t c = vm->m_data[i].getContent();
+ if (surface_y >= water_level - 20) {
+ if (bt == BT_TAIGA && c == c_dirt) {
+ vm->m_data[i] = n_dirt_with_snow;
+ } else if (bt == BT_TUNDRA) {
+ if (c == c_dirt) {
+ vm->m_data[i] = n_snowblock;
+ vm->m_area.add_y(em, i, -1);
+ vm->m_data[i] = n_dirt_with_snow;
+ } else if (c == c_stone && surface_y < node_max.Y) {
+ vm->m_area.add_y(em, i, 1);
+ vm->m_data[i] = n_snowblock;
+ }
+ } else if (c == c_dirt) {
+ vm->m_data[i] = n_dirt_with_grass;
+ }
+ }
+ }
+}
+
+
+void MapgenV6::generateCaves(int max_stone_y)
+{
+ float cave_amount = NoisePerlin2D(np_cave, node_min.X, node_min.Y, seed);
+ int volume_nodes = (node_max.X - node_min.X + 1) *
+ (node_max.Y - node_min.Y + 1) * MAP_BLOCKSIZE;
+ cave_amount = MYMAX(0.0, cave_amount);
+ u32 caves_count = cave_amount * volume_nodes / 50000;
+ u32 bruises_count = 1;
+ PseudoRandom ps(blockseed + 21343);
+ PseudoRandom ps2(blockseed + 1032);
+
+ if (ps.range(1, 6) == 1)
+ bruises_count = ps.range(0, ps.range(0, 2));
+
+ if (getBiome(v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
+ caves_count /= 3;
+ bruises_count /= 3;
+ }
+
+ for (u32 i = 0; i < caves_count + bruises_count; i++) {
+ CavesV6 cave(ndef, &gennotify, water_level, c_water_source, c_lava_source);
+
+ bool large_cave = (i >= caves_count);
+ cave.makeCave(vm, node_min, node_max, &ps, &ps2,
+ large_cave, max_stone_y, heightmap);
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+#include "noise.h"
+
+#define MGV6_AVERAGE_MUD_AMOUNT 4
+#define MGV6_DESERT_STONE_BASE -32
+#define MGV6_ICE_BASE 0
+#define MGV6_FREQ_HOT 0.4
+#define MGV6_FREQ_SNOW -0.4
+#define MGV6_FREQ_TAIGA 0.5
+#define MGV6_FREQ_JUNGLE 0.5
+
+//////////// Mapgen V6 flags
+#define MGV6_JUNGLES 0x01
+#define MGV6_BIOMEBLEND 0x02
+#define MGV6_MUDFLOW 0x04
+#define MGV6_SNOWBIOMES 0x08
+#define MGV6_FLAT 0x10
+#define MGV6_TREES 0x20
+
+
+extern FlagDesc flagdesc_mapgen_v6[];
+
+
+enum BiomeV6Type
+{
+ BT_NORMAL,
+ BT_DESERT,
+ BT_JUNGLE,
+ BT_TUNDRA,
+ BT_TAIGA,
+};
+
+
+struct MapgenV6Params : public MapgenParams {
+ u32 spflags = MGV6_JUNGLES | MGV6_SNOWBIOMES | MGV6_TREES |
+ MGV6_BIOMEBLEND | MGV6_MUDFLOW;
+ float freq_desert = 0.45f;
+ float freq_beach = 0.15f;
+ NoiseParams np_terrain_base;
+ NoiseParams np_terrain_higher;
+ NoiseParams np_steepness;
+ NoiseParams np_height_select;
+ NoiseParams np_mud;
+ NoiseParams np_beach;
+ NoiseParams np_biome;
+ NoiseParams np_cave;
+ NoiseParams np_humidity;
+ NoiseParams np_trees;
+ NoiseParams np_apple_trees;
+
+ MapgenV6Params();
+ ~MapgenV6Params() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+
+class MapgenV6 : public Mapgen {
+public:
+ EmergeManager *m_emerge;
+
+ int ystride;
+ u32 spflags;
+
+ v3s16 node_min;
+ v3s16 node_max;
+ v3s16 full_node_min;
+ v3s16 full_node_max;
+ v3s16 central_area_size;
+
+ Noise *noise_terrain_base;
+ Noise *noise_terrain_higher;
+ Noise *noise_steepness;
+ Noise *noise_height_select;
+ Noise *noise_mud;
+ Noise *noise_beach;
+ Noise *noise_biome;
+ Noise *noise_humidity;
+ NoiseParams *np_cave;
+ NoiseParams *np_humidity;
+ NoiseParams *np_trees;
+ NoiseParams *np_apple_trees;
+ float freq_desert;
+ float freq_beach;
+
+ content_t c_stone;
+ content_t c_dirt;
+ content_t c_dirt_with_grass;
+ content_t c_sand;
+ content_t c_water_source;
+ content_t c_lava_source;
+ content_t c_gravel;
+ content_t c_desert_stone;
+ content_t c_desert_sand;
+ content_t c_dirt_with_snow;
+ content_t c_snow;
+ content_t c_snowblock;
+ content_t c_ice;
+
+ content_t c_cobble;
+ content_t c_mossycobble;
+ content_t c_stair_cobble;
+ content_t c_stair_desert_stone;
+
+ MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge);
+ ~MapgenV6();
+
+ virtual MapgenType getType() const { return MAPGEN_V6; }
+
+ void makeChunk(BlockMakeData *data);
+ int getGroundLevelAtPoint(v2s16 p);
+ int getSpawnLevelAtPoint(v2s16 p);
+
+ float baseTerrainLevel(float terrain_base, float terrain_higher,
+ float steepness, float height_select);
+ virtual float baseTerrainLevelFromNoise(v2s16 p);
+ virtual float baseTerrainLevelFromMap(v2s16 p);
+ virtual float baseTerrainLevelFromMap(int index);
+
+ s16 find_stone_level(v2s16 p2d);
+ bool block_is_underground(u64 seed, v3s16 blockpos);
+ s16 find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision);
+
+ float getHumidity(v2s16 p);
+ float getTreeAmount(v2s16 p);
+ bool getHaveAppleTree(v2s16 p);
+ float getMudAmount(v2s16 p);
+ virtual float getMudAmount(int index);
+ bool getHaveBeach(v2s16 p);
+ bool getHaveBeach(int index);
+ BiomeV6Type getBiome(v2s16 p);
+ BiomeV6Type getBiome(int index, v2s16 p);
+
+ u32 get_blockseed(u64 seed, v3s16 p);
+
+ virtual void calculateNoise();
+ int generateGround();
+ void addMud();
+ void flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos);
+ void moveMud(u32 remove_index, u32 place_index,
+ u32 above_remove_index, v2s16 pos, v3s16 em);
+ void growGrass();
+ void placeTreesAndJungleGrass();
+ virtual void generateCaves(int max_stone_y);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "content_sao.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+//#include "profiler.h" // For TimeTaker
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "cavegen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_v7.h"
+
+
+FlagDesc flagdesc_mapgen_v7[] = {
+ {"mountains", MGV7_MOUNTAINS},
+ {"ridges", MGV7_RIDGES},
+ {"floatlands", MGV7_FLOATLANDS},
+ {"caverns", MGV7_CAVERNS},
+ {NULL, 0}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ spflags = params->spflags;
+ mount_zero_level = params->mount_zero_level;
+ cave_width = params->cave_width;
+ large_cave_depth = params->large_cave_depth;
+ lava_depth = params->lava_depth;
+ float_mount_density = params->float_mount_density;
+ floatland_level = params->floatland_level;
+ shadow_limit = params->shadow_limit;
+ cavern_limit = params->cavern_limit;
+ cavern_taper = params->cavern_taper;
+ cavern_threshold = params->cavern_threshold;
+
+ // This is to avoid a divide-by-zero.
+ // Parameter will be saved to map_meta.txt in limited form.
+ params->float_mount_height = MYMAX(params->float_mount_height, 1.0f);
+ float_mount_height = params->float_mount_height;
+
+ // 2D noise
+ noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Z);
+ noise_terrain_alt = new Noise(¶ms->np_terrain_alt, seed, csize.X, csize.Z);
+ noise_terrain_persist = new Noise(¶ms->np_terrain_persist, seed, csize.X, csize.Z);
+ noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Z);
+ noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
+
+ if (spflags & MGV7_MOUNTAINS)
+ noise_mount_height = new Noise(¶ms->np_mount_height, seed, csize.X, csize.Z);
+
+ if (spflags & MGV7_FLOATLANDS) {
+ noise_floatland_base = new Noise(¶ms->np_floatland_base, seed, csize.X, csize.Z);
+ noise_float_base_height = new Noise(¶ms->np_float_base_height, seed, csize.X, csize.Z);
+ }
+
+ if (spflags & MGV7_RIDGES) {
+ noise_ridge_uwater = new Noise(¶ms->np_ridge_uwater, seed, csize.X, csize.Z);
+ // 3D noise, 1-up 1-down overgeneration
+ noise_ridge = new Noise(¶ms->np_ridge, seed, csize.X, csize.Y + 2, csize.Z);
+ }
+ // 3D noise, 1 up, 1 down overgeneration
+ if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS))
+ noise_mountain = new Noise(¶ms->np_mountain, seed, csize.X, csize.Y + 2, csize.Z);
+ // 3D noise, 1 down overgeneration
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+}
+
+
+MapgenV7::~MapgenV7()
+{
+ delete noise_terrain_base;
+ delete noise_terrain_alt;
+ delete noise_terrain_persist;
+ delete noise_height_select;
+ delete noise_filler_depth;
+
+ if (spflags & MGV7_MOUNTAINS)
+ delete noise_mount_height;
+
+ if (spflags & MGV7_FLOATLANDS) {
+ delete noise_floatland_base;
+ delete noise_float_base_height;
+ }
+
+ if (spflags & MGV7_RIDGES) {
+ delete noise_ridge_uwater;
+ delete noise_ridge;
+ }
+
+ if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS))
+ delete noise_mountain;
+}
+
+
+MapgenV7Params::MapgenV7Params():
+ np_terrain_base (4, 70, v3f(600, 600, 600), 82341, 5, 0.6, 2.0),
+ np_terrain_alt (4, 25, v3f(600, 600, 600), 5934, 5, 0.6, 2.0),
+ np_terrain_persist (0.6, 0.1, v3f(2000, 2000, 2000), 539, 3, 0.6, 2.0),
+ np_height_select (-8, 16, v3f(500, 500, 500), 4213, 6, 0.7, 2.0),
+ np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
+ np_mount_height (256, 112, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0),
+ np_ridge_uwater (0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0),
+ np_floatland_base (-0.6, 1.5, v3f(600, 600, 600), 114, 5, 0.6, 2.0),
+ np_float_base_height (48, 24, v3f(300, 300, 300), 907, 4, 0.7, 2.0),
+ np_mountain (-0.6, 1, v3f(250, 350, 250), 5333, 5, 0.63, 2.0),
+ np_ridge (0, 1, v3f(100, 100, 100), 6467, 4, 0.75, 2.0),
+ np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
+{
+}
+
+
+void MapgenV7Params::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7);
+ settings->getS16NoEx("mgv7_mount_zero_level", mount_zero_level);
+ settings->getFloatNoEx("mgv7_cave_width", cave_width);
+ settings->getS16NoEx("mgv7_large_cave_depth", large_cave_depth);
+ settings->getS16NoEx("mgv7_lava_depth", lava_depth);
+ settings->getFloatNoEx("mgv7_float_mount_density", float_mount_density);
+ settings->getFloatNoEx("mgv7_float_mount_height", float_mount_height);
+ settings->getS16NoEx("mgv7_floatland_level", floatland_level);
+ settings->getS16NoEx("mgv7_shadow_limit", shadow_limit);
+ settings->getS16NoEx("mgv7_cavern_limit", cavern_limit);
+ settings->getS16NoEx("mgv7_cavern_taper", cavern_taper);
+ settings->getFloatNoEx("mgv7_cavern_threshold", cavern_threshold);
+
+ settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base);
+ settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt);
+ settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist);
+ settings->getNoiseParams("mgv7_np_height_select", np_height_select);
+ settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgv7_np_mount_height", np_mount_height);
+ settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater);
+ settings->getNoiseParams("mgv7_np_floatland_base", np_floatland_base);
+ settings->getNoiseParams("mgv7_np_float_base_height", np_float_base_height);
+ settings->getNoiseParams("mgv7_np_mountain", np_mountain);
+ settings->getNoiseParams("mgv7_np_ridge", np_ridge);
+ settings->getNoiseParams("mgv7_np_cavern", np_cavern);
+ settings->getNoiseParams("mgv7_np_cave1", np_cave1);
+ settings->getNoiseParams("mgv7_np_cave2", np_cave2);
+}
+
+
+void MapgenV7Params::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7, U32_MAX);
+ settings->setS16("mgv7_mount_zero_level", mount_zero_level);
+ settings->setFloat("mgv7_cave_width", cave_width);
+ settings->setS16("mgv7_large_cave_depth", large_cave_depth);
+ settings->setS16("mgv7_lava_depth", lava_depth);
+ settings->setFloat("mgv7_float_mount_density", float_mount_density);
+ settings->setFloat("mgv7_float_mount_height", float_mount_height);
+ settings->setS16("mgv7_floatland_level", floatland_level);
+ settings->setS16("mgv7_shadow_limit", shadow_limit);
+ settings->setS16("mgv7_cavern_limit", cavern_limit);
+ settings->setS16("mgv7_cavern_taper", cavern_taper);
+ settings->setFloat("mgv7_cavern_threshold", cavern_threshold);
+
+ settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base);
+ settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt);
+ settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist);
+ settings->setNoiseParams("mgv7_np_height_select", np_height_select);
+ settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgv7_np_mount_height", np_mount_height);
+ settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater);
+ settings->setNoiseParams("mgv7_np_floatland_base", np_floatland_base);
+ settings->setNoiseParams("mgv7_np_float_base_height", np_float_base_height);
+ settings->setNoiseParams("mgv7_np_mountain", np_mountain);
+ settings->setNoiseParams("mgv7_np_ridge", np_ridge);
+ settings->setNoiseParams("mgv7_np_cavern", np_cavern);
+ settings->setNoiseParams("mgv7_np_cave1", np_cave1);
+ settings->setNoiseParams("mgv7_np_cave2", np_cave2);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+int MapgenV7::getSpawnLevelAtPoint(v2s16 p)
+{
+ // If rivers are enabled, first check if in a river
+ if (spflags & MGV7_RIDGES) {
+ float width = 0.2;
+ float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2;
+ if (fabs(uwatern) <= width)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+ }
+
+ // Terrain noise 'offset' is the average level of that terrain.
+ // At least 50% of terrain will be below the higher of base and alt terrain
+ // 'offset's.
+ // Raising the maximum spawn level above 'water_level + 16' is necessary
+ // for when terrain 'offset's are set much higher than water_level.
+ s16 max_spawn_y = MYMAX(MYMAX(noise_terrain_alt->np.offset,
+ noise_terrain_base->np.offset),
+ water_level + 16);
+ // Base terrain calculation
+ s16 y = baseTerrainLevelAtPoint(p.X, p.Y);
+
+ // If mountains are disabled, terrain level is base terrain level.
+ // Avoids mid-air spawn where mountain terrain would have been.
+ if (!(spflags & MGV7_MOUNTAINS)) {
+ if (y < water_level || y > max_spawn_y)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ // y + 2 because y is surface level and due to biome 'dust'
+ return y + 2;
+ }
+
+ // Search upwards for first node without mountain terrain
+ int iters = 256;
+ while (iters > 0 && y <= max_spawn_y) {
+ if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) {
+ if (y <= water_level || y > max_spawn_y)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ // y + 1 due to biome 'dust'
+ return y + 1;
+ }
+ y++;
+ iters--;
+ }
+
+ // Unsuitable spawn point
+ return MAX_MAP_GENERATION_LIMIT;
+}
+
+
+void MapgenV7::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate base and mountain terrain
+ // An initial heightmap is no longer created here for use in generateRidgeTerrain()
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Generate rivers
+ if (spflags & MGV7_RIDGES)
+ generateRidgeTerrain();
+
+ // Create heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Init biome generator, place biome-specific nodes, and build biomemap
+ biomegen->calcBiomeNoise(node_min);
+
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ // Generate caverns, tunnels and classic caves
+ if (flags & MG_CAVES) {
+ bool near_cavern = false;
+ // Generate caverns
+ if (spflags & MGV7_CAVERNS)
+ near_cavern = generateCaverns(stone_surface_max_y);
+ // Generate tunnels and classic caves
+ if (near_cavern)
+ // Disable classic caves in this mapchunk by setting
+ // 'large cave depth' to world base. Avoids excessive liquid in
+ // large caverns and floating blobs of overgenerated liquid.
+ generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
+ else
+ generateCaves(stone_surface_max_y, large_cave_depth);
+ }
+
+ // Generate dungeons
+ if (flags & MG_DUNGEONS)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //printf("makeChunk: %dms\n", t.stop());
+
+ // Update liquids
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ // Calculate lighting
+ // Limit floatland shadow
+ bool propagate_shadow = !((spflags & MGV7_FLOATLANDS) &&
+ node_min.Y <= shadow_limit && node_max.Y >= shadow_limit);
+
+ if (flags & MG_LIGHT)
+ calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
+ full_node_min, full_node_max, propagate_shadow);
+
+ //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
+ // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
+
+ this->generating = false;
+}
+
+
+float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z)
+{
+ float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed);
+ hselect = rangelim(hselect, 0.0, 1.0);
+
+ float persist = NoisePerlin2D(&noise_terrain_persist->np, x, z, seed);
+
+ noise_terrain_base->np.persist = persist;
+ float height_base = NoisePerlin2D(&noise_terrain_base->np, x, z, seed);
+
+ noise_terrain_alt->np.persist = persist;
+ float height_alt = NoisePerlin2D(&noise_terrain_alt->np, x, z, seed);
+
+ if (height_alt > height_base)
+ return height_alt;
+
+ return (height_base * hselect) + (height_alt * (1.0 - hselect));
+}
+
+
+float MapgenV7::baseTerrainLevelFromMap(int index)
+{
+ float hselect = rangelim(noise_height_select->result[index], 0.0, 1.0);
+ float height_base = noise_terrain_base->result[index];
+ float height_alt = noise_terrain_alt->result[index];
+
+ if (height_alt > height_base)
+ return height_alt;
+
+ return (height_base * hselect) + (height_alt * (1.0 - hselect));
+}
+
+
+bool MapgenV7::getMountainTerrainAtPoint(s16 x, s16 y, s16 z)
+{
+ float mnt_h_n =
+ MYMAX(NoisePerlin2D(&noise_mount_height->np, x, z, seed), 1.0f);
+ float density_gradient = -((float)(y - mount_zero_level) / mnt_h_n);
+ float mnt_n = NoisePerlin3D(&noise_mountain->np, x, y, z, seed);
+
+ return mnt_n + density_gradient >= 0.0;
+}
+
+
+bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y)
+{
+ float mounthn = MYMAX(noise_mount_height->result[idx_xz], 1.0f);
+ float density_gradient = -((float)(y - mount_zero_level) / mounthn);
+ float mountn = noise_mountain->result[idx_xyz];
+
+ return mountn + density_gradient >= 0.0;
+}
+
+
+bool MapgenV7::getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y)
+{
+ // Make rim 2 nodes thick to match floatland base terrain
+ float density_gradient = (y >= floatland_level) ?
+ -pow((float)(y - floatland_level) / float_mount_height, 0.75f) :
+ -pow((float)(floatland_level - 1 - y) / float_mount_height, 0.75f);
+
+ float floatn = noise_mountain->result[idx_xyz] + float_mount_density;
+
+ return floatn + density_gradient >= 0.0f;
+}
+
+
+void MapgenV7::floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz)
+{
+ // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT
+ s16 base_min = MAX_MAP_GENERATION_LIMIT + 1;
+ s16 base_max = MAX_MAP_GENERATION_LIMIT;
+
+ float n_base = noise_floatland_base->result[idx_xz];
+ if (n_base > 0.0f) {
+ float n_base_height =
+ MYMAX(noise_float_base_height->result[idx_xz], 1.0f);
+ float amp = n_base * n_base_height;
+ float ridge = n_base_height / 3.0f;
+ base_min = floatland_level - amp / 1.5f;
+
+ if (amp > ridge * 2.0f) {
+ // Lake bed
+ base_max = floatland_level - (amp - ridge * 2.0f) / 2.0f;
+ } else {
+ // Hills and ridges
+ float diff = fabs(amp - ridge) / ridge;
+ // Smooth ridges using the 'smoothstep function'
+ float smooth_diff = diff * diff * (3.0f - 2.0f * diff);
+ base_max = floatland_level + ridge - smooth_diff * ridge;
+ }
+ }
+
+ *float_base_min = base_min;
+ *float_base_max = base_max;
+}
+
+
+int MapgenV7::generateTerrain()
+{
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_stone(c_stone);
+ MapNode n_water(c_water_source);
+
+ //// Calculate noise for terrain generation
+ noise_terrain_persist->perlinMap2D(node_min.X, node_min.Z);
+ float *persistmap = noise_terrain_persist->result;
+
+ noise_terrain_base->perlinMap2D(node_min.X, node_min.Z, persistmap);
+ noise_terrain_alt->perlinMap2D(node_min.X, node_min.Z, persistmap);
+ noise_height_select->perlinMap2D(node_min.X, node_min.Z);
+
+ if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) {
+ noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+ }
+
+ if (spflags & MGV7_MOUNTAINS) {
+ noise_mount_height->perlinMap2D(node_min.X, node_min.Z);
+ }
+
+ if (spflags & MGV7_FLOATLANDS) {
+ noise_floatland_base->perlinMap2D(node_min.X, node_min.Z);
+ noise_float_base_height->perlinMap2D(node_min.X, node_min.Z);
+ }
+
+ //// Place nodes
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 index2d = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) {
+ s16 surface_y = baseTerrainLevelFromMap(index2d);
+ if (surface_y > stone_surface_max_y)
+ stone_surface_max_y = surface_y;
+
+ // Get extent of floatland base terrain
+ // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT
+ s16 float_base_min = MAX_MAP_GENERATION_LIMIT + 1;
+ s16 float_base_max = MAX_MAP_GENERATION_LIMIT;
+ if (spflags & MGV7_FLOATLANDS)
+ floatBaseExtentFromMap(&float_base_min, &float_base_max, index2d);
+
+ u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
+ u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
+
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
+ if (y <= surface_y) {
+ vm->m_data[vi] = n_stone; // Base terrain
+ } else if ((spflags & MGV7_MOUNTAINS) &&
+ getMountainTerrainFromMap(index3d, index2d, y)) {
+ vm->m_data[vi] = n_stone; // Mountain terrain
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if ((spflags & MGV7_FLOATLANDS) &&
+ ((y >= float_base_min && y <= float_base_max) ||
+ getFloatlandMountainFromMap(index3d, index2d, y))) {
+ vm->m_data[vi] = n_stone; // Floatland terrain
+ stone_surface_max_y = node_max.Y;
+ } else if (y <= water_level) {
+ vm->m_data[vi] = n_water; // Ground level water
+ } else if ((spflags & MGV7_FLOATLANDS) &&
+ (y >= float_base_max && y <= floatland_level)) {
+ vm->m_data[vi] = n_water; // Floatland water
+ } else {
+ vm->m_data[vi] = n_air;
+ }
+ }
+ vm->m_area.add_y(em, vi, 1);
+ index3d += ystride;
+ }
+ }
+
+ return stone_surface_max_y;
+}
+
+
+void MapgenV7::generateRidgeTerrain()
+{
+ if (node_max.Y < water_level - 16 ||
+ ((spflags & MGV7_FLOATLANDS) && node_max.Y > shadow_limit))
+ return;
+
+ noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+ noise_ridge_uwater->perlinMap2D(node_min.X, node_min.Z);
+
+ MapNode n_water(c_water_source);
+ MapNode n_air(CONTENT_AIR);
+ u32 index = 0;
+ float width = 0.2;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++, vi++) {
+ int j = (z - node_min.Z) * csize.X + (x - node_min.X);
+
+ float uwatern = noise_ridge_uwater->result[j] * 2;
+ if (fabs(uwatern) > width)
+ continue;
+
+ float altitude = y - water_level;
+ float height_mod = (altitude + 17) / 2.5;
+ float width_mod = width - fabs(uwatern);
+ float nridge = noise_ridge->result[index] * MYMAX(altitude, 0) / 7.0;
+
+ if (nridge + width_mod * height_mod < 0.6)
+ continue;
+
+ vm->m_data[vi] = (y > water_level) ? n_air : n_water;
+ }
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Code Boneyard
+////
+//// Much of the stuff here has potential to become useful again at some point
+//// in the future, but we don't want it to get lost or forgotten in version
+//// control.
+////
+
+#if 0
+int MapgenV7::generateMountainTerrain(s16 ymax)
+{
+ MapNode n_stone(c_stone);
+ u32 j = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ u32 vi = vm->m_area.index(node_min.X, y, z);
+ for (s16 x = node_min.X; x <= node_max.X; x++) {
+ int index = (z - node_min.Z) * csize.X + (x - node_min.X);
+ content_t c = vm->m_data[vi].getContent();
+
+ if (getMountainTerrainFromMap(j, index, y)
+ && (c == CONTENT_AIR || c == c_water_source)) {
+ vm->m_data[vi] = n_stone;
+ if (y > ymax)
+ ymax = y;
+ }
+
+ vi++;
+ j++;
+ }
+ }
+
+ return ymax;
+}
+#endif
+
+
+#if 0
+void MapgenV7::carveRivers() {
+ MapNode n_air(CONTENT_AIR), n_water_source(c_water_source);
+ MapNode n_stone(c_stone);
+ u32 index = 0;
+
+ int river_depth = 4;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ float terrain_mod = noise_terrain_mod->result[index];
+ NoiseParams *np = noise_terrain_river->np;
+ np.persist = noise_terrain_persist->result[index];
+ float terrain_river = NoisePerlin2DNoTxfm(np, x, z, seed);
+ float height = terrain_river * (1 - abs(terrain_mod)) *
+ noise_terrain_river->np.scale;
+ height = log(height * height); //log(h^3) is pretty interesting for terrain
+
+ s16 y = heightmap[index];
+ if (height < 1.0 && y > river_depth &&
+ y - river_depth >= node_min.Y && y <= node_max.Y) {
+
+ for (s16 ry = y; ry != y - river_depth; ry--) {
+ u32 vi = vm->m_area.index(x, ry, z);
+ vm->m_data[vi] = n_air;
+ }
+
+ u32 vi = vm->m_area.index(x, y - river_depth, z);
+ vm->m_data[vi] = n_water_source;
+ }
+ }
+}
+#endif
+
+
+#if 0
+void MapgenV7::addTopNodes()
+{
+ v3s16 em = vm->m_area.getExtent();
+ s16 ntopnodes;
+ u32 index = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
+ Biome *biome = bmgr->biomes[biomemap[index]];
+
+ //////////////////// First, add top nodes below the ridge
+ s16 y = ridge_heightmap[index];
+
+ // This cutoff is good enough, but not perfect.
+ // It will cut off potentially placed top nodes at chunk boundaries
+ if (y < node_min.Y)
+ continue;
+ if (y > node_max.Y) {
+ y = node_max.Y; // Let's see if we can still go downward anyway
+ u32 vi = vm->m_area.index(x, y, z);
+ content_t c = vm->m_data[vi].getContent();
+ if (ndef->get(c).walkable)
+ continue;
+ }
+
+ // N.B. It is necessary to search downward since ridge_heightmap[i]
+ // might not be the actual height, just the lowest part in the chunk
+ // where a ridge had been carved
+ u32 i = vm->m_area.index(x, y, z);
+ for (; y >= node_min.Y; y--) {
+ content_t c = vm->m_data[i].getContent();
+ if (ndef->get(c).walkable)
+ break;
+ vm->m_area.add_y(em, i, -1);
+ }
+
+ if (y != node_min.Y - 1 && y >= water_level) {
+ ridge_heightmap[index] = y; //update ridgeheight
+ ntopnodes = biome->top_depth;
+ for (; y <= node_max.Y && ntopnodes; y++) {
+ ntopnodes--;
+ vm->m_data[i] = MapNode(biome->c_top);
+ vm->m_area.add_y(em, i, 1);
+ }
+ // If dirt, grow grass on it.
+ if (y > water_level - 10 &&
+ vm->m_data[i].getContent() == CONTENT_AIR) {
+ vm->m_area.add_y(em, i, -1);
+ if (vm->m_data[i].getContent() == c_dirt)
+ vm->m_data[i] = MapNode(c_dirt_with_grass);
+ }
+ }
+
+ //////////////////// Now, add top nodes on top of the ridge
+ y = heightmap[index];
+ if (y > node_max.Y) {
+ y = node_max.Y; // Let's see if we can still go downward anyway
+ u32 vi = vm->m_area.index(x, y, z);
+ content_t c = vm->m_data[vi].getContent();
+ if (ndef->get(c).walkable)
+ continue;
+ }
+
+ i = vm->m_area.index(x, y, z);
+ for (; y >= node_min.Y; y--) {
+ content_t c = vm->m_data[i].getContent();
+ if (ndef->get(c).walkable)
+ break;
+ vm->m_area.add_y(em, i, -1);
+ }
+
+ if (y != node_min.Y - 1) {
+ ntopnodes = biome->top_depth;
+ // Let's see if we've already added it...
+ if (y == ridge_heightmap[index] + ntopnodes - 1)
+ continue;
+
+ for (; y <= node_max.Y && ntopnodes; y++) {
+ ntopnodes--;
+ vm->m_data[i] = MapNode(biome->c_top);
+ vm->m_area.add_y(em, i, 1);
+ }
+ // If dirt, grow grass on it.
+ if (y > water_level - 10 &&
+ vm->m_data[i].getContent() == CONTENT_AIR) {
+ vm->m_area.add_y(em, i, -1);
+ if (vm->m_data[i].getContent() == c_dirt)
+ vm->m_data[i] = MapNode(c_dirt_with_grass);
+ }
+ }
+ }
+}
+#endif
--- /dev/null
+/*
+Minetest
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+///////////// Mapgen V7 flags
+#define MGV7_MOUNTAINS 0x01
+#define MGV7_RIDGES 0x02
+#define MGV7_FLOATLANDS 0x04
+#define MGV7_CAVERNS 0x08
+#define MGV7_BIOMEREPEAT 0x10 // Now unused
+
+class BiomeManager;
+
+extern FlagDesc flagdesc_mapgen_v7[];
+
+
+struct MapgenV7Params : public MapgenParams {
+ u32 spflags = MGV7_MOUNTAINS | MGV7_RIDGES | MGV7_CAVERNS;
+ s16 mount_zero_level = 0;
+ float cave_width = 0.09f;
+ s16 large_cave_depth = -33;
+ s16 lava_depth = -256;
+ float float_mount_density = 0.6f;
+ float float_mount_height = 128.0f;
+ s16 floatland_level = 1280;
+ s16 shadow_limit = 1024;
+ s16 cavern_limit = -256;
+ s16 cavern_taper = 256;
+ float cavern_threshold = 0.7f;
+
+ NoiseParams np_terrain_base;
+ NoiseParams np_terrain_alt;
+ NoiseParams np_terrain_persist;
+ NoiseParams np_height_select;
+ NoiseParams np_filler_depth;
+ NoiseParams np_mount_height;
+ NoiseParams np_ridge_uwater;
+ NoiseParams np_floatland_base;
+ NoiseParams np_float_base_height;
+ NoiseParams np_mountain;
+ NoiseParams np_ridge;
+ NoiseParams np_cavern;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+
+ MapgenV7Params();
+ ~MapgenV7Params() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+class MapgenV7 : public MapgenBasic {
+public:
+ MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge);
+ ~MapgenV7();
+
+ virtual MapgenType getType() const { return MAPGEN_V7; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+
+ float baseTerrainLevelAtPoint(s16 x, s16 z);
+ float baseTerrainLevelFromMap(int index);
+ bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z);
+ bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y);
+ bool getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y);
+ void floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz);
+
+ int generateTerrain();
+ void generateRidgeTerrain();
+
+private:
+ s16 mount_zero_level;
+ s16 large_cave_depth;
+ float float_mount_density;
+ float float_mount_height;
+ s16 floatland_level;
+ s16 shadow_limit;
+
+ Noise *noise_terrain_base;
+ Noise *noise_terrain_alt;
+ Noise *noise_terrain_persist;
+ Noise *noise_height_select;
+ Noise *noise_mount_height;
+ Noise *noise_ridge_uwater;
+ Noise *noise_floatland_base;
+ Noise *noise_float_base_height;
+ Noise *noise_mountain;
+ Noise *noise_ridge;
+};
--- /dev/null
+/*
+Minetest Valleys C
+Copyright (C) 2016-2017 Duane Robertson <duane@duanerobertson.com>
+Copyright (C) 2016-2017 paramat
+
+Based on Valleys Mapgen by Gael de Sailly
+ (https://forum.minetest.net/viewtopic.php?f=9&t=11430)
+and mapgen_v7, mapgen_flat by kwolekr and paramat.
+
+Licensing changed by permission of Gael de Sailly.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mapgen.h"
+#include "voxel.h"
+#include "noise.h"
+#include "mapblock.h"
+#include "mapnode.h"
+#include "map.h"
+#include "nodedef.h"
+#include "voxelalgorithms.h"
+#include "settings.h" // For g_settings
+#include "emerge.h"
+#include "dungeongen.h"
+#include "mg_biome.h"
+#include "mg_ore.h"
+#include "mg_decoration.h"
+#include "mapgen_valleys.h"
+#include "cavegen.h"
+
+
+//#undef NDEBUG
+//#include "assert.h"
+
+//#include "util/timetaker.h"
+//#include "profiler.h"
+
+
+//static Profiler mapgen_prof;
+//Profiler *mapgen_profiler = &mapgen_prof;
+
+static FlagDesc flagdesc_mapgen_valleys[] = {
+ {"altitude_chill", MGVALLEYS_ALT_CHILL},
+ {"humid_rivers", MGVALLEYS_HUMID_RIVERS},
+ {NULL, 0}
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+MapgenValleys::MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge)
+ : MapgenBasic(mapgenid, params, emerge)
+{
+ // NOTE: MapgenValleys has a hard dependency on BiomeGenOriginal
+ m_bgen = (BiomeGenOriginal *)biomegen;
+
+ BiomeParamsOriginal *bp = (BiomeParamsOriginal *)params->bparams;
+
+ spflags = params->spflags;
+ altitude_chill = params->altitude_chill;
+ large_cave_depth = params->large_cave_depth;
+ lava_features_lim = rangelim(params->lava_features, 0, 10);
+ massive_cave_depth = params->massive_cave_depth;
+ river_depth_bed = params->river_depth + 1.f;
+ river_size_factor = params->river_size / 100.f;
+ water_features_lim = rangelim(params->water_features, 0, 10);
+ cave_width = params->cave_width;
+
+ //// 2D Terrain noise
+ noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
+ noise_inter_valley_slope = new Noise(¶ms->np_inter_valley_slope, seed, csize.X, csize.Z);
+ noise_rivers = new Noise(¶ms->np_rivers, seed, csize.X, csize.Z);
+ noise_terrain_height = new Noise(¶ms->np_terrain_height, seed, csize.X, csize.Z);
+ noise_valley_depth = new Noise(¶ms->np_valley_depth, seed, csize.X, csize.Z);
+ noise_valley_profile = new Noise(¶ms->np_valley_profile, seed, csize.X, csize.Z);
+
+ //// 3D Terrain noise
+ // 1-up 1-down overgeneration
+ noise_inter_valley_fill = new Noise(¶ms->np_inter_valley_fill, seed, csize.X, csize.Y + 2, csize.Z);
+ // 1-down overgeneraion
+ noise_cave1 = new Noise(¶ms->np_cave1, seed, csize.X, csize.Y + 1, csize.Z);
+ noise_cave2 = new Noise(¶ms->np_cave2, seed, csize.X, csize.Y + 1, csize.Z);
+ noise_massive_caves = new Noise(¶ms->np_massive_caves, seed, csize.X, csize.Y + 1, csize.Z);
+
+ humid_rivers = (spflags & MGVALLEYS_HUMID_RIVERS);
+ use_altitude_chill = (spflags & MGVALLEYS_ALT_CHILL);
+ humidity_adjust = bp->np_humidity.offset - 50.f;
+
+ // a small chance of overflows if the settings are very high
+ cave_water_max_height = water_level + MYMAX(0, water_features_lim - 4) * 50;
+ lava_max_height = water_level + MYMAX(0, lava_features_lim - 4) * 50;
+
+ tcave_cache = new float[csize.Y + 2];
+}
+
+
+MapgenValleys::~MapgenValleys()
+{
+ delete noise_cave1;
+ delete noise_cave2;
+ delete noise_filler_depth;
+ delete noise_inter_valley_fill;
+ delete noise_inter_valley_slope;
+ delete noise_rivers;
+ delete noise_massive_caves;
+ delete noise_terrain_height;
+ delete noise_valley_depth;
+ delete noise_valley_profile;
+
+ delete[] tcave_cache;
+}
+
+
+MapgenValleysParams::MapgenValleysParams():
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_filler_depth (0.f, 1.2f, v3f(256, 256, 256), 1605, 3, 0.5f, 2.f),
+ np_inter_valley_fill (0.f, 1.f, v3f(256, 512, 256), 1993, 6, 0.8f, 2.f),
+ np_inter_valley_slope (0.5f, 0.5f, v3f(128, 128, 128), 746, 1, 1.f, 2.f),
+ np_rivers (0.f, 1.f, v3f(256, 256, 256), -6050, 5, 0.6f, 2.f),
+ np_massive_caves (0.f, 1.f, v3f(768, 256, 768), 59033, 6, 0.63f, 2.f),
+ np_terrain_height (-10.f, 50.f, v3f(1024, 1024, 1024), 5202, 6, 0.4f, 2.f),
+ np_valley_depth (5.f, 4.f, v3f(512, 512, 512), -1914, 1, 1.f, 2.f),
+ np_valley_profile (0.6f, 0.5f, v3f(512, 512, 512), 777, 1, 1.f, 2.f)
+{
+}
+
+
+void MapgenValleysParams::readParams(const Settings *settings)
+{
+ settings->getFlagStrNoEx("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys);
+ settings->getU16NoEx("mgvalleys_altitude_chill", altitude_chill);
+ settings->getS16NoEx("mgvalleys_large_cave_depth", large_cave_depth);
+ settings->getU16NoEx("mgvalleys_lava_features", lava_features);
+ settings->getS16NoEx("mgvalleys_massive_cave_depth", massive_cave_depth);
+ settings->getU16NoEx("mgvalleys_river_depth", river_depth);
+ settings->getU16NoEx("mgvalleys_river_size", river_size);
+ settings->getU16NoEx("mgvalleys_water_features", water_features);
+ settings->getFloatNoEx("mgvalleys_cave_width", cave_width);
+
+ settings->getNoiseParams("mgvalleys_np_cave1", np_cave1);
+ settings->getNoiseParams("mgvalleys_np_cave2", np_cave2);
+ settings->getNoiseParams("mgvalleys_np_filler_depth", np_filler_depth);
+ settings->getNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill);
+ settings->getNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope);
+ settings->getNoiseParams("mgvalleys_np_rivers", np_rivers);
+ settings->getNoiseParams("mgvalleys_np_massive_caves", np_massive_caves);
+ settings->getNoiseParams("mgvalleys_np_terrain_height", np_terrain_height);
+ settings->getNoiseParams("mgvalleys_np_valley_depth", np_valley_depth);
+ settings->getNoiseParams("mgvalleys_np_valley_profile", np_valley_profile);
+}
+
+
+void MapgenValleysParams::writeParams(Settings *settings) const
+{
+ settings->setFlagStr("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys, U32_MAX);
+ settings->setU16("mgvalleys_altitude_chill", altitude_chill);
+ settings->setS16("mgvalleys_large_cave_depth", large_cave_depth);
+ settings->setU16("mgvalleys_lava_features", lava_features);
+ settings->setS16("mgvalleys_massive_cave_depth", massive_cave_depth);
+ settings->setU16("mgvalleys_river_depth", river_depth);
+ settings->setU16("mgvalleys_river_size", river_size);
+ settings->setU16("mgvalleys_water_features", water_features);
+ settings->setFloat("mgvalleys_cave_width", cave_width);
+
+ settings->setNoiseParams("mgvalleys_np_cave1", np_cave1);
+ settings->setNoiseParams("mgvalleys_np_cave2", np_cave2);
+ settings->setNoiseParams("mgvalleys_np_filler_depth", np_filler_depth);
+ settings->setNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill);
+ settings->setNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope);
+ settings->setNoiseParams("mgvalleys_np_rivers", np_rivers);
+ settings->setNoiseParams("mgvalleys_np_massive_caves", np_massive_caves);
+ settings->setNoiseParams("mgvalleys_np_terrain_height", np_terrain_height);
+ settings->setNoiseParams("mgvalleys_np_valley_depth", np_valley_depth);
+ settings->setNoiseParams("mgvalleys_np_valley_profile", np_valley_profile);
+}
+
+
+///////////////////////////////////////
+
+
+void MapgenValleys::makeChunk(BlockMakeData *data)
+{
+ // Pre-conditions
+ assert(data->vmanip);
+ assert(data->nodedef);
+ assert(data->blockpos_requested.X >= data->blockpos_min.X &&
+ data->blockpos_requested.Y >= data->blockpos_min.Y &&
+ data->blockpos_requested.Z >= data->blockpos_min.Z);
+ assert(data->blockpos_requested.X <= data->blockpos_max.X &&
+ data->blockpos_requested.Y <= data->blockpos_max.Y &&
+ data->blockpos_requested.Z <= data->blockpos_max.Z);
+
+ this->generating = true;
+ this->vm = data->vmanip;
+ this->ndef = data->nodedef;
+
+ //TimeTaker t("makeChunk");
+
+ v3s16 blockpos_min = data->blockpos_min;
+ v3s16 blockpos_max = data->blockpos_max;
+ node_min = blockpos_min * MAP_BLOCKSIZE;
+ node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+ full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
+ full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
+
+ blockseed = getBlockSeed2(full_node_min, seed);
+
+ // Generate biome noises. Note this must be executed strictly before
+ // generateTerrain, because generateTerrain depends on intermediate
+ // biome-related noises.
+ m_bgen->calcBiomeNoise(node_min);
+
+ // Generate noise maps and base terrain height.
+ // Modify heat and humidity maps.
+ calculateNoise();
+
+ // Generate base terrain with initial heightmaps
+ s16 stone_surface_max_y = generateTerrain();
+
+ // Recalculate heightmap
+ updateHeightmap(node_min, node_max);
+
+ // Place biome-specific nodes and build biomemap
+ MgStoneType mgstone_type;
+ content_t biome_stone;
+ generateBiomes(&mgstone_type, &biome_stone);
+
+ // Cave creation.
+ if (flags & MG_CAVES)
+ generateCaves(stone_surface_max_y, large_cave_depth);
+
+ // Dungeon creation
+ if ((flags & MG_DUNGEONS) && node_max.Y < 50)
+ generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
+
+ // Generate the registered decorations
+ if (flags & MG_DECORATIONS)
+ m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
+
+ // Generate the registered ores
+ m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+
+ // Sprinkle some dust on top after everything else was generated
+ dustTopNodes();
+
+ //TimeTaker tll("liquid_lighting");
+
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+
+ if (flags & MG_LIGHT)
+ calcLighting(
+ node_min - v3s16(0, 1, 0),
+ node_max + v3s16(0, 1, 0),
+ full_node_min,
+ full_node_max);
+
+ //mapgen_profiler->avg("liquid_lighting", tll.stop() / 1000.f);
+ //mapgen_profiler->avg("makeChunk", t.stop() / 1000.f);
+
+ this->generating = false;
+}
+
+
+// Populate the noise tables and do most of the
+// calculation necessary to determine terrain height.
+void MapgenValleys::calculateNoise()
+{
+ //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO);
+
+ int x = node_min.X;
+ int y = node_min.Y - 1;
+ int z = node_min.Z;
+
+ //TimeTaker tcn("actualNoise");
+
+ noise_inter_valley_slope->perlinMap2D(x, z);
+ noise_rivers->perlinMap2D(x, z);
+ noise_terrain_height->perlinMap2D(x, z);
+ noise_valley_depth->perlinMap2D(x, z);
+ noise_valley_profile->perlinMap2D(x, z);
+
+ noise_inter_valley_fill->perlinMap3D(x, y, z);
+
+ //mapgen_profiler->avg("noisemaps", tcn.stop() / 1000.f);
+
+ float heat_offset = 0.f;
+ float humidity_scale = 1.f;
+
+ // Altitude chill tends to reduce the average heat.
+ if (use_altitude_chill)
+ heat_offset = 5.f;
+
+ // River humidity tends to increase the humidity range.
+ if (humid_rivers) {
+ humidity_scale = 0.8f;
+ }
+
+ for (s32 index = 0; index < csize.X * csize.Z; index++) {
+ m_bgen->heatmap[index] += heat_offset;
+ m_bgen->humidmap[index] *= humidity_scale;
+ }
+
+ TerrainNoise tn;
+
+ u32 index = 0;
+ for (tn.z = node_min.Z; tn.z <= node_max.Z; tn.z++)
+ for (tn.x = node_min.X; tn.x <= node_max.X; tn.x++, index++) {
+ // The parameters that we actually need to generate terrain
+ // are passed by address (and the return value).
+ tn.terrain_height = noise_terrain_height->result[index];
+ // River noise is replaced with base terrain, which
+ // is basically the height of the water table.
+ tn.rivers = &noise_rivers->result[index];
+ // Valley depth noise is replaced with the valley
+ // number that represents the height of terrain
+ // over rivers and is used to determine about
+ // how close a river is for humidity calculation.
+ tn.valley = &noise_valley_depth->result[index];
+ tn.valley_profile = noise_valley_profile->result[index];
+ // Slope noise is replaced by the calculated slope
+ // which is used to get terrain height in the slow
+ // method, to create sharper mountains.
+ tn.slope = &noise_inter_valley_slope->result[index];
+ tn.inter_valley_fill = noise_inter_valley_fill->result[index];
+
+ // This is the actual terrain height.
+ float mount = terrainLevelFromNoise(&tn);
+ noise_terrain_height->result[index] = mount;
+ }
+}
+
+
+// This keeps us from having to maintain two similar sets of
+// complicated code to determine ground level.
+float MapgenValleys::terrainLevelFromNoise(TerrainNoise *tn)
+{
+ // The square function changes the behaviour of this noise:
+ // very often small, and sometimes very high.
+ float valley_d = MYSQUARE(*tn->valley);
+
+ // valley_d is here because terrain is generally higher where valleys
+ // are deep (mountains). base represents the height of the
+ // rivers, most of the surface is above.
+ float base = tn->terrain_height + valley_d;
+
+ // "river" represents the distance from the river, in arbitrary units.
+ float river = fabs(*tn->rivers) - river_size_factor;
+
+ // Use the curve of the function 1-exp(-(x/a)^2) to model valleys.
+ // Making "a" vary (0 < a <= 1) changes the shape of the valleys.
+ // Try it with a geometry software !
+ // (here x = "river" and a = valley_profile).
+ // "valley" represents the height of the terrain, from the rivers.
+ {
+ float t = river / tn->valley_profile;
+ *tn->valley = valley_d * (1.f - exp(- MYSQUARE(t)));
+ }
+
+ // approximate height of the terrain at this point
+ float mount = base + *tn->valley;
+
+ *tn->slope *= *tn->valley;
+
+ // Rivers are placed where "river" is negative, so where the original
+ // noise value is close to zero.
+ // Base ground is returned as rivers since it's basically the water table.
+ *tn->rivers = base;
+ if (river < 0.f) {
+ // Use the the function -sqrt(1-x^2) which models a circle.
+ float depth;
+ {
+ float t = river / river_size_factor + 1;
+ depth = (river_depth_bed * sqrt(MYMAX(0, 1.f - MYSQUARE(t))));
+ }
+
+ // base - depth : height of the bottom of the river
+ // water_level - 3 : don't make rivers below 3 nodes under the surface
+ // We use three because that's as low as the swamp biomes go.
+ // There is no logical equivalent to this using rangelim.
+ mount = MYMIN(MYMAX(base - depth, (float)(water_level - 3)), mount);
+
+ // Slope has no influence on rivers.
+ *tn->slope = 0.f;
+ }
+
+ return mount;
+}
+
+
+// This avoids duplicating the code in terrainLevelFromNoise, adding
+// only the final step of terrain generation without a noise map.
+float MapgenValleys::adjustedTerrainLevelFromNoise(TerrainNoise *tn)
+{
+ float mount = terrainLevelFromNoise(tn);
+ s16 y_start = myround(mount);
+
+ for (s16 y = y_start; y <= y_start + 1000; y++) {
+ float fill = NoisePerlin3D(&noise_inter_valley_fill->np, tn->x, y, tn->z, seed);
+
+ if (fill * *tn->slope < y - mount) {
+ mount = MYMAX(y - 1, mount);
+ break;
+ }
+ }
+
+ return mount;
+}
+
+
+int MapgenValleys::getSpawnLevelAtPoint(v2s16 p)
+{
+ // Check to make sure this isn't a request for a location in a river.
+ float rivers = NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed);
+ if (fabs(rivers) < river_size_factor)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
+ if (level_at_point <= water_level ||
+ level_at_point > water_level + 32)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+
+ return level_at_point;
+}
+
+
+float MapgenValleys::terrainLevelAtPoint(s16 x, s16 z)
+{
+ TerrainNoise tn;
+
+ float rivers = NoisePerlin2D(&noise_rivers->np, x, z, seed);
+ float valley = NoisePerlin2D(&noise_valley_depth->np, x, z, seed);
+ float inter_valley_slope = NoisePerlin2D(&noise_inter_valley_slope->np, x, z, seed);
+
+ tn.x = x;
+ tn.z = z;
+ tn.terrain_height = NoisePerlin2D(&noise_terrain_height->np, x, z, seed);
+ tn.rivers = &rivers;
+ tn.valley = &valley;
+ tn.valley_profile = NoisePerlin2D(&noise_valley_profile->np, x, z, seed);
+ tn.slope = &inter_valley_slope;
+ tn.inter_valley_fill = 0.f;
+
+ return adjustedTerrainLevelFromNoise(&tn);
+}
+
+
+int MapgenValleys::generateTerrain()
+{
+ // Raising this reduces the rate of evaporation.
+ static const float evaporation = 300.f;
+ // from the lua
+ static const float humidity_dropoff = 4.f;
+ // constant to convert altitude chill (compatible with lua) to heat
+ static const float alt_to_heat = 20.f;
+ // humidity reduction by altitude
+ static const float alt_to_humid = 10.f;
+
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_river_water(c_river_water_source);
+ MapNode n_stone(c_stone);
+ MapNode n_water(c_water_source);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT;
+ u32 index_2d = 0;
+
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) {
+ float river_y = noise_rivers->result[index_2d];
+ float surface_y = noise_terrain_height->result[index_2d];
+ float slope = noise_inter_valley_slope->result[index_2d];
+ float t_heat = m_bgen->heatmap[index_2d];
+
+ heightmap[index_2d] = -MAX_MAP_GENERATION_LIMIT;
+
+ if (surface_y > surface_max_y)
+ surface_max_y = ceil(surface_y);
+
+ if (humid_rivers) {
+ // Derive heat from (base) altitude. This will be most correct
+ // at rivers, since other surface heights may vary below.
+ if (use_altitude_chill && (surface_y > 0.f || river_y > 0.f))
+ t_heat -= alt_to_heat * MYMAX(surface_y, river_y) / altitude_chill;
+
+ // If humidity is low or heat is high, lower the water table.
+ float delta = m_bgen->humidmap[index_2d] - 50.f;
+ if (delta < 0.f) {
+ float t_evap = (t_heat - 32.f) / evaporation;
+ river_y += delta * MYMAX(t_evap, 0.08f);
+ }
+ }
+
+ u32 index_3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
+ u32 index_data = vm->m_area.index(x, node_min.Y - 1, z);
+
+ // Mapgens concern themselves with stone and water.
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
+ if (vm->m_data[index_data].getContent() == CONTENT_IGNORE) {
+ float fill = noise_inter_valley_fill->result[index_3d];
+ float surface_delta = (float)y - surface_y;
+ bool river = y + 1 < river_y;
+
+ if (slope * fill > surface_delta) {
+ // ground
+ vm->m_data[index_data] = n_stone;
+ if (y > heightmap[index_2d])
+ heightmap[index_2d] = y;
+ if (y > surface_max_y)
+ surface_max_y = y;
+ } else if (y <= water_level) {
+ // sea
+ vm->m_data[index_data] = n_water;
+ } else if (river) {
+ // river
+ vm->m_data[index_data] = n_river_water;
+ } else { // air
+ vm->m_data[index_data] = n_air;
+ }
+ }
+
+ vm->m_area.add_y(em, index_data, 1);
+ index_3d += ystride;
+ }
+
+ if (heightmap[index_2d] == -MAX_MAP_GENERATION_LIMIT) {
+ s16 surface_y_int = myround(surface_y);
+ if (surface_y_int > node_max.Y + 1 || surface_y_int < node_min.Y - 1) {
+ // If surface_y is outside the chunk, it's good enough.
+ heightmap[index_2d] = surface_y_int;
+ } else {
+ // If the ground is outside of this chunk, but surface_y
+ // is within the chunk, give a value outside.
+ heightmap[index_2d] = node_min.Y - 2;
+ }
+ }
+
+ if (humid_rivers) {
+ // Use base ground (water table) in a riverbed, to
+ // avoid an unnatural rise in humidity.
+ float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]);
+ float humid = m_bgen->humidmap[index_2d];
+ float water_depth = (t_alt - river_y) / humidity_dropoff;
+ humid *= 1.f + pow(0.5f, MYMAX(water_depth, 1.f));
+
+ // Reduce humidity with altitude (ignoring riverbeds).
+ // This is similar to the lua version's seawater adjustment,
+ // but doesn't increase the base humidity, which causes
+ // problems with the default biomes.
+ if (t_alt > 0.f)
+ humid -= alt_to_humid * t_alt / altitude_chill;
+
+ m_bgen->humidmap[index_2d] = humid;
+ }
+
+ // Assign the heat adjusted by any changed altitudes.
+ // The altitude will change about half the time.
+ if (use_altitude_chill) {
+ // ground height ignoring riverbeds
+ float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]);
+ if (humid_rivers && heightmap[index_2d] == (s16)myround(surface_y))
+ // The altitude hasn't changed. Use the first result.
+ m_bgen->heatmap[index_2d] = t_heat;
+ else if (t_alt > 0.f)
+ m_bgen->heatmap[index_2d] -= alt_to_heat * t_alt / altitude_chill;
+ }
+ }
+
+ return surface_max_y;
+}
+
+void MapgenValleys::generateCaves(s16 max_stone_y, s16 large_cave_depth)
+{
+ if (max_stone_y < node_min.Y)
+ return;
+
+ noise_cave1->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+ noise_cave2->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ PseudoRandom ps(blockseed + 72202);
+
+ MapNode n_air(CONTENT_AIR);
+ MapNode n_lava(c_lava_source);
+ MapNode n_water(c_river_water_source);
+
+ const v3s16 &em = vm->m_area.getExtent();
+
+ // Cave blend distance near YMIN, YMAX
+ const float massive_cave_blend = 128.f;
+ // noise threshold for massive caves
+ const float massive_cave_threshold = 0.6f;
+ // mct: 1 = small rare caves, 0.5 1/3rd ground volume, 0 = 1/2 ground volume.
+
+ float yblmin = -mapgen_limit + massive_cave_blend * 1.5f;
+ float yblmax = massive_cave_depth - massive_cave_blend * 1.5f;
+ bool made_a_big_one = false;
+
+ // Cache the tcave values as they only vary by altitude.
+ if (node_max.Y <= massive_cave_depth) {
+ noise_massive_caves->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ for (s16 y = node_min.Y - 1; y <= node_max.Y; y++) {
+ float tcave = massive_cave_threshold;
+
+ if (y < yblmin) {
+ float t = (yblmin - y) / massive_cave_blend;
+ tcave += MYSQUARE(t);
+ } else if (y > yblmax) {
+ float t = (y - yblmax) / massive_cave_blend;
+ tcave += MYSQUARE(t);
+ }
+
+ tcave_cache[y - node_min.Y + 1] = tcave;
+ }
+ }
+
+ // lava_depth varies between one and ten as you approach
+ // the bottom of the world.
+ s16 lava_depth = ceil((lava_max_height - node_min.Y + 1) * 10.f / mapgen_limit);
+ // This allows random lava spawns to be less common at the surface.
+ s16 lava_chance = MYCUBE(lava_features_lim) * lava_depth;
+ // water_depth varies between ten and one on the way down.
+ s16 water_depth = ceil((mapgen_limit - abs(node_min.Y) + 1) * 10.f / mapgen_limit);
+ // This allows random water spawns to be more common at the surface.
+ s16 water_chance = MYCUBE(water_features_lim) * water_depth;
+
+ // Reduce the odds of overflows even further.
+ if (node_max.Y > water_level) {
+ lava_chance /= 3;
+ water_chance /= 3;
+ }
+
+ u32 index_2d = 0;
+ for (s16 z = node_min.Z; z <= node_max.Z; z++)
+ for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) {
+ Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index_2d]);
+ bool tunnel_air_above = false;
+ bool is_under_river = false;
+ bool underground = false;
+ u32 index_data = vm->m_area.index(x, node_max.Y, z);
+ u32 index_3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + (x - node_min.X);
+
+ // Dig caves on down loop to check for air above.
+ // Don't excavate the overgenerated stone at node_max.Y + 1,
+ // this creates a 'roof' over the tunnel, preventing light in
+ // tunnels at mapchunk borders when generating mapchunks upwards.
+ // This 'roof' is removed when the mapchunk above is generated.
+ for (s16 y = node_max.Y; y >= node_min.Y - 1; y--,
+ index_3d -= ystride,
+ vm->m_area.add_y(em, index_data, -1)) {
+
+ float terrain = noise_terrain_height->result[index_2d];
+
+ // Saves some time.
+ if (y > terrain + 10)
+ continue;
+
+ if (y < terrain - 40)
+ underground = true;
+
+ // Dig massive caves.
+ if (node_max.Y <= massive_cave_depth
+ && noise_massive_caves->result[index_3d]
+ > tcave_cache[y - node_min.Y + 1]) {
+ vm->m_data[index_data] = n_air;
+ made_a_big_one = true;
+ continue;
+ }
+
+ content_t c = vm->m_data[index_data].getContent();
+ // Detect river water to place riverbed nodes in tunnels
+ if (c == biome->c_river_water)
+ is_under_river = true;
+
+ float d1 = contour(noise_cave1->result[index_3d]);
+ float d2 = contour(noise_cave2->result[index_3d]);
+
+ if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) {
+ // in a tunnel
+ vm->m_data[index_data] = n_air;
+ tunnel_air_above = true;
+ } else if (c == biome->c_filler || c == biome->c_stone) {
+ if (tunnel_air_above) {
+ // at the tunnel floor
+ s16 sr = ps.range(0, 39);
+ u32 j = index_data;
+ vm->m_area.add_y(em, j, 1);
+
+ if (sr > terrain - y) {
+ // Put biome nodes in tunnels near the surface
+ if (is_under_river)
+ vm->m_data[index_data] = MapNode(biome->c_riverbed);
+ else if (underground)
+ vm->m_data[index_data] = MapNode(biome->c_filler);
+ else
+ vm->m_data[index_data] = MapNode(biome->c_top);
+ } else if (sr < 3 && underground) {
+ sr = abs(ps.next());
+ if (lava_features_lim > 0 && y <= lava_max_height
+ && c == biome->c_stone && sr < lava_chance)
+ vm->m_data[j] = n_lava;
+
+ sr -= lava_chance;
+
+ // If sr < 0 then we should have already placed lava --
+ // don't immediately dump water on it.
+ if (water_features_lim > 0 && y <= cave_water_max_height
+ && sr >= 0 && sr < water_chance)
+ vm->m_data[j] = n_water;
+ }
+ }
+
+ tunnel_air_above = false;
+ underground = true;
+ } else {
+ tunnel_air_above = false;
+ }
+ }
+ }
+
+ if (node_max.Y <= large_cave_depth && !made_a_big_one) {
+ u32 bruises_count = ps.range(0, 2);
+ for (u32 i = 0; i < bruises_count; i++) {
+ CavesRandomWalk cave(ndef, &gennotify, seed, water_level,
+ c_water_source, c_lava_source, lava_max_height);
+
+ cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap);
+ }
+ }
+}
--- /dev/null
+/*
+Minetest Valleys C
+Copyright (C) 2016-2017 Duane Robertson <duane@duanerobertson.com>
+Copyright (C) 2016-2017 paramat
+
+Based on Valleys Mapgen by Gael de Sailly
+ (https://forum.minetest.net/viewtopic.php?f=9&t=11430)
+and mapgen_v7 by kwolekr and paramat.
+
+Licensing changed by permission of Gael de Sailly.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "mapgen.h"
+
+////////////// Mapgen Valleys flags
+#define MGVALLEYS_ALT_CHILL 0x01
+#define MGVALLEYS_HUMID_RIVERS 0x02
+
+// Feed only one variable into these.
+#define MYSQUARE(x) (x) * (x)
+#define MYCUBE(x) (x) * (x) * (x)
+
+class BiomeManager;
+class BiomeGenOriginal;
+
+// Global profiler
+//class Profiler;
+//extern Profiler *mapgen_profiler;
+
+
+struct MapgenValleysParams : public MapgenParams {
+ u32 spflags = MGVALLEYS_HUMID_RIVERS | MGVALLEYS_ALT_CHILL;
+ s16 large_cave_depth = -33;
+ s16 massive_cave_depth = -256; // highest altitude of massive caves
+ u16 altitude_chill = 90; // The altitude at which temperature drops by 20C.
+ u16 lava_features = 0; // How often water will occur in caves.
+ u16 river_depth = 4; // How deep to carve river channels.
+ u16 river_size = 5; // How wide to make rivers.
+ u16 water_features = 0; // How often water will occur in caves.
+ float cave_width = 0.09f;
+ NoiseParams np_cave1;
+ NoiseParams np_cave2;
+ NoiseParams np_filler_depth;
+ NoiseParams np_inter_valley_fill;
+ NoiseParams np_inter_valley_slope;
+ NoiseParams np_rivers;
+ NoiseParams np_massive_caves;
+ NoiseParams np_terrain_height;
+ NoiseParams np_valley_depth;
+ NoiseParams np_valley_profile;
+
+ MapgenValleysParams();
+ ~MapgenValleysParams() = default;
+
+ void readParams(const Settings *settings);
+ void writeParams(Settings *settings) const;
+};
+
+struct TerrainNoise {
+ s16 x;
+ s16 z;
+ float terrain_height;
+ float *rivers;
+ float *valley;
+ float valley_profile;
+ float *slope;
+ float inter_valley_fill;
+};
+
+class MapgenValleys : public MapgenBasic {
+public:
+
+ MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge);
+ ~MapgenValleys();
+
+ virtual MapgenType getType() const { return MAPGEN_VALLEYS; }
+
+ virtual void makeChunk(BlockMakeData *data);
+ int getSpawnLevelAtPoint(v2s16 p);
+
+ s16 large_cave_depth;
+
+private:
+ BiomeGenOriginal *m_bgen;
+
+ bool humid_rivers;
+ bool use_altitude_chill;
+ float humidity_adjust;
+ s16 cave_water_max_height;
+ s16 lava_max_height;
+
+ float altitude_chill;
+ s16 lava_features_lim;
+ s16 massive_cave_depth;
+ float river_depth_bed;
+ float river_size_factor;
+ float *tcave_cache;
+ s16 water_features_lim;
+ Noise *noise_inter_valley_fill;
+ Noise *noise_inter_valley_slope;
+ Noise *noise_rivers;
+ Noise *noise_cave1;
+ Noise *noise_cave2;
+ Noise *noise_massive_caves;
+ Noise *noise_terrain_height;
+ Noise *noise_valley_depth;
+ Noise *noise_valley_profile;
+
+ float terrainLevelAtPoint(s16 x, s16 z);
+
+ void calculateNoise();
+
+ virtual int generateTerrain();
+ float terrainLevelFromNoise(TerrainNoise *tn);
+ float adjustedTerrainLevelFromNoise(TerrainNoise *tn);
+
+ virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mg_biome.h"
+#include "mg_decoration.h"
+#include "emerge.h"
+#include "server.h"
+#include "nodedef.h"
+#include "map.h" //for MMVManip
+#include "util/numeric.h"
+#include "porting.h"
+#include "settings.h"
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+BiomeManager::BiomeManager(Server *server) :
+ ObjDefManager(server, OBJDEF_BIOME)
+{
+ m_server = server;
+
+ // Create default biome to be used in case none exist
+ Biome *b = new Biome;
+
+ b->name = "Default";
+ b->flags = 0;
+ b->depth_top = 0;
+ b->depth_filler = -MAX_MAP_GENERATION_LIMIT;
+ b->depth_water_top = 0;
+ b->depth_riverbed = 0;
+ b->y_min = -MAX_MAP_GENERATION_LIMIT;
+ b->y_max = MAX_MAP_GENERATION_LIMIT;
+ b->heat_point = 0.0;
+ b->humidity_point = 0.0;
+
+ b->m_nodenames.emplace_back("mapgen_stone");
+ b->m_nodenames.emplace_back("mapgen_stone");
+ b->m_nodenames.emplace_back("mapgen_stone");
+ b->m_nodenames.emplace_back("mapgen_water_source");
+ b->m_nodenames.emplace_back("mapgen_water_source");
+ b->m_nodenames.emplace_back("mapgen_river_water_source");
+ b->m_nodenames.emplace_back("mapgen_stone");
+ b->m_nodenames.emplace_back("ignore");
+ m_ndef->pendNodeResolve(b);
+
+ add(b);
+}
+
+
+void BiomeManager::clear()
+{
+ EmergeManager *emerge = m_server->getEmergeManager();
+
+ // Remove all dangling references in Decorations
+ DecorationManager *decomgr = emerge->decomgr;
+ for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
+ Decoration *deco = (Decoration *)decomgr->getRaw(i);
+ deco->biomes.clear();
+ }
+
+ // Don't delete the first biome
+ for (size_t i = 1; i < m_objects.size(); i++)
+ delete (Biome *)m_objects[i];
+
+ m_objects.resize(1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+
+void BiomeParamsOriginal::readParams(const Settings *settings)
+{
+ settings->getNoiseParams("mg_biome_np_heat", np_heat);
+ settings->getNoiseParams("mg_biome_np_heat_blend", np_heat_blend);
+ settings->getNoiseParams("mg_biome_np_humidity", np_humidity);
+ settings->getNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend);
+}
+
+
+void BiomeParamsOriginal::writeParams(Settings *settings) const
+{
+ settings->setNoiseParams("mg_biome_np_heat", np_heat);
+ settings->setNoiseParams("mg_biome_np_heat_blend", np_heat_blend);
+ settings->setNoiseParams("mg_biome_np_humidity", np_humidity);
+ settings->setNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr,
+ BiomeParamsOriginal *params, v3s16 chunksize)
+{
+ m_bmgr = biomemgr;
+ m_params = params;
+ m_csize = chunksize;
+
+ noise_heat = new Noise(¶ms->np_heat,
+ params->seed, m_csize.X, m_csize.Z);
+ noise_humidity = new Noise(¶ms->np_humidity,
+ params->seed, m_csize.X, m_csize.Z);
+ noise_heat_blend = new Noise(¶ms->np_heat_blend,
+ params->seed, m_csize.X, m_csize.Z);
+ noise_humidity_blend = new Noise(¶ms->np_humidity_blend,
+ params->seed, m_csize.X, m_csize.Z);
+
+ heatmap = noise_heat->result;
+ humidmap = noise_humidity->result;
+ biomemap = new biome_t[m_csize.X * m_csize.Z];
+}
+
+BiomeGenOriginal::~BiomeGenOriginal()
+{
+ delete []biomemap;
+
+ delete noise_heat;
+ delete noise_humidity;
+ delete noise_heat_blend;
+ delete noise_humidity_blend;
+}
+
+
+Biome *BiomeGenOriginal::calcBiomeAtPoint(v3s16 pos) const
+{
+ float heat =
+ NoisePerlin2D(&m_params->np_heat, pos.X, pos.Z, m_params->seed) +
+ NoisePerlin2D(&m_params->np_heat_blend, pos.X, pos.Z, m_params->seed);
+ float humidity =
+ NoisePerlin2D(&m_params->np_humidity, pos.X, pos.Z, m_params->seed) +
+ NoisePerlin2D(&m_params->np_humidity_blend, pos.X, pos.Z, m_params->seed);
+
+ return calcBiomeFromNoise(heat, humidity, pos.Y);
+}
+
+
+void BiomeGenOriginal::calcBiomeNoise(v3s16 pmin)
+{
+ m_pmin = pmin;
+
+ noise_heat->perlinMap2D(pmin.X, pmin.Z);
+ noise_humidity->perlinMap2D(pmin.X, pmin.Z);
+ noise_heat_blend->perlinMap2D(pmin.X, pmin.Z);
+ noise_humidity_blend->perlinMap2D(pmin.X, pmin.Z);
+
+ for (s32 i = 0; i < m_csize.X * m_csize.Z; i++) {
+ noise_heat->result[i] += noise_heat_blend->result[i];
+ noise_humidity->result[i] += noise_humidity_blend->result[i];
+ }
+}
+
+
+biome_t *BiomeGenOriginal::getBiomes(s16 *heightmap)
+{
+ for (s32 i = 0; i != m_csize.X * m_csize.Z; i++) {
+ Biome *biome = calcBiomeFromNoise(
+ noise_heat->result[i],
+ noise_humidity->result[i],
+ heightmap[i]);
+
+ biomemap[i] = biome->index;
+ }
+
+ return biomemap;
+}
+
+
+Biome *BiomeGenOriginal::getBiomeAtPoint(v3s16 pos) const
+{
+ return getBiomeAtIndex(
+ (pos.Z - m_pmin.Z) * m_csize.X + (pos.X - m_pmin.X),
+ pos.Y);
+}
+
+
+Biome *BiomeGenOriginal::getBiomeAtIndex(size_t index, s16 y) const
+{
+ return calcBiomeFromNoise(
+ noise_heat->result[index],
+ noise_humidity->result[index],
+ y);
+}
+
+
+Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, s16 y) const
+{
+ Biome *b, *biome_closest = NULL;
+ float dist_min = FLT_MAX;
+
+ for (size_t i = 1; i < m_bmgr->getNumObjects(); i++) {
+ b = (Biome *)m_bmgr->getRaw(i);
+ if (!b || y > b->y_max || y < b->y_min)
+ continue;
+
+ float d_heat = heat - b->heat_point;
+ float d_humidity = humidity - b->humidity_point;
+ float dist = (d_heat * d_heat) +
+ (d_humidity * d_humidity);
+ if (dist < dist_min) {
+ dist_min = dist;
+ biome_closest = b;
+ }
+ }
+
+ return biome_closest ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+void Biome::resolveNodeNames()
+{
+ getIdFromNrBacklog(&c_top, "mapgen_stone", CONTENT_AIR);
+ getIdFromNrBacklog(&c_filler, "mapgen_stone", CONTENT_AIR);
+ getIdFromNrBacklog(&c_stone, "mapgen_stone", CONTENT_AIR);
+ getIdFromNrBacklog(&c_water_top, "mapgen_water_source", CONTENT_AIR);
+ getIdFromNrBacklog(&c_water, "mapgen_water_source", CONTENT_AIR);
+ getIdFromNrBacklog(&c_river_water, "mapgen_river_water_source", CONTENT_AIR);
+ getIdFromNrBacklog(&c_riverbed, "mapgen_stone", CONTENT_AIR);
+ getIdFromNrBacklog(&c_dust, "ignore", CONTENT_IGNORE);
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2014-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "objdef.h"
+#include "nodedef.h"
+#include "noise.h"
+
+class Server;
+class Settings;
+class BiomeManager;
+
+////
+//// Biome
+////
+
+typedef u8 biome_t;
+
+#define BIOME_NONE ((biome_t)0)
+
+// TODO(hmmmm): Decide whether this is obsolete or will be used in the future
+enum BiomeType {
+ BIOMETYPE_NORMAL,
+ BIOMETYPE_LIQUID,
+ BIOMETYPE_NETHER,
+ BIOMETYPE_AETHER,
+ BIOMETYPE_FLAT,
+};
+
+class Biome : public ObjDef, public NodeResolver {
+public:
+ u32 flags;
+
+ content_t c_top;
+ content_t c_filler;
+ content_t c_stone;
+ content_t c_water_top;
+ content_t c_water;
+ content_t c_river_water;
+ content_t c_riverbed;
+ content_t c_dust;
+
+ s16 depth_top;
+ s16 depth_filler;
+ s16 depth_water_top;
+ s16 depth_riverbed;
+
+ s16 y_min;
+ s16 y_max;
+ float heat_point;
+ float humidity_point;
+
+ virtual void resolveNodeNames();
+};
+
+
+////
+//// BiomeGen
+////
+
+enum BiomeGenType {
+ BIOMEGEN_ORIGINAL,
+};
+
+struct BiomeParams {
+ virtual void readParams(const Settings *settings) = 0;
+ virtual void writeParams(Settings *settings) const = 0;
+ virtual ~BiomeParams() = default;
+
+ s32 seed;
+};
+
+class BiomeGen {
+public:
+ virtual ~BiomeGen() = default;
+
+ virtual BiomeGenType getType() const = 0;
+
+ // Calculates the biome at the exact position provided. This function can
+ // be called at any time, but may be less efficient than the latter methods,
+ // depending on implementation.
+ virtual Biome *calcBiomeAtPoint(v3s16 pos) const = 0;
+
+ // Computes any intermediate results needed for biome generation. Must be
+ // called before using any of: getBiomes, getBiomeAtPoint, or getBiomeAtIndex.
+ // Calling this invalidates the previous results stored in biomemap.
+ virtual void calcBiomeNoise(v3s16 pmin) = 0;
+
+ // Gets all biomes in current chunk using each corresponding element of
+ // heightmap as the y position, then stores the results by biome index in
+ // biomemap (also returned)
+ virtual biome_t *getBiomes(s16 *heightmap) = 0;
+
+ // Gets a single biome at the specified position, which must be contained
+ // in the region formed by m_pmin and (m_pmin + m_csize - 1).
+ virtual Biome *getBiomeAtPoint(v3s16 pos) const = 0;
+
+ // Same as above, but uses a raw numeric index correlating to the (x,z) position.
+ virtual Biome *getBiomeAtIndex(size_t index, s16 y) const = 0;
+
+ // Result of calcBiomes bulk computation.
+ biome_t *biomemap = nullptr;
+
+protected:
+ BiomeManager *m_bmgr = nullptr;
+ v3s16 m_pmin;
+ v3s16 m_csize;
+};
+
+
+////
+//// BiomeGen implementations
+////
+
+//
+// Original biome algorithm (Whittaker's classification + surface height)
+//
+
+struct BiomeParamsOriginal : public BiomeParams {
+ BiomeParamsOriginal() :
+ np_heat(50, 50, v3f(1000.0, 1000.0, 1000.0), 5349, 3, 0.5, 2.0),
+ np_humidity(50, 50, v3f(1000.0, 1000.0, 1000.0), 842, 3, 0.5, 2.0),
+ np_heat_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 13, 2, 1.0, 2.0),
+ np_humidity_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 90003, 2, 1.0, 2.0)
+ {
+ }
+
+ virtual void readParams(const Settings *settings);
+ virtual void writeParams(Settings *settings) const;
+
+ NoiseParams np_heat;
+ NoiseParams np_humidity;
+ NoiseParams np_heat_blend;
+ NoiseParams np_humidity_blend;
+};
+
+class BiomeGenOriginal : public BiomeGen {
+public:
+ BiomeGenOriginal(BiomeManager *biomemgr,
+ BiomeParamsOriginal *params, v3s16 chunksize);
+ virtual ~BiomeGenOriginal();
+
+ BiomeGenType getType() const { return BIOMEGEN_ORIGINAL; }
+
+ Biome *calcBiomeAtPoint(v3s16 pos) const;
+ void calcBiomeNoise(v3s16 pmin);
+
+ biome_t *getBiomes(s16 *heightmap);
+ Biome *getBiomeAtPoint(v3s16 pos) const;
+ Biome *getBiomeAtIndex(size_t index, s16 y) const;
+
+ Biome *calcBiomeFromNoise(float heat, float humidity, s16 y) const;
+
+ float *heatmap;
+ float *humidmap;
+
+private:
+ BiomeParamsOriginal *m_params;
+
+ Noise *noise_heat;
+ Noise *noise_humidity;
+ Noise *noise_heat_blend;
+ Noise *noise_humidity_blend;
+};
+
+
+////
+//// BiomeManager
+////
+
+class BiomeManager : public ObjDefManager {
+public:
+ BiomeManager(Server *server);
+ virtual ~BiomeManager() = default;
+
+ const char *getObjectTitle() const
+ {
+ return "biome";
+ }
+
+ static Biome *create(BiomeType type)
+ {
+ return new Biome;
+ }
+
+ BiomeGen *createBiomeGen(BiomeGenType type, BiomeParams *params, v3s16 chunksize)
+ {
+ switch (type) {
+ case BIOMEGEN_ORIGINAL:
+ return new BiomeGenOriginal(this,
+ (BiomeParamsOriginal *)params, chunksize);
+ default:
+ return NULL;
+ }
+ }
+
+ static BiomeParams *createBiomeParams(BiomeGenType type)
+ {
+ switch (type) {
+ case BIOMEGEN_ORIGINAL:
+ return new BiomeParamsOriginal;
+ default:
+ return NULL;
+ }
+ }
+
+ virtual void clear();
+
+private:
+ Server *m_server;
+
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mg_decoration.h"
+#include "mg_schematic.h"
+#include "mapgen.h"
+#include "noise.h"
+#include "map.h"
+#include "log.h"
+#include "util/numeric.h"
+#include <algorithm>
+
+
+FlagDesc flagdesc_deco[] = {
+ {"place_center_x", DECO_PLACE_CENTER_X},
+ {"place_center_y", DECO_PLACE_CENTER_Y},
+ {"place_center_z", DECO_PLACE_CENTER_Z},
+ {"force_placement", DECO_FORCE_PLACEMENT},
+ {"liquid_surface", DECO_LIQUID_SURFACE},
+ {"all_floors", DECO_ALL_FLOORS},
+ {"all_ceilings", DECO_ALL_CEILINGS},
+ {NULL, 0}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+DecorationManager::DecorationManager(IGameDef *gamedef) :
+ ObjDefManager(gamedef, OBJDEF_DECORATION)
+{
+}
+
+
+size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed,
+ v3s16 nmin, v3s16 nmax)
+{
+ size_t nplaced = 0;
+
+ for (size_t i = 0; i != m_objects.size(); i++) {
+ Decoration *deco = (Decoration *)m_objects[i];
+ if (!deco)
+ continue;
+
+ nplaced += deco->placeDeco(mg, blockseed, nmin, nmax);
+ blockseed++;
+ }
+
+ return nplaced;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void Decoration::resolveNodeNames()
+{
+ getIdsFromNrBacklog(&c_place_on);
+ getIdsFromNrBacklog(&c_spawnby);
+}
+
+
+bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p)
+{
+ // Check if the decoration can be placed on this node
+ u32 vi = vm->m_area.index(p);
+ if (!CONTAINS(c_place_on, vm->m_data[vi].getContent()))
+ return false;
+
+ // Don't continue if there are no spawnby constraints
+ if (nspawnby == -1)
+ return true;
+
+ int nneighs = 0;
+ static const v3s16 dirs[16] = {
+ v3s16( 0, 0, 1),
+ v3s16( 0, 0, -1),
+ v3s16( 1, 0, 0),
+ v3s16(-1, 0, 0),
+ v3s16( 1, 0, 1),
+ v3s16(-1, 0, 1),
+ v3s16(-1, 0, -1),
+ v3s16( 1, 0, -1),
+
+ v3s16( 0, 1, 1),
+ v3s16( 0, 1, -1),
+ v3s16( 1, 1, 0),
+ v3s16(-1, 1, 0),
+ v3s16( 1, 1, 1),
+ v3s16(-1, 1, 1),
+ v3s16(-1, 1, -1),
+ v3s16( 1, 1, -1)
+ };
+
+ // Check these 16 neighbouring nodes for enough spawnby nodes
+ for (size_t i = 0; i != ARRLEN(dirs); i++) {
+ u32 index = vm->m_area.index(p + dirs[i]);
+ if (!vm->m_area.contains(index))
+ continue;
+
+ if (CONTAINS(c_spawnby, vm->m_data[index].getContent()))
+ nneighs++;
+ }
+
+ if (nneighs < nspawnby)
+ return false;
+
+ return true;
+}
+
+
+size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
+{
+ PcgRandom ps(blockseed + 53);
+ int carea_size = nmax.X - nmin.X + 1;
+
+ // Divide area into parts
+ // If chunksize is changed it may no longer be divisable by sidelen
+ if (carea_size % sidelen)
+ sidelen = carea_size;
+
+ s16 divlen = carea_size / sidelen;
+ int area = sidelen * sidelen;
+
+ for (s16 z0 = 0; z0 < divlen; z0++)
+ for (s16 x0 = 0; x0 < divlen; x0++) {
+ v2s16 p2d_center( // Center position of part of division
+ nmin.X + sidelen / 2 + sidelen * x0,
+ nmin.Z + sidelen / 2 + sidelen * z0
+ );
+ v2s16 p2d_min( // Minimum edge of part of division
+ nmin.X + sidelen * x0,
+ nmin.Z + sidelen * z0
+ );
+ v2s16 p2d_max( // Maximum edge of part of division
+ nmin.X + sidelen + sidelen * x0 - 1,
+ nmin.Z + sidelen + sidelen * z0 - 1
+ );
+
+ // Amount of decorations
+ float nval = (flags & DECO_USE_NOISE) ?
+ NoisePerlin2D(&np, p2d_center.X, p2d_center.Y, mapseed) :
+ fill_ratio;
+ u32 deco_count = 0;
+ float deco_count_f = (float)area * nval;
+ if (deco_count_f >= 1.f) {
+ deco_count = deco_count_f;
+ } else if (deco_count_f > 0.f) {
+ // For low density decorations calculate a chance for 1 decoration
+ if (ps.range(1000) <= deco_count_f * 1000.f)
+ deco_count = 1;
+ }
+
+ for (u32 i = 0; i < deco_count; i++) {
+ s16 x = ps.range(p2d_min.X, p2d_max.X);
+ s16 z = ps.range(p2d_min.Y, p2d_max.Y);
+ int mapindex = carea_size * (z - nmin.Z) + (x - nmin.X);
+
+ if ((flags & DECO_ALL_FLOORS) ||
+ (flags & DECO_ALL_CEILINGS)) {
+ // All-surfaces decorations
+ // Check biome of column
+ if (mg->biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator iter =
+ biomes.find(mg->biomemap[mapindex]);
+ if (iter == biomes.end())
+ continue;
+ }
+
+ // Get all floors and ceilings in node column
+ u16 size = (nmax.Y - nmin.Y + 1) / 2;
+ s16 floors[size];
+ s16 ceilings[size];
+ u16 num_floors = 0;
+ u16 num_ceilings = 0;
+
+ mg->getSurfaces(v2s16(x, z), nmin.Y, nmax.Y,
+ floors, ceilings, &num_floors, &num_ceilings);
+
+ if ((flags & DECO_ALL_FLOORS) && num_floors > 0) {
+ // Floor decorations
+ for (u16 fi = 0; fi < num_floors; fi++) {
+ s16 y = floors[fi];
+ if (y < y_min || y > y_max)
+ continue;
+
+ v3s16 pos(x, y, z);
+ if (generate(mg->vm, &ps, pos, false))
+ mg->gennotify.addEvent(
+ GENNOTIFY_DECORATION, pos, index);
+ }
+ }
+
+ if ((flags & DECO_ALL_CEILINGS) && num_ceilings > 0) {
+ // Ceiling decorations
+ for (u16 ci = 0; ci < num_ceilings; ci++) {
+ s16 y = ceilings[ci];
+ if (y < y_min || y > y_max)
+ continue;
+
+ v3s16 pos(x, y, z);
+ if (generate(mg->vm, &ps, pos, true))
+ mg->gennotify.addEvent(
+ GENNOTIFY_DECORATION, pos, index);
+ }
+ }
+ } else { // Heightmap decorations
+ s16 y = -MAX_MAP_GENERATION_LIMIT;
+ if (flags & DECO_LIQUID_SURFACE)
+ y = mg->findLiquidSurface(v2s16(x, z), nmin.Y, nmax.Y);
+ else if (mg->heightmap)
+ y = mg->heightmap[mapindex];
+ else
+ y = mg->findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y);
+
+ if (y < y_min || y > y_max || y < nmin.Y || y > nmax.Y)
+ continue;
+
+ if (mg->biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator iter =
+ biomes.find(mg->biomemap[mapindex]);
+ if (iter == biomes.end())
+ continue;
+ }
+
+ v3s16 pos(x, y, z);
+ if (generate(mg->vm, &ps, pos, false))
+ mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, index);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void DecoSimple::resolveNodeNames()
+{
+ Decoration::resolveNodeNames();
+ getIdsFromNrBacklog(&c_decos);
+}
+
+
+size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
+{
+ // Don't bother if there aren't any decorations to place
+ if (c_decos.empty())
+ return 0;
+
+ if (!canPlaceDecoration(vm, p))
+ return 0;
+
+ // Check for placement outside the voxelmanip volume
+ if (ceiling) {
+ // Ceiling decorations
+ // 'place offset y' is inverted
+ if (p.Y - place_offset_y - std::max(deco_height, deco_height_max) <
+ vm->m_area.MinEdge.Y)
+ return 0;
+
+ if (p.Y - 1 - place_offset_y > vm->m_area.MaxEdge.Y)
+ return 0;
+
+ } else { // Heightmap and floor decorations
+ if (p.Y + place_offset_y + std::max(deco_height, deco_height_max) >
+ vm->m_area.MaxEdge.Y)
+ return 0;
+
+ if (p.Y + 1 + place_offset_y < vm->m_area.MinEdge.Y)
+ return 0;
+ }
+
+ content_t c_place = c_decos[pr->range(0, c_decos.size() - 1)];
+ s16 height = (deco_height_max > 0) ?
+ pr->range(deco_height, deco_height_max) : deco_height;
+ u8 param2 = (deco_param2_max > 0) ?
+ pr->range(deco_param2, deco_param2_max) : deco_param2;
+ bool force_placement = (flags & DECO_FORCE_PLACEMENT);
+
+ const v3s16 &em = vm->m_area.getExtent();
+ u32 vi = vm->m_area.index(p);
+
+ if (ceiling) {
+ // Ceiling decorations
+ // 'place offset y' is inverted
+ vm->m_area.add_y(em, vi, -place_offset_y);
+
+ for (int i = 0; i < height; i++) {
+ vm->m_area.add_y(em, vi, -1);
+ content_t c = vm->m_data[vi].getContent();
+ if (c != CONTENT_AIR && c != CONTENT_IGNORE && !force_placement)
+ break;
+
+ vm->m_data[vi] = MapNode(c_place, 0, param2);
+ }
+ } else { // Heightmap and floor decorations
+ vm->m_area.add_y(em, vi, place_offset_y);
+
+ for (int i = 0; i < height; i++) {
+ vm->m_area.add_y(em, vi, 1);
+ content_t c = vm->m_data[vi].getContent();
+ if (c != CONTENT_AIR && c != CONTENT_IGNORE && !force_placement)
+ break;
+
+ vm->m_data[vi] = MapNode(c_place, 0, param2);
+ }
+ }
+
+ return 1;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
+{
+ // Schematic could have been unloaded but not the decoration
+ // In this case generate() does nothing (but doesn't *fail*)
+ if (schematic == NULL)
+ return 0;
+
+ if (!canPlaceDecoration(vm, p))
+ return 0;
+
+ if (flags & DECO_PLACE_CENTER_Y) {
+ p.Y -= (schematic->size.Y - 1) / 2;
+ } else {
+ // Only apply 'place offset y' if not 'deco place center y'
+ if (ceiling)
+ // Shift down so that schematic top layer is level with ceiling
+ // 'place offset y' is inverted
+ p.Y -= (place_offset_y + schematic->size.Y - 1);
+ else
+ p.Y += place_offset_y;
+ }
+
+ // Check schematic top and base are in voxelmanip
+ if (p.Y + schematic->size.Y - 1 > vm->m_area.MaxEdge.Y)
+ return 0;
+
+ if (p.Y < vm->m_area.MinEdge.Y)
+ return 0;
+
+ if (flags & DECO_PLACE_CENTER_X)
+ p.X -= (schematic->size.X - 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Z)
+ p.Z -= (schematic->size.Z - 1) / 2;
+
+ Rotation rot = (rotation == ROTATE_RAND) ?
+ (Rotation)pr->range(ROTATE_0, ROTATE_270) : rotation;
+ bool force_placement = (flags & DECO_FORCE_PLACEMENT);
+
+ schematic->blitToVManip(vm, p, rot, force_placement);
+
+ return 1;
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <unordered_set>
+#include "objdef.h"
+#include "noise.h"
+#include "nodedef.h"
+
+class Mapgen;
+class MMVManip;
+class PcgRandom;
+class Schematic;
+
+enum DecorationType {
+ DECO_SIMPLE,
+ DECO_SCHEMATIC,
+ DECO_LSYSTEM
+};
+
+#define DECO_PLACE_CENTER_X 0x01
+#define DECO_PLACE_CENTER_Y 0x02
+#define DECO_PLACE_CENTER_Z 0x04
+#define DECO_USE_NOISE 0x08
+#define DECO_FORCE_PLACEMENT 0x10
+#define DECO_LIQUID_SURFACE 0x20
+#define DECO_ALL_FLOORS 0x40
+#define DECO_ALL_CEILINGS 0x80
+
+extern FlagDesc flagdesc_deco[];
+
+
+class Decoration : public ObjDef, public NodeResolver {
+public:
+ Decoration() = default;
+ virtual ~Decoration() = default;
+
+ virtual void resolveNodeNames();
+
+ bool canPlaceDecoration(MMVManip *vm, v3s16 p);
+ size_t placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+
+ virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) = 0;
+
+ u32 flags = 0;
+ int mapseed = 0;
+ std::vector<content_t> c_place_on;
+ s16 sidelen = 1;
+ s16 y_min;
+ s16 y_max;
+ float fill_ratio = 0.0f;
+ NoiseParams np;
+ std::vector<content_t> c_spawnby;
+ s16 nspawnby;
+ s16 place_offset_y = 0;
+
+ std::unordered_set<u8> biomes;
+};
+
+
+class DecoSimple : public Decoration {
+public:
+ virtual void resolveNodeNames();
+ virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling);
+
+ std::vector<content_t> c_decos;
+ s16 deco_height;
+ s16 deco_height_max;
+ u8 deco_param2;
+ u8 deco_param2_max;
+};
+
+
+class DecoSchematic : public Decoration {
+public:
+ DecoSchematic() = default;
+
+ virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling);
+
+ Rotation rotation;
+ Schematic *schematic = nullptr;
+};
+
+
+/*
+class DecoLSystem : public Decoration {
+public:
+ virtual void generate(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+};
+*/
+
+
+class DecorationManager : public ObjDefManager {
+public:
+ DecorationManager(IGameDef *gamedef);
+ virtual ~DecorationManager() = default;
+
+ const char *getObjectTitle() const
+ {
+ return "decoration";
+ }
+
+ static Decoration *create(DecorationType type)
+ {
+ switch (type) {
+ case DECO_SIMPLE:
+ return new DecoSimple;
+ case DECO_SCHEMATIC:
+ return new DecoSchematic;
+ //case DECO_LSYSTEM:
+ // return new DecoLSystem;
+ default:
+ return NULL;
+ }
+ }
+
+ size_t placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "mg_ore.h"
+#include "mapgen.h"
+#include "noise.h"
+#include "map.h"
+#include "log.h"
+#include "util/numeric.h"
+#include <algorithm>
+
+
+FlagDesc flagdesc_ore[] = {
+ {"absheight", OREFLAG_ABSHEIGHT}, // Non-functional
+ {"puff_cliffs", OREFLAG_PUFF_CLIFFS},
+ {"puff_additive_composition", OREFLAG_PUFF_ADDITIVE},
+ {NULL, 0}
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+OreManager::OreManager(IGameDef *gamedef) :
+ ObjDefManager(gamedef, OBJDEF_ORE)
+{
+}
+
+
+size_t OreManager::placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
+{
+ size_t nplaced = 0;
+
+ for (size_t i = 0; i != m_objects.size(); i++) {
+ Ore *ore = (Ore *)m_objects[i];
+ if (!ore)
+ continue;
+
+ nplaced += ore->placeOre(mg, blockseed, nmin, nmax);
+ blockseed++;
+ }
+
+ return nplaced;
+}
+
+
+void OreManager::clear()
+{
+ for (ObjDef *object : m_objects) {
+ Ore *ore = (Ore *) object;
+ delete ore;
+ }
+ m_objects.clear();
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+Ore::~Ore()
+{
+ delete noise;
+}
+
+
+void Ore::resolveNodeNames()
+{
+ getIdFromNrBacklog(&c_ore, "", CONTENT_AIR);
+ getIdsFromNrBacklog(&c_wherein);
+}
+
+
+size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
+{
+ if (nmin.Y > y_max || nmax.Y < y_min)
+ return 0;
+
+ int actual_ymin = MYMAX(nmin.Y, y_min);
+ int actual_ymax = MYMIN(nmax.Y, y_max);
+ if (clust_size >= actual_ymax - actual_ymin + 1)
+ return 0;
+
+ nmin.Y = actual_ymin;
+ nmax.Y = actual_ymax;
+ generate(mg->vm, mg->seed, blockseed, nmin, nmax, mg->biomemap);
+
+ return 1;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ u32 sizex = (nmax.X - nmin.X + 1);
+ u32 volume = (nmax.X - nmin.X + 1) *
+ (nmax.Y - nmin.Y + 1) *
+ (nmax.Z - nmin.Z + 1);
+ u32 csize = clust_size;
+ u32 cvolume = csize * csize * csize;
+ u32 nclusters = volume / clust_scarcity;
+
+ for (u32 i = 0; i != nclusters; i++) {
+ int x0 = pr.range(nmin.X, nmax.X - csize + 1);
+ int y0 = pr.range(nmin.Y, nmax.Y - csize + 1);
+ int z0 = pr.range(nmin.Z, nmax.Z - csize + 1);
+
+ if ((flags & OREFLAG_USE_NOISE) &&
+ (NoisePerlin3D(&np, x0, y0, z0, mapseed) < nthresh))
+ continue;
+
+ if (biomemap && !biomes.empty()) {
+ u32 index = sizex * (z0 - nmin.Z) + (x0 - nmin.X);
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ for (u32 z1 = 0; z1 != csize; z1++)
+ for (u32 y1 = 0; y1 != csize; y1++)
+ for (u32 x1 = 0; x1 != csize; x1++) {
+ if (pr.range(1, cvolume) > clust_num_ores)
+ continue;
+
+ u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1);
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 4234);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ u16 max_height = column_height_max;
+ int y_start_min = nmin.Y + max_height;
+ int y_start_max = nmax.Y - max_height;
+
+ int y_start = y_start_min < y_start_max ?
+ pr.range(y_start_min, y_start_max) :
+ (y_start_min + y_start_max) / 2;
+
+ if (!noise) {
+ int sx = nmax.X - nmin.X + 1;
+ int sz = nmax.Z - nmin.Z + 1;
+ noise = new Noise(&np, 0, sx, sz);
+ }
+ noise->seed = mapseed + y_start;
+ noise->perlinMap2D(nmin.X, nmin.Z);
+
+ size_t index = 0;
+ for (int z = nmin.Z; z <= nmax.Z; z++)
+ for (int x = nmin.X; x <= nmax.X; x++, index++) {
+ float noiseval = noise->result[index];
+ if (noiseval < nthresh)
+ continue;
+
+ if (biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ u16 height = pr.range(column_height_min, column_height_max);
+ int ymidpoint = y_start + noiseval;
+ int y0 = MYMAX(nmin.Y, ymidpoint - height * (1 - column_midpoint_factor));
+ int y1 = MYMIN(nmax.Y, y0 + height - 1);
+
+ for (int y = y0; y <= y1; y++) {
+ u32 i = vm->m_area.index(x, y, z);
+ if (!vm->m_area.contains(i))
+ continue;
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+OrePuff::~OrePuff()
+{
+ delete noise_puff_top;
+ delete noise_puff_bottom;
+}
+
+
+void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 4234);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ int y_start = pr.range(nmin.Y, nmax.Y);
+
+ if (!noise) {
+ int sx = nmax.X - nmin.X + 1;
+ int sz = nmax.Z - nmin.Z + 1;
+ noise = new Noise(&np, 0, sx, sz);
+ noise_puff_top = new Noise(&np_puff_top, 0, sx, sz);
+ noise_puff_bottom = new Noise(&np_puff_bottom, 0, sx, sz);
+ }
+
+ noise->seed = mapseed + y_start;
+ noise->perlinMap2D(nmin.X, nmin.Z);
+ bool noise_generated = false;
+
+ size_t index = 0;
+ for (int z = nmin.Z; z <= nmax.Z; z++)
+ for (int x = nmin.X; x <= nmax.X; x++, index++) {
+ float noiseval = noise->result[index];
+ if (noiseval < nthresh)
+ continue;
+
+ if (biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ if (!noise_generated) {
+ noise_generated = true;
+ noise_puff_top->perlinMap2D(nmin.X, nmin.Z);
+ noise_puff_bottom->perlinMap2D(nmin.X, nmin.Z);
+ }
+
+ float ntop = noise_puff_top->result[index];
+ float nbottom = noise_puff_bottom->result[index];
+
+ if (!(flags & OREFLAG_PUFF_CLIFFS)) {
+ float ndiff = noiseval - nthresh;
+ if (ndiff < 1.0f) {
+ ntop *= ndiff;
+ nbottom *= ndiff;
+ }
+ }
+
+ int ymid = y_start;
+ int y0 = ymid - nbottom;
+ int y1 = ymid + ntop;
+
+ if ((flags & OREFLAG_PUFF_ADDITIVE) && (y0 > y1))
+ SWAP(int, y0, y1);
+
+ for (int y = y0; y <= y1; y++) {
+ u32 i = vm->m_area.index(x, y, z);
+ if (!vm->m_area.contains(i))
+ continue;
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 2404);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ u32 sizex = (nmax.X - nmin.X + 1);
+ u32 volume = (nmax.X - nmin.X + 1) *
+ (nmax.Y - nmin.Y + 1) *
+ (nmax.Z - nmin.Z + 1);
+ u32 csize = clust_size;
+ u32 nblobs = volume / clust_scarcity;
+
+ if (!noise)
+ noise = new Noise(&np, mapseed, csize, csize, csize);
+
+ for (u32 i = 0; i != nblobs; i++) {
+ int x0 = pr.range(nmin.X, nmax.X - csize + 1);
+ int y0 = pr.range(nmin.Y, nmax.Y - csize + 1);
+ int z0 = pr.range(nmin.Z, nmax.Z - csize + 1);
+
+ if (biomemap && !biomes.empty()) {
+ u32 bmapidx = sizex * (z0 - nmin.Z) + (x0 - nmin.X);
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ bool noise_generated = false;
+ noise->seed = blockseed + i;
+
+ size_t index = 0;
+ for (u32 z1 = 0; z1 != csize; z1++)
+ for (u32 y1 = 0; y1 != csize; y1++)
+ for (u32 x1 = 0; x1 != csize; x1++, index++) {
+ u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1);
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ // Lazily generate noise only if there's a chance of ore being placed
+ // This simple optimization makes calls 6x faster on average
+ if (!noise_generated) {
+ noise_generated = true;
+ noise->perlinMap3D(x0, y0, z0);
+ }
+
+ float noiseval = noise->result[index];
+
+ float xdist = (s32)x1 - (s32)csize / 2;
+ float ydist = (s32)y1 - (s32)csize / 2;
+ float zdist = (s32)z1 - (s32)csize / 2;
+
+ noiseval -= (sqrt(xdist * xdist + ydist * ydist + zdist * zdist) / csize);
+
+ if (noiseval < nthresh)
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+OreVein::~OreVein()
+{
+ delete noise2;
+}
+
+
+void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 520);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ u32 sizex = (nmax.X - nmin.X + 1);
+
+ if (!noise) {
+ int sx = nmax.X - nmin.X + 1;
+ int sy = nmax.Y - nmin.Y + 1;
+ int sz = nmax.Z - nmin.Z + 1;
+ noise = new Noise(&np, mapseed, sx, sy, sz);
+ noise2 = new Noise(&np, mapseed + 436, sx, sy, sz);
+ }
+ bool noise_generated = false;
+
+ size_t index = 0;
+ for (int z = nmin.Z; z <= nmax.Z; z++)
+ for (int y = nmin.Y; y <= nmax.Y; y++)
+ for (int x = nmin.X; x <= nmax.X; x++, index++) {
+ u32 i = vm->m_area.index(x, y, z);
+ if (!vm->m_area.contains(i))
+ continue;
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ if (biomemap && !biomes.empty()) {
+ u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X);
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ // Same lazy generation optimization as in OreBlob
+ if (!noise_generated) {
+ noise_generated = true;
+ noise->perlinMap3D(nmin.X, nmin.Y, nmin.Z);
+ noise2->perlinMap3D(nmin.X, nmin.Y, nmin.Z);
+ }
+
+ // randval ranges from -1..1
+ float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - 1.f;
+ float noiseval = contour(noise->result[index]);
+ float noiseval2 = contour(noise2->result[index]);
+ if (noiseval * noiseval2 + randval * random_factor < nthresh)
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+OreStratum::~OreStratum()
+{
+ delete noise_stratum_thickness;
+}
+
+
+void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap)
+{
+ PcgRandom pr(blockseed + 4234);
+ MapNode n_ore(c_ore, 0, ore_param2);
+
+ if (flags & OREFLAG_USE_NOISE) {
+ if (!(noise && noise_stratum_thickness)) {
+ int sx = nmax.X - nmin.X + 1;
+ int sz = nmax.Z - nmin.Z + 1;
+ noise = new Noise(&np, 0, sx, sz);
+ noise_stratum_thickness = new Noise(&np_stratum_thickness, 0, sx, sz);
+ }
+ noise->perlinMap2D(nmin.X, nmin.Z);
+ noise_stratum_thickness->perlinMap2D(nmin.X, nmin.Z);
+ }
+
+ size_t index = 0;
+
+ for (int z = nmin.Z; z <= nmax.Z; z++)
+ for (int x = nmin.X; x <= nmax.X; x++, index++) {
+ if (biomemap && !biomes.empty()) {
+ std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ if (it == biomes.end())
+ continue;
+ }
+
+ int y0;
+ int y1;
+
+ if (flags & OREFLAG_USE_NOISE) {
+ float nmid = noise->result[index];
+ float nhalfthick = noise_stratum_thickness->result[index] / 2.0f;
+ y0 = MYMAX(nmin.Y, nmid - nhalfthick);
+ y1 = MYMIN(nmax.Y, nmid + nhalfthick);
+ } else {
+ y0 = nmin.Y;
+ y1 = nmax.Y;
+ }
+
+ for (int y = y0; y <= y1; y++) {
+ if (pr.range(1, clust_scarcity) != 1)
+ continue;
+
+ u32 i = vm->m_area.index(x, y, z);
+ if (!vm->m_area.contains(i))
+ continue;
+ if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
+ continue;
+
+ vm->m_data[i] = n_ore;
+ }
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <unordered_set>
+#include "objdef.h"
+#include "noise.h"
+#include "nodedef.h"
+
+class Noise;
+class Mapgen;
+class MMVManip;
+
+/////////////////// Ore generation flags
+
+#define OREFLAG_ABSHEIGHT 0x01 // Non-functional but kept to not break flags
+#define OREFLAG_PUFF_CLIFFS 0x02
+#define OREFLAG_PUFF_ADDITIVE 0x04
+#define OREFLAG_USE_NOISE 0x08
+
+enum OreType {
+ ORE_SCATTER,
+ ORE_SHEET,
+ ORE_PUFF,
+ ORE_BLOB,
+ ORE_VEIN,
+ ORE_STRATUM,
+};
+
+extern FlagDesc flagdesc_ore[];
+
+class Ore : public ObjDef, public NodeResolver {
+public:
+ static const bool NEEDS_NOISE = false;
+
+ content_t c_ore; // the node to place
+ std::vector<content_t> c_wherein; // the nodes to be placed in
+ u32 clust_scarcity; // ore cluster has a 1-in-clust_scarcity chance of appearing at a node
+ s16 clust_num_ores; // how many ore nodes are in a chunk
+ s16 clust_size; // how large (in nodes) a chunk of ore is
+ s16 y_min;
+ s16 y_max;
+ u8 ore_param2; // to set node-specific attributes
+ u32 flags = 0; // attributes for this ore
+ float nthresh; // threshold for noise at which an ore is placed
+ NoiseParams np; // noise for distribution of clusters (NULL for uniform scattering)
+ Noise *noise = nullptr;
+ std::unordered_set<u8> biomes;
+
+ Ore() = default;;
+ virtual ~Ore();
+
+ virtual void resolveNodeNames();
+
+ size_t placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap) = 0;
+};
+
+class OreScatter : public Ore {
+public:
+ static const bool NEEDS_NOISE = false;
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreSheet : public Ore {
+public:
+ static const bool NEEDS_NOISE = true;
+
+ u16 column_height_min;
+ u16 column_height_max;
+ float column_midpoint_factor;
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OrePuff : public Ore {
+public:
+ static const bool NEEDS_NOISE = true;
+
+ NoiseParams np_puff_top;
+ NoiseParams np_puff_bottom;
+ Noise *noise_puff_top = nullptr;
+ Noise *noise_puff_bottom = nullptr;
+
+ OrePuff() = default;
+ virtual ~OrePuff();
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreBlob : public Ore {
+public:
+ static const bool NEEDS_NOISE = true;
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreVein : public Ore {
+public:
+ static const bool NEEDS_NOISE = true;
+
+ float random_factor;
+ Noise *noise2 = nullptr;
+
+ OreVein() = default;
+ virtual ~OreVein();
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreStratum : public Ore {
+public:
+ static const bool NEEDS_NOISE = false;
+
+ NoiseParams np_stratum_thickness;
+ Noise *noise_stratum_thickness = nullptr;
+
+ OreStratum() = default;
+ virtual ~OreStratum();
+
+ virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
+ v3s16 nmin, v3s16 nmax, u8 *biomemap);
+};
+
+class OreManager : public ObjDefManager {
+public:
+ OreManager(IGameDef *gamedef);
+ virtual ~OreManager() = default;
+
+ const char *getObjectTitle() const
+ {
+ return "ore";
+ }
+
+ static Ore *create(OreType type)
+ {
+ switch (type) {
+ case ORE_SCATTER:
+ return new OreScatter;
+ case ORE_SHEET:
+ return new OreSheet;
+ case ORE_PUFF:
+ return new OrePuff;
+ case ORE_BLOB:
+ return new OreBlob;
+ case ORE_VEIN:
+ return new OreVein;
+ case ORE_STRATUM:
+ return new OreStratum;
+ default:
+ return nullptr;
+ }
+ }
+
+ void clear();
+
+ size_t placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+};
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <fstream>
+#include <typeinfo>
+#include "mg_schematic.h"
+#include "server.h"
+#include "mapgen.h"
+#include "emerge.h"
+#include "map.h"
+#include "mapblock.h"
+#include "log.h"
+#include "util/numeric.h"
+#include "util/serialize.h"
+#include "serialization.h"
+#include "filesys.h"
+#include "voxelalgorithms.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+SchematicManager::SchematicManager(Server *server) :
+ ObjDefManager(server, OBJDEF_SCHEMATIC),
+ m_server(server)
+{
+}
+
+
+void SchematicManager::clear()
+{
+ EmergeManager *emerge = m_server->getEmergeManager();
+
+ // Remove all dangling references in Decorations
+ DecorationManager *decomgr = emerge->decomgr;
+ for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
+ Decoration *deco = (Decoration *)decomgr->getRaw(i);
+
+ try {
+ DecoSchematic *dschem = dynamic_cast<DecoSchematic *>(deco);
+ if (dschem)
+ dschem->schematic = NULL;
+ } catch (const std::bad_cast &) {
+ }
+ }
+
+ ObjDefManager::clear();
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+Schematic::Schematic()
+= default;
+
+
+Schematic::~Schematic()
+{
+ delete []schemdata;
+ delete []slice_probs;
+}
+
+
+void Schematic::resolveNodeNames()
+{
+ getIdsFromNrBacklog(&c_nodes, true, CONTENT_AIR);
+
+ size_t bufsize = size.X * size.Y * size.Z;
+ for (size_t i = 0; i != bufsize; i++) {
+ content_t c_original = schemdata[i].getContent();
+ content_t c_new = c_nodes[c_original];
+ schemdata[i].setContent(c_new);
+ }
+}
+
+
+void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place)
+{
+ sanity_check(m_ndef != NULL);
+
+ int xstride = 1;
+ int ystride = size.X;
+ int zstride = size.X * size.Y;
+
+ s16 sx = size.X;
+ s16 sy = size.Y;
+ s16 sz = size.Z;
+
+ int i_start, i_step_x, i_step_z;
+ switch (rot) {
+ case ROTATE_90:
+ i_start = sx - 1;
+ i_step_x = zstride;
+ i_step_z = -xstride;
+ SWAP(s16, sx, sz);
+ break;
+ case ROTATE_180:
+ i_start = zstride * (sz - 1) + sx - 1;
+ i_step_x = -xstride;
+ i_step_z = -zstride;
+ break;
+ case ROTATE_270:
+ i_start = zstride * (sz - 1);
+ i_step_x = -zstride;
+ i_step_z = xstride;
+ SWAP(s16, sx, sz);
+ break;
+ default:
+ i_start = 0;
+ i_step_x = xstride;
+ i_step_z = zstride;
+ }
+
+ s16 y_map = p.Y;
+ for (s16 y = 0; y != sy; y++) {
+ if ((slice_probs[y] != MTSCHEM_PROB_ALWAYS) &&
+ (slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS)))
+ continue;
+
+ for (s16 z = 0; z != sz; z++) {
+ u32 i = z * i_step_z + y * ystride + i_start;
+ for (s16 x = 0; x != sx; x++, i += i_step_x) {
+ u32 vi = vm->m_area.index(p.X + x, y_map, p.Z + z);
+ if (!vm->m_area.contains(vi))
+ continue;
+
+ if (schemdata[i].getContent() == CONTENT_IGNORE)
+ continue;
+
+ u8 placement_prob = schemdata[i].param1 & MTSCHEM_PROB_MASK;
+ bool force_place_node = schemdata[i].param1 & MTSCHEM_FORCE_PLACE;
+
+ if (placement_prob == MTSCHEM_PROB_NEVER)
+ continue;
+
+ if (!force_place && !force_place_node) {
+ content_t c = vm->m_data[vi].getContent();
+ if (c != CONTENT_AIR && c != CONTENT_IGNORE)
+ continue;
+ }
+
+ if ((placement_prob != MTSCHEM_PROB_ALWAYS) &&
+ (placement_prob <= myrand_range(1, MTSCHEM_PROB_ALWAYS)))
+ continue;
+
+ vm->m_data[vi] = schemdata[i];
+ vm->m_data[vi].param1 = 0;
+
+ if (rot)
+ vm->m_data[vi].rotateAlongYAxis(m_ndef, rot);
+ }
+ }
+ y_map++;
+ }
+}
+
+
+bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags,
+ Rotation rot, bool force_place)
+{
+ assert(vm != NULL);
+ assert(schemdata != NULL);
+ sanity_check(m_ndef != NULL);
+
+ //// Determine effective rotation and effective schematic dimensions
+ if (rot == ROTATE_RAND)
+ rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270);
+
+ v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ?
+ v3s16(size.Z, size.Y, size.X) : size;
+
+ //// Adjust placement position if necessary
+ if (flags & DECO_PLACE_CENTER_X)
+ p.X -= (s.X + 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Y)
+ p.Y -= (s.Y + 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Z)
+ p.Z -= (s.Z + 1) / 2;
+
+ blitToVManip(vm, p, rot, force_place);
+
+ return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1,1,1)));
+}
+
+void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags,
+ Rotation rot, bool force_place)
+{
+ std::map<v3s16, MapBlock *> lighting_modified_blocks;
+ std::map<v3s16, MapBlock *> modified_blocks;
+ std::map<v3s16, MapBlock *>::iterator it;
+
+ assert(map != NULL);
+ assert(schemdata != NULL);
+ sanity_check(m_ndef != NULL);
+
+ //// Determine effective rotation and effective schematic dimensions
+ if (rot == ROTATE_RAND)
+ rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270);
+
+ v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ?
+ v3s16(size.Z, size.Y, size.X) : size;
+
+ //// Adjust placement position if necessary
+ if (flags & DECO_PLACE_CENTER_X)
+ p.X -= (s.X + 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Y)
+ p.Y -= (s.Y + 1) / 2;
+ if (flags & DECO_PLACE_CENTER_Z)
+ p.Z -= (s.Z + 1) / 2;
+
+ //// Create VManip for effected area, emerge our area, modify area
+ //// inside VManip, then blit back.
+ v3s16 bp1 = getNodeBlockPos(p);
+ v3s16 bp2 = getNodeBlockPos(p + s - v3s16(1,1,1));
+
+ MMVManip vm(map);
+ vm.initialEmerge(bp1, bp2);
+
+ blitToVManip(&vm, p, rot, force_place);
+
+ voxalgo::blit_back_with_light(map, &vm, &modified_blocks);
+
+ //// Carry out post-map-modification actions
+
+ //// Create & dispatch map modification events to observers
+ MapEditEvent event;
+ event.type = MEET_OTHER;
+ for (it = modified_blocks.begin(); it != modified_blocks.end(); ++it)
+ event.modified_blocks.insert(it->first);
+
+ map->dispatchEvent(&event);
+}
+
+
+bool Schematic::deserializeFromMts(std::istream *is,
+ std::vector<std::string> *names)
+{
+ std::istream &ss = *is;
+ content_t cignore = CONTENT_IGNORE;
+ bool have_cignore = false;
+
+ //// Read signature
+ u32 signature = readU32(ss);
+ if (signature != MTSCHEM_FILE_SIGNATURE) {
+ errorstream << __FUNCTION__ << ": invalid schematic "
+ "file" << std::endl;
+ return false;
+ }
+
+ //// Read version
+ u16 version = readU16(ss);
+ if (version > MTSCHEM_FILE_VER_HIGHEST_READ) {
+ errorstream << __FUNCTION__ << ": unsupported schematic "
+ "file version" << std::endl;
+ return false;
+ }
+
+ //// Read size
+ size = readV3S16(ss);
+
+ //// Read Y-slice probability values
+ delete []slice_probs;
+ slice_probs = new u8[size.Y];
+ for (int y = 0; y != size.Y; y++)
+ slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS_OLD;
+
+ //// Read node names
+ u16 nidmapcount = readU16(ss);
+ for (int i = 0; i != nidmapcount; i++) {
+ std::string name = deSerializeString(ss);
+
+ // Instances of "ignore" from v1 are converted to air (and instances
+ // are fixed to have MTSCHEM_PROB_NEVER later on).
+ if (name == "ignore") {
+ name = "air";
+ cignore = i;
+ have_cignore = true;
+ }
+
+ names->push_back(name);
+ }
+
+ //// Read node data
+ size_t nodecount = size.X * size.Y * size.Z;
+
+ delete []schemdata;
+ schemdata = new MapNode[nodecount];
+
+ MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata,
+ nodecount, 2, 2, true);
+
+ // Fix probability values for nodes that were ignore; removed in v2
+ if (version < 2) {
+ for (size_t i = 0; i != nodecount; i++) {
+ if (schemdata[i].param1 == 0)
+ schemdata[i].param1 = MTSCHEM_PROB_ALWAYS_OLD;
+ if (have_cignore && schemdata[i].getContent() == cignore)
+ schemdata[i].param1 = MTSCHEM_PROB_NEVER;
+ }
+ }
+
+ // Fix probability values for probability range truncation introduced in v4
+ if (version < 4) {
+ for (s16 y = 0; y != size.Y; y++)
+ slice_probs[y] >>= 1;
+ for (size_t i = 0; i != nodecount; i++)
+ schemdata[i].param1 >>= 1;
+ }
+
+ return true;
+}
+
+
+bool Schematic::serializeToMts(std::ostream *os,
+ const std::vector<std::string> &names)
+{
+ std::ostream &ss = *os;
+
+ writeU32(ss, MTSCHEM_FILE_SIGNATURE); // signature
+ writeU16(ss, MTSCHEM_FILE_VER_HIGHEST_WRITE); // version
+ writeV3S16(ss, size); // schematic size
+
+ for (int y = 0; y != size.Y; y++) // Y slice probabilities
+ writeU8(ss, slice_probs[y]);
+
+ writeU16(ss, names.size()); // name count
+ for (size_t i = 0; i != names.size(); i++)
+ ss << serializeString(names[i]); // node names
+
+ // compressed bulk node data
+ MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE,
+ schemdata, size.X * size.Y * size.Z, 2, 2, true);
+
+ return true;
+}
+
+
+bool Schematic::serializeToLua(std::ostream *os,
+ const std::vector<std::string> &names, bool use_comments, u32 indent_spaces)
+{
+ std::ostream &ss = *os;
+
+ std::string indent("\t");
+ if (indent_spaces > 0)
+ indent.assign(indent_spaces, ' ');
+
+ //// Write header
+ {
+ ss << "schematic = {" << std::endl;
+ ss << indent << "size = "
+ << "{x=" << size.X
+ << ", y=" << size.Y
+ << ", z=" << size.Z
+ << "}," << std::endl;
+ }
+
+ //// Write y-slice probabilities
+ {
+ ss << indent << "yslice_prob = {" << std::endl;
+
+ for (u16 y = 0; y != size.Y; y++) {
+ u8 probability = slice_probs[y] & MTSCHEM_PROB_MASK;
+
+ ss << indent << indent << "{"
+ << "ypos=" << y
+ << ", prob=" << (u16)probability * 2
+ << "}," << std::endl;
+ }
+
+ ss << indent << "}," << std::endl;
+ }
+
+ //// Write node data
+ {
+ ss << indent << "data = {" << std::endl;
+
+ u32 i = 0;
+ for (u16 z = 0; z != size.Z; z++)
+ for (u16 y = 0; y != size.Y; y++) {
+ if (use_comments) {
+ ss << std::endl
+ << indent << indent
+ << "-- z=" << z
+ << ", y=" << y << std::endl;
+ }
+
+ for (u16 x = 0; x != size.X; x++, i++) {
+ u8 probability = schemdata[i].param1 & MTSCHEM_PROB_MASK;
+ bool force_place = schemdata[i].param1 & MTSCHEM_FORCE_PLACE;
+
+ ss << indent << indent << "{"
+ << "name=\"" << names[schemdata[i].getContent()]
+ << "\", prob=" << (u16)probability * 2
+ << ", param2=" << (u16)schemdata[i].param2;
+
+ if (force_place)
+ ss << ", force_place=true";
+
+ ss << "}," << std::endl;
+ }
+ }
+
+ ss << indent << "}," << std::endl;
+ }
+
+ ss << "}" << std::endl;
+
+ return true;
+}
+
+
+bool Schematic::loadSchematicFromFile(const std::string &filename,
+ INodeDefManager *ndef, StringMap *replace_names)
+{
+ std::ifstream is(filename.c_str(), std::ios_base::binary);
+ if (!is.good()) {
+ errorstream << __FUNCTION__ << ": unable to open file '"
+ << filename << "'" << std::endl;
+ return false;
+ }
+
+ size_t origsize = m_nodenames.size();
+ if (!deserializeFromMts(&is, &m_nodenames))
+ return false;
+
+ m_nnlistsizes.push_back(m_nodenames.size() - origsize);
+
+ name = filename;
+
+ if (replace_names) {
+ for (size_t i = origsize; i < m_nodenames.size(); i++) {
+ std::string &node_name = m_nodenames[i];
+ StringMap::iterator it = replace_names->find(node_name);
+ if (it != replace_names->end())
+ node_name = it->second;
+ }
+ }
+
+ if (ndef)
+ ndef->pendNodeResolve(this);
+
+ return true;
+}
+
+
+bool Schematic::saveSchematicToFile(const std::string &filename,
+ INodeDefManager *ndef)
+{
+ MapNode *orig_schemdata = schemdata;
+ std::vector<std::string> ndef_nodenames;
+ std::vector<std::string> *names;
+
+ if (m_resolve_done && ndef == NULL)
+ ndef = m_ndef;
+
+ if (ndef) {
+ names = &ndef_nodenames;
+
+ u32 volume = size.X * size.Y * size.Z;
+ schemdata = new MapNode[volume];
+ for (u32 i = 0; i != volume; i++)
+ schemdata[i] = orig_schemdata[i];
+
+ generate_nodelist_and_update_ids(schemdata, volume, names, ndef);
+ } else { // otherwise, use the names we have on hand in the list
+ names = &m_nodenames;
+ }
+
+ std::ostringstream os(std::ios_base::binary);
+ bool status = serializeToMts(&os, *names);
+
+ if (ndef) {
+ delete []schemdata;
+ schemdata = orig_schemdata;
+ }
+
+ if (!status)
+ return false;
+
+ return fs::safeWriteToFile(filename, os.str());
+}
+
+
+bool Schematic::getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2)
+{
+ MMVManip *vm = new MMVManip(map);
+
+ v3s16 bp1 = getNodeBlockPos(p1);
+ v3s16 bp2 = getNodeBlockPos(p2);
+ vm->initialEmerge(bp1, bp2);
+
+ size = p2 - p1 + 1;
+
+ slice_probs = new u8[size.Y];
+ for (s16 y = 0; y != size.Y; y++)
+ slice_probs[y] = MTSCHEM_PROB_ALWAYS;
+
+ schemdata = new MapNode[size.X * size.Y * size.Z];
+
+ u32 i = 0;
+ for (s16 z = p1.Z; z <= p2.Z; z++)
+ for (s16 y = p1.Y; y <= p2.Y; y++) {
+ u32 vi = vm->m_area.index(p1.X, y, z);
+ for (s16 x = p1.X; x <= p2.X; x++, i++, vi++) {
+ schemdata[i] = vm->m_data[vi];
+ schemdata[i].param1 = MTSCHEM_PROB_ALWAYS;
+ }
+ }
+
+ delete vm;
+ return true;
+}
+
+
+void Schematic::applyProbabilities(v3s16 p0,
+ std::vector<std::pair<v3s16, u8> > *plist,
+ std::vector<std::pair<s16, u8> > *splist)
+{
+ for (size_t i = 0; i != plist->size(); i++) {
+ v3s16 p = (*plist)[i].first - p0;
+ int index = p.Z * (size.Y * size.X) + p.Y * size.X + p.X;
+ if (index < size.Z * size.Y * size.X) {
+ u8 prob = (*plist)[i].second;
+ schemdata[index].param1 = prob;
+
+ // trim unnecessary node names from schematic
+ if (prob == MTSCHEM_PROB_NEVER)
+ schemdata[index].setContent(CONTENT_AIR);
+ }
+ }
+
+ for (size_t i = 0; i != splist->size(); i++) {
+ s16 y = (*splist)[i].first - p0.Y;
+ slice_probs[y] = (*splist)[i].second;
+ }
+}
+
+
+void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount,
+ std::vector<std::string> *usednodes, INodeDefManager *ndef)
+{
+ std::unordered_map<content_t, content_t> nodeidmap;
+ content_t numids = 0;
+
+ for (size_t i = 0; i != nodecount; i++) {
+ content_t id;
+ content_t c = nodes[i].getContent();
+
+ std::unordered_map<content_t, content_t>::const_iterator it = nodeidmap.find(c);
+ if (it == nodeidmap.end()) {
+ id = numids;
+ numids++;
+
+ usednodes->push_back(ndef->get(c).name);
+ nodeidmap.insert(std::make_pair(c, id));
+ } else {
+ id = it->second;
+ }
+ nodes[i].setContent(id);
+ }
+}
--- /dev/null
+/*
+Minetest
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2017 paramat
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <map>
+#include "mg_decoration.h"
+#include "util/string.h"
+
+class Map;
+class ServerMap;
+class Mapgen;
+class MMVManip;
+class PseudoRandom;
+class NodeResolver;
+class Server;
+
+/*
+ Minetest Schematic File Format
+
+ All values are stored in big-endian byte order.
+ [u32] signature: 'MTSM'
+ [u16] version: 4
+ [u16] size X
+ [u16] size Y
+ [u16] size Z
+ For each Y:
+ [u8] slice probability value
+ [Name-ID table] Name ID Mapping Table
+ [u16] name-id count
+ For each name-id mapping:
+ [u16] name length
+ [u8[]] name
+ ZLib deflated {
+ For each node in schematic: (for z, y, x)
+ [u16] content
+ For each node in schematic:
+ [u8] param1
+ bit 0-6: probability
+ bit 7: specific node force placement
+ For each node in schematic:
+ [u8] param2
+ }
+
+ Version changes:
+ 1 - Initial version
+ 2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always
+ 3 - Added y-slice probabilities; this allows for variable height structures
+ 4 - Compressed range of node occurence prob., added per-node force placement bit
+*/
+
+//// Schematic constants
+#define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM'
+#define MTSCHEM_FILE_VER_HIGHEST_READ 4
+#define MTSCHEM_FILE_VER_HIGHEST_WRITE 4
+
+#define MTSCHEM_PROB_MASK 0x7F
+
+#define MTSCHEM_PROB_NEVER 0x00
+#define MTSCHEM_PROB_ALWAYS 0x7F
+#define MTSCHEM_PROB_ALWAYS_OLD 0xFF
+
+#define MTSCHEM_FORCE_PLACE 0x80
+
+enum SchematicType
+{
+ SCHEMATIC_NORMAL,
+};
+
+enum SchematicFormatType {
+ SCHEM_FMT_HANDLE,
+ SCHEM_FMT_MTS,
+ SCHEM_FMT_LUA,
+};
+
+class Schematic : public ObjDef, public NodeResolver {
+public:
+ Schematic();
+ virtual ~Schematic();
+
+ virtual void resolveNodeNames();
+
+ bool loadSchematicFromFile(const std::string &filename, INodeDefManager *ndef,
+ StringMap *replace_names=NULL);
+ bool saveSchematicToFile(const std::string &filename, INodeDefManager *ndef);
+ bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2);
+
+ bool deserializeFromMts(std::istream *is, std::vector<std::string> *names);
+ bool serializeToMts(std::ostream *os, const std::vector<std::string> &names);
+ bool serializeToLua(std::ostream *os, const std::vector<std::string> &names,
+ bool use_comments, u32 indent_spaces);
+
+ void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place);
+ bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place);
+ void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place);
+
+ void applyProbabilities(v3s16 p0,
+ std::vector<std::pair<v3s16, u8> > *plist,
+ std::vector<std::pair<s16, u8> > *splist);
+
+ std::vector<content_t> c_nodes;
+ u32 flags = 0;
+ v3s16 size;
+ MapNode *schemdata = nullptr;
+ u8 *slice_probs = nullptr;
+};
+
+class SchematicManager : public ObjDefManager {
+public:
+ SchematicManager(Server *server);
+ virtual ~SchematicManager() = default;
+
+ virtual void clear();
+
+ const char *getObjectTitle() const
+ {
+ return "schematic";
+ }
+
+ static Schematic *create(SchematicType type)
+ {
+ return new Schematic;
+ }
+
+private:
+ Server *m_server;
+};
+
+void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount,
+ std::vector<std::string> *usednodes, INodeDefManager *ndef);
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>,
+ 2012-2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "irr_v3d.h"
+#include <stack>
+#include "util/pointer.h"
+#include "util/numeric.h"
+#include "map.h"
+#include "mapblock.h"
+#include "serverenvironment.h"
+#include "nodedef.h"
+#include "treegen.h"
+#include "voxelalgorithms.h"
+
+namespace treegen
+{
+
+void make_tree(MMVManip &vmanip, v3s16 p0,
+ bool is_apple_tree, INodeDefManager *ndef, s32 seed)
+{
+ /*
+ NOTE: Tree-placing code is currently duplicated in the engine
+ and in games that have saplings; both are deprecated but not
+ replaced yet
+ */
+ MapNode treenode(ndef->getId("mapgen_tree"));
+ MapNode leavesnode(ndef->getId("mapgen_leaves"));
+ MapNode applenode(ndef->getId("mapgen_apple"));
+
+ PseudoRandom pr(seed);
+ s16 trunk_h = pr.range(4, 5);
+ v3s16 p1 = p0;
+ for (s16 ii = 0; ii < trunk_h; ii++) {
+ if (vmanip.m_area.contains(p1)) {
+ u32 vi = vmanip.m_area.index(p1);
+ vmanip.m_data[vi] = treenode;
+ }
+ p1.Y++;
+ }
+
+ // p1 is now the last piece of the trunk
+ p1.Y -= 1;
+
+ VoxelArea leaves_a(v3s16(-2, -1, -2), v3s16(2, 2, 2));
+ Buffer<u8> leaves_d(leaves_a.getVolume());
+ for (s32 i = 0; i < leaves_a.getVolume(); i++)
+ leaves_d[i] = 0;
+
+ // Force leaves at near the end of the trunk
+ s16 d = 1;
+ for (s16 z = -d; z <= d; z++)
+ for (s16 y = -d; y <= d; y++)
+ for (s16 x = -d; x <= d; x++) {
+ leaves_d[leaves_a.index(v3s16(x, y, z))] = 1;
+ }
+
+ // Add leaves randomly
+ for (u32 iii = 0; iii < 7; iii++) {
+ v3s16 p(
+ pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d),
+ pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d),
+ pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d)
+ );
+
+ for (s16 z = 0; z <= d; z++)
+ for (s16 y = 0; y <= d; y++)
+ for (s16 x = 0; x <= d; x++) {
+ leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1;
+ }
+ }
+
+ // Blit leaves to vmanip
+ for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
+ for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
+ v3s16 pmin(leaves_a.MinEdge.X, y, z);
+ u32 i = leaves_a.index(pmin);
+ u32 vi = vmanip.m_area.index(pmin + p1);
+ for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
+ v3s16 p(x, y, z);
+ if (vmanip.m_area.contains(p + p1) &&
+ (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
+ vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) {
+ if (leaves_d[i] == 1) {
+ bool is_apple = pr.range(0, 99) < 10;
+ if (is_apple_tree && is_apple)
+ vmanip.m_data[vi] = applenode;
+ else
+ vmanip.m_data[vi] = leavesnode;
+ }
+ }
+ vi++;
+ i++;
+ }
+ }
+}
+
+
+// L-System tree LUA spawner
+treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0,
+ INodeDefManager *ndef, const TreeDef &tree_definition)
+{
+ ServerMap *map = &env->getServerMap();
+ std::map<v3s16, MapBlock*> modified_blocks;
+ MMVManip vmanip(map);
+ v3s16 tree_blockp = getNodeBlockPos(p0);
+ treegen::error e;
+
+ vmanip.initialEmerge(tree_blockp - v3s16(1, 1, 1), tree_blockp + v3s16(1, 3, 1));
+ e = make_ltree(vmanip, p0, ndef, tree_definition);
+ if (e != SUCCESS)
+ return e;
+
+ voxalgo::blit_back_with_light(map, &vmanip, &modified_blocks);
+
+ // Send a MEET_OTHER event
+ MapEditEvent event;
+ event.type = MEET_OTHER;
+ for (auto &modified_block : modified_blocks)
+ event.modified_blocks.insert(modified_block.first);
+ map->dispatchEvent(&event);
+ return SUCCESS;
+}
+
+
+//L-System tree generator
+treegen::error make_ltree(MMVManip &vmanip, v3s16 p0,
+ INodeDefManager *ndef, TreeDef tree_definition)
+{
+ MapNode dirtnode(ndef->getId("mapgen_dirt"));
+ s32 seed;
+ if (tree_definition.explicit_seed)
+ seed = tree_definition.seed + 14002;
+ else
+ seed = p0.X * 2 + p0.Y * 4 + p0.Z; // use the tree position to seed PRNG
+ PseudoRandom ps(seed);
+
+ // chance of inserting abcd rules
+ double prop_a = 9;
+ double prop_b = 8;
+ double prop_c = 7;
+ double prop_d = 6;
+
+ //randomize tree growth level, minimum=2
+ s16 iterations = tree_definition.iterations;
+ if (tree_definition.iterations_random_level > 0)
+ iterations -= ps.range(0, tree_definition.iterations_random_level);
+ if (iterations < 2)
+ iterations = 2;
+
+ s16 MAX_ANGLE_OFFSET = 5;
+ double angle_in_radians = (double)tree_definition.angle * M_PI / 180;
+ double angleOffset_in_radians = (s16)(ps.range(0, 1) % MAX_ANGLE_OFFSET) * M_PI / 180;
+
+ //initialize rotation matrix, position and stacks for branches
+ core::matrix4 rotation;
+ rotation = setRotationAxisRadians(rotation, M_PI / 2, v3f(0, 0, 1));
+ v3f position;
+ position.X = p0.X;
+ position.Y = p0.Y;
+ position.Z = p0.Z;
+ std::stack <core::matrix4> stack_orientation;
+ std::stack <v3f> stack_position;
+
+ //generate axiom
+ std::string axiom = tree_definition.initial_axiom;
+ for (s16 i = 0; i < iterations; i++) {
+ std::string temp;
+ for (s16 j = 0; j < (s16)axiom.size(); j++) {
+ char axiom_char = axiom.at(j);
+ switch (axiom_char) {
+ case 'A':
+ temp += tree_definition.rules_a;
+ break;
+ case 'B':
+ temp += tree_definition.rules_b;
+ break;
+ case 'C':
+ temp += tree_definition.rules_c;
+ break;
+ case 'D':
+ temp += tree_definition.rules_d;
+ break;
+ case 'a':
+ if (prop_a >= ps.range(1, 10))
+ temp += tree_definition.rules_a;
+ break;
+ case 'b':
+ if (prop_b >= ps.range(1, 10))
+ temp += tree_definition.rules_b;
+ break;
+ case 'c':
+ if (prop_c >= ps.range(1, 10))
+ temp += tree_definition.rules_c;
+ break;
+ case 'd':
+ if (prop_d >= ps.range(1, 10))
+ temp += tree_definition.rules_d;
+ break;
+ default:
+ temp += axiom_char;
+ break;
+ }
+ }
+ axiom = temp;
+ }
+
+ //make sure tree is not floating in the air
+ if (tree_definition.trunk_type == "double") {
+ tree_node_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y - 1, position.Z),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X, position.Y - 1, position.Z + 1),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y - 1, position.Z + 1),
+ dirtnode
+ );
+ } else if (tree_definition.trunk_type == "crossed") {
+ tree_node_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y - 1, position.Z),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X - 1, position.Y - 1, position.Z),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X, position.Y - 1, position.Z + 1),
+ dirtnode
+ );
+ tree_node_placement(
+ vmanip,
+ v3f(position.X, position.Y - 1, position.Z - 1),
+ dirtnode
+ );
+ }
+
+ /* build tree out of generated axiom
+
+ Key for Special L-System Symbols used in Axioms
+
+ G - move forward one unit with the pen up
+ F - move forward one unit with the pen down drawing trunks and branches
+ f - move forward one unit with the pen down drawing leaves (100% chance)
+ T - move forward one unit with the pen down drawing trunks only
+ R - move forward one unit with the pen down placing fruit
+ A - replace with rules set A
+ B - replace with rules set B
+ C - replace with rules set C
+ D - replace with rules set D
+ a - replace with rules set A, chance 90%
+ b - replace with rules set B, chance 80%
+ c - replace with rules set C, chance 70%
+ d - replace with rules set D, chance 60%
+ + - yaw the turtle right by angle degrees
+ - - yaw the turtle left by angle degrees
+ & - pitch the turtle down by angle degrees
+ ^ - pitch the turtle up by angle degrees
+ / - roll the turtle to the right by angle degrees
+ * - roll the turtle to the left by angle degrees
+ [ - save in stack current state info
+ ] - recover from stack state info
+
+ */
+
+ s16 x,y,z;
+ for (s16 i = 0; i < (s16)axiom.size(); i++) {
+ char axiom_char = axiom.at(i);
+ core::matrix4 temp_rotation;
+ temp_rotation.makeIdentity();
+ v3f dir;
+ switch (axiom_char) {
+ case 'G':
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+ case 'T':
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z),
+ tree_definition
+ );
+ if (tree_definition.trunk_type == "double" &&
+ !tree_definition.thin_branches) {
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z + 1),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z + 1),
+ tree_definition
+ );
+ } else if (tree_definition.trunk_type == "crossed" &&
+ !tree_definition.thin_branches) {
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X - 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z + 1),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z - 1),
+ tree_definition
+ );
+ }
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+ case 'F':
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z),
+ tree_definition
+ );
+ if ((stack_orientation.empty() &&
+ tree_definition.trunk_type == "double") ||
+ (!stack_orientation.empty() &&
+ tree_definition.trunk_type == "double" &&
+ !tree_definition.thin_branches)) {
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X +1 , position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z + 1),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z + 1),
+ tree_definition
+ );
+ } else if ((stack_orientation.empty() &&
+ tree_definition.trunk_type == "crossed") ||
+ (!stack_orientation.empty() &&
+ tree_definition.trunk_type == "crossed" &&
+ !tree_definition.thin_branches)) {
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X + 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X - 1, position.Y, position.Z),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z + 1),
+ tree_definition
+ );
+ tree_trunk_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z - 1),
+ tree_definition
+ );
+ } if (!stack_orientation.empty()) {
+ s16 size = 1;
+ for (x = -size; x <= size; x++)
+ for (y = -size; y <= size; y++)
+ for (z = -size; z <= size; z++) {
+ if (abs(x) == size &&
+ abs(y) == size &&
+ abs(z) == size) {
+ tree_leaves_placement(
+ vmanip,
+ v3f(position.X + x + 1, position.Y + y,
+ position.Z + z),
+ ps.next(),
+ tree_definition
+ );
+ tree_leaves_placement(
+ vmanip,
+ v3f(position.X + x - 1, position.Y + y,
+ position.Z + z),
+ ps.next(),
+ tree_definition
+ );
+ tree_leaves_placement(
+ vmanip,v3f(position.X + x, position.Y + y,
+ position.Z + z + 1),
+ ps.next(),
+ tree_definition
+ );
+ tree_leaves_placement(
+ vmanip,v3f(position.X + x, position.Y + y,
+ position.Z + z - 1),
+ ps.next(),
+ tree_definition
+ );
+ }
+ }
+ }
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+ case 'f':
+ tree_single_leaves_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z),
+ ps.next(),
+ tree_definition
+ );
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+ case 'R':
+ tree_fruit_placement(
+ vmanip,
+ v3f(position.X, position.Y, position.Z),
+ tree_definition
+ );
+ dir = v3f(1, 0, 0);
+ dir = transposeMatrix(rotation, dir);
+ position += dir;
+ break;
+
+ // turtle orientation commands
+ case '[':
+ stack_orientation.push(rotation);
+ stack_position.push(position);
+ break;
+ case ']':
+ if (stack_orientation.empty())
+ return UNBALANCED_BRACKETS;
+ rotation = stack_orientation.top();
+ stack_orientation.pop();
+ position = stack_position.top();
+ stack_position.pop();
+ break;
+ case '+':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians + angleOffset_in_radians, v3f(0, 0, 1));
+ rotation *= temp_rotation;
+ break;
+ case '-':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians + angleOffset_in_radians, v3f(0, 0, -1));
+ rotation *= temp_rotation;
+ break;
+ case '&':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians + angleOffset_in_radians, v3f(0, 1, 0));
+ rotation *= temp_rotation;
+ break;
+ case '^':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians + angleOffset_in_radians, v3f(0, -1, 0));
+ rotation *= temp_rotation;
+ break;
+ case '*':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians, v3f(1, 0, 0));
+ rotation *= temp_rotation;
+ break;
+ case '/':
+ temp_rotation.makeIdentity();
+ temp_rotation = setRotationAxisRadians(temp_rotation,
+ angle_in_radians, v3f(-1, 0, 0));
+ rotation *= temp_rotation;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return SUCCESS;
+}
+
+
+void tree_node_placement(MMVManip &vmanip, v3f p0, MapNode node)
+{
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ if (vmanip.m_data[vi].getContent() != CONTENT_AIR
+ && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+ return;
+ vmanip.m_data[vmanip.m_area.index(p1)] = node;
+}
+
+
+void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition)
+{
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ content_t current_node = vmanip.m_data[vi].getContent();
+ if (current_node != CONTENT_AIR && current_node != CONTENT_IGNORE
+ && current_node != tree_definition.leavesnode.getContent()
+ && current_node != tree_definition.leaves2node.getContent()
+ && current_node != tree_definition.fruitnode.getContent())
+ return;
+ vmanip.m_data[vi] = tree_definition.trunknode;
+}
+
+
+void tree_leaves_placement(MMVManip &vmanip, v3f p0,
+ PseudoRandom ps, TreeDef &tree_definition)
+{
+ MapNode leavesnode = tree_definition.leavesnode;
+ if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance)
+ leavesnode = tree_definition.leaves2node;
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ if (vmanip.m_data[vi].getContent() != CONTENT_AIR
+ && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+ return;
+ if (tree_definition.fruit_chance > 0) {
+ if (ps.range(1, 100) > 100 - tree_definition.fruit_chance)
+ vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode;
+ else
+ vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
+ } else if (ps.range(1, 100) > 20) {
+ vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
+ }
+}
+
+
+void tree_single_leaves_placement(MMVManip &vmanip, v3f p0,
+ PseudoRandom ps, TreeDef &tree_definition)
+{
+ MapNode leavesnode = tree_definition.leavesnode;
+ if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance)
+ leavesnode = tree_definition.leaves2node;
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ if (vmanip.m_data[vi].getContent() != CONTENT_AIR
+ && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+ return;
+ vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
+}
+
+
+void tree_fruit_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition)
+{
+ v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
+ if (!vmanip.m_area.contains(p1))
+ return;
+ u32 vi = vmanip.m_area.index(p1);
+ if (vmanip.m_data[vi].getContent() != CONTENT_AIR
+ && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
+ return;
+ vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode;
+}
+
+
+irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis)
+{
+ double c = cos(angle);
+ double s = sin(angle);
+ double t = 1.0 - c;
+
+ double tx = t * axis.X;
+ double ty = t * axis.Y;
+ double tz = t * axis.Z;
+ double sx = s * axis.X;
+ double sy = s * axis.Y;
+ double sz = s * axis.Z;
+
+ M[0] = tx * axis.X + c;
+ M[1] = tx * axis.Y + sz;
+ M[2] = tx * axis.Z - sy;
+
+ M[4] = ty * axis.X - sz;
+ M[5] = ty * axis.Y + c;
+ M[6] = ty * axis.Z + sx;
+
+ M[8] = tz * axis.X + sy;
+ M[9] = tz * axis.Y - sx;
+ M[10] = tz * axis.Z + c;
+ return M;
+}
+
+
+v3f transposeMatrix(irr::core::matrix4 M, v3f v)
+{
+ v3f translated;
+ double x = M[0] * v.X + M[4] * v.Y + M[8] * v.Z +M[12];
+ double y = M[1] * v.X + M[5] * v.Y + M[9] * v.Z +M[13];
+ double z = M[2] * v.X + M[6] * v.Y + M[10] * v.Z +M[14];
+ translated.X = x;
+ translated.Y = y;
+ translated.Z = z;
+ return translated;
+}
+
+
+void make_jungletree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed)
+{
+ /*
+ NOTE: Tree-placing code is currently duplicated in the engine
+ and in games that have saplings; both are deprecated but not
+ replaced yet
+ */
+ content_t c_tree = ndef->getId("mapgen_jungletree");
+ content_t c_leaves = ndef->getId("mapgen_jungleleaves");
+ if (c_tree == CONTENT_IGNORE)
+ c_tree = ndef->getId("mapgen_tree");
+ if (c_leaves == CONTENT_IGNORE)
+ c_leaves = ndef->getId("mapgen_leaves");
+
+ MapNode treenode(c_tree);
+ MapNode leavesnode(c_leaves);
+
+ PseudoRandom pr(seed);
+ for (s16 x= -1; x <= 1; x++)
+ for (s16 z= -1; z <= 1; z++) {
+ if (pr.range(0, 2) == 0)
+ continue;
+ v3s16 p1 = p0 + v3s16(x, 0, z);
+ v3s16 p2 = p0 + v3s16(x, -1, z);
+ u32 vi1 = vmanip.m_area.index(p1);
+ u32 vi2 = vmanip.m_area.index(p2);
+
+ if (vmanip.m_area.contains(p2) &&
+ vmanip.m_data[vi2].getContent() == CONTENT_AIR)
+ vmanip.m_data[vi2] = treenode;
+ else if (vmanip.m_area.contains(p1) &&
+ vmanip.m_data[vi1].getContent() == CONTENT_AIR)
+ vmanip.m_data[vi1] = treenode;
+ }
+ vmanip.m_data[vmanip.m_area.index(p0)] = treenode;
+
+ s16 trunk_h = pr.range(8, 12);
+ v3s16 p1 = p0;
+ for (s16 ii = 0; ii < trunk_h; ii++) {
+ if (vmanip.m_area.contains(p1)) {
+ u32 vi = vmanip.m_area.index(p1);
+ vmanip.m_data[vi] = treenode;
+ }
+ p1.Y++;
+ }
+
+ // p1 is now the last piece of the trunk
+ p1.Y -= 1;
+
+ VoxelArea leaves_a(v3s16(-3, -2, -3), v3s16(3, 2, 3));
+ //SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]);
+ Buffer<u8> leaves_d(leaves_a.getVolume());
+ for (s32 i = 0; i < leaves_a.getVolume(); i++)
+ leaves_d[i] = 0;
+
+ // Force leaves at near the end of the trunk
+ s16 d = 1;
+ for (s16 z = -d; z <= d; z++)
+ for (s16 y = -d; y <= d; y++)
+ for (s16 x = -d; x <= d; x++) {
+ leaves_d[leaves_a.index(v3s16(x,y,z))] = 1;
+ }
+
+ // Add leaves randomly
+ for (u32 iii = 0; iii < 30; iii++) {
+ v3s16 p(
+ pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d),
+ pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d),
+ pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d)
+ );
+
+ for (s16 z = 0; z <= d; z++)
+ for (s16 y = 0; y <= d; y++)
+ for (s16 x = 0; x <= d; x++) {
+ leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1;
+ }
+ }
+
+ // Blit leaves to vmanip
+ for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
+ for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
+ v3s16 pmin(leaves_a.MinEdge.X, y, z);
+ u32 i = leaves_a.index(pmin);
+ u32 vi = vmanip.m_area.index(pmin + p1);
+ for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
+ v3s16 p(x, y, z);
+ if (vmanip.m_area.contains(p + p1) &&
+ (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
+ vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) {
+ if (leaves_d[i] == 1)
+ vmanip.m_data[vi] = leavesnode;
+ }
+ vi++;
+ i++;
+ }
+ }
+}
+
+
+void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed)
+{
+ /*
+ NOTE: Tree-placing code is currently duplicated in the engine
+ and in games that have saplings; both are deprecated but not
+ replaced yet
+ */
+ content_t c_tree = ndef->getId("mapgen_pine_tree");
+ content_t c_leaves = ndef->getId("mapgen_pine_needles");
+ content_t c_snow = ndef->getId("mapgen_snow");
+ if (c_tree == CONTENT_IGNORE)
+ c_tree = ndef->getId("mapgen_tree");
+ if (c_leaves == CONTENT_IGNORE)
+ c_leaves = ndef->getId("mapgen_leaves");
+ if (c_snow == CONTENT_IGNORE)
+ c_snow = CONTENT_AIR;
+
+ MapNode treenode(c_tree);
+ MapNode leavesnode(c_leaves);
+ MapNode snownode(c_snow);
+
+ PseudoRandom pr(seed);
+ u16 trunk_h = pr.range(9, 13);
+ v3s16 p1 = p0;
+ for (u16 ii = 0; ii < trunk_h; ii++) {
+ if (vmanip.m_area.contains(p1)) {
+ u32 vi = vmanip.m_area.index(p1);
+ vmanip.m_data[vi] = treenode;
+ }
+ p1.Y++;
+ }
+
+ // Make p1 the top node of the trunk
+ p1.Y -= 1;
+
+ VoxelArea leaves_a(v3s16(-3, -6, -3), v3s16(3, 3, 3));
+ Buffer<u8> leaves_d(leaves_a.getVolume());
+ for (s32 i = 0; i < leaves_a.getVolume(); i++)
+ leaves_d[i] = 0;
+
+ // Upper branches
+ u16 dev = 3;
+ for (s16 yy = -1; yy <= 1; yy++) {
+ for (s16 zz = -dev; zz <= dev; zz++) {
+ u32 i = leaves_a.index(v3s16(-dev, yy, zz));
+ u32 ia = leaves_a.index(v3s16(-dev, yy+1, zz));
+ for (s16 xx = -dev; xx <= dev; xx++) {
+ if (pr.range(0, 20) <= 19 - dev) {
+ leaves_d[i] = 1;
+ leaves_d[ia] = 2;
+ }
+ i++;
+ ia++;
+ }
+ }
+ dev--;
+ }
+
+ // Centre top nodes
+ leaves_d[leaves_a.index(v3s16(0, 1, 0))] = 1;
+ leaves_d[leaves_a.index(v3s16(0, 2, 0))] = 1;
+ leaves_d[leaves_a.index(v3s16(0, 3, 0))] = 2;
+
+ // Lower branches
+ s16 my = -6;
+ for (u32 iii = 0; iii < 20; iii++) {
+ s16 xi = pr.range(-3, 2);
+ s16 yy = pr.range(-6, -5);
+ s16 zi = pr.range(-3, 2);
+ if (yy > my)
+ my = yy;
+ for (s16 zz = zi; zz <= zi + 1; zz++) {
+ u32 i = leaves_a.index(v3s16(xi, yy, zz));
+ u32 ia = leaves_a.index(v3s16(xi, yy + 1, zz));
+ for (s32 xx = xi; xx <= xi + 1; xx++) {
+ leaves_d[i] = 1;
+ if (leaves_d[ia] == 0)
+ leaves_d[ia] = 2;
+ i++;
+ ia++;
+ }
+ }
+ }
+
+ dev = 2;
+ for (s16 yy = my + 1; yy <= my + 2; yy++) {
+ for (s16 zz = -dev; zz <= dev; zz++) {
+ u32 i = leaves_a.index(v3s16(-dev, yy, zz));
+ u32 ia = leaves_a.index(v3s16(-dev, yy + 1, zz));
+ for (s16 xx = -dev; xx <= dev; xx++) {
+ if (pr.range(0, 20) <= 19 - dev) {
+ leaves_d[i] = 1;
+ leaves_d[ia] = 2;
+ }
+ i++;
+ ia++;
+ }
+ }
+ dev--;
+ }
+
+ // Blit leaves to vmanip
+ for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
+ for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
+ v3s16 pmin(leaves_a.MinEdge.X, y, z);
+ u32 i = leaves_a.index(pmin);
+ u32 vi = vmanip.m_area.index(pmin + p1);
+ for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
+ v3s16 p(x, y, z);
+ if (vmanip.m_area.contains(p + p1) &&
+ (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
+ vmanip.m_data[vi].getContent() == CONTENT_IGNORE ||
+ vmanip.m_data[vi] == snownode)) {
+ if (leaves_d[i] == 1)
+ vmanip.m_data[vi] = leavesnode;
+ else if (leaves_d[i] == 2)
+ vmanip.m_data[vi] = snownode;
+ }
+ vi++;
+ i++;
+ }
+ }
+}
+
+}; // namespace treegen
--- /dev/null
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>,
+ 2012-2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <matrix4.h>
+#include "noise.h"
+
+class MMVManip;
+class INodeDefManager;
+class ServerEnvironment;
+
+
+namespace treegen {
+
+ enum error {
+ SUCCESS,
+ UNBALANCED_BRACKETS
+ };
+
+ struct TreeDef {
+ std::string initial_axiom;
+ std::string rules_a;
+ std::string rules_b;
+ std::string rules_c;
+ std::string rules_d;
+
+ MapNode trunknode;
+ MapNode leavesnode;
+ MapNode leaves2node;
+
+ int leaves2_chance;
+ int angle;
+ int iterations;
+ int iterations_random_level;
+ std::string trunk_type;
+ bool thin_branches;
+ MapNode fruitnode;
+ int fruit_chance;
+ s32 seed;
+ bool explicit_seed;
+ };
+
+ // Add default tree
+ void make_tree(MMVManip &vmanip, v3s16 p0,
+ bool is_apple_tree, INodeDefManager *ndef, s32 seed);
+ // Add jungle tree
+ void make_jungletree(MMVManip &vmanip, v3s16 p0,
+ INodeDefManager *ndef, s32 seed);
+ // Add pine tree
+ void make_pine_tree(MMVManip &vmanip, v3s16 p0,
+ INodeDefManager *ndef, s32 seed);
+
+ // Add L-Systems tree (used by engine)
+ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef,
+ TreeDef tree_definition);
+ // Spawn L-systems tree from LUA
+ treegen::error spawn_ltree (ServerEnvironment *env, v3s16 p0, INodeDefManager *ndef,
+ const TreeDef &tree_definition);
+
+ // L-System tree gen helper functions
+ void tree_node_placement(MMVManip &vmanip, v3f p0,
+ MapNode node);
+ void tree_trunk_placement(MMVManip &vmanip, v3f p0,
+ TreeDef &tree_definition);
+ void tree_leaves_placement(MMVManip &vmanip, v3f p0,
+ PseudoRandom ps, TreeDef &tree_definition);
+ void tree_single_leaves_placement(MMVManip &vmanip, v3f p0,
+ PseudoRandom ps, TreeDef &tree_definition);
+ void tree_fruit_placement(MMVManip &vmanip, v3f p0,
+ TreeDef &tree_definition);
+ irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis);
+
+ v3f transposeMatrix(irr::core::matrix4 M ,v3f v);
+
+}; // namespace treegen
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2016 paramat, Matt Gregory
-Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2017 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-
-#include <cmath>
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "content_sao.h"
-#include "nodedef.h"
-#include "voxelalgorithms.h"
-//#include "profiler.h" // For TimeTaker
-#include "settings.h" // For g_settings
-#include "emerge.h"
-#include "dungeongen.h"
-#include "cavegen.h"
-#include "mg_biome.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mapgen_carpathian.h"
-
-
-FlagDesc flagdesc_mapgen_carpathian[] = {
- {"caverns", MGCARPATHIAN_CAVERNS},
- {NULL, 0}
-};
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-MapgenCarpathian::MapgenCarpathian(
- int mapgenid, MapgenCarpathianParams *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
-{
- spflags = params->spflags;
- cave_width = params->cave_width;
- large_cave_depth = params->large_cave_depth;
- lava_depth = params->lava_depth;
- cavern_limit = params->cavern_limit;
- cavern_taper = params->cavern_taper;
- cavern_threshold = params->cavern_threshold;
- grad_wl = 1 - water_level;
-
- //// 2D Terrain noise
- noise_base = new Noise(¶ms->np_base, seed, csize.X, csize.Z);
- noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
- noise_height1 = new Noise(¶ms->np_height1, seed, csize.X, csize.Z);
- noise_height2 = new Noise(¶ms->np_height2, seed, csize.X, csize.Z);
- noise_height3 = new Noise(¶ms->np_height3, seed, csize.X, csize.Z);
- noise_height4 = new Noise(¶ms->np_height4, seed, csize.X, csize.Z);
- noise_hills_terrain = new Noise(¶ms->np_hills_terrain, seed, csize.X, csize.Z);
- noise_ridge_terrain = new Noise(¶ms->np_ridge_terrain, seed, csize.X, csize.Z);
- noise_step_terrain = new Noise(¶ms->np_step_terrain, seed, csize.X, csize.Z);
- noise_hills = new Noise(¶ms->np_hills, seed, csize.X, csize.Z);
- noise_ridge_mnt = new Noise(¶ms->np_ridge_mnt, seed, csize.X, csize.Z);
- noise_step_mnt = new Noise(¶ms->np_step_mnt, seed, csize.X, csize.Z);
-
- //// 3D terrain noise
- // 1 up 1 down overgeneration
- noise_mnt_var = new Noise(¶ms->np_mnt_var, seed, csize.X, csize.Y + 2, csize.Z);
-
- //// Cave noise
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
- MapgenBasic::np_cavern = params->np_cavern;
-}
-
-
-MapgenCarpathian::~MapgenCarpathian()
-{
- delete noise_base;
- delete noise_filler_depth;
- delete noise_height1;
- delete noise_height2;
- delete noise_height3;
- delete noise_height4;
- delete noise_hills_terrain;
- delete noise_ridge_terrain;
- delete noise_step_terrain;
- delete noise_hills;
- delete noise_ridge_mnt;
- delete noise_step_mnt;
- delete noise_mnt_var;
-}
-
-
-MapgenCarpathianParams::MapgenCarpathianParams():
- np_base (12, 1, v3f(2557, 2557, 2557), 6538, 4, 0.8, 0.5),
- np_filler_depth (0, 1, v3f(128, 128, 128), 261, 3, 0.7, 2.0),
- np_height1 (0, 5, v3f(251, 251, 251), 9613, 5, 0.5, 2.0),
- np_height2 (0, 5, v3f(383, 383, 383), 1949, 5, 0.5, 2.0),
- np_height3 (0, 5, v3f(509, 509, 509), 3211, 5, 0.5, 2.0),
- np_height4 (0, 5, v3f(631, 631, 631), 1583, 5, 0.5, 2.0),
- np_hills_terrain (1, 1, v3f(1301, 1301, 1301), 1692, 5, 0.5, 2.0),
- np_ridge_terrain (1, 1, v3f(1889, 1889, 1889), 3568, 5, 0.5, 2.0),
- np_step_terrain (1, 1, v3f(1889, 1889, 1889), 4157, 5, 0.5, 2.0),
- np_hills (0, 3, v3f(257, 257, 257), 6604, 6, 0.5, 2.0),
- np_ridge_mnt (0, 12, v3f(743, 743, 743), 5520, 6, 0.7, 2.0),
- np_step_mnt (0, 8, v3f(509, 509, 509), 2590, 6, 0.6, 2.0),
- np_mnt_var (0, 1, v3f(499, 499, 499), 2490, 5, 0.55, 2.0),
- np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
- np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0)
-{
-}
-
-
-void MapgenCarpathianParams::readParams(const Settings *settings)
-{
- settings->getFlagStrNoEx("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian);
- settings->getFloatNoEx("mgcarpathian_cave_width", cave_width);
- settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth);
- settings->getS16NoEx("mgcarpathian_lava_depth", lava_depth);
- settings->getS16NoEx("mgcarpathian_cavern_limit", cavern_limit);
- settings->getS16NoEx("mgcarpathian_cavern_taper", cavern_taper);
- settings->getFloatNoEx("mgcarpathian_cavern_threshold", cavern_threshold);
-
- settings->getNoiseParams("mgcarpathian_np_base", np_base);
- settings->getNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth);
- settings->getNoiseParams("mgcarpathian_np_height1", np_height1);
- settings->getNoiseParams("mgcarpathian_np_height2", np_height2);
- settings->getNoiseParams("mgcarpathian_np_height3", np_height3);
- settings->getNoiseParams("mgcarpathian_np_height4", np_height4);
- settings->getNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain);
- settings->getNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain);
- settings->getNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain);
- settings->getNoiseParams("mgcarpathian_np_hills", np_hills);
- settings->getNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt);
- settings->getNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt);
- settings->getNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var);
- settings->getNoiseParams("mgcarpathian_np_cave1", np_cave1);
- settings->getNoiseParams("mgcarpathian_np_cave2", np_cave2);
- settings->getNoiseParams("mgcarpathian_np_cavern", np_cavern);
-}
-
-
-void MapgenCarpathianParams::writeParams(Settings *settings) const
-{
- settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian, U32_MAX);
- settings->setFloat("mgcarpathian_cave_width", cave_width);
- settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth);
- settings->setS16("mgcarpathian_lava_depth", lava_depth);
- settings->setS16("mgcarpathian_cavern_limit", cavern_limit);
- settings->setS16("mgcarpathian_cavern_taper", cavern_taper);
- settings->setFloat("mgcarpathian_cavern_threshold", cavern_threshold);
-
- settings->setNoiseParams("mgcarpathian_np_base", np_base);
- settings->setNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth);
- settings->setNoiseParams("mgcarpathian_np_height1", np_height1);
- settings->setNoiseParams("mgcarpathian_np_height2", np_height2);
- settings->setNoiseParams("mgcarpathian_np_height3", np_height3);
- settings->setNoiseParams("mgcarpathian_np_height4", np_height4);
- settings->setNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain);
- settings->setNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain);
- settings->setNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain);
- settings->setNoiseParams("mgcarpathian_np_hills", np_hills);
- settings->setNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt);
- settings->setNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt);
- settings->setNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var);
- settings->setNoiseParams("mgcarpathian_np_cave1", np_cave1);
- settings->setNoiseParams("mgcarpathian_np_cave2", np_cave2);
- settings->setNoiseParams("mgcarpathian_np_cavern", np_cavern);
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-// Lerp function
-inline float MapgenCarpathian::getLerp(float noise1, float noise2, float mod)
-{
- return noise1 + mod * (noise2 - noise1);
-}
-
-// Steps function
-float MapgenCarpathian::getSteps(float noise)
-{
- float w = 0.5f;
- float k = floor(noise / w);
- float f = (noise - k * w) / w;
- float s = std::fmin(2.f * f, 1.f);
- return (k + s) * w;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-void MapgenCarpathian::makeChunk(BlockMakeData *data)
-{
- // Pre-conditions
- assert(data->vmanip);
- assert(data->nodedef);
- assert(data->blockpos_requested.X >= data->blockpos_min.X &&
- data->blockpos_requested.Y >= data->blockpos_min.Y &&
- data->blockpos_requested.Z >= data->blockpos_min.Z);
- assert(data->blockpos_requested.X <= data->blockpos_max.X &&
- data->blockpos_requested.Y <= data->blockpos_max.Y &&
- data->blockpos_requested.Z <= data->blockpos_max.Z);
-
- this->generating = true;
- this->vm = data->vmanip;
- this->ndef = data->nodedef;
-
- v3s16 blockpos_min = data->blockpos_min;
- v3s16 blockpos_max = data->blockpos_max;
- node_min = blockpos_min * MAP_BLOCKSIZE;
- node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
- full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
- full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- // Create a block-specific seed
- blockseed = getBlockSeed2(full_node_min, seed);
-
- // Generate terrain
- s16 stone_surface_max_y = generateTerrain();
-
- // Create heightmap
- updateHeightmap(node_min, node_max);
-
- // Init biome generator, place biome-specific nodes, and build biomemap
- biomegen->calcBiomeNoise(node_min);
-
- MgStoneType mgstone_type;
- content_t biome_stone;
- generateBiomes(&mgstone_type, &biome_stone);
-
- // Generate caverns, tunnels and classic caves
- if (flags & MG_CAVES) {
- bool has_cavern = false;
- // Generate caverns
- if (spflags & MGCARPATHIAN_CAVERNS)
- has_cavern = generateCaverns(stone_surface_max_y);
- // Generate tunnels and classic caves
- if (has_cavern)
- // Disable classic caves in this mapchunk by setting
- // 'large cave depth' to world base. Avoids excessive liquid in
- // large caverns and floating blobs of overgenerated liquid.
- generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
- else
- generateCaves(stone_surface_max_y, large_cave_depth);
- }
-
- // Generate dungeons
- if (flags & MG_DUNGEONS)
- generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
-
- // Generate the registered decorations
- if (flags & MG_DECORATIONS)
- m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
-
- // Generate the registered ores
- m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
-
- // Sprinkle some dust on top after everything else was generated
- dustTopNodes();
-
- // Update liquids
- updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
-
- // Calculate lighting
- if (flags & MG_LIGHT) {
- calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
- full_node_min, full_node_max);
- }
-
- this->generating = false;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p)
-{
- s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
- if (level_at_point <= water_level || level_at_point > water_level + 32)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- return level_at_point;
-}
-
-
-float MapgenCarpathian::terrainLevelAtPoint(s16 x, s16 z)
-{
- float ground = NoisePerlin2D(&noise_base->np, x, z, seed);
- float height1 = NoisePerlin2D(&noise_height1->np, x, z, seed);
- float height2 = NoisePerlin2D(&noise_height2->np, x, z, seed);
- float height3 = NoisePerlin2D(&noise_height3->np, x, z, seed);
- float height4 = NoisePerlin2D(&noise_height4->np, x, z, seed);
- float hter = NoisePerlin2D(&noise_hills_terrain->np, x, z, seed);
- float rter = NoisePerlin2D(&noise_ridge_terrain->np, x, z, seed);
- float ster = NoisePerlin2D(&noise_step_terrain->np, x, z, seed);
- float n_hills = NoisePerlin2D(&noise_hills->np, x, z, seed);
- float n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, x, z, seed);
- float n_step_mnt = NoisePerlin2D(&noise_step_mnt->np, x, z, seed);
-
- int height = -MAX_MAP_GENERATION_LIMIT;
-
- for (s16 y = 1; y <= 30; y++) {
- float mnt_var = NoisePerlin3D(&noise_mnt_var->np, x, y, z, seed);
-
- // Gradient & shallow seabed
- s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y;
-
- // Hill/Mountain height (hilliness)
- float hill1 = getLerp(height1, height2, mnt_var);
- float hill2 = getLerp(height3, height4, mnt_var);
- float hill3 = getLerp(height3, height2, mnt_var);
- float hill4 = getLerp(height1, height4, mnt_var);
- float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
-
- // Rolling hills
- float hill_mnt = hilliness * pow(n_hills, 2.f);
- float hills = pow(hter, 3.f) * hill_mnt;
-
- // Ridged mountains
- float ridge_mnt = hilliness * (1.f - fabs(n_ridge_mnt));
- float ridged_mountains = pow(rter, 3.f) * ridge_mnt;
-
- // Step (terraced) mountains
- float step_mnt = hilliness * getSteps(n_step_mnt);
- float step_mountains = pow(ster, 3.f) * step_mnt;
-
- // Final terrain level
- float mountains = hills + ridged_mountains + step_mountains;
- float surface_level = ground + mountains + grad;
-
- if (y > surface_level && height < 0)
- height = y;
- }
-
- return height;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-int MapgenCarpathian::generateTerrain()
-{
- MapNode mn_air(CONTENT_AIR);
- MapNode mn_stone(c_stone);
- MapNode mn_water(c_water_source);
-
- s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
- u32 index2d = 0;
- u32 index3d = 0;
-
- // Calculate noise for terrain generation
- noise_base->perlinMap2D(node_min.X, node_min.Z);
- noise_height1->perlinMap2D(node_min.X, node_min.Z);
- noise_height2->perlinMap2D(node_min.X, node_min.Z);
- noise_height3->perlinMap2D(node_min.X, node_min.Z);
- noise_height4->perlinMap2D(node_min.X, node_min.Z);
- noise_hills_terrain->perlinMap2D(node_min.X, node_min.Z);
- noise_ridge_terrain->perlinMap2D(node_min.X, node_min.Z);
- noise_step_terrain->perlinMap2D(node_min.X, node_min.Z);
- noise_hills->perlinMap2D(node_min.X, node_min.Z);
- noise_ridge_mnt->perlinMap2D(node_min.X, node_min.Z);
- noise_step_mnt->perlinMap2D(node_min.X, node_min.Z);
- noise_mnt_var->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
-
- //// Place nodes
- for (s16 z = node_min.Z; z <= node_max.Z; z++) {
- for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
- u32 vi = vm->m_area.index(node_min.X, y, z);
- for (s16 x = node_min.X; x <= node_max.X;
- x++, vi++, index2d++, index3d++) {
- if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
- continue;
-
- // Base terrain
- float ground = noise_base->result[index2d];
-
- // Gradient & shallow seabed
- s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y;
-
- // Hill/Mountain height (hilliness)
- float height1 = noise_height1->result[index2d];
- float height2 = noise_height2->result[index2d];
- float height3 = noise_height3->result[index2d];
- float height4 = noise_height4->result[index2d];
- float mnt_var = noise_mnt_var->result[index3d];
- // Combine height noises and apply 3D variation
- float hill1 = getLerp(height1, height2, mnt_var);
- float hill2 = getLerp(height3, height4, mnt_var);
- float hill3 = getLerp(height3, height2, mnt_var);
- float hill4 = getLerp(height1, height4, mnt_var);
- // 'hilliness' determines whether hills/mountains are
- // small or large
- float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
-
- // Rolling hills
- float hter = noise_hills_terrain->result[index2d];
- float n_hills = noise_hills->result[index2d];
- float hill_mnt = hilliness * pow(n_hills, 2.f);
- float hills = pow(fabs(hter), 3.f) * hill_mnt;
-
- // Ridged mountains
- float rter = noise_ridge_terrain->result[index2d];
- float n_ridge_mnt = noise_ridge_mnt->result[index2d];
- float ridge_mnt = hilliness * (1.f - fabs(n_ridge_mnt));
- float ridged_mountains = pow(fabs(rter), 3.f) * ridge_mnt;
-
- // Step (terraced) mountains
- float ster = noise_step_terrain->result[index2d];
- float n_step_mnt = noise_step_mnt->result[index2d];
- float step_mnt = hilliness * getSteps(n_step_mnt);
- float step_mountains = pow(fabs(ster), 3.f) * step_mnt;
-
- // Final terrain level
- float mountains = hills + ridged_mountains + step_mountains;
- float surface_level = ground + mountains + grad;
-
- if (y < surface_level) {
- vm->m_data[vi] = mn_stone; // Stone
- if (y > stone_surface_max_y)
- stone_surface_max_y = y;
- } else if (y <= water_level) {
- vm->m_data[vi] = mn_water; // Sea water
- } else {
- vm->m_data[vi] = mn_air; // Air
- }
- }
- index2d -= ystride;
- }
- index2d += ystride;
- }
-
- return stone_surface_max_y;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2016 paramat, Matt Gregory
-Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2017 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "mapgen.h"
-
-///////// Mapgen Carpathian flags
-#define MGCARPATHIAN_CAVERNS 0x01
-
-class BiomeManager;
-
-extern FlagDesc flagdesc_mapgen_carpathian[];
-
-
-struct MapgenCarpathianParams : public MapgenParams
-{
- u32 spflags = MGCARPATHIAN_CAVERNS;
- float cave_width = 0.09f;
- s16 large_cave_depth = -33;
- s16 lava_depth = -256;
- s16 cavern_limit = -256;
- s16 cavern_taper = 256;
- float cavern_threshold = 0.7f;
-
- NoiseParams np_base;
- NoiseParams np_filler_depth;
- NoiseParams np_height1;
- NoiseParams np_height2;
- NoiseParams np_height3;
- NoiseParams np_height4;
- NoiseParams np_hills_terrain;
- NoiseParams np_ridge_terrain;
- NoiseParams np_step_terrain;
- NoiseParams np_hills;
- NoiseParams np_ridge_mnt;
- NoiseParams np_step_mnt;
- NoiseParams np_mnt_var;
- NoiseParams np_cave1;
- NoiseParams np_cave2;
- NoiseParams np_cavern;
-
- MapgenCarpathianParams();
- ~MapgenCarpathianParams() = default;
-
- void readParams(const Settings *settings);
- void writeParams(Settings *settings) const;
-};
-
-class MapgenCarpathian : public MapgenBasic
-{
-public:
- MapgenCarpathian(int mapgenid, MapgenCarpathianParams *params,
- EmergeManager *emerge);
- ~MapgenCarpathian();
-
- virtual MapgenType getType() const { return MAPGEN_CARPATHIAN; }
-
- float getSteps(float noise);
- inline float getLerp(float noise1, float noise2, float mod);
-
- virtual void makeChunk(BlockMakeData *data);
- int getSpawnLevelAtPoint(v2s16 p);
-
-private:
- s16 large_cave_depth;
- s32 grad_wl;
-
- Noise *noise_base;
- Noise *noise_height1;
- Noise *noise_height2;
- Noise *noise_height3;
- Noise *noise_height4;
- Noise *noise_hills_terrain;
- Noise *noise_ridge_terrain;
- Noise *noise_step_terrain;
- Noise *noise_hills;
- Noise *noise_ridge_mnt;
- Noise *noise_step_mnt;
- Noise *noise_mnt_var;
-
- float terrainLevelAtPoint(s16 x, s16 z);
- int generateTerrain();
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2015-2017 paramat
-Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "content_sao.h"
-#include "nodedef.h"
-#include "voxelalgorithms.h"
-//#include "profiler.h" // For TimeTaker
-#include "settings.h" // For g_settings
-#include "emerge.h"
-#include "dungeongen.h"
-#include "cavegen.h"
-#include "mg_biome.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mapgen_flat.h"
-
-
-FlagDesc flagdesc_mapgen_flat[] = {
- {"lakes", MGFLAT_LAKES},
- {"hills", MGFLAT_HILLS},
- {NULL, 0}
-};
-
-///////////////////////////////////////////////////////////////////////////////////////
-
-
-MapgenFlat::MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
-{
- spflags = params->spflags;
- ground_level = params->ground_level;
- large_cave_depth = params->large_cave_depth;
- lava_depth = params->lava_depth;
- cave_width = params->cave_width;
- lake_threshold = params->lake_threshold;
- lake_steepness = params->lake_steepness;
- hill_threshold = params->hill_threshold;
- hill_steepness = params->hill_steepness;
-
- // 2D noise
- noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
-
- if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
- noise_terrain = new Noise(¶ms->np_terrain, seed, csize.X, csize.Z);
- // 3D noise
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
-}
-
-
-MapgenFlat::~MapgenFlat()
-{
- delete noise_filler_depth;
-
- if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
- delete noise_terrain;
-}
-
-
-MapgenFlatParams::MapgenFlatParams():
- np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0),
- np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
- np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
-{
-}
-
-
-void MapgenFlatParams::readParams(const Settings *settings)
-{
- settings->getFlagStrNoEx("mgflat_spflags", spflags, flagdesc_mapgen_flat);
- settings->getS16NoEx("mgflat_ground_level", ground_level);
- settings->getS16NoEx("mgflat_large_cave_depth", large_cave_depth);
- settings->getS16NoEx("mgflat_lava_depth", lava_depth);
- settings->getFloatNoEx("mgflat_cave_width", cave_width);
- settings->getFloatNoEx("mgflat_lake_threshold", lake_threshold);
- settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness);
- settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold);
- settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness);
-
- settings->getNoiseParams("mgflat_np_terrain", np_terrain);
- settings->getNoiseParams("mgflat_np_filler_depth", np_filler_depth);
- settings->getNoiseParams("mgflat_np_cave1", np_cave1);
- settings->getNoiseParams("mgflat_np_cave2", np_cave2);
-}
-
-
-void MapgenFlatParams::writeParams(Settings *settings) const
-{
- settings->setFlagStr("mgflat_spflags", spflags, flagdesc_mapgen_flat, U32_MAX);
- settings->setS16("mgflat_ground_level", ground_level);
- settings->setS16("mgflat_large_cave_depth", large_cave_depth);
- settings->setS16("mgflat_lava_depth", lava_depth);
- settings->setFloat("mgflat_cave_width", cave_width);
- settings->setFloat("mgflat_lake_threshold", lake_threshold);
- settings->setFloat("mgflat_lake_steepness", lake_steepness);
- settings->setFloat("mgflat_hill_threshold", hill_threshold);
- settings->setFloat("mgflat_hill_steepness", hill_steepness);
-
- settings->setNoiseParams("mgflat_np_terrain", np_terrain);
- settings->setNoiseParams("mgflat_np_filler_depth", np_filler_depth);
- settings->setNoiseParams("mgflat_np_cave1", np_cave1);
- settings->setNoiseParams("mgflat_np_cave2", np_cave2);
-}
-
-
-/////////////////////////////////////////////////////////////////
-
-
-int MapgenFlat::getSpawnLevelAtPoint(v2s16 p)
-{
- s16 level_at_point = ground_level;
- float n_terrain = 0.0f;
- if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
- n_terrain = NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed);
-
- if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
- level_at_point = ground_level -
- (lake_threshold - n_terrain) * lake_steepness;
- } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
- level_at_point = ground_level +
- (n_terrain - hill_threshold) * hill_steepness;
- }
-
- if (ground_level < water_level) // Ocean world, allow spawn in water
- return MYMAX(level_at_point, water_level);
-
- if (level_at_point > water_level)
- return level_at_point; // Spawn on land
-
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-}
-
-
-void MapgenFlat::makeChunk(BlockMakeData *data)
-{
- // Pre-conditions
- assert(data->vmanip);
- assert(data->nodedef);
- assert(data->blockpos_requested.X >= data->blockpos_min.X &&
- data->blockpos_requested.Y >= data->blockpos_min.Y &&
- data->blockpos_requested.Z >= data->blockpos_min.Z);
- assert(data->blockpos_requested.X <= data->blockpos_max.X &&
- data->blockpos_requested.Y <= data->blockpos_max.Y &&
- data->blockpos_requested.Z <= data->blockpos_max.Z);
-
- this->generating = true;
- this->vm = data->vmanip;
- this->ndef = data->nodedef;
- //TimeTaker t("makeChunk");
-
- v3s16 blockpos_min = data->blockpos_min;
- v3s16 blockpos_max = data->blockpos_max;
- node_min = blockpos_min * MAP_BLOCKSIZE;
- node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
- full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
- full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- blockseed = getBlockSeed2(full_node_min, seed);
-
- // Generate base terrain, mountains, and ridges with initial heightmaps
- s16 stone_surface_max_y = generateTerrain();
-
- // Create heightmap
- updateHeightmap(node_min, node_max);
-
- // Init biome generator, place biome-specific nodes, and build biomemap
- biomegen->calcBiomeNoise(node_min);
-
- MgStoneType mgstone_type;
- content_t biome_stone;
- generateBiomes(&mgstone_type, &biome_stone);
-
- if (flags & MG_CAVES)
- generateCaves(stone_surface_max_y, large_cave_depth);
-
- if (flags & MG_DUNGEONS)
- generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
-
- // Generate the registered decorations
- if (flags & MG_DECORATIONS)
- m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
-
- // Generate the registered ores
- m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
-
- // Sprinkle some dust on top after everything else was generated
- dustTopNodes();
-
- //printf("makeChunk: %dms\n", t.stop());
-
- updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
-
- if (flags & MG_LIGHT)
- calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
- full_node_min, full_node_max);
-
- //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
- // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
-
- this->generating = false;
-}
-
-
-s16 MapgenFlat::generateTerrain()
-{
- MapNode n_air(CONTENT_AIR);
- MapNode n_stone(c_stone);
- MapNode n_water(c_water_source);
-
- const v3s16 &em = vm->m_area.getExtent();
- s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
- u32 ni2d = 0;
-
- bool use_noise = (spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS);
- if (use_noise)
- noise_terrain->perlinMap2D(node_min.X, node_min.Z);
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, ni2d++) {
- s16 stone_level = ground_level;
- float n_terrain = use_noise ? noise_terrain->result[ni2d] : 0.0f;
-
- if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
- s16 depress = (lake_threshold - n_terrain) * lake_steepness;
- stone_level = ground_level - depress;
- } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
- s16 rise = (n_terrain - hill_threshold) * hill_steepness;
- stone_level = ground_level + rise;
- }
-
- u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
- for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
- if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
- if (y <= stone_level) {
- vm->m_data[vi] = n_stone;
- if (y > stone_surface_max_y)
- stone_surface_max_y = y;
- } else if (y <= water_level) {
- vm->m_data[vi] = n_water;
- } else {
- vm->m_data[vi] = n_air;
- }
- }
- vm->m_area.add_y(em, vi, 1);
- }
- }
-
- return stone_surface_max_y;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2015-2017 paramat
-Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "mapgen.h"
-
-/////// Mapgen Flat flags
-#define MGFLAT_LAKES 0x01
-#define MGFLAT_HILLS 0x02
-
-class BiomeManager;
-
-extern FlagDesc flagdesc_mapgen_flat[];
-
-struct MapgenFlatParams : public MapgenParams
-{
- u32 spflags = 0;
- s16 ground_level = 8;
- s16 large_cave_depth = -33;
- s16 lava_depth = -256;
- float cave_width = 0.09f;
- float lake_threshold = -0.45f;
- float lake_steepness = 48.0f;
- float hill_threshold = 0.45f;
- float hill_steepness = 64.0f;
- NoiseParams np_terrain;
- NoiseParams np_filler_depth;
- NoiseParams np_cave1;
- NoiseParams np_cave2;
-
- MapgenFlatParams();
- ~MapgenFlatParams() = default;
-
- void readParams(const Settings *settings);
- void writeParams(Settings *settings) const;
-};
-
-class MapgenFlat : public MapgenBasic
-{
-public:
- MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge);
- ~MapgenFlat();
-
- virtual MapgenType getType() const { return MAPGEN_FLAT; }
-
- virtual void makeChunk(BlockMakeData *data);
- int getSpawnLevelAtPoint(v2s16 p);
- s16 generateTerrain();
-
-private:
- s16 ground_level;
- s16 large_cave_depth;
- float lake_threshold;
- float lake_steepness;
- float hill_threshold;
- float hill_steepness;
- Noise *noise_terrain;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2015-2017 paramat
-Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "content_sao.h"
-#include "nodedef.h"
-#include "voxelalgorithms.h"
-//#include "profiler.h" // For TimeTaker
-#include "settings.h" // For g_settings
-#include "emerge.h"
-#include "dungeongen.h"
-#include "cavegen.h"
-#include "mg_biome.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mapgen_fractal.h"
-
-
-FlagDesc flagdesc_mapgen_fractal[] = {
- {NULL, 0}
-};
-
-///////////////////////////////////////////////////////////////////////////////////////
-
-
-MapgenFractal::MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
-{
- spflags = params->spflags;
- cave_width = params->cave_width;
- large_cave_depth = params->large_cave_depth;
- lava_depth = params->lava_depth;
- fractal = params->fractal;
- iterations = params->iterations;
- scale = params->scale;
- offset = params->offset;
- slice_w = params->slice_w;
- julia_x = params->julia_x;
- julia_y = params->julia_y;
- julia_z = params->julia_z;
- julia_w = params->julia_w;
-
- //// 2D terrain noise
- noise_seabed = new Noise(¶ms->np_seabed, seed, csize.X, csize.Z);
- noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
-
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
-
- formula = fractal / 2 + fractal % 2;
- julia = fractal % 2 == 0;
-}
-
-
-MapgenFractal::~MapgenFractal()
-{
- delete noise_seabed;
- delete noise_filler_depth;
-}
-
-
-MapgenFractalParams::MapgenFractalParams():
- np_seabed (-14, 9, v3f(600, 600, 600), 41900, 5, 0.6, 2.0),
- np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
- np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
-{
-}
-
-
-void MapgenFractalParams::readParams(const Settings *settings)
-{
- settings->getFlagStrNoEx("mgfractal_spflags", spflags, flagdesc_mapgen_fractal);
- settings->getFloatNoEx("mgfractal_cave_width", cave_width);
- settings->getS16NoEx("mgfractal_large_cave_depth", large_cave_depth);
- settings->getS16NoEx("mgfractal_lava_depth", lava_depth);
- settings->getU16NoEx("mgfractal_fractal", fractal);
- settings->getU16NoEx("mgfractal_iterations", iterations);
- settings->getV3FNoEx("mgfractal_scale", scale);
- settings->getV3FNoEx("mgfractal_offset", offset);
- settings->getFloatNoEx("mgfractal_slice_w", slice_w);
- settings->getFloatNoEx("mgfractal_julia_x", julia_x);
- settings->getFloatNoEx("mgfractal_julia_y", julia_y);
- settings->getFloatNoEx("mgfractal_julia_z", julia_z);
- settings->getFloatNoEx("mgfractal_julia_w", julia_w);
-
- settings->getNoiseParams("mgfractal_np_seabed", np_seabed);
- settings->getNoiseParams("mgfractal_np_filler_depth", np_filler_depth);
- settings->getNoiseParams("mgfractal_np_cave1", np_cave1);
- settings->getNoiseParams("mgfractal_np_cave2", np_cave2);
-}
-
-
-void MapgenFractalParams::writeParams(Settings *settings) const
-{
- settings->setFlagStr("mgfractal_spflags", spflags, flagdesc_mapgen_fractal, U32_MAX);
- settings->setFloat("mgfractal_cave_width", cave_width);
- settings->setS16("mgfractal_large_cave_depth", large_cave_depth);
- settings->setS16("mgfractal_lava_depth", lava_depth);
- settings->setU16("mgfractal_fractal", fractal);
- settings->setU16("mgfractal_iterations", iterations);
- settings->setV3F("mgfractal_scale", scale);
- settings->setV3F("mgfractal_offset", offset);
- settings->setFloat("mgfractal_slice_w", slice_w);
- settings->setFloat("mgfractal_julia_x", julia_x);
- settings->setFloat("mgfractal_julia_y", julia_y);
- settings->setFloat("mgfractal_julia_z", julia_z);
- settings->setFloat("mgfractal_julia_w", julia_w);
-
- settings->setNoiseParams("mgfractal_np_seabed", np_seabed);
- settings->setNoiseParams("mgfractal_np_filler_depth", np_filler_depth);
- settings->setNoiseParams("mgfractal_np_cave1", np_cave1);
- settings->setNoiseParams("mgfractal_np_cave2", np_cave2);
-}
-
-
-/////////////////////////////////////////////////////////////////
-
-
-int MapgenFractal::getSpawnLevelAtPoint(v2s16 p)
-{
- bool solid_below = false; // Dry solid node is present below to spawn on
- u8 air_count = 0; // Consecutive air nodes above the dry solid node
- s16 seabed_level = NoisePerlin2D(&noise_seabed->np, p.X, p.Y, seed);
- // Seabed can rise above water_level or might be raised to create dry land
- s16 search_start = MYMAX(seabed_level, water_level + 1);
- if (seabed_level > water_level)
- solid_below = true;
-
- for (s16 y = search_start; y <= search_start + 128; y++) {
- if (getFractalAtPoint(p.X, y, p.Y)) { // Fractal node
- solid_below = true;
- air_count = 0;
- } else if (solid_below) { // Air above solid node
- air_count++;
- // 3 to account for snowblock dust
- if (air_count == 3)
- return y - 2;
- }
- }
-
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-}
-
-
-void MapgenFractal::makeChunk(BlockMakeData *data)
-{
- // Pre-conditions
- assert(data->vmanip);
- assert(data->nodedef);
- assert(data->blockpos_requested.X >= data->blockpos_min.X &&
- data->blockpos_requested.Y >= data->blockpos_min.Y &&
- data->blockpos_requested.Z >= data->blockpos_min.Z);
- assert(data->blockpos_requested.X <= data->blockpos_max.X &&
- data->blockpos_requested.Y <= data->blockpos_max.Y &&
- data->blockpos_requested.Z <= data->blockpos_max.Z);
-
- this->generating = true;
- this->vm = data->vmanip;
- this->ndef = data->nodedef;
- //TimeTaker t("makeChunk");
-
- v3s16 blockpos_min = data->blockpos_min;
- v3s16 blockpos_max = data->blockpos_max;
- node_min = blockpos_min * MAP_BLOCKSIZE;
- node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
- full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
- full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- blockseed = getBlockSeed2(full_node_min, seed);
-
- // Generate base terrain, mountains, and ridges with initial heightmaps
- s16 stone_surface_max_y = generateTerrain();
-
- // Create heightmap
- updateHeightmap(node_min, node_max);
-
- // Init biome generator, place biome-specific nodes, and build biomemap
- biomegen->calcBiomeNoise(node_min);
-
- MgStoneType mgstone_type;
- content_t biome_stone;
- generateBiomes(&mgstone_type, &biome_stone);
-
- if (flags & MG_CAVES)
- generateCaves(stone_surface_max_y, large_cave_depth);
-
- if (flags & MG_DUNGEONS)
- generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
-
- // Generate the registered decorations
- if (flags & MG_DECORATIONS)
- m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
-
- // Generate the registered ores
- m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
-
- // Sprinkle some dust on top after everything else was generated
- dustTopNodes();
-
- //printf("makeChunk: %dms\n", t.stop());
-
- updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
-
- if (flags & MG_LIGHT)
- calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
- full_node_min, full_node_max);
-
- //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
- // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
-
- this->generating = false;
-}
-
-
-bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z)
-{
- float cx, cy, cz, cw, ox, oy, oz, ow;
-
- if (julia) { // Julia set
- cx = julia_x;
- cy = julia_y;
- cz = julia_z;
- cw = julia_w;
- ox = (float)x / scale.X - offset.X;
- oy = (float)y / scale.Y - offset.Y;
- oz = (float)z / scale.Z - offset.Z;
- ow = slice_w;
- } else { // Mandelbrot set
- cx = (float)x / scale.X - offset.X;
- cy = (float)y / scale.Y - offset.Y;
- cz = (float)z / scale.Z - offset.Z;
- cw = slice_w;
- ox = 0.0f;
- oy = 0.0f;
- oz = 0.0f;
- ow = 0.0f;
- }
-
- float nx = 0.0f;
- float ny = 0.0f;
- float nz = 0.0f;
- float nw = 0.0f;
-
- for (u16 iter = 0; iter < iterations; iter++) {
- switch (formula) {
- default:
- case 1: // 4D "Roundy"
- nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
- ny = 2.0f * (ox * oy + oz * ow) + cy;
- nz = 2.0f * (ox * oz + oy * ow) + cz;
- nw = 2.0f * (ox * ow + oy * oz) + cw;
- break;
- case 2: // 4D "Squarry"
- nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
- ny = 2.0f * (ox * oy + oz * ow) + cy;
- nz = 2.0f * (ox * oz + oy * ow) + cz;
- nw = 2.0f * (ox * ow - oy * oz) + cw;
- break;
- case 3: // 4D "Mandy Cousin"
- nx = ox * ox - oy * oy - oz * oz + ow * ow + cx;
- ny = 2.0f * (ox * oy + oz * ow) + cy;
- nz = 2.0f * (ox * oz + oy * ow) + cz;
- nw = 2.0f * (ox * ow + oy * oz) + cw;
- break;
- case 4: // 4D "Variation"
- nx = ox * ox - oy * oy - oz * oz - ow * ow + cx;
- ny = 2.0f * (ox * oy + oz * ow) + cy;
- nz = 2.0f * (ox * oz - oy * ow) + cz;
- nw = 2.0f * (ox * ow + oy * oz) + cw;
- break;
- case 5: // 3D "Mandelbrot/Mandelbar"
- nx = ox * ox - oy * oy - oz * oz + cx;
- ny = 2.0f * ox * oy + cy;
- nz = -2.0f * ox * oz + cz;
- break;
- case 6: // 3D "Christmas Tree"
- // Altering the formula here is necessary to avoid division by zero
- if (fabs(oz) < 0.000000001f) {
- nx = ox * ox - oy * oy - oz * oz + cx;
- ny = 2.0f * oy * ox + cy;
- nz = 4.0f * oz * ox + cz;
- } else {
- float a = (2.0f * ox) / (sqrt(oy * oy + oz * oz));
- nx = ox * ox - oy * oy - oz * oz + cx;
- ny = a * (oy * oy - oz * oz) + cy;
- nz = a * 2.0f * oy * oz + cz;
- }
- break;
- case 7: // 3D "Mandelbulb"
- if (fabs(oy) < 0.000000001f) {
- nx = ox * ox - oz * oz + cx;
- ny = cy;
- nz = -2.0f * oz * sqrt(ox * ox) + cz;
- } else {
- float a = 1.0f - (oz * oz) / (ox * ox + oy * oy);
- nx = (ox * ox - oy * oy) * a + cx;
- ny = 2.0f * ox * oy * a + cy;
- nz = -2.0f * oz * sqrt(ox * ox + oy * oy) + cz;
- }
- break;
- case 8: // 3D "Cosine Mandelbulb"
- if (fabs(oy) < 0.000000001f) {
- nx = 2.0f * ox * oz + cx;
- ny = 4.0f * oy * oz + cy;
- nz = oz * oz - ox * ox - oy * oy + cz;
- } else {
- float a = (2.0f * oz) / sqrt(ox * ox + oy * oy);
- nx = (ox * ox - oy * oy) * a + cx;
- ny = 2.0f * ox * oy * a + cy;
- nz = oz * oz - ox * ox - oy * oy + cz;
- }
- break;
- case 9: // 4D "Mandelbulb"
- float rxy = sqrt(ox * ox + oy * oy);
- float rxyz = sqrt(ox * ox + oy * oy + oz * oz);
- if (fabs(ow) < 0.000000001f && fabs(oz) < 0.000000001f) {
- nx = (ox * ox - oy * oy) + cx;
- ny = 2.0f * ox * oy + cy;
- nz = -2.0f * rxy * oz + cz;
- nw = 2.0f * rxyz * ow + cw;
- } else {
- float a = 1.0f - (ow * ow) / (rxyz * rxyz);
- float b = a * (1.0f - (oz * oz) / (rxy * rxy));
- nx = (ox * ox - oy * oy) * b + cx;
- ny = 2.0f * ox * oy * b + cy;
- nz = -2.0f * rxy * oz * a + cz;
- nw = 2.0f * rxyz * ow + cw;
- }
- break;
- }
-
- if (nx * nx + ny * ny + nz * nz + nw * nw > 4.0f)
- return false;
-
- ox = nx;
- oy = ny;
- oz = nz;
- ow = nw;
- }
-
- return true;
-}
-
-
-s16 MapgenFractal::generateTerrain()
-{
- MapNode n_air(CONTENT_AIR);
- MapNode n_stone(c_stone);
- MapNode n_water(c_water_source);
-
- s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
- u32 index2d = 0;
-
- noise_seabed->perlinMap2D(node_min.X, node_min.Z);
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++) {
- for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
- u32 vi = vm->m_area.index(node_min.X, y, z);
- for (s16 x = node_min.X; x <= node_max.X; x++, vi++, index2d++) {
- if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
- s16 seabed_height = noise_seabed->result[index2d];
-
- if (y <= seabed_height || getFractalAtPoint(x, y, z)) {
- vm->m_data[vi] = n_stone;
- if (y > stone_surface_max_y)
- stone_surface_max_y = y;
- } else if (y <= water_level) {
- vm->m_data[vi] = n_water;
- } else {
- vm->m_data[vi] = n_air;
- }
- }
- }
- index2d -= ystride;
- }
- index2d += ystride;
- }
-
- return stone_surface_max_y;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2015-2017 paramat
-Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-
-Fractal formulas from http://www.bugman123.com/Hypercomplex/index.html
-by Paul Nylander, and from http://www.fractalforums.com, thank you.
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "mapgen.h"
-
-class BiomeManager;
-
-extern FlagDesc flagdesc_mapgen_fractal[];
-
-struct MapgenFractalParams : public MapgenParams
-{
- u32 spflags = 0;
- float cave_width = 0.09f;
- s16 large_cave_depth = -33;
- s16 lava_depth = -256;
- u16 fractal = 1;
- u16 iterations = 11;
- v3f scale = v3f(4096.0, 1024.0, 4096.0);
- v3f offset = v3f(1.52, 0.0, 0.0);
- float slice_w = 0.0f;
- float julia_x = 0.267f;
- float julia_y = 0.2f;
- float julia_z = 0.133f;
- float julia_w = 0.067f;
- NoiseParams np_seabed;
- NoiseParams np_filler_depth;
- NoiseParams np_cave1;
- NoiseParams np_cave2;
-
- MapgenFractalParams();
- ~MapgenFractalParams() = default;
-
- void readParams(const Settings *settings);
- void writeParams(Settings *settings) const;
-};
-
-class MapgenFractal : public MapgenBasic
-{
-public:
- MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge);
- ~MapgenFractal();
-
- virtual MapgenType getType() const { return MAPGEN_FRACTAL; }
-
- virtual void makeChunk(BlockMakeData *data);
- int getSpawnLevelAtPoint(v2s16 p);
- bool getFractalAtPoint(s16 x, s16 y, s16 z);
- s16 generateTerrain();
-
-private:
- u16 formula;
- bool julia;
-
- s16 large_cave_depth;
- u16 fractal;
- u16 iterations;
- v3f scale;
- v3f offset;
- float slice_w;
- float julia_x;
- float julia_y;
- float julia_z;
- float julia_w;
- Noise *noise_seabed;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "mapgen_singlenode.h"
-#include "voxel.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "nodedef.h"
-#include "voxelalgorithms.h"
-#include "emerge.h"
-
-
-MapgenSinglenode::MapgenSinglenode(int mapgenid,
- MapgenParams *params, EmergeManager *emerge)
- : Mapgen(mapgenid, params, emerge)
-{
- flags = params->flags;
-
- INodeDefManager *ndef = emerge->ndef;
-
- c_node = ndef->getId("mapgen_singlenode");
- if (c_node == CONTENT_IGNORE)
- c_node = CONTENT_AIR;
-
- MapNode n_node(c_node);
- set_light = (ndef->get(n_node).sunlight_propagates) ? LIGHT_SUN : 0x00;
-}
-
-
-//////////////////////// Map generator
-
-void MapgenSinglenode::makeChunk(BlockMakeData *data)
-{
- // Pre-conditions
- assert(data->vmanip);
- assert(data->nodedef);
- assert(data->blockpos_requested.X >= data->blockpos_min.X &&
- data->blockpos_requested.Y >= data->blockpos_min.Y &&
- data->blockpos_requested.Z >= data->blockpos_min.Z);
- assert(data->blockpos_requested.X <= data->blockpos_max.X &&
- data->blockpos_requested.Y <= data->blockpos_max.Y &&
- data->blockpos_requested.Z <= data->blockpos_max.Z);
-
- this->generating = true;
- this->vm = data->vmanip;
- this->ndef = data->nodedef;
-
- v3s16 blockpos_min = data->blockpos_min;
- v3s16 blockpos_max = data->blockpos_max;
-
- // Area of central chunk
- v3s16 node_min = blockpos_min * MAP_BLOCKSIZE;
- v3s16 node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- blockseed = getBlockSeed2(node_min, data->seed);
-
- MapNode n_node(c_node);
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 y = node_min.Y; y <= node_max.Y; y++) {
- u32 i = vm->m_area.index(node_min.X, y, z);
- for (s16 x = node_min.X; x <= node_max.X; x++) {
- if (vm->m_data[i].getContent() == CONTENT_IGNORE)
- vm->m_data[i] = n_node;
- i++;
- }
- }
-
- // Add top and bottom side of water to transforming_liquid queue
- updateLiquid(&data->transforming_liquid, node_min, node_max);
-
- // Set lighting
- if ((flags & MG_LIGHT) && set_light == LIGHT_SUN)
- setLighting(LIGHT_SUN, node_min, node_max);
-
- this->generating = false;
-}
-
-
-int MapgenSinglenode::getSpawnLevelAtPoint(v2s16 p)
-{
- return 0;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "mapgen.h"
-
-struct MapgenSinglenodeParams : public MapgenParams
-{
- MapgenSinglenodeParams() = default;
- ~MapgenSinglenodeParams() = default;
-
- void readParams(const Settings *settings) {}
- void writeParams(Settings *settings) const {}
-};
-
-class MapgenSinglenode : public Mapgen
-{
-public:
- u32 flags;
- content_t c_node;
- u8 set_light;
-
- MapgenSinglenode(int mapgenid, MapgenParams *params, EmergeManager *emerge);
- ~MapgenSinglenode() = default;
-
- virtual MapgenType getType() const { return MAPGEN_SINGLENODE; }
-
- void makeChunk(BlockMakeData *data);
- int getSpawnLevelAtPoint(v2s16 p);
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2017 paramat
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "content_sao.h"
-#include "nodedef.h"
-#include "voxelalgorithms.h"
-//#include "profiler.h" // For TimeTaker
-#include "settings.h" // For g_settings
-#include "emerge.h"
-#include "dungeongen.h"
-#include "cavegen.h"
-#include "mg_biome.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mapgen_v5.h"
-
-
-FlagDesc flagdesc_mapgen_v5[] = {
- {"caverns", MGV5_CAVERNS},
- {NULL, 0}
-};
-
-
-MapgenV5::MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
-{
- spflags = params->spflags;
- cave_width = params->cave_width;
- large_cave_depth = params->large_cave_depth;
- lava_depth = params->lava_depth;
- cavern_limit = params->cavern_limit;
- cavern_taper = params->cavern_taper;
- cavern_threshold = params->cavern_threshold;
-
- // Terrain noise
- noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
- noise_factor = new Noise(¶ms->np_factor, seed, csize.X, csize.Z);
- noise_height = new Noise(¶ms->np_height, seed, csize.X, csize.Z);
-
- // 3D terrain noise
- // 1-up 1-down overgeneration
- noise_ground = new Noise(¶ms->np_ground, seed, csize.X, csize.Y + 2, csize.Z);
- // 1 down overgeneration
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
- MapgenBasic::np_cavern = params->np_cavern;
-}
-
-
-MapgenV5::~MapgenV5()
-{
- delete noise_filler_depth;
- delete noise_factor;
- delete noise_height;
- delete noise_ground;
-}
-
-
-MapgenV5Params::MapgenV5Params():
- np_filler_depth (0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0),
- np_factor (0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0),
- np_height (0, 10, v3f(250, 250, 250), 84174, 4, 0.5, 2.0),
- np_ground (0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED),
- np_cave1 (0, 12, v3f(50, 50, 50), 52534, 4, 0.5, 2.0),
- np_cave2 (0, 12, v3f(50, 50, 50), 10325, 4, 0.5, 2.0),
- np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0)
-{
-}
-
-
-void MapgenV5Params::readParams(const Settings *settings)
-{
- settings->getFlagStrNoEx("mgv5_spflags", spflags, flagdesc_mapgen_v5);
- settings->getFloatNoEx("mgv5_cave_width", cave_width);
- settings->getS16NoEx("mgv5_large_cave_depth", large_cave_depth);
- settings->getS16NoEx("mgv5_lava_depth", lava_depth);
- settings->getS16NoEx("mgv5_cavern_limit", cavern_limit);
- settings->getS16NoEx("mgv5_cavern_taper", cavern_taper);
- settings->getFloatNoEx("mgv5_cavern_threshold", cavern_threshold);
-
- settings->getNoiseParams("mgv5_np_filler_depth", np_filler_depth);
- settings->getNoiseParams("mgv5_np_factor", np_factor);
- settings->getNoiseParams("mgv5_np_height", np_height);
- settings->getNoiseParams("mgv5_np_ground", np_ground);
- settings->getNoiseParams("mgv5_np_cave1", np_cave1);
- settings->getNoiseParams("mgv5_np_cave2", np_cave2);
- settings->getNoiseParams("mgv5_np_cavern", np_cavern);
-}
-
-
-void MapgenV5Params::writeParams(Settings *settings) const
-{
- settings->setFlagStr("mgv5_spflags", spflags, flagdesc_mapgen_v5, U32_MAX);
- settings->setFloat("mgv5_cave_width", cave_width);
- settings->setS16("mgv5_large_cave_depth", large_cave_depth);
- settings->setS16("mgv5_lava_depth", lava_depth);
- settings->setS16("mgv5_cavern_limit", cavern_limit);
- settings->setS16("mgv5_cavern_taper", cavern_taper);
- settings->setFloat("mgv5_cavern_threshold", cavern_threshold);
-
- settings->setNoiseParams("mgv5_np_filler_depth", np_filler_depth);
- settings->setNoiseParams("mgv5_np_factor", np_factor);
- settings->setNoiseParams("mgv5_np_height", np_height);
- settings->setNoiseParams("mgv5_np_ground", np_ground);
- settings->setNoiseParams("mgv5_np_cave1", np_cave1);
- settings->setNoiseParams("mgv5_np_cave2", np_cave2);
- settings->setNoiseParams("mgv5_np_cavern", np_cavern);
-}
-
-
-int MapgenV5::getSpawnLevelAtPoint(v2s16 p)
-{
-
- float f = 0.55 + NoisePerlin2D(&noise_factor->np, p.X, p.Y, seed);
- if (f < 0.01)
- f = 0.01;
- else if (f >= 1.0)
- f *= 1.6;
- float h = NoisePerlin2D(&noise_height->np, p.X, p.Y, seed);
-
- // noise_height 'offset' is the average level of terrain. At least 50% of
- // terrain will be below this.
- // Raising the maximum spawn level above 'water_level + 16' is necessary
- // for when noise_height 'offset' is set much higher than water_level.
- s16 max_spawn_y = MYMAX(noise_height->np.offset, water_level + 16);
-
- // Starting spawn search at max_spawn_y + 128 ensures 128 nodes of open
- // space above spawn position. Avoids spawning in possibly sealed voids.
- for (s16 y = max_spawn_y + 128; y >= water_level; y--) {
- float n_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed);
-
- if (n_ground * f > y - h) { // If solid
- if (y < water_level || y > max_spawn_y)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- // y + 2 because y is surface and due to biome 'dust' nodes.
- return y + 2;
- }
- }
- // Unsuitable spawn position, no ground found
- return MAX_MAP_GENERATION_LIMIT;
-}
-
-
-void MapgenV5::makeChunk(BlockMakeData *data)
-{
- // Pre-conditions
- assert(data->vmanip);
- assert(data->nodedef);
- assert(data->blockpos_requested.X >= data->blockpos_min.X &&
- data->blockpos_requested.Y >= data->blockpos_min.Y &&
- data->blockpos_requested.Z >= data->blockpos_min.Z);
- assert(data->blockpos_requested.X <= data->blockpos_max.X &&
- data->blockpos_requested.Y <= data->blockpos_max.Y &&
- data->blockpos_requested.Z <= data->blockpos_max.Z);
-
- this->generating = true;
- this->vm = data->vmanip;
- this->ndef = data->nodedef;
- //TimeTaker t("makeChunk");
-
- v3s16 blockpos_min = data->blockpos_min;
- v3s16 blockpos_max = data->blockpos_max;
- node_min = blockpos_min * MAP_BLOCKSIZE;
- node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
- full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
- full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- // Create a block-specific seed
- blockseed = getBlockSeed2(full_node_min, seed);
-
- // Generate base terrain
- s16 stone_surface_max_y = generateBaseTerrain();
-
- // Create heightmap
- updateHeightmap(node_min, node_max);
-
- // Init biome generator, place biome-specific nodes, and build biomemap
- biomegen->calcBiomeNoise(node_min);
-
- MgStoneType mgstone_type;
- content_t biome_stone;
- generateBiomes(&mgstone_type, &biome_stone);
-
- // Generate caverns, tunnels and classic caves
- if (flags & MG_CAVES) {
- bool near_cavern = false;
- // Generate caverns
- if (spflags & MGV5_CAVERNS)
- near_cavern = generateCaverns(stone_surface_max_y);
- // Generate tunnels and classic caves
- if (near_cavern)
- // Disable classic caves in this mapchunk by setting
- // 'large cave depth' to world base. Avoids excessive liquid in
- // large caverns and floating blobs of overgenerated liquid.
- generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
- else
- generateCaves(stone_surface_max_y, large_cave_depth);
- }
-
- // Generate dungeons and desert temples
- if (flags & MG_DUNGEONS)
- generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
-
- // Generate the registered decorations
- if (flags & MG_DECORATIONS)
- m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
-
- // Generate the registered ores
- m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
-
- // Sprinkle some dust on top after everything else was generated
- dustTopNodes();
-
- //printf("makeChunk: %dms\n", t.stop());
-
- // Add top and bottom side of water to transforming_liquid queue
- updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
-
- // Calculate lighting
- if (flags & MG_LIGHT) {
- calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
- full_node_min, full_node_max);
- }
-
- this->generating = false;
-}
-
-
-int MapgenV5::generateBaseTerrain()
-{
- u32 index = 0;
- u32 index2d = 0;
- int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
-
- noise_factor->perlinMap2D(node_min.X, node_min.Z);
- noise_height->perlinMap2D(node_min.X, node_min.Z);
- noise_ground->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
-
- for (s16 z=node_min.Z; z<=node_max.Z; z++) {
- for (s16 y=node_min.Y - 1; y<=node_max.Y + 1; y++) {
- u32 vi = vm->m_area.index(node_min.X, y, z);
- for (s16 x=node_min.X; x<=node_max.X; x++, vi++, index++, index2d++) {
- if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
- continue;
-
- float f = 0.55 + noise_factor->result[index2d];
- if (f < 0.01)
- f = 0.01;
- else if (f >= 1.0)
- f *= 1.6;
- float h = noise_height->result[index2d];
-
- if (noise_ground->result[index] * f < y - h) {
- if (y <= water_level)
- vm->m_data[vi] = MapNode(c_water_source);
- else
- vm->m_data[vi] = MapNode(CONTENT_AIR);
- } else {
- vm->m_data[vi] = MapNode(c_stone);
- if (y > stone_surface_max_y)
- stone_surface_max_y = y;
- }
- }
- index2d -= ystride;
- }
- index2d += ystride;
- }
-
- return stone_surface_max_y;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2017 paramat
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "mapgen.h"
-
-///////// Mapgen V5 flags
-#define MGV5_CAVERNS 0x01
-
-class BiomeManager;
-
-extern FlagDesc flagdesc_mapgen_v5[];
-
-struct MapgenV5Params : public MapgenParams
-{
- u32 spflags = MGV5_CAVERNS;
- float cave_width = 0.125f;
- s16 large_cave_depth = -256;
- s16 lava_depth = -256;
- s16 cavern_limit = -256;
- s16 cavern_taper = 256;
- float cavern_threshold = 0.7f;
-
- NoiseParams np_filler_depth;
- NoiseParams np_factor;
- NoiseParams np_height;
- NoiseParams np_ground;
- NoiseParams np_cave1;
- NoiseParams np_cave2;
- NoiseParams np_cavern;
-
- MapgenV5Params();
- ~MapgenV5Params() = default;
-
- void readParams(const Settings *settings);
- void writeParams(Settings *settings) const;
-};
-
-class MapgenV5 : public MapgenBasic
-{
-public:
- MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge);
- ~MapgenV5();
-
- virtual MapgenType getType() const { return MAPGEN_V5; }
-
- virtual void makeChunk(BlockMakeData *data);
- int getSpawnLevelAtPoint(v2s16 p);
- int generateBaseTerrain();
-
-private:
- s16 large_cave_depth;
- Noise *noise_factor;
- Noise *noise_height;
- Noise *noise_ground;
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-//#include "serverobject.h"
-#include "content_sao.h"
-#include "nodedef.h"
-#include "voxelalgorithms.h"
-//#include "profiler.h" // For TimeTaker
-#include "settings.h" // For g_settings
-#include "emerge.h"
-#include "dungeongen.h"
-#include "cavegen.h"
-#include "treegen.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mapgen_v6.h"
-
-
-FlagDesc flagdesc_mapgen_v6[] = {
- {"jungles", MGV6_JUNGLES},
- {"biomeblend", MGV6_BIOMEBLEND},
- {"mudflow", MGV6_MUDFLOW},
- {"snowbiomes", MGV6_SNOWBIOMES},
- {"flat", MGV6_FLAT},
- {"trees", MGV6_TREES},
- {NULL, 0}
-};
-
-
-/////////////////////////////////////////////////////////////////////////////
-
-
-MapgenV6::MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge)
- : Mapgen(mapgenid, params, emerge)
-{
- m_emerge = emerge;
- ystride = csize.X; //////fix this
-
- heightmap = new s16[csize.X * csize.Z];
-
- spflags = params->spflags;
- freq_desert = params->freq_desert;
- freq_beach = params->freq_beach;
-
- np_cave = ¶ms->np_cave;
- np_humidity = ¶ms->np_humidity;
- np_trees = ¶ms->np_trees;
- np_apple_trees = ¶ms->np_apple_trees;
-
- //// Create noise objects
- noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Y);
- noise_terrain_higher = new Noise(¶ms->np_terrain_higher, seed, csize.X, csize.Y);
- noise_steepness = new Noise(¶ms->np_steepness, seed, csize.X, csize.Y);
- noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Y);
- noise_mud = new Noise(¶ms->np_mud, seed, csize.X, csize.Y);
- noise_beach = new Noise(¶ms->np_beach, seed, csize.X, csize.Y);
- noise_biome = new Noise(¶ms->np_biome, seed,
- csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE);
- noise_humidity = new Noise(¶ms->np_humidity, seed,
- csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE);
-
- //// Resolve nodes to be used
- INodeDefManager *ndef = emerge->ndef;
-
- c_stone = ndef->getId("mapgen_stone");
- c_dirt = ndef->getId("mapgen_dirt");
- c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass");
- c_sand = ndef->getId("mapgen_sand");
- c_water_source = ndef->getId("mapgen_water_source");
- c_lava_source = ndef->getId("mapgen_lava_source");
- c_gravel = ndef->getId("mapgen_gravel");
- c_desert_stone = ndef->getId("mapgen_desert_stone");
- c_desert_sand = ndef->getId("mapgen_desert_sand");
- c_dirt_with_snow = ndef->getId("mapgen_dirt_with_snow");
- c_snow = ndef->getId("mapgen_snow");
- c_snowblock = ndef->getId("mapgen_snowblock");
- c_ice = ndef->getId("mapgen_ice");
-
- if (c_gravel == CONTENT_IGNORE)
- c_gravel = c_stone;
- if (c_desert_stone == CONTENT_IGNORE)
- c_desert_stone = c_stone;
- if (c_desert_sand == CONTENT_IGNORE)
- c_desert_sand = c_sand;
- if (c_dirt_with_snow == CONTENT_IGNORE)
- c_dirt_with_snow = c_dirt_with_grass;
- if (c_snow == CONTENT_IGNORE)
- c_snow = CONTENT_AIR;
- if (c_snowblock == CONTENT_IGNORE)
- c_snowblock = c_dirt_with_grass;
- if (c_ice == CONTENT_IGNORE)
- c_ice = c_water_source;
-
- c_cobble = ndef->getId("mapgen_cobble");
- c_mossycobble = ndef->getId("mapgen_mossycobble");
- c_stair_cobble = ndef->getId("mapgen_stair_cobble");
- c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone");
-
- if (c_mossycobble == CONTENT_IGNORE)
- c_mossycobble = c_cobble;
- if (c_stair_cobble == CONTENT_IGNORE)
- c_stair_cobble = c_cobble;
- if (c_stair_desert_stone == CONTENT_IGNORE)
- c_stair_desert_stone = c_desert_stone;
-}
-
-
-MapgenV6::~MapgenV6()
-{
- delete noise_terrain_base;
- delete noise_terrain_higher;
- delete noise_steepness;
- delete noise_height_select;
- delete noise_mud;
- delete noise_beach;
- delete noise_biome;
- delete noise_humidity;
-
- delete[] heightmap;
-}
-
-
-MapgenV6Params::MapgenV6Params():
- np_terrain_base (-4, 20.0, v3f(250.0, 250.0, 250.0), 82341, 5, 0.6, 2.0),
- np_terrain_higher (20, 16.0, v3f(500.0, 500.0, 500.0), 85039, 5, 0.6, 2.0),
- np_steepness (0.85, 0.5, v3f(125.0, 125.0, 125.0), -932, 5, 0.7, 2.0),
- np_height_select (0, 1.0, v3f(250.0, 250.0, 250.0), 4213, 5, 0.69, 2.0),
- np_mud (4, 2.0, v3f(200.0, 200.0, 200.0), 91013, 3, 0.55, 2.0),
- np_beach (0, 1.0, v3f(250.0, 250.0, 250.0), 59420, 3, 0.50, 2.0),
- np_biome (0, 1.0, v3f(500.0, 500.0, 500.0), 9130, 3, 0.50, 2.0),
- np_cave (6, 6.0, v3f(250.0, 250.0, 250.0), 34329, 3, 0.50, 2.0),
- np_humidity (0.5, 0.5, v3f(500.0, 500.0, 500.0), 72384, 3, 0.50, 2.0),
- np_trees (0, 1.0, v3f(125.0, 125.0, 125.0), 2, 4, 0.66, 2.0),
- np_apple_trees (0, 1.0, v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0)
-{
-}
-
-
-void MapgenV6Params::readParams(const Settings *settings)
-{
- settings->getFlagStrNoEx("mgv6_spflags", spflags, flagdesc_mapgen_v6);
- settings->getFloatNoEx("mgv6_freq_desert", freq_desert);
- settings->getFloatNoEx("mgv6_freq_beach", freq_beach);
-
- settings->getNoiseParams("mgv6_np_terrain_base", np_terrain_base);
- settings->getNoiseParams("mgv6_np_terrain_higher", np_terrain_higher);
- settings->getNoiseParams("mgv6_np_steepness", np_steepness);
- settings->getNoiseParams("mgv6_np_height_select", np_height_select);
- settings->getNoiseParams("mgv6_np_mud", np_mud);
- settings->getNoiseParams("mgv6_np_beach", np_beach);
- settings->getNoiseParams("mgv6_np_biome", np_biome);
- settings->getNoiseParams("mgv6_np_cave", np_cave);
- settings->getNoiseParams("mgv6_np_humidity", np_humidity);
- settings->getNoiseParams("mgv6_np_trees", np_trees);
- settings->getNoiseParams("mgv6_np_apple_trees", np_apple_trees);
-}
-
-
-void MapgenV6Params::writeParams(Settings *settings) const
-{
- settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6, U32_MAX);
- settings->setFloat("mgv6_freq_desert", freq_desert);
- settings->setFloat("mgv6_freq_beach", freq_beach);
-
- settings->setNoiseParams("mgv6_np_terrain_base", np_terrain_base);
- settings->setNoiseParams("mgv6_np_terrain_higher", np_terrain_higher);
- settings->setNoiseParams("mgv6_np_steepness", np_steepness);
- settings->setNoiseParams("mgv6_np_height_select", np_height_select);
- settings->setNoiseParams("mgv6_np_mud", np_mud);
- settings->setNoiseParams("mgv6_np_beach", np_beach);
- settings->setNoiseParams("mgv6_np_biome", np_biome);
- settings->setNoiseParams("mgv6_np_cave", np_cave);
- settings->setNoiseParams("mgv6_np_humidity", np_humidity);
- settings->setNoiseParams("mgv6_np_trees", np_trees);
- settings->setNoiseParams("mgv6_np_apple_trees", np_apple_trees);
-}
-
-
-//////////////////////// Some helper functions for the map generator
-
-// Returns Y one under area minimum if not found
-s16 MapgenV6::find_stone_level(v2s16 p2d)
-{
- const v3s16 &em = vm->m_area.getExtent();
- s16 y_nodes_max = vm->m_area.MaxEdge.Y;
- s16 y_nodes_min = vm->m_area.MinEdge.Y;
- u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y);
- s16 y;
-
- for (y = y_nodes_max; y >= y_nodes_min; y--) {
- content_t c = vm->m_data[i].getContent();
- if (c != CONTENT_IGNORE && (c == c_stone || c == c_desert_stone))
- break;
-
- vm->m_area.add_y(em, i, -1);
- }
- return (y >= y_nodes_min) ? y : y_nodes_min - 1;
-}
-
-
-// Required by mapgen.h
-bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos)
-{
- /*s16 minimum_groundlevel = (s16)get_sector_minimum_ground_level(
- seed, v2s16(blockpos.X, blockpos.Z));*/
- // Nah, this is just a heuristic, just return something
- s16 minimum_groundlevel = water_level;
-
- if(blockpos.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel)
- return true;
-
- return false;
-}
-
-
-//////////////////////// Base terrain height functions
-
-float MapgenV6::baseTerrainLevel(float terrain_base, float terrain_higher,
- float steepness, float height_select)
-{
- float base = 1 + terrain_base;
- float higher = 1 + terrain_higher;
-
- // Limit higher ground level to at least base
- if(higher < base)
- higher = base;
-
- // Steepness factor of cliffs
- float b = steepness;
- b = rangelim(b, 0.0, 1000.0);
- b = 5 * b * b * b * b * b * b * b;
- b = rangelim(b, 0.5, 1000.0);
-
- // Values 1.5...100 give quite horrible looking slopes
- if (b > 1.5 && b < 100.0)
- b = (b < 10.0) ? 1.5 : 100.0;
-
- float a_off = -0.20; // Offset to more low
- float a = 0.5 + b * (a_off + height_select);
- a = rangelim(a, 0.0, 1.0); // Limit
-
- return base * (1.0 - a) + higher * a;
-}
-
-
-float MapgenV6::baseTerrainLevelFromNoise(v2s16 p)
-{
- if (spflags & MGV6_FLAT)
- return water_level;
-
- float terrain_base = NoisePerlin2D_PO(&noise_terrain_base->np,
- p.X, 0.5, p.Y, 0.5, seed);
- float terrain_higher = NoisePerlin2D_PO(&noise_terrain_higher->np,
- p.X, 0.5, p.Y, 0.5, seed);
- float steepness = NoisePerlin2D_PO(&noise_steepness->np,
- p.X, 0.5, p.Y, 0.5, seed);
- float height_select = NoisePerlin2D_PO(&noise_height_select->np,
- p.X, 0.5, p.Y, 0.5, seed);
-
- return baseTerrainLevel(terrain_base, terrain_higher,
- steepness, height_select);
-}
-
-
-float MapgenV6::baseTerrainLevelFromMap(v2s16 p)
-{
- int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
- return baseTerrainLevelFromMap(index);
-}
-
-
-float MapgenV6::baseTerrainLevelFromMap(int index)
-{
- if (spflags & MGV6_FLAT)
- return water_level;
-
- float terrain_base = noise_terrain_base->result[index];
- float terrain_higher = noise_terrain_higher->result[index];
- float steepness = noise_steepness->result[index];
- float height_select = noise_height_select->result[index];
-
- return baseTerrainLevel(terrain_base, terrain_higher,
- steepness, height_select);
-}
-
-
-s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision)
-{
- return baseTerrainLevelFromNoise(p2d) + MGV6_AVERAGE_MUD_AMOUNT;
-}
-
-
-int MapgenV6::getGroundLevelAtPoint(v2s16 p)
-{
- return baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
-}
-
-
-int MapgenV6::getSpawnLevelAtPoint(v2s16 p)
-{
- s16 level_at_point = baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT;
- if (level_at_point <= water_level ||
- level_at_point > water_level + 16)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- return level_at_point;
-}
-
-
-//////////////////////// Noise functions
-
-float MapgenV6::getMudAmount(v2s16 p)
-{
- int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
- return getMudAmount(index);
-}
-
-
-bool MapgenV6::getHaveBeach(v2s16 p)
-{
- int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X);
- return getHaveBeach(index);
-}
-
-
-BiomeV6Type MapgenV6::getBiome(v2s16 p)
-{
- int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE)
- + (p.X - full_node_min.X);
- return getBiome(index, p);
-}
-
-
-float MapgenV6::getHumidity(v2s16 p)
-{
- /*double noise = noise2d_perlin(
- 0.5+(float)p.X/500, 0.5+(float)p.Y/500,
- seed+72384, 4, 0.66);
- noise = (noise + 1.0)/2.0;*/
-
- int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE)
- + (p.X - full_node_min.X);
- float noise = noise_humidity->result[index];
-
- if (noise < 0.0)
- noise = 0.0;
- if (noise > 1.0)
- noise = 1.0;
- return noise;
-}
-
-
-float MapgenV6::getTreeAmount(v2s16 p)
-{
- /*double noise = noise2d_perlin(
- 0.5+(float)p.X/125, 0.5+(float)p.Y/125,
- seed+2, 4, 0.66);*/
-
- float noise = NoisePerlin2D(np_trees, p.X, p.Y, seed);
- float zeroval = -0.39;
- if (noise < zeroval)
- return 0;
-
- return 0.04 * (noise - zeroval) / (1.0 - zeroval);
-}
-
-
-bool MapgenV6::getHaveAppleTree(v2s16 p)
-{
- /*is_apple_tree = noise2d_perlin(
- 0.5+(float)p.X/100, 0.5+(float)p.Z/100,
- data->seed+342902, 3, 0.45) > 0.2;*/
-
- float noise = NoisePerlin2D(np_apple_trees, p.X, p.Y, seed);
-
- return noise > 0.2;
-}
-
-
-float MapgenV6::getMudAmount(int index)
-{
- if (spflags & MGV6_FLAT)
- return MGV6_AVERAGE_MUD_AMOUNT;
-
- /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin(
- 0.5+(float)p.X/200, 0.5+(float)p.Y/200,
- seed+91013, 3, 0.55));*/
-
- return noise_mud->result[index];
-}
-
-
-bool MapgenV6::getHaveBeach(int index)
-{
- // Determine whether to have sand here
- /*double sandnoise = noise2d_perlin(
- 0.2+(float)p2d.X/250, 0.7+(float)p2d.Y/250,
- seed+59420, 3, 0.50);*/
-
- float sandnoise = noise_beach->result[index];
- return (sandnoise > freq_beach);
-}
-
-
-BiomeV6Type MapgenV6::getBiome(int index, v2s16 p)
-{
- // Just do something very simple as for now
- /*double d = noise2d_perlin(
- 0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250,
- seed+9130, 3, 0.50);*/
-
- float d = noise_biome->result[index];
- float h = noise_humidity->result[index];
-
- if (spflags & MGV6_SNOWBIOMES) {
- float blend = (spflags & MGV6_BIOMEBLEND) ? noise2d(p.X, p.Y, seed) / 40 : 0;
-
- if (d > MGV6_FREQ_HOT + blend) {
- if (h > MGV6_FREQ_JUNGLE + blend)
- return BT_JUNGLE;
-
- return BT_DESERT;
- }
-
- if (d < MGV6_FREQ_SNOW + blend) {
- if (h > MGV6_FREQ_TAIGA + blend)
- return BT_TAIGA;
-
- return BT_TUNDRA;
- }
-
- return BT_NORMAL;
- }
-
- if (d > freq_desert)
- return BT_DESERT;
-
- if ((spflags & MGV6_BIOMEBLEND) && (d > freq_desert - 0.10) &&
- ((noise2d(p.X, p.Y, seed) + 1.0) > (freq_desert - d) * 20.0))
- return BT_DESERT;
-
- if ((spflags & MGV6_JUNGLES) && h > 0.75)
- return BT_JUNGLE;
-
- return BT_NORMAL;
-
-}
-
-
-u32 MapgenV6::get_blockseed(u64 seed, v3s16 p)
-{
- s32 x = p.X, y = p.Y, z = p.Z;
- return (u32)(seed % 0x100000000ULL) + z * 38134234 + y * 42123 + x * 23;
-}
-
-
-//////////////////////// Map generator
-
-void MapgenV6::makeChunk(BlockMakeData *data)
-{
- // Pre-conditions
- assert(data->vmanip);
- assert(data->nodedef);
- assert(data->blockpos_requested.X >= data->blockpos_min.X &&
- data->blockpos_requested.Y >= data->blockpos_min.Y &&
- data->blockpos_requested.Z >= data->blockpos_min.Z);
- assert(data->blockpos_requested.X <= data->blockpos_max.X &&
- data->blockpos_requested.Y <= data->blockpos_max.Y &&
- data->blockpos_requested.Z <= data->blockpos_max.Z);
-
- this->generating = true;
- this->vm = data->vmanip;
- this->ndef = data->nodedef;
-
- // Hack: use minimum block coords for old code that assumes a single block
- v3s16 blockpos_min = data->blockpos_min;
- v3s16 blockpos_max = data->blockpos_max;
-
- // Area of central chunk
- node_min = blockpos_min * MAP_BLOCKSIZE;
- node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- // Full allocated area
- full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
- full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- central_area_size = node_max - node_min + v3s16(1, 1, 1);
- assert(central_area_size.X == central_area_size.Z);
-
- // Create a block-specific seed
- blockseed = get_blockseed(data->seed, full_node_min);
-
- // Make some noise
- calculateNoise();
-
- // Maximum height of the stone surface and obstacles.
- // This is used to guide the cave generation
- s16 stone_surface_max_y;
-
- // Generate general ground level to full area
- stone_surface_max_y = generateGround();
-
- // Create initial heightmap to limit caves
- updateHeightmap(node_min, node_max);
-
- const s16 max_spread_amount = MAP_BLOCKSIZE;
- // Limit dirt flow area by 1 because mud is flown into neighbors.
- s16 mudflow_minpos = -max_spread_amount + 1;
- s16 mudflow_maxpos = central_area_size.X + max_spread_amount - 2;
-
- // Loop this part, it will make stuff look older and newer nicely
- const u32 age_loops = 2;
- for (u32 i_age = 0; i_age < age_loops; i_age++) { // Aging loop
- // Make caves (this code is relatively horrible)
- if (flags & MG_CAVES)
- generateCaves(stone_surface_max_y);
-
- // Add mud to the central chunk
- addMud();
-
- // Flow mud away from steep edges
- if (spflags & MGV6_MUDFLOW)
- flowMud(mudflow_minpos, mudflow_maxpos);
-
- }
-
- // Update heightmap after mudflow
- updateHeightmap(node_min, node_max);
-
- // Add dungeons
- if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) {
- DungeonParams dp;
-
- dp.seed = seed;
- dp.c_water = c_water_source;
- dp.c_river_water = c_water_source;
-
- dp.only_in_ground = true;
- dp.corridor_len_min = 1;
- dp.corridor_len_max = 13;
- dp.rooms_min = 2;
- dp.rooms_max = 16;
- dp.y_min = -MAX_MAP_GENERATION_LIMIT;
- dp.y_max = MAX_MAP_GENERATION_LIMIT;
-
- dp.np_density
- = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
- dp.np_alt_wall
- = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
-
- if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
- dp.c_wall = c_desert_stone;
- dp.c_alt_wall = CONTENT_IGNORE;
- dp.c_stair = c_stair_desert_stone;
-
- dp.diagonal_dirs = true;
- dp.holesize = v3s16(2, 3, 2);
- dp.room_size_min = v3s16(6, 9, 6);
- dp.room_size_max = v3s16(10, 11, 10);
- dp.room_size_large_min = v3s16(10, 13, 10);
- dp.room_size_large_max = v3s16(18, 21, 18);
- dp.notifytype = GENNOTIFY_TEMPLE;
- } else {
- dp.c_wall = c_cobble;
- dp.c_alt_wall = c_mossycobble;
- dp.c_stair = c_stair_cobble;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(1, 2, 1);
- dp.room_size_min = v3s16(4, 4, 4);
- dp.room_size_max = v3s16(8, 6, 8);
- dp.room_size_large_min = v3s16(8, 8, 8);
- dp.room_size_large_max = v3s16(16, 16, 16);
- dp.notifytype = GENNOTIFY_DUNGEON;
- }
-
- DungeonGen dgen(ndef, &gennotify, &dp);
- dgen.generate(vm, blockseed, full_node_min, full_node_max);
- }
-
- // Add top and bottom side of water to transforming_liquid queue
- updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
-
- // Add surface nodes
- growGrass();
-
- // Generate some trees, and add grass, if a jungle
- if (spflags & MGV6_TREES)
- placeTreesAndJungleGrass();
-
- // Generate the registered decorations
- if (flags & MG_DECORATIONS)
- m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
-
- // Generate the registered ores
- m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
-
- // Calculate lighting
- if (flags & MG_LIGHT)
- calcLighting(node_min - v3s16(1, 1, 1) * MAP_BLOCKSIZE,
- node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE,
- full_node_min, full_node_max);
-
- this->generating = false;
-}
-
-
-void MapgenV6::calculateNoise()
-{
- int x = node_min.X;
- int z = node_min.Z;
- int fx = full_node_min.X;
- int fz = full_node_min.Z;
-
- if (!(spflags & MGV6_FLAT)) {
- noise_terrain_base->perlinMap2D_PO(x, 0.5, z, 0.5);
- noise_terrain_higher->perlinMap2D_PO(x, 0.5, z, 0.5);
- noise_steepness->perlinMap2D_PO(x, 0.5, z, 0.5);
- noise_height_select->perlinMap2D_PO(x, 0.5, z, 0.5);
- noise_mud->perlinMap2D_PO(x, 0.5, z, 0.5);
- }
-
- noise_beach->perlinMap2D_PO(x, 0.2, z, 0.7);
-
- noise_biome->perlinMap2D_PO(fx, 0.6, fz, 0.2);
- noise_humidity->perlinMap2D_PO(fx, 0.0, fz, 0.0);
- // Humidity map does not need range limiting 0 to 1,
- // only humidity at point does
-}
-
-
-int MapgenV6::generateGround()
-{
- //TimeTaker timer1("Generating ground level");
- MapNode n_air(CONTENT_AIR), n_water_source(c_water_source);
- MapNode n_stone(c_stone), n_desert_stone(c_desert_stone);
- MapNode n_ice(c_ice);
- int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
-
- u32 index = 0;
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
- // Surface height
- s16 surface_y = (s16)baseTerrainLevelFromMap(index);
-
- // Log it
- if (surface_y > stone_surface_max_y)
- stone_surface_max_y = surface_y;
-
- BiomeV6Type bt = getBiome(v2s16(x, z));
-
- // Fill ground with stone
- const v3s16 &em = vm->m_area.getExtent();
- u32 i = vm->m_area.index(x, node_min.Y, z);
- for (s16 y = node_min.Y; y <= node_max.Y; y++) {
- if (vm->m_data[i].getContent() == CONTENT_IGNORE) {
- if (y <= surface_y) {
- vm->m_data[i] = (y >= MGV6_DESERT_STONE_BASE
- && bt == BT_DESERT) ?
- n_desert_stone : n_stone;
- } else if (y <= water_level) {
- vm->m_data[i] = (y >= MGV6_ICE_BASE
- && bt == BT_TUNDRA) ?
- n_ice : n_water_source;
- } else {
- vm->m_data[i] = n_air;
- }
- }
- vm->m_area.add_y(em, i, 1);
- }
- }
-
- return stone_surface_max_y;
-}
-
-
-void MapgenV6::addMud()
-{
- // 15ms @cs=8
- //TimeTaker timer1("add mud");
- MapNode n_dirt(c_dirt), n_gravel(c_gravel);
- MapNode n_sand(c_sand), n_desert_sand(c_desert_sand);
- MapNode addnode;
-
- u32 index = 0;
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
- // Randomize mud amount
- s16 mud_add_amount = getMudAmount(index) / 2.0 + 0.5;
-
- // Find ground level
- s16 surface_y = find_stone_level(v2s16(x, z)); /////////////////optimize this!
-
- // Handle area not found
- if (surface_y == vm->m_area.MinEdge.Y - 1)
- continue;
-
- BiomeV6Type bt = getBiome(v2s16(x, z));
- addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt;
-
- if (bt == BT_DESERT && surface_y + mud_add_amount <= water_level + 1) {
- addnode = n_sand;
- } else if (mud_add_amount <= 0) {
- mud_add_amount = 1 - mud_add_amount;
- addnode = n_gravel;
- } else if (bt != BT_DESERT && getHaveBeach(index) &&
- surface_y + mud_add_amount <= water_level + 2) {
- addnode = n_sand;
- }
-
- if ((bt == BT_DESERT || bt == BT_TUNDRA) && surface_y > 20)
- mud_add_amount = MYMAX(0, mud_add_amount - (surface_y - 20) / 5);
-
- /* If topmost node is grass, change it to mud. It might be if it was
- // flown to there from a neighboring chunk and then converted.
- u32 i = vm->m_area.index(x, surface_y, z);
- if (vm->m_data[i].getContent() == c_dirt_with_grass)
- vm->m_data[i] = n_dirt;*/
-
- // Add mud on ground
- s16 mudcount = 0;
- const v3s16 &em = vm->m_area.getExtent();
- s16 y_start = surface_y + 1;
- u32 i = vm->m_area.index(x, y_start, z);
- for (s16 y = y_start; y <= node_max.Y; y++) {
- if (mudcount >= mud_add_amount)
- break;
-
- vm->m_data[i] = addnode;
- mudcount++;
-
- vm->m_area.add_y(em, i, 1);
- }
- }
-}
-
-
-void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos)
-{
- // 340ms @cs=8
- //TimeTaker timer1("flow mud");
-
- // Iterate a few times
- for (s16 k = 0; k < 3; k++) {
- for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++)
- for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) {
- // Invert coordinates every 2nd iteration
- if (k % 2 == 0) {
- x = mudflow_maxpos - (x - mudflow_minpos);
- z = mudflow_maxpos - (z - mudflow_minpos);
- }
-
- // Node position in 2d
- v2s16 p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z);
-
- const v3s16 &em = vm->m_area.getExtent();
- u32 i = vm->m_area.index(p2d.X, node_max.Y, p2d.Y);
- s16 y = node_max.Y;
-
- while (y >= node_min.Y) {
-
- for (;; y--) {
- MapNode *n = NULL;
- // Find mud
- for (; y >= node_min.Y; y--) {
- n = &vm->m_data[i];
- if (n->getContent() == c_dirt ||
- n->getContent() == c_dirt_with_grass ||
- n->getContent() == c_gravel)
- break;
-
- vm->m_area.add_y(em, i, -1);
- }
-
- // Stop if out of area
- //if(vmanip.m_area.contains(i) == false)
- if (y < node_min.Y)
- break;
-
- if (n->getContent() == c_dirt ||
- n->getContent() == c_dirt_with_grass) {
- // Make it exactly mud
- n->setContent(c_dirt);
-
- // Don't flow it if the stuff under it is not mud
- {
- u32 i2 = i;
- vm->m_area.add_y(em, i2, -1);
- // Cancel if out of area
- if (!vm->m_area.contains(i2))
- continue;
- MapNode *n2 = &vm->m_data[i2];
- if (n2->getContent() != c_dirt &&
- n2->getContent() != c_dirt_with_grass)
- continue;
- }
- }
-
- v3s16 dirs4[4] = {
- v3s16(0, 0, 1), // back
- v3s16(1, 0, 0), // right
- v3s16(0, 0, -1), // front
- v3s16(-1, 0, 0), // left
- };
-
- // Check that upper is walkable. Cancel
- // dropping if upper keeps it in place.
- u32 i3 = i;
- vm->m_area.add_y(em, i3, 1);
- MapNode *n3 = NULL;
-
- if (vm->m_area.contains(i3)) {
- n3 = &vm->m_data[i3];
- if (ndef->get(*n3).walkable)
- continue;
- }
-
- // Drop mud on side
- for (const v3s16 &dirp : dirs4) {
- u32 i2 = i;
- // Move to side
- vm->m_area.add_p(em, i2, dirp);
- // Fail if out of area
- if (!vm->m_area.contains(i2))
- continue;
- // Check that side is air
- MapNode *n2 = &vm->m_data[i2];
- if (ndef->get(*n2).walkable)
- continue;
- // Check that under side is air
- vm->m_area.add_y(em, i2, -1);
- if (!vm->m_area.contains(i2))
- continue;
- n2 = &vm->m_data[i2];
- if (ndef->get(*n2).walkable)
- continue;
- // Loop further down until not air
- bool dropped_to_unknown = false;
- do {
- vm->m_area.add_y(em, i2, -1);
- n2 = &vm->m_data[i2];
- // if out of known area
- if (!vm->m_area.contains(i2) ||
- n2->getContent() == CONTENT_IGNORE) {
- dropped_to_unknown = true;
- break;
- }
- } while (!ndef->get(*n2).walkable);
- // Loop one up so that we're in air
- vm->m_area.add_y(em, i2, 1);
-
- // Move mud to new place. Outside mapchunk remove
- // any decorations above removed or placed mud.
- if (!dropped_to_unknown)
- moveMud(i, i2, i3, p2d, em);
-
- // Done
- break;
- }
- }
- }
- }
- }
-}
-
-
-void MapgenV6::moveMud(u32 remove_index, u32 place_index,
- u32 above_remove_index, v2s16 pos, v3s16 em)
-{
- MapNode n_air(CONTENT_AIR);
- // Copy mud from old place to new place
- vm->m_data[place_index] = vm->m_data[remove_index];
- // Set old place to be air
- vm->m_data[remove_index] = n_air;
- // Outside the mapchunk decorations may need to be removed if above removed
- // mud or if half-buried in placed mud. Placed mud is to the side of pos so
- // use 'pos.X >= node_max.X' etc.
- if (pos.X >= node_max.X || pos.X <= node_min.X ||
- pos.Y >= node_max.Z || pos.Y <= node_min.Z) {
- // 'above remove' node is above removed mud. If it is not air, water or
- // 'ignore' it is a decoration that needs removing. Also search upwards
- // to remove a possible stacked decoration.
- // Check for 'ignore' because stacked decorations can penetrate into
- // 'ignore' nodes above the mapchunk.
- while (vm->m_area.contains(above_remove_index) &&
- vm->m_data[above_remove_index].getContent() != CONTENT_AIR &&
- vm->m_data[above_remove_index].getContent() != c_water_source &&
- vm->m_data[above_remove_index].getContent() != CONTENT_IGNORE) {
- vm->m_data[above_remove_index] = n_air;
- vm->m_area.add_y(em, above_remove_index, 1);
- }
- // Mud placed may have partially-buried a stacked decoration, search
- // above and remove.
- vm->m_area.add_y(em, place_index, 1);
- while (vm->m_area.contains(place_index) &&
- vm->m_data[place_index].getContent() != CONTENT_AIR &&
- vm->m_data[place_index].getContent() != c_water_source &&
- vm->m_data[place_index].getContent() != CONTENT_IGNORE) {
- vm->m_data[place_index] = n_air;
- vm->m_area.add_y(em, place_index, 1);
- }
- }
-}
-
-
-void MapgenV6::placeTreesAndJungleGrass()
-{
- //TimeTaker t("placeTrees");
- if (node_max.Y < water_level)
- return;
-
- PseudoRandom grassrandom(blockseed + 53);
- content_t c_junglegrass = ndef->getId("mapgen_junglegrass");
- // if we don't have junglegrass, don't place cignore... that's bad
- if (c_junglegrass == CONTENT_IGNORE)
- c_junglegrass = CONTENT_AIR;
- MapNode n_junglegrass(c_junglegrass);
- const v3s16 &em = vm->m_area.getExtent();
-
- // Divide area into parts
- s16 div = 8;
- s16 sidelen = central_area_size.X / div;
- double area = sidelen * sidelen;
-
- // N.B. We must add jungle grass first, since tree leaves will
- // obstruct the ground, giving us a false ground level
- for (s16 z0 = 0; z0 < div; z0++)
- for (s16 x0 = 0; x0 < div; x0++) {
- // Center position of part of division
- v2s16 p2d_center(
- node_min.X + sidelen / 2 + sidelen * x0,
- node_min.Z + sidelen / 2 + sidelen * z0
- );
- // Minimum edge of part of division
- v2s16 p2d_min(
- node_min.X + sidelen * x0,
- node_min.Z + sidelen * z0
- );
- // Maximum edge of part of division
- v2s16 p2d_max(
- node_min.X + sidelen + sidelen * x0 - 1,
- node_min.Z + sidelen + sidelen * z0 - 1
- );
-
- // Get biome at center position of part of division
- BiomeV6Type bt = getBiome(p2d_center);
-
- // Amount of trees
- u32 tree_count;
- if (bt == BT_JUNGLE || bt == BT_TAIGA || bt == BT_NORMAL) {
- tree_count = area * getTreeAmount(p2d_center);
- if (bt == BT_JUNGLE)
- tree_count *= 4;
- } else {
- tree_count = 0;
- }
-
- // Add jungle grass
- if (bt == BT_JUNGLE) {
- float humidity = getHumidity(p2d_center);
- u32 grass_count = 5 * humidity * tree_count;
- for (u32 i = 0; i < grass_count; i++) {
- s16 x = grassrandom.range(p2d_min.X, p2d_max.X);
- s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y);
- int mapindex = central_area_size.X * (z - node_min.Z)
- + (x - node_min.X);
- s16 y = heightmap[mapindex];
- if (y < water_level)
- continue;
-
- u32 vi = vm->m_area.index(x, y, z);
- // place on dirt_with_grass, since we know it is exposed to sunlight
- if (vm->m_data[vi].getContent() == c_dirt_with_grass) {
- vm->m_area.add_y(em, vi, 1);
- vm->m_data[vi] = n_junglegrass;
- }
- }
- }
-
- // Put trees in random places on part of division
- for (u32 i = 0; i < tree_count; i++) {
- s16 x = myrand_range(p2d_min.X, p2d_max.X);
- s16 z = myrand_range(p2d_min.Y, p2d_max.Y);
- int mapindex = central_area_size.X * (z - node_min.Z)
- + (x - node_min.X);
- s16 y = heightmap[mapindex];
- // Don't make a tree under water level
- // Don't make a tree so high that it doesn't fit
- if (y < water_level || y > node_max.Y - 6)
- continue;
-
- v3s16 p(x, y, z);
- // Trees grow only on mud and grass
- {
- u32 i = vm->m_area.index(p);
- content_t c = vm->m_data[i].getContent();
- if (c != c_dirt &&
- c != c_dirt_with_grass &&
- c != c_dirt_with_snow)
- continue;
- }
- p.Y++;
-
- // Make a tree
- if (bt == BT_JUNGLE) {
- treegen::make_jungletree(*vm, p, ndef, myrand());
- } else if (bt == BT_TAIGA) {
- treegen::make_pine_tree(*vm, p - v3s16(0, 1, 0), ndef, myrand());
- } else if (bt == BT_NORMAL) {
- bool is_apple_tree = (myrand_range(0, 3) == 0) &&
- getHaveAppleTree(v2s16(x, z));
- treegen::make_tree(*vm, p, is_apple_tree, ndef, myrand());
- }
- }
- }
- //printf("placeTreesAndJungleGrass: %dms\n", t.stop());
-}
-
-
-void MapgenV6::growGrass() // Add surface nodes
-{
- MapNode n_dirt_with_grass(c_dirt_with_grass);
- MapNode n_dirt_with_snow(c_dirt_with_snow);
- MapNode n_snowblock(c_snowblock);
- MapNode n_snow(c_snow);
- const v3s16 &em = vm->m_area.getExtent();
-
- u32 index = 0;
- for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++)
- for (s16 x = full_node_min.X; x <= full_node_max.X; x++, index++) {
- // Find the lowest surface to which enough light ends up to make
- // grass grow. Basically just wait until not air and not leaves.
- s16 surface_y = 0;
- {
- u32 i = vm->m_area.index(x, node_max.Y, z);
- s16 y;
- // Go to ground level
- for (y = node_max.Y; y >= full_node_min.Y; y--) {
- MapNode &n = vm->m_data[i];
- if (ndef->get(n).param_type != CPT_LIGHT ||
- ndef->get(n).liquid_type != LIQUID_NONE ||
- n.getContent() == c_ice)
- break;
- vm->m_area.add_y(em, i, -1);
- }
- surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y;
- }
-
- BiomeV6Type bt = getBiome(index, v2s16(x, z));
- u32 i = vm->m_area.index(x, surface_y, z);
- content_t c = vm->m_data[i].getContent();
- if (surface_y >= water_level - 20) {
- if (bt == BT_TAIGA && c == c_dirt) {
- vm->m_data[i] = n_dirt_with_snow;
- } else if (bt == BT_TUNDRA) {
- if (c == c_dirt) {
- vm->m_data[i] = n_snowblock;
- vm->m_area.add_y(em, i, -1);
- vm->m_data[i] = n_dirt_with_snow;
- } else if (c == c_stone && surface_y < node_max.Y) {
- vm->m_area.add_y(em, i, 1);
- vm->m_data[i] = n_snowblock;
- }
- } else if (c == c_dirt) {
- vm->m_data[i] = n_dirt_with_grass;
- }
- }
- }
-}
-
-
-void MapgenV6::generateCaves(int max_stone_y)
-{
- float cave_amount = NoisePerlin2D(np_cave, node_min.X, node_min.Y, seed);
- int volume_nodes = (node_max.X - node_min.X + 1) *
- (node_max.Y - node_min.Y + 1) * MAP_BLOCKSIZE;
- cave_amount = MYMAX(0.0, cave_amount);
- u32 caves_count = cave_amount * volume_nodes / 50000;
- u32 bruises_count = 1;
- PseudoRandom ps(blockseed + 21343);
- PseudoRandom ps2(blockseed + 1032);
-
- if (ps.range(1, 6) == 1)
- bruises_count = ps.range(0, ps.range(0, 2));
-
- if (getBiome(v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
- caves_count /= 3;
- bruises_count /= 3;
- }
-
- for (u32 i = 0; i < caves_count + bruises_count; i++) {
- CavesV6 cave(ndef, &gennotify, water_level, c_water_source, c_lava_source);
-
- bool large_cave = (i >= caves_count);
- cave.makeCave(vm, node_min, node_max, &ps, &ps2,
- large_cave, max_stone_y, heightmap);
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "mapgen.h"
-#include "noise.h"
-
-#define MGV6_AVERAGE_MUD_AMOUNT 4
-#define MGV6_DESERT_STONE_BASE -32
-#define MGV6_ICE_BASE 0
-#define MGV6_FREQ_HOT 0.4
-#define MGV6_FREQ_SNOW -0.4
-#define MGV6_FREQ_TAIGA 0.5
-#define MGV6_FREQ_JUNGLE 0.5
-
-//////////// Mapgen V6 flags
-#define MGV6_JUNGLES 0x01
-#define MGV6_BIOMEBLEND 0x02
-#define MGV6_MUDFLOW 0x04
-#define MGV6_SNOWBIOMES 0x08
-#define MGV6_FLAT 0x10
-#define MGV6_TREES 0x20
-
-
-extern FlagDesc flagdesc_mapgen_v6[];
-
-
-enum BiomeV6Type
-{
- BT_NORMAL,
- BT_DESERT,
- BT_JUNGLE,
- BT_TUNDRA,
- BT_TAIGA,
-};
-
-
-struct MapgenV6Params : public MapgenParams {
- u32 spflags = MGV6_JUNGLES | MGV6_SNOWBIOMES | MGV6_TREES |
- MGV6_BIOMEBLEND | MGV6_MUDFLOW;
- float freq_desert = 0.45f;
- float freq_beach = 0.15f;
- NoiseParams np_terrain_base;
- NoiseParams np_terrain_higher;
- NoiseParams np_steepness;
- NoiseParams np_height_select;
- NoiseParams np_mud;
- NoiseParams np_beach;
- NoiseParams np_biome;
- NoiseParams np_cave;
- NoiseParams np_humidity;
- NoiseParams np_trees;
- NoiseParams np_apple_trees;
-
- MapgenV6Params();
- ~MapgenV6Params() = default;
-
- void readParams(const Settings *settings);
- void writeParams(Settings *settings) const;
-};
-
-
-class MapgenV6 : public Mapgen {
-public:
- EmergeManager *m_emerge;
-
- int ystride;
- u32 spflags;
-
- v3s16 node_min;
- v3s16 node_max;
- v3s16 full_node_min;
- v3s16 full_node_max;
- v3s16 central_area_size;
-
- Noise *noise_terrain_base;
- Noise *noise_terrain_higher;
- Noise *noise_steepness;
- Noise *noise_height_select;
- Noise *noise_mud;
- Noise *noise_beach;
- Noise *noise_biome;
- Noise *noise_humidity;
- NoiseParams *np_cave;
- NoiseParams *np_humidity;
- NoiseParams *np_trees;
- NoiseParams *np_apple_trees;
- float freq_desert;
- float freq_beach;
-
- content_t c_stone;
- content_t c_dirt;
- content_t c_dirt_with_grass;
- content_t c_sand;
- content_t c_water_source;
- content_t c_lava_source;
- content_t c_gravel;
- content_t c_desert_stone;
- content_t c_desert_sand;
- content_t c_dirt_with_snow;
- content_t c_snow;
- content_t c_snowblock;
- content_t c_ice;
-
- content_t c_cobble;
- content_t c_mossycobble;
- content_t c_stair_cobble;
- content_t c_stair_desert_stone;
-
- MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge);
- ~MapgenV6();
-
- virtual MapgenType getType() const { return MAPGEN_V6; }
-
- void makeChunk(BlockMakeData *data);
- int getGroundLevelAtPoint(v2s16 p);
- int getSpawnLevelAtPoint(v2s16 p);
-
- float baseTerrainLevel(float terrain_base, float terrain_higher,
- float steepness, float height_select);
- virtual float baseTerrainLevelFromNoise(v2s16 p);
- virtual float baseTerrainLevelFromMap(v2s16 p);
- virtual float baseTerrainLevelFromMap(int index);
-
- s16 find_stone_level(v2s16 p2d);
- bool block_is_underground(u64 seed, v3s16 blockpos);
- s16 find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision);
-
- float getHumidity(v2s16 p);
- float getTreeAmount(v2s16 p);
- bool getHaveAppleTree(v2s16 p);
- float getMudAmount(v2s16 p);
- virtual float getMudAmount(int index);
- bool getHaveBeach(v2s16 p);
- bool getHaveBeach(int index);
- BiomeV6Type getBiome(v2s16 p);
- BiomeV6Type getBiome(int index, v2s16 p);
-
- u32 get_blockseed(u64 seed, v3s16 p);
-
- virtual void calculateNoise();
- int generateGround();
- void addMud();
- void flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos);
- void moveMud(u32 remove_index, u32 place_index,
- u32 above_remove_index, v2s16 pos, v3s16 em);
- void growGrass();
- void placeTreesAndJungleGrass();
- virtual void generateCaves(int max_stone_y);
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "content_sao.h"
-#include "nodedef.h"
-#include "voxelalgorithms.h"
-//#include "profiler.h" // For TimeTaker
-#include "settings.h" // For g_settings
-#include "emerge.h"
-#include "dungeongen.h"
-#include "cavegen.h"
-#include "mg_biome.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mapgen_v7.h"
-
-
-FlagDesc flagdesc_mapgen_v7[] = {
- {"mountains", MGV7_MOUNTAINS},
- {"ridges", MGV7_RIDGES},
- {"floatlands", MGV7_FLOATLANDS},
- {"caverns", MGV7_CAVERNS},
- {NULL, 0}
-};
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
-{
- spflags = params->spflags;
- mount_zero_level = params->mount_zero_level;
- cave_width = params->cave_width;
- large_cave_depth = params->large_cave_depth;
- lava_depth = params->lava_depth;
- float_mount_density = params->float_mount_density;
- floatland_level = params->floatland_level;
- shadow_limit = params->shadow_limit;
- cavern_limit = params->cavern_limit;
- cavern_taper = params->cavern_taper;
- cavern_threshold = params->cavern_threshold;
-
- // This is to avoid a divide-by-zero.
- // Parameter will be saved to map_meta.txt in limited form.
- params->float_mount_height = MYMAX(params->float_mount_height, 1.0f);
- float_mount_height = params->float_mount_height;
-
- // 2D noise
- noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Z);
- noise_terrain_alt = new Noise(¶ms->np_terrain_alt, seed, csize.X, csize.Z);
- noise_terrain_persist = new Noise(¶ms->np_terrain_persist, seed, csize.X, csize.Z);
- noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Z);
- noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
-
- if (spflags & MGV7_MOUNTAINS)
- noise_mount_height = new Noise(¶ms->np_mount_height, seed, csize.X, csize.Z);
-
- if (spflags & MGV7_FLOATLANDS) {
- noise_floatland_base = new Noise(¶ms->np_floatland_base, seed, csize.X, csize.Z);
- noise_float_base_height = new Noise(¶ms->np_float_base_height, seed, csize.X, csize.Z);
- }
-
- if (spflags & MGV7_RIDGES) {
- noise_ridge_uwater = new Noise(¶ms->np_ridge_uwater, seed, csize.X, csize.Z);
- // 3D noise, 1-up 1-down overgeneration
- noise_ridge = new Noise(¶ms->np_ridge, seed, csize.X, csize.Y + 2, csize.Z);
- }
- // 3D noise, 1 up, 1 down overgeneration
- if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS))
- noise_mountain = new Noise(¶ms->np_mountain, seed, csize.X, csize.Y + 2, csize.Z);
- // 3D noise, 1 down overgeneration
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
- MapgenBasic::np_cavern = params->np_cavern;
-}
-
-
-MapgenV7::~MapgenV7()
-{
- delete noise_terrain_base;
- delete noise_terrain_alt;
- delete noise_terrain_persist;
- delete noise_height_select;
- delete noise_filler_depth;
-
- if (spflags & MGV7_MOUNTAINS)
- delete noise_mount_height;
-
- if (spflags & MGV7_FLOATLANDS) {
- delete noise_floatland_base;
- delete noise_float_base_height;
- }
-
- if (spflags & MGV7_RIDGES) {
- delete noise_ridge_uwater;
- delete noise_ridge;
- }
-
- if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS))
- delete noise_mountain;
-}
-
-
-MapgenV7Params::MapgenV7Params():
- np_terrain_base (4, 70, v3f(600, 600, 600), 82341, 5, 0.6, 2.0),
- np_terrain_alt (4, 25, v3f(600, 600, 600), 5934, 5, 0.6, 2.0),
- np_terrain_persist (0.6, 0.1, v3f(2000, 2000, 2000), 539, 3, 0.6, 2.0),
- np_height_select (-8, 16, v3f(500, 500, 500), 4213, 6, 0.7, 2.0),
- np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
- np_mount_height (256, 112, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0),
- np_ridge_uwater (0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0),
- np_floatland_base (-0.6, 1.5, v3f(600, 600, 600), 114, 5, 0.6, 2.0),
- np_float_base_height (48, 24, v3f(300, 300, 300), 907, 4, 0.7, 2.0),
- np_mountain (-0.6, 1, v3f(250, 350, 250), 5333, 5, 0.63, 2.0),
- np_ridge (0, 1, v3f(100, 100, 100), 6467, 4, 0.75, 2.0),
- np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0),
- np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
-{
-}
-
-
-void MapgenV7Params::readParams(const Settings *settings)
-{
- settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7);
- settings->getS16NoEx("mgv7_mount_zero_level", mount_zero_level);
- settings->getFloatNoEx("mgv7_cave_width", cave_width);
- settings->getS16NoEx("mgv7_large_cave_depth", large_cave_depth);
- settings->getS16NoEx("mgv7_lava_depth", lava_depth);
- settings->getFloatNoEx("mgv7_float_mount_density", float_mount_density);
- settings->getFloatNoEx("mgv7_float_mount_height", float_mount_height);
- settings->getS16NoEx("mgv7_floatland_level", floatland_level);
- settings->getS16NoEx("mgv7_shadow_limit", shadow_limit);
- settings->getS16NoEx("mgv7_cavern_limit", cavern_limit);
- settings->getS16NoEx("mgv7_cavern_taper", cavern_taper);
- settings->getFloatNoEx("mgv7_cavern_threshold", cavern_threshold);
-
- settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base);
- settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt);
- settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist);
- settings->getNoiseParams("mgv7_np_height_select", np_height_select);
- settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth);
- settings->getNoiseParams("mgv7_np_mount_height", np_mount_height);
- settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater);
- settings->getNoiseParams("mgv7_np_floatland_base", np_floatland_base);
- settings->getNoiseParams("mgv7_np_float_base_height", np_float_base_height);
- settings->getNoiseParams("mgv7_np_mountain", np_mountain);
- settings->getNoiseParams("mgv7_np_ridge", np_ridge);
- settings->getNoiseParams("mgv7_np_cavern", np_cavern);
- settings->getNoiseParams("mgv7_np_cave1", np_cave1);
- settings->getNoiseParams("mgv7_np_cave2", np_cave2);
-}
-
-
-void MapgenV7Params::writeParams(Settings *settings) const
-{
- settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7, U32_MAX);
- settings->setS16("mgv7_mount_zero_level", mount_zero_level);
- settings->setFloat("mgv7_cave_width", cave_width);
- settings->setS16("mgv7_large_cave_depth", large_cave_depth);
- settings->setS16("mgv7_lava_depth", lava_depth);
- settings->setFloat("mgv7_float_mount_density", float_mount_density);
- settings->setFloat("mgv7_float_mount_height", float_mount_height);
- settings->setS16("mgv7_floatland_level", floatland_level);
- settings->setS16("mgv7_shadow_limit", shadow_limit);
- settings->setS16("mgv7_cavern_limit", cavern_limit);
- settings->setS16("mgv7_cavern_taper", cavern_taper);
- settings->setFloat("mgv7_cavern_threshold", cavern_threshold);
-
- settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base);
- settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt);
- settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist);
- settings->setNoiseParams("mgv7_np_height_select", np_height_select);
- settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth);
- settings->setNoiseParams("mgv7_np_mount_height", np_mount_height);
- settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater);
- settings->setNoiseParams("mgv7_np_floatland_base", np_floatland_base);
- settings->setNoiseParams("mgv7_np_float_base_height", np_float_base_height);
- settings->setNoiseParams("mgv7_np_mountain", np_mountain);
- settings->setNoiseParams("mgv7_np_ridge", np_ridge);
- settings->setNoiseParams("mgv7_np_cavern", np_cavern);
- settings->setNoiseParams("mgv7_np_cave1", np_cave1);
- settings->setNoiseParams("mgv7_np_cave2", np_cave2);
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-int MapgenV7::getSpawnLevelAtPoint(v2s16 p)
-{
- // If rivers are enabled, first check if in a river
- if (spflags & MGV7_RIDGES) {
- float width = 0.2;
- float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2;
- if (fabs(uwatern) <= width)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
- }
-
- // Terrain noise 'offset' is the average level of that terrain.
- // At least 50% of terrain will be below the higher of base and alt terrain
- // 'offset's.
- // Raising the maximum spawn level above 'water_level + 16' is necessary
- // for when terrain 'offset's are set much higher than water_level.
- s16 max_spawn_y = MYMAX(MYMAX(noise_terrain_alt->np.offset,
- noise_terrain_base->np.offset),
- water_level + 16);
- // Base terrain calculation
- s16 y = baseTerrainLevelAtPoint(p.X, p.Y);
-
- // If mountains are disabled, terrain level is base terrain level.
- // Avoids mid-air spawn where mountain terrain would have been.
- if (!(spflags & MGV7_MOUNTAINS)) {
- if (y < water_level || y > max_spawn_y)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- // y + 2 because y is surface level and due to biome 'dust'
- return y + 2;
- }
-
- // Search upwards for first node without mountain terrain
- int iters = 256;
- while (iters > 0 && y <= max_spawn_y) {
- if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) {
- if (y <= water_level || y > max_spawn_y)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- // y + 1 due to biome 'dust'
- return y + 1;
- }
- y++;
- iters--;
- }
-
- // Unsuitable spawn point
- return MAX_MAP_GENERATION_LIMIT;
-}
-
-
-void MapgenV7::makeChunk(BlockMakeData *data)
-{
- // Pre-conditions
- assert(data->vmanip);
- assert(data->nodedef);
- assert(data->blockpos_requested.X >= data->blockpos_min.X &&
- data->blockpos_requested.Y >= data->blockpos_min.Y &&
- data->blockpos_requested.Z >= data->blockpos_min.Z);
- assert(data->blockpos_requested.X <= data->blockpos_max.X &&
- data->blockpos_requested.Y <= data->blockpos_max.Y &&
- data->blockpos_requested.Z <= data->blockpos_max.Z);
-
- this->generating = true;
- this->vm = data->vmanip;
- this->ndef = data->nodedef;
- //TimeTaker t("makeChunk");
-
- v3s16 blockpos_min = data->blockpos_min;
- v3s16 blockpos_max = data->blockpos_max;
- node_min = blockpos_min * MAP_BLOCKSIZE;
- node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
- full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
- full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- blockseed = getBlockSeed2(full_node_min, seed);
-
- // Generate base and mountain terrain
- // An initial heightmap is no longer created here for use in generateRidgeTerrain()
- s16 stone_surface_max_y = generateTerrain();
-
- // Generate rivers
- if (spflags & MGV7_RIDGES)
- generateRidgeTerrain();
-
- // Create heightmap
- updateHeightmap(node_min, node_max);
-
- // Init biome generator, place biome-specific nodes, and build biomemap
- biomegen->calcBiomeNoise(node_min);
-
- MgStoneType mgstone_type;
- content_t biome_stone;
- generateBiomes(&mgstone_type, &biome_stone);
-
- // Generate caverns, tunnels and classic caves
- if (flags & MG_CAVES) {
- bool near_cavern = false;
- // Generate caverns
- if (spflags & MGV7_CAVERNS)
- near_cavern = generateCaverns(stone_surface_max_y);
- // Generate tunnels and classic caves
- if (near_cavern)
- // Disable classic caves in this mapchunk by setting
- // 'large cave depth' to world base. Avoids excessive liquid in
- // large caverns and floating blobs of overgenerated liquid.
- generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
- else
- generateCaves(stone_surface_max_y, large_cave_depth);
- }
-
- // Generate dungeons
- if (flags & MG_DUNGEONS)
- generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
-
- // Generate the registered decorations
- if (flags & MG_DECORATIONS)
- m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
-
- // Generate the registered ores
- m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
-
- // Sprinkle some dust on top after everything else was generated
- dustTopNodes();
-
- //printf("makeChunk: %dms\n", t.stop());
-
- // Update liquids
- updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
-
- // Calculate lighting
- // Limit floatland shadow
- bool propagate_shadow = !((spflags & MGV7_FLOATLANDS) &&
- node_min.Y <= shadow_limit && node_max.Y >= shadow_limit);
-
- if (flags & MG_LIGHT)
- calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
- full_node_min, full_node_max, propagate_shadow);
-
- //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
- // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
-
- this->generating = false;
-}
-
-
-float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z)
-{
- float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed);
- hselect = rangelim(hselect, 0.0, 1.0);
-
- float persist = NoisePerlin2D(&noise_terrain_persist->np, x, z, seed);
-
- noise_terrain_base->np.persist = persist;
- float height_base = NoisePerlin2D(&noise_terrain_base->np, x, z, seed);
-
- noise_terrain_alt->np.persist = persist;
- float height_alt = NoisePerlin2D(&noise_terrain_alt->np, x, z, seed);
-
- if (height_alt > height_base)
- return height_alt;
-
- return (height_base * hselect) + (height_alt * (1.0 - hselect));
-}
-
-
-float MapgenV7::baseTerrainLevelFromMap(int index)
-{
- float hselect = rangelim(noise_height_select->result[index], 0.0, 1.0);
- float height_base = noise_terrain_base->result[index];
- float height_alt = noise_terrain_alt->result[index];
-
- if (height_alt > height_base)
- return height_alt;
-
- return (height_base * hselect) + (height_alt * (1.0 - hselect));
-}
-
-
-bool MapgenV7::getMountainTerrainAtPoint(s16 x, s16 y, s16 z)
-{
- float mnt_h_n =
- MYMAX(NoisePerlin2D(&noise_mount_height->np, x, z, seed), 1.0f);
- float density_gradient = -((float)(y - mount_zero_level) / mnt_h_n);
- float mnt_n = NoisePerlin3D(&noise_mountain->np, x, y, z, seed);
-
- return mnt_n + density_gradient >= 0.0;
-}
-
-
-bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y)
-{
- float mounthn = MYMAX(noise_mount_height->result[idx_xz], 1.0f);
- float density_gradient = -((float)(y - mount_zero_level) / mounthn);
- float mountn = noise_mountain->result[idx_xyz];
-
- return mountn + density_gradient >= 0.0;
-}
-
-
-bool MapgenV7::getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y)
-{
- // Make rim 2 nodes thick to match floatland base terrain
- float density_gradient = (y >= floatland_level) ?
- -pow((float)(y - floatland_level) / float_mount_height, 0.75f) :
- -pow((float)(floatland_level - 1 - y) / float_mount_height, 0.75f);
-
- float floatn = noise_mountain->result[idx_xyz] + float_mount_density;
-
- return floatn + density_gradient >= 0.0f;
-}
-
-
-void MapgenV7::floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz)
-{
- // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT
- s16 base_min = MAX_MAP_GENERATION_LIMIT + 1;
- s16 base_max = MAX_MAP_GENERATION_LIMIT;
-
- float n_base = noise_floatland_base->result[idx_xz];
- if (n_base > 0.0f) {
- float n_base_height =
- MYMAX(noise_float_base_height->result[idx_xz], 1.0f);
- float amp = n_base * n_base_height;
- float ridge = n_base_height / 3.0f;
- base_min = floatland_level - amp / 1.5f;
-
- if (amp > ridge * 2.0f) {
- // Lake bed
- base_max = floatland_level - (amp - ridge * 2.0f) / 2.0f;
- } else {
- // Hills and ridges
- float diff = fabs(amp - ridge) / ridge;
- // Smooth ridges using the 'smoothstep function'
- float smooth_diff = diff * diff * (3.0f - 2.0f * diff);
- base_max = floatland_level + ridge - smooth_diff * ridge;
- }
- }
-
- *float_base_min = base_min;
- *float_base_max = base_max;
-}
-
-
-int MapgenV7::generateTerrain()
-{
- MapNode n_air(CONTENT_AIR);
- MapNode n_stone(c_stone);
- MapNode n_water(c_water_source);
-
- //// Calculate noise for terrain generation
- noise_terrain_persist->perlinMap2D(node_min.X, node_min.Z);
- float *persistmap = noise_terrain_persist->result;
-
- noise_terrain_base->perlinMap2D(node_min.X, node_min.Z, persistmap);
- noise_terrain_alt->perlinMap2D(node_min.X, node_min.Z, persistmap);
- noise_height_select->perlinMap2D(node_min.X, node_min.Z);
-
- if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) {
- noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
- }
-
- if (spflags & MGV7_MOUNTAINS) {
- noise_mount_height->perlinMap2D(node_min.X, node_min.Z);
- }
-
- if (spflags & MGV7_FLOATLANDS) {
- noise_floatland_base->perlinMap2D(node_min.X, node_min.Z);
- noise_float_base_height->perlinMap2D(node_min.X, node_min.Z);
- }
-
- //// Place nodes
- const v3s16 &em = vm->m_area.getExtent();
- s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
- u32 index2d = 0;
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) {
- s16 surface_y = baseTerrainLevelFromMap(index2d);
- if (surface_y > stone_surface_max_y)
- stone_surface_max_y = surface_y;
-
- // Get extent of floatland base terrain
- // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT
- s16 float_base_min = MAX_MAP_GENERATION_LIMIT + 1;
- s16 float_base_max = MAX_MAP_GENERATION_LIMIT;
- if (spflags & MGV7_FLOATLANDS)
- floatBaseExtentFromMap(&float_base_min, &float_base_max, index2d);
-
- u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
- u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
-
- for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
- if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
- if (y <= surface_y) {
- vm->m_data[vi] = n_stone; // Base terrain
- } else if ((spflags & MGV7_MOUNTAINS) &&
- getMountainTerrainFromMap(index3d, index2d, y)) {
- vm->m_data[vi] = n_stone; // Mountain terrain
- if (y > stone_surface_max_y)
- stone_surface_max_y = y;
- } else if ((spflags & MGV7_FLOATLANDS) &&
- ((y >= float_base_min && y <= float_base_max) ||
- getFloatlandMountainFromMap(index3d, index2d, y))) {
- vm->m_data[vi] = n_stone; // Floatland terrain
- stone_surface_max_y = node_max.Y;
- } else if (y <= water_level) {
- vm->m_data[vi] = n_water; // Ground level water
- } else if ((spflags & MGV7_FLOATLANDS) &&
- (y >= float_base_max && y <= floatland_level)) {
- vm->m_data[vi] = n_water; // Floatland water
- } else {
- vm->m_data[vi] = n_air;
- }
- }
- vm->m_area.add_y(em, vi, 1);
- index3d += ystride;
- }
- }
-
- return stone_surface_max_y;
-}
-
-
-void MapgenV7::generateRidgeTerrain()
-{
- if (node_max.Y < water_level - 16 ||
- ((spflags & MGV7_FLOATLANDS) && node_max.Y > shadow_limit))
- return;
-
- noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
- noise_ridge_uwater->perlinMap2D(node_min.X, node_min.Z);
-
- MapNode n_water(c_water_source);
- MapNode n_air(CONTENT_AIR);
- u32 index = 0;
- float width = 0.2;
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
- u32 vi = vm->m_area.index(node_min.X, y, z);
- for (s16 x = node_min.X; x <= node_max.X; x++, index++, vi++) {
- int j = (z - node_min.Z) * csize.X + (x - node_min.X);
-
- float uwatern = noise_ridge_uwater->result[j] * 2;
- if (fabs(uwatern) > width)
- continue;
-
- float altitude = y - water_level;
- float height_mod = (altitude + 17) / 2.5;
- float width_mod = width - fabs(uwatern);
- float nridge = noise_ridge->result[index] * MYMAX(altitude, 0) / 7.0;
-
- if (nridge + width_mod * height_mod < 0.6)
- continue;
-
- vm->m_data[vi] = (y > water_level) ? n_air : n_water;
- }
- }
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-//// Code Boneyard
-////
-//// Much of the stuff here has potential to become useful again at some point
-//// in the future, but we don't want it to get lost or forgotten in version
-//// control.
-////
-
-#if 0
-int MapgenV7::generateMountainTerrain(s16 ymax)
-{
- MapNode n_stone(c_stone);
- u32 j = 0;
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
- u32 vi = vm->m_area.index(node_min.X, y, z);
- for (s16 x = node_min.X; x <= node_max.X; x++) {
- int index = (z - node_min.Z) * csize.X + (x - node_min.X);
- content_t c = vm->m_data[vi].getContent();
-
- if (getMountainTerrainFromMap(j, index, y)
- && (c == CONTENT_AIR || c == c_water_source)) {
- vm->m_data[vi] = n_stone;
- if (y > ymax)
- ymax = y;
- }
-
- vi++;
- j++;
- }
- }
-
- return ymax;
-}
-#endif
-
-
-#if 0
-void MapgenV7::carveRivers() {
- MapNode n_air(CONTENT_AIR), n_water_source(c_water_source);
- MapNode n_stone(c_stone);
- u32 index = 0;
-
- int river_depth = 4;
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
- float terrain_mod = noise_terrain_mod->result[index];
- NoiseParams *np = noise_terrain_river->np;
- np.persist = noise_terrain_persist->result[index];
- float terrain_river = NoisePerlin2DNoTxfm(np, x, z, seed);
- float height = terrain_river * (1 - abs(terrain_mod)) *
- noise_terrain_river->np.scale;
- height = log(height * height); //log(h^3) is pretty interesting for terrain
-
- s16 y = heightmap[index];
- if (height < 1.0 && y > river_depth &&
- y - river_depth >= node_min.Y && y <= node_max.Y) {
-
- for (s16 ry = y; ry != y - river_depth; ry--) {
- u32 vi = vm->m_area.index(x, ry, z);
- vm->m_data[vi] = n_air;
- }
-
- u32 vi = vm->m_area.index(x, y - river_depth, z);
- vm->m_data[vi] = n_water_source;
- }
- }
-}
-#endif
-
-
-#if 0
-void MapgenV7::addTopNodes()
-{
- v3s16 em = vm->m_area.getExtent();
- s16 ntopnodes;
- u32 index = 0;
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
- Biome *biome = bmgr->biomes[biomemap[index]];
-
- //////////////////// First, add top nodes below the ridge
- s16 y = ridge_heightmap[index];
-
- // This cutoff is good enough, but not perfect.
- // It will cut off potentially placed top nodes at chunk boundaries
- if (y < node_min.Y)
- continue;
- if (y > node_max.Y) {
- y = node_max.Y; // Let's see if we can still go downward anyway
- u32 vi = vm->m_area.index(x, y, z);
- content_t c = vm->m_data[vi].getContent();
- if (ndef->get(c).walkable)
- continue;
- }
-
- // N.B. It is necessary to search downward since ridge_heightmap[i]
- // might not be the actual height, just the lowest part in the chunk
- // where a ridge had been carved
- u32 i = vm->m_area.index(x, y, z);
- for (; y >= node_min.Y; y--) {
- content_t c = vm->m_data[i].getContent();
- if (ndef->get(c).walkable)
- break;
- vm->m_area.add_y(em, i, -1);
- }
-
- if (y != node_min.Y - 1 && y >= water_level) {
- ridge_heightmap[index] = y; //update ridgeheight
- ntopnodes = biome->top_depth;
- for (; y <= node_max.Y && ntopnodes; y++) {
- ntopnodes--;
- vm->m_data[i] = MapNode(biome->c_top);
- vm->m_area.add_y(em, i, 1);
- }
- // If dirt, grow grass on it.
- if (y > water_level - 10 &&
- vm->m_data[i].getContent() == CONTENT_AIR) {
- vm->m_area.add_y(em, i, -1);
- if (vm->m_data[i].getContent() == c_dirt)
- vm->m_data[i] = MapNode(c_dirt_with_grass);
- }
- }
-
- //////////////////// Now, add top nodes on top of the ridge
- y = heightmap[index];
- if (y > node_max.Y) {
- y = node_max.Y; // Let's see if we can still go downward anyway
- u32 vi = vm->m_area.index(x, y, z);
- content_t c = vm->m_data[vi].getContent();
- if (ndef->get(c).walkable)
- continue;
- }
-
- i = vm->m_area.index(x, y, z);
- for (; y >= node_min.Y; y--) {
- content_t c = vm->m_data[i].getContent();
- if (ndef->get(c).walkable)
- break;
- vm->m_area.add_y(em, i, -1);
- }
-
- if (y != node_min.Y - 1) {
- ntopnodes = biome->top_depth;
- // Let's see if we've already added it...
- if (y == ridge_heightmap[index] + ntopnodes - 1)
- continue;
-
- for (; y <= node_max.Y && ntopnodes; y++) {
- ntopnodes--;
- vm->m_data[i] = MapNode(biome->c_top);
- vm->m_area.add_y(em, i, 1);
- }
- // If dirt, grow grass on it.
- if (y > water_level - 10 &&
- vm->m_data[i].getContent() == CONTENT_AIR) {
- vm->m_area.add_y(em, i, -1);
- if (vm->m_data[i].getContent() == c_dirt)
- vm->m_data[i] = MapNode(c_dirt_with_grass);
- }
- }
- }
-}
-#endif
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "mapgen.h"
-
-///////////// Mapgen V7 flags
-#define MGV7_MOUNTAINS 0x01
-#define MGV7_RIDGES 0x02
-#define MGV7_FLOATLANDS 0x04
-#define MGV7_CAVERNS 0x08
-#define MGV7_BIOMEREPEAT 0x10 // Now unused
-
-class BiomeManager;
-
-extern FlagDesc flagdesc_mapgen_v7[];
-
-
-struct MapgenV7Params : public MapgenParams {
- u32 spflags = MGV7_MOUNTAINS | MGV7_RIDGES | MGV7_CAVERNS;
- s16 mount_zero_level = 0;
- float cave_width = 0.09f;
- s16 large_cave_depth = -33;
- s16 lava_depth = -256;
- float float_mount_density = 0.6f;
- float float_mount_height = 128.0f;
- s16 floatland_level = 1280;
- s16 shadow_limit = 1024;
- s16 cavern_limit = -256;
- s16 cavern_taper = 256;
- float cavern_threshold = 0.7f;
-
- NoiseParams np_terrain_base;
- NoiseParams np_terrain_alt;
- NoiseParams np_terrain_persist;
- NoiseParams np_height_select;
- NoiseParams np_filler_depth;
- NoiseParams np_mount_height;
- NoiseParams np_ridge_uwater;
- NoiseParams np_floatland_base;
- NoiseParams np_float_base_height;
- NoiseParams np_mountain;
- NoiseParams np_ridge;
- NoiseParams np_cavern;
- NoiseParams np_cave1;
- NoiseParams np_cave2;
-
- MapgenV7Params();
- ~MapgenV7Params() = default;
-
- void readParams(const Settings *settings);
- void writeParams(Settings *settings) const;
-};
-
-class MapgenV7 : public MapgenBasic {
-public:
- MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge);
- ~MapgenV7();
-
- virtual MapgenType getType() const { return MAPGEN_V7; }
-
- virtual void makeChunk(BlockMakeData *data);
- int getSpawnLevelAtPoint(v2s16 p);
-
- float baseTerrainLevelAtPoint(s16 x, s16 z);
- float baseTerrainLevelFromMap(int index);
- bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z);
- bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y);
- bool getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y);
- void floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz);
-
- int generateTerrain();
- void generateRidgeTerrain();
-
-private:
- s16 mount_zero_level;
- s16 large_cave_depth;
- float float_mount_density;
- float float_mount_height;
- s16 floatland_level;
- s16 shadow_limit;
-
- Noise *noise_terrain_base;
- Noise *noise_terrain_alt;
- Noise *noise_terrain_persist;
- Noise *noise_height_select;
- Noise *noise_mount_height;
- Noise *noise_ridge_uwater;
- Noise *noise_floatland_base;
- Noise *noise_float_base_height;
- Noise *noise_mountain;
- Noise *noise_ridge;
-};
+++ /dev/null
-/*
-Minetest Valleys C
-Copyright (C) 2016-2017 Duane Robertson <duane@duanerobertson.com>
-Copyright (C) 2016-2017 paramat
-
-Based on Valleys Mapgen by Gael de Sailly
- (https://forum.minetest.net/viewtopic.php?f=9&t=11430)
-and mapgen_v7, mapgen_flat by kwolekr and paramat.
-
-Licensing changed by permission of Gael de Sailly.
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "mapgen.h"
-#include "voxel.h"
-#include "noise.h"
-#include "mapblock.h"
-#include "mapnode.h"
-#include "map.h"
-#include "nodedef.h"
-#include "voxelalgorithms.h"
-#include "settings.h" // For g_settings
-#include "emerge.h"
-#include "dungeongen.h"
-#include "mg_biome.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mapgen_valleys.h"
-#include "cavegen.h"
-
-
-//#undef NDEBUG
-//#include "assert.h"
-
-//#include "util/timetaker.h"
-//#include "profiler.h"
-
-
-//static Profiler mapgen_prof;
-//Profiler *mapgen_profiler = &mapgen_prof;
-
-static FlagDesc flagdesc_mapgen_valleys[] = {
- {"altitude_chill", MGVALLEYS_ALT_CHILL},
- {"humid_rivers", MGVALLEYS_HUMID_RIVERS},
- {NULL, 0}
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-MapgenValleys::MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
-{
- // NOTE: MapgenValleys has a hard dependency on BiomeGenOriginal
- m_bgen = (BiomeGenOriginal *)biomegen;
-
- BiomeParamsOriginal *bp = (BiomeParamsOriginal *)params->bparams;
-
- spflags = params->spflags;
- altitude_chill = params->altitude_chill;
- large_cave_depth = params->large_cave_depth;
- lava_features_lim = rangelim(params->lava_features, 0, 10);
- massive_cave_depth = params->massive_cave_depth;
- river_depth_bed = params->river_depth + 1.f;
- river_size_factor = params->river_size / 100.f;
- water_features_lim = rangelim(params->water_features, 0, 10);
- cave_width = params->cave_width;
-
- //// 2D Terrain noise
- noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z);
- noise_inter_valley_slope = new Noise(¶ms->np_inter_valley_slope, seed, csize.X, csize.Z);
- noise_rivers = new Noise(¶ms->np_rivers, seed, csize.X, csize.Z);
- noise_terrain_height = new Noise(¶ms->np_terrain_height, seed, csize.X, csize.Z);
- noise_valley_depth = new Noise(¶ms->np_valley_depth, seed, csize.X, csize.Z);
- noise_valley_profile = new Noise(¶ms->np_valley_profile, seed, csize.X, csize.Z);
-
- //// 3D Terrain noise
- // 1-up 1-down overgeneration
- noise_inter_valley_fill = new Noise(¶ms->np_inter_valley_fill, seed, csize.X, csize.Y + 2, csize.Z);
- // 1-down overgeneraion
- noise_cave1 = new Noise(¶ms->np_cave1, seed, csize.X, csize.Y + 1, csize.Z);
- noise_cave2 = new Noise(¶ms->np_cave2, seed, csize.X, csize.Y + 1, csize.Z);
- noise_massive_caves = new Noise(¶ms->np_massive_caves, seed, csize.X, csize.Y + 1, csize.Z);
-
- humid_rivers = (spflags & MGVALLEYS_HUMID_RIVERS);
- use_altitude_chill = (spflags & MGVALLEYS_ALT_CHILL);
- humidity_adjust = bp->np_humidity.offset - 50.f;
-
- // a small chance of overflows if the settings are very high
- cave_water_max_height = water_level + MYMAX(0, water_features_lim - 4) * 50;
- lava_max_height = water_level + MYMAX(0, lava_features_lim - 4) * 50;
-
- tcave_cache = new float[csize.Y + 2];
-}
-
-
-MapgenValleys::~MapgenValleys()
-{
- delete noise_cave1;
- delete noise_cave2;
- delete noise_filler_depth;
- delete noise_inter_valley_fill;
- delete noise_inter_valley_slope;
- delete noise_rivers;
- delete noise_massive_caves;
- delete noise_terrain_height;
- delete noise_valley_depth;
- delete noise_valley_profile;
-
- delete[] tcave_cache;
-}
-
-
-MapgenValleysParams::MapgenValleysParams():
- np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
- np_filler_depth (0.f, 1.2f, v3f(256, 256, 256), 1605, 3, 0.5f, 2.f),
- np_inter_valley_fill (0.f, 1.f, v3f(256, 512, 256), 1993, 6, 0.8f, 2.f),
- np_inter_valley_slope (0.5f, 0.5f, v3f(128, 128, 128), 746, 1, 1.f, 2.f),
- np_rivers (0.f, 1.f, v3f(256, 256, 256), -6050, 5, 0.6f, 2.f),
- np_massive_caves (0.f, 1.f, v3f(768, 256, 768), 59033, 6, 0.63f, 2.f),
- np_terrain_height (-10.f, 50.f, v3f(1024, 1024, 1024), 5202, 6, 0.4f, 2.f),
- np_valley_depth (5.f, 4.f, v3f(512, 512, 512), -1914, 1, 1.f, 2.f),
- np_valley_profile (0.6f, 0.5f, v3f(512, 512, 512), 777, 1, 1.f, 2.f)
-{
-}
-
-
-void MapgenValleysParams::readParams(const Settings *settings)
-{
- settings->getFlagStrNoEx("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys);
- settings->getU16NoEx("mgvalleys_altitude_chill", altitude_chill);
- settings->getS16NoEx("mgvalleys_large_cave_depth", large_cave_depth);
- settings->getU16NoEx("mgvalleys_lava_features", lava_features);
- settings->getS16NoEx("mgvalleys_massive_cave_depth", massive_cave_depth);
- settings->getU16NoEx("mgvalleys_river_depth", river_depth);
- settings->getU16NoEx("mgvalleys_river_size", river_size);
- settings->getU16NoEx("mgvalleys_water_features", water_features);
- settings->getFloatNoEx("mgvalleys_cave_width", cave_width);
-
- settings->getNoiseParams("mgvalleys_np_cave1", np_cave1);
- settings->getNoiseParams("mgvalleys_np_cave2", np_cave2);
- settings->getNoiseParams("mgvalleys_np_filler_depth", np_filler_depth);
- settings->getNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill);
- settings->getNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope);
- settings->getNoiseParams("mgvalleys_np_rivers", np_rivers);
- settings->getNoiseParams("mgvalleys_np_massive_caves", np_massive_caves);
- settings->getNoiseParams("mgvalleys_np_terrain_height", np_terrain_height);
- settings->getNoiseParams("mgvalleys_np_valley_depth", np_valley_depth);
- settings->getNoiseParams("mgvalleys_np_valley_profile", np_valley_profile);
-}
-
-
-void MapgenValleysParams::writeParams(Settings *settings) const
-{
- settings->setFlagStr("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys, U32_MAX);
- settings->setU16("mgvalleys_altitude_chill", altitude_chill);
- settings->setS16("mgvalleys_large_cave_depth", large_cave_depth);
- settings->setU16("mgvalleys_lava_features", lava_features);
- settings->setS16("mgvalleys_massive_cave_depth", massive_cave_depth);
- settings->setU16("mgvalleys_river_depth", river_depth);
- settings->setU16("mgvalleys_river_size", river_size);
- settings->setU16("mgvalleys_water_features", water_features);
- settings->setFloat("mgvalleys_cave_width", cave_width);
-
- settings->setNoiseParams("mgvalleys_np_cave1", np_cave1);
- settings->setNoiseParams("mgvalleys_np_cave2", np_cave2);
- settings->setNoiseParams("mgvalleys_np_filler_depth", np_filler_depth);
- settings->setNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill);
- settings->setNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope);
- settings->setNoiseParams("mgvalleys_np_rivers", np_rivers);
- settings->setNoiseParams("mgvalleys_np_massive_caves", np_massive_caves);
- settings->setNoiseParams("mgvalleys_np_terrain_height", np_terrain_height);
- settings->setNoiseParams("mgvalleys_np_valley_depth", np_valley_depth);
- settings->setNoiseParams("mgvalleys_np_valley_profile", np_valley_profile);
-}
-
-
-///////////////////////////////////////
-
-
-void MapgenValleys::makeChunk(BlockMakeData *data)
-{
- // Pre-conditions
- assert(data->vmanip);
- assert(data->nodedef);
- assert(data->blockpos_requested.X >= data->blockpos_min.X &&
- data->blockpos_requested.Y >= data->blockpos_min.Y &&
- data->blockpos_requested.Z >= data->blockpos_min.Z);
- assert(data->blockpos_requested.X <= data->blockpos_max.X &&
- data->blockpos_requested.Y <= data->blockpos_max.Y &&
- data->blockpos_requested.Z <= data->blockpos_max.Z);
-
- this->generating = true;
- this->vm = data->vmanip;
- this->ndef = data->nodedef;
-
- //TimeTaker t("makeChunk");
-
- v3s16 blockpos_min = data->blockpos_min;
- v3s16 blockpos_max = data->blockpos_max;
- node_min = blockpos_min * MAP_BLOCKSIZE;
- node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
- full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
- full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
-
- blockseed = getBlockSeed2(full_node_min, seed);
-
- // Generate biome noises. Note this must be executed strictly before
- // generateTerrain, because generateTerrain depends on intermediate
- // biome-related noises.
- m_bgen->calcBiomeNoise(node_min);
-
- // Generate noise maps and base terrain height.
- // Modify heat and humidity maps.
- calculateNoise();
-
- // Generate base terrain with initial heightmaps
- s16 stone_surface_max_y = generateTerrain();
-
- // Recalculate heightmap
- updateHeightmap(node_min, node_max);
-
- // Place biome-specific nodes and build biomemap
- MgStoneType mgstone_type;
- content_t biome_stone;
- generateBiomes(&mgstone_type, &biome_stone);
-
- // Cave creation.
- if (flags & MG_CAVES)
- generateCaves(stone_surface_max_y, large_cave_depth);
-
- // Dungeon creation
- if ((flags & MG_DUNGEONS) && node_max.Y < 50)
- generateDungeons(stone_surface_max_y, mgstone_type, biome_stone);
-
- // Generate the registered decorations
- if (flags & MG_DECORATIONS)
- m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
-
- // Generate the registered ores
- m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
-
- // Sprinkle some dust on top after everything else was generated
- dustTopNodes();
-
- //TimeTaker tll("liquid_lighting");
-
- updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
-
- if (flags & MG_LIGHT)
- calcLighting(
- node_min - v3s16(0, 1, 0),
- node_max + v3s16(0, 1, 0),
- full_node_min,
- full_node_max);
-
- //mapgen_profiler->avg("liquid_lighting", tll.stop() / 1000.f);
- //mapgen_profiler->avg("makeChunk", t.stop() / 1000.f);
-
- this->generating = false;
-}
-
-
-// Populate the noise tables and do most of the
-// calculation necessary to determine terrain height.
-void MapgenValleys::calculateNoise()
-{
- //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO);
-
- int x = node_min.X;
- int y = node_min.Y - 1;
- int z = node_min.Z;
-
- //TimeTaker tcn("actualNoise");
-
- noise_inter_valley_slope->perlinMap2D(x, z);
- noise_rivers->perlinMap2D(x, z);
- noise_terrain_height->perlinMap2D(x, z);
- noise_valley_depth->perlinMap2D(x, z);
- noise_valley_profile->perlinMap2D(x, z);
-
- noise_inter_valley_fill->perlinMap3D(x, y, z);
-
- //mapgen_profiler->avg("noisemaps", tcn.stop() / 1000.f);
-
- float heat_offset = 0.f;
- float humidity_scale = 1.f;
-
- // Altitude chill tends to reduce the average heat.
- if (use_altitude_chill)
- heat_offset = 5.f;
-
- // River humidity tends to increase the humidity range.
- if (humid_rivers) {
- humidity_scale = 0.8f;
- }
-
- for (s32 index = 0; index < csize.X * csize.Z; index++) {
- m_bgen->heatmap[index] += heat_offset;
- m_bgen->humidmap[index] *= humidity_scale;
- }
-
- TerrainNoise tn;
-
- u32 index = 0;
- for (tn.z = node_min.Z; tn.z <= node_max.Z; tn.z++)
- for (tn.x = node_min.X; tn.x <= node_max.X; tn.x++, index++) {
- // The parameters that we actually need to generate terrain
- // are passed by address (and the return value).
- tn.terrain_height = noise_terrain_height->result[index];
- // River noise is replaced with base terrain, which
- // is basically the height of the water table.
- tn.rivers = &noise_rivers->result[index];
- // Valley depth noise is replaced with the valley
- // number that represents the height of terrain
- // over rivers and is used to determine about
- // how close a river is for humidity calculation.
- tn.valley = &noise_valley_depth->result[index];
- tn.valley_profile = noise_valley_profile->result[index];
- // Slope noise is replaced by the calculated slope
- // which is used to get terrain height in the slow
- // method, to create sharper mountains.
- tn.slope = &noise_inter_valley_slope->result[index];
- tn.inter_valley_fill = noise_inter_valley_fill->result[index];
-
- // This is the actual terrain height.
- float mount = terrainLevelFromNoise(&tn);
- noise_terrain_height->result[index] = mount;
- }
-}
-
-
-// This keeps us from having to maintain two similar sets of
-// complicated code to determine ground level.
-float MapgenValleys::terrainLevelFromNoise(TerrainNoise *tn)
-{
- // The square function changes the behaviour of this noise:
- // very often small, and sometimes very high.
- float valley_d = MYSQUARE(*tn->valley);
-
- // valley_d is here because terrain is generally higher where valleys
- // are deep (mountains). base represents the height of the
- // rivers, most of the surface is above.
- float base = tn->terrain_height + valley_d;
-
- // "river" represents the distance from the river, in arbitrary units.
- float river = fabs(*tn->rivers) - river_size_factor;
-
- // Use the curve of the function 1-exp(-(x/a)^2) to model valleys.
- // Making "a" vary (0 < a <= 1) changes the shape of the valleys.
- // Try it with a geometry software !
- // (here x = "river" and a = valley_profile).
- // "valley" represents the height of the terrain, from the rivers.
- {
- float t = river / tn->valley_profile;
- *tn->valley = valley_d * (1.f - exp(- MYSQUARE(t)));
- }
-
- // approximate height of the terrain at this point
- float mount = base + *tn->valley;
-
- *tn->slope *= *tn->valley;
-
- // Rivers are placed where "river" is negative, so where the original
- // noise value is close to zero.
- // Base ground is returned as rivers since it's basically the water table.
- *tn->rivers = base;
- if (river < 0.f) {
- // Use the the function -sqrt(1-x^2) which models a circle.
- float depth;
- {
- float t = river / river_size_factor + 1;
- depth = (river_depth_bed * sqrt(MYMAX(0, 1.f - MYSQUARE(t))));
- }
-
- // base - depth : height of the bottom of the river
- // water_level - 3 : don't make rivers below 3 nodes under the surface
- // We use three because that's as low as the swamp biomes go.
- // There is no logical equivalent to this using rangelim.
- mount = MYMIN(MYMAX(base - depth, (float)(water_level - 3)), mount);
-
- // Slope has no influence on rivers.
- *tn->slope = 0.f;
- }
-
- return mount;
-}
-
-
-// This avoids duplicating the code in terrainLevelFromNoise, adding
-// only the final step of terrain generation without a noise map.
-float MapgenValleys::adjustedTerrainLevelFromNoise(TerrainNoise *tn)
-{
- float mount = terrainLevelFromNoise(tn);
- s16 y_start = myround(mount);
-
- for (s16 y = y_start; y <= y_start + 1000; y++) {
- float fill = NoisePerlin3D(&noise_inter_valley_fill->np, tn->x, y, tn->z, seed);
-
- if (fill * *tn->slope < y - mount) {
- mount = MYMAX(y - 1, mount);
- break;
- }
- }
-
- return mount;
-}
-
-
-int MapgenValleys::getSpawnLevelAtPoint(v2s16 p)
-{
- // Check to make sure this isn't a request for a location in a river.
- float rivers = NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed);
- if (fabs(rivers) < river_size_factor)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
- if (level_at_point <= water_level ||
- level_at_point > water_level + 32)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- return level_at_point;
-}
-
-
-float MapgenValleys::terrainLevelAtPoint(s16 x, s16 z)
-{
- TerrainNoise tn;
-
- float rivers = NoisePerlin2D(&noise_rivers->np, x, z, seed);
- float valley = NoisePerlin2D(&noise_valley_depth->np, x, z, seed);
- float inter_valley_slope = NoisePerlin2D(&noise_inter_valley_slope->np, x, z, seed);
-
- tn.x = x;
- tn.z = z;
- tn.terrain_height = NoisePerlin2D(&noise_terrain_height->np, x, z, seed);
- tn.rivers = &rivers;
- tn.valley = &valley;
- tn.valley_profile = NoisePerlin2D(&noise_valley_profile->np, x, z, seed);
- tn.slope = &inter_valley_slope;
- tn.inter_valley_fill = 0.f;
-
- return adjustedTerrainLevelFromNoise(&tn);
-}
-
-
-int MapgenValleys::generateTerrain()
-{
- // Raising this reduces the rate of evaporation.
- static const float evaporation = 300.f;
- // from the lua
- static const float humidity_dropoff = 4.f;
- // constant to convert altitude chill (compatible with lua) to heat
- static const float alt_to_heat = 20.f;
- // humidity reduction by altitude
- static const float alt_to_humid = 10.f;
-
- MapNode n_air(CONTENT_AIR);
- MapNode n_river_water(c_river_water_source);
- MapNode n_stone(c_stone);
- MapNode n_water(c_water_source);
-
- const v3s16 &em = vm->m_area.getExtent();
- s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT;
- u32 index_2d = 0;
-
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) {
- float river_y = noise_rivers->result[index_2d];
- float surface_y = noise_terrain_height->result[index_2d];
- float slope = noise_inter_valley_slope->result[index_2d];
- float t_heat = m_bgen->heatmap[index_2d];
-
- heightmap[index_2d] = -MAX_MAP_GENERATION_LIMIT;
-
- if (surface_y > surface_max_y)
- surface_max_y = ceil(surface_y);
-
- if (humid_rivers) {
- // Derive heat from (base) altitude. This will be most correct
- // at rivers, since other surface heights may vary below.
- if (use_altitude_chill && (surface_y > 0.f || river_y > 0.f))
- t_heat -= alt_to_heat * MYMAX(surface_y, river_y) / altitude_chill;
-
- // If humidity is low or heat is high, lower the water table.
- float delta = m_bgen->humidmap[index_2d] - 50.f;
- if (delta < 0.f) {
- float t_evap = (t_heat - 32.f) / evaporation;
- river_y += delta * MYMAX(t_evap, 0.08f);
- }
- }
-
- u32 index_3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
- u32 index_data = vm->m_area.index(x, node_min.Y - 1, z);
-
- // Mapgens concern themselves with stone and water.
- for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
- if (vm->m_data[index_data].getContent() == CONTENT_IGNORE) {
- float fill = noise_inter_valley_fill->result[index_3d];
- float surface_delta = (float)y - surface_y;
- bool river = y + 1 < river_y;
-
- if (slope * fill > surface_delta) {
- // ground
- vm->m_data[index_data] = n_stone;
- if (y > heightmap[index_2d])
- heightmap[index_2d] = y;
- if (y > surface_max_y)
- surface_max_y = y;
- } else if (y <= water_level) {
- // sea
- vm->m_data[index_data] = n_water;
- } else if (river) {
- // river
- vm->m_data[index_data] = n_river_water;
- } else { // air
- vm->m_data[index_data] = n_air;
- }
- }
-
- vm->m_area.add_y(em, index_data, 1);
- index_3d += ystride;
- }
-
- if (heightmap[index_2d] == -MAX_MAP_GENERATION_LIMIT) {
- s16 surface_y_int = myround(surface_y);
- if (surface_y_int > node_max.Y + 1 || surface_y_int < node_min.Y - 1) {
- // If surface_y is outside the chunk, it's good enough.
- heightmap[index_2d] = surface_y_int;
- } else {
- // If the ground is outside of this chunk, but surface_y
- // is within the chunk, give a value outside.
- heightmap[index_2d] = node_min.Y - 2;
- }
- }
-
- if (humid_rivers) {
- // Use base ground (water table) in a riverbed, to
- // avoid an unnatural rise in humidity.
- float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]);
- float humid = m_bgen->humidmap[index_2d];
- float water_depth = (t_alt - river_y) / humidity_dropoff;
- humid *= 1.f + pow(0.5f, MYMAX(water_depth, 1.f));
-
- // Reduce humidity with altitude (ignoring riverbeds).
- // This is similar to the lua version's seawater adjustment,
- // but doesn't increase the base humidity, which causes
- // problems with the default biomes.
- if (t_alt > 0.f)
- humid -= alt_to_humid * t_alt / altitude_chill;
-
- m_bgen->humidmap[index_2d] = humid;
- }
-
- // Assign the heat adjusted by any changed altitudes.
- // The altitude will change about half the time.
- if (use_altitude_chill) {
- // ground height ignoring riverbeds
- float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]);
- if (humid_rivers && heightmap[index_2d] == (s16)myround(surface_y))
- // The altitude hasn't changed. Use the first result.
- m_bgen->heatmap[index_2d] = t_heat;
- else if (t_alt > 0.f)
- m_bgen->heatmap[index_2d] -= alt_to_heat * t_alt / altitude_chill;
- }
- }
-
- return surface_max_y;
-}
-
-void MapgenValleys::generateCaves(s16 max_stone_y, s16 large_cave_depth)
-{
- if (max_stone_y < node_min.Y)
- return;
-
- noise_cave1->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
- noise_cave2->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
-
- PseudoRandom ps(blockseed + 72202);
-
- MapNode n_air(CONTENT_AIR);
- MapNode n_lava(c_lava_source);
- MapNode n_water(c_river_water_source);
-
- const v3s16 &em = vm->m_area.getExtent();
-
- // Cave blend distance near YMIN, YMAX
- const float massive_cave_blend = 128.f;
- // noise threshold for massive caves
- const float massive_cave_threshold = 0.6f;
- // mct: 1 = small rare caves, 0.5 1/3rd ground volume, 0 = 1/2 ground volume.
-
- float yblmin = -mapgen_limit + massive_cave_blend * 1.5f;
- float yblmax = massive_cave_depth - massive_cave_blend * 1.5f;
- bool made_a_big_one = false;
-
- // Cache the tcave values as they only vary by altitude.
- if (node_max.Y <= massive_cave_depth) {
- noise_massive_caves->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
-
- for (s16 y = node_min.Y - 1; y <= node_max.Y; y++) {
- float tcave = massive_cave_threshold;
-
- if (y < yblmin) {
- float t = (yblmin - y) / massive_cave_blend;
- tcave += MYSQUARE(t);
- } else if (y > yblmax) {
- float t = (y - yblmax) / massive_cave_blend;
- tcave += MYSQUARE(t);
- }
-
- tcave_cache[y - node_min.Y + 1] = tcave;
- }
- }
-
- // lava_depth varies between one and ten as you approach
- // the bottom of the world.
- s16 lava_depth = ceil((lava_max_height - node_min.Y + 1) * 10.f / mapgen_limit);
- // This allows random lava spawns to be less common at the surface.
- s16 lava_chance = MYCUBE(lava_features_lim) * lava_depth;
- // water_depth varies between ten and one on the way down.
- s16 water_depth = ceil((mapgen_limit - abs(node_min.Y) + 1) * 10.f / mapgen_limit);
- // This allows random water spawns to be more common at the surface.
- s16 water_chance = MYCUBE(water_features_lim) * water_depth;
-
- // Reduce the odds of overflows even further.
- if (node_max.Y > water_level) {
- lava_chance /= 3;
- water_chance /= 3;
- }
-
- u32 index_2d = 0;
- for (s16 z = node_min.Z; z <= node_max.Z; z++)
- for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) {
- Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index_2d]);
- bool tunnel_air_above = false;
- bool is_under_river = false;
- bool underground = false;
- u32 index_data = vm->m_area.index(x, node_max.Y, z);
- u32 index_3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + (x - node_min.X);
-
- // Dig caves on down loop to check for air above.
- // Don't excavate the overgenerated stone at node_max.Y + 1,
- // this creates a 'roof' over the tunnel, preventing light in
- // tunnels at mapchunk borders when generating mapchunks upwards.
- // This 'roof' is removed when the mapchunk above is generated.
- for (s16 y = node_max.Y; y >= node_min.Y - 1; y--,
- index_3d -= ystride,
- vm->m_area.add_y(em, index_data, -1)) {
-
- float terrain = noise_terrain_height->result[index_2d];
-
- // Saves some time.
- if (y > terrain + 10)
- continue;
-
- if (y < terrain - 40)
- underground = true;
-
- // Dig massive caves.
- if (node_max.Y <= massive_cave_depth
- && noise_massive_caves->result[index_3d]
- > tcave_cache[y - node_min.Y + 1]) {
- vm->m_data[index_data] = n_air;
- made_a_big_one = true;
- continue;
- }
-
- content_t c = vm->m_data[index_data].getContent();
- // Detect river water to place riverbed nodes in tunnels
- if (c == biome->c_river_water)
- is_under_river = true;
-
- float d1 = contour(noise_cave1->result[index_3d]);
- float d2 = contour(noise_cave2->result[index_3d]);
-
- if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) {
- // in a tunnel
- vm->m_data[index_data] = n_air;
- tunnel_air_above = true;
- } else if (c == biome->c_filler || c == biome->c_stone) {
- if (tunnel_air_above) {
- // at the tunnel floor
- s16 sr = ps.range(0, 39);
- u32 j = index_data;
- vm->m_area.add_y(em, j, 1);
-
- if (sr > terrain - y) {
- // Put biome nodes in tunnels near the surface
- if (is_under_river)
- vm->m_data[index_data] = MapNode(biome->c_riverbed);
- else if (underground)
- vm->m_data[index_data] = MapNode(biome->c_filler);
- else
- vm->m_data[index_data] = MapNode(biome->c_top);
- } else if (sr < 3 && underground) {
- sr = abs(ps.next());
- if (lava_features_lim > 0 && y <= lava_max_height
- && c == biome->c_stone && sr < lava_chance)
- vm->m_data[j] = n_lava;
-
- sr -= lava_chance;
-
- // If sr < 0 then we should have already placed lava --
- // don't immediately dump water on it.
- if (water_features_lim > 0 && y <= cave_water_max_height
- && sr >= 0 && sr < water_chance)
- vm->m_data[j] = n_water;
- }
- }
-
- tunnel_air_above = false;
- underground = true;
- } else {
- tunnel_air_above = false;
- }
- }
- }
-
- if (node_max.Y <= large_cave_depth && !made_a_big_one) {
- u32 bruises_count = ps.range(0, 2);
- for (u32 i = 0; i < bruises_count; i++) {
- CavesRandomWalk cave(ndef, &gennotify, seed, water_level,
- c_water_source, c_lava_source, lava_max_height);
-
- cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap);
- }
- }
-}
+++ /dev/null
-/*
-Minetest Valleys C
-Copyright (C) 2016-2017 Duane Robertson <duane@duanerobertson.com>
-Copyright (C) 2016-2017 paramat
-
-Based on Valleys Mapgen by Gael de Sailly
- (https://forum.minetest.net/viewtopic.php?f=9&t=11430)
-and mapgen_v7 by kwolekr and paramat.
-
-Licensing changed by permission of Gael de Sailly.
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "mapgen.h"
-
-////////////// Mapgen Valleys flags
-#define MGVALLEYS_ALT_CHILL 0x01
-#define MGVALLEYS_HUMID_RIVERS 0x02
-
-// Feed only one variable into these.
-#define MYSQUARE(x) (x) * (x)
-#define MYCUBE(x) (x) * (x) * (x)
-
-class BiomeManager;
-class BiomeGenOriginal;
-
-// Global profiler
-//class Profiler;
-//extern Profiler *mapgen_profiler;
-
-
-struct MapgenValleysParams : public MapgenParams {
- u32 spflags = MGVALLEYS_HUMID_RIVERS | MGVALLEYS_ALT_CHILL;
- s16 large_cave_depth = -33;
- s16 massive_cave_depth = -256; // highest altitude of massive caves
- u16 altitude_chill = 90; // The altitude at which temperature drops by 20C.
- u16 lava_features = 0; // How often water will occur in caves.
- u16 river_depth = 4; // How deep to carve river channels.
- u16 river_size = 5; // How wide to make rivers.
- u16 water_features = 0; // How often water will occur in caves.
- float cave_width = 0.09f;
- NoiseParams np_cave1;
- NoiseParams np_cave2;
- NoiseParams np_filler_depth;
- NoiseParams np_inter_valley_fill;
- NoiseParams np_inter_valley_slope;
- NoiseParams np_rivers;
- NoiseParams np_massive_caves;
- NoiseParams np_terrain_height;
- NoiseParams np_valley_depth;
- NoiseParams np_valley_profile;
-
- MapgenValleysParams();
- ~MapgenValleysParams() = default;
-
- void readParams(const Settings *settings);
- void writeParams(Settings *settings) const;
-};
-
-struct TerrainNoise {
- s16 x;
- s16 z;
- float terrain_height;
- float *rivers;
- float *valley;
- float valley_profile;
- float *slope;
- float inter_valley_fill;
-};
-
-class MapgenValleys : public MapgenBasic {
-public:
-
- MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge);
- ~MapgenValleys();
-
- virtual MapgenType getType() const { return MAPGEN_VALLEYS; }
-
- virtual void makeChunk(BlockMakeData *data);
- int getSpawnLevelAtPoint(v2s16 p);
-
- s16 large_cave_depth;
-
-private:
- BiomeGenOriginal *m_bgen;
-
- bool humid_rivers;
- bool use_altitude_chill;
- float humidity_adjust;
- s16 cave_water_max_height;
- s16 lava_max_height;
-
- float altitude_chill;
- s16 lava_features_lim;
- s16 massive_cave_depth;
- float river_depth_bed;
- float river_size_factor;
- float *tcave_cache;
- s16 water_features_lim;
- Noise *noise_inter_valley_fill;
- Noise *noise_inter_valley_slope;
- Noise *noise_rivers;
- Noise *noise_cave1;
- Noise *noise_cave2;
- Noise *noise_massive_caves;
- Noise *noise_terrain_height;
- Noise *noise_valley_depth;
- Noise *noise_valley_profile;
-
- float terrainLevelAtPoint(s16 x, s16 z);
-
- void calculateNoise();
-
- virtual int generateTerrain();
- float terrainLevelFromNoise(TerrainNoise *tn);
- float adjustedTerrainLevelFromNoise(TerrainNoise *tn);
-
- virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth);
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "mg_biome.h"
-#include "mg_decoration.h"
-#include "emerge.h"
-#include "server.h"
-#include "nodedef.h"
-#include "map.h" //for MMVManip
-#include "util/numeric.h"
-#include "porting.h"
-#include "settings.h"
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-BiomeManager::BiomeManager(Server *server) :
- ObjDefManager(server, OBJDEF_BIOME)
-{
- m_server = server;
-
- // Create default biome to be used in case none exist
- Biome *b = new Biome;
-
- b->name = "Default";
- b->flags = 0;
- b->depth_top = 0;
- b->depth_filler = -MAX_MAP_GENERATION_LIMIT;
- b->depth_water_top = 0;
- b->depth_riverbed = 0;
- b->y_min = -MAX_MAP_GENERATION_LIMIT;
- b->y_max = MAX_MAP_GENERATION_LIMIT;
- b->heat_point = 0.0;
- b->humidity_point = 0.0;
-
- b->m_nodenames.emplace_back("mapgen_stone");
- b->m_nodenames.emplace_back("mapgen_stone");
- b->m_nodenames.emplace_back("mapgen_stone");
- b->m_nodenames.emplace_back("mapgen_water_source");
- b->m_nodenames.emplace_back("mapgen_water_source");
- b->m_nodenames.emplace_back("mapgen_river_water_source");
- b->m_nodenames.emplace_back("mapgen_stone");
- b->m_nodenames.emplace_back("ignore");
- m_ndef->pendNodeResolve(b);
-
- add(b);
-}
-
-
-void BiomeManager::clear()
-{
- EmergeManager *emerge = m_server->getEmergeManager();
-
- // Remove all dangling references in Decorations
- DecorationManager *decomgr = emerge->decomgr;
- for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
- Decoration *deco = (Decoration *)decomgr->getRaw(i);
- deco->biomes.clear();
- }
-
- // Don't delete the first biome
- for (size_t i = 1; i < m_objects.size(); i++)
- delete (Biome *)m_objects[i];
-
- m_objects.resize(1);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-
-void BiomeParamsOriginal::readParams(const Settings *settings)
-{
- settings->getNoiseParams("mg_biome_np_heat", np_heat);
- settings->getNoiseParams("mg_biome_np_heat_blend", np_heat_blend);
- settings->getNoiseParams("mg_biome_np_humidity", np_humidity);
- settings->getNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend);
-}
-
-
-void BiomeParamsOriginal::writeParams(Settings *settings) const
-{
- settings->setNoiseParams("mg_biome_np_heat", np_heat);
- settings->setNoiseParams("mg_biome_np_heat_blend", np_heat_blend);
- settings->setNoiseParams("mg_biome_np_humidity", np_humidity);
- settings->setNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend);
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-
-BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr,
- BiomeParamsOriginal *params, v3s16 chunksize)
-{
- m_bmgr = biomemgr;
- m_params = params;
- m_csize = chunksize;
-
- noise_heat = new Noise(¶ms->np_heat,
- params->seed, m_csize.X, m_csize.Z);
- noise_humidity = new Noise(¶ms->np_humidity,
- params->seed, m_csize.X, m_csize.Z);
- noise_heat_blend = new Noise(¶ms->np_heat_blend,
- params->seed, m_csize.X, m_csize.Z);
- noise_humidity_blend = new Noise(¶ms->np_humidity_blend,
- params->seed, m_csize.X, m_csize.Z);
-
- heatmap = noise_heat->result;
- humidmap = noise_humidity->result;
- biomemap = new biome_t[m_csize.X * m_csize.Z];
-}
-
-BiomeGenOriginal::~BiomeGenOriginal()
-{
- delete []biomemap;
-
- delete noise_heat;
- delete noise_humidity;
- delete noise_heat_blend;
- delete noise_humidity_blend;
-}
-
-
-Biome *BiomeGenOriginal::calcBiomeAtPoint(v3s16 pos) const
-{
- float heat =
- NoisePerlin2D(&m_params->np_heat, pos.X, pos.Z, m_params->seed) +
- NoisePerlin2D(&m_params->np_heat_blend, pos.X, pos.Z, m_params->seed);
- float humidity =
- NoisePerlin2D(&m_params->np_humidity, pos.X, pos.Z, m_params->seed) +
- NoisePerlin2D(&m_params->np_humidity_blend, pos.X, pos.Z, m_params->seed);
-
- return calcBiomeFromNoise(heat, humidity, pos.Y);
-}
-
-
-void BiomeGenOriginal::calcBiomeNoise(v3s16 pmin)
-{
- m_pmin = pmin;
-
- noise_heat->perlinMap2D(pmin.X, pmin.Z);
- noise_humidity->perlinMap2D(pmin.X, pmin.Z);
- noise_heat_blend->perlinMap2D(pmin.X, pmin.Z);
- noise_humidity_blend->perlinMap2D(pmin.X, pmin.Z);
-
- for (s32 i = 0; i < m_csize.X * m_csize.Z; i++) {
- noise_heat->result[i] += noise_heat_blend->result[i];
- noise_humidity->result[i] += noise_humidity_blend->result[i];
- }
-}
-
-
-biome_t *BiomeGenOriginal::getBiomes(s16 *heightmap)
-{
- for (s32 i = 0; i != m_csize.X * m_csize.Z; i++) {
- Biome *biome = calcBiomeFromNoise(
- noise_heat->result[i],
- noise_humidity->result[i],
- heightmap[i]);
-
- biomemap[i] = biome->index;
- }
-
- return biomemap;
-}
-
-
-Biome *BiomeGenOriginal::getBiomeAtPoint(v3s16 pos) const
-{
- return getBiomeAtIndex(
- (pos.Z - m_pmin.Z) * m_csize.X + (pos.X - m_pmin.X),
- pos.Y);
-}
-
-
-Biome *BiomeGenOriginal::getBiomeAtIndex(size_t index, s16 y) const
-{
- return calcBiomeFromNoise(
- noise_heat->result[index],
- noise_humidity->result[index],
- y);
-}
-
-
-Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, s16 y) const
-{
- Biome *b, *biome_closest = NULL;
- float dist_min = FLT_MAX;
-
- for (size_t i = 1; i < m_bmgr->getNumObjects(); i++) {
- b = (Biome *)m_bmgr->getRaw(i);
- if (!b || y > b->y_max || y < b->y_min)
- continue;
-
- float d_heat = heat - b->heat_point;
- float d_humidity = humidity - b->humidity_point;
- float dist = (d_heat * d_heat) +
- (d_humidity * d_humidity);
- if (dist < dist_min) {
- dist_min = dist;
- biome_closest = b;
- }
- }
-
- return biome_closest ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE);
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-
-void Biome::resolveNodeNames()
-{
- getIdFromNrBacklog(&c_top, "mapgen_stone", CONTENT_AIR);
- getIdFromNrBacklog(&c_filler, "mapgen_stone", CONTENT_AIR);
- getIdFromNrBacklog(&c_stone, "mapgen_stone", CONTENT_AIR);
- getIdFromNrBacklog(&c_water_top, "mapgen_water_source", CONTENT_AIR);
- getIdFromNrBacklog(&c_water, "mapgen_water_source", CONTENT_AIR);
- getIdFromNrBacklog(&c_river_water, "mapgen_river_water_source", CONTENT_AIR);
- getIdFromNrBacklog(&c_riverbed, "mapgen_stone", CONTENT_AIR);
- getIdFromNrBacklog(&c_dust, "ignore", CONTENT_IGNORE);
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "objdef.h"
-#include "nodedef.h"
-#include "noise.h"
-
-class Server;
-class Settings;
-class BiomeManager;
-
-////
-//// Biome
-////
-
-typedef u8 biome_t;
-
-#define BIOME_NONE ((biome_t)0)
-
-// TODO(hmmmm): Decide whether this is obsolete or will be used in the future
-enum BiomeType {
- BIOMETYPE_NORMAL,
- BIOMETYPE_LIQUID,
- BIOMETYPE_NETHER,
- BIOMETYPE_AETHER,
- BIOMETYPE_FLAT,
-};
-
-class Biome : public ObjDef, public NodeResolver {
-public:
- u32 flags;
-
- content_t c_top;
- content_t c_filler;
- content_t c_stone;
- content_t c_water_top;
- content_t c_water;
- content_t c_river_water;
- content_t c_riverbed;
- content_t c_dust;
-
- s16 depth_top;
- s16 depth_filler;
- s16 depth_water_top;
- s16 depth_riverbed;
-
- s16 y_min;
- s16 y_max;
- float heat_point;
- float humidity_point;
-
- virtual void resolveNodeNames();
-};
-
-
-////
-//// BiomeGen
-////
-
-enum BiomeGenType {
- BIOMEGEN_ORIGINAL,
-};
-
-struct BiomeParams {
- virtual void readParams(const Settings *settings) = 0;
- virtual void writeParams(Settings *settings) const = 0;
- virtual ~BiomeParams() = default;
-
- s32 seed;
-};
-
-class BiomeGen {
-public:
- virtual ~BiomeGen() = default;
-
- virtual BiomeGenType getType() const = 0;
-
- // Calculates the biome at the exact position provided. This function can
- // be called at any time, but may be less efficient than the latter methods,
- // depending on implementation.
- virtual Biome *calcBiomeAtPoint(v3s16 pos) const = 0;
-
- // Computes any intermediate results needed for biome generation. Must be
- // called before using any of: getBiomes, getBiomeAtPoint, or getBiomeAtIndex.
- // Calling this invalidates the previous results stored in biomemap.
- virtual void calcBiomeNoise(v3s16 pmin) = 0;
-
- // Gets all biomes in current chunk using each corresponding element of
- // heightmap as the y position, then stores the results by biome index in
- // biomemap (also returned)
- virtual biome_t *getBiomes(s16 *heightmap) = 0;
-
- // Gets a single biome at the specified position, which must be contained
- // in the region formed by m_pmin and (m_pmin + m_csize - 1).
- virtual Biome *getBiomeAtPoint(v3s16 pos) const = 0;
-
- // Same as above, but uses a raw numeric index correlating to the (x,z) position.
- virtual Biome *getBiomeAtIndex(size_t index, s16 y) const = 0;
-
- // Result of calcBiomes bulk computation.
- biome_t *biomemap = nullptr;
-
-protected:
- BiomeManager *m_bmgr = nullptr;
- v3s16 m_pmin;
- v3s16 m_csize;
-};
-
-
-////
-//// BiomeGen implementations
-////
-
-//
-// Original biome algorithm (Whittaker's classification + surface height)
-//
-
-struct BiomeParamsOriginal : public BiomeParams {
- BiomeParamsOriginal() :
- np_heat(50, 50, v3f(1000.0, 1000.0, 1000.0), 5349, 3, 0.5, 2.0),
- np_humidity(50, 50, v3f(1000.0, 1000.0, 1000.0), 842, 3, 0.5, 2.0),
- np_heat_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 13, 2, 1.0, 2.0),
- np_humidity_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 90003, 2, 1.0, 2.0)
- {
- }
-
- virtual void readParams(const Settings *settings);
- virtual void writeParams(Settings *settings) const;
-
- NoiseParams np_heat;
- NoiseParams np_humidity;
- NoiseParams np_heat_blend;
- NoiseParams np_humidity_blend;
-};
-
-class BiomeGenOriginal : public BiomeGen {
-public:
- BiomeGenOriginal(BiomeManager *biomemgr,
- BiomeParamsOriginal *params, v3s16 chunksize);
- virtual ~BiomeGenOriginal();
-
- BiomeGenType getType() const { return BIOMEGEN_ORIGINAL; }
-
- Biome *calcBiomeAtPoint(v3s16 pos) const;
- void calcBiomeNoise(v3s16 pmin);
-
- biome_t *getBiomes(s16 *heightmap);
- Biome *getBiomeAtPoint(v3s16 pos) const;
- Biome *getBiomeAtIndex(size_t index, s16 y) const;
-
- Biome *calcBiomeFromNoise(float heat, float humidity, s16 y) const;
-
- float *heatmap;
- float *humidmap;
-
-private:
- BiomeParamsOriginal *m_params;
-
- Noise *noise_heat;
- Noise *noise_humidity;
- Noise *noise_heat_blend;
- Noise *noise_humidity_blend;
-};
-
-
-////
-//// BiomeManager
-////
-
-class BiomeManager : public ObjDefManager {
-public:
- BiomeManager(Server *server);
- virtual ~BiomeManager() = default;
-
- const char *getObjectTitle() const
- {
- return "biome";
- }
-
- static Biome *create(BiomeType type)
- {
- return new Biome;
- }
-
- BiomeGen *createBiomeGen(BiomeGenType type, BiomeParams *params, v3s16 chunksize)
- {
- switch (type) {
- case BIOMEGEN_ORIGINAL:
- return new BiomeGenOriginal(this,
- (BiomeParamsOriginal *)params, chunksize);
- default:
- return NULL;
- }
- }
-
- static BiomeParams *createBiomeParams(BiomeGenType type)
- {
- switch (type) {
- case BIOMEGEN_ORIGINAL:
- return new BiomeParamsOriginal;
- default:
- return NULL;
- }
- }
-
- virtual void clear();
-
-private:
- Server *m_server;
-
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "mg_decoration.h"
-#include "mg_schematic.h"
-#include "mapgen.h"
-#include "noise.h"
-#include "map.h"
-#include "log.h"
-#include "util/numeric.h"
-#include <algorithm>
-
-
-FlagDesc flagdesc_deco[] = {
- {"place_center_x", DECO_PLACE_CENTER_X},
- {"place_center_y", DECO_PLACE_CENTER_Y},
- {"place_center_z", DECO_PLACE_CENTER_Z},
- {"force_placement", DECO_FORCE_PLACEMENT},
- {"liquid_surface", DECO_LIQUID_SURFACE},
- {"all_floors", DECO_ALL_FLOORS},
- {"all_ceilings", DECO_ALL_CEILINGS},
- {NULL, 0}
-};
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-DecorationManager::DecorationManager(IGameDef *gamedef) :
- ObjDefManager(gamedef, OBJDEF_DECORATION)
-{
-}
-
-
-size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed,
- v3s16 nmin, v3s16 nmax)
-{
- size_t nplaced = 0;
-
- for (size_t i = 0; i != m_objects.size(); i++) {
- Decoration *deco = (Decoration *)m_objects[i];
- if (!deco)
- continue;
-
- nplaced += deco->placeDeco(mg, blockseed, nmin, nmax);
- blockseed++;
- }
-
- return nplaced;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-void Decoration::resolveNodeNames()
-{
- getIdsFromNrBacklog(&c_place_on);
- getIdsFromNrBacklog(&c_spawnby);
-}
-
-
-bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p)
-{
- // Check if the decoration can be placed on this node
- u32 vi = vm->m_area.index(p);
- if (!CONTAINS(c_place_on, vm->m_data[vi].getContent()))
- return false;
-
- // Don't continue if there are no spawnby constraints
- if (nspawnby == -1)
- return true;
-
- int nneighs = 0;
- static const v3s16 dirs[16] = {
- v3s16( 0, 0, 1),
- v3s16( 0, 0, -1),
- v3s16( 1, 0, 0),
- v3s16(-1, 0, 0),
- v3s16( 1, 0, 1),
- v3s16(-1, 0, 1),
- v3s16(-1, 0, -1),
- v3s16( 1, 0, -1),
-
- v3s16( 0, 1, 1),
- v3s16( 0, 1, -1),
- v3s16( 1, 1, 0),
- v3s16(-1, 1, 0),
- v3s16( 1, 1, 1),
- v3s16(-1, 1, 1),
- v3s16(-1, 1, -1),
- v3s16( 1, 1, -1)
- };
-
- // Check these 16 neighbouring nodes for enough spawnby nodes
- for (size_t i = 0; i != ARRLEN(dirs); i++) {
- u32 index = vm->m_area.index(p + dirs[i]);
- if (!vm->m_area.contains(index))
- continue;
-
- if (CONTAINS(c_spawnby, vm->m_data[index].getContent()))
- nneighs++;
- }
-
- if (nneighs < nspawnby)
- return false;
-
- return true;
-}
-
-
-size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
-{
- PcgRandom ps(blockseed + 53);
- int carea_size = nmax.X - nmin.X + 1;
-
- // Divide area into parts
- // If chunksize is changed it may no longer be divisable by sidelen
- if (carea_size % sidelen)
- sidelen = carea_size;
-
- s16 divlen = carea_size / sidelen;
- int area = sidelen * sidelen;
-
- for (s16 z0 = 0; z0 < divlen; z0++)
- for (s16 x0 = 0; x0 < divlen; x0++) {
- v2s16 p2d_center( // Center position of part of division
- nmin.X + sidelen / 2 + sidelen * x0,
- nmin.Z + sidelen / 2 + sidelen * z0
- );
- v2s16 p2d_min( // Minimum edge of part of division
- nmin.X + sidelen * x0,
- nmin.Z + sidelen * z0
- );
- v2s16 p2d_max( // Maximum edge of part of division
- nmin.X + sidelen + sidelen * x0 - 1,
- nmin.Z + sidelen + sidelen * z0 - 1
- );
-
- // Amount of decorations
- float nval = (flags & DECO_USE_NOISE) ?
- NoisePerlin2D(&np, p2d_center.X, p2d_center.Y, mapseed) :
- fill_ratio;
- u32 deco_count = 0;
- float deco_count_f = (float)area * nval;
- if (deco_count_f >= 1.f) {
- deco_count = deco_count_f;
- } else if (deco_count_f > 0.f) {
- // For low density decorations calculate a chance for 1 decoration
- if (ps.range(1000) <= deco_count_f * 1000.f)
- deco_count = 1;
- }
-
- for (u32 i = 0; i < deco_count; i++) {
- s16 x = ps.range(p2d_min.X, p2d_max.X);
- s16 z = ps.range(p2d_min.Y, p2d_max.Y);
- int mapindex = carea_size * (z - nmin.Z) + (x - nmin.X);
-
- if ((flags & DECO_ALL_FLOORS) ||
- (flags & DECO_ALL_CEILINGS)) {
- // All-surfaces decorations
- // Check biome of column
- if (mg->biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator iter =
- biomes.find(mg->biomemap[mapindex]);
- if (iter == biomes.end())
- continue;
- }
-
- // Get all floors and ceilings in node column
- u16 size = (nmax.Y - nmin.Y + 1) / 2;
- s16 floors[size];
- s16 ceilings[size];
- u16 num_floors = 0;
- u16 num_ceilings = 0;
-
- mg->getSurfaces(v2s16(x, z), nmin.Y, nmax.Y,
- floors, ceilings, &num_floors, &num_ceilings);
-
- if ((flags & DECO_ALL_FLOORS) && num_floors > 0) {
- // Floor decorations
- for (u16 fi = 0; fi < num_floors; fi++) {
- s16 y = floors[fi];
- if (y < y_min || y > y_max)
- continue;
-
- v3s16 pos(x, y, z);
- if (generate(mg->vm, &ps, pos, false))
- mg->gennotify.addEvent(
- GENNOTIFY_DECORATION, pos, index);
- }
- }
-
- if ((flags & DECO_ALL_CEILINGS) && num_ceilings > 0) {
- // Ceiling decorations
- for (u16 ci = 0; ci < num_ceilings; ci++) {
- s16 y = ceilings[ci];
- if (y < y_min || y > y_max)
- continue;
-
- v3s16 pos(x, y, z);
- if (generate(mg->vm, &ps, pos, true))
- mg->gennotify.addEvent(
- GENNOTIFY_DECORATION, pos, index);
- }
- }
- } else { // Heightmap decorations
- s16 y = -MAX_MAP_GENERATION_LIMIT;
- if (flags & DECO_LIQUID_SURFACE)
- y = mg->findLiquidSurface(v2s16(x, z), nmin.Y, nmax.Y);
- else if (mg->heightmap)
- y = mg->heightmap[mapindex];
- else
- y = mg->findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y);
-
- if (y < y_min || y > y_max || y < nmin.Y || y > nmax.Y)
- continue;
-
- if (mg->biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator iter =
- biomes.find(mg->biomemap[mapindex]);
- if (iter == biomes.end())
- continue;
- }
-
- v3s16 pos(x, y, z);
- if (generate(mg->vm, &ps, pos, false))
- mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, index);
- }
- }
- }
-
- return 0;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-void DecoSimple::resolveNodeNames()
-{
- Decoration::resolveNodeNames();
- getIdsFromNrBacklog(&c_decos);
-}
-
-
-size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
-{
- // Don't bother if there aren't any decorations to place
- if (c_decos.empty())
- return 0;
-
- if (!canPlaceDecoration(vm, p))
- return 0;
-
- // Check for placement outside the voxelmanip volume
- if (ceiling) {
- // Ceiling decorations
- // 'place offset y' is inverted
- if (p.Y - place_offset_y - std::max(deco_height, deco_height_max) <
- vm->m_area.MinEdge.Y)
- return 0;
-
- if (p.Y - 1 - place_offset_y > vm->m_area.MaxEdge.Y)
- return 0;
-
- } else { // Heightmap and floor decorations
- if (p.Y + place_offset_y + std::max(deco_height, deco_height_max) >
- vm->m_area.MaxEdge.Y)
- return 0;
-
- if (p.Y + 1 + place_offset_y < vm->m_area.MinEdge.Y)
- return 0;
- }
-
- content_t c_place = c_decos[pr->range(0, c_decos.size() - 1)];
- s16 height = (deco_height_max > 0) ?
- pr->range(deco_height, deco_height_max) : deco_height;
- u8 param2 = (deco_param2_max > 0) ?
- pr->range(deco_param2, deco_param2_max) : deco_param2;
- bool force_placement = (flags & DECO_FORCE_PLACEMENT);
-
- const v3s16 &em = vm->m_area.getExtent();
- u32 vi = vm->m_area.index(p);
-
- if (ceiling) {
- // Ceiling decorations
- // 'place offset y' is inverted
- vm->m_area.add_y(em, vi, -place_offset_y);
-
- for (int i = 0; i < height; i++) {
- vm->m_area.add_y(em, vi, -1);
- content_t c = vm->m_data[vi].getContent();
- if (c != CONTENT_AIR && c != CONTENT_IGNORE && !force_placement)
- break;
-
- vm->m_data[vi] = MapNode(c_place, 0, param2);
- }
- } else { // Heightmap and floor decorations
- vm->m_area.add_y(em, vi, place_offset_y);
-
- for (int i = 0; i < height; i++) {
- vm->m_area.add_y(em, vi, 1);
- content_t c = vm->m_data[vi].getContent();
- if (c != CONTENT_AIR && c != CONTENT_IGNORE && !force_placement)
- break;
-
- vm->m_data[vi] = MapNode(c_place, 0, param2);
- }
- }
-
- return 1;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
-{
- // Schematic could have been unloaded but not the decoration
- // In this case generate() does nothing (but doesn't *fail*)
- if (schematic == NULL)
- return 0;
-
- if (!canPlaceDecoration(vm, p))
- return 0;
-
- if (flags & DECO_PLACE_CENTER_Y) {
- p.Y -= (schematic->size.Y - 1) / 2;
- } else {
- // Only apply 'place offset y' if not 'deco place center y'
- if (ceiling)
- // Shift down so that schematic top layer is level with ceiling
- // 'place offset y' is inverted
- p.Y -= (place_offset_y + schematic->size.Y - 1);
- else
- p.Y += place_offset_y;
- }
-
- // Check schematic top and base are in voxelmanip
- if (p.Y + schematic->size.Y - 1 > vm->m_area.MaxEdge.Y)
- return 0;
-
- if (p.Y < vm->m_area.MinEdge.Y)
- return 0;
-
- if (flags & DECO_PLACE_CENTER_X)
- p.X -= (schematic->size.X - 1) / 2;
- if (flags & DECO_PLACE_CENTER_Z)
- p.Z -= (schematic->size.Z - 1) / 2;
-
- Rotation rot = (rotation == ROTATE_RAND) ?
- (Rotation)pr->range(ROTATE_0, ROTATE_270) : rotation;
- bool force_placement = (flags & DECO_FORCE_PLACEMENT);
-
- schematic->blitToVManip(vm, p, rot, force_placement);
-
- return 1;
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <unordered_set>
-#include "objdef.h"
-#include "noise.h"
-#include "nodedef.h"
-
-class Mapgen;
-class MMVManip;
-class PcgRandom;
-class Schematic;
-
-enum DecorationType {
- DECO_SIMPLE,
- DECO_SCHEMATIC,
- DECO_LSYSTEM
-};
-
-#define DECO_PLACE_CENTER_X 0x01
-#define DECO_PLACE_CENTER_Y 0x02
-#define DECO_PLACE_CENTER_Z 0x04
-#define DECO_USE_NOISE 0x08
-#define DECO_FORCE_PLACEMENT 0x10
-#define DECO_LIQUID_SURFACE 0x20
-#define DECO_ALL_FLOORS 0x40
-#define DECO_ALL_CEILINGS 0x80
-
-extern FlagDesc flagdesc_deco[];
-
-
-class Decoration : public ObjDef, public NodeResolver {
-public:
- Decoration() = default;
- virtual ~Decoration() = default;
-
- virtual void resolveNodeNames();
-
- bool canPlaceDecoration(MMVManip *vm, v3s16 p);
- size_t placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
-
- virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) = 0;
-
- u32 flags = 0;
- int mapseed = 0;
- std::vector<content_t> c_place_on;
- s16 sidelen = 1;
- s16 y_min;
- s16 y_max;
- float fill_ratio = 0.0f;
- NoiseParams np;
- std::vector<content_t> c_spawnby;
- s16 nspawnby;
- s16 place_offset_y = 0;
-
- std::unordered_set<u8> biomes;
-};
-
-
-class DecoSimple : public Decoration {
-public:
- virtual void resolveNodeNames();
- virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling);
-
- std::vector<content_t> c_decos;
- s16 deco_height;
- s16 deco_height_max;
- u8 deco_param2;
- u8 deco_param2_max;
-};
-
-
-class DecoSchematic : public Decoration {
-public:
- DecoSchematic() = default;
-
- virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling);
-
- Rotation rotation;
- Schematic *schematic = nullptr;
-};
-
-
-/*
-class DecoLSystem : public Decoration {
-public:
- virtual void generate(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
-};
-*/
-
-
-class DecorationManager : public ObjDefManager {
-public:
- DecorationManager(IGameDef *gamedef);
- virtual ~DecorationManager() = default;
-
- const char *getObjectTitle() const
- {
- return "decoration";
- }
-
- static Decoration *create(DecorationType type)
- {
- switch (type) {
- case DECO_SIMPLE:
- return new DecoSimple;
- case DECO_SCHEMATIC:
- return new DecoSchematic;
- //case DECO_LSYSTEM:
- // return new DecoLSystem;
- default:
- return NULL;
- }
- }
-
- size_t placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "mg_ore.h"
-#include "mapgen.h"
-#include "noise.h"
-#include "map.h"
-#include "log.h"
-#include "util/numeric.h"
-#include <algorithm>
-
-
-FlagDesc flagdesc_ore[] = {
- {"absheight", OREFLAG_ABSHEIGHT}, // Non-functional
- {"puff_cliffs", OREFLAG_PUFF_CLIFFS},
- {"puff_additive_composition", OREFLAG_PUFF_ADDITIVE},
- {NULL, 0}
-};
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-OreManager::OreManager(IGameDef *gamedef) :
- ObjDefManager(gamedef, OBJDEF_ORE)
-{
-}
-
-
-size_t OreManager::placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
-{
- size_t nplaced = 0;
-
- for (size_t i = 0; i != m_objects.size(); i++) {
- Ore *ore = (Ore *)m_objects[i];
- if (!ore)
- continue;
-
- nplaced += ore->placeOre(mg, blockseed, nmin, nmax);
- blockseed++;
- }
-
- return nplaced;
-}
-
-
-void OreManager::clear()
-{
- for (ObjDef *object : m_objects) {
- Ore *ore = (Ore *) object;
- delete ore;
- }
- m_objects.clear();
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-Ore::~Ore()
-{
- delete noise;
-}
-
-
-void Ore::resolveNodeNames()
-{
- getIdFromNrBacklog(&c_ore, "", CONTENT_AIR);
- getIdsFromNrBacklog(&c_wherein);
-}
-
-
-size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
-{
- if (nmin.Y > y_max || nmax.Y < y_min)
- return 0;
-
- int actual_ymin = MYMAX(nmin.Y, y_min);
- int actual_ymax = MYMIN(nmax.Y, y_max);
- if (clust_size >= actual_ymax - actual_ymin + 1)
- return 0;
-
- nmin.Y = actual_ymin;
- nmax.Y = actual_ymax;
- generate(mg->vm, mg->seed, blockseed, nmin, nmax, mg->biomemap);
-
- return 1;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
-{
- PcgRandom pr(blockseed);
- MapNode n_ore(c_ore, 0, ore_param2);
-
- u32 sizex = (nmax.X - nmin.X + 1);
- u32 volume = (nmax.X - nmin.X + 1) *
- (nmax.Y - nmin.Y + 1) *
- (nmax.Z - nmin.Z + 1);
- u32 csize = clust_size;
- u32 cvolume = csize * csize * csize;
- u32 nclusters = volume / clust_scarcity;
-
- for (u32 i = 0; i != nclusters; i++) {
- int x0 = pr.range(nmin.X, nmax.X - csize + 1);
- int y0 = pr.range(nmin.Y, nmax.Y - csize + 1);
- int z0 = pr.range(nmin.Z, nmax.Z - csize + 1);
-
- if ((flags & OREFLAG_USE_NOISE) &&
- (NoisePerlin3D(&np, x0, y0, z0, mapseed) < nthresh))
- continue;
-
- if (biomemap && !biomes.empty()) {
- u32 index = sizex * (z0 - nmin.Z) + (x0 - nmin.X);
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
- if (it == biomes.end())
- continue;
- }
-
- for (u32 z1 = 0; z1 != csize; z1++)
- for (u32 y1 = 0; y1 != csize; y1++)
- for (u32 x1 = 0; x1 != csize; x1++) {
- if (pr.range(1, cvolume) > clust_num_ores)
- continue;
-
- u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1);
- if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
- continue;
-
- vm->m_data[i] = n_ore;
- }
- }
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
-{
- PcgRandom pr(blockseed + 4234);
- MapNode n_ore(c_ore, 0, ore_param2);
-
- u16 max_height = column_height_max;
- int y_start_min = nmin.Y + max_height;
- int y_start_max = nmax.Y - max_height;
-
- int y_start = y_start_min < y_start_max ?
- pr.range(y_start_min, y_start_max) :
- (y_start_min + y_start_max) / 2;
-
- if (!noise) {
- int sx = nmax.X - nmin.X + 1;
- int sz = nmax.Z - nmin.Z + 1;
- noise = new Noise(&np, 0, sx, sz);
- }
- noise->seed = mapseed + y_start;
- noise->perlinMap2D(nmin.X, nmin.Z);
-
- size_t index = 0;
- for (int z = nmin.Z; z <= nmax.Z; z++)
- for (int x = nmin.X; x <= nmax.X; x++, index++) {
- float noiseval = noise->result[index];
- if (noiseval < nthresh)
- continue;
-
- if (biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
- if (it == biomes.end())
- continue;
- }
-
- u16 height = pr.range(column_height_min, column_height_max);
- int ymidpoint = y_start + noiseval;
- int y0 = MYMAX(nmin.Y, ymidpoint - height * (1 - column_midpoint_factor));
- int y1 = MYMIN(nmax.Y, y0 + height - 1);
-
- for (int y = y0; y <= y1; y++) {
- u32 i = vm->m_area.index(x, y, z);
- if (!vm->m_area.contains(i))
- continue;
- if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
- continue;
-
- vm->m_data[i] = n_ore;
- }
- }
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-OrePuff::~OrePuff()
-{
- delete noise_puff_top;
- delete noise_puff_bottom;
-}
-
-
-void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
-{
- PcgRandom pr(blockseed + 4234);
- MapNode n_ore(c_ore, 0, ore_param2);
-
- int y_start = pr.range(nmin.Y, nmax.Y);
-
- if (!noise) {
- int sx = nmax.X - nmin.X + 1;
- int sz = nmax.Z - nmin.Z + 1;
- noise = new Noise(&np, 0, sx, sz);
- noise_puff_top = new Noise(&np_puff_top, 0, sx, sz);
- noise_puff_bottom = new Noise(&np_puff_bottom, 0, sx, sz);
- }
-
- noise->seed = mapseed + y_start;
- noise->perlinMap2D(nmin.X, nmin.Z);
- bool noise_generated = false;
-
- size_t index = 0;
- for (int z = nmin.Z; z <= nmax.Z; z++)
- for (int x = nmin.X; x <= nmax.X; x++, index++) {
- float noiseval = noise->result[index];
- if (noiseval < nthresh)
- continue;
-
- if (biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
- if (it == biomes.end())
- continue;
- }
-
- if (!noise_generated) {
- noise_generated = true;
- noise_puff_top->perlinMap2D(nmin.X, nmin.Z);
- noise_puff_bottom->perlinMap2D(nmin.X, nmin.Z);
- }
-
- float ntop = noise_puff_top->result[index];
- float nbottom = noise_puff_bottom->result[index];
-
- if (!(flags & OREFLAG_PUFF_CLIFFS)) {
- float ndiff = noiseval - nthresh;
- if (ndiff < 1.0f) {
- ntop *= ndiff;
- nbottom *= ndiff;
- }
- }
-
- int ymid = y_start;
- int y0 = ymid - nbottom;
- int y1 = ymid + ntop;
-
- if ((flags & OREFLAG_PUFF_ADDITIVE) && (y0 > y1))
- SWAP(int, y0, y1);
-
- for (int y = y0; y <= y1; y++) {
- u32 i = vm->m_area.index(x, y, z);
- if (!vm->m_area.contains(i))
- continue;
- if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
- continue;
-
- vm->m_data[i] = n_ore;
- }
- }
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
-{
- PcgRandom pr(blockseed + 2404);
- MapNode n_ore(c_ore, 0, ore_param2);
-
- u32 sizex = (nmax.X - nmin.X + 1);
- u32 volume = (nmax.X - nmin.X + 1) *
- (nmax.Y - nmin.Y + 1) *
- (nmax.Z - nmin.Z + 1);
- u32 csize = clust_size;
- u32 nblobs = volume / clust_scarcity;
-
- if (!noise)
- noise = new Noise(&np, mapseed, csize, csize, csize);
-
- for (u32 i = 0; i != nblobs; i++) {
- int x0 = pr.range(nmin.X, nmax.X - csize + 1);
- int y0 = pr.range(nmin.Y, nmax.Y - csize + 1);
- int z0 = pr.range(nmin.Z, nmax.Z - csize + 1);
-
- if (biomemap && !biomes.empty()) {
- u32 bmapidx = sizex * (z0 - nmin.Z) + (x0 - nmin.X);
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]);
- if (it == biomes.end())
- continue;
- }
-
- bool noise_generated = false;
- noise->seed = blockseed + i;
-
- size_t index = 0;
- for (u32 z1 = 0; z1 != csize; z1++)
- for (u32 y1 = 0; y1 != csize; y1++)
- for (u32 x1 = 0; x1 != csize; x1++, index++) {
- u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1);
- if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
- continue;
-
- // Lazily generate noise only if there's a chance of ore being placed
- // This simple optimization makes calls 6x faster on average
- if (!noise_generated) {
- noise_generated = true;
- noise->perlinMap3D(x0, y0, z0);
- }
-
- float noiseval = noise->result[index];
-
- float xdist = (s32)x1 - (s32)csize / 2;
- float ydist = (s32)y1 - (s32)csize / 2;
- float zdist = (s32)z1 - (s32)csize / 2;
-
- noiseval -= (sqrt(xdist * xdist + ydist * ydist + zdist * zdist) / csize);
-
- if (noiseval < nthresh)
- continue;
-
- vm->m_data[i] = n_ore;
- }
- }
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-OreVein::~OreVein()
-{
- delete noise2;
-}
-
-
-void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
-{
- PcgRandom pr(blockseed + 520);
- MapNode n_ore(c_ore, 0, ore_param2);
-
- u32 sizex = (nmax.X - nmin.X + 1);
-
- if (!noise) {
- int sx = nmax.X - nmin.X + 1;
- int sy = nmax.Y - nmin.Y + 1;
- int sz = nmax.Z - nmin.Z + 1;
- noise = new Noise(&np, mapseed, sx, sy, sz);
- noise2 = new Noise(&np, mapseed + 436, sx, sy, sz);
- }
- bool noise_generated = false;
-
- size_t index = 0;
- for (int z = nmin.Z; z <= nmax.Z; z++)
- for (int y = nmin.Y; y <= nmax.Y; y++)
- for (int x = nmin.X; x <= nmax.X; x++, index++) {
- u32 i = vm->m_area.index(x, y, z);
- if (!vm->m_area.contains(i))
- continue;
- if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
- continue;
-
- if (biomemap && !biomes.empty()) {
- u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X);
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]);
- if (it == biomes.end())
- continue;
- }
-
- // Same lazy generation optimization as in OreBlob
- if (!noise_generated) {
- noise_generated = true;
- noise->perlinMap3D(nmin.X, nmin.Y, nmin.Z);
- noise2->perlinMap3D(nmin.X, nmin.Y, nmin.Z);
- }
-
- // randval ranges from -1..1
- float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - 1.f;
- float noiseval = contour(noise->result[index]);
- float noiseval2 = contour(noise2->result[index]);
- if (noiseval * noiseval2 + randval * random_factor < nthresh)
- continue;
-
- vm->m_data[i] = n_ore;
- }
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-OreStratum::~OreStratum()
-{
- delete noise_stratum_thickness;
-}
-
-
-void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
-{
- PcgRandom pr(blockseed + 4234);
- MapNode n_ore(c_ore, 0, ore_param2);
-
- if (flags & OREFLAG_USE_NOISE) {
- if (!(noise && noise_stratum_thickness)) {
- int sx = nmax.X - nmin.X + 1;
- int sz = nmax.Z - nmin.Z + 1;
- noise = new Noise(&np, 0, sx, sz);
- noise_stratum_thickness = new Noise(&np_stratum_thickness, 0, sx, sz);
- }
- noise->perlinMap2D(nmin.X, nmin.Z);
- noise_stratum_thickness->perlinMap2D(nmin.X, nmin.Z);
- }
-
- size_t index = 0;
-
- for (int z = nmin.Z; z <= nmax.Z; z++)
- for (int x = nmin.X; x <= nmax.X; x++, index++) {
- if (biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
- if (it == biomes.end())
- continue;
- }
-
- int y0;
- int y1;
-
- if (flags & OREFLAG_USE_NOISE) {
- float nmid = noise->result[index];
- float nhalfthick = noise_stratum_thickness->result[index] / 2.0f;
- y0 = MYMAX(nmin.Y, nmid - nhalfthick);
- y1 = MYMIN(nmax.Y, nmid + nhalfthick);
- } else {
- y0 = nmin.Y;
- y1 = nmax.Y;
- }
-
- for (int y = y0; y <= y1; y++) {
- if (pr.range(1, clust_scarcity) != 1)
- continue;
-
- u32 i = vm->m_area.index(x, y, z);
- if (!vm->m_area.contains(i))
- continue;
- if (!CONTAINS(c_wherein, vm->m_data[i].getContent()))
- continue;
-
- vm->m_data[i] = n_ore;
- }
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <unordered_set>
-#include "objdef.h"
-#include "noise.h"
-#include "nodedef.h"
-
-class Noise;
-class Mapgen;
-class MMVManip;
-
-/////////////////// Ore generation flags
-
-#define OREFLAG_ABSHEIGHT 0x01 // Non-functional but kept to not break flags
-#define OREFLAG_PUFF_CLIFFS 0x02
-#define OREFLAG_PUFF_ADDITIVE 0x04
-#define OREFLAG_USE_NOISE 0x08
-
-enum OreType {
- ORE_SCATTER,
- ORE_SHEET,
- ORE_PUFF,
- ORE_BLOB,
- ORE_VEIN,
- ORE_STRATUM,
-};
-
-extern FlagDesc flagdesc_ore[];
-
-class Ore : public ObjDef, public NodeResolver {
-public:
- static const bool NEEDS_NOISE = false;
-
- content_t c_ore; // the node to place
- std::vector<content_t> c_wherein; // the nodes to be placed in
- u32 clust_scarcity; // ore cluster has a 1-in-clust_scarcity chance of appearing at a node
- s16 clust_num_ores; // how many ore nodes are in a chunk
- s16 clust_size; // how large (in nodes) a chunk of ore is
- s16 y_min;
- s16 y_max;
- u8 ore_param2; // to set node-specific attributes
- u32 flags = 0; // attributes for this ore
- float nthresh; // threshold for noise at which an ore is placed
- NoiseParams np; // noise for distribution of clusters (NULL for uniform scattering)
- Noise *noise = nullptr;
- std::unordered_set<u8> biomes;
-
- Ore() = default;;
- virtual ~Ore();
-
- virtual void resolveNodeNames();
-
- size_t placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
- virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap) = 0;
-};
-
-class OreScatter : public Ore {
-public:
- static const bool NEEDS_NOISE = false;
-
- virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
-};
-
-class OreSheet : public Ore {
-public:
- static const bool NEEDS_NOISE = true;
-
- u16 column_height_min;
- u16 column_height_max;
- float column_midpoint_factor;
-
- virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
-};
-
-class OrePuff : public Ore {
-public:
- static const bool NEEDS_NOISE = true;
-
- NoiseParams np_puff_top;
- NoiseParams np_puff_bottom;
- Noise *noise_puff_top = nullptr;
- Noise *noise_puff_bottom = nullptr;
-
- OrePuff() = default;
- virtual ~OrePuff();
-
- virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
-};
-
-class OreBlob : public Ore {
-public:
- static const bool NEEDS_NOISE = true;
-
- virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
-};
-
-class OreVein : public Ore {
-public:
- static const bool NEEDS_NOISE = true;
-
- float random_factor;
- Noise *noise2 = nullptr;
-
- OreVein() = default;
- virtual ~OreVein();
-
- virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
-};
-
-class OreStratum : public Ore {
-public:
- static const bool NEEDS_NOISE = false;
-
- NoiseParams np_stratum_thickness;
- Noise *noise_stratum_thickness = nullptr;
-
- OreStratum() = default;
- virtual ~OreStratum();
-
- virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
-};
-
-class OreManager : public ObjDefManager {
-public:
- OreManager(IGameDef *gamedef);
- virtual ~OreManager() = default;
-
- const char *getObjectTitle() const
- {
- return "ore";
- }
-
- static Ore *create(OreType type)
- {
- switch (type) {
- case ORE_SCATTER:
- return new OreScatter;
- case ORE_SHEET:
- return new OreSheet;
- case ORE_PUFF:
- return new OrePuff;
- case ORE_BLOB:
- return new OreBlob;
- case ORE_VEIN:
- return new OreVein;
- case ORE_STRATUM:
- return new OreStratum;
- default:
- return nullptr;
- }
- }
-
- void clear();
-
- size_t placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
-};
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include <fstream>
-#include <typeinfo>
-#include "mg_schematic.h"
-#include "server.h"
-#include "mapgen.h"
-#include "emerge.h"
-#include "map.h"
-#include "mapblock.h"
-#include "log.h"
-#include "util/numeric.h"
-#include "util/serialize.h"
-#include "serialization.h"
-#include "filesys.h"
-#include "voxelalgorithms.h"
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-SchematicManager::SchematicManager(Server *server) :
- ObjDefManager(server, OBJDEF_SCHEMATIC),
- m_server(server)
-{
-}
-
-
-void SchematicManager::clear()
-{
- EmergeManager *emerge = m_server->getEmergeManager();
-
- // Remove all dangling references in Decorations
- DecorationManager *decomgr = emerge->decomgr;
- for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
- Decoration *deco = (Decoration *)decomgr->getRaw(i);
-
- try {
- DecoSchematic *dschem = dynamic_cast<DecoSchematic *>(deco);
- if (dschem)
- dschem->schematic = NULL;
- } catch (const std::bad_cast &) {
- }
- }
-
- ObjDefManager::clear();
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-Schematic::Schematic()
-= default;
-
-
-Schematic::~Schematic()
-{
- delete []schemdata;
- delete []slice_probs;
-}
-
-
-void Schematic::resolveNodeNames()
-{
- getIdsFromNrBacklog(&c_nodes, true, CONTENT_AIR);
-
- size_t bufsize = size.X * size.Y * size.Z;
- for (size_t i = 0; i != bufsize; i++) {
- content_t c_original = schemdata[i].getContent();
- content_t c_new = c_nodes[c_original];
- schemdata[i].setContent(c_new);
- }
-}
-
-
-void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place)
-{
- sanity_check(m_ndef != NULL);
-
- int xstride = 1;
- int ystride = size.X;
- int zstride = size.X * size.Y;
-
- s16 sx = size.X;
- s16 sy = size.Y;
- s16 sz = size.Z;
-
- int i_start, i_step_x, i_step_z;
- switch (rot) {
- case ROTATE_90:
- i_start = sx - 1;
- i_step_x = zstride;
- i_step_z = -xstride;
- SWAP(s16, sx, sz);
- break;
- case ROTATE_180:
- i_start = zstride * (sz - 1) + sx - 1;
- i_step_x = -xstride;
- i_step_z = -zstride;
- break;
- case ROTATE_270:
- i_start = zstride * (sz - 1);
- i_step_x = -zstride;
- i_step_z = xstride;
- SWAP(s16, sx, sz);
- break;
- default:
- i_start = 0;
- i_step_x = xstride;
- i_step_z = zstride;
- }
-
- s16 y_map = p.Y;
- for (s16 y = 0; y != sy; y++) {
- if ((slice_probs[y] != MTSCHEM_PROB_ALWAYS) &&
- (slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS)))
- continue;
-
- for (s16 z = 0; z != sz; z++) {
- u32 i = z * i_step_z + y * ystride + i_start;
- for (s16 x = 0; x != sx; x++, i += i_step_x) {
- u32 vi = vm->m_area.index(p.X + x, y_map, p.Z + z);
- if (!vm->m_area.contains(vi))
- continue;
-
- if (schemdata[i].getContent() == CONTENT_IGNORE)
- continue;
-
- u8 placement_prob = schemdata[i].param1 & MTSCHEM_PROB_MASK;
- bool force_place_node = schemdata[i].param1 & MTSCHEM_FORCE_PLACE;
-
- if (placement_prob == MTSCHEM_PROB_NEVER)
- continue;
-
- if (!force_place && !force_place_node) {
- content_t c = vm->m_data[vi].getContent();
- if (c != CONTENT_AIR && c != CONTENT_IGNORE)
- continue;
- }
-
- if ((placement_prob != MTSCHEM_PROB_ALWAYS) &&
- (placement_prob <= myrand_range(1, MTSCHEM_PROB_ALWAYS)))
- continue;
-
- vm->m_data[vi] = schemdata[i];
- vm->m_data[vi].param1 = 0;
-
- if (rot)
- vm->m_data[vi].rotateAlongYAxis(m_ndef, rot);
- }
- }
- y_map++;
- }
-}
-
-
-bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags,
- Rotation rot, bool force_place)
-{
- assert(vm != NULL);
- assert(schemdata != NULL);
- sanity_check(m_ndef != NULL);
-
- //// Determine effective rotation and effective schematic dimensions
- if (rot == ROTATE_RAND)
- rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270);
-
- v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ?
- v3s16(size.Z, size.Y, size.X) : size;
-
- //// Adjust placement position if necessary
- if (flags & DECO_PLACE_CENTER_X)
- p.X -= (s.X + 1) / 2;
- if (flags & DECO_PLACE_CENTER_Y)
- p.Y -= (s.Y + 1) / 2;
- if (flags & DECO_PLACE_CENTER_Z)
- p.Z -= (s.Z + 1) / 2;
-
- blitToVManip(vm, p, rot, force_place);
-
- return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1,1,1)));
-}
-
-void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags,
- Rotation rot, bool force_place)
-{
- std::map<v3s16, MapBlock *> lighting_modified_blocks;
- std::map<v3s16, MapBlock *> modified_blocks;
- std::map<v3s16, MapBlock *>::iterator it;
-
- assert(map != NULL);
- assert(schemdata != NULL);
- sanity_check(m_ndef != NULL);
-
- //// Determine effective rotation and effective schematic dimensions
- if (rot == ROTATE_RAND)
- rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270);
-
- v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ?
- v3s16(size.Z, size.Y, size.X) : size;
-
- //// Adjust placement position if necessary
- if (flags & DECO_PLACE_CENTER_X)
- p.X -= (s.X + 1) / 2;
- if (flags & DECO_PLACE_CENTER_Y)
- p.Y -= (s.Y + 1) / 2;
- if (flags & DECO_PLACE_CENTER_Z)
- p.Z -= (s.Z + 1) / 2;
-
- //// Create VManip for effected area, emerge our area, modify area
- //// inside VManip, then blit back.
- v3s16 bp1 = getNodeBlockPos(p);
- v3s16 bp2 = getNodeBlockPos(p + s - v3s16(1,1,1));
-
- MMVManip vm(map);
- vm.initialEmerge(bp1, bp2);
-
- blitToVManip(&vm, p, rot, force_place);
-
- voxalgo::blit_back_with_light(map, &vm, &modified_blocks);
-
- //// Carry out post-map-modification actions
-
- //// Create & dispatch map modification events to observers
- MapEditEvent event;
- event.type = MEET_OTHER;
- for (it = modified_blocks.begin(); it != modified_blocks.end(); ++it)
- event.modified_blocks.insert(it->first);
-
- map->dispatchEvent(&event);
-}
-
-
-bool Schematic::deserializeFromMts(std::istream *is,
- std::vector<std::string> *names)
-{
- std::istream &ss = *is;
- content_t cignore = CONTENT_IGNORE;
- bool have_cignore = false;
-
- //// Read signature
- u32 signature = readU32(ss);
- if (signature != MTSCHEM_FILE_SIGNATURE) {
- errorstream << __FUNCTION__ << ": invalid schematic "
- "file" << std::endl;
- return false;
- }
-
- //// Read version
- u16 version = readU16(ss);
- if (version > MTSCHEM_FILE_VER_HIGHEST_READ) {
- errorstream << __FUNCTION__ << ": unsupported schematic "
- "file version" << std::endl;
- return false;
- }
-
- //// Read size
- size = readV3S16(ss);
-
- //// Read Y-slice probability values
- delete []slice_probs;
- slice_probs = new u8[size.Y];
- for (int y = 0; y != size.Y; y++)
- slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS_OLD;
-
- //// Read node names
- u16 nidmapcount = readU16(ss);
- for (int i = 0; i != nidmapcount; i++) {
- std::string name = deSerializeString(ss);
-
- // Instances of "ignore" from v1 are converted to air (and instances
- // are fixed to have MTSCHEM_PROB_NEVER later on).
- if (name == "ignore") {
- name = "air";
- cignore = i;
- have_cignore = true;
- }
-
- names->push_back(name);
- }
-
- //// Read node data
- size_t nodecount = size.X * size.Y * size.Z;
-
- delete []schemdata;
- schemdata = new MapNode[nodecount];
-
- MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata,
- nodecount, 2, 2, true);
-
- // Fix probability values for nodes that were ignore; removed in v2
- if (version < 2) {
- for (size_t i = 0; i != nodecount; i++) {
- if (schemdata[i].param1 == 0)
- schemdata[i].param1 = MTSCHEM_PROB_ALWAYS_OLD;
- if (have_cignore && schemdata[i].getContent() == cignore)
- schemdata[i].param1 = MTSCHEM_PROB_NEVER;
- }
- }
-
- // Fix probability values for probability range truncation introduced in v4
- if (version < 4) {
- for (s16 y = 0; y != size.Y; y++)
- slice_probs[y] >>= 1;
- for (size_t i = 0; i != nodecount; i++)
- schemdata[i].param1 >>= 1;
- }
-
- return true;
-}
-
-
-bool Schematic::serializeToMts(std::ostream *os,
- const std::vector<std::string> &names)
-{
- std::ostream &ss = *os;
-
- writeU32(ss, MTSCHEM_FILE_SIGNATURE); // signature
- writeU16(ss, MTSCHEM_FILE_VER_HIGHEST_WRITE); // version
- writeV3S16(ss, size); // schematic size
-
- for (int y = 0; y != size.Y; y++) // Y slice probabilities
- writeU8(ss, slice_probs[y]);
-
- writeU16(ss, names.size()); // name count
- for (size_t i = 0; i != names.size(); i++)
- ss << serializeString(names[i]); // node names
-
- // compressed bulk node data
- MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE,
- schemdata, size.X * size.Y * size.Z, 2, 2, true);
-
- return true;
-}
-
-
-bool Schematic::serializeToLua(std::ostream *os,
- const std::vector<std::string> &names, bool use_comments, u32 indent_spaces)
-{
- std::ostream &ss = *os;
-
- std::string indent("\t");
- if (indent_spaces > 0)
- indent.assign(indent_spaces, ' ');
-
- //// Write header
- {
- ss << "schematic = {" << std::endl;
- ss << indent << "size = "
- << "{x=" << size.X
- << ", y=" << size.Y
- << ", z=" << size.Z
- << "}," << std::endl;
- }
-
- //// Write y-slice probabilities
- {
- ss << indent << "yslice_prob = {" << std::endl;
-
- for (u16 y = 0; y != size.Y; y++) {
- u8 probability = slice_probs[y] & MTSCHEM_PROB_MASK;
-
- ss << indent << indent << "{"
- << "ypos=" << y
- << ", prob=" << (u16)probability * 2
- << "}," << std::endl;
- }
-
- ss << indent << "}," << std::endl;
- }
-
- //// Write node data
- {
- ss << indent << "data = {" << std::endl;
-
- u32 i = 0;
- for (u16 z = 0; z != size.Z; z++)
- for (u16 y = 0; y != size.Y; y++) {
- if (use_comments) {
- ss << std::endl
- << indent << indent
- << "-- z=" << z
- << ", y=" << y << std::endl;
- }
-
- for (u16 x = 0; x != size.X; x++, i++) {
- u8 probability = schemdata[i].param1 & MTSCHEM_PROB_MASK;
- bool force_place = schemdata[i].param1 & MTSCHEM_FORCE_PLACE;
-
- ss << indent << indent << "{"
- << "name=\"" << names[schemdata[i].getContent()]
- << "\", prob=" << (u16)probability * 2
- << ", param2=" << (u16)schemdata[i].param2;
-
- if (force_place)
- ss << ", force_place=true";
-
- ss << "}," << std::endl;
- }
- }
-
- ss << indent << "}," << std::endl;
- }
-
- ss << "}" << std::endl;
-
- return true;
-}
-
-
-bool Schematic::loadSchematicFromFile(const std::string &filename,
- INodeDefManager *ndef, StringMap *replace_names)
-{
- std::ifstream is(filename.c_str(), std::ios_base::binary);
- if (!is.good()) {
- errorstream << __FUNCTION__ << ": unable to open file '"
- << filename << "'" << std::endl;
- return false;
- }
-
- size_t origsize = m_nodenames.size();
- if (!deserializeFromMts(&is, &m_nodenames))
- return false;
-
- m_nnlistsizes.push_back(m_nodenames.size() - origsize);
-
- name = filename;
-
- if (replace_names) {
- for (size_t i = origsize; i < m_nodenames.size(); i++) {
- std::string &node_name = m_nodenames[i];
- StringMap::iterator it = replace_names->find(node_name);
- if (it != replace_names->end())
- node_name = it->second;
- }
- }
-
- if (ndef)
- ndef->pendNodeResolve(this);
-
- return true;
-}
-
-
-bool Schematic::saveSchematicToFile(const std::string &filename,
- INodeDefManager *ndef)
-{
- MapNode *orig_schemdata = schemdata;
- std::vector<std::string> ndef_nodenames;
- std::vector<std::string> *names;
-
- if (m_resolve_done && ndef == NULL)
- ndef = m_ndef;
-
- if (ndef) {
- names = &ndef_nodenames;
-
- u32 volume = size.X * size.Y * size.Z;
- schemdata = new MapNode[volume];
- for (u32 i = 0; i != volume; i++)
- schemdata[i] = orig_schemdata[i];
-
- generate_nodelist_and_update_ids(schemdata, volume, names, ndef);
- } else { // otherwise, use the names we have on hand in the list
- names = &m_nodenames;
- }
-
- std::ostringstream os(std::ios_base::binary);
- bool status = serializeToMts(&os, *names);
-
- if (ndef) {
- delete []schemdata;
- schemdata = orig_schemdata;
- }
-
- if (!status)
- return false;
-
- return fs::safeWriteToFile(filename, os.str());
-}
-
-
-bool Schematic::getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2)
-{
- MMVManip *vm = new MMVManip(map);
-
- v3s16 bp1 = getNodeBlockPos(p1);
- v3s16 bp2 = getNodeBlockPos(p2);
- vm->initialEmerge(bp1, bp2);
-
- size = p2 - p1 + 1;
-
- slice_probs = new u8[size.Y];
- for (s16 y = 0; y != size.Y; y++)
- slice_probs[y] = MTSCHEM_PROB_ALWAYS;
-
- schemdata = new MapNode[size.X * size.Y * size.Z];
-
- u32 i = 0;
- for (s16 z = p1.Z; z <= p2.Z; z++)
- for (s16 y = p1.Y; y <= p2.Y; y++) {
- u32 vi = vm->m_area.index(p1.X, y, z);
- for (s16 x = p1.X; x <= p2.X; x++, i++, vi++) {
- schemdata[i] = vm->m_data[vi];
- schemdata[i].param1 = MTSCHEM_PROB_ALWAYS;
- }
- }
-
- delete vm;
- return true;
-}
-
-
-void Schematic::applyProbabilities(v3s16 p0,
- std::vector<std::pair<v3s16, u8> > *plist,
- std::vector<std::pair<s16, u8> > *splist)
-{
- for (size_t i = 0; i != plist->size(); i++) {
- v3s16 p = (*plist)[i].first - p0;
- int index = p.Z * (size.Y * size.X) + p.Y * size.X + p.X;
- if (index < size.Z * size.Y * size.X) {
- u8 prob = (*plist)[i].second;
- schemdata[index].param1 = prob;
-
- // trim unnecessary node names from schematic
- if (prob == MTSCHEM_PROB_NEVER)
- schemdata[index].setContent(CONTENT_AIR);
- }
- }
-
- for (size_t i = 0; i != splist->size(); i++) {
- s16 y = (*splist)[i].first - p0.Y;
- slice_probs[y] = (*splist)[i].second;
- }
-}
-
-
-void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount,
- std::vector<std::string> *usednodes, INodeDefManager *ndef)
-{
- std::unordered_map<content_t, content_t> nodeidmap;
- content_t numids = 0;
-
- for (size_t i = 0; i != nodecount; i++) {
- content_t id;
- content_t c = nodes[i].getContent();
-
- std::unordered_map<content_t, content_t>::const_iterator it = nodeidmap.find(c);
- if (it == nodeidmap.end()) {
- id = numids;
- numids++;
-
- usednodes->push_back(ndef->get(c).name);
- nodeidmap.insert(std::make_pair(c, id));
- } else {
- id = it->second;
- }
- nodes[i].setContent(id);
- }
-}
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2017 paramat
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <map>
-#include "mg_decoration.h"
-#include "util/string.h"
-
-class Map;
-class ServerMap;
-class Mapgen;
-class MMVManip;
-class PseudoRandom;
-class NodeResolver;
-class Server;
-
-/*
- Minetest Schematic File Format
-
- All values are stored in big-endian byte order.
- [u32] signature: 'MTSM'
- [u16] version: 4
- [u16] size X
- [u16] size Y
- [u16] size Z
- For each Y:
- [u8] slice probability value
- [Name-ID table] Name ID Mapping Table
- [u16] name-id count
- For each name-id mapping:
- [u16] name length
- [u8[]] name
- ZLib deflated {
- For each node in schematic: (for z, y, x)
- [u16] content
- For each node in schematic:
- [u8] param1
- bit 0-6: probability
- bit 7: specific node force placement
- For each node in schematic:
- [u8] param2
- }
-
- Version changes:
- 1 - Initial version
- 2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always
- 3 - Added y-slice probabilities; this allows for variable height structures
- 4 - Compressed range of node occurence prob., added per-node force placement bit
-*/
-
-//// Schematic constants
-#define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM'
-#define MTSCHEM_FILE_VER_HIGHEST_READ 4
-#define MTSCHEM_FILE_VER_HIGHEST_WRITE 4
-
-#define MTSCHEM_PROB_MASK 0x7F
-
-#define MTSCHEM_PROB_NEVER 0x00
-#define MTSCHEM_PROB_ALWAYS 0x7F
-#define MTSCHEM_PROB_ALWAYS_OLD 0xFF
-
-#define MTSCHEM_FORCE_PLACE 0x80
-
-enum SchematicType
-{
- SCHEMATIC_NORMAL,
-};
-
-enum SchematicFormatType {
- SCHEM_FMT_HANDLE,
- SCHEM_FMT_MTS,
- SCHEM_FMT_LUA,
-};
-
-class Schematic : public ObjDef, public NodeResolver {
-public:
- Schematic();
- virtual ~Schematic();
-
- virtual void resolveNodeNames();
-
- bool loadSchematicFromFile(const std::string &filename, INodeDefManager *ndef,
- StringMap *replace_names=NULL);
- bool saveSchematicToFile(const std::string &filename, INodeDefManager *ndef);
- bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2);
-
- bool deserializeFromMts(std::istream *is, std::vector<std::string> *names);
- bool serializeToMts(std::ostream *os, const std::vector<std::string> &names);
- bool serializeToLua(std::ostream *os, const std::vector<std::string> &names,
- bool use_comments, u32 indent_spaces);
-
- void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place);
- bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place);
- void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place);
-
- void applyProbabilities(v3s16 p0,
- std::vector<std::pair<v3s16, u8> > *plist,
- std::vector<std::pair<s16, u8> > *splist);
-
- std::vector<content_t> c_nodes;
- u32 flags = 0;
- v3s16 size;
- MapNode *schemdata = nullptr;
- u8 *slice_probs = nullptr;
-};
-
-class SchematicManager : public ObjDefManager {
-public:
- SchematicManager(Server *server);
- virtual ~SchematicManager() = default;
-
- virtual void clear();
-
- const char *getObjectTitle() const
- {
- return "schematic";
- }
-
- static Schematic *create(SchematicType type)
- {
- return new Schematic;
- }
-
-private:
- Server *m_server;
-};
-
-void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount,
- std::vector<std::string> *usednodes, INodeDefManager *ndef);
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2013 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 Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#ifdef HAVE_TOUCHSCREENGUI
-#include "touchscreengui.h"
-#endif
-
-class GUIModalMenu;
-
-class IMenuManager
-{
-public:
- // A GUIModalMenu calls these when this class is passed as a parameter
- virtual void createdMenu(gui::IGUIElement *menu) = 0;
- virtual void deletingMenu(gui::IGUIElement *menu) = 0;
-};
-
-/*
- Remember to drop() the menu after creating, so that it can
- remove itself when it wants to.
-*/
-
-class GUIModalMenu : public gui::IGUIElement
-{
-public:
- GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id,
- IMenuManager *menumgr):
- IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
- core::rect<s32>(0,0,100,100))
- {
- m_menumgr = menumgr;
-
- setVisible(true);
- Environment->setFocus(this);
- m_menumgr->createdMenu(this);
- }
-
- virtual ~GUIModalMenu()
- {
- m_menumgr->deletingMenu(this);
- }
-
- void allowFocusRemoval(bool allow)
- {
- m_allow_focus_removal = allow;
- }
-
- bool canTakeFocus(gui::IGUIElement *e)
- {
- return (e && (e == this || isMyChild(e))) || m_allow_focus_removal;
- }
-
- void draw()
- {
- if(!IsVisible)
- return;
-
- video::IVideoDriver* driver = Environment->getVideoDriver();
- v2u32 screensize = driver->getScreenSize();
- if(screensize != m_screensize_old /*|| m_force_regenerate_gui*/)
- {
- m_screensize_old = screensize;
- regenerateGui(screensize);
- //m_force_regenerate_gui = false;
- }
-
- drawMenu();
- }
-
- /*
- This should be called when the menu wants to quit.
-
- WARNING: THIS DEALLOCATES THE MENU FROM MEMORY. Return
- immediately if you call this from the menu itself.
-
- (More precisely, this decrements the reference count.)
- */
- void quitMenu()
- {
- allowFocusRemoval(true);
- // This removes Environment's grab on us
- Environment->removeFocus(this);
- m_menumgr->deletingMenu(this);
- this->remove();
-#ifdef HAVE_TOUCHSCREENGUI
- if (g_touchscreengui)
- g_touchscreengui->show();
-#endif
- }
-
- void removeChildren()
- {
- const core::list<gui::IGUIElement*> &children = getChildren();
- core::list<gui::IGUIElement*> children_copy;
- for (gui::IGUIElement *i : children) {
- children_copy.push_back(i);
- }
-
- for (gui::IGUIElement *i : children_copy) {
- i->remove();
- }
- }
-
- virtual void regenerateGui(v2u32 screensize) = 0;
- virtual void drawMenu() = 0;
- virtual bool preprocessEvent(const SEvent& event) { return false; };
- virtual bool OnEvent(const SEvent& event) { return false; };
- virtual bool pausesGame(){ return false; } // Used for pause menu
-
-protected:
- //bool m_force_regenerate_gui;
- v2u32 m_screensize_old;
-private:
- IMenuManager *m_menumgr;
- // This might be necessary to expose to the implementation if it
- // wants to launch other menus
- bool m_allow_focus_removal = false;
-};
#pragma once
#include <cassert>
-#include "../threading/thread.h"
+#include "threading/thread.h"
#include "connection.h"
namespace con
#include "tool.h"
#include "serverobject.h"
#include "porting.h"
-#include "mg_schematic.h"
+#include "mapgen/mg_schematic.h"
#include "noise.h"
#include "util/pointedthing.h"
#include "debug.h" // For FATAL_ERROR
#include "common/c_converter.h"
#include "log.h"
#include "environment.h"
-#include "mapgen.h"
+#include "mapgen/mapgen.h"
#include "lua_api/l_env.h"
#include "server.h"
#include "cpp_api/s_base.h"
#include "util/string.h"
-#include "../guiMainMenu.h"
+#include "gui/guiMainMenu.h"
class ScriptApiMainMenu : virtual public ScriptApiBase {
public:
#include "l_internal.h"
#include "lua_api/l_item.h"
#include "lua_api/l_nodemeta.h"
-#include "mainmenumanager.h"
+#include "gui/mainmenumanager.h"
#include "map.h"
#include "util/string.h"
#include "nodedef.h"
#include "daynightratio.h"
#include "util/pointedthing.h"
#include "content_sao.h"
-#include "treegen.h"
+#include "mapgen/treegen.h"
#include "emerge.h"
#include "pathfinder.h"
#include "face_position_cache.h"
#include "lua_api/l_internal.h"
#include "common/c_content.h"
#include "cpp_api/s_async.h"
-#include "guiEngine.h"
-#include "guiMainMenu.h"
-#include "guiKeyChangeMenu.h"
-#include "guiPathSelectMenu.h"
+#include "gui/guiEngine.h"
+#include "gui/guiMainMenu.h"
+#include "gui/guiKeyChangeMenu.h"
+#include "gui/guiPathSelectMenu.h"
#include "subgame.h"
#include "version.h"
#include "porting.h"
#include "filesys.h"
#include "convert_json.h"
#include "serverlist.h"
-#include "mapgen.h"
+#include "mapgen/mapgen.h"
#include "settings.h"
#include <IFileArchive.h>
#include "server.h"
#include "environment.h"
#include "emerge.h"
-#include "mg_biome.h"
-#include "mg_ore.h"
-#include "mg_decoration.h"
-#include "mg_schematic.h"
-#include "mapgen_v5.h"
-#include "mapgen_v7.h"
+#include "mapgen/mg_biome.h"
+#include "mapgen/mg_ore.h"
+#include "mapgen/mg_decoration.h"
+#include "mapgen/mg_schematic.h"
+#include "mapgen/mapgen_v5.h"
+#include "mapgen/mapgen_v7.h"
#include "filesys.h"
#include "settings.h"
#include "log.h"
#include "l_sound.h"
#include "l_internal.h"
#include "common/c_content.h"
-#include "guiEngine.h"
+#include "gui/guiEngine.h"
int ModApiSound::l_sound_play(lua_State *L)
#include "map.h"
#include "mapblock.h"
#include "server.h"
-#include "mapgen.h"
+#include "mapgen/mapgen.h"
#include "voxelalgorithms.h"
// garbage collector
#include "itemdef.h"
#include "craftdef.h"
#include "emerge.h"
-#include "mapgen.h"
-#include "mg_biome.h"
+#include "mapgen/mapgen.h"
+#include "mapgen/mg_biome.h"
#include "content_mapnode.h"
#include "content_nodemeta.h"
#include "content_sao.h"
#include "util/base64.h"
#include "util/sha1.h"
#include "util/hex.h"
-#include "database.h"
+#include "database/database.h"
#include "chatmessage.h"
#include "chat_interface.h"
#include "remoteplayer.h"
#include "threading/mutex_auto_lock.h"
#include "filesys.h"
#include "gameparams.h"
-#include "database-dummy.h"
-#include "database-files.h"
-#include "database-sqlite3.h"
+#include "database/database-dummy.h"
+#include "database/database-files.h"
+#include "database/database-sqlite3.h"
#if USE_POSTGRESQL
-#include "database-postgresql.h"
+#include "database/database-postgresql.h"
#endif
#include <algorithm>
#include "log.h"
#include "util/strfnd.h"
#include "defaultsettings.h" // for override_default_settings
-#include "mapgen.h" // for MapgenParams
+#include "mapgen/mapgen.h" // for MapgenParams
#include "util/string.h"
#ifndef SERVER
+++ /dev/null
-/*
-Copyright (C) 2014 sapier
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "touchscreengui.h"
-#include "irrlichttypes.h"
-#include "irr_v2d.h"
-#include "log.h"
-#include "keycode.h"
-#include "settings.h"
-#include "gettime.h"
-#include "util/numeric.h"
-#include "porting.h"
-#include "guiscalingfilter.h"
-
-#include <iostream>
-#include <algorithm>
-
-#include <ISceneCollisionManager.h>
-
-// Very slow button repeat frequency (in seconds)
-#define SLOW_BUTTON_REPEAT (1.0f)
-
-using namespace irr::core;
-
-const char** touchgui_button_imagenames = (const char*[]) {
- "up_arrow.png",
- "down_arrow.png",
- "left_arrow.png",
- "right_arrow.png",
- "jump_btn.png",
- "down.png"
-};
-
-static irr::EKEY_CODE id2keycode(touch_gui_button_id id)
-{
- std::string key = "";
- switch (id) {
- case forward_id:
- key = "forward";
- break;
- case left_id:
- key = "left";
- break;
- case right_id:
- key = "right";
- break;
- case backward_id:
- key = "backward";
- break;
- case inventory_id:
- key = "inventory";
- break;
- case drop_id:
- key = "drop";
- break;
- case jump_id:
- key = "jump";
- break;
- case crunch_id:
- key = "sneak";
- break;
- case fly_id:
- key = "freemove";
- break;
- case noclip_id:
- key = "noclip";
- break;
- case fast_id:
- key = "fastmove";
- break;
- case debug_id:
- key = "toggle_debug";
- break;
- case chat_id:
- key = "chat";
- break;
- case camera_id:
- key = "camera_mode";
- break;
- case range_id:
- key = "rangeselect";
- break;
- }
- assert(key != "");
- return keyname_to_keycode(g_settings->get("keymap_" + key).c_str());
-}
-
-TouchScreenGUI *g_touchscreengui;
-
-static void load_button_texture(button_info* btn, const char* path,
- rect<s32> button_rect, ISimpleTextureSource* tsrc, video::IVideoDriver *driver)
-{
- unsigned int tid;
- video::ITexture *texture = guiScalingImageButton(driver,
- tsrc->getTexture(path, &tid), button_rect.getWidth(),
- button_rect.getHeight());
- if (texture) {
- btn->guibutton->setUseAlphaChannel(true);
- if (g_settings->getBool("gui_scaling_filter")) {
- rect<s32> txr_rect = rect<s32>(0, 0, button_rect.getWidth(), button_rect.getHeight());
- btn->guibutton->setImage(texture, txr_rect);
- btn->guibutton->setPressedImage(texture, txr_rect);
- btn->guibutton->setScaleImage(false);
- } else {
- btn->guibutton->setImage(texture);
- btn->guibutton->setPressedImage(texture);
- btn->guibutton->setScaleImage(true);
- }
- btn->guibutton->setDrawBorder(false);
- btn->guibutton->setText(L"");
- }
-}
-
-AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device,
- IEventReceiver* receiver) :
- m_driver(device->getVideoDriver()),
- m_guienv(device->getGUIEnvironment()),
- m_receiver(receiver)
-{
-}
-
-void AutoHideButtonBar::init(ISimpleTextureSource* tsrc,
- const char* starter_img, int button_id, v2s32 UpperLeft,
- v2s32 LowerRight, autohide_button_bar_dir dir, float timeout)
-{
- m_texturesource = tsrc;
-
- m_upper_left = UpperLeft;
- m_lower_right = LowerRight;
-
- /* init settings bar */
-
- irr::core::rect<int> current_button = rect<s32>(UpperLeft.X, UpperLeft.Y,
- LowerRight.X, LowerRight.Y);
-
- m_starter.guibutton = m_guienv->addButton(current_button, 0, button_id, L"", 0);
- m_starter.guibutton->grab();
- m_starter.repeatcounter = -1;
- m_starter.keycode = KEY_OEM_8; // use invalid keycode as it's not relevant
- m_starter.immediate_release = true;
- m_starter.ids.clear();
-
- load_button_texture(&m_starter, starter_img, current_button,
- m_texturesource, m_driver);
-
- m_dir = dir;
- m_timeout_value = timeout;
-
- m_initialized = true;
-}
-
-AutoHideButtonBar::~AutoHideButtonBar()
-{
- if (m_starter.guibutton) {
- m_starter.guibutton->setVisible(false);
- m_starter.guibutton->drop();
- }
-}
-
-void AutoHideButtonBar::addButton(touch_gui_button_id button_id,
- const wchar_t* caption, const char* btn_image)
-{
-
- if (!m_initialized) {
- errorstream << "AutoHideButtonBar::addButton not yet initialized!"
- << std::endl;
- return;
- }
- int button_size = 0;
-
- if ((m_dir == AHBB_Dir_Top_Bottom) || (m_dir == AHBB_Dir_Bottom_Top)) {
- button_size = m_lower_right.X - m_upper_left.X;
- } else {
- button_size = m_lower_right.Y - m_upper_left.Y;
- }
-
- irr::core::rect<int> current_button;
-
- if ((m_dir == AHBB_Dir_Right_Left) || (m_dir == AHBB_Dir_Left_Right)) {
-
- int x_start = 0;
- int x_end = 0;
-
- if (m_dir == AHBB_Dir_Left_Right) {
- x_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
- + (button_size * 0.25);
- x_end = x_start + button_size;
- } else {
- x_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size())
- - (button_size * 0.25);
- x_start = x_end - button_size;
- }
-
- current_button = rect<s32>(x_start, m_upper_left.Y, x_end,
- m_lower_right.Y);
- } else {
- int y_start = 0;
- int y_end = 0;
-
- if (m_dir == AHBB_Dir_Top_Bottom) {
- y_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
- + (button_size * 0.25);
- y_end = y_start + button_size;
- } else {
- y_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size())
- - (button_size * 0.25);
- y_start = y_end - button_size;
- }
-
- current_button = rect<s32>(m_upper_left.X, y_start, m_lower_right.Y,
- y_end);
- }
-
- button_info* btn = new button_info();
- btn->guibutton = m_guienv->addButton(current_button, 0, button_id, caption, 0);
- btn->guibutton->grab();
- btn->guibutton->setVisible(false);
- btn->guibutton->setEnabled(false);
- btn->repeatcounter = -1;
- btn->keycode = id2keycode(button_id);
- btn->immediate_release = true;
- btn->ids.clear();
-
- load_button_texture(btn, btn_image, current_button, m_texturesource,
- m_driver);
-
- m_buttons.push_back(btn);
-}
-
-bool AutoHideButtonBar::isButton(const SEvent &event)
-{
- IGUIElement* rootguielement = m_guienv->getRootGUIElement();
-
- if (rootguielement == NULL) {
- return false;
- }
-
- gui::IGUIElement *element = rootguielement->getElementFromPoint(
- core::position2d<s32>(event.TouchInput.X, event.TouchInput.Y));
-
- if (element == NULL) {
- return false;
- }
-
- if (m_active) {
- /* check for all buttons in vector */
-
- std::vector<button_info*>::iterator iter = m_buttons.begin();
-
- while (iter != m_buttons.end()) {
- if ((*iter)->guibutton == element) {
-
- SEvent* translated = new SEvent();
- memset(translated, 0, sizeof(SEvent));
- translated->EventType = irr::EET_KEY_INPUT_EVENT;
- translated->KeyInput.Key = (*iter)->keycode;
- translated->KeyInput.Control = false;
- translated->KeyInput.Shift = false;
- translated->KeyInput.Char = 0;
-
- /* add this event */
- translated->KeyInput.PressedDown = true;
- m_receiver->OnEvent(*translated);
-
- /* remove this event */
- translated->KeyInput.PressedDown = false;
- m_receiver->OnEvent(*translated);
-
- delete translated;
-
- (*iter)->ids.push_back(event.TouchInput.ID);
-
- m_timeout = 0;
-
- return true;
- }
- ++iter;
- }
- } else {
- /* check for starter button only */
- if (element == m_starter.guibutton) {
- m_starter.ids.push_back(event.TouchInput.ID);
- m_starter.guibutton->setVisible(false);
- m_starter.guibutton->setEnabled(false);
- m_active = true;
- m_timeout = 0;
-
- std::vector<button_info*>::iterator iter = m_buttons.begin();
-
- while (iter != m_buttons.end()) {
- (*iter)->guibutton->setVisible(true);
- (*iter)->guibutton->setEnabled(true);
- ++iter;
- }
-
- return true;
- }
- }
- return false;
-}
-
-bool AutoHideButtonBar::isReleaseButton(int eventID)
-{
- std::vector<int>::iterator id = std::find(m_starter.ids.begin(),
- m_starter.ids.end(), eventID);
-
- if (id != m_starter.ids.end()) {
- m_starter.ids.erase(id);
- return true;
- }
-
- std::vector<button_info*>::iterator iter = m_buttons.begin();
-
- while (iter != m_buttons.end()) {
- std::vector<int>::iterator id = std::find((*iter)->ids.begin(),
- (*iter)->ids.end(), eventID);
-
- if (id != (*iter)->ids.end()) {
- (*iter)->ids.erase(id);
- // TODO handle settings button release
- return true;
- }
- ++iter;
- }
-
- return false;
-}
-
-void AutoHideButtonBar::step(float dtime)
-{
- if (m_active) {
- m_timeout += dtime;
-
- if (m_timeout > m_timeout_value) {
- deactivate();
- }
- }
-}
-
-void AutoHideButtonBar::deactivate()
-{
- if (m_visible) {
- m_starter.guibutton->setVisible(true);
- m_starter.guibutton->setEnabled(true);
- }
- m_active = false;
-
- std::vector<button_info*>::iterator iter = m_buttons.begin();
-
- while (iter != m_buttons.end()) {
- (*iter)->guibutton->setVisible(false);
- (*iter)->guibutton->setEnabled(false);
- ++iter;
- }
-}
-
-void AutoHideButtonBar::hide()
-{
- m_visible = false;
- m_starter.guibutton->setVisible(false);
- m_starter.guibutton->setEnabled(false);
-
- std::vector<button_info*>::iterator iter = m_buttons.begin();
-
- while (iter != m_buttons.end()) {
- (*iter)->guibutton->setVisible(false);
- (*iter)->guibutton->setEnabled(false);
- ++iter;
- }
-}
-
-void AutoHideButtonBar::show()
-{
- m_visible = true;
-
- if (m_active) {
- std::vector<button_info*>::iterator iter = m_buttons.begin();
-
- while (iter != m_buttons.end()) {
- (*iter)->guibutton->setVisible(true);
- (*iter)->guibutton->setEnabled(true);
- ++iter;
- }
- } else {
- m_starter.guibutton->setVisible(true);
- m_starter.guibutton->setEnabled(true);
- }
-}
-
-TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver):
- m_device(device),
- m_guienv(device->getGUIEnvironment()),
- m_receiver(receiver),
- m_settingsbar(device, receiver),
- m_rarecontrolsbar(device, receiver)
-{
- for (unsigned int i=0; i < after_last_element_id; i++) {
- m_buttons[i].guibutton = 0;
- m_buttons[i].repeatcounter = -1;
- m_buttons[i].repeatdelay = BUTTON_REPEAT_DELAY;
- }
-
- m_screensize = m_device->getVideoDriver()->getScreenSize();
-}
-
-void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
- std::wstring caption, bool immediate_release, float repeat_delay)
-{
-
- button_info* btn = &m_buttons[id];
- btn->guibutton = m_guienv->addButton(button_rect, 0, id, caption.c_str());
- btn->guibutton->grab();
- btn->repeatcounter = -1;
- btn->repeatdelay = repeat_delay;
- btn->keycode = id2keycode(id);
- btn->immediate_release = immediate_release;
- btn->ids.clear();
-
- load_button_texture(btn,touchgui_button_imagenames[id],button_rect,
- m_texturesource, m_device->getVideoDriver());
-}
-
-static int getMaxControlPadSize(float density) {
- return 200 * density * g_settings->getFloat("hud_scaling");
-}
-
-int TouchScreenGUI::getGuiButtonSize()
-{
- u32 control_pad_size = MYMIN((2 * m_screensize.Y) / 3,
- getMaxControlPadSize(porting::getDisplayDensity()));
-
- return control_pad_size / 3;
-}
-
-void TouchScreenGUI::init(ISimpleTextureSource* tsrc)
-{
- assert(tsrc != 0);
-
- u32 button_size = getGuiButtonSize();
- m_visible = true;
- m_texturesource = tsrc;
- /*
- draw control pad
- 0 1 2
- 3 4 5
- for now only 0, 1, 2, and 4 are used
- */
- int number = 0;
- for (int y = 0; y < 2; ++y)
- for (int x = 0; x < 3; ++x, ++number) {
- rect<s32> button_rect(
- x * button_size, m_screensize.Y - button_size * (2 - y),
- (x + 1) * button_size, m_screensize.Y - button_size * (1 - y)
- );
- touch_gui_button_id id = after_last_element_id;
- std::wstring caption;
- switch (number) {
- case 0:
- id = left_id;
- caption = L"<";
- break;
- case 1:
- id = forward_id;
- caption = L"^";
- break;
- case 2:
- id = right_id;
- caption = L">";
- break;
- case 4:
- id = backward_id;
- caption = L"v";
- break;
- }
- if (id != after_last_element_id) {
- initButton(id, button_rect, caption, false);
- }
- }
-
- /* init jump button */
- initButton(jump_id,
- rect<s32>(m_screensize.X-(1.75*button_size),
- m_screensize.Y - (0.5*button_size),
- m_screensize.X-(0.25*button_size),
- m_screensize.Y),
- L"x",false);
-
- /* init crunch button */
- initButton(crunch_id,
- rect<s32>(m_screensize.X-(3.25*button_size),
- m_screensize.Y - (0.5*button_size),
- m_screensize.X-(1.75*button_size),
- m_screensize.Y),
- L"H",false);
-
- m_settingsbar.init(m_texturesource, "gear_icon.png", settings_starter_id,
- v2s32(m_screensize.X - (button_size / 2),
- m_screensize.Y - ((SETTINGS_BAR_Y_OFFSET + 1) * button_size)
- + (button_size * 0.5)),
- v2s32(m_screensize.X,
- m_screensize.Y - (SETTINGS_BAR_Y_OFFSET * button_size)
- + (button_size * 0.5)), AHBB_Dir_Right_Left,
- 3.0);
-
- m_settingsbar.addButton(fly_id, L"fly", "fly_btn.png");
- m_settingsbar.addButton(noclip_id, L"noclip", "noclip_btn.png");
- m_settingsbar.addButton(fast_id, L"fast", "fast_btn.png");
- m_settingsbar.addButton(debug_id, L"debug", "debug_btn.png");
- m_settingsbar.addButton(camera_id, L"camera", "camera_btn.png");
- m_settingsbar.addButton(range_id, L"rangeview", "rangeview_btn.png");
-
- m_rarecontrolsbar.init(m_texturesource, "rare_controls.png",
- rare_controls_starter_id,
- v2s32(0,
- m_screensize.Y
- - ((RARE_CONTROLS_BAR_Y_OFFSET + 1) * button_size)
- + (button_size * 0.5)),
- v2s32(button_size / 2,
- m_screensize.Y - (RARE_CONTROLS_BAR_Y_OFFSET * button_size)
- + (button_size * 0.5)), AHBB_Dir_Left_Right,
- 2);
-
- m_rarecontrolsbar.addButton(chat_id, L"Chat", "chat_btn.png");
- m_rarecontrolsbar.addButton(inventory_id, L"inv", "inventory_btn.png");
- m_rarecontrolsbar.addButton(drop_id, L"drop", "drop_btn.png");
-
-}
-
-touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y)
-{
- IGUIElement* rootguielement = m_guienv->getRootGUIElement();
-
- if (rootguielement != NULL) {
- gui::IGUIElement *element =
- rootguielement->getElementFromPoint(core::position2d<s32>(x,y));
-
- if (element) {
- for (unsigned int i=0; i < after_last_element_id; i++) {
- if (element == m_buttons[i].guibutton) {
- return (touch_gui_button_id) i;
- }
- }
- }
- }
- return after_last_element_id;
-}
-
-touch_gui_button_id TouchScreenGUI::getButtonID(int eventID)
-{
- for (unsigned int i=0; i < after_last_element_id; i++) {
- button_info* btn = &m_buttons[i];
-
- std::vector<int>::iterator id =
- std::find(btn->ids.begin(),btn->ids.end(), eventID);
-
- if (id != btn->ids.end())
- return (touch_gui_button_id) i;
- }
-
- return after_last_element_id;
-}
-
-bool TouchScreenGUI::isHUDButton(const SEvent &event)
-{
- // check if hud item is pressed
- for (std::map<int,rect<s32> >::iterator iter = m_hud_rects.begin();
- iter != m_hud_rects.end(); ++iter) {
- if (iter->second.isPointInside(
- v2s32(event.TouchInput.X,
- event.TouchInput.Y)
- )) {
- if ( iter->first < 8) {
- SEvent* translated = new SEvent();
- memset(translated,0,sizeof(SEvent));
- translated->EventType = irr::EET_KEY_INPUT_EVENT;
- translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + iter->first);
- translated->KeyInput.Control = false;
- translated->KeyInput.Shift = false;
- translated->KeyInput.PressedDown = true;
- m_receiver->OnEvent(*translated);
- m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key;
- delete translated;
- return true;
- }
- }
- }
- return false;
-}
-
-bool TouchScreenGUI::isReleaseHUDButton(int eventID)
-{
- std::map<int,irr::EKEY_CODE>::iterator iter = m_hud_ids.find(eventID);
-
- if (iter != m_hud_ids.end()) {
- SEvent* translated = new SEvent();
- memset(translated,0,sizeof(SEvent));
- translated->EventType = irr::EET_KEY_INPUT_EVENT;
- translated->KeyInput.Key = iter->second;
- translated->KeyInput.PressedDown = false;
- translated->KeyInput.Control = false;
- translated->KeyInput.Shift = false;
- m_receiver->OnEvent(*translated);
- m_hud_ids.erase(iter);
- delete translated;
- return true;
- }
- return false;
-}
-
-void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button,
- int eventID, bool action)
-{
- button_info* btn = &m_buttons[button];
- SEvent* translated = new SEvent();
- memset(translated,0,sizeof(SEvent));
- translated->EventType = irr::EET_KEY_INPUT_EVENT;
- translated->KeyInput.Key = btn->keycode;
- translated->KeyInput.Control = false;
- translated->KeyInput.Shift = false;
- translated->KeyInput.Char = 0;
-
- /* add this event */
- if (action) {
- assert(std::find(btn->ids.begin(),btn->ids.end(), eventID) == btn->ids.end());
-
- btn->ids.push_back(eventID);
-
- if (btn->ids.size() > 1) return;
-
- btn->repeatcounter = 0;
- translated->KeyInput.PressedDown = true;
- translated->KeyInput.Key = btn->keycode;
- m_receiver->OnEvent(*translated);
- }
- /* remove event */
- if ((!action) || (btn->immediate_release)) {
-
- std::vector<int>::iterator pos =
- std::find(btn->ids.begin(),btn->ids.end(), eventID);
- /* has to be in touch list */
- assert(pos != btn->ids.end());
- btn->ids.erase(pos);
-
- if (btn->ids.size() > 0) { return; }
-
- translated->KeyInput.PressedDown = false;
- btn->repeatcounter = -1;
- m_receiver->OnEvent(*translated);
- }
- delete translated;
-}
-
-
-void TouchScreenGUI::handleReleaseEvent(int evt_id)
-{
- touch_gui_button_id button = getButtonID(evt_id);
-
- /* handle button events */
- if (button != after_last_element_id) {
- handleButtonEvent(button, evt_id, false);
- }
- /* handle hud button events */
- else if (isReleaseHUDButton(evt_id)) {
- /* nothing to do here */
- } else if (m_settingsbar.isReleaseButton(evt_id)) {
- /* nothing to do here */
- } else if (m_rarecontrolsbar.isReleaseButton(evt_id)) {
- /* nothing to do here */
- }
- /* handle the point used for moving view */
- else if (evt_id == m_move_id) {
- m_move_id = -1;
-
- /* if this pointer issued a mouse event issue symmetric release here */
- if (m_move_sent_as_mouse_event) {
- SEvent* translated = new SEvent;
- memset(translated,0,sizeof(SEvent));
- translated->EventType = EET_MOUSE_INPUT_EVENT;
- translated->MouseInput.X = m_move_downlocation.X;
- translated->MouseInput.Y = m_move_downlocation.Y;
- translated->MouseInput.Shift = false;
- translated->MouseInput.Control = false;
- translated->MouseInput.ButtonStates = 0;
- translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
- m_receiver->OnEvent(*translated);
- delete translated;
- }
- else {
- /* do double tap detection */
- doubleTapDetection();
- }
- }
- else {
- infostream
- << "TouchScreenGUI::translateEvent released unknown button: "
- << evt_id << std::endl;
- }
-
- for (std::vector<id_status>::iterator iter = m_known_ids.begin();
- iter != m_known_ids.end(); ++iter) {
- if (iter->id == evt_id) {
- m_known_ids.erase(iter);
- break;
- }
- }
-}
-
-void TouchScreenGUI::translateEvent(const SEvent &event)
-{
- if (!m_visible) {
- infostream << "TouchScreenGUI::translateEvent got event but not visible?!" << std::endl;
- return;
- }
-
- if (event.EventType != EET_TOUCH_INPUT_EVENT) {
- return;
- }
-
- if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
-
- /* add to own copy of eventlist ...
- * android would provide this information but irrlicht guys don't
- * wanna design a efficient interface
- */
- id_status toadd;
- toadd.id = event.TouchInput.ID;
- toadd.X = event.TouchInput.X;
- toadd.Y = event.TouchInput.Y;
- m_known_ids.push_back(toadd);
-
- int eventID = event.TouchInput.ID;
-
- touch_gui_button_id button =
- getButtonID(event.TouchInput.X, event.TouchInput.Y);
-
- /* handle button events */
- if (button != after_last_element_id) {
- handleButtonEvent(button, eventID, true);
- m_settingsbar.deactivate();
- m_rarecontrolsbar.deactivate();
- } else if (isHUDButton(event)) {
- m_settingsbar.deactivate();
- m_rarecontrolsbar.deactivate();
- /* already handled in isHUDButton() */
- } else if (m_settingsbar.isButton(event)) {
- m_rarecontrolsbar.deactivate();
- /* already handled in isSettingsBarButton() */
- } else if (m_rarecontrolsbar.isButton(event)) {
- m_settingsbar.deactivate();
- /* already handled in isSettingsBarButton() */
- }
- /* handle non button events */
- else {
- m_settingsbar.deactivate();
- m_rarecontrolsbar.deactivate();
- /* if we don't already have a moving point make this the moving one */
- if (m_move_id == -1) {
- m_move_id = event.TouchInput.ID;
- m_move_has_really_moved = false;
- m_move_downtime = porting::getTimeMs();
- m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y);
- m_move_sent_as_mouse_event = false;
- }
- }
-
- m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y);
- }
- else if (event.TouchInput.Event == ETIE_LEFT_UP) {
- verbosestream << "Up event for pointerid: " << event.TouchInput.ID << std::endl;
- handleReleaseEvent(event.TouchInput.ID);
- }
- else {
- assert(event.TouchInput.Event == ETIE_MOVED);
- int move_idx = event.TouchInput.ID;
-
- if (m_pointerpos[event.TouchInput.ID] ==
- v2s32(event.TouchInput.X, event.TouchInput.Y)) {
- return;
- }
-
- if (m_move_id != -1) {
- if ((event.TouchInput.ID == m_move_id) &&
- (!m_move_sent_as_mouse_event)) {
-
- double distance = sqrt(
- (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) *
- (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) +
- (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) *
- (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y));
-
- if ((distance > g_settings->getU16("touchscreen_threshold")) ||
- (m_move_has_really_moved)) {
- m_move_has_really_moved = true;
- s32 X = event.TouchInput.X;
- s32 Y = event.TouchInput.Y;
-
- // update camera_yaw and camera_pitch
- s32 dx = X - m_pointerpos[event.TouchInput.ID].X;
- s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y;
-
- /* adapt to similar behaviour as pc screen */
- double d = g_settings->getFloat("mouse_sensitivity") *4;
- double old_yaw = m_camera_yaw_change;
- double old_pitch = m_camera_pitch;
-
- m_camera_yaw_change -= dx * d;
- m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * d), -180), 180);
-
- // update shootline
- m_shootline = m_device
- ->getSceneManager()
- ->getSceneCollisionManager()
- ->getRayFromScreenCoordinates(v2s32(X, Y));
- m_pointerpos[event.TouchInput.ID] = v2s32(X, Y);
- }
- }
- else if ((event.TouchInput.ID == m_move_id) &&
- (m_move_sent_as_mouse_event)) {
- m_shootline = m_device
- ->getSceneManager()
- ->getSceneCollisionManager()
- ->getRayFromScreenCoordinates(
- v2s32(event.TouchInput.X,event.TouchInput.Y));
- }
- } else {
- handleChangedButton(event);
- }
- }
-}
-
-void TouchScreenGUI::handleChangedButton(const SEvent &event)
-{
- for (unsigned int i = 0; i < after_last_element_id; i++) {
-
- if (m_buttons[i].ids.empty()) {
- continue;
- }
- for (std::vector<int>::iterator iter = m_buttons[i].ids.begin();
- iter != m_buttons[i].ids.end(); ++iter) {
-
- if (event.TouchInput.ID == *iter) {
-
- int current_button_id =
- getButtonID(event.TouchInput.X, event.TouchInput.Y);
-
- if (current_button_id == i) {
- continue;
- }
-
- /* remove old button */
- handleButtonEvent((touch_gui_button_id) i,*iter,false);
-
- if (current_button_id == after_last_element_id) {
- return;
- }
- handleButtonEvent((touch_gui_button_id) current_button_id,*iter,true);
- return;
-
- }
- }
- }
-
- int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y);
-
- if (current_button_id == after_last_element_id) {
- return;
- }
-
- button_info* btn = &m_buttons[current_button_id];
- if (std::find(btn->ids.begin(),btn->ids.end(), event.TouchInput.ID)
- == btn->ids.end())
- {
- handleButtonEvent((touch_gui_button_id) current_button_id,
- event.TouchInput.ID, true);
- }
-
-}
-
-bool TouchScreenGUI::doubleTapDetection()
-{
- m_key_events[0].down_time = m_key_events[1].down_time;
- m_key_events[0].x = m_key_events[1].x;
- m_key_events[0].y = m_key_events[1].y;
- m_key_events[1].down_time = m_move_downtime;
- m_key_events[1].x = m_move_downlocation.X;
- m_key_events[1].y = m_move_downlocation.Y;
-
- u64 delta = porting::getDeltaMs(m_key_events[0].down_time, porting::getTimeMs());
- if (delta > 400)
- return false;
-
- double distance = sqrt(
- (m_key_events[0].x - m_key_events[1].x) * (m_key_events[0].x - m_key_events[1].x) +
- (m_key_events[0].y - m_key_events[1].y) * (m_key_events[0].y - m_key_events[1].y));
-
-
- if (distance > (20 + g_settings->getU16("touchscreen_threshold")))
- return false;
-
- SEvent* translated = new SEvent();
- memset(translated, 0, sizeof(SEvent));
- translated->EventType = EET_MOUSE_INPUT_EVENT;
- translated->MouseInput.X = m_key_events[0].x;
- translated->MouseInput.Y = m_key_events[0].y;
- translated->MouseInput.Shift = false;
- translated->MouseInput.Control = false;
- translated->MouseInput.ButtonStates = EMBSM_RIGHT;
-
- // update shootline
- m_shootline = m_device
- ->getSceneManager()
- ->getSceneCollisionManager()
- ->getRayFromScreenCoordinates(v2s32(m_key_events[0].x, m_key_events[0].y));
-
- translated->MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
- verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl;
- m_receiver->OnEvent(*translated);
-
- translated->MouseInput.ButtonStates = 0;
- translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
- verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
- m_receiver->OnEvent(*translated);
- delete translated;
- return true;
-
-}
-
-TouchScreenGUI::~TouchScreenGUI()
-{
- for (unsigned int i = 0; i < after_last_element_id; i++) {
- button_info* btn = &m_buttons[i];
- if (btn->guibutton != 0) {
- btn->guibutton->drop();
- btn->guibutton = NULL;
- }
- }
-}
-
-void TouchScreenGUI::step(float dtime)
-{
- /* simulate keyboard repeats */
- for (unsigned int i = 0; i < after_last_element_id; i++) {
- button_info* btn = &m_buttons[i];
-
- if (btn->ids.size() > 0) {
- btn->repeatcounter += dtime;
-
- /* in case we're moving around digging does not happen */
- if (m_move_id != -1)
- m_move_has_really_moved = true;
-
- if (btn->repeatcounter < btn->repeatdelay) continue;
-
- btn->repeatcounter = 0;
- SEvent translated;
- memset(&translated, 0, sizeof(SEvent));
- translated.EventType = irr::EET_KEY_INPUT_EVENT;
- translated.KeyInput.Key = btn->keycode;
- translated.KeyInput.PressedDown = false;
- m_receiver->OnEvent(translated);
-
- translated.KeyInput.PressedDown = true;
- m_receiver->OnEvent(translated);
- }
- }
-
- /* if a new placed pointer isn't moved for some time start digging */
- if ((m_move_id != -1) &&
- (!m_move_has_really_moved) &&
- (!m_move_sent_as_mouse_event)) {
-
- u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
-
- if (delta > MIN_DIG_TIME_MS) {
- m_shootline = m_device
- ->getSceneManager()
- ->getSceneCollisionManager()
- ->getRayFromScreenCoordinates(
- v2s32(m_move_downlocation.X,m_move_downlocation.Y));
-
- SEvent translated;
- memset(&translated, 0, sizeof(SEvent));
- translated.EventType = EET_MOUSE_INPUT_EVENT;
- translated.MouseInput.X = m_move_downlocation.X;
- translated.MouseInput.Y = m_move_downlocation.Y;
- translated.MouseInput.Shift = false;
- translated.MouseInput.Control = false;
- translated.MouseInput.ButtonStates = EMBSM_LEFT;
- translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
- verbosestream << "TouchScreenGUI::step left click press" << std::endl;
- m_receiver->OnEvent(translated);
- m_move_sent_as_mouse_event = true;
- }
- }
-
- m_settingsbar.step(dtime);
- m_rarecontrolsbar.step(dtime);
-}
-
-void TouchScreenGUI::resetHud()
-{
- m_hud_rects.clear();
-}
-
-void TouchScreenGUI::registerHudItem(int index, const rect<s32> &rect)
-{
- m_hud_rects[index] = rect;
-}
-
-void TouchScreenGUI::Toggle(bool visible)
-{
- m_visible = visible;
- for (unsigned int i = 0; i < after_last_element_id; i++) {
- button_info* btn = &m_buttons[i];
- if (btn->guibutton != 0) {
- btn->guibutton->setVisible(visible);
- }
- }
-
- /* clear all active buttons */
- if (!visible) {
- while (m_known_ids.size() > 0) {
- handleReleaseEvent(m_known_ids.begin()->id);
- }
-
- m_settingsbar.hide();
- m_rarecontrolsbar.hide();
- } else {
- m_settingsbar.show();
- m_rarecontrolsbar.show();
- }
-}
-
-void TouchScreenGUI::hide()
-{
- if (!m_visible)
- return;
-
- Toggle(false);
-}
-
-void TouchScreenGUI::show()
-{
- if (m_visible)
- return;
-
- Toggle(true);
-}
+++ /dev/null
-/*
-Copyright (C) 2014 sapier
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <IEventReceiver.h>
-#include <IGUIButton.h>
-#include <IGUIEnvironment.h>
-
-#include <map>
-#include <vector>
-
-#include "client/tile.h"
-#include "game.h"
-
-using namespace irr;
-using namespace irr::core;
-using namespace irr::gui;
-
-typedef enum {
- forward_id = 0,
- backward_id,
- left_id,
- right_id,
- jump_id,
- crunch_id,
- after_last_element_id,
- settings_starter_id,
- rare_controls_starter_id,
- fly_id,
- noclip_id,
- fast_id,
- debug_id,
- camera_id,
- range_id,
- chat_id,
- inventory_id,
- drop_id
-} touch_gui_button_id;
-
-typedef enum {
- AHBB_Dir_Top_Bottom,
- AHBB_Dir_Bottom_Top,
- AHBB_Dir_Left_Right,
- AHBB_Dir_Right_Left
-} autohide_button_bar_dir;
-
-#define MIN_DIG_TIME_MS 500
-#define MAX_TOUCH_COUNT 64
-#define BUTTON_REPEAT_DELAY 0.2f
-
-#define SETTINGS_BAR_Y_OFFSET 6.5
-#define RARE_CONTROLS_BAR_Y_OFFSET 4
-
-extern const char **touchgui_button_imagenames;
-
-struct button_info
-{
- float repeatcounter;
- float repeatdelay;
- irr::EKEY_CODE keycode;
- std::vector<int> ids;
- IGUIButton *guibutton = nullptr;
- bool immediate_release;
-};
-
-class AutoHideButtonBar
-{
-public:
- AutoHideButtonBar(IrrlichtDevice *device, IEventReceiver *receiver);
-
- void init(ISimpleTextureSource *tsrc, const char *starter_img, int button_id,
- v2s32 UpperLeft, v2s32 LowerRight, autohide_button_bar_dir dir,
- float timeout);
-
- ~AutoHideButtonBar();
-
- /* add button to be shown */
- void addButton(touch_gui_button_id id, const wchar_t *caption,
- const char *btn_image);
-
- /* detect settings bar button events */
- bool isButton(const SEvent &event);
-
- /* handle released hud buttons */
- bool isReleaseButton(int eventID);
-
- /* step handler */
- void step(float dtime);
-
- /* deactivate button bar */
- void deactivate();
-
- /* hide the whole buttonbar */
- void hide();
-
- /* unhide the buttonbar */
- void show();
-
-private:
- ISimpleTextureSource *m_texturesource = nullptr;
- irr::video::IVideoDriver *m_driver;
- IGUIEnvironment *m_guienv;
- IEventReceiver *m_receiver;
- button_info m_starter;
- std::vector<button_info *> m_buttons;
-
- v2s32 m_upper_left;
- v2s32 m_lower_right;
-
- /* show settings bar */
- bool m_active = false;
-
- bool m_visible = true;
-
- /* settings bar timeout */
- float m_timeout = 0.0f;
- float m_timeout_value = 3.0f;
- bool m_initialized = false;
- autohide_button_bar_dir m_dir = AHBB_Dir_Right_Left;
-};
-
-class TouchScreenGUI
-{
-public:
- TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver);
- ~TouchScreenGUI();
-
- void translateEvent(const SEvent &event);
-
- void init(ISimpleTextureSource *tsrc);
-
- double getYawChange()
- {
- double res = m_camera_yaw_change;
- m_camera_yaw_change = 0;
- return res;
- }
-
- double getPitch() { return m_camera_pitch; }
-
- /*!
- * Returns a line which describes what the player is pointing at.
- * The starting point and looking direction are significant,
- * the line should be scaled to match its length to the actual distance
- * the player can reach.
- * The line starts at the camera and ends on the camera's far plane.
- * The coordinates do not contain the camera offset.
- */
- line3d<f32> getShootline() { return m_shootline; }
-
- void step(float dtime);
- void resetHud();
- void registerHudItem(int index, const rect<s32> &rect);
- void Toggle(bool visible);
-
- void hide();
- void show();
-
-private:
- IrrlichtDevice *m_device;
- IGUIEnvironment *m_guienv;
- IEventReceiver *m_receiver;
- ISimpleTextureSource *m_texturesource;
- v2u32 m_screensize;
- std::map<int, rect<s32>> m_hud_rects;
- std::map<int, irr::EKEY_CODE> m_hud_ids;
- bool m_visible; // is the gui visible
-
- /* value in degree */
- double m_camera_yaw_change = 0.0;
- double m_camera_pitch = 0.0;
-
- /*!
- * A line starting at the camera and pointing towards the
- * selected object.
- * The line ends on the camera's far plane.
- * The coordinates do not contain the camera offset.
- */
- line3d<f32> m_shootline;
-
- int m_move_id = -1;
- bool m_move_has_really_moved = false;
- s64 m_move_downtime = 0;
- bool m_move_sent_as_mouse_event = false;
- v2s32 m_move_downlocation = v2s32(-10000, -10000);
-
- button_info m_buttons[after_last_element_id];
-
- /* gui button detection */
- touch_gui_button_id getButtonID(s32 x, s32 y);
-
- /* gui button by eventID */
- touch_gui_button_id getButtonID(int eventID);
-
- /* check if a button has changed */
- void handleChangedButton(const SEvent &event);
-
- /* initialize a button */
- void initButton(touch_gui_button_id id, rect<s32> button_rect,
- std::wstring caption, bool immediate_release,
- float repeat_delay = BUTTON_REPEAT_DELAY);
-
- struct id_status
- {
- int id;
- int X;
- int Y;
- };
-
- /* vector to store known ids and their initial touch positions*/
- std::vector<id_status> m_known_ids;
-
- /* handle a button event */
- void handleButtonEvent(touch_gui_button_id bID, int eventID, bool action);
-
- /* handle pressed hud buttons */
- bool isHUDButton(const SEvent &event);
-
- /* handle released hud buttons */
- bool isReleaseHUDButton(int eventID);
-
- /* handle double taps */
- bool doubleTapDetection();
-
- /* handle release event */
- void handleReleaseEvent(int evt_id);
-
- /* get size of regular gui control button */
- int getGuiButtonSize();
-
- /* doubleclick detection variables */
- struct key_event
- {
- unsigned int down_time;
- s32 x;
- s32 y;
- };
-
- /* array for saving last known position of a pointer */
- v2s32 m_pointerpos[MAX_TOUCH_COUNT];
-
- /* array for doubletap detection */
- key_event m_key_events[2];
-
- /* settings bar */
- AutoHideButtonBar m_settingsbar;
-
- /* rare controls bar */
- AutoHideButtonBar m_rarecontrolsbar;
-};
-extern TouchScreenGUI *g_touchscreengui;
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>,
- 2012-2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "irr_v3d.h"
-#include <stack>
-#include "util/pointer.h"
-#include "util/numeric.h"
-#include "map.h"
-#include "mapblock.h"
-#include "serverenvironment.h"
-#include "nodedef.h"
-#include "treegen.h"
-#include "voxelalgorithms.h"
-
-namespace treegen
-{
-
-void make_tree(MMVManip &vmanip, v3s16 p0,
- bool is_apple_tree, INodeDefManager *ndef, s32 seed)
-{
- /*
- NOTE: Tree-placing code is currently duplicated in the engine
- and in games that have saplings; both are deprecated but not
- replaced yet
- */
- MapNode treenode(ndef->getId("mapgen_tree"));
- MapNode leavesnode(ndef->getId("mapgen_leaves"));
- MapNode applenode(ndef->getId("mapgen_apple"));
-
- PseudoRandom pr(seed);
- s16 trunk_h = pr.range(4, 5);
- v3s16 p1 = p0;
- for (s16 ii = 0; ii < trunk_h; ii++) {
- if (vmanip.m_area.contains(p1)) {
- u32 vi = vmanip.m_area.index(p1);
- vmanip.m_data[vi] = treenode;
- }
- p1.Y++;
- }
-
- // p1 is now the last piece of the trunk
- p1.Y -= 1;
-
- VoxelArea leaves_a(v3s16(-2, -1, -2), v3s16(2, 2, 2));
- Buffer<u8> leaves_d(leaves_a.getVolume());
- for (s32 i = 0; i < leaves_a.getVolume(); i++)
- leaves_d[i] = 0;
-
- // Force leaves at near the end of the trunk
- s16 d = 1;
- for (s16 z = -d; z <= d; z++)
- for (s16 y = -d; y <= d; y++)
- for (s16 x = -d; x <= d; x++) {
- leaves_d[leaves_a.index(v3s16(x, y, z))] = 1;
- }
-
- // Add leaves randomly
- for (u32 iii = 0; iii < 7; iii++) {
- v3s16 p(
- pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d),
- pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d),
- pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d)
- );
-
- for (s16 z = 0; z <= d; z++)
- for (s16 y = 0; y <= d; y++)
- for (s16 x = 0; x <= d; x++) {
- leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1;
- }
- }
-
- // Blit leaves to vmanip
- for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
- for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
- v3s16 pmin(leaves_a.MinEdge.X, y, z);
- u32 i = leaves_a.index(pmin);
- u32 vi = vmanip.m_area.index(pmin + p1);
- for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
- v3s16 p(x, y, z);
- if (vmanip.m_area.contains(p + p1) &&
- (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
- vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) {
- if (leaves_d[i] == 1) {
- bool is_apple = pr.range(0, 99) < 10;
- if (is_apple_tree && is_apple)
- vmanip.m_data[vi] = applenode;
- else
- vmanip.m_data[vi] = leavesnode;
- }
- }
- vi++;
- i++;
- }
- }
-}
-
-
-// L-System tree LUA spawner
-treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0,
- INodeDefManager *ndef, const TreeDef &tree_definition)
-{
- ServerMap *map = &env->getServerMap();
- std::map<v3s16, MapBlock*> modified_blocks;
- MMVManip vmanip(map);
- v3s16 tree_blockp = getNodeBlockPos(p0);
- treegen::error e;
-
- vmanip.initialEmerge(tree_blockp - v3s16(1, 1, 1), tree_blockp + v3s16(1, 3, 1));
- e = make_ltree(vmanip, p0, ndef, tree_definition);
- if (e != SUCCESS)
- return e;
-
- voxalgo::blit_back_with_light(map, &vmanip, &modified_blocks);
-
- // Send a MEET_OTHER event
- MapEditEvent event;
- event.type = MEET_OTHER;
- for (auto &modified_block : modified_blocks)
- event.modified_blocks.insert(modified_block.first);
- map->dispatchEvent(&event);
- return SUCCESS;
-}
-
-
-//L-System tree generator
-treegen::error make_ltree(MMVManip &vmanip, v3s16 p0,
- INodeDefManager *ndef, TreeDef tree_definition)
-{
- MapNode dirtnode(ndef->getId("mapgen_dirt"));
- s32 seed;
- if (tree_definition.explicit_seed)
- seed = tree_definition.seed + 14002;
- else
- seed = p0.X * 2 + p0.Y * 4 + p0.Z; // use the tree position to seed PRNG
- PseudoRandom ps(seed);
-
- // chance of inserting abcd rules
- double prop_a = 9;
- double prop_b = 8;
- double prop_c = 7;
- double prop_d = 6;
-
- //randomize tree growth level, minimum=2
- s16 iterations = tree_definition.iterations;
- if (tree_definition.iterations_random_level > 0)
- iterations -= ps.range(0, tree_definition.iterations_random_level);
- if (iterations < 2)
- iterations = 2;
-
- s16 MAX_ANGLE_OFFSET = 5;
- double angle_in_radians = (double)tree_definition.angle * M_PI / 180;
- double angleOffset_in_radians = (s16)(ps.range(0, 1) % MAX_ANGLE_OFFSET) * M_PI / 180;
-
- //initialize rotation matrix, position and stacks for branches
- core::matrix4 rotation;
- rotation = setRotationAxisRadians(rotation, M_PI / 2, v3f(0, 0, 1));
- v3f position;
- position.X = p0.X;
- position.Y = p0.Y;
- position.Z = p0.Z;
- std::stack <core::matrix4> stack_orientation;
- std::stack <v3f> stack_position;
-
- //generate axiom
- std::string axiom = tree_definition.initial_axiom;
- for (s16 i = 0; i < iterations; i++) {
- std::string temp;
- for (s16 j = 0; j < (s16)axiom.size(); j++) {
- char axiom_char = axiom.at(j);
- switch (axiom_char) {
- case 'A':
- temp += tree_definition.rules_a;
- break;
- case 'B':
- temp += tree_definition.rules_b;
- break;
- case 'C':
- temp += tree_definition.rules_c;
- break;
- case 'D':
- temp += tree_definition.rules_d;
- break;
- case 'a':
- if (prop_a >= ps.range(1, 10))
- temp += tree_definition.rules_a;
- break;
- case 'b':
- if (prop_b >= ps.range(1, 10))
- temp += tree_definition.rules_b;
- break;
- case 'c':
- if (prop_c >= ps.range(1, 10))
- temp += tree_definition.rules_c;
- break;
- case 'd':
- if (prop_d >= ps.range(1, 10))
- temp += tree_definition.rules_d;
- break;
- default:
- temp += axiom_char;
- break;
- }
- }
- axiom = temp;
- }
-
- //make sure tree is not floating in the air
- if (tree_definition.trunk_type == "double") {
- tree_node_placement(
- vmanip,
- v3f(position.X + 1, position.Y - 1, position.Z),
- dirtnode
- );
- tree_node_placement(
- vmanip,
- v3f(position.X, position.Y - 1, position.Z + 1),
- dirtnode
- );
- tree_node_placement(
- vmanip,
- v3f(position.X + 1, position.Y - 1, position.Z + 1),
- dirtnode
- );
- } else if (tree_definition.trunk_type == "crossed") {
- tree_node_placement(
- vmanip,
- v3f(position.X + 1, position.Y - 1, position.Z),
- dirtnode
- );
- tree_node_placement(
- vmanip,
- v3f(position.X - 1, position.Y - 1, position.Z),
- dirtnode
- );
- tree_node_placement(
- vmanip,
- v3f(position.X, position.Y - 1, position.Z + 1),
- dirtnode
- );
- tree_node_placement(
- vmanip,
- v3f(position.X, position.Y - 1, position.Z - 1),
- dirtnode
- );
- }
-
- /* build tree out of generated axiom
-
- Key for Special L-System Symbols used in Axioms
-
- G - move forward one unit with the pen up
- F - move forward one unit with the pen down drawing trunks and branches
- f - move forward one unit with the pen down drawing leaves (100% chance)
- T - move forward one unit with the pen down drawing trunks only
- R - move forward one unit with the pen down placing fruit
- A - replace with rules set A
- B - replace with rules set B
- C - replace with rules set C
- D - replace with rules set D
- a - replace with rules set A, chance 90%
- b - replace with rules set B, chance 80%
- c - replace with rules set C, chance 70%
- d - replace with rules set D, chance 60%
- + - yaw the turtle right by angle degrees
- - - yaw the turtle left by angle degrees
- & - pitch the turtle down by angle degrees
- ^ - pitch the turtle up by angle degrees
- / - roll the turtle to the right by angle degrees
- * - roll the turtle to the left by angle degrees
- [ - save in stack current state info
- ] - recover from stack state info
-
- */
-
- s16 x,y,z;
- for (s16 i = 0; i < (s16)axiom.size(); i++) {
- char axiom_char = axiom.at(i);
- core::matrix4 temp_rotation;
- temp_rotation.makeIdentity();
- v3f dir;
- switch (axiom_char) {
- case 'G':
- dir = v3f(1, 0, 0);
- dir = transposeMatrix(rotation, dir);
- position += dir;
- break;
- case 'T':
- tree_trunk_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z),
- tree_definition
- );
- if (tree_definition.trunk_type == "double" &&
- !tree_definition.thin_branches) {
- tree_trunk_placement(
- vmanip,
- v3f(position.X + 1, position.Y, position.Z),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z + 1),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X + 1, position.Y, position.Z + 1),
- tree_definition
- );
- } else if (tree_definition.trunk_type == "crossed" &&
- !tree_definition.thin_branches) {
- tree_trunk_placement(
- vmanip,
- v3f(position.X + 1, position.Y, position.Z),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X - 1, position.Y, position.Z),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z + 1),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z - 1),
- tree_definition
- );
- }
- dir = v3f(1, 0, 0);
- dir = transposeMatrix(rotation, dir);
- position += dir;
- break;
- case 'F':
- tree_trunk_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z),
- tree_definition
- );
- if ((stack_orientation.empty() &&
- tree_definition.trunk_type == "double") ||
- (!stack_orientation.empty() &&
- tree_definition.trunk_type == "double" &&
- !tree_definition.thin_branches)) {
- tree_trunk_placement(
- vmanip,
- v3f(position.X +1 , position.Y, position.Z),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z + 1),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X + 1, position.Y, position.Z + 1),
- tree_definition
- );
- } else if ((stack_orientation.empty() &&
- tree_definition.trunk_type == "crossed") ||
- (!stack_orientation.empty() &&
- tree_definition.trunk_type == "crossed" &&
- !tree_definition.thin_branches)) {
- tree_trunk_placement(
- vmanip,
- v3f(position.X + 1, position.Y, position.Z),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X - 1, position.Y, position.Z),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z + 1),
- tree_definition
- );
- tree_trunk_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z - 1),
- tree_definition
- );
- } if (!stack_orientation.empty()) {
- s16 size = 1;
- for (x = -size; x <= size; x++)
- for (y = -size; y <= size; y++)
- for (z = -size; z <= size; z++) {
- if (abs(x) == size &&
- abs(y) == size &&
- abs(z) == size) {
- tree_leaves_placement(
- vmanip,
- v3f(position.X + x + 1, position.Y + y,
- position.Z + z),
- ps.next(),
- tree_definition
- );
- tree_leaves_placement(
- vmanip,
- v3f(position.X + x - 1, position.Y + y,
- position.Z + z),
- ps.next(),
- tree_definition
- );
- tree_leaves_placement(
- vmanip,v3f(position.X + x, position.Y + y,
- position.Z + z + 1),
- ps.next(),
- tree_definition
- );
- tree_leaves_placement(
- vmanip,v3f(position.X + x, position.Y + y,
- position.Z + z - 1),
- ps.next(),
- tree_definition
- );
- }
- }
- }
- dir = v3f(1, 0, 0);
- dir = transposeMatrix(rotation, dir);
- position += dir;
- break;
- case 'f':
- tree_single_leaves_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z),
- ps.next(),
- tree_definition
- );
- dir = v3f(1, 0, 0);
- dir = transposeMatrix(rotation, dir);
- position += dir;
- break;
- case 'R':
- tree_fruit_placement(
- vmanip,
- v3f(position.X, position.Y, position.Z),
- tree_definition
- );
- dir = v3f(1, 0, 0);
- dir = transposeMatrix(rotation, dir);
- position += dir;
- break;
-
- // turtle orientation commands
- case '[':
- stack_orientation.push(rotation);
- stack_position.push(position);
- break;
- case ']':
- if (stack_orientation.empty())
- return UNBALANCED_BRACKETS;
- rotation = stack_orientation.top();
- stack_orientation.pop();
- position = stack_position.top();
- stack_position.pop();
- break;
- case '+':
- temp_rotation.makeIdentity();
- temp_rotation = setRotationAxisRadians(temp_rotation,
- angle_in_radians + angleOffset_in_radians, v3f(0, 0, 1));
- rotation *= temp_rotation;
- break;
- case '-':
- temp_rotation.makeIdentity();
- temp_rotation = setRotationAxisRadians(temp_rotation,
- angle_in_radians + angleOffset_in_radians, v3f(0, 0, -1));
- rotation *= temp_rotation;
- break;
- case '&':
- temp_rotation.makeIdentity();
- temp_rotation = setRotationAxisRadians(temp_rotation,
- angle_in_radians + angleOffset_in_radians, v3f(0, 1, 0));
- rotation *= temp_rotation;
- break;
- case '^':
- temp_rotation.makeIdentity();
- temp_rotation = setRotationAxisRadians(temp_rotation,
- angle_in_radians + angleOffset_in_radians, v3f(0, -1, 0));
- rotation *= temp_rotation;
- break;
- case '*':
- temp_rotation.makeIdentity();
- temp_rotation = setRotationAxisRadians(temp_rotation,
- angle_in_radians, v3f(1, 0, 0));
- rotation *= temp_rotation;
- break;
- case '/':
- temp_rotation.makeIdentity();
- temp_rotation = setRotationAxisRadians(temp_rotation,
- angle_in_radians, v3f(-1, 0, 0));
- rotation *= temp_rotation;
- break;
- default:
- break;
- }
- }
-
- return SUCCESS;
-}
-
-
-void tree_node_placement(MMVManip &vmanip, v3f p0, MapNode node)
-{
- v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
- if (!vmanip.m_area.contains(p1))
- return;
- u32 vi = vmanip.m_area.index(p1);
- if (vmanip.m_data[vi].getContent() != CONTENT_AIR
- && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
- return;
- vmanip.m_data[vmanip.m_area.index(p1)] = node;
-}
-
-
-void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition)
-{
- v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
- if (!vmanip.m_area.contains(p1))
- return;
- u32 vi = vmanip.m_area.index(p1);
- content_t current_node = vmanip.m_data[vi].getContent();
- if (current_node != CONTENT_AIR && current_node != CONTENT_IGNORE
- && current_node != tree_definition.leavesnode.getContent()
- && current_node != tree_definition.leaves2node.getContent()
- && current_node != tree_definition.fruitnode.getContent())
- return;
- vmanip.m_data[vi] = tree_definition.trunknode;
-}
-
-
-void tree_leaves_placement(MMVManip &vmanip, v3f p0,
- PseudoRandom ps, TreeDef &tree_definition)
-{
- MapNode leavesnode = tree_definition.leavesnode;
- if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance)
- leavesnode = tree_definition.leaves2node;
- v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
- if (!vmanip.m_area.contains(p1))
- return;
- u32 vi = vmanip.m_area.index(p1);
- if (vmanip.m_data[vi].getContent() != CONTENT_AIR
- && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
- return;
- if (tree_definition.fruit_chance > 0) {
- if (ps.range(1, 100) > 100 - tree_definition.fruit_chance)
- vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode;
- else
- vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
- } else if (ps.range(1, 100) > 20) {
- vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
- }
-}
-
-
-void tree_single_leaves_placement(MMVManip &vmanip, v3f p0,
- PseudoRandom ps, TreeDef &tree_definition)
-{
- MapNode leavesnode = tree_definition.leavesnode;
- if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance)
- leavesnode = tree_definition.leaves2node;
- v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
- if (!vmanip.m_area.contains(p1))
- return;
- u32 vi = vmanip.m_area.index(p1);
- if (vmanip.m_data[vi].getContent() != CONTENT_AIR
- && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
- return;
- vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode;
-}
-
-
-void tree_fruit_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition)
-{
- v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z));
- if (!vmanip.m_area.contains(p1))
- return;
- u32 vi = vmanip.m_area.index(p1);
- if (vmanip.m_data[vi].getContent() != CONTENT_AIR
- && vmanip.m_data[vi].getContent() != CONTENT_IGNORE)
- return;
- vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode;
-}
-
-
-irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis)
-{
- double c = cos(angle);
- double s = sin(angle);
- double t = 1.0 - c;
-
- double tx = t * axis.X;
- double ty = t * axis.Y;
- double tz = t * axis.Z;
- double sx = s * axis.X;
- double sy = s * axis.Y;
- double sz = s * axis.Z;
-
- M[0] = tx * axis.X + c;
- M[1] = tx * axis.Y + sz;
- M[2] = tx * axis.Z - sy;
-
- M[4] = ty * axis.X - sz;
- M[5] = ty * axis.Y + c;
- M[6] = ty * axis.Z + sx;
-
- M[8] = tz * axis.X + sy;
- M[9] = tz * axis.Y - sx;
- M[10] = tz * axis.Z + c;
- return M;
-}
-
-
-v3f transposeMatrix(irr::core::matrix4 M, v3f v)
-{
- v3f translated;
- double x = M[0] * v.X + M[4] * v.Y + M[8] * v.Z +M[12];
- double y = M[1] * v.X + M[5] * v.Y + M[9] * v.Z +M[13];
- double z = M[2] * v.X + M[6] * v.Y + M[10] * v.Z +M[14];
- translated.X = x;
- translated.Y = y;
- translated.Z = z;
- return translated;
-}
-
-
-void make_jungletree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed)
-{
- /*
- NOTE: Tree-placing code is currently duplicated in the engine
- and in games that have saplings; both are deprecated but not
- replaced yet
- */
- content_t c_tree = ndef->getId("mapgen_jungletree");
- content_t c_leaves = ndef->getId("mapgen_jungleleaves");
- if (c_tree == CONTENT_IGNORE)
- c_tree = ndef->getId("mapgen_tree");
- if (c_leaves == CONTENT_IGNORE)
- c_leaves = ndef->getId("mapgen_leaves");
-
- MapNode treenode(c_tree);
- MapNode leavesnode(c_leaves);
-
- PseudoRandom pr(seed);
- for (s16 x= -1; x <= 1; x++)
- for (s16 z= -1; z <= 1; z++) {
- if (pr.range(0, 2) == 0)
- continue;
- v3s16 p1 = p0 + v3s16(x, 0, z);
- v3s16 p2 = p0 + v3s16(x, -1, z);
- u32 vi1 = vmanip.m_area.index(p1);
- u32 vi2 = vmanip.m_area.index(p2);
-
- if (vmanip.m_area.contains(p2) &&
- vmanip.m_data[vi2].getContent() == CONTENT_AIR)
- vmanip.m_data[vi2] = treenode;
- else if (vmanip.m_area.contains(p1) &&
- vmanip.m_data[vi1].getContent() == CONTENT_AIR)
- vmanip.m_data[vi1] = treenode;
- }
- vmanip.m_data[vmanip.m_area.index(p0)] = treenode;
-
- s16 trunk_h = pr.range(8, 12);
- v3s16 p1 = p0;
- for (s16 ii = 0; ii < trunk_h; ii++) {
- if (vmanip.m_area.contains(p1)) {
- u32 vi = vmanip.m_area.index(p1);
- vmanip.m_data[vi] = treenode;
- }
- p1.Y++;
- }
-
- // p1 is now the last piece of the trunk
- p1.Y -= 1;
-
- VoxelArea leaves_a(v3s16(-3, -2, -3), v3s16(3, 2, 3));
- //SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]);
- Buffer<u8> leaves_d(leaves_a.getVolume());
- for (s32 i = 0; i < leaves_a.getVolume(); i++)
- leaves_d[i] = 0;
-
- // Force leaves at near the end of the trunk
- s16 d = 1;
- for (s16 z = -d; z <= d; z++)
- for (s16 y = -d; y <= d; y++)
- for (s16 x = -d; x <= d; x++) {
- leaves_d[leaves_a.index(v3s16(x,y,z))] = 1;
- }
-
- // Add leaves randomly
- for (u32 iii = 0; iii < 30; iii++) {
- v3s16 p(
- pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d),
- pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d),
- pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d)
- );
-
- for (s16 z = 0; z <= d; z++)
- for (s16 y = 0; y <= d; y++)
- for (s16 x = 0; x <= d; x++) {
- leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1;
- }
- }
-
- // Blit leaves to vmanip
- for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
- for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
- v3s16 pmin(leaves_a.MinEdge.X, y, z);
- u32 i = leaves_a.index(pmin);
- u32 vi = vmanip.m_area.index(pmin + p1);
- for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
- v3s16 p(x, y, z);
- if (vmanip.m_area.contains(p + p1) &&
- (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
- vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) {
- if (leaves_d[i] == 1)
- vmanip.m_data[vi] = leavesnode;
- }
- vi++;
- i++;
- }
- }
-}
-
-
-void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed)
-{
- /*
- NOTE: Tree-placing code is currently duplicated in the engine
- and in games that have saplings; both are deprecated but not
- replaced yet
- */
- content_t c_tree = ndef->getId("mapgen_pine_tree");
- content_t c_leaves = ndef->getId("mapgen_pine_needles");
- content_t c_snow = ndef->getId("mapgen_snow");
- if (c_tree == CONTENT_IGNORE)
- c_tree = ndef->getId("mapgen_tree");
- if (c_leaves == CONTENT_IGNORE)
- c_leaves = ndef->getId("mapgen_leaves");
- if (c_snow == CONTENT_IGNORE)
- c_snow = CONTENT_AIR;
-
- MapNode treenode(c_tree);
- MapNode leavesnode(c_leaves);
- MapNode snownode(c_snow);
-
- PseudoRandom pr(seed);
- u16 trunk_h = pr.range(9, 13);
- v3s16 p1 = p0;
- for (u16 ii = 0; ii < trunk_h; ii++) {
- if (vmanip.m_area.contains(p1)) {
- u32 vi = vmanip.m_area.index(p1);
- vmanip.m_data[vi] = treenode;
- }
- p1.Y++;
- }
-
- // Make p1 the top node of the trunk
- p1.Y -= 1;
-
- VoxelArea leaves_a(v3s16(-3, -6, -3), v3s16(3, 3, 3));
- Buffer<u8> leaves_d(leaves_a.getVolume());
- for (s32 i = 0; i < leaves_a.getVolume(); i++)
- leaves_d[i] = 0;
-
- // Upper branches
- u16 dev = 3;
- for (s16 yy = -1; yy <= 1; yy++) {
- for (s16 zz = -dev; zz <= dev; zz++) {
- u32 i = leaves_a.index(v3s16(-dev, yy, zz));
- u32 ia = leaves_a.index(v3s16(-dev, yy+1, zz));
- for (s16 xx = -dev; xx <= dev; xx++) {
- if (pr.range(0, 20) <= 19 - dev) {
- leaves_d[i] = 1;
- leaves_d[ia] = 2;
- }
- i++;
- ia++;
- }
- }
- dev--;
- }
-
- // Centre top nodes
- leaves_d[leaves_a.index(v3s16(0, 1, 0))] = 1;
- leaves_d[leaves_a.index(v3s16(0, 2, 0))] = 1;
- leaves_d[leaves_a.index(v3s16(0, 3, 0))] = 2;
-
- // Lower branches
- s16 my = -6;
- for (u32 iii = 0; iii < 20; iii++) {
- s16 xi = pr.range(-3, 2);
- s16 yy = pr.range(-6, -5);
- s16 zi = pr.range(-3, 2);
- if (yy > my)
- my = yy;
- for (s16 zz = zi; zz <= zi + 1; zz++) {
- u32 i = leaves_a.index(v3s16(xi, yy, zz));
- u32 ia = leaves_a.index(v3s16(xi, yy + 1, zz));
- for (s32 xx = xi; xx <= xi + 1; xx++) {
- leaves_d[i] = 1;
- if (leaves_d[ia] == 0)
- leaves_d[ia] = 2;
- i++;
- ia++;
- }
- }
- }
-
- dev = 2;
- for (s16 yy = my + 1; yy <= my + 2; yy++) {
- for (s16 zz = -dev; zz <= dev; zz++) {
- u32 i = leaves_a.index(v3s16(-dev, yy, zz));
- u32 ia = leaves_a.index(v3s16(-dev, yy + 1, zz));
- for (s16 xx = -dev; xx <= dev; xx++) {
- if (pr.range(0, 20) <= 19 - dev) {
- leaves_d[i] = 1;
- leaves_d[ia] = 2;
- }
- i++;
- ia++;
- }
- }
- dev--;
- }
-
- // Blit leaves to vmanip
- for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++)
- for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) {
- v3s16 pmin(leaves_a.MinEdge.X, y, z);
- u32 i = leaves_a.index(pmin);
- u32 vi = vmanip.m_area.index(pmin + p1);
- for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) {
- v3s16 p(x, y, z);
- if (vmanip.m_area.contains(p + p1) &&
- (vmanip.m_data[vi].getContent() == CONTENT_AIR ||
- vmanip.m_data[vi].getContent() == CONTENT_IGNORE ||
- vmanip.m_data[vi] == snownode)) {
- if (leaves_d[i] == 1)
- vmanip.m_data[vi] = leavesnode;
- else if (leaves_d[i] == 2)
- vmanip.m_data[vi] = snownode;
- }
- vi++;
- i++;
- }
- }
-}
-
-}; // namespace treegen
+++ /dev/null
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>,
- 2012-2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <matrix4.h>
-#include "noise.h"
-
-class MMVManip;
-class INodeDefManager;
-class ServerEnvironment;
-
-
-namespace treegen {
-
- enum error {
- SUCCESS,
- UNBALANCED_BRACKETS
- };
-
- struct TreeDef {
- std::string initial_axiom;
- std::string rules_a;
- std::string rules_b;
- std::string rules_c;
- std::string rules_d;
-
- MapNode trunknode;
- MapNode leavesnode;
- MapNode leaves2node;
-
- int leaves2_chance;
- int angle;
- int iterations;
- int iterations_random_level;
- std::string trunk_type;
- bool thin_branches;
- MapNode fruitnode;
- int fruit_chance;
- s32 seed;
- bool explicit_seed;
- };
-
- // Add default tree
- void make_tree(MMVManip &vmanip, v3s16 p0,
- bool is_apple_tree, INodeDefManager *ndef, s32 seed);
- // Add jungle tree
- void make_jungletree(MMVManip &vmanip, v3s16 p0,
- INodeDefManager *ndef, s32 seed);
- // Add pine tree
- void make_pine_tree(MMVManip &vmanip, v3s16 p0,
- INodeDefManager *ndef, s32 seed);
-
- // Add L-Systems tree (used by engine)
- treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef,
- TreeDef tree_definition);
- // Spawn L-systems tree from LUA
- treegen::error spawn_ltree (ServerEnvironment *env, v3s16 p0, INodeDefManager *ndef,
- const TreeDef &tree_definition);
-
- // L-System tree gen helper functions
- void tree_node_placement(MMVManip &vmanip, v3f p0,
- MapNode node);
- void tree_trunk_placement(MMVManip &vmanip, v3f p0,
- TreeDef &tree_definition);
- void tree_leaves_placement(MMVManip &vmanip, v3f p0,
- PseudoRandom ps, TreeDef &tree_definition);
- void tree_single_leaves_placement(MMVManip &vmanip, v3f p0,
- PseudoRandom ps, TreeDef &tree_definition);
- void tree_fruit_placement(MMVManip &vmanip, v3f p0,
- TreeDef &tree_definition);
- irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis);
-
- v3f transposeMatrix(irr::core::matrix4 M ,v3f v);
-
-}; // namespace treegen
#include "noise.h"
#include "settings.h"
-#include "mapgen_v5.h"
+#include "mapgen/mapgen_v5.h"
#include "util/sha1.h"
#include "map_settings_manager.h"
#include "test.h"
-#include "mg_schematic.h"
+#include "mapgen/mg_schematic.h"
#include "gamedef.h"
#include "nodedef.h"
#pragma once
-#include "../irrlichttypes.h"
-#include "../exceptions.h"
-#include "../threading/mutex_auto_lock.h"
-#include "../threading/semaphore.h"
+#include "irrlichttypes.h"
+#include "exceptions.h"
+#include "threading/mutex_auto_lock.h"
+#include "threading/semaphore.h"
#include <list>
#include <vector>
#include <map>
#pragma once
-#include "../irrlichttypes.h"
-#include "../irr_v3d.h"
+#include "irrlichttypes.h"
+#include "irr_v3d.h"
extern const v3s16 g_6dirs[6];
#include "numeric.h"
#include "log.h"
-#include "../constants.h" // BS, MAP_BLOCKSIZE
-#include "../noise.h" // PseudoRandom, PcgRandom
-#include "../threading/mutex_auto_lock.h"
+#include "constants.h" // BS, MAP_BLOCKSIZE
+#include "noise.h" // PseudoRandom, PcgRandom
+#include "threading/mutex_auto_lock.h"
#include <cstring>
#pragma once
#include "basic_macros.h"
-#include "../irrlichttypes.h"
-#include "../irr_v2d.h"
-#include "../irr_v3d.h"
-#include "../irr_aabb3d.h"
+#include "irrlichttypes.h"
+#include "irr_v2d.h"
+#include "irr_v3d.h"
+#include "irr_aabb3d.h"
#define rangelim(d, min, max) ((d) < (min) ? (min) : ((d) > (max) ? (max) : (d)))
#define myfloor(x) ((x) < 0.0 ? (int)(x) - 1 : (int)(x))
#include "pointedthing.h"
#include "serialize.h"
-#include "../exceptions.h"
+#include "exceptions.h"
#include <sstream>
PointedThing::PointedThing(const v3s16 &under, const v3s16 &above,
#pragma once
-#include "../irrlichttypes.h"
-#include "../irr_v3d.h"
+#include "irrlichttypes.h"
+#include "irr_v3d.h"
#include <iostream>
#include <string>
#pragma once
-#include "../irrlichttypes.h"
-#include "../debug.h" // For assert()
+#include "irrlichttypes.h"
+#include "debug.h" // For assert()
#include <cstring>
template <typename T>
#include "pointer.h"
#include "porting.h"
#include "util/string.h"
-#include "../exceptions.h"
-#include "../irrlichttypes.h"
+#include "exceptions.h"
+#include "irrlichttypes.h"
#include <sstream>
#include <iomanip>
#pragma once
-#include "../irrlichttypes_bloated.h"
-#include "../exceptions.h" // for SerializationError
-#include "../debug.h" // for assert
+#include "irrlichttypes_bloated.h"
+#include "exceptions.h" // for SerializationError
+#include "debug.h" // for assert
#include "config.h"
#if HAVE_ENDIAN_H
#include "log.h"
#include "hex.h"
-#include "../porting.h"
-#include "../translation.h"
+#include "porting.h"
+#include "translation.h"
#include <algorithm>
#include <sstream>
#pragma once
-#include "../irrlichttypes.h"
-#include "../threading/thread.h"
-#include "../threading/mutex_auto_lock.h"
+#include "irrlichttypes.h"
+#include "threading/thread.h"
+#include "threading/mutex_auto_lock.h"
#include "porting.h"
#include "log.h"
#include "container.h"
#include "timetaker.h"
-#include "../porting.h"
-#include "../log.h"
+#include "porting.h"
+#include "log.h"
#include <ostream>
TimeTaker::TimeTaker(const std::string &name, u64 *result, TimePrecision prec)
#pragma once
-#include "../irrlichttypes.h"
-#include "../gettime.h"
+#include "irrlichttypes.h"
+#include "gettime.h"
/*
TimeTaker
src/ban.cpp
src/camera.cpp
src/camera.h
-src/cavegen.cpp
-src/cavegen.h
+src/mapgen/cavegen.cpp
+src/mapgen/cavegen.h
src/chat.cpp
src/chat.h
src/chat_interface.h
src/convert_json.h
src/craftdef.cpp
src/craftdef.h
-src/database.cpp
-src/database-dummy.cpp
-src/database-files.cpp
-src/database-leveldb.cpp
-src/database-postgresql.cpp
-src/database-postgresql.h
-src/database-redis.cpp
-src/database-sqlite3.cpp
-src/database-sqlite3.h
+src/database/database.cpp
+src/database/database-dummy.cpp
+src/database/database-files.cpp
+src/database/database-leveldb.cpp
+src/database/database-postgresql.cpp
+src/database/database-postgresql.h
+src/database/database-redis.cpp
+src/database/database-sqlite3.cpp
+src/database/database-sqlite3.h
src/daynightratio.h
src/debug.cpp
src/debug.h
src/defaultsettings.cpp
-src/dungeongen.cpp
-src/dungeongen.h
+src/mapgen/dungeongen.cpp
+src/mapgen/dungeongen.h
src/emerge.cpp
src/emerge.h
src/environment.cpp
src/genericobject.h
src/gettext.cpp
src/gettext.h
-src/guiChatConsole.cpp
-src/guiChatConsole.h
-src/guiEditBoxWithScrollbar.cpp
-src/guiEditBoxWithScrollbar.h
-src/guiEngine.cpp
-src/guiEngine.h
-src/guiPathSelectMenu.cpp
-src/guiPathSelectMenu.h
-src/guiFormSpecMenu.cpp
-src/guiFormSpecMenu.h
-src/guiKeyChangeMenu.cpp
-src/guiMainMenu.h
-src/guiPasswordChange.cpp
+src/gui/guiChatConsole.cpp
+src/gui/guiChatConsole.h
+src/gui/guiEditBoxWithScrollbar.cpp
+src/gui/guiEditBoxWithScrollbar.h
+src/gui/guiEngine.cpp
+src/gui/guiEngine.h
+src/gui/guiPathSelectMenu.cpp
+src/gui/guiPathSelectMenu.h
+src/gui/guiFormSpecMenu.cpp
+src/gui/guiFormSpecMenu.h
+src/gui/guiKeyChangeMenu.cpp
+src/gui/guiMainMenu.h
+src/gui/guiPasswordChange.cpp
src/guiscalingfilter.cpp
src/guiscalingfilter.h
-src/guiTable.cpp
-src/guiTable.h
-src/guiVolumeChange.cpp
-src/guiVolumeChange.h
+src/gui/guiTable.cpp
+src/gui/guiTable.h
+src/gui/guiVolumeChange.cpp
+src/gui/guiVolumeChange.h
src/httpfetch.cpp
src/hud.cpp
src/hud.h
src/imagefilters.cpp
src/imagefilters.h
-src/intlGUIEditBox.cpp
-src/intlGUIEditBox.h
+src/gui/intlGUIEditBox.cpp
+src/gui/intlGUIEditBox.h
src/inventory.cpp
src/inventory.h
src/inventorymanager.cpp
src/log.cpp
src/log.h
src/main.cpp
-src/mainmenumanager.h
+src/gui/mainmenumanager.h
src/mapblock.cpp
src/mapblock.h
src/mapblock_mesh.cpp
src/mapblock_mesh.h
src/map.cpp
src/map.h
-src/mapgen.cpp
-src/mapgen.h
-src/mapgen_carpathian.cpp
-src/mapgen_carpathian.h
-src/mapgen_flat.cpp
-src/mapgen_flat.h
-src/mapgen_fractal.cpp
-src/mapgen_fractal.h
-src/mapgen_singlenode.cpp
-src/mapgen_singlenode.h
-src/mapgen_v5.cpp
-src/mapgen_v5.h
-src/mapgen_v6.cpp
-src/mapgen_v6.h
-src/mapgen_v7.cpp
-src/mapgen_v7.h
-src/mapgen_valleys.cpp
-src/mapgen_valleys.h
+src/mapgen/mapgen.cpp
+src/mapgen/mapgen.h
+src/mapgen/mapgen_carpathian.cpp
+src/mapgen/mapgen_carpathian.h
+src/mapgen/mapgen_flat.cpp
+src/mapgen/mapgen_flat.h
+src/mapgen/mapgen_fractal.cpp
+src/mapgen/mapgen_fractal.h
+src/mapgen/mapgen_singlenode.cpp
+src/mapgen/mapgen_singlenode.h
+src/mapgen/mapgen_v5.cpp
+src/mapgen/mapgen_v5.h
+src/mapgen/mapgen_v6.cpp
+src/mapgen/mapgen_v6.h
+src/mapgen/mapgen_v7.cpp
+src/mapgen/mapgen_v7.h
+src/mapgen/mapgen_valleys.cpp
+src/mapgen/mapgen_valleys.h
src/mapnode.cpp
src/mapnode.h
src/mapsector.cpp
src/mesh.h
src/mesh_generator_thread.cpp
src/metadata.h
-src/mg_biome.cpp
-src/mg_biome.h
-src/mg_decoration.cpp
-src/mg_decoration.h
-src/mg_ore.cpp
-src/mg_ore.h
-src/mg_schematic.cpp
-src/mg_schematic.h
+src/mapgen/mg_biome.cpp
+src/mapgen/mg_biome.h
+src/mapgen/mg_decoration.cpp
+src/mapgen/mg_decoration.h
+src/mapgen/mg_ore.cpp
+src/mapgen/mg_ore.h
+src/mapgen/mg_schematic.cpp
+src/mapgen/mg_schematic.h
src/minimap.cpp
src/minimap.h
-src/modalMenu.h
+src/gui/modalMenu.h
src/mods.cpp
src/mods.h
src/network/clientopcodes.cpp
src/tileanimation.cpp
src/tool.cpp
src/tool.h
-src/touchscreengui.cpp
+src/gui/touchscreengui.cpp
src/translation.cpp
-src/treegen.cpp
-src/treegen.h
+src/mapgen/treegen.cpp
+src/mapgen/treegen.h
src/unittest/test.cpp
src/unittest/test.h
src/unittest/test_areastore.cpp