Listen update

This commit is contained in:
kinther 2025-12-15 20:37:39 -08:00
parent 7a23aba72d
commit 4d9fa5771e
9 changed files with 380 additions and 11 deletions

View file

@ -4623,6 +4623,21 @@ Examples:
See also: BUY, SELL, SHOPS, VALUE
#0
LISTEN
Usage: listen
Focus all of your senses on the subtle sounds around you. While LISTEN is
active you can overhear table talk even when you are seated elsewhere, catch
pieces of whispered conversations, and sometimes pick up what is being said
in a nearby room. Closed doors make the task much harder and only master
perceptionists (81%+) can pierce them.
Issuing LISTEN again stops the effect early. Its duration scales with your
PERCEPTION skill, much like HIDE or SNEAK.
See also: PERCEPTION, SCAN, SAY, TALK, WHISPER
#0
LITTERING
Do not load mobiles or objects in other people's zones. Especially in TBA zone

View file

@ -17,6 +17,8 @@
#include "handler.h"
#include "db.h"
#include "screen.h"
#include "constants.h"
#include "spells.h"
#include "improved-edit.h"
#include "dg_scripts.h"
#include "act.h"
@ -250,6 +252,291 @@ static void wrap_line(const char *src, char *dst, size_t dstsz, int width)
dst[out] = '\0';
}
static void capitalize_leading_you(char *line)
{
if (!line)
return;
if (strn_cmp(line, "you", 3) != 0)
return;
char next = line[3];
if (next && !isspace((unsigned char)next) && next != ',' && next != ':' && next != ';')
return;
line[0] = UPPER(line[0]);
}
#define LISTEN_DC_TABLE 10
#define LISTEN_DC_TABLE_REMOTE 21
#define LISTEN_DC_TABLE_REMOTE_CLOSED 26
#define LISTEN_DC_WHISPER 15
#define LISTEN_DC_ROOM 18
#define LISTEN_DC_CLOSED 23
#define LISTEN_MASTERY_MIN 81
static void compose_history_entry(char *out, size_t outsz,
const char *first_line,
const char *speech)
{
if (!out || outsz == 0)
return;
out[0] = '\0';
if (first_line && *first_line)
strlcpy(out, first_line, outsz);
strlcat(out, "\r\n \"", outsz);
if (speech && *speech)
strlcat(out, speech, outsz);
strlcat(out, "\"", outsz);
}
static bool can_attempt_listen(struct char_data *ch)
{
if (!ch)
return FALSE;
if (!AFF_FLAGGED(ch, AFF_LISTEN))
return FALSE;
if (GET_POS(ch) <= POS_SLEEPING)
return FALSE;
if (!GET_SKILL(ch, SKILL_PERCEPTION))
return FALSE;
return TRUE;
}
static int roll_listen_total(struct char_data *ch)
{
int bonus = GET_ABILITY_MOD(GET_WIS(ch)) +
GET_PROFICIENCY(GET_SKILL(ch, SKILL_PERCEPTION));
int total = rand_number(1, 20) + bonus;
if (FIGHTING(ch))
total -= 4;
return total;
}
static bool perform_listen_check(struct char_data *ch, int difficulty, bool require_mastery)
{
bool success;
if (!can_attempt_listen(ch))
return FALSE;
if (require_mastery && GET_SKILL(ch, SKILL_PERCEPTION) < LISTEN_MASTERY_MIN)
return FALSE;
success = (roll_listen_total(ch) >= difficulty);
gain_skill(ch, "perception", success);
return success;
}
static void deliver_listen_output(struct char_data *listener, const char *first_line, const char *speech)
{
char wrapped_line[MAX_STRING_LENGTH];
char hist_buf[MAX_STRING_LENGTH];
wrap_line(first_line, wrapped_line, sizeof(wrapped_line), 80);
send_to_char(listener, "%s\r\n \"%s\"\r\n", wrapped_line, speech);
compose_history_entry(hist_buf, sizeof(hist_buf), wrapped_line, speech);
add_history(listener, hist_buf, HIST_SAY);
}
static void send_overheard_table(struct char_data *listener,
struct char_data *speaker,
const char *furn_name,
const char *speech,
const struct targeted_phrase *bracket_phrase,
const struct targeted_phrase *paren_phrase)
{
char prefix[MAX_STRING_LENGTH] = "";
char suffix[MAX_STRING_LENGTH] = "";
char first_line[MAX_STRING_LENGTH];
const char *label = (furn_name && *furn_name) ? furn_name : "the table";
if (bracket_phrase)
render_targeted_phrase(speaker, bracket_phrase, FALSE, listener, prefix, sizeof(prefix));
if (paren_phrase)
render_targeted_phrase(speaker, paren_phrase, FALSE, listener, suffix, sizeof(suffix));
strlcpy(first_line, "You overhear ", sizeof(first_line));
if (*prefix) {
char capped[MAX_STRING_LENGTH];
strlcpy(capped, prefix, sizeof(capped));
CAP(capped);
strlcat(first_line, capped, sizeof(first_line));
strlcat(first_line, ", ", sizeof(first_line));
}
strlcat(first_line, get_char_sdesc(speaker), sizeof(first_line));
strlcat(first_line, " at ", sizeof(first_line));
strlcat(first_line, label, sizeof(first_line));
if (*suffix) {
strlcat(first_line, ", ", sizeof(first_line));
strlcat(first_line, suffix, sizeof(first_line));
}
strlcat(first_line, ":", sizeof(first_line));
deliver_listen_output(listener, first_line, speech);
}
static void send_overheard_whisper(struct char_data *listener,
struct char_data *speaker,
struct char_data *vict,
const char *speech,
const struct targeted_phrase *bracket_phrase,
const struct targeted_phrase *paren_phrase)
{
char prefix[MAX_STRING_LENGTH] = "";
char suffix[MAX_STRING_LENGTH] = "";
char first_line[MAX_STRING_LENGTH];
if (bracket_phrase)
render_targeted_phrase(speaker, bracket_phrase, FALSE, listener, prefix, sizeof(prefix));
if (paren_phrase)
render_targeted_phrase(speaker, paren_phrase, FALSE, listener, suffix, sizeof(suffix));
strlcpy(first_line, "You overhear ", sizeof(first_line));
if (*prefix) {
char capped[MAX_STRING_LENGTH];
strlcpy(capped, prefix, sizeof(capped));
CAP(capped);
strlcat(first_line, capped, sizeof(first_line));
strlcat(first_line, ", ", sizeof(first_line));
}
strlcat(first_line, get_char_sdesc(speaker), sizeof(first_line));
strlcat(first_line, " whisper to ", sizeof(first_line));
strlcat(first_line, get_char_sdesc(vict), sizeof(first_line));
if (*suffix) {
strlcat(first_line, ", ", sizeof(first_line));
strlcat(first_line, suffix, sizeof(first_line));
}
strlcat(first_line, ":", sizeof(first_line));
deliver_listen_output(listener, first_line, speech);
}
static void send_overheard_room(struct char_data *listener,
struct char_data *speaker,
const char *context_label,
const char *dir_name,
bool closed_door,
const char *speech,
const struct targeted_phrase *bracket_phrase,
const struct targeted_phrase *paren_phrase)
{
char prefix[MAX_STRING_LENGTH] = "";
char suffix[MAX_STRING_LENGTH] = "";
char first_line[MAX_STRING_LENGTH];
if (bracket_phrase)
render_targeted_phrase(speaker, bracket_phrase, FALSE, listener, prefix, sizeof(prefix));
if (paren_phrase)
render_targeted_phrase(speaker, paren_phrase, FALSE, listener, suffix, sizeof(suffix));
strlcpy(first_line, "You overhear ", sizeof(first_line));
if (*prefix) {
char capped[MAX_STRING_LENGTH];
strlcpy(capped, prefix, sizeof(capped));
CAP(capped);
strlcat(first_line, capped, sizeof(first_line));
strlcat(first_line, ", ", sizeof(first_line));
}
strlcat(first_line, get_char_sdesc(speaker), sizeof(first_line));
if (context_label && *context_label) {
strlcat(first_line, " at ", sizeof(first_line));
strlcat(first_line, context_label, sizeof(first_line));
}
if (closed_door) {
strlcat(first_line, " through a closed door to the ", sizeof(first_line));
} else {
strlcat(first_line, " from the ", sizeof(first_line));
}
strlcat(first_line, dir_name ? dir_name : "unknown", sizeof(first_line));
if (*suffix) {
strlcat(first_line, ", ", sizeof(first_line));
strlcat(first_line, suffix, sizeof(first_line));
}
strlcat(first_line, ":", sizeof(first_line));
deliver_listen_output(listener, first_line, speech);
}
static void notify_adjacent_listeners_internal(struct char_data *speaker,
const char *speech,
const struct targeted_phrase *bracket_phrase,
const struct targeted_phrase *paren_phrase,
int open_dc,
int closed_dc,
bool closed_requires_mastery,
const char *context_label)
{
room_rnum origin;
if (!speaker || !speech || !*speech)
return;
origin = IN_ROOM(speaker);
if (origin == NOWHERE)
return;
for (int dir = 0; dir < NUM_OF_DIRS; dir++) {
struct room_direction_data *exit = world[origin].dir_option[dir];
room_rnum other_room;
bool closed_door;
if (!exit || exit->to_room == NOWHERE)
continue;
other_room = exit->to_room;
if (ROOM_FLAGGED(origin, ROOM_SOUNDPROOF) || ROOM_FLAGGED(other_room, ROOM_SOUNDPROOF))
continue;
closed_door = EXIT_FLAGGED(exit, EX_CLOSED) && EXIT_FLAGGED(exit, EX_ISDOOR);
for (struct char_data *listener = world[other_room].people; listener; listener = listener->next_in_room) {
if (!perform_listen_check(listener,
closed_door ? closed_dc : open_dc,
closed_requires_mastery && closed_door))
continue;
send_overheard_room(listener, speaker, context_label, dirs[dir], closed_door,
speech, bracket_phrase, paren_phrase);
}
}
}
static void notify_adjacent_listeners(struct char_data *speaker,
const char *speech,
const struct targeted_phrase *bracket_phrase,
const struct targeted_phrase *paren_phrase)
{
notify_adjacent_listeners_internal(speaker, speech,
bracket_phrase, paren_phrase,
LISTEN_DC_ROOM, LISTEN_DC_CLOSED, TRUE, NULL);
}
static void notify_adjacent_table_listeners(struct char_data *speaker,
const char *furn_name,
const char *speech,
const struct targeted_phrase *bracket_phrase,
const struct targeted_phrase *paren_phrase)
{
if (!furn_name)
furn_name = "the table";
notify_adjacent_listeners_internal(speaker, speech,
bracket_phrase, paren_phrase,
LISTEN_DC_TABLE_REMOTE,
LISTEN_DC_TABLE_REMOTE_CLOSED,
TRUE,
furn_name);
}
ACMD(do_say)
{
char *p = argument;
@ -311,7 +598,7 @@ ACMD(do_say)
render_targeted_phrase(ch, &paren_phrase, FALSE, vict, suffix, sizeof(suffix));
if (self)
strlcpy(speaker, "You", sizeof(speaker));
strlcpy(speaker, "you", sizeof(speaker));
else
strlcpy(speaker, PERS(ch, vict), sizeof(speaker));
@ -336,17 +623,23 @@ ACMD(do_say)
}
strlcat(first_line, ":", sizeof(first_line));
if (self)
capitalize_leading_you(first_line);
char wrapped_line[MAX_STRING_LENGTH];
wrap_line(first_line, wrapped_line, sizeof(wrapped_line), 80);
send_to_char(vict, "%s\r\n \"%s\"\r\n", wrapped_line, speech);
if (!self || !suppress_self) {
char hist_buf[MAX_STRING_LENGTH];
snprintf(hist_buf, sizeof(hist_buf), "%s\r\n \"%s\"", wrapped_line, speech);
compose_history_entry(hist_buf, sizeof(hist_buf), wrapped_line, speech);
add_history(vict, hist_buf, HIST_SAY);
}
}
notify_adjacent_listeners(ch, speech,
has_bracket ? &bracket_phrase : NULL,
has_paren ? &paren_phrase : NULL);
if (suppress_self)
send_to_char(ch, "%s", CONFIG_OK);
@ -414,7 +707,6 @@ ACMD(do_talk)
? furniture->short_description : "the furniture";
bool suppress_self = (!IS_NPC(ch) && PRF_FLAGGED(ch, PRF_NOREPEAT));
bool delivered = FALSE;
for (struct char_data *tch = OBJ_SAT_IN_BY(furniture); tch; tch = NEXT_SITTING(tch)) {
if (tch == ch)
@ -463,14 +755,10 @@ ACMD(do_talk)
send_to_char(tch, "%s\r\n \"%s\"\r\n", wrapped_line, speech);
char hist_buf[MAX_STRING_LENGTH];
snprintf(hist_buf, sizeof(hist_buf), "%s\r\n \"%s\"", wrapped_line, speech);
compose_history_entry(hist_buf, sizeof(hist_buf), wrapped_line, speech);
add_history(tch, hist_buf, HIST_SAY);
delivered = TRUE;
}
if (!delivered)
send_to_char(ch, "No one else seated there hears you.\r\n");
if (suppress_self)
send_to_char(ch, "%s", CONFIG_OK);
else {
@ -489,8 +777,9 @@ ACMD(do_talk)
CAP(capped);
strlcpy(first_line, capped, sizeof(first_line));
strlcat(first_line, ", you", sizeof(first_line));
} else
} else {
strlcpy(first_line, "you", sizeof(first_line));
}
strlcat(first_line, " say", sizeof(first_line));
strlcat(first_line, ", ", sizeof(first_line));
@ -501,12 +790,13 @@ ACMD(do_talk)
char locbuf[MAX_INPUT_LENGTH];
snprintf(locbuf, sizeof(locbuf), "at %s,", furn_name);
strlcat(first_line, locbuf, sizeof(first_line));
capitalize_leading_you(first_line);
char wrapped_line[MAX_STRING_LENGTH];
wrap_line(first_line, wrapped_line, sizeof(wrapped_line), 80);
send_to_char(ch, "%s\r\n \"%s\"\r\n", wrapped_line, speech);
char hist_buf[MAX_STRING_LENGTH];
snprintf(hist_buf, sizeof(hist_buf), "%s\r\n \"%s\"", wrapped_line, speech);
compose_history_entry(hist_buf, sizeof(hist_buf), wrapped_line, speech);
add_history(ch, hist_buf, HIST_SAY);
}
@ -519,6 +809,13 @@ ACMD(do_talk)
if (SITTING(onlooker) == furniture && GET_POS(onlooker) == POS_SITTING)
continue; /* already heard the speech */
if (perform_listen_check(onlooker, LISTEN_DC_TABLE, FALSE)) {
send_overheard_table(onlooker, ch, furn_name, speech,
has_bracket ? &bracket_phrase : NULL,
has_paren ? &paren_phrase : NULL);
continue;
}
char prefix[MAX_STRING_LENGTH] = "";
char suffix[MAX_STRING_LENGTH] = "";
char line[MAX_STRING_LENGTH];
@ -556,6 +853,10 @@ ACMD(do_talk)
speech_mtrigger(ch, speech);
speech_wtrigger(ch, speech);
notify_adjacent_table_listeners(ch, furn_name, speech,
has_bracket ? &bracket_phrase : NULL,
has_paren ? &paren_phrase : NULL);
}
ACMD(do_ooc)
@ -910,6 +1211,13 @@ ACMD(do_spec_comm)
if (GET_POS(onlooker) <= POS_SLEEPING)
continue;
if (perform_listen_check(onlooker, LISTEN_DC_WHISPER, FALSE)) {
send_overheard_whisper(onlooker, ch, vict, speech,
has_bracket ? &bracket_phrase : NULL,
has_paren ? &paren_phrase : NULL);
continue;
}
char prefix[MAX_STRING_LENGTH] = "";
char suffix[MAX_STRING_LENGTH] = "";
char line[MAX_STRING_LENGTH];

