Jump to content

Live map cloning feature


Tokei

Recommended Posts


  • Group:  Members
  • Topic Count:  16
  • Topics Per Day:  0.00
  • Content Count:  659
  • Reputation:   664
  • Joined:  11/12/12
  • Last Seen:  

This feature allows you to clone maps ingame without having to reboot your server or do any changes client-side. The cloned maps will behave just like any other regular map, except players will not be able to save in them. If they log out, they will return to their spawn point. The commands:

@clonemap source_map new_map
@delmap new_map
@clonemaplist

This is useful if want to make an event on a map but there's an annoying NPC in the way (and you don't want to reboot your server just for that!). Another big upside being you don't need to send a patch for that either, the new map will be added 'live'. You can load scripts on these maps as well (they will be removed upon deleting the map).

Usage example:
@clonemap prontera newmap
@addwarp newmap 150 150 test_portal
(@unloadnpc test_portal)

Have fun!

These maps will be lost when rebooting the server.

diff --git a/src/map/atcommand.c b/src/map/atcommand.c
index d4b329c..25e4c8b 100644
--- a/src/map/atcommand.c
+++ b/src/map/atcommand.c
@@ -799,6 +799,11 @@ ACMD_FUNC(save)
 {
 	nullpo_retr(-1, sd);
 
+	if( map[sd->bl.m].clone_id ) {
+		clif_displaymessage(fd, "You cannot create a savepoint in this map.");
+		return 1;
+	}
+
 	if( map[sd->bl.m].instance_id ) {
 		clif_displaymessage(fd, msg_txt(sd,383)); // You cannot create a savepoint in an instance.
 		return 1;
@@ -5945,6 +5950,11 @@ ACMD_FUNC(autotrade) {
 		return -1;
 	}
 
+	if (map[sd->bl.m].clone_id) {
+		clif_displaymessage(fd, "You cannot use autotrade on cloned maps.");
+		return -1;
+	}
+
 	sd->state.autotrade = 1;
 	if (battle_config.autotrade_monsterignore)
 		sd->state.monster_ignore = 1;
@@ -9937,6 +9947,111 @@ ACMD_FUNC(adopt)
 	return -1;
 }
 
+/**
+ * Clones an existing map without having to reboot your server.
+ * Usage: @clonemap <source_map_name> <new_map_name>
+ * @author Tokeiburu
+ */
+ACMD_FUNC(clonemap)
+{
+	int res;
+	char map_name_cur[MAP_NAME_LENGTH_EXT];
+	char map_name_new[MAP_NAME_LENGTH_EXT];
+
+	memset(map_name_cur, '\0', sizeof(map_name_cur));
+	memset(map_name_new, '\0', sizeof(map_name_new));
+
+	if (!message || !*message || sscanf(message, "%15s %15s", map_name_cur, map_name_new) < 2) {
+		clif_displaymessage(fd, "Usage: @clonemap <source_map_name> <new_map_name>");
+		return -1;
+	}
+
+	if (map_mapname2mapid(map_name_cur) < 0) {
+		sprintf(atcmd_output, "Source map not found: %s", map_name_cur);
+		clif_displaymessage(fd, atcmd_output);
+		return -1;
+	}
+
+	if (map_mapname2mapid(map_name_new) >= 0) { // This will always show a warning in the console, but removing it is not worth the trouble.
+		sprintf(atcmd_output, "Destination map already exists: %s", map_name_new);
+		clif_displaymessage(fd, atcmd_output);
+		return -1;
+	}
+
+	if ((res = map_addclonemap(map_name_cur, map_name_new)) < 0) {
+		sprintf(atcmd_output, "Failed to create a new map, error: %d", res);
+		clif_displaymessage(fd, atcmd_output);
+		return -1;
+	}
+
+	sprintf(atcmd_output, "New map cloned (%s).", map_name_new);
+	clif_displaymessage(fd, atcmd_output);
+	return 0;
+}
+
+/**
+ * Removes a cloned map.
+ * Usage: @delmap <map_name>
+ * @author Tokeiburu
+ */
+ACMD_FUNC(delmap)
+{
+	int res;
+	int m;
+	char map_name[MAP_NAME_LENGTH_EXT];
+
+	memset(map_name, '\0', sizeof(map_name));
+
+	if (!message || !*message || sscanf(message, "%15s", map_name) < 1) {
+		clif_displaymessage(fd, "Usage: @delmap <map_name>");
+		return -1;
+	}
+
+	if ((m = map_mapname2mapid(map_name)) < 0) {
+		sprintf(atcmd_output, "Map not found: %s", map_name);
+		clif_displaymessage(fd, atcmd_output);
+		return -1;
+	}
+
+	if (map[m].clone_id == 0) {
+		clif_displaymessage(fd, "Only cloned maps can be removed.");
+		return -1;
+	}
+
+	if ((res = map_delclonemap(map_name)) != 1) {
+		sprintf(atcmd_output, "Failed to remove map: %s", map_name);
+		clif_displaymessage(fd, atcmd_output);
+		return -1;
+	}
+
+	sprintf(atcmd_output, "Cloned map removed (%s).", map_name);
+	clif_displaymessage(fd, atcmd_output);
+	return 0;
+}
+
+/**
+ * Lists all cloned maps.
+ * Usage: @clonemaplist
+ * @author Tokeiburu
+ */
+ACMD_FUNC(clonemaplist)
+{
+	int i;
+	int count = 0;
+
+	for (i = instance_start; i < MAX_MAP_PER_SERVER; i++) {
+		if (map[i].clone_id > 0) {
+			sprintf(atcmd_output, "%s (source: %s)", map[i].name, map[map[i].clone_id].name);
+			clif_displaymessage(fd, atcmd_output);
+			count++;
+		}
+	}
+
+	sprintf(atcmd_output, "Found %d cloned map(s).", count);
+	clif_displaymessage(fd, atcmd_output);
+	return 0;
+}
+
 #include "../custom/atcommand.inc"
 
 /**
@@ -10234,6 +10349,10 @@ void atcommand_basecommands(void) {
 		ACMD_DEF(adopt),
 		ACMD_DEF(agitstart3),
 		ACMD_DEF(agitend3),
+
+		ACMD_DEF(clonemap),
+		ACMD_DEF(delmap),
+		ACMD_DEF(clonemaplist),
 	};
 	AtCommandInfo* atcommand;
 	int i;
diff --git a/src/map/chrif.c b/src/map/chrif.c
index 4276434..cea689f 100644
--- a/src/map/chrif.c
+++ b/src/map/chrif.c
@@ -336,7 +336,7 @@ int chrif_save(struct map_session_data *sd, enum e_chrif_save_opt flag) {
 	WFIFOB(char_fd,12) = (flag&CSAVE_QUIT) ? 1 : 0; //Flag to tell char-server this character is quitting.
 
 	// If the user is on a instance map, we have to fake his current position
-	if( map[sd->bl.m].instance_id ){
+	if( map[sd->bl.m].instance_id || map[sd->bl.m].clone_id ){
 		struct mmo_charstatus status;
 
 		// Copy the whole status
diff --git a/src/map/clif.c b/src/map/clif.c
index 6c77044..97d8864 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -10352,7 +10352,7 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd)
 #endif
 
 		// Instances do not need their own channels
-		if( channel_config.map_enable && channel_config.map_autojoin && !map[sd->bl.m].flag.chmautojoin && !map[sd->bl.m].instance_id )
+		if( channel_config.map_enable && channel_config.map_autojoin && !map[sd->bl.m].flag.chmautojoin && !map[sd->bl.m].instance_id && !map[sd->bl.m].clone_id )
 			channel_mjoin(sd); //join new map
 	} else if (sd->guild && (battle_config.guild_notice_changemap == 2 || guild_notice))
 		clif_guild_notice(sd); // Displays at end
diff --git a/src/map/map.c b/src/map/map.c
index c7e326b..37d65f1 100644
--- a/src/map/map.c
+++ b/src/map/map.c
@@ -2095,7 +2095,7 @@ int map_quit(struct map_session_data *sd) {
 
 	unit_remove_map_pc(sd,CLR_RESPAWN);
 
-	if( map[sd->bl.m].instance_id ) { // Avoid map conflicts and warnings on next login
+	if( map[sd->bl.m].instance_id || map[sd->bl.m].clone_id ) { // Avoid map conflicts and warnings on next login
 		int16 m;
 		struct point *pt;
 		if( map[sd->bl.m].save.map )
@@ -2728,6 +2728,133 @@ int map_delinstancemap(int m)
 	return 1;
 }
 
+/*==========================================
+ * Add a cloned map
+ *------------------------------------------*/
+int map_addclonemap(const char *name, const char *newname)
+{
+	int src_m = map_mapname2mapid(name);
+	int dst_m = -1, i;
+	size_t num_cell, size;
+
+	if(src_m < 0)
+		return -1;
+
+	if(strlen(name) >= MAP_NAME_LENGTH) {
+		// against buffer overflow
+		ShowError("map_addclonemap: can't add long map name \"%s\"\n", name);
+		return -2;
+	}
+
+	if(strlen(newname) >= MAP_NAME_LENGTH) {
+		// against buffer overflow
+		ShowError("map_addclonemap: can't add long map name \"%s\"\n", newname);
+		return -2;
+	}
+
+	for(i = instance_start; i < MAX_MAP_PER_SERVER; i++) {
+		if(!map[i].name[0])
+			break;
+	}
+	if(i < map_num) // Destination map value overwrites another
+		dst_m = i;
+	else if(i < MAX_MAP_PER_SERVER) // Destination map value increments to new map
+		dst_m = map_num++;
+	else {
+		// Out of bounds
+		ShowError("map_addclonemap failed. map_num(%d) > map_max(%d)\n",map_num, MAX_MAP_PER_SERVER);
+		return -3;
+	}
+
+	if (map[src_m].clone_id) {
+		ShowError("map_addclonemap failed. Cannot clone a cloned map.\n",map_num, MAX_MAP_PER_SERVER);
+		return -4;
+	}
+
+	// Copy the map
+	memcpy(&map[dst_m], &map[src_m], sizeof(struct map_data));
+
+	// Alter the name
+	// Due to this being custom we only worry about preserving as many characters as necessary for accurate map distinguishing
+	// This also allows us to maintain complete independence with main map functions
+	strncpy(map[dst_m].name,newname,MAP_NAME_LENGTH);
+
+	map[dst_m].m = dst_m;
+	map[dst_m].clone_id = src_m;
+	map[dst_m].users = 0;
+
+	memset(map[dst_m].npc, 0, sizeof(map[dst_m].npc));
+	map[dst_m].npc_num = 0;
+	
+	// Reallocate cells
+	num_cell = map[dst_m].xs * map[dst_m].ys;
+	CREATE( map[dst_m].cell, struct mapcell, num_cell );
+	memcpy( map[dst_m].cell, map[src_m].cell, num_cell * sizeof(struct mapcell) );
+
+	// Remove ontouch NPC cells
+	for (i = 0; i < num_cell; i++) {
+		map[dst_m].cell[i].npc = 0;
+	}
+
+	// Remove all mob spawners (these should not be copied over)
+	for (i = 0; i < MAX_MOB_LIST_PER_MAP; i++) {
+		map[dst_m].moblist[i] = NULL;
+	}
+
+	// Do not copy PVP nightmare drops
+	memset(map[dst_m].drop_list, 0, sizeof(map[dst_m].drop_list));
+
+	// Remove quest information from the map
+	map[dst_m].qi_data = NULL;
+
+	size = map[dst_m].bxs * map[dst_m].bys * sizeof(struct block_list*);
+	map[dst_m].block = (struct block_list **)aCalloc(1,size);
+	map[dst_m].block_mob = (struct block_list **)aCalloc(1,size);
+
+	map[dst_m].index = mapindex_addmap(-1, map[dst_m].name);
+	map[dst_m].channel = NULL;
+	map[dst_m].mob_delete_timer = INVALID_TIMER;
+
+	map_addmap2db(&map[dst_m]);
+
+	return dst_m;
+}
+
+/*==========================================
+ * Deleting a cloned map
+ *------------------------------------------*/
+int map_delclonemap(const char* mapname)
+{
+	int m = map_mapname2mapid(mapname);
+
+	if(m < 0 || map[m].instance_id || map[m].clone_id <= 0)
+		return 0;
+
+	// Kick everyone out
+	map_foreachinmap(map_instancemap_leave, m, BL_PC);
+
+	// Do the unit cleanup
+	map_foreachinmap(map_instancemap_clean, m, BL_ALL);
+
+	if( map[m].mob_delete_timer != INVALID_TIMER )
+		delete_timer(map[m].mob_delete_timer, map_removemobs_timer);
+
+	// Free memory
+	aFree(map[m].cell);
+	map[m].cell = NULL;
+	aFree(map[m].block);
+	map[m].block = NULL;
+	aFree(map[m].block_mob);
+	map[m].block_mob = NULL;
+	map_free_questinfo(m);
+
+	mapindex_removemap( map[m].index );
+	map_removemapdb(&map[m]);
+	memset(&map[m], 0x00, sizeof(map[0]));
+	map[m].mob_delete_timer = INVALID_TIMER;
+	return 1;
+}
+
 /*=========================================
  * Dynamic Mobs [Wizputer]
  *-----------------------------------------*/
@@ -2838,6 +2965,10 @@ void map_removemobs(int16 m)
  *------------------------------------------*/
 const char* map_mapid2mapname(int m)
 {
+	if (map[m].clone_id) { // Clone map check
+		return map[map[m].clone_id].name;
+	}
+
 	if (map[m].instance_id) { // Instance map check
 		struct instance_data *im = &instance_data[map[m].instance_id];
 
diff --git a/src/map/map.h b/src/map/map.h
index ec0b2b0..762ab2d 100644
--- a/src/map/map.h
+++ b/src/map/map.h
@@ -705,6 +705,9 @@ struct map_data {
 	unsigned short instance_id;
 	int instance_src_map;
 
+	// Clone map system
+	unsigned short clone_id;
+
 	/* rAthena Local Chat */
 	struct Channel *channel;
 
@@ -820,6 +823,10 @@ int map_addflooritem(struct item *item, int amount, int16 m, int16 x, int16 y, i
 int map_addinstancemap(const char *name, unsigned short instance_id);
 int map_delinstancemap(int m);
 
+// Clone map system
+int map_delclonemap(const char* mapname);
+int map_addclonemap(const char *name, const char *newname);
+
 // player to map session
 void map_addnickdb(int charid, const char* nick);
 void map_delnickdb(int charid, const char* nick);
diff --git a/src/map/pc.c b/src/map/pc.c
index 66a42f4..07d395b 100755
--- a/src/map/pc.c
+++ b/src/map/pc.c
@@ -5599,6 +5599,11 @@ bool pc_memo(struct map_session_data* sd, int pos)
 		pos = 0;
 	}
 
+	if( map[sd->bl.m].clone_id ) {
+		clif_displaymessage( sd->fd, "You cannot create a memo in this map." );
+		return false;
+	}
+
 	if( map[sd->bl.m].instance_id ) {
 		clif_displaymessage( sd->fd, msg_txt(sd,384) ); // You cannot create a memo in an instance.
 		return false;
diff --git a/src/map/script.c b/src/map/script.c
index f53b799..9f59068 100644
--- a/src/map/script.c
+++ b/src/map/script.c
@@ -9607,6 +9607,11 @@ BUILDIN_FUNC(savepoint)
 	y = script_getnum(st,4);
 	m = map_mapindex2mapid(map_idx);
 
+	if( map[m].clone_id ) {
+		ShowError("buildin_savepoint: You cannot create a savepoint on this map %s.\n", str);
+		return SCRIPT_CMD_FAILURE;
+	}
+
 	if (cid_pos == 7) {
 		int dx = script_getnum(st,5), dy = script_getnum(st,6),
 			x1 = x + dx, y1 = y + dy,

 

 

clonemap.diff

Edited by Tokei
  • Upvote 11
  • Love 2
Link to comment
Share on other sites


  • Group:  Forum Moderator
  • Topic Count:  33
  • Topics Per Day:  0.01
  • Content Count:  1268
  • Reputation:   382
  • Joined:  02/03/12
  • Last Seen:  

Another awesome contribution thank you very much @Tokei I'll be using this one soon.

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  162
  • Topics Per Day:  0.04
  • Content Count:  740
  • Reputation:   47
  • Joined:  03/12/14
  • Last Seen:  

nice i will test this later

Edited by Bringer
Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  162
  • Topics Per Day:  0.05
  • Content Count:  1546
  • Reputation:   192
  • Joined:  07/23/14
  • Last Seen:  

Nice release @Tokei ! thank you so much for this..

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  22
  • Topics Per Day:  0.00
  • Content Count:  235
  • Reputation:   55
  • Joined:  12/02/11
  • Last Seen:  

@Tokei

Thank you! for awesome contribution this is really neat.

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  4
  • Topics Per Day:  0.00
  • Content Count:  303
  • Reputation:   117
  • Joined:  12/10/16
  • Last Seen:  

Your contributiones are awesome.
Thank you very much.

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  45
  • Topics Per Day:  0.01
  • Content Count:  140
  • Reputation:   11
  • Joined:  09/28/14
  • Last Seen:  

Thanks for this @Tokei great release! 

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  27
  • Topics Per Day:  0.01
  • Content Count:  505
  • Reputation:   126
  • Joined:  04/04/16
  • Last Seen:  

This is awesome! Thanks @Tokei

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  162
  • Topics Per Day:  0.05
  • Content Count:  1546
  • Reputation:   192
  • Joined:  07/23/14
  • Last Seen:  

@Tokei I received this warning

map.c: In function map_addclonemap’:
map.c:2784:2: warning: format not a string literal and no format arguments [-Wformat-security]
  snprintf(map[dst_m].name, sizeof(map[dst_m].name), newname);
  ^
map.c:2784:2: warning: format not a string literal and no format arguments [-Wformat-security]

and it was pointing on this part

snprintf(map[dst_m].name, sizeof(map[dst_m].name), newname);

 

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  16
  • Topics Per Day:  0.00
  • Content Count:  659
  • Reputation:   664
  • Joined:  11/12/12
  • Last Seen:  

7 hours ago, Radian said:

@Tokei I received this warning


map.c: In function map_addclonemap’:
map.c:2784:2: warning: format not a string literal and no format arguments [-Wformat-security]
  snprintf(map[dst_m].name, sizeof(map[dst_m].name), newname);
  ^
map.c:2784:2: warning: format not a string literal and no format arguments [-Wformat-security]

and it was pointing on this part


snprintf(map[dst_m].name, sizeof(map[dst_m].name), newname);

 

Updated the diff, I'm surprised Visual Studio didn't have any issue with this line whatsoever.

  • Upvote 1
Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  162
  • Topics Per Day:  0.05
  • Content Count:  1546
  • Reputation:   192
  • Joined:  07/23/14
  • Last Seen:  

On 3/12/2017 at 0:20 PM, Tokei said:

Updated the diff, I'm surprised Visual Studio didn't have any issue with this line whatsoever.

Works now thank you!

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  14
  • Topics Per Day:  0.00
  • Content Count:  265
  • Reputation:   95
  • Joined:  09/30/14
  • Last Seen:  

Tokei is the king of RO.

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  22
  • Topics Per Day:  0.00
  • Content Count:  235
  • Reputation:   55
  • Joined:  12/02/11
  • Last Seen:  

On 3/12/2017 at 11:20 AM, Tokei said:

Updated the diff, I'm surprised Visual Studio didn't have any issue with this line whatsoever.

@Tokei just wondering is this usually normal? it appears on my console everytime i make clone maps.

I must say this is really unique! no need to touched the client-side anymore! Good for EVENT!
 

Capture.PNG

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  16
  • Topics Per Day:  0.00
  • Content Count:  659
  • Reputation:   664
  • Joined:  11/12/12
  • Last Seen:  

2 minutes ago, Kaze said:

@Tokei just wondering is this usually normal? it appears on my console everytime i make clone maps.

I must say this is really unique! no need to touched the client-side anymore! Good for EVENT!
 

Capture.PNG

Yep, it's normal xD. It's from this line:

	if (map_mapname2mapid(map_name_new) >= 0) { // This will always show a warning in the console, but removing it is not worth the trouble.
		sprintf(atcmd_output, "Destination map already exists: %s", map_name_new);
		clif_displaymessage(fd, atcmd_output);
		return -1;
	}

It simply checks if the map already exists and prevents you from copying on top of an already existing one. Since the map currently doesn't exist, it gives you a warning but it's what we're looking for. It's like when you use... "@warp unkonwnmap", which also gives you that warning. (Not something worth fixing ;P)

Link to comment
Share on other sites

  • 4 months later...

  • Group:  Members
  • Topic Count:  1
  • Topics Per Day:  0.00
  • Content Count:  13
  • Reputation:   1
  • Joined:  04/26/12
  • Last Seen:  

GJ!

I'll have to try this out when I can!


With a small modification it could be used to instance crowded maps on overpopulated servers - like @instance to list instances of the map or open a new instance of the map you're currently on, or the same but with some trigger

or with some more to create a GW style party system (I don't like that system, but there are people who do)

Link to comment
Share on other sites

  • 2 years later...

  • Group:  Members
  • Topic Count:  53
  • Topics Per Day:  0.01
  • Content Count:  411
  • Reputation:   261
  • Joined:  04/25/12
  • Last Seen:  

Actually, this is removing instance maps when deleting. If you clone some maps and so delete. When you enter in some instances map are black.

Link to comment
Share on other sites

  • 2 months later...

  • Group:  Members
  • Topic Count:  1
  • Topics Per Day:  0.00
  • Content Count:  8
  • Reputation:   1
  • Joined:  11/11/17
  • Last Seen:  

Can someone update this script please?

Link to comment
Share on other sites

  • 2 months later...

  • Group:  Members
  • Topic Count:  8
  • Topics Per Day:  0.00
  • Content Count:  62
  • Reputation:   2
  • Joined:  01/16/12
  • Last Seen:  

has error to patch this files ?

Link to comment
Share on other sites

  • 2 years later...

  • Group:  Members
  • Topic Count:  27
  • Topics Per Day:  0.01
  • Content Count:  159
  • Reputation:   26
  • Joined:  11/20/11
  • Last Seen:  

what a feature i hope there will be another update for this...

 

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  25
  • Topics Per Day:  0.01
  • Content Count:  283
  • Reputation:   76
  • Joined:  06/13/13
  • Last Seen:  

with rAthena commit ac7292c instance system can do this just set the owner instance to IM_NONE and set the time limit of the instance to unlimited in the instance_db.yml

sample entry in instance_db.yml.

  - Id: 777
    Name: Payon Clone
    TimeLimit: 0
    IdleTimeOut: 0
    Enter:
      Map: payon
      X: 150
      Y: 150

on the script for instance creation

.@instance_name$ = "Prontera Clone";
.@instance_mode  = IM_NONE;
instance_create(.@instance_name$, .@instance_mode);

 

  • Upvote 1
  • Love 1
Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  27
  • Topics Per Day:  0.01
  • Content Count:  159
  • Reputation:   26
  • Joined:  11/20/11
  • Last Seen:  

On 10/10/2022 at 7:09 AM, Litro Endemic said:

with rAthena commit ac7292c instance system can do this just set the owner instance to IM_NONE and set the time limit of the instance to unlimited in the instance_db.yml

sample entry in instance_db.yml.

  - Id: 777
    Name: Payon Clone
    TimeLimit: 0
    IdleTimeOut: 0
    Enter:
      Map: payon
      X: 150
      Y: 150

on the script for instance creation

.@instance_name$ = "Prontera Clone";
.@instance_mode  = IM_NONE;
instance_create(.@instance_name$, .@instance_mode);

 

thank you for this ? I will test it now

thanks for this again ? very helpful haha

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...