aboutsummaryrefslogtreecommitdiff
path: root/src/craftdef.h
blob: 46ee2164e6057f9bc7fffe527ea548ff92fc9156 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/*
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 <iostream>
#include <vector>
#include <utility>
#include "gamedef.h"
#include "inventory.h"

/*
	Crafting methods.

	The crafting method depends on the inventory list
	that the crafting input comes from.
*/
enum CraftMethod
{
	// Crafting grid
	CRAFT_METHOD_NORMAL,
	// Cooking something in a furnace
	CRAFT_METHOD_COOKING,
	// Using something as fuel for a furnace
	CRAFT_METHOD_FUEL,
};

/*
	The type a hash can be. The earlier a type is mentioned in this enum,
	the earlier it is tried at crafting, and the less likely is a collision.
	Changing order causes changes in behaviour, so know what you do.
 */
enum CraftHashType
{
	// Hashes the normalized names of the recipe's elements.
	// Only recipes without group usage can be found here,
	// because groups can't be guessed efficiently.
	CRAFT_HASH_TYPE_ITEM_NAMES,

	// Counts the non-empty slots.
	CRAFT_HASH_TYPE_COUNT,

	// This layer both spares an extra variable, and helps to retain (albeit rarely used) functionality. Maps to 0.
	// Before hashes are "initialized", all hashes reside here, after initialisation, none are.
	CRAFT_HASH_TYPE_UNHASHED

};
const int craft_hash_type_max = (int) CRAFT_HASH_TYPE_UNHASHED;

/*
	Input: The contents of the crafting slots, arranged in matrix form
*/
struct CraftInput
{
	CraftMethod method = CRAFT_METHOD_NORMAL;
	unsigned int width = 0;
	std::vector<ItemStack> items;

	CraftInput() = default;

	CraftInput(CraftMethod method_, unsigned int width_,
			const std::vector<ItemStack> &items_):
		method(method_), width(width_), items(items_)
	{}

	std::string dump() const;
};

/*
	Output: Result of crafting operation
*/
struct CraftOutput
{
	// Used for normal crafting and cooking, itemstring
	std::string item = "";
	// Used for cooking (cook time) and fuel (burn time), seconds
	float time = 0.0f;

	CraftOutput() = default;

	CraftOutput(const std::string &item_, float time_):
		item(item_), time(time_)
	{}
	std::string dump() const;
};

/*
	A list of replacements. A replacement indicates that a specific
	input item should not be deleted (when crafting) but replaced with
	a different item. Each replacements is a pair (itemstring to remove,
	itemstring to replace with)

	Example: If ("bucket:bucket_water", "bucket:bucket_empty") is a
	replacement pair, the crafting input slot that contained a water
	bucket will contain an empty bucket after crafting.

	Note: replacements only work correctly when stack_max of the item
	to be replaced is 1. It is up to the mod writer to ensure this.
*/
struct CraftReplacements
{
	// List of replacements
	std::vector<std::pair<std::string, std::string> > pairs;

	CraftReplacements() = default;
	CraftReplacements(const std::vector<std::pair<std::string, std::string> > &pairs_):
		pairs(pairs_)
	{}
	std::string dump() const;
};

/*
	Crafting definition base class
*/
class CraftDefinition
{
public:
	CraftDefinition() = default;
	virtual ~CraftDefinition() = default;

	// Returns type of crafting definition
	virtual std::string getName() const=0;

	// Checks whether the recipe is applicable
	virtual bool check(const CraftInput &input, IGameDef *gamedef) const=0;
	// Returns the output structure, meaning depends on crafting method
	// The implementation can assume that check(input) returns true
	virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const=0;
	// the inverse of the above
	virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const=0;
	// Decreases count of every input item
	virtual void decrementInput(CraftInput &input,
		std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const=0;

	virtual CraftHashType getHashType() const = 0;
	virtual u64 getHash(CraftHashType type) const = 0;

	// to be called after all mods are loaded, so that we catch all aliases
	virtual void initHash(IGameDef *gamedef) = 0;

	virtual std::string dump() const=0;
};

/*
	A plain-jane (shaped) crafting definition

	Supported crafting method: CRAFT_METHOD_NORMAL.
	Requires the input items to be arranged exactly like in the recipe.
*/
class CraftDefinitionShaped: public CraftDefinition
{
public:
	CraftDefinitionShaped() = delete;