View file

@ -250,6 +250,7 @@ ACMD(do_use);
ACMD(do_display);
ACMD(do_group);
ACMD(do_hide);
ACMD(do_listen);
ACMD(do_not_here);
ACMD(do_report);
ACMD(do_save);

View file

@ -250,6 +250,16 @@ int roll_scan_perception(struct char_data *ch)
return total;
}
static int listen_effect_duration(struct char_data *ch)
{
int skill = GET_SKILL(ch, SKILL_PERCEPTION);
if (skill <= 0)
return 1;
return MAX(1, skill / 10);
}
ACMD(do_sneak)
{
struct affected_type af;
@ -659,6 +669,36 @@ ACMD(do_scan)
GET_MOVE(ch) -= 10;
}
ACMD(do_listen)
{
struct affected_type af;
if (!GET_SKILL(ch, SKILL_PERCEPTION)) {
send_to_char(ch, "You have no idea how to do that.\r\n");
return;
}
if (AFF_FLAGGED(ch, AFF_LISTEN)) {
affect_from_char(ch, SKILL_LISTEN);
send_to_char(ch, "You stop actively listening for hushed voices.\r\n");
return;
}
new_affect(&af);
af.spell = SKILL_LISTEN;
af.location = APPLY_NONE;
af.modifier = 0;
af.duration = listen_effect_duration(ch);
memset(af.bitvector, 0, sizeof(af.bitvector));
SET_BIT_AR(af.bitvector, AFF_LISTEN);
affect_to_char(ch, &af);
send_to_char(ch, "You focus entirely on every whisper and distant sound.\r\n");
WAIT_STATE(ch, PULSE_VIOLENCE / 2);
GET_MOVE(ch) -= 10;
}
ACMD(do_steal)
{
struct char_data *vict;

View file

@ -311,6 +311,7 @@ const char *affected_bits[] =
"SCAN",
"CHARM",
"BANDAGED",
"LISTEN",
"\n"
};

