From 148ab787d51c7c9c71540f799d37fb748cf19135 Mon Sep 17 00:00:00 2001
From: HoraK-FDF <horak-fdf@web.de>
Date: Thu, 17 Nov 2022 13:37:20 +0000
Subject: MobInfo

* includes @mobinfo ingame command aswell as mobinfo functions for scripts
* enhanced summon script command to take a name to support spawn names
* moved @summon to where other mob related commands are
* added enchanter and koyntety cooldown symbols
* some translations
* some constants added for drops and mobs
---
 src/map/atcommand.cpp    | 212 ++++++++++++++++++-----
 src/map/globals.cpp      |   2 +-
 src/map/globals.hpp      |   3 +-
 src/map/mob.cpp          |   8 +-
 src/map/mob.hpp          |   6 +-
 src/map/script-fun.cpp   | 441 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/map/script-fun.hpp   |   2 +
 src/map/script-fun.t.hpp |  96 +++++++++++
 src/map/skill.cpp        |   4 +
 src/mmo/skill.t.hpp      |   2 +
 10 files changed, 721 insertions(+), 55 deletions(-)
 create mode 100644 src/map/script-fun.t.hpp

diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp
index 459672d..b4faed5 100644
--- a/src/map/atcommand.cpp
+++ b/src/map/atcommand.cpp
@@ -1821,6 +1821,172 @@ ATCE atcommand_hair_color(Session *s, dumb_ptr<map_session_data> sd,
     return ATCE::OKAY;
 }
 
