diff --git a/config/rel_slices.yml b/config/rel_slices.yml index 26ab6766..a6913fed 100644 --- a/config/rel_slices.yml +++ b/config/rel_slices.yml @@ -182,6 +182,11 @@ m_needlework.c: .text: [0x803C98EC, 0x803C9F7C] .data: [0x8065ABC0, 0x8065AE30] .bss: [0x81298F60, 0x81299180] +m_npc.c: + .text: [0x803CB340, 0x803D7570] + .rodata: [0x806429B8, 0x806429F0] + .data: [0x8065AEE8, 0x8065B638] + .bss: [0x81299240, 0x8129CC48] m_npc_schedule.c: .text: [0x803D7570, 0x803D7890] .data: [0x8065B638, 0x8065B7F0] diff --git a/include/ac_my_room.h b/include/ac_my_room.h index b6e499db..74a9018e 100644 --- a/include/ac_my_room.h +++ b/include/ac_my_room.h @@ -4,6 +4,7 @@ #include "types.h" #include "m_actor.h" #include "ac_furniture.h" +#include "m_room_type.h" #ifdef __cplusplus extern "C" { @@ -67,6 +68,10 @@ typedef struct my_room_clip_s { extern ACTOR_PROFILE My_Room_Profile; +extern int aMR_CorrespondFurniture(mActor_name_t ftr0, mActor_name_t ftr1); +extern int aMR_GetFurnitureUnit(mActor_name_t ftr); +extern mActor_name_t aMR_FurnitureFg_to_FurnitureFgWithDirect(mActor_name_t ftr, int direct); + #ifdef __cplusplus } #endif diff --git a/include/m_collision_bg.h b/include/m_collision_bg.h index 6baef4e0..6082cb49 100644 --- a/include/m_collision_bg.h +++ b/include/m_collision_bg.h @@ -39,8 +39,11 @@ enum background_attribute { mCoBG_ATTRIBUTE_SOIL0, mCoBG_ATTRIBUTE_SOIL1, mCoBG_ATTRIBUTE_SOIL2, - mCoBG_ATTRIBUTE_BUSH = 9, - mCoBG_ATTRIBUTE_WAVE = 11, + mCoBG_ATTRIBUTE_STONE, + mCoBG_ATTRIBUTE_FLOOR, + mCoBG_ATTRIBUTE_BUSH, + mCoBG_ATTRIBUTE_HOLE, + mCoBG_ATTRIBUTE_WAVE, mCoBG_ATTRIBUTE_WATER, mCoBG_ATTRIBUTE_WATERFALL, mCoBG_ATTRIBUTE_RIVER_N, @@ -54,6 +57,7 @@ enum background_attribute { mCoBG_ATTRIBUTE_SAND, mCoBG_ATTRIBUTE_WOOD, mCoBG_ATTRIBUTE_SEA, + // ... }; enum { @@ -193,6 +197,8 @@ extern int mCoBG_CheckAttribute_BallRolling(s16* angles, const xyz_t* wpos); extern f32 mCoBG_CheckBallRollingArea(s16 angle, const xyz_t* wpos); extern int mCoBG_ExistHeightGap_KeepAndNow_Detail(xyz_t wpos); extern int mCoBG_GetHoleNumber(xyz_t wpos); +extern int mCoBG_Attr2CheckPlaceNpc(u32 attribute); +extern int mCoBG_ExistHeightGap_KeepAndNow(xyz_t wpos); extern void mCoBG_InitMoveBgData(); extern void mCoBG_InitBlockBgCheckMode(); diff --git a/include/m_common_data.h b/include/m_common_data.h index b02f334f..91d1eb56 100644 --- a/include/m_common_data.h +++ b/include/m_common_data.h @@ -79,7 +79,7 @@ typedef struct lighthouse_s { typedef struct Save_s { /* 0x000000 */ mFRm_chk_t save_check; /* save information */ /* 0x000014 */ int scene_no; /* current 'scene' id */ - /* 0x000018 */ u8 now_npc_max; /* current number of villagers living in town (see mNpc_(Add/Sub)NowNpcMax) */ + /* 0x000018 */ u8 now_npc_max; /* current number of villagers living in town (see (Add/Sub)NowNpcMax) */ /* 0x000019 */ u8 remove_animal_idx; /* index of the villager which is scheduled to leave town, 0xFF when none selected */ /* 0x00001A */ u16 copy_protect; /* 'unique' value between [1, 65520] used for copy protection (see mCD_get_land_copyProtect) */ /* 0x00001C */ u8 pad_1C[4]; @@ -197,8 +197,7 @@ typedef struct common_data_s { /* 0x026150 */ s16 bg_item_type; /* 0x026152 */ s16 bg_item_profile; /* 0x026154 */ u8 _26154[0x26164 - 0x26154]; - /* 0x026164 */ mNpc_NpcList_c npclist[ANIMAL_NUM_MAX]; - /* 0x0264AC */ mNpc_NpcList_c unk_264AC; // fits exact size of npc list struct, seems unused + /* 0x026164 */ mNpc_NpcList_c npclist[ANIMAL_NUM_MAX + 1]; /* 0x0264E4 */ mNpc_NpcList_c island_npclist[1]; // TODO: define for island npc count /* 0x02651C */ mActor_name_t house_owner_name; /* 0x02651E */ mActor_name_t last_field_id; @@ -216,7 +215,8 @@ typedef struct common_data_s { /* 0x0266A4 */ int scene_from_title_demo; /* next scene to be loaded when title demo finishes */ /* 0x0266A8 */ mNPS_schedule_c npc_schedule[SCHEDULE_NUM]; /* 0x0267A8 */ mNpc_walk_c npc_walk; - /* 0x026838 */ u8 _26838[0x28528 - 0x26838]; + /* 0x026838 */ mNpc_EventNpc_c event_npc[mNpc_EVENT_NPC_NUM]; + /* 0x026878 */ mNpc_MaskNpc_c mask_npc[mNpc_MASK_NPC_NUM]; /* 0x028528 */ int snowman_msg_id; /* 0x02852C */ s16 money_power; /* 0x02852E */ s16 goods_power; diff --git a/include/m_event_map_npc.h b/include/m_event_map_npc.h index dea307a3..7b9f2017 100644 --- a/include/m_event_map_npc.h +++ b/include/m_event_map_npc.h @@ -10,6 +10,8 @@ extern "C" { #endif extern int mEvMN_GetJointEventRandomNpc(mActor_name_t* selected_npc_id); +extern int mEvNM_CheckJointEvent(); +extern void mEvMN_GetEventNpcName(mActor_name_t* npc_name, int event, int idx, int param_4); #ifdef __cplusplus } diff --git a/include/m_handbill.h b/include/m_handbill.h index 2c323a62..9940377d 100644 --- a/include/m_handbill.h +++ b/include/m_handbill.h @@ -94,6 +94,8 @@ typedef struct mHandbillz_info_s { int header_back_start; } mHandbillz_Info_c; +extern int mHandbillz_load(mHandbillz_Info_c* info); + #ifdef __cplusplus } #endif diff --git a/include/m_mail.h b/include/m_mail.h index 3fa21a0c..66564849 100644 --- a/include/m_mail.h +++ b/include/m_mail.h @@ -48,8 +48,8 @@ enum { }; enum { - mMl_TYPE_MUSEUM = 0, - mMl_TYPE_1 = 1, + mMl_TYPE_MAIL = 0, + mMl_TYPE_XMAS = 1, mMl_TYPE_SHOP_SALE_LEAFLET = 2, mMl_TYPE_BROKER_SALE_LEAFLET = 3, @@ -57,6 +57,7 @@ enum { mMl_TYPE_FISHING_CONTENST = 9, + mMl_TYPE_SPNPC_PASSWORD = 11, mMl_TYPE_12 = 12 }; diff --git a/include/m_mail_check.h b/include/m_mail_check.h index 2d548a0c..a37ba4ae 100644 --- a/include/m_mail_check.h +++ b/include/m_mail_check.h @@ -2,12 +2,13 @@ #define M_MAIL_CHECK_H #include "types.h" +#include "game_h.h" #ifdef __cplusplus extern "C" { #endif -extern int mMC_get_mail_hit_rate(int* len, u8* str); +extern int mMC_get_mail_hit_rate(int* len, u8* str, GAME* game); #ifdef __cplusplus } diff --git a/include/m_name_table.h b/include/m_name_table.h index c99d0f85..bcf3b0d9 100644 --- a/include/m_name_table.h +++ b/include/m_name_table.h @@ -61,6 +61,37 @@ enum { mNT_TREE_TYPE_NUM }; +/* TODO: these should be calculated via definitions later */ +#define NPC_NUM 236 +#define NPC_ISLANDER_NUM 18 + +#define FTR_NUM 1266 +#define PAPER_NUM 256 +#define MONEY_NUM 4 +#define TOOL_NUM 92 +#define FISH_NUM 40 +#define CLOTH_NUM 255 +#define ETC_NUM 49 +#define CARPET_NUM 67 +#define WALL_NUM 67 +#define FRUIT_NUM 8 +#define PLANT_NUM 11 +#define MINIDISK_NUM 55 +#define DIARY_NUM 16 +#define TICKET_NUM 96 +#define INSECT_NUM 40 + 5 // 5 spirits +#define HUKUBUKURO_NUM 2 +#define KABU_NUM 4 + +#define PAPER_UNIQUE_NUM 64 +#define PAINT_NUM 12 +#define FLOWER_NUM 9 +#define HANIWA_NUM 127 +#define NOT_SECRET_MD_NUM 52 +#define UMBRELLA_NUM 32 + +extern u8 npc_looks_table[]; + extern int mNT_check_unknown(mActor_name_t item_no); extern int FGTreeType_check(mActor_name_t tree); extern mActor_name_t bg_item_fg_sub_tree_grow(mActor_name_t tree, int past_days, int check_plant); @@ -149,32 +180,6 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define BG_CATEGORY 0 #define ENV_CATEGORY 8 -/* TODO: these should be calculated via definitions later */ -#define FTR_NUM 1266 -#define PAPER_NUM 256 -#define MONEY_NUM 4 -#define TOOL_NUM 92 -#define FISH_NUM 40 -#define CLOTH_NUM 255 -#define ETC_NUM 49 -#define CARPET_NUM 67 -#define WALL_NUM 67 -#define FRUIT_NUM 8 -#define PLANT_NUM 11 -#define MINIDISK_NUM 55 -#define DIARY_NUM 16 -#define TICKET_NUM 96 -#define INSECT_NUM 40 + 5 // 5 spirits -#define HUKUBUKURO_NUM 2 -#define KABU_NUM 4 - -#define PAPER_UNIQUE_NUM 64 -#define PAINT_NUM 12 -#define FLOWER_NUM 9 -#define HANIWA_NUM 127 -#define NOT_SECRET_MD_NUM 52 -#define UMBRELLA_NUM 32 - #define EMPTY_NO 0x0000 #define TREE_STUMP001 (EMPTY_NO + 1) #define TREE_STUMP002 (EMPTY_NO + 2) @@ -483,7 +488,8 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); // #define HANIWA_END 0x17AB -#define FTR_CLOTH_MANNIQUIN000_SOUTH 0x17AC +#define FTR_CLOTH_START 0x17AC +#define FTR_CLOTH_MANNIQUIN000_SOUTH FTR_CLOTH_START #define FTR_REDALOHASHIRT 0x1814 @@ -491,27 +497,33 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define FTR_CLOTH_MANNIQUIN254_SOUTH 0x1BA4 #define FTR_CLOTH_MANNIQUIN254_WEST 0x1BA7 +#define FTR_CLOTH_END FTR_CLOTH_MANNIQUIN254_WEST #define FTR_CLOTH_MANNIQUIN_MY_ORIGINAL0 0x1BA8 -#define FTR_INSECT00 0x1BC8 +#define FTR_INSECT_START 0x1BC8 +#define FTR_INSECT00 FTR_INSECT_START #define FTR_INSECT39 0x1C64 #define FTR_INSECT39_EAST 0x1C65 #define FTR_INSECT39_NORTH 0x1C66 #define FTR_INSECT39_WEST 0x1C67 - -#define FTR_FISH00 0x1C68 +#define FTR_INSECT_END FTR_INSECT39_WEST +#define FTR_FISH_START 0x1C68 +#define FTR_FISH00 FTR_FISH_START #define FTR_FISH39 0x1D04 #define FTR_FISH39_EAST 0x1D05 #define FTR_FISH39_NORTH 0x1D06 #define FTR_FISH39_WEST 0x1D07 - -#define FTR_UMBRELLA00_SOUTH 0x1D08 +#define FTR_FISH_END FTR_FISH39_WEST +#define FTR_UMBRELLA_START 0x1D08 +#define FTR_UMBRELLA00_SOUTH FTR_UMBRELLA_START #define FTR_UMBRELLA31_WEST 0x1D87 +#define FTR_UMBRELLA_END FTR_UMBRELLA31_WEST -#define FTR_FAMICOM_CLU_CLU_LAND 0x1DA8 +#define FTR_FAMICOM_START 0x1DA8 +#define FTR_FAMICOM_CLU_CLU_LAND FTR_FAMICOM_START #define FTR_FAMICOM_BALLOON_FIGHT 0x1DAC #define FTR_FAMICOM_DONKEY_KONG 0x1DB0 #define FTR_FAMICOM_DK_JR_MATCH 0x1DB4 @@ -530,6 +542,7 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define FTR_FAMICOM_MARIO_BROS 0x1DE8 #define FTR_FAMICOM_SUPER_MARIO_BROS 0x1DEC #define FTR_FAMICOM_LEGEND_OF_ZELDA 0x1DF0 +#define FTR_FAMICOM_END 0x1DF3 #define FTR_FAMICOM 0x1DF4 #define FTR_TAPEDECK 0x1E58 @@ -538,11 +551,13 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define FTR_FESTIVE_TREE 0x1EBC -#define FTR_DINO_TRICERA_SKULL 0x1EEC +#define FTR_DINO_START 0x1EEC +#define FTR_DINO_TRICERA_SKULL FTR_DINO_START #define FTR_DINO_TREX_SKULL 0x1EF8 #define FTR_DINO_TRILOBITE_WEST 0x1F4F +#define FTR_DINO_END FTR_DINO_TRILOBITE_WEST #define FTR_DINO_DISP_TRICERA 0x1F7C #define FTR_DINO_DISP_TREX 0x1F80 @@ -1111,6 +1126,62 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define ITM_YELLOW_TULIP_BAG 0x290A #define ITM_MINIDISK_START 0x2A00 +#define ITM_MINIDISK00 (ITM_MINIDISK_START + 0) +#define ITM_MINIDISK01 (ITM_MINIDISK_START + 1) +#define ITM_MINIDISK02 (ITM_MINIDISK_START + 2) +#define ITM_MINIDISK03 (ITM_MINIDISK_START + 3) +#define ITM_MINIDISK04 (ITM_MINIDISK_START + 4) +#define ITM_MINIDISK05 (ITM_MINIDISK_START + 5) +#define ITM_MINIDISK06 (ITM_MINIDISK_START + 6) +#define ITM_MINIDISK07 (ITM_MINIDISK_START + 7) +#define ITM_MINIDISK08 (ITM_MINIDISK_START + 8) +#define ITM_MINIDISK09 (ITM_MINIDISK_START + 9) +#define ITM_MINIDISK10 (ITM_MINIDISK_START + 10) +#define ITM_MINIDISK11 (ITM_MINIDISK_START + 11) +#define ITM_MINIDISK12 (ITM_MINIDISK_START + 12) +#define ITM_MINIDISK13 (ITM_MINIDISK_START + 13) +#define ITM_MINIDISK14 (ITM_MINIDISK_START + 14) +#define ITM_MINIDISK15 (ITM_MINIDISK_START + 15) +#define ITM_MINIDISK16 (ITM_MINIDISK_START + 16) +#define ITM_MINIDISK17 (ITM_MINIDISK_START + 17) +#define ITM_MINIDISK18 (ITM_MINIDISK_START + 18) +#define ITM_MINIDISK19 (ITM_MINIDISK_START + 19) +#define ITM_MINIDISK20 (ITM_MINIDISK_START + 20) +#define ITM_MINIDISK21 (ITM_MINIDISK_START + 21) +#define ITM_MINIDISK22 (ITM_MINIDISK_START + 22) +#define ITM_MINIDISK23 (ITM_MINIDISK_START + 23) +#define ITM_MINIDISK24 (ITM_MINIDISK_START + 24) +#define ITM_MINIDISK25 (ITM_MINIDISK_START + 25) +#define ITM_MINIDISK26 (ITM_MINIDISK_START + 26) +#define ITM_MINIDISK27 (ITM_MINIDISK_START + 27) +#define ITM_MINIDISK28 (ITM_MINIDISK_START + 28) +#define ITM_MINIDISK29 (ITM_MINIDISK_START + 29) +#define ITM_MINIDISK30 (ITM_MINIDISK_START + 30) +#define ITM_MINIDISK31 (ITM_MINIDISK_START + 31) +#define ITM_MINIDISK32 (ITM_MINIDISK_START + 32) +#define ITM_MINIDISK33 (ITM_MINIDISK_START + 33) +#define ITM_MINIDISK34 (ITM_MINIDISK_START + 34) +#define ITM_MINIDISK35 (ITM_MINIDISK_START + 35) +#define ITM_MINIDISK36 (ITM_MINIDISK_START + 36) +#define ITM_MINIDISK37 (ITM_MINIDISK_START + 37) +#define ITM_MINIDISK38 (ITM_MINIDISK_START + 38) +#define ITM_MINIDISK39 (ITM_MINIDISK_START + 39) +#define ITM_MINIDISK40 (ITM_MINIDISK_START + 40) +#define ITM_MINIDISK41 (ITM_MINIDISK_START + 41) +#define ITM_MINIDISK42 (ITM_MINIDISK_START + 42) +#define ITM_MINIDISK43 (ITM_MINIDISK_START + 43) +#define ITM_MINIDISK44 (ITM_MINIDISK_START + 44) +#define ITM_MINIDISK45 (ITM_MINIDISK_START + 45) +#define ITM_MINIDISK46 (ITM_MINIDISK_START + 46) +#define ITM_MINIDISK47 (ITM_MINIDISK_START + 47) +#define ITM_MINIDISK48 (ITM_MINIDISK_START + 48) +#define ITM_MINIDISK49 (ITM_MINIDISK_START + 49) +#define ITM_MINIDISK50 (ITM_MINIDISK_START + 50) +#define ITM_MINIDISK51 (ITM_MINIDISK_START + 51) +#define ITM_MINIDISK52 (ITM_MINIDISK_START + 52) +#define ITM_MINIDISK53 (ITM_MINIDISK_START + 53) +#define ITM_MINIDISK54 (ITM_MINIDISK_START + 54) +#define ITM_MINIDISK_END (ITM_MINIDISK_START + 55) #define ITM_DIARY_START 0x2B00 #define ITM_DIARY00 (ITM_DIARY_START + 0) @@ -1380,6 +1451,8 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define ETC_AIRPLANE ETC_START #define ETC_BOXTRICK (ETC_START + 4) #define ETC_BOXMANAGER (ETC_START + 5) +#define ETC_SNOWMAN_BALL_A (ETC_START + 13) +#define ETC_SNOWMAN_BALL_B (ETC_START + 14) #define ETC_TRAIN_WINDOW (ETC_START + 17) #define MISC_ACTOR_START 0x9000 @@ -1428,27 +1501,85 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define SP_NPC_POST_GIRL2 (SP_NPC_START + 18) // D012 #define SP_NPC_EV_BROKER2 (SP_NPC_START + 19) // D013 #define SP_NPC_RCN_GUIDE (SP_NPC_START + 20) // D014 -// +#define SP_NPC_RCN_GUIDE_1 (SP_NPC_START + 21) // D015 +#define SP_NPC_RCN_GUIDE_2 (SP_NPC_START + 22) // D016 +#define SP_NPC_RCN_GUIDE_3 (SP_NPC_START + 23) // D017 #define SP_NPC_P_SEL (SP_NPC_START + 24) // D018 #define SP_NPC_RCN_GUIDE2 (SP_NPC_START + 25) // D019 -// +#define SP_NPC_RCN_GUIDE2_1 (SP_NPC_START + 26) // D01A +#define SP_NPC_RCN_GUIDE2_2 (SP_NPC_START + 27) // D01B +#define SP_NPC_RCN_GUIDE2_3 (SP_NPC_START + 28) // D01C #define SP_NPC_ANGLER (SP_NPC_START + 29) // D01D #define SP_NPC_SHOP_MASTERSP (SP_NPC_START + 30) // D01E #define SP_NPC_P_SEL2 (SP_NPC_START + 31) // D01F -// +#define SP_NPC_EV_HALLOWEEN_0 (SP_NPC_START + 32) // D020 +#define SP_NPC_EV_HALLOWEEN_1 (SP_NPC_START + 33) // D021 +#define SP_NPC_EV_HALLOWEEN_2 (SP_NPC_START + 34) // D022 +#define SP_NPC_EV_HALLOWEEN_3 (SP_NPC_START + 35) // D023 +#define SP_NPC_EV_HALLOWEEN_4 (SP_NPC_START + 36) // D024 #define SP_NPC_HALLOWEEN (SP_NPC_START + 37) // D025 #define SP_NPC_MAMEDANUKI0 (SP_NPC_START + 38) // D026 -// +#define SP_NPC_EV_HANABI_0 (SP_NPC_START + 39) // D027 +#define SP_NPC_EV_HANABI_1 (SP_NPC_START + 40) // D028 +#define SP_NPC_EV_HANABI_2 (SP_NPC_START + 41) // D029 +#define SP_NPC_EV_HANABI_3 (SP_NPC_START + 42) // D02A +#define SP_NPC_EV_HANABI_4 (SP_NPC_START + 43) // D02B #define SP_NPC_EV_YOMISE (SP_NPC_START + 44) // D02C -// +#define SP_NPC_EV_TOKYOSO_0 (SP_NPC_START + 45) // D02D +#define SP_NPC_EV_TOKYOSO_1 (SP_NPC_START + 46) // D02E +#define SP_NPC_EV_TOKYOSO_2 (SP_NPC_START + 47) // D02F +#define SP_NPC_EV_TOKYOSO_3 (SP_NPC_START + 48) // D030 +#define SP_NPC_EV_TOKYOSO_4 (SP_NPC_START + 49) // D031 +#define SP_NPC_EV_HANAMI_0 (SP_NPC_START + 50) // D032 +#define SP_NPC_EV_HANAMI_1 (SP_NPC_START + 51) // D033 +#define SP_NPC_EV_HANAMI_2 (SP_NPC_START + 52) // D034 +#define SP_NPC_EV_HANAMI_3 (SP_NPC_START + 53) // D035 +#define SP_NPC_EV_HANAMI_4 (SP_NPC_START + 54) // D036 #define SP_NPC_MAMEDANUKI1 (SP_NPC_START + 55) // D037 #define SP_NPC_SLEEP_OBABA (SP_NPC_START + 56) // D038 -// +#define SP_NPC_EV_YOMISE2 (SP_NPC_START + 57) // D039 +#define SP_NPC_SHOP_MASTERSP_2 (SP_NPC_START + 58) // D03A +#define SP_NPC_SHOP_MASTERSP_3 (SP_NPC_START + 59) // D03B +#define SP_NPC_SHOP_MASTERSP_4 (SP_NPC_START + 60) // D03C #define SP_NPC_EV_MIKO (SP_NPC_START + 61) // D03D #define SP_NPC_MAJIN (SP_NPC_START + 62) // D03E -// +#define SP_NPC_EV_TUKIMI_0 (SP_NPC_START + 63) // D03F +#define SP_NPC_EV_TUKIMI_1 (SP_NPC_START + 64) // D040 +#define SP_NPC_EV_TUKIMI_2 (SP_NPC_START + 65) // D041 +#define SP_NPC_EV_TUKIMI_3 (SP_NPC_START + 66) // D042 +#define SP_NPC_EV_TUKIMI_4 (SP_NPC_START + 67) // D043 +#define SP_NPC_EV_COUNTDOWN_0 (SP_NPC_START + 68) // D044 +#define SP_NPC_EV_COUNTDOWN_1 (SP_NPC_START + 69) // D045 +#define SP_NPC_EV_COUNTDOWN_2 (SP_NPC_START + 70) // D046 +#define SP_NPC_EV_COUNTDOWN_3 (SP_NPC_START + 71) // D047 +#define SP_NPC_EV_COUNTDOWN_4 (SP_NPC_START + 72) // D048 +#define SP_NPC_EV_TURI_0 (SP_NPC_START + 73) // D049 +#define SP_NPC_EV_TURI_1 (SP_NPC_START + 74) // D04A +#define SP_NPC_EV_TURI_2 (SP_NPC_START + 75) // D04B +#define SP_NPC_EV_TURI_3 (SP_NPC_START + 76) // D04C +#define SP_NPC_EV_TURI_4 (SP_NPC_START + 77) // D04D +#define SP_NPC_EV_TAISOU_0 (SP_NPC_START + 78) // D04E +#define SP_NPC_EV_TAISOU_1 (SP_NPC_START + 79) // D04F +#define SP_NPC_EV_TAISOU_2 (SP_NPC_START + 80) // D050 +#define SP_NPC_EV_TAISOU_3 (SP_NPC_START + 81) // D051 +#define SP_NPC_EV_TAISOU_4 (SP_NPC_START + 82) // D052 +#define SP_NPC_EV_TAMAIRE_0 (SP_NPC_START + 83) // D053 +#define SP_NPC_EV_TAMAIRE_1 (SP_NPC_START + 84) // D054 +#define SP_NPC_EV_TAMAIRE_2 (SP_NPC_START + 85) // D055 +#define SP_NPC_EV_TAMAIRE_3 (SP_NPC_START + 86) // D056 +#define SP_NPC_EV_TAMAIRE_4 (SP_NPC_START + 87) // D057 +#define SP_NPC_EV_HATUMODE_0 (SP_NPC_START + 88) // D058 +#define SP_NPC_EV_HATUMODE_1 (SP_NPC_START + 89) // D059 +#define SP_NPC_EV_HATUMODE_2 (SP_NPC_START + 90) // D05A +#define SP_NPC_EV_HATUMODE_3 (SP_NPC_START + 91) // D05B +#define SP_NPC_EV_HATUMODE_4 (SP_NPC_START + 92) // D05C #define SP_NPC_TOTAKEKE (SP_NPC_START + 93) // D05D -// +#define SP_NPC_EV_KAMAKURA_0 (SP_NPC_START + 94) // D05E +#define SP_NPC_EV_TUNAHIKI_0 (SP_NPC_START + 95) // D05F +#define SP_NPC_EV_TUNAHIKI_1 (SP_NPC_START + 96) // D060 +#define SP_NPC_EV_TUNAHIKI_2 (SP_NPC_START + 97) // D061 +#define SP_NPC_EV_TUNAHIKI_3 (SP_NPC_START + 98) // D062 +#define SP_NPC_EV_TUNAHIKI_4 (SP_NPC_START + 99) // D063 #define SP_NPC_EV_DOZAEMON (SP_NPC_START + 100) // D064 #define SP_NPC_MAJIN2 (SP_NPC_START + 101) // D065 #define SP_NPC_RTC (SP_NPC_START + 102) // D066 @@ -1469,15 +1600,27 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define SP_NPC_MASK_CAT (SP_NPC_START + 117) // D075 #define SP_NPC_MASK_CAT2 (SP_NPC_START + 118) // D076 // +#define SP_NPC_SONCHO_D078 (SP_NPC_START + 120) // D078 +#define SP_NPC_SONCHO_D079 (SP_NPC_START + 121) // D079 #define SP_NPC_SASHO (SP_NPC_START + 122) // D07A // +#define SP_NPC_MAJIN_D07C (SP_NPC_START + 124) // D07C +#define SP_NPC_MAJIN_D07D (SP_NPC_START + 125) // D07D #define SP_NPC_MAJIN_BROTHER (SP_NPC_START + 126) // D07E #define SP_NPC_SONCHO (SP_NPC_START + 127) // D07F -// +#define SP_NPC_MAJIN_D080 (SP_NPC_START + 128) // D080 #define SP_NPC_EV_MAJIN (SP_NPC_START + 129) // D081 -// +#define SP_NPC_EV_HARVEST_0 (SP_NPC_START + 130) // D082 +#define SP_NPC_EV_HARVEST_1 (SP_NPC_START + 131) // D083 +#define SP_NPC_EV_HARVEST_2 (SP_NPC_START + 132) // D084 +#define SP_NPC_EV_HARVEST_3 (SP_NPC_START + 133) // D085 +#define SP_NPC_EV_HARVEST_4 (SP_NPC_START + 134) // D086 #define SP_NPC_EV_SPEECH_SONCHO (SP_NPC_START + 135) // D087 -// +#define SP_NPC_EV_GROUNDHOG_0 (SP_NPC_START + 136) // D088 +#define SP_NPC_EV_GROUNDHOG_1 (SP_NPC_START + 137) // D089 +#define SP_NPC_EV_GROUNDHOG_2 (SP_NPC_START + 138) // D08A +#define SP_NPC_EV_GROUNDHOG_3 (SP_NPC_START + 139) // D08B +#define SP_NPC_EV_GROUNDHOG_4 (SP_NPC_START + 140) // D08C #define SP_NPC_TURKEY (SP_NPC_START + 141) // D08D #define SP_NPC_HEM (SP_NPC_START + 142) // D08E @@ -1500,6 +1643,7 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define NPC_BIFF 0xE0C2 #define DUMMY_START 0xF000 +#define DUMMY_RESERVE 0xF0EE // unsure about this, only true in DnM #define DUMMY_HANIWA0 0xF0FB #define DUMMY_HANIWA1 (DUMMY_HANIWA0 + 1) #define DUMMY_HANIWA2 (DUMMY_HANIWA1 + 1) @@ -1511,6 +1655,22 @@ extern mActor_name_t bg_item_fg_sub_dig2take_conv(mActor_name_t item); #define RSV_FE1C 0xFE1C #define RSV_FE1F 0xFE1F #define RSV_CLOTH 0xFE20 +#define RSV_ISLAND_FTR0 0xFEB3 /* mRmTp_FTRSIZE_1x1 S */ +#define RSV_ISLAND_FTR1 0xFEB4 /* mRmTp_FTRSIZE_1x1 E */ +#define RSV_ISLAND_FTR2 0xFEB5 /* mRmTp_FTRSIZE_1x1 N */ +#define RSV_ISLAND_FTR3 0xFEB6 /* mRmTp_FTRSIZE_1x1 W */ +#define RSV_ISLAND_FTR4 0xFEB7 /* mRmTp_FTRSIZE_1x1 S */ +#define RSV_ISLAND_FTR5 0xFEB8 /* mRmTp_FTRSIZE_1x1 E */ +#define RSV_ISLAND_FTR6 0xFEB9 /* mRmTp_FTRSIZE_1x1 N */ +#define RSV_ISLAND_FTR7 0xFEBA /* mRmTp_FTRSIZE_1x1 W */ +#define RSV_ISLAND_FTR8 0xFEBB /* mRmTp_FTRSIZE_1x2 S */ +#define RSV_ISLAND_FTR9 0xFEBC /* mRmTp_FTRSIZE_1x2 E */ +#define RSV_ISLAND_FTR10 0xFEBD /* mRmTp_FTRSIZE_1x2 N */ +#define RSV_ISLAND_FTR11 0xFEBE /* mRmTp_FTRSIZE_1x2 W */ +#define RSV_ISLAND_FTR12 0xFEBF /* mRmTp_FTRSIZE_2x2 S */ +#define RSV_ISLAND_FTR13 0xFEC0 /* mRmTp_FTRSIZE_2x2 E */ +#define RSV_ISLAND_FTR14 0xFEC1 /* mRmTp_FTRSIZE_2x2 N */ +#define RSV_ISLAND_FTR15 0xFEC2 /* mRmTp_FTRSIZE_2x2 W */ #define RSV_WALL_NO 0xFFFE /* interior wall item, no collision */ #define RSV_NO 0xFFFF /* reserved space, can't interact but no collision */ diff --git a/include/m_npc.h b/include/m_npc.h index 0d07c0e6..8512135d 100644 --- a/include/m_npc.h +++ b/include/m_npc.h @@ -13,6 +13,7 @@ #include "m_lib.h" #include "m_private_h.h" #include "m_field_make.h" +#include "m_name_table.h" #ifdef __cplusplus extern "C" { @@ -22,15 +23,18 @@ extern "C" { #define mNpc_GET_TYPE(npc_id) ((npc_id) & 0xF000) #define mNpc_IS_SPECIAL(npc_id) (mNpc_GET_TYPE(npc_id) == 0xD000) -#define NPC_NUM 236 - +#define ANIMAL_NUM_MIN 5 #define ANIMAL_NUM_MAX 15 /* Maximum number of villagers possible in town */ #define ANIMAL_MEMORY_NUM 7 #define ANIMAL_CATCHPHRASE_LEN 10 -#define ANIMAL_HP_MAIL_NUM 4 +#define ANIMAL_HP_MAIL_NUM PLAYER_NUM #define ANIMAL_NAME_LEN PLAYER_NAME_LEN +#define mNpc_MINIMUM_DAYS_BEFORE_FORCE_REMOVAL 10 #define mNpc_ISLAND_FTR_SAVE_NUM 4 +#define mNpc_ISLAND_FTR_NUM 16 +#define mNpc_EVENT_NPC_NUM 5 +#define mNpc_MASK_NPC_NUM 3 enum { mNpc_MOOD_0, @@ -46,13 +50,69 @@ enum { mNpc_MOOD_NUM }; +enum { + mNpc_LETTER_RANK_BAD, + mNpc_LETTER_RANK_OK, + + mNpc_LETTER_RANK_NUM +}; + +enum { + mNpc_EVENT_MAIL_VT_DAY, /* valentine's day */ + mNpc_EVENT_MAIL_WT_DAY, /* white day (JP only) */ + + mNpc_EVENT_MAIL_NUM +}; + +enum { + mNpc_EVENT_MAIL_BEST_FRIEND, + mNpc_EVENT_MAIL_OK_FRIEND, + mNpc_EVENT_MAIL_NOT_FRIEND, + + mNpc_EVENT_MAIL_FRIEND_NUM +}; + +enum { + mNpc_GROW_STARTER, + mNpc_GROW_MOVE_IN, + mNpc_GROW_ISLANDER, + + mNpc_GROW_NUM +}; + +enum { + mNpc_NAME_TYPE_SPNPC, + mNpc_NAME_TYPE_NPC, + + mNpc_NAME_TYPE_NUM +}; + +enum { + mNpc_FEEL_NORMAL, + mNpc_FEEL_HAPPY, + mNpc_FEEL_ANGRY, + mNpc_FEEL_SAD, + mNpc_FEEL_SLEEPY, + mNpc_FEEL_PITFALL, + + mNpc_FEEL_NUM +}; + +enum { + mNpc_PATIENCE_MILDLY_ANNOYED, + mNpc_PATIENCE_ANNOYED, + mNpc_PATIENCE_NORMAL, + + mNpc_PATIENCE_NUM +}; + /* sizeof(Anmremail_c) == 0x16 */ typedef struct animal_remail_s { lbRTC_ymd_c date; /* date sent */ u8 name[ANIMAL_NAME_LEN]; /* villager name */ u8 land_name[LAND_NAME_SIZE]; /* town name */ struct { - u8 password_letter:1; /* is mail normal or password */ + u8 cond:1; /* was letter good or not */ u8 looks:7; /* personality */ } flags; } Anmremail_c; @@ -82,7 +142,7 @@ typedef struct animal_home_s { /* sizeof(Anmlet_c) == 1 */ typedef struct animal_letter_info_s { u8 exists:1; /* letter received by villager and exists */ - u8 password_letter:1; /* set when the letter contains a 'key' symbol and is considered a password letter */ + u8 cond:1; /* mNpc_LETTER_RANK_* */ u8 send_reply:1; /* set when the villager should reply */ u8 has_present_cloth:1; /* set when the villager's held present shirt is from this letter */ u8 wearing_present_cloth:1; /* set when a villager is wearing the shirt sent with the saved letter */ @@ -95,10 +155,15 @@ typedef struct animal_land_mem_s { /* 0x08 */ u16 id; } Anmlnd_c; -/* sizeof(memuni_U) == 0x12 */ +typedef struct island_animal_best_ftr_s { + u32 check; + u16 have_bitfield; +} Anm_bestFtr_c; + +/* sizeof(memuni_u) == 0xC */ typedef union { Anmlnd_c land; /* size = 0xA */ - u32 check; /* size = 4 */ + Anm_bestFtr_c island; /* size = 6 */ } memuni_u; /* sizeof(Anmmem_c) == 0x138 */ @@ -141,7 +206,7 @@ typedef struct animal_s { /* 0x8E8 */ u8 is_home; /* TRUE when the villager is home, otherwise FALSE */ /* 0x8E9 */ u8 moved_in; /* TRUE when the villager moved in after town creation, FALSE if they started out in town */ /* 0x8EA */ u8 removing; /* TRUE when the villager is leaving town, FALSE otherwise */ - /* 0x8EB */ s8 cloth_original_id; /* 0xFF when not wearing an Able Sister's pattern, otherwise 0-3 indicating which pattern */ + /* 0x8EB */ u8 cloth_original_id; /* 0xFF when not wearing an Able Sister's pattern, otherwise 0-3 indicating which pattern */ /* 0x8EC */ s8 umbrella_id; /* 0xFF when no umbrella, 0-31 when a standard umbrella, 32-35 when using an Able Sister's pattern /* 0x8ED */ u8 unk_8ED; /* Exists according to mISL_gc_to_agb_animal, but seems unused in practice */ /* 0x8EE */ mActor_name_t present_cloth; /* The most recently received shirt from a letter which the villager may change into */ @@ -181,7 +246,9 @@ typedef struct { typedef struct npc_conversation_s { u8 beesting:1; // talk to player about their beesting - u8 unk:7; + u8 fish_complete:1; // talk to the player about catching all fish + u8 insect_complete:1; // talk to the player about catching all insects + u8 unk:5; } mNpc_NpcConversation_c; typedef struct npc_list_s { @@ -215,56 +282,204 @@ typedef struct mask_npc_s { Animal_c animal_data; /* animal data is copied if the mask npc is a standard villager NPC */ } mNpc_MaskNpc_c; -/* anm_id could also just be a Animal_c pointer */ -extern void mNpc_GetNpcWorldNameAnm(u8* name, AnmPersonalID_c* anm_id); -extern int mNpc_CheckFreeAnimalPersonalID(AnmPersonalID_c* anm_id); -extern void mNpc_SetAnimalTitleDemo(mNpc_demo_npc_c* demo_npc, Animal_c* animal, GAME* game); -extern void mNpc_SetNpcList(mNpc_NpcList_c* npc_list, Animal_c* animal, int max, int unused); -extern void mNpc_ClearCacheName(); -extern void mNpc_ClearInAnimal(); -extern void mNpc_FirstClearGoodbyMail(); -extern void mNpc_ClearIslandNpcRoomData(); -extern void mNpc_CopyAnimalPersonalID(AnmPersonalID_c* dst, AnmPersonalID_c* src); -extern AnmPersonalID_c* mNpc_GetOtherAnimalPersonalID(AnmPersonalID_c* ids, int num_ids); -extern void mNpc_ClearEventNpc(); -extern void mNpc_ClearMaskNpc(); -extern int mNpc_SearchAnimalinfo(Animal_c* animal, mActor_name_t npc_name, int count); -extern int mNpc_RegistEventNpc(mActor_name_t actor_name, mActor_name_t tex_name, mActor_name_t npc_name, mActor_name_t cloth_name); -extern void mNpc_ClearAnimalInfo(Animal_c* animal_p); -extern Animal_c* mNpc_GetInAnimalP(); -extern int mNpc_GetLooks2Sex(int looks); -extern int mNpc_CheckFreeAnimalInfo(Animal_c* animal); -extern int mNpc_GetFriendAnimalNum(Private_c* private_p); -extern int mNpc_GetLooks(mActor_name_t npc_name); -extern void mNpc_LoadNpcNameString(u8* buf, u8 idx); -extern u8 mNpc_GetPaperType(); -extern void mMl_set_mail_name_npcinfo(Mail_nm_c* mail_name, AnmPersonalID_c* anm_pid); -extern int mNpc_ReceiveHPMail(Mail_c* hp_mail); -extern void mNpc_SendMailtoNpc(Mail_c* mail); -extern void mNpc_SetNpcinfo(ACTOR* actor, s8 npc_info_idx); -extern void mNpc_InitNpcAllInfo(int malloc_flag); -extern void mNpc_SetRemoveAnimalNo(u8* remove_animal_no, Animal_c* animals, int remove_no); -extern void mNpc_ClearAnimalPersonalID(AnmPersonalID_c* id); -extern int mNpc_CheckCmpAnimalPersonalID(AnmPersonalID_c* id0, AnmPersonalID_c* id1); -extern int mNpc_SearchAnimalPersonalID(AnmPersonalID_c* id); -extern void mNpc_GetActorWorldName(u8* buf, mActor_name_t id); -extern u8 mNpc_CheckNormalMail_length(int* len, u8* body); -extern mActor_name_t mNpc_GetNpcFurniture(AnmPersonalID_c* pid); -extern void mNpc_GetNpcWorldNameTableNo(u8* buf, mActor_name_t name_id); -extern void mNpc_GetRandomAnimalName(u8* buf); -extern int mNpc_GetIslandRoomFtrNum(); -extern mActor_name_t* mNpc_GetIslandRoomP(mActor_name_t npc_name); -extern void mNpc_ChangeIslandRoom(mActor_name_t* items); -extern void mNpc_SetNpcFurnitureRandom(mFM_fg_data_c** fg_data_list, int fg_name_start); -extern void mNpc_SetNpcHomeYpos(); -extern void mNpc_SendRegisteredGoodbyMail(); -extern void mNpc_IslandNpcRoomDataSet(mFM_fg_data_c** sorted_fg_data_list, int fg_name_start); -extern Animal_c* mNpc_GetAnimalInfoP(mActor_name_t npc_name); -extern int mNpc_RegistMaskNpc(mActor_name_t mask_id, mActor_name_t npc_id, mActor_name_t cloth); -extern int mNpc_CheckNpcSet(int bx, int bz, int ut_x, int ut_z); -extern int mNpc_GetMakeUtNuminBlock_hard_area(int* ut_x, int* ut_z, int bx, int bz, int start_ut); +typedef struct npc_default_data_s { + mActor_name_t cloth; + u16 catchphrase_str_idx; + s8 umbrella; +} mNpc_Default_Data_c; +extern void mNpc_AddNowNpcMax(u8* npc_max); +extern void mNpc_SubNowNpcMax(u8* npc_max); +extern void mNpc_ClearAnimalPersonalID(AnmPersonalID_c* pid); +extern int mNpc_CheckFreeAnimalPersonalID(AnmPersonalID_c* pid); +extern void mNpc_CopyAnimalPersonalID(AnmPersonalID_c* dst, AnmPersonalID_c* src); +extern int mNpc_CheckCmpAnimalPersonalID(AnmPersonalID_c* pid0, AnmPersonalID_c* pid1); +extern int mNpc_GetAnimalNum(); +extern int mNpc_CheckRemoveExp(Animal_c* animal); +extern int mNpc_GetRemoveTime(Animal_c* animal); +extern void mNpc_AddRemoveTime(Animal_c* animal); +extern void mNpc_SetRemoveExp(Animal_c* animal, u16 remove_exp); +extern void mNpc_SetRemoveExp(Animal_c* animal, u16 remove_exp); +extern void mNpc_SetParentName(Animal_c* animal, PersonalID_c* parent_id); +extern void mNpc_SetParentNameAllAnimal(); +extern void mNpc_ClearAnimalMail(Anmplmail_c* mail); +extern void mNpc_CopyAnimalMail(Anmplmail_c* dst, Anmplmail_c* src); +extern void mNpc_ClearAnimalMemory(Anmmem_c* memory, int num); +extern void mNpc_ClearIslandAnimalMemory(Anmmem_c* memory, int num); +extern void mNpc_CopyAnimalMemory(Anmmem_c* dst, Anmmem_c* src); +extern void mNpc_AddFriendship(Anmmem_c* memory, int amount); +extern int mNpc_CheckFreeAnimalMemory(Anmmem_c* memory); +extern void mNpc_RenewalAnimalMemory(); +extern int mNpc_GetOldAnimalMemoryIdx(Anmmem_c* memory, int num); +extern int mNpc_GetFreeAnimalMemoryIdx(Anmmem_c* memory, int num); +extern int mNpc_GetOldPlayerAnimalMemoryIdx(Anmmem_c* memory, int num); +extern int mNpc_ForceGetFreeAnimalMemoryIdx(Animal_c* animal, Anmmem_c* memory, int num); +extern void mNpc_SetAnimalMemory(PersonalID_c* pid, AnmPersonalID_c* anm_id, Anmmem_c* memory); +extern int mNpc_GetAnimalMemoryIdx(PersonalID_c* pid, Anmmem_c* memory, int num); +extern void mNpc_SetAnimalLastTalk(Animal_c* animal); +extern void mNpc_SetAnimalPersonalID2Memory(AnmPersonalID_c* anm_id); +extern int mNpc_GetHighestFriendshipIdx(Anmmem_c* memory, int num); +extern int mNpc_GetAnimalMemoryBestFriend(Anmmem_c* memory, int num); +extern int mNpc_GetAnimalMemoryNum(Anmmem_c* memory, int count); +extern int mNpc_GetAnimalMemoryLetterNum(Anmmem_c* memory, int count); +extern int mNpc_GetAnimalMemoryLandKindNum(Anmmem_c* memory, int count); +extern void mNpc_ClearAnimalInfo(Animal_c* animal); +extern void mNpc_ClearIslandAnimalInfo(Animal_c* animal); +extern void mNpc_ClearAnyAnimalInfo(Animal_c* animal, int count); +extern int mNpc_CheckFreeAnimalInfo(Animal_c* animal); +extern int mNpc_GetFreeAnimalInfo(Animal_c* animal, int count); +extern int mNpc_UseFreeAnimalInfo(Animal_c* animal, int count); +extern void mNpc_CopyAnimalInfo(Animal_c* dst, Animal_c* src); +extern int mNpc_SearchAnimalinfo(Animal_c* animal, mActor_name_t npc_id, int count); +extern Animal_c* mNpc_GetAnimalInfoP(mActor_name_t npc_id); +extern int mNpc_SearchAnimalPersonalID(AnmPersonalID_c* anm_pid); +extern AnmPersonalID_c* mNpc_GetOtherAnimalPersonalIDOtherBlock(AnmPersonalID_c* pids, int count, int bx, int bz, int check_flag); +extern AnmPersonalID_c* mNpc_GetOtherAnimalPersonalID(AnmPersonalID_c* pids, int count); +extern void mNpc_SetAnimalThisLand(Animal_c* animal, int count); +extern int mNpc_GetSameLooksNum(u8 looks); +extern int mNpc_CheckNpcExistBlock(int idx, int check_bx, int check_bz); +extern void mNpc_Mail2AnimalMail(Anmplmail_c* animal_mail, Mail_c* mail); +extern void mNpc_AnimalMail2Mail(Mail_c* mail, Anmplmail_c* animal_mail, PersonalID_c* pid, AnmPersonalID_c* anm_id); +extern int mNpc_CheckNormalMail_sub(int* char_num, u8* body); +extern u8 mNpc_CheckNormalMail_length(int* len, u8* body); +extern u8 mNpc_CheckNormalMail_nes(u8* body); +extern int mNpc_SendMailtoNpc(Mail_c* mail); +extern void mNpc_ClearRemail(Anmremail_c* remail); +extern void mNpc_Remail(); +extern u8 mNpc_GetPaperType(); +extern int mNpc_SendVtdayMail(); +extern int mNpc_CheckFriendship(PersonalID_c* pid, Animal_c* animal); +extern int mNpc_SendEventBirthdayCard(PersonalID_c* pid); +extern int mNpc_SendEventBirthdayCard2(PersonalID_c* pid, int player_no); +extern int mNpc_SendEventXmasCard(PersonalID_c* pid, int player_no); +extern int mNpc_GetPresentClothMemoryIdx(Anmmem_c* memory); +extern int mNpc_GetPresentClothMemoryIdx_rnd(Anmmem_c* memory); +extern int mNpc_CheckTalkPresentCloth(Animal_c* animal); +extern void mNpc_ChangePresentCloth(); +extern u8* mNpc_GetWordEnding(ACTOR* actor); +extern void mNpc_ResetWordEnding(ACTOR* actor); +extern int mNpc_GetFreeEventNpcIdx(); +extern int mNpc_RegistEventNpc(mActor_name_t event_id, mActor_name_t texture_id, mActor_name_t npc_id, mActor_name_t cloth_id); +extern void mNpc_UnRegistEventNpc(mNpc_EventNpc_c* npc); +extern void mNpc_ClearEventNpc(); +extern mNpc_EventNpc_c* mNpc_GetSameEventNpc(mActor_name_t event_id); +extern int mNpc_GetFreeMaskNpcIdx(); +extern int mNpc_RegistMaskNpc(mActor_name_t mask_id, mActor_name_t npc_id, mActor_name_t cloth_id); +extern void mNpc_UnRegistMaskNpc(mNpc_MaskNpc_c* npc); +extern void mNpc_ClearMaskNpc(); +extern mNpc_MaskNpc_c* mNpc_GetSameMaskNpc(mActor_name_t mask_id); +extern u8 mNpc_GetLooks(mActor_name_t npc_id); +extern void mNpc_SetDefAnimalCloth(Animal_c* animal); +extern void mNpc_SetDefAnimalUmbrella(Animal_c* animal); +extern void mNpc_SetDefAnimal(Animal_c* animal, mActor_name_t npc_id, mNpc_Default_Data_c* def_data); +extern void mNpc_SetAnimalTitleDemo(mNpc_demo_npc_c* demo_npc, Animal_c* animal, GAME* game); +extern int mNpc_GetReservedUtNum(int* ut_x, int* ut_z, mActor_name_t* item); +extern int mNpc_BlockNum2ReservedUtNum(int* ut_x, int* ut_z, int bx, int bz); +extern void mNpc_MakeReservedListBeforeFieldct(Anmhome_c* reserved, int reserved_num, u8* reserved_count); +extern void mNpc_MakeReservedListAfterFieldct(Anmhome_c* reserved, int reserved_num, u8* reserved_count, u8 bx_max, u8 bz_max); +extern mNpc_InitNpcData(); +extern void mNpc_InitNpcList(mNpc_NpcList_c* npclist, int count); +extern void mNpc_SetNpcList(mNpc_NpcList_c* npclist, Animal_c* animal, int count, int malloc_flag); +extern void mNpc_SetNpcinfo(ACTOR* actor, s8 npc_info_idx); +extern void mNpc_AddNpc_inBlock(mFM_move_actor_c* move_actor_list, u8 bx, u8 bz); +extern void mNpc_RenewalNpcRoom(s16* wall_floor); +extern void mNpc_RenewalSetNpc(ACTOR* actor); +extern int mNpc_GetFriendAnimalNum(PersonalID_c* pid); +extern int mNpc_CheckFriendAllAnimal(PersonalID_c* pid); +extern void mNpc_SetNpcFurnitureRandom(mFM_fg_data_c** fg_data_table, int fg_base_id); +extern mActor_name_t mNpc_GetNpcFurniture(AnmPersonalID_c* anm_id); +extern void mNpc_ClearInAnimal(); +extern Animal_c* mNpc_GetInAnimalP(); +extern void mNpc_SetRemoveAnimalNo(u8* remove_animal_no, Animal_c* animal, int ignored_idx); +extern int mNpc_GetGoodbyAnimalIdx(int ignored_idx); +extern void mNpc_FirstClearGoodbyMail(); +extern void mNpc_SendRegisteredGoodbyMail(); +extern void mNpc_GetRemoveAnimal(Animal_c* transferring_animal, int moving_out); +extern void mNpc_SetReturnAnimal(Animal_c* return_animal); +extern void mNpc_AddActor_inBlock(mFM_move_actor_c* move_actor_list, u8 bx, u8 bz); +extern void mNpc_LoadNpcNameString(u8* dst, u8 name_id); +extern void mNpc_GetNpcWorldNameTableNo(u8* dst, mActor_name_t npc_id); +extern void mNpc_ClearCacheName(); +extern void mNpc_GetNpcWorldNameAnm(u8* dst, AnmPersonalID_c* anm_id); +extern void mNpc_GetActorWorldName(u8* dst, mActor_name_t npc_id); +extern u8* mNpc_GetNpcWorldNameP(mActor_name_t npc_id); +extern void mNpc_GetNpcWorldName(u8* dst, ACTOR* actor); +extern void mNpc_GetRandomAnimalName(u8* dst); +extern void mNpc_GetAnimalPlateName(u8* dst, xyz_t wpos); +extern int mNpc_GetNpcLooks(ACTOR* actor); +extern int mNpc_GetLooks2Sex(int looks); +extern int mNpc_GetAnimalSex(Animal_c* animal); +extern int mNpc_GetNpcSex(ACTOR* actor); +extern int mNpc_GetNpcSoundSpec(ACTOR* actor); +extern void mNpc_InitNpcAllInfo(int malloc_flag); +extern void mNpc_Grow(); +extern void mNpc_ForceRemove(); +extern int mNpc_DecideMaskNpc_summercamp(mActor_name_t* npc_id); +extern int mNpc_RegistMaskNpc_summercamp(mActor_name_t mask_id, mActor_name_t npc_id, mActor_name_t cloth_id); +extern int mNpc_CheckNpcSet_fgcol(mActor_name_t fg_item, u32 attribute); +extern int mNpc_CheckNpcSet(int bx, int bz, int ut_x, int ut_z); +extern int mNpc_GetMakeUtNuminBlock_hard_area(int* ut_x, int* ut_z, int bx, int bz, int restrict_area); +extern int mNpc_GetMakeUtNuminBlock_area(int* ut_x, int* ut_z, int bx, int bz, int restrict_area); +extern int mNpc_GetMakeUtNuminBlock(int* ut_x, int* ut_z, int bx, int bz); +extern int mNpc_GetMakeUtNuminBlock33(int* make_ut_x, int* make_ut_z, int ut_x, int ut_z, int bx, int bz); +extern int mNpc_GetMakeUtNuminBlock_hide_hard_area(int* ut_x, int* ut_z, int bx, int bz, int restrict_area); +extern void mNpc_ClearTalkInfo(); +extern int mNpc_CheckOverImpatient(int animal_idx, int feel); +extern int mNpc_GetOverImpatient(int animal_idx, int feel); +extern int mNpc_CheckQuestRequest(int animal_idx); +extern void mNpc_SetQuestRequestOFF(int animal_idx, int feel); +extern void mNpc_TalkInfoMove(); +extern void mNpc_TalkEndMove(int animal_idx, int feel); +extern int mNpc_GetNpcFloorNo(); +extern int mNpc_GetNpcWallNo(); +extern void mNpc_SetTalkBee(); +extern u8 mNpc_GetFishCompleteTalk(mNpc_NpcList_c* npclist); +extern u8 mNpc_GetInsectCompleteTalk(mNpc_NpcList_c* npclist); +extern void mNpc_SetFishCompleteTalk(mNpc_NpcList_c* npclist); +extern void mNpc_SetInsectCompleteTalk(mNpc_NpcList_c* npclist); +extern void mNpc_SetNpcHomeYpos(); +extern void mNpc_DecideIslandNpc(Animal_c* animal); +extern void mNpc_SetIslandRoomFtr(Animal_c* animal); +extern void mNpc_SetIslandGetFtr(mActor_name_t ftr); +extern void mNpc_SetIslandGetFtrtoRoom(); +extern void mNpc_SetIslandGetLetter(int get); +extern int mNpc_GetIslandGetLetter(); +extern void mNpc_SetIslandCheckFtrMsg(int set); +extern int mNpc_GetIslandCheckFtrMsg(); +extern void mNpc_ClearIslandNpcRoomData(); +extern void mNpc_IslandNpcRoomDataSet(mFM_fg_data_c** data_table, int base_idx); +extern mActor_name_t* mNpc_GetIslandRoomP(mActor_name_t npc_id); +extern void mNpc_GetIslandWallFloorIdx(int* wall, int* floor, mActor_name_t npc_id); +extern int mNpc_CheckIslandNpcRoomFtrItemNo_keep(mActor_name_t ftr); +extern void mNpc_ChangeIslandRoom(mActor_name_t* items); +extern int mNpc_CheckFtrIsIslandBestFtr(mActor_name_t ftr); +extern int mNpc_CheckFtrIsIslandNormalFtr(mActor_name_t ftr); +extern int mNpc_SetIslandFtr(PersonalID_c* pid, mActor_name_t ftr); +extern int mNpc_EraseIslandFtr(mActor_name_t ftr); +extern int mNpc_EraseIslandFtr_keep(mActor_name_t ftr); +extern void mNpc_ClearIslandPresentFtrInfo(); +extern void mNpc_SetIslandPresentFtr(); +extern void mNpc_RestoreIslandPresentFtr(); +extern int mNpc_GetIslandRoomFtrNum(); +extern int mNpc_CheckIslandPresentFtrIs(); +extern mActor_name_t mNpc_GetIslandPresentFtr(); +extern PersonalID_c* mNpc_GetIslandPresentFtrPersonalID(); +extern mActor_name_t mNpc_GetRandomBestFtr(); +extern Anmmem_c* mNpc_GetOtherBestFtr(PersonalID_c* pid, mActor_name_t* other_best_ftr, mActor_name_t exist_ftr); +extern mActor_name_t mNpc_GetPlayerBestFtr(PersonalID_c* pid, mActor_name_t exist_ftr); +extern mActor_name_t mNpc_GetPlayerFtr(PersonalID_c* pid); +extern int mNpc_CheckIslandAnimal(Animal_c* animal); +extern u32 mNpc_GetMDIdx(mActor_name_t npc_id); +extern u32 mNpc_GetIslandMDIdx(); +extern void mNpc_ClearHPMail(AnmHPMail_c* hp_mail, int count); +extern void mNpc_AllClearHPMailPlayerIdx(int player_no); +extern int mNpc_ReceiveHPMail(Mail_c* mail); +extern void mNpc_SendHPMail(); extern void mNpc_PrintRemoveInfo(gfxprint_t* gfxprint); +extern void mNpc_set_addd_bit(int bit); +extern void mNpc_set_addd_edit_bit(int bit); +extern void mNpc_set_addd_edit_info(int mtype, int disp_add); +extern void mNpc_SetTalkAnimalIdx_fdebug(AnmPersonalID_c* anm_id); extern void mNpc_PrintFriendship_fdebug(gfxprint_t* gfxprint); #ifdef __cplusplus diff --git a/include/m_personal_id.h b/include/m_personal_id.h index 0eafd880..14e2a19c 100644 --- a/include/m_personal_id.h +++ b/include/m_personal_id.h @@ -10,6 +10,10 @@ extern "C" { #endif +#define PLAYER_NUM 4 +#define FOREIGNER_NUM 1 +#define TOTAL_PLAYER_NUM (PLAYER_NUM + FOREIGNER_NUM) + #define PLAYER_NAME_LEN 8 /* sizeof(PersonalID_c) == 0x14 */ diff --git a/include/m_private.h b/include/m_private.h index e9bf6054..151d68e6 100644 --- a/include/m_private.h +++ b/include/m_private.h @@ -15,10 +15,6 @@ extern "C" { #endif -#define PLAYER_NUM 4 -#define FOREIGNER_NUM 1 -#define TOTAL_PLAYER_NUM (PLAYER_NUM + FOREIGNER_NUM) - #define mPr_WALLET_MAX 99999 #define mPr_DEPOSIT_MAX 999999999 @@ -56,8 +52,9 @@ enum { enum { mPr_SEX_MALE, mPr_SEX_FEMALE, + mPr_SEX_OTHER, - mPr_SEX_NUM + mPr_SEX_NUM = mPr_SEX_OTHER }; enum { diff --git a/include/types.h b/include/types.h index 11101428..01bcac30 100644 --- a/include/types.h +++ b/include/types.h @@ -60,6 +60,7 @@ typedef u32 unknown; #endif #define ARRAY_SIZE(arr, type) (sizeof(arr) / sizeof(type)) +#define ARRAY_COUNT(arr) (int)(sizeof(arr) / sizeof(arr[0])) #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) diff --git a/rel/ac_haniwa.c b/rel/ac_haniwa.c index 3e0f5a85..94d592d9 100644 --- a/rel/ac_haniwa.c +++ b/rel/ac_haniwa.c @@ -291,7 +291,7 @@ static int aHNW_decide_msg_idx_dance(ACTOR* actor) { int house_arrange_idx = mHS_get_arrange_idx(Common_Get(player_no)); mHm_hs_c* house = Save_GetPointer(homes[house_arrange_idx]); if (house->flags.has_saved == FALSE && - mEv_CheckFirstJob() == TRUE && mNpc_GetFriendAnimalNum(Common_Get(now_private)) == 0 + mEv_CheckFirstJob() == TRUE && mNpc_GetFriendAnimalNum(&Common_Get(now_private)->player_ID) == 0 ) { res = aHNW_MSG_NEED_FRIEND; /* player owns this house, but is in intro and has not spoken to any villagers */ } diff --git a/rel/m_mail_check.c b/rel/m_mail_check.c index 2b502537..71d491af 100644 --- a/rel/m_mail_check.c +++ b/rel/m_mail_check.c @@ -2,6 +2,6 @@ #include "m_mail_check_ovl.h" -extern int mMC_get_mail_hit_rate(int* len, u8* str) { +extern int mMC_get_mail_hit_rate(int* len, u8* str, GAME* game) { return mMck_check_key_hit(len, str); } diff --git a/rel/m_museum.c b/rel/m_museum.c index 9ba6f012..7ee06e1b 100644 --- a/rel/m_museum.c +++ b/rel/m_museum.c @@ -666,7 +666,7 @@ extern void mMsm_SendCompMail() { mHandbill_Set_free_str(mHandbill_FREE_STR0, Save_Get(land_info.name), land_name_len); - if (mMl_send_mail_postoffice(&priv->player_ID, i, FTR_MUSEUM_MODEL, ITM_PAPER24, 0x22F, l_museum_name_str, mMl_DATA2, mMl_TYPE_MUSEUM) == TRUE) { + if (mMl_send_mail_postoffice(&priv->player_ID, i, FTR_MUSEUM_MODEL, ITM_PAPER24, 0x22F, l_museum_name_str, mMl_DATA2, mMl_TYPE_MAIL) == TRUE) { mMsm_SetPrivateCompMail(priv, mPr_FLAG_MUSEUM_COMP_HANDBILL_RECEIVED); } } diff --git a/rel/m_name_table.c b/rel/m_name_table.c new file mode 100644 index 00000000..43dd3fea --- /dev/null +++ b/rel/m_name_table.c @@ -0,0 +1,244 @@ +#include "m_name_table.h" + +#include "m_npc_personal_id.h" + +u8 npc_looks_table[NPC_NUM] = { + mNpc_LOOKS_BOY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_BOY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_BOY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_BOY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_BOY, + mNpc_LOOKS_GRIM_MAN, + mNpc_LOOKS_GIRL, + mNpc_LOOKS_KO_GIRL, + mNpc_LOOKS_SPORT_MAN, + mNpc_LOOKS_NANIWA_LADY, + mNpc_LOOKS_BOY, + mNpc_LOOKS_BOY, + mNpc_LOOKS_KO_GIRL +}; diff --git a/rel/m_npc.c b/rel/m_npc.c new file mode 100644 index 00000000..6640378d --- /dev/null +++ b/rel/m_npc.c @@ -0,0 +1,7531 @@ +#include "m_npc.h" + +#include "m_font.h" +#include "m_name_table.h" +#include "m_common_data.h" +#include "m_mail_check.h" +#include "m_mail_check_ovl.h" +#include "m_handbill.h" +#include "m_string.h" +#include "m_shop.h" +#include "m_house.h" +#include "m_event_map_npc.h" +#include "ac_my_room.h" +#include "m_mail_password_check.h" +#include "libultra/libultra.h" +#include "libjsys/jsyswrapper.h" + +extern mNpc_Default_Data_c npc_def_list[]; +extern s8 npc_grow_list[]; +extern mNpc_NpcHouseData_c npc_house_list[]; + +typedef struct sp_actor_name_data_s { + mActor_name_t sp_npc_id; + u16 sex; + int name_str_no; + int sound_id; +} mNpc_Sp_Npc_Name_c; + +static mNpc_Sp_Npc_Name_c l_sp_actor_name[] = { + // Tom Nook + { SP_NPC_SHOP_MASTER, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_SHOP_MASTERSP, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_SHOP_MASTERSP_2, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_SHOP_MASTERSP_3, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_SHOP_MASTERSP_4, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_CONV_MASTER, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_SUPER_MASTER, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_DEPART_MASTER, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_RCN_GUIDE, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_RCN_GUIDE2, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_RCN_GUIDE_1, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_RCN_GUIDE2_1, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_RCN_GUIDE_2, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_RCN_GUIDE2_2, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_RCN_GUIDE_3, mPr_SEX_MALE, 0x205, 2 }, + { SP_NPC_RCN_GUIDE2_3, mPr_SEX_MALE, 0x205, 2 }, + // Redd + { SP_NPC_BROKER, mPr_SEX_MALE, 0x206, 2 }, + { SP_NPC_EV_BROKER2, mPr_SEX_MALE, 0x206, 2 }, + { SP_NPC_EV_YOMISE, mPr_SEX_MALE, 0x206, 2 }, + { SP_NPC_EV_YOMISE2, mPr_SEX_MALE, 0x206, 2 }, + // Katrina + { SP_NPC_GYPSY, mPr_SEX_FEMALE, 0x207, 2 }, + { SP_NPC_EV_MIKO, mPr_SEX_FEMALE, 0x207, 2 }, + // Saharah + { SP_NPC_CARPETPEDDLER, mPr_SEX_MALE, 0x208, 2 }, + // Wendell + { SP_NPC_ARTIST, mPr_SEX_MALE, 0x209, 3 }, + // Jingle + { SP_NPC_SANTA, mPr_SEX_MALE, 0x20A, 2 }, + // Gracie + { SP_NPC_DESIGNER, mPr_SEX_MALE, 0x20B, 2 }, + // Joan + { SP_NPC_KABUPEDDLER, mPr_SEX_FEMALE, 0x20C, 8 }, + // Pelly + { SP_NPC_POST_GIRL, mPr_SEX_FEMALE, 0x20D, 4 }, + // Phyllis + { SP_NPC_POST_GIRL2, mPr_SEX_FEMALE, 0x20E, 4 }, + // Pete + { SP_NPC_POST_MAN, mPr_SEX_MALE, 0x20F, 3 }, + // Copper + { SP_NPC_POLICE, mPr_SEX_MALE, 0x210, 2 }, + // Booker + { SP_NPC_POLICE2, mPr_SEX_MALE, 0x6D9, 3 }, + // Copper + { SP_NPC_EV_TAISOU_0, mPr_SEX_MALE, 0x210, 2 }, + // Porter + { SP_NPC_STATION_MASTER, mPr_SEX_MALE, 0x211, 2 }, + // Jack + { SP_NPC_HALLOWEEN, mPr_SEX_MALE, 0x212, 3 }, + // Gyroid + { ACTOR_PROP_HANIWA0, mPr_SEX_OTHER, 0x213, 2 }, + { ACTOR_PROP_HANIWA1, mPr_SEX_OTHER, 0x213, 2 }, + { ACTOR_PROP_HANIWA2, mPr_SEX_OTHER, 0x213, 2 }, + { ACTOR_PROP_HANIWA3, mPr_SEX_OTHER, 0x213, 2 }, + // K.K. Slider + { SP_NPC_P_SEL, mPr_SEX_OTHER, 0x214, 2 }, + { SP_NPC_TOTAKEKE, mPr_SEX_OTHER, 0x214, 2 }, + { SP_NPC_RTC, mPr_SEX_OTHER, 0x214, 2 }, + // Rover + { SP_NPC_GUIDE, mPr_SEX_MALE, 0x215, 2 }, + { SP_NPC_GUIDE2, mPr_SEX_MALE, 0x215, 2 }, + // Chip + { SP_NPC_ANGLER, mPr_SEX_MALE, 0x216, 2 }, + // Timmy + { SP_NPC_MAMEDANUKI0, mPr_SEX_MALE, 0x217, 2 }, + // Tommy + { SP_NPC_MAMEDANUKI1, mPr_SEX_MALE, 0x218, 2 }, + // Resetti + { SP_NPC_MAJIN, mPr_SEX_MALE, 0x4E0, 2 }, + { SP_NPC_MAJIN2, mPr_SEX_MALE, 0x4E0, 2 }, + { SP_NPC_MAJIN3, mPr_SEX_MALE, 0x4E0, 2 }, + { SP_NPC_MAJIN4, mPr_SEX_MALE, 0x4E0, 2 }, + { SP_NPC_MAJIN5, mPr_SEX_MALE, 0x4E0, 2 }, + { SP_NPC_MAJIN_D07C, mPr_SEX_MALE, 0x4E0, 2 }, + { SP_NPC_MAJIN_D07D, mPr_SEX_MALE, 0x4E0, 2 }, + // Don + { SP_NPC_MAJIN_BROTHER, mPr_SEX_MALE, 0x6D8, 2 }, + // Resetti + { SP_NPC_MAJIN_D080, mPr_SEX_MALE, 0x4E0, 2 }, + { SP_NPC_EV_MAJIN, mPr_SEX_MALE, 0x4E0, 2 }, + // Gulliver + { SP_NPC_EV_DOZAEMON, mPr_SEX_MALE, 0x4E1, 2 }, + // Snowman + { ETC_SNOWMAN_BALL_A, mPr_SEX_MALE, 0x4E2, 2 }, + { ETC_SNOWMAN_BALL_B, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN0, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN1, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN2, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN3, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN4, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN5, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN6, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN7, mPr_SEX_MALE, 0x4E2, 2 }, + { SNOWMAN8, mPr_SEX_MALE, 0x4E2, 2 }, + // Tortimer + { SP_NPC_EV_SONCHO, mPr_SEX_MALE, 0x4E3, 2 }, + { SP_NPC_EV_SONCHO2, mPr_SEX_MALE, 0x4E3, 2 }, + { SP_NPC_SONCHO_D078, mPr_SEX_MALE, 0x4E3, 2 }, + { SP_NPC_SONCHO_D079, mPr_SEX_MALE, 0x4E3, 2 }, + { SP_NPC_SONCHO, mPr_SEX_MALE, 0x4E3, 2 }, + { SP_NPC_EV_SPEECH_SONCHO, mPr_SEX_MALE, 0x4E3, 2 }, + // Blathers + { SP_NPC_CURATOR, mPr_SEX_MALE, 0x4E4, 2 }, + // Mabel + { SP_NPC_NEEDLEWORK0, mPr_SEX_FEMALE, 0x4E5, 4 }, + // Sabel + { SP_NPC_NEEDLEWORK1, mPr_SEX_FEMALE, 0x4E6, 4 }, + // Kapp'n + { SP_NPC_SENDO, mPr_SEX_MALE, 0x4E7, 2 }, + // Wisp + { SP_NPC_EV_GHOST, mPr_SEX_MALE, 0x4E8, 2 }, + // Blanca + { SP_NPC_MASK_CAT, mPr_SEX_MALE, 0x4E9, 4 }, + { SP_NPC_MASK_CAT2, mPr_SEX_MALE, 0x4E9, 4 }, + // Franklin + { SP_NPC_TURKEY, mPr_SEX_MALE, 0x6DA, 2 }, + // Farley + { SP_NPC_HEM, mPr_SEX_MALE, 0x6DB, 9 } +}; + +static u8 l_no_name_npc_name[ANIMAL_NAME_LEN] = { 0xD4, 0x8E, 0xA6, 0x90, 0x85, 0x42 }; // never translated +static u8 l_no_ending_npc_ending[ANIMAL_CATCHPHRASE_LEN] = { 0xD3, 0xAF, 0x9D, 0x20 }; // never translated + +static void mNpc_MakeRandTable(int* table, int count, int swap_num) { + int a; + int b; + int i; + f32 c = count; + + for (i = 0; i < count; i++) { + table[i] = i; + } + + while (swap_num--) { + int temp; + + a = RANDOM_F(c); + b = RANDOM_F(c); + temp = table[a]; + + table[a] = table[b]; + table[b] = temp; + } +} + +static void mNpc_ClearBufSpace1(u8* buf, int size) { + mem_clear(buf, size, CHAR_SPACE); +} + +extern void mNpc_AddNowNpcMax(u8* npc_max) { + if (npc_max[0] < ANIMAL_NUM_MAX) { + npc_max[0]++; + } +} + +extern void mNpc_SubNowNpcMax(u8* npc_max) { + if (npc_max[0] > ANIMAL_NUM_MIN) { + npc_max[0]--; + } +} + +extern void mNpc_ClearAnimalPersonalID(AnmPersonalID_c* pid) { + pid->npc_id = EMPTY_NO; + pid->name_id = 0xFF; + pid->land_id = 0xFFFF; + pid->looks = mNpc_LOOKS_UNSET; + mNPS_reset_schedule_area(pid); + mLd_ClearLandName(pid->land_name); +} + +extern int mNpc_CheckFreeAnimalPersonalID(AnmPersonalID_c* pid) { + int res = FALSE; + + if ((pid->npc_id == EMPTY_NO && pid->name_id == 0xFF) || (ITEM_NAME_GET_TYPE(pid->npc_id) != NAME_TYPE_NPC)) { + res = TRUE; + } + + return res; +} + +extern void mNpc_CopyAnimalPersonalID(AnmPersonalID_c* dst, AnmPersonalID_c* src) { + if (src != NULL && ITEM_NAME_GET_TYPE(src->npc_id) == NAME_TYPE_NPC) { + dst->npc_id = src->npc_id; + dst->name_id = src->name_id; + dst->land_id = src->land_id; + dst->looks = src->looks; + mLd_CopyLandName(dst->land_name, src->land_name); + } +} + +extern int mNpc_CheckCmpAnimalPersonalID(AnmPersonalID_c* pid0, AnmPersonalID_c* pid1) { + int res = FALSE; + + if ( + (pid0 != NULL && pid1 != NULL) && + (pid0->npc_id == pid1->npc_id) && + (pid0->name_id == pid1->name_id) && + (pid0->land_id == pid1->land_id) && + mLd_CheckCmpLandName(pid0->land_name, pid1->land_name) == TRUE + ) { + res = TRUE; + } + + return res; +} + +extern int mNpc_GetAnimalNum() { + Animal_c* animal = Save_Get(animals); + int num = 0; + int i; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalPersonalID(&animal->id) == FALSE) { + num++; + } + + animal++; + } + + return num; +} + +extern int mNpc_CheckRemoveExp(Animal_c* animal) { + return (animal->remove_info >> 13) & 0b111; +} + +extern int mNpc_GetRemoveTime(Animal_c* animal) { + return animal->remove_info & 0x1FFF; +} + +/* @unused */ +extern void mNpc_AddRemoveTime(Animal_c* animal) { + if ((animal->remove_info & 0x1FFF) < 0x1FFF) { + animal->remove_info++; + } +} + +/* @unused */ +#ifdef MUST_MATCH +extern void mNpc_SetRemoveExp(Animal_c* animal, u16 remove_exp) { + animal->remove_info = ((((animal->remove_info >> 13) & 0b111) >> 13) | remove_exp) << 13; // what the... "official" impl from DnM +} +#else +extern void mNpc_SetRemoveExp(Animal_c* animal, u16 remove_exp) { + animal->remove_info |= (remove_exp & 0b111) << 13; +} +#endif + +extern void mNpc_SetParentName(Animal_c* animal, PersonalID_c* parent_id) { + if (parent_id != NULL && mPr_NullCheckPersonalID(parent_id) == FALSE && mPr_NullCheckPlayerName(animal->parent_name) == TRUE) { + mPr_CopyPlayerName(animal->parent_name, parent_id->player_name); + } +} + +extern void mNpc_SetParentNameAllAnimal() { + Private_c* priv = Common_Get(now_private); + + if (priv != NULL) { + PersonalID_c* parent_id = &priv->player_ID; + Animal_c* animal = Save_Get(animals); + int i; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + mNpc_SetParentName(animal, parent_id); + animal++; + } + } +} + +extern void mNpc_ClearAnimalMail(Anmplmail_c* mail) { + mail->font = 0xFF; + mail->paper_type = 0; + mail->present = EMPTY_NO; + mem_clear(mail->body, sizeof(mail->body), CHAR_SPACE); + mail->date.year = mTM_rtcTime_ymd_clear_code.year; + mail->date.month = mTM_rtcTime_ymd_clear_code.month; + mail->date.day = mTM_rtcTime_ymd_clear_code.day; +} + +/* @unused */ +/* +extern void mNpc_CopyAnimalMail(Anmplmail_c* dst, Anmplmail_c* src) { + mem_copy((u8*)dst, (u8*)src, sizeof(Anmplmail_c)); +} +*/ + +extern void mNpc_ClearAnimalMemory(Anmmem_c* memory, int num) { + for (num; num != 0; num--) { + if (memory != NULL) { + mPr_ClearPersonalID(&memory->memory_player_id); + lbRTC_TimeCopy(&memory->last_speak_time, &mTM_rtcTime_clear_code); + mLd_ClearLandName(memory->memuni.land.name); + memory->memuni.land.id = 0; + memory->saved_town_tune = 0; + memory->friendship = 0; + memory->letter_info.exists = FALSE; + memory->letter_info.cond = mNpc_LETTER_RANK_BAD; + memory->letter_info.send_reply = FALSE; + memory->letter_info.has_present_cloth = FALSE; + mNpc_ClearAnimalMail(&memory->letter); + } + + memory++; + } +} + +/* TODO: this probably implies the union is on the memory itself and not a struct inside it */ +extern void mNpc_ClearIslandAnimalMemory(Anmmem_c* memory, int num) { + for (num; num != 0; num--) { + if (memory != NULL) { + mPr_ClearPersonalID(&memory->memory_player_id); + lbRTC_TimeCopy(&memory->last_speak_time, &mTM_rtcTime_clear_code); + bzero(&memory->memuni.island, sizeof(memuni_u)); + memory->saved_town_tune = 0; + memory->friendship = 0; + memory->letter_info.exists = FALSE; + memory->letter_info.cond = mNpc_LETTER_RANK_BAD; + memory->letter_info.send_reply = FALSE; + memory->letter_info.has_present_cloth = FALSE; + mNpc_ClearAnimalMail(&memory->letter); + } + + memory++; + } +} + +/* @unused */ +/* +extern void mNpc_CopyAnimalMemory(Anmmem_c* dst, Anmmem_c* src) { + mem_copy((u8*)dst, (u8*)src, sizeof(Anmmem_c)); +} +*/ + +extern void mNpc_AddFriendship(Anmmem_c* memory, int amount) { + /* @BUG - devs checked for memory being NULL *after* deferencing it */ + #ifdef BUGFIXES + if (memory == NULL) { + return; + } + #endif + + int friendship = memory->friendship + amount; + + #ifndef BUGFIXES + if (memory == NULL) { + return; //??? + } + #endif + + if (friendship > 127) { + memory->friendship = 127; + } + else if (friendship < 0) { + memory->friendship = 0; + } + else { + memory->friendship = friendship; + } +} + +extern int mNpc_CheckFreeAnimalMemory(Anmmem_c* memory) { + int res = FALSE; + + if (mPr_NullCheckPersonalID(&memory->memory_player_id) == TRUE) { + res = TRUE; + } + + return res; +} + +extern void mNpc_RenewalAnimalMemory() { + Private_c* priv; + Animal_c* animal = Save_Get(animals); + Anmmem_c* memory; + int i; + int j; + int k; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + memory = animal->memories; + + for (j = 0; j < ANIMAL_MEMORY_NUM; j++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE && mLd_CheckThisLand(memory->memory_player_id.land_name, memory->memory_player_id.land_id) == TRUE) { + priv = Save_Get(private); + + for (k = 0; k < PLAYER_NUM; k++) { + if (mPr_NullCheckPersonalID(&priv->player_ID) == FALSE && mPr_CheckCmpPersonalID(&priv->player_ID, &memory->memory_player_id) == TRUE) { + break; + } + + priv++; + } + + if (k == PLAYER_NUM) { + mNpc_ClearAnimalMemory(memory, 1); // player was deleted so free the memory + } + } + + memory++; + } + + animal++; + } +} + +/* @unused */ +/* +extern int mNpc_GetOldAnimalMemoryIdx(Anmmem_c* memory, int num) { + lbRTC_time_c oldest; + int res; + int i; + + lbRTC_TimeCopy(&oldest, &mTM_rtcTime_clear_code); + res = 0; + + for (i = 0; i < num; i++) { + if (lbRTC_IsOverTime(&oldest, &memory->last_speak_time) == lbRTC_LESS) { + res = i; + lbRTC_TimeCopy(&oldest, &memory->last_speak_time); + } + + memory++; + } + + return res; +} +*/ + +extern int mNpc_GetFreeAnimalMemoryIdx(Anmmem_c* memory, int num) { + int res = -1; + int i; + + for (i = 0; i < num; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == TRUE) { + res = i; + break; + } + + memory++; + } + + return res; +} + +extern int mNpc_GetOldPlayerAnimalMemoryIdx(Anmmem_c* memory, int num) { + Private_c* priv; + int res = -1; + int i; + int j; + + for (i = 0; i < num; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE && mLd_CheckThisLand(memory->memory_player_id.land_name, memory->memory_player_id.land_id) == TRUE) { + priv = Save_Get(private); + + for (j = 0; j < PLAYER_NUM; j++) { + if (mPr_NullCheckPersonalID(&priv->player_ID) == FALSE && mPr_CheckCmpPersonalID(&priv->player_ID, &memory->memory_player_id) == TRUE) { + break; + } + + priv++; + } + + /* memory belongs to a player from this town which was deleted */ + if (j == PLAYER_NUM) { + res = i; + break; + } + } + + memory++; + } + + return res; +} + +extern int mNpc_ForceGetFreeAnimalMemoryIdx(Animal_c* animal, Anmmem_c* memory, int num) { + Anmmem_c* mem_p = memory; + Anmmem_c* chosen_mem = NULL; + int free_idx = mNpc_GetFreeAnimalMemoryIdx(memory, num); + int i; + + if (free_idx == -1) { + for (i = 0; i < num; i++) { + if (mLd_CheckThisLand(memory->memory_player_id.land_name, memory->memory_player_id.land_id) == FALSE) { + if (chosen_mem != NULL) { + if (chosen_mem->letter_info.exists == memory->letter_info.exists) { + if (chosen_mem->friendship > memory->friendship) { + chosen_mem = memory; + free_idx = i; + } + else if (chosen_mem->friendship == memory->friendship) { + if (lbRTC_IsOverTime(&chosen_mem->last_speak_time, &memory->last_speak_time) == lbRTC_LESS) { + chosen_mem = memory; + free_idx = i; + } + } + } + else if (memory->letter_info.exists == FALSE) { + chosen_mem = memory; + free_idx = i; + } + } + else { + chosen_mem = memory; + free_idx = i; + } + } + + memory++; + } + } + else { + chosen_mem = memory + free_idx; + } + + if (free_idx == -1 || chosen_mem == NULL) { + free_idx = mNpc_GetOldPlayerAnimalMemoryIdx(mem_p, num); + + if (free_idx == -1) { + free_idx = 0; // all else failed, so pick the first memory + } + + chosen_mem = mem_p + free_idx; + } + + if (chosen_mem != NULL) { + if (mNpc_CheckIslandAnimalID(&animal->id) == FALSE) { + mNpc_ClearAnimalMemory(chosen_mem, 1); + } + else { + mNpc_ClearIslandAnimalMemory(chosen_mem, 1); + } + } + + return free_idx; +} + +static void mNpc_ResetAnimalRelation(int idx) { + Animal_c* animal = Save_Get(animals); + u8* this_relations = animal[idx].animal_relations; + int i; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + this_relations[0] = 128; + this_relations++; + } + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (i != idx) { + animal->animal_relations[idx] = 128; + } + + animal++; + } +} + +static void mNpc_SetAnimalMemory_NotSetDay(PersonalID_c* pid, AnmPersonalID_c* anm_id, Anmmem_c* memory) { + if (memory != NULL) { + if (mNpc_CheckIslandAnimalID(anm_id) == FALSE) { + mNpc_ClearAnimalMemory(memory, 1); + } + else { + mNpc_ClearIslandAnimalMemory(memory, 1); + } + + mPr_CopyPersonalID(&memory->memory_player_id, pid); + memory->friendship = 1; + } +} + +extern void mNpc_SetAnimalMemory(PersonalID_c* pid, AnmPersonalID_c* anm_id, Anmmem_c* memory) { + lbRTC_time_c* rtc_time = Common_GetPointer(time.rtc_time); + + if (memory != NULL) { + mNpc_SetAnimalMemory_NotSetDay(pid, anm_id, memory); + lbRTC_TimeCopy(&memory->last_speak_time, rtc_time); + } +} + +extern int mNpc_GetAnimalMemoryIdx(PersonalID_c* pid, Anmmem_c* memory, int num) { + int res = -1; + int i; + + for (i = 0; i < num; i++) { + if (mPr_CheckCmpPersonalID(&memory->memory_player_id, pid) == TRUE) { + res = i; + break; + } + + memory++; + } + + return res; +} + +extern void mNpc_SetAnimalLastTalk(Animal_c* animal) { + Private_c* priv = Common_Get(now_private); + Anmmem_c* memory = NULL; + + if (priv != NULL && animal != NULL) { + int memory_idx = mNpc_GetAnimalMemoryIdx(&priv->player_ID, animal->memories, ANIMAL_MEMORY_NUM); + + if (memory_idx != -1) { + memory = animal->memories + memory_idx; + } + else { + memory_idx = mNpc_ForceGetFreeAnimalMemoryIdx(animal, animal->memories, ANIMAL_MEMORY_NUM); + + if (memory_idx != -1) { + memory = animal->memories + memory_idx; + mNpc_SetAnimalMemory(&priv->player_ID, &animal->id, memory); + } + } + + if (memory != NULL) { + lbRTC_TimeCopy(&memory->last_speak_time, Common_GetPointer(time.rtc_time)); + mLd_CopyLandName(memory->memuni.land.name, Save_Get(land_info).name); + memory->memuni.land.id = Save_Get(land_info).id; + memory->saved_town_tune = Save_Get(melody); + } + } +} + +extern void mNpc_SetAnimalPersonalID2Memory(AnmPersonalID_c* anm_id) { + Private_c* priv = Common_Get(now_private); + + if (priv != NULL) { + int anm_idx = mNpc_SearchAnimalPersonalID(anm_id); + + if (anm_idx != -1) { + Animal_c* animal = Save_Get(animals) + anm_idx; + int mem_idx = mNpc_GetAnimalMemoryIdx(&priv->player_ID, animal->memories, ANIMAL_MEMORY_NUM); + + if (mem_idx == -1) { + int free_idx = mNpc_ForceGetFreeAnimalMemoryIdx(animal, animal->memories, ANIMAL_MEMORY_NUM); + + if (free_idx != -1) { + mNpc_SetAnimalMemory_NotSetDay(&priv->player_ID, &animal->id, animal->memories + free_idx); + } + } + } + } +} + +extern int mNpc_GetHighestFriendshipIdx(Anmmem_c* memory, int num) { + s8 max = 0; + int res = -1; + int i; + + for (i = 0; i < num; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE) { + if (max <= memory->friendship) { + max = memory->friendship; + res = i; + } + } + + memory++; + } + + return res; +} + +static int mNpc_SelectBestFriend(Anmmem_c** best_friend_mem, Anmmem_c* memory, s8* best_friend_friendship) { + int res = FALSE; + Anmmem_c* best_friend; + + if (memory->friendship > best_friend_friendship[0]) { + best_friend_friendship[0] = memory->friendship; + best_friend_mem[0] = memory; + res = TRUE; + } + else if (memory->friendship == best_friend_friendship[0]) { + best_friend = best_friend_mem[0]; + + if (best_friend != NULL) { + if (best_friend->letter_info.exists == memory->letter_info.exists) { + if (lbRTC_IsOverTime(&best_friend->last_speak_time, &memory->last_speak_time) == lbRTC_LESS) { + best_friend_mem[0] = memory; + res = TRUE; + } + } + else if (memory->letter_info.exists == TRUE) { + best_friend_mem[0] = memory; + res = TRUE; + } + } + else { + best_friend_mem[0] = memory; + res = TRUE; + } + } + + return res; +} + +extern int mNpc_GetAnimalMemoryBestFriend(Anmmem_c* memory, int num) { + Anmmem_c* memory_p = memory; + Anmmem_c* best_friend = NULL; + s8 best_friendship = 0; + int res = -1; + int i; + + for (i = 0; i < num; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE && mNpc_SelectBestFriend(&best_friend, memory, &best_friendship) == TRUE) { + res = i; + } + + memory++; + } + + memory = memory_p; + if (res != -1) { + memory = &memory[res]; + + if (memory->friendship < 80) { + res = -1; // minimum best friend friendship value failed + } + } + + return res; +} + +static int mNpc_GetAnimalMemoryFriend_Land_Sex(Anmmem_c* memory, int num, int sex) { + Anmmem_c* best_friend = NULL; + s8 best_friendship = 0; + int priv_idx; + int res = -1; + int i; + + for (i = 0; i < num; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE) { + priv_idx = mPr_GetPrivateIdx(&memory->memory_player_id); + + if (priv_idx != -1 && Save_Get(private[priv_idx]).gender == sex && mNpc_SelectBestFriend(&best_friend, memory, &best_friendship) == TRUE) { + res = i; + } + } + + memory++; + } + + return res; +} + +extern int mNpc_GetAnimalMemoryNum(Anmmem_c* memory, int count) { + int num = 0; + int i; + + for (i = 0; i < count; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE) { + num++; + } + + memory++; + } + + return num; +} + +extern int mNpc_GetAnimalMemoryLetterNum(Anmmem_c* memory, int count) { + int num = 0; + int i; + + for (i = 0; i < count; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE && memory->letter_info.exists == TRUE) { + num++; + } + + memory++; + } + + return num; +} + +extern int mNpc_GetAnimalMemoryLandKindNum(Anmmem_c* memory, int count) { + Anmmem_c* memory2; + Anmlnd_c* land; + Anmlnd_c* land2; + u8 bitfield = 0b11111111; + int num = 0; + int i; + int j; + + for (i = 0; i < count; i++) { + land = &memory->memuni.land; + + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE) { + if (((bitfield >> i) & 1) == 1) { + memory2 = memory + 1; + land2 = &memory2->memuni.land; + + for (j = i + 1; j < count; j++) { + if (((bitfield >> j) & 1) == 1) { + if (mLd_CheckCmpLand(land->name, land->id, land2->name, land2->id) == TRUE) { + bitfield &= ~(1 << j); // duplicate town here, so remove this memory from being checked + } + } + + memory2++; + } + } + + num++; + } + else { + bitfield &= ~(1 << i); + } + + memory++; + } + + return num; +} + +extern void mNpc_ClearAnimalInfo(Animal_c* animal) { + bzero(animal, sizeof(Animal_c)); + mNpc_ClearAnimalPersonalID(&animal->id); + mNpc_ClearAnimalMemory(animal->memories, ANIMAL_MEMORY_NUM); + animal->cloth = EMPTY_NO; + animal->home_info.type_unused = 0; + animal->home_info.block_x = 0xFF; + animal->home_info.block_z = 0xFF; + animal->home_info.ut_x = 0xFF; + animal->home_info.ut_z = 0xFF; + mNpc_ClearBufSpace1(animal->catchphrase, ANIMAL_CATCHPHRASE_LEN); + mQst_ClearContest(&animal->contest_quest); + mLd_ClearLandName(animal->anmuni.previous_land_name); + animal->previous_land_id = 0; + animal->remove_info = 0; + animal->is_home = TRUE; + mPr_ClearPlayerName(animal->parent_name); +} + +extern void mNpc_ClearIslandAnimalInfo(Animal_c* animal) { + bzero(animal, sizeof(Animal_c)); + mNpc_ClearAnimalPersonalID(&animal->id); + mNpc_ClearIslandAnimalMemory(animal->memories, ANIMAL_MEMORY_NUM); + animal->cloth = EMPTY_NO; + animal->home_info.type_unused = 0; + animal->home_info.block_x = 0xFF; + animal->home_info.block_z = 0xFF; + animal->home_info.ut_x = 0xFF; + animal->home_info.ut_z = 0xFF; + mNpc_ClearBufSpace1(animal->catchphrase, ANIMAL_CATCHPHRASE_LEN); + mQst_ClearContest(&animal->contest_quest); + animal->remove_info = 0; + animal->is_home = TRUE; + mPr_ClearPlayerName(animal->parent_name); +} + +extern void mNpc_ClearAnyAnimalInfo(Animal_c* animal, int count) { + int i; + + for (i = 0; i < count; i++) { + mNpc_ClearAnimalInfo(animal); + animal++; + } +} + +extern int mNpc_CheckFreeAnimalInfo(Animal_c* animal) { + int res = FALSE; + + if ((animal->home_info.block_x == 0xFF && animal->home_info.block_z == 0xFF) && ITEM_NAME_GET_TYPE(animal->id.npc_id) != NAME_TYPE_NPC) { + res = TRUE; + } + + return res; +} + +extern int mNpc_GetFreeAnimalInfo(Animal_c* animal, int count) { + int res = -1; + int i; + + for (i = 0; i < count; i++) { + if (mNpc_CheckFreeAnimalInfo(animal) == TRUE) { + res = i; + break; + } + + animal++; + } + + return res; +} + +extern int mNpc_UseFreeAnimalInfo(Animal_c* animal, int count) { + int free_idx = mNpc_GetFreeAnimalInfo(animal, count); + + if (free_idx != -1) { + mNpc_ClearAnimalInfo(animal + free_idx); + mNpc_ResetAnimalRelation(free_idx); + } + + return free_idx; +} + +extern void mNpc_CopyAnimalInfo(Animal_c* dst, Animal_c* src) { + mem_copy((u8*)dst, (u8*)src, sizeof(Animal_c)); +} + +extern int mNpc_SearchAnimalinfo(Animal_c* animal, mActor_name_t npc_id, int count) { + int i; + + animal = animal + (count - 1); + for (i = count - 1; i >= 0; i--) { + if (animal->id.npc_id == npc_id) { + return i; + } + + animal--; + } + + return -1; +} + +extern Animal_c* mNpc_GetAnimalInfoP(mActor_name_t npc_id) { + Animal_c* animal = NULL; + int idx = mNpc_SearchAnimalinfo(Save_Get(animals), npc_id, ANIMAL_NUM_MAX); + + if (idx != -1) { + animal = Save_Get(animals) + idx; + } + + return animal; +} + +extern int mNpc_SearchAnimalPersonalID(AnmPersonalID_c* anm_pid) { + Animal_c* animal = Save_Get(animals); + int res = -1; + int i; + + if (anm_pid != NULL && !mNpc_CheckFreeAnimalPersonalID(anm_pid)) { + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckCmpAnimalPersonalID(anm_pid, &animal->id) == TRUE) { + res = i; + break; + } + + animal++; + } + } + + return res; +} + +extern AnmPersonalID_c* mNpc_GetOtherAnimalPersonalIDOtherBlock(AnmPersonalID_c* pids, int count, int bx, int bz, int check_flag) { + Animal_c* animal; + AnmPersonalID_c* other_id; + int npc_max; + int homes_in_block; + int ids = count; + u16 bitfield; + int j; + int i; + + other_id = NULL; + npc_max = Save_Get(now_npc_max); + animal = Save_Get(animals); + homes_in_block = 0; + ids = count; + bitfield = 0xFFFF; + + if (bx == 0xFF || bz == 0xFF) { + check_flag = FALSE; + } + + if (pids == NULL && count != 0) { + count = 0; + } + + /* clear any uninitialized personal ids */ + for (i = 0; i < count; i++) { + if (mNpc_CheckFreeAnimalPersonalID(pids + i) == TRUE) { + ids--; + bitfield &= ~(1 << i); + } + } + + /* clear any npcs who are in the list and live in the acre */ + if (check_flag == TRUE) { + for (j = 0; j < ANIMAL_NUM_MAX; j++) { + if (animal->home_info.block_x == bx && animal->home_info.block_z == bz) { + homes_in_block++; + + for (i = 0; i < count; i++) { + if (((bitfield >> i) & 1) == 1 && mNpc_CheckCmpAnimalPersonalID(&animal->id, &pids[i]) == TRUE) { + ids--; + bitfield &= ~(1 << i); + break; + } + } + } + + animal++; + } + } + + if (npc_max > (ids + homes_in_block) && count < ANIMAL_NUM_MAX) { + int valid; + int t; + + animal = Save_Get(animals); + homes_in_block = RANDOM((npc_max - ids) - homes_in_block); + + for (j = 0; j < ANIMAL_NUM_MAX; j++) { + i = 0; + valid = TRUE; + + if (!mNpc_CheckFreeAnimalPersonalID(&animal->id)) { + for (npc_max = 0; npc_max < count; npc_max++) { + if (((bitfield >> npc_max) & 1) == 1 && !mNpc_CheckCmpAnimalPersonalID(&animal->id, &pids[npc_max])) { + i++; + } + } + + if (i != ids) { + valid = FALSE; + } + else if (check_flag == TRUE && animal->home_info.block_x == bx && animal->home_info.block_z == bz) { + if (homes_in_block > 0) { + homes_in_block--; + } + + valid = FALSE; + } + + if (valid == TRUE) { + if (homes_in_block == 0) { + other_id = &animal->id; + break; + } + else { + homes_in_block--; + } + } + } + + animal++; + } + } + + return other_id; +} + +extern AnmPersonalID_c* mNpc_GetOtherAnimalPersonalID(AnmPersonalID_c* pids, int count) { + return mNpc_GetOtherAnimalPersonalIDOtherBlock(pids, count, 0, 0, FALSE); +} + +extern void mNpc_SetAnimalThisLand(Animal_c* animal, int count) { + mLd_land_info_c* land_info = Save_GetPointer(land_info); + + for (count; count != 0; count--) { + if (!mNpc_CheckFreeAnimalPersonalID(&animal->id)) { + animal->id.land_id = land_info->id; + mLd_CopyLandName(animal->id.land_name, land_info->name); + } + + animal++; + } +} + +extern int mNpc_GetSameLooksNum(u8 looks) { + Animal_c* animal = Save_Get(animals); + int i; + int num = 0; + + if (looks < mNpc_LOOKS_UNSET) { + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (looks == animal->id.looks) { + num++; + } + + animal++; + } + } + + return num; +} + +extern int mNpc_CheckNpcExistBlock(int idx, int check_bx, int check_bz) { + int bx; + int bz; + int res = FALSE; + + if (idx >= 0 && idx < (ANIMAL_NUM_MAX + 1)) { + mNpc_NpcList_c* list = Common_GetPointer(npclist[idx]); + + if (mFI_Wpos2BlockNum(&bx, &bz, list->position) == TRUE && bx == check_bx && bz == check_bz) { + res = TRUE; + } + } + + return res; +} + +extern void mNpc_Mail2AnimalMail(Anmplmail_c* animal_mail, Mail_c* mail) { + mem_copy(animal_mail->body, mail->content.body, MAIL_BODY_LEN); + mem_copy(animal_mail->header, mail->content.header, MAIL_HEADER_LEN); + mem_copy(animal_mail->footer, mail->content.footer, MAIL_FOOTER_LEN); + + animal_mail->header_back_start = mail->content.header_back_start; + animal_mail->font = mail->content.font; + animal_mail->present = mail->present; + animal_mail->paper_type = mail->content.paper_type; +} + +extern void mNpc_AnimalMail2Mail(Mail_c* mail, Anmplmail_c* animal_mail, PersonalID_c* pid, AnmPersonalID_c* anm_id) { + mem_copy(mail->content.body, animal_mail->body, MAIL_BODY_LEN); + mem_copy(mail->content.header, animal_mail->header, MAIL_HEADER_LEN); + mem_copy(mail->content.footer, animal_mail->footer, MAIL_FOOTER_LEN); + + mail->content.header_back_start = animal_mail->header_back_start; + mail->content.font = animal_mail->font; + mail->present = animal_mail->present; + mail->content.paper_type = animal_mail->paper_type; + + if (pid != NULL) { + mPr_CopyPersonalID(&mail->header.sender.personalID, pid); + mail->header.sender.type = mMl_NAME_TYPE_PLAYER; + } + + if (anm_id != NULL) { + mMl_set_mail_name_npcinfo(&mail->header.recipient, anm_id); + } +} + +static int mNpc_CheckMailChar(u8 c) { + if ( + (c == CHAR_EXCLAMATION) || + (c == CHAR_QUOTATION) || + (c == CHAR_UNDERSCORE) || + (c == CHAR_HYPHEN) || + (c == CHAR_SYMBOL_ANNOYED) || + ((c >= CHAR_PERCENT) && (c <= CHAR_AT_SIGN)) || + ((c >= CHAR_CONTROL_CODE) && (c <= CHAR_INTERPUNCT)) + ) { + return TRUE; + } + + return FALSE; +} + +extern int mNpc_CheckNormalMail_sub(int* char_num, u8* body) { + u8 last_char = CHAR_SPACE; + int run_len = 1; + int consecutive_chars = FALSE; + int t = 0; + int i; + + char_num[0] = 0; + + for (i = 0; i < MAIL_BODY_LEN; i++) { + if (body[0] != CHAR_SPACE) { + + if (last_char == body[0]) { + run_len++; + + if (run_len >= 3) { + if (mNpc_CheckMailChar(body[0]) == TRUE) { + if (run_len >= 8) { + consecutive_chars = TRUE; + break; + } + } + else { + consecutive_chars = TRUE; + break; + } + } + } + else { + run_len = 0; + last_char = body[0]; + } + + char_num[0]++; + } + + body++; + t++; + } + + for (i = t; i < MAIL_BODY_LEN; i++) { + if (body[0] != CHAR_SPACE) { + char_num[0]++; + } + + body++; + } + + return consecutive_chars; +} + +extern u8 mNpc_CheckNormalMail_length(int* len, u8* body) { + int hit_chars; + u8 rank = mNpc_LETTER_RANK_NUM; + int hit_rate = mMC_get_mail_hit_rate(&hit_chars, body, NULL); + int run_on = mNpc_CheckNormalMail_sub(len, body); + + if (hit_chars < 3) { + if (len[0] < 5) { + rank = mNpc_LETTER_RANK_BAD; + } + else if (run_on == TRUE) { + rank = mNpc_LETTER_RANK_BAD; + } + } + else if (hit_rate >= 30) { + rank = mNpc_LETTER_RANK_OK; + } + else if (run_on == TRUE) { + rank = mNpc_LETTER_RANK_BAD; + } + + return rank; +} + +extern u8 mNpc_CheckNormalMail_nes(u8* body) { + u8 rank = mNpc_LETTER_RANK_NUM; + int key_hit = mMck_check_key_hit_nes(body); + + if (key_hit >= 100) { + rank = mNpc_LETTER_RANK_OK; + } + else if (key_hit < 50) { + rank = mNpc_LETTER_RANK_BAD; + } + + return rank; +} + +static u8 mNpc_CheckNormalMail(u8* body) { + return mNpc_CheckNormalMail_nes(body); +} + +static int mNpc_SetMailCondThisLand(Anmmem_c* memory, u8* body) { + u8 letter_rank; + + memory->letter.date.year = Common_Get(time.rtc_time).year; + memory->letter.date.month = Common_Get(time.rtc_time).month; + memory->letter.date.day = Common_Get(time.rtc_time).day; + + letter_rank = mNpc_CheckNormalMail(body); + + if (letter_rank < mNpc_LETTER_RANK_NUM) { + memory->letter_info.cond = letter_rank; + memory->letter_info.send_reply = TRUE; + } + + return letter_rank; +} + +static int mNpc_SetMailCondOtherLand(Animal_c* animal, u8* body) { + u8 letter_rank = mNpc_CheckNormalMail(body); + + if (letter_rank < mNpc_LETTER_RANK_NUM) { + Private_c* priv = Common_Get(now_private); + Anmremail_c* remail = &priv->remail; + + remail->date.year = Common_Get(time.rtc_time).year; + remail->date.month = Common_Get(time.rtc_time).month; + remail->date.day = Common_Get(time.rtc_time).day; + remail->flags.cond = letter_rank; + remail->flags.looks = animal->id.looks; + + mNpc_GetNpcWorldNameAnm(remail->name, &animal->id); + mLd_CopyLandName(remail->land_name, mLd_GetLandName()); + } + + return letter_rank; +} + +static int mNpc_SetRemailCond(Animal_c* animal, Anmmem_c* memory, u8* body) { + if (mLd_PlayerManKindCheck() == FALSE) { + return mNpc_SetMailCondThisLand(memory, body); + } + else { + return mNpc_SetMailCondOtherLand(animal, body); + } +} + +static int mNpc_SetPresentCloth(Animal_c* animal, PersonalID_c* player_id, mActor_name_t cloth) { + Anmmem_c* mem; + int res = FALSE; + + if ( + animal != NULL && + player_id != NULL && + ITEM_NAME_GET_TYPE(cloth) == NAME_TYPE_ITEM1 && + ITEM_NAME_GET_CAT(cloth) == ITEM1_CAT_CLOTH && + !mNpc_CheckFreeAnimalPersonalID(&animal->id) && + !mPr_NullCheckPersonalID(player_id) + ) { + int memory_idx = mNpc_GetAnimalMemoryIdx(player_id, animal->memories, ANIMAL_MEMORY_NUM); + + if (memory_idx != -1) { + Anmmem_c* m = &animal->memories[memory_idx]; + + if (m->friendship > 30) { + Anmmem_c* memory = animal->memories; + int i; + + for (i = 0; i < ANIMAL_MEMORY_NUM; i++) { + if (i == memory_idx) { + memory->letter_info.has_present_cloth = TRUE; + } + else { + memory->letter_info.has_present_cloth = FALSE; + } + + memory->letter_info.wearing_present_cloth = FALSE; + memory++; + } + + animal->present_cloth = cloth; + res = TRUE; + } + } + } + + return res; +} + +extern int mNpc_SendMailtoNpc(Mail_c* mail) { + int res = FALSE; + Animal_c* animal; + AnmPersonalID_c anm_id; + int anm_idx; + int memory_idx; + Anmmem_c* memory; + Anmplmail_c* plmail; + int letter_rank; + Animal_c* anm; + + if (mMl_get_npcinfo_from_mail_name(&anm_id, &mail->header.recipient) == TRUE) { + anm = Save_Get(animals); + anm_idx = mNpc_SearchAnimalPersonalID(&anm_id); + + if (anm_idx != -1) { + animal = anm + anm_idx; + memory_idx = mNpc_GetAnimalMemoryIdx(&mail->header.sender.personalID, animal->memories, ANIMAL_MEMORY_NUM); + + if (memory_idx == -1) { + memory_idx = mNpc_ForceGetFreeAnimalMemoryIdx(anm, animal->memories, ANIMAL_MEMORY_NUM); + + if (memory_idx >= 0) { + mPr_CopyPersonalID(&animal->memories[memory_idx].memory_player_id, &mail->header.sender.personalID); + } + } + else { + mNpc_SetPresentCloth(animal, &mail->header.sender.personalID, mail->present); + } + + memory = animal->memories + memory_idx; + plmail = &memory->letter; + memory->letter_info.exists = TRUE; + mNpc_ClearAnimalMail(plmail); + mNpc_Mail2AnimalMail(plmail, mail); + letter_rank = mNpc_SetRemailCond(animal, memory, mail->content.body); + + if (mEv_CheckFirstJob() == TRUE) { + mQst_errand_c* first_job = mQst_GetFirstJobData(); + + if ( + (first_job->base.quest_kind == mQst_ERRAND_FIRSTJOB_SEND_LETTER || first_job->base.quest_kind == mQst_ERRAND_FIRSTJOB_SEND_LETTER2) && + first_job->base.progress != 0 + ) { + first_job->base.progress = 3; + memory->letter_info.send_reply = FALSE; + } + } + else { + int friendship; + + if (mLd_PlayerManKindCheck() == FALSE) { + int occur_idx = mQst_GetOccuredContestIdx(mQst_CONTEST_KIND_LETTER); + if (occur_idx == anm_idx) { + mQst_SetReceiveLetter(&animal->contest_quest, &mail->header.sender.personalID, mail->content.body, mail->present); + memory->letter_info.send_reply = FALSE; + } + } + + friendship = 3; + + if (letter_rank == mNpc_LETTER_RANK_BAD) { + friendship = -2; + } + + if (mail->present != EMPTY_NO) { + friendship += 3; + } + + mNpc_AddFriendship(memory, friendship); + } + + res = TRUE; + } + } + + return res; +} + +extern void mNpc_ClearRemail(Anmremail_c* remail) { + remail->date = mTM_rtcTime_ymd_clear_code; + mPr_ClearPlayerName(remail->name); + mLd_ClearLandName(remail->land_name); + remail->flags.cond = mNpc_LETTER_RANK_BAD; + remail->flags.looks = 0x7F; +} + +static void mNpc_GetRemailPresent(mActor_name_t* present) { + static int category_table[2] = { mSP_KIND_FURNITURE, mSP_KIND_CLOTH }; + + mSP_SelectRandomItem_New(NULL, present, 1, NULL, 0, category_table[RANDOM(4) & 1], mSP_LISTTYPE_ABC, FALSE); +} + +static int mNpc_GetHandbillz(Mail_c* mail, int super_num, int mail_a_num, int mail_b_num, int mail_c_num, int ps_num) { + static u8 tmp_super[MAIL_HEADER2_LEN]; + static u8 tmp_ps[MAIL_FOOTER2_LEN]; + + mHandbillz_Info_c handbill_info; + int res; + + handbill_info.super_buf_p = tmp_super; + handbill_info.super_buf_size = MAIL_HEADER2_LEN; + handbill_info.mail_buf_p = mail->content.body; + handbill_info.mail_buf_size = MAIL_BODY_LEN; + handbill_info.ps_buf_p = tmp_ps; + handbill_info.ps_buf_size = MAIL_FOOTER2_LEN; + handbill_info.super_no = super_num; + handbill_info.maila_no = mail_a_num; + handbill_info.mailb_no = mail_b_num; + handbill_info.mailc_no = mail_c_num; + handbill_info.ps_no = ps_num; + + res = mHandbillz_load(&handbill_info); + if (res == TRUE) { + mail->content.header_back_start = handbill_info.header_back_start; + mem_copy(mail->content.header, tmp_super, MAIL_HEADER_LEN); + mem_copy(mail->content.footer, tmp_ps, MAIL_FOOTER_LEN); + } + + return res; +} + +static void mNpc_SetRemailFreeString(PersonalID_c* pid, AnmPersonalID_c* anm_id, Anmremail_c* remail) { + static int base_str_no[11] = { + 0x314, // food + 0x334, // sports + 0x2F4, // hobby games + 0x6A1, // fish + 0x679, // insects + 0x354, // food tastes (sweet, spicy, ...) + 0x374, // feelings + 0x394, // music genres + 0x3D4, // food satisfaction feelings (delicious, stuffed, satisfying, ...) + 0x3F4, // "good" descriptors (brilliant, splendid, ...) + 0x3B4 // "bad" descriptors (unimpressive, awful, ...) + }; + + /* number of string entries per type */ + static f32 rand_max_table[11] = { + 32.0f, + 32.0f, + 32.0f, + 40.0f, + 40.0f, + 32.0f, + 32.0f, + 32.0f, + 32.0f, + 32.0f, + 32.0f + }; + + u8 free_str[16]; + AnmPersonalID_c* other_id; + int i; + + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + + if (remail == NULL) { + mNpc_GetNpcWorldNameAnm(free_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR1, free_str, ANIMAL_NAME_LEN); + other_id = mNpc_GetOtherAnimalPersonalID(anm_id, 1); + + if (other_id != NULL) { + mNpc_GetNpcWorldNameAnm(free_str, other_id); + mHandbill_Set_free_str(mHandbill_FREE_STR2, free_str, ANIMAL_NAME_LEN); + } + } + else { + mHandbill_Set_free_str(mHandbill_FREE_STR1, remail->name, ANIMAL_NAME_LEN); // foreign npc name + other_id = mNpc_GetOtherAnimalPersonalID(NULL, 0); + mNpc_GetNpcWorldNameAnm(free_str, other_id); + mHandbill_Set_free_str(mHandbill_FREE_STR2, free_str, ANIMAL_NAME_LEN); // random player town npc name + mHandbill_Set_free_str(mHandbill_FREE_STR14, remail->land_name, LAND_NAME_SIZE); // foreign town name + mHandbill_Set_free_str(mHandbill_FREE_STR15, mLd_GetLandName(), LAND_NAME_SIZE); // player town name + } + + for (i = 0; i < ARRAY_COUNT(base_str_no); i++) { + int rand = RANDOM_F(rand_max_table[i]); + + mString_Load_StringFromRom(free_str, ARRAY_COUNT(free_str), base_str_no[i] + rand); + mHandbill_Set_free_str(mHandbill_FREE_STR3 + i, free_str, ARRAY_COUNT(free_str)); + } +} + +static void mNpc_GetRemailGoodData(Mail_c* mail, PersonalID_c* pid, AnmPersonalID_c* anm_id, Anmremail_c* remail, u8 foreign) { + static int start_no[mNpc_LOOKS_NUM] = { + 0x020, + 0x040, + 0x000, + 0x060, + 0x080, + 0x0A0 + }; + + static int ohter_start_no[mNpc_LOOKS_NUM] = { + 0x0E0, + 0x100, + 0x0C0, + 0x120, + 0x140, + 0x160 + }; + + static int* start_no_table[2] = { + start_no, + ohter_start_no + }; + + mActor_name_t present = EMPTY_NO; + int msg_no; + int* msg_tbl; + int looks; + int give_present; + + if (remail == NULL) { + looks = anm_id->looks; + } + else { + looks = remail->flags.looks; + } + + msg_tbl = start_no_table[foreign]; + + give_present = RANDOM(4) & 1; + if (give_present == 0) { + mNpc_GetRemailPresent(&present); + } + + mNpc_SetRemailFreeString(pid, anm_id, remail); + msg_no = msg_tbl[looks]; + mNpc_GetHandbillz(mail, msg_no + RANDOM(32), msg_no + RANDOM(32), msg_no + RANDOM(16) + give_present * 16, msg_no + RANDOM(32), msg_no + RANDOM(32)); + mail->present = present; +} + +static void mNpc_GetRemailWrongData(Mail_c* mail, PersonalID_c* pid, AnmPersonalID_c* anm_id, Anmremail_c* remail, u8 foreign) { + static int mail_no[2] = { 0xC5, 0xD8 }; + static u8 tmp_super[MAIL_HEADER2_LEN]; + static u8 tmp_ps[MAIL_FOOTER2_LEN]; + + int looks; + int header_back_start; + int msg_no; + + if (remail == NULL) { + looks = anm_id->looks; + } + else { + looks = remail->flags.looks; + } + + mNpc_SetRemailFreeString(pid, anm_id, remail); + msg_no = mail_no[foreign] + looks * 3; + msg_no += RANDOM(3); + mHandbill_Load_HandbillFromRom(tmp_super, &header_back_start, tmp_ps, mail->content.body, msg_no); + mail->content.header_back_start = header_back_start; + mem_copy(mail->content.header, tmp_super, MAIL_HEADER_LEN); + mem_copy(mail->content.footer, tmp_ps, MAIL_FOOTER_LEN); + mail->present = EMPTY_NO; +} + +typedef void (*mNPC_GET_REMAIL_PROC)(Mail_c*, PersonalID_c*, AnmPersonalID_c*, Anmremail_c*, u8); + +static void mNpc_GetRemailData(Mail_c* mail, PersonalID_c* pid, AnmPersonalID_c* anm_id, Anmremail_c* remail, int cond, u8 foreign) { + static mNPC_GET_REMAIL_PROC get_remail[mNpc_LETTER_RANK_NUM] = { + &mNpc_GetRemailWrongData, + &mNpc_GetRemailGoodData + }; + + mActor_name_t paper; + int paper_no; + + (*get_remail[cond])(mail, pid, anm_id, remail, foreign); + mail->content.font = mMl_FONT_0; + mail->content.mail_type = mMl_TYPE_MAIL; + mPr_CopyPersonalID(&mail->header.recipient.personalID, pid); + mail->header.recipient.type = mMl_NAME_TYPE_PLAYER; + + if (remail == NULL) { + mMl_set_mail_name_npcinfo(&mail->header.sender, anm_id); + } + else { + mPr_CopyPlayerName(mail->header.sender.personalID.player_name, remail->name); + mLd_CopyLandName(mail->header.sender.personalID.land_name, remail->land_name); + mail->header.sender.type = mMl_NAME_TYPE_NPC; + } + + mSP_SelectRandomItem_New(NULL, &paper, 1, NULL, 0, mSP_KIND_PAPER, mSP_LISTTYPE_ABC, FALSE); + mail->content.paper_type = (paper - ITM_PAPER_START) % PAPER_UNIQUE_NUM; +} + +static Mail_c l_npc_mail; + +static int mNpc_SendRemailPostOffice(PersonalID_c* pid, AnmPersonalID_c* anm_id, Anmremail_c* remail, int cond, u8 foreign) { + Mail_c* mail = &l_npc_mail; + int res = FALSE; + + if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE) { + mMl_clear_mail(mail); + mNpc_GetRemailData(mail, pid, anm_id, remail, cond, foreign); + res = mPO_receipt_proc(mail, mPO_SENDTYPE_MAIL); + } + + return res; +} + +static int mNpc_CheckLetterTime(lbRTC_ymd_c* letter_date, lbRTC_time_c* rtc_time) { + int res = FALSE; + + if (letter_date->day != 0xFF && (letter_date->year != rtc_time->year || letter_date->month != rtc_time->month || letter_date->day != rtc_time->day)) { + res = TRUE; + } + + return res; +} + +// 25% + +extern void mNpc_Remail() { + Animal_c* animal = Save_Get(animals); + Anmremail_c* remail; + Private_c* priv = Common_Get(now_private); + Anmmem_c* memory; + lbRTC_time_c* rtc_time; + + rtc_time = Common_GetPointer(time.rtc_time); + + if (mPr_NullCheckPersonalID(&priv->player_ID) == FALSE && mLd_PlayerManKindCheck() == FALSE) { + int i; + + /* process replies from town villagers first */ + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalPersonalID(&animal->id) == FALSE) { + int mem_idx = mNpc_GetAnimalMemoryIdx(&priv->player_ID, animal->memories, ANIMAL_MEMORY_NUM); + + if (mem_idx != -1) { + memory = animal->memories + mem_idx; + + if (memory->letter_info.send_reply && mNpc_CheckLetterTime(&memory->letter.date, rtc_time) == TRUE) { + if (mNpc_SendRemailPostOffice(&priv->player_ID, &animal->id, NULL, memory->letter_info.cond, FALSE) != TRUE) { + break; + } + + memory->letter_info.send_reply = FALSE; + } + } + } + + animal++; + } + + /* process info from the last foreign villager (if any) */ + remail = &priv->remail; + if (remail->flags.looks != 0x7F && mNpc_SendRemailPostOffice(&priv->player_ID, &animal->id, remail, remail->flags.cond, TRUE) == TRUE) { + mNpc_ClearRemail(remail); + } + } +} + +extern u8 mNpc_GetPaperType() { + mActor_name_t paper; + + mSP_SelectRandomItem_New(NULL, &paper, 1, NULL, 0, mSP_KIND_PAPER, mSP_LISTTYPE_ABC, FALSE); + return (paper - ITM_PAPER_START) % PAPER_UNIQUE_NUM; +} + +static void mNpc_LoadMailDataCommon2(Mail_c* mail, PersonalID_c* pid, AnmPersonalID_c* anm_id, mActor_name_t present, u8 paper_type, int mail_no) { + u8 super[MAIL_HEADER2_LEN]; + u8 ps[MAIL_FOOTER2_LEN]; + int header_back_start; + + mMl_clear_mail(mail); + mHandbill_Load_HandbillFromRom2(super, MAIL_HEADER2_LEN, &header_back_start, ps, MAIL_FOOTER2_LEN, mail->content.body, mail_no); + mem_copy(mail->content.header, super, MAIL_HEADER_LEN); + mem_copy(mail->content.footer, ps, MAIL_FOOTER_LEN); + mail->content.header_back_start = header_back_start; + mail->content.font = mMl_FONT_0; + mail->content.mail_type = mMl_TYPE_MAIL; + mPr_CopyPersonalID(&mail->header.recipient.personalID, pid); + mail->header.recipient.type = mMl_NAME_TYPE_PLAYER; + mMl_set_mail_name_npcinfo(&mail->header.sender, anm_id); + mail->present = present; + mail->content.paper_type = paper_type; +} + +static void mNpc_GetEventPresent(mActor_name_t* present, int type) { + static int priority_table[mNpc_EVENT_MAIL_FRIEND_NUM] = { + mSP_LISTTYPE_RARE, + mSP_LISTTYPE_UNCOMMON, + mSP_LISTTYPE_COMMON + }; + + static int category_table[mNpc_EVENT_MAIL_FRIEND_NUM] = { + mSP_KIND_FURNITURE, + mSP_KIND_FURNITURE, + mSP_KIND_CLOTH + }; + + mSP_SelectRandomItem_New(NULL, present, 1, NULL, 0, category_table[type], priority_table[type], FALSE); +} + +static void mNpc_GetEventMail(Mail_c* mail, PersonalID_c* pid, AnmPersonalID_c* anm_id, int type, int looks) { + static u8 animal_name[ANIMAL_NAME_LEN]; + + mActor_name_t present; + + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + mNpc_GetNpcWorldNameAnm(animal_name, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR6, animal_name, ANIMAL_NAME_LEN); + mNpc_GetEventPresent(&present, type); + mNpc_LoadMailDataCommon2(mail, pid, anm_id, present, mNpc_GetPaperType(), 0x60 + looks * mNpc_EVENT_MAIL_FRIEND_NUM + type); +} + +static int mNpc_SendEventPresentMail(PersonalID_c* pid, int player_no, AnmPersonalID_c* anm_id, int type) { + Mail_c* mail = &l_npc_mail; + mHm_hs_c* home; + int looks = anm_id->looks; + int res = FALSE; + + home = Save_GetPointer(homes[mHS_get_arrange_idx(player_no)]); + + if (mPr_CheckCmpPersonalID(pid, &home->ownerID) == TRUE) { + int free_idx; + + mMl_clear_mail(mail); + free_idx = mMl_chk_mail_free_space(home->mailbox, HOME_MAILBOX_SIZE); + + if (free_idx != -1) { + mNpc_GetEventMail(mail, pid, anm_id, type, looks); + mMl_copy_mail(home->mailbox + free_idx, mail); + res = TRUE; + } + else if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE) { + mNpc_GetEventMail(mail, pid, anm_id, type, looks); + res = mPO_receipt_proc(mail, mPO_SENDTYPE_MAIL); + } + } + + return res; +} + +static void mNpc_SendEventPresentMailSex(int* selected, u8* type, Animal_c* animal, int animal_sex) { + int best_friend_idx = mNpc_GetAnimalMemoryBestFriend(animal->memories, ANIMAL_MEMORY_NUM); + int other_sex_best_friend_idx = mNpc_GetAnimalMemoryFriend_Land_Sex(animal->memories, ANIMAL_MEMORY_NUM, (~animal_sex) & 1); + + if (best_friend_idx != -1 && other_sex_best_friend_idx == best_friend_idx) { + selected[0] = other_sex_best_friend_idx; + type[0] = mNpc_EVENT_MAIL_BEST_FRIEND; + } + else if (other_sex_best_friend_idx != -1) { + selected[0] = other_sex_best_friend_idx; + + if (animal->memories[other_sex_best_friend_idx].friendship >= 80) { + type[0] = mNpc_EVENT_MAIL_OK_FRIEND; + } + else { + type[0] = mNpc_EVENT_MAIL_NOT_FRIEND; + } + } +} + +/* this used to be mNpc_SendEventPresentMail_common in DnM and DnM+ (handled both valentine's day & white day) */ + +extern int mNpc_SendVtdayMail() { + u8 types[ANIMAL_NUM_MAX]; + int other_sex_best_friends[ANIMAL_NUM_MAX]; + Animal_c* animal_p; + int sent; + u8 player_bitfield; + int mem_idx; + PersonalID_c* pid; + int player_no; + int i; + int j; + + animal_p = Save_Get(animals); + sent = 0; + player_bitfield = 0b1111; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + other_sex_best_friends[i] = -1; + + if (mNpc_CheckFreeAnimalPersonalID(&animal_p->id) == FALSE) { + mNpc_SendEventPresentMailSex(&other_sex_best_friends[i], &types[i], animal_p, mNpc_GetAnimalSex(animal_p)); + } + + animal_p++; + } + + for (i = 0; i < mNpc_EVENT_MAIL_FRIEND_NUM; i++) { + animal_p = Save_Get(animals); + + for (j = 0; j < ANIMAL_NUM_MAX; j++) { + if (other_sex_best_friends[j] >= 0 && types[j] == i) { + pid = &animal_p->memories[other_sex_best_friends[j]].memory_player_id; + player_no = mPr_GetPrivateIdx(pid); + + if (((player_bitfield >> player_no) & 1) == 1) { + if (mNpc_SendEventPresentMail(pid, player_no, &animal_p->id, i) == TRUE) { + other_sex_best_friends[j] = -1; + sent++; + } + else { + player_bitfield &= ~(1 << player_no); // remove player from bitfield because they can't receive letters + } + } + } + + if (player_bitfield == 0) { + return sent; // all players cannot receive any further mail + } + + animal_p++; + } + } + + return sent; +} + +static void mNpc_GetBirthdayPresent(mActor_name_t* present) { + static u8 category_table[5] = { mSP_KIND_FURNITURE, mSP_KIND_FURNITURE, mSP_KIND_CLOTH, mSP_KIND_CLOTH, 0xFF }; + + int selected = category_table[RANDOM(ARRAY_COUNT(category_table))]; + + if (selected == 0xFF) { + mSP_RandomUmbSelect(present, 1); + } + else { + mSP_SelectRandomItem_New(NULL, present, 1, NULL, 0, selected, mSP_LISTTYPE_RARE, FALSE); + } +} + +static void mNpc_GetBirthdayCard(Mail_c* mail, PersonalID_c* pid, AnmPersonalID_c* anm_id) { + static u8 animal_name[ANIMAL_NAME_LEN]; + + u8 item_name[mIN_ITEM_NAME_LEN]; + int mail_no = 0xEA + anm_id->looks * 3 + RANDOM(3); + mActor_name_t present; + + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + mNpc_GetNpcWorldNameAnm(animal_name, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR1, animal_name, ANIMAL_NAME_LEN); + mNpc_GetBirthdayPresent(&present); + mIN_copy_name_str(item_name, present); + mHandbill_Set_free_str(mHandbill_FREE_STR2, item_name, mIN_ITEM_NAME_LEN); + mNpc_LoadMailDataCommon2(mail, pid, anm_id, present, mNpc_GetPaperType(), mail_no); +} + +static int mNpc_SendBirthdayCard(PersonalID_c* pid, int player_no, AnmPersonalID_c* anm_id) { + Mail_c* mail = &l_npc_mail; + mHm_hs_c* home; + int free_idx; + int res = FALSE; + + home = Save_GetPointer(homes[mHS_get_arrange_idx(player_no)]); + + if (mPr_NullCheckPersonalID(pid) == FALSE && mPr_CheckCmpPersonalID(pid, &home->ownerID) == TRUE) { + mMl_clear_mail(mail); + free_idx = mMl_chk_mail_free_space(home->mailbox, HOME_MAILBOX_SIZE); + + if (free_idx != -1) { + mNpc_GetBirthdayCard(mail, pid, anm_id); + mMl_copy_mail(home->mailbox + free_idx, mail); + res = TRUE; + } + else if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE) { + mNpc_GetBirthdayCard(mail, pid, anm_id); + res = mPO_receipt_proc(mail, mPO_SENDTYPE_MAIL); + } + } + + return res; +} + +extern int mNpc_CheckFriendship(PersonalID_c* pid, Animal_c* animal) { + int best_idx; + int idx = -1; + + if (mNpc_CheckFreeAnimalPersonalID(&animal->id) == FALSE) { + best_idx = mNpc_GetHighestFriendshipIdx(animal->memories, ANIMAL_MEMORY_NUM); + + if ( + best_idx != -1 && + mPr_CheckCmpPersonalID(pid, &animal->memories[best_idx].memory_player_id) && + Common_Get(now_private)->birthday_present_npc != animal->id.npc_id + ) { + idx = best_idx; + } + } + + return idx; +} + +/* @unused */ +/* +extern int mNpc_SendEventBirthdayCard(PersonalID_c* pid) { + return FALSE; // seemingly stubbed +} +*/ + +extern int mNpc_SendEventBirthdayCard2(PersonalID_c* pid, int player_no) { + Animal_c* animal = Save_Get(animals); + int res = FALSE; + int i; + + if (pid != NULL && mLd_CheckThisLand(pid->land_name, pid->land_id) == TRUE) { + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFriendship(pid, animal) != -1 && mNpc_SendBirthdayCard(pid, player_no, &animal->id) == TRUE) { + res = TRUE; + } + + animal++; + } + + if (Common_Get(now_private)->celebrated_birthday_year != Common_Get(time.rtc_time).year) { + Common_Get(now_private)->birthday_present_npc = EMPTY_NO; + Common_Get(now_private)->celebrated_birthday_year = Common_Get(time.rtc_time).year; + } + } + + return res; +} + +static void mNpc_GetXmasCardData(Mail_c* mail, PersonalID_c* pid) { + int header_back_start; + + mHandbill_Load_HandbillFromRom(mail->content.header, &header_back_start, mail->content.footer, mail->content.body, 0xD7); + mail->content.header_back_start = header_back_start; + mail->content.font = mMl_FONT_0; + mail->content.mail_type = mMl_TYPE_XMAS; + mPr_CopyPersonalID(&mail->header.recipient.personalID, pid); + mail->header.recipient.type = mMl_NAME_TYPE_PLAYER; + mail->present = FTR_FAMICOM_BALLOON_FIGHT; + mail->content.paper_type = 22; // festive paper +} + +extern int mNpc_SendEventXmasCard(PersonalID_c* pid, int player_no) { + Mail_c* mail = &l_npc_mail; + mHm_hs_c* home; + int res = FALSE; + + if (mPr_NullCheckPersonalID(pid) == FALSE) { + home = Save_GetPointer(homes[mHS_get_arrange_idx(player_no)]); + + if (mPr_CheckCmpPersonalID(pid, &home->ownerID) == TRUE) { + int free_space = mMl_chk_mail_free_space(home->mailbox, HOME_MAILBOX_SIZE); + + if (free_space != -1) { + mMl_clear_mail(mail); + mNpc_GetXmasCardData(mail, pid); + mMl_copy_mail(home->mailbox + free_space, mail); + res = TRUE; + } + } + } + + return res; +} + +extern int mNpc_GetPresentClothMemoryIdx(Anmmem_c* memory) { + int i; + int res = -1; + + if (memory != NULL) { + for (i = 0; i < ANIMAL_MEMORY_NUM; i++) { + if (memory->letter_info.wearing_present_cloth == TRUE) { + res = i; + break; + } + + memory++; + } + } + + return res; +} + +extern int mNpc_GetPresentClothMemoryIdx_rnd(Anmmem_c* memory) { + Anmmem_c* mem_p = memory; + int selected; + int cloth_mem = 0; + int res = -1; + int i; + + if (memory != NULL) { + for (i = 0; i < ANIMAL_MEMORY_NUM; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE && memory->letter_info.wearing_present_cloth == TRUE) { + cloth_mem++; + } + + memory++; + } + + if (cloth_mem > 0) { + selected = RANDOM(cloth_mem); + memory = mem_p; + for (i = 0; i < ANIMAL_MEMORY_NUM; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE && memory->letter_info.wearing_present_cloth == TRUE) { + if (selected <= 0) { + res = i; + break; + } + else { + selected--; + } + } + + memory++; + } + } + } + + return res; +} + +extern int mNpc_CheckTalkPresentCloth(Animal_c* animal) { + int res = FALSE; + + if (animal != NULL && mNpc_GetPresentClothMemoryIdx(animal->memories) != -1) { + res = TRUE; + } + + return res; +} + +extern void mNpc_ChangePresentCloth() { + Animal_c* animal = Save_Get(animals); + Anmmem_c* memory; + mActor_name_t present_cloth; + int i; + int j; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + present_cloth = animal->present_cloth; + + if ( + mNpc_CheckFreeAnimalPersonalID(&animal->id) == FALSE && + present_cloth != EMPTY_NO && + ITEM_NAME_GET_TYPE(present_cloth) == NAME_TYPE_ITEM1 && + ITEM_NAME_GET_CAT(present_cloth) == ITEM1_CAT_CLOTH + ) { + memory = animal->memories; + + for (j = 0; j < ANIMAL_MEMORY_NUM; j++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE && memory->letter_info.has_present_cloth == TRUE) { + memory->letter_info.wearing_present_cloth = TRUE; + memory->letter_info.has_present_cloth = FALSE; + animal->cloth = present_cloth; + break; + } + + memory++; + } + } + + animal++; + } +} + +extern u8* mNpc_GetWordEnding(ACTOR* actor) { + NPC_ACTOR* npc; + u8* word_ending; + + /* @BUG - devs used & instead of && */ + #ifndef BUGFIXES + if (actor != NULL & actor->part == ACTOR_PART_NPC) { + #else + if (actor != NULL && actor->part == ACTOR_PART_NPC) { + #endif + npc = (NPC_ACTOR*)actor; + + if (npc->npc_info.animal != NULL) { + word_ending = npc->npc_info.animal->catchphrase; + } + else { + word_ending = l_no_ending_npc_ending; + } + } + else { + word_ending = l_no_ending_npc_ending; + } + + return word_ending; +} + +extern void mNpc_ResetWordEnding(ACTOR* actor) { + if (actor != NULL && actor->part == ACTOR_PART_NPC) { + NPC_ACTOR* npc = (NPC_ACTOR*)actor; + Animal_c* animal = npc->npc_info.animal; + + if (animal != NULL) { + mString_Load_StringFromRom(animal->catchphrase, ANIMAL_CATCHPHRASE_LEN, npc_def_list[animal->id.npc_id & 0xFFF].catchphrase_str_idx); + } + } +} + +extern int mNpc_GetFreeEventNpcIdx() { + mNpc_EventNpc_c* event_npc = Common_Get(event_npc); + int i; + + for (i = 0; i < mNpc_EVENT_NPC_NUM; i++) { + if (event_npc->in_use == FALSE) { + return i; + } + + event_npc++; + } + + return -1; +} + +extern int mNpc_RegistEventNpc(mActor_name_t event_id, mActor_name_t texture_id, mActor_name_t npc_id, mActor_name_t cloth_id) { + int free_idx = mNpc_GetFreeEventNpcIdx(); + int res = FALSE; + + if (free_idx != -1) { + mNpc_EventNpc_c* event_npc = Common_GetPointer(event_npc[free_idx]); + + if (cloth_id != EMPTY_NO) { + int valid = (cloth_id == RSV_CLOTH) || (cloth_id >= ITM_CLOTH_START && cloth_id < ITM_CLOTH_END); + + if (valid == FALSE) { + cloth_id = ITM_CLOTH000; + } + } + + event_npc->event_id = event_id; + event_npc->texture_id = texture_id; + event_npc->npc_id = npc_id; + event_npc->cloth_id = cloth_id; + event_npc->exists = FALSE; + event_npc->in_use = TRUE; + + res = TRUE; + } + + return res; +} + +extern void mNpc_UnRegistEventNpc(mNpc_EventNpc_c* npc) { + mNpc_EventNpc_c* event_npc = Common_Get(event_npc); + int i; + + for (i = 0; i < mNpc_EVENT_NPC_NUM; i++) { + if (event_npc == npc) { + bzero(npc, sizeof(mNpc_EventNpc_c)); + break; + } + + event_npc++; + } +} + +extern void mNpc_ClearEventNpc() { + bzero(Common_Get(event_npc), mNpc_EVENT_NPC_NUM * sizeof(mNpc_EventNpc_c)); +} + +extern mNpc_EventNpc_c* mNpc_GetSameEventNpc(mActor_name_t event_id) { + mNpc_EventNpc_c* event_npc = Common_Get(event_npc); + int i; + + for (i = 0; i < mNpc_EVENT_NPC_NUM; i++) { + if (event_npc->event_id == event_id) { + return event_npc; + } + + event_npc++; + } + + return NULL; +} + +extern int mNpc_GetFreeMaskNpcIdx() { + mNpc_MaskNpc_c* mask_npc = Common_Get(mask_npc); + int i; + + for (i = 0; i < mNpc_MASK_NPC_NUM; i++) { + if (mask_npc->in_use == FALSE) { + return i; + } + + mask_npc++; + } + + return -1; +} + +extern int mNpc_RegistMaskNpc(mActor_name_t mask_id, mActor_name_t npc_id, mActor_name_t cloth_id) { + int free_idx = mNpc_GetFreeMaskNpcIdx(); + int res = FALSE; + + if (free_idx != -1) { + mNpc_MaskNpc_c* mask_npc = Common_GetPointer(mask_npc[free_idx]); + + if (cloth_id != EMPTY_NO) { + int valid = (cloth_id == RSV_CLOTH) || (cloth_id >= ITM_CLOTH_START && cloth_id < ITM_CLOTH_END); + + if (valid == FALSE) { + cloth_id = ITM_CLOTH000; + } + } + + mask_npc->mask_id = mask_id; + mask_npc->npc_id = npc_id; + mask_npc->exists = FALSE; + mask_npc->in_use = TRUE; + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC) { + int animal_idx = mNpc_SearchAnimalinfo(Save_Get(animals), npc_id, ANIMAL_NUM_MAX); + + if (animal_idx != -1) { + mNpc_CopyAnimalInfo(&mask_npc->animal_data, Save_GetPointer(animals[animal_idx])); + } + else { + mNpc_ClearAnimalInfo(&mask_npc->animal_data); + mNpc_SetDefAnimal(&mask_npc->animal_data, npc_id, npc_def_list); + } + + if (cloth_id == EMPTY_NO) { + mask_npc->cloth_id = mask_npc->animal_data.cloth; + } + else { + mask_npc->cloth_id = cloth_id; + } + } + else { + mask_npc->cloth_id = cloth_id; + } + + res = TRUE; + } + + return res; +} + +extern void mNpc_UnRegistMaskNpc(mNpc_MaskNpc_c* npc) { + mNpc_MaskNpc_c* mask_npc = Common_Get(mask_npc); + int i; + + for (i = 0; i < mNpc_MASK_NPC_NUM; i++) { + if (mask_npc == npc) { + bzero(npc, sizeof(mNpc_MaskNpc_c)); + break; + } + + mask_npc++; + } +} + +extern void mNpc_ClearMaskNpc() { + bzero(Common_Get(mask_npc), mNpc_MASK_NPC_NUM * sizeof(mNpc_MaskNpc_c)); +} + +extern mNpc_MaskNpc_c* mNpc_GetSameMaskNpc(mActor_name_t mask_id) { + mNpc_MaskNpc_c* mask_npc = Common_Get(mask_npc); + int i; + + for (i = 0; i < mNpc_MASK_NPC_NUM; i++) { + if (mask_npc->mask_id == mask_id) { + return mask_npc; + } + + mask_npc++; + } + + return NULL; +} + +extern u8 mNpc_GetLooks(mActor_name_t npc_id) { + u8 looks = mNpc_LOOKS_GIRL; + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC) { + looks = npc_looks_table[npc_id & 0xFFF]; + } + + return looks; +} + +static void mNpc_SetDefAnimalInfo(Animal_c* animal, mActor_name_t npc_id, u8 looks, mNpc_Default_Data_c* def_data) { + mLd_land_info_c* land_info = Save_GetPointer(land_info); + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC) { + animal->id.npc_id = npc_id; + animal->id.looks = looks; + animal->cloth = def_data->cloth; + animal->umbrella_id = def_data->umbrella; + mString_Load_StringFromRom(animal->catchphrase, ANIMAL_CATCHPHRASE_LEN, def_data->catchphrase_str_idx); + animal->id.land_id = land_info->id; + mLd_CopyLandName(animal->id.land_name, land_info->name); + } +} + +extern void mNpc_SetDefAnimalCloth(Animal_c* animal) { + mActor_name_t npc_id = animal->id.npc_id; + + if (ITEM_NAME_GET_TYPE(npc_id) != NAME_TYPE_NPC) { + return; + } + + if ((npc_id & 0xFFF) < 0) { + return; + } + + if ((npc_id & 0xFFF) >= NPC_NUM) { + return; + } + + animal->cloth = npc_def_list[npc_id & 0xFFF].cloth; + animal->cloth_original_id = 0xFF; +} + +extern void mNpc_SetDefAnimalUmbrella(Animal_c* animal) { + mActor_name_t npc_id = animal->id.npc_id; + + if (ITEM_NAME_GET_TYPE(npc_id) != NAME_TYPE_NPC) { + return; + } + + if ((npc_id & 0xFFF) < 0) { + return; + } + + if ((npc_id & 0xFFF) >= NPC_NUM) { + return; + } + + animal->umbrella_id = npc_def_list[npc_id & 0xFFF].umbrella; +} + +extern void mNpc_SetDefAnimal(Animal_c* animal, mActor_name_t npc_id, mNpc_Default_Data_c* def_data) { + int idx = npc_id & 0xFFF; + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC) { + mNpc_SetDefAnimalInfo(animal, npc_id, npc_looks_table[idx], &def_data[idx]); + } +} + +static void mNpc_SetHaveAppeared(mActor_name_t npc_id) { + u8 v; + int idx; + int bit; + u8* used_tbl = Save_Get(npc_used_tbl); + int i; + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC) { + idx = npc_id & 0xFFF; + i = idx / 8; + + if (i < ARRAY_COUNT(Save_Get(npc_used_tbl))) { + /* what in the fuck */ + v = used_tbl[i]; + bit = idx & 7; + bit = 1 << bit; + v |= bit; + used_tbl[i] = v; + } + } +} + +static int mNpc_GetHaveAppeared_idx(int idx) { + u8* used_tbl = Save_Get(npc_used_tbl); + int i = idx / 8; + int res = TRUE; + + if (i < ARRAY_COUNT(Save_Get(npc_used_tbl))) { + res = (used_tbl[i] >> (idx & 7)) & 1; + } + + return res; +} + +static int mNpc_GetDefGrowPermission(int idx, s8* grow_list, int count) { + int res = mNpc_GROW_MOVE_IN; + + if (idx < count) { + res = grow_list[idx]; + } + + return res; +} + +static int mNpc_GetLooks2NotHaveAppearedNum(u8 looks) { + u8* looks_tbl; + s8* grow_list = npc_grow_list; + int num = 0; + int i; + + if (looks < mNpc_LOOKS_NUM) { + looks_tbl = npc_looks_table; + + for (i = 0; i < NPC_NUM; i++) { + if (looks == looks_tbl[0]) { + int grow_type = mNpc_GetDefGrowPermission(i, grow_list, NPC_NUM); + + if (mNpc_GetHaveAppeared_idx(i) == FALSE && (grow_type == mNpc_GROW_STARTER || grow_type == mNpc_GROW_MOVE_IN)) { + num++; + } + } + + looks_tbl++; + } + } + + return num; +} + +static void mNpc_ResetHaveAppeared_common(u8* npc_used_tbl, Animal_c* animal) { + int i; + + bzero(npc_used_tbl, sizeof(Save_Get(npc_used_tbl))); + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE) { + mNpc_SetHaveAppeared(animal->id.npc_id); + } + + animal++; + } +} + +static void mNpc_ResetHaveAppeared() { + s8* grow_list = npc_grow_list; + int i; + + for (i = 0; i < NPC_NUM; i++) { + u32 grow_perm = mNpc_GetDefGrowPermission(i, grow_list, NPC_NUM); + + if (mNpc_GetHaveAppeared_idx(i) == FALSE && grow_perm <= mNpc_GROW_MOVE_IN) { + break; + } + } + + /* All animals have appeared in town (islanders excluded) */ + if (i == NPC_NUM) { + mNpc_ResetHaveAppeared_common(Save_Get(npc_used_tbl), Save_Get(animals)); + } +} + +static void mNpc_DecideLivingNpcMax(Animal_c* animal, u8 count, int malloc_flag) { + static int fakeTable[NPC_NUM]; + + mNpc_Default_Data_c* def_list; + s8* grow_list; + u8 looks_bitfield = 0; + int i = 0; + int animal_idx = 0; + + bzero(fakeTable, sizeof(fakeTable)); + mNpc_MakeRandTable(fakeTable, NPC_NUM, NPC_NUM); + def_list = npc_def_list; + grow_list = npc_grow_list; + + while (count != 0) { + if (animal == NULL) { + break; + } + + if (animal->id.npc_id == EMPTY_NO) { + int idx = fakeTable[i]; + mActor_name_t npc_id = NPC_START | idx; + int grow_perm = mNpc_GetDefGrowPermission(idx, grow_list, NPC_NUM); + + if (grow_perm == mNpc_GROW_STARTER) { + int looks = npc_looks_table[idx]; + + if (((looks_bitfield >> looks) & 1) == 0) { + mNpc_SetDefAnimal(animal, npc_id, def_list); + looks_bitfield |= (1 << looks); + mNpc_SetHaveAppeared(npc_id); + mNpc_ResetAnimalRelation(animal_idx); + animal++; + animal_idx++; + count--; + } + } + else if (grow_perm == -1) { + mNpc_SetHaveAppeared(npc_id); + } + } + else { + animal++;; + animal_idx++; + count--; + } + + i++; + } +} + +extern void mNpc_SetAnimalTitleDemo(mNpc_demo_npc_c* demo_npc, Animal_c* animal, GAME* game) { + mNpc_Default_Data_c* def_list = npc_def_list; + int i; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + mNpc_SetDefAnimal(animal, demo_npc->npc_name, def_list); + animal->home_info.type_unused = 0; + animal->home_info.block_x = demo_npc->block_x; + animal->home_info.block_z = demo_npc->block_z; + animal->home_info.ut_x = demo_npc->ut_x; + animal->home_info.ut_z = demo_npc->ut_z; + + animal++; + demo_npc++; + } +} + +/* @unused */ +extern int mNpc_GetReservedUtNum(int* ut_x, int* ut_z, mActor_name_t* item) { + f32 reserve_num = 0.0f; + int rand; + int res = FALSE; + int i; + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if ((*item >= SIGN00 && *item < WISHING_WELL) || *item == DUMMY_RESERVE) { + reserve_num++; + } + + item++; + } + + if (reserve_num > 0.0f) { + item -= UT_TOTAL_NUM; + rand = RANDOM(reserve_num); + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if ((*item >= SIGN00 && *item < WISHING_WELL) || *item == DUMMY_RESERVE) { + if (rand == 0) { + *ut_x = i % UT_Z_NUM; + *ut_z = i / UT_X_NUM; + res = TRUE; + + break; + } + else { + rand--; + } + } + + item++; + } + } + + return res; +} + +/* @unused */ +extern int mNpc_BlockNum2ReservedUtNum(int* ut_x, int* ut_z, int bx, int bz) { + mActor_name_t* items = mFI_BkNumtoUtFGTop(bx, bz); + int res = FALSE; + + if (items != NULL) { + res = mNpc_GetReservedUtNum(ut_x, ut_z, items); + } + + return res; +} + +extern void mNpc_MakeReservedListBeforeFieldct(Anmhome_c* reserved, int reserved_num, u8* reserved_count) { + mFM_fg_c* fg = Save_GetPointer(fg[0][0]); + Anmhome_c* reserved_p = reserved; + mActor_name_t* item; + u8 num = 0; + u8 i; + u8 j; + u8 k; + u8 l; + + for (i = 0; i < reserved_num; i++) { + reserved->block_x = -1; + reserved->block_z = -1; + reserved->ut_x = -1; + reserved->ut_z = -1; + reserved++; + } + + reserved = reserved_p; + for (i = 0; i < FG_BLOCK_Z_NUM; i++) { + for (j = 0; j < FG_BLOCK_X_NUM; j++) { + item = &fg->items[0][0]; + + for (k = 0; k < UT_Z_NUM; k++) { + for (l = 0; l < UT_X_NUM; l++) { + if (mNT_IS_RESERVE(*item)) { + num++; + reserved->block_x = j + 1; + reserved->block_z = i + 1; + reserved->ut_x = l; + reserved->ut_z = k; + reserved++; + if (num >= reserved_num) { + *reserved_count = num; + return; + } + } + + item++; + } + } + + fg++; + } + } + + *reserved_count = num; +} + +extern void mNpc_MakeReservedListAfterFieldct(Anmhome_c* reserved, int reserved_num, u8* reserved_count, u8 bx_max, u8 bz_max) { + mFM_block_info_c* block_info = mFI_GetBlockTopP(); + Anmhome_c* reserved_p = reserved; + mActor_name_t* item; + u8 num = 0; + u8 i; + u8 j; + u8 k; + u8 l; + + for (i = 0; i < reserved_num; i++) { + reserved->block_x = -1; + reserved->block_z = -1; + reserved->ut_x = -1; + reserved->ut_z = -1; + reserved++; + } + + reserved = reserved_p; + for (i = 0; i < bz_max; i++) { + for (j = 0; j < bx_max; j++) { + item = block_info->fg_info.items_p; + + for (k = 0; k < UT_Z_NUM; k++) { + for (l = 0; l < UT_X_NUM; l++) { + if (mNT_IS_RESERVE(*item)) { + num++; + reserved->block_x = j; + reserved->block_z = i; + reserved->ut_x = l; + reserved->ut_z = k; + reserved++; + if (num >= reserved_num) { + *reserved_count = num; + return; + } + } + + item++; + } + } + + block_info++; + } + } + + *reserved_count = num; +} + +static void mNpc_BuildHouseBeforeFieldct(mActor_name_t npc_id, int bx, int bz, int ut_x, int ut_z) { + static int ut_d[9][2] = { + { 0, 0 }, + { -1, 1 }, + { 0, 1 }, + { 1, 1 }, + { -1, 0 }, + { 1, 0 }, + { -1, -1 }, + { 0, -1 }, + { 1, -1 } + }; + + static mActor_name_t set_fg[9] = { + EMPTY_NO, // npc house id + ACTOR_PROP_VILLAGER_SIGNBOARD, + RSV_NO, + RSV_NO, + RSV_NO, + RSV_NO, + RSV_NO, + RSV_NO, + RSV_NO + }; + + u16* deposit; + mFM_fg_c* fg; + int x; + int z; + int i; + mActor_name_t house = npc_id - 0x10000 + 0x7000; // this shifts it to the house range 0x50XX + + if ( + bx >= 0 && bx < FG_BLOCK_X_NUM && + bz >= 0 && bz < FG_BLOCK_Z_NUM && + ut_x > 0 && ut_x < (UT_X_NUM - 1) && + ut_z > 0 && ut_z < (UT_Z_NUM - 1) + ) { + set_fg[0] = house; + fg = Save_GetPointer(fg[bz][bx]); + + deposit = mFI_GetDepositP(bx + 1, bz + 1); + + for (i = 0; i < ARRAY_COUNT(set_fg); i++) { + x = ut_d[i][0] + ut_x; + z = ut_d[i][1] + ut_z; + + mPB_keep_item(fg->items[z][x]); + if (deposit != NULL) { + mFI_BlockDepositOFF(deposit, x, z); + } + + fg->items[z][x] = set_fg[i]; + } + } +} + +static void mNpc_DestroyHouse(Anmhome_c* home) { + u8 bx = home->block_x - 1; + u8 bz = home->block_z - 1; + u8 ut_x = home->ut_x; + u8 ut_z = home->ut_z - 1; + + if (bx < FG_BLOCK_X_NUM && bz < FG_BLOCK_Z_NUM && ut_x > 0 && ut_x < (UT_X_NUM - 1) && ut_z > 0 && ut_z < (UT_Z_NUM - 1)) { + mActor_name_t reserve_no = mFM_GetReseveName(home->block_x, home->block_z); + + Save_Get(fg[bz][bx]).items[ut_z][ut_x] = reserve_no; + Save_Get(fg[bz][bx]).items[ut_z + 1][ut_x - 1] = EMPTY_NO; + Save_Get(fg[bz][bx]).items[ut_z + 1][ut_x] = EMPTY_NO; + Save_Get(fg[bz][bx]).items[ut_z + 1][ut_x + 1] = EMPTY_NO; + Save_Get(fg[bz][bx]).items[ut_z][ut_x - 1] = EMPTY_NO; + Save_Get(fg[bz][bx]).items[ut_z][ut_x + 1] = EMPTY_NO; + Save_Get(fg[bz][bx]).items[ut_z - 1][ut_x - 1] = EMPTY_NO; + Save_Get(fg[bz][bx]).items[ut_z - 1][ut_x] = EMPTY_NO; + Save_Get(fg[bz][bx]).items[ut_z - 1][ut_x + 1] = EMPTY_NO; + } +} + +static void mNpc_SetNpcHome(Animal_c* animal, Anmhome_c* reserved, u8 reserved_num) { + static int fake_table[60]; + + Anmhome_c* home; + int* fake_table_p = fake_table; + int n = 0; + int i; + int idx; + + if (reserved_num > ARRAY_COUNT(fake_table)) { + reserved_num = ARRAY_COUNT(fake_table); + } + + + if (reserved_num > 0) { + bzero(fake_table_p, sizeof(fake_table)); + mNpc_MakeRandTable(fake_table_p, reserved_num, ARRAY_COUNT(fake_table) / 2); + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (ITEM_NAME_GET_TYPE(animal->id.npc_id) == NAME_TYPE_NPC && animal->home_info.block_x == 0xFF && animal->home_info.block_z == 0xFF) { + idx = fake_table_p[n]; + + if (idx >= reserved_num) { + /* I don't like this */ + while ((idx = fake_table_p[n], idx >= reserved_num) && n < reserved_num) { + n++; + } + + if (n >= reserved_num) { + return; + } + } + + if (idx < reserved_num) { + home = &reserved[idx]; + + animal->home_info.block_x = home->block_x; + animal->home_info.block_z = home->block_z; + animal->home_info.ut_x = home->ut_x; + animal->home_info.ut_z = home->ut_z + 1; + mNpc_BuildHouseBeforeFieldct(animal->id.npc_id, animal->home_info.block_x - 1, animal->home_info.block_z - 1, animal->home_info.ut_x, home->ut_z); + } + + n++; + } + + if (n >= reserved_num) { + break; + } + + animal++; + } + } +} + +extern mNpc_InitNpcData() { + static Anmhome_c reserved[60]; + + u8 reserved_num = 0; + + mNpc_MakeReservedListBeforeFieldct(reserved, ARRAY_COUNT(reserved), &reserved_num); + mNpc_SetNpcHome(Save_Get(animals), reserved, reserved_num); +} + +extern void mNpc_InitNpcList(mNpc_NpcList_c* npclist, int count) { + int i; + + if (npclist != NULL) { + bzero(npclist, count * sizeof(mNpc_NpcList_c)); + + for (i = 0; i < count; i++) { + npclist->name = EMPTY_NO; + npclist->field_name = RSV_NO; + mQst_ClearQuestInfo(&npclist->quest_info); + npclist->house_data.type = 0xFF; + npclist->house_data.palette = 0xFF; + npclist->house_data.wall_id = 0xFF; + npclist->house_data.floor_id = 0xFF; + npclist->house_data.main_layer_id = 203; + + npclist++; + } + } +} + +extern void mNpc_SetNpcList(mNpc_NpcList_c* npclist, Animal_c* animal, int count, int malloc_flag) { + xyz_t pos; + mActor_name_t npc_id; + int i; + mNpc_NpcHouseData_c* house_list = npc_house_list; + + for (i = 0; i < count; i++) { + npc_id = animal->id.npc_id; + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC && animal->home_info.block_x != 0xFF) { + npclist->name = npc_id; + + npclist->house_position.x = animal->home_info.block_x * mFI_BK_WORLDSIZE_X_F; + npclist->house_position.z = animal->home_info.block_z * mFI_BK_WORLDSIZE_Z_F; + mFI_UtNum2PosXZInBk(&pos.x, &pos.z, animal->home_info.ut_x, animal->home_info.ut_z); + npclist->house_position.x += pos.x; + npclist->house_position.z += pos.z; + npclist->house_position.y = 0.0f; + + npclist->position.x = npclist->house_position.x; + npclist->position.y = npclist->house_position.y; + npclist->position.z = npclist->house_position.z; + + npclist->appear_flag = TRUE; + npclist->reward_furniture = EMPTY_NO; + + npclist->house_data.type = house_list[npc_id & 0xFFF].type; + npclist->house_data.palette = house_list[npc_id & 0xFFF].palette; + npclist->house_data.wall_id = house_list[npc_id & 0xFFF].wall_id; + npclist->house_data.floor_id = house_list[npc_id & 0xFFF].floor_id; + npclist->house_data.main_layer_id = house_list[npc_id & 0xFFF].main_layer_id; + npclist->house_data.secondary_layer_id = house_list[npc_id & 0xFFF].secondary_layer_id; + } + + npclist++; + animal++; + } +} + +extern void mNpc_SetNpcinfo(ACTOR* actor, s8 npc_info_idx) { + if (actor->part == ACTOR_PART_NPC) { + NPC_ACTOR* npc = (NPC_ACTOR*)actor; + mActor_name_t npc_id = actor->npc_id; + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_SPNPC) { + mNpc_EventNpc_c* event_npc = mNpc_GetSameEventNpc(npc_id); + + if (event_npc != NULL) { + npc_info_idx = mNpc_SearchAnimalinfo(Save_Get(animals), event_npc->npc_id, ANIMAL_NUM_MAX); + } + } + + if (npc_info_idx >= 0 && npc_info_idx < ANIMAL_NUM_MAX) { + npc->npc_info.animal = Save_GetPointer(animals[npc_info_idx]); + npc->npc_info.list = Common_GetPointer(npclist[npc_info_idx]); + } + else if (npc_info_idx == -ANIMAL_NUM_MAX) { + npc->npc_info.animal = &Save_Get(island).animal; + npc->npc_info.list = Common_GetPointer(island_npclist[0]); + } + else { + mNpc_MaskNpc_c* mask_npc = mNpc_GetSameMaskNpc(npc_id); + + if (mask_npc != NULL) { + if (ITEM_NAME_GET_TYPE(mask_npc->npc_id) == NAME_TYPE_NPC) { + npc->npc_info.animal = &mask_npc->animal_data; + npc->npc_info.list = NULL; + } + else { + npc->npc_info.animal = NULL; + npc->npc_info.list = NULL; + } + } + else { + npc->npc_info.animal = NULL; + npc->npc_info.list = NULL; + } + } + } +} + +static void mNpc_AddNpc_inNpcRoom(mFM_move_actor_c* move_actor, int move_actor_idx, u8 bx, u8 bz) { + u32 owner_id = Common_Get(house_owner_name); + mActor_name_t id = owner_id; + + if (id != RSV_NO) { + if (id != EMPTY_NO && mEvNM_CheckJointEvent(owner_id) != TRUE) { + int idx = mNpc_SearchAnimalinfo(Save_Get(animals), id, ANIMAL_NUM_MAX); + + if (idx != -1) { + move_actor->name_id = id; + move_actor->ut_x = 4; + move_actor->ut_z = 7; + move_actor->npc_info_idx = idx; + move_actor->arg = -1; + mFI_SetMoveActorBitData_ON(move_actor_idx, bx, bz); + } + } + } +} + +static void mNpc_AddNpc_inNpcRoomIsland(mFM_move_actor_c* move_actor, int move_actor_idx, u8 bx, u8 bz) { + move_actor->name_id = Save_Get(island).animal.id.npc_id; + move_actor->ut_x = 4; + move_actor->ut_z = 7; + move_actor->npc_info_idx = -ANIMAL_NUM_MAX; + move_actor->arg = -1; + mFI_SetMoveActorBitData_ON(move_actor_idx, bx, bz); +} + +static void mNpc_AddNpc_inKamakura(mFM_move_actor_c* move_actor, int move_actor_idx, u8 bx, u8 bz) { + u8* kamakura_event = (u8*)mEv_get_save_area(mEv_EVENT_KAMAKURA, 15); + + if (kamakura_event != NULL) { + int idx = *kamakura_event; + mActor_name_t npc_id = Save_Get(animals[idx]).id.npc_id; + + mEvMN_GetEventNpcName(&npc_id, mEv_EVENT_KAMAKURA, idx, 0); + move_actor->name_id = npc_id; + move_actor->ut_x = 4; + move_actor->ut_z = 7; + move_actor->npc_info_idx = idx; + move_actor->arg = -1; + mFI_SetMoveActorBitData_ON(move_actor_idx, bx, bz); + } +} + +extern void mNpc_AddNpc_inBlock(mFM_move_actor_c* move_actor_list, u8 bx, u8 bz) { + int scene = Save_Get(scene_no); + + if ((scene == SCENE_NPC_HOUSE || scene == SCENE_KAMAKURA || scene == SCENE_COTTAGE_NPC) && move_actor_list != NULL) { + mActor_name_t field_id = mFI_GetFieldId(); // unused + int move_actor_idx = mFI_GetMoveActorListIdx(move_actor_list, mFM_MOVE_ACTOR_NUM, EMPTY_NO); + + if (move_actor_idx != -1 && move_actor_idx < ANIMAL_NUM_MAX) { + move_actor_list += move_actor_idx; + + switch (scene) { + case SCENE_NPC_HOUSE: + mNpc_AddNpc_inNpcRoom(move_actor_list, move_actor_idx, bx, bz); + break; + case SCENE_COTTAGE_NPC: + mNpc_AddNpc_inNpcRoomIsland(move_actor_list, move_actor_idx, bx, bz); + break; + case SCENE_KAMAKURA: + mNpc_AddNpc_inKamakura(move_actor_list, move_actor_idx, bx, bz); + break; + } + } + } +} + +extern void mNpc_RenewalNpcRoom(s16* wall_floor) { + mActor_name_t field_id = mFI_GetFieldId(); + mActor_name_t owner_id = Common_Get(house_owner_name); + int island_wall = 0; + int island_floor = 0; + u8 wall = 0; + u8 floor = 0; + int island_room_ftr_num; + + if (Save_Get(scene_no) == SCENE_COTTAGE_NPC) { + wall = 8; // exotic wall + floor = 32; // bamboo flooring + island_room_ftr_num = mNpc_GetIslandRoomFtrNum(); + + if (island_room_ftr_num >= 2) { + mNpc_GetIslandWallFloorIdx(&island_wall, &island_floor, Save_Get(island).animal.id.npc_id); + wall = island_wall; + + if (island_room_ftr_num >= 5) { + floor = island_floor; + } + } + } + else if (mFI_GET_TYPE(field_id) == mFI_FIELD_NPCROOM0 && owner_id != EMPTY_NO && owner_id != RSV_NO) { + int idx = mNpc_SearchAnimalinfo(Save_Get(animals), owner_id, ANIMAL_NUM_MAX); + mNpc_NpcList_c* npclist; + + if (idx == -1) { + idx = 0; + } + + npclist = Common_GetPointer(npclist[idx]); + + wall = npclist->house_data.wall_id; + floor = npclist->house_data.floor_id; + } + + wall_floor[0] = (wall << 8) | floor; +} + +extern void mNpc_RenewalSetNpc(ACTOR* actor) { + NPC_ACTOR* npc = (NPC_ACTOR*)actor; + Animal_c* animal; + + if (npc->npc_info.list != NULL) { + mActor_name_t field_id = mFI_GetFieldId(); + mNpc_NpcList_c* npclist = npc->npc_info.list; + xyz_t* npc_pos = &npclist->position; + + if (mFI_GET_TYPE(field_id) == mFI_FIELD_FG) { + xyz_t_move(npc_pos, &actor->world.position); + npclist->appear_flag = TRUE; + animal = &Save_Get(island).animal; + + if (actor->npc_id == animal->id.npc_id) { + int bnum = mFI_GetBlockNum(actor->block_x, actor->block_z); + mFM_move_actor_c* move_actor_list = g_fdinfo->block_info[bnum].fg_info.move_actors; + + if (move_actor_list != NULL) { + int move_actor_idx = mFI_GetMoveActorListIdx(move_actor_list, mFM_MOVE_ACTOR_NUM, actor->npc_id); + + if (move_actor_idx != -1) { + int bx; + int bz; + int ut_x; + int ut_z; + + if (mFI_Wpos2BkandUtNuminBlock(&bx, &bz, &ut_x, &ut_z, npclist->position) == FALSE) { + ut_x = animal->home_info.ut_x; + ut_z = animal->home_info.ut_z; + } + else if (mNpc_CheckNpcSet(bx, bz, ut_x, ut_z) == FALSE && mNpc_GetMakeUtNuminBlock_area(&ut_x, &ut_z, bx, bz, 0) == FALSE) { + ut_x = animal->home_info.ut_x; + ut_z = animal->home_info.ut_z; + } + + move_actor_list[move_actor_idx].ut_x = ut_x; + move_actor_list[move_actor_idx].ut_z = ut_z; + } + } + + mFI_MyMoveActorBitData_ON(actor); + } + } + } + else { + mFI_MyMoveActorBitData_ON(actor); + } +} + +extern int mNpc_GetFriendAnimalNum(PersonalID_c* pid) { + Animal_c* animal = Save_Get(animals); + int num = 0; + int i; + + if (pid != NULL) { + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalPersonalID(&animal->id) == FALSE && mNpc_GetAnimalMemoryIdx(pid, animal->memories, ANIMAL_MEMORY_NUM) != -1) { + num++; + } + + animal++; + } + } + + return num; +} + +extern int mNpc_CheckFriendAllAnimal(PersonalID_c* pid) { + int now_npc_max = Save_Get(now_npc_max); + int friend_num = mNpc_GetFriendAnimalNum(pid); + + if (now_npc_max <= friend_num) { + return TRUE; + } + + return FALSE; +} + +static int mNpc_CheckSelectFurniture(mActor_name_t item) { + int res = FALSE; + + if ( + !(item >= FTR_CLOTH_START && item <= FTR_CLOTH_END ) && // Clothing (furniture) + !(item >= FTR_UMBRELLA_START && item <= FTR_UMBRELLA_END) && // Umbrellas (furniture) + !(item >= FTR_INSECT_START && item <= FTR_INSECT_END ) && // Insects (furniture) + !(item >= FTR_FISH_START && item <= FTR_FISH_END ) && // Fish (furniture) + !(item >= HANIWA_START && item <= HANIWA_END ) && // Gyroids + !(item >= FTR_DINO_START && item <= FTR_DINO_END ) && // Idenitfied fossils + !(item >= FTR_FAMICOM_START && item <= FTR_FAMICOM_END ) // NES/Famicom games + ) { + res = TRUE; + } + + return res; +} + +static mActor_name_t mNpc_DecideNpcFurniture(mFM_fg_data_c** fg_data_table, mNpc_NpcList_c* npclist, int fg_base_id) { + int data_idx = npclist->house_data.main_layer_id - fg_base_id; + mActor_name_t* fg_items; + u8 num = 0; + int ut_z; + int ut_x; + + if (data_idx < 0) { + data_idx = 0; + } + + fg_items = fg_data_table[data_idx]->items[0]; + + for (ut_z = 0; ut_z < 10; ut_z++) { + for (ut_x = 0; ut_x < 10; ut_x++) { + if (ITEM_IS_FTR(*fg_items) && mNpc_CheckSelectFurniture(*fg_items) == TRUE) { + num++; + } + + fg_items++; + } + + fg_items += UT_X_NUM - 10; + } + + if (num != 0) { + num = RANDOM(num); + + fg_items = fg_data_table[data_idx]->items[0]; + for (ut_z = 0; ut_z < 10; ut_z++) { + for (ut_x = 0; ut_x < 10; ut_x++) { + if (ITEM_IS_FTR(*fg_items) && mNpc_CheckSelectFurniture(*fg_items) == TRUE) { + if (num <= 0) { + return *fg_items; + } + else { + num--; + } + } + + fg_items++; + } + + fg_items += UT_X_NUM - 10; + } + } + + return EMPTY_NO; +} + +extern void mNpc_SetNpcFurnitureRandom(mFM_fg_data_c** fg_data_table, int fg_base_id) { + mNpc_NpcList_c* npclist = Common_Get(npclist); + Animal_c* animal = Save_Get(animals); + int i; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalPersonalID(&animal->id) == FALSE) { + npclist->reward_furniture = mNpc_DecideNpcFurniture(fg_data_table, npclist, fg_base_id); + } + + npclist++; + animal++; + } +} + +extern mActor_name_t mNpc_GetNpcFurniture(AnmPersonalID_c* anm_id) { + mActor_name_t furniture = EMPTY_NO; + int idx = mNpc_SearchAnimalPersonalID(anm_id); + + if (idx != -1) { + furniture = Common_Get(npclist[idx]).reward_furniture; + } + + return furniture; +} + +static Animal_c l_mnpc_remove_in_animal; + +extern void mNpc_ClearInAnimal() { + mNpc_ClearAnimalInfo(&l_mnpc_remove_in_animal); +} + +extern Animal_c* mNpc_GetInAnimalP() { + return &l_mnpc_remove_in_animal; +} + +static void mNpc_RenewRemoveHistory() { + mTM_set_renew_time(Save_GetPointer(force_remove_date), Common_GetPointer(time.rtc_time)); +} + +static int mNpc_DecideRemoveAnimalNo_Friend(Animal_c* animal, int ignored_idx, int met_all_flag) { + int res; + Private_c* priv_p; + int n_players; + int num_possible; + u16 remove_bitfield; + int met; + int selected; + int i; + int j; + + res = -1; + n_players = 0; + num_possible = 0; + remove_bitfield = 0; + priv_p = Save_Get(private); + + for (i = 0; i < PLAYER_NUM; i++) { + if (mPr_NullCheckPersonalID(&priv_p->player_ID) == FALSE) { + n_players++; + } + + priv_p++; + } + + if (n_players > 0) { + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE && i != ignored_idx) { + met = 0; + priv_p = Save_Get(private); + + for (j = 0; j < PLAYER_NUM; j++) { + if (mPr_NullCheckPersonalID(&priv_p->player_ID) == FALSE) { + if (mNpc_GetAnimalMemoryIdx(&priv_p->player_ID, animal->memories, ANIMAL_MEMORY_NUM) == -1) { + break; + } + + met++; + } + + priv_p++; + } + + if (met_all_flag == TRUE) { + if (met == n_players) { + remove_bitfield |= (1 << i); + num_possible++; + } + } + else if (met > 0) { + remove_bitfield |= (1 << i); + num_possible++; + } + } + + animal++; + } + } + + if (num_possible > 0) { + selected = RANDOM(num_possible); + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (((remove_bitfield >> i) & 1) == 1) { + if (selected <= 0) { + res = i; + break; + } + else { + selected--; + } + } + } + } + + return res; +} + +static int mNpc_DecideRemoveAnimalNo(Animal_c* animal, int ignored_idx, int now_npc_max) { + int i; + int selected; + int res = -1; + + if (ignored_idx >= 0 || ignored_idx < ANIMAL_NUM_MAX) { + now_npc_max--; + } + + if (now_npc_max > 0 && now_npc_max <= ANIMAL_NUM_MAX) { + selected = RANDOM(now_npc_max); + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE) { + if (selected <= 0 && ignored_idx != i) { + res = i; + break; + } + else { + selected--; + } + } + + animal++; + } + } + + return res; +} + +/* 50% */ + +extern void mNpc_SetRemoveAnimalNo(u8* remove_animal_no, Animal_c* animal, int ignored_idx) { + u8 now_npc_max = Save_Get(now_npc_max); + + if (now_npc_max > ANIMAL_NUM_MIN && remove_animal_no[0] == 0xFF) { + /* First, try to pick an animal which all players have met before */ + int remove_no = mNpc_DecideRemoveAnimalNo_Friend(animal, ignored_idx, TRUE); + + if (remove_no == -1) { + /* Next, try to pick an animal which at least one player has met before */ + remove_no = mNpc_DecideRemoveAnimalNo_Friend(animal, ignored_idx, FALSE); + + if (remove_no == -1) { + /* Finally, forcefully find an animal to remove */ + remove_no = mNpc_DecideRemoveAnimalNo(animal, ignored_idx, now_npc_max); + } + } + + if (remove_no != -1) { + remove_animal_no[0] = remove_no; + } + } +} + +static int mNpc_CheckGoodbyAnimalMemoryNum(Animal_c* a0, Animal_c* a1) { + Anmmem_c* memories0 = a0->memories; + Anmmem_c* memories1 = a1->memories; + int res = 0; + int res0; + int res1; + + res0 = mNpc_GetAnimalMemoryNum(memories0, ANIMAL_MEMORY_NUM); + res1 = mNpc_GetAnimalMemoryNum(memories1, ANIMAL_MEMORY_NUM); + + /* Check the total number of memories first */ + if (res1 > res0) { + res = 1; + } + else if (res1 == res0) { + /* Next, check the number of memories which have a letter associated with them */ + res0 = mNpc_GetAnimalMemoryLetterNum(memories0, ANIMAL_MEMORY_NUM); + res1 = mNpc_GetAnimalMemoryLetterNum(memories1, ANIMAL_MEMORY_NUM); + + if (res1 > res0) { + res = 1; + } + else if (res1 == res0) { + /* Next, check the number of unique towns each animal's memories contains */ + res0 = mNpc_GetAnimalMemoryLandKindNum(memories0, ANIMAL_MEMORY_NUM); + res1 = mNpc_GetAnimalMemoryLandKindNum(memories1, ANIMAL_MEMORY_NUM); + + if (res1 > res0) { + res = 1; + } + else if (res1 == res0) { + /* Finally, check the total number of animals in town with the same personality and pick the one with more */ + res0 = mNpc_GetSameLooksNum(a0->id.looks); + res1 = mNpc_GetSameLooksNum(a1->id.looks); + + if (res1 < res0) { + res = 1; + } + else { + res = -1; + } + } + } + } + + return res; +} + +extern int mNpc_GetGoodbyAnimalIdx(int ignored_idx) { + Animal_c* animal; + Animal_c* other_animal = NULL; + int res0; + int res1; + int selected; + u16 remove_bitfield; + int candidate_num; + int goodby_animal_idx; + int i; + + animal = Save_Get(animals); + remove_bitfield = 0; + candidate_num = 0; + goodby_animal_idx = -1; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if ( + i != Save_Get(remove_animal_idx) && + ((ignored_idx >= 0 && ignored_idx < ANIMAL_NUM_MAX && i != ignored_idx) || ignored_idx == -1) && + mNpc_CheckFreeAnimalInfo(animal) == FALSE + ) { + if (other_animal != NULL) { + /* Compare remove experience first (unused feature) */ + if (mNpc_CheckRemoveExp(animal) == 0) { + if (mNpc_CheckRemoveExp(other_animal) != 0) { + candidate_num = 1; + remove_bitfield = (1 << i); + other_animal = animal; + goodby_animal_idx = i; + } + else { + /* Compare total memories in each animal */ + res1 = mNpc_CheckGoodbyAnimalMemoryNum(animal, other_animal); + switch (res1) { + case -1: + candidate_num++; + remove_bitfield |= (1 << i); + break; + case 1: + candidate_num = 1; + remove_bitfield = (1 << i); + other_animal = animal; + goodby_animal_idx = i; + break; + } + } + } + else if (mNpc_CheckRemoveExp(other_animal) != 0) { + /* NOTE: this path can never be taken due to remove experiences never being set */ + /* Compare times removed (unused feature) */ + res0 = mNpc_GetRemoveTime(animal); + res1 = mNpc_GetRemoveTime(other_animal); + if (res1 > res0) { + candidate_num = 1; + remove_bitfield = (1 << i); + other_animal = animal; + goodby_animal_idx = i; + } + else if (res1 == res0) { + /* Compare total memories in each animal */ + res1 = mNpc_CheckGoodbyAnimalMemoryNum(animal, other_animal); + switch (res1) { + case -1: + candidate_num++; + remove_bitfield |= (1 << i); + break; + case 1: + candidate_num = 1; + remove_bitfield = (1 << i); + other_animal = animal; + goodby_animal_idx = i; + break; + } + } + } + } + else { + candidate_num = 1; + remove_bitfield = (1 << i); + other_animal = animal; + goodby_animal_idx = i; + } + } + + animal++; + } + + animal = Save_Get(animals); + if (candidate_num > 1) { + selected = RANDOM(candidate_num); + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (((remove_bitfield >> i) & 1) == 1) { + if (selected == 0) { + goodby_animal_idx = i; + break; + } + else { + selected--; + } + } + + animal++; + } + } + + return goodby_animal_idx; +} + +typedef struct animal_goodbye_mail_s { + AnmPersonalID_c id; + u8 deliver_to_bitfield; +} Anm_GoodbyMail_c; + +static Anm_GoodbyMail_c l_mnpc_goodby_mail; + +static void mNpc_ClearGoodbyMail(Anm_GoodbyMail_c* goodby_mail) { + mNpc_ClearAnimalPersonalID(&goodby_mail->id); + goodby_mail->deliver_to_bitfield = 0; +} + +extern void mNpc_FirstClearGoodbyMail() { + mNpc_ClearGoodbyMail(&l_mnpc_goodby_mail); +} + +static void mNpc_SetGoodbyAnimalMail(Anm_GoodbyMail_c* goodby_mail, AnmPersonalID_c* anm_id) { + Private_c* priv = Save_Get(private); + int i; + + if (anm_id != NULL && mNpc_CheckFreeAnimalPersonalID(anm_id) == FALSE) { + mNpc_ClearGoodbyMail(goodby_mail); + mNpc_CopyAnimalPersonalID(&goodby_mail->id, anm_id); + + for (i = 0; i < PLAYER_NUM; i++) { + if (mPr_NullCheckPersonalID(&priv->player_ID) == FALSE) { + goodby_mail->deliver_to_bitfield |= (1 << i); + } + + priv++; + } + } +} + +static int mNpc_SetGoodbyMailData(Mail_c* mail, PersonalID_c* pid, AnmPersonalID_c* anm_id) { + static u8 animal_name[ANIMAL_NAME_LEN]; + + u8 looks = anm_id->looks; + int mail_no; + int res = FALSE; + + if (looks < mNpc_LOOKS_NUM) { + mail_no = 0x20E + looks * 3; + mail_no += mQst_GetRandom(3); + mNpc_GetNpcWorldNameAnm(animal_name, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR1, animal_name, ANIMAL_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR3, Save_Get(land_info).name, LAND_NAME_SIZE); + mNpc_LoadMailDataCommon2(mail, pid, anm_id, EMPTY_NO, mNpc_GetPaperType(), mail_no); + res = TRUE; + } + + return res; +} + +static int mNpc_SendGoodbyAnimalMailOne(Mail_c* mail, PersonalID_c* pid, int player_no, Anm_GoodbyMail_c* goodby_mail) { + int free_space; + mHm_hs_c* home; + int res = FALSE; + + home = Save_GetPointer(homes[mHS_get_arrange_idx(player_no)]); + + if (mPr_NullCheckPersonalID(pid) == FALSE && mPr_CheckCmpPersonalID(pid, &home->ownerID) == TRUE) { + free_space = mMl_chk_mail_free_space(home->mailbox, HOME_MAILBOX_SIZE); + + if (free_space != -1) { + if (mNpc_SetGoodbyMailData(mail, pid, &goodby_mail->id) == TRUE) { + mMl_copy_mail(home->mailbox + free_space, mail); + res = TRUE; + } + } + else if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE && mNpc_SetGoodbyMailData(mail, pid, &goodby_mail->id) == TRUE) { + res = mPO_receipt_proc(mail, mPO_SENDTYPE_MAIL); + } + } + + return res; +} + +static void mNpc_SendGoodbyAnimalMail(Anm_GoodbyMail_c* goodby_mail) { + Private_c* priv = Save_Get(private); + int i; + + if (mNpc_CheckFreeAnimalPersonalID(&goodby_mail->id) == FALSE) { + for (i = 0; i < PLAYER_NUM; i++) { + if (((goodby_mail->deliver_to_bitfield >> i) & 1) == 1) { + mMl_clear_mail(&l_npc_mail); + + if (mNpc_SendGoodbyAnimalMailOne(&l_npc_mail, &priv->player_ID, i, goodby_mail) == TRUE) { + goodby_mail->deliver_to_bitfield &= ~(1 << i); // player received goodbye letter + } + } + + priv++; + } + + if (goodby_mail->deliver_to_bitfield == 0) { + mNpc_ClearGoodbyMail(goodby_mail); // all players received goodbye letter + } + } +} + +extern void mNpc_SendRegisteredGoodbyMail() { + mNpc_SendGoodbyAnimalMail(&l_mnpc_goodby_mail); +} + +extern void mNpc_GetRemoveAnimal(Animal_c* transferring_animal, int moving_out) { + Private_c* priv = Common_Get(now_private); + u8* now_npc_max = Save_GetPointer(now_npc_max); + int new_animal; + Animal_c* animal2; + Animal_c* animal = Save_Get(animals); + int free_animal_idx; + u8* remove_animal_idx = Save_GetPointer(remove_animal_idx); + int bruh; + int i; + + new_animal = TRUE; + + if (mLd_PlayerManKindCheck() == FALSE) { + if (moving_out == TRUE) { + if (*remove_animal_idx != 0xFF) { + animal2 = Save_GetPointer(animals[*remove_animal_idx]); + animal2->removing = FALSE; + mNpc_CopyAnimalInfo(transferring_animal, animal2); + mNpc_DestroyHouse(&animal2->home_info); + mNpc_ClearAnimalInfo(animal2); + now_npc_max[0]--; + *remove_animal_idx = 0xFF; + } + } + } + else { + if (mNpc_CheckFreeAnimalInfo(transferring_animal) == FALSE && transferring_animal->removing == FALSE) { + mActor_name_t* summercamper_id; + + /* The incoming animal cannot be present in the town already */ + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (transferring_animal->id.npc_id == animal->id.npc_id) { + new_animal = FALSE; + break; + } + + animal++; + } + + /* The last animal who left the town cannot be the next animal to transfer in */ + if (transferring_animal->id.npc_id == Save_Get(last_removed_animal_id).npc_id) { + new_animal = FALSE; + } + + /* Ensure that the animal moving in is not the same one that's currently visiting as a summer camper */ + summercamper_id = (mActor_name_t*)mEv_get_save_area(mEv_EVENT_SUMMER_CAMPER, 0); + if (summercamper_id != NULL) { + if (ITEM_NAME_GET_TYPE(summercamper_id[0]) == NAME_TYPE_NPC) { + mActor_name_t id = transferring_animal->id.npc_id; + + if (id == summercamper_id[0]) { + new_animal = FALSE; + } + } + } + } + else { + /* The new animal was unintialized or was scheduled to move out */ + new_animal = FALSE; + } + + if (new_animal == TRUE) { + animal = Save_Get(animals); + + /* Try to find an unused slot in the animal data */ + free_animal_idx = mNpc_GetFreeAnimalInfo(Save_Get(animals), ANIMAL_NUM_MAX); + + if (free_animal_idx == -1) { + /* No slot, so we're going to replace a villager from in town */ + free_animal_idx = mNpc_GetGoodbyAnimalIdx(-1); + + if (free_animal_idx == -1) { + /* Final fallback, delete the first villager */ + free_animal_idx = 0; + } + + animal += free_animal_idx; + mNpc_DestroyHouse(&animal->home_info); + mNpc_SetGoodbyAnimalMail(&l_mnpc_goodby_mail, &animal->id); + mNpc_SendRegisteredGoodbyMail(); + } + else { + animal += free_animal_idx; + mNpc_AddNowNpcMax(now_npc_max); + } + + mNpc_ClearAnimalInfo(animal); + mNpc_CopyAnimalInfo(animal, transferring_animal); + animal->home_info.block_x = 0xFF; + animal->home_info.block_z = 0xFF; + animal->home_info.ut_x = 0xFF; + animal->home_info.ut_z = 0xFF; + mLd_CopyLandName(animal->anmuni.previous_land_name, Common_Get(now_private)->player_ID.land_name); + animal->previous_land_id = Common_Get(now_private)->player_ID.land_id; + mQst_ClearContest(&animal->contest_quest); + mNpc_ClearHPMail(animal->hp_mail, ANIMAL_HP_MAIL_NUM); + transferring_animal->removing = TRUE; + priv->animal_memory.npc_id = animal->id.npc_id; + mLd_CopyLandName(priv->animal_memory.land_name, mLd_GetLandName()); + mNpc_SetHaveAppeared(animal->id.npc_id); + mNpc_ResetAnimalRelation(free_animal_idx); + mNpc_RenewRemoveHistory(); + } + } +} + +static int mNpc_CheckBuildHouse(int bx, int bz, u8 ut_x, u8 ut_z) { + int res = FALSE; + + if (bx >= 0 && bx < FG_BLOCK_X_NUM && bz >= 0 && bz < FG_BLOCK_Z_NUM && ut_x < UT_X_NUM && ut_z < UT_Z_NUM) { + mActor_name_t item = Save_Get(fg[bz][bx]).items[ut_z][ut_x]; + + if (mNT_IS_RESERVE(item)) { + res = TRUE; + } + } + + return res; +} + +extern void mNpc_SetReturnAnimal(Animal_c* return_animal) { + Anmhome_c* home_info; + Animal_c* animals = Save_Get(animals); + Animal_c* found_animal = NULL; + u8* now_npc_max = Save_GetPointer(now_npc_max); + Anmret_c* return_animal_p = Save_GetPointer(return_animal); + int found_animal_idx; + int ignored_idx = -1; + + if (mLd_PlayerManKindCheck() == FALSE && mNpc_CheckFreeAnimalInfo(return_animal) == FALSE) { + if (return_animal->removing == FALSE) { + found_animal_idx = mNpc_SearchAnimalinfo(animals, return_animal->id.npc_id, ANIMAL_NUM_MAX); + + if (found_animal_idx != -1) { + found_animal = &animals[found_animal_idx]; + mNpc_DestroyHouse(&found_animal->home_info); + (*now_npc_max)--; + + if (Save_Get(remove_animal_idx) == found_animal_idx) { + Save_Set(remove_animal_idx, 0xFF); + } + } + else if ((*now_npc_max) >= ANIMAL_NUM_MAX) { + if (Save_Get(scene_no) == SCENE_PLAYERSELECT_2) { + /* Don't remove the animal we just saw on the player select screen */ + ignored_idx = Common_Get(player_select_animal_no); + } + + found_animal_idx = mNpc_GetGoodbyAnimalIdx(ignored_idx); + + if (found_animal_idx == -1) { + found_animal_idx = 0; // fallback + } + + found_animal = &animals[found_animal_idx]; + mNpc_SetGoodbyAnimalMail(&l_mnpc_goodby_mail, &found_animal->id); + mNpc_DestroyHouse(&found_animal->home_info); + (*now_npc_max)--; + + if (Save_Get(remove_animal_idx) == found_animal_idx) { + Save_Set(remove_animal_idx, 0xFF); + } + } + else { + found_animal_idx = mNpc_GetFreeAnimalInfo(animals, ANIMAL_NUM_MAX); + + if (found_animal_idx != -1) { + found_animal = &animals[found_animal_idx]; + } + } + + if (found_animal != NULL) { + mNpc_ClearAnimalInfo(found_animal); + mNpc_CopyAnimalInfo(found_animal, return_animal); + mNpc_ResetAnimalRelation(found_animal_idx); + home_info = &found_animal->home_info; + + if ( + mNpc_CheckBuildHouse( + home_info->block_x - 1, home_info->block_z - 1, + home_info->ut_x, home_info->ut_z - 1 + ) == TRUE) { + mNpc_BuildHouseBeforeFieldct( + found_animal->id.npc_id, + home_info->block_x - 1, home_info->block_z - 1, + home_info->ut_x, home_info->ut_z - 1 + ); + } + else { + home_info->block_x = 0xFF; + home_info->block_z = 0xFF; + home_info->ut_x = 0xFF; + home_info->ut_z = 0xFF; + } + + (*now_npc_max)++; + + if ((*now_npc_max) > ANIMAL_NUM_MAX) { + (*now_npc_max) = ANIMAL_NUM_MAX; + } + } + } + else { + return_animal_p->npc_id = return_animal->id.npc_id; + return_animal_p->talk_bit = FALSE; + return_animal_p->exist = FALSE; + lbRTC_TimeCopy(&return_animal_p->renew_time, Common_GetPointer(time.rtc_time)); + mNpc_CopyAnimalPersonalID(Save_GetPointer(last_removed_animal_id), &return_animal->id); + } + + mNpc_ClearAnimalInfo(return_animal); + } +} + +extern void mNpc_AddActor_inBlock(mFM_move_actor_c* move_actor_list, u8 bx, u8 bz) { + mNpc_AddNpc_inBlock(move_actor_list, bx, bz); +} + +static void mNpc_SetNpcNameID(Animal_c* animal, int num) { + int i; + + for (i = 0; i < num; i++) { + if (animal != NULL && ITEM_NAME_GET_TYPE(animal->id.npc_id) == NAME_TYPE_NPC) { + animal->id.name_id = (u8)animal->id.npc_id; + } + + animal++; + } +} + +extern void mNpc_LoadNpcNameString(u8* dst, u8 name_id) { + static u8 dma_area[64] ATTRIBUTE_ALIGN(32); + u32 addr; + u32 ofs; + u32 base_addr; + + mem_clear(dma_area, sizeof(dma_area), CHAR_SPACE); + addr = JW_GetAramAddress(RESOURCE_NPC_NAME_STR_TABLE); + + if (name_id < 0xFF && dst != NULL && addr != 0) { + ofs = (name_id) * ANIMAL_NAME_LEN; /* offset into dma table */ + base_addr = ALIGN_PREV(ofs, 32); /* align it to the nearest 32 bytes for DMA */ + + _JW_GetResourceAram(base_addr + addr, dma_area, sizeof(dma_area)); // DMA transfer from ARAM + bcopy(dma_area + (ofs - base_addr), dst, ANIMAL_NAME_LEN); // perform final copy + } +} + +static u8* mNpc_GetNpcWorldNamePTableNo(mActor_name_t npc_id) { + static u8 npc_name[ANIMAL_NAME_LEN]; + + u8* world_name_p = NULL; + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC && (u8)(npc_id & 0xFF) < 0xFF) { + mNpc_LoadNpcNameString(npc_name, npc_id & 0xFF); + world_name_p = npc_name; + } + + return world_name_p; +} + +extern void mNpc_GetNpcWorldNameTableNo(u8* dst, mActor_name_t npc_id) { + u8* world_name_p = mNpc_GetNpcWorldNamePTableNo(npc_id); + + if (world_name_p == NULL) { + world_name_p = l_no_name_npc_name; + } + + mPr_CopyPlayerName(dst, world_name_p); +} + +typedef struct npc_name_cache_s { + mActor_name_t npc_id; + u8 name[ANIMAL_NAME_LEN]; + u8 npc_type; // 0? = Special NPC, 1 = Regular NPC +} mNpc_NameCache_c; + +static mNpc_NameCache_c l_npc_name_cache; + +extern void mNpc_ClearCacheName() { + mPr_ClearPlayerName(l_npc_name_cache.name); + l_npc_name_cache.npc_id = RSV_NO; + l_npc_name_cache.npc_type = 0; +} + +static int mNpc_GetCacheName(u8* dst, mActor_name_t npc_id, u8 npc_type) { + int res = FALSE; + + if (npc_id == l_npc_name_cache.npc_id && dst != NULL && npc_type == l_npc_name_cache.npc_type) { + mPr_CopyPlayerName(dst, l_npc_name_cache.name); + res = TRUE; + } + + return res; +} + +static void mNpc_SetCacheName(u8* name, mActor_name_t npc_id, u8 npc_type) { + mNpc_NameCache_c* name_cache = &l_npc_name_cache; + + if (name != NULL) { + mPr_CopyPlayerName(name_cache->name, name); + name_cache->npc_id = npc_id; + name_cache->npc_type = npc_type; + } +} + +extern void mNpc_GetNpcWorldNameAnm(u8* dst, AnmPersonalID_c* anm_id) { + u8 animal_name[ANIMAL_NAME_LEN]; + u8* name = l_no_name_npc_name; + + if (anm_id != NULL) { + if (anm_id->npc_id != RSV_NO && mNpc_GetCacheName(animal_name, anm_id->npc_id, mNpc_NAME_TYPE_NPC) != FALSE) { + name = animal_name; + } + else if (ITEM_NAME_GET_TYPE(anm_id->npc_id) == NAME_TYPE_NPC) { + mNpc_GetNpcWorldNameTableNo(animal_name, anm_id->npc_id); + name = animal_name; + mNpc_SetCacheName(name, anm_id->npc_id, mNpc_NAME_TYPE_NPC); + } + } + + mPr_CopyPlayerName(dst, name); +} + +static u8* mNpc_GetActorWorldNameP(mActor_name_t npc_id) { + static u8 load_name[ANIMAL_NAME_LEN]; + + mNpc_Sp_Npc_Name_c* sp_npc_name = l_sp_actor_name; + u8* name_p = NULL; + int i; + + if (npc_id != RSV_NO && mNpc_GetCacheName(load_name, npc_id, mNpc_NAME_TYPE_NPC) != FALSE) { + name_p = load_name; + } + else if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC) { + mNpc_GetNpcWorldNameTableNo(load_name, npc_id); + name_p = load_name; + mNpc_SetCacheName(load_name, npc_id, mNpc_NAME_TYPE_NPC); + } + else { + for (i = 0; i < ARRAY_COUNT(l_sp_actor_name); i++) { + if (npc_id == sp_npc_name->sp_npc_id) { + mString_Load_StringFromRom(load_name, ANIMAL_NAME_LEN, sp_npc_name->name_str_no); + name_p = load_name; + mNpc_SetCacheName(load_name, npc_id, mNpc_NAME_TYPE_NPC); + + break; + } + + sp_npc_name++; + } + } + + return name_p; +} + +extern void mNpc_GetActorWorldName(u8* dst, mActor_name_t npc_id) { + u8* name_p = mNpc_GetActorWorldNameP(npc_id); + + if (name_p == NULL) { + name_p = l_no_name_npc_name; + } + + mPr_CopyPlayerName(dst, name_p); +} + +extern u8* mNpc_GetNpcWorldNameP(mActor_name_t npc_id) { + u8* name_p = mNpc_GetNpcWorldNamePTableNo(npc_id); + + if (name_p == NULL) { + name_p = mNpc_GetActorWorldNameP(npc_id); + + if (name_p == NULL) { + name_p = l_no_name_npc_name; + } + } + + return name_p; +} + +extern void mNpc_GetNpcWorldName(u8* dst, ACTOR* actor) { + u8 npc_name[ANIMAL_NAME_LEN]; + u8* name_p = l_no_name_npc_name; + + if (dst != NULL && actor != NULL) { + if (actor->part == ACTOR_PART_NPC) { + NPC_ACTOR* npc = (NPC_ACTOR*)actor; + Animal_c* animal = npc->npc_info.animal; + + if (animal != NULL) { + mNpc_GetNpcWorldNameAnm(npc_name, &animal->id); + } + else { + mActor_name_t npc_id; + mNpc_EventNpc_c* event_npc; + mNpc_MaskNpc_c* mask_npc; + + event_npc = mNpc_GetSameEventNpc(actor->npc_id); + + if (event_npc != NULL) { + npc_id = event_npc->npc_id; + } + else { + mask_npc = mNpc_GetSameMaskNpc(actor->npc_id); + + if (mask_npc != NULL) { + npc_id = mask_npc->npc_id; + } + else { + npc_id = actor->npc_id; + } + } + + if (npc_id == SP_NPC_EV_GHOST) { + u8 arg = actor->actor_specific; + + if (mNpc_GetCacheName(npc_name, npc_id, arg) == FALSE) { + if (arg != 1) { + /* Load the ?????? name */ + mString_Load_StringFromRom(npc_name, ANIMAL_NAME_LEN, 0x6D7); + mNpc_SetCacheName(npc_name, npc_id, arg); + } + else { + mNpc_GetActorWorldName(npc_name, npc_id); + } + } + } + else { + mNpc_GetActorWorldName(npc_name, npc_id); + } + } + } + else { + mNpc_GetActorWorldName(npc_name, actor->npc_id); + } + + name_p = npc_name; + } + + mPr_CopyPlayerName(dst, name_p); +} + +extern void mNpc_GetRandomAnimalName(u8* dst) { + Animal_c* animal = Save_Get(animals); + Animal_c* animal_p = animal; + u16 candidate_bitfield = 0; + int candidate_num = 0; + int i; + + if (dst != NULL) { + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalInfo(animal_p) == FALSE) { + candidate_bitfield |= (1 << i); + candidate_num++; + } + + animal_p++; + } + + if (candidate_num > 0) { + int selected = RANDOM(candidate_num); + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (((candidate_bitfield >> i) & 1) == 1) { + if (selected == 0) { + mNpc_GetNpcWorldNameAnm(dst, &animal->id); + break; + } + else { + selected--; + } + } + + animal++; + } + } + } +} + +extern void mNpc_GetAnimalPlateName(u8* dst, xyz_t wpos) { + Animal_c* animal = Save_Get(animals); + int bx; + int bz; + int ut_x; + int ut_z; + + if (mFI_Wpos2BkandUtNuminBlock(&bx, &bz, &ut_x, &ut_z, wpos) == TRUE) { + if (mFI_CheckBlockKind_OR(bx, bz, mRF_BLOCKKIND_OCEAN) == TRUE) { + mNpc_GetNpcWorldNameAnm(dst, &Save_Get(island).animal.id); + } + else { + int i; + + ut_x++; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE) { + Anmhome_c* home = &animal->home_info; + + if ( + home->block_x == bx && + home->block_z == bz && + home->ut_x == ut_x && + home->ut_z == ut_z + ) { + mNpc_GetNpcWorldNameAnm(dst, &animal->id); + break; + } + } + + animal++; + } + } + } +} + +extern int mNpc_GetNpcLooks(ACTOR* actor) { + int looks = mNpc_LOOKS_BOY; + + if (actor != NULL && actor->part == ACTOR_PART_NPC) { + NPC_ACTOR* npc = (NPC_ACTOR*)actor; + + if (npc->npc_info.animal != NULL) { + looks = npc->npc_info.animal->id.looks; + } + } + + return looks; +} + +static int mNpc_GetActorSex(mActor_name_t npc_id) { + mNpc_Sp_Npc_Name_c* sp_name_info = l_sp_actor_name; + int sex = mPr_SEX_MALE; + int i; + + for (i = 0; i < ARRAY_COUNT(l_sp_actor_name); i++) { + if (npc_id == sp_name_info->sp_npc_id) { + sex = sp_name_info->sex; + break; + } + + sp_name_info++; + } + + return sex; +} + +extern int mNpc_GetLooks2Sex(int looks) { + int sex = mPr_SEX_OTHER; + + if (looks == mNpc_LOOKS_GIRL || looks == mNpc_LOOKS_KO_GIRL || looks == mNpc_LOOKS_NANIWA_LADY) { + sex = mPr_SEX_FEMALE; + } + else if (looks == mNpc_LOOKS_BOY || looks == mNpc_LOOKS_SPORT_MAN || looks == mNpc_LOOKS_GRIM_MAN) { + sex = mPr_SEX_MALE; + } + + return sex; +} + +extern int mNpc_GetAnimalSex(Animal_c* animal) { + int sex = mPr_SEX_OTHER; + + if (animal != NULL) { + sex = mNpc_GetLooks2Sex(animal->id.looks); + } + + return sex; +} + +extern int mNpc_GetNpcSex(ACTOR* actor) { + if (actor != NULL && actor->part == ACTOR_PART_NPC) { + NPC_ACTOR* npc = (NPC_ACTOR*)actor; + + return mNpc_GetAnimalSex(npc->npc_info.animal); + } + + + return mNpc_GetActorSex(actor->npc_id); +} + +static int mNpc_GetNpcSoundSpecNotAnimal(mActor_name_t npc_id) { + mNpc_Sp_Npc_Name_c* sp_name_info = l_sp_actor_name; + int sound_spec = 2; + int i; + + for (i = 0; i < ARRAY_COUNT(l_sp_actor_name); i++) { + if (npc_id == sp_name_info->sp_npc_id) { + sound_spec = sp_name_info->sound_id; + break; + } + + sp_name_info++; + } + + return sound_spec; +} + +extern int mNpc_GetNpcSoundSpec(ACTOR* actor) { + static int spec_table[mNpc_LOOKS_NUM] = { + 4, + 4, + 2, + 2, + 3, + 4 + }; + + int sound_spec = 2; + + if (actor != NULL) { + if (actor->part == ACTOR_PART_NPC) { + NPC_ACTOR* npc = (NPC_ACTOR*)actor; + + if (npc->npc_info.animal != NULL) { + int looks = npc->npc_info.animal->id.looks; + + if (looks >= mNpc_LOOKS_NUM) { + looks = mNpc_LOOKS_BOY; + } + + sound_spec = spec_table[looks]; + } + else { + sound_spec = mNpc_GetNpcSoundSpecNotAnimal(actor->npc_id); + } + } + else { + sound_spec = mNpc_GetNpcSoundSpecNotAnimal(actor->npc_id); + } + } + + return sound_spec; +} + +static void mNpc_InitRemoveHistory() { + lbRTC_ymd_c* ymd = Save_GetPointer(force_remove_date); + + ymd->year = 0xFFFF; + ymd->month = 0xFF; + ymd->day = 0xFF; +} + +extern void mNpc_InitNpcAllInfo(int malloc_flag) { + Animal_c* animals = Save_Get(animals); + + Save_Set(now_npc_max, mNpc_LOOKS_NUM); + Save_Set(remove_animal_idx, 0xFF); + mNpc_InitRemoveHistory(); + mNpc_ClearAnimalPersonalID(Save_GetPointer(last_removed_animal_id)); + mNpc_ClearAnyAnimalInfo(animals, ANIMAL_NUM_MAX); + mNpc_DecideLivingNpcMax(animals, mNpc_LOOKS_NUM, malloc_flag); + mNpc_SetNpcNameID(animals, ANIMAL_NUM_MAX); +} + +static int mNpc_CheckGrowFieldRank() { + static int npc_grow_prob[mFAs_FIELDRANK_NUM] = { + 40, + 50, + 60, + 70, + 80, + 90, + 100 + }; + + int rank = mFAs_GetFieldRank(); + int res = FALSE; + + if (rank >= 0 && rank < mFAs_FIELDRANK_NUM) { + if (RANDOM(100) < npc_grow_prob[rank]) { + res = TRUE; + } + } + + return res; +} + +static int mNpc_CheckGrow() { + lbRTC_time_c* rtc_time = Common_GetPointer(time.rtc_time); + lbRTC_time_c* last_grow_time = Save_GetPointer(last_grow_time); + int res = FALSE; + + if (Save_Get(now_npc_max) < ANIMAL_NUM_MAX) { + if (lbRTC_IsEqualTime(&mTM_rtcTime_clear_code, last_grow_time, lbRTC_CHECK_ALL) == TRUE) { + lbRTC_TimeCopy(last_grow_time, rtc_time); // first initialization + } + else if (mNpc_CheckGrowFieldRank() == TRUE) { + u32 interval; + + if (lbRTC_IsOverTime(last_grow_time, rtc_time) == lbRTC_OVER) { + interval = lbRTC_IntervalTime(rtc_time, last_grow_time); + } + else { + interval = lbRTC_IntervalTime(last_grow_time, rtc_time); + } + + if ( + interval >= (lbRTC_HOURS_PER_DAY * lbRTC_MINUTES_PER_HOUR) && /* Only one new villager per day */ + mLd_PlayerManKindCheck() == FALSE && /* Player must be from the town being loaded */ + mNpc_CheckFriendAllAnimal(&Common_Get(now_private)->player_ID) == TRUE /* The player loading must have talked to all current villagers */ + ) { + res = TRUE; + } + } + } + + return res; +} + +static u8 mNpc_GetMinLooks(u8* min_looks_bitfield, int* min_looks_num) { + int min_looks_animals; + u8 min_looks; + u8 i; + + min_looks_bitfield[0] = 0; + min_looks_num[0] = 0; + min_looks_animals = ANIMAL_NUM_MAX; + min_looks = mNpc_LOOKS_UNSET; + + for (i = 0; i < mNpc_LOOKS_NUM; i++) { + int not_appear_num = mNpc_GetLooks2NotHaveAppearedNum(i); + + if (not_appear_num > 0) { + int same_looks_num = mNpc_GetSameLooksNum(i); + + if (min_looks_animals > same_looks_num) { + min_looks_animals = same_looks_num; + min_looks = i; + min_looks_bitfield[0] = 0; + min_looks_bitfield[0] |= (1 << i); + min_looks_num[0] = 1; + } + else if (min_looks_animals == same_looks_num) { + min_looks_bitfield[0] |= (1 << i); + min_looks_num[0]++; + } + } + } + + if (min_looks_bitfield[0] == 0 && min_looks_num[0] == 0) { + /* All animals have appeared before, so reset the list */ + mNpc_ResetHaveAppeared_common(Save_Get(npc_used_tbl), Save_Get(animals)); + + for (i = 0; i < mNpc_LOOKS_NUM; i++) { + min_looks_bitfield[0] |= (1 << i); + min_looks_num[0]++; + } + } + + if (min_looks_num[0] > 1) { + min_looks = mNpc_LOOKS_UNSET; + } + + return min_looks; +} + +static int mNpc_GetMinSex() { + Animal_c* animal = Save_Get(animals); + int males = 0; + int females = 0; + int res; + int i; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE) { + switch (mNpc_GetAnimalSex(animal)) { + case mPr_SEX_MALE: + males++; + break; + case mPr_SEX_FEMALE: + females++; + break; + } + } + + animal++; + } + + /* TODO: is this bugged? Shouldn't this check be inverted? */ + if (females < males) { + res = mPr_SEX_MALE; + } + else { + res = mPr_SEX_FEMALE; + } + + return res; +} + +static int mNpc_GrowLooksNpcIdx(u8 looks) { + static u8 candidate_table[ALIGN_NEXT(NPC_NUM, 8) / 8]; + + u8* looks_table = npc_looks_table; + s8* grow_list = npc_grow_list; + int npc_idx; + int candidates; + int res; + int i; + int j; + int selected; + + npc_idx = 0; + candidates = 0; + res = -1; + + if (looks < mNpc_LOOKS_UNSET) { + bzero(candidate_table, sizeof(candidate_table)); + + for (i = 0; i < ARRAY_COUNT(candidate_table); i++) { + for (j = 0; j < 8; j++) { + if (looks == *looks_table) { + int grow_perm = mNpc_GetDefGrowPermission(npc_idx, grow_list, NPC_NUM); + + if ( + (mNpc_SearchAnimalinfo(Save_Get(animals), npc_idx | NPC_START, ANIMAL_NUM_MAX) == -1) && + (mNpc_GetHaveAppeared_idx(npc_idx) == FALSE) && + (grow_perm == mNpc_GROW_STARTER || grow_perm == mNpc_GROW_MOVE_IN) + ) { + candidates++; + candidate_table[i] |= 1 << j; + } + } + + npc_idx++; + looks_table++; + + if (npc_idx >= NPC_NUM) { + break; + } + } + + if (npc_idx >= NPC_NUM) { + break; + } + } + } + + if (candidates > 0) { + selected = RANDOM(candidates); + npc_idx = 0; + + for (i = 0; i < ARRAY_COUNT(candidate_table); i++) { + if (npc_idx >= NPC_NUM) { + break; + } + + for (j = 0; j < 8; j++) { + if (npc_idx >= NPC_NUM) { + break; + } + + if (((candidate_table[i] >> j) & 1) == 1) { + if (selected <= 0) { + res = npc_idx; + break; + } + else { + selected--; + } + } + + npc_idx++; + } + + if (res != -1) { + break; + } + } + } + + return res; +} + +static void mNpc_SetAnimalInfoNpcIdx(Animal_c* animal, int idx) { + if (idx >= 0 && idx < NPC_NUM) { + mNpc_SetDefAnimalInfo(animal, NPC_START | idx, npc_looks_table[idx], &npc_def_list[idx]); + } +} + +static int mNpc_SetGrowNpc(u8 looks) { + Animal_c* animal = Save_Get(animals); + int idx = mNpc_UseFreeAnimalInfo(animal, ANIMAL_NUM_MAX); + int npc_idx; + + if (idx != -1) { + animal += idx; + npc_idx = mNpc_GrowLooksNpcIdx(looks); + + if (npc_idx != -1) { + mNpc_SetAnimalInfoNpcIdx(animal, npc_idx); + mNpc_SetHaveAppeared(animal->id.npc_id); + animal->moved_in = TRUE; + } + else { + idx = -1; + } + } + + return idx; +} + +extern void mNpc_Grow() { + int selected; + int grow_idx; + + if (mNpc_CheckGrow() == TRUE) { + lbRTC_time_c* rtc_time = Common_GetPointer(time.rtc_time); + lbRTC_time_c* last_grow_time = Save_GetPointer(last_grow_time); + u8 min_looks_bitfield; + int min_looks_num; + u8 min_looks; + u8 selected_looks; + + mNpc_ResetHaveAppeared(); + lbRTC_TimeCopy(last_grow_time, rtc_time); + + min_looks = mNpc_GetMinLooks(&min_looks_bitfield, &min_looks_num); + selected_looks = min_looks; + + /* Multiple personalities are tied for minimum */ + if (min_looks == mNpc_LOOKS_UNSET) { + int min_sex = mNpc_GetMinSex(); + u8 min_looks_bitfield_save; + int min_looks_num_save; + u8 looks; + + if (min_sex != mPr_SEX_OTHER) { + min_looks_bitfield_save = min_looks_bitfield; + min_looks_num_save = min_looks_num; + + /* Clear any tied minimum personalities which are of the greater sex */ + for (looks = 0; looks < mNpc_LOOKS_NUM; looks++) { + if (((min_looks_bitfield >> looks) & 1) == 1 && mNpc_GetLooks2Sex(looks) != min_sex) { + min_looks_bitfield &= ~(1 << looks); + min_looks_num--; + } + } + + if (min_looks_num == 0 || min_looks_num == min_looks_num_save) { + /* Either all personalities were cleared or none were, so restore the prior state */ + min_looks_bitfield = min_looks_bitfield_save; + min_looks_num = min_looks_num_save; + } + } + + selected = RANDOM(min_looks_num); + + for (looks = 0; looks < mNpc_LOOKS_NUM; looks++) { + if (((min_looks_bitfield >> looks) & 1) == 1) { + if (selected <= 0) { + selected_looks = looks; + break; + } + else { + selected--; + } + } + } + } + + grow_idx = mNpc_SetGrowNpc(selected_looks); + + if (grow_idx >= 0 && grow_idx < ANIMAL_NUM_MAX) { + mNpc_SetNpcNameID(Save_GetPointer(animals[grow_idx]), 1); + mNpc_AddNowNpcMax(Save_GetPointer(now_npc_max)); + mNpc_RenewRemoveHistory(); + } + } +} + +extern void mNpc_ForceRemove() { + lbRTC_time_c force_remove_time; + lbRTC_time_c* rtc_time = Common_GetPointer(time.rtc_time); + lbRTC_ymd_c* force_remove_date = Save_GetPointer(force_remove_date); + Animal_c* animal = Save_Get(animals); + int interval; + int animal_num = mNpc_GetAnimalNum(); + int ignored_idx = -1; + int idx; + + if ( + animal_num == ANIMAL_NUM_MAX && + force_remove_date->year != 0xFFFF && + force_remove_date->month != 0xFF && + force_remove_date->day != 0xFF + ) { + mTM_ymd_2_time(&force_remove_time, force_remove_date); + + if (lbRTC_IsOverTime(&force_remove_time, rtc_time) == lbRTC_OVER) { + interval = lbRTC_GetIntervalDays(&force_remove_time, rtc_time); + } + else { + interval = lbRTC_GetIntervalDays(rtc_time, &force_remove_time); + } + + + if (interval >= mNpc_MINIMUM_DAYS_BEFORE_FORCE_REMOVAL) { + if (Save_Get(scene_no) == SCENE_PLAYERSELECT_2) { + ignored_idx = Common_Get(player_select_animal_no); + } + + idx = mNpc_GetGoodbyAnimalIdx(ignored_idx); + + if (idx != -1) { + animal += idx; + + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE) { + mNpc_DestroyHouse(&animal->home_info); + mNpc_SetGoodbyAnimalMail(&l_mnpc_goodby_mail, &animal->id); + mNpc_SendRegisteredGoodbyMail(); + mNpc_ClearAnimalInfo(animal); + mNpc_SubNowNpcMax(Save_GetPointer(now_npc_max)); + mNpc_RenewRemoveHistory(); + } + } + } + } +} + +extern int mNpc_DecideMaskNpc_summercamp(mActor_name_t* npc_id) { + static int looks_table[mNpc_LOOKS_NUM]; + + u8 looks; + int i; + int res = FALSE; + + if (npc_id != NULL) { + npc_id[0] = EMPTY_NO; + mNpc_ResetHaveAppeared(); + mNpc_MakeRandTable(looks_table, mNpc_LOOKS_NUM, NPC_NUM); // why are we swapping this 'NPC_NUM' times??? + + for (i = 0; i < mNpc_LOOKS_NUM; i++) { + looks = looks_table[i]; + + if (mNpc_GetLooks2NotHaveAppearedNum(looks) > 0) { + int idx = mNpc_GrowLooksNpcIdx(looks); + + if (idx != -1) { + npc_id[0] = NPC_START | idx; + /* This makes summer campers impossible to appear by 'natural growth' in your town + unless you've seen every non-islander in your town as a villager or summer camper... */ + mNpc_SetHaveAppeared(npc_id[0]); + res = TRUE; + + break; + } + } + } + } + + return res; +} + +extern int mNpc_RegistMaskNpc_summercamp(mActor_name_t mask_id, mActor_name_t npc_id, mActor_name_t cloth_id) { + int free_mask_idx = mNpc_GetFreeMaskNpcIdx(); + int res = FALSE; + + if (free_mask_idx != -1) { + mNpc_MaskNpc_c* mask_npc = Common_GetPointer(mask_npc[free_mask_idx]); + + if (cloth_id != EMPTY_NO) { + int valid_cloth = (cloth_id == RSV_CLOTH) || (cloth_id >= ITM_CLOTH_START && cloth_id < ITM_CLOTH_END); + + if (valid_cloth == FALSE) { + cloth_id = ITM_CLOTH_START; + } + } + + if (ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC) { + mask_npc->mask_id = mask_id; + mask_npc->npc_id = npc_id; + mask_npc->exists = FALSE; + mask_npc->in_use = TRUE; + mNpc_ClearAnimalInfo(&mask_npc->animal_data); + mNpc_SetDefAnimal(&mask_npc->animal_data, npc_id, npc_def_list); + + if (cloth_id == EMPTY_NO) { + mask_npc->cloth_id = mask_npc->animal_data.cloth; + } + else { + mask_npc->cloth_id = cloth_id; + } + + if (Common_Get(npc_is_summercamper) == TRUE) { + mNpc_ClearAnimalMemory(mask_npc->animal_data.memories, 1); + mNpc_SetAnimalMemory(&Common_Get(now_private)->player_ID, &mask_npc->animal_data.id, &mask_npc->animal_data.memories[0]); + } + + res = TRUE; + } + } + + return res; +} + +extern int mNpc_CheckNpcSet_fgcol(mActor_name_t fg_item, u32 attribute) { + int res = FALSE; + + if (mFI_CheckFGNpcOn(fg_item)) { + res = mCoBG_Attr2CheckPlaceNpc(attribute); + } + + return res; +} + +static int mNpc_CheckNpcSet_fgcol_hard(mActor_name_t fg_item, u32 attribute) { + int res = FALSE; + + if (mFI_CheckFGNpcOn(fg_item) && (attribute <= mCoBG_ATTRIBUTE_GRASS2 || (attribute >= mCoBG_ATTRIBUTE_SOIL0 && attribute <= mCoBG_ATTRIBUTE_SOIL2) || attribute == mCoBG_ATTRIBUTE_STONE)) { + res = TRUE; + } + + return res; +} + +extern int mNpc_CheckNpcSet(int bx, int bz, int ut_x, int ut_z) { + mCoBG_Collision_u* collision = mFI_GetBkNum2ColTop(bx, bz); + mActor_name_t* items = mFI_BkNumtoUtFGTop(bx, bz); + int res = FALSE; + + if (collision != NULL && items != NULL) { + res = mNpc_CheckNpcSet_fgcol(items[ut_z * UT_X_NUM + ut_x], collision[ut_z * UT_X_NUM + ut_x].data.unit_attribute); + } + + return res; +} + +extern int mNpc_GetMakeUtNuminBlock_hard_area(int* ut_x, int* ut_z, int bx, int bz, int restrict_area) { + int now_ut_x; + int now_ut_z; + mCoBG_Collision_u* col; + mCoBG_Collision_u* col_p; + mActor_name_t* items; + int min_ut_x; + int min_ut_z; + int res; + u32 center; + int i; + int j; + + col = mFI_GetBkNum2ColTop(bx, bz); + items = mFI_BkNumtoUtFGTop(bx, bz); + min_ut_x = UT_X_NUM; + min_ut_z = UT_Z_NUM; + res = FALSE; + + if (restrict_area < 0 || restrict_area > 9) { + return FALSE; + } + + if (items != NULL && col != NULL) { + for (i = restrict_area; i < UT_Z_NUM - restrict_area; i++) { + for (j = restrict_area; j < UT_X_NUM - restrict_area; j++) { + col_p = &col[i * UT_X_NUM + j]; + + if (mNpc_CheckNpcSet_fgcol_hard(items[i * UT_X_NUM + j], col_p->data.unit_attribute) == TRUE) { + center = col_p->data.center; + + /* Check that the height of each point on the unit is equal*/ + if ( + (int)center == col_p->data.top_left && + center == col_p->data.bot_left && + center == col_p->data.top_right && + (int)center == col_p->data.bot_right + ) { + + now_ut_x = 8 - j; + now_ut_z = 8 - i; + + now_ut_x = ABS(now_ut_x); + now_ut_z = ABS(now_ut_z); + + if (min_ut_x > now_ut_x && min_ut_z > now_ut_z) { + *ut_x = j; + *ut_z = i; + min_ut_x = now_ut_x; + min_ut_z = now_ut_z; + res = TRUE; + } + } + } + } + } + } + + return res; +} + +extern int mNpc_GetMakeUtNuminBlock_area(int* ut_x, int* ut_z, int bx, int bz, int restrict_area) { + static u16 fg_flag[UT_Z_NUM]; + + mCoBG_Collision_u* col_top; + mActor_name_t* fg_top; + u16* fg_flag_p = fg_flag; + int candidates; + int res; + int i; + int j; + int rand; + + col_top = mFI_GetBkNum2ColTop(bx, bz); + fg_top = mFI_BkNumtoUtFGTop(bx, bz); + candidates = 0; + res = FALSE; + + if (restrict_area < 0 || restrict_area > 9) { + return FALSE; + } + + bzero(fg_flag, sizeof(fg_flag)); + + if (fg_top != NULL && col_top != NULL) { + for (i = restrict_area; i < UT_Z_NUM - restrict_area; i++) { + for (j = restrict_area; j < UT_X_NUM - restrict_area; j++) { + if (mNpc_CheckNpcSet_fgcol(fg_top[i * UT_X_NUM + j], col_top[i * UT_X_NUM + j].data.unit_attribute) == TRUE) { + *fg_flag_p |= 1 << j; + candidates++; + } + } + + fg_flag_p++; + } + } + + fg_flag_p = fg_flag; + if (candidates > 0) { + rand = RANDOM(candidates); + + for (i = restrict_area; i < UT_Z_NUM - restrict_area; i++) { + for (j = restrict_area; j < UT_X_NUM - restrict_area; j++) { + if (((*fg_flag_p >> j) & 1) == 1) { + if (rand == 0) { + *ut_x = j; + *ut_z = i; + res = TRUE; + + break; + } + else { + rand--; + } + } + } + + if (res == TRUE) { + break; + } + + fg_flag_p++; + } + } + + return res; +} + +extern int mNpc_GetMakeUtNuminBlock(int* ut_x, int* ut_z, int bx, int bz) { + return mNpc_GetMakeUtNuminBlock_area(ut_x, ut_z, bx, bz, 1); // can't spawn in the outer-most square of units +} + +static int mNpc_CheckNpcSet_height(int bx, int bz, int ut_x, int ut_z, mActor_name_t item, mCoBG_Collision_u* col) { + int res = FALSE; + + if (col != NULL && mNpc_CheckNpcSet_fgcol(item, col->data.unit_attribute)) { + xyz_t wpos; + + mFI_BkandUtNum2Wpos(&wpos, bx, bz, ut_x, ut_z); + + if (mCoBG_ExistHeightGap_KeepAndNow(wpos) == FALSE) { + res = TRUE; + } + } + + return res; +} + +extern int mNpc_GetMakeUtNuminBlock33(int* make_ut_x, int* make_ut_z, int ut_x, int ut_z, int bx, int bz) { + static int ut_xnum_table[9] = { 0, 1, -1, 0, 1, -1, 0, 1, -1 }; + static int ut_znum_table[9] = { 0, 0, 0, 1, 1, 1, -1, -1, -1 }; + + mCoBG_Collision_u* col_top; + mActor_name_t* fg_top; + int res; + int x; + int z; + int idx; + int i; + + col_top = mFI_GetBkNum2ColTop(bx, bz); + fg_top = mFI_BkNumtoUtFGTop(bx, bz); + res = FALSE; + + if (col_top != NULL && fg_top != NULL) { + for (i = 0; i < 9; i++) { + x = ut_x + ut_xnum_table[i]; + z = ut_z + ut_znum_table[i]; + + if (x >= 1 && x < (UT_X_NUM - 1) && z >= 1 && z < (UT_Z_NUM - 1)) { + idx = z * UT_X_NUM + x; + + if (mNpc_CheckNpcSet_height(bx, bz, x, z, fg_top[idx], &col_top[idx]) == TRUE) { + make_ut_x[0] = x; + make_ut_z[0] = z; + res = TRUE; + + break; + } + } + } + } + + return res; +} + +extern int mNpc_GetMakeUtNuminBlock_hide_hard_area(int* ut_x, int* ut_z, int bx, int bz, int restrict_area) { + static u16 hide_ut_bit[UT_Z_NUM]; + + int now_ut_z; + int now_ut_x; + mCoBG_Collision_u* col_top; + mCoBG_Collision_u* col_p; + mActor_name_t* fg_top; + mActor_name_t* item_p; + u32 center; + int min_ut_x; + int min_ut_z; + int res; + int i; + int j; + + col_top = mFI_GetBkNum2ColTop(bx, bz); + fg_top = mFI_BkNumtoUtFGTop(bx, bz); + min_ut_x = UT_X_NUM; + min_ut_z = UT_Z_NUM; + res = FALSE; + + if (restrict_area < 0 || restrict_area > 9) { + return FALSE; + } + + if (fg_top != NULL && col_top != NULL) { + bzero(hide_ut_bit, sizeof(hide_ut_bit)); + mAGrw_SetHideUtInfo(hide_ut_bit, fg_top); + + for (i = restrict_area; i < UT_Z_NUM - restrict_area; i++) { + for (j = restrict_area; j < UT_X_NUM - restrict_area; j++) { + item_p = &fg_top[i * UT_X_NUM + j]; + col_p = &col_top[i * UT_X_NUM + j]; + + if (mNpc_CheckNpcSet_fgcol_hard(item_p[0], col_p->data.unit_attribute) == TRUE) { + center = col_p->data.center; + + /* Check that the height of each point on the unit is equal*/ + if ( + (int)center == col_p->data.top_left && + center == col_p->data.bot_left && + center == col_p->data.top_right && + (int)center == col_p->data.bot_right && + ((hide_ut_bit[i] >> j) & 1) == 1 + ) { + + now_ut_x = 8 - j; + now_ut_z = 8 - i; + + now_ut_x = ABS(now_ut_x); + now_ut_z = ABS(now_ut_z); + + if (min_ut_x > now_ut_x && min_ut_z > now_ut_z) { + *ut_x = j; + *ut_z = i; + min_ut_x = now_ut_x; + min_ut_z = now_ut_z; + res = TRUE; + } + } + } + } + } + } + + return res; +} + +typedef struct npc_temper_s { + u16 unlock_timer; + u8 over_impatient_num; + u8 talk_num_max; +} mNpc_Temper_c; + +typedef struct npc_talk_info_s { + u16 timer; + u8 talk_num; + u8 quest_request; + u16 unlock_timer; + u16 reset_timer; +} mNpc_Talk_Info_c; + +static mNpc_Talk_Info_c l_npc_talk_info[ANIMAL_NUM_MAX + mISL_ISLANDER_NUM]; +static mNpc_Temper_c l_npc_temper[mNpc_FEEL_NUM] = { + { 4000, 12, 15 }, // mNpc_FEEL_NORMAL + { 3000, 10, 13 }, // mNpc_FEEL_HAPPY + { 4000, 12, 15 }, // mNpc_FEEL_ANGRY + { 4000, 10, 13 }, // mNpc_FEEL_SAD + { 5000, 9, 12 }, // mNpc_FEEL_SLEEPY + { 5000, 9, 12 } // mNpc_FEEL_PITFALL +}; + +extern void mNpc_ClearTalkInfo() { + mNpc_Talk_Info_c* talk_info_p = l_npc_talk_info; + int i; + + bzero(talk_info_p, sizeof(l_npc_talk_info)); + + for (i = 0; i < ARRAY_COUNT(l_npc_talk_info); i++) { + talk_info_p->quest_request = TRUE; + talk_info_p++; + } +} + +static void mNpc_TimerCountDown(mNpc_Talk_Info_c* talk_info) { + if (talk_info->timer > 0) { + talk_info->timer--; + } +} + +static void mNpc_SetUnlockTimer(u16* unlock_timer, u16* reset_timer, int feel) { + unlock_timer[0] = l_npc_temper[feel].unlock_timer; + reset_timer[0] = unlock_timer[0]; +} + +static int mNpc_CountTalkNum(int animal_idx, int feel) { + int res = FALSE; + + if (animal_idx >= 0 && animal_idx < ARRAY_COUNT(l_npc_talk_info)) { + mNpc_Talk_Info_c* talk_info = &l_npc_talk_info[animal_idx]; + + if (talk_info->talk_num < l_npc_temper[feel].talk_num_max && talk_info->timer > 0) { + talk_info->talk_num++; + res = TRUE; + } + } + + return res; +} + +extern int mNpc_CheckOverImpatient(int animal_idx, int feel) { + int res = FALSE; + + if (animal_idx >= 0 && animal_idx < ARRAY_COUNT(l_npc_talk_info)) { + mNpc_Talk_Info_c* talk_info = &l_npc_talk_info[animal_idx]; + + if (talk_info->talk_num >= l_npc_temper[feel].over_impatient_num) { + res = TRUE; + } + } + + return res; +} + +extern int mNpc_GetOverImpatient(int animal_idx, int feel) { + int patience = mNpc_PATIENCE_NORMAL; + + if (animal_idx >= 0 && animal_idx < ARRAY_COUNT(l_npc_talk_info)) { + mNpc_Talk_Info_c* talk_info = &l_npc_talk_info[animal_idx]; + + if (talk_info->talk_num >= l_npc_temper[feel].over_impatient_num) { + if (talk_info->talk_num >= l_npc_temper[feel].talk_num_max) { + patience = mNpc_PATIENCE_ANNOYED; // refuse to talk + } + else { + patience = mNpc_PATIENCE_MILDLY_ANNOYED; // dialog shows mild annoyance + } + } + } + + return patience; +} + +extern int mNpc_CheckQuestRequest(int animal_idx) { + int res = FALSE; + + if (animal_idx >= 0 && animal_idx < ARRAY_COUNT(l_npc_talk_info)) { + mNpc_Talk_Info_c* talk_info = &l_npc_talk_info[animal_idx]; + + if (talk_info->quest_request == TRUE) { + res = TRUE; + } + } + + return res; +} + +extern void mNpc_SetQuestRequestOFF(int animal_idx, int feel) { + mNpc_Talk_Info_c* talk_info = &l_npc_talk_info[animal_idx]; + + if (animal_idx >= 0 && animal_idx < ARRAY_COUNT(l_npc_talk_info)) { + if (talk_info->quest_request == TRUE) { + mNpc_SetUnlockTimer(&talk_info->unlock_timer, &talk_info->reset_timer, feel); + } + + talk_info->quest_request = FALSE; + } +} + +static void mNpc_UnlockTimerCountDown(mNpc_Talk_Info_c* talk_info) { + if (talk_info != NULL && talk_info->unlock_timer > 0 && talk_info->unlock_timer > (talk_info->reset_timer - 1000)) { + talk_info->unlock_timer--; + } +} + +extern void mNpc_TalkInfoMove() { + mNpc_Talk_Info_c* talk_info_p = l_npc_talk_info; + int i; + + if (mFI_CheckPlayerWade(mFI_WADE_START) == TRUE) { + for (i = 0; i < ARRAY_COUNT(l_npc_talk_info); i++) { + talk_info_p->reset_timer = talk_info_p->unlock_timer; + talk_info_p++; + } + } + + talk_info_p = l_npc_talk_info; + for (i = 0; i < ARRAY_COUNT(l_npc_talk_info); i++) { + mNpc_TimerCountDown(talk_info_p); + mNpc_UnlockTimerCountDown(talk_info_p); + + if (talk_info_p->unlock_timer == 0 && talk_info_p->reset_timer > 0) { + talk_info_p->talk_num = 0; + talk_info_p->quest_request = TRUE; + talk_info_p->reset_timer = 0; + } + + talk_info_p++; + } +} + +extern void mNpc_TalkEndMove(int animal_idx, int feel) { + if (animal_idx >= 0 && animal_idx < ARRAY_COUNT(l_npc_talk_info) && feel >= 0 && feel < mNpc_FEEL_NUM) { + mNpc_Talk_Info_c* talk_info = &l_npc_talk_info[animal_idx]; + talk_info->timer = 1000; + + if (mNpc_CountTalkNum(animal_idx, feel) == TRUE && mNpc_CheckOverImpatient(animal_idx, feel) == TRUE) { + mNpc_SetUnlockTimer(&talk_info->unlock_timer, &talk_info->reset_timer, feel); + } + } +} + +extern int mNpc_GetNpcFloorNo() { + mActor_name_t field_id = mFI_GetFieldId(); + mActor_name_t owner_name = Common_Get(house_owner_name); + int floor_no = -1; + + if (mFI_GET_TYPE(field_id) == mFI_FIELD_NPCROOM0) { + + if (owner_name != EMPTY_NO && owner_name != RSV_NO) { + int idx = mNpc_SearchAnimalinfo(Save_Get(animals), owner_name, ANIMAL_NUM_MAX); + + floor_no = Common_Get(npclist[idx]).house_data.floor_id; + } + } + + return floor_no; +} + +extern int mNpc_GetNpcWallNo() { + mActor_name_t field_id = mFI_GetFieldId(); + mActor_name_t owner_name = Common_Get(house_owner_name); + int wall_no = -1; + + if (mFI_GET_TYPE(field_id) == mFI_FIELD_NPCROOM0) { + + if (owner_name != EMPTY_NO && owner_name != RSV_NO) { + int idx = mNpc_SearchAnimalinfo(Save_Get(animals), owner_name, ANIMAL_NUM_MAX); + + wall_no = Common_Get(npclist[idx]).house_data.wall_id; + } + } + + return wall_no; +} + +/* 75% */ + +extern void mNpc_SetTalkBee() { + if (Common_Get(player_bee_swell_flag) != TRUE) { + Animal_c* animal = Save_Get(animals); + mNpc_NpcList_c* npclist = Common_Get(npclist); + int i; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE) { + npclist->conversation_flags.beesting = TRUE; + } + + animal++; + npclist++; + } + + if (mNpc_CheckFreeAnimalInfo(&Save_Get(island).animal) == FALSE) { + Common_Get(island_npclist[0]).conversation_flags.beesting = TRUE; + } + } +} + +extern u8 mNpc_GetFishCompleteTalk(mNpc_NpcList_c* npclist) { + u8 res = TRUE; + + if (npclist != NULL) { + res = npclist->conversation_flags.fish_complete; + } + + return res; +} + +extern u8 mNpc_GetInsectCompleteTalk(mNpc_NpcList_c* npclist) { + u8 res = TRUE; + + if (npclist != NULL) { + res = npclist->conversation_flags.insect_complete; + } + + return res; +} + +extern void mNpc_SetFishCompleteTalk(mNpc_NpcList_c* npclist) { + if (npclist != NULL) { + npclist->conversation_flags.fish_complete = TRUE; + } +} + +extern void mNpc_SetInsectCompleteTalk(mNpc_NpcList_c* npclist) { + if (npclist != NULL) { + npclist->conversation_flags.insect_complete = TRUE; + } +} + +extern void mNpc_SetNpcHomeYpos() { + Animal_c* animal = Save_Get(animals); + mNpc_NpcList_c* npclist = Common_Get(npclist); + int i; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE) { + npclist->house_position.y = mCoBG_GetBgY_OnlyCenter_FromWpos2(npclist->house_position, 0.0f); + } + + animal++; + npclist++; + } +} + +static void mNpc_SetIslandAnimalHouse(Anmhome_c* home) { + int island_x_blocks[mISL_FG_BLOCK_X_NUM]; + int set = FALSE; + int bx; + int ut; + + mFI_GetIslandBlockNumX(island_x_blocks); + + for (bx = 0; bx < mISL_FG_BLOCK_X_NUM; bx++) { + mActor_name_t* items = &Save_Get(island).fgblock[0][bx].items[0][0]; + + if (items != NULL) { + for (ut = 0; ut < UT_TOTAL_NUM; ut++) { + if (*items == COTTAGE_NPC) { + home->block_x = island_x_blocks[bx]; + home->block_z = mISL_BLOCK_Z; + home->ut_x = ut & 15; + home->ut_z = (ut >> 4) + 1; + set = TRUE; + + break; + } + + items++; + } + + if (set == TRUE) { + break; + } + } + } +} + +extern void mNpc_DecideIslandNpc(Animal_c* animal) { + static int cand_table[NPC_ISLANDER_NUM]; + + int* cand_p = cand_table; + int candidates = 0; + mNpc_Default_Data_c* def_list; + mActor_name_t npc_id; + s8* grow_list; + int i; + + bzero(cand_p, sizeof(cand_table)); + mNpc_ClearIslandAnimalInfo(animal); + def_list = npc_def_list; + grow_list = npc_grow_list; + + for (i = 0; i < NPC_NUM; i++) { + if (*grow_list == mNpc_GROW_ISLANDER) { + cand_p[0] = i; + cand_p++; + candidates++; + } + + if (candidates >= NPC_ISLANDER_NUM) { + break; + } + + grow_list++; + } + + if (candidates <= 0) { + candidates = 1; + } + + npc_id = NPC_START | cand_table[RANDOM(candidates)]; + mNpc_SetDefAnimal(animal, npc_id, def_list); + mNpc_SetHaveAppeared(npc_id); + mNpc_SetIslandAnimalHouse(&animal->home_info); +} + +static mActor_name_t l_mnpc_get_ftr = EMPTY_NO; +static int l_mnpc_get_letter = FALSE; +static int l_mnpc_chk_ftr_msg = FALSE; + +typedef struct island_ftr_s { + u16 set_ftr_bitfield; + mActor_name_t trade_list[mNpc_ISLAND_FTR_SAVE_NUM]; + mActor_name_t item_list[mNpc_ISLAND_FTR_NUM]; +} mNpc_Island_Ftr_c; + +static mNpc_Island_Ftr_c l_mnpc_island_ftr; + +static void mNpc_ClearIslandItemList(mActor_name_t* item_list, int count) { + bzero(item_list, count * sizeof(mActor_name_t)); +} + +static int mNpc_GetFreeIslandItemListIdx(mActor_name_t* item_list, int count) { + int idx = -1; + int i; + + for (i = 0; i < count; i++) { + if (*item_list == EMPTY_NO) { + idx = i; + break; + } + + item_list++; + } + + return idx; +} + +static int mNpc_SetIslandItemList(mActor_name_t* item_list, int count, mActor_name_t item) { + int res = FALSE; + int free_idx = mNpc_GetFreeIslandItemListIdx(item_list, count); + + if (free_idx != -1) { + item_list[free_idx] = item; + res = TRUE; + } + + return res; +} + +extern void mNpc_SetIslandRoomFtr(Animal_c* animal) { + mNpc_Island_Ftr_c* island_ftr_p = &l_mnpc_island_ftr; + + if (animal != NULL) { + Anmmem_c* memory = animal->memories; + int i; + + island_ftr_p->set_ftr_bitfield = 0; + for (i = 0; i < ANIMAL_MEMORY_NUM; i++) { + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE) { + island_ftr_p->set_ftr_bitfield |= memory->memuni.island.have_bitfield; + } + + memory++; + } + + bcopy(&animal->anmuni.island_ftr, island_ftr_p->trade_list, mNpc_ISLAND_FTR_SAVE_NUM * sizeof(mActor_name_t)); + mNpc_ClearIslandItemList(island_ftr_p->item_list, mNpc_ISLAND_FTR_NUM); + } +} + +static int mNpc_GetIslandFtrIdx(mActor_name_t item) { + mActor_name_t* island_room = mNpc_GetIslandRoomP(Save_Get(island).animal.id.npc_id); + int n = 0; + int idx = -1; + int i; + + if (island_room != NULL && ITEM_IS_FTR(item)) { + if (mNpc_CheckFtrIsIslandBestFtr(item) == TRUE) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + if (aMR_CorrespondFurniture(item, *island_room) == TRUE) { + idx = n; + break; + } + + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + n++; + } + + if (n == mNpc_ISLAND_FTR_NUM) { + break; + } + + island_room++; + } + } + else { + int ftr_unit = aMR_GetFurnitureUnit(item); + u32 ftr_variant0; + u32 ftr_variant1; + int j; + + if (ftr_unit == mRmTp_FTRSIZE_1x1) { + ftr_variant0 = RSV_ISLAND_FTR0; + ftr_variant1 = RSV_ISLAND_FTR4; + } + else { + ftr_variant0 = (mActor_name_t)(RSV_ISLAND_FTR0 + (ftr_unit + 1) * mRmTp_DIRECT_NUM); + ftr_variant1 = EMPTY_NO; + } + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + if (*island_room == (mActor_name_t)(ftr_variant0 + j) || *island_room == (mActor_name_t)(ftr_variant1 + j)) { + idx = n; + break; + } + } + + if (idx != -1) { + break; + } + + n++; + } + + if (n == mNpc_ISLAND_FTR_NUM) { + break; + } + + island_room++; + } + } + } + + return idx; +} + +extern void mNpc_SetIslandGetFtr(mActor_name_t ftr) { + int n; + mNpc_Island_Ftr_c* island_ftr_p = &l_mnpc_island_ftr; + mActor_name_t* island_room = mNpc_GetIslandRoomP(Save_Get(island).animal.id.npc_id); + int set; + int variant; + int slot; + int i; + + n = 0; + set = FALSE; + + if (ftr != EMPTY_NO && ITEM_IS_FTR(ftr) && island_room != NULL) { + if (mNpc_CheckFtrIsIslandBestFtr(ftr) == TRUE) { + int idx = mNpc_GetIslandFtrIdx(ftr); + + if (idx >= 0 && idx < mNpc_ISLAND_FTR_NUM && ((island_ftr_p->set_ftr_bitfield >> idx) & 1) == 0) { + island_ftr_p->set_ftr_bitfield |= (1 << idx); + set = TRUE; + } + } + else { + int ftr_unit = aMR_GetFurnitureUnit(ftr); + mActor_name_t ftr_variant0; + mActor_name_t ftr_variant1; + int j; + + if (ftr_unit == mRmTp_FTRSIZE_1x1) { + ftr_variant0 = RSV_ISLAND_FTR0; + ftr_variant1 = RSV_ISLAND_FTR4; + } + else { + ftr_variant0 = RSV_ISLAND_FTR0 + (ftr_unit + 1) * 4; + ftr_variant1 = EMPTY_NO; + } + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + variant = -1; + + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + if (*island_room == (mActor_name_t)(ftr_variant0 + j)) { + variant = 0; + break; + } + else if (*island_room == (mActor_name_t)(ftr_variant1 + j)) { + variant = 1; + break; + } + } + + if (variant != -1 && n >= 0 && n < mNpc_ISLAND_FTR_NUM && ((island_ftr_p->set_ftr_bitfield >> n) & 1) == 0) { + island_ftr_p->set_ftr_bitfield |= (1 << n); + + if (variant == 0) { + slot = (ftr_variant0 - RSV_ISLAND_FTR0) / mNpc_ISLAND_FTR_SAVE_NUM; + } + else { + slot = (ftr_variant1 - RSV_ISLAND_FTR0) / mNpc_ISLAND_FTR_SAVE_NUM; + } + + if (slot < mNpc_ISLAND_FTR_SAVE_NUM) { + ftr = aMR_FurnitureFg_to_FurnitureFgWithDirect(ftr, j); + island_ftr_p->trade_list[slot] = ftr; + set = TRUE; + } + + break; + } + + n++; + } + + if (n == mNpc_ISLAND_FTR_NUM) { + break; + } + + island_room++; + } + } + + if (set == TRUE) { + mNpc_SetIslandItemList(island_ftr_p->item_list, mNpc_ISLAND_FTR_NUM, ftr); + } + } +} + +extern void mNpc_SetIslandGetFtrtoRoom() { + mActor_name_t* item_list = l_mnpc_island_ftr.item_list; + Animal_c* animal = &Save_Get(island).animal; + + if (Common_Get(now_private) != NULL) { + Private_c* priv = Common_Get(now_private); + int i; + + for (i = 0; i < mNpc_ISLAND_FTR_NUM; i++) { + if (*item_list != EMPTY_NO && ITEM_IS_FTR(*item_list)) { + mNpc_SetIslandFtr(&priv->player_ID, *item_list); + } + + item_list++; + } + + mNpc_RestoreIslandPresentFtr(); + mNpc_ClearIslandPresentFtrInfo(); + mNpc_SetIslandRoomFtr(animal); + } +} + +extern void mNpc_SetIslandGetLetter(int get) { + l_mnpc_get_letter = get; +} + +extern int mNpc_GetIslandGetLetter() { + return l_mnpc_get_letter; +} + +extern void mNpc_SetIslandCheckFtrMsg(int set) { + l_mnpc_chk_ftr_msg = set; +} + +extern int mNpc_GetIslandCheckFtrMsg() { + return l_mnpc_chk_ftr_msg; +} + +typedef struct npc_island_room_data_s { + mActor_name_t items[UT_Z_NUM][UT_X_NUM]; + int wall_no; + int floor_no; + mActor_name_t npc_id; +} mNpc_Island_Npc_Room_Data_c; + +static mNpc_Island_Npc_Room_Data_c l_island_npc_room_data[NPC_ISLANDER_NUM]; + +static mActor_name_t l_island_npc_best_fg_id[NPC_ISLANDER_NUM][2] = { + { 0x0250, 0x0251 }, + { 0x0346, 0x0347 }, + { 0x0372, 0x0373 }, + { 0x027C, 0x027D }, + { 0x02EA, 0x02EB }, + { 0x0326, 0x0327 }, + { 0x0270, 0x0271 }, + { 0x01C0, 0x01C1 }, + { 0x039A, 0x039B }, + { 0x02F0, 0x02F1 }, + { 0x02C4, 0x02C5 }, + { 0x0224, 0x0225 }, + { 0x02D4, 0x02D5 }, + { 0x02A4, 0x02A5 }, + { 0x038C, 0x038D }, + { 0x01FE, 0x01FF }, + { 0x032E, 0x032F }, + { 0x01E6, 0x01E7 } +}; + +extern void mNpc_ClearIslandNpcRoomData() { + bzero(l_island_npc_room_data, sizeof(l_island_npc_room_data)); +} + +extern void mNpc_IslandNpcRoomDataSet(mFM_fg_data_c** data_table, int base_idx) { + mNpc_Island_Npc_Room_Data_c* dst = l_island_npc_room_data; + s8* grow_list = npc_grow_list; + mNpc_NpcHouseData_c* house_list = npc_house_list; + int islander = 0; + int i; + + for (i = 0; i < NPC_NUM; i++) { + if (*grow_list == mNpc_GROW_ISLANDER) { + int idx = l_island_npc_best_fg_id[islander][0] - base_idx; + mFM_fg_data_c* best_id_p = data_table[idx]; + + if (best_id_p != NULL) { + dst->wall_no = house_list->wall_id; + dst->floor_no = house_list->floor_id; + bcopy(data_table[idx]->items, dst->items, sizeof(best_id_p->items)); + dst->npc_id = NPC_START | i; + + islander++; + dst++; + } + } + + if (islander >= NPC_ISLANDER_NUM) { + break; + } + + grow_list++; + house_list++; + } +} + +static mNpc_Island_Npc_Room_Data_c* mNpc_GetIslandRoomInfoP(mActor_name_t npc_id) { + mNpc_Island_Npc_Room_Data_c* room_p = l_island_npc_room_data; + mNpc_Island_Npc_Room_Data_c* res = NULL; + int i; + + for (i = 0; i < NPC_ISLANDER_NUM; i++) { + /* @BUG - devs used & instead of && */ + #ifndef BUGFIXES + if (npc_id != EMPTY_NO & room_p->npc_id == npc_id) { + #else + if (npc_id != EMPTY_NO && room_p->npc_id == npc_id) { + #endif + res = room_p; + break; + } + + room_p++; + } + + return res; +} + +extern mActor_name_t* mNpc_GetIslandRoomP(mActor_name_t npc_id) { + mActor_name_t* res = NULL; + mNpc_Island_Npc_Room_Data_c* room_info = mNpc_GetIslandRoomInfoP(npc_id); + + if (room_info != NULL) { + res = room_info->items[0]; + } + + return res; +} + +extern void mNpc_GetIslandWallFloorIdx(int* wall, int* floor, mActor_name_t npc_id) { + mNpc_Island_Npc_Room_Data_c* room_info; + + if (wall != NULL && floor != NULL) { + wall[0] = 8; + floor[0] = 32; + + room_info = mNpc_GetIslandRoomInfoP(npc_id); + + if (room_info != NULL) { + wall[0] = room_info->wall_no; + floor[0] = room_info->floor_no; + } + } +} + +static mActor_name_t mNpc_getNormalFtr(mActor_name_t rsv_name, Animal_c* animal) { + mActor_name_t ftr = EMPTY_NO; + int idx; + + if (rsv_name >= RSV_ISLAND_FTR0 && rsv_name <= RSV_ISLAND_FTR15) { + int rsv = rsv_name - RSV_ISLAND_FTR0; + + idx = (rsv - (rsv % mNpc_ISLAND_FTR_SAVE_NUM)) / mNpc_ISLAND_FTR_SAVE_NUM; + if (idx >= mNpc_ISLAND_FTR_SAVE_NUM) { + idx = 0; + } + + ftr = animal->anmuni.island_ftr[idx]; + } + + return ftr; +} + +static int mNpc_CheckIslandNpcRoomFtrIdx(int ftr_idx) { + Animal_c* animal = &Save_Get(island).animal; + Anmmem_c* memory; + int i; + int res = -1; + + if (mNpc_CheckFreeAnimalInfo(animal) == FALSE && ftr_idx >= 0 && ftr_idx < mNpc_ISLAND_FTR_NUM) { + memory = animal->memories; + + for (i = 0; i < ANIMAL_MEMORY_NUM; i++) { + if ( + mPr_NullCheckPersonalID(&memory->memory_player_id) == FALSE && + ((memory->memuni.island.have_bitfield >> ftr_idx) & 1) == 1 + ) { + res = i; + break; + } + + memory++; + } + } + + return res; +} + +extern int mNpc_CheckIslandNpcRoomFtrItemNo_keep(mActor_name_t ftr) { + mActor_name_t* island_room; + int keep; + mActor_name_t* trade_list; + mNpc_Island_Ftr_c* island_ftr_p; + mActor_name_t rsv_item; + int n; + int found; + int i; + int j; + + island_ftr_p = &l_mnpc_island_ftr; + trade_list = l_mnpc_island_ftr.trade_list; + rsv_item = EMPTY_NO; + found = FALSE; + n = 0; + keep = FALSE; + island_room = mNpc_GetIslandRoomP(Save_Get(island).animal.id.npc_id); + + if (ITEM_IS_FTR(ftr) && island_room != NULL) { + if (mNpc_CheckFtrIsIslandBestFtr(ftr) == TRUE) { + int idx = mNpc_GetIslandFtrIdx(ftr); + + /* @BUG & should've been && */ + #ifndef BUGFIXES + if (((idx >= 0) & (idx < mNpc_ISLAND_FTR_NUM)) && ((island_ftr_p->set_ftr_bitfield >> idx) & 1) == 0) { + #else + if (((idx >= 0) && (idx < mNpc_ISLAND_FTR_NUM)) && ((island_ftr_p->set_ftr_bitfield >> idx) & 1) == 0) { + #endif + keep = TRUE; + } + } + else { + for (i = 0; i < mNpc_ISLAND_FTR_SAVE_NUM; i++) { + /* Why loop through each direction instead of just masking off the rotation bits */ + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + mActor_name_t dir_item = aMR_FurnitureFg_to_FurnitureFgWithDirect(ftr, j); + + if (dir_item == trade_list[i]) { + found = TRUE; + break; + } + } + + if (found == TRUE) { + break; + } + } + + if (found == FALSE) { + int ftr_unit = aMR_GetFurnitureUnit(ftr); + + if (ftr_unit == mRmTp_FTRSIZE_1x1) { + if (trade_list[0] == EMPTY_NO) { + rsv_item = RSV_ISLAND_FTR0; + } + else if (trade_list[1] == EMPTY_NO) { + rsv_item = RSV_ISLAND_FTR4; + } + } + else if (ftr_unit != -1) { + ftr_unit++; + + if (trade_list[ftr_unit] == EMPTY_NO) { + rsv_item = RSV_ISLAND_FTR0 + ftr_unit * mRmTp_DIRECT_NUM; + } + } + + if (rsv_item != EMPTY_NO) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + if (*island_room == (mActor_name_t)(rsv_item + j)) { + keep = TRUE; + break; + } + } + } + + if (keep == TRUE) { + break; + } + + island_room++; + } + } + } + } + } + + return keep; +} + +extern void mNpc_ChangeIslandRoom(mActor_name_t* items) { + Animal_c* animal = &Save_Get(island).animal; + int n = 0; + int i; + + if (items != NULL) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*items)) { + if (mNpc_CheckIslandNpcRoomFtrIdx(n) == -1) { + items[0] = RSV_NO; // cleared because the item hasn't been "obtained" by the islander yet + } + + n++; + } + else if (*items >= RSV_ISLAND_FTR0 && *items <= RSV_ISLAND_FTR15) { + if (mNpc_CheckIslandNpcRoomFtrIdx(n) == -1) { + items[0] = RSV_NO; // cleared because the item hasn't been "obtained" by the islander yet + } + else { + items[0] = mNpc_getNormalFtr(*items, animal); // update with "not best" furniture + } + + n++; + } + + if (n >= mNpc_ISLAND_FTR_NUM) { + break; + } + + items++; + } + } +} + +extern int mNpc_CheckFtrIsIslandBestFtr(mActor_name_t ftr) { + mActor_name_t* island_room; + int i; + int res = FALSE; + + island_room = mNpc_GetIslandRoomP(Save_Get(island).animal.id.npc_id); + if (island_room != NULL && ITEM_IS_FTR(ftr)) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (aMR_CorrespondFurniture(ftr, *island_room) == TRUE) { + res = TRUE; + break; + } + + island_room++; + } + } + + return res; +} + +extern int mNpc_CheckFtrIsIslandNormalFtr(mActor_name_t ftr) { + int res = FALSE; + + if (ITEM_IS_FTR(ftr) && mNpc_CheckFtrIsIslandBestFtr(ftr) == FALSE) { + res = TRUE; + } + + return res; +} + +extern int mNpc_SetIslandFtr(PersonalID_c* pid, mActor_name_t ftr) { + int n; + Anmmem_c* memory; + mActor_name_t* island_room; + int slot; + Animal_c* animal; + mActor_name_t rsv_item; + int i; + int j; + int variant; + int set; + int direct; + + memory = Save_Get(island).animal.memories; + animal = &Save_Get(island).animal; + island_room = mNpc_GetIslandRoomP(animal->id.npc_id); + n = 0; + rsv_item = EMPTY_NO; + set = FALSE; + slot = 0; + direct = 0; + + if (ITEM_IS_FTR(ftr) && island_room != NULL && pid != NULL && mPr_NullCheckPersonalID(pid) == FALSE) { + int mem_idx = mNpc_GetAnimalMemoryIdx(pid, memory, ANIMAL_MEMORY_NUM); + + if (mem_idx == -1) { + memory = &memory[mNpc_ForceGetFreeAnimalMemoryIdx(animal, memory, ANIMAL_MEMORY_NUM)]; + mNpc_SetAnimalMemory(pid, &animal->id, memory); + } + else { + memory = &memory[mem_idx]; + } + + /* Unnecessary NULL check, memory is guaranteed to exist */ + if (memory != NULL) { + if (mNpc_CheckFtrIsIslandBestFtr(ftr) == TRUE) { + int idx = mNpc_GetIslandFtrIdx(ftr); + + if (idx != -1 && mNpc_CheckIslandNpcRoomFtrIdx(idx) == -1) { + memory->memuni.island.have_bitfield |= (1 << idx); + set = TRUE; + } + } + else { + mActor_name_t ftr_variant0; + mActor_name_t ftr_variant1; + int ftr_unit = aMR_GetFurnitureUnit(ftr); + + if (ftr_unit == mRmTp_FTRSIZE_1x1) { + ftr_variant0 = RSV_ISLAND_FTR0; + ftr_variant1 = RSV_ISLAND_FTR4; + } + else { + ftr_variant0 = RSV_ISLAND_FTR0 + (ftr_unit + 1) * 4; + ftr_variant1 = EMPTY_NO; + } + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + variant = -1; + + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + if (*island_room == (mActor_name_t)(ftr_variant0 + j)) { + direct = j; + variant = 0; + break; + } + else if (*island_room == (mActor_name_t)(ftr_variant1 + j)) { + direct = j; + variant = 1; + break; + } + } + + if (variant != -1 && mNpc_CheckIslandNpcRoomFtrIdx(n) == -1) { + memory->memuni.island.have_bitfield |= (1 << n); + + if (variant == 0) { + slot = (ftr_variant0 - RSV_ISLAND_FTR0) / mNpc_ISLAND_FTR_SAVE_NUM; + } + else { + slot = (ftr_variant1 - RSV_ISLAND_FTR0) / mNpc_ISLAND_FTR_SAVE_NUM; + } + + if (slot < mNpc_ISLAND_FTR_SAVE_NUM) { + Save_Get(island).animal.anmuni.island_ftr[slot] = aMR_FurnitureFg_to_FurnitureFgWithDirect(ftr, direct); + set = TRUE; + } + + break; + } + + n++; + } + + if (n == mNpc_ISLAND_FTR_NUM) { + break; + } + + island_room++; + } + } + } + } + + return set; +} + +extern int mNpc_EraseIslandFtr(mActor_name_t ftr) { + int n; + Animal_c* animal; + mActor_name_t* island_room; + mActor_name_t* island_ftr; + mActor_name_t rsv_no; + int i; + int j; + int variant; + int set; + + animal = &Save_Get(island).animal; + island_room = mNpc_GetIslandRoomP(animal->id.npc_id); + island_ftr = Save_Get(island).animal.anmuni.island_ftr; + rsv_no = EMPTY_NO; + n = 0; + set = FALSE; + + if (ITEM_IS_FTR(ftr) && island_room != NULL) { + if (mNpc_CheckFtrIsIslandBestFtr(ftr) == TRUE) { + int idx = mNpc_GetIslandFtrIdx(ftr); + int mem_idx = mNpc_CheckIslandNpcRoomFtrIdx(idx); + + if (idx != -1 && mem_idx != -1) { + Anmmem_c* memory = &Save_Get(island).animal.memories[mem_idx]; + u16* have_bitfield_p = &memory->memuni.island.have_bitfield; + u16 have_bitfield = have_bitfield_p[0] & ~(1 << idx); + have_bitfield_p[0] = have_bitfield; + set = TRUE; + } + } + else { + for (i = 0; i < mNpc_ISLAND_FTR_SAVE_NUM; i++) { + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + mActor_name_t ftr_rot = aMR_FurnitureFg_to_FurnitureFgWithDirect(ftr, j); + + if (ftr_rot == island_ftr[i]) { + rsv_no = RSV_ISLAND_FTR0 + i * mRmTp_DIRECT_NUM; + island_ftr[i] = EMPTY_NO; + break; + } + } + + if (rsv_no != EMPTY_NO) { + break; + } + } + + if (rsv_no != EMPTY_NO) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + variant = -1; + + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + if (*island_room == (mActor_name_t)(rsv_no + j)) { + variant = 1; + break; + } + } + + if (variant != -1) { + int mem_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + if (mem_idx != -1) { + Anmmem_c* memory = &Save_Get(island).animal.memories[mem_idx]; + u16* have_bitfield_p = &memory->memuni.island.have_bitfield; + u16 have_bitfield = have_bitfield_p[0] & ~(1 << n); + have_bitfield_p[0] = have_bitfield; + set = TRUE; + + break; + } + } + + n++; + } + + if (n == mNpc_ISLAND_FTR_NUM) { + break; + } + + island_room++; + } + } + } + } + + return set; +} + +extern int mNpc_EraseIslandFtr_keep(mActor_name_t ftr) { + int n; + Animal_c* animal; + mNpc_Island_Ftr_c* island_ftr_p; + mActor_name_t* island_room; + mActor_name_t* trade_list; + int set; + mActor_name_t rsv_no; + int i; + int j; + + animal = &Save_Get(island).animal; + island_ftr_p = &l_mnpc_island_ftr; + island_room = mNpc_GetIslandRoomP(animal->id.npc_id); + trade_list = l_mnpc_island_ftr.trade_list; + rsv_no = EMPTY_NO; + n = 0; + set = FALSE; + + if (ITEM_IS_FTR(ftr) && island_room != NULL) { + if (mNpc_CheckFtrIsIslandBestFtr(ftr) == TRUE) { + int idx = mNpc_GetIslandFtrIdx(ftr); + + if (idx >= 0 && idx < mNpc_ISLAND_FTR_NUM && ((island_ftr_p->set_ftr_bitfield >> idx) & 1) == 1) { + u16* set_ftr_bitfield_p = &island_ftr_p->set_ftr_bitfield; + u16 set_bitfield = set_ftr_bitfield_p[0] & ~(1 << idx); + set_ftr_bitfield_p[0] = set_bitfield; + set = TRUE; + } + } + else { + for (i = 0; i < mNpc_ISLAND_FTR_SAVE_NUM; i++) { + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + mActor_name_t ftr_rot = aMR_FurnitureFg_to_FurnitureFgWithDirect(ftr, j); + + if (ftr_rot == trade_list[i]) { + rsv_no = RSV_ISLAND_FTR0 + i * mRmTp_DIRECT_NUM; + trade_list[i] = EMPTY_NO; + break; + } + } + + if (rsv_no != EMPTY_NO) { + break; + } + } + + if (rsv_no != EMPTY_NO) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + int variant = -1; + + for (j = 0; j < mRmTp_DIRECT_NUM; j++) { + if (*island_room == (mActor_name_t)(rsv_no + j)) { + variant = 1; + break; + } + } + + if (variant != -1 && n >= 0 && n < mNpc_ISLAND_FTR_NUM && ((island_ftr_p->set_ftr_bitfield >> n) & 1) == 1) { + u16* set_ftr_bitfield_p = &island_ftr_p->set_ftr_bitfield; + u16 set_bitfield = set_ftr_bitfield_p[0] & ~(1 << n); + set_ftr_bitfield_p[0] = set_bitfield; + set = TRUE; + + break; + } + + n++; + } + + if (n == mNpc_ISLAND_FTR_NUM) { + break; + } + + island_room++; + } + } + } + } + + return set; +} + +typedef struct island_present_ftr_s { + PersonalID_c pid; + mActor_name_t present; +} mNpc_IslandPresentFtr_c; + +static mNpc_IslandPresentFtr_c l_mnpc_island_present_ftr; + +static void mNpc_ClearIslandPresentFtrInfo_common(mNpc_IslandPresentFtr_c* info) { + mPr_ClearPersonalID(&info->pid); + info->present = EMPTY_NO; +} + +extern void mNpc_ClearIslandPresentFtrInfo() { + mNpc_ClearIslandPresentFtrInfo_common(&l_mnpc_island_present_ftr); +} + +extern void mNpc_SetIslandPresentFtr() { + Animal_c* animal; + AnmPersonalID_c* anm_id; + mNpc_IslandPresentFtr_c* present_ftr_p; + Anmmem_c* memory; + int ftr_num; + mActor_name_t* island_room; + int selected; + int n; + int i; + + ftr_num = 0; + n = 0; + animal = &Save_Get(island).animal; + anm_id = &Save_Get(island).animal.id; + memory = Save_Get(island).animal.memories; + present_ftr_p = &l_mnpc_island_present_ftr; + + mNpc_ClearIslandPresentFtrInfo_common(present_ftr_p); + + for (i = 0; i < mNpc_ISLAND_FTR_NUM; i++) { + if (mNpc_CheckIslandNpcRoomFtrIdx(i) != -1) { + ftr_num++; + } + } + + if (ftr_num > 1) { + selected = RANDOM(ftr_num); + island_room = mNpc_GetIslandRoomP(anm_id->npc_id); + + if (island_room != NULL) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + if (mNpc_CheckIslandNpcRoomFtrIdx(n) != -1) { + if (selected == 0) { + int mem_idx; + + present_ftr_p->present = aMR_FurnitureFg_to_FurnitureFgWithDirect(*island_room, mRmTp_DIRECT_SOUTH); + mem_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + if (mem_idx != -1) { + memory = &memory[mem_idx]; + mPr_CopyPersonalID(&present_ftr_p->pid, &memory->memory_player_id); + } + + break; + } + else { + selected--; + } + } + + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + if (mNpc_CheckIslandNpcRoomFtrIdx(n) != -1) { + if (selected == 0) { + int mem_idx; + + present_ftr_p->present = mNpc_getNormalFtr(*island_room, animal); + mem_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + if (mem_idx != -1) { + memory = &memory[mem_idx]; + mPr_CopyPersonalID(&present_ftr_p->pid, &memory->memory_player_id); + } + + break; + } + else { + selected--; + } + } + + n++; + } + + island_room++; + } + } + } + + if (mNpc_CheckIslandPresentFtrIs() == TRUE) { + mNpc_EraseIslandFtr(mNpc_GetIslandPresentFtr()); + } +} + +extern void mNpc_RestoreIslandPresentFtr() { + PersonalID_c* pid; + mActor_name_t present; + + if (mNpc_CheckIslandPresentFtrIs() == TRUE) { + present = mNpc_GetIslandPresentFtr(); + pid = mNpc_GetIslandPresentFtrPersonalID(); + + if (present != EMPTY_NO && pid != NULL && mPr_NullCheckPersonalID(pid) == FALSE) { + mNpc_SetIslandFtr(pid, present); + } + } +} + +extern int mNpc_GetIslandRoomFtrNum() { + int num = 0; + int i; + + for (i = 0; i < mNpc_ISLAND_FTR_NUM; i++) { + if (mNpc_CheckIslandNpcRoomFtrIdx(i) != -1) { + num++; + } + } + + return num; +} + +extern int mNpc_CheckIslandPresentFtrIs() { + mNpc_IslandPresentFtr_c* present_ftr_p = &l_mnpc_island_present_ftr; + int res = FALSE; + + if (present_ftr_p->present != EMPTY_NO && mPr_NullCheckPersonalID(&present_ftr_p->pid) == FALSE) { + res = TRUE; + } + + return res; +} + +extern mActor_name_t mNpc_GetIslandPresentFtr() { + return l_mnpc_island_present_ftr.present; +} + +extern PersonalID_c* mNpc_GetIslandPresentFtrPersonalID() { + PersonalID_c* pid = NULL; + + if (mNpc_CheckIslandPresentFtrIs() == TRUE) { + pid = &l_mnpc_island_present_ftr.pid; + } + + return pid; +} + +extern mActor_name_t mNpc_GetRandomBestFtr() { + Animal_c* animal = &Save_Get(island).animal; + mActor_name_t* island_room = mNpc_GetIslandRoomP(animal->id.npc_id); + mNpc_Island_Ftr_c* island_ftr_p = &l_mnpc_island_ftr; + mActor_name_t* island_room_p = island_room; + int other_ftr_num = 0; + int n = 0; + mActor_name_t best_ftr = EMPTY_NO; + mActor_name_t present = mNpc_GetIslandPresentFtr(); + int selected; + int i; + + if (island_room != NULL) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + if (n >= 0 && n < mNpc_ISLAND_FTR_NUM && + ((island_ftr_p->set_ftr_bitfield >> n) & 1) == 0 && + aMR_CorrespondFurniture(present, *island_room) == FALSE + ) { + other_ftr_num++; + } + + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + n++; + } + + island_room++; + } + + island_room = island_room_p; + n = 0; + + if (island_room != NULL && other_ftr_num > 0) { + selected = RANDOM(other_ftr_num); + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + if (n >= 0 && n < mNpc_ISLAND_FTR_NUM && + ((island_ftr_p->set_ftr_bitfield >> n) & 1) == 0 && + aMR_CorrespondFurniture(present, *island_room) == FALSE + ) { + if (selected == 0) { + best_ftr = aMR_FurnitureFg_to_FurnitureFgWithDirect(*island_room, mRmTp_DIRECT_SOUTH); + break; + } + else { + selected--; + } + } + + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + n++; + } + + island_room++; + } + } + } + + return best_ftr; +} + +extern Anmmem_c* mNpc_GetOtherBestFtr(PersonalID_c* pid, mActor_name_t* other_best_ftr, mActor_name_t exist_ftr) { + Animal_c* animal; + mActor_name_t* island_room; + Anmmem_c* memory; + Anmmem_c* memories; + mActor_name_t* island_room_p; + int other_ftr_num; + int n; + int mem_idx; + int i; + + animal = &Save_Get(island).animal; + memories = Save_Get(island).animal.memories; + memory = NULL; + island_room = mNpc_GetIslandRoomP(animal->id.npc_id); + island_room_p = island_room; + other_ftr_num = 0; + n = 0; + + if (island_room != NULL && other_best_ftr != NULL) { + other_best_ftr[0] = EMPTY_NO; + mem_idx = mNpc_GetAnimalMemoryIdx(pid, memories, ANIMAL_MEMORY_NUM); + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + if (aMR_CorrespondFurniture(*island_room, exist_ftr) == FALSE) { + int ftr_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + /* The item must not have been gifted by the current player */ + if (ftr_idx != -1 && ftr_idx != mem_idx) { + other_ftr_num++; + } + } + + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + n++; + } + + island_room++; + } + + island_room = island_room_p; + n = 0; + + if (island_room != NULL && other_ftr_num > 0) { + int selected = RANDOM(other_ftr_num); + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + if (aMR_CorrespondFurniture(*island_room, exist_ftr) == FALSE) { + int ftr_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + /* The item must not have been gifted by the current player */ + if (ftr_idx != -1 && ftr_idx != mem_idx) { + if (selected == 0) { + other_best_ftr[0] = aMR_FurnitureFg_to_FurnitureFgWithDirect(*island_room, mRmTp_DIRECT_SOUTH); + memory = &memories[ftr_idx]; /* Memory of the other player who gave it */ + break; + } + else { + selected--; + } + } + } + + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + n++; + } + + island_room++; + } + } + } + + return memory; +} + +extern mActor_name_t mNpc_GetPlayerBestFtr(PersonalID_c* pid, mActor_name_t exist_ftr) { + AnmPersonalID_c* anm_id; + Anmmem_c* memories; + mActor_name_t* island_room; + mActor_name_t* island_room_p; + int mem_idx; + int other_ftr_num; + int n; + int selected; + int i; + mActor_name_t best_ftr; + + anm_id = &Save_Get(island).animal.id; + memories = Save_Get(island).animal.memories; + island_room = mNpc_GetIslandRoomP(anm_id->npc_id); + other_ftr_num = 0; + n = 0; + best_ftr = EMPTY_NO; + island_room_p = island_room; + + if (island_room != NULL && pid != NULL) { + mem_idx = mNpc_GetAnimalMemoryIdx(pid, memories, ANIMAL_MEMORY_NUM); + + if (mem_idx != -1) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + if (aMR_CorrespondFurniture(*island_room, exist_ftr) == FALSE) { + int ftr_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + /* The item must have been gifted by the current player */ + if (ftr_idx == mem_idx) { + other_ftr_num++; + } + } + + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + n++; + } + + island_room++; + } + + n = 0; + + if (island_room_p != NULL && other_ftr_num > 0) { + selected = RANDOM(other_ftr_num); + island_room = island_room_p; + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room)) { + if (aMR_CorrespondFurniture(*island_room, exist_ftr) == FALSE) { + int ftr_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + /* The item must have been gifted by the current player */ + if (ftr_idx == mem_idx) { + if (selected == 0) { + best_ftr = aMR_FurnitureFg_to_FurnitureFgWithDirect(*island_room, mRmTp_DIRECT_SOUTH); + break; + } + else { + selected--; + } + } + } + + n++; + } + else if (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15) { + n++; + } + + island_room++; + } + } + } + } + + return best_ftr; +} + +extern mActor_name_t mNpc_GetPlayerFtr(PersonalID_c* pid) { + Animal_c* animal = &Save_Get(island).animal; + mActor_name_t* island_room = mNpc_GetIslandRoomP(Save_Get(island).animal.id.npc_id); + mActor_name_t best_ftr = EMPTY_NO; + mActor_name_t* island_room_p = island_room; + int other_ftr_num = 0; + int n = 0; + int mem_idx = mNpc_GetAnimalMemoryIdx(pid, animal->memories, ANIMAL_MEMORY_NUM); + int selected; + int i; + + if (island_room != NULL && mem_idx != -1) { + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room) || (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15)) { + int ftr_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + /* The item must have been gifted by the current player */ + if (ftr_idx == mem_idx) { + other_ftr_num++; + } + + n++; + } + + island_room++; + } + + island_room = island_room_p; + n = 0; + + if (island_room != NULL && other_ftr_num > 0) { + selected = RANDOM(other_ftr_num); + + for (i = 0; i < UT_TOTAL_NUM; i++) { + if (ITEM_IS_FTR(*island_room) || (*island_room >= RSV_ISLAND_FTR0 && *island_room <= RSV_ISLAND_FTR15)) { + int ftr_idx = mNpc_CheckIslandNpcRoomFtrIdx(n); + + /* The item must have been gifted by the current player */ + if (ftr_idx == mem_idx) { + if (selected == 0) { + mActor_name_t ftr = *island_room; + + if (ftr >= RSV_ISLAND_FTR0 && ftr <= RSV_ISLAND_FTR15) { + ftr = mNpc_getNormalFtr(ftr, animal); + } + + best_ftr = aMR_FurnitureFg_to_FurnitureFgWithDirect(ftr, mRmTp_DIRECT_SOUTH); + break; + } + else { + selected--; + } + } + + n++; + } + + island_room++; + } + } + } + + return best_ftr; +} + +static int mNpc_CheckIslandAnimalTableNo(mActor_name_t npc_id) { + int res = FALSE; + + if ( + ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC && + (npc_id & 0xFFF) < NPC_NUM && + npc_grow_list[npc_id & 0xFFF] == mNpc_GROW_ISLANDER + ) { + res = TRUE; + } + + return res; +} + +static int mNpc_CheckIslandAnimalID(AnmPersonalID_c* anm_id) { + int res = FALSE; + + if (anm_id != NULL && mNpc_CheckFreeAnimalPersonalID(anm_id) == FALSE) { + res = mNpc_CheckIslandAnimalTableNo(anm_id->npc_id); + } + + return res; +} + +extern int mNpc_CheckIslandAnimal(Animal_c* animal) { + int res = FALSE; + + if (animal != NULL) { + res = mNpc_CheckIslandAnimalID(&animal->id); + } + + return res; +} + +static mActor_name_t l_mnpc_island_md_table[NPC_ISLANDER_NUM] = { + ITM_MINIDISK16, // K.K. Tango + ITM_MINIDISK08, // K.K. Aria + ITM_MINIDISK06, // K.K. Etude + ITM_MINIDISK33, // K.K. Soul + ITM_MINIDISK26, // K.K. Rock + ITM_MINIDISK15, // K.K. Ska + ITM_MINIDISK07, // K.K. Lullaby + ITM_MINIDISK05, // K.K. Fusion + ITM_MINIDISK43, // Comrade K.K. + ITM_MINIDISK09, // K.K. Samba + ITM_MINIDISK49, // Cafe K.K. + ITM_MINIDISK13, // K.K. Mambo + ITM_MINIDISK01, // K.K. March + ITM_MINIDISK44, // K.K. Lament + ITM_MINIDISK40, // K.K. Country + ITM_MINIDISK10, // K.K. Bossa + ITM_MINIDISK18, // Aloha K.K. + ITM_MINIDISK46 // K.K. Dirge +}; + +extern u32 mNpc_GetMDIdx(mActor_name_t npc_id) { + mNpc_Island_Npc_Room_Data_c* room_data = l_island_npc_room_data; + u32 idx = 0; + int i; + + for (i = 0; i < NPC_ISLANDER_NUM; i++) { + if (room_data->npc_id == npc_id) { + idx = (u16)(l_mnpc_island_md_table[i] - ITM_MINIDISK_START); + // TODO: Bug? missing break here + } + + room_data++; + } + + return idx; +} + +extern u32 mNpc_GetIslandMDIdx() { + return mNpc_GetMDIdx(Save_Get(island).animal.id.npc_id); +} + +static Mail_c l_npc_hp_mail; + +extern void mNpc_ClearHPMail(AnmHPMail_c* hp_mail, int count) { + bzero(hp_mail, count * sizeof(AnmHPMail_c)); +} + +extern void mNpc_AllClearHPMailPlayerIdx(int player_no) { + Animal_c* animal = Save_Get(animals); + int i; + + if (player_no >= 0 && player_no < PLAYER_NUM) { + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + mNpc_ClearHPMail(&animal->hp_mail[player_no], 1); + animal++; + } + } +} + +extern int mNpc_ReceiveHPMail(Mail_c* mail) { + static u8 password[mMpswd_PASSWORD_DATA_LEN]; + + Animal_c* animal = Save_Get(animals); + AnmPersonalID_c anm_id; + int res = FALSE; + int private_idx = mPr_GetPrivateIdx(&mail->header.sender.personalID); + + if (mail->header.sender.type == mMl_NAME_TYPE_PLAYER && private_idx >= 0 && private_idx < PLAYER_NUM) { + int is_password = mMpswd_restore_code(mail->content.body, password); + + if (is_password == TRUE && mMl_get_npcinfo_from_mail_name(&anm_id, &mail->header.recipient) == TRUE) { + int npc_idx = mNpc_SearchAnimalinfo(animal, anm_id.npc_id, ANIMAL_NUM_MAX); + + if (npc_idx != -1) { + animal = &animal[npc_idx]; + + if (animal != NULL) { + bcopy(password, animal->hp_mail[private_idx].password, mMpswd_PASSWORD_DATA_LEN); + lbRTC_TimeCopy(&animal->hp_mail[private_idx].receive_time, Common_GetPointer(time.rtc_time)); + res = TRUE; + } + } + } + } + + return res; +} + +static int mNpc_SendHPMailNum_famicom(PersonalID_c* pid, AnmPersonalID_c* anm_id, mMpswd_password_c* password) { + static u8 name_str[mIN_ITEM_NAME_LEN]; + + Mail_c* hp_mail = &l_npc_hp_mail; + int mail_no; + mActor_name_t present = EMPTY_NO; + int res = FALSE; + + if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE) { + mMl_clear_mail(hp_mail); + + if ( + password->hit_rate_index == 1 && // hit rate index must be 1 + mMpswd_password_zuru_check(password) == FALSE && // checksum is valid + mMpswd_check_present(password) == TRUE && // present is valid + mMpswd_check_name(password) == TRUE // player & town name match + ) { + mail_no = 0x24A + anm_id->looks; // valid password + present = password->item; + } + else { + mail_no = 0x250 + anm_id->looks; // invalid password + } + + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_GetNpcWorldNameAnm(name_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR1, name_str, ANIMAL_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR2, password->str1, PLAYER_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR3, password->str0, PLAYER_NAME_LEN); + + if (present != EMPTY_NO) { + mNpc_ClearBufSpace1(name_str, mIN_ITEM_NAME_LEN); + mIN_copy_name_str(name_str, present); + mHandbill_Set_free_str(mHandbill_FREE_STR4, name_str, mIN_ITEM_NAME_LEN); + } + + /* Why is free string 6 the same as free string 1? */ + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_GetNpcWorldNameAnm(name_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR6, name_str, ANIMAL_NAME_LEN); + + mNpc_LoadMailDataCommon2(hp_mail, pid, anm_id, present, mNpc_GetPaperType(), mail_no); + res = mPO_receipt_proc(hp_mail, mPO_SENDTYPE_MAIL); + } + + return res; +} + +static int mNpc_SendHPMailNum_popular(PersonalID_c* pid, AnmPersonalID_c* anm_id, mMpswd_password_c* password) { + static u8 name_str[mIN_ITEM_NAME_LEN]; + static u8 submenu_name_str[mIN_ITEM_NAME_LEN]; + + Mail_c* hp_mail = &l_npc_hp_mail; + mActor_name_t npc_id; + int mail_no; + mActor_name_t present; + u8 valid; + u8 special; + mActor_name_t target_npc_id; + int npc_idx; + int looks; + int res; + + present = EMPTY_NO; + res = FALSE; + valid = FALSE; + special = FALSE; + + if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE) { + mMl_clear_mail(hp_mail); + + if ( + mMpswd_password_zuru_check(password) == FALSE && // checksum is valid + mMpswd_check_npc_code(password) == TRUE && // valid npc code + mMpswd_check_present(password) == TRUE && // present is valid + mMpswd_check_name(password) == TRUE // player & town name match + ) { + /* Valid password */ + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_ClearBufSpace1(submenu_name_str, ANIMAL_NAME_LEN); + + if (password->npc_type == mMpswd_NPCTYPE_NORMAL) { + mActor_name_t npc_id = anm_id->npc_id; + + npc_idx = npc_id & 0xFFF; + target_npc_id = NPC_START | password->npc_code; + looks = mNpc_GetLooks(target_npc_id); + + if (npc_idx == password->npc_code) { + mail_no = 0x256 + looks; + } + else if (mNpc_CheckIslandAnimalTableNo(target_npc_id) == TRUE) { + mail_no = 0x262 + looks; + } + else { + looks = mNpc_GetLooks(target_npc_id); // re-calling the function? + mail_no = 0x25C + looks; + } + + mNpc_GetNpcWorldNameTableNo(name_str, NPC_START | password->npc_code); + mNpc_GetNpcWorldNameTableNo(submenu_name_str, NPC_START | password->npc_code); + valid = TRUE; + } + else { + /* Special NPC */ + mail_no = 0x268 + password->npc_code; + npc_id = mMpswd_get_sp_npc_num(password->npc_code); + mNpc_GetActorWorldName(name_str, npc_id); + mNpc_GetActorWorldName(submenu_name_str, npc_id); + + /* Leftover from JP, replace ちん (-san) honorific with spaces */ + if (submenu_name_str[4] == 0x0A && submenu_name_str[5] == 0xC3) { + submenu_name_str[4] = CHAR_SPACE; + submenu_name_str[5] = CHAR_SPACE; + } + + valid = TRUE; + special = TRUE; + } + + + if (password->item != RSV_NO) { + present = password->item; + } + } + else { + /* Invalid password */ + mail_no = 0x288 + anm_id->looks; + } + + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR1, name_str, ANIMAL_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR2, password->str1, PLAYER_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR3, password->str0, PLAYER_NAME_LEN); + + if (present != EMPTY_NO) { + mNpc_ClearBufSpace1(name_str, mIN_ITEM_NAME_LEN); + mIN_copy_name_str(name_str, present); + mHandbill_Set_free_str(mHandbill_FREE_STR4, name_str, mIN_ITEM_NAME_LEN); + } + + /* Why is free string 6 the same as free string 1? */ + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_GetNpcWorldNameAnm(name_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR6, name_str, ANIMAL_NAME_LEN); + + mNpc_LoadMailDataCommon2(hp_mail, pid, anm_id, present, mNpc_GetPaperType(), mail_no); + + if (valid == TRUE) { + // Overwrite the sender name with the name of the password's embedded villager name + bcopy(submenu_name_str, hp_mail->header.sender.personalID.player_name, PLAYER_NAME_LEN); + bcopy(pid->player_name, hp_mail->header.recipient.personalID.player_name, PLAYER_NAME_LEN); + + if (special == TRUE) { + hp_mail->content.mail_type = mMl_TYPE_SPNPC_PASSWORD; + } + } + + res = mPO_receipt_proc(hp_mail, mPO_SENDTYPE_MAIL); + } + + return res; +} + +static int mNpc_CheckHit_Rate(u8 hitrate, int n) { + u8 perfect_bit = 0xFF; + int res = FALSE; + int i; + + for (i = 0; i < n; i++) { + perfect_bit &= ~(1 << i); + } + + if ((hitrate & perfect_bit) == 0) { + res = TRUE; + } + + return res; +} + +static int mNpc_GetHit(u8 hitrate) { + static u8 prob_table[] = { 80, 60, 30, 0, 100 }; + + int res = FALSE; + + if (hitrate < ARRAY_COUNT(prob_table)) { + u8 hit = RANDOM(100); + + if (hit < prob_table[hitrate]) { + res = TRUE; + } + } + + return res; +} + +static void mNPC_get_gobi_str_from_name(u8* str, mActor_name_t npc_id) { + if (str != NULL && ITEM_NAME_GET_TYPE(npc_id) == NAME_TYPE_NPC) { + int idx = npc_id & 0xFFF; + + /* @BUG - devs used && instead of || */ + #ifndef BUGFIXES + if (idx < 0 && idx >= NPC_NUM) { + #else + if (idx < 0 || idx >= NPC_NUM) { + #endif + idx = 0; + } + + mString_Load_StringFromRom(str, ANIMAL_CATCHPHRASE_LEN, npc_def_list[idx].catchphrase_str_idx); + } +} + +static int mNpc_GetHit_cardE(u8 hitrate) { + static int prob_table[] = { 80, 60, 40, 20 }; + + int res = FALSE; + + if (hitrate < ARRAY_COUNT(prob_table)) { + u8 hit = RANDOM(100); + + if (hit < prob_table[hitrate]) { + res = TRUE; + } + } + + return res; +} + +static int mNpc_SendHPMailNum_cardE(PersonalID_c* pid, AnmPersonalID_c* anm_id, mMpswd_password_c* password) { + static u8 submenu_name_str[mIN_ITEM_NAME_LEN]; + static u8 name_str[mIN_ITEM_NAME_LEN]; + static u8 gobi_str[ANIMAL_CATCHPHRASE_LEN]; + + Mail_c* hp_mail = &l_npc_hp_mail; + int mail_no; + mActor_name_t present = EMPTY_NO; + int res = FALSE; + u8 valid = FALSE; + u8 special = FALSE; + int famicom_hit; + + if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE) { + mMl_clear_mail(hp_mail); + + if ( + mMpswd_password_zuru_check(password) == FALSE && // checksum is valid + mMpswd_check_present(password) == TRUE && // present is valid + mMpswd_check_npc_code(password) == TRUE // valid npc code + ) { + /* Valid password */ + famicom_hit = FALSE; + mNpc_ClearBufSpace1(submenu_name_str, ANIMAL_NAME_LEN); + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_ClearBufSpace1(gobi_str, ANIMAL_CATCHPHRASE_LEN); + + if (password->npc_type == mMpswd_NPCTYPE_NORMAL) { + mActor_name_t target_npc_id = NPC_START | password->npc_code; + int villager_idx = mNpc_SearchAnimalinfo(Save_Get(animals), target_npc_id, ANIMAL_NUM_MAX); + int islander_idx = mNpc_SearchAnimalinfo(&Save_Get(island).animal, target_npc_id, 1); + + if ( + (villager_idx != -1 || islander_idx != -1) && + ( + mNpc_GetAnimalMemoryIdx(&Common_Get(now_private)->player_ID, Save_Get(animals[villager_idx]).memories, ANIMAL_MEMORY_NUM) != -1 || + mNpc_GetAnimalMemoryIdx(&Common_Get(now_private)->player_ID, Save_Get(island).animal.memories, ANIMAL_MEMORY_NUM) != -1 + ) + ) { + int looks = mNpc_GetLooks(target_npc_id); + + if (mNpc_GetHit_cardE(password->hit_rate_index)) { + famicom_hit = TRUE; + mail_no = mNpc_CheckIslandAnimalTableNo(target_npc_id) == TRUE ? 0x3CA + looks : 0x3C4 + looks; + mNpc_GetNpcWorldNameTableNo(name_str, NPC_START | password->npc_code); + mNpc_GetNpcWorldNameTableNo(submenu_name_str, NPC_START | password->npc_code); + valid = TRUE; + } + } + + if (famicom_hit == FALSE) { + mail_no = 0x2B2 + password->npc_code; + mNpc_GetNpcWorldNameTableNo(name_str, NPC_START | password->npc_code); + mNpc_GetNpcWorldNameTableNo(submenu_name_str, NPC_START | password->npc_code); + valid = TRUE; + } + + mNPC_get_gobi_str_from_name(gobi_str, target_npc_id); + mHandbill_Set_free_str(mHandbill_FREE_STR5, gobi_str, ANIMAL_CATCHPHRASE_LEN); + } + else { + /* Special NPC */ + mActor_name_t npc_id; + + mail_no = 0x39E + password->npc_code; + npc_id = mMpswd_get_sp_npc_num(password->npc_code); + mNpc_GetActorWorldName(name_str, npc_id); + mNpc_GetActorWorldName(submenu_name_str, npc_id); + + /* Leftover from JP, replace ちん (-san) honorific with spaces */ + if (submenu_name_str[4] == 0x0A && submenu_name_str[5] == 0xC3) { + submenu_name_str[4] = CHAR_SPACE; + submenu_name_str[5] = CHAR_SPACE; + } + + valid = TRUE; + special = TRUE; + } + + if (famicom_hit == FALSE && mMpswd_check_present(password) == TRUE) { + mActor_name_t item = password->item; + + if (item == RSV_NO) { + present = EMPTY_NO; + } + else { + present = item; + } + } + else if (famicom_hit == TRUE) { + int famicom_idx = RANDOM_F(8); + + present = mRmTp_FtrIdx2FtrItemNo(pswd_famicom_list[famicom_idx], mRmTp_DIRECT_SOUTH); + } + } + else { + /* Invalid password */ + mail_no = 0x3BE + anm_id->looks; + } + + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR1, name_str, ANIMAL_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR2, password->str1, PLAYER_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR3, password->str0, PLAYER_NAME_LEN); + + if (present != EMPTY_NO) { + mNpc_ClearBufSpace1(name_str, mIN_ITEM_NAME_LEN); + mIN_copy_name_str(name_str, present); + mHandbill_Set_free_str(mHandbill_FREE_STR4, name_str, mIN_ITEM_NAME_LEN); + } + + /* Why is free string 6 the same as free string 1? */ + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_GetNpcWorldNameAnm(name_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR6, name_str, ANIMAL_NAME_LEN); + + mNpc_LoadMailDataCommon2(hp_mail, pid, anm_id, present, mNpc_GetPaperType(), mail_no); + + if (valid == TRUE) { + // Overwrite the sender name with the name of the password's embedded villager name + bcopy(submenu_name_str, hp_mail->header.sender.personalID.player_name, PLAYER_NAME_LEN); + bcopy(pid->player_name, hp_mail->header.recipient.personalID.player_name, PLAYER_NAME_LEN); + + if (special == TRUE) { + hp_mail->content.mail_type = mMl_TYPE_SPNPC_PASSWORD; + } + } + + res = mPO_receipt_proc(hp_mail, mPO_SENDTYPE_MAIL); + } + + return res; +} + +static int mNpc_SendHPMailNum_NG(PersonalID_c* pid, AnmPersonalID_c* anm_id) { + static u8 name_str[mIN_ITEM_NAME_LEN]; + + Mail_c* hp_mail = &l_npc_hp_mail; + int mail_no; + int res = FALSE; + + if (anm_id != NULL && mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE) { + mMl_clear_mail(hp_mail); + mail_no = 0x3BE + anm_id->looks; + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_GetNpcWorldNameAnm(name_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR6, name_str, ANIMAL_NAME_LEN); + mNpc_LoadMailDataCommon2(hp_mail, pid, anm_id, EMPTY_NO, mNpc_GetPaperType(), mail_no); + res = mPO_receipt_proc(hp_mail, mPO_SENDTYPE_MAIL); + } + + return res; +} + +static int mNpc_SendHPMailNum_cardE_mini_user_password_common(PersonalID_c* pid, AnmPersonalID_c* anm_id) { + static u8 name_str[ANIMAL_NAME_LEN]; + + Mail_c* hp_mail = &l_npc_hp_mail; + int looks = anm_id->looks; + int res = FALSE; + + if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE && looks < mNpc_LOOKS_NUM) { + mMl_clear_mail(hp_mail); + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_GetNpcWorldNameAnm(name_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR6, name_str, ANIMAL_NAME_LEN); + mNpc_LoadMailDataCommon2(hp_mail, pid, anm_id, EMPTY_NO, mNpc_GetPaperType(), 0x3D0 + looks); + res = mPO_receipt_proc(hp_mail, mPO_SENDTYPE_MAIL); + } + + return res; +} + +static int mNpc_SendHPMailNum_user_password(PersonalID_c* pid, AnmPersonalID_c* anm_id, mMpswd_password_c* password) { + if ( + mMpswd_password_zuru_check(password) == FALSE && + mMpswd_check_present(password) == TRUE && + mMpswd_check_name(password) == TRUE + ) { + return mNpc_SendHPMailNum_cardE_mini_user_password_common(pid, anm_id); + } + + return mNpc_SendHPMailNum_NG(pid, anm_id); +} + +static int mNpc_SendHPMailNum_cardE_mini(PersonalID_c* pid, AnmPersonalID_c* anm_id, mMpswd_password_c* password) { + if (mMpswd_password_zuru_check(password) == FALSE) { + return mNpc_SendHPMailNum_cardE_mini_user_password_common(pid, anm_id); + } + + return mNpc_SendHPMailNum_NG(pid, anm_id); +} + +static int mNpc_SendHPMailNum_magazine(PersonalID_c* pid, AnmPersonalID_c* anm_id, mMpswd_password_c* password) { + static u8 name_str[mIN_ITEM_NAME_LEN]; + + Mail_c* hp_mail = &l_npc_hp_mail; + int mail_no; + mActor_name_t present = EMPTY_NO; + int res = FALSE; + + if (mPO_get_keep_mail_sum() < mPO_MAIL_STORAGE_SIZE) { + mMl_clear_mail(hp_mail); + + if ( + mNpc_CheckHit_Rate(password->hit_rate_index, 3) == TRUE && // hitrate index is valid + mMpswd_password_zuru_check(password) == FALSE && // checksum is valid + mMpswd_check_present(password) == TRUE // present is valid + ) { + /* Valid password */ + if (mNpc_GetHit(password->hit_rate_index) == TRUE) { + /* Player "won" */ + mail_no = 0x2A0 + anm_id->looks; + + if (password->item != RSV_NO) { + present = password->item; + } + } + else { + /* Player did not "win" */ + mail_no = 0x2A6 + anm_id->looks; + } + } + else { + /* Invalid password */ + mail_no = 0x2AC + anm_id->looks; + } + + mHandbill_Set_free_str(mHandbill_FREE_STR0, pid->player_name, PLAYER_NAME_LEN); + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_GetNpcWorldNameAnm(name_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR1, name_str, ANIMAL_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR2, password->str1, PLAYER_NAME_LEN); + mHandbill_Set_free_str(mHandbill_FREE_STR3, password->str0, PLAYER_NAME_LEN); + + if (present != EMPTY_NO) { + mNpc_ClearBufSpace1(name_str, mIN_ITEM_NAME_LEN); + mIN_copy_name_str(name_str, present); + mHandbill_Set_free_str(mHandbill_FREE_STR4, name_str, mIN_ITEM_NAME_LEN); + } + + /* Why is free string 6 the same as free string 1? */ + mNpc_ClearBufSpace1(name_str, ANIMAL_NAME_LEN); + mNpc_GetNpcWorldNameAnm(name_str, anm_id); + mHandbill_Set_free_str(mHandbill_FREE_STR6, name_str, ANIMAL_NAME_LEN); + + mNpc_LoadMailDataCommon2(hp_mail, pid, anm_id, present, mNpc_GetPaperType(), mail_no); + res = mPO_receipt_proc(hp_mail, mPO_SENDTYPE_MAIL); + } + + return res; +} + +typedef int (*mNpc_HPMAIL_SEND_PROC)(PersonalID_c*, AnmPersonalID_c*, mMpswd_password_c*); + +static int mNpc_SendHPMail_analysis(PersonalID_c* pid, AnmPersonalID_c* anm_id, AnmHPMail_c* hp_mail) { + static mNpc_HPMAIL_SEND_PROC send_proc[mMpswd_CODETYPE_NUM] = { + &mNpc_SendHPMailNum_famicom, + &mNpc_SendHPMailNum_popular, + &mNpc_SendHPMailNum_cardE, + &mNpc_SendHPMailNum_magazine, + &mNpc_SendHPMailNum_user_password, + &mNpc_SendHPMailNum_cardE_mini + }; + + mMpswd_password_c password; + + mMpswd_password(hp_mail->password, &password); + + if (password.type < mMpswd_CODETYPE_NUM) { + return (*send_proc[password.type])(pid, anm_id, &password); + } + + return mNpc_SendHPMailNum_NG(pid, anm_id); +} + +extern void mNpc_SendHPMail() { + Private_c* priv = Save_Get(private); + Private_c* priv_p; + Animal_c* animal = Save_Get(animals); + AnmHPMail_c* hp_mail; + lbRTC_time_c* rtc_time = Common_GetPointer(time.rtc_time); + int i; + int j; + + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + if (mNpc_CheckFreeAnimalPersonalID(&animal->id) == FALSE) { + hp_mail = animal->hp_mail; + priv_p = priv; + + for (j = 0; j < ANIMAL_HP_MAIL_NUM; j++) { + if (hp_mail->receive_time.year != 0 && hp_mail->receive_time.day != 0 && mPr_NullCheckPersonalID(&priv_p->player_ID) == FALSE) { + int days; + + if (lbRTC_IsOverTime(&hp_mail->receive_time, rtc_time) == lbRTC_OVER) { + days = lbRTC_GetIntervalDays(&hp_mail->receive_time, rtc_time); + } + else { + days = lbRTC_GetIntervalDays(rtc_time, &hp_mail->receive_time); + } + + if (days >= 1 && mNpc_SendHPMail_analysis(&priv_p->player_ID, &animal->id, hp_mail) == TRUE) { + mNpc_ClearHPMail(hp_mail, 1); + } + } + + priv_p++; + hp_mail++; + } + } + + animal++; + } +} + +extern void mNpc_PrintRemoveInfo(gfxprint_t* gfxprint) { + Animal_c* remove_anm = NULL; + Animal_c* in_anm = mNpc_GetInAnimalP(); + u8 remove_idx = Save_Get(remove_animal_idx); + + gfxprint_color(gfxprint, 200, 150, 50, 255); + gfxprint_locate8x8(gfxprint, 3, 10); + + if (remove_idx < ANIMAL_NUM_MAX) { + remove_anm = Save_GetPointer(animals[remove_idx]); + } + + if (remove_anm != NULL) { + if (in_anm != NULL) { + gfxprint_printf(gfxprint, "%4x %3d %3d", remove_idx, remove_anm->id.name_id, in_anm->id.name_id); + } + else { + gfxprint_printf(gfxprint, "%4x %3d ---", remove_idx, remove_anm->id.name_id); + } + } + else if (in_anm != NULL) { + gfxprint_printf(gfxprint, "%4x --- %3d", remove_idx, in_anm->id.name_id); + } + else { + gfxprint_printf(gfxprint, "%4x --- ---", remove_idx); + } +} + +static int l_mnpc_display = 0; + +typedef struct npc_add_debug_s { + int add_switch; + u16 memory; + u8 count; + u8 add_bit; + u8 edit_bit; + int mtype; + int disp_add; +} mNpc_Add_Debug_c; + +static mNpc_Add_Debug_c l_mnpc_addd; + +/* @unused mNpc_set_switch 1.2.5 size: 0x14 */ +/* @unused mNpc_clear_addd 1.2.5 size: 0x44 */ +/* @unused mNpc_set_addd 1.2.5 size: 0x0C */ + +/* @unused @fabricated */ +extern void mNpc_set_addd_bit(int bit) { + l_mnpc_addd.add_bit |= (1 << bit); +} + +/* @unused */ +extern void mNpc_set_addd_edit_bit(int bit) { + l_mnpc_addd.edit_bit |= (1 << bit); +} + +/* @unused */ +extern void mNpc_set_addd_edit_info(int mtype, int disp_add) { + l_mnpc_addd.mtype = mtype; + l_mnpc_addd.disp_add = disp_add; +} + +/* @unused mNpc_set_addd_memory 1.2.5 size: 0x34 */ +/* @unused mNpc_set_addd_count 1.2.5 size: 0x10 */ + +static void mNpc_PrintFriendship_fdebug_1(gfxprint_t* gfxprint) { + int add_switch = l_mnpc_addd.add_switch; + int i; + + gfxprint_color(gfxprint, 250, 100, 170, 255); + gfxprint_locate8x8(gfxprint, 3, 18); + + if (add_switch == 0) { + gfxprint_printf(gfxprint, "----------------------- %d", l_mnpc_addd.add_switch); + } + else if (add_switch == 1) { + gfxprint_printf(gfxprint, "*********************** %d", l_mnpc_addd.add_switch); + } + else if (add_switch == 2) { + gfxprint_printf(gfxprint, ">>>>>>>>>>>>>>>>>>>>>>> %d", l_mnpc_addd.add_switch); + } + else { + gfxprint_printf(gfxprint, "%2d ", l_mnpc_addd.count); + for (i = 0; i < ANIMAL_NUM_MAX; i++) { + gfxprint_printf(gfxprint, "%1d", (l_mnpc_addd.memory >> i) & 1); + } + + gfxprint_locate8x8(gfxprint, 3, 19); + gfxprint_printf(gfxprint, "add bit "); + for (i = 0; i < 8; i++) { + gfxprint_printf(gfxprint, "%1d", (l_mnpc_addd.add_bit >> i) & 1); + } + + gfxprint_printf(gfxprint, " edit bit "); + for (i = 0; i < 8; i++) { + gfxprint_printf(gfxprint, "%1d", (l_mnpc_addd.edit_bit >> i) & 1); + } + + gfxprint_locate8x8(gfxprint, 3, 20); + gfxprint_printf(gfxprint, "mtype %d disp_add 0x%x", l_mnpc_addd.mtype, l_mnpc_addd.disp_add); + } +} + +static int l_mnpc_animal_idx = -1; + +extern void mNpc_SetTalkAnimalIdx_fdebug(AnmPersonalID_c* anm_id) { + if (anm_id != NULL) { + l_mnpc_animal_idx = mNpc_SearchAnimalPersonalID(anm_id); + } +} + +static void mNpc_PrintFriendship_fdebug_0(gfxprint_t* gfxprint) { + Anmmem_c* memory; + Private_c* priv; + int x = 3; + int y = 19; + int i; + + if (l_mnpc_animal_idx >= 0 && l_mnpc_animal_idx < ANIMAL_NUM_MAX) { + memory = Save_Get(animals[l_mnpc_animal_idx]).memories; + + if (memory != NULL) { + gfxprint_color(gfxprint, 250, 100, 170, 255); + gfxprint_locate8x8(gfxprint, 3, 18); + priv = Common_Get(now_private); + + if (priv != NULL) { + gfxprint_printf(gfxprint, "%4x ", priv->player_ID.player_id); + } + else { + gfxprint_printf(gfxprint, "**** "); + } + + gfxprint_color(gfxprint, 50, 150, 50, 255); + + for (i = 0; i < ANIMAL_MEMORY_NUM; i++) { + if (i == 4) { + y++; + x = 3; + } + + gfxprint_locate8x8(gfxprint, x, y); + if (mNpc_CheckFreeAnimalMemory(memory) == FALSE) { + gfxprint_printf(gfxprint, "%4x %3d ", memory->memory_player_id.player_id, memory->friendship); + } + else { + gfxprint_printf(gfxprint, "**** *** "); + } + + memory++; + x += 9; + } + } + } +} + +typedef void (*mNpc_PRINT_FRIENDSHIP_PROC)(gfxprint_t*); + +extern void mNpc_PrintFriendship_fdebug(gfxprint_t* gfxprint) { + static mNpc_PRINT_FRIENDSHIP_PROC print_proc[2] = { + &mNpc_PrintFriendship_fdebug_0, + &mNpc_PrintFriendship_fdebug_1 + }; + + (*print_proc[l_mnpc_display & 1])(gfxprint); +} diff --git a/rel/m_private.c b/rel/m_private.c index f67c2eb5..c478a8a3 100644 --- a/rel/m_private.c +++ b/rel/m_private.c @@ -1222,13 +1222,13 @@ static void mPr_GetForeingerAnimalMail(Mail_c* mail, Private_c* priv, mPr_animal u8 header[40]; u8 footer[48]; int ofs; - int looks; + u8 looks; int header_back_start; looks = mNpc_GetLooks(anm_mem->npc_id); ofs = RANDOM(3); mail_no = 0xFC; - mail_no += ofs + (u8)looks * 3; + mail_no += ofs + looks * 3; mHandbill_Set_free_str(0, priv->player_ID.player_name, PLAYER_NAME_LEN); // player's name mNpc_LoadNpcNameString(npc_name, anm_mem->npc_id); mHandbill_Set_free_str(1, npc_name, ANIMAL_NAME_LEN); // animal's name diff --git a/rel/m_quest.c b/rel/m_quest.c index 8d984648..790a020c 100644 --- a/rel/m_quest.c +++ b/rel/m_quest.c @@ -689,7 +689,7 @@ static u8 mQst_GetMailRank(u8* body, mActor_name_t present) { rank = mQst_LETTER_RANK_1; } - if (score_bonus >= 1) { + if (score_bonus >= mNpc_LETTER_RANK_OK) { rank += mQst_LETTER_SCORE_BONUS; } diff --git a/rel/m_trademark.c b/rel/m_trademark.c index 34e6aa05..3b527c05 100644 --- a/rel/m_trademark.c +++ b/rel/m_trademark.c @@ -112,7 +112,13 @@ static mNpc_demo_npc_c demo_npc_list[] = { DEMO_NPC(SAMSON, 5, 2, 12, 4), DEMO_NPC(JANE, 5, 2, 9, 11), DEMO_NPC(TYBALT, 5, 4, 11, 4), + /* @BUG - mNpc_SetAnimalTitleDemo uses ANIMAL_NUM_MAX (15) but only 14 are set */ + #ifndef BUGFIXES DEMO_NPC(CUBE, 5, 5, 5, 11) + #else + DEMO_NPC(CUBE, 5, 5, 5, 11), + { EMPTY_NO, 0, 0, 0, 0 } + #endif }; static int demo_npc_num = sizeof(demo_npc_list) / sizeof(mNpc_demo_npc_c);