1
0
Fork 0
cuberite-2a/src/World.cpp

4218 lines
105 KiB
C++
Raw Normal View History

2014-08-19 20:14:37 +00:00
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "World.h"
#include "ClientHandle.h"
#include "Server.h"
#include "Root.h"
2014-10-23 13:15:10 +00:00
#include "IniFile.h"
#include "Generating/ChunkDesc.h"
#include "SetChunkData.h"
#include "DeadlockDetect.h"
#include "LineBlockTracer.h"
2017-08-25 12:43:18 +00:00
#include "UUID.h"
2014-02-13 19:36:24 +00:00
2014-02-20 14:38:37 +00:00
// Serializers
2014-01-22 13:49:21 +00:00
#include "WorldStorage/ScoreboardSerializer.h"
// Entities (except mobs):
#include "Entities/ExpOrb.h"
#include "Entities/FallingBlock.h"
2014-01-12 13:33:32 +00:00
#include "Entities/Minecart.h"
#include "Entities/Pickup.h"
#include "Entities/Player.h"
#include "Entities/TNTEntity.h"
2014-01-23 12:57:04 +00:00
#include "BlockEntities/CommandBlockEntity.h"
2014-07-30 20:19:51 +00:00
#include "BlockEntities/BeaconEntity.h"
2014-01-23 12:57:04 +00:00
// Simulators:
#include "Simulator/FloodyFluidSimulator.h"
#include "Simulator/FluidSimulator.h"
#include "Simulator/FireSimulator.h"
#include "Simulator/NoopFluidSimulator.h"
#include "Simulator/NoopRedstoneSimulator.h"
#include "Simulator/IncrementalRedstoneSimulator/IncrementalRedstoneSimulator.h"
#include "Simulator/SandSimulator.h"
#include "Simulator/VanillaFluidSimulator.h"
#include "Simulator/VaporizeFluidSimulator.h"
// Mobs:
#include "Mobs/IncludeAllMonsters.h"
#include "MobCensus.h"
#include "MobSpawner.h"
#include "Generating/Trees.h"
#include "Bindings/PluginManager.h"
#include "Blocks/BlockHandler.h"
#ifndef _WIN32
#include <stdlib.h>
#endif
#include "Broadcaster.h"
#include "SpawnPrepare.h"
#include "FastRandom.h"
const int TIME_SUNSET = 12000;
const int TIME_NIGHT_START = 13187;
const int TIME_NIGHT_END = 22812;
const int TIME_SUNRISE = 23999;
const int TIME_SPAWN_DIVISOR = 148;
////////////////////////////////////////////////////////////////////////////////
// cWorld::cLock:
cWorld::cLock::cLock(cWorld & a_World) :
super(&(a_World.m_ChunkMap->GetCS()))
{
}
////////////////////////////////////////////////////////////////////////////////
// cWorld::cTickThread:
cWorld::cTickThread::cTickThread(cWorld & a_World) :
super(Printf("WorldTickThread: %s", a_World.GetName().c_str())),
m_World(a_World)
{
}
void cWorld::cTickThread::Execute(void)
{
auto LastTime = std::chrono::steady_clock::now();
auto TickTime = std::chrono::duration_cast<std::chrono::milliseconds>(cTickTime(1));
while (!m_ShouldTerminate)
{
auto NowTime = std::chrono::steady_clock::now();
auto WaitTime = std::chrono::duration_cast<std::chrono::milliseconds>(NowTime - LastTime);
m_World.Tick(WaitTime, TickTime);
TickTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - NowTime);
2016-01-12 13:04:59 +00:00
if (TickTime < cTickTime(1))
{
// Stretch tick time until it's at least 1 tick
std::this_thread::sleep_for(cTickTime(1) - TickTime);
}
LastTime = NowTime;
}
}
////////////////////////////////////////////////////////////////////////////////
// cWorld:
cWorld::cWorld(const AString & a_WorldName, eDimension a_Dimension, const AString & a_LinkedOverworldName) :
m_WorldName(a_WorldName),
m_LinkedOverworldName(a_LinkedOverworldName),
m_IniFileName(m_WorldName + "/world.ini"),
m_StorageSchema("Default"),
#ifdef __arm__
2014-01-25 21:29:27 +00:00
m_StorageCompressionFactor(0),
#else
2014-01-17 19:01:14 +00:00
m_StorageCompressionFactor(6),
#endif
2014-06-12 17:56:48 +00:00
m_Dimension(a_Dimension),
m_IsSpawnExplicitlySet(false),
2015-01-23 10:51:07 +00:00
m_SpawnX(0),
m_SpawnY(0),
m_SpawnZ(0),
m_BroadcastDeathMessages(true),
m_BroadcastAchievementMessages(true),
2014-08-10 14:46:03 +00:00
m_IsDaylightCycleEnabled(true),
m_WorldAge(0),
m_TimeOfDay(0),
m_LastTimeUpdate(0),
m_LastChunkCheck(0),
m_LastSave(0),
m_SkyDarkness(0),
2014-08-21 19:53:25 +00:00
m_GameMode(gmNotSet),
m_bEnabledPVP(false),
m_IsDeepSnowEnabled(false),
m_ShouldLavaSpawnFire(true),
m_VillagersShouldHarvestCrops(true),
2014-10-10 14:33:19 +00:00
m_SimulatorManager(),
m_SandSimulator(),
2014-10-20 20:55:07 +00:00
m_WaterSimulator(nullptr),
2014-10-10 14:33:19 +00:00
m_LavaSimulator(nullptr),
m_FireSimulator(),
2014-10-20 20:55:07 +00:00
m_RedstoneSimulator(nullptr),
2014-08-21 19:53:25 +00:00
m_MaxPlayers(10),
2014-10-10 14:33:19 +00:00
m_ChunkMap(),
2014-08-21 19:53:25 +00:00
m_bAnimals(true),
m_Weather(eWeather_Sunny),
m_WeatherInterval(24000), // Guaranteed 1 game-day of sunshine at server start :)
m_MaxSunnyTicks(180000), // 150 real-world minutes -+
m_MinSunnyTicks(12000), // 10 real-world minutes |
m_MaxRainTicks(24000), // 20 real-world minutes +- all values adapted from Vanilla 1.7.2
m_MinRainTicks(12000), // 10 real-world minutes |
m_MaxThunderStormTicks(15600), // 13 real-world minutes |
m_MinThunderStormTicks(3600), // 3 real-world minutes -+
2014-08-21 19:53:25 +00:00
m_MaxCactusHeight(3),
m_MaxSugarcaneHeight(4),
2017-02-14 10:13:55 +00:00
m_IsBeetrootsBonemealable(true),
2014-08-21 19:53:25 +00:00
m_IsCactusBonemealable(false),
m_IsCarrotsBonemealable(true),
m_IsCropsBonemealable(true),
m_IsGrassBonemealable(true),
m_IsMelonStemBonemealable(true),
m_IsMelonBonemealable(true),
m_IsPotatoesBonemealable(true),
m_IsPumpkinStemBonemealable(true),
m_IsPumpkinBonemealable(true),
m_IsSaplingBonemealable(true),
m_IsSugarcaneBonemealable(false),
m_IsBigFlowerBonemealable(true),
m_IsTallGrassBonemealable(true),
2014-08-21 19:53:25 +00:00
m_bCommandBlocksEnabled(true),
m_bUseChatPrefixes(false),
m_TNTShrapnelLevel(slNone),
2014-10-30 20:24:10 +00:00
m_MaxViewDistance(12),
2014-02-05 17:43:49 +00:00
m_Scoreboard(this),
2014-02-23 13:03:40 +00:00
m_MapManager(this),
m_GeneratorCallbacks(*this),
m_ChunkSender(*this),
2014-07-23 20:12:59 +00:00
m_TickThread(*this)
{
LOGD("cWorld::cWorld(\"%s\")", a_WorldName.c_str());
cFile::CreateFolder(FILE_IO_PREFIX + m_WorldName);
2014-01-22 13:49:21 +00:00
// Load the scoreboard
cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard);
Serializer.Load();
}
cWorld::~cWorld()
{
2014-10-20 20:55:07 +00:00
delete m_WaterSimulator; m_WaterSimulator = nullptr;
delete m_LavaSimulator; m_LavaSimulator = nullptr;
delete m_RedstoneSimulator; m_RedstoneSimulator = nullptr;
m_Storage.WaitForFinish();
2014-01-22 13:49:21 +00:00
// Unload the scoreboard
cScoreboardSerializer Serializer(m_WorldName, &m_Scoreboard);
Serializer.Save();
2014-02-23 13:03:40 +00:00
m_MapManager.SaveMapData();
// Explicitly destroy the chunkmap, so that it's guaranteed to be destroyed before the other internals
// This fixes crashes on stopping the server, because chunk destructor deletes entities and those access the world.
m_ChunkMap.reset();
}
void cWorld::CastThunderbolt (int a_BlockX, int a_BlockY, int a_BlockZ)
{
BroadcastThunderbolt(a_BlockX, a_BlockY, a_BlockZ);
2017-02-15 05:05:24 +00:00
BroadcastSoundEffect("entity.lightning.thunder", a_BlockX, a_BlockY, a_BlockZ, 50, 1);
}
int cWorld::GetDefaultWeatherInterval(eWeather a_Weather)
{
2017-06-13 19:35:30 +00:00
auto & Random = GetRandomProvider();
switch (a_Weather)
{
case eWeather_Sunny:
{
2017-06-13 19:35:30 +00:00
return Random.RandInt(m_MinSunnyTicks, m_MaxSunnyTicks);
}
case eWeather_Rain:
{
2017-06-13 19:35:30 +00:00
return Random.RandInt(m_MinRainTicks, m_MaxRainTicks);
}
case eWeather_ThunderStorm:
{
2017-06-13 19:35:30 +00:00
return Random.RandInt(m_MinThunderStormTicks, m_MaxThunderStormTicks);
}
2014-04-25 03:25:03 +00:00
}
#ifndef __clang__
ASSERT(!"Unknown weather");
return -1;
#endif
}
void cWorld::SetWeather(eWeather a_NewWeather)
{
// Do the plugins agree? Do they want a different weather?
if (cRoot::Get()->GetPluginManager()->CallHookWeatherChanging(*this, a_NewWeather))
{
m_WeatherInterval = GetDefaultWeatherInterval(m_Weather);
return;
}
2016-01-12 13:04:59 +00:00
// Set new period for the selected weather:
m_WeatherInterval = GetDefaultWeatherInterval(a_NewWeather);
2016-01-12 13:04:59 +00:00
// The weather can't be found:
if (m_WeatherInterval < 0)
{
return;
}
2016-01-12 13:04:59 +00:00
m_Weather = a_NewWeather;
BroadcastWeather(m_Weather);
2016-01-12 13:04:59 +00:00
// Let the plugins know about the change:
cPluginManager::Get()->CallHookWeatherChanged(*this);
}
void cWorld::ChangeWeather(void)
{
// In the next tick the weather will be changed
m_WeatherInterval = 0;
}
void cWorld::SetNextBlockTick(int a_BlockX, int a_BlockY, int a_BlockZ)
{
return m_ChunkMap->SetNextBlockTick(a_BlockX, a_BlockY, a_BlockZ);
}
bool cWorld::SetSpawn(double a_X, double a_Y, double a_Z)
{
cIniFile IniFile;
IniFile.ReadFile(m_IniFileName);
IniFile.SetValueF("SpawnPosition", "X", a_X);
IniFile.SetValueF("SpawnPosition", "Y", a_Y);
IniFile.SetValueF("SpawnPosition", "Z", a_Z);
if (IniFile.WriteFile(m_IniFileName))
{
m_SpawnX = a_X;
m_SpawnY = a_Y;
m_SpawnZ = a_Z;
LOGD("Spawn set at {%f, %f, %f}", m_SpawnX, m_SpawnY, m_SpawnZ);
return true;
}
else
{
LOGWARNING("Couldn't write new spawn settings to \"%s\".", m_IniFileName.c_str());
}
return false;
}
void cWorld::InitializeSpawn(void)
{
// For the debugging builds, don't make the server build too much world upon start:
#if defined(_DEBUG) || defined(ANDROID)
const int DefaultViewDist = 9;
#else
const int DefaultViewDist = 20; // Always prepare an area 20 chunks across, no matter what the actual cClientHandle::VIEWDISTANCE is
#endif // _DEBUG
if (!m_IsSpawnExplicitlySet)
{
// Spawn position wasn't already explicitly set, enumerate random solid-land coordinate and then write it to the world configuration:
GenerateRandomSpawn(DefaultViewDist);
}
cIniFile IniFile;
IniFile.ReadFile(m_IniFileName);
int ViewDist = IniFile.GetValueSetI("SpawnPosition", "PregenerateDistance", DefaultViewDist);
IniFile.WriteFile(m_IniFileName);
int ChunkX = 0, ChunkZ = 0;
cChunkDef::BlockToChunk(FloorC(m_SpawnX), FloorC(m_SpawnZ), ChunkX, ChunkZ);
cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, ViewDist);
}
void cWorld::Start(cDeadlockDetect & a_DeadlockDetect)
{
// Track the CSs used by this world in the deadlock detector:
a_DeadlockDetect.TrackCriticalSection(m_CSClients, Printf("World %s clients", m_WorldName.c_str()));
a_DeadlockDetect.TrackCriticalSection(m_CSPlayers, Printf("World %s players", m_WorldName.c_str()));
a_DeadlockDetect.TrackCriticalSection(m_CSTasks, Printf("World %s tasks", m_WorldName.c_str()));
m_SpawnX = 0;
m_SpawnY = cChunkDef::Height;
m_SpawnZ = 0;
m_GameMode = eGameMode_Survival;
cIniFile IniFile;
if (!IniFile.ReadFile(m_IniFileName))
{
LOGWARNING("Cannot read world settings from \"%s\", defaults will be used.", m_IniFileName.c_str());
// TODO: More descriptions for each key
IniFile.AddHeaderComment(" This is the per-world configuration file, managing settings such as generators, simulators, and spawn points");
IniFile.AddKeyComment(" LinkedWorlds", "This section governs portal world linkage; leave a value blank to disabled that associated method of teleportation");
}
2014-06-10 19:43:27 +00:00
// The presence of a configuration value overrides everything
// If no configuration value is found, GetDimension() is written to file and the variable is written to again to ensure that cosmic rays haven't sneakily changed its value
m_Dimension = StringToDimension(IniFile.GetValueSet("General", "Dimension", DimensionToString(GetDimension())));
int UnusedDirtyChunksCap = IniFile.GetValueSetI("General", "UnusedChunkCap", 1000);
if (UnusedDirtyChunksCap < 0)
{
UnusedDirtyChunksCap *= -1;
IniFile.SetValueI("General", "UnusedChunkCap", UnusedDirtyChunksCap);
}
m_UnusedDirtyChunksCap = static_cast<size_t>(UnusedDirtyChunksCap);
m_BroadcastDeathMessages = IniFile.GetValueSetB("Broadcasting", "BroadcastDeathMessages", true);
m_BroadcastAchievementMessages = IniFile.GetValueSetB("Broadcasting", "BroadcastAchievementMessages", true);
2014-10-30 20:24:10 +00:00
SetMaxViewDistance(IniFile.GetValueSetI("SpawnPosition", "MaxViewDistance", 12));
// Try to find the "SpawnPosition" key and coord values in the world configuration, set the flag if found
int KeyNum = IniFile.FindKey("SpawnPosition");
m_IsSpawnExplicitlySet =
(
(KeyNum >= 0) &&
(
(IniFile.FindValue(KeyNum, "X") >= 0) &&
(IniFile.FindValue(KeyNum, "Y") >= 0) &&
(IniFile.FindValue(KeyNum, "Z") >= 0)
)
);
if (m_IsSpawnExplicitlySet)
{
LOGD("Spawnpoint explicitly set!");
m_SpawnX = IniFile.GetValueF("SpawnPosition", "X", m_SpawnX);
m_SpawnY = IniFile.GetValueF("SpawnPosition", "Y", m_SpawnY);
m_SpawnZ = IniFile.GetValueF("SpawnPosition", "Z", m_SpawnZ);
}
2014-03-20 08:28:29 +00:00
m_StorageSchema = IniFile.GetValueSet ("Storage", "Schema", m_StorageSchema);
m_StorageCompressionFactor = IniFile.GetValueSetI("Storage", "CompressionFactor", m_StorageCompressionFactor);
m_MaxCactusHeight = IniFile.GetValueSetI("Plants", "MaxCactusHeight", 3);
m_MaxSugarcaneHeight = IniFile.GetValueSetI("Plants", "MaxSugarcaneHeight", 3);
2017-02-14 10:13:55 +00:00
m_IsBeetrootsBonemealable = IniFile.GetValueSetB("Plants", "IsBeetrootsBonemealable", true);
2014-03-20 08:28:29 +00:00
m_IsCactusBonemealable = IniFile.GetValueSetB("Plants", "IsCactusBonemealable", false);
m_IsCarrotsBonemealable = IniFile.GetValueSetB("Plants", "IsCarrotsBonemealable", true);
m_IsCropsBonemealable = IniFile.GetValueSetB("Plants", "IsCropsBonemealable", true);
m_IsGrassBonemealable = IniFile.GetValueSetB("Plants", "IsGrassBonemealable", true);
m_IsMelonStemBonemealable = IniFile.GetValueSetB("Plants", "IsMelonStemBonemealable", true);
m_IsMelonBonemealable = IniFile.GetValueSetB("Plants", "IsMelonBonemealable", false);
m_IsPotatoesBonemealable = IniFile.GetValueSetB("Plants", "IsPotatoesBonemealable", true);
m_IsPumpkinStemBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinStemBonemealable", true);
m_IsPumpkinBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinBonemealable", false);
m_IsSaplingBonemealable = IniFile.GetValueSetB("Plants", "IsSaplingBonemealable", true);
m_IsSugarcaneBonemealable = IniFile.GetValueSetB("Plants", "IsSugarcaneBonemealable", false);
m_IsBigFlowerBonemealable = IniFile.GetValueSetB("Plants", "IsBigFlowerBonemealable", true);
m_IsTallGrassBonemealable = IniFile.GetValueSetB("Plants", "IsTallGrassBonemealable", true);
2014-03-20 08:28:29 +00:00
m_IsDeepSnowEnabled = IniFile.GetValueSetB("Physics", "DeepSnow", true);
m_ShouldLavaSpawnFire = IniFile.GetValueSetB("Physics", "ShouldLavaSpawnFire", true);
int TNTShrapnelLevel = IniFile.GetValueSetI("Physics", "TNTShrapnelLevel", static_cast<int>(slAll));
2014-03-20 08:28:29 +00:00
m_bCommandBlocksEnabled = IniFile.GetValueSetB("Mechanics", "CommandBlocksEnabled", false);
m_bEnabledPVP = IniFile.GetValueSetB("Mechanics", "PVPEnabled", true);
m_bUseChatPrefixes = IniFile.GetValueSetB("Mechanics", "UseChatPrefixes", true);
m_MinNetherPortalWidth = IniFile.GetValueSetI("Mechanics", "MinNetherPortalWidth", 2);
m_MaxNetherPortalWidth = IniFile.GetValueSetI("Mechanics", "MaxNetherPortalWidth", 21);
m_MinNetherPortalHeight = IniFile.GetValueSetI("Mechanics", "MinNetherPortalHeight", 3);
m_MaxNetherPortalHeight = IniFile.GetValueSetI("Mechanics", "MaxNetherPortalHeight", 21);
2014-03-20 08:28:29 +00:00
m_VillagersShouldHarvestCrops = IniFile.GetValueSetB("Monsters", "VillagersShouldHarvestCrops", true);
2014-08-10 14:48:20 +00:00
m_IsDaylightCycleEnabled = IniFile.GetValueSetB("General", "IsDaylightCycleEnabled", true);
int GameMode = IniFile.GetValueSetI("General", "Gamemode", static_cast<int>(m_GameMode));
int Weather = IniFile.GetValueSetI("General", "Weather", static_cast<int>(m_Weather));
m_WorldAge = std::chrono::milliseconds(IniFile.GetValueSetI("General", "WorldAgeMS", 0LL));
// Load the weather frequency data:
if (m_Dimension == dimOverworld)
{
m_MaxSunnyTicks = IniFile.GetValueSetI("Weather", "MaxSunnyTicks", m_MaxSunnyTicks);
m_MinSunnyTicks = IniFile.GetValueSetI("Weather", "MinSunnyTicks", m_MinSunnyTicks);
m_MaxRainTicks = IniFile.GetValueSetI("Weather", "MaxRainTicks", m_MaxRainTicks);
m_MinRainTicks = IniFile.GetValueSetI("Weather", "MinRainTicks", m_MinRainTicks);
m_MaxThunderStormTicks = IniFile.GetValueSetI("Weather", "MaxThunderStormTicks", m_MaxThunderStormTicks);
m_MinThunderStormTicks = IniFile.GetValueSetI("Weather", "MinThunderStormTicks", m_MinThunderStormTicks);
if (m_MaxSunnyTicks < m_MinSunnyTicks)
{
std::swap(m_MaxSunnyTicks, m_MinSunnyTicks);
}
if (m_MaxRainTicks < m_MinRainTicks)
{
std::swap(m_MaxRainTicks, m_MinRainTicks);
}
if (m_MaxThunderStormTicks < m_MinThunderStormTicks)
{
std::swap(m_MaxThunderStormTicks, m_MinThunderStormTicks);
}
}
2016-01-12 13:04:59 +00:00
2014-07-22 09:33:16 +00:00
if (GetDimension() == dimOverworld)
{
AString MyNetherName = GetName() + "_nether";
AString MyEndName = GetName() + "_the_end";
if (cRoot::Get()->GetWorld(MyNetherName) == nullptr)
{
MyNetherName = "";
}
if (cRoot::Get()->GetWorld(MyEndName) == nullptr)
{
MyEndName = GetName() + "_end";
if (cRoot::Get()->GetWorld(MyEndName) == nullptr)
{
MyEndName = "";
}
}
m_LinkedNetherWorldName = IniFile.GetValueSet("LinkedWorlds", "NetherWorldName", MyNetherName);
m_LinkedEndWorldName = IniFile.GetValueSet("LinkedWorlds", "EndWorldName", MyEndName);
2014-07-22 09:33:16 +00:00
}
else
{
m_LinkedOverworldName = IniFile.GetValueSet("LinkedWorlds", "OverworldName", GetLinkedOverworldName());
}
2016-01-12 13:04:59 +00:00
2016-04-18 19:29:39 +00:00
// If we are linked to one or more worlds that do not exist, unlink them
cRoot * Root = cRoot::Get();
if (GetDimension() == dimOverworld)
{
if ((!m_LinkedNetherWorldName.empty()) && (Root->GetWorld(m_LinkedNetherWorldName) == nullptr))
{
2016-04-18 19:29:39 +00:00
IniFile.SetValue("LinkedWorlds", "NetherWorldName", "");
LOG("%s Is linked to a nonexisting nether world called \"%s\". The server has modified \"%s/world.ini\" and removed this invalid link.",
GetName().c_str(), m_LinkedNetherWorldName.c_str(), GetName().c_str());
m_LinkedNetherWorldName = "";
}
if ((!m_LinkedEndWorldName.empty()) && (Root->GetWorld(m_LinkedEndWorldName) == nullptr))
{
2016-04-18 19:29:39 +00:00
IniFile.SetValue("LinkedWorlds", "EndWorldName", "");
LOG("%s Is linked to a nonexisting end world called \"%s\". The server has modified \"%s/world.ini\" and removed this invalid link.",
GetName().c_str(), m_LinkedEndWorldName.c_str(), GetName().c_str());
m_LinkedEndWorldName = "";
}
}
else
{
if ((!m_LinkedOverworldName.empty()) && (Root->GetWorld(m_LinkedOverworldName) == nullptr))
{
2016-04-18 19:29:39 +00:00
IniFile.SetValue("LinkedWorlds", "OverworldName", "");
LOG("%s Is linked to a nonexisting overworld called \"%s\". The server has modified \"%s/world.ini\" and removed this invalid link.",
GetName().c_str(), m_LinkedOverworldName.c_str(), GetName().c_str());
m_LinkedOverworldName = "";
}
}
2016-04-18 19:29:39 +00:00
2014-03-20 08:28:29 +00:00
// Adjust the enum-backed variables into their respective bounds:
m_GameMode = static_cast<eGameMode> (Clamp<int>(GameMode, gmSurvival, gmSpectator));
m_TNTShrapnelLevel = static_cast<eShrapnelLevel>(Clamp<int>(TNTShrapnelLevel, slNone, slAll));
m_Weather = static_cast<eWeather> (Clamp<int>(Weather, wSunny, wStorm));
2014-07-21 21:49:06 +00:00
InitialiseGeneratorDefaults(IniFile);
InitialiseAndLoadMobSpawningValues(IniFile);
SetTimeOfDay(IniFile.GetValueSetI("General", "TimeInTicks", GetTimeOfDay()));
m_ChunkMap = cpp14::make_unique<cChunkMap>(this);
m_ChunkMap->TrackInDeadlockDetect(a_DeadlockDetect, m_WorldName);
2016-01-12 13:04:59 +00:00
2013-09-10 20:02:46 +00:00
// preallocate some memory for ticking blocks so we don't need to allocate that often
m_BlockTickQueue.reserve(1000);
m_BlockTickQueueCopy.reserve(1000);
// Simulators:
m_SimulatorManager = cpp14::make_unique<cSimulatorManager>(*this);
m_WaterSimulator = InitializeFluidSimulator(IniFile, "Water", E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER);
m_LavaSimulator = InitializeFluidSimulator(IniFile, "Lava", E_BLOCK_LAVA, E_BLOCK_STATIONARY_LAVA);
m_SandSimulator = cpp14::make_unique<cSandSimulator>(*this, IniFile);
m_FireSimulator = cpp14::make_unique<cFireSimulator>(*this, IniFile);
m_RedstoneSimulator = InitializeRedstoneSimulator(IniFile);
2014-02-08 10:20:00 +00:00
// Water, Lava and Redstone simulators get registered in their initialize function.
2014-10-10 14:33:19 +00:00
m_SimulatorManager->RegisterSimulator(m_SandSimulator.get(), 1);
m_SimulatorManager->RegisterSimulator(m_FireSimulator.get(), 1);
m_Lighting.Start(this);
m_Storage.Start(this, m_StorageSchema, m_StorageCompressionFactor);
m_Generator.Start(m_GeneratorCallbacks, m_GeneratorCallbacks, IniFile);
m_ChunkSender.Start();
m_TickThread.Start();
// Init of the spawn monster time (as they are supposed to have different spawn rate)
m_LastSpawnMonster.insert(std::map<cMonster::eFamily, cTickTimeLong>::value_type(cMonster::mfHostile, cTickTimeLong(0)));
m_LastSpawnMonster.insert(std::map<cMonster::eFamily, cTickTimeLong>::value_type(cMonster::mfPassive, cTickTimeLong(0)));
m_LastSpawnMonster.insert(std::map<cMonster::eFamily, cTickTimeLong>::value_type(cMonster::mfAmbient, cTickTimeLong(0)));
m_LastSpawnMonster.insert(std::map<cMonster::eFamily, cTickTimeLong>::value_type(cMonster::mfWater, cTickTimeLong(0)));
m_MapManager.LoadMapData();
// Save any changes that the defaults may have done to the ini file:
if (!IniFile.WriteFile(m_IniFileName))
{
LOGWARNING("Could not write world config to %s", m_IniFileName.c_str());
}
}
void cWorld::GenerateRandomSpawn(int a_MaxSpawnRadius)
{
LOGD("Generating random spawnpoint...");
// Number of checks to make sure we have a valid biome
// 100 checks will check across 400 chunks, we should have
// a valid biome by then.
static const int BiomeCheckCount = 100;
// Make sure we are in a valid biome
Vector3i BiomeOffset = Vector3i(0, 0, 0);
for (int BiomeCheckIndex = 0; BiomeCheckIndex < BiomeCheckCount; ++BiomeCheckIndex)
{
EMCSBiome Biome = GetBiomeAt(BiomeOffset.x, BiomeOffset.z);
if ((Biome == EMCSBiome::biOcean) || (Biome == EMCSBiome::biFrozenOcean))
{
BiomeOffset += Vector3d(cChunkDef::Width * 4, 0, 0);
continue;
}
// Found a usable biome
// Spawn chunks so we can find a nice spawn.
int ChunkX = 0, ChunkZ = 0;
cChunkDef::BlockToChunk(BiomeOffset.x, BiomeOffset.z, ChunkX, ChunkZ);
cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, a_MaxSpawnRadius);
break;
}
2016-01-12 13:04:59 +00:00
// Check 0, 0 first.
double SpawnY = 0.0;
if (CanSpawnAt(BiomeOffset.x, SpawnY, BiomeOffset.z))
{
SetSpawn(BiomeOffset.x + 0.5, SpawnY, BiomeOffset.z + 0.5);
LOGINFO("Generated spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ);
return;
}
// A search grid (searches clockwise around the origin)
static const int HalfChunk = static_cast<int>(cChunkDef::Width / 2.0f);
static const Vector3i ChunkOffset[] =
{
Vector3i(0, 0, HalfChunk),
Vector3i(HalfChunk, 0, HalfChunk),
Vector3i(HalfChunk, 0, 0),
Vector3i(HalfChunk, 0, -HalfChunk),
Vector3i(0, 0, -HalfChunk),
Vector3i(-HalfChunk, 0, -HalfChunk),
Vector3i(-HalfChunk, 0, 0),
Vector3i(-HalfChunk, 0, HalfChunk),
};
static const int PerRadiSearchCount = ARRAYCOUNT(ChunkOffset);
for (int RadiusOffset = 1; RadiusOffset < (a_MaxSpawnRadius * 2); ++RadiusOffset)
{
for (int SearchGridIndex = 0; SearchGridIndex < PerRadiSearchCount; ++SearchGridIndex)
{
const Vector3i PotentialSpawn = BiomeOffset + (ChunkOffset[SearchGridIndex] * RadiusOffset);
2016-01-12 13:04:59 +00:00
if (CanSpawnAt(PotentialSpawn.x, SpawnY, PotentialSpawn.z))
{
SetSpawn(PotentialSpawn.x + 0.5, SpawnY, PotentialSpawn.z + 0.5);
int ChunkX, ChunkZ;
cChunkDef::BlockToChunk(static_cast<int>(m_SpawnX), static_cast<int>(m_SpawnZ), ChunkX, ChunkZ);
cSpawnPrepare::PrepareChunks(*this, ChunkX, ChunkZ, a_MaxSpawnRadius);
LOGINFO("Generated spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ);
return;
}
}
}
m_SpawnY = GetHeight(static_cast<int>(m_SpawnX), static_cast<int>(m_SpawnZ));
LOGWARNING("Did not find an acceptable spawnpoint. Generated a random spawnpoint position at {%.2f, %.2f, %.2f}", m_SpawnX, m_SpawnY, m_SpawnZ);
}
bool cWorld::CanSpawnAt(double a_X, double & a_Y, double a_Z)
{
// All this blocks can only be found above ground.
// Apart from netherrack (as the Nether is technically a massive cave)
static const BLOCKTYPE ValidSpawnBlocks[] =
{
E_BLOCK_GRASS,
E_BLOCK_SAND,
E_BLOCK_SNOW,
E_BLOCK_SNOW_BLOCK,
E_BLOCK_NETHERRACK
};
static const int ValidSpawnBlocksCount = ARRAYCOUNT(ValidSpawnBlocks);
// Increase this by two, because we need two more blocks for body and head
static const int HighestSpawnPoint = GetHeight(static_cast<int>(a_X), static_cast<int>(a_Z)) + 2;
static const int LowestSpawnPoint = static_cast<int>(HighestSpawnPoint / 2.0f);
for (int PotentialY = HighestSpawnPoint; PotentialY > LowestSpawnPoint; --PotentialY)
{
BLOCKTYPE HeadBlock = GetBlock(static_cast<int>(a_X), PotentialY, static_cast<int>(a_Z));
// Is this block safe for spawning
if (HeadBlock != E_BLOCK_AIR)
{
continue;
}
BLOCKTYPE BodyBlock = GetBlock(static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z));
// Is this block safe for spawning
if (BodyBlock != E_BLOCK_AIR)
{
continue;
}
BLOCKTYPE FloorBlock = GetBlock(static_cast<int>(a_X), PotentialY - 2, static_cast<int>(a_Z));
// Early out - Is the floor block air
if (FloorBlock == E_BLOCK_AIR)
{
continue;
}
// Is the floor block ok
bool ValidSpawnBlock = false;
for (int BlockIndex = 0; BlockIndex < ValidSpawnBlocksCount; ++BlockIndex)
{
ValidSpawnBlock |= (ValidSpawnBlocks[BlockIndex] == FloorBlock);
}
if (!ValidSpawnBlock)
{
continue;
}
if (!CheckPlayerSpawnPoint(static_cast<int>(a_X), PotentialY - 1, static_cast<int>(a_Z)))
{
continue;
}
a_Y = PotentialY - 1.0;
return true;
}
return false;
}
bool cWorld::CheckPlayerSpawnPoint(int a_PosX, int a_PosY, int a_PosZ)
{
// Check height bounds
if (!cChunkDef::IsValidHeight(a_PosY))
{
return false;
}
// Check that surrounding blocks are neither solid or liquid
static const Vector3i SurroundingCoords[] =
{
Vector3i(0, 0, 1),
Vector3i(1, 0, 1),
Vector3i(1, 0, 0),
Vector3i(1, 0, -1),
Vector3i(0, 0, -1),
Vector3i(-1, 0, -1),
Vector3i(-1, 0, 0),
Vector3i(-1, 0, 1),
};
static const int SurroundingCoordsCount = ARRAYCOUNT(SurroundingCoords);
for (int CoordIndex = 0; CoordIndex < SurroundingCoordsCount; ++CoordIndex)
{
const int XPos = a_PosX + SurroundingCoords[CoordIndex].x;
const int ZPos = a_PosZ + SurroundingCoords[CoordIndex].z;
const BLOCKTYPE BlockType = GetBlock(XPos, a_PosY, ZPos);
if (cBlockInfo::IsSolid(BlockType) || IsBlockLiquid(BlockType))
{
return false;
}
}
return true;
}
eWeather cWorld::ChooseNewWeather()
{
// Pick a new weather. Only reasonable transitions allowed:
switch (m_Weather)
{
case eWeather_Sunny:
case eWeather_ThunderStorm: return eWeather_Rain;
2016-01-12 13:04:59 +00:00
case eWeather_Rain:
{
// 1 / 8 chance of turning into a thunderstorm
2017-06-13 19:35:30 +00:00
return GetRandomProvider().RandBool(0.125) ? eWeather_ThunderStorm : eWeather_Sunny;
}
}
#ifndef __clang__
ASSERT(!"Unknown weather");
return eWeather_Sunny;
#endif
}
2014-07-21 21:49:06 +00:00
void cWorld::InitialiseGeneratorDefaults(cIniFile & a_IniFile)
{
switch (GetDimension())
{
case dimEnd:
{
a_IniFile.GetValueSet("Generator", "Generator", "Composable");
a_IniFile.GetValueSet("Generator", "BiomeGen", "Constant");
a_IniFile.GetValueSet("Generator", "ConstantBiome", "End");
a_IniFile.GetValueSet("Generator", "ShapeGen", "End");
2014-07-21 21:49:06 +00:00
a_IniFile.GetValueSet("Generator", "CompositionGen", "End");
break;
}
case dimOverworld:
{
a_IniFile.GetValueSet("Generator", "Generator", "Composable");
a_IniFile.GetValueSet("Generator", "BiomeGen", "Grown");
a_IniFile.GetValueSet("Generator", "ShapeGen", "BiomalNoise3D");
a_IniFile.GetValueSet("Generator", "CompositionGen", "Biomal");
a_IniFile.GetValueSet("Generator", "Finishers", "RoughRavines, WormNestCaves, WaterLakes, WaterSprings, LavaLakes, LavaSprings, OreNests, Mineshafts, Trees, Villages, TallGrass, SprinkleFoliage, Ice, Snow, Lilypads, BottomLava, DeadBushes, NaturalPatches, PreSimulator, Animals");
2014-07-21 21:49:06 +00:00
break;
}
case dimNether:
{
a_IniFile.GetValueSet("Generator", "Generator", "Composable");
a_IniFile.GetValueSet("Generator", "BiomeGen", "Constant");
a_IniFile.GetValueSet("Generator", "ConstantBiome", "Nether");
a_IniFile.GetValueSet("Generator", "ShapeGen", "HeightMap");
a_IniFile.GetValueSet("Generator", "HeightGen", "Flat");
a_IniFile.GetValueSet("Generator", "FlatHeight", "128");
a_IniFile.GetValueSet("Generator", "CompositionGen", "Nether");
a_IniFile.GetValueSet("Generator", "Finishers", "SoulsandRims, WormNestCaves, BottomLava, LavaSprings, NetherClumpFoliage, NetherOreNests, PieceStructures: NetherFort, GlowStone, PreSimulator");
2014-07-21 21:49:06 +00:00
a_IniFile.GetValueSet("Generator", "BottomLavaHeight", "30");
break;
}
2014-09-08 18:15:29 +00:00
case dimNotSet:
{
2014-09-08 20:31:47 +00:00
ASSERT(!"Dimension not set");
2014-09-08 18:15:29 +00:00
break;
}
2014-07-21 21:49:06 +00:00
}
}
void cWorld::InitialiseAndLoadMobSpawningValues(cIniFile & a_IniFile)
{
AString DefaultMonsters;
switch (m_Dimension)
{
case dimOverworld: DefaultMonsters = "bat, cavespider, chicken, cow, creeper, guardian, horse, mooshroom, ocelot, pig, rabbit, sheep, silverfish, skeleton, slime, spider, squid, wolf, zombie"; break; // TODO Re-add Enderman when bugs are fixed
2014-07-21 21:49:06 +00:00
case dimNether: DefaultMonsters = "blaze, ghast, magmacube, skeleton, zombie, zombiepigman"; break;
case dimEnd: DefaultMonsters = ""; break; // TODO Re-add Enderman when bugs are fixed
2014-09-08 20:31:47 +00:00
case dimNotSet: ASSERT(!"Dimension not set"); break;
2014-07-21 21:49:06 +00:00
}
2016-01-12 13:04:59 +00:00
2014-07-21 21:49:06 +00:00
m_bAnimals = a_IniFile.GetValueSetB("Monsters", "AnimalsOn", true);
AString AllMonsters = a_IniFile.GetValueSet("Monsters", "Types", DefaultMonsters);
if (!m_bAnimals)
{
return;
}
AStringVector SplitList = StringSplitAndTrim(AllMonsters, ",");
for (AStringVector::const_iterator itr = SplitList.begin(), end = SplitList.end(); itr != end; ++itr)
{
eMonsterType ToAdd = cMonster::StringToMobType(*itr);
if (ToAdd != mtInvalidType)
2014-07-21 21:49:06 +00:00
{
m_AllowedMobs.insert(ToAdd);
LOGD("Allowed mob: %s", itr->c_str());
}
else
{
LOG("World \"%s\": Unknown mob type: %s", m_WorldName.c_str(), itr->c_str());
}
}
}
void cWorld::Stop(cDeadlockDetect & a_DeadlockDetect)
{
// Delete the clients that have been in this world:
{
cCSLock Lock(m_CSClients);
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
{
(*itr)->Destroy();
} // for itr - m_Clients[]
m_Clients.clear();
}
2014-06-04 19:00:55 +00:00
// Write settings to file; these are all plugin changeable values - keep updated!
cIniFile IniFile;
IniFile.ReadFile(m_IniFileName);
if (GetDimension() == dimOverworld)
2014-06-04 19:00:55 +00:00
{
IniFile.SetValue("LinkedWorlds", "NetherWorldName", m_LinkedNetherWorldName);
IniFile.SetValue("LinkedWorlds", "EndWorldName", m_LinkedEndWorldName);
2014-06-04 19:00:55 +00:00
}
2014-06-10 19:43:27 +00:00
else
{
IniFile.SetValue("LinkedWorlds", "OverworldName", m_LinkedOverworldName);
2014-06-10 19:43:27 +00:00
}
IniFile.SetValueI("Physics", "TNTShrapnelLevel", static_cast<int>(m_TNTShrapnelLevel));
2014-06-04 19:00:55 +00:00
IniFile.SetValueB("Mechanics", "CommandBlocksEnabled", m_bCommandBlocksEnabled);
IniFile.SetValueB("Mechanics", "UseChatPrefixes", m_bUseChatPrefixes);
2014-08-10 14:48:20 +00:00
IniFile.SetValueB("General", "IsDaylightCycleEnabled", m_IsDaylightCycleEnabled);
IniFile.SetValueI("General", "Weather", static_cast<int>(m_Weather));
IniFile.SetValueI("General", "TimeInTicks", GetTimeOfDay());
IniFile.SetValueI("General", "WorldAgeMS", static_cast<Int64>(m_WorldAge.count()));
2014-06-04 19:00:55 +00:00
IniFile.WriteFile(m_IniFileName);
2016-01-12 13:04:59 +00:00
m_TickThread.Stop();
m_Lighting.Stop();
m_Generator.Stop();
m_ChunkSender.Stop();
m_Storage.Stop();
a_DeadlockDetect.UntrackCriticalSection(m_CSClients);
a_DeadlockDetect.UntrackCriticalSection(m_CSPlayers);
a_DeadlockDetect.UntrackCriticalSection(m_CSTasks);
m_ChunkMap->UntrackInDeadlockDetect(a_DeadlockDetect);
}
void cWorld::Tick(std::chrono::milliseconds a_Dt, std::chrono::milliseconds a_LastTickDurationMSec)
{
// Call the plugins
cPluginManager::Get()->CallHookWorldTick(*this, a_Dt, a_LastTickDurationMSec);
2016-01-12 13:04:59 +00:00
// Set any chunk data that has been queued for setting:
cSetChunkDataPtrs SetChunkDataQueue;
{
cCSLock Lock(m_CSSetChunkDataQueue);
std::swap(SetChunkDataQueue, m_SetChunkDataQueue);
}
for (cSetChunkDataPtrs::iterator itr = SetChunkDataQueue.begin(), end = SetChunkDataQueue.end(); itr != end; ++itr)
{
SetChunkData(**itr);
} // for itr - SetChunkDataQueue[]
m_WorldAge += a_Dt;
2014-08-10 14:46:03 +00:00
if (m_IsDaylightCycleEnabled)
{
2015-01-17 22:24:25 +00:00
// We need sub-tick precision here, that's why we store the time in milliseconds and calculate ticks off of it
m_TimeOfDay += a_Dt;
// Wrap time of day each 20 minutes (1200 seconds)
if (m_TimeOfDay > std::chrono::minutes(20))
{
m_TimeOfDay -= std::chrono::minutes(20);
}
// Updates the sky darkness based on current time of day
UpdateSkyDarkness();
// Broadcast time update every 40 ticks (2 seconds)
if (m_LastTimeUpdate < m_WorldAge - cTickTime(40))
{
BroadcastTimeUpdate();
m_LastTimeUpdate = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
}
}
// Add entities waiting in the queue to be added:
{
cCSLock Lock(m_CSEntitiesToAdd);
2016-03-29 18:23:53 +00:00
for (auto & Entity : m_EntitiesToAdd)
{
2016-03-29 18:23:53 +00:00
Entity->SetWorld(this);
auto EntityPtr = Entity.get();
m_ChunkMap->AddEntity(std::move(Entity));
ASSERT(!EntityPtr->IsTicking());
EntityPtr->SetIsTicking(true);
}
m_EntitiesToAdd.clear();
}
// Add players waiting in the queue to be added:
AddQueuedPlayers();
m_ChunkMap->Tick(a_Dt);
TickMobs(a_Dt);
2015-06-30 14:50:15 +00:00
m_MapManager.TickMaps();
2015-01-18 10:25:16 +00:00
TickClients(static_cast<float>(a_Dt.count()));
TickQueuedBlocks();
TickQueuedTasks();
2016-01-12 13:04:59 +00:00
2015-01-18 10:25:16 +00:00
GetSimulatorManager()->Simulate(static_cast<float>(a_Dt.count()));
2015-01-18 10:25:16 +00:00
TickWeather(static_cast<float>(a_Dt.count()));
if (m_WorldAge - m_LastChunkCheck > std::chrono::seconds(10))
{
// Unload every 10 seconds
UnloadUnusedChunks();
if (m_WorldAge - m_LastSave > std::chrono::minutes(5))
{
// Save every 5 minutes
SaveAllChunks();
}
else if (GetNumUnusedDirtyChunks() > m_UnusedDirtyChunksCap)
{
// Save if we have too many dirty unused chunks
SaveAllChunks();
}
}
}
void cWorld::TickWeather(float a_Dt)
{
2013-12-22 14:55:24 +00:00
UNUSED(a_Dt);
// There are no weather changes anywhere but in the Overworld:
if (GetDimension() != dimOverworld)
{
return;
}
if (m_WeatherInterval > 0)
{
// Not yet, wait for the weather period to end
m_WeatherInterval--;
}
else
{
// Change weather:
SetWeather(ChooseNewWeather());
}
if (m_Weather == eWeather_ThunderStorm)
{
// 0.5% chance per tick of thunderbolt
2017-06-13 19:35:30 +00:00
if (GetRandomProvider().RandBool(0.005))
{
CastThunderbolt(0, 0, 0); // TODO: find random positions near players to cast thunderbolts.
}
}
}
void cWorld::TickMobs(std::chrono::milliseconds a_Dt)
{
// _X 2013_10_22: This is a quick fix for #283 - the world needs to be locked while ticking mobs
cWorld::cLock Lock(*this);
// before every Mob action, we have to count them depending on the distance to players, on their family ...
cMobCensus MobCensus;
m_ChunkMap->CollectMobCensus(MobCensus);
if (m_bAnimals)
{
// Spawning is enabled, spawn now:
static const cMonster::eFamily AllFamilies[] =
{
cMonster::mfHostile,
cMonster::mfPassive,
cMonster::mfAmbient,
cMonster::mfWater,
} ;
2013-12-22 14:55:24 +00:00
for (size_t i = 0; i < ARRAYCOUNT(AllFamilies); i++)
{
cMonster::eFamily Family = AllFamilies[i];
cTickTime SpawnDelay = cTickTime(cMonster::GetSpawnDelay(Family));
if (
(m_LastSpawnMonster[Family] > m_WorldAge - SpawnDelay) || // Not reached the needed ticks before the next round
MobCensus.IsCapped(Family)
)
{
continue;
}
m_LastSpawnMonster[Family] = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
cMobSpawner Spawner(Family, m_AllowedMobs);
if (Spawner.CanSpawnAnything())
{
m_ChunkMap->SpawnMobs(Spawner);
// do the spawn
for (cMobSpawner::tSpawnedContainer::const_iterator itr2 = Spawner.getSpawned().begin(); itr2 != Spawner.getSpawned().end(); ++itr2)
{
SpawnMobFinalize(std::move(const_cast<std::unique_ptr<cMonster> &>(*itr2)));
}
}
} // for i - AllFamilies[]
} // if (Spawning enabled)
2016-04-06 09:16:14 +00:00
class cCallback : public cEntityCallback
{
virtual bool Item(cEntity * a_Entity) override
{
if (!a_Entity->IsMob())
{
return false;
}
if (!a_Entity->IsTicking())
{
return false;
}
auto Monster = static_cast<cMonster *>(a_Entity);
ASSERT(Monster->GetParentChunk() != nullptr); // A ticking entity must have a valid parent chunk
// Tick close mobs
if (Monster->GetParentChunk()->HasAnyClients())
{
Monster->Tick(m_Dt, *(a_Entity->GetParentChunk()));
}
// Destroy far hostile mobs except if last target was a player
else if ((Monster->GetMobFamily() == cMonster::eFamily::mfHostile) && !Monster->WasLastTargetAPlayer())
2016-04-06 09:16:14 +00:00
{
if (Monster->GetMobType() != eMonsterType::mtWolf)
{
Monster->Destroy(true);
}
else
{
auto Wolf = static_cast<cWolf *>(Monster);
if (Wolf->IsAngry())
{
Monster->Destroy(true);
}
}
}
return false;
}
public:
std::chrono::milliseconds m_Dt;
} Callback;
Callback.m_Dt = a_Dt;
ForEachEntity(Callback);
}
void cWorld::TickQueuedTasks(void)
{
2014-10-12 11:18:52 +00:00
// Move the tasks to be executed to a seperate vector to avoid deadlocks on accessing m_Tasks
decltype(m_Tasks) Tasks;
{
cCSLock Lock(m_CSTasks);
if (m_Tasks.empty())
{
return;
}
// Partition everything to be executed by returning false to move to end of list if time reached
auto MoveBeginIterator = std::partition(m_Tasks.begin(), m_Tasks.end(), [this](const decltype(m_Tasks)::value_type & a_Task)
{
if (a_Task.first < std::chrono::duration_cast<cTickTimeLong>(m_WorldAge).count())
{
return false;
}
return true;
}
);
// Cut all the due tasks from m_Tasks into Tasks:
Tasks.insert(
Tasks.end(),
std::make_move_iterator(MoveBeginIterator),
std::make_move_iterator(m_Tasks.end())
);
m_Tasks.erase(MoveBeginIterator, m_Tasks.end());
}
// Execute each task:
for (const auto & Task : Tasks)
{
Task.second(*this);
} // for itr - m_Tasks[]
}
void cWorld::TickClients(float a_Dt)
{
cClientHandlePtrs RemoveClients;
{
cCSLock Lock(m_CSClients);
2016-01-12 13:04:59 +00:00
// Remove clients scheduled for removal:
for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
{
for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC)
{
if (itrC->get() == *itr)
{
m_Clients.erase(itrC);
break;
}
}
} // for itr - m_ClientsToRemove[]
m_ClientsToRemove.clear();
2016-01-12 13:04:59 +00:00
// Add clients scheduled for adding:
for (auto itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
{
ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end());
m_Clients.push_back(*itr);
} // for itr - m_ClientsToRemove[]
m_ClientsToAdd.clear();
2016-01-12 13:04:59 +00:00
// Tick the clients, take out those that have been destroyed into RemoveClients
for (auto itr = m_Clients.begin(); itr != m_Clients.end();)
{
if ((*itr)->IsDestroyed())
{
// Remove the client later, when CS is not held, to avoid deadlock
RemoveClients.push_back(*itr);
itr = m_Clients.erase(itr);
continue;
}
(*itr)->Tick(a_Dt);
++itr;
} // for itr - m_Clients[]
}
// Delete the clients queued for removal:
RemoveClients.clear();
}
void cWorld::UpdateSkyDarkness(void)
{
int TempTime = std::chrono::duration_cast<cTickTime>(m_TimeOfDay).count();
if (TempTime <= TIME_SUNSET)
{
m_SkyDarkness = 0;
}
else if (TempTime <= TIME_NIGHT_START)
{
2014-09-08 18:15:29 +00:00
m_SkyDarkness = static_cast<NIBBLETYPE>((TIME_NIGHT_START - TempTime) / TIME_SPAWN_DIVISOR);
}
else if (TempTime <= TIME_NIGHT_END)
{
m_SkyDarkness = 8;
}
else
{
2014-09-08 18:15:29 +00:00
m_SkyDarkness = static_cast<NIBBLETYPE>((TIME_SUNRISE - TempTime) / TIME_SPAWN_DIVISOR);
}
}
void cWorld::WakeUpSimulators(Vector3i a_Block)
{
return m_ChunkMap->WakeUpSimulators(a_Block);
}
void cWorld::WakeUpSimulatorsInArea(int a_MinBlockX, int a_MaxBlockX, int a_MinBlockY, int a_MaxBlockY, int a_MinBlockZ, int a_MaxBlockZ)
{
LOGWARNING("cWorld::WakeUpSimulatorsInArea(int, int, int) is deprecated, use cWorld::WakeUpSimulatorsInArea(Vector3i) instead.");
WakeUpSimulatorsInArea(cCuboid({a_MinBlockX, a_MinBlockY, a_MinBlockZ}, {a_MaxBlockX, a_MaxBlockY, a_MaxBlockZ}));
}
void cWorld::WakeUpSimulatorsInArea(const cCuboid & a_Area)
{
m_SimulatorManager->WakeUpArea(a_Area);
}
bool cWorld::ForEachBlockEntityInChunk(int a_ChunkX, int a_ChunkZ, cBlockEntityCallback & a_Callback)
{
return m_ChunkMap->ForEachBlockEntityInChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
2015-09-24 08:48:33 +00:00
bool cWorld::ForEachBrewingstandInChunk(int a_ChunkX, int a_ChunkZ, cBrewingstandCallback & a_Callback)
{
return m_ChunkMap->ForEachBrewingstandInChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
bool cWorld::ForEachChestInChunk(int a_ChunkX, int a_ChunkZ, cChestCallback & a_Callback)
{
return m_ChunkMap->ForEachChestInChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
bool cWorld::ForEachDispenserInChunk(int a_ChunkX, int a_ChunkZ, cDispenserCallback & a_Callback)
{
return m_ChunkMap->ForEachDispenserInChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
bool cWorld::ForEachDropperInChunk(int a_ChunkX, int a_ChunkZ, cDropperCallback & a_Callback)
{
return m_ChunkMap->ForEachDropperInChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
bool cWorld::ForEachDropSpenserInChunk(int a_ChunkX, int a_ChunkZ, cDropSpenserCallback & a_Callback)
{
return m_ChunkMap->ForEachDropSpenserInChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
bool cWorld::ForEachFurnaceInChunk(int a_ChunkX, int a_ChunkZ, cFurnaceCallback & a_Callback)
{
return m_ChunkMap->ForEachFurnaceInChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
void cWorld::DoExplosionAt(double a_ExplosionSize, double a_BlockX, double a_BlockY, double a_BlockZ, bool a_CanCauseFire, eExplosionSource a_Source, void * a_SourceData)
{
if (cPluginManager::Get()->CallHookExploding(*this, a_ExplosionSize, a_CanCauseFire, a_BlockX, a_BlockY, a_BlockZ, a_Source, a_SourceData) || (a_ExplosionSize <= 0))
{
return;
}
2016-01-12 13:04:59 +00:00
2014-08-29 12:44:10 +00:00
// TODO: Implement block hardiness
Vector3d explosion_pos = Vector3d(a_BlockX, a_BlockY, a_BlockZ);
cVector3iArray BlocksAffected;
m_ChunkMap->DoExplosionAt(a_ExplosionSize, a_BlockX, a_BlockY, a_BlockZ, BlocksAffected);
2017-02-15 05:05:24 +00:00
BroadcastSoundEffect("entity.generic.explode", static_cast<double>(a_BlockX), static_cast<double>(a_BlockY), static_cast<double>(a_BlockZ), 1.0f, 0.6f);
2014-08-29 12:44:10 +00:00
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if (ch == nullptr)
{
continue;
}
2014-08-29 12:44:10 +00:00
Vector3d distance_explosion = (*itr)->GetPosition() - explosion_pos;
if (distance_explosion.SqrLength() < 4096.0)
{
2014-08-29 12:44:10 +00:00
double real_distance = std::max(0.004, distance_explosion.Length());
double power = a_ExplosionSize / real_distance;
if (power <= 1)
{
power = 0;
}
distance_explosion.Normalize();
distance_explosion *= power;
ch->SendExplosion(a_BlockX, a_BlockY, a_BlockZ, static_cast<float>(a_ExplosionSize), BlocksAffected, distance_explosion);
}
}
}
2014-08-29 12:44:10 +00:00
cPluginManager::Get()->CallHookExploded(*this, a_ExplosionSize, a_CanCauseFire, a_BlockX, a_BlockY, a_BlockZ, a_Source, a_SourceData);
}
bool cWorld::DoWithBlockEntityAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBlockEntityCallback & a_Callback)
{
return m_ChunkMap->DoWithBlockEntityAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithBeaconAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBeaconCallback & a_Callback)
2014-07-30 20:19:51 +00:00
{
return m_ChunkMap->DoWithBeaconAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithBedAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBedCallback & a_Callback)
{
return m_ChunkMap->DoWithBedAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
2015-09-24 08:48:33 +00:00
bool cWorld::DoWithBrewingstandAt(int a_BlockX, int a_BlockY, int a_BlockZ, cBrewingstandCallback & a_Callback)
{
return m_ChunkMap->DoWithBrewingstandAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithChestAt(int a_BlockX, int a_BlockY, int a_BlockZ, cChestCallback & a_Callback)
{
return m_ChunkMap->DoWithChestAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithDispenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDispenserCallback & a_Callback)
{
return m_ChunkMap->DoWithDispenserAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithDropperAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropperCallback & a_Callback)
{
return m_ChunkMap->DoWithDropperAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithDropSpenserAt(int a_BlockX, int a_BlockY, int a_BlockZ, cDropSpenserCallback & a_Callback)
{
return m_ChunkMap->DoWithDropSpenserAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithFurnaceAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFurnaceCallback & a_Callback)
{
return m_ChunkMap->DoWithFurnaceAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithNoteBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cNoteBlockCallback & a_Callback)
{
return m_ChunkMap->DoWithNoteBlockAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
2014-01-18 13:16:47 +00:00
bool cWorld::DoWithCommandBlockAt(int a_BlockX, int a_BlockY, int a_BlockZ, cCommandBlockCallback & a_Callback)
{
return m_ChunkMap->DoWithCommandBlockAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
}
bool cWorld::DoWithMobHeadAt(int a_BlockX, int a_BlockY, int a_BlockZ, cMobHeadCallback & a_Callback)
2014-02-18 20:40:02 +00:00
{
return m_ChunkMap->DoWithMobHeadAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
2014-02-18 20:40:02 +00:00
}
2014-03-07 00:30:34 +00:00
bool cWorld::DoWithFlowerPotAt(int a_BlockX, int a_BlockY, int a_BlockZ, cFlowerPotCallback & a_Callback)
2014-02-18 20:40:02 +00:00
{
2014-03-07 00:30:34 +00:00
return m_ChunkMap->DoWithFlowerPotAt(a_BlockX, a_BlockY, a_BlockZ, a_Callback);
2014-02-18 20:40:02 +00:00
}
bool cWorld::GetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, AString & a_Line1, AString & a_Line2, AString & a_Line3, AString & a_Line4)
{
return m_ChunkMap->GetSignLines(a_BlockX, a_BlockY, a_BlockZ, a_Line1, a_Line2, a_Line3, a_Line4);
}
bool cWorld::DoWithChunk(int a_ChunkX, int a_ChunkZ, cChunkCallback & a_Callback)
{
return m_ChunkMap->DoWithChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
bool cWorld::DoWithChunk(int a_ChunkX, int a_ChunkZ, std::function<bool(cChunk &)> a_Callback)
{
struct cCallBackWrapper : cChunkCallback
{
cCallBackWrapper(std::function<bool(cChunk &)> a_InnerCallback) :
m_Callback(a_InnerCallback)
{
}
2016-01-12 13:04:59 +00:00
virtual bool Item(cChunk * a_Chunk)
{
return m_Callback(*a_Chunk);
}
private:
std::function<bool(cChunk &)> m_Callback;
} callback(a_Callback);
return m_ChunkMap->DoWithChunk(a_ChunkX, a_ChunkZ, callback);
}
bool cWorld::DoWithChunkAt(Vector3i a_BlockPos, std::function<bool(cChunk &)> a_Callback)
{
return m_ChunkMap->DoWithChunkAt(a_BlockPos, a_Callback);
}
void cWorld::GrowTree(int a_X, int a_Y, int a_Z)
{
if (GetBlock(a_X, a_Y, a_Z) == E_BLOCK_SAPLING)
{
// There is a sapling here, grow a tree according to its type:
GrowTreeFromSapling(a_X, a_Y, a_Z, GetBlockMeta(a_X, a_Y, a_Z));
}
else
{
// There is nothing here, grow a tree based on the current biome here:
GrowTreeByBiome(a_X, a_Y, a_Z);
}
}
void cWorld::GrowTreeFromSapling(int a_X, int a_Y, int a_Z, NIBBLETYPE a_SaplingMeta)
{
cNoise Noise(m_Generator.GetSeed());
sSetBlockVector Logs, Other;
auto WorldAge = static_cast<int>(std::chrono::duration_cast<cTickTimeLong>(m_WorldAge).count() & 0xffffffff);
switch (a_SaplingMeta & 0x07)
{
case E_META_SAPLING_APPLE: GetAppleTreeImage (a_X, a_Y, a_Z, Noise, WorldAge, Logs, Other); break;
case E_META_SAPLING_BIRCH: GetBirchTreeImage (a_X, a_Y, a_Z, Noise, WorldAge, Logs, Other); break;
case E_META_SAPLING_CONIFER: GetConiferTreeImage(a_X, a_Y, a_Z, Noise, WorldAge, Logs, Other); break;
case E_META_SAPLING_ACACIA: GetAcaciaTreeImage (a_X, a_Y, a_Z, Noise, WorldAge, Logs, Other); break;
case E_META_SAPLING_JUNGLE:
{
bool IsLarge = GetLargeTreeAdjustment(*this, a_X, a_Y, a_Z, a_SaplingMeta);
GetJungleTreeImage (a_X, a_Y, a_Z, Noise, WorldAge, Logs, Other, IsLarge);
break;
}
case E_META_SAPLING_DARK_OAK:
{
if (!GetLargeTreeAdjustment(*this, a_X, a_Y, a_Z, a_SaplingMeta))
{
return;
}
GetDarkoakTreeImage(a_X, a_Y, a_Z, Noise, WorldAge, Logs, Other);
break;
}
}
Other.insert(Other.begin(), Logs.begin(), Logs.end());
Logs.clear();
GrowTreeImage(Other);
}
void cWorld::GrowTreeByBiome(int a_X, int a_Y, int a_Z)
{
cNoise Noise(m_Generator.GetSeed());
sSetBlockVector Logs, Other;
GetTreeImageByBiome(a_X, a_Y, a_Z, Noise, static_cast<int>(std::chrono::duration_cast<cTickTimeLong>(m_WorldAge).count() & 0xffffffff), GetBiomeAt(a_X, a_Z), Logs, Other);
Other.insert(Other.begin(), Logs.begin(), Logs.end());
Logs.clear();
GrowTreeImage(Other);
}
void cWorld::GrowTreeImage(const sSetBlockVector & a_Blocks)
{
// Check that the tree has place to grow
2016-01-12 13:04:59 +00:00
// Make a copy of the log blocks:
sSetBlockVector b2;
for (sSetBlockVector::const_iterator itr = a_Blocks.begin(); itr != a_Blocks.end(); ++itr)
{
if (itr->m_BlockType == E_BLOCK_LOG)
{
b2.push_back(*itr);
}
} // for itr - a_Blocks[]
2016-01-12 13:04:59 +00:00
// Query blocktypes and metas at those log blocks:
if (!GetBlocks(b2, false))
{
return;
}
2016-01-12 13:04:59 +00:00
// Check that at each log's coord there's an block allowed to be overwritten:
for (sSetBlockVector::const_iterator itr = b2.begin(); itr != b2.end(); ++itr)
{
switch (itr->m_BlockType)
{
CASE_TREE_ALLOWED_BLOCKS:
{
break;
}
default:
{
return;
}
}
} // for itr - b2[]
2016-01-12 13:04:59 +00:00
// All ok, replace blocks with the tree image:
m_ChunkMap->ReplaceTreeBlocks(a_Blocks);
}
bool cWorld::GrowRipePlant(int a_BlockX, int a_BlockY, int a_BlockZ, bool a_IsByBonemeal)
{
2017-06-13 19:35:30 +00:00
auto & random = GetRandomProvider();
BLOCKTYPE BlockType;
NIBBLETYPE BlockMeta;
GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
switch (BlockType)
{
2017-02-14 10:13:55 +00:00
case E_BLOCK_BEETROOTS:
{
if ((a_IsByBonemeal && !m_IsBeetrootsBonemealable) || (BlockMeta >= 3))
{
return false;
}
if (!a_IsByBonemeal)
{
++BlockMeta;
}
else
{
BlockMeta += 1;
BlockMeta = std::min(BlockMeta, static_cast<NIBBLETYPE>(3));
}
FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
case E_BLOCK_CARROTS:
{
if ((a_IsByBonemeal && !m_IsCarrotsBonemealable) || (BlockMeta >= 7))
{
return false;
}
if (!a_IsByBonemeal)
{
++BlockMeta;
}
else
{
2017-06-14 15:35:34 +00:00
BlockMeta += random.RandInt<NIBBLETYPE>(2, 5);
BlockMeta = std::min(BlockMeta, static_cast<NIBBLETYPE>(7));
}
FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
2014-12-13 19:31:37 +00:00
case E_BLOCK_COCOA_POD:
{
NIBBLETYPE TypeMeta = BlockMeta & 0x03;
2014-12-14 12:14:48 +00:00
int GrowState = BlockMeta >> 2;
2014-12-13 19:31:37 +00:00
if (GrowState >= 2)
2014-12-13 19:31:37 +00:00
{
return false;
2014-12-13 19:31:37 +00:00
}
++GrowState;
FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, static_cast<NIBBLETYPE>(GrowState << 2 | TypeMeta));
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
2014-12-13 19:31:37 +00:00
}
case E_BLOCK_CROPS:
{
if ((a_IsByBonemeal && !m_IsCropsBonemealable) || (BlockMeta >= 7))
{
return false;
}
if (!a_IsByBonemeal)
{
++BlockMeta;
}
else
{
2017-06-14 15:35:34 +00:00
BlockMeta += random.RandInt<NIBBLETYPE>(2, 5);
BlockMeta = std::min(BlockMeta, static_cast<NIBBLETYPE>(7));
}
FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
2016-01-12 13:04:59 +00:00
case E_BLOCK_MELON_STEM:
{
if (BlockMeta < 7)
{
if (a_IsByBonemeal && !m_IsMelonStemBonemealable)
{
return false;
}
if (!a_IsByBonemeal)
{
++BlockMeta;
}
else
{
2017-06-14 15:35:34 +00:00
BlockMeta += random.RandInt<NIBBLETYPE>(2, 5);
BlockMeta = std::min(BlockMeta, static_cast<NIBBLETYPE>(7));
}
FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
}
else
{
if (a_IsByBonemeal && !m_IsMelonBonemealable)
{
return false;
}
if (!GrowMelonPumpkin(a_BlockX, a_BlockY, a_BlockZ, BlockType))
{
return false;
}
}
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
2016-01-12 13:04:59 +00:00
case E_BLOCK_POTATOES:
{
if ((a_IsByBonemeal && !m_IsPotatoesBonemealable) || (BlockMeta >= 7))
{
return false;
}
if (!a_IsByBonemeal)
{
++BlockMeta;
}
else
{
2017-06-14 15:35:34 +00:00
BlockMeta += random.RandInt<NIBBLETYPE>(2, 5);
BlockMeta = std::min(BlockMeta, static_cast<NIBBLETYPE>(7));
}
FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
2016-01-12 13:04:59 +00:00
case E_BLOCK_PUMPKIN_STEM:
{
if (BlockMeta < 7)
{
if (a_IsByBonemeal && !m_IsPumpkinStemBonemealable)
{
return false;
}
if (!a_IsByBonemeal)
{
++BlockMeta;
}
else
{
2017-06-14 15:35:34 +00:00
BlockMeta += random.RandInt<NIBBLETYPE>(2, 5);
BlockMeta = std::min(BlockMeta, static_cast<NIBBLETYPE>(7));
}
FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, BlockMeta);
}
else
{
if (a_IsByBonemeal && !m_IsPumpkinBonemealable)
{
return false;
}
if (!GrowMelonPumpkin(a_BlockX, a_BlockY, a_BlockZ, BlockType))
{
return false;
}
}
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
2016-01-12 13:04:59 +00:00
case E_BLOCK_SAPLING:
{
if (a_IsByBonemeal && !m_IsSaplingBonemealable)
{
return false;
}
NIBBLETYPE TypeMeta = BlockMeta & 0x07;
int GrowState = BlockMeta >> 3;
if (GrowState < 1)
{
// Non-bonemeal forces a growth, while bonemeal only has a chance of growing it
if (!a_IsByBonemeal)
{
++GrowState;
}
2017-06-13 19:35:30 +00:00
else if (random.RandBool(0.45))
{
++GrowState;
}
FastSetBlock(a_BlockX, a_BlockY, a_BlockZ, BlockType, static_cast<NIBBLETYPE>(GrowState << 3 | TypeMeta));
}
2017-06-13 19:35:30 +00:00
else if (random.RandBool(0.45))
{
GrowTreeFromSapling(a_BlockX, a_BlockY, a_BlockZ, BlockMeta);
}
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
2016-01-12 13:04:59 +00:00
case E_BLOCK_GRASS:
{
if (a_IsByBonemeal && !m_IsGrassBonemealable)
{
return false;
}
2017-06-13 19:35:30 +00:00
auto & r1 = GetRandomProvider();
for (int i = 0; i < 60; i++)
{
2017-06-13 19:35:30 +00:00
int OfsX = (r1.RandInt(3) + r1.RandInt(3) + r1.RandInt(3) + r1.RandInt(3)) / 2 - 3;
int OfsY = r1.RandInt(3) + r1.RandInt(3) - 3;
int OfsZ = (r1.RandInt(3) + r1.RandInt(3) + r1.RandInt(3) + r1.RandInt(3)) / 2 - 3;
BLOCKTYPE Ground = GetBlock(a_BlockX + OfsX, a_BlockY + OfsY, a_BlockZ + OfsZ);
if (Ground != E_BLOCK_GRASS)
{
continue;
}
BLOCKTYPE Above = GetBlock(a_BlockX + OfsX, a_BlockY + OfsY + 1, a_BlockZ + OfsZ);
if (Above != E_BLOCK_AIR)
{
continue;
}
BLOCKTYPE SpawnType;
NIBBLETYPE SpawnMeta = 0;
2017-06-13 19:35:30 +00:00
switch (r1.RandInt(10))
{
case 0: SpawnType = E_BLOCK_YELLOW_FLOWER; break;
case 1: SpawnType = E_BLOCK_RED_ROSE; break;
default:
{
SpawnType = E_BLOCK_TALL_GRASS;
SpawnMeta = E_META_TALL_GRASS_GRASS;
break;
}
} // switch (random spawn block type)
FastSetBlock(a_BlockX + OfsX, a_BlockY + OfsY + 1, a_BlockZ + OfsZ, SpawnType, SpawnMeta);
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX + OfsX, a_BlockY + OfsY, a_BlockZ + OfsZ, 0);
} // for i - 50 times
return true;
}
2016-01-12 13:04:59 +00:00
case E_BLOCK_SUGARCANE:
{
if (a_IsByBonemeal && !m_IsSugarcaneBonemealable)
{
return false;
}
if (m_ChunkMap->GrowSugarcane(a_BlockX, a_BlockY, a_BlockZ, 1) == 0)
{
return false;
}
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
2016-01-12 13:04:59 +00:00
case E_BLOCK_CACTUS:
{
if (a_IsByBonemeal && !m_IsCactusBonemealable)
{
return false;
}
if (m_ChunkMap->GrowCactus(a_BlockX, a_BlockY, a_BlockZ, 1) == 0)
{
return false;
}
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
case E_BLOCK_TALL_GRASS:
{
if (a_IsByBonemeal && !m_IsTallGrassBonemealable)
{
return false;
}
if (!m_ChunkMap->GrowTallGrass(a_BlockX, a_BlockY, a_BlockZ))
{
return false;
}
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
case E_BLOCK_BIG_FLOWER:
{
if (a_IsByBonemeal && !m_IsBigFlowerBonemealable)
{
return false;
}
if (BlockMeta & 8) // the upper flower block does not save the type of the flower
{
GetBlockTypeMeta(a_BlockX, a_BlockY - 1, a_BlockZ, BlockType, BlockMeta);
if (BlockType != E_BLOCK_BIG_FLOWER)
{
return false;
}
}
if (
(BlockMeta == E_META_BIG_FLOWER_DOUBLE_TALL_GRASS) ||
(BlockMeta == E_META_BIG_FLOWER_LARGE_FERN)
) // tall grass and fern do not work
{
return false;
}
// spawn flower item
BroadcastSoundParticleEffect(EffectID::PARTICLE_HAPPY_VILLAGER, a_BlockX, a_BlockY, a_BlockZ, 0);
cItems FlowerItem;
FlowerItem.Add(E_BLOCK_BIG_FLOWER, 1, BlockMeta);
SpawnItemPickups(FlowerItem, a_BlockX + 0.5, a_BlockY + 0.5, a_BlockZ + 0.5);
return true;
}
} // switch (BlockType)
return false;
}
int cWorld::GrowCactus(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow)
{
return m_ChunkMap->GrowCactus(a_BlockX, a_BlockY, a_BlockZ, a_NumBlocksToGrow);
}
bool cWorld::GrowMelonPumpkin(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType)
{
2017-06-13 19:35:30 +00:00
return m_ChunkMap->GrowMelonPumpkin(a_BlockX, a_BlockY, a_BlockZ, a_BlockType);
}
int cWorld::GrowSugarcane(int a_BlockX, int a_BlockY, int a_BlockZ, int a_NumBlocksToGrow)
{
return m_ChunkMap->GrowSugarcane(a_BlockX, a_BlockY, a_BlockZ, a_NumBlocksToGrow);
}
EMCSBiome cWorld::GetBiomeAt (int a_BlockX, int a_BlockZ)
{
return m_ChunkMap->GetBiomeAt(a_BlockX, a_BlockZ);
}
bool cWorld::SetBiomeAt(int a_BlockX, int a_BlockZ, EMCSBiome a_Biome)
{
return m_ChunkMap->SetBiomeAt(a_BlockX, a_BlockZ, a_Biome);
}
bool cWorld::SetAreaBiome(int a_MinX, int a_MaxX, int a_MinZ, int a_MaxZ, EMCSBiome a_Biome)
{
return m_ChunkMap->SetAreaBiome(a_MinX, a_MaxX, a_MinZ, a_MaxZ, a_Biome);
}
bool cWorld::SetAreaBiome(const cCuboid & a_Area, EMCSBiome a_Biome)
{
return SetAreaBiome(
std::min(a_Area.p1.x, a_Area.p2.x), std::max(a_Area.p1.x, a_Area.p2.x),
std::min(a_Area.p1.z, a_Area.p2.z), std::max(a_Area.p1.z, a_Area.p2.z),
a_Biome
);
}
void cWorld::SetMaxViewDistance(int a_MaxViewDistance)
{
m_MaxViewDistance = Clamp(a_MaxViewDistance, cClientHandle::MIN_VIEW_DISTANCE, cClientHandle::MAX_VIEW_DISTANCE);
}
void cWorld::SetBlock(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, bool a_SendToClients)
{
2014-09-12 17:07:20 +00:00
m_ChunkMap->SetBlock(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta, a_SendToClients);
}
2016-07-06 10:39:56 +00:00
void cWorld::SetBlockMeta(int a_X, int a_Y, int a_Z, NIBBLETYPE a_MetaData, bool a_ShouldMarkDirty, bool a_ShouldInformClients)
{
2016-07-06 10:39:56 +00:00
m_ChunkMap->SetBlockMeta(a_X, a_Y, a_Z, a_MetaData, a_ShouldMarkDirty, a_ShouldInformClients);
}
NIBBLETYPE cWorld::GetBlockSkyLight(int a_X, int a_Y, int a_Z)
{
return m_ChunkMap->GetBlockSkyLight(a_X, a_Y, a_Z);
}
NIBBLETYPE cWorld::GetBlockBlockLight(int a_BlockX, int a_BlockY, int a_BlockZ)
{
return m_ChunkMap->GetBlockBlockLight(a_BlockX, a_BlockY, a_BlockZ);
}
bool cWorld::GetBlockTypeMeta(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta)
{
return m_ChunkMap->GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_BlockMeta);
}
bool cWorld::GetBlockInfo(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_Meta, NIBBLETYPE & a_SkyLight, NIBBLETYPE & a_BlockLight)
{
return m_ChunkMap->GetBlockInfo(a_BlockX, a_BlockY, a_BlockZ, a_BlockType, a_Meta, a_SkyLight, a_BlockLight);
}
bool cWorld::WriteBlockArea(cBlockArea & a_Area, int a_MinBlockX, int a_MinBlockY, int a_MinBlockZ, int a_DataTypes)
{
return m_ChunkMap->WriteBlockArea(a_Area, a_MinBlockX, a_MinBlockY, a_MinBlockZ, a_DataTypes);
}
void cWorld::SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_FlyAwaySpeed, bool IsPlayerCreated)
{
2017-06-13 19:35:30 +00:00
auto & Random = GetRandomProvider();
2013-12-09 23:43:06 +00:00
a_FlyAwaySpeed /= 100; // Pre-divide, so that we don't have to divide each time inside the loop
for (cItems::const_iterator itr = a_Pickups.begin(); itr != a_Pickups.end(); ++itr)
{
if (!IsValidItem(itr->m_ItemType) || (itr->m_ItemType == E_BLOCK_AIR))
{
// Don't spawn pickup if item isn't even valid; should prevent client crashing too
continue;
}
2017-06-13 19:35:30 +00:00
float SpeedX = static_cast<float>(a_FlyAwaySpeed * Random.RandInt(-5, 5));
float SpeedY = static_cast<float>(a_FlyAwaySpeed * Random.RandInt(50));
float SpeedZ = static_cast<float>(a_FlyAwaySpeed * Random.RandInt(-5, 5));
2016-01-12 13:04:59 +00:00
auto Pickup = cpp14::make_unique<cPickup>(
2013-09-18 21:20:08 +00:00
a_BlockX, a_BlockY, a_BlockZ,
*itr, IsPlayerCreated, SpeedX, SpeedY, SpeedZ
);
auto PickupPtr = Pickup.get();
PickupPtr->Initialize(std::move(Pickup), *this);
}
}
void cWorld::SpawnItemPickups(const cItems & a_Pickups, double a_BlockX, double a_BlockY, double a_BlockZ, double a_SpeedX, double a_SpeedY, double a_SpeedZ, bool IsPlayerCreated)
{
for (cItems::const_iterator itr = a_Pickups.begin(); itr != a_Pickups.end(); ++itr)
{
if (!IsValidItem(itr->m_ItemType) || (itr->m_ItemType == E_BLOCK_AIR))
{
continue;
}
auto Pickup = cpp14::make_unique<cPickup>(
2013-09-18 21:20:08 +00:00
a_BlockX, a_BlockY, a_BlockZ,
*itr, IsPlayerCreated, static_cast<float>(a_SpeedX), static_cast<float>(a_SpeedY), static_cast<float>(a_SpeedZ)
);
auto PickupPtr = Pickup.get();
PickupPtr->Initialize(std::move(Pickup), *this);
}
}
UInt32 cWorld::SpawnItemPickup(double a_PosX, double a_PosY, double a_PosZ, const cItem & a_Item, float a_SpeedX, float a_SpeedY, float a_SpeedZ, int a_LifetimeTicks, bool a_CanCombine)
{
auto Pickup = cpp14::make_unique<cPickup>(a_PosX, a_PosY, a_PosZ, a_Item, false, a_SpeedX, a_SpeedY, a_SpeedZ, a_LifetimeTicks, a_CanCombine);
auto PickupPtr = Pickup.get();
if (!PickupPtr->Initialize(std::move(Pickup), *this))
{
return cEntity::INVALID_ID;
}
return PickupPtr->GetUniqueID();
}
2015-03-21 14:18:17 +00:00
UInt32 cWorld::SpawnFallingBlock(int a_X, int a_Y, int a_Z, BLOCKTYPE BlockType, NIBBLETYPE BlockMeta)
{
auto FallingBlock = cpp14::make_unique<cFallingBlock>(Vector3i(a_X, a_Y, a_Z), BlockType, BlockMeta);
auto FallingBlockPtr = FallingBlock.get();
auto ID = FallingBlock->GetUniqueID();
if (!FallingBlockPtr->Initialize(std::move(FallingBlock), *this))
{
return cEntity::INVALID_ID;
}
return ID;
}
2015-03-21 14:18:17 +00:00
UInt32 cWorld::SpawnExperienceOrb(double a_X, double a_Y, double a_Z, int a_Reward)
{
2014-04-18 11:54:17 +00:00
if (a_Reward < 1)
{
2015-03-21 14:18:17 +00:00
LOGWARNING("%s: Attempting to create an experience orb with non-positive reward!", __FUNCTION__);
return cEntity::INVALID_ID;
2014-04-18 11:54:17 +00:00
}
auto ExpOrb = cpp14::make_unique<cExpOrb>(a_X, a_Y, a_Z, a_Reward);
auto ExpOrbPtr = ExpOrb.get();
if (!ExpOrbPtr->Initialize(std::move(ExpOrb), *this))
{
return cEntity::INVALID_ID;
}
return ExpOrbPtr->GetUniqueID();
}
2015-03-21 14:18:17 +00:00
UInt32 cWorld::SpawnMinecart(double a_X, double a_Y, double a_Z, int a_MinecartType, const cItem & a_Content, int a_BlockHeight)
2014-01-12 13:33:32 +00:00
{
std::unique_ptr<cMinecart> Minecart;
2014-01-12 13:33:32 +00:00
switch (a_MinecartType)
{
case E_ITEM_MINECART: Minecart = cpp14::make_unique<cRideableMinecart>(a_X, a_Y, a_Z, a_Content, a_BlockHeight); break;
case E_ITEM_CHEST_MINECART: Minecart = cpp14::make_unique<cMinecartWithChest>(a_X, a_Y, a_Z); break;
case E_ITEM_FURNACE_MINECART: Minecart = cpp14::make_unique<cMinecartWithFurnace>(a_X, a_Y, a_Z); break;
case E_ITEM_MINECART_WITH_TNT: Minecart = cpp14::make_unique<cMinecartWithTNT>(a_X, a_Y, a_Z); break;
case E_ITEM_MINECART_WITH_HOPPER: Minecart = cpp14::make_unique<cMinecartWithHopper>(a_X, a_Y, a_Z); break;
2014-01-12 13:33:32 +00:00
default:
{
2015-03-21 14:18:17 +00:00
return cEntity::INVALID_ID;
2014-01-12 13:33:32 +00:00
}
} // switch (a_MinecartType)
auto MinecartPtr = Minecart.get();
if (!MinecartPtr->Initialize(std::move(Minecart), *this))
{
return cEntity::INVALID_ID;
}
return MinecartPtr->GetUniqueID();
2014-01-12 13:33:32 +00:00
}
UInt32 cWorld::SpawnBoat(double a_X, double a_Y, double a_Z, cBoat::eMaterial a_Material)
{
auto Boat = cpp14::make_unique<cBoat>(a_X, a_Y, a_Z, a_Material);
auto BoatPtr = Boat.get();
if (!BoatPtr->Initialize(std::move(Boat), *this))
{
return cEntity::INVALID_ID;
}
return BoatPtr->GetUniqueID();
}
2015-03-21 14:18:17 +00:00
UInt32 cWorld::SpawnPrimedTNT(double a_X, double a_Y, double a_Z, int a_FuseTicks, double a_InitialVelocityCoeff)
{
auto TNT = cpp14::make_unique<cTNTEntity>(a_X, a_Y, a_Z, a_FuseTicks);
auto TNTPtr = TNT.get();
if (!TNTPtr->Initialize(std::move(TNT), *this))
{
return cEntity::INVALID_ID;
}
2017-06-13 19:35:30 +00:00
auto & Random = GetRandomProvider();
TNTPtr->SetSpeed(
2017-06-13 19:35:30 +00:00
a_InitialVelocityCoeff * Random.RandInt(-1, 1),
a_InitialVelocityCoeff * 2,
2017-06-13 19:35:30 +00:00
a_InitialVelocityCoeff * Random.RandInt(-1, 1)
2014-03-20 08:28:29 +00:00
);
return TNTPtr->GetUniqueID();
}
void cWorld::SetBlocks(const sSetBlockVector & a_Blocks)
{
m_ChunkMap->SetBlocks(a_Blocks);
}
void cWorld::ReplaceBlocks(const sSetBlockVector & a_Blocks, BLOCKTYPE a_FilterBlockType)
{
m_ChunkMap->ReplaceBlocks(a_Blocks, a_FilterBlockType);
}
bool cWorld::GetBlocks(sSetBlockVector & a_Blocks, bool a_ContinueOnFailure)
{
return m_ChunkMap->GetBlocks(a_Blocks, a_ContinueOnFailure);
}
bool cWorld::DigBlock(int a_X, int a_Y, int a_Z)
{
cBlockHandler * Handler = cBlockInfo::GetHandler(GetBlock(a_X, a_Y, a_Z));
2014-02-01 13:06:32 +00:00
cChunkInterface ChunkInterface(GetChunkMap());
Handler->OnDestroyed(ChunkInterface, *this, a_X, a_Y, a_Z);
return m_ChunkMap->DigBlock(a_X, a_Y, a_Z);
}
2017-07-31 19:50:40 +00:00
void cWorld::SendBlockTo(int a_X, int a_Y, int a_Z, cPlayer & a_Player)
{
m_ChunkMap->SendBlockTo(a_X, a_Y, a_Z, a_Player);
}
int cWorld::GetHeight(int a_X, int a_Z)
{
return m_ChunkMap->GetHeight(a_X, a_Z);
}
bool cWorld::TryGetHeight(int a_BlockX, int a_BlockZ, int & a_Height)
{
return m_ChunkMap->TryGetHeight(a_BlockX, a_BlockZ, a_Height);
}
1.9 / 1.9.2 / 1.9.3 / 1.9.4 protocol support (#3135) * Semistable update to 15w31a I'm going through snapshots in a sequential order since it should make things easier, and since protocol version history is written. * Update to 15w34b protocol Also, fix an issue with the Entity Equipment packet from the past version. Clients are able to connect and do stuff! * Partially update to 15w35e Chunk data doesn't work, but the client joins. I'm waiting to do chunk data because chunk data has an incomplete format until 15w36d. * Add '/blk' debug command This command lets one see what block they are looking at, and makes figuring out what's supposed to be where in a highly broken chunk possible. * Fix CRLF normalization in CheckBasicStyle.lua Normally, this doesn't cause an issue, but when running from cygwin, it detects the CR as whitespace and creates thousands of violations for every single line. Lua, when run on windows, will normalize automatically, but when run via cygwin, it won't. The bug was simply that gsub was returning a replaced version, but not changing the parameter, so the replaced version was ignored. * Update to 15w40b This includes chunk serialization. Fully functional chunk serialization for 1.9. I'm not completely happy with the chunk serialization as-is (correct use of palettes would be great), but cuberite also doesn't skip sending empty chunks so this performance optimization should probably come later. The creation of a full buffer is suboptimal, but it's the easiest way to implement this code. * Write long-by-long rather than creating a buffer This is a bit faster and should be equivalent. However, the code still doesn't look too good. * Update to 15w41a protocol This includes the new set passengers packet, which works off of the ridden entity, not the rider. That means, among other things, that information about the previously ridden vehicle is needed when detaching. So a new method with that info was added. * Update to 15w45a * 15w51b protocol * Update to 1.9.0 protocol Closes #3067. There are still a few things that need to be worked out (picking up items, effects, particles, and most importantly inventory), but in general this should work. I'll make a few more changes tomorrow to get the rest of the protocol set up, along with 1.9.1/1.9.2 (which did make a few changes). Chunks, however, _are_ working, along with most other parts of the game (placing/breaking blocks). * Fix item pickup packet not working That was a silly mistake, but at least it was an easy one. * 1.9.2 protocol support * Fix version info found in server list ping Thus, the client reports that it can connect rather than saying that the server is out of date. This required creating separate classes for 1.9.1 and 1.9.2, unfortunately. * Fix build errors generated by clang These didn't happen in MSVC. * Add protocol19x.cpp and protocol19x.h to CMakeLists * Ignore warnings in protocol19x that are ignored in protocol18x * Document BLOCK_FACE and DIG_STATUS constants * Fix BLOCK_FACE links and add separate section for DIG_STATUS * Fix bat animation and object spawning The causes of both of these are explained in #3135, but the gist is that both were typos. * Implement Use Item packet This means that buckets, bows, fishing rods, and several other similar items now work when not looking at a block. * Handle DIG_STATUS_SWAP_ITEM_IN_HAND * Add support for spawn eggs and potions The items are transformed from the 1.9 version to the 1.8 version when reading and transformed back when sending. * Remove spammy potion debug logging * Fix wolf collar color metadata The wrong type was being used, causing several clientside issues (including the screen going black). * Fix 1.9 chunk sending in the nether The nether and the end don't send skylight. * Fix clang build errors * Fix water bottles becoming mundane potions This happened because the can become splash potion bit got set incorrectly. Water bottles and mundane potions are only differentiated by the fact that water bottles have a metadata of 0, so setting that bit made it a mundane potion. Also add missing break statements to the read item NBT switch, which would otherwise break items with custom names and also cause incorrect "Unimplemented NBT data when parsing!" logging. * Copy Protocol18x as Protocol19x Aditionally, method and class names have been swapped to clean up other diffs. This commit is only added to make the following diffs more readable; it doesn't make any other changes (beyond class names). * Make thrown potions use the correct appearence This was caused by potions now using metadata. * Add missing api doc for cSplashPotionEntity::GetItem * Fix compile error in SplashPotionEntity.cpp * Fix fix of cSplashPotionEntity API doc * Temporarilly disable fall damage particles These were causing issues in 1.9 due to the changed effect ID. * Properly send a kick packet when connecting with an invalid version This means that the client no longer waits on the server screen with no indication whatsoever. However, right now the server list ping isn't implemented for unknown versions, so it'll only load "Old" on the ping. I also added a GetVarIntSize method to cByteBuffer. This helps clean up part of the code here (and I think it could clean up other parts), but it may make sense for it to be moved elsewhere (or declared in a different way). * Handle server list pings from unrecognized versions This isn't the cleanest way of writing it (it feels odd to use ProtocolRecognizer to send packets, and the addition of m_InPingForUnrecognizedVersion feels like the wrong technique), but it works and I can't think of a better way (apart from creating a full separate protocol class to handle only the ping... which would be worse). * Use cPacketizer for the disconnect packet This also should fix clang build errors. * Add 1.9.3 / 1.9.4 support * Fix incorrect indentation in APIDesc
2016-05-14 19:12:42 +00:00
void cWorld::BroadcastAttachEntity(const cEntity & a_Entity, const cEntity & a_Vehicle)
{
1.9 / 1.9.2 / 1.9.3 / 1.9.4 protocol support (#3135) * Semistable update to 15w31a I'm going through snapshots in a sequential order since it should make things easier, and since protocol version history is written. * Update to 15w34b protocol Also, fix an issue with the Entity Equipment packet from the past version. Clients are able to connect and do stuff! * Partially update to 15w35e Chunk data doesn't work, but the client joins. I'm waiting to do chunk data because chunk data has an incomplete format until 15w36d. * Add '/blk' debug command This command lets one see what block they are looking at, and makes figuring out what's supposed to be where in a highly broken chunk possible. * Fix CRLF normalization in CheckBasicStyle.lua Normally, this doesn't cause an issue, but when running from cygwin, it detects the CR as whitespace and creates thousands of violations for every single line. Lua, when run on windows, will normalize automatically, but when run via cygwin, it won't. The bug was simply that gsub was returning a replaced version, but not changing the parameter, so the replaced version was ignored. * Update to 15w40b This includes chunk serialization. Fully functional chunk serialization for 1.9. I'm not completely happy with the chunk serialization as-is (correct use of palettes would be great), but cuberite also doesn't skip sending empty chunks so this performance optimization should probably come later. The creation of a full buffer is suboptimal, but it's the easiest way to implement this code. * Write long-by-long rather than creating a buffer This is a bit faster and should be equivalent. However, the code still doesn't look too good. * Update to 15w41a protocol This includes the new set passengers packet, which works off of the ridden entity, not the rider. That means, among other things, that information about the previously ridden vehicle is needed when detaching. So a new method with that info was added. * Update to 15w45a * 15w51b protocol * Update to 1.9.0 protocol Closes #3067. There are still a few things that need to be worked out (picking up items, effects, particles, and most importantly inventory), but in general this should work. I'll make a few more changes tomorrow to get the rest of the protocol set up, along with 1.9.1/1.9.2 (which did make a few changes). Chunks, however, _are_ working, along with most other parts of the game (placing/breaking blocks). * Fix item pickup packet not working That was a silly mistake, but at least it was an easy one. * 1.9.2 protocol support * Fix version info found in server list ping Thus, the client reports that it can connect rather than saying that the server is out of date. This required creating separate classes for 1.9.1 and 1.9.2, unfortunately. * Fix build errors generated by clang These didn't happen in MSVC. * Add protocol19x.cpp and protocol19x.h to CMakeLists * Ignore warnings in protocol19x that are ignored in protocol18x * Document BLOCK_FACE and DIG_STATUS constants * Fix BLOCK_FACE links and add separate section for DIG_STATUS * Fix bat animation and object spawning The causes of both of these are explained in #3135, but the gist is that both were typos. * Implement Use Item packet This means that buckets, bows, fishing rods, and several other similar items now work when not looking at a block. * Handle DIG_STATUS_SWAP_ITEM_IN_HAND * Add support for spawn eggs and potions The items are transformed from the 1.9 version to the 1.8 version when reading and transformed back when sending. * Remove spammy potion debug logging * Fix wolf collar color metadata The wrong type was being used, causing several clientside issues (including the screen going black). * Fix 1.9 chunk sending in the nether The nether and the end don't send skylight. * Fix clang build errors * Fix water bottles becoming mundane potions This happened because the can become splash potion bit got set incorrectly. Water bottles and mundane potions are only differentiated by the fact that water bottles have a metadata of 0, so setting that bit made it a mundane potion. Also add missing break statements to the read item NBT switch, which would otherwise break items with custom names and also cause incorrect "Unimplemented NBT data when parsing!" logging. * Copy Protocol18x as Protocol19x Aditionally, method and class names have been swapped to clean up other diffs. This commit is only added to make the following diffs more readable; it doesn't make any other changes (beyond class names). * Make thrown potions use the correct appearence This was caused by potions now using metadata. * Add missing api doc for cSplashPotionEntity::GetItem * Fix compile error in SplashPotionEntity.cpp * Fix fix of cSplashPotionEntity API doc * Temporarilly disable fall damage particles These were causing issues in 1.9 due to the changed effect ID. * Properly send a kick packet when connecting with an invalid version This means that the client no longer waits on the server screen with no indication whatsoever. However, right now the server list ping isn't implemented for unknown versions, so it'll only load "Old" on the ping. I also added a GetVarIntSize method to cByteBuffer. This helps clean up part of the code here (and I think it could clean up other parts), but it may make sense for it to be moved elsewhere (or declared in a different way). * Handle server list pings from unrecognized versions This isn't the cleanest way of writing it (it feels odd to use ProtocolRecognizer to send packets, and the addition of m_InPingForUnrecognizedVersion feels like the wrong technique), but it works and I can't think of a better way (apart from creating a full separate protocol class to handle only the ping... which would be worse). * Use cPacketizer for the disconnect packet This also should fix clang build errors. * Add 1.9.3 / 1.9.4 support * Fix incorrect indentation in APIDesc
2016-05-14 19:12:42 +00:00
m_ChunkMap->BroadcastAttachEntity(a_Entity, a_Vehicle);
}
2015-05-19 11:28:31 +00:00
void cWorld::BroadcastBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, Byte a_Byte1, Byte a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastBlockAction(a_BlockX, a_BlockY, a_BlockZ, static_cast<char>(a_Byte1), static_cast<char>(a_Byte2), a_BlockType, a_Exclude);
}
2015-03-21 14:18:17 +00:00
void cWorld::BroadcastBlockBreakAnimation(UInt32 a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastBlockBreakAnimation(a_EntityID, a_BlockX, a_BlockY, a_BlockZ, a_Stage, a_Exclude);
}
void cWorld::BroadcastBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastBlockEntity(a_BlockX, a_BlockY, a_BlockZ, a_Exclude);
}
void cWorld::BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude, eMessageType a_ChatPrefix)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
ch->SendChat(a_Message, a_ChatPrefix);
}
}
void cWorld::BroadcastChat(const cCompositeChat & a_Message, const cClientHandle * a_Exclude)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
ch->SendChat(a_Message);
}
}
2016-12-15 19:21:43 +00:00
void cWorld::BroadcastCollectEntity(const cEntity & a_Entity, const cPlayer & a_Player, int a_Count, const cClientHandle * a_Exclude)
{
2016-12-15 19:21:43 +00:00
m_ChunkMap->BroadcastCollectEntity(a_Entity, a_Player, a_Count, a_Exclude);
}
void cWorld::BroadcastDestroyEntity(const cEntity & a_Entity, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastDestroyEntity(a_Entity, a_Exclude);
}
1.9 / 1.9.2 / 1.9.3 / 1.9.4 protocol support (#3135) * Semistable update to 15w31a I'm going through snapshots in a sequential order since it should make things easier, and since protocol version history is written. * Update to 15w34b protocol Also, fix an issue with the Entity Equipment packet from the past version. Clients are able to connect and do stuff! * Partially update to 15w35e Chunk data doesn't work, but the client joins. I'm waiting to do chunk data because chunk data has an incomplete format until 15w36d. * Add '/blk' debug command This command lets one see what block they are looking at, and makes figuring out what's supposed to be where in a highly broken chunk possible. * Fix CRLF normalization in CheckBasicStyle.lua Normally, this doesn't cause an issue, but when running from cygwin, it detects the CR as whitespace and creates thousands of violations for every single line. Lua, when run on windows, will normalize automatically, but when run via cygwin, it won't. The bug was simply that gsub was returning a replaced version, but not changing the parameter, so the replaced version was ignored. * Update to 15w40b This includes chunk serialization. Fully functional chunk serialization for 1.9. I'm not completely happy with the chunk serialization as-is (correct use of palettes would be great), but cuberite also doesn't skip sending empty chunks so this performance optimization should probably come later. The creation of a full buffer is suboptimal, but it's the easiest way to implement this code. * Write long-by-long rather than creating a buffer This is a bit faster and should be equivalent. However, the code still doesn't look too good. * Update to 15w41a protocol This includes the new set passengers packet, which works off of the ridden entity, not the rider. That means, among other things, that information about the previously ridden vehicle is needed when detaching. So a new method with that info was added. * Update to 15w45a * 15w51b protocol * Update to 1.9.0 protocol Closes #3067. There are still a few things that need to be worked out (picking up items, effects, particles, and most importantly inventory), but in general this should work. I'll make a few more changes tomorrow to get the rest of the protocol set up, along with 1.9.1/1.9.2 (which did make a few changes). Chunks, however, _are_ working, along with most other parts of the game (placing/breaking blocks). * Fix item pickup packet not working That was a silly mistake, but at least it was an easy one. * 1.9.2 protocol support * Fix version info found in server list ping Thus, the client reports that it can connect rather than saying that the server is out of date. This required creating separate classes for 1.9.1 and 1.9.2, unfortunately. * Fix build errors generated by clang These didn't happen in MSVC. * Add protocol19x.cpp and protocol19x.h to CMakeLists * Ignore warnings in protocol19x that are ignored in protocol18x * Document BLOCK_FACE and DIG_STATUS constants * Fix BLOCK_FACE links and add separate section for DIG_STATUS * Fix bat animation and object spawning The causes of both of these are explained in #3135, but the gist is that both were typos. * Implement Use Item packet This means that buckets, bows, fishing rods, and several other similar items now work when not looking at a block. * Handle DIG_STATUS_SWAP_ITEM_IN_HAND * Add support for spawn eggs and potions The items are transformed from the 1.9 version to the 1.8 version when reading and transformed back when sending. * Remove spammy potion debug logging * Fix wolf collar color metadata The wrong type was being used, causing several clientside issues (including the screen going black). * Fix 1.9 chunk sending in the nether The nether and the end don't send skylight. * Fix clang build errors * Fix water bottles becoming mundane potions This happened because the can become splash potion bit got set incorrectly. Water bottles and mundane potions are only differentiated by the fact that water bottles have a metadata of 0, so setting that bit made it a mundane potion. Also add missing break statements to the read item NBT switch, which would otherwise break items with custom names and also cause incorrect "Unimplemented NBT data when parsing!" logging. * Copy Protocol18x as Protocol19x Aditionally, method and class names have been swapped to clean up other diffs. This commit is only added to make the following diffs more readable; it doesn't make any other changes (beyond class names). * Make thrown potions use the correct appearence This was caused by potions now using metadata. * Add missing api doc for cSplashPotionEntity::GetItem * Fix compile error in SplashPotionEntity.cpp * Fix fix of cSplashPotionEntity API doc * Temporarilly disable fall damage particles These were causing issues in 1.9 due to the changed effect ID. * Properly send a kick packet when connecting with an invalid version This means that the client no longer waits on the server screen with no indication whatsoever. However, right now the server list ping isn't implemented for unknown versions, so it'll only load "Old" on the ping. I also added a GetVarIntSize method to cByteBuffer. This helps clean up part of the code here (and I think it could clean up other parts), but it may make sense for it to be moved elsewhere (or declared in a different way). * Handle server list pings from unrecognized versions This isn't the cleanest way of writing it (it feels odd to use ProtocolRecognizer to send packets, and the addition of m_InPingForUnrecognizedVersion feels like the wrong technique), but it works and I can't think of a better way (apart from creating a full separate protocol class to handle only the ping... which would be worse). * Use cPacketizer for the disconnect packet This also should fix clang build errors. * Add 1.9.3 / 1.9.4 support * Fix incorrect indentation in APIDesc
2016-05-14 19:12:42 +00:00
void cWorld::BroadcastDetachEntity(const cEntity & a_Entity, const cEntity & a_PreviousVehicle)
{
m_ChunkMap->BroadcastDetachEntity(a_Entity, a_PreviousVehicle);
}
2017-08-21 08:46:41 +00:00
void cWorld::BroadcastLeashEntity(const cEntity & a_Entity, const cEntity & a_EntityLeashedTo)
{
m_ChunkMap->BroadcastLeashEntity(a_Entity, a_EntityLeashedTo);
}
void cWorld::BroadcastUnleashEntity(const cEntity & a_Entity)
{
m_ChunkMap->BroadcastUnleashEntity(a_Entity);
}
void cWorld::BroadcastEntityEffect(const cEntity & a_Entity, int a_EffectID, int a_Amplifier, short a_Duration, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityEffect(a_Entity, a_EffectID, a_Amplifier, a_Duration, a_Exclude);
}
void cWorld::BroadcastEntityEquipment(const cEntity & a_Entity, short a_SlotNum, const cItem & a_Item, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityEquipment(a_Entity, a_SlotNum, a_Item, a_Exclude);
}
void cWorld::BroadcastEntityHeadLook(const cEntity & a_Entity, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityHeadLook(a_Entity, a_Exclude);
}
void cWorld::BroadcastEntityLook(const cEntity & a_Entity, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityLook(a_Entity, a_Exclude);
}
void cWorld::BroadcastEntityMetadata(const cEntity & a_Entity, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityMetadata(a_Entity, a_Exclude);
}
void cWorld::BroadcastEntityRelMove(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityRelMove(a_Entity, a_RelX, a_RelY, a_RelZ, a_Exclude);
}
void cWorld::BroadcastEntityRelMoveLook(const cEntity & a_Entity, char a_RelX, char a_RelY, char a_RelZ, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityRelMoveLook(a_Entity, a_RelX, a_RelY, a_RelZ, a_Exclude);
}
void cWorld::BroadcastEntityStatus(const cEntity & a_Entity, char a_Status, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityStatus(a_Entity, a_Status, a_Exclude);
}
void cWorld::BroadcastEntityVelocity(const cEntity & a_Entity, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityVelocity(a_Entity, a_Exclude);
}
void cWorld::BroadcastEntityAnimation(const cEntity & a_Entity, char a_Animation, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastEntityAnimation(a_Entity, a_Animation, a_Exclude);
}
2013-12-22 13:45:25 +00:00
void cWorld::BroadcastPlayerListAddPlayer(const cPlayer & a_Player, const cClientHandle * a_Exclude)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
ch->SendPlayerListAddPlayer(a_Player);
}
}
void cWorld::BroadcastPlayerListRemovePlayer(const cPlayer & a_Player, const cClientHandle * a_Exclude)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn())
{
continue;
}
ch->SendPlayerListRemovePlayer(a_Player);
}
}
void cWorld::BroadcastPlayerListUpdateGameMode(const cPlayer & a_Player, const cClientHandle * a_Exclude)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
ch->SendPlayerListUpdateGameMode(a_Player);
}
}
void cWorld::BroadcastPlayerListUpdatePing(const cPlayer & a_Player, const cClientHandle * a_Exclude)
2013-12-22 13:45:25 +00:00
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
ch->SendPlayerListUpdatePing(a_Player);
}
2013-12-22 13:45:25 +00:00
}
2014-09-26 15:37:19 +00:00
void cWorld::BroadcastPlayerListUpdateDisplayName(const cPlayer & a_Player, const AString & a_CustomName, const cClientHandle * a_Exclude)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
2014-09-26 15:37:19 +00:00
ch->SendPlayerListUpdateDisplayName(a_Player, a_CustomName);
}
}
void cWorld::BroadcastRemoveEntityEffect(const cEntity & a_Entity, int a_EffectID, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastRemoveEntityEffect(a_Entity, a_EffectID, a_Exclude);
}
2014-01-21 13:58:17 +00:00
void cWorld::BroadcastScoreboardObjective(const AString & a_Name, const AString & a_DisplayName, Byte a_Mode)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
2014-01-21 13:58:17 +00:00
{
continue;
}
ch->SendScoreboardObjective(a_Name, a_DisplayName, a_Mode);
}
}
void cWorld::BroadcastScoreUpdate(const AString & a_Objective, const AString & a_Player, cObjective::Score a_Score, Byte a_Mode)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
2014-01-21 13:58:17 +00:00
{
continue;
}
ch->SendScoreUpdate(a_Objective, a_Player, a_Score, a_Mode);
}
}
void cWorld::BroadcastDisplayObjective(const AString & a_Objective, cScoreboard::eDisplaySlot a_Display)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
2014-01-21 13:58:17 +00:00
{
continue;
}
ch->SendDisplayObjective(a_Objective, a_Display);
}
}
void cWorld::BroadcastSoundEffect(const AString & a_SoundName, double a_X, double a_Y, double a_Z, float a_Volume, float a_Pitch, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastSoundEffect(a_SoundName, a_X, a_Y, a_Z, a_Volume, a_Pitch, a_Exclude);
}
void cWorld::BroadcastSoundParticleEffect(const EffectID a_EffectID, int a_SrcX, int a_SrcY, int a_SrcZ, int a_Data, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastSoundParticleEffect(a_EffectID, a_SrcX, a_SrcY, a_SrcZ, a_Data, a_Exclude);
}
void cWorld::BroadcastSpawnEntity(cEntity & a_Entity, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastSpawnEntity(a_Entity, a_Exclude);
}
void cWorld::BroadcastTeleportEntity(const cEntity & a_Entity, const cClientHandle * a_Exclude)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
ch->SendTeleportEntity(a_Entity);
}
}
void cWorld::BroadcastThunderbolt(int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude)
{
m_ChunkMap->BroadcastThunderbolt(a_BlockX, a_BlockY, a_BlockZ, a_Exclude);
}
void cWorld::BroadcastTimeUpdate(const cClientHandle * a_Exclude)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
ch->SendTimeUpdate(std::chrono::duration_cast<cTickTimeLong>(m_WorldAge).count(), std::chrono::duration_cast<cTickTimeLong>(m_TimeOfDay).count(), m_IsDaylightCycleEnabled);
}
}
void cWorld::BroadcastUseBed(const cEntity & a_Entity, int a_BlockX, int a_BlockY, int a_BlockZ)
{
m_ChunkMap->BroadcastUseBed(a_Entity, a_BlockX, a_BlockY, a_BlockZ);
}
void cWorld::BroadcastWeather(eWeather a_Weather, const cClientHandle * a_Exclude)
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch == a_Exclude) || (ch == nullptr) || !ch->IsLoggedIn() || ch->IsDestroyed())
{
continue;
}
ch->SendWeather(a_Weather);
}
}
void cWorld::SendBlockEntity(int a_BlockX, int a_BlockY, int a_BlockZ, cClientHandle & a_Client)
{
m_ChunkMap->SendBlockEntity(a_BlockX, a_BlockY, a_BlockZ, a_Client);
}
2016-04-18 10:30:23 +00:00
void cWorld::MarkChunkDirty(int a_ChunkX, int a_ChunkZ)
{
2016-04-18 10:30:23 +00:00
m_ChunkMap->MarkChunkDirty(a_ChunkX, a_ChunkZ);
}
void cWorld::MarkChunkSaving(int a_ChunkX, int a_ChunkZ)
{
m_ChunkMap->MarkChunkSaving(a_ChunkX, a_ChunkZ);
}
void cWorld::MarkChunkSaved (int a_ChunkX, int a_ChunkZ)
{
m_ChunkMap->MarkChunkSaved (a_ChunkX, a_ChunkZ);
}
2017-07-20 11:19:18 +00:00
void cWorld::QueueSetChunkData(cSetChunkDataPtr a_SetChunkData)
{
// Validate biomes, if needed:
if (!a_SetChunkData->AreBiomesValid())
{
// The biomes are not assigned, get them from the generator:
m_Generator.GenerateBiomes(a_SetChunkData->GetChunkX(), a_SetChunkData->GetChunkZ(), a_SetChunkData->GetBiomes());
a_SetChunkData->MarkBiomesValid();
}
2016-01-12 13:04:59 +00:00
// Validate heightmap, if needed:
if (!a_SetChunkData->IsHeightMapValid())
{
a_SetChunkData->CalculateHeightMap();
}
2016-01-12 13:04:59 +00:00
// Store a copy of the data in the queue:
// TODO: If the queue is too large, wait for it to get processed. Not likely, though.
cCSLock Lock(m_CSSetChunkDataQueue);
m_SetChunkDataQueue.emplace_back(std::move(a_SetChunkData));
}
void cWorld::SetChunkData(cSetChunkData & a_SetChunkData)
{
ASSERT(a_SetChunkData.AreBiomesValid());
ASSERT(a_SetChunkData.IsHeightMapValid());
2016-01-12 13:04:59 +00:00
m_ChunkMap->SetChunkData(a_SetChunkData);
2016-01-12 13:04:59 +00:00
// Initialize the entities (outside the m_ChunkMap's CS, to fix FS #347):
for (auto & Entity : a_SetChunkData.GetEntities())
{
auto EntityPtr = Entity.get();
EntityPtr->Initialize(std::move(Entity), *this);
}
2016-01-12 13:04:59 +00:00
// If a client is requesting this chunk, send it to them:
int ChunkX = a_SetChunkData.GetChunkX();
int ChunkZ = a_SetChunkData.GetChunkZ();
cChunkSender & ChunkSender = m_ChunkSender;
DoWithChunk(
ChunkX, ChunkZ,
[&ChunkSender] (cChunk & a_Chunk) -> bool
{
if (a_Chunk.HasAnyClients())
{
ChunkSender.QueueSendChunkTo(
a_Chunk.GetPosX(),
a_Chunk.GetPosZ(),
cChunkSender::E_CHUNK_PRIORITY_MEDIUM,
a_Chunk.GetAllClients()
);
}
return true;
}
);
// Save the chunk right after generating, so that we don't have to generate it again on next run
if (a_SetChunkData.ShouldMarkDirty())
{
2014-08-28 09:36:35 +00:00
m_Storage.QueueSaveChunk(ChunkX, ChunkZ);
}
}
void cWorld::ChunkLighted(
int a_ChunkX, int a_ChunkZ,
const cChunkDef::BlockNibbles & a_BlockLight,
const cChunkDef::BlockNibbles & a_SkyLight
)
{
m_ChunkMap->ChunkLighted(a_ChunkX, a_ChunkZ, a_BlockLight, a_SkyLight);
}
bool cWorld::GetChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataCallback & a_Callback)
{
return m_ChunkMap->GetChunkData(a_ChunkX, a_ChunkZ, a_Callback);
}
bool cWorld::GetChunkBlockTypes(int a_ChunkX, int a_ChunkZ, BLOCKTYPE * a_BlockTypes)
{
return m_ChunkMap->GetChunkBlockTypes(a_ChunkX, a_ChunkZ, a_BlockTypes);
}
bool cWorld::IsChunkQueued(int a_ChunkX, int a_ChunkZ) const
{
return m_ChunkMap->IsChunkQueued(a_ChunkX, a_ChunkZ);
}
bool cWorld::IsChunkValid(int a_ChunkX, int a_ChunkZ) const
{
return m_ChunkMap->IsChunkValid(a_ChunkX, a_ChunkZ);
}
bool cWorld::HasChunkAnyClients(int a_ChunkX, int a_ChunkZ) const
{
return m_ChunkMap->HasChunkAnyClients(a_ChunkX, a_ChunkZ);
}
void cWorld::UnloadUnusedChunks(void)
{
m_LastChunkCheck = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
m_ChunkMap->UnloadUnusedChunks();
}
void cWorld::QueueUnloadUnusedChunks(void)
{
QueueTask([](cWorld & a_World) { a_World.UnloadUnusedChunks(); });
}
2014-02-11 13:01:25 +00:00
void cWorld::CollectPickupsByPlayer(cPlayer & a_Player)
{
m_ChunkMap->CollectPickupsByPlayer(a_Player);
}
void cWorld::AddPlayer(std::unique_ptr<cPlayer> a_Player, cWorld * a_OldWorld)
{
cCSLock Lock(m_CSPlayersToAdd);
m_PlayersToAdd.emplace_back(std::move(a_Player), a_OldWorld);
}
std::unique_ptr<cPlayer> cWorld::RemovePlayer(cPlayer & a_Player, bool a_RemoveFromChunk)
{
std::unique_ptr<cPlayer> PlayerPtr;
2014-07-29 19:50:30 +00:00
if (a_RemoveFromChunk)
2014-06-21 21:07:38 +00:00
{
2014-07-29 19:50:30 +00:00
// To prevent iterator invalidations when an entity goes through a portal and calls this function whilst being ticked by cChunk
// we should not change cChunk's entity list if asked not to
PlayerPtr = std::unique_ptr<cPlayer>(static_cast<cPlayer *>(m_ChunkMap->RemoveEntity(a_Player).release()));
2014-06-21 21:07:38 +00:00
}
{
cCSLock Lock(m_CSPlayersToAdd);
m_PlayersToAdd.remove_if([&](const decltype(m_PlayersToAdd)::value_type & value) -> bool
{
return (value.first.get() == &a_Player);
});
}
{
cCSLock Lock(m_CSPlayers);
LOGD("Removing player %s from world \"%s\"", a_Player.GetName().c_str(), m_WorldName.c_str());
m_Players.remove(&a_Player);
}
2016-01-12 13:04:59 +00:00
// Remove the player's client from the list of clients to be ticked:
cClientHandle * Client = a_Player.GetClientHandle();
2014-10-20 20:55:07 +00:00
if (Client != nullptr)
{
Client->RemoveFromWorld();
m_ChunkMap->RemoveClientFromChunks(Client);
cCSLock Lock(m_CSClients);
m_ClientsToRemove.push_back(Client);
}
return PlayerPtr;
}
#ifdef _DEBUG
bool cWorld::IsPlayerReferencedInWorldOrChunk(cPlayer & a_Player)
{
if (m_ChunkMap->RemoveEntity(a_Player) != nullptr)
{
return true;
}
{
cCSLock Lock(m_CSPlayersToAdd);
if (std::find_if(
m_PlayersToAdd.begin(), m_PlayersToAdd.end(),
[&a_Player](const cAwaitingPlayerList::value_type & Item) { return Item.first.get() == &a_Player; }) != m_PlayersToAdd.end()
)
{
return true;
}
}
{
cCSLock Lock(m_CSPlayers);
if (std::find(m_Players.begin(), m_Players.end(), &a_Player) != m_Players.end())
{
return true;
}
}
{
cCSLock Lock(m_CSEntitiesToAdd);
if (std::find(m_ClientsToAdd.begin(), m_ClientsToAdd.end(), a_Player.GetClientHandlePtr()) != m_ClientsToAdd.end())
{
return true;
}
}
{
cCSLock Lock(m_CSClients);
if (std::find(m_Clients.begin(), m_Clients.end(), a_Player.GetClientHandlePtr()) != m_Clients.end())
{
return true;
}
}
// Assume OK if in ClientsToRemove or PlayersToRemove
return false;
}
#endif
bool cWorld::ForEachPlayer(cPlayerListCallback & a_Callback)
{
// Calls the callback for each player in the list
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(), itr2 = itr; itr != m_Players.end(); itr = itr2)
{
++itr2;
if (!(*itr)->IsTicking())
{
continue;
}
if (a_Callback.Item(*itr))
{
return false;
}
} // for itr - m_Players[]
return true;
}
bool cWorld::DoWithPlayer(const AString & a_PlayerName, cPlayerListCallback & a_Callback)
{
// Calls the callback for the specified player in the list
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
if (!(*itr)->IsTicking())
{
continue;
}
if (NoCaseCompare((*itr)->GetName(), a_PlayerName) == 0)
{
a_Callback.Item(*itr);
return true;
}
} // for itr - m_Players[]
return false;
}
bool cWorld::FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCallback & a_Callback)
{
2014-10-20 20:55:07 +00:00
cPlayer * BestMatch = nullptr;
2014-05-08 18:16:35 +00:00
size_t BestRating = 0;
size_t NameLength = a_PlayerNameHint.length();
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
if (!(*itr)->IsTicking())
{
continue;
}
2014-05-08 18:16:35 +00:00
size_t Rating = RateCompareString (a_PlayerNameHint, (*itr)->GetName());
if (Rating >= BestRating)
{
BestMatch = *itr;
BestRating = Rating;
}
if (Rating == NameLength) // Perfect match
{
break;
}
} // for itr - m_Players[]
2014-10-20 20:55:07 +00:00
if (BestMatch != nullptr)
{
return a_Callback.Item (BestMatch);
}
return false;
}
2017-08-25 12:43:18 +00:00
bool cWorld::DoWithPlayerByUUID(const cUUID & a_PlayerUUID, cPlayerListCallback & a_Callback)
2016-10-12 12:38:45 +00:00
{
return DoWithPlayerByUUID(a_PlayerUUID, std::bind(&cPlayerListCallback::Item, &a_Callback, std::placeholders::_1));
}
2017-08-25 12:43:18 +00:00
bool cWorld::DoWithPlayerByUUID(const cUUID & a_PlayerUUID, cLambdaPlayerCallback a_Callback)
2014-11-02 20:01:23 +00:00
{
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
if (!(*itr)->IsTicking())
{
continue;
}
if ((*itr)->GetUUID() == a_PlayerUUID)
{
2016-10-12 12:38:45 +00:00
return a_Callback(*itr);
2014-11-02 20:01:23 +00:00
}
}
return false;
}
cPlayer * cWorld::FindClosestPlayer(Vector3d a_Pos, float a_SightLimit, bool a_CheckLineOfSight)
{
2014-03-14 13:36:44 +00:00
double ClosestDistance = a_SightLimit;
2014-10-20 20:55:07 +00:00
cPlayer * ClosestPlayer = nullptr;
cCSLock Lock(m_CSPlayers);
for (cPlayerList::const_iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
if (!(*itr)->IsTicking())
{
continue;
}
Vector3f Pos = (*itr)->GetPosition();
2014-03-14 13:36:44 +00:00
double Distance = (Pos - a_Pos).Length();
// If the player is too far, skip them:
if (Distance > ClosestDistance)
{
continue;
}
// Check LineOfSight, if requested:
if (
a_CheckLineOfSight &&
!cLineBlockTracer::LineOfSightTrace(*this, a_Pos, Pos, cLineBlockTracer::losAirWater)
)
{
continue;
}
ClosestDistance = Distance;
ClosestPlayer = *itr;
}
return ClosestPlayer;
}
void cWorld::SendPlayerList(cPlayer * a_DestPlayer)
{
// Sends the playerlist to a_DestPlayer
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(); itr != m_Players.end(); ++itr)
{
cClientHandle * ch = (*itr)->GetClientHandle();
2014-10-20 20:55:07 +00:00
if ((ch != nullptr) && !ch->IsDestroyed())
{
a_DestPlayer->GetClientHandle()->SendPlayerListAddPlayer(*(*itr));
}
}
}
bool cWorld::ForEachEntity(cEntityCallback & a_Callback)
{
return m_ChunkMap->ForEachEntity(a_Callback);
}
bool cWorld::ForEachEntityInChunk(int a_ChunkX, int a_ChunkZ, cEntityCallback & a_Callback)
{
return m_ChunkMap->ForEachEntityInChunk(a_ChunkX, a_ChunkZ, a_Callback);
}
2014-09-03 15:00:26 +00:00
bool cWorld::ForEachEntityInBox(const cBoundingBox & a_Box, cEntityCallback & a_Callback)
{
return m_ChunkMap->ForEachEntityInBox(a_Box, a_Callback);
}
2015-03-21 14:18:17 +00:00
bool cWorld::DoWithEntityByID(UInt32 a_UniqueID, cEntityCallback & a_Callback)
2016-10-12 12:38:45 +00:00
{
return DoWithEntityByID(a_UniqueID, std::bind(&cEntityCallback::Item, &a_Callback, std::placeholders::_1));
}
bool cWorld::DoWithEntityByID(UInt32 a_UniqueID, cLambdaEntityCallback a_Callback)
{
// First check the entities-to-add:
{
cCSLock Lock(m_CSEntitiesToAdd);
for (const auto & ent: m_EntitiesToAdd)
{
if (ent->GetUniqueID() == a_UniqueID)
{
a_Callback(ent.get());
return true;
}
} // for ent - m_EntitiesToAdd[]
}
// Then check the chunkmap:
return m_ChunkMap->DoWithEntityByID(a_UniqueID, a_Callback);
}
void cWorld::CompareChunkClients(int a_ChunkX1, int a_ChunkZ1, int a_ChunkX2, int a_ChunkZ2, cClientDiffCallback & a_Callback)
{
m_ChunkMap->CompareChunkClients(a_ChunkX1, a_ChunkZ1, a_ChunkX2, a_ChunkZ2, a_Callback);
}
bool cWorld::AddChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client)
{
return m_ChunkMap->AddChunkClient(a_ChunkX, a_ChunkZ, a_Client);
}
void cWorld::RemoveChunkClient(int a_ChunkX, int a_ChunkZ, cClientHandle * a_Client)
{
m_ChunkMap->RemoveChunkClient(a_ChunkX, a_ChunkZ, a_Client);
}
void cWorld::RemoveClientFromChunks(cClientHandle * a_Client)
{
m_ChunkMap->RemoveClientFromChunks(a_Client);
}
void cWorld::SendChunkTo(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority, cClientHandle * a_Client)
{
m_ChunkSender.QueueSendChunkTo(a_ChunkX, a_ChunkZ, a_Priority, a_Client);
}
void cWorld::ForceSendChunkTo(int a_ChunkX, int a_ChunkZ, cChunkSender::eChunkPriority a_Priority, cClientHandle * a_Client)
{
a_Client->AddWantedChunk(a_ChunkX, a_ChunkZ);
m_ChunkSender.QueueSendChunkTo(a_ChunkX, a_ChunkZ, a_Priority, a_Client);
}
void cWorld::RemoveClientFromChunkSender(cClientHandle * a_Client)
{
m_ChunkSender.RemoveClient(a_Client);
}
2014-08-28 09:36:35 +00:00
void cWorld::TouchChunk(int a_ChunkX, int a_ChunkZ)
{
2014-08-28 09:36:35 +00:00
m_ChunkMap->TouchChunk(a_ChunkX, a_ChunkZ);
}
2015-05-30 10:11:17 +00:00
void cWorld::PrepareChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallAfter)
{
2015-05-30 10:11:17 +00:00
m_ChunkMap->PrepareChunk(a_ChunkX, a_ChunkZ, std::move(a_CallAfter));
}
2014-08-28 09:36:35 +00:00
void cWorld::ChunkLoadFailed(int a_ChunkX, int a_ChunkZ)
{
2014-08-28 09:36:35 +00:00
m_ChunkMap->ChunkLoadFailed(a_ChunkX, a_ChunkZ);
}
bool cWorld::SetSignLines(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Line1, const AString & a_Line2, const AString & a_Line3, const AString & a_Line4, cPlayer * a_Player)
{
AString Line1(a_Line1);
AString Line2(a_Line2);
AString Line3(a_Line3);
AString Line4(a_Line4);
2014-11-15 14:16:52 +00:00
if (cRoot::Get()->GetPluginManager()->CallHookUpdatingSign(*this, a_BlockX, a_BlockY, a_BlockZ, Line1, Line2, Line3, Line4, a_Player))
{
return false;
}
2014-11-15 14:16:52 +00:00
if (m_ChunkMap->SetSignLines(a_BlockX, a_BlockY, a_BlockZ, Line1, Line2, Line3, Line4))
{
cRoot::Get()->GetPluginManager()->CallHookUpdatedSign(*this, a_BlockX, a_BlockY, a_BlockZ, Line1, Line2, Line3, Line4, a_Player);
return true;
}
2014-11-15 14:16:52 +00:00
return false;
}
2014-01-23 12:57:04 +00:00
bool cWorld::SetCommandBlockCommand(int a_BlockX, int a_BlockY, int a_BlockZ, const AString & a_Command)
{
class cUpdateCommandBlock : public cCommandBlockCallback
{
AString m_Command;
public:
2014-09-08 17:56:27 +00:00
cUpdateCommandBlock(const AString & a_CallbackCommand) : m_Command(a_CallbackCommand) {}
2016-01-12 13:04:59 +00:00
2014-01-23 12:57:04 +00:00
virtual bool Item(cCommandBlockEntity * a_CommandBlock) override
{
a_CommandBlock->SetCommand(m_Command);
return false;
}
} CmdBlockCB (a_Command);
return DoWithCommandBlockAt(a_BlockX, a_BlockY, a_BlockZ, CmdBlockCB);
}
bool cWorld::IsTrapdoorOpen(int a_BlockX, int a_BlockY, int a_BlockZ)
{
BLOCKTYPE Block;
NIBBLETYPE Meta;
GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, Block, Meta);
2014-09-30 18:04:49 +00:00
if ((Block != E_BLOCK_TRAPDOOR) && (Block != E_BLOCK_IRON_TRAPDOOR))
{
return false;
}
2016-01-12 13:04:59 +00:00
return (Meta & 0x4) > 0;
}
bool cWorld::SetTrapdoorOpen(int a_BlockX, int a_BlockY, int a_BlockZ, bool a_Open)
{
BLOCKTYPE Block;
NIBBLETYPE Meta;
GetBlockTypeMeta(a_BlockX, a_BlockY, a_BlockZ, Block, Meta);
2014-09-30 18:04:49 +00:00
if ((Block != E_BLOCK_TRAPDOOR) && (Block != E_BLOCK_IRON_TRAPDOOR))
{
return false;
}
2016-01-12 13:04:59 +00:00
bool IsOpen = (Meta & 0x4) != 0;
if (a_Open != IsOpen)
{
SetBlockMeta(a_BlockX, a_BlockY, a_BlockZ, Meta ^ 0x4);
2017-02-15 05:05:24 +00:00
BroadcastSoundParticleEffect(EffectID::SFX_RANDOM_WOODEN_TRAPDOOR_OPEN, a_BlockX, a_BlockY, a_BlockZ, 0);
return true;
}
return false;
}
void cWorld::RegenerateChunk(int a_ChunkX, int a_ChunkZ)
{
m_ChunkMap->MarkChunkRegenerating(a_ChunkX, a_ChunkZ);
2016-01-12 13:04:59 +00:00
m_Generator.QueueGenerateChunk(a_ChunkX, a_ChunkZ, true);
}
void cWorld::GenerateChunk(int a_ChunkX, int a_ChunkZ)
{
m_ChunkMap->GenerateChunk(a_ChunkX, a_ChunkZ);
}
2015-05-30 10:11:17 +00:00
void cWorld::QueueLightChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_Callback)
{
2015-05-30 10:11:17 +00:00
m_Lighting.QueueChunk(a_ChunkX, a_ChunkZ, std::move(a_Callback));
}
bool cWorld::IsChunkLighted(int a_ChunkX, int a_ChunkZ)
{
return m_ChunkMap->IsChunkLighted(a_ChunkX, a_ChunkZ);
}
bool cWorld::ForEachChunkInRect(int a_MinChunkX, int a_MaxChunkX, int a_MinChunkZ, int a_MaxChunkZ, cChunkDataCallback & a_Callback)
{
return m_ChunkMap->ForEachChunkInRect(a_MinChunkX, a_MaxChunkX, a_MinChunkZ, a_MaxChunkZ, a_Callback);
}
bool cWorld::ForEachLoadedChunk(std::function<bool(int, int)> a_Callback)
{
return m_ChunkMap->ForEachLoadedChunk(a_Callback);
}
void cWorld::SaveAllChunks(void)
{
m_LastSave = std::chrono::duration_cast<cTickTimeLong>(m_WorldAge);
m_ChunkMap->SaveAllChunks();
}
void cWorld::QueueSaveAllChunks(void)
{
QueueTask([](cWorld & a_World) { a_World.SaveAllChunks(); });
}
void cWorld::QueueTask(std::function<void(cWorld &)> a_Task)
{
cCSLock Lock(m_CSTasks);
m_Tasks.emplace_back(0, a_Task);
}
void cWorld::ScheduleTask(int a_DelayTicks, std::function<void (cWorld &)> a_Task)
{
Int64 TargetTick = a_DelayTicks + std::chrono::duration_cast<cTickTimeLong>(m_WorldAge).count();
// Insert the task into the list of scheduled tasks
{
cCSLock Lock(m_CSTasks);
m_Tasks.emplace_back(TargetTick, a_Task);
}
}
void cWorld::AddEntity(OwnedEntity a_Entity)
{
a_Entity->SetWorld(this);
cCSLock Lock(m_CSEntitiesToAdd);
m_EntitiesToAdd.emplace_back(std::move(a_Entity));
}
2015-03-21 14:18:17 +00:00
bool cWorld::HasEntity(UInt32 a_UniqueID)
{
// Check if the entity is in the queue to be added to the world:
{
cCSLock Lock(m_CSEntitiesToAdd);
for (cEntityList::const_iterator itr = m_EntitiesToAdd.begin(), end = m_EntitiesToAdd.end(); itr != end; ++itr)
{
if ((*itr)->GetUniqueID() == a_UniqueID)
{
return true;
}
} // for itr - m_EntitiesToAdd[]
}
// Check if the entity is in the chunkmap:
if (m_ChunkMap.get() == nullptr)
{
// Chunkmap has already been destroyed, there are no entities anymore.
return false;
}
return m_ChunkMap->HasEntity(a_UniqueID);
}
/*
unsigned int cWorld::GetNumPlayers(void)
{
cCSLock Lock(m_CSPlayers);
return m_Players.size();
}
*/
size_t cWorld::GetNumChunks(void) const
{
return m_ChunkMap->GetNumChunks();
}
size_t cWorld::GetNumUnusedDirtyChunks(void) const
{
return m_ChunkMap->GetNumUnusedDirtyChunks();
}
void cWorld::GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue)
{
m_ChunkMap->GetChunkStats(a_NumValid, a_NumDirty);
a_NumInLightingQueue = static_cast<int>(m_Lighting.GetQueueLength());
}
void cWorld::TickQueuedBlocks(void)
{
if (m_BlockTickQueue.empty())
{
return;
}
m_BlockTickQueueCopy.clear();
m_BlockTickQueue.swap(m_BlockTickQueueCopy);
for (std::vector<BlockTickQueueItem *>::iterator itr = m_BlockTickQueueCopy.begin(); itr != m_BlockTickQueueCopy.end(); ++itr)
{
BlockTickQueueItem * Block = (*itr);
Block->TicksToWait -= 1;
if (Block->TicksToWait <= 0)
{
// TODO: Handle the case when the chunk is already unloaded
m_ChunkMap->TickBlock(Block->X, Block->Y, Block->Z);
delete Block; // We don't have to remove it from the vector, this will happen automatically on the next tick
}
else
{
m_BlockTickQueue.push_back(Block); // Keep the block in the queue
}
} // for itr - m_BlockTickQueueCopy[]
}
void cWorld::QueueBlockForTick(int a_BlockX, int a_BlockY, int a_BlockZ, int a_TicksToWait)
{
BlockTickQueueItem * Block = new BlockTickQueueItem;
Block->X = a_BlockX;
Block->Y = a_BlockY;
Block->Z = a_BlockZ;
Block->TicksToWait = a_TicksToWait;
2016-01-12 13:04:59 +00:00
m_BlockTickQueue.push_back(Block);
}
bool cWorld::IsBlockDirectlyWatered(int a_BlockX, int a_BlockY, int a_BlockZ)
{
return (
IsBlockWater(GetBlock(a_BlockX - 1, a_BlockY, a_BlockZ)) ||
IsBlockWater(GetBlock(a_BlockX + 1, a_BlockY, a_BlockZ)) ||
IsBlockWater(GetBlock(a_BlockX, a_BlockY, a_BlockZ - 1)) ||
IsBlockWater(GetBlock(a_BlockX, a_BlockY, a_BlockZ + 1))
);
}
2015-07-16 13:06:54 +00:00
UInt32 cWorld::SpawnMob(double a_PosX, double a_PosY, double a_PosZ, eMonsterType a_MonsterType, bool a_Baby)
{
auto Monster = cMonster::NewMonsterFromType(a_MonsterType);
2015-03-21 14:18:17 +00:00
if (Monster == nullptr)
{
2015-03-21 14:18:17 +00:00
return cEntity::INVALID_ID;
}
2015-03-21 14:18:17 +00:00
Monster->SetPosition(a_PosX, a_PosY, a_PosZ);
2016-01-12 13:04:59 +00:00
2015-07-16 13:06:54 +00:00
if (a_Baby)
{
Monster->SetAge(-1);
}
return SpawnMobFinalize(std::move(Monster));
}
UInt32 cWorld::SpawnMobFinalize(std::unique_ptr<cMonster> a_Monster)
{
2015-03-21 14:18:17 +00:00
ASSERT(a_Monster != nullptr);
// Give the mob full health.
a_Monster->SetHealth(a_Monster->GetMaxHealth());
// A plugin doesn't agree with the spawn. bail out.
if (cPluginManager::Get()->CallHookSpawningMonster(*this, *a_Monster))
{
2015-03-21 14:18:17 +00:00
return cEntity::INVALID_ID;
}
auto & Monster = *a_Monster;
// Initialize the monster into the current world.
if (!Monster.Initialize(std::move(a_Monster), *this))
{
2015-03-21 14:18:17 +00:00
return cEntity::INVALID_ID;
}
cPluginManager::Get()->CallHookSpawnedMonster(*this, Monster);
return Monster.GetUniqueID();
}
2015-03-21 14:18:17 +00:00
UInt32 cWorld::CreateProjectile(double a_PosX, double a_PosY, double a_PosZ, cProjectileEntity::eKind a_Kind, cEntity * a_Creator, const cItem * a_Item, const Vector3d * a_Speed)
{
auto Projectile = cProjectileEntity::Create(a_Kind, a_Creator, a_PosX, a_PosY, a_PosZ, a_Item, a_Speed);
2014-10-20 20:55:07 +00:00
if (Projectile == nullptr)
{
2015-03-21 14:18:17 +00:00
return cEntity::INVALID_ID;
}
auto ProjectilePtr = Projectile.get();
if (!ProjectilePtr->Initialize(std::move(Projectile), *this))
{
2015-03-21 14:18:17 +00:00
return cEntity::INVALID_ID;
}
return ProjectilePtr->GetUniqueID();
}
2017-06-13 19:35:30 +00:00
int cWorld::GetTickRandomNumber(int a_Range)
{
return GetRandomProvider().RandInt(a_Range);
}
void cWorld::TabCompleteUserName(const AString & a_Text, AStringVector & a_Results)
{
typedef std::pair<AString::size_type, AString> pair_t;
size_t LastSpace = a_Text.find_last_of(" "); // Find the position of the last space
AString LastWord = a_Text.substr(LastSpace + 1, a_Text.length()); // Find the last word
if (LastWord.empty())
{
return;
}
std::vector<pair_t> UsernamesByWeight;
cCSLock Lock(m_CSPlayers);
for (cPlayerList::iterator itr = m_Players.begin(), end = m_Players.end(); itr != end; ++itr)
{
AString PlayerName ((*itr)->GetName());
2014-09-02 17:12:35 +00:00
if ((*itr)->HasCustomName())
{
PlayerName = (*itr)->GetCustomName();
}
AString::size_type Found = StrToLower(PlayerName).find(StrToLower(LastWord)); // Try to find last word in playername
if (Found == AString::npos)
{
continue; // No match
}
2014-09-02 17:12:35 +00:00
UsernamesByWeight.push_back(std::make_pair(Found, PlayerName)); // Match! Store it with the position of the match as a weight
}
Lock.Unlock();
std::sort(UsernamesByWeight.begin(), UsernamesByWeight.end()); // Sort lexicographically (by the first value, then second), so higher weights (usernames with match closer to start) come first (#1274)
/* TODO: Uncomment once migrated to C++11
std::transform(
UsernamesByWeight.begin(),
UsernamesByWeight.end(),
std::back_inserter(a_Results),
[](const pair_t & p) { return p.first; }
);
*/
a_Results.reserve(UsernamesByWeight.size());
for (std::vector<pair_t>::const_iterator itr = UsernamesByWeight.begin(); itr != UsernamesByWeight.end(); ++itr)
{
a_Results.push_back(itr->second);
}
}
void cWorld::SetChunkAlwaysTicked(int a_ChunkX, int a_ChunkZ, bool a_AlwaysTicked)
{
m_ChunkMap->SetChunkAlwaysTicked(a_ChunkX, a_ChunkZ, a_AlwaysTicked);
}
2014-10-25 20:54:00 +00:00
cRedstoneSimulator * cWorld::InitializeRedstoneSimulator(cIniFile & a_IniFile)
{
2014-07-31 21:11:51 +00:00
AString SimulatorName = a_IniFile.GetValueSet("Physics", "RedstoneSimulator", "Incremental");
if (SimulatorName.empty())
{
2014-07-31 21:11:51 +00:00
LOGWARNING("[Physics] RedstoneSimulator not present or empty in %s, using the default of \"Incremental\".", GetIniFileName().c_str());
SimulatorName = "Incremental";
}
2016-01-12 13:04:59 +00:00
2014-10-25 20:54:00 +00:00
cRedstoneSimulator * res = nullptr;
2014-07-31 21:11:51 +00:00
if (NoCaseCompare(SimulatorName, "Incremental") == 0)
{
2014-10-25 20:54:00 +00:00
res = new cIncrementalRedstoneSimulator(*this);
}
else if (NoCaseCompare(SimulatorName, "noop") == 0)
{
res = new cRedstoneNoopSimulator(*this);
}
else
{
LOGWARNING("[Physics] Unknown RedstoneSimulator \"%s\" in %s, using the default of \"Incremental\".", SimulatorName.c_str(), GetIniFileName().c_str());
res = new cIncrementalRedstoneSimulator(*this);
}
2016-01-12 13:04:59 +00:00
m_SimulatorManager->RegisterSimulator(res, 2 /* Two game ticks is a redstone tick */);
2016-01-12 13:04:59 +00:00
return res;
}
cFluidSimulator * cWorld::InitializeFluidSimulator(cIniFile & a_IniFile, const char * a_FluidName, BLOCKTYPE a_SimulateBlock, BLOCKTYPE a_StationaryBlock)
{
AString SimulatorNameKey;
Printf(SimulatorNameKey, "%sSimulator", a_FluidName);
AString SimulatorSectionName;
Printf(SimulatorSectionName, "%sSimulator", a_FluidName);
bool IsWater = (strcmp(a_FluidName, "Water") == 0); // Used for defaults
AString DefaultSimulatorName = ((GetDimension() == dimNether) && IsWater) ? "Vaporise" : "Vanilla";
AString SimulatorName = a_IniFile.GetValueSet("Physics", SimulatorNameKey, DefaultSimulatorName);
if (SimulatorName.empty())
{
LOGWARNING("[Physics] %s not present or empty in %s, using the default of \"%s\".", SimulatorNameKey.c_str(), GetIniFileName().c_str(), DefaultSimulatorName.c_str());
SimulatorName = DefaultSimulatorName;
}
2014-10-20 20:55:07 +00:00
cFluidSimulator * res = nullptr;
int Rate = 1;
if (
(NoCaseCompare(SimulatorName, "vaporize") == 0) ||
(NoCaseCompare(SimulatorName, "vaporise") == 0)
)
{
res = new cVaporizeFluidSimulator(*this, a_SimulateBlock, a_StationaryBlock);
}
else if (
(NoCaseCompare(SimulatorName, "noop") == 0) ||
(NoCaseCompare(SimulatorName, "nop") == 0) ||
(NoCaseCompare(SimulatorName, "null") == 0) ||
(NoCaseCompare(SimulatorName, "nil") == 0)
)
{
res = new cNoopFluidSimulator(*this, a_SimulateBlock, a_StationaryBlock);
}
else
{
int Falloff = a_IniFile.GetValueSetI(SimulatorSectionName, "Falloff", IsWater ? 1 : 2);
int TickDelay = a_IniFile.GetValueSetI(SimulatorSectionName, "TickDelay", IsWater ? 5 : 30);
int NumNeighborsForSource = a_IniFile.GetValueSetI(SimulatorSectionName, "NumNeighborsForSource", IsWater ? 2 : -1);
2016-01-12 13:04:59 +00:00
2014-09-08 17:56:27 +00:00
if ((Falloff > 15) || (Falloff < 0))
{
LOGWARNING("Falloff for %s simulator is out of range, assuming default of %d", a_FluidName, IsWater ? 1 : 2);
2014-09-09 11:18:20 +00:00
Falloff = IsWater ? 1 : 2;
2014-09-08 17:56:27 +00:00
}
if (NoCaseCompare(SimulatorName, "floody") == 0)
{
2014-09-08 17:56:27 +00:00
res = new cFloodyFluidSimulator(*this, a_SimulateBlock, a_StationaryBlock, static_cast<NIBBLETYPE>(Falloff), TickDelay, NumNeighborsForSource);
}
else if (NoCaseCompare(SimulatorName, "vanilla") == 0)
{
2014-09-08 17:56:27 +00:00
res = new cVanillaFluidSimulator(*this, a_SimulateBlock, a_StationaryBlock, static_cast<NIBBLETYPE>(Falloff), TickDelay, NumNeighborsForSource);
}
else
{
// The simulator name doesn't match anything we have, issue a warning:
LOGWARNING("%s [Physics]:%s specifies an unknown simulator, using the default \"Vanilla\".", GetIniFileName().c_str(), SimulatorNameKey.c_str());
2014-09-08 17:56:27 +00:00
res = new cVanillaFluidSimulator(*this, a_SimulateBlock, a_StationaryBlock, static_cast<NIBBLETYPE>(Falloff), TickDelay, NumNeighborsForSource);
}
}
2016-01-12 13:04:59 +00:00
m_SimulatorManager->RegisterSimulator(res, Rate);
return res;
}
2014-02-13 19:36:24 +00:00
void cWorld::AddQueuedPlayers(void)
{
ASSERT(m_TickThread.IsCurrentThread());
// Grab the list of players to add, it has to be locked to access it:
cAwaitingPlayerList PlayersToAdd;
{
cCSLock Lock(m_CSPlayersToAdd);
std::swap(PlayersToAdd, m_PlayersToAdd);
}
2016-01-12 13:04:59 +00:00
// Temporary (#3115-will-fix): store pointers to player objects after ownership transferral
std::vector<std::pair<cPlayer *, cWorld *>> AddedPlayerPtrs;
AddedPlayerPtrs.reserve(PlayersToAdd.size());
// Add all the players in the grabbed list:
{
cCSLock Lock(m_CSPlayers);
for (auto & AwaitingPlayer : PlayersToAdd)
{
auto & Player = AwaitingPlayer.first;
ASSERT(std::find(m_Players.begin(), m_Players.end(), Player.get()) == m_Players.end()); // Is it already in the list? HOW?
2016-03-29 18:23:53 +00:00
LOGD("Adding player %s to world \"%s\".", Player->GetName().c_str(), m_WorldName.c_str());
m_Players.push_back(Player.get());
2016-03-29 18:23:53 +00:00
Player->SetWorld(this);
// Add to chunkmap, if not already there (Spawn vs MoveToWorld):
auto PlayerPtr = Player.get();
m_ChunkMap->AddEntityIfNotPresent(std::move(Player));
ASSERT(!PlayerPtr->IsTicking());
PlayerPtr->SetIsTicking(true);
AddedPlayerPtrs.emplace_back(PlayerPtr, AwaitingPlayer.second);
} // for itr - PlayersToAdd[]
} // Lock(m_CSPlayers)
// Add all the players' clienthandles:
{
cCSLock Lock(m_CSClients);
for (auto & AwaitingPlayer : AddedPlayerPtrs)
{
auto & Player = AwaitingPlayer.first;
cClientHandlePtr Client = Player->GetClientHandlePtr();
2014-10-20 20:55:07 +00:00
if (Client != nullptr)
{
m_Clients.push_back(Client);
}
} // for itr - PlayersToAdd[]
} // Lock(m_CSClients)
// Stream chunks to all eligible clients:
for (auto & AwaitingPlayer : AddedPlayerPtrs)
{
auto & Player = AwaitingPlayer.first;
cClientHandle * Client = Player->GetClientHandle();
2014-10-20 20:55:07 +00:00
if (Client != nullptr)
{
2014-06-12 14:21:07 +00:00
Client->SendPlayerMoveLook();
Client->SendHealth();
Client->SendWholeInventory(*Player->GetWindow());
}
} // for itr - PlayersToAdd[]
// Call EntityChangedWorld callback on all eligible clients
for (auto & AwaitingPlayer : AddedPlayerPtrs)
{
if (AwaitingPlayer.second != nullptr)
{
cRoot::Get()->GetPluginManager()->CallHookEntityChangedWorld(*(static_cast <cEntity *>(AwaitingPlayer.first)), *AwaitingPlayer.second);
}
}
}
////////////////////////////////////////////////////////////////////////////////
// cWorld::cChunkGeneratorCallbacks:
cWorld::cChunkGeneratorCallbacks::cChunkGeneratorCallbacks(cWorld & a_World) :
m_World(&a_World)
{
}
void cWorld::cChunkGeneratorCallbacks::OnChunkGenerated(cChunkDesc & a_ChunkDesc)
{
cChunkDef::BlockNibbles BlockMetas;
a_ChunkDesc.CompressBlockMetas(BlockMetas);
auto SetChunkData = cpp14::make_unique<cSetChunkData>(
a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ(),
a_ChunkDesc.GetBlockTypes(), BlockMetas,
2014-10-20 20:55:07 +00:00
nullptr, nullptr, // We don't have lighting, chunk will be lighted when needed
&a_ChunkDesc.GetHeightMap(), &a_ChunkDesc.GetBiomeMap(),
std::move(a_ChunkDesc.GetEntities()), std::move(a_ChunkDesc.GetBlockEntities()),
true
);
SetChunkData->RemoveInvalidBlockEntities();
2017-07-20 11:19:18 +00:00
m_World->QueueSetChunkData(std::move(SetChunkData));
}
bool cWorld::cChunkGeneratorCallbacks::IsChunkValid(int a_ChunkX, int a_ChunkZ)
{
return m_World->IsChunkValid(a_ChunkX, a_ChunkZ);
}
bool cWorld::cChunkGeneratorCallbacks::IsChunkQueued(int a_ChunkX, int a_ChunkZ)
{
return m_World->IsChunkQueued(a_ChunkX, a_ChunkZ);
}
bool cWorld::cChunkGeneratorCallbacks::HasChunkAnyClients(int a_ChunkX, int a_ChunkZ)
{
return m_World->HasChunkAnyClients(a_ChunkX, a_ChunkZ);
}
void cWorld::cChunkGeneratorCallbacks::CallHookChunkGenerating(cChunkDesc & a_ChunkDesc)
{
cPluginManager::Get()->CallHookChunkGenerating(
*m_World, a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ(), &a_ChunkDesc
);
}
void cWorld::cChunkGeneratorCallbacks::CallHookChunkGenerated (cChunkDesc & a_ChunkDesc)
{
cPluginManager::Get()->CallHookChunkGenerated(
*m_World, a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ(), &a_ChunkDesc
);
}
cBroadcaster cWorld::GetBroadcaster()
{
return cBroadcaster(this);
}