Add Cake #81

Merged
TheBrokenRail merged 22 commits from bigjango13/minecraft-pi-reborn:master into master 2024-02-07 06:47:47 +00:00
15 changed files with 186 additions and 43 deletions
Showing only changes of commit f78a4e47ac - Show all commits

@ -1 +1 @@
Subproject commit 0b696bd55b31416929d0a29d84ad50ab5ba0ceae
Subproject commit ea31be1cb2b43decb9c906215f80ff20efd2b479

View File

@ -22,6 +22,7 @@ void misc_run_on_creative_inventory_setup(misc_update_function_FillingContainer_
typedef void (*misc_update_function_void_t)(void *obj);
void misc_run_on_tiles_setup(misc_update_function_void_t function); // obj == NULL
void misc_run_on_items_setup(misc_update_function_void_t function); // obj == NULL
void misc_run_on_language_setup(misc_update_function_void_t function); // obj == NULL
void Level_saveLevelData_injection(Level *level);

View File

@ -6,31 +6,57 @@
#include <mods/misc/misc.h>
// Items
Item *bucket = NULL;
FoodItem *bucket = NULL;
Review

Do we really care about MCPE compatibility that much? IMO, the code might be nicer if the milk bucket was just a separate item. What are your thoughts?

Do we really care about MCPE compatibility that much? IMO, the code might be nicer if the milk bucket was just a separate item. What are your thoughts?
Review

What MCPE compatibility? As far as I know, each type of bucket has it's own id (326 water bucket, 327 lava bucket, and 335 milk bucket). While it might be a little bit nicer without all the aux value checks, it would also be a lot more verbose (even with just splitting off milk buckets), and I think that the current systems is a bit harder to understand the first time, but once it's understood it's a lot easier to read/work with than splitting it off. (It also takes up 1-3 less item ids, but that's a pretty minor bonus.)

