summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml91
-rw-r--r--.gitmodules5
-rw-r--r--CONTRIBUTING.md1
-rw-r--r--Makefile.in11
-rw-r--r--README.md98
-rwxr-xr-xconfigure8
m---------deps/googletest0
-rw-r--r--src/ast/item.cpp1
-rw-r--r--src/ast/item.hpp2
-rw-r--r--src/ast/item_test.cpp20
-rw-r--r--src/char/char.cpp2
-rw-r--r--src/map/atcommand.cpp45
-rw-r--r--src/map/battle.cpp258
-rw-r--r--src/map/chrif.cpp2
-rw-r--r--src/map/clif.cpp13
-rw-r--r--src/map/itemdb.cpp1
-rw-r--r--src/map/itemdb.hpp2
-rw-r--r--src/map/map.cpp2
-rw-r--r--src/map/map.hpp17
-rw-r--r--src/map/map.t.hpp3
-rw-r--r--src/map/mob.cpp262
-rw-r--r--src/map/mob.hpp4
-rw-r--r--src/map/npc.cpp7
-rw-r--r--src/map/pc.cpp299
-rw-r--r--src/map/pc.t.hpp10
-rw-r--r--src/map/script-fun.cpp69
-rw-r--r--src/map/script-fun.t.hpp93
-rw-r--r--src/map/skill-pools.cpp4
-rw-r--r--src/map/skill.cpp6
-rw-r--r--src/map/storage.cpp6
-rw-r--r--src/mmo/clif.t.hpp18
-rw-r--r--src/mmo/consts.hpp6
-rw-r--r--src/mmo/enums.hpp46
-rw-r--r--src/mmo/extract_enums.hpp3
-rw-r--r--src/mmo/skill.t.hpp1
-rw-r--r--tools/debug-debug.gdb15
-rwxr-xr-xtools/protocol.py67
37 files changed, 1029 insertions, 469 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 a07e803..8197e60 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "deps/attoconf"]
path = deps/attoconf
- url = git://github.com/o11c/attoconf.git
+ url = https://github.com/o11c/attoconf.git
+[submodule "deps/googletest"]
+ path = deps/googletest
+ url = https://github.com/google/googletest.git
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..1582527
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1 @@
+We do NOT accept AGPL contributions.
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/ast/item.cpp b/src/ast/item.cpp
index d27e231..623f5c6 100644
--- a/src/ast/item.cpp
+++ b/src/ast/item.cpp
@@ -142,6 +142,7 @@ namespace item
SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.wlv);
SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.elv);
SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.view);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.mode);
item.use_script = TRY(lex_script(lr));
item.equip_script = TRY(lex_script(lr));
ItemOrComment rv = std::move(item);
diff --git a/src/ast/item.hpp b/src/ast/item.hpp
index c772655..90d51a1 100644
--- a/src/ast/item.hpp
+++ b/src/ast/item.hpp
@@ -63,6 +63,8 @@ namespace item
Spanned<int> wlv;
Spanned<int> elv;
Spanned<ItemLook> view;
+ Spanned<ItemMode> mode;
+
ast::script::ScriptBody use_script;
ast::script::ScriptBody equip_script;
};
diff --git a/src/ast/item_test.cpp b/src/ast/item_test.cpp
index 7bb7193..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)
@@ -135,10 +135,12 @@ namespace item
EXPECT_SPAN(p->elv.span, 1,42, 1,43);
EXPECT_EQ(p->elv.data, 13);
EXPECT_SPAN(p->view.span, 1,45, 1,46);
- EXPECT_EQ(p->view.data, ItemLook::BOW);
- EXPECT_SPAN(p->use_script.span, 1,49, 1,54);
+ EXPECT_EQ(p->view.data, ItemLook::W_BOW);
+ 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 8b403b3..70ad049 100644
--- a/src/char/char.cpp
+++ b/src/char/char.cpp
@@ -746,7 +746,7 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t
cd.hair_color = hair_color;
cd.clothes_color = 0;
// removed initial armor/weapon - unused and problematic
- cd.weapon = ItemLook::NONE;
+ cd.weapon = ItemLook::W_FIST;
cd.shield = ItemNameId();
cd.head_top = ItemNameId();
cd.head_mid = ItemNameId();
diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp
index f5ca72d..eaefd4f 100644
--- a/src/map/atcommand.cpp
+++ b/src/map/atcommand.cpp
@@ -1270,7 +1270,7 @@ ATCE atcommand_option(Session *s, dumb_ptr<map_session_data> sd,
sd->status.option = param3;
clif_changeoption(sd);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_displaymessage(s, "Options changed."_s);
return ATCE::OKAY;
@@ -1532,7 +1532,7 @@ ATCE atcommand_baselevelup(Session *s, dumb_ptr<map_session_data> sd,
clif_updatestatus(sd, SP::BASELEVEL);
clif_updatestatus(sd, SP::NEXTBASEEXP);
clif_updatestatus(sd, SP::STATUSPOINT);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
pc_heal(sd, sd->status.max_hp, sd->status.max_sp);
clif_misceffect(sd, 0);
clif_displaymessage(s, "Base level raised."_s);
@@ -1560,7 +1560,7 @@ ATCE atcommand_baselevelup(Session *s, dumb_ptr<map_session_data> sd,
sd->status.base_level += level;
clif_updatestatus(sd, SP::BASELEVEL);
clif_updatestatus(sd, SP::NEXTBASEEXP);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_displaymessage(s, "Base level lowered."_s);
}
@@ -1595,7 +1595,7 @@ ATCE atcommand_joblevelup(Session *s, dumb_ptr<map_session_data> sd,
clif_updatestatus(sd, SP::NEXTJOBEXP);
sd->status.skill_point += level;
clif_updatestatus(sd, SP::SKILLPOINT);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_misceffect(sd, 1);
clif_displaymessage(s, "Job level raised."_s);
}
@@ -1620,7 +1620,7 @@ ATCE atcommand_joblevelup(Session *s, dumb_ptr<map_session_data> sd,
clif_updatestatus(sd, SP::SKILLPOINT);
}
// to add: remove status points from skills
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_displaymessage(s, "Job level lowered."_s);
}
@@ -1852,8 +1852,8 @@ ATCE atcommand_mobinfo(Session *s, dumb_ptr<map_session_data> sd,
clif_displaymessage(s, STRPRINTF("Monster ID: %i, English Name: %s, Japanese Name: %s"_fmt, mob_id, get_mob_db(mob_id).name, get_mob_db(mob_id).jname));
clif_displaymessage(s, STRPRINTF("Level: %i, HP: %i, SP: %i, Base EXP: %i, JEXP: %i"_fmt, get_mob_db(mob_id).lv, get_mob_db(mob_id).max_hp, get_mob_db(mob_id).max_sp, get_mob_db(mob_id).base_exp, get_mob_db(mob_id).job_exp));
- clif_displaymessage(s, STRPRINTF("Range1: %i, ATK1: %i, ATK2: %i, DEF: %i, MDEF: %i"_fmt, get_mob_db(mob_id).range, get_mob_db(mob_id).atk1, get_mob_db(mob_id).atk2, get_mob_db(mob_id).def, get_mob_db(mob_id).mdef));
- clif_displaymessage(s, STRPRINTF("Stats: STR: %i, AGI: %i, VIT: %i, INT: %i, DEX:, %i LUK:, %i"_fmt, get_mob_db(mob_id).attrs[ATTR::STR], get_mob_db(mob_id).attrs[ATTR::AGI], get_mob_db(mob_id).attrs[ATTR::VIT], get_mob_db(mob_id).attrs[ATTR::INT], get_mob_db(mob_id).attrs[ATTR::DEX], get_mob_db(mob_id).attrs[ATTR::LUK]));
+ clif_displaymessage(s, STRPRINTF("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)
@@ -1922,7 +1922,7 @@ 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+1, 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, get_mob_db(mob_id).dropitem[i].nameid, item_name, str, drop_rate2));
}
else
break;
@@ -2283,7 +2283,7 @@ ATCE atcommand_param(Session *s, dumb_ptr<map_session_data> sd,
sd->status.attrs[attr] = new_value;
clif_updatestatus(sd, attr_to_sp(attr));
clif_updatestatus(sd, attr_to_usp(attr));
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_displaymessage(s, "Stat changed."_s);
}
else
@@ -2318,7 +2318,7 @@ ATCE atcommand_all_stats(Session *s, dumb_ptr<map_session_data> sd,
sd->status.attrs[attr] = new_value;
clif_updatestatus(sd, attr_to_sp(attr));
clif_updatestatus(sd, attr_to_usp(attr));
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
count++;
}
}
@@ -2570,12 +2570,23 @@ ATCE atcommand_character_stats_full(Session *s, dumb_ptr<map_session_data>,
clif_displaymessage(s, output);
output = STRPRINTF("PERFECT_HIT_RATE: %d | PERFECT_HIT_ADD_RATE: %d | CRITICAL: %d | CRITICAL_RATE: %d | HIT: %d | HIT_RATE: %d"_fmt,
pl_sd->perfect_hit,
- pl_sd->perfect_hit_add,
+ pl_sd->perfect_hit_add_rate,
pl_sd->critical,
pl_sd->critical_rate,
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,
+ pl_sd->deadly_strike,
+ pl_sd->deadly_strike_add_rate,
+ pl_sd->base_weapon_delay_adjust.count());
+ clif_displaymessage(s, output);
+ output = STRPRINTF("arrow_atk: %d | arrow_cri: %d | arrow_hit: %d | arrow_range: %d"_fmt,
+ pl_sd->arrow_atk,
+ pl_sd->arrow_cri,
+ pl_sd->arrow_hit,
+ pl_sd->arrow_range);
+ clif_displaymessage(s, output);
output = STRPRINTF("HP_DRAIN_RATE: %d | HP_DRAIN_RATE per: %d | SP_DRAIN_RATE: %d | SP_DRAIN_RATE per: %d"_fmt,
pl_sd->hp_drain_rate,
pl_sd->hp_drain_per,
@@ -2671,7 +2682,7 @@ ATCE atcommand_character_option(Session *s, dumb_ptr<map_session_data> sd,
pl_sd->status.option = opt3;
clif_changeoption(pl_sd);
- pc_calcstatus(pl_sd, 0);
+ pc_calcstatus(pl_sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_displaymessage(s, "Character's options changed."_s);
}
else
@@ -2993,7 +3004,7 @@ ATCE atcommand_character_baselevel(Session *s, dumb_ptr<map_session_data> sd,
clif_updatestatus(pl_sd, SP::BASELEVEL);
clif_updatestatus(pl_sd, SP::NEXTBASEEXP);
clif_updatestatus(pl_sd, SP::STATUSPOINT);
- pc_calcstatus(pl_sd, 0);
+ pc_calcstatus(pl_sd, (int)CalcStatusKind::NORMAL_RECALC);
pc_heal(pl_sd, pl_sd->status.max_hp, pl_sd->status.max_sp);
clif_misceffect(pl_sd, 0);
clif_displaymessage(s, "Character's base level raised."_s);
@@ -3023,7 +3034,7 @@ ATCE atcommand_character_baselevel(Session *s, dumb_ptr<map_session_data> sd,
clif_updatestatus(pl_sd, SP::BASELEVEL);
clif_updatestatus(pl_sd, SP::NEXTBASEEXP);
clif_updatestatus(pl_sd, SP::BASEEXP);
- pc_calcstatus(pl_sd, 0);
+ pc_calcstatus(pl_sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_displaymessage(s, "Character's base level lowered."_s);
}
// Reset their stat points to prevent extra points from stacking
@@ -3077,7 +3088,7 @@ ATCE atcommand_character_joblevel(Session *s, dumb_ptr<map_session_data> sd,
clif_updatestatus(pl_sd, SP::NEXTJOBEXP);
pl_sd->status.skill_point += level;
clif_updatestatus(pl_sd, SP::SKILLPOINT);
- pc_calcstatus(pl_sd, 0);
+ pc_calcstatus(pl_sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_misceffect(pl_sd, 1);
clif_displaymessage(s, "character's job level raised."_s);
}
@@ -3101,7 +3112,7 @@ ATCE atcommand_character_joblevel(Session *s, dumb_ptr<map_session_data> sd,
clif_updatestatus(pl_sd, SP::SKILLPOINT);
}
// to add: remove status points from skills
- pc_calcstatus(pl_sd, 0);
+ pc_calcstatus(pl_sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_displaymessage(s, "Character's job level lowered."_s);
}
}
@@ -3584,7 +3595,7 @@ ATCE atcommand_char_wipe(Session *s, dumb_ptr<map_session_data> sd,
pc_additem(pl_sd, &item, 1);
// Reset stats and skills
- pc_calcstatus(pl_sd, 0);
+ pc_calcstatus(pl_sd, (int)CalcStatusKind::NORMAL_RECALC);
pc_resetstate(pl_sd);
pc_resetskill(pl_sd);
pc_setglobalreg(pl_sd, stringish<VarName>("MAGIC_FLAGS"_s), 0);
diff --git a/src/map/battle.cpp b/src/map/battle.cpp
index 52be591..b745e05 100644
--- a/src/map/battle.cpp
+++ b/src/map/battle.cpp
@@ -1469,7 +1469,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
ATK dmg_lv = ATK::ZERO;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data, t_sc_data;
int watk;
- bool da = false;
+ bool da = false, ds = false;
int ac_flag = 0;
int target_distance;
@@ -1482,7 +1482,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
sd->state.attack_type = BF::WEAPON; // 攻撃タイプは武器攻撃 | Attack type is weapon attack
- // ターゲット
+ // ターゲット | target
if (target->bl_type == BL::PC) // 対象がPCなら | If the target is a PC
tsd = target->is_player(); // tsdに代入(tmdはNULL) | Assign to tsd (tmd is NULL)
else if (target->bl_type == BL::MOB) // 対象がMobなら | If the target is a mob
@@ -1561,7 +1561,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
}
OMATCH_END ();
}
- if (sd->status.weapon == ItemLook::BOW)
+ if (sd->status.weapon == ItemLook::W_BOW)
{ // 武器が弓矢の場合 | If the weapon is a bow and arrow
atkmin = watk * ((atkmin < watk) ? atkmin : watk) / 100; // 弓用最低ATK計算 | Bows are calculated with minimum ATK
flag = (flag & ~BF::RANGEMASK) | BF::LONG; // 遠距離攻撃フラグを有効 | Enable ranged attack flag
@@ -1575,141 +1575,154 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
if (atkmin > atkmax && !(sd->state.arrow_atk))
atkmin = atkmax; // 弓は最低が上回る場合あり | Bow may exceed minimum
- if (sd->double_rate > 0 && skill_num == SkillID::ZERO && skill_lv >= 0)
- da = random_::chance({sd->double_rate, 100});
-
- if (!da)
- { // ダブルアタックが発動していない | Double Attack is not activated
- // クリティカル計算 | critical calculation
- cri = battle_get_critical(src);
-
- if (sd->state.arrow_atk)
- cri += sd->arrow_cri;
- cri -= battle_get_luk(target) * 3;
- if (ac_flag)
- cri = 1000;
- }
-
- if (tsd && tsd->critical_def)
- cri = cri * (100 - tsd->critical_def) / 100;
+ if (tmd && !bool(t_mode & MobMode::BOSS)) // deadly strike works only on mobs but not on mobs with boss flag
+ if (sd->deadly_strike > 0)
+ ds = random_::chance({sd->deadly_strike, 100});
- // ダブルアタックが発動していない | Double Attack is not activated
- // 判定(スキルの場合は無視) | Judgment (ignored for skills)
- if (!da && skill_num == SkillID::ZERO && skill_lv >= 0
- && random_::chance({cri, 1000}))
+ if (!ds) // no double, crit and normal damage calculation needed if it is a deadly strike
{
- damage += atkmax;
- if (sd->atk_rate != 100)
- {
- damage = (damage * sd->atk_rate) / 100;
- }
- if (sd->state.arrow_atk)
- damage += sd->arrow_atk;
- type = DamageType::CRITICAL;
- }
- else
- {
- int vitbonusmax;
+ if (sd->double_rate > 0 && skill_num == SkillID::ZERO && skill_lv >= 0)
+ da = random_::chance({sd->double_rate, 100});
- if (atkmax > atkmin)
- damage += random_::in(atkmin, atkmax);
- else
- damage += atkmin;
- if (sd->atk_rate != 100)
+ if (!da)
{
- damage = (damage * sd->atk_rate) / 100;
+ // ダブルアタックが発動していない | Double Attack is not activated
+ // クリティカル計算 | critical calculation
+ cri = battle_get_critical(src);
+
+ if (sd->state.arrow_atk)
+ cri += sd->arrow_cri;
+ cri -= battle_get_luk(target) * 3;
+ if (ac_flag)
+ cri = 1000;
}
- if (sd->state.arrow_atk)
+ if (tsd && tsd->critical_def)
+ cri = cri * (100 - tsd->critical_def) / 100;
+ else if (tmd && tmd->stats[mob_stat::CRITICAL_DEF])
+ cri = cri * (100 - tmd->stats[mob_stat::CRITICAL_DEF]) / 100;
+
+ // ダブルアタックが発動していない | Double Attack is not activated
+ // 判定(スキルの場合は無視) | Judgment (ignored for skills)
+ if (!da && skill_num == SkillID::ZERO && skill_lv >= 0
+ && random_::chance({cri, 1000}))
{
- if (sd->arrow_atk > 0)
- damage += random_::in(0, sd->arrow_atk);
- hitrate += sd->arrow_hit;
+ damage += atkmax;
+ if (sd->atk_rate != 100)
+ {
+ damage = (damage * sd->atk_rate) / 100;
+ }
+ if (sd->state.arrow_atk)
+ damage += sd->arrow_atk;
+ type = DamageType::CRITICAL;
}
-
- if (skill_num != SkillID::ZERO && skill_num != SkillID::NEGATIVE)
+ else
{
- flag = (flag & ~BF::SKILLMASK) | BF::SKILL;
- }
+ int vitbonusmax;
- {
- // 対 象の防御力によるダメージの減少 | Decreased damage due to target's defense
- // ディバインプロテクション(ここでいいのかな?) | Divine Protection (maybe here?)
- if (def1 < 1000000)
- { // DEF, VIT無視 | DEF, VIT ignore
- int t_def;
- target_count =
- 1 + battle_counttargeted(target, src,
- battle_config.vit_penaly_count_lv);
- if (battle_config.vit_penaly_type > 0)
- {
- if (target_count >= battle_config.vit_penaly_count)
+ if (atkmax > atkmin)
+ damage += random_::in(atkmin, atkmax);
+ else
+ damage += atkmin;
+ if (sd->atk_rate != 100)
+ {
+ damage = (damage * sd->atk_rate) / 100;
+ }
+
+ if (sd->state.arrow_atk)
+ {
+ if (sd->arrow_atk > 0)
+ damage += random_::in(0, sd->arrow_atk);
+ hitrate += sd->arrow_hit;
+ }
+
+ if (skill_num != SkillID::ZERO && skill_num != SkillID::NEGATIVE)
+ {
+ flag = (flag & ~BF::SKILLMASK) | BF::SKILL;
+ }
+
+ {
+ // 対 象の防御力によるダメージの減少 | Decreased damage due to target's defense
+ // ディバインプロテクション(ここでいいのかな?) | Divine Protection (maybe here?)
+ if (def1 < 1000000)
+ { // DEF, VIT無視 | DEF, VIT ignore
+ int t_def;
+ target_count =
+ 1 + battle_counttargeted(target, src,
+ battle_config.vit_penaly_count_lv);
+ if (battle_config.vit_penaly_type > 0)
{
- if (battle_config.vit_penaly_type == 1)
+ if (target_count >= battle_config.vit_penaly_count)
{
- def1 =
- (def1 *
- (100 -
- (target_count -
- (battle_config.vit_penaly_count -
- 1)) * battle_config.vit_penaly_num)) /
- 100;
- def2 =
- (def2 *
- (100 -
- (target_count -
- (battle_config.vit_penaly_count -
- 1)) * battle_config.vit_penaly_num)) /
- 100;
- t_vit =
- (t_vit *
- (100 -
- (target_count -
- (battle_config.vit_penaly_count -
- 1)) * battle_config.vit_penaly_num)) /
- 100;
- }
- else if (battle_config.vit_penaly_type == 2)
- {
- def1 -=
- (target_count -
- (battle_config.vit_penaly_count -
- 1)) * battle_config.vit_penaly_num;
- def2 -=
- (target_count -
- (battle_config.vit_penaly_count -
- 1)) * battle_config.vit_penaly_num;
- t_vit -=
- (target_count -
- (battle_config.vit_penaly_count -
- 1)) * battle_config.vit_penaly_num;
+ if (battle_config.vit_penaly_type == 1)
+ {
+ def1 =
+ (def1 *
+ (100 -
+ (target_count -
+ (battle_config.vit_penaly_count -
+ 1)) * battle_config.vit_penaly_num)) /
+ 100;
+ def2 =
+ (def2 *
+ (100 -
+ (target_count -
+ (battle_config.vit_penaly_count -
+ 1)) * battle_config.vit_penaly_num)) /
+ 100;
+ t_vit =
+ (t_vit *
+ (100 -
+ (target_count -
+ (battle_config.vit_penaly_count -
+ 1)) * battle_config.vit_penaly_num)) /
+ 100;
+ }
+ else if (battle_config.vit_penaly_type == 2)
+ {
+ def1 -=
+ (target_count -
+ (battle_config.vit_penaly_count -
+ 1)) * battle_config.vit_penaly_num;
+ def2 -=
+ (target_count -
+ (battle_config.vit_penaly_count -
+ 1)) * battle_config.vit_penaly_num;
+ t_vit -=
+ (target_count -
+ (battle_config.vit_penaly_count -
+ 1)) * battle_config.vit_penaly_num;
+ }
+ if (def1 < 0)
+ def1 = 0;
+ if (def2 < 1)
+ def2 = 1;
+ if (t_vit < 1)
+ t_vit = 1;
}
- if (def1 < 0)
- def1 = 0;
- if (def2 < 1)
- def2 = 1;
- if (t_vit < 1)
- t_vit = 1;
}
- }
- t_def = def2 * 8 / 10;
- vitbonusmax = (t_vit / 20) * (t_vit / 20) - 1;
+ t_def = def2 * 8 / 10;
+ vitbonusmax = (t_vit / 20) * (t_vit / 20) - 1;
- {
{
- damage = damage * (100 - def1) / 100;
- damage -= t_def;
- if (vitbonusmax > 0)
- damage -= random_::in(0, vitbonusmax);
+ {
+ damage = damage * (100 - def1) / 100;
+ damage -= t_def;
+ if (vitbonusmax > 0)
+ damage -= random_::in(0, vitbonusmax);
+ }
}
}
}
}
+ // 精錬ダメージの追加 | Add refining damage
+ { // DEF, VIT無視 | DEF, VIT ignore
+ damage += battle_get_atk2(src);
+ }
}
- // 精錬ダメージの追加 | Add refining damage
- { // DEF, VIT無視 | DEF, VIT ignore
- damage += battle_get_atk2(src);
- }
+ else
+ if (sd->state.arrow_atk)
+ hitrate += sd->arrow_hit;
// 0未満だった場合1に補正 | Corrected to 1 if less than 0
if (damage < 1)
@@ -1741,6 +1754,15 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
if (damage < 0)
damage = 0;
+ if (ds)
+ {
+ damage = tmd->hp;
+ //type = DamageType::DEADLY;
+ // M+ does not support this value on package 0x008a it lags a bit and displays an error message.
+ // This can be implemented if ManaVerse is the only supported client so long DamageType::CRITICAL will do
+ type = DamageType::CRITICAL;
+ }
+
// 右手,短剣のみ | right hand, dagger only
if (da)
{ // ダブルアタックが発動しているか | Is double attack activated?
@@ -2036,7 +2058,7 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
battle_check_range(src, target, 0))
{
// 攻撃対象となりうるので攻撃 | Attack because it can be attacked
- if (sd && sd->status.weapon == ItemLook::BOW)
+ if (sd && sd->status.weapon == ItemLook::W_BOW)
{
IOff0 aidx = sd->equip_index_maybe[EQUIP::ARROW];
if (aidx.ok())
diff --git a/src/map/chrif.cpp b/src/map/chrif.cpp
index 972bfd0..52c311b 100644
--- a/src/map/chrif.cpp
+++ b/src/map/chrif.cpp
@@ -587,7 +587,7 @@ void chrif_changedsex(Session *, const Packet_Fixed<0x2b0d>& fixed)
&& bool(sd->status.inventory[i].equip))
pc_unequipitem(sd, i, CalcStatus::LATER);
}
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
// save character
chrif_save(sd);
sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters
diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index 0eb2b8c..9edf2af 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -3762,7 +3762,7 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd)
clif_equiplist(sd);
clif_initialstatus(sd);
clif_changeoption(sd);
- clif_changelook(sd, LOOK::WEAPON, static_cast<uint16_t>(ItemLook::NONE));
+ clif_changelook(sd, LOOK::WEAPON, static_cast<uint16_t>(ItemLook::W_FIST));
clif_updatestatus(sd, SP::MAXWEIGHT);
clif_updatestatus(sd, SP::WEIGHT);
npc_event_doall_l(stringish<ScriptLabel>("OnPCLoginEvent"_s), sd->bl_id, nullptr);
@@ -4617,6 +4617,11 @@ RecvResult clif_parse_DropItem(Session *s, dumb_ptr<map_session_data> sd)
clif_displaymessage(sd->sess, "Can't drop items here."_s);
return rv;
}
+ if (bool(itemdb_search(sd->status.inventory[fixed.ioff2.unshift()].nameid)->mode & ItemMode::NO_DROP))
+ {
+ clif_displaymessage(sd->sess, "This item can't be dropped."_s);
+ return rv;
+ }
if (sd->npc_id
|| sd->opt1 != Opt1::ZERO)
{
@@ -4895,6 +4900,12 @@ RecvResult clif_parse_TradeAddItem(Session *s, dumb_ptr<map_session_data> sd)
if (fixed.zeny_or_ioff2.index != 0 && !fixed.zeny_or_ioff2.ok())
return RecvResult::Error;
+ if (fixed.zeny_or_ioff2.ok())
+ if (bool(itemdb_search(sd->status.inventory[fixed.zeny_or_ioff2.unshift()].nameid)->mode & ItemMode::NO_TRADE))
+ {
+ clif_displaymessage(sd->sess, "This item can't be traded."_s);
+ return rv;
+ }
trade_tradeadditem(sd, fixed.zeny_or_ioff2, fixed.amount);
return rv;
diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp
index 7b23503..fa675d2 100644
--- a/src/map/itemdb.cpp
+++ b/src/map/itemdb.cpp
@@ -201,6 +201,7 @@ bool itemdb_readdb(ZString filename)
idv.wlv = item.wlv.data;
idv.elv = item.elv.data;
idv.look = item.view.data;
+ idv.mode = item.mode.data;
idv.use_script = compile_script(STRPRINTF("use script %d"_fmt, idv.nameid), item.use_script, true);
idv.equip_script = compile_script(STRPRINTF("equip script %d"_fmt, idv.nameid), item.equip_script, true);
diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp
index 19f40a8..10805f5 100644
--- a/src/map/itemdb.hpp
+++ b/src/map/itemdb.hpp
@@ -50,6 +50,8 @@ struct item_data
ItemLook look;
int elv;
int wlv;
+ ItemMode mode;
+
std::unique_ptr<const ScriptBuffer> use_script;
std::unique_ptr<const ScriptBuffer> equip_script;
};
diff --git a/src/map/map.cpp b/src/map/map.cpp
index e7b0da8..ff69a56 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -816,7 +816,7 @@ void map_quit(dumb_ptr<map_session_data> sd)
pc_stopattack(sd);
pc_delinvincibletimer(sd);
- pc_calcstatus(sd, 4);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC_NO_CLIENT_UPDATE);
clif_clearchar(sd, BeingRemoveWhy::QUIT);
diff --git a/src/map/map.hpp b/src/map/map.hpp
index eddbfad..7cf43d5 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -188,7 +188,7 @@ struct map_session_data : block_list, SessionData
MapName mapname_;
Session *sess; // use this, you idiots!
short to_x, to_y;
- interval_t speed;
+ interval_t speed, speed_cap;
Opt1 opt1;
Opt2 opt2;
Opt3 opt3;
@@ -248,7 +248,7 @@ struct map_session_data : block_list, SessionData
ItemLook weapontype1;
earray<int, ATTR, ATTR::COUNT> paramb, paramc, parame, paramcard;
int hit, flee, flee2;
- interval_t aspd, amotion, dmotion;
+ interval_t aspd, amotion, dmotion, base_weapon_delay_adjust;
int watk, watk2;
int def, def2, mdef, mdef2, critical, matk1, matk2;
int hprate, sprate, dsprate;
@@ -259,10 +259,10 @@ struct map_session_data : block_list, SessionData
int aspd_rate, speed_rate, hprecov_rate, sprecov_rate, critical_def,
double_rate;
int matk_rate;
- int perfect_hit;
+ int perfect_hit, deadly_strike;
int critical_rate, hit_rate, flee_rate, flee2_rate, def_rate, def2_rate,
mdef_rate, mdef2_rate;
- int double_add_rate, speed_add_rate, aspd_add_rate, perfect_hit_add;
+ int double_add_rate, speed_add_rate, aspd_add_rate, perfect_hit_add_rate, deadly_strike_add_rate;
short hp_drain_rate, hp_drain_per, sp_drain_rate, sp_drain_per;
int die_counter;
@@ -307,6 +307,15 @@ struct map_session_data : block_list, SessionData
unsigned guild:1;
} mute;
+ struct
+ {
+ int kills;
+ int casts;
+ int items_used;
+ int tiles_walked;
+ int attacks;
+ } activity;
+
AutoMod automod;
tick_t flood_rates[0x220];
diff --git a/src/map/map.t.hpp b/src/map/map.t.hpp
index d685c01..0f3e608 100644
--- a/src/map/map.t.hpp
+++ b/src/map/map.t.hpp
@@ -71,6 +71,7 @@ enum class mob_stat
DEF,
MDEF,
SPEED,
+ CRITICAL_DEF,
// These must come last:
// [Fate] Encoded as base to 1024: 1024 means 100%
XP_BONUS,
@@ -181,7 +182,7 @@ namespace e
enum class MapCell : uint8_t
{
// the usual thing
- UNWALKABLE = 0x01,
+ UNWALKABLE = 0x01,
// not in tmwa data
_range = 0x04,
};
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
index 0f273a8..4fd9d6d 100644
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -173,6 +173,7 @@ earray<int, mob_stat, mob_stat::XP_BONUS> mutation_value //=
2, // mob_stat::DEF
2, // mob_stat::MDEF
2, // mob_stat::SPEED
+ 2, // mob_stat::CRITICAL_DEF
}};
// The mutation scale indicates how far `up' we can go, with 256 indicating 100% Note that this may stack with multiple
@@ -194,6 +195,7 @@ earray<int, mob_stat, mob_stat::XP_BONUS> mutation_scale //=
48, // mob_stat::DEF
48, // mob_stat::MDEF
80, // mob_stat::SPEED
+ 128, // mob_stat::CRITICAL_DEF
}};
// The table below indicates the `average' value for each of the statistics, or -1 if there is none.
@@ -220,13 +222,14 @@ earray<int, mob_stat, mob_stat::XP_BONUS> mutation_base //=
-1, // mob_stat::DEF
20, // mob_stat::MDEF
-1, // mob_stat::SPEED
+ -1, // mob_stat::CRITICAL_DEF
}};
/*========================================
* Mutates a MOB. For large `direction' values, calling this multiple times will give bigger XP boni.
+ * intensity: positive: strengthen, negative: weaken. 256 = 100%.
*----------------------------------------
*/
-// intensity: positive: strengthen, negative: weaken. 256 = 100%.
static
void mob_mutate(dumb_ptr<mob_data> md, mob_stat stat, int intensity)
{
@@ -306,7 +309,10 @@ void mob_mutate(dumb_ptr<mob_data> md, mob_stat stat, int intensity)
}
}
-// This calculates the exp of a given mob
+/*==========================================
+ * This calculates the exp of a given mob
+ *------------------------------------------
+ */
static
int mob_gen_exp(mob_db_ *mob)
{
@@ -340,6 +346,10 @@ int mob_gen_exp(mob_db_ *mob)
return xp;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
static
void mob_init(dumb_ptr<mob_data> md)
{
@@ -361,6 +371,7 @@ void mob_init(dumb_ptr<mob_data> md)
md->stats[mob_stat::ADELAY] = get_mob_db(mob_class).adelay.count();
md->stats[mob_stat::DEF] = get_mob_db(mob_class).def;
md->stats[mob_stat::MDEF] = get_mob_db(mob_class).mdef;
+ md->stats[mob_stat::CRITICAL_DEF] = get_mob_db(mob_class).critical_def;
md->stats[mob_stat::SPEED] = get_mob_db(mob_class).speed.count();
md->stats[mob_stat::XP_BONUS] = MOB_XP_BONUS_BASE;
@@ -513,46 +524,82 @@ BlockId mob_once_spawn_area(dumb_ptr<map_session_data> sd,
}
// TODO: deprecate these
+/*==========================================
+ *
+ *------------------------------------------
+ */
short mob_get_hair(Species mob_class)
{
return get_mob_db(mob_class).hair;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
short mob_get_hair_color(Species mob_class)
{
return get_mob_db(mob_class).hair_color;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
short mob_get_weapon(Species mob_class)
{
return get_mob_db(mob_class).weapon;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
ItemNameId mob_get_shield(Species mob_class)
{
return get_mob_db(mob_class).shield;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
ItemNameId mob_get_head_top(Species mob_class)
{
return get_mob_db(mob_class).head_top;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
ItemNameId mob_get_head_mid(Species mob_class)
{
return get_mob_db(mob_class).head_mid;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
ItemNameId mob_get_head_buttom(Species mob_class)
{
return get_mob_db(mob_class).head_buttom;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
short mob_get_clothes_color(Species mob_class) // Add for player monster dye - Valaris
{
return get_mob_db(mob_class).clothes_color; // End
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
int mob_get_equip(Species mob_class) // mob equip [Valaris]
{
return get_mob_db(mob_class).equip;
@@ -785,6 +832,10 @@ int mob_check_attack(dumb_ptr<mob_data> md)
return 1;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
static
void mob_ancillary_attack(dumb_ptr<block_list> bl,
dumb_ptr<block_list> mdbl, dumb_ptr<block_list> tbl, tick_t tick)
@@ -811,7 +862,7 @@ int mob_attack(dumb_ptr<mob_data> md, tick_t tick)
return 0;
if (battle_config.monster_attack_direction_change)
- md->dir = map_calc_dir(md, tbl->bl_x, tbl->bl_y); // 向き設定
+ md->dir = map_calc_dir(md, tbl->bl_x, tbl->bl_y); // 向き設定 | Orientation setting
//clif_fixmobpos(md);
@@ -937,7 +988,7 @@ void mob_timer(TimerData *, tick_t tick, BlockId id, unsigned char data)
dumb_ptr<block_list> bl;
bl = map_id2bl(id);
if (bl == nullptr)
- { //攻撃してきた敵がもういないのは正常のようだ
+ { // 攻撃してきた敵がもういないのは正常のようだ | It seems normal that the enemy that attacked us is no longer there.
return;
}
@@ -1297,13 +1348,13 @@ int mob_can_reach(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl, int range)
return 0;
}
- if (md->bl_m != bl->bl_m) // 違うャbプ
+ if (md->bl_m != bl->bl_m) // 違うャbプ | Different type
return 0;
- if (range > 0 && range < arange) // 遠すぎる
+ if (range > 0 && range < arange) // 遠すぎる | too far
return 0;
- if (md->bl_x == bl->bl_x && md->bl_y == bl->bl_y) // 同じャX
+ if (md->bl_x == bl->bl_x && md->bl_y == bl->bl_y) // 同じャX | Same guy
return 1;
// Obstacle judging
@@ -1396,6 +1447,10 @@ int mob_target(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl, int dist)
return 0;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
int mob_aggravate(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl)
{
if (md->bl_type != BL::MOB)
@@ -1430,7 +1485,7 @@ void mob_ai_sub_hard_activesearch(dumb_ptr<block_list> bl,
else
return;
- //敵味方判定
+ // 敵味方判定 | Enemy/ally determination
if (battle_check_target(smd, bl, BCT_ENEMY) == 0)
return;
@@ -1439,11 +1494,11 @@ void mob_ai_sub_hard_activesearch(dumb_ptr<block_list> bl,
else
mode = smd->mode;
- // アクティブでターゲット射程内にいるなら、ロックする
+ // アクティブでターゲット射程内にいるなら、ロックする | Lock if active and within target range
if (bool(mode & MobMode::AGGRESSIVE))
{
Race race = get_mob_db(smd->mob_class).race;
- //対象がPCの場合
+ // 対象がPCの場合 | If the target is a PC
if (tsd &&
!pc_isdead(tsd) &&
tsd->bl_m == smd->bl_m &&
@@ -1457,29 +1512,29 @@ void mob_ai_sub_hard_activesearch(dumb_ptr<block_list> bl,
|| race == Race::_insect
|| race == Race::_demon))
{
- // 妨害がないか判定
- // 到達可能性判定
+ // 妨害がないか判定 | Determine if there is any interference
+ // 到達可能性判定 | Arrival possibility determination
if (mob_can_reach(smd, bl, 12)
&& random_::chance({1, ++*pcc}))
{
- // 範囲内PCで等確率にする
+ // 範囲内PCで等確率にする | Make the probability equal for PCs within the range
smd->target_id = tsd->bl_id;
smd->state.attackable = true;
smd->min_chase = 13;
}
}
}
- //対象がMobの場合
+ // 対象がMobの場合 | If the target is a mob
else if (tmd &&
tmd->bl_m == smd->bl_m &&
(dist =
distance(smd->bl_x, smd->bl_y, tmd->bl_x, tmd->bl_y)) < 9)
{
- // 到達可能性判定
+ // 到達可能性判定 | Arrival possibility determination
if (mob_can_reach(smd, bl, 12)
&& random_::chance({1, ++*pcc}))
{
- // 範囲内で等確率にする
+ // 範囲内で等確率にする | Make the probability equal within the range
smd->target_id = bl->bl_id;
smd->state.attackable = true;
smd->min_chase = 13;
@@ -1677,7 +1732,7 @@ int mob_ai_sub_hard_slavemob(dumb_ptr<mob_data> md, tick_t tick)
|| (!sd->state.gangsterparadise
|| race == Race::_insect
|| race == Race::_demon))
- { // 妨害がないか判定
+ { // 妨害がないか判定 | Determine if there is any interference
md->target_id = sd->bl_id;
md->state.attackable = true;
@@ -1871,7 +1926,7 @@ void mob_ai_sub_hard(dumb_ptr<block_list> bl, tick_t tick)
if (md->master_id && md->state.special_mob_ai == 0)
mob_ai_sub_hard_slavemob(md, tick);
- // アクティヴモンスターの策敵 (?? of a bitter taste TIVU monster)
+ // アクティヴモンスターの策敵 | Active monster's enemy (?? of a bitter taste TIVU monster)
if ((!md->target_id || !md->state.attackable)
&& bool(mode & MobMode::AGGRESSIVE) && !md->state.master_check
&& battle_config.monster_active_enable == 1)
@@ -1923,40 +1978,40 @@ void mob_ai_sub_hard(dumb_ptr<block_list> bl, tick_t tick)
|| (dist =
distance(md->bl_x, md->bl_y, tbl->bl_x,
tbl->bl_y)) >= md->min_chase)
- mob_unlocktarget(md, tick); // 別マップか、視界外
+ mob_unlocktarget(md, tick); // 別マップか、視界外 | Another map or out of sight?
else if (tsd && !bool(mode & MobMode::BOSS)
&& (tsd->state.gangsterparadise
&& race != Race::_insect
&& race != Race::_demon))
- mob_unlocktarget(md, tick); // スキルなどによる策敵妨害
+ mob_unlocktarget(md, tick); // スキルなどによる策敵妨害 | Interfering with enemy tactics using skills etc.
else if (!battle_check_range(md, tbl, get_mob_db(md->mob_class).range))
{
- // 攻撃範囲外なので移動
+ // 攻撃範囲外なので移動 | Move because you are out of attack range
if (!bool(mode & MobMode::CAN_MOVE))
- { // 移動しないモード
+ { // 移動しないモード | No-move mode
mob_unlocktarget(md, tick);
return;
}
- if (!mob_can_move(md)) // 動けない状態にある
+ if (!mob_can_move(md)) // 動けない状態にある | unable to move
return;
- md->state.skillstate = MobSkillState::MSS_CHASE; // 突撃時スキル
+ md->state.skillstate = MobSkillState::MSS_CHASE; // 突撃時スキル | Assault skills
mobskill_use(md, tick, MobSkillCondition::ANY);
if (md->timer && md->state.state != MS::ATTACK
&& (md->next_walktime < tick
|| distance(md->to_x, md->to_y, tbl->bl_x, tbl->bl_y) < 2))
- return; // 既に移動中
+ return; // 既に移動中 | Already on the move
if (!mob_can_reach(md, tbl, (md->min_chase > 13) ? md->min_chase : 13))
- mob_unlocktarget(md, tick); // 移動できないのでタゲ解除(IWとか?)
+ mob_unlocktarget(md, tick); // 移動できないのでタゲ解除(IWとか?) | I can't move so I can't target it (IW or something?)
else
{
- // 追跡
+ // 追跡 | tracking
md->next_walktime = tick + 500_ms;
i = 0;
do
{
if (i == 0)
{
- // 最初はAEGISと同じ方法で検索
+ // 最初はAEGISと同じ方法で検索 | First search in the same way as AEGIS
dx = tbl->bl_x - md->bl_x;
dy = tbl->bl_y - md->bl_y;
if (dx < 0)
@@ -1970,7 +2025,7 @@ void mob_ai_sub_hard(dumb_ptr<block_list> bl, tick_t tick)
}
else
{
- // だめならAthena式(ランダム)
+ // だめならAthena式(ランダム) | If not, use Athena style (random)
// {0 1 2}
dx = tbl->bl_x - md->bl_x + random_::in(-1, 1);
dy = tbl->bl_y - md->bl_y + random_::in(-1, 1);
@@ -1981,7 +2036,7 @@ void mob_ai_sub_hard(dumb_ptr<block_list> bl, tick_t tick)
while (ret && i < 5);
if (ret)
- { // 移動不可能な所からの攻撃なら2歩下る
+ { // 移動不可能な所からの攻撃なら2歩下る | If attacking from a place where you can't move, take 2 steps back.
if (dx < 0)
dx = 2;
else if (dx > 0)
@@ -1996,57 +2051,57 @@ void mob_ai_sub_hard(dumb_ptr<block_list> bl, tick_t tick)
}
}
else
- { // 攻撃射程範囲内
+ { // 攻撃射程範囲内 | Within attack range
md->state.skillstate = MobSkillState::MSS_ATTACK;
if (md->state.state == MS::WALK)
- mob_stop_walking(md, 1); // 歩行中なら停止
+ mob_stop_walking(md, 1); // 歩行中なら停止 | Stop if you are walking
if (md->state.state == MS::ATTACK)
- return; // 既に攻撃中
+ return; // 既に攻撃中 | already under attack
mob_changestate(md, MS::ATTACK, attack_type);
}
return;
}
else
- { // ルートモンスター処理
+ { // ルートモンスター処理 | Root monster processing
if (tbl == nullptr || tbl->bl_type != BL::ITEM || tbl->bl_m != md->bl_m
|| (dist =
distance(md->bl_x, md->bl_y, tbl->bl_x,
tbl->bl_y)) >= md->min_chase
|| !bool(get_mob_db(md->mob_class).mode & MobMode::LOOTER))
{
- // 遠すぎるかアイテムがなくなった
+ // 遠すぎるかアイテムがなくなった | Too far or missing item
mob_unlocktarget(md, tick);
if (md->state.state == MS::WALK)
- mob_stop_walking(md, 1); // 歩行中なら停止
+ mob_stop_walking(md, 1); // 歩行中なら停止 | Stop if you are walking
}
else if (dist)
{
if (!bool(mode & MobMode::CAN_MOVE))
- { // 移動しないモード
+ { // 移動しないモード | no-move mode
mob_unlocktarget(md, tick);
return;
}
- if (!mob_can_move(md)) // 動けない状態にある
+ if (!mob_can_move(md)) // 動けない状態にある | unable to move
return;
- md->state.skillstate = MobSkillState::MSS_LOOT; // ルート時スキル使用
+ md->state.skillstate = MobSkillState::MSS_LOOT; // ルート時スキル使用 | Skill use during root
mobskill_use(md, tick, MobSkillCondition::ANY);
if (md->timer && md->state.state != MS::ATTACK
&& (md->next_walktime < tick
|| distance(md->to_x, md->to_y, tbl->bl_x, tbl->bl_y) <= 0))
- return; // 既に移動中
+ return; // 既に移動中 | Already on the move
md->next_walktime = tick + 500_ms;
dx = tbl->bl_x - md->bl_x;
dy = tbl->bl_y - md->bl_y;
ret = mob_walktoxy(md, md->bl_x + dx, md->bl_y + dy, 0);
if (ret)
- mob_unlocktarget(md, tick); // 移動できないのでタゲ解除(IWとか?)
+ mob_unlocktarget(md, tick); // 移動できないのでタゲ解除(IWとか?) | I can't move so I can't target it (IW or something?)
}
else
- { // アイテムまでたどり着いた
+ { // アイテムまでたどり着いた | I got to the item
if (md->state.state == MS::ATTACK)
- return; // 攻撃中
+ return; // 攻撃中 | Under attack
if (md->state.state == MS::WALK)
- mob_stop_walking(md, 1); // 歩行中なら停止
+ mob_stop_walking(md, 1); // 歩行中なら停止 | Stop if you are walking
fitem = tbl->is_item();
md->lootitemv.push_back(fitem->item_data);
map_clearflooritem(tbl->bl_id);
@@ -2059,7 +2114,7 @@ void mob_ai_sub_hard(dumb_ptr<block_list> bl, tick_t tick)
{
mob_unlocktarget(md, tick);
if (md->state.state == MS::WALK)
- mob_stop_walking(md, 4); // 歩行中なら停止
+ mob_stop_walking(md, 4); // 歩行中なら停止 | Stop if you are walking
return;
}
}
@@ -2298,6 +2353,10 @@ int mob_delete(dumb_ptr<mob_data> md)
return 0;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
int mob_catch_delete(dumb_ptr<mob_data> md, BeingRemoveWhy type)
{
nullpo_retr(1, md);
@@ -2311,6 +2370,10 @@ int mob_catch_delete(dumb_ptr<mob_data> md, BeingRemoveWhy type)
return 0;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
void mob_timer_delete(TimerData *, tick_t, BlockId id)
{
dumb_ptr<block_list> bl = map_id2bl(id);
@@ -2374,7 +2437,7 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
tick_t tick = gettick();
dumb_ptr<map_session_data> mvp_sd = nullptr, second_sd = nullptr, third_sd = nullptr;
- nullpo_retz(md); //srcはNULLで呼ばれる場合もあるので、他でチェック
+ nullpo_retz(md); // srcはNULLで呼ばれる場合もあるので、他でチェック | src may be called as NULL, so check it elsewhere.
if (src && src->bl_id == md->master_id
&& bool(md->mode & MobMode::TURNS_AGAINST_BAD_MASTER))
@@ -2491,6 +2554,13 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
md->hp -= damage;
+ // activity
+ if (sd)
+ if (sd->activity.attacks == 2147483647)
+ sd->activity.attacks = 1;
+ else
+ sd->activity.attacks++;
+
if (md->hp > 0)
{
return 0;
@@ -2498,7 +2568,7 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
MAP_LOG("MOB%d DEAD"_fmt, md->bl_id);
- // ----- ここから死亡処理 -----
+ // ----- ここから死亡処理 | Death processing begins here -----
MapBlockLock lock;
// cancels timers
@@ -2510,8 +2580,8 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
if (src && src->bl_type == BL::MOB)
mob_unlocktarget(src->is_mob(), tick);
- // map外に消えた人は計算から除くので
- // overkill分は無いけどsumはmax_hpとは違う
+ // map外に消えた人は計算から除くので | People who disappear outside the map will be excluded from the calculation.
+ // overkill分は無いけどsumはmax_hpとは違う | There is no overkill portion, but sum is different from max_hp
// snip a prelude loop, now merged
@@ -2541,6 +2611,12 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
if (tmpsdi->bl_m != md->bl_m || pc_isdead(tmpsdi))
continue;
+ // activity
+ if (tmpsdi->activity.kills == 2147483647)
+ tmpsdi->activity.kills = 1;
+ else
+ tmpsdi->activity.kills++;
+
// this way is actually fair, unlike the old way
// that refers to the subsequents ... was buggy though
if (tmpdmg > mvp_dmg)
@@ -2700,7 +2776,7 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
}
} // [MouseJstr]
- // SCRIPT実行
+ // SCRIPT実行 | SCRIPT execution
{
if (sd == nullptr)
{
@@ -2733,6 +2809,7 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
/*==========================================
* mob回復
+ * mob recovery
*------------------------------------------
*/
int mob_heal(dumb_ptr<mob_data> md, int heal)
@@ -2780,6 +2857,7 @@ int mob_warpslave(dumb_ptr<mob_data> md, int x, int y)
/*==========================================
* mobワープ
+ * mob warp
*------------------------------------------
*/
int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m_, int x, int y, BeingRemoveWhy type)
@@ -2802,7 +2880,7 @@ int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m_, int x, int y
map_delblock(md);
if (bx > 0 && by > 0)
- { // 位置指定の場合周囲9セルを探索
+ { // 位置指定の場合周囲9セルを探索 | When specifying a location, search the surrounding 9 cells
xs = ys = 9;
}
@@ -2813,13 +2891,13 @@ int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m_, int x, int y
{
if (xs > 0 && ys > 0 && i < 250)
{
- // 指定位置付近の探索
+ // 指定位置付近の探索 | Search near specified location
x = bx + random_::to(xs) - xs / 2;
y = by + random_::to(ys) - ys / 2;
}
else
{
- // 完全ランダム探索
+ // 完全ランダム探索 | Completely random search
x = random_::in(1, m->xs - 2);
y = random_::in(1, m->ys - 2);
}
@@ -2837,7 +2915,7 @@ int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m_, int x, int y
PRINTF("MOB %d warp failed, mob_class = %d\n"_fmt, md->bl_id, md->mob_class);
}
- md->target_id = BlockId(); // タゲを解除する
+ md->target_id = BlockId(); // タゲを解除する | remove target
md->state.attackable = false;
md->attacked_id = BlockId();
md->state.skillstate = MobSkillState::MSS_IDLE;
@@ -2863,6 +2941,7 @@ int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m_, int x, int y
/*==========================================
* 画面内の取り巻きの数計算用(foreachinarea)
+ * For calculating the number of entourage in the screen (foreachinarea)
*------------------------------------------
*/
static
@@ -2879,6 +2958,7 @@ void mob_countslave_sub(dumb_ptr<block_list> bl, BlockId id, int *c)
/*==========================================
* 画面内の取り巻きの数計算
+ * Calculating the number of entourage in the screen
*------------------------------------------
*/
static
@@ -2898,6 +2978,7 @@ int mob_countslave(dumb_ptr<mob_data> md)
/*==========================================
* 手下MOB召喚
+ * MOB summoning
*------------------------------------------
*/
int mob_summonslave(dumb_ptr<mob_data> md2, int *value_, int amount, int flag)
@@ -2961,8 +3042,8 @@ int mob_summonslave(dumb_ptr<mob_data> md2, int *value_, int amount, int flag)
md->spawn.xs = 0;
md->spawn.ys = 0;
md->stats[mob_stat::SPEED] = md2->stats[mob_stat::SPEED];
- md->spawn.delay1 = static_cast<interval_t>(-1); // 一度のみフラグ
- md->spawn.delay2 = static_cast<interval_t>(-1); // 一度のみフラグ
+ md->spawn.delay1 = static_cast<interval_t>(-1); // 一度のみフラグ | once flag
+ md->spawn.delay2 = static_cast<interval_t>(-1); // 一度のみフラグ | once flag
md->npc_event = NpcEvent();
md->bl_type = BL::MOB;
@@ -2978,6 +3059,7 @@ int mob_summonslave(dumb_ptr<mob_data> md2, int *value_, int amount, int flag)
/*==========================================
* 自分をロックしているPCの数を数える(foreachclient)
+ * Count the number of PCs that have locked you (foreachclient)
*------------------------------------------
*/
static
@@ -3007,6 +3089,7 @@ void mob_counttargeted_sub(dumb_ptr<block_list> bl,
/*==========================================
* 自分をロックしているPCの数を数える
+ * Count the number of PCs that have locked you
*------------------------------------------
*/
int mob_counttargeted(dumb_ptr<mob_data> md, dumb_ptr<block_list> src,
@@ -3025,11 +3108,12 @@ int mob_counttargeted(dumb_ptr<mob_data> md, dumb_ptr<block_list> src,
}
//
-// MOBスキル
+// MOBスキル | MOB skills
//
/*==========================================
* スキル使用(詠唱完了、ID指定)
+ * Skill use (casting completed, ID designation)
*------------------------------------------
*/
void mobskill_castend_id(TimerData *, tick_t tick, BlockId id)
@@ -3039,7 +3123,7 @@ void mobskill_castend_id(TimerData *, tick_t tick, BlockId id)
dumb_ptr<block_list> mbl;
int range;
- if ((mbl = map_id2bl(id)) == nullptr) //詠唱したMobがもういないというのは良くある正常処理
+ if ((mbl = map_id2bl(id)) == nullptr) // 詠唱したMobがもういないというのは良くある正常処理 | It's common and normal that the mob that cast is no longer there.
return;
if ((md = mbl->is_mob()) == nullptr)
{
@@ -3056,13 +3140,13 @@ void mobskill_castend_id(TimerData *, tick_t tick, BlockId id)
md->last_thinktime = tick + battle_get_adelay(md);
if ((bl = map_id2bl(md->skilltarget)) == nullptr || bl->bl_prev == nullptr)
- { //スキルターゲットが存在しない
+ { // スキルターゲットが存在しない | Skill target does not exist
return;
}
if (md->bl_m != bl->bl_m)
return;
- if (((skill_get_inf(md->skillid) & 1) || (skill_get_inf2(md->skillid) & 4)) && // 彼我敵対関係チェック
+ if (((skill_get_inf(md->skillid) & 1) || (skill_get_inf2(md->skillid) & 4)) && // 彼我敵対関係チェック | Self-enemy relationship check
battle_check_target(md, bl, BCT_ENEMY) <= 0)
return;
range = skill_get_range(md->skillid, md->skilllv);
@@ -3080,14 +3164,14 @@ void mobskill_castend_id(TimerData *, tick_t tick, BlockId id)
switch (skill_get_nk(md->skillid))
{
- // 攻撃系/吹き飛ばし系
+ // 攻撃系/吹き飛ばし系 | Attack type/Blow type
case 0:
case 2:
skill_castend_damage_id(md, bl,
md->skillid, md->skilllv,
tick, BCT_ZERO);
break;
- case 1: // 支援系
+ case 1: // 支援系 | Support system
skill_castend_nodamage_id(md, bl,
md->skillid, md->skilllv);
break;
@@ -3096,6 +3180,7 @@ void mobskill_castend_id(TimerData *, tick_t tick, BlockId id)
/*==========================================
* スキル使用(詠唱完了、場所指定)
+ * Skill use (casting completed, location specified)
*------------------------------------------
*/
void mobskill_castend_pos(TimerData *, tick_t tick, BlockId id)
@@ -3104,7 +3189,8 @@ void mobskill_castend_pos(TimerData *, tick_t tick, BlockId id)
dumb_ptr<block_list> bl;
int range;
- //mobskill_castend_id同様詠唱したMobが詠唱完了時にもういないというのはありそうなのでnullpoから除外
+ // mobskill_castend_id同様詠唱したMobが詠唱完了時にもういないというのはありそうなのでnullpoから除外
+ // mobskill_castend_id It is likely that the mob that cast the same as the one is no longer there when the cast is completed, so it is excluded from nullpo.
if ((bl = map_id2bl(id)) == nullptr)
return;
@@ -3160,7 +3246,7 @@ int mobskill_use_id(dumb_ptr<mob_data> md, dumb_ptr<block_list> target,
if (skill_get_inf2(skill_id) & 0x200 && md->bl_id == target->bl_id)
return 0;
- // 射程と障害物チェック
+ // 射程と障害物チェック | Range and obstacle check
range = skill_get_range(skill_id, skill_lv);
if (range < 0)
range = battle_get_range(md) - (range + 1);
@@ -3179,7 +3265,7 @@ int mobskill_use_id(dumb_ptr<mob_data> md, dumb_ptr<block_list> target,
target->bl_id, skill_id, skill_lv,
static_cast<uint32_t>(casttime.count()), md->mob_class);
- if (casttime <= interval_t::zero()) // 詠唱の無いものはキャンセルされない
+ if (casttime <= interval_t::zero()) // 詠唱の無いものはキャンセルされない | Anything without a chant will not be canceled
md->state.skillcastcancel = 0;
md->skilltarget = target->bl_id;
@@ -3206,6 +3292,7 @@ int mobskill_use_id(dumb_ptr<mob_data> md, dumb_ptr<block_list> target,
/*==========================================
* スキル使用(場所指定)
+ * Skill usage (Location)
*------------------------------------------
*/
static
@@ -3229,7 +3316,7 @@ int mobskill_use_pos(dumb_ptr<mob_data> md,
if (bool(md->opt1))
return 0;
- // 射程と障害物チェック
+ // 射程と障害物チェック | Range and obstacle check
bl.bl_type = BL::NUL;
bl.bl_m = md->bl_m;
bl.bl_x = skill_x;
@@ -3300,11 +3387,11 @@ int mobskill_use(dumb_ptr<mob_data> md, tick_t tick,
tick_t& sdii = md->skilldelayup[&msii - &ms.front()];
int flag = 0;
- // ディレイ中
+ // ディレイ中 | During delay
if (tick < sdii + msii.delay)
continue;
- // 状態判定
+ // 状態判定 | Status determination
if (msii.state != MobSkillState::ANY && msii.state != md->state.skillstate)
continue;
@@ -3332,13 +3419,13 @@ int mobskill_use(dumb_ptr<mob_data> md, tick_t tick,
}
}
- // 確率判定
+ // 確率判定 | Probability judgment
if (flag && random_::chance({msii.permillage, 10000}))
{
if (skill_get_inf(msii.skill_id) & 2)
{
- // 場所指定
+ // 場所指定 | Specify location
dumb_ptr<block_list> bl = nullptr;
int x = 0, y = 0;
{
@@ -3400,7 +3487,7 @@ int mobskill_event(dumb_ptr<mob_data> md, BF flag)
}
//
-// 初期化
+// 初期化 | Initialization
//
/*==========================================
* Since un-setting [ mob ] up was used, it is an initial provisional value setup.
@@ -3447,6 +3534,10 @@ int mob_makedummymobdb(Species mob_class)
return 0;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
static
bool impl_extract(XString str, LevelElement *le)
{
@@ -3459,6 +3550,10 @@ bool impl_extract(XString str, LevelElement *le)
return false;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
bool mob_readdb(ZString filename)
{
bool rv = true;
@@ -3495,6 +3590,7 @@ bool mob_readdb(ZString filename)
lstripping(&mdbv.atk2),
lstripping(&mdbv.def),
lstripping(&mdbv.mdef),
+ lstripping(&mdbv.critical_def),
lstripping(&mdbv.attrs[ATTR::STR]),
lstripping(&mdbv.attrs[ATTR::AGI]),
lstripping(&mdbv.attrs[ATTR::VIT]),
@@ -3527,6 +3623,10 @@ bool mob_readdb(ZString filename)
lstripping(&mdbv.dropitem[6].p.num),
lstripping(&mdbv.dropitem[7].nameid),
lstripping(&mdbv.dropitem[7].p.num),
+ lstripping(&mdbv.dropitem[8].nameid),
+ lstripping(&mdbv.dropitem[8].p.num),
+ lstripping(&mdbv.dropitem[9].nameid),
+ lstripping(&mdbv.dropitem[9].p.num),
&ignore,
&ignore,
&ignore,
@@ -3605,6 +3705,10 @@ bool mob_readdb(ZString filename)
return rv;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
static
bool impl_extract(XString str, MobSkillCondition *msc)
{
@@ -3629,6 +3733,10 @@ bool impl_extract(XString str, MobSkillCondition *msc)
return false;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
static
bool impl_extract(XString str, MobSkillState *mss)
{
@@ -3652,6 +3760,10 @@ bool impl_extract(XString str, MobSkillState *mss)
return false;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
static
bool impl_extract(XString str, MobSkillTarget *mst)
{
@@ -3673,6 +3785,10 @@ bool impl_extract(XString str, MobSkillTarget *mst)
return false;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
bool mob_readskilldb(ZString filename)
{
bool rv = true;
@@ -3755,6 +3871,10 @@ bool mob_readskilldb(ZString filename)
return rv;
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
void do_init_mob2(void)
{
Timer(gettick() + MIN_MOBTHINKTIME,
diff --git a/src/map/mob.hpp b/src/map/mob.hpp
index 47da095..3aa7fc5 100644
--- a/src/map/mob.hpp
+++ b/src/map/mob.hpp
@@ -43,7 +43,7 @@ namespace map
#define JAPANESE_NAME stringish<MobName>("--ja--"_s)
#define MOB_THIS_MAP stringish<MapName>("this"_s)
-#define MaxDrops 8
+#define MaxDrops 10
#define MinMobID 1001
#define MaxMobID 2000
@@ -69,7 +69,7 @@ struct mob_db_
int max_hp, max_sp;
int base_exp, job_exp;
int atk1, atk2;
- int def, mdef;
+ int def, mdef, critical_def;
earray<int, ATTR, ATTR::COUNT> attrs;
int range, range2, range3;
// always 1
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 4d9a8d1..ee2f30c 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -993,6 +993,12 @@ int npc_selllist(dumb_ptr<map_session_data> sd,
if (!nameid ||
sd->status.inventory[item_list[i].ioff2.unshift()].amount < item_list[i].count)
return 1;
+ if (bool(itemdb_search(nameid)->mode & ItemMode::NO_SELL_TO_NPC))
+ {
+ //clif_displaymessage(sd->sess, "This item can't be sold to an NPC."_s);
+ // M+ already outputs "Unable to sell unsellable item." on return value 3.
+ return 3;
+ }
if (sd->trade_partner)
return 2; // cant sell while trading
z += static_cast<double>(itemdb_value_sell(nameid)) * item_list[i].count;
@@ -1009,7 +1015,6 @@ int npc_selllist(dumb_ptr<map_session_data> sd,
}
return 0;
-
}
static
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 9e76ecf..12af48f 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -127,71 +127,90 @@ int sp_coefficient_0 = 100;
static //const
earray<interval_t, ItemLook, ItemLook::COUNT> aspd_base_0 //=
{{
-650_ms, // 0 NONE
-700_ms, // 1 BLADE or some other common weapons
-750_ms, // 2
-610_ms, // 3 SETZER_AND_SCYTHE
-2000_ms, // 4
-2000_ms, // 5
-800_ms, // 6 Falchion
-2000_ms, // 7
-700_ms, // 8
-700_ms, // 9
-650_ms, //10 STAFF / Sandcutter
-900_ms, //11 BOW
-2000_ms, //12
-2000_ms, //13
-2000_ms, //14
-2000_ms, //15
-2000_ms, //16
+ 550_ms, // 0 { "Fist", W_FIST },
+ 600_ms, // 1 { "Dagger", W_DAGGER },
+ 650_ms, // 2 { "Sword", W_1HSWORD },
+ 700_ms, // 3 { "TwoHandSword", W_2HSWORD },
+ 650_ms, // 4 { "Spear", W_1HSPEAR },
+ 700_ms, // 5 { "TwoHandSpear", W_2HSPEAR },
+ 700_ms, // 6 { "Axe", W_1HAXE },
+ 750_ms, // 7 { "TwoHandAxe", W_2HAXE },
+ 700_ms, // 8 { "Mace", W_MACE },
+ 750_ms, // 9 { "TwoHandMace", W_2HMACE },
+ 700_ms, // 10 { "Rod", W_STAFF },
+ 800_ms, // 11 { "Bow", W_BOW },
+ 575_ms, // 12 { "Knuckle", W_KNUCKLE },
+ 800_ms, // 13 { "Instrument", W_MUSICAL },
+ 675_ms, // 14 { "Whip", W_WHIP },
+ 700_ms, // 15 { "Book", W_BOOK },
+ 600_ms, // 16 { "Katar", W_KATAR },
+ 600_ms, // 17 { "Revolver", W_REVOLVER },
+ 800_ms, // 18 { "Rifle", W_RIFLE },
+ 500_ms, // 19 { "GatlingGun", W_GATLING },
+ 1000_ms, // 20 { "Shotgun", W_SHOTGUN },
+ 2000_ms, // 21 { "GrenadeLauncher", W_GRENADE },
+ 650_ms, // 22 { "FuumaShuriken", W_HUUMA },
+ 750_ms, // 23 { "TwoHandRod", W_2HSTAFF },
}};
static const
int exp_table_0[MAX_LEVEL] =
{
- // 1 .. 9
+ // 1 .. 10
9, 16, 25, 36,
77, 112, 153, 200, 253,
- // 10 .. 19
+ // 11 .. 20
320, 385, 490, 585, 700,
830, 970, 1120, 1260, 1420,
- // 20 .. 29
+ // 21 .. 30
1620, 1860, 1990, 2240, 2504,
2950, 3426, 3934, 4474, 6889,
- // 30 .. 39
+ // 31 .. 40
7995, 9174, 10425, 11748, 13967,
15775, 17678, 19677, 21773, 30543,
- // 40 .. 49
+ // 41 .. 50
34212, 38065, 42102, 46323, 53026,
58419, 64041, 69892, 75973, 102468,
- // 50 .. 59
+ // 51 .. 60
115254, 128692, 142784, 157528, 178184,
196300, 215198, 234879, 255341, 330188,
- // 60 .. 69
+ // 61 .. 70
365914, 403224, 442116, 482590, 536948,
585191, 635278, 687211, 740988, 925400,
- // 70 .. 79
+ // 71 .. 80
1473746, 1594058, 1718928, 1848355, 1982340,
2230113, 2386162, 2547417, 2713878, 3206160,
- // 80 .. 89
+ // 81 .. 90
3681024, 4022472, 4377024, 4744680, 5125440,
5767272, 6204000, 6655464, 7121664, 7602600,
- // 90 .. 99
+ // 91 .. 100
9738720, 11649960, 13643520, 18339300, 23836800,
35658000, 48687000, 58135000, 99999999, 103000000,
- // 100 .. 109
+ // 101 .. 110
107000000, 112000000, 116000000, 121000000, 125000000,
130000000, 134000000, 139000000, 145000000, 152200000,
- // 110 .. 119
+ // 111 .. 120
160840000, 171200000, 191930000, 202290000, 214720000,
229640000, 247550000, 283370000, 301280000, 322770000,
- // 120 .. 129
+ // 121 .. 130
348560000, 379500000, 417450000, 459195000, 505114500,
555625950, 622301064, 696977191, 780614454, 880533104,
- // 130 .. 135
- 993241342, 1120376234, 1263784392, 1425548794, 1608019039,
- 2147483647, 0
+ // 131 .. 140
+ 993241342, 1120376234, 1263784392, 1425548794, 1588019039,
+ 1680000000, 1780000000, 1890000000, 2010000000, 2140000000,
+ // 141
+ 0
};
+// The old table contained a bug, actually max level was 136 and not 135 but to reach level 136 you must hit exactly the value 2147483647
+// else and overflow occurs and you lose all the progress of that level and start with level 135 at 0% again.
+// I made a buffer that is great enough now I guess its 7483647 while looking through all gm logs I saw the highest values ever where 300%.
+// A Xakelbael killed on 300% exp one time tabbed gives around 6 mil so I have a little buffer on top of that as well, it won't make it for
+// 400% exp but since this will most likely never happen and the chance that one will make his lvl up with Xakelbael one time tabbed
+// is extremely rare as well.
+ // 130 .. 135
+ //993241342, 1120376234, 1263784392, 1425548794, 1608019039,
+ //2147483647, 0
+
// is this *actually* used anywhere?
static const
@@ -694,7 +713,7 @@ int pc_setequipindex(dumb_ptr<map_session_data> sd)
}
OMATCH_CASE_NONE ()
{
- sd->weapontype1 = ItemLook::NONE;
+ sd->weapontype1 = ItemLook::W_FIST;
}
}
OMATCH_END ();
@@ -786,13 +805,13 @@ void pc_set_attack_info(dumb_ptr<map_session_data> sd, interval_t speed, int ran
if (speed == interval_t::zero())
{
- pc_calcstatus(sd, 1);
+ pc_calcstatus(sd, (int)CalcStatusKind::INITIAL_CALC);
clif_updatestatus(sd, SP::ASPD);
clif_updatestatus(sd, SP::ATTACKRANGE);
}
else
{
- pc_calcstatus(sd, 9);
+ pc_calcstatus(sd, ((int)CalcStatusKind::INITIAL_CALC + (int)CalcStatusKind::MAGIC_OVERRIDE));
clif_updatestatus(sd, SP::ASPD);
clif_updatestatus(sd, SP::ATTACKRANGE);
}
@@ -831,7 +850,7 @@ int pc_authok(AccountId id, int login_id2, ClientVersion client_version,
sd->state.connect_new = 1;
sd->bl_prev = sd->bl_next = nullptr;
- sd->weapontype1 = ItemLook::NONE;
+ sd->weapontype1 = ItemLook::W_FIST;
sd->speed = DEFAULT_WALK_SPEED;
sd->state.dead_sit = 0;
sd->dir = DIR::S;
@@ -929,7 +948,7 @@ int pc_authok(AccountId id, int login_id2, ClientVersion client_version,
sd->die_counter = pc_readglobalreg(sd, stringish<VarName>("PC_DIE_COUNTER"_s));
// ステータス初期計算など | Status initial calculation, etc.
- pc_calcstatus(sd, 1);
+ pc_calcstatus(sd, (int)CalcStatusKind::INITIAL_CALC);
if (pc_isGM(sd))
{
@@ -962,7 +981,7 @@ int pc_authok(AccountId id, int login_id2, ClientVersion client_version,
sd->packet_flood_reset_due = tick_t();
sd->packet_flood_in = 0;
- pc_calcstatus(sd, 1);
+ pc_calcstatus(sd, (int)CalcStatusKind::INITIAL_CALC);
if(sd->bl_m->mask > 0)
clif_send_mask(sd, sd->bl_m->mask);
@@ -1087,10 +1106,11 @@ void pc_set_weapon_look(dumb_ptr<map_session_data> sd)
* Actively changed parameters should be send on their own
*
* First is a bitmask
- * &1 = ?
- * &2 = ?
- * &4 = ?
- * &8 = magic override
+ * &0 = Status Recalculation | ステータス再計算
+ * &1 = Status initial calculation, etc.| ステータス初期計算など
+ * &2 = Recalculate item bonus (used in pc_checkitem but not handled in this function atm, so if function is called with first = 2 its basically first = 0 only)
+ * &4 = Status Recalculation but don't use any clif_updatestatus to send status to client (used by map_quit)
+ * &8 = magic override (used in pc_set_attack_info)
*------------------------------------------
*/
int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
@@ -1115,11 +1135,11 @@ 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;
- interval_t b_aspd = sd->aspd;
+ interval_t b_aspd = sd->aspd, b_base_weapon_delay_adjust = interval_t::zero();
b_watk = sd->watk;
b_def = sd->def;
b_watk2 = sd->watk2;
@@ -1137,7 +1157,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->max_weight = max_weight_base_0 + sd->status.attrs[ATTR::STR] * 300;
- if (first & 1)
+ if (first & (int)CalcStatusKind::INITIAL_CALC)
{
sd->weight = 0;
for (IOff0 i : IOff0::iter())
@@ -1161,7 +1181,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->flee = 0;
sd->flee2 = 0;
sd->critical = 0;
- sd->aspd = interval_t::zero();
+ sd->aspd = sd->base_weapon_delay_adjust = interval_t::zero();
sd->watk = 0;
sd->def = 0;
sd->mdef = 0;
@@ -1174,6 +1194,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->matk1 = 0;
sd->matk2 = 0;
sd->speed = DEFAULT_WALK_SPEED;
+ sd->speed_cap = interval_t::zero();
sd->hprate = 100;
sd->sprate = 100;
sd->dsprate = 100;
@@ -1192,11 +1213,11 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->double_rate = 0;
sd->atk_rate = sd->matk_rate = 100;
sd->arrow_cri = 0;
- sd->perfect_hit = 0;
+ sd->perfect_hit = sd->deadly_strike = 0;
sd->critical_rate = sd->hit_rate = sd->flee_rate = sd->flee2_rate = 100;
sd->def_rate = sd->def2_rate = sd->mdef_rate = sd->mdef2_rate = 100;
sd->speed_add_rate = sd->aspd_add_rate = 100;
- sd->double_add_rate = sd->perfect_hit_add = 0;
+ sd->double_add_rate = sd->perfect_hit_add_rate = sd->deadly_strike_add_rate = 0;
sd->hp_drain_rate = sd->hp_drain_per = sd->sp_drain_rate =
sd->sp_drain_per = 0;
@@ -1324,10 +1345,11 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
if (sd->attackrange < 1)
sd->attackrange = 1;
- if (sd->status.weapon == ItemLook::BOW)
+ if (sd->status.weapon == ItemLook::W_BOW)
sd->attackrange += sd->arrow_range;
sd->double_rate += sd->double_add_rate;
- sd->perfect_hit += sd->perfect_hit_add;
+ sd->perfect_hit += sd->perfect_hit_add_rate;
+ sd->deadly_strike += sd->deadly_strike_add_rate;
if (sd->speed_add_rate != 100)
sd->speed_rate += sd->speed_add_rate - 100;
if (sd->aspd_add_rate != 100)
@@ -1341,7 +1363,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
for (ATTR attr : ATTRs)
sd->paramc[attr] = std::max(0, sd->status.attrs[attr] + sd->paramb[attr] + sd->parame[attr]);
- if (sd->status.weapon == ItemLook::BOW)
+ if (sd->status.weapon == ItemLook::W_BOW)
{
str = sd->paramc[ATTR::DEX];
dex = sd->paramc[ATTR::STR];
@@ -1356,6 +1378,13 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->base_atk += str + dstr * dstr + dex / 5 + sd->paramc[ATTR::LUK] / 5;
sd->matk1 += sd->paramc[ATTR::INT] + (sd->paramc[ATTR::INT] / 5) * (sd->paramc[ATTR::INT] / 5);
sd->matk2 += sd->paramc[ATTR::INT] + (sd->paramc[ATTR::INT] / 7) * (sd->paramc[ATTR::INT] / 7);
+
+ if (sd->sc_data[StatusChange::SC_MATKPOT].timer)
+ {
+ sd->matk1 += sd->sc_data[StatusChange::SC_MATKPOT].val1;
+ sd->matk2 += sd->sc_data[StatusChange::SC_MATKPOT].val1;
+ }
+
if (sd->matk1 < sd->matk2)
{
int temp = sd->matk2;
@@ -1435,9 +1464,12 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
//二刀流 ASPD 修正 | Two-cut ASPD correction
{
- sd->aspd += aspd_base_0[sd->status.weapon]
+ b_base_weapon_delay_adjust = aspd_base_0[sd->status.weapon] + sd->base_weapon_delay_adjust;
+ if (b_base_weapon_delay_adjust.count() < 0)
+ b_base_weapon_delay_adjust = interval_t::zero();
+ sd->aspd += b_base_weapon_delay_adjust
- (sd->paramc[ATTR::AGI] * 4 + sd->paramc[ATTR::DEX])
- * aspd_base_0[sd->status.weapon] / 1000;
+ * b_base_weapon_delay_adjust / 1000;
}
aspd_rate = sd->aspd_rate;
@@ -1507,11 +1539,6 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
if (sd->sc_data[StatusChange::SC_ATKPOT].timer)
sd->watk += sd->sc_data[StatusChange::SC_ATKPOT].val1;
- if (sd->sc_data[StatusChange::SC_MATKPOT].timer)
- {
- sd->matk1 += sd->sc_data[StatusChange::SC_MATKPOT].val1;
- sd->matk2 += sd->sc_data[StatusChange::SC_MATKPOT].val1;
- }
if (sd->sc_data[StatusChange::SC_SPEEDPOTION0].timer)
aspd_rate -= sd->sc_data[StatusChange::SC_SPEEDPOTION0].val1;
@@ -1528,17 +1555,20 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
if (sd->speed_rate != 100)
sd->speed = sd->speed * sd->speed_rate / 100;
sd->speed = std::max(sd->speed, 1_ms);
+ if (sd->speed_cap < interval_t::zero())
+ sd->speed_cap = interval_t::zero();
+ if (sd->speed < sd->speed_cap)
+ sd->speed = sd->speed_cap;
+ if (aspd_rate != 100)
+ sd->aspd = sd->aspd * aspd_rate / 100;
/* Magic speed */
- if (sd->attack_spell_override || first & 8)
+ if (sd->attack_spell_override || first & (int)CalcStatusKind::MAGIC_OVERRIDE)
sd->aspd = sd->attack_spell_delay;
- if (aspd_rate != 100)
- sd->aspd = sd->aspd * aspd_rate / 100;
-
- /* 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);
@@ -1551,14 +1581,14 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
if (sd->status.sp > sd->status.max_sp)
sd->status.sp = sd->status.max_sp;
- if (first & 4)
+ if (first & (int)CalcStatusKind::NORMAL_RECALC_NO_CLIENT_UPDATE)
return 0;
- if (first & 3)
+ if (first & ((int)CalcStatusKind::INITIAL_CALC + (int)CalcStatusKind::ITEM_BONUS_RECALC)) // never executed atm
{
clif_updatestatus(sd, SP::SPEED);
clif_updatestatus(sd, SP::MAXHP);
clif_updatestatus(sd, SP::MAXSP);
- if (first & 1)
+ if (first & (int)CalcStatusKind::INITIAL_CALC) // its always 1 here if first is 3 so this if is not needed normally
{
clif_updatestatus(sd, SP::HP);
clif_updatestatus(sd, SP::SP);
@@ -1792,7 +1822,7 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val)
break;
case SP::PERFECT_HIT_ADD_RATE:
if (!sd->state.lr_flag_is_arrow_2)
- sd->perfect_hit_add += val;
+ sd->perfect_hit_add_rate += val;
break;
case SP::CRITICAL_RATE:
if (!sd->state.lr_flag_is_arrow_2)
@@ -1829,6 +1859,41 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val)
case SP::DEAF:
sd->special_state.deaf = 1;
break;
+ case SP::SPEED_CAP:
+ if (!sd->state.lr_flag_is_arrow_2)
+ // highest value (slowest speed) is taken others are ignored
+ if (sd->speed_cap < interval_t(val))
+ sd->speed_cap = interval_t(val);
+ break;
+ case SP::ALL_STATS:
+ sd->parame[ATTR::STR] += val;
+ sd->parame[ATTR::AGI] += val;
+ sd->parame[ATTR::VIT] += val;
+ sd->parame[ATTR::INT] += val;
+ sd->parame[ATTR::DEX] += val;
+ sd->parame[ATTR::LUK] += val;
+ break;
+ case SP::AGI_VIT:
+ sd->parame[ATTR::AGI] += val;
+ sd->parame[ATTR::VIT] += val;
+ break;
+ case SP::AGI_DEX_STR:
+ sd->parame[ATTR::AGI] += val;
+ sd->parame[ATTR::DEX] += val;
+ sd->parame[ATTR::STR] += val;
+ break;
+ case SP::DEADLY_STRIKE_RATE:
+ if (!sd->state.lr_flag_is_arrow_2 && sd->deadly_strike < val)
+ sd->deadly_strike = val;
+ break;
+ case SP::DEADLY_STRIKE_ADD_RATE:
+ if (!sd->state.lr_flag_is_arrow_2)
+ sd->deadly_strike_add_rate += val;
+ break;
+ case SP::BASE_WEAPON_DELAY_ADJUST:
+ if (!sd->state.lr_flag_is_arrow_2)
+ sd->base_weapon_delay_adjust += interval_t(val);
+ break;
default:
if (battle_config.error_log)
PRINTF("pc_bonus: unknown type %d %d !\n"_fmt,
@@ -1840,6 +1905,7 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val)
/*==========================================
* ソスソス ソスソスソスiソスノゑソスソスソスソス\ソスヘ難ソスソスフボソス[ソスiソスXソスン抵ソス
+ * sos sos sos sos sos i sos no sos sos sos sos\
*------------------------------------------
*/
int pc_bonus2(dumb_ptr<map_session_data> sd, SP type, int type2, int val)
@@ -1889,7 +1955,7 @@ int pc_skill(dumb_ptr<map_session_data> sd, SkillID id, int level, int flag)
if (!flag && (sd->status.skill[id].lv || level == 0))
{
sd->status.skill[id].lv = level;
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_skillinfoblock(sd);
}
else if (sd->status.skill[id].lv < level)
@@ -2341,6 +2407,13 @@ int pc_useitem(dumb_ptr<map_session_data> sd, IOff0 n)
clif_useitemack(sd, n, amount - 1, 1);
pc_delitem(sd, n, 1, 1);
+ // activity
+ if (sd)
+ if (sd->activity.items_used == 2147483647)
+ sd->activity.items_used = 1;
+ else
+ sd->activity.items_used++;
+
run_script(ScriptPointer(script, 0), sd->bl_id, BlockId());
}
OMATCH_END ();
@@ -2664,6 +2737,13 @@ int pc_walktoxy_sub(dumb_ptr<map_session_data> sd)
}
clif_movechar(sd);
+ // activity
+ if (sd)
+ if (sd->activity.tiles_walked == 2147483647)
+ sd->activity.tiles_walked = 1;
+ else
+ sd->activity.tiles_walked++;
+
return 0;
}
@@ -2841,14 +2921,14 @@ void pc_attack_timer(TimerData *, tick_t tick, BlockId id)
sd->attack_spell_override = BlockId();
pc_set_weapon_icon(sd, 0, StatusChange::ZERO, ItemNameId());
pc_set_attack_info(sd, interval_t::zero(), 0);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
}
else
{
dist = distance(sd->bl_x, sd->bl_y, bl->bl_x, bl->bl_y);
range = sd->attackrange;
- if (sd->status.weapon != ItemLook::BOW)
+ if (sd->status.weapon != ItemLook::W_BOW)
range++;
if (dist > range)
{ //届 かないので移動 | Move because it does not arrive
@@ -2977,7 +3057,7 @@ int pc_checkbaselevelup(dumb_ptr<map_session_data> sd)
clif_updatestatus(sd, SP::STATUSPOINT);
clif_updatestatus(sd, SP::BASELEVEL);
clif_updatestatus(sd, SP::NEXTBASEEXP);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
pc_heal(sd, sd->status.max_hp, sd->status.max_sp, true);
clif_misceffect(sd, 0);
@@ -3036,7 +3116,7 @@ int pc_checkjoblevelup(dumb_ptr<map_session_data> sd)
{ // [Fate] Bah, this is is painful.
// But the alternative is quite error-prone, and eAthena has far worse performance issues...
sd->status.job_exp = next - 1;
- pc_calcstatus(sd,0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
return 0;
}
@@ -3045,7 +3125,7 @@ int pc_checkjoblevelup(dumb_ptr<map_session_data> sd)
clif_updatestatus(sd, SP::NEXTJOBEXP);
sd->status.skill_point++;
clif_updatestatus(sd, SP::SKILLPOINT);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
MAP_LOG_PC(sd, "SKILLPOINTS-UP %d"_fmt, sd->status.skill_point);
@@ -3282,7 +3362,7 @@ int pc_statusup(dumb_ptr<map_session_data> sd, SP type)
}
clif_updatestatus(sd, SP::STATUSPOINT);
clif_updatestatus(sd, type);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_statusupack(sd, type, 1, val);
MAP_LOG_STATS(sd, "STATUP"_fmt);
@@ -3311,7 +3391,7 @@ int pc_statusup2(dumb_ptr<map_session_data> sd, SP type, int val)
sd->status.attrs[attr] = val;
clif_updatestatus(sd, sp_to_usp(type));
clif_updatestatus(sd, type);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_statusupack(sd, type, 1, val);
MAP_LOG_STATS(sd, "STATUP2"_fmt);
@@ -3334,7 +3414,7 @@ int pc_skillup(dumb_ptr<map_session_data> sd, SkillID skill_num)
sd->status.skill_point -= sd->status.skill[skill_num].lv;
sd->status.skill[skill_num].lv++;
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
clif_skillup(sd, skill_num);
clif_updatestatus(sd, SP::SKILLPOINT);
clif_skillinfoblock(sd);
@@ -3366,7 +3446,7 @@ int pc_resetstate(dumb_ptr<map_session_data> sd)
for (ATTR attr : ATTRs)
clif_updatestatus(sd, attr_to_usp(attr));
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
return 0;
}
@@ -3392,7 +3472,7 @@ int pc_resetskill(dumb_ptr<map_session_data> sd)
clif_updatestatus(sd, SP::SKILLPOINT);
clif_skillinfoblock(sd);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
return 0;
}
@@ -3485,7 +3565,7 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd,
pc_set_weapon_icon(sd, 0, StatusChange::ZERO, ItemNameId());
pc_set_attack_info(sd, interval_t::zero(), 0);
}
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
if (battle_config.death_penalty_type > 0 && sd->status.base_level >= 20)
{
@@ -3785,6 +3865,21 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
case SP::MUTE_GUILD:
val = sd ? sd->mute.guild : 0;
break;
+ case SP::KILLS:
+ val = sd->activity.kills;
+ break;
+ case SP::CASTS:
+ val = sd->activity.casts;
+ break;
+ case SP::ITEMS_USED:
+ val = sd->activity.items_used;
+ break;
+ case SP::TILES_WALKED:
+ val = sd->activity.tiles_walked;
+ break;
+ case SP::ATTACKS:
+ val = sd->activity.attacks;
+ break;
case SP::AUTOMOD:
val = sd ? static_cast<int>(sd->automod) : 0;
break;
@@ -3835,7 +3930,7 @@ int pc_setparam(dumb_ptr<block_list> bl, SP type, int val)
clif_updatestatus(sd, SP::NEXTBASEEXP);
clif_updatestatus(sd, SP::STATUSPOINT);
clif_updatestatus(sd, SP::BASEEXP);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
pc_heal(sd, sd->status.max_hp, sd->status.max_sp, true);
break;
case SP::JOBLEVEL:
@@ -3853,7 +3948,7 @@ int pc_setparam(dumb_ptr<block_list> bl, SP type, int val)
clif_updatestatus(sd, SP::JOBLEVEL);
clif_updatestatus(sd, SP::NEXTJOBEXP);
clif_updatestatus(sd, SP::JOBEXP);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
break;
case SP::CLASS:
// TODO: mob class change
@@ -3933,7 +4028,7 @@ int pc_setparam(dumb_ptr<block_list> bl, SP type, int val)
&& !pc_isequip(sd, j))
pc_unequipitem(sd, j, CalcStatus::LATER);
}
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
chrif_save(sd);
clif_fixpcpos(sd);
}
@@ -4034,6 +4129,22 @@ int pc_setparam(dumb_ptr<block_list> bl, SP type, int val)
nullpo_retz(sd);
sd->mute.guild = (val == 1);
break;
+ // atm only setting of casts is needed since magic is handled in serverdata but I let the others here as well for whatever reason
+ case SP::KILLS:
+ sd->activity.kills = val;
+ break;
+ case SP::CASTS:
+ sd->activity.casts = val;
+ break;
+ case SP::ITEMS_USED:
+ sd->activity.items_used = val;
+ break;
+ case SP::TILES_WALKED:
+ sd->activity.tiles_walked = val;
+ break;
+ case SP::ATTACKS:
+ sd->activity.attacks = val;
+ break;
case SP::AUTOMOD:
nullpo_retz(sd);
sd->automod = static_cast<AutoMod>(val);
@@ -4413,7 +4524,7 @@ int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val)
if (reg == stringish<VarName>("PC_DIE_COUNTER"_s) && sd->die_counter != val)
{
sd->die_counter = val;
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
Option<P<struct quest_data>> quest_data_ = questdb_searchname(var);
OMATCH_BEGIN_SOME(quest_data, quest_data_)
@@ -4780,11 +4891,11 @@ int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS)
sd->status.inventory[n].equip = pos;
ItemNameId view_i;
- ItemLook view_l = ItemLook::NONE;
+ ItemLook view_l = ItemLook::W_FIST;
// TODO: This is ugly.
OMATCH_BEGIN_SOME (sdidn, sd->inventory_data[n])
{
- bool look_not_weapon = sdidn->look == ItemLook::NONE;
+ bool look_not_weapon = sdidn->look == ItemLook::W_FIST;
bool equip_is_weapon = bool(sd->status.inventory[n].equip & EPOS::WEAPON);
assert (look_not_weapon != equip_is_weapon);
@@ -4844,7 +4955,7 @@ int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS)
}
pc_signal_advanced_equipment_change(sd, n);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
return 0;
}
@@ -4873,9 +4984,9 @@ int pc_unequipitem(dumb_ptr<map_session_data> sd, IOff0 n, CalcStatus type)
}
if (bool(sd->status.inventory[n].equip & EPOS::WEAPON))
{
- sd->weapontype1 = ItemLook::NONE;
+ sd->weapontype1 = ItemLook::W_FIST;
// when reading the diff, think twice about this
- sd->status.weapon = ItemLook::NONE;
+ sd->status.weapon = ItemLook::W_FIST;
pc_calcweapontype(sd);
pc_set_weapon_look(sd);
}
@@ -4911,7 +5022,7 @@ int pc_unequipitem(dumb_ptr<map_session_data> sd, IOff0 n, CalcStatus type)
}
if (type == CalcStatus::NOW)
{
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
return 0;
@@ -4984,7 +5095,7 @@ int pc_checkitem(dumb_ptr<map_session_data> sd)
pc_setequipindex(sd);
if (calc_flag)
- pc_calcstatus(sd, 2);
+ pc_calcstatus(sd, (int)CalcStatusKind::ITEM_BONUS_RECALC);
return 0;
}
@@ -5369,7 +5480,7 @@ void pc_natural_heal_sub(dumb_ptr<map_session_data> sd)
if (sd->spellpower_bonus_target < sd->spellpower_bonus_current)
{
sd->spellpower_bonus_current = sd->spellpower_bonus_target;
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
else if (sd->spellpower_bonus_target > sd->spellpower_bonus_current)
{
@@ -5377,7 +5488,7 @@ void pc_natural_heal_sub(dumb_ptr<map_session_data> sd)
1 +
((sd->spellpower_bonus_target -
sd->spellpower_bonus_current) >> 5);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
if (sd->sc_data[StatusChange::SC_HALT_REGENERATE].timer)
diff --git a/src/map/pc.t.hpp b/src/map/pc.t.hpp
index c9235fa..870b25a 100644
--- a/src/map/pc.t.hpp
+++ b/src/map/pc.t.hpp
@@ -56,5 +56,15 @@ enum class CalcStatus
NOW,
LATER,
};
+
+enum class CalcStatusKind
+{
+ NORMAL_RECALC = 0,
+ INITIAL_CALC = 1,
+ ITEM_BONUS_RECALC = 2,
+ NORMAL_RECALC_NO_CLIENT_UPDATE = 4,
+ MAGIC_OVERRIDE = 8,
+};
+
} // namespace map
} // namespace tmwa
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index e42dcd2..8dc1989 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -117,6 +117,10 @@ void builtin_mes(ScriptState *st)
clif_scriptmes(sd, st->oid, mes);
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
static
void builtin_mesq(ScriptState *st)
{
@@ -131,6 +135,31 @@ void builtin_mesq(ScriptState *st)
clif_scriptmes(sd, st->oid, RString(mesq));
}
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+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");
+ 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;
+ mesq += "["_s;
+ mesq += mes;
+ mesq += "]"_s;
+ clif_scriptmes(sd, st->oid, RString(mesq));
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
static
void builtin_clear(ScriptState *st)
{
@@ -2423,7 +2452,7 @@ void builtin_overrideattack(ScriptState *st)
sd->attack_spell_override = BlockId();
pc_set_weapon_icon(sd, 0, StatusChange::ZERO, ItemNameId());
pc_set_attack_info(sd, interval_t::zero(), 0);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
}
@@ -2491,7 +2520,7 @@ void builtin_setopt2(ScriptState *st)
return;
sd->opt2 = new_opt2;
clif_changeoption(sd);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
@@ -2700,6 +2729,9 @@ void builtin_mobinfo(ScriptState *st)
case MobInfo::MDEF:
info = get_mob_db(mob_id).mdef;
break;
+ case MobInfo::CRITICAL_DEF:
+ info = get_mob_db(mob_id).critical_def;
+ break;
case MobInfo::STR:
info = get_mob_db(mob_id).attrs[ATTR::STR];
break;
@@ -2757,6 +2789,19 @@ void builtin_mobinfo(ScriptState *st)
case MobInfo::MUTATION_POWER:
info = get_mob_db(mob_id).mutation_power;
break;
+ case MobInfo::DROPID0:
+ info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[0].nameid);
+ break;
+ case MobInfo::DROPNAME0:
+ {
+ Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[0].nameid));
+ info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+ mode = 1;
+ }
+ break;
+ case MobInfo::DROPPERCENT0:
+ info = get_mob_db(mob_id).dropitem[0].p.num;
+ break;
case MobInfo::DROPID1:
info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[0].nameid);
break;
@@ -2861,6 +2906,19 @@ void builtin_mobinfo(ScriptState *st)
case MobInfo::DROPPERCENT8:
info = get_mob_db(mob_id).dropitem[7].p.num;
break;
+ case MobInfo::DROPID9:
+ info = unwrap<ItemNameId>(get_mob_db(mob_id).dropitem[7].nameid);
+ break;
+ case MobInfo::DROPNAME9:
+ {
+ Option<P<struct item_data>> i_data = Some(itemdb_search(get_mob_db(mob_id).dropitem[7].nameid));
+ info_str = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+ mode = 1;
+ }
+ break;
+ case MobInfo::DROPPERCENT9:
+ info = get_mob_db(mob_id).dropitem[7].p.num;
+ break;
default:
PRINTF("builtin_mobinfo: unknown request\n"_fmt);
push_int<ScriptDataInt>(st->stack, -1);
@@ -3842,6 +3900,7 @@ void builtin_sc_start(ScriptState *st)
case StatusChange::SC_COOLDOWN_AR:
case StatusChange::SC_COOLDOWN_ENCH:
case StatusChange::SC_COOLDOWN_KOY:
+ case StatusChange::SC_COOLDOWN_UPMARMU:
break;
default:
@@ -4780,7 +4839,7 @@ void builtin_nude(ScriptState *st)
if (idx.ok())
pc_unequipitem(sd, idx, CalcStatus::LATER);
}
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
@@ -4803,7 +4862,7 @@ void builtin_unequipbyid(ScriptState *st)
pc_unequipitem(sd, idx, CalcStatus::LATER);
}
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
}
@@ -5516,7 +5575,6 @@ void builtin_mapexit(ScriptState *)
runflag = 0;
}
-
#define BUILTIN(func, args, ret) \
{builtin_##func, #func ## _s, args, ret}
@@ -5524,6 +5582,7 @@ BuiltinFunction builtin_functions[] =
{
BUILTIN(mes, "?"_s, '\0'),
BUILTIN(mesq, "?"_s, '\0'),
+ BUILTIN(mesn, "?"_s, '\0'),
BUILTIN(clear, ""_s, '\0'),
BUILTIN(goto, "L"_s, '\0'),
BUILTIN(callfunc, "F"_s, '\0'),
diff --git a/src/map/script-fun.t.hpp b/src/map/script-fun.t.hpp
index d0c753b..6cc2683 100644
--- a/src/map/script-fun.t.hpp
+++ b/src/map/script-fun.t.hpp
@@ -41,49 +41,56 @@ enum class MobInfo : uint8_t
ATK2 = 10,
DEF = 11,
MDEF = 12,
- STR = 13,
- AGI = 14,
- VIT = 15,
- INT = 16,
- DEX = 17,
- LUK = 18,
- RANGE2 = 19,
- RANGE3 = 20,
- SCALE = 21,
- RACE = 22,
- ELEMENT = 23,
- ELEMENT_LVL = 24,
- MODE = 25,
- SPEED = 26,
- ADELAY = 27,
- AMOTION = 28,
- DMOTION = 29,
- MUTATION_NUM = 30,
- MUTATION_POWER = 31,
- DROPID1 = 32,
- DROPNAME1 = 33,
- DROPPERCENT1 = 34,
- DROPID2 = 35,
- DROPNAME2 = 36,
- DROPPERCENT2 = 37,
- DROPID3 = 38,
- DROPNAME3 = 39,
- DROPPERCENT3 = 40,
- DROPID4 = 41,
- DROPNAME4 = 42,
- DROPPERCENT4 = 43,
- DROPID5 = 44,
- DROPNAME5 = 45,
- DROPPERCENT5 = 46,
- DROPID6 = 47,
- DROPNAME6 = 48,
- DROPPERCENT6 = 49,
- DROPID7 = 50,
- DROPNAME7 = 51,
- DROPPERCENT7 = 52,
- DROPID8 = 53,
- DROPNAME8 = 54,
- DROPPERCENT8 = 55,
+ CRITICAL_DEF = 13,
+ STR = 14,
+ AGI = 15,
+ VIT = 16,
+ INT = 17,
+ DEX = 18,
+ LUK = 19,
+ RANGE2 = 20,
+ RANGE3 = 21,
+ SCALE = 22,
+ RACE = 23,
+ ELEMENT = 24,
+ ELEMENT_LVL = 25,
+ MODE = 26,
+ SPEED = 27,
+ ADELAY = 28,
+ AMOTION = 29,
+ DMOTION = 30,
+ MUTATION_NUM = 31,
+ MUTATION_POWER = 32,
+ DROPID0 = 33,
+ DROPNAME0 = 34,
+ DROPPERCENT0 = 35,
+ DROPID1 = 36,
+ DROPNAME1 = 37,
+ DROPPERCENT1 = 38,
+ DROPID2 = 39,
+ DROPNAME2 = 40,
+ DROPPERCENT2 = 41,
+ DROPID3 = 42,
+ DROPNAME3 = 43,
+ DROPPERCENT3 = 44,
+ DROPID4 = 45,
+ DROPNAME4 = 46,
+ DROPPERCENT4 = 47,
+ DROPID5 = 48,
+ DROPNAME5 = 49,
+ DROPPERCENT5 = 50,
+ DROPID6 = 51,
+ DROPNAME6 = 52,
+ DROPPERCENT6 = 53,
+ DROPID7 = 54,
+ DROPNAME7 = 55,
+ DROPPERCENT7 = 56,
+ DROPID8 = 57,
+ DROPNAME8 = 58,
+ DROPPERCENT8 = 59,
+ DROPID9 = 60,
+ DROPNAME9 = 61,
+ DROPPERCENT9 = 62,
};
enum class MobInfo_DropArrays : uint8_t
diff --git a/src/map/skill-pools.cpp b/src/map/skill-pools.cpp
index dfc70b0..ddec824 100644
--- a/src/map/skill-pools.cpp
+++ b/src/map/skill-pools.cpp
@@ -77,7 +77,7 @@ int skill_pool_activate(dumb_ptr<map_session_data> sd, SkillID skill_id)
&& (skill_pool_size(sd) < skill_pool_max(sd)))
{
sd->status.skill[skill_id].flags |= SkillFlags::POOL_ACTIVATED;
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
MAP_LOG_PC(sd, "SKILL-ACTIVATE %d %d %d"_fmt,
skill_id, sd->status.skill[skill_id].lv,
skill_power(sd, skill_id));
@@ -98,7 +98,7 @@ int skill_pool_deactivate(dumb_ptr<map_session_data> sd, SkillID skill_id)
{
sd->status.skill[skill_id].flags &= ~SkillFlags::POOL_ACTIVATED;
MAP_LOG_PC(sd, "SKILL-DEACTIVATE %d"_fmt, skill_id);
- pc_calcstatus(sd, 0);
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC);
return 0;
}
diff --git a/src/map/skill.cpp b/src/map/skill.cpp
index cdaa0ba..87bbbda 100644
--- a/src/map/skill.cpp
+++ b/src/map/skill.cpp
@@ -764,6 +764,7 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa
case StatusChange::SC_COOLDOWN_AR:
case StatusChange::SC_COOLDOWN_ENCH:
case StatusChange::SC_COOLDOWN_KOY:
+ case StatusChange::SC_COOLDOWN_UPMARMU:
break;
/* option2 */
@@ -804,7 +805,7 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa
clif_changeoption(bl);
if (bl->bl_type == BL::PC && calc_flag)
- pc_calcstatus(bl->is_player(), 0); /* ステータス再計算 | Status Recalculation */
+ pc_calcstatus(bl->is_player(), (int)CalcStatusKind::NORMAL_RECALC); /* ステータス再計算 | Status Recalculation */
}
int skill_update_heal_animation(dumb_ptr<map_session_data> sd)
@@ -1041,6 +1042,7 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
case StatusChange::SC_COOLDOWN_AR:
case StatusChange::SC_COOLDOWN_ENCH:
case StatusChange::SC_COOLDOWN_KOY:
+ case StatusChange::SC_COOLDOWN_UPMARMU:
break;
case StatusChange::SC_FLYING_BACKPACK:
updateflag = SP::WEIGHT;
@@ -1083,7 +1085,7 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
bl->bl_id, type));
if (bl->bl_type == BL::PC && calc_flag)
- pc_calcstatus(sd, 0); /* ステータス再計算 | Status recalculation */
+ pc_calcstatus(sd, (int)CalcStatusKind::NORMAL_RECALC); /* ステータス再計算 | Status recalculation */
if (bl->bl_type == BL::PC && updateflag != SP::ZERO)
clif_updatestatus(sd, updateflag); /* ステータスをクライアントに送る | Send status to client */
diff --git a/src/map/storage.cpp b/src/map/storage.cpp
index 1327146..54398f3 100644
--- a/src/map/storage.cpp
+++ b/src/map/storage.cpp
@@ -186,6 +186,12 @@ int storage_storageadd(dumb_ptr<map_session_data> sd, IOff0 index, int amount)
if (amount < 1 || amount > sd->status.inventory[index].amount)
return 0;
+ if (bool(itemdb_search(sd->status.inventory[index].nameid)->mode & ItemMode::NO_STORAGE))
+ {
+ clif_displaymessage(sd->sess, "This item can't be stored."_s);
+ return 0;
+ }
+
// log_tostorage(sd, index, 0);
if (storage_additem(sd, stor, &sd->status.inventory[index], amount) == 0)
{
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index 33a6f3c..f8350a7 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -153,6 +153,7 @@ enum class DamageType : uint8_t
DOUBLED = 0x08,
CRITICAL = 0x0a,
FLEE2 = 0x0b,
+ DEADLY = 0x0c,
};
enum class LOOK : uint8_t
@@ -332,6 +333,12 @@ enum class SP : uint16_t
DEAF = 70,
+ KILLS = 490,
+ CASTS = 491,
+ ITEMS_USED = 492,
+ TILES_WALKED = 493,
+ ATTACKS = 494,
+
// sent to client
GM = 500,
@@ -472,6 +479,17 @@ enum class SP : uint16_t
MUTE_PARTY = 1084,
MUTE_GUILD = 1085,
AUTOMOD = 1086,
+
+ SPEED_CAP = 1087,
+
+ ALL_STATS = 1088,
+ AGI_VIT = 1089,
+ AGI_DEX_STR = 1090,
+
+ DEADLY_STRIKE_RATE = 1091,
+ DEADLY_STRIKE_ADD_RATE = 1092,
+
+ BASE_WEAPON_DELAY_ADJUST = 1093,
};
constexpr
diff --git a/src/mmo/consts.hpp b/src/mmo/consts.hpp
index 5445186..d56facd 100644
--- a/src/mmo/consts.hpp
+++ b/src/mmo/consts.hpp
@@ -36,7 +36,9 @@ constexpr int MAX_MAP_PER_SERVER = 512;
constexpr int MAX_INVENTORY = 100;
constexpr int MAX_AMOUNT = 30000;
constexpr int MAX_ZENY = 1000000000; // 1G zeny
-constexpr int TRADE_MAX = 10;
+constexpr int TRADE_MAX = 12;
+// M+ 1.9.3.23 only supports 12 items in trade window it will make the trade with more but brings error messages for every item above 12.
+// So I let it 12 for now until ManaVerse (with cuocos fix) is the only client.
constexpr int GLOBAL_REG_NUM = 96;
constexpr size_t ACCOUNT_REG_NUM = 16;
@@ -44,7 +46,7 @@ constexpr size_t ACCOUNT_REG2_NUM = 16;
constexpr interval_t DEFAULT_WALK_SPEED = 150_ms;
constexpr interval_t MIN_WALK_SPEED = interval_t::zero();
constexpr interval_t MAX_WALK_SPEED = 1_s;
-constexpr int MAX_STORAGE = 300;
+constexpr int MAX_STORAGE = 500;
constexpr int MAX_PARTY = 120;
#define MIN_HAIR_STYLE battle_config.min_hair_style
diff --git a/src/mmo/enums.hpp b/src/mmo/enums.hpp
index 2564ec9..c4a1b17 100644
--- a/src/mmo/enums.hpp
+++ b/src/mmo/enums.hpp
@@ -99,14 +99,47 @@ constexpr ATTR ATTRs[6] =
enum class ItemLook : uint16_t
{
- NONE = 0,
- BLADE = 1, // or some other common weapons
- SETZER_AND_SCYTHE = 3,
- STAFF = 10,
- BOW = 11,
- COUNT = 17,
+ W_FIST, // 0 Fist
+ W_DAGGER, // 1 Dagger
+ W_1HSWORD, // 2 Sword
+ W_2HSWORD, // 3 TwoHandSword
+ W_1HSPEAR, // 4 Spear
+ W_2HSPEAR, // 5 TwoHandSpear
+ W_1HAXE, // 6 Axe
+ W_2HAXE, // 7 TwoHandAxe
+ W_MACE, // 8 Mace
+ W_2HMACE, // 9 TwoHandMace
+ W_STAFF, // 10 Rod
+ W_BOW, // 11 Bow
+ W_KNUCKLE, // 12 Knuckle
+ W_MUSICAL, // 13 Instrument
+ W_WHIP, // 14 Whip
+ W_BOOK, // 15 Book
+ W_KATAR, // 16 Katar
+ W_REVOLVER, // 17 Revolver
+ W_RIFLE, // 18 Rifle
+ W_GATLING, // 19 GatlingGun
+ W_SHOTGUN, // 20 Shotgun
+ W_GRENADE, // 21 GrenadeLauncher
+ W_HUUMA, // 22 FuumaShuriken
+ W_2HSTAFF, // 23 TwoHandRod
+ COUNT,
};
+namespace e
+{
+enum class ItemMode : uint8_t
+{
+ NONE = 0,
+ NO_DROP = 1,
+ NO_TRADE = 2,
+ NO_SELL_TO_NPC = 4,
+ NO_STORAGE = 8,
+};
+ENUM_BITWISE_OPERATORS(ItemMode)
+}
+using e::ItemMode;
+
enum class SEX : uint8_t
{
FEMALE = 0,
@@ -117,6 +150,7 @@ enum class SEX : uint8_t
NEUTRAL = 3,
__OTHER = 4, // used in ManaPlus only
};
+
inline
char sex_to_char(SEX sex)
{
diff --git a/src/mmo/extract_enums.hpp b/src/mmo/extract_enums.hpp
index 0e8ac4c..14e7b17 100644
--- a/src/mmo/extract_enums.hpp
+++ b/src/mmo/extract_enums.hpp
@@ -35,6 +35,7 @@ enum class EPOS : uint16_t;
enum class Opt1 : uint16_t;
enum class Opt2 : uint16_t;
enum class Opt0 : uint16_t;
+enum class ItemMode : uint8_t;
inline
bool impl_extract(XString str, EPOS *iv) { return extract_as_int(str, iv); }
@@ -44,6 +45,8 @@ inline
bool impl_extract(XString str, Opt2 *iv) { return extract_as_int(str, iv); }
inline
bool impl_extract(XString str, Opt0 *iv) { return extract_as_int(str, iv); }
+inline
+bool impl_extract(XString str, ItemMode *iv) { return extract_as_int(str, iv); }
} // namespace e
enum class ItemLook : uint16_t;
diff --git a/src/mmo/skill.t.hpp b/src/mmo/skill.t.hpp
index 1509852..782980c 100644
--- a/src/mmo/skill.t.hpp
+++ b/src/mmo/skill.t.hpp
@@ -59,6 +59,7 @@ enum class StatusChange : uint16_t
SC_COOLDOWN_AR = 75, // Frillyar cooldown
SC_COOLDOWN_ENCH = 76, // Enchanter cooldown
SC_COOLDOWN_KOY = 77, // Koyntety cooldown
+ SC_COOLDOWN_UPMARMU = 78, // Upmarmu cooldown
SC_POISON = 132, // bad; actually used
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'