I stumbled and eventually built a hometown code for tba2023. It was a long hard slog for me. I worked with Grok Bot to convert it to stock 2025 Code.
Hometowns are something I have seen since the late 90's on a mud and seen in various versions, most notably Envy/UltraEnvy and always wondered why Circle/TBA never really had it.
Here you will find a few things. The code changes, including the new files you need to add, both in lib as /lib/town and a few other things. I went with a default hometown to start but later on there is a Travel Agent you can buy a new hometown from through spec_proc and spec_assign. Midgaard is obviously the first Hometown. I am sure someone if they want can incorporate it into the character creation system.
Here is the basic snippet. Hope you like it.
Code:
HOMETOWN SNIPPET for tbaMUD 2025 - edits to stock files Bramage
==========================================================================
HOW TO READ THIS FILE
Each file has a ===== src/file ===== header, then numbered steps in
the order to do them. Each step says roughly WHERE (function name), then:
Find: the stock lines to search for, between ---- lines
Add these lines BELOW it: / Add these lines ABOVE it: / CHANGE it to:
the new code, between ++++ lines
Copy only what is BETWEEN the ---- or ++++ lines. Tabs are real tabs,
as in stock. Every new line carries or sits under a "Bramage" comment.
Search for a short piece of the Find text if your copy is not stock.
===== src/structs.h =====
Step 1 of 1. struct player_special_data_saved - the very end of the struct, just before its closing };
Find:
----
time_t lastnews; /**< Last time player read news */
};
----
CHANGE it to:
++++
time_t lastnews; /**< Last time player read news */
/* Bramage: HOMETOWN. Index into town_table[] (town.c); 0 = the first
* town in lib/town/town.lst (Midgaard). Saved in the pfile by NAME as
* "Htwn:" so reordering town.lst never moves anyone. */
int hometown;
};
++++
===== src/utils.h =====
Step 1 of 1. The GET_LOADROOM() macro, with the other player_specials macros.
Find:
----
#define GET_LOADROOM(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.load_room))
----
Add these lines BELOW it:
++++
/** Bramage: index into town_table[] (town.c). Use valid_hometown(ch) to read it safely. */
#define GET_HOMETOWN(ch) CHECK_PLAYER_SPECIAL((ch), ((ch)->player_specials->saved.hometown))
++++
===== src/db.h =====
Step 1 of 3. The Mac (#if defined(CIRCLE_MACINTOSH)) LIB_ block near the top - the lines with colons.
Find:
----
#define LIB_HOUSE ":house:"
----
Add these lines BELOW it:
++++
#define LIB_TOWN ":town:" /* Bramage: hometown files (town.c) */
++++
Step 2 of 3. The normal LIB_ block right under it - the lines with slashes.
Find:
----
#define LIB_PLRFILES "plrfiles/"
----
Add these lines BELOW it:
++++
#define LIB_TOWN "town/" /* Bramage: hometown files (town.c) */
++++
Step 3 of 3. The list of data files (CONFIG_FILE, PLAYER_FILE, ... TIME_FILE).
Find:
----
#define TIME_FILE LIB_ETC"time" /* for calendar system */
----
Add these lines BELOW it:
++++
#define TOWN_LIST_FILE LIB_TOWN"town.lst" /* Bramage: hometown list, first = default */
++++
===== src/db.c =====
Step 1 of 3. At the top of the file, with the other #includes.
Find:
----
#include <sys/stat.h>
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 3. boot_db() - right after boot_world(), before the help entries load.
Find:
----
boot_world();
log("Loading help entries.");
----
CHANGE it to:
++++
boot_world();
/* Bramage: hometowns (lib/town/). The room vnums are looked up when
* used, so this only has to come before players log in. */
log("Loading hometown files.");
load_town_files();
log("Loading help entries.");
++++
Step 3 of 3. init_char() - just after the player_special structure is created.
Find:
----
/* If this is our first player make him IMPL. */
if (top_of_p_table == 0) {
----
Add these lines ABOVE it:
++++
/* Bramage: no hometown menu at creation - everyone starts in the first
* town in lib/town/town.lst (Midgaard). The Travel Agent changes it. */
GET_HOMETOWN(ch) = 0;
++++
===== src/players.c =====
Step 1 of 4. At the top of the file, with the other #includes.
Find:
----
#include "quest.h"
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 4. load_char() - the block that sets the PFDEF_ defaults.
Find:
----
GET_LOADROOM(ch) = PFDEF_LOADROOM;
----
Add these lines BELOW it:
++++
GET_HOMETOWN(ch) = 0; /* Bramage: no Htwn line = first town (Midgaard) */
++++
Step 3 of 4. load_char() - the tag switch, case 'H'.
Find:
----
else if (!strcmp(tag, "Hite")) GET_HEIGHT(ch) = atoi(line);
----
Add these lines BELOW it:
++++
else if (!strcmp(tag, "Htwn")) {
/* Bramage: hometown saved by name; unknown name = first town. */
int ht = town_by_name(line);
GET_HOMETOWN(ch) = (ht < 0) ? 0 : ht;
}
++++
Step 4 of 4. save_char() - where the Room: line is written.
Find:
----
if (GET_LOADROOM(ch) != PFDEF_LOADROOM) fprintf(fl, "Room: %d\n", GET_LOADROOM(ch));
----
Add these lines BELOW it:
++++
/* Bramage: hometown by name, only when it isn't the default first town. */
if (valid_hometown(ch) != 0) fprintf(fl, "Htwn: %s\n", town_table[valid_hometown(ch)].name);
++++
===== src/interpreter.c =====
Step 1 of 2. At the top of the file, with the other #includes.
Find:
----
#include "mud_event.h"
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 2. enter_player_game() - where a char with no load room gets a start room.
Find:
----
if (GET_LEVEL(d->character) >= LVL_IMMORT)
load_room = r_immort_start_room;
else
load_room = r_mortal_start_room;
----
CHANGE it to:
++++
if (GET_LEVEL(d->character) >= LVL_IMMORT)
load_room = r_immort_start_room;
else /* Bramage: mortals (and brand-new chars) start at their HOMETOWN
* Start room. Immortal/frozen start rooms stay global (cedit). */
load_room = hometown_room(d->character, TOWN_START);
++++
===== src/fight.c =====
Step 1 of 2. At the top of the file, with the other #includes.
Find:
----
#include "quest.h"
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 2. raw_kill() - between make_corpse() and extract_char().
Find:
----
make_corpse(ch);
extract_char(ch);
----
CHANGE it to:
++++
make_corpse(ch);
/* Bramage: a dead player comes back at their HOMETOWN Death room
* (town.c). extract_char() saves the pfile, and the menu's "enter game"
* uses this load room once, then clears it. A player with a sticky
* load room (PLR_LOADROOM, set by an imm) keeps that instead. */
if (!IS_NPC(ch) && !PLR_FLAGGED(ch, PLR_LOADROOM) && GET_LEVEL(ch) < LVL_IMMORT)
GET_LOADROOM(ch) = GET_ROOM_VNUM(hometown_room(ch, TOWN_DEATH));
extract_char(ch);
++++
===== src/spells.c =====
Step 1 of 2. At the top of the file, with the other #includes.
Find:
----
#include "fight.h"
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 2. ASPELL(spell_recall) - where the victim is moved.
Find:
----
char_to_room(victim, r_mortal_start_room);
----
CHANGE it to:
++++
/* Bramage: recall goes to the victim's HOMETOWN Recall room (town.c). */
char_to_room(victim, hometown_room(victim, TOWN_RECALL));
++++
===== src/act.item.c =====
Step 1 of 2. At the top of the file, with the other #includes.
Find:
----
#include "quest.h"
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 2. ACMD(do_drop) - case SCMD_DONATE, right after the donation room switch.
Find:
----
case 4: RDR = real_room(CONFIG_DON_ROOM_3); break;
}
if (RDR == NOWHERE) {
----
CHANGE it to:
++++
case 4: RDR = real_room(CONFIG_DON_ROOM_3); break;
}
/* Bramage: a real donation goes to the donor's HOMETOWN Donation room
* (town.c). The stock junk chance (case 0) above is kept. The cedit
* donation rooms are only used if the town room is missing. */
if (mode == SCMD_DONATE && !IS_NPC(ch) && real_room(hometown_vnum(ch, TOWN_DONATION)) != NOWHERE)
RDR = real_room(hometown_vnum(ch, TOWN_DONATION));
if (RDR == NOWHERE) {
++++
===== src/limits.c =====
Step 1 of 2. At the top of the file, with the other #includes.
Find:
----
#include "mud_event.h"
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 2. check_idling() - the very top of the function body.
Find:
----
static void check_idling(struct char_data *ch)
{
----
Add these lines BELOW it:
++++
/* Bramage: a connected player idling in their hometown's Rest room is
* never pulled into the void or idle-rented. If they go linkdead the
* normal rules apply, so a dropped connection can't leave a ghost. */
if (ch->desc && IN_ROOM(ch) != NOWHERE &&
hometown_vnum(ch, TOWN_REST) != NOWHERE &&
GET_ROOM_VNUM(IN_ROOM(ch)) == hometown_vnum(ch, TOWN_REST))
return;
++++
===== src/act.informative.c =====
Step 1 of 2. At the top of the file, with the other #includes.
Find:
----
#include "quest.h"
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 2. ACMD(do_score) - after the exp / gold / questpoints line.
Find:
----
send_to_char(ch, "You have %d exp, %d gold coins, and %d questpoints.\r\n",
GET_EXP(ch), GET_GOLD(ch), GET_QUESTPOINTS(ch));
----
Add these lines BELOW it:
++++
/* Bramage: hometown (town.c). */
send_to_char(ch, "Your hometown is %s.\r\n", town_table[valid_hometown(ch)].name);
++++
===== src/act.wizard.c =====
Step 1 of 4. At the top of the file, with the other #includes.
Find:
----
#include "ban.h"
#include "screen.h"
----
Add these lines BELOW it:
++++
#include "town.h" /* Bramage: hometowns */
++++
Step 2 of 4. do_stat_character() - after the Created / Last Logon line (PC only part).
Find:
----
send_to_char(ch, "Created: [%s], Last Logon: [%s]\r\n", buf1, buf2);
----
Add these lines BELOW it:
++++
/* Bramage: hometown (town.c). */
send_to_char(ch, "Hometown: [%s]\r\n", town_table[valid_hometown(k)].name);
++++
Step 3 of 4. set_fields[] in do_set's table - add a new LAST entry, just above the "\n" line.
If your list is longer than stock, use the next free number here AND in the
case below (stock's last entry is questhistory = 57, so ours is 58).
Find:
----
{ "questhistory", LVL_GOD, PC, NUMBER },
----
Add these lines BELOW it:
++++
{ "hometown", LVL_GOD, PC, MISC }, /* 58 Bramage */
++++
Step 4 of 4. perform_set() - the switch (mode); add a case just above default:.
Find:
----
default:
send_to_char(ch, "Can't set that!\r\n");
----
Add these lines ABOVE it:
++++
case 58: /* Bramage: hometown, by town name (town.c) */
if ((i = town_by_name(val_arg)) < 0) {
send_to_char(ch, "No such hometown. Towns:");
for (i = 0; i < num_towns; i++)
send_to_char(ch, " %s%s", town_table[i].name, i < num_towns - 1 ? "," : "");
send_to_char(ch, "\r\n");
return (0);
}
GET_HOMETOWN(vict) = i;
send_to_char(ch, "%s's hometown is now %s.\r\n", GET_NAME(vict), town_table[i].name);
return (1);
++++
===== src/spec_procs.h =====
Step 1 of 1. The SPECIAL() prototypes for spec_procs.c.
Find:
----
SPECIAL(bank);
----
Add these lines BELOW it:
++++
SPECIAL(travel_agent); /* Bramage: hometown changer, town.c */
++++
===== src/spec_assign.c =====
Step 1 of 2. assign_mobiles() - at the end, after the receptionists.
Change 3014 to whatever mob is your travel agent (see README).
Find:
----
ASSIGNMOB(27730, receptionist);
}
----
CHANGE it to:
++++
ASSIGNMOB(27730, receptionist);
/* Bramage: the Midgaard TRAVEL AGENT (town.c) - "list" shows the
* hometowns, "buy <town> confirm" changes yours for gold. Never put
* travel_agent on a shopkeeper; shops answer "list" and "buy" too. */
ASSIGNMOB(3014, travel_agent);
}
++++
Step 2 of 2. spec_func_list[] - so stat shows the mob's SpecProc as "Travel Agent".
Find:
----
{"Shopkeeper", shop_keeper },
----
Add these lines BELOW it:
++++
{"Travel Agent", travel_agent }, /* Bramage: hometown changer, town.c */
++++
===== END =====
That is all 31 edits in 14 files. Now rebuild (README.txt, step 4).
Here is the town file town list and template for new towns.
As always, I do not guarantee this will work with any customization already in tba2025 by coders. It compiles and boots.
Hope you folks like it.