conf/battle/player.conf | 10 ++++
src/map/atcommand.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++
src/map/battle.c | 8 +++
src/map/battle.h | 4 ++
src/map/chat.c | 18 ++++++
src/map/chat.h | 2 +
src/map/clif.c | 4 +-
src/map/map.c | 3 +
src/map/mob.c | 42 ++++++++++++++
src/map/mob.h | 5 ++
src/map/pc.h | 2 +
src/map/script.c | 8 +++
src/map/skill.c | 4 ++
13 files changed, 257 insertions(+), 1 deletion(-)
diff --git a/conf/battle/player.conf b/conf/battle/player.conf
index cdfcece..974fc65 100644
--- a/conf/battle/player.conf
+++ b/conf/battle/player.conf
@@ -168,3 +168,13 @@ snovice_call_type: 0
// Be mindful that the more options used, the easier it becomes to cheat features that rely on idletime (e.g. checkidle()).
// Default: walk ( 0x1 ) + useskilltoid ( 0x2 ) + useskilltopos ( 0x4 ) + useitem ( 0x8 ) + attack ( 0x10 ) = 0x1F
idletime_criteria: 0x1F
+
+// how many seconds delay from creating another market clone ? (default: 10)
+// Note: if you set it too low like 1 second, player might spawn the clone all over the town, unit->free doesn't act fast enough to remove them, 10 seconds is ideal
+market_clone_delay: 10
+
+// the color message when clicking on the market clone's chat room (default: PINK ! hahaha)
+market_msg_color: 0xFFC0CB
+
+// the zeny cost for creating a market clone
+market_clone_zenycost: 0
\ No newline at end of file
diff --git a/src/map/atcommand.c b/src/map/atcommand.c
index 497e465..e061fc4 100644
--- a/src/map/atcommand.c
+++ b/src/map/atcommand.c
@@ -1958,6 +1958,8 @@ int atkillmonster_sub(struct block_list *bl, va_list ap)
nullpo_ret(md=(struct mob_data *)bl);
flag = va_arg(ap, int);
+ if ( md->market_chat_id )
+ return 0;
if (md->guardian_data)
return 0; //Do not touch WoE mobs!
@@ -3636,6 +3638,11 @@ void atcommand_raise_sub(struct map_session_data* sd) {
if (pl_sd->st && pl_sd->st->state != END)
pl_sd->st->state = END;
}
+ if ( pl_sd->market_clone_id ) {
+ status_kill( map->id2bl( pl_sd->market_clone_id ) );
+ pl_sd->market_clone_id = 0;
+ pl_sd->market_clone_delay = (int)time(NULL);
+ }
}
mapit->free(iter);
@@ -9372,6 +9379,145 @@ static inline void atcmd_channel_help(int fd, const char *command, bool can_crea
clif->message(fd,atcmd_output);
return true;
}
+
+ACMD(market){
+ char title[CHAT_SIZE_MAX], msg[CHAT_SIZE_MAX];
+ if ( sd->market_clone_id ) {
+ clif->message( fd, "You already have a Market clone summoned. Type '@marketkill' to remove that clone.");
+ return false;
+ }
+ if ( sd->market_clone_delay + battle_config.market_clone_delay > (int)time(NULL) ) {
+ safesnprintf( atcmd_output, CHAT_SIZE_MAX, "You must wait %d seconds before using this command again.", sd->market_clone_delay + battle_config.market_clone_delay - (int)time(NULL) );
+ clif->message( fd, atcmd_output );
+ return false;
+ }
+ if ( !map->list[sd->bl.m].flag.town ) {
+ clif->message( fd, "You can only use @market in a town.");
+ return false;
+ }
+ if ( map->getcell( sd->bl.m, sd->bl.x, sd->bl.y, CELL_CHKNOCHAT ) ) {
+ clif->message( fd, "You cannot use @market in this area.");
+ return false;
+ }
+ if ( sd->status.zeny < battle_config.market_clone_zenycost ) {
+ safesnprintf( atcmd_output, CHAT_SIZE_MAX, "You must have at least %d zeny to have a Market clone.", battle_config.market_clone_zenycost );
+ clif->message( fd, atcmd_output );
+ return false;
+ }
+ if ( pc_isdead(sd) ) {
+ clif->message( fd, "You can't create a Market clone while you are dead." );
+ return false;
+ }
+ if ( sd->chatID ) {
+ clif->message( fd, "You can't create a Market clone while you already open a chatroom." );
+ return false;
+ }
+ if ( sd->state.vending ) {
+ clif->message( fd, "You can't create a Market clone while you are vending." );
+ return false;
+ }
+ if ( npc->isnear( &sd->bl ) ) {
+ clif->message( fd, "You can't create a Market clone too near to an npc." );
+ return false;
+ }
+ if ( !message || !*message ) {
+ clif->message( fd, "Syntax: @market \"<Title>\" \"<Message>\"");
+ return false;
+ }
+// if ( sscanf( message, "\"%[^\"]\" \"%[^\"]\"", title, msg ) < 2 ) {
+// clif->message( fd, "Remember the Quotation Mark -> \"");
+// clif->message( fd, "Syntax: @market \"<Title>\" \"<Message>\"");
+// return false;
+// } no more f*cking sscanf
+ { // say hello to the dirties string calculation ~ Hooray ~ !!
+ int i = 0, j = 0, l = strlen( message ) +1;
+ char *temp = (char*)aMalloc( strlen( message ) +1 );
+ if ( message[0] != '\"' ) {
+ clif->message( fd, "Remember the <Title> should start with a Quotation Mark -> \"");
+ clif->message( fd, "Syntax: @market \"<Title>\" \"<Message>\"");
+ return false;
+ }
+ i = 1;
+ while ( i <= l ) {
+ if ( message[i] == '\"' || message[i] == '\0' )
+ break;
+ else
+ temp[j++] = message[i];
+ i++;
+ }
+ if ( message[i] != '\"' ) {
+ clif->message( fd, "Remember the <Title> should end with a Quotation Mark -> \"");
+ clif->message( fd, "Syntax: @market \"<Title>\" \"<Message>\"");
+ return false;
+ }
+ temp[j] = '\0';
+ safestrncpy( title, temp, CHAT_SIZE_MAX );
+ i++;
+ if ( message[i] != ' ' ) {
+ clif->message( fd, "Remember the [Space] between the <Title> and <Message>.");
+ clif->message( fd, "Syntax: @market \"<Title>\" \"<Message>\"");
+ return false;
+ }
+ i++;
+ if ( message[i] != '\"' ) {
+ clif->message( fd, "Remember the <Message> should start with a Quotation Mark -> \"");
+ clif->message( fd, "Syntax: @market \"<Title>\" \"<Message>\"");
+ return false;
+ }
+ i++;
+ j = 0;
+ while ( i <= l ) {
+ if ( message[i] == '\"' || message[i] == '\0' )
+ break;
+ else
+ temp[j++] = message[i];
+ i++;
+ }
+ if ( message[i] != '\"' ) {
+ clif->message( fd, "Remember the <Message> should end with a Quotation Mark -> \"");
+ clif->message( fd, "Syntax: @market \"<Title>\" \"<Message>\"");
+ return false;
+ }
+ temp[j] = '\0';
+ safestrncpy( msg, temp, CHAT_SIZE_MAX );
+ aFree( temp );
+ }
+ if ( strlen( title ) < 4 ) {
+ clif->message( fd, "The Title must more than 4 characters." );
+ return false;
+ }
+ if ( strlen( title ) >= CHATROOM_TITLE_SIZE ) {
+ safesnprintf( atcmd_output, CHAT_SIZE_MAX, "The Title must not more than %d characters.", CHATROOM_TITLE_SIZE );
+ clif->message( fd, atcmd_output );
+ return false;
+ }
+ if ( strlen( msg ) < 4 ) {
+ clif->message( fd, "The Message must more than 4 characters." );
+ return false;
+ }
+ sd->market_clone_id = mob->clone_spawn_market( sd, sd->bl.m, sd->bl.x, sd->bl.y, title, msg );
+ if ( !sd->market_clone_id ) {
+ clif->message( fd, "Market clone Cannot be Created." );
+ return false;
+ }
+ if ( battle_config.market_clone_zenycost )
+ pc->payzeny( sd, battle_config.market_clone_zenycost, LOG_TYPE_COMMAND, NULL );
+ clif->message( fd, "Market clone Created." );
+ return true;
+}
+
+ACMD(marketkill){
+ if ( !sd->market_clone_id ) {
+ clif->message( fd, "You don't have a market clone yet. Type '@market \"<title>\" \"<message>\"' to create one.");
+ return false;
+ }
+ status_kill( map->id2bl( sd->market_clone_id ) );
+ clif->message( fd, "Your market clone has removed." );
+ sd->market_clone_id = 0;
+ sd->market_clone_delay = (int)time(NULL);
+ return true;
+}
+
/**
* Fills the reference of available commands in atcommand DBMap
**/
@@ -9382,6 +9528,8 @@ void atcommand_basecommands(void) {
* Command reference list, place the base of your commands here
**/
AtCommandInfo atcommand_base[] = {
+ ACMD_DEF(market),
+ ACMD_DEF(marketkill),
ACMD_DEF2("warp", mapmove),
ACMD_DEF(where),
ACMD_DEF(jumpto),
diff --git a/src/map/battle.c b/src/map/battle.c
index c511c58..52554fb 100644
--- a/src/map/battle.c
+++ b/src/map/battle.c
@@ -6106,6 +6106,10 @@ int battle_check_target( struct block_list *src, struct block_list *target,int f
if( (s_bl = battle->get_master(src)) == NULL )
s_bl = src;
+ if ( target->type == BL_MOB )
+ if ( ((TBL_MOB*)target)->market_chat_id )
+ return -1;
+
if ( s_bl->type == BL_PC ) {
switch( t_bl->type ) {
case BL_MOB: // Source => PC, Target => MOB
@@ -6826,6 +6830,10 @@ bool battle_check_range(struct block_list *src, struct block_list *bl, int range
{ "guild_castle_expulsion", &battle_config.guild_castle_expulsion, 0, 0, 1, },
{ "song_timer_reset", &battle_config.song_timer_reset, 0, 0, 1, },
{ "snap_dodge", &battle_config.snap_dodge, 0, 0, 1, },
+
+ { "market_clone_delay", &battle_config.market_clone_delay, 0, 0, INT_MAX, },
+ { "market_msg_color", &battle_config.market_msg_color, 0xFFC0CB, 0, INT_MAX, },
+ { "market_clone_zenycost", &battle_config.market_clone_zenycost, 0, 0, MAX_ZENY, },
};
#ifndef STATS_OPT_OUT
/**
diff --git a/src/map/battle.h b/src/map/battle.h
index 734a618..3b756bf 100644
--- a/src/map/battle.h
+++ b/src/map/battle.h
@@ -477,6 +477,10 @@ struct Battle_Config {
int song_timer_reset; // [csnv]
int snap_dodge; // Enable or disable dodging damage snapping away [csnv]
+
+ int market_clone_delay;
+ int market_msg_color;
+ int market_clone_zenycost;
};
extern struct Battle_Config battle_config;
diff --git a/src/map/chat.c b/src/map/chat.c
index cd7b5f8..64034ee 100644
--- a/src/map/chat.c
+++ b/src/map/chat.c
@@ -125,6 +125,24 @@ bool chat_joinchat(struct map_session_data* sd, int chatid, const char* pass) {
return false;
}
+ if ( cd->owner->type == BL_MOB ) {
+ struct mob_data *md = (TBL_MOB*)cd->owner;
+ if ( md->market_chat_id ) {
+ short msg_len = 0;
+ char output[CHAT_SIZE_MAX];
+ safesnprintf( output, CHAT_SIZE_MAX, "%s : %s", md->name, md->market_message );
+ msg_len = strlen( output )+1;
+ WFIFOHEAD( sd->fd, msg_len + 12 );
+ WFIFOW( sd->fd, 0 ) = 0x2C1;
+ WFIFOW( sd->fd, 2 ) = msg_len + 12;
+ WFIFOL( sd->fd, 4 ) = 0;
+ WFIFOL( sd->fd, 8 ) = (battle_config.market_msg_color & 0x0000FF) << 16 | (battle_config.market_msg_color & 0x00FF00) | (battle_config.market_msg_color & 0xFF0000) >> 16;;
+ safestrncpy( (char*)WFIFOP( sd->fd,12 ), output, msg_len );
+ WFIFOSET( sd->fd, msg_len + 12 );
+ return true;
+ }
+ }
+
if( !cd->pub && strncmp(pass, cd->pass, sizeof(cd->pass)) != 0 && !pc_has_permission(sd, PC_PERM_JOIN_ALL_CHAT) )
{
clif->joinchatfail(sd,1); // wrong password
diff --git a/src/map/chat.h b/src/map/chat.h
index e055c04..1f6bf4d 100644
--- a/src/map/chat.h
+++ b/src/map/chat.h
@@ -6,6 +6,8 @@
#define MAP_CHAT_H
#include "map.h" // struct block_list, CHATROOM_TITLE_SIZE
+#include "mob.h"
+#include "../common/socket.h"
#include "../common/cbasetypes.h"
#include "../common/db.h"
diff --git a/src/map/clif.c b/src/map/clif.c
index f6ab3f4..a206501 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -4361,6 +4361,8 @@ void clif_getareachar_unit(struct map_session_data* sd,struct block_list *bl) {
}
}
#endif
+ if ( md->market_chat_id )
+ clif->dispchat( (struct chat_data*)map->id2bl( md->market_chat_id ), sd->fd );
}
break;
case BL_PET:
@@ -8609,7 +8611,7 @@ void clif_charnameack (int fd, struct block_list *bl)
memcpy(WBUFP(buf,54), md->guardian_data->g->name, NAME_LENGTH);
memcpy(WBUFP(buf,78), md->guardian_data->castle->castle_name, NAME_LENGTH);
}
- else if( battle_config.show_mob_info )
+ else if( battle_config.show_mob_info && !md->market_chat_id )
{
char mobhp[50], *str_p = mobhp;
WBUFW(buf, 0) = cmd = 0x195;
diff --git a/src/map/map.c b/src/map/map.c
index 14af392..b88ff9a 100644
--- a/src/map/map.c
+++ b/src/map/map.c
@@ -1670,6 +1670,9 @@ void map_deliddb(struct block_list *bl)
int map_quit(struct map_session_data *sd) {
int i;
+ if ( sd->market_clone_id )
+ status_kill( map->id2bl( sd->market_clone_id ) );
+
if(!sd->state.active) { //Removing a player that is not active.
struct auth_node *node = chrif->search(sd->status.account_id);
if (node && node->char_id == sd->status.char_id &&
diff --git a/src/map/mob.c b/src/map/mob.c
index ffab804..ec7cb64 100644
--- a/src/map/mob.c
+++ b/src/map/mob.c
@@ -3532,6 +3532,47 @@ int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, cons
return md->bl.id;
}
+int mob_clone_spawn_market( struct map_session_data *sd, int16 m, int16 x, int16 y, char market_title[], char market_msg[] ) { //Copy of mob_clone_spawn with some modification.
+ int class_;
+ struct mob_data *md;
+ struct mob_db* db;
+ struct status_data *mstatus;
+ struct chat_data* cd;
+
+ ARR_FIND( MOB_CLONE_START, MOB_CLONE_END, class_, mob->db_data[class_] == NULL );
+ if ( class_ >= MOB_CLONE_END )
+ return 0;
+
+ db = mob->db_data[class_] = (struct mob_db*)aCalloc( 1, sizeof(struct mob_db) );
+ mstatus = &db->status;
+ safestrncpy( db->sprite, sd->status.name, NAME_LENGTH );
+ safestrncpy( db->name, sd->status.name, NAME_LENGTH );
+ safestrncpy( db->jname, sd->status.name, NAME_LENGTH );
+ db->lv = status->get_lv(&sd->bl);
+ memcpy( mstatus, &sd->base_status, sizeof( struct status_data ) );
+ mstatus->rhw.atk = mstatus->rhw.atk2 = mstatus->lhw.atk = mstatus->lhw.atk2 = mstatus->hp = mstatus->max_hp = mstatus->sp = mstatus->max_sp = 1;
+ mstatus->mode = 0;
+ memcpy( &db->vd, &sd->vd, sizeof( struct view_data ) );
+ db->base_exp = db->job_exp = db->range2 = db->range3 = 1;
+ db->option = 0;
+
+ md = mob->once_spawn_sub( &sd->bl, m, x, y, sd->status.name, class_, "", SZ_SMALL, AI_NONE );
+ if ( !md )
+ return 0;
+ md->special_state.clone = 1;
+ mob->spawn(md);
+ unit->setdir( &md->bl, unit->getdir(&sd->bl) );
+ cd = chat->create( &md->bl, market_title, "", 1, false, 0, "", 0, 1, MAX_LEVEL );
+ if ( !cd )
+ return 0;
+ md->market_chat_id = cd->bl.id;
+ safestrncpy( md->market_message, market_msg, CHAT_SIZE_MAX );
+ clif->dispchat( cd, 0 );
+ if ( sd->vd.dead_sit == 2 )
+ clif->sitting( &md->bl );
+ return md->bl.id;
+}
+
int mob_clone_delete(struct mob_data *md)
{
const int class_ = md->class_;
@@ -4807,6 +4848,7 @@ void mob_defaults(void) {
mob->skill_event = mobskill_event;
mob->is_clone = mob_is_clone;
mob->clone_spawn = mob_clone_spawn;
+ mob->clone_spawn_market = mob_clone_spawn_market;
mob->clone_delete = mob_clone_delete;
mob->drop_adjust = mob_drop_adjust;
mob->item_dropratio_adjust = item_dropratio_adjust;
diff --git a/src/map/mob.h b/src/map/mob.h
index 728f3d8..9b71a6b 100644
--- a/src/map/mob.h
+++ b/src/map/mob.h
@@ -8,6 +8,7 @@
#include "map.h" // struct status_data, struct view_data, struct mob_skill
#include "status.h" // struct status_data, struct status_change
#include "unit.h" // struct unit_data
+#include "chat.h"
#include "../common/cbasetypes.h"
#include "../common/mmo.h" // struct item
@@ -185,6 +186,9 @@ struct mob_data {
* MvP Tombstone NPC ID
**/
int tomb_nid;
+
+ int market_chat_id;
+ char market_message[CHAT_SIZE_MAX];
};
@@ -338,6 +342,7 @@ struct mob_interface {
int (*skill_event) (struct mob_data *md, struct block_list *src, int64 tick, int flag);
int (*is_clone) (int class_);
int (*clone_spawn) (struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id, int mode, int flag, unsigned int duration);
+ int (*clone_spawn_market) ( struct map_session_data *sd, int16 m, int16 x, int16 y, char market_title[], char market_msg[] );
int (*clone_delete) (struct mob_data *md);
unsigned int (*drop_adjust) (int baserate, int rate_adjust, unsigned short rate_min, unsigned short rate_max);
void (*item_dropratio_adjust) (int nameid, int mob_id, int *rate_adjust);
diff --git a/src/map/pc.h b/src/map/pc.h
index c36704b..4b44bb9 100644
--- a/src/map/pc.h
+++ b/src/map/pc.h
@@ -547,6 +547,8 @@ struct map_session_data {
const char* delunit_prevfile;
int delunit_prevline;
+ int market_clone_id;
+ int market_clone_delay;
};
#define EQP_WEAPON EQP_HAND_R
diff --git a/src/map/script.c b/src/map/script.c
index f776b1b..c18fb69 100644
--- a/src/map/script.c
+++ b/src/map/script.c
@@ -9240,6 +9240,8 @@ int buildin_killmonster_sub_strip(struct block_list *bl,va_list ap)
char *event=va_arg(ap,char *);
int allflag=va_arg(ap,int);
+ if ( md->market_chat_id )
+ return 0;
md->state.npc_killmonster = 1;
if(!allflag) {
@@ -9258,6 +9260,8 @@ int buildin_killmonster_sub(struct block_list *bl,va_list ap)
char *event=va_arg(ap,char *);
int allflag=va_arg(ap,int);
+ if ( md->market_chat_id )
+ return 0;
if(!allflag) {
if(strcmp(event,md->npc_event)==0)
status_kill(bl);
@@ -9301,6 +9305,8 @@ int buildin_killmonsterall_sub_strip(struct block_list *bl,va_list ap)
struct mob_data *md;
md = BL_CAST(BL_MOB, bl);
+ if ( md->market_chat_id )
+ return 0;
if (md->npc_event[0])
md->npc_event[0] = 0;
@@ -9309,6 +9315,8 @@ int buildin_killmonsterall_sub_strip(struct block_list *bl,va_list ap)
}
int buildin_killmonsterall_sub(struct block_list *bl,va_list ap)
{
+ if ( ((TBL_MOB*)bl)->market_chat_id )
+ return 0;
status_kill(bl);
return 0;
}
diff --git a/src/map/skill.c b/src/map/skill.c
index e20a584..1f85cdf 100644
--- a/src/map/skill.c
+++ b/src/map/skill.c
@@ -519,6 +519,10 @@ int skillnotok (uint16 skill_id, struct map_session_data *sd)
clif->skill_fail(sd,skill_id,USESKILL_FAIL_THERE_ARE_NPC_AROUND,0);
return 1;
}
+ if ( sd->market_clone_id ) {
+ clif->colormes( sd->fd, COLOR_RED, "You can't use vending while you already have a market clone." );
+ return 1;
+ }
case MC_IDENTIFY:
return 0; // always allowed
case WZ_ICEWALL: