Implement GSC berry/apricorn tree functionality. (#7777)

Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
This commit is contained in:
Zimmermann Gyula 2025-11-14 20:28:55 +01:00 committed by GitHub
parent ca575f2701
commit 772abe6e7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 407 additions and 1 deletions

View File

@ -3,6 +3,7 @@
#include "config/item.h"
#include "constants/global.h"
#include "constants/apprentice.h"
#include "constants/apricorn_tree.h"
#include "constants/battle.h"
#include "constants/battle_arena.h"
#include "constants/battle_dome.h"
@ -1160,3 +1161,4 @@ EventScript_VsSeekerChargingDone::
.include "data/text/birch_speech.inc"
.include "data/scripts/dexnav.inc"
.include "data/scripts/battle_frontier.inc"
.include "data/scripts/apricorn_tree.inc"

View File

@ -13,6 +13,7 @@
#include "constants/trainer_types.h"
#include "constants/berry.h"
#include "constants/species.h"
#include "constants/apricorn_tree.h"
.include "asm/macros.inc"
.include "constants/constants.inc"

View File

@ -0,0 +1,89 @@
ApricornTreeScript::
lock
faceplayer
message ApricornTree_Text_Intro
waitmessage
special ObjectEventInteractionGetApricornTreeData
goto_if_gt VAR_0x8005, 0, ApricornTree_EventScript_WantToPick
message ApricornTree_Text_Empty
waitmessage
waitbuttonpress
release
end
ApricornTree_EventScript_WantToPick::
buffernumberstring STR_VAR_2, VAR_0x8005
msgbox ApricornTree_Text_WantToPick, MSGBOX_YESNO
goto_if_eq VAR_RESULT, YES, ApricornTree_EventScript_PickApricorn
goto_if_eq VAR_RESULT, NO, ApricornTree_EventScript_CancelPickingApricorn
.set APRICORN_NORMAL_BAG_FULL, 0
.set APRICORN_NORMAL_SPACE_IN_BAG, 1
ApricornTree_EventScript_PickApricorn::
special ObjectEventInteractionPickApricornTree
call EventScript_BufferPocketNameAndTryFanfare
goto_if_eq VAR_0x8006, APRICORN_NORMAL_BAG_FULL, ApricornTree_EventScript_PocketFull
message ApricornTree_Text_PickedTheApricorn
.if OW_SHOW_ITEM_DESCRIPTIONS != OW_ITEM_DESCRIPTIONS_OFF
copyvar VAR_0x8006 VAR_0x8004
.endif
delay 10
.if OW_SHOW_ITEM_DESCRIPTIONS != OW_ITEM_DESCRIPTIONS_OFF
showberrydesc
.endif
playfanfare MUS_OBTAIN_BERRY
waitmessage
waitfanfare
waitbuttonpress
message ApricornTree_Text_PutAwayApricorn
waitmessage
waitbuttonpress
.if OW_SHOW_ITEM_DESCRIPTIONS != OW_ITEM_DESCRIPTIONS_OFF
hideitemdesc
.endif
release
end
ApricornTree_EventScript_PocketFull::
message ApricornTree_Text_PocketFull
waitmessage
waitbuttonpress
release
end
ApricornTree_EventScript_CancelPickingApricorn::
message ApricornTree_Text_ApricornLeftUnpicked
waitmessage
waitbuttonpress
release
end
ApricornTree_Text_Intro:
.string "It's an Apricorn Tree!$"
ApricornTree_Text_Empty:
.string "There are no Apricorns…$"
ApricornTree_Text_WantToPick:
.string "…It's {STR_VAR_2} {STR_VAR_1}!\p"
.string "Do you want to pick the\n"
.string "{STR_VAR_1}?$"
ApricornTree_Text_PickedTheApricorn:
.string "{PLAYER} obtained\n"
.string "{STR_VAR_2} {STR_VAR_1}.$"
ApricornTree_Text_PutAwayApricorn:
.string "{PLAYER} put away the\n"
.string "{STR_VAR_1} in\l"
.string "the BAG's {STR_VAR_3} POCKET.$"
ApricornTree_Text_PocketFull:
.string "The BAG's {STR_VAR_3} POCKET is full.\p"
.string "{PLAYER} gave up on the\p"
.string "{STR_VAR_1}…$"
ApricornTree_Text_ApricornLeftUnpicked:
.string "{PLAYER} gave up on the\p"
.string "{STR_VAR_1}…$"

View File

@ -565,3 +565,5 @@ gSpecials::
def_special GetCodeFeedback
def_special SetHiddenNature
def_special SetAbility
def_special ObjectEventInteractionGetApricornTreeData
def_special ObjectEventInteractionPickApricornTree

View File

@ -0,0 +1,58 @@
# How to interact with Apricorn Trees
![apricorn-tree](/docs/tutorials/img/apricorn_tree/apricorn-tree.gif)
### Adding a new apricorn tree
To add a new tree, first increase the tree count and expand the tree list in `include/constants/apricorn_tree.h`.
Note that each tree will take a bit in the savegame's `SaveBlock3` struct so increasing `APRICORN_TREE_COUNT` **breaks the savegame**.
Due to this, pokeemerald-expansion doesn't have any trees set up by default to prevent breaking downstream savegames.
The trees support random yields and properly use plural case on plural yields.
```diff
#define APRICORN_TREE_NONE 0
-#define APRICORN_TREE_COUNT 0
+#define APRICORN_TREE_ROUTE101_RED_TREE 1
+
+#define APRICORN_TREE_COUNT 32
```
Then list its data in `src/data/apricorns.h`.
```diff
const struct ApricornTree gApricornTrees[APRICORN_TREE_COUNT] =
{
[APRICORN_TREE_NONE] =
{
.minimum = 1,
.maximum = 1,
.apricornType = APRICORN_RED,
},
+ [APRICORN_TREE_ROUTE101_RED_TREE] =
+ {
+ .minimum = 1,
+ .maximum = 1,
+ .apricornType = APRICORN_RED,
+ },
};
```
Finally, just place your new tree using Porymap.
Similarly to berries, the Sight Radius / Berry Tree ID field is used for the tree's ID.
![apricorn-tree-porymap](/docs/tutorials/img/apricorn_tree/apricorn-tree-porymap.png)
### Add a new apricorn type
After you created your new item, simply expand the `ApricornType` enum in `include/constants/apricorn_tree.h`.
```diff
enum ApricornType
{
[...]
APRICORN_BERRY_MARANGA = ITEM_MARANGA_BERRY,
+ APRICORN_BROWN = ITEM_BROWN_APRICORN,
};
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

15
include/apricorn_tree.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef GUARD_APRICORN_TREE_H
#define GUARD_APRICORN_TREE_H
#include "constants/apricorn_tree.h"
bool8 IsApricornTreePicked(u32 id);
void SetApricornTreePicked(u32 id);
void DailyResetApricornTrees(void);
void ObjectEventInteractionGetApricornTreeData(void);
void ObjectEventInteractionPickApricornTree(void);
enum ApricornType GetApricornTypeByApricornTreeId(u32 id);
u8 GetApricornCountByApricornTreeId(u32 id);
#endif //GUARD_APRICORN_TREE_H

View File

@ -0,0 +1,90 @@
#ifndef GUARD_CONSTANTS_APRICORN_TREE_H
#define GUARD_CONSTANTS_APRICORN_TREE_H
#include "constants/items.h"
// Apricorn lookup table, added to allow adding new apricorns without being forced to rearrange the item constants.
enum ApricornType
{
APRICORN_RED = ITEM_RED_APRICORN,
APRICORN_BLUE = ITEM_BLUE_APRICORN,
APRICORN_YELLOW = ITEM_YELLOW_APRICORN,
APRICORN_GREEN = ITEM_GREEN_APRICORN,
APRICORN_PINK = ITEM_PINK_APRICORN,
APRICORN_WHITE = ITEM_WHITE_APRICORN,
APRICORN_BLACK = ITEM_BLACK_APRICORN,
APRICORN_BERRY_CHERI = ITEM_CHERI_BERRY,
APRICORN_BERRY_CHESTO = ITEM_CHESTO_BERRY,
APRICORN_BERRY_PECHA = ITEM_PECHA_BERRY,
APRICORN_BERRY_RAWST = ITEM_RAWST_BERRY,
APRICORN_BERRY_ASPEAR = ITEM_ASPEAR_BERRY,
APRICORN_BERRY_LEPPA = ITEM_LEPPA_BERRY,
APRICORN_BERRY_ORAN = ITEM_ORAN_BERRY,
APRICORN_BERRY_PERSIM = ITEM_PERSIM_BERRY,
APRICORN_BERRY_LUM = ITEM_LUM_BERRY,
APRICORN_BERRY_SITRUS = ITEM_SITRUS_BERRY,
APRICORN_BERRY_FIGY = ITEM_FIGY_BERRY,
APRICORN_BERRY_WIKI = ITEM_WIKI_BERRY,
APRICORN_BERRY_MAGO = ITEM_MAGO_BERRY,
APRICORN_BERRY_AGUAV = ITEM_AGUAV_BERRY,
APRICORN_BERRY_IAPAPA = ITEM_IAPAPA_BERRY,
APRICORN_BERRY_RAZZ = ITEM_RAZZ_BERRY,
APRICORN_BERRY_BLUK = ITEM_BLUK_BERRY,
APRICORN_BERRY_NANAB = ITEM_NANAB_BERRY,
APRICORN_BERRY_WEPEAR = ITEM_WEPEAR_BERRY,
APRICORN_BERRY_PINAP = ITEM_PINAP_BERRY,
APRICORN_BERRY_POMEG = ITEM_POMEG_BERRY,
APRICORN_BERRY_KELPSY = ITEM_KELPSY_BERRY,
APRICORN_BERRY_QUALOT = ITEM_QUALOT_BERRY,
APRICORN_BERRY_HONDEW = ITEM_HONDEW_BERRY,
APRICORN_BERRY_GREPA = ITEM_GREPA_BERRY,
APRICORN_BERRY_TAMATO = ITEM_TAMATO_BERRY,
APRICORN_BERRY_CORNN = ITEM_CORNN_BERRY,
APRICORN_BERRY_MAGOST = ITEM_MAGOST_BERRY,
APRICORN_BERRY_RABUTA = ITEM_RABUTA_BERRY,
APRICORN_BERRY_NOMEL = ITEM_NOMEL_BERRY,
APRICORN_BERRY_SPELON = ITEM_SPELON_BERRY,
APRICORN_BERRY_PAMTRE = ITEM_PAMTRE_BERRY,
APRICORN_BERRY_WATMEL = ITEM_WATMEL_BERRY,
APRICORN_BERRY_DURIN = ITEM_DURIN_BERRY,
APRICORN_BERRY_BELUE = ITEM_BELUE_BERRY,
APRICORN_BERRY_OCCA = ITEM_OCCA_BERRY,
APRICORN_BERRY_PASSHO = ITEM_PASSHO_BERRY,
APRICORN_BERRY_WACAN = ITEM_WACAN_BERRY,
APRICORN_BERRY_RINDO = ITEM_RINDO_BERRY,
APRICORN_BERRY_YACHE = ITEM_YACHE_BERRY,
APRICORN_BERRY_CHOPLE = ITEM_CHOPLE_BERRY,
APRICORN_BERRY_KEBIA = ITEM_KEBIA_BERRY,
APRICORN_BERRY_SHUCA = ITEM_SHUCA_BERRY,
APRICORN_BERRY_COBA = ITEM_COBA_BERRY,
APRICORN_BERRY_PAYAPA = ITEM_PAYAPA_BERRY,
APRICORN_BERRY_TANGA = ITEM_TANGA_BERRY,
APRICORN_BERRY_CHARTI = ITEM_CHARTI_BERRY,
APRICORN_BERRY_KASIB = ITEM_KASIB_BERRY,
APRICORN_BERRY_HABAN = ITEM_HABAN_BERRY,
APRICORN_BERRY_COLBUR = ITEM_COLBUR_BERRY,
APRICORN_BERRY_BABIRI = ITEM_BABIRI_BERRY,
APRICORN_BERRY_CHILAN = ITEM_CHILAN_BERRY,
APRICORN_BERRY_LIECHI = ITEM_LIECHI_BERRY,
APRICORN_BERRY_GANLON = ITEM_GANLON_BERRY,
APRICORN_BERRY_SALAC = ITEM_SALAC_BERRY,
APRICORN_BERRY_PETAYA = ITEM_PETAYA_BERRY,
APRICORN_BERRY_APICOT = ITEM_APICOT_BERRY,
APRICORN_BERRY_LANSAT = ITEM_LANSAT_BERRY,
APRICORN_BERRY_STARF = ITEM_STARF_BERRY,
APRICORN_BERRY_ENIGMA = ITEM_ENIGMA_BERRY,
APRICORN_BERRY_MICLE = ITEM_MICLE_BERRY,
APRICORN_BERRY_CUSTAP = ITEM_CUSTAP_BERRY,
APRICORN_BERRY_JABOCA = ITEM_JABOCA_BERRY,
APRICORN_BERRY_ROWAP = ITEM_ROWAP_BERRY,
APRICORN_BERRY_ROSELI = ITEM_ROSELI_BERRY,
APRICORN_BERRY_KEE = ITEM_KEE_BERRY,
APRICORN_BERRY_MARANGA = ITEM_MARANGA_BERRY,
};
// Trees
#define APRICORN_TREE_NONE 0
#define APRICORN_TREE_COUNT 0
#endif //GUARD_CONSTANTS_APRICORN_TREE_H

View File

@ -246,11 +246,12 @@
#define OBJ_EVENT_GFX_POKE_BALL 239
#define OBJ_EVENT_GFX_OW_MON 240
#define OBJ_EVENT_GFX_LIGHT_SPRITE 241
#define OBJ_EVENT_GFX_APRICORN_TREE 242
// NOTE: The maximum amount of object events has been expanded from 255 to 65535.
// Since dynamic graphics ids still require at least 16 free values, the actual limit
// is 65519, but even considering follower Pokémon, this should be more than enough :)
#define NUM_OBJ_EVENT_GFX 242
#define NUM_OBJ_EVENT_GFX 243
// These are dynamic object gfx ids.

View File

@ -512,4 +512,6 @@ bool8 MovementAction_EmoteX_Step0(struct ObjectEvent *, struct Sprite *);
bool8 MovementAction_EmoteDoubleExclamationMark_Step0(struct ObjectEvent *, struct Sprite *);
bool8 PlayerIsUnderWaterfall(struct ObjectEvent *objectEvent);
u8 GetObjectEventApricornTreeId(u8 objectEventId);
#endif //GUARD_EVENT_OBJECT_MOVEMENT_H

View File

@ -14,6 +14,7 @@
#include "constants/vars.h"
#include "constants/species.h"
#include "constants/pokedex.h"
#include "constants/apricorn_tree.h"
#include "constants/berry.h"
#include "constants/maps.h"
#include "constants/pokemon.h"
@ -134,6 +135,8 @@
#define NUM_FLAG_BYTES ROUND_BITS_TO_BYTES(FLAGS_COUNT)
#define NUM_TRENDY_SAYING_BYTES ROUND_BITS_TO_BYTES(NUM_TRENDY_SAYINGS)
#define NUM_APRICORN_TREE_BYTES ROUND_BITS_TO_BYTES(APRICORN_TREE_COUNT)
// This produces an error at compile-time if expr is zero.
// It looks like file.c:line: size of array `id' is negative
#define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1];
@ -252,6 +255,9 @@ struct SaveBlock3
u8 dexNavSearchLevels[NUM_SPECIES];
#endif
u8 dexNavChain;
#if APRICORN_TREE_COUNT > 0
u8 apricornTrees[NUM_APRICORN_TREE_BYTES];
#endif
}; /* max size 1624 bytes */
extern struct SaveBlock3 *gSaveBlock3Ptr;