+static
+ATCE atcommand_mobinfo(Session *s, dumb_ptr<map_session_data> sd,
+        ZString message)
+{
+    MobName name;
+    Species mob_id;
+    int exp;
+
+    if (!extract(message, &name) || !name)
+        return ATCE::USAGE;
+
+    if ((mob_id = wrap<Species>(static_cast<uint16_t>(atoi(name.c_str())))) == Species())
+        mob_id = mobdb_searchname(name);
+    else
+        mob_id = mobdb_checkid(mob_id);
+
+    if (mob_id == Species())
+        return ATCE::EXIST;
+
+    clif_displaymessage(s, STRPRINTF("Monster ID: %i, English Name: %s, Japanese Name: %s"_fmt, mob_id, get_mob_db(mob_id).name, get_mob_db(mob_id).jname));
+    clif_displaymessage(s, STRPRINTF("Level: %i, HP: %i, SP: %i, Base EXP: %i, JEXP: %i"_fmt, get_mob_db(mob_id).lv, get_mob_db(mob_id).max_hp, get_mob_db(mob_id).max_sp, get_mob_db(mob_id).base_exp, get_mob_db(mob_id).job_exp));
+    clif_displaymessage(s, STRPRINTF("Range1: %i, ATK1: %i, ATK2: %i, DEF: %i, MDEF: %i"_fmt, get_mob_db(mob_id).range, get_mob_db(mob_id).atk1, get_mob_db(mob_id).atk2, get_mob_db(mob_id).def, get_mob_db(mob_id).mdef));
+    clif_displaymessage(s, STRPRINTF("Stats: STR: %i, AGI: %i, VIT: %i, INT: %i, DEX:, %i LUK:, %i"_fmt, get_mob_db(mob_id).attrs[ATTR::STR], get_mob_db(mob_id).attrs[ATTR::AGI], get_mob_db(mob_id).attrs[ATTR::VIT], get_mob_db(mob_id).attrs[ATTR::INT], get_mob_db(mob_id).attrs[ATTR::DEX], get_mob_db(mob_id).attrs[ATTR::LUK]));
+    clif_displaymessage(s, STRPRINTF("Range2: %i, Range3: %i, Scale: %i, Race: %i, Element: %i, Element Level: %i, Mode: %i"_fmt, get_mob_db(mob_id).range2, get_mob_db(mob_id).range3, get_mob_db(mob_id).size, get_mob_db(mob_id).race, get_mob_db(mob_id).element.element, get_mob_db(mob_id).element.level, get_mob_db(mob_id).mode));
+    clif_displaymessage(s, STRPRINTF("Speed: %i, Adelay: %i, Amotion: %i, Dmotion: %i"_fmt, get_mob_db(mob_id).speed.count(), get_mob_db(mob_id).adelay.count(), get_mob_db(mob_id).amotion.count(), get_mob_db(mob_id).dmotion.count()));
+    if (get_mob_db(mob_id).mutations_nr)
+        clif_displaymessage(s, STRPRINTF("May mutate %i attribute up to %i%%"_fmt, get_mob_db(mob_id).mutations_nr, get_mob_db(mob_id).mutation_power));
+
+    for (int i = 0; i < MaxDrops; ++i)
+        if (get_mob_db(mob_id).dropitem[i].nameid)
+        {
+            Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[i].nameid));
+            RString item_name = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+
+            int drop_rate = get_mob_db(mob_id).dropitem[i].p.num;
+
+            char str[6];
+            char strpos = 0;
+
+            char min = 0;
+            char mod = 0;
+            char fraction = drop_rate % 100;
+            char integer = drop_rate / 100;
+
+            if (fraction)
+            {
+                if (fraction < 10)
+                    min = 1;
+                do
+                {
+                    mod = fraction % 10;
+                    fraction = fraction / 10;
+                    if (!(strpos == 0 && mod == 0))
+                    {
+                        str[strpos] = '0' + mod;
+                        ++strpos;
+                    }
+                } while (fraction > 0);
+                if (min)
+                {
+                    str[strpos] = '0';
+                    ++strpos;
+                }
+                str[strpos] = '.';
+                ++strpos;
+            }
+            if (integer)
+            {
+                do
+                {
+                    mod = integer % 10;
+                    integer = integer / 10;
+                    str[strpos] = '0' + mod;
+                    ++strpos;
+                } while (integer > 0);
+            }
+            else
+            {
+                str[strpos] = '0';
+                ++strpos;
+            }
+            // flip string
+            for (char i=0, tmpstrpos=strpos-1; i <= tmpstrpos/2; ++i, --tmpstrpos)
+            {
+                char tmp = str[tmpstrpos];
+                str[tmpstrpos]=str[i];
+                str[i]=tmp;
+            }
+            str[strpos] = '\0';
+
+            int drop_rate2 = 10000/drop_rate;
+            clif_displaymessage(s, STRPRINTF("Drop ID %i: %i, Item Name: %s, Drop Chance: %s%% (1:%i)"_fmt,i+1, get_mob_db(mob_id).dropitem[i].nameid, item_name, str, drop_rate2));
+        }
+        else
+            break;
+
+    clif_displaymessage(s, STRPRINTF("Mob Mode Info:"_fmt));
+    if (!bool(get_mob_db(mob_id).mode & MobMode::ZERO))
+    {
+        if (bool(get_mob_db(mob_id).mode & MobMode::CAN_MOVE))
+            clif_displaymessage(s, STRPRINTF("Mobile"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::LOOTER))
+            clif_displaymessage(s, STRPRINTF("Picks up loot"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::AGGRESSIVE))
+            clif_displaymessage(s, STRPRINTF("Aggro"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::ASSIST))
+            clif_displaymessage(s, STRPRINTF("Assists"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::CAST_SENSOR))
+            clif_displaymessage(s, STRPRINTF("Cast Sensor"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::BOSS))
+            clif_displaymessage(s, STRPRINTF("Boss"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::PLANT))
+            clif_displaymessage(s, STRPRINTF("Plant"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::CAN_ATTACK))
+            clif_displaymessage(s, STRPRINTF("Can attack"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::DETECTOR))
+            clif_displaymessage(s, STRPRINTF("Detector"_fmt));
+        if (bool(get_mob_db(mob_id).mode & MobMode::CHANGE_TARGET))
+            clif_displaymessage(s, STRPRINTF("Change Target"_fmt));
+            /*
+            Not needed here i guess
+            SUMMONED
+            TURNS_AGAINST_BAD_MASTER
+            SENSIBLE_MASK
+            */
+    }
+    return ATCE::OKAY;
+}
+
+static
+ATCE atcommand_summon(Session *, dumb_ptr<map_session_data> sd,
+        ZString message)
+{
+    MobName name;
+    Species mob_id;
+    int x = 0;
+    int y = 0;
+    tick_t tick = gettick();
+
+    if (!extract(message, &name) || !name)
+        return ATCE::USAGE;
+
+    if ((mob_id = wrap<Species>(static_cast<uint16_t>(atoi(name.c_str())))) == Species())
+        mob_id = mobdb_searchname(name);
+    if (mob_id == Species())
+        return ATCE::EXIST;
+
+    x = sd->bl_x + random_::in(-5, 4);
+    y = sd->bl_y + random_::in(-5, 4);
+
+    BlockId id = mob_once_spawn(sd, MOB_THIS_MAP, x, y, JAPANESE_NAME, mob_id, 1, NpcEvent());
+    dumb_ptr<mob_data> md = map_id_is_mob(id);
+    if (md)
+    {
+        md->master_id = sd->bl_id;
+        md->state.special_mob_ai = 1;
+        md->mode = get_mob_db(md->mob_class).mode | MobMode::AGGRESSIVE;
+        md->deletetimer = Timer(tick + 1_min,
+                std::bind(mob_timer_delete, ph::_1, ph::_2,
+                    id));
+        clif_misceffect(md, 344);
+    }
+
+    return ATCE::OKAY;
+}
+
 static
 ATCE atcommand_spawn(Session *s, dumb_ptr<map_session_data> sd,
         ZString message)
