diff --git a/include/decl.h b/include/decl.h index f0d806dc8..53902a07f 100644 --- a/include/decl.h +++ b/include/decl.h @@ -160,6 +160,7 @@ E NEARDATA struct sinfo { } program_state; E boolean restoring; +E boolean ransacked; E const char quitchars[]; E const char vowels[]; diff --git a/include/dungeon.h b/include/dungeon.h index f7b16cf2a..45246f150 100644 --- a/include/dungeon.h +++ b/include/dungeon.h @@ -146,7 +146,8 @@ typedef struct branch { #define MIGR_WITH_HERO 9 /* mon: followers; obj: trap door */ #define MIGR_NOBREAK 1024 /* bitmask: don't break on delivery */ #define MIGR_NOSCATTER 2048 /* don't scatter on delivery */ - +#define MIGR_TO_SPECIES 4096 /* migrating to species as they are made */ +#define MIGR_LEFTOVERS 8192 /* grab remaining MIGR_TO_SPECIES objects */ /* level information (saved via ledger number) */ struct linfo { diff --git a/include/extern.h b/include/extern.h index abd4be118..ed186e667 100644 --- a/include/extern.h +++ b/include/extern.h @@ -428,6 +428,8 @@ E const char *NDECL(roguename); E struct obj *FDECL(realloc_obj, (struct obj *, int, genericptr_t, int, const char *)); E char *FDECL(coyotename, (struct monst *, char *)); +E char *FDECL(rndorcname, (char *)); +E struct monst *FDECL(christen_orc, (struct monst *, char *)); E const char *FDECL(noveltitle, (int *)); E const char *FDECL(lookup_novel, (const char *, int *)); @@ -519,6 +521,7 @@ E void FDECL(container_impact_dmg, (struct obj *, XCHAR_P, XCHAR_P)); E int NDECL(dokick); E boolean FDECL(ship_object, (struct obj *, XCHAR_P, XCHAR_P, BOOLEAN_P)); E void FDECL(obj_delivery, (BOOLEAN_P)); +E void FDECL(deliver_obj_to_mon, (struct monst *mtmp, unsigned long)); E schar FDECL(down_gate, (XCHAR_P, XCHAR_P)); E void FDECL(impact_drop, (struct obj *, XCHAR_P, XCHAR_P, XCHAR_P)); @@ -1283,6 +1286,7 @@ E void FDECL(new_omailcmd, (struct obj *, const char *)); E void FDECL(free_omailcmd, (struct obj *)); E struct obj *FDECL(mkobj_at, (CHAR_P, int, int, BOOLEAN_P)); E struct obj *FDECL(mksobj_at, (int, int, int, BOOLEAN_P, BOOLEAN_P)); +E struct obj *FDECL(mksobj_migr_to_species, (int, unsigned, BOOLEAN_P, BOOLEAN_P)); E struct obj *FDECL(mkobj, (CHAR_P, BOOLEAN_P)); E int NDECL(rndmonnum); E boolean FDECL(bogon_is_pname, (CHAR_P)); @@ -1753,7 +1757,7 @@ E char *FDECL(self_lookat, (char *)); E void FDECL(mhidden_description, (struct monst *, BOOLEAN_P, char *)); E boolean FDECL(object_from_map, (int,int,int,struct obj **)); E int FDECL(do_screen_description, (coord, BOOLEAN_P, int, char *, - const char **)); + const char **, struct permonst **)); E int FDECL(do_look, (int, coord *)); E int NDECL(dowhatis); E int NDECL(doquickwhatis); diff --git a/include/hack.h b/include/hack.h index ed699a943..f65d2ddd1 100644 --- a/include/hack.h +++ b/include/hack.h @@ -270,6 +270,12 @@ typedef struct sortloot_item Loot; #define SHIFT_SEENMSG 0x01 /* put out a message if in sight */ #define SHIFT_MSG 0x02 /* always put out a message */ +/* flags for deliver_obj_to_mon */ +#define DF_NONE 0x00 +#define DF_RANDOM2 0x01 +#define DF_RANDOM3 0x02 +#define DF_ALL 0x04 + /* special mhpmax value when loading bones monster to flag as extinct or * genocided */ #define DEFUNCT_MONSTER (-100) diff --git a/src/decl.c b/src/decl.c index 2a9c28746..ffd9ef974 100644 --- a/src/decl.c +++ b/src/decl.c @@ -57,6 +57,7 @@ NEARDATA char pl_fruit[PL_FSIZ] = DUMMY; NEARDATA struct fruit *ffruit = (struct fruit *) 0; NEARDATA char tune[6] = DUMMY; +NEARDATA boolean ransacked = 0; const char *occtxt = DUMMY; const char quitchars[] = " \r\n\033"; diff --git a/src/do_name.c b/src/do_name.c index e4a874126..ef1870742 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -543,7 +543,7 @@ int cx, cy; cc.x = cx; cc.y = cy; - if (do_screen_description(cc, TRUE, sym, tmpbuf, &firstmatch)) { + if (do_screen_description(cc, TRUE, sym, tmpbuf, &firstmatch, (struct permonst **)0)) { (void) coord_desc(cx, cy, tmpbuf, iflags.getpos_coords); custompline(SUPPRESS_HISTORY, "%s%s%s%s%s", firstmatch, *tmpbuf ? " " : "", tmpbuf, @@ -593,7 +593,8 @@ int gloc; any.a_int = i + 1; tmpcc.x = garr[i].x; tmpcc.y = garr[i].y; - if (do_screen_description(tmpcc, TRUE, sym, tmpbuf, &firstmatch)) { + if (do_screen_description(tmpcc, TRUE, sym, tmpbuf, + &firstmatch, (struct permonst **)0)) { (void) coord_desc(garr[i].x, garr[i].y, tmpbuf, iflags.getpos_coords); Sprintf(fullbuf, "%s%s%s", firstmatch, @@ -2067,6 +2068,47 @@ char *buf; return buf; } +char * +rndorcname(s) +char *s; +{ + int i; + const char *v[] = {"a", "ai", "og", "u"}; + const char *snd[] = {"gor", "gris", "un", "bane", "ruk", + "oth","ul", "z", "thos","akh","hai"}; + int vstart = rn2(2); + + if (s) { + *s = '\0'; + for (i = 0; i < rn2(2) + 3; ++i) { + vstart = 1 - vstart; /* 0 -> 1, 1 -> 0 */ + if (!rn2(30) && i > 0) + (void) strcat(s, "-"); + (void) sprintf(eos(s), "%s", vstart ? v[rn2(SIZE(v))] : + snd[rn2(SIZE(snd))]); + } + } + return s; +} + +struct monst * +christen_orc(mtmp, gang) +struct monst *mtmp; +char *gang; +{ + size_t sz = 0; + char buf[BUFSZ], buf2[BUFSZ], *orcname; + + orcname = rndorcname(buf2); + sz = strlen(gang) + strlen(orcname) + strlen(" of "); + if (buf && gang && orcname && (sz < (BUFSZ - 1))) { + Sprintf(buf, "%s of %s", + upstart(orcname), upstart(gang)); + mtmp = christen_monst(mtmp, buf); + } + return mtmp; +} + /* make sure "The Colour of Magic" remains the first entry in here */ static const char *const sir_Terry_novels[] = { "The Colour of Magic", "The Light Fantastic", "Equal Rites", "Mort", diff --git a/src/dog.c b/src/dog.c index 4bcdd7722..46cd832ef 100644 --- a/src/dog.c +++ b/src/dog.c @@ -407,6 +407,12 @@ boolean with_you; break; } + if ((mtmp->mspare1 & MIGR_LEFTOVERS) != 0L) { + /* Pick up the rest of the MIGR_TO_SPECIES objects */ + if (migrating_objs) + deliver_obj_to_mon(mtmp, DF_ALL); + } + if (xlocale && wander) { /* monster moved a bit; pick a nearby location */ /* mnearto() deals w/stone, et al */ diff --git a/src/dokick.c b/src/dokick.c index 874db822a..b2d36de70 100644 --- a/src/dokick.c +++ b/src/dokick.c @@ -1612,6 +1612,9 @@ boolean near_hero; continue; where = (int) (otmp->owornmask & 0x7fffL); /* destination code */ + if ((where & MIGR_TO_SPECIES) != 0) + continue; + nobreak = (where & MIGR_NOBREAK) != 0; noscatter = (where & MIGR_WITH_HERO) != 0; where &= ~(MIGR_NOBREAK | MIGR_NOSCATTER); @@ -1667,6 +1670,48 @@ boolean near_hero; } } +void +deliver_obj_to_mon(mtmp, deliverflags) +struct monst *mtmp; +unsigned long deliverflags; +{ + struct obj *otmp, *otmp2; + int where, cnt = 0, maxobj = 0; + + if (deliverflags & DF_RANDOM3) + maxobj = rn2(3) + 1; + else if (deliverflags & DF_RANDOM2) + maxobj = rn2(2) + 1; + else if (deliverflags == DF_NONE) + maxobj = 1; + + for (otmp = migrating_objs; otmp; otmp = otmp2) { + otmp2 = otmp->nobj; + where = (int) (otmp->owornmask & 0x7fffL); /* destination code */ + if ((where & MIGR_TO_SPECIES) == 0) + continue; + + if ((mtmp->data->mflags2 & otmp->corpsenm) != 0) { + obj_extract_self(otmp); + otmp->owornmask = 0L; + otmp->ox = otmp->oy = 0; + + /* special treatment for orcs and their kind */ + if ((otmp->corpsenm & M2_ORC) != 0 && has_oname(otmp)) { + if (!has_mname(mtmp)) + mtmp = christen_orc(mtmp, ONAME(otmp)); + free_oname(otmp); + } + otmp->corpsenm = 0; + (void) add_to_minv(mtmp, otmp); + cnt++; + if (maxobj && cnt >= maxobj) + break; + /* getting here implies DF_ALL */ + } + } +} + STATIC_OVL void otransit_msg(otmp, nodrop, num) register struct obj *otmp; diff --git a/src/makemon.c b/src/makemon.c index 0d38383dc..e4cece72d 100644 --- a/src/makemon.c +++ b/src/makemon.c @@ -1390,6 +1390,9 @@ int mmflags; mtmp->mstrategy |= STRAT_APPEARMSG; } + if (allow_minvent && migrating_objs) + deliver_obj_to_mon(mtmp, DF_RANDOM3); /* in case there's waiting items */ + if (!in_mklev) newsym(mtmp->mx, mtmp->my); /* make sure the mon shows up */ diff --git a/src/mkmaze.c b/src/mkmaze.c index 22fb42d03..2e2646647 100644 --- a/src/mkmaze.c +++ b/src/mkmaze.c @@ -25,6 +25,10 @@ STATIC_DCL boolean FDECL(put_lregion_here, (XCHAR_P, XCHAR_P, XCHAR_P, STATIC_DCL void NDECL(baalz_fixup); STATIC_DCL void NDECL(setup_waterlevel); STATIC_DCL void NDECL(unsetup_waterlevel); +STATIC_DCL void FDECL(check_ransacked, (char *)); +STATIC_DCL void FDECL(migr_booty_item, (int, const char *)); +STATIC_DCL void FDECL(migrate_orc, (struct monst *, unsigned long)); +STATIC_DCL void NDECL(stolen_booty); /* adjust a coordinate one step in the specified direction */ #define mz_move(X, Y, dir) \ @@ -610,6 +614,8 @@ fixup_special() } else if (on_level(&u.uz, &baalzebub_level)) { /* custom wallify the "beetle" potion of the level */ baalz_fixup(); + } else if (u.uz.dnum == mines_dnum && ransacked) { + stolen_booty(); } if (lregions) @@ -617,6 +623,141 @@ fixup_special() num_lregions = 0; } +void +check_ransacked(s) +char *s; +{ + /* this kludge only works as long as orctown is minetn-1 */ + if (dungeons[u.uz.dnum].dname + && !strcmp(dungeons[u.uz.dnum].dname, "The Gnomish Mines") + && !strcmp(s, "minetn-1")) + ransacked = 1; + else + ransacked = 0; +} + +#define ORC_LEADER 1 + +void +migrate_orc(mtmp, flags) +struct monst *mtmp; +unsigned long flags; +{ + int nlev, max_depth, cur_depth; + d_level dest; + + cur_depth = (int) depth(&u.uz); + max_depth = dunlevs_in_dungeon(&u.uz) + + (dungeons[u.uz.dnum].depth_start - 1); + if (flags == ORC_LEADER) { + /* Note that the orc leader will take possession of any + * remaining stuff not already delivered to other + * orcs between here and the bottom of the mines. + */ + nlev = max_depth; + mtmp->mspare1 = MIGR_LEFTOVERS; + } else { + nlev = rn2(max_depth - cur_depth) + cur_depth + 1; + if (nlev == cur_depth) + nlev++; + if (nlev > max_depth) + nlev = max_depth; + mtmp->mspare1 = 0L; + } + get_level(&dest, nlev); + migrate_to_level(mtmp, ledger_no(&dest), MIGR_RANDOM, (coord *) 0); +} + +void +migr_booty_item(otyp, gang) +int otyp; +const char *gang; +{ + struct obj *otmp; + otmp = mksobj_migr_to_species(otyp, (unsigned long) M2_ORC, FALSE, FALSE); + if (otmp && gang) { + new_oname(otmp, strlen(gang) + 1); /* removes old name if one is present */ + Strcpy(ONAME(otmp), gang); + } +} + +void +stolen_booty(VOID_ARGS) +{ + char *gang, gang_name[BUFSZ]; + struct monst *mtmp; + int cnt, i, otyp; + + /* + * -------------------------------------------------------- + * Mythos: + * + * A tragic accident has occurred in Frontier Town... + * It has been overrun by orcs. + * + * The booty that the orcs took from the town is now + * in the possession of the orcs that did this and + * have long since fled the level. + * -------------------------------------------------------- + */ + + gang = rndorcname(gang_name); + /* create the leader of the orc gang */ + mtmp = makemon(&mons[PM_ORC_CAPTAIN], 0, 0, MM_NONAME); + if (mtmp) { + mtmp = christen_monst(mtmp, upstart(gang)); + mtmp->mpeaceful = 0; + migrate_orc(mtmp, ORC_LEADER); + } + /* create the stuff that the rest of the gang took */ + cnt = rn2(3) + 1; + for (i = 0; i < cnt; ++i) + migr_booty_item(rn2(4) ? TALLOW_CANDLE : WAX_CANDLE, gang); + cnt = rn2(2) + 1; + for (i = 0; i < cnt; ++i) + migr_booty_item(SKELETON_KEY, gang); + migr_booty_item(rn2(2) ? LONG_SWORD : SILVER_SABER, gang); + otyp = rn2((GAUNTLETS_OF_DEXTERITY - LEATHER_GLOVES) + 1) + LEATHER_GLOVES; + migr_booty_item(otyp, gang); + cnt = rn2(9) + 1; + for (i = 0; i < cnt; ++i) { + /* Food items - but no lembas! (or some other weird things) */ + otyp = rn2((TIN - TRIPE_RATION) + 1) + TRIPE_RATION; + if (otyp != LEMBAS_WAFER && otyp != GLOB_OF_GRAY_OOZE && + otyp != GLOB_OF_BROWN_PUDDING && otyp != GLOB_OF_GREEN_SLIME && + otyp != GLOB_OF_BLACK_PUDDING && otyp != MEAT_STICK && + otyp != MEATBALL && otyp != MEAT_STICK && otyp != MEAT_RING && + otyp != HUGE_CHUNK_OF_MEAT && otyp != CORPSE) + migr_booty_item(otyp, gang); + } + /* Make most of the orcs on the level be part of the invading gang */ + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { + if (DEADMONSTER(mtmp)) + continue; + + if (is_orc(mtmp->data) && !has_mname(mtmp) && rn2(10)) + mtmp = christen_orc(mtmp, upstart(gang)); + } + /* Lastly, ensure there's a few more orcs from the gang along the way + * The mechanics are such that they aren't actually identified as + * members of the invading gang until they get their spoils assigned + * to the inventory; handled during that assignment. + */ + cnt = rn2(6) + 1; + for (i = 0; i < cnt; ++i) { + int mtyp; + + mtyp = rn2((PM_ORC_SHAMAN - PM_ORC) + 1) + PM_ORC; + mtmp = makemon(&mons[mtyp], 0, 0, MM_NONAME); + if (mtmp) + migrate_orc(mtmp, 0L); + } + + ransacked = 0; +} + +#undef ORC_LEADER + boolean maze_inbounds(x, y) int x, y; @@ -819,6 +960,7 @@ const char *s; } if (*protofile) { + check_ransacked(protofile); Strcat(protofile, LEV_EXT); if (load_special(protofile)) { /* some levels can end up with monsters diff --git a/src/mkobj.c b/src/mkobj.c index cf7a77233..558be1dc6 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -224,6 +224,23 @@ boolean init, artif; return otmp; } +struct obj * +mksobj_migr_to_species(otyp, mflags2, init, artif) +int otyp; +unsigned mflags2; +boolean init, artif; +{ + struct obj *otmp; + + otmp = mksobj(otyp, init, artif); + if (otmp) { + add_to_migration(otmp); + otmp->owornmask = (long) MIGR_TO_SPECIES; + otmp->corpsenm = mflags2; + } + return otmp; +} + /* mkobj(): select a type of item from a class, use mksobj() to create it */ struct obj * mkobj(oclass, artif) diff --git a/src/pager.c b/src/pager.c index 9c2d74946..6ef7bf6ce 100644 --- a/src/pager.c +++ b/src/pager.c @@ -16,8 +16,9 @@ STATIC_DCL void FDECL(look_at_monster, (char *, char *, struct monst *, int, int)); STATIC_DCL struct permonst *FDECL(lookat, (int, int, char *, char *)); STATIC_DCL void FDECL(checkfile, (char *, struct permonst *, - BOOLEAN_P, BOOLEAN_P)); + BOOLEAN_P, BOOLEAN_P, char *)); STATIC_DCL void FDECL(look_all, (BOOLEAN_P,BOOLEAN_P)); +STATIC_DCL void FDECL(do_supplemental_info, (char *, struct permonst *,BOOLEAN_P)); STATIC_DCL void NDECL(whatdoes_help); STATIC_DCL void NDECL(docontact); STATIC_DCL void NDECL(dispfile_help); @@ -509,10 +510,11 @@ char *buf, *monbuf; * Therefore, we create a copy of inp _just_ for data.base lookup. */ STATIC_OVL void -checkfile(inp, pm, user_typed_name, without_asking) +checkfile(inp, pm, user_typed_name, without_asking, supplemental_name) char *inp; struct permonst *pm; boolean user_typed_name, without_asking; +char *supplemental_name; { dlb *fp; char buf[BUFSZ], newstr[BUFSZ], givenname[BUFSZ]; @@ -619,7 +621,7 @@ boolean user_typed_name, without_asking; int chk_skip, pass = 1; boolean yes_to_moreinfo, found_in_file, pass1found_in_file, skipping_entry; - char *ap, *alt = 0; /* alternate description */ + char *sp, *ap, *alt = 0; /* alternate description */ /* adjust the input to remove "named " and "called " */ if ((ep = strstri(dbase_str, " named ")) != 0) { @@ -629,6 +631,8 @@ boolean user_typed_name, without_asking; } else if ((ep = strstri(dbase_str, " called ")) != 0) { copynchars(givenname, ep + 8, BUFSZ - 1); alt = givenname; + if (supplemental_name && (sp = strstri(inp, " called ")) != 0) + copynchars(supplemental_name, sp + 8, BUFSZ - 1); } else ep = strstri(dbase_str, ", "); if (ep && ep > dbase_str) @@ -760,12 +764,13 @@ boolean user_typed_name, without_asking; } int -do_screen_description(cc, looked, sym, out_str, firstmatch) +do_screen_description(cc, looked, sym, out_str, firstmatch, for_supplement) coord cc; boolean looked; int sym; char *out_str; const char **firstmatch; +struct permonst **for_supplement; { static const char mon_interior[] = "the interior of a monster", unreconnoitered[] = "unreconnoitered"; @@ -1008,11 +1013,15 @@ const char **firstmatch; didlook: if (looked) { + struct permonst *pm = (struct permonst *)0; + if (found > 1 || need_to_look) { char monbuf[BUFSZ]; char temp_buf[BUFSZ]; - (void) lookat(cc.x, cc.y, look_buf, monbuf); + pm = lookat(cc.x, cc.y, look_buf, monbuf); + if (pm && for_supplement) + *for_supplement = pm; *firstmatch = look_buf; if (*(*firstmatch)) { Sprintf(temp_buf, " (%s)", *firstmatch); @@ -1043,7 +1052,7 @@ coord *click_cc; boolean clicklook = (mode == 2); /* right mouse-click method */ char out_str[BUFSZ] = DUMMY; const char *firstmatch = 0; - struct permonst *pm = 0; + struct permonst *pm = 0, *supplemental_pm = 0; int i = '\0', ans = 0; int sym; /* typed symbol or converted glyph */ int found; /* count of matching syms found */ @@ -1137,7 +1146,7 @@ coord *click_cc; break; } if (*out_str) - checkfile(out_str, pm, TRUE, TRUE); + checkfile(out_str, pm, TRUE, TRUE, (char *) 0); return 0; } case '?': @@ -1151,7 +1160,7 @@ coord *click_cc; return 0; if (out_str[1]) { /* user typed in a complete string */ - checkfile(out_str, pm, TRUE, TRUE); + checkfile(out_str, pm, TRUE, TRUE, (char *) 0); return 0; } sym = out_str[0]; @@ -1204,7 +1213,7 @@ coord *click_cc; } found = do_screen_description(cc, (from_screen || clicklook), sym, - out_str, &firstmatch); + out_str, &firstmatch, &supplemental_pm); /* Finally, print out our explanation. */ if (found) { @@ -1215,16 +1224,18 @@ coord *click_cc; if (found == 1 && ans != LOOK_QUICK && ans != LOOK_ONCE && (ans == LOOK_VERBOSE || (flags.help && !quick)) && !clicklook) { - char temp_buf[BUFSZ]; + char temp_buf[BUFSZ], supplemental_name[BUFSZ]; Strcpy(temp_buf, firstmatch); checkfile(temp_buf, pm, FALSE, - (boolean) (ans == LOOK_VERBOSE)); + (boolean) (ans == LOOK_VERBOSE), supplemental_name); + if (supplemental_pm && supplemental_name) + do_supplemental_info(supplemental_name, supplemental_pm, + (boolean) (ans == LOOK_VERBOSE)); } } else { pline("I've never heard of such things."); } - } while (from_screen && !quick && ans != LOOK_ONCE && !clicklook); flags.verbose = save_verbose; @@ -1319,6 +1330,68 @@ boolean do_mons; /* True => monsters, False => objects */ destroy_nhwindow(win); } +void +do_supplemental_info(name, pm, without_asking) +char *name; +struct permonst *pm; +boolean without_asking; +{ + winid datawin = WIN_ERR; + char *entrytext = name, *bp; + char question[QBUFSZ]; + boolean yes_to_moreinfo = FALSE; + + /* + * Provide some info on some specific things + * meant to support in-game mythology, and not + * available from data.base or other sources. + */ + if (name && pm && is_orc(pm) && + (strlen(name) < (BUFSZ - 1)) && + (bp = strstri(name, " of ")) != 0) { + char fullname[BUFSZ]; + + Strcpy(fullname, name); + if (!without_asking) { + Strcpy(question, "More info about \""); + /* +2 => length of "\"?" */ + copynchars(eos(question), entrytext, + (int) (sizeof question - 1 - (strlen(question) + 2))); + Strcat(question, "\"?"); + if (yn(question) == 'y') + yes_to_moreinfo = TRUE; + } + if (yes_to_moreinfo) { + int i, subs = 0; + char *gang = bp + 4; + char *text[] = { + "%s is a member of a marauding horde of orcs", + "rumored to have brutally attacked and plundered the ordinarily", + "sheltered town that is located deep within The Gnomish Mines.", + "", + "The members of that vicious horde proudly and defiantly acclaim their", + "allegiance to their leader %s in their names.", + }; + + *bp = '\0'; + datawin = create_nhwindow(NHW_MENU); + for (i = 0; i < SIZE(text); i++) { + char buf[BUFSZ], *txt; + + if (strstri(text[i], "%s") != 0) { + Sprintf(buf, text[i], + subs++ ? gang : fullname); + txt = buf; + } else + txt = text[i]; + putstr(datawin, 0, txt); + } + display_nhwindow(datawin, FALSE); + destroy_nhwindow(datawin), datawin = WIN_ERR; + } + } +} + /* the '/' command */ int dowhatis()