View File

@ -612,6 +612,8 @@ $(OBJEVENTGFXDIR)/berry_trees/rowap.4bpp: %.4bpp: %.png
$(OBJEVENTGFXDIR)/berry_trees/micle.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 2 -mheight 4
$(OBJEVENTGFXDIR)/misc/apricorn_tree.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 2 -mheight 2
$(OBJEVENTGFXDIR)/misc/breakable_rock.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 2 -mheight 2

86
src/apricorn_tree.c Normal file
View File

@ -0,0 +1,86 @@
#include "global.h"
#include "apricorn_tree.h"
#include "event_data.h"
#include "event_object_movement.h"
#include "event_scripts.h"
#include "item.h"
#include "random.h"
#include "string_util.h"
#include "data/apricorns.h"
void DailyResetApricornTrees(void)
{
#if (APRICORN_TREE_COUNT > 0)
memset(&gSaveBlock3Ptr->apricornTrees[0], 0, NUM_APRICORN_TREE_BYTES);
#endif
}
void ObjectEventInteractionGetApricornTreeData(void)
{
u32 id = GetObjectEventApricornTreeId(gSelectedObjectEvent);
gSpecialVar_0x8004 = GetApricornTypeByApricornTreeId(id);
gSpecialVar_0x8005 = GetApricornCountByApricornTreeId(id);
CopyItemNameHandlePlural(gSpecialVar_0x8004, gStringVar1, gSpecialVar_0x8005);
}
void ObjectEventInteractionPickApricornTree(void)
{
u32 id = GetObjectEventApricornTreeId(gSelectedObjectEvent);
enum ApricornType apricorn = GetApricornTypeByApricornTreeId(id);
gSpecialVar_0x8006 = CheckBagHasSpace(apricorn, GetApricornCountByApricornTreeId(id));
if (gSpecialVar_0x8006)
{
AddBagItem(apricorn, GetApricornCountByApricornTreeId(id));
SetApricornTreePicked(id);
}
gSpecialVar_Result = GetItemPocket(apricorn);
}
enum ApricornType GetApricornTypeByApricornTreeId(u32 id)
{
if (APRICORN_TREE_COUNT > 0)
return gApricornTrees[id].apricornType;
else
return 0;
}
u8 GetApricornCountByApricornTreeId(u32 id)
{
if (IsApricornTreePicked(id))
return 0;
if (APRICORN_TREE_COUNT > 0)
{
if (gApricornTrees[id].maximum > gApricornTrees[id].minimum)
return gApricornTrees[id].minimum + Random() % (gApricornTrees[id].maximum - gApricornTrees[id].minimum);
else
return gApricornTrees[id].minimum;
}
else
return 0;
}
bool8 IsApricornTreePicked(u32 id)
{
if (id > APRICORN_TREE_COUNT)
return TRUE;
#if (APRICORN_TREE_COUNT > 0)
return gSaveBlock3Ptr->apricornTrees[id / 8] & (1 << (id % 8));
#else
return TRUE;
#endif
}
void SetApricornTreePicked(u32 id)
{
if (id > APRICORN_TREE_COUNT)
return;
#if (APRICORN_TREE_COUNT > 0)
u8 *flagByte = &gSaveBlock3Ptr->apricornTrees[id / 8];
*flagByte = (*flagByte) | (1 << (id % 8));
#endif
}

