diff --git a/db/import-tmpl/mob_avail.txt b/db/import-tmpl/mob_avail.txt
deleted file mode 100644
index 414d7a3518..0000000000
--- a/db/import-tmpl/mob_avail.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-// Mob Availability and Alias Database
-//
-// Structure of Database:
-// MobID,SpriteID{,Equipment}
-//
-// 01. MobID Mob ID to change.
-// 02. SpriteID Mob ID which will be sent to the client instead of MobID.
-// If 0, the mob becomes unavailable for use.
-// 03. Equipment Item ID of pet equipment (must be available for pet counterpart, or this will cause problems).
-//
-// To disguise a mob as a player:
-// MobID,SpriteID,Sex,Hair_Style,Hair_Color,Weapon,Shield,Head_Top,Head_Middle,Head_Bottom,Option,Dye_Color
-//
-// SpriteID is a job class value.
-// Weapon and Shield uses Item ID, while Head uses View ID.
-
diff --git a/db/import-tmpl/mob_avail.yml b/db/import-tmpl/mob_avail.yml
new file mode 100644
index 0000000000..cd1ea0c11a
--- /dev/null
+++ b/db/import-tmpl/mob_avail.yml
@@ -0,0 +1,123 @@
+# This file is a part of rAthena.
+# Copyright(C) 2019 rAthena Development Team
+# https://rathena.org - https://github.com/rathena
+#
+# This program 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 .
+#
+###########################################################################
+# Mob Availability and Alias Database
+###########################################################################
+#
+# Mob Availability and Alias Settings
+#
+###########################################################################
+# - Mob Mob to adjust.
+# Sprite Sprite which will be sent to the client instead of Mob.
+# Sex Sex (if Sprite is a player). (Default: Female)
+# HairStyle Hair Style ID (if Sprite is a player). (Default: 0)
+# HairColor Hair Color ID (if Sprite is a player). (Default: 0)
+# ClothColor Cloth Color ID (if Sprite is a player). (Default: 0)
+# Weapon Item name of weapon (if Sprite is a player). (Default: 0)
+# Shield Item name of shield (if Sprite is a player). (Default: 0)
+# HeadTop Item name of headgear (if Sprite is a player). (Default: 0)
+# HeadMid Item name of headgear (if Sprite is a player). (Default: 0)
+# HeadLow Item name of headgear (if Sprite is a player). (Default: 0)
+# PetEquip Item name of pet equipment (if Mob is a valid pet). (Default: 0)
+# Options: Set an option for an object. (Optional)
+# : bool
+###########################################################################
+
+Header:
+ Type: MOB_AVAIL_DB
+ Version: 1
+
+#Body:
+ # Examples
+# - Mob: PORING
+# Sprite: BAPHOMET
+# - Mob: E_OBEAUNE
+# Sprite: PORING
+# PetEquip: Backpack
+
+ # Easter Event Monsters
+# - Mob: MOROCC_3
+# Sprite: DOPPELGANGER
+# - Mob: MOROCC_4
+# Sprite: ECLIPSE
+
+ # rAthena Dev Team
+ # Valaris
+# - Mob: BOW_GUARDIAN_
+# Sprite: JOB_ASSASSIN_CROSS
+# Sex: Male
+# HairStyle: 1
+# HairColor: 1
+# ClothColor: 1
+# Weapon: Jamadhar
+# HeadTop: Sahkkat
+# HeadMid: Sunglasses
+# HeadLow: Cigar
+# Options:
+# Falcon: true
+ # Valaris Worshiper
+# - Mob: E_CONDOR
+# Sprite: JOB_THIEF
+# Sex: Male
+# HairStyle: 1
+# HairColor: 1
+# ClothColor: 1
+# Weapon: Gladius
+# Shield: Guard
+# HeadTop: Sahkkat
+# HeadMid: Sunglasses
+# HeadLow: Cigar
+ # MC Cameri
+# - Mob: E_TREASURE1
+# Sprite: JOB_CRUSADER
+# Sex: Male
+# HairStyle: 6
+# HairColor: 6
+# ClothColor: 3
+# Weapon: Sword
+# Shield: Shield
+# Options:
+# Riding: true
+ # Poki#3
+# - Mob: E_TREASURE2
+# Sprite: JOB_SNIPER
+# Sex: Male
+# HairStyle: 21
+# Weapon: Bow_Of_Rudra
+# HeadTop: Boy's_Cap
+# HeadMid: Takius_Blindfold
+# HeadLow: Centimental_Leaf
+# Options:
+# Falcon: true
+ # Sentry
+# - Mob: BOMBPORING
+# Sprite: KNIGHT_GUARDIAN
+
+ # iRO Halloween Event 2009
+# - Mob: EP14_MORS_BOSSB
+# Sprite: ZOMBIE
+# - Mob: EP14_MORS_MOB1
+# Sprite: GHOUL
+# - Mob: EP14_MORS_MOB2
+# Sprite: ZOMBIE_MASTER
+
+ # iRO Halloween Event 2009
+# - Mob: EP14_3_DEATH_B_MOB2
+# Sprite: WHISPER
+# - Mob: EP14_3_DEATH_B_MOB3
+# Sprite: DARK_LORD
diff --git a/db/mob_avail.txt b/db/mob_avail.txt
deleted file mode 100644
index 65b779c973..0000000000
--- a/db/mob_avail.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-// Mob Availability and Alias Database
-//
-// Structure of Database:
-// MobID,SpriteID{,Equipment}
-//
-// 01. MobID Mob ID to change.
-// 02. SpriteID Mob ID which will be sent to the client instead of MobID.
-// If 0, the mob becomes unavailable for use.
-// 03. Equipment Item ID of pet equipment (must be available for pet counterpart, or this will cause problems).
-//
-// To disguise a mob as a player:
-// MobID,SpriteID,Sex,Hair_Style,Hair_Color,Weapon,Shield,Head_Top,Head_Middle,Head_Bottom,Option,Dye_Color
-//
-// SpriteID is a job class value.
-// Weapon and Shield uses Item ID, while Head uses View ID.
-// Option for carts only works if you compiled your server for a packet version before 2012-02-01
-
-//1002,1039 // Poring - Baphomet
-//1970,1002,10013 // Displays a Poring with a backpack
-
-// Easter Event Monsters
-//1920,1047,0
-//1921,1093,0
-
-// rAthena Dev Team
-// Valaris
-//1900,4013,1,1,1,1254,0,67,12,54,16,1
-// Valaris Worshiper
-//1901,6,1,1,1,1219,2101,67,12,54,0,1
-// MC Cameri
-//1902,14,1,6,6,1101,2105,0,0,0,32,3
-// Poki#3
-//1903,4012,1,21,0,1720,0,102,184,57,16,0
-// Sentry
-//1904,1286,0
-
-// iRO Halloween Event 2008
-//3000,1015,0
-//3001,1036,0
-//3002,1298,0
-
-// iRO Halloween Event 2009
-//3014,1179,0
-//3015,1272,0
diff --git a/doc/mob_avail.txt b/doc/mob_avail.txt
new file mode 100644
index 0000000000..2d6207ae6d
--- /dev/null
+++ b/doc/mob_avail.txt
@@ -0,0 +1,108 @@
+//===== rAthena Documentation ================================
+//= rAthena Monster Availability Database Reference
+//===== By: ==================================================
+//= rAthena Dev Team
+//===== Last Updated: ========================================
+//= 20191213
+//===== Description: =========================================
+//= Explanation of the mob_avail.yml file and structure.
+//============================================================
+
+---------------------------------------
+
+Mob: The AEGIS name of the monster.
+
+---------------------------------------
+
+Sprite: The name of the sprite the monster will be changed to.
+
+This can be another mob, a player (prefixed with 'JOB_'), or an NPC. When using an NPC sprite,
+the prefix is not required in the mob_avail database as the script engine will strip it.
+
+Example:
+ - Mob: POPORING
+ Sprite: PORING # This will change the Poporing into a Poring.
+
+ - Mob: PORING
+ Sprite: JOB_STALKER # This will change the Poring into a Stalker.
+
+ - Mob: WOLF
+ Sprite: 4_M_BARBER # This will change the Wolf into the Barber NPC.
+
+These constants can be found in src/map/script_constants.hpp.
+
+---------------------------------------
+
+Sex: The sex to be displayed if the Sprite is a player.
+
+Valid types:
+ Female
+ Male
+
+---------------------------------------
+
+HairStyle: The hair style ID to be displayed if the Sprite is a player.
+
+---------------------------------------
+
+HairColor: The hair color ID to be displayed if the Sprite is a player.
+
+---------------------------------------
+
+ClothColor: The cloth color ID to be displayed if the Sprite is a player.
+
+---------------------------------------
+
+Weapon: The AEGIS name of the item to be displayed if the Sprite is a player.
+
+---------------------------------------
+
+Shield: The AEGIS name of the item to be displayed if the Sprite is a player.
+
+---------------------------------------
+
+HeadTop: The AEGIS name of the item to be displayed if the Sprite is a player.
+
+---------------------------------------
+
+HeadMid: The AEGIS name of the item to be displayed if the Sprite is a player.
+
+---------------------------------------
+
+HeadLow: The AEGIS name of the item to be displayed if the Sprite is a player.
+
+---------------------------------------
+
+PetEquip: The AEGIS name of the item to be displayed if the Mob is a valid pet.
+
+---------------------------------------
+
+Options: The view option to be applied to the Mob.
+
+Valid types:
+ Sight
+ Cart1
+ Falcon
+ Riding
+ Cart2
+ Cart3
+ Cart4
+ Cart5
+ Orcish
+ Wedding
+ Ruwach
+ Flying
+ Xmas
+ Transform
+ Summer
+ Dragon1
+ Wug
+ WugRider
+ MadoGear
+ Dragon2
+ Dragon3
+ Dragon4
+ Dragon5
+ Hanbok
+ Oktoberfest
+ Summer2
diff --git a/doc/script_commands.txt b/doc/script_commands.txt
index 62c1cbf51f..83deab0f5f 100644
--- a/doc/script_commands.txt
+++ b/doc/script_commands.txt
@@ -7979,6 +7979,8 @@ Parameters (indexes) for monsters are:
UMOB_ADELAY
UMOB_DMOTION
UMOB_TARGETID
+ UMOB_ROBE
+ UMOB_BODY2
-----
@@ -8152,7 +8154,6 @@ Parameter (indexes) for elementals are:
-----
Parameter (indexes) for NPCs are:
- UNPC_DISPLAY
UNPC_LEVEL
UNPC_HP
UNPC_MAXHP
@@ -8185,6 +8186,19 @@ Parameter (indexes) for NPCs are:
UNPC_AMOTION
UNPC_ADELAY
UNPC_DMOTION
+ UNPC_SEX
+ UNPC_CLASS
+ UNPC_HAIRSTYLE
+ UNPC_HAIRCOLOR
+ UNPC_HEADBOTTOM
+ UNPC_HEADMIDDLE
+ UNPC_HEADTOP
+ UNPC_CLOTHCOLOR
+ UNPC_SHIELD
+ UNPC_WEAPON
+ UNPC_ROBE
+ UNPC_BODY2
+ UNPC_DEADSIT
*Notes:
- *_SIZE: small (0); medium (1); large (2)
@@ -8201,12 +8215,14 @@ Parameter (indexes) for NPCs are:
- *_AMOTION: see doc/mob_db.txt
- *_ADELAY: see doc/mob_db.txt
- *_DMOTION: see doc/mob_db.txt
+ - *_BODY2: enable (1) the alternate display, or disable (0)
- UMOB_AI: none (0); attack (1); marine sphere (2); flora (3); zanzou (4); legion (5); faw (6)
- UMOB_SCOPTION: see the 'Variables' section at the top of this document
- UMOB_SLAVECPYMSTRMD: make the slave copy the master's mode (1), or not (0)
- - UNPC_PLUSALLSTAT: same as 'bAllStats'; increases/decreses all stats by given amount
+ - UNPC_PLUSALLSTAT: same as 'bAllStats'; increases/decreases all stats by given amount
+ - UNPC_DEADSIT: stand (0), dead (1), sit (2)
Example:
// Spawn some Porings and save the Game ID.
diff --git a/doc/yaml/db/mob_avail.yml b/doc/yaml/db/mob_avail.yml
new file mode 100644
index 0000000000..c482e847ec
--- /dev/null
+++ b/doc/yaml/db/mob_avail.yml
@@ -0,0 +1,22 @@
+###########################################################################
+# Mob Availability and Alias Database
+###########################################################################
+#
+# Mob Availability and Alias Settings
+#
+###########################################################################
+# - Mob Mob to adjust.
+# Sprite Sprite which will be sent to the client instead of Mob.
+# Sex Sex (if Sprite is a player). (Default: Female)
+# HairStyle Hair Style ID (if Sprite is a player). (Default: 0)
+# HairColor Hair Color ID (if Sprite is a player). (Default: 0)
+# ClothColor Cloth Color ID (if Sprite is a player). (Default: 0)
+# Weapon Item name of weapon (if Sprite is a player). (Default: 0)
+# Shield Item name of shield (if Sprite is a player). (Default: 0)
+# HeadTop Item name of headgear (if Sprite is a player). (Default: 0)
+# HeadMid Item name of headgear (if Sprite is a player). (Default: 0)
+# HeadLow Item name of headgear (if Sprite is a player). (Default: 0)
+# PetEquip Item name of pet equipment (if Mob is a valid pet). (Default: 0)
+# Options: Set an option for an object. (Optional)
+# : bool
+###########################################################################
diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index 6ef185ef61..6ce1c03c4a 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -283,7 +283,12 @@ static inline unsigned char clif_bl_type(struct block_list *bl) {
case BL_SKILL: return 0x3; //SKILL_TYPE
case BL_CHAT: return 0x4; //UNKNOWN_TYPE
case BL_MOB: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x5; //NPC_MOB_TYPE
- case BL_NPC: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x6; //NPC_EVT_TYPE
+ case BL_NPC:
+#if PACKETVER >= 20170726
+ return 0x6; //NPC_EVT_TYPE
+#else
+ return (pcdb_checkid(status_get_viewdata(bl)->class_) ? 0x0 : 0x6);
+#endif
case BL_PET: return pcdb_checkid(status_get_viewdata(bl)->class_)?0x0:0x7; //NPC_PET_TYPE
case BL_HOM: return 0x8; //NPC_HOM_TYPE
case BL_MER: return 0x9; //NPC_MERSOL_TYPE
@@ -1144,7 +1149,7 @@ static int clif_set_unit_idle(struct block_list* bl, unsigned char* buffer, bool
WBUFW(buf,53) = (sd ? sd->status.font : 0);
#endif
#if PACKETVER >= 20120221
- if ( battle_config.monster_hp_bars_info && !map_getmapflag(bl->m, MF_HIDEMOBHPBAR) && bl->type == BL_MOB && (status_get_hp(bl) < status_get_max_hp(bl)) ) {
+ if ( battle_config.monster_hp_bars_info && bl->type == BL_MOB && !map_getmapflag(bl->m, MF_HIDEMOBHPBAR) && (status_get_hp(bl) < status_get_max_hp(bl)) ) {
WBUFL(buf,55) = status_get_max_hp(bl); // maxHP
WBUFL(buf,59) = status_get_hp(bl); // HP
} else {
@@ -1447,7 +1452,7 @@ int clif_spawn(struct block_list *bl)
if(bl->type == BL_NPC && !((TBL_NPC*)bl)->chat_id && (((TBL_NPC*)bl)->sc.option&OPTION_INVISIBLE))
return 0;
- len = clif_set_unit_idle(bl, buf,true);
+ len = clif_set_unit_idle(bl, buf, (bl->type == BL_NPC && vd->dead_sit ? false : true));
clif_send(buf, len, bl, AREA_WOS);
if (disguised(bl))
clif_setdisguise(bl, buf, len);
@@ -3606,7 +3611,7 @@ void clif_changelook(struct block_list *bl, int type, int val) {
#if PACKETVER < 20150513
return;
#else
- if (val && sd->sc.option&OPTION_COSTUME)
+ if (val && sd && sd->sc.option&OPTION_COSTUME)
val = 0;
vd->body_style = val;
#endif
@@ -3620,17 +3625,19 @@ void clif_changelook(struct block_list *bl, int type, int val) {
#if PACKETVER < 4
clif_sprite_change(bl, bl->id, type, val, 0, target);
#else
- if(type == LOOK_WEAPON || type == LOOK_SHIELD) {
- nullpo_retv(vd);
- type = LOOK_WEAPON;
- val = vd->weapon;
- val2 = vd->shield;
- }
- if( disguised(bl) ) {
- clif_sprite_change(bl, bl->id, type, val, val2, AREA_WOS);
- clif_sprite_change(bl, -bl->id, type, val, val2, SELF);
+ if (bl->type != BL_NPC) {
+ if (type == LOOK_WEAPON || type == LOOK_SHIELD) {
+ type = LOOK_WEAPON;
+ val = (vd ? vd->weapon : 0);
+ val2 = (vd ? vd->shield : 0);
+ }
+ if (disguised(bl)) {
+ clif_sprite_change(bl, bl->id, type, val, val2, AREA_WOS);
+ clif_sprite_change(bl, -bl->id, type, val, val2, SELF);
+ } else
+ clif_sprite_change(bl, bl->id, type, val, val2, target);
} else
- clif_sprite_change(bl, bl->id, type, val, val2, target);
+ unit_refresh(bl);
#endif
}
diff --git a/src/map/map-server.vcxproj b/src/map/map-server.vcxproj
index 6508693a2a..92cc353bad 100644
--- a/src/map/map-server.vcxproj
+++ b/src/map/map-server.vcxproj
@@ -340,7 +340,7 @@
-
+
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
index c17e2f740f..e74e7a61e6 100644
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -4348,55 +4348,308 @@ static int mob_read_sqldb(void)
return 0;
}
-/*==========================================
- * MOB display graphic change data reading
- *------------------------------------------*/
-static bool mob_readdb_mobavail(char* str[], int columns, int current)
-{
- int mob_id, sprite_id;
- struct mob_db *db;
+const std::string MobAvailDatabase::getDefaultLocation() {
+ return std::string(db_path) + "/" + DBIMPORT + "/mob_avail.yml";
+}
- mob_id = atoi(str[0]);
+/**
+ * Reads and parses an entry from the mob_avail.
+ * @param node: YAML node containing the entry.
+ * @return count of successfully parsed rows
+ */
+uint64 MobAvailDatabase::parseBodyNode(const YAML::Node &node) {
+ std::string mob_name;
- if( (db=mob_db(mob_id)) == NULL) // invalid class (probably undefined in db)
- {
- ShowWarning("mob_readdb_mobavail: Unknown mob id %d.\n", mob_id);
- return false;
+ if (!this->asString(node, "Mob", mob_name))
+ return 0;
+
+ uint32 mob_id = mobdb_searchname(mob_name.c_str());
+
+ if (mob_id == 0) {
+ this->invalidWarning(node["Mob"], "Unknown mob %s.\n", mob_name.c_str());
+ return 0;
}
- sprite_id = atoi(str[1]);
+ struct mob_db *mob = mob_db(mob_id);
- memset(&db->vd, 0, sizeof(struct view_data));
- db->vd.class_ = sprite_id;
+ if (this->nodeExists(node, "Sprite")) {
+ std::string sprite;
- //Player sprites
- if(pcdb_checkid(sprite_id) && columns==12) {
- db->vd.sex=atoi(str[2]);
- db->vd.hair_style=atoi(str[3]);
- db->vd.hair_color=atoi(str[4]);
- db->vd.weapon=atoi(str[5]);
- db->vd.shield=atoi(str[6]);
- db->vd.head_top=atoi(str[7]);
- db->vd.head_mid=atoi(str[8]);
- db->vd.head_bottom=atoi(str[9]);
- db->option=atoi(str[10])&~(OPTION_HIDE|OPTION_CLOAK|OPTION_INVISIBLE);
- db->vd.cloth_color=atoi(str[11]); // Monster player dye option - Valaris
+ if (!this->asString(node, "Sprite", sprite))
+ return 0;
+
+ int constant;
+
+ if (script_get_constant(sprite.c_str(), &constant)) {
+ if (npcdb_checkid(constant) == 0 && pcdb_checkid(constant) == 0) {
+ this->invalidWarning(node["Sprite"], "Unknown sprite constant %s.\n", sprite.c_str());
+ return 0;
+ }
+ } else {
+ constant = mobdb_searchname(sprite.c_str());
+
+ if (constant == 0) {
+ this->invalidWarning(node["Sprite"], "Unknown mob sprite constant %s.\n", sprite.c_str());
+ return 0;
+ }
+ }
+
+ mob->vd.class_ = constant;
+ } else {
+ this->invalidWarning(node["Sprite"], "Sprite is missing.\n");
+ return 0;
+ }
+
+ if (this->nodeExists(node, "Sex")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["Sex"], "Sex is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ std::string sex;
+
+ if (!this->asString(node, "Sex", sex))
+ return 0;
+
+ std::string sex_constant = "SEX_" + sex;
+
+ int constant;
+
+ if (!script_get_constant(sex_constant.c_str(), &constant)) {
+ this->invalidWarning(node["Sex"], "Unknown sex constant %s.\n", sex.c_str());
+ return 0;
+ }
+
+ if (constant < SEX_FEMALE || constant > SEX_MALE) {
+ this->invalidWarning(node["Sex"], "Sex %s is not valid.\n", sex.c_str());
+ return 0;
+ }
+
+ mob->vd.sex = constant;
+ }
+
+ if (this->nodeExists(node, "HairStyle")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["HairStyle"], "HairStyle is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ uint16 hair_style;
+
+ if (!this->asUInt16(node, "HairStyle", hair_style))
+ return 0;
+
+ if (hair_style < MIN_HAIR_STYLE || hair_style > MAX_HAIR_STYLE) {
+ this->invalidWarning(node["HairStyle"], "HairStyle %d is out of range %d~%d. Setting to MIN_HAIR_STYLE.\n", hair_style, MIN_HAIR_STYLE, MAX_HAIR_STYLE);
+ hair_style = MIN_HAIR_STYLE;
+ }
+
+ mob->vd.hair_style = hair_style;
+ }
+
+ if (this->nodeExists(node, "HairColor")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["HairColor"], "HairColor is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ uint16 hair_color;
+
+ if (!this->asUInt16(node, "HairColor", hair_color))
+ return 0;
+
+ if (hair_color < MIN_HAIR_COLOR || hair_color > MAX_HAIR_COLOR) {
+ this->invalidWarning(node["HairColor"], "HairColor %d is out of range %d~%d. Setting to MIN_HAIR_COLOR.\n", hair_color, MIN_HAIR_COLOR, MAX_HAIR_COLOR);
+ hair_color = MIN_HAIR_COLOR;
+ }
+
+ mob->vd.hair_color = hair_color;
+ }
+
+ if (this->nodeExists(node, "ClothColor")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["ClothColor"], "ClothColor is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ uint32 cloth_color;
+
+ if (!this->asUInt32(node, "ClothColor", cloth_color))
+ return 0;
+
+ if (cloth_color < MIN_CLOTH_COLOR || cloth_color > MAX_CLOTH_COLOR) {
+ this->invalidWarning(node["ClothColor"], "ClothColor %d is out of range %d~%d. Setting to MIN_CLOTH_CLOR.\n", cloth_color, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR);
+ cloth_color = MIN_CLOTH_COLOR;
+ }
+
+ mob->vd.cloth_color = cloth_color;
+ }
+
+ if (this->nodeExists(node, "Weapon")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["Weapon"], "Weapon is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ std::string weapon;
+
+ if (!this->asString(node, "Weapon", weapon))
+ return 0;
+
+ struct item_data *item = itemdb_searchname(weapon.c_str());
+
+ if (item == nullptr) {
+ this->invalidWarning(node["Weapon"], "Weapon %s is not a valid item.\n", weapon.c_str());
+ return 0;
+ }
+
+ mob->vd.weapon = item->nameid;
+ }
+
+ if (this->nodeExists(node, "Shield")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["Shield"], "Shield is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ std::string shield;
+
+ if (!this->asString(node, "Shield", shield))
+ return 0;
+
+ struct item_data *item = itemdb_searchname(shield.c_str());
+
+ if (item == nullptr) {
+ this->invalidWarning(node["Shield"], "Shield %s is not a valid item.\n", shield.c_str());
+ return 0;
+ }
+
+ mob->vd.shield = item->nameid;
+ }
+
+ if (this->nodeExists(node, "HeadTop")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["HeadTop"], "HeadTop is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ std::string head;
+
+ if (!this->asString(node, "HeadTop", head))
+ return 0;
+
+ struct item_data *item;
+
+ if ((item = itemdb_searchname(head.c_str())) == nullptr) {
+ this->invalidWarning(node["HeadTop"], "HeadTop %s is not a valid item.\n", head.c_str());
+ return 0;
+ }
+
+ mob->vd.head_top = item->look;
+ }
+
+ if (this->nodeExists(node, "HeadMid")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["HeadMid"], "HeadMid is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ std::string head;
+
+ if (!this->asString(node, "HeadMid", head))
+ return 0;
+
+ struct item_data *item = itemdb_searchname(head.c_str());
+
+ if (item == nullptr) {
+ this->invalidWarning(node["HeadMid"], "HeadMid %s is not a valid item.\n", head.c_str());
+ return 0;
+ }
+
+ mob->vd.head_mid = item->look;
+ }
+
+ if (this->nodeExists(node, "HeadLow")) {
+ if (pcdb_checkid(mob->vd.class_) == 0) {
+ this->invalidWarning(node["HeadLow"], "HeadLow is only applicable to Job sprites.\n");
+ return 0;
+ }
+
+ std::string head;
+
+ if (!this->asString(node, "HeadLow", head))
+ return 0;
+
+ struct item_data *item = itemdb_searchname(head.c_str());
+
+ if (item == nullptr) {
+ this->invalidWarning(node["HeadLow"], "HeadLow %s is not a valid item.\n", head.c_str());
+ return 0;
+ }
+
+ mob->vd.head_bottom = item->look;
+ }
+
+ if (this->nodeExists(node, "PetEquip")) {
+ std::shared_ptr pet_db_ptr = pet_db.find(mob->vd.class_);
+
+ if (pet_db_ptr == nullptr) {
+ this->invalidWarning(node["PetEquip"], "PetEquip is only applicable to defined pets.\n");
+ return 0;
+ }
+
+ std::string equipment;
+
+ if (!this->asString(node, "PetEquip", equipment))
+ return 0;
+
+ struct item_data *item = itemdb_searchname(equipment.c_str());
+
+ if (item == nullptr) {
+ this->invalidWarning(node["PetEquip"], "PetEquip %s is not a valid item.\n", equipment.c_str());
+ return 0;
+ }
+
+ mob->vd.head_bottom = item->nameid;
+ }
+
+ if (this->nodeExists(node, "Options")) {
+ for (const auto &optionNode : node["Options"]) {
+ std::string option = optionNode.first.as();
+ std::string option_constant = "OPTION_" + option;
+ int constant;
+
+ if (!script_get_constant(option_constant.c_str(), &constant)) {
+ this->invalidWarning(optionNode, "Unknown option constant %s, skipping.\n", option.c_str());
+ continue;
+ }
+
+ bool active;
+
+ if (!this->asBool(node, option, active))
+ continue;
#ifdef NEW_CARTS
- if( db->option & OPTION_CART ){
- ShowWarning("mob_readdb_mobavail: You tried to use a cart for mob id %d. This does not work with setting an option anymore.\n", mob_id );
- db->option &= ~OPTION_CART;
- }
+ if (constant & OPTION_CART) {
+ this->invalidWarning(optionNode, "OPTION_CART was replace by SC_PUSH_CART, skipping.\n");
+ continue;
+ }
#endif
- }
- else if(columns==3)
- db->vd.head_bottom=atoi(str[2]); // mob equipment [Valaris]
- else if( columns != 2 )
- return false;
- return true;
+ if (active)
+ mob->option |= constant;
+ else
+ mob->option &= ~constant;
+ }
+
+ mob->option &= ~(OPTION_HIDE | OPTION_CLOAK | OPTION_INVISIBLE | OPTION_CHASEWALK); // Remove hiding types
+ }
+
+ return 1;
}
+MobAvailDatabase mob_avail_db;
+
/*==========================================
* Reading of random monster data
* MobGroup,MobID,DummyName,Rate
@@ -5282,7 +5535,6 @@ static void mob_load(void)
mob_readskilldb(dbsubpath2, silent);
}
- sv_readdb(dbsubpath1, "mob_avail.txt", ',', 2, 12, -1, &mob_readdb_mobavail,silent);
sv_readdb(dbsubpath2, "mob_race2_db.txt", ',', 2, MAX_RACE2_MOBS, -1, &mob_readdb_race2, silent);
sv_readdb(dbsubpath1, "mob_item_ratio.txt", ',', 2, 2+MAX_ITEMRATIO_MOBS, -1, &mob_readdb_itemratio, silent);
sv_readdb(dbsubpath1, "mob_chat_db.txt", '#', 3, 3, -1, &mob_parse_row_chatdb, silent);
@@ -5299,6 +5551,8 @@ static void mob_load(void)
aFree(dbsubpath2);
}
+ mob_avail_db.load();
+
mob_drop_ratio_adjust();
mob_skill_db_set();
}
@@ -5392,15 +5646,12 @@ static int mob_reload_sub( struct mob_data *md, va_list args ){
static int mob_reload_sub_npc( struct npc_data *nd, va_list args ){
// If the view data points to a mob
if( mobdb_checkid(nd->class_) ){
- // Get the new view data from the mob database
- nd->vd = mob_get_viewdata(nd->class_);
+ struct view_data *vd = mob_get_viewdata(nd->class_);
- // If they are spawned right now
- if( nd->bl.prev != NULL ){
- // Respawn all NPCs on client side so that they are displayed correctly(if their view id changed)
- clif_clearunit_area(&nd->bl, CLR_OUTSIGHT);
- clif_spawn(&nd->bl);
- }
+ if (vd) // Get the new view data from the mob database
+ memcpy(&nd->vd, vd, sizeof(struct view_data));
+ if (nd->bl.prev) // If they are spawned right now
+ unit_refresh(&nd->bl); // Respawn all NPCs on client side so that they are displayed correctly(if their view id changed)
}
return 0;
diff --git a/src/map/mob.hpp b/src/map/mob.hpp
index 54b886f8a3..f9e3a8b846 100644
--- a/src/map/mob.hpp
+++ b/src/map/mob.hpp
@@ -6,6 +6,7 @@
#include
+#include "../common/database.hpp"
#include "../common/mmo.hpp" // struct item
#include "../common/timer.hpp"
@@ -243,6 +244,17 @@ struct mob_data {
int tomb_nid;
};
+class MobAvailDatabase : public YamlDatabase {
+public:
+ MobAvailDatabase() : YamlDatabase("MOB_AVAIL_DB", 1) {
+
+ }
+
+ void clear() { };
+ const std::string getDefaultLocation();
+ uint64 parseBodyNode(const YAML::Node& node);
+};
+
enum e_mob_skill_target {
MST_TARGET = 0,
MST_RANDOM, //Random Target!
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index c33c414a80..fb18e62373 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -118,8 +118,12 @@ struct script_event_s{
// Holds pointers to the commonly executed scripts for speedup. [Skotlex]
std::map> script_event;
-struct view_data* npc_get_viewdata(int class_)
-{ //Returns the viewdata for normal npc classes.
+/**
+ * Returns the viewdata for normal NPC classes.
+ * @param class_: NPC class ID
+ * @return viewdata or nullptr if the ID is invalid
+ */
+struct view_data* npc_get_viewdata(int class_) {
if( class_ == JT_INVISIBLE )
return &npc_viewdb[0];
if (npcdb_checkid(class_)){
@@ -129,7 +133,7 @@ struct view_data* npc_get_viewdata(int class_)
return &npc_viewdb[class_];
}
}
- return NULL;
+ return nullptr;
}
int npc_isnear_sub(struct block_list* bl, va_list args) {
@@ -2556,17 +2560,18 @@ int npc_parseview(const char* w4, const char* start, const char* buffer, const c
* @return npc_data
*/
struct npc_data *npc_create_npc(int16 m, int16 x, int16 y){
- struct npc_data *nd;
+ struct npc_data *nd = nullptr;
CREATE(nd, struct npc_data, 1);
nd->bl.id = npc_get_new_npc_id();
- nd->bl.prev = nd->bl.next = NULL;
+ nd->bl.prev = nd->bl.next = nullptr;
nd->bl.m = m;
nd->bl.x = x;
nd->bl.y = y;
- nd->sc_display = NULL;
+ nd->sc_display = nullptr;
nd->sc_display_count = 0;
nd->progressbar.timeout = 0;
+ nd->vd = npc_viewdb[0]; // Default to JT_INVISIBLE
return nd;
}
@@ -3749,14 +3754,9 @@ void npc_setclass(struct npc_data* nd, short class_)
if( nd->class_ == class_ )
return;
- struct map_data *mapdata = map_getmapdata(nd->bl.m);
-
- if( mapdata->users )
- clif_clearunit_area(&nd->bl, CLR_OUTSIGHT);// fade out
nd->class_ = class_;
status_set_viewdata(&nd->bl, class_);
- if( mapdata->users )
- clif_spawn(&nd->bl);// fade in
+ unit_refresh(&nd->bl);
}
// @commands (script based)
diff --git a/src/map/npc.hpp b/src/map/npc.hpp
index 06b0f18bb6..e00fa759e7 100644
--- a/src/map/npc.hpp
+++ b/src/map/npc.hpp
@@ -41,8 +41,8 @@ struct s_npc_buy_list {
struct npc_data {
struct block_list bl;
- struct unit_data ud; //Because they need to be able to move....
- struct view_data *vd;
+ struct unit_data ud; //Because they need to be able to move....
+ struct view_data vd;
struct status_change sc; //They can't have status changes, but.. they want the visual opt values.
struct npc_data *master_nd;
short class_,speed,instance_id;
diff --git a/src/map/script.cpp b/src/map/script.cpp
index 43fe92c87c..6fb9decdd8 100644
--- a/src/map/script.cpp
+++ b/src/map/script.cpp
@@ -16542,10 +16542,7 @@ BUILDIN_FUNC(setnpcdisplay)
if( class_ != JT_FAKENPC && nd->class_ != class_ )
npc_setclass(nd, class_);
else if( size != -1 )
- { // Required to update the visual size
- clif_clearunit_area(&nd->bl, CLR_OUTSIGHT);
- clif_spawn(&nd->bl);
- }
+ unit_refresh(&nd->bl); // Required to update the visual size
script_pushint(st,0);
return SCRIPT_CMD_SUCCESS;
@@ -17609,6 +17606,8 @@ BUILDIN_FUNC(getunitdata)
getunitdata_sub(UMOB_ADELAY, md->status.adelay);
getunitdata_sub(UMOB_DMOTION, md->status.dmotion);
getunitdata_sub(UMOB_TARGETID, md->target_id);
+ getunitdata_sub(UMOB_ROBE, md->vd->robe);
+ getunitdata_sub(UMOB_BODY2, md->vd->body_style);
break;
case BL_HOM:
@@ -17797,7 +17796,6 @@ BUILDIN_FUNC(getunitdata)
ShowWarning("buildin_getunitdata: Error in finding object BL_NPC!\n");
return SCRIPT_CMD_FAILURE;
}
- getunitdata_sub(UNPC_DISPLAY, nd->class_);
getunitdata_sub(UNPC_LEVEL, nd->level);
getunitdata_sub(UNPC_HP, nd->status.hp);
getunitdata_sub(UNPC_MAXHP, nd->status.max_hp);
@@ -17830,6 +17828,19 @@ BUILDIN_FUNC(getunitdata)
getunitdata_sub(UNPC_AMOTION, nd->status.amotion);
getunitdata_sub(UNPC_ADELAY, nd->status.adelay);
getunitdata_sub(UNPC_DMOTION, nd->status.dmotion);
+ getunitdata_sub(UNPC_SEX, nd->vd.sex);
+ getunitdata_sub(UNPC_CLASS, nd->vd.class_);
+ getunitdata_sub(UNPC_HAIRSTYLE, nd->vd.hair_style);
+ getunitdata_sub(UNPC_HAIRCOLOR, nd->vd.hair_color);
+ getunitdata_sub(UNPC_HEADBOTTOM, nd->vd.head_bottom);
+ getunitdata_sub(UNPC_HEADMIDDLE, nd->vd.head_mid);
+ getunitdata_sub(UNPC_HEADTOP, nd->vd.head_top);
+ getunitdata_sub(UNPC_CLOTHCOLOR, nd->vd.cloth_color);
+ getunitdata_sub(UNPC_SHIELD, nd->vd.shield);
+ getunitdata_sub(UNPC_WEAPON, nd->vd.weapon);
+ getunitdata_sub(UNPC_ROBE, nd->vd.robe);
+ getunitdata_sub(UNPC_BODY2, nd->vd.body_style);
+ getunitdata_sub(UNPC_DEADSIT, nd->vd.dead_sit);
break;
default:
@@ -17912,6 +17923,8 @@ BUILDIN_FUNC(setunitdata)
case UMOB_CLOTHCOLOR:
case UMOB_SHIELD:
case UMOB_WEAPON:
+ case UMOB_ROBE:
+ case UMOB_BODY2:
mob_set_dynamic_viewdata( md );
break;
}
@@ -17929,8 +17942,8 @@ BUILDIN_FUNC(setunitdata)
case UMOB_MODE: md->base_status->mode = (enum e_mode)value; calc_status = true; break;
case UMOB_AI: md->special_state.ai = (enum mob_ai)value; break;
case UMOB_SCOPTION: md->sc.option = (unsigned short)value; break;
- case UMOB_SEX: md->vd->sex = (char)value; clif_clearunit_area(bl, CLR_OUTSIGHT); clif_spawn(bl); break;
- case UMOB_CLASS: status_set_viewdata(bl, (unsigned short)value); clif_clearunit_area(bl, CLR_OUTSIGHT); clif_spawn(bl); break;
+ case UMOB_SEX: md->vd->sex = (char)value; unit_refresh(bl); break;
+ case UMOB_CLASS: status_set_viewdata(bl, (unsigned short)value); unit_refresh(bl); break;
case UMOB_HAIRSTYLE: clif_changelook(bl, LOOK_HAIR, (unsigned short)value); break;
case UMOB_HAIRCOLOR: clif_changelook(bl, LOOK_HAIR_COLOR, (unsigned short)value); break;
case UMOB_HEADBOTTOM: clif_changelook(bl, LOOK_HEAD_BOTTOM, (unsigned short)value); break;
@@ -17987,6 +18000,8 @@ BUILDIN_FUNC(setunitdata)
mob_target(md,target,0);
break;
}
+ case UMOB_ROBE: clif_changelook(bl, LOOK_ROBE, (unsigned short)value); break;
+ case UMOB_BODY2: clif_changelook(bl, LOOK_BODY2, (unsigned short)value); break;
default:
ShowError("buildin_setunitdata: Unknown data identifier %d for BL_MOB.\n", type);
return SCRIPT_CMD_FAILURE;
@@ -18233,7 +18248,6 @@ BUILDIN_FUNC(setunitdata)
return SCRIPT_CMD_FAILURE;
}
switch (type) {
- case UNPC_DISPLAY: status_set_viewdata(bl, (unsigned short)value); break;
case UNPC_LEVEL: nd->level = (unsigned int)value; break;
case UNPC_HP: nd->status.hp = (unsigned int)value; status_set_hp(bl, (unsigned int)value, 0); break;
case UNPC_MAXHP: nd->status.hp = nd->status.max_hp = (unsigned int)value; status_set_maxhp(bl, (unsigned int)value, 0); break;
@@ -18265,6 +18279,19 @@ BUILDIN_FUNC(setunitdata)
case UNPC_AMOTION: nd->status.amotion = (short)value; break;
case UNPC_ADELAY: nd->status.adelay = (short)value; break;
case UNPC_DMOTION: nd->status.dmotion = (short)value; break;
+ case UNPC_SEX: nd->vd.sex = (char)value; unit_refresh(bl); break;
+ case UNPC_CLASS: npc_setclass(nd, (short)value); break;
+ case UNPC_HAIRSTYLE: clif_changelook(bl, LOOK_HAIR, (unsigned short)value); break;
+ case UNPC_HAIRCOLOR: clif_changelook(bl, LOOK_HAIR_COLOR, (unsigned short)value); break;
+ case UNPC_HEADBOTTOM: clif_changelook(bl, LOOK_HEAD_BOTTOM, (unsigned short)value); break;
+ case UNPC_HEADMIDDLE: clif_changelook(bl, LOOK_HEAD_MID, (unsigned short)value); break;
+ case UNPC_HEADTOP: clif_changelook(bl, LOOK_HEAD_TOP, (unsigned short)value); break;
+ case UNPC_CLOTHCOLOR: clif_changelook(bl, LOOK_CLOTHES_COLOR, (unsigned short)value); break;
+ case UNPC_SHIELD: clif_changelook(bl, LOOK_SHIELD, (unsigned short)value); break;
+ case UNPC_WEAPON: clif_changelook(bl, LOOK_WEAPON, (unsigned short)value); break;
+ case UNPC_ROBE: clif_changelook(bl, LOOK_ROBE, (unsigned short)value); break;
+ case UNPC_BODY2: clif_changelook(bl, LOOK_BODY2, (unsigned short)value); break;
+ case UNPC_DEADSIT: nd->vd.dead_sit = (char)value; unit_refresh(bl); break;
default:
ShowError("buildin_setunitdata: Unknown data identifier %d for BL_NPC.\n", type);
return SCRIPT_CMD_FAILURE;
diff --git a/src/map/script.hpp b/src/map/script.hpp
index 0ed8c8caca..04014386d2 100644
--- a/src/map/script.hpp
+++ b/src/map/script.hpp
@@ -476,6 +476,8 @@ enum unitdata_mobtypes {
UMOB_ADELAY,
UMOB_DMOTION,
UMOB_TARGETID,
+ UMOB_ROBE,
+ UMOB_BODY2,
};
enum unitdata_homuntypes {
@@ -644,8 +646,7 @@ enum unitdata_elemtypes {
};
enum unitdata_npctypes {
- UNPC_DISPLAY = 0,
- UNPC_LEVEL,
+ UNPC_LEVEL = 0,
UNPC_HP,
UNPC_MAXHP,
UNPC_MAPID,
@@ -677,6 +678,19 @@ enum unitdata_npctypes {
UNPC_AMOTION,
UNPC_ADELAY,
UNPC_DMOTION,
+ UNPC_SEX,
+ UNPC_CLASS,
+ UNPC_HAIRSTYLE,
+ UNPC_HAIRCOLOR,
+ UNPC_HEADBOTTOM,
+ UNPC_HEADMIDDLE,
+ UNPC_HEADTOP,
+ UNPC_CLOTHCOLOR,
+ UNPC_SHIELD,
+ UNPC_WEAPON,
+ UNPC_ROBE,
+ UNPC_BODY2,
+ UNPC_DEADSIT,
};
enum navigation_service {
diff --git a/src/map/script_constants.hpp b/src/map/script_constants.hpp
index 70dd2b0715..cb11a70364 100644
--- a/src/map/script_constants.hpp
+++ b/src/map/script_constants.hpp
@@ -3964,9 +3964,14 @@
export_constant(OPTION_SIGHT);
export_constant(OPTION_HIDE);
export_constant(OPTION_CLOAK);
+ export_constant(OPTION_CART1);
export_constant(OPTION_FALCON);
export_constant(OPTION_RIDING);
export_constant(OPTION_INVISIBLE);
+ export_constant(OPTION_CART2);
+ export_constant(OPTION_CART3);
+ export_constant(OPTION_CART4);
+ export_constant(OPTION_CART5);
export_constant(OPTION_ORCISH);
export_constant(OPTION_WEDDING);
export_constant(OPTION_RUWACH);
@@ -3985,6 +3990,7 @@
export_constant(OPTION_DRAGON5);
export_constant(OPTION_HANBOK);
export_constant(OPTION_OKTOBERFEST);
+ export_constant(OPTION_SUMMER2);
/* status option compounds */
export_constant(OPTION_DRAGON);
@@ -4059,6 +4065,8 @@
export_constant(UMOB_ADELAY);
export_constant(UMOB_DMOTION);
export_constant(UMOB_TARGETID);
+ export_constant(UMOB_ROBE);
+ export_constant(UMOB_BODY2);
/* unit control - homunculus */
export_constant(UHOM_SIZE);
@@ -4222,7 +4230,7 @@
export_constant(UELE_TARGETID);
/* unit control - NPC */
- export_constant(UNPC_DISPLAY);
+ export_deprecated_constant3("UNPC_DISPLAY", UNPC_CLASS, "UNPC_CLASS");
export_constant(UNPC_LEVEL);
export_constant(UNPC_HP);
export_constant(UNPC_MAXHP);
@@ -4255,6 +4263,19 @@
export_constant(UNPC_AMOTION);
export_constant(UNPC_ADELAY);
export_constant(UNPC_DMOTION);
+ export_constant(UNPC_SEX);
+ export_constant(UNPC_CLASS);
+ export_constant(UNPC_HAIRSTYLE);
+ export_constant(UNPC_HAIRCOLOR);
+ export_constant(UNPC_HEADBOTTOM);
+ export_constant(UNPC_HEADMIDDLE);
+ export_constant(UNPC_HEADTOP);
+ export_constant(UNPC_CLOTHCOLOR);
+ export_constant(UNPC_SHIELD);
+ export_constant(UNPC_WEAPON);
+ export_constant(UNPC_ROBE);
+ export_constant(UNPC_BODY2);
+ export_constant(UNPC_DEADSIT);
export_constant(NAV_NONE);
export_constant(NAV_AIRSHIP_ONLY);
@@ -4954,7 +4975,9 @@
/* NPC view ids */
// Special macro to strip the prefix 'JT_'
- #define export_constant_npc(a) export_constant_offset(a,3)
+ #if !defined(export_constant_npc)
+ #define export_constant_npc(a) export_constant_offset(a,3)
+ #endif
export_constant_npc(JT_WARPNPC);
export_constant_npc(JT_1_ETC_01);
diff --git a/src/map/status.cpp b/src/map/status.cpp
index 97bd7ab091..d35a249129 100644
--- a/src/map/status.cpp
+++ b/src/map/status.cpp
@@ -7797,7 +7797,7 @@ struct view_data* status_get_viewdata(struct block_list *bl)
case BL_PC: return &((TBL_PC*)bl)->vd;
case BL_MOB: return ((TBL_MOB*)bl)->vd;
case BL_PET: return &((TBL_PET*)bl)->vd;
- case BL_NPC: return ((TBL_NPC*)bl)->vd;
+ case BL_NPC: return &((TBL_NPC*)bl)->vd;
case BL_HOM: return ((TBL_HOM*)bl)->vd;
case BL_MER: return ((TBL_MER*)bl)->vd;
case BL_ELEM: return ((TBL_ELEM*)bl)->vd;
@@ -7861,10 +7861,10 @@ void status_set_viewdata(struct block_list *bl, int class_)
sd->vd.head_top = sd->status.head_top;
sd->vd.head_mid = sd->status.head_mid;
sd->vd.head_bottom = sd->status.head_bottom;
- sd->vd.hair_style = cap_value(sd->status.hair,0,battle_config.max_hair_style);
- sd->vd.hair_color = cap_value(sd->status.hair_color,0,battle_config.max_hair_color);
- sd->vd.cloth_color = cap_value(sd->status.clothes_color,0,battle_config.max_cloth_color);
- sd->vd.body_style = cap_value(sd->status.body,0,battle_config.max_body_style);
+ sd->vd.hair_style = cap_value(sd->status.hair, MIN_HAIR_STYLE, MAX_HAIR_STYLE);
+ sd->vd.hair_color = cap_value(sd->status.hair_color, MIN_HAIR_COLOR, MAX_HAIR_COLOR);
+ sd->vd.cloth_color = cap_value(sd->status.clothes_color, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR);
+ sd->vd.body_style = cap_value(sd->status.body, MIN_BODY_STYLE, MAX_BODY_STYLE);
sd->vd.sex = sd->status.sex;
if (sd->vd.cloth_color) {
@@ -7898,6 +7898,8 @@ void status_set_viewdata(struct block_list *bl, int class_)
mob_set_dynamic_viewdata( md );
md->vd->class_ = class_;
+ md->vd->hair_style = cap_value(md->vd->hair_style, MIN_HAIR_STYLE, MAX_HAIR_STYLE);
+ md->vd->hair_color = cap_value(md->vd->hair_color, MIN_HAIR_COLOR, MAX_HAIR_COLOR);
}else
ShowError("status_set_viewdata (MOB): No view data for class %d\n", class_);
}
@@ -7923,15 +7925,18 @@ void status_set_viewdata(struct block_list *bl, int class_)
{
TBL_NPC* nd = (TBL_NPC*)bl;
if (vd)
- nd->vd = vd;
- else {
- ShowError("status_set_viewdata (NPC): No view data for class %d\n", class_);
+ memcpy(&nd->vd, vd, sizeof(struct view_data));
+ else if (pcdb_checkid(class_)) {
+ memset(&nd->vd, 0, sizeof(struct view_data));
+ nd->vd.class_ = class_;
+ nd->vd.hair_style = cap_value(nd->vd.hair_style, MIN_HAIR_STYLE, MAX_HAIR_STYLE);
+ } else {
if (bl->m >= 0)
ShowDebug("Source (NPC): %s at %s (%d,%d)\n", nd->name, map_mapid2mapname(bl->m), bl->x, bl->y);
else
ShowDebug("Source (NPC): %s (invisible/not on a map)\n", nd->name);
- break;
}
+ break;
}
break;
case BL_HOM:
diff --git a/src/map/unit.cpp b/src/map/unit.cpp
index b4c744dd58..a9114a217c 100644
--- a/src/map/unit.cpp
+++ b/src/map/unit.cpp
@@ -3134,6 +3134,26 @@ int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file,
return 1;
}
+/**
+ * Refresh the area with a change in display of a unit.
+ * @bl: Object to update
+ */
+void unit_refresh(struct block_list *bl) {
+ nullpo_retv(bl);
+
+ if (bl->m < 0)
+ return;
+
+ struct map_data *mapdata = map_getmapdata(bl->m);
+
+ // Using CLR_TRICKDEAD because other flags show effects
+ // Probably need to use another flag or other way to refresh it
+ if (mapdata->users) {
+ clif_clearunit_area(bl, CLR_TRICKDEAD); // Fade out
+ clif_spawn(bl); // Fade in
+ }
+}
+
/**
* Removes units of a master when the master is removed from map
* @param sd: Player
diff --git a/src/map/unit.hpp b/src/map/unit.hpp
index eb53dda885..fa559de7ab 100644
--- a/src/map/unit.hpp
+++ b/src/map/unit.hpp
@@ -79,7 +79,7 @@ unsigned short
cloth_color,
body_style;
char sex;
- unsigned dead_sit : 2;
+ unsigned dead_sit : 2; // 0: Standing, 1: Dead, 2: Sitting
};
/// Enum for unit_blown_immune
@@ -161,6 +161,7 @@ void unit_dataset(struct block_list *bl);
// Remove unit
struct unit_data* unit_bl2ud(struct block_list *bl);
void unit_remove_map_pc(struct map_session_data *sd, clr_type clrtype);
+void unit_refresh(struct block_list *bl);
void unit_free_pc(struct map_session_data *sd);
#define unit_remove_map(bl,clrtype) unit_remove_map_(bl,clrtype,__FILE__,__LINE__,__func__)
int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file, int line, const char* func);
diff --git a/src/tool/csv2yaml.cpp b/src/tool/csv2yaml.cpp
index 67dba63981..7e7077de56 100644
--- a/src/tool/csv2yaml.cpp
+++ b/src/tool/csv2yaml.cpp
@@ -43,9 +43,9 @@
#include "../map/npc.hpp"
#include "../map/pc.hpp"
#include "../map/pet.hpp"
+#include "../map/quest.hpp"
#include "../map/script.hpp"
#include "../map/storage.hpp"
-#include "../map/quest.hpp"
using namespace rathena;
@@ -73,9 +73,11 @@ static bool skill_parse_row_magicmushroomdb(char* split[], int column, int curre
static bool skill_parse_row_abradb(char* split[], int columns, int current);
static bool skill_parse_row_improvisedb(char* split[], int columns, int current);
static bool skill_parse_row_spellbookdb(char* split[], int columns, int current);
+static bool mob_readdb_mobavail(char *str[], int columns, int current);
// Constants for conversion
std::unordered_map aegis_itemnames;
+std::unordered_map aegis_itemviewid;
std::unordered_map aegis_mobnames;
std::unordered_map aegis_skillnames;
std::unordered_map constants;
@@ -218,6 +220,7 @@ int do_init( int argc, char** argv ){
sv_readdb( path_db_import.c_str(), "skill_db.txt", ',', 18, 18, -1, parse_skill_constants, false );
// Load constants
+ #define export_constant_npc(a) export_constant(a)
#include "../map/script_constants.hpp"
std::vector root_paths = {
@@ -262,6 +265,12 @@ int do_init( int argc, char** argv ){
return 0;
}
+ if (!process("MOB_AVAIL_DB", 1, root_paths, "mob_avail", [](const std::string& path, const std::string& name_ext) -> bool {
+ return sv_readdb(path.c_str(), name_ext.c_str(), ',', 2, 12, -1, &mob_readdb_mobavail, false);
+ })) {
+ return 0;
+ }
+
// TODO: add implementations ;-)
return 0;
@@ -419,6 +428,9 @@ static bool parse_item_constants( const char* path ){
aegis_itemnames[item_id] = std::string(name);
+ if (atoi(str[14]) & (EQP_HELM | EQP_COSTUME_HELM) && util::umap_find(aegis_itemviewid, (uint16)atoi(str[18])) == nullptr)
+ aegis_itemviewid[atoi(str[18])] = item_id;
+
count++;
}
@@ -808,3 +820,233 @@ static bool skill_parse_row_spellbookdb(char* split[], int columns, int current)
return true;
}
+
+// Copied and adjusted from mob.cpp
+static bool mob_readdb_mobavail(char* str[], int columns, int current) {
+ uint16 mob_id = atoi(str[0]);
+ std::string *mob_name = util::umap_find(aegis_mobnames, mob_id);
+
+ if (mob_name == nullptr) {
+ ShowWarning("mob_avail reading: Invalid mob-class %hu, Mob not read.\n", mob_id);
+ return false;
+ }
+
+ body << YAML::Key << "Mob" << YAML::Value << *mob_name;
+
+ uint16 sprite_id = atoi(str[1]);
+ std::string *sprite_name = util::umap_find(aegis_mobnames, sprite_id);
+
+ if (sprite_name == nullptr) {
+ char *sprite = const_cast(constant_lookup(sprite_id, "JOB_"));
+
+ if (sprite == nullptr) {
+ sprite = const_cast(constant_lookup(sprite_id, "JT_"));
+
+ if (sprite == nullptr) {
+ ShowError("Sprite name %s is not known.\n", sprite);
+ return false;
+ }
+
+ sprite += 3; // Strip JT_ here because the script engine doesn't send this prefix for NPC.
+
+ body << YAML::Key << "Sprite" << YAML::Value << *sprite;
+ } else
+ body << YAML::Key << "Sprite" << YAML::Value << *sprite;
+ } else
+ body << YAML::Key << "Sprite" << YAML::Value << *sprite_name;
+
+ if (columns == 12) {
+ body << YAML::Key << "Sex" << YAML::Value << (atoi(str[2]) ? "Male" : "Female");
+ if (atoi(str[3]) != 0)
+ body << YAML::Key << "HairStyle" << YAML::Value << atoi(str[3]);
+ if (atoi(str[4]) != 0)
+ body << YAML::Key << "HairColor" << YAML::Value << atoi(str[4]);
+ if (atoi(str[11]) != 0)
+ body << YAML::Key << "ClothColor" << YAML::Value << atoi(str[11]);
+
+ if (atoi(str[5]) != 0) {
+ uint16 weapon_item_id = atoi(str[5]);
+ std::string *weapon_item_name = util::umap_find(aegis_itemnames, weapon_item_id);
+
+ if (weapon_item_name == nullptr) {
+ ShowError("Item name for item ID %hu (weapon) is not known.\n", weapon_item_id);
+ return false;
+ }
+
+ body << YAML::Key << "Weapon" << YAML::Value << *weapon_item_name;
+ }
+
+ if (atoi(str[6]) != 0) {
+ uint16 shield_item_id = atoi(str[6]);
+ std::string *shield_item_name = util::umap_find(aegis_itemnames, shield_item_id);
+
+ if (shield_item_name == nullptr) {
+ ShowError("Item name for item ID %hu (shield) is not known.\n", shield_item_id);
+ return false;
+ }
+
+ body << YAML::Key << "Shield" << YAML::Value << *shield_item_name;
+ }
+
+ if (atoi(str[7]) != 0) {
+ uint16 *headtop_item_id = util::umap_find(aegis_itemviewid, (uint16)atoi(str[7]));
+
+ if (headtop_item_id == nullptr) {
+ ShowError("Item ID for view ID %hu (head top) is not known.\n", atoi(str[7]));
+ return false;
+ }
+
+ std::string *headtop_item_name = util::umap_find(aegis_itemnames, *headtop_item_id);
+
+ if (headtop_item_name == nullptr) {
+ ShowError("Item name for item ID %hu (head top) is not known.\n", *headtop_item_id);
+ return false;
+ }
+
+ body << YAML::Key << "HeadTop" << YAML::Value << *headtop_item_name;
+ }
+
+ if (atoi(str[8]) != 0) {
+ uint16 *headmid_item_id = util::umap_find(aegis_itemviewid, (uint16)atoi(str[8]));
+
+ if (headmid_item_id == nullptr) {
+ ShowError("Item ID for view ID %hu (head mid) is not known.\n", atoi(str[8]));
+ return false;
+ }
+
+ std::string *headmid_item_name = util::umap_find(aegis_itemnames, *headmid_item_id);
+
+ if (headmid_item_name == nullptr) {
+ ShowError("Item name for item ID %hu (head mid) is not known.\n", *headmid_item_id);
+ return false;
+ }
+
+ body << YAML::Key << "HeadMid" << YAML::Value << *headmid_item_name;
+ }
+
+ if (atoi(str[9]) != 0) {
+ uint16 *headlow_item_id = util::umap_find(aegis_itemviewid, (uint16)atoi(str[9]));
+
+ if (headlow_item_id == nullptr) {
+ ShowError("Item ID for view ID %hu (head low) is not known.\n", atoi(str[9]));
+ return false;
+ }
+
+ std::string *headlow_item_name = util::umap_find(aegis_itemnames, *headlow_item_id);
+
+ if (headlow_item_name == nullptr) {
+ ShowError("Item name for item ID %hu (head low) is not known.\n", *headlow_item_id);
+ return false;
+ }
+
+ body << YAML::Key << "HeadLow" << YAML::Value << *headlow_item_name;
+ }
+
+ if (atoi(str[10]) != 0) {
+ uint32 options = atoi(str[10]);
+
+ body << YAML::Key << "Options";
+ body << YAML::BeginMap;
+
+ while (options > OPTION_NOTHING && options <= OPTION_SUMMER2) {
+ if (options & OPTION_SIGHT) {
+ body << YAML::Key << "Sight" << YAML::Value << "true";
+ options &= ~OPTION_SIGHT;
+ } else if (options & OPTION_CART1) {
+ body << YAML::Key << "Cart1" << YAML::Value << "true";
+ options &= ~OPTION_CART1;
+ } else if (options & OPTION_FALCON) {
+ body << YAML::Key << "Falcon" << YAML::Value << "true";
+ options &= ~OPTION_FALCON;
+ } else if (options & OPTION_RIDING) {
+ body << YAML::Key << "Riding" << YAML::Value << "true";
+ options &= ~OPTION_RIDING;
+ } else if (options & OPTION_CART2) {
+ body << YAML::Key << "Cart2" << YAML::Value << "true";
+ options &= ~OPTION_CART2;
+ } else if (options & OPTION_CART3) {
+ body << YAML::Key << "Cart2" << YAML::Value << "true";
+ options &= ~OPTION_CART3;
+ } else if (options & OPTION_CART4) {
+ body << YAML::Key << "Cart4" << YAML::Value << "true";
+ options &= ~OPTION_CART4;
+ } else if (options & OPTION_CART5) {
+ body << YAML::Key << "Cart5" << YAML::Value << "true";
+ options &= ~OPTION_CART5;
+ } else if (options & OPTION_ORCISH) {
+ body << YAML::Key << "Orcish" << YAML::Value << "true";
+ options &= ~OPTION_ORCISH;
+ } else if (options & OPTION_WEDDING) {
+ body << YAML::Key << "Wedding" << YAML::Value << "true";
+ options &= ~OPTION_WEDDING;
+ } else if (options & OPTION_RUWACH) {
+ body << YAML::Key << "Ruwach" << YAML::Value << "true";
+ options &= ~OPTION_RUWACH;
+ } else if (options & OPTION_FLYING) {
+ body << YAML::Key << "Flying" << YAML::Value << "true";
+ options &= ~OPTION_FLYING;
+ } else if (options & OPTION_XMAS) {
+ body << YAML::Key << "Xmas" << YAML::Value << "true";
+ options &= ~OPTION_XMAS;
+ } else if (options & OPTION_TRANSFORM) {
+ body << YAML::Key << "Transform" << YAML::Value << "true";
+ options &= ~OPTION_TRANSFORM;
+ } else if (options & OPTION_SUMMER) {
+ body << YAML::Key << "Summer" << YAML::Value << "true";
+ options &= ~OPTION_SUMMER;
+ } else if (options & OPTION_DRAGON1) {
+ body << YAML::Key << "Dragon1" << YAML::Value << "true";
+ options &= ~OPTION_DRAGON1;
+ } else if (options & OPTION_WUG) {
+ body << YAML::Key << "Wug" << YAML::Value << "true";
+ options &= ~OPTION_WUG;
+ } else if (options & OPTION_WUGRIDER) {
+ body << YAML::Key << "WugRider" << YAML::Value << "true";
+ options &= ~OPTION_WUGRIDER;
+ } else if (options & OPTION_MADOGEAR) {
+ body << YAML::Key << "MadoGear" << YAML::Value << "true";
+ options &= ~OPTION_MADOGEAR;
+ } else if (options & OPTION_DRAGON2) {
+ body << YAML::Key << "Dragon2" << YAML::Value << "true";
+ options &= ~OPTION_DRAGON2;
+ } else if (options & OPTION_DRAGON3) {
+ body << YAML::Key << "Dragon3" << YAML::Value << "true";
+ options &= ~OPTION_DRAGON3;
+ } else if (options & OPTION_DRAGON4) {
+ body << YAML::Key << "Dragon4" << YAML::Value << "true";
+ options &= ~OPTION_DRAGON4;
+ } else if (options & OPTION_DRAGON5) {
+ body << YAML::Key << "Dragon5" << YAML::Value << "true";
+ options &= ~OPTION_DRAGON5;
+ } else if (options & OPTION_HANBOK) {
+ body << YAML::Key << "Hanbok" << YAML::Value << "true";
+ options &= ~OPTION_HANBOK;
+ } else if (options & OPTION_OKTOBERFEST) {
+ body << YAML::Key << "Oktoberfest" << YAML::Value << "true";
+ options &= ~OPTION_OKTOBERFEST;
+ } else if (options & OPTION_SUMMER2) {
+ body << YAML::Key << "Summer2" << YAML::Value << "true";
+ options &= ~OPTION_SUMMER2;
+ }
+ }
+
+ body << YAML::EndMap;
+ }
+ } else if (columns == 3) {
+ if (atoi(str[5]) != 0) {
+ uint16 peteq_item_id = atoi(str[5]);
+ std::string *peteq_item_name = util::umap_find(aegis_itemnames, peteq_item_id);
+
+ if (peteq_item_name == nullptr) {
+ ShowError("Item name for item ID %hu (pet equip) is not known.\n", peteq_item_id);
+ return false;
+ }
+
+ body << YAML::Key << "PetEquip" << YAML::Value << *peteq_item_name;
+ }
+ }
+
+ body << YAML::EndMap;
+
+ return true;
+}