4jcraft/Minecraft.World/Entities/Mobs/Arrow.cpp
MatthewBeshay a0fdc643d1 Merge branch 'upstream-dev' into cleanup/nullptr-replacement
# Conflicts:
#	Minecraft.Client/Network/PlayerChunkMap.cpp
#	Minecraft.Client/Network/PlayerList.cpp
#	Minecraft.Client/Network/ServerChunkCache.cpp
#	Minecraft.Client/Platform/Common/Consoles_App.cpp
#	Minecraft.Client/Platform/Common/DLC/DLCManager.cpp
#	Minecraft.Client/Platform/Common/GameRules/LevelGenerationOptions.cpp
#	Minecraft.Client/Platform/Common/GameRules/LevelRuleset.cpp
#	Minecraft.Client/Platform/Common/Tutorial/Tutorial.cpp
#	Minecraft.Client/Platform/Common/Tutorial/TutorialTask.cpp
#	Minecraft.Client/Platform/Common/UI/IUIScene_CreativeMenu.cpp
#	Minecraft.Client/Platform/Common/UI/UIComponent_Panorama.cpp
#	Minecraft.Client/Platform/Common/UI/UIController.cpp
#	Minecraft.Client/Platform/Common/UI/UIController.h
#	Minecraft.Client/Platform/Extrax64Stubs.cpp
#	Minecraft.Client/Platform/Windows64/4JLibs/inc/4J_Input.h
#	Minecraft.Client/Platform/Windows64/4JLibs/inc/4J_Storage.h
#	Minecraft.Client/Player/EntityTracker.cpp
#	Minecraft.Client/Player/ServerPlayer.cpp
#	Minecraft.Client/Rendering/EntityRenderers/PlayerRenderer.cpp
#	Minecraft.Client/Textures/Packs/DLCTexturePack.cpp
#	Minecraft.Client/Textures/Stitching/StitchedTexture.cpp
#	Minecraft.Client/Textures/Stitching/TextureMap.cpp
#	Minecraft.Client/Textures/Textures.cpp
#	Minecraft.World/Blocks/NotGateTile.cpp
#	Minecraft.World/Blocks/PressurePlateTile.cpp
#	Minecraft.World/Blocks/TileEntities/PotionBrewing.cpp
#	Minecraft.World/Enchantments/EnchantmentHelper.cpp
#	Minecraft.World/Entities/HangingEntity.cpp
#	Minecraft.World/Entities/LeashFenceKnotEntity.cpp
#	Minecraft.World/Entities/LivingEntity.cpp
#	Minecraft.World/Entities/Mobs/Boat.cpp
#	Minecraft.World/Entities/Mobs/Minecart.cpp
#	Minecraft.World/Entities/Mobs/Witch.cpp
#	Minecraft.World/Entities/SyncedEntityData.cpp
#	Minecraft.World/Items/LeashItem.cpp
#	Minecraft.World/Items/PotionItem.cpp
#	Minecraft.World/Level/BaseMobSpawner.cpp
#	Minecraft.World/Level/CustomLevelSource.cpp
#	Minecraft.World/Level/Level.cpp
#	Minecraft.World/Level/Storage/DirectoryLevelStorage.cpp
#	Minecraft.World/Level/Storage/McRegionLevelStorage.cpp
#	Minecraft.World/Level/Storage/RegionFileCache.cpp
#	Minecraft.World/Player/Player.cpp
#	Minecraft.World/WorldGen/Biomes/BiomeCache.cpp
#	Minecraft.World/WorldGen/Features/RandomScatteredLargeFeature.cpp
#	Minecraft.World/WorldGen/Layers/BiomeOverrideLayer.cpp
2026-03-30 16:28:40 +11:00

518 lines
16 KiB
C++

