Hi there,
The three patches attached here enable the 'follow' command for non-DM
players. It allows players to follow each other, including through exits
and permanent apartments.
When two players are on the same tiled map, this code simply calls
player_move() in the direction of the player being followed. If the
player being followed moves through an exit, that the exit object is
copied to a new field in the player structure. The follower then moves
to the exit (instead of the player) and applies the exit once on top.
This means that gates, check_inv's, and other forms of blocked movement
will still continue to be effective.
This patch reduces the possibility of abuse by requiring players be in
the same party in order to follow each other. If the followed player
leaves the party at any point, players can no longer follow them.
Patch 2 allows followers to follow into unique maps such as permanent
apartments. This allows players to visit each others' permanent apartments.
I welcome your feedback and discussion about these changes here, or on
the SourceForge tracker:
https://sourceforge.net/p/crossfire/patches/400/
Regards,
Kevin
From f0c24d6d2ec3a7a3b38977e2c45b8b6edadc446c Mon Sep 17 00:00:00 2001
From: Kevin Zheng <[email protected]>
Date: Sat, 14 Nov 2020 11:55:29 -0800
Subject: [PATCH 1/3] Enable 'follow' command for non-DMs
The 'follow' command allows DMs to follow players by teleporting to them
when they move away. This patch reworks this command so that ordinary
players can use it to follow each other, including through exits,
without teleportation (and thus violating the map security model).
When two players are on the same tiled map, this code simply calls
player_move() in the direction of the player being followed. If the
player being followed moves through an exit, that the exit object is
copied to a new field in the player structure. The follower then moves
to the exit (instead of the player) and applies the exit once on top.
This means that gates, check_inv's, and other forms of blocked movement
will still continue to be effective.
---
common/player.c | 5 +++
include/player.h | 2 +
include/server.h | 2 +
server/c_wiz.c | 32 ++++++++++++----
server/commands.c | 2 +-
server/server.c | 95 +++++++++++++++++++++++++++++++++++++----------
6 files changed, 110 insertions(+), 28 deletions(-)
diff --git a/common/player.c b/common/player.c
index e8c68880..6e5a7da2 100644
--- a/common/player.c
+++ b/common/player.c
@@ -46,6 +46,11 @@ void clear_player(player *pl) {
}
if (pl->unarmed_skill)
FREE_AND_CLEAR_STR(pl->unarmed_skill);
+
+ if (pl->last_exit != NULL) {
+ object_free(pl->last_exit, FREE_OBJ_NO_DESTROY_CALLBACK);
+ pl->last_exit = NULL;
+ }
}
/**
diff --git a/include/player.h b/include/player.h
index c4fd6a06..84ecf362 100644
--- a/include/player.h
+++ b/include/player.h
@@ -188,6 +188,8 @@ typedef struct pl {
* but we will have to get password first
* so we have to remember which party to
* join. */
+ struct obj *last_exit; /**< Last exit used by player or NULL */
+
party_rejoin_mode rejoin_party; /**< Whether to rejoin or not party at login. */
char search_str[MAX_BUF]; /**< Item we are looking for. */
uint32_t mark_count; /**< Count of marked object. */
diff --git a/include/server.h b/include/server.h
index 81361b07..db1e771a 100644
--- a/include/server.h
+++ b/include/server.h
@@ -9,4 +9,6 @@ void player_map_change_common(object* op, mapstruct* const oldmap,
mapstruct* const newmap);
void login_check_shutdown(object* const op);
+bool can_follow(object*, player*);
+
#endif
diff --git a/server/c_wiz.c b/server/c_wiz.c
index eaf0acb4..f0bd7c9c 100644
--- a/server/c_wiz.c
+++ b/server/c_wiz.c
@@ -1982,8 +1982,6 @@ void command_nowiz(object *op, const char *params) { /* 'noadm' is alias */
CLEAR_FLAG(op, FLAG_WIZ);
CLEAR_FLAG(op, FLAG_WIZPASS);
CLEAR_FLAG(op, FLAG_WIZCAST);
- if (op->contr->followed_player)
- FREE_AND_CLEAR_STR(op->contr->followed_player);
if (settings.real_wiz == TRUE)
CLEAR_FLAG(op, FLAG_WAS_WIZ);
@@ -2689,11 +2687,16 @@ void command_style_map_info(object *op, const char *params) {
(unsigned long)(objects_used*sizeof(object)));
}
+bool can_follow(object* op, player* other) {
+ // Only allow follow from same party.
+ return !(other->ob->contr->party == NULL || op->contr->party != other->ob->contr->party);
+}
+
/**
* DM wants to follow a player, or stop following a player.
*
* @param op
- * wizard.
+ * Player follower
* @param params
* player to follow. If NULL, stop following player.
*/
@@ -2702,7 +2705,7 @@ void command_follow(object *op, const char *params) {
if (*params == '\0') {
if (op->contr->followed_player != NULL) {
- draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_DM, "You stop following %s.", op->contr->followed_player);
+ draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, "You stop following %s.", op->contr->followed_player);
FREE_AND_CLEAR_STR(op->contr->followed_player);
}
return;
@@ -2710,19 +2713,34 @@ void command_follow(object *op, const char *params) {
other = find_player_partial_name(params);
if (!other) {
- draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_DM, "No such player or ambiguous name.");
+ draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE, "No such player or ambiguous name.");
return;
}
if (other == op->contr) {
- draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_DM, "You can't follow yourself.");
+ draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE, "You can't follow yourself.");
return;
}
+ // Players trying to 'follow' are subject to additional checks.
+ if (!QUERY_FLAG(op, FLAG_WIZ)) {
+ if (!can_follow(op, other)) {
+ draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND,
+ MSG_TYPE_COMMAND_FAILURE,
+ "You can only follow members in the same party.");
+ return;
+ }
+ rv_vector rv;
+ if (!get_rangevector(op, other->ob, &rv, 0) || rv.distance > 1) {
+ draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE, "You need to go to them first!");
+ return;
+ }
+ }
+
if (op->contr->followed_player)
FREE_AND_CLEAR_STR(op->contr->followed_player);
op->contr->followed_player = add_string(other->ob->name);
- draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_DM, "Following %s.", op->contr->followed_player);
+ draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS, "Following %s.", op->contr->followed_player);
}
void command_purge_quest(object *op, const char * param) {
diff --git a/server/commands.c b/server/commands.c
index 2a5ef96b..2acca90a 100644
--- a/server/commands.c
+++ b/server/commands.c
@@ -50,6 +50,7 @@ command_array_struct Commands[] = {
{ "empty", command_empty, 1.0 },
{ "examine", command_examine, 0.5 },
{ "fix_me", command_fix_me, 0.0 },
+ { "follow", command_follow, 0.0 },
{ "forget_spell", command_forget_spell, 0.0 },
{ "get", command_take, 1.0 },
{ "help", command_help, 0.0 },
@@ -218,7 +219,6 @@ command_array_struct WizCommands [] = {
{ "dumpallmaps", command_dumpallmaps, 0.0 },
{ "dumpallobjects", command_dumpallobjects, 0.0 },
{ "dumpmap", command_dumpmap, 0.0 },
- { "follow", command_follow, 0.0 },
{ "free", command_free, 0.0 },
{ "freeze", command_freeze, 0.0 },
{ "goto", command_goto, 0.0 },
diff --git a/server/server.c b/server/server.c
index 4b5c9dcf..9aa6a402 100644
--- a/server/server.c
+++ b/server/server.c
@@ -837,6 +837,79 @@ void enter_exit(object *op, object *exit_ob) {
/* For exits that cause damages (like pits) */
if (exit_ob->stats.dam && op->type == PLAYER)
hit_player(op, exit_ob->stats.dam, exit_ob, exit_ob->attacktype, 1);
+
+ if (op->contr) {
+ object* exit_copy = object_new();
+ object_copy(exit_ob, exit_copy);
+ exit_copy->map = exit_ob->map; // hack to set map without actually inserting
+ if (op->contr->last_exit != NULL) {
+ object_free(op->contr->last_exit, FREE_OBJ_NO_DESTROY_CALLBACK);
+ }
+ op->contr->last_exit = exit_copy;
+ }
+}
+
+static int move_towards(object* ob, object* towards, int mindist) {
+ rv_vector rv;
+ get_rangevector(ob, towards, &rv, 0);
+ if (rv.direction != 0 && rv.distance > mindist) {
+ if (ob->speed_left > 0) {
+ move_player(ob, rv.direction);
+ }
+ }
+ return rv.direction;
+}
+
+/**
+ * Return true if the player object is on the given exit. This is required
+ * because some multi-tile exits are unpassable from a certain direction.
+ */
+static bool object_on_exit(object* ob, object* exit) {
+ int x = exit->x;
+ int y = exit->y;
+ int sx, sy, sx2, sy2;
+ object_get_multi_size(exit, &sx, &sy, &sx2, &sy2);
+ return (ob->x >= x+sx2) && (ob->x <= x+sx) && (ob->y >= y+sy2) && (ob->y <= y+sy);
+}
+
+static void do_follow(player* pl) {
+ player *followed = find_player_partial_name(pl->followed_player);
+ if (followed && followed->ob && followed->ob->map) {
+ if (query_flag(pl->ob, FLAG_WIZ)) {
+ rv_vector rv;
+ if (!get_rangevector(pl->ob, followed->ob, &rv, 0) || rv.distance > 4) {
+ int space = object_find_free_spot(pl->ob, followed->ob->map, followed->ob->x, followed->ob->y, 1, 25);
+ if (space == -1)
+ /** This is a DM, just teleport on the top of player. */
+ space = 0;
+ object_remove(pl->ob);
+ object_insert_in_map_at(pl->ob, followed->ob->map, NULL, 0, followed->ob->x+freearr_x[space], followed->ob->y+freearr_y[space]);
+ map_newmap_cmd(&pl->socket);
+ player_update_bg_music(pl->ob);
+ }
+ } else {
+ if (!can_follow(pl->ob, followed)) {
+ draw_ext_info_format(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
+ "%s stops letting you follow them.", pl->followed_player);
+ FREE_AND_CLEAR_STR(pl->followed_player);
+ return;
+ }
+ int can_move = move_towards(pl->ob, followed->ob, 1);
+ if (can_move == 0 && followed->ob->contr->last_exit != NULL) {
+ // Move to and apply exit
+ object* exit = followed->ob->contr->last_exit;
+ if (!object_on_exit(pl->ob, exit)) {
+ move_towards(pl->ob, exit, 0);
+ } else {
+ enter_exit(pl->ob, exit);
+ }
+ }
+ }
+ } else {
+ draw_ext_info_format(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
+ "You stop following %s.", pl->followed_player);
+ FREE_AND_CLEAR_STR(pl->followed_player);
+ }
}
/**
@@ -863,27 +936,9 @@ static void process_players1(void) {
*/
if (!flag) pl->ticks_played++;
- /** Handle DM follow command */
if (pl->followed_player) {
- player *followed = find_player_partial_name(pl->followed_player);
- if (followed && followed->ob && followed->ob->map) {
- rv_vector rv;
-
- if (!get_rangevector(pl->ob, followed->ob, &rv, 0) || rv.distance > 4) {
- int space = object_find_free_spot(pl->ob, followed->ob->map, followed->ob->x, followed->ob->y, 1, 25);
- if (space == -1)
- /** This is a DM, just teleport on the top of player. */
- space = 0;
- object_remove(pl->ob);
- object_insert_in_map_at(pl->ob, followed->ob->map, NULL, 0, followed->ob->x+freearr_x[space], followed->ob->y+freearr_y[space]);
- map_newmap_cmd(&pl->socket);
- player_update_bg_music(pl->ob);
- }
- } else {
- draw_ext_info_format(NDI_UNIQUE, 0, pl->ob, MSG_TYPE_ADMIN, MSG_TYPE_ADMIN_DM, "Player %s left or ambiguous name.", pl->followed_player);
- FREE_AND_CLEAR_STR(pl->followed_player);
- }
- } /** End of follow */
+ do_follow(pl);
+ }
if (pl->ob->speed_left > 0) {
if (handle_newcs_player(pl->ob))
--
2.31.1
From 47c494367f10f14a9c8714b37c62527e108aa32e Mon Sep 17 00:00:00 2001
From: Kevin Zheng <[email protected]>
Date: Sat, 29 May 2021 14:57:03 -0700
Subject: [PATCH 2/3] Follow followed player through unique exits
---
server/server.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/server/server.c b/server/server.c
index 9aa6a402..41249e5d 100644
--- a/server/server.c
+++ b/server/server.c
@@ -600,9 +600,13 @@ static void enter_random_template_map(object *pl, object *exit_ob) {
static void enter_unique_map(object *op, object *exit_ob) {
char apartment[HUGE_BUF], path[MAX_BUF];
mapstruct *newmap;
+ const char* player = op->name;
+ if (op->contr->followed_player) {
+ player = op->contr->followed_player;
+ }
if (EXIT_PATH(exit_ob)[0] == '/') {
- snprintf(apartment, sizeof(apartment), "~%s/%s", op->name, clean_path(EXIT_PATH(exit_ob), path, sizeof(path)));
+ snprintf(apartment, sizeof(apartment), "~%s/%s", player, clean_path(EXIT_PATH(exit_ob), path, sizeof(path)));
newmap = ready_map_name(apartment, MAP_PLAYER_UNIQUE);
if (!newmap) {
newmap = mapfile_load(EXIT_PATH(exit_ob), 0);
@@ -638,7 +642,7 @@ static void enter_unique_map(object *op, object *exit_ob) {
* use the basic logic - don't need to demangle the path name
*/
path_combine_and_normalize(exit_ob->map->path, EXIT_PATH(exit_ob), reldir, sizeof(reldir));
- snprintf(apartment, sizeof(apartment), "~%s/%s", op->name, clean_path(reldir, path, sizeof(path)));
+ snprintf(apartment, sizeof(apartment), "~%s/%s", player, clean_path(reldir, path, sizeof(path)));
newmap = ready_map_name(apartment, MAP_PLAYER_UNIQUE);
if (!newmap) {
path_combine_and_normalize(exit_ob->map->path, EXIT_PATH(exit_ob), reldir, sizeof(reldir));
--
2.31.1
From b8cff044bb4b70eed2803b0e38116a57cd8555e7 Mon Sep 17 00:00:00 2001
From: Kevin Zheng <[email protected]>
Date: Sat, 29 May 2021 21:00:07 -0700
Subject: [PATCH 3/3] Update 'follow' command help
---
lib/help/follow.en | 5 +++++
lib/wizhelp/follow.en | 3 ---
2 files changed, 5 insertions(+), 3 deletions(-)
create mode 100644 lib/help/follow.en
delete mode 100644 lib/wizhelp/follow.en
diff --git a/lib/help/follow.en b/lib/help/follow.en
new file mode 100644
index 00000000..0c71f254
--- /dev/null
+++ b/lib/help/follow.en
@@ -0,0 +1,5 @@
+syntax: follow <player name>
+
+Follow another player, including through exits and into permanent apartments. The player you are following must be in the same party as you. You can only start following another player if they are adjacent to you. To stop following, type 'follow' without any arguments.
+
+The 'follow' command can be used with a willing party member to visit their permanent apartments. To prevent party members from following you, you must leave the party.
diff --git a/lib/wizhelp/follow.en b/lib/wizhelp/follow.en
deleted file mode 100644
index c272c0b3..00000000
--- a/lib/wizhelp/follow.en
+++ /dev/null
@@ -1,3 +0,0 @@
-syntax: follow <playername>
-
-This command allows a DM to follow a specified player and keeps the DM at four (4) or fewer squares away from a player (works better when hidden). To end following, just type the command 'follow' again.
--
2.31.1
_______________________________________________
crossfire mailing list
[email protected]
http://mailman.metalforge.org/mailman/listinfo/crossfire