Skip to content

Commit

Permalink
Add a whole new generator for shop names
Browse files Browse the repository at this point in the history
Since the beginning of time, shop names have been one thing that stayed
pretty uninspiring in a game otherwise chock full of very colourful and
varied procedural text generators for other random game elements.

The old generator consisted of:
"<name>'s <type> [Shoppe|Boutique|Emporium|Shop]"

And that was it.

This commit fixes all that with a brand new and far more colourful
generator largely consisting of a nearly 3000-line textdb file.

Now many synonyms are used for different shop contents, words
for "shop", as well as adding a couple of entirely new paths for
shopkeeper names that are rather more memorable than the classic
make_name() which is used everywhere (e.g. Pan Lords, artifacts,
ghosts...) There are a few grammatical variations on the name
structure (e.g. Dave's Weapon Shop vs Weapons By Dave vs
Ye Olde Weapon Shop).

Names start off fairly straightforward in the early levels but
increase in variety and eccentricity based on dungeon level (or
player XL for Gozag shops) by varying the length of the generated
names, both upwards and downwards. So you end up with very
baroque names as well as very short ones.

There are enough permutations that no two names are ever
likely to be similar over a single game, although if by chance
they occasionally are it's enough of a funny coincidence and
just makes it look like the shops are competitors or perhaps a chain.

This is also something of a flavour enhancement for Gozag as
they get some special handling and rather more extravangant
shopkeeper names.

Vault-defined shop names are left alone and will resort to the old
generator where they are partially defined.

Support for Gadget shops is included in expectation of them being
reinstated in a separate commit.
  • Loading branch information
chucksellick committed Aug 15, 2024
1 parent f5571fc commit 6807301
Show file tree
Hide file tree
Showing 13 changed files with 3,059 additions and 75 deletions.
2,839 changes: 2,839 additions & 0 deletions crawl-ref/source/dat/database/shopname.txt

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions crawl-ref/source/database.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ static TextDB AllDBs[] =
"montitle.txt", // titles for monsters (i.e. uniques)
"decorlines.txt", // miscellaneous lines for walking on decoration
"gizmo.txt", // name-assembling for gizmos
"shopname.txt", // fancy shop name generator
}),

TextDB("quotes", "descript/",
Expand Down Expand Up @@ -700,7 +701,6 @@ static void _call_recursive_replacement(string &str, TextDB &db,
break;
}

string marker_full = str.substr(pos, end - pos + 1);
string marker = str.substr(pos + 1, end - pos - 1);

string replacement =
Expand All @@ -714,7 +714,7 @@ static void _call_recursive_replacement(string &str, TextDB &db,
}
else
{
str.replace(pos, marker_full.length(), replacement);
str.replace(pos, marker.length() + 2, replacement);

// Start search from pos rather than end + 1, so that if
// the replacement contains its own @foo@, we can replace
Expand Down
26 changes: 17 additions & 9 deletions crawl-ref/source/dungeon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6248,16 +6248,24 @@ void place_spec_shop(const coord_def& where, shop_spec &spec, int shop_level)
shop_struct& shop = env.shop[where];

const int level_number = shop_level ? shop_level : env.absdepth0;

for (int j = 0; j < 3; j++)
shop.keeper_name[j] = 1 + random2(200);
shop.shop_name = spec.name;
shop.shop_type_name = spec.type;
shop.shop_suffix_name = spec.suffix;
shop.type = spec.sh_type == SHOP_RANDOM ? _random_shop() : spec.sh_type;
shop.level = level_number * 2;
shop.type = spec.sh_type;
if (shop.type == SHOP_RANDOM)
shop.type = _random_shop();
if (!spec.full_name.empty())
{
shop.shop_name = spec.name;
shop.full_shop_name = spec.full_name;
}
else if (spec.name.empty() && spec.type.empty() && spec.suffix.empty())
{
auto names = generate_shop_name(shop.type, shop.level, spec.gozag);
std::tie(shop.full_shop_name, shop.shop_name) = names;
}
else
{
shop.shop_name = spec.name;
shop.shop_type_name = spec.type;
shop.shop_suffix_name = spec.suffix;
}
shop.greed = _shop_greed(shop.type, level_number, spec.greed);
shop.pos = where;

Expand Down
66 changes: 23 additions & 43 deletions crawl-ref/source/god-abil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3505,6 +3505,7 @@ static int _gozag_shop_price(int index)
static void _setup_gozag_shop(int index, vector<shop_type> &valid_shops)
{
ASSERT(!you.props.exists(make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, index)));
ASSERT(!you.props.exists(make_stringf(GOZAG_SHOP_NAME_KEY, index)));

shop_type type = NUM_SHOPS;
int choice = random2(valid_shops.size());
Expand All @@ -3513,17 +3514,11 @@ static void _setup_gozag_shop(int index, vector<shop_type> &valid_shops)
valid_shops.erase(valid_shops.begin() + choice);
you.props[make_stringf(GOZAG_SHOP_TYPE_KEY, index)].get_int() = type;

you.props[make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, index)].get_string()
= make_name();

const bool need_suffix = type != SHOP_GENERAL
&& type != SHOP_GENERAL_ANTIQUE
&& type != SHOP_DISTILLERY;
you.props[make_stringf(GOZAG_SHOP_SUFFIX_KEY, index)].get_string()
= need_suffix
? random_choose("Shoppe", "Boutique",
"Emporium", "Shop")
: "";
auto names = generate_shop_name(type, you.experience_level, true);
you.props[make_stringf(GOZAG_SHOP_NAME_KEY, index)]
= names.first;
you.props[make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, index)]
= names.second;

