diff --git a/config/rel_slices.yml b/config/rel_slices.yml index e2259ad3..0d2e2ee4 100644 --- a/config/rel_slices.yml +++ b/config/rel_slices.yml @@ -547,6 +547,10 @@ ac_groundhog_control.c: ac_ev_dokutu.c: .text: [0x8051DFF4, 0x8051E228] .data: [0x806A05C8, 0x806A0608] +ac_ev_gypsy.c: + .text: [0x80520D78, 0x80521414] + .rodata: [0x80649228, 0x80649238] + .data: [0x806A0938, 0x806A09F8] ac_ev_soncho.c: .text: [0x8052400C, 0x8052475C] .rodata: [0x80649270, 0x80649278] diff --git a/include/ac_ev_gypsy.h b/include/ac_ev_gypsy.h index f6375790..1c90abb2 100644 --- a/include/ac_ev_gypsy.h +++ b/include/ac_ev_gypsy.h @@ -3,11 +3,28 @@ #include "types.h" #include "m_actor.h" +#include "ac_npc.h" #ifdef __cplusplus extern "C" { #endif +#define aEGPS_FORTUNE_PRICE 50 + +typedef struct ev_gypsy_actor_s EV_GYPSY_ACTOR; + +typedef void (*aEGPS_TALK_INIT_PROC)(EV_GYPSY_ACTOR*, GAME_PLAY*); +typedef void (*aEGPS_TALK_PROC)(EV_GYPSY_ACTOR*, GAME_PLAY*); +typedef void (*aEGPS_TALK_SETUP_PROC)(EV_GYPSY_ACTOR*, GAME_PLAY*, int); + +struct ev_gypsy_actor_s { + NPC_ACTOR npc_class; + int talk_action; + aEGPS_TALK_PROC talk_proc; + aEGPS_TALK_SETUP_PROC setup_talk_proc; + int fortune_given; +}; + extern ACTOR_PROFILE Ev_Gypsy_Profile; #ifdef __cplusplus diff --git a/src/ac_ev_gypsy.c b/src/ac_ev_gypsy.c new file mode 100644 index 00000000..a99ff63c --- /dev/null +++ b/src/ac_ev_gypsy.c @@ -0,0 +1,93 @@ +#include "ac_ev_gypsy.h" + +#include "m_choice.h" +#include "m_common_data.h" +#include "m_item_name.h" +#include "m_msg.h" +#include "m_play.h" +#include "m_string.h" + +enum { + aEGPS_ACTION_TALK_END_WAIT, + aEGPS_ACTION_CALL_IN, + aEGPS_ACTION_DECIDE_RESULT, + aEGPS_ACTION_DECIDE_RESULT2, + + aEGPS_ACTION_NUM +}; + +enum { + aEGPS_STR_TYPE_ADJECTIVE, + aEGPS_STR_TYPE_NOUN, + aEGPS_STR_TYPE_VERB, + aEGPS_STR_TYPE_PLACE, + + aEGPS_STR_TYPE_NUM +}; + +static void aEGPS_actor_ct(ACTOR*, GAME*); +static void aEGPS_actor_dt(ACTOR*, GAME*); +static void aEGPS_actor_move(ACTOR*, GAME*); +static void aEGPS_actor_draw(ACTOR*, GAME*); +static void aEGPS_actor_save(ACTOR*, GAME*); +static void aEGPS_actor_init(ACTOR*, GAME*); + +ACTOR_PROFILE Ev_Gypsy_Profile = { + mAc_PROFILE_EV_GYPSY, + ACTOR_PART_NPC, + ACTOR_STATE_NONE, + SP_NPC_GYPSY, + ACTOR_OBJ_BANK_KEEP, + sizeof(EV_GYPSY_ACTOR), + &aEGPS_actor_ct, + &aEGPS_actor_dt, + &aEGPS_actor_init, + mActor_NONE_PROC1, + &aEGPS_actor_save +}; + +static void aEGPS_talk_request(ACTOR* actorx, GAME* game); +static int aEGPS_talk_init(ACTOR* actorx, GAME* game); +static int aEGPS_talk_end_chk(ACTOR* actorx, GAME* game); +static void aEGPS_setupAction(EV_GYPSY_ACTOR* gypsy, GAME_PLAY* play, int action); + +static void aEGPS_actor_ct(ACTOR* actorx, GAME* game) { + static aNPC_ct_data_c ct_data = { + &aEGPS_actor_move, + &aEGPS_actor_draw, + 2, + &aEGPS_talk_request, + &aEGPS_talk_init, + &aEGPS_talk_end_chk, + 0 + }; + + EV_GYPSY_ACTOR* gypsy = (EV_GYPSY_ACTOR*)actorx; + + if ((*Common_Get(clip).npc_clip->birth_check_proc)(actorx, game) == TRUE) { + (*Common_Get(clip).npc_clip->ct_proc)(actorx, game, &ct_data); + actorx->talk_distance = 80.0f; + actorx->status_data.weight = 255; + actorx->world.position.x += 20.0f; + gypsy->setup_talk_proc = &aEGPS_setupAction; + } +} + +static void aEGPS_actor_save(ACTOR* actorx, GAME* game) { + (*Common_Get(clip).npc_clip->save_proc)(actorx, game); +} + +static void aEGPS_actor_dt(ACTOR* actorx, GAME* game) { + (*Common_Get(clip).npc_clip->dt_proc)(actorx, game); + mEv_actor_dying_message(mEv_EVENT_GYPSY, actorx); +} + +static void aEGPS_actor_init(ACTOR* actorx, GAME* game) { + (*Common_Get(clip).npc_clip->init_proc)(actorx, game); +} + +static void aEGPS_actor_draw(ACTOR* actorx, GAME* game) { + (*Common_Get(clip).npc_clip->draw_proc)(actorx, game); +} + +#include "../src/ac_ev_gypsy_move.c_inc" diff --git a/src/ac_ev_gypsy_move.c_inc b/src/ac_ev_gypsy_move.c_inc new file mode 100644 index 00000000..dd355970 --- /dev/null +++ b/src/ac_ev_gypsy_move.c_inc @@ -0,0 +1,197 @@ +static void aEGPS_set_string(int type) { + static int string_num[aEGPS_STR_TYPE_NUM] = { 0x1A4, 0x1C4, 0x184, 0x164 }; + u8 str[mIN_ITEM_NAME_LEN]; + int str_no = RANDOM(32) + string_num[type]; + + mString_Load_StringFromRom(str, sizeof(str), str_no); + mMsg_Set_item_str(mMsg_Get_base_window_p(), type, str, sizeof(str)); +} + +static void aEGPS_call_in(EV_GYPSY_ACTOR* gypsy, GAME_PLAY* play) { + static int msg_no[] = { + 0x971, /* 'No' choice */ + 0x973, /* 'Yes' choice */ + 0x972 /* 'Yes' choice, but not enough Bells */ + }; + + static int next_act_idx[] = { + aEGPS_ACTION_TALK_END_WAIT, + aEGPS_ACTION_DECIDE_RESULT, + aEGPS_ACTION_TALK_END_WAIT + }; + + mMsg_Window_c* msg_p = mMsg_Get_base_window_p(); + + if (mMsg_Check_MainNormalContinue(msg_p) == TRUE) { + int type = -1; + + switch (mChoice_Get_ChoseNum(mChoice_Get_base_window_p())) { + case mChoice_CHOICE0: /* Yes */ + if (Common_Get(now_private)->inventory.wallet >= aEGPS_FORTUNE_PRICE) { + type = 1; // yes + } + else { + type = 2; // not enough bells + } + break; + case mChoice_CHOICE1: /* No */ + type = 0; // no + break; + } + + if (type != -1) { + mMsg_Set_continue_msg_num(msg_p, msg_no[type]); + (*gypsy->setup_talk_proc)(gypsy, play, next_act_idx[type]); + } + } +} + +static void aEGPS_decide_result(EV_GYPSY_ACTOR* gypsy, GAME_PLAY* play) { + static int msg_no[] = { + 0x974, + 0x975, + 0x976, + 0x977, + 0x978, + 0x979 + }; + + static int msg_no2[] = { + 0x97C, + 0x97D, + 0x97E, + 0x97F, + 0x980 + }; + + static u8 destiny[] = { + mPr_DESTINY_NORMAL, + mPr_DESTINY_POPULAR, + mPr_DESTINY_UNPOPULAR, + mPr_DESTINY_BAD_LUCK, + mPr_DESTINY_MONEY_LUCK, + mPr_DESTINY_GOODS_LUCK + }; + + int demo_order = mDemo_Get_OrderValue(mDemo_ORDER_NPC0, 9); + int selected; + + if (demo_order != 0) { + mMsg_Window_c* msg_p = mMsg_Get_base_window_p(); + + mDemo_Set_OrderValue(mDemo_ORDER_NPC0, 9, 0); + if (gypsy->talk_action == aEGPS_ACTION_DECIDE_RESULT) { + selected = RANDOM(10); + + if (selected < 4) { + selected = 4; /* 50% chance of 'normal' luck, 10% chance for other types */ + } + + selected -= 4; + Common_Get(now_private)->destiny.type = destiny[selected]; + mMsg_Set_continue_msg_num(msg_p, msg_no[selected]); + } + else { + /* aEGPS_ACTION_DECIDE_RESULT2 */ + selected = Common_Get(now_private)->destiny.type - 1; + mMsg_Set_continue_msg_num(msg_p, msg_no2[selected]); + } + + (*gypsy->setup_talk_proc)(gypsy, play, aEGPS_ACTION_TALK_END_WAIT); + } +} + +static void aEGPS_decide_result_init(EV_GYPSY_ACTOR* gypsy, GAME_PLAY* play) { + u32 wallet = Common_Get(now_private)->inventory.wallet; + wallet -= aEGPS_FORTUNE_PRICE; + Common_Get(now_private)->inventory.wallet = wallet; + aEGPS_set_string(aEGPS_STR_TYPE_ADJECTIVE); + aEGPS_set_string(aEGPS_STR_TYPE_NOUN); + aEGPS_set_string(aEGPS_STR_TYPE_VERB); + aEGPS_set_string(aEGPS_STR_TYPE_PLACE); + gypsy->fortune_given = TRUE; +} + +static void aEGPS_init_proc(EV_GYPSY_ACTOR* gypsy, GAME_PLAY* play, int action) { + static aEGPS_TALK_INIT_PROC init_proc[aEGPS_ACTION_NUM] = { + (aEGPS_TALK_INIT_PROC)&none_proc1, + (aEGPS_TALK_INIT_PROC)&none_proc1, + &aEGPS_decide_result_init, + (aEGPS_TALK_INIT_PROC)&none_proc1 + }; + + (*init_proc[action])(gypsy, play); +} + +static void aEGPS_setupAction(EV_GYPSY_ACTOR* gypsy, GAME_PLAY* play, int action) { + static aEGPS_TALK_PROC process[aEGPS_ACTION_NUM] = { + (aEGPS_TALK_PROC)&none_proc1, + &aEGPS_call_in, + &aEGPS_decide_result, + &aEGPS_decide_result + }; + + gypsy->talk_action = action; + gypsy->talk_proc = process[action]; + aEGPS_init_proc(gypsy, play, action); +} + +static void aEGPS_set_talk_info(ACTOR* actorx) { + EV_GYPSY_ACTOR* gypsy = (EV_GYPSY_ACTOR*)actorx; + int msg_num; + + if (gypsy->fortune_given == TRUE) { + msg_num = 0x982; // Fortune given msg but scene not reloaded? + } + else if (Common_Get(now_private)->destiny.type == mPr_DESTINY_NORMAL) { + msg_num = 0x970; // Should fortune be given? + } + else { + msg_num = 0x97B; // Fortune given msg after scene was reloaded? + } + + mDemo_Set_msg_num(msg_num); +} + +static void aEGPS_talk_request(ACTOR* actorx, GAME* game) { + mDemo_Request(mDemo_TYPE_TALK, actorx, &aEGPS_set_talk_info); +} + +static int aEGPS_talk_init(ACTOR* actorx, GAME* game) { + EV_GYPSY_ACTOR* gypsy = (EV_GYPSY_ACTOR*)actorx; + GAME_PLAY* play = (GAME_PLAY*)game; + int next_act_idx; + + if (Common_Get(now_private)->destiny.type == mPr_DESTINY_NORMAL) { + next_act_idx = aEGPS_ACTION_CALL_IN; + } + else { + next_act_idx = aEGPS_ACTION_DECIDE_RESULT2; + } + + (*gypsy->setup_talk_proc)(gypsy, play, next_act_idx); + mDemo_Set_ListenAble(); + return TRUE; +} + +static int aEGPS_talk_end_chk(ACTOR* actorx, GAME* game) { + EV_GYPSY_ACTOR* gypsy = (EV_GYPSY_ACTOR*)actorx; + GAME_PLAY* play = (GAME_PLAY*)game; + int res = FALSE; + + (*gypsy->talk_proc)(gypsy, play); + if (mDemo_Check(mDemo_TYPE_TALK, actorx) == FALSE) { + res = TRUE; + } + + return res; +} + +static void aEGPS_actor_move(ACTOR* actorx, GAME* game) { + (*Common_Get(clip).npc_clip->move_proc)(actorx, game); + + if (mDemo_Check(mDemo_TYPE_TALK, actorx) == FALSE) { + chase_angle(&actorx->shape_info.rotation.y, 0, DEG2SHORT_ANGLE(5.625f)); + actorx->world.angle.y = actorx->shape_info.rotation.y; + } +}