What MCPE compatibility? As far as I know, each type of bucket has it's own id (326 water bucket, 327 lava bucket, and 335 milk bucket). While it might be a little bit nicer without all the aux value checks, it would also be a lot more verbose (even with just splitting off milk buckets), and I think that the current systems is a bit harder to understand the first time, but once it's understood it's a lot easier to read/work with than splitting it off. (It also takes up 1-3 less item ids, but that's a pretty minor bonus.)
// Description And Texture
static std::string BucketItem_getDescriptionId(__attribute__((unused)) Item *item, ItemInstance *item_instance) {
static std::string BucketItem_getDescriptionId(__attribute__((unused)) FoodItem *item, ItemInstance *item_instance) {
if (item_instance->auxiliary == Tile_water->id) {
return "item.bucketWater";
} else if (item_instance->auxiliary == Tile_lava->id) {
return "item.bucketLava";
} else if (item_instance->auxiliary == 1) {
return "item.bucketMilk";
} else {
return "item.bucket";
}
}
static int32_t BucketItem_getIcon(__attribute__((unused)) Item *item, int32_t auxiliary) {
static int32_t BucketItem_getIcon(__attribute__((unused)) FoodItem *item, int32_t auxiliary) {
if (auxiliary == Tile_water->id) {
return 75;
} else if (auxiliary == Tile_lava->id) {
return 76;
} else if (auxiliary == 1) {
return 77;
} else {
return 74;
}
}
// Filling
static bool fill_bucket(ItemInstance *item_instance, Player *player, int new_auxiliary) {
bool success = false;
if (item_instance->count == 1) {
item_instance->auxiliary = new_auxiliary;
success = true;
} else {
ItemInstance new_item;
new_item.id = bucket->id;
new_item.count = 1;
new_item.auxiliary = new_auxiliary;
Inventory *inventory = player->inventory;
if (inventory->vtable->add(inventory, &new_item)) {
// Added To Inventory
success = true;
item_instance->count -= 1;
}
}
return success;
}
// Use Bucket
static int32_t BucketItem_useOn(__attribute__((unused)) Item *item, ItemInstance *item_instance, Player *player, Level *level, int32_t x, int32_t y, int32_t z, int32_t hit_side, __attribute__((unused)) float hit_x, __attribute__((unused)) float hit_y, __attribute__((unused)) float hit_z) {
if (item_instance->count < 1) {
static int32_t BucketItem_useOn(__attribute__((unused)) FoodItem *item, ItemInstance *item_instance, Player *player, Level *level, int32_t x, int32_t y, int32_t z, int32_t hit_side, __attribute__((unused)) float hit_x, __attribute__((unused)) float hit_y, __attribute__((unused)) float hit_z) {
if (item_instance->count < 1 || item_instance->auxiliary == 1) {
return 0;
} else if (item_instance->auxiliary == 0) {
// Empty Bucket
@ -43,23 +69,7 @@ static int32_t BucketItem_useOn(__attribute__((unused)) Item *item, ItemInstance
}
if (new_auxiliary != 0) {
// Valid
bool success = false;
if (item_instance->count == 1) {
item_instance->auxiliary = new_auxiliary;
success = true;
} else {
ItemInstance new_item;
new_item.id = bucket->id;
new_item.count = 1;
new_item.auxiliary = new_auxiliary;
Inventory *inventory = player->inventory;
if (inventory->vtable->add(inventory, &new_item)) {
// Added To Inventory
success = true;
item_instance->count -= 1;
}
}
if (success) {
if (fill_bucket(item_instance, player, new_auxiliary)) {
Level_setTileAndData(level, x, y, z, 0, 0);
return 1;
} else {
@ -116,28 +126,64 @@ static int32_t BucketItem_useOn(__attribute__((unused)) Item *item, ItemInstance
}
}
static int BucketItem_getUseDuration(__attribute__((unused)) FoodItem *item, ItemInstance *item_instance) {
if (item_instance->auxiliary == 1) {
return 0x20;
}
return 0;
}
static void BucketItem_useTimeDepleted(FoodItem *item, uchar *param_1, ItemInstance *item_instance, Level *level, Player *player) {
if (item_instance->auxiliary == 1) {
(*FoodItem_useTimeDepleted_vtable_addr)(item, param_1, item_instance, level, player);
bigjango13 marked this conversation as resolved Outdated

Use FoodItem_useTimeDepleted_non_virtual rather than de-referencing the address yourself.

Use `FoodItem_useTimeDepleted_non_virtual` rather than de-referencing the address yourself.
// Set it to a empty bucket
item_instance->auxiliary = 0;
item_instance->count = 1;
}
}
static int BucketItem_getUseAnimation(__attribute__((unused)) FoodItem *item) {
return 2;
}
static bool BucketItem_isFood(__attribute__((unused)) FoodItem *item) {
return true;
}
static ItemInstance *BucketItem_use(FoodItem *item, ItemInstance *item_instance, __attribute__((unused)) Level *level, Player *player) {
if (item_instance->auxiliary == 1) {
return (*FoodItem_use_vtable_addr)(item, item_instance, level, player);
}
return item_instance;
}
// Bucket VTable
static Item_vtable *get_bucket_vtable() {
static Item_vtable *vtable = NULL;
static FoodItem_vtable *get_bucket_vtable() {
static FoodItem_vtable *vtable = NULL;
if (vtable == NULL) {
// Init
vtable = dup_Item_vtable(Item_vtable_base);
vtable = dup_FoodItem_vtable(FoodItem_vtable_base);
ALLOC_CHECK(vtable);
// Modify
vtable->getDescriptionId = BucketItem_getDescriptionId;
vtable->getIcon = BucketItem_getIcon;
vtable->useOn = BucketItem_useOn;
vtable->getUseDuration = BucketItem_getUseDuration;
vtable->useTimeDepleted = BucketItem_useTimeDepleted;
vtable->getUseAnimation = BucketItem_getUseAnimation;
vtable->isFood = BucketItem_isFood;
vtable->use = BucketItem_use;
}
return vtable;
}
// Create Items
static Item *create_bucket(int32_t id, int32_t texture_x, int32_t texture_y, std::string name) {
static FoodItem *create_bucket(int32_t id, int32_t texture_x, int32_t texture_y, std::string name) {
// Construct
Item *item = alloc_Item();
FoodItem *item = alloc_FoodItem();
ALLOC_CHECK(item);
Item_constructor(item, id);
Item_constructor((Item *) item, id);
// Set VTable
item->vtable = get_bucket_vtable();
@ -149,6 +195,7 @@ static Item *create_bucket(int32_t id, int32_t texture_x, int32_t texture_y, std
item->category = 2;
item->max_damage = 0;
item->max_stack_size = 1;
item->nutrition = 0;
bigjango13 marked this conversation as resolved Outdated

Does FoodItem not have its own constructor?

Does `FoodItem` not have its own constructor?

I think it was inlined, it must have been pretty minor, as I can't find any trace of it in minecraft-pi or libminecraftpe.so

I think it was inlined, it must have been pretty minor, as I can't find any trace of it in minecraft-pi or libminecraftpe.so
// Return
return item;
@ -168,17 +215,29 @@ static int32_t ItemInstance_getMaxStackSize_injection(ItemInstance *item_instanc
}
}
// Milking
bool Cow_interact_injection(Cow *self, Player *player) {
ItemInstance *item = Inventory_getSelected(player->inventory);
if (item && item->id == bucket->id && item->auxiliary == 0) {
// Fill with milk
fill_bucket(item, player, 1);
return true;
}
return Cow_interact_non_virtual(self, player);
}
// Creative Inventory
static void inventory_add_item(FillingContainer *inventory, Item *item, int32_t auxiliary) {
static void inventory_add_item(FillingContainer *inventory, FoodItem *item, int32_t auxiliary) {
ItemInstance *item_instance = new ItemInstance;
ALLOC_CHECK(item_instance);
item_instance = ItemInstance_constructor_item_extra(item_instance, item, 1, auxiliary);
item_instance = ItemInstance_constructor_item_extra(item_instance, (Item *) item, 1, auxiliary);
FillingContainer_addItem(inventory, item_instance);
}
static void Inventory_setupDefault_FillingContainer_addItem_call_injection(FillingContainer *filling_container) {
inventory_add_item(filling_container, bucket, 0);
inventory_add_item(filling_container, bucket, Tile_water->id);
inventory_add_item(filling_container, bucket, Tile_lava->id);
inventory_add_item(filling_container, bucket, 1);
}
// Make Liquids Selectable
@ -273,6 +332,10 @@ static void FurnaceTileEntity_tick_ItemInstance_setNull_injection(ItemInstance *
}
}
static void Language_injection(__attribute__((unused)) void *null) {
I18n__strings.insert(std::make_pair("item.bucketMilk.name", "Milk Bucket"));
}
// Init
bigjango13 marked this conversation as resolved Outdated

This should probably have a comment before it.

This should probably have a comment before it.
void init_bucket() {
// Add Buckets
@ -281,6 +344,8 @@ void init_bucket() {
misc_run_on_items_setup(Item_initItems_injection);
// Change Max Stack Size Based On Auxiliary
overwrite_calls((void *) ItemInstance_getMaxStackSize, (void *) ItemInstance_getMaxStackSize_injection);
// Enable milking
patch_address((void *) Cow_interact_vtable_addr, (void *) Cow_interact_injection);
// Creative Inventory
misc_run_on_creative_inventory_setup(Inventory_setupDefault_FillingContainer_addItem_call_injection);
// Make Liquids Selectable
@ -293,5 +358,7 @@ void init_bucket() {
// Custom Furnace Fuel
overwrite_calls((void *) FurnaceTileEntity_getBurnDuration, (void *) FurnaceTileEntity_getBurnDuration_injection);
overwrite_call((void *) 0xd351c, (void *) FurnaceTileEntity_tick_ItemInstance_setNull_injection);
// Language for milk
misc_run_on_language_setup(Language_injection);
}
}

View File

@ -77,6 +77,7 @@ SETUP_CALLBACK(creative_inventory_setup, FillingContainer);
static void Inventory_setupDefault_FillingContainer_addItem_call_injection(FillingContainer *filling_container, ItemInstance *item_instance) {
// Call Original Method
FillingContainer_addItem(filling_container, item_instance);
// Run Functions
handle_misc_creative_inventory_setup(filling_container);
}
@ -85,22 +86,33 @@ static void Inventory_setupDefault_FillingContainer_addItem_call_injection(Filli
SETUP_CALLBACK(tiles_setup, void);
// Handle Custom Tiles Setup Behavior
static void Tile_initTiles_injection() {
// Run Functions
handle_misc_tiles_setup(NULL);
// Call Original Method
Tile_initTiles();
// Run Functions
handle_misc_tiles_setup(NULL);
}
// Run Functions On Items Setup
SETUP_CALLBACK(items_setup, void);
// Handle Custom Items Setup Behavior
static void Item_initItems_injection() {
// Run Functions
handle_misc_items_setup(NULL);
// Call Original Method
Item_initItems();
// Run Functions
handle_misc_items_setup(NULL);

Why was the order of Item_initItems_injection changed?

Why was the order of `Item_initItems_injection` changed?

A few reasons:

  • It allows for mods to modify and overwrite existing items
  • It allows for mods to tell if an item is already assigned to an id
  • It doesn't really change anything beyond that (the same isn't true for tiles)
A few reasons: - It allows for mods to modify and overwrite existing items - It allows for mods to tell if an item is already assigned to an id - It doesn't really change anything beyond that (the same isn't true for tiles)
}
// Run Functions On Language Setup
SETUP_CALLBACK(language_setup, void);
// Handle Custom Items Setup Behavior
static void I18n_loadLanguage_injection(AppPlatform *app, std::string language_name) {
// Call Original Method
I18n_loadLanguage(app, language_name);
// Run Functions
handle_misc_language_setup(NULL);
}
// Init
@ -117,4 +129,6 @@ void _init_misc_api() {
// Handle Custom Item/Tile Init Behavior
overwrite_calls((void *) Tile_initTiles, (void *) Tile_initTiles_injection);
overwrite_calls((void *) Item_initItems, (void *) Item_initItems_injection);
// Handle Custom Language Entries
overwrite_calls((void *) I18n_loadLanguage, (void *) I18n_loadLanguage_injection);
}

View File

@ -545,6 +545,19 @@ static int FurnaceTileEntity_getLitProgress_injection(FurnaceTileEntity *furnace
return ret;
}
// Fix used items transferring durability
static int selected_slot = -1;
static void Player_startUsingItem_injection(Player *self, ItemInstance *item_instance, int time) {
selected_slot = self->inventory->selectedSlot;
Player_startUsingItem(self, item_instance, time);
}
static void Player_stopUsingItem_injection(Player *self) {
if (selected_slot != self->inventory->selectedSlot) {
self->itemBeingUsed.id = 0;
}
Player_stopUsingItem(self);
bigjango13 marked this conversation as resolved Outdated

Rather than all this, why not just make the player stop using the item when the selected slot is changed?

Rather than all this, why not just make the player stop using the item when the selected slot is changed?

That already happens, however if itemBeingUsed.id == heldItem.id, heldItem's auxiliary will be set to that of itemBeingUsed. This fixes it by insuring that itemBeingUsed's id cannot be the same, as empty slots are (almost1) always NULL instead of having an id of 0.


  1. There are a few way to obtain an EmptyItemInstance, however it's throught bugs/mods and it almost always crashes within a few seconds of being held. ↩︎

That already happens, however if `itemBeingUsed.id == heldItem.id`, `heldItem`'s `auxiliary` will be set to that of `itemBeingUsed`. This fixes it by insuring that `itemBeingUsed`'s `id` cannot be the same, as empty slots are (almost[^1]) always NULL instead of having an id of 0. [^1]: There are a few way to obtain an EmptyItemInstance, however it's throught bugs/mods and it almost always crashes within a few seconds of being held.
}
// Init
static void nop() {
}
@ -731,6 +744,10 @@ void init_misc() {
unsigned char mov_r3_ff[4] = {0xff, 0x30, 0xa0, 0xe3}; // "mov r3, #0xff"
patch((void *) 0x7178c, mov_r3_ff);
// Fix used items transferring durability
overwrite_calls((void *) Player_startUsingItem, (void *) Player_startUsingItem_injection);
overwrite_calls((void *) Player_stopUsingItem, (void *) Player_stopUsingItem_injection);
// Init C++ And Logging
_init_misc_cpp();
_init_misc_logging();

View File

@ -40,8 +40,9 @@ set(SRC
src/entity/MobFactory.def
src/entity/EntityRenderDispatcher.def
src/entity/MobRenderer.def
src/entity/AgableMob.def
src/entity/Animal.def
src/entity/animal/AgableMob.def
src/entity/animal/Animal.def
src/entity/animal/Cow.def
src/entity/Mob.def
src/entity/player/ServerPlayer.def
src/entity/player/Player.def
@ -82,6 +83,7 @@ set(SRC
src/item/ArmorMaterial.def
src/item/ArmorItem.def
src/item/TileItem.def
src/item/FoodItem.def
src/api/OffsetPosTranslator.def
src/api/CommandServer.def
src/api/ConnectedClient.def
@ -125,6 +127,7 @@ set(SRC
src/tile/GrassTile.def
src/tile/HeavyTile.def
src/misc/Strings.def
src/misc/I18n.def
src/entity/ModelPart.def
src/misc/Tesselator.def
src/misc/AABB.def

View File

@ -1,5 +1,6 @@
virtual-method void remove() = 0x10;
virtual-method void tick() = 0x34;
virtual-method bool interact(Player *with) = 0x6c;
virtual-method bool hurt(Entity *attacker, int damage) = 0xa4;
virtual-method int getEntityTypeId() = 0xdc;

View File

@ -0,0 +1,3 @@
extends Animal;
vtable 0x10ba38;

View File

@ -2,12 +2,18 @@ extends Mob;
vtable 0x10de70;
method int isUsingItem() = 0x8f15c;
virtual-method void drop(ItemInstance *item_instance, bool is_death) = 0x208;
virtual-method void stopSleepInBed(bool param_1, bool param_2, bool param_3) = 0x228;
virtual-method void openTextEdit(TileEntity *sign) = 0x230;
method ItemInstance *getArmor(int slot) = 0x8fda4;
property std::string username = 0xbf4;
method int isUsingItem() = 0x8f15c;
method void stopUsingItem() = 0x8f514;
method void startUsingItem(ItemInstance *item_instance, int use_duration) = 0x8f4b8;
method ItemInstance *getArmor(int slot) = 0x8fda4;
method bool isHurt() = 0x8fb44;
property Inventory *inventory = 0xbe0;
property std::string username = 0xbf4;
property bool immortal = 0xbfc;
property bool infinite_items = 0xbff;
property ItemInstance itemBeingUsed = 0xc34;

View File

@ -0,0 +1,7 @@
extends Item;
size 0x30;
bigjango13 marked this conversation as resolved
Review

FoodItem is 3 32-bit ints bigger than Item, but only one is accounted for nutrition. What are the others? This is important because FoodItem is constructed manually and therefore anything not set manually will be left uninitialized.

`FoodItem` is 3 32-bit ints bigger than `Item`, but only one is accounted for `nutrition`. What are the others? This is important because `FoodItem` is constructed manually and therefore anything not set manually will be left uninitialized.
vtable 0x10e7b0;
vtable-size 0x98;
property int nutrition = 0x24;

View File

@ -6,12 +6,22 @@ vtable 0x10f128;
size 0x24;
constructor (int id) = 0x99488;
virtual-method void setIcon(int texture_x, int texture_y) = 0x18;
virtual-method int getIcon(int auxiliary) = 0x14;
virtual-method void setIcon(int texture_x, int texture_y) = 0x18;
virtual-method int useOn(ItemInstance *item_instance, Player *player, Level *level, int x, int y, int z, int hit_side, float hit_x, float hit_y, float hit_z) = 0x20;
// Normally returns 0
virtual-method int getUseDuration(ItemInstance *item_instance) = 0x24;
// I don't know much about param_1, it might be some partially initialized ItemInstance*
virtual-method void useTimeDepleted(uchar *param_1, ItemInstance *item_instance, Level *level, Player *player) = 0x28;
bigjango13 marked this conversation as resolved
Review

This seems wrong. According to Ghidra, this function's signature is Item::useTimeDepleted(ItemInstance*, Level*, Player*) (and of course an extra Item *self).

It could be returning a structure, IIRC if a function returns a structure, the caller allocates the structure and passes a pointer to it as the first argument top the function (like how this is passed) and then the function writes to that pointer. Check out CommandServer::parse in Ghidra.

This seems wrong. According to Ghidra, this function's signature is `Item::useTimeDepleted(ItemInstance*, Level*, Player*)` (and of course an extra `Item *self`). It could be returning a structure, IIRC if a function returns a structure, the caller allocates the structure and passes a pointer to it as the first argument top the function (like how `this` is passed) and then the function writes to that pointer. Check out `CommandServer::parse` in Ghidra.
Review

Hmm, seems like it. it tripped me up because both Item and ItemInstance have the id at 0x4.

Hmm, seems like it. it tripped me up because both Item and ItemInstance have the id at 0x4.
virtual-method int getDestorySpeed(ItemInstance *item_instance, Tile *tile) = 0x2c;
virtual-method ItemInstance *use(ItemInstance *item_instance, Level *level, Player *player) = 0x30;
virtual-method bool mineBlock(ItemInstance *instance, int tile_id, int x, int y, int z) = 0x48;
virtual-method bool isFood() = 0x64;
virtual-method bool isArmor() = 0x68;
virtual-method void setDescriptionId(std::string *name) = 0x6c;
virtual-method std::string getDescriptionId(ItemInstance *item_instance) = 0x7c;
// Swing = 0, eating = 1, drinking = 2, bow = 4, anything else is nothing
virtual-method int getUseAnimation() = 0x94;
property int id = 0x4;
property int max_damage = 0x8;

View File

@ -0,0 +1,3 @@
static-method void loadLanguage(AppPlatform *app, std::string language_name) = 0x680d0;
static-property std::map<std::string, std::string> _strings = 0x137d98;

View File

@ -1,4 +1,15 @@
method void Write_uchar(uchar *i) = 0x18448;
method void Write_int(int *i) = 0x18454;
method void Write_ushort(ushort *i) = 0x45a68;
method void Write_short(short *i) = 0x71918;
// right_aligned should be true
method void WriteBits(uchar *buff, uint bits, bool right_aligned) = 0xd41b4;
method void Read_uchar(uchar *i) = 0x45ab0;
method void Read_int(int *i) = 0x184ec;
method void Read_ushort(ushort *i) = 0x45acc;
method void Read_short(short *i) = 0x72070;
// right_aligned should be true
method void ReadBits(uchar *buff, uint bits, bool right_aligned) = 0xd3e18;
property uint readOffset = 0x8;