you.props[make_stringf(GOZAG_SHOP_COST_KEY, index)].get_int()
= gozag_price_for_shop();
Expand All @@ -3542,20 +3537,18 @@ static string _describe_gozag_shop(int index)
const int cost = _gozag_shop_price(index);

const char offer_letter = 'a' + index;

const string shop_name =
apostrophise(you.props[make_stringf(GOZAG_SHOPKEEPER_NAME_KEY,
index)].get_string());
you.props[make_stringf(GOZAG_SHOP_NAME_KEY,
index)].get_string();
const shop_type type = _gozag_shop_type(index);
const string type_name = shop_type_name(type);
const string suffix =
you.props[make_stringf(GOZAG_SHOP_SUFFIX_KEY, index)].get_string();

return make_stringf(" [%c] %5d gold - %s %s %s",
return make_stringf(" [%c] %5d gold - %s (%s)",
offer_letter,
cost,
shop_name.c_str(),
type_name.c_str(),
suffix.c_str());
type_name.c_str());
}

/**
Expand Down Expand Up @@ -3591,24 +3584,15 @@ static int _gozag_choose_shop()
/**
* Make a vault spec for the gozag shop offer at the given index.
*/
static string _gozag_shop_spec(int index)
static shop_spec _gozag_shop_spec(int index)
{
const shop_type type = _gozag_shop_type(index);
const string name =
you.props[make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, index)];
const string full_name =
you.props[make_stringf(GOZAG_SHOP_NAME_KEY, index)];

string suffix = replace_all(
you.props[make_stringf(GOZAG_SHOP_SUFFIX_KEY,
index)]
.get_string(), " ", "_");
if (!suffix.empty())
suffix = " suffix:" + suffix;

return make_stringf("%s shop name:%s%s gozag",
shoptype_to_str(type),
replace_all(name, " ", "_").c_str(),
suffix.c_str());

return shop_spec(type, name, "", "", -1, -1, false, true, full_name);
}