@@ -4551,43 +4717,6 @@ ATCE atcommand_leaves(Session *, dumb_ptr<map_session_data> sd,
     return ATCE::OKAY;
 }
 
-static
-ATCE atcommand_summon(Session *, dumb_ptr<map_session_data> sd,
-        ZString message)
-{
-    MobName name;
-    Species mob_id;
-    int x = 0;
-    int y = 0;
-    tick_t tick = gettick();
-
-    if (!extract(message, &name) || !name)
-        return ATCE::USAGE;
-
-    if ((mob_id = wrap<Species>(static_cast<uint16_t>(atoi(name.c_str())))) == Species())
-        mob_id = mobdb_searchname(name);
-    if (mob_id == Species())
-        return ATCE::EXIST;
-
-    x = sd->bl_x + random_::in(-5, 4);
-    y = sd->bl_y + random_::in(-5, 4);
-
-    BlockId id = mob_once_spawn(sd, MOB_THIS_MAP, x, y, JAPANESE_NAME, mob_id, 1, NpcEvent());
-    dumb_ptr<mob_data> md = map_id_is_mob(id);
-    if (md)
-    {
-        md->master_id = sd->bl_id;
-        md->state.special_mob_ai = 1;
-        md->mode = get_mob_db(md->mob_class).mode | MobMode::AGGRESSIVE;
-        md->deletetimer = Timer(tick + 1_min,
-                std::bind(mob_timer_delete, ph::_1, ph::_2,
-                    id));
-        clif_misceffect(md, 344);
-    }
-
-    return ATCE::OKAY;
-}
-
 static
 ATCE atcommand_adjcmdlvl(Session *s, dumb_ptr<map_session_data>,
         ZString message)
@@ -5325,6 +5454,12 @@ Map<XString, AtCommandInfo> atcommand_info =
     {"model"_s, {"<style> [color] [dye]"_s,
         98, atcommand_model,
         "Change your hairstyle and hair color"_s}},
+    {"mobinfo"_s, {"<mob-id-or-name>"_s,
+        20, atcommand_mobinfo,
+        "Show stats of a monster."_s}},
+    {"summon"_s, {"<mob-id-or-name>"_s,
+        50, atcommand_summon,
+        "Summon a slave monster temporarily"_s}},
     {"spawn"_s, {"<mob-name-or-id> [count] [x] [y]"_s,
         50, atcommand_spawn,
         "Spawn normal monsters at location."_s}},
@@ -5577,9 +5712,6 @@ Map<XString, AtCommandInfo> atcommand_info =
     {"leaves"_s, {""_s,
         98, atcommand_leaves,
         "Enable the leaves mapflag"_s}},
-    {"summon"_s, {"<mob-id-or-name>"_s,
-        50, atcommand_summon,
-        "Summon a slave monster temporarily"_s}},
     {"adjgmlvl"_s, {"<level> <charname>"_s,
         98, atcommand_adjgmlvl,
         "Temporarily adjust the GM level of a player"_s}},
diff --git a/src/map/globals.cpp b/src/map/globals.cpp
index 97533c8..d2c1993 100644
--- a/src/map/globals.cpp
+++ b/src/map/globals.cpp
@@ -70,7 +70,7 @@ namespace tmwa
         block_list bl_head;
         std::unique_ptr<io::AppendFile> map_logfile;
         long map_logfile_index;
-        mob_db_ mob_db[2001];
+        mob_db_ mob_db[MaxMobID+1];
         std::list<AString> npc_srcs;
         int npc_warp, npc_shop, npc_script, npc_mob;
         BlockId npc_id = START_NPC_NUM;
diff --git a/src/map/globals.hpp b/src/map/globals.hpp
index 107ed5e..2fcdd95 100644
--- a/src/map/globals.hpp
+++ b/src/map/globals.hpp
@@ -36,6 +36,7 @@
 #include "../mmo/skill.t.hpp"
 
 #include "consts.hpp"
+#include "mob.hpp"
 #include "script-buffer.hpp"
 
 
@@ -63,7 +64,7 @@ namespace tmwa
         extern block_list bl_head;
         extern std::unique_ptr<io::AppendFile> map_logfile;
         extern long map_logfile_index;
-        extern mob_db_ mob_db[2001];
+        extern mob_db_ mob_db[MaxMobID+1];
         extern std::list<AString> npc_srcs;
         extern int npc_warp, npc_shop, npc_script, npc_mob;
         extern BlockId npc_id;
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
index 0da946a..0f273a8 100644
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -115,7 +115,7 @@ Species mobdb_searchname(MobName str)
 Species mobdb_checkid(Species id)
 {
     // value range is [1001, 2000]
-    if (wrap<Species>(1000) < id && id < wrap<Species>(2001))
+    if (wrap<Species>(MinMobID-1) < id && id < wrap<Species>(MaxMobID+1))
         return id;
     return Species();
 }
