Improved follower message system.

This commit is contained in:
Ariel A 2024-02-11 17:51:36 -05:00
parent a407c72235
commit 915f40c654
3 changed files with 178 additions and 171 deletions

View File

@ -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,

View File

@ -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)) {

View File

@ -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)},
},
};