Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/act.informative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1441,6 +1441,10 @@ void list_one_char(struct char_data * i, struct char_data * ch)
snprintf(ENDOF(buf), sizeof(buf) - strlen(buf), "^y...%s%s only has work for less-experienced 'runners.^n\r\n",
HSSH(i),
already_printed ? " also" : "");
if (PRF_FLAGGED(ch, PRF_FAVOURS)) {
snprintf(ENDOF(buf), sizeof(buf) - strlen(buf), "^y...but %s might need to call in a favor.^n\r\n",
HSSH(i));
}
}
already_printed = TRUE;
}
Expand Down Expand Up @@ -8554,4 +8558,4 @@ void write_gsgp_file(int player_count, const char *path) {
fprintf(fl, "}");

fclose(fl);
}
}
7 changes: 6 additions & 1 deletion src/act.other.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1218,7 +1218,9 @@ const char *tog_messages[][2] = {
{"You will now participate in combat again.\r\n",
"OK, you will no longer be able to initiate combat or fight back.\r\n"},
{"You will now see ambiance messages and environmental echoes about traffic.\r\n",
"You will no longer see ambiance messages and environmental echoes about traffic.\r\n"}
"You will no longer see ambiance messages and environmental echoes about traffic.\r\n"},
{"You are no longer interested in doing favors for Johnsons.\r\n",
"You are now willing to do favors for Johnsons.\r\n"}
};

ACMD(do_toggle)
Expand Down Expand Up @@ -1510,6 +1512,9 @@ ACMD(do_toggle)
} else if (is_abbrev(argument, "traffic") || is_abbrev(argument, "notraffic") || is_abbrev(argument, "no traffic")) {
result = PRF_TOG_CHK(ch, PRF_NOTRAFFIC);
mode = 52;
} else if (is_abbrev(argument, "favors")) {
result = PRF_TOG_CHK(ch, PRF_FAVOURS);
mode = 53;
} else {
send_to_char("That is not a valid toggle option.\r\n", ch);
return;
Expand Down
3 changes: 2 additions & 1 deletion src/awake.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,8 @@ enum {
#define PRF_NOFOLLOW 69
#define PRF_SUPPRESS_PROMPT_CHANGE 70
#define PRF_PASSIVE_IN_COMBAT 71
#define PRF_MAX 72
#define PRF_FAVOURS 72
#define PRF_MAX 73

/* log watch */

Expand Down
2 changes: 2 additions & 0 deletions src/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/constants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ struct preference_bit_struct preference_bits_v2[] = {
{ "No Follow" , FALSE, TRUE },
{ "No Prompt Change Message", FALSE, TRUE },
{ "Passive Combat" , FALSE, TRUE },
{ "Johnson Favors" , TRUE , TRUE },
{ "\n" , 0 , 0 }
};

Expand Down Expand Up @@ -823,6 +824,7 @@ const char *preference_bits[] =
"NOFOLLOW",
"!PROMPT_CHANGE_MSG",
"PASSIVE_COMBAT",
"FAVOURS",
MAX_FLAG_MARKER
};

Expand Down
59 changes: 46 additions & 13 deletions src/quest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,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
Expand Down Expand Up @@ -1194,13 +1194,21 @@ 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);
act("You give some nuyen to $N.", TRUE, johnson, 0, ch, TO_CHAR);
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),
Expand All @@ -1221,7 +1229,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));
Expand Down Expand Up @@ -1267,7 +1275,7 @@ 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);
}
Expand Down Expand Up @@ -1460,7 +1468,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;
Expand Down Expand Up @@ -1488,6 +1496,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");
Expand Down Expand Up @@ -1524,6 +1533,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;
Expand Down Expand Up @@ -1600,6 +1611,8 @@ SPECIAL(johnson)
if (need_to_act)
do_action(ch, argument, cmd, 0);