@@ -2647,7 +2647,7 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
         // item drop
         if (!(type & 1))
         {
-            for (int i = 0; i < 8; i++)
+            for (int i = 0; i < MaxDrops; i++)
             {
                 if (md->state.special_mob_ai >= 1 && battle_config.alchemist_summon_reward != 1)    // Added [Valaris]
                     break;      // End
@@ -3439,7 +3439,7 @@ int mob_makedummymobdb(Species mob_class)
     get_mob_db(mob_class).adelay = 1000_ms;
     get_mob_db(mob_class).amotion = 500_ms;
     get_mob_db(mob_class).dmotion = 500_ms;
-    for (i = 0; i < 8; i++)
+    for (i = 0; i < MaxDrops; i++)
     {
         get_mob_db(mob_class).dropitem[i].nameid = ItemNameId();
         get_mob_db(mob_class).dropitem[i].p.num = 0;
@@ -3577,7 +3577,7 @@ bool mob_readdb(ZString filename)
             // TODO move this lower
             get_mob_db(mob_class) = std::move(mdbv);
 
-            for (int i = 0; i < 8; i++)
+            for (int i = 0; i < MaxDrops; i++)
             {
                 int rate = get_mob_db(mob_class).dropitem[i].p.num;
                 if (rate < 1) rate = 1;
diff --git a/src/map/mob.hpp b/src/map/mob.hpp
index a466d0b..47da095 100644
--- a/src/map/mob.hpp
+++ b/src/map/mob.hpp
@@ -43,6 +43,10 @@ namespace map
 #define JAPANESE_NAME stringish<MobName>("--ja--"_s)
 #define MOB_THIS_MAP stringish<MapName>("this"_s)
 
+#define MaxDrops 8
+#define MinMobID 1001
+#define MaxMobID 2000
+
 struct mob_skill
 {
     MobSkillState state;
@@ -79,7 +83,7 @@ struct mob_db_
     {
         ItemNameId nameid;
         random_::Fixed<int, 10000> p;
-    } dropitem[8];
+    } dropitem[MaxDrops];
     short hair, hair_color, weapon;
     ItemNameId shield, head_top, head_mid, head_buttom;
     short option, clothes_color; // [Valaris]
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index bcb5a45..58646bc 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -2623,6 +2623,423 @@ void builtin_getexp(ScriptState *st)
 
 }
 
+/*==========================================
+ * Returns attributes of a monster
+ * return value -1 = mob not found
+ *------------------------------------------
+ */
+static
+void builtin_mobinfo(ScriptState *st)
+{
+    Species mob_id = wrap<Species>(conv_num(st, &AARG(0)));
+    MobInfo request = MobInfo(conv_num(st, &AARG(1)));
+    int info = 0;
+    AString info_str;
+    char mode = 0; // 0 = int, 1 = str
+
+    if (mobdb_checkid(mob_id) == Species())
+    {
+        push_int<ScriptDataInt>(st->stack, -1);
+        return;
+    }
+
+    switch (request)
+    {
+        case MobInfo::ID:
+            info = unwrap<Species>(mob_id);
+            break;
+        case MobInfo::ENG_NAME:
+            info_str = get_mob_db(mob_id).name;
+            mode = 1;
+            break;
+        case MobInfo::JAP_NAME:
+            info_str = get_mob_db(mob_id).jname;
+            mode = 1;
+            break;
+        case MobInfo::LVL:
+            info = get_mob_db(mob_id).lv;
+            break;
+        case MobInfo::HP:
+            info = get_mob_db(mob_id).max_hp;
+            break;
+        case MobInfo::SP:
+            info = get_mob_db(mob_id).max_sp;
+            break;
+        case MobInfo::BASE_EXP:
+            info = get_mob_db(mob_id).base_exp;
+            break;
+        case MobInfo::JOB_EXP:
+            info = get_mob_db(mob_id).job_exp;
+            break;
+        case MobInfo::RANGE1:
+            info = get_mob_db(mob_id).range;
+            break;
+        case MobInfo::ATK1:
+            info = get_mob_db(mob_id).atk1;
+            break;
+        case MobInfo::ATK2:
+            info = get_mob_db(mob_id).atk2;
+            break;
+        case MobInfo::DEF:
+            info = get_mob_db(mob_id).def;
+            break;
+        case MobInfo::MDEF:
+            info = get_mob_db(mob_id).mdef;
+            break;
+        case MobInfo::STR:
+            info = get_mob_db(mob_id).attrs[ATTR::STR];
+            break;
+        case MobInfo::AGI:
+            info = get_mob_db(mob_id).attrs[ATTR::AGI];
+            break;
+        case MobInfo::VIT:
+            info = get_mob_db(mob_id).attrs[ATTR::VIT];
+            break;
+        case MobInfo::INT:
+            info = get_mob_db(mob_id).attrs[ATTR::INT];
+            break;
+        case MobInfo::DEX:
+            info = get_mob_db(mob_id).attrs[ATTR::DEX];
+            break;
+        case MobInfo::LUK:
+            info = get_mob_db(mob_id).attrs[ATTR::LUK];
+            break;
+        case MobInfo::RANGE2:
+            info = get_mob_db(mob_id).range2;
+            break;
+        case MobInfo::RANGE3:
+            info = get_mob_db(mob_id).range3;
+            break;
+        case MobInfo::SCALE:
+            info = get_mob_db(mob_id).size;
+            break;
+        case MobInfo::RACE:
+            info = int(get_mob_db(mob_id).race);
+            break;
+        case MobInfo::ELEMENT:
+            info = int(get_mob_db(mob_id).element.element);
+            break;
+        case MobInfo::ELEMENT_LVL:
+            info = get_mob_db(mob_id).element.level;
+            break;
+        case MobInfo::MODE:
+            info = int(get_mob_db(mob_id).mode);
+            break;
+        case MobInfo::SPEED:
+            info = get_mob_db(mob_id).speed.count();
+            break;
+        case MobInfo::ADELAY:
+            info = get_mob_db(mob_id).adelay.count();
+            break;
+        case MobInfo::AMOTION:
+            info = get_mob_db(mob_id).amotion.count();
+            break;
+        case MobInfo::DMOTION:
+            info = get_mob_db(mob_id).dmotion.count();
+            break;
+        case MobInfo::MUTATION_NUM:
+            info = get_mob_db(mob_id).mutations_nr;
+            break;
+        case MobInfo::MUTATION_POWER:
+            info = get_mob_db(mob_id).mutation_power;
+            break;
+        case MobInfo::DROPID1:
+            info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[0].nameid);
+            break;
+        case MobInfo::DROPNAME1:
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[0].nameid));
+                info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                mode = 1;
+            }
+            break;
+        case MobInfo::DROPPERCENT1:
+            info = get_mob_db(mob_id).dropitem[0].p.num;
+            break;
+        case MobInfo::DROPID2:
+            info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[1].nameid);
+            break;
+        case MobInfo::DROPNAME2:
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[1].nameid));
+                info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                mode = 1;
+            }
+            break;
+        case MobInfo::DROPPERCENT2:
+            info = get_mob_db(mob_id).dropitem[1].p.num;
+            break;
+        case MobInfo::DROPID3:
+            info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[2].nameid);
+            break;
+        case MobInfo::DROPNAME3:
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[2].nameid));
+                info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                mode = 1;
+            }
+            break;
+        case MobInfo::DROPPERCENT3:
+            info = get_mob_db(mob_id).dropitem[2].p.num;
+            break;
+        case MobInfo::DROPID4:
+            info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[3].nameid);
+            break;
+        case MobInfo::DROPNAME4:
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[3].nameid));
+                info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                mode = 1;
+            }
+            break;
+        case MobInfo::DROPPERCENT4:
+            info = get_mob_db(mob_id).dropitem[3].p.num;
+            break;
+        case MobInfo::DROPID5:
+            info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[4].nameid);
+            break;
+        case MobInfo::DROPNAME5:
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[4].nameid));
+                info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                mode = 1;
+            }
+            break;
+        case MobInfo::DROPPERCENT5:
+            info = get_mob_db(mob_id).dropitem[4].p.num;
+            break;
+        case MobInfo::DROPID6:
+            info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[5].nameid);
+            break;
+        case MobInfo::DROPNAME6:
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[5].nameid));
+                info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                mode = 1;
+            }
+            break;
+        case MobInfo::DROPPERCENT6:
+            info = get_mob_db(mob_id).dropitem[5].p.num;
+            break;
+        case MobInfo::DROPID7:
+            info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[6].nameid);
+            break;
+        case MobInfo::DROPNAME7:
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[6].nameid));
+                info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                mode = 1;
+            }
+            break;
+        case MobInfo::DROPPERCENT7:
+            info = get_mob_db(mob_id).dropitem[6].p.num;
+            break;
+        case MobInfo::DROPID8:
+            info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[7].nameid);
+            break;
+        case MobInfo::DROPNAME8:
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[7].nameid));
+                info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                mode = 1;
+            }
+            break;
+        case MobInfo::DROPPERCENT8:
+            info = get_mob_db(mob_id).dropitem[7].p.num;
+            break;
+        default:
+            PRINTF("builtin_mobinfo: unknown request\n"_fmt);
+            push_int<ScriptDataInt>(st->stack, -1);
+            return;
+            break;
+    }
+    if (!mode)
+        push_int<ScriptDataInt>(st->stack, info);
+    else
+        push_str<ScriptDataStr>(st->stack, info_str);
+}
+
+/*==========================================
+ * Returns drops of a monster to an array
+ * return values:
+ * 0 = mob not found or error
+ * 1 = mob found and has drops
+ * 2 = mob found and has no drops
+ *------------------------------------------
+ */
+static
+void builtin_mobinfo_droparrays(ScriptState *st)
+{
+    dumb_ptr<block_list> bl = nullptr;
+
+    Species mob_id = wrap<Species>(conv_num(st, &AARG(0)));
+
+    MobInfo_DropArrays request = MobInfo_DropArrays(conv_num(st, &AARG(1)));
+
+    SIR reg = AARG(2).get_if<ScriptDataVariable>()->reg;
+    ZString name = variable_names.outtern(reg.base());
+
+    char prefix = name.front();
+    char postfix = name.back();
+    
+    int status = 0; // 0 = mob not found or error, 1 = mob found and has drops, 2 = mob found and has no drops
+
+    if (prefix != '$' && prefix != '@' && prefix != '.')
+    {
+        PRINTF("builtin_mobinfo_droparrays: illegal scope!\n"_fmt);
+        push_int<ScriptDataInt>(st->stack, 0);
+        return;
+    }
+
+    if (prefix == '.' && !name.startswith(".@"_s))
+        bl = map_id_is_npc(st->oid);
+    else if (prefix != '$' && !name.startswith(".@"_s))
+    {
+        bl = map_id_is_player(st->rid);
+        script_nullpo_end(bl, "player not found");
+    }
+
+    switch (request)
+    {
+        case MobInfo_DropArrays::IDS:
+            if (postfix == '$')
+            {
+                PRINTF("builtin_mobinfo_droparrays: wrong array type for ID's (Int expected but String found)!\n"_fmt);
+                push_int<ScriptDataInt>(st->stack, 0);
+                return;
+            }
+            break;
+        case MobInfo_DropArrays::NAMES:
+            if (postfix != '$')
+            {
+                PRINTF("builtin_mobinfo_droparrays: wrong array type for Names (String expected but Int found)!\n"_fmt);
+                push_int<ScriptDataInt>(st->stack, 0);
+                return;
+            }
+            break;
+        case MobInfo_DropArrays::PERCENTS:
+            if (postfix == '$')
+            {
+                PRINTF("builtin_mobinfo_droparrays: wrong array type for Percents (Int expected but String found)!\n"_fmt);
+                push_int<ScriptDataInt>(st->stack, 0);
+                return;
+            }
+            break;
+        default:
+            PRINTF("builtin_mobinfo_droparrays: unknown request\n"_fmt);
+            push_int<ScriptDataInt>(st->stack, 0);
+            return;
+            break;
+    }
+
+    if (mobdb_checkid(mob_id) == Species())
+    {
+        push_int<ScriptDataInt>(st->stack, status);
+        return;
+    }
+    
+    for (int i = 0; i < MaxDrops; ++i)
+        if (get_mob_db(mob_id).dropitem[i].nameid)
+        {
+            status = 1;
+            switch (request)
+            {
+                case MobInfo_DropArrays::IDS:
+                    if (name.startswith(".@"_s))
+                        {
+                            struct script_data vd = script_data(ScriptDataInt{unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[i].nameid)});
+                            set_scope_reg(st, reg.iplus(i), &vd);
+                        }
+                    else
+                        set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[i].nameid));
+                    break;
+                case MobInfo_DropArrays::NAMES:
+                    {
+                        Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[i].nameid));
+                        RString item_name = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+
+                        if (name.startswith(".@"_s))
+                        {
+                            struct script_data vd = script_data(ScriptDataStr{item_name});
+                            set_scope_reg(st, reg.iplus(i), &vd);
+                        }
+                        else
+                            set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), item_name);
+                        break;
+                    }
+                case MobInfo_DropArrays::PERCENTS:
+                    if (name.startswith(".@"_s))
+                        {
+                            struct script_data vd = script_data(ScriptDataInt{get_mob_db(mob_id).dropitem[i].p.num});
+                            set_scope_reg(st, reg.iplus(i), &vd);
+                        }
+                    else
+                        set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), get_mob_db(mob_id).dropitem[i].p.num);
+                    break;
+            }
+        }
+        else
+        {
+            if (i == 0)
+                status = 2;
+            break;
+        }
+
+    push_int<ScriptDataInt>(st->stack, status);
+}
+
+/*==========================================
+ * Returns drops of a monster to standardized arrays
+ * return values:
+ * 0 = mob not found
+ * 1 = mob found and has drops
+ * 2 = mob found and has no drops
+ *------------------------------------------
+ */
+static
+void builtin_getmobdrops(ScriptState *st)
+{
+    dumb_ptr<block_list> bl = nullptr;
+
+    Species mob_id = wrap<Species>(conv_num(st, &AARG(0)));
+
+    int status = 0; // 0 = mob not found, 1 = mob found and has drops, 2 = mob found and has no drops
+    int i = 0;
+
+    if (mobdb_checkid(mob_id) == Species())
+    {
+        push_int<ScriptDataInt>(st->stack, status);
+        return;
+    }
+
+    status = 1;
+
+    for (; i < MaxDrops; ++i)
+        if (get_mob_db(mob_id).dropitem[i].nameid)
+        {
+            set_reg(bl, VariableCode::VARIABLE, SIR::from(variable_names.intern("$@MobDrop_item"_s), i), get_mob_db(mob_id).dropitem[i].p.num);
+
+            Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[i].nameid));
+            RString item_name = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+            set_reg(bl, VariableCode::VARIABLE, SIR::from(variable_names.intern("$@MobDrop_name$"_s), i), item_name);
+
+            set_reg(bl, VariableCode::VARIABLE, SIR::from(variable_names.intern("$@MobDrop_rate"_s), i), get_mob_db(mob_id).dropitem[i].p.num);
+        }
+        else
+        {
+            if (i == 0)
+                status = 2;
+            break;
+        }
+
+    if (status == 1)
+        set_reg(bl, VariableCode::VARIABLE, SIR::from(variable_names.intern("$@MobDrop_count"_s), 0), i);
+    else
+        set_reg(bl, VariableCode::VARIABLE, SIR::from(variable_names.intern("$@MobDrop_count"_s), 0), 0);
+
+    push_int<ScriptDataInt>(st->stack, status);
+}
+
 /*==========================================
  *
  *------------------------------------------
@@ -2636,11 +3053,12 @@ void builtin_summon(ScriptState *st)
     int y = conv_num(st, &AARG(2));
     dumb_ptr<block_list> owner_e = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(3))));
     dumb_ptr<map_session_data> owner = nullptr;
-    Species monster_id = wrap<Species>(conv_num(st, &AARG(4)));
-    MonsterAttitude monster_attitude = static_cast<MonsterAttitude>(conv_num(st, &AARG(5)));
-    interval_t lifespan = static_cast<interval_t>(conv_num(st, &AARG(6)));
+    MobName str = stringish<MobName>(ZString(conv_str(st, &AARG(4))));
+    Species monster_id = wrap<Species>(conv_num(st, &AARG(5)));
+    MonsterAttitude monster_attitude = static_cast<MonsterAttitude>(conv_num(st, &AARG(6)));
+    interval_t lifespan = static_cast<interval_t>(conv_num(st, &AARG(7)));
     if (HARG(7))
-        extract(ZString(conv_str(st, &AARG(7))), &event);
+        extract(ZString(conv_str(st, &AARG(8))), &event);
 
     if (!owner_e)
     {
@@ -2652,7 +3070,9 @@ void builtin_summon(ScriptState *st)
         && owner_e->bl_type == BL::PC)
         owner = owner_e->is_player(); // XXX in the future this should also work with mobs as owner
 
-    BlockId mob_id = mob_once_spawn(owner, map, x, y, MobName(), monster_id, 1, event);
+    if (!str)
+        str = MobName();
+    BlockId mob_id = mob_once_spawn(owner, map, x, y, str, monster_id, 1, event);
     dumb_ptr<mob_data> mob = map_id_is_mob(mob_id);
 
     if (mob)
@@ -3157,9 +3577,9 @@ void builtin_getusers(ScriptState *st)
     int val = 0;
     switch (flag & 0x07)
     {
-        /*case 0:
+        case 0:
             val = bl->bl_m->users;
-            break;*/
+            break;
         case 1:
             val = map_getusers();
             break;
