summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml91
-rw-r--r--.gitmodules3
-rw-r--r--Makefile.in11
-rw-r--r--README.md98
-rwxr-xr-xconfigure8
m---------deps/googletest0
-rw-r--r--src/admin/ladmin.cpp3
-rw-r--r--src/ast/item_test.cpp18
-rw-r--r--src/char/char.cpp16
-rw-r--r--src/login/login.cpp2
-rw-r--r--src/map/atcommand.cpp174
-rw-r--r--src/map/map.hpp6
-rw-r--r--src/map/mob.cpp5
-rw-r--r--src/map/npc-parse.cpp8
-rw-r--r--src/map/npc.cpp43
-rw-r--r--src/map/pc.cpp6
-rw-r--r--src/map/script-call.cpp8
-rw-r--r--src/map/script-fun.cpp215
-rwxr-xr-xtools/config.py2
-rw-r--r--tools/debug-debug.gdb15
-rwxr-xr-xtools/protocol.py67
21 files changed, 547 insertions, 252 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..f7c750a
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,91 @@
+# Copied in from Moubootaur Legends's Hercules .gitlab-ci.yml
+stages:
+ - build
+ - test
+
+variables: &base_vars
+ DEBIAN_COMMON_PACKAGES: make git gcc g++
+ # Depth of clone. If no tag is made after this many commits, then
+ # the git describe call and version header generation will fail.
+ GIT_DEPTH: 100 # Will break again eventually.
+
+.prerequisites: &prerequisites
+ before_script:
+ - uname -a
+ - apt-get update
+ - apt-get install -y -qq $INSTALL_PACKAGES $DEBIAN_COMMON_PACKAGES
+
+
+# Active server OS?
+re:ubuntu1804:build:
+ <<: *prerequisites
+ stage: build
+ image: ubuntu:18.04
+ variables:
+ <<: *base_vars
+ INSTALL_PACKAGES: python
+ script:
+ - echo "Building TMW Athena $CI_BUILD_NAME"
+ - git submodule update --init
+ - git fetch -t
+ - printf "Building TMW Athena version %s\n" "$(git describe --tags HEAD)"
+ - ./configure --user
+ - make
+ - whoami
+ - make install
+ artifacts: # required for test stage
+ untracked: true
+ expire_in: 30 mins
+
+
+
+
+# Next server OS?
+re:ubuntu2204:build:
+ <<: *prerequisites
+ stage: build
+ image: ubuntu:22.04
+ variables:
+ <<: *base_vars
+ INSTALL_PACKAGES: python2
+ script:
+ - ln -s /usr/bin/python2 /usr/bin/python
+ - git submodule update --init
+ - git fetch -t
+ - printf "Building TMW Athena version %s\n" "$(git describe --tags HEAD)"
+ - ./configure --user
+ - make
+ - whoami
+ - make install
+ artifacts: # required for test stage
+ untracked: true
+ expire_in: 30 mins
+
+
+
+
+# Disabled. fails with:
+# (1) GDB failing to resolve a type
+# (2) /usr/bin/ld: Dwarf Error: Can't find .debug_ranges section.
+.re:ubuntu1804:test:
+ <<: *prerequisites
+ stage: test
+ image: ubuntu:18.04
+ variables:
+ <<: *base_vars
+ INSTALL_PACKAGES: python gdb
+ script:
+ - printf "Testing TMW Athena version %s\n" "$(git describe --tags HEAD)"
+ - make test
+
+re:ubuntu2204:test:
+ <<: *prerequisites
+ stage: test
+ image: ubuntu:22.04
+ variables:
+ <<: *base_vars
+ INSTALL_PACKAGES: python2 gdb
+ script:
+ - ln -s /usr/bin/python2 /usr/bin/python
+ - printf "Testing TMW Athena version %s\n" "$(git describe --tags HEAD)"
+ - make test
diff --git a/.gitmodules b/.gitmodules
index 931035d..8197e60 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "deps/attoconf"]
path = deps/attoconf
url = https://github.com/o11c/attoconf.git
+[submodule "deps/googletest"]
+ path = deps/googletest
+ url = https://github.com/google/googletest.git
diff --git a/Makefile.in b/Makefile.in
index 610e7f6..10f909c 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -492,8 +492,15 @@ lib/%.a:
${GTEST_BINARIES}: obj/gtest_main.pdc.o obj/gtest-all.pdc.o
-# This isn't perfect.
-$(filter %_test.pdc.o,${PDC_OBJECTS}) obj/gtest_main.pdc.o obj/gtest-all.pdc.o: override CPPFLAGS += -DGTEST_HAS_PTHREAD=0 -I${GTEST_DIR}
+# Need gtest include for both compile (*_test.pdc.o) and dependency-generation (*_test.d) steps.
+GTEST_NEED_INCLUDE := $(filter %_test.pdc.o,${PDC_OBJECTS}) $(filter %_test.d,${DEPENDS}) obj/gtest_main.pdc.o obj/gtest-all.pdc.o
+${GTEST_NEED_INCLUDE}: override CXXFLAGS += -DGTEST_HAS_PTHREAD=0 -I${GTEST_DIR}/include
+
+
+# Special rule for src/gtest-all.cc, it includes "src/gtest.cc"
+# and thus additionally needs toplevel as include
+obj/gtest-all.pdc.o: override CXXFLAGS += -DGTEST_HAS_PTHREAD=0 -I${GTEST_DIR}
+
obj/gtest-all.pdc.o obj/gtest_main.pdc.o: override WARNINGS :=
obj/gtest%.pdc.o: ${GTEST_DIR}/src/gtest%.cc
$(MKDIR_FIRST)
diff --git a/README.md b/README.md
index 2b61743..b2309f5 100644
--- a/README.md
+++ b/README.md
@@ -2,22 +2,13 @@
![The Mana World logo](share/tmwa/TheManaWorldLogo.png)
-This is TMWA, an MMORPG server used by The Mana World Legacy that uses a
-protocol based on one of many projects that foolishly chose the name "Athena".
+This is TMWA, an MMORPG server used by The Mana World that uses a protocol
+based on one of many projects that foolishly chose the name "Athena".
Specifically, it was forked from eAthena, a Ragnarok Online clone, in 2004.
-# TMWA IS NO LONGER SUPPORTED! don't use this
-
-<br><br>
-
-<details>
-<summary>click _here_ for the old readme</summary>
-
-<br>
-
Take a look at the [wiki](http://wiki.themanaworld.org/index.php/How_to_Develop) for user instructions.
-**Important note:** building from a github-generated tarball does not work!
+**Important note:** building from a GitHub-generated tarball does not work!
You must either build from a git checkout or from a 'make dist' tarball.
@@ -26,24 +17,14 @@ The rest of this file contains information relevant only to:
1. Distributors.
2. Contributors.
-
-TMWA has been maintained by o11c (Ben Longbons) since early 2011 or so.
-Before that, it never really had a proper maintainer, since everyone
-thought that ManaServ was going to be the thing. But it won't ever be,
-at least not for TMW.
-
-TMWA has a [bugtracker](https://github.com/themanaworld/tmwa/issues).
+TMWA has a [bugtracker](https://git.themanaworld.org/legacy/tmwa/-/issues).
But it's probably worth getting on IRC first:
-* Use an IRC client: irc://chat.freenode.net/tmwa
-* Or just use the [webchat](https://webchat.freenode.net/?channels=#tmwa).
-
-Note that this channel is *only* for technical discussion of TMWA (and
-attoconf), not general chat or TMW content development.
+* Use an IRC client: irc://irc.libera.chat/themanaworld-dev
+* Or just use the [webchat](https://web.libera.chat/?channel=#themanaworld-dev).
-I'm active in the Pacific timezone, but I might not have internet access
-all the time. I'm usually never AFK longer than 48 hours; when there is an
-exception, I always tell the content devs who also idle there.
+Note that this channel is not specific to technical discussion of TMWA (and
+attoconf), but for any discussions related to TMW development.
## 1. Distributors.
### Important notes:
@@ -154,22 +135,6 @@ current working directory; this was the only thing that makes sense
since the files are dependent on the server-data. However, a migration
to installed files has begun.
-
-#### tmwa-monitor:
-&lt;DEPRECATED&gt;
-Formerly known as `eathena-monitor`.
-
-An unmaintained tool whose job was to keep restarting the servers
-every time they crashed. It still builds in case anyone was using it,
-but it proved inflexible and has't really been kept up-to-date with our
-(TMW's) server-data, and besides, the server doesn't crash much now.
-There are also a number of other Open Source programs that monitor
-services already.
-
-There is a `run-all` script in the server-data repo that starts the
-appropriate server for that config. On the main server, we instead
-start the servers (and bots) individually in a tmux.
-
#### tmwa-admin:
Formerly known as `ladmin` ("local").
@@ -221,7 +186,7 @@ developers get annoyed when wushin makes us work straight from his
client-data repo.
Currently, there is only *one* set of server data that is known to be
-compatible with TMWA: https://github.com/themanaworld/tmwa-server-data
+compatible with TMWA: https://git.themanaworld.org/legacy/serverdata
The only recommended way of using this is by following the instructions
in the [How to Develop](https://wiki.themanaworld.org/index.php/Dev:How_to_Develop) article. These instructions are only designed
@@ -242,37 +207,14 @@ Note also that The Mana World has not investigated the copyright status
of other sets of server data.
## 2. Contributors.
-The most important thing if you want to help improve TMWA is *talk* to me.
-No, wait, that's the second most important thing.
-
-The real most important thing if you want to help improve TMWA is that it's
-*work*. You can't just stop by and chat for a few hours and help at all.
-If you're going to work on TMWA, you have to be work months in the future.
-
-TMWA was terrible when I got it, and I've only fixed enough to make it
-sane, not pretty. Even a minimal change is likely to touch the whole tree,
-so merge conflicts are a constant problem.
-
-That said, there *are* several tasks that I could use help with. Several
-essential tasks have been left undone just because they don't conflict with
-the main body of my work.
-
-But I do not want someone who will just work for a few hours, go to bed,
-then never return. I have wasted far too many hours answering their
-questions. If you're going to help, you have to actually *help*.
-
-The following skills are good to know required for various tasks:
-
- - ability to read
- - ability to write
- - ability to notice error messages
- - ability to solve your own problems
- - willingness to accept review of your changes. It's not personal if I
- say your work is wrong, I'm just seeing more than you do, and tiny
- details are often incredibly important.
- - familiarity with gdb
- - Python (A low entry barrier, but Python alone is not enough for the
- tasks. Particularly, reread the bit about review.)
- - C++11 (Not a low entry barrier. I'm not really expecting help with this,
- and since this is conflict heavy, please do the other tasks first).
-</details>
+
+You're welcome to help maintain this server, but please make sure to
+get in touch with the currently active maintainers first.
+
+Remember that this server is what currently keeps The Mana World alive,
+so any changes should be made with extreme care. It is important that
+each change be tested and reviewed before it goes live.
+
+Finally, if you got yourself somewhat familiar with this code, please
+consider sticking around either to fix further issues as they come up
+or to help reviewing changes by others. Thanks!
diff --git a/configure b/configure
index 67bcf53..aa1d0fd 100755
--- a/configure
+++ b/configure
@@ -84,7 +84,13 @@ class Configuration(Cxx, Install, ConfigHash, Templates):
def vars(self):
super(Configuration, self).vars()
- self.add_option('GTEST_DIR', init='/usr/src/gtest',
+ # Why submodule gtest?
+ # 1) make test requires gtest-all.cc. This file is shipped by Ubuntu,
+ # but not by Gentoo;
+ # 2) Modern distros ship gtest-1.13+. It requires C++14+, while
+ # TMWA is currently a C++0x codebase.
+ self.add_option('GTEST_DIR',
+ init=os.path.join(os.getcwd(), 'deps/googletest/googletest'),
# http://code.google.com/p/googletest/wiki/FAQ#Why_is_it_not_recommended_to_install_a_pre-compiled_copy_of_Goog
type=filepath, check=lambda build, GTEST_DIR: None,
help='Location of Google Test sources, must contain src/gtest-all.cc (linking to a precompiled library is NOT supported)', hidden=False)
diff --git a/deps/googletest b/deps/googletest
new file mode 160000
+Subproject 2fe3bd994b3189899d93f1d5a881e725e046fdc
diff --git a/src/admin/ladmin.cpp b/src/admin/ladmin.cpp
index 6e8642b..240582b 100644
--- a/src/admin/ladmin.cpp
+++ b/src/admin/ladmin.cpp
@@ -2571,7 +2571,7 @@ void parse_fromlogin(Session *s)
}
else
{
- PRINTF("Variables %i of %i used.\n"_fmt, repeat.size(), ACCOUNT_REG2_NUM);
+ PRINTF("Variables %zi of %zi used.\n"_fmt, repeat.size(), ACCOUNT_REG2_NUM);
auto jlim = std::min(repeat.size(), ACCOUNT_REG2_NUM);
for (size_t j = 0; j < jlim; ++j)
PRINTF("Variable %s == `%i`\n"_fmt, repeat[j].name, repeat[j].value);
@@ -2789,7 +2789,6 @@ int do_init(Slice<ZString> argv)
admin::eathena_interactive_session = isatty(0);
- LADMIN_LOG(""_fmt);
LADMIN_LOG("Configuration file readed.\n"_fmt);
Iprintf("EAthena login-server administration tool.\n"_fmt);
diff --git a/src/ast/item_test.cpp b/src/ast/item_test.cpp
index 7e5f1a9..33ed9cb 100644
--- a/src/ast/item_test.cpp
+++ b/src/ast/item_test.cpp
@@ -87,11 +87,11 @@ namespace item
QuietFd q;
LString inputs[] =
{
- // 1 2 3 4 5
- //2345678901234567890123456789012345678901234567890123456789
- "1,abc , 3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}"_s,
- "1,abc , 3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}\n"_s,
- "1,abc , 3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}\nabc"_s,
+ // 1 2 3 4 5 6
+ //23456789012345678901234567890123456789012345678901234567890123456789
+ "1,abc , 3,4,5,6,7,8,9,10,xx,2,16,12,13,11,1, {end;}, {}"_s,
+ "1,abc , 3,4,5,6,7,8,9,10,xx,2,16,12,13,11,1, {end;}, {}\n"_s,
+ "1,abc , 3,4,5,6,7,8,9,10,xx,2,16,12,13,11,1, {end;}, {}\nabc"_s,
};
for (auto input : inputs)
{
@@ -99,7 +99,7 @@ namespace item
auto res = TRY_UNWRAP(parse_item(lr), FAIL());
EXPECT_TRUE(res.get_success().is_some());
auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL());
- EXPECT_SPAN(top.span, 1,1, 1,58);
+ EXPECT_SPAN(top.span, 1,1, 1,60);
auto p = top.get_if<Item>();
EXPECT_TRUE(p);
if (p)
@@ -136,9 +136,11 @@ namespace item
EXPECT_EQ(p->elv.data, 13);
EXPECT_SPAN(p->view.span, 1,45, 1,46);
EXPECT_EQ(p->view.data, ItemLook::W_BOW);
- EXPECT_SPAN(p->use_script.span, 1,49, 1,54);
+ EXPECT_SPAN(p->mode.span, 1,48, 1,48);
+ EXPECT_EQ(p->mode.data, ItemMode::NO_DROP);
+ EXPECT_SPAN(p->use_script.span, 1,51, 1,56);
EXPECT_EQ(p->use_script.braced_body, "{end;}"_s);
- EXPECT_SPAN(p->equip_script.span, 1,57, 1,58);
+ EXPECT_SPAN(p->equip_script.span, 1,59, 1,60);
EXPECT_EQ(p->equip_script.braced_body, "{}"_s);
}
}
diff --git a/src/char/char.cpp b/src/char/char.cpp
index 70ad049..7ffdd0f 100644
--- a/src/char/char.cpp
+++ b/src/char/char.cpp
@@ -1040,11 +1040,15 @@ int set_account_reg2(AccountId acc, Slice<GlobalReg> reg)
{
if (cd.key.account_id == acc)
{
- for (int i = 0; i < num; ++i)
- cd.data->account_reg2[i] = reg[i];
- cd.data->account_reg2_num = num;
- for (int i = num; i < ACCOUNT_REG2_NUM; ++i)
- cd.data->account_reg2[i] = GlobalReg{};
+ int i = 0;
+ for (const GlobalReg& r : reg)
+ cd.data->account_reg2[i++] = r;
+
+ cd.data->account_reg2_num = i;
+
+ while (i < ACCOUNT_REG2_NUM)
+ cd.data->account_reg2[i++] = GlobalReg{};
+
c++;
}
}
@@ -2900,8 +2904,6 @@ int do_init(Slice<ZString> argv)
if (!loaded_config_yet)
runflag &= load_config_file("conf/tmwa-char.conf"_s, char_::char_confs);
- // a newline in the log...
- CHAR_LOG(""_fmt);
CHAR_LOG("do_init: char-server starting...\n"_fmt);
runflag &= lan_check();
diff --git a/src/login/login.cpp b/src/login/login.cpp
index 8f3aa70..e4c1197 100644
--- a/src/login/login.cpp
+++ b/src/login/login.cpp
@@ -3104,8 +3104,6 @@ bool display_conf_warnings(void)
static
void save_config_in_log(void)
{
- // a newline in the log...
- LOGIN_LOG(""_fmt);
LOGIN_LOG("The login-server starting...\n"_fmt);
// save configuration in log file
diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp
index eaefd4f..346b0ac 100644
--- a/src/map/atcommand.cpp
+++ b/src/map/atcommand.cpp
@@ -1667,36 +1667,100 @@ ATCE atcommand_pvpoff(Session *s, dumb_ptr<map_session_data> sd,
return ATCE::OKAY;
}
+
+
+static int extract_rate(Session *s, ZString message)
+{
+ int rate;
+ AString output;
+
+ if (!extract(message, &rate))
+ {
+ clif_displaymessage(s, "Please enter the new rate"_s);
+ return -1;
+ }
+ if (rate <= 0 || rate > battle_config.max_rate)
+ {
+ output = STRPRINTF("Rate adjustment must be between 1 and %d%%"_fmt,
+ battle_config.max_rate);
+ clif_displaymessage(s, output);
+ return -1;
+ }
+ return rate;
+}
+
+// Command not removed during bexprate/jexprate split
+// because serverdata calls it.
static
ATCE atcommand_exprate(Session *s, dumb_ptr<map_session_data>,
ZString message)
{
- int rate;
+ int rate = extract_rate(s, message);
+ if (rate < 0)
+ return ATCE::USAGE;
- if (!extract(message, &rate) || !rate)
- {
- clif_displaymessage(s,
- "Please, enter a rate adjustement (usage: @exprate <percent>)."_s);
+ battle_config.base_exp_rate = rate;
+ battle_config.job_exp_rate = rate;
+ AString output = STRPRINTF("Base & job XP rates now at %d percent"_fmt, rate);
+ clif_displaymessage(s, output);
+ return ATCE::OKAY;
+}
+
+static
+ATCE atcommand_bexprate(Session *s, dumb_ptr<map_session_data>,
+ ZString message)
+{
+ int rate = extract_rate(s, message);
+ if (rate < 0)
return ATCE::USAGE;
- }
+
battle_config.base_exp_rate = rate;
+ AString output = STRPRINTF("Base XP rate now at %d percent"_fmt, rate);
+ clif_displaymessage(s, output);
+ return ATCE::OKAY;
+}
+
+static
+ATCE atcommand_jexprate(Session *s, dumb_ptr<map_session_data>,
+ ZString message)
+{
+ int rate = extract_rate(s, message);
+ if (rate < 0)
+ return ATCE::USAGE;
+
battle_config.job_exp_rate = rate;
- AString output = STRPRINTF("All Xp at %d percent"_fmt, rate);
+ AString output = STRPRINTF("Job XP rate now at %d percent"_fmt, rate);
clif_displaymessage(s, output);
return ATCE::OKAY;
}
static
-ATCE atcommand_rates(Session *s, dumb_ptr<map_session_data>,
+ATCE atcommand_droprate(Session *s, dumb_ptr<map_session_data>,
ZString message)
{
- AString output = STRPRINTF("Experience rates: Base %d%% / Job %d%%"_fmt, battle_config.base_exp_rate, battle_config.job_exp_rate);
+ int rate = extract_rate(s, message);
+ if (rate < 0)
+ return ATCE::USAGE;
+
+ battle_config.drop_rate = rate;
+ AString output = STRPRINTF("Drops rate now at %d percent"_fmt, rate);
clif_displaymessage(s, output);
- output = STRPRINTF("Drop rate: 100%%"_fmt);
+ return ATCE::OKAY;
+}
+
+static
+ATCE atcommand_rates(Session *s, dumb_ptr<map_session_data>,
+ ZString message)
+{
+ AString output = STRPRINTF(
+ "Experience rates: Base %d%% / Job %d%%. Drop rate: %d%%"_fmt,
+ battle_config.base_exp_rate, battle_config.job_exp_rate,
+ battle_config.drop_rate);
clif_displaymessage(s, output);
return ATCE::OKAY;
}
+
static
ATCE atcommand_pvpon(Session *s, dumb_ptr<map_session_data> sd,
ZString)
@@ -1850,22 +1914,47 @@ ATCE atcommand_mobinfo(Session *s, dumb_ptr<map_session_data> sd,
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, CRITICAL_DEF: %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, get_mob_db(mob_id).critical_def));
- 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));
+ const struct mob_db_& mob = get_mob_db(mob_id);
+
+ clif_displaymessage(s, STRPRINTF(
+ "Monster ID: %i, English Name: %s, Japanese Name: %s"_fmt,
+ mob_id, mob.name, mob.jname));
+ clif_displaymessage(s, STRPRINTF(
+ "Level: %i, HP: %i, SP: %i, Base EXP: %i, JEXP: %i"_fmt,
+ mob.lv, mob.max_hp, mob.max_sp, mob.base_exp, mob.job_exp));
+ clif_displaymessage(s, STRPRINTF(
+ "Range1: %i, ATK1: %i, ATK2: %i, DEF: %i, MDEF: %i, CRITICAL_DEF: %i"_fmt,
+ mob.range, mob.atk1, mob.atk2,
+ mob.def, mob.mdef, mob.critical_def));
+ clif_displaymessage(s, STRPRINTF(
+ "Stats: STR: %i, AGI: %i, VIT: %i, INT: %i, DEX: %i, LUK: %i"_fmt,
+ mob.attrs[ATTR::STR], mob.attrs[ATTR::AGI],
+ mob.attrs[ATTR::VIT], mob.attrs[ATTR::INT],
+ mob.attrs[ATTR::DEX], mob.attrs[ATTR::LUK]));
+ clif_displaymessage(s, STRPRINTF(
+ "Range2: %i, Range3: %i, Scale: %i, Race: %i, Element: %i, Element Level: %i, Mode: %i"_fmt,
+ mob.range2, mob.range3, mob.size, static_cast<int>(mob.race),
+ static_cast<int>(mob.element.element), mob.element.level,
+ static_cast<int>(mob.mode)));
+ clif_displaymessage(s, STRPRINTF(
+ "Speed: %li, Adelay: %li, Amotion: %li, Dmotion: %li"_fmt,
+ mob.speed.count(), mob.adelay.count(),
+ mob.amotion.count(), mob.dmotion.count()));
+
+ if (mob.mutations_nr)
+ {
+ clif_displaymessage(s, STRPRINTF(
+ "May mutate %i attribute up to %i%%"_fmt,
+ mob.mutations_nr, mob.mutation_power));
+ }
for (int i = 0; i < MaxDrops; ++i)
- if (get_mob_db(mob_id).dropitem[i].nameid)
+ if (mob.dropitem[i].nameid)
{
- Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[i].nameid));
+ Option<P<struct item_data>> i_data = Some(itemdb_search(mob.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;
+ int drop_rate = mob.dropitem[i].p.num;
char str[6];
char strpos = 0;
@@ -1922,33 +2011,35 @@ ATCE atcommand_mobinfo(Session *s, dumb_ptr<map_session_data> sd,
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, get_mob_db(mob_id).dropitem[i].nameid, item_name, str, drop_rate2));
+ clif_displaymessage(s, STRPRINTF(
+ "Drop ID %i: %i, Item Name: %s, Drop Chance: %s%% (1:%i)"_fmt,
+ i, mob.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(mob.mode & MobMode::ZERO))
{
- if (bool(get_mob_db(mob_id).mode & MobMode::CAN_MOVE))
+ if (bool(mob.mode & MobMode::CAN_MOVE))
clif_displaymessage(s, STRPRINTF("Mobile"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::LOOTER))
+ if (bool(mob.mode & MobMode::LOOTER))
clif_displaymessage(s, STRPRINTF("Picks up loot"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::AGGRESSIVE))
+ if (bool(mob.mode & MobMode::AGGRESSIVE))
clif_displaymessage(s, STRPRINTF("Aggro"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::ASSIST))
+ if (bool(mob.mode & MobMode::ASSIST))
clif_displaymessage(s, STRPRINTF("Assists"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::CAST_SENSOR))
+ if (bool(mob.mode & MobMode::CAST_SENSOR))
clif_displaymessage(s, STRPRINTF("Cast Sensor"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::BOSS))
+ if (bool(mob.mode & MobMode::BOSS))
clif_displaymessage(s, STRPRINTF("Boss"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::PLANT))
+ if (bool(mob.mode & MobMode::PLANT))
clif_displaymessage(s, STRPRINTF("Plant"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::CAN_ATTACK))
+ if (bool(mob.mode & MobMode::CAN_ATTACK))
clif_displaymessage(s, STRPRINTF("Can attack"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::DETECTOR))
+ if (bool(mob.mode & MobMode::DETECTOR))
clif_displaymessage(s, STRPRINTF("Detector"_fmt));
- if (bool(get_mob_db(mob_id).mode & MobMode::CHANGE_TARGET))
+ if (bool(mob.mode & MobMode::CHANGE_TARGET))
clif_displaymessage(s, STRPRINTF("Change Target"_fmt));
/*
Not needed here i guess
@@ -2552,7 +2643,7 @@ ATCE atcommand_character_stats_full(Session *s, dumb_ptr<map_session_data>,
pl_sd->def2,
pl_sd->def2_rate);
clif_displaymessage(s, output);
- output = STRPRINTF("ADD_SPEED: %d | SPEED_RATE: %d | SPEED_ADDRATE: %d | ASPD: %d | ASPD_RATE: %d | ASPD_ADDRATE: %d"_fmt,
+ output = STRPRINTF("ADD_SPEED: %ld | SPEED_RATE: %d | SPEED_ADDRATE: %d | ASPD: %ld | ASPD_RATE: %d | ASPD_ADDRATE: %d"_fmt,
pl_sd->speed.count(),
pl_sd->speed_rate,
pl_sd->speed_add_rate,
@@ -2576,7 +2667,7 @@ ATCE atcommand_character_stats_full(Session *s, dumb_ptr<map_session_data>,
pl_sd->hit,
pl_sd->hit_rate);
clif_displaymessage(s, output);
- output = STRPRINTF("DEADLY_STRIKE_RATE: %d | DEADLY_STRIKE_ADD_RATE: %d | BASE_WEAPON_DELAY_ADJUST: %d"_fmt,
+ output = STRPRINTF("DEADLY_STRIKE_RATE: %d | DEADLY_STRIKE_ADD_RATE: %d | BASE_WEAPON_DELAY_ADJUST: %ld"_fmt,
pl_sd->deadly_strike,
pl_sd->deadly_strike_add_rate,
pl_sd->base_weapon_delay_adjust.count());
@@ -5599,10 +5690,19 @@ Map<XString, AtCommandInfo> atcommand_info =
"Enable PvP on your map"_s}},
{"exprate"_s, {"<percent>"_s,
60, atcommand_exprate,
- "Set base job/exp rate"_s}},
+ "Set base and job exp rate"_s}},
+ {"bexprate"_s, {"<percent>"_s,
+ 60, atcommand_bexprate,
+ "Set base exp rate"_s}},
+ {"jexprate"_s, {"<percent>"_s,
+ 60, atcommand_jexprate,
+ "Set job exp rate"_s}},
+ {"droprate"_s, {"<percent>"_s,
+ 60, atcommand_droprate,
+ "Set drop rate"_s}},
{"rates"_s, {""_s,
0, atcommand_rates,
- "Show base job/exp rate"_s}},
+ "Show base and job exp and drop rates"_s}},
{"pvpon"_s, {""_s,
60, atcommand_pvpon,
"Disable PvP on your map"_s}},
diff --git a/src/map/map.hpp b/src/map/map.hpp
index 7cf43d5..56d07a5 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -360,7 +360,11 @@ struct npc_data : block_list
Opt0 option;
short flag;
- bool deletion_pending;
+ enum {
+ NOT_DELETING = 0,
+ DELETION_QUEUED = 1,
+ DELETION_ACTIVE = 2
+ } deletion_pending;
Array<Timer, MAX_EVENTTIMER> eventtimer;
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
index 4fd9d6d..996e2bb 100644
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -2556,10 +2556,12 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
// activity
if (sd)
+ {
if (sd->activity.attacks == 2147483647)
sd->activity.attacks = 1;
else
sd->activity.attacks++;
+ }
if (md->hp > 0)
{
@@ -2736,6 +2738,9 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
if (sd && md && battle_config.pk_mode == 1
&& (get_mob_db(md->mob_class).lv - sd->status.base_level >= 20))
drop_rate.num *= 1.25; // pk_mode increase drops if 20 level difference [Valaris]
+
+ // server-wide drop rate scaling
+ drop_rate.num = (drop_rate.num * battle_config.drop_rate) / 100;
if (!random_::chance(drop_rate))
continue;
diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp
index 47b851c..df1a09a 100644
--- a/src/map/npc-parse.cpp
+++ b/src/map/npc-parse.cpp
@@ -164,7 +164,7 @@ bool npc_load_warp(ast::npc::Warp& warp)
nd->warp.xs = xs;
nd->warp.ys = ys;
- nd->deletion_pending = false;
+ nd->deletion_pending = npc_data::NOT_DELETING;
npc_warp++;
nd->bl_type = BL::NPC;
@@ -228,7 +228,7 @@ bool npc_load_shop(ast::npc::Shop& shop)
nd->opt2 = Opt2::ZERO;
nd->opt3 = Opt3::ZERO;
- nd->deletion_pending = false;
+ nd->deletion_pending = npc_data::NOT_DELETING;
npc_shop++;
nd->bl_type = BL::NPC;
@@ -458,7 +458,7 @@ bool npc_load_script_none(ast::script::ScriptBody& body, ast::npc::ScriptNone& s
nd->opt2 = Opt2::ZERO;
nd->opt3 = Opt3::ZERO;
- nd->deletion_pending = false;
+ nd->deletion_pending = npc_data::NOT_DELETING;
npc_script++;
nd->bl_type = BL::NPC;
@@ -568,7 +568,7 @@ bool npc_load_script_map(ast::script::ScriptBody& body, ast::npc::ScriptMap& scr
nd->opt2 = Opt2::ZERO;
nd->opt3 = Opt3::ZERO;
- nd->deletion_pending = false;
+ nd->deletion_pending = npc_data::NOT_DELETING;
npc_script++;
nd->bl_type = BL::NPC;
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index ee2f30c..7d3e62b 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -357,7 +357,7 @@ void npc_eventtimer(TimerData *, tick_t, BlockId, NpcEvent data)
data);
return;
});
- if ((nd = ev->nd) == nullptr || nd->deletion_pending == true)
+ if ((nd = ev->nd) == nullptr || nd->deletion_pending != npc_data::NOT_DELETING)
{
if (battle_config.error_log)
PRINTF("npc_event: event not found [%s]\n"_fmt,
@@ -591,7 +591,7 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
ev.pos = ev2->pos;
}
- if ((nd = ev.nd) == nullptr || nd->deletion_pending == true)
+ if ((nd = ev.nd) == nullptr || nd->deletion_pending != npc_data::NOT_DELETING)
{
if (!mob_kill && battle_config.error_log)
PRINTF("npc_event: event not found [%s]\n"_fmt,
@@ -767,13 +767,22 @@ int npc_click(dumb_ptr<map_session_data> sd, BlockId id)
}
}
- if (npc_checknear(sd, id)) {
+ if (npc_checknear(sd, id))
+ {
clif_scriptclose(sd, id);
return 1;
}
nd = map_id_is_npc(id);
+ // If someone clicked on an NPC that is about to no longer exist, then
+ // release them
+ if (nd->deletion_pending != npc_data::NOT_DELETING)
+ {
+ clif_scriptclose(sd, id);
+ return 1;
+ }
+
if (nd->flag & 1) // 無効化されている
return 1;
@@ -818,18 +827,19 @@ int npc_scriptcont(dumb_ptr<map_session_data> sd, BlockId id)
nd = map_id_is_npc(id);
- if (!nd /* NPC was disposed? */)
+ // If the NPC is about to be deleted, release the PC
+ if (nd->deletion_pending != npc_data::NOT_DELETING)
{
clif_scriptclose(sd, id);
npc_event_dequeue(sd);
- return 0;
+ return 1;
}
if (nd->is_script()->scr.parent &&
map_id2bl(nd->is_script()->scr.parent) == nullptr)
{
npc_free(nd);
- return 0;
+ return 1;
}
sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), sd->npc_pos), sd->bl_id, id);
@@ -1050,6 +1060,23 @@ void npc_free_internal(dumb_ptr<npc_data> nd_)
nd_->bl_m->npc[nd_->n] = nullptr;
}
+ // Also clean up any events we registered to the global ev_db
+ if (auto nd = nd_->is_script())
+ {
+ std::vector<NpcEvent> to_erase;
+ for (auto& pair : ev_db)
+ {
+ if (pair.second.nd == nd)
+ {
+ to_erase.push_back(pair.first);
+ }
+ }
+ for (auto& key : to_erase)
+ {
+ ev_db.erase(key);
+ }
+ }
+
nd_.delete_();
}
@@ -1074,10 +1101,10 @@ void npc_propagate_update(dumb_ptr<npc_data> nd)
void npc_free(dumb_ptr<npc_data> nd)
{
- if (nd == nullptr || nd->deletion_pending == true)
+ if (nd == nullptr || nd->deletion_pending == npc_data::DELETION_ACTIVE)
return;
- nd->deletion_pending = true;
+ nd->deletion_pending = npc_data::DELETION_ACTIVE;
nd->flag |= 1;
clif_clearchar(nd, BeingRemoveWhy::GONE);
npc_propagate_update(nd);
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index c7c6acf..12af48f 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -1135,7 +1135,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
b_weight = sd->weight;
b_max_weight = sd->max_weight;
earray<int, ATTR, ATTR::COUNT> b_paramb = sd->paramb;
- earray<int, ATTR, ATTR::COUNT> b_parame = sd->paramc;
+ earray<int, ATTR, ATTR::COUNT> b_parame = sd->parame;
earray<SkillValue, SkillID, MAX_SKILL> b_skill = sd->status.skill;
b_hit = sd->hit;
b_flee = sd->flee;
@@ -1566,9 +1566,9 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
if (sd->attack_spell_override || first & (int)CalcStatusKind::MAGIC_OVERRIDE)
sd->aspd = sd->attack_spell_delay;
- /* Red Threshold Calculation (TODO) */
+ /* Red Threshold Calculation */
if (sd->aspd < 300_ms) {
- sd->aspd = 300_ms + ((sd->aspd - 300_ms) * 20 / 20);
+ sd->aspd = 300_ms + ((sd->aspd - 300_ms) * 19 / 20);
}
sd->aspd = std::max(sd->aspd, battle_config.max_aspd);
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index 76bae8d..f551ec4 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -1004,12 +1004,12 @@ int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid,
st.freeloop = 0;
st.is_true = 0;
- for (i = 0; i < args.size(); i++)
+ for (const argrec_t& arg : args)
{
- if (args[i].name.back() == '$')
- pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s);
+ if (arg.name.back() == '$')
+ pc_setregstr(sd, SIR::from(variable_names.intern(arg.name)), arg.v.s);
else
- pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i);
+ pc_setreg(sd, SIR::from(variable_names.intern(arg.name)), arg.v.i);
}
run_script_main(&st, rootscript);
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index e51a692..2c09ba6 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -80,14 +80,14 @@ namespace map
if (st->oid) { \
dumb_ptr<npc_data> nullpo_nd = map_id_is_npc(st->oid); \
if (nullpo_nd && nullpo_nd->name) { \
- PRINTF("script:%s: " #error " @ %s\n"_fmt, BUILTIN_NAME(), nullpo_nd->name); \
+ PRINTF("script:%s: %s @ %s\n"_fmt, BUILTIN_NAME(), error, nullpo_nd->name); \
} else if (nullpo_nd) { \
- PRINTF("script:%s: " #error " (unnamed npc)\n"_fmt, BUILTIN_NAME()); \
+ PRINTF("script:%s: %s (unnamed npc)\n"_fmt, BUILTIN_NAME(), error); \
} else { \
- PRINTF("script:%s: " #error " (no npc)\n"_fmt, BUILTIN_NAME()); \
+ PRINTF("script:%s: %s (no npc)\n"_fmt, BUILTIN_NAME(), error); \
} \
} else { \
- PRINTF("script:%s: " #error " (no npc)\n"_fmt, BUILTIN_NAME()); \
+ PRINTF("script:%s: %s (no npc)\n"_fmt, BUILTIN_NAME(), error); \
} \
st->state = ScriptEndState::END; \
return; \
@@ -111,7 +111,7 @@ static
void builtin_mes(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
sd->state.npc_dialog_mes = 1;
RString mes = HARG(0) ? conv_str(st, &AARG(0)) : ""_s;
clif_scriptmes(sd, st->oid, mes);
@@ -125,7 +125,7 @@ static
void builtin_mesq(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
sd->state.npc_dialog_mes = 1;
RString mes = HARG(0) ? conv_str(st, &AARG(0)) : ""_s;
MString mesq;
@@ -145,8 +145,8 @@ void builtin_mesn(ScriptState *st)
dumb_ptr<map_session_data> sd = script_rid2sd(st);
dumb_ptr<npc_data> nd;
nd = map_id_is_npc(st->oid);
- script_nullpo_end(nd, "npc not found");
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(nd, "npc not found"_s);
+ script_nullpo_end(sd, "player not found"_s);
sd->state.npc_dialog_mes = 1;
RString mes = HARG(0) ? conv_str(st, &AARG(0)) : RString(nd->name.xislice_h(std::find(nd->name.begin(), nd->name.end(), '#'))); // strnpcinf
MString mesq;
@@ -164,7 +164,7 @@ static
void builtin_clear(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
clif_npc_action(sd, st->oid, 9, 0, 0, 0);
}
@@ -389,7 +389,7 @@ static
void builtin_next(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
st->state = ScriptEndState::STOP;
clif_scriptnext(sd, st->oid);
}
@@ -411,7 +411,7 @@ void builtin_close(ScriptState *st)
}
st->state = ScriptEndState::END;
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (sd->state.npc_dialog_mes)
clif_scriptclose(sd, st->oid);
@@ -428,7 +428,7 @@ void builtin_close2(ScriptState *st)
{
st->state = ScriptEndState::STOP;
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (sd->state.npc_dialog_mes)
clif_scriptclose(sd, st->oid);
else
@@ -443,7 +443,7 @@ static
void builtin_menu(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (sd->state.menu_or_input == 0)
{
@@ -676,7 +676,7 @@ void builtin_isat(ScriptState *st)
x = conv_num(st, &AARG(1));
y = conv_num(st, &AARG(2));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack,
(x == sd->bl_x) && (y == sd->bl_y)
@@ -695,7 +695,7 @@ void builtin_warp(ScriptState *st)
MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
x = conv_num(st, &AARG(1));
y = conv_num(st, &AARG(2));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
pc_setpos(sd, str, x, y, BeingRemoveWhy::GONE);
}
@@ -748,7 +748,7 @@ void builtin_heal(ScriptState *st)
hp = conv_num(st, &AARG(0));
sp = conv_num(st, &AARG(1));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if(sd != nullptr && (sd->status.hp < 1 && hp > 0)){
pc_setstand(sd);
@@ -901,7 +901,7 @@ void builtin_input(ScriptState *st)
char postfix = name.back();
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (sd->state.menu_or_input)
{
// Second time (rerun)
@@ -962,7 +962,7 @@ void builtin_requestitem(ScriptState *st)
}
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (sd->state.menu_or_input)
{
// Second time (rerunline)
@@ -1055,7 +1055,7 @@ void builtin_requestlang(ScriptState *st)
ZString name = variable_names.outtern(reg.base());
char postfix = name.back();
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (postfix != '$')
{
@@ -1247,7 +1247,7 @@ void builtin_foreach(ScriptState *st)
else if (st->rid)
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
map_foreachinarea(std::bind(builtin_foreach_sub, ph::_1, event, sd->bl_id),
m,
@@ -1279,12 +1279,23 @@ void builtin_destroy(ScriptState *st)
/* Not safe to call destroy if others may also be paused on this NPC! */
if (st->rid) {
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
npc_event_dequeue(sd);
}
+ // Cancel all existing timers on the NPC.
+ // They "would" never fire, and we don't want race conditions here.
+ for (int i = 0; i < MAX_EVENTTIMER; i++)
+ {
+ nd->eventtimer[i].cancel();
+ }
+ // Schedule the NPC to be freed on the next available tick.
+ // Scripts can be invoked under iteration of the ev_db global event
+ // database, and we don't want to invalidate active iterators.
+ nd->deletion_pending = npc_data::DELETION_QUEUED;
+ nd->eventtimer[0] = Timer(gettick(), std::bind(npc_free, nd));
+
nd = nd->is_script();
- npc_free(nd);
st->oid = BlockId();
if (!HARG(0))
@@ -1350,7 +1361,7 @@ void builtin_puppet(ScriptState *st)
nd->npc_subtype = NpcSubtype::SCRIPT;
npc_script++;
- nd->deletion_pending = false;
+ nd->deletion_pending = npc_data::NOT_DELETING;
nd->n = map_addnpc(nd->bl_m, nd);
@@ -1453,7 +1464,7 @@ void builtin_set(ScriptState *st)
else
{
bl = script_rid2sd(st);
- script_nullpo_end(bl, "player not found");
+ script_nullpo_end(bl, "player not found"_s);
}
int val = conv_num(st, &AARG(1));
@@ -1628,14 +1639,14 @@ void builtin_setarray(ScriptState *st)
else
bl = map_id_is_npc(wrap<BlockId>(tid));
}
- script_nullpo_end(bl, "npc not found");
+ script_nullpo_end(bl, "npc not found"_s);
if (st->oid && bl->bl_id != st->oid)
j = getarraysize2(reg, bl);
}
else if (prefix != '$' && !name.startswith(".@"_s))
{
bl = map_id_is_player(st->rid);
- script_nullpo_end(bl, "player not found");
+ script_nullpo_end(bl, "player not found"_s);
}
for (; i < st->end - st->start - 2 && j < 256; i++, j++)
@@ -1674,7 +1685,7 @@ void builtin_cleararray(ScriptState *st)
else if (prefix != '$' && !name.startswith(".@"_s))
{
bl = map_id_is_player(st->rid);
- script_nullpo_end(bl, "player not found");
+ script_nullpo_end(bl, "player not found"_s);
}
for (int i = 0; i < sz; i++)
@@ -1847,7 +1858,7 @@ void builtin_gmlog(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
ZString message = ZString(conv_str(st, &AARG(0)));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
log_atcommand(sd, STRPRINTF("{SCRIPT} %s"_fmt, message));
}
@@ -1861,7 +1872,7 @@ void builtin_setlook(ScriptState *st)
dumb_ptr<map_session_data> sd = script_rid2sd(st);
LOOK type = LOOK(conv_num(st, &AARG(0)));
int val = conv_num(st, &AARG(1));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
pc_changelook(sd, type, val);
@@ -1881,7 +1892,7 @@ void builtin_countitem(ScriptState *st)
struct script_data *data;
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
data = &AARG(0);
get_val(st, data);
@@ -1929,7 +1940,7 @@ void builtin_checkweight(ScriptState *st)
struct script_data *data;
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
data = &AARG(0);
get_val(st, data);
@@ -1978,7 +1989,7 @@ void builtin_getitem(ScriptState *st)
struct script_data *data;
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
data = &AARG(0);
get_val(st, data);
@@ -2082,7 +2093,7 @@ void builtin_delitem(ScriptState *st)
struct script_data *data;
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
data = &AARG(0);
get_val(st, data);
@@ -2133,7 +2144,7 @@ static
void builtin_getversion(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, unwrap<ClientVersion>(sd->client_version));
}
@@ -2220,7 +2231,7 @@ void builtin_strcharinfo(ScriptState *st)
else
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
num = conv_num(st, &AARG(0));
if (num == 0)
@@ -2277,7 +2288,7 @@ void builtin_getequipid(ScriptState *st)
else
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
num = conv_num(st, &AARG(0));
IOff0 i = pc_checkequip(sd, equip[num - 1]);
if (i.ok())
@@ -2336,7 +2347,7 @@ void builtin_bonus(ScriptState *st)
type = SP(conv_num(st, &AARG(0)));
int val = conv_num(st, &AARG(1));
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
pc_bonus(sd, type, val);
}
@@ -2357,7 +2368,7 @@ void builtin_bonus2(ScriptState *st)
int type2 = conv_num(st, &AARG(1));
int val = conv_num(st, &AARG(2));
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
pc_bonus2(sd, type, type2, val);
}
@@ -2378,7 +2389,7 @@ void builtin_skill(ScriptState *st)
if (HARG(2))
flag = conv_num(st, &AARG(2));
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
pc_skill(sd, id, level, flag);
clif_skillinfoblock(sd);
@@ -2397,7 +2408,7 @@ void builtin_setskill(ScriptState *st)
SkillID id = static_cast<SkillID>(conv_num(st, &AARG(0)));
level = conv_num(st, &AARG(1));
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
level = std::min(level, MAX_SKILL_LEVEL);
level = std::max(level, 0);
@@ -2415,7 +2426,7 @@ void builtin_getskilllv(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
SkillID id = SkillID(conv_num(st, &AARG(0)));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, pc_checkskill(sd, id));
}
@@ -2427,7 +2438,7 @@ static
void builtin_overrideattack(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
//PRINTF("inside override attack!!\n"_fmt);
if (HARG(0))
@@ -2477,7 +2488,7 @@ static
void builtin_getgmlevel(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, pc_isGM(sd).get_all_bits());
}
@@ -2510,7 +2521,7 @@ void builtin_getopt2(ScriptState *st)
dumb_ptr<map_session_data> sd;
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, static_cast<uint16_t>(sd->opt2));
@@ -2527,7 +2538,7 @@ void builtin_setopt2(ScriptState *st)
Opt2 new_opt2 = Opt2(conv_num(st, &AARG(0)));
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (new_opt2 == sd->opt2)
return;
@@ -2548,7 +2559,7 @@ void builtin_savepoint(ScriptState *st)
dumb_ptr<map_session_data> sd = script_rid2sd(st);
int x, y;
MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
x = conv_num(st, &AARG(1));
y = conv_num(st, &AARG(2));
@@ -2648,7 +2659,7 @@ void builtin_openstorage(ScriptState *st)
// int sync = 0;
// if (st->end >= 3) sync = conv_num(st,& (st->stack->stack_data[st->start+2]));
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
// if (sync) {
st->state = ScriptEndState::STOP;
@@ -2668,7 +2679,7 @@ void builtin_getexp(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
int base = 0, job = 0;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
base = conv_num(st, &AARG(0));
job = conv_num(st, &AARG(1));
@@ -2981,7 +2992,7 @@ void builtin_mobinfo_droparrays(ScriptState *st)
else if (prefix != '$' && !name.startswith(".@"_s))
{
bl = map_id_is_player(st->rid);
- script_nullpo_end(bl, "player not found");
+ script_nullpo_end(bl, "player not found"_s);
}
switch (request)
@@ -3334,7 +3345,7 @@ void builtin_addtimer(ScriptState *st)
else if (st->rid)
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
pc_addeventtimer(sd, tick, event);
}
@@ -3367,7 +3378,7 @@ void builtin_initnpctimer(ScriptState *st)
nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0)))));
else
nd_ = map_id_is_npc(st->oid);
- script_nullpo_end(nd_, "no npc");
+ script_nullpo_end(nd_, "no npc"_s);
assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
dumb_ptr<npc_data_script> nd = nd_->is_script();
@@ -3388,7 +3399,7 @@ void builtin_startnpctimer(ScriptState *st)
nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0)))));
else
nd_ = map_id_is_npc(st->oid);
- script_nullpo_end(nd_, "no npc");
+ script_nullpo_end(nd_, "no npc"_s);
assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
dumb_ptr<npc_data_script> nd = nd_->is_script();
@@ -3408,7 +3419,7 @@ void builtin_stopnpctimer(ScriptState *st)
nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0)))));
else
nd_ = map_id_is_npc(st->oid);
- script_nullpo_end(nd_, "no npc");
+ script_nullpo_end(nd_, "no npc"_s);
assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
dumb_ptr<npc_data_script> nd = nd_->is_script();
@@ -3430,7 +3441,7 @@ void builtin_getnpctimer(ScriptState *st)
nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(1)))));
else
nd_ = map_id_is_npc(st->oid);
- script_nullpo_end(nd_, "no npc");
+ script_nullpo_end(nd_, "no npc"_s);
assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
dumb_ptr<npc_data_script> nd = nd_->is_script();
@@ -3463,7 +3474,7 @@ void builtin_setnpctimer(ScriptState *st)
nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(1)))));
else
nd_ = map_id_is_npc(st->oid);
- script_nullpo_end(nd_, "no npc");
+ script_nullpo_end(nd_, "no npc"_s);
assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
dumb_ptr<npc_data_script> nd = nd_->is_script();
@@ -3482,7 +3493,7 @@ void builtin_npcaction(ScriptState *st)
int id = 0;
short x = HARG(2) ? conv_num(st, &AARG(2)) : 0;
short y = HARG(3) ? conv_num(st, &AARG(3)) : 0;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if(HARG(1))
{
@@ -3507,7 +3518,7 @@ static
void builtin_camera(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (HARG(0))
{
@@ -3564,7 +3575,7 @@ void builtin_setnpcdirection(ScriptState *st)
else
nd_ = map_id_is_npc(st->oid);
- script_nullpo_end(nd_, "no npc");
+ script_nullpo_end(nd_, "no npc"_s);
if (bool(conv_num(st, &AARG(1))))
action = DamageType::SIT;
@@ -3580,7 +3591,7 @@ void builtin_setnpcdirection(ScriptState *st)
if (st->rid)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
clif_sitnpc_towards(sd, nd_, action);
clif_setnpcdirection_towards(sd, nd_, dir);
}
@@ -3610,7 +3621,7 @@ void builtin_announce(ScriptState *st)
bl = map_id2bl(st->oid);
else
bl = script_rid2sd(st);
- script_nullpo_end(bl, "player not found");
+ script_nullpo_end(bl, "player not found"_s);
clif_GMmessage(bl, str, flag);
}
else
@@ -3992,7 +4003,7 @@ void builtin_resetstatus(ScriptState *st)
{
dumb_ptr<map_session_data> sd;
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
pc_resetstate(sd);
}
@@ -4174,7 +4185,7 @@ void builtin_setpvpchannel(ScriptState *st)
if (flag < 1)
flag = 0;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
sd->state.pvpchannel = flag;
}
@@ -4191,7 +4202,7 @@ void builtin_getpvpflag(ScriptState *st)
else
sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
int num = conv_num(st, &AARG(0));
int flag = 0;
@@ -4373,7 +4384,7 @@ static
void builtin_getpartnerid2(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status.partner_id));
}
@@ -4438,7 +4449,7 @@ void builtin_explode(ScriptState *st)
else if (prefix != '$' && prefix != '.')
{
bl = map_id2bl(st->rid)->is_player();
- script_nullpo_end(bl, "target player not found");
+ script_nullpo_end(bl, "target player not found"_s);
}
@@ -4489,7 +4500,7 @@ void builtin_getinventorylist(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
int j = 0;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
for (IOff0 i : IOff0::iter())
{
@@ -4520,7 +4531,7 @@ void builtin_getactivatedpoolskilllist(ScriptState *st)
int skill_pool_size = skill_pool(sd, pool_skills);
int i, count = 0;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
for (i = 0; i < skill_pool_size; i++)
{
@@ -4553,7 +4564,7 @@ void builtin_getunactivatedpoolskilllist(ScriptState *st)
dumb_ptr<map_session_data> sd = script_rid2sd(st);
int i, count = 0;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
for (i = 0; i < skill_pool_skills.size(); i++)
{
@@ -4585,7 +4596,7 @@ void builtin_poolskill(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
SkillID skill_id = SkillID(conv_num(st, &AARG(0)));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
skill_pool_activate(sd, skill_id);
clif_skillinfoblock(sd);
@@ -4600,7 +4611,7 @@ void builtin_unpoolskill(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
SkillID skill_id = SkillID(conv_num(st, &AARG(0)));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
skill_pool_deactivate(sd, skill_id);
clif_skillinfoblock(sd);
@@ -4844,7 +4855,7 @@ void builtin_nude(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
for (EQUIP i : EQUIPs)
{
@@ -4864,7 +4875,7 @@ static
void builtin_unequipbyid(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
EQUIP slot_id = EQUIP(conv_num(st, &AARG(0)));
@@ -5005,7 +5016,7 @@ void builtin_title(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
ZString msg = ZString(conv_str(st, &AARG(0)));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
clif_npc_send_title(sd->sess, st->oid, msg);
}
@@ -5026,7 +5037,7 @@ void builtin_smsg(ScriptState *st)
int type = HARG(1) ? conv_num(st, &AARG(0)) : 0;
ZString msg = ZString(conv_str(st, (HARG(1) ? &AARG(1) : &AARG(0))));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (type < 0 || type > 0xFF)
type = 0;
@@ -5041,7 +5052,7 @@ static
void builtin_remotecmd(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (HARG(1))
{
@@ -5068,7 +5079,7 @@ void builtin_sendcollision(ScriptState *st)
short x1, y1, x2, y2;
x1 = x2 = conv_num(st, &AARG(2));
y1 = y2 = conv_num(st, &AARG(3));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
if (HARG(5))
{
@@ -5101,7 +5112,7 @@ void builtin_music(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
ZString msg = ZString(conv_str(st, &AARG(0)));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
clif_change_music(sd, msg);
}
@@ -5126,7 +5137,7 @@ void builtin_mapmask(ScriptState *st)
else if(HARG(1) && nd)
nd->bl_m->mask = map_mask;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
clif_send_mask(sd, map_mask);
}
@@ -5208,7 +5219,7 @@ void builtin_getlook(ScriptState *st)
dumb_ptr<map_session_data> sd = script_rid2sd(st);
LOOK type = LOOK(conv_num(st, &AARG(0)));
int val = -1;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
switch (type)
{
@@ -5252,7 +5263,7 @@ void builtin_getsavepoint(ScriptState *st)
{
int x, y, type;
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
type = conv_num(st, &AARG(0));
@@ -5346,7 +5357,7 @@ void builtin_isin(ScriptState *st)
x2 = conv_num(st, &AARG(3));
y2 = conv_num(st, &AARG(4));
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack,
(sd->bl_x >= x1 && sd->bl_x <= x2)
@@ -5382,7 +5393,7 @@ void builtin_shop(ScriptState *st)
dumb_ptr<map_session_data> sd = script_rid2sd(st);
dumb_ptr<npc_data> nd;
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0))));
nd = npc_name2id(name);
@@ -5400,7 +5411,7 @@ static
void builtin_isdead(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, pc_isdead(sd));
}
@@ -5434,7 +5445,7 @@ static
void builtin_getx(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, sd->bl_x);
}
@@ -5446,7 +5457,7 @@ static
void builtin_gety(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, sd->bl_y);
}
@@ -5458,7 +5469,7 @@ static
void builtin_getdir(ScriptState *st)
{
dumb_ptr<map_session_data> sd = script_rid2sd(st);
- script_nullpo_end(sd, "player not found");
+ script_nullpo_end(sd, "player not found"_s);
push_int<ScriptDataInt>(st->stack, static_cast<uint8_t>(sd->dir));
}
@@ -5485,6 +5496,30 @@ void builtin_getmap(ScriptState *st)
}
/*==========================================
+ * Get the maximum x coordinate of a map
+ *------------------------------------------
+ */
+static
+void builtin_getmapmaxx(ScriptState *st)
+{
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+ push_int<ScriptDataInt>(st->stack, m->xs-1);
+}
+
+/*==========================================
+ * Get the maximum y coordinate of a map
+ *------------------------------------------
+ */
+static
+void builtin_getmapmaxy(ScriptState *st)
+{
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+ push_int<ScriptDataInt>(st->stack, m->ys-1);
+}
+
+/*==========================================
* Get the NPC's info
*------------------------------------------
*/
@@ -5513,7 +5548,7 @@ void builtin_strnpcinfo(ScriptState *st)
nd = map_id_is_npc(st->oid);
}
- script_nullpo_end(nd, "npc not found");
+ script_nullpo_end(nd, "npc not found"_s);
switch(num)
{
@@ -5551,7 +5586,7 @@ void builtin_getnpcx(ScriptState *st)
nd = map_id_is_npc(st->oid);
}
- script_nullpo_end(nd, "no npc");
+ script_nullpo_end(nd, "no npc"_s);
push_int<ScriptDataInt>(st->stack, nd->bl_x);
}
@@ -5573,7 +5608,7 @@ void builtin_getnpcy(ScriptState *st)
nd = map_id_is_npc(st->oid);
}
- script_nullpo_end(nd, "no npc");
+ script_nullpo_end(nd, "no npc"_s);
push_int<ScriptDataInt>(st->stack, nd->bl_y);
}
@@ -5745,6 +5780,8 @@ BuiltinFunction builtin_functions[] =
BUILTIN(getnpcy, "?"_s, 'i'),
BUILTIN(strnpcinfo, "i?"_s, 's'),
BUILTIN(getmap, "?"_s, 's'),
+ BUILTIN(getmapmaxx, "M"_s, 'i'),
+ BUILTIN(getmapmaxy, "M"_s, 'i'),
BUILTIN(mapexit, ""_s, '\0'),
BUILTIN(freeloop, "i"_s, '\0'),
BUILTIN(if_then_else, "iii"_s, 'v'),
diff --git a/tools/config.py b/tools/config.py
index a187c06..2f0781d 100755
--- a/tools/config.py
+++ b/tools/config.py
@@ -601,6 +601,8 @@ def build_config():
battle_conf.opt('item_third_get_time', milliseconds, '1_s')
battle_conf.opt('base_exp_rate', percent, '100')
battle_conf.opt('job_exp_rate', percent, '100')
+ battle_conf.opt('drop_rate', percent, '100')
+ battle_conf.opt('max_rate', percent, '500')
battle_conf.opt('death_penalty_type', i32, '0', min='0', max='2')
battle_conf.opt('death_penalty_base', per10kd, '0')
battle_conf.opt('death_penalty_job', per10kd, '0')
diff --git a/tools/debug-debug.gdb b/tools/debug-debug.gdb
index 2e59437..b50a49b 100644
--- a/tools/debug-debug.gdb
+++ b/tools/debug-debug.gdb
@@ -42,8 +42,21 @@ set print frame-arguments none
set python print-stack full
set logging on
-rbreak do_breakpoint
+# Workaround "Function... not defined in.." (breakpoints not found) (GDB bug)
+# https://sourceware.org/bugzilla/show_bug.cgi?id=15962
+# In some gdb versions rbreak works, in some break does.
+# This code should work for any.
+python
+bpoint = gdb.Breakpoint("do_breakpoint")
+
+if bpoint.pending:
+ print("`break ...` found no breakpoints, trying `rbreak ...`")
+ bpoint.delete()
+ gdb.execute("rbreak do_breakpoint")
+
+end
set logging off
+
commands
silent
python hit_breakpoint()
diff --git a/tools/protocol.py b/tools/protocol.py
index 7419639..cf89a16 100755
--- a/tools/protocol.py
+++ b/tools/protocol.py
@@ -984,6 +984,18 @@ class BasePacket(object):
d[p].post_set(d, n - 1 + (len(self.post) == 1), accum)
return accum
+ def dump_client_enum(self, f):
+ id = self.id
+ define = self.define
+ name = self.name
+ if define and id:
+ f.write(' %s ' % define)
+ f.write(' ' * (30 - len(define)))
+ f.write('= 0x%04x,' % id)
+ if name:
+ f.write(" // " + name)
+ f.write('\n')
+
class FixedPacket(BasePacket):
__slots__ = ('fixed_struct')
@@ -1016,6 +1028,15 @@ class FixedPacket(BasePacket):
self.fixed_struct.dump_convert(f)
f.write('\n')
+ def dump_client_packet_info(self, f):
+ size = self.fixed_struct.size
+ define = self.define
+ if define and id:
+ f.write(' { %s, ' % define)
+ f.write(' ' * (30 - len(define)))
+ f.write('%d, "%s" },' % (size, define))
+ f.write('\n')
+
class VarPacket(BasePacket):
__slots__ = ('head_struct', 'repeat_struct')
@@ -1053,6 +1074,14 @@ class VarPacket(BasePacket):
self.repeat_struct.dump_convert(f)
f.write('\n')
+ def dump_client_packet_info(self, f):
+ define = self.define
+ if define and id:
+ f.write(' { %s, ' % define)
+ f.write(' ' * (30 - len(define)))
+ f.write('VAR, "%s" },' % define)
+ f.write('\n')
+
def sanitize_line(line, n):
if not line:
return line
@@ -1181,6 +1210,20 @@ class Channel(object):
p.dump_convert(f)
f.write('} // namespace tmwa\n')
+ def dump_client_enum(self, f):
+ if any(p.define and p.id for p in self.packets):
+ f.write(' // %s server messages\n' % self.server)
+ for p in self.packets:
+ p.dump_client_enum(f)
+ f.write('\n')
+
+ def dump_client_packet_info(self, f):
+ if any(p.define and p.id for p in self.packets):
+ f.write(' // %s server messages\n' % self.server)
+ for p in self.packets:
+ p.dump_client_packet_info(f)
+ f.write('\n')
+
ident_translation = ''.join(chr(c) if chr(c).isalnum() else '_' for c in range(256))
@@ -1293,6 +1336,20 @@ class Context(object):
ty.dump(f)
f.write('} // namespace tmwa\n')
+ # for net/tmwa/protocol.h in Mana client
+ with OpenWrite(os.path.join(outdir, 'client-enum.hpp')) as f:
+ f.write('enum {\n')
+ for ch in self._channels:
+ ch.dump_client_enum(f)
+ f.write('};\n')
+
+ # for net/tmwa/network.cpp in Mana client
+ with OpenWrite(os.path.join(outdir, 'client-packet-info.cpp')) as f:
+ f.write('static const PacketInfo packet_infos[] = {\n')
+ for ch in self._channels:
+ ch.dump_client_packet_info(f)
+ f.write('};\n')
+
for g in glob.glob(os.path.join(outdir, '*.old')):
print('Obsolete: %s' % g)
os.remove(g)
@@ -3651,7 +3708,7 @@ def build_context():
''',
)
map_user.r(0x00f5, 'storage take',
- define='CSMG_MOVE_FROM_STORAGE',
+ define='CMSG_MOVE_FROM_STORAGE',
fixed=[
at(0, u16, 'packet id'),
at(2, soff1, 'soff1'),
@@ -6267,6 +6324,7 @@ def build_context():
# TOC_MISC
# any client
any_user.r(0x7530, 'version',
+ define='CMSG_SERVER_VERSION_REQUEST',
fixed=[
at(0, u16, 'packet id'),
],
@@ -6278,6 +6336,7 @@ def build_context():
''',
)
any_user.s(0x7531, 'version result',
+ define='SMSG_SERVER_VERSION_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, version, 'version'),
@@ -6290,6 +6349,7 @@ def build_context():
''',
)
any_user.r(0x7532, 'disconnect',
+ define='CMSG_CLIENT_DISCONNECT',
fixed=[
at(0, u16, 'packet id'),
],
@@ -6300,9 +6360,6 @@ def build_context():
Request from client or ladmin to disconnect.
''',
)
- # 0x7530 define='CMSG_SERVER_VERSION_REQUEST',
- # 0x7531 define='SMSG_SERVER_VERSION_RESPONSE',
- # 0x7532 define='CMSG_CLIENT_DISCONNECT',
# TOC_LOGINADMIN
# login admin
@@ -6937,7 +6994,7 @@ def build_context():
delete a login-stored ##register of an account.
''',
)
-
+
# TOC_NEW
## new-style packets
# notify packets, standalone, can occur at any time; always 'payload'