2026-03-01 12:16:08 +08:00
# include "stdafx.h"
# include "com.mojang.nbt.h"
# include "net.minecraft.world.entity.h"
# include "net.minecraft.world.entity.player.h"
# include "net.minecraft.world.item.h"
# include "net.minecraft.world.level.h"
# include "net.minecraft.world.level.material.h"
# include "net.minecraft.world.level.tile.h"
# include "net.minecraft.world.phys.h"
# include "net.minecraft.world.damagesource.h"
# include "Boat.h"
const double Boat : : MAX_SPEED = 0.35 ;
const double Boat : : MAX_COLLISION_SPEED = MAX_SPEED * 0.75 ;
const double Boat : : MIN_ACCELERATION = 0.07 ;
const double Boat : : MAX_ACCELERATION = 0.35 ;
// 4J - added for common ctor code
void Boat : : _init ( )
{
doLerp = true ;
acceleration = MIN_ACCELERATION ;
lSteps = 0 ;
lx = ly = lz = lyr = lxr = 0.0 ;
lxd = lyd = lzd = 0.0 ;
blocksBuilding = true ;
setSize ( 1.5f , 0.6f ) ;
heightOffset = bbHeight / 2.0f ;
// 4J Stu - This function call had to be moved here from the Entity ctor to ensure that
// the derived version of the function is called
this - > defineSynchedData ( ) ;
}
Boat : : Boat ( Level * level ) : Entity ( level )
{
_init ( ) ;
}
bool Boat : : makeStepSound ( )
{
return false ;
}
void Boat : : defineSynchedData ( )
{
entityData - > define ( DATA_ID_HURT , 0 ) ;
entityData - > define ( DATA_ID_HURTDIR , 1 ) ;
entityData - > define ( DATA_ID_DAMAGE , 0 ) ;
}
2026-03-02 17:37:16 +07:00
AABB * Boat : : getCollideAgainstBox ( shared_ptr < Entity > entity )
2026-03-01 12:16:08 +08:00
{
return entity - > bb ;
}
AABB * Boat : : getCollideBox ( )
{
return bb ;
}
bool Boat : : isPushable ( )
{
return true ;
}
Boat : : Boat ( Level * level , double x , double y , double z ) : Entity ( level )
{
_init ( ) ;
setPos ( x , y + heightOffset , z ) ;
xd = 0 ;
yd = 0 ;
zd = 0 ;
xo = x ;
yo = y ;
zo = z ;
}
double Boat : : getRideHeight ( )
{
return bbHeight * 0.0f - 0.3f ;
}
bool Boat : : hurt ( DamageSource * source , int hurtDamage )
{
if ( level - > isClientSide | | removed ) return true ;
// 4J-JEV: Fix for #88212,
// Untrusted players shouldn't be able to damage minecarts or boats.
if ( dynamic_cast < EntityDamageSource * > ( source ) ! = NULL )
{
2026-03-02 17:37:16 +07:00
shared_ptr < Entity > attacker = source - > getDirectEntity ( ) ;
2026-03-01 12:16:08 +08:00
2026-03-02 17:37:16 +07:00
if ( dynamic_pointer_cast < Player > ( attacker ) ! = NULL & &
2026-03-02 17:36:56 +07:00
! dynamic_pointer_cast < Player > ( attacker ) - > isAllowedToHurtEntity ( shared_from_this ( ) ) )
2026-03-01 12:16:08 +08:00
return false ;
}
setHurtDir ( - getHurtDir ( ) ) ;
setHurtTime ( 10 ) ;
// 4J Stu - If someone is riding in this, then it can tick multiple times which causes the damage to
// decrease too quickly. So just make the damage a bit higher to start with for similar behaviour
// to an unridden one. Only do this change if the riding player is attacking it.
if ( rider . lock ( ) ! = NULL & & rider . lock ( ) = = source - > getEntity ( ) ) hurtDamage + = 1 ;
setDamage ( getDamage ( ) + hurtDamage * 10 ) ;
markHurt ( ) ;
// 4J Stu - Brought froward from 12w36 to fix #46611 - TU5: Gameplay: Minecarts and boat requires more hits than one to be destroyed in creative mode
2026-03-02 17:37:16 +07:00
shared_ptr < Player > player = dynamic_pointer_cast < Player > ( source - > getEntity ( ) ) ;
2026-03-01 12:16:08 +08:00
if ( player ! = NULL & & player - > abilities . instabuild ) setDamage ( 100 ) ;
if ( getDamage ( ) > 20 * 2 )
{
if ( rider . lock ( ) ! = NULL ) rider . lock ( ) - > ride ( shared_from_this ( ) ) ;
spawnAtLocation ( Item : : boat_Id , 1 , 0 ) ;
remove ( ) ;
}
return true ;
}
void Boat : : animateHurt ( )
{
setHurtDir ( - getHurtDir ( ) ) ;
setHurtTime ( 10 ) ;
setDamage ( getDamage ( ) * 11 ) ;
}
bool Boat : : isPickable ( )
{
return ! removed ;
}
void Boat : : lerpTo ( double x , double y , double z , float yRot , float xRot , int steps )
{
if ( doLerp )
{
lSteps = steps + 5 ;
}
else
{
double xdiff = x - this - > x ;
double ydiff = y - this - > y ;
double zdiff = z - this - > z ;
double diff = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff ;
if ( diff > 1 )
{
lSteps = 3 ;
}
else
{
return ;
}
}
lx = x ;
ly = y ;
lz = z ;
lyr = yRot ;
lxr = xRot ;
this - > xd = lxd ;
this - > yd = lyd ;
this - > zd = lzd ;
}
void Boat : : lerpMotion ( double xd , double yd , double zd )
{
lxd = this - > xd = xd ;
lyd = this - > yd = yd ;
lzd = this - > zd = zd ;
}
void Boat : : tick ( )
{
Entity : : tick ( ) ;
if ( getHurtTime ( ) > 0 ) setHurtTime ( getHurtTime ( ) - 1 ) ;
if ( getDamage ( ) > 0 ) setDamage ( getDamage ( ) - 1 ) ;
xo = x ;
yo = y ;
zo = z ;
int steps = 5 ;
double waterPercentage = 0 ;
for ( int i = 0 ; i < steps ; i + + )
{
double y0 = bb - > y0 + ( bb - > y1 - bb - > y0 ) * ( i + 0 ) / steps - 2 / 16.0f ;
double y1 = bb - > y0 + ( bb - > y1 - bb - > y0 ) * ( i + 1 ) / steps - 2 / 16.0f ;
AABB * bb2 = AABB : : newTemp ( bb - > x0 , y0 , bb - > z0 , bb - > x1 , y1 , bb - > z1 ) ;
if ( level - > containsLiquid ( bb2 , Material : : water ) )
{
waterPercentage + = 1.0 / steps ;
}
}
double lastSpeed = sqrt ( xd * xd + zd * zd ) ;
if ( lastSpeed > MAX_COLLISION_SPEED )
{
double xa = cos ( yRot * PI / 180 ) ;
double za = sin ( yRot * PI / 180 ) ;
for ( int i = 0 ; i < 1 + lastSpeed * 60 ; i + + )
{
double side = ( random - > nextFloat ( ) * 2 - 1 ) ;
double side2 = ( random - > nextInt ( 2 ) * 2 - 1 ) * 0.7 ;
if ( random - > nextBoolean ( ) )
{
double xx = x - xa * side * 0.8 + za * side2 ;
double zz = z - za * side * 0.8 - xa * side2 ;
level - > addParticle ( eParticleType_splash , xx , y - 2 / 16.0f , zz , + xd , yd , + zd ) ;
}
else
{
double xx = x + xa + za * side * 0.7 ;
double zz = z + za - xa * side * 0.7 ;
level - > addParticle ( eParticleType_splash , xx , y - 2 / 16.0f , zz , + xd , yd , + zd ) ;
}
}
}
if ( level - > isClientSide & & doLerp )
{
if ( lSteps > 0 )
{
double xt = x + ( lx - x ) / lSteps ;
double yt = y + ( ly - y ) / lSteps ;
double zt = z + ( lz - z ) / lSteps ;
double yrd = Mth : : wrapDegrees ( lyr - yRot ) ;
yRot + = ( float ) ( ( yrd ) / lSteps ) ;
xRot + = ( float ) ( ( lxr - xRot ) / lSteps ) ;
lSteps - - ;
this - > setPos ( xt , yt , zt ) ;
this - > setRot ( yRot , xRot ) ;
}
else
{
# if 1
// Original
//double xt = x + xd;
//double yt = y + yd;
//double zt = z + zd;
//this->setPos(xt, yt, zt);
// 4J Stu - Fix for various boat bugs, ensure that we check collision on client-side movement
this - > move ( xd , yd , zd ) ;
if ( onGround )
{
xd * = 0.5f ;
yd * = 0.5f ;
zd * = 0.5f ;
}
xd * = 0.99f ;
yd * = 0.95f ;
zd * = 0.99f ;
# else
// 4J Stu - Fix for #8280 - Gameplay : Boats behave erratically when exited next to land.
// The client shouldn't change the position of the boat
double xt = x ; // + xd;
double yt = y + yd ;
double zt = z ; // + zd;
this - > setPos ( xt , yt , zt ) ;
// 4J Stu - Fix for #9579 - GAMEPLAY: Boats with a player in them slowly sink under the water over time, and with no player in them they float into the sky.
// Just make the boats bob up and down rather than any other client-side movement when not receiving packets from server
if ( waterPercentage < 1 )
{
double bob = waterPercentage * 2 - 1 ;
yd + = 0.04f * bob ;
}
else
{
if ( yd < 0 ) yd / = 2 ;
yd + = 0.007f ;
}
//if (onGround)
//{
xd * = 0.5f ;
yd * = 0.5f ;
zd * = 0.5f ;
//}
//xd *= 0.99f;
//yd *= 0.95f;
//zd *= 0.99f;
# endif
}
return ;
}
if ( waterPercentage < 1 )
{
double bob = waterPercentage * 2 - 1 ;
yd + = 0.04f * bob ;
}
else
{
if ( yd < 0 ) yd / = 2 ;
yd + = 0.007f ;
}
if ( rider . lock ( ) ! = NULL )
{
xd + = rider . lock ( ) - > xd * acceleration ;
zd + = rider . lock ( ) - > zd * acceleration ;
}
double curSpeed = sqrt ( xd * xd + zd * zd ) ;
if ( curSpeed > MAX_SPEED )
{
double ratio = MAX_SPEED / curSpeed ;
xd * = ratio ;
zd * = ratio ;
curSpeed = MAX_SPEED ;
}
if ( curSpeed > lastSpeed & & acceleration < MAX_ACCELERATION )
{
acceleration + = ( MAX_ACCELERATION - acceleration ) / 35 ;
if ( acceleration > MAX_ACCELERATION ) acceleration = MAX_ACCELERATION ;
}
else
{
acceleration - = ( acceleration - MIN_ACCELERATION ) / 35 ;
if ( acceleration < MIN_ACCELERATION ) acceleration = MIN_ACCELERATION ;
}
if ( onGround )
{
xd * = 0.5f ;
yd * = 0.5f ;
zd * = 0.5f ;
}
move ( xd , yd , zd ) ;
if ( ( horizontalCollision & & lastSpeed > 0.20 ) )
{
if ( ! level - > isClientSide )
{
remove ( ) ;
for ( int i = 0 ; i < 3 ; i + + )
{
spawnAtLocation ( Tile : : wood_Id , 1 , 0 ) ;
}
for ( int i = 0 ; i < 2 ; i + + )
{
spawnAtLocation ( Item : : stick - > id , 1 , 0 ) ;
}
}
}
else
{
xd * = 0.99f ;
yd * = 0.95f ;
zd * = 0.99f ;
}
xRot = 0 ;
double yRotT = yRot ;
double xDiff = xo - x ;
double zDiff = zo - z ;
if ( xDiff * xDiff + zDiff * zDiff > 0.001 )
{
yRotT = ( float ) ( atan2 ( zDiff , xDiff ) * 180 / PI ) ;
}
double rotDiff = Mth : : wrapDegrees ( yRotT - yRot ) ;
if ( rotDiff > 20 ) rotDiff = 20 ;
if ( rotDiff < - 20 ) rotDiff = - 20 ;
yRot + = ( float ) rotDiff ;
setRot ( yRot , xRot ) ;
if ( level - > isClientSide ) return ;
2026-03-02 17:37:16 +07:00
vector < shared_ptr < Entity > > * entities = level - > getEntities ( shared_from_this ( ) , this - > bb - > grow ( 0.2f , 0 , 0.2f ) ) ;
2026-03-01 12:16:08 +08:00
if ( entities ! = NULL & & ! entities - > empty ( ) )
{
AUTO_VAR ( itEnd , entities - > end ( ) ) ;
for ( AUTO_VAR ( it , entities - > begin ( ) ) ; it ! = itEnd ; it + + )
{
2026-03-02 17:37:16 +07:00
shared_ptr < Entity > e = ( * it ) ; // entities->at(i);
2026-03-01 12:16:08 +08:00
if ( e ! = rider . lock ( ) & & e - > isPushable ( ) & & e - > GetType ( ) = = eTYPE_BOAT )
{
e - > push ( shared_from_this ( ) ) ;
}
}
}
for ( int i = 0 ; i < 4 ; i + + )
{
int xx = Mth : : floor ( x + ( ( i % 2 ) - 0.5 ) * 0.8 ) ;
int zz = Mth : : floor ( z + ( ( i / 2 ) - 0.5 ) * 0.8 ) ;
for ( int j = 0 ; j < 2 ; j + + )
{
int yy = Mth : : floor ( y ) + j ;
int tile = level - > getTile ( xx , yy , zz ) ;
int data = level - > getData ( xx , yy , zz ) ;
if ( tile = = Tile : : topSnow_Id )
{
level - > setTile ( xx , yy , zz , 0 ) ;
}
else if ( tile = = Tile : : waterLily_Id )
{
Tile : : waterLily - > spawnResources ( level , xx , yy , zz , data , 0.3f , 0 ) ;
level - > setTile ( xx , yy , zz , 0 ) ;
}
}
}
if ( rider . lock ( ) ! = NULL )
{
if ( rider . lock ( ) - > removed ) rider = weak_ptr < Entity > ( ) ;
}
}
void Boat : : positionRider ( )
{
if ( rider . lock ( ) = = NULL ) return ;
double xa = cos ( yRot * PI / 180 ) * 0.4 ;
double za = sin ( yRot * PI / 180 ) * 0.4 ;
rider . lock ( ) - > setPos ( x + xa , y + getRideHeight ( ) + rider . lock ( ) - > getRidingHeight ( ) , z + za ) ;
}
void Boat : : addAdditonalSaveData ( CompoundTag * base )
{
}
void Boat : : readAdditionalSaveData ( CompoundTag * base )
{
}
float Boat : : getShadowHeightOffs ( )
{
return 0 ;
}
wstring Boat : : getName ( )
{
return L " Boat " ;
}
2026-03-02 17:37:16 +07:00
bool Boat : : interact ( shared_ptr < Player > player )
2026-03-01 12:16:08 +08:00
{
2026-03-02 17:36:56 +07:00
if ( rider . lock ( ) ! = NULL & & dynamic_pointer_cast < Player > ( rider . lock ( ) ) ! = NULL & & rider . lock ( ) ! = player ) return true ;
2026-03-01 12:16:08 +08:00
if ( ! level - > isClientSide )
{
// 4J HEG - Fixed issue with player not being able to dismount boat (issue #4446)
player - > ride ( rider . lock ( ) = = player ? nullptr : shared_from_this ( ) ) ;
}
return true ;
}
void Boat : : setDamage ( int damage )
{
entityData - > set ( DATA_ID_DAMAGE , damage ) ;
}
int Boat : : getDamage ( )
{
return entityData - > getInteger ( DATA_ID_DAMAGE ) ;
}
void Boat : : setHurtTime ( int hurtTime )
{
entityData - > set ( DATA_ID_HURT , hurtTime ) ;
}
int Boat : : getHurtTime ( )
{
return entityData - > getInteger ( DATA_ID_HURT ) ;
}
void Boat : : setHurtDir ( int hurtDir )
{
entityData - > set ( DATA_ID_HURTDIR , hurtDir ) ;
}
int Boat : : getHurtDir ( )
{
return entityData - > getInteger ( DATA_ID_HURTDIR ) ;
}
bool Boat : : getDoLerp ( )
{
return doLerp ;
}
void Boat : : setDoLerp ( bool doLerp )
{
this - > doLerp = doLerp ;
}