diff --git a/include/follower_helper.h b/include/follower_helper.h index d8ab0621b1..bfe19fb2d4 100644 --- a/include/follower_helper.h +++ b/include/follower_helper.h @@ -16,81 +16,67 @@ enum { FOLLOWER_EMOTION_LENGTH, }; -// This struct is optimized for size -// Each "section" can be used to combine multiple conditions, -// i.e, species and map -// Just set the flags accordingly and use the right union member -struct __attribute__((packed)) FollowerMsgInfoExtended { +// Can be either 3 bytes, a u16 and a byte, or a 24-bit value +union __attribute__((packed)) MsgConditionData { + u8 bytes[3]; + struct __attribute__((packed)) { + u16 hw; + u8 b; + } split; + u32 raw:24; +}; // size = 0x3 + +struct __attribute__((packed)) MsgCondition { + u32 type:8; + union MsgConditionData data; +}; // size = 0x4 + +struct FollowerMsgInfoExtended { const u8 *text; const u8 *script; - union __attribute__((packed)) { - u16 species:10; - struct __attribute__((packed)) { - u16 type1:5; - u16 type2:5; // if >= NUMBER_OF_MON_TYPES, inverts checking for type1 - } types; - u16 status:10; - } st; - u16 stFlags:2; // 0 = no matching, 1 = species matching, 2 = type matching, 3 = status - u16 emotion:4; // emotion for this message - - union __attribute__((packed)) { - struct __attribute__((packed)) { - u16 mapSec:8; - } mapSec; - struct __attribute__((packed)) { - u16 mapNum:8; - u16 mapGroup:6; - } map; - struct __attribute__((packed)) { - u16 behavior1:8; - u16 behavior2:6; // not full; only goes up to 0x3F - } mb; - } mm; - u16 mmFlags:2; // 1 = map sec, 2 = map, 3 = metatile behavior - - union __attribute__((packed)) { - struct __attribute__((packed)) { - u16 weather1:5; - u16 weather2:5; - } weather; - u16 song:10; - u16 timeOfDay:10; - } wt; - u16 wtFlags:2; // 1 = weather matching, 2 = song, 3 = time - u16 weight:3; + u32 emotion:4; + u32 weight:3; // if set, `text` is treated as an array of up to 4 texts instead - u16 textSpread:1; + // which one is displayed is chosen at random + u32 textSpread:1; + u32 orFlag:1; // if set, *any* condition can match, rather than all - union __attribute__((packed)) { - struct __attribute__((packed)) { - u16 behavior:8; - u16 distance:6; - } mb; - } near; - u16 nearFlags:2; // 1 = mb within '+'-shaped distance away -}; + struct MsgCondition conditions[5]; +}; // size = 8 + 4 + 5*4 = 32, 0x20 -enum { - ST_FLAGS_SPECIES = 1, - ST_FLAGS_TYPE, - ST_FLAGS_STATUS, -}; +// Follower message conditions +#define MSG_COND_NONE 0 +#define MSG_COND_SPECIES 1 +#define MSG_COND_TYPE 2 +#define MSG_COND_STATUS 3 +#define MSG_COND_MAPSEC 4 +#define MSG_COND_MAP 5 +#define MSG_COND_ON_MB 6 +#define MSG_COND_WEATHER 7 +#define MSG_COND_MUSIC 8 +#define MSG_COND_TIME_OF_DAY 9 +#define MSG_COND_NEAR_MB 10 -enum { - MM_FLAGS_MAPSEC = 1, - MM_FLAGS_MAP, - MM_FLAGS_MB, // (m)etatile (b)ehavior -}; +#define MATCH_U24(type, value) {type, {.raw = value}} +#define MATCH_U16(type, value1, value2) {type, {.split = {.hw = value1, .b = value2}}} +#define MATCH_U8(type, v1, v2, v3) {type, {.bytes = {v1, v2, v3}}} -enum { - WT_FLAGS_WEATHER = 1, - WT_FLAGS_MUSIC, - WT_FLAGS_TIME, -}; - -#define NEAR_FLAGS_MB 1 +#define MATCH_SPECIES(species) MATCH_U24(MSG_COND_SPECIES, species) +#define MATCH_TYPES(type1, type2) MATCH_U8(MSG_COND_TYPE, type1, type2, 0) +// Checks that follower has *neither* of the two types +#define MATCH_NOT_TYPES(type1, type2) MATCH_U8(MSG_COND_TYPE, type1, type2, TYPE_NONE) +#define MATCH_STATUS(status) MATCH_U24(MSG_COND_STATUS, status) +#define MATCH_MAPSEC(mapsec) MATCH_U24(MSG_COND_MAPSEC, mapsec) +#define MATCH_MAP_RAW(mapGroup, mapNum) MATCH_U8(MSG_COND_MAP, mapGroup, mapNum, 0) +#define MATCH_MAP(map) MATCH_U8(MSG_COND_MAP, MAP_GROUP(map), MAP_NUM(map), 0) +// Matches one of two metatile behaviors follower is standing on +#define MATCH_ON_MB(mb1, mb2) MATCH_U8(MSG_COND_ON_MB, mb1, mb2, 0) +#define MATCH_WEATHER(weather1, weather2) MATCH_U8(MSG_COND_WEATHER, weather1, weather2, 0) +#define MATCH_MUSIC(song) MATCH_U24(MSG_COND_MUSIC, song) +#define MATCH_TIME_OF_DAY(time) MATCH_U24(MSG_COND_TIME_OF_DAY, time) +// Matches metatile behavior within a '+' shape of size `distance` +#define MATCH_NEAR_MB(mb, distance) MATCH_U8(MSG_COND_NEAR_MB, mb, distance, 0) enum { COND_MSG_CELEBI, diff --git a/src/event_object_movement.c b/src/event_object_movement.c index a428dd2aed..de5a3c855a 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -2095,6 +2095,78 @@ static u32 FindMetatileBehaviorWithinRange(s32 x, s32 y, u32 mb, u8 distance) { return DIR_NONE; } +// Check a single follower message condition +bool32 CheckMsgCondition(const struct MsgCondition *cond, struct Pokemon *mon, u32 species, struct ObjectEvent *obj) { + u32 multi; + if (species == SPECIES_NONE) + species = GetMonData(mon, MON_DATA_SPECIES); + + switch (cond->type) + { + case MSG_COND_SPECIES: + return (cond->data.raw == species); + case MSG_COND_TYPE: + multi = (SpeciesHasType(species, cond->data.bytes[0]) || + SpeciesHasType(species, cond->data.bytes[1])); + // if bytes[2] == TYPE_NONE, + // invert; check that mon has *neither* type! + if (cond->data.bytes[2] == 0) + return multi; + else + return !multi; + break; + case MSG_COND_STATUS: + return (cond->data.raw & mon->status); + case MSG_COND_MAPSEC: + return (cond->data.raw == gMapHeader.regionMapSectionId); + case MSG_COND_MAP: + return (gSaveBlock1Ptr->location.mapGroup == cond->data.bytes[0] && + gSaveBlock1Ptr->location.mapNum == cond->data.bytes[1]); + case MSG_COND_ON_MB: + return (obj->currentMetatileBehavior == cond->data.bytes[0] || + obj->currentMetatileBehavior == cond->data.bytes[1]); + case MSG_COND_WEATHER: + multi = GetCurrentWeather(); + return (multi == cond->data.bytes[0] || multi == cond->data.bytes[1]); + case MSG_COND_MUSIC: + return (cond->data.raw == GetCurrentMapMusic()); + // Added on `lighting` branch + // case MSG_COND_TIME_OF_DAY: + // break; + case MSG_COND_NEAR_MB: + multi = FindMetatileBehaviorWithinRange( + obj->currentCoords.x, obj->currentCoords.y, + cond->data.bytes[0], cond->data.bytes[1]); + if (multi) + gSpecialVar_Result = multi; + return multi; + case MSG_COND_NONE: + // fallthrough + default: + return TRUE; + } +} + +// Check if follower info can be displayed in the current situation; +// i.e, if all its conditions match +bool32 CheckMsgInfo(const struct FollowerMsgInfoExtended *info, struct Pokemon *mon, u32 species, struct ObjectEvent *obj) { + u32 i; + + // any condition matches + if (info->orFlag) { + for (i = 0; i < ARRAY_COUNT(info->conditions) && info->conditions[i].type; i++) + if (CheckMsgCondition(&info->conditions[i], mon, species, obj)) + return TRUE; + return FALSE; + // all conditions must match + } else { + for (i = 0; i < ARRAY_COUNT(info->conditions) && info->conditions[i].type; i++) + if (!CheckMsgCondition(&info->conditions[i], mon, species, obj)) + return FALSE; + return TRUE; + } +} + // Call an applicable follower message script bool8 ScrFunc_getfolloweraction(struct ScriptContext *ctx) // Essentially a big switch for follower messages { @@ -2226,41 +2298,8 @@ bool8 ScrFunc_getfolloweraction(struct ScriptContext *ctx) // Essentially a big // (50% chance) Match *scripted* conditional messages, from follower_helper.c for (i = (Random() & 1) ? COND_MSG_COUNT : 0, j = 1; i < COND_MSG_COUNT; i++) { const struct FollowerMsgInfoExtended *info = &gFollowerConditionalMessages[i]; - if (info->stFlags == 1 && species != info->st.species) + if (!CheckMsgInfo(info, mon, species, objEvent)) continue; - if (info->stFlags == 2 && - (info->st.types.type2 >= NUMBER_OF_MON_TYPES ? - SpeciesHasType(species, info->st.types.type1) : - !(SpeciesHasType(species, info->st.types.type1) || SpeciesHasType(species, info->st.types.type2))) - ) - continue; - if (info->stFlags == 3 && !(mon->status & info->st.status)) - continue; - if (info->mmFlags == 1 && gMapHeader.regionMapSectionId != info->mm.mapSec.mapSec) - continue; - if (info->mmFlags == 2 && - !(gSaveBlock1Ptr->location.mapNum == info->mm.map.mapNum && - gSaveBlock1Ptr->location.mapGroup == info->mm.map.mapGroup) - ) - continue; - if (info->mmFlags == 3 && - !(objEvent->currentMetatileBehavior == info->mm.mb.behavior1 || - objEvent->currentMetatileBehavior == info->mm.mb.behavior2) - ) - continue; - if (info->wtFlags == 1 && - !(GetCurrentWeather() == info->wt.weather.weather1 || - GetCurrentWeather() == info->wt.weather.weather2) - ) - continue; - if (info->wtFlags == 2 && GetCurrentMapMusic() != info->wt.song) - continue; - if (info->nearFlags == 1) { - if ((multi2 = FindMetatileBehaviorWithinRange(objEvent->currentCoords.x, objEvent->currentCoords.y, info->near.mb.behavior, info->near.mb.distance))) - gSpecialVar_Result = multi2; - else - continue; - } // replace choice with weight/j chance if (Random() < (0x10000 / (j++)) * (info->weight ? info->weight : 1)) { diff --git a/src/follower_helper.c b/src/follower_helper.c index ace1eec45c..b2f44e7d1a 100644 --- a/src/follower_helper.c +++ b/src/follower_helper.c @@ -73,226 +73,208 @@ const struct FollowerMsgInfoExtended gFollowerConditionalMessages[COND_MSG_COUNT .text = (u8*)sCelebiTexts, .textSpread = 1, .script = EventScript_FollowerDance, - .st = {.species = SPECIES_CELEBI}, - .stFlags = ST_FLAGS_SPECIES, .emotion = FOLLOWER_EMOTION_NEUTRAL, + .conditions = {MATCH_SPECIES(SPECIES_CELEBI)}, }, [COND_MSG_FIRE] = { .text = (u8*)sFireTexts, - .st = {.types = {.type1 = TYPE_FIRE, .type2 = TYPE_FIRE}}, - .stFlags = ST_FLAGS_TYPE, - .emotion = FOLLOWER_EMOTION_NEUTRAL, .textSpread = 1, + .emotion = FOLLOWER_EMOTION_NEUTRAL, + .conditions = {MATCH_TYPES(TYPE_FIRE, TYPE_FIRE)}, }, [COND_MSG_EVER_GRANDE] = { .text = sCondMsg06, .script = EventScript_FollowerFaceUp, - .mm = {.map = {.mapNum = MAP_NUM(EVER_GRANDE_CITY), .mapGroup = MAP_GROUP(EVER_GRANDE_CITY)}}, - .mmFlags = MM_FLAGS_MAP, .emotion = FOLLOWER_EMOTION_HAPPY, + .conditions = {MATCH_MAP(EVER_GRANDE_CITY)}, }, [COND_MSG_ROUTE_112] = { .text = sCondMsg07, - .mm = {.map = {.mapNum = MAP_NUM(ROUTE112), .mapGroup = MAP_GROUP(ROUTE112)}}, - .mmFlags = MM_FLAGS_MAP, .emotion = FOLLOWER_EMOTION_HAPPY, + .conditions = {MATCH_MAP(ROUTE112)}, }, [COND_MSG_DAY_CARE] = { .text = sCondMsg08, .script = EventScript_FollowerNostalgia, - .mm = {.map = {.mapNum = MAP_NUM(ROUTE117_POKEMON_DAY_CARE), .mapGroup = MAP_GROUP(ROUTE117_POKEMON_DAY_CARE)}}, - .mmFlags = MM_FLAGS_MAP, .emotion = FOLLOWER_EMOTION_NEUTRAL, + .conditions = {MATCH_MAP(ROUTE117_POKEMON_DAY_CARE)}, }, [COND_MSG_MART] = { .text = (u8*)sShopTexts, .textSpread = 1, .script = EventScript_FollowerLookAround, - .wt = {.song = MUS_POKE_MART}, - .wtFlags = WT_FLAGS_MUSIC, .emotion = FOLLOWER_EMOTION_NEUTRAL, + .conditions = {MATCH_MUSIC(MUS_POKE_MART)}, }, [COND_MSG_VICTORY_ROAD] = { .text = sCondMsg11, - .wt = {.song = MUS_VICTORY_ROAD}, - .wtFlags = WT_FLAGS_MUSIC, .emotion = FOLLOWER_EMOTION_PENSIVE, + .conditions = {MATCH_MUSIC(MUS_VICTORY_ROAD)}, }, [COND_MSG_BIKE_SHOP] = { .text = sCondMsg12, - .mm = {.map = {.mapNum = MAP_NUM(MAUVILLE_CITY_BIKE_SHOP), .mapGroup = MAP_GROUP(MAUVILLE_CITY_BIKE_SHOP)}}, - .mmFlags = MM_FLAGS_MAP, .emotion = FOLLOWER_EMOTION_PENSIVE, + .conditions = {MATCH_MAP(MAUVILLE_CITY_BIKE_SHOP)}, }, [COND_MSG_MACHINES] = { .text = (u8*)sMachineTexts, - .mm = {.map = {.mapNum = MAP_NUM(NEW_MAUVILLE_INSIDE), .mapGroup = MAP_GROUP(NEW_MAUVILLE_INSIDE)}}, - .mmFlags = MM_FLAGS_MAP, - .emotion = FOLLOWER_EMOTION_MUSIC, .textSpread = 1, + .emotion = FOLLOWER_EMOTION_MUSIC, + .orFlag = 1, // match any of these maps + .conditions = { + MATCH_MAP(NEW_MAUVILLE_INSIDE), + MATCH_MAP(SLATEPORT_CITY_STERNS_SHIPYARD_1F), + MATCH_MAP(SLATEPORT_CITY_STERNS_SHIPYARD_2F), + } }, [COND_MSG_SAILING] = { .text = (u8*)sBoatTexts, - .script = EventScript_FollowerLookAround, - .wt = {.song = MUS_SAILING}, - .wtFlags = WT_FLAGS_MUSIC, - .emotion = FOLLOWER_EMOTION_MUSIC, .textSpread = 1, + .emotion = FOLLOWER_EMOTION_MUSIC, + .script = EventScript_FollowerLookAround, + .conditions = {MATCH_MUSIC(MUS_SAILING)}, }, [COND_MSG_PUDDLE] = { .text = sCondMsg18, .script = EventScript_FollowerHopping, - .mm = {.mb = {.behavior1 = MB_SHALLOW_WATER, .behavior2 = MB_PUDDLE}}, - .mmFlags = MM_FLAGS_MB, .emotion = FOLLOWER_EMOTION_MUSIC, + .conditions = {MATCH_ON_MB(MB_SHALLOW_WATER, MB_PUDDLE)}, }, [COND_MSG_SAND] = { .text = sCondMsg19, - .mm = {.mb = {.behavior1 = MB_SAND, .behavior2 = MB_DEEP_SAND}}, - .mmFlags = MM_FLAGS_MB, .emotion = FOLLOWER_EMOTION_MUSIC, + .conditions = {MATCH_ON_MB(MB_SAND, MB_DEEP_SAND)}, }, [COND_MSG_GRASS] = { .text = sCondMsg20, - .mm = {.mb = {.behavior1 = MB_TALL_GRASS, .behavior2 = MB_LONG_GRASS}}, - .mmFlags = MM_FLAGS_MB, .emotion = FOLLOWER_EMOTION_MUSIC, + .conditions = {MATCH_ON_MB(MB_TALL_GRASS, MB_LONG_GRASS)}, }, [COND_MSG_FOOTPRINTS] = { .text = sCondMsg21, - .mm = {.mb = {.behavior1 = MB_SAND, .behavior2 = MB_FOOTPRINTS}}, - .mmFlags = MM_FLAGS_MB, .emotion = FOLLOWER_EMOTION_MUSIC, + .conditions = {MATCH_ON_MB(MB_SAND, MB_FOOTPRINTS)}, }, [COND_MSG_ELEVATOR] = { .text = (u8*)sElevatorTexts, .textSpread = 1, - .mm = {.map = {.mapNum = MAP_NUM(LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR), .mapGroup = MAP_GROUP(LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR)}}, - .mmFlags = MM_FLAGS_MAP, .emotion = FOLLOWER_EMOTION_SURPRISE, + .conditions = {MATCH_MAP(LILYCOVE_CITY_DEPARTMENT_STORE_ELEVATOR)}, }, [COND_MSG_ICE_ROOM] = { .text = (u8*)sColdTexts, .textSpread = 1, - .mm = {.map = {.mapNum = MAP_NUM(SHOAL_CAVE_LOW_TIDE_ICE_ROOM), .mapGroup = MAP_GROUP(SHOAL_CAVE_LOW_TIDE_ICE_ROOM)}}, - .mmFlags = MM_FLAGS_MAP, .emotion = FOLLOWER_EMOTION_SURPRISE, + .conditions = {MATCH_MAP(SHOAL_CAVE_LOW_TIDE_ICE_ROOM)}, }, [COND_MSG_ROUTE_117] = { .text = sCondMsg27, - .mm = {.map = {.mapNum = MAP_NUM(ROUTE117), .mapGroup = MAP_GROUP(ROUTE117)}}, - .mmFlags = MM_FLAGS_MAP, .emotion = FOLLOWER_EMOTION_SURPRISE, + .conditions = {MATCH_MAP(ROUTE117)}, }, [COND_MSG_DRAGON_GROWL] = { .text = sCondMsg28, - .st = {.types = {.type1 = TYPE_DRAGON, .type2 = TYPE_DRAGON}}, - .stFlags = ST_FLAGS_TYPE, - .mm = {.mapSec = {.mapSec = MAPSEC_SKY_PILLAR}}, - .mmFlags = MM_FLAGS_MAPSEC, .emotion = FOLLOWER_EMOTION_UPSET, + .conditions = { + MATCH_TYPES(TYPE_DRAGON, TYPE_DRAGON), + MATCH_MAPSEC(MAPSEC_SKY_PILLAR), + } }, [COND_MSG_FEAR] = { .text = (u8*)sFearTexts, .textSpread = 1, - .st = {.types = {.type1 = TYPE_GHOST, .type2 = TYPE_NOT_TYPE1}}, - .stFlags = ST_FLAGS_TYPE, - .mm = {.mapSec = {.mapSec = MAPSEC_MT_PYRE}}, - .mmFlags = MM_FLAGS_MAPSEC, - .wt = {.song = MUS_MT_PYRE}, - .wtFlags = WT_FLAGS_MUSIC, .emotion = FOLLOWER_EMOTION_UPSET, + .conditions = { + MATCH_NOT_TYPES(TYPE_GHOST, TYPE_GHOST), + MATCH_MAPSEC(MAPSEC_MT_PYRE), + MATCH_MUSIC(MUS_MT_PYRE), + } }, [COND_MSG_FIRE_RAIN] = { .text = sCondMsg31, - .st = {.types = {.type1 = TYPE_FIRE, .type2 = TYPE_FIRE}}, - .stFlags = ST_FLAGS_TYPE, - .wt = {.weather = {.weather1 = WEATHER_RAIN, .weather2 = WEATHER_RAIN_THUNDERSTORM}}, - .wtFlags = WT_FLAGS_WEATHER, .emotion = FOLLOWER_EMOTION_UPSET, + .conditions = { + MATCH_TYPES(TYPE_FIRE, TYPE_FIRE), + MATCH_WEATHER(WEATHER_RAIN, WEATHER_RAIN_THUNDERSTORM), + } }, [COND_MSG_FROZEN] = { .text = sCondMsg32, - .st = {.status = STATUS1_FREEZE}, - .stFlags = ST_FLAGS_STATUS, .emotion = FOLLOWER_EMOTION_UPSET, + .conditions = { + MATCH_STATUS(STATUS1_FREEZE), + } }, [COND_MSG_SEASIDE] = { .text = (u8*)sSeaTexts, .textSpread = 1, .script = EventScript_FollowerFaceResult, - .near = {.mb = {.behavior = MB_OCEAN_WATER, .distance = 5}}, - .nearFlags = NEAR_FLAGS_MB, .emotion = FOLLOWER_EMOTION_MUSIC, + .conditions = {MATCH_NEAR_MB(MB_OCEAN_WATER, 5)}, }, [COND_MSG_WATERFALL] = { .text = sCondMsg36, .script = EventScript_FollowerFaceResult, - .near = {.mb = {.behavior = MB_WATERFALL, .distance = 5}}, - .nearFlags = NEAR_FLAGS_MB, .emotion = FOLLOWER_EMOTION_MUSIC, + .conditions = {MATCH_NEAR_MB(MB_WATERFALL, 5)}, }, [COND_MSG_RAIN] = { .text = sCondMsg37, - .st = {.types = {.type1 = TYPE_FIRE, .type2 = TYPE_NOT_TYPE1}}, - .stFlags = ST_FLAGS_TYPE, - .wt = {.weather = {.weather1 = WEATHER_RAIN, .weather2 = WEATHER_RAIN_THUNDERSTORM}}, - .wtFlags = WT_FLAGS_WEATHER, .emotion = FOLLOWER_EMOTION_MUSIC, + .conditions = { + MATCH_NOT_TYPES(TYPE_FIRE, TYPE_FIRE), + MATCH_WEATHER(WEATHER_RAIN, WEATHER_RAIN_THUNDERSTORM) + } }, [COND_MSG_REFLECTION] = { .text = sCondMsg38, .script = EventScript_FollowerFaceResult, - .near = {.mb = {.behavior = MB_POND_WATER, .distance = 1}}, - .nearFlags = NEAR_FLAGS_MB, .emotion = FOLLOWER_EMOTION_PENSIVE, + .conditions = {MATCH_NEAR_MB(MB_POND_WATER, 1)}, }, [COND_MSG_LEAVES] = { .text = sCondMsg39, - .mm = {.mapSec = {.mapSec = MAPSEC_PETALBURG_WOODS}}, - .mmFlags = MM_FLAGS_MAPSEC, .emotion = FOLLOWER_EMOTION_PENSIVE, + .conditions = {MATCH_MAPSEC(MAPSEC_PETALBURG_WOODS)}, }, [COND_MSG_ICE] = { .text = (u8*)sIceTexts, .textSpread = 1, .script = EventScript_FollowerFaceResult, - .near = {.mb = {.behavior = MB_ICE, .distance = 1}}, - .nearFlags = NEAR_FLAGS_MB, .emotion = FOLLOWER_EMOTION_PENSIVE, + .conditions = {MATCH_NEAR_MB(MB_ICE, 1)}, }, [COND_MSG_BURN] = { .text = sCondMsg42, - .st = {.status = STATUS1_BURN}, - .stFlags = ST_FLAGS_STATUS, .emotion = FOLLOWER_EMOTION_SAD, + .conditions = {MATCH_STATUS(STATUS1_BURN)}, }, };