/**
Expand All @@ -3619,13 +3603,8 @@ static string _gozag_shop_spec(int index)
static void _gozag_place_shop(int index)
{
ASSERT(env.grid(you.pos()) == DNGN_FLOOR);
keyed_mapspec kmspec;
kmspec.set_feat(_gozag_shop_spec(index), false);

feature_spec feat = kmspec.get_feat();
if (!feat.shop)
die("Invalid shop spec?");
place_spec_shop(you.pos(), *feat.shop, you.experience_level);
shop_spec spec = _gozag_shop_spec(index);
place_spec_shop(you.pos(), spec, you.experience_level);

link_items();
env.markers.add(new map_feature_marker(you.pos(), DNGN_ABANDONED_SHOP));
Expand All @@ -3634,12 +3613,9 @@ static void _gozag_place_shop(int index)
shop_struct *shop = shop_at(you.pos());
ASSERT(shop);

const gender_type gender = random_choose(GENDER_FEMALE, GENDER_MALE,
GENDER_NEUTRAL);

mprf(MSGCH_GOD, "%s invites you to visit %s %s%s%s.",
shop->shop_name.c_str(),
decline_pronoun(gender, PRONOUN_POSSESSIVE),
decline_pronoun(GENDER_NEUTRAL, PRONOUN_POSSESSIVE),
shop_type_name(shop->type).c_str(),
!shop->shop_suffix_name.empty() ? " " : "",
shop->shop_suffix_name.c_str());
Expand All @@ -3651,6 +3627,7 @@ static bool _shop_type_valid(shop_type type)
{
#if TAG_MAJOR_VERSION == 34
case SHOP_FOOD:
return false;
#endif
case SHOP_EVOKABLES:
return !you.has_mutation(MUT_NO_ARTIFICE);
Expand Down Expand Up @@ -3708,9 +3685,12 @@ bool gozag_call_merchant()

for (int j = 0; j < GOZAG_MAX_SHOPS; j++)
{
#if TAG_MAJOR_VERSION == 34
you.props.erase(make_stringf(GOZAG_SHOPKEEPER_NAME_KEY, j));
you.props.erase(make_stringf(GOZAG_SHOP_TYPE_KEY, j));
you.props.erase(make_stringf(GOZAG_SHOP_SUFFIX_KEY, j));
#endif
you.props.erase(make_stringf(GOZAG_SHOP_NAME_KEY, j));
you.props.erase(make_stringf(GOZAG_SHOP_TYPE_KEY, j));
you.props.erase(make_stringf(GOZAG_SHOP_COST_KEY, j));
}

Expand Down
5 changes: 4 additions & 1 deletion crawl-ref/source/god-abil.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ const char * const GOZAG_POTIONS_KEY = "gozag_potions%d";
const char * const GOZAG_PRICE_KEY = "gozag_price%d";

const char * const GOZAG_SHOPKEEPER_NAME_KEY = "gozag_shopkeeper_%d";
const char * const GOZAG_SHOP_TYPE_KEY = "gozag_shop_type_%d";
#if TAG_MAJOR_VERSION == 34
const char * const GOZAG_SHOP_SUFFIX_KEY = "gozag_shop_suffix_%d";
#endif
const char * const GOZAG_SHOP_NAME_KEY = "gozag_shop_%d";
const char * const GOZAG_SHOP_TYPE_KEY = "gozag_shop_type_%d";
const char * const GOZAG_SHOP_COST_KEY = "gozag_shop_cost_%d";

#define GOZAG_GOLD_AURA_KEY "gozag_gold_aura_amount"
Expand Down
13 changes: 13 additions & 0 deletions crawl-ref/source/l-dgn.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "libutil.h"
#include "mapmark.h"
#include "maps.h"
#include "shopping.h"
#include "shout.h"
#include "spl-util.h"
#include "state.h"
Expand Down Expand Up @@ -1780,6 +1781,16 @@ LUAFN(dgn_state_is_descent)
return 1;
}

LUAFN(dgn_shopname)
{
const char* type_name = luaL_checkstring(ls, 1);
shop_type shop = str_to_shoptype(type_name);
int level = luaL_safe_checkint(ls, 2);
bool gozag = lua_isboolean(ls, 3) ? lua_toboolean(ls, 3) : false;
auto generated = generate_shop_name(shop, level, gozag);
PLUARET(string, generated.first.c_str());
}

const struct luaL_reg dgn_dlib[] =
{
{ "reset_level", _dgn_reset_level },
Expand Down Expand Up @@ -1898,6 +1909,8 @@ const struct luaL_reg dgn_dlib[] =

{ "is_descent", dgn_state_is_descent },

{ "shopname", dgn_shopname },

{ nullptr, nullptr }
};

Expand Down
3 changes: 2 additions & 1 deletion crawl-ref/source/mapdef.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6091,6 +6091,7 @@ feature_spec keyed_mapspec::parse_shop(string s, int weight, int mimic,

const bool gozag = strip_tag(s, "gozag");

string full_shop_name = replace_all_of(strip_tag_prefix(s, "full_name:"), "_", " ");
string shop_name = replace_all_of(strip_tag_prefix(s, "name:"), "_", " ");
string shop_type_name = replace_all_of(strip_tag_prefix(s, "type:"),
"_", " ");
Expand Down Expand Up @@ -6131,7 +6132,7 @@ feature_spec keyed_mapspec::parse_shop(string s, int weight, int mimic,
feature_spec fspec(-1, weight, mimic, no_mimic);
fspec.shop.reset(new shop_spec(shop, shop_name, shop_type_name,
shop_suffix_name, greed,
num_items, use_all, gozag));
num_items, use_all, gozag, full_shop_name));
fspec.shop->items = items;
return fspec;
}
Expand Down
8 changes: 6 additions & 2 deletions crawl-ref/source/mapdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -799,10 +799,14 @@ struct shop_spec
* stock).
* */

string full_name; /**< Overrides all other name parameters and sets the entire
name of the shop */

shop_spec(shop_type sh, string n="", string t="",
string s="", int g=-1, int ni=-1, bool u=false, bool goz=false)
string s="", int g=-1, int ni=-1, bool u=false, bool goz=false,
string fname="")
: sh_type(sh), name(n), type(t), suffix(s),
greed(g), num_items(ni), items(), use_all(u), gozag(goz) { }
greed(g), num_items(ni), items(), use_all(u), gozag(goz), full_name(fname) { }
};

/**
Expand Down
31 changes: 31 additions & 0 deletions crawl-ref/source/scripts/shopnames.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-- Generates a huge number of shop names to test the generator
-- Runs through the full range of "levels" (we increment 2 levels per dungeon
-- level, so 54), all shop types, and again with Gozag shops
-- Usage:
-- ./crawl -script shopnames 1> shopnames_test.txt

local shop_types = {
"general",
"antiques",
"weapon",
"antique weapon",
"armour",
"antique armour",
"book",
"scroll",
"distillery",
"jewellery",
"gadget"
}

for i,shop in ipairs(shop_types) do
print (shop .. ":\n")
for level = 1,54 do
print (level .. ": " .. dgn.shopname(shop, level))
end
print ("\n" .. shop .. " (Gozag):\n")
for level = 1,54 do
print (level .. ": " .. dgn.shopname(shop, level, true))
end
print ("\n")
end
Loading

0 comments on commit 6807301

Please sign in to comment.