From 41c5b02bbdb78381bc203251fe2a2ef034264642 Mon Sep 17 00:00:00 2001 From: Ben Longbons Date: Wed, 8 Oct 2014 13:05:25 -0700 Subject: Split npc parse functions in to their own file --- src/map/atcommand.cpp | 1 + src/map/fwd.hpp | 5 +- src/map/magic-stmt.cpp | 1 + src/map/map.cpp | 1 + src/map/npc-internal.hpp | 44 +++ src/map/npc-parse.cpp | 876 +++++++++++++++++++++++++++++++++++++++++++++++ src/map/npc-parse.hpp | 47 +++ src/map/npc.cpp | 856 +-------------------------------------------- src/map/npc.hpp | 12 - src/mmo/fwd.hpp | 3 +- 10 files changed, 989 insertions(+), 857 deletions(-) create mode 100644 src/map/npc-internal.hpp create mode 100644 src/map/npc-parse.cpp create mode 100644 src/map/npc-parse.hpp (limited to 'src') diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index 69e4b78..858f617 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -64,6 +64,7 @@ #include "map.hpp" #include "mob.hpp" #include "npc.hpp" +#include "npc-parse.hpp" #include "party.hpp" #include "pc.hpp" #include "skill.hpp" diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp index da5763d..578b08a 100644 --- a/src/map/fwd.hpp +++ b/src/map/fwd.hpp @@ -39,13 +39,16 @@ class npc_data_shop; class npc_data_warp; class npc_data_message; struct NpcEvent; +struct MobName; +struct NpcName; +struct ScriptLabel; +struct ItemName; struct item_data; enum class SP : uint16_t; struct ScriptBuffer; -struct ScriptLabel; struct ScriptState; namespace magic diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp index f33f663..a5d667c 100644 --- a/src/map/magic-stmt.cpp +++ b/src/map/magic-stmt.cpp @@ -42,6 +42,7 @@ #include "magic-interpreter-base.hpp" #include "mob.hpp" #include "npc.hpp" +#include "npc-parse.hpp" #include "pc.hpp" #include "script-call.hpp" #include "skill.hpp" diff --git a/src/map/map.cpp b/src/map/map.cpp index 5e9db39..87092cf 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -70,6 +70,7 @@ #include "magic-v2.hpp" #include "mob.hpp" #include "npc.hpp" +#include "npc-parse.hpp" #include "party.hpp" #include "pc.hpp" #include "script-startup.hpp" diff --git a/src/map/npc-internal.hpp b/src/map/npc-internal.hpp new file mode 100644 index 0000000..65cef3d --- /dev/null +++ b/src/map/npc-internal.hpp @@ -0,0 +1,44 @@ +#pragma once +// npc-internal.hpp - Noncombatants. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// 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 . + +#include "npc.hpp" +#include "fwd.hpp" + +#include "../generic/fwd.hpp" + + +namespace tmwa +{ +extern +BlockId npc_id; + +struct event_data +{ + dumb_ptr nd; + int pos; +}; + +extern +Map ev_db; +extern +DMap> npcs_by_name; +} // namespace tmwa diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp new file mode 100644 index 0000000..6df42c9 --- /dev/null +++ b/src/map/npc-parse.cpp @@ -0,0 +1,876 @@ +#include "npc-parse.hpp" +// npc-parse.cpp - Noncombatants. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// 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 . + +#include + +#include "../compat/nullpo.hpp" + +#include "../strings/astring.hpp" +#include "../strings/xstring.hpp" +#include "../strings/literal.hpp" + +#include "../generic/enum.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/read.hpp" + +#include "../mmo/config_parse.hpp" + +#include "battle.hpp" +#include "clif.hpp" +#include "itemdb.hpp" +#include "map.hpp" +#include "mob.hpp" +#include "npc-internal.hpp" +#include "script-parse.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +static +std::list npc_srcs; + +static +int npc_warp, npc_shop, npc_script, npc_mob; + +// +// 初期化関係 +// + +/*========================================== + * 読み込むnpcファイルのクリア + *------------------------------------------ + */ +static +void npc_clearsrcfile(void) +{ + npc_srcs.clear(); +} + +/*========================================== + * 読み込むnpcファイルの追加 + *------------------------------------------ + */ +void npc_addsrcfile(AString name) +{ + if (name == "clear"_s) + { + npc_clearsrcfile(); + return; + } + + npc_srcs.push_back(name); +} + +/*========================================== + * 読み込むnpcファイルの削除 + *------------------------------------------ + */ +void npc_delsrcfile(XString name) +{ + if (name == "all"_s) + { + npc_clearsrcfile(); + return; + } + + for (auto it = npc_srcs.begin(); it != npc_srcs.end(); ++it) + { + if (*it == name) + { + npc_srcs.erase(it); + return; + } + } +} + +static +void register_npc_name(dumb_ptr nd) +{ + earray types //= + {{ + "WARP"_s, + "SHOP"_s, + "SCRIPT"_s, + "MESSAGE"_s, + }}; + if (!nd->name) + { + if (nd->npc_subtype == NpcSubtype::MESSAGE) + return; + PRINTF("WARNING: npc with no name:\n%s @ %s,%d,%d\n"_fmt, + types[nd->npc_subtype], + nd->bl_m->name_, nd->bl_x, nd->bl_y); + return; + } + if (dumb_ptr nd_old = npcs_by_name.get(nd->name)) + { + if (nd->npc_subtype != NpcSubtype::WARP + || nd_old->npc_subtype != NpcSubtype::WARP) + { + PRINTF("WARNING: replacing npc with name: %s\n"_fmt, nd->name); + PRINTF("old: %s @ %s,%d,%d\n"_fmt, + types[nd_old->npc_subtype], + nd_old->bl_m->name_, nd_old->bl_x, nd_old->bl_y); + PRINTF("new: %s @ %s,%d,%d\n"_fmt, + types[nd->npc_subtype], + nd->bl_m->name_, nd->bl_x, nd->bl_y); + } + } + // TODO also check #s ? + npcs_by_name.put(nd->name, nd); +} + +/*========================================== + * warp行解析 + *------------------------------------------ + */ +int npc_parse_warp(XString w1, XString, NpcName w3, XString w4) +{ + int x, y, xs, ys, to_x, to_y; + int i, j; + MapName mapname, to_mapname; + dumb_ptr nd; + + if (!extract(w1, record<','>(&mapname, &x, &y)) || + !extract(w4, record<','>(&xs, &ys, &to_mapname, &to_x, &to_y))) + { + PRINTF("bad warp line : %s\n"_fmt, w3); + return 1; + } + + map_local *m = map_mapname2mapid(mapname); + + nd.new_(); + nd->bl_id = npc_get_new_npc_id(); + nd->n = map_addnpc(m, nd); + + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = m; + nd->bl_x = x; + nd->bl_y = y; + nd->dir = DIR::S; + nd->flag = 0; + nd->name = w3; + + if (!battle_config.warp_point_debug) + nd->npc_class = WARP_CLASS; + else + nd->npc_class = WARP_DEBUG_CLASS; + nd->speed = 200_ms; + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; + nd->warp.name = to_mapname; + xs += 2; + ys += 2; + nd->warp.x = to_x; + nd->warp.y = to_y; + nd->warp.xs = xs; + nd->warp.ys = ys; + + for (i = 0; i < ys; i++) + { + for (j = 0; j < xs; j++) + { + int x_lo = x - xs / 2; + int y_lo = y - ys / 2; + int xc = x_lo + j; + int yc = y_lo + i; + MapCell t = map_getcell(m, xc, yc); + if (bool(t & MapCell::UNWALKABLE)) + continue; + map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); + } + } + + npc_warp++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::WARP; + map_addblock(nd); + clif_spawnnpc(nd); + register_npc_name(nd); + + return 0; +} + +static +bool extract(XString xs, npc_item_list *itv) +{ + XString name_or_id; + if (!extract(xs, record<':'>(&name_or_id, &itv->value))) + return false; + struct item_data *id = nullptr; + if (extract(name_or_id, &itv->nameid) && itv->nameid) + goto return_true; + + id = itemdb_searchname(name_or_id.rstrip()); + if (id == nullptr) + return false; + itv->nameid = id->nameid; + goto return_true; + +return_true: + if (itv->value < 0) + { + if (id == nullptr) + id = itemdb_search(itv->nameid); + itv->value = id->value_buy * abs(itv->value); + } + return true; +} + +/*========================================== + * shop行解析 + *------------------------------------------ + */ +static +int npc_parse_shop(XString w1, XString, NpcName w3, ZString w4a) +{ + int x, y; + DIR dir; + MapName mapname; + dumb_ptr nd; + ZString::iterator w4comma; + Species npc_class; + + int dir_; // TODO use enum directly in extract + if (!extract(w1, record<','>(&mapname, &x, &y, &dir_)) + || dir_ < 0 || dir_ >= 8 + || (w4comma = std::find(w4a.begin(), w4a.end(), ',')) == w4a.end() + || !extract(w4a.xislice_h(w4comma), &npc_class)) + { + PRINTF("bad shop line : %s\n"_fmt, w3); + return 1; + } + dir = static_cast(dir_); + map_local *m = map_mapname2mapid(mapname); + + nd.new_(); + ZString w4b = w4a.xislice_t(w4comma + 1); + + if (!extract(w4b, vrec<','>(&nd->shop_items))) + { + PRINTF("bad shop items : %s\n"_fmt, w3); + PRINTF(" somewhere --> %s\n"_fmt, w4b); + nd->shop_items.clear(); + } + + if (nd->shop_items.empty()) + { + nd.delete_(); + return 1; + } + + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = m; + nd->bl_x = x; + nd->bl_y = y; + nd->bl_id = npc_get_new_npc_id(); + nd->dir = dir; + nd->flag = 0; + nd->name = w3; + nd->npc_class = npc_class; + nd->speed = 200_ms; + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; + + npc_shop++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::SHOP; + nd->n = map_addnpc(m, nd); + map_addblock(nd); + clif_spawnnpc(nd); + register_npc_name(nd); + + return 0; +} + +/*========================================== + * NPCのラベルデータコンバート + *------------------------------------------ + */ +static +void npc_convertlabel_db(ScriptLabel lname, int pos, dumb_ptr nd) +{ + nullpo_retv(nd); + + struct npc_label_list eln {}; + eln.name = lname; + eln.pos = pos; + nd->scr.label_listv.push_back(std::move(eln)); +} + +/*========================================== + * script行解析 + *------------------------------------------ + */ +static +int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4, + XString first_line, io::ReadFile& fp, int *lines) +{ + int x, y; + DIR dir = DIR::S; + map_local *m; + int xs = 0, ys = 0; // [Valaris] thanks to fov + Species npc_class; + MapName mapname; + std::unique_ptr script = nullptr; + dumb_ptr nd; + int evflag = 0; + + if (w1 == "-"_s) + { + x = 0; + y = 0; + m = nullptr; + } + else + { + int dir_; // TODO use enum directly in extract + if (!extract(w1, record<','>(&mapname, &x, &y, &dir_)) + || dir_ < 0 || dir_ >= 8 + || (w2 == "script"_s && !w4.contains(','))) + { + PRINTF("bad script line : %s\n"_fmt, w3); + return 1; + } + dir = static_cast(dir_); + m = map_mapname2mapid(mapname); + } + + if (w2 == "script"_s) + { + // may be empty + MString srcbuf; + srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{')); + // Note: it was a bug that this was missing. I think. + int startline = *lines; + + // while (!srcbuf.rstrip().endswith('}')) + while (true) + { + auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; }); + if (it != srcbuf.rend() && *it == '}') + break; + + AString line; + if (!fp.getline(line)) + // eof + break; + (*lines)++; + if (!srcbuf) + { + // may be a no-op + srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{')); + // safe to execute more than once + // But will usually only happen once + startline = *lines; + } + else + srcbuf += line; + srcbuf += '\n'; + } + script = parse_script(AString(srcbuf), startline, false); + if (script == nullptr) + // script parse error? + return 1; + } + else + { + assert(0 && "duplicate() is no longer supported!\n"_s); + return 0; + } + + nd.new_(); + + if (m == nullptr) + { + } + else if (extract(w4, record<','>(&npc_class, &xs, &ys))) + { + if (xs >= 0) + xs = xs * 2 + 1; + if (ys >= 0) + ys = ys * 2 + 1; + + if (npc_class != NEGATIVE_SPECIES) + { + + for (int i = 0; i < ys; i++) + { + for (int j = 0; j < xs; j++) + { + int x_lo = x - xs / 2; + int y_lo = y - ys / 2; + int xc = x_lo + j; + int yc = y_lo + i; + MapCell t = map_getcell(m, xc, yc); + if (bool(t & MapCell::UNWALKABLE)) + continue; + map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); + } + } + } + + nd->scr.xs = xs; + nd->scr.ys = ys; + } + else + { + XString w4x = w4; + if (w4x.endswith(',')) + w4x = w4x.xrslice_h(1); + if (!extract(w4x, &npc_class)) + abort(); + nd->scr.xs = 0; + nd->scr.ys = 0; + } + + if (npc_class == NEGATIVE_SPECIES && m != nullptr) + { + evflag = 1; + } + + if (w3.contains(':')) + { + assert(false && "feature removed"_s); + abort(); + } + + { + nd->name = w3; + } + + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = m; + nd->bl_x = x; + nd->bl_y = y; + nd->bl_id = npc_get_new_npc_id(); + nd->dir = dir; + nd->flag = 0; + nd->npc_class = npc_class; + nd->speed = 200_ms; + nd->scr.script = std::move(script); + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; + + npc_script++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::SCRIPT; + if (m != nullptr) + { + nd->n = map_addnpc(m, nd); + map_addblock(nd); + + if (evflag) + { + struct event_data ev {}; + ev.nd = nd; + ev.pos = 0; + NpcEvent npcev; + npcev.npc = nd->name; + npcev.label = ScriptLabel(); + ev_db.insert(npcev, ev); + } + else + clif_spawnnpc(nd); + } + register_npc_name(nd); + + for (auto& pair : scriptlabel_db) + npc_convertlabel_db(pair.first, pair.second, nd); + + for (npc_label_list& el : nd->scr.label_listv) + { + ScriptLabel lname = el.name; + int pos = el.pos; + + if (lname.startswith("On"_s)) + { + struct event_data ev {}; + ev.nd = nd; + ev.pos = pos; + NpcEvent buf; + buf.npc = nd->name; + buf.label = lname; + ev_db.insert(buf, ev); + } + } + + //----------------------------------------- + // ラベルデータからタイマーイベント取り込み + for (npc_label_list& el : nd->scr.label_listv) + { + int t_ = 0; + ScriptLabel lname = el.name; + int pos = el.pos; + if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0) + { + interval_t t = static_cast(t_); + + npc_timerevent_list tel {}; + tel.timer = t; + tel.pos = pos; + + auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel, + [](const npc_timerevent_list& l, const npc_timerevent_list& r) + { + return l.timer < r.timer; + } + ); + assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer); + + nd->scr.timer_eventv.insert(it, std::move(tel)); + } + } + // The counter starts stopped with 0 ticks, which is the first event, + // unless there is none, in which case begin == end. + nd->scr.timer = interval_t::zero(); + nd->scr.next_event = nd->scr.timer_eventv.begin(); + // nd->scr.timerid = nullptr; + + return 0; +} + +/*========================================== + * function行解析 + *------------------------------------------ + */ +static +int npc_parse_function(XString, XString, XString w3, ZString, + XString first_line, io::ReadFile& fp, int *lines) +{ + MString srcbuf; + srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{')); + int startline = *lines; + + while (true) + { + auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; }); + if (it != srcbuf.rend() && *it == '}') + break; + + AString line; + if (!fp.getline(line)) + break; + (*lines)++; + if (!srcbuf) + { + srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{')); + startline = *lines; + } + else + srcbuf += line; + srcbuf += '\n'; + } + std::unique_ptr script = parse_script(AString(srcbuf), startline, false); + if (script == nullptr) + { + // script parse error? + return 1; + } + + userfunc_db.put(w3, std::move(script)); + + return 0; +} + +/*========================================== + * mob行解析 + *------------------------------------------ + */ +static +int npc_parse_mob(XString w1, XString, MobName w3, ZString w4) +{ + int x, y, xs, ys, num; + Species mob_class; + int i; + MapName mapname; + NpcEvent eventname; + dumb_ptr md; + + xs = ys = 0; + int delay1_ = 0, delay2_ = 0; + if (!extract(w1, record<',', 3>(&mapname, &x, &y, &xs, &ys)) || + !extract(w4, record<',', 2>(&mob_class, &num, &delay1_, &delay2_, &eventname))) + { + PRINTF("bad monster line : %s\n"_fmt, w3); + return 1; + } + interval_t delay1 = std::chrono::milliseconds(delay1_); + interval_t delay2 = std::chrono::milliseconds(delay2_); + + map_local *m = map_mapname2mapid(mapname); + + if (num > 1 && battle_config.mob_count_rate != 100) + { + if ((num = num * battle_config.mob_count_rate / 100) < 1) + num = 1; + } + + for (i = 0; i < num; i++) + { + md.new_(); + + md->bl_prev = nullptr; + md->bl_next = nullptr; + md->bl_m = m; + md->bl_x = x; + md->bl_y = y; + if (w3 == ENGLISH_NAME) + md->name = get_mob_db(mob_class).name; + else if (w3 == JAPANESE_NAME) + md->name = get_mob_db(mob_class).jname; + else + md->name = w3; + + md->n = i; + md->mob_class = mob_class; + md->bl_id = npc_get_new_npc_id(); + md->spawn.m = m; + md->spawn.x0 = x; + md->spawn.y0 = y; + md->spawn.xs = xs; + md->spawn.ys = ys; + md->spawn.delay1 = delay1; + md->spawn.delay2 = delay2; + + really_memzero_this(&md->state); + // md->timer = nullptr; + md->target_id = BlockId(); + md->attacked_id = BlockId(); + + md->lootitemv.clear(); + + md->npc_event = eventname; + + md->bl_type = BL::MOB; + map_addiddb(md); + mob_spawn(md->bl_id); + + npc_mob++; + } + + return 0; +} + +/*========================================== + * マップフラグ行の解析 + *------------------------------------------ + */ +static +int npc_parse_mapflag(XString w1, XString, XString w3, ZString w4) +{ + MapName mapname, savemap; + int savex, savey; + + mapname = stringish(w1); + if (!mapname) + return 1; + + map_local *m = map_mapname2mapid(mapname); + if (m == nullptr) + return 1; + + MapFlag mf; + if (!extract(w3, &mf)) + return 1; + + if (battle_config.pk_mode && mf == MapFlag::NOPVP) + { + m->flag.set(MapFlag::NOPVP, 1); + m->flag.set(MapFlag::PVP, 0); + return 0; + } + + if (mf == MapFlag::NOSAVE) + { + if (w4 == "SavePoint"_s) + { + m->save.map_ = stringish("SavePoint"_s); + m->save.x = -1; + m->save.y = -1; + } + else if (extract(w4, record<','>(&savemap, &savex, &savey))) + { + m->save.map_ = savemap; + m->save.x = savex; + m->save.y = savey; + } + } + if (mf == MapFlag::RESAVE) + { + if (extract(w4, record<','>(&savemap, &savex, &savey))) + { + m->resave.map_ = savemap; + m->resave.x = savex; + m->resave.y = savey; + } + } + m->flag.set(mf, true); + + return 0; +} + +dumb_ptr npc_spawn_text(map_local *m, int x, int y, + Species npc_class, NpcName name, AString message) +{ + dumb_ptr retval; + retval.new_(); + retval->bl_id = npc_get_new_npc_id(); + retval->bl_x = x; + retval->bl_y = y; + retval->bl_m = m; + retval->bl_type = BL::NPC; + retval->npc_subtype = NpcSubtype::MESSAGE; + + retval->name = name; + if (message) + retval->message = message; + + retval->npc_class = npc_class; + retval->speed = 200_ms; + + clif_spawnnpc(retval); + map_addblock(retval); + map_addiddb(retval); + register_npc_name(retval); + + return retval; +} + +/*========================================== + * npc初期化 + *------------------------------------------ + */ +bool do_init_npc(void) +{ + bool rv = true; + + for (; !npc_srcs.empty(); npc_srcs.pop_front()) + { + AString nsl = npc_srcs.front(); + io::ReadFile fp(nsl); + if (!fp.is_open()) + { + PRINTF("file not found : %s\n"_fmt, nsl); + rv = false; + continue; + } + PRINTF("\rLoading NPCs [%d]: %-54s"_fmt, unwrap(npc_id) - unwrap(START_NPC_NUM), + nsl); + int lines = 0; + AString zline; + while (fp.getline(zline)) + { + XString w1, w2, w3, w4x; + ZString w4z; + lines++; + + if (is_comment(zline)) + continue; + + if (!extract(zline, record<'|', 3>(&w1, &w2, &w3, &w4x)) || !w1 || !w2 || !w3) + { + FPRINTF(stderr, "%s:%d: Broken script line: %s\n"_fmt, nsl, lines, zline); + rv = false; + continue; + } + if (&*w4x.end() == &*zline.end()) + { + w4z = zline.xrslice_t(w4x.size()); + } + assert(bool(w4x) == bool(w4z)); + + if (w1 != "-"_s && w1 != "function"_s) + { + auto comma = std::find(w1.begin(), w1.end(), ','); + MapName mapname = stringish(w1.xislice_h(comma)); + map_local *m = map_mapname2mapid(mapname); + if (m == nullptr) + { + // "mapname" is not assigned to this server + FPRINTF(stderr, "%s:%d: Map not found: %s\n"_fmt, nsl, lines, mapname); + rv = false; + continue; + } + } + if (w2 == "warp"_s) + { + NpcName npcname = stringish(w3); + npc_parse_warp(w1, w2, npcname, w4z); + } + else if (w2 == "shop"_s) + { + NpcName npcname = stringish(w3); + npc_parse_shop(w1, w2, npcname, w4z); + } + else if (w2 == "script"_s) + { + if (w1 == "function"_s) + { + npc_parse_function(w1, w2, w3, w4z, + w4x, fp, &lines); + } + else + { + NpcName npcname = stringish(w3); + npc_parse_script(w1, w2, npcname, w4z, + w4x, fp, &lines); + } + } + else if (w2 == "monster"_s) + { + MobName mobname = stringish(w3); + npc_parse_mob(w1, w2, mobname, w4z); + } + else if (w2 == "mapflag"_s) + { + npc_parse_mapflag(w1, w2, w3, w4z); + } + else + { + PRINTF("odd script line: %s\n"_fmt, zline); + script_errors++; + } + } + fflush(stdout); + } + PRINTF("\rNPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d] %20s\n"_fmt, + unwrap(npc_id) - unwrap(START_NPC_NUM), npc_warp, npc_shop, npc_script, npc_mob, ""_s); + + if (script_errors) + { + PRINTF("Cowardly refusing to continue after %d errors\n"_fmt, script_errors); + rv = false; + } + return rv; +} +} // namespace tmwa diff --git a/src/map/npc-parse.hpp b/src/map/npc-parse.hpp new file mode 100644 index 0000000..fe528a4 --- /dev/null +++ b/src/map/npc-parse.hpp @@ -0,0 +1,47 @@ +#pragma once +// npc-parse.hpp - Noncombatants. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// 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 . + +#include "fwd.hpp" + +#include "../generic/fwd.hpp" + +#include "../strings/fwd.hpp" + +#include "../mmo/fwd.hpp" + + +namespace tmwa +{ +int npc_parse_warp(XString w1, XString, NpcName w3, XString w4); + +/** + * Spawns and installs a talk-only NPC + * + * \param message The message to speak. If message is nullptr, the NPC will not do anything at all. + */ +dumb_ptr npc_spawn_text(map_local *m, int x, int y, + Species class_, NpcName name, AString message); + +void npc_addsrcfile(AString name); +void npc_delsrcfile(XString name); +bool do_init_npc(void); +} // namespace tmwa diff --git a/src/map/npc.cpp b/src/map/npc.cpp index 22aac75..9a5c45a 100644 --- a/src/map/npc.cpp +++ b/src/map/npc.cpp @@ -1,4 +1,4 @@ -#include "npc.hpp" +#include "npc-internal.hpp" // npc.cpp - Noncombatants. // // Copyright © ????-2004 Athena Dev Teams @@ -38,11 +38,9 @@ #include "../generic/db.hpp" #include "../io/cxxstdio.hpp" -#include "../io/read.hpp" #include "../net/timer.hpp" -#include "../mmo/config_parse.hpp" #include "../mmo/extract.hpp" #include "../mmo/utils.hpp" @@ -52,10 +50,8 @@ #include "clif.hpp" #include "itemdb.hpp" #include "map.hpp" -#include "mob.hpp" #include "pc.hpp" #include "script-call.hpp" -#include "script-parse.hpp" #include "skill.hpp" #include "../poison.hpp" @@ -63,13 +59,7 @@ namespace tmwa { -static -std::list npc_srcs; - -static BlockId npc_id = START_NPC_NUM; -static -int npc_warp, npc_shop, npc_script, npc_mob; BlockId npc_get_new_npc_id(void) { @@ -78,20 +68,24 @@ BlockId npc_get_new_npc_id(void) return rv; } -struct event_data -{ - dumb_ptr nd; - int pos; -}; -static Map ev_db; -static DMap> npcs_by_name; // used for clock-based event triggers // only tm_min, tm_hour, and tm_mday are used static -struct tm ev_tm_b; +struct tm ev_tm_b = +{ + .tm_sec= 0, + .tm_min= -1, + .tm_hour= -1, + .tm_mday= -1, + .tm_mon= 0, + .tm_year= 0, + .tm_wday= 0, + .tm_yday= 0, + .tm_isdst= 0, +}; /*========================================== * NPCの無効化/有効化 @@ -919,718 +913,6 @@ int npc_selllist(dumb_ptr sd, } -// -// 初期化関係 -// - -/*========================================== - * 読み込むnpcファイルのクリア - *------------------------------------------ - */ -static -void npc_clearsrcfile(void) -{ - npc_srcs.clear(); -} - -/*========================================== - * 読み込むnpcファイルの追加 - *------------------------------------------ - */ -void npc_addsrcfile(AString name) -{ - if (name == "clear"_s) - { - npc_clearsrcfile(); - return; - } - - npc_srcs.push_back(name); -} - -/*========================================== - * 読み込むnpcファイルの削除 - *------------------------------------------ - */ -void npc_delsrcfile(XString name) -{ - if (name == "all"_s) - { - npc_clearsrcfile(); - return; - } - - for (auto it = npc_srcs.begin(); it != npc_srcs.end(); ++it) - { - if (*it == name) - { - npc_srcs.erase(it); - return; - } - } -} - -static -void register_npc_name(dumb_ptr nd) -{ - earray types //= - {{ - "WARP"_s, - "SHOP"_s, - "SCRIPT"_s, - "MESSAGE"_s, - }}; - if (!nd->name) - { - if (nd->npc_subtype == NpcSubtype::MESSAGE) - return; - PRINTF("WARNING: npc with no name:\n%s @ %s,%d,%d\n"_fmt, - types[nd->npc_subtype], - nd->bl_m->name_, nd->bl_x, nd->bl_y); - return; - } - if (dumb_ptr nd_old = npcs_by_name.get(nd->name)) - { - if (nd->npc_subtype != NpcSubtype::WARP - || nd_old->npc_subtype != NpcSubtype::WARP) - { - PRINTF("WARNING: replacing npc with name: %s\n"_fmt, nd->name); - PRINTF("old: %s @ %s,%d,%d\n"_fmt, - types[nd_old->npc_subtype], - nd_old->bl_m->name_, nd_old->bl_x, nd_old->bl_y); - PRINTF("new: %s @ %s,%d,%d\n"_fmt, - types[nd->npc_subtype], - nd->bl_m->name_, nd->bl_x, nd->bl_y); - } - } - // TODO also check #s ? - npcs_by_name.put(nd->name, nd); -} - -/*========================================== - * warp行解析 - *------------------------------------------ - */ -int npc_parse_warp(XString w1, XString, NpcName w3, XString w4) -{ - int x, y, xs, ys, to_x, to_y; - int i, j; - MapName mapname, to_mapname; - dumb_ptr nd; - - if (!extract(w1, record<','>(&mapname, &x, &y)) || - !extract(w4, record<','>(&xs, &ys, &to_mapname, &to_x, &to_y))) - { - PRINTF("bad warp line : %s\n"_fmt, w3); - return 1; - } - - map_local *m = map_mapname2mapid(mapname); - - nd.new_(); - nd->bl_id = npc_get_new_npc_id(); - nd->n = map_addnpc(m, nd); - - nd->bl_prev = nd->bl_next = nullptr; - nd->bl_m = m; - nd->bl_x = x; - nd->bl_y = y; - nd->dir = DIR::S; - nd->flag = 0; - nd->name = w3; - - if (!battle_config.warp_point_debug) - nd->npc_class = WARP_CLASS; - else - nd->npc_class = WARP_DEBUG_CLASS; - nd->speed = 200_ms; - nd->option = Opt0::ZERO; - nd->opt1 = Opt1::ZERO; - nd->opt2 = Opt2::ZERO; - nd->opt3 = Opt3::ZERO; - nd->warp.name = to_mapname; - xs += 2; - ys += 2; - nd->warp.x = to_x; - nd->warp.y = to_y; - nd->warp.xs = xs; - nd->warp.ys = ys; - - for (i = 0; i < ys; i++) - { - for (j = 0; j < xs; j++) - { - int x_lo = x - xs / 2; - int y_lo = y - ys / 2; - int xc = x_lo + j; - int yc = y_lo + i; - MapCell t = map_getcell(m, xc, yc); - if (bool(t & MapCell::UNWALKABLE)) - continue; - map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); - } - } - - npc_warp++; - nd->bl_type = BL::NPC; - nd->npc_subtype = NpcSubtype::WARP; - map_addblock(nd); - clif_spawnnpc(nd); - register_npc_name(nd); - - return 0; -} - -static -bool extract(XString xs, npc_item_list *itv) -{ - XString name_or_id; - if (!extract(xs, record<':'>(&name_or_id, &itv->value))) - return false; - struct item_data *id = nullptr; - if (extract(name_or_id, &itv->nameid) && itv->nameid) - goto return_true; - - id = itemdb_searchname(name_or_id.rstrip()); - if (id == nullptr) - return false; - itv->nameid = id->nameid; - goto return_true; - -return_true: - if (itv->value < 0) - { - if (id == nullptr) - id = itemdb_search(itv->nameid); - itv->value = id->value_buy * abs(itv->value); - } - return true; -} - -/*========================================== - * shop行解析 - *------------------------------------------ - */ -static -int npc_parse_shop(XString w1, XString, NpcName w3, ZString w4a) -{ - int x, y; - DIR dir; - MapName mapname; - dumb_ptr nd; - ZString::iterator w4comma; - Species npc_class; - - int dir_; // TODO use enum directly in extract - if (!extract(w1, record<','>(&mapname, &x, &y, &dir_)) - || dir_ < 0 || dir_ >= 8 - || (w4comma = std::find(w4a.begin(), w4a.end(), ',')) == w4a.end() - || !extract(w4a.xislice_h(w4comma), &npc_class)) - { - PRINTF("bad shop line : %s\n"_fmt, w3); - return 1; - } - dir = static_cast(dir_); - map_local *m = map_mapname2mapid(mapname); - - nd.new_(); - ZString w4b = w4a.xislice_t(w4comma + 1); - - if (!extract(w4b, vrec<','>(&nd->shop_items))) - { - PRINTF("bad shop items : %s\n"_fmt, w3); - PRINTF(" somewhere --> %s\n"_fmt, w4b); - nd->shop_items.clear(); - } - - if (nd->shop_items.empty()) - { - nd.delete_(); - return 1; - } - - nd->bl_prev = nd->bl_next = nullptr; - nd->bl_m = m; - nd->bl_x = x; - nd->bl_y = y; - nd->bl_id = npc_get_new_npc_id(); - nd->dir = dir; - nd->flag = 0; - nd->name = w3; - nd->npc_class = npc_class; - nd->speed = 200_ms; - nd->option = Opt0::ZERO; - nd->opt1 = Opt1::ZERO; - nd->opt2 = Opt2::ZERO; - nd->opt3 = Opt3::ZERO; - - npc_shop++; - nd->bl_type = BL::NPC; - nd->npc_subtype = NpcSubtype::SHOP; - nd->n = map_addnpc(m, nd); - map_addblock(nd); - clif_spawnnpc(nd); - register_npc_name(nd); - - return 0; -} - -/*========================================== - * NPCのラベルデータコンバート - *------------------------------------------ - */ -static -void npc_convertlabel_db(ScriptLabel lname, int pos, dumb_ptr nd) -{ - nullpo_retv(nd); - - struct npc_label_list eln {}; - eln.name = lname; - eln.pos = pos; - nd->scr.label_listv.push_back(std::move(eln)); -} - -/*========================================== - * script行解析 - *------------------------------------------ - */ -static -int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4, - XString first_line, io::ReadFile& fp, int *lines) -{ - int x, y; - DIR dir = DIR::S; - map_local *m; - int xs = 0, ys = 0; // [Valaris] thanks to fov - Species npc_class; - MapName mapname; - std::unique_ptr script = nullptr; - dumb_ptr nd; - int evflag = 0; - - if (w1 == "-"_s) - { - x = 0; - y = 0; - m = nullptr; - } - else - { - int dir_; // TODO use enum directly in extract - if (!extract(w1, record<','>(&mapname, &x, &y, &dir_)) - || dir_ < 0 || dir_ >= 8 - || (w2 == "script"_s && !w4.contains(','))) - { - PRINTF("bad script line : %s\n"_fmt, w3); - return 1; - } - dir = static_cast(dir_); - m = map_mapname2mapid(mapname); - } - - if (w2 == "script"_s) - { - // may be empty - MString srcbuf; - srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{')); - // Note: it was a bug that this was missing. I think. - int startline = *lines; - - // while (!srcbuf.rstrip().endswith('}')) - while (true) - { - auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; }); - if (it != srcbuf.rend() && *it == '}') - break; - - AString line; - if (!fp.getline(line)) - // eof - break; - (*lines)++; - if (!srcbuf) - { - // may be a no-op - srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{')); - // safe to execute more than once - // But will usually only happen once - startline = *lines; - } - else - srcbuf += line; - srcbuf += '\n'; - } - script = parse_script(AString(srcbuf), startline, false); - if (script == nullptr) - // script parse error? - return 1; - } - else - { - assert(0 && "duplicate() is no longer supported!\n"_s); - return 0; - } - - nd.new_(); - - if (m == nullptr) - { - } - else if (extract(w4, record<','>(&npc_class, &xs, &ys))) - { - if (xs >= 0) - xs = xs * 2 + 1; - if (ys >= 0) - ys = ys * 2 + 1; - - if (npc_class != NEGATIVE_SPECIES) - { - - for (int i = 0; i < ys; i++) - { - for (int j = 0; j < xs; j++) - { - int x_lo = x - xs / 2; - int y_lo = y - ys / 2; - int xc = x_lo + j; - int yc = y_lo + i; - MapCell t = map_getcell(m, xc, yc); - if (bool(t & MapCell::UNWALKABLE)) - continue; - map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); - } - } - } - - nd->scr.xs = xs; - nd->scr.ys = ys; - } - else - { - XString w4x = w4; - if (w4x.endswith(',')) - w4x = w4x.xrslice_h(1); - if (!extract(w4x, &npc_class)) - abort(); - nd->scr.xs = 0; - nd->scr.ys = 0; - } - - if (npc_class == NEGATIVE_SPECIES && m != nullptr) - { - evflag = 1; - } - - if (w3.contains(':')) - { - assert(false && "feature removed"_s); - abort(); - } - - { - nd->name = w3; - } - - nd->bl_prev = nd->bl_next = nullptr; - nd->bl_m = m; - nd->bl_x = x; - nd->bl_y = y; - nd->bl_id = npc_get_new_npc_id(); - nd->dir = dir; - nd->flag = 0; - nd->npc_class = npc_class; - nd->speed = 200_ms; - nd->scr.script = std::move(script); - nd->option = Opt0::ZERO; - nd->opt1 = Opt1::ZERO; - nd->opt2 = Opt2::ZERO; - nd->opt3 = Opt3::ZERO; - - npc_script++; - nd->bl_type = BL::NPC; - nd->npc_subtype = NpcSubtype::SCRIPT; - if (m != nullptr) - { - nd->n = map_addnpc(m, nd); - map_addblock(nd); - - if (evflag) - { - struct event_data ev {}; - ev.nd = nd; - ev.pos = 0; - NpcEvent npcev; - npcev.npc = nd->name; - npcev.label = ScriptLabel(); - ev_db.insert(npcev, ev); - } - else - clif_spawnnpc(nd); - } - register_npc_name(nd); - - for (auto& pair : scriptlabel_db) - npc_convertlabel_db(pair.first, pair.second, nd); - - for (npc_label_list& el : nd->scr.label_listv) - { - ScriptLabel lname = el.name; - int pos = el.pos; - - if (lname.startswith("On"_s)) - { - struct event_data ev {}; - ev.nd = nd; - ev.pos = pos; - NpcEvent buf; - buf.npc = nd->name; - buf.label = lname; - ev_db.insert(buf, ev); - } - } - - //----------------------------------------- - // ラベルデータからタイマーイベント取り込み - for (npc_label_list& el : nd->scr.label_listv) - { - int t_ = 0; - ScriptLabel lname = el.name; - int pos = el.pos; - if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0) - { - interval_t t = static_cast(t_); - - npc_timerevent_list tel {}; - tel.timer = t; - tel.pos = pos; - - auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel, - [](const npc_timerevent_list& l, const npc_timerevent_list& r) - { - return l.timer < r.timer; - } - ); - assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer); - - nd->scr.timer_eventv.insert(it, std::move(tel)); - } - } - // The counter starts stopped with 0 ticks, which is the first event, - // unless there is none, in which case begin == end. - nd->scr.timer = interval_t::zero(); - nd->scr.next_event = nd->scr.timer_eventv.begin(); - // nd->scr.timerid = nullptr; - - return 0; -} - -/*========================================== - * function行解析 - *------------------------------------------ - */ -static -int npc_parse_function(XString, XString, XString w3, ZString, - XString first_line, io::ReadFile& fp, int *lines) -{ - MString srcbuf; - srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{')); - int startline = *lines; - - while (true) - { - auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; }); - if (it != srcbuf.rend() && *it == '}') - break; - - AString line; - if (!fp.getline(line)) - break; - (*lines)++; - if (!srcbuf) - { - srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{')); - startline = *lines; - } - else - srcbuf += line; - srcbuf += '\n'; - } - std::unique_ptr script = parse_script(AString(srcbuf), startline, false); - if (script == nullptr) - { - // script parse error? - return 1; - } - - userfunc_db.put(w3, std::move(script)); - - return 0; -} - -/*========================================== - * mob行解析 - *------------------------------------------ - */ -static -int npc_parse_mob(XString w1, XString, MobName w3, ZString w4) -{ - int x, y, xs, ys, num; - Species mob_class; - int i; - MapName mapname; - NpcEvent eventname; - dumb_ptr md; - - xs = ys = 0; - int delay1_ = 0, delay2_ = 0; - if (!extract(w1, record<',', 3>(&mapname, &x, &y, &xs, &ys)) || - !extract(w4, record<',', 2>(&mob_class, &num, &delay1_, &delay2_, &eventname))) - { - PRINTF("bad monster line : %s\n"_fmt, w3); - return 1; - } - interval_t delay1 = std::chrono::milliseconds(delay1_); - interval_t delay2 = std::chrono::milliseconds(delay2_); - - map_local *m = map_mapname2mapid(mapname); - - if (num > 1 && battle_config.mob_count_rate != 100) - { - if ((num = num * battle_config.mob_count_rate / 100) < 1) - num = 1; - } - - for (i = 0; i < num; i++) - { - md.new_(); - - md->bl_prev = nullptr; - md->bl_next = nullptr; - md->bl_m = m; - md->bl_x = x; - md->bl_y = y; - if (w3 == ENGLISH_NAME) - md->name = get_mob_db(mob_class).name; - else if (w3 == JAPANESE_NAME) - md->name = get_mob_db(mob_class).jname; - else - md->name = w3; - - md->n = i; - md->mob_class = mob_class; - md->bl_id = npc_get_new_npc_id(); - md->spawn.m = m; - md->spawn.x0 = x; - md->spawn.y0 = y; - md->spawn.xs = xs; - md->spawn.ys = ys; - md->spawn.delay1 = delay1; - md->spawn.delay2 = delay2; - - really_memzero_this(&md->state); - // md->timer = nullptr; - md->target_id = BlockId(); - md->attacked_id = BlockId(); - - md->lootitemv.clear(); - - md->npc_event = eventname; - - md->bl_type = BL::MOB; - map_addiddb(md); - mob_spawn(md->bl_id); - - npc_mob++; - } - - return 0; -} - -/*========================================== - * マップフラグ行の解析 - *------------------------------------------ - */ -static -int npc_parse_mapflag(XString w1, XString, XString w3, ZString w4) -{ - MapName mapname, savemap; - int savex, savey; - - mapname = stringish(w1); - if (!mapname) - return 1; - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return 1; - - MapFlag mf; - if (!extract(w3, &mf)) - return 1; - - if (battle_config.pk_mode && mf == MapFlag::NOPVP) - { - m->flag.set(MapFlag::NOPVP, 1); - m->flag.set(MapFlag::PVP, 0); - return 0; - } - - if (mf == MapFlag::NOSAVE) - { - if (w4 == "SavePoint"_s) - { - m->save.map_ = stringish("SavePoint"_s); - m->save.x = -1; - m->save.y = -1; - } - else if (extract(w4, record<','>(&savemap, &savex, &savey))) - { - m->save.map_ = savemap; - m->save.x = savex; - m->save.y = savey; - } - } - if (mf == MapFlag::RESAVE) - { - if (extract(w4, record<','>(&savemap, &savex, &savey))) - { - m->resave.map_ = savemap; - m->resave.x = savex; - m->resave.y = savey; - } - } - m->flag.set(mf, true); - - return 0; -} - -dumb_ptr npc_spawn_text(map_local *m, int x, int y, - Species npc_class, NpcName name, AString message) -{ - dumb_ptr retval; - retval.new_(); - retval->bl_id = npc_get_new_npc_id(); - retval->bl_x = x; - retval->bl_y = y; - retval->bl_m = m; - retval->bl_type = BL::NPC; - retval->npc_subtype = NpcSubtype::MESSAGE; - - retval->name = name; - if (message) - retval->message = message; - - retval->npc_class = npc_class; - retval->speed = 200_ms; - - clif_spawnnpc(retval); - map_addblock(retval); - map_addiddb(retval); - register_npc_name(retval); - - return retval; -} - static void npc_free_internal(dumb_ptr nd_) { @@ -1676,116 +958,4 @@ void npc_free(dumb_ptr nd) map_delblock(nd); npc_free_internal(nd); } - -/*========================================== - * npc初期化 - *------------------------------------------ - */ -bool do_init_npc(void) -{ - bool rv = true; - // other fields unused - ev_tm_b.tm_min = -1; - ev_tm_b.tm_hour = -1; - ev_tm_b.tm_mday = -1; - - for (; !npc_srcs.empty(); npc_srcs.pop_front()) - { - AString nsl = npc_srcs.front(); - io::ReadFile fp(nsl); - if (!fp.is_open()) - { - PRINTF("file not found : %s\n"_fmt, nsl); - rv = false; - continue; - } - PRINTF("\rLoading NPCs [%d]: %-54s"_fmt, unwrap(npc_id) - unwrap(START_NPC_NUM), - nsl); - int lines = 0; - AString zline; - while (fp.getline(zline)) - { - XString w1, w2, w3, w4x; - ZString w4z; - lines++; - - if (is_comment(zline)) - continue; - - if (!extract(zline, record<'|', 3>(&w1, &w2, &w3, &w4x)) || !w1 || !w2 || !w3) - { - FPRINTF(stderr, "%s:%d: Broken script line: %s\n"_fmt, nsl, lines, zline); - rv = false; - continue; - } - if (&*w4x.end() == &*zline.end()) - { - w4z = zline.xrslice_t(w4x.size()); - } - assert(bool(w4x) == bool(w4z)); - - if (w1 != "-"_s && w1 != "function"_s) - { - auto comma = std::find(w1.begin(), w1.end(), ','); - MapName mapname = stringish(w1.xislice_h(comma)); - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - { - // "mapname" is not assigned to this server - FPRINTF(stderr, "%s:%d: Map not found: %s\n"_fmt, nsl, lines, mapname); - rv = false; - continue; - } - } - if (w2 == "warp"_s) - { - NpcName npcname = stringish(w3); - npc_parse_warp(w1, w2, npcname, w4z); - } - else if (w2 == "shop"_s) - { - NpcName npcname = stringish(w3); - npc_parse_shop(w1, w2, npcname, w4z); - } - else if (w2 == "script"_s) - { - if (w1 == "function"_s) - { - npc_parse_function(w1, w2, w3, w4z, - w4x, fp, &lines); - } - else - { - NpcName npcname = stringish(w3); - npc_parse_script(w1, w2, npcname, w4z, - w4x, fp, &lines); - } - } - else if (w2 == "monster"_s) - { - MobName mobname = stringish(w3); - npc_parse_mob(w1, w2, mobname, w4z); - } - else if (w2 == "mapflag"_s) - { - npc_parse_mapflag(w1, w2, w3, w4z); - } - else - { - PRINTF("odd script line: %s\n"_fmt, zline); - script_errors++; - } - } - fflush(stdout); - } - PRINTF("\rNPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d] %20s\n"_fmt, - unwrap(npc_id) - unwrap(START_NPC_NUM), npc_warp, npc_shop, npc_script, npc_mob, ""_s); - - if (script_errors) - { - PRINTF("Cowardly refusing to continue after %d errors\n"_fmt, script_errors); - rv = false; - } - return rv; -} } // namespace tmwa diff --git a/src/map/npc.hpp b/src/map/npc.hpp index 30b0178..3bd64af 100644 --- a/src/map/npc.hpp +++ b/src/map/npc.hpp @@ -58,29 +58,17 @@ int npc_scriptcont(dumb_ptr, BlockId); int npc_buysellsel(dumb_ptr, BlockId, int); int npc_buylist(dumb_ptr, const std::vector>&); int npc_selllist(dumb_ptr, const std::vector>&); -int npc_parse_warp(XString w1, XString, NpcName w3, XString w4); int npc_enable(NpcName name, bool flag); dumb_ptr npc_name2id(NpcName name); BlockId npc_get_new_npc_id(void); -/** - * Spawns and installs a talk-only NPC - * - * \param message The message to speak. If message is nullptr, the NPC will not do anything at all. - */ -dumb_ptr npc_spawn_text(map_local *m, int x, int y, - Species class_, NpcName name, AString message); - /** * Uninstalls and frees an NPC */ void npc_free(dumb_ptr npc); -void npc_addsrcfile(AString); -void npc_delsrcfile(XString); -bool do_init_npc(void); int npc_event_do_oninit(void); int npc_event_doall_l(ScriptLabel name, BlockId rid, Slice argv); diff --git a/src/mmo/fwd.hpp b/src/mmo/fwd.hpp index 3b56bfb..6612aab 100644 --- a/src/mmo/fwd.hpp +++ b/src/mmo/fwd.hpp @@ -30,11 +30,12 @@ class CharPair; class HumanTimeDiff; +class Species; class AccountId; class CharId; class PartyId; -class ItemUnkId; class ItemNameId; +class BlockId; class GmLevel; class AccountName; -- cgit v1.2.3-60-g2f50