#include "../../Platform/stdafx.h"
#include "../../Headers/net.minecraft.world.level.h"
#include "../../Headers/net.minecraft.world.level.tile.h"
#include "../../Headers/net.minecraft.world.entity.h"
#include "../../Headers/net.minecraft.world.entity.player.h"
#include "../../Headers/net.minecraft.world.phys.h"
#include "../../Headers/net.minecraft.world.item.h"
#include "../../Headers/net.minecraft.world.damagesource.h"
#include "../../Headers/net.minecraft.world.item.enchantment.h"
#include "../../Headers/net.minecraft.network.packet.h"
#include "../../../Minecraft.Client/Player/ServerPlayer.h"
#include "../../../Minecraft.Client/Network/PlayerConnection.h"
#include "../../Headers/com.mojang.nbt.h"
#include "Arrow.h"
// 4J : WESTY : Added for other award, kill creeper with arrow.
#include "../../Headers/net.minecraft.world.entity.monster.h"
#include "../../Headers/net.minecraft.stats.h"
#include "../../Util/SoundTypes.h"
// base damage, multiplied with velocity
const double Arrow::ARROW_BASE_DAMAGE = 2.0f;
// 4J - added common ctor code.
void Arrow::_init() {
// 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();
xTile = -1;
yTile = -1;
zTile = -1;
lastTile = 0;
lastData = 0;
inGround = false;
pickup = PICKUP_DISALLOWED;
shakeTime = 0;
flightTime = 0;
owner = nullptr;
life = 0;
baseDamage = ARROW_BASE_DAMAGE;
knockback = 0;
}
Arrow::Arrow(Level* level) : Entity(level) {
_init();
viewScale = 10;
setSize(0.5f, 0.5f);
}
Arrow::Arrow(Level* level, std::shared_ptr<LivingEntity> mob,
std::shared_ptr<LivingEntity> target, float power,
float uncertainty)
: Entity(level) {
_init();
viewScale = 10;
owner = mob;
if (mob->instanceof(eTYPE_PLAYER)) pickup = PICKUP_ALLOWED;
y = mob->y + mob->getHeadHeight() - 0.1f;
double xd = target->x - mob->x;
double yd = (target->y + target->getHeadHeight() - 0.7f) - y;
double zd = target->z - mob->z;
double sd = sqrt(xd * xd + zd * zd);
if (sd < 0.0000001) return;
float yRot = (float)(atan2(zd, xd) * 180 / PI) - 90;
float xRot = (float)-(atan2(yd, sd) * 180 / PI);
double xdn = xd / sd;
double zdn = zd / sd;
moveTo(mob->x + xdn, y, mob->z + zdn, yRot, xRot);
heightOffset = 0;
float yo = (float)sd * 0.2f;
shoot(xd, yd + yo, zd, power, uncertainty);
}
Arrow::Arrow(Level* level, double x, double y, double z) : Entity(level) {
_init();
viewScale = 10;
setSize(0.5f, 0.5f);
setPos(x, y, z);
heightOffset = 0;
}
Arrow::Arrow(Level* level, std::shared_ptr<LivingEntity> mob, float power)
: Entity(level) {
_init();
viewScale = 10;
owner = mob;
if (mob->instanceof(eTYPE_PLAYER)) pickup = PICKUP_ALLOWED;
setSize(0.5f, 0.5f);
moveTo(mob->x, mob->y + mob->getHeadHeight(), mob->z, mob->yRot, mob->xRot);
x -= Mth::cos(yRot / 180 * PI) * 0.16f;
y -= 0.1f;
z -= Mth::sin(yRot / 180 * PI) * 0.16f;
setPos(x, y, z);
heightOffset = 0;
xd = -Mth::sin(yRot / 180 * PI) * Mth::cos(xRot / 180 * PI);
zd = Mth::cos(yRot / 180 * PI) * Mth::cos(xRot / 180 * PI);
yd = -Mth::sin(xRot / 180 * PI);
shoot(xd, yd, zd, power * 1.5f, 1);
}
void Arrow::defineSynchedData() { entityData->define(ID_FLAGS, (uint8_t)0); }
void Arrow::shoot(double xd, double yd, double zd, float pow,
float uncertainty) {
float dist = (float)sqrt(xd * xd + yd * yd + zd * zd);
xd /= dist;
yd /= dist;
zd /= dist;
xd += (random->nextGaussian() * (random->nextBoolean() ? -1 : 1)) *
0.0075f * uncertainty;
yd += (random->nextGaussian() * (random->nextBoolean() ? -1 : 1)) *
0.0075f * uncertainty;
zd += (random->nextGaussian() * (random->nextBoolean() ? -1 : 1)) *
0.0075f * uncertainty;
xd *= pow;
yd *= pow;
zd *= pow;
this->xd = xd;
this->yd = yd;
this->zd = zd;
double sd = sqrt(xd * xd + zd * zd);
yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI);
xRotO = xRot = (float)(atan2(yd, sd) * 180 / PI);
life = 0;
}
void Arrow::lerpTo(double x, double y, double z, float yRot, float xRot,
int steps) {
setPos(x, y, z);
setRot(yRot, xRot);
}
void Arrow::lerpMotion(double xd, double yd, double zd) {
this->xd = xd;
this->yd = yd;
this->zd = zd;
if (xRotO == 0 && yRotO == 0) {
double sd = sqrt(xd * xd + zd * zd);
yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI);
xRotO = xRot = (float)(atan2(yd, sd) * 180 / PI);
xRotO = xRot;
yRotO = yRot;
app.DebugPrintf("%f %f : 0x%x\n", xRot, yRot, &yRot);
moveTo(x, y, z, yRot, xRot);
life = 0;
}
}
void Arrow::tick() {
Entity::tick();
if (xRotO == 0 && yRotO == 0) {
double sd = sqrt(xd * xd + zd * zd);
yRotO = yRot = (float)(atan2(xd, zd) * 180 / PI);
xRotO = xRot = (float)(atan2(yd, sd) * 180 / PI);
}
{
int t = level->getTile(xTile, yTile, zTile);
if (t > 0) {
Tile::tiles[t]->updateShape(level, xTile, yTile, zTile);
auto aabb = Tile::tiles[t]->getAABB(level, xTile, yTile, zTile);
Vec3 pos{x, y, z};
if (aabb.has_value() && aabb->contains(pos)) {
inGround = true;
}
}
}
if (shakeTime > 0) shakeTime--;
if (inGround) {
int tile = level->getTile(xTile, yTile, zTile);
int data = level->getData(xTile, yTile, zTile);
if (tile != lastTile || data != lastData) {
inGround = false;
xd *= random->nextFloat() * 0.2f;
yd *= random->nextFloat() * 0.2f;
zd *= random->nextFloat() * 0.2f;
life = 0;
flightTime = 0;
return;
}
else {
life++;
if (life == 20 * 60) remove();
return;
}
}
else {
flightTime++;
}
Vec3 from{x, y, z};
Vec3 to{x + xd, y + yd, z + zd};
HitResult* res = level->clip(&from, &to, false, true);
from = Vec3{x, y, z};
to = Vec3{x + xd, y + yd, z + zd};
if (res != nullptr) {
to = Vec3{res->pos.x, res->pos.y, res->pos.z};
}
std::shared_ptr<Entity> hitEntity = nullptr;
AABB grown = bb.expand(xd, yd, zd).grow(1, 1, 1);
std::vector<std::shared_ptr<Entity> >* objects =
level->getEntities(shared_from_this(), &grown);
double nearest = 0;
auto itEnd = objects->end();
for (auto it = objects->begin(); it != itEnd; it++) {
std::shared_ptr<Entity> e = *it; // objects->at(i);
if (!e->isPickable() || (e == owner && flightTime < 5)) continue;
float rr = 0.3f;
AABB bb = e->bb.grow(rr, rr, rr);
HitResult* p = bb.clip(from, to);
if (p != nullptr) {
double dd = from.distanceTo(p->pos);
if (dd < nearest || nearest == 0) {
hitEntity = e;
nearest = dd;
}
delete p;
}
}
if (hitEntity != nullptr) {
delete res;
res = new HitResult(hitEntity);
}
if ((res != nullptr) && (res->entity != nullptr) &&
res->entity->instanceof(eTYPE_PLAYER)) {
std::shared_ptr<Player> player =
std::dynamic_pointer_cast<Player>(res->entity);
// 4J: Check for owner being null
if (player->abilities.invulnerable ||
((owner != nullptr) &&
(owner->instanceof(eTYPE_PLAYER) &&
!std::dynamic_pointer_cast<Player>(owner)->canHarmPlayer(
player)))) {
res = nullptr;
}
}
if (res != nullptr) {
if (res->entity != nullptr) {
float pow = Mth::sqrt(xd * xd + yd * yd + zd * zd);
int dmg = (int)Mth::ceil((float)(pow * baseDamage));
if (isCritArrow()) dmg += random->nextInt(dmg / 2 + 2);
DamageSource* damageSource = nullptr;
if (owner == nullptr) {
damageSource = DamageSource::arrow(
std::dynamic_pointer_cast<Arrow>(shared_from_this()),
shared_from_this());
} else {
damageSource = DamageSource::arrow(
std::dynamic_pointer_cast<Arrow>(shared_from_this()),
owner);
}
if (res->entity->hurt(damageSource, dmg)) {
// Firx for #67839 - Customer Encountered: Bows enchanted with
// "Flame" still set things on fire if pvp/attack animals is
// turned off 4J Stu - We should not set the entity on fire
// unless we can cause some damage (this doesn't necessarily
// mean that the arrow hit lowered their health) set targets on
// fire first because we want cooked pork/chicken/steak
if (isOnFire() && res->entity->GetType() != eTYPE_ENDERMAN) {
res->entity->setOnFire(5);
}
if (res->entity->instanceof(eTYPE_LIVINGENTITY)) {
std::shared_ptr<LivingEntity> mob =
std::dynamic_pointer_cast<LivingEntity>(res->entity);
if (!level->isClientSide) {
mob->setArrowCount(mob->getArrowCount() + 1);
}
if (knockback > 0) {
float pushLen = sqrt(xd * xd + zd * zd);
if (pushLen > 0) {
res->entity->push(xd * knockback * .6f / pushLen,
0.1,
zd * knockback * .6f / pushLen);
}
}
if (owner != nullptr) {
ThornsEnchantment::doThornsAfterAttack(owner, mob,
random);
}
if (owner != nullptr && res->entity != owner &&
owner->GetType() == eTYPE_SERVERPLAYER) {
std::dynamic_pointer_cast<ServerPlayer>(owner)
->connection->send(std::shared_ptr<GameEventPacket>(
new GameEventPacket(
GameEventPacket::SUCCESSFUL_BOW_HIT, 0)));
}
}
// 4J : WESTY : For award, need to track if creeper was killed
// by arrow from the player.
if (owner != nullptr &&
owner->instanceof(eTYPE_PLAYER) // arrow owner is a player
&& !res->entity->isAlive() // target is now dead
&& (res->entity->GetType() ==
eTYPE_CREEPER)) // target is a creeper
{
std::dynamic_pointer_cast<Player>(owner)->awardStat(
GenericStats::arrowKillCreeper(),
GenericStats::param_arrowKillCreeper());
}
playSound(eSoundType_RANDOM_BOW_HIT, 1.0f,
1.2f / (random->nextFloat() * 0.2f + 0.9f));
if (res->entity->GetType() != eTYPE_ENDERDRAGON) remove();
} else {
xd *= -0.1f;
yd *= -0.1f;
zd *= -0.1f;
yRot += 180;
yRotO += 180;
flightTime = 0;
}
delete damageSource;
} else {
xTile = res->x;
yTile = res->y;
zTile = res->z;
lastTile = level->getTile(xTile, yTile, zTile);
lastData = level->getData(xTile, yTile, zTile);
xd = (float)(res->pos.x - x);
yd = (float)(res->pos.y - y);
zd = (float)(res->pos.z - z);
float dd = (float)sqrt(xd * xd + yd * yd + zd * zd);
// 4J added check - zero dd here was creating NaNs
if (dd > 0.0001f) {
x -= (xd / dd) * 0.05f;
y -= (yd / dd) * 0.05f;
z -= (zd / dd) * 0.05f;
}
playSound(eSoundType_RANDOM_BOW_HIT, 1.0f,
1.2f / (random->nextFloat() * 0.2f + 0.9f));
inGround = true;
shakeTime = 7;
setCritArrow(false);
if (lastTile != 0) {
Tile::tiles[lastTile]->entityInside(level, xTile, yTile, zTile,
shared_from_this());
}
}
}
delete res;
if (isCritArrow()) {
for (int i = 0; i < 4; i++) {
level->addParticle(eParticleType_crit, x + xd * i / 4.0f,
y + yd * i / 4.0f, z + zd * i / 4.0f, -xd,
-yd + 0.2, -zd);
}
}
x += xd;
y += yd;
z += zd;
double sd = sqrt(xd * xd + zd * zd);
yRot = (float)(atan2(xd, zd) * 180 / PI);
xRot = (float)(atan2(yd, sd) * 180 / PI);
while (xRot - xRotO < -180) xRotO -= 360;
while (xRot - xRotO >= 180) xRotO += 360;
while (yRot - yRotO < -180) yRotO -= 360;
while (yRot - yRotO >= 180) yRotO += 360;
xRot = xRotO + (xRot - xRotO) * 0.2f;
yRot = yRotO + (yRot - yRotO) * 0.2f;
float inertia = 0.99f;
float gravity = 0.05f;
if (isInWater()) {
for (int i = 0; i < 4; i++) {
float s = 1 / 4.0f;
level->addParticle(eParticleType_bubble, x - xd * s, y - yd * s,
z - zd * s, xd, yd, zd);
}
inertia = 0.80f;
}
xd *= inertia;
yd *= inertia;
zd *= inertia;
yd -= gravity;
setPos(x, y, z);
checkInsideTiles();
}
void Arrow::addAdditonalSaveData(CompoundTag* tag) {
tag->putShort(L"xTile", (short)xTile);
tag->putShort(L"yTile", (short)yTile);
tag->putShort(L"zTile", (short)zTile);
tag->putByte(L"inTile", (uint8_t)lastTile);
tag->putByte(L"inData", (uint8_t)lastData);
tag->putByte(L"shake", (uint8_t)shakeTime);
tag->putByte(L"inGround", (uint8_t)(inGround ? 1 : 0));
tag->putByte(L"pickup", (uint8_t)pickup);
tag->putDouble(L"damage", baseDamage);
}
void Arrow::readAdditionalSaveData(CompoundTag* tag) {
xTile = tag->getShort(L"xTile");
yTile = tag->getShort(L"yTile");
zTile = tag->getShort(L"zTile");
lastTile = tag->getByte(L"inTile") & 0xff;
lastData = tag->getByte(L"inData") & 0xff;
shakeTime = tag->getByte(L"shake") & 0xff;
inGround = tag->getByte(L"inGround") == 1;
if (tag->contains(L"damage")) {
baseDamage = tag->getDouble(L"damage");
}
if (tag->contains(L"pickup")) {
pickup = tag->getByte(L"pickup");
} else if (tag->contains(L"player")) {
pickup =
tag->getBoolean(L"player") ? PICKUP_ALLOWED : PICKUP_DISALLOWED;
}
}
void Arrow::playerTouch(std::shared_ptr<Player> player) {
if (level->isClientSide || !inGround || shakeTime > 0) return;
bool bRemove =
pickup == PICKUP_ALLOWED ||
(pickup == PICKUP_CREATIVE_ONLY && player->abilities.instabuild);
if (pickup == PICKUP_ALLOWED) {
if (!player->inventory->add(std::shared_ptr<ItemInstance>(
new ItemInstance(Item::arrow, 1)))) {
bRemove = false;
}
}
if (bRemove) {
playSound(
eSoundType_RANDOM_POP, 0.2f,
((random->nextFloat() - random->nextFloat()) * 0.7f + 1.0f) * 2.0f);
player->take(shared_from_this(), 1);
remove();
}
}
bool Arrow::makeStepSound() { return false; }
float Arrow::getShadowHeightOffs() { return 0; }
void Arrow::setBaseDamage(double baseDamage) { this->baseDamage = baseDamage; }
double Arrow::getBaseDamage() { return baseDamage; }
void Arrow::setKnockback(int knockback) { this->knockback = knockback; }
bool Arrow::isAttackable() { return false; }
void Arrow::setCritArrow(bool critArrow) {
uint8_t flags = entityData->getByte(ID_FLAGS);
if (critArrow) {
entityData->set(ID_FLAGS, (uint8_t)(flags | FLAG_CRIT));
} else {
entityData->set(ID_FLAGS, (uint8_t)(flags & ~FLAG_CRIT));
}
}
bool Arrow::isCritArrow() {
uint8_t flags = entityData->getByte(ID_FLAGS);
return (flags & FLAG_CRIT) != 0;
}