View file

@ -190,6 +190,7 @@ cpp_extern const struct command_info cmd_info[] = {
{ "last" , "last" , POS_DEAD , do_last , LVL_GOD, 0 },
{ "leave" , "lea" , POS_STANDING, do_leave , 0, 0 },
{ "list" , "lis" , POS_STANDING, do_not_here , 0, 0 },
{ "listen" , "lisn" , POS_RESTING , do_listen , 0, 0 },
{ "links" , "lin" , POS_STANDING, do_links , LVL_GOD, 0 },
{ "lock" , "loc" , POS_SITTING , do_gen_door , 0, SCMD_LOCK },
{ "load" , "load" , POS_DEAD , do_load , LVL_BUILDER, 0 },

View file

@ -936,4 +936,5 @@ void mag_assign_spells(void) {
skillo(SKILL_BLUDGEONING_WEAPONS, "bludgeoning weapons");
skillo(SKILL_PERCEPTION, "perception");
skillo(SKILL_STEALTH, "stealth");
skillo(SKILL_LISTEN, "listen");
}

View file

@ -118,6 +118,7 @@
#define SKILL_BLUDGEONING_WEAPONS 146 /* Reserved Skill[] DO NOT CHANGE */
#define SKILL_PERCEPTION 147 /* Reserved Skill[] DO NOT CHANGE */
#define SKILL_STEALTH 148 /* Shared stealth skill for hide/sneak */
#define SKILL_LISTEN 149 /* Anchor for the listen affect */
/* New skills may be added here up to MAX_SKILLS (200) */

View file

@ -301,8 +301,9 @@
#define AFF_SCAN 21 /**< Actively scanning for hidden threats */
#define AFF_CHARM 22 /**< Char is charmed */
#define AFF_BANDAGED 23 /**< Character was bandaged recently */
#define AFF_LISTEN 24 /**< Actively eavesdropping */
/** Total number of affect flags */
#define NUM_AFF_FLAGS 24
#define NUM_AFF_FLAGS 25
/* Modes of connectedness: used by descriptor_data.state */
#define CON_PLAYING 0 /**< Playing - Nominal state */