View File

@ -13,6 +13,7 @@
#include "tv.h"
#include "wallclock.h"
#include "constants/form_change_types.h"
#include "apricorn_tree.h"
static void UpdatePerDay(struct Time *localTime);
static void UpdatePerMinute(struct Time *localTime);
@ -55,6 +56,7 @@ static void UpdatePerDay(struct Time *localTime)
SetShoalItemFlag(daysSince);
SetRandomLotteryNumber(daysSince);
UpdateDaysPassedSinceFormChange(daysSince);
DailyResetApricornTrees();
*days = localTime->days;
}
}

18
src/data/apricorns.h Normal file
View File

@ -0,0 +1,18 @@
struct ApricornTree
{
u8 minimum;
u8 maximum;
enum ApricornType apricornType;
};
const struct ApricornTree gApricornTrees[APRICORN_TREE_COUNT] =
{
#if APRICORN_TREE_COUNT > 0
[APRICORN_TREE_NONE] =
{
.minimum = 1,
.maximum = 1,
.apricornType = APRICORN_RED,
},
#endif
};

View File

@ -465,3 +465,5 @@ const u16 gObjectEventPal_StrangeBall[] = INCBIN_U16("graphics/object_events/pic
const u32 gFieldEffectObjectPic_CaveDust[] = INCBIN_U32("graphics/field_effects/pics/cave_dust.4bpp");
const u16 gFieldEffectObjectPalette_CaveDust[] = INCBIN_U16("graphics/field_effects/palettes/cave_dust.gbapal");
const u32 gObjectEventPic_ApricornTree[] = INCBIN_U32("graphics/object_events/pics/misc/apricorn_tree.4bpp");

View File

@ -4690,3 +4690,22 @@ const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_BallLight = {
.images = gFieldEffectObjectPicTable_BallLight,
.affineAnims = gDummySpriteAffineAnimTable,
};
const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_ApricornTree = {
.tileTag = TAG_NONE,
.paletteTag = OBJ_EVENT_PAL_TAG_NPC_3,
.reflectionPaletteTag = OBJ_EVENT_PAL_TAG_NONE,
.size = 128,
.width = 16,
.height = 16,
.paletteSlot = PALSLOT_NPC_3,
.shadowSize = SHADOW_SIZE_S,
.inanimate = TRUE,
.compressed = FALSE,
.tracks = TRACKS_NONE,
.oam = &gObjectEventBaseOam_16x16,
.subspriteTables = sOamTables_16x16,
.anims = sAnimTable_Inanimate,
.images = sPicTable_ApricornTree,
.affineAnims = gDummySpriteAffineAnimTable,
};

View File

@ -248,6 +248,7 @@ extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_Storyteller
extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_Giddy;
extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_UnusedMauvilleOldMan1;
extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_UnusedMauvilleOldMan2;
extern const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_ApricornTree;
const struct ObjectEventGraphicsInfo *const gObjectEventGraphicsInfoPointers[NUM_OBJ_EVENT_GFX] = {
[OBJ_EVENT_GFX_BRENDAN_NORMAL] = &gObjectEventGraphicsInfo_BrendanNormal,
@ -492,6 +493,7 @@ const struct ObjectEventGraphicsInfo *const gObjectEventGraphicsInfoPointers[NUM
[OBJ_EVENT_GFX_POKE_BALL] = &gObjectEventGraphicsInfo_PokeBall,
[OBJ_EVENT_GFX_OW_MON] = &gObjectEventGraphicsInfo_Follower,
[OBJ_EVENT_GFX_LIGHT_SPRITE] = &gObjectEventGraphicsInfo_BallLight,
[OBJ_EVENT_GFX_APRICORN_TREE] = &gObjectEventGraphicsInfo_ApricornTree,
};
const struct ObjectEventGraphicsInfo *const gMauvilleOldManGraphicsInfoPointers[] = {

View File

@ -1356,3 +1356,7 @@ static const struct SpriteFrameImage sPicTable_KirliaOld[] = {
static const struct SpriteFrameImage sPicTable_RubySapphireMay[] = {
overworld_ascending_frames(gObjectEventPic_RubySapphireMayNormal, 2, 4),
};
static const struct SpriteFrameImage sPicTable_ApricornTree[] = {
overworld_frame(gObjectEventPic_ApricornTree, 2, 2, 0),
};

View File

@ -11544,3 +11544,8 @@ bool8 MovementAction_SurfStillRight_Step1(struct ObjectEvent *objectEvent, struc
}
return FALSE;
}
u8 GetObjectEventApricornTreeId(u8 objectEventId)
{
return gObjectEvents[objectEventId].trainerRange_berryTreeId;
}