fix: enchantment table + frost walker (kinda)

fixes:
- treasure enchants being selectable within the enchantment table menu
- frost walker resemblance to decompiled legacy console edition
- enchantment table featuring hot-swappable abilities where the player could replace one tool with another whilst keeping the exact same potential enchantments in the enchant menu
This commit is contained in:
Fireblade
2026-07-18 22:03:54 -04:00
parent f5afc92cdd
commit 525b89484c
17 changed files with 144 additions and 42 deletions
+1 -1
View File
@@ -94,7 +94,7 @@ void Enchantment::staticCtor()
for(unsigned int i = 0; i < 256; ++i)
{
Enchantment *enchantment = enchantments[i];
if (enchantment != nullptr && !enchantment->isTreasureEnchantment())
if (enchantment != nullptr && !enchantment->isTreasureOnly())
{
validEnchantments.push_back(enchantment);
}
+1 -1
View File
@@ -85,9 +85,9 @@ public:
virtual int getDamageProtection(int level, DamageSource *source);
virtual float getDamageBonus(int level, shared_ptr<LivingEntity> target);
virtual bool isCompatibleWith(Enchantment *other) const;
virtual bool isTreasureEnchantment() const { return false; }
virtual Enchantment *setDescriptionId(int id);
virtual int getDescriptionId();
virtual bool isTreasureOnly() { return false; }
virtual HtmlString getFullname(int level);
virtual bool canEnchant(shared_ptr<ItemInstance> item);
// 4J Added
+5
View File
@@ -481,6 +481,11 @@ unordered_map<int, EnchantmentInstance *> *EnchantmentHelper::getAvailableEnchan
continue;
}
if (e->isTreasureOnly())
{
continue;
}
// Only picks "normal" enchantments, no specialcases
if (!e->category->canEnchant(item) && !isBook)
{
+2
View File
@@ -5,6 +5,7 @@ class Inventory;
class DamageSource;
class Enchantment;
class EnchantmentInstance;
class LivingEntity;
class EnchantmentHelper
{
@@ -50,6 +51,7 @@ private:
*/
public:
static int getDamageProtection(ItemInstanceArray armor, DamageSource *source);
static bool hasFrostWalker(shared_ptr<LivingEntity> living);
private:
class GetDamageBonusIteration : public EnchantmentIterationMethod
+30 -1
View File
@@ -98,9 +98,36 @@ vector<EnchantmentInstance*> EnchantmentMenu::getEnchantment() {
void EnchantmentMenu::slotsChanged(int a) // 4J used to take a shared_ptr<Container> container but wasn't using it, so removed to simplify things
{
shared_ptr<ItemInstance> item = enchantSlots->getItem(0);
shared_ptr<ItemInstance> lapis = lapisSlot->getItem(1);
bool itemChanged = false;
if (item == nullptr || lastEnchantmentItem == nullptr)
{
itemChanged = (item == nullptr) != (lastEnchantmentItem == nullptr);
}
else if (item->id != lastEnchantmentItem->id || item->getDamageValue() != lastEnchantmentItem->getDamageValue())
{
itemChanged = true;
}
if (itemChanged)
{
alreadyRan = false;
for (int i = 0; i < 3; ++i)
{
if (cachedEnchantments[i] != nullptr)
{
for (EnchantmentInstance *cached : *cachedEnchantments[i])
{
delete cached;
}
cachedEnchantments[i]->clear();
delete cachedEnchantments[i];
cachedEnchantments[i] = nullptr;
}
}
}
if (item == nullptr || !item->isEnchantable())
{
if (!level->isClientSide)
@@ -220,6 +247,7 @@ void EnchantmentMenu::slotsChanged(int a) // 4J used to take a shared_ptr<Contai
}
wasLapis = lapis != nullptr;
lastEnchantmentItem = item;
}
bool EnchantmentMenu::clickMenuButton(shared_ptr<Player> player, int i)
@@ -257,6 +285,7 @@ bool EnchantmentMenu::clickMenuButton(shared_ptr<Player> player, int i)
delete e;
}
delete newEnchantment;
cachedEnchantments[i] = nullptr;
slotsChanged(1);// Removed enchantSlots parameter as the function can reference it directly
}
}
+1
View File
@@ -17,6 +17,7 @@ public:
public:
shared_ptr<Container> enchantSlots;
shared_ptr<Container> lapisSlot;
shared_ptr<ItemInstance> lastEnchantmentItem;
bool alreadyRan = false;
bool en = false;
// Header
+45 -24
View File
@@ -2,9 +2,12 @@
#include "net.minecraft.world.level.h"
#include "net.minecraft.world.level.tile.h"
#include "net.minecraft.world.entity.h"
#include "net.minecraft.world.entity.player.h"
#include "Material.h"
#include "Mth.h"
#include "BlockPos.h"
#include "FrostWalkerEnchantment.h"
#include "EnchantmentHelper.h"
FrostWalkerEnchantment::FrostWalkerEnchantment(int id, int frequency) : Enchantment(id, frequency, EnchantmentCategory::armor_feet)
{
@@ -26,38 +29,56 @@ int FrostWalkerEnchantment::getMaxLevel()
return 2;
}
void FrostWalkerEnchantment::freezeNearby(shared_ptr<LivingEntity> living, Level *level, int px, int py, int pz, int enchLevel)
bool FrostWalkerEnchantment::isCompatibleWith(Enchantment *other) const
{
if (!Enchantment::isCompatibleWith(other)) return false;
return other != Enchantment::waterWalker;
}
bool EnchantmentHelper::hasFrostWalker(shared_ptr<LivingEntity> living)
{
return getFrostWalker(living) != 0;
}
void FrostWalkerEnchantment::onEntityMoved(shared_ptr<LivingEntity> living, Level *level, BlockPos pos, int enchLevel)
{
if (!living->onGround) return;
int radius = 2 + enchLevel;
Player *player = dynamic_cast<Player *>(living.get());
if (player != nullptr && player->abilities.flying) return;
int radius = enchLevel + 2;
if (radius > 16) radius = 16;
float f = (float)radius;
int r = radius;
float f = static_cast<float>(radius);
for (int dx = -r; dx <= r; dx++)
BlockPos minPos = pos.offset(-radius, -1, -radius);
BlockPos maxPos = pos.offset(radius, -1, radius);
for (int x = minPos.getX(); x <= maxPos.getX(); ++x)
{
for (int dz = -r; dz <= r; dz++)
for (int y = minPos.getY(); y <= maxPos.getY(); ++y)
{
int bx = px + dx;
int by = py - 1;
int bz = pz + dz;
double ddx = (bx + 0.5) - living->x;
double ddy = (by + 0.5) - living->y;
double ddz = (bz + 0.5) - living->z;
if (ddx * ddx + ddy * ddy + ddz * ddz > (double)(f * f)) continue;
if (level->getTile(bx, by + 1, bz) != 0) continue;
Material *ground = level->getMaterial(bx, by, bz);
if (ground != Material::water) continue;
if (level->getData(bx, by, bz) != 0) continue;
if (Tile::frosted_ice->mayPlace(level, bx, by, bz))
for (int z = minPos.getZ(); z <= maxPos.getZ(); ++z)
{
level->setTileAndData(bx, by, bz, Tile::frosted_ice_Id, 0, Tile::UPDATE_ALL);
double dx = (x + 0.5) - living->x;
double dy = (y + 0.5) - living->y;
double dz = (z + 0.5) - living->z;
if (dx * dx + dy * dy + dz * dz > static_cast<double>(f * f)) continue;
if (level->getTile(x, y + 1, z) != 0) continue;
Material *ground = level->getMaterial(x, y, z);
if (ground != Material::water) continue;
if (level->getData(x, y, z) != 0) continue;
bool canPlace = level->mayPlace(Tile::frosted_ice_Id, x, y, z, false, 0, nullptr, nullptr);
if (!canPlace) continue;
level->setTileAndData(x, y, z, Tile::frosted_ice_Id, 0, Tile::UPDATE_ALL);
int ticks = Mth::nextInt(living->getRandom(), 60, 120);
level->addToTickNextTick(x, y, z, Tile::frosted_ice_Id, ticks);
}
}
}
}
}
+5 -2
View File
@@ -4,6 +4,7 @@
class Level;
class LivingEntity;
class BlockPos;
class FrostWalkerEnchantment : public Enchantment
{
@@ -13,7 +14,9 @@ public:
virtual int getMinCost(int level) override;
virtual int getMaxCost(int level) override;
virtual int getMaxLevel() override;
virtual bool isTreasureEnchantment() const override { return true; }
virtual bool isCompatibleWith(Enchantment *other) const override;
virtual bool isTreasureOnly() override { return true; }
static void freezeNearby(shared_ptr<LivingEntity> living, Level *level, int px, int py, int pz, int enchLevel);
static void freezeNearby(shared_ptr<LivingEntity> living, Level *level, int x, int y, int z, int enchLevel);
static void onEntityMoved(shared_ptr<LivingEntity> living, Level *level, BlockPos pos, int enchLevel);
};
+18 -11
View File
@@ -33,6 +33,8 @@
#include "Dimension.h"
#include "GenericStats.h"
#include "ItemEntity.h"
#include "TilePos.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "../Minecraft.Server/FourKitBridge.h"
#endif
@@ -296,17 +298,7 @@ void LivingEntity::baseTick()
tickEffects();
animStepO = animStep;
if (!level->isClientSide && isAlive())
{
int frostWalkerLevel = EnchantmentHelper::getFrostWalker(dynamic_pointer_cast<LivingEntity>(shared_from_this()));
if (frostWalkerLevel > 0)
{
FrostWalkerEnchantment::freezeNearby(dynamic_pointer_cast<LivingEntity>(shared_from_this()), level,
Mth::floor(x), Mth::floor(y), Mth::floor(z), frostWalkerLevel);
}
}
yBodyRotO = yBodyRot;
yHeadRotO = yHeadRot;
yRotO = yRot;
@@ -1075,6 +1067,21 @@ int LivingEntity::getDeathSound()
return eSoundType_DAMAGE_HURT;
}
// frost walker-specific
void LivingEntity::onChangedBlock(BlockPos pos)
{
shared_ptr<LivingEntity> self = dynamic_pointer_cast<LivingEntity>(shared_from_this());
int frostWalkerLevel = EnchantmentHelper::getEnchantmentLevel(Enchantment::frostWalker->id, getEquipmentSlots());
if (frostWalkerLevel < 1)
{
return;
}
FrostWalkerEnchantment::onEntityMoved(self, level, pos, frostWalkerLevel);
}
/**
* Drop extra rare loot. Only occurs roughly 5% of the time, rareRootLevel
* is set to 1 (otherwise 0) 1% of the time.
+1
View File
@@ -203,6 +203,7 @@ public:
virtual bool onLadder();
virtual bool isShootable();
virtual bool isAlive();
virtual void onChangedBlock(BlockPos pos);
virtual void causeFallDamage(float distance);
virtual void animateHurt();
virtual int getArmorValue();
+1 -1
View File
@@ -26,4 +26,4 @@ int MendingEnchantment::getMaxLevel()
bool MendingEnchantment::canEnchant(shared_ptr<ItemInstance> item)
{
return item->isDamageableItem();
}
}
+1 -1
View File
@@ -10,6 +10,6 @@ public:
virtual int getMinCost(int level) override;
virtual int getMaxCost(int level) override;
virtual int getMaxLevel() override;
virtual bool isTreasureEnchantment() const override { return true; }
virtual bool isTreasureOnly() override { return true; };
virtual bool canEnchant(shared_ptr<ItemInstance> item) override;
};
+16
View File
@@ -145,6 +145,7 @@ Player::Player(Level *level, const wstring &name) : LivingEntity( level )
Pos *spawnPos = level->getSharedSpawnPos();
moveTo(spawnPos->x + 0.5, spawnPos->y + 1, spawnPos->z + 0.5, 0, 0);
delete spawnPos;
lastChangedBlockPos = BlockPos(x, y, z);
rotOffs = 180;
flameTime = 20;
@@ -330,6 +331,13 @@ void Player::tick()
LivingEntity::tick();
BlockPos currentBlockPos(Mth::floor(x), Mth::floor(y), Mth::floor(z));
if (currentBlockPos != lastChangedBlockPos)
{
lastChangedBlockPos = currentBlockPos;
onChangedBlock(currentBlockPos);
}
if (!level->isClientSide)
{
if (containerMenu != nullptr && !containerMenu->stillValid( dynamic_pointer_cast<Player>( shared_from_this() ) ))
@@ -624,6 +632,14 @@ bool Player::isImmobile()
return getHealth() <= 0 || isSleeping();
}
void Player::onChangedBlock(BlockPos pos)
{
if (true /*!isSpectator()*/) // spectator is not a thing yet
{
LivingEntity::onChangedBlock(pos);
}
}
void Player::closeContainer()
{
containerMenu = inventoryMenu;
+3
View File
@@ -8,6 +8,7 @@ using namespace std;
#include "PlayerEnderChestContainer.h"
#include "CommandSender.h"
#include "ScoreHolder.h"
#include "BlockPos.h"
class AbstractContainerMenu;
class Stats;
@@ -133,6 +134,7 @@ public:
private:
int sleepCounter; // animation timer
int deathFadeCounter; // animation timer
BlockPos lastChangedBlockPos;
public:
float bedOffsetX, bedOffsetY, bedOffsetZ;
@@ -207,6 +209,7 @@ protected:
public:
virtual void handleEntityEvent(byte id);
void onChangedBlock(BlockPos pos);
protected:
bool isImmobile();