@@ -3404,6 +3824,8 @@ void builtin_sc_start(ScriptState *st)
             case StatusChange::SC_COOLDOWN_MT:
             case StatusChange::SC_COOLDOWN_R:
             case StatusChange::SC_COOLDOWN_AR:
+            case StatusChange::SC_COOLDOWN_ENCH:
+            case StatusChange::SC_COOLDOWN_KOY:
             break;
 
             default:
@@ -5129,7 +5551,10 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(gettime, "i"_s, 'i'),
     BUILTIN(openstorage, ""_s, '\0'),
     BUILTIN(getexp, "ii"_s, '\0'),
-    BUILTIN(summon, "Mxysmii?"_s, '\0'),
+    BUILTIN(mobinfo, "ii"_s, 'v'),
+    BUILTIN(mobinfo_droparrays, "iiN"_s, 'i'),
+    BUILTIN(getmobdrops, "i"_s, 'i'),
+    BUILTIN(summon, "Mxyssmii?"_s, '\0'),
     BUILTIN(monster, "Mxysmi?"_s, '\0'),
     BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'),
     BUILTIN(killmonster, "ME"_s, '\0'),
diff --git a/src/map/script-fun.hpp b/src/map/script-fun.hpp
index 81d68fe..61feb17 100644
--- a/src/map/script-fun.hpp
+++ b/src/map/script-fun.hpp
@@ -20,6 +20,8 @@
 //    You should have received a copy of the GNU General Public License
 //    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+#include "script-fun.t.hpp"
