mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/LCE-Revelations.git
synced 2026-05-24 17:44:34 +00:00
Adds the FourKit .NET 10 plugin host as a second dedicated server
build flavour alongside the existing vanilla server. Both flavours
build from the same source tree, with FourKit gated by the
MINECRAFT_SERVER_FOURKIT_BUILD preprocessor define.
Build layout:
Minecraft.Server vanilla, no plugin support, no .NET dep
Minecraft.Server.FourKit FourKit-enabled, ships with bundled
.NET 10 self-contained runtime in runtime/
and an empty plugins/ folder
Both produce a Minecraft.Server.exe in their own per-target output
dir. The variant identity lives in the directory name, not the
binary name, so either flavour can be shipped as a drop-in.
Native bridge (Minecraft.Server/FourKit*.{cpp,h}):
* FourKitRuntime: hosts CoreCLR via hostfxr's command-line init API
(the runtime-config API does not support self-contained components)
* FourKitBridge: ~50 Fire* event entry points, with inline no-op
stubs for the standalone build so gameplay code can call them
unconditionally
* FourKitNatives: ~80 native callbacks the managed side invokes
for player/world/inventory mutations
* FourKitMappers: type and enum mapping helpers
Managed plugin host (Minecraft.Server.FourKit/):
* Bukkit-style API: Player, World, Block, Inventory, Command,
Listener, EventHandler attribute, ~54 event classes
* PluginLoader with per-plugin AssemblyLoadContext
* FourKitHost as the [UnmanagedCallersOnly] entry point table
* Runtime resolves plugins relative to the host process so they
always live next to Minecraft.Server.exe regardless of where the
managed assembly itself is loaded from
Engine hooks (Minecraft.Client/, Minecraft.World/):
* Player lifecycle (PreLogin, Login, Join, Quit, Kick, Move,
Teleport, Portal, Death) wired into PendingConnection and
PlayerConnection without disturbing the cipher handshake or
identity-token security flow
* Inventory open/click/drop hooks across every container menu type
* Block place/break/grow/burn/spread/from-to hooks across the
full tile family
* Bed enter/leave, sign change, entity damage/death, ender pearl
teleport hooks
Regression fixes preserved while applying donor diffs:
* ServerPlayer::die() retains the LCE-Revelations hardcore branch
(setGameMode(ADVENTURE) + banPlayerForHardcoreDeath) in both the
FourKit and non-FourKit code paths
* ServerLevel::entityAdded() retains the sub-entity ID reassignment
loop required by the client's handleAddMob offset, fixing Ender
Dragon and Wither boss multi-part hit detection
* LivingEntity::travel() retains the raw Player* cast and the
cached frictionTile, both Revelations perf wins that the donor
silently reverted
* ServerLogger.cpp keeps the file-logging code donor stripped
* PlayerList.cpp end portal transition fix and UIScene_EndPoem
bounds-check are intact
Build system:
* Top-level CMakeLists.txt adds the Minecraft.Server.FourKit
subdirectory and pulls in the new shared cmake/ServerTarget.cmake
helper
* Minecraft.Server/cmake/sources/Common.cmake is now location
independent (uses CMAKE_CURRENT_LIST_DIR) so the source list
can be consumed from either server target's CMakeLists.txt
* The seven FourKit*.cpp/h files live in their own
_MINECRAFT_SERVER_COMMON_SERVER_FOURKIT variable so the
standalone target omits them
* configure-time .NET 10 SDK check fails fast with a clear
download link if the SDK is missing
* global.json pins the SDK to 10.0.100 with latestFeature
rollforward
Sample plugin (samples/HelloPlugin/) demonstrates the loader and
the PlayerJoinEvent listener pattern.
CI:
* nightly.yml builds both server flavours, ships
LCE-Revelations-Server-Win64.zip and
LCE-Revelations-Server-Win64-FourKit.zip, attests both, and
updates release notes for the dual-flavour layout
* pull-request.yml pulls in actions/setup-dotnet so the FourKit
publish step works in PR validation
* All zip artifacts and the client zip are renamed from
LCREWindows64 to LCE-Revelations-{Client,Server}-Win64
Documentation:
* COMPILE.md gets a VS 2022 quick start, .NET 10 prereq section,
server flavours explanation, and a troubleshooting section
* docs/FOURKIT_PORT_RECON.md captures the file-by-file recon that
drove the port
* docs/FOURKIT_PARITY.md is the canonical reference for which
events FourKit fires
Docker:
* docker-compose.dedicated-server.yml MC_RUNTIME_DIR default points
at the vanilla CMake output. The FourKit Docker image is
intentionally NOT shipped yet because hosting .NET 10 self
contained inside Wine has not been smoke-tested
427 lines
12 KiB
C++
427 lines
12 KiB
C++
#include "stdafx.h"
|
|
#include "net.minecraft.world.level.h"
|
|
#include "net.minecraft.world.level.dimension.h"
|
|
#include "net.minecraft.world.h"
|
|
#include "net.minecraft.world.level.tile.h"
|
|
#include "FireTile.h"
|
|
#include "SoundTypes.h"
|
|
#include "..\Minecraft.Client\MinecraftServer.h"
|
|
#include "..\Minecraft.Client\PlayerList.h"
|
|
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
|
#include "..\Minecraft.Server\FourKitBridge.h"
|
|
#endif
|
|
|
|
// AP - added for Vita to set Alpha Cut out
|
|
#include "IntBuffer.h"
|
|
#include "..\Minecraft.Client\Tesselator.h"
|
|
|
|
|
|
const wstring FireTile::TEXTURE_FIRST = L"fire_0";
|
|
const wstring FireTile::TEXTURE_SECOND = L"fire_1";
|
|
|
|
FireTile::FireTile(int id) : Tile(id, Material::fire,isSolidRender())
|
|
{
|
|
flameOdds = new int[256];
|
|
memset( flameOdds,0,sizeof(int)*256);
|
|
|
|
burnOdds = new int[256];
|
|
memset( burnOdds,0,sizeof(int)*256);
|
|
|
|
icons = nullptr;
|
|
|
|
setTicking(true);
|
|
}
|
|
|
|
FireTile::~FireTile()
|
|
{
|
|
delete [] flameOdds;
|
|
delete [] burnOdds;
|
|
}
|
|
|
|
void FireTile::init()
|
|
{
|
|
setFlammable(Tile::wood_Id, FLAME_HARD, BURN_MEDIUM);
|
|
setFlammable(Tile::woodSlab_Id, FLAME_HARD, BURN_MEDIUM);
|
|
setFlammable(Tile::woodSlabHalf_Id, FLAME_HARD, BURN_MEDIUM);
|
|
setFlammable(Tile::fence_Id, FLAME_HARD, BURN_MEDIUM);
|
|
setFlammable(Tile::stairs_wood_Id, FLAME_HARD, BURN_MEDIUM);
|
|
setFlammable(Tile::stairs_birchwood_Id, FLAME_HARD, BURN_MEDIUM);
|
|
setFlammable(Tile::stairs_sprucewood_Id, FLAME_HARD, BURN_MEDIUM);
|
|
setFlammable(Tile::stairs_junglewood_Id, FLAME_HARD, BURN_MEDIUM);
|
|
setFlammable(Tile::treeTrunk_Id, FLAME_HARD, BURN_HARD);
|
|
setFlammable(Tile::leaves_Id, FLAME_EASY, BURN_EASY);
|
|
setFlammable(Tile::bookshelf_Id, FLAME_EASY, BURN_MEDIUM);
|
|
setFlammable(Tile::tnt_Id, FLAME_MEDIUM, BURN_INSTANT);
|
|
setFlammable(Tile::tallgrass_Id, FLAME_INSTANT, BURN_INSTANT);
|
|
setFlammable(Tile::wool_Id, FLAME_EASY, BURN_EASY);
|
|
setFlammable(Tile::vine_Id, FLAME_MEDIUM, BURN_INSTANT);
|
|
setFlammable(Tile::coalBlock_Id, FLAME_HARD, BURN_HARD);
|
|
setFlammable(Tile::hayBlock_Id, FLAME_INSTANT, BURN_MEDIUM);
|
|
}
|
|
|
|
void FireTile::setFlammable(int id, int flame, int burn)
|
|
{
|
|
flameOdds[id] = flame;
|
|
burnOdds[id] = burn;
|
|
}
|
|
|
|
AABB *FireTile::getAABB(Level *level, int x, int y, int z)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
bool FireTile::blocksLight()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FireTile::isSolidRender(bool isServerLevel)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FireTile::isCubeShaped()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int FireTile::getRenderShape()
|
|
{
|
|
return Tile::SHAPE_FIRE;
|
|
}
|
|
|
|
int FireTile::getResourceCount(Random *random)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int FireTile::getTickDelay(Level *level)
|
|
{
|
|
return 30;
|
|
}
|
|
|
|
void FireTile::tick(Level *level, int x, int y, int z, Random *random)
|
|
{
|
|
if (!level->getGameRules()->getBoolean(GameRules::RULE_DOFIRETICK))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 4J added - we don't want fire to do anything that might create new fire, or destroy this fire, if we aren't actually tracking (for network) the chunk this is in in the player
|
|
// chunk map. If we did change something in that case, then the change wouldn't get sent to any player that had already received that full chunk, and so we'd just become desynchronised.
|
|
// Seems safest just to do an addToTickNextTick here instead with a decent delay, to make sure that we will get ticked again in the future, when we might again be in a chunk
|
|
// that is being tracked.
|
|
if( !level->isClientSide ) // Note - should only be being ticked on the server
|
|
{
|
|
if( !MinecraftServer::getInstance()->getPlayers()->isTrackingTile(x, y, z, level->dimension->id) )
|
|
{
|
|
level->addToTickNextTick(x, y, z, id, getTickDelay(level) * 5);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
bool infiniBurn = level->getTile(x, y - 1, z) == Tile::netherRack_Id;
|
|
if (level->dimension->id == 1) // 4J - was == instanceof TheEndDimension
|
|
{
|
|
if (level->getTile(x, y - 1, z) == Tile::unbreakable_Id) infiniBurn = true;
|
|
}
|
|
|
|
if (!mayPlace(level, x, y, z))
|
|
{
|
|
level->removeTile(x, y, z);
|
|
}
|
|
|
|
if (!infiniBurn && level->isRaining())
|
|
{
|
|
if (level->isRainingAt(x, y, z) || level->isRainingAt(x - 1, y, z) || level->isRainingAt(x + 1, y, z) || level->isRainingAt(x, y, z - 1) || level->isRainingAt(x, y, z + 1)) {
|
|
|
|
level->removeTile(x, y, z);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int age = level->getData(x, y, z);
|
|
if (age < 15)
|
|
{
|
|
level->setData(x, y, z, age + random->nextInt(3) / 2, Tile::UPDATE_NONE);
|
|
}
|
|
level->addToTickNextTick(x, y, z, id, getTickDelay(level) + random->nextInt(10));
|
|
|
|
if (!infiniBurn && !isValidFireLocation(level, x, y, z))
|
|
{
|
|
if (!level->isTopSolidBlocking(x, y - 1, z) || age > 3) level->removeTile(x, y, z);
|
|
return;
|
|
}
|
|
|
|
if (!infiniBurn && !canBurn(level, x, y - 1, z))
|
|
{
|
|
if (age == 15 && random->nextInt(4) == 0)
|
|
{
|
|
level->removeTile(x, y, z);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool isHumid = level->isHumidAt(x, y, z);
|
|
int extra = 0;
|
|
if (isHumid)
|
|
{
|
|
extra = -50;
|
|
}
|
|
checkBurnOut(level, x + 1, y, z, 300 + extra, random, age);
|
|
checkBurnOut(level, x - 1, y, z, 300 + extra, random, age);
|
|
checkBurnOut(level, x, y - 1, z, 250 + extra, random, age);
|
|
checkBurnOut(level, x, y + 1, z, 250 + extra, random, age);
|
|
checkBurnOut(level, x, y, z - 1, 300 + extra, random, age);
|
|
checkBurnOut(level, x, y, z + 1, 300 + extra, random, age);
|
|
if( app.GetGameHostOption(eGameHostOption_FireSpreads) )
|
|
{
|
|
for (int xx = x - 1; xx <= x + 1; xx++)
|
|
{
|
|
for (int zz = z - 1; zz <= z + 1; zz++)
|
|
{
|
|
for (int yy = y - 1; yy <= y + 4; yy++)
|
|
{
|
|
if (xx == x && yy == y && zz == z) continue;
|
|
|
|
int rate = 100;
|
|
if (yy > y + 1)
|
|
{
|
|
rate += ((yy - (y + 1)) * 100);
|
|
}
|
|
|
|
int fodds = getFireOdds(level, xx, yy, zz);
|
|
if (fodds > 0) {
|
|
int odds = (fodds + 40 + (level->difficulty * 7)) / (age + 30);
|
|
if (isHumid)
|
|
{
|
|
odds /= 2;
|
|
}
|
|
if (odds > 0 && random->nextInt(rate) <= odds)
|
|
{
|
|
if (!(level->isRaining() && level->isRainingAt(xx, yy, zz) || level->isRainingAt(xx - 1, yy, z) || level->isRainingAt(xx + 1, yy, zz) || level->isRainingAt(xx, yy, zz - 1) || level->isRainingAt(xx, yy, zz + 1)))
|
|
{
|
|
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
|
if (!FourKitBridge::FireBlockSpread(level->dimension->id, xx, yy, zz, x, y, z, id, min(age + random->nextInt(5) / 4, 15)))
|
|
#endif
|
|
{
|
|
int tAge = age + random->nextInt(5) / 4;
|
|
if (tAge > 15) tAge = 15;
|
|
level->setTileAndData(xx, yy, zz, id, tAge, Tile::UPDATE_ALL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FireTile::canInstantlyTick()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void FireTile::checkBurnOut(Level *level, int x, int y, int z, int chance, Random *random, int age)
|
|
{
|
|
int odds = burnOdds[level->getTile(x, y, z)];
|
|
if (random->nextInt(chance) < odds)
|
|
{
|
|
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
|
if (FourKitBridge::FireBlockBurn(level->dimension->id, x, y, z))
|
|
return;
|
|
#endif
|
|
bool wasTnt = level->getTile(x, y, z) == Tile::tnt_Id;
|
|
if (random->nextInt(age + 10) < 5 && !level->isRainingAt(x, y, z) && app.GetGameHostOption(eGameHostOption_FireSpreads))
|
|
{
|
|
int tAge = age + random->nextInt(5) / 4;
|
|
if (tAge > 15) tAge = 15;
|
|
level->setTileAndData(x, y, z, id, tAge, Tile::UPDATE_ALL);
|
|
} else
|
|
{
|
|
level->removeTile(x, y, z);
|
|
}
|
|
if (wasTnt)
|
|
{
|
|
Tile::tnt->destroy(level, x, y, z, TntTile::EXPLODE_BIT);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FireTile::isValidFireLocation(Level *level, int x, int y, int z)
|
|
{
|
|
if (canBurn(level, x + 1, y, z)) return true;
|
|
if (canBurn(level, x - 1, y, z)) return true;
|
|
if (canBurn(level, x, y - 1, z)) return true;
|
|
if (canBurn(level, x, y + 1, z)) return true;
|
|
if (canBurn(level, x, y, z - 1)) return true;
|
|
if (canBurn(level, x, y, z + 1)) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int FireTile::getFireOdds(Level *level, int x, int y, int z)
|
|
{
|
|
int odds = 0;
|
|
if (!level->isEmptyTile(x, y, z)) return 0;
|
|
|
|
odds = getFlammability(level, x + 1, y, z, odds);
|
|
odds = getFlammability(level, x - 1, y, z, odds);
|
|
odds = getFlammability(level, x, y - 1, z, odds);
|
|
odds = getFlammability(level, x, y + 1, z, odds);
|
|
odds = getFlammability(level, x, y, z - 1, odds);
|
|
odds = getFlammability(level, x, y, z + 1, odds);
|
|
|
|
return odds;
|
|
}
|
|
|
|
bool FireTile::mayPick()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FireTile::canBurn(LevelSource *level, int x, int y, int z)
|
|
{
|
|
return flameOdds[level->getTile(x, y, z)] > 0;
|
|
}
|
|
|
|
int FireTile::getFlammability(Level *level, int x, int y, int z, int odds)
|
|
{
|
|
int f = flameOdds[level->getTile(x, y, z)];
|
|
if (f > odds) return f;
|
|
return odds;
|
|
}
|
|
|
|
bool FireTile::mayPlace(Level *level, int x, int y, int z)
|
|
{
|
|
return level->isTopSolidBlocking(x, y - 1, z) || isValidFireLocation(level, x, y, z);
|
|
}
|
|
|
|
void FireTile::neighborChanged(Level *level, int x, int y, int z, int type)
|
|
{
|
|
if (!level->isTopSolidBlocking(x, y - 1, z) && !isValidFireLocation(level, x, y, z))
|
|
{
|
|
level->removeTile(x, y, z);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void FireTile::onPlace(Level *level, int x, int y, int z)
|
|
{
|
|
if (level->dimension->id <= 0 && level->getTile(x, y - 1, z) == Tile::obsidian_Id)
|
|
{
|
|
if (Tile::portalTile->trySpawnPortal(level, x, y, z, true))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if (!level->isTopSolidBlocking(x, y - 1, z) && !isValidFireLocation(level, x, y, z))
|
|
{
|
|
level->removeTile(x, y, z);
|
|
return;
|
|
}
|
|
level->addToTickNextTick(x, y, z, id, getTickDelay(level) + level->random->nextInt(10));
|
|
}
|
|
|
|
bool FireTile::isFlammable(int tile)
|
|
{
|
|
return flameOdds[tile] > 0;
|
|
}
|
|
|
|
void FireTile::animateTick(Level *level, int x, int y, int z, Random *random)
|
|
{
|
|
if (random->nextInt(24) == 0)
|
|
{
|
|
level->playLocalSound(x + 0.5f, y + 0.5f, z + 0.5f,eSoundType_FIRE_FIRE, 1 + random->nextFloat(), random->nextFloat() * 0.7f + 0.3f, false);
|
|
}
|
|
|
|
if (level->isTopSolidBlocking(x, y - 1, z) || Tile::fire->canBurn(level, x, y - 1, z))
|
|
{
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
float xx = x + random->nextFloat();
|
|
float yy = y + random->nextFloat() * 0.5f + 0.5f;
|
|
float zz = z + random->nextFloat();
|
|
level->addParticle(eParticleType_largesmoke, xx, yy, zz, 0, 0, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Tile::fire->canBurn(level, x - 1, y, z))
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
float xx = x + random->nextFloat() * 0.1f;
|
|
float yy = y + random->nextFloat();
|
|
float zz = z + random->nextFloat();
|
|
level->addParticle(eParticleType_largesmoke, xx, yy, zz, 0, 0, 0);
|
|
}
|
|
}
|
|
if (Tile::fire->canBurn(level, x + 1, y, z))
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
float xx = x + 1 - random->nextFloat() * 0.1f;
|
|
float yy = y + random->nextFloat();
|
|
float zz = z + random->nextFloat();
|
|
level->addParticle(eParticleType_largesmoke, xx, yy, zz, 0, 0, 0);
|
|
}
|
|
}
|
|
if (Tile::fire->canBurn(level, x, y, z - 1))
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
float xx = x + random->nextFloat();
|
|
float yy = y + random->nextFloat();
|
|
float zz = z + random->nextFloat() * 0.1f;
|
|
level->addParticle(eParticleType_largesmoke, xx, yy, zz, 0, 0, 0);
|
|
}
|
|
}
|
|
if (Tile::fire->canBurn(level, x, y, z + 1))
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
float xx = x + random->nextFloat();
|
|
float yy = y + random->nextFloat();
|
|
float zz = z + 1 - random->nextFloat() * 0.1f;
|
|
level->addParticle(eParticleType_largesmoke, xx, yy, zz, 0, 0, 0);
|
|
}
|
|
}
|
|
if (Tile::fire->canBurn(level, x, y + 1, z))
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
float xx = x + random->nextFloat();
|
|
float yy = y + 1 - random->nextFloat() * 0.1f;
|
|
float zz = z + random->nextFloat();
|
|
level->addParticle(eParticleType_largesmoke, xx, yy, zz, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FireTile::registerIcons(IconRegister *iconRegister)
|
|
{
|
|
icons = new Icon*[2];
|
|
icons[0] = iconRegister->registerIcon(TEXTURE_FIRST);
|
|
icons[1] = iconRegister->registerIcon(TEXTURE_SECOND);
|
|
}
|
|
|
|
Icon *FireTile::getTextureLayer(int layer)
|
|
{
|
|
#ifdef __PSVITA__
|
|
// AP - alpha cut out is expensive on vita. Set the Alpha Cut out flag
|
|
Tesselator* t = Tesselator::getInstance();
|
|
t->setAlphaCutOut( true );
|
|
#endif
|
|
|
|
return icons[layer];
|
|
}
|
|
|
|
Icon *FireTile::getTexture(int face, int data)
|
|
{
|
|
return icons[0];
|
|
}
|