bool doing_favours = PRF_FLAGGED(ch, PRF_FAVOURS);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the interplay between the "favor" keyword and the PRF flag? I'm trying to think of what the behavior should be if someone says they want to do a favor but doesn't have the PRF set, and I'm having a hard time coming up with it.

If there's a special 'favor' keyword, do we need a PRF flag at all, or should we just tweak the J room-display logic to show the favors line if you've outrepped all their jobs?

Copy link
Contributor Author

@scoutyoshimoto scoutyoshimoto Nov 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh, the toggle probably only there at this point because someone suggested it in the issue?

the PRF flag... it mostly comes into play when doing the cached_new_q/cached_new_f check when you say "yes" or "no" that's independent of whether you said "job" or "favour" before, cause I got a fair amount of "what the hell are you talking about"s from broken johnsons trying to give me cached quests in a weird state before I got it working

but it seems to work the same if this bool is false by default, and set to true if you say "favours" though, so that would probably mean the toggle is vestigial (in an earlier version it was more important, but I think I took out enough of where it was being used that it might not actually be necessary any more) it seems like the johnsons need something that persists to decide which cache to use or they break, so I'll say the interplay is "making the cache work"?

the other logic in my head was just to not show it to newbies, but that could be a reputation/TKE check as well, or maybe not even something that needs to be a thing

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Js need a persistent thing on the PC to track that, I'd probably set it as a PLR or AFF flag that doesn't show up for the player, then clear that when they do anything else with the J (accept/decline/etc). A separate toggle feels like the kind of thing that will trip people up in practice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought about it and i came up with some other possible ways to track it, I'll try that and those later on this evening though and see how it goes (and add like, help files etc :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ty for pointing that option out, feels like it works a fair bit better :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i had another look at the conversation that started the idea and realised it should probably track the two job histories entirely separately, so tried that out too :)

the main additions here would be:

  • it uses a PLR flag instead of the toggle
  • it shouldn't say anything about favours in the information line until you're out of newbie status
  • the logs are a little easier to distinguish instead of having to infer from the 0/0 rewards
  • adds a favours table to the db to make the favours history totally separate from the job history
  • short circuits if you try to start a run you haven't outrepped as a favour

getting there!


switch (comm) {
case CMD_JOB_QUIT:
return attempt_quit_job(ch, johnson);
Expand Down Expand Up @@ -1671,7 +1684,7 @@ 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));
forget(johnson, ch);

if (GET_QUEST(ch) == QST_MAGE_INTRO && GET_TRADITION(ch) != TRAD_MUNDANE)
Expand All @@ -1680,11 +1693,22 @@ 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: {
favour = (comm == CMD_JOB_FAVOUR);

if (favour) {
if (PRF_FLAGGED(ch, PRF_FAVOURS)) {
do_say(johnson, "Yeah, I might need to call in a favor...", 0, 0);
} else {
do_say(johnson, "I don't need any favors from you, come back when you want some real work.", 0, 0);
return TRUE;
}
}

// 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);

Expand All @@ -1694,11 +1718,14 @@ SPECIAL(johnson)
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
Expand Down Expand Up @@ -1829,11 +1856,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 && !doing_favours) {
new_q = cached_new_q;
cached_new_q = -2;
} else if (cached_new_f != -2 && doing_favours) {
new_q = cached_new_f;
cached_new_f = -2;
} else {
new_q = new_quest(johnson, ch);
new_q = new_quest(johnson, ch, doing_favours);
}

//Handle out of quests and broken johnsons.
Expand Down Expand Up @@ -1899,11 +1929,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 && !doing_favours) {
new_q = cached_new_q;
cached_new_q = -2;
} else if (cached_new_f != -2 && doing_favours) {
new_q = cached_new_f;
cached_new_f = -2;
} else {
new_q = new_quest(johnson, ch);
new_q = new_quest(johnson, ch, doing_favours);
}

//Handle out of quests and broken johnsons.
Expand Down
1 change: 1 addition & 0 deletions src/quest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading