feat: Entities drop exp now

This commit is contained in:
Alexander Medvedev 2026-05-02 16:24:08 +02:00
parent d33484d057
commit 501fd0ced5
8 changed files with 1941 additions and 1732 deletions

File diff suppressed because one or more lines are too long

View File

@ -15,6 +15,7 @@ pub struct EntityType {
/// Numeric registry ID for this entity type.
pub id: u16,
pub attributes: Option<Vec<BTreeMap<String, f64>>>,
pub experience_reward: Option<u32>,
/// Static hurt sound event name when it is safely derivable from extracted entity data.
pub hurt_sound: Option<String>,
/// Whether this entity can be attacked by players or other entities.
@ -180,10 +181,13 @@ impl ToTokens for NamedEntityType<'_> {
quote! { None }
};
let experience_reward = entity.experience_reward.unwrap_or(0);
tokens.extend(quote! {
EntityType {
id: #id,
attributes: #attributes_field,
experience_reward: #experience_reward,
hurt_sound: #hurt_sound,
attackable: #attackable,
mob: #mob,
@ -247,6 +251,7 @@ pub fn build() -> TokenStream {
pub struct EntityType {
pub id: u16,
pub attributes: &'static [(Attributes, f64)],
pub experience_reward: u32,
pub hurt_sound: Option<Sound>,
pub attackable: Option<bool>,
pub mob: bool,

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ use std::sync::atomic::{
use std::{collections::HashMap, sync::atomic::AtomicI32};
use tracing::warn;
use super::experience_orb::ExperienceOrbEntity;
use super::{Entity, NBTStorage};
use super::{EntityBase, NBTStorageInit};
use crate::block::OnLandedUponArgs;
@ -1301,6 +1302,16 @@ impl LivingEntity {
// Drop loot
self.drop_loot(params).await;
// Award experience
if params.killed_by_player.unwrap_or(false)
&& world.level_info.load().game_rules.mob_drops
{
let amount = dyn_self.get_experience_reward(cause);
if amount > 0 {
ExperienceOrbEntity::spawn(&world, self.entity.pos.load(), amount).await;
}
}
self.entity.pose.store(EntityPose::Dying);
let block_pos = self.entity.block_pos.load();

View File

@ -335,6 +335,10 @@ pub trait Mob: EntityBase + Send + Sync {
fn is_sitting(&self) -> bool {
false
}
fn get_base_experience_reward(&self) -> u32 {
self.get_entity().entity_type.experience_reward
}
}
impl<T: Mob + Send + 'static> EntityBase for T {
fn tick<'a>(
@ -511,6 +515,23 @@ impl<T: Mob + Send + 'static> EntityBase for T {
fn get_y_velocity_drag(&self) -> Option<f64> {
self.get_mob_y_velocity_drag()
}
fn get_experience_reward(&self, _killer: Option<&dyn EntityBase>) -> u32 {
if self
.get_entity()
.age
.load(std::sync::atomic::Ordering::Relaxed)
< 0
{
return 0;
}
// TODO: apply enchantment processing like in vanilla
Mob::get_base_experience_reward(self)
}
fn get_base_experience_reward(&self) -> u32 {
Mob::get_base_experience_reward(self)
}
}
#[expect(dead_code)]

View File

@ -363,6 +363,14 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
/// Returns itself as the nbt storage for saving and loading data.
fn as_nbt_storage(&self) -> &dyn NBTStorage;
fn get_experience_reward(&self, _killer: Option<&dyn EntityBase>) -> u32 {
0
}
fn get_base_experience_reward(&self) -> u32 {
0
}
}
#[derive(Clone, Copy, Eq, PartialEq)]

View File

@ -3842,6 +3842,12 @@ impl EntityBase for Player {
self
}
fn get_experience_reward(&self, _killer: Option<&dyn EntityBase>) -> u32 {
// vanilla: min(level * 7, 100)
let level = self.experience_level.load(Ordering::Relaxed);
(level * 7).min(100) as u32
}
fn tick_in_void<'a>(&'a self, dyn_self: &'a dyn EntityBase) -> EntityBaseFuture<'a, ()> {
Box::pin(async move {
self.living_entity.tick_in_void(dyn_self).await;

View File

@ -10,7 +10,7 @@ use pumpkin_util::{
};
use rand::RngExt;
#[derive(Default)]
#[derive(Default, Copy, Clone)]
pub struct LootContextParameters {
pub explosion_radius: Option<f32>,
pub block_state: Option<&'static BlockState>,