smartcmd-MinecraftConsoles/Minecraft.World/Entity.cpp

2146 lines
48 KiB
C++
Raw Normal View History

2026-03-01 12:16:08 +08:00
#include "stdafx.h"
#include "com.mojang.nbt.h"
#include "net.minecraft.world.item.h"
#include "net.minecraft.world.item.enchantment.h"
#include "net.minecraft.world.level.h"
#include "net.minecraft.world.level.dimension.h"
2026-03-01 12:16:08 +08:00
#include "net.minecraft.world.level.tile.h"
#include "net.minecraft.world.phys.h"
#include "net.minecraft.world.entity.item.h"
#include "net.minecraft.world.level.material.h"
#include "net.minecraft.world.damagesource.h"
#include "SynchedEntityData.h"
#include "EntityIO.h"
#include "SharedConstants.h"
#include "ParticleTypes.h"
#include "EntityPos.h"
#include "Entity.h"
#include "SoundTypes.h"
#include "..\minecraft.Client\HumanoidModel.h"
#include "..\Minecraft.Client\MinecraftServer.h"
#include "..\Minecraft.Client\MultiPlayerLevel.h"
#include "..\Minecraft.Client\MultiplayerLocalPlayer.h"
#include "..\Minecraft.Client\ServerLevel.h"
#include "..\Minecraft.Client\PlayerList.h"
2026-03-01 12:16:08 +08:00
const wstring Entity::RIDING_TAG = L"Riding";
2026-03-01 12:16:08 +08:00
int Entity::entityCounter = 2048; // 4J - changed initialiser to 2048, as we are using range 0 - 2047 as special unique smaller ids for things that need network tracked
DWORD Entity::tlsIdx = TlsAlloc();
// 4J - added getSmallId & freeSmallId methods
unsigned int Entity::entityIdUsedFlags[2048/32] = {0};
unsigned int Entity::entityIdWanderFlags[2048/32] = {0};
unsigned int Entity::entityIdRemovingFlags[2048/32] = {0};
int Entity::extraWanderIds[EXTRA_WANDER_MAX] = {0};
int Entity::extraWanderTicks = 0;
int Entity::extraWanderCount = 0;
int Entity::getSmallId()
{
unsigned int *puiUsedFlags = entityIdUsedFlags;
unsigned int *puiRemovedFlags = nullptr;
2026-03-01 12:16:08 +08:00
// If we are the server (we should be, if we are allocating small Ids), then check with the server if there are any small Ids which are
// still in the ServerPlayer's vectors of entities to be removed - these are used to gather up a set of entities into one network packet
// for final notification to the client that the entities are removed. We can't go re-using these small Ids yet, as otherwise we will
// potentially end up telling the client that the entity has been removed After we have already re-used its Id and created a new entity.
// This ends up with newly created client-side entities being removed by accident, causing invisible mobs.
if( ((size_t)TlsGetValue(tlsIdx) != 0 ) )
{
MinecraftServer *server = MinecraftServer::getInstance();
if( server )
{
// In some attempt to optimise this, flagEntitiesToBeRemoved most of the time shouldn't do anything at all, and in this case it
// doesn't even memset the entityIdRemovingFlags array, so we shouldn't use it if the return value is false.
bool removedFound = server->flagEntitiesToBeRemoved(entityIdRemovingFlags);
if( removedFound )
{
// Has set up the entityIdRemovingFlags vector in this case, so we should check against this when allocating new ids
// app.DebugPrintf("getSmallId: Removed entities found\n");
2026-03-01 12:16:08 +08:00
puiRemovedFlags = entityIdRemovingFlags;
}
}
}
for( int i = 0; i < (2048 / 32 ); i++ )
{
unsigned int uiFlags = *puiUsedFlags;
if( uiFlags != 0xffffffff )
{
unsigned int uiMask = 0x80000000;
for( int j = 0; j < 32; j++ )
{
// See comments above - now checking (if required) that these aren't newly removed entities that the clients still haven't been told about,
// so we don't reuse those ids before we should.
if( puiRemovedFlags )
{
if( puiRemovedFlags[i] & uiMask )
{
// app.DebugPrintf("Avoiding using ID %d (0x%x)\n", i * 32 + j,puiRemovedFlags[i]);
2026-03-01 12:16:08 +08:00
uiMask >>= 1;
continue;
}
}
if( ( uiFlags & uiMask ) == 0 )
{
uiFlags |= uiMask;
*puiUsedFlags = uiFlags;
return i * 32 + j;
}
uiMask >>= 1;
}
}
puiUsedFlags++;
}
Fix server DedicatedServer issues (#1266) * add: Dedicated Server implementation - Introduced `ServerMain.cpp` for the dedicated server logic, handling command-line arguments, server initialization, and network management. - Created `postbuild_server.ps1` script for post-build tasks, including copying necessary resources and DLLs for the dedicated server. - Added `CopyServerAssets.cmake` to manage the copying of server assets during the build process, ensuring required files are available for the dedicated server. - Defined project filters in `Minecraft.Server.vcxproj.filters` for better organization of server-related files. * add: refactor world loader & add server properties - Introduced ServerLogger for logging startup steps and world I/O operations. - Implemented ServerProperties for loading and saving server configuration from `server.properties`. - Added WorldManager to handle world loading and creation based on server properties. - Updated ServerMain to integrate server properties loading and world management. - Enhanced project files to include new source and header files for the server components. * update: implement enhanced logging functionality with configurable log levels * update: update keyboard and mouse input initialization 1dc8a005ed111463c22c17b487e5ec8a3e2d30f3 * fix: change virtual screen resolution to 1920x1080(HD) Since 31881af56936aeef38ff322b975fd0 , `skinHud.swf` for 720 is not included in `MediaWindows64.arc`, the app crashes unless the virtual screen is set to HD. * fix: dedicated server build settings for miniaudio migration and missing sources - remove stale Windows64 Miles (mss64) link/copy references from server build - add Common/Filesystem/Filesystem.cpp to Minecraft.Server.vcxproj - add Windows64/PostProcesser.cpp to Minecraft.Server.vcxproj - fix unresolved externals (PostProcesser::*, FileExists) in dedicated server build * update: changed the virtual screen to 720p Since the crash caused by the 720p `skinHud.swf` not being included in `MediaWindows64.arc` has been resolved, switching back to 720p to reduce resource usage. * add: add Docker support for Dedicated Server add with entrypoint and build scripts * fix: add initial save for newly created worlds in dedicated server on the server side, I fixed the behavior introduced after commit aadb511, where newly created worlds are intentionally not saved to disk immediately. * update: add basically all configuration options that are implemented in the classes to `server.properties` * update: add LAN advertising configuration for server.properties LAN-Discovery, which isn’t needed in server mode and could potentially be a security risk, has also been disabled(only server mode). * add: add implementing interactive command line using linenoise - Integrated linenoise library for line editing and completion in the server console. - Updated ServerLogger to handle external writes safely during logging. - Modified ServerMain to initialize and manage the ServerCli for command input. - The implementation is separate from everything else, so it doesn't affect anything else. - The command input section and execution section are separated into threads. * update: enhance command line completion with predictive hints Like most command line tools, it highlights predictions in gray. * add: implement `StringUtils` for string manipulation and refactor usages Unified the scattered utility functions. * fix: send DisconnectPacket on shutdown and fix Win64 recv-thread teardown race Before this change, server/host shutdown closed sockets directly in ServerConnection::stop(), which bypassed the normal disconnect flow. As a result, clients could be dropped without receiving a proper DisconnectPacket during stop/kill/world-close paths. Also, WinsockNetLayer::Shutdown() could destroy synchronization objects while host-side recv threads were still exiting, causing a crash in RecvThreadProc (access violation on world close in host mode). * fix: return client to menus when Win64 host connection drops - Add client-side host disconnect handling in CPlatformNetworkManagerStub::DoWork() for _WINDOWS64. - When in QNET_STATE_GAME_PLAY as a non-host and WinsockNetLayer::IsConnected() becomes false, trigger g_NetworkManager.HandleDisconnect(false) to enter the normal disconnect/UI flow. - Use m_bLeaveGameOnTick as a one-shot guard to prevent repeated disconnect handling while the link remains down. - Reset m_bLeaveGameOnTick on LeaveGame(), HostGame(), and JoinGame() to avoid stale state across sessions. * update: converted Japanese comments to English * add: create `Minecraft.Server` developer guide in English and Japanese * update: add note about issue * add: add `nlohmann/json` json lib * add: add FileUtils Moved file operations to `utils`. * add: Dedicated Server BAN access manager with persistent player and IP bans - add Access frontend that publishes thread-safe ban manager snapshots for dedicated server use - add BanManager storage for banned-players.json and banned-ips.json with load/save/update flows - add persistent player and IP ban checks during dedicated server connection handling - add UTF-8 BOM-safe JSON parsing and shared file helpers backed by nlohmann/json - add Unicode-safe ban file read/write and safer atomic replacement behavior on Windows - add active-ban snapshot APIs and expiry-aware filtering for expires metadata - add RAII-based dedicated access shutdown handling during server startup and teardown * update: changed file read/write operations to use `FileUtils`. - As a side effect, saving has become faster! * fix: Re-added the source that had somehow disappeared. * add: significantly improved the dedicated server logging system - add ServerLogManager to Minecraft.Server as the single entry point for dedicated-server log output - forward CMinecraftApp logger output to the server logger when running with g_Win64DedicatedServer - add named network logs for incoming, accepted, rejected, and disconnected connections - cache connection metadata by smallId so player name and remote IP remain available for disconnect logs - keep Minecraft.Client changes minimal by using lightweight hook points and handling log orchestration on the server side * fix: added the updated library source * add: add `ban` and `pardon` commands for Player and IP * fix: fix stop command shutdown process add dedicated server shutdown request handling * fix: fixed the save logic during server shutdown Removed redundant repeated saves and eliminated the risks of async writes. * update: added new sever files to Docker entrypoint * fix: replace shutdown flag with atomic variable for thread safety * update: update Dedicated Server developer guide English is machine translated. Please forgive me. * update: check for the existence of `GameHDD` and create * add: add Whitelist to Dedicated Server * refactor: clean up and refactor the code - unify duplicated implementations that were copied repeatedly - update outdated patterns to more modern ones * fix: include UI header (new update fix) * fix: fix the detection range for excessive logging `getHighestNonEmptyY()` returning `-1` occurs normally when the chunk is entirely air. The caller (`Minecraft.World/LevelChunk.cpp:2400`) normalizes `-1` to `0`. * update: add world size config to dedicated server properties * update: update README add explanation of `server.properties` & launch arguments * update: add nightly release workflow for dedicated server and client builds to Actions * fix: update name for workflow * add random seed generation * add: add Docker nightly workflow for Dedicated Server publish to GitHub Container Registry * fix: ghost player when clients disconnect out of order #4 * fix: fix 7zip option * fix: fix Docker workflow for Dedicated Server artifact handling * add: add no build Dedicated Server startup scripts and Docker Compose * update: add README for Docker Dedicated Server setup with no local build * refactor: refactor command path structure As the number of commands has increased and become harder to navigate, each command has been organized into separate folders. * update: support stream(file stdin) input mode for server CLI Support for the stream (file stdin) required when attaching a tty to a Docker container on Linux. * add: add new CLI Console Commands for Dedicated Server Most of these commands are executed using the command dispatcher implemented on the `Minecraft.World` side. When registering them with the dispatcher, the sender uses a permission-enabled configuration that treats the CLI as a player. - default game. - enchant - experience. - give - kill(currently, getting a permission error for some reason) - time - weather. - update tp & gamemode command * fix: change player map icon to random select * update: increase the player limit * add: restore the basic anti-cheat implementation and add spawn protection Added the following anti-cheat measures and add spawn protection to `server.properties`. - instant break - speed - reach * fix: fix Docker image tag * make chunks delay less for dedi * fix: prevent overwriting allow-flight value on server startup * fix: mitigate entity id overflow and crash for max chunk updates * remove autosave prompt for dedicated server * fix: fix `Failed to create window instance.` Wait for Xvfb to be fully ready before starting. * Revert wrong readme order --------- Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com> Co-authored-by: Loki Rautio <lokirautio@gmail.com>
2026-03-16 05:50:12 +09:00
#ifdef MINECRAFT_SERVER_BUILD
// in mc server dedi, a server with 8+ playerrs can cause this to go wack
int fallbackId = Entity::entityCounter++;
if (entityCounter == 0x7ffffff)
{
entityCounter = 2048;
}
return fallbackId;
#else
2026-03-01 12:16:08 +08:00
app.DebugPrintf("Out of small entity Ids... possible leak?\n");
__debugbreak();
return -1;
Fix server DedicatedServer issues (#1266) * add: Dedicated Server implementation - Introduced `ServerMain.cpp` for the dedicated server logic, handling command-line arguments, server initialization, and network management. - Created `postbuild_server.ps1` script for post-build tasks, including copying necessary resources and DLLs for the dedicated server. - Added `CopyServerAssets.cmake` to manage the copying of server assets during the build process, ensuring required files are available for the dedicated server. - Defined project filters in `Minecraft.Server.vcxproj.filters` for better organization of server-related files. * add: refactor world loader & add server properties - Introduced ServerLogger for logging startup steps and world I/O operations. - Implemented ServerProperties for loading and saving server configuration from `server.properties`. - Added WorldManager to handle world loading and creation based on server properties. - Updated ServerMain to integrate server properties loading and world management. - Enhanced project files to include new source and header files for the server components. * update: implement enhanced logging functionality with configurable log levels * update: update keyboard and mouse input initialization 1dc8a005ed111463c22c17b487e5ec8a3e2d30f3 * fix: change virtual screen resolution to 1920x1080(HD) Since 31881af56936aeef38ff322b975fd0 , `skinHud.swf` for 720 is not included in `MediaWindows64.arc`, the app crashes unless the virtual screen is set to HD. * fix: dedicated server build settings for miniaudio migration and missing sources - remove stale Windows64 Miles (mss64) link/copy references from server build - add Common/Filesystem/Filesystem.cpp to Minecraft.Server.vcxproj - add Windows64/PostProcesser.cpp to Minecraft.Server.vcxproj - fix unresolved externals (PostProcesser::*, FileExists) in dedicated server build * update: changed the virtual screen to 720p Since the crash caused by the 720p `skinHud.swf` not being included in `MediaWindows64.arc` has been resolved, switching back to 720p to reduce resource usage. * add: add Docker support for Dedicated Server add with entrypoint and build scripts * fix: add initial save for newly created worlds in dedicated server on the server side, I fixed the behavior introduced after commit aadb511, where newly created worlds are intentionally not saved to disk immediately. * update: add basically all configuration options that are implemented in the classes to `server.properties` * update: add LAN advertising configuration for server.properties LAN-Discovery, which isn’t needed in server mode and could potentially be a security risk, has also been disabled(only server mode). * add: add implementing interactive command line using linenoise - Integrated linenoise library for line editing and completion in the server console. - Updated ServerLogger to handle external writes safely during logging. - Modified ServerMain to initialize and manage the ServerCli for command input. - The implementation is separate from everything else, so it doesn't affect anything else. - The command input section and execution section are separated into threads. * update: enhance command line completion with predictive hints Like most command line tools, it highlights predictions in gray. * add: implement `StringUtils` for string manipulation and refactor usages Unified the scattered utility functions. * fix: send DisconnectPacket on shutdown and fix Win64 recv-thread teardown race Before this change, server/host shutdown closed sockets directly in ServerConnection::stop(), which bypassed the normal disconnect flow. As a result, clients could be dropped without receiving a proper DisconnectPacket during stop/kill/world-close paths. Also, WinsockNetLayer::Shutdown() could destroy synchronization objects while host-side recv threads were still exiting, causing a crash in RecvThreadProc (access violation on world close in host mode). * fix: return client to menus when Win64 host connection drops - Add client-side host disconnect handling in CPlatformNetworkManagerStub::DoWork() for _WINDOWS64. - When in QNET_STATE_GAME_PLAY as a non-host and WinsockNetLayer::IsConnected() becomes false, trigger g_NetworkManager.HandleDisconnect(false) to enter the normal disconnect/UI flow. - Use m_bLeaveGameOnTick as a one-shot guard to prevent repeated disconnect handling while the link remains down. - Reset m_bLeaveGameOnTick on LeaveGame(), HostGame(), and JoinGame() to avoid stale state across sessions. * update: converted Japanese comments to English * add: create `Minecraft.Server` developer guide in English and Japanese * update: add note about issue * add: add `nlohmann/json` json lib * add: add FileUtils Moved file operations to `utils`. * add: Dedicated Server BAN access manager with persistent player and IP bans - add Access frontend that publishes thread-safe ban manager snapshots for dedicated server use - add BanManager storage for banned-players.json and banned-ips.json with load/save/update flows - add persistent player and IP ban checks during dedicated server connection handling - add UTF-8 BOM-safe JSON parsing and shared file helpers backed by nlohmann/json - add Unicode-safe ban file read/write and safer atomic replacement behavior on Windows - add active-ban snapshot APIs and expiry-aware filtering for expires metadata - add RAII-based dedicated access shutdown handling during server startup and teardown * update: changed file read/write operations to use `FileUtils`. - As a side effect, saving has become faster! * fix: Re-added the source that had somehow disappeared. * add: significantly improved the dedicated server logging system - add ServerLogManager to Minecraft.Server as the single entry point for dedicated-server log output - forward CMinecraftApp logger output to the server logger when running with g_Win64DedicatedServer - add named network logs for incoming, accepted, rejected, and disconnected connections - cache connection metadata by smallId so player name and remote IP remain available for disconnect logs - keep Minecraft.Client changes minimal by using lightweight hook points and handling log orchestration on the server side * fix: added the updated library source * add: add `ban` and `pardon` commands for Player and IP * fix: fix stop command shutdown process add dedicated server shutdown request handling * fix: fixed the save logic during server shutdown Removed redundant repeated saves and eliminated the risks of async writes. * update: added new sever files to Docker entrypoint * fix: replace shutdown flag with atomic variable for thread safety * update: update Dedicated Server developer guide English is machine translated. Please forgive me. * update: check for the existence of `GameHDD` and create * add: add Whitelist to Dedicated Server * refactor: clean up and refactor the code - unify duplicated implementations that were copied repeatedly - update outdated patterns to more modern ones * fix: include UI header (new update fix) * fix: fix the detection range for excessive logging `getHighestNonEmptyY()` returning `-1` occurs normally when the chunk is entirely air. The caller (`Minecraft.World/LevelChunk.cpp:2400`) normalizes `-1` to `0`. * update: add world size config to dedicated server properties * update: update README add explanation of `server.properties` & launch arguments * update: add nightly release workflow for dedicated server and client builds to Actions * fix: update name for workflow * add random seed generation * add: add Docker nightly workflow for Dedicated Server publish to GitHub Container Registry * fix: ghost player when clients disconnect out of order #4 * fix: fix 7zip option * fix: fix Docker workflow for Dedicated Server artifact handling * add: add no build Dedicated Server startup scripts and Docker Compose * update: add README for Docker Dedicated Server setup with no local build * refactor: refactor command path structure As the number of commands has increased and become harder to navigate, each command has been organized into separate folders. * update: support stream(file stdin) input mode for server CLI Support for the stream (file stdin) required when attaching a tty to a Docker container on Linux. * add: add new CLI Console Commands for Dedicated Server Most of these commands are executed using the command dispatcher implemented on the `Minecraft.World` side. When registering them with the dispatcher, the sender uses a permission-enabled configuration that treats the CLI as a player. - default game. - enchant - experience. - give - kill(currently, getting a permission error for some reason) - time - weather. - update tp & gamemode command * fix: change player map icon to random select * update: increase the player limit * add: restore the basic anti-cheat implementation and add spawn protection Added the following anti-cheat measures and add spawn protection to `server.properties`. - instant break - speed - reach * fix: fix Docker image tag * make chunks delay less for dedi * fix: prevent overwriting allow-flight value on server startup * fix: mitigate entity id overflow and crash for max chunk updates * remove autosave prompt for dedicated server * fix: fix `Failed to create window instance.` Wait for Xvfb to be fully ready before starting. * Revert wrong readme order --------- Co-authored-by: sylvessa <225480449+sylvessa@users.noreply.github.com> Co-authored-by: Loki Rautio <lokirautio@gmail.com>
2026-03-16 05:50:12 +09:00
#endif
2026-03-01 12:16:08 +08:00
}
void Entity::countFlagsForPIX()
{
int freecount = 0;
unsigned int *puiUsedFlags = entityIdUsedFlags;
for( int i = 0; i < (2048 / 32 ); i++ )
{
unsigned int uiFlags = *puiUsedFlags;
if( uiFlags != 0xffffffff )
{
unsigned int uiMask = 0x80000000;
for( int j = 0; j < 32; j++ )
{
if( ( uiFlags & uiMask ) == 0 )
{
freecount++;
}
uiMask >>= 1;
}
}
puiUsedFlags++;
}
PIXAddNamedCounter(freecount,"Small Ids free");
PIXAddNamedCounter(2048 - freecount,"Small Ids used");
}
void Entity::resetSmallId()
{
freeSmallId(entityId);
if( ((size_t)TlsGetValue(tlsIdx) != 0 ) )
{
entityId = getSmallId();
}
}
void Entity::freeSmallId(int index)
{
if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return; // Don't do anything with small ids if this isn't the server thread
if( index >= 2048 ) return; // Don't do anything if this isn't a short id
unsigned int i = index / 32;
unsigned int j = index % 32;
unsigned int uiMask = ~(0x80000000 >> j);
entityIdUsedFlags[i] &= uiMask;
entityIdWanderFlags[i] &= uiMask;
}
void Entity::useSmallIds()
{
TlsSetValue(tlsIdx,(LPVOID)1);
}
// Things also added here to be able to manage the concept of a number of extra "wandering" entities - normally path finding entities aren't allowed to
// randomly wander about once they are a certain distance away from any player, but we want to be able to (in a controlled fashion) allow some to be able
// to move so that we can determine whether they have been enclosed in some kind of farm, and so be able to better determine what shouldn't or shouldn't be despawned.
// Let the management system here know whether or not to consider this particular entity for some extra wandering
void Entity::considerForExtraWandering(bool enable)
{
if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return; // Don't do anything with small ids if this isn't the server thread
if( entityId >= 2048 ) return; // Don't do anything if this isn't a short id
unsigned int i = entityId / 32;
unsigned int j = entityId % 32;
if( enable )
{
unsigned int uiMask = 0x80000000 >> j;
entityIdWanderFlags[i] |= uiMask;
}
else
{
unsigned int uiMask = ~(0x80000000 >> j);
entityIdWanderFlags[i] &= uiMask;
}
}
// Should this entity do wandering in addition to what the java code would have done?
bool Entity::isExtraWanderingEnabled()
{
if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return false; // Don't do anything with small ids if this isn't the server thread
if( entityId >= 2048 ) return false; // Don't do anything if this isn't a short id
for( int i = 0; i < extraWanderCount; i++ )
{
if( extraWanderIds[i] == entityId ) return true;
}
return false;
}
// Returns a quadrant of direction that a given entity should be moved in - this is to stop the randomness of the wandering/strolling from just making the entity double back
// on itself and thus making the determination of whether the entity has been enclosed take longer than it needs to. This function returns a quadrant from 0 to 3
// that should be consistent within one period of an entity being considered for extra wandering, but should potentially vary between tries and between different entities.
int Entity::getWanderingQuadrant()
{
return ( entityId + ( extraWanderTicks / EXTRA_WANDER_TICKS ) ) & 3;
}
// Every EXTRA_WANDER_TICKS ticks, attempt to find EXTRA_WANDER_MAX entity Ids from those that have been flagged as ones that should be considered for
// extra wandering
void Entity::tickExtraWandering()
{
extraWanderTicks++;
// Time to move onto some new entities?
if( ( extraWanderTicks % EXTRA_WANDER_TICKS == 0 ) )
{
// printf("Updating extras: ");
// Start from the next Id after the one that we last found, or zero if we didn't find anything last time
int entityId = 0;
if( extraWanderCount )
{
entityId = ( extraWanderIds[ extraWanderCount - 1 ] + 1 ) % 2048;
}
extraWanderCount = 0;
for( int k = 0; ( k < 2048 ) && ( extraWanderCount < EXTRA_WANDER_MAX); k++ )
{
unsigned int i = entityId / 32;
unsigned int j = entityId % 32;
unsigned int uiMask = 0x80000000 >> j;
if( entityIdWanderFlags[i] & uiMask )
{
extraWanderIds[ extraWanderCount++ ] = entityId;
// printf("%d, ", entityId);
}
entityId = ( entityId + 1 ) % 2048;
}
// printf("\n");
}
}
// 4J - added for common ctor code
// Do all the default initialisations done in the java class
void Entity::_init(bool useSmallId, Level *level)
2026-03-01 12:16:08 +08:00
{
// 4J - changed to assign two different types of ids. A range from 0-2047 is used for things that we'll be wanting to identify over the network,
// so we should only need 11 bits rather than 32 to uniquely identify them. The rest of the range is used for anything we don't need to track like this,
// currently particles. We only ever want to allocate this type of id from the server thread, so using thread local storage to isolate this.
if( useSmallId && ((size_t)TlsGetValue(tlsIdx) != 0 ) )
{
entityId = getSmallId();
}
else
{
entityId = Entity::entityCounter++;
if(entityCounter == 0x7ffffff ) entityCounter = 2048;
}
viewScale = 1.0;
blocksBuilding = false;
rider = weak_ptr<Entity>();
riding = nullptr;
forcedLoading = false;
2026-03-01 12:16:08 +08:00
//level = nullptr; // Level is assigned to in the original c_tor code
2026-03-01 12:16:08 +08:00
xo = yo = zo = 0.0;
x = y = z = 0.0;
xd = yd = zd = 0.0;
yRot = xRot = 0.0f;
yRotO = xRotO = 0.0f;
bb = AABB::newPermanent(0, 0, 0, 0, 0, 0); // 4J Was final
onGround = false;
horizontalCollision = verticalCollision = false;
collision = false;
hurtMarked = false;
isStuckInWeb = false;
slide = true;
removed = false;
heightOffset = 0 / 16.0f;
bbWidth = 0.6f;
bbHeight = 1.8f;
walkDistO = 0;
walkDist = 0;
moveDist = 0.0f;
2026-03-01 12:16:08 +08:00
fallDistance = 0;
nextStep = 1;
xOld = yOld = zOld = 0.0;
ySlideOffset = 0;
footSize = 0.0f;
noPhysics = false;
pushthrough = 0.0f;
random = new Random();
tickCount = 0;
flameTime = 1;
onFire = 0;
wasInWater = false;
invulnerableTime = 0;
firstTick = true;
fireImmune = false;
// values that need to be sent to clients in SMP
if( useSmallId )
{
entityData = std::make_shared<SynchedEntityData>();
}
else
{
entityData = nullptr;
}
2026-03-01 12:16:08 +08:00
xRideRotA = yRideRotA = 0.0;
inChunk = false;
xChunk = yChunk = zChunk = 0;
xp = yp = zp = 0;
xRotp = yRotp = 0;
noCulling = false;
hasImpulse = false;
changingDimensionDelay = 0;
isInsidePortal = false;
portalTime = 0;
dimension = 0;
portalEntranceDir = 0;
invulnerable = false;
if( useSmallId )
{
uuid = L"ent" + Mth::createInsecureUUID(random);
}
2026-03-01 12:16:08 +08:00
// 4J Added
m_ignoreVerticalCollisions = false;
m_uiAnimOverrideBitmask = 0L;
m_ignorePortal = false;
2026-03-01 12:16:08 +08:00
}
Entity::Entity(Level *level, bool useSmallId) // 4J - added useSmallId parameter
{
MemSect(16);
_init(useSmallId, level);
2026-03-01 12:16:08 +08:00
MemSect(0);
this->level = level;
// resetPos();
setPos(0, 0, 0);
if (level != nullptr)
{
dimension = level->dimension->id;
}
if( entityData )
{
entityData->define(DATA_SHARED_FLAGS_ID, static_cast<byte>(0));
entityData->define(DATA_AIR_SUPPLY_ID, TOTAL_AIR_SUPPLY); // 4J Stu - Brought forward from 1.2.3 to fix 38654 - Gameplay: Player will take damage when air bubbles are present if resuming game from load/autosave underwater.
}
2026-03-01 12:16:08 +08:00
// 4J Stu - We cannot call virtual functions in ctors, as at this point the object
// is of type Entity and not a derived class
//this->defineSynchedData();
}
Entity::~Entity()
{
freeSmallId(entityId);
delete random;
delete bb;
}
shared_ptr<SynchedEntityData> Entity::getEntityData()
2026-03-01 12:16:08 +08:00
{
return entityData;
}
/*
public bool equals(Object obj) {
if (obj instanceof Entity) {
return ((Entity) obj).entityId == entityId;
}
return false;
}
public int hashCode() {
return entityId;
}
*/
void Entity::resetPos()
{
if (level == nullptr) return;
2026-03-01 12:16:08 +08:00
shared_ptr<Entity> sharedThis = shared_from_this();
2026-03-01 12:16:08 +08:00
while (true && y > 0)
{
setPos(x, y, z);
if (level->getCubes(sharedThis, bb)->empty()) break;
y += 1;
}
xd = yd = zd = 0;
xRot = 0;
}
void Entity::remove()
{
removed = true;
}
void Entity::setSize(float w, float h)
{
if (w != bbWidth || h != bbHeight)
2026-03-01 12:16:08 +08:00
{
float oldW = bbWidth;
bbWidth = w;
bbHeight = h;
bb->x1 = bb->x0 + bbWidth;
bb->z1 = bb->z0 + bbWidth;
bb->y1 = bb->y0 + bbHeight;
if (bbWidth > oldW && !firstTick && !level->isClientSide)
2026-03-01 12:16:08 +08:00
{
move(oldW - bbWidth, 0, oldW - bbWidth);
}
}
}
void Entity::setPos(EntityPos *pos)
{
if (pos->move) setPos(pos->x, pos->y, pos->z);
else setPos(x, y, z);
if (pos->rot) setRot(pos->yRot, pos->xRot);
else setRot(yRot, xRot);
}
void Entity::setRot(float yRot, float xRot)
{
/* JAVA:
2026-03-01 12:16:08 +08:00
this->yRot = yRot % 360.0f;
this->xRot = xRot % 360.0f;
C++ Cannot do mod of non-integral type
*/
while( yRot >= 360.0f )
yRot -= 360.0f;
while( yRot < 0 )
yRot += 360.0f;
while( xRot >= 360.0f )
xRot -= 360.0f;
this->yRot = yRot;
this->xRot = xRot;
}
void Entity::setPos(double x, double y, double z)
{
this->x = x;
this->y = y;
this->z = z;
float w = bbWidth / 2;
float h = bbHeight;
bb->set(x - w, y - heightOffset + ySlideOffset, z - w, x + w, y - heightOffset + ySlideOffset + h, z + w);
}
void Entity::turn(float xo, float yo)
{
float xRotOld = xRot;
float yRotOld = yRot;
yRot += xo * 0.15f;
xRot -= yo * 0.15f;
if (xRot < -90) xRot = -90;
if (xRot > 90) xRot = 90;
xRotO += xRot - xRotOld;
yRotO += yRot - yRotOld;
}
void Entity::interpolateTurn(float xo, float yo)
{
yRot += xo * 0.15f;
xRot -= yo * 0.15f;
if (xRot < -90) xRot = -90;
if (xRot > 90) xRot = 90;
}
void Entity::tick()
{
baseTick();
}
void Entity::baseTick()
{
// 4J Stu - Not needed
//util.Timer.push("entityBaseTick");
if (riding != nullptr && riding->removed)
{
riding = nullptr;
}
2026-03-01 12:16:08 +08:00
walkDistO = walkDist;
xo = x;
yo = y;
zo = z;
xRotO = xRot;
yRotO = yRot;
if (!level->isClientSide) // 4J Stu - Don't need this && level instanceof ServerLevel)
{
if(!m_ignorePortal) // 4J Added
{
MinecraftServer *server = dynamic_cast<ServerLevel *>(level)->getServer();
int waitTime = getPortalWaitTime();
if (isInsidePortal)
{
if (server->isNetherEnabled())
{
if (riding == nullptr)
{
if (portalTime++ >= waitTime)
{
portalTime = waitTime;
changingDimensionDelay = getDimensionChangingDelay();
int targetDimension;
if (level->dimension->id == -1)
{
targetDimension = 0;
}
else
{
targetDimension = -1;
}
changeDimension(targetDimension);
}
}
isInsidePortal = false;
}
}
else
{
if (portalTime > 0) portalTime -= 4;
if (portalTime < 0) portalTime = 0;
}
if (changingDimensionDelay > 0) changingDimensionDelay--;
}
}
2026-03-01 12:16:08 +08:00
if (isSprinting() && !isInWater() && canCreateParticles())
{
int xt = Mth::floor(x);
int yt = Mth::floor(y - 0.2f - heightOffset);
2026-03-01 12:16:08 +08:00
int zt = Mth::floor(z);
int t = level->getTile(xt, yt, zt);
int d = level->getData(xt, yt, zt);
if (t > 0)
{
level->addParticle(PARTICLE_TILECRACK(t,d), x + (random->nextFloat() - 0.5) * bbWidth, bb->y0 + 0.1, z + (random->nextFloat() - 0.5) * bbWidth, -xd * 4, 1.5, -zd * 4);
}
}
updateInWaterState();
2026-03-01 12:16:08 +08:00
if (level->isClientSide)
{
onFire = 0;
}
else
2026-03-01 12:16:08 +08:00
{
if (onFire > 0)
{
if (fireImmune)
{
onFire -= 4;
if (onFire < 0) onFire = 0;
}
else
{
if (onFire % 20 == 0)
{
hurt(DamageSource::onFire, 1);
}
onFire--;
}
}
}
if (isInLava())
{
lavaHurt();
fallDistance *= .5f;
}
if (y < -64)
{
outOfWorld();
}
if (!level->isClientSide)
{
setSharedFlag(FLAG_ONFIRE, onFire > 0);
}
firstTick = false;
// 4J Stu - Unused
//util.Timer.pop();
}
int Entity::getPortalWaitTime()
{
return 0;
}
2026-03-01 12:16:08 +08:00
void Entity::lavaHurt()
{
if (fireImmune)
{
}
else
{
hurt(DamageSource::lava, 4);
setOnFire(15);
}
}
void Entity::setOnFire(int numberOfSeconds)
{
int newValue = numberOfSeconds * SharedConstants::TICKS_PER_SECOND;
newValue = ProtectionEnchantment::getFireAfterDampener(shared_from_this(), newValue);
if (onFire < newValue)
{
onFire = newValue;
}
}
void Entity::clearFire()
{
onFire = 0;
}
void Entity::outOfWorld()
{
remove();
}
bool Entity::isFree(float xa, float ya, float za, float grow)
{
AABB *box = bb->grow(grow, grow, grow)->cloneMove(xa, ya, za);
AABBList *aABBs = level->getCubes(shared_from_this(), box);
if (!aABBs->empty()) return false;
if (level->containsAnyLiquid(box)) return false;
return true;
}
bool Entity::isFree(double xa, double ya, double za)
{
AABB *box = bb->cloneMove(xa, ya, za);
AABBList *aABBs = level->getCubes(shared_from_this(), box);
if (!aABBs->empty()) return false;
if (level->containsAnyLiquid(box)) return false;
return true;
}
void Entity::move(double xa, double ya, double za, bool noEntityCubes) // 4J - added noEntityCubes parameter
{
if (noPhysics)
{
bb->move(xa, ya, za);
x = (bb->x0 + bb->x1) / 2.0f;
y = bb->y0 + heightOffset - ySlideOffset;
z = (bb->z0 + bb->z1) / 2.0f;
return;
}
ySlideOffset *= 0.4f;
double xo = x;
double yo = y;
2026-03-01 12:16:08 +08:00
double zo = z;
if (isStuckInWeb)
{
isStuckInWeb = false;
xa *= 0.25f;
ya *= 0.05f;
za *= 0.25f;
xd = 0.0f;
yd = 0.0f;
zd = 0.0f;
}
double xaOrg = xa;
double yaOrg = ya;
double zaOrg = za;
AABB *bbOrg = bb->copy();
bool isPlayerSneaking = onGround && isSneaking() && instanceof(eTYPE_PLAYER);
2026-03-01 12:16:08 +08:00
if (isPlayerSneaking)
{
double d = 0.05;
while (xa != 0 && level->getCubes(shared_from_this(), bb->cloneMove(xa, -1.0, 0))->empty())
{
if (xa < d && xa >= -d) xa = 0;
else if (xa > 0) xa -= d;
else xa += d;
xaOrg = xa;
}
while (za != 0 && level->getCubes(shared_from_this(), bb->cloneMove(0, -1.0, za))->empty())
{
if (za < d && za >= -d) za = 0;
else if (za > 0) za -= d;
else za += d;
zaOrg = za;
}
while (xa != 0 && za != 0 && level->getCubes(shared_from_this(), bb->cloneMove(xa, -1.0, za))->empty())
{
if (xa < d && xa >= -d) xa = 0;
else if (xa > 0) xa -= d;
else xa += d;
if (za < d && za >= -d) za = 0;
else if (za > 0) za -= d;
else za += d;
xaOrg = xa;
zaOrg = za;
}
}
AABBList *aABBs = level->getCubes(shared_from_this(), bb->expand(xa, ya, za), noEntityCubes, true);
// 4J Stu - Particles (and possibly other entities) don't have xChunk and zChunk set, so calculate the chunk instead
int xc = Mth::floor(x / 16);
int zc = Mth::floor(z / 16);
2026-03-01 12:16:08 +08:00
if(!level->isClientSide || level->reallyHasChunk(xc, zc))
{
// 4J Stu - It's horrible that the client is doing any movement at all! But if we don't have the chunk
// data then all the collision info will be incorrect as well
for ( auto& it : *aABBs )
ya = it->clipYCollide(bb, ya);
2026-03-01 12:16:08 +08:00
bb->move(0, ya, 0);
}
if (!slide && yaOrg != ya)
{
xa = ya = za = 0;
}
bool og = onGround || (yaOrg != ya && yaOrg < 0);
for ( auto& it : *aABBs )
xa = it->clipXCollide(bb, xa);
2026-03-01 12:16:08 +08:00
bb->move(xa, 0, 0);
if (!slide && xaOrg != xa)
{
xa = ya = za = 0;
}
for ( auto& it : *aABBs )
za = it->clipZCollide(bb, za);
2026-03-01 12:16:08 +08:00
bb->move(0, 0, za);
if (!slide && zaOrg != za)
{
xa = ya = za = 0;
}
if (footSize > 0 && og && (isPlayerSneaking || ySlideOffset < 0.05f) && ((xaOrg != xa) || (zaOrg != za)))
{
double xaN = xa;
double yaN = ya;
double zaN = za;
xa = xaOrg;
ya = footSize;
za = zaOrg;
AABB *normal = bb->copy();
bb->set(bbOrg);
// 4J - added extra expand, as if we don't move up by footSize by hitting a block above us, then overall we could be trying to move as much as footSize downwards,
// so we'd better include cubes under our feet in this list of things we might possibly collide with
aABBs = level->getCubes(shared_from_this(), bb->expand(xa, ya, za)->expand(0,-ya,0),false,true);
if(!level->isClientSide || level->reallyHasChunk(xc, zc))
{
// 4J Stu - It's horrible that the client is doing any movement at all! But if we don't have the chunk
// data then all the collision info will be incorrect as well
for ( auto& it : *aABBs )
ya = it->clipYCollide(bb, ya);
2026-03-01 12:16:08 +08:00
bb->move(0, ya, 0);
}
if (!slide && yaOrg != ya)
{
xa = ya = za = 0;
}
for ( auto& it : *aABBs )
xa = it->clipXCollide(bb, xa);
2026-03-01 12:16:08 +08:00
bb->move(xa, 0, 0);
if (!slide && xaOrg != xa)
{
xa = ya = za = 0;
}
for ( auto& it : *aABBs )
za = it->clipZCollide(bb, za);
2026-03-01 12:16:08 +08:00
bb->move(0, 0, za);
if (!slide && zaOrg != za)
{
xa = ya = za = 0;
}
if (!slide && yaOrg != ya)
{
xa = ya = za = 0;
}
else
{
ya = -footSize;
// LAND FIRST, then x and z
for ( auto& it : *aABBs )
ya = it->clipYCollide(bb, ya);
2026-03-01 12:16:08 +08:00
bb->move(0, ya, 0);
}
if (xaN * xaN + zaN * zaN >= xa * xa + za * za)
{
xa = xaN;
ya = yaN;
za = zaN;
bb->set(normal);
}
}
x = (bb->x0 + bb->x1) / 2.0f;
y = bb->y0 + heightOffset - ySlideOffset;
z = (bb->z0 + bb->z1) / 2.0f;
horizontalCollision = (xaOrg != xa) || (zaOrg != za);
verticalCollision = !m_ignoreVerticalCollisions && (yaOrg != ya);
onGround = !m_ignoreVerticalCollisions && yaOrg != ya && yaOrg < 0;
collision = horizontalCollision || verticalCollision;
checkFallDamage(ya, onGround);
if (xaOrg != xa) xd = 0;
if (yaOrg != ya) yd = 0;
if (zaOrg != za) zd = 0;
double xm = x - xo;
double ym = y - yo;
2026-03-01 12:16:08 +08:00
double zm = z - zo;
if (makeStepSound() && !isPlayerSneaking && riding == nullptr)
2026-03-01 12:16:08 +08:00
{
int xt = Mth::floor(x);
int yt = Mth::floor(y - 0.2f - heightOffset);
2026-03-01 12:16:08 +08:00
int zt = Mth::floor(z);
int t = level->getTile(xt, yt, zt);
if (t == 0)
{
int renderShape = level->getTileRenderShape(xt, yt - 1, zt);
if (renderShape == Tile::SHAPE_FENCE || renderShape == Tile::SHAPE_WALL || renderShape == Tile::SHAPE_FENCE_GATE)
{
t = level->getTile(xt, yt - 1, zt);
}
}
if (t != Tile::ladder_Id)
{
ym = 0;
}
walkDist += Mth::sqrt(xm * xm + zm * zm) * 0.6;
moveDist += Mth::sqrt(xm * xm + ym * ym + zm * zm) * 0.6;
2026-03-01 12:16:08 +08:00
if (moveDist > nextStep && t > 0)
2026-03-01 12:16:08 +08:00
{
nextStep = static_cast<int>(moveDist) + 1;
if (isInWater())
{
float speed = Mth::sqrt(xd * xd * 0.2f + yd * yd + zd * zd * 0.2f) * 0.35f;
if (speed > 1) speed = 1;
playSound(eSoundType_LIQUID_SWIM, speed, 1 + (random->nextFloat() - random->nextFloat()) * 0.4f);
}
2026-03-01 12:16:08 +08:00
playStepSound(xt, yt, zt, t);
Tile::tiles[t]->stepOn(level, xt, yt, zt, shared_from_this());
}
}
checkInsideTiles();
bool water = isInWaterOrRain();
2026-03-01 12:16:08 +08:00
if (level->containsFireTile(bb->shrink(0.001, 0.001, 0.001)))
{
burn(1);
if (!water)
{
onFire++;
if (onFire == 0) setOnFire(8);
}
}
else
{
if (onFire <= 0)
{
onFire = -flameTime;
}
}
if (water && onFire > 0)
{
playSound(eSoundType_RANDOM_FIZZ, 0.7f, 1.6f + (random->nextFloat() - random->nextFloat()) * 0.4f);
2026-03-01 12:16:08 +08:00
onFire = -flameTime;
}
}
void Entity::checkInsideTiles()
{
int x0 = Mth::floor(bb->x0 + 0.001);
int y0 = Mth::floor(bb->y0 + 0.001);
int z0 = Mth::floor(bb->z0 + 0.001);
int x1 = Mth::floor(bb->x1 - 0.001);
int y1 = Mth::floor(bb->y1 - 0.001);
int z1 = Mth::floor(bb->z1 - 0.001);
if (level->hasChunksAt(x0, y0, z0, x1, y1, z1))
{
for (int x = x0; x <= x1; x++)
for (int y = y0; y <= y1; y++)
for (int z = z0; z <= z1; z++)
{
int t = level->getTile(x, y, z);
if (t > 0)
{
Tile::tiles[t]->entityInside(level, x, y, z, shared_from_this());
}
}
}
}
void Entity::playStepSound(int xt, int yt, int zt, int t)
{
const Tile::SoundType *soundType = Tile::tiles[t]->soundType;
MemSect(31);
if (GetType() == eTYPE_PLAYER)
2026-03-01 12:16:08 +08:00
{
// should we turn off step sounds?
unsigned int uiAnimOverrideBitmask=getAnimOverrideBitmask(); // this is masked for custom anim off, and force anim
if(( uiAnimOverrideBitmask& (1<<HumanoidModel::eAnim_NoLegAnim))!=0)
{
return;
}
}
if (level->getTile(xt, yt + 1, zt) == Tile::topSnow_Id)
2026-03-01 12:16:08 +08:00
{
soundType = Tile::topSnow->soundType;
playSound(soundType->getStepSound(), soundType->getVolume() * 0.15f, soundType->getPitch());
}
else if (!Tile::tiles[t]->material->isLiquid())
{
playSound(soundType->getStepSound(), soundType->getVolume() * 0.15f, soundType->getPitch());
2026-03-01 12:16:08 +08:00
}
2026-03-01 12:16:08 +08:00
MemSect(0);
}
void Entity::playSound(int iSound, float volume, float pitch)
{
level->playEntitySound(shared_from_this(), iSound, volume, pitch);
2026-03-01 12:16:08 +08:00
}
bool Entity::makeStepSound()
{
return true;
}
void Entity::checkFallDamage(double ya, bool onGround)
{
if (onGround)
{
if (fallDistance > 0)
{
causeFallDamage(fallDistance);
fallDistance = 0;
}
}
2026-03-01 12:16:08 +08:00
else
{
if (ya < 0) fallDistance -= static_cast<float>(ya);
2026-03-01 12:16:08 +08:00
}
}
AABB *Entity::getCollideBox()
{
return nullptr;
2026-03-01 12:16:08 +08:00
}
void Entity::burn(int dmg)
{
if (!fireImmune)
{
hurt(DamageSource::inFire, dmg);
}
}
bool Entity::isFireImmune()
{
return fireImmune;
}
void Entity::causeFallDamage(float distance)
{
if (rider.lock() != nullptr) rider.lock()->causeFallDamage(distance);
2026-03-01 12:16:08 +08:00
}
bool Entity::isInWaterOrRain()
{
return wasInWater || (level->isRainingAt( Mth::floor(x), Mth::floor(y), Mth::floor(z)) || level->isRainingAt(Mth::floor(x), Mth::floor(y + bbHeight), Mth::floor(z)));
2026-03-01 12:16:08 +08:00
}
bool Entity::isInWater()
{
return wasInWater;
}
bool Entity::updateInWaterState()
{
if(level->checkAndHandleWater(bb->grow(0, -0.4f, 0)->shrink(0.001, 0.001, 0.001), Material::water, shared_from_this()))
{
if (!wasInWater && !firstTick && canCreateParticles())
{
float speed = Mth::sqrt(xd * xd * 0.2f + yd * yd + zd * zd * 0.2f) * 0.2f;
if (speed > 1) speed = 1;
MemSect(31);
playSound(eSoundType_RANDOM_SPLASH, speed, 1 + (random->nextFloat() - random->nextFloat()) * 0.4f);
MemSect(0);
float yt = static_cast<float>(Mth::floor(bb->y0));
for (int i = 0; i < 1 + bbWidth * 20; i++)
{
float xo = (random->nextFloat() * 2 - 1) * bbWidth;
float zo = (random->nextFloat() * 2 - 1) * bbWidth;
level->addParticle(eParticleType_bubble, x + xo, yt + 1, z + zo, xd, yd - random->nextFloat() * 0.2f, zd);
}
for (int i = 0; i < 1 + bbWidth * 20; i++)
{
float xo = (random->nextFloat() * 2 - 1) * bbWidth;
float zo = (random->nextFloat() * 2 - 1) * bbWidth;
level->addParticle(eParticleType_splash, x + xo, yt + 1, z + zo, xd, yd, zd);
}
}
fallDistance = 0;
wasInWater = true;
onFire = 0;
}
else
{
wasInWater = false;
}
return wasInWater;
2026-03-01 12:16:08 +08:00
}
bool Entity::isUnderLiquid(Material *material)
{
double yp = y + getHeadHeight();
int xt = Mth::floor(x);
int yt = Mth::floor(yp); // 4J - this used to be a nested pair of floors for some reason
int zt = Mth::floor(z);
int t = level->getTile(xt, yt, zt);
if (t != 0 && Tile::tiles[t]->material == material) {
float hh = LiquidTile::getHeight(level->getData(xt, yt, zt)) - 1 / 9.0f;
float h = yt + 1 - hh;
return yp < h;
}
return false;
}
float Entity::getHeadHeight()
{
return 0;
}
bool Entity::isInLava()
{
return level->containsMaterial(bb->grow(-0.1f, -0.4f, -0.1f), Material::lava);
}
void Entity::moveRelative(float xa, float za, float speed)
{
float dist = xa * xa + za * za;
if (dist < 0.01f * 0.01f) return;
dist = sqrt(dist);
if (dist < 1) dist = 1;
dist = speed / dist;
xa *= dist;
za *= dist;
float sinVar = Mth::sin(yRot * PI / 180);
float cosVar = Mth::cos(yRot * PI / 180);
xd += xa * cosVar - za * sinVar;
zd += za * cosVar + xa * sinVar;
}
// 4J - change brought forward from 1.8.2
int Entity::getLightColor(float a)
{
int xTile = Mth::floor(x);
int zTile = Mth::floor(z);
if (level->hasChunkAt(xTile, 0, zTile))
{
double hh = (bb->y1 - bb->y0) * 0.66;
int yTile = Mth::floor(y - heightOffset + hh);
2026-03-01 12:16:08 +08:00
return level->getLightColor(xTile, yTile, zTile, 0);
}
return 0;
}
// 4J - changes brought forward from 1.8.2
float Entity::getBrightness(float a)
{
int xTile = Mth::floor(x);
int zTile = Mth::floor(z);
if (level->hasChunkAt(xTile, 0, zTile))
{
double hh = (bb->y1 - bb->y0) * 0.66;
int yTile = Mth::floor(y - heightOffset + hh);
2026-03-01 12:16:08 +08:00
return level->getBrightness(xTile, yTile, zTile);
}
return 0;
}
void Entity::setLevel(Level *level)
{
this->level = level;
}
void Entity::absMoveTo(double x, double y, double z, float yRot, float xRot)
{
xo = this->x = x;
yo = this->y = y;
zo = this->z = z;
yRotO = this->yRot = yRot;
xRotO = this->xRot = xRot;
2026-03-01 12:16:08 +08:00
ySlideOffset = 0;
double yRotDiff = yRotO - yRot;
if (yRotDiff < -180) yRotO += 360;
if (yRotDiff >= 180) yRotO -= 360;
setPos(this->x, this->y, this->z);
setRot(yRot, xRot);
2026-03-01 12:16:08 +08:00
}
void Entity::moveTo(double x, double y, double z, float yRot, float xRot)
{
xOld = xo = this->x = x;
yOld = yo = this->y = y + heightOffset;
zOld = zo = this->z = z;
2026-03-01 12:16:08 +08:00
this->yRot = yRot;
this->xRot = xRot;
setPos(this->x, this->y, this->z);
2026-03-01 12:16:08 +08:00
}
float Entity::distanceTo(shared_ptr<Entity> e)
2026-03-01 12:16:08 +08:00
{
float xd = static_cast<float>(x - e->x);
float yd = static_cast<float>(y - e->y);
float zd = static_cast<float>(z - e->z);
2026-03-01 12:16:08 +08:00
return sqrt(xd * xd + yd * yd + zd * zd);
}
double Entity::distanceToSqr(double x2, double y2, double z2)
{
double xd = (x - x2);
double yd = (y - y2);
double zd = (z - z2);
return xd * xd + yd * yd + zd * zd;
}
double Entity::distanceTo(double x2, double y2, double z2)
{
double xd = (x - x2);
double yd = (y - y2);
double zd = (z - z2);
return sqrt(xd * xd + yd * yd + zd * zd);
}
double Entity::distanceToSqr(shared_ptr<Entity> e)
2026-03-01 12:16:08 +08:00
{
double xd = x - e->x;
double yd = y - e->y;
double zd = z - e->z;
return xd * xd + yd * yd + zd * zd;
}
void Entity::playerTouch(shared_ptr<Player> player)
2026-03-01 12:16:08 +08:00
{
}
void Entity::push(shared_ptr<Entity> e)
2026-03-01 12:16:08 +08:00
{
if (e->rider.lock().get() == this || e->riding.get() == this) return;
double xa = e->x - x;
double za = e->z - z;
double dd = Mth::asbMax(xa, za);
if (dd >= 0.01f)
{
dd = sqrt(dd);
xa /= dd;
za /= dd;
double pow = 1 / dd;
if (pow > 1) pow = 1;
xa *= pow;
za *= pow;
xa *= 0.05f;
za *= 0.05f;
xa *= 1 - pushthrough;
za *= 1 - pushthrough;
push(-xa, 0, -za);
2026-03-01 12:16:08 +08:00
e->push(xa, 0, za);
}
}
void Entity::push(double xa, double ya, double za)
{
xd += xa;
yd += ya;
zd += za;
hasImpulse = true;
2026-03-01 12:16:08 +08:00
}
void Entity::markHurt()
{
hurtMarked = true;
2026-03-01 12:16:08 +08:00
}
bool Entity::hurt(DamageSource *source, float damage)
2026-03-01 12:16:08 +08:00
{
if(isInvulnerable()) return false;
2026-03-01 12:16:08 +08:00
markHurt();
return false;
}
bool Entity::intersects(double x0, double y0, double z0, double x1, double y1, double z1)
{
return bb->intersects(x0, y0, z0, x1, y1, z1);
}
bool Entity::isPickable()
{
return false;
}
bool Entity::isPushable()
{
return false;
}
bool Entity::isShootable()
{
return false;
}
void Entity::awardKillScore(shared_ptr<Entity> victim, int score)
2026-03-01 12:16:08 +08:00
{
}
bool Entity::shouldRender(Vec3 *c)
{
double xd = x - c->x;
double yd = y - c->y;
double zd = z - c->z;
double distance = xd * xd + yd * yd + zd * zd;
return shouldRenderAtSqrDistance(distance);
}
bool Entity::shouldRenderAtSqrDistance(double distance)
{
double size = bb->getSize();
size *= 64.0f * viewScale;
return distance < size * size;
}
bool Entity::isCreativeModeAllowed()
2026-03-01 12:16:08 +08:00
{
return false;
2026-03-01 12:16:08 +08:00
}
bool Entity::saveAsMount(CompoundTag *entityTag)
2026-03-01 12:16:08 +08:00
{
wstring id = getEncodeId();
if (removed || id.empty() )
{
return false;
}
// TODO Is this fine to be casting to a non-const char pointer?
entityTag->putString(L"id", id );
saveWithoutId(entityTag);
return true;
2026-03-01 12:16:08 +08:00
}
bool Entity::save(CompoundTag *entityTag)
{
wstring id = getEncodeId();
if (removed || id.empty() || (rider.lock() != nullptr) )
2026-03-01 12:16:08 +08:00
{
return false;
}
// TODO Is this fine to be casting to a non-const char pointer?
entityTag->putString(L"id", id );
saveWithoutId(entityTag);
return true;
}
void Entity::saveWithoutId(CompoundTag *entityTag)
{
entityTag->put(L"Pos", newDoubleList(3, x, y + ySlideOffset, z));
entityTag->put(L"Motion", newDoubleList(3, xd, yd, zd));
entityTag->put(L"Rotation", newFloatList(2, yRot, xRot));
entityTag->putFloat(L"FallDistance", fallDistance);
entityTag->putShort(L"Fire", static_cast<short>(onFire));
entityTag->putShort(L"Air", static_cast<short>(getAirSupply()));
2026-03-01 12:16:08 +08:00
entityTag->putBoolean(L"OnGround", onGround);
entityTag->putInt(L"Dimension", dimension);
entityTag->putBoolean(L"Invulnerable", invulnerable);
entityTag->putInt(L"PortalCooldown", changingDimensionDelay);
entityTag->putString(L"UUID", uuid);
2026-03-01 12:16:08 +08:00
addAdditonalSaveData(entityTag);
if (riding != nullptr)
{
CompoundTag *ridingTag = new CompoundTag(RIDING_TAG);
if (riding->saveAsMount(ridingTag))
{
entityTag->put(L"Riding", ridingTag);
}
}
2026-03-01 12:16:08 +08:00
}
void Entity::load(CompoundTag *tag)
{
ListTag<DoubleTag> *pos = (ListTag<DoubleTag> *) tag->getList(L"Pos");
ListTag<DoubleTag> *motion = (ListTag<DoubleTag> *) tag->getList(L"Motion");
ListTag<FloatTag> *rotation = (ListTag<FloatTag> *) tag->getList(L"Rotation");
xd = motion->get(0)->data;
yd = motion->get(1)->data;
zd = motion->get(2)->data;
if (abs(xd) > 10.0)
{
xd = 0;
}
if (abs(yd) > 10.0)
{
yd = 0;
}
if (abs(zd) > 10.0)
{
zd = 0;
}
xo = xOld = x = pos->get(0)->data;
yo = yOld = y = pos->get(1)->data;
zo = zOld = z = pos->get(2)->data;
yRotO = yRot = rotation->get(0)->data;
xRotO = xRot = rotation->get(1)->data;
fallDistance = tag->getFloat(L"FallDistance");
onFire = tag->getShort(L"Fire");
setAirSupply(tag->getShort(L"Air"));
onGround = tag->getBoolean(L"OnGround");
dimension = tag->getInt(L"Dimension");
invulnerable = tag->getBoolean(L"Invulnerable");
changingDimensionDelay = tag->getInt(L"PortalCooldown");
if (tag->contains(L"UUID"))
{
uuid = tag->getString(L"UUID");
}
2026-03-01 12:16:08 +08:00
setPos(x, y, z);
setRot(yRot, xRot);
readAdditionalSaveData(tag);
// set position again because bb size may have changed
if (repositionEntityAfterLoad()) setPos(x, y, z);
2026-03-01 12:16:08 +08:00
}
bool Entity::repositionEntityAfterLoad()
{
return true;
}
2026-03-01 12:16:08 +08:00
const wstring Entity::getEncodeId()
{
return EntityIO::getEncodeId( shared_from_this() );
}
/**
* Called after load() has finished and the entity has been added to the
* world
*/
void Entity::onLoadedFromSave()
{
}
template<typename ...Args>
ListTag<DoubleTag> *Entity::newDoubleList(unsigned int, double firstValue, Args... args)
2026-03-01 12:16:08 +08:00
{
ListTag<DoubleTag> *res = new ListTag<DoubleTag>();
// Add the first parameter to the ListTag
res->add( new DoubleTag(L"", firstValue ) );
// use pre-C++17 fold trick (TODO: once we drop C++14 support, use C++14 fold expression)
using expander = int[];
(void)expander{0, (res->add(new DoubleTag(L"", static_cast<double>(args))), 0)...};
2026-03-01 12:16:08 +08:00
return res;
}
ListTag<FloatTag> *Entity::newFloatList(unsigned int number, float firstValue, float secondValue)
{
ListTag<FloatTag> *res = new ListTag<FloatTag>();
// Add the first parameter to the ListTag
res->add( new FloatTag( L"", firstValue ) );
// TODO - 4J Stu For some reason the va_list wasn't working correctly here
// We only make a list of two floats so just overriding and not using va_list
res->add( new FloatTag( L"", secondValue ) );
/*
va_list vl;
va_start(vl,firstValue);
float val;
for (unsigned int i = 1; i < number; i++)
{
val = va_arg(vl,float);
res->add(new FloatTag(val));
}
va_end(vl);
*/
return res;
}
float Entity::getShadowHeightOffs()
{
return bbHeight / 2;
}
shared_ptr<ItemEntity> Entity::spawnAtLocation(int resource, int count)
2026-03-01 12:16:08 +08:00
{
return spawnAtLocation(resource, count, 0);
}
shared_ptr<ItemEntity> Entity::spawnAtLocation(int resource, int count, float yOffs)
2026-03-01 12:16:08 +08:00
{
return spawnAtLocation(std::make_shared<ItemInstance>(resource, count, 0), yOffs);
2026-03-01 12:16:08 +08:00
}
shared_ptr<ItemEntity> Entity::spawnAtLocation(shared_ptr<ItemInstance> itemInstance, float yOffs)
2026-03-01 12:16:08 +08:00
{
if (itemInstance->count == 0)
{
return nullptr;
}
shared_ptr<ItemEntity> ie = std::make_shared<ItemEntity>(level, x, y + yOffs, z, itemInstance);
2026-03-01 12:16:08 +08:00
ie->throwTime = 10;
level->addEntity(ie);
return ie;
}
bool Entity::isAlive()
{
return !removed;
}
bool Entity::isInWall()
{
for (int i = 0; i < 8; i++)
{
float xo = ((i >> 0) % 2 - 0.5f) * bbWidth * 0.8f;
float yo = ((i >> 1) % 2 - 0.5f) * 0.1f;
float zo = ((i >> 2) % 2 - 0.5f) * bbWidth * 0.8f;
int xt = Mth::floor(x + xo);
int yt = Mth::floor(y + getHeadHeight() + yo);
2026-03-01 12:16:08 +08:00
int zt = Mth::floor(z + zo);
if (level->isSolidBlockingTile(xt, yt, zt))
{
return true;
}
}
return false;
}
bool Entity::interact(shared_ptr<Player> player)
2026-03-01 12:16:08 +08:00
{
return false;
}
AABB *Entity::getCollideAgainstBox(shared_ptr<Entity> entity)
2026-03-01 12:16:08 +08:00
{
return nullptr;
2026-03-01 12:16:08 +08:00
}
void Entity::rideTick()
{
if (riding->removed)
{
riding = nullptr;
return;
}
xd = yd = zd = 0;
tick();
if (riding == nullptr) return;
2026-03-01 12:16:08 +08:00
// Sets riders old&new position to it's mount's old&new position (plus the ride y-seperatation).
riding->positionRider();
yRideRotA += (riding->yRot - riding->yRotO);
xRideRotA += (riding->xRot - riding->xRotO);
// Wrap rotation angles.
while (yRideRotA >= 180) yRideRotA -= 360;
while (yRideRotA < -180) yRideRotA += 360;
while (xRideRotA >= 180) xRideRotA -= 360;
while (xRideRotA < -180) xRideRotA += 360;
double yra = yRideRotA * 0.5;
double xra = xRideRotA * 0.5;
// Cap rotation speed.
float max = 10;
if (yra > max) yra = max;
if (yra < -max) yra = -max;
if (xra > max) xra = max;
if (xra < -max) xra = -max;
yRideRotA -= yra;
xRideRotA -= xra;
// jeb: This caused the crosshair to "drift" while riding horses. For now I've just disabled it,
// because I can't figure out what it's needed for. Riding boats and minecarts seem unaffected...
// yRot += yra;
// xRot += xra;
2026-03-01 12:16:08 +08:00
}
void Entity::positionRider()
{
shared_ptr<Entity> lockedRider = rider.lock();
if( lockedRider == nullptr)
2026-03-01 12:16:08 +08:00
{
return;
2026-03-01 12:16:08 +08:00
}
lockedRider->setPos(x, y + getRideHeight() + lockedRider->getRidingHeight(), z);
2026-03-01 12:16:08 +08:00
}
double Entity::getRidingHeight()
{
return heightOffset;
}
double Entity::getRideHeight()
{
return bbHeight * .75;
}
void Entity::ride(shared_ptr<Entity> e)
2026-03-01 12:16:08 +08:00
{
xRideRotA = 0;
yRideRotA = 0;
if (e == nullptr)
2026-03-01 12:16:08 +08:00
{
if (riding != nullptr)
2026-03-01 12:16:08 +08:00
{
// 4J Stu - Position should already be updated before the SetEntityLinkPacket comes in
2026-03-01 12:16:08 +08:00
if(!level->isClientSide) moveTo(riding->x, riding->bb->y0 + riding->bbHeight, riding->z, yRot, xRot);
riding->rider = weak_ptr<Entity>();
}
riding = nullptr;
return;
}
if (riding != nullptr)
2026-03-01 12:16:08 +08:00
{
riding->rider = weak_ptr<Entity>();
}
riding = e;
e->rider = shared_from_this();
}
void Entity::lerpTo(double x, double y, double z, float yRot, float xRot, int steps)
{
setPos(x, y, z);
setRot(yRot, xRot);
// 4J - don't know what this special y collision is specifically for, but its definitely bad news
// for arrows as they are actually Meant to intersect the geometry they land in slightly.
if( GetType() != eTYPE_ARROW )
{
AABBList *collisions = level->getCubes(shared_from_this(), bb->shrink(1 / 32.0, 0, 1 / 32.0));
if ( collisions && !collisions->empty())
2026-03-01 12:16:08 +08:00
{
double yTop = 0;
for ( const AABB *ab : *collisions )
2026-03-01 12:16:08 +08:00
{
if ( ab && ab->y1 > yTop) yTop = ab->y1;
2026-03-01 12:16:08 +08:00
}
y += yTop - bb->y0;
setPos(x, y, z);
}
}
}
float Entity::getPickRadius()
{
return 0.1f;
}
Vec3 *Entity::getLookAngle()
{
return nullptr;
2026-03-01 12:16:08 +08:00
}
void Entity::handleInsidePortal()
{
if (changingDimensionDelay > 0)
{
changingDimensionDelay = getDimensionChangingDelay();
return;
}
double xd = xo - x;
double zd = zo - z;
if (!level->isClientSide && !isInsidePortal)
{
portalEntranceDir = Direction::getDirection(xd, zd);
}
isInsidePortal = true;
}
int Entity::getDimensionChangingDelay()
{
return SharedConstants::TICKS_PER_SECOND * 45;
2026-03-01 12:16:08 +08:00
}
void Entity::lerpMotion(double xd, double yd, double zd)
{
this->xd = xd;
this->yd = yd;
this->zd = zd;
}
void Entity::handleEntityEvent(byte eventId)
{
}
void Entity::animateHurt()
{
}
ItemInstanceArray Entity::getEquipmentSlots() // ItemInstance[]
{
return ItemInstanceArray(); // Default ctor creates nullptr internal array
2026-03-01 12:16:08 +08:00
}
// 4J Stu - Brought forward change from 1.3 to fix #64688 - Customer Encountered: TU7: Content: Art: Aura of enchanted item is not displayed for other players in online game
void Entity::setEquippedSlot(int slot, shared_ptr<ItemInstance> item)
2026-03-01 12:16:08 +08:00
{
}
bool Entity::isOnFire()
{
return !fireImmune && (onFire > 0 || getSharedFlag(FLAG_ONFIRE));
2026-03-01 12:16:08 +08:00
}
bool Entity::isRiding()
{
return riding != nullptr;
2026-03-01 12:16:08 +08:00
}
bool Entity::isSneaking()
{
return getSharedFlag(FLAG_SNEAKING);
}
void Entity::setSneaking(bool value)
{
setSharedFlag(FLAG_SNEAKING, value);
}
bool Entity::isIdle()
{
return getSharedFlag(FLAG_IDLEANIM);
}
void Entity::setIsIdle(bool value)
{
setSharedFlag(FLAG_IDLEANIM, value);
}
bool Entity::isSprinting()
{
return getSharedFlag(FLAG_SPRINTING);
}
void Entity::setSprinting(bool value)
{
setSharedFlag(FLAG_SPRINTING, value);
}
bool Entity::isInvisible()
{
return getSharedFlag(FLAG_INVISIBLE);
}
bool Entity::isInvisibleTo(shared_ptr<Player> plr)
2026-03-01 12:16:08 +08:00
{
return isInvisible();
}
void Entity::setInvisible(bool value)
{
setSharedFlag(FLAG_INVISIBLE, value);
}
bool Entity::isWeakened()
{
return getSharedFlag(FLAG_EFFECT_WEAKENED);
}
void Entity::setWeakened(bool value)
{
setSharedFlag(FLAG_EFFECT_WEAKENED, value);
}
bool Entity::isUsingItemFlag()
{
return getSharedFlag(FLAG_USING_ITEM);
}
void Entity::setUsingItemFlag(bool value)
{
setSharedFlag(FLAG_USING_ITEM, value);
}
bool Entity::getSharedFlag(int flag)
{
if( entityData )
{
return (entityData->getByte(DATA_SHARED_FLAGS_ID) & (1 << flag)) != 0;
}
else
{
return false;
}
2026-03-01 12:16:08 +08:00
}
void Entity::setSharedFlag(int flag, bool value)
{
if( entityData )
2026-03-01 12:16:08 +08:00
{
byte currentValue = entityData->getByte(DATA_SHARED_FLAGS_ID);
if (value)
{
entityData->set(DATA_SHARED_FLAGS_ID, static_cast<byte>(currentValue | (1 << flag)));
}
else
{
entityData->set(DATA_SHARED_FLAGS_ID, static_cast<byte>(currentValue & ~(1 << flag)));
}
2026-03-01 12:16:08 +08:00
}
}
// 4J Stu - Brought forward from 1.2.3 to fix 38654 - Gameplay: Player will take damage when air bubbles are present if resuming game from load/autosave underwater.
int Entity::getAirSupply()
{
return entityData->getShort(DATA_AIR_SUPPLY_ID);
}
// 4J Stu - Brought forward from 1.2.3 to fix 38654 - Gameplay: Player will take damage when air bubbles are present if resuming game from load/autosave underwater.
void Entity::setAirSupply(int supply)
{
entityData->set(DATA_AIR_SUPPLY_ID, static_cast<short>(supply));
2026-03-01 12:16:08 +08:00
}
void Entity::thunderHit(const LightningBolt *lightningBolt)
{
burn(5);
onFire++;
if (onFire == 0) setOnFire(8);
}
void Entity::killed(shared_ptr<LivingEntity> mob)
2026-03-01 12:16:08 +08:00
{
}
bool Entity::checkInTile(double x, double y, double z)
{
int xTile = Mth::floor(x);
int yTile = Mth::floor(y);
int zTile = Mth::floor(z);
double xd = x - (xTile);
double yd = y - (yTile);
double zd = z - (zTile);
vector<AABB *> *cubes = level->getTileCubes(bb);
if ( (cubes && !cubes->empty()) || level->isFullAABBTile(xTile, yTile, zTile))
2026-03-01 12:16:08 +08:00
{
bool west = !level->isFullAABBTile(xTile - 1, yTile, zTile);
bool east = !level->isFullAABBTile(xTile + 1, yTile, zTile);
bool down = !level->isFullAABBTile(xTile, yTile - 1, zTile);
bool up = !level->isFullAABBTile(xTile, yTile + 1, zTile);
bool north = !level->isFullAABBTile(xTile, yTile, zTile - 1);
bool south = !level->isFullAABBTile(xTile, yTile, zTile + 1);
int dir = 3;
2026-03-01 12:16:08 +08:00
double closest = 9999;
if (west && xd < closest)
{
closest = xd;
dir = 0;
}
if (east && 1 - xd < closest)
{
closest = 1 - xd;
dir = 1;
}
if (up && 1 - yd < closest)
2026-03-01 12:16:08 +08:00
{
closest = 1 - yd;
dir = 3;
}
if (north && zd < closest)
{
closest = zd;
dir = 4;
}
if (south && 1 - zd < closest)
{
closest = 1 - zd;
dir = 5;
}
float speed = random->nextFloat() * 0.2f + 0.1f;
if (dir == 0) this->xd = -speed;
if (dir == 1) this->xd = +speed;
if (dir == 2) this->yd = -speed;
if (dir == 3) this->yd = +speed;
if (dir == 4) this->zd = -speed;
if (dir == 5) this->zd = +speed;
2026-03-01 12:16:08 +08:00
return true;
}
return false;
}
void Entity::makeStuckInWeb()
{
isStuckInWeb = true;
fallDistance = 0;
}
wstring Entity::getAName()
{
#ifdef _DEBUG
2026-03-01 12:16:08 +08:00
wstring id = EntityIO::getEncodeId(shared_from_this());
if (id.empty()) id = L"generic";
return L"entity." + id + std::to_wstring(entityId);
#else
return L"";
#endif
2026-03-01 12:16:08 +08:00
}
vector<shared_ptr<Entity> > *Entity::getSubEntities()
2026-03-01 12:16:08 +08:00
{
return nullptr;
2026-03-01 12:16:08 +08:00
}
bool Entity::is(shared_ptr<Entity> other)
2026-03-01 12:16:08 +08:00
{
return shared_from_this() == other;
}
float Entity::getYHeadRot()
{
return 0;
}
void Entity::setYHeadRot(float yHeadRot)
{
}
bool Entity::isAttackable()
{
return true;
}
bool Entity::skipAttackInteraction(shared_ptr<Entity> source)
2026-03-01 12:16:08 +08:00
{
return false;
}
bool Entity::isInvulnerable()
{
return invulnerable;
}
void Entity::copyPosition(shared_ptr<Entity> target)
2026-03-01 12:16:08 +08:00
{
moveTo(target->x, target->y, target->z, target->yRot, target->xRot);
}
void Entity::restoreFrom(shared_ptr<Entity> oldEntity, bool teleporting)
{
CompoundTag *tag = new CompoundTag();
oldEntity->saveWithoutId(tag);
load(tag);
delete tag;
changingDimensionDelay = oldEntity->changingDimensionDelay;
portalEntranceDir = oldEntity->portalEntranceDir;
}
void Entity::changeDimension(int i)
{
if (level->isClientSide || removed) return;
MinecraftServer *server = MinecraftServer::getInstance();
int lastDimension = dimension;
ServerLevel *oldLevel = server->getLevel(lastDimension);
ServerLevel *newLevel = server->getLevel(i);
if (lastDimension == 1 && i == 1)
{
newLevel = server->getLevel(0);
}
// 4J: Restrictions on what can go through
{
// 4J: Some things should just be destroyed when they hit a portal
if (instanceof(eTYPE_FALLINGTILE))
{
removed = true;
return;
}
// 4J: Check server level entity limit (arrows, item entities, experience orbs, etc)
if (newLevel->atEntityLimit(shared_from_this())) return;
// 4J: Check level limit on living entities, minecarts and boats
if (!instanceof(eTYPE_PLAYER) && !newLevel->canCreateMore(GetType(), Level::eSpawnType_Portal)) return;
}
// 4J: Definitely sending, set dimension now
dimension = newLevel->dimension->id;
level->removeEntity(shared_from_this());
removed = false;
server->getPlayers()->repositionAcrossDimension(shared_from_this(), lastDimension, oldLevel, newLevel);
shared_ptr<Entity> newEntity = EntityIO::newEntity(EntityIO::getEncodeId(shared_from_this()), newLevel);
if (newEntity != nullptr)
{
newEntity->restoreFrom(shared_from_this(), true);
if (lastDimension == 1 && i == 1)
{
Pos *spawnPos = newLevel->getSharedSpawnPos();
spawnPos->y = level->getTopSolidBlock(spawnPos->x, spawnPos->z);
newEntity->moveTo(spawnPos->x, spawnPos->y, spawnPos->z, newEntity->yRot, newEntity->xRot);
delete spawnPos;
}
newLevel->addEntity(newEntity);
}
removed = true;
oldLevel->resetEmptyTime();
newLevel->resetEmptyTime();
}
float Entity::getTileExplosionResistance(Explosion *explosion, Level *level, int x, int y, int z, Tile *tile)
{
return tile->getExplosionResistance(shared_from_this());
}
bool Entity::shouldTileExplode(Explosion *explosion, Level *level, int x, int y, int z, int id, float power)
{
return true;
}
int Entity::getMaxFallDistance()
{
return 3;
}
int Entity::getPortalEntranceDir()
{
return portalEntranceDir;
}
bool Entity::isIgnoringTileTriggers()
{
return false;
}
bool Entity::displayFireAnimation()
{
return isOnFire();
}
void Entity::setUUID(const wstring &UUID)
{
uuid = UUID;
}
wstring Entity::getUUID()
{
return uuid;
}
bool Entity::isPushedByWater()
{
return true;
}
wstring Entity::getDisplayName()
{
return getAName();
}
// 4J: Added to retrieve name that should be sent in ChatPackets (important on Xbox One for players)
wstring Entity::getNetworkName()
{
return getDisplayName();
}
void Entity::setAnimOverrideBitmask(unsigned int uiBitmask)
2026-03-01 12:16:08 +08:00
{
m_uiAnimOverrideBitmask=uiBitmask;
app.DebugPrintf("!!! Setting anim override bitmask to %d\n",uiBitmask);
}
unsigned int Entity::getAnimOverrideBitmask()
{
2026-03-01 12:16:08 +08:00
if(app.GetGameSettings(eGameSetting_CustomSkinAnim)==0 )
{
// We have a force animation for some skins (claptrap)
// 4J-PB - treat all the eAnim_Disable flags as a force anim
unsigned int uiIgnoreUserCustomSkinAnimSettingMask=(1<<HumanoidModel::eAnim_ForceAnim) |
(1<<HumanoidModel::eAnim_DisableRenderArm0) |
(1<<HumanoidModel::eAnim_DisableRenderArm1) |
(1<<HumanoidModel::eAnim_DisableRenderTorso) |
(1<<HumanoidModel::eAnim_DisableRenderLeg0) |
(1<<HumanoidModel::eAnim_DisableRenderLeg1) |
Squashed commit of the following: commit b40530fa5e12bdd0e2d03686b111964f9c5b3359 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Wed Apr 15 19:59:46 2026 -0700 Implemented skin offsets in UI Added code to render skin offsets in the skin select UI. commit a8384d984089b989a162550705dee7a505412e22 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Wed Apr 15 19:38:08 2026 -0700 Partially implemented offsets Added code that visually shifts the player's model parts, but only in game not in the skin select UI. commit 875100cf9afe7df258dc653a4b33857d3cd285c1 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Wed Apr 15 16:48:03 2026 -0700 Minor change Simplified redundant conditions in HumanoidModel.cpp commit 96f683d1fb93d09a49987460ef698e0d125c2c93 Merge: db685a74 24c74aa2 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Tue Apr 14 16:37:30 2026 -0700 Merge branch 'feat/64x64-skins' into feat/skin-offsets commit db685a74f34d02cc83e33ab97c5a7ad9a62ef023 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Tue Apr 14 15:35:38 2026 -0700 Fixed skin offset data Fixed skin offsets so they now return the actual data instead of the defaults, added a few minor tweaks, and added code in PlayerRenderer.cpp to access offsets (Can read the offsets but can not apply them). commit aa769d54adb8f5bf96c75d10766422d23cc9129a Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Sat Apr 11 19:36:52 2026 -0700 Fixed crashes Fixed code for offsets preventing crashes. The amount of offsets is correctly obtain, but lacks the actual data. commit f18ac12cc072db74ed9b8da937e00c2e15f889a0 Merge: 8e76763a fd2fd659 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Fri Apr 10 16:06:57 2026 -0700 Merge branch 'feat/64x64-skins' into feat/skin-offsets commit 8e76763a3ddeaff943243fd0469d6c4a7b12b6c1 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Tue Apr 7 16:50:43 2026 -0700 Made more changes Made more changes in files to support skin offsets. The game still crashes when trying to load skins. commit 1a8f3532979033717044e74e6ceb766e23e35032 Merge: a1d9ae59 bb5fa506 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Tue Apr 7 13:12:39 2026 -0700 Merge branch 'feat/64x64-skins' into feat/skin-offsets commit a1d9ae591ac27117626aac708a4204a9e2451684 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Fri Apr 3 21:50:42 2026 -0700 Added small additions Added more code referencing skin offsets. Still doesn't work correctly. commit d28a751d9ca0527854e40be82e7841a650c2f189 Merge: 3888de7a 8bf03435 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Thu Apr 2 17:09:08 2026 -0700 Merge branch 'smartcmd:main' into feat/skin-offsets commit 3888de7ab401d9562b7b922eca8b898aa73ba024 Author: Langtanium <94726057+Langtanium@users.noreply.github.com> Date: Thu Apr 2 17:07:48 2026 -0700 Added code for skin offsets Added code to the file which have the functionality to get skin boxes and duplicated the functionality for skin offsets. The code causes the game to crash when switching to third person. The error occurs with the skin offsets returning as an empty class object.
2026-04-15 20:00:28 -07:00
(1<<HumanoidModel::eAnim_DisableRenderHair) |
(1<<HumanoidModel::eAnim_DisableRenderSleeve1) |
(1<<HumanoidModel::eAnim_DisableRenderSleeve0) |
(1<<HumanoidModel::eAnim_DisableRenderPants1) |
(1<<HumanoidModel::eAnim_DisableRenderPants0) |
(1<<HumanoidModel::eAnim_DisableRenderJacket);
2026-03-01 12:16:08 +08:00
if((m_uiAnimOverrideBitmask & HumanoidModel::m_staticBitmaskIgnorePlayerCustomAnimSetting)!=0)
{
return m_uiAnimOverrideBitmask;
}
return 0;
}
return m_uiAnimOverrideBitmask;
}