summaryrefslogtreecommitdiff
path: root/src/map
diff options
context:
space:
mode:
Diffstat (limited to 'src/map')
-rw-r--r--src/map/atcommand.c260
-rw-r--r--src/map/atcommand.h2
-rw-r--r--src/map/battle.c33
-rw-r--r--src/map/clif.c256
-rw-r--r--src/map/clif.h4
-rw-r--r--src/map/duel.c23
-rw-r--r--src/map/duel.h2
-rw-r--r--src/map/elemental.c4
-rw-r--r--src/map/homunculus.c2
-rw-r--r--src/map/instance.c2
-rw-r--r--src/map/itemdb.h2
-rw-r--r--src/map/map.c144
-rw-r--r--src/map/map.h6
-rw-r--r--src/map/messages_main.h150
-rw-r--r--src/map/messages_re.h150
-rw-r--r--src/map/messages_zero.h171
-rw-r--r--src/map/mob.c865
-rw-r--r--src/map/mob.h9
-rw-r--r--src/map/npc.c749
-rw-r--r--src/map/npc.h29
-rw-r--r--src/map/packets.h5
-rw-r--r--src/map/packets_keys_main.h12
-rw-r--r--src/map/packets_keys_zero.h9
-rw-r--r--src/map/packets_shuffle_main.h12
-rw-r--r--src/map/packets_shuffle_re.h9
-rw-r--r--src/map/packets_shuffle_zero.h9
-rw-r--r--src/map/packets_struct.h105
-rw-r--r--src/map/pc.c54
-rw-r--r--src/map/pc.h16
-rw-r--r--src/map/pet.c5
-rw-r--r--src/map/script.c675
-rw-r--r--src/map/script.h23
-rw-r--r--src/map/skill.c466
-rw-r--r--src/map/skill.h16
-rw-r--r--src/map/status.c33
-rw-r--r--src/map/status.h1
-rw-r--r--src/map/unit.c838
-rw-r--r--src/map/unit.h23
-rw-r--r--src/map/unitdefines.h58
39 files changed, 3715 insertions, 1517 deletions
diff --git a/src/map/atcommand.c b/src/map/atcommand.c
index 3cd26079d..410cd7af7 100644
--- a/src/map/atcommand.c
+++ b/src/map/atcommand.c
@@ -4123,16 +4123,36 @@ ACMD(mapinfo)
for (i = 0; i < map->list[m_id].npc_num;) {
struct npc_data *nd = map->list[m_id].npc[i];
switch(nd->dir) {
- case 0: strcpy(direction, msg_fd(fd,1101)); break; // North
- case 1: strcpy(direction, msg_fd(fd,1102)); break; // North West
- case 2: strcpy(direction, msg_fd(fd,1103)); break; // West
- case 3: strcpy(direction, msg_fd(fd,1104)); break; // South West
- case 4: strcpy(direction, msg_fd(fd,1105)); break; // South
- case 5: strcpy(direction, msg_fd(fd,1106)); break; // South East
- case 6: strcpy(direction, msg_fd(fd,1107)); break; // East
- case 7: strcpy(direction, msg_fd(fd,1108)); break; // North East
- case 9: strcpy(direction, msg_fd(fd,1109)); break; // North
- default: strcpy(direction, msg_fd(fd,1110)); break; // Unknown
+ case UNIT_DIR_NORTH:
+ strcpy(direction, msg_fd(fd, 1101)); // North
+ break;
+ case UNIT_DIR_NORTHWEST:
+ strcpy(direction, msg_fd(fd, 1102)); // North West
+ break;
+ case UNIT_DIR_WEST:
+ strcpy(direction, msg_fd(fd, 1103)); // West
+ break;
+ case UNIT_DIR_SOUTHWEST:
+ strcpy(direction, msg_fd(fd, 1104)); // South West
+ break;
+ case UNIT_DIR_SOUTH:
+ strcpy(direction, msg_fd(fd, 1105)); // South
+ break;
+ case UNIT_DIR_SOUTHEAST:
+ strcpy(direction, msg_fd(fd, 1106)); // South East
+ break;
+ case UNIT_DIR_EAST:
+ strcpy(direction, msg_fd(fd, 1107)); // East
+ break;
+ case UNIT_DIR_NORTHEAST:
+ strcpy(direction, msg_fd(fd, 1108)); // North East
+ break;
+ case 9: // is this actually used? [skyleo]
+ strcpy(direction, msg_fd(fd, 1109)); // North
+ break;
+ default:
+ strcpy(direction, msg_fd(fd, 1110)); // Unknown
+ break;
}
if(strcmp(nd->name,nd->exname) == 0)
safesnprintf(atcmd_output, sizeof(atcmd_output), msg_fd(fd,1111), // NPC %d: %s | Direction: %s | Sprite: %d | Location: %d %d
@@ -4212,12 +4232,19 @@ ACMD(mount_peco)
return true;
}
if ((sd->job & MAPID_THIRDMASK) == MAPID_MECHANIC) {
+ int mtype = MADO_ROBOT;
+ if (!*message)
+ sscanf(message, "%d", &mtype);
+ if (mtype < MADO_ROBOT || mtype >= MADO_MAX) {
+ clif->message(fd, msg_fd(fd, 173)); // Please enter a valid madogear type.
+ return false;
+ }
if (!pc_ismadogear(sd)) {
clif->message(sd->fd,msg_fd(fd,1123)); // You have mounted your Mado Gear.
- pc->setmadogear(sd, true);
+ pc->setmadogear(sd, true, (enum mado_type)mtype);
} else {
clif->message(sd->fd,msg_fd(fd,1124)); // You have released your Mado Gear.
- pc->setmadogear(sd, false);
+ pc->setmadogear(sd, false, (enum mado_type)mtype);
}
return true;
}
@@ -4483,59 +4510,90 @@ ACMD(loadnpc)
return true;
}
+/**
+ * Unloads a specific NPC.
+ *
+ * @code{.herc}
+ * @unloadnpc <NPC_name> {<flag>}
+ * @endcode
+ *
+ **/
ACMD(unloadnpc)
{
- struct npc_data *nd;
- char NPCname[NAME_LENGTH+1];
-
- memset(NPCname, '\0', sizeof(NPCname));
+ char npc_name[NAME_LENGTH + 1] = {'\0'};
+ int flag = 1;
- if (!*message || sscanf(message, "%24[^\n]", NPCname) < 1) {
- clif->message(fd, msg_fd(fd,1133)); // Please enter a NPC name (usage: @npcoff <NPC_name>).
+ if (*message == '\0' || sscanf(message, "%24s %1d", npc_name, &flag) < 1) {
+ clif->message(fd, msg_fd(fd, 1133)); /// Please enter a NPC name (Usage: @unloadnpc <NPC_name> {<flag>}).
return false;
}
-
- if ((nd = npc->name2id(NPCname)) == NULL) {
- clif->message(fd, msg_fd(fd,111)); // This NPC doesn't exist.
+
+ struct npc_data *nd = npc->name2id(npc_name);
+
+ if (nd == NULL) {
+ clif->message(fd, msg_fd(fd, 111)); /// This NPC doesn't exist.
return false;
}
- npc->unload_duplicates(nd);
- npc->unload(nd,true);
+ npc->unload_duplicates(nd, (flag != 0));
+ npc->unload(nd, true, (flag != 0));
npc->read_event_script();
- clif->message(fd, msg_fd(fd,112)); // Npc Disabled.
+ clif->message(fd, msg_fd(fd, 112)); /// Npc Disabled.
return true;
}
-/// Unload existing NPC within the NPC file and reload it.
-/// Usage: @reloadnpc npc/sample_npc.txt
+/**
+ * Unloads a script file and reloads it.
+ * Note: Be aware that some changes made by NPC are not reverted on unload. See doc/atcommands.txt for details.
+ *
+ * @code{.herc}
+ * @reloadnpc <path> {<flag>}
+ * @endcode
+ *
+ **/
ACMD(reloadnpc)
{
- if (!*message) {
- clif->message(fd, msg_fd(fd, 1385)); // Usage: @unloadnpcfile <file name>
+ char format[20];
+
+ snprintf(format, sizeof(format), "%%%ds %%1d", MAX_DIR_PATH);
+
+ char file_path[MAX_DIR_PATH + 1] = {'\0'};
+ int flag = 1;
+
+ if (*message == '\0' || (sscanf(message, format, file_path, &flag) < 1)) {
+ clif->message(fd, msg_fd(fd, 1516)); /// Usage: @reloadnpc <path> {<flag>}
return false;
- } else if (npc->unloadfile(message) == true) {
- clif->message(fd, msg_fd(fd, 1386)); // File unloaded. Be aware that mapflags and monsters spawned directly are not removed.
+ }
- FILE *fp = fopen(message, "r");
- // check if script file exists
- if (fp == NULL) {
- clif->message(fd, msg_fd(fd, 261));
- return false;
- }
- fclose(fp);
+ if (!exists(file_path)) {
+ clif->message(fd, msg_fd(fd, 1387)); /// File not found.
+ return false;
+ }
- // add to list of script sources and run it
- npc->addsrcfile(message);
- npc->parsesrcfile(message, true);
- npc->read_event_script();
+ if (!is_file(file_path)) {
+ clif->message(fd, msg_fd(fd, 1518)); /// Not a file.
+ return false;
+ }
- clif->message(fd, msg_fd(fd, 262));
- } else {
- clif->message(fd, msg_fd(fd, 1387)); // File not found.
+ FILE *fp = fopen(file_path, "r");
+
+ if (fp == NULL) {
+ clif->message(fd, msg_fd(fd, 1519)); /// Can't open file.
return false;
}
+ fclose(fp);
+
+ if (!npc->unloadfile(file_path, (flag != 0))) {
+ clif->message(fd, msg_fd(fd, 1517)); /// Script could not be unloaded.
+ return false;
+ }
+
+ clif->message(fd, msg_fd(fd, 1386)); /// File unloaded. Be aware that...
+ npc->addsrcfile(file_path);
+ npc->parsesrcfile(file_path, true);
+ npc->read_event_script();
+ clif->message(fd, msg_fd(fd, 262)); /// Script loaded.
return true;
}
@@ -6576,47 +6634,52 @@ ACMD(reset)
/*==========================================
*
*------------------------------------------*/
+
+/**
+ * Spawns mobs which treats the invoking as its master.
+ *
+ * @code{.herc}
+ * @summon <monster name/ID> {<duration>}
+ * @endcode
+ *
+ **/
ACMD(summon)
{
- char name[NAME_LENGTH];
- int mob_id = 0;
+ char name[NAME_LENGTH + 1] = {'\0'};
int duration = 0;
- struct mob_data *md;
- int64 tick=timer->gettick();
- if (!*message || sscanf(message, "%23s %12d", name, &duration) < 1)
- {
- clif->message(fd, msg_fd(fd,1225)); // Please enter a monster name (usage: @summon <monster name> {duration}).
+ if (*message == '\0' || sscanf(message, "%24s %12d", name, &duration) < 1) {
+ clif->message(fd, msg_fd(fd, 1225)); /// Please enter a monster name (usage: @summon <monster name> {duration}).
return false;
}
- if (duration < 1)
- duration =1;
- else if (duration > 60)
- duration =60;
+ int mob_id = atoi(name);
- if ((mob_id = atoi(name)) == 0)
+ if (mob_id == 0)
mob_id = mob->db_searchname(name);
- if(mob_id == 0 || mob->db_checkid(mob_id) == 0)
- {
- clif->message(fd, msg_fd(fd,40)); // Invalid monster ID or name.
+
+ if (mob_id == 0 || mob->db_checkid(mob_id) == 0) {
+ clif->message(fd, msg_fd(fd, 40)); /// Invalid monster ID or name.
return false;
}
- md = mob->once_spawn_sub(&sd->bl, sd->bl.m, -1, -1, DEFAULT_MOB_JNAME, mob_id, "", SZ_SMALL, AI_NONE);
+ struct mob_data *md = mob->once_spawn_sub(&sd->bl, sd->bl.m, -1, -1, DEFAULT_MOB_JNAME, mob_id, "",
+ SZ_SMALL, AI_NONE, 0);
- if(!md)
+ if (md == NULL)
return false;
md->master_id = sd->bl.id;
md->special_state.ai = AI_ATTACK;
- md->deletetimer = timer->add(tick+(duration*60000),mob->timer_delete,md->bl.id,0);
- clif->specialeffect(&md->bl,344,AREA);
- mob->spawn(md);
- sc_start4(NULL,&md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 60000);
- clif->skill_poseffect(&sd->bl,AM_CALLHOMUN,1,md->bl.x,md->bl.y,tick);
- clif->message(fd, msg_fd(fd,39)); // All monster summoned!
+ const int64 tick = timer->gettick();
+
+ md->deletetimer = timer->add(tick + (int64)cap_value(duration, 1, 60) * 60000, mob->timer_delete, md->bl.id, 0);
+ clif->specialeffect(&md->bl, 344, AREA);
+ mob->spawn(md);
+ sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 60000);
+ clif->skill_poseffect(&sd->bl, AM_CALLHOMUN, 1, md->bl.x, md->bl.y, tick);
+ clif->message(fd, msg_fd(fd, 39)); /// All monster summoned!
return true;
}
@@ -6856,7 +6919,7 @@ ACMD(identify)
}
}
}
-
+
if (num == 0)
clif->message(fd,msg_fd(fd,1238)); // There are no items to appraise.
else if (!identifyall)
@@ -8048,10 +8111,11 @@ ACMD(duel)
return false;
}
- if (!duel->checktime(sd)) {
+ int64 diff = duel->difftime(sd);
+ if (diff > 0) {
char output[CHAT_SIZE_MAX];
- // "Duel: You can take part in duel only one time per %d minutes."
- sprintf(output, msg_fd(fd,356), battle_config.duel_time_interval);
+ // "Duel: You can take part in duel again after %d secconds."
+ sprintf(output, msg_fd(fd,356), (int)diff);
clif->message(fd, output);
return false;
}
@@ -8101,10 +8165,11 @@ ACMD(leave)
ACMD(accept)
{
- if (!duel->checktime(sd)) {
+ int64 diff = duel->difftime(sd);
+ if (diff > 0) {
char output[CHAT_SIZE_MAX];
- // "Duel: You can take part in duel only one time per %d minutes."
- sprintf(output, msg_fd(fd,356), battle_config.duel_time_interval);
+ // "Duel: You can take part in duel again after %d seconds."
+ sprintf(output, msg_fd(fd,356), (int)diff);
clif->message(fd, output);
return false;
}
@@ -9007,19 +9072,54 @@ ACMD(addperm)
return true;
}
+/**
+ * Unloads a script file.
+ * Note: Be aware that some changes made by NPC are not reverted on unload. See doc/atcommands.txt for details.
+ *
+ * @code{.herc}
+ * @unloadnpcfile <path> {<flag>}
+ * @endcode
+ *
+ **/
ACMD(unloadnpcfile)
{
- if (!*message) {
- clif->message(fd, msg_fd(fd,1385)); // Usage: @unloadnpcfile <file name>
+ char format[20];
+
+ snprintf(format, sizeof(format), "%%%ds %%1d", MAX_DIR_PATH);
+
+ char file_path[MAX_DIR_PATH + 1] = {'\0'};
+ int flag = 1;
+
+ if (*message == '\0' || (sscanf(message, format, file_path, &flag) < 1)) {
+ clif->message(fd, msg_fd(fd, 1385)); /// Usage: @unloadnpcfile <path> {<flag>}
return false;
}
- if (npc->unloadfile(message)) {
- clif->message(fd, msg_fd(fd,1386)); // File unloaded. Be aware that mapflags and monsters spawned directly are not removed.
- } else {
- clif->message(fd, msg_fd(fd,1387)); // File not found.
+ if (!exists(file_path)) {
+ clif->message(fd, msg_fd(fd, 1387)); /// File not found.
+ return false;
+ }
+
+ if (!is_file(file_path)) {
+ clif->message(fd, msg_fd(fd, 1518)); /// Not a file.
return false;
}
+
+ FILE *fp = fopen(file_path, "r");
+
+ if (fp == NULL) {
+ clif->message(fd, msg_fd(fd, 1519)); /// Can't open file.
+ return false;
+ }
+
+ fclose(fp);
+
+ if (!npc->unloadfile(file_path, (flag != 0))) {
+ clif->message(fd, msg_fd(fd, 1517)); /// Script could not be unloaded.
+ return false;
+ }
+
+ clif->message(fd, msg_fd(fd, 1386)); /// File unloaded. Be aware that...
return true;
}
diff --git a/src/map/atcommand.h b/src/map/atcommand.h
index f3b1be51b..66827b3b2 100644
--- a/src/map/atcommand.h
+++ b/src/map/atcommand.h
@@ -41,7 +41,7 @@ struct config_setting_t;
* Defines
**/
#define ATCOMMAND_LENGTH 50
-#define MAX_MSG 1516
+#define MAX_MSG 1520
#define msg_txt(idx) atcommand->msg(idx)
#define msg_sd(sd,msg_number) atcommand->msgsd((sd),(msg_number))
#define msg_fd(fd,msg_number) atcommand->msgfd((fd),(msg_number))
diff --git a/src/map/battle.c b/src/map/battle.c
index 0b88f17c9..985d2bca4 100644
--- a/src/map/battle.c
+++ b/src/map/battle.c
@@ -3231,12 +3231,11 @@ static int64 battle_calc_damage(struct block_list *src, struct block_list *bl, s
if (!damage) return 0;
if( (sce = sc->data[SC_LIGHTNINGWALK]) && flag&BF_LONG && rnd()%100 < sce->val1 ) {
- int dx[8]={0,-1,-1,-1,0,1,1,1};
- int dy[8]={1,1,0,-1,-1,-1,0,1};
- uint8 dir = map->calc_dir(bl, src->x, src->y);
- if( unit->movepos(bl, src->x-dx[dir], src->y-dy[dir], 1, 1) ) {
- clif->slide(bl,src->x-dx[dir],src->y-dy[dir]);
- unit->setdir(bl, dir);
+ enum unit_dir dir = map->calc_dir(bl, src->x, src->y);
+ Assert_ret(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
+ if (unit->movepos(bl, src->x - dirx[dir], src->y - diry[dir], 1, 1)) {
+ clif->slide(bl, src->x - dirx[dir], src->y - diry[dir]);
+ unit->set_dir(bl, dir);
}
d->dmg_lv = ATK_DEF;
status_change_end(bl, SC_LIGHTNINGWALK, INVALID_TIMER);
@@ -4977,6 +4976,7 @@ static struct Damage battle_calc_weapon_attack(struct block_list *src, struct bl
switch (sd->weapontype) {
case W_BOW:
case W_REVOLVER:
+ case W_RIFLE:
case W_GATLING:
case W_SHOTGUN:
case W_GRENADE:
@@ -5857,10 +5857,10 @@ static void battle_reflect_damage(struct block_list *target, struct block_list *
if( wd->flag & BF_SHORT ) {
if( !is_boss(src) ) {
if( sc->data[SC_DEATHBOUND] && skill_id != WS_CARTTERMINATION ) {
- uint8 dir = map->calc_dir(target,src->x,src->y),
- t_dir = unit->getdir(target);
+ enum unit_dir dir = map->calc_dir(target, src->x, src->y);
+ enum unit_dir t_dir = unit->getdir(target);
- if( !map->check_dir(dir,t_dir) ) {
+ if (map->check_dir(dir, t_dir) == 0) {
int64 rd1 = damage * sc->data[SC_DEATHBOUND]->val2 / 100; // Amplify damage.
trdamage += rdamage = rd1 - (damage = rd1 * 30 / 100); // not normalized as intended.
@@ -6228,10 +6228,10 @@ static enum damage_lv battle_weapon_attack(struct block_list *src, struct block_
status_change_end(src, SC_CLOAKINGEXCEED, INVALID_TIMER);
}
if( tsc && tsc->data[SC_AUTOCOUNTER] && status->check_skilluse(target, src, KN_AUTOCOUNTER, 1) ) {
- uint8 dir = map->calc_dir(target,src->x,src->y);
- int t_dir = unit->getdir(target);
+ enum unit_dir dir = map->calc_dir(target, src->x, src->y);
+ enum unit_dir t_dir = unit->getdir(target);
int dist = distance_bl(src, target);
- if(dist <= 0 || (!map->check_dir(dir,t_dir) && dist <= tstatus->rhw.range+1)) {
+ if(dist <= 0 || (map->check_dir(dir, t_dir) == 0 && dist <= tstatus->rhw.range + 1)) {
uint16 skill_lv = tsc->data[SC_AUTOCOUNTER]->val1;
clif->skillcastcancel(target); //Remove the casting bar. [Skotlex]
clif->damage(src, target, sstatus->amotion, 1, 0, 1, BDT_NORMAL, 0); //Display MISS.
@@ -6600,10 +6600,6 @@ static int battle_check_target(struct block_list *src, struct block_list *target
m = target->m;
- if (flag & BCT_ENEMY && (map->getcell(m, src, src->x, src->y, CELL_CHKBASILICA) || map->getcell(m, src, target->x, target->y, CELL_CHKBASILICA))) {
- return -1;
- }
-
//t_bl/s_bl hold the 'master' of the attack, while src/target are the actual
//objects involved.
if( (t_bl = battle->get_master(target)) == NULL )
@@ -6612,6 +6608,11 @@ static int battle_check_target(struct block_list *src, struct block_list *target
if( (s_bl = battle->get_master(src)) == NULL )
s_bl = src;
+ if ((flag & BCT_ENEMY) != 0 && (status_get_mode(s_bl) & MD_BOSS) == 0 && (map->getcell(m, src, src->x, src->y, CELL_CHKBASILICA) != 0
+ || map->getcell(m, src, target->x, target->y, CELL_CHKBASILICA) != 0)) {
+ return -1;
+ }
+
if (s_bl->type == BL_PC) {
const struct map_session_data *s_sd = BL_UCCAST(BL_PC, s_bl);
switch (t_bl->type) {
diff --git a/src/map/clif.c b/src/map/clif.c
index 3dff01523..a8b6ea768 100644
--- a/src/map/clif.c
+++ b/src/map/clif.c
@@ -4032,51 +4032,58 @@ static void clif_misceffect(struct block_list *bl, int type)
/// 0229 <id>.L <body state>.W <health state>.W <effect state>.L <pk mode>.B (ZC_STATE_CHANGE3)
static void clif_changeoption(struct block_list *bl)
{
- unsigned char buf[32];
- struct status_change *sc;
- struct map_session_data* sd;
-
nullpo_retv(bl);
- if ( !(sc = status->get_sc(bl)) && bl->type != BL_NPC ) return; //How can an option change if there's no sc?
+ struct status_change *sc = status->get_sc(bl);
- sd = BL_CAST(BL_PC, bl);
+ if (sc == NULL && bl->type != BL_NPC) // How can an option change if there's no sc?
+ return;
-#if PACKETVER >= 7
- WBUFW(buf,0) = 0x229;
- WBUFL(buf,2) = bl->id;
- WBUFW(buf,6) = (sc) ? sc->opt1 : 0;
- WBUFW(buf,8) = (sc) ? sc->opt2 : 0;
- WBUFL(buf,10) = (sc != NULL) ? sc->option : ((bl->type == BL_NPC) ? BL_UCCAST(BL_NPC, bl)->option : 0);
- WBUFB(buf,14) = (sd)? sd->status.karma : 0;
+ struct map_session_data *sd = BL_CAST(BL_PC, bl);
+ struct PACKET_ZC_STATE_CHANGE p;
+ p.packetType = HEADER_ZC_STATE_CHANGE;
+ p.AID = bl->id;
+ p.bodyState = (sc != NULL) ? sc->opt1 : 0;
+ p.healthState = (sc != NULL) ? sc->opt2 : 0;
+ p.effectState = (sc != NULL) ? sc->option : BL_UCCAST(BL_NPC, bl)->option;
+ p.isPKModeON = (sd != NULL) ? sd->status.karma : 0;
if (clif->isdisguised(bl)) {
- clif->send(buf,packet_len(0x229),bl,AREA_WOS);
- WBUFL(buf,2) = -bl->id;
- clif->send(buf,packet_len(0x229),bl,SELF);
- WBUFL(buf,2) = bl->id;
- WBUFL(buf,10) = OPTION_INVISIBLE;
- clif->send(buf,packet_len(0x229),bl,SELF);
+ clif->send(&p, sizeof(p), bl, AREA_WOS);
+ p.AID = -bl->id;
+ clif->send(&p, sizeof(p), bl, SELF);
+ p.AID = bl->id;
+ p.effectState = OPTION_INVISIBLE;
+ clif->send(&p, sizeof(p), bl, SELF);
} else {
- clif->send(buf,packet_len(0x229),bl,AREA);
+ clif->send(&p, sizeof(p), bl, AREA);
}
-#else
- WBUFW(buf,0) = 0x119;
- WBUFL(buf,2) = bl->id;
- WBUFW(buf,6) = (sc) ? sc->opt1 : 0;
- WBUFW(buf,8) = (sc) ? sc->opt2 : 0;
- WBUFL(buf,10) = (sc != NULL) ? sc->option : ((bl->type == BL_NPC) ? BL_UCCAST(BL_NPC, bl)->option : 0);
- WBUFB(buf,12) = (sd)? sd->status.karma : 0;
+}
+
+static void clif_changeoption_target(struct block_list *bl, struct block_list *target_bl, enum send_target target)
+{
+ nullpo_retv(bl);
+ nullpo_retv(target_bl);
+
+ struct status_change *sc = status->get_sc(bl);
+
+ if (sc == NULL && bl->type != BL_NPC) // How can an option change if there's no sc?
+ return;
+
+ struct map_session_data *sd = BL_CAST(BL_PC, bl);
+ struct PACKET_ZC_STATE_CHANGE p;
+ p.packetType = HEADER_ZC_STATE_CHANGE;
+ p.AID = bl->id;
+ p.bodyState = (sc != NULL) ? sc->opt1 : 0;
+ p.healthState = (sc != NULL) ? sc->opt2 : 0;
+ p.effectState = (sc != NULL) ? sc->option : BL_UCCAST(BL_NPC, bl)->option;
+ p.isPKModeON = (sd != NULL) ? sd->status.karma : 0;
if (clif->isdisguised(bl)) {
- clif->send(buf,packet_len(0x119),bl,AREA_WOS);
- WBUFL(buf,2) = -bl->id;
- clif->send(buf,packet_len(0x119),bl,SELF);
- WBUFL(buf,2) = bl->id;
- WBUFW(buf,10) = OPTION_INVISIBLE;
- clif->send(buf,packet_len(0x119),bl,SELF);
- } else {
- clif->send(buf,packet_len(0x119),bl,AREA);
+ p.AID = -bl->id;
+ clif->send(&p, sizeof(p), target_bl, target);
+ p.AID = bl->id;
+ p.effectState = OPTION_INVISIBLE;
}
-#endif
+ clif->send(&p, sizeof(p), target_bl, target);
}
/// Displays status change effects on NPCs/monsters (ZC_NPC_SHOWEFST_UPDATE).
@@ -4893,7 +4900,7 @@ static int clif_damage(struct block_list *src, struct block_list *dst, int sdela
}
if(src == dst) {
- unit->setdir(src,unit->getdir(src));
+ unit->set_dir(src, unit->getdir(src));
}
//Return adjusted can't walk delay for further processing.
@@ -6747,21 +6754,28 @@ static void clif_item_refine_list(struct map_session_data *sd)
/// 0147 <skill id>.W <type>.L <level>.W <sp cost>.W <atk range>.W <skill name>.24B <upgradeable>.B
static void clif_item_skill(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv)
{
- int fd;
-
nullpo_retv(sd);
- fd=sd->fd;
- WFIFOHEAD(fd,packet_len(0x147));
- WFIFOW(fd, 0)=0x147;
- WFIFOW(fd, 2)=skill_id;
- WFIFOL(fd, 4)=skill->get_inf(skill_id);
- WFIFOW(fd, 8)=skill_lv;
- WFIFOW(fd,10)=skill->get_sp(skill_id,skill_lv);
- WFIFOW(fd,12)=skill->get_range2(&sd->bl, skill_id,skill_lv);
- safestrncpy(WFIFOP(fd,14),skill->get_name(skill_id),NAME_LENGTH);
- WFIFOB(fd,38)=0;
- WFIFOSET(fd,packet_len(0x147));
+ int fd = sd->fd;
+
+ WFIFOHEAD(fd, sizeof(struct PACKET_ZC_AUTORUN_SKILL));
+
+ struct PACKET_ZC_AUTORUN_SKILL *p = WFIFOP(fd, 0);
+ int type = skill->get_inf(skill_id);
+
+ if (sd->state.itemskill_castonself == 1 && skill->is_item_skill(sd, skill_id, skill_lv))
+ type = INF_SELF_SKILL;
+
+ p->packetType = HEADER_ZC_AUTORUN_SKILL;
+ p->skill_id = skill_id;
+ p->skill_type = type;
+ p->skill_lv = skill_lv;
+ p->skill_sp = skill->get_sp(skill_id, skill_lv);
+ p->skill_range = skill->get_range2(&sd->bl, skill_id, skill_lv);
+ safestrncpy(p->skill_name, skill->get_name(skill_id), NAME_LENGTH);
+ p->up_flag = 0;
+
+ WFIFOSET(fd, sizeof(struct PACKET_ZC_AUTORUN_SKILL));
}
/// Adds an item to character's cart.
@@ -11116,7 +11130,7 @@ static void clif_parse_WalkToXY(int fd, struct map_session_data *sd)
//Set last idle time... [Skotlex]
pc->update_idle_time(sd, BCIDLE_WALK);
- unit->walktoxy(&sd->bl, x, y, 4);
+ unit->walk_toxy(&sd->bl, x, y, 4);
}
/// Notification about the result of a disconnect request (ZC_ACK_REQ_DISCONNECT).
@@ -11334,15 +11348,7 @@ static void clif_parse_MapMove(int fd, struct map_session_data *sd)
/// 0 = straight
/// 1 = turned CW
/// 2 = turned CCW
-/// dir:
-/// 0 = north
-/// 1 = northwest
-/// 2 = west
-/// 3 = southwest
-/// 4 = south
-/// 5 = southeast
-/// 6 = east
-/// 7 = northeast
+/// dir: @see enum unit_dir
static void clif_changed_dir(struct block_list *bl, enum send_target target)
{
unsigned char buf[64];
@@ -12919,6 +12925,7 @@ static void clif_parse_UseSkillMap(int fd, struct map_session_data *sd)
pc->delinvincibletimer(sd);
skill->castend_map(sd,skill_id,map_name);
+ pc->itemskill_clear(sd);
}
static void clif_parse_RequestMemo(int fd, struct map_session_data *sd) __attribute__((nonnull (2)));
@@ -15273,8 +15280,8 @@ static void clif_parse_GMKick(int fd, struct map_session_data *sd)
clif->GM_kickack(sd, 0);
return;
}
- npc->unload_duplicates(nd);
- npc->unload(nd,true);
+ npc->unload_duplicates(nd, true);
+ npc->unload(nd, true, true);
npc->read_event_script();
}
break;
@@ -16444,7 +16451,7 @@ static void clif_parse_HomMoveToMaster(int fd, struct map_session_data *sd)
unit->calc_pos(bl, sd->bl.x, sd->bl.y, sd->ud.dir);
ud = unit->bl2ud(bl);
- unit->walktoxy(bl, ud->to_x, ud->to_y, 4);
+ unit->walk_toxy(bl, ud->to_x, ud->to_y, 4);
}
static void clif_parse_HomMoveTo(int fd, struct map_session_data *sd) __attribute__((nonnull (2)));
@@ -16468,7 +16475,7 @@ static void clif_parse_HomMoveTo(int fd, struct map_session_data *sd)
else
return;
- unit->walktoxy(bl, x, y, 4);
+ unit->walk_toxy(bl, x, y, 4);
}
static void clif_parse_HomAttack(int fd, struct map_session_data *sd) __attribute__((nonnull (2)));
@@ -20028,7 +20035,7 @@ static void clif_cashShopOpen(int fd, struct map_session_data *sd, int tab)
p->packetType = HEADER_ZC_SE_CASHSHOP_OPEN;
p->cashPoints = sd->cashPoints; //[Ryuuzaki] - switched positions to reflect proper values
p->kafraPoints = sd->kafraPoints;
-#if PACKETVER_ZERO_NUM >= 20191224
+#if PACKETVER_MAIN_NUM >= 20200129 || PACKETVER_RE_NUM >= 20200205 || PACKETVER_ZERO_NUM >= 20191224
p->tab = tab;
#endif
WFIFOSET(fd, sizeof(struct PACKET_ZC_SE_CASHSHOP_OPEN));
@@ -23209,6 +23216,121 @@ static void clif_parse_NPCBarterPurchase(int fd, struct map_session_data *sd)
#endif
}
+static void clif_parse_npc_expanded_barter_closed(int fd, struct map_session_data *sd) __attribute__((nonnull (2)));
+static void clif_parse_npc_expanded_barter_closed(int fd, struct map_session_data *sd)
+{
+}
+
+#if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127
+#define NEXT_EXPANDED_BARTER_ITEM(var, count) \
+ var = (struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub *)((char*)item + \
+ sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub) - \
+ sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2) + \
+ count * sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2))
+#endif
+
+static void clif_npc_expanded_barter_open(struct map_session_data *sd, struct npc_data *nd)
+{
+#if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127
+ nullpo_retv(sd);
+ nullpo_retv(nd);
+ struct npc_item_list *shop = nd->u.scr.shop->item;
+ const int shop_size = nd->u.scr.shop->items;
+
+ int items_count = 0;
+ int currencies_count = 0;
+ struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN *packet = (struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN*)&packet_buf[0];
+ STATIC_ASSERT(sizeof(packet_buf) > sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN), "packet_buf size too small");
+ int buf_left = sizeof(packet_buf) - sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN);
+ packet->packetType = HEADER_ZC_NPC_EXPANDED_BARTER_OPEN;
+ struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub *item = &packet->items[0];
+
+ // Workaround for fix Visual Studio bug (error C2233)
+ // Here should be sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub)
+ const int ptr_size = sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub) -
+ sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2);
+ for (int i = 0; i < shop_size && buf_left >= ptr_size; i++) {
+ if (shop[i].nameid) {
+ struct item_data *id = itemdb->exists(shop[i].nameid);
+ if (id == NULL)
+ continue;
+
+ item->nameid = shop[i].nameid;
+ item->type = itemtype(id->type);
+ item->amount = shop[i].qty;
+ item->weight = id->weight * 10;
+ item->index = i;
+ item->zeny = shop[i].value;
+ item->currency_count = 0;
+ buf_left -= ptr_size;
+ items_count ++;
+ int count = shop[i].value2;
+ if (buf_left < sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2) * count) {
+ NEXT_EXPANDED_BARTER_ITEM(item, 0);
+ break;
+ }
+ for (int j = 0; j < count; j ++) {
+ struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2 *packet_currency = &item->currencies[j];
+ struct npc_barter_currency *currency = &shop[i].currency[j];
+ struct item_data *id2 = itemdb->exists(currency->nameid);
+ if (id2 == NULL)
+ continue;
+ packet_currency->nameid = currency->nameid;
+ if (currency->refine == -1)
+ packet_currency->refine_level = 0;
+ else
+ packet_currency->refine_level = currency->refine;
+ packet_currency->amount = currency->amount;
+ packet_currency->type = itemtype(id2->type);
+ currencies_count ++;
+ item->currency_count ++;
+ }
+ NEXT_EXPANDED_BARTER_ITEM(item, item->currency_count);
+ }
+ }
+
+ packet->items_count = items_count;
+ packet->packetLength = sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN) +
+ ptr_size * items_count +
+ sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2) * currencies_count;
+ clif->send(packet, packet->packetLength, &sd->bl, SELF);
+#endif
+}
+
+#undef NEXT_EXPANDED_BARTER_ITEM
+
+static void clif_parse_npc_expanded_barter_purchase(int fd, struct map_session_data *sd) __attribute__((nonnull (2)));
+static void clif_parse_npc_expanded_barter_purchase(int fd, struct map_session_data *sd)
+{
+#if PACKETVER_MAIN_NUM >= 20190904 || PACKETVER_RE_NUM >= 20190904 || PACKETVER_ZERO_NUM >= 20190828
+ if (sd->state.trading || pc_isdead(sd) || pc_isvending(sd))
+ return;
+
+ const struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE *const p = RP2PTR(fd);
+ int count = (p->packetLength - sizeof(struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE)) / sizeof p->list[0];
+ struct barteritemlist item_list;
+
+ Assert_retv(count >= 0 && count <= sd->status.inventorySize);
+
+ VECTOR_INIT(item_list);
+ VECTOR_ENSURE(item_list, count, 1);
+
+ for (int i = 0; i < count; i++) {
+ struct barter_itemlist_entry entry = { 0 };
+ entry.addId = p->list[i].itemId;
+ entry.addAmount = p->list[i].amount;
+ entry.removeIndex = -1;
+ entry.shopIndex = p->list[i].shopIndex;
+ VECTOR_PUSH(item_list, entry);
+ }
+
+ int response = npc->expanded_barter_buylist(sd, &item_list);
+ clif->npc_buy_result(sd, response);
+
+ VECTOR_CLEAR(item_list);
+#endif
+}
+
static void clif_parse_clientVersion(int fd, struct map_session_data *sd) __attribute__((nonnull (2)));
static void clif_parse_clientVersion(int fd, struct map_session_data *sd)
{
@@ -24102,6 +24224,7 @@ void clif_defaults(void)
/* visual effects client-side */
clif->misceffect = clif_misceffect;
clif->changeoption = clif_changeoption;
+ clif->changeoption_target = clif_changeoption_target;
clif->changeoption2 = clif_changeoption2;
clif->emotion = clif_emotion;
clif->talkiebox = clif_talkiebox;
@@ -24766,6 +24889,9 @@ void clif_defaults(void)
clif->npc_barter_open = clif_npc_barter_open;
clif->pNPCBarterClosed = clif_parse_NPCBarterClosed;
clif->pNPCBarterPurchase = clif_parse_NPCBarterPurchase;
+ clif->npc_expanded_barter_open = clif_npc_expanded_barter_open;
+ clif->pNPCExpandedBarterPurchase = clif_parse_npc_expanded_barter_purchase;
+ clif->pNPCExpandedBarterClosed = clif_parse_npc_expanded_barter_closed;
clif->pClientVersion = clif_parse_clientVersion;
clif->pPing = clif_parse_ping;
clif->ping = clif_ping;
diff --git a/src/map/clif.h b/src/map/clif.h
index b61772bba..25ac65af5 100644
--- a/src/map/clif.h
+++ b/src/map/clif.h
@@ -1006,6 +1006,7 @@ struct clif_interface {
/* visual effects client-side */
void (*misceffect) (struct block_list* bl,int type);
void (*changeoption) (struct block_list* bl);
+ void (*changeoption_target) (struct block_list *bl, struct block_list *target_bl, enum send_target target);
void (*changeoption2) (struct block_list* bl);
void (*emotion) (struct block_list *bl,int type);
void (*talkiebox) (struct block_list* bl, const char* talkie);
@@ -1663,6 +1664,9 @@ struct clif_interface {
void (*npc_barter_open) (struct map_session_data *sd, struct npc_data *nd);
void (*pNPCBarterClosed) (int fd, struct map_session_data *sd);
void (*pNPCBarterPurchase) (int fd, struct map_session_data *sd);
+ void (*pNPCExpandedBarterClosed) (int fd, struct map_session_data *sd);
+ void (*pNPCExpandedBarterPurchase) (int fd, struct map_session_data *sd);
+ void (*npc_expanded_barter_open) (struct map_session_data *sd, struct npc_data *nd);
void (*pClientVersion) (int fd, struct map_session_data *sd);
void (*pPing) (int fd, struct map_session_data *sd);
void (*ping) (struct map_session_data *sd);
diff --git a/src/map/duel.c b/src/map/duel.c
index dca040f83..c66fd6fc2 100644
--- a/src/map/duel.c
+++ b/src/map/duel.c
@@ -41,27 +41,12 @@ struct duel_interface *duel;
*------------------------------------------*/
static void duel_savetime(struct map_session_data *sd)
{
- time_t clock;
- struct tm *t;
-
- time(&clock);
- t = localtime(&clock);
-
- pc_setglobalreg(sd, script->add_variable("PC_LAST_DUEL_TIME"), t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min);
+ pc_setglobalreg(sd, script->add_variable("PC_LAST_DUEL_TIME"), (int)time(NULL));
}
-static int duel_checktime(struct map_session_data *sd)
+static int64 duel_difftime(struct map_session_data *sd)
{
- int diff;
- time_t clock;
- struct tm *t;
-
- time(&clock);
- t = localtime(&clock);
-
- diff = t->tm_mday*24*60 + t->tm_hour*60 + t->tm_min - pc_readglobalreg(sd, script->add_variable("PC_LAST_DUEL_TIME") );
-
- return !(diff >= 0 && diff < battle_config.duel_time_interval);
+ return (pc_readglobalreg(sd, script->add_variable("PC_LAST_DUEL_TIME")) + battle_config.duel_time_interval - (int)time(NULL));
}
static int duel_showinfo_sub(struct map_session_data *sd, va_list va)
@@ -233,7 +218,7 @@ void duel_defaults(void)
duel->reject = duel_reject;
duel->leave = duel_leave;
duel->showinfo = duel_showinfo;
- duel->checktime = duel_checktime;
+ duel->difftime = duel_difftime;
duel->init = do_init_duel;
duel->final = do_final_duel;
diff --git a/src/map/duel.h b/src/map/duel.h
index 4e8985b96..1620ca891 100644
--- a/src/map/duel.h
+++ b/src/map/duel.h
@@ -52,7 +52,7 @@ struct duel_interface {
void (*reject) (const unsigned int did, struct map_session_data* sd);
void (*leave) (const unsigned int did, struct map_session_data* sd);
void (*showinfo) (const unsigned int did, struct map_session_data* sd);
- int (*checktime) (struct map_session_data* sd);
+ int64 (*difftime) (struct map_session_data* sd);
void (*init) (bool minimal);
void (*final) (void);
diff --git a/src/map/elemental.c b/src/map/elemental.c
index 1c1d98634..f176bb9e2 100644
--- a/src/map/elemental.c
+++ b/src/map/elemental.c
@@ -788,8 +788,8 @@ static int elemental_ai_sub_timer(struct elemental_data *ed, struct map_session_
return 0; //Already walking to him
if( DIFF_TICK(tick, ed->ud.canmove_tick) < 0 )
return 0; //Can't move yet.
- if( map->search_freecell(&ed->bl, sd->bl.m, &x, &y, MIN_ELEDISTANCE, MIN_ELEDISTANCE, 1)
- && unit->walktoxy(&ed->bl, x, y, 0) )
+ if (map->search_freecell(&ed->bl, sd->bl.m, &x, &y, MIN_ELEDISTANCE, MIN_ELEDISTANCE, 1) != 0
+ && unit->walk_toxy(&ed->bl, x, y, 0) == 0)
return 0;
}
diff --git a/src/map/homunculus.c b/src/map/homunculus.c
index 189cf29e4..65c457283 100644
--- a/src/map/homunculus.c
+++ b/src/map/homunculus.c
@@ -179,7 +179,7 @@ static int homunculus_vaporize(struct map_session_data *sd, enum homun_state sta
nullpo_ret(sd);
hd = sd->hd;
- if (!hd || hd->homunculus.vaporize != HOM_ST_ACTIVE)
+ if (hd == NULL || hd->bl.prev == NULL || hd->homunculus.vaporize != HOM_ST_ACTIVE)
return 0;
if (status->isdead(&hd->bl))
diff --git a/src/map/instance.c b/src/map/instance.c
index 90f2217b1..1104b7e88 100644
--- a/src/map/instance.c
+++ b/src/map/instance.c
@@ -446,7 +446,7 @@ static int instance_cleanup_sub(struct block_list *bl, va_list ap)
map->quit(BL_UCAST(BL_PC, bl));
break;
case BL_NPC:
- npc->unload(BL_UCAST(BL_NPC, bl), true);
+ npc->unload(BL_UCAST(BL_NPC, bl), true, true);
break;
case BL_MOB:
unit->free(bl,CLR_OUTSIGHT);
diff --git a/src/map/itemdb.h b/src/map/itemdb.h
index d2592af4e..5f0790b10 100644
--- a/src/map/itemdb.h
+++ b/src/map/itemdb.h
@@ -95,6 +95,8 @@ enum item_itemid {
ITEMID_ALOEBERA = 606,
ITEMID_SPECTACLES = 611,
ITEMID_POISON_BOTTLE = 678,
+ ITEMID_EARTH_SCROLL_1_3 = 686,
+ ITEMID_EARTH_SCROLL_1_5 = 687,
ITEMID_EMPTY_BOTTLE = 713,
ITEMID_EMPERIUM = 714,
ITEMID_YELLOW_GEMSTONE = 715,
diff --git a/src/map/map.c b/src/map/map.c
index afdc2ed41..defa56b2e 100644
--- a/src/map/map.c
+++ b/src/map/map.c
@@ -531,10 +531,12 @@ static struct skill_unit *map_find_skill_unit_oncell(struct block_list *target,
return NULL;
}
-/** @name Functions for block_list search and manipulation
+/**
+ * @name Functions for block_list search and manipulation
+ *
+ * @{
*/
-/* @{ */
/**
* Applies func to every block_list in bl_list starting with bl_list[blockcount].
* Sets bl_list_count back to blockcount.
@@ -638,9 +640,12 @@ static int map_foreachinmap(int (*func)(struct block_list*, va_list), int16 m, i
static int map_forcountinmap(int (*func)(struct block_list*, va_list), int16 m, int count, int type, ...)
{
- int returnCount;
+ int returnCount = 0;
va_list ap;
+ if (m < 0)
+ return returnCount;
+
va_start(ap, type);
returnCount = map->vforcountinarea(func, m, 0, 0, map->list[m].xs, map->list[m].ys, count, type, ap);
va_end(ap);
@@ -1665,7 +1670,7 @@ static int map_search_freecell(struct block_list *src, int16 m, int16 *x, int16
*------------------------------------------*/
static bool map_closest_freecell(int16 m, const struct block_list *bl, int16 *x, int16 *y, int type, int flag)
{
- uint8 dir = 6;
+ enum unit_dir dir = UNIT_DIR_EAST;
int16 tx;
int16 ty;
int costrange = 10;
@@ -1684,7 +1689,7 @@ static bool map_closest_freecell(int16 m, const struct block_list *bl, int16 *x,
short dy = diry[dir];
//Linear search
- if(dir%2 == 0 && costrange%MOVE_COST == 0) {
+ if (!unit_is_diagonal_dir(dir) && (costrange % MOVE_COST) == 0) {
tx = *x+dx*(costrange/MOVE_COST);
ty = *y+dy*(costrange/MOVE_COST);
if (!map->count_oncell(m, tx, ty, type, flag) && map->getcell(m, bl, tx, ty, CELL_CHKPASS)) {
@@ -1694,7 +1699,7 @@ static bool map_closest_freecell(int16 m, const struct block_list *bl, int16 *x,
}
}
//Full diagonal search
- else if(dir%2 == 1 && costrange%MOVE_DIAGONAL_COST == 0) {
+ else if (unit_is_diagonal_dir(dir) && (costrange % MOVE_DIAGONAL_COST) == 0) {
tx = *x+dx*(costrange/MOVE_DIAGONAL_COST);
ty = *y+dy*(costrange/MOVE_DIAGONAL_COST);
if (!map->count_oncell(m, tx, ty, type, flag) && map->getcell(m, bl, tx, ty, CELL_CHKPASS)) {
@@ -1704,16 +1709,24 @@ static bool map_closest_freecell(int16 m, const struct block_list *bl, int16 *x,
}
}
//One cell diagonal, rest linear (TODO: Find a better algorithm for this)
- else if(dir%2 == 1 && costrange%MOVE_COST == 4) {
- tx = *x+dx*((dir%4==3)?(costrange/MOVE_COST):1);
- ty = *y+dy*((dir%4==1)?(costrange/MOVE_COST):1);
+ else if (unit_is_diagonal_dir(dir) && (costrange % MOVE_COST) == 4) {
+ tx = *x + dx;
+ ty = *y + dy;
+ if (unit_is_dir_or_opposite(dir, UNIT_DIR_SOUTHWEST))
+ tx *= costrange / MOVE_COST;
+ if (unit_is_dir_or_opposite(dir, UNIT_DIR_NORTHWEST))
+ ty *= costrange / MOVE_COST;
if (!map->count_oncell(m, tx, ty, type, flag) && map->getcell(m, bl, tx, ty, CELL_CHKPASS)) {
*x = tx;
*y = ty;
return true;
}
- tx = *x+dx*((dir%4==1)?(costrange/MOVE_COST):1);
- ty = *y+dy*((dir%4==3)?(costrange/MOVE_COST):1);
+ tx = *x + dx;
+ ty = *y + dy;
+ if (unit_is_dir_or_opposite(dir, UNIT_DIR_NORTHWEST))
+ tx *= costrange / MOVE_COST;
+ if (unit_is_dir_or_opposite(dir, UNIT_DIR_SOUTHWEST))
+ ty *= costrange / MOVE_COST;
if (!map->count_oncell(m, tx, ty, type, flag) && map->getcell(m, bl, tx, ty, CELL_CHKPASS)) {
*x = tx;
*y = ty;
@@ -1722,17 +1735,17 @@ static bool map_closest_freecell(int16 m, const struct block_list *bl, int16 *x,
}
//Get next direction
- if (dir == 5) {
+ if (dir == UNIT_DIR_SOUTHEAST) {
//Diagonal search complete, repeat with higher cost range
if(costrange == 14) costrange += 6;
else if(costrange == 28 || costrange >= 38) costrange += 2;
else costrange += 4;
- dir = 6;
- } else if (dir == 4) {
+ dir = UNIT_DIR_EAST;
+ } else if (dir == UNIT_DIR_SOUTH) {
//Linear search complete, switch to diagonal directions
- dir = 7;
+ dir = UNIT_DIR_NORTHEAST;
} else {
- dir = (dir+2)%8;
+ dir = unit_get_ccw90_dir(dir);
}
}
@@ -2840,63 +2853,70 @@ static int map_mapname2ipport(unsigned short name, uint32 *ip, uint16 *port)
return 0;
}
-/*==========================================
+/**
* Checks if both dirs point in the same direction.
- *------------------------------------------*/
-static int map_check_dir(int s_dir, int t_dir)
+ * @param s_dir: direction source is facing
+ * @param t_dir: direction target is facing
+ * @return 0: success(both face the same direction), 1: failure
+ **/
+static int map_check_dir(enum unit_dir s_dir, enum unit_dir t_dir)
{
- if(s_dir == t_dir)
+ if (s_dir == t_dir || ((t_dir + UNIT_DIR_MAX - 1) % UNIT_DIR_MAX) == s_dir
+ || ((t_dir + UNIT_DIR_MAX + 1) % UNIT_DIR_MAX) == s_dir)
return 0;
- switch(s_dir) {
- case 0: if(t_dir == 7 || t_dir == 1 || t_dir == 0) return 0; break;
- case 1: if(t_dir == 0 || t_dir == 2 || t_dir == 1) return 0; break;
- case 2: if(t_dir == 1 || t_dir == 3 || t_dir == 2) return 0; break;
- case 3: if(t_dir == 2 || t_dir == 4 || t_dir == 3) return 0; break;
- case 4: if(t_dir == 3 || t_dir == 5 || t_dir == 4) return 0; break;
- case 5: if(t_dir == 4 || t_dir == 6 || t_dir == 5) return 0; break;
- case 6: if(t_dir == 5 || t_dir == 7 || t_dir == 6) return 0; break;
- case 7: if(t_dir == 6 || t_dir == 0 || t_dir == 7) return 0; break;
- }
return 1;
}
-/*==========================================
+/**
* Returns the direction of the given cell, relative to 'src'
- *------------------------------------------*/
-static uint8 map_calc_dir(struct block_list *src, int16 x, int16 y)
+ * @param src: object to put in relation between coordinates
+ * @param x: x-coordinate of cell
+ * @param y: y-coordinate of cell
+ * @return the direction of the given cell, relative to 'src'
+ **/
+static enum unit_dir map_calc_dir(const struct block_list *src, int16 x, int16 y)
{
- uint8 dir = 0;
- int dx, dy;
-
- nullpo_ret(src);
+ nullpo_retr(UNIT_DIR_NORTH, src);
+ enum unit_dir dir = UNIT_DIR_NORTH;
- dx = x-src->x;
- dy = y-src->y;
+ int dx = x - src->x;
+ int dy = y - src->y;
if (dx == 0 && dy == 0) {
// both are standing on the same spot.
// aegis-style, makes knockback default to the left.
// athena-style, makes knockback default to behind 'src'.
- dir = (battle_config.knockback_left ? 6 : unit->getdir(src));
- } else if (dx >= 0 && dy >=0) {
- // upper-right
- if( dx*2 < dy || dx == 0 ) dir = 0; // up
- else if( dx > dy*2+1 || dy == 0 ) dir = 6; // right
- else dir = 7; // up-right
+ if (battle_config.knockback_left != 0)
+ dir = UNIT_DIR_EAST;
+ else
+ dir = unit->getdir(src);
+ } else if (dx >= 0 && dy >= 0) {
+ if (dx * 2 < dy || dx == 0)
+ dir = UNIT_DIR_NORTH;
+ else if (dx > dy * 2 + 1 || dy == 0)
+ dir = UNIT_DIR_EAST;
+ else
+ dir = UNIT_DIR_NORTHEAST;
} else if (dx >= 0 && dy <= 0) {
- // lower-right
- if( dx*2 < -dy || dx == 0 ) dir = 4; // down
- else if( dx > -dy*2+1 || dy == 0 ) dir = 6; // right
- else dir = 5; // down-right
+ if (dx * 2 < -dy || dx == 0)
+ dir = UNIT_DIR_SOUTH;
+ else if (dx > -dy * 2 + 1 || dy == 0)
+ dir = UNIT_DIR_EAST;
+ else
+ dir = UNIT_DIR_SOUTHEAST;
} else if (dx <= 0 && dy <= 0) {
- // lower-left
- if( dx*2 > dy || dx == 0 ) dir = 4; // down
- else if( dx < dy*2-1 || dy == 0 ) dir = 2; // left
- else dir = 3; // down-left
+ if (dx * 2 > dy || dx == 0 )
+ dir = UNIT_DIR_SOUTH;
+ else if (dx < dy * 2 + 1 || dy == 0)
+ dir = UNIT_DIR_WEST;
+ else
+ dir = UNIT_DIR_SOUTHWEST;
} else {
- // upper-left
- if( -dx*2 < dy || dx == 0 ) dir = 0; // up
- else if( -dx > dy*2+1 || dy == 0) dir = 2; // left
- else dir = 1; // up-left
+ if (-dx * 2 < dy || dx == 0 )
+ dir = UNIT_DIR_NORTH;
+ else if (-dx > dy * 2 + 1 || dy == 0)
+ dir = UNIT_DIR_WEST;
+ else
+ dir = UNIT_DIR_NORTHWEST;
}
return dir;
}
@@ -2924,11 +2944,11 @@ static int map_random_dir(struct block_list *bl, int16 *x, int16 *y)
if (dist < 1) dist =1;
do {
- int j = 1 + 2*(rnd()%4); //Pick a random diagonal direction
+ enum unit_dir dir = unit_get_rnd_diagonal_dir();
short segment = 1+(rnd()%dist); //Pick a random interval from the whole vector in that direction
- xi = bl->x + segment*dirx[j];
+ xi = bl->x + segment * dirx[dir];
segment = (short)sqrt((float)(dist2 - segment*segment)); //The complement of the previously picked segment
- yi = bl->y + segment*diry[j];
+ yi = bl->y + segment * diry[dir];
} while ((map->getcell(bl->m, bl, xi, yi, CELL_CHKNOPASS) || !path->search(NULL, bl, bl->m, bl->x, bl->y, xi, yi, 1, CELL_CHKNOREACH))
&& (++i)<100);
@@ -4452,6 +4472,7 @@ static bool inter_config_read_database_names(const char *filename, const struct
libconfig->setting_lookup_mutable_string(setting, "autotrade_data_db", map->autotrade_data_db, sizeof(map->autotrade_data_db));
libconfig->setting_lookup_mutable_string(setting, "npc_market_data_db", map->npc_market_data_db, sizeof(map->npc_market_data_db));
libconfig->setting_lookup_mutable_string(setting, "npc_barter_data_db", map->npc_barter_data_db, sizeof(map->npc_barter_data_db));
+ libconfig->setting_lookup_mutable_string(setting, "npc_expanded_barter_data_db", map->npc_expanded_barter_data_db, sizeof(map->npc_expanded_barter_data_db));
if (!mapreg->config_read(filename, setting, imported))
retval = false;
@@ -6091,7 +6112,7 @@ static int cleanup_sub(struct block_list *bl, va_list ap)
map->quit(BL_UCAST(BL_PC, bl));
break;
case BL_NPC:
- npc->unload(BL_UCAST(BL_NPC, bl), false);
+ npc->unload(BL_UCAST(BL_NPC, bl), false, true);
break;
case BL_MOB:
unit->free(bl,CLR_OUTSIGHT);
@@ -6749,6 +6770,7 @@ int do_init(int argc, char *argv[])
npc->event_do_oninit( false ); // Init npcs (OnInit)
npc->market_fromsql(); /* after OnInit */
npc->barter_fromsql(); /* after OnInit */
+ npc->expanded_barter_fromsql(); /* after OnInit */
if (battle_config.pk_mode)
ShowNotice("Server is running on '"CL_WHITE"PK Mode"CL_RESET"'.\n");
diff --git a/src/map/map.h b/src/map/map.h
index 78f1a3c89..a876539d0 100644
--- a/src/map/map.h
+++ b/src/map/map.h
@@ -27,6 +27,7 @@
#include "common/db.h"
#include "common/mapindex.h"
#include "common/mmo.h"
+#include "map/unitdefines.h" // enum unit_dir
#include <stdio.h>
#include <stdarg.h>
@@ -1056,6 +1057,7 @@ struct map_interface {
char autotrade_data_db[32];
char npc_market_data_db[32];
char npc_barter_data_db[32];
+ char npc_expanded_barter_data_db[32];
char default_codepage[32];
char default_lang_str[64];
@@ -1215,8 +1217,8 @@ END_ZEROED_BLOCK;
// reload config file looking only for npcs
void (*reloadnpc) (bool clear);
- int (*check_dir) (int s_dir,int t_dir);
- uint8 (*calc_dir) (struct block_list *src,int16 x,int16 y);
+ int (*check_dir) (enum unit_dir s_dir, enum unit_dir t_dir);
+ enum unit_dir (*calc_dir) (const struct block_list *src, int16 x, int16 y);
int (*random_dir) (struct block_list *bl, short *x, short *y); // [Skotlex]
int (*cleanup_sub) (struct block_list *bl, va_list ap);
diff --git a/src/map/messages_main.h b/src/map/messages_main.h
index b2b67f08c..9f5a17662 100644
--- a/src/map/messages_main.h
+++ b/src/map/messages_main.h
@@ -24,7 +24,7 @@
/* This file is autogenerated, please do not commit manual changes
-Latest version: 20200108
+Latest version: 20200304
*/
enum clif_messages {
@@ -22464,6 +22464,154 @@ Search
*/
MSG_ID_EC1 = 0xec1,
#endif
+#if PACKETVER >= 20200122
+/*20200122 to latest
+리서치 리포트 상태가 됩니다.
+*/
+ MSG_ID_EC2 = 0xec2,
+/*20200122 to latest
+리서치 리포트 상태가 해제됩니다.
+*/
+ MSG_ID_EC3 = 0xec3,
+/*20200122 to latest
+제조에 성공 했습니다.
+*/
+ MSG_ID_EC4 = 0xec4,
+/*20200122 to latest
+제조에 실패 했습니다.
+*/
+ MSG_ID_EC5 = 0xec5,
+/*20200122 to latest
+쉐도우 장비가 파괴 및 해제에서 보호됩니다.
+*/
+ MSG_ID_EC6 = 0xec6,
+/*20200122 to latest
+풀 쉐도우 프로텍션이 해제됩니다.
+*/
+ MSG_ID_EC7 = 0xec7,
+/*20200122 to latest
+식물형, 무형 몬스터에게 주는 데미지가 증가합니다.
+*/
+ MSG_ID_EC8 = 0xec8,
+/*20200122 to latest
+지옥 나무의 가루효과가 사라집니다.
+*/
+ MSG_ID_EC9 = 0xec9,
+#endif
+#if PACKETVER >= 20200129
+/*20200129 to latest
+공격 장치가 활성화되었습니다.
+*/
+ MSG_ID_ECA = 0xeca,
+/*20200129 to latest
+공격 장치가 해제되었습니다.
+*/
+ MSG_ID_ECB = 0xecb,
+/*20200129 to latest
+물리 방어력 및 물리 저항력이 증가되었습니다.
+*/
+ MSG_ID_ECC = 0xecc,
+/*20200129 to latest
+방어 장치가 해제되었습니다.
+*/
+ MSG_ID_ECD = 0xecd,
+/*20200129 to latest
+검색
+Search
+*/
+ MSG_ID_ECE = 0xece,
+#endif
+#if PACKETVER >= 20200212
+/*20200212 to latest
+합주를 혼자 사용할 수 있습니다.
+*/
+ MSG_ID_ECF = 0xecf,
+/*20200212 to latest
+크바시르의 지혜가 사라집니다.
+*/
+ MSG_ID_ED0 = 0xed0,
+/*20200212 to latest
+미스틱 심포니의 효과가 부여됩니다.
+*/
+ MSG_ID_ED1 = 0xed1,
+/*20200212 to latest
+미스틱 심포니의 효과가 사라집니다.
+*/
+ MSG_ID_ED2 = 0xed2,
+/*20200212 to latest
+마법 저항력이 감소했습니다.
+*/
+ MSG_ID_ED3 = 0xed3,
+/*20200212 to latest
+게페니아 녹턴의 효과가 해제 되었습니다.
+*/
+ MSG_ID_ED4 = 0xed4,
+/*20200212 to latest
+물리 저항력이 감소했습니다.
+*/
+ MSG_ID_ED5 = 0xed5,
+/*20200212 to latest
+마인워커 랩소디 상태가 해제되었습니다.
+*/
+ MSG_ID_ED6 = 0xed6,
+/*20200212 to latest
+물리 저항력이 증가했습니다.
+*/
+ MSG_ID_ED7 = 0xed7,
+/*20200212 to latest
+뮤지컬 인터루드 상태가 해제되었습니다.
+*/
+ MSG_ID_ED8 = 0xed8,
+/*20200212 to latest
+특성 마법 공격력과 이동 속도가 증가합니다.
+*/
+ MSG_ID_ED9 = 0xed9,
+/*20200212 to latest
+저녁 노을의 세레나데 효과가 해제되었습니다.
+*/
+ MSG_ID_EDA = 0xeda,
+/*20200212 to latest
+특성 물리 공격력과 이동 속도가 증가합니다.
+*/
+ MSG_ID_EDB = 0xedb,
+/*20200212 to latest
+ 프론테라의 행진곡 효과가 해제되었습니다.
+*/
+ MSG_ID_EDC = 0xedc,
+/*20200212 to latest
+바람의 분노가 시전자에게 흘러 들어옵니다.
+*/
+ MSG_ID_EDD = 0xedd,
+/*20200212 to latest
+캘러미티 가일 상태가 해제되었습니다.
+*/
+ MSG_ID_EDE = 0xede,
+/*20200212 to latest
+바람에 의해 약점과 모습이 드러납니다.
+*/
+ MSG_ID_EDF = 0xedf,
+/*20200212 to latest
+윈드 사인 효과가 사라집니다.
+*/
+ MSG_ID_EE0 = 0xee0,
+#endif
+#if PACKETVER >= 20200304
+/*20200304 to latest
+E X P : %.1f%% ( basic 100.0%% %s %.1f%%)
+EXP: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE1 = 0xee1,
+/*20200304 to latest
+DROP : %.1f%% ( basic 100.0%% %s %.1f%%)
+DROP: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE2 = 0xee2,
+/*20200304 to latest
+DEATH : %.1f%% ( basic 100.0%% %s %.1f%%)
+DEATH: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE3 = 0xee3,
+#endif
};
#endif /* MAP_MESSAGES_MAIN_H */
diff --git a/src/map/messages_re.h b/src/map/messages_re.h
index 8cb3b6624..e32f6b275 100644
--- a/src/map/messages_re.h
+++ b/src/map/messages_re.h
@@ -24,7 +24,7 @@
/* This file is autogenerated, please do not commit manual changes
-Latest version: 20200108
+Latest version: 20200304
*/
enum clif_messages {
@@ -21941,6 +21941,154 @@ Search
*/
MSG_ID_EC1 = 0xec1,
#endif
+#if PACKETVER >= 20200122
+/*20200122 to latest
+리서치 리포트 상태가 됩니다.
+*/
+ MSG_ID_EC2 = 0xec2,
+/*20200122 to latest
+리서치 리포트 상태가 해제됩니다.
+*/
+ MSG_ID_EC3 = 0xec3,
+/*20200122 to latest
+제조에 성공 했습니다.
+*/
+ MSG_ID_EC4 = 0xec4,
+/*20200122 to latest
+제조에 실패 했습니다.
+*/
+ MSG_ID_EC5 = 0xec5,
+/*20200122 to latest
+쉐도우 장비가 파괴 및 해제에서 보호됩니다.
+*/
+ MSG_ID_EC6 = 0xec6,
+/*20200122 to latest
+풀 쉐도우 프로텍션이 해제됩니다.
+*/
+ MSG_ID_EC7 = 0xec7,
+/*20200122 to latest
+식물형, 무형 몬스터에게 주는 데미지가 증가합니다.
+*/
+ MSG_ID_EC8 = 0xec8,
+/*20200122 to latest
+지옥 나무의 가루효과가 사라집니다.
+*/
+ MSG_ID_EC9 = 0xec9,
+#endif
+#if PACKETVER >= 20200205
+/*20200205 to latest
+공격 장치가 활성화되었습니다.
+*/
+ MSG_ID_ECA = 0xeca,
+/*20200205 to latest
+공격 장치가 해제되었습니다.
+*/
+ MSG_ID_ECB = 0xecb,
+/*20200205 to latest
+물리 방어력 및 물리 저항력이 증가되었습니다.
+*/
+ MSG_ID_ECC = 0xecc,
+/*20200205 to latest
+방어 장치가 해제되었습니다.
+*/
+ MSG_ID_ECD = 0xecd,
+/*20200205 to latest
+검색
+Search
+*/
+ MSG_ID_ECE = 0xece,
+#endif
+#if PACKETVER >= 20200212
+/*20200212 to latest
+합주를 혼자 사용할 수 있습니다.
+*/
+ MSG_ID_ECF = 0xecf,
+/*20200212 to latest
+크바시르의 지혜가 사라집니다.
+*/
+ MSG_ID_ED0 = 0xed0,
+/*20200212 to latest
+미스틱 심포니의 효과가 부여됩니다.
+*/
+ MSG_ID_ED1 = 0xed1,
+/*20200212 to latest
+미스틱 심포니의 효과가 사라집니다.
+*/
+ MSG_ID_ED2 = 0xed2,
+/*20200212 to latest
+마법 저항력이 감소했습니다.
+*/
+ MSG_ID_ED3 = 0xed3,
+/*20200212 to latest
+게페니아 녹턴의 효과가 해제 되었습니다.
+*/
+ MSG_ID_ED4 = 0xed4,
+/*20200212 to latest
+물리 저항력이 감소했습니다.
+*/
+ MSG_ID_ED5 = 0xed5,
+/*20200212 to latest
+마인워커 랩소디 상태가 해제되었습니다.
+*/
+ MSG_ID_ED6 = 0xed6,
+/*20200212 to latest
+물리 저항력이 증가했습니다.
+*/
+ MSG_ID_ED7 = 0xed7,
+/*20200212 to latest
+뮤지컬 인터루드 상태가 해제되었습니다.
+*/
+ MSG_ID_ED8 = 0xed8,
+/*20200212 to latest
+특성 마법 공격력과 이동 속도가 증가합니다.
+*/
+ MSG_ID_ED9 = 0xed9,
+/*20200212 to latest
+저녁 노을의 세레나데 효과가 해제되었습니다.
+*/
+ MSG_ID_EDA = 0xeda,
+/*20200212 to latest
+특성 물리 공격력과 이동 속도가 증가합니다.
+*/
+ MSG_ID_EDB = 0xedb,
+/*20200212 to latest
+ 프론테라의 행진곡 효과가 해제되었습니다.
+*/
+ MSG_ID_EDC = 0xedc,
+/*20200212 to latest
+바람의 분노가 시전자에게 흘러 들어옵니다.
+*/
+ MSG_ID_EDD = 0xedd,
+/*20200212 to latest
+캘러미티 가일 상태가 해제되었습니다.
+*/
+ MSG_ID_EDE = 0xede,
+/*20200212 to latest
+바람에 의해 약점과 모습이 드러납니다.
+*/
+ MSG_ID_EDF = 0xedf,
+/*20200212 to latest
+윈드 사인 효과가 사라집니다.
+*/
+ MSG_ID_EE0 = 0xee0,
+#endif
+#if PACKETVER >= 20200304
+/*20200304 to latest
+E X P : %.1f%% ( basic 100.0%% %s %.1f%%)
+EXP: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE1 = 0xee1,
+/*20200304 to latest
+DROP : %.1f%% ( basic 100.0%% %s %.1f%%)
+DROP: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE2 = 0xee2,
+/*20200304 to latest
+DEATH : %.1f%% ( basic 100.0%% %s %.1f%%)
+DEATH: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE3 = 0xee3,
+#endif
};
#endif /* MAP_MESSAGES_RE_H */
diff --git a/src/map/messages_zero.h b/src/map/messages_zero.h
index 18aed7d5b..babe9384c 100644
--- a/src/map/messages_zero.h
+++ b/src/map/messages_zero.h
@@ -24,7 +24,7 @@
/* This file is autogenerated, please do not commit manual changes
-Latest version: 20191224
+Latest version: 20200304
*/
enum clif_messages {
@@ -6734,9 +6734,11 @@ The Memorial Dungeon's entry time limit expired; it has been destroyed.
The Memorial Dungeon has been removed.
*/
MSG_MEMORIAL_DUN_DESTROY_REQUEST = 0x544,
-/*20171018 to latest
+/*20171018 to 20191224
메모리얼 던전에 시스템 오류가 발생하였습니다. 정상적인 게임 진행을 위해 재접속을 해주십시오.
A system error has occurred in the Memorial Dungeon. Please relog in to the game to continue playing.
+20200115 to latest
+메모리얼 던전에 통신 장애가 발생하였습니다. 정상적인 게임 진행을 위해 잠시 후, 재접속을 해주십시오.
*/
MSG_MEMORIAL_DUN_ERROR = 0x545,
/*20171018 to latest
@@ -18319,8 +18321,10 @@ VTC 인증에 실패하였습니다.
가나다 정렬
*/
MSG_ID_E8A = 0xe8a,
-/*20191113 to latest
+/*20191113 to 20191224
기본 결과물은 %s %d개 이나, 낮은 확률로 최대 %d개까지 생성될 수 있습니다.
+20200115 to latest
+※[%s] %d~%d개 제작
*/
MSG_ID_E8B = 0xe8b,
/*20191113 to latest
@@ -18536,6 +18540,167 @@ https://member.gnjoy.com.tw/billing.aspx
*/
MSG_ID_EBE = 0xebe,
#endif
+#if PACKETVER >= 20200115
+/*20200115 to latest
+역순 정렬
+*/
+ MSG_ID_EBF = 0xebf,
+/*20200115 to latest
+검색 내용 입력
+*/
+ MSG_ID_EC0 = 0xec0,
+/*20200115 to latest
+검색
+Search
+*/
+ MSG_ID_EC1 = 0xec1,
+/*20200115 to latest
+리서치 리포트 상태가 됩니다.
+*/
+ MSG_ID_EC2 = 0xec2,
+/*20200115 to latest
+리서치 리포트 상태가 해제됩니다.
+*/
+ MSG_ID_EC3 = 0xec3,
+/*20200115 to latest
+제조에 성공 했습니다.
+*/
+ MSG_ID_EC4 = 0xec4,
+/*20200115 to latest
+제조에 실패 했습니다.
+*/
+ MSG_ID_EC5 = 0xec5,
+/*20200115 to latest
+쉐도우 장비가 파괴 및 해제에서 보호됩니다.
+*/
+ MSG_ID_EC6 = 0xec6,
+/*20200115 to latest
+풀 쉐도우 프로텍션이 해제됩니다.
+*/
+ MSG_ID_EC7 = 0xec7,
+/*20200115 to latest
+식물형, 무형 몬스터에게 주는 데미지가 증가합니다.
+*/
+ MSG_ID_EC8 = 0xec8,
+/*20200115 to latest
+지옥 나무의 가루효과가 사라집니다.
+*/
+ MSG_ID_EC9 = 0xec9,
+#endif
+#if PACKETVER >= 20200129
+/*20200129 to latest
+공격 장치가 활성화되었습니다.
+*/
+ MSG_ID_ECA = 0xeca,
+/*20200129 to latest
+공격 장치가 해제되었습니다.
+*/
+ MSG_ID_ECB = 0xecb,
+/*20200129 to latest
+물리 방어력 및 물리 저항력이 증가되었습니다.
+*/
+ MSG_ID_ECC = 0xecc,
+/*20200129 to latest
+방어 장치가 해제되었습니다.
+*/
+ MSG_ID_ECD = 0xecd,
+#endif
+#if PACKETVER >= 20200212
+/*20200212 to latest
+검색
+Search
+*/
+ MSG_ID_ECE = 0xece,
+/*20200212 to latest
+합주를 혼자 사용할 수 있습니다.
+*/
+ MSG_ID_ECF = 0xecf,
+/*20200212 to latest
+크바시르의 지혜가 사라집니다.
+*/
+ MSG_ID_ED0 = 0xed0,
+/*20200212 to latest
+미스틱 심포니의 효과가 부여됩니다.
+*/
+ MSG_ID_ED1 = 0xed1,
+/*20200212 to latest
+미스틱 심포니의 효과가 사라집니다.
+*/
+ MSG_ID_ED2 = 0xed2,
+/*20200212 to latest
+마법 저항력이 감소했습니다.
+*/
+ MSG_ID_ED3 = 0xed3,
+/*20200212 to latest
+게페니아 녹턴의 효과가 해제 되었습니다.
+*/
+ MSG_ID_ED4 = 0xed4,
+/*20200212 to latest
+물리 저항력이 감소했습니다.
+*/
+ MSG_ID_ED5 = 0xed5,
+/*20200212 to latest
+마인워커 랩소디 상태가 해제되었습니다.
+*/
+ MSG_ID_ED6 = 0xed6,
+/*20200212 to latest
+물리 저항력이 증가했습니다.
+*/
+ MSG_ID_ED7 = 0xed7,
+/*20200212 to latest
+뮤지컬 인터루드 상태가 해제되었습니다.
+*/
+ MSG_ID_ED8 = 0xed8,
+/*20200212 to latest
+특성 마법 공격력과 이동 속도가 증가합니다.
+*/
+ MSG_ID_ED9 = 0xed9,
+/*20200212 to latest
+저녁 노을의 세레나데 효과가 해제되었습니다.
+*/
+ MSG_ID_EDA = 0xeda,
+/*20200212 to latest
+특성 물리 공격력과 이동 속도가 증가합니다.
+*/
+ MSG_ID_EDB = 0xedb,
+/*20200212 to latest
+ 프론테라의 행진곡 효과가 해제되었습니다.
+*/
+ MSG_ID_EDC = 0xedc,
+/*20200212 to latest
+바람의 분노가 시전자에게 흘러 들어옵니다.
+*/
+ MSG_ID_EDD = 0xedd,
+/*20200212 to latest
+캘러미티 가일 상태가 해제되었습니다.
+*/
+ MSG_ID_EDE = 0xede,
+/*20200212 to latest
+바람에 의해 약점과 모습이 드러납니다.
+*/
+ MSG_ID_EDF = 0xedf,
+/*20200212 to latest
+윈드 사인 효과가 사라집니다.
+*/
+ MSG_ID_EE0 = 0xee0,
+#endif
+#if PACKETVER >= 20200304
+/*20200304 to latest
+E X P : %.1f%% ( basic 100.0%% %s %.1f%%)
+EXP: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE1 = 0xee1,
+/*20200304 to latest
+DROP : %.1f%% ( basic 100.0%% %s %.1f%%)
+DROP: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE2 = 0xee2,
+/*20200304 to latest
+DEATH : %.1f%% ( basic 100.0%% %s %.1f%%)
+DEATH: %.1f%% (basic: 100.0%%, %s: %.1f%%)
+*/
+ MSG_ID_EE3 = 0xee3,
+#endif
};
#endif /* MAP_MESSAGES_ZERO_H */
diff --git a/src/map/mob.c b/src/map/mob.c
index 4b74abc8f..51a32abd9 100644
--- a/src/map/mob.c
+++ b/src/map/mob.c
@@ -337,15 +337,23 @@ static int mob_parse_dataset(struct spawn_data *data)
return 1;
}
-/*==========================================
- * Generates the basic mob data using the spawn_data provided.
- *------------------------------------------*/
-static struct mob_data *mob_spawn_dataset(struct spawn_data *data)
+
+/**
+ * Generates basic mob data by using the passed spawn data.
+ *
+ * @param data The mobs spawn data.
+ * @param npc_id If spawned by NPC script, this holds the ID of the invoking NPC.
+ * @return The generated mob data, later used to spawn the mob.
+ *
+ **/
+static struct mob_data *mob_spawn_dataset(struct spawn_data *data, int npc_id)
{
- struct mob_data *md = NULL;
nullpo_retr(NULL, data);
- CREATE(md, struct mob_data, 1);
- md->bl.id= npc->get_new_npc_id();
+
+ struct mob_data *md = (struct mob_data *)aCalloc(1, sizeof(struct mob_data));
+
+ memcpy(md->name, data->name, NAME_LENGTH);
+ md->bl.id = npc->get_new_npc_id();
md->bl.type = BL_MOB;
md->bl.m = data->m;
md->bl.x = data->x;
@@ -353,24 +361,29 @@ static struct mob_data *mob_spawn_dataset(struct spawn_data *data)
md->class_ = data->class_;
md->state.boss = data->state.boss;
md->db = mob->db(md->class_);
+ md->npc_id = npc_id;
+ md->spawn_timer = INVALID_TIMER;
+ md->deletetimer = INVALID_TIMER;
+ md->skill_idx = -1;
+
if (data->level > 0 && data->level <= MAX_LEVEL)
md->level = data->level;
- memcpy(md->name, data->name, NAME_LENGTH);
- if (data->state.ai)
+
+ if (data->state.ai > 0)
md->special_state.ai = data->state.ai;
- if (data->state.size)
+
+ if (data->state.size > 0)
md->special_state.size = data->state.size;
- if (data->eventname[0] && strlen(data->eventname) >= 4)
+
+ if (data->eventname[0] != '\0' && strlen(data->eventname) >= 4)
memcpy(md->npc_event, data->eventname, 50);
- if(md->db->status.mode&MD_LOOTER)
- md->lootitem = (struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item));
- md->spawn_timer = INVALID_TIMER;
- md->deletetimer = INVALID_TIMER;
- md->skill_idx = -1;
+
+ if ((md->db->status.mode & MD_LOOTER) == MD_LOOTER)
+ md->lootitem = (struct item *)aCalloc(LOOTITEM_SIZE, sizeof(struct item));
+
status->set_viewdata(&md->bl, md->class_);
status->change_init(&md->bl);
unit->dataset(&md->bl);
-
map->addiddb(&md->bl);
return md;
}
@@ -503,7 +516,24 @@ static bool mob_ksprotected(struct block_list *src, struct block_list *target)
return false;
}
-static struct mob_data *mob_once_spawn_sub(struct block_list *bl, int16 m, int16 x, int16 y, const char *mobname, int class_, const char *event, unsigned int size, unsigned int ai)
+/**
+ * Prepares a mob's spawn data.
+ *
+ * @param bl The invoking character's block list.
+ * @param m The ID of the map where the mob should be spawned.
+ * @param x The x coordinate where the mob should be spawned.
+ * @param y The y coordinate where the mob should be spawned.
+ * @param mobname The mob's display name.
+ * @param class_ The mob's ID in database.
+ * @param event The name of the event which should be executed when the mob is killed.
+ * @param size The mob's size.
+ * @param ai The mob's AI.
+ * @param npc_id If spawned by NPC script, this holds the ID of the invoking NPC.
+ * @return The mob data generated by mob->spawn_dataset().
+ *
+ **/
+static struct mob_data *mob_once_spawn_sub(struct block_list *bl, int16 m, int16 x, int16 y, const char *mobname, int class_,
+ const char *event, unsigned int size, unsigned int ai, int npc_id)
{
struct spawn_data data;
@@ -521,76 +551,100 @@ static struct mob_data *mob_once_spawn_sub(struct block_list *bl, int16 m, int16
else
strcpy(data.name, DEFAULT_MOB_JNAME);
- if (event)
+ if (event != NULL)
safestrncpy(data.eventname, event, sizeof(data.eventname));
- // Locate spot next to player.
- if (bl && (x < 0 || y < 0))
+ /** Locate spot next to player. **/
+ if (bl != NULL && (x < 0 || y < 0))
map->search_freecell(bl, m, &x, &y, 1, 1, 0);
- // if none found, pick random position on map
+ /** If none found, pick random position on map. **/
if (x <= 0 || x >= map->list[m].xs || y <= 0 || y >= map->list[m].ys)
map->search_freecell(NULL, m, &x, &y, -1, -1, 1);
data.x = x;
data.y = y;
- if (!mob->parse_dataset(&data))
+ if (mob->parse_dataset(&data) == 0)
return NULL;
- return mob->spawn_dataset(&data);
+ return mob->spawn_dataset(&data, npc_id);
}
-/*==========================================
- * Spawn a single mob on the specified coordinates.
- *------------------------------------------*/
-static int mob_once_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *mobname, int class_, int amount, const char *event, unsigned int size, unsigned int ai)
+/**
+ * Spawns a given amount of mobs.
+ *
+ * @param sd The invoking character.
+ * @param m The ID of the map where the mob should be spawned.
+ * @param x The x coordinate where the mob should be spawned.
+ * @param y The y coordinate where the mob should be spawned.
+ * @param mobname The mob's display name.
+ * @param class_ The mob's ID in database.
+ * @param amount The amount of mobs to spawn.
+ * @param event The name of the event which should be executed when the mob is killed.
+ * @param size The mob's size.
+ * @param ai The mob's AI.
+ * @return The last spawned mob's GID, or 0 if spawning failed.
+ *
+ **/
+static int mob_once_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *mobname, int class_,
+ int amount, const char *event, unsigned int size, unsigned int ai)
{
- struct mob_data* md = NULL;
- int count, lv;
bool no_guardian_data = false;
- if( ai && ai&0x200 ) {
+ if (ai > 0 && (ai & 0x200) == 0x200) {
no_guardian_data = true;
- ai &=~ 0x200;
+ ai &= ~0x200;
}
if (m < 0 || amount <= 0)
- return 0; // invalid input
+ return 0;
- lv = (sd) ? sd->status.base_level : 255;
+ struct mob_data *md = NULL;
- for (count = 0; count < amount; count++) {
- int c = (class_ >= 0) ? class_ : mob->get_random_id(-class_ - 1, (battle_config.random_monster_checklv) ? 3 : 1, lv);
- md = mob->once_spawn_sub((sd) ? &sd->bl : NULL, m, x, y, mobname, c, event, size, ai);
+ for (int i = 0; i < amount; i++) {
+ int mob_id = class_;
- if (!md)
+ if (mob_id < 0) {
+ mob_id = mob->get_random_id(-class_ - 1, (battle_config.random_monster_checklv == 1) ? 3 : 1,
+ (sd != NULL) ? sd->status.base_level : 255);
+ }
+
+ md = mob->once_spawn_sub((sd != NULL) ? &sd->bl : NULL, m, x, y, mobname, mob_id, event, size, ai,
+ (sd != NULL) ? sd->npc_id : 0);
+
+ if (md == NULL)
continue;
if (class_ == MOBID_EMPELIUM && !no_guardian_data) {
- struct guild_castle* gc = guild->mapindex2gc(map_id2index(m));
- struct guild* g = (gc) ? guild->search(gc->guild_id) : NULL;
- if( gc ) {
+ struct guild_castle *gc = guild->mapindex2gc(map_id2index(m));
+
+ if (gc != NULL) {
+ struct guild *g = guild->search(gc->guild_id);
+
md->guardian_data = (struct guardian_data*)aCalloc(1, sizeof(struct guardian_data));
md->guardian_data->castle = gc;
md->guardian_data->number = MAX_GUARDIANS;
- if( g )
+
+ if (g != NULL)
md->guardian_data->g = g;
- else if( gc->guild_id ) //Guild not yet available, retry in 5.
- timer->add(timer->gettick()+5000,mob->spawn_guardian_sub,md->bl.id,gc->guild_id);
+ else if (gc->guild_id > 0) /// Guild not yet available, retry in 5s.
+ timer->add(timer->gettick() + 5000, mob->spawn_guardian_sub, md->bl.id,
+ gc->guild_id);
}
- } // end addition [Valaris]
+ }
mob->spawn(md);
- if (class_ < 0 && battle_config.dead_branch_active) {
- //Behold Aegis's masterful decisions yet again...
- //"I understand the "Aggressive" part, but the "Can Move" and "Can Attack" is just stupid" - Poki#3
- sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE|MD_CANATTACK|MD_CANMOVE|MD_ANGRY, 0, 60000);
+ if (class_ < 0 && battle_config.dead_branch_active == 1) {
+ /// Behold Aegis' masterful decisions yet again...
+ /// "I understand the "Aggressive" part, but the "Can Move" and "Can Attack" is just stupid" [Poki]
+ sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE|MD_CANATTACK|MD_CANMOVE|MD_ANGRY,
+ 0, 60000);
}
}
- return (md) ? md->bl.id : 0; // id of last spawned mob
+ return (md != NULL) ? md->bl.id : 0; /// ID of last spawned mob.
}
/*==========================================
@@ -649,196 +703,236 @@ static int mob_once_spawn_area(struct map_session_data *sd, int16 m, int16 x0, i
}
/**
- * Sets a guardian's guild data and liberates castle if couldn't retrieve guild data
- * @param data (int)guild_id
- * @retval Always 0
+ * Sets a guardian's guild data and liberates castle if couldn't retrieve guild data.
+ * Required because the guild data may not be available at guardian spawn time.
+ *
+ * @param tid Required parameter for timer functions. Unused inside the function.
+ * @param tick Required parameter for timer functions. Unused inside the function.
+ * @param id The guardian mob's GID.
+ * @param data The guild ID.
+ * @return 1 on success, 0 on failure.
+ *
* @author Skotlex
+ *
**/
static int mob_spawn_guardian_sub(int tid, int64 tick, int id, intptr_t data)
{
- //Needed because the guild data may not be available at guardian spawn time.
- struct block_list* bl = map->id2bl(id);
- struct mob_data* md;
- struct guild* g;
+ struct block_list *bl = map->id2bl(id);
- if( bl == NULL ) //It is possible mob was already removed from map when the castle has no owner. [Skotlex]
+ if (bl == NULL || bl->type != BL_MOB) /// It is possible mob was already removed from map when the castle has no owner. [Skotlex]
return 0;
- Assert_ret(bl->type == BL_MOB);
- md = BL_UCAST(BL_MOB, bl);
+ struct mob_data *md = BL_UCAST(BL_MOB, bl);
+
+ if (md->guardian_data == NULL)
+ return 0;
- nullpo_ret(md->guardian_data);
- g = guild->search((int)data);
+ struct guild *g = guild->search((int)data);
- if( g == NULL ) { //Liberate castle, if the guild is not found this is an error! [Skotlex]
+ if (g == NULL) { /// Liberate castle, if the guild is not found this is an error! [Skotlex]
ShowError("mob_spawn_guardian_sub: Couldn't load guild %d!\n", (int)data);
- //Not sure this is the best way, but otherwise we'd be invoking this for ALL guardians spawned later on.
- if (md->class_ == MOBID_EMPELIUM && md->guardian_data) {
+
+ /// Not sure this is the best way, but otherwise we'd be invoking this for ALL guardians spawned later on.
+ if (md->class_ == MOBID_EMPELIUM) {
md->guardian_data->g = NULL;
- if( md->guardian_data->castle->guild_id ) {//Free castle up.
- ShowNotice("Clearing ownership of castle %d (%s)\n", md->guardian_data->castle->castle_id, md->guardian_data->castle->castle_name);
+
+ if (md->guardian_data->castle->guild_id > 0) { /// Free castle up.
+ ShowNotice("mob_spawn_guardian_sub: Clearing ownership of castle %d (%s).\n",
+ md->guardian_data->castle->castle_id,
+ md->guardian_data->castle->castle_name);
guild->castledatasave(md->guardian_data->castle->castle_id, 1, 0);
}
} else {
- if( md->guardian_data && md->guardian_data->number >= 0 && md->guardian_data->number < MAX_GUARDIANS
- && md->guardian_data->castle->guardian[md->guardian_data->number].visible )
- guild->castledatasave(md->guardian_data->castle->castle_id, 10+md->guardian_data->number,0);
+ if (md->guardian_data->number >= 0 && md->guardian_data->number < MAX_GUARDIANS &&
+ md->guardian_data->castle->guardian[md->guardian_data->number].visible == 1)
+ guild->castledatasave(md->guardian_data->castle->castle_id,
+ 10 + md->guardian_data->number, 0);
- unit->free(&md->bl,CLR_OUTSIGHT); // Remove guardian.
+ unit->free(&md->bl, CLR_OUTSIGHT); /// Remove guardian.
}
+
return 0;
}
- if( guild->checkskill(g,GD_GUARDUP) )
- status_calc_mob(md, SCO_NONE); // Give bonuses.
+ if (guild->checkskill(g, GD_GUARDUP) > 0)
+ status_calc_mob(md, SCO_NONE); /// Give bonuses.
- return 0;
+ return 1;
}
-/*==========================================
- * Summoning Guardians [Valaris]
- *------------------------------------------*/
-static int mob_spawn_guardian(const char *mapname, short x, short y, const char *mobname, int class_, const char *event, int guardian, bool has_index)
+/**
+ * Summons a castle guardian mob.
+ *
+ * @param mapname The name of the map where the guardian should be spawned.
+ * @param x The x coordinate where the guardian should be spawned.
+ * @param y The y coordinate where the guardian should be spawned.
+ * @param mobname The guardian's display name.
+ * @param class_ The guardian's mob ID in database.
+ * @param event The name of the event which should be executed when the guardian is killed.
+ * @param guardian The guardian's index.
+ * @param has_index If false, the guardian will be temporarily.
+ * @param npc_id If spawned by NPC script, this holds the ID of the invoking NPC.
+ * @return The spawned guardian's GID, or 0 if spawning failed.
+ *
+ * @author Valaris
+ *
+ **/
+static int mob_spawn_guardian(const char *mapname, short x, short y, const char *mobname, int class_, const char *event,
+ int guardian, bool has_index, int npc_id)
{
- struct mob_data *md=NULL;
- struct spawn_data data;
- struct guild *g=NULL;
- struct guild_castle *gc;
- int16 m;
-
nullpo_ret(mapname);
nullpo_ret(mobname);
nullpo_ret(event);
- memset(&data, 0, sizeof(struct spawn_data));
- data.num = 1;
-
- m=map->mapname2mapid(mapname);
+ const int map_id = map->mapname2mapid(mapname);
- if(m<0)
- {
+ if (map_id == INDEX_NOT_FOUND) {
ShowWarning("mob_spawn_guardian: Map [%s] not found.\n", mapname);
return 0;
}
- data.m = m;
- data.num = 1;
- if(class_<=0) {
- class_ = mob->get_random_id(-class_-1, 1, 99);
- if (!class_) return 0;
+
+ if ((x <= 0 || y <= 0) && map->search_freecell(NULL, map_id, &x, &y, -1, -1, 1) == 0) {
+ ShowWarning("mob_spawn_guardian: Couldn't locate a spawn cell for guardian class %d (index %d) on castle map %s.\n",
+ class_, guardian, mapname);
+ return 0;
}
- data.class_ = class_;
+ if (class_ <= 0 && (class_ = mob->get_random_id(-class_ - 1, 1, 99)) == 0)
+ return 0;
- if( !has_index ) {
+ if (!has_index) {
guardian = -1;
- } else if( guardian < 0 || guardian >= MAX_GUARDIANS ) {
- ShowError("mob_spawn_guardian: Invalid guardian index %d for guardian %d (castle map %s)\n", guardian, class_, map->list[m].name);
+ } else if (guardian < 0 || guardian >= MAX_GUARDIANS) {
+ ShowError("mob_spawn_guardian: Invalid guardian index %d for guardian %d on castle map %s.\n",
+ guardian, class_, mapname);
return 0;
}
- if((x<=0 || y<=0) && !map->search_freecell(NULL, m, &x, &y, -1,-1, 1)) {
- ShowWarning("mob_spawn_guardian: Couldn't locate a spawn cell for guardian class %d (index %d) at castle map %s\n",class_, guardian, map->list[m].name);
- return 0;
- }
+ struct spawn_data data;
+
+ memset(&data, 0, sizeof(struct spawn_data));
+ data.num = 1;
+ data.m = map_id;
+ data.class_ = class_;
data.x = x;
data.y = y;
safestrncpy(data.name, mobname, sizeof(data.name));
safestrncpy(data.eventname, event, sizeof(data.eventname));
- if (!mob->parse_dataset(&data))
+
+ if (mob->parse_dataset(&data) == 0)
return 0;
- gc=guild->mapname2gc(map->list[m].name);
+ struct guild_castle *gc = guild->mapname2gc(mapname);
+
if (gc == NULL) {
- ShowError("mob_spawn_guardian: No castle set at map %s\n", map->list[m].name);
+ ShowError("mob_spawn_guardian: No castle set on map %s.\n", mapname);
return 0;
}
- if (!gc->guild_id)
- ShowWarning("mob_spawn_guardian: Spawning guardian %d on a castle with no guild (castle map %s)\n", class_, map->list[m].name);
+
+ struct guild *g = NULL;
+
+ if (gc->guild_id == 0)
+ ShowWarning("mob_spawn_guardian: Spawning guardian %d on a castle map %s with no guild.\n",
+ class_, mapname);
else
g = guild->search(gc->guild_id);
- if( has_index && gc->guardian[guardian].id ) {
- //Check if guardian already exists, refuse to spawn if so.
- struct block_list *bl2 = map->id2bl(gc->guardian[guardian].id); // TODO: Why does this not use map->id2md?
- struct mob_data *md2 = BL_CAST(BL_MOB, bl2);
- if (md2 != NULL && md2->guardian_data != NULL && md2->guardian_data->number == guardian) {
- ShowError("mob_spawn_guardian: Attempted to spawn guardian in position %d which already has a guardian (castle map %s)\n", guardian, map->list[m].name);
+ if (has_index && gc->guardian[guardian].id != 0) { /// Check if guardian already exists, refuse to spawn if so.
+ struct mob_data *md = map->id2md(gc->guardian[guardian].id);
+
+ if (md != NULL && md->guardian_data != NULL && md->guardian_data->number == guardian) {
+ ShowError("mob_spawn_guardian: Attempted to spawn guardian in position %d which already has a guardian on castle map %s.\n",
+ guardian, mapname);
return 0;
}
}
- md = mob->spawn_dataset(&data);
+ struct mob_data *md = mob->spawn_dataset(&data, npc_id);
+
md->guardian_data = (struct guardian_data*)aCalloc(1, sizeof(struct guardian_data));
md->guardian_data->number = guardian;
md->guardian_data->castle = gc;
- if( has_index )
- {// permanent guardian
+
+ if (has_index) { /// Permanent guardian.
gc->guardian[guardian].id = md->bl.id;
- }
- else
- {// temporary guardian
+ } else { /// Temporary guardian.
int i;
+
ARR_FIND(0, gc->temp_guardians_max, i, gc->temp_guardians[i] == 0);
- if( i == gc->temp_guardians_max )
- {
+
+ if (i == gc->temp_guardians_max) {
++(gc->temp_guardians_max);
RECREATE(gc->temp_guardians, int, gc->temp_guardians_max);
}
+
gc->temp_guardians[i] = md->bl.id;
}
- if( g )
+
+ if (g != NULL)
md->guardian_data->g = g;
- else if( gc->guild_id )
- timer->add(timer->gettick()+5000,mob->spawn_guardian_sub,md->bl.id,gc->guild_id);
- mob->spawn(md);
+ else if (gc->guild_id > 0)
+ timer->add(timer->gettick() + 5000, mob->spawn_guardian_sub, md->bl.id, gc->guild_id);
+ mob->spawn(md);
return md->bl.id;
}
-/*==========================================
- * Summoning BattleGround [Zephyrus]
- *------------------------------------------*/
-static int mob_spawn_bg(const char *mapname, short x, short y, const char *mobname, int class_, const char *event, unsigned int bg_id)
+/**
+ * Spawn a mob with allegiance to the given battle group.
+ *
+ * @param mapname The name of the map where the mob should be spawned.
+ * @param x The x coordinate where the mob should be spawned.
+ * @param y The y coordinate where the mob should be spawned.
+ * @param mobname The mob's display name.
+ * @param class_ The mob's mob ID in database.
+ * @param event The name of the event which should be executed when the mob is killed.
+ * @param bg_id The battle group ID.
+ * @param npc_id If spawned by NPC script, this holds the ID of the invoking NPC.
+ * @return The spawned mob's GID, or 0 if spawning failed.
+ *
+ * @author Zephyrus
+ *
+ **/
+static int mob_spawn_bg(const char *mapname, short x, short y, const char *mobname, int class_, const char *event,
+ unsigned int bg_id, int npc_id)
{
- struct mob_data *md = NULL;
- struct spawn_data data;
- int16 m;
-
nullpo_ret(mapname);
nullpo_ret(mobname);
nullpo_ret(event);
- if( (m = map->mapname2mapid(mapname)) < 0 ) {
+ const int map_id = map->mapname2mapid(mapname);
+
+ if (map_id == INDEX_NOT_FOUND) {
ShowWarning("mob_spawn_bg: Map [%s] not found.\n", mapname);
return 0;
}
- memset(&data, 0, sizeof(struct spawn_data));
- data.m = m;
- data.num = 1;
- if( class_ <= 0 )
- {
- class_ = mob->get_random_id(-class_-1,1,99);
- if( !class_ ) return 0;
+ if ((x <= 0 || y <= 0) && map->search_freecell(NULL, map_id, &x, &y, -1, -1, 1) == 0) {
+ ShowWarning("mob_spawn_bg: Couldn't locate a spawn cell for guardian class %d (bg_id %u) on map %s.\n", class_, bg_id, mapname);
+ return 0;
}
- data.class_ = class_;
- if( (x <= 0 || y <= 0) && !map->search_freecell(NULL, m, &x, &y, -1,-1, 1) ) {
- ShowWarning("mob_spawn_bg: Couldn't locate a spawn cell for guardian class %d (bg_id %u) at map %s\n", class_, bg_id, map->list[m].name);
+ if (class_ <= 0 && (class_ = mob->get_random_id(-class_ - 1, 1, 99)) == 0)
return 0;
- }
+ struct spawn_data data;
+
+ memset(&data, 0, sizeof(struct spawn_data));
+ data.num = 1;
+ data.m = map_id;
+ data.class_ = class_;
data.x = x;
data.y = y;
safestrncpy(data.name, mobname, sizeof(data.name));
safestrncpy(data.eventname, event, sizeof(data.eventname));
- if( !mob->parse_dataset(&data) )
+
+ if (mob->parse_dataset(&data) == 0)
return 0;
- md = mob->spawn_dataset(&data);
- mob->spawn(md);
- md->bg_id = bg_id; // BG Team ID
+ struct mob_data *md = mob->spawn_dataset(&data, npc_id);
+ mob->spawn(md);
+ md->bg_id = bg_id;
return md->bl.id;
}
@@ -1368,7 +1462,7 @@ static int mob_ai_sub_hard_slavemob(struct mob_data *md, int64 tick)
// If master is BL_MOB and in battle, lock & chase to master's target instead, unless configured not to.
if ((battle_config.slave_chase_masters_chasetarget == 0 || (m_md != NULL && !mob->is_in_battle_state(m_md)))
&& map->search_freecell(&md->bl, bl->m, &x, &y, MOB_SLAVEDISTANCE, MOB_SLAVEDISTANCE, 1)
- && unit->walktoxy(&md->bl, x, y, 0))
+ && unit->walk_toxy(&md->bl, x, y, 0) == 0)
return 1;
}
} else if (bl->m != md->bl.m && map_flag_gvg(md->bl.m)) {
@@ -1450,7 +1544,7 @@ static int mob_unlocktarget(struct mob_data *md, int64 tick)
unit->set_target(&md->ud, 0);
}
if(battle_config.official_cell_stack_limit && map->count_oncell(md->bl.m, md->bl.x, md->bl.y, BL_CHAR|BL_NPC, 0x1 | 0x2) > battle_config.official_cell_stack_limit) {
- unit->walktoxy(&md->bl, md->bl.x, md->bl.y, 8);
+ unit->walk_toxy(&md->bl, md->bl.x, md->bl.y, 8);
}
return 0;
@@ -1482,9 +1576,9 @@ static int mob_randomwalk(struct mob_data *md, int64 tick)
x+=md->bl.x;
y+=md->bl.y;
- if (((x != md->bl.x) || (y != md->bl.y)) && map->getcell(md->bl.m, &md->bl, x, y, CELL_CHKPASS) && unit->walktoxy(&md->bl, x, y, 8)) {
+ if ((x != md->bl.x || y != md->bl.y) && map->getcell(md->bl.m, &md->bl, x, y, CELL_CHKPASS) != 0
+ && unit->walk_toxy(&md->bl, x, y, 8) == 0)
break;
- }
}
if(i==retrycount){
md->move_fail_count++;
@@ -3100,18 +3194,25 @@ static int mob_countslave(struct block_list *bl)
return map->foreachinmap(mob->countslave_sub, bl->m, BL_MOB,bl->id);
}
-/*==========================================
- * Summons amount slaves contained in the value[5] array using round-robin. [adapted by Skotlex]
- *------------------------------------------*/
+/**
+ * Summons amount slaves contained in the value[5] array using round-robin.
+ *
+ * @param md2 The mob which summons the slaves.
+ * @param value Array with slave mob IDs.
+ * @param amount The amount of slaves to spawn.
+ * @param skill_id The Id of the skill which summons the slaves.
+ * @return 1 on success, 0 on failure.
+ *
+ * @author Skotlex
+ *
+ **/
static int mob_summonslave(struct mob_data *md2, int *value, int amount, uint16 skill_id)
{
- struct mob_data *md;
- struct spawn_data data;
- int count = 0,k=0,hp_rate=0;
-
nullpo_ret(md2);
nullpo_ret(value);
+ struct spawn_data data;
+
memset(&data, 0, sizeof(struct spawn_data));
data.m = md2->bl.m;
data.x = md2->bl.x;
@@ -3120,31 +3221,42 @@ static int mob_summonslave(struct mob_data *md2, int *value, int amount, uint16
data.state.size = md2->special_state.size;
data.state.ai = md2->special_state.ai;
- if(mob->db_checkid(value[0]) == 0)
+ if (mob->db_checkid(value[0]) == 0)
return 0;
- /**
- * Flags this monster is able to summon; saves a worth amount of memory upon deletion
- **/
- md2->can_summon = 1;
- while(count < 5 && mob->db_checkid(value[count])) count++;
- if(count < 1) return 0;
- if (amount > 0 && amount < count) { //Do not start on 0, pick some random sub subset [Skotlex]
- k = rnd()%count;
- amount+=k; //Increase final value by same amount to preserve total number to summon.
+ md2->can_summon = 1; /// Flags this monster is able to summon; saves a worth amount of memory upon deletion.
+
+ int count = 0;
+
+ while (count < 5 && mob->db_checkid(value[count]) != 0)
+ count++;
+
+ if (count < 1)
+ return 0;
+
+ int k = 0;
+
+ if (amount > 0 && amount < count) { /// Do not start on 0, pick some random sub subset. [Skotlex]
+ k = rnd() % count;
+ amount += k; /// Increase final value by same amount to preserve total number to summon.
}
- if (!battle_config.monster_class_change_recover &&
- (skill_id == NPC_TRANSFORMATION || skill_id == NPC_METAMORPHOSIS))
+ int hp_rate = 0;
+
+ if ((skill_id == NPC_TRANSFORMATION || skill_id == NPC_METAMORPHOSIS) &&
+ battle_config.monster_class_change_recover == 0)
hp_rate = get_percentage(md2->status.hp, md2->status.max_hp);
- for(;k<amount;k++) {
- short x,y;
- data.class_ = value[k%count]; //Summon slaves in round-robin fashion. [Skotlex]
+ for (; k < amount; k++) {
+ data.class_ = value[k % count]; /// Summon slaves in round-robin fashion. [Skotlex]
+
if (mob->db_checkid(data.class_) == 0)
continue;
- if (map->search_freecell(&md2->bl, 0, &x, &y, MOB_SLAVEDISTANCE, MOB_SLAVEDISTANCE, 0)) {
+ short x;
+ short y;
+
+ if (map->search_freecell(&md2->bl, 0, &x, &y, MOB_SLAVEDISTANCE, MOB_SLAVEDISTANCE, 0) != 0) {
data.x = x;
data.y = y;
} else {
@@ -3152,49 +3264,52 @@ static int mob_summonslave(struct mob_data *md2, int *value, int amount, uint16
data.y = md2->bl.y;
}
- //These two need to be loaded from the db for each slave.
if (battle_config.override_mob_names == 1)
strcpy(data.name, DEFAULT_MOB_NAME);
else
strcpy(data.name, DEFAULT_MOB_JNAME);
- if (!mob->parse_dataset(&data))
+ if (mob->parse_dataset(&data) == 0)
continue;
- md= mob->spawn_dataset(&data);
- if(skill_id == NPC_SUMMONSLAVE){
- md->master_id=md2->bl.id;
+ struct mob_data *md = mob->spawn_dataset(&data, 0);
+
+ if (skill_id == NPC_SUMMONSLAVE) {
+ md->master_id = md2->bl.id;
md->special_state.ai = md2->special_state.ai;
}
+
mob->spawn(md);
- if (hp_rate) //Scale HP
- md->status.hp = md->status.max_hp*hp_rate/100;
+ if (hp_rate > 0) /// Scale HP.
+ md->status.hp = md->status.max_hp * hp_rate / 100;
- //Inherit the aggressive mode of the master.
- if (battle_config.slaves_inherit_mode && md->master_id) {
+ /** Inherit the aggressive mode of the master. **/
+ if (battle_config.slaves_inherit_mode > 0 && md->master_id > 0) {
switch (battle_config.slaves_inherit_mode) {
- case 1: //Always aggressive
- if (!(md->status.mode&MD_AGGRESSIVE))
- sc_start4(NULL, &md->bl, SC_MODECHANGE, 100,1,0, MD_AGGRESSIVE, 0, 0);
+ case 1: /// Always aggressive.
+ if ((md->status.mode & MD_AGGRESSIVE) == 0)
+ sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 0);
+
break;
- case 2: //Always passive
- if (md->status.mode&MD_AGGRESSIVE)
- sc_start4(NULL, &md->bl, SC_MODECHANGE, 100,1,0, 0, MD_AGGRESSIVE, 0);
+ case 2: /// Always passive.
+ if ((md->status.mode & MD_AGGRESSIVE) == MD_AGGRESSIVE)
+ sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, 0, MD_AGGRESSIVE, 0);
+
break;
- default: //Copy master.
- if (md2->status.mode&MD_AGGRESSIVE)
- sc_start4(NULL, &md->bl, SC_MODECHANGE, 100,1,0, MD_AGGRESSIVE, 0, 0);
+ default: /// Copy master.
+ if ((md2->status.mode & MD_AGGRESSIVE) == MD_AGGRESSIVE)
+ sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 0);
else
- sc_start4(NULL, &md->bl, SC_MODECHANGE, 100,1,0, 0, MD_AGGRESSIVE, 0);
+ sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, 0, MD_AGGRESSIVE, 0);
break;
}
}
- clif->skill_nodamage(&md->bl,&md->bl,skill_id,amount,1);
+ clif->skill_nodamage(&md->bl, &md->bl, skill_id, amount, 1);
}
- return 0;
+ return 1;
}
/*==========================================
@@ -3588,211 +3703,239 @@ static int mob_is_clone(int class_)
return class_;
}
-//Flag values:
-//&1: Set special AI (fight mobs, not players)
-//If mode is not passed, a default aggressive mode is used.
-//If master_id is passed, clone is attached to him.
-//Returns: ID of newly crafted copy.
-static int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id, uint32 mode, int flag, unsigned int duration)
+/**
+ * Spawns a mob which is a clone of another character.
+ *
+ * @param sd The character which should be cloned.
+ * @param m The ID of the map where the clone should be spawned.
+ * @param x The x coordinate where the clone should be spawned.
+ * @param y The y coordinate where the clone should be spawned.
+ * @param event The name of the event which should be executed when the clone is killed.
+ * @param master_id If passed, the clone will be attached to this account ID.
+ * @param mode The clone's mob mode(s). (Defaults to MD_CANMOVE|MD_AGGRESSIVE|MD_ASSIST|MD_CANATTACK.)
+ * @param flag 0 - target characters; 1 - target mobs.
+ * @param duration How long the clone will live before it is auto-removed. (ms)
+ * @return The spawned clone's GID, or 0 if spawning failed.
+ *
+ **/
+static int mob_clone_spawn(struct map_session_data *sd, int16 m, int16 x, int16 y, const char *event, int master_id,
+ uint32 mode, int flag, unsigned int duration)
{
- int class_;
- int i,j,h,inf, fd;
- struct mob_data *md;
- struct mob_skill *ms;
- struct mob_db* db;
- struct status_data *mstatus;
-
nullpo_ret(sd);
- if(pc_isdead(sd) && master_id && flag&1)
+ if (pc_isdead(sd) && master_id != 0 && flag == 1)
return 0;
- ARR_FIND( MOB_CLONE_START, MOB_CLONE_END, class_, mob->db_data[class_] == NULL );
- if(class_ < 0 || class_ >= MOB_CLONE_END)
+ int class_;
+
+ ARR_FIND(MOB_CLONE_START, MOB_CLONE_END, class_, mob->db_data[class_] == NULL);
+
+ if (class_ < 0 || class_ >= MOB_CLONE_END)
return 0;
- db = mob->db_data[class_]=(struct mob_db*)aCalloc(1, sizeof(struct mob_db));
- mstatus = &db->status;
- strcpy(db->sprite,sd->status.name);
- strcpy(db->name,sd->status.name);
- strcpy(db->jname,sd->status.name);
- db->lv=status->get_lv(&sd->bl);
+ mob->db_data[class_] = (struct mob_db*)aCalloc(1, sizeof(struct mob_db));
+
+ struct mob_db *db = mob->db_data[class_];
+ struct status_data *mstatus = &db->status;
+
+ strcpy(db->sprite, sd->status.name);
+ strcpy(db->name, sd->status.name);
+ strcpy(db->jname, sd->status.name);
+ db->lv = status->get_lv(&sd->bl);
+ db->dmg_taken_rate = 100;
memcpy(mstatus, &sd->base_status, sizeof(struct status_data));
- mstatus->rhw.atk2= mstatus->dex + mstatus->rhw.atk + mstatus->rhw.atk2; //Max ATK
- mstatus->rhw.atk = mstatus->dex; //Min ATK
- if (mstatus->lhw.atk) {
- mstatus->lhw.atk2= mstatus->dex + mstatus->lhw.atk + mstatus->lhw.atk2; //Max ATK
- mstatus->lhw.atk = mstatus->dex; //Min ATK
+ mstatus->rhw.atk2 = mstatus->dex + mstatus->rhw.atk + mstatus->rhw.atk2; /// Max ATK.
+ mstatus->rhw.atk = mstatus->dex; /// Min ATK.
+
+ if (mstatus->lhw.atk > 0) {
+ mstatus->lhw.atk2 = mstatus->dex + mstatus->lhw.atk + mstatus->lhw.atk2; /// Max ATK.
+ mstatus->lhw.atk = mstatus->dex; /// Min ATK.
}
- if (mode != MD_NONE) //User provided mode.
+
+ if (mode != MD_NONE) /// User provided mode.
mstatus->mode = mode;
- else if (flag&1) //Friendly Character, remove looting.
+ else if (flag == 1) /// Friendly Character, remove looting.
mstatus->mode &= ~MD_LOOTER;
+
mstatus->hp = mstatus->max_hp;
mstatus->sp = mstatus->max_sp;
memcpy(&db->vd, &sd->vd, sizeof(struct view_data));
- db->base_exp=1;
- db->job_exp=1;
- db->range2=AREA_SIZE; //Let them have the same view-range as players.
- db->range3=AREA_SIZE; //Min chase of a screen.
- db->option=sd->sc.option;
+ db->base_exp = 1;
+ db->job_exp = 1;
+ db->range2 = AREA_SIZE; /// Let them have the same view-range as players.
+ db->range3 = AREA_SIZE; /// Min chase of a screen.
+ db->option = sd->sc.option;
- //Skill copy [Skotlex]
- ms = &db->skill[0];
+ const int fd = sd->fd;
- /**
- * We temporarily disable sd's fd so it doesn't receive the messages from skill_check_condition_castbegin
- **/
- fd = sd->fd;
- sd->fd = 0;
-
- //Go Backwards to give better priority to advanced skills.
- for (i=0,j = MAX_SKILL_TREE-1;j>=0 && i< MAX_MOBSKILL ;j--) {
- int idx = pc->skill_tree[pc->class2idx(sd->status.class)][j].idx;
- int skill_id = pc->skill_tree[pc->class2idx(sd->status.class)][j].id;
- if (!skill_id || sd->status.skill[idx].lv < 1 ||
- (skill->dbs->db[idx].inf2&(INF2_WEDDING_SKILL|INF2_GUILD_SKILL))
- )
+ sd->fd = 0; /// Temporarily disable sd's fd so it doesn't receive the messages from skill_check_condition_castbegin.
+
+ struct mob_skill *mob_skills = &db->skill[0];
+
+ /// Go Backwards to give better priority to advanced skills.
+ for (int i = 0, j = MAX_SKILL_TREE - 1; j >= 0 && i < MAX_MOBSKILL; j--) {
+ const int idx = pc->skill_tree[pc->class2idx(sd->status.class)][j].idx;
+ const int skill_id = pc->skill_tree[pc->class2idx(sd->status.class)][j].id;
+
+ if (skill_id == 0 || sd->status.skill[idx].lv < 1 ||
+ (skill->dbs->db[idx].inf2 & (INF2_WEDDING_SKILL | INF2_GUILD_SKILL)) > 0)
continue;
- for(h = 0; h < map->list[sd->bl.m].zone->disabled_skills_count; h++) {
- if( skill_id == map->list[sd->bl.m].zone->disabled_skills[h]->nameid && map->list[sd->bl.m].zone->disabled_skills[h]->subtype == MZS_CLONE ) {
+
+ int h;
+
+ for (h = 0; h < map->list[sd->bl.m].zone->disabled_skills_count; h++) {
+ if (skill_id == map->list[sd->bl.m].zone->disabled_skills[h]->nameid &&
+ map->list[sd->bl.m].zone->disabled_skills[h]->subtype == MZS_CLONE)
break;
- }
}
- if( h < map->list[sd->bl.m].zone->disabled_skills_count )
+
+ if (h < map->list[sd->bl.m].zone->disabled_skills_count)
continue;
- //Normal aggressive mob, disable skills that cannot help them fight
- //against players (those with flags UF_NOMOB and UF_NOPC are specific
- //to always aid players!) [Skotlex]
- if (!(flag&1) &&
- skill->get_unit_id(skill_id, 0) &&
- skill->get_unit_flag(skill_id)&(UF_NOMOB|UF_NOPC))
+
+ /// Normal aggressive mob. Disable skills that cannot help fighting against players. (Those with flags UF_NOMOB and UF_NOPC are specific to always aid players!) [Skotlex]
+ if (flag == 0 && skill->get_unit_id(skill_id, 0) != 0 &&
+ (skill->get_unit_flag(skill_id) & (UF_NOMOB | UF_NOPC)) > 0)
continue;
- /**
- * The clone should be able to cast the skill (e.g. have the required weapon) bugreport:5299)
- **/
- if( !skill->check_condition_castbegin(sd,skill_id,sd->status.skill[idx].lv) )
+
+ /// The clone should be able to cast the skill. (E.g. have the required weapon.) [bugreport:5299]
+ if (skill->check_condition_castbegin(sd, skill_id, sd->status.skill[idx].lv) == 0)
continue;
- memset (&ms[i], 0, sizeof(struct mob_skill));
- ms[i].skill_id = skill_id;
- ms[i].skill_lv = sd->status.skill[idx].lv;
- ms[i].state = MSS_ANY;
- ms[i].permillage = 500*battle_config.mob_skill_rate/100; //Default chance of all skills: 5%
- ms[i].emotion = -1;
- ms[i].cancel = 0;
- ms[i].casttime = skill->cast_fix(&sd->bl,skill_id, ms[i].skill_lv);
- ms[i].delay = 5000+skill->delay_fix(&sd->bl,skill_id, ms[i].skill_lv);
-
- inf = skill->dbs->db[idx].inf;
- if (inf&INF_ATTACK_SKILL) {
- ms[i].target = MST_TARGET;
- ms[i].cond1 = MSC_ALWAYS;
- if (skill->get_range(skill_id, ms[i].skill_lv) > 3)
- ms[i].state = MSS_ANYTARGET;
+ memset(&mob_skills[i], 0, sizeof(struct mob_skill));
+ mob_skills[i].skill_id = skill_id;
+ mob_skills[i].skill_lv = sd->status.skill[idx].lv;
+ mob_skills[i].state = MSS_ANY;
+ mob_skills[i].permillage = 500 * battle_config.mob_skill_rate / 100; /// Default chance of all skills: 5%
+ mob_skills[i].emotion = -1;
+ mob_skills[i].cancel = 0;
+ mob_skills[i].casttime = skill->cast_fix(&sd->bl, skill_id, mob_skills[i].skill_lv);
+ mob_skills[i].delay = 5000 + skill->delay_fix(&sd->bl, skill_id, mob_skills[i].skill_lv);
+
+ const int inf = skill->dbs->db[idx].inf;
+
+ if ((inf & INF_ATTACK_SKILL) == INF_ATTACK_SKILL) {
+ mob_skills[i].target = MST_TARGET;
+ mob_skills[i].cond1 = MSC_ALWAYS;
+
+ if (skill->get_range(skill_id, mob_skills[i].skill_lv) > 3)
+ mob_skills[i].state = MSS_ANYTARGET;
else
- ms[i].state = MSS_BERSERK;
- } else if(inf&INF_GROUND_SKILL) {
- if (skill->get_inf2(skill_id)&INF2_TRAP) { //Traps!
- ms[i].state = MSS_IDLE;
- ms[i].target = MST_AROUND2;
- ms[i].delay = 60000;
- } else if (skill->get_unit_target(skill_id) == BCT_ENEMY) { //Target Enemy
- ms[i].state = MSS_ANYTARGET;
- ms[i].target = MST_TARGET;
- ms[i].cond1 = MSC_ALWAYS;
- } else { //Target allies
- ms[i].target = MST_FRIEND;
- ms[i].cond1 = MSC_FRIENDHPLTMAXRATE;
- ms[i].cond2 = 95;
+ mob_skills[i].state = MSS_BERSERK;
+ } else if ((inf & INF_GROUND_SKILL) == INF_GROUND_SKILL) {
+ if ((skill->get_inf2(skill_id) & INF2_TRAP) == INF2_TRAP) { /// Traps!
+ mob_skills[i].state = MSS_IDLE;
+ mob_skills[i].target = MST_AROUND2;
+ mob_skills[i].delay = 60000;
+ } else if (skill->get_unit_target(skill_id) == BCT_ENEMY) { /// Target Enemy.
+ mob_skills[i].state = MSS_ANYTARGET;
+ mob_skills[i].target = MST_TARGET;
+ mob_skills[i].cond1 = MSC_ALWAYS;
+ } else { /// Target allies.
+ mob_skills[i].target = MST_FRIEND;
+ mob_skills[i].cond1 = MSC_FRIENDHPLTMAXRATE;
+ mob_skills[i].cond2 = 95;
}
- } else if (inf&INF_SELF_SKILL) {
- if (skill->get_inf2(skill_id)&INF2_NO_TARGET_SELF) { //auto-select target skill.
- ms[i].target = MST_TARGET;
- ms[i].cond1 = MSC_ALWAYS;
- if (skill->get_range(skill_id, ms[i].skill_lv) > 3) {
- ms[i].state = MSS_ANYTARGET;
- } else {
- ms[i].state = MSS_BERSERK;
- }
- } else { //Self skill
- ms[i].target = MST_SELF;
- ms[i].cond1 = MSC_MYHPLTMAXRATE;
- ms[i].cond2 = 90;
- ms[i].permillage = 2000;
- //Delay: Remove the stock 5 secs and add half of the support time.
- ms[i].delay += -5000 +(skill->get_time(skill_id, ms[i].skill_lv) + skill->get_time2(skill_id, ms[i].skill_lv))/2;
- if (ms[i].delay < 5000)
- ms[i].delay = 5000; //With a minimum of 5 secs.
+ } else if ((inf & INF_SELF_SKILL) == INF_SELF_SKILL) {
+ if ((skill->get_inf2(skill_id) & INF2_NO_TARGET_SELF) == INF2_NO_TARGET_SELF) { /// Auto-select target skill.
+ mob_skills[i].target = MST_TARGET;
+ mob_skills[i].cond1 = MSC_ALWAYS;
+
+ if (skill->get_range(skill_id, mob_skills[i].skill_lv) > 3)
+ mob_skills[i].state = MSS_ANYTARGET;
+ else
+ mob_skills[i].state = MSS_BERSERK;
+ } else { /// Self skill.
+ mob_skills[i].target = MST_SELF;
+ mob_skills[i].cond1 = MSC_MYHPLTMAXRATE;
+ mob_skills[i].cond2 = 90;
+ mob_skills[i].permillage = 2000;
+
+ const int time1 = skill->get_time(skill_id, mob_skills[i].skill_lv);
+ const int time2 = skill->get_time2(skill_id, mob_skills[i].skill_lv);
+
+ /** Delay: Remove the stock 5 secs and add half of the support time. **/
+ mob_skills[i].delay += -5000 + (time1 + time2) / 2;
+
+ if (mob_skills[i].delay < 5000)
+ mob_skills[i].delay = 5000; /// With a minimum of 5 seconds.
}
- } else if (inf&INF_SUPPORT_SKILL) {
- ms[i].target = MST_FRIEND;
- ms[i].cond1 = MSC_FRIENDHPLTMAXRATE;
- ms[i].cond2 = 90;
+ } else if ((inf & INF_SUPPORT_SKILL) == INF_SUPPORT_SKILL) {
+ mob_skills[i].target = MST_FRIEND;
+ mob_skills[i].cond1 = MSC_FRIENDHPLTMAXRATE;
+ mob_skills[i].cond2 = 90;
+
if (skill_id == AL_HEAL)
- ms[i].permillage = 5000; //Higher skill rate usage for heal.
+ mob_skills[i].permillage = 5000; /// Higher skill rate usage for heal.
else if (skill_id == ALL_RESURRECTION)
- ms[i].cond2 = 1;
- //Delay: Remove the stock 5 secs and add half of the support time.
- ms[i].delay += -5000 +(skill->get_time(skill_id, ms[i].skill_lv) + skill->get_time2(skill_id, ms[i].skill_lv))/2;
- if (ms[i].delay < 2000)
- ms[i].delay = 2000; //With a minimum of 2 secs.
-
- if (i+1 < MAX_MOBSKILL) { //duplicate this so it also triggers on self.
- memcpy(&ms[i+1], &ms[i], sizeof(struct mob_skill));
+ mob_skills[i].cond2 = 1;
+
+ const int time1 = skill->get_time(skill_id, mob_skills[i].skill_lv);
+ const int time2 = skill->get_time2(skill_id, mob_skills[i].skill_lv);
+
+ /** Delay: Remove the stock 5 secs and add half of the support time. **/
+ mob_skills[i].delay += -5000 + (time1 + time2) / 2;
+
+ if (mob_skills[i].delay < 2000)
+ mob_skills[i].delay = 2000; /// With a minimum of 2 seconds.
+
+ if (i + 1 < MAX_MOBSKILL) { /// Duplicate this so it also triggers on self.
+ memcpy(&mob_skills[i + 1], &mob_skills[i], sizeof(struct mob_skill));
db->maxskill = ++i;
- ms[i].target = MST_SELF;
- ms[i].cond1 = MSC_MYHPLTMAXRATE;
+ mob_skills[i].target = MST_SELF;
+ mob_skills[i].cond1 = MSC_MYHPLTMAXRATE;
}
} else {
- switch (skill_id) { //Certain Special skills that are passive, and thus, never triggered.
- case MO_TRIPLEATTACK:
- case TF_DOUBLE:
- case GS_CHAINACTION:
- ms[i].state = MSS_BERSERK;
- ms[i].target = MST_TARGET;
- ms[i].cond1 = MSC_ALWAYS;
- ms[i].permillage = skill_id==MO_TRIPLEATTACK?(3000-ms[i].skill_lv*100):(ms[i].skill_lv*500);
- ms[i].delay -= 5000; //Remove the added delay as these could trigger on "all hits".
- break;
- default: //Untreated Skill
- continue;
+ switch (skill_id) { /// Certain special skills that are passive, and thus, never triggered.
+ case MO_TRIPLEATTACK:
+ FALLTHROUGH
+ case TF_DOUBLE:
+ FALLTHROUGH
+ case GS_CHAINACTION:
+ mob_skills[i].state = MSS_BERSERK;
+ mob_skills[i].target = MST_TARGET;
+ mob_skills[i].cond1 = MSC_ALWAYS;
+
+ if (skill_id == MO_TRIPLEATTACK)
+ mob_skills[i].permillage = 3000 - mob_skills[i].skill_lv * 100;
+ else
+ mob_skills[i].permillage = mob_skills[i].skill_lv * 500;
+
+ mob_skills[i].delay -= 5000; /// Remove the added delay as these could trigger on "all hits".
+ break;
+ default: /// Untreated skill.
+ continue;
}
}
- if (battle_config.mob_skill_rate!= 100)
- ms[i].permillage = ms[i].permillage*battle_config.mob_skill_rate/100;
- if (battle_config.mob_skill_delay != 100)
- ms[i].delay = ms[i].delay*battle_config.mob_skill_delay/100;
+ mob_skills[i].permillage *= battle_config.mob_skill_rate / 100;
+ mob_skills[i].delay *= battle_config.mob_skill_delay / 100;
db->maxskill = ++i;
}
- /**
- * We grant the session it's fd value back.
- **/
- sd->fd = fd;
+ sd->fd = fd; /// We grant the session it's fd value back.
+
+ /// Finally spawn it.
+ struct mob_data *md = mob->once_spawn_sub(&sd->bl, m, x, y, DEFAULT_MOB_NAME, class_, event, SZ_SMALL, AI_NONE, 0);
- //Finally, spawn it.
- md = mob->once_spawn_sub(&sd->bl, m, x, y, DEFAULT_MOB_NAME, class_, event, SZ_SMALL, AI_NONE);
- if (!md) return 0; //Failed?
+ if (md == NULL)
+ return 0; /// Failed?
md->special_state.clone = 1;
- if (master_id || flag || duration) { //Further manipulate crafted char.
- if (flag&1) //Friendly Character
- md->special_state.ai = AI_ATTACK;
- if (master_id) //Attach to Master
- md->master_id = master_id;
- if (duration) //Auto Delete after a while.
- {
- if( md->deletetimer != INVALID_TIMER )
- timer->delete(md->deletetimer, mob->timer_delete);
- md->deletetimer = timer->add(timer->gettick() + duration, mob->timer_delete, md->bl.id, 0);
- }
+ if (flag == 1) /// Friendly character.
+ md->special_state.ai = AI_ATTACK;
+
+ if (master_id != 0) /// Attach to master.
+ md->master_id = master_id;
+
+ if (duration > 0) { /// Auto delete after a while.
+ if (md->deletetimer != INVALID_TIMER)
+ timer->delete(md->deletetimer, mob->timer_delete);
+
+ md->deletetimer = timer->add(timer->gettick() + duration, mob->timer_delete, md->bl.id, 0);
}
mob->spawn(md);
-
return md->bl.id;
}
@@ -4206,8 +4349,12 @@ static void mob_read_db_viewdata_sub(struct mob_db *entry, struct config_setting
entry->vd.head_mid = libconfig->setting_get_int(it);
if ((it = libconfig->setting_get_member(t, "HeadLowId")) != NULL)
entry->vd.head_bottom = libconfig->setting_get_int(it);
+
if ((it = libconfig->setting_get_member(t, "HairStyleId")) != NULL)
entry->vd.hair_style = libconfig->setting_get_int(it);
+ else
+ entry->vd.hair_style = 1;
+
if ((it = libconfig->setting_get_member(t, "BodyStyleId")) != NULL)
entry->vd.body_style = libconfig->setting_get_int(it);
if ((it = libconfig->setting_get_member(t, "HairColorId")) != NULL)
diff --git a/src/map/mob.h b/src/map/mob.h
index 8fd16f191..8839809f2 100644
--- a/src/map/mob.h
+++ b/src/map/mob.h
@@ -255,6 +255,7 @@ struct mob_data {
int areanpc_id; //Required in OnTouchNPC (to avoid multiple area touchs)
unsigned int bg_id; // BattleGround System
int clan_id; // Clan System
+ int npc_id; // NPC ID if spawned with monster/areamonster/guardian/bg_monster/atcommand("@monster xy") (Used to kill mob on NPC unload.)
int64 next_walktime, last_thinktime, last_linktime, last_pcneartime, dmgtick;
short move_fail_count;
@@ -507,14 +508,14 @@ struct mob_interface {
int (*db_checkid) (const int id);
struct view_data* (*get_viewdata) (int class_);
int (*parse_dataset) (struct spawn_data *data);
- struct mob_data* (*spawn_dataset) (struct spawn_data *data);
+ struct mob_data* (*spawn_dataset) (struct spawn_data *data, int npc_id);
int (*get_random_id) (int type, int flag, int lv);
bool (*ksprotected) (struct block_list *src, struct block_list *target);
- struct mob_data* (*once_spawn_sub) (struct block_list *bl, int16 m, int16 x, int16 y, const char *mobname, int class_, const char *event, unsigned int size, unsigned int ai);
+ struct mob_data* (*once_spawn_sub) (struct block_list *bl, int16 m, int16 x, int16 y, const char *mobname, int class_, const char *event, unsigned int size, unsigned int ai, int npc_id);
int (*once_spawn) (struct map_session_data *sd, int16 m, int16 x, int16 y, const char *mobname, int class_, int amount, const char *event, unsigned int size, unsigned int ai);
int (*once_spawn_area) (struct map_session_data *sd, int16 m, int16 x0, int16 y0, int16 x1, int16 y1, const char *mobname, int class_, int amount, const char *event, unsigned int size, unsigned int ai);
- int (*spawn_guardian) (const char *mapname, short x, short y, const char *mobname, int class_, const char *event, int guardian, bool has_index);
- int (*spawn_bg) (const char *mapname, short x, short y, const char *mobname, int class_, const char *event, unsigned int bg_id);
+ int (*spawn_guardian) (const char *mapname, short x, short y, const char *mobname, int class_, const char *event, int guardian, bool has_index, int npc_id);
+ int (*spawn_bg) (const char *mapname, short x, short y, const char *mobname, int class_, const char *event, unsigned int bg_id, int npc_id);
int (*can_reach) (struct mob_data *md, struct block_list *bl, int range, int state);
int (*linksearch) (struct block_list *bl, va_list ap);
int (*delayspawn) (int tid, int64 tick, int id, intptr_t data);
diff --git a/src/map/npc.c b/src/map/npc.c
index cc588e52c..2ac99948b 100644
--- a/src/map/npc.c
+++ b/src/map/npc.c
@@ -1335,53 +1335,68 @@ static int npc_click(struct map_session_data *sd, struct npc_data *nd)
return 0;
}
-/*==========================================
+/**
+ * Validates a character's script related data and (re-)runs the script if validation was successful.
*
- *------------------------------------------*/
+ * Is called when:
+ * - The Next/Close button was clicked.
+ * - A menu option was selected.
+ * - A value was entered by input() script command.
+ * - A progress bar has reached 100%.
+ * - The character timed out because of idling.
+ *
+ * @param sd The character's session data.
+ * @param id The NPC ID.
+ * @param closing Whether the script is closing, or not.
+ * @return 0 on success, otherwise 1.
+ *
+**/
static int npc_scriptcont(struct map_session_data *sd, int id, bool closing)
{
- struct block_list *target = map->id2bl(id);
nullpo_retr(1, sd);
- if( id != sd->npc_id ){
- struct npc_data *nd_sd = map->id2nd(sd->npc_id);
- struct npc_data *nd = BL_CAST(BL_NPC, target);
- ShowDebug("npc_scriptcont: %s (sd->npc_id=%d) is not %s (id=%d).\n",
- nd_sd?(char*)nd_sd->name:"'Unknown NPC'", (int)sd->npc_id,
- nd?(char*)nd->name:"'Unknown NPC'", (int)id);
- return 1;
+ struct block_list *target = map->id2bl(id);
+
+#ifdef SECURE_NPCTIMEOUT
+ if (sd->npc_idle_timer != INVALID_TIMER) { /// Not yet timed out.
+#endif
+ if (id != sd->npc_id) {
+ struct npc_data *nd_sd = map->id2nd(sd->npc_id);
+ struct npc_data *nd = BL_CAST(BL_NPC, target);
+
+ ShowDebug("npc_scriptcont: %s (sd->npc_id=%d) is not %s (id=%d).\n",
+ (nd_sd != NULL) ? nd_sd->name : "'Unknown NPC'", sd->npc_id,
+ (nd != NULL) ? nd->name : "'Unknown NPC'", id);
+
+ return 1;
+ }
+#ifdef SECURE_NPCTIMEOUT
}
+#endif
- if (id != npc->fake_nd->bl.id) { // Not item script
+ if (id != npc->fake_nd->bl.id) { /// Not an item script.
if (sd->state.npc_unloaded != 0) {
sd->state.npc_unloaded = 0;
- } else if ((npc->checknear(sd,target)) == NULL) {
- ShowWarning("npc_scriptcont: failed npc->checknear test.\n");
+ } else if (npc->checknear(sd, target) == NULL) {
+ ShowWarning("npc_scriptcont: Failed npc->checknear test.\n");
return 1;
}
}
- /**
- * For the Secure NPC Timeout option (check config/Secure.h) [RR]
- **/
+
#ifdef SECURE_NPCTIMEOUT
- /**
- * Update the last NPC iteration
- **/
- sd->npc_idle_tick = timer->gettick();
+ sd->npc_idle_tick = timer->gettick(); /// Update the last NPC iteration.
#endif
- /**
- * WPE can get to this point with a progressbar; we deny it.
- **/
- if( sd->progressbar.npc_id && DIFF_TICK(sd->progressbar.timeout,timer->gettick()) > 0 )
+ /// WPE can get to this point with a progressbar; we deny it.
+ if (sd->progressbar.npc_id != 0 && DIFF_TICK(sd->progressbar.timeout, timer->gettick()) > 0)
return 1;
- if( !sd->st ) {
+ if (sd->st == NULL) {
sd->npc_id = 0;
return 1;
}
- if( closing && sd->st->state == CLOSE )
+ if (closing && sd->st->state == CLOSE)
sd->st->state = END;
script->run_main(sd->st);
@@ -1457,7 +1472,11 @@ static int npc_cashshop_buylist(struct map_session_data *sd, int points, struct
return ERROR_TYPE_NPC;
if( nd->subtype != CASHSHOP ) {
- if (nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type != NST_ZENY && nd->u.scr.shop->type != NST_MARKET && nd->u.scr.shop->type != NST_BARTER) {
+ if (nd->subtype == SCRIPT && nd->u.scr.shop &&
+ nd->u.scr.shop->type != NST_ZENY &&
+ nd->u.scr.shop->type != NST_MARKET &&
+ nd->u.scr.shop->type != NST_BARTER &&
+ nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
shop = nd->u.scr.shop->item;
shop_size = nd->u.scr.shop->items;
} else {
@@ -1623,7 +1642,7 @@ static void npc_market_tosql(struct npc_data *nd, int index)
{
nullpo_retv(nd);
Assert_retv(index >= 0 && index < nd->u.scr.shop->items);
- if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s','%d','%u')",
+ if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s','%d','%d')",
map->npc_market_data_db, nd->exname, nd->u.scr.shop->item[index].nameid, nd->u.scr.shop->item[index].qty))
Sql_ShowDebug(map->mysql_handle);
}
@@ -1716,7 +1735,9 @@ static void npc_barter_tosql(struct npc_data *nd, int index)
nullpo_retv(nd);
Assert_retv(index >= 0 && index < nd->u.scr.shop->items);
const struct npc_item_list *const item = &nd->u.scr.shop->item[index];
- if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s', '%d', '%u', '%u', '%d')",
+ if (item->qty == -1)
+ return;
+ if (SQL_ERROR == SQL->Query(map->mysql_handle, "REPLACE INTO `%s` VALUES ('%s', '%d', '%d', '%u', '%d')",
map->npc_barter_data_db, nd->exname, item->nameid, item->qty, item->value, item->value2)) {
Sql_ShowDebug(map->mysql_handle);
}
@@ -1753,6 +1774,178 @@ static void npc_barter_delfromsql(struct npc_data *nd, int index)
}
}
+
+/**
+ * Loads persistent NPC Expanded Barter Data from SQL
+ **/
+static void npc_expanded_barter_fromsql(void)
+{
+ struct SqlStmt *stmt = SQL->StmtMalloc(map->mysql_handle);
+ char name[NAME_LENGTH + 1];
+ int itemid;
+ int amount;
+ int zeny;
+ StringBuf buf;
+
+ StrBuf->Init(&buf);
+ StrBuf->AppendStr(&buf, "SELECT `name`, `itemId`, `amount`, `zeny`");
+ for (int k = 1; k < 11; k ++) {
+ StrBuf->Printf(&buf, ", `currencyId%d`, `currencyAmount%d`, `currencyRefine%d`", k, k, k);
+ }
+ StrBuf->Printf(&buf, " FROM `%s`", map->npc_expanded_barter_data_db);
+
+ if (SQL_ERROR == SQL->StmtPrepareStr(stmt, StrBuf->Value(&buf))
+ || SQL_ERROR == SQL->StmtExecute(stmt)
+ ) {
+ SqlStmt_ShowDebug(stmt);
+ SQL->StmtFree(stmt);
+ return;
+ }
+
+ struct npc_barter_currency tempCurrency[10];
+ SQL->StmtBindColumn(stmt, 0, SQLDT_STRING, &name, sizeof name, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 1, SQLDT_INT, &itemid, sizeof itemid, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 2, SQLDT_UINT32, &amount, sizeof amount, NULL, NULL);
+ SQL->StmtBindColumn(stmt, 3, SQLDT_UINT32, &zeny, sizeof zeny, NULL, NULL);
+ for (int k = 0; k < 10; k ++) {
+ SQL->StmtBindColumn(stmt, k * 3 + 4, SQLDT_INT, &tempCurrency[k].nameid, sizeof tempCurrency[k].nameid, NULL, NULL);
+ SQL->StmtBindColumn(stmt, k * 3 + 5, SQLDT_INT, &tempCurrency[k].amount, sizeof tempCurrency[k].amount, NULL, NULL);
+ SQL->StmtBindColumn(stmt, k * 3 + 6, SQLDT_INT, &tempCurrency[k].refine, sizeof tempCurrency[k].refine, NULL, NULL);
+ }
+
+ while (SQL_SUCCESS == SQL->StmtNextRow(stmt)) {
+ struct npc_data *nd = NULL;
+ unsigned short i;
+
+ if ((nd = npc->name2id(name)) == NULL) {
+ ShowError("npc_expanded_barter_fromsql: NPC '%s' not found! skipping...\n",name);
+ npc->expanded_barter_delfromsql_sub(name, INT_MAX, 0, 0, NULL);
+ continue;
+ } else if (nd->subtype != SCRIPT || nd->u.scr.shop == NULL || nd->u.scr.shop->items == 0 || nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
+ ShowError("npc_expanded_barter_fromsql: NPC '%s' is not proper for barter, skipping...\n",name);
+ npc->expanded_barter_delfromsql_sub(name, INT_MAX, 0, 0, NULL);
+ continue;
+ }
+
+ for (i = 0; i < nd->u.scr.shop->items; i++) {
+ struct npc_item_list *const item = &nd->u.scr.shop->item[i];
+ if (item->nameid == itemid && item->value == zeny) {
+ int count = nd->u.scr.shop->item[i].value2;
+ if (count > 10)
+ count = 10;
+ int curIndex;
+ for (curIndex = 0; curIndex < count; curIndex ++) {
+ struct npc_barter_currency *currency = &nd->u.scr.shop->item[i].currency[curIndex];
+ struct npc_barter_currency *currency2 = &tempCurrency[curIndex];
+ if (currency->nameid != currency2->nameid ||
+ currency->amount != currency2->amount ||
+ currency->refine != currency2->refine) {
+ break;
+ }
+ }
+ if (curIndex == count) {
+ item->qty = amount;
+ break;
+ }
+ }
+ }
+
+ if (i == nd->u.scr.shop->items) {
+ ShowError("npc_expanded_barter_fromsql: NPC '%s' does not sell item %d (qty %d), deleting...\n", name, itemid, amount);
+ npc->expanded_barter_delfromsql_sub(name, itemid, zeny, 10, &tempCurrency[0]);
+ continue;
+ }
+ }
+ SQL->StmtFree(stmt);
+ StrBuf->Destroy(&buf);
+}
+
+/**
+ * Saves persistent NPC Expanded Barter Data into SQL
+ **/
+static void npc_expanded_barter_tosql(struct npc_data *nd, int index)
+{
+ nullpo_retv(nd);
+ Assert_retv(index >= 0 && index < nd->u.scr.shop->items);
+ const struct npc_item_list *const item = &nd->u.scr.shop->item[index];
+ if (item->qty == -1)
+ return;
+
+ npc->expanded_barter_delfromsql(nd, index);
+
+ StringBuf buf;
+ StrBuf->Init(&buf);
+ StrBuf->Printf(&buf, "INSERT INTO `%s` VALUES ('%s', '%d', '%d', '%u'", map->npc_expanded_barter_data_db, nd->exname, item->nameid, item->qty, item->value);
+ int currencyCount = item->value2;
+ if (currencyCount > 10)
+ currencyCount = 10;
+ int k;
+ for (k = 0; k < currencyCount; k++) {
+ struct npc_barter_currency *currency = &item->currency[k];
+ StrBuf->Printf(&buf, ", '%d', '%d', '%d'", currency->nameid, currency->amount, currency->refine);
+ }
+ for (; k < 10; k ++) {
+ StrBuf->Printf(&buf, ", '0', '0', '0'");
+ }
+ StrBuf->AppendStr(&buf, ")");
+
+ if (SQL_ERROR == SQL->QueryStr(map->mysql_handle, StrBuf->Value(&buf))) {
+ Sql_ShowDebug(map->mysql_handle);
+ }
+ StrBuf->Destroy(&buf);
+}
+
+/**
+ * Removes persistent NPC Expanded Barter Data from SQL
+ */
+static void npc_expanded_barter_delfromsql_sub(const char *npcname, int itemId, int zeny, int currencyCount, struct npc_barter_currency* currency)
+{
+ if (itemId == INT_MAX) {
+ if (SQL_ERROR == SQL->Query(map->mysql_handle, "DELETE FROM `%s` WHERE `name`='%s'", map->npc_expanded_barter_data_db, npcname))
+ Sql_ShowDebug(map->mysql_handle);
+ } else {
+ StringBuf buf;
+
+ StrBuf->Init(&buf);
+ StrBuf->Printf(&buf, "DELETE FROM `%s` WHERE `name`='%s' AND `itemId`='%d' AND `zeny`='%d'",
+ map->npc_expanded_barter_data_db, npcname, itemId, zeny);
+ int k = 0;
+ if (currencyCount > 10)
+ currencyCount = 10;
+ for (k = 0; k < currencyCount; k++) {
+ struct npc_barter_currency *currency1 = &currency[k];
+ StrBuf->Printf(&buf, " AND currencyId%d='%d' and currencyAmount%d='%d' and currencyRefine%d='%d'",
+ k + 1, currency1->nameid, k + 1, currency1->amount, k + 1, currency1->refine);
+ }
+ for (; k < 10; k ++) {
+ StrBuf->Printf(&buf, " AND currencyId%d='0' and currencyAmount%d='0' and currencyRefine%d='0'",
+ k + 1, k + 1, k + 1);
+ }
+ StrBuf->AppendStr(&buf, " LIMIT 1");
+
+ if (SQL_ERROR == SQL->QueryStr(map->mysql_handle, StrBuf->Value(&buf))) {
+ Sql_ShowDebug(map->mysql_handle);
+ }
+ StrBuf->Destroy(&buf);
+ }
+}
+
+
+/**
+ * Removes persistent NPC Expanded Barter Data from SQL
+ **/
+static void npc_expanded_barter_delfromsql(struct npc_data *nd, int index)
+{
+ nullpo_retv(nd);
+ if (index == INT_MAX) {
+ npc->expanded_barter_delfromsql_sub(nd->exname, INT_MAX, 0, 0, NULL);
+ } else {
+ Assert_retv(index >= 0 && index < nd->u.scr.shop->items);
+ const struct npc_item_list *const item = &nd->u.scr.shop->item[index];
+ npc->expanded_barter_delfromsql_sub(nd->exname, item->nameid, item->value, item->value2, &item->currency[0]);
+ }
+}
+
/**
* Judges whether to allow and spawn a trader's window.
**/
@@ -1788,6 +1981,9 @@ static bool npc_trader_open(struct map_session_data *sd, struct npc_data *nd)
case NST_BARTER:
clif->npc_barter_open(sd, nd);
break;
+ case NST_EXPANDED_BARTER:
+ clif->npc_expanded_barter_open(sd, nd);
+ break;
default:
clif->cashshop_show(sd,nd);
break;
@@ -1914,7 +2110,11 @@ static int npc_cashshop_buy(struct map_session_data *sd, int nameid, int amount,
return ERROR_TYPE_ITEM_ID; // Invalid Item
if( nd->subtype != CASHSHOP ) {
- if (nd->subtype == SCRIPT && nd->u.scr.shop && nd->u.scr.shop->type != NST_ZENY && nd->u.scr.shop->type != NST_MARKET && nd->u.scr.shop->type != NST_BARTER) {
+ if (nd->subtype == SCRIPT && nd->u.scr.shop &&
+ nd->u.scr.shop->type != NST_ZENY &&
+ nd->u.scr.shop->type != NST_MARKET &&
+ nd->u.scr.shop->type != NST_BARTER &&
+ nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
shop = nd->u.scr.shop->item;
shop_size = nd->u.scr.shop->items;
} else {
@@ -2262,6 +2462,9 @@ static int npc_barter_buylist(struct map_session_data *sd, struct barteritemlist
if (n < 0 || n >= sd->status.inventorySize)
return 11; // wrong inventory index
+ if (entry->addAmount <= 0)
+ return 14; // not enough item amount in inventory
+
int removeId = sd->status.inventory[n].nameid;
const int j = entry->shopIndex;
if (j < 0 || j >= shop_size)
@@ -2355,6 +2558,152 @@ static int npc_barter_buylist(struct map_session_data *sd, struct barteritemlist
return 12;
}
+
+/**
+ * Processes incoming npc expanded barter purchase list
+ **/
+static int npc_expanded_barter_buylist(struct map_session_data *sd, struct barteritemlist *item_list)
+{
+ nullpo_retr(1, sd);
+ nullpo_retr(1, item_list);
+
+ struct npc_data* nd = npc->checknear(sd, map->id2bl(sd->npc_shopid));
+
+ if (nd == NULL || nd->subtype != SCRIPT || VECTOR_LENGTH(*item_list) == 0 ||
+ !nd->u.scr.shop || nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
+ return 11;
+ }
+
+ struct npc_item_list *shop = nd->u.scr.shop->item;
+ unsigned short shop_size = nd->u.scr.shop->items;
+ int w = 0;
+ int new_ = 0;
+ int64 z = 0;
+ int items[MAX_INVENTORY] = { 0 };
+
+ // process entries in buy list, one by one
+ for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) {
+ struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i);
+
+ if (entry->addAmount <= 0)
+ return 14; // not enough item amount in inventory
+
+ const int j = entry->shopIndex;
+ if (j < 0 || j >= shop_size)
+ return 13; // no such item in shop
+ if (entry->addId != shop[j].nameid && entry->addId != itemdb_viewid(shop[j].nameid))
+ return 13; // no such item in shop
+ entry->addId = shop[j].nameid; // item_avail replacement
+ if (!itemdb->exists(entry->addId))
+ return 13; // item no longer in itemdb
+
+ if ((int)shop[j].qty != -1 && entry->addAmount > (int)shop[j].qty)
+ return 14; // not enough item amount in shop
+
+ int currencyCount = shop[j].value2;
+ for (int currencyIndex = 0; currencyIndex < currencyCount; currencyIndex ++) {
+ struct npc_barter_currency *currency = &shop[j].currency[currencyIndex];
+ const int currencyItemId = currency->nameid;
+ const int currencyRefine = currency->refine;
+ int removeAmount = currency->amount * entry->addAmount;
+ if (removeAmount <= 0)
+ continue;
+ for (int n = 0; n < sd->status.inventorySize && removeAmount > 0; ++n) {
+ // check item id and existing amount
+ if (sd->status.inventory[n].nameid == currencyItemId && sd->status.inventory[n].amount > 0) {
+ // check item refine level
+ if (currencyRefine != -1 && sd->status.inventory[n].refine != currencyRefine)
+ continue;
+ if (sd->status.inventory[n].amount >= removeAmount) {
+ items[n] += removeAmount;
+ removeAmount = 0;
+ w -= itemdb_weight(currencyItemId) * removeAmount;
+ break;
+ } else {
+ items[n] += sd->status.inventory[n].amount;
+ removeAmount -= sd->status.inventory[n].amount;
+ w -= itemdb_weight(currencyItemId) * sd->status.inventory[n].amount;
+ }
+ }
+ if (items[n] > sd->status.inventory[n].amount)
+ return 14; // not enough item amount in inventory
+ }
+ if (removeAmount != 0) {
+ return 14; // not enough item amount in inventory
+ }
+ }
+
+ entry->addId = shop[j].nameid; //item_avail replacement
+
+ npc_market_qty[i] = j;
+
+ if (!itemdb->isstackable(entry->addId) && entry->addAmount > 1) {
+ //Exploit? You can't buy more than 1 of equipment types o.O
+ ShowWarning("Player %s (%d:%d) sent a hexed packet trying to buy %d of non-stackable item %d!\n",
+ sd->status.name, sd->status.account_id, sd->status.char_id, entry->addAmount, entry->addId);
+ entry->addAmount = 1;
+ }
+
+ switch (pc->checkadditem(sd, entry->addId, entry->addAmount)) {
+ case ADDITEM_EXIST:
+ break;
+ case ADDITEM_NEW:
+ new_++;
+ break;
+ case ADDITEM_OVERAMOUNT: /* TODO find official response for this */
+ return 1;
+ }
+
+ z += (int64)shop[j].value * entry->addAmount;
+ w += itemdb_weight(entry->addId) * entry->addAmount;
+ }
+
+ if (z > sd->status.zeny)
+ return 3; // Not enough Zeny
+
+ if ((int64)w + sd->weight > sd->max_weight)
+ return 2; // Too heavy
+
+ if (pc->inventoryblank(sd) < new_)
+ return 3; // Not enough space to store items
+
+ for (int i = 0; i < sd->status.inventorySize; ++i) {
+ const int removeAmountTotal = items[i];
+ if (removeAmountTotal == 0)
+ continue;
+ if (pc->delitem(sd, i, removeAmountTotal, 0, DELITEM_SOLD, LOG_TYPE_NPC) != 0) {
+ return 11; // unknown exploit
+ }
+ }
+
+ pc->payzeny(sd, (int)z, LOG_TYPE_NPC, NULL);
+
+ for (int i = 0; i < VECTOR_LENGTH(*item_list); ++i) {
+ struct barter_itemlist_entry *entry = &VECTOR_INDEX(*item_list, i);
+ const int shopIdx = npc_market_qty[i];
+
+ if ((int)shop[shopIdx].qty != -1) {
+ if (entry->addAmount > (int)shop[shopIdx].qty) /* wohoo someone tampered with the packet. */
+ return 14;
+ shop[shopIdx].qty -= entry->addAmount;
+ }
+
+ npc->expanded_barter_tosql(nd, shopIdx);
+
+ if (itemdb_type(entry->addId) == IT_PETEGG) {
+ pet->create_egg(sd, entry->addId);
+ } else {
+ struct item item_tmp;
+ memset(&item_tmp, 0, sizeof(item_tmp));
+ item_tmp.nameid = entry->addId;
+ item_tmp.identify = 1;
+ pc->additem(sd, &item_tmp, entry->addAmount, LOG_TYPE_NPC);
+ }
+ }
+
+ return 12;
+}
+
/// npc_selllist for script-controlled shops
static int npc_selllist_sub(struct map_session_data *sd, struct itemlist *item_list, struct npc_data *nd)
{
@@ -2601,121 +2950,193 @@ static int npc_unload_ev_label(union DBKey key, struct DBData *data, va_list ap)
return 0;
}
-//Chk if npc matches src_id, then unload.
-//Sub-function used to find duplicates.
+/**
+ * Unloads a NPC if it's a duplicate of the passed one.
+ *
+ * @param nd The NPC to check.
+ * @param args List of arguments.
+ * @return Always 0.
+ *
+ **/
static int npc_unload_dup_sub(struct npc_data *nd, va_list args)
{
- int src_id;
-
nullpo_ret(nd);
- src_id = va_arg(args, int);
+
+ const int src_id = va_arg(args, int);
+ const int unload_mobs = va_arg(args, int);
+
if (nd->src_id == src_id)
- npc->unload(nd, true);
+ npc->unload(nd, true, (unload_mobs == 1));
+
return 0;
}
-//Removes all npcs that are duplicates of the passed one. [Skotlex]
-static void npc_unload_duplicates(struct npc_data *nd)
+/**
+ * Unloads all NPCs which are duplicates of the passed one.
+ *
+ * @param nd The source NPC.
+ * @param unload_mobs If true, mobs spawned by duplicates will be removed.
+ *
+ * @author Skotlex
+ *
+ **/
+static void npc_unload_duplicates(struct npc_data *nd, bool unload_mobs)
{
nullpo_retv(nd);
- map->foreachnpc(npc->unload_dup_sub,nd->bl.id);
+
+ map->foreachnpc(npc->unload_dup_sub, nd->bl.id, unload_mobs);
}
-//Removes an npc from map and db.
-//Single is to free name (for duplicates).
-static int npc_unload(struct npc_data *nd, bool single)
+/**
+ * Removes a mob, which was spawned by a NPC (monster/areamonster/guardian/bg_monster/atcommand("@monster xy")).
+ *
+ * @param md The mob to remove.
+ * @param args List of arguments.
+ * @return 1 on success, 0 on failure.
+ *
+ * @author Kenpachi
+ *
+ **/
+static int npc_unload_mob(struct mob_data *md, va_list args)
+{
+ nullpo_ret(md);
+
+ const int npc_id = va_arg(args, int);
+
+ if (md->npc_id == npc_id) {
+ md->state.npc_killmonster = 1;
+ status_kill(&md->bl);
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Removes a NPC from map and database.
+ *
+ * @param nd The NPC which should be removed.
+ * @param single If true, names are freed. (For duplicates.)
+ * @param unload_mobs If true, mobs spawned by the NPC will be removed.
+ * @return Always 0.
+ *
+ **/
+static int npc_unload(struct npc_data *nd, bool single, bool unload_mobs)
{
nullpo_ret(nd);
- if( nd->ud && nd->ud != &npc->base_ud ) {
+ if (nd->ud != NULL && nd->ud != &npc->base_ud)
skill->clear_unitgroup(&nd->bl);
- }
npc->remove_map(nd);
map->deliddb(&nd->bl);
- if( single )
+
+ if (single)
strdb_remove(npc->name_db, nd->exname);
- if (nd->chat_id) // remove npc chatroom object and kick users
+ if (nd->chat_id != 0) /// Remove NPC chatroom object and kick users.
chat->delete_npc_chat(nd);
- npc_chat->finalize(nd); // deallocate npc PCRE data structures
+ npc_chat->finalize(nd); /// Deallocate NPC PCRE data structures.
if (single && nd->path != NULL) {
npc->releasepathreference(nd->path);
nd->path = NULL;
}
- if (single && nd->bl.m != -1)
+ if (single && nd->bl.m != INDEX_NOT_FOUND)
map->remove_questinfo(nd->bl.m, nd);
+
npc->questinfo_clear(nd);
- if (nd->src_id == 0 && ( nd->subtype == SHOP || nd->subtype == CASHSHOP)) {
- //src check for duplicate shops [Orcao]
- aFree(nd->u.shop.shop_item);
+ if (nd->src_id == 0 && (nd->subtype == SHOP || nd->subtype == CASHSHOP)) {
+ aFree(nd->u.shop.shop_item); /// src check for duplicate shops. [Orcao]
} else if (nd->subtype == SCRIPT) {
- struct s_mapiterator *iter;
- struct map_session_data *sd = NULL;
+ char evname[EVENT_NAME_LENGTH];
+
+ snprintf(evname, ARRAYLENGTH(evname), "%s::OnNPCUnload", nd->exname);
+
+ struct event_data *ev = strdb_get(npc->ev_db, evname);
+
+ if (ev != NULL)
+ script->run_npc(nd->u.scr.script, ev->pos, 0, nd->bl.id); /// Run OnNPCUnload.
- if( single ) {
- npc->ev_db->foreach(npc->ev_db,npc->unload_ev,nd->exname); //Clean up all events related
- npc->ev_label_db->foreach(npc->ev_label_db,npc->unload_ev_label,nd);
+ if (single) {
+ npc->ev_db->foreach(npc->ev_db, npc->unload_ev, nd->exname); /// Clean up all related events.
+ npc->ev_label_db->foreach(npc->ev_label_db, npc->unload_ev_label, nd);
}
- iter = mapit_geteachpc();
- for (sd = BL_UCAST(BL_PC, mapit->first(iter)); mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) {
- if (sd->npc_timer_id != INVALID_TIMER ) {
+ struct s_mapiterator *iter = mapit_geteachpc();
+ struct map_session_data *sd = BL_UCAST(BL_PC, mapit->first(iter));
+
+ for (; mapit->exists(iter); sd = BL_UCAST(BL_PC, mapit->next(iter))) {
+ if (sd->npc_timer_id != INVALID_TIMER) {
const struct TimerData *td = timer->get(sd->npc_timer_id);
- if( td && td->id != nd->bl.id )
+ if (td != NULL && td->id != nd->bl.id)
continue;
- if( td && td->data )
+ if (td != NULL && td->data != 0)
ers_free(npc->timer_event_ers, (void*)td->data);
+
timer->delete(sd->npc_timer_id, npc->timerevent);
sd->npc_timer_id = INVALID_TIMER;
}
}
+
mapit->free(iter);
if (nd->u.scr.timerid != INVALID_TIMER) {
- const struct TimerData *td;
- td = timer->get(nd->u.scr.timerid);
- if (td && td->data)
+ const struct TimerData *td = timer->get(nd->u.scr.timerid);
+
+ if (td != NULL && td->data != 0)
ers_free(npc->timer_event_ers, (void*)td->data);
+
timer->delete(nd->u.scr.timerid, npc->timerevent);
}
- if (nd->u.scr.timer_event)
+
+ if (nd->u.scr.timer_event != NULL)
aFree(nd->u.scr.timer_event);
+
if (nd->src_id == 0) {
- if(nd->u.scr.script) {
+ if (nd->u.scr.script != NULL) {
script->free_code(nd->u.scr.script);
nd->u.scr.script = NULL;
}
- if (nd->u.scr.label_list) {
+
+ if (nd->u.scr.label_list != NULL) {
aFree(nd->u.scr.label_list);
nd->u.scr.label_list = NULL;
nd->u.scr.label_list_num = 0;
}
- if(nd->u.scr.shop) {
- if(nd->u.scr.shop->item)
+
+ if (nd->u.scr.shop != NULL) {
+ if (nd->u.scr.shop->item != NULL) {
+ for (int i = 0; i < nd->u.scr.shop->items; i ++) {
+ if (nd->u.scr.shop->item[i].currency != NULL)
+ aFree(nd->u.scr.shop->item[i].currency);
+ }
aFree(nd->u.scr.shop->item);
+ }
+
aFree(nd->u.scr.shop);
}
}
- if( nd->u.scr.guild_id )
+
+ if (nd->u.scr.guild_id > 0)
guild->flag_remove(nd);
}
- if( nd->ud && nd->ud != &npc->base_ud ) {
+ if (nd->ud != NULL && nd->ud != &npc->base_ud) {
aFree(nd->ud);
nd->ud = NULL;
}
- HPM->data_store_destroy(&nd->hdata);
+ if (unload_mobs)
+ map->foreachmob(npc->unload_mob, nd->bl.id);
+ HPM->data_store_destroy(&nd->hdata);
aFree(nd);
-
return 0;
}
@@ -2970,7 +3391,7 @@ static bool npc_viewisid(const char *viewid)
* @param class_ The NPC view class.
* @return A pointer to the created NPC data (ownership passed to the caller).
*/
-static struct npc_data *npc_create_npc(enum npc_subtype subtype, int m, int x, int y, uint8 dir, int class_)
+static struct npc_data *npc_create_npc(enum npc_subtype subtype, int m, int x, int y, enum unit_dir dir, int class_)
{
struct npc_data *nd;
@@ -4092,18 +4513,23 @@ static const char *npc_parse_function(const char *w1, const char *w2, const char
return end;
}
-/*==========================================
- * Parse Mob 1 - Parse mob list into each map
- * Parse Mob 2 - Actually Spawns Mob
- * [Wizputer]
- *------------------------------------------*/
+/**
+ * Spawns a mob by using the passed spawn data. (Permanent mob spawns.)
+ * npc_parse_mob() - Parses mob list into each map.
+ * npc_parse_mob2() - Actually spawns mob.
+ *
+ * @param mobspawn The mobs spawn data.
+ *
+ * @author Wizputer
+ *
+ **/
static void npc_parse_mob2(struct spawn_data *mobspawn)
{
- int i;
-
nullpo_retv(mobspawn);
- for( i = mobspawn->active; i < mobspawn->num; ++i ) {
- struct mob_data* md = mob->spawn_dataset(mobspawn);
+
+ for (int i = mobspawn->active; i < mobspawn->num; ++i) {
+ struct mob_data *md = mob->spawn_dataset(mobspawn, 0);
+
md->spawn = mobspawn;
md->spawn->active++;
mob->spawn(md);
@@ -5160,128 +5586,143 @@ static void npc_process_files(int npc_min)
npc->npc_id - npc_min, npc->npc_warp, npc->npc_shop, npc->npc_script, npc->npc_mob, npc->npc_cache_mob, npc->npc_delay_mob);
}
-//Clear then reload npcs files
+/**
+ * Clears and then reloads all NPC files.
+ *
+ * @return Always 0.
+ *
+ **/
static int npc_reload(void)
{
- int npc_new_min = npc->npc_id;
- struct s_mapiterator* iter;
- struct block_list* bl;
-
- if (map->retval == EXIT_FAILURE)
- map->retval = EXIT_SUCCESS; // Clear return status in case something failed before.
-
- /* clear guild flag cache */
- guild->flags_clear();
+ if (map->retval == EXIT_FAILURE) /// Clear return status in case something failed before.
+ map->retval = EXIT_SUCCESS;
+ guild->flags_clear(); /// Clear guild flag cache.
npc->path_db->clear(npc->path_db, npc->path_db_clear_sub);
-
db_clear(npc->name_db);
db_clear(npc->ev_db);
npc->ev_label_db->clear(npc->ev_label_db, npc->ev_label_db_clear_sub);
-
npc->npc_last_npd = NULL;
npc->npc_last_path = NULL;
npc->npc_last_ref = NULL;
+
+ const int npc_new_min = npc->npc_id;
+ struct s_mapiterator *iter = mapit_geteachiddb();
- //Remove all npcs/mobs. [Skotlex]
- iter = mapit_geteachiddb();
- for (bl = mapit->first(iter); mapit->exists(iter); bl = mapit->next(iter)) {
- switch(bl->type) {
- case BL_NPC:
- if( bl->id != npc->fake_nd->bl.id )// don't remove fake_nd
- npc->unload(BL_UCAST(BL_NPC, bl), false);
- break;
- case BL_MOB:
- unit->free(bl,CLR_OUTSIGHT);
- break;
+ /** Remove all NPCs/mobs. [Skotlex] **/
+ for (struct block_list *bl = mapit->first(iter); mapit->exists(iter); bl = mapit->next(iter)) {
+ switch (bl->type) {
+ case BL_NPC:
+ if (bl->id != npc->fake_nd->bl.id) /// Don't remove fake_nd.
+ npc->unload(BL_UCAST(BL_NPC, bl), false, false);
+
+ break;
+ case BL_MOB:
+ unit->free(bl, CLR_OUTSIGHT);
+ break;
+ default:
+ break;
}
}
+
mapit->free(iter);
- if(battle_config.dynamic_mobs) {// dynamic check by [random]
- int16 m;
- for (m = 0; m < map->count; m++) {
- int16 i;
- for (i = 0; i < MAX_MOB_LIST_PER_MAP; i++) {
+ if (battle_config.dynamic_mobs) { /// Dynamic check. [random]
+ for (int m = 0; m < map->count; m++) {
+ for (int i = 0; i < MAX_MOB_LIST_PER_MAP; i++) {
if (map->list[m].moblist[i] != NULL) {
aFree(map->list[m].moblist[i]);
map->list[m].moblist[i] = NULL;
}
- if( map->list[m].mob_delete_timer != INVALID_TIMER )
- { // Mobs were removed anyway,so delete the timer [Inkfish]
+
+ if (map->list[m].mob_delete_timer != INVALID_TIMER) { /// Mobs were removed anyway, so delete the timer. [Inkfish]
timer->delete(map->list[m].mob_delete_timer, map->removemobs_timer);
map->list[m].mob_delete_timer = INVALID_TIMER;
}
}
+
if (map->list[m].npc_num > 0)
- ShowWarning("npc_reload: %d npcs weren't removed at map %s!\n", map->list[m].npc_num, map->list[m].name);
+ ShowWarning("npc_reload: %d NPCs weren't removed from map %s!\n",
+ map->list[m].npc_num, map->list[m].name);
}
}
- // clear mob spawn lookup index
mob->clear_spawninfo();
-
- npc->npc_warp = npc->npc_shop = npc->npc_script = 0;
- npc->npc_mob = npc->npc_cache_mob = npc->npc_delay_mob = 0;
-
- // reset mapflags
+ npc->npc_warp = 0;
+ npc->npc_shop = 0;
+ npc->npc_script = 0;
+ npc->npc_mob = 0;
+ npc->npc_cache_mob = 0;
+ npc->npc_delay_mob = 0;
map->zone_reload();
map->flags_init();
-
- // Reprocess npc files and reload constants
itemdb->name_constants();
clan->set_constants();
- npc_process_files( npc_new_min );
-
+ npc_process_files(npc_new_min);
instance->reload();
-
map->zone_init();
-
- npc->motd = npc->name2id("HerculesMOTD"); /* [Ind/Hercules] */
-
- //Re-read the NPC Script Events cache.
+ npc->motd = npc->name2id("HerculesMOTD"); /// [Ind/Hercules]
npc->read_event_script();
- // Execute main initialisation events
- // The correct initialisation order is:
- // OnInit -> OnInterIfInit -> OnInterIfInitOnce -> OnAgitInit -> OnAgitInit2
- npc->event_do_oninit( true );
+ /**
+ * Execute main initialization events
+ * The correct initialization order is:
+ * OnInit -> OnInterIfInit -> OnInterIfInitOnce -> OnAgitInit -> OnAgitInit2
+ *
+ **/
+ npc->event_do_oninit(true);
+
npc->market_fromsql();
npc->barter_fromsql();
- // Execute rest of the startup events if connected to char-server. [Lance]
- // Executed when connection is established with char-server in chrif_connectack
- if( !intif->CheckForCharServer() ) {
- ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc->event_doall("OnInterIfInit"));
- ShowStatus("Event '"CL_WHITE"OnInterIfInitOnce"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n", npc->event_doall("OnInterIfInitOnce"));
- }
- // Refresh guild castle flags on both woe setups
- // These events are only executed after receiving castle information from char-server
+ npc->expanded_barter_fromsql();
+
+ /*
+ * Execute rest of the startup events if connected to char-server. [Lance]
+ * Executed when connection is established with char-server in chrif_connectack().
+ */
+ if (intif->CheckForCharServer() == 0) {
+ ShowStatus("Event '"CL_WHITE"OnInterIfInit"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n",
+ npc->event_doall("OnInterIfInit"));
+ ShowStatus("Event '"CL_WHITE"OnInterIfInitOnce"CL_RESET"' executed with '"CL_WHITE"%d"CL_RESET"' NPCs.\n",
+ npc->event_doall("OnInterIfInitOnce"));
+ }
+
+ /*
+ * Refresh guild castle flags on both WoE setups.
+ * These events are only executed after receiving castle information from char-server.
+ */
npc->event_doall("OnAgitInit");
npc->event_doall("OnAgitInit2");
return 0;
}
-//Unload all npc in the given file
-static bool npc_unloadfile(const char *filepath)
+/**
+ * Unloads all NPCs in the given file.
+ *
+ * @param filepath Path to the file which should be unloaded.
+ * @param unload_mobs If true, mobs spawned by NPCs in the file will be removed.
+ * @return true if at least one NPC was unloaded, otherwise false.
+ *
+ **/
+static bool npc_unloadfile(const char *filepath, bool unload_mobs)
{
+ nullpo_retr(false, filepath);
+
struct DBIterator *iter = db_iterator(npc->name_db);
- struct npc_data* nd = NULL;
bool found = false;
- nullpo_retr(false, filepath);
-
- for( nd = dbi_first(iter); dbi_exists(iter); nd = dbi_next(iter) ) {
- if( nd->path && strcasecmp(nd->path,filepath) == 0 ) { // FIXME: This can break in case-sensitive file systems
+ for (struct npc_data *nd = dbi_first(iter); dbi_exists(iter); nd = dbi_next(iter)) {
+ if (nd->path != NULL && strcasecmp(nd->path, filepath) == 0) { // FIXME: This can break in case-sensitive file systems.
found = true;
- npc->unload_duplicates(nd);/* unload any npcs which could duplicate this but be in a different file */
- npc->unload(nd, true);
+ npc->unload_duplicates(nd, unload_mobs); /// Unload any NPC which could duplicate this but be in a different file.
+ npc->unload(nd, true, unload_mobs);
}
}
dbi_destroy(iter);
- if( found ) /* refresh event cache */
+ if (found) /// Refresh event cache.
npc->read_event_script();
return found;
@@ -5514,6 +5955,7 @@ void npc_defaults(void)
npc->unload_ev_label = npc_unload_ev_label;
npc->unload_dup_sub = npc_unload_dup_sub;
npc->unload_duplicates = npc_unload_duplicates;
+ npc->unload_mob = npc_unload_mob;
npc->unload = npc_unload;
npc->clearsrcfile = npc_clearsrcfile;
npc->addsrcfile = npc_addsrcfile;
@@ -5567,6 +6009,7 @@ void npc_defaults(void)
npc->trader_update = npc_trader_update;
npc->market_buylist = npc_market_buylist;
npc->barter_buylist = npc_barter_buylist;
+ npc->expanded_barter_buylist = npc_expanded_barter_buylist;
npc->trader_open = npc_trader_open;
npc->market_fromsql = npc_market_fromsql;
npc->market_tosql = npc_market_tosql;
@@ -5576,6 +6019,10 @@ void npc_defaults(void)
npc->barter_tosql = npc_barter_tosql;
npc->barter_delfromsql = npc_barter_delfromsql;
npc->barter_delfromsql_sub = npc_barter_delfromsql_sub;
+ npc->expanded_barter_fromsql = npc_expanded_barter_fromsql;
+ npc->expanded_barter_tosql = npc_expanded_barter_tosql;
+ npc->expanded_barter_delfromsql = npc_expanded_barter_delfromsql;
+ npc->expanded_barter_delfromsql_sub = npc_expanded_barter_delfromsql_sub;
npc->db_checkid = npc_db_checkid;
npc->refresh = npc_refresh;
npc->questinfo_clear = npc_questinfo_clear;
diff --git a/src/map/npc.h b/src/map/npc.h
index c5f44f0e0..1585a2bc8 100644
--- a/src/map/npc.h
+++ b/src/map/npc.h
@@ -48,6 +48,7 @@ enum npc_shop_types {
NST_MARKET, /* official npc market type */
NST_CUSTOM,
NST_BARTER, /* official npc barter type */
+ NST_EXPANDED_BARTER, /* official npc expanded barter type */
/* */
NST_MAX,
};
@@ -60,17 +61,25 @@ struct npc_label_list {
int pos;
};
+struct npc_barter_currency {
+ int nameid;
+ int refine;
+ int amount;
+};
+
struct npc_item_list {
int nameid;
unsigned int value; // price or barter currency item id
- int value2; // barter currency item amount
- unsigned int qty;
+ int value2; // barter currency item amount / expanded barter currency size
+ int qty;
+ struct npc_barter_currency *currency;
};
struct npc_shop_data {
unsigned char type;/* what am i */
struct npc_item_list *item;/* list */
unsigned int items;/* total */
+ int shop_last_index; // only for NST_EXPANDED_BARTER
};
struct npc_parse;
struct npc_data {
@@ -86,7 +95,7 @@ struct npc_data {
int chat_id;
int touching_id;
int64 next_walktime;
- uint8 dir;
+ enum unit_dir dir;
uint8 area_size;
int clan_id;
@@ -261,8 +270,9 @@ struct npc_interface {
int (*unload_ev) (union DBKey key, struct DBData *data, va_list ap);
int (*unload_ev_label) (union DBKey key, struct DBData *data, va_list ap);
int (*unload_dup_sub) (struct npc_data *nd, va_list args);
- void (*unload_duplicates) (struct npc_data *nd);
- int (*unload) (struct npc_data *nd, bool single);
+ void (*unload_duplicates) (struct npc_data *nd, bool unload_mobs);
+ int (*unload_mob) (struct mob_data *md, va_list args);
+ int (*unload) (struct npc_data *nd, bool single, bool unload_mobs);
void (*clearsrcfile) (void);
void (*addsrcfile) (const char *name);
void (*delsrcfile) (const char *name);
@@ -271,7 +281,7 @@ struct npc_interface {
void (*parsename) (struct npc_data *nd, const char *name, const char *start, const char *buffer, const char *filepath);
int (*parseview) (const char *w4, const char *start, const char *buffer, const char *filepath);
bool (*viewisid) (const char *viewid);
- struct npc_data *(*create_npc) (enum npc_subtype subtype, int m, int x, int y, uint8 dir, int class_);
+ struct npc_data *(*create_npc) (enum npc_subtype subtype, int m, int x, int y, enum unit_dir dir, int class_);
struct npc_data* (*add_warp) (char *name, short from_mapid, short from_x, short from_y, short xs, short ys, unsigned short to_mapindex, short to_x, short to_y);
const char *(*parse_warp) (const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval);
const char *(*parse_shop) (const char *w1, const char *w2, const char *w3, const char *w4, const char *start, const char *buffer, const char *filepath, int *retval);
@@ -304,7 +314,7 @@ struct npc_interface {
int (*path_db_clear_sub) (union DBKey key, struct DBData *data, va_list args);
int (*ev_label_db_clear_sub) (union DBKey key, struct DBData *data, va_list args);
int (*reload) (void);
- bool (*unloadfile) (const char *filepath);
+ bool (*unloadfile) (const char *filepath, bool unload_mobs);
void (*do_clear_npc) (void);
void (*debug_warps_sub) (struct npc_data *nd);
void (*debug_warps) (void);
@@ -314,6 +324,7 @@ struct npc_interface {
void (*trader_update) (int master);
enum market_buy_result (*market_buylist) (struct map_session_data *sd, struct itemlist *item_list);
int (*barter_buylist) (struct map_session_data *sd, struct barteritemlist *item_list);
+ int (*expanded_barter_buylist) (struct map_session_data *sd, struct barteritemlist *item_list);
bool (*trader_open) (struct map_session_data *sd, struct npc_data *nd);
void (*market_fromsql) (void);
void (*market_tosql) (struct npc_data *nd, int index);
@@ -323,6 +334,10 @@ struct npc_interface {
void (*barter_tosql) (struct npc_data *nd, int index);
void (*barter_delfromsql) (struct npc_data *nd, int index);
void (*barter_delfromsql_sub) (const char *npcname, int itemId, int itemId2, int amount2);
+ void (*expanded_barter_fromsql) (void);
+ void (*expanded_barter_tosql) (struct npc_data *nd, int index);
+ void (*expanded_barter_delfromsql) (struct npc_data *nd, int index);
+ void (*expanded_barter_delfromsql_sub) (const char *npcname, int itemId, int zeny, int currencyCount, struct npc_barter_currency* currency);
bool (*db_checkid) (const int id);
void (*refresh) (struct npc_data* nd);
void (*questinfo_clear) (struct npc_data *nd);
diff --git a/src/map/packets.h b/src/map/packets.h
index 90b9beeb7..1e6dc71bc 100644
--- a/src/map/packets.h
+++ b/src/map/packets.h
@@ -1983,6 +1983,11 @@ packet(0x96e,clif->ackmergeitems);
packet(0x0b4c,clif->pCashShopLimitedReq);
#endif
+#if PACKETVER_MAIN_NUM >= 20190904 || PACKETVER_RE_NUM >= 20190904 || PACKETVER_ZERO_NUM >= 20190828
+ packet(0x0b57,clif->pNPCExpandedBarterPurchase);
+ packet(0x0b58,clif->pNPCExpandedBarterClosed);
+#endif
+
#if PACKETVER >= 20191224
packet(0x0b6d,clif->pCashShopOpen2);
#endif
diff --git a/src/map/packets_keys_main.h b/src/map/packets_keys_main.h
index 1d5f96ee9..5fddf9eaf 100644
--- a/src/map/packets_keys_main.h
+++ b/src/map/packets_keys_main.h
@@ -37,7 +37,7 @@
packetKeys(0x49357d72,0x22c370a1,0x5f836591);
#endif
-// 2010-11-23aRagexeRE, 2010-11-24aRagexeRE, 2010-11-24bRagexeRE, 2010-11-25aRagexeRE, 2010-11-26aRagexeRE, 2010-11-30aRagexeRE, 2010-12-07aRagexeRE, 2010-12-14aRagexeRE, 2010-12-21aRagexeRE, 2010-12-23aRagexeRE, 2010-12-28aRagexeRE, 2011-01-04aRagexeRE, 2011-01-05aRagexeRE, 2011-01-11aRagexeRE, 2011-01-18aRagexeRE, 2011-01-25aRagexeRE, 2011-01-26aRagexeRE, 2011-01-26bRagexeRE, 2011-01-31aRagexeRE, 2011-01-31bRagexeRE, 2011-01-31cRagexeRE, 2011-02-08aRagexeRE, 2011-02-15aRagexeRE, 2011-02-22aRagexeRE, 2011-02-23aRagexeRE, 2011-02-23bRagexeRE, 2011-02-24aRagexeRE, 2011-02-25aRagexeRE, 2011-02-28aRagexeRE, 2011-03-08aRagexeRE, 2011-03-09aRagexeRE, 2011-03-09bRagexeRE, 2011-03-09cRagexeRE, 2011-03-09dRagexeRE, 2011-03-15aRagexeRE, 2011-03-22aRagexeRE, 2011-03-29aRagexeRE, 2011-03-30aRagexeRE, 2011-03-30cRagexeRE, 2011-04-05aRagexeRE, 2011-04-12aRagexeRE, 2011-04-19aRagexeRE, 2011-04-20aRagexeRE, 2011-04-26aRagexeRE, 2011-04-27aRagexeRE, 2011-05-03aRagexeRE, 2011-05-11aRagexeRE, 2011-05-17bRagexeRE, 2011-05-24aRagexeRE, 2011-05-26aRagexeRE, 2011-05-31aRagexeRE, 2011-06-07aRagexeRE, 2011-06-08aRagexeRE, 2011-06-08bRagexeRE, 2011-06-08cRagexeRE, 2011-06-09aRagexeRE, 2011-06-14bRagexeRE, 2011-06-22aRagexeRE, 2011-06-28aRagexeRE, 2011-07-06aRagexeRE, 2011-07-13aRagexeRE, 2011-07-13bRagexeRE, 2011-07-13cRagexeRE, 2011-07-19aRagexeRE, 2011-07-26aRagexeRE, 2011-08-03aRagexeRE, 2011-08-03bRagexeRE, 2011-08-10aRagexeRE, 2013-12-23aRagexeRE, 2014-05-08aRagexe, 2014-05-08aRagexeRE, 2014-06-11eRagexe, 2015-02-25hRagexe, 2018-03-15aRagexe, 2018-03-21aRagexe, 2018-03-21aRagexeRE, 2018-03-28bRagexe, 2018-03-28bRagexeRE, 2018-04-04bRagexe, 2018-04-04cRagexeRE, 2018-04-18aRagexe, 2018-04-18bRagexeRE, 2018-04-25cRagexe, 2018-04-25cRagexeRE, 2018-05-02bRagexe, 2018-05-02bRagexeRE, 2018-05-02dRagexeRE, 2018-05-09aRagexe, 2018-05-16cRagexe, 2018-05-16cRagexeRE, 2018-05-23aRagexe, 2018-05-23aRagexeRE, 2018-05-30aRagexe, 2018-05-30bRagexeRE, 2018-05-30cRagexeRE, 2018-06-05bRagexe, 2018-06-05bRagexeRE, 2018-06-12aRagexeRE, 2018-06-12bRagexeRE, 2018-06-20cRagexe, 2018-06-20dRagexeRE, 2018-06-20eRagexe, 2018-06-20eRagexeRE, 2018-06-21aRagexe, 2018-06-21aRagexeRE, 2018-07-04aRagexe, 2018-07-04aRagexeRE, 2018-07-11aRagexeRE, 2018-07-18bRagexe, 2018-07-18bRagexeRE, 2018-07-18bRagexeRE1, 2018-07-18cRagexe, 2018-07-18cRagexeRE, 2018-08-01cRagexe, 2018-08-01cRagexeRE, 2018-08-08bRagexe, 2018-08-08bRagexeRE, 2018-08-22cRagexe, 2018-08-22cRagexeRE, 2018-08-29aRagexe, 2018-08-29aRagexeRE, 2018-08-29bRagexeRE, 2018-08-31aRagexe, 2018-09-12dRagexe, 2018-09-12dRagexeRE, 2018-09-19aRagexe, 2018-09-19aRagexeRE, 2018-10-02aRagexe, 2018-10-02aRagexeRE, 2018-10-02bRagexe, 2018-10-02bRagexeRE, 2018-10-17_02aRagexe, 2018-10-17_02aRagexeRE, 2018-10-17_03aRagexe, 2018-10-17_03aRagexeRE, 2018-10-17bRagexe, 2018-10-17bRagexeRE, 2018-10-24bRagexe, 2018-10-31aRagexe, 2018-10-31bRagexe, 2018-10-31cRagexeRE, 2018-11-07aRagexe, 2018-11-07aRagexeRE, 2018-11-14cRagexe, 2018-11-14cRagexeRE, 2018-11-14dRagexe, 2018-11-14dRagexeRE, 2018-11-21bRagexe, 2018-11-21cRagexeRE, 2018-11-28aRagexe, 2018-11-28aRagexeRE, 2018-11-28bRagexe, 2018-11-28cRagexe, 2018-12-05aRagexe, 2018-12-05bRagexeRE, 2018-12-12aRagexe, 2018-12-12aRagexeRE, 2018-12-12bRagexe, 2018-12-12bRagexeRE, 2018-12-19bRagexe, 2018-12-19bRagexeRE, 2018-12-26aRagexe, 2018-12-26aRagexeRE, 2019-01-09aRagexe, 2019-01-09bRagexeRE, 2019-01-16bRagexe, 2019-01-16bRagexeRE, 2019-01-16cRagexe, 2019-01-16cRagexeRE, 2019-01-23dRagexe, 2019-01-23dRagexeRE, 2019-02-13IRagexeRE, 2019-02-13bRagexe, 2019-02-13eRagexe, 2019-02-20aRagexeRE, 2019-02-27aRagexe, 2019-02-27bRagexeRE, 2019-02-28aRagexe, 2019-02-28aRagexeRE, 2019-03-06bRagexe, 2019-03-06bRagexeRE, 2019-03-06cRagexe, 2019-03-06cRagexeRE, 2019-03-13aRagexe, 2019-03-20aRagexe, 2019-03-20aRagexeRE, 2019-03-22aRagexe, 2019-03-22aRagexeRE, 2019-03-27bRagexe, 2019-03-27bRagexeRE, 2019-04-03aRagexe, 2019-04-03bRagexeRE, 2019-04-03cRagexeRE, 2019-04-17aRagexe, 2019-04-17cRagexeRE, 2019-04-18aRagexe, 2019-04-18aRagexeRE, 2019-05-08cRagexe, 2019-05-08dRagexeRE, 2019-05-08eRagexeRE, 2019-05-22bRagexe, 2019-05-22bRagexeRE, 2019-05-22cRagexe, 2019-05-22cRagexeRE, 2019-05-23aRagexe, 2019-05-29aRagexe, 2019-05-29bRagexeRE, 2019-05-29cRagexe, 2019-05-29cRagexeRE, 2019-05-30aRagexe, 2019-05-30aRagexeRE, 2019-06-05JRagexeRE, 2019-06-05KRagexe, 2019-06-05LRagexeRE, 2019-06-05fRagexe, 2019-06-05hRagexeRE, 2019-06-19bRagexe, 2019-06-19cRagexeRE, 2019-06-19eRagexe, 2019-06-19hRagexe, 2019-06-26bRagexeRE, 2019-07-03aRagexe, 2019-07-03bRagexeRE, 2019-07-17aRagexe, 2019-07-17cRagexeRE, 2019-07-17dRagexe, 2019-07-17dRagexeRE, 2019-07-24aRagexe, 2019-07-24bRagexeRE, 2019-07-31bRagexe, 2019-07-31bRagexeRE, 2019-08-02aRagexe, 2019-08-02aRagexeRE, 2019-08-07aRagexe, 2019-08-07dRagexeRE, 2019-08-21aRagexe, 2019-08-21cRagexeRE, 2019-08-21dRagexeRE, 2019-08-28aRagexe, 2019-08-28aRagexeRE, 2019-09-04aRagexe, 2019-09-04bRagexe, 2019-09-04bRagexeRE, 2019-09-18bRagexe, 2019-09-18cRagexeRE, 2019-09-25aRagexe, 2019-09-25aRagexeRE, 2019-09-25bRagexe, 2019-09-25bRagexeRE, 2019-10-02bRagexeRE, 2019-10-02cRagexe, 2019-10-02dRagexe, 2019-10-02dRagexeRE, 2019-10-02dRagexeRE_2, 2019-10-16fRagexe, 2019-10-16fRagexeRE, 2019-10-16gRagexe, 2019-10-16gRagexeRE, 2019-10-18aRagexe, 2019-10-23aRagexe, 2019-10-23aRagexeRE, 2019-10-30bRagexeRE, 2019-10-30cRagexe, 2019-11-06aRagexe, 2019-11-06bRagexeRE, 2019-11-07aRagexe, 2019-11-07aRagexeRE, 2019-11-13cRagexe, 2019-11-13eRagexe, 2019-11-13eRagexeRE, 2019-11-20aRagexe, 2019-11-20cRagexeRE, 2019-11-20dRagexe, 2019-11-27aRagexe, 2019-11-27aRagexeRE, 2019-11-27bRagexe, 2019-12-04aRagexe, 2019-12-04aRagexeRE, 2019-12-04bRagexe, 2019-12-04bRagexeRE, 2019-12-04cRagexeRE, 2019-12-11aRagexe, 2019-12-11fRagexeRE, 2019-12-18bRagexe, 2019-12-18bRagexeRE, 2019-12-24aRagexe, 2019-12-24aRagexeRE, 2019-12-24bRagexe, 2019-12-24bRagexeRE, 2020-01-08aRagexe, 2020-01-08bRagexeRE
+// 2010-11-23aRagexeRE, 2010-11-24aRagexeRE, 2010-11-24bRagexeRE, 2010-11-25aRagexeRE, 2010-11-26aRagexeRE, 2010-11-30aRagexeRE, 2010-12-07aRagexeRE, 2010-12-14aRagexeRE, 2010-12-21aRagexeRE, 2010-12-23aRagexeRE, 2010-12-28aRagexeRE, 2011-01-04aRagexeRE, 2011-01-05aRagexeRE, 2011-01-11aRagexeRE, 2011-01-18aRagexeRE, 2011-01-25aRagexeRE, 2011-01-26aRagexeRE, 2011-01-26bRagexeRE, 2011-01-31aRagexeRE, 2011-01-31bRagexeRE, 2011-01-31cRagexeRE, 2011-02-08aRagexeRE, 2011-02-15aRagexeRE, 2011-02-22aRagexeRE, 2011-02-23aRagexeRE, 2011-02-23bRagexeRE, 2011-02-24aRagexeRE, 2011-02-25aRagexeRE, 2011-02-28aRagexeRE, 2011-03-08aRagexeRE, 2011-03-09aRagexeRE, 2011-03-09bRagexeRE, 2011-03-09cRagexeRE, 2011-03-09dRagexeRE, 2011-03-15aRagexeRE, 2011-03-22aRagexeRE, 2011-03-29aRagexeRE, 2011-03-30aRagexeRE, 2011-03-30cRagexeRE, 2011-04-05aRagexeRE, 2011-04-12aRagexeRE, 2011-04-19aRagexeRE, 2011-04-20aRagexeRE, 2011-04-26aRagexeRE, 2011-04-27aRagexeRE, 2011-05-03aRagexeRE, 2011-05-11aRagexeRE, 2011-05-17bRagexeRE, 2011-05-24aRagexeRE, 2011-05-26aRagexeRE, 2011-05-31aRagexeRE, 2011-06-07aRagexeRE, 2011-06-08aRagexeRE, 2011-06-08bRagexeRE, 2011-06-08cRagexeRE, 2011-06-09aRagexeRE, 2011-06-14bRagexeRE, 2011-06-22aRagexeRE, 2011-06-28aRagexeRE, 2011-07-06aRagexeRE, 2011-07-13aRagexeRE, 2011-07-13bRagexeRE, 2011-07-13cRagexeRE, 2011-07-19aRagexeRE, 2011-07-26aRagexeRE, 2011-08-03aRagexeRE, 2011-08-03bRagexeRE, 2011-08-10aRagexeRE, 2013-12-23aRagexeRE, 2014-05-08aRagexe, 2014-05-08aRagexeRE, 2014-06-11eRagexe, 2015-02-25hRagexe, 2018-03-15aRagexe, 2018-03-21aRagexe, 2018-03-21aRagexeRE, 2018-03-28bRagexe, 2018-03-28bRagexeRE, 2018-04-04bRagexe, 2018-04-04cRagexeRE, 2018-04-18aRagexe, 2018-04-18bRagexeRE, 2018-04-25cRagexe, 2018-04-25cRagexeRE, 2018-05-02bRagexe, 2018-05-02bRagexeRE, 2018-05-02dRagexeRE, 2018-05-09aRagexe, 2018-05-16cRagexe, 2018-05-16cRagexeRE, 2018-05-23aRagexe, 2018-05-23aRagexeRE, 2018-05-30aRagexe, 2018-05-30bRagexeRE, 2018-05-30cRagexeRE, 2018-06-05bRagexe, 2018-06-05bRagexeRE, 2018-06-12aRagexeRE, 2018-06-12bRagexeRE, 2018-06-20cRagexe, 2018-06-20dRagexeRE, 2018-06-20eRagexe, 2018-06-20eRagexeRE, 2018-06-21aRagexe, 2018-06-21aRagexeRE, 2018-07-04aRagexe, 2018-07-04aRagexeRE, 2018-07-11aRagexeRE, 2018-07-18bRagexe, 2018-07-18bRagexeRE, 2018-07-18bRagexeRE1, 2018-07-18cRagexe, 2018-07-18cRagexeRE, 2018-08-01cRagexe, 2018-08-01cRagexeRE, 2018-08-08bRagexe, 2018-08-08bRagexeRE, 2018-08-22cRagexe, 2018-08-22cRagexeRE, 2018-08-29aRagexe, 2018-08-29aRagexeRE, 2018-08-29bRagexeRE, 2018-08-31aRagexe, 2018-09-12dRagexe, 2018-09-12dRagexeRE, 2018-09-19aRagexe, 2018-09-19aRagexeRE, 2018-10-02aRagexe, 2018-10-02aRagexeRE, 2018-10-02bRagexe, 2018-10-02bRagexeRE, 2018-10-17_02aRagexe, 2018-10-17_02aRagexeRE, 2018-10-17_03aRagexe, 2018-10-17_03aRagexeRE, 2018-10-17bRagexe, 2018-10-17bRagexeRE, 2018-10-24bRagexe, 2018-10-31aRagexe, 2018-10-31bRagexe, 2018-10-31cRagexeRE, 2018-11-07aRagexe, 2018-11-07aRagexeRE, 2018-11-14cRagexe, 2018-11-14cRagexeRE, 2018-11-14dRagexe, 2018-11-14dRagexeRE, 2018-11-21bRagexe, 2018-11-21cRagexeRE, 2018-11-28aRagexe, 2018-11-28aRagexeRE, 2018-11-28bRagexe, 2018-11-28cRagexe, 2018-12-05aRagexe, 2018-12-05bRagexeRE, 2018-12-12aRagexe, 2018-12-12aRagexeRE, 2018-12-12bRagexe, 2018-12-12bRagexeRE, 2018-12-19bRagexe, 2018-12-19bRagexeRE, 2018-12-26aRagexe, 2018-12-26aRagexeRE, 2019-01-09aRagexe, 2019-01-09bRagexeRE, 2019-01-16bRagexe, 2019-01-16bRagexeRE, 2019-01-16cRagexe, 2019-01-16cRagexeRE, 2019-01-23dRagexe, 2019-01-23dRagexeRE, 2019-02-13IRagexeRE, 2019-02-13bRagexe, 2019-02-13eRagexe, 2019-02-20aRagexeRE, 2019-02-27aRagexe, 2019-02-27bRagexeRE, 2019-02-28aRagexe, 2019-02-28aRagexeRE, 2019-03-06bRagexe, 2019-03-06bRagexeRE, 2019-03-06cRagexe, 2019-03-06cRagexeRE, 2019-03-13aRagexe, 2019-03-20aRagexe, 2019-03-20aRagexeRE, 2019-03-22aRagexe, 2019-03-22aRagexeRE, 2019-03-27bRagexe, 2019-03-27bRagexeRE, 2019-04-03aRagexe, 2019-04-03bRagexeRE, 2019-04-03cRagexeRE, 2019-04-17aRagexe, 2019-04-17cRagexeRE, 2019-04-18aRagexe, 2019-04-18aRagexeRE, 2019-05-08cRagexe, 2019-05-08dRagexeRE, 2019-05-08eRagexeRE, 2019-05-22bRagexe, 2019-05-22bRagexeRE, 2019-05-22cRagexe, 2019-05-22cRagexeRE, 2019-05-23aRagexe, 2019-05-29aRagexe, 2019-05-29bRagexeRE, 2019-05-29cRagexe, 2019-05-29cRagexeRE, 2019-05-30aRagexe, 2019-05-30aRagexeRE, 2019-06-05JRagexeRE, 2019-06-05KRagexe, 2019-06-05LRagexeRE, 2019-06-05fRagexe, 2019-06-05hRagexeRE, 2019-06-19bRagexe, 2019-06-19cRagexeRE, 2019-06-19eRagexe, 2019-06-19hRagexe, 2019-06-26bRagexeRE, 2019-07-03aRagexe, 2019-07-03bRagexeRE, 2019-07-17aRagexe, 2019-07-17cRagexeRE, 2019-07-17dRagexe, 2019-07-17dRagexeRE, 2019-07-24aRagexe, 2019-07-24bRagexeRE, 2019-07-31bRagexe, 2019-07-31bRagexeRE, 2019-08-02aRagexe, 2019-08-02aRagexeRE, 2019-08-07aRagexe, 2019-08-07dRagexeRE, 2019-08-21aRagexe, 2019-08-21cRagexeRE, 2019-08-21dRagexeRE, 2019-08-28aRagexe, 2019-08-28aRagexeRE, 2019-09-04aRagexe, 2019-09-04bRagexe, 2019-09-04bRagexeRE, 2019-09-18bRagexe, 2019-09-18cRagexeRE, 2019-09-25aRagexe, 2019-09-25aRagexeRE, 2019-09-25bRagexe, 2019-09-25bRagexeRE, 2019-10-02bRagexeRE, 2019-10-02cRagexe, 2019-10-02dRagexe, 2019-10-02dRagexeRE, 2019-10-02dRagexeRE_2, 2019-10-16fRagexe, 2019-10-16fRagexeRE, 2019-10-16gRagexe, 2019-10-16gRagexeRE, 2019-10-18aRagexe, 2019-10-23aRagexe, 2019-10-23aRagexeRE, 2019-10-30bRagexeRE, 2019-10-30cRagexe, 2019-11-06aRagexe, 2019-11-06bRagexeRE, 2019-11-07aRagexe, 2019-11-07aRagexeRE, 2019-11-13cRagexe, 2019-11-13eRagexe, 2019-11-13eRagexeRE, 2019-11-20aRagexe, 2019-11-20cRagexeRE, 2019-11-20dRagexe, 2019-11-27aRagexe, 2019-11-27aRagexeRE, 2019-11-27bRagexe, 2019-12-04aRagexe, 2019-12-04aRagexeRE, 2019-12-04bRagexe, 2019-12-04bRagexeRE, 2019-12-04cRagexeRE, 2019-12-11aRagexe, 2019-12-11fRagexeRE, 2019-12-18bRagexe, 2019-12-18bRagexeRE, 2019-12-24aRagexe, 2019-12-24aRagexeRE, 2019-12-24bRagexe, 2019-12-24bRagexeRE, 2020-01-08aRagexe, 2020-01-08bRagexeRE, 2020-01-22cRagexe, 2020-01-22cRagexeRE, 2020-01-29bRagexe, 2020-01-30aRagexe, 2020-02-05aRagexe, 2020-02-05aRagexeRE, 2020-02-06aRagexe, 2020-02-12aRagexe, 2020-02-12aRagexeRE, 2020-02-19dRagexe, 2020-02-19eRagexeRE, 2020-03-04aRagexe, 2020-03-04aRagexeRE
#if PACKETVER == 20101123 || \
PACKETVER == 20101124 || \
PACKETVER == 20101125 || \
@@ -181,7 +181,15 @@
PACKETVER == 20191211 || \
PACKETVER == 20191218 || \
PACKETVER == 20191224 || \
- PACKETVER >= 20200108
+ PACKETVER == 20200108 || \
+ PACKETVER == 20200122 || \
+ PACKETVER == 20200129 || \
+ PACKETVER == 20200130 || \
+ PACKETVER == 20200205 || \
+ PACKETVER == 20200206 || \
+ PACKETVER == 20200212 || \
+ PACKETVER == 20200219 || \
+ PACKETVER >= 20200304
packetKeys(0x00000000,0x00000000,0x00000000);
#endif
diff --git a/src/map/packets_keys_zero.h b/src/map/packets_keys_zero.h
index d4c96beca..facf0e151 100644
--- a/src/map/packets_keys_zero.h
+++ b/src/map/packets_keys_zero.h
@@ -30,7 +30,7 @@
/* This file is autogenerated, please do not commit manual changes */
-// 2017-10-18aRagexe_zero, 2017-10-19aRagexe_zero, 2017-10-23aRagexe_zero, 2017-10-23bRagexe_zero, 2017-10-23cRagexe_zero, 2017-10-24aRagexe_2_zero, 2017-10-24aRagexe_zero, 2017-10-25bRagexe_zero, 2017-10-27aRagexe_zero, 2017-10-27bRagexe_zero, 2017-10-30aRagexe_zero, 2017-10-31aRagexe_zero, 2017-11-09aRagexe_zero, 2017-11-13aRagexe_zero, 2017-11-13bRagexe_zero, 2018-03-15aRagexe_zero, 2018-03-21aRagexe_zero, 2018-03-21bRagexe_zero, 2018-03-28_1aRagexe_zero, 2018-03-28cRagexe_zero, 2018-04-11aRagexe_zero, 2018-04-25_3aRagexe_zero, 2018-05-09_3aRagexe_zero, 2018-05-23aRagexe_zero, 2018-06-05bRagexe_zero, 2018-06-05cRagexe_zero, 2018-06-27aRagexe_zero, 2018-07-03aRagexe_zero, 2018-07-11_2aRagexe_zero, 2018-07-25_2aRagexe_zero, 2018-08-01aRagexe_zero, 2018-08-08_2aRagexe_zero, 2018-08-22aRagexe_zero, 2018-08-29aRagexe_zero, 2018-09-05aRagexe_zero, 2018-09-12aRagexe_zero, 2018-09-19aRagexe_zero, 2018-09-28aRagexe_zero, 2018-10-10_2aRagexe_zero, 2018-10-24_2aRagexe_zero, 2018-11-14aRagexe_zero, 2018-11-20aRagexe_zero, 2018-11-28aRagexe_zero, 2018-12-12aRagexe_zero, 2018-12-19aRagexe_zero, 2018-12-26_2aRagexe_zero, 2019-01-16_2aRagexe_zero, 2019-01-17_1aRagexe_zero, 2019-01-30_2aRagexe_zero, 2019-02-13aRagexe_zero, 2019-02-20aRagexe_zero, 2019-02-27aRagexe_zero, 2019-03-13aRagexe_zero, 2019-03-27_2aRagexe_zero, 2019-03-27_3aRagexe_zero, 2019-04-03aRagexe_zero, 2019-04-10bRagexe_zero, 2019-04-24aRagexe_zero, 2019-05-02aRagexe_zero, 2019-05-08_2aRagexe_zero, 2019-05-08aRagexe_zero, 2019-05-15aRagexe_zero, 2019-05-29aRagexe_zero, 2019-05-30aRagexe_zero, 2019-06-05_2aRagexe_zero, 2019-06-26_2aRagexe_zero, 2019-06-26_3aRagexe_zero, 2019-07-09aRagexe_zero, 2019-07-10_3aRagexe_zero, 2019-07-17aRagexe_zero, 2019-07-24aRagexe_zero, 2019-08-14_3aRagexe_zero, 2019-08-28_2aRagexe_zero, 2019-08-28_3aRagexe_zero, 2019-09-11aRagexe_zero, 2019-09-18_2aRagexe_zero, 2019-09-18aRagexe_zero, 2019-09-25_3aRagexe_zero, 2019-09-25_5aRagexe_zero, 2019-10-08_2aRagexe_zero, 2019-10-23_2aRagexe_zero, 2019-11-06aRagexe_zero, 2019-11-13aRagexe_zero, 2019-11-27_2aRagexe_zero, 2019-11-27aRagexe_zero, 2019-12-04aRagexe_zero, 2019-12-11_2aRagexe_zero, 2019-12-24_4aRagexe_zero, 2019-12-24_5aRagexe_zero
+// 2017-10-18aRagexe_zero, 2017-10-19aRagexe_zero, 2017-10-23aRagexe_zero, 2017-10-23bRagexe_zero, 2017-10-23cRagexe_zero, 2017-10-24aRagexe_2_zero, 2017-10-24aRagexe_zero, 2017-10-25bRagexe_zero, 2017-10-27aRagexe_zero, 2017-10-27bRagexe_zero, 2017-10-30aRagexe_zero, 2017-10-31aRagexe_zero, 2017-11-09aRagexe_zero, 2017-11-13aRagexe_zero, 2017-11-13bRagexe_zero, 2018-03-15aRagexe_zero, 2018-03-21aRagexe_zero, 2018-03-21bRagexe_zero, 2018-03-28_1aRagexe_zero, 2018-03-28cRagexe_zero, 2018-04-11aRagexe_zero, 2018-04-25_3aRagexe_zero, 2018-05-09_3aRagexe_zero, 2018-05-23aRagexe_zero, 2018-06-05bRagexe_zero, 2018-06-05cRagexe_zero, 2018-06-27aRagexe_zero, 2018-07-03aRagexe_zero, 2018-07-11_2aRagexe_zero, 2018-07-25_2aRagexe_zero, 2018-08-01aRagexe_zero, 2018-08-08_2aRagexe_zero, 2018-08-22aRagexe_zero, 2018-08-29aRagexe_zero, 2018-09-05aRagexe_zero, 2018-09-12aRagexe_zero, 2018-09-19aRagexe_zero, 2018-09-28aRagexe_zero, 2018-10-10_2aRagexe_zero, 2018-10-24_2aRagexe_zero, 2018-11-14aRagexe_zero, 2018-11-20aRagexe_zero, 2018-11-28aRagexe_zero, 2018-12-12aRagexe_zero, 2018-12-19aRagexe_zero, 2018-12-26_2aRagexe_zero, 2019-01-16_2aRagexe_zero, 2019-01-17_1aRagexe_zero, 2019-01-30_2aRagexe_zero, 2019-02-13aRagexe_zero, 2019-02-20aRagexe_zero, 2019-02-27aRagexe_zero, 2019-03-13aRagexe_zero, 2019-03-27_2aRagexe_zero, 2019-03-27_3aRagexe_zero, 2019-04-03aRagexe_zero, 2019-04-10bRagexe_zero, 2019-04-24aRagexe_zero, 2019-05-02aRagexe_zero, 2019-05-08_2aRagexe_zero, 2019-05-08aRagexe_zero, 2019-05-15aRagexe_zero, 2019-05-29aRagexe_zero, 2019-05-30aRagexe_zero, 2019-06-05_2aRagexe_zero, 2019-06-26_2aRagexe_zero, 2019-06-26_3aRagexe_zero, 2019-07-09aRagexe_zero, 2019-07-10_3aRagexe_zero, 2019-07-17aRagexe_zero, 2019-07-24aRagexe_zero, 2019-08-14_3aRagexe_zero, 2019-08-28_2aRagexe_zero, 2019-08-28_3aRagexe_zero, 2019-09-11aRagexe_zero, 2019-09-18_2aRagexe_zero, 2019-09-18aRagexe_zero, 2019-09-25_3aRagexe_zero, 2019-09-25_5aRagexe_zero, 2019-10-08_2aRagexe_zero, 2019-10-23_2aRagexe_zero, 2019-11-06aRagexe_zero, 2019-11-13aRagexe_zero, 2019-11-27_2aRagexe_zero, 2019-11-27aRagexe_zero, 2019-12-04aRagexe_zero, 2019-12-11_2aRagexe_zero, 2019-12-24_4aRagexe_zero, 2019-12-24_5aRagexe_zero, 2020-01-15_2aRagexe_zero, 2020-01-15aRagexe_zero, 2020-01-29_2aRagexe_zero, 2020-01-29aRagexe_zero, 2020-02-12aRagexe_zero, 2020-02-26aRagexe_zero, 2020-02-26bRagexe_zero, 2020-03-04aRagexe_zero
#if PACKETVER == 20171018 || \
PACKETVER == 20171019 || \
PACKETVER == 20171023 || \
@@ -103,7 +103,12 @@
PACKETVER == 20191127 || \
PACKETVER == 20191204 || \
PACKETVER == 20191211 || \
- PACKETVER >= 20191224
+ PACKETVER == 20191224 || \
+ PACKETVER == 20200115 || \
+ PACKETVER == 20200129 || \
+ PACKETVER == 20200212 || \
+ PACKETVER == 20200226 || \
+ PACKETVER >= 20200304
packetKeys(0x00000000,0x00000000,0x00000000);
#endif
diff --git a/src/map/packets_shuffle_main.h b/src/map/packets_shuffle_main.h
index 83a9107f2..25024c9f9 100644
--- a/src/map/packets_shuffle_main.h
+++ b/src/map/packets_shuffle_main.h
@@ -9794,7 +9794,7 @@
packet(0x083c,clif->pSearchStoreInfoListItemClick,2,6,10); // CZ_SSILIST_ITEM_CLICK // 14
#endif
-// 2019-09-04aRagexe, 2019-09-04bRagexe, 2019-09-18bRagexe, 2019-09-25aRagexe, 2019-09-25bRagexe, 2019-10-02cRagexe, 2019-10-02dRagexe, 2019-10-16fRagexe, 2019-10-16gRagexe, 2019-10-18aRagexe, 2019-10-23aRagexe, 2019-10-30cRagexe, 2019-11-06aRagexe, 2019-11-07aRagexe, 2019-11-13cRagexe, 2019-11-13eRagexe, 2019-11-20aRagexe, 2019-11-20dRagexe, 2019-11-27aRagexe, 2019-11-27bRagexe, 2019-12-04aRagexe, 2019-12-04bRagexe, 2019-12-11aRagexe, 2019-12-18bRagexe, 2019-12-24aRagexe, 2019-12-24bRagexe, 2020-01-08aRagexe
+// 2019-09-04aRagexe, 2019-09-04bRagexe, 2019-09-18bRagexe, 2019-09-25aRagexe, 2019-09-25bRagexe, 2019-10-02cRagexe, 2019-10-02dRagexe, 2019-10-16fRagexe, 2019-10-16gRagexe, 2019-10-18aRagexe, 2019-10-23aRagexe, 2019-10-30cRagexe, 2019-11-06aRagexe, 2019-11-07aRagexe, 2019-11-13cRagexe, 2019-11-13eRagexe, 2019-11-20aRagexe, 2019-11-20dRagexe, 2019-11-27aRagexe, 2019-11-27bRagexe, 2019-12-04aRagexe, 2019-12-04bRagexe, 2019-12-11aRagexe, 2019-12-18bRagexe, 2019-12-24aRagexe, 2019-12-24bRagexe, 2020-01-08aRagexe, 2020-01-22cRagexe, 2020-01-29bRagexe, 2020-01-30aRagexe, 2020-02-05aRagexe, 2020-02-06aRagexe, 2020-02-12aRagexe, 2020-02-19dRagexe, 2020-03-04aRagexe
#if PACKETVER == 20190904 || \
PACKETVER == 20190918 || \
PACKETVER == 20190925 || \
@@ -9812,7 +9812,15 @@
PACKETVER == 20191211 || \
PACKETVER == 20191218 || \
PACKETVER == 20191224 || \
- PACKETVER == 20200108
+ PACKETVER == 20200108 || \
+ PACKETVER == 20200122 || \
+ PACKETVER == 20200129 || \
+ PACKETVER == 20200130 || \
+ PACKETVER == 20200205 || \
+ PACKETVER == 20200206 || \
+ PACKETVER == 20200212 || \
+ PACKETVER == 20200219 || \
+ PACKETVER == 20200304
packet(0x0202,clif->pFriendsListAdd,2); // CZ_ADD_FRIENDS // 26
packet(0x022d,clif->pHomMenu,2,4); // CZ_COMMAND_MER // 5
packet(0x023b,clif->pStoragePassword,0); // CZ_ACK_STORE_PASSWORD // 36
diff --git a/src/map/packets_shuffle_re.h b/src/map/packets_shuffle_re.h
index 9c9df85ed..490d517fd 100644
--- a/src/map/packets_shuffle_re.h
+++ b/src/map/packets_shuffle_re.h
@@ -9744,7 +9744,7 @@
packet(0x083c,clif->pSearchStoreInfoListItemClick,2,6,10); // CZ_SSILIST_ITEM_CLICK // 14
#endif
-// 2019-09-04bRagexeRE, 2019-09-18cRagexeRE, 2019-09-25aRagexeRE, 2019-09-25bRagexeRE, 2019-10-02bRagexeRE, 2019-10-02dRagexeRE, 2019-10-02dRagexeRE_2, 2019-10-16fRagexeRE, 2019-10-16gRagexeRE, 2019-10-23aRagexeRE, 2019-10-30bRagexeRE, 2019-11-06bRagexeRE, 2019-11-07aRagexeRE, 2019-11-13eRagexeRE, 2019-11-20cRagexeRE, 2019-11-27aRagexeRE, 2019-12-04aRagexeRE, 2019-12-04bRagexeRE, 2019-12-04cRagexeRE, 2019-12-11fRagexeRE, 2019-12-18bRagexeRE, 2019-12-24aRagexeRE, 2019-12-24bRagexeRE, 2020-01-08bRagexeRE
+// 2019-09-04bRagexeRE, 2019-09-18cRagexeRE, 2019-09-25aRagexeRE, 2019-09-25bRagexeRE, 2019-10-02bRagexeRE, 2019-10-02dRagexeRE, 2019-10-02dRagexeRE_2, 2019-10-16fRagexeRE, 2019-10-16gRagexeRE, 2019-10-23aRagexeRE, 2019-10-30bRagexeRE, 2019-11-06bRagexeRE, 2019-11-07aRagexeRE, 2019-11-13eRagexeRE, 2019-11-20cRagexeRE, 2019-11-27aRagexeRE, 2019-12-04aRagexeRE, 2019-12-04bRagexeRE, 2019-12-04cRagexeRE, 2019-12-11fRagexeRE, 2019-12-18bRagexeRE, 2019-12-24aRagexeRE, 2019-12-24bRagexeRE, 2020-01-08bRagexeRE, 2020-01-22cRagexeRE, 2020-02-05aRagexeRE, 2020-02-12aRagexeRE, 2020-02-19eRagexeRE, 2020-03-04aRagexeRE
#if PACKETVER == 20190904 || \
PACKETVER == 20190918 || \
PACKETVER == 20190925 || \
@@ -9761,7 +9761,12 @@
PACKETVER == 20191211 || \
PACKETVER == 20191218 || \
PACKETVER == 20191224 || \
- PACKETVER == 20200108
+ PACKETVER == 20200108 || \
+ PACKETVER == 20200122 || \
+ PACKETVER == 20200205 || \
+ PACKETVER == 20200212 || \
+ PACKETVER == 20200219 || \
+ PACKETVER == 20200304
packet(0x0202,clif->pFriendsListAdd,2); // CZ_ADD_FRIENDS // 26
packet(0x022d,clif->pHomMenu,2,4); // CZ_COMMAND_MER // 5
packet(0x023b,clif->pStoragePassword,0); // CZ_ACK_STORE_PASSWORD // 36
diff --git a/src/map/packets_shuffle_zero.h b/src/map/packets_shuffle_zero.h
index 91b0c1e89..b7c26dbe7 100644
--- a/src/map/packets_shuffle_zero.h
+++ b/src/map/packets_shuffle_zero.h
@@ -803,7 +803,7 @@
packet(0x083c,clif->pSearchStoreInfoListItemClick,2,6,10); // CZ_SSILIST_ITEM_CLICK // 14
#endif
-// 2019-08-28_2aRagexe_zero, 2019-08-28_3aRagexe_zero, 2019-09-11aRagexe_zero, 2019-09-18_2aRagexe_zero, 2019-09-18aRagexe_zero, 2019-09-25_3aRagexe_zero, 2019-09-25_5aRagexe_zero, 2019-10-08_2aRagexe_zero, 2019-10-23_2aRagexe_zero, 2019-11-06aRagexe_zero, 2019-11-13aRagexe_zero, 2019-11-27_2aRagexe_zero, 2019-11-27aRagexe_zero, 2019-12-04aRagexe_zero, 2019-12-11_2aRagexe_zero, 2019-12-24_4aRagexe_zero, 2019-12-24_5aRagexe_zero
+// 2019-08-28_2aRagexe_zero, 2019-08-28_3aRagexe_zero, 2019-09-11aRagexe_zero, 2019-09-18_2aRagexe_zero, 2019-09-18aRagexe_zero, 2019-09-25_3aRagexe_zero, 2019-09-25_5aRagexe_zero, 2019-10-08_2aRagexe_zero, 2019-10-23_2aRagexe_zero, 2019-11-06aRagexe_zero, 2019-11-13aRagexe_zero, 2019-11-27_2aRagexe_zero, 2019-11-27aRagexe_zero, 2019-12-04aRagexe_zero, 2019-12-11_2aRagexe_zero, 2019-12-24_4aRagexe_zero, 2019-12-24_5aRagexe_zero, 2020-01-15_2aRagexe_zero, 2020-01-15aRagexe_zero, 2020-01-29_2aRagexe_zero, 2020-01-29aRagexe_zero, 2020-02-12aRagexe_zero, 2020-02-26aRagexe_zero, 2020-02-26bRagexe_zero, 2020-03-04aRagexe_zero
#if PACKETVER == 20190828 || \
PACKETVER == 20190911 || \
PACKETVER == 20190918 || \
@@ -815,7 +815,12 @@
PACKETVER == 20191127 || \
PACKETVER == 20191204 || \
PACKETVER == 20191211 || \
- PACKETVER == 20191224
+ PACKETVER == 20191224 || \
+ PACKETVER == 20200115 || \
+ PACKETVER == 20200129 || \
+ PACKETVER == 20200212 || \
+ PACKETVER == 20200226 || \
+ PACKETVER == 20200304
packet(0x0202,clif->pFriendsListAdd,2); // CZ_ADD_FRIENDS // 26
packet(0x022d,clif->pHomMenu,2,4); // CZ_COMMAND_MER // 5
packet(0x023b,clif->pStoragePassword,0); // CZ_ACK_STORE_PASSWORD // 36
diff --git a/src/map/packets_struct.h b/src/map/packets_struct.h
index 0fa602ba5..71f986a90 100644
--- a/src/map/packets_struct.h
+++ b/src/map/packets_struct.h
@@ -3770,7 +3770,7 @@ struct PACKET_CZ_SE_CASHSHOP_LIMITED_REQ {
DEFINE_PACKET_HEADER(CZ_SE_CASHSHOP_LIMITED_REQ, 0x0b4c);
#endif
-#if PACKETVER_ZERO_NUM >= 20191224
+#if PACKETVER_MAIN_NUM >= 20200129 || PACKETVER_RE_NUM >= 20200205 || PACKETVER_ZERO_NUM >= 20191224
struct PACKET_ZC_SE_CASHSHOP_OPEN {
int16 packetType;
uint32 cashPoints;
@@ -3788,6 +3788,109 @@ struct PACKET_ZC_SE_CASHSHOP_OPEN {
DEFINE_PACKET_HEADER(ZC_SE_CASHSHOP_OPEN, 0x0845);
#endif
+#if PACKETVER_MAIN_NUM >= 20190904 || PACKETVER_RE_NUM >= 20190904 || PACKETVER_ZERO_NUM >= 20190828
+struct PACKET_CZ_NPC_EXPANDED_BARTER_CLOSE {
+ int16 packetType;
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(CZ_NPC_EXPANDED_BARTER_CLOSE, 0x0b58);
+#endif
+
+#if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127
+struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2 {
+#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
+ uint32 nameid;
+#else
+ uint16 nameid;
+#endif
+ uint16 refine_level;
+ uint32 amount;
+ uint16 type;
+} __attribute__((packed));
+
+struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub {
+#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
+ uint32 nameid;
+#else
+ uint16 nameid;
+#endif
+ uint16 type;
+ uint32 amount;
+ uint32 weight;
+ uint32 index;
+ uint32 zeny;
+ uint32 currency_count;
+ // Workaround for fix Visual Studio bug (error C2233). Here should be currencies[]
+ struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2 currencies[1];
+} __attribute__((packed));
+
+// Workaround check for Visual Studio bug (error C2233)
+STATIC_ASSERT(sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2[1]) ==
+ sizeof(struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub2),
+ "Wrong PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub size");
+
+struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN {
+ int16 packetType;
+ int16 packetLength;
+ int32 items_count;
+ struct PACKET_ZC_NPC_EXPANDED_BARTER_OPEN_sub items[];
+} __attribute__((packed));
+
+DEFINE_PACKET_HEADER(ZC_NPC_EXPANDED_BARTER_OPEN, 0x0b56);
+#endif
+
+#if PACKETVER_MAIN_NUM >= 20190904 || PACKETVER_RE_NUM >= 20190904 || PACKETVER_ZERO_NUM >= 20190828
+struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE_sub {
+#if PACKETVER_MAIN_NUM >= 20181121 || PACKETVER_RE_NUM >= 20180704 || PACKETVER_ZERO_NUM >= 20181114
+ uint32 itemId;
+#else
+ uint16 itemId;
+#endif
+ uint32 shopIndex;
+ uint32 amount;
+} __attribute__((packed));
+
+struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE {
+ int16 packetType;
+ int16 packetLength;
+ struct PACKET_CZ_NPC_EXPANDED_BARTER_PURCHASE_sub list[];
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(CZ_NPC_EXPANDED_BARTER_PURCHASE, 0x0b57);
+#endif
+
+#if PACKETVER >= 7
+struct PACKET_ZC_STATE_CHANGE {
+ int16 packetType;
+ uint32 AID;
+ int16 bodyState;
+ int16 healthState;
+ int32 effectState;
+ int8 isPKModeON;
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(ZC_STATE_CHANGE, 0x0229);
+#else
+struct PACKET_ZC_STATE_CHANGE {
+ int16 PacketType;
+ uint32 AID;
+ int16 bodyState;
+ int16 healthState;
+ int16 effectState;
+ int8 isPKModeON;
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(ZC_STATE_CHANGE, 0x0119);
+#endif
+
+struct PACKET_ZC_AUTORUN_SKILL {
+ int16 packetType;
+ uint16 skill_id;
+ uint32 skill_type;
+ uint16 skill_lv;
+ uint16 skill_sp;
+ uint16 skill_range;
+ char skill_name[NAME_LENGTH];
+ char up_flag;
+} __attribute__((packed));
+DEFINE_PACKET_HEADER(ZC_AUTORUN_SKILL, 0x0147);
+
#if !defined(sun) && (!defined(__NETBSD__) || __NetBSD_Version__ >= 600000000) // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute
#pragma pack(pop)
#endif // not NetBSD < 6 / Solaris
diff --git a/src/map/pc.c b/src/map/pc.c
index c4f9d8be0..c96e957c7 100644
--- a/src/map/pc.c
+++ b/src/map/pc.c
@@ -5257,6 +5257,14 @@ static int pc_useitem(struct map_session_data *sd, int n)
#endif
if( battle_config.item_restricted_consumption_type && sd->status.inventory[n].expire_time == 0 ) {
clif->useitemack(sd,n,sd->status.inventory[n].amount-1,true);
+
+ // If Earth Spike Scroll is used while SC_EARTHSCROLL is active, there is a chance to don't consume the scroll. [Kenpachi]
+ if ((nameid == ITEMID_EARTH_SCROLL_1_3 || nameid == ITEMID_EARTH_SCROLL_1_5)
+ && sd->sc.count > 0 && sd->sc.data[SC_EARTHSCROLL] != NULL
+ && rnd() % 100 > sd->sc.data[SC_EARTHSCROLL]->val2) {
+ return 0;
+ }
+
pc->delitem(sd, n, 1, 1, DELITEM_NORMAL, LOG_TYPE_CONSUME);
}
return 0;
@@ -5302,11 +5310,37 @@ static int pc_useitem(struct map_session_data *sd, int n)
script->run_use_script(sd, sd->inventory_data[n], npc->fake_nd->bl.id);
script->potion_flag = 0;
+ // If Earth Spike Scroll is used while SC_EARTHSCROLL is active, there is a chance to don't consume the scroll. [Kenpachi]
+ if ((nameid == ITEMID_EARTH_SCROLL_1_3 || nameid == ITEMID_EARTH_SCROLL_1_5) && sd->sc.count > 0
+ && sd->sc.data[SC_EARTHSCROLL] != NULL && rnd() % 100 > sd->sc.data[SC_EARTHSCROLL]->val2) {
+ removeItem = false;
+ }
+
if (removeItem)
pc->delitem(sd, n, 1, 1, DELITEM_NORMAL, LOG_TYPE_CONSUME);
return 1;
}
+/**
+ * Sets state flags and helper variables, used by itemskill() script command, to 0.
+ *
+ * @param sd The character's session data.
+ * @return 0 if parameter sd is NULL, otherwise 1.
+ */
+static int pc_itemskill_clear(struct map_session_data *sd)
+{
+ nullpo_ret(sd);
+
+ sd->itemskill_id = 0;
+ sd->itemskill_lv = 0;
+ sd->state.itemskill_conditions_checked = 0;
+ sd->state.itemskill_no_conditions = 0;
+ sd->state.itemskill_no_casttime = 0;
+ sd->state.itemskill_castonself = 0;
+
+ return 1;
+}
+
/*==========================================
* Add item on cart for given index.
* Return:
@@ -8417,8 +8451,12 @@ static int64 pc_readparam(const struct map_session_data *sd, int type)
case SP_FLEE1: val = sd->battle_status.flee; break;
case SP_FLEE2: val = sd->battle_status.flee2; break;
case SP_DEFELE: val = sd->battle_status.def_ele; break;
-#ifndef RENEWAL_CAST
case SP_VARCASTRATE:
+#ifdef RENEWAL_CAST
+ val = sd->bonus.varcastrate;
+ break;
+#else
+ FALLTHROUGH
#endif
case SP_CASTRATE:
val = sd->castrate;
@@ -8504,7 +8542,6 @@ static int64 pc_readparam(const struct map_session_data *sd, int type)
case SP_FIXCASTRATE: val = sd->bonus.fixcastrate; break;
case SP_ADD_FIXEDCAST: val = sd->bonus.add_fixcast; break;
#ifdef RENEWAL_CAST
- case SP_VARCASTRATE: val = sd->bonus.varcastrate; break;
case SP_ADD_VARIABLECAST:val = sd->bonus.add_varcast; break;
#endif
}
@@ -9345,15 +9382,23 @@ static void pc_setridingpeco(struct map_session_data *sd, bool flag)
*
* @param sd Target player.
* @param flag New state.
+ * @param mtype Type of the mado gear.
**/
-static void pc_setmadogear(struct map_session_data *sd, bool flag)
+static void pc_setmadogear(struct map_session_data *sd, bool flag, enum mado_type mtype)
{
nullpo_retv(sd);
+ Assert_retv(mtype >= MADO_ROBOT && mtype < MADO_MAX);
+
if (flag) {
- if ((sd->job & MAPID_THIRDMASK) == MAPID_MECHANIC)
+ if ((sd->job & MAPID_THIRDMASK) == MAPID_MECHANIC) {
pc->setoption(sd, sd->sc.option|OPTION_MADOGEAR);
+#if PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106
+ sc_start(&sd->bl, &sd->bl, SC_MADOGEAR, 100, (int)mtype, INFINITE_DURATION);
+#endif
+ }
} else if (pc_ismadogear(sd)) {
pc->setoption(sd, sd->sc.option&~OPTION_MADOGEAR);
+ // pc->setoption resets status effects when changing mado, no need to re do it here.
}
}
@@ -12637,6 +12682,7 @@ void pc_defaults(void)
pc->unequipitem_pos = pc_unequipitem_pos;
pc->checkitem = pc_checkitem;
pc->useitem = pc_useitem;
+ pc->itemskill_clear = pc_itemskill_clear;
pc->skillatk_bonus = pc_skillatk_bonus;
pc->skillheal_bonus = pc_skillheal_bonus;
diff --git a/src/map/pc.h b/src/map/pc.h
index f4f0044a9..e940c3310 100644
--- a/src/map/pc.h
+++ b/src/map/pc.h
@@ -240,6 +240,10 @@ struct map_session_data {
unsigned int refine_ui : 1;
unsigned int npc_unloaded : 1; ///< The player is talking with an unloaded NPCs (respawned tombstones)
unsigned int lapine_ui : 1;
+ unsigned int itemskill_conditions_checked : 1; // Used by itemskill() script command, to prevent second check of conditions after target was selected.
+ unsigned int itemskill_no_conditions : 1; // Used by itemskill() script command, to ignore skill conditions and don't consume them.
+ unsigned int itemskill_no_casttime : 1; // Used by itemskill() script command, to cast skill instantaneously.
+ unsigned int itemskill_castonself : 1; // Used by itemskill() script command, to forcefully cast skill on invoking character.
} state;
struct {
unsigned char no_weapon_damage, no_magic_damage, no_misc_damage;
@@ -643,6 +647,15 @@ END_ZEROED_BLOCK;
bool achievements_received;
// Title
VECTOR_DECL(int) title_ids;
+
+ /*
+ * itemskill_conditions_checked/itemskill_no_conditions/itemskill_no_casttime/itemskill_castonself abuse prevention.
+ * If a skill, casted by itemskill() script command, is aborted while target selection,
+ * the map server gets no notification where these states could be unset.
+ * Thus we need this helper variables to prevent abusing these states for next skill cast.
+ */
+ int itemskill_id;
+ int itemskill_lv;
};
#define EQP_WEAPON EQP_HAND_R
@@ -1021,6 +1034,7 @@ END_ZEROED_BLOCK; /* End */
void (*unequipitem_pos) (struct map_session_data *sd, int n, int pos);
int (*checkitem) (struct map_session_data *sd);
int (*useitem) (struct map_session_data *sd,int n);
+ int (*itemskill_clear) (struct map_session_data *sd);
int (*skillatk_bonus) (struct map_session_data *sd, uint16 skill_id);
int (*skillheal_bonus) (struct map_session_data *sd, uint16 skill_id);
@@ -1037,7 +1051,7 @@ END_ZEROED_BLOCK; /* End */
int (*setcart) (struct map_session_data* sd, int type);
void (*setfalcon) (struct map_session_data *sd, bool flag);
void (*setridingpeco) (struct map_session_data *sd, bool flag);
- void (*setmadogear) (struct map_session_data *sd, bool flag);
+ void (*setmadogear) (struct map_session_data *sd, bool flag, enum mado_type mtype);
void (*setridingdragon) (struct map_session_data *sd, unsigned int type);
void (*setridingwug) (struct map_session_data *sd, bool flag);
int (*changelook) (struct map_session_data *sd,int type,int val);
diff --git a/src/map/pet.c b/src/map/pet.c
index f20de2650..aeb372c05 100644
--- a/src/map/pet.c
+++ b/src/map/pet.c
@@ -891,7 +891,8 @@ static int pet_randomwalk(struct pet_data *pd, int64 tick)
int r=rnd();
int x=pd->bl.x+r%(d*2+1)-d;
int y=pd->bl.y+r/(d*2+1)%(d*2+1)-d;
- if(map->getcell (pd->bl.m, &pd->bl, x, y, CELL_CHKPASS) && unit->walktoxy(&pd->bl, x, y, 0)) {
+ if (map->getcell(pd->bl.m, &pd->bl, x, y, CELL_CHKPASS) != 0
+ && unit->walk_toxy(&pd->bl, x, y, 0) == 0) {
pd->move_fail_count=0;
break;
}
@@ -991,7 +992,7 @@ static int pet_ai_sub_hard(struct pet_data *pd, struct map_session_data *sd, int
return 0; //Already walking to him
unit->calc_pos(&pd->bl, sd->bl.x, sd->bl.y, sd->ud.dir);
- if(!unit->walktoxy(&pd->bl,pd->ud.to_x,pd->ud.to_y,0))
+ if (unit->walk_toxy(&pd->bl, pd->ud.to_x, pd->ud.to_y, 0) != 0)
pet->randomwalk(pd,tick);
return 0;
diff --git a/src/map/script.c b/src/map/script.c
index 755a099ec..0cfd6da73 100644
--- a/src/map/script.c
+++ b/src/map/script.c
@@ -10694,6 +10694,7 @@ static BUILDIN(checkmount)
/**
* Mounts or dismounts a combat mount.
*
+ * setmount <flag>, <mtype>;
* setmount <flag>;
* setmount;
*
@@ -10712,6 +10713,8 @@ static BUILDIN(checkmount)
* If an invalid value or no flag is specified, the appropriate mount is
* auto-detected. As a result of this, there is no need to specify a flag at
* all, unless it is a dragon color other than green.
+ *
+ * In newer clients you can specify the mado gear type though the mtype argument.
*/
static BUILDIN(setmount)
{
@@ -10724,6 +10727,12 @@ static BUILDIN(setmount)
if (script_hasdata(st,2))
flag = script_getnum(st,2);
+ enum mado_type mtype = script_hasdata(st, 3) ? script_getnum(st, 3) : MADO_ROBOT;
+ if (mtype < MADO_ROBOT || mtype >= MADO_MAX) {
+ ShowError("script_setmount: Invalid mado type has been passed (%d).\n", flag);
+ return false;
+ }
+
// Color variants for Rune Knight dragon mounts.
if (flag != SETMOUNT_TYPE_NONE) {
if (flag < SETMOUNT_TYPE_AUTODETECT || flag >= SETMOUNT_TYPE_MAX) {
@@ -10750,7 +10759,7 @@ static BUILDIN(setmount)
} else if ((sd->job & MAPID_THIRDMASK) == MAPID_MECHANIC) {
// Mechanic (Mado Gear)
if (pc->checkskill(sd, NC_MADOLICENCE))
- pc->setmadogear(sd, true);
+ pc->setmadogear(sd, true, mtype);
} else {
// Knight / Crusader (Peco Peco)
if (pc->checkskill(sd, KN_RIDING))
@@ -10764,7 +10773,7 @@ static BUILDIN(setmount)
pc->setridingwug(sd, false);
}
if (pc_ismadogear(sd)) {
- pc->setmadogear(sd, false);
+ pc->setmadogear(sd, false, mtype);
}
if (pc_isridingpeco(sd)) {
pc->setridingpeco(sd, false);
@@ -10976,33 +10985,51 @@ static BUILDIN(guildopenstorage)
return true;
}
-/*==========================================
- * Make player use a skill trought item usage
- *------------------------------------------*/
-/// itemskill <skill id>,<level>{,flag
-/// itemskill "<skill name>",<level>{,flag
+/**
+ * Makes the attached character use a skill by using an item.
+ *
+ * @code{.herc}
+ * itemskill(<skill id>, <skill level>{, <flag>});
+ * itemskill("<skill name>", <skill level>{, <flag>});
+ * @endcode
+ *
+ */
static BUILDIN(itemskill)
{
- int id;
- int lv;
struct map_session_data *sd = script->rid2sd(st);
+
if (sd == NULL || sd->ud.skilltimer != INVALID_TIMER)
return true;
- id = ( script_isstringtype(st,2) ? skill->name2id(script_getstr(st,2)) : script_getnum(st,2) );
- lv = script_getnum(st,3);
-/* temporarily disabled, awaiting for kenpachi to detail this so we can make it work properly */
-#if 0
- if( !script_hasdata(st, 4) ) {
- if( !skill->check_condition_castbegin(sd,id,lv) || !skill->check_condition_castend(sd,id,lv) )
+ sd->skillitem = script_isstringtype(st, 2) ? skill->name2id(script_getstr(st, 2)) : script_getnum(st, 2);
+ sd->skillitemlv = script_getnum(st, 3);
+ sd->state.itemskill_conditions_checked = 0; // Skill casting items will check the conditions prior to the target selection in AEGIS. Thus we need a flag to prevent checking them twice.
+
+ int flag = script_hasdata(st, 4) ? script_getnum(st, 4) : ISF_NONE;
+
+ sd->state.itemskill_no_conditions = ((flag & ISF_IGNORECONDITIONS) == ISF_IGNORECONDITIONS) ? 1 : 0; // Unset in pc_itemskill_clear().
+
+ if (sd->state.itemskill_no_conditions == 0) {
+ if (skill->check_condition_castbegin(sd, sd->skillitem, sd->skillitemlv) == 0
+ || skill->check_condition_castend(sd, sd->skillitem, sd->skillitemlv) == 0) {
return true;
+ }
+
+ sd->state.itemskill_conditions_checked = 1; // Unset in pc_itemskill_clear().
}
-#endif
- sd->skillitem=id;
- sd->skillitemlv=lv;
- clif->item_skill(sd,id,lv);
+
+ sd->state.itemskill_no_casttime = ((flag & ISF_INSTANTCAST) == ISF_INSTANTCAST) ? 1 : 0; // Unset in pc_itemskill_clear().
+ sd->state.itemskill_castonself = ((flag & ISF_CASTONSELF) == ISF_CASTONSELF) ? 1 : 0; // Unset in pc_itemskill_clear().
+
+ // itemskill_conditions_checked/itemskill_no_conditions/itemskill_no_casttime/itemskill_castonself abuse prevention. Unset in pc_itemskill_clear().
+ sd->itemskill_id = sd->skillitem;
+ sd->itemskill_lv = sd->skillitemlv;
+
+ clif->item_skill(sd, sd->skillitem, sd->skillitemlv);
+
return true;
}
+
/*==========================================
* Attempt to create an item
*------------------------------------------*/
@@ -11775,6 +11802,13 @@ static BUILDIN(getunits)
const char *mapname = script_getstr(st, 5);
int16 m = map->mapname2mapid(mapname);
+ if (m == -1) {
+ ShowError("script:getunits: Invalid map(%s) provided.\n", mapname);
+ script->reportdata(data);
+ st->state = END;
+ return false;
+ }
+
if (script_hasdata(st, 9)) {
int16 x1 = script_getnum(st, 6);
int16 y1 = script_getnum(st, 7);
@@ -12489,6 +12523,56 @@ static BUILDIN(hideonnpc)
npc->enable(str,4);
return true;
}
+/*==========================================
+ *------------------------------------------*/
+static BUILDIN(cloakonnpc)
+{
+ struct npc_data *nd = npc->name2id(script_getstr(st, 2));
+ if (nd == NULL) {
+ ShowError("buildin_cloakonnpc: invalid npc name '%s'.\n", script_getstr(st, 2));
+ return false;
+ }
+
+ if (script_hasdata(st, 3)) {
+ struct map_session_data *sd = map->id2sd(script_getnum(st, 3));
+ if (sd == NULL)
+ return false;
+
+ uint32 val = nd->option;
+ nd->option |= OPTION_CLOAK;
+ clif->changeoption_target(&nd->bl, &sd->bl, SELF);
+ nd->option = val;
+ } else {
+ nd->option |= OPTION_CLOAK;
+ clif->changeoption(&nd->bl);
+ }
+ return true;
+}
+/*==========================================
+ *------------------------------------------*/
+static BUILDIN(cloakoffnpc)
+{
+ struct npc_data *nd = npc->name2id(script_getstr(st, 2));
+ if (nd == NULL) {
+ ShowError("buildin_cloakoffnpc: invalid npc name '%s'.\n", script_getstr(st, 2));
+ return false;
+ }
+
+ if (script_hasdata(st, 3)) {
+ struct map_session_data *sd = map->id2sd(script_getnum(st, 3));
+ if (sd == NULL)
+ return false;
+
+ uint32 val = nd->option;
+ nd->option &= ~OPTION_CLOAK;
+ clif->changeoption_target(&nd->bl, &sd->bl, SELF);
+ nd->option = val;
+ } else {
+ nd->option &= ~OPTION_CLOAK;
+ clif->changeoption(&nd->bl);
+ }
+ return true;
+}
/* Starts a status effect on the target unit or on the attached player.
*
@@ -14526,47 +14610,53 @@ static BUILDIN(strmobinfo)
return true;
}
-/*==========================================
- * Summon guardians [Valaris]
- * guardian("<map name>",<x>,<y>,"<name to show>",<mob id>{,"<event label>"}{,<guardian index>}) -> <id>
- *------------------------------------------*/
+/**
+ * Summons a castle guardian mob.
+ *
+ * @code{.herc}
+ * guardian("<map name>", <x>, <y>, "<name to show>", <mob id>{, <guardian index>});
+ * guardian("<map name>", <x>, <y>, "<name to show>", <mob id>{, "<event label>"{, <guardian index>}});
+ * @endcode
+ *
+ * @author Valaris
+ *
+ **/
static BUILDIN(guardian)
{
- int class_ = 0, x = 0, y = 0, guardian = 0;
- const char *str, *mapname, *evt="";
bool has_index = false;
+ int guardian = 0;
+ const char *event = "";
- mapname = script_getstr(st,2);
- x = script_getnum(st,3);
- y = script_getnum(st,4);
- str = script_getstr(st,5);
- class_ = script_getnum(st,6);
-
- if( script_hasdata(st,8) )
- {// "<event label>",<guardian index>
- evt=script_getstr(st,7);
- guardian=script_getnum(st,8);
+ if (script_hasdata(st, 8)) { /// "<event label>", <guardian index>
+ event = script_getstr(st, 7);
+ script->check_event(st, event);
+ guardian = script_getnum(st, 8);
has_index = true;
- } else if( script_hasdata(st,7) ) {
- struct script_data *data = script_getdata(st,7);
- script->get_val(st,data); // Dereference if it's a variable
- if( data_isstring(data) ) {
- // "<event label>"
- evt=script_getstr(st,7);
- } else if( data_isint(data) ) {
- // <guardian index>
- guardian=script_getnum(st,7);
+ } else if (script_hasdata(st, 7)) {
+ struct script_data *data = script_getdata(st, 7);
+
+ script->get_val(st, data); /// Dereference if it's a variable.
+
+ if (data_isstring(data)) { /// "<event label>"
+ event = script_getstr(st, 7);
+ script->check_event(st, event);
+ } else if (data_isint(data)) { /// <guardian index>
+ guardian = script_getnum(st, 7);
has_index = true;
} else {
- ShowError("script:guardian: invalid data type for argument #6 (from 1)\n");
+ ShowError("script:guardian: Invalid data type for argument #6!\n");
script->reportdata(data);
return false;
}
}
- script->check_event(st, evt);
- script_pushint(st, mob->spawn_guardian(mapname,x,y,str,class_,evt,guardian,has_index));
+ const char *mapname = script_getstr(st, 2);
+ const char *name = script_getstr(st, 5);
+ const int x = script_getnum(st, 3);
+ const int y = script_getnum(st, 4);
+ const int mob_id = script_getnum(st, 6);
+ script_pushint(st, mob->spawn_guardian(mapname, x, y, name, mob_id, event, guardian, has_index, st->oid));
return true;
}
/*==========================================
@@ -16468,7 +16558,7 @@ static BUILDIN(npcwalkto)
} else {
status_calc_npc(nd, SCO_NONE);
}
- unit->walktoxy(&nd->bl, x, y, 0);
+ unit->walk_toxy(&nd->bl, x, y, 0);
}
return true;
@@ -16894,38 +16984,54 @@ static BUILDIN(logmes)
return true;
}
+/**
+ * Summons a mob which will act as a slave for the invoking character.
+ *
+ * @code{.herc}
+ * summon("mob name", <mob id>{, <timeout>{, "event label"}});
+ * @endcode
+ *
+ * @author Celest
+ *
+ **/
static BUILDIN(summon)
{
- int class_, timeout=0;
- const char *str,*event="";
- struct mob_data *md;
- int64 tick = timer->gettick();
struct map_session_data *sd = script->rid2sd(st);
+
if (sd == NULL)
return true;
- str = script_getstr(st,2);
- class_ = script_getnum(st,3);
- if( script_hasdata(st,4) )
- timeout=script_getnum(st,4);
- if( script_hasdata(st,5) ) {
- event=script_getstr(st,5);
+ const int64 tick = timer->gettick();
+
+ clif->skill_poseffect(&sd->bl, AM_CALLHOMUN, 1, sd->bl.x, sd->bl.y, tick);
+
+ const char *event = "";
+
+ if (script_hasdata(st, 5)) {
+ event = script_getstr(st, 5);
script->check_event(st, event);
}
- clif->skill_poseffect(&sd->bl,AM_CALLHOMUN,1,sd->bl.x,sd->bl.y,tick);
+ const char *name = script_getstr(st, 2);
+ const int mob_id = script_getnum(st, 3);
+ struct mob_data *md = mob->once_spawn_sub(&sd->bl, sd->bl.m, sd->bl.x, sd->bl.y, name, mob_id, event,
+ SZ_SMALL, AI_NONE, 0);
- md = mob->once_spawn_sub(&sd->bl, sd->bl.m, sd->bl.x, sd->bl.y, str, class_, event, SZ_SMALL, AI_NONE);
- if (md) {
- md->master_id=sd->bl.id;
+ if (md != NULL) {
+ md->master_id = sd->bl.id;
md->special_state.ai = AI_ATTACK;
- if( md->deletetimer != INVALID_TIMER )
+
+ if (md->deletetimer != INVALID_TIMER)
timer->delete(md->deletetimer, mob->timer_delete);
- md->deletetimer = timer->add(tick+(timeout>0?timeout*1000:60000),mob->timer_delete,md->bl.id,0);
- mob->spawn (md); //Now it is ready for spawning.
- clif->specialeffect(&md->bl,344,AREA);
+
+ const int timeout = script_hasdata(st, 4) ? script_getnum(st, 4) * 1000 : 60000;
+
+ md->deletetimer = timer->add(tick + ((timeout == 0) ? 60000 : timeout), mob->timer_delete, md->bl.id, 0);
+ mob->spawn(md);
+ clif->specialeffect(&md->bl, 344, AREA);
sc_start4(NULL, &md->bl, SC_MODECHANGE, 100, 1, 0, MD_AGGRESSIVE, 0, 60000);
}
+
return true;
}
@@ -19574,7 +19680,7 @@ static BUILDIN(setunitdata)
unit->warp(bl, (short) val, (short) val2, (short) val3, CLR_TELEPORT);
break;
case UDT_WALKTOXY:
- if (!unit->walktoxy(bl, (short) val, (short) val2, 2))
+ if (unit->walk_toxy(bl, (short)val, (short)val2, 2) != 0)
unit->movepos(bl, (short) val, (short) val2, 0, 0);
break;
case UDT_SPEED:
@@ -19621,7 +19727,7 @@ static BUILDIN(setunitdata)
clif->changelook(bl, LOOK_WEAPON, val);
break;
case UDT_LOOKDIR:
- unit->setdir(bl, (uint8) val);
+ unit->set_dir(bl, (enum unit_dir)val);
break;
case UDT_CANMOVETICK:
md->ud.canmove_tick = val;
@@ -19745,7 +19851,7 @@ static BUILDIN(setunitdata)
unit->warp(bl, (short) val, (short) val2, (short) val3, CLR_TELEPORT);
break;
case UDT_WALKTOXY:
- if (!unit->walktoxy(bl, (short) val, (short) val2, 2))
+ if (unit->walk_toxy(bl, (short)val, (short)val2, 2) != 0)
unit->movepos(bl, (short) val, (short) val2, 0, 0);
break;
case UDT_SPEED:
@@ -19753,7 +19859,7 @@ static BUILDIN(setunitdata)
status->calc_misc(bl, &hd->base_status, hd->homunculus.level);
break;
case UDT_LOOKDIR:
- unit->setdir(bl, (unsigned char) val);
+ unit->set_dir(bl, (enum unit_dir)val);
break;
case UDT_CANMOVETICK:
hd->ud.canmove_tick = val;
@@ -19884,7 +19990,7 @@ static BUILDIN(setunitdata)
unit->warp(bl, (short) val, (short) val2, (short) val3, CLR_TELEPORT);
break;
case UDT_WALKTOXY:
- if (!unit->walktoxy(bl, (short) val, (short) val2, 2))
+ if (unit->walk_toxy(bl, (short)val, (short)val2, 2) != 0)
unit->movepos(bl, (short) val, (short) val2, 0, 0);
break;
case UDT_SPEED:
@@ -19892,7 +19998,7 @@ static BUILDIN(setunitdata)
status->calc_misc(bl, &pd->status, pd->pet.level);
break;
case UDT_LOOKDIR:
- unit->setdir(bl, (unsigned char) val);
+ unit->set_dir(bl, (enum unit_dir)val);
break;
case UDT_CANMOVETICK:
pd->ud.canmove_tick = val;
@@ -20017,7 +20123,7 @@ static BUILDIN(setunitdata)
unit->warp(bl, (short) val, (short) val2, (short) val3, CLR_TELEPORT);
break;
case UDT_WALKTOXY:
- if (!unit->walktoxy(bl, (short) val, (short) val2, 2))
+ if (unit->walk_toxy(bl, (short)val, (short)val2, 2) != 0)
unit->movepos(bl, (short) val, (short) val2, 0, 0);
break;
case UDT_SPEED:
@@ -20025,7 +20131,7 @@ static BUILDIN(setunitdata)
status->calc_misc(bl, &mc->base_status, mc->db->lv);
break;
case UDT_LOOKDIR:
- unit->setdir(bl, (unsigned char) val);
+ unit->set_dir(bl, (enum unit_dir)val);
break;
case UDT_CANMOVETICK:
mc->ud.canmove_tick = val;
@@ -20151,7 +20257,7 @@ static BUILDIN(setunitdata)
unit->warp(bl, (short) val, (short) val2, (short) val3, CLR_TELEPORT);
break;
case UDT_WALKTOXY:
- if (!unit->walktoxy(bl, (short) val, (short) val2, 2))
+ if (unit->walk_toxy(bl, (short)val, (short)val2, 2) != 0)
unit->movepos(bl, (short) val, (short) val2, 0, 0);
break;
case UDT_SPEED:
@@ -20159,7 +20265,7 @@ static BUILDIN(setunitdata)
status->calc_misc(bl, &ed->base_status, ed->db->lv);
break;
case UDT_LOOKDIR:
- unit->setdir(bl, (unsigned char) val);
+ unit->set_dir(bl, (enum unit_dir)val);
break;
case UDT_CANMOVETICK:
ed->ud.canmove_tick = val;
@@ -20280,7 +20386,7 @@ static BUILDIN(setunitdata)
unit->warp(bl, (short) val, (short) val2, (short) val3, CLR_TELEPORT);
break;
case UDT_WALKTOXY:
- if (!unit->walktoxy(bl, (short) val, (short) val2, 2))
+ if (unit->walk_toxy(bl, (short)val, (short)val2, 2) != 0)
unit->movepos(bl, (short) val, (short) val2, 0, 0);
break;
case UDT_CLASS:
@@ -20291,7 +20397,7 @@ static BUILDIN(setunitdata)
status->calc_misc(bl, &nd->status, nd->level);
break;
case UDT_LOOKDIR:
- unit->setdir(bl, (unsigned char) val);
+ unit->set_dir(bl, (enum unit_dir)val);
break;
case UDT_STR:
nd->status.str = (unsigned short) val;
@@ -21015,7 +21121,10 @@ static BUILDIN(unitwalk)
if (script_hasdata(st, 4)) {
int x = script_getnum(st, 3);
int y = script_getnum(st, 4);
- script_pushint(st, unit->walktoxy(bl, x, y, 0));// We'll use harder calculations.
+ if (unit->walk_toxy(bl, x, y, 0) == 0) // We'll use harder calculations.
+ script_pushint(st, 1);
+ else
+ script_pushint(st, 0);
}
else {
int target_id = script_getnum(st, 3);
@@ -22251,6 +22360,23 @@ static BUILDIN(achievement_progress)
return true;
}
+static BUILDIN(achievement_iscompleted)
+{
+ struct map_session_data *sd = script_hasdata(st, 3) ? map->id2sd(script_getnum(st, 3)) : script->rid2sd(st);
+ if (sd == NULL)
+ return false;
+
+ int aid = script_getnum(st, 2);
+ const struct achievement_data *ad = achievement->get(aid);
+ if (ad == NULL) {
+ ShowError("buildin_achievement_iscompleted: Invalid Achievement %d provided.\n", aid);
+ return false;
+ }
+
+ script_pushint(st, achievement->check_complete(sd, ad));
+ return true;
+}
+
/*==========================================
* BattleGround System
*------------------------------------------*/
@@ -22372,20 +22498,31 @@ static BUILDIN(bg_warp)
return true;
}
+/**
+ * Spawns a mob with allegiance to the given battle group.
+ *
+ * @code{.herc}
+ * bg_monster(<battle group>, "<map name>", <x>, <y>, "<name to show>", <mob id>{, "<event label>"});
+ * @endcode
+ *
+ **/
static BUILDIN(bg_monster)
{
- int class_ = 0, x = 0, y = 0, bg_id = 0;
- const char *str, *mapname, *evt="";
+ const char *event = "";
+
+ if (script_hasdata(st, 8)) {
+ event = script_getstr(st, 8);
+ script->check_event(st, event);
+ }
- bg_id = script_getnum(st,2);
- mapname = script_getstr(st,3);
- x = script_getnum(st,4);
- y = script_getnum(st,5);
- str = script_getstr(st,6);
- class_ = script_getnum(st,7);
- if( script_hasdata(st,8) ) evt = script_getstr(st,8);
- script->check_event(st, evt);
- script_pushint(st, mob->spawn_bg(mapname,x,y,str,class_,evt,bg_id));
+ const char *mapname = script_getstr(st, 3);
+ const char *name = script_getstr(st, 6);
+ const int bg_id = script_getnum(st, 2);
+ const int x = script_getnum(st, 4);
+ const int y = script_getnum(st, 5);
+ const int mob_id = script_getnum(st, 7);
+
+ script_pushint(st, mob->spawn_bg(mapname, x, y, name, mob_id, event, bg_id, st->oid));
return true;
}
@@ -23148,7 +23285,6 @@ static BUILDIN(progressbar_unit)
}
static BUILDIN(pushpc)
{
- uint8 dir;
int cells, dx, dy;
struct map_session_data* sd;
@@ -23157,14 +23293,14 @@ static BUILDIN(pushpc)
return true;
}
- dir = script_getnum(st,2);
- cells = script_getnum(st,3);
+ enum unit_dir dir = script_getnum(st, 2);
+ cells = script_getnum(st,3);
- if (dir > 7) {
+ if (dir >= UNIT_DIR_MAX) {
ShowWarning("buildin_pushpc: Invalid direction %d specified.\n", dir);
script->reportsrc(st);
- dir%= 8; // trim spin-over
+ dir %= UNIT_DIR_MAX; // trim spin-over
}
if(!cells)
@@ -23173,10 +23309,11 @@ static BUILDIN(pushpc)
}
else if(cells<0)
{// pushing backwards
- dir = (dir+4)%8; // turn around
- cells = -cells;
+ dir = unit_get_opposite_dir(dir);
+ cells = -cells;
}
+ Assert_retr(false, dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
dx = dirx[dir];
dy = diry[dir];
@@ -24709,6 +24846,133 @@ static BUILDIN(openshop)
return true;
}
+static bool script_sellitemcurrency_add(struct npc_data *nd, struct script_state* st, int argIndex)
+{
+ nullpo_retr(false, nd);
+ nullpo_retr(false, st);
+
+ if (!script_hasdata(st, argIndex + 1))
+ return false;
+
+ int id = script_getnum(st, argIndex);
+ struct item_data *it;
+ if (!(it = itemdb->exists(id))) {
+ ShowWarning("buildin_sellitemcurrency: unknown item id '%d'!\n", id);
+ return false;
+ }
+ int qty = 0;
+ if ((qty = script_getnum(st, argIndex + 1)) <= 0) {
+ ShowError("buildin_sellitemcurrency: invalid 'qty'!\n");
+ return false;
+ }
+ int refine_level = -1;
+ if (script_hasdata(st, argIndex + 2)) {
+ refine_level = script_getnum(st, argIndex + 2);
+ }
+ int items = nd->u.scr.shop->items;
+ if (nd->u.scr.shop == NULL || items == 0) {
+ ShowWarning("buildin_sellitemcurrency: shop not have items!\n");
+ return false;
+ }
+ if (nd->u.scr.shop->shop_last_index >= items || nd->u.scr.shop->shop_last_index < 0) {
+ ShowWarning("buildin_sellitemcurrency: wrong selected shop index!\n");
+ return false;
+ }
+
+ struct npc_item_list *item_list = &nd->u.scr.shop->item[nd->u.scr.shop->shop_last_index];
+ int index = item_list->value2;
+ if (item_list->currency == NULL) {
+ CREATE(item_list->currency, struct npc_barter_currency, 1);
+ item_list->value2 ++;
+ } else {
+ RECREATE(item_list->currency, struct npc_barter_currency, ++item_list->value2);
+ }
+ struct npc_barter_currency *currency = &item_list->currency[index];
+ currency->nameid = id;
+ currency->refine = refine_level;
+ currency->amount = qty;
+ return true;
+}
+
+/**
+ * @call sellitemcurrency <Item_ID>,qty{,refine}};
+ *
+ * adds <Item_ID> to last item in expanded barter shop
+ **/
+static BUILDIN(sellitemcurrency)
+{
+ struct npc_data *nd;
+ if ((nd = map->id2nd(st->oid)) == NULL) {
+ ShowWarning("buildin_sellitemcurrency: trying to run without a proper NPC!\n");
+ return false;
+ }
+ if (nd->u.scr.shop == NULL || nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
+ ShowWarning("buildin_sellitemcurrency: this command can be used only with expanded barter shops!\n");
+ return false;
+ }
+
+ script->sellitemcurrency_add(nd, st, 2);
+ return true;
+}
+
+/**
+ * @call endsellitem;
+ *
+ * complete sell item in expanded barter shop (NST_EXPANDED_BARTER)
+ **/
+static BUILDIN(endsellitem)
+{
+ struct npc_data *nd;
+ if ((nd = map->id2nd(st->oid)) == NULL) {
+ ShowWarning("buildin_endsellitem: trying to run without a proper NPC!\n");
+ return false;
+ }
+ if (nd->u.scr.shop == NULL || nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
+ ShowWarning("buildin_endsellitem: this command can be used only with expanded barter shops!\n");
+ return false;
+ }
+
+ int newIndex = nd->u.scr.shop->shop_last_index;
+ const struct npc_item_list *const newItem = &nd->u.scr.shop->item[newIndex];
+ int i = 0;
+ for (i = 0; i < nd->u.scr.shop->items - 1; i++) {
+ const struct npc_item_list *const item = &nd->u.scr.shop->item[i];
+ if (item->nameid != newItem->nameid || item->value != newItem->value)
+ continue;
+ if (item->value2 != newItem->value2)
+ continue;
+ bool found = true;
+ for (int k = 0; k < item->value2; k ++) {
+ struct npc_barter_currency *currency = &item->currency[k];
+ struct npc_barter_currency *newCurrency = &newItem->currency[k];
+ if (currency->nameid != newCurrency->nameid ||
+ currency->amount != newCurrency->amount ||
+ currency->refine != newCurrency->refine) {
+ found = false;
+ break;
+ }
+ }
+ if (!found)
+ continue;
+ break;
+ }
+
+ if (i != nd->u.scr.shop->items - 1) {
+ if (nd->u.scr.shop->item[i].qty != -1) {
+ nd->u.scr.shop->item[i].qty += nd->u.scr.shop->item[newIndex].qty;
+ npc->expanded_barter_tosql(nd, i);
+ }
+ nd->u.scr.shop->shop_last_index --;
+ nd->u.scr.shop->items--;
+ if (nd->u.scr.shop->item[newIndex].currency != NULL) {
+ aFree(nd->u.scr.shop->item[newIndex].currency);
+ nd->u.scr.shop->item[newIndex].currency = NULL;
+ }
+ }
+
+ return true;
+}
+
/**
* @call sellitem <Item_ID>,{,price{,qty}};
*
@@ -24732,30 +24996,65 @@ static BUILDIN(sellitem)
return false;
}
- if (!nd->u.scr.shop) {
+ const bool have_shop = (nd->u.scr.shop != NULL);
+ if (!have_shop) {
npc->trader_update(nd->src_id ? nd->src_id : nd->bl.id);
- if (nd->u.scr.shop->type == NST_BARTER) {
- if (!script_hasdata(st, 5)) {
- ShowError("buildin_sellitem: invalid number of parameters for barter-type shop!\n");
- return false;
- }
- value = script_getnum(st, 4);
- value2 = script_getnum(st, 5);
+ }
+
+ if (nd->u.scr.shop->type != NST_BARTER) {
+ value = script_hasdata(st, 3) ? script_getnum(st, 3) : it->value_buy;
+ if (value == -1)
+ value = it->value_buy;
+ }
+
+ if (nd->u.scr.shop->type == NST_BARTER) {
+ if (!script_hasdata(st, 5)) {
+ ShowError("buildin_sellitem: invalid number of parameters for barter-type shop!\n");
+ return false;
+ }
+ value = script_getnum(st, 4);
+ value2 = script_getnum(st, 5);
+ } else if (nd->u.scr.shop->type == NST_EXPANDED_BARTER) {
+ if (!script_hasdata(st, 4)) {
+ ShowError("buildin_sellitem: invalid number of parameters for expanded barter type shop!\n");
+ return false;
+ }
+ if ((qty = script_getnum(st, 4)) <= 0 && qty != -1) {
+ ShowError("buildin_sellitem: invalid 'qty' for expanded barter type shop!\n");
+ return false;
}
- } else {/* no need to run this if its empty */
+ }
+
+ if (have_shop) {
if (nd->u.scr.shop->type == NST_BARTER) {
- if (!script_hasdata(st, 5)) {
- ShowError("buildin_sellitem: invalid number of parameters for barter-type shop!\n");
- return false;
- }
- value = script_getnum(st, 4);
- value2 = script_getnum(st, 5);
for (i = 0; i < nd->u.scr.shop->items; i++) {
const struct npc_item_list *const item = &nd->u.scr.shop->item[i];
if (item->nameid == id && item->value == value && item->value2 == value2) {
break;
}
}
+ } else if (nd->u.scr.shop->type == NST_EXPANDED_BARTER) {
+ for (i = 0; i < nd->u.scr.shop->items; i++) {
+ const struct npc_item_list *const item = &nd->u.scr.shop->item[i];
+ if (item->nameid != id || item->value != value)
+ continue;
+ if (item->value2 != (script_lastdata(st) - 4) / 3)
+ continue;
+ bool found = true;
+ for (int k = 0; k < item->value2; k ++) {
+ const int scriptOffset = k * 3 + 5;
+ struct npc_barter_currency *currency = &item->currency[k];
+ if (currency->nameid != script_getnum(st, scriptOffset) ||
+ currency->amount != script_getnum(st, scriptOffset + 1) ||
+ currency->refine != script_getnum(st, scriptOffset + 2)) {
+ found = false;
+ break;
+ }
+ }
+ if (!found)
+ continue;
+ break;
+ }
} else {
for (i = 0; i < nd->u.scr.shop->items; i++) {
if (nd->u.scr.shop->item[i].nameid == id) {
@@ -24765,12 +25064,6 @@ static BUILDIN(sellitem)
}
}
- if (nd->u.scr.shop->type != NST_BARTER) {
- value = script_hasdata(st,3) ? script_getnum(st, 3) : it->value_buy;
- if( value == -1 )
- value = it->value_buy;
- }
-
if( nd->u.scr.shop->type == NST_MARKET ) {
if( !script_hasdata(st,4) || ( qty = script_getnum(st, 4) ) <= 0 ) {
ShowError("buildin_sellitem: invalid 'qty' for market-type shop!\n");
@@ -24791,7 +25084,8 @@ static BUILDIN(sellitem)
}
}
- if (i != nd->u.scr.shop->items) {
+ bool foundInShop = (i != nd->u.scr.shop->items);
+ if (foundInShop) {
nd->u.scr.shop->item[i].value = value;
nd->u.scr.shop->item[i].qty = qty;
if (nd->u.scr.shop->type == NST_MARKET) /* has been manually updated, make it reflect on sql */
@@ -24817,8 +25111,84 @@ static BUILDIN(sellitem)
nd->u.scr.shop->item[i].value = value;
nd->u.scr.shop->item[i].value2 = value2;
nd->u.scr.shop->item[i].qty = qty;
+ nd->u.scr.shop->item[i].currency = NULL;
+ }
+ nd->u.scr.shop->shop_last_index = i;
+
+ if (!foundInShop) {
+ for (int k = 5; k <= script_lastdata(st); k += 3) {
+ script->sellitemcurrency_add(nd, st, k);
+ }
}
+ if (foundInShop) {
+ if (nd->u.scr.shop->type == NST_EXPANDED_BARTER) { /* has been manually updated, make it reflect on sql */
+ npc->expanded_barter_tosql(nd, i);
+ }
+ }
+ return true;
+}
+
+/**
+ * @call startsellitem <Item_ID>,{,price{,qty}};
+ *
+ * Starts adding item into expanded barter shop (NST_EXPANDED_BARTER)
+ **/
+static BUILDIN(startsellitem)
+{
+ struct npc_data *nd;
+ struct item_data *it;
+ int i = 0, id = script_getnum(st,2);
+ int value2 = 0;
+ int qty = 0;
+
+ if (!(nd = map->id2nd(st->oid))) {
+ ShowWarning("buildin_startsellitem: trying to run without a proper NPC!\n");
+ return false;
+ } else if (!(it = itemdb->exists(id))) {
+ ShowWarning("buildin_startsellitem: unknown item id '%d'!\n", id);
+ return false;
+ }
+
+ const bool have_shop = (nd->u.scr.shop != NULL);
+ if (!have_shop) {
+ npc->trader_update(nd->src_id ? nd->src_id : nd->bl.id);
+ }
+
+ if (nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
+ ShowWarning("script_startsellitem: can works only for NST_EXPANDED_BARTER shops");
+ return false;
+ }
+
+ int value = script_hasdata(st, 3) ? script_getnum(st, 3) : it->value_buy;
+ if (value == -1)
+ value = it->value_buy;
+
+ if ((qty = script_getnum(st, 4)) <= 0 && qty != -1) {
+ ShowError("buildin_startsellitem: invalid 'qty' for expanded barter type shop!\n");
+ return false;
+ }
+
+ for (i = 0; i < nd->u.scr.shop->items; i++) {
+ if (nd->u.scr.shop->item[i].nameid == 0)
+ break;
+ }
+
+ if (i == nd->u.scr.shop->items) {
+ if (nd->u.scr.shop->items == USHRT_MAX) {
+ ShowWarning("buildin_startsellitem: Can't add %s (%s/%s), shop list is full!\n", it->name, nd->exname, nd->path);
+ return false;
+ }
+ i = nd->u.scr.shop->items;
+ RECREATE(nd->u.scr.shop->item, struct npc_item_list, ++nd->u.scr.shop->items);
+ }
+
+ nd->u.scr.shop->item[i].nameid = it->nameid;
+ nd->u.scr.shop->item[i].value = value;
+ nd->u.scr.shop->item[i].value2 = value2;
+ nd->u.scr.shop->item[i].qty = qty;
+ nd->u.scr.shop->item[i].currency = NULL;
+ nd->u.scr.shop->shop_last_index = i;
return true;
}
@@ -24852,6 +25222,18 @@ static BUILDIN(stopselling)
break;
}
}
+ } else if (nd->u.scr.shop->type == NST_EXPANDED_BARTER) {
+ if (!script_hasdata(st, 3)) {
+ ShowError("buildin_stopselling: called with wrong number of arguments\n");
+ return false;
+ }
+ const int price = script_getnum(st, 3);
+ for (i = 0; i < nd->u.scr.shop->items; i++) {
+ const struct npc_item_list *const item = &nd->u.scr.shop->item[i];
+ if (item->nameid == id && item->value == price) {
+ break;
+ }
+ }
} else {
for (i = 0; i < nd->u.scr.shop->items; i++) {
if (nd->u.scr.shop->item[i].nameid == id) {
@@ -24865,13 +25247,19 @@ static BUILDIN(stopselling)
if (nd->u.scr.shop->type == NST_MARKET)
npc->market_delfromsql(nd, i);
- if (nd->u.scr.shop->type == NST_BARTER)
+ else if (nd->u.scr.shop->type == NST_BARTER)
npc->barter_delfromsql(nd, i);
+ else if (nd->u.scr.shop->type == NST_EXPANDED_BARTER)
+ npc->expanded_barter_delfromsql(nd, i);
nd->u.scr.shop->item[i].nameid = 0;
nd->u.scr.shop->item[i].value = 0;
nd->u.scr.shop->item[i].value2 = 0;
nd->u.scr.shop->item[i].qty = 0;
+ if (nd->u.scr.shop->item[i].currency != NULL) {
+ aFree(nd->u.scr.shop->item[i].currency);
+ nd->u.scr.shop->item[i].currency = NULL;
+ }
for (i = 0, cursor = 0; i < nd->u.scr.shop->items; i++) {
if (nd->u.scr.shop->item[i].nameid == 0)
@@ -24882,14 +25270,18 @@ static BUILDIN(stopselling)
nd->u.scr.shop->item[cursor].value = nd->u.scr.shop->item[i].value;
nd->u.scr.shop->item[cursor].value2 = nd->u.scr.shop->item[i].value2;
nd->u.scr.shop->item[cursor].qty = nd->u.scr.shop->item[i].qty;
+ nd->u.scr.shop->item[cursor].currency = nd->u.scr.shop->item[i].currency;
}
cursor++;
}
+ nd->u.scr.shop->items--;
+ nd->u.scr.shop->item[nd->u.scr.shop->items].currency = NULL;
script_pushint(st, 1);
- } else
+ } else {
script_pushint(st, 0);
+ }
return true;
}
@@ -24948,6 +25340,7 @@ static BUILDIN(tradertype)
}
npc->market_delfromsql(nd, INT_MAX);
npc->barter_delfromsql(nd, INT_MAX);
+ npc->expanded_barter_delfromsql(nd, INT_MAX);
}
#if PACKETVER < 20131223
@@ -24962,6 +25355,12 @@ static BUILDIN(tradertype)
script->reportsrc(st);
}
#endif
+#if PACKETVER_MAIN_NUM < 20191120 && PACKETVER_RE_NUM < 20191106 && PACKETVER_ZERO_NUM < 20191127
+ if (type == NST_EXPANDED_BARTER) {
+ ShowWarning("buildin_tradertype: NST_EXPANDED_BARTER is only available with PACKETVER_ZERO_NUM 20191127 or PACKETVER_MAIN_NUM 20191120 or PACKETVER_RE_NUM 20191106 or newer!\n");
+ script->reportsrc(st);
+ }
+#endif
if( nd->u.scr.shop )
nd->u.scr.shop->type = type;
@@ -25005,8 +25404,8 @@ static BUILDIN(shopcount)
} else if ( !nd->u.scr.shop || !nd->u.scr.shop->items ) {
ShowWarning("buildin_shopcount(%d): trying to use without any items!\n",id);
return false;
- } else if (nd->u.scr.shop->type != NST_MARKET && nd->u.scr.shop->type != NST_BARTER) {
- ShowWarning("buildin_shopcount(%d): trying to use on a non-NST_MARKET and non-NST_BARTER shop!\n",id);
+ } else if (nd->u.scr.shop->type != NST_MARKET && nd->u.scr.shop->type != NST_BARTER && nd->u.scr.shop->type != NST_EXPANDED_BARTER) {
+ ShowWarning("buildin_shopcount(%d): trying to use on a non-NST_MARKET and non-NST_BARTER and non-NST_EXPANDED_BARTER shop!\n",id);
return false;
}
@@ -26395,7 +26794,7 @@ static void script_parse_builtin(void)
BUILDIN_DEF(checkcart,""),
BUILDIN_DEF(setfalcon,"?"),
BUILDIN_DEF(checkfalcon,""),
- BUILDIN_DEF(setmount,"?"),
+ BUILDIN_DEF(setmount,"??"),
BUILDIN_DEF(checkmount,""),
BUILDIN_DEF(checkwug,""),
BUILDIN_DEF(savepoint,"sii"),
@@ -26442,6 +26841,8 @@ static void script_parse_builtin(void)
BUILDIN_DEF(disablenpc,"s"),
BUILDIN_DEF(hideoffnpc,"s"),
BUILDIN_DEF(hideonnpc,"s"),
+ BUILDIN_DEF(cloakonnpc,"s?"),
+ BUILDIN_DEF(cloakoffnpc,"s?"),
BUILDIN_DEF(sc_start,"iii???"),
BUILDIN_DEF2(sc_start,"sc_start2","iiii???"),
BUILDIN_DEF2(sc_start,"sc_start4","iiiiii???"),
@@ -26725,6 +27126,7 @@ static void script_parse_builtin(void)
BUILDIN_DEF(agitcheck2,""),
// Achievements [Smokexyz/Hercules]
BUILDIN_DEF(achievement_progress, "iiii?"),
+ BUILDIN_DEF(achievement_iscompleted, "i?"),
// BattleGround
BUILDIN_DEF(waitingroom2bg,"siiss?"),
BUILDIN_DEF(waitingroom2bg_single,"isiis"),
@@ -26836,7 +27238,10 @@ static void script_parse_builtin(void)
/* New Shop Support */
BUILDIN_DEF(openshop,"?"),
- BUILDIN_DEF(sellitem,"i???"),
+ BUILDIN_DEF(sellitem, "i???*"),
+ BUILDIN_DEF(sellitemcurrency, "ii?"),
+ BUILDIN_DEF(startsellitem, "iii"),
+ BUILDIN_DEF(endsellitem, ""),
BUILDIN_DEF(stopselling,"i??"),
BUILDIN_DEF(setcurrency,"i?"),
BUILDIN_DEF(tradertype,"i"),
@@ -27397,6 +27802,7 @@ static void script_hardcoded_constants(void)
script->set_constant("NST_MARKET", NST_MARKET, false, false);
script->set_constant("NST_CUSTOM", NST_CUSTOM, false, false);
script->set_constant("NST_BARTER", NST_BARTER, false, false);
+ script->set_constant("NST_EXPANDED_BARTER", NST_EXPANDED_BARTER, false, false);
script->constdb_comment("script unit data types");
script->set_constant("UDT_TYPE", UDT_TYPE, false, false);
@@ -27482,6 +27888,23 @@ static void script_hardcoded_constants(void)
script->set_constant("GUILDINFO_MASTER_NAME", GUILDINFO_MASTER_NAME, false, false);
script->set_constant("GUILDINFO_MASTER_CID", GUILDINFO_MASTER_CID, false, false);
+ script->constdb_comment("madogear types");
+ script->set_constant("MADO_ROBOT", MADO_ROBOT, false, false);
+ script->set_constant("MADO_SUITE", MADO_SUITE, false, false);
+
+ script->constdb_comment("itemskill option flags");
+ script->set_constant("ISF_NONE", ISF_NONE, false, false);
+ script->set_constant("ISF_IGNORECONDITIONS", ISF_IGNORECONDITIONS, false, false);
+ script->set_constant("ISF_INSTANTCAST", ISF_INSTANTCAST, false, false);
+ script->set_constant("ISF_CASTONSELF", ISF_CASTONSELF, false, false);
+
+ script->constdb_comment("Item Bound Types");
+ script->set_constant("IBT_ANY", IBT_NONE, false, false); // for *checkbound()
+ script->set_constant("IBT_ACCOUNT", IBT_ACCOUNT, false, false);
+ script->set_constant("IBT_GUILD", IBT_GUILD, false, false);
+ script->set_constant("IBT_PARTY", IBT_PARTY, false, false);
+ script->set_constant("IBT_CHARACTER", IBT_CHARACTER, false, false);
+
script->constdb_comment("Renewal");
#ifdef RENEWAL
script->set_constant("RENEWAL", 1, false, false);
@@ -27845,4 +28268,6 @@ void script_defaults(void)
script->run_item_rental_start_script = script_run_item_rental_start_script;
script->run_item_rental_end_script = script_run_item_rental_end_script;
script->run_item_lapineddukddak_script = script_run_item_lapineddukddak_script;
+
+ script->sellitemcurrency_add = script_sellitemcurrency_add;
}
diff --git a/src/map/script.h b/src/map/script.h
index d652f2155..857d22c61 100644
--- a/src/map/script.h
+++ b/src/map/script.h
@@ -553,6 +553,28 @@ enum siege_type {
};
/**
+ * Types of MadoGear
+ */
+enum mado_type {
+ MADO_ROBOT = 0x00,
+ // unused = 0x01,
+ MADO_SUITE = 0x02,
+#ifndef MADO_MAX
+ MADO_MAX
+#endif
+};
+
+/**
+ * Option flags for itemskill() script command.
+ **/
+enum itemskill_flag {
+ ISF_NONE = 0x00,
+ ISF_IGNORECONDITIONS = 0x01, // Ignore skill conditions and don't consume them.
+ ISF_INSTANTCAST = 0x02, // Cast skill instantaneously.
+ ISF_CASTONSELF = 0x04, // Forcefully cast skill on invoking character without showing the target selection cursor.
+};
+
+/**
* Structures
**/
@@ -1054,6 +1076,7 @@ struct script_interface {
void (*run_item_rental_end_script) (struct map_session_data *sd, struct item_data *data, int oid);
void (*run_item_rental_start_script) (struct map_session_data *sd, struct item_data *data, int oid);
void (*run_item_lapineddukddak_script) (struct map_session_data *sd, struct item_data *data, int oid);
+ bool (*sellitemcurrency_add) (struct npc_data *nd, struct script_state* st, int argIndex);
};
#ifdef HERCULES_CORE
diff --git a/src/map/skill.c b/src/map/skill.c
index 4579b2ea7..b3ed46fbd 100644
--- a/src/map/skill.c
+++ b/src/map/skill.c
@@ -67,20 +67,6 @@
#define SKILLUNITTIMER_INTERVAL 100
-// ranges reserved for mapping skill ids to skilldb offsets
-#define HM_SKILLRANGEMIN 750
-#define HM_SKILLRANGEMAX (HM_SKILLRANGEMIN + MAX_HOMUNSKILL)
-#define MC_SKILLRANGEMIN (HM_SKILLRANGEMAX + 1)
-#define MC_SKILLRANGEMAX (MC_SKILLRANGEMIN + MAX_MERCSKILL)
-#define EL_SKILLRANGEMIN (MC_SKILLRANGEMAX + 1)
-#define EL_SKILLRANGEMAX (EL_SKILLRANGEMIN + MAX_ELEMENTALSKILL)
-#define GD_SKILLRANGEMIN (EL_SKILLRANGEMAX + 1)
-#define GD_SKILLRANGEMAX (GD_SKILLRANGEMIN + MAX_GUILDSKILL)
-
-#if GD_SKILLRANGEMAX > 999
- #error GD_SKILLRANGEMAX is greater than 999
-#endif
-
static struct skill_interface skill_s;
static struct s_skill_dbs skilldbs;
@@ -110,41 +96,55 @@ static int skill_name2id(const char *name)
/// Returns the skill's array index, or 0 (Unknown Skill).
static int skill_get_index(int skill_id)
{
- // avoid ranges reserved for mapping guild/homun/mercenary skills
- if( (skill_id >= GD_SKILLRANGEMIN && skill_id <= GD_SKILLRANGEMAX)
- || (skill_id >= HM_SKILLRANGEMIN && skill_id <= HM_SKILLRANGEMAX)
- || (skill_id >= MC_SKILLRANGEMIN && skill_id <= MC_SKILLRANGEMAX)
- || (skill_id >= EL_SKILLRANGEMIN && skill_id <= EL_SKILLRANGEMAX) )
+ int skillRange[] = { NV_BASIC, NPC_LEX_AETERNA,
+ KN_CHARGEATK, SA_ELEMENTWIND,
+ RK_ENCHANTBLADE, AB_SILENTIUM,
+ WL_WHITEIMPRISON, SC_FEINTBOMB,
+ LG_CANNONSPEAR, SR_GENTLETOUCH_REVITALIZE,
+ WA_SWING_DANCE, WA_MOONLIT_SERENADE,
+ MI_RUSH_WINDMILL, MI_HARMONIZE,
+ WM_LESSON, WM_UNLIMITED_HUMMING_VOICE,
+ SO_FIREWALK, SO_EARTH_INSIGNIA,
+ GN_TRAINING_SWORD, GN_SLINGITEM_RANGEMELEEATK,
+ AB_SECRAMENT, LG_OVERBRAND_PLUSATK,
+ ALL_ODINS_RECALL, ALL_LIGHTGUARD,
+ RL_GLITTERING_GREED, RL_GLITTERING_GREED_ATK,
+ KO_YAMIKUMO, OB_AKAITSUKI,
+ ECL_SNOWFLIP, ALL_THANATOS_RECALL,
+ GC_DARKCROW, NC_MAGMA_ERUPTION_DOTDAMAGE,
+ SU_BASIC_SKILL, SU_SPIRITOFSEA,
+ HLIF_HEAL, MH_VOLCANIC_ASH,
+ MS_BASH, MER_INVINCIBLEOFF2,
+ EL_CIRCLE_OF_FIRE, EL_STONE_RAIN,
+ GD_APPROVAL, GD_DEVELOPMENT
+ CUSTOM_SKILL_RANGES};
+ int length = sizeof(skillRange) / sizeof(int);
+ STATIC_ASSERT(sizeof(skillRange) / sizeof(int) % 2 == 0, "skill_get_index: skillRange should be multiple of 2");
+
+
+ if (skill_id < skillRange[0] || skill_id > skillRange[length - 1]) {
+ ShowWarning("skill_get_index: skill id '%d' is not being handled!\n", skill_id);
return 0;
+ }
- // map skill id to skill db index
- if( skill_id >= GD_SKILLBASE )
- skill_id = GD_SKILLRANGEMIN + skill_id - GD_SKILLBASE;
- else if( skill_id >= EL_SKILLBASE )
- skill_id = EL_SKILLRANGEMIN + skill_id - EL_SKILLBASE;
- else if( skill_id >= MC_SKILLBASE )
- skill_id = MC_SKILLRANGEMIN + skill_id - MC_SKILLBASE;
- else if( skill_id >= HM_SKILLBASE )
- skill_id = HM_SKILLRANGEMIN + skill_id - HM_SKILLBASE;
- //[Ind/Hercules] GO GO GO LESS! - http://herc.ws/board/topic/512-skill-id-processing-overhaul/
- else if( skill_id > 1019 && skill_id < 8001 ) {
- if( skill_id < 2058 ) // 1020 - 2000 are empty
- skill_id = 1020 + skill_id - 2001;
- else if( skill_id < 2549 ) // 2058 - 2200 are empty - 1020+57
- skill_id = (1077) + skill_id - 2201;
- else if ( skill_id < 3036 ) // 2549 - 3000 are empty - 1020+57+348
- skill_id = (1425) + skill_id - 3001;
- else if ( skill_id < 5044 ) // 3036 - 5000 are empty - 1020+57+348+35
- skill_id = (1460) + skill_id - 5001;
- else
- ShowWarning("skill_get_index: skill id '%d' is not being handled!\n",skill_id);
+ int skill_idx = 0;
+ // Map Skill ID to Skill Indexes (in reverse order)
+ for (int i = 0; i < length; i += 2) {
+ // Check if SkillID belongs to this range.
+ if (skill_id <= skillRange[i + 1] && skill_id >= skillRange[i]) {
+ skill_idx += (skillRange[i + 1] - skill_id);
+ break;
+ }
+ // Add the difference of current range
+ skill_idx += (skillRange[i + 1] - skillRange[i] + 1);
}
- // validate result
- if (skill_id <= 0|| skill_id >= MAX_SKILL_DB)
+ if (skill_idx >= MAX_SKILL_DB) {
+ ShowWarning("skill_get_index: skill id '%d'(idx: %d) is not being handled as it exceeds MAX_SKILL_DB!\n", skill_id, skill_idx);
return 0;
+ }
- return skill_id;
+ return skill_idx;
}
static const char *skill_get_name(int skill_id)
@@ -1180,7 +1180,6 @@ static int skillnotok_mercenary(uint16 skill_id, struct mercenary_data *md)
static struct s_skill_unit_layout *skill_get_unit_layout(uint16 skill_id, uint16 skill_lv, struct block_list *src, int x, int y)
{
int pos = skill->get_unit_layout_type(skill_id,skill_lv);
- uint8 dir;
nullpo_retr(&skill->dbs->unit_layout[0], src);
if (pos < -1 || pos >= MAX_SKILL_UNIT_LAYOUT) {
@@ -1191,7 +1190,9 @@ static struct s_skill_unit_layout *skill_get_unit_layout(uint16 skill_id, uint16
if (pos != -1) // simple single-definition layout
return &skill->dbs->unit_layout[pos];
- dir = (src->x == x && src->y == y) ? 6 : map->calc_dir(src,x,y); // 6 - default aegis direction
+ enum unit_dir dir = UNIT_DIR_EAST; // default aegis direction
+ if (src->x != x || src->y != y)
+ dir = map->calc_dir(src, x, y);
if (skill_id == MG_FIREWALL)
return &skill->dbs->unit_layout [skill->firewall_unit_pos + dir];
@@ -2626,11 +2627,11 @@ static int skill_strip_equip(struct block_list *bl, unsigned short where, int ra
/*=========================================================================
* Used to knock back players, monsters, traps, etc
* 'count' is the number of squares to knock back
- * 'direction' indicates the way OPPOSITE to the knockback direction (or -1 for default behavior)
+ * 'direction' indicates the way OPPOSITE to the knockback direction (or UNIT_DIR_UNDEFINED for default behavior)
* if 'flag&0x1', position update packets must not be sent.
* if 'flag&0x2', skill blown ignores players' special_state.no_knockback
*/
-static int skill_blown(struct block_list *src, struct block_list *target, int count, int8 dir, int flag)
+static int skill_blown(struct block_list *src, struct block_list *target, int count, enum unit_dir dir, int flag)
{
int dx = 0, dy = 0;
struct status_change *tsc = status->get_sc(target);
@@ -2672,10 +2673,10 @@ static int skill_blown(struct block_list *src, struct block_list *target, int co
break;
}
- if (dir == -1) // <optimized>: do the computation here instead of outside
+ if (dir == UNIT_DIR_UNDEFINED) // <optimized>: do the computation here instead of outside
dir = map->calc_dir(target, src->x, src->y); // direction from src to target, reversed
- if (dir >= 0 && dir < 8) {
+ if (dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX) {
// take the reversed 'direction' and reverse it
dx = -dirx[dir];
dy = -diry[dir];
@@ -3297,7 +3298,7 @@ static int skill_attack(int attack_type, struct block_list *src, struct block_li
//Only knockback if it's still alive, otherwise a "ghost" is left behind. [Skotlex]
//Reflected spells do not bounce back (bl == dsrc since it only happens for direct skills)
if (dmg.blewcount > 0 && bl!=dsrc && !status->isdead(bl)) {
- int8 dir = -1; // default
+ enum unit_dir dir = UNIT_DIR_UNDEFINED; // default
switch(skill_id) {//direction
case MG_FIREWALL:
case PR_SANCTUARY:
@@ -3310,13 +3311,13 @@ static int skill_attack(int attack_type, struct block_list *src, struct block_li
// This ensures the storm randomly pushes instead of exactly a cell backwards per official mechanics.
case WZ_STORMGUST:
if(!battle_config.stormgust_knockback)
- dir = rnd()%8;
+ dir = rnd() % UNIT_DIR_MAX;
break;
case WL_CRIMSONROCK:
dir = map->calc_dir(bl,skill->area_temp[4],skill->area_temp[5]);
break;
case MC_CARTREVOLUTION:
- dir = 6; // Official servers push target to the West
+ dir = UNIT_DIR_EAST; // Official servers push target to the West
break;
default:
dir = skill->attack_dir_unknown(&attack_type, src, dsrc, bl, &skill_id, &skill_lv, &tick, &flag);
@@ -3337,8 +3338,12 @@ static int skill_attack(int attack_type, struct block_list *src, struct block_li
case SR_KNUCKLEARROW:
if( skill->blown(dsrc,bl,dmg.blewcount,dir,0) && !(flag&4) ) {
short dir_x, dir_y;
- dir_x = dirx[(dir+4)%8];
- dir_y = diry[(dir+4)%8];
+ if (Assert_chk(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX)) {
+ map->freeblock_unlock(); // unblock before assert-returning
+ return 0;
+ }
+ dir_x = dirx[unit_get_opposite_dir(dir)];
+ dir_y = diry[unit_get_opposite_dir(dir)];
if (map->getcell(bl->m, bl, bl->x + dir_x, bl->y + dir_y, CELL_CHKNOPASS) != 0)
skill->addtimerskill(src, tick + 300 * ((flag&2) ? 1 : 2), bl->id, 0, 0, skill_id, skill_lv, BF_WEAPON, flag|4);
}
@@ -3498,10 +3503,12 @@ static int skill_attack_copy_unknown(int *attack_type, struct block_list *src, s
static int skill_attack_dir_unknown(int *attack_type, struct block_list *src, struct block_list *dsrc, struct block_list *bl, uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag)
{
- return -1;
+ return UNIT_DIR_UNDEFINED;
}
-static void skill_attack_blow_unknown(int *attack_type, struct block_list *src, struct block_list *dsrc, struct block_list *bl, uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag, int *type, struct Damage *dmg, int64 *damage, int8 *dir)
+static void skill_attack_blow_unknown(int *attack_type, struct block_list *src, struct block_list *dsrc, struct block_list *bl,
+ uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag, int *type,
+ struct Damage *dmg, int64 *damage, enum unit_dir *dir)
{
nullpo_retv(bl);
nullpo_retv(dmg);
@@ -3512,7 +3519,7 @@ static void skill_attack_blow_unknown(int *attack_type, struct block_list *src,
if (!dmg->blewcount && bl->type == BL_SKILL && *damage > 0){
struct skill_unit *su = BL_UCAST(BL_SKILL, bl);
if (su->group && su->group->skill_id == HT_BLASTMINE)
- skill->blown(src, bl, 3, -1, 0);
+ skill->blown(src, bl, 3, UNIT_DIR_UNDEFINED, 0);
}
}
@@ -4190,6 +4197,11 @@ static void skill_castend_type(int type, struct block_list *src, struct block_li
skill->castend_damage_id(src, bl, skill_id, skill_lv, tick, flag);
break;
}
+
+ struct map_session_data *sd = BL_CAST(BL_PC, src);
+
+ if (sd != NULL)
+ pc->itemskill_clear(sd);
}
/*==========================================
@@ -4401,7 +4413,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
case KN_CHARGEATK: {
bool path_exists = path->search_long(NULL, src, src->m, src->x, src->y, bl->x, bl->y,CELL_CHKWALL);
unsigned int dist = distance_bl(src, bl);
- uint8 dir = map->calc_dir(bl, src->x, src->y);
+ enum unit_dir dir = map->calc_dir(bl, src->x, src->y);
// teleport to target (if not on WoE grounds)
if( !map_flag_gvg2(src->m) && !map->list[src->m].flag.battleground && unit->movepos(src, bl->x, bl->y, 0, 1) )
@@ -4413,7 +4425,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
skill->blown(src, bl, dist, dir, 0);
//HACK: since knockback officially defaults to the left, the client also turns to the left... therefore,
// make the caster look in the direction of the target
- unit->setdir(src, (dir+4)%8);
+ unit->set_dir(src, unit_get_opposite_dir(dir));
}
}
@@ -4452,12 +4464,13 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
case RG_BACKSTAP:
{
- uint8 dir = map->calc_dir(src, bl->x, bl->y), t_dir = unit->getdir(bl);
- if ((!check_distance_bl(src, bl, 0) && !map->check_dir(dir, t_dir)) || bl->type == BL_SKILL) {
+ enum unit_dir dir = map->calc_dir(src, bl->x, bl->y);
+ enum unit_dir t_dir = unit->getdir(bl);
+ if ((!check_distance_bl(src, bl, 0) && map->check_dir(dir, t_dir) == 0) || bl->type == BL_SKILL) {
status_change_end(src, SC_HIDING, INVALID_TIMER);
skill->attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag);
- dir = dir < 4 ? dir+4 : dir-4; // change direction [Celest]
- unit->setdir(bl,dir);
+ dir = unit_get_opposite_dir(dir); // change direction [Celest]
+ unit->set_dir(bl, dir);
}
else if (sd)
clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0, 0);
@@ -4484,7 +4497,6 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
{
short x, y, i = 2; // Move 2 cells for Issen(from target)
struct block_list *mbl = bl;
- short dir = 0;
skill->attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,flag);
@@ -4506,13 +4518,13 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
status->set_hp(src, 1, STATUS_HEAL_DEFAULT);
#endif // RENEWAL
}
- dir = map->calc_dir(src,bl->x,bl->y);
- if( dir > 0 && dir < 4) x = -i;
- else if( dir > 4 ) x = i;
- else x = 0;
- if( dir > 2 && dir < 6 ) y = -i;
- else if( dir == 7 || dir < 2 ) y = i;
- else y = 0;
+ enum unit_dir dir = map->calc_dir(src, bl->x, bl->y);
+ if (Assert_chk(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX)) {
+ map->freeblock_unlock(); // unblock before assert-returning
+ return 0;
+ }
+ x = i * dirx[dir];
+ y = i * diry[dir];
if ((mbl == src || (!map_flag_gvg2(src->m) && !map->list[src->m].flag.battleground))) { // only NJ_ISSEN don't have slide effect in GVG
if (!(unit->movepos(src, mbl->x+x, mbl->y+y, 1, 1))) {
// The cell is not reachable (wall, object, ...), move next to the target
@@ -4739,12 +4751,12 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
if(idb_exists(skill->bowling_db, bl->id))
break;
// Random direction
- dir = rnd()%8;
+ dir = rnd() % UNIT_DIR_MAX;
} else {
// Create an empty list of already hit targets
db_clear(skill->bowling_db);
// Direction is walkpath
- dir = (unit->getdir(src)+4)%8;
+ dir = unit_get_opposite_dir(unit->getdir(src));
}
// Add current target to the list of already hit targets
idb_put(skill->bowling_db, bl->id, bl);
@@ -4753,6 +4765,10 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
ty = bl->y;
for(i=0;i<c;i++) {
// Target coordinates (get changed even if knockback fails)
+ if (Assert_chk(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX)) {
+ map->freeblock_unlock(); // unblock before assert-returning
+ return 0;
+ }
tx -= dirx[dir];
ty -= diry[dir];
// If target cell is a wall then break
@@ -4781,18 +4797,24 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
if (bl->id==skill->area_temp[1])
break;
if (skill->attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,SD_ANIMATION))
- skill->blown(src,bl,skill->area_temp[2],-1,0);
+ skill->blown(src, bl, skill->area_temp[2], UNIT_DIR_UNDEFINED, 0);
} else {
- int x=bl->x,y=bl->y,i,dir;
- dir = map->calc_dir(bl,src->x,src->y);
+ int x = bl->x;
+ int y = bl->y;
+ int i;
+ enum unit_dir dir = map->calc_dir(bl, src->x, src->y);
skill->area_temp[1] = bl->id;
skill->area_temp[2] = skill->get_blewcount(skill_id,skill_lv);
// all the enemies between the caster and the target are hit, as well as the target
if (skill->attack(BF_WEAPON,src,src,bl,skill_id,skill_lv,tick,0))
- skill->blown(src,bl,skill->area_temp[2],-1,0);
+ skill->blown(src, bl, skill->area_temp[2], UNIT_DIR_UNDEFINED, 0);
for (i=0;i<4;i++) {
map->foreachincell(skill->area_sub,bl->m,x,y,BL_CHAR,src,skill_id,skill_lv,
tick,flag|BCT_ENEMY|1,skill->castend_damage_id);
+ if (Assert_chk(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX)) {
+ map->freeblock_unlock(); // unblock before assert-returning
+ return 0;
+ }
x += dirx[dir];
y += diry[dir];
}
@@ -5014,7 +5036,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
if(rnd()%100 < (10 + 3*skill_lv)) {
if( !sd || pc->checkskill(sd,KN_SPEARBOOMERANG) == 0 )
break; // Spear Boomerang auto cast chance only works if you have mastered Spear Boomerang.
- skill->blown(src,bl,6,-1,0);
+ skill->blown(src, bl, 6, UNIT_DIR_UNDEFINED, 0);
skill->addtimerskill(src,tick+800,bl->id,0,0,skill_id,skill_lv,BF_WEAPON,flag);
skill->castend_damage_id(src,bl,KN_SPEARBOOMERANG,1,tick,0);
}
@@ -5022,7 +5044,7 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
case RK_PHANTOMTHRUST:
{
struct map_session_data *tsd = BL_CAST(BL_PC, bl);
- unit->setdir(src,map->calc_dir(src, bl->x, bl->y));
+ unit->set_dir(src, map->calc_dir(src, bl->x, bl->y));
clif->skill_nodamage(src,bl,skill_id,skill_lv,1);
skill->blown(src,bl,distance_bl(src,bl)-1,unit->getdir(src),0);
@@ -5036,16 +5058,13 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
case KO_JYUMONJIKIRI:
case GC_DARKILLUSION:
{
- short x, y;
- short dir = map->calc_dir(bl, src->x, src->y);
-
- if ( dir < 4 ) {
- x = bl->x + 2 * (dir > 0) - 3 * (dir > 0);
- y = bl->y + 1 - (dir / 2) - (dir > 2);
- } else {
- x = bl->x + 2 * (dir > 4) - 1 * (dir > 4);
- y = bl->y + (dir / 6) - 1 + (dir > 6);
+ enum unit_dir dir = map->calc_dir(bl, src->x, src->y);
+ if (Assert_chk(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX)) {
+ map->freeblock_unlock(); // unblock before assert-returning
+ return 0;
}
+ short x = bl->x + dirx[dir];
+ short y = bl->y + diry[dir];
if ( unit->movepos(src, x, y, 1, 1) ) {
clif->slide(src, x, y);
@@ -5202,14 +5221,16 @@ static int skill_castend_damage_id(struct block_list *src, struct block_list *bl
skill->attack(BF_MAGIC,src,src,bl,skill_id,skill_lv,tick,flag|ELE_DARK);
break;
case RA_WUGSTRIKE:
- if( sd && pc_isridingwug(sd) ){
- short x[8]={0,-1,-1,-1,0,1,1,1};
- short y[8]={1,1,0,-1,-1,-1,0,1};
- uint8 dir = map->calc_dir(bl, src->x, src->y);
-
- if( unit->movepos(src, bl->x+x[dir], bl->y+y[dir], 1, 1) )
- {
- clif->slide(src, bl->x+x[dir], bl->y+y[dir]);
+ if (sd != NULL && pc_isridingwug(sd)) {
+ enum unit_dir dir = map->calc_dir(bl, src->x, src->y);
+ if (Assert_chk(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX)) {
+ map->freeblock_unlock(); // unblock before assert-returning
+ return 0;
+ }
+ short x = bl->x + dirx[dir];
+ short y = bl->y + diry[dir];
+ if (unit->movepos(src, x, y, 1, 1) != 0) {
+ clif->slide(src, x, y);
clif->fixpos(src);
skill->attack(BF_WEAPON, src, src, bl, skill_id, skill_lv, tick, flag);
}
@@ -5613,6 +5634,8 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data)
// Use a do so that you can break out of it when the skill fails.
do {
+ bool is_asura = (ud->skill_id == MO_EXTREMITYFIST);
+
if(!target || target->prev==NULL) break;
if(src->m != target->m || status->isdead(src)) break;
@@ -5650,8 +5673,9 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data)
}
if(ud->skill_id == RG_BACKSTAP) {
- uint8 dir = map->calc_dir(src,target->x,target->y),t_dir = unit->getdir(target);
- if(check_distance_bl(src, target, 0) || map->check_dir(dir,t_dir)) {
+ enum unit_dir dir = map->calc_dir(src, target->x, target->y);
+ enum unit_dir t_dir = unit->getdir(target);
+ if (check_distance_bl(src, target, 0) || map->check_dir(dir, t_dir) != 0) {
break;
}
}
@@ -5845,8 +5869,9 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data)
ud->skill_lv = ud->skilltarget = 0;
}
- if (src->id != target->id)
- unit->setdir(src, map->calc_dir(src, target->x, target->y));
+ // Asura Strike caster doesn't look to their target in the end
+ if (src->id != target->id && !is_asura)
+ unit->set_dir(src, map->calc_dir(src, target->x, target->y));
map->freeblock_unlock();
return 1;
@@ -5869,20 +5894,19 @@ static int skill_castend_id(int tid, int64 tick, int id, intptr_t data)
}
if (target && target->m == src->m) {
//Move character to target anyway.
- int dir, x, y;
- dir = map->calc_dir(src,target->x,target->y);
- if( dir > 0 && dir < 4) x = -2;
- else if( dir > 4 ) x = 2;
- else x = 0;
- if( dir > 2 && dir < 6 ) y = -2;
- else if( dir == 7 || dir < 2 ) y = 2;
- else y = 0;
- if (unit->movepos(src, src->x+x, src->y+y, 1, 1)) {
+ enum unit_dir dir = map->calc_dir(src, target->x, target->y);
+ Assert_ret(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
+ int dist = 3; // number of cells that asura caster will walk
+ int x = dist * dirx[dir];
+ int y = dist * diry[dir];
+
+ if (unit->movepos(src, src->x + x, src->y + y, 1, 1) != 0) {
//Display movement + animation.
- clif->slide(src,src->x,src->y);
+ clif->slide(src, src->x, src->y);
clif->spiritball(src);
}
- clif->skill_fail(sd, ud->skill_id, USESKILL_FAIL_LEVEL, 0, 0);
+ // "Skill Failed" message was already shown when checking that target is invalid
+ //clif->skill_fail(sd, ud->skill_id, USESKILL_FAIL_LEVEL, 0, 0);
}
}
@@ -7306,27 +7330,34 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list *
break;
case PR_STRECOVERY:
- if(status->isimmune(bl)) {
- clif->skill_nodamage(src,bl,skill_id,skill_lv,0);
+ if (status->isimmune(bl) != 0) {
+ clif->skill_nodamage(src, bl, skill_id, skill_lv, 0);
break;
}
- if (tsc && tsc->opt1) {
- status_change_end(bl, SC_FREEZE, INVALID_TIMER);
- status_change_end(bl, SC_STONE, INVALID_TIMER);
- status_change_end(bl, SC_SLEEP, INVALID_TIMER);
- status_change_end(bl, SC_STUN, INVALID_TIMER);
- status_change_end(bl, SC_WHITEIMPRISON, INVALID_TIMER);
- }
- status_change_end(bl, SC_NETHERWORLD, INVALID_TIMER);
- //Is this equation really right? It looks so... special.
- if( battle->check_undead(tstatus->race,tstatus->def_ele) ) {
- status->change_start(src, bl, SC_BLIND,
- 100*(100-(tstatus->int_/2+tstatus->vit/3+tstatus->luk/10)), 1,0,0,0,
- skill->get_time2(skill_id, skill_lv) * (100-(tstatus->int_+tstatus->vit)/2)/100,SCFLAG_NONE);
+
+ if (!battle->check_undead(tstatus->race, tstatus->def_ele)) {
+ if (tsc != NULL && tsc->opt1 != 0) {
+ status_change_end(bl, SC_FREEZE, INVALID_TIMER);
+ status_change_end(bl, SC_STONE, INVALID_TIMER);
+ status_change_end(bl, SC_SLEEP, INVALID_TIMER);
+ status_change_end(bl, SC_STUN, INVALID_TIMER);
+ status_change_end(bl, SC_WHITEIMPRISON, INVALID_TIMER);
+ }
+
+ status_change_end(bl, SC_NETHERWORLD, INVALID_TIMER);
+ } else {
+ int rate = 100 * (100 - (tstatus->int_ / 2 + tstatus->vit / 3 + tstatus->luk / 10));
+ int duration = skill->get_time2(skill_id, skill_lv);
+
+ duration *= (100 - (tstatus->int_ + tstatus->vit) / 2) / 100;
+ status->change_start(src, bl, SC_BLIND, rate, 1, 0, 0, 0, duration, SCFLAG_NONE);
}
- clif->skill_nodamage(src,bl,skill_id,skill_lv,1);
- if(dstmd)
- mob->unlocktarget(dstmd,tick);
+
+ clif->skill_nodamage(src, bl, skill_id, skill_lv, 1);
+
+ if (dstmd != NULL)
+ mob->unlocktarget(dstmd, tick);
+
break;
// Mercenary Supportive Skills
@@ -7809,7 +7840,9 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list *
case TK_HIGHJUMP:
{
- int x,y, dir = unit->getdir(src);
+ int x;
+ int y;
+ enum unit_dir dir = unit->getdir(src);
//Fails on noteleport maps, except for GvG and BG maps [Skotlex]
if( map->list[src->m].flag.noteleport
@@ -8058,11 +8091,19 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list *
case NPC_RUN:
{
- const int mask[8][2] = {{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1}};
- uint8 dir = (bl == src)?unit->getdir(src):map->calc_dir(src,bl->x,bl->y); //If cast on self, run forward, else run away.
+ enum unit_dir dir;
+ if (bl == src) //If cast on self, run forward, else run away.
+ dir = unit->getdir(src);
+ else
+ dir = map->calc_dir(src, bl->x, bl->y);
+ if (Assert_chk(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX)) {
+ map->freeblock_unlock(); // unblock before assert-returning
+ return 0;
+ }
unit->stop_attack(src);
//Run skillv tiles overriding the can-move check.
- if (unit->walktoxy(src, src->x + skill_lv * mask[dir][0], src->y + skill_lv * mask[dir][1], 2) && md)
+ if (unit->walk_toxy(src, src->x + skill_lv * -dirx[dir], src->y + skill_lv * -diry[dir], 2) == 0
+ && md != NULL)
md->state.skillstate = MSS_WALK; //Otherwise it isn't updated in the AI.
}
break;
@@ -9423,7 +9464,9 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list *
case NC_F_SIDESLIDE:
case NC_B_SIDESLIDE:
{
- uint8 dir = (skill_id == NC_F_SIDESLIDE) ? (unit->getdir(src)+4)%8 : unit->getdir(src);
+ enum unit_dir dir = unit->getdir(src);
+ if (skill_id == NC_F_SIDESLIDE)
+ dir = unit_get_opposite_dir(dir);
skill->blown(src,bl,skill->get_blewcount(skill_id,skill_lv),dir,0);
clif->slide(src,src->x,src->y);
clif->skill_nodamage(src,bl,skill_id,skill_lv,1);
@@ -9433,7 +9476,7 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list *
case NC_SELFDESTRUCTION:
if (sd) {
if (pc_ismadogear(sd))
- pc->setmadogear(sd, false);
+ pc->setmadogear(sd, false, MADO_ROBOT);
clif->skill_nodamage(src, bl, skill_id, skill_lv, 1);
skill->castend_damage_id(src, src, skill_id, skill_lv, tick, flag);
status->set_sp(src, 0, STATUS_HEAL_DEFAULT);
@@ -10403,7 +10446,7 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list *
if(sd) {
struct mob_data *summon_md;
- summon_md = mob->once_spawn_sub(src, src->m, src->x, src->y, clif->get_bl_name(src), MOBID_KO_KAGE, "", SZ_SMALL, AI_NONE);
+ summon_md = mob->once_spawn_sub(src, src->m, src->x, src->y, clif->get_bl_name(src), MOBID_KO_KAGE, "", SZ_SMALL, AI_NONE, 0);
if( summon_md ) {
summon_md->master_id = src->id;
summon_md->special_state.ai = AI_ZANZOU;
@@ -10586,7 +10629,7 @@ static int skill_castend_nodamage_id(struct block_list *src, struct block_list *
for (i = 0; i < summons[skill_lv-1].quantity; i++) {
struct mob_data *summon_md = mob->once_spawn_sub(src, src->m, src->x, src->y, clif->get_bl_name(src),
- summons[skill_lv-1].mob_id, "", SZ_SMALL, AI_ATTACK);
+ summons[skill_lv-1].mob_id, "", SZ_SMALL, AI_ATTACK, 0);
if (summon_md != NULL) {
summon_md->master_id = src->id;
if (summon_md->deletetimer != INVALID_TIMER)
@@ -10801,7 +10844,7 @@ static int skill_castend_pos(int tid, int64 tick, int id, intptr_t data)
if( sd && sd->skillitem != AL_WARP ) // Warp-Portal thru items will clear data in skill_castend_map. [Inkfish]
sd->skillitem = sd->skillitemlv = 0;
- unit->setdir(src, map->calc_dir(src, ud->skillx, ud->skilly));
+ unit->set_dir(src, map->calc_dir(src, ud->skillx, ud->skilly));
if (ud->skilltimer == INVALID_TIMER) {
if (md) md->skill_idx = -1;
@@ -11369,7 +11412,7 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill
}
// Correct info, don't change any of this! [Celest]
- md = mob->once_spawn_sub(src, src->m, x, y, clif->get_bl_name(src), class_, "", SZ_SMALL, AI_NONE);
+ md = mob->once_spawn_sub(src, src->m, x, y, clif->get_bl_name(src), class_, "", SZ_SMALL, AI_NONE, 0);
if (md) {
md->master_id = src->id;
md->special_state.ai = (skill_id == AM_SPHEREMINE) ? AI_SPHERE : AI_FLORA;
@@ -11471,7 +11514,7 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill
clif->skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0, 0);
} else {
int mob_id = skill_lv < 2 ? MOBID_BLACK_MUSHROOM + rnd()%2 : MOBID_RED_PLANT + rnd()%6;
- struct mob_data *md = mob->once_spawn_sub(src, src->m, x, y, DEFAULT_MOB_JNAME, mob_id, "", SZ_SMALL, AI_NONE);
+ struct mob_data *md = mob->once_spawn_sub(src, src->m, x, y, DEFAULT_MOB_JNAME, mob_id, "", SZ_SMALL, AI_NONE, 0);
int i;
if (md == NULL)
break;
@@ -11580,17 +11623,16 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill
case WL_EARTHSTRAIN:
{
- int i, wave = skill_lv + 4, dir = map->calc_dir(src,x,y);
+ int i;
+ int wave = skill_lv + 4;
+ enum unit_dir dir = map->calc_dir(src, x, y);
+ Assert_ret(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
int sx = x = src->x, sy = y = src->y; // Store first caster's location to avoid glitch on unit setting
- for( i = 1; i <= wave; i++ )
- {
- switch( dir ){
- case 0: case 1: case 7: sy = y + i; break;
- case 3: case 4: case 5: sy = y - i; break;
- case 2: sx = x - i; break;
- case 6: sx = x + i; break;
- }
+ for (i = 1; i <= wave; i++) {
+ sy = y + i * diry[dir];
+ if (dir == UNIT_DIR_WEST || dir == UNIT_DIR_EAST)
+ sx = x + i * dirx[dir];
skill->addtimerskill(src,timer->gettick() + (140 * i),0,sx,sy,skill_id,skill_lv,dir,flag&2);
}
}
@@ -11617,7 +11659,7 @@ static int skill_castend_pos2(struct block_list *src, int x, int y, uint16 skill
case NC_SILVERSNIPER:
{
- struct mob_data *md = mob->once_spawn_sub(src, src->m, x, y, clif->get_bl_name(src), MOBID_SILVERSNIPER, "", SZ_SMALL, AI_NONE);
+ struct mob_data *md = mob->once_spawn_sub(src, src->m, x, y, clif->get_bl_name(src), MOBID_SILVERSNIPER, "", SZ_SMALL, AI_NONE, 0);
if (md) {
md->master_id = src->id;
md->special_state.ai = AI_FLORA;
@@ -13809,12 +13851,14 @@ static int skill_check_condition_char_sub(struct block_list *bl, va_list ap)
} else {
switch(skill_id) {
- case PR_BENEDICTIO: {
- uint8 dir = map->calc_dir(&sd->bl,tsd->bl.x,tsd->bl.y);
- dir = (unit->getdir(&sd->bl) + dir)%8; //This adjusts dir to account for the direction the sd is facing.
- if ((tsd->job & MAPID_BASEMASK) == MAPID_ACOLYTE && (dir == 2 || dir == 6) //Must be standing to the left/right of Priest.
- && sd->status.sp >= 10)
+ case PR_BENEDICTIO:
+ {
+ enum unit_dir dir = map->calc_dir(&sd->bl, tsd->bl.x, tsd->bl.y);
+ dir = (unit->getdir(&sd->bl) + dir) % UNIT_DIR_MAX; //This adjusts dir to account for the direction the sd is facing.
+ if ((tsd->job & MAPID_BASEMASK) == MAPID_ACOLYTE && (dir == UNIT_DIR_WEST || dir == UNIT_DIR_EAST) //Must be standing to the left/right of Priest.
+ && sd->status.sp >= 10) {
p_sd[(*c)++]=tsd->bl.id;
+ }
return 1;
}
case AB_ADORAMUS:
@@ -13968,6 +14012,22 @@ static bool skill_is_combo(int skill_id)
return false;
}
+/**
+ * Checks if a skill is casted by an item (itemskill() script command).
+ *
+ * @param sd The charcater's session data.
+ * @param skill_id The skill's ID.
+ * @param skill_lv The skill's level.
+ * @return true if skill is casted by an item, otherwise false.
+ */
+static bool skill_is_item_skill(struct map_session_data *sd, int skill_id, int skill_lv)
+{
+ nullpo_retr(false, sd);
+
+ return (sd->skillitem == skill_id && sd->skillitemlv == skill_lv
+ && sd->itemskill_id == skill_id && sd->itemskill_lv == skill_lv);
+}
+
static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv)
{
struct status_data *st;
@@ -13976,9 +14036,17 @@ static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 s
nullpo_ret(sd);
+ if (skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL)
+ return 0;
+
if (sd->chat_id != 0)
return 0;
+ if ((sd->state.itemskill_conditions_checked == 1 || sd->state.itemskill_no_conditions == 1)
+ && skill->is_item_skill(sd, skill_id, skill_lv)) {
+ return 1;
+ }
+
if (pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id) {
//GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex]
sd->state.arrow_atk = skill->get_ammotype(skill_id)?1:0; //Need to do arrow state check.
@@ -14020,24 +14088,21 @@ static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 s
if( (i = sd->itemindex) == -1 ||
sd->status.inventory[i].nameid != sd->itemid ||
sd->inventory_data[i] == NULL ||
- !sd->inventory_data[i]->flag.delay_consume ||
sd->status.inventory[i].amount < 1
) {
//Something went wrong, item exploit?
sd->itemid = sd->itemindex = -1;
return 0;
}
+
//Consume
sd->itemid = sd->itemindex = -1;
- if( skill_id == WZ_EARTHSPIKE && sc && sc->data[SC_EARTHSCROLL] && rnd()%100 > sc->data[SC_EARTHSCROLL]->val2 ) // [marquis007]
- ; //Do not consume item.
- else if( sd->status.inventory[i].expire_time == 0 ) // Rental usable items are not consumed until expiration
+ if (sd->status.inventory[i].expire_time == 0 && sd->inventory_data[i]->flag.delay_consume == 1) // Rental usable items are not consumed until expiration
pc->delitem(sd, i, 1, 0, DELITEM_NORMAL, LOG_TYPE_CONSUME);
}
- return 1;
}
- if( pc_is90overweight(sd) ) {
+ if (pc_is90overweight(sd) && sd->skillitem != skill_id) { /// Skill casting items ignore the overweight restriction. [Kenpachi]
clif->skill_fail(sd, skill_id, USESKILL_FAIL_WEIGHTOVER, 0, 0);
return 0;
}
@@ -14161,9 +14226,6 @@ static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 s
}
}
- if( skill_lv < 1 || skill_lv > MAX_SKILL_LEVEL )
- return 0;
-
require = skill->get_requirement(sd,skill_id,skill_lv);
//Can only update state when weapon/arrow info is checked.
@@ -14911,7 +14973,7 @@ static int skill_check_condition_castbegin(struct map_session_data *sd, uint16 s
return 0;
}
- if( require.sp > 0 && st->sp < (unsigned int)require.sp) {
+ if (require.sp > 0 && st->sp < (unsigned int)require.sp && sd->skillitem != skill_id) { /// Skill casting items and Hocus-Pocus skills don't consume SP. [Kenpachi]
clif->skill_fail(sd, skill_id, USESKILL_FAIL_SP_INSUFFICIENT, 0, 0);
return 0;
}
@@ -14969,6 +15031,11 @@ static int skill_check_condition_castend(struct map_session_data *sd, uint16 ski
if (sd->chat_id != 0)
return 0;
+ if ((sd->state.itemskill_conditions_checked == 1 || sd->state.itemskill_no_conditions == 1)
+ && skill->is_item_skill(sd, skill_id, skill_lv)) {
+ return 1;
+ }
+
if( pc_has_permission(sd, PC_PERM_SKILL_UNCONDITIONAL) && sd->skillitem != skill_id ) {
//GMs don't override the skillItem check, otherwise they can use items without them being consumed! [Skotlex]
sd->state.arrow_atk = skill->get_ammotype(skill_id)?1:0; //Need to do arrow state check.
@@ -14996,14 +15063,8 @@ static int skill_check_condition_castend(struct map_session_data *sd, uint16 ski
return 0;
break;
}
- /* temporarily disabled, awaiting for kenpachi to detail this so we can make it work properly */
-#if 0
- if( sd->state.abra_flag ) // Casting finished (Hocus-Pocus)
- return 1;
-#endif
- if( sd->skillitem == skill_id )
- return 1;
- if( pc_is90overweight(sd) ) {
+
+ if (pc_is90overweight(sd) && sd->skillitem != skill_id) { /// Skill casting items ignore the overweight restriction. [Kenpachi]
clif->skill_fail(sd, skill_id, USESKILL_FAIL_WEIGHTOVER, 0, 0);
return 0;
}
@@ -15176,6 +15237,9 @@ static int skill_consume_requirement(struct map_session_data *sd, uint16 skill_i
nullpo_ret(sd);
+ if (sd->state.itemskill_no_conditions == 1 && skill->is_item_skill(sd, skill_id, skill_lv))
+ return 1;
+
req = skill->get_requirement(sd,skill_id,skill_lv);
if (type&1) {
@@ -15184,9 +15248,15 @@ static int skill_consume_requirement(struct map_session_data *sd, uint16 skill_i
case MC_IDENTIFY:
req.sp = 0;
break;
+ case WZ_EARTHSPIKE:
+ if (sd->sc.count > 0 && sd->sc.data[SC_EARTHSCROLL] != NULL) // If Earth Spike Scroll is used while SC_EARTHSCROLL is active, 10 SP are consumed. [Kenpachi]
+ req.sp = 10;
+
+ break;
default:
- if( sd->state.autocast )
+ if (sd->state.autocast == 1 || sd->skillitem == skill_id) /// Skill casting items and Hocus-Pocus skills don't consume SP. [Kenpachi]
req.sp = 0;
+
break;
}
@@ -15264,12 +15334,6 @@ static struct skill_condition skill_get_requirement(struct map_session_data *sd,
if( !sd )
return req;
-#if 0 /* temporarily disabled, awaiting for kenpachi to detail this so we can make it work properly */
- if( sd->state.abra_flag )
-#else // not 0
- if( sd->skillitem == skill_id )
-#endif // 0
- return req; // Hocus-Pocus don't have requirements.
sc = &sd->sc;
if( !sc->count )
@@ -15930,11 +15994,11 @@ struct square {
int val2[5];
};
-static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int16 y)
+static void skill_brandishspear_first(struct square *tc, enum unit_dir dir, int16 x, int16 y)
{
nullpo_retv(tc);
- if(dir == 0){
+ if (dir == UNIT_DIR_NORTH) {
tc->val1[0]=x-2;
tc->val1[1]=x-1;
tc->val1[2]=x;
@@ -15945,7 +16009,7 @@ static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int
tc->val2[2]=
tc->val2[3]=
tc->val2[4]=y-1;
- } else if(dir==2){
+ } else if (dir == UNIT_DIR_WEST) {
tc->val1[0]=
tc->val1[1]=
tc->val1[2]=
@@ -15956,7 +16020,7 @@ static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int
tc->val2[2]=y;
tc->val2[3]=y-1;
tc->val2[4]=y-2;
- } else if(dir==4){
+ } else if (dir == UNIT_DIR_SOUTH) {
tc->val1[0]=x-2;
tc->val1[1]=x-1;
tc->val1[2]=x;
@@ -15967,7 +16031,7 @@ static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int
tc->val2[2]=
tc->val2[3]=
tc->val2[4]=y+1;
- } else if(dir==6){
+ } else if (dir == UNIT_DIR_EAST) {
tc->val1[0]=
tc->val1[1]=
tc->val1[2]=
@@ -15978,7 +16042,7 @@ static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int
tc->val2[2]=y;
tc->val2[3]=y-1;
tc->val2[4]=y-2;
- } else if(dir==1){
+ } else if (dir == UNIT_DIR_NORTHWEST) {
tc->val1[0]=x-1;
tc->val1[1]=x;
tc->val1[2]=x+1;
@@ -15989,7 +16053,7 @@ static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int
tc->val2[2]=y-1;
tc->val2[3]=y;
tc->val2[4]=y+1;
- } else if(dir==3){
+ } else if (dir == UNIT_DIR_SOUTHWEST) {
tc->val1[0]=x+3;
tc->val1[1]=x+2;
tc->val1[2]=x+1;
@@ -16000,7 +16064,7 @@ static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int
tc->val2[2]=y+1;
tc->val2[3]=y+2;
tc->val2[4]=y+3;
- } else if(dir==5){
+ } else if (dir == UNIT_DIR_SOUTHEAST) {
tc->val1[0]=x+1;
tc->val1[1]=x;
tc->val1[2]=x-1;
@@ -16011,7 +16075,7 @@ static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int
tc->val2[2]=y+1;
tc->val2[3]=y;
tc->val2[4]=y-1;
- } else if(dir==7){
+ } else if (dir == UNIT_DIR_NORTHEAST) {
tc->val1[0]=x-3;
tc->val1[1]=x-2;
tc->val1[2]=x-1;
@@ -16026,36 +16090,27 @@ static void skill_brandishspear_first(struct square *tc, uint8 dir, int16 x, int
}
-static void skill_brandishspear_dir(struct square *tc, uint8 dir, int are)
+static void skill_brandishspear_dir(struct square *tc, enum unit_dir dir, int are)
{
- int c;
nullpo_retv(tc);
+ Assert_retv(dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
- for( c = 0; c < 5; c++ ) {
- switch( dir ) {
- case 0: tc->val2[c]+=are; break;
- case 1: tc->val1[c]-=are; tc->val2[c]+=are; break;
- case 2: tc->val1[c]-=are; break;
- case 3: tc->val1[c]-=are; tc->val2[c]-=are; break;
- case 4: tc->val2[c]-=are; break;
- case 5: tc->val1[c]+=are; tc->val2[c]-=are; break;
- case 6: tc->val1[c]+=are; break;
- case 7: tc->val1[c]+=are; tc->val2[c]+=are; break;
- }
+ for (int c = 0; c < 5; c++) {
+ tc->val1[c] += dirx[dir] * are;
+ tc->val2[c] += diry[dir] * are;
}
}
static void skill_brandishspear(struct block_list *src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int64 tick, int flag)
{
int c,n=4;
- uint8 dir;
struct square tc;
int x, y;
nullpo_retv(bl);
x = bl->x;
y = bl->y;
- dir = map->calc_dir(src, x, y);
+ enum unit_dir dir = map->calc_dir(src, x, y);
skill->brandishspear_first(&tc,dir,x,y);
skill->brandishspear_dir(&tc,dir,4);
skill->area_temp[1] = bl->id;
@@ -18779,7 +18834,7 @@ static int skill_magicdecoy(struct map_session_data *sd, int nameid)
break;
}
- md = mob->once_spawn_sub(&sd->bl, sd->bl.m, x, y, sd->status.name, class_, "", SZ_SMALL, AI_NONE);
+ md = mob->once_spawn_sub(&sd->bl, sd->bl.m, x, y, sd->status.name, class_, "", SZ_SMALL, AI_NONE, 0);
if( md ) {
md->master_id = sd->bl.id;
md->special_state.ai = AI_FLORA;
@@ -21581,6 +21636,7 @@ void skill_defaults(void)
skill->cast_fix_sc = skill_castfix_sc;
skill->vf_cast_fix = skill_vfcastfix;
skill->delay_fix = skill_delay_fix;
+ skill->is_item_skill = skill_is_item_skill;
skill->check_condition_castbegin = skill_check_condition_castbegin;
skill->check_condition_castend = skill_check_condition_castend;
skill->consume_requirement = skill_consume_requirement;
diff --git a/src/map/skill.h b/src/map/skill.h
index dbda6470f..c65547181 100644
--- a/src/map/skill.h
+++ b/src/map/skill.h
@@ -23,6 +23,7 @@
#include "map/map.h" // struct block_list
#include "map/status.h" // enum sc_type
+#include "map/unitdefines.h" // enum unit_dir
#include "common/hercules.h"
#include "common/db.h"
#include "common/mmo.h" // MAX_SKILL_DB, struct square
@@ -59,6 +60,12 @@ struct status_change_entry;
#define MAX_SKILLUNITGROUPTICKSET 25
#define MAX_SKILL_NAME_LENGTH 32
+// Custom Skill Ranges is used in skill_get_index, to allocate indexes based on ID and gaps between 2 SkillID
+#ifndef CUSTOM_SKILL_RANGES
+ #define CUSTOM_SKILL_RANGES
+#endif // CUSTOM_SKILL_RANGES
+
+
// (Epoque:) To-do: replace this macro with some sort of skill tree check (rather than hard-coded skill names)
#define skill_ischangesex(id) ( \
((id) >= BD_ADAPTATION && (id) <= DC_SERVICEFORYOU) || ((id) >= CG_ARROWVULCAN && (id) <= CG_MARIONETTE) || \
@@ -1996,7 +2003,7 @@ struct skill_interface {
int (*addtimerskill) (struct block_list *src, int64 tick, int target, int x, int y, uint16 skill_id, uint16 skill_lv, int type, int flag);
int (*additional_effect) (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, int dmg_lv, int64 tick);
int (*counter_additional_effect) (struct block_list* src, struct block_list *bl, uint16 skill_id, uint16 skill_lv, int attack_type, int64 tick);
- int (*blown) (struct block_list* src, struct block_list* target, int count, int8 dir, int flag);
+ int (*blown) (struct block_list* src, struct block_list* target, int count, enum unit_dir dir, int flag);
int (*break_equip) (struct block_list *bl, unsigned short where, int rate, int flag);
int (*strip_equip) (struct block_list *bl, unsigned short where, int rate, int lv, int time);
struct skill_unit_group* (*id2group) (int group_id);
@@ -2013,6 +2020,7 @@ struct skill_interface {
int (*cast_fix_sc) ( struct block_list *bl, int time);
int (*vf_cast_fix) ( struct block_list *bl, double time, uint16 skill_id, uint16 skill_lv);
int (*delay_fix) ( struct block_list *bl, uint16 skill_id, uint16 skill_lv);
+ bool (*is_item_skill) (struct map_session_data *sd, int skill_id, int skill_lv);
int (*check_condition_castbegin) (struct map_session_data *sd, uint16 skill_id, uint16 skill_lv);
int (*check_condition_castend) (struct map_session_data *sd, uint16 skill_id, uint16 skill_lv);
int (*consume_requirement) (struct map_session_data *sd, uint16 skill_id, uint16 skill_lv, short type);
@@ -2078,8 +2086,8 @@ struct skill_interface {
bool (*dance_switch) (struct skill_unit* su, int flag);
int (*check_condition_char_sub) (struct block_list *bl, va_list ap);
int (*check_condition_mob_master_sub) (struct block_list *bl, va_list ap);
- void (*brandishspear_first) (struct square *tc, uint8 dir, int16 x, int16 y);
- void (*brandishspear_dir) (struct square* tc, uint8 dir, int are);
+ void (*brandishspear_first) (struct square *tc, enum unit_dir dir, int16 x, int16 y);
+ void (*brandishspear_dir) (struct square* tc, enum unit_dir dir, int are);
int (*get_fixed_cast) (int skill_id, int skill_lv);
int (*sit_count) (struct block_list *bl, va_list ap);
int (*sit_in) (struct block_list *bl, va_list ap);
@@ -2156,7 +2164,7 @@ struct skill_interface {
void (*attack_display_unknown) (int *attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag, int *type, struct Damage *dmg, int64 *damage);
int (*attack_copy_unknown) (int *attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag);
int (*attack_dir_unknown) (int *attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag);
- void (*attack_blow_unknown) (int *attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag, int *type, struct Damage *dmg, int64 *damage, int8 *dir);
+ void (*attack_blow_unknown) (int *attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag, int *type, struct Damage *dmg, int64 *damage, enum unit_dir *dir);
void (*attack_post_unknown) (int *attack_type, struct block_list* src, struct block_list *dsrc, struct block_list *bl, uint16 *skill_id, uint16 *skill_lv, int64 *tick, int *flag);
bool (*timerskill_dead_unknown) (struct block_list *src, struct unit_data *ud, struct skill_timerskill *skl);
void (*timerskill_target_unknown) (int tid, int64 tick, struct block_list *src, struct block_list *target, struct unit_data *ud, struct skill_timerskill *skl);
diff --git a/src/map/status.c b/src/map/status.c
index 3b99b99b8..4d798b606 100644
--- a/src/map/status.c
+++ b/src/map/status.c
@@ -801,21 +801,21 @@ static void initChangeTables(void)
set_sc_with_vfx( GN_ILLUSIONDOPING , SC_ILLUSIONDOPING , SCB_HIT );
// Storing the target job rather than simply SC_SOULLINK simplifies code later on.
- status->dbs->Skill2SCTable[SL_ALCHEMIST] = (sc_type)MAPID_ALCHEMIST,
- status->dbs->Skill2SCTable[SL_MONK] = (sc_type)MAPID_MONK,
- status->dbs->Skill2SCTable[SL_STAR] = (sc_type)MAPID_STAR_GLADIATOR,
- status->dbs->Skill2SCTable[SL_SAGE] = (sc_type)MAPID_SAGE,
- status->dbs->Skill2SCTable[SL_CRUSADER] = (sc_type)MAPID_CRUSADER,
- status->dbs->Skill2SCTable[SL_SUPERNOVICE] = (sc_type)MAPID_SUPER_NOVICE,
- status->dbs->Skill2SCTable[SL_KNIGHT] = (sc_type)MAPID_KNIGHT,
- status->dbs->Skill2SCTable[SL_WIZARD] = (sc_type)MAPID_WIZARD,
- status->dbs->Skill2SCTable[SL_PRIEST] = (sc_type)MAPID_PRIEST,
- status->dbs->Skill2SCTable[SL_BARDDANCER] = (sc_type)MAPID_BARDDANCER,
- status->dbs->Skill2SCTable[SL_ROGUE] = (sc_type)MAPID_ROGUE,
- status->dbs->Skill2SCTable[SL_ASSASIN] = (sc_type)MAPID_ASSASSIN,
- status->dbs->Skill2SCTable[SL_BLACKSMITH] = (sc_type)MAPID_BLACKSMITH,
- status->dbs->Skill2SCTable[SL_HUNTER] = (sc_type)MAPID_HUNTER,
- status->dbs->Skill2SCTable[SL_SOULLINKER] = (sc_type)MAPID_SOUL_LINKER,
+ status->dbs->Skill2SCTable[skill->get_index(SL_ALCHEMIST)] = (sc_type)MAPID_ALCHEMIST,
+ status->dbs->Skill2SCTable[skill->get_index(SL_MONK)] = (sc_type)MAPID_MONK,
+ status->dbs->Skill2SCTable[skill->get_index(SL_STAR)] = (sc_type)MAPID_STAR_GLADIATOR,
+ status->dbs->Skill2SCTable[skill->get_index(SL_SAGE)] = (sc_type)MAPID_SAGE,
+ status->dbs->Skill2SCTable[skill->get_index(SL_CRUSADER)] = (sc_type)MAPID_CRUSADER,
+ status->dbs->Skill2SCTable[skill->get_index(SL_SUPERNOVICE)] = (sc_type)MAPID_SUPER_NOVICE,
+ status->dbs->Skill2SCTable[skill->get_index(SL_KNIGHT)] = (sc_type)MAPID_KNIGHT,
+ status->dbs->Skill2SCTable[skill->get_index(SL_WIZARD)] = (sc_type)MAPID_WIZARD,
+ status->dbs->Skill2SCTable[skill->get_index(SL_PRIEST)] = (sc_type)MAPID_PRIEST,
+ status->dbs->Skill2SCTable[skill->get_index(SL_BARDDANCER)] = (sc_type)MAPID_BARDDANCER,
+ status->dbs->Skill2SCTable[skill->get_index(SL_ROGUE)] = (sc_type)MAPID_ROGUE,
+ status->dbs->Skill2SCTable[skill->get_index(SL_ASSASIN)] = (sc_type)MAPID_ASSASSIN,
+ status->dbs->Skill2SCTable[skill->get_index(SL_BLACKSMITH)] = (sc_type)MAPID_BLACKSMITH,
+ status->dbs->Skill2SCTable[skill->get_index(SL_HUNTER)] = (sc_type)MAPID_HUNTER,
+ status->dbs->Skill2SCTable[skill->get_index(SL_SOULLINKER)] = (sc_type)MAPID_SOUL_LINKER,
// Other SC which are not necessarily associated to skills.
status->dbs->ChangeFlagTable[SC_ATTHASTE_POTION1] |= SCB_ASPD;
@@ -9798,6 +9798,9 @@ static int status_get_val_flag(enum sc_type type)
case SC_DAILYSENDMAILCNT:
val_flag |= 1 | 2;
break;
+ case SC_MADOGEAR:
+ val_flag |= 1;
+ break;
}
return val_flag;
}
diff --git a/src/map/status.h b/src/map/status.h
index ecf27d411..ada18bc0a 100644
--- a/src/map/status.h
+++ b/src/map/status.h
@@ -853,6 +853,7 @@ typedef enum sc_type {
SC_RESIST_PROPERTY_FIRE,
SC_RESIST_PROPERTY_WIND,
SC_CLIENT_ONLY_EQUIP_ARROW,
+ SC_MADOGEAR,
#ifndef SC_MAX
SC_MAX, //Automatically updated max, used in for's to check we are within bounds.
#endif
diff --git a/src/map/unit.c b/src/map/unit.c
index 482440978..99682e2d3 100644
--- a/src/map/unit.c
+++ b/src/map/unit.c
@@ -57,6 +57,7 @@
#include "common/showmsg.h"
#include "common/socket.h"
#include "common/timer.h"
+#include "common/utils.h"
#include <stdio.h>
#include <stdlib.h>
@@ -71,20 +72,65 @@ struct unit_interface *unit;
/**
* Returns the unit_data for the given block_list. If the object is using
* shared unit_data (i.e. in case of BL_NPC), it returns the shared data.
- * @param bl block_list to process
+ *
+ * __Warning:__ if bl->type is not known or NULL,
+ * an assertion will be triggered and NULL returned.
+ * @param bl block_list to process, it is expected to be not NULL.
* @return a pointer to the given object's unit_data
**/
static struct unit_data *unit_bl2ud(struct block_list *bl)
{
- if (bl == NULL) return NULL;
- if (bl->type == BL_PC) return &BL_UCAST(BL_PC, bl)->ud;
- if (bl->type == BL_MOB) return &BL_UCAST(BL_MOB, bl)->ud;
- if (bl->type == BL_PET) return &BL_UCAST(BL_PET, bl)->ud;
- if (bl->type == BL_NPC) return BL_UCAST(BL_NPC, bl)->ud;
- if (bl->type == BL_HOM) return &BL_UCAST(BL_HOM, bl)->ud;
- if (bl->type == BL_MER) return &BL_UCAST(BL_MER, bl)->ud;
- if (bl->type == BL_ELEM) return &BL_UCAST(BL_ELEM, bl)->ud;
- return NULL;
+ Assert_retr(NULL, bl != NULL);
+ switch (bl->type) {
+ case BL_PC:
+ return &BL_UCAST(BL_PC, bl)->ud;
+ case BL_MOB:
+ return &BL_UCAST(BL_MOB, bl)->ud;
+ case BL_PET:
+ return &BL_UCAST(BL_PET, bl)->ud;
+ case BL_NPC:
+ return BL_UCAST(BL_NPC, bl)->ud;
+ case BL_HOM:
+ return &BL_UCAST(BL_HOM, bl)->ud;
+ case BL_MER:
+ return &BL_UCAST(BL_MER, bl)->ud;
+ case BL_ELEM:
+ return &BL_UCAST(BL_ELEM, bl)->ud;
+ default:
+ Assert_retr(NULL, false);
+ }
+}
+
+/**
+ * Returns the const unit_data for the given const block_list. If the object is using
+ * shared unit_data (i.e. in case of BL_NPC), it returns the shared data.
+ *
+ * __Warning:__ if bl->type is not known or NULL,
+ * an assertion will be triggered and NULL returned.
+ * @param bl block_list to process, it is expected to be not NULL.
+ * @return a pointer to the given object's unit_data
+ **/
+static const struct unit_data *unit_cbl2ud(const struct block_list *bl)
+{
+ Assert_retr(NULL, bl != NULL);
+ switch (bl->type) {
+ case BL_PC:
+ return &BL_UCCAST(BL_PC, bl)->ud;
+ case BL_MOB:
+ return &BL_UCCAST(BL_MOB, bl)->ud;
+ case BL_PET:
+ return &BL_UCCAST(BL_PET, bl)->ud;
+ case BL_NPC:
+ return BL_UCCAST(BL_NPC, bl)->ud;
+ case BL_HOM:
+ return &BL_UCCAST(BL_HOM, bl)->ud;
+ case BL_MER:
+ return &BL_UCCAST(BL_MER, bl)->ud;
+ case BL_ELEM:
+ return &BL_UCCAST(BL_ELEM, bl)->ud;
+ default:
+ Assert_retr(NULL, false);
+ }
}
/**
@@ -105,42 +151,46 @@ static struct unit_data *unit_bl2ud2(struct block_list *bl)
return unit->bl2ud(bl);
}
-static int unit_walktoxy_sub(struct block_list *bl)
+/**
+ * TODO: understand purpose of this function
+ * @param bl block_list to process
+ * @return 0: success, 1: fail, 2: nullpointer
+ */
+static int unit_walk_toxy_sub(struct block_list *bl)
{
- int i;
- struct walkpath_data wpd;
- struct unit_data *ud = NULL;
-
- nullpo_retr(1, bl);
- ud = unit->bl2ud(bl);
- if(ud == NULL) return 0;
+ nullpo_retr(2, bl);
+ struct unit_data *ud = unit->bl2ud(bl);
+ if (ud == NULL)
+ return 2;
- memset(&wpd, 0, sizeof(wpd));
+ struct walkpath_data wpd = {0};
- if( !path->search(&wpd,bl,bl->m,bl->x,bl->y,ud->to_x,ud->to_y,ud->state.walk_easy,CELL_CHKNOPASS) )
- return 0;
+ if (!path->search(&wpd, bl, bl->m, bl->x, bl->y, ud->to_x, ud->to_y, ud->state.walk_easy, CELL_CHKNOPASS))
+ return 1;
#ifdef OFFICIAL_WALKPATH
- if( !path->search_long(NULL, bl, bl->m, bl->x, bl->y, ud->to_x, ud->to_y, CELL_CHKNOPASS) // Check if there is an obstacle between
- && wpd.path_len > 14 // Official number of walkable cells is 14 if and only if there is an obstacle between. [malufett]
- && (bl->type != BL_NPC) ) // If type is a NPC, please disregard.
- return 0;
+ if (bl->type != BL_NPC // If type is an NPC, disregard.
+ && !path->search_long(NULL, bl, bl->m, bl->x, bl->y, ud->to_x, ud->to_y, CELL_CHKNOPASS) // Check if there is an obstacle between
+ && wpd.path_len > 14) { // Official number of walkable cells is 14 if and only if there is an obstacle between. [malufett]
+ return 1;
+ }
#endif
- memcpy(&ud->walkpath,&wpd,sizeof(wpd));
+ ud->walkpath = wpd;
- if (ud->target_to && ud->chaserange>1) {
- //Generally speaking, the walk path is already to an adjacent tile
- //so we only need to shorten the path if the range is greater than 1.
+ if (ud->target_to != 0 && ud->chaserange > 1) {
+ // Generally speaking, the walk path is already to an adjacent tile
+ // so we only need to shorten the path if the range is greater than 1.
- //Trim the last part of the path to account for range,
- //but always move at least one cell when requested to move.
- for (i = (ud->chaserange*10)-10; i > 0 && ud->walkpath.path_len>1;) {
- uint8 dir;
+ // Trim the last part of the path to account for range,
+ // but always move at least one cell when requested to move.
+ for (int i = ud->chaserange * 10 - 10; i > 0 && ud->walkpath.path_len > 1;) {
+ enum unit_dir dir;
ud->walkpath.path_len--;
dir = ud->walkpath.path[ud->walkpath.path_len];
- if (dir&1)
- i -= MOVE_COST*20; //When chasing, units will target a diamond-shaped area in range [Playtester]
+ Assert_retr(1, dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
+ if (unit_is_diagonal_dir(dir))
+ i -= MOVE_COST * 20; // When chasing, units will target a diamond-shaped area in range [Playtester]
else
i -= MOVE_COST;
ud->to_x -= dirx[dir];
@@ -148,7 +198,7 @@ static int unit_walktoxy_sub(struct block_list *bl)
}
}
- ud->state.change_walk_target=0;
+ ud->state.change_walk_target = 0;
if (bl->type == BL_PC) {
struct map_session_data *sd = BL_UCAST(BL_PC, bl);
@@ -157,15 +207,17 @@ static int unit_walktoxy_sub(struct block_list *bl)
}
clif->move(ud);
- if(ud->walkpath.path_pos>=ud->walkpath.path_len)
- i = -1;
- else if(ud->walkpath.path[ud->walkpath.path_pos]&1)
- i = status->get_speed(bl)*MOVE_DIAGONAL_COST/MOVE_COST;
+ int timer_delay;
+ if (ud->walkpath.path_pos >= ud->walkpath.path_len)
+ timer_delay = -1;
+ else if ((ud->walkpath.path[ud->walkpath.path_pos] & 1) != 0)
+ timer_delay = status->get_speed(bl) * MOVE_DIAGONAL_COST / MOVE_COST;
else
- i = status->get_speed(bl);
- if( i > 0)
- ud->walktimer = timer->add(timer->gettick()+i,unit->walktoxy_timer,bl->id,i);
- return 1;
+ timer_delay = status->get_speed(bl);
+
+ if (timer_delay > 0)
+ ud->walktimer = timer->add(timer->gettick() + timer_delay, unit->walk_toxy_timer, bl->id, 0); //TODO: check if unit->walk_toxy_timer uses any intptr data
+ return 0;
}
/**
@@ -173,289 +225,278 @@ static int unit_walktoxy_sub(struct block_list *bl)
* @param tid: Timer ID
* @param tick: Unused
* @param id: ID of bl to do the action
- * @param data: Not used
- * @return 1: Success 0: Fail (No valid bl)
+ * @param data: Unused
+ * @return 0: success, 1: fail, 2: nullpointer
*/
-static int unit_step_timer(int tid, int64 tick, int id, intptr_t data)
+static int unit_steptimer(int tid, int64 tick, int id, intptr_t data)
{
- struct block_list *bl;
- struct unit_data *ud;
- int target_id;
-
- bl = map->id2bl(id);
-
- if (!bl || bl->prev == NULL)
- return 0;
-
- ud = unit->bl2ud(bl);
-
- if(!ud)
- return 0;
+ struct block_list *bl = map->id2bl(id);
+ if (bl == NULL || bl->prev == NULL)
+ return 2;
+ struct unit_data *ud = unit->bl2ud(bl);
+ if (ud == NULL)
+ return 2;
- if(ud->steptimer != tid) {
- ShowError("unit_step_timer mismatch %d != %d\n",ud->steptimer,tid);
- return 0;
+ if (ud->steptimer != tid) {
+ ShowError("unit_steptimer mismatch %d != %d\n", ud->steptimer, tid);
+ return 1;
}
ud->steptimer = INVALID_TIMER;
- if(!ud->stepaction)
- return 0;
+ if (!ud->stepaction)
+ return 1;
- //Set to false here because if an error occurs, it should not be executed again
+ // Set to false here because if an error occurs, it should not be executed again
ud->stepaction = false;
- if(!ud->target_to)
- return 0;
+ if (ud->target_to == 0)
+ return 1;
- //Flush target_to as it might contain map coordinates which should not be used by other functions
- target_id = ud->target_to;
+ // Flush target_to as it might contain map coordinates which should not be used by other functions
+ int target_id = ud->target_to;
ud->target_to = 0;
- //If stepaction is set then we remembered a client request that should be executed on the next step
- //Execute request now if target is in attack range
- if(ud->stepskill_id && skill->get_inf(ud->stepskill_id) & INF_GROUND_SKILL) {
- //Execute ground skill
+ // If stepaction is set then we remembered a client request that should be executed on the next step
+ // Execute request now if target is in attack range
+ if (ud->stepskill_id != 0 && (skill->get_inf(ud->stepskill_id) & INF_GROUND_SKILL) != 0) {
+ // Execute ground skill
struct map_data *md = &map->list[bl->m];
- unit->skilluse_pos(bl, target_id%md->xs, target_id/md->xs, ud->stepskill_id, ud->stepskill_lv);
+ unit->skilluse_pos(bl, target_id % md->xs, target_id / md->xs, ud->stepskill_id, ud->stepskill_lv);
} else {
- //If a player has target_id set and target is in range, attempt attack
+ // If a player has target_id set and target is in range, attempt attack
struct block_list *tbl = map->id2bl(target_id);
- if (!tbl || !status->check_visibility(bl, tbl)) {
- return 0;
- }
- if(ud->stepskill_id == 0) {
- //Execute normal attack
- unit->attack(bl, tbl->id, (ud->state.attack_continue) + 2);
- } else {
- //Execute non-ground skill
- unit->skilluse_id(bl, tbl->id, ud->stepskill_id, ud->stepskill_lv);
- }
+ nullpo_retr(2, tbl);
+ if (status->check_visibility(bl, tbl) == 0) // Target not visible
+ return 1;
+ if (ud->stepskill_id == 0)
+ unit->attack(bl, tbl->id, ud->state.attack_continue + 2); // Execute normal attack
+ else
+ unit->skilluse_id(bl, tbl->id, ud->stepskill_id, ud->stepskill_lv); // Execute non-ground skill
}
- return 1;
+ return 0;
}
-static int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data)
+/**
+ * Warps homunculus or mercenary towards his master in case he's too far away for 3 seconds.
+ * @param master_bl: block_list of master
+ * @param slave_bl: block_list of homunculus/mercenary master owns
+ * @return 0: success, 1: fail
+ */
+static int unit_warpto_master(struct block_list *master_bl, struct block_list *slave_bl)
{
- int i;
- int x,y,dx,dy;
- unsigned char icewall_walk_block;
- uint8 dir;
- struct block_list *bl;
- struct map_session_data *sd;
- struct mob_data *md;
- struct unit_data *ud;
- struct mercenary_data *mrd;
+ nullpo_retr(1, master_bl);
+ nullpo_retr(1, slave_bl);
+ int64 *masterteleport_timer;
+ struct homun_data *hd = BL_CAST(BL_HOM, slave_bl);
+ struct mercenary_data *md = BL_CAST(BL_MER, slave_bl);
+
+ bool check = true;
+ if (hd != NULL) {
+ masterteleport_timer = &hd->masterteleport_timer;
+ check = homun_alive(hd);
+ } else if (md != NULL) {
+ masterteleport_timer = &md->masterteleport_timer;
+ } else {
+ return 1;
+ }
- bl = map->id2bl(id);
- if(bl == NULL)
- return 0;
- sd = BL_CAST(BL_PC, bl);
- md = BL_CAST(BL_MOB, bl);
- mrd = BL_CAST(BL_MER, bl);
- ud = unit->bl2ud(bl);
+ if (check && !check_distance_bl(master_bl, slave_bl, MAX_MER_DISTANCE)) {
+ if (*masterteleport_timer == 0) {
+ *masterteleport_timer = timer->gettick();
+ return 0;
+ } else if (DIFF_TICK(timer->gettick(), *masterteleport_timer) > 3000) {
+ unit->warp(slave_bl, master_bl->m, master_bl->x, master_bl->y, CLR_TELEPORT);
+ }
+ }
+ *masterteleport_timer = 0; // resets tick in case he isn't far anymore.
- if(ud == NULL) return 0;
+ return 0;
+}
- if(ud->walktimer != tid){
+/**
+ * Timer for walking to target coordinates or object.
+ * @param tid: timer id
+ * @param tick: tick
+ * @param id: id of bl to do the action
+ * @param data: unused
+ * @return 0: success, 1: fail
+ */
+static int unit_walk_toxy_timer(int tid, int64 tick, int id, intptr_t data)
+{
+ struct block_list *bl = map->id2bl(id);
+ if (bl == NULL || bl->prev == NULL) // Stop moved because it is missing from the block_list
+ return 1;
+ struct unit_data *ud = unit->bl2ud(bl);
+ if (ud == NULL)
+ return 1;
+
+ if (ud->walktimer != tid) {
ShowError("unit_walk_timer mismatch %d != %d\n",ud->walktimer,tid);
- return 0;
+ return 1;
}
ud->walktimer = INVALID_TIMER;
- if (bl->prev == NULL) return 0; // Stop moved because it is missing from the block_list
- if(ud->walkpath.path_pos>=ud->walkpath.path_len)
- return 0;
-
- if(ud->walkpath.path[ud->walkpath.path_pos]>=8)
+ if (ud->walkpath.path_pos >= ud->walkpath.path_len)
return 1;
- x = bl->x;
- y = bl->y;
- dir = ud->walkpath.path[ud->walkpath.path_pos];
+ enum unit_dir dir = ud->walkpath.path[ud->walkpath.path_pos];
+ Assert_retr(1, dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
+ int x = bl->x;
+ int y = bl->y;
+
ud->dir = dir;
- dx = dirx[(int)dir];
- dy = diry[(int)dir];
+ int dx = dirx[dir];
+ int dy = diry[dir];
- //Get icewall walk block depending on boss mode (players can't be trapped)
- if(md && md->status.mode&MD_BOSS)
- icewall_walk_block = battle_config.boss_icewall_walk_block;
- else if(md)
- icewall_walk_block = battle_config.mob_icewall_walk_block;
- else
- icewall_walk_block = 0;
+ // Get icewall walk block depending on boss mode (players can't be trapped)
+ unsigned char icewall_walk_block = 0;
+ struct mob_data *md = BL_CAST(BL_MOB, bl);
+ if (md != NULL) {
+ if ((md->status.mode & MD_BOSS) != 0)
+ icewall_walk_block = battle_config.boss_icewall_walk_block;
+ else
+ icewall_walk_block = battle_config.mob_icewall_walk_block;
+ }
- //Monsters will walk into an icewall from the west and south if they already started walking
+ // Monsters will walk into an icewall from the west and south if they already started walking
if (map->getcell(bl->m, bl, x + dx, y + dy, CELL_CHKNOPASS)
&& (icewall_walk_block == 0 || !map->getcell(bl->m, bl, x + dx, y + dy, CELL_CHKICEWALL) || dx < 0 || dy < 0))
- return unit->walktoxy_sub(bl);
+ return unit->walk_toxy_sub(bl);
- //Monsters can only leave icewalls to the west and south
- //But if movement fails more than icewall_walk_block times, they can ignore this rule
- if (md && md->walktoxy_fail_count < icewall_walk_block && map->getcell(bl->m, bl, x, y, CELL_CHKICEWALL) && (dx > 0 || dy > 0)) {
- //Needs to be done here so that rudeattack skills are invoked
+ // Monsters can only leave icewalls to the west and south
+ // But if movement fails more than icewall_walk_block times, they can ignore this rule
+ if (md != NULL && md->walktoxy_fail_count < icewall_walk_block && map->getcell(bl->m, bl, x, y, CELL_CHKICEWALL) != 0 && (dx > 0 || dy > 0)) {
+ // Needs to be done here so that rudeattack skills are invoked
md->walktoxy_fail_count++;
clif->fixpos(bl);
- //Monsters in this situation first use a chase skill, then unlock target and then use an idle skill
- if (!(++ud->walk_count%WALK_SKILL_INTERVAL))
+ // Monsters in this situation first use a chase skill, then unlock target and then use an idle skill
+ if ((++ud->walk_count % WALK_SKILL_INTERVAL) == 0)
mob->skill_use(md, tick, -1);
mob->unlocktarget(md, tick);
- if (!(++ud->walk_count%WALK_SKILL_INTERVAL))
+ if ((++ud->walk_count % WALK_SKILL_INTERVAL) != 0)
mob->skill_use(md, tick, -1);
- return 0;
+ return 1;
}
+ struct map_session_data *sd = BL_CAST(BL_PC, bl);
//Refresh view for all those we lose sight
- map->foreachinmovearea(clif->outsight, bl, AREA_SIZE, dx, dy, sd?BL_ALL:BL_PC, bl);
+ map->foreachinmovearea(clif->outsight, bl, AREA_SIZE, dx, dy, (sd != NULL ? BL_ALL : BL_PC), bl);
x += dx;
y += dy;
map->moveblock(bl, x, y, tick);
- ud->walk_count++; //walked cell counter, to be used for walk-triggered skills. [Skotlex]
+ ud->walk_count++; // walked cell counter, to be used for walk-triggered skills. [Skotlex]
status_change_end(bl, SC_ROLLINGCUTTER, INVALID_TIMER); //If you move, you lose your counters. [malufett]
if (bl->x != x || bl->y != y || ud->walktimer != INVALID_TIMER)
- return 0; //map->moveblock has altered the object beyond what we expected (moved/warped it)
+ return 1; // map->moveblock has altered the object beyond what we expected (moved/warped it)
ud->walktimer = -2; // arbitrary non-INVALID_TIMER value to make the clif code send walking packets
- map->foreachinmovearea(clif->insight, bl, AREA_SIZE, -dx, -dy, sd?BL_ALL:BL_PC, bl);
+ map->foreachinmovearea(clif->insight, bl, AREA_SIZE, -dx, -dy, (sd != NULL ? BL_ALL : BL_PC), bl);
ud->walktimer = INVALID_TIMER;
- if(sd) {
- if( sd->touching_id )
- npc->touchnext_areanpc(sd,false);
+ struct mercenary_data *mrd = BL_CAST(BL_MER, bl);
+ if (sd != NULL) {
+ if (sd->touching_id != 0)
+ npc->touchnext_areanpc(sd, false);
if (map->getcell(bl->m, bl, x, y, CELL_CHKNPC)) {
- npc->touch_areanpc(sd,bl->m,x,y);
+ npc->touch_areanpc(sd, bl->m, x, y);
if (bl->prev == NULL) //Script could have warped char, abort remaining of the function.
return 0;
- } else
+ } else {
npc->untouch_areanpc(sd, bl->m, x, y);
-
- if( sd->md ) { // mercenary should be warped after being 3 seconds too far from the master [greenbox]
- if( !check_distance_bl(&sd->bl, &sd->md->bl, MAX_MER_DISTANCE) ) {
- if (sd->md->masterteleport_timer == 0)
- sd->md->masterteleport_timer = timer->gettick();
- else if (DIFF_TICK(timer->gettick(), sd->md->masterteleport_timer) > 3000) {
- sd->md->masterteleport_timer = 0;
- unit->warp( &sd->md->bl, sd->bl.m, sd->bl.x, sd->bl.y, CLR_TELEPORT );
- }
- } else // reset the tick, he is not far anymore
- sd->md->masterteleport_timer = 0;
- }
- if( sd->hd ) {
- if( homun_alive(sd->hd) && !check_distance_bl(&sd->bl, &sd->hd->bl, MAX_MER_DISTANCE) ) {
- if (sd->hd->masterteleport_timer == 0)
- sd->hd->masterteleport_timer = timer->gettick();
- else if (DIFF_TICK(timer->gettick(), sd->hd->masterteleport_timer) > 3000) {
- sd->hd->masterteleport_timer = 0;
- unit->warp( &sd->hd->bl, sd->bl.m, sd->bl.x, sd->bl.y, CLR_TELEPORT );
- }
- } else
- sd->hd->masterteleport_timer = 0;
}
+
+ if (sd->md != NULL) // mercenary should be warped after being 3 seconds too far from the master [greenbox]
+ unit->warpto_master(bl, &sd->md->bl);
+ if (sd->hd != NULL)
+ unit->warpto_master(bl, &sd->hd->bl);
} else if (md) {
- //Movement was successful, reset walktoxy_fail_count
+ // Movement was successful, reset walktoxy_fail_count
md->walktoxy_fail_count = 0;
- if (map->getcell(bl->m, bl, x, y, CELL_CHKNPC)) {
- if( npc->touch_areanpc2(md) ) return 0; // Warped
- } else
+
+ if (map->getcell(bl->m, bl, x, y, CELL_CHKNPC) != 0 && npc->touch_areanpc2(md))
+ return 0; // Warped
+ else
md->areanpc_id = 0;
- if (md->min_chase > md->db->range3) md->min_chase--;
- //Walk skills are triggered regardless of target due to the idle-walk mob state.
- //But avoid triggering on stop-walk calls.
- if (tid != INVALID_TIMER
- && !(ud->walk_count%WALK_SKILL_INTERVAL)
- && map->list[bl->m].users > 0
- && mob->skill_use(md, tick, -1)
- ) {
+
+ if (md->min_chase > md->db->range3)
+ md->min_chase--;
+ // Walk skills are triggered regardless of target due to the idle-walk mob state.
+ // But avoid triggering on stop-walk calls.
+ if (tid != INVALID_TIMER && (ud->walk_count % WALK_SKILL_INTERVAL) == 0
+ && map->list[bl->m].users > 0 && mob->skill_use(md, tick, -1) == 1) {
+ // Walk skills are supposed to be used while walking
if (!(ud->skill_id == NPC_SELFDESTRUCTION && ud->skilltimer != INVALID_TIMER)
- && md->state.skillstate != MSS_WALK //Walk skills are supposed to be used while walking
- ) {
- //Skill used, abort walking
- clif->fixpos(bl); //Fix position as walk has been canceled.
- return 0;
+ && md->state.skillstate != MSS_WALK) {
+ // Skill used, abort walking
+ clif->fixpos(bl); // Fix position as walk has been canceled.
+ return 1;
}
- //Resend walk packet for proper Self Destruction display.
+ // Resend walk packet for proper Self Destruction display.
clif->move(ud);
}
- }
- else if( mrd && mrd->master )
- {
- if (!check_distance_bl(&mrd->master->bl, bl, MAX_MER_DISTANCE))
- {
- // mercenary should be warped after being 3 seconds too far from the master [greenbox]
- if (mrd->masterteleport_timer == 0)
- {
- mrd->masterteleport_timer = timer->gettick();
- }
- else if (DIFF_TICK(timer->gettick(), mrd->masterteleport_timer) > 3000)
- {
- mrd->masterteleport_timer = 0;
- unit->warp( bl, mrd->master->bl.m, mrd->master->bl.x, mrd->master->bl.y, CLR_TELEPORT );
- }
- }
- else
- {
- mrd->masterteleport_timer = 0;
- }
+ } else if (mrd != NULL && mrd->master != NULL) {
+ unit->warpto_master(&mrd->master->bl, bl);
}
- if(tid == INVALID_TIMER) //A directly invoked timer is from battle_stop_walking, therefore the rest is irrelevant.
+ if(tid == INVALID_TIMER) // A directly invoked timer is from battle_stop_walking, therefore the rest is irrelevant.
return 0;
- //If stepaction is set then we remembered a client request that should be executed on the next step
- if (ud->stepaction && ud->target_to) {
- //Delete old stepaction even if not executed yet, the latest command is what counts
- if(ud->steptimer != INVALID_TIMER) {
- timer->delete(ud->steptimer, unit->step_timer);
+ // If stepaction is set then we remembered a client request that should be executed on the next step
+ if (ud->stepaction && ud->target_to != 0) {
+ // Delete old stepaction even if not executed yet, the latest command is what counts
+ if (ud->steptimer != INVALID_TIMER) {
+ timer->delete(ud->steptimer, unit->steptimer);
ud->steptimer = INVALID_TIMER;
}
- //Delay stepactions by half a step (so they are executed at full step)
- if(ud->walkpath.path[ud->walkpath.path_pos]&1)
- i = status->get_speed(bl)*14/20;
+ // Delay stepactions by half a step (so they are executed at full step)
+ int timer_delay;
+ if ((ud->walkpath.path[ud->walkpath.path_pos] & 1) != 0)
+ timer_delay = status->get_speed(bl) * 14 / 20;
else
- i = status->get_speed(bl)/2;
- ud->steptimer = timer->add(tick+i, unit->step_timer, bl->id, 0);
+ timer_delay = status->get_speed(bl) / 2;
+ ud->steptimer = timer->add(tick + timer_delay, unit->steptimer, bl->id, 0);
}
- if(ud->state.change_walk_target) {
- if(unit->walktoxy_sub(bl)) {
- return 1;
- } else {
- clif->fixpos(bl);
+ if (ud->state.change_walk_target) {
+ if (unit->walk_toxy_sub(bl) == 0)
return 0;
- }
+ clif->fixpos(bl);
+ return 1;
}
+ int timer_delay;
ud->walkpath.path_pos++;
if(ud->walkpath.path_pos>=ud->walkpath.path_len)
- i = -1;
- else if(ud->walkpath.path[ud->walkpath.path_pos]&1)
- i = status->get_speed(bl)*14/10;
+ timer_delay = -1;
+ else if ((ud->walkpath.path[ud->walkpath.path_pos] & 1) != 0)
+ timer_delay = status->get_speed(bl) * 14 / 10;
else
- i = status->get_speed(bl);
+ timer_delay = status->get_speed(bl);
- if(i > 0) {
- ud->walktimer = timer->add(tick+i,unit->walktoxy_timer,id,i);
- if( md && DIFF_TICK(tick,md->dmgtick) < 3000 )//not required not damaged recently
+ if (timer_delay > 0) {
+ ud->walktimer = timer->add(tick + timer_delay, unit->walk_toxy_timer, id, 0);
+ if (md != NULL && DIFF_TICK(tick, md->dmgtick) < 3000) // not required not damaged recently
clif->move(ud);
- } else if(ud->state.running) {
- //Keep trying to run.
- if ( !(unit->run(bl, NULL, SC_RUN) || unit->run(bl, sd, SC_WUGDASH)) )
+ } else if (ud->state.running != 0) {
+ // Keep trying to run.
+ if (!(unit->run(bl, NULL, SC_RUN) || unit->run(bl, sd, SC_WUGDASH)))
ud->state.running = 0;
- } else if (!ud->stepaction && ud->target_to) {
- //Update target trajectory.
+ } else if (!ud->stepaction && ud->target_to != 0) {
+ // Update target trajectory.
struct block_list *tbl = map->id2bl(ud->target_to);
- if (!tbl || !status->check_visibility(bl, tbl)) {
- //Cancel chase.
+ if (tbl == NULL || status->check_visibility(bl, tbl) == 0) { // not visible
+ // Cancel chase.
ud->to_x = bl->x;
ud->to_y = bl->y;
- if (tbl && bl->type == BL_MOB && mob->warpchase(BL_UCAST(BL_MOB, bl), tbl))
+ if (tbl != NULL && bl->type == BL_MOB && mob->warpchase(BL_UCAST(BL_MOB, bl), tbl) != 0)
return 0;
ud->target_to = 0;
- return 0;
+ return 1;
}
if (tbl->m == bl->m && check_distance_bl(bl, tbl, ud->chaserange)) {
//Reached destination.
@@ -466,81 +507,106 @@ static int unit_walktoxy_timer(int tid, int64 tick, int id, intptr_t data)
clif->fixpos(bl);
unit->attack(bl, tbl->id, ud->state.attack_continue);
}
- } else { //Update chase-path
- unit->walktobl(bl, tbl, ud->chaserange, ud->state.walk_easy|(ud->state.attack_continue? 1 : 0));
+ } else { // Update chase-path
+ unit->walktobl(bl, tbl, ud->chaserange, ud->state.walk_easy | ud->state.attack_continue);
return 0;
}
} else {
- //Stopped walking. Update to_x and to_y to current location [Skotlex]
+ // Stopped walking. Update to_x and to_y to current location [Skotlex]
ud->to_x = bl->x;
ud->to_y = bl->y;
- if (battle_config.official_cell_stack_limit && map->count_oncell(bl->m, x, y, BL_CHAR|BL_NPC, 0x1 | 0x2) > battle_config.official_cell_stack_limit) {
- //Walked on occupied cell, call unit_walktoxy again
- if(ud->steptimer != INVALID_TIMER) {
- //Execute step timer on next step instead
- timer->delete(ud->steptimer, unit->step_timer);
+ if (battle_config.official_cell_stack_limit != 0 && map->count_oncell(bl->m, x, y, BL_CHAR | BL_NPC, 0x1 | 0x2) > battle_config.official_cell_stack_limit) {
+ // Walked on occupied cell, call unit->walk_toxy again
+ if (ud->steptimer != INVALID_TIMER) {
+ // Execute step timer on next step instead
+ timer->delete(ud->steptimer, unit->steptimer);
ud->steptimer = INVALID_TIMER;
}
- return unit->walktoxy(bl, x, y, 8);
+ return unit->walk_toxy(bl, x, y, 8);
}
}
return 0;
}
-static int unit_delay_walktoxy_timer(int tid, int64 tick, int id, intptr_t data)
+/**
+ * Timer for delayed execution of unit->walk_toxy once triggered
+ * @param tid: Timer ID, unused
+ * @param tick: Tick, unused
+ * @param id: ID of block_list to execute the action
+ * @param data: uint32 data cast to intptr_t with x-coord in lowest 16 bits and y-coord in highest 16 bits
+ * @return 0: success, 1: failure
+ */
+static int unit_delay_walk_toxy_timer(int tid, int64 tick, int id, intptr_t data)
{
struct block_list *bl = map->id2bl(id);
-
- if (!bl || bl->prev == NULL)
- return 0;
- unit->walktoxy(bl, (short)((data>>16)&0xffff), (short)(data&0xffff), 0);
- return 1;
+ if (bl == NULL || bl->prev == NULL)
+ return 1;
+ short x = (short)GetWord((uint32)data, 0);
+ short y = (short)GetWord((uint32)data, 1);
+ unit->walk_toxy(bl, x, y, 0);
+ return 0;
}
-//flag parameter:
-//&1 -> 1/0 = easy/hard
-//&2 -> force walking
-//&4 -> Delay walking if the reason you can't walk is the canwalk delay
-//&8 -> Search for an unoccupied cell and cancel if none available
-static int unit_walktoxy(struct block_list *bl, short x, short y, int flag)
+/**
+ * Makes a unit walk to (x, y) coordinates
+ * @param bl: block_list of unit to move
+ * @param x: x-coordinate
+ * @param y: y-coordinate
+ * @param flag: flag paramater with following options:
+ * - `& 1` -> 1/0 = easy / hard
+ * - `& 2` -> Force walking
+ * - `& 4` -> Delay walking, if the reason you can't walk is the `canwalk delay`
+ * - `& 8` -> Search for an unoccupied cell and cancel if none available
+ * .
+ * @return 0: success, 1: failure
+ */
+static int unit_walk_toxy(struct block_list *bl, short x, short y, int flag)
{
+ // TODO: change flag to enum? [skyleo]
struct unit_data* ud = NULL;
struct status_change* sc = NULL;
struct walkpath_data wpd;
- nullpo_ret(bl);
+ nullpo_retr(1, bl);
ud = unit->bl2ud(bl);
- if( ud == NULL) return 0;
+ if (ud == NULL)
+ return 1;
- if (battle_config.check_occupied_cells && (flag&8) && !map->closest_freecell(bl->m, bl, &x, &y, BL_CHAR|BL_NPC, 1)) //This might change x and y
- return 0;
+ if ((flag & 8) != 0 && battle_config.check_occupied_cells != 0) {
+ if (!map->closest_freecell(bl->m, bl, &x, &y, BL_CHAR | BL_NPC, 1)) // This might change x and y
+ return 1;
+ }
- if (!path->search(&wpd, bl, bl->m, bl->x, bl->y, x, y, flag&1, CELL_CHKNOPASS)) // Count walk path cells
- return 0;
+ if (!path->search(&wpd, bl, bl->m, bl->x, bl->y, x, y, flag & 1, CELL_CHKNOPASS)) // Count walk path cells
+ return 1;
+ if (bl->type != BL_NPC) {
#ifdef OFFICIAL_WALKPATH
- if( !path->search_long(NULL, bl, bl->m, bl->x, bl->y, x, y, CELL_CHKNOPASS) // Check if there is an obstacle between
- && (wpd.path_len > (battle_config.max_walk_path/17)*14) // Official number of walkable cells is 14 if and only if there is an obstacle between. [malufett]
- && (bl->type != BL_NPC) ) // If type is a NPC, please disregard.
- return 0;
+ // Check if there is an obstacle between
+ // Official number of walkable cells is 14 if and only if there is an obstacle between. [malufett]
+ if (!path->search_long(NULL, bl, bl->m, bl->x, bl->y, x, y, CELL_CHKNOPASS)
+ && (wpd.path_len > (battle_config.max_walk_path / 17) * 14))
+ return 1;
#endif
- if ((wpd.path_len > battle_config.max_walk_path) && (bl->type != BL_NPC))
- return 0;
+ if (wpd.path_len > battle_config.max_walk_path)
+ return 1;
+ }
- if (flag&4 && DIFF_TICK(ud->canmove_tick, timer->gettick()) > 0 &&
- DIFF_TICK(ud->canmove_tick, timer->gettick()) < 2000) {
+ if ((flag & 4) != 0 && DIFF_TICK(ud->canmove_tick, timer->gettick()) > 0
+ && DIFF_TICK(ud->canmove_tick, timer->gettick()) < 2000) {
// Delay walking command. [Skotlex]
- timer->add(ud->canmove_tick+1, unit->delay_walktoxy_timer, bl->id, (x<<16)|(y&0xFFFF));
- return 1;
+ timer->add(ud->canmove_tick + 1, unit->delay_walk_toxy_timer, bl->id,
+ (intptr_t)MakeDWord((uint16)x, (uint16)y));
+ return 0;
}
- if(!(flag&2) && (!(status_get_mode(bl)&MD_CANMOVE) || !unit->can_move(bl)))
- return 0;
+ if ((flag & 2) == 0 && ((status_get_mode(bl) & MD_CANMOVE) == 0 || unit->can_move(bl) == 0))
+ return 1;
- ud->state.walk_easy = flag&1;
+ ud->state.walk_easy = flag & 1;
ud->to_x = x;
ud->to_y = y;
unit->stop_attack(bl); //Sets target to 0
@@ -548,44 +614,63 @@ static int unit_walktoxy(struct block_list *bl, short x, short y, int flag)
unit->stop_stepaction(bl); // unit->walktoxy removes any remembered stepaction and resets ud->target_to
sc = status->get_sc(bl);
- if( sc ) {
- if( sc->data[SC_CONFUSION] || sc->data[SC__CHAOS] ) //Randomize the target position
+ if (sc != NULL) {
+ if (sc->data[SC_CONFUSION] != NULL || sc->data[SC__CHAOS] != NULL) // Randomize the target position
map->random_dir(bl, &ud->to_x, &ud->to_y);
- if( sc->data[SC_COMBOATTACK] )
+ if (sc->data[SC_COMBOATTACK] != NULL)
status_change_end(bl, SC_COMBOATTACK, INVALID_TIMER);
}
- if(ud->walktimer != INVALID_TIMER) {
+ if (ud->walktimer != INVALID_TIMER) {
// When you come to the center of the grid because the change of destination while you're walking right now
- // Call a function from a timer unit->walktoxy_sub
+ // Call a function from a timer unit->walk_toxy_sub
ud->state.change_walk_target = 1;
- return 1;
+ return 0;
}
- return unit->walktoxy_sub(bl);
+ return unit->walk_toxy_sub(bl);
}
-//To set Mob's CHASE/FOLLOW states (shouldn't be done if there's no path to reach)
-static inline void set_mobstate(struct block_list *bl, int flag)
+/**
+ * Sets CHASE / FOLLOW states, in case bl is a mob.
+ * WARNING: This shouldn't be done if there's no path to reach
+ * @param bl: block_list of mob
+ */
+static inline void set_mobstate(struct block_list *bl)
{
- struct mob_data* md = BL_CAST(BL_MOB,bl);
+ struct mob_data* md = BL_CAST(BL_MOB, bl);
- if( md && flag )
- md->state.skillstate = md->state.aggressive ? MSS_FOLLOW : MSS_RUSH;
+ if (md != NULL) {
+ if (md->state.aggressive != 0)
+ md->state.skillstate = MSS_FOLLOW;
+ else
+ md->state.skillstate = MSS_RUSH;
+ }
}
-static int unit_walktobl_sub(int tid, int64 tick, int id, intptr_t data)
+/**
+ * Timer used for when a unit can't walk towards its target yet due to it's canmove_tick,
+ * keeps retrying until it works or target changes.
+ * @param tid: Timer ID, unused
+ * @param tick: Tick, unused
+ * @param id: ID of block_list to execute the action
+ * @param data: ID of block_list to walk towards
+ * @return 0: success, 1: failure
+ */
+static int unit_walktobl_timer(int tid, int64 tick, int id, intptr_t data)
{
struct block_list *bl = map->id2bl(id);
- struct unit_data *ud = bl?unit->bl2ud(bl):NULL;
-
- if (ud && ud->walktimer == INVALID_TIMER && ud->target == data) {
- if (DIFF_TICK(ud->canmove_tick, tick) > 0) //Keep waiting?
- timer->add(ud->canmove_tick+1, unit->walktobl_sub, id, data);
- else if (unit->can_move(bl)) {
- if (unit->walktoxy_sub(bl))
- set_mobstate(bl, ud->state.attack_continue);
- }
+ if (bl == NULL)
+ return 1;
+ struct unit_data *ud = unit->bl2ud(bl);
+ if (ud == NULL)
+ return 1;
+
+ if (ud->walktimer == INVALID_TIMER && ud->target == data) {
+ if (DIFF_TICK(ud->canmove_tick, tick) > 0) // Keep waiting?
+ timer->add(ud->canmove_tick + 1, unit->walktobl_timer, id, data);
+ else if (unit->can_move(bl) != 0 && unit->walk_toxy_sub(bl) == 0 && ud->state.attack_continue != 0)
+ set_mobstate(bl);
}
return 0;
}
@@ -629,22 +714,23 @@ static int unit_walktobl(struct block_list *bl, struct block_list *tbl, int rang
if(ud->walktimer != INVALID_TIMER) {
ud->state.change_walk_target = 1;
- set_mobstate(bl, flag&2);
+ if ((flag & 2) != 0)
+ set_mobstate(bl);
return 1;
}
if (DIFF_TICK(ud->canmove_tick, timer->gettick()) > 0) {
//Can't move, wait a bit before invoking the movement.
- timer->add(ud->canmove_tick+1, unit->walktobl_sub, bl->id, ud->target);
+ timer->add(ud->canmove_tick + 1, unit->walktobl_timer, bl->id, ud->target);
return 1;
}
if(!unit->can_move(bl))
return 0;
- if (unit->walktoxy_sub(bl)) {
- set_mobstate(bl, flag&2);
- return 1;
+ if (unit->walk_toxy_sub(bl) == 0 && (flag & 2) != 0) {
+ set_mobstate(bl);
+ return 0;
}
return 0;
}
@@ -732,14 +818,14 @@ static bool unit_run(struct block_list *bl, struct map_session_data *sd, enum sc
return false;
}
- if( unit->walktoxy(bl, to_x, to_y, 1) )
+ if (unit->walk_toxy(bl, to_x, to_y, 1) == 0)
return true;
//There must be an obstacle nearby. Attempt walking one cell at a time.
do {
to_x -= dir_x;
to_y -= dir_y;
- } while (--i > 0 && !unit->walktoxy(bl, to_x, to_y, 1));
+ } while (--i > 0 && unit->walk_toxy(bl, to_x, to_y, 1) != 0);
if ( i == 0 ) {
unit->run_hit(bl, sc, sd, type);
@@ -752,19 +838,21 @@ static bool unit_run(struct block_list *bl, struct map_session_data *sd, enum sc
//Makes bl attempt to run dist cells away from target. Uses hard-paths.
static int unit_escape(struct block_list *bl, struct block_list *target, short dist)
{
- uint8 dir;
nullpo_ret(bl);
- dir = map->calc_dir(target, bl->x, bl->y);
+ enum unit_dir dir = map->calc_dir(target, bl->x, bl->y);
+ Assert_retr(1, dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
while (dist > 0 && map->getcell(bl->m, bl, bl->x + dist * dirx[dir], bl->y + dist * diry[dir], CELL_CHKNOREACH))
dist--;
- return ( dist > 0 && unit->walktoxy(bl, bl->x + dist*dirx[dir], bl->y + dist*diry[dir], 0) );
+ if (dist > 0 && unit->walk_toxy(bl, bl->x + dist * dirx[dir], bl->y + dist * diry[dir], 0) == 0)
+ return 1;
+ else
+ return 0;
}
//Instant warp function.
static int unit_movepos(struct block_list *bl, short dst_x, short dst_y, int easy, bool checkpath)
{
short dx,dy;
- uint8 dir;
struct unit_data *ud = NULL;
struct map_session_data *sd = NULL;
@@ -783,7 +871,7 @@ static int unit_movepos(struct block_list *bl, short dst_x, short dst_y, int eas
ud->to_x = dst_x;
ud->to_y = dst_y;
- dir = map->calc_dir(bl, dst_x, dst_y);
+ enum unit_dir dir = map->calc_dir(bl, dst_x, dst_y);
ud->dir = dir;
dx = dst_x - bl->x;
@@ -825,12 +913,18 @@ static int unit_movepos(struct block_list *bl, short dst_x, short dst_y, int eas
return 1;
}
-static int unit_setdir(struct block_list *bl, unsigned char dir)
+/**
+ * Sets the facing direction of a unit
+ * @param bl: unit to modify
+ * @param dir: the facing direction @see enum unit_dir
+ * @return 0: success, 1: failure
+ */
+static int unit_set_dir(struct block_list *bl, enum unit_dir dir)
{
- struct unit_data *ud;
- nullpo_ret(bl );
- ud = unit->bl2ud(bl);
- if (!ud) return 0;
+ nullpo_retr(1, bl);
+ struct unit_data *ud = unit->bl2ud(bl);
+ if (ud == NULL)
+ return 1;
ud->dir = dir;
if (bl->type == BL_PC)
BL_UCAST(BL_PC, bl)->head_dir = 0;
@@ -838,15 +932,20 @@ static int unit_setdir(struct block_list *bl, unsigned char dir)
return 0;
}
-static uint8 unit_getdir(struct block_list *bl)
+/**
+ * Get the facing direction of a unit
+ * @param bl: unit to request data from
+ * @return the facing direction @see enum unit_dir
+ */
+static enum unit_dir unit_getdir(const struct block_list *bl)
{
- struct unit_data *ud;
- nullpo_ret(bl);
+ nullpo_retr(UNIT_DIR_NORTH, bl);
- if( bl->type == BL_NPC )
+ if (bl->type == BL_NPC)
return BL_UCCAST(BL_NPC, bl)->dir;
- ud = unit->bl2ud(bl);
- if (!ud) return 0;
+ const struct unit_data *ud = unit->cbl2ud(bl);
+ if (ud == NULL)
+ return UNIT_DIR_NORTH;
return ud->dir;
}
@@ -1010,7 +1109,7 @@ static int unit_stop_walking(struct block_list *bl, int flag)
//timer->delete function does not messes with it. If the function's
//behavior changes in the future, this code could break!
td = timer->get(ud->walktimer);
- timer->delete(ud->walktimer, unit->walktoxy_timer);
+ timer->delete(ud->walktimer, unit->walk_toxy_timer);
ud->walktimer = INVALID_TIMER;
ud->state.change_walk_target = 0;
tick = timer->gettick();
@@ -1018,7 +1117,7 @@ static int unit_stop_walking(struct block_list *bl, int flag)
|| (flag&STOPWALKING_FLAG_NEXTCELL && td && DIFF_TICK(td->tick, tick) <= td->data/2) //Enough time has passed to cover half-cell
) {
ud->walkpath.path_len = ud->walkpath.path_pos+1;
- unit->walktoxy_timer(INVALID_TIMER, tick, bl->id, ud->walkpath.path_pos);
+ unit->walk_toxy_timer(INVALID_TIMER, tick, bl->id, ud->walkpath.path_pos);
}
if(flag&STOPWALKING_FLAG_FIXPOS)
@@ -1041,11 +1140,15 @@ static int unit_stop_walking(struct block_list *bl, int flag)
static int unit_skilluse_id(struct block_list *src, int target_id, uint16 skill_id, uint16 skill_lv)
{
- return unit->skilluse_id2(
- src, target_id, skill_id, skill_lv,
- skill->cast_fix(src, skill_id, skill_lv),
- skill->get_castcancel(skill_id)
- );
+ int casttime = skill->cast_fix(src, skill_id, skill_lv);
+ int castcancel = skill->get_castcancel(skill_id);
+ int ret = unit->skilluse_id2(src, target_id, skill_id, skill_lv, casttime, castcancel);
+ struct map_session_data *sd = BL_CAST(BL_PC, src);
+
+ if (sd != NULL)
+ pc->itemskill_clear(sd);
+
+ return ret;
}
static int unit_is_walking(struct block_list *bl)
@@ -1231,7 +1334,7 @@ static int unit_set_walkdelay(struct block_list *bl, int64 tick, int delay, int
} else {
unit->stop_walking(bl, STOPWALKING_FLAG_NEXTCELL);
if (ud->target)
- timer->add(ud->canmove_tick+1, unit->walktobl_sub, bl->id, ud->target);
+ timer->add(ud->canmove_tick + 1, unit->walktobl_timer, bl->id, ud->target);
}
}
}
@@ -1418,15 +1521,8 @@ static int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill
}
}
- if (sd) {
- /* temporarily disabled, awaiting for kenpachi to detail this so we can make it work properly */
-#if 0
- if (sd->skillitem != skill_id && !skill->check_condition_castbegin(sd, skill_id, skill_lv))
-#else
- if (!skill->check_condition_castbegin(sd, skill_id, skill_lv))
-#endif
- return 0;
- }
+ if (sd != NULL && skill->check_condition_castbegin(sd, skill_id, skill_lv) == 0)
+ return 0;
if (src->type == BL_MOB) {
const struct mob_data *src_md = BL_UCCAST(BL_MOB, src);
@@ -1453,7 +1549,7 @@ static int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill
ud->target_to = target_id;
ud->stepskill_id = skill_id;
ud->stepskill_lv = skill_lv;
- return 0; // Attacking will be handled by unit_walktoxy_timer in this case
+ return 0; // Attacking will be handled by unit_walk_toxy_timer in this case
}
//Check range when not using skill on yourself or is a combo-skill during attack
@@ -1609,6 +1705,9 @@ static int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill
if (!ud->state.running) //need TK_RUN or WUGDASH handler to be done before that, see bugreport:6026
unit->stop_walking(src, STOPWALKING_FLAG_FIXPOS);// even though this is not how official works but this will do the trick. bugreport:6829
+ if (sd != NULL && sd->state.itemskill_no_casttime == 1 && skill->is_item_skill(sd, skill_id, skill_lv))
+ casttime = 0;
+
// in official this is triggered even if no cast time.
clif->useskill(src, src->id, target_id, 0,0, skill_id, skill_lv, casttime);
if( casttime > 0 || temp )
@@ -1663,7 +1762,7 @@ static int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill
if( casttime > 0 ) {
if (src->id != target->id) // self-targeted skills shouldn't show different direction
- unit->setdir(src, map->calc_dir(src, target->x, target->y));
+ unit->set_dir(src, map->calc_dir(src, target->x, target->y));
ud->skilltimer = timer->add( tick+casttime, skill->castend_id, src->id, 0 );
if (sd && (pc->checkskill(sd, SA_FREECAST) > 0 || skill_id == LG_EXEEDBREAK || (skill->get_inf2(ud->skill_id) & INF2_FREE_CAST_REDUCED) != 0))
status_calc_bl(&sd->bl, SCB_SPEED|SCB_ASPD);
@@ -1678,11 +1777,15 @@ static int unit_skilluse_id2(struct block_list *src, int target_id, uint16 skill
static int unit_skilluse_pos(struct block_list *src, short skill_x, short skill_y, uint16 skill_id, uint16 skill_lv)
{
- return unit->skilluse_pos2(
- src, skill_x, skill_y, skill_id, skill_lv,
- skill->cast_fix(src, skill_id, skill_lv),
- skill->get_castcancel(skill_id)
- );
+ int casttime = skill->cast_fix(src, skill_id, skill_lv);
+ int castcancel = skill->get_castcancel(skill_id);
+ int ret = unit->skilluse_pos2(src, skill_x, skill_y, skill_id, skill_lv, casttime, castcancel);
+ struct map_session_data *sd = BL_CAST(BL_PC, src);
+
+ if (sd != NULL)
+ pc->itemskill_clear(sd);
+
+ return ret;
}
static int unit_skilluse_pos2(struct block_list *src, short skill_x, short skill_y, uint16 skill_id, uint16 skill_lv, int casttime, int castcancel)
@@ -1753,7 +1856,7 @@ static int unit_skilluse_pos2(struct block_list *src, short skill_x, short skill
ud->target_to = (skill_x + skill_y*md->xs);
ud->stepskill_id = skill_id;
ud->stepskill_lv = skill_lv;
- return 0; // Attacking will be handled by unit_walktoxy_timer in this case
+ return 0; // Attacking will be handled by unit_walk_toxy_timer in this case
}
if( skill->get_state(ud->skill_id) == ST_MOVE_ENABLE ) {
@@ -1807,10 +1910,14 @@ static int unit_skilluse_pos2(struct block_list *src, short skill_x, short skill
}
unit->stop_walking(src, STOPWALKING_FLAG_FIXPOS);
+
+ if (sd != NULL && sd->state.itemskill_no_casttime == 1 && skill->is_item_skill(sd, skill_id, skill_lv))
+ casttime = 0;
+
// in official this is triggered even if no cast time.
clif->useskill(src, src->id, 0, skill_x, skill_y, skill_id, skill_lv, casttime);
if( casttime > 0 ) {
- unit->setdir(src, map->calc_dir(src, skill_x, skill_y));
+ unit->set_dir(src, map->calc_dir(src, skill_x, skill_y));
ud->skilltimer = timer->add( tick+casttime, skill->castend_pos, src->id, 0 );
if ((sd && pc->checkskill(sd, SA_FREECAST) > 0) || skill_id == LG_EXEEDBREAK || (skill->get_inf2(ud->skill_id) & INF2_FREE_CAST_REDUCED) != 0) {
status_calc_bl(&sd->bl, SCB_SPEED|SCB_ASPD);
@@ -1889,7 +1996,7 @@ static void unit_stop_stepaction(struct block_list *bl)
return;
//Clear timer
- timer->delete(ud->steptimer, unit->step_timer);
+ timer->delete(ud->steptimer, unit->steptimer);
ud->steptimer = INVALID_TIMER;
}
@@ -1973,7 +2080,7 @@ static int unit_attack(struct block_list *src, int target_id, int continuous)
ud->target_to = ud->target;
ud->stepskill_id = 0;
ud->stepskill_lv = 0;
- return 0; // Attacking will be handled by unit_walktoxy_timer in this case
+ return 0; // Attacking will be handled by unit_walk_toxy_timer in this case
}
if(DIFF_TICK(ud->attackabletime, timer->gettick()) > 0)
@@ -2074,14 +2181,13 @@ static bool unit_can_reach_bl(struct block_list *bl, struct block_list *tbl, int
/*==========================================
* Calculates position of Pet/Mercenary/Homunculus/Elemental
*------------------------------------------*/
-static int unit_calc_pos(struct block_list *bl, int tx, int ty, uint8 dir)
+static int unit_calc_pos(struct block_list *bl, int tx, int ty, enum unit_dir dir)
{
int dx, dy, x, y;
struct unit_data *ud = unit->bl2ud(bl);
nullpo_ret(ud);
- if(dir > 7)
- return 1;
+ Assert_retr(1, dir >= UNIT_DIR_FIRST && dir < UNIT_DIR_MAX);
ud->to_x = tx;
ud->to_y = ty;
@@ -2098,7 +2204,7 @@ static int unit_calc_pos(struct block_list *bl, int tx, int ty, uint8 dir)
if (!unit->can_reach_pos(bl, x, y, 0)) {
int i;
for (i = 0; i < 12; i++) {
- int k = rnd()%8; // Pick a Random Dir
+ enum unit_dir k = rnd() % UNIT_DIR_MAX; // Pick a Random Dir
dx = -dirx[k] * 2;
dy = -diry[k] * 2;
x = tx + dx;
@@ -2264,7 +2370,7 @@ static int unit_attack_timer_sub(struct block_list *src, int tid, int64 tick)
}
if(ud->state.attack_continue) {
- unit->setdir(src, map->calc_dir(src, target->x, target->y));
+ unit->set_dir(src, map->calc_dir(src, target->x, target->y));
if( src->type == BL_PC )
pc->update_idle_time(sd, BCIDLE_ATTACK);
ud->attacktimer = timer->add(ud->attackabletime,unit->attack_timer,src->id,0);
@@ -2961,10 +3067,10 @@ static int do_init_unit(bool minimal)
return 0;
timer->add_func_list(unit->attack_timer, "unit_attack_timer");
- timer->add_func_list(unit->walktoxy_timer,"unit_walktoxy_timer");
- timer->add_func_list(unit->walktobl_sub, "unit_walktobl_sub");
- timer->add_func_list(unit->delay_walktoxy_timer,"unit_delay_walktoxy_timer");
- timer->add_func_list(unit->step_timer,"unit_step_timer");
+ timer->add_func_list(unit->walk_toxy_timer, "unit_walk_toxy_timer");
+ timer->add_func_list(unit->walktobl_timer, "unit_walktobl_timer");
+ timer->add_func_list(unit->delay_walk_toxy_timer, "unit_delay_walk_toxy_timer");
+ timer->add_func_list(unit->steptimer, "unit_steptimer");
return 0;
}
@@ -2982,26 +3088,28 @@ void unit_defaults(void)
unit->final = do_final_unit;
/* */
unit->bl2ud = unit_bl2ud;
+ unit->cbl2ud = unit_cbl2ud;
unit->bl2ud2 = unit_bl2ud2;
unit->init_ud = unit_init_ud;
unit->attack_timer = unit_attack_timer;
- unit->walktoxy_timer = unit_walktoxy_timer;
- unit->walktoxy_sub = unit_walktoxy_sub;
- unit->delay_walktoxy_timer = unit_delay_walktoxy_timer;
- unit->walktoxy = unit_walktoxy;
- unit->walktobl_sub = unit_walktobl_sub;
+ unit->walk_toxy_timer = unit_walk_toxy_timer;
+ unit->walk_toxy_sub = unit_walk_toxy_sub;
+ unit->delay_walk_toxy_timer = unit_delay_walk_toxy_timer;
+ unit->walk_toxy = unit_walk_toxy;
+ unit->walktobl_timer = unit_walktobl_timer;
unit->walktobl = unit_walktobl;
unit->run = unit_run;
unit->run_hit = unit_run_hit;
unit->escape = unit_escape;
unit->movepos = unit_movepos;
- unit->setdir = unit_setdir;
+ unit->set_dir = unit_set_dir;
unit->getdir = unit_getdir;
unit->blown = unit_blown;
unit->warp = unit_warp;
+ unit->warpto_master = unit_warpto_master;
unit->stop_walking = unit_stop_walking;
unit->skilluse_id = unit_skilluse_id;
- unit->step_timer = unit_step_timer;
+ unit->steptimer = unit_steptimer;
unit->stop_stepaction = unit_stop_stepaction;
unit->is_walking = unit_is_walking;
unit->can_move = unit_can_move;
diff --git a/src/map/unit.h b/src/map/unit.h
index 5437a172a..3f288e0d3 100644
--- a/src/map/unit.h
+++ b/src/map/unit.h
@@ -24,6 +24,7 @@
#include "map/clif.h" // clr_type
#include "map/path.h" // struct walkpath_data
#include "map/skill.h" // 'MAX_SKILLTIMERSKILL, struct skill_timerskill, struct skill_unit_group, struct skill_unit_group_tickset
+#include "map/unitdefines.h" // enum unit_dir
#include "common/hercules.h"
struct map_session_data;
@@ -67,7 +68,7 @@ struct unit_data {
int64 attackabletime;
int64 canact_tick;
int64 canmove_tick;
- uint8 dir;
+ enum unit_dir dir;
unsigned char walk_count;
unsigned char target_count;
struct {
@@ -102,26 +103,28 @@ struct unit_interface {
int (*final) (void);
/* */
struct unit_data* (*bl2ud) (struct block_list *bl);
+ const struct unit_data* (*cbl2ud) (const struct block_list *bl);
struct unit_data* (*bl2ud2) (struct block_list *bl);
void (*init_ud) (struct unit_data *ud);
int (*attack_timer) (int tid, int64 tick, int id, intptr_t data);
- int (*walktoxy_timer) (int tid, int64 tick, int id, intptr_t data);
- int (*walktoxy_sub) (struct block_list *bl);
- int (*delay_walktoxy_timer) (int tid, int64 tick, int id, intptr_t data);
- int (*walktoxy) (struct block_list *bl, short x, short y, int flag);
- int (*walktobl_sub) (int tid, int64 tick, int id, intptr_t data);
+ int (*walk_toxy_timer) (int tid, int64 tick, int id, intptr_t data);
+ int (*walk_toxy_sub) (struct block_list *bl);
+ int (*delay_walk_toxy_timer) (int tid, int64 tick, int id, intptr_t data);
+ int (*walk_toxy) (struct block_list *bl, short x, short y, int flag);
+ int (*walktobl_timer) (int tid, int64 tick, int id, intptr_t data);
int (*walktobl) (struct block_list *bl, struct block_list *tbl, int range, int flag);
bool (*run) (struct block_list *bl, struct map_session_data *sd, enum sc_type type);
void (*run_hit) (struct block_list *bl, struct status_change *sc, struct map_session_data *sd, enum sc_type type);
int (*escape) (struct block_list *bl, struct block_list *target, short dist);
int (*movepos) (struct block_list *bl, short dst_x, short dst_y, int easy, bool checkpath);
- int (*setdir) (struct block_list *bl, unsigned char dir);
- uint8 (*getdir) (struct block_list *bl);
+ int (*set_dir) (struct block_list *bl, enum unit_dir dir);
+ enum unit_dir (*getdir) (const struct block_list *bl);
int (*blown) (struct block_list *bl, int dx, int dy, int count, int flag);
int (*warp) (struct block_list *bl, short m, short x, short y, enum clr_type type);
+ int (*warpto_master) (struct block_list *master_bl, struct block_list *slave_bl);
int (*stop_walking) (struct block_list *bl, int type);
int (*skilluse_id) (struct block_list *src, int target_id, uint16 skill_id, uint16 skill_lv);
- int (*step_timer) (int tid, int64 tick, int id, intptr_t data);
+ int (*steptimer) (int tid, int64 tick, int id, intptr_t data);
void (*stop_stepaction) (struct block_list *bl);
int (*is_walking) (struct block_list *bl);
int (*can_move) (struct block_list *bl);
@@ -137,7 +140,7 @@ struct unit_interface {
int (*cancel_combo) (struct block_list *bl);
bool (*can_reach_pos) (struct block_list *bl, int x, int y, int easy);
bool (*can_reach_bl) (struct block_list *bl, struct block_list *tbl, int range, int easy, short *x, short *y);
- int (*calc_pos) (struct block_list *bl, int tx, int ty, uint8 dir);
+ int (*calc_pos) (struct block_list *bl, int tx, int ty, enum unit_dir dir);
int (*attack_timer_sub) (struct block_list *src, int tid, int64 tick);
int (*skillcastcancel) (struct block_list *bl, int type);
void (*dataset) (struct block_list *bl);
diff --git a/src/map/unitdefines.h b/src/map/unitdefines.h
new file mode 100644
index 000000000..0ee30998c
--- /dev/null
+++ b/src/map/unitdefines.h
@@ -0,0 +1,58 @@
+/**
+ * This file is part of Hercules.
+ * http://herc.ws - http://github.com/HerculesWS/Hercules
+ *
+ * Copyright (C) 2012-2019 Hercules Dev Team
+ * Copyright (C) Athena Dev Teams
+ *
+ * Hercules is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef MAP_UNITDEFINES_H
+#define MAP_UNITDEFINES_H
+
+/**
+ * Used for directions, @see unit_data.dir
+ */
+enum unit_dir {
+ UNIT_DIR_UNDEFINED = -1,
+ UNIT_DIR_FIRST = 0,
+ UNIT_DIR_NORTH = 0,
+ UNIT_DIR_NORTHWEST = 1,
+ UNIT_DIR_WEST = 2,
+ UNIT_DIR_SOUTHWEST = 3,
+ UNIT_DIR_SOUTH = 4,
+ UNIT_DIR_SOUTHEAST = 5,
+ UNIT_DIR_EAST = 6,
+ UNIT_DIR_NORTHEAST = 7,
+ UNIT_DIR_MAX = 8,
+ /* IMPORTANT: Changing the order would break the above macros
+ * and several usages of directions anywhere */
+};
+
+/* Returns the opposite of the facing direction */
+#define unit_get_opposite_dir(dir) ( ((dir) + 4) % UNIT_DIR_MAX )
+
+/* Returns true when direction is diagonal/combined (ex. UNIT_DIR_NORTHWEST, UNIT_DIR_SOUTHWEST, ...) */
+#define unit_is_diagonal_dir(dir) ( ((dir) % 2) == UNIT_DIR_NORTHWEST )
+
+/* Returns true if direction equals val or the opposite direction of val */
+#define unit_is_dir_or_opposite(dir, val) ( ((dir) % 4) == (val) )
+
+/* Returns the next direction after 90° CCW on a compass */
+#define unit_get_ccw90_dir(dir) ( ((dir) + 2) % UNIT_DIR_MAX )
+
+/* Returns a random diagonal direction */
+#define unit_get_rnd_diagonal_dir() ( UNIT_DIR_NORTHWEST + 2 * (rnd() % 4) )
+
+#endif /* MAP_UNITDEFINES_H */