	CraftDefinitionShaped(
			const std::string &output_,
			unsigned int width_,
			const std::vector<std::string> &recipe_,
			const CraftReplacements &replacements_):
		output(output_), width(width_), recipe(recipe_),
		replacements(replacements_)
	{}
	virtual ~CraftDefinitionShaped() = default;

	virtual std::string getName() const;
	virtual bool check(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const;
	virtual void decrementInput(CraftInput &input,
		std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;

	virtual CraftHashType getHashType() const;
	virtual u64 getHash(CraftHashType type) const;

	virtual void initHash(IGameDef *gamedef);

	virtual std::string dump() const;

private:
	// Output itemstring
	std::string output = "";
	// Width of recipe
	unsigned int width = 1;
	// Recipe matrix (itemstrings)
	std::vector<std::string> recipe;
	// Recipe matrix (item names)
	std::vector<std::string> recipe_names;
	// bool indicating if initHash has been called already
	bool hash_inited = false;
	// Replacement items for decrementInput()
	CraftReplacements replacements;
};

/*
	A shapeless crafting definition
	Supported crafting method: CRAFT_METHOD_NORMAL.
	Input items can arranged in any way.
*/
class CraftDefinitionShapeless: public CraftDefinition
{
public:
	CraftDefinitionShapeless() = delete;
	CraftDefinitionShapeless(
			const std::string &output_,
			const std::vector<std::string> &recipe_,
			const CraftReplacements &replacements_):
		output(output_), recipe(recipe_), replacements(replacements_)
	{}
	virtual ~CraftDefinitionShapeless() = default;

	virtual std::string getName() const;
	virtual bool check(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const;
	virtual void decrementInput(CraftInput &input,
		std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;

	virtual CraftHashType getHashType() const;
	virtual u64 getHash(CraftHashType type) const;

	virtual void initHash(IGameDef *gamedef);

	virtual std::string dump() const;

private:
	// Output itemstring
	std::string output;
	// Recipe list (itemstrings)
	std::vector<std::string> recipe;
	// Recipe list (item names)
	std::vector<std::string> recipe_names;
	// bool indicating if initHash has been called already
	bool hash_inited = false;
	// Replacement items for decrementInput()
	CraftReplacements replacements;
};

/*
	Tool repair crafting definition
	Supported crafting method: CRAFT_METHOD_NORMAL.
	Put two damaged tools into the crafting grid, get one tool back.
	There should only be one crafting definition of this type.
*/
class CraftDefinitionToolRepair: public CraftDefinition
{
public:
	CraftDefinitionToolRepair() = delete;
	CraftDefinitionToolRepair(float additional_wear_):
		additional_wear(additional_wear_)
	{}
	virtual ~CraftDefinitionToolRepair() = default;

	virtual std::string getName() const;
	virtual bool check(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const;
	virtual void decrementInput(CraftInput &input,
		std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;

	virtual CraftHashType getHashType() const { return CRAFT_HASH_TYPE_COUNT; }
	virtual u64 getHash(CraftHashType type) const { return 2; }

	virtual void initHash(IGameDef *gamedef) {}

	virtual std::string dump() const;

private:
	// This is a constant that is added to the wear of the result.
	// May be positive or negative, allowed range [-1,1].
	// 1 = new tool is completely broken
	// 0 = simply add remaining uses of both input tools
	// -1 = new tool is completely pristine
	float additional_wear = 0.0f;
};

/*
	A cooking (in furnace) definition
	Supported crafting method: CRAFT_METHOD_COOKING.
*/
class CraftDefinitionCooking: public CraftDefinition
{
public:
	CraftDefinitionCooking() = delete;
	CraftDefinitionCooking(
			const std::string &output_,
			const std::string &recipe_,
			float cooktime_,
			const CraftReplacements &replacements_):
		output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_)
	{}
	virtual ~CraftDefinitionCooking() = default;

	virtual std::string getName() const;
	virtual bool check(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const;
	virtual void decrementInput(CraftInput &input,
		std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;

	virtual CraftHashType getHashType() const;
	virtual u64 getHash(CraftHashType type) const;

	virtual void initHash(IGameDef *gamedef);

	virtual std::string dump() const;

private:
	// Output itemstring
	std::string output;
	// Recipe itemstring
	std::string recipe;
	// Recipe item name
	std::string recipe_name;
	// bool indicating if initHash has been called already
	bool hash_inited = false;
	// Time in seconds
	float cooktime;
	// Replacement items for decrementInput()
	CraftReplacements replacements;
};

/*
	A fuel (for furnace) definition
	Supported crafting method: CRAFT_METHOD_FUEL.
*/
class CraftDefinitionFuel: public CraftDefinition
{
public:
	CraftDefinitionFuel() = delete;
	CraftDefinitionFuel(const std::string &recipe_,
			float burntime_,
			const CraftReplacements &replacements_):
		recipe(recipe_), burntime(burntime_), replacements(replacements_)
	{}
	virtual ~CraftDefinitionFuel() = default;

	virtual std::string getName() const;
	virtual bool check(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const;
	virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const;
	virtual void decrementInput(CraftInput &input,
		std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;

	virtual CraftHashType getHashType() const;
	virtual u64 getHash(CraftHashType type) const;

	virtual void initHash(IGameDef *gamedef);

	virtual std::string dump() const;

private:
	// Recipe itemstring
	std::string recipe;
	// Recipe item name
	std::string recipe_name;
	// bool indicating if initHash has been called already
	bool hash_inited = false;
	// Time in seconds
	float burntime;
	// Replacement items for decrementInput()
	CraftReplacements replacements;
};

/*
	Crafting definition manager
*/
class ICraftDefManager
{
public:
	ICraftDefManager() = default;
	virtual ~ICraftDefManager() = default;

	// The main crafting function
	virtual bool getCraftResult(CraftInput &input, CraftOutput &output,
			std::vector<ItemStack> &output_replacements,
			bool decrementInput, IGameDef *gamedef) const=0;
	virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
			IGameDef *gamedef, unsigned limit=0) const=0;

	// Print crafting recipes for debugging
	virtual std::string dump() const=0;
};

class IWritableCraftDefManager : public ICraftDefManager
{
public:
	IWritableCraftDefManager() = default;
	virtual ~IWritableCraftDefManager() = default;

	// The main crafting function
	virtual bool getCraftResult(CraftInput &input, CraftOutput &output,
			std::vector<ItemStack> &output_replacements,
			bool decrementInput, IGameDef *gamedef) const=0;
	virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
			IGameDef *gamedef, unsigned limit=0) const=0;

	virtual bool clearCraftRecipesByOutput(const CraftOutput &output, IGameDef *gamedef) = 0;
	virtual bool clearCraftRecipesByInput(CraftMethod craft_method,
			unsigned int craft_grid_width, const std::vector<std::string> &recipe, IGameDef *gamedef) = 0;

	// Print crafting recipes for debugging
	virtual std::string dump() const=0;

	// Add a crafting definition.
	// After calling this, the pointer belongs to the manager.
	virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef) = 0;

	// Delete all crafting definitions
	virtual void clear()=0;

	// To be called after all mods are loaded, so that we catch all aliases
	virtual void initHashes(IGameDef *gamedef) = 0;
};

IWritableCraftDefManager* createCraftDefManager();
an>), 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->getS16NoEx("mgv6_dungeon_ymin", dungeon_ymin); settings->getS16NoEx("mgv6_dungeon_ymax", dungeon_ymax); 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->setS16("mgv6_dungeon_ymin", dungeon_ymin); settings->setS16("mgv6_dungeon_ymax", dungeon_ymax); 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; VoxelArea::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 && full_node_min.Y >= dungeon_ymin && full_node_max.Y <= dungeon_ymax) { DungeonParams dp; dp.seed = seed; 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; } } VoxelArea::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++; VoxelArea::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; VoxelArea::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; VoxelArea::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; } } static const 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; VoxelArea::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 VoxelArea::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 VoxelArea::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 { VoxelArea::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 VoxelArea::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; VoxelArea::add_y(em, above_remove_index, 1); } // Mud placed may have partially-buried a stacked decoration, search // above and remove. VoxelArea::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; VoxelArea::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) { VoxelArea::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; VoxelArea::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; VoxelArea::add_y(em, i, -1); vm->m_data[i] = n_dirt_with_snow; } else if (c == c_stone && surface_y < node_max.Y) { VoxelArea::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); } }