diff --git a/SQL/Migrations/add_favours.sql b/SQL/Migrations/add_favours.sql new file mode 100644 index 000000000..31b8e7174 --- /dev/null +++ b/SQL/Migrations/add_favours.sql @@ -0,0 +1,7 @@ +CREATE TABLE `pfiles_favours` ( + `idnum` mediumint(5) unsigned default '0', + `number` smallint(3) unsigned default '0', + `questnum` mediumint(3) unsigned default '0', + `completed` boolean default FALSE, + KEY(`idnum`) +); \ No newline at end of file diff --git a/src/act.informative.cpp b/src/act.informative.cpp index f21b4a38a..a72728182 100644 --- a/src/act.informative.cpp +++ b/src/act.informative.cpp @@ -73,6 +73,7 @@ extern int find_sight(struct char_data *ch); extern int belongs_to(struct char_data *ch, struct obj_data *obj); extern int calculate_vehicle_entry_load(struct veh_data *veh); extern unsigned int get_johnson_overall_max_rep(struct char_data *johnson); +extern unsigned int get_johnson_lowest_max_rep(struct char_data *johnson); extern const char *get_crap_count_string(int crap_count, const char *default_color = "^n", bool screenreader = FALSE); extern void display_gamba_ledger_leaderboard(struct char_data *ch); const char *convert_and_write_string_to_file(const char *str, const char *path); @@ -598,6 +599,46 @@ void list_veh_to_char(struct veh_data * list, struct char_data * ch) } } +void brief_list_veh_to_char(struct veh_data * list, struct char_data * ch) +{ + int bikes, cars, trucks, drones; + struct veh_data *i; + for (i = list; i; i = i->next_veh) { + if (ch->in_veh != i && ch->char_specials.rigging != i) { + if (i->owner && GET_IDNUM(ch) == i->owner) { + show_veh_to_char(i, ch); + } else { + switch (i->type) { + case VEH_BIKE: + bikes++; + case VEH_CAR: + cars++; + case VEH_TRUCK: + trucks++; + case VEH_DRONE: + drones++; + default: + break; + } + } + } + + send_to_char(ch, "There are also %d bikes, %d cars, %d trucks and %d drones here.", bikes, cars, trucks, drones); + + if (i == i->next_veh) { + char errbuf[1000]; + snprintf(errbuf, sizeof(errbuf), "SYSERR: Infinite loop in list_veh_to_char for %s (%ld) at %s (%ld). Breaking the list.", + GET_VEH_NAME(i), + i->veh_number, + GET_ROOM_NAME(get_veh_in_room(i)), + GET_ROOM_VNUM(get_veh_in_room(i))); + i->next_veh = NULL; + mudlog(errbuf, ch, LOG_SYSLOG, TRUE); + break; + } + } +} + bool items_are_visually_similar(struct obj_data *first, struct obj_data *second) { if (!first || !second) { mudlog("SYSERR: Received null object to items_are_visually_similar.", NULL, LOG_SYSLOG, TRUE); @@ -1432,11 +1473,17 @@ void list_one_char(struct char_data * i, struct char_data * ch) } if (MOB_HAS_SPEC(i, johnson)) { unsigned int max_rep = get_johnson_overall_max_rep(i); + unsigned int lowest_rep = get_johnson_lowest_max_rep(i); if (max_rep >= GET_REP(ch) || max_rep >= 10000) { snprintf(ENDOF(buf), sizeof(buf) - strlen(buf), "^y...%s%s might have a job for you.%s^n\r\n", HSSH(i), already_printed ? " also" : "", SHOULD_SEE_TIPS(ch) ? " See ^YHELP JOB^y for instructions." : ""); + } else if (GET_TKE(ch) >= NEWBIE_KARMA_THRESHOLD && lowest_rep < GET_REP(ch)) { + snprintf(ENDOF(buf), sizeof(buf) - strlen(buf), "^y...%s%s might need to call in a favor.^n\r\n", + HSSH(i), + already_printed ? " also" : "", + SHOULD_SEE_TIPS(ch) ? " See ^YHELP FAVOR^y for instructions." : ""); } else { snprintf(ENDOF(buf), sizeof(buf) - strlen(buf), "^y...%s%s only has work for less-experienced 'runners.^n\r\n", HSSH(i), @@ -2093,7 +2140,7 @@ void look_in_veh(struct char_data * ch) list_obj_to_char(veh->in_room->contents, ch, SHOW_MODE_ON_GROUND, FALSE, TRUE); list_char_to_char(veh->in_room->people, ch); CCHAR = "^y"; - list_veh_to_char(veh->in_room->vehicles, ch); + brief_list_veh_to_char(veh->in_room->vehicles, ch); if (PLR_FLAGGED(ch, PLR_REMOTE)) ch->in_room = was_in; else @@ -2436,7 +2483,7 @@ void look_at_room(struct char_data * ch, int ignore_brief, int is_quicklook) list_obj_to_char(ch->in_room->contents, ch, SHOW_MODE_ON_GROUND, FALSE, TRUE); list_char_to_char(ch->in_room->people, ch); CCHAR = "^y"; - list_veh_to_char(ch->in_room->vehicles, ch); + brief_list_veh_to_char(ch->in_room->vehicles, ch); } void peek_into_adjacent(struct char_data * ch, int dir) diff --git a/src/awake.hpp b/src/awake.hpp index bec8ab751..987108674 100644 --- a/src/awake.hpp +++ b/src/awake.hpp @@ -366,7 +366,8 @@ enum { #define PLR_ADDITIONAL_SCRUTINY 56 #define PLR_RECEIVED_GHOUL_INDEX_DELTA 57 /* Player has had their ghoul index cost reset */ #define PLR_PAID_FOR_WHOTITLE 58 -#define PLR_MAX 59 +#define PLR_DOING_FAVOUR 59 +#define PLR_MAX 60 // Adding something here? Add it to constants.cpp's player_bits too. @@ -3154,7 +3155,8 @@ enum { #define DIRTY_BIT_ECHOES 8 #define DIRTY_BIT_QUESTS 9 #define DIRTY_BIT_BULLETPANTS 10 -#define NUM_DIRTY_BITS 11 +#define DIRTY_BIT_FAVOURS 11 +#define NUM_DIRTY_BITS 12 #define SMARTLINK_II_MODIFIER 3 diff --git a/src/config.hpp b/src/config.hpp index ca401fa9d..2d7076e6b 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -29,6 +29,8 @@ extern const char *CHARACTER_DELETED_NAME_FOR_SQL; #define KARMA_GAIN_MULTIPLIER 2.0 #define NUYEN_GAIN_MULTIPLIER 2.0 #define GROUP_QUEST_REWARD_MULTIPLIER 1.5 +#define FAVOURS_KARMA_MULTIPLIER 0.0 +#define FAVOURS_NUYEN_MULTIPLIER 0.0 // How well should markets regenerate over time? (currently every 2 mins) #define MAX_PAYDATA_MARKET_INCREASE_PER_TICK 250 diff --git a/src/constants.cpp b/src/constants.cpp index a412a9f5c..0d3b92693 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -620,6 +620,7 @@ const char *player_bits[] = "STAFF_SCRUTINY", "GHOUL_IDX_DONE", "PAID_FOR_WHOTITLE", + "DOING_FAVOUR", "\n" }; diff --git a/src/interpreter.cpp b/src/interpreter.cpp index 0f8ef7848..1f0c1a7ab 100644 --- a/src/interpreter.cpp +++ b/src/interpreter.cpp @@ -684,6 +684,8 @@ struct command_info cmd_info[] = // TODO: Make this a rigging and matrix command too { "factions" , POS_MORTALLYW, do_factions, LVL_PRESIDENT, 0, ALLOWS_IDLE_REWARD }, + { "favors" , POS_DEAD , do_recap , 0, 0, BLOCKS_IDLE_REWARD }, + { "favours" , POS_DEAD , do_recap , 0, 0, BLOCKS_IDLE_REWARD }, { "force" , POS_SLEEPING, do_force , LVL_EXECUTIVE, 0, BLOCKS_IDLE_REWARD }, { "forceget" , POS_SLEEPING, do_forceget , LVL_PRESIDENT, 0, BLOCKS_IDLE_REWARD }, { "forceput" , POS_SLEEPING, do_forceput , LVL_PRESIDENT, 0, BLOCKS_IDLE_REWARD }, @@ -1333,6 +1335,8 @@ struct command_info mtx_htr_info[] = { "look", 0, do_not_here, 0, 0, BLOCKS_IDLE_REWARD }, // shadows 'l' so people reflexively looking don't get DC'd from hitch session { "logoff", 0, do_logoff, 0, 0, BLOCKS_IDLE_REWARD }, { "emote", 0, do_echo, 0, SCMD_EMOTE , BLOCKS_IDLE_REWARD }, + { "favors", 0, do_recap, 0, 0, BLOCKS_IDLE_REWARD }, + { "favours", 0, do_recap, 0, 0, BLOCKS_IDLE_REWARD }, { ":", 0, do_echo, 0, SCMD_EMOTE , BLOCKS_IDLE_REWARD }, { "exit", 0, do_logoff, 0, 0, BLOCKS_IDLE_REWARD }, { "help", 0, do_help, 0, 0, BLOCKS_IDLE_REWARD }, @@ -1397,6 +1401,8 @@ struct command_info mtx_info[] = { "emote", 0, do_echo, 0, SCMD_EMOTE , BLOCKS_IDLE_REWARD }, { ":", 0, do_echo, 0, SCMD_EMOTE , BLOCKS_IDLE_REWARD }, { "exit", 0, do_logoff, 0, 0, BLOCKS_IDLE_REWARD }, + { "favors", 0, do_recap, 0, 0, BLOCKS_IDLE_REWARD }, + { "favours", 0, do_recap, 0, 0, BLOCKS_IDLE_REWARD }, { "hangup", 0, do_comcall, 0, SCMD_HANGUP, BLOCKS_IDLE_REWARD }, { "help", 0, do_help, 0, 0, BLOCKS_IDLE_REWARD }, { "ht", 0, do_gen_comm , 0, SCMD_HIREDTALK, BLOCKS_IDLE_REWARD }, @@ -1498,6 +1504,8 @@ struct command_info rig_info[] = { "enter", 0, do_enter, 0, 0, BLOCKS_IDLE_REWARD }, { "examine", 0, do_look_while_rigging, 0, 0, BLOCKS_IDLE_REWARD }, { "exits", 0, do_exits, 0, 0, BLOCKS_IDLE_REWARD }, + { "favors", 0, do_recap, 0, 0, BLOCKS_IDLE_REWARD }, + { "favours", 0, do_recap, 0, 0, BLOCKS_IDLE_REWARD }, { "flyto", 0, do_flyto, 0, 0, BLOCKS_IDLE_REWARD }, { "get", 0, do_get, 0, 0, BLOCKS_IDLE_REWARD }, { "gridguide", 0, do_gridguide, 0, 0, BLOCKS_IDLE_REWARD }, diff --git a/src/newdb.cpp b/src/newdb.cpp index f6f1121a1..cc2551744 100644 --- a/src/newdb.cpp +++ b/src/newdb.cpp @@ -65,6 +65,7 @@ void auto_repair_obj(struct obj_data *obj, idnum_t owner); void save_adept_powers_to_db(struct char_data *player); void save_spells_to_db(struct char_data *player); void save_quests_to_db(struct char_data *player); +void save_favours_to_db(struct char_data * player); void save_inventory_to_db(struct char_data *player); void save_worn_equipment_to_db(struct char_data *player); void save_metamagic_to_db(struct char_data *player); @@ -1535,6 +1536,9 @@ static bool save_char(char_data *player, DBIndex::vnum_t loadroom, bool fromCopy /* Save data for quests the player has run. */ SAVE_IF_DIRTY_BIT_SET(GET_QUEST_DIRTY_BIT, save_quests_to_db); + /* Save data for favours the player has run. */ + SAVE_IF_DIRTY_BIT_SET(GET_FAVOUR_DIRTY_BIT, save_favours_to_db); + /* Wipe out their memory, then re-write it. */ SAVE_IF_DIRTY_BIT_SET(GET_MEMORY_DIRTY_BIT, save_pc_memory_to_db); @@ -2825,6 +2829,37 @@ void save_quests_to_db(struct char_data *player) { } } +void save_favours_to_db(struct char_data *player) { + PERF_PROF_SCOPE(pr_, __func__); + int i, q; + + snprintf(buf, sizeof(buf), "DELETE FROM pfiles_favours WHERE idnum=%ld", GET_IDNUM(player)); + mysql_wrapper(mysql, buf); + strlcpy(buf, "INSERT INTO pfiles_favours (idnum, number, questnum, completed) VALUES (", sizeof(buf)); + for (i = 0, q = 0; i <= QUEST_TIMER - 1; i++) { + if (GET_LFAVOUR(player ,i)) { + bool found = FALSE; + // Check to see if it's in the CQUEST list. If it is, store it as completed. + for (int c_idx = 0; c_idx <= QUEST_TIMER - 1; c_idx++) { + if (GET_LFAVOUR(player, i) == GET_CFAVOUR(player, c_idx)) { + found = TRUE; + break; + } + } + + snprintf(ENDOF(buf), sizeof(buf) - strlen(buf), "%s%ld, %d, %ld, %s", + q ? "), (" : "", + GET_IDNUM(player), i, GET_LFAVOUR(player, i), found ? "TRUE" : "FALSE"); + q = 1; + } + } + if (q) { + strcat(buf, ");"); + mysql_wrapper(mysql, buf); + } +} + + void save_elementals_to_db(struct char_data *player) { PERF_PROF_SCOPE(pr_, __func__); if (GET_TRADITION(player) == TRAD_HERMETIC) { diff --git a/src/quest.cpp b/src/quest.cpp index 12558dda1..4075c2a6e 100644 --- a/src/quest.cpp +++ b/src/quest.cpp @@ -156,8 +156,13 @@ void end_quest(struct char_data *ch, bool succeeded); void initialize_quest_for_ch(struct char_data *ch, int quest_rnum, struct char_data *johnson) { // Assign them the quest. - GET_QUEST(ch) = quest_rnum; - GET_QUEST_STARTED(ch) = time(0); + if (PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { + GET_QUEST(ch) = quest_rnum; + GET_QUEST_STARTED(ch) = time(0); + } else { + GET_QUEST(ch) = quest_rnum; + GET_QUEST_STARTED(ch) = time(0); + } // Create their memory structures. ch->player_specials->obj_complete = new sh_int[quest_table[GET_QUEST(ch)].num_objs]; @@ -179,6 +184,8 @@ void initialize_quest_for_ch(struct char_data *ch, int quest_rnum, struct char_d } bool attempt_quit_job(struct char_data *ch, struct char_data *johnson) { + bool favour = PLR_FLAGGED(ch, PLR_DOING_FAVOUR); + // Precondition: I cannot be talking right now. if (GET_SPARE1(johnson) == 0) { if (!memory(johnson, ch)) { @@ -221,6 +228,9 @@ bool attempt_quit_job(struct char_data *ch, struct char_data *johnson) { end_quest(ch, FALSE); forget(johnson, ch); + if (favour) { + PLR_FLAGS(ch).RemoveBit(PLR_DOING_FAVOUR); + } return TRUE; } @@ -907,22 +917,38 @@ bool check_quest_kill(struct char_data *ch, struct char_data *victim) void end_quest(struct char_data *ch, bool succeeded) { - if (IS_NPC(ch) || !GET_QUEST(ch)) + bool favour = PLR_FLAGGED(ch, PLR_DOING_FAVOUR); + if (IS_NPC(ch) || !GET_QUEST(ch)) { return; + } extract_quest_targets(GET_IDNUM_EVEN_IF_PROJECTING(ch)); + // We mark the quest as completed here because if you fail... //well you failed. Better luck next time chummer. - for (int i = QUEST_TIMER - 1; i > 0; i--) { - GET_LQUEST(ch, i) = GET_LQUEST(ch, i - 1); + if (favour) { + for (int i = QUEST_TIMER - 1; i > 0; i--) { + GET_LFAVOUR(ch, i) = GET_LFAVOUR(ch, i - 1); + + if (succeeded) + GET_CFAVOUR(ch, i) = GET_CFAVOUR(ch, i - 1); + } + GET_LFAVOUR(ch, 0) = quest_table[GET_QUEST(ch)].vnum; if (succeeded) - GET_CQUEST(ch, i) = GET_CQUEST(ch, i - 1); - } + GET_CFAVOUR(ch, 0) = quest_table[GET_QUEST(ch)].vnum; + } else { + for (int i = QUEST_TIMER - 1; i > 0; i--) { + GET_LQUEST(ch, i) = GET_LQUEST(ch, i - 1); - GET_LQUEST(ch, 0) = quest_table[GET_QUEST(ch)].vnum; - if (succeeded) - GET_CQUEST(ch, 0) = quest_table[GET_QUEST(ch)].vnum; + if (succeeded) + GET_CQUEST(ch, i) = GET_CQUEST(ch, i - 1); + } + + GET_LQUEST(ch, 0) = quest_table[GET_QUEST(ch)].vnum; + if (succeeded) + GET_CQUEST(ch, 0) = quest_table[GET_QUEST(ch)].vnum; + } GET_QUEST(ch) = 0; GET_QUEST_STARTED(ch) = 0; @@ -932,7 +958,11 @@ void end_quest(struct char_data *ch, bool succeeded) ch->player_specials->mob_complete = NULL; ch->player_specials->obj_complete = NULL; - GET_QUEST_DIRTY_BIT(ch) = TRUE; + if (favour) { + GET_FAVOUR_DIRTY_BIT(ch) = TRUE; + } else { + GET_QUEST_DIRTY_BIT(ch) = TRUE; + } } bool rep_too_high(struct char_data *ch, int num) @@ -957,6 +987,17 @@ bool rep_too_low(struct char_data *ch, int num) return FALSE; } +bool rep_cap_too_high(struct char_data *ch, int num) +{ + if (num < 0 || num > top_of_questt) + return TRUE; + + if (quest_table[num].max_rep >= 10000 || GET_REP(ch) < quest_table[num].max_rep) + return TRUE; + + return FALSE; +} + bool would_be_rewarded_for_turnin(struct char_data *ch) { int nuyen = 0, karma = 0; @@ -1046,7 +1087,7 @@ void award_follower_payout(struct char_data *follower, int karma, int nuyen, str GET_IDNUM(questor)); } -void reward(struct char_data *ch, struct char_data *johnson) +void reward(struct char_data *ch, struct char_data *johnson, bool favour = FALSE) { if (vnum_from_non_approved_zone(quest_table[GET_QUEST(ch)].vnum)) { #ifdef IS_BUILDPORT @@ -1194,6 +1235,11 @@ void reward(struct char_data *ch, struct char_data *johnson) } } + if (favour) { + karma = karma * FAVOURS_KARMA_MULTIPLIER; + nuyen = nuyen * FAVOURS_KARMA_MULTIPLIER; + } + gain_nuyen(ch, nuyen, NUYEN_INCOME_AUTORUNS); int gained = gain_karma(ch, karma, TRUE, FALSE, TRUE); act("$n gives some nuyen to $N.", TRUE, johnson, 0, ch, TO_NOTVICT); @@ -1201,13 +1247,23 @@ void reward(struct char_data *ch, struct char_data *johnson) snprintf(buf, sizeof(buf), "$n gives you %d nuyen.", nuyen); act(buf, FALSE, johnson, 0, ch, TO_VICT); send_to_char(ch, "You gain %.2f karma.\r\n", ((float) gained / 100)); + if (favour) { + send_to_char(ch, "^L[OOC: Karma and nuyen rewards for this run were suppressed as it was a favor.]^n\r\n"); + } - mudlog_vfprintf(ch, LOG_GRIDLOG, "%s gains %0.2fk and %dn from job %ld. Elapsed time v2 %0.2f seconds.", - GET_CHAR_NAME(ch), - (float) gained * 0.01, - nuyen, - GET_QUEST(ch), - difftime(time(0), GET_QUEST_STARTED(ch))); + if (favour) { + mudlog_vfprintf(ch, LOG_GRIDLOG, "%s completed job %ld as a favor. Elapsed time v2 %0.2f seconds.", + GET_CHAR_NAME(ch), + GET_QUEST(ch), + difftime(time(0), GET_QUEST_STARTED(ch))); + } else { + mudlog_vfprintf(ch, LOG_GRIDLOG, "%s gains %0.2fk and %dn from job %ld. Elapsed time v2 %0.2f seconds.", + GET_CHAR_NAME(ch), + (float) gained * 0.01, + nuyen, + GET_QUEST(ch), + difftime(time(0), GET_QUEST_STARTED(ch))); + } end_quest(ch, TRUE); } @@ -1221,7 +1277,7 @@ bool compareRep(const quest_entry &a, const quest_entry &b) //done, and outgrown, sorts it by reputation and returns the lowest //rep one first. It returns 0 if no more quests are available or -1 if //the johnson is broken. -int new_quest(struct char_data *mob, struct char_data *ch) +int new_quest(struct char_data *mob, struct char_data *ch, bool favour = FALSE) { int num = 0; bool allow_disconnected = vnum_from_non_approved_zone(GET_MOB_VNUM(mob)); @@ -1267,13 +1323,20 @@ int new_quest(struct char_data *mob, struct char_data *ch) #endif } - if (rep_too_high(ch, quest_idx)) { + if (rep_too_high(ch, quest_idx) && !favour) { if (access_level(ch, LVL_BUILDER)) { send_to_char(ch, "[Skipping quest %ld: You exceed rep cap of %d.]\r\n", quest_table[quest_idx].vnum, quest_table[quest_idx].max_rep); } continue; } + if (favour && rep_cap_too_high(ch, quest_idx)) { + if (access_level(ch, LVL_BUILDER)) { + send_to_char(ch, "[Skipping quest %ld as a favor: You are under the rep cap of %d.]\r\n", quest_table[quest_idx].vnum, quest_table[quest_idx].max_rep); + } + continue; + } + if (ch->master && GET_QUEST(ch->master) == quest_idx) { if (access_level(ch, LVL_BUILDER)) { send_to_char(ch, "[Skipping quest %ld: Your group leader %s already has it.]\r\n", quest_table[quest_idx].vnum, GET_CHAR_NAME(ch->master)); @@ -1296,9 +1359,16 @@ int new_quest(struct char_data *mob, struct char_data *ch) bool found = FALSE; for (int q = QUEST_TIMER - 1; q >= 0; q--) { - if (GET_LQUEST(ch, q) == quest_table[quest_idx].vnum) { - found = TRUE; - break; + if (favour) { + if (GET_LFAVOUR(ch, q) == quest_table[quest_idx].vnum) { + found = TRUE; + break; + } + } else { + if (GET_LQUEST(ch, q) == quest_table[quest_idx].vnum) { + found = TRUE; + break; + } } } if (found) { @@ -1460,7 +1530,7 @@ void handle_info(struct char_data *johnson, int num, struct char_data *target) SPECIAL(johnson) { struct char_data *johnson = (struct char_data *) me, *temp = NULL; - int i, obj_complete = 0, mob_complete = 0, new_q, cached_new_q = -2, comm = CMD_JOB_NONE; + int i, obj_complete = 0, mob_complete = 0, new_q, cached_new_q = -2, cached_new_f = -2, comm = CMD_JOB_NONE; if (!IS_NPC(johnson)) return FALSE; @@ -1488,6 +1558,7 @@ SPECIAL(johnson) skip_spaces(&argument); + bool favour = FALSE; bool need_to_speak = FALSE; bool need_to_act = FALSE; bool is_sayto = CMD_IS("sayto") || CMD_IS("\"") || CMD_IS("ask") || CMD_IS("whisper"); @@ -1524,6 +1595,8 @@ SPECIAL(johnson) str_str(argument, "run") || str_str(argument, "shadowrun") || str_str(argument, "job") || str_str(argument, "help")) comm = CMD_JOB_START; + else if (str_str(argument, "favor") || str_str(argument, "favour")) + comm = CMD_JOB_FAVOUR; else if (str_str(argument, "yes") || str_str(argument, "accept") || str_str(argument, "yeah") || str_str(argument, "sure") || str_str(argument, "okay")) comm = CMD_JOB_YES; @@ -1534,8 +1607,13 @@ SPECIAL(johnson) GET_LQUEST(ch, i) = 0; GET_CQUEST(ch, i) = 0; } + for (int i = QUEST_TIMER - 1; i >= 0; i--) { + GET_LFAVOUR(ch, i) = 0; + GET_CFAVOUR(ch, i) = 0; + } send_to_char("OK, your quest history has been cleared.\r\n", ch); GET_QUEST_DIRTY_BIT(ch) = TRUE; + GET_FAVOUR_DIRTY_BIT(ch) = TRUE; return FALSE; } else { //snprintf(buf, sizeof(buf), "INFO: No Johnson keywords found in %s's speech: '%s'.", GET_CHAR_NAME(ch), argument); @@ -1557,6 +1635,13 @@ SPECIAL(johnson) } else { return FALSE; } + } else if (CMD_IS("favor") || CMD_IS("favors") || CMD_IS("favour") || CMD_IS("favours")) { + if (!GET_QUEST(ch)) { + do_say(ch, "Are you calling in any favors?", 0, 0); + comm = CMD_JOB_FAVOUR; + } else { + return FALSE; + } } else if (CMD_IS("complete")) { if (GET_QUEST(ch)) { do_say(ch, "I've finished the job.", 0, 0); @@ -1605,6 +1690,7 @@ SPECIAL(johnson) return attempt_quit_job(ch, johnson); case CMD_JOB_DONE: // Precondition: I cannot be talking right now. + favour = PLR_FLAGGED(ch, PLR_DOING_FAVOUR); if (GET_SPARE1(johnson) == 0) { if (!memory(johnson, ch)) { do_say(johnson, "Hold on, I'm talking to someone else right now.", 0, 0); @@ -1634,6 +1720,9 @@ SPECIAL(johnson) do_say(johnson, "You fragged it up, and you still want to get paid?", 0, 0); end_quest(ch, FALSE); forget(johnson, ch); + if (favour) { + PLR_FLAGS(ch).RemoveBit(PLR_DOING_FAVOUR); + } return TRUE; } } @@ -1671,7 +1760,10 @@ SPECIAL(johnson) mudlog(buf, ch, LOG_SYSLOG, TRUE); do_say(johnson, "Well done.", 0, 0); } - reward(ch, johnson); + reward(ch, johnson, quest_table[GET_QUEST(ch)].max_rep < GET_REP(ch)); + if (PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { + PLR_FLAGS(ch).RemoveBit(PLR_DOING_FAVOUR); + } forget(johnson, ch); if (GET_QUEST(ch) == QST_MAGE_INTRO && GET_TRADITION(ch) != TRAD_MUNDANE) @@ -1680,25 +1772,39 @@ SPECIAL(johnson) do_say(johnson, "You haven't completed any of your objectives yet.", 0, 0); return TRUE; - case CMD_JOB_START: { + case CMD_JOB_START: + case CMD_JOB_FAVOUR: { + // set the favour bool and flag, if we said job and the favour flag is still dangling for any reason, drop it + favour = (comm == CMD_JOB_FAVOUR); + if (favour) { + PLR_FLAGS(ch).SetBit(PLR_DOING_FAVOUR); + } else + if (PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { + PLR_FLAGS(ch).RemoveBit(PLR_DOING_FAVOUR); + } // Reject high-rep characters. unsigned int johnson_max_rep = get_johnson_overall_max_rep(johnson); - if (johnson_max_rep < 10000 && johnson_max_rep < GET_REP(ch)) { + if (johnson_max_rep < 10000 && johnson_max_rep < GET_REP(ch) && !favour) { do_say(johnson, "My jobs aren't high-profile enough for someone with your rep!", 0, 0); send_to_char(ch, "[OOC: This Johnson caps out at %d reputation, so you won't get any further work from them.]\r\n", johnson_max_rep); GET_SPARE1(johnson) = -1; - if (memory(johnson, ch)) + //We need to ensure that trying to start a job when you are already doing a favour won't get you blanked + if (memory(johnson, ch) && !PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { forget(johnson, ch); + } return TRUE; } - new_q = new_quest(johnson, ch); + new_q = new_quest(johnson, ch, favour); //Clever hack to safely save us a call to new_quest() that compiler will be ok with. //If we have a cached quest use that and reset the cache integer back to -2 when //it is consumed. - cached_new_q = new_q; + if (!favour) + cached_new_q = new_q; + else + cached_new_f = new_q; //Handle out of quests and broken johnsons. //Calls to new_quest() return 0 when there's no quest left available and @@ -1733,8 +1839,12 @@ SPECIAL(johnson) if (PLR_FLAGGED(ch, PLR_KILLER) || PLR_FLAGGED(ch, PLR_BLACKLIST)) { do_say(johnson, "Word on the street is you can't be trusted.", 0, 0); GET_SPARE1(johnson) = -1; - if (memory(johnson, ch)) + if (memory(johnson, ch)) { + if (favour) { + PLR_FLAGS(ch).RemoveBit(PLR_DOING_FAVOUR); + } forget(johnson, ch); + } } // Reject low-rep characters. @@ -1784,8 +1894,9 @@ SPECIAL(johnson) } GET_SPARE1(johnson) = -1; - if (memory(johnson, ch)) + if (memory(johnson, ch)) { forget(johnson, ch); + } return TRUE; } @@ -1804,7 +1915,11 @@ SPECIAL(johnson) else { snprintf(buf, sizeof(buf), "WARNING: Null intro string in quest %ld!", quest_table[new_q].vnum); mudlog(buf, ch, LOG_SYSLOG, TRUE); - do_say(johnson, "I've got a job for you.", 0, 0); + if (favour) { + do_say(johnson, "I'm calling in a favour.", 0, 0); + } else { + do_say(johnson, "I've got a job for you.", 0, 0); + } } do_say(johnson, "Are you interested?", 0, 0); if (!memory(johnson, ch)) @@ -1829,11 +1944,14 @@ SPECIAL(johnson) //Clever hack to safely save us a call to new_quest() that compiler will be ok with. //If we have a cached quest use that and reset the cache integer back to -2 when //it is consumed. - if (cached_new_q != -2) { + if (cached_new_q != -2 && !PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { new_q = cached_new_q; cached_new_q = -2; + } else if (cached_new_f != -2 && PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { + new_q = cached_new_f; + cached_new_f = -2; } else { - new_q = new_quest(johnson, ch); + new_q = new_quest(johnson, ch, PLR_FLAGGED(ch, PLR_DOING_FAVOUR)); } //Handle out of quests and broken johnsons. @@ -1899,11 +2017,14 @@ SPECIAL(johnson) //Clever hack to safely save us a call to new_quest() that compiler will be ok with. //If we have a cached quest use that and reset the cache integer back to -2 when //it is consumed. - if (cached_new_q != -2) { + if (cached_new_q != -2 && !PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { new_q = cached_new_q; cached_new_q = -2; + } else if (cached_new_f != -2 && PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { + new_q = cached_new_f; + cached_new_f = -2; } else { - new_q = new_quest(johnson, ch); + new_q = new_quest(johnson, ch, PLR_FLAGGED(ch, PLR_DOING_FAVOUR)); } //Handle out of quests and broken johnsons. @@ -1924,6 +2045,9 @@ SPECIAL(johnson) GET_SPARE1(johnson) = -1; GET_QUEST(ch) = 0; GET_QUEST_STARTED(ch) = 0; + if (PLR_FLAGGED(ch, PLR_DOING_FAVOUR)) { + PLR_FLAGS(ch).RemoveBit(PLR_DOING_FAVOUR); + } forget(johnson, ch); if (quest_table[new_q].decline_emote && *quest_table[new_q].decline_emote) { // Don't @ me about this, it's the only way to reliably display a newline in this context. @@ -3996,6 +4120,26 @@ unsigned int get_johnson_overall_min_rep(struct char_data *johnson) { return min_rep; } +unsigned int get_johnson_lowest_max_rep(struct char_data *johnson) { + unsigned int max_rep = UINT_MAX; + + bool johnson_is_from_disconnected_zone = vnum_from_non_approved_zone(GET_MOB_VNUM(johnson)); +#ifdef IS_BUILDPORT + johnson_is_from_disconnected_zone = TRUE; +#endif + + for (int i = 0; i <= top_of_questt; i++) { + if (quest_table[i].johnson == GET_MOB_VNUM(johnson) + && (johnson_is_from_disconnected_zone + || !vnum_from_non_approved_zone(quest_table[i].vnum))) + { + max_rep = MIN(max_rep, quest_table[i].max_rep); + } + } + + return max_rep; +} + // TODO: Have quests able to disable this printout (mystery quests etc) void display_quest_goals_to_ch(struct char_data *ch) { rnum_t johnson_rnum = real_mobile(quest_table[GET_QUEST(ch)].johnson); diff --git a/src/quest.hpp b/src/quest.hpp index 96aab887b..96cf68fe2 100644 --- a/src/quest.hpp +++ b/src/quest.hpp @@ -123,6 +123,7 @@ struct quest_entry { #define CMD_JOB_START 3 #define CMD_JOB_YES 4 #define CMD_JOB_NO 5 +#define CMD_JOB_FAVOUR 6 void load_quest_targets(struct char_data *johnson, struct char_data *ch); void handle_info(struct char_data *johnson, int num, struct char_data *target); diff --git a/src/structs.hpp b/src/structs.hpp index 24c7ccc35..dcac5f0f7 100644 --- a/src/structs.hpp +++ b/src/structs.hpp @@ -735,6 +735,8 @@ struct player_special_data sh_int *mob_complete; long last_quest[QUEST_TIMER]; long completed_quest[QUEST_TIMER]; + long last_favour[QUEST_TIMER]; + long completed_favour[QUEST_TIMER]; ush_int drugs[NUM_DRUGS][NUM_DRUG_PLAYER_SPECIAL_FIELDS]; time_t drug_last_fix[NUM_DRUGS]; ubyte mental_loss; @@ -753,6 +755,8 @@ struct player_special_data { ZERO_OUT_ARRAY(last_quest, QUEST_TIMER); ZERO_OUT_ARRAY(completed_quest, QUEST_TIMER); + ZERO_OUT_ARRAY(last_favour, QUEST_TIMER); + ZERO_OUT_ARRAY(completed_favour, QUEST_TIMER); ZERO_OUT_ARRAY(drug_last_fix, NUM_DRUGS); for (int i = 0; i < NUM_DRUGS; i++) { diff --git a/src/utils.hpp b/src/utils.hpp index 147e06ed9..7c0119397 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -707,6 +707,8 @@ int get_armor_penalty_grade(struct char_data *ch); (ch)->player_specials->quest_started) #define GET_LQUEST(ch, i) ((ch)->player_specials->last_quest[i]) #define GET_CQUEST(ch, i) ((ch)->player_specials->completed_quest[i]) +#define GET_LFAVOUR(ch, i) ((ch)->player_specials->last_favour[i]) +#define GET_CFAVOUR(ch, i) ((ch)->player_specials->completed_favour[i]) #define GET_PLAYER_WHERE_COMMANDS(ch) ((ch)->player_specials->wherelist_checks) #define POOFIN(ch) ((ch)->player.poofin) #define POOFOUT(ch) ((ch)->player.poofout) @@ -777,6 +779,7 @@ int get_armor_penalty_grade(struct char_data *ch); #define GET_MEMORY_DIRTY_BIT(ch) ((ch)->char_specials.dirty_bits[DIRTY_BIT_MEMORY]) #define GET_ALIAS_DIRTY_BIT(ch) ((ch)->char_specials.dirty_bits[DIRTY_BIT_ALIAS]) #define GET_QUEST_DIRTY_BIT(ch) ((ch)->char_specials.dirty_bits[DIRTY_BIT_QUESTS]) +#define GET_FAVOUR_DIRTY_BIT(ch) ((ch)->char_specials.dirty_bits[DIRTY_BIT_FAVOURS]) #define GET_BULLETPANTS_DIRTY_BIT(ch) ((ch)->char_specials.dirty_bits[DIRTY_BIT_BULLETPANTS]) #define GET_CONGREGATION_BONUS(ch) ((ch)->congregation_bonus_pool)