+
 #include "fwd.hpp"
 
 #include "../strings/literal.hpp"
diff --git a/src/map/script-fun.t.hpp b/src/map/script-fun.t.hpp
new file mode 100644
index 0000000..d0c753b
--- /dev/null
+++ b/src/map/script-fun.t.hpp
@@ -0,0 +1,96 @@
+#pragma once
+//    script-fun.t.hpp - EAthena script frontend, engine, and library.
+//
+//    Copyright © ????-2004 Athena Dev Teams
+//    Copyright © 2004-2011 The Mana World Development Team
+//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+//    This file is part of The Mana World (Athena server)
+//
+//    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 <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+namespace tmwa
+{
+namespace map
+{
+enum class MobInfo : uint8_t
+{
+    ID             =  0,
+    ENG_NAME       =  1,
+    JAP_NAME       =  2,
+    LVL            =  3,
+    HP             =  4,
+    SP             =  5,
+    BASE_EXP       =  6,
+    JOB_EXP        =  7,
+    RANGE1         =  8,
+    ATK1           =  9,
+    ATK2           = 10,
+    DEF            = 11,
+    MDEF           = 12,
+    STR            = 13,
+    AGI            = 14,
+    VIT            = 15,
+    INT            = 16,
+    DEX            = 17,
+    LUK            = 18,
+    RANGE2         = 19,
+    RANGE3         = 20,
+    SCALE          = 21,
+    RACE           = 22,
+    ELEMENT        = 23,
+    ELEMENT_LVL    = 24,
+    MODE           = 25,
+    SPEED          = 26,
+    ADELAY         = 27,
+    AMOTION        = 28,
+    DMOTION        = 29,
+    MUTATION_NUM   = 30,
+    MUTATION_POWER = 31,
+    DROPID1        = 32,
+    DROPNAME1      = 33,
+    DROPPERCENT1   = 34,
+    DROPID2        = 35,
+    DROPNAME2      = 36,
+    DROPPERCENT2   = 37,
+    DROPID3        = 38,
+    DROPNAME3      = 39,
+    DROPPERCENT3   = 40,
+    DROPID4        = 41,
+    DROPNAME4      = 42,
+    DROPPERCENT4   = 43,
+    DROPID5        = 44,
+    DROPNAME5      = 45,
+    DROPPERCENT5   = 46,
+    DROPID6        = 47,
+    DROPNAME6      = 48,
+    DROPPERCENT6   = 49,
+    DROPID7        = 50,
+    DROPNAME7      = 51,
+    DROPPERCENT7   = 52,
+    DROPID8        = 53,
+    DROPNAME8      = 54,
+    DROPPERCENT8   = 55,
+};
+
+enum class MobInfo_DropArrays : uint8_t
+{
+    IDS      =  0,
+    NAMES    =  1,
+    PERCENTS =  2,
+};
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/skill.cpp b/src/map/skill.cpp
index b90207f..4182f5a 100644
--- a/src/map/skill.cpp
+++ b/src/map/skill.cpp
@@ -748,6 +748,8 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa
         case StatusChange::SC_COOLDOWN_MT:
         case StatusChange::SC_COOLDOWN_R:
         case StatusChange::SC_COOLDOWN_AR:
+        case StatusChange::SC_COOLDOWN_ENCH:
+        case StatusChange::SC_COOLDOWN_KOY:
             break;
 
             /* option2 */
@@ -1023,6 +1025,8 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
         case StatusChange::SC_COOLDOWN_MT:
         case StatusChange::SC_COOLDOWN_R:
         case StatusChange::SC_COOLDOWN_AR:
+        case StatusChange::SC_COOLDOWN_ENCH:
+        case StatusChange::SC_COOLDOWN_KOY:
             break;
         case StatusChange::SC_FLYING_BACKPACK:
             updateflag = SP::WEIGHT;
diff --git a/src/mmo/skill.t.hpp b/src/mmo/skill.t.hpp
index 166543c..1509852 100644
--- a/src/mmo/skill.t.hpp
+++ b/src/mmo/skill.t.hpp
@@ -57,6 +57,8 @@ enum class StatusChange : uint16_t
     SC_COOLDOWN_MT      = 73,   // Mana Tyrant cooldown
     SC_COOLDOWN_R       = 74,   // Kaflosh cooldown
     SC_COOLDOWN_AR      = 75,   // Frillyar cooldown
+    SC_COOLDOWN_ENCH    = 76,   // Enchanter cooldown
+    SC_COOLDOWN_KOY     = 77,   // Koyntety cooldown
 
     SC_POISON           = 132,  // bad; actually used
 
-- 
cgit v1.2.3-70-g09d2