summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore16
-rw-r--r--.travis.yml19
-rw-r--r--CHANGELOG94
-rw-r--r--Makefile.in225
-rw-r--r--README257
-rw-r--r--README.md272
-rwxr-xr-xconfigure8
-rw-r--r--generate.make25
-rw-r--r--include/tmwa/shared.hpp4
-rw-r--r--old-doc/mapserver-logging (renamed from doc/mapserver-logging)0
-rw-r--r--old-doc/old-spell-language (renamed from doc/old-spell-language)0
-rw-r--r--share/tmwa/TheManaWorldLogo.pngbin0 -> 27865 bytes
-rw-r--r--src/admin/fwd.hpp13
-rw-r--r--src/admin/globals.cpp48
-rw-r--r--src/admin/globals.hpp (renamed from src/ints/cmp.cpp)17
-rw-r--r--src/admin/ladmin.cpp518
-rw-r--r--src/admin/main.cpp2
-rw-r--r--src/ast/fwd.hpp45
-rw-r--r--src/ast/item.cpp155
-rw-r--r--src/ast/item.hpp82
-rw-r--r--src/ast/item_test.cpp150
-rw-r--r--src/ast/npc.cpp620
-rw-r--r--src/ast/npc.hpp142
-rw-r--r--src/ast/npc_test.cpp556
-rw-r--r--src/ast/quest.cpp125
-rw-r--r--src/ast/quest.hpp65
-rw-r--r--src/ast/script.cpp75
-rw-r--r--src/ast/script.hpp112
-rw-r--r--src/char/char.cpp645
-rw-r--r--src/char/char.hpp25
-rw-r--r--src/char/consts.hpp (renamed from src/map/magic-interpreter.cpp)12
-rw-r--r--src/char/fwd.hpp25
-rw-r--r--src/char/globals.cpp68
-rw-r--r--src/char/globals.hpp59
-rw-r--r--src/char/int_party.cpp159
-rw-r--r--src/char/int_party.hpp11
-rw-r--r--src/char/int_storage.cpp55
-rw-r--r--src/char/int_storage.hpp13
-rw-r--r--src/char/inter.cpp80
-rw-r--r--src/char/inter.hpp20
-rw-r--r--src/char/main.cpp2
-rw-r--r--src/compat/attr.hpp4
-rw-r--r--src/compat/borrow.hpp111
-rw-r--r--src/compat/borrow.py18
-rw-r--r--src/compat/fwd.hpp15
-rw-r--r--src/compat/nullpo.hpp10
-rw-r--r--src/compat/operators.hpp (renamed from src/generic/operators.hpp)0
-rw-r--r--src/compat/option.hpp469
-rw-r--r--src/compat/option.py39
-rw-r--r--src/compat/option_test.cpp499
-rw-r--r--src/compat/rawmem.hpp21
-rw-r--r--src/compat/result.hpp94
-rw-r--r--src/compat/result_test.cpp79
-rw-r--r--src/compat/time_t.cpp26
-rw-r--r--src/compat/time_t.hpp84
-rw-r--r--src/diagnostics.hpp3
-rw-r--r--src/generic/array.hpp9
-rw-r--r--src/generic/db.cpp26
-rw-r--r--src/generic/db.hpp40
-rw-r--r--src/generic/dumb_ptr.cpp26
-rw-r--r--src/generic/dumb_ptr.hpp5
-rw-r--r--src/generic/enum.hpp3
-rw-r--r--src/generic/fwd.hpp32
-rw-r--r--src/generic/matrix.cpp26
-rw-r--r--src/generic/md5.hpp1
-rw-r--r--src/generic/operators.cpp26
-rw-r--r--src/generic/random.hpp4
-rw-r--r--src/generic/random2.hpp4
-rw-r--r--src/high/core.cpp (renamed from src/mmo/core.cpp)18
-rw-r--r--src/high/core.hpp (renamed from src/mmo/core.hpp)2
-rw-r--r--src/high/extract_mmo.cpp (renamed from src/mmo/extract.cpp)43
-rw-r--r--src/high/extract_mmo.hpp (renamed from src/compat/memory.cpp)15
-rw-r--r--src/high/fwd.hpp40
-rw-r--r--src/high/md5more.cpp (renamed from src/mmo/md5more.cpp)2
-rw-r--r--src/high/md5more.hpp (renamed from src/mmo/md5more.hpp)4
-rw-r--r--src/high/mmo.hpp (renamed from src/mmo/mmo.hpp)16
-rw-r--r--src/high/utils.cpp (renamed from src/mmo/utils.cpp)39
-rw-r--r--src/high/utils.hpp (renamed from src/compat/cast.cpp)11
-rw-r--r--src/ints/fwd.hpp2
-rw-r--r--src/ints/little.cpp26
-rw-r--r--src/ints/udl.cpp26
-rw-r--r--src/ints/wrap.cpp26
-rw-r--r--src/ints/wrap.py5
-rw-r--r--src/io/cxxstdio.cpp26
-rw-r--r--src/io/cxxstdio_enums.cpp26
-rw-r--r--src/io/dir.hpp2
-rw-r--r--src/io/extract.cpp220
-rw-r--r--src/io/extract.hpp (renamed from src/mmo/extract.hpp)59
-rw-r--r--src/io/extract_test.cpp (renamed from src/mmo/extract_test.cpp)94
-rw-r--r--src/io/fd.cpp5
-rw-r--r--src/io/fd.hpp2
-rw-r--r--src/io/fwd.hpp13
-rw-r--r--src/io/line.cpp107
-rw-r--r--src/io/line.hpp46
-rw-r--r--src/io/line_test.cpp66
-rw-r--r--src/io/read.cpp47
-rw-r--r--src/io/read.hpp14
-rw-r--r--src/io/read_test.cpp67
-rw-r--r--src/io/span.cpp113
-rw-r--r--src/io/span.hpp92
-rw-r--r--src/io/tty.cpp27
-rw-r--r--src/io/write.cpp2
-rw-r--r--src/io/write.hpp2
-rw-r--r--src/login/consts.hpp (renamed from src/map/magic-expr-eval.cpp)13
-rw-r--r--src/login/fwd.hpp22
-rw-r--r--src/login/globals.cpp54
-rw-r--r--src/login/globals.hpp (renamed from src/mmo/consts.cpp)28
-rw-r--r--src/login/login.cpp1188
-rw-r--r--src/login/login.hpp60
-rw-r--r--src/login/login.t.hpp24
-rw-r--r--src/login/main.cpp2
-rw-r--r--src/main-gdb-head.py89
-rw-r--r--src/map/atcommand.cpp762
-rw-r--r--src/map/atcommand.hpp14
-rw-r--r--src/map/battle.cpp628
-rw-r--r--src/map/battle.hpp132
-rw-r--r--src/map/battle.t.hpp3
-rw-r--r--src/map/chrif.cpp370
-rw-r--r--src/map/chrif.hpp23
-rw-r--r--src/map/clif.cpp399
-rw-r--r--src/map/clif.hpp42
-rw-r--r--src/map/consts.hpp (renamed from src/generic/intern-pool.cpp)14
-rw-r--r--src/map/fwd.hpp37
-rw-r--r--src/map/globals.cpp149
-rw-r--r--src/map/globals.hpp109
-rw-r--r--src/map/grfio.cpp13
-rw-r--r--src/map/grfio.hpp7
-rw-r--r--src/map/intif.cpp20
-rw-r--r--src/map/intif.hpp13
-rw-r--r--src/map/itemdb.cpp173
-rw-r--r--src/map/itemdb.hpp17
-rw-r--r--src/map/magic-expr-eval.hpp3
-rw-r--r--src/map/magic-expr.cpp198
-rw-r--r--src/map/magic-expr.hpp13
-rw-r--r--src/map/magic-expr.py8
-rw-r--r--src/map/magic-interpreter-base.cpp27
-rw-r--r--src/map/magic-interpreter-base.hpp12
-rw-r--r--src/map/magic-interpreter.hpp23
-rw-r--r--src/map/magic-interpreter.py212
-rw-r--r--src/map/magic-interpreter.t.hpp3
-rw-r--r--src/map/magic-stmt.cpp85
-rw-r--r--src/map/magic-stmt.hpp9
-rw-r--r--src/map/magic-stmt.py8
-rw-r--r--src/map/magic-v2.cpp51
-rw-r--r--src/map/magic-v2.hpp3
-rw-r--r--src/map/magic.cpp6
-rw-r--r--src/map/magic.hpp9
-rw-r--r--src/map/main.cpp5
-rw-r--r--src/map/map.cpp523
-rw-r--r--src/map/map.hpp151
-rw-r--r--src/map/map.py34
-rw-r--r--src/map/map.t.hpp10
-rw-r--r--src/map/mapflag.cpp7
-rw-r--r--src/map/mapflag.hpp7
-rw-r--r--src/map/mapflag.py10
-rw-r--r--src/map/mob.cpp151
-rw-r--r--src/map/mob.hpp16
-rw-r--r--src/map/mob.t.hpp3
-rw-r--r--src/map/npc-internal.hpp (renamed from src/compat/rawmem.cpp)19
-rw-r--r--src/map/npc-parse.cpp807
-rw-r--r--src/map/npc-parse.hpp44
-rw-r--r--src/map/npc.cpp956
-rw-r--r--src/map/npc.hpp27
-rw-r--r--src/map/party.cpp143
-rw-r--r--src/map/party.hpp15
-rw-r--r--src/map/path.cpp17
-rw-r--r--src/map/path.hpp5
-rw-r--r--src/map/pc.cpp978
-rw-r--r--src/map/pc.hpp27
-rw-r--r--src/map/pc.t.hpp3
-rw-r--r--src/map/quest.cpp135
-rw-r--r--src/map/quest.hpp (renamed from src/sexpr/bind.cpp)34
-rw-r--r--src/map/script-buffer.hpp45
-rw-r--r--src/map/script-call-internal.hpp100
-rw-r--r--src/map/script-call-internal.tcc79
-rw-r--r--src/map/script-call.cpp926
-rw-r--r--src/map/script-call.hpp67
-rw-r--r--src/map/script-call.t.hpp48
-rw-r--r--src/map/script-fun.cpp3164
-rw-r--r--src/map/script-fun.hpp (renamed from src/sexpr/variant.cpp)33
-rw-r--r--src/map/script-parse-internal.hpp67
-rw-r--r--src/map/script-parse.cpp851
-rw-r--r--src/map/script-parse.hpp (renamed from src/tests/test.cpp)23
-rw-r--r--src/map/script-parse.py553
-rw-r--r--src/map/script-persist.hpp128
-rw-r--r--src/map/script-persist.py37
-rw-r--r--src/map/script-startup-internal.hpp36
-rw-r--r--src/map/script-startup.cpp265
-rw-r--r--src/map/script-startup.hpp (renamed from src/compat/iter.cpp)18
-rw-r--r--src/map/script.cpp5100
-rw-r--r--src/map/script.hpp277
-rw-r--r--src/map/script.py25
-rw-r--r--src/map/skill-pools.cpp22
-rw-r--r--src/map/skill-pools.hpp3
-rw-r--r--src/map/skill.cpp68
-rw-r--r--src/map/skill.hpp18
-rw-r--r--src/map/storage.cpp62
-rw-r--r--src/map/storage.hpp13
-rw-r--r--src/map/tmw.cpp34
-rw-r--r--src/map/tmw.hpp7
-rw-r--r--src/map/trade.cpp43
-rw-r--r--src/map/trade.hpp7
-rw-r--r--src/mmo/clif.t.hpp (renamed from src/map/clif.t.hpp)10
-rw-r--r--src/mmo/config_parse.cpp211
-rw-r--r--src/mmo/config_parse.hpp7
-rw-r--r--src/mmo/config_parse_test.cpp60
-rw-r--r--src/mmo/consts.hpp7
-rw-r--r--src/mmo/cxxstdio_enums.hpp (renamed from src/io/cxxstdio_enums.hpp)51
-rw-r--r--src/mmo/enums.cpp26
-rw-r--r--src/mmo/enums.hpp25
-rw-r--r--src/mmo/extract_enums.cpp33
-rw-r--r--src/mmo/extract_enums.hpp50
-rw-r--r--src/mmo/fwd.hpp36
-rw-r--r--src/mmo/human_time_diff.cpp42
-rw-r--r--src/mmo/human_time_diff.hpp45
-rw-r--r--src/mmo/human_time_diff_test.cpp2
-rw-r--r--src/mmo/ids.cpp16
-rw-r--r--src/mmo/ids.hpp18
-rw-r--r--src/mmo/ids.py1
-rw-r--r--src/mmo/login.t.hpp44
-rw-r--r--src/mmo/mmo.cpp26
-rw-r--r--src/mmo/skill.t.hpp (renamed from src/map/skill.t.hpp)3
-rw-r--r--src/mmo/strs.cpp6
-rw-r--r--src/mmo/strs.hpp33
-rw-r--r--src/mmo/utils.hpp167
-rw-r--r--src/mmo/version.cpp33
-rw-r--r--src/mmo/version.hpp9
-rw-r--r--src/monitor/main.cpp257
-rw-r--r--src/net/fwd.hpp9
-rw-r--r--src/net/ip.cpp34
-rw-r--r--src/net/ip.hpp8
-rw-r--r--src/net/ip_test.cpp1
-rw-r--r--src/net/socket.cpp16
-rw-r--r--src/net/socket.hpp19
-rw-r--r--src/net/timer.hpp3
-rw-r--r--src/net/timer.py117
-rw-r--r--src/net/timestamp-utils.cpp72
-rw-r--r--src/net/timestamp-utils.hpp54
-rw-r--r--src/proto-base/fwd.hpp (renamed from src/generic/array.cpp)12
-rw-r--r--src/proto-base/net-array.hpp53
-rw-r--r--src/proto-base/net-neutral.hpp38
-rw-r--r--src/proto-base/net-skewed-length.hpp46
-rw-r--r--src/proto-base/net-string.hpp87
-rw-r--r--src/range/fwd.hpp2
-rw-r--r--src/range/slice.cpp26
-rw-r--r--src/sanity.hpp10
-rw-r--r--src/sexpr/fwd.hpp4
-rw-r--r--src/sexpr/lexer.hpp10
-rw-r--r--src/sexpr/lexer_test.cpp32
-rw-r--r--src/sexpr/parser.hpp2
-rw-r--r--src/sexpr/parser_test.cpp25
-rw-r--r--src/sexpr/union_test.cpp (renamed from src/sexpr/union.cpp)2
-rw-r--r--src/sexpr/variant.hpp49
-rw-r--r--src/sexpr/variant.tcc3
-rw-r--r--src/sexpr/variant_test.cpp32
-rw-r--r--src/sexpr/void.cpp29
-rw-r--r--src/strings/astring.py2
-rw-r--r--src/strings/fwd.hpp2
-rw-r--r--src/strings/rstring.cpp21
-rw-r--r--src/strings/rstring.hpp2
-rw-r--r--src/strings/rstring.py7
-rw-r--r--src/strings/strings2_test.cpp9
-rw-r--r--src/strings/vstring.cpp29
-rw-r--r--src/strings/xstring.py12
-rw-r--r--src/strings/zstring.py12
-rw-r--r--src/tests/fdhack.cpp26
-rw-r--r--src/tests/fwd.hpp4
-rw-r--r--src/warnings.hpp3
-rw-r--r--src/wire/fwd.hpp (renamed from src/generic/enum.cpp)14
-rw-r--r--src/wire/packets.cpp (renamed from src/net/packets.cpp)20
-rw-r--r--src/wire/packets.hpp (renamed from src/net/packets.hpp)9
-rwxr-xr-xtools/colorize11
-rwxr-xr-xtools/config.py684
-rwxr-xr-xtools/debug-debug-scripts107
-rw-r--r--tools/debug-debug.gdb5
-rwxr-xr-xtools/nightly111
-rwxr-xr-xtools/protocol.py3733
277 files changed, 21296 insertions, 14659 deletions
diff --git a/.gitignore b/.gitignore
index 77f1b19..50fc305 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,16 +2,30 @@
/conf-raw/
/obj/
/bin/
+/stamp/
/dist/
+/doc-gen/
# Generated source files
/src/proto2/
-/src/debug-debug/test.cpp
+/src/debug-debug/
+/src/*/*_conf.[ch]pp
# tags file
/tags
# generated by configure
/Makefile
/config.status
/lib
+# generated by python
+*.pyc
+
# nightlies
/build-x86_64-linux-gnu/
/build-i586-linux-gnu/
+# the rest are not active yet, see comments in tools/nightly
+/build-x86_64-linux-gnux32/
+/build-aarch64-linux-gnu/
+/build-arm-linux-gnueabi/
+/build-arm-linux-gnueabihf/
+/build-mips-linux-gnu/
+/build-mipsel-linux-gnu/
+/build-powerpc-linux-gnu/
diff --git a/.travis.yml b/.travis.yml
index 9e700cc..d857970 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -27,9 +27,17 @@ install:
-
if [ -n "$PPA" ];
then
+ echo sudo add-apt-repository --yes $PPA;
sudo add-apt-repository --yes $PPA;
fi
+ -
+ if [ -n "$PPA2" ];
+ then
+ echo sudo add-apt-repository --yes $PPA2;
+ sudo add-apt-repository --yes $PPA2;
+ fi
- sudo apt-get update -qq
+ - echo sudo apt-get install -qq $PACKAGE $DEBUGPACKAGE
- sudo apt-get install -qq $PACKAGE $DEBUGPACKAGE
- sudo apt-get install -qq libgtest-dev valgrind
- make --version
@@ -42,13 +50,14 @@ before_script:
wget http://clang.llvm.org/libstdc++4.6-clang11.patch;
sudo patch /usr/include/c++/4.6/type_traits < libstdc++4.6-clang11.patch;
fi
- - sudo sed -i /usr/lib/debug/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.*-gdb.py -e "s:^libdir = .*$:libdir = '/usr/lib/x86_64-linux-gnu':"
+ - if test -f /usr/lib/debug/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.*-gdb.py; then sudo sed -i /usr/lib/debug/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.*-gdb.py -e "s:^libdir = .*$:libdir = '/usr/lib/x86_64-linux-gnu':"; fi
## Main test script
script:
- mkdir build
- cd build
- git init
+ - echo ../configure --build=x86_64-linux-gnu --dev CPPFLAGS=-DQUIET `! [[ $CXX =~ clang* ]] || echo --disable-abi6` $EXTRA_CONFIGURE_ARGS
- ../configure --build=x86_64-linux-gnu --dev CPPFLAGS=-DQUIET `! [[ $CXX =~ clang* ]] || echo --disable-abi6` $EXTRA_CONFIGURE_ARGS
- make -R -k -j2
- make -R -k -j2 test TESTER='valgrind --error-exitcode=1 --track-fds=yes'
@@ -77,20 +86,22 @@ env:
matrix:
allow_failures:
- compiler: clang
- env: REAL_CC=clang-3.2 REAL_CXX=clang++-3.2 PPA=ppa:h-rayflood/llvm PACKAGE=clang-3.2 DEBUGPACKAGE=libstdc++6-4.6-dbg
+ env: REAL_CC=clang-3.3 REAL_CXX=clang++-3.3 PPA=ppa:h-rayflood/llvm PACKAGE=clang-3.3 DEBUGPACKAGE=libstdc++6-4.6-dbg
exclude:
- env: ignore=this
include:
- compiler: clang
- env: REAL_CC=clang-3.2 REAL_CXX=clang++-3.2 PPA=ppa:h-rayflood/llvm PACKAGE=clang-3.2 DEBUGPACKAGE=libstdc++6-4.6-dbg
- - compiler: clang
env: REAL_CC=clang-3.3 REAL_CXX=clang++-3.3 PPA=ppa:h-rayflood/llvm PACKAGE=clang-3.3 DEBUGPACKAGE=libstdc++6-4.6-dbg
- compiler: clang
env: REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PPA=ppa:h-rayflood/llvm PACKAGE=clang-3.4 DEBUGPACKAGE=libstdc++6-4.6-dbg
+ - compiler: clang
+ env: REAL_CC=clang-3.5 REAL_CXX=clang++-3.5 PPA=ppa:h-rayflood/llvm-upper PPA2=ppa:h-rayflood/gcc-upper PACKAGE=clang-3.5 DEBUGPACKAGE=libstdc++6-4.6-dbg
- compiler: gcc
env: REAL_CC=gcc-4.7 REAL_CXX=g++-4.7 PPA=ppa:ubuntu-toolchain-r/test PACKAGE=g++-4.7 DEBUGPACKAGE=libstdc++6-4.8-dbg
- compiler: gcc
env: REAL_CC=gcc-4.8 REAL_CXX=g++-4.8 PPA=ppa:ubuntu-toolchain-r/test PACKAGE=g++-4.8 DEBUGPACKAGE=libstdc++6-4.8-dbg
+ - compiler: gcc
+ env: REAL_CC=gcc-4.9 REAL_CXX=g++-4.9 PPA=ppa:ubuntu-toolchain-r/test PACKAGE=g++-4.9 DEBUGPACKAGE=libstdc++6-4.9-dbg
- compiler: gcc
env: REAL_CC=gcc-4.7 REAL_CXX=g++-4.7 PPA=ppa:ubuntu-toolchain-r/test PACKAGE=g++-4.7 DEBUGPACKAGE=libstdc++6-4.8-dbg EXTRA_CONFIGURE_ARGS=--disable-warnings
diff --git a/CHANGELOG b/CHANGELOG
index 4258e94..79900d6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,95 @@
+v15.5.04
+ - remove check for pk_mode for mapflag NOPVP
+ - remove atcommand gm
+ - add atcommand npc
+ - remove builtin readparam
+ - remove builtin statusup2
+ - remove builtin changesex
+ - remove builtin gmcommand
+ - add builtin wgm
+ - add builtin gmlog
+ - do not force disconnection after sex change
+ - add new parameter to builtin emotion
+ - remove braces and jname from getitemlink
+ - add quest log
+ - add sanity check to freeloop
+ - add OnPCLoginEvent handler
+ - some typo fixes and rewording
+v15.4.20
+ - make spam warnings appear in General chat tab
+ - add builtin getnpcx
+ - add builtin getnpcy
+ - add builtin pvp
+ - add builtin getpvpflag
+ - add builtin strnpcinfo
+ - remove builtin cmdothernpc
+ - remove builtin killmonsterall
+ - remove builtin percentheal
+ - remove builtin itemheal
+ - modify builtin heal to include itemheal
+ - remove atcommand killer
+ - remove atcommand killable
+ - remove atcommand charkiller
+ - add atcommand pvp
+ - add atcommand charpvp
+ - add atcommand exprate
+ - remove warp debug (npc 722)
+ - add support for emote IDs over 100
+v15.2.28
+ - Added Freeloop
+v15.1.23:
+ - update changelog for this and last release
+ - segregate per-server global variables
+ - use namespaces for every server
+ - generate all config parsers
+ - support fine-tuned per-version sections in config files
+ - track spans for config parsing
+ - remove deprecated config settings
+ - fuck adl
+ - error out (after global ctors) if someone is dumb enough to run as root
+ - really fix shop prices
+ - add gcc 4.9 on travis
+ - use an uglier bug more reliable macro for MATCH
+ - fill the house with dancing elves, it is the xmas season
+ - workaround the clang bug preventing shared libraries
+ - fix clang 3.5 builds (previously only 3.4 worked)
+ - fix infinite loop in previous release
+ - flatten protocol generator and add pre/post/desc digraph and wiki
+ - new script builtins: npcareawarp, iscollision
+ - use manaplus url format
+ - fix nightlies
+v14.11.11:
+ - avoid this release at all costs
+ - fix an obscure bug breaking asan
+ - add new way of checking for mapless events
+ - fix off-by-one overflow with too many script arguments
+ - more script debugging in python instead of in the executable
+ - attempt to fix shops
+ - eliminate buggy microoptimization with sc_count
+ - fix crash with divorce
+ - parse npcs and items into an ast first
+ - simplify nightly cross-builds now that there's a defaults package
+ - semiformalize the concept of "ranked" directories of headers
+ - convert readme to markdown
+ - fix bugs caused by using more Option<T>
+ - bump clang version on travis
+ - various build system improvements to build more, smaller, files
+ - pretty-print pointers by symbol/segment
+ - move more stuff to Option<T>
+ - split parsing functions into their own files
+ - get rid of crazy 2-based arrays for script arguments
+ - finish removing offhanded weapons
+v14.10.5:
+ - fix the stupid refactoring bug with mob slave summons
+ - officially drop support for clang, all versions are too buggy
+ - implement Option<T> and Borrowed<T> with tests, but don't use them yet
+ - rename enum Option to Opt0 to move it out of the way
+ - add support for static libraries (clang is being retarded again)
+ - critical fix for the bug in parties (thanks Rawng)
+ - add support for cross-builds to the buildbot script
+ - fix a refactoring bug in magic
+ - fix an exposed bug in the magic 'dir_towards' function
+ - fix an exposed bug in char-server whispering
v14.8.29:
- fix testsuite
- add missing changelog
@@ -32,7 +124,7 @@ v14.6.30:
- play with obscure corners of the C++ standard that no one can interpret
- replace hand-coded network protocols with generate ones
- lots source layout and formatting cleanup
- - faster depedency calculation
+ - faster dependency calculation
- strict ids (yay less bugs, boo account/block entanglement)
- beginning of integers
- support libc++ (experimental)
diff --git a/Makefile.in b/Makefile.in
index 3cf1817..006594a 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -44,12 +44,14 @@ LDLIBS = @LIBS@
CXX = @CXX@
CXXFLAGS = @CXXFLAGS@
GTEST_DIR = @GTEST_DIR@
+GDB = @GDB@
ENABLE_WARNINGS = @ENABLE_WARNINGS@
ENABLE_ABI6 = @ENABLE_ABI6@
ENABLE_CYGWIN_HACKS = @ENABLE_CYGWIN_HACKS@
ENABLE_DEBUG = @ENABLE_DEBUG@
ENABLE_RPATH = @ENABLE_RPATH@
+ENABLE_SHARED = @ENABLE_SHARED@
TESTER =
TEST_ARGS =
@@ -150,6 +152,18 @@ $(info The Road goes ever on and on ...)
endif
endif
+include ${SRC_DIR}/version.make
+
+ifeq (${ENABLE_SHARED},yes)
+LIB_SUFFIX_FAKE := so
+LIB_SUFFIX_SHORT := ${SO_SHORT}
+LIB_SUFFIX_LONG := ${SO_LONG}
+else
+LIB_SUFFIX_FAKE := a
+LIB_SUFFIX_SHORT := a
+LIB_SUFFIX_LONG := a
+endif
+
c = @colorize 6: 2:$< 3:$@ :
ifneq '$c' ''
l = @colorize 6: $(patsubst %,2:%,$^) 3:$@ :
@@ -171,7 +185,7 @@ SHELL=bash
# need to generate source files before path lists
$(shell make -f ${SRC_DIR}/generate.make >&2)
-obj/generated.stamp:
+stamp/generated.stamp:
# if you get here, the shell above failed
false
@@ -182,10 +196,16 @@ PIES := $(shell cd ${SRC_DIR}; find src/ -name '*.py')
PIES := $(filter-out src/main-gdb-%.py,${PIES})
SOURCES := ${REAL_SOURCES}
HEADERS := ${REAL_HEADERS}
+CHECK_HEADERS := $(patsubst src/%.hpp,stamp/%.hpp.check,$(filter %.hpp,${REAL_HEADERS}))
+CHECK_RANK_FWDS := $(patsubst src/%,stamp/%.rank,${REAL_HEADERS} $(filter-out %_test.cpp,${REAL_SOURCES}))
+CHECK_FWDS := $(patsubst src/%/fwd.hpp,stamp/%.fwdcheck,$(filter %/fwd.hpp,${REAL_HEADERS}))
PATTERN_ROOTS := $(patsubst src/%.cpp,%,${SOURCES})
+PATTERN_PIES := $(patsubst src/%.py,%,${PIES})
PATTERN_MAINS := $(patsubst %/main,%,$(filter %/main,${PATTERN_ROOTS}))
PATTERN_LIBS := $(patsubst %/lib,%,$(filter %/lib,${PATTERN_ROOTS}))
PATTERN_TESTS := $(patsubst %/test,%,$(filter %/test,${PATTERN_ROOTS}))
+PATTERN_GTESTS := $(subst /,--,$(patsubst %_test,%,$(filter %_test,${PATTERN_ROOTS})))
+PATTERN_DTESTS := $(patsubst debug-debug/%,%,$(filter debug-debug/%,${PATTERN_ROOTS}))
DEPENDS := $(patsubst src/%.cpp,obj/%.d,${SOURCES})
PREPROCESSED := $(patsubst %.d,%.ii,${DEPENDS})
IRS := $(patsubst %.d,%.ll,${DEPENDS})
@@ -196,9 +216,18 @@ PIC_OBJECTS := $(patsubst %.d,%.pic.o,${DEPENDS})
MAIN_SOURCES := $(filter %/main.cpp,${SOURCES})
LIB_SOURCES := $(filter %/lib.cpp,${SOURCES})
TEST_SOURCES := $(filter %/test.cpp,${SOURCES})
-BINARIES := $(patsubst src/%/main.cpp,bin/${tmwa}-%,${MAIN_SOURCES})
-LIBRARIES := $(patsubst src/%/lib.cpp,lib/lib${tmwa}-%.so,${LIB_SOURCES})
-TEST_BINARIES := $(patsubst src/%/test.cpp,bin/test-%,${TEST_SOURCES})
+GTEST_SOURCES := $(filter %_test.cpp,${SOURCES})
+DTEST_SOURCES := $(filter src/debug-debug/%.cpp,${SOURCES})
+BINARIES := $(patsubst src/%/main.cpp,bin/${tmwa}-%.elf,${MAIN_SOURCES})
+LIBRARIES := $(patsubst src/%/lib.cpp,lib/lib${tmwa}-%.${LIB_SUFFIX_FAKE},${LIB_SOURCES})
+TEST_BINARIES := $(patsubst src/%/test.cpp,bin/tests/test-%.elf,${TEST_SOURCES})
+GTEST_BINARIES := $(patsubst src--%_test.cpp,bin/tests/gtest-%.elf,$(subst /,--,${GTEST_SOURCES}))
+DTEST_BINARIES := $(patsubst src/debug-debug/%.cpp,bin/tests/dtest-%.elf,${DTEST_SOURCES})
+
+DOC_DOTS := $(shell cd ${SRC_DIR}; find doc-gen/ -name '*.gv')
+DOC_PNGS := $(patsubst %.gv,${SRC_DIR}/%.png,${DOC_DOTS})
+DOC_OBSOLETE :=
+DOC_OBSOLETE += $(filter-out ${DOC_PNGS},$(wildcard ${SRC_DIR}/doc-gen/*.png))
# tricky part
@@ -213,8 +242,8 @@ TEST_BINARIES := $(patsubst src/%/test.cpp,bin/test-%,${TEST_SOURCES})
define RECURSIVE_DEPS_IMPL
$(eval more_deps := $(value ${1}))
$(eval more_deps := $(patsubst src/%.hpp,%,${more_deps}))
-$(eval lib_deps += $(patsubst include/${tmwa}/%.hpp,lib/lib${tmwa}-%.${SO_LONG},$(filter include/${tmwa}/%.hpp,${more_deps})))
-$(eval more_deps := $(filter ${PATTERN_ROOTS},${more_deps}))
+$(eval lib_deps += $(patsubst include/${tmwa}/%.hpp,lib/lib${tmwa}-%.${LIB_SUFFIX_LONG},$(filter include/${tmwa}/%.hpp,${more_deps})))
+$(eval more_deps := $(filter ${PATTERN_ROOTS} ${PATTERN_PIES},${more_deps}))
$(eval more_deps := $(filter-out ${cur_deps},${more_deps}))
$(eval cur_deps += ${more_deps})
$(foreach dep,${more_deps},${call RECURSIVE_DEPS_IMPL,${dep}})
@@ -229,8 +258,9 @@ define RECURSIVE_DEPS
$(eval cur_deps := ${1})
$(eval lib_deps :=)
$(call RECURSIVE_DEPS_IMPL,${1})
+$(eval py_deps := $(filter ${PATTERN_PIES},${cur_deps}))
-${cur_deps}
+$(filter ${PATTERN_ROOTS},${cur_deps})
endef
# Apply the rules to all the main.cpp files
@@ -302,9 +332,6 @@ else
include ${DEPENDS}
endif
-include ${SRC_DIR}/version.make
-
-
thisdir := $(abspath .)
#$(foreach root,${PATTERN_ROOTS},$(info pre-root: ${root} := $(value ${root}))$(info ))
# can't do $(filter %.hpp yet)
@@ -320,37 +347,59 @@ $(foreach root,${PATTERN_ROOTS},$(eval obj/${root}.ii obj/${root}.ll obj/${root}
$(foreach test,tests,$(eval ${test}/test += $(patsubst %,src/%.hpp,$(filter %_test,${PATTERN_ROOTS}))))
-$(foreach main,${PATTERN_MAINS},$(eval main-${main} := $(strip $(call RECURSIVE_DEPS,${main}/main))) $(eval main-${main}-libs := ${lib_deps}))
+$(foreach it,${PATTERN_MAINS},$(eval main-${it} := $(strip $(call RECURSIVE_DEPS,${it}/main))) $(eval main-${it}-libs := ${lib_deps}) $(eval main-${it}-pies := ${py_deps}))
+# actual rule deps
+$(foreach it,${PATTERN_MAINS},$(eval bin/${tmwa}-${it}.elf : $(patsubst %,obj/%.pdc.o,$(value main-${it})) $(value main-${it}-libs)))
+$(foreach it,${PATTERN_MAINS},$(eval bin/${tmwa}-${it}-gdb.py : $(patsubst %,src/%.py,$(value main-${it}-pies))))
+#$(foreach it,${PATTERN_MAINS},$(info post-main: main-${it}: $(value main-${it})) $(info post-main: main-${it}-libs: $(value main-${it}-libs)) $(info ))
+
+$(foreach it,${PATTERN_LIBS},$(eval lib-${it} := $(strip $(call RECURSIVE_DEPS,${it}/lib))) $(eval lib-${it}-libs := ${lib_deps}) $(eval lib-${it}-pies := ${py_deps}))
+# actual rule deps
+$(foreach it,${PATTERN_LIBS},$(eval lib/lib${tmwa}-${it}.a : $(patsubst %,obj/%.pdc.o,$(value lib-${it})) $(filter-out lib/lib${tmwa}-${it}.a,$(value lib-${it}-libs))))
+# no -gdb.py for static libs
+$(foreach it,${PATTERN_LIBS},$(eval lib/lib${tmwa}-${it}.${SO_LONG} : $(patsubst %,obj/%.pic.o,$(value lib-${it})) $(filter-out lib/lib${tmwa}-${it}.${SO_LONG},$(value lib-${it}-libs))))
+$(foreach it,${PATTERN_LIBS},$(eval lib/lib${tmwa}-${it}.${SO_LONG}-gdb.py : $(patsubst %,src/%.py,$(value lib-${it}-pies))))
+#$(foreach it,${PATTERN_LIBS},$(info post-lib: lib-${it}: $(value lib-${it})) $(info post-lib: lib-${it}-libs: $(value lib-${it}-libs)) $(info ))
+
+$(foreach it,${PATTERN_TESTS},$(eval test-${it} := $(strip $(call RECURSIVE_DEPS,${it}/test))) $(eval test-${it}-libs := ${lib_deps}) $(eval test-${it}-pies := ${py_deps}))
# actual rule deps
-$(foreach main,${PATTERN_MAINS},$(eval bin/${tmwa}-${main} : $(patsubst %,obj/%.pdc.o,$(value main-${main})) $(value main-${main}-libs)))
-$(foreach main,${PATTERN_MAINS},$(eval bin/${tmwa}-${main}-gdb.py : $(filter ${PIES},$(patsubst %,src/%.py,$(value main-${main})))))
-#$(foreach main,${PATTERN_MAINS},$(info post-main: main-${main}: $(value main-${main})) $(info post-main: main-${main}-libs: $(value main-${main}-libs)) $(info ))
+$(foreach it,${PATTERN_TESTS},$(eval bin/tests/test-${it}.elf : $(patsubst %,obj/%.pdc.o,$(value test-${it})) $(value test-${it}-libs)))
+$(foreach it,${PATTERN_TESTS},$(eval bin/tests/test-${it}-gdb.py : $(patsubst %,src/%.py,$(value test-${it}-pies))))
+#$(foreach it,${PATTERN_TESTS},$(info post-test: test-${it}: $(value test-${it})) $(info post-test: test-${it}-libs: $(value test-${it}-libs)) $(info ))
-$(foreach lib,${PATTERN_LIBS},$(eval lib-${lib} := $(strip $(call RECURSIVE_DEPS,${lib}/lib))) $(eval lib-${lib}-libs := ${lib_deps}))
+$(foreach it,${PATTERN_GTESTS},$(eval gtest-${it} := $(strip $(call RECURSIVE_DEPS,$(subst --,/,${it})_test))) $(eval gtest-${it}-libs := ${lib_deps}) $(eval gtest-${it}-pies := ${py_deps}))
# actual rule deps
-$(foreach lib,${PATTERN_LIBS},$(eval lib/lib${tmwa}-${lib}.${SO_LONG} : $(patsubst %,obj/%.pic.o,$(value lib-${lib})) $(filter-out lib/lib${tmwa}-${lib}.${SO_LONG},$(value lib-${lib}-libs))))
-$(foreach lib,${PATTERN_LIBS},$(eval lib/lib${tmwa}-${lib}.${SO_LONG}-gdb.py : $(filter ${PIES},$(patsubst %,src/%.py,$(value lib-${lib})))))
-#$(foreach lib,${PATTERN_LIBS},$(info post-lib: lib-${lib}: $(value lib-${lib})) $(info post-lib: lib-${lib}-libs: $(value lib-${lib}-libs)) $(info ))
+$(foreach it,${PATTERN_GTESTS},$(eval bin/tests/gtest-${it}.elf : $(patsubst %,obj/%.pdc.o,$(value gtest-${it})) $(value gtest-${it}-libs)))
+$(foreach it,${PATTERN_GTESTS},$(eval bin/tests/gtest-${it}-gdb.py : $(patsubst %,src/%.py,$(value gtest-${it}-pies))))
+#$(foreach it,${PATTERN_GTESTS},$(info post-gtest: gtest-${it}: $(value gtest-${it})) $(info post-gtest: gtest-${it}-libs: $(value gtest-${it}-libs)) $(info ))
-$(foreach test,${PATTERN_TESTS},$(eval test-${test} := $(strip $(call RECURSIVE_DEPS,${test}/test))) $(eval test-${test}-libs := ${lib_deps}))
+$(foreach it,${PATTERN_DTESTS},$(eval dtest-${it} := $(strip $(call RECURSIVE_DEPS,debug-debug/${it}))) $(eval dtest-${it}-libs := ${lib_deps}) $(eval dtest-${it}-pies := ${py_deps}))
# actual rule deps
-$(foreach test,${PATTERN_TESTS},$(eval bin/test-${test} : $(patsubst %,obj/%.pdc.o,$(value test-${test})) $(value test-${test}-libs)))
-$(foreach test,${PATTERN_TESTS},$(eval bin/test-${test}-gdb.py : $(filter ${PIES},$(patsubst %,src/%.py,$(value test-${test})))))
-#$(foreach test,${PATTERN_TESTS},$(info post-test: test-${test}: $(value test-${test})) $(info post-test: test-${test}-libs: $(value test-${test}-libs)) $(info ))
+$(foreach it,${PATTERN_DTESTS},$(eval bin/tests/dtest-${it}.elf : $(patsubst %,obj/%.pdc.o,$(value dtest-${it})) $(value dtest-${it}-libs)))
+$(foreach it,${PATTERN_DTESTS},$(eval bin/tests/dtest-${it}-gdb.py : $(patsubst %,src/%.py,$(value dtest-${it}-pies))))
+#$(foreach it,${PATTERN_DTESTS},$(info post-dtest: dtest-${it}: $(value dtest-${it})) $(info post-dtest: dtest-${it}-libs: $(value dtest-${it}-libs)) $(info ))
-vpath %.cpp ${SRC_DIR}
-vpath %.hpp ${SRC_DIR}
-vpath %.tcc ${SRC_DIR}
+vpath src/%.cpp ${SRC_DIR}
+vpath src/%.hpp ${SRC_DIR}
+vpath include/%.hpp ${SRC_DIR}
+vpath src/%.tcc ${SRC_DIR}
vpath tools/% ${SRC_DIR}
-vpath %.py ${SRC_DIR}
+vpath src/%.py ${SRC_DIR}
.DELETE_ON_ERROR:
.DEFAULT_GOAL := all
# main goals
all: bin lib
+.PHONY: bin lib
bin: ${BINARIES}
-lib: $(patsubst %.so,%.${SO_LONG},${LIBRARIES})
+ifeq (${ENABLE_SHARED},yes)
+lib: sharedlib
+else
+lib: staticlib
+endif
+sharedlib: $(patsubst %.${LIB_SUFFIX_FAKE},%.${SO_LONG},${LIBRARIES})
+staticlib: $(patsubst %.${LIB_SUFFIX_FAKE},%.a,${LIBRARIES})
ii: ${PREPROCESSED}
ll: ${IRS}
bc: ${BITCODES}
@@ -361,7 +410,7 @@ o: ${PDC_OBJECTS}
# currently defined for PDC
clean-stamp:
- -$l find obj -name '*.stamp' -delete
+ $l rm -rf stamp
clean-deps:
-$l find obj -name '*.d' -delete
clean-format:
@@ -371,17 +420,18 @@ clean-obj:
clean-conf:
$l rm -rf conf-raw
mostlyclean: clean-conf
- $l rm -rf obj
+ $l rm -rf obj stamp
clean: mostlyclean
$l rm -rf bin lib
distclean: clean gen-clean
gen-clean:
- $l rm -f obj/generated.stamp
+ $l rm -f stamp/generate*.stamp
$l rm -rf ${SRC_DIR}/src/proto2/
$l rm -rf ${SRC_DIR}/src/debug-debug/
+ $l rm -rf ${SRC_DIR}/src/*/*_conf.[ch]pp
ifndef MAKE_RESTARTS
-obj/%.d: src/%.cpp | obj/generated.stamp
+obj/%.d: src/%.cpp | stamp/generated.stamp
$(MKDIR_FIRST)
# Not using $c because it's slow and this should be fast
${CXX} ${CPPFLAGS} -DGENERATING_DEPENDENCIES ${CXXFLAGS} -MG -MM \
@@ -407,6 +457,10 @@ obj/%.pdc.o: src/%.cpp
obj/%.pic.o: src/%.cpp
$(MKDIR_FIRST)
$c ${CXX} ${CPPFLAGS} ${CXXFLAGS} -fPIC -c -o $@ $<
+stamp/%.hpp.check: src/%.hpp
+ $(MKDIR_FIRST)
+ ${CXX} ${CPPFLAGS} ${CXXFLAGS} -x c++ -fsyntax-only - <<< '#include "$<"'
+ touch $@
bin/%-gdb.py: src/main-gdb-head.py src/main-gdb-tail.py
$(MKDIR_FIRST)
@@ -414,7 +468,9 @@ bin/%-gdb.py: src/main-gdb-head.py src/main-gdb-tail.py
$(filter-out %/main-gdb-head.py %/main-gdb-tail.py,$^) \
$(filter %/main-gdb-tail.py,$^) \
> $@
-bin/%: bin/%-gdb.py
+ @# aliases to let in-place execution work
+ ln -sf $(notdir $@) $(patsubst %-gdb.py,%.elf-gdb.py,$@)
+bin/%.elf: bin/%-gdb.py
$(MKDIR_FIRST)
$l ${CXX} ${LDFLAGS} $(filter-out bin/%-gdb.py,$^) ${LDLIBS} -o $@
@@ -427,33 +483,74 @@ lib/%.${SO_LONG}-gdb.py:
lib/%.${SO_LONG}: lib/%.${SO_LONG}-gdb.py
$(MKDIR_FIRST)
$l ${CXX} -shared -Wl,-soname=$*.${SO_SHORT} ${LDFLAGS} $(filter-out lib/%-gdb.py,$^) ${LDLIBS} -o $@
- $c ln -sf $*.${SO_LONG} lib/$*.${SO_SHORT}
- $c ln -sf $*.${SO_SHORT} lib/$*.so
+ $c ln -sfT $*.${SO_LONG} lib/$*.${SO_SHORT}
+ $c ln -sfT $*.${SO_SHORT} lib/$*.so
+lib/%.a:
+ $(MKDIR_FIRST)
+ rm -f $@
+ ar cr $@ $^
-#${TEST_BINARIES}: obj/gtest-all.pdc.o
-bin/test-tests: obj/gtest-all.pdc.o
+${GTEST_BINARIES}: obj/gtest_main.pdc.o obj/gtest-all.pdc.o
# This isn't perfect.
-$(filter %_test.pdc.o,${PDC_OBJECTS}) obj/gtest-all.pdc.o: override CPPFLAGS += -DGTEST_HAS_PTHREAD=0 -I${GTEST_DIR}
-obj/gtest-all.pdc.o: override WARNINGS :=
-obj/gtest-all.pdc.o: ${GTEST_DIR}/src/gtest-all.cc
+$(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}
+obj/gtest-all.pdc.o obj/gtest_main.pdc.o: override WARNINGS :=
+obj/gtest%.pdc.o: ${GTEST_DIR}/src/gtest%.cc
$(MKDIR_FIRST)
$c ${CXX} ${CPPFLAGS} ${CXXFLAGS} -c -o $@ $<
-obj/debug-debug/test.pdc.o: override CXXFLAGS += -g -O0 -gdwarf-3
-obj/run-test-debug-debug.stamp: override TESTER=gdb -return-child-result -nx -batch -x ${SRC_DIR}/tools/debug-debug.gdb --args false
-obj/run-test-debug-debug.stamp: tools/debug-debug.gdb
+DTEST_OBJS := $(filter obj/debug-debug/%.pdc.o,${PDC_OBJECTS})
+DTEST_STAMPS := $(patsubst bin/tests/%.elf,stamp/run-%.stamp,${DTEST_BINARIES})
+${DTEST_OBJS}: override CXXFLAGS += -g -O0 -gdwarf-3
+${DTEST_STAMPS}: override TESTER=${GDB} -return-child-result -nx -batch -ex 'python file_to_load = "$<"' -x ${SRC_DIR}/tools/debug-debug.gdb --args false
+${DTEST_STAMPS}: tools/debug-debug.gdb
ifeq '$(findstring clang,${CXX})' 'clang'
-obj/run-test-debug-debug.stamp:
+${DTEST_STAMPS}:
@echo "Error: sorry, but clang can't be used with gdb"
-false
endif
-test: $(patsubst bin/%,obj/run-%.stamp,${TEST_BINARIES})
-obj/run-%.stamp: bin/%
+test: test-direct
+test-direct: $(patsubst bin/tests/%.elf,stamp/run-%.stamp,${TEST_BINARIES})
+
+test: test-gtest
+test-gtest: $(patsubst bin/tests/%.elf,stamp/run-%.stamp,${GTEST_BINARIES})
+
+test: test-dtest
+test-dtest: $(patsubst bin/tests/%.elf,stamp/run-%.stamp,${DTEST_BINARIES})
+
+$(patsubst bin/tests/%.elf,stamp/run-%.stamp,${TEST_BINARIES} ${GTEST_BINARIES} ${DTEST_BINARIES}): stamp/symlink-test-lib-dir.stamp
+stamp/symlink-test-lib-dir.stamp:
+ @mkdir -p bin
+ ln -sfT ../lib bin/lib
+ touch $@
+
+stamp/run-%.stamp: bin/tests/%.elf
+ $(MKDIR_FIRST)
${TESTER} $< ${TEST_ARGS}
touch $@
+test: test-headers
+test-headers: ${CHECK_HEADERS}
+
+test: test-rank-fwd
+test-rank-fwd: ${CHECK_RANK_FWDS}
+stamp/%.rank: src/%
+ $(MKDIR_FIRST)
+ includes=$$(grep '#include.*".*/.*"' $< | sed 's/^[^"]*"//;s/"[^"]*$$//;s:/[^/]*$$::' | sort -u | fgrep -vx -e '..' -e 'conf-raw' -e '../conf'); \
+ for inc in $$includes; do if ! test -f ${<D}/fwd.hpp; then continue; fi; echo fgrep -q $${inc}/fwd.hpp ${<D}/fwd.hpp; fgrep -q $${inc}/fwd.hpp ${<D}/fwd.hpp || { echo ${<D}/fwd.hpp:''23: error: No $${inc}/fwd.hpp; exit 1; }; done
+ touch $@
+
+test: test-check-fwd
+test-check-fwd: ${CHECK_FWDS}
+stamp/%.fwdcheck: src/%/fwd.hpp
+ $(MKDIR_FIRST)
+ types=$$(grep -o '\(enum \|class \| struct \|union \)\+[A-Za-z_0-9]\+;$$' $< | sed 's/\(;\|enum \|class \|struct \|union \)//g'); \
+ for t in $$types; do echo grep -q $$t $(filter-out %/fwd.hpp,$(wildcard ${<D}/*.hpp)); grep -qw '\(enum \|class \|struct \|union \)'$$t $(filter-out %/fwd.hpp,$(wildcard ${<D}/*.hpp)) || { grep -Hnw $$t';$$' $<; exit 1; }; done
+ includes=$$(grep -o '#include.*".*/fwd\.hpp"' $< | sed 's:^[^"]*"::;s:/[^/]*"$$:/:'); \
+ for inc in $$includes; do echo fgrep -q $$inc $(filter-out %/fwd.hpp,$(wildcard ${<D}/*.hpp ${<D}/*.tcc ${<D}/*.cpp)); fgrep -q $$inc $(filter-out %/fwd.hpp,$(wildcard ${<D}/*.hpp ${<D}/*.tcc ${<D}/*.cpp)) || { grep -Hn $$inc $<; exit 1; }; done
+ touch $@
+
install := install
install_exe := ${install}
install_dir := ${install} -d
@@ -468,8 +565,9 @@ install: install-bin
install-bin:
@echo + Installing binaries
${install_dir} ${DESTDIR}${BINDIR}
- ${install_exe} -t ${DESTDIR}${BINDIR} \
- ${BINARIES}
+ for exe in $(patsubst bin/%.elf,%,${BINARIES}); do \
+ ${install_exe} -T bin/$${exe}.elf ${DESTDIR}${BINDIR}/$${exe}; \
+ done
uninstall: uninstall-bin
uninstall-bin:
@echo - Uninstalling binaries
@@ -479,12 +577,17 @@ install: install-lib
install-lib:
@echo + Installing libraries
${install_dir} ${DESTDIR}${LIBDIR}
+ifeq (${ENABLE_SHARED},yes)
${install_data} -t ${DESTDIR}${LIBDIR} \
$(patsubst %.so,%.${SO_LONG},${LIBRARIES})
for lib in $(patsubst lib/%.so,%,${LIBRARIES}); do \
- ln -sf $$lib.${SO_LONG} ${DESTDIR}${LIBDIR}/$$lib.${SO_SHORT}; \
- ln -sf $$lib.${SO_SHORT} ${DESTDIR}${LIBDIR}/$$lib.so; \
+ ln -sfT $$lib.${SO_LONG} ${DESTDIR}${LIBDIR}/$$lib.${SO_SHORT}; \
+ ln -sfT $$lib.${SO_SHORT} ${DESTDIR}${LIBDIR}/$$lib.so; \
done
+else
+ ${install_data} -t ${DESTDIR}${LIBDIR} \
+ ${LIBRARIES}
+endif
uninstall: uninstall-lib
uninstall-lib:
@echo - Uninstalling libraries
@@ -496,7 +599,7 @@ ifeq (${ENABLE_DEBUG},yes)
@echo + Installing debug files
${install_dir} ${DESTDIR}${DEBUGDIR}${BINDIR}
${install_data} -t ${DESTDIR}${DEBUGDIR}${BINDIR} \
- $(patsubst %,%-gdb.py,${BINARIES})
+ $(patsubst %.elf,%-gdb.py,${BINARIES})
${install_dir} ${DESTDIR}${DEBUGDIR}${LIBDIR}
${install_data} -t ${DESTDIR}${DEBUGDIR}${LIBDIR} \
$(patsubst %.so,%.${SO_LONG}-gdb.py,${LIBRARIES})
@@ -622,9 +725,9 @@ endif
.PHONY: dist
format: format-cpp format-hpp
-format-cpp: $(patsubst src/%,obj/%.formatted,${REAL_SOURCES})
-format-hpp: $(patsubst src/%,obj/%.formatted,${REAL_HEADERS})
-obj/%.cpp.formatted: src/%.cpp tools/indenter
+format-cpp: $(patsubst src/%,stamp/%.formatted,${REAL_SOURCES})
+format-hpp: $(patsubst src/%,stamp/%.formatted,${REAL_HEADERS})
+stamp/%.cpp.formatted: src/%.cpp tools/indenter
$(MKDIR_FIRST)
apply-filter 'indenter -cpp' $<
fgrep -q Copyright $<
@@ -633,7 +736,7 @@ obj/%.cpp.formatted: src/%.cpp tools/indenter
grep -q '^namespace tmwa$$' $<
grep -q '^} // namespace tmwa$$' $<
touch $@
-obj/%.hpp.formatted: src/%.hpp tools/indenter
+stamp/%.hpp.formatted: src/%.hpp tools/indenter
$(MKDIR_FIRST)
apply-filter 'indenter -cpp' $<
fgrep -q Copyright $<
@@ -644,7 +747,7 @@ obj/%.hpp.formatted: src/%.hpp tools/indenter
grep -q '^} // namespace tmwa$$' $<
grep -q '^#pragma once$$' $<
touch $@
-obj/%.tcc.formatted: src/%.tcc tools/indenter
+stamp/%.tcc.formatted: src/%.tcc tools/indenter
$(MKDIR_FIRST)
apply-filter 'indenter -cpp' $<
fgrep -q Copyright $<
@@ -654,6 +757,14 @@ obj/%.tcc.formatted: src/%.tcc tools/indenter
touch $@
.PHONY: format format-cpp format-hpp
-most: $(filter-out bin/${tmwa}-map,${BINARIES})
+vpath doc-gen/%.gv ${SRC_DIR}
+${SRC_DIR}/doc-gen/%.png: doc-gen/%.gv
+ dot -Tpng $< -o $@
+doc: ${DOC_PNGS}
+ test -e ${SRC_DIR}/doc-gen/.git
+ rm -f ${DOC_OBSOLETE}
+.PHONY: doc
+
+most: $(filter-out bin/${tmwa}-map.elf,${BINARIES})
magic: $(filter obj/map/magic%,${PDC_OBJECTS})
-common: $(filter-out %/lib.pdc.o %_test.pdc.o obj/login/% obj/char/% obj/map/% obj/admin/% obj/monitor/%,${PDC_OBJECTS})
+common: $(filter-out %/lib.pdc.o obj/debug-debug/% %_test.pdc.o obj/login/% obj/char/% obj/map/% obj/admin/%,${PDC_OBJECTS})
diff --git a/README b/README
deleted file mode 100644
index e4d034f..0000000
--- a/README
+++ /dev/null
@@ -1,257 +0,0 @@
-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 maintained in conjunction with The Mana World, but is not tied to
-it. However, please read the note about server-data below.
-
-
-For user instructions, see:
-http://wiki.themanaworld.org/index.php/How_to_Develop
-
-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.
-
-
-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.
-
-TMWA has a bugtracker: https://github.com/themanaworld/tmwa/issues
-But it's probably worth getting on IRC first:
-irc://chat.freenode.net/tmwa
-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.
-
-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.
-
-(TODO put this file in some sort of markup - github likes .md)
-
-
-1. Distributors.
-# random not-quite-YAML
-Important notes:
- - Go read version.make
- - TMWA requires git to build by default, use 'make dist' to get a tarball.
-platform dependencies:
- architecture:
- tested: x86 and (finally!) amd64; possibly x32
- required: little-endian (for now)
- operating system:
- known bad: Linux 2.6.26 and earlier
- maintained: Linux 3.2 and later
- likely to break: Cygwin, BSD
- filesystem:
- must support symlinks
-build dependencies:
- python:
- required: 2.7.x only, installed in $PATH as 'python'
- C library:
- recommended: glibc 2.17 or higher
- supported: glibc 2.13
- known bad: glibc 2.8 or below
- unsupported: anything that's not glibc
- C++ compiler:
- required: g++ 4.7.2 or higher
- recommended: g++ 4.8.1 or higher
- likely to work: clang++ 3.3 or higher
- C++ library:
- recommended: libstdc++ to match g++; may need patch for clang++
- may work: libc++
- attoconf:
- special: see below
-runtime dependencies:
- glibc:
- depends on what it was built against
- libstdc++:
- depends on what it was built against
-instructions:
- configuration:
- ./configure --disable-compat-symlinks
- (Takes most of the options GNU Autoconf's configure does - I won't
- repeat the output of ./configure --help here.)
- (--prefix=/usr, not --prefix usr, in order to prevent an ambiguity.
- "In the face of ambiguity, refuse the temptation to guess.")
- (Out-of-tree builds work.)
- (Note that there is no option to disable dependency tracking, as it
- is also used to generate link information. There is also no option
- to ignore unknown options - I refuse to lie.)
- build:
- make -jN
- build test:
- make test
- (Not really useful yet. Requires source of Google Test.)
- install:
- make install DESTDIR=/whatever
- (See "what is installed" below)
- install test:
- not implemented
-
-Note about attoconf:
- TMWA's ./configure script is implemented using a python package
- 'attoconf', which I wrote over a weekend after reading GNU autoconf's
- documentation and realizing that it was 1. insane, and 2. still trying
- to solve the wrong sort of problem.
-
- Currently, attoconf's API is still in the "experimental" stage, so the
- real rule is "does ./configure work?".
- When it gets to 1.0, it will start guaranteeing compatibility.
-
- Attoconf is available at https://github.com/o11c/attoconf/ and is a
- well-behaving python package.
-
- Attoconf requires Python 2.7; a port to Python 2.6 is doable with a bit
- of work, but it is not known if this would benefit anybody.
-
- If you're Arch - you broke Python for us all, you clean up your own mess.
- Patches to call a nonexistent /usr/bin/python2 will NOT be accepted.
-
-What is installed:
- Overview:
- Currently, 'make install' installs 5 binaries. Depending on how it is
- configured, it may also install 5 symlinks for the old names. As a
- distributor, though, you don't want that, and they will go away soon.
-
- In future there may be data and configuration files, and 'make install'
- will offer subtargets (make install-exec) as appropriate.
-
- The 4 main programs below are typically running on the same machine,
- though in theory they may be configured to run on different machines
- in a fast LAN. Also, the internal protocol between the programs is
- subject to change without notice, so they *must* be upgraded
- simultaneously.
-
- All of these programs currently read their config files relative to the
- current working directory; this is the only thing that makes sense
- since the files are dependent on the server-data.
-
-
- tmwa-monitor:
- 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.
-
- At some point I plan to rewrite it and ship a new conf file, unless
- everyone agrees to use systemd, in which case I maybe can use that.
-
- In the mean time, 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").
-
- This is an essential tool to maintain the server's flatfile "databases".
- It doesn't actually touch the files directly, just connects to
- tmwa-login.
-
- Even when everything is rewritten to use SQL, it will be kept, if just
- to keep a consistent interface. In fact, if we use SQLite we *can't*
- edit the databases independently. This wouldn't be a problem with
- Postgres, but people seem to think it's hard to install (that's not my
- experience on Debian though. Did they ever try themselves, or are they
- just repeating what they've heard?)
-
- tmwa-login:
- Formerly known as login-server.
-
- User-facing server to deal with account checks.
-
- Also accepts internal connections from tmwa-admin and tmwa-char,
- subject to a plaintext password (and for tmwa-admin, also an IP check).
-
- tmwa-char:
- Formerly known as char-server.
-
- User-facing server to deal with character persistence.
-
- Connects to tmwa-login; also takes internal connections from tmwa-map.
-
- Note that it is fully supported for more than one tmwa-char to connect
- to the same tmwa-login; the client will be presented with a list of
- "worlds" before leaving the login-server.
-
- tmwa-map:
- Formerly known as map-server.
-
- Connects to tmwa-char.
-
- It is technically possible for more than one tmwa-map to connect to
- a single tmwa-char, but this is poorly supported by our current config
- and moderation tools.
-
- about server data:
- Just having the binaries is not enough: you also need a complete set of
- content: config files, game scripts, savefiles, and client updates.
-
- A web server to serve the updates is also strongly recommended, as even
- 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
-
- The only recommended way of using this is by following the instructions
- in the "How to Develop" article. These instructions are only designed
- for people contributing to TMW itself, not to people trying to start
- a fork - we know all forks are doomed to be unsuccessful anyway, so
- please don't split the development effort, and you can't split the
- player community.
-
- In particular, the instructions do NOT provide information on how to
- secure a server by changing all the default passwords.
-
- There are 3 other known sets of complete server data: regional ones
- for Germany and Brasil, and Evol. Evol requires their own fork of
- the tmwa server (for some reason they don't like me to call it evola),
- and nobody seems to know of the foreign servers are keeping active.
-
- 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).
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..309a1d6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,272 @@
+#The Mana World Athena Readme
+
+![The Mana World logo](share/tmwa/TheManaWorldLogo.png)
+
+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 maintained in conjunction with The Mana World, but is not tied to
+it. However, please read the note about server-data below.
+
+
+Take a look at the [wiki](http://wiki.themanaworld.org/index.php/How_to_Develop) for user instructions.
+
+<b>Important note:</b> building from a github-generated tarball does not work!
+You must either build from a git checkout or from a 'make dist' tarball.
+
+
+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).
+
+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.
+
+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.
+
+##1. Distributors.
+###Important notes:
+
+- Go read [version.make](version.make)
+- TMWA requires git to build by default, use 'make dist' to get a tarball.
+
+###Platform dependencies:
+####Architecture:
+
+ tested: x86, amd64, x32
+ likely to work: all architectures (patches welcome if they don't)
+
+####Operating system:
+ known bad: Linux 2.6.26 and earlier
+ maintained: Linux 3.2 and later
+ likely to break: Cygwin, BSD
+####Filesystem:
+ must support symlinks
+
+###Build dependencies:
+####Python:
+ required: 2.7.x only, installed in $PATH as 'python'
+####C library:
+ recommended: glibc 2.17 or higher
+ supported: glibc 2.13
+ known bad: glibc 2.8 or below
+ unsupported: anything that's not glibc
+####C++ compiler:
+ required: g++ 4.7.2 or higher
+ recommended: g++ 4.8.1 or higher
+ not recommended: clang++ 3.3 or higher (all versions have unfixed bugs)
+####C++ library:
+ recommended: libstdc++ to match g++; may need patch for clang++
+ may work: libc++
+####attoconf:
+ special: see below
+###Runtime dependencies:
+####glibc:
+ depends on what it was built against
+####libstdc++:
+ depends on what it was built against
+###Instructions:
+####Configuration:
+ ./configure
+_Takes most of the options GNU Autoconf's configure does - I won't
+ repeat the output of `./configure --help` here._
+
+_`--prefix=/usr`, not `--prefix usr`, in order to prevent an ambiguity.
+ "In the face of ambiguity, refuse the temptation to guess."_
+
+_Out-of-tree builds work._
+
+_Note that there is no option to disable dependency tracking, as it
+ is also used to generate link information. There is also no option
+ to ignore unknown options - I refuse to lie._
+####Build:
+ make -jN
+####Build test:
+ make test
+
+_Nowhere near complete useful yet. Requires source of Google Test._
+
+ make format; git diff --exit-code
+####Install:
+ make install DESTDIR=/whatever
+
+_See [what is installed](#what-is-installed) below_
+####Install test:
+_not implemented_
+####Distribution tarballs:
+ make dist
+ make bindist
+
+###Note about attoconf:
+TMWA's `./configure` script is implemented using a python package
+'attoconf', which I wrote over a weekend after reading GNU autoconf's
+documentation and realizing that it was 1. insane, and 2. still trying
+to solve the wrong sort of problem.
+
+Currently, attoconf's API is still in the "experimental" stage, so the
+real rule is "does ./configure work?".
+When it gets to 1.0, it will start guaranteeing compatibility.
+
+Attoconf is available at [Github](https://github.com/o11c/attoconf/) and is a
+well-behaving python package.
+
+Attoconf requires Python 2.7; a port to Python 2.6 is doable with a bit
+of work, but it is not known if this would benefit anybody.
+
+If you're Arch - you broke Python for us all, you clean up your own mess.
+Patches to call a nonexistent `/usr/bin/python2` will NOT be accepted.
+
+###What is installed:
+####Overview:
+Currently, `make install` installs 5 binaries, along with a handful
+of libraries, headers, data files, config files, and debug files, each
+of which has a `make install-something` target.
+
+The 4 main programs below are typically running on the same machine,
+though in theory they may be configured to run on different machines
+in a fast LAN. Also, the internal protocol between the programs is
+subject to change without notice, so they *must* be upgraded
+simultaneously.
+
+These programs currently read most of their files relative to the
+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:
+<DEPRECATED>
+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").
+
+This is an essential tool to maintain the server's flatfile "databases".
+It doesn't actually touch the files directly, just connects to
+tmwa-login.
+
+Even when everything is rewritten to use SQL, it will be kept, if just
+to keep a consistent interface. In fact, if we use SQLite we *can't*
+edit the databases independently. This wouldn't be a problem with
+Postgres, but people seem to think it's hard to install (that's not my
+experience on Debian though. Did they ever try themselves, or are they
+just repeating what they've heard?)
+
+####tmwa-login:
+Formerly known as `login-server`.
+
+User-facing server to deal with account checks.
+
+Also accepts internal connections from `tmwa-admin` and `tmwa-char`,
+subject to a plaintext password (and for `tmwa-admin`, also an IP check).
+
+####tmwa-char:
+Formerly known as `char-server`.
+
+User-facing server to deal with character persistence.
+
+Connects to `tmwa-login`; also takes internal connections from `tmwa-map`.
+
+Note that it is fully supported for more than one `tmwa-char` to connect
+to the same `tmwa-login`; the client will be presented with a list of
+"worlds" before leaving the login server.
+
+####tmwa-map:
+Formerly known as `map-server`.
+
+Connects to `tmwa-char`.
+
+It is technically possible for more than one `tmwa-map` to connect to
+a single tmwa-char, but this is poorly supported by our current config
+and moderation tools, and there are likely undiscovered bugs.
+
+####About server data:
+Just having the binaries is not enough: you also need a complete set of
+content: config files, game scripts, savefiles, and client updates.
+
+A web server to serve the updates is also strongly recommended, as even
+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
+
+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
+for people contributing to TMW itself, not to people trying to start
+a fork - we know all forks are doomed to be unsuccessful anyway, so
+please don't split the development effort, and you can't split the
+player community.
+
+In particular, the instructions do NOT provide information on how to
+secure a server by changing all the default passwords.
+
+There are 3 other known sets of complete server data: regional ones
+for Germany and Brasil, and Evol. Evol requires their own fork of
+the tmwa server (for some reason they don't like me to call it evola),
+and nobody seems to know of the foreign servers are keeping active.
+
+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).
diff --git a/configure b/configure
index ee75e6e..dc67d49 100755
--- a/configure
+++ b/configure
@@ -38,7 +38,7 @@ from attoconf.lib.c import Cxx
from attoconf.lib.install import Install
from attoconf.lib.config_hash import ConfigHash
from attoconf.lib.templates import Templates
-from attoconf.types import enum, filepath
+from attoconf.types import enum, filepath, ShellList
yesno = enum('yes', 'no')
@@ -78,6 +78,9 @@ class Configuration(Cxx, Install, ConfigHash, Templates):
self.add_option('--enable-rpath', init='none',
type=enum('none', 'relative', 'absolute'), check=lambda build, ENABLE_RPATH: None,
help='Use rpaths to find libraries', hidden=False)
+ self.add_bool_feature('shared', init='yes',
+ check=lambda build, ENABLE_SHARED: None,
+ help='Build shared libraries instead of static (required for debug, DOES NOT WORK WITH CLANG)')
def vars(self):
super(Configuration, self).vars()
@@ -85,6 +88,9 @@ class Configuration(Cxx, Install, ConfigHash, Templates):
# 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)
+ self.add_option('GDB', init=['gdb'],
+ type=ShellList, check=lambda build, GDB: None,
+ help='debugger to run tests', hidden=False)
def add_bool_feature(self, arg, hidden=False, **kwargs):
positive = '--enable-' + arg
diff --git a/generate.make b/generate.make
index 19ff2c5..c32ca8d 100644
--- a/generate.make
+++ b/generate.make
@@ -13,21 +13,30 @@ vpath %.tcc ${SRC_DIR}
vpath tools/% ${SRC_DIR}
vpath %.py ${SRC_DIR}
-obj/generated.stamp:
+stamp/generated.stamp:
$(MKDIR_FIRST)
touch $@
-obj/generated.stamp: obj/generate-proto2.stamp
-obj/generate-proto2.stamp: tools/protocol.py
+
+stamp/generated.stamp: stamp/generate-proto2.stamp
+stamp/generate-proto2.stamp: tools/protocol.py
$(MKDIR_FIRST)
- rm -f obj/generated.stamp
+ rm -f stamp/generated.stamp
mkdir -p ${SRC_DIR}/src/proto2
cd ${SRC_DIR} && protocol.py
touch $@
-obj/generated.stamp: obj/generate-debug-debug.stamp
-obj/generate-debug-debug.stamp: tools/debug-debug-scripts ${PIES}
+
+stamp/generated.stamp: stamp/generate-debug-debug.stamp
+stamp/generate-debug-debug.stamp: tools/debug-debug-scripts ${PIES}
$(MKDIR_FIRST)
- rm -f obj/generated.stamp
+ rm -f stamp/generated.stamp
mkdir -p ${SRC_DIR}/src/debug-debug
- debug-debug-scripts $(wordlist 2,$(words $^),$^) > ${SRC_DIR}/src/debug-debug/test.cpp
+ rm -f ${SRC_DIR}/src/debug-debug/test.cpp
+ debug-debug-scripts ${SRC_DIR}/src/debug-debug/ $(wordlist 2,$(words $^),$^)
touch $@
+stamp/generated.stamp: stamp/generate-config.stamp
+stamp/generate-config.stamp: tools/config.py
+ $(MKDIR_FIRST)
+ rm -f stamp/generated.stamp
+ cd ${SRC_DIR} && config.py
+ touch $@
diff --git a/include/tmwa/shared.hpp b/include/tmwa/shared.hpp
index c624e54..95174ab 100644
--- a/include/tmwa/shared.hpp
+++ b/include/tmwa/shared.hpp
@@ -21,8 +21,10 @@
#include <cstdbool>
+// TODO also make sure any exception classes are public
+#pragma GCC visibility push(default)
namespace tmwa
{
- __attribute__((visibility("default")))
void check_paths();
} // namespace tmwa
+#pragma GCC visibility pop
diff --git a/doc/mapserver-logging b/old-doc/mapserver-logging
index 573c61f..573c61f 100644
--- a/doc/mapserver-logging
+++ b/old-doc/mapserver-logging
diff --git a/doc/old-spell-language b/old-doc/old-spell-language
index 5f70f89..5f70f89 100644
--- a/doc/old-spell-language
+++ b/old-doc/old-spell-language
diff --git a/share/tmwa/TheManaWorldLogo.png b/share/tmwa/TheManaWorldLogo.png
new file mode 100644
index 0000000..770c13a
--- /dev/null
+++ b/share/tmwa/TheManaWorldLogo.png
Binary files differ
diff --git a/src/admin/fwd.hpp b/src/admin/fwd.hpp
index 46674c8..0beaf50 100644
--- a/src/admin/fwd.hpp
+++ b/src/admin/fwd.hpp
@@ -20,8 +20,21 @@
#include "../sanity.hpp"
+#include "../strings/fwd.hpp" // rank 1
+#include "../io/fwd.hpp" // rank 4
+#include "../net/fwd.hpp" // rank 5
+#include "../mmo/fwd.hpp" // rank 6
+#include "../proto2/fwd.hpp" // rank 8
+#include "../high/fwd.hpp" // rank 9
+#include "../wire/fwd.hpp" // rank 9
+// admin/fwd.hpp is rank ∞ because it is an executable
+
namespace tmwa
{
+namespace admin
+{
+ struct AdminConf;
// meh, add more when I feel like it
+} // namespace admin
} // namespace tmwa
diff --git a/src/admin/globals.cpp b/src/admin/globals.cpp
new file mode 100644
index 0000000..e61128a
--- /dev/null
+++ b/src/admin/globals.cpp
@@ -0,0 +1,48 @@
+#include "globals.hpp"
+// globals.cpp - Evil global variables for tmwa-admin.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../strings/tstring.hpp"
+
+#include "../mmo/ids.hpp"
+
+#include "admin_conf.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+ namespace admin
+ {
+ bool eathena_interactive_session;
+ AdminConf admin_conf;
+ Session *login_session;
+ // flag to know if we waiting bytes from login-server
+ bool bytes_to_read = false;
+ // needs to be global since it's passed to the parse function
+ // really should be added to session data
+ TString parameters;
+ // parameters to display a list of accounts
+ AccountId list_first, list_last;
+ int list_type, list_count;
+ // sometimes, the exit function is called twice... so, don't log twice the message
+ bool already_exit_function = false;
+ } // namespace admin
+} // namespace tmwa
diff --git a/src/ints/cmp.cpp b/src/admin/globals.hpp
index 94ff0e3..3935499 100644
--- a/src/ints/cmp.cpp
+++ b/src/admin/globals.hpp
@@ -1,5 +1,5 @@
-#include "cmp.hpp"
-// cmp.cpp - comparison related operations
+#pragma once
+// globals.hpp - Evil global variables for tmwa-admin.
//
// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
@@ -18,9 +18,20 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "fwd.hpp"
namespace tmwa
{
+ namespace admin
+ {
+ extern bool eathena_interactive_session;
+ extern AdminConf admin_conf;
+ extern Session *login_session;
+ extern bool bytes_to_read;
+ extern TString parameters;
+ extern AccountId list_first, list_last;
+ extern int list_type, list_count;
+ extern bool already_exit_function;
+ } // namespace admin
} // namespace tmwa
diff --git a/src/admin/ladmin.cpp b/src/admin/ladmin.cpp
index 9dae089..e0d6b06 100644
--- a/src/admin/ladmin.cpp
+++ b/src/admin/ladmin.cpp
@@ -35,49 +35,48 @@
#include "../strings/vstring.hpp"
#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
#include "../io/read.hpp"
+#include "../io/span.hpp"
#include "../io/tty.hpp"
#include "../io/write.hpp"
#include "../net/ip.hpp"
-#include "../net/packets.hpp"
+#include "../net/timestamp-utils.hpp"
#include "../proto2/any-user.hpp"
#include "../proto2/login-admin.hpp"
#include "../mmo/config_parse.hpp"
-#include "../mmo/core.hpp"
#include "../mmo/human_time_diff.hpp"
-#include "../mmo/mmo.hpp"
-#include "../mmo/utils.hpp"
+#include "../high/mmo.hpp"
#include "../mmo/version.hpp"
+#include "../high/core.hpp"
+#include "../high/utils.hpp"
+
+#include "../wire/packets.hpp"
+
+#include "admin_conf.hpp"
+#include "globals.hpp"
+
#include "../poison.hpp"
namespace tmwa
{
-static
-int eathena_interactive_session;
-#define Iprintf if (eathena_interactive_session) PRINTF
-
-//-------------------------------INSTRUCTIONS------------------------------
-// Set the variables below:
-// IP of the login server.
-// Port where the login-server listens incoming packets.
-// Password of administration (same of config_athena.conf).
-// IMPORTANT:
-// Be sure that you authorize remote administration in login-server
-// (see login_athena.conf, 'admin_state' parameter)
-//-------------------------------------------------------------------------
-static
-IP4Address login_ip = IP4_LOCALHOST; // IP of login-server
-static
-int login_port = 6900; // Port of login-server
-static
-AccountPass admin_pass = stringish<AccountPass>("admin"_s); // Administration password
-static
-AString ladmin_log_filename = "log/ladmin.log"_s;
+DIAG_PUSH();
+DIAG_I(missing_noreturn);
+void SessionDeleter::operator()(SessionData *)
+{
+ assert(false && "ladmin does not have sessions"_s);
+}
+DIAG_POP();
+
+namespace admin
+{
+#define Iprintf if (tmwa::admin::eathena_interactive_session) PRINTF
+
//-------------------------------------------------------------------------
// LIST of COMMANDs that you can type at the prompt:
// To use these commands you can only type only the first letters.
@@ -220,29 +219,6 @@ AString ladmin_log_filename = "log/ladmin.log"_s;
// all other values are 'No MSG', then use state 9 please.
// 'error_message_#7': message of the code error 6 = Your are Prohibited to log in until %s (packet 0x006a)
//
-// timeadd <account_name> <modifier>
-// Adds or substracts time from the validity limit of an account.
-// Modifier is done as follows:
-// Adjustment value (-1, 1, +1, etc...)
-// Modified element:
-// a or y: year
-// m: month
-// j or d: day
-// h: hour
-// mn: minute
-// s: second
-// <example> timeadd testname +1m-2mn1s-6y
-// this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time.
-// NOTE: You can not modify a unlimited validity limit.
-// If you want modify it, you want probably create a limited validity limit.
-// So, at first, you must set the validity limit to a date/time.
-//
-// timeset <account_name> yyyy/mm/dd [hh:mm:ss]
-// Changes the validity limit of an account.
-// Default time [hh:mm:ss]: 23:59:59.
-// timeset <account_name> 0
-// Gives an unlimited validity limit (0 = unlimited).
-//
// unban/unbanish <account name>
// Unban an account.
// Like banset <account name> 0.
@@ -258,37 +234,16 @@ AString ladmin_log_filename = "log/ladmin.log"_s;
// Displays complete information of an account.
//
//-------------------------------------------------------------------------
-static
-Session *login_session;
-static
-int bytes_to_read = 0; // flag to know if we waiting bytes from login-server
-static
-TString parameters; // needs to be global since it's passed to the parse function
-// really should be added to session data
-static
-AccountId list_first, list_last;
-static
-int list_type, list_count; // parameter to display a list of accounts
-static
-int already_exit_function = 0; // sometimes, the exit function is called twice... so, don't log twice the message
-
-DIAG_PUSH();
-DIAG_I(missing_noreturn);
-void SessionDeleter::operator()(SessionData *)
-{
- assert(false && "ladmin does not have sessions"_s);
-}
-DIAG_POP();
//------------------------------
// Writing function of logs file
//------------------------------
#define LADMIN_LOG(fmt, ...) \
- ladmin_log(STRPRINTF(fmt, ## __VA_ARGS__))
+ tmwa::admin::ladmin_log(STRPRINTF(fmt, ## __VA_ARGS__))
static
void ladmin_log(XString line)
{
- io::AppendFile logfp(ladmin_log_filename);
+ io::AppendFile logfp(admin_conf.ladmin_log_filename);
if (!logfp.is_open())
return;
log_with_timestamp(logfp, line);
@@ -300,9 +255,9 @@ void delete_fromlogin(Session *)
login_session = nullptr;
{
PRINTF("Impossible to have a connection with the login-server [%s:%d] !\n"_fmt,
- login_ip, login_port);
+ admin_conf.login_ip, admin_conf.login_port);
LADMIN_LOG("Impossible to have a connection with the login-server [%s:%d] !\n"_fmt,
- login_ip, login_port);
+ admin_conf.login_ip, admin_conf.login_port);
exit(0);
}
}
@@ -487,13 +442,6 @@ void display_help(ZString param)
PRINTF(" Research by name is not possible with this command.\n"_fmt);
PRINTF(" <example> list 10 9999999\n"_fmt);
}
- else if (command == "itemfrob"_s)
- {
- PRINTF("itemfrob <source-id> <dest-id>\n"_fmt);
- PRINTF(" Translates item IDs for all accounts.\n"_fmt);
- PRINTF(" Any items matching the source item ID will be mapped to the dest-id.\n"_fmt);
- PRINTF(" <example> itemfrob 500 700\n"_fmt);
- }
else if (command == "listban"_s)
{
PRINTF("listban [start_id [end_id]]\n"_fmt);
@@ -565,34 +513,6 @@ void display_help(ZString param)
PRINTF(" 'error_message_#7': message of the code error 6\n"_fmt);
PRINTF(" = Your are Prohibited to log in until... (packet 0x006a)\n"_fmt);
}
- else if (command == "timeadd"_s)
- {
- PRINTF("timeadd <account_name> <modifier>\n"_fmt);
- PRINTF(" Adds or substracts time from the validity limit of an account.\n"_fmt);
- PRINTF(" Modifier is done as follows:\n"_fmt);
- PRINTF(" Adjustment value (-1, 1, +1, etc...)\n"_fmt);
- PRINTF(" Modified element:\n"_fmt);
- PRINTF(" a or y: year\n"_fmt);
- PRINTF(" m: month\n"_fmt);
- PRINTF(" j or d: day\n"_fmt);
- PRINTF(" h: hour\n"_fmt);
- PRINTF(" mn: minute\n"_fmt);
- PRINTF(" s: second\n"_fmt);
- PRINTF(" <example> timeadd testname +1m-2mn1s-6y\n"_fmt);
- PRINTF(" this example adds 1 month and 1 second, and substracts 2 minutes\n"_fmt);
- PRINTF(" and 6 years at the same time.\n"_fmt);
- PRINTF("NOTE: You can not modify a unlimited validity limit.\n"_fmt);
- PRINTF(" If you want modify it, you want probably create a limited validity limit.\n"_fmt);
- PRINTF(" So, at first, you must set the validity limit to a date/time.\n"_fmt);
- }
- else if (command == "timeadd"_s)
- {
- PRINTF("timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n"_fmt);
- PRINTF(" Changes the validity limit of an account.\n"_fmt);
- PRINTF(" Default time [hh:mm:ss]: 23:59:59.\n"_fmt);
- PRINTF("timeset <account_name> 0\n"_fmt);
- PRINTF(" Gives an unlimited validity limit (0 = unlimited).\n"_fmt);
- }
else if (command == "unban"_s)
{
PRINTF("unban/unbanish <account name>\n"_fmt);
@@ -644,7 +564,6 @@ void display_help(ZString param)
PRINTF(" gm <account_name> [GM_level] -- Modify the GM level of an account\n"_fmt);
PRINTF(" id <account name> -- Give the id of an account\n"_fmt);
PRINTF(" info <account_id> -- Display all information of an account\n"_fmt);
- PRINTF(" itemfrob <source-id> <dest-id> -- Map all items from one item ID to another\n"_fmt);
PRINTF(" kami <message> -- Sends a broadcast message (in yellow)\n"_fmt);
PRINTF(" kamib <message> -- Sends a broadcast message (in blue)\n"_fmt);
PRINTF(" list [First_id [Last_id]] -- Display a list of accounts\n"_fmt);
@@ -661,10 +580,6 @@ void display_help(ZString param)
PRINTF(" search <expression> -- Seek accounts\n"_fmt);
PRINTF(" sex <nomcompte> <sexe> -- Modify the sex of an account\n"_fmt);
PRINTF(" state <account_name> <new_state> <error_message_#7> -- Change the state\n"_fmt);
- PRINTF(" timeadd <account_name> <modifier> -- Add or substract time from the\n"_fmt);
- PRINTF(" example: ta apple +1m-2mn1s-2y validity limit of an account\n"_fmt);
- PRINTF(" timeset <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit\n"_fmt);
- PRINTF(" timeset <account_name> 0 -- Give a unlimited validity limit\n"_fmt);
PRINTF(" unban <account name> -- Remove the banishment of an account\n"_fmt);
PRINTF(" unblock <account name> -- Set state 0 (Account ok) to an account\n"_fmt);
PRINTF(" version -- Gives the version of the login-server\n"_fmt);
@@ -1260,29 +1175,6 @@ void listaccount(ZString param, int type)
list_count = 0;
}
-//--------------------------------------------------------
-// Sub-function: Frobnicate items
-//--------------------------------------------------------
-static
-int itemfrob(ZString param)
-{
- ItemNameId source_id, dest_id;
-
- if (!extract(param, record<' '>(&source_id, &dest_id)))
- {
- PRINTF("You must provide the source and destination item IDs.\n"_fmt);
- return 1;
- }
-
- Packet_Fixed<0x7924> fixed_24;
- fixed_24.source_item_id = source_id;
- fixed_24.dest_item_id = dest_id;
- send_fpacket<0x7924, 10>(login_session, fixed_24);
- bytes_to_read = 1; // all logging is done to the three main servers
-
- return 0;
-}
-
//--------------------------------------------
// Sub-function: Asking to modify a memo field
//--------------------------------------------
@@ -1552,179 +1444,6 @@ void blockaccount(ZString param)
changestatesub(name, 5, "-"_s); // state 5, no error message
}
-//---------------------------------------------------------------------
-// Sub-function: Add/substract time to the validity limit of an account
-//---------------------------------------------------------------------
-static
-void timeaddaccount(ZString param)
-{
- AccountName name;
- HumanTimeDiff modif {};
-
- if (!qsplit(param, &name, &modif))
- {
- PRINTF("Please input an account name and a modifier.\n"_fmt);
- PRINTF(" <example>: timeadd testname +1m-2mn1s-6y\n"_fmt);
- PRINTF(" this example adds 1 month and 1 second, and substracts 2 minutes\n"_fmt);
- PRINTF(" and 6 years at the same time.\n"_fmt);
- LADMIN_LOG("Incomplete parameters to modify a limit time ('timeadd' command).\n"_fmt);
- return;
- }
- if (name.is_print())
- {
- return;
- }
-
- if (!modif)
- {
- PRINTF("Please give an adjustment with this command:\n"_fmt);
- PRINTF(" Adjustment value (-1, 1, +1, etc...)\n"_fmt);
- PRINTF(" Modified element:\n"_fmt);
- PRINTF(" a or y: year\n"_fmt);
- PRINTF(" m: month\n"_fmt);
- PRINTF(" j or d: day\n"_fmt);
- PRINTF(" h: hour\n"_fmt);
- PRINTF(" mn: minute\n"_fmt);
- PRINTF(" s: second\n"_fmt);
- PRINTF(" <example> timeadd testname +1m-2mn1s-6y\n"_fmt);
- PRINTF(" this example adds 1 month and 1 second, and substracts 2 minutes\n"_fmt);
- PRINTF(" and 6 years at the same time.\n"_fmt);
- LADMIN_LOG("No adjustment isn't an adjustment ('timeadd' command).\n"_fmt);
- return;
- }
-
- LADMIN_LOG("Request to login-server to modify a time limit.\n"_fmt);
-
- Packet_Fixed<0x7950> fixed_50;
- fixed_50.account_name = name;
- fixed_50.valid_add = modif;
- send_fpacket<0x7950, 38>(login_session, fixed_50);
- bytes_to_read = 1;
-}
-
-//-------------------------------------------------
-// Sub-function: Set a validity limit of an account
-//-------------------------------------------------
-static
-void timesetaccount(ZString param)
-{
- AccountName name;
- XString date;
- XString time_;
- int year, month, day, hour, minute, second;
-
- year = month = day = hour = minute = second = 0;
-
- // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited)
- TimeT connect_until_time = TimeT();
- struct tm tmtime = connect_until_time; // initialize
-
- if (!qsplit(param, &name, &date, &time_)
- && !qsplit(param, &name, &date))
- {
- PRINTF("Please input an account name, a date and a hour.\n"_fmt);
- PRINTF("<example>: timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n"_fmt);
- PRINTF(" timeset <account_name> 0 (0 = unlimited)\n"_fmt);
- PRINTF(" Default time [hh:mm:ss]: 23:59:59.\n"_fmt);
- LADMIN_LOG("Incomplete parameters to set a limit time ('timeset' command).\n"_fmt);
- return;
- }
- if (!name.is_print())
- return;
-
- if (!time_)
- time_ = "23:59:59"_s;
-
- if (date != "0"_s
- && ((!extract(date, record<'/'>(&year, &month, &day))
- && !extract(date, record<'-'>(&year, &month, &day))
- && !extract(date, record<'.'>(&year, &month, &day)))
- || !extract(time_, record<':'>(&hour, &minute, &second))))
- {
- PRINTF("Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"_fmt);
- LADMIN_LOG("Invalid format for the date/time ('timeset' command).\n"_fmt);
- return;
- }
-
- if (date == "0"_s)
- {
- connect_until_time = TimeT();
- }
- else
- {
- if (year < 70)
- {
- year = year + 100;
- }
- if (year >= 1900)
- {
- year = year - 1900;
- }
- if (month < 1 || month > 12)
- {
- PRINTF("Please give a correct value for the month (from 1 to 12).\n"_fmt);
- LADMIN_LOG("Invalid month for the date ('timeset' command).\n"_fmt);
- return;
- }
- month = month - 1;
- if (day < 1 || day > 31)
- {
- PRINTF("Please give a correct value for the day (from 1 to 31).\n"_fmt);
- LADMIN_LOG("Invalid day for the date ('timeset' command).\n"_fmt);
- return;
- }
- if (((month == 3 || month == 5 || month == 8 || month == 10)
- && day > 30) ||(month == 1 && day > 29))
- {
- PRINTF("Please give a correct value for a day of this month (%d).\n"_fmt,
- month);
- LADMIN_LOG("Invalid day for this month ('timeset' command).\n"_fmt);
- return;
- }
- if (hour < 0 || hour > 23)
- {
- PRINTF("Please give a correct value for the hour (from 0 to 23).\n"_fmt);
- LADMIN_LOG("Invalid hour for the time ('timeset' command).\n"_fmt);
- return;
- }
- if (minute < 0 || minute > 59)
- {
- PRINTF("Please give a correct value for the minutes (from 0 to 59).\n"_fmt);
- LADMIN_LOG("Invalid minute for the time ('timeset' command).\n"_fmt);
- return;
- }
- if (second < 0 || second > 59)
- {
- PRINTF("Please give a correct value for the seconds (from 0 to 59).\n"_fmt);
- LADMIN_LOG("Invalid second for the time ('timeset' command).\n"_fmt);
- return;
- }
- tmtime.tm_year = year;
- tmtime.tm_mon = month;
- tmtime.tm_mday = day;
- tmtime.tm_hour = hour;
- tmtime.tm_min = minute;
- tmtime.tm_sec = second;
- tmtime.tm_isdst = -1; // -1: no winter/summer time modification
- connect_until_time = tmtime;
- if (connect_until_time.error())
- {
- PRINTF("Invalid date.\n"_fmt);
- PRINTF("Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"_fmt);
- LADMIN_LOG("Invalid date. ('timeset' command).\n"_fmt);
- return;
- }
- }
-
- LADMIN_LOG("Request to login-server to set a time limit.\n"_fmt);
-
- Packet_Fixed<0x7948> fixed_48;
- fixed_48.account_name = name;
- fixed_48.valid_until = connect_until_time;
- send_fpacket<0x7948, 30>(login_session, fixed_48);
- bytes_to_read = 1;
-}
-
//------------------------------------------------------------------------------
// Sub-function: Asking to displaying information about an account (by its name)
//------------------------------------------------------------------------------
@@ -1874,8 +1593,6 @@ void prompt(void)
infoaccount(wrap<AccountId>(static_cast<uint32_t>(atoi(parameters.c_str()))));
else if (command == "kami"_s)
sendbroadcast(parameters); // flag for normal
- else if (command == "itemfrob"_s)
- itemfrob(parameters); // 0: to list all
else if (command == "list"_s)
listaccount(parameters, 0); // 0: to list all
else if (command == "listban"_s)
@@ -1898,10 +1615,6 @@ void prompt(void)
changesex(parameters);
else if (command == "state"_s)
changestate(parameters);
- else if (command == "timeadd"_s)
- timeaddaccount(parameters);
- else if (command == "timeset"_s)
- timesetaccount(parameters);
else if (command == "unban"_s)
unbanaccount(parameters);
else if (command == "unblock"_s)
@@ -1974,7 +1687,7 @@ void parse_fromlogin(Session *s)
break;
Iprintf(" Login-Server [%s:%d]\n"_fmt,
- login_ip, login_port);
+ admin_conf.login_ip, admin_conf.login_port);
Version version = fixed.version;
Iprintf(" tmwA version %hhu.%hhu.%hhu (dev? %hhu) (flags %hhx) (which %hhx) (vend %hu)\n"_fmt,
version.major, version.minor, version.patch,
@@ -1986,17 +1699,6 @@ void parse_fromlogin(Session *s)
break;
}
- case 0x7925: // Itemfrob-OK
- {
- Packet_Fixed<0x7925> fixed;
- rv = recv_fpacket<0x7925, 2>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- bytes_to_read = 0;
- break;
- }
-
case 0x7921: // Displaying of the list of accounts
{
std::vector<Packet_Repeat<0x7921>> repeat;
@@ -2487,47 +2189,6 @@ void parse_fromlogin(Session *s)
break;
}
- case 0x7949: // answer of an account validity limit set
- {
- Packet_Fixed<0x7949> fixed;
- rv = recv_fpacket<0x7949, 34>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- AccountId account_id = fixed.account_id;
- AccountName name = fixed.account_name;
- if (!account_id)
- {
- PRINTF("Account [%s] validity limit changing failed. Account doesn't exist.\n"_fmt,
- name);
- LADMIN_LOG("Account [%s] validity limit changing failed. Account doesn't exist.\n"_fmt,
- name);
- }
- else
- {
- TimeT timestamp = fixed.valid_until;
- if (!timestamp)
- {
- PRINTF("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited].\n"_fmt,
- name, account_id);
- LADMIN_LOG("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited].\n"_fmt,
- name, account_id);
- }
- else
- {
- timestamp_seconds_buffer tmpstr;
- stamp_time(tmpstr, &timestamp);
- PRINTF("Validity Limit of the account [%s][id: %d] successfully changed to be until %s.\n"_fmt,
- name, account_id, tmpstr);
- LADMIN_LOG("Validity Limit of the account [%s][id: %d] successfully changed to be until %s.\n"_fmt,
- name, account_id,
- tmpstr);
- }
- }
- bytes_to_read = 0;
- break;
- }
-
case 0x794b: // answer of an account ban set
{
Packet_Fixed<0x794b> fixed;
@@ -2632,50 +2293,6 @@ void parse_fromlogin(Session *s)
break;
}
- case 0x7951: // answer of an account validity limit changing
- {
- Packet_Fixed<0x7951> fixed;
- rv = recv_fpacket<0x7951, 34>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- AccountId account_id = fixed.account_id;
- AccountName name = fixed.account_name;
- if (!account_id)
- {
- PRINTF("Account [%s] validity limit changing failed. Account doesn't exist.\n"_fmt,
- name);
- LADMIN_LOG("Account [%s] validity limit changing failed. Account doesn't exist.\n"_fmt,
- name);
- }
- else
- {
- TimeT timestamp = fixed.valid_until;
- if (!timestamp)
- {
- PRINTF("Validity limit of the account [%s][id: %d] unchanged.\n"_fmt,
- name, account_id);
- PRINTF("The account have an unlimited validity limit or\n"_fmt);
- PRINTF("the changing is impossible with the proposed adjustments.\n"_fmt);
- LADMIN_LOG("Validity limit of the account [%s][id: %d] unchanged. The account have an unlimited validity limit or the changing is impossible with the proposed adjustments.\n"_fmt,
- name, account_id);
- }
- else
- {
- timestamp_seconds_buffer tmpstr;
- stamp_time(tmpstr, &timestamp);
- PRINTF("Validity limit of the account [%s][id: %d] successfully changed to be until %s.\n"_fmt,
- name, account_id,
- tmpstr);
- LADMIN_LOG("Validity limit of the account [%s][id: %d] successfully changed to be until %s.\n"_fmt,
- name, account_id,
- tmpstr);
- }
- }
- bytes_to_read = 0;
- break;
- }
-
case 0x7953: // answer of a request about informations of an account (by account name/id)
{
Packet_Head<0x7953> head;
@@ -2696,7 +2313,6 @@ void parse_fromlogin(Session *s)
timestamp_milliseconds_buffer lastlogin = head.last_login_string;
VString<15> last_ip_ = head.ip_string;
AccountEmail email = head.email;
- TimeT connect_until_time = head.connect_until;
TimeT ban_until_time = head.ban_until;
AString& memo = repeat;
if (!account_id)
@@ -2790,17 +2406,9 @@ void parse_fromlogin(Session *s)
connections);
PRINTF(" Last connection at: %s (ip: %s)\n"_fmt,
lastlogin, last_ip_);
- if (!connect_until_time)
{
PRINTF(" Validity limit: unlimited.\n"_fmt);
}
- else
- {
- timestamp_seconds_buffer tmpstr;
- stamp_time(tmpstr, &connect_until_time);
- PRINTF(" Validity limit: until %s.\n"_fmt,
- tmpstr);
- }
PRINTF(" Memo: '%s'\n"_fmt, memo);
}
}
@@ -2836,12 +2444,12 @@ void parse_fromlogin(Session *s)
// Function to connect to login-server
//------------------------------------
static
-int Connect_login_server(void)
+int connect_login_server(void)
{
Iprintf("Attempt to connect to login-server...\n"_fmt);
LADMIN_LOG("Attempt to connect to login-server...\n"_fmt);
- login_session = make_connection(login_ip, login_port, SessionParsers{.func_parse= parse_fromlogin, .func_delete= delete_fromlogin});
+ login_session = make_connection(admin_conf.login_ip, admin_conf.login_port, SessionParsers{.func_parse= parse_fromlogin, .func_delete= delete_fromlogin});
if (!login_session)
return 0;
@@ -2849,7 +2457,7 @@ int Connect_login_server(void)
{
Packet_Fixed<0x7918> fixed_18;
fixed_18.encryption_zero = 0;
- fixed_18.account_pass = admin_pass;
+ fixed_18.account_pass = admin_conf.admin_pass;
send_fpacket<0x7918, 28>(login_session, fixed_18);
bytes_to_read = 1;
@@ -2861,59 +2469,36 @@ int Connect_login_server(void)
}
static
-bool admin_confs(XString w1, ZString w2)
+bool admin_config(io::Spanned<XString> key, io::Spanned<ZString> value)
{
+ return parse_admin_conf(admin_conf, key, value);
+}
+
+static
+bool admin_confs(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ if (key.data == "admin_conf"_s)
{
- if (w1 == "login_ip"_s)
- {
- struct hostent *h = gethostbyname(w2.c_str());
- if (h != nullptr)
- {
- Iprintf("Login server IP address: %s -> %s\n"_fmt,
- w2, login_ip);
- login_ip = IP4Address({
- static_cast<uint8_t>(h->h_addr[0]),
- static_cast<uint8_t>(h->h_addr[1]),
- static_cast<uint8_t>(h->h_addr[2]),
- static_cast<uint8_t>(h->h_addr[3]),
- });
- }
- }
- else if (w1 == "login_port"_s)
- {
- login_port = atoi(w2.c_str());
- }
- else if (w1 == "admin_pass"_s)
- {
- admin_pass = stringish<AccountPass>(w2);
- }
- else if (w1 == "ladmin_log_filename"_s)
- {
- ladmin_log_filename = w2;
- }
- else
- {
- PRINTF("WARNING: unknown ladmin config key: %s\n"_fmt, AString(w1));
- return false;
- }
+ return load_config_file(value.data, admin_config);
}
- return true;
+ key.span.error("Unknown meta-key for admin nonserver"_s);
+ return false;
}
+} // namespace admin
//--------------------------------------
// Function called at exit of the server
//--------------------------------------
void term_func(void)
{
-
- if (already_exit_function == 0)
+ if (admin::already_exit_function == 0)
{
- delete_session(login_session);
+ delete_session(admin::login_session);
Iprintf(SGR_RESET "----End of Ladmin (normal end with closing of all files).\n"_fmt);
LADMIN_LOG("----End of Ladmin (normal end with closing of all files).\n"_fmt);
- already_exit_function = 1;
+ admin::already_exit_function = 1;
}
}
@@ -2949,14 +2534,14 @@ int do_init(Slice<ZString> argv)
else
{
loaded_config_yet = true;
- runflag &= load_config_file(argvi, admin_confs);
+ runflag &= load_config_file(argvi, admin::admin_confs);
}
}
if (!loaded_config_yet)
- runflag &= load_config_file("conf/tmwa-admin.conf"_s, admin_confs);
+ runflag &= load_config_file("conf/tmwa-admin.conf"_s, admin::admin_confs);
- eathena_interactive_session = isatty(0);
+ admin::eathena_interactive_session = isatty(0);
LADMIN_LOG(""_fmt);
LADMIN_LOG("Configuration file readed.\n"_fmt);
@@ -2973,8 +2558,9 @@ int do_init(Slice<ZString> argv)
LADMIN_LOG("Ladmin is ready.\n"_fmt);
Iprintf("Ladmin is " SGR_BOLD SGR_GREEN "ready" SGR_RESET ".\n\n"_fmt);
- Connect_login_server();
+ admin::connect_login_server();
return 0;
}
+// namespace admin ends before term_func and do_init
} // namespace tmwa
diff --git a/src/admin/main.cpp b/src/admin/main.cpp
index 1849d80..babf195 100644
--- a/src/admin/main.cpp
+++ b/src/admin/main.cpp
@@ -17,7 +17,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../mmo/core.hpp"
+#include "../high/core.hpp"
#include "ladmin.hpp"
diff --git a/src/ast/fwd.hpp b/src/ast/fwd.hpp
new file mode 100644
index 0000000..24bf545
--- /dev/null
+++ b/src/ast/fwd.hpp
@@ -0,0 +1,45 @@
+#pragma once
+// ast/fwd.hpp - list of type names for new independent tmwa ast
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../sanity.hpp"
+
+#include "../compat/fwd.hpp" // rank 2
+#include "../io/fwd.hpp" // rank 4
+#include "../net/fwd.hpp" // rank 5
+#include "../sexpr/fwd.hpp" // rank 5
+#include "../mmo/fwd.hpp" // rank 6
+#include "../high/fwd.hpp" // rank 9
+// ast/fwd.hpp is rank 10
+
+namespace tmwa
+{
+namespace ast
+{
+namespace npc
+{
+class Warp;
+} // namespace npc
+namespace script
+{
+class ScriptBody;
+} // namespace script
+} // namespace ast
+// meh, add more when I feel like it
+} // namespace tmwa
diff --git a/src/ast/item.cpp b/src/ast/item.cpp
new file mode 100644
index 0000000..bd4f295
--- /dev/null
+++ b/src/ast/item.cpp
@@ -0,0 +1,155 @@
+#include "item.hpp"
+// ast/item.cpp - Structure of itemdb
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../io/extract.hpp"
+#include "../io/line.hpp"
+
+#include "../mmo/extract_enums.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace item
+{
+ using io::respan;
+
+ static
+ void skip_comma_space(io::LineCharReader& lr)
+ {
+ io::LineChar c;
+ if (lr.get(c) && c.ch() == ',')
+ {
+ lr.adv();
+ while (lr.get(c) && c.ch() == ' ')
+ {
+ lr.adv();
+ }
+ }
+ }
+ static
+ Option<Spanned<RString>> lex_nonscript(io::LineCharReader& lr, bool first)
+ {
+ io::LineChar c;
+ if (first)
+ {
+ while (lr.get(c) && c.ch() == '\n')
+ {
+ lr.adv();
+ }
+ }
+ if (!lr.get(c))
+ {
+ return None;
+ }
+ io::LineSpan span;
+ MString accum;
+ accum += c.ch();
+ span.begin = c;
+ span.end = c;
+ lr.adv();
+ if (c.ch() != '/')
+ first = false;
+
+ if (first && lr.get(c) && c.ch() == '/')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ while (lr.get(c) && c.ch() != '\n')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ }
+ return Some(respan(span, RString(accum)));
+ }
+
+ while (lr.get(c) && c.ch() != ',' && c.ch() != '\n')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ }
+ skip_comma_space(lr);
+ return Some(respan(span, RString(accum)));
+ }
+
+ static
+ Result<ast::script::ScriptBody> lex_script(io::LineCharReader& lr)
+ {
+ ast::script::ScriptOptions opt;
+ opt.implicit_start = true;
+ opt.implicit_end = true;
+ opt.one_line = true;
+ opt.no_event = true;
+ auto rv = ast::script::parse_script_body(lr, opt);
+ if (rv.get_success().is_some())
+ {
+ skip_comma_space(lr);
+ }
+ return rv;
+ }
+
+#define SPAN_EXTRACT(bitexpr, var) ({ auto bit = bitexpr; if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract "_s #var)); var.span = bit.span; })
+
+#define EOL_ERROR(lr) ({ io::LineChar c; lr.get(c) ? Err(c.error_str("unexpected EOL"_s)) : Err("unexpected EOF before unexpected EOL"_s); })
+ Option<Result<ItemOrComment>> parse_item(io::LineCharReader& lr)
+ {
+ Spanned<RString> first = TRY_UNWRAP(lex_nonscript(lr, true), return None);
+ if (first.data.startswith("//"_s))
+ {
+ Comment comment;
+ comment.comment = first.data;
+ ItemOrComment rv = std::move(comment);
+ rv.span = first.span;
+ return Some(Ok(std::move(rv)));
+ }
+ Item item;
+ SPAN_EXTRACT(first, item.id);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.name);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.jname);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.type);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.buy_price);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.sell_price);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.weight);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.atk);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.def);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.range);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.magic_bonus);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.slot_unused);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.gender);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.loc);
+ 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);
+ item.use_script = TRY(lex_script(lr));
+ item.equip_script = TRY(lex_script(lr));
+ ItemOrComment rv = std::move(item);
+ rv.span.begin = item.id.span.begin;
+ rv.span.end = item.equip_script.span.end;
+ return Some(Ok(std::move(rv)));
+ }
+} // namespace item
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/item.hpp b/src/ast/item.hpp
new file mode 100644
index 0000000..a8fe908
--- /dev/null
+++ b/src/ast/item.hpp
@@ -0,0 +1,82 @@
+#pragma once
+// ast/item.hpp - Structure of tmwa itemdb
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../compat/result.hpp"
+
+#include "../io/span.hpp"
+
+#include "../sexpr/variant.hpp"
+
+#include "../mmo/clif.t.hpp"
+#include "../mmo/ids.hpp"
+#include "../mmo/strs.hpp"
+
+#include "script.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace item
+{
+ using io::Spanned;
+
+ struct Comment
+ {
+ RString comment;
+ };
+ struct Item
+ {
+ Spanned<ItemNameId> id;
+ Spanned<ItemName> name;
+ Spanned<ItemName> jname;
+ Spanned<ItemType> type;
+ Spanned<int> buy_price;
+ Spanned<int> sell_price;
+ Spanned<int> weight;
+ Spanned<int> atk;
+ Spanned<int> def;
+ Spanned<int> range;
+ Spanned<int> magic_bonus;
+ Spanned<RString> slot_unused;
+ Spanned<SEX> gender;
+ Spanned<EPOS> loc;
+ Spanned<int> wlv;
+ Spanned<int> elv;
+ Spanned<ItemLook> view;
+ ast::script::ScriptBody use_script;
+ ast::script::ScriptBody equip_script;
+ };
+
+ using ItemOrCommentBase = Variant<Comment, Item>;
+ struct ItemOrComment : ItemOrCommentBase
+ {
+ ItemOrComment(Comment o) : ItemOrCommentBase(std::move(o)) {}
+ ItemOrComment(Item o) : ItemOrCommentBase(std::move(o)) {}
+ io::LineSpan span;
+ };
+
+ Option<Result<ItemOrComment>> parse_item(io::LineCharReader& lr);
+} // namespace item
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/item_test.cpp b/src/ast/item_test.cpp
new file mode 100644
index 0000000..a77662e
--- /dev/null
+++ b/src/ast/item_test.cpp
@@ -0,0 +1,150 @@
+#include "item.hpp"
+// ast/item_test.cpp - Testsuite for itemdb parser.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <gtest/gtest.h>
+
+#include "../io/line.hpp"
+
+#include "../tests/fdhack.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace item
+{
+#define EXPECT_SPAN(span, bl,bc, el,ec) \
+ ({ \
+ EXPECT_EQ((span).begin.line, bl); \
+ EXPECT_EQ((span).begin.column, bc); \
+ EXPECT_EQ((span).end.line, el); \
+ EXPECT_EQ((span).end.column, ec); \
+ })
+
+ TEST(itemast, eof)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ ""_s,
+ "\n"_s,
+ "\n\n"_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = parse_item(lr);
+ EXPECT_TRUE(res.is_none());
+ }
+ }
+ TEST(itemast, comment)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ //23456789
+ "// hello"_s,
+ "// hello\n "_s,
+ "// hello\nabc"_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ 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,8);
+ auto p = top.get_if<Comment>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_EQ(p->comment, "// hello"_s);
+ }
+ }
+ }
+ TEST(itemast, item)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3 4 5
+ //2345678901234567890123456789012345678901234567890123456789
+ "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}"_s,
+ "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}\n"_s,
+ "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}\nabc"_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ 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);
+ auto p = top.get_if<Item>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->id.span, 1,1, 1,1);
+ EXPECT_EQ(p->id.data, wrap<ItemNameId>(1));
+ EXPECT_SPAN(p->name.span, 1,3, 1,6);
+ EXPECT_EQ(p->name.data, stringish<ItemName>("abc "_s));
+ EXPECT_SPAN(p->jname.span, 1,10, 1,12);
+ EXPECT_EQ(p->jname.data, stringish<ItemName>("def"_s));
+ EXPECT_SPAN(p->type.span, 1,14, 1,14);
+ EXPECT_EQ(p->type.data, ItemType::JUNK);
+ EXPECT_SPAN(p->buy_price.span, 1,16, 1,16);
+ EXPECT_EQ(p->buy_price.data, 4);
+ EXPECT_SPAN(p->sell_price.span, 1,18, 1,18);
+ EXPECT_EQ(p->sell_price.data, 5);
+ EXPECT_SPAN(p->weight.span, 1,20, 1,20);
+ EXPECT_EQ(p->weight.data, 6);
+ EXPECT_SPAN(p->atk.span, 1,22, 1,22);
+ EXPECT_EQ(p->atk.data, 7);
+ EXPECT_SPAN(p->def.span, 1,24, 1,24);
+ EXPECT_EQ(p->def.data, 8);
+ EXPECT_SPAN(p->range.span, 1,26, 1,26);
+ EXPECT_EQ(p->range.data, 9);
+ EXPECT_SPAN(p->magic_bonus.span, 1,28, 1,29);
+ EXPECT_EQ(p->magic_bonus.data, 10);
+ EXPECT_SPAN(p->slot_unused.span, 1,31, 1,32);
+ EXPECT_EQ(p->slot_unused.data, "xx"_s);
+ EXPECT_SPAN(p->gender.span, 1,34, 1,34);
+ EXPECT_EQ(p->gender.data, SEX::NEUTRAL);
+ EXPECT_SPAN(p->loc.span, 1,36, 1,37);
+ EXPECT_EQ(p->loc.data, EPOS::MISC1);
+ EXPECT_SPAN(p->wlv.span, 1,39, 1,40);
+ EXPECT_EQ(p->wlv.data, 12);
+ 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->use_script.braced_body, "{end;}"_s);
+ EXPECT_SPAN(p->equip_script.span, 1,57, 1,58);
+ EXPECT_EQ(p->equip_script.braced_body, "{}"_s);
+ }
+ }
+ }
+} // namespace item
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/npc.cpp b/src/ast/npc.cpp
new file mode 100644
index 0000000..8d4a43e
--- /dev/null
+++ b/src/ast/npc.cpp
@@ -0,0 +1,620 @@
+#include "npc.hpp"
+// ast/npc.cpp - Structure of non-player characters (including mobs).
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
+#include "../io/line.hpp"
+
+#include "../mmo/extract_enums.hpp"
+
+#include "../high/extract_mmo.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace npc
+{
+ using io::respan;
+
+#define TRY_EXTRACT(bit, var) ({ if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract "_s #var)); var.span = bit.span; })
+
+ static
+ Result<Warp> parse_warp(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 3)
+ {
+ return Err(bits[0].span.error_str("in |component 1| expect 3 ,component,s"_s));
+ }
+ assert(bits[1].data.size() == 1);
+ assert(bits[1].data[0].data == "warp"_s);
+ if (bits[2].data.size() != 1)
+ {
+ return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s));
+ }
+ if (bits[3].data.size() != 5)
+ {
+ return Err(bits[3].span.error_str("in |component 4| expect 5 ,component,s"_s));
+ }
+
+ Warp warp;
+ TRY_EXTRACT(bits[0].data[0], warp.m);
+ TRY_EXTRACT(bits[0].data[1], warp.x);
+ TRY_EXTRACT(bits[0].data[2], warp.y);
+ warp.key_span = bits[1].data[0].span;
+ TRY_EXTRACT(bits[2].data[0], warp.name);
+ if (bits[3].data[0].data == "-1"_s)
+ bits[3].data[0].data = "4294967295"_s;
+ TRY_EXTRACT(bits[3].data[0], warp.xs);
+ warp.xs.data += 2;
+ if (bits[3].data[1].data == "-1"_s)
+ bits[3].data[1].data = "4294967295"_s;
+ TRY_EXTRACT(bits[3].data[1], warp.ys);
+ warp.ys.data += 2;
+ TRY_EXTRACT(bits[3].data[2], warp.to_m);
+ TRY_EXTRACT(bits[3].data[3], warp.to_x);
+ TRY_EXTRACT(bits[3].data[4], warp.to_y);
+ return Ok(std::move(warp));
+ }
+ static
+ Result<Shop> parse_shop(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 4)
+ {
+ return Err(bits[0].span.error_str("in |component 1| expect 4 ,component,s"_s));
+ }
+ assert(bits[1].data.size() == 1);
+ assert(bits[1].data[0].data == "shop"_s);
+ if (bits[2].data.size() != 1)
+ {
+ return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s));
+ }
+ if (bits[3].data.size() < 2)
+ {
+ return Err(bits[3].span.error_str("in |component 4| expect at least 2 ,component,s"_s));
+ }
+
+ Shop shop;
+ TRY_EXTRACT(bits[0].data[0], shop.m);
+ TRY_EXTRACT(bits[0].data[1], shop.x);
+ TRY_EXTRACT(bits[0].data[2], shop.y);
+ TRY_EXTRACT(bits[0].data[3], shop.d);
+ shop.key_span = bits[1].data[0].span;
+ TRY_EXTRACT(bits[2].data[0], shop.name);
+ TRY_EXTRACT(bits[3].data[0], shop.npc_class);
+ shop.items.span = bits[3].span;
+ shop.items.span.begin = bits[3].data[1].span.begin;
+ for (size_t i = 1; i < bits[3].data.size(); ++i)
+ {
+ shop.items.data.emplace_back();
+ auto& item = shop.items.data.back();
+ auto& data = bits[3].data[i];
+ assert(data.span.begin.line == data.span.end.line);
+ item.span = data.span;
+
+ XString name, value;
+ if (!extract(data.data, record<':'>(&name, &value)))
+ return Err(data.span.error_str("Failed to split item:value"_s));
+ item.data.name.span = item.span;
+ item.data.name.span.end.column = item.data.name.span.begin.column + name.size() - 1;
+ item.data.value.span = item.span;
+ item.data.value.span.begin.column = item.data.name.span.begin.column + name.size() + 1;
+ if (name.endswith(' '))
+ {
+ item.data.name.span.warning("Shop item has useless space before the colon"_s);
+ name = name.rstrip();
+ }
+ if (name.is_digit10())
+ {
+ item.data.name.span.warning("Shop item is an id; should be a name"_s);
+ }
+ if (!extract(name, &item.data.name.data))
+ {
+ return Err(item.data.name.span.error_str("item name problem (too long?)"_s));
+ }
+ item.data.value_multiply = false;
+ if (value.startswith('-'))
+ {
+ item.data.value.span.begin.warning("Shop value multiplier should use '*' instead of '-' now"_s);
+ item.data.value_multiply = true;
+ item.data.value.span.begin.column += 1;
+ value = value.xslice_t(1);
+ }
+ else if (value.startswith('*'))
+ {
+ item.data.value_multiply = true;
+ item.data.value.span.begin.column += 1;
+ value = value.xslice_t(1);
+ }
+ if (!extract(value, &item.data.value.data))
+ {
+ return Err(item.data.value.span.error_str("invalid item value"_s));
+ }
+ }
+ return Ok(std::move(shop));
+ }
+ static
+ Result<Monster> parse_monster(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 3 && bits[0].data.size() != 5)
+ {
+ return Err(bits[0].span.error_str("in |component 1| expect 3 or 5 ,component,s"_s));
+ }
+ assert(bits[1].data.size() == 1);
+ assert(bits[1].data[0].data == "monster"_s);
+ if (bits[2].data.size() != 1)
+ {
+ return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s));
+ }
+ if (bits[3].data.size() != 2 && bits[3].data.size() != 4 && bits[3].data.size() != 5)
+ {
+ return Err(bits[3].span.error_str("in |component 4| expect 2, 4, or 5 ,component,s"_s));
+ }
+
+ Monster mob;
+ TRY_EXTRACT(bits[0].data[0], mob.m);
+ TRY_EXTRACT(bits[0].data[1], mob.x);
+ TRY_EXTRACT(bits[0].data[2], mob.y);
+ if (bits[0].data.size() >= 5)
+ {
+ TRY_EXTRACT(bits[0].data[3], mob.xs);
+ TRY_EXTRACT(bits[0].data[4], mob.ys);
+ }
+ else
+ {
+ mob.xs.data = 0;
+ mob.xs.span = bits[0].data[2].span;
+ mob.xs.span.end.column++;
+ mob.xs.span.begin.column = mob.xs.span.end.column;
+ mob.ys.data = 0;
+ mob.ys.span = mob.xs.span;
+ }
+ mob.key_span = bits[1].data[0].span;
+ TRY_EXTRACT(bits[2].data[0], mob.name);
+ TRY_EXTRACT(bits[3].data[0], mob.mob_class);
+ TRY_EXTRACT(bits[3].data[1], mob.num);
+ if (bits[3].data.size() >= 4)
+ {
+ static_assert(std::is_same<decltype(mob.delay1.data)::period, std::chrono::milliseconds::period>::value, "delay1 is milliseconds");
+ static_assert(std::is_same<decltype(mob.delay2.data)::period, std::chrono::milliseconds::period>::value, "delay2 is milliseconds");
+ if (bits[3].data[2].data.is_digit10())
+ bits[3].data[2].span.warning("delay1 lacks units; defaulting to ms"_s);
+ if (bits[3].data[3].data.is_digit10())
+ bits[3].data[3].span.warning("delay2 lacks units; defaulting to ms"_s);
+ TRY_EXTRACT(bits[3].data[2], mob.delay1);
+ TRY_EXTRACT(bits[3].data[3], mob.delay2);
+ if (bits[3].data.size() >= 5)
+ {
+ TRY_EXTRACT(bits[3].data[4], mob.event);
+ }
+ else
+ {
+ mob.event.data = NpcEvent();
+ mob.event.span = bits[3].data[3].span;
+ mob.event.span.end.column++;
+ mob.event.span.begin.column = mob.event.span.end.column;
+ }
+ }
+ else
+ {
+ mob.delay1.data = std::chrono::milliseconds::zero();
+ mob.delay1.span = bits[3].data[1].span;
+ mob.delay1.span.end.column++;
+ mob.delay1.span.begin.column = mob.delay1.span.end.column;
+ mob.delay2.data = std::chrono::milliseconds::zero();
+ mob.delay2.span = mob.delay1.span;
+ mob.event.data = NpcEvent();
+ mob.event.span = mob.delay1.span;
+ }
+ return Ok(std::move(mob));
+ }
+ static
+ Result<MapFlag> parse_mapflag(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ if (bits.size() != 3 && bits.size() != 4)
+ {
+ return Err(span.error_str("expect 3 or 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 1)
+ {
+ return Err(bits[0].span.error_str("in |component 1| expect 1 ,component,s"_s));
+ }
+ assert(bits[1].data.size() == 1);
+ assert(bits[1].data[0].data == "mapflag"_s);
+ if (bits[2].data.size() != 1)
+ {
+ return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s));
+ }
+
+ MapFlag mapflag;
+ TRY_EXTRACT(bits[0].data[0], mapflag.m);
+ mapflag.key_span = bits[1].data[0].span;
+ TRY_EXTRACT(bits[2].data[0], mapflag.name);
+ if (bits.size() >= 4)
+ {
+ mapflag.vec_extra.span = bits[3].span;
+ for (auto& bit : bits[3].data)
+ {
+ mapflag.vec_extra.data.emplace_back();
+ TRY_EXTRACT(bit, mapflag.vec_extra.data.back());
+ }
+ }
+ else
+ {
+ mapflag.vec_extra.data = {};
+ mapflag.vec_extra.span = bits[2].span;
+ mapflag.vec_extra.span.end.column++;
+ mapflag.vec_extra.span.begin.column = mapflag.vec_extra.span.end.column;
+ }
+ return Ok(std::move(mapflag));
+ }
+ static
+ Result<ScriptFunction> parse_script_function_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ // ScriptFunction: function|script|Fun Name{code}
+ if (bits.size() != 3)
+ {
+ return Err(span.error_str("expect 3 |component|s"_s));
+ }
+ assert(bits[0].data.size() == 1);
+ assert(bits[0].data[0].data == "function"_s);
+ assert(bits[1].data.size() == 1);
+ assert(bits[1].data[0].data == "script"_s);
+ if (bits[2].data.size() != 1)
+ {
+ return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s));
+ }
+
+ ScriptFunction script_function;
+ script_function.key1_span = bits[0].data[0].span;
+ TRY_EXTRACT(bits[2].data[0], script_function.name);
+ // also expect '{' and parse real script (in caller)
+ return Ok(std::move(script_function));
+ }
+ static
+ Result<ScriptNone> parse_script_none_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ // ScriptNone: -|script|script name|32767{code}
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ assert(bits[0].data.size() == 1);
+ assert(bits[0].data[0].data == "-"_s);
+ assert(bits[1].data.size() == 1);
+ assert(bits[1].data[0].data == "script"_s);
+ if (bits[2].data.size() != 1)
+ {
+ return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s));
+ }
+ assert(bits[3].data[0].data == "32767"_s);
+ if (bits[3].data.size() != 1)
+ {
+ return Err(bits[3].span.error_str("in |component 4| should be just 32767"_s));
+ }
+
+ ScriptNone script_none;
+ script_none.key1_span = bits[0].data[0].span;
+ TRY_EXTRACT(bits[2].data[0], script_none.name);
+ script_none.key4_span = bits[3].data[0].span;
+ // also expect '{' and parse real script (in caller)
+ return Ok(std::move(script_none));
+ }
+ static
+ Result<ScriptMap> parse_script_map_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits)
+ {
+ // ScriptMap: m,x,y,d|script|script name|class,xs,ys{code}
+ if (bits.size() != 4)
+ {
+ return Err(span.error_str("expect 4 |component|s"_s));
+ }
+ if (bits[0].data.size() != 4)
+ {
+ return Err(bits[0].span.error_str("in |component 1| expect 3 ,component,s"_s));
+ }
+ assert(bits[1].data.size() == 1);
+ assert(bits[1].data[0].data == "script"_s);
+ if (bits[2].data.size() != 1)
+ {
+ return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s));
+ }
+ if (bits[3].data.size() != 1 && bits[3].data.size() != 3)
+ {
+ return Err(bits[3].span.error_str("in |component 4| expect 1 or 3 ,component,s"_s));
+ }
+
+ ScriptMap script_map;
+ TRY_EXTRACT(bits[0].data[0], script_map.m);
+ TRY_EXTRACT(bits[0].data[1], script_map.x);
+ TRY_EXTRACT(bits[0].data[2], script_map.y);
+ TRY_EXTRACT(bits[0].data[3], script_map.d);
+ TRY_EXTRACT(bits[2].data[0], script_map.name);
+ TRY_EXTRACT(bits[3].data[0], script_map.npc_class);
+ if (bits[3].data.size() >= 3)
+ {
+ TRY_EXTRACT(bits[3].data[1], script_map.xs);
+ script_map.xs.data = script_map.xs.data * 2 + 1;
+ TRY_EXTRACT(bits[3].data[2], script_map.ys);
+ script_map.ys.data = script_map.ys.data * 2 + 1;
+ }
+ else
+ {
+ script_map.xs.data = 0;
+ script_map.xs.span = script_map.npc_class.span;
+ script_map.xs.span.end.column++;
+ script_map.xs.span.begin = script_map.xs.span.end;
+ script_map.ys.data = 0;
+ script_map.ys.span = script_map.xs.span;
+ }
+ // also expect '{' and parse real script (in caller)
+ return Ok(std::move(script_map));
+ }
+ static
+ Result<Script> parse_script_any(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits, io::LineCharReader& lr)
+ {
+ // 3 cases:
+ // ScriptFunction: function|script|Fun Name{code}
+ // ScriptNone: -|script|script name|32767{code}
+ // ScriptMap: m,x,y,d|script|script name|class,xs,ys{code}
+ if (bits[0].data[0].data == "function"_s)
+ {
+ Script rv = TRY(parse_script_function_head(span, bits));
+ rv.key_span = bits[1].data[0].span;
+
+ ast::script::ScriptOptions opt;
+ opt.implicit_start = true;
+ opt.default_label = "OnCall"_s;
+ opt.no_event = true;
+ rv.body = TRY(ast::script::parse_script_body(lr, opt));
+ return Ok(std::move(rv));
+ }
+ else if (bits[0].data[0].data == "-"_s)
+ {
+ Script rv = TRY(parse_script_none_head(span, bits));
+ rv.key_span = bits[1].data[0].span;
+
+ ast::script::ScriptOptions opt;
+ opt.implicit_start = true;
+ opt.no_start = true;
+ rv.body = TRY(ast::script::parse_script_body(lr, opt));
+ return Ok(std::move(rv));
+ }
+ else
+ {
+ ScriptMap script_map = TRY(parse_script_map_head(span, bits));
+ bool no_touch = !script_map.xs.data && !script_map.ys.data;
+ Script rv = std::move(script_map);
+ rv.key_span = bits[1].data[0].span;
+
+ ast::script::ScriptOptions opt;
+ opt.implicit_start = true;
+ opt.default_label = "OnClick"_s;
+ opt.no_touch = no_touch;
+ rv.body = TRY(ast::script::parse_script_body(lr, opt));
+ return Ok(std::move(rv));
+ }
+ }
+
+ /// Try to extract a top-level token
+ /// Return None at EOL, or Some(span)
+ /// This will alternate betweeen returning words and separators
+ static
+ Option<Spanned<RString>> lex(io::LineCharReader& lr, bool first)
+ {
+ // you know, I just realized a lot of the if (.get()) checks are not
+ // actually going to fail, since LineCharReader guarantees the \n
+ // occurs before EOF
+ io::LineChar c;
+ // at start of line, skip whitespace
+ if (first)
+ {
+ while (lr.get(c) && (/*c.ch() == ' ' ||*/ c.ch() == '\n'))
+ {
+ lr.adv();
+ }
+ }
+ // at end of file, end of line, or start of script, signal end
+ if (!lr.get(c) || c.ch() == '\n' || c.ch() == '{')
+ {
+ return None;
+ }
+ // separators are simple ... NOT.
+ // Reasonably, we should only ever eat a single char here.
+ // Unfortunately, we aren't dealing with reasonable people here.
+ if (c.ch() == '|' || c.ch() == ',')
+ {
+ lr.adv();
+ while (true)
+ {
+ io::LineChar c2;
+ if (!lr.get(c2) || c2.ch() == '\n' || c2.ch() == '{')
+ {
+ c.warning("Separator at EOL"_s);
+ return None;
+ }
+ if (c2.ch() == ',' || c2.ch() == '|')
+ {
+ lr.adv();
+ c = c2;
+ c.warning("Adjacent separators"_s);
+ continue;
+ }
+ break;
+ }
+ return Some(respan({c, c}, RString(VString<1>(c.ch()))));
+ }
+ io::LineSpan span;
+ MString accum;
+ accum += c.ch();
+ span.begin = c;
+ span.end = c;
+ lr.adv();
+ if (c.ch() != '/')
+ first = false;
+
+ // if one-char token followed by an end-of-line or separator, stop
+ if (!lr.get(c) || c.ch() == '\n' || c.ch() == '{' || c.ch() == ',' || c.ch() == '|')
+ {
+ return Some(respan(span, RString(accum)));
+ }
+
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+
+ // if first token on line, can get comment
+ if (first && c.ch() == '/')
+ {
+ while (lr.get(c) && c.ch() != '\n')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ }
+ return Some(respan(span, RString(accum)));
+ }
+ // otherwise, collect until an end of line or separator
+ while (lr.get(c) && c.ch() != '\n' && c.ch() != '{' && c.ch() != ',' && c.ch() != '|')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ }
+ return Some(respan(span, RString(accum)));
+ }
+
+ Option<Result<TopLevel>> parse_top(io::LineCharReader& in)
+ {
+ Spanned<std::vector<Spanned<std::vector<Spanned<RString>>>>> bits;
+
+ // special logic for the first 'bit'
+ {
+ Spanned<RString> mayc = TRY_UNWRAP(lex(in, true),
+ {
+ io::LineChar c;
+ if (in.get(c) && c.ch() == '{')
+ {
+ return Err(c.error_str("Unexpected script open"_s));
+ }
+ return None;
+ });
+ if (mayc.data.startswith("//"_s))
+ {
+ Comment com;
+ com.comment = std::move(mayc.data);
+ TopLevel rv = std::move(com);
+ rv.span = std::move(mayc.span);
+ return Some(Ok(std::move(rv)));
+ }
+
+ if (mayc.data == "|"_s || mayc.data == ","_s)
+ return Err(mayc.span.error_str("Unexpected separator"_s));
+ bits.span = mayc.span;
+ bits.data.emplace_back();
+ bits.data.back().span = mayc.span;
+ bits.data.back().data.push_back(mayc);
+ }
+
+ while (true)
+ {
+ Spanned<RString> sep = TRY_UNWRAP(lex(in, false),
+ break);
+ if (sep.data == "|"_s)
+ {
+ bits.data.emplace_back();
+ }
+ else if (sep.data != ","_s)
+ {
+ return Err(sep.span.error_str("Expected separator"_s));
+ }
+
+ Spanned<RString> word = TRY_UNWRAP(lex(in, false),
+ return Err(sep.span.error_str("Expected word after separator"_s)));
+ if (bits.data.back().data.empty())
+ bits.data.back().span = word.span;
+ else
+ bits.data.back().span.end = word.span.end;
+ bits.span.end = word.span.end;
+ bits.data.back().data.push_back(std::move(word));
+ }
+
+ if (bits.data.size() < 2)
+ return Err(bits.span.error_str("Expected a line with |s in it"_s));
+ for (auto& bit : bits.data)
+ {
+ if (bit.data.empty())
+ return Err(bit.span.error_str("Empty components are not cool"_s));
+ }
+ if (bits.data[1].data.size() != 1)
+ return Err(bits.data[1].span.error_str("Expected a single word in type position"_s));
+ Spanned<RString>& w2 = bits.data[1].data[0];
+ if (w2.data == "warp"_s)
+ {
+ TopLevel rv = TRY(parse_warp(bits.span, bits.data));
+ rv.span = bits.span;
+ return Some(Ok(std::move(rv)));
+ }
+ else if (w2.data == "shop"_s)
+ {
+ TopLevel rv = TRY(parse_shop(bits.span, bits.data));
+ rv.span = bits.span;
+ return Some(Ok(std::move(rv)));
+ }
+ else if (w2.data == "monster"_s)
+ {
+ TopLevel rv = TRY(parse_monster(bits.span, bits.data));
+ rv.span = bits.span;
+ return Some(Ok(std::move(rv)));
+ }
+ else if (w2.data == "mapflag"_s)
+ {
+ TopLevel rv = TRY(parse_mapflag(bits.span, bits.data));
+ rv.span = bits.span;
+ return Some(Ok(std::move(rv)));
+ }
+ else if (w2.data == "script"_s)
+ {
+ TopLevel rv = TRY_MOVE(parse_script_any(bits.span, bits.data, in));
+ rv.span = bits.span;
+ return Some(Ok(std::move(rv)));
+ }
+ else
+ {
+ return Err(w2.span.error_str("Unknown type"_s));
+ }
+ }
+} // namespace npc
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/npc.hpp b/src/ast/npc.hpp
new file mode 100644
index 0000000..ca6479e
--- /dev/null
+++ b/src/ast/npc.hpp
@@ -0,0 +1,142 @@
+#pragma once
+// ast/npc.hpp - Structure of non-player characters (including mobs).
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../compat/result.hpp"
+
+#include "../io/span.hpp"
+
+#include "../net/timer.t.hpp"
+
+#include "../sexpr/variant.hpp"
+
+#include "../mmo/clif.t.hpp"
+#include "../mmo/ids.hpp"
+#include "../mmo/strs.hpp"
+
+#include "script.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace npc
+{
+ using io::Spanned;
+
+ struct Comment
+ {
+ RString comment;
+ };
+ struct Warp
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ io::LineSpan key_span;
+ Spanned<NpcName> name;
+ Spanned<unsigned> xs, ys;
+ Spanned<MapName> to_m;
+ Spanned<unsigned> to_x, to_y;
+ };
+ struct ShopItem
+ {
+ Spanned<ItemName> name;
+ bool value_multiply;
+ Spanned<int> value;
+ };
+ struct Shop
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ Spanned<DIR> d;
+ io::LineSpan key_span;
+ Spanned<NpcName> name;
+ Spanned<Species> npc_class;
+ Spanned<std::vector<Spanned<ShopItem>>> items;
+ };
+ struct Monster
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ Spanned<unsigned> xs, ys;
+ io::LineSpan key_span;
+ Spanned<MobName> name;
+ Spanned<Species> mob_class;
+ Spanned<unsigned> num;
+ Spanned<interval_t> delay1, delay2;
+ Spanned<NpcEvent> event;
+ };
+ struct MapFlag
+ {
+ Spanned<MapName> m;
+ io::LineSpan key_span;
+ Spanned<RString> name;
+ Spanned<std::vector<Spanned<RString>>> vec_extra;
+ };
+ struct ScriptFunction
+ {
+ io::LineSpan key1_span;
+ Spanned<RString> name;
+ };
+ struct ScriptNone
+ {
+ io::LineSpan key1_span;
+ Spanned<NpcName> name;
+ io::LineSpan key4_span;
+ };
+ struct ScriptMap
+ {
+ Spanned<MapName> m;
+ Spanned<unsigned> x, y;
+ Spanned<DIR> d;
+ Spanned<NpcName> name;
+ Spanned<Species> npc_class;
+ Spanned<unsigned> xs, ys;
+ };
+ using ScriptBase = Variant<ScriptFunction, ScriptNone, ScriptMap>;
+ struct Script : ScriptBase
+ {
+ Script() = default;
+ Script(ScriptFunction s) : ScriptBase(std::move(s)) {}
+ Script(ScriptNone s) : ScriptBase(std::move(s)) {}
+ Script(ScriptMap s) : ScriptBase(std::move(s)) {}
+
+ io::LineSpan key_span;
+ ast::script::ScriptBody body;
+ };
+ using TopLevelBase = Variant<Comment, Warp, Shop, Monster, MapFlag, Script>;
+ struct TopLevel : TopLevelBase
+ {
+ TopLevel() = default;
+ TopLevel(Comment t) : TopLevelBase(std::move(t)) {}
+ TopLevel(Warp t) : TopLevelBase(std::move(t)) {}
+ TopLevel(Shop t) : TopLevelBase(std::move(t)) {}
+ TopLevel(Monster t) : TopLevelBase(std::move(t)) {}
+ TopLevel(MapFlag t) : TopLevelBase(std::move(t)) {}
+ TopLevel(Script t) : TopLevelBase(std::move(t)) {}
+ io::LineSpan span;
+ };
+
+ Option<Result<TopLevel>> parse_top(io::LineCharReader& in);
+} // namespace npc
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/npc_test.cpp b/src/ast/npc_test.cpp
new file mode 100644
index 0000000..dadeba7
--- /dev/null
+++ b/src/ast/npc_test.cpp
@@ -0,0 +1,556 @@
+#include "npc.hpp"
+// ast/npc_test.cpp - Testsuite for npc parser.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <gtest/gtest.h>
+
+#include "../io/line.hpp"
+
+#include "../tests/fdhack.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace npc
+{
+#define EXPECT_SPAN(span, bl,bc, el,ec) \
+ ({ \
+ EXPECT_EQ((span).begin.line, bl); \
+ EXPECT_EQ((span).begin.column, bc); \
+ EXPECT_EQ((span).end.line, el); \
+ EXPECT_EQ((span).end.column, ec); \
+ })
+
+ TEST(npcast, eof)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ ""_s,
+ "\n"_s,
+ "\n\n"_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = parse_top(lr);
+ EXPECT_TRUE(res.is_none());
+ }
+ }
+ TEST(npcast, comment)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ //23456789
+ "// hello"_s,
+ "// hello\n "_s,
+ "// hello\nabc"_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = TRY_UNWRAP(parse_top(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,8);
+ auto p = top.get_if<Comment>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_EQ(p->comment, "// hello"_s);
+ }
+ }
+ }
+ TEST(npcast, warp)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3 4
+ //234567890123456789012345678901234567890123456789
+ "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8"_s,
+ "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8\n"_s,
+ "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8{"_s,
+ // no optional fields in warp
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = TRY_UNWRAP(parse_top(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,47);
+ auto p = top.get_if<Warp>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ EXPECT_EQ(p->m.data, stringish<MapName>("map"_s));
+ EXPECT_SPAN(p->x.span, 1,9, 1,9);
+ EXPECT_EQ(p->x.data, 1);
+ EXPECT_SPAN(p->y.span, 1,11, 1,11);
+ EXPECT_EQ(p->y.data, 2);
+ EXPECT_SPAN(p->key_span, 1,13, 1,16);
+ EXPECT_SPAN(p->name.span, 1,18, 1,29);
+ EXPECT_EQ(p->name.data, stringish<NpcName>("To Other Map"_s));
+ EXPECT_SPAN(p->xs.span, 1,31, 1,31);
+ EXPECT_EQ(p->xs.data, 5);
+ EXPECT_SPAN(p->ys.span, 1,33, 1,33);
+ EXPECT_EQ(p->ys.data, 6);
+ EXPECT_SPAN(p->to_m.span, 1,35, 1,43);
+ EXPECT_EQ(p->to_m.data, stringish<MapName>("other"_s));
+ EXPECT_SPAN(p->to_x.span, 1,45, 1,45);
+ EXPECT_EQ(p->to_x.data, 7);
+ EXPECT_SPAN(p->to_y.span, 1,47, 1,47);
+ EXPECT_EQ(p->to_y.data, 8);
+ }
+ }
+ }
+ TEST(npcast, shop)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3 4 5
+ //2345678901234567890123456789012345678901234567890123456789
+ "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :*8"_s,
+ "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :*8\n"_s,
+ "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :*8{"_s,
+ // no optional fields in shop
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = TRY_UNWRAP(parse_top(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,55);
+ auto p = top.get_if<Shop>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ EXPECT_EQ(p->m.data, stringish<MapName>("map"_s));
+ EXPECT_SPAN(p->x.span, 1,9, 1,9);
+ EXPECT_EQ(p->x.data, 1);
+ EXPECT_SPAN(p->y.span, 1,11, 1,11);
+ EXPECT_EQ(p->y.data, 2);
+ EXPECT_SPAN(p->d.span, 1,13, 1,13);
+ EXPECT_EQ(p->d.data, DIR::NW);
+ EXPECT_SPAN(p->key_span, 1,15, 1,18);
+ EXPECT_SPAN(p->name.span, 1,20, 1,30);
+ EXPECT_EQ(p->name.data, stringish<NpcName>("Flower Shop"_s));
+ EXPECT_SPAN(p->npc_class.span, 1,32, 1,32);
+ EXPECT_EQ(p->npc_class.data, wrap<Species>(4));
+ EXPECT_SPAN(p->items.span, 1,34, 1,55);
+ EXPECT_EQ(p->items.data.size(), 3);
+ EXPECT_SPAN(p->items.data[0].span, 1,34, 1,36);
+ EXPECT_SPAN(p->items.data[0].data.name.span, 1,34, 1,34);
+ EXPECT_EQ(p->items.data[0].data.name.data, stringish<ItemName>("5"_s));
+ EXPECT_EQ(p->items.data[0].data.value_multiply, false);
+ EXPECT_SPAN(p->items.data[0].data.value.span, 1,36, 1,36);
+ EXPECT_EQ(p->items.data[0].data.value.data, 6);
+ EXPECT_SPAN(p->items.data[1].span, 1,38, 1,44);
+ EXPECT_SPAN(p->items.data[1].data.name.span, 1,38, 1,42);
+ EXPECT_EQ(p->items.data[1].data.name.data, stringish<ItemName>("Named"_s));
+ EXPECT_EQ(p->items.data[1].data.value_multiply, false);
+ EXPECT_SPAN(p->items.data[1].data.value.span, 1,44, 1,44);
+ EXPECT_EQ(p->items.data[1].data.value.data, 7);
+ EXPECT_SPAN(p->items.data[2].span, 1,46, 1,55);
+ EXPECT_SPAN(p->items.data[2].data.name.span, 1,46, 1,52);
+ EXPECT_EQ(p->items.data[2].data.name.data, stringish<ItemName>("Spaced"_s));
+ EXPECT_EQ(p->items.data[2].data.value_multiply, true);
+ EXPECT_SPAN(p->items.data[2].data.value.span, 1,55, 1,55);
+ EXPECT_EQ(p->items.data[2].data.value.data, 8);
+ }
+ }
+ }
+ TEST(npcast, monster)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3 4 5 6
+ //23456789012345678901234567890123456789012345678901234567890123456789
+ "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event"_s,
+ "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event\n"_s,
+ "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event{"_s,
+ "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000"_s,
+ "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000\n"_s,
+ "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000{"_s,
+ "nap.gat,1,20304|monster|Feeping Creature|506,700008000"_s,
+ "nap.gat,1,20304|monster|Feeping Creature|506,700008000\n"_s,
+ "nap.gat,1,20304|monster|Feeping Creature|506,700008000{"_s,
+ };
+ for (auto input : inputs)
+ {
+ bool first = input.startswith('m');
+ bool second = input.startswith('M');
+ bool third = input.startswith('n');
+ assert(first + second + third == 1);
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = TRY_UNWRAP(parse_top(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,first?65:54);
+ auto p = top.get_if<Monster>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ if (first)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("map"_s));
+ }
+ else if (second)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s));
+ }
+ else
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("nap"_s));
+ }
+ EXPECT_SPAN(p->x.span, 1,9, 1,9);
+ EXPECT_EQ(p->x.data, 1);
+ if (!third)
+ {
+ EXPECT_SPAN(p->y.span, 1,11, 1,11);
+ EXPECT_EQ(p->y.data, 2);
+ EXPECT_SPAN(p->xs.span, 1,13, 1,13);
+ EXPECT_EQ(p->xs.data, 3);
+ EXPECT_SPAN(p->ys.span, 1,15, 1,15);
+ EXPECT_EQ(p->ys.data, 4);
+ }
+ else
+ {
+ EXPECT_SPAN(p->y.span, 1,11, 1,15);
+ EXPECT_EQ(p->y.data, 20304);
+ EXPECT_SPAN(p->xs.span, 1,16, 1,16);
+ EXPECT_EQ(p->xs.data, 0);
+ EXPECT_SPAN(p->ys.span, 1,16, 1,16);
+ EXPECT_EQ(p->ys.data, 0);
+ }
+ EXPECT_SPAN(p->key_span, 1,17, 1,23);
+ EXPECT_SPAN(p->name.span, 1,25, 1,40);
+ EXPECT_EQ(p->name.data, stringish<MobName>("Feeping Creature"_s));
+ if (!third)
+ {
+ EXPECT_SPAN(p->mob_class.span, 1,42, 1,42);
+ EXPECT_EQ(p->mob_class.data, wrap<Species>(5));
+ EXPECT_SPAN(p->num.span, 1,44, 1,44);
+ EXPECT_EQ(p->num.data, 6);
+ EXPECT_SPAN(p->delay1.span, 1,46, 1,49);
+ EXPECT_EQ(p->delay1.data, 7_s);
+ EXPECT_SPAN(p->delay2.span, 1,51, 1,54);
+ EXPECT_EQ(p->delay2.data, 8_s);
+ }
+ else
+ {
+ EXPECT_SPAN(p->mob_class.span, 1,42, 1,44);
+ EXPECT_EQ(p->mob_class.data, wrap<Species>(506));
+ EXPECT_SPAN(p->num.span, 1,46, 1,54);
+ EXPECT_EQ(p->num.data, 700008000);
+ EXPECT_SPAN(p->delay1.span, 1,55, 1,55);
+ EXPECT_EQ(p->delay1.data, 0_s);
+ EXPECT_SPAN(p->delay2.span, 1,55, 1,55);
+ EXPECT_EQ(p->delay2.data, 0_s);
+ }
+ if (first)
+ {
+ EXPECT_SPAN(p->event.span, 1,56, 1,65);
+ EXPECT_EQ(p->event.data.npc, stringish<NpcName>("Npc"_s));
+ EXPECT_EQ(p->event.data.label, stringish<ScriptLabel>("Event"_s));
+ }
+ else
+ {
+ EXPECT_SPAN(p->event.span, 1,55, 1,55);
+ EXPECT_EQ(p->event.data.npc, NpcName());
+ EXPECT_EQ(p->event.data.label, ScriptLabel());
+ }
+ }
+ }
+ }
+ TEST(npcast, mapflag)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "map.gat|mapflag|flagname"_s,
+ "map.gat|mapflag|flagname\n"_s,
+ "map.gat|mapflag|flagname{"_s,
+ "Map.gat|mapflag|flagname|optval"_s,
+ "Map.gat|mapflag|flagname|optval\n"_s,
+ "Map.gat|mapflag|flagname|optval{"_s,
+ "nap.gat|mapflag|flagname|aa,b,c"_s,
+ "nap.gat|mapflag|flagname|aa,b,c\n"_s,
+ "nap.gat|mapflag|flagname|aa,b,c{"_s,
+ };
+ for (auto input : inputs)
+ {
+ bool first = input.startswith('m');
+ bool second = input.startswith('M');
+ bool third = input.startswith('n');
+ EXPECT_EQ(first + second + third, 1);
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = TRY_UNWRAP(parse_top(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,first?24:31);
+ auto p = top.get_if<MapFlag>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ if (first)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("map"_s));
+ }
+ if (second)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s));
+ }
+ if (third)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("nap"_s));
+ }
+ EXPECT_SPAN(p->key_span, 1,9, 1,15);
+ EXPECT_SPAN(p->name.span, 1,17, 1,24);
+ EXPECT_EQ(p->name.data, "flagname"_s);
+ if (first)
+ {
+ EXPECT_SPAN(p->vec_extra.span, 1,25, 1,25);
+ EXPECT_EQ(p->vec_extra.data.size(), 0);
+ }
+ if (second)
+ {
+ EXPECT_SPAN(p->vec_extra.span, 1,26, 1,31);
+ EXPECT_EQ(p->vec_extra.data.size(), 1);
+ EXPECT_SPAN(p->vec_extra.data[0].span, 1,26, 1,31);
+ EXPECT_EQ(p->vec_extra.data[0].data, "optval"_s);
+ }
+ if (third)
+ {
+ EXPECT_SPAN(p->vec_extra.span, 1,26, 1,31);
+ EXPECT_EQ(p->vec_extra.data.size(), 3);
+ EXPECT_SPAN(p->vec_extra.data[0].span, 1,26, 1,27);
+ EXPECT_EQ(p->vec_extra.data[0].data, "aa"_s);
+ EXPECT_SPAN(p->vec_extra.data[1].span, 1,29, 1,29);
+ EXPECT_EQ(p->vec_extra.data[1].data, "b"_s);
+ EXPECT_SPAN(p->vec_extra.data[2].span, 1,31, 1,31);
+ EXPECT_EQ(p->vec_extra.data[2].data, "c"_s);
+ }
+ }
+ }
+ }
+
+ TEST(npcast, scriptfun)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "function|script|Fun Name{end;}"_s,
+ // 123456
+ "function|script|Fun Name\n{end;}\n"_s,
+ // 1234567
+ "function|script|Fun Name\n \n {end;} "_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = TRY_UNWRAP(parse_top(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,24);
+ auto script = top.get_if<Script>();
+ EXPECT_TRUE(script);
+ auto p = script->get_if<ScriptFunction>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->key1_span, 1,1, 1,8);
+ EXPECT_SPAN(script->key_span, 1,10, 1,15);
+ EXPECT_SPAN(p->name.span, 1,17, 1,24);
+ EXPECT_EQ(p->name.data, "Fun Name"_s);
+ if (input.endswith('}'))
+ {
+ EXPECT_SPAN(script->body.span, 1,25, 1,30);
+ }
+ else if (input.endswith('\n'))
+ {
+ EXPECT_SPAN(script->body.span, 2,1, 2,6);
+ }
+ else if (input.endswith(' '))
+ {
+ EXPECT_SPAN(script->body.span, 3,2, 3,7);
+ }
+ else
+ {
+ FAIL();
+ }
+ EXPECT_EQ(script->body.braced_body, "{end;}"_s);
+ }
+ }
+ }
+ TEST(npcast, scriptnone)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "-|script|#config|32767{end;}"_s,
+ // 123456
+ "-|script|#config|32767\n{end;}\n"_s,
+ // 1234567
+ "-|script|#config|32767\n \n {end;} "_s,
+ };
+ for (auto input : inputs)
+ {
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = TRY_UNWRAP(parse_top(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,22);
+ auto script = top.get_if<Script>();
+ EXPECT_TRUE(script);
+ auto p = script->get_if<ScriptNone>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->key1_span, 1,1, 1,1);
+ EXPECT_SPAN(script->key_span, 1,3, 1,8);
+ EXPECT_SPAN(p->name.span, 1,10, 1,16);
+ EXPECT_EQ(p->name.data, stringish<NpcName>("#config"_s));
+ EXPECT_SPAN(p->key4_span, 1,18, 1,22);
+ if (input.endswith('}'))
+ {
+ EXPECT_SPAN(script->body.span, 1,23, 1,28);
+ }
+ else if (input.endswith('\n'))
+ {
+ EXPECT_SPAN(script->body.span, 2,1, 2,6);
+ }
+ else if (input.endswith(' '))
+ {
+ EXPECT_SPAN(script->body.span, 3,2, 3,7);
+ }
+ else
+ {
+ FAIL();
+ }
+ EXPECT_EQ(script->body.braced_body, "{end;}"_s);
+ }
+ }
+ }
+ TEST(npcast, scriptmap)
+ {
+ QuietFd q;
+ LString inputs[] =
+ {
+ // 1 2 3
+ //23456789012345678901234567890123456789
+ "map.gat,1,2,3|script|Asdf|4,5,6{end;}"_s,
+ "map.gat,1,2,3|script|Asdf|4,5,6\n{end;}\n"_s,
+ "map.gat,1,2,3|script|Asdf|4,5,6\n \n {end;} "_s,
+ "Map.gat,1,2,3|script|Asdf|40506{end;}"_s,
+ "Map.gat,1,2,3|script|Asdf|40506\n{end;}\n"_s,
+ "Map.gat,1,2,3|script|Asdf|40506\n \n {end;} "_s,
+ };
+ for (auto input : inputs)
+ {
+ bool second = input.startswith('M');
+ io::LineCharReader lr(io::from_string, "<string>"_s, input);
+ auto res = TRY_UNWRAP(parse_top(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,31);
+ auto script = top.get_if<Script>();
+ EXPECT_TRUE(script);
+ auto p = script->get_if<ScriptMap>();
+ EXPECT_TRUE(p);
+ if (p)
+ {
+ EXPECT_SPAN(p->m.span, 1,1, 1,7);
+ if (!second)
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("map"_s));
+ }
+ else
+ {
+ EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s));
+ }
+ EXPECT_SPAN(p->x.span, 1,9, 1,9);
+ EXPECT_EQ(p->x.data, 1);
+ EXPECT_SPAN(p->y.span, 1,11, 1,11);
+ EXPECT_EQ(p->y.data, 2);
+ EXPECT_SPAN(p->d.span, 1,13, 1,13);
+ EXPECT_EQ(p->d.data, DIR::NW);
+ EXPECT_SPAN(script->key_span, 1,15, 1,20);
+ EXPECT_SPAN(p->name.span, 1,22, 1,25);
+ EXPECT_EQ(p->name.data, stringish<NpcName>("Asdf"_s));
+ if (!second)
+ {
+ EXPECT_SPAN(p->npc_class.span, 1,27, 1,27);
+ EXPECT_EQ(p->npc_class.data, wrap<Species>(4));
+ EXPECT_SPAN(p->xs.span, 1,29, 1,29);
+ EXPECT_EQ(p->xs.data, 11);
+ EXPECT_SPAN(p->ys.span, 1,31, 1,31);
+ EXPECT_EQ(p->ys.data, 13);
+ }
+ else
+ {
+ EXPECT_SPAN(p->npc_class.span, 1,27, 1,31);
+ EXPECT_EQ(p->npc_class.data, wrap<Species>(40506));
+ EXPECT_SPAN(p->xs.span, 1,32, 1,32);
+ EXPECT_EQ(p->xs.data, 0);
+ EXPECT_SPAN(p->ys.span, 1,32, 1,32);
+ EXPECT_EQ(p->ys.data, 0);
+ }
+ if (input.endswith('}'))
+ {
+ EXPECT_SPAN(script->body.span, 1,32, 1,37);
+ }
+ else if (input.endswith('\n'))
+ {
+ EXPECT_SPAN(script->body.span, 2,1, 2,6);
+ }
+ else if (input.endswith(' '))
+ {
+ EXPECT_SPAN(script->body.span, 3,2, 3,7);
+ }
+ else
+ {
+ FAIL();
+ }
+ EXPECT_EQ(script->body.braced_body, "{end;}"_s);
+ }
+ }
+ }
+} // namespace npc
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/quest.cpp b/src/ast/quest.cpp
new file mode 100644
index 0000000..bd339c2
--- /dev/null
+++ b/src/ast/quest.cpp
@@ -0,0 +1,125 @@
+#include "quest.hpp"
+// ast/quest.cpp - Structure of tmwa questdb
+//
+// Copyright © 2015 Ed Pasek <pasekei@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../io/extract.hpp"
+#include "../io/line.hpp"
+
+#include "../mmo/extract_enums.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace quest
+{
+ using io::respan;
+
+ static
+ void skip_comma_space(io::LineCharReader& lr)
+ {
+ io::LineChar c;
+ if (lr.get(c) && c.ch() == ',')
+ {
+ lr.adv();
+ while (lr.get(c) && c.ch() == ' ')
+ {
+ lr.adv();
+ }
+ }
+ }
+ static
+ Option<Spanned<RString>> lex_nonscript(io::LineCharReader& lr, bool first)
+ {
+ io::LineChar c;
+ if (first)
+ {
+ while (lr.get(c) && c.ch() == '\n')
+ {
+ lr.adv();
+ }
+ }
+ if (!lr.get(c))
+ {
+ return None;
+ }
+ io::LineSpan span;
+ MString accum;
+ accum += c.ch();
+ span.begin = c;
+ span.end = c;
+ lr.adv();
+ if (c.ch() != '/')
+ first = false;
+
+ if (first && lr.get(c) && c.ch() == '/')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ while (lr.get(c) && c.ch() != '\n')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ }
+ return Some(respan(span, RString(accum)));
+ }
+
+ while (lr.get(c) && c.ch() != ',' && c.ch() != '\n')
+ {
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ }
+ skip_comma_space(lr);
+ return Some(respan(span, RString(accum)));
+ }
+
+#define SPAN_EXTRACT(bitexpr, var) ({ auto bit = bitexpr; if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract "_s #var)); var.span = bit.span; })
+
+#define EOL_ERROR(lr) ({ io::LineChar c; lr.get(c) ? Err(c.error_str("unexpected EOL"_s)) : Err("unexpected EOF before unexpected EOL"_s); })
+ Option<Result<QuestOrComment>> parse_quest(io::LineCharReader& lr)
+ {
+ Spanned<RString> first = TRY_UNWRAP(lex_nonscript(lr, true), return None);
+ if (first.data.startswith("//"_s))
+ {
+ Comment comment;
+ comment.comment = first.data;
+ QuestOrComment rv = std::move(comment);
+ rv.span = first.span;
+ return Some(Ok(std::move(rv)));
+ }
+ Quest quest;
+ SPAN_EXTRACT(first, quest.questid);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), quest.quest_var);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), quest.quest_vr);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), quest.quest_shift);
+ SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), quest.quest_mask);
+ QuestOrComment rv = std::move(quest);
+ rv.span.begin = quest.questid.span.begin;
+ rv.span.end = quest.quest_mask.span.end;
+ return Some(Ok(std::move(rv)));
+ }
+} // namespace quest
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/quest.hpp b/src/ast/quest.hpp
new file mode 100644
index 0000000..5112524
--- /dev/null
+++ b/src/ast/quest.hpp
@@ -0,0 +1,65 @@
+#pragma once
+// ast/quest.hpp - Structure of tmwa questdb
+//
+// Copyright © 2015 Ed Pasek <pasekei@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../compat/result.hpp"
+
+#include "../io/span.hpp"
+
+#include "../sexpr/variant.hpp"
+
+#include "../mmo/clif.t.hpp"
+#include "../mmo/ids.hpp"
+#include "../mmo/strs.hpp"
+
+namespace tmwa
+{
+namespace ast
+{
+namespace quest
+{
+ using io::Spanned;
+
+ struct Comment
+ {
+ RString comment;
+ };
+ struct Quest
+ {
+ Spanned<QuestId> questid;
+ Spanned<VarName> quest_var;
+ Spanned<VarName> quest_vr;
+ Spanned<int> quest_shift;
+ Spanned<int> quest_mask;
+ };
+
+ using QuestOrCommentBase = Variant<Comment, Quest>;
+ struct QuestOrComment : QuestOrCommentBase
+ {
+ QuestOrComment(Comment o) : QuestOrCommentBase(std::move(o)) {}
+ QuestOrComment(Quest o) : QuestOrCommentBase(std::move(o)) {}
+ io::LineSpan span;
+ };
+
+ Option<Result<QuestOrComment>> parse_quest(io::LineCharReader& lr);
+} // namespace quest
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/script.cpp b/src/ast/script.cpp
new file mode 100644
index 0000000..ec958e1
--- /dev/null
+++ b/src/ast/script.cpp
@@ -0,0 +1,75 @@
+#include "script.hpp"
+// ast/script.cpp - Structure of tmwa-script
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../io/line.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace script
+{
+ Result<ScriptBody> parse_script_body(io::LineCharReader& lr, ScriptOptions opt)
+ {
+ io::LineSpan span;
+ io::LineChar c;
+ while (true)
+ {
+ if (!lr.get(c))
+ {
+ return Err("error: unexpected EOF before '{' in parse_script_body"_s);
+ }
+ if (c.ch() == ' ' || (!opt.one_line && c.ch() == '\n'))
+ {
+ lr.adv();
+ continue;
+ }
+ break;
+ }
+ if (c.ch() != '{')
+ {
+ return Err(c.error_str("expected opening '{'"_s));
+ }
+
+ MString accum;
+ accum += c.ch();
+ span.begin = c;
+ lr.adv();
+ while (true)
+ {
+ if (!lr.get(c))
+ return Err(c.error_str("unexpected EOF before '}' in parse_script_body"_s));
+ if (opt.one_line && c.ch() == '\n')
+ return Err(c.error_str("unexpected EOL before '}' in parse_script_body"_s));
+ accum += c.ch();
+ span.end = c;
+ lr.adv();
+ if (c.ch() == '}')
+ {
+ return Ok(ScriptBody{RString(accum), std::move(span)});
+ }
+ }
+ }
+} // namespace script
+} // namespace ast
+} // namespace tmwa
diff --git a/src/ast/script.hpp b/src/ast/script.hpp
new file mode 100644
index 0000000..74b11e1
--- /dev/null
+++ b/src/ast/script.hpp
@@ -0,0 +1,112 @@
+#pragma once
+// ast/script.hpp - Structure of tmwa-script
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../compat/result.hpp"
+
+#include "../io/span.hpp"
+
+
+namespace tmwa
+{
+namespace ast
+{
+namespace script
+{
+ using io::Spanned;
+
+ struct ScriptBody
+ {
+ RString braced_body;
+ io::LineSpan span;
+ };
+
+ struct ScriptOptions
+ {
+ // don't require a label at the beginning
+ bool implicit_start = false;
+ // label to generate at the beginning if not already present
+ RString default_label;
+ // beginning must be only 'end;'
+ bool no_start;
+ // don't requite an 'end;' at the end
+ bool implicit_end = false;
+ // forbid newlines anywhere between { and }
+ bool one_line = false;
+ // forbid the OnTouch event
+ bool no_touch = false;
+ // forbid all events
+ bool no_event = false;
+ };
+
+ Result<ScriptBody> parse_script_body(io::LineCharReader& lr, ScriptOptions opt);
+
+ /*
+ (-- First bare-body-chunk only allowed for npcs, items, magic, functions.
+ It is not allowed for events. Basically it's an implicit label at times.
+ Last normal-lines is only permitted on item and magic scripts. --)
+ { script-body }: "{" bare-body-chunk? body-chunk* normal-lines? "}"
+ body-chunk: (comment* labelname ":")+ bare-body-chunk
+ bare-body-chunk: normal-lines terminator-line
+ normal-lines: normal-line*
+ any-line: normal-line
+ any-line: terminator-line
+ normal-line: "if" "(" expr ")" any-line
+ normal-line: normal-command ((expr ",")* expr)? ";"
+ terminator-line: "menu" (expr, labelname)* expr, labelname ";"
+ terminator-line: "goto" labelname ";"
+ terminator-line: terminator ((expr ",")* expr)? ";"
+ terminator: "return"
+ terminator: "close"
+ terminator: "end"
+ terminator: "mapexit"
+ terminator: "shop"
+
+ expr: subexpr_-1
+ subexpr_N: ("+" | "-" | "!" | "~") subexpr_7
+ subexpr_N: simple-expr (op_M subexpr_M | "(" ((expr ",")+ expr)? ")")* if N < M; function call only if N < 8 and preceding simple-expr (op sub)* is a known function
+ op_0: "||"
+ op_1: "&&"
+ op_2: "=="
+ op_2: "!="
+ op_2: ">="
+ op_2: ">"
+ op_2: "<="
+ op_2: "<"
+ op_3: "^"
+ op_4: "|"
+ op_5: "&"
+ op_5: ">>"
+ op_5: "<<"
+ op_6: "+"
+ op_6: "-"
+ op_7: "*"
+ op_7: "/"
+ op_7: "%"
+ simple-expr: "(" expr ")"
+ simple-expr: integer
+ simple-expr: string
+ simple-expr: variable ("[" expr "]")?
+ simple-expr: function // no longer command/label though
+ */
+} // namespace script
+} // namespace ast
+} // namespace tmwa
diff --git a/src/char/char.cpp b/src/char/char.cpp
index d5e887b..ed9e369 100644
--- a/src/char/char.cpp
+++ b/src/char/char.cpp
@@ -49,13 +49,13 @@
#include "../generic/array.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+#include "../io/extract.hpp"
#include "../io/lock.hpp"
#include "../io/read.hpp"
+#include "../io/span.hpp"
#include "../io/tty.hpp"
#include "../io/write.hpp"
-#include "../net/packets.hpp"
#include "../net/socket.hpp"
#include "../net/timer.hpp"
@@ -66,15 +66,23 @@
#include "../proto2/char-user.hpp"
#include "../mmo/config_parse.hpp"
-#include "../mmo/core.hpp"
-#include "../mmo/extract.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
#include "../mmo/extract_enums.hpp"
#include "../mmo/human_time_diff.hpp"
-#include "../mmo/mmo.hpp"
-#include "../mmo/utils.hpp"
#include "../mmo/version.hpp"
+#include "../high/core.hpp"
+#include "../high/extract_mmo.hpp"
+#include "../high/mmo.hpp"
+#include "../high/utils.hpp"
+
+#include "../wire/packets.hpp"
+
+#include "char_conf.hpp"
+#include "char_lan_conf.hpp"
+#include "globals.hpp"
#include "inter.hpp"
+#include "inter_conf.hpp"
#include "int_party.hpp"
#include "int_storage.hpp"
@@ -83,58 +91,8 @@
namespace tmwa
{
-static
-Array<struct mmo_map_server, MAX_MAP_SERVERS> server;
-static
-Array<Session *, MAX_MAP_SERVERS> server_session;
-static
-Array<int, MAX_MAP_SERVERS> server_freezeflag; // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed
-static
-int anti_freeze_enable = 0;
-static
-std::chrono::seconds anti_freeze_interval = 6_s;
-
-constexpr
-std::chrono::milliseconds DEFAULT_AUTOSAVE_INTERVAL =
- 5_min;
-
-static
-Session *login_session, *char_session;
-static
-AccountName userid;
-static
-AccountPass passwd;
-static
-ServerName server_name;
-static
-CharName wisp_server_name = stringish<CharName>("Server"_s);
-static
-IP4Address login_ip;
-static
-int login_port = 6900;
-static
-IP4Address char_ip;
-static
-int char_port = 6121;
-static
-AString char_txt;
-static
-CharName unknown_char_name = stringish<CharName>("Unknown"_s);
-static
-AString char_log_filename = "log/char.log"_s;
-//Added for lan support
-static
-IP4Address lan_map_ip = IP4_LOCALHOST;
-static
-IP4Mask lan_subnet = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST);
-static
-int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor]
-static
-std::bitset<256> char_name_letters; // list of letters/symbols authorised (or not) in a character name. by [Yor]
-static constexpr
-GmLevel default_gm_level = GmLevel::from(0_u32);
-
-
+namespace char_
+{
struct char_session_data : SessionData
{
AccountId account_id;
@@ -142,69 +100,16 @@ struct char_session_data : SessionData
SEX sex;
unsigned short packet_tmw_version;
AccountEmail email;
- TimeT connect_until_time;
};
+} // namespace char_
void SessionDeleter::operator()(SessionData *sd)
{
- really_delete1 static_cast<char_session_data *>(sd);
+ really_delete1 static_cast<char_::char_session_data *>(sd);
}
-struct AuthFifoEntry
+namespace char_
{
- AccountId account_id;
- CharId char_id;
- int login_id1, login_id2;
- IP4Address ip;
- int delflag;
- SEX sex;
- unsigned short packet_tmw_version;
- TimeT connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited)
-};
-static
-std::array<AuthFifoEntry, 256> auth_fifo;
-static
-auto auth_fifo_iter = auth_fifo.begin();
-
-static
-int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system)
-
-static
-CharId char_id_count = wrap<CharId>(150000);
-static
-std::vector<CharPair> char_keys;
-static
-int max_connect_user = 0;
-static
-std::chrono::milliseconds autosave_time = DEFAULT_AUTOSAVE_INTERVAL;
-
-// Initial position (it's possible to set it in conf file)
-static
-Point start_point = { {"001-1.gat"_s}, 273, 354 };
-
-static
-std::vector<GM_Account> gm_accounts;
-
-// online players by [Yor]
-static
-AString online_txt_filename = "online.txt"_s;
-static
-AString online_html_filename = "online.html"_s;
-static
-int online_sorting_option = 0; // sorting option to display online players in online files
-static
-int online_refresh_html = 20; // refresh time (in sec) of the html file in the explorer
-static
-GmLevel online_gm_display_min_level = GmLevel::from(20_u32); // minimum GM level to display 'GM' when we want to display it
-
-static
-std::vector<Session *> online_chars; // same size of char_keys, and id value of current server (or -1)
-static
-TimeT update_online; // to update online files when we receiving information from a server (not less than 8 seconds)
-
-static
-pid_t pid = 0; // For forked DB writes
-
auto iter_map_sessions() -> decltype(filter_iterator<Session *>(std::declval<Array<Session *, MAX_MAP_SERVERS> *>()))
{
@@ -250,7 +155,7 @@ void delete_frommap(Session *sess)
//------------------------------
void char_log(XString line)
{
- io::AppendFile logfp(char_log_filename, true);
+ io::AppendFile logfp(char_conf.char_log_filename, true);
if (!logfp.is_open())
return;
log_with_timestamp(logfp, line);
@@ -327,7 +232,7 @@ AString mmo_char_tostr(struct CharPair *cp)
// on multi-map server, sometimes it's posssible that last_point become void. (reason???) We check that to not lost character at restart.
if (!p->last_point.map_)
{
- p->last_point = start_point;
+ p->last_point = char_conf.start_point;
}
MString str_p;
@@ -414,12 +319,6 @@ AString mmo_char_tostr(struct CharPair *cp)
return AString(str_p);
}
-static
-bool extract(XString str, Point *p)
-{
- return extract(str, record<','>(&p->map_, &p->x, &p->y));
-}
-
struct skill_loader
{
SkillID id;
@@ -428,7 +327,7 @@ struct skill_loader
};
static
-bool extract(XString str, struct skill_loader *s)
+bool impl_extract(XString str, struct skill_loader *s)
{
uint32_t flags_and_level;
if (!extract(str,
@@ -438,13 +337,16 @@ bool extract(XString str, struct skill_loader *s)
s->flags = SkillFlags(flags_and_level >> 16);
return true;
}
+} // namespace char
//-------------------------------------------------------------------------
// Function to set the character from the line (at read of characters file)
//-------------------------------------------------------------------------
static
-bool extract(XString str, CharPair *cp)
+bool impl_extract(XString str, CharPair *cp)
{
+ using namespace tmwa::char_;
+
CharKey *k = &cp->key;
CharData *p = cp->data.get();
@@ -489,7 +391,7 @@ bool extract(XString str, CharPair *cp)
else if (!extract(hair_style, &p->hair))
return false;
- if (wisp_server_name == k->name)
+ if (WISP_SERVER_NAME == k->name)
return false;
// TODO replace *every* lookup with a map lookup
@@ -532,6 +434,8 @@ bool extract(XString str, CharPair *cp)
return true;
}
+namespace char_
+{
//---------------------------------
// Function to read characters file
//---------------------------------
@@ -541,11 +445,11 @@ int mmo_char_init(void)
char_keys.clear();
online_chars.clear();
- io::ReadFile in(char_txt);
+ io::ReadFile in(char_conf.char_txt);
if (!in.is_open())
{
- PRINTF("Characters file not found: %s.\n"_fmt, char_txt);
- CHAR_LOG("Characters file not found: %s.\n"_fmt, char_txt);
+ PRINTF("Characters file not found: %s.\n"_fmt, char_conf.char_txt);
+ CHAR_LOG("Characters file not found: %s.\n"_fmt, char_conf.char_txt);
CHAR_LOG("Id for the next created character: %d.\n"_fmt,
char_id_count);
return 0;
@@ -583,9 +487,9 @@ int mmo_char_init(void)
}
PRINTF("mmo_char_init: %zu characters read in %s.\n"_fmt,
- char_keys.size(), char_txt);
+ char_keys.size(), char_conf.char_txt);
CHAR_LOG("mmo_char_init: %zu characters read in %s.\n"_fmt,
- char_keys.size(), char_txt);
+ char_keys.size(), char_conf.char_txt);
CHAR_LOG("Id for the next created character: %d.\n"_fmt,
char_id_count);
@@ -599,7 +503,7 @@ int mmo_char_init(void)
static
void mmo_char_sync(void)
{
- io::WriteLock fp(char_txt);
+ io::WriteLock fp(char_conf.char_txt);
if (!fp.is_open())
{
PRINTF("WARNING: Server can't not save characters.\n"_fmt);
@@ -687,28 +591,18 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t
}
// Check Authorised letters/symbols in the name of the character
- if (char_name_option == 1)
{
// only letters/symbols in char_name_letters are authorised
for (uint8_t c : name.to__actual())
- if (!char_name_letters[c])
+ {
+ if (!char_conf.char_name_letters[c])
{
CHAR_LOG("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c.\n"_fmt,
s, sd->account_id, name, c);
return nullptr;
}
+ }
}
- else if (char_name_option == 2)
- {
- // letters/symbols in char_name_letters are forbidden
- for (uint8_t c : name.to__actual())
- if (char_name_letters[c])
- {
- CHAR_LOG("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c.\n"_fmt,
- s, sd->account_id, name, c);
- return nullptr;
- }
- } // else, all letters/symbols are authorised (except control char removed before)
// TODO this comment is obsolete
// this is why it needs to be unsigned
@@ -763,10 +657,10 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t
}
}
- if (wisp_server_name == name)
+ if (WISP_SERVER_NAME == name)
{
CHAR_LOG("Make new char error (name used is wisp name for server): (connection #%d, account: %d) slot %d, name: %s (actual name whisper server: %s), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d.\n"_fmt,
- s, sd->account_id, slot, name, wisp_server_name,
+ s, sd->account_id, slot, name, WISP_SERVER_NAME,
stats.str, stats.agi, stats.vit, stats.int_, stats.dex, stats.luk,
stats.str + stats.agi + stats.vit + stats.int_ + stats.dex + stats.luk,
hair_style, hair_color);
@@ -807,7 +701,7 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t
cd.sp = cd.max_sp;
cd.status_point = 0;
cd.skill_point = 0;
- cd.option = static_cast<Option>(0x0000); // Option is only declared
+ cd.option = static_cast<Opt0>(0x0000); // Opt0 is only declared
cd.karma = 0;
cd.manner = 0;
cd.party_id = PartyId();
@@ -821,8 +715,8 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t
cd.head_top = ItemNameId();
cd.head_mid = ItemNameId();
cd.head_bottom = ItemNameId();
- cd.last_point = start_point;
- cd.save_point = start_point;
+ cd.last_point = char_conf.start_point;
+ cd.save_point = char_conf.start_point;
char_keys.push_back(std::move(cp));
online_chars.push_back(nullptr);
@@ -836,10 +730,10 @@ static
void create_online_files(void)
{
// write files
- io::WriteFile fp(online_txt_filename);
+ io::WriteFile fp(char_conf.online_txt_filename);
if (fp.is_open())
{
- io::WriteFile fp2(online_html_filename);
+ io::WriteFile fp2(char_conf.online_html_filename);
if (fp2.is_open())
{
// get time
@@ -847,15 +741,15 @@ void create_online_files(void)
stamp_time(timetemp);
// write heading
FPRINTF(fp2, "<HTML>\n"_fmt);
- FPRINTF(fp2, " <META http-equiv=\"Refresh\" content=\"%d\">\n"_fmt, online_refresh_html); // update on client explorer every x seconds
+ FPRINTF(fp2, " <META http-equiv=\"Refresh\" content=\"%d\">\n"_fmt, char_conf.online_refresh_html); // update on client explorer every x seconds
FPRINTF(fp2, " <HEAD>\n"_fmt);
FPRINTF(fp2, " <TITLE>Online Players on %s</TITLE>\n"_fmt,
- server_name);
+ char_conf.server_name);
FPRINTF(fp2, " </HEAD>\n"_fmt);
FPRINTF(fp2, " <BODY>\n"_fmt);
FPRINTF(fp2, " <H3>Online Players on %s (%s):</H3>\n"_fmt,
- server_name, timetemp);
- FPRINTF(fp, "Online Players on %s (%s):\n"_fmt, server_name, timetemp);
+ char_conf.server_name, timetemp);
+ FPRINTF(fp, "Online Players on %s (%s):\n"_fmt, char_conf.server_name, timetemp);
FPRINTF(fp, "\n"_fmt);
int players = 0;
@@ -891,14 +785,14 @@ void create_online_files(void)
// without/with 'GM' display
GmLevel gml = isGM(cd.key.account_id);
{
- if (gml.satisfies(online_gm_display_min_level))
+ if (gml.satisfies(char_conf.online_gm_display_min_level))
FPRINTF(fp, "%-24s (GM) "_fmt, cd.key.name);
else
FPRINTF(fp, "%-24s "_fmt, cd.key.name);
}
// name of the character in the html (no < >, because that create problem in html code)
FPRINTF(fp2, " <td>"_fmt);
- if (gml.satisfies(online_gm_display_min_level))
+ if (gml.satisfies(char_conf.online_gm_display_min_level))
FPRINTF(fp2, "<b>"_fmt);
for (char c : cd.key.name.to__actual())
{
@@ -918,7 +812,7 @@ void create_online_files(void)
break;
};
}
- if (gml.satisfies(online_gm_display_min_level))
+ if (gml.satisfies(char_conf.online_gm_display_min_level))
FPRINTF(fp2, "</b> (GM)"_fmt);
FPRINTF(fp2, "</td>\n"_fmt);
}
@@ -1278,13 +1172,12 @@ void parse_tologin(Session *ls)
fixed_6c.code = 0x42;
send_fpacket<0x006c, 3>(s2, fixed_6c);
}
- else if (max_connect_user == 0
- || count_users() < max_connect_user)
+ else if (char_conf.max_connect_user == 0
+ || count_users() < char_conf.max_connect_user)
{
sd->email = stringish<AccountEmail>(fixed.email);
if (!e_mail_check(sd->email))
sd->email = DEFAULT_EMAIL;
- sd->connect_until_time = fixed.connect_until;
// send characters to player
mmo_char_send006b(s2, sd);
}
@@ -1323,7 +1216,6 @@ void parse_tologin(Session *ls)
sd->email = fixed.email;
if (!e_mail_check(sd->email))
sd->email = DEFAULT_EMAIL;
- sd->connect_until_time = fixed.connect_until;
break;
}
}
@@ -1331,28 +1223,6 @@ void parse_tologin(Session *ls)
break;
}
- case 0x2721: // gm reply
- {
- Packet_Fixed<0x2721> fixed;
- rv = recv_fpacket<0x2721, 10>(ls, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- {
- AccountId acc = fixed.account_id;
- GmLevel gml = fixed.gm_level;
-
- Packet_Fixed<0x2b0b> fixed_2b;
- fixed_2b.account_id = acc;
- fixed_2b.gm_level = gml;
- for (Session *ss : iter_map_sessions())
- {
- send_fpacket<0x2b0b, 10>(ss, fixed_2b);
- }
- }
- break;
- }
-
case 0x2723: // changesex reply (modified by [Yor])
{
Packet_Fixed<0x2723> fixed;
@@ -1481,65 +1351,6 @@ void parse_tologin(Session *ls)
break;
}
- case 0x7924:
- { // [Fate] Itemfrob package: forwarded from login-server
- Packet_Fixed<0x7924> fixed;
- rv = recv_fpacket<0x7924, 10>(ls, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- ItemNameId source_id = fixed.source_item_id;
- ItemNameId dest_id = fixed.dest_item_id;
-
- Packet_Fixed<0x2afa> fixed_fa;
- fixed_fa.source_item_id = source_id;
- fixed_fa.dest_item_id = dest_id;
-
- // forward package to map servers
- for (Session *ss : iter_map_sessions())
- {
- send_fpacket<0x2afa, 10>(ss, fixed_fa);
- }
-
- for (CharPair& cp : char_keys)
- {
- CharKey *k = &cp.key;
- CharData& cd = *cp.data.get();
- CharData *c = &cd;
- Storage *s = account2storage(k->account_id);
- int changes = 0;
-#define FIX(v) if (v == source_id) {v = dest_id; ++changes; }
- for (IOff0 j : IOff0::iter())
- {
- FIX(c->inventory[j].nameid);
- }
- // used to FIX cart, but it's no longer supported
- // FIX(c->weapon);
- FIX(c->shield);
- FIX(c->head_top);
- FIX(c->head_mid);
- FIX(c->head_bottom);
-
- if (s)
- {
- for (SOff0 j : SOff0::iter())
- {
- FIX(s->storage_[j].nameid);
- }
- }
-#undef FIX
- if (changes)
- CHAR_LOG("itemfrob(%d -> %d): `%s'(%d, account %d): changed %d times\n"_fmt,
- source_id, dest_id, k->name, k->char_id,
- k->account_id, changes);
-
- }
-
- mmo_char_sync();
- inter_storage_save();
- break;
- }
-
// Account deletion notification (from login-server)
case 0x2730:
{
@@ -1737,22 +1548,6 @@ void parse_frommap(Session *ms)
{
switch (packet_id)
{
- // request from map-server to reload GM accounts. Transmission to login-server (by Yor)
- case 0x2af7:
- {
- Packet_Fixed<0x2af7> fixed;
- rv = recv_fpacket<0x2af7, 2>(ms, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- if (login_session)
- { // don't send request if no login-server
- Packet_Fixed<0x2709> fixed_09;
- send_fpacket<0x2709, 2>(login_session, fixed_09);
- }
- break;
- }
-
// Receiving map names list from the map-server
case 0x2afa:
{
@@ -1781,7 +1576,6 @@ void parse_frommap(Session *ms)
Packet_Fixed<0x2afb> fixed_fb;
fixed_fb.unknown = 0;
- fixed_fb.whisper_name = wisp_server_name;
send_fpacket<0x2afb, 27>(ms, fixed_fb);
{
@@ -1857,7 +1651,7 @@ void parse_frommap(Session *ms)
afi.login_id1 == login_id1 &&
// here, it's the only area where it's possible that we doesn't know login_id2 (map-server asks just after 0x72 packet, that doesn't given the value)
(afi.login_id2 == login_id2 || login_id2 == 0) && // relate to the versions higher than 18
- (!check_ip_flag || afi.ip == ip)
+ afi.ip == ip
&& !afi.delflag)
{
CharPair *cp = nullptr;
@@ -1878,7 +1672,6 @@ void parse_frommap(Session *ms)
Packet_Payload<0x2afd> payload_fd; // not file descriptor
payload_fd.account_id = account_id;
payload_fd.login_id2 = afi.login_id2;
- payload_fd.connect_until = afi.connect_until_time;
cd->sex = afi.sex;
payload_fd.packet_tmw_version = afi.packet_tmw_version;
FPRINTF(stderr,
@@ -1912,7 +1705,7 @@ void parse_frommap(Session *ms)
server[id].users = head.users;
assert (head.users == repeat.size());
- if (anti_freeze_enable)
+ if (char_conf.anti_freeze_enable)
server_freezeflag[id] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed
// remove all previously online players of the server
for (Session *& oci : online_chars)
@@ -1984,7 +1777,6 @@ void parse_frommap(Session *ms)
auth_fifo_iter->login_id1 = fixed.login_id1;
auth_fifo_iter->login_id2 = fixed.login_id2;
auth_fifo_iter->delflag = 2;
- auth_fifo_iter->connect_until_time = TimeT(); // unlimited/unknown time by default (not display in map-server)
auth_fifo_iter->ip = fixed.ip;
auth_fifo_iter++;
@@ -2023,7 +1815,6 @@ void parse_frommap(Session *ms)
auth_fifo_iter->char_id = fixed.char_id;
auth_fifo_iter->delflag = 0;
auth_fifo_iter->sex = fixed.sex;
- auth_fifo_iter->connect_until_time = TimeT(); // unlimited/unknown time by default (not display in map-server)
auth_fifo_iter->ip = fixed.client_ip;
// default, if not found in the loop
@@ -2044,33 +1835,6 @@ void parse_frommap(Session *ms)
break;
}
- // it is a request to become GM
- case 0x2b0a:
- {
- Packet_Head<0x2b0a> head;
- AString repeat;
- rv = recv_vpacket<0x2b0a, 8, 1>(ms, head, repeat);
- if (rv != RecvResult::Complete)
- break;
-
- AccountId account_id = head.account_id;
- if (login_session)
- { // don't send request if no login-server
- Packet_Head<0x2720> head_20;
- head_20.account_id = account_id;
- AString& repeat_20 = repeat;
- send_vpacket<0x2720, 8, 1>(login_session, head_20, repeat_20);
- }
- else
- {
- Packet_Fixed<0x2b0b> fixed_0b;
- fixed_0b.account_id = account_id;
- fixed_0b.gm_level = GmLevel();
- send_fpacket<0x2b0b, 10>(ms, fixed_0b);
- }
- break;
- }
-
// Map server send information to change an email of an account -> login-server
case 0x2b0c:
{
@@ -2336,7 +2100,7 @@ int search_mapserver(XString map)
static
int lan_ip_check(IP4Address addr)
{
- bool lancheck = lan_subnet.covers(addr);
+ bool lancheck = char_lan_conf.lan_subnet.covers(addr);
PRINTF("LAN test (result): %s.\n"_fmt,
(lancheck) ? SGR_BOLD SGR_CYAN "LAN source" SGR_RESET ""_s : SGR_BOLD SGR_GREEN "WAN source" SGR_RESET ""_s);
@@ -2401,7 +2165,7 @@ void handle_x0066(Session *s, struct char_session_data *sd, uint8_t rfifob_2, IP
sd->account_id, ck->char_num, ip);
PRINTF("--Send IP of map-server. "_fmt);
if (lan_ip_check(ip))
- fixed_71.ip = lan_map_ip;
+ fixed_71.ip = char_lan_conf.lan_map_ip;
else
fixed_71.ip = server[i].ip;
fixed_71.port = server[i].port;
@@ -2415,7 +2179,6 @@ void handle_x0066(Session *s, struct char_session_data *sd, uint8_t rfifob_2, IP
auth_fifo_iter->login_id2 = sd->login_id2;
auth_fifo_iter->delflag = 0;
auth_fifo_iter->sex = sd->sex;
- auth_fifo_iter->connect_until_time = sd->connect_until_time;
auth_fifo_iter->ip = s->client_ip;
auth_fifo_iter->packet_tmw_version = sd->packet_tmw_version;
auth_fifo_iter++;
@@ -2484,7 +2247,6 @@ void parse_char(Session *s)
s->session_data = make_unique<char_session_data, SessionDeleter>();
sd = static_cast<char_session_data *>(s->session_data.get());
sd->email = stringish<AccountEmail>("no mail"_s); // put here a mail without '@' to refuse deletion if we don't receive the e-mail
- sd->connect_until_time = TimeT(); // unknow or illimited (not displaying on map-server)
}
sd->account_id = account_id;
sd->login_id1 = fixed.login_id1;
@@ -2503,13 +2265,12 @@ void parse_char(Session *s)
if (afi.account_id == sd->account_id
&& afi.login_id1 == sd->login_id1
&& afi.login_id2 == sd->login_id2
- && (!check_ip_flag
- || afi.ip == s->client_ip)
+ && afi.ip == s->client_ip
&& afi.delflag == 2)
{
afi.delflag = 1;
- if (max_connect_user == 0
- || count_users() < max_connect_user)
+ if (char_conf.max_connect_user == 0
+ || count_users() < char_conf.max_connect_user)
{
{
// there is always a login server
@@ -2608,7 +2369,7 @@ void parse_char(Session *s)
fixed_6d.char_select.gloves = ItemNameId();
fixed_6d.char_select.cape = ItemNameId();
fixed_6d.char_select.misc1 = ItemNameId();
- fixed_6d.char_select.option = Option();
+ fixed_6d.char_select.option = Opt0();
fixed_6d.char_select.unused = 0;
// this was buggy until the protocol became generated
@@ -2663,9 +2424,6 @@ void parse_char(Session *s)
s->set_eof();
return;
}
- AccountEmail email = fixed.email;
- if (!e_mail_check(email))
- email = DEFAULT_EMAIL;
{
{
@@ -2723,8 +2481,8 @@ void parse_char(Session *s)
}
AccountName userid_ = fixed.account_name;
AccountPass passwd_ = fixed.account_pass;
- if (i == MAX_MAP_SERVERS || userid_ != userid
- || passwd_ != passwd)
+ if (i == MAX_MAP_SERVERS || userid_ != char_conf.userid
+ || passwd_ != char_conf.passwd)
{
fixed_f9.code = 3;
send_fpacket<0x2af9, 3>(s, fixed_f9);
@@ -2734,7 +2492,7 @@ void parse_char(Session *s)
fixed_f9.code = 0;
s->set_parsers(SessionParsers{.func_parse= parse_frommap, .func_delete= delete_frommap});
server_session[i] = s;
- if (anti_freeze_enable)
+ if (char_conf.anti_freeze_enable)
server_freezeflag[i] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed
// ignore fixed.unknown
server[i].ip = fixed.ip;
@@ -2822,19 +2580,19 @@ void check_connect_login_server(TimerData *, tick_t)
if (!login_session)
{
PRINTF("Attempt to connect to login-server...\n"_fmt);
- login_session = make_connection(login_ip, login_port,
+ login_session = make_connection(char_conf.login_ip, char_conf.login_port,
SessionParsers{.func_parse= parse_tologin, .func_delete= delete_tologin});
if (!login_session)
return;
realloc_fifo(login_session, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK);
Packet_Fixed<0x2710> fixed_10;
- fixed_10.account_name = userid;
- fixed_10.account_pass = passwd;
+ fixed_10.account_name = char_conf.userid;
+ fixed_10.account_pass = char_conf.passwd;
fixed_10.unknown = 0;
- fixed_10.ip = char_ip;
- fixed_10.port = char_port;
- fixed_10.server_name = server_name;
+ fixed_10.ip = char_conf.char_ip;
+ fixed_10.port = char_conf.char_port;
+ fixed_10.server_name = char_conf.server_name;
fixed_10.unknown2 = 0;
fixed_10.maintenance = 0;
fixed_10.is_new = 0;
@@ -2842,61 +2600,13 @@ void check_connect_login_server(TimerData *, tick_t)
}
}
-//-------------------------------------------
-// Reading Lan Support configuration by [Yor]
-//-------------------------------------------
-static
-bool char_lan_config(XString w1, ZString w2)
-{
- struct hostent *h = nullptr;
-
- {
- if (w1 == "lan_map_ip"_s)
- {
- // Read map-server Lan IP Address
- h = gethostbyname(w2.c_str());
- if (h != nullptr)
- {
- lan_map_ip = IP4Address({
- static_cast<uint8_t>(h->h_addr[0]),
- static_cast<uint8_t>(h->h_addr[1]),
- static_cast<uint8_t>(h->h_addr[2]),
- static_cast<uint8_t>(h->h_addr[3]),
- });
- }
- else
- {
- PRINTF("Bad IP value: %s\n"_fmt, w2);
- return false;
- }
- PRINTF("LAN IP of map-server: %s.\n"_fmt, lan_map_ip);
- }
- else if (w1 == "subnet"_s /*backward compatibility*/
- || w1 == "lan_subnet"_s)
- {
- if (!extract(w2, &lan_subnet))
- {
- PRINTF("Bad IP mask: %s\n"_fmt, w2);
- return false;
- }
- PRINTF("Sub-network of the map-server: %s.\n"_fmt,
- lan_subnet);
- }
- else
- {
- return false;
- }
- }
- return true;
-}
-
static
bool lan_check()
{
// sub-network check of the map-server
{
PRINTF("LAN test of LAN IP of the map-server: "_fmt);
- if (!lan_ip_check(lan_map_ip))
+ if (!lan_ip_check(char_lan_conf.lan_map_ip))
{
PRINTF(SGR_BOLD SGR_RED "***ERROR: LAN IP of the map-server doesn't belong to the specified Sub-network." SGR_RESET "\n"_fmt);
return false;
@@ -2907,161 +2617,46 @@ bool lan_check()
}
static
-bool char_config(XString w1, ZString w2)
+bool char_config(io::Spanned<XString> key, io::Spanned<ZString> value)
{
- struct hostent *h = nullptr;
+ return parse_char_conf(char_conf, key, value);
+}
+static
+bool char_lan_config(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ return parse_char_lan_conf(char_lan_conf, key, value);
+}
+
+static
+bool inter_config(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ return parse_inter_conf(inter_conf, key, value);
+}
+
+static
+bool char_confs(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ if (key.data == "char_conf"_s)
{
- if (w1 == "userid"_s)
- userid = stringish<AccountName>(w2);
- else if (w1 == "passwd"_s)
- passwd = stringish<AccountPass>(w2);
- else if (w1 == "server_name"_s)
- {
- server_name = stringish<ServerName>(w2);
- PRINTF("%s server has been intialized\n"_fmt, w2);
- }
- else if (w1 == "wisp_server_name"_s)
- {
- if (w2.size() >= 4)
- wisp_server_name = stringish<CharName>(w2);
- }
- else if (w1 == "login_ip"_s)
- {
- h = gethostbyname(w2.c_str());
- if (h != nullptr)
- {
- login_ip = IP4Address({
- static_cast<uint8_t>(h->h_addr[0]),
- static_cast<uint8_t>(h->h_addr[1]),
- static_cast<uint8_t>(h->h_addr[2]),
- static_cast<uint8_t>(h->h_addr[3]),
- });
- PRINTF("Login server IP address : %s -> %s\n"_fmt,
- w2, login_ip);
- }
- else
- {
- PRINTF("Bad IP value: %s\n"_fmt, w2);
- return false;
- }
- }
- else if (w1 == "login_port"_s)
- {
- login_port = atoi(w2.c_str());
- }
- else if (w1 == "char_ip"_s)
- {
- h = gethostbyname(w2.c_str());
- if (h != nullptr)
- {
- char_ip = IP4Address({
- static_cast<uint8_t>(h->h_addr[0]),
- static_cast<uint8_t>(h->h_addr[1]),
- static_cast<uint8_t>(h->h_addr[2]),
- static_cast<uint8_t>(h->h_addr[3]),
- });
- PRINTF("Character server IP address : %s -> %s\n"_fmt,
- w2, char_ip);
- }
- else
- {
- PRINTF("Bad IP value: %s\n"_fmt, w2);
- return false;
- }
- }
- else if (w1 == "char_port"_s)
- {
- char_port = atoi(w2.c_str());
- }
- else if (w1 == "char_txt"_s)
- {
- char_txt = w2;
- }
- else if (w1 == "max_connect_user"_s)
- {
- max_connect_user = atoi(w2.c_str());
- if (max_connect_user < 0)
- max_connect_user = 0; // unlimited online players
- }
- else if (w1 == "check_ip_flag"_s)
- {
- check_ip_flag = config_switch(w2);
- }
- else if (w1 == "autosave_time"_s)
- {
- autosave_time = std::chrono::seconds(atoi(w2.c_str()));
- if (autosave_time <= std::chrono::seconds::zero())
- autosave_time = DEFAULT_AUTOSAVE_INTERVAL;
- }
- else if (w1 == "start_point"_s)
- {
- extract(w2, &start_point);
- }
- else if (w1 == "unknown_char_name"_s)
- {
- unknown_char_name = stringish<CharName>(w2);
- }
- else if (w1 == "char_log_filename"_s)
- {
- char_log_filename = w2;
- }
- else if (w1 == "char_name_option"_s)
- {
- char_name_option = atoi(w2.c_str());
- }
- else if (w1 == "char_name_letters"_s)
- {
- if (!w2)
- char_name_letters.reset();
- else
- for (uint8_t c : w2)
- char_name_letters[c] = true;
- }
- else if (w1 == "online_txt_filename"_s)
- {
- online_txt_filename = w2;
- }
- else if (w1 == "online_html_filename"_s)
- {
- online_html_filename = w2;
- }
- else if (w1 == "online_sorting_option"_s)
- {
- online_sorting_option = atoi(w2.c_str());
- }
- else if (w1 == "online_gm_display_min_level"_s)
- {
- // minimum GM level to display 'GM' when we want to display it
- return extract(w2, &online_gm_display_min_level);
- }
- else if (w1 == "online_refresh_html"_s)
- {
- online_refresh_html = atoi(w2.c_str());
- if (online_refresh_html < 1)
- online_refresh_html = 1;
- }
- else if (w1 == "anti_freeze_enable"_s)
- {
- anti_freeze_enable = config_switch(w2);
- }
- else if (w1 == "anti_freeze_interval"_s)
- {
- anti_freeze_interval = std::max(
- std::chrono::seconds(atoi(w2.c_str())),
- 5_s);
- }
- else
- {
- return false;
- }
+ return load_config_file(value.data, char_config);
}
-
- return true;
+ if (key.data == "char_lan_conf"_s)
+ {
+ return load_config_file(value.data, char_lan_config);
+ }
+ if (key.data == "inter_conf"_s)
+ {
+ return load_config_file(value.data, inter_config);
+ }
+ key.span.error("Unknown meta-key for char server"_s);
+ return false;
}
+} // namespace char_
void term_func(void)
{
+ using namespace tmwa::char_;
// write online players files with no player
std::fill(online_chars.begin(), online_chars.end(), nullptr);
create_online_files();
@@ -3079,20 +2674,9 @@ void term_func(void)
CHAR_LOG("----End of char-server (normal end with closing of all files).\n"_fmt);
}
-static
-bool char_confs(XString key, ZString value)
-{
- unsigned sum = 0;
- sum += char_config(key, value);
- sum += char_lan_config(key, value);
- sum += inter_config(key, value);
- if (sum >= 2)
- abort();
- return sum;
-}
-
int do_init(Slice<ZString> argv)
{
+ using namespace tmwa::char_;
ZString argv0 = argv.pop_front();
bool loaded_config_yet = false;
@@ -3121,12 +2705,12 @@ int do_init(Slice<ZString> argv)
else
{
loaded_config_yet = true;
- runflag &= load_config_file(argvi, char_confs);
+ runflag &= load_config_file(argvi, char_::char_confs);
}
}
if (!loaded_config_yet)
- runflag &= load_config_file("conf/tmwa-char.conf"_s, char_confs);
+ runflag &= load_config_file("conf/tmwa-char.conf"_s, char_::char_confs);
// a newline in the log...
CHAR_LOG(""_fmt);
@@ -3140,7 +2724,7 @@ int do_init(Slice<ZString> argv)
update_online = TimeT::now();
create_online_files(); // update online players files at start of the server
- char_session = make_listen_port(char_port, SessionParsers{parse_char, delete_char});
+ char_session = make_listen_port(char_conf.char_port, SessionParsers{parse_char, delete_char});
Timer(gettick() + 1_s,
check_connect_login_server,
@@ -3150,25 +2734,26 @@ int do_init(Slice<ZString> argv)
send_users_tologin,
5_s
).detach();
- Timer(gettick() + autosave_time,
+ Timer(gettick() + char_conf.autosave_time,
mmo_char_sync_timer,
- autosave_time
+ char_conf.autosave_time
).detach();
- if (anti_freeze_enable > 0)
+ if (char_conf.anti_freeze_enable > 0)
{
Timer(gettick() + 1_s,
map_anti_freeze_system,
- anti_freeze_interval
+ char_conf.anti_freeze_interval
).detach();
}
CHAR_LOG("The char-server is ready (Server is listening on the port %d).\n"_fmt,
- char_port);
+ char_conf.char_port);
PRINTF("The char-server is " SGR_BOLD SGR_GREEN "ready" SGR_RESET " (Server is listening on the port %d).\n\n"_fmt,
- char_port);
+ char_conf.char_port);
return 0;
}
+// namespace char_ ends before term_func and do_init
} // namespace tmwa
diff --git a/src/char/char.hpp b/src/char/char.hpp
index a9c786f..4f55c04 100644
--- a/src/char/char.hpp
+++ b/src/char/char.hpp
@@ -22,18 +22,36 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
+#include "../ints/udl.hpp"
#include "../generic/array.hpp"
#include "../net/ip.hpp"
-#include "../mmo/mmo.hpp"
+#include "../high/mmo.hpp"
+
+#include "consts.hpp"
namespace tmwa
{
-constexpr int MAX_MAP_SERVERS = 30;
+namespace char_
+{
+constexpr
+std::chrono::seconds DEFAULT_AUTOSAVE_INTERVAL = 5_min;
+constexpr
+GmLevel default_gm_level = GmLevel::from(0_u32);
+
+struct AuthFifoEntry
+{
+ AccountId account_id;
+ CharId char_id;
+ int login_id1, login_id2;
+ IP4Address ip;
+ int delflag;
+ SEX sex;
+ unsigned short packet_tmw_version;
+};
struct mmo_map_server
{
@@ -53,4 +71,5 @@ void char_log(XString line);
#define CHAR_LOG(fmt, ...) \
char_log(STRPRINTF(fmt, ## __VA_ARGS__))
+} // namespace char_
} // namespace tmwa
diff --git a/src/map/magic-interpreter.cpp b/src/char/consts.hpp
index 389a821..e3b6c57 100644
--- a/src/map/magic-interpreter.cpp
+++ b/src/char/consts.hpp
@@ -1,5 +1,5 @@
-#include "magic-interpreter.hpp"
-// magic-interpreter.cpp - Old magic.
+#pragma once
+// consts.hpp - Constants for tmwa-char.
//
// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
@@ -18,12 +18,14 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "fwd.hpp"
namespace tmwa
{
-namespace magic
+namespace char_
{
-} // namespace magic
+constexpr
+int MAX_MAP_SERVERS = 30;
+} // namespace char_
} // namespace tmwa
diff --git a/src/char/fwd.hpp b/src/char/fwd.hpp
index 31cd1ba..8086083 100644
--- a/src/char/fwd.hpp
+++ b/src/char/fwd.hpp
@@ -20,8 +20,31 @@
#include "../sanity.hpp"
+#include "../ints/fwd.hpp" // rank 1
+#include "../strings/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp" // rank 2
+#include "../generic/fwd.hpp" // rank 3
+#include "../io/fwd.hpp" // rank 4
+#include "../net/fwd.hpp" // rank 5
+#include "../mmo/fwd.hpp" // rank 6
+#include "../proto2/fwd.hpp" // rank 8
+#include "../high/fwd.hpp" // rank 9
+#include "../wire/fwd.hpp" // rank 9
+// char/fwd.hpp is rank ∞ because it is an executable
+
namespace tmwa
{
-// meh, add more when I feel like it
+namespace char_
+{
+ struct CharConf;
+ struct CharLanConf;
+ struct InterConf;
+
+ struct AuthFifoEntry;
+ struct mmo_map_server;
+
+ struct accreg;
+ // meh, add more when I feel like it
+} // namespace char_
} // namespace tmwa
diff --git a/src/char/globals.cpp b/src/char/globals.cpp
new file mode 100644
index 0000000..6733ad5
--- /dev/null
+++ b/src/char/globals.cpp
@@ -0,0 +1,68 @@
+#include "globals.hpp"
+// globals.cpp - Evil global variables for tmwa-char.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../compat/time_t.hpp"
+
+#include "../generic/db.hpp"
+
+#include "../proto2/net-Storage.hpp"
+
+#include "char.hpp"
+#include "char_conf.hpp"
+#include "char_lan_conf.hpp"
+#include "inter.hpp"
+#include "inter_conf.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+ namespace char_
+ {
+ CharConf char_conf;
+ CharLanConf char_lan_conf;
+ InterConf inter_conf;
+ Array<mmo_map_server, MAX_MAP_SERVERS> server;
+ Array<Session *, MAX_MAP_SERVERS> server_session;
+ // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed
+ Array<int, MAX_MAP_SERVERS> server_freezeflag;
+ Session *login_session, *char_session;
+ const CharName WISP_SERVER_NAME = stringish<CharName>("Server"_s);
+ std::array<AuthFifoEntry, 256> auth_fifo;
+ decltype(auth_fifo)::iterator auth_fifo_iter = auth_fifo.begin();
+ CharId char_id_count = wrap<CharId>(150000);
+ std::vector<CharPair> char_keys;
+ std::vector<GM_Account> gm_accounts;
+ // same size of char_keys, and id value of current server (or -1)
+ std::vector<Session *> online_chars;
+ // to update online files when we receiving information from a server (not less than 8 seconds)
+ TimeT update_online;
+ // For forked DB writes
+ pid_t pid = 0;
+
+ Map<AccountId, accreg> accreg_db;
+
+ Map<PartyId, PartyMost> party_db;
+ PartyId party_newid = wrap<PartyId>(100_u32);
+
+ Map<AccountId, Storage> storage_db;
+ } // namespace char_
+} // namespace tmwa
diff --git a/src/char/globals.hpp b/src/char/globals.hpp
new file mode 100644
index 0000000..2df3f21
--- /dev/null
+++ b/src/char/globals.hpp
@@ -0,0 +1,59 @@
+#pragma once
+// globals.hpp - Evil global variables for tmwa-char.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include <sys/types.h>
+
+#include <array>
+#include <vector>
+
+#include "consts.hpp"
+
+
+namespace tmwa
+{
+ namespace char_
+ {
+ extern CharConf char_conf;
+ extern CharLanConf char_lan_conf;
+ extern InterConf inter_conf;
+ extern Array<mmo_map_server, MAX_MAP_SERVERS> server;
+ extern Array<Session *, MAX_MAP_SERVERS> server_session;
+ extern Array<int, MAX_MAP_SERVERS> server_freezeflag;
+ extern Session *login_session, *char_session;
+ extern const CharName WISP_SERVER_NAME;
+ extern std::array<AuthFifoEntry, 256> auth_fifo;
+ extern AuthFifoEntry *auth_fifo_iter;
+ extern CharId char_id_count;
+ extern std::vector<CharPair> char_keys;
+ extern std::vector<GM_Account> gm_accounts;
+ extern std::vector<Session *> online_chars;
+ extern TimeT update_online;
+ extern pid_t pid;
+
+ extern Map<AccountId, accreg> accreg_db;
+
+ extern Map<PartyId, PartyMost> party_db;
+ extern PartyId party_newid;
+
+ extern Map<AccountId, Storage> storage_db;
+ } // namespace char_
+} // namespace tmwa
diff --git a/src/char/int_party.cpp b/src/char/int_party.cpp
index c9ab5a4..5ee65ad 100644
--- a/src/char/int_party.cpp
+++ b/src/char/int_party.cpp
@@ -29,33 +29,32 @@
#include "../generic/db.hpp"
#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
#include "../io/lock.hpp"
#include "../io/read.hpp"
#include "../io/write.hpp"
-#include "../net/packets.hpp"
-
#include "../proto2/char-map.hpp"
-#include "../mmo/extract.hpp"
#include "../mmo/ids.hpp"
-#include "../mmo/mmo.hpp"
+
+#include "../high/extract_mmo.hpp"
+#include "../high/mmo.hpp"
+
+#include "../wire/packets.hpp"
#include "char.hpp"
+#include "globals.hpp"
#include "inter.hpp"
+#include "inter_conf.hpp"
#include "../poison.hpp"
namespace tmwa
{
-AString party_txt = "save/party.txt"_s;
-
-static
-Map<PartyId, PartyMost> party_db;
-static
-PartyId party_newid = wrap<PartyId>(100_u32);
-
+namespace char_
+{
static
void mapif_party_broken(PartyId party_id, int flag);
static
@@ -89,9 +88,10 @@ AString inter_party_tostr(PartyPair p)
return AString(str);
}
+} // namespace char_
static
-bool extract(XString str, PartyPair *pp)
+bool impl_extract(XString str, PartyPair *pp)
{
PartyPair& p = *pp;
@@ -129,6 +129,8 @@ bool extract(XString str, PartyPair *pp)
return true;
}
+namespace char_
+{
static
void party_check_deleted_init(PartyPair p)
{
@@ -153,7 +155,7 @@ void party_check_deleted_init(PartyPair p)
// パーティデータのロード
void inter_party_init(void)
{
- io::ReadFile in(party_txt);
+ io::ReadFile in(inter_conf.party_txt);
if (!in.is_open())
return;
@@ -171,19 +173,20 @@ void inter_party_init(void)
}
PartyMost pm;
- PartyPair pp;
- pp.party_most = &pm;
+ PartyPair pp{PartyId(), borrow(pm)};
if (extract(line, &pp) && pp.party_id)
{
if (party_newid < next(pp.party_id))
party_newid = next(pp.party_id);
party_check_deleted_init(pp);
party_db.insert(pp.party_id, pm);
+ // note: this is still referring to the noncanonical copy of
+ // the PartyMost pointer. This is okay, though.
party_check_empty(pp);
}
else
{
- PRINTF("int_party: broken data [%s] line %d\n"_fmt, party_txt,
+ PRINTF("int_party: broken data [%s] line %d\n"_fmt, inter_conf.party_txt,
c + 1);
}
c++;
@@ -201,18 +204,16 @@ void inter_party_save_sub(PartyPair data, io::WriteFile& fp)
// パーティーデータのセーブ
int inter_party_save(void)
{
- io::WriteLock fp(party_txt);
+ io::WriteLock fp(inter_conf.party_txt);
if (!fp.is_open())
{
PRINTF("int_party: cant write [%s] !!! data is lost !!!\n"_fmt,
- party_txt);
+ inter_conf.party_txt);
return 1;
}
for (auto& pair : party_db)
{
- PartyPair tmp;
- tmp.party_id = pair.first;
- tmp.party_most = &pair.second;
+ PartyPair tmp{pair.first, borrow(pair.second)};
inter_party_save_sub(tmp, fp);
}
@@ -221,23 +222,21 @@ int inter_party_save(void)
// パーティ名検索用
static
-void search_partyname_sub(PartyPair p, PartyName str, PartyPair *dst)
+void search_partyname_sub(PartyPair p, PartyName str, Borrowed<Option<PartyPair>> dst)
{
if (p->name == str)
- *dst = p;
+ *dst = Some(p);
}
// パーティ名検索
static
-PartyPair search_partyname(PartyName str)
+Option<PartyPair> search_partyname(PartyName str)
{
- PartyPair p;
+ Option<PartyPair> p = None;
for (auto& pair : party_db)
{
- PartyPair tmp;
- tmp.party_id = pair.first;
- tmp.party_most = &pair.second;
- search_partyname_sub(tmp, str, &p);
+ PartyPair tmp{pair.first, borrow(pair.second)};
+ search_partyname_sub(tmp, str, borrow(p));
}
return p;
@@ -262,11 +261,11 @@ int party_check_exp_share(PartyPair p)
}
}
- return (maxlv == 0 || maxlv - minlv <= party_share_level);
+ return (maxlv == 0 || maxlv - minlv <= inter_conf.party_share_level);
}
// パーティが空かどうかチェック
-int party_check_empty(PartyPair p)
+int party_check_empty(const PartyPair p)
{
int i;
@@ -313,9 +312,7 @@ void party_check_conflict(PartyId party_id, AccountId account_id, CharName nick)
{
for (auto& pair : party_db)
{
- PartyPair tmp;
- tmp.party_id = pair.first;
- tmp.party_most = &pair.second;
+ PartyPair tmp{pair.first, borrow(pair.second)};
party_check_conflict_sub(tmp,
party_id, account_id, nick);
}
@@ -326,23 +323,27 @@ void party_check_conflict(PartyId party_id, AccountId account_id, CharName nick)
// パーティ作成可否
static
-void mapif_party_created(Session *s, AccountId account_id, PartyPair p)
+void mapif_party_created(Session *s, AccountId account_id, Option<PartyPair> p_)
{
Packet_Fixed<0x3820> fixed_20;
fixed_20.account_id = account_id;
- if (p)
- {
- fixed_20.error = 0;
- fixed_20.party_id = p.party_id;
- fixed_20.party_name = p->name;
- PRINTF("int_party: created! %d %s\n"_fmt, p.party_id, p->name);
- }
- else
+ OMATCH_BEGIN (p_)
{
- fixed_20.error = 1;
- fixed_20.party_id = PartyId();
- fixed_20.party_name = stringish<PartyName>("error"_s);
+ OMATCH_CASE_SOME (p)
+ {
+ fixed_20.error = 0;
+ fixed_20.party_id = p.party_id;
+ fixed_20.party_name = p->name;
+ PRINTF("int_party: created! %d %s\n"_fmt, p.party_id, p->name);
+ }
+ OMATCH_CASE_NONE ()
+ {
+ fixed_20.error = 1;
+ fixed_20.party_id = PartyId();
+ fixed_20.party_name = stringish<PartyName>("error"_s);
+ }
}
+ OMATCH_END ();
send_fpacket<0x3820, 35>(s, fixed_20);
}
@@ -359,7 +360,7 @@ void mapif_party_noinfo(Session *s, PartyId party_id)
// パーティ情報まとめ送り
static
-void mapif_party_info(Session *s, PartyPair p)
+void mapif_party_info(Session *s, const PartyPair p)
{
Packet_Head<0x3821> head_21;
head_21.party_id = p.party_id;
@@ -490,22 +491,21 @@ void mapif_parse_CreateParty(Session *s, AccountId account_id, PartyName name, C
if (!name.is_print())
{
PRINTF("int_party: illegal party name [%s]\n"_fmt, name);
- mapif_party_created(s, account_id, PartyPair());
+ mapif_party_created(s, account_id, None);
return;
}
}
- if (search_partyname(name))
+ if (search_partyname(name).is_some())
{
PRINTF("int_party: same name party exists [%s]\n"_fmt, name);
- mapif_party_created(s, account_id, PartyPair());
+ mapif_party_created(s, account_id, None);
return;
}
+
PartyMost p {};
- PartyPair pp;
- pp.party_most = &p;
party_newid = next(party_newid);
- pp.party_id = party_newid;
+ PartyPair pp{party_newid, borrow(p)};
p.name = name;
p.exp = 0;
p.item = 0;
@@ -518,7 +518,8 @@ void mapif_parse_CreateParty(Session *s, AccountId account_id, PartyName name, C
party_db.insert(pp.party_id, p);
- mapif_party_created(s, account_id, pp);
+ // pointer to noncanonical version
+ mapif_party_created(s, account_id, Some(pp));
mapif_party_info(s, pp);
}
@@ -526,12 +527,19 @@ void mapif_parse_CreateParty(Session *s, AccountId account_id, PartyName name, C
static
void mapif_parse_PartyInfo(Session *s, PartyId party_id)
{
- PartyPair p;
- p.party_most = party_db.search(party_id);
- if (p)
- mapif_party_info(s, p);
- else
- mapif_party_noinfo(s, party_id);
+ Option<P<PartyMost>> maybe_party_most = party_db.search(party_id);
+ OMATCH_BEGIN (maybe_party_most)
+ {
+ OMATCH_CASE_SOME (party_most)
+ {
+ mapif_party_info(s, PartyPair{party_id, party_most});
+ }
+ OMATCH_CASE_NONE ()
+ {
+ mapif_party_noinfo(s, party_id);
+ }
+ }
+ OMATCH_END ();
}
// パーティ追加要求
@@ -539,13 +547,13 @@ static
void mapif_parse_PartyAddMember(Session *s, PartyId party_id, AccountId account_id,
CharName nick, MapName map, int lv)
{
- PartyPair p;
- p.party_most = party_db.search(party_id);
- if (!p)
- {
- mapif_party_memberadded(s, party_id, account_id, 1);
- return;
- }
+ Option<P<PartyMost>> maybe_party_most = party_db.search(party_id);
+ P<PartyMost> party_most = TRY_UNWRAP(maybe_party_most,
+ {
+ mapif_party_memberadded(s, party_id, account_id, 1);
+ return;
+ });
+ PartyPair p{party_id, party_most};
for (int i = 0; i < MAX_PARTY; i++)
{
@@ -580,10 +588,7 @@ static
void mapif_parse_PartyChangeOption(Session *s, PartyId party_id, AccountId account_id,
int exp, int item)
{
- PartyPair p;
- p.party_most = party_db.search(party_id);
- if (!p)
- return;
+ PartyPair p{party_id, TRY_UNWRAP(party_db.search(party_id), return)};
p->exp = exp;
int flag = 0;
@@ -601,10 +606,8 @@ void mapif_parse_PartyChangeOption(Session *s, PartyId party_id, AccountId accou
// パーティ脱退要求
void mapif_parse_PartyLeave(Session *, PartyId party_id, AccountId account_id)
{
- PartyPair p;
- p.party_most = party_db.search(party_id);
- if (!p)
- return;
+ PartyPair p{party_id, TRY_UNWRAP(party_db.search(party_id), return)};
+
for (int i = 0; i < MAX_PARTY; i++)
{
if (p->member[i].account_id != account_id)
@@ -623,10 +626,7 @@ static
void mapif_parse_PartyChangeMap(Session *s, PartyId party_id, AccountId account_id,
MapName map, int online, int lv)
{
- PartyPair p;
- p.party_most = party_db.search(party_id);
- if (!p)
- return;
+ PartyPair p{party_id, TRY_UNWRAP(party_db.search(party_id), return)};
for (int i = 0; i < MAX_PARTY; i++)
{
@@ -822,4 +822,5 @@ void inter_party_leave(PartyId party_id, AccountId account_id)
{
mapif_parse_PartyLeave(nullptr, party_id, account_id);
}
+} // namespace char_
} // namespace tmwa
diff --git a/src/char/int_party.hpp b/src/char/int_party.hpp
index f33fc0d..d66afbf 100644
--- a/src/char/int_party.hpp
+++ b/src/char/int_party.hpp
@@ -22,21 +22,16 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../net/fwd.hpp"
-
-#include "../mmo/fwd.hpp"
-
namespace tmwa
{
+namespace char_
+{
void inter_party_init(void);
int inter_party_save(void);
RecvResult inter_party_parse_frommap(Session *ms, uint16_t);
void inter_party_leave(PartyId party_id, AccountId account_id);
-
-extern AString party_txt;
+} // namespace char_
} // namespace tmwa
diff --git a/src/char/int_storage.cpp b/src/char/int_storage.cpp
index 76eff34..32af231 100644
--- a/src/char/int_storage.cpp
+++ b/src/char/int_storage.cpp
@@ -28,30 +28,30 @@
#include "../generic/db.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+#include "../io/extract.hpp"
#include "../io/lock.hpp"
#include "../io/read.hpp"
#include "../io/write.hpp"
-#include "../net/packets.hpp"
-
#include "../proto2/char-map.hpp"
-#include "../mmo/extract.hpp"
-#include "../mmo/mmo.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
+
+#include "../high/extract_mmo.hpp"
+#include "../high/mmo.hpp"
+
+#include "../wire/packets.hpp"
+
+#include "globals.hpp"
+#include "inter_conf.hpp"
#include "../poison.hpp"
namespace tmwa
{
-// ファイル名のデフォルト
-// inter_config_read()で再設定される
-AString storage_txt = "save/storage.txt"_s;
-
-static
-Map<AccountId, Storage> storage_db;
-
+namespace char_
+{
// 倉庫データを文字列に変換
static
AString storage_tostr(Storage *p)
@@ -90,10 +90,11 @@ AString storage_tostr(Storage *p)
return AString();
return AString(str);
}
+} // namespace char_
// 文字列を倉庫データに変換
static
-bool extract(XString str, Storage *p)
+bool impl_extract(XString str, Storage *p)
{
std::vector<Item> storage_items;
if (!extract(str,
@@ -116,15 +117,13 @@ bool extract(XString str, Storage *p)
return true;
}
+namespace char_
+{
// アカウントから倉庫データインデックスを得る(新規倉庫追加可能)
-Storage *account2storage(AccountId account_id)
+Borrowed<Storage> account2storage(AccountId account_id)
{
- Storage *s = storage_db.search(account_id);
- if (s == nullptr)
- {
- s = storage_db.init(account_id);
- s->account_id = account_id;
- }
+ P<Storage> s = storage_db.init(account_id);
+ s->account_id = account_id;
return s;
}
@@ -134,10 +133,10 @@ void inter_storage_init(void)
{
int c = 0;
- io::ReadFile in(storage_txt);
+ io::ReadFile in(inter_conf.storage_txt);
if (!in.is_open())
{
- PRINTF("cant't read : %s\n"_fmt, storage_txt);
+ PRINTF("cant't read : %s\n"_fmt, inter_conf.storage_txt);
return;
}
@@ -152,7 +151,7 @@ void inter_storage_init(void)
else
{
PRINTF("int_storage: broken data [%s] line %d\n"_fmt,
- storage_txt, c);
+ inter_conf.storage_txt, c);
}
c++;
}
@@ -170,12 +169,12 @@ void inter_storage_save_sub(Storage *data, io::WriteFile& fp)
// 倉庫データを書き込む
int inter_storage_save(void)
{
- io::WriteLock fp(storage_txt);
+ io::WriteLock fp(inter_conf.storage_txt);
if (!fp.is_open())
{
PRINTF("int_storage: cant write [%s] !!! data is lost !!!\n"_fmt,
- storage_txt);
+ inter_conf.storage_txt);
return 1;
}
for (auto& pair : storage_db)
@@ -196,7 +195,7 @@ void inter_storage_delete(AccountId account_id)
static
void mapif_load_storage(Session *ss, AccountId account_id)
{
- Storage *st = account2storage(account_id);
+ P<Storage> st = account2storage(account_id);
Packet_Payload<0x3810> payload_10;
payload_10.account_id = account_id;
payload_10.storage = *st;
@@ -240,11 +239,10 @@ RecvResult mapif_parse_SaveStorage(Session *ss)
if (rv != RecvResult::Complete)
return rv;
- Storage *st;
AccountId account_id = payload.account_id;
{
- st = account2storage(account_id);
+ P<Storage> st = account2storage(account_id);
*st = payload.storage;
mapif_save_storage_ack(ss, account_id);
}
@@ -273,4 +271,5 @@ RecvResult inter_storage_parse_frommap(Session *ms, uint16_t packet_id)
}
return rv;
}
+} // namespace char_
} // namespace tmwa
diff --git a/src/char/int_storage.hpp b/src/char/int_storage.hpp
index b8ec9db..3741061 100644
--- a/src/char/int_storage.hpp
+++ b/src/char/int_storage.hpp
@@ -22,21 +22,16 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../net/fwd.hpp"
-
-#include "../mmo/fwd.hpp"
-
namespace tmwa
{
+namespace char_
+{
void inter_storage_init(void);
int inter_storage_save(void);
void inter_storage_delete(AccountId account_id);
-Storage *account2storage(AccountId account_id);
+Borrowed<Storage> account2storage(AccountId account_id);
RecvResult inter_storage_parse_frommap(Session *ms, uint16_t);
-
-extern AString storage_txt;
+} // namespace char_
} // namespace tmwa
diff --git a/src/char/inter.cpp b/src/char/inter.cpp
index f757991..3bf3bfc 100644
--- a/src/char/inter.cpp
+++ b/src/char/inter.cpp
@@ -34,18 +34,24 @@
#include "../generic/db.hpp"
#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
#include "../io/lock.hpp"
#include "../io/read.hpp"
+#include "../io/span.hpp"
#include "../io/write.hpp"
-#include "../net/packets.hpp"
+#include "../mmo/config_parse.hpp"
#include "../proto2/char-map.hpp"
-#include "../mmo/extract.hpp"
-#include "../mmo/mmo.hpp"
+#include "../high/extract_mmo.hpp"
+#include "../high/mmo.hpp"
+
+#include "../wire/packets.hpp"
#include "char.hpp"
+#include "globals.hpp"
+#include "inter_conf.hpp"
#include "int_party.hpp"
#include "int_storage.hpp"
@@ -54,20 +60,8 @@
namespace tmwa
{
-static
-AString accreg_txt = "save/accreg.txt"_s;
-
-struct accreg
+namespace char_
{
- AccountId account_id;
- int reg_num;
- Array<GlobalReg, ACCOUNT_REG_NUM> reg;
-};
-static
-Map<AccountId, struct accreg> accreg_db;
-
-int party_share_level = 10;
-
//--------------------------------------------------------
// アカウント変数を文字列へ変換
@@ -84,7 +78,7 @@ AString inter_accreg_tostr(struct accreg *reg)
// アカウント変数を文字列から変換
static
-bool extract(XString str, struct accreg *reg)
+bool impl_extract(XString str, struct accreg *reg)
{
std::vector<GlobalReg> vars;
if (!extract(str,
@@ -108,7 +102,7 @@ void inter_accreg_init(void)
{
int c = 0;
- io::ReadFile in(accreg_txt);
+ io::ReadFile in(inter_conf.accreg_txt);
if (!in.is_open())
return;
AString line;
@@ -121,7 +115,7 @@ void inter_accreg_init(void)
}
else
{
- PRINTF("inter: accreg: broken data [%s] line %d\n"_fmt, accreg_txt,
+ PRINTF("inter: accreg: broken data [%s] line %d\n"_fmt, inter_conf.accreg_txt,
c);
}
c++;
@@ -143,11 +137,11 @@ void inter_accreg_save_sub(struct accreg *reg, io::WriteFile& fp)
static
int inter_accreg_save(void)
{
- io::WriteLock fp(accreg_txt);
+ io::WriteLock fp(inter_conf.accreg_txt);
if (!fp.is_open())
{
PRINTF("int_accreg: cant write [%s] !!! data is lost !!!\n"_fmt,
- accreg_txt);
+ inter_conf.accreg_txt);
return 1;
}
for (auto& pair : accreg_db)
@@ -156,36 +150,6 @@ int inter_accreg_save(void)
return 0;
}
-bool inter_config(XString w1, ZString w2)
-{
- {
- if (w1 == "storage_txt"_s)
- {
- storage_txt = w2;
- }
- else if (w1 == "party_txt"_s)
- {
- party_txt = w2;
- }
- else if (w1 == "accreg_txt"_s)
- {
- accreg_txt = w2;
- }
- else if (w1 == "party_share_level"_s)
- {
- party_share_level = atoi(w2.c_str());
- if (party_share_level < 0)
- party_share_level = 0;
- }
- else
- {
- return false;
- }
- }
-
- return true;
-}
-
// セーブ
void inter_save(void)
{
@@ -264,12 +228,12 @@ void mapif_account_reg(Session *s, AccountId account_id, const std::vector<Packe
static
void mapif_account_reg_reply(Session *s, AccountId account_id)
{
- struct accreg *reg = accreg_db.search(account_id);
+ Option<P<struct accreg>> reg_ = accreg_db.search(account_id);
Packet_Head<0x3804> head_04;
head_04.account_id = account_id;
std::vector<Packet_Repeat<0x3804>> repeat_04;
- if (reg)
+ OMATCH_BEGIN_SOME (reg, reg_)
{
repeat_04.resize(reg->reg_num);
assert (reg->reg_num < ACCOUNT_REG_NUM);
@@ -279,6 +243,7 @@ void mapif_account_reg_reply(Session *s, AccountId account_id)
repeat_04[j].value = reg->reg[j].value;
}
}
+ OMATCH_END ();
send_vpacket<0x3804, 8, 36>(s, head_04, repeat_04);
}
@@ -408,13 +373,9 @@ RecvResult mapif_parse_AccReg(Session *s)
if (rv != RecvResult::Complete)
return rv;
- struct accreg *reg = accreg_db.search(head.account_id);
-
- if (reg == nullptr)
+ P<struct accreg> reg = accreg_db.init(head.account_id);
{
- AccountId account_id = head.account_id;
- reg = accreg_db.init(account_id);
- reg->account_id = account_id;
+ reg->account_id = head.account_id;
}
size_t jlim = std::min(repeat.size(), ACCOUNT_REG_NUM);
@@ -488,4 +449,5 @@ RecvResult inter_parse_frommap(Session *ms, uint16_t packet_id)
return rv;
}
+} // namespace char_
} // namespace tmwa
diff --git a/src/char/inter.hpp b/src/char/inter.hpp
index 19900f9..c641254 100644
--- a/src/char/inter.hpp
+++ b/src/char/inter.hpp
@@ -22,17 +22,27 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
+#include "../generic/array.hpp"
-#include "../net/fwd.hpp"
+#include "../mmo/consts.hpp"
+#include "../mmo/ids.hpp"
+
+#include "../proto2/net-GlobalReg.hpp"
namespace tmwa
{
-bool inter_config(XString key, ZString value);
+namespace char_
+{
+struct accreg
+{
+ AccountId account_id;
+ int reg_num;
+ Array<GlobalReg, ACCOUNT_REG_NUM> reg;
+};
+
void inter_init2();
void inter_save(void);
RecvResult inter_parse_frommap(Session *ms, uint16_t packet_id);
-
-extern int party_share_level;
+} // namespace char_
} // namespace tmwa
diff --git a/src/char/main.cpp b/src/char/main.cpp
index 3648a74..7d6fee3 100644
--- a/src/char/main.cpp
+++ b/src/char/main.cpp
@@ -17,7 +17,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../mmo/core.hpp"
+#include "../high/core.hpp"
#include "char.hpp"
diff --git a/src/compat/attr.hpp b/src/compat/attr.hpp
index 238a5d5..5ebef6d 100644
--- a/src/compat/attr.hpp
+++ b/src/compat/attr.hpp
@@ -1,7 +1,7 @@
#pragma once
// attr.hpp - Attributes.
//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -28,4 +28,6 @@ namespace tmwa
#else
# define FALLTHROUGH /* fallthrough */
#endif
+
+#define JOIN(a, b) a##b
} // namespace tmwa
diff --git a/src/compat/borrow.hpp b/src/compat/borrow.hpp
new file mode 100644
index 0000000..0ea6a26
--- /dev/null
+++ b/src/compat/borrow.hpp
@@ -0,0 +1,111 @@
+#pragma once
+// borrow.hpp - a non-null, unowned, pointer
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include <cstdlib>
+
+#include <iterator>
+
+#include "option.hpp"
+
+// unit tests currention in option_test.cpp
+
+namespace tmwa
+{
+ // TODO see if const-by-default is a thing
+ template<class T>
+ class Borrowed
+ {
+ T *stupid;
+ public:
+ Borrowed() = delete;
+ explicit
+ Borrowed(T *p) : stupid(p)
+ {
+ if (!p) abort();
+ }
+
+ T& operator *() const
+ {
+ return *stupid;
+ }
+
+ T *operator ->() const
+ {
+ return stupid;
+ }
+
+ template<class U>
+ Borrowed<U> downcast_to() const
+ {
+ static_assert(std::is_base_of<T, U>::value, "base check");
+ static_assert(!std::is_same<T, U>::value, "same check");
+ return Borrowed<U>(static_cast<U *>(stupid));
+ }
+
+ template<class U>
+ Borrowed<U> upcast_to() const
+ {
+ static_assert(std::is_base_of<U, T>::value, "base check");
+ static_assert(!std::is_same<T, U>::value, "same check");
+ return Borrowed<U>(stupid);
+ }
+
+ friend bool operator == (Borrowed l, Borrowed r)
+ {
+ return l.stupid == r.stupid;
+ }
+ friend bool operator != (Borrowed l, Borrowed r)
+ {
+ return !(l == r);
+ }
+ };
+
+ namespace option
+ {
+ template<class T>
+ class OptionRepr<Borrowed<T>>
+ {
+ T *stupider;
+ public:
+ void set_none() { stupider = nullptr; }
+ void post_set_some() {}
+ bool is_some() const { return stupider != nullptr; }
+ Borrowed<T> *ptr() { return reinterpret_cast<Borrowed<T> *>(&stupider); }
+ const Borrowed<T> *ptr() const { return reinterpret_cast<const Borrowed<T> *>(&stupider); }
+ };
+ }
+
+ template<class T>
+ using P = Borrowed<T>;
+
+ template<class T>
+ Borrowed<T> borrow(T& ref)
+ {
+ return Borrowed<T>(&ref);
+ }
+
+ template<class T>
+ T *as_raw_pointer(Option<Borrowed<T>> ptr)
+ {
+ return &*TRY_UNWRAP(ptr, return nullptr);
+ }
+} // namespace tmwa
diff --git a/src/compat/borrow.py b/src/compat/borrow.py
new file mode 100644
index 0000000..58cd19b
--- /dev/null
+++ b/src/compat/borrow.py
@@ -0,0 +1,18 @@
+class Borrowed(object):
+ __slots__ = ('_value')
+ name = 'tmwa::Borrowed'
+ enabled = True
+
+ def __init__(self, value):
+ self._value = value['stupid']
+
+ def to_string(self):
+ return self._value
+
+ test_extra = '''
+ static int borrow_thingy;
+ '''
+
+ tests = [
+ ('tmwa::borrow(borrow_thingy)', '<borrow_thingy>'),
+ ]
diff --git a/src/compat/fwd.hpp b/src/compat/fwd.hpp
index 45f3c24..3fa0dd2 100644
--- a/src/compat/fwd.hpp
+++ b/src/compat/fwd.hpp
@@ -20,8 +20,23 @@
#include "../sanity.hpp"
+#include "../ints/fwd.hpp" // rank 1
+#include "../strings/fwd.hpp" // rank 1
+// compat/fwd.hpp is rank 2
+
namespace tmwa
{
+ namespace option
+ {
+ template<class T>
+ class Option;
+ }
+ using option::Option;
+
+ template<class T>
+ class Borrowed;
+
+ struct TimeT;
// meh, add more when I feel like it
} // namespace tmwa
diff --git a/src/compat/nullpo.hpp b/src/compat/nullpo.hpp
index 5be674a..38c8e92 100644
--- a/src/compat/nullpo.hpp
+++ b/src/compat/nullpo.hpp
@@ -52,13 +52,15 @@ bool nullpo_chk(const char *file, int line, const char *func,
const void *target);
template<class T>
-bool nullpo_chk(const char *file, int line, const char *func, T target)
-{
- return nullpo_chk(file, line, func, target.operator->());
-}
+bool nullpo_chk(const char *, int, const char *, Borrowed<T>) = delete;
template<class T>
bool nullpo_chk(const char *file, int line, const char *func, T *target)
{
return nullpo_chk(file, line, func, static_cast<const void *>(target));
}
+template<class T>
+bool nullpo_chk(const char *file, int line, const char *func, T target)
+{
+ return nullpo_chk(file, line, func, target.operator->());
+}
} // namespace tmwa
diff --git a/src/generic/operators.hpp b/src/compat/operators.hpp
index bb05765..bb05765 100644
--- a/src/generic/operators.hpp
+++ b/src/compat/operators.hpp
diff --git a/src/compat/option.hpp b/src/compat/option.hpp
new file mode 100644
index 0000000..b6e7655
--- /dev/null
+++ b/src/compat/option.hpp
@@ -0,0 +1,469 @@
+#pragma once
+// option.hpp - a data type that may or may not exist
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include <cassert>
+
+#include <utility>
+
+
+namespace tmwa
+{
+namespace option
+{
+ enum option_hack_type { option_hack_value };
+
+ template<class T>
+ class OptionRepr
+ {
+ __attribute__((aligned(alignof(T))))
+ char _data[sizeof(T)];
+ bool _some;
+ public:
+ void set_none() { _some = false; }
+ // maybe add pre_set_some if it is useful for other specializations
+ void post_set_some() { _some = true; }
+ bool is_some() const { return _some; }
+ T *ptr() { return reinterpret_cast<T *>(&_data); }
+ const T *ptr() const { return reinterpret_cast<const T *>(&_data); }
+ };
+ template<class T>
+ class OptionRepr<T&>;
+ template<class T>
+ class OptionRepr<T&&>;
+
+ template<class T>
+ Option<T> None(option_hack_type=option_hack_value)
+ {
+ return None;
+ }
+
+ template<class T>
+ Option<T> Some(T v)
+ {
+ Option<T> rv = None;
+ rv.do_construct(std::move(v));
+ return rv;
+ }
+
+ // TODO all *_or and *_set methods should have a lazy version too
+ template<class T>
+ class Option
+ {
+ static_assert(std::is_pod<OptionRepr<T>>::value, "repr should itself be pod, copies are done manually");
+ OptionRepr<T> repr;
+
+ friend Option<T> Some<T>(T);
+
+ void do_init()
+ {
+ repr.set_none();
+ }
+ template<class... U>
+ void do_construct(U&&... u)
+ {
+ new(repr.ptr()) T(std::forward<U>(u)...);
+ repr.post_set_some();
+ }
+ void do_move_construct(Option&& r)
+ {
+ if (r.repr.is_some())
+ {
+ do_construct(std::move(*r.repr.ptr()));
+ r.do_destruct();
+ }
+ }
+ void do_copy_construct(const Option& r)
+ {
+ if (r.repr.is_some())
+ {
+ do_construct(*r.repr.ptr());
+ }
+ }
+ void do_move_assign(Option&& r)
+ {
+ if (repr.is_some())
+ {
+ if (r.repr.is_some())
+ {
+ *repr.ptr() = std::move(*r.repr.ptr());
+ }
+ else
+ {
+ do_destruct();
+ }
+ return;
+ }
+ else
+ {
+ do_move_construct(std::move(r));
+ }
+ }
+ void do_copy_assign(const Option& r)
+ {
+ if (repr.is_some())
+ {
+ if (r.repr.is_some())
+ {
+ *repr.ptr() = *r.repr.ptr();
+ }
+ else
+ {
+ do_destruct();
+ }
+ return;
+ }
+ else
+ {
+ do_copy_construct(r);
+ }
+ }
+ void do_destruct()
+ {
+ repr.ptr()->~T();
+ repr.set_none();
+ }
+ public:
+ Option() = delete;
+ Option(Option(*)(option_hack_type))
+ {
+ do_init();
+ }
+ Option(Option&& r)
+ {
+ do_init();
+ do_move_construct(std::move(r));
+ }
+ Option(const Option& r)
+ {
+ do_init();
+ do_copy_construct(r);
+ }
+ Option& operator = (Option&& r)
+ {
+ do_move_assign(std::move(r));
+ return *this;
+ }
+ Option& operator = (const Option& r)
+ {
+ do_copy_assign(r);
+ return *this;
+ }
+ ~Option()
+ {
+ if (repr.is_some())
+ {
+ do_destruct();
+ }
+ }
+
+ T move_or(T def)
+ {
+ if (repr.is_some())
+ {
+ def = std::move(*repr.ptr());
+ do_destruct();
+ }
+ return def;
+ }
+ T copy_or(T def) const
+ {
+ if (repr.is_some())
+ {
+ def = *repr.ptr();
+ }
+ return def;
+ }
+ T& ref_or(T& def)
+ {
+ return repr.is_some() ? *repr.ptr() : def;
+ }
+ const T& ref_or(const T& def) const
+ {
+ return repr.is_some() ? *repr.ptr() : def;
+ }
+ T *ptr_or(T *def)
+ {
+ return repr.is_some() ? repr.ptr() : def;
+ }
+ const T *ptr_or(const T *def) const
+ {
+ return repr.is_some() ? repr.ptr() : def;
+ }
+ bool is_some() const
+ {
+ return repr.is_some();
+ }
+ bool is_none() const
+ {
+ return !is_some();
+ }
+
+ template<class F>
+ auto move_map(F&& f) -> Option<decltype(std::forward<F>(f)(std::move(*repr.ptr())))>
+ {
+ if (repr.is_some())
+ {
+ auto rv = Some(std::forward<F>(f)(std::move(*repr.ptr())));
+ do_destruct();
+ return rv;
+ }
+ else
+ {
+ return None;
+ }
+ }
+ template<class F>
+ auto map(F&& f) -> Option<decltype(std::forward<F>(f)(*repr.ptr()))>
+ {
+ if (repr.is_some())
+ {
+ return Some(std::forward<F>(f)(*repr.ptr()));
+ }
+ else
+ {
+ return None;
+ }
+ }
+ template<class F>
+ auto map(F&& f) const -> Option<decltype(std::forward<F>(f)(*repr.ptr()))>
+ {
+ if (repr.is_some())
+ {
+ return Some(std::forward<F>(f)(*repr.ptr()));
+ }
+ else
+ {
+ return None;
+ }
+ }
+ // shortcut for flatten(o.map()) that avoids explicit Some's inside
+ template<class B, class F>
+ auto cmap(B&& b, F&& f) const -> Option<decltype(std::forward<F>(f)(*repr.ptr()))>
+ {
+ if (repr.is_some() && std::forward<B>(b)(*repr.ptr()))
+ {
+ return Some(std::forward<F>(f)(*repr.ptr()));
+ }
+ else
+ {
+ return None;
+ }
+ }
+ // wanting members is *so* common
+ template<class M, class B>
+ Option<M> pmd_get(const M B::*pmd) const
+ {
+ if (repr.is_some())
+ {
+ return Some((*repr.ptr()).*pmd);
+ }
+ else
+ {
+ return None;
+ }
+ }
+ template<class M, class B>
+ void pmd_set(M B::*pmd, M value)
+ {
+ if (repr.is_some())
+ {
+ ((*repr.ptr()).*pmd) = std::move(value);
+ }
+ }
+ template<class M, class B>
+ Option<M> pmd_pget(const M B::*pmd) const
+ {
+ if (repr.is_some())
+ {
+ return Some((**repr.ptr()).*pmd);
+ }
+ else
+ {
+ return None;
+ }
+ }
+ template<class M, class B>
+ void pmd_pset(M B::*pmd, M value)
+ {
+ if (repr.is_some())
+ {
+ ((**repr.ptr()).*pmd) = std::move(value);
+ }
+ }
+ };
+
+ template<class T>
+ struct most_flattened_type
+ {
+ using type = T;
+
+ static Option<type> flatten(Option<T> o)
+ {
+ return std::move(o);
+ }
+ };
+ template<class T>
+ struct most_flattened_type<Option<T>>
+ {
+ using type = typename most_flattened_type<T>::type;
+
+ static Option<type> flatten(Option<Option<T>> o)
+ {
+ return most_flattened_type<T>::flatten(o.move_or(None));
+ }
+ };
+
+ template<class T>
+ Option<typename most_flattened_type<T>::type> flatten(Option<T> o)
+ {
+ return most_flattened_type<T>::flatten(std::move(o));
+ }
+
+ template<class T>
+ bool operator == (const Option<T>& l, const Option<T>& r)
+ {
+ const T *l2 = l.ptr_or(nullptr);
+ const T *r2 = r.ptr_or(nullptr);
+ if (!l2 && !r2)
+ return true;
+ if (l2 && r2)
+ {
+ return *l2 == *r2;
+ }
+ return false;
+ }
+ template<class T>
+ bool operator != (const Option<T>& l, const Option<T>& r)
+ {
+ return !(l == r);
+ }
+ template<class T>
+ bool operator < (const Option<T>& l, const Option<T>& r)
+ {
+ const T *l2 = l.ptr_or(nullptr);
+ const T *r2 = r.ptr_or(nullptr);
+
+ if (!l2 && r2)
+ return true;
+ if (l2 && r2)
+ {
+ return *l2 < *r2;
+ }
+ return false;
+ }
+ template<class T>
+ bool operator > (const Option<T>& l, const Option<T>& r)
+ {
+ return (r < l);
+ }
+ template<class T>
+ bool operator <= (const Option<T>& l, const Option<T>& r)
+ {
+ return !(r < l);
+ }
+ template<class T>
+ bool operator >= (const Option<T>& l, const Option<T>& r)
+ {
+ return !(l < r);
+ }
+
+ // workaround for the fact that most references can't escape
+ template<class T>
+ struct RefWrapper
+ {
+ T maybe_ref;
+
+ T maybe_ref_fun() { return std::forward<T>(maybe_ref); }
+ };
+
+ template<class T>
+ RefWrapper<T> option_unwrap(RefWrapper<Option<T>> o)
+ { return RefWrapper<T>{std::move(*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr())}; }
+ template<class T>
+ RefWrapper<T&> option_unwrap(RefWrapper<Option<T>&> o)
+ { return RefWrapper<T&>{*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr()}; }
+ template<class T>
+ RefWrapper<T&&> option_unwrap(RefWrapper<Option<T>&&> o)
+ { return RefWrapper<T&&>{std::move(*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr())}; }
+ template<class T>
+ RefWrapper<T> option_unwrap(RefWrapper<const Option<T>> o)
+ { return RefWrapper<T>{std::move(*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr())}; }
+ template<class T>
+ RefWrapper<const T&> option_unwrap(RefWrapper<const Option<T>&> o)
+ { return RefWrapper<const T&>{*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr()}; }
+ template<class T>
+ RefWrapper<const T&&> option_unwrap(RefWrapper<const Option<T>&&> o)
+ { return RefWrapper<const T&&>{std::move(*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr())}; }
+
+ // if you think you understand this, you're not trying hard enough.
+#define TRY_UNWRAP(opt, falsy) \
+ ({ \
+ tmwa::option::RefWrapper<decltype((opt))> o = {(opt)}; \
+ if (o.maybe_ref.is_none()) falsy; \
+ tmwa::option::option_unwrap(std::move(o)); \
+ }).maybe_ref_fun()
+
+#define OMATCH_BEGIN(expr) \
+ { \
+ auto&& _omatch_var = (expr); \
+ switch (_omatch_var.is_some()) \
+ { \
+ { \
+ { \
+ /*}}}}*/
+#define OMATCH_END() \
+ /*{{{{*/ \
+ } \
+ } \
+ } \
+ (void) _omatch_var; \
+ }
+
+#define OMATCH_BEGIN_SOME(var, expr) \
+ OMATCH_BEGIN (expr) \
+ OMATCH_CASE_SOME (var)
+
+#define OMATCH_CASE_SOME(var) \
+ /*{{{{*/ \
+ } \
+ break; \
+ } \
+ { \
+ case true: \
+ { \
+ auto&& var = *_omatch_var.ptr_or(nullptr); \
+ /*}}}}*/
+#define OMATCH_CASE_NONE() \
+ /*{{{{*/ \
+ } \
+ break; \
+ } \
+ { \
+ case false: \
+ { \
+ /*}}}}*/
+} // namespace option
+
+//using option::Option;
+using option::None;
+using option::Some;
+} // namespace tmwa
diff --git a/src/compat/option.py b/src/compat/option.py
new file mode 100644
index 0000000..7704174
--- /dev/null
+++ b/src/compat/option.py
@@ -0,0 +1,39 @@
+class Option(object):
+ __slots__ = ('_value')
+ name = 'tmwa::option::Option'
+ enabled = True
+
+ def __init__(self, value):
+ self._value = value['repr']
+
+ def to_string(self):
+ value = self._value
+ ty = value.type.template_argument(0)
+ try:
+ some = bool(value['_some'])
+ except gdb.error:
+ stupider = value['stupider']
+ if stupider:
+ return 'Some<%s>(%s)' % (ty, stupider)
+ else:
+ return 'None<%s>' % ty
+ else:
+ if some:
+ data = value['_data']
+ data = data.address.cast(ty.pointer()).dereference()
+ return 'Some<%s>(%s)' % (ty, data)
+ else:
+ return 'None<%s>' % ty
+
+ test_extra = '''
+ #include "../compat/borrow.hpp"
+
+ static int option_borrow_thingy;
+ '''
+
+ tests = [
+ ('tmwa::None<int>()', 'None<int>'),
+ ('tmwa::Some(1)', 'Some<int>(1)'),
+ ('tmwa::Option<tmwa::Borrowed<int>>(tmwa::None)', 'None<tmwa::Borrowed<int>>'),
+ ('tmwa::Some(tmwa::borrow(option_borrow_thingy))', 'Some<tmwa::Borrowed<int>>(<option_borrow_thingy>)'),
+ ]
diff --git a/src/compat/option_test.cpp b/src/compat/option_test.cpp
new file mode 100644
index 0000000..69f3a60
--- /dev/null
+++ b/src/compat/option_test.cpp
@@ -0,0 +1,499 @@
+#include "option.hpp"
+// option_test.cpp - Testsuite for a type that may or may not exist
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <gtest/gtest.h>
+
+#include "../strings/literal.hpp"
+
+#include "borrow.hpp"
+
+#include "../diagnostics.hpp"
+//#include "../poison.hpp"
+
+
+namespace tmwa
+{
+TEST(Option, somenone)
+{
+ {
+ option::Option<int> opt = option::None;
+ opt = option::None;
+ }
+ {
+ option::Option<int> opt = option::None<int>;
+ opt = option::None<int>;
+ }
+ {
+ option::Option<int> opt = option::None<int>();
+ opt = option::None<int>();
+ }
+ {
+ option::Option<int> opt = option::Some(123);
+ opt = option::Some(123);
+ }
+ {
+ option::Option<int> opt = option::Some<int>(123);
+ opt = option::Some<int>(123);
+ }
+}
+TEST(Option, somenonenocopy)
+{
+ struct Foo
+ {
+ Foo() = default;
+ Foo(Foo&&) = default;
+ Foo(const Foo&) = delete;
+ Foo& operator = (Foo&&) = default;
+ Foo& operator = (const Foo&) = delete;
+ };
+ {
+ option::Option<Foo> opt = option::None;
+ opt = option::None;
+ }
+ // clang <= 3.4 is buggy
+ // since clang doesn't version, there is no way to restrict it to clang 3.5+
+#ifndef __clang__
+ {
+ option::Option<Foo> opt = option::None<Foo>;
+ opt = option::None<Foo>;
+ }
+#endif
+ {
+ option::Option<Foo> opt = option::None<Foo>();
+ opt = option::None<Foo>();
+ }
+ {
+ option::Option<Foo> opt = option::Some(Foo());
+ opt = option::Some(Foo());
+ }
+ {
+ option::Option<Foo> opt = option::Some<Foo>(Foo());
+ opt = option::Some<Foo>(Foo());
+ }
+}
+TEST(Option, customrepr)
+{
+ int iv = 123;
+ Borrowed<int> i = borrow(iv);
+
+ EXPECT_EQ(&iv, as_raw_pointer(Some(i)));
+
+ {
+ option::Option<Borrowed<int>> opt = option::None;
+ opt = option::None;
+ }
+ {
+ option::Option<Borrowed<int>> opt = option::None<Borrowed<int>>;
+ opt = option::None<Borrowed<int>>;
+ }
+ {
+ option::Option<Borrowed<int>> opt = option::None<Borrowed<int>>();
+ opt = option::None<Borrowed<int>>();
+ }
+ {
+ option::Option<Borrowed<int>> opt = option::Some(i);
+ opt = option::Some(i);
+ }
+ {
+ option::Option<Borrowed<int>> opt = option::Some<Borrowed<int>>(i);
+ opt = option::Some<Borrowed<int>>(i);
+ }
+}
+
+TEST(Option, destruct)
+{
+ struct BugCheck
+ {
+ bool *destroyed;
+
+ BugCheck(bool *d)
+ : destroyed(d)
+ {}
+ BugCheck(BugCheck&& r)
+ : destroyed(r.destroyed)
+ {
+ r.destroyed = nullptr;
+ }
+ BugCheck& operator = (BugCheck&& r)
+ {
+ std::swap(destroyed, r.destroyed);
+ return *this;
+ }
+ ~BugCheck()
+ {
+ if (!destroyed)
+ return;
+ if (*destroyed)
+ abort();
+ *destroyed = true;
+ }
+ };
+
+ bool destroyed = false;
+
+ Option<BugCheck> bug = Some(BugCheck(&destroyed));
+ bug = None;
+}
+
+TEST(Option, def)
+{
+ struct Tracked
+ {
+ int id;
+ int gen;
+
+ Tracked(int i, int g=0) : id(i), gen(g) {}
+ Tracked(Tracked&&) = default;
+ Tracked(const Tracked& r) : id(r.id), gen(r.gen + 1) {}
+ Tracked& operator = (Tracked&&) = default;
+ Tracked& operator = (const Tracked& r) { id = r.id; gen = r.gen + 1; return *this; }
+
+ bool operator == (const Tracked& r) const
+ {
+ return this->id == r.id && this->gen == r.gen;
+ }
+ bool operator != (const Tracked& r) const
+ {
+ return !(*this == r);
+ }
+ };
+
+ {
+ option::Option<Tracked> o = option::None;
+ EXPECT_EQ(o.move_or(Tracked(1)), Tracked(1));
+ EXPECT_EQ(o.copy_or(Tracked(2)), Tracked(2));
+ Tracked t3(3);
+ Tracked& r3 = o.ref_or(t3);
+ EXPECT_EQ(&r3, &t3);
+ Tracked t4(4);
+ Tracked *r4 = o.ptr_or(&t4);
+ EXPECT_EQ(r4, &t4);
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ }
+ {
+ const option::Option<Tracked> o = option::None;
+ EXPECT_EQ(o.copy_or(Tracked(2)), Tracked(2));
+ Tracked t3(3);
+ const Tracked& r3 = o.ref_or(t3);
+ EXPECT_EQ(&r3, &t3);
+ Tracked t4(4);
+ const Tracked *r4 = o.ptr_or(&t4);
+ EXPECT_EQ(r4, &t4);
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ }
+ {
+ option::Option<Tracked> o = option::Some(Tracked(0));
+ EXPECT_EQ(o.move_or(Tracked(1)), Tracked(0));
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ o = option::Some(Tracked(0));
+ EXPECT_EQ(o.copy_or(Tracked(2)), Tracked(0, 1));
+ Tracked t3(3);
+ Tracked& r3 = o.ref_or(t3);
+ EXPECT_NE(&r3, &t3);
+ Tracked t4(4);
+ Tracked *r4 = o.ptr_or(&t4);
+ EXPECT_NE(r4, &t4);
+ EXPECT_NE(o.ptr_or(nullptr), nullptr);
+ EXPECT_EQ(&r3, r4);
+ EXPECT_EQ(r4, reinterpret_cast<Tracked *>(&o));
+ }
+ {
+ const option::Option<Tracked> o = option::Some(Tracked(0));
+ EXPECT_EQ(o.copy_or(Tracked(2)), Tracked(0, 1));
+ Tracked t3(3);
+ const Tracked& r3 = o.ref_or(t3);
+ EXPECT_NE(&r3, &t3);
+ Tracked t4(4);
+ const Tracked *r4 = o.ptr_or(&t4);
+ EXPECT_NE(r4, &t4);
+ EXPECT_NE(o.ptr_or(nullptr), nullptr);
+ EXPECT_EQ(&r3, r4);
+ EXPECT_EQ(r4, reinterpret_cast<const Tracked *>(&o));
+ }
+}
+
+TEST(Option, map)
+{
+ struct Foo
+ {
+ Foo() = default;
+ Foo(Foo&&) = default;
+ Foo(const Foo&) = delete;
+ Foo& operator = (Foo&&) = default;
+ Foo& operator = (const Foo&) = delete;
+ };
+
+ // move
+ {
+ option::Option<Foo> o = option::None;
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ option::Option<int> i = o.move_map([](Foo){ return 0; });
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ EXPECT_EQ(i.ptr_or(nullptr), nullptr);
+ }
+ {
+ option::Option<Foo> o = option::Some(Foo());
+ EXPECT_NE(o.ptr_or(nullptr), nullptr);
+ option::Option<int> i = o.move_map([](Foo){ return 1; });
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ EXPECT_NE(i.ptr_or(nullptr), nullptr);
+ EXPECT_EQ(i.copy_or(0), 1);
+ }
+ // mut ref
+ {
+ option::Option<Foo> o = option::None;
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ option::Option<int> i = o.map([](Foo&){ return 0; });
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ EXPECT_EQ(i.ptr_or(nullptr), nullptr);
+ }
+ {
+ option::Option<Foo> o = option::Some(Foo());
+ EXPECT_NE(o.ptr_or(nullptr), nullptr);
+ option::Option<int> i = o.map([](Foo&){ return 1; });
+ EXPECT_NE(o.ptr_or(nullptr), nullptr);
+ EXPECT_NE(i.ptr_or(nullptr), nullptr);
+ EXPECT_EQ(i.copy_or(0), 1);
+ }
+ // const ref
+ {
+ option::Option<Foo> o = option::None;
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ option::Option<int> i = o.map([](const Foo&){ return 0; });
+ EXPECT_EQ(o.ptr_or(nullptr), nullptr);
+ EXPECT_EQ(i.ptr_or(nullptr), nullptr);
+ }
+ {
+ option::Option<Foo> o = option::Some(Foo());
+ EXPECT_NE(o.ptr_or(nullptr), nullptr);
+ option::Option<int> i = o.map([](const Foo&){ return 1; });
+ EXPECT_NE(o.ptr_or(nullptr), nullptr);
+ EXPECT_NE(i.ptr_or(nullptr), nullptr);
+ EXPECT_EQ(i.copy_or(0), 1);
+ }
+}
+
+TEST(Option, member)
+{
+ struct Foo
+ {
+ int bar = 404;
+ };
+
+ Option<Foo> vng = None;
+ EXPECT_EQ(vng.pmd_get(&Foo::bar).copy_or(42), 42);
+ Option<Foo> vsg = Some(Foo());
+ EXPECT_EQ(vsg.pmd_get(&Foo::bar).copy_or(42), 404);
+
+ Option<Foo> vns = None;
+ vns.pmd_set(&Foo::bar, 42);
+ EXPECT_EQ(vns.copy_or(Foo()).bar, 404);
+ Option<Foo> vss = Some(Foo());
+ vss.pmd_set(&Foo::bar, 42);
+ EXPECT_EQ(vss.copy_or(Foo()).bar, 42);
+
+ Foo foo, alt;
+
+ Option<P<Foo>> png = None;
+ EXPECT_EQ(png.pmd_pget(&Foo::bar).copy_or(42), 42);
+ Option<P<Foo>> psg = Some(borrow(foo));
+ EXPECT_EQ(psg.pmd_pget(&Foo::bar).copy_or(42), 404);
+
+ Option<P<Foo>> pns = None;
+ pns.pmd_pset(&Foo::bar, 42);
+ EXPECT_EQ(pns.copy_or(borrow(alt))->bar, 404);
+ EXPECT_EQ(foo.bar, 404);
+ Option<P<Foo>> pss = Some(borrow(foo));
+ pss.pmd_pset(&Foo::bar, 42);
+ EXPECT_EQ(pss.copy_or(borrow(alt))->bar, 42);
+ EXPECT_EQ(foo.bar, 42);
+ EXPECT_EQ(alt.bar, 404);
+}
+
+#if __cplusplus >= 201300 // c++14 as given by gcc 4.9
+# define DECLTYPE_AUTO decltype(auto)
+#else
+# define DECLTYPE_AUTO auto&&
+#endif
+
+TEST(Option, unwrap)
+{
+ int x;
+
+ Option<int> v = Some(1);
+ Option<int>& l = v;
+ Option<int>&& r = std::move(v);
+ const Option<int> cv = v;
+ // significantly, see the mut
+ const Option<int>& cl = v;
+ const Option<int>&& cr = std::move(v);
+
+ auto fv = [&]() -> Option<int> { return v; };
+ auto fl = [&]() -> Option<int>& { return l; };
+ auto fr = [&]() -> Option<int>&& { return std::move(r); };
+ auto fcv = [&]() -> const Option<int> { return v; };
+ auto fcl = [&]() -> const Option<int>& { return l; };
+ auto fcr = [&]() -> const Option<int>&& { return std::move(r); };
+
+ DIAG_PUSH();
+ DIAG_I(useless_cast);
+
+#define CHECK(v, t) \
+ { \
+ DECLTYPE_AUTO out = TRY_UNWRAP(v, abort() ); \
+ DECLTYPE_AUTO cmp = static_cast<t>(x); \
+ static_assert(std::is_same<decltype(out), decltype(cmp)>::value, #v); \
+ }
+
+ CHECK(v, int&);
+ CHECK(cv, const int&);
+ CHECK(l, int&);
+ CHECK(cl, const int&);
+ CHECK(r, int&);
+ CHECK(cr, const int&);
+ // repeat the same forcing expressions, since that matters with decltype
+ CHECK((v), int&);
+ CHECK((cv), const int&);
+ CHECK((l), int&);
+ CHECK((cl), const int&);
+ CHECK((r), int&);
+ CHECK((cr), const int&);
+
+ CHECK(fv(), int);
+ CHECK(fcv(), int);
+ CHECK(fl(), int&);
+ CHECK(fcl(), const int&);
+ CHECK(fr(), int&&);
+ CHECK(fcr(), const int&&);
+
+ DIAG_POP();
+#undef CHECK
+
+ v = None; TRY_UNWRAP(v, v = Some(1));
+ v = None; TRY_UNWRAP(l, v = Some(1));
+ v = None; TRY_UNWRAP(cl, v = Some(1));
+ v = None; TRY_UNWRAP(r, v = Some(1));
+ v = None; TRY_UNWRAP(cr, v = Some(1));
+
+ v = None; TRY_UNWRAP(fl(), v = Some(1));
+ v = None; TRY_UNWRAP(fcl(), v = Some(1));
+ v = None; TRY_UNWRAP(fr(), v = Some(1));
+ v = None; TRY_UNWRAP(fcr(), v = Some(1));
+
+ v = None;
+ OMATCH_BEGIN (v)
+ {
+ OMATCH_CASE_SOME (o)
+ {
+ EXPECT_NE(o, o);
+ }
+ OMATCH_CASE_NONE ()
+ {
+ SUCCEED();
+ }
+ }
+ OMATCH_END ();
+
+ v = Some(1);
+ OMATCH_BEGIN (v)
+ {
+ OMATCH_CASE_SOME (o)
+ {
+ EXPECT_EQ(o, 1);
+ }
+ OMATCH_CASE_NONE ()
+ {
+ FAIL();
+ }
+ }
+ OMATCH_END ();
+}
+
+TEST(Option, flatten)
+{
+ using option::Option;
+ using option::Some;
+ using option::None;
+
+ struct Foo
+ {
+ int x;
+ };
+ auto f1 = Some(Foo{42});
+ auto f2 = Some(f1);
+ auto f3 = Some(f2);
+ EXPECT_EQ(flatten(f1).copy_or(Foo{404}).x, 42);
+ EXPECT_EQ(flatten(f2).copy_or(Foo{404}).x, 42);
+ EXPECT_EQ(flatten(f3).copy_or(Foo{404}).x, 42);
+
+ decltype(f1) n1 = None;
+ decltype(f2) n2a = None;
+ decltype(f2) n2b = Some(n1);
+ decltype(f3) n3a = None;
+ decltype(f3) n3b = Some(n2a);
+ decltype(f3) n3c = Some(n2b);
+ EXPECT_EQ(flatten(n1).copy_or(Foo{404}).x, 404);
+ EXPECT_EQ(flatten(n2a).copy_or(Foo{404}).x, 404);
+ EXPECT_EQ(flatten(n2b).copy_or(Foo{404}).x, 404);
+ EXPECT_EQ(flatten(n3a).copy_or(Foo{404}).x, 404);
+ EXPECT_EQ(flatten(n3b).copy_or(Foo{404}).x, 404);
+ EXPECT_EQ(flatten(n3c).copy_or(Foo{404}).x, 404);
+}
+
+#define EQ(a, b) ({ EXPECT_TRUE(a == b); EXPECT_FALSE(a != b); EXPECT_FALSE(a < b); EXPECT_TRUE(a <= b); EXPECT_FALSE(a > b); EXPECT_TRUE(a >= b); })
+#define LT(a, b) ({ EXPECT_FALSE(a == b); EXPECT_TRUE(a != b); EXPECT_TRUE(a < b); EXPECT_TRUE(a <= b); EXPECT_FALSE(a > b); EXPECT_FALSE(a >= b); })
+#define GT(a, b) ({ EXPECT_FALSE(a == b); EXPECT_TRUE(a != b); EXPECT_FALSE(a < b); EXPECT_FALSE(a <= b); EXPECT_TRUE(a > b); EXPECT_TRUE(a >= b); })
+
+TEST(Option, cmp)
+{
+ using option::Option;
+ using option::Some;
+ using option::None;
+
+ Option<int> none = None;
+
+ EQ(none, none);
+ EQ(none, None);
+ LT(none, Some(-1));
+ LT(none, Some(0));
+ LT(none, Some(1));
+ EQ((None), none);
+ // EQ((None), None); // actually a function template
+ LT((None), Some(-1));
+ LT((None), Some(0));
+ LT((None), Some(1));
+ GT(Some(-1), none);
+ GT(Some(-1), None);
+ EQ(Some(-1), Some(-1));
+ LT(Some(-1), Some(0));
+ LT(Some(-1), Some(1));
+ GT(Some(0), none);
+ GT(Some(0), None);
+ GT(Some(0), Some(-1));
+ EQ(Some(0), Some(0));
+ LT(Some(0), Some(1));
+ GT(Some(1), none);
+ GT(Some(1), None);
+ GT(Some(1), Some(-1));
+ GT(Some(1), Some(0));
+ EQ(Some(1), Some(1));
+}
+
+} // namespace tmwa
diff --git a/src/compat/rawmem.hpp b/src/compat/rawmem.hpp
index c271a56..66af204 100644
--- a/src/compat/rawmem.hpp
+++ b/src/compat/rawmem.hpp
@@ -22,6 +22,8 @@
#include <cstdint>
#include <cstring>
+#include <type_traits>
+
#include "fwd.hpp"
@@ -49,4 +51,23 @@ void really_memset0(uint8_t *dest, size_t n)
{
memset(dest, '\0', n);
}
+
+template<class T>
+struct is_trivially_copyable
+: std::integral_constant<bool,
+ // come back when GCC actually implements the public traits properly
+ __has_trivial_copy(T)
+ && __has_trivial_assign(T)
+ && __has_trivial_destructor(T)>
+{};
+
+template<class T>
+void really_memzero_this(T *v)
+{
+ static_assert(is_trivially_copyable<T>::value, "only for mostly-pod types");
+ static_assert(std::is_class<T>::value || std::is_union<T>::value, "Only for user-defined structures (for now)");
+ memset(v, '\0', sizeof(*v));
+}
+template<class T, size_t n>
+void really_memzero_this(T (&)[n]) = delete;
} // namespace tmwa
diff --git a/src/compat/result.hpp b/src/compat/result.hpp
new file mode 100644
index 0000000..6adc552
--- /dev/null
+++ b/src/compat/result.hpp
@@ -0,0 +1,94 @@
+#pragma once
+// result.hpp - A possibly failed return value
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../strings/rstring.hpp"
+
+#include "option.hpp"
+
+namespace tmwa
+{
+ namespace result
+ {
+ enum ResultMagicFlag { magic_flag };
+
+ template<class T>
+ class Result
+ {
+ Option<T> success;
+ RString failure;
+ public:
+ Result(ResultMagicFlag, T v)
+ : success(Some(std::move(v))), failure()
+ {}
+ Result(ResultMagicFlag, RString msg)
+ : success(None), failure(std::move(msg))
+ {}
+
+ bool is_ok() { return success.is_some(); }
+ bool is_err() { return !is_ok(); }
+
+ Option<T>& get_success() { return success; }
+ RString& get_failure() { return failure; }
+ };
+
+ template<class T>
+ Result<T> Ok(T v)
+ {
+ return Result<T>(magic_flag, std::move(v));
+ }
+
+ struct Err
+ {
+ RString message;
+ Err(RString m) : message(std::move(m)) {}
+
+ template<class T>
+ operator Result<T>()
+ {
+ return Result<T>(magic_flag, message);
+ }
+ template<class T>
+ operator Option<Result<T>>()
+ {
+ return Some(Result<T>(magic_flag, message));
+ }
+ };
+
+ template<class T>
+ bool operator == (const Result<T>& l, const Result<T>& r)
+ {
+ return l.get_success() == r.get_success() && l.get_failure() == r.get_failure();
+ }
+ template<class T>
+ bool operator != (const Result<T>& l, const Result<T>& r)
+ {
+ return !(l == r);
+ }
+ } // namespace result
+ using result::Result;
+ using result::Ok;
+ using result::Err;
+
+#define TRY(r) ({ auto _res = r; TRY_UNWRAP(_res.get_success(), return ::tmwa::Err(_res.get_failure())); })
+ // TODO the existence of this as a separate macro is a bug.
+#define TRY_MOVE(r) ({ auto _res = r; TRY_UNWRAP(std::move(_res.get_success()), return ::tmwa::Err(_res.get_failure())); })
+} // namespace tmwa
diff --git a/src/compat/result_test.cpp b/src/compat/result_test.cpp
new file mode 100644
index 0000000..0fcc181
--- /dev/null
+++ b/src/compat/result_test.cpp
@@ -0,0 +1,79 @@
+#include "result.hpp"
+// result_test.cpp - Testsuite for possibly failing return values
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <gtest/gtest.h>
+
+#include "../strings/literal.hpp"
+
+//#include "../poison.hpp"
+
+
+namespace tmwa
+{
+TEST(Result, inspect)
+{
+ struct Foo
+ {
+ int val;
+
+ Foo(int v) : val(v) {}
+ Foo(Foo&&) = default;
+ Foo(const Foo&) = delete;
+ Foo& operator = (Foo&&) = default;
+ Foo& operator = (const Foo&) = delete;
+
+ bool operator == (const Foo& other) const
+ {
+ return this->val == other.val;
+ }
+ };
+
+ Result<Foo> foo = Ok(Foo(1));
+ EXPECT_TRUE(foo.is_ok());
+ EXPECT_FALSE(foo.is_err());
+ EXPECT_EQ(foo.get_success(), Some(Foo(1)));
+ EXPECT_EQ(foo.get_failure(), ""_s);
+ foo = Err("oops"_s);
+ EXPECT_FALSE(foo.is_ok());
+ EXPECT_TRUE(foo.is_err());
+ EXPECT_EQ(foo.get_success(), None<Foo>());
+ EXPECT_EQ(foo.get_failure(), "oops"_s);
+}
+
+static
+Result<int> try_you(bool b)
+{
+ return b ? Ok(0) : Err("die"_s);
+}
+
+static
+Result<int> try_me(bool b)
+{
+ return Ok(TRY(try_you(b)) + 1);
+}
+
+TEST(Result, try)
+{
+ Result<int> t = try_me(true);
+ EXPECT_EQ(t.get_success(), Some(1));
+ Result<int> f = try_me(false);
+ EXPECT_EQ(f.get_failure(), "die"_s);
+}
+} // namespace tmwa
diff --git a/src/compat/time_t.cpp b/src/compat/time_t.cpp
deleted file mode 100644
index ee0bbde..0000000
--- a/src/compat/time_t.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "time_t.hpp"
-// time_t.cpp - time_t with a reliable representation
-//
-// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/compat/time_t.hpp b/src/compat/time_t.hpp
index 9e7cf25..e9c97c4 100644
--- a/src/compat/time_t.hpp
+++ b/src/compat/time_t.hpp
@@ -20,10 +20,90 @@
#include "fwd.hpp"
-// TODO fix this ordering violation by promoting TimeT here
-#include "../mmo/utils.hpp"
+#include <ctime>
+
+#include "../ints/little.hpp"
+
+#include "operators.hpp"
namespace tmwa
{
+// Exists in place of time_t, to give it a predictable printf-format.
+// (on x86 and amd64, time_t == long, but not on x32)
+static_assert(sizeof(long long) >= sizeof(time_t), "long long >= time_t");
+struct TimeT : Comparable
+{
+ long long value;
+
+ // conversion
+ TimeT(time_t t=0) : value(t) {}
+ TimeT(struct tm t) : value(timegm(&t)) {}
+ operator time_t() const { return value; }
+ operator struct tm() const { time_t v = value; return *gmtime(&v); }
+
+ explicit operator bool() const { return value; }
+ bool operator !() const { return !value; }
+
+ // prevent surprises
+ template<class T>
+ TimeT(T) = delete;
+ template<class T>
+ operator T() const = delete;
+
+ static
+ TimeT now()
+ {
+ // poisoned, but this is still in header-land
+ return time(nullptr);
+ }
+
+ bool error() const
+ {
+ return value == -1;
+ }
+ bool okay() const
+ {
+ return !error();
+ }
+};
+
+inline
+long long convert_for_printf(TimeT t)
+{
+ return t.value;
+}
+
+// 2038 problem
+inline __attribute__((warn_unused_result))
+bool native_to_network(Little32 *net, TimeT nat)
+{
+ time_t tmp = nat;
+ return native_to_network(net, static_cast<uint32_t>(tmp));
+}
+
+inline __attribute__((warn_unused_result))
+bool network_to_native(TimeT *nat, Little32 net)
+{
+ uint32_t tmp;
+ bool rv = network_to_native(&tmp, net);
+ *nat = static_cast<time_t>(tmp);
+ return rv;
+}
+
+inline __attribute__((warn_unused_result))
+bool native_to_network(Little64 *net, TimeT nat)
+{
+ time_t tmp = nat;
+ return native_to_network(net, static_cast<uint64_t>(tmp));
+}
+
+inline __attribute__((warn_unused_result))
+bool network_to_native(TimeT *nat, Little64 net)
+{
+ uint64_t tmp;
+ bool rv = network_to_native(&tmp, net);
+ *nat = static_cast<time_t>(tmp);
+ return rv;
+}
} // namespace tmwa
diff --git a/src/diagnostics.hpp b/src/diagnostics.hpp
index dab15b5..5eee323 100644
--- a/src/diagnostics.hpp
+++ b/src/diagnostics.hpp
@@ -600,7 +600,8 @@ namespace tmwa
/// Warn about maybe uninitialized automatic variables
#define DIAG_maybe_uninitialized "-Wmaybe-uninitialized"
-#if GCC >= 407
+// buggy in 4.7 with tmwa::Option<>
+#if GCC >= 408
# define HAS_DIAG_maybe_uninitialized 1
#else
# define HAS_DIAG_maybe_uninitialized 0
diff --git a/src/generic/array.hpp b/src/generic/array.hpp
index dccb91e..3575db6 100644
--- a/src/generic/array.hpp
+++ b/src/generic/array.hpp
@@ -26,6 +26,7 @@
#include "oops.hpp"
+// half the important stuff is now in fwd.hpp !!!
namespace tmwa
{
template<class I, I be, I en>
@@ -39,9 +40,6 @@ struct ExclusiveIndexing
constexpr static size_t alloc_size = index_to_offset(en) - index_to_offset(be);
};
-template<size_t n>
-using SimpleIndexing = ExclusiveIndexing<size_t, 0, n>;
-
template<class I, I lo, I hi>
struct InclusiveIndexing
{
@@ -53,7 +51,7 @@ struct InclusiveIndexing
constexpr static size_t alloc_size = index_to_offset(hi) - index_to_offset(lo) + 1;
};
-template<class E, E n=E::COUNT>
+template<class E, E n>
struct EnumIndexing : ExclusiveIndexing<E, static_cast<E>(0), n>
{
};
@@ -112,7 +110,4 @@ public:
return !(lhs == rhs);
}
};
-
-template<class T, size_t n>
-using Array = GenericArray<T, SimpleIndexing<n>>;
} // namespace tmwa
diff --git a/src/generic/db.cpp b/src/generic/db.cpp
deleted file mode 100644
index 458068c..0000000
--- a/src/generic/db.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "db.hpp"
-// db.cpp - convenience wrappers over std::map<K, V>
-//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/generic/db.hpp b/src/generic/db.hpp
index 90c4f92..04ead79 100644
--- a/src/generic/db.hpp
+++ b/src/generic/db.hpp
@@ -23,6 +23,8 @@
#include <map>
#include <memory>
+#include "../compat/borrow.hpp"
+
namespace tmwa
{
@@ -45,19 +47,19 @@ public:
const_iterator begin() const { return impl.begin(); }
const_iterator end() const { return impl.end(); }
- V *search(const K& k)
+ Option<Borrowed<V>> search(const K& k)
{
iterator it = impl.find(k);
if (it == impl.end())
- return nullptr;
- return &it->second;
+ return None;
+ return Some(borrow(it->second));
}
- const V *search(const K& k) const
+ Option<Borrowed<const V>> search(const K& k) const
{
const_iterator it = impl.find(k);
if (it == impl.end())
- return nullptr;
- return &it->second;
+ return None;
+ return Some(borrow(it->second));
}
void insert(const K& k, V v)
{
@@ -72,9 +74,9 @@ public:
return (void)&it->second;
}
- V *init(const K& k)
+ Borrowed<V> init(const K& k)
{
- return &impl[k];
+ return borrow(impl[k]);
}
void erase(const K& k)
{
@@ -112,8 +114,13 @@ public:
// const V& ? with a static default V?
V get(const K& k)
{
- V *vp = impl.search(k);
- return vp ? *vp : V();
+ Option<Borrowed<V>> vp = impl.search(k);
+ OMATCH_BEGIN_SOME (v, vp)
+ {
+ return *v;
+ }
+ OMATCH_END ();
+ return V();
}
void put(const K& k, V v)
{
@@ -153,10 +160,15 @@ public:
const_iterator end() const { return impl.end(); }
// const V& ? with a static default V?
- V *get(const K& k)
- {
- U *up = impl.search(k);
- return up ? up->get() : nullptr;
+ Option<Borrowed<V>> get(const K& k)
+ {
+ Option<Borrowed<U>> up = impl.search(k);
+ OMATCH_BEGIN_SOME (u, up)
+ {
+ return Some(borrow(*u->get()));
+ }
+ OMATCH_END ();
+ return None;
}
void put(const K& k, U v)
{
diff --git a/src/generic/dumb_ptr.cpp b/src/generic/dumb_ptr.cpp
deleted file mode 100644
index e690f7d..0000000
--- a/src/generic/dumb_ptr.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "dumb_ptr.hpp"
-// dumb_ptr.cpp - dummy file to make Make dependencies work
-//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/generic/dumb_ptr.hpp b/src/generic/dumb_ptr.hpp
index 72247d5..2ada93b 100644
--- a/src/generic/dumb_ptr.hpp
+++ b/src/generic/dumb_ptr.hpp
@@ -35,8 +35,11 @@ class dumb_ptr
friend class dumb_ptr;
T *impl;
public:
+ dumb_ptr() noexcept
+ : impl(nullptr)
+ {}
explicit
- dumb_ptr(T *p=nullptr) noexcept
+ dumb_ptr(T *p) noexcept
: impl(p)
{}
template<class U>
diff --git a/src/generic/enum.hpp b/src/generic/enum.hpp
index 81c9b12..d5d50ea 100644
--- a/src/generic/enum.hpp
+++ b/src/generic/enum.hpp
@@ -33,8 +33,7 @@
namespace tmwa
{
-template<class T, class E, E max>
-using earray = GenericArray<T, EnumIndexing<E, max>>;
+// part moved to fwd.hpp
template<class T, class E, E max>
class eptr
diff --git a/src/generic/fwd.hpp b/src/generic/fwd.hpp
index 3215903..31fb13a 100644
--- a/src/generic/fwd.hpp
+++ b/src/generic/fwd.hpp
@@ -20,10 +20,42 @@
#include "../sanity.hpp"
+#include "../strings/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp" // rank 2
+// generic/fwd.hpp is rank 3
+
namespace tmwa
{
// meh, add more when I feel like it
template<class T>
class dumb_ptr;
+
+template<class K, class V>
+class Map;
+template<class K, class V>
+class DMap;
+template<class K, class V>
+class UPMap;
+
+class InternPool;
+
+// arrays are complicated
+template<class I, I be, I en>
+struct ExclusiveIndexing;
+template<size_t n>
+using SimpleIndexing = ExclusiveIndexing<size_t, 0, n>;
+template<class I, I lo, I hi>
+struct InclusiveIndexing;
+template<class E, E n=E::COUNT>
+struct EnumIndexing;
+template<class I, size_t limit>
+struct InventoryIndexing;
+template<class T, class I>
+struct GenericArray;
+template<class T, size_t n>
+using Array = GenericArray<T, SimpleIndexing<n>>;
+
+template<class T, class E, E max>
+using earray = GenericArray<T, EnumIndexing<E, max>>;
} // namespace tmwa
diff --git a/src/generic/matrix.cpp b/src/generic/matrix.cpp
deleted file mode 100644
index b14ab7d..0000000
--- a/src/generic/matrix.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "matrix.hpp"
-// matrix.cpp - A 2D array.
-//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/generic/md5.hpp b/src/generic/md5.hpp
index 50bc987..2f07789 100644
--- a/src/generic/md5.hpp
+++ b/src/generic/md5.hpp
@@ -24,7 +24,6 @@
#include <array>
-#include "../strings/fwd.hpp"
#include "../strings/vstring.hpp"
diff --git a/src/generic/operators.cpp b/src/generic/operators.cpp
deleted file mode 100644
index 614ae51..0000000
--- a/src/generic/operators.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "operators.hpp"
-// operators.cpp - ADL helper for value wrappers.
-//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/generic/random.hpp b/src/generic/random.hpp
index 5d67236..897ad43 100644
--- a/src/generic/random.hpp
+++ b/src/generic/random.hpp
@@ -18,10 +18,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "fwd.hpp"
-
#include "random.t.hpp"
+#include "fwd.hpp"
+
#include <random>
diff --git a/src/generic/random2.hpp b/src/generic/random2.hpp
index 23d165c..3d481f4 100644
--- a/src/generic/random2.hpp
+++ b/src/generic/random2.hpp
@@ -18,10 +18,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "fwd.hpp"
-
#include "random.hpp"
+#include "fwd.hpp"
+
#include <algorithm>
#include "../compat/iter.hpp"
diff --git a/src/mmo/core.cpp b/src/high/core.cpp
index ff12f17..8a4d7ca 100644
--- a/src/mmo/core.cpp
+++ b/src/high/core.cpp
@@ -23,6 +23,7 @@
#include <sys/wait.h>
#include <alloca.h>
+#include <unistd.h>
#include <csignal>
#include <cstdlib>
@@ -106,8 +107,21 @@ void sig_proc(int)
*/
} // namespace tmwa
+static
+void check_caps()
+{
+ if (geteuid() == 0)
+ {
+ puts("Please don't run as root!");
+ _exit(1);
+ }
+}
+
int tmwa_main(int argc, char **argv)
{
+ // run before anything else (except global constructors)
+ check_caps();
+
using namespace tmwa;
check_paths();
@@ -153,8 +167,8 @@ int tmwa_main(int argc, char **argv)
// may wait too long in sendrecv
tick_t now = milli_clock::now();
interval_t next = do_timer(now);
- do_sendrecv(next);
- do_parsepacket();
+ runflag &= do_sendrecv(next);
+ runflag &= do_parsepacket();
}
return 0;
diff --git a/src/mmo/core.hpp b/src/high/core.hpp
index 259dd90..0ded246 100644
--- a/src/mmo/core.hpp
+++ b/src/high/core.hpp
@@ -24,8 +24,6 @@
#include "../range/slice.hpp"
-#include "../strings/fwd.hpp"
-
namespace tmwa
{
diff --git a/src/mmo/extract.cpp b/src/high/extract_mmo.cpp
index a480984..ab4290d 100644
--- a/src/mmo/extract.cpp
+++ b/src/high/extract_mmo.cpp
@@ -1,5 +1,5 @@
-#include "extract.hpp"
-// extract.cpp - a simple, hierarchical, tokenizer
+#include "extract_mmo.hpp"
+// extract_mmo.cpp - a simple, hierarchical, tokenizer
//
// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
//
@@ -20,37 +20,25 @@
#include <algorithm>
-#include "../strings/astring.hpp"
-#include "../strings/xstring.hpp"
-#include "../strings/vstring.hpp"
+#include "../io/extract.hpp"
+
+#include "../mmo/extract_enums.hpp"
-#include "extract_enums.hpp"
#include "mmo.hpp"
#include "../poison.hpp"
+// TODO also pass an io::LineSpan around.
namespace tmwa
{
-bool extract(XString str, XString *rv)
-{
- *rv = str;
- return true;
-}
-
-bool extract(XString str, AString *rv)
-{
- *rv = str;
- return true;
-}
-
-bool extract(XString str, GlobalReg *var)
+bool impl_extract(XString str, GlobalReg *var)
{
return extract(str,
record<','>(&var->str, &var->value));
}
-bool extract(XString str, Item *it)
+bool impl_extract(XString str, Item *it)
{
XString ignored;
XString corruption_hack_amount;
@@ -78,7 +66,7 @@ bool extract(XString str, Item *it)
return rv;
}
-bool extract(XString str, MapName *m)
+bool impl_extract(XString str, MapName *m)
{
XString::iterator it = std::find(str.begin(), str.end(), '.');
str = str.xislice_h(it);
@@ -88,7 +76,7 @@ bool extract(XString str, MapName *m)
return rv;
}
-bool extract(XString str, CharName *out)
+bool impl_extract(XString str, CharName *out)
{
VString<23> tmp;
if (extract(str, &tmp))
@@ -98,4 +86,15 @@ bool extract(XString str, CharName *out)
}
return false;
}
+
+bool impl_extract(XString str, NpcEvent *ev)
+{
+ XString mid;
+ return extract(str, record<':'>(&ev->npc, &mid, &ev->label)) && !mid;
+}
+
+bool impl_extract(XString str, Point *p)
+{
+ return extract(str, record<','>(&p->map_, &p->x, &p->y));
+}
} // namespace tmwa
diff --git a/src/compat/memory.cpp b/src/high/extract_mmo.hpp
index f9f2c22..f374658 100644
--- a/src/compat/memory.cpp
+++ b/src/high/extract_mmo.hpp
@@ -1,7 +1,7 @@
-#include "memory.hpp"
-// memory.cpp - I forget ...
+#pragma once
+// extract_mmo.hpp - a simple, hierarchical, tokenizer
//
-// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2012-2013 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -18,9 +18,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "fwd.hpp"
namespace tmwa
{
+bool impl_extract(XString str, GlobalReg *var);
+bool impl_extract(XString str, Item *it);
+bool impl_extract(XString str, MapName *m);
+bool impl_extract(XString str, CharName *out);
+
+bool impl_extract(XString str, NpcEvent *ev);
+bool impl_extract(XString str, Point *p);
} // namespace tmwa
diff --git a/src/high/fwd.hpp b/src/high/fwd.hpp
new file mode 100644
index 0000000..96875d3
--- /dev/null
+++ b/src/high/fwd.hpp
@@ -0,0 +1,40 @@
+#pragma once
+// high/fwd.hpp - list of type names for mmo lib
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../sanity.hpp"
+
+#include "../range/fwd.hpp" // rank 1
+#include "../strings/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp" // rank 2
+#include "../generic/fwd.hpp" // rank 3
+#include "../io/fwd.hpp" // rank 4
+#include "../net/fwd.hpp" // rank 5
+#include "../mmo/fwd.hpp" // rank 6
+#include "../proto2/fwd.hpp" // rank 8
+// high/fwd.hpp is rank 9
+
+
+namespace tmwa
+{
+class CharPair;
+class PartyPair;
+struct GM_Account;
+// meh, add more when I feel like it
+} // namespace tmwa
diff --git a/src/mmo/md5more.cpp b/src/high/md5more.cpp
index 4e5d2da..05149ac 100644
--- a/src/mmo/md5more.cpp
+++ b/src/high/md5more.cpp
@@ -31,7 +31,7 @@
#include "../net/ip.hpp"
-#include "../mmo/mmo.hpp"
+#include "mmo.hpp"
#include "../poison.hpp"
diff --git a/src/mmo/md5more.hpp b/src/high/md5more.hpp
index 7d50713..33ebe24 100644
--- a/src/mmo/md5more.hpp
+++ b/src/high/md5more.hpp
@@ -24,10 +24,6 @@
#include "../generic/md5.hpp"
-#include "../io/fwd.hpp"
-
-#include "../net/fwd.hpp"
-
namespace tmwa
{
diff --git a/src/mmo/mmo.hpp b/src/high/mmo.hpp
index cfa278d..b5bcac8 100644
--- a/src/mmo/mmo.hpp
+++ b/src/high/mmo.hpp
@@ -22,9 +22,13 @@
#include "fwd.hpp"
+#include "../compat/borrow.hpp"
#include "../compat/memory.hpp"
-#include "../proto2/types.hpp"
+#include "../proto2/net-CharData.hpp"
+#include "../proto2/net-CharKey.hpp"
+#include "../proto2/net-PartyMost.hpp"
+#include "../proto2/net-SkillValue.hpp"
namespace tmwa
@@ -58,12 +62,10 @@ struct GM_Account
struct PartyPair
{
- PartyId party_id = {};
- PartyMost *party_most = {};
+ PartyId party_id;
+ Borrowed<PartyMost> party_most;
- explicit
- operator bool() const { return party_most; }
- bool operator !() const { return !party_most; }
- PartyMost *operator->() const { return party_most; }
+ PartyMost& operator *() const { return *party_most; }
+ Borrowed<PartyMost> operator->() const { return party_most; }
};
} // namespace tmwa
diff --git a/src/mmo/utils.cpp b/src/high/utils.cpp
index f8aff2e..9b89c31 100644
--- a/src/mmo/utils.cpp
+++ b/src/high/utils.cpp
@@ -28,9 +28,7 @@
#include "../strings/xstring.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/write.hpp"
-
-#include "extract.hpp"
+#include "../io/extract.hpp"
#include "../poison.hpp"
@@ -85,39 +83,4 @@ int config_switch(ZString str)
FPRINTF(stderr, "Fatal: bad option value %s"_fmt, str);
abort();
}
-
-static_assert(sizeof(timestamp_seconds_buffer) == 20, "seconds buffer");
-static_assert(sizeof(timestamp_milliseconds_buffer) == 24, "millis buffer");
-
-void stamp_time(timestamp_seconds_buffer& out, const TimeT *t)
-{
- struct tm when = t ? *t : TimeT::now();
- char buf[20];
- strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when);
- out = stringish<timestamp_seconds_buffer>(VString<19>(strings::really_construct_from_a_pointer, buf));
-}
-void stamp_time(timestamp_milliseconds_buffer& out)
-{
- struct timeval tv;
- gettimeofday(&tv, nullptr);
- struct tm when = TimeT(tv.tv_sec);
- char buf[24];
- strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when);
- sprintf(buf + 19, ".%03d", static_cast<int>(tv.tv_usec / 1000));
- out = stringish<timestamp_milliseconds_buffer>(VString<23>(strings::really_construct_from_a_pointer, buf));
-}
-
-void log_with_timestamp(io::WriteFile& out, XString line)
-{
- if (!line)
- {
- out.put('\n');
- return;
- }
- timestamp_milliseconds_buffer tmpstr;
- stamp_time(tmpstr);
- out.really_put(tmpstr.data(), tmpstr.size());
- out.really_put(": ", 2);
- out.put_line(line);
-}
} // namespace tmwa
diff --git a/src/compat/cast.cpp b/src/high/utils.hpp
index 482529d..c815e03 100644
--- a/src/compat/cast.cpp
+++ b/src/high/utils.hpp
@@ -1,6 +1,8 @@
-#include "cast.hpp"
-// cast.cpp - Change the type of a variable.
+#pragma once
+// utils.hpp - Useful stuff that hasn't been categorized.
//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
@@ -18,9 +20,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
-
+#include "fwd.hpp"
namespace tmwa
{
+bool e_mail_check(XString email);
+int config_switch(ZString str);
} // namespace tmwa
diff --git a/src/ints/fwd.hpp b/src/ints/fwd.hpp
index a08e546..536eba1 100644
--- a/src/ints/fwd.hpp
+++ b/src/ints/fwd.hpp
@@ -20,6 +20,8 @@
#include "../sanity.hpp"
+// ints/fwd.hpp is rank 1
+
namespace tmwa
{
diff --git a/src/ints/little.cpp b/src/ints/little.cpp
deleted file mode 100644
index 0ae5bf7..0000000
--- a/src/ints/little.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "little.hpp"
-// little.cpp - integers of known endianness
-//
-// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/ints/udl.cpp b/src/ints/udl.cpp
deleted file mode 100644
index 3988903..0000000
--- a/src/ints/udl.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "udl.hpp"
-// udl.cpp - user-defined literals for integers.
-//
-// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/ints/wrap.cpp b/src/ints/wrap.cpp
deleted file mode 100644
index 84d4b33..0000000
--- a/src/ints/wrap.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "wrap.hpp"
-// wrap.cpp - basic integer wrapper classes
-//
-// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/ints/wrap.py b/src/ints/wrap.py
index c8a8c6d..d5a6e99 100644
--- a/src/ints/wrap.py
+++ b/src/ints/wrap.py
@@ -9,5 +9,10 @@ class Wrapped(object):
def to_string(self):
return self._value['_value']
+ test_extra = '''
+ void do_breakpoint();
+ void do_breakpoint() {}
+ '''
+
# tests are in src/mmo/ids.py instead
tests = []
diff --git a/src/io/cxxstdio.cpp b/src/io/cxxstdio.cpp
deleted file mode 100644
index ca4e880..0000000
--- a/src/io/cxxstdio.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "cxxstdio.hpp"
-// cxxstdio.cpp - pass C++ types through printf
-//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/io/cxxstdio_enums.cpp b/src/io/cxxstdio_enums.cpp
deleted file mode 100644
index 216da1d..0000000
--- a/src/io/cxxstdio_enums.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "cxxstdio_enums.hpp"
-// cxxstdio_enums.cpp - Opt-in integer formatting support for enums.
-//
-// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/io/dir.hpp b/src/io/dir.hpp
index 071f309..f6fedbf 100644
--- a/src/io/dir.hpp
+++ b/src/io/dir.hpp
@@ -20,8 +20,6 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
#include "fd.hpp"
diff --git a/src/io/extract.cpp b/src/io/extract.cpp
new file mode 100644
index 0000000..fce4dab
--- /dev/null
+++ b/src/io/extract.cpp
@@ -0,0 +1,220 @@
+#include "extract.hpp"
+// extract.cpp - a simple, hierarchical, tokenizer
+//
+// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <algorithm>
+
+#include "../strings/astring.hpp"
+#include "../strings/xstring.hpp"
+#include "../strings/vstring.hpp"
+
+#include "../poison.hpp"
+
+
+// TODO also pass an io::LineSpan around.
+namespace tmwa
+{
+bool impl_extract(XString str, XString *rv)
+{
+ *rv = str;
+ return true;
+}
+
+bool impl_extract(XString str, RString *rv)
+{
+ *rv = str;
+ return true;
+}
+
+bool impl_extract(XString str, AString *rv)
+{
+ *rv = str;
+ return true;
+}
+
+bool impl_extract(XString str, std::chrono::nanoseconds *ns)
+{
+ std::chrono::nanoseconds::rep rep;
+ if (extract(str, &rep))
+ {
+ *ns = std::chrono::nanoseconds(rep);
+ return true;
+ }
+ if (str.endswith("ns"_s))
+ {
+ if (extract(str.xrslice_h("ns"_s.size()), &rep))
+ {
+ *ns = std::chrono::nanoseconds(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::microseconds bigger;
+ if (extract(str, &bigger))
+ {
+ *ns = bigger;
+ return *ns == bigger;
+ }
+ return false;
+}
+bool impl_extract(XString str, std::chrono::microseconds *us)
+{
+ std::chrono::microseconds::rep rep;
+ if (extract(str, &rep))
+ {
+ *us = std::chrono::microseconds(rep);
+ return true;
+ }
+ if (str.endswith("us"_s))
+ {
+ if (extract(str.xrslice_h("us"_s.size()), &rep))
+ {
+ *us = std::chrono::microseconds(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::milliseconds bigger;
+ if (extract(str, &bigger))
+ {
+ *us = bigger;
+ return *us == bigger;
+ }
+ return false;
+}
+bool impl_extract(XString str, std::chrono::milliseconds *ms)
+{
+ std::chrono::milliseconds::rep rep;
+ if (extract(str, &rep))
+ {
+ *ms = std::chrono::milliseconds(rep);
+ return true;
+ }
+ if (str.endswith("ms"_s))
+ {
+ if (extract(str.xrslice_h("ms"_s.size()), &rep))
+ {
+ *ms = std::chrono::milliseconds(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::seconds bigger;
+ if (extract(str, &bigger))
+ {
+ *ms = bigger;
+ return *ms == bigger;
+ }
+ return false;
+}
+bool impl_extract(XString str, std::chrono::seconds *s)
+{
+ std::chrono::seconds::rep rep;
+ if (extract(str, &rep))
+ {
+ *s = std::chrono::seconds(rep);
+ return true;
+ }
+ if (str.endswith("s"_s))
+ {
+ if (extract(str.xrslice_h("s"_s.size()), &rep))
+ {
+ *s = std::chrono::seconds(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::minutes bigger;
+ if (extract(str, &bigger))
+ {
+ *s = bigger;
+ return *s == bigger;
+ }
+ return false;
+}
+bool impl_extract(XString str, std::chrono::minutes *min)
+{
+ std::chrono::minutes::rep rep;
+ if (extract(str, &rep))
+ {
+ *min = std::chrono::minutes(rep);
+ return true;
+ }
+ if (str.endswith("min"_s))
+ {
+ if (extract(str.xrslice_h("min"_s.size()), &rep))
+ {
+ *min = std::chrono::minutes(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::hours bigger;
+ if (extract(str, &bigger))
+ {
+ *min = bigger;
+ return *min == bigger;
+ }
+ return false;
+}
+bool impl_extract(XString str, std::chrono::hours *h)
+{
+ std::chrono::hours::rep rep;
+ if (extract(str, &rep))
+ {
+ *h = std::chrono::hours(rep);
+ return true;
+ }
+ if (str.endswith("h"_s))
+ {
+ if (extract(str.xrslice_h("h"_s.size()), &rep))
+ {
+ *h = std::chrono::hours(rep);
+ return true;
+ }
+ return false;
+ }
+ std::chrono::duration<int, std::ratio<60*60*24>> bigger;
+ if (extract(str, &bigger))
+ {
+ *h = bigger;
+ return *h == bigger;
+ }
+ return false;
+}
+bool impl_extract(XString str, std::chrono::duration<int, std::ratio<60*60*24>> *d)
+{
+ std::chrono::duration<int, std::ratio<60*60*24>>::rep rep;
+ if (extract(str, &rep))
+ {
+ *d = std::chrono::duration<int, std::ratio<60*60*24>>(rep);
+ return true;
+ }
+ if (str.endswith("d"_s))
+ {
+ if (extract(str.xrslice_h("d"_s.size()), &rep))
+ {
+ *d = std::chrono::duration<int, std::ratio<60*60*24>>(rep);
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+} // namespace tmwa
diff --git a/src/mmo/extract.hpp b/src/io/extract.hpp
index ed2eb78..897f50e 100644
--- a/src/mmo/extract.hpp
+++ b/src/io/extract.hpp
@@ -24,24 +24,25 @@
#include <cstdlib>
#include <algorithm>
+#include <chrono>
#include <vector>
#include "../ints/wrap.hpp"
#include "../strings/xstring.hpp"
-#include "../generic/enum.hpp"
+#include "../compat/time_t.hpp"
-#include "utils.hpp"
+#include "../generic/enum.hpp"
namespace tmwa
{
template<class T>
-bool do_extract(XString str, T t);
+bool extract(XString str, T t);
template<class T, typename=typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && !std::is_same<T, bool>::value>::type>
-bool extract(XString str, T *iv)
+bool impl_extract(XString str, T *iv)
{
if (!str || str.size() > 20)
return false;
@@ -74,7 +75,7 @@ bool extract(XString str, T *iv)
}
inline
-bool extract(XString str, TimeT *tv)
+bool impl_extract(XString str, TimeT *tv)
{
return extract(str, &tv->value);
}
@@ -92,12 +93,12 @@ bool extract_as_int(XString str, T *iv)
return true;
}
-bool extract(XString str, XString *rv);
-
-bool extract(XString str, AString *rv);
+bool impl_extract(XString str, XString *rv);
+bool impl_extract(XString str, RString *rv);
+bool impl_extract(XString str, AString *rv);
template<uint8_t N>
-bool extract(XString str, VString<N> *out)
+bool impl_extract(XString str, VString<N> *out)
{
if (str.size() > N)
return false;
@@ -106,7 +107,7 @@ bool extract(XString str, VString<N> *out)
}
inline
-bool extract(XString str, LString exact)
+bool impl_extract(XString str, LString exact)
{
return str == exact;
}
@@ -125,7 +126,7 @@ LStripper<T> lstripping(T v)
}
template<class T>
-bool extract(XString str, LStripper<T> out)
+bool impl_extract(XString str, LStripper<T> out)
{
return extract(str.lstrip(), out.impl);
}
@@ -162,13 +163,13 @@ Record<split, n, T...> record(T... t)
}
template<char split, int n>
-bool extract(XString str, Record<split, n>)
+bool impl_extract(XString str, Record<split, n>)
{
return !str;
}
template<char split, int n, class F, class... R>
-bool extract(XString str, Record<split, n, F, R...> rec)
+bool impl_extract(XString str, Record<split, n, F, R...> rec)
{
XString::iterator s = std::find(str.begin(), str.end(), split);
XString::iterator s2 = s;
@@ -197,7 +198,7 @@ VRecord<split, T> vrec(std::vector<T> *arr)
}
template<char split, class T>
-bool extract(XString str, VRecord<split, T> rec)
+bool impl_extract(XString str, VRecord<split, T> rec)
{
if (!str)
return true;
@@ -209,23 +210,25 @@ bool extract(XString str, VRecord<split, T> rec)
&& extract(str.xislice_t(s + 1), rec);
}
-bool extract(XString str, GlobalReg *var);
-
-bool extract(XString str, Item *it);
-
-bool extract(XString str, MapName *m);
-
-bool extract(XString str, CharName *out);
-
-template<class T>
-bool do_extract(XString str, T t)
+template<class R>
+bool impl_extract(XString str, Wrapped<R> *w)
{
- return extract(str, t);
+ return extract(str, &w->_value);
}
-template<class R>
-bool extract(XString str, Wrapped<R> *w)
+bool impl_extract(XString str, std::chrono::nanoseconds *ns);
+bool impl_extract(XString str, std::chrono::microseconds *us);
+bool impl_extract(XString str, std::chrono::milliseconds *ms);
+bool impl_extract(XString str, std::chrono::seconds *s);
+bool impl_extract(XString str, std::chrono::minutes *min);
+bool impl_extract(XString str, std::chrono::hours *h);
+bool impl_extract(XString str, std::chrono::duration<int, std::ratio<60*60*24>> *d);
+
+// this must come after all non-`namespace tmwa` `impl_extract`s.
+// In particular, the ones for `*int*` and `std::chrono::*`
+template<class T>
+bool extract(XString str, T t)
{
- return extract(str, &w->_value);
+ return impl_extract(str, t);
}
} // namespace tmwa
diff --git a/src/mmo/extract_test.cpp b/src/io/extract_test.cpp
index e6dc7b2..ee4cb08 100644
--- a/src/mmo/extract_test.cpp
+++ b/src/io/extract_test.cpp
@@ -22,7 +22,11 @@
#include "../strings/xstring.hpp"
-#include "mmo.hpp"
+#include "../net/timer.t.hpp"
+
+#include "../mmo/strs.hpp"
+
+#include "../high/extract_mmo.hpp"
#include "../poison.hpp"
@@ -357,4 +361,92 @@ TEST(extract, mapname)
EXPECT_TRUE(extract("abcdefghijklmno.gat"_s, &map));
EXPECT_EQ(map, "abcdefghijklmno"_s);
}
+
+TEST(extract, chrono)
+{
+ std::chrono::nanoseconds ns;
+ std::chrono::microseconds us;
+ std::chrono::milliseconds ms;
+ std::chrono::seconds s;
+ std::chrono::minutes min;
+ std::chrono::hours h;
+ std::chrono::duration<int, std::ratio<60*60*24>> d;
+
+ EXPECT_TRUE(extract("1"_s, &ns));
+ EXPECT_EQ(ns, 1_ns);
+ EXPECT_TRUE(extract("3ns"_s, &ns));
+ EXPECT_EQ(ns, 3_ns);
+ EXPECT_TRUE(extract("4us"_s, &ns));
+ EXPECT_EQ(ns, 4_us);
+ EXPECT_TRUE(extract("5ms"_s, &ns));
+ EXPECT_EQ(ns, 5_ms);
+ EXPECT_TRUE(extract("6s"_s, &ns));
+ EXPECT_EQ(ns, 6_s);
+ EXPECT_TRUE(extract("7min"_s, &ns));
+ EXPECT_EQ(ns, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &ns));
+ EXPECT_EQ(ns, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &ns));
+ EXPECT_EQ(ns, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &us));
+ EXPECT_EQ(us, 1_us);
+ EXPECT_TRUE(extract("4us"_s, &us));
+ EXPECT_EQ(us, 4_us);
+ EXPECT_TRUE(extract("5ms"_s, &us));
+ EXPECT_EQ(us, 5_ms);
+ EXPECT_TRUE(extract("6s"_s, &us));
+ EXPECT_EQ(us, 6_s);
+ EXPECT_TRUE(extract("7min"_s, &us));
+ EXPECT_EQ(us, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &us));
+ EXPECT_EQ(us, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &us));
+ EXPECT_EQ(us, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &ms));
+ EXPECT_EQ(ms, 1_ms);
+ EXPECT_TRUE(extract("5ms"_s, &ms));
+ EXPECT_EQ(ms, 5_ms);
+ EXPECT_TRUE(extract("6s"_s, &ms));
+ EXPECT_EQ(ms, 6_s);
+ EXPECT_TRUE(extract("7min"_s, &ms));
+ EXPECT_EQ(ms, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &ms));
+ EXPECT_EQ(ms, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &ms));
+ EXPECT_EQ(ms, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &s));
+ EXPECT_EQ(s, 1_s);
+ EXPECT_TRUE(extract("6s"_s, &s));
+ EXPECT_EQ(s, 6_s);
+ EXPECT_TRUE(extract("7min"_s, &s));
+ EXPECT_EQ(s, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &s));
+ EXPECT_EQ(s, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &s));
+ EXPECT_EQ(s, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &min));
+ EXPECT_EQ(min, 1_min);
+ EXPECT_TRUE(extract("7min"_s, &min));
+ EXPECT_EQ(min, 7_min);
+ EXPECT_TRUE(extract("8h"_s, &min));
+ EXPECT_EQ(min, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &min));
+ EXPECT_EQ(min, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &h));
+ EXPECT_EQ(h, 1_h);
+ EXPECT_TRUE(extract("8h"_s, &h));
+ EXPECT_EQ(h, 8_h);
+ EXPECT_TRUE(extract("9d"_s, &h));
+ EXPECT_EQ(h, 9_d);
+
+ EXPECT_TRUE(extract("1"_s, &d));
+ EXPECT_EQ(d, 1_d);
+ EXPECT_TRUE(extract("9d"_s, &d));
+ EXPECT_EQ(d, 9_d);
+}
} // namespace tmwa
diff --git a/src/io/fd.cpp b/src/io/fd.cpp
index c0b44e8..bb0bbd5 100644
--- a/src/io/fd.cpp
+++ b/src/io/fd.cpp
@@ -102,6 +102,11 @@ namespace io
int FD::close()
{
+ if (fd == -1)
+ {
+ errno = EBADF;
+ return -1;
+ }
return ::close(fd);
}
int FD::shutdown(int how)
diff --git a/src/io/fd.hpp b/src/io/fd.hpp
index d04d5bf..03a8b44 100644
--- a/src/io/fd.hpp
+++ b/src/io/fd.hpp
@@ -23,8 +23,6 @@
#include <sys/select.h>
#include <sys/socket.h>
-#include "../strings/fwd.hpp"
-
#include "../diagnostics.hpp"
diff --git a/src/io/fwd.hpp b/src/io/fwd.hpp
index deeb08c..3b9452b 100644
--- a/src/io/fwd.hpp
+++ b/src/io/fwd.hpp
@@ -20,6 +20,12 @@
#include "../sanity.hpp"
+#include "../ints/fwd.hpp" // rank 1
+#include "../strings/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp" // rank 2
+#include "../generic/fwd.hpp" // rank 3
+// io/fwd.hpp is rank 4
+
namespace tmwa
{
@@ -28,5 +34,12 @@ namespace io
class ReadFile;
class WriteFile;
class AppendFile;
+ class LineReader;
+ class LineCharReader;
+ class Line;
+ class LineChar;
+ class LineSpan;
+ template<class T>
+ class Spanned;
} // namespace io
} // namespace tmwa
diff --git a/src/io/line.cpp b/src/io/line.cpp
index a1cdf42..5d7e792 100644
--- a/src/io/line.cpp
+++ b/src/io/line.cpp
@@ -19,9 +19,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "../strings/astring.hpp"
-#include "../strings/mstring.hpp"
#include "../strings/zstring.hpp"
-#include "../strings/xstring.hpp"
#include "cxxstdio.hpp"
@@ -32,71 +30,6 @@ namespace tmwa
{
namespace io
{
- AString Line::message_str(ZString cat, ZString msg) const
- {
- MString out;
- if (column)
- out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt,
- filename, line, column, cat, msg);
- else
- out += STRPRINTF("%s:%u: %s: %s\n"_fmt,
- filename, line, cat, msg);
- out += STRPRINTF("%s\n"_fmt, text);
- out += STRPRINTF("%*c\n"_fmt, column, '^');
- return AString(out);
- }
-
- void Line::message(ZString cat, ZString msg) const
- {
- if (column)
- FPRINTF(stderr, "%s:%u:%u: %s: %s\n"_fmt,
- filename, line, column, cat, msg);
- else
- FPRINTF(stderr, "%s:%u: %s: %s\n"_fmt,
- filename, line, cat, msg);
- FPRINTF(stderr, "%s\n"_fmt, text);
- FPRINTF(stderr, "%*c\n"_fmt, column, '^');
- }
-
- AString LineSpan::message_str(ZString cat, ZString msg) const
- {
- assert (begin.column);
- assert (end.column);
- assert (begin.line < end.line || begin.column <= end.column);
-
- MString out;
- if (begin.line == end.line)
- {
- out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt,
- begin.filename, begin.line, begin.column, cat, msg);
- out += STRPRINTF("%s\n"_fmt, begin.text);
- out += STRPRINTF("%*c"_fmt, begin.column, '^');
- for (unsigned c = begin.column; c != end.column; ++c)
- out += '~';
- out += '\n';
- }
- else
- {
- out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt,
- begin.filename, begin.line, begin.column, cat, msg);
- out += STRPRINTF("%s\n"_fmt, begin.text);
- out += STRPRINTF("%*c"_fmt, begin.column, '^');
- for (unsigned c = begin.column; c != begin.text.size(); ++c)
- out += '~';
- out += " ...\n"_s;
- out += STRPRINTF("%s\n"_fmt, end.text);
- for (unsigned c = 0; c != end.column; ++c)
- out += '~';
- out += '\n';
- }
- return AString(out);
- }
-
- void LineSpan::message(ZString cat, ZString msg) const
- {
- FPRINTF(stderr, "%s"_fmt, message_str(cat, msg));
- }
-
LineReader::LineReader(ZString name)
: filename(name), line(0), column(0), rf(name)
{}
@@ -105,6 +38,14 @@ namespace io
: filename(name), line(0), column(0), rf(fd)
{}
+ LineReader::LineReader(read_file_from_string, ZString name, XString content, int startline, FD fd)
+ : filename(name), line(startline-1), column(0), rf(from_string, content, fd)
+ {}
+
+ LineReader::LineReader(read_file_from_string, ZString name, LString content, int startline, FD fd)
+ : filename(name), line(startline-1), column(0), rf(from_string, content, fd)
+ {}
+
bool LineReader::read_line(Line& l)
{
AString text;
@@ -145,6 +86,38 @@ namespace io
column = 0;
}
+ LineCharReader::LineCharReader(read_file_from_string, ZString name, XString content, int startline, int startcol, FD fd)
+ : LineReader(from_string, name, content, 1, fd)
+ {
+ column = 1; // not 0, not whole line
+ if (rf.is_open())
+ adv();
+ if (!line)
+ column = 0;
+ else
+ {
+ line = startline;
+ column = startcol;
+ line_text = STRPRINTF("%*s"_fmt, static_cast<int>(column-1 + line_text.size()), line_text);
+ }
+ }
+
+ LineCharReader::LineCharReader(read_file_from_string, ZString name, LString content, int startline, int startcol, FD fd)
+ : LineReader(from_string, name, content, 1, fd)
+ {
+ column = 1; // not 0, not whole line
+ if (rf.is_open())
+ adv();
+ if (!line)
+ column = 0;
+ else
+ {
+ line = startline;
+ column = startcol;
+ line_text = STRPRINTF("%*s"_fmt, static_cast<int>(column-1 + line_text.size()), line_text);
+ }
+ }
+
bool LineCharReader::get(LineChar& c)
{
if (!column)
diff --git a/src/io/line.hpp b/src/io/line.hpp
index 8244c5e..c94eeb9 100644
--- a/src/io/line.hpp
+++ b/src/io/line.hpp
@@ -21,55 +21,15 @@
#include "fwd.hpp"
#include "../strings/rstring.hpp"
-#include "../strings/zstring.hpp"
-#include "../strings/literal.hpp"
#include "read.hpp"
+#include "span.hpp"
namespace tmwa
{
namespace io
{
- // TODO split this out
- struct Line
- {
- RString text;
-
- RString filename;
- // 1-based
- uint16_t line, column;
-
- AString message_str(ZString cat, ZString msg) const;
- void message(ZString cat, ZString msg) const;
- void note(ZString msg) const { message("note"_s, msg); }
- void warning(ZString msg) const { message("warning"_s, msg); }
- void error(ZString msg) const { message("error"_s, msg); }
- };
-
- // psst, don't tell anyone
- struct LineChar : Line
- {
- char ch()
- {
- size_t c = column - 1;
- if (c == text.size())
- return '\n';
- return text[c];
- }
- };
-
- struct LineSpan
- {
- LineChar begin, end;
-
- AString message_str(ZString cat, ZString msg) const;
- void message(ZString cat, ZString msg) const;
- void note(ZString msg) const { message("note"_s, msg); }
- void warning(ZString msg) const { message("warning"_s, msg); }
- void error(ZString msg) const { message("error"_s, msg); }
- };
-
class LineReader
{
protected:
@@ -83,6 +43,8 @@ namespace io
LineReader& operator = (LineReader&&) = delete;
// needed for unit tests
LineReader(ZString name, FD fd);
+ LineReader(read_file_from_string, ZString name, XString content, int startline=1, FD fd=FD());
+ LineReader(read_file_from_string, ZString name, LString content, int startline=1, FD fd=FD());
bool read_line(Line& l);
bool is_open();
@@ -97,6 +59,8 @@ namespace io
LineCharReader(LineCharReader&&) = delete;
LineCharReader& operator = (LineCharReader&&) = delete;
LineCharReader(ZString name, FD fd);
+ LineCharReader(read_file_from_string, ZString name, XString content, int startline=1, int startcol=1, FD fd=FD());
+ LineCharReader(read_file_from_string, ZString name, LString content, int startline=1, int startcol=1, FD fd=FD());
bool get(LineChar& c);
void adv();
diff --git a/src/io/line_test.cpp b/src/io/line_test.cpp
index edf60bd..582ee81 100644
--- a/src/io/line_test.cpp
+++ b/src/io/line_test.cpp
@@ -23,6 +23,8 @@
#include "../strings/astring.hpp"
#include "../strings/zstring.hpp"
+#include "../tests/fdhack.hpp"
+
#include "../poison.hpp"
@@ -57,6 +59,7 @@ TEST(io, line1)
}
TEST(io, line2)
{
+ QuietFd q;
io::LineReader lr("<string2>"_s, string_pipe("Hello\nWorld"_s));
io::Line hi;
EXPECT_TRUE(lr.read_line(hi));
@@ -73,6 +76,7 @@ TEST(io, line2)
}
TEST(io, line3)
{
+ QuietFd q;
io::LineReader lr("<string3>"_s, string_pipe("Hello\rWorld"_s));
io::Line hi;
EXPECT_TRUE(lr.read_line(hi));
@@ -89,6 +93,7 @@ TEST(io, line3)
}
TEST(io, line4)
{
+ QuietFd q;
io::LineReader lr("<string4>"_s, string_pipe("Hello\r\nWorld"_s));
io::Line hi;
EXPECT_TRUE(lr.read_line(hi));
@@ -105,6 +110,7 @@ TEST(io, line4)
}
TEST(io, line5)
{
+ QuietFd q;
io::LineReader lr("<string5>"_s, string_pipe("Hello\n\rWorld"_s));
io::Line hi;
EXPECT_TRUE(lr.read_line(hi));
@@ -124,6 +130,20 @@ TEST(io, line5)
EXPECT_EQ(hi.column, 0);
EXPECT_FALSE(lr.read_line(hi));
}
+TEST(io, line1text)
+{
+ io::LineReader lr(io::from_string, "<string1text>"_s, "Hello\nWorld"_s, 2);
+ io::Line hi;
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "Hello"_s);
+ EXPECT_EQ(hi.line, 2);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_TRUE(lr.read_line(hi));
+ EXPECT_EQ(hi.text, "World"_s);
+ EXPECT_EQ(hi.line, 3);
+ EXPECT_EQ(hi.column, 0);
+ EXPECT_FALSE(lr.read_line(hi));
+}
TEST(io, linechar1)
{
@@ -175,6 +195,7 @@ TEST(io, linechar1)
}
TEST(io, linechar2)
{
+ QuietFd q;
io::LineCharReader lr("<stringchar2>"_s, string_pipe("Hi\nWu"_s));
io::LineChar c;
EXPECT_TRUE(lr.get(c));
@@ -223,6 +244,7 @@ TEST(io, linechar2)
}
TEST(io, linechar3)
{
+ QuietFd q;
io::LineCharReader lr("<stringchar3>"_s, string_pipe("Hi\rWu"_s));
io::LineChar c;
EXPECT_TRUE(lr.get(c));
@@ -271,6 +293,7 @@ TEST(io, linechar3)
}
TEST(io, linechar4)
{
+ QuietFd q;
io::LineCharReader lr("<stringchar4>"_s, string_pipe("Hi\r\nWu"_s));
io::LineChar c;
EXPECT_TRUE(lr.get(c));
@@ -319,6 +342,7 @@ TEST(io, linechar4)
}
TEST(io, linechar5)
{
+ QuietFd q;
io::LineCharReader lr("<stringchar5>"_s, string_pipe("Hi\n\rWu"_s));
io::LineChar c;
EXPECT_TRUE(lr.get(c));
@@ -372,6 +396,42 @@ TEST(io, linechar5)
lr.adv();
EXPECT_FALSE(lr.get(c));
}
+TEST(io, linechar1text)
+{
+ io::LineCharReader lr(io::from_string, "<stringchar1text>"_s, "Hi\nWu\n"_s, 2, 3);
+ io::LineChar c;
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'H');
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'i');
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 4);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.line, 2);
+ EXPECT_EQ(c.column, 5);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'W');
+ EXPECT_EQ(c.line, 3);
+ EXPECT_EQ(c.column, 1);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), 'u');
+ EXPECT_EQ(c.line, 3);
+ EXPECT_EQ(c.column, 2);
+ lr.adv();
+ EXPECT_TRUE(lr.get(c));
+ EXPECT_EQ(c.ch(), '\n');
+ EXPECT_EQ(c.line, 3);
+ EXPECT_EQ(c.column, 3);
+ lr.adv();
+ EXPECT_FALSE(lr.get(c));
+}
TEST(io, linespan)
{
@@ -402,17 +462,17 @@ TEST(io, linespan)
}
while (span.end.ch() != 'r');
- EXPECT_EQ(span.begin.message_str("note"_s, "foo"_s),
+ EXPECT_EQ(span.begin.note_str("foo"_s),
"<span>:1:5: note: foo\n"
"Hello,\n"
" ^\n"_s
);
- EXPECT_EQ(span.end.message_str("warning"_s, "bar"_s),
+ EXPECT_EQ(span.end.warning_str("bar"_s),
"<span>:2:3: warning: bar\n"
"World!\n"
" ^\n"_s
);
- EXPECT_EQ(span.message_str("error"_s, "qux"_s),
+ EXPECT_EQ(span.error_str("qux"_s),
"<span>:1:5: error: qux\n"
"Hello,\n"
" ^~ ...\n"
diff --git a/src/io/read.cpp b/src/io/read.cpp
index 3ae5246..32974d6 100644
--- a/src/io/read.cpp
+++ b/src/io/read.cpp
@@ -1,7 +1,7 @@
#include "read.hpp"
// io/read.cpp - Input from files
//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -25,7 +25,7 @@
#include "../strings/zstring.hpp"
#include "../strings/literal.hpp"
-#include "../io/cxxstdio.hpp"
+#include "cxxstdio.hpp"
#include "../poison.hpp"
@@ -36,6 +36,8 @@ namespace io
{
ReadFile::ReadFile(FD f)
: fd(f), start(0), end(0)
+ // only for debug-sanity
+ , buf{}
{
}
ReadFile::ReadFile(ZString name)
@@ -46,6 +48,32 @@ namespace io
: fd(dir.open_fd(name, O_RDONLY | O_CLOEXEC)), start(0), end(0)
{
}
+ ReadFile::ReadFile(read_file_from_string, XString content, FD f)
+ : fd(f), start(0), end(), extra()
+ {
+ if (content.size() <= 4096)
+ {
+ end = content.size();
+ auto z = std::copy(content.begin(), content.end(), buf);
+ // only for debug sanity
+ std::fill(z, std::end(buf), 0);
+ return;
+ }
+ auto base = content.base();
+ if (!base)
+ {
+ extra = content;
+ end = content.size();
+ return;
+ }
+ start = &*content.begin() - &*base->begin();
+ end = &*content.end() - &*base->begin();
+ extra = *base;
+ }
+ ReadFile::ReadFile(read_file_from_string, LString content, FD f)
+ : ReadFile(from_string, RString(content), f)
+ {
+ }
ReadFile::~ReadFile()
{
fd.close();
@@ -54,6 +82,14 @@ namespace io
bool ReadFile::get(char& c)
{
+ if (extra)
+ {
+ c = extra[start];
+ ++start;
+ if (start == end)
+ extra = ""_s;
+ return true;
+ }
if (start == end)
{
if (fd == FD())
@@ -82,6 +118,7 @@ namespace io
}
bool ReadFile::getline(AString& line)
{
+ bool was_real_file = fd != FD();
MString tmp;
char c;
bool anything = false;
@@ -115,15 +152,17 @@ namespace io
else
FPRINTF(stderr, "warning: file contains bare CR\n"_fmt);
}
- else if (!happy && anything)
+ else if (!happy && anything && was_real_file)
+ {
FPRINTF(stderr, "warning: file does not contain a trailing newline\n"_fmt);
+ }
line = AString(tmp);
return anything;
}
bool ReadFile::is_open()
{
- return fd != FD();
+ return fd != FD() || start != end;
}
} // namespace io
} // namespace tmwa
diff --git a/src/io/read.hpp b/src/io/read.hpp
index 1ec26ca..2e3611b 100644
--- a/src/io/read.hpp
+++ b/src/io/read.hpp
@@ -1,7 +1,7 @@
#pragma once
// io/read.hpp - Input from files.
//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -20,7 +20,7 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
+#include "../strings/rstring.hpp"
#include "dir.hpp"
#include "fd.hpp"
@@ -29,18 +29,28 @@ namespace tmwa
{
namespace io
{
+ enum read_file_from_string
+ {
+ from_string,
+ };
+
+ // TODO - for internal warnings, it would be convenient if this class
+ // didn't exist at all, and instead everything was done with line info.
class ReadFile
{
private:
FD fd;
unsigned short start, end;
char buf[4096];
+ RString extra;
public:
explicit
ReadFile(FD fd);
explicit
ReadFile(ZString name);
ReadFile(const DirFd& dir, ZString name);
+ ReadFile(read_file_from_string, XString content, FD fd=FD());
+ ReadFile(read_file_from_string, LString content, FD fd=FD());
ReadFile& operator = (ReadFile&&) = delete;
ReadFile(ReadFile&&) = delete;
diff --git a/src/io/read_test.cpp b/src/io/read_test.cpp
index 8fe84b7..22c67c8 100644
--- a/src/io/read_test.cpp
+++ b/src/io/read_test.cpp
@@ -24,6 +24,8 @@
#include "../strings/zstring.hpp"
#include "../strings/literal.hpp"
+#include "../tests/fdhack.hpp"
+
#include "../poison.hpp"
@@ -47,6 +49,7 @@ io::FD string_pipe(ZString sz)
TEST(io, read1)
{
+ QuietFd q;
io::ReadFile rf(string_pipe("Hello"_s));
AString hi;
EXPECT_TRUE(rf.getline(hi));
@@ -63,6 +66,7 @@ TEST(io, read2)
}
TEST(io, read3)
{
+ QuietFd q;
io::ReadFile rf(string_pipe("Hello\r"_s));
AString hi;
EXPECT_TRUE(rf.getline(hi));
@@ -71,6 +75,7 @@ TEST(io, read3)
}
TEST(io, read4)
{
+ QuietFd q;
io::ReadFile rf(string_pipe("Hello\r\n"_s));
AString hi;
EXPECT_TRUE(rf.getline(hi));
@@ -79,6 +84,7 @@ TEST(io, read4)
}
TEST(io, read5)
{
+ QuietFd q;
io::ReadFile rf(string_pipe("Hello\n\r"_s));
AString hi;
EXPECT_TRUE(rf.getline(hi));
@@ -87,4 +93,65 @@ TEST(io, read5)
EXPECT_FALSE(hi);
EXPECT_FALSE(rf.getline(hi));
}
+
+#define S15 "0123456789abcde"_s
+#define S16 "0123456789abcdef"_s
+#define S255 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S15
+#define S256 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16
+#define S4095 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S255
+#define S4096 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256
+
+TEST(io, readstringr)
+{
+ LString tests[] =
+ {
+ S15,
+ S16,
+ S255,
+ S256,
+ S4095,
+ S4096,
+ S4096 S16,
+ };
+ for (RString test : tests)
+ {
+ char buf[test.size() + 1];
+
+ io::ReadFile rf(io::from_string, test);
+ EXPECT_EQ(rf.get(buf, sizeof(buf)), test.size());
+ EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr));
+
+ io::ReadFile rf2(io::from_string, test, string_pipe("\na"_s));
+ EXPECT_EQ(rf2.get(buf, sizeof(buf)), test.size() + 1);
+ EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr));
+ EXPECT_EQ('\n', buf[test.size()]);
+ }
+}
+
+TEST(io, readstringx)
+{
+ LString tests[] =
+ {
+ S15,
+ S16,
+ S255,
+ S256,
+ S4095,
+ S4096,
+ S4096 S16,
+ };
+ for (XString test : tests)
+ {
+ char buf[test.size() + 1];
+
+ io::ReadFile rf(io::from_string, test);
+ EXPECT_EQ(rf.get(buf, sizeof(buf)), test.size());
+ EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr));
+
+ io::ReadFile rf2(io::from_string, test, string_pipe("\na"_s));
+ EXPECT_EQ(rf2.get(buf, sizeof(buf)), test.size() + 1);
+ EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr));
+ EXPECT_EQ('\n', buf[test.size()]);
+ }
+}
} // namespace tmwa
diff --git a/src/io/span.cpp b/src/io/span.cpp
new file mode 100644
index 0000000..6d116c7
--- /dev/null
+++ b/src/io/span.cpp
@@ -0,0 +1,113 @@
+#include "span.hpp"
+// io/span.cpp - Tracking info about input
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../strings/astring.hpp"
+#include "../strings/mstring.hpp"
+#include "../strings/zstring.hpp"
+
+#include "cxxstdio.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace io
+{
+ io::LineSpan Line::to_span() const
+ {
+ io::LineSpan rv;
+ rv.begin.text = this->text;
+ rv.begin.filename = this->filename;
+ rv.begin.line = this->line;
+ rv.begin.column = 1;
+ rv.end.text = this->text;
+ rv.end.filename = this->filename;
+ rv.end.line = this->line;
+ rv.end.column = this->text.size();
+ return rv;
+ }
+
+ AString Line::message_str(ZString cat, ZString msg) const
+ {
+ MString out;
+ if (column)
+ out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt,
+ filename, line, column, cat, msg);
+ else
+ out += STRPRINTF("%s:%u: %s: %s\n"_fmt,
+ filename, line, cat, msg);
+ out += STRPRINTF("%s\n"_fmt, text);
+ out += STRPRINTF("%*c\n"_fmt, column, '^');
+ return AString(out);
+ }
+
+ void Line::message(ZString cat, ZString msg) const
+ {
+ if (column)
+ FPRINTF(stderr, "%s:%u:%u: %s: %s\n"_fmt,
+ filename, line, column, cat, msg);
+ else
+ FPRINTF(stderr, "%s:%u: %s: %s\n"_fmt,
+ filename, line, cat, msg);
+ FPRINTF(stderr, "%s\n"_fmt, text);
+ FPRINTF(stderr, "%*c\n"_fmt, column, '^');
+ }
+
+ AString LineSpan::message_str(ZString cat, ZString msg) const
+ {
+ assert (begin.column);
+ assert (end.column);
+ assert (begin.line < end.line || begin.column <= end.column);
+
+ MString out;
+ if (begin.line == end.line)
+ {
+ out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt,
+ begin.filename, begin.line, begin.column, cat, msg);
+ out += STRPRINTF("%s\n"_fmt, begin.text);
+ out += STRPRINTF("%*c"_fmt, begin.column, '^');
+ for (unsigned c = begin.column; c != end.column; ++c)
+ out += '~';
+ out += '\n';
+ }
+ else
+ {
+ out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt,
+ begin.filename, begin.line, begin.column, cat, msg);
+ out += STRPRINTF("%s\n"_fmt, begin.text);
+ out += STRPRINTF("%*c"_fmt, begin.column, '^');
+ for (unsigned c = begin.column; c != begin.text.size(); ++c)
+ out += '~';
+ out += " ...\n"_s;
+ out += STRPRINTF("%s\n"_fmt, end.text);
+ for (unsigned c = 0; c != end.column; ++c)
+ out += '~';
+ out += '\n';
+ }
+ return AString(out);
+ }
+
+ void LineSpan::message(ZString cat, ZString msg) const
+ {
+ FPRINTF(stderr, "%s"_fmt, message_str(cat, msg));
+ }
+} // namespace io
+} // namespace tmwa
diff --git a/src/io/span.hpp b/src/io/span.hpp
new file mode 100644
index 0000000..9962b7c
--- /dev/null
+++ b/src/io/span.hpp
@@ -0,0 +1,92 @@
+#pragma once
+// io/span.hpp - Tracking info about input
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../strings/rstring.hpp"
+#include "../strings/zstring.hpp"
+#include "../strings/literal.hpp"
+
+
+namespace tmwa
+{
+namespace io
+{
+ // TODO split this out
+ struct Line
+ {
+ RString text;
+
+ RString filename;
+ // 1-based
+ uint16_t line, column;
+
+ AString message_str(ZString cat, ZString msg) const;
+ AString note_str(ZString msg) const { return message_str("note"_s, msg); }
+ AString warning_str(ZString msg) const { return message_str("warning"_s, msg); }
+ AString error_str(ZString msg) const { return message_str("error"_s, msg); }
+ void message(ZString cat, ZString msg) const;
+ void note(ZString msg) const { message("note"_s, msg); }
+ void warning(ZString msg) const { message("warning"_s, msg); }
+ void error(ZString msg) const { message("error"_s, msg); }
+
+ LineSpan to_span() const;
+ };
+
+ // psst, don't tell anyone
+ struct LineChar : Line
+ {
+ char ch()
+ {
+ size_t c = column - 1;
+ if (c == text.size())
+ return '\n';
+ return text[c];
+ }
+ };
+
+ struct LineSpan
+ {
+ LineChar begin, end;
+
+ AString message_str(ZString cat, ZString msg) const;
+ AString note_str(ZString msg) const { return message_str("note"_s, msg); }
+ AString warning_str(ZString msg) const { return message_str("warning"_s, msg); }
+ AString error_str(ZString msg) const { return message_str("error"_s, msg); }
+ void message(ZString cat, ZString msg) const;
+ void note(ZString msg) const { message("note"_s, msg); }
+ void warning(ZString msg) const { message("warning"_s, msg); }
+ void error(ZString msg) const { message("error"_s, msg); }
+ };
+
+ template<class T>
+ struct Spanned
+ {
+ T data;
+ LineSpan span;
+ };
+
+ template<class T>
+ Spanned<T> respan(LineSpan span, T data)
+ {
+ return Spanned<T>{std::move(data), std::move(span)};
+ }
+} // namespace io
+} // namespace tmwa
diff --git a/src/io/tty.cpp b/src/io/tty.cpp
deleted file mode 100644
index c498740..0000000
--- a/src/io/tty.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "tty.hpp"
-// io/tty.cpp - terminal escape sequences
-//
-// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-/* Nothing to see here, move along */
-} // namespace tmwa
diff --git a/src/io/write.cpp b/src/io/write.cpp
index 5359c7a..a98954b 100644
--- a/src/io/write.cpp
+++ b/src/io/write.cpp
@@ -37,6 +37,8 @@ namespace io
{
WriteFile::WriteFile(FD f, bool linebuffered)
: fd(f), lb(linebuffered), buflen(0)
+ // only for debug-sanity
+ , buf{}
{}
WriteFile::WriteFile(ZString name, bool linebuffered)
: fd(FD::open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666)), lb(linebuffered), buflen(0)
diff --git a/src/io/write.hpp b/src/io/write.hpp
index 1ab05f3..d7d3699 100644
--- a/src/io/write.hpp
+++ b/src/io/write.hpp
@@ -22,8 +22,6 @@
#include <cstdarg>
-#include "../strings/fwd.hpp"
-
#include "dir.hpp"
#include "fd.hpp"
diff --git a/src/map/magic-expr-eval.cpp b/src/login/consts.hpp
index 9903600..4af2e41 100644
--- a/src/map/magic-expr-eval.cpp
+++ b/src/login/consts.hpp
@@ -1,5 +1,6 @@
-#include "magic-expr-eval.hpp"
-// magic-expr-eval.cpp - Utilities for evaluating magic.
+
+#pragma once
+// consts.hpp - Constants for tmwa-login.
//
// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
@@ -18,12 +19,14 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "fwd.hpp"
namespace tmwa
{
-namespace magic
+namespace login
{
-} // namespace magic
+constexpr int MAX_SERVERS = 30;
+constexpr int AUTH_FIFO_SIZE = 256;
+} // namespace login
} // namespace tmwa
diff --git a/src/login/fwd.hpp b/src/login/fwd.hpp
index 94fe3c0..6516404 100644
--- a/src/login/fwd.hpp
+++ b/src/login/fwd.hpp
@@ -20,8 +20,28 @@
#include "../sanity.hpp"
+#include "../ints/fwd.hpp" // rank 1
+#include "../strings/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp"
+#include "../generic/fwd.hpp" // rank 3
+#include "../io/fwd.hpp" // rank 4
+#include "../net/fwd.hpp" // rank 5
+#include "../mmo/fwd.hpp" // rank 6
+#include "../proto2/fwd.hpp" // rank 8
+#include "../high/fwd.hpp" // rank 9
+#include "../wire/fwd.hpp" // rank 9
+// login/fwd.hpp is rank ∞ because it is an executable
+
namespace tmwa
{
-// meh, add more when I feel like it
+namespace login
+{
+ struct LoginConf;
+ struct LoginLanConf;
+ struct AuthFifo;
+ struct mmo_char_server;
+ struct AuthData;
+ // meh, add more when I feel like it
+} // namespace login
} // namespace tmwa
diff --git a/src/login/globals.cpp b/src/login/globals.cpp
new file mode 100644
index 0000000..0c8a150
--- /dev/null
+++ b/src/login/globals.cpp
@@ -0,0 +1,54 @@
+#include "globals.hpp"
+// globals.cpp - Evil global variables for tmwa-login.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../generic/array.hpp"
+#include "../generic/db.hpp"
+
+#include "login.hpp"
+#include "login_conf.hpp"
+#include "login_lan_conf.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+ namespace login
+ {
+ LoginConf login_conf;
+ LoginLanConf login_lan_conf;
+ AccountId account_id_count = START_ACCOUNT_NUM;
+ tick_t creation_time_GM_account_file;
+ Array<mmo_char_server, MAX_SERVERS> server;
+ Array<Session *, MAX_SERVERS> server_session;
+ // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed
+ Array<int, MAX_SERVERS> server_freezeflag;
+ Session *login_session;
+ // minimum level of player/GM (0: player, 1-99: gm) to connect on the server
+ Array<AuthFifo, AUTH_FIFO_SIZE> auth_fifo;
+ // TODO replace with auto_fifo_it
+ int auth_fifo_pos = 0;
+ std::vector<AuthData> auth_data;
+ // TODO make this just be Map<AccountId, GmLevel>
+ Map<AccountId, GM_Account> gm_account_db;
+ // For forked DB writes
+ pid_t pid = 0;
+ } // namespace login
+} // namespace tmwa
diff --git a/src/mmo/consts.cpp b/src/login/globals.hpp
index e49cdf5..541ea98 100644
--- a/src/mmo/consts.cpp
+++ b/src/login/globals.hpp
@@ -1,5 +1,5 @@
-#include "consts.hpp"
-// consts.cpp - empty mess of constants
+#pragma once
+// globals.hpp - Evil global variables for tmwa-login.
//
// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
@@ -18,9 +18,31 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "fwd.hpp"
+
+#include <vector>
+
+#include "../net/timer.t.hpp"
+
+#include "consts.hpp"
namespace tmwa
{
+ namespace login
+ {
+ extern LoginConf login_conf;
+ extern LoginLanConf login_lan_conf;
+ extern AccountId account_id_count;
+ extern tick_t creation_time_GM_account_file;
+ extern Array<mmo_char_server, MAX_SERVERS> server;
+ extern Array<Session *, MAX_SERVERS> server_session;
+ extern Array<int, MAX_SERVERS> server_freezeflag;
+ extern Session *login_session;
+ extern Array<AuthFifo, AUTH_FIFO_SIZE> auth_fifo;
+ extern int auth_fifo_pos;
+ extern std::vector<AuthData> auth_data;
+ extern Map<AccountId, GM_Account> gm_account_db;
+ extern pid_t pid;
+ } // namespace login
} // namespace tmwa
diff --git a/src/login/login.cpp b/src/login/login.cpp
index ccb68fc..92f8cc0 100644
--- a/src/login/login.cpp
+++ b/src/login/login.cpp
@@ -45,23 +45,19 @@
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
#include "../io/lock.hpp"
#include "../io/read.hpp"
+#include "../io/span.hpp"
#include "../io/tty.hpp"
#include "../io/write.hpp"
-#include "../net/packets.hpp"
#include "../net/socket.hpp"
#include "../net/timer.hpp"
#include "../mmo/config_parse.hpp"
-#include "../mmo/core.hpp"
-#include "../mmo/extract.hpp"
#include "../mmo/human_time_diff.hpp"
#include "../mmo/ids.hpp"
-#include "../mmo/md5more.hpp"
-#include "../mmo/mmo.hpp"
-#include "../mmo/utils.hpp"
#include "../mmo/version.hpp"
#include "../proto2/any-user.hpp"
@@ -69,115 +65,23 @@
#include "../proto2/login-char.hpp"
#include "../proto2/login-user.hpp"
-#include "../poison.hpp"
-
+#include "../high/core.hpp"
+#include "../high/extract_mmo.hpp"
+#include "../high/md5more.hpp"
+#include "../high/mmo.hpp"
+#include "../high/utils.hpp"
-namespace tmwa
-{
-constexpr int MAX_SERVERS = 30;
+#include "../wire/packets.hpp"
-constexpr AccountId START_ACCOUNT_NUM = wrap<AccountId>(2000000);
-constexpr AccountId END_ACCOUNT_NUM = wrap<AccountId>(100000000);
-
-struct mmo_account
-{
- AccountName userid;
- AccountPass passwd;
- int passwdenc;
+#include "globals.hpp"
+#include "login_conf.hpp"
+#include "login_lan_conf.hpp"
- AccountId account_id;
- int login_id1;
- int login_id2;
- AccountId char_id;
- timestamp_milliseconds_buffer lastlogin;
- SEX sex;
-};
-
-struct mmo_char_server
-{
- ServerName name;
- IP4Address ip;
- uint16_t port;
- uint16_t users;
-};
-
-static
-AccountId account_id_count = START_ACCOUNT_NUM;
-static
-int new_account = 0;
-static
-int login_port = 6900;
-static
-IP4Address lan_char_ip = IP4_LOCALHOST;
-static
-IP4Mask lan_subnet = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST);
-static
-AString update_host;
-static
-AccountName userid;
-static
-AccountPass passwd;
-static
-ServerName main_server;
-
-static
-AString account_filename = "save/account.txt"_s;
-static
-AString gm_account_filename = "save/gm_account.txt"_s;
-static
-AString login_log_filename = "log/login.log"_s;
-static
-AString login_log_unknown_packets_filename = "log/login_unknown_packets.log"_s;
-static
-int save_unknown_packets = 0;
-static
-tick_t creation_time_GM_account_file;
-static
-std::chrono::seconds gm_account_filename_check_timer = 15_s;
-
-static
-int display_parse_login = 0; // 0: no, 1: yes
-static
-int display_parse_admin = 0; // 0: no, 1: yes
-static
-int display_parse_fromchar = 0; // 0: no, 1: yes (without packet 0x2714), 2: all packets
-
-static
-Array<struct mmo_char_server, MAX_SERVERS> server;
-static
-Array<Session *, MAX_SERVERS> server_session;
-static
-Array<int, MAX_SERVERS> server_freezeflag; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed
-static
-int anti_freeze_enable = 0;
-static
-std::chrono::seconds anti_freeze_interval = 15_s;
+#include "../poison.hpp"
-static
-Session *login_session;
-enum class ACO
+namespace tmwa
{
- DENY_ALLOW,
- ALLOW_DENY,
- MUTUAL_FAILURE,
-};
-
-static
-ACO access_order = ACO::DENY_ALLOW;
-static
-std::vector<IP4Mask>
-access_allow, access_deny, access_ladmin;
-
-static
-GmLevel min_level_to_connect = GmLevel::from(0_u32); // minimum level of player/GM (0: player, 1-99: gm) to connect on the server
-static
-int add_to_unlimited_account = 0; // Give possibility or not to adjust (ladmin command: timeadd) the time of an unlimited account.
-static
-int start_limited_time = -1; // Starting additional sec from now for the limited time at creation of accounts (-1: unlimited time, 0 or more: additional sec from now)
-static
-int check_ip_flag = 1; // It's to check IP of a player between login-server and char-server (part of anti-hacking system)
-
DIAG_PUSH();
DIAG_I(missing_noreturn);
void SessionDeleter::operator()(SessionData *)
@@ -186,67 +90,54 @@ void SessionDeleter::operator()(SessionData *)
}
DIAG_POP();
-constexpr int AUTH_FIFO_SIZE = 256;
-struct AuthFifo
+// out of namespace because ADL is dumb
+bool extract(XString str, login::ACO *aco)
{
- AccountId account_id;
- int login_id1, login_id2;
- IP4Address ip;
- SEX sex;
- int delflag;
-};
-static
-Array<AuthFifo, AUTH_FIFO_SIZE> auth_fifo;
-// TODO replace with auto_fifo_it
-static
-int auth_fifo_pos = 0;
-
-struct AuthData
+ using login::ACO;
+ if (str == "deny,allow"_s || str == "deny, allow"_s)
+ {
+ *aco = ACO::DENY_ALLOW;
+ return true;
+ }
+ if (str == "allow,deny"_s || str == "allow, deny"_s)
+ {
+ *aco = ACO::ALLOW_DENY;
+ return true;
+ }
+ // typo from old config files
+ if (str == "mutual-failture"_s || str == "mutual-failure"_s)
+ {
+ *aco = ACO::MUTUAL_FAILURE;
+ return true;
+ }
+ return false;
+}
+namespace login
+{
+struct mmo_account
{
- AccountId account_id;
- SEX sex;
AccountName userid;
- AccountCrypt pass;
+ AccountPass passwd;
+ int passwdenc;
+
+ AccountId account_id;
+ int login_id1;
+ int login_id2;
+ AccountId char_id;
timestamp_milliseconds_buffer lastlogin;
- int logincount;
- int state; // packet 0x006a value + 1 (0: compte OK)
- AccountEmail email; // e-mail (by default: a@a.com)
- timestamp_seconds_buffer error_message; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a)
- TimeT ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban)
- TimeT connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited)
- IP4Address last_ip; // save of last IP of connection
- VString<254> memo; // a memo field
- int account_reg2_num;
- Array<GlobalReg, ACCOUNT_REG2_NUM> account_reg2;
+ SEX sex;
};
-static
-std::vector<AuthData> auth_data;
-
-static
-int admin_state = 0;
-static
-AccountPass admin_pass;
-static
-AString gm_pass;
-static
-GmLevel level_new_gm = GmLevel::from(60u);
-
-static
-Map<AccountId, GM_Account> gm_account_db;
-
-static
-pid_t pid = 0; // For forked DB writes
//------------------------------
// Writing function of logs file
//------------------------------
#define LOGIN_LOG(fmt, ...) \
- login_log(STRPRINTF(fmt, ## __VA_ARGS__))
+ tmwa::login::login_log(STRPRINTF(fmt, ## __VA_ARGS__))
static
void login_log(XString line)
{
- io::AppendFile logfp(login_log_filename);
+ io::AppendFile logfp(login_conf.login_log_filename);
if (!logfp.is_open())
return;
log_with_timestamp(logfp, line);
@@ -287,10 +178,8 @@ void delete_admin(Session *s)
static
GmLevel isGM(AccountId account_id)
{
- GM_Account *p = gm_account_db.search(account_id);
- if (p == nullptr)
- return GmLevel();
- return p->level;
+ Option<P<GM_Account>> p = gm_account_db.search(account_id);
+ return TRY_UNWRAP(p, return GmLevel())->level;
}
//-------------------------------------------------------
@@ -303,16 +192,16 @@ int read_gm_account(void)
gm_account_db.clear();
- creation_time_GM_account_file = file_modified(gm_account_filename);
+ creation_time_GM_account_file = file_modified(login_conf.gm_account_filename);
- io::ReadFile fp(gm_account_filename);
+ io::ReadFile fp(login_conf.gm_account_filename);
if (!fp.is_open())
{
PRINTF("read_gm_account: GM accounts file [%s] not found.\n"_fmt,
- gm_account_filename);
+ login_conf.gm_account_filename);
PRINTF(" Actually, there is no GM accounts on the server.\n"_fmt);
LOGIN_LOG("read_gm_account: GM accounts file [%s] not found.\n"_fmt,
- gm_account_filename);
+ login_conf.gm_account_filename);
LOGIN_LOG(" Actually, there is no GM accounts on the server.\n"_fmt);
return 1;
}
@@ -326,7 +215,7 @@ int read_gm_account(void)
GM_Account p {};
if (!extract(line, record<' '>(&p.account_id, &p.level)))
PRINTF("read_gm_account: file [%s], invalid 'id_acount level' format: '%s'\n"_fmt,
- gm_account_filename, line);
+ login_conf.gm_account_filename, line);
else
{
GmLevel GM_level = isGM(p.account_id);
@@ -356,9 +245,9 @@ int read_gm_account(void)
}
PRINTF("read_gm_account: file '%s' readed (%d GM accounts found).\n"_fmt,
- gm_account_filename, c);
+ login_conf.gm_account_filename, c);
LOGIN_LOG("read_gm_account: file '%s' readed (%d GM accounts found).\n"_fmt,
- gm_account_filename, c);
+ login_conf.gm_account_filename, c);
return 0;
}
@@ -377,29 +266,29 @@ bool check_ip(IP4Address ip)
};
ACF flag = ACF::DEF;
- if (access_allow.empty() && access_deny.empty())
+ if (login_conf.allow.empty() && login_conf.deny.empty())
return 1;
// When there is no restriction, all IP are authorised.
- if (std::find_if(access_allow.begin(), access_allow.end(),
+ if (std::find_if(login_conf.allow.begin(), login_conf.allow.end(),
[&ip](IP4Mask m)
{
return m.covers(ip);
- }) != access_allow.end())
+ }) != login_conf.allow.end())
{
{
flag = ACF::ALLOW;
- if (access_order == ACO::ALLOW_DENY)
+ if (login_conf.order == ACO::ALLOW_DENY)
// With 'allow, deny' (deny if not allow), allow has priority
return 1;
}
}
- if (std::find_if(access_deny.begin(), access_deny.end(),
+ if (std::find_if(login_conf.deny.begin(), login_conf.deny.end(),
[&ip](IP4Mask m)
{
return m.covers(ip);
- }) != access_deny.end())
+ }) != login_conf.deny.end())
{
{
flag = ACF::DENY;
@@ -408,7 +297,7 @@ bool check_ip(IP4Address ip)
}
}
- return flag == ACF::ALLOW || access_order == ACO::DENY_ALLOW;
+ return flag == ACF::ALLOW || login_conf.order == ACO::DENY_ALLOW;
// With 'mutual-failture', only 'allow' and non 'deny' IP are authorised.
// A non 'allow' (even non 'deny') IP is not authorised. It's like: if allowed and not denied, it's authorised.
// So, it's disapproval if you have no description at the time of 'mutual-failture'.
@@ -421,15 +310,15 @@ bool check_ip(IP4Address ip)
static
bool check_ladminip(IP4Address ip)
{
- if (access_ladmin.empty())
+ if (login_conf.ladminallowip.empty())
// When there is no restriction, all IP are authorised.
return true;
- return std::find_if(access_ladmin.begin(), access_ladmin.end(),
+ return std::find_if(login_conf.ladminallowip.begin(), login_conf.ladminallowip.end(),
[&ip](IP4Mask m)
{
return m.covers(ip);
- }) != access_ladmin.end();
+ }) != login_conf.ladminallowip.end();
}
//-----------------------------------------------
@@ -480,7 +369,7 @@ AString mmo_auth_tostr(const AuthData *p)
p->state,
p->email,
p->error_message,
- p->connect_until_time,
+ TimeT(),
p->last_ip,
p->memo,
p->ban_until_time);
@@ -495,8 +384,9 @@ AString mmo_auth_tostr(const AuthData *p)
}
static
-bool extract(XString line, AuthData *ad)
+bool impl_extract(XString line, AuthData *ad)
{
+ TimeT unused_connect_until_time;
std::vector<GlobalReg> vars;
VString<1> sex;
VString<15> ip;
@@ -511,7 +401,7 @@ bool extract(XString line, AuthData *ad)
&ad->state,
&ad->email,
&ad->error_message,
- &ad->connect_until_time,
+ &unused_connect_until_time,
&ip,
&ad->memo,
&ad->ban_until_time,
@@ -577,13 +467,13 @@ int mmo_auth_init(void)
{
int gm_count = 0;
- io::ReadFile in(account_filename);
+ io::ReadFile in(login_conf.account_filename);
if (!in.is_open())
{
// no account file -> no account -> no login, including char-server (ERROR)
// not anymore! :-)
PRINTF(SGR_BOLD SGR_RED "mmo_auth_init: Accounts file [%s] not found." SGR_RESET "\n"_fmt,
- account_filename);
+ login_conf.account_filename);
return 0;
}
@@ -620,7 +510,7 @@ int mmo_auth_init(void)
}
AString str = STRPRINTF("%s has %zu accounts (%d GMs)\n"_fmt,
- account_filename, auth_data.size(), gm_count);
+ login_conf.account_filename, auth_data.size(), gm_count);
PRINTF("mmo_auth_init: %s\n"_fmt, str);
LOGIN_LOG("%s\n"_fmt, line);
@@ -633,7 +523,7 @@ int mmo_auth_init(void)
static
void mmo_auth_sync(void)
{
- io::WriteLock fp(account_filename);
+ io::WriteLock fp(login_conf.account_filename);
if (!fp.is_open())
{
@@ -643,7 +533,7 @@ void mmo_auth_sync(void)
FPRINTF(fp,
"// Accounts file: here are saved all information about the accounts.\n"_fmt);
FPRINTF(fp,
- "// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time, last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value)\n"_fmt);
+ "// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time (unused), last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value)\n"_fmt);
FPRINTF(fp, "// Some explanations:\n"_fmt);
FPRINTF(fp,
"// account name : between 4 to 23 char for a normal account (standard client can't send less than 4 char).\n"_fmt);
@@ -754,11 +644,11 @@ static
void check_GM_file(TimerData *, tick_t)
{
// if we would not check
- if (gm_account_filename_check_timer == interval_t::zero())
+ if (login_conf.gm_account_filename_check_timer == interval_t::zero())
return;
// get last modify time/date
- tick_t new_time = file_modified(gm_account_filename);
+ tick_t new_time = file_modified(login_conf.gm_account_filename);
if (new_time != creation_time_GM_account_file)
{
@@ -795,17 +685,6 @@ AccountId mmo_auth_new(struct mmo_account *account, SEX sex, AccountEmail email)
ad.error_message = stringish<timestamp_seconds_buffer>("-"_s);
ad.ban_until_time = TimeT();
- if (start_limited_time < 0)
- ad.connect_until_time = TimeT(); // unlimited
- else
- {
- // limited time
- TimeT timestamp = static_cast<time_t>(TimeT::now()) + start_limited_time;
- // there used to be a silly overflow check here, but it wasn't
- // correct, and we don't support time-limited accounts.
- ad.connect_until_time = timestamp;
- }
-
ad.last_ip = IP4Address();
ad.memo = "!"_s;
ad.account_reg2_num = 0;
@@ -827,7 +706,7 @@ int mmo_auth(struct mmo_account *account, Session *s)
// Account creation with _M/_F
if (account->passwdenc == 0
&& (account->userid.endswith("_F"_s) || account->userid.endswith("_M"_s))
- && new_account == 1 && account_id_count < END_ACCOUNT_NUM
+ && login_conf.new_account && account_id_count < END_ACCOUNT_NUM
&& (account->userid.size() - 2) >= 4 && account->passwd.size() >= 4)
{
new_account_sex = account->userid.back();
@@ -899,14 +778,6 @@ int mmo_auth(struct mmo_account *account, Session *s)
}
}
- if (ad->connect_until_time
- && ad->connect_until_time < TimeT::now())
- {
- LOGIN_LOG("Connection refused (account: %s, expired ID, ip: %s)\n"_fmt,
- account->userid, ip);
- return 2; // 2 = This ID is expired
- }
-
LOGIN_LOG("Authentification accepted (account: %s (id: %d), ip: %s)\n"_fmt,
account->userid, ad->account_id, ip);
}
@@ -989,28 +860,12 @@ void parse_fromchar(Session *s)
uint16_t packet_id;
while (rv == RecvResult::Complete && packet_peek_id(s, &packet_id))
{
- if (display_parse_fromchar == 2 || (display_parse_fromchar == 1 && packet_id != 0x2714)) // 0x2714 is done very often (number of players)
+ if (login_conf.display_parse_fromchar == 2 || (login_conf.display_parse_fromchar == 1 && packet_id != 0x2714)) // 0x2714 is done very often (number of players)
PRINTF("parse_fromchar: connection #%d, packet: 0x%x (with being read: %zu bytes).\n"_fmt,
s, packet_id, packet_avail(s));
switch (packet_id)
{
- // request from map-server via char-server to reload GM accounts (by Yor).
- case 0x2709:
- {
- Packet_Fixed<0x2709> fixed;
- rv = recv_fpacket<0x2709, 2>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- LOGIN_LOG("Char-server '%s': Request to re-load GM configuration file (ip: %s).\n"_fmt,
- server[id].name, ip);
- read_gm_account();
- // send GM accounts to all char-servers
- send_GM_accounts();
- break;
- }
-
case 0x2712: // request from char-server to authentify an account
{
Packet_Fixed<0x2712> fixed;
@@ -1027,8 +882,7 @@ void parse_fromchar(Session *s)
auth_fifo[i].login_id1 == fixed.login_id1 &&
auth_fifo[i].login_id2 == fixed.login_id2 && // relate to the versions higher than 18
auth_fifo[i].sex == fixed.sex &&
- (!check_ip_flag
- || auth_fifo[i].ip == fixed.ip)
+ auth_fifo[i].ip == fixed.ip
&& !auth_fifo[i].delflag)
{
auth_fifo[i].delflag = 1;
@@ -1057,7 +911,6 @@ void parse_fromchar(Session *s)
fixed_13.account_id = acc;
fixed_13.invalid = 0;
fixed_13.email = ad.email;
- fixed_13.connect_until = ad.connect_until_time;
send_fpacket<0x2713, 51>(s, fixed_13);
break;
@@ -1092,7 +945,7 @@ void parse_fromchar(Session *s)
break;
server[id].users = fixed.users;
- if (anti_freeze_enable)
+ if (login_conf.anti_freeze_enable)
server_freezeflag[id] = 5; // Char anti-freeze system. Counter. 5 ok, 4...0 freezed
break;
}
@@ -1116,7 +969,6 @@ void parse_fromchar(Session *s)
Packet_Fixed<0x2717> fixed_17;
fixed_17.account_id = account_id;
fixed_17.email = ad.email;
- fixed_17.connect_until = ad.connect_until_time;
send_fpacket<0x2717, 50>(s, fixed_17);
if (rv != RecvResult::Complete)
@@ -1130,93 +982,6 @@ void parse_fromchar(Session *s)
break;
}
- case 0x2720: // To become GM request
- {
- Packet_Head<0x2720> head;
- AString repeat;
- rv = recv_vpacket<0x2720, 8, 1>(s, head, repeat);
- if (rv != RecvResult::Complete)
- break;
-
- {
- AccountId acc = head.account_id;
-
- Packet_Fixed<0x2721> fixed_21;
- fixed_21.account_id = acc;
- fixed_21.gm_level = GmLevel();
-
- AString pass = repeat;
-
- if (pass == gm_pass)
- {
- // only non-GM can become GM
- if (!isGM(acc))
- {
- // if we autorise creation
- if (level_new_gm)
- {
- // if we can open the file to add the new GM
- io::AppendFile fp(gm_account_filename);
- if (fp.is_open())
- {
- timestamp_seconds_buffer tmpstr;
- stamp_time(tmpstr);
- FPRINTF(fp,
- "\n// %s: @GM command on account %d\n%d %d\n"_fmt,
- tmpstr,
- acc, acc, level_new_gm);
- if (!fp.close())
- {
- PRINTF("warning: didn't actually save GM file\n"_fmt);
- }
- fixed_21.gm_level = level_new_gm;
- read_gm_account();
- send_GM_accounts();
- PRINTF("GM Change of the account %d: level 0 -> %d.\n"_fmt,
- acc, level_new_gm);
- LOGIN_LOG("Char-server '%s': GM Change of the account %d: level 0 -> %d (ip: %s).\n"_fmt,
- server[id].name, acc,
- level_new_gm, ip);
- }
- else
- {
- PRINTF("Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file)\n"_fmt,
- acc);
- LOGIN_LOG("Char-server '%s': Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file, ip: %s).\n"_fmt,
- server[id].name, acc, ip);
- }
- }
- else
- {
- PRINTF("Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0))\n"_fmt,
- acc);
- LOGIN_LOG("Char-server '%s': Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0), ip: %s).\n"_fmt,
- server[id].name, acc, ip);
- }
- }
- else
- {
- PRINTF("Error of GM change (suggested account: %d (already GM), correct password).\n"_fmt,
- acc);
- LOGIN_LOG("Char-server '%s': Error of GM change (suggested account: %d (already GM), correct password, ip: %s).\n"_fmt,
- server[id].name, acc, ip);
- }
- }
- else
- {
- PRINTF("Error of GM change (suggested account: %d, invalid password).\n"_fmt,
- acc);
- LOGIN_LOG("Char-server '%s': Error of GM change (suggested account: %d, invalid password, ip: %s).\n"_fmt,
- server[id].name, acc, ip);
- }
- for (Session *ss : iter_char_sessions())
- {
- send_fpacket<0x2721, 10>(ss, fixed_21);
- }
- }
- break;
- }
-
// Map server send information to change an email of an account via char-server
case 0x2722: // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B
{
@@ -1603,19 +1368,17 @@ void parse_fromchar(Session *s)
default:
{
- io::AppendFile logfp(login_log_unknown_packets_filename);
- if (logfp.is_open())
{
timestamp_milliseconds_buffer timestr;
stamp_time(timestr);
- FPRINTF(logfp,
+ FPRINTF(stderr,
"%s: receiving of an unknown packet -> disconnection\n"_fmt,
timestr);
- FPRINTF(logfp,
+ FPRINTF(stderr,
"parse_fromchar: connection #%d (ip: %s), packet: 0x%x (with being read: %zu).\n"_fmt,
s, ip, packet_id, packet_avail(s));
- FPRINTF(logfp, "Detail (in hex):\n"_fmt);
- packet_dump(logfp, s);
+ FPRINTF(stderr, "Detail (in hex):\n"_fmt);
+ packet_dump(s);
}
PRINTF("parse_fromchar: Unknown packet 0x%x (from a char-server)! -> disconnection.\n"_fmt,
packet_id);
@@ -1641,7 +1404,7 @@ void parse_admin(Session *s)
uint16_t packet_id;
while (rv == RecvResult::Complete && packet_peek_id(s, &packet_id))
{
- if (display_parse_admin == 1)
+ if (login_conf.display_parse_admin)
PRINTF("parse_admin: connection #%d, packet: 0x%x (with being read: %zu bytes).\n"_fmt,
s, packet_id, packet_avail(s));
@@ -1718,23 +1481,6 @@ void parse_admin(Session *s)
break;
}
- case 0x7924:
- { // [Fate] Itemfrob package: change item IDs
- Packet_Fixed<0x7924> fixed;
- rv = recv_fpacket<0x7924, 10>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- for (Session *ss : iter_char_sessions())
- {
- send_fpacket<0x7924, 10>(ss, fixed);
- }
-
- Packet_Fixed<0x7925> fixed_25;
- send_fpacket<0x7925, 2>(s, fixed_25);
- break;
- }
-
case 0x7930: // Request for an account creation
{
Packet_Fixed<0x7930> fixed;
@@ -2096,10 +1842,10 @@ void parse_admin(Session *s)
AccountId GM_account;
GmLevel GM_level;
int modify_flag;
- io::WriteLock fp2(gm_account_filename);
+ io::WriteLock fp2(login_conf.gm_account_filename);
if (fp2.is_open())
{
- io::ReadFile fp(gm_account_filename);
+ io::ReadFile fp(login_conf.gm_account_filename);
if (fp.is_open())
{
timestamp_seconds_buffer tmpstr;
@@ -2330,48 +2076,6 @@ void parse_admin(Session *s)
break;
}
- case 0x7948: // Request to change the validity limit (timestamp) (absolute value)
- {
- Packet_Fixed<0x7948> fixed;
- rv = recv_fpacket<0x7948, 30>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- Packet_Fixed<0x7949> fixed_49;
- {
- fixed_49.account_id = AccountId();
- AccountName account_name = stringish<AccountName>(fixed.account_name.to_print());
- TimeT timestamp = fixed.valid_until;
- timestamp_seconds_buffer tmpstr = stringish<timestamp_seconds_buffer>("unlimited"_s);
- if (timestamp)
- stamp_time(tmpstr, &timestamp);
- AuthData *ad = search_account(account_name);
- if (ad)
- {
- fixed_49.account_name = ad->userid;
- LOGIN_LOG("'ladmin': Change of a validity limit (account: %s, new validity: %lld (%s), ip: %s)\n"_fmt,
- ad->userid,
- timestamp,
- tmpstr,
- ip);
- ad->connect_until_time = timestamp;
- fixed_49.account_id = ad->account_id;
- }
- else
- {
- fixed_49.account_name = account_name;
- LOGIN_LOG("'ladmin': Attempt to change the validity limit of an unknown account (account: %s, received validity: %lld (%s), ip: %s)\n"_fmt,
- account_name,
- timestamp,
- tmpstr,
- ip);
- }
- fixed_49.valid_until = timestamp;
- }
- send_fpacket<0x7949, 34>(s, fixed_49);
- break;
- }
-
case 0x794a: // Request to change the final date of a banishment (timestamp) (absolute value)
{
Packet_Fixed<0x794a> fixed;
@@ -2475,7 +2179,7 @@ void parse_admin(Session *s)
timestamp_seconds_buffer tmpstr = stringish<timestamp_seconds_buffer>("no banishment"_s);
if (timestamp)
stamp_time(tmpstr, &timestamp);
- LOGIN_LOG("'ladmin': Adjustment of a final date of a banishment (account: %s, (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %lld (%s), ip: %s)\n"_fmt,
+ LOGIN_LOG("'ladmin': Adjustment of a final date of a banishment (account: %s, (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new ban: %lld (%s), ip: %s)\n"_fmt,
ad->userid,
ban_diff.year, ban_diff.month,
ban_diff.day, ban_diff.hour,
@@ -2583,99 +2287,6 @@ void parse_admin(Session *s)
break;
}
- case 0x7950: // Request to change the validity limite (timestamp) (relative change)
- {
- Packet_Fixed<0x7950> fixed;
- rv = recv_fpacket<0x7950, 38>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- Packet_Fixed<0x7951> fixed_51;
- {
- fixed_51.account_id = AccountId();
- AccountName account_name = stringish<AccountName>(fixed.account_name.to_print());
- AuthData *ad = search_account(account_name);
- if (ad)
- {
- fixed_51.account_id = ad->account_id;
- fixed_51.account_name = ad->userid;
- if (add_to_unlimited_account == 0 && !ad->connect_until_time)
- {
- LOGIN_LOG("'ladmin': Attempt to adjust the validity limit of an unlimited account (account: %s, ip: %s)\n"_fmt,
- ad->userid, ip);
- fixed_51.valid_until = TimeT();
- }
- else
- {
- TimeT now = TimeT::now();
- TimeT timestamp = ad->connect_until_time;
- if (!timestamp || timestamp < now)
- timestamp = now;
- struct tm tmtime = timestamp;
- HumanTimeDiff v_diff = fixed.valid_add;
- tmtime.tm_year += v_diff.year;
- tmtime.tm_mon += v_diff.month;
- tmtime.tm_mday += v_diff.day;
- tmtime.tm_hour += v_diff.hour;
- tmtime.tm_min += v_diff.minute;
- tmtime.tm_sec += v_diff.second;
- timestamp = tmtime;
- if (timestamp.okay())
- {
- timestamp_seconds_buffer tmpstr = stringish<timestamp_seconds_buffer>("unlimited"_s);
- timestamp_seconds_buffer tmpstr2 = stringish<timestamp_seconds_buffer>("unlimited"_s);
- if (ad->connect_until_time)
- stamp_time(tmpstr, &ad->connect_until_time);
- if (timestamp)
- stamp_time(tmpstr2, &timestamp);
- LOGIN_LOG("'ladmin': Adjustment of a validity limit (account: %s, %lld (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %lld (%s), ip: %s)\n"_fmt,
- ad->userid,
- ad->connect_until_time,
- tmpstr,
- v_diff.year,
- v_diff.month,
- v_diff.day,
- v_diff.hour,
- v_diff.minute,
- v_diff.second,
- timestamp,
- tmpstr2,
- ip);
- ad->connect_until_time = timestamp;
- fixed_51.valid_until = timestamp;
- }
- else
- {
- timestamp_seconds_buffer tmpstr = stringish<timestamp_seconds_buffer>("unlimited"_s);
- if (ad->connect_until_time)
- stamp_time(tmpstr, &ad->connect_until_time);
- LOGIN_LOG("'ladmin': Impossible to adjust a validity limit (account: %s, %lld (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)\n"_fmt,
- ad->userid,
- ad->connect_until_time,
- tmpstr,
- v_diff.year,
- v_diff.month,
- v_diff.day,
- v_diff.hour,
- v_diff.minute,
- v_diff.second,
- ip);
- fixed_51.valid_until = TimeT();
- }
- }
- }
- else
- {
- fixed_51.account_name = account_name;
- LOGIN_LOG("'ladmin': Attempt to adjust the validity limit of an unknown account (account: %s, ip: %s)\n"_fmt,
- account_name, ip);
- fixed_51.valid_until = TimeT();
- }
- }
- send_fpacket<0x7951, 34>(s, fixed_51);
- break;
- }
-
case 0x7952: // Request about informations of an account (by account name)
{
Packet_Fixed<0x7952> fixed;
@@ -2699,7 +2310,6 @@ void parse_admin(Session *s)
head_53.last_login_string = ad->lastlogin;
head_53.ip_string = convert_for_printf(ad->last_ip);
head_53.email = ad->email;
- head_53.connect_until = ad->connect_until_time;
head_53.ban_until = ad->ban_until_time;
XString repeat_53 = ad->memo;
@@ -2745,7 +2355,6 @@ void parse_admin(Session *s)
head_53.last_login_string = ad.lastlogin;
head_53.ip_string = convert_for_printf(ad.last_ip);
head_53.email = ad.email;
- head_53.connect_until = ad.connect_until_time;
head_53.ban_until = ad.ban_until_time;
XString repeat_53 = ad.memo;
send_vpacket<0x7953, 150, 1>(s, head_53, repeat_53);
@@ -2779,19 +2388,17 @@ void parse_admin(Session *s)
default:
{
- io::AppendFile logfp(login_log_unknown_packets_filename);
- if (logfp.is_open())
{
timestamp_milliseconds_buffer timestr;
stamp_time(timestr);
- FPRINTF(logfp,
+ FPRINTF(stderr,
"%s: receiving of an unknown packet -> disconnection\n"_fmt,
timestr);
- FPRINTF(logfp,
+ FPRINTF(stderr,
"parse_admin: connection #%d (ip: %s), packet: 0x%x (with being read: %zu).\n"_fmt,
s, ip, packet_id, packet_avail(s));
- FPRINTF(logfp, "Detail (in hex):\n"_fmt);
- packet_dump(logfp, s);
+ FPRINTF(stderr, "Detail (in hex):\n"_fmt);
+ packet_dump(s);
}
LOGIN_LOG("'ladmin': End of connection, unknown packet (ip: %s)\n"_fmt,
ip);
@@ -2812,7 +2419,7 @@ void parse_admin(Session *s)
static
bool lan_ip_check(IP4Address p)
{
- bool lancheck = lan_subnet.covers(p);
+ bool lancheck = login_lan_conf.lan_subnet.covers(p);
PRINTF("LAN test (result): %s.\n"_fmt,
(lancheck) ? SGR_BOLD SGR_CYAN "LAN source" SGR_RESET ""_s : SGR_BOLD SGR_GREEN "WAN source" SGR_RESET ""_s);
@@ -2833,7 +2440,7 @@ void parse_login(Session *s)
uint16_t packet_id;
while (rv == RecvResult::Complete && packet_peek_id(s, &packet_id))
{
- if (display_parse_login == 1)
+ if (login_conf.display_parse_login)
{
if (packet_id == 0x64)
{
@@ -2895,10 +2502,10 @@ void parse_login(Session *s)
if (result == -1)
{
GmLevel gm_level = isGM(account.account_id);
- if (!(gm_level.satisfies(min_level_to_connect)))
+ if (!(gm_level.satisfies(login_conf.min_level_to_connect)))
{
LOGIN_LOG("Connection refused: the minimum GM level for connection is %d (account: %s, GM level: %d, ip: %s).\n"_fmt,
- min_level_to_connect, account.userid,
+ login_conf.min_level_to_connect, account.userid,
gm_level, ip);
Packet_Fixed<0x0081> fixed_81;
fixed_81.error_code = 1; // 01 = Server closed
@@ -2928,9 +2535,9 @@ void parse_login(Session *s)
*/
// if (version_2 & VERSION_2_UPDATEHOST)
{
- if (update_host)
+ if (login_conf.update_host)
{
- send_packet_repeatonly<0x0063, 4, 1>(s, update_host);
+ send_packet_repeatonly<0x0063, 4, 1>(s, login_conf.update_host);
}
}
@@ -2943,7 +2550,7 @@ void parse_login(Session *s)
{
Packet_Repeat<0x0069> info;
if (lan_ip_check(ip))
- info.ip = lan_char_ip;
+ info.ip = login_lan_conf.lan_char_ip;
else
info.ip = server[i].ip;
info.port = server[i].port;
@@ -3043,11 +2650,11 @@ void parse_login(Session *s)
ServerName server_name = stringish<ServerName>(fixed.server_name.to_print());
LOGIN_LOG("Connection request of the char-server '%s' @ %s:%d (ip: %s)\n"_fmt,
server_name, fixed.ip, fixed.port, ip);
- if (account.userid == userid && account.passwd == passwd)
+ if (account.userid == login_conf.userid && account.passwd == login_conf.passwd)
{
// If this is the main server, and we don't already have a main server
if (!server_session[0]
- && server_name == main_server)
+ && server_name == login_conf.main_server)
{
account.account_id = wrap<AccountId>(0_u32);
goto x2710_okay;
@@ -3082,7 +2689,7 @@ void parse_login(Session *s)
//maintenance = RFIFOW(fd, 82);
//is_new = RFIFOW(fd, 84);
server_session[unwrap<AccountId>(account.account_id)] = s;
- if (anti_freeze_enable)
+ if (login_conf.anti_freeze_enable)
server_freezeflag[unwrap<AccountId>(account.account_id)] = 5; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed
Packet_Fixed<0x2711> fixed_11;
@@ -3124,7 +2731,7 @@ void parse_login(Session *s)
Packet_Fixed<0x7531> fixed_31;
Version version = CURRENT_LOGIN_SERVER_VERSION;
- version.flags = new_account ? 1 : 0;
+ version.flags = login_conf.new_account ? 1 : 0;
fixed_31.version = version;
send_fpacket<0x7531, 10>(s, fixed_31);
break;
@@ -3163,8 +2770,8 @@ void parse_login(Session *s)
// non encrypted password
AccountPass password = stringish<AccountPass>(fixed.account_pass.to_print());
// If remote administration is enabled and password sent by client matches password read from login server configuration file
- if ((admin_state == 1)
- && (password == admin_pass))
+ if (login_conf.admin_state
+ && password == login_conf.admin_pass)
{
LOGIN_LOG("'ladmin'-login: Connection in administration mode accepted (non encrypted password: %s, ip: %s)\n"_fmt,
password, ip);
@@ -3172,7 +2779,7 @@ void parse_login(Session *s)
fixed_19.error = 0;
s->set_parsers(SessionParsers{.func_parse= parse_admin, .func_delete= delete_admin});
}
- else if (admin_state != 1)
+ else if (!login_conf.admin_state)
LOGIN_LOG("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (non encrypted password: %s, ip: %s)\n"_fmt,
password, ip);
else
@@ -3194,22 +2801,19 @@ void parse_login(Session *s)
default:
{
- if (save_unknown_packets)
{
- io::AppendFile logfp(login_log_unknown_packets_filename);
- if (logfp.is_open())
{
timestamp_milliseconds_buffer timestr;
stamp_time(timestr);
- FPRINTF(logfp,
+ FPRINTF(stderr,
"%s: receiving of an unknown packet -> disconnection\n"_fmt,
timestr);
- FPRINTF(logfp,
+ FPRINTF(stderr,
"parse_login: connection #%d (ip: %s), packet: 0x%x (with being read: %zu).\n"_fmt,
s, ip, packet_id,
packet_avail(s));
- FPRINTF(logfp, "Detail (in hex):\n"_fmt);
- packet_dump(logfp, s);
+ FPRINTF(stderr, "Detail (in hex):\n"_fmt);
+ packet_dump(s);
}
}
LOGIN_LOG("End of connection, unknown packet (ip: %s)\n"_fmt, ip);
@@ -3222,67 +2826,19 @@ void parse_login(Session *s)
s->set_eof();
}
-//----------------------------------
-// Reading Lan Support configuration
-//----------------------------------
-static
-bool login_lan_config(XString w1, ZString w2)
-{
- struct hostent *h = nullptr;
-
- {
- if (w1 == "lan_char_ip"_s)
- {
- // Read Char-Server Lan IP Address
- h = gethostbyname(w2.c_str());
- if (h != nullptr)
- {
- lan_char_ip = IP4Address({
- static_cast<uint8_t>(h->h_addr[0]),
- static_cast<uint8_t>(h->h_addr[1]),
- static_cast<uint8_t>(h->h_addr[2]),
- static_cast<uint8_t>(h->h_addr[3]),
- });
- }
- else
- {
- PRINTF("Bad IP value: %s\n"_fmt, w2);
- return false;
- }
- PRINTF("LAN IP of char-server: %s.\n"_fmt, lan_char_ip);
- }
- else if (w1 == "subnet"_s /*backward compatibility*/
- || w1 == "lan_subnet"_s)
- {
- if (!extract(w2, &lan_subnet))
- {
- PRINTF("Bad IP mask: %s\n"_fmt, w2);
- return false;
- }
- PRINTF("Sub-network of the char-server: %s.\n"_fmt,
- lan_subnet);
- }
- else
- {
- return false;
- }
- }
- return true;
-}
-
static
bool lan_check()
{
// log the LAN configuration
LOGIN_LOG("The LAN configuration of the server is set:\n"_fmt);
- LOGIN_LOG("- with LAN IP of char-server: %s.\n"_fmt, lan_char_ip);
+ LOGIN_LOG("- with LAN IP of char-server: %s.\n"_fmt, login_lan_conf.lan_char_ip);
LOGIN_LOG("- with the sub-network of the char-server: %s.\n"_fmt,
- lan_subnet);
+ login_lan_conf.lan_subnet);
// sub-network check of the char-server
{
PRINTF("LAN test of LAN IP of the char-server: "_fmt);
- if (!lan_ip_check(lan_char_ip))
+ if (!lan_ip_check(login_lan_conf.lan_char_ip))
{
PRINTF(SGR_BOLD SGR_RED "***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network"_fmt SGR_RESET "\n");
LOGIN_LOG("***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network.\n"_fmt);
@@ -3293,229 +2849,6 @@ bool lan_check()
return true;
}
-//-----------------------------------
-// Reading general configuration file
-//-----------------------------------
-static
-bool login_config(XString w1, ZString w2)
-{
- {
- if (w1 == "admin_state"_s)
- {
- admin_state = config_switch(w2);
- }
- else if (w1 == "admin_pass"_s)
- {
- admin_pass = stringish<AccountPass>(w2);
- }
- else if (w1 == "ladminallowip"_s)
- {
- if (w2 == "clear"_s)
- {
- access_ladmin.clear();
- }
- else
- {
- // a.b.c.d/0.0.0.0 (canonically, 0.0.0.0/0) covers all
- if (w2 == "all"_s)
- {
- // reset all previous values
- access_ladmin.clear();
- // set to all
- access_ladmin.push_back(IP4Mask());
- }
- else if (w2
- && !(access_ladmin.size() == 1
- && access_ladmin.front().mask() == IP4Address()))
- {
- // don't add IP if already 'all'
- IP4Mask n;
- if (!extract(w2, &n))
- {
- PRINTF("Bad IP mask: %s\n"_fmt, w2);
- return false;
- }
- access_ladmin.push_back(n);
- }
- }
- }
- else if (w1 == "gm_pass"_s)
- {
- gm_pass = w2;
- }
- else if (w1 == "level_new_gm"_s)
- {
- level_new_gm = GmLevel::from(static_cast<uint32_t>(atoi(w2.c_str())));
- }
- else if (w1 == "new_account"_s)
- {
- new_account = config_switch(w2);
- }
- else if (w1 == "login_port"_s)
- {
- login_port = atoi(w2.c_str());
- }
- else if (w1 == "account_filename"_s)
- {
- account_filename = w2;
- }
- else if (w1 == "gm_account_filename"_s)
- {
- gm_account_filename = w2;
- }
- else if (w1 == "gm_account_filename_check_timer"_s)
- {
- gm_account_filename_check_timer = std::chrono::seconds(atoi(w2.c_str()));
- }
- else if (w1 == "login_log_filename"_s)
- {
- login_log_filename = w2;
- }
- else if (w1 == "login_log_unknown_packets_filename"_s)
- {
- login_log_unknown_packets_filename = w2;
- }
- else if (w1 == "save_unknown_packets"_s)
- {
- save_unknown_packets = config_switch(w2);
- }
- else if (w1 == "display_parse_login"_s)
- {
- display_parse_login = config_switch(w2); // 0: no, 1: yes
- }
- else if (w1 == "display_parse_admin"_s)
- {
- display_parse_admin = config_switch(w2); // 0: no, 1: yes
- }
- else if (w1 == "display_parse_fromchar"_s)
- {
- display_parse_fromchar = config_switch(w2); // 0: no, 1: yes (without packet 0x2714), 2: all packets
- }
- else if (w1 == "min_level_to_connect"_s)
- {
- min_level_to_connect = GmLevel::from(static_cast<uint32_t>(atoi(w2.c_str())));
- }
- else if (w1 == "add_to_unlimited_account"_s)
- {
- add_to_unlimited_account = config_switch(w2);
- }
- else if (w1 == "start_limited_time"_s)
- {
- start_limited_time = atoi(w2.c_str());
- }
- else if (w1 == "check_ip_flag"_s)
- {
- check_ip_flag = config_switch(w2);
- }
- else if (w1 == "order"_s)
- {
- if (w2 == "deny,allow"_s || w2 == "deny, allow"_s)
- access_order = ACO::DENY_ALLOW;
- else if (w2 == "allow,deny"_s || w2 == "allow, deny"_s)
- access_order = ACO::ALLOW_DENY;
- else if (w2 == "mutual-failture"_s || w2 == "mutual-failure"_s)
- access_order = ACO::MUTUAL_FAILURE;
- else
- {
- PRINTF("Bad order: %s\n"_fmt, w2);
- return false;
- }
- }
- else if (w1 == "allow"_s)
- {
- if (w2 == "clear"_s)
- {
- access_allow.clear();
- }
- else
- {
- if (w2 == "all"_s)
- {
- // reset all previous values
- access_allow.clear();
- // set to all
- access_allow.push_back(IP4Mask());
- }
- else if (w2
- && !(access_allow.size() == 1
- && access_allow.front().mask() == IP4Address()))
- {
- // don't add IP if already 'all'
- IP4Mask n;
- if (!extract(w2, &n))
- {
- PRINTF("Bad IP mask: %s\n"_fmt, w2);
- return false;
- }
- access_allow.push_back(n);
- }
- }
- }
- else if (w1 == "deny"_s)
- {
- if (w2 == "clear"_s)
- {
- access_deny.clear();
- }
- else
- {
- if (w2 == "all"_s)
- {
- // reset all previous values
- access_deny.clear();
- // set to all
- access_deny.push_back(IP4Mask());
- }
- else if (w2
- && !(access_deny.size() == 1
- && access_deny.front().mask() == IP4Address()))
- {
- // don't add IP if already 'all'
- IP4Mask n;
- if (!extract(w2, &n))
- {
- PRINTF("Bad IP mask: %s\n"_fmt, w2);
- return false;
- }
- access_deny.push_back(n);
- }
- }
- }
- else if (w1 == "anti_freeze_enable"_s)
- {
- anti_freeze_enable = config_switch(w2);
- }
- else if (w1 == "anti_freeze_interval"_s)
- {
- anti_freeze_interval = std::max(
- std::chrono::seconds(atoi(w2.c_str())),
- 5_s);
- }
- else if (w1 == "update_host"_s)
- {
- update_host = w2;
- }
- else if (w1 == "main_server"_s)
- {
- main_server = stringish<ServerName>(w2);
- }
- else if (w1 == "userid"_s)
- {
- userid = stringish<AccountName>(w2);
- }
- else if (w1 == "passwd"_s)
- {
- passwd = stringish<AccountPass>(w2);
- }
- else
- {
- return false;
- }
- }
-
- return true;
-}
-
//-------------------------------------
// Displaying of configuration warnings
//-------------------------------------
@@ -3523,135 +2856,60 @@ static
bool display_conf_warnings(void)
{
bool rv = true;
- if (admin_state != 0 && admin_state != 1)
- {
- PRINTF("***WARNING: Invalid value for admin_state parameter -> set to 0 (no remote admin).\n"_fmt);
- admin_state = 0;
- rv = false;
- }
- if (admin_state == 1)
+ if (login_conf.admin_state)
{
- if (!admin_pass)
+ if (!login_conf.admin_pass)
{
PRINTF("***WARNING: Administrator password is void (admin_pass).\n"_fmt);
rv = false;
}
- else if (admin_pass == stringish<AccountPass>("admin"_s))
+ else if (login_conf.admin_pass == stringish<AccountPass>("admin"_s))
{
PRINTF("***WARNING: You are using the default administrator password (admin_pass).\n"_fmt);
PRINTF(" We highly recommend that you change it.\n"_fmt);
}
}
- if (!gm_pass)
+ if (!login_conf.gm_pass)
{
PRINTF("***WARNING: 'To GM become' password is void (gm_pass).\n"_fmt);
PRINTF(" We highly recommend that you set one password.\n"_fmt);
rv = false;
}
- else if (gm_pass == "gm"_s)
+ else if (login_conf.gm_pass == "gm"_s)
{
PRINTF("***WARNING: You are using the default GM password (gm_pass).\n"_fmt);
PRINTF(" We highly recommend that you change it.\n"_fmt);
}
- if (new_account != 0 && new_account != 1)
- {
- PRINTF("***WARNING: Invalid value for new_account parameter -> set to 0 (no new account).\n"_fmt);
- new_account = 0;
- rv = false;
- }
-
- if (login_port < 1024 || login_port > 65535)
- {
- PRINTF("***WARNING: Invalid value for login_port parameter -> set to 6900 (default).\n"_fmt);
- login_port = 6900;
- rv = false;
- }
-
- if (gm_account_filename_check_timer.count() < 0)
+ if (login_conf.gm_account_filename_check_timer.count() < 0)
{
PRINTF("***WARNING: Invalid value for gm_account_filename_check_timer parameter.\n"_fmt);
PRINTF(" -> set to 15 sec (default).\n"_fmt);
- gm_account_filename_check_timer = 15_s;
+ login_conf.gm_account_filename_check_timer = 15_s;
rv = false;
}
- else if (gm_account_filename_check_timer == 1_s)
+ else if (login_conf.gm_account_filename_check_timer == 1_s)
{
PRINTF("***WARNING: Invalid value for gm_account_filename_check_timer parameter.\n"_fmt);
PRINTF(" -> set to 2 sec (minimum value).\n"_fmt);
- gm_account_filename_check_timer = 2_s;
- rv = false;
- }
-
- if (save_unknown_packets != 0 && save_unknown_packets != 1)
- {
- PRINTF("WARNING: Invalid value for save_unknown_packets parameter -> set to 0-no save.\n"_fmt);
- save_unknown_packets = 0;
- rv = false;
- }
-
- if (display_parse_login != 0 && display_parse_login != 1)
- { // 0: no, 1: yes
- PRINTF("***WARNING: Invalid value for display_parse_login parameter\n"_fmt);
- PRINTF(" -> set to 0 (no display).\n"_fmt);
- display_parse_login = 0;
- rv = false;
- }
-
- if (display_parse_admin != 0 && display_parse_admin != 1)
- { // 0: no, 1: yes
- PRINTF("***WARNING: Invalid value for display_parse_admin parameter\n"_fmt);
- PRINTF(" -> set to 0 (no display).\n"_fmt);
- display_parse_admin = 0;
- rv = false;
- }
-
- if (display_parse_fromchar < 0 || display_parse_fromchar > 2)
- { // 0: no, 1: yes (without packet 0x2714), 2: all packets
- PRINTF("***WARNING: Invalid value for display_parse_fromchar parameter\n"_fmt);
- PRINTF(" -> set to 0 (no display).\n"_fmt);
- display_parse_fromchar = 0;
- rv = false;
- }
-
- if (add_to_unlimited_account != 0 && add_to_unlimited_account != 1)
- { // 0: no, 1: yes
- PRINTF("***WARNING: Invalid value for add_to_unlimited_account parameter\n"_fmt);
- PRINTF(" -> set to 0 (impossible to add a time to an unlimited account).\n"_fmt);
- add_to_unlimited_account = 0;
- rv = false;
- }
-
- if (start_limited_time < -1)
- { // -1: create unlimited account, 0 or more: additionnal sec from now to create limited time
- PRINTF("***WARNING: Invalid value for start_limited_time parameter\n"_fmt);
- PRINTF(" -> set to -1 (new accounts are created with unlimited time).\n"_fmt);
- start_limited_time = -1;
+ login_conf.gm_account_filename_check_timer = 2_s;
rv = false;
}
- if (check_ip_flag != 0 && check_ip_flag != 1)
- { // 0: no, 1: yes
- PRINTF("***WARNING: Invalid value for check_ip_flag parameter\n"_fmt);
- PRINTF(" -> set to 1 (check players ip between login-server & char-server).\n"_fmt);
- check_ip_flag = 1;
- rv = false;
- }
-
- if (access_order == ACO::DENY_ALLOW)
+ if (login_conf.order == ACO::DENY_ALLOW)
{
- if (access_deny.size() == 1 && access_deny.front().mask() == IP4Address())
+ if (login_conf.deny.size() == 1 && login_conf.deny.front().mask() == IP4Address())
{
PRINTF("***WARNING: The IP security order is 'deny,allow' (allow if not deny).\n"_fmt);
PRINTF(" And you refuse ALL IP.\n"_fmt);
rv = false;
}
}
- else if (access_order == ACO::ALLOW_DENY)
+ else if (login_conf.order == ACO::ALLOW_DENY)
{
- if (access_allow.empty())
+ if (login_conf.allow.empty())
{
PRINTF("***WARNING: The IP security order is 'allow,deny' (deny if not allow).\n"_fmt);
PRINTF(" But, NO IP IS AUTHORISED!\n"_fmt);
@@ -3661,14 +2919,14 @@ bool display_conf_warnings(void)
else
{
// ACO::MUTUAL_FAILURE
- if (access_allow.empty())
+ if (login_conf.allow.empty())
{
PRINTF("***WARNING: The IP security order is 'mutual-failture'\n"_fmt);
PRINTF(" (allow if in the allow list and not in the deny list).\n"_fmt);
PRINTF(" But, NO IP IS AUTHORISED!\n"_fmt);
rv = false;
}
- else if (access_deny.size() == 1 && access_deny.front().mask() == IP4Address())
+ else if (login_conf.deny.size() == 1 && login_conf.deny.front().mask() == IP4Address())
{
PRINTF("***WARNING: The IP security order is mutual-failture\n"_fmt);
PRINTF(" (allow if in the allow list and not in the deny list).\n"_fmt);
@@ -3692,195 +2950,188 @@ void save_config_in_log(void)
// save configuration in log file
LOGIN_LOG("The configuration of the server is set:\n"_fmt);
- if (admin_state != 1)
+ if (!login_conf.admin_state)
LOGIN_LOG("- with no remote administration.\n"_fmt);
- else if (!admin_pass)
+ else if (!login_conf.admin_pass)
LOGIN_LOG("- with a remote administration with a VOID password.\n"_fmt);
- else if (admin_pass == stringish<AccountPass>("admin"_s))
+ else if (login_conf.admin_pass == stringish<AccountPass>("admin"_s))
LOGIN_LOG("- with a remote administration with the DEFAULT password.\n"_fmt);
else
LOGIN_LOG("- with a remote administration with the password of %zu character(s).\n"_fmt,
- admin_pass.size());
- if (access_ladmin.empty()
- || (access_ladmin.size() == 1 && access_ladmin.front().mask() == IP4Address()))
+ login_conf.admin_pass.size());
+ if (login_conf.ladminallowip.empty()
+ || (login_conf.ladminallowip.size() == 1 && login_conf.ladminallowip.front().mask() == IP4Address()))
{
LOGIN_LOG("- to accept any IP for remote administration\n"_fmt);
}
else
{
LOGIN_LOG("- to accept following IP for remote administration:\n"_fmt);
- for (const IP4Mask& ae : access_ladmin)
+ for (const IP4Mask& ae : login_conf.ladminallowip)
LOGIN_LOG(" %s\n"_fmt, ae);
}
- if (!gm_pass)
+ if (!login_conf.gm_pass)
LOGIN_LOG("- with a VOID 'To GM become' password (gm_pass).\n"_fmt);
- else if (gm_pass == "gm"_s)
+ else if (login_conf.gm_pass == "gm"_s)
LOGIN_LOG("- with the DEFAULT 'To GM become' password (gm_pass).\n"_fmt);
else
LOGIN_LOG("- with a 'To GM become' password (gm_pass) of %zu character(s).\n"_fmt,
- gm_pass.size());
- if (!level_new_gm)
+ login_conf.gm_pass.size());
+ if (!login_conf.level_new_gm)
LOGIN_LOG("- to refuse any creation of GM with @gm.\n"_fmt);
else
LOGIN_LOG("- to create GM with level '%d' when @gm is used.\n"_fmt,
- level_new_gm);
+ login_conf.level_new_gm);
- if (new_account == 1)
+ if (login_conf.new_account)
LOGIN_LOG("- to ALLOW new users (with _F/_M).\n"_fmt);
else
LOGIN_LOG("- to NOT ALLOW new users (with _F/_M).\n"_fmt);
- LOGIN_LOG("- with port: %d.\n"_fmt, login_port);
+ LOGIN_LOG("- with port: %d.\n"_fmt, login_conf.login_port);
LOGIN_LOG("- with the accounts file name: '%s'.\n"_fmt,
- account_filename);
+ login_conf.account_filename);
LOGIN_LOG("- with the GM accounts file name: '%s'.\n"_fmt,
- gm_account_filename);
- if (gm_account_filename_check_timer == interval_t::zero())
+ login_conf.gm_account_filename);
+ if (login_conf.gm_account_filename_check_timer == interval_t::zero())
LOGIN_LOG("- to NOT check GM accounts file modifications.\n"_fmt);
else
LOGIN_LOG("- to check GM accounts file modifications every %lld seconds.\n"_fmt,
- maybe_cast<long long>(gm_account_filename_check_timer.count()));
+ maybe_cast<long long>(login_conf.gm_account_filename_check_timer.count()));
// not necessary to log the 'login_log_filename', we are inside :)
- LOGIN_LOG("- with the unknown packets file name: '%s'.\n"_fmt,
- login_log_unknown_packets_filename);
- if (save_unknown_packets)
- LOGIN_LOG("- to SAVE all unkown packets.\n"_fmt);
- else
- LOGIN_LOG("- to SAVE only unkown packets sending by a char-server or a remote administration.\n"_fmt);
- if (display_parse_login)
+ if (login_conf.display_parse_login)
LOGIN_LOG("- to display normal parse packets on console.\n"_fmt);
else
LOGIN_LOG("- to NOT display normal parse packets on console.\n"_fmt);
- if (display_parse_admin)
+ if (login_conf.display_parse_admin)
LOGIN_LOG("- to display administration parse packets on console.\n"_fmt);
else
LOGIN_LOG("- to NOT display administration parse packets on console.\n"_fmt);
- if (display_parse_fromchar)
+ if (login_conf.display_parse_fromchar)
LOGIN_LOG("- to display char-server parse packets on console.\n"_fmt);
else
LOGIN_LOG("- to NOT display char-server parse packets on console.\n"_fmt);
- if (!min_level_to_connect) // 0: all players, 1-99 at least gm level x
+ if (!login_conf.min_level_to_connect) // 0: all players, 1-99 at least gm level x
LOGIN_LOG("- with no minimum level for connection.\n"_fmt);
else
LOGIN_LOG("- to accept only GM with level %d or more.\n"_fmt,
- min_level_to_connect);
-
- if (add_to_unlimited_account)
- LOGIN_LOG("- to authorize adjustment (with timeadd ladmin) on an unlimited account.\n"_fmt);
- else
- LOGIN_LOG("- to refuse adjustment (with timeadd ladmin) on an unlimited account. You must use timeset (ladmin command) before.\n"_fmt);
+ login_conf.min_level_to_connect);
- if (start_limited_time < 0)
- LOGIN_LOG("- to create new accounts with an unlimited time.\n"_fmt);
- else if (start_limited_time == 0)
- LOGIN_LOG("- to create new accounts with a limited time: time of creation.\n"_fmt);
- else
- LOGIN_LOG("- to create new accounts with a limited time: time of creation + %d second(s).\n"_fmt,
- start_limited_time);
-
- if (check_ip_flag)
- LOGIN_LOG("- with control of players IP between login-server and char-server.\n"_fmt);
- else
- LOGIN_LOG("- to not check players IP between login-server and char-server.\n"_fmt);
-
- if (access_order == ACO::DENY_ALLOW)
+ if (login_conf.order == ACO::DENY_ALLOW)
{
- if (access_deny.empty())
+ if (login_conf.deny.empty())
{
LOGIN_LOG("- with the IP security order: 'deny,allow' (allow if not deny). You refuse no IP.\n"_fmt);
}
- else if (access_deny.size() == 1 && access_deny.front().mask() == IP4Address())
+ else if (login_conf.deny.size() == 1 && login_conf.deny.front().mask() == IP4Address())
{
LOGIN_LOG("- with the IP security order: 'deny,allow' (allow if not deny). You refuse ALL IP.\n"_fmt);
}
else
{
LOGIN_LOG("- with the IP security order: 'deny,allow' (allow if not deny). Refused IP are:\n"_fmt);
- for (IP4Mask ae : access_deny)
+ for (IP4Mask ae : login_conf.deny)
LOGIN_LOG(" %s\n"_fmt, ae);
}
}
- else if (access_order == ACO::ALLOW_DENY)
+ else if (login_conf.order == ACO::ALLOW_DENY)
{
- if (access_allow.empty())
+ if (login_conf.allow.empty())
{
LOGIN_LOG("- with the IP security order: 'allow,deny' (deny if not allow). But, NO IP IS AUTHORISED!\n"_fmt);
}
- else if (access_allow.size() == 1 && access_allow.front().mask() == IP4Address())
+ else if (login_conf.allow.size() == 1 && login_conf.allow.front().mask() == IP4Address())
{
LOGIN_LOG("- with the IP security order: 'allow,deny' (deny if not allow). You authorise ALL IP.\n"_fmt);
}
else
{
LOGIN_LOG("- with the IP security order: 'allow,deny' (deny if not allow). Authorised IP are:\n"_fmt);
- for (IP4Mask ae : access_allow)
+ for (IP4Mask ae : login_conf.allow)
LOGIN_LOG(" %s\n"_fmt, ae);
}
}
else
{ // ACO_MUTUAL_FAILTURE
LOGIN_LOG("- with the IP security order: 'mutual-failture' (allow if in the allow list and not in the deny list).\n"_fmt);
- if (access_allow.empty())
+ if (login_conf.allow.empty())
{
LOGIN_LOG(" But, NO IP IS AUTHORISED!\n"_fmt);
}
- else if (access_deny.size() == 1 && access_deny.front().mask() == IP4Address())
+ else if (login_conf.deny.size() == 1 && login_conf.deny.front().mask() == IP4Address())
{
LOGIN_LOG(" But, you refuse ALL IP!\n"_fmt);
}
else
{
- if (access_allow.size() == 1 && access_allow.front().mask() == IP4Address())
+ if (login_conf.allow.size() == 1 && login_conf.allow.front().mask() == IP4Address())
{
LOGIN_LOG(" You authorise ALL IP.\n"_fmt);
}
else
{
LOGIN_LOG(" Authorised IP are:\n"_fmt);
- for (IP4Mask ae : access_allow)
+ for (IP4Mask ae : login_conf.allow)
LOGIN_LOG(" %s\n"_fmt, ae);
}
LOGIN_LOG(" Refused IP are:\n"_fmt);
- for (IP4Mask ae : access_deny)
+ for (IP4Mask ae : login_conf.deny)
LOGIN_LOG(" %s\n"_fmt, ae);
}
}
}
+static
+bool login_config(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ return parse_login_conf(login_conf, key, value);
+}
+
+static
+bool login_lan_config(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ return parse_login_lan_conf(login_lan_conf, key, value);
+}
+
+static
+bool login_confs(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ if (key.data == "login_conf"_s)
+ {
+ return load_config_file(value.data, login_config);
+ }
+ if (key.data == "login_lan_conf"_s)
+ {
+ return load_config_file(value.data, login_lan_config);
+ }
+ key.span.error("Unknown meta-key for login server"_s);
+ return false;
+}
+} // namespace login
+
//--------------------------------------
// Function called at exit of the server
//--------------------------------------
void term_func(void)
{
- mmo_auth_sync();
+ login::mmo_auth_sync();
- auth_data.clear();
- gm_account_db.clear();
- for (int i = 0; i < MAX_SERVERS; i++)
+ login::auth_data.clear();
+ login::gm_account_db.clear();
+ for (int i = 0; i < login::MAX_SERVERS; i++)
{
- Session *s = server_session[i];
+ Session *s = login::server_session[i];
if (s)
delete_session(s);
}
- delete_session(login_session);
+ delete_session(login::login_session);
LOGIN_LOG("----End of login-server (normal end with closing of all files).\n"_fmt);
}
-static
-bool login_confs(XString key, ZString value)
-{
- unsigned sum = 0;
- sum += login_config(key, value);
- sum += login_lan_config(key, value);
- if (sum >= 2)
- abort();
- return sum;
-}
-
//------------------------------
// Main function of login-server
//------------------------------
@@ -3913,59 +3164,60 @@ int do_init(Slice<ZString> argv)
else
{
loaded_config_yet = true;
- runflag &= load_config_file(argvi, login_confs);
+ runflag &= load_config_file(argvi, login::login_confs);
}
}
if (!loaded_config_yet)
- runflag &= load_config_file("conf/tmwa-login.conf"_s, login_confs);
+ runflag &= load_config_file("conf/tmwa-login.conf"_s, login::login_confs);
// not in login_config_read, because we can use 'import' option, and display same message twice or more
// (why is that bad?)
- runflag &= display_conf_warnings();
+ runflag &= login::display_conf_warnings();
// not before, because log file name can be changed
// (that doesn't stop the char-server though)
- save_config_in_log();
- runflag &= lan_check();
+ login::save_config_in_log();
+ runflag &= login::lan_check();
- for (int i = 0; i < AUTH_FIFO_SIZE; i++)
- auth_fifo[i].delflag = 1;
- for (int i = 0; i < MAX_SERVERS; i++)
- server_session[i] = nullptr;
+ for (int i = 0; i < login::AUTH_FIFO_SIZE; i++)
+ login::auth_fifo[i].delflag = 1;
+ for (int i = 0; i < login::MAX_SERVERS; i++)
+ login::server_session[i] = nullptr;
- read_gm_account();
- mmo_auth_init();
+ login::read_gm_account();
+ login::mmo_auth_init();
// set_termfunc (mmo_auth_sync);
- login_session = make_listen_port(login_port, SessionParsers{.func_parse= parse_login, .func_delete= delete_login});
+ login::login_session = make_listen_port(login::login_conf.login_port, SessionParsers{.func_parse= login::parse_login, .func_delete= login::delete_login});
Timer(gettick() + 5_min,
- check_auth_sync,
+ login::check_auth_sync,
5_min
).detach();
- if (anti_freeze_enable > 0)
+ if (login::login_conf.anti_freeze_enable > 0)
{
Timer(gettick() + 1_s,
- char_anti_freeze_system,
- anti_freeze_interval
+ login::char_anti_freeze_system,
+ login::login_conf.anti_freeze_interval
).detach();
}
// add timer to check GM accounts file modification
- std::chrono::seconds j = gm_account_filename_check_timer;
+ std::chrono::seconds j = login::login_conf.gm_account_filename_check_timer;
if (j == interval_t::zero())
j = 1_min;
Timer(gettick() + j,
- check_GM_file,
+ login::check_GM_file,
j).detach();
LOGIN_LOG("The login-server is ready (Server is listening on the port %d).\n"_fmt,
- login_port);
+ login::login_conf.login_port);
PRINTF("The login-server is " SGR_BOLD SGR_GREEN "ready" SGR_RESET " (Server is listening on the port %d).\n\n"_fmt,
- login_port);
+ login::login_conf.login_port);
return 0;
}
+// namespace login ends before term_func and do_init
} // namespace tmwa
diff --git a/src/login/login.hpp b/src/login/login.hpp
index 92f3c76..ae99558 100644
--- a/src/login/login.hpp
+++ b/src/login/login.hpp
@@ -18,11 +18,69 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
+#include "login.t.hpp"
+
#include "fwd.hpp"
-#include "login.t.hpp"
+#include "../strings/vstring.hpp"
+
+#include "../compat/time_t.hpp"
+
+#include "../generic/array.hpp"
+
+#include "../net/ip.hpp"
+#include "../net/timestamp-utils.hpp"
+
+#include "../mmo/consts.hpp"
+#include "../mmo/enums.hpp"
+#include "../mmo/ids.hpp"
+#include "../mmo/strs.hpp"
+
+#include "../proto2/net-GlobalReg.hpp"
+
+#include "../high/mmo.hpp"
namespace tmwa
{
+namespace login
+{
+constexpr AccountId START_ACCOUNT_NUM = wrap<AccountId>(2000000);
+constexpr AccountId END_ACCOUNT_NUM = wrap<AccountId>(100000000);
+
+struct AuthData
+{
+ AccountId account_id;
+ SEX sex;
+ AccountName userid;
+ AccountCrypt pass;
+ timestamp_milliseconds_buffer lastlogin;
+ int logincount;
+ int state; // packet 0x006a value + 1 (0: compte OK)
+ AccountEmail email; // e-mail (by default: a@a.com)
+ timestamp_seconds_buffer error_message; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a)
+ TimeT ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban)
+ IP4Address last_ip; // save of last IP of connection
+ VString<254> memo; // a memo field
+ int account_reg2_num;
+ Array<GlobalReg, ACCOUNT_REG2_NUM> account_reg2;
+};
+
+struct mmo_char_server
+{
+ ServerName name;
+ IP4Address ip;
+ uint16_t port;
+ uint16_t users;
+};
+
+struct AuthFifo
+{
+ AccountId account_id;
+ int login_id1, login_id2;
+ IP4Address ip;
+ SEX sex;
+ int delflag;
+};
+} // namespace login
} // namespace tmwa
diff --git a/src/login/login.t.hpp b/src/login/login.t.hpp
index f2c775a..9be0620 100644
--- a/src/login/login.t.hpp
+++ b/src/login/login.t.hpp
@@ -1,9 +1,10 @@
#pragma once
-// login.t.hpp - externally useful types from login
+// login.t.hpp - Types for the login server
//
// Copyright © ????-2004 Athena Dev Teams
// Copyright © 2004-2011 The Mana World Development Team
// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2014 MadCamel
//
// This file is part of The Mana World (Athena server)
//
@@ -22,23 +23,18 @@
#include "fwd.hpp"
-#include <cstdint>
-
-#include "../generic/enum.hpp"
-
namespace tmwa
{
-namespace e
+namespace login
{
-enum class VERSION_2 : uint8_t
+enum class ACO
{
- /// client supports updatehost
- UPDATEHOST = 0x01,
- /// send servers in forward order
- SERVERORDER = 0x02,
+ DENY_ALLOW,
+ ALLOW_DENY,
+ MUTUAL_FAILURE,
};
-ENUM_BITWISE_OPERATORS(VERSION_2)
-}
-using e::VERSION_2;
+} // namespace login
+// out of namespace because ADL is dumb
+bool extract(XString str, login::ACO *aco);
} // namespace tmwa
diff --git a/src/login/main.cpp b/src/login/main.cpp
index 48a471a..4495bda 100644
--- a/src/login/main.cpp
+++ b/src/login/main.cpp
@@ -17,7 +17,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../mmo/core.hpp"
+#include "../high/core.hpp"
#include "login.hpp"
diff --git a/src/main-gdb-head.py b/src/main-gdb-head.py
index 2dac471..a465c97 100644
--- a/src/main-gdb-head.py
+++ b/src/main-gdb-head.py
@@ -29,22 +29,55 @@ def get_basic_type(type_):
type_ = type_.strip_typedefs()
return type_.unqualified()
+def info_symbol(addr):
+ ''' returns (symbol, offset, section, lib or None) or None?
+ '''
+ info = gdb.execute('info symbol %d' % addr, to_string=True)
+ try:
+ sym_and_off, sec_and_lib = info.split(' in section ')
+ except ValueError:
+ return None
+ try:
+ sym, off = sym_and_off.split(' + ')
+ except ValueError:
+ sym = sym_and_off
+ off = 0
+ else:
+ off = int(off, 10)
+ try:
+ sec, lib = sec_and_lib.split(' of ')
+ except ValueError:
+ sec = sec_and_lib
+ lib = None
+ return (sym, off, sec, lib)
+
def finish():
- global finish, initial_globals, FastPrinters, EnumPrinter
+ global finish, initial_globals, FastPrinters, EnumPrinter, PointerPrinter
final_globals = {id(v):v for v in globals().values()}
diff = set(final_globals.keys()) - set(initial_globals.keys()) \
- - {'finish', 'initial_globals', 'FastPrinters'}
+ - {
+ 'finish',
+ 'initial_globals',
+ 'FastPrinters',
+ 'EnumPrinter',
+ 'PointerPrinter',
+ }
fp = FastPrinters()
ep = EnumPrinter
+ ptrp = PointerPrinter
# After this, don't access any more globals in this function.
- del finish, initial_globals, FastPrinters, EnumPrinter
+ del finish, initial_globals, FastPrinters, EnumPrinter, PointerPrinter
for i in diff:
v = final_globals[i]
if hasattr(v, 'children') or hasattr(v, 'to_string'):
fp.add_printer(v)
+ # TODO see if there's a way to detect the top-level printers too
+ # the problem is that some of them collide and the order *is*
+ # important, but sets and dicts don't preserve order.
+ # Particularly, 'PointerPrinter' must come after 'FastPrinters'.
obj = gdb.current_objfile()
if obj is None:
@@ -53,9 +86,12 @@ def finish():
else:
filename = obj.filename
obj.pretty_printers.append(fp)
+ n = len(obj.pretty_printers)
obj.pretty_printers.append(ep)
- print('Added %d+1 custom printers for %s'
- % (len(fp.printers), filename))
+ obj.pretty_printers.append(ptrp)
+ n = len(obj.pretty_printers) - n
+ print('Added %d+%d custom printers for %s'
+ % (len(fp.printers), n, filename))
class EnumPrinter(object):
__slots__ = ('_value')
@@ -82,6 +118,49 @@ class EnumPrinter(object):
scope = get_basic_type(v.type).tag
return '%s::%s' % (scope, name)
+class PointerPrinter(object):
+ __slots__ = ('_value')
+ name = 'any-symbol-pointer'
+ enabled = True
+
+ def __new__(cls, v):
+ type = get_basic_type(v.type)
+ if type.code != gdb.TYPE_CODE_PTR:
+ return None
+ return object.__new__(cls)
+
+ def __init__(self, v):
+ self._value = v
+
+ def to_string(self):
+ v = self._value
+ uptr = gdb.lookup_type('uintptr_t')
+ addr = int(v.cast(uptr))
+ if not addr:
+ s = 'nullptr'
+ else:
+ try:
+ sym, off, sec, lib = info_symbol(addr)
+ except TypeError:
+ sp = gdb.parse_and_eval('$sp')
+ sp = int(sp.cast(uptr))
+ LOTS = 8 * 1024 * 1024
+ diff = addr - sp
+ if +diff >= 0 and +diff <= LOTS:
+ a = '<$sp+0x%x>' % +diff
+ elif -diff >= 0 and -diff <= LOTS:
+ a = '<$sp-0x%x>' % -diff
+ else:
+ a = '<heap 0x%x>' % addr
+ s = '(%s)%s' % (v.type, a)
+ else:
+ if off:
+ s = '<%s+%d>' % off
+ else:
+ s = '<%s>' % sym
+ return s
+
+
class FastPrinters(object):
''' printer dispatch the way gdb *should* have done it
'''
diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp
index 0d70b36..7739966 100644
--- a/src/map/atcommand.cpp
+++ b/src/map/atcommand.cpp
@@ -24,8 +24,6 @@
#include <algorithm>
-#include "../conf/version.hpp"
-
#include "../compat/nullpo.hpp"
#include "../compat/fun.hpp"
@@ -39,31 +37,40 @@
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+#include "../io/extract.hpp"
#include "../io/read.hpp"
#include "../io/write.hpp"
#include "../net/socket.hpp"
#include "../net/timer.hpp"
+#include "../net/timestamp-utils.hpp"
#include "../mmo/config_parse.hpp"
-#include "../mmo/core.hpp"
-#include "../mmo/extract.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
#include "../mmo/extract_enums.hpp"
#include "../mmo/human_time_diff.hpp"
#include "../mmo/ids.hpp"
-#include "../mmo/mmo.hpp"
-#include "../mmo/utils.hpp"
#include "../mmo/version.hpp"
+#include "../high/core.hpp"
+#include "../high/extract_mmo.hpp"
+#include "../high/mmo.hpp"
+#include "../high/utils.hpp"
+
+#include "../ast/npc.hpp"
+
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "chrif.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "intif.hpp"
#include "itemdb.hpp"
#include "map.hpp"
+#include "map_conf.hpp"
#include "mob.hpp"
#include "npc.hpp"
+#include "npc-parse.hpp"
#include "party.hpp"
#include "pc.hpp"
#include "skill.hpp"
@@ -76,6 +83,8 @@
namespace tmwa
{
+namespace map
+{
enum class ATCE
{
OKAY,
@@ -105,7 +114,7 @@ Map<XString, AtCommandInfo> atcommand_info;
static
-AtCommandInfo *atcommand(XString cmd);
+Option<Borrowed<AtCommandInfo>> atcommand(XString cmd);
// These @commands are used within other @commands.
static
@@ -203,9 +212,7 @@ void log_atcommand(dumb_ptr<map_session_data> sd, ZString cmd)
return;
timestamp_seconds_buffer tmpstr;
stamp_time(tmpstr);
- MapName map = (sd->bl_m
- ? sd->bl_m->name_
- : stringish<MapName>("undefined.gat"_s));
+ MapName map = (sd->bl_m->name_);
FPRINTF(*fp, "[%s] %s(%d,%d) %s(%d) : %s\n"_fmt,
tmpstr,
map, sd->bl_x, sd->bl_y,
@@ -213,11 +220,9 @@ void log_atcommand(dumb_ptr<map_session_data> sd, ZString cmd)
cmd);
}
-AString gm_log;
-
io::AppendFile *get_gm_log()
{
- if (!gm_log)
+ if (!map_conf.gm_log)
return nullptr;
struct tm ctime = TimeT::now();
@@ -233,7 +238,7 @@ io::AppendFile *get_gm_log()
last_logfile_nr = logfile_nr;
AString fullname = STRPRINTF("%s.%04d-%02d"_fmt,
- gm_log, year, month);
+ map_conf.gm_log, year, month);
if (gm_logfile)
gm_logfile.reset();
@@ -243,7 +248,7 @@ io::AppendFile *get_gm_log()
if (!gm_logfile)
{
perror("GM log file");
- gm_log = AString();
+ map_conf.gm_log = AString();
}
return gm_logfile.get();
}
@@ -260,8 +265,6 @@ bool is_atcommand(Session *s, dumb_ptr<map_session_data> sd,
ZString arg;
asplit(message, &command, &arg);
- AtCommandInfo *info = atcommand(command);
-
if (!gmlvl)
gmlvl = pc_isGM(sd);
if (battle_config.atcommand_gm_only != 0 && !gmlvl)
@@ -271,14 +274,16 @@ bool is_atcommand(Session *s, dumb_ptr<map_session_data> sd,
clif_displaymessage(s, output);
return true;
}
- if (!info)
+
+ Option<P<AtCommandInfo>> info_ = atcommand(command);
+ P<AtCommandInfo> info = TRY_UNWRAP(info_,
{
AString output = STRPRINTF("GM command not found: %s"_fmt,
AString(command));
clif_displaymessage(s, output);
return true;
// don't show in chat
- }
+ });
if (!(gmlvl.satisfies(info->level)))
{
AString output = STRPRINTF("GM command is level %d, but you are level %d: %s"_fmt,
@@ -320,15 +325,15 @@ bool is_atcommand(Session *s, dumb_ptr<map_session_data> sd,
}
}
-AtCommandInfo *atcommand(XString cmd)
+Option<Borrowed<AtCommandInfo>> atcommand(XString cmd)
{
if (cmd.startswith('@'))
{
XString command = cmd.xslice_t(1);
- AtCommandInfo *it = atcommand_info.search(command);
+ Option<P<AtCommandInfo>> it = atcommand_info.search(command);
return it;
}
- return nullptr;
+ return None;
}
static
@@ -344,51 +349,42 @@ void atkillmonster_sub(dumb_ptr<block_list> bl, int flag)
}
static
-AtCommandInfo *get_atcommandinfo_byname(XString name)
+Option<Borrowed<AtCommandInfo>> get_atcommandinfo_byname(XString name)
{
return atcommand_info.search(name);
}
-bool atcommand_config_read(ZString cfgName)
+static
+bool atcommand_config(io::Spanned<XString> w1, io::Spanned<ZString> w2)
{
- io::ReadFile in(cfgName);
- if (!in.is_open())
- {
- PRINTF("At commands configuration file not found: %s\n"_fmt, cfgName);
- return false;
- }
-
bool rv = true;
- AString line;
- while (in.getline(line))
{
- if (is_comment(line))
- continue;
- XString w1;
- ZString w2;
- if (!config_split(line, &w1, &w2))
+ Option<P<AtCommandInfo>> p_ = get_atcommandinfo_byname(w1.data);
+ OMATCH_BEGIN (p_)
{
- PRINTF("Bad config line: %s\n"_fmt, line);
- rv = false;
- continue;
- }
- AtCommandInfo *p = get_atcommandinfo_byname(w1);
- if (p != nullptr)
- {
- p->level = GmLevel::from(static_cast<uint32_t>(atoi(w2.c_str())));
- }
- else if (w1 == "import"_s)
- rv &= atcommand_config_read(w2);
- else
- {
- PRINTF("%s: bad line: %s\n"_fmt, cfgName, line);
- rv = false;
+ OMATCH_CASE_SOME (p)
+ {
+ p->level = GmLevel::from(static_cast<uint32_t>(atoi(w2.data.c_str())));
+ }
+ OMATCH_CASE_NONE ()
+ {
+ {
+ w1.span.error("Unknown @command for permission level config."_s);
+ rv = false;
+ }
+ }
}
+ OMATCH_END ();
}
return rv;
}
+bool atcommand_config_read(ZString cfgName)
+{
+ return load_config_file(cfgName, atcommand_config);
+}
+
/// @ command processing functions
static
@@ -421,9 +417,7 @@ ATCE atcommand_help(Session *s, dumb_ptr<map_session_data>,
if (message.startswith('@'))
{
ZString cmd = message.xslice_t(1);
- const AtCommandInfo *info = atcommand_info.search(cmd);
- if (!info)
- return ATCE::EXIST;
+ P<AtCommandInfo> info = TRY_UNWRAP(atcommand_info.search(cmd), return ATCE::EXIST);
clif_displaymessage(s, STRPRINTF("Usage: @%s %s"_fmt, cmd, info->args));
clif_displaymessage(s, info->help);
return ATCE::OKAY;
@@ -534,16 +528,16 @@ ATCE atcommand_charwarp(Session *s, dumb_ptr<map_session_data> sd,
// you can rura+ only lower or same GM level
if (x > 0 && x < 800 && y > 0 && y < 800)
{
- map_local *m = map_mapname2mapid(map_name);
- if (m != nullptr && m->flag.get(MapFlag::NOWARPTO)
- && !pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))
+ Option<P<map_local>> m = map_mapname2mapid(map_name);
+ if (m.map([](P<map_local> m_){ return m_->flag.get(MapFlag::NOWARPTO); }).copy_or(false)
+ && !pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level))
{
clif_displaymessage(s,
"You are not authorised to warp someone to this map."_s);
return ATCE::PERM;
}
- if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (pl_sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp this player from its actual map."_s);
@@ -603,16 +597,16 @@ ATCE atcommand_warp(Session *s, dumb_ptr<map_session_data> sd,
if (x > 0 && x < 800 && y > 0 && y < 800)
{
- map_local *m = map_mapname2mapid(map_name);
- if (m != nullptr && m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ Option<P<map_local>> m = map_mapname2mapid(map_name);
+ if (m.map([](P<map_local> m_){ return m_->flag.get(MapFlag::NOWARPTO); }).copy_or(false)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you to this map."_s);
return ATCE::PERM;
}
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you from your actual map."_s);
@@ -645,7 +639,7 @@ ATCE atcommand_where(Session *s, dumb_ptr<map_session_data> sd,
dumb_ptr<map_session_data> pl_sd = character.to__actual() ? map_nick2sd(character) : sd;
if (pl_sd != nullptr &&
!((battle_config.hide_GM_session
- || bool(pl_sd->status.option & Option::HIDE))
+ || bool(pl_sd->status.option & Opt0::HIDE))
&& !(pc_isGM(sd).detects(pc_isGM(pl_sd)))))
{
// you can look only lower or same level
@@ -679,15 +673,15 @@ ATCE atcommand_goto(Session *s, dumb_ptr<map_session_data> sd,
dumb_ptr<map_session_data> pl_sd = map_nick2sd(character);
if (pl_sd != nullptr)
{
- if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (pl_sd->bl_m->flag.get(MapFlag::NOWARPTO)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you to the map of this player."_s);
return ATCE::PERM;
}
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you from your actual map."_s);
@@ -707,6 +701,69 @@ ATCE atcommand_goto(Session *s, dumb_ptr<map_session_data> sd,
}
static
+ATCE atcommand_npc(Session *s, dumb_ptr<map_session_data> sd,
+ ZString message)
+{
+ NpcName npc;
+
+ if (!asplit(message, &npc))
+ {
+ clif_displaymessage(s,
+ "Please, enter a npc name (usage: @npc/@warptonpc/@gotonpc <npc>)."_s);
+ return ATCE::USAGE;
+ }
+
+ dumb_ptr<npc_data> nd = npc_name2id(npc);
+ if (nd != nullptr)
+ {
+ if (nd->bl_m->flag.get(MapFlag::NOWARPTO)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
+ {
+ clif_displaymessage(s,
+ "You are not authorised to warp you to the map of this npc."_s);
+ return ATCE::PERM;
+ }
+ if (sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
+ {
+ clif_displaymessage(s,
+ "You are not authorised to warp you from your actual map."_s);
+ return ATCE::PERM;
+ }
+
+ int x = nd->bl_x, y = nd->bl_y, x0 = (x >= 5)? (x - 5): 0, j = 0,
+ y0 = (y >= 5)? (y - 5): 0, x1 = (x + 5), y1 = (y + 5), max;
+ max = (y1 - y0 + 1) * (x1 - x0 + 1) * 3;
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(nd->bl_m->name_), return ATCE::OKAY);
+ if (max > 1000)
+ max = 1000;
+ if (bool(map_getcell(m, x, y) & MapCell::UNWALKABLE)){
+ do
+ {
+ x = random_::in(x0, x1);
+ y = random_::in(y0, y1);
+ }
+ while (bool(map_getcell(m, x, y) & MapCell::UNWALKABLE)
+ && (++j) < max);
+ if (j >= max)
+ {
+ return ATCE::OKAY; // Since reference of the place which boils first went wrong, it stops.
+ }
+ }
+ pc_setpos(sd, nd->bl_m->name_, x, y, BeingRemoveWhy::WARPED);
+ AString output = STRPRINTF("Jump to %s"_fmt, npc);
+ clif_displaymessage(s, output);
+ }
+ else
+ {
+ clif_displaymessage(s, "Npc not found."_s);
+ return ATCE::EXIST;
+ }
+
+ return ATCE::OKAY;
+}
+
+static
ATCE atcommand_jump(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
@@ -720,15 +777,15 @@ ATCE atcommand_jump(Session *s, dumb_ptr<map_session_data> sd,
y = random_::in(1, 399);
if (x > 0 && x < 800 && y > 0 && y < 800)
{
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARPTO)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you to your actual map."_s);
return ATCE::PERM;
}
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you from your actual map."_s);
@@ -768,7 +825,7 @@ ATCE atcommand_who(Session *s, dumb_ptr<map_session_data> sd,
GmLevel pl_gm_level = pc_isGM(pl_sd);
if (!
((battle_config.hide_GM_session
- || bool(pl_sd->status.option & Option::HIDE))
+ || bool(pl_sd->status.option & Opt0::HIDE))
&& !(gm_level.detects(pl_gm_level))))
{
// you can look only lower or same level
@@ -812,7 +869,6 @@ ATCE atcommand_whogroup(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
int count;
- PartyPair p;
VString<23> match_text = message;
match_text = match_text.to_lower();
@@ -830,7 +886,7 @@ ATCE atcommand_whogroup(Session *s, dumb_ptr<map_session_data> sd,
GmLevel pl_gm_level = pc_isGM(pl_sd);
if (!
((battle_config.hide_GM_session
- || bool(pl_sd->status.option & Option::HIDE))
+ || bool(pl_sd->status.option & Opt0::HIDE))
&& (!(gm_level.detects(pl_gm_level)))))
{
// you can look only lower or same level
@@ -838,8 +894,8 @@ ATCE atcommand_whogroup(Session *s, dumb_ptr<map_session_data> sd,
if (player_name.contains_seq(match_text))
{
// search with no case sensitive
- p = party_search(pl_sd->status.party_id);
- PartyName temp0 = p ? p->name : stringish<PartyName>("None"_s);
+ Option<PartyPair> p_ = party_search(pl_sd->status.party_id);
+ PartyName temp0 = p_.pmd_pget(&PartyMost::name).move_or(stringish<PartyName>("None"_s));
AString output;
if (pl_gm_level)
output = STRPRINTF(
@@ -870,15 +926,14 @@ ATCE atcommand_whomap(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
int count;
- map_local *map_id;
- {
+ Borrowed<map_local> map_id =
+ ({
MapName map_name;
extract(message, &map_name);
- map_id = map_mapname2mapid(map_name);
- if (map_id == nullptr)
- map_id = sd->bl_m;
- }
+
+ map_mapname2mapid(map_name).copy_or(sd->bl_m);
+ });
count = 0;
GmLevel gm_level = pc_isGM(sd);
@@ -893,7 +948,7 @@ ATCE atcommand_whomap(Session *s, dumb_ptr<map_session_data> sd,
GmLevel pl_gm_level = pc_isGM(pl_sd);
if (!
((battle_config.hide_GM_session
- || bool(pl_sd->status.option & Option::HIDE))
+ || bool(pl_sd->status.option & Opt0::HIDE))
&& (!(gm_level.detects(pl_gm_level)))))
{
// you can look only lower or same level
@@ -929,16 +984,14 @@ ATCE atcommand_whomapgroup(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
int count;
- PartyPair p;
- map_local *map_id;
- {
+ P<map_local> map_id =
+ ({
MapName map_name;
extract(message, &map_name);
- map_id = map_mapname2mapid(map_name);
- if (map_id == nullptr)
- map_id = sd->bl_m;
- }
+
+ map_mapname2mapid(map_name).copy_or(sd->bl_m);
+ });
count = 0;
GmLevel gm_level = pc_isGM(sd);
@@ -953,14 +1006,14 @@ ATCE atcommand_whomapgroup(Session *s, dumb_ptr<map_session_data> sd,
GmLevel pl_gm_level = pc_isGM(pl_sd);
if (!
((battle_config.hide_GM_session
- || bool(pl_sd->status.option & Option::HIDE))
+ || bool(pl_sd->status.option & Opt0::HIDE))
&& (!(gm_level.detects(pl_gm_level)))))
{
// you can look only lower or same level
if (pl_sd->bl_m == map_id)
{
- p = party_search(pl_sd->status.party_id);
- PartyName temp0 = p ? p->name : stringish<PartyName>("None"_s);
+ Option<PartyPair> p_ = party_search(pl_sd->status.party_id);
+ PartyName temp0 = p_.pmd_pget(&PartyMost::name).copy_or(stringish<PartyName>("None"_s));
AString output;
if (pl_gm_level)
output = STRPRINTF("Name: %s (GM:%d) | Party: '%s'"_fmt,
@@ -994,7 +1047,6 @@ ATCE atcommand_whogm(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
int count;
- PartyPair p;
VString<23> match_text = message;
match_text = match_text.to_lower();
@@ -1014,7 +1066,7 @@ ATCE atcommand_whogm(Session *s, dumb_ptr<map_session_data> sd,
{
if (!
((battle_config.hide_GM_session
- || bool(pl_sd->status.option & Option::HIDE))
+ || bool(pl_sd->status.option & Opt0::HIDE))
&& (!(gm_level.detects(pl_gm_level)))))
{
// you can look only lower or same level
@@ -1034,8 +1086,8 @@ ATCE atcommand_whogm(Session *s, dumb_ptr<map_session_data> sd,
"Novice/Human"_s,
pl_sd->status.job_level);
clif_displaymessage(s, output);
- p = party_search(pl_sd->status.party_id);
- PartyName temp0 = p ? p->name : stringish<PartyName>("None"_s);
+ Option<PartyPair> p_ = party_search(pl_sd->status.party_id);
+ PartyName temp0 = p_.pmd_pget(&PartyMost::name).copy_or(stringish<PartyName>("None"_s));
output = STRPRINTF(
" Party: '%s'"_fmt,
temp0);
@@ -1076,16 +1128,16 @@ static
ATCE atcommand_load(Session *s, dumb_ptr<map_session_data> sd,
ZString)
{
- map_local *m = map_mapname2mapid(sd->status.save_point.map_);
- if (m != nullptr && m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ Option<P<map_local>> m = map_mapname2mapid(sd->status.save_point.map_);
+ if (m.map([](P<map_local> m_){ return m_->flag.get(MapFlag::NOWARPTO); }).copy_or(false)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you to your save map."_s);
return ATCE::PERM;
}
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you from your actual map."_s);
@@ -1148,16 +1200,13 @@ static
ATCE atcommand_storage(Session *s, dumb_ptr<map_session_data> sd,
ZString)
{
- Storage *stor;
-
if (sd->state.storage_open)
{
clif_displaymessage(s, "msg_table[250]"_s);
return ATCE::EXIST;
}
- if ((stor = account2storage2(sd->status_key.account_id)) != nullptr
- && stor->storage_status == 1)
+ if (account2storage2(sd->status_key.account_id).pmd_pget(&Storage::storage_status).copy_or(0) == 1)
{
clif_displaymessage(s, "msg_table[250]"_s);
return ATCE::EXIST;
@@ -1174,7 +1223,7 @@ ATCE atcommand_option(Session *s, dumb_ptr<map_session_data> sd,
{
Opt1 param1 = Opt1::ZERO;
Opt2 param2 = Opt2::ZERO;
- Option param3 = Option::ZERO;
+ Opt0 param3 = Opt0::ZERO;
if (!extract(message, record<',', 1>(&param1, &param2, &param3)))
return ATCE::USAGE;
@@ -1194,14 +1243,14 @@ static
ATCE atcommand_hide(Session *s, dumb_ptr<map_session_data> sd,
ZString)
{
- if (bool(sd->status.option & Option::HIDE))
+ if (bool(sd->status.option & Opt0::HIDE))
{
- sd->status.option &= ~Option::HIDE;
+ sd->status.option &= ~Opt0::HIDE;
clif_displaymessage(s, "Invisible: Off."_s);
}
else
{
- sd->status.option |= Option::HIDE;
+ sd->status.option |= Opt0::HIDE;
clif_displaymessage(s, "Invisible: On."_s);
}
clif_changeoption(sd);
@@ -1259,8 +1308,8 @@ ATCE atcommand_alive(Session *s, dumb_ptr<map_session_data> sd,
sd->status.hp = sd->status.max_hp;
sd->status.sp = sd->status.max_sp;
pc_setstand(sd);
- if (static_cast<interval_t>(battle_config.player_invincible_time) > interval_t::zero())
- pc_setinvincibletimer(sd, static_cast<interval_t>(battle_config.player_invincible_time));
+ if (battle_config.player_invincible_time > interval_t::zero())
+ pc_setinvincibletimer(sd, battle_config.player_invincible_time);
clif_updatestatus(sd, SP::HP);
clif_updatestatus(sd, SP::SP);
clif_resurrection(sd, 1);
@@ -1312,7 +1361,7 @@ ATCE atcommand_heal(Session *s, dumb_ptr<map_session_data> sd,
if (hp < 0)
// display like damage
- clif_damage(sd, sd, gettick(), interval_t::zero(), interval_t::zero(), -hp, 0, DamageType::RETURNED, 0);
+ clif_damage(sd, sd, gettick(), interval_t::zero(), interval_t::zero(), -hp, 0, DamageType::RETURNED);
if (hp != 0 || sp != 0)
{
@@ -1332,13 +1381,27 @@ ATCE atcommand_heal(Session *s, dumb_ptr<map_session_data> sd,
}
static
+Option<P<struct item_data>> extract_item_opt(XString item_name)
+{
+ Option<P<struct item_data>> item_data = itemdb_searchname(item_name);
+ if (item_data.is_some())
+ return item_data;
+
+ ItemNameId item_id;
+ if (extract(item_name, &item_id))
+ {
+ item_data = itemdb_exists(item_id);
+ return item_data;
+ }
+ return None;
+}
+
+static
ATCE atcommand_item(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
XString item_name;
int number = 0;
- ItemNameId item_id;
- struct item_data *item_data = nullptr;
int get_count, i;
if (!extract(message, record<' ', 1>(&item_name, &number)))
@@ -1351,14 +1414,10 @@ ATCE atcommand_item(Session *s, dumb_ptr<map_session_data> sd,
if (number <= 0)
number = 1;
- if ((item_data = itemdb_searchname(item_name)) != nullptr)
- item_id = item_data->nameid;
- else if (extract(item_name, &item_id) && (item_data = itemdb_exists(item_id)) != nullptr)
- item_id = item_data->nameid;
- else
- return ATCE::EXIST;
+ P<struct item_data> item_data = TRY_UNWRAP(extract_item_opt(item_name), return ATCE::EXIST);
+ ItemNameId item_id = item_data->nameid;
+ assert (item_id);
- if (item_id)
{
get_count = number;
if (item_data->type == ItemType::WEAPON
@@ -1379,11 +1438,6 @@ ATCE atcommand_item(Session *s, dumb_ptr<map_session_data> sd,
}
clif_displaymessage(s, "Item created."_s);
}
- else
- {
- clif_displaymessage(s, "Invalid item ID or name."_s);
- return ATCE::EXIST;
- }
return ATCE::OKAY;
}
@@ -1537,25 +1591,6 @@ ATCE atcommand_joblevelup(Session *s, dumb_ptr<map_session_data> sd,
}
static
-ATCE atcommand_gm(Session *s, dumb_ptr<map_session_data> sd,
- ZString message)
-{
- if (!message)
- return ATCE::USAGE;
-
- if (pc_isGM(sd))
- {
- // a GM can not use this function. only a normal player (become gm is not for gm!)
- clif_displaymessage(s, "You already have some GM powers."_s);
- return ATCE::PERM;
- }
- else
- chrif_changegm(sd->status_key.account_id, message);
-
- return ATCE::OKAY;
-}
-
-static
ATCE atcommand_pvpoff(Session *s, dumb_ptr<map_session_data> sd,
ZString)
{
@@ -1595,6 +1630,25 @@ ATCE atcommand_pvpoff(Session *s, dumb_ptr<map_session_data> sd,
}
static
+ATCE atcommand_exprate(Session *s, dumb_ptr<map_session_data>,
+ ZString message)
+{
+ int rate;
+
+ if (!extract(message, &rate) || !rate)
+ {
+ clif_displaymessage(s,
+ "Please, enter a rate adjustement (usage: @exprate <percent>)."_s);
+ return ATCE::USAGE;
+ }
+ battle_config.base_exp_rate = rate;
+ battle_config.job_exp_rate = rate;
+ AString output = STRPRINTF("All Xp at %d percent"_fmt, rate);
+ clif_displaymessage(s, output);
+ return ATCE::OKAY;
+}
+
+static
ATCE atcommand_pvpon(Session *s, dumb_ptr<map_session_data> sd,
ZString)
{
@@ -1621,7 +1675,6 @@ ATCE atcommand_pvpon(Session *s, dumb_ptr<map_session_data> sd,
pl_sd->pvp_timer = Timer(gettick() + 200_ms,
std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2, pl_sd->bl_id));
pl_sd->pvp_rank = 0;
- pl_sd->pvp_lastusers = 0;
pl_sd->pvp_point = 5;
}
}
@@ -1809,14 +1862,13 @@ static
void atcommand_killmonster_sub(Session *s, dumb_ptr<map_session_data> sd,
ZString message, const int drop)
{
- map_local *map_id;
- {
+ P<map_local> map_id =
+ ({
MapName map_name;
extract(message, &map_name);
- map_id = map_mapname2mapid(map_name);
- if (map_id == nullptr)
- map_id = sd->bl_m;
- }
+
+ map_mapname2mapid(map_name).copy_or(sd->bl_m);
+ });
map_foreachinarea(std::bind(atkillmonster_sub, ph::_1, drop),
map_id,
@@ -2080,15 +2132,15 @@ ATCE atcommand_recall(Session *s, dumb_ptr<map_session_data> sd,
if (pc_isGM(sd).overwhelms(pc_isGM(pl_sd)))
{
// you can recall only lower or same level
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARPTO)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp somenone to your actual map."_s);
return ATCE::PERM;
}
- if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (pl_sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp this player from its actual map."_s);
@@ -2127,8 +2179,8 @@ ATCE atcommand_revive(Session *s, dumb_ptr<map_session_data> sd,
{
pl_sd->status.hp = pl_sd->status.max_hp;
pc_setstand(pl_sd);
- if (static_cast<interval_t>(battle_config.player_invincible_time) > interval_t::zero())
- pc_setinvincibletimer(sd, static_cast<interval_t>(battle_config.player_invincible_time));
+ if (battle_config.player_invincible_time > interval_t::zero())
+ pc_setinvincibletimer(sd, battle_config.player_invincible_time);
clif_updatestatus(pl_sd, SP::HP);
clif_updatestatus(pl_sd, SP::SP);
clif_resurrection(pl_sd, 1);
@@ -2257,7 +2309,7 @@ ATCE atcommand_character_option(Session *s, dumb_ptr<map_session_data> sd,
{
Opt1 opt1;
Opt2 opt2;
- Option opt3;
+ Opt0 opt3;
CharName character;
if (!asplit(message, &opt1, &opt2, &opt3, &character))
return ATCE::USAGE;
@@ -2403,16 +2455,15 @@ ATCE atcommand_character_save(Session *s, dumb_ptr<map_session_data> sd,
if (pc_isGM(sd).overwhelms(pc_isGM(pl_sd)))
{
// you can change save point only to lower or same gm level
- map_local *m = map_mapname2mapid(map_name);
- if (m == nullptr)
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(map_name),
{
clif_displaymessage(s, "Map not found."_s);
return ATCE::EXIST;
- }
- else
+ });
+
{
- if (m != nullptr && m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (m->flag.get(MapFlag::NOWARPTO)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to set this map as a save map."_s);
@@ -2976,7 +3027,6 @@ ATCE atcommand_idsearch(Session *s, dumb_ptr<map_session_data>,
{
ItemName item_name;
int match;
- struct item_data *item;
if (!extract(message, &item_name) || !item_name)
return ATCE::USAGE;
@@ -2986,8 +3036,8 @@ ATCE atcommand_idsearch(Session *s, dumb_ptr<map_session_data>,
match = 0;
for (ItemNameId i = wrap<ItemNameId>(0); i < wrap<ItemNameId>(-1); i = next(i))
{
- if ((item = itemdb_exists(i)) != nullptr
- && item->jname.contains_seq(item_name))
+ P<struct item_data> item = TRY_UNWRAP(itemdb_exists(i), continue);
+ if (item->jname.contains_seq(item_name))
{
match++;
output = STRPRINTF("%s: %d"_fmt, item->jname, item->nameid);
@@ -3350,8 +3400,8 @@ ATCE atcommand_recallall(Session *s, dumb_ptr<map_session_data> sd,
{
int count;
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARPTO)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp somenone to your actual map."_s);
@@ -3371,8 +3421,8 @@ ATCE atcommand_recallall(Session *s, dumb_ptr<map_session_data> sd,
&& pc_isGM(sd).overwhelms(pc_isGM(pl_sd)))
{
// you can recall only lower or same level
- if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (pl_sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
count++;
else
pc_setpos(pl_sd, sd->mapname_, sd->bl_x, sd->bl_y, BeingRemoveWhy::QUIT);
@@ -3396,57 +3446,62 @@ ATCE atcommand_partyrecall(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
PartyName party_name;
- PartyPair p;
int count;
if (!extract(message, &party_name) || !party_name)
return ATCE::USAGE;
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARPTO)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp somenone to your actual map."_s);
return ATCE::PERM;
}
- if ((p = party_searchname(party_name)) ||
- // name first to avoid error when name begin with a number
- (p = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(message.c_str()))))))
+ // name first to avoid error when name begin with a number
+ Option<PartyPair> p_ = party_searchname(party_name);
+ if (p_.is_none())
+ p_ = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(message.c_str()))));
+ OMATCH_BEGIN (p_)
{
- count = 0;
- for (io::FD i : iter_fds())
+ OMATCH_CASE_SOME (p)
{
- Session *s2 = get_session(i);
- if (!s2)
- continue;
- dumb_ptr<map_session_data> pl_sd = dumb_ptr<map_session_data>(static_cast<map_session_data *>(s2->session_data.get()));
- if (pl_sd && pl_sd->state.auth
- && sd->status_key.account_id != pl_sd->status_key.account_id
- && pl_sd->status.party_id == p.party_id)
+ count = 0;
+ for (io::FD i : iter_fds())
{
- if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
- count++;
- else
- pc_setpos(pl_sd, sd->mapname_, sd->bl_x, sd->bl_y, BeingRemoveWhy::QUIT);
+ Session *s2 = get_session(i);
+ if (!s2)
+ continue;
+ dumb_ptr<map_session_data> pl_sd = dumb_ptr<map_session_data>(static_cast<map_session_data *>(s2->session_data.get()));
+ if (pl_sd && pl_sd->state.auth
+ && sd->status_key.account_id != pl_sd->status_key.account_id
+ && pl_sd->status.party_id == p.party_id)
+ {
+ if (pl_sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
+ count++;
+ else
+ pc_setpos(pl_sd, sd->mapname_, sd->bl_x, sd->bl_y, BeingRemoveWhy::QUIT);
+ }
+ }
+ AString output = STRPRINTF("All online characters of the %s party are near you."_fmt, p->name);
+ clif_displaymessage(s, output);
+ if (count)
+ {
+ output = STRPRINTF(
+ "Because you are not authorised to warp from some maps, %d player(s) have not been recalled."_fmt,
+ count);
+ clif_displaymessage(s, output);
}
}
- AString output = STRPRINTF("All online characters of the %s party are near you."_fmt, p->name);
- clif_displaymessage(s, output);
- if (count)
+ OMATCH_CASE_NONE ()
{
- output = STRPRINTF(
- "Because you are not authorised to warp from some maps, %d player(s) have not been recalled."_fmt,
- count);
- clif_displaymessage(s, output);
+ clif_displaymessage(s, "Incorrect name or ID, or no one from the party is online."_s);
+ return ATCE::EXIST;
}
}
- else
- {
- clif_displaymessage(s, "Incorrect name or ID, or no one from the party is online."_s);
- return ATCE::EXIST;
- }
+ OMATCH_END ();
return ATCE::OKAY;
}
@@ -3468,9 +3523,7 @@ ATCE atcommand_mapinfo(Session *s, dumb_ptr<map_session_data> sd,
if (!map_name)
map_name = sd->mapname_;
- map_local *m_id = map_mapname2mapid(map_name);
- if (m_id != nullptr)
- return ATCE::EXIST;
+ P<map_local> m_id = TRY_UNWRAP(map_mapname2mapid(map_name), return ATCE::EXIST);
clif_displaymessage(s, "------ Map Info ------"_s);
AString output = STRPRINTF("Map Name: %s"_fmt, map_name);
@@ -3592,29 +3645,34 @@ ATCE atcommand_partyspy(Session *s, dumb_ptr<map_session_data> sd,
if (!extract(message, &party_name))
return ATCE::USAGE;
- PartyPair p;
- if ((p = party_searchname(party_name)) ||
- // name first to avoid error when name begin with a number
- (p = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(message.c_str()))))))
+ // name first to avoid error when name begin with a number
+ Option<PartyPair> p_ = party_searchname(party_name);
+ if (p_.is_none())
+ p_ = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(message.c_str()))));
+ OMATCH_BEGIN (p_)
{
- if (sd->partyspy == p.party_id)
+ OMATCH_CASE_SOME (p)
{
- sd->partyspy = PartyId();
- AString output = STRPRINTF("No longer spying on the %s party."_fmt, p->name);
- clif_displaymessage(s, output);
+ if (sd->partyspy == p.party_id)
+ {
+ sd->partyspy = PartyId();
+ AString output = STRPRINTF("No longer spying on the %s party."_fmt, p->name);
+ clif_displaymessage(s, output);
+ }
+ else
+ {
+ sd->partyspy = p.party_id;
+ AString output = STRPRINTF("Spying on the %s party."_fmt, p->name);
+ clif_displaymessage(s, output);
+ }
}
- else
+ OMATCH_CASE_NONE ()
{
- sd->partyspy = p.party_id;
- AString output = STRPRINTF("Spying on the %s party."_fmt, p->name);
- clif_displaymessage(s, output);
+ clif_displaymessage(s, "Incorrect name or ID, or no one from the party is online."_s);
+ return ATCE::EXIST;
}
}
- else
- {
- clif_displaymessage(s, "Incorrect name or ID, or no one from the party is online."_s);
- return ATCE::EXIST;
- }
+ OMATCH_END ();
return ATCE::OKAY;
}
@@ -3684,19 +3742,15 @@ ATCE atcommand_chardelitem(Session *s, dumb_ptr<map_session_data> sd,
CharName character;
XString item_name;
int i, number = 0;
- ItemNameId item_id;
int count;
- struct item_data *item_data;
if (!asplit(message, &item_name, &number, &character) || number < 1)
return ATCE::USAGE;
- if ((item_data = itemdb_searchname(item_name)) != nullptr)
- item_id = item_data->nameid;
- else if (extract(item_name, &item_id) && (item_data = itemdb_exists(item_id)) != nullptr)
- item_id = item_data->nameid;
+ P<struct item_data> item_data = TRY_UNWRAP(extract_item_opt(item_name), return ATCE::EXIST);
+ ItemNameId item_id = item_data->nameid;
+ assert (item_id);
- if (item_id)
{
dumb_ptr<map_session_data> pl_sd = map_nick2sd(character);
if (pl_sd != nullptr)
@@ -3744,11 +3798,6 @@ ATCE atcommand_chardelitem(Session *s, dumb_ptr<map_session_data> sd,
return ATCE::EXIST;
}
}
- else
- {
- clif_displaymessage(s, "Invalid item ID or name."_s);
- return ATCE::RANGE;
- }
return ATCE::OKAY;
}
@@ -3855,7 +3904,6 @@ static
ATCE atcommand_character_item_list(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
- struct item_data *item_data = nullptr;
int count, counter;
CharName character;
@@ -3872,10 +3920,10 @@ ATCE atcommand_character_item_list(Session *s, dumb_ptr<map_session_data> sd,
count = 0;
for (IOff0 i : IOff0::iter())
{
- if (pl_sd->status.inventory[i].nameid
- && (item_data =
- itemdb_search(pl_sd->status.inventory[i].nameid)) !=
- nullptr)
+ if (!pl_sd->status.inventory[i].nameid)
+ continue;
+ P<struct item_data> item_data = TRY_UNWRAP(itemdb_exists(pl_sd->status.inventory[i].nameid), continue);
+
{
counter = counter + pl_sd->status.inventory[i].amount;
count++;
@@ -3966,8 +4014,6 @@ static
ATCE atcommand_character_storage_list(Session *s, dumb_ptr<map_session_data> sd,
ZString message)
{
- Storage *stor;
- struct item_data *item_data = nullptr;
int count, counter;
CharName character;
@@ -3980,50 +4026,56 @@ ATCE atcommand_character_storage_list(Session *s, dumb_ptr<map_session_data> sd,
if (pc_isGM(sd).overwhelms(pc_isGM(pl_sd)))
{
// you can look items only lower or same level
- if ((stor = account2storage2(pl_sd->status_key.account_id)) != nullptr)
+ Option<P<Storage>> stor_ = account2storage2(pl_sd->status_key.account_id);
+ OMATCH_BEGIN (stor_)
{
- counter = 0;
- count = 0;
- for (SOff0 i : SOff0::iter())
+ OMATCH_CASE_SOME (stor)
{
- if (stor->storage_[i].nameid
- && (item_data =
- itemdb_search(stor->storage_[i].nameid)) != nullptr)
+ counter = 0;
+ count = 0;
+ for (SOff0 i : SOff0::iter())
{
- counter = counter + stor->storage_[i].amount;
- count++;
- if (count == 1)
+ if (!stor->storage_[i].nameid)
+ continue;
+ P<struct item_data> item_data = TRY_UNWRAP(itemdb_exists(stor->storage_[i].nameid), continue);
+
{
- AString output = STRPRINTF(
- "------ Storage items list of '%s' ------"_fmt,
- pl_sd->status_key.name);
+ counter = counter + stor->storage_[i].amount;
+ count++;
+ if (count == 1)
+ {
+ AString output = STRPRINTF(
+ "------ Storage items list of '%s' ------"_fmt,
+ pl_sd->status_key.name);
+ clif_displaymessage(s, output);
+ }
+ AString output;
+ if (true)
+ output = STRPRINTF("%d %s (%s, id: %d)"_fmt,
+ stor->storage_[i].amount,
+ item_data->name, item_data->jname,
+ stor->storage_[i].nameid);
clif_displaymessage(s, output);
}
- AString output;
- if (true)
- output = STRPRINTF("%d %s (%s, id: %d)"_fmt,
- stor->storage_[i].amount,
- item_data->name, item_data->jname,
- stor->storage_[i].nameid);
+ }
+ if (count == 0)
+ clif_displaymessage(s,
+ "No item found in the storage of this player."_s);
+ else
+ {
+ AString output = STRPRINTF(
+ "%d item(s) found in %d kind(s) of items."_fmt,
+ counter, count);
clif_displaymessage(s, output);
}
}
- if (count == 0)
- clif_displaymessage(s,
- "No item found in the storage of this player."_s);
- else
+ OMATCH_CASE_NONE ()
{
- AString output = STRPRINTF(
- "%d item(s) found in %d kind(s) of items."_fmt,
- counter, count);
- clif_displaymessage(s, output);
+ clif_displaymessage(s, "This player has no storage."_s);
+ return ATCE::OKAY;
}
}
- else
- {
- clif_displaymessage(s, "This player has no storage."_s);
- return ATCE::OKAY;
- }
+ OMATCH_END ();
}
else
{
@@ -4041,81 +4093,40 @@ ATCE atcommand_character_storage_list(Session *s, dumb_ptr<map_session_data> sd,
}
static
-ATCE atcommand_killer(Session *s, dumb_ptr<map_session_data> sd,
+ATCE atcommand_pvp(Session *s, dumb_ptr<map_session_data> sd,
ZString)
{
- sd->special_state.killer = !sd->special_state.killer;
-
- if (sd->special_state.killer)
- clif_displaymessage(s, "You be a killa..."_s);
- else
- clif_displaymessage(s, "You gonna be own3d..."_s);
-
- return ATCE::OKAY;
-}
-
-static
-ATCE atcommand_charkiller(Session *s, dumb_ptr<map_session_data>,
- ZString message)
-{
- CharName character;
-
- if (!asplit(message, &character))
- return ATCE::USAGE;
-
- dumb_ptr<map_session_data> pl_sd = map_nick2sd(character);
- if (pl_sd == nullptr)
- return ATCE::EXIST;
-
- pl_sd->special_state.killer = !pl_sd->special_state.killer;
+ int chan = sd->state.pvpchannel;
+ if (sd->pvp_timer || (chan > 1))
+ return ATCE::OKAY;
- if (pl_sd->special_state.killer)
- {
- clif_displaymessage(s, "The player is now a killer"_s);
- clif_displaymessage(pl_sd->sess, "You are now a killer"_s);
- }
- else
- {
- clif_displaymessage(s, "The player is no longer a killer"_s);
- clif_displaymessage(pl_sd->sess, "You are no longer a killer"_s);
+ if (chan < 1) {
+ sd->state.pvpchannel = 1;
+ clif_displaymessage(s, "##3PvP : ##BOn"_s);
+ } else {
+ sd->state.pvpchannel = 0;
+ clif_displaymessage(s, "##3PvP : ##BOff"_s);
}
+ pc_setpvptimer(sd, battle_config.player_pvp_time);
return ATCE::OKAY;
}
static
-ATCE atcommand_killable(Session *s, dumb_ptr<map_session_data> sd,
- ZString)
-{
- sd->special_state.killable = !sd->special_state.killable;
-
- if (sd->special_state.killable)
- clif_displaymessage(s, "You gonna be own3d..."_s);
- else
- clif_displaymessage(s, "You be a killa..."_s);
-
- return ATCE::OKAY;
-}
-
-static
-ATCE atcommand_charkillable(Session *s, dumb_ptr<map_session_data>,
+ATCE atcommand_charpvp(Session *, dumb_ptr<map_session_data>,
ZString message)
{
CharName character;
+ int channel;
- if (!asplit(message, &character))
+ if (!extract(message, record<' '>(&character, &channel)))
return ATCE::USAGE;
dumb_ptr<map_session_data> pl_sd = map_nick2sd(character);
if (pl_sd == nullptr)
return ATCE::EXIST;
- pl_sd->special_state.killable = !pl_sd->special_state.killable;
-
- if (pl_sd->special_state.killable)
- clif_displaymessage(s, "The player is now killable"_s);
- else
- clif_displaymessage(s, "The player is no longer killable"_s);
+ pl_sd->state.pvpchannel = channel;
return ATCE::OKAY;
}
@@ -4155,13 +4166,21 @@ ATCE atcommand_addwarp(Session *s, dumb_ptr<map_session_data> sd,
if (!extract(message, record<' '>(&mapname, &x, &y)))
return ATCE::USAGE;
- AString w1 = STRPRINTF("%s,%d,%d"_fmt, sd->mapname_, sd->bl_x, sd->bl_y);
AString w3 = STRPRINTF("%s%d%d%d%d"_fmt, mapname, sd->bl_x, sd->bl_y, x, y);
- AString w4 = STRPRINTF("1,1,%s.gat,%d,%d"_fmt, mapname, x, y);
-
NpcName w3name = stringish<NpcName>(w3);
- int ret = npc_parse_warp(w1, "warp"_s, w3name, w4);
- if (ret)
+
+ ast::npc::Warp warp;
+ warp.m.data = sd->mapname_;
+ warp.x.data = sd->bl_x;
+ warp.y.data = sd->bl_y;
+ warp.name.data = w3name;
+ warp.xs.data = 1+2;
+ warp.ys.data = 1+2;
+ warp.to_m.data = mapname;
+ warp.to_x.data = x;
+ warp.to_y.data = y;
+
+ if (!npc_load_warp(warp))
// warp failed
return ATCE::RANGE;
@@ -4433,14 +4452,15 @@ ATCE atcommand_adjcmdlvl(Session *s, dumb_ptr<map_session_data>,
return ATCE::USAGE;
}
- AtCommandInfo *it = atcommand_info.search(cmd);
+ Option<P<AtCommandInfo>> it_ = atcommand_info.search(cmd);
{
- if (it)
+ OMATCH_BEGIN_SOME (it, it_)
{
it->level = newlev;
clif_displaymessage(s, "@command level changed."_s);
return ATCE::OKAY;
}
+ OMATCH_END ();
}
clif_displaymessage(s, "@command not found."_s);
@@ -4665,15 +4685,15 @@ ATCE atcommand_jump_iterate(Session *s, dumb_ptr<map_session_data> sd,
pl_sd = get_start();
}
- if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARPTO)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (pl_sd->bl_m->flag.get(MapFlag::NOWARPTO)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you to the map of this player."_s);
return ATCE::PERM;
}
- if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP)
- && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))))
+ if (sd->bl_m->flag.get(MapFlag::NOWARP)
+ && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)))
{
clif_displaymessage(s,
"You are not authorised to warp you from your actual map."_s);
@@ -4744,11 +4764,11 @@ ATCE atcommand_skillpool_info(Session *s, dumb_ptr<map_session_data>,
clif_displaymessage(s, buf);
}
- buf = STRPRINTF("Learned skills out of %d for %s:"_fmt,
- skill_pool_skills_size, character);
+ buf = STRPRINTF("Learned skills out of %zu for %s:"_fmt,
+ skill_pool_skills.size(), character);
clif_displaymessage(s, buf);
- for (i = 0; i < skill_pool_skills_size; ++i)
+ for (i = 0; i < skill_pool_skills.size(); ++i)
{
const RString& name = skill_name(skill_pool_skills[i]);
int lvl = pl_sd->status.skill[skill_pool_skills[i]].lv;
@@ -4919,14 +4939,10 @@ static
ATCE atcommand_source(Session *s, dumb_ptr<map_session_data>,
ZString)
{
- clif_displaymessage(s,
- "This server code consists of Free Software under GPL3&AGPL3"_s);
- clif_displaymessage(s,
- "This is commit " VERSION_HASH ", also known as " VERSION_FULL ""_s);
- clif_displaymessage(s,
- "The version is " VERSION_STRING ""_s);
- clif_displaymessage(s,
- "For source, see " VENDOR_SOURCE ""_s);
+ clif_displaymessage(s, VERSION_INFO_HEADER);
+ clif_displaymessage(s, VERSION_INFO_COMMIT);
+ clif_displaymessage(s, VERSION_INFO_NUMBER);
+ clif_displaymessage(s, VERSION_INFO_URL);
return ATCE::OKAY;
}
@@ -4954,6 +4970,9 @@ Map<XString, AtCommandInfo> atcommand_info =
{"goto"_s, {"<charname>"_s,
40, atcommand_goto,
"Warp yourself to another character"_s}},
+ {"npc"_s, {"<npc>"_s,
+ 40, atcommand_npc,
+ "Warp yourself to a npc"_s}},
{"jump"_s, {"[x] [y]"_s,
40, atcommand_jump,
"Warp yourself within a map"_s}},
@@ -5023,12 +5042,12 @@ Map<XString, AtCommandInfo> atcommand_info =
{"jlvl"_s, {"<delta>"_s,
60, atcommand_joblevelup,
"Adjust your job level"_s}},
- {"gm"_s, {"<password>"_s,
- 100, atcommand_gm,
- "Receive GM powers"_s}},
{"pvpoff"_s, {""_s,
60, atcommand_pvpoff,
"Enable PvP on your map"_s}},
+ {"exprate"_s, {"<percent>"_s,
+ 60, atcommand_exprate,
+ "Set base job/exp rate"_s}},
{"pvpon"_s, {""_s,
60, atcommand_pvpon,
"Disable PvP on your map"_s}},
@@ -5242,21 +5261,15 @@ Map<XString, AtCommandInfo> atcommand_info =
{"addwarp"_s, {"<mapname> <x> <y>"_s,
80, atcommand_addwarp,
"Create a new permanent warp"_s}},
- {"killer"_s, {""_s,
- 60, atcommand_killer,
- "Toggle whether you are a killer"_s}},
- {"charkiller"_s, {"<charname>"_s,
- 60, atcommand_charkiller,
- "Toggle whether a player is a killer"_s}},
+ {"pvp"_s, {""_s,
+ 0, atcommand_pvp,
+ "Toggle your pvp flag"_s}},
{"npcmove"_s, {"<x> <y> <npc-name>"_s,
80, atcommand_npcmove,
"Force an NPC to move on the map"_s}},
- {"killable"_s, {""_s,
- 60, atcommand_killable,
- "Toggle whether you are killable"_s}},
- {"charkillable"_s, {"<charname>"_s,
- 60, atcommand_charkillable,
- "Toggle whether a player is killable"_s}},
+ {"charpvp"_s, {"<charname> <channel>"_s,
+ 40, atcommand_charpvp,
+ "Set the pvp channel of another player"_s}},
{"chareffect"_s, {"<type> <target>"_s,
40, atcommand_chareffect,
"Apply effect type with arg 0 to a player"_s}},
@@ -5357,4 +5370,5 @@ Map<XString, AtCommandInfo> atcommand_info =
0, atcommand_source,
"Legal information about source code (must be a level 0 command!)"_s}},
};
+} // namespace map
} // namespace tmwa
diff --git a/src/map/atcommand.hpp b/src/map/atcommand.hpp
index 4bf5277..4c0e421 100644
--- a/src/map/atcommand.hpp
+++ b/src/map/atcommand.hpp
@@ -22,17 +22,11 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
-
-#include "../net/fwd.hpp"
-
-#include "../mmo/fwd.hpp"
-
namespace tmwa
{
+namespace map
+{
bool is_atcommand(Session *s, dumb_ptr<map_session_data> sd,
ZString message, GmLevel gmlvl);
@@ -40,8 +34,6 @@ bool atcommand_config_read(ZString cfgName);
void log_atcommand(dumb_ptr<map_session_data> sd, ZString cmd);
-// only used by map.cpp
-extern AString gm_log;
-
void atcommand_config_write(ZString cfgName);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/battle.cpp b/src/map/battle.cpp
index 856408c..5b63acc 100644
--- a/src/map/battle.cpp
+++ b/src/map/battle.cpp
@@ -32,12 +32,17 @@
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
#include "../io/read.hpp"
+#include "../io/span.hpp"
#include "../mmo/config_parse.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
+#include "../high/utils.hpp"
+
+#include "battle_conf.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "itemdb.hpp"
#include "map.hpp"
#include "mob.hpp"
@@ -50,13 +55,8 @@
namespace tmwa
{
-static Battle_Config init_battle_config();
-
-DIAG_PUSH();
-DIAG_I(shadow);
-struct Battle_Config battle_config = init_battle_config();
-DIAG_POP();
-
+namespace map
+{
/*==========================================
* 自分をロックしている対象の数を返す(汎用)
* 戻りは整数で0以上
@@ -482,21 +482,6 @@ int battle_get_atk(dumb_ptr<block_list> bl)
}
/*==========================================
- * 対象の左手Atkを返す(汎用)
- * 戻りは整数で0以上
- *------------------------------------------
- */
-static
-int battle_get_atk_(dumb_ptr<block_list> bl)
-{
- nullpo_retz(bl);
- if (bl->bl_type == BL::PC)
- return bl->is_player()->watk_;
- else
- return 0;
-}
-
-/*==========================================
* 対象のAtk2を返す(汎用)
* 戻りは整数で0以上
*------------------------------------------
@@ -520,21 +505,6 @@ int battle_get_atk2(dumb_ptr<block_list> bl)
}
/*==========================================
- * 対象の左手Atk2を返す(汎用)
- * 戻りは整数で0以上
- *------------------------------------------
- */
-static
-int battle_get_atk_2(dumb_ptr<block_list> bl)
-{
- nullpo_retz(bl);
- if (bl->bl_type == BL::PC)
- return bl->is_player()->watk_2;
- else
- return 0;
-}
-
-/*==========================================
* 対象のMAtk1を返す(汎用)
* 戻りは整数で0以上
*------------------------------------------
@@ -756,7 +726,7 @@ interval_t battle_get_adelay(dumb_ptr<block_list> bl)
if (aspd_rate != 100)
adelay = adelay * aspd_rate / 100;
- return std::max(adelay, static_cast<interval_t>(battle_config.monster_max_aspd) * 2);
+ return std::max(adelay, battle_config.monster_max_aspd * 2);
}
}
@@ -771,7 +741,7 @@ interval_t battle_get_amotion(dumb_ptr<block_list> bl)
interval_t amotion = 2_s;
int aspd_rate = 100;
if (bl->bl_type == BL::MOB)
- amotion = static_cast<interval_t>(get_mob_db(bl->is_mob()->mob_class).amotion);
+ amotion = get_mob_db(bl->is_mob()->mob_class).amotion;
if (sc_data)
{
@@ -783,7 +753,7 @@ interval_t battle_get_amotion(dumb_ptr<block_list> bl)
if (aspd_rate != 100)
amotion = amotion * aspd_rate / 100;
- return std::max(amotion, static_cast<interval_t>(battle_config.monster_max_aspd));
+ return std::max(amotion, battle_config.monster_max_aspd);
}
}
@@ -792,7 +762,7 @@ interval_t battle_get_dmotion(dumb_ptr<block_list> bl)
nullpo_retr(interval_t::zero(), bl);
if (bl->bl_type == BL::MOB)
{
- return static_cast<interval_t>(get_mob_db(bl->is_mob()->mob_class).dmotion);
+ return get_mob_db(bl->is_mob()->mob_class).dmotion;
}
else if (bl->bl_type == BL::PC)
{
@@ -884,16 +854,6 @@ eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> battle_
return nullptr;
}
-short *battle_get_sc_count(dumb_ptr<block_list> bl)
-{
- nullpo_retr(nullptr, bl);
- if (bl->bl_type == BL::MOB)
- return &bl->is_mob()->sc_count;
- else if (bl->bl_type == BL::PC)
- return &bl->is_player()->sc_count;
- return nullptr;
-}
-
Opt1 *battle_get_opt1(dumb_ptr<block_list> bl)
{
nullpo_retn(bl);
@@ -930,7 +890,7 @@ Opt3 *battle_get_opt3(dumb_ptr<block_list> bl)
return nullptr;
}
-Option *battle_get_option(dumb_ptr<block_list> bl)
+Opt0 *battle_get_option(dumb_ptr<block_list> bl)
{
nullpo_retn(bl);
if (bl->bl_type == BL::MOB)
@@ -1023,17 +983,6 @@ int battle_stopattack(dumb_ptr<block_list> bl)
return 0;
}
-// 移動停止
-int battle_stopwalking(dumb_ptr<block_list> bl, int type)
-{
- nullpo_retz(bl);
- if (bl->bl_type == BL::MOB)
- return mob_stop_walking(bl->is_mob(), type);
- else if (bl->bl_type == BL::PC)
- return pc_stop_walking(bl->is_player(), type);
- return 0;
-}
-
/*==========================================
* ダメージ最終計算
*------------------------------------------
@@ -1082,7 +1031,7 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src,
int def2 = battle_get_def2(target);
int t_vit = battle_get_vit(target);
struct Damage wd {};
- int damage, damage2 = 0;
+ int damage;
DamageType type;
int div_;
BF flag;
@@ -1112,7 +1061,7 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src,
|| battle_config.vit_penaly_type > 0)
target_count +=
battle_counttargeted(target, src,
- ATK(battle_config.agi_penaly_count_lv)); // FIXME
+ battle_config.agi_penaly_count_lv);
if (battle_config.agi_penaly_type > 0)
{
if (target_count >= battle_config.agi_penaly_count)
@@ -1198,7 +1147,7 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src,
int t_def;
target_count =
1 + battle_counttargeted(target, src,
- ATK(battle_config.vit_penaly_count_lv)); // FIXME
+ battle_config.vit_penaly_count_lv);
if (battle_config.vit_penaly_type > 0)
{
if (target_count >= battle_config.vit_penaly_count)
@@ -1273,7 +1222,7 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src,
if (type == DamageType::NORMAL && !random_::chance({hitrate, 100}))
{
- damage = damage2 = 0;
+ damage = 0;
dmg_lv = ATK::FLEE;
}
else
@@ -1312,7 +1261,6 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src,
skill_num, skill_lv, flag);
wd.damage = damage;
- wd.damage2 = 0;
wd.type = type;
wd.div_ = div_;
wd.amotion = battle_get_amotion(src);
@@ -1357,14 +1305,13 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
int def2 = battle_get_def2(target);
int t_vit = battle_get_vit(target);
struct Damage wd {};
- int damage, damage2;
+ int damage;
DamageType type;
int div_;
BF flag;
ATK dmg_lv = ATK::ZERO;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data, t_sc_data;
- int atkmax_ = 0, atkmin_ = 0; //二刀流用
- int watk, watk_;
+ int watk;
bool da = false;
int ac_flag = 0;
int target_distance;
@@ -1392,7 +1339,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
flee = battle_get_flee(target);
if (battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) //AGI、VITペナルティ設定が有効
target_count += battle_counttargeted(target, src,
- ATK(battle_config.agi_penaly_count_lv)); //対象の数を算出
+ battle_config.agi_penaly_count_lv); //対象の数を算出
if (battle_config.agi_penaly_type > 0)
{
if (target_count >= battle_config.agi_penaly_count)
@@ -1428,13 +1375,12 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
dex = battle_get_dex(src); //DEX
watk = battle_get_atk(src); //ATK
- watk_ = battle_get_atk_(src); //ATK左手
type = DamageType::NORMAL;
div_ = 1; // single attack
{
- damage = damage2 = battle_get_baseatk(sd); //damega,damega2初登場、base_atkの取得
+ damage = battle_get_baseatk(sd); //damega,damega2初登場、base_atkの取得
}
if (sd->attackrange > 2)
{ // [fate] ranged weapon?
@@ -1443,22 +1389,21 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
damage * (256 +
((range_damage_bonus * target_distance) /
sd->attackrange)) >> 8;
- damage2 =
- damage2 * (256 +
- ((range_damage_bonus * target_distance) /
- sd->attackrange)) >> 8;
}
- atkmin = atkmin_ = dex; //最低ATKはDEXで初期化?
+ atkmin = dex; //最低ATKはDEXで初期化?
sd->state.arrow_atk = 0; //arrow_atk初期化
IOff0 widx = sd->equip_index_maybe[EQUIP::WEAPON];
- IOff0 sidx = sd->equip_index_maybe[EQUIP::SHIELD];
- if (widx.ok() && sd->inventory_data[widx])
- atkmin = atkmin * (80 + sd->inventory_data[widx]->wlv * 20) / 100;
- if (sidx.ok() && sd->inventory_data[sidx])
- atkmin_ = atkmin_ * (80 + sd->inventory_data[sidx]->wlv * 20) / 100;
+ if (widx.ok())
+ {
+ OMATCH_BEGIN_SOME (sdidw, sd->inventory_data[widx])
+ {
+ atkmin = atkmin * (80 + sdidw->wlv * 20) / 100;
+ }
+ OMATCH_END ();
+ }
if (sd->status.weapon == ItemLook::BOW)
{ //武器が弓矢の場合
atkmin = watk * ((atkmin < watk) ? atkmin : watk) / 100; //弓用最低ATK計算
@@ -1468,13 +1413,10 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
{
atkmax = watk;
- atkmax_ = watk_;
}
if (atkmin > atkmax && !(sd->state.arrow_atk))
atkmin = atkmax; //弓は最低が上回る場合あり
- if (atkmin_ > atkmax_)
- atkmin_ = atkmax_;
if (sd->double_rate > 0 && skill_num == SkillID::ZERO && skill_lv >= 0)
da = random_::chance({sd->double_rate, 100});
@@ -1486,9 +1428,6 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
if (sd->state.arrow_atk)
cri += sd->arrow_cri;
- if (sd->status.weapon == ItemLook::_16)
- // カタールの場合、クリティカルを倍に
- cri <<= 1;
cri -= battle_get_luk(target) * 3;
if (ac_flag)
cri = 1000;
@@ -1503,11 +1442,9 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
&& random_::chance({cri, 1000}))
{
damage += atkmax;
- damage2 += atkmax_;
if (sd->atk_rate != 100)
{
damage = (damage * sd->atk_rate) / 100;
- damage2 = (damage2 * sd->atk_rate) / 100;
}
if (sd->state.arrow_atk)
damage += sd->arrow_atk;
@@ -1521,14 +1458,9 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
damage += random_::in(atkmin, atkmax);
else
damage += atkmin;
- if (atkmax_ > atkmin_)
- damage2 += random_::in(atkmin_, atkmax_);
- else
- damage2 += atkmin_;
if (sd->atk_rate != 100)
{
damage = (damage * sd->atk_rate) / 100;
- damage2 = (damage2 * sd->atk_rate) / 100;
}
if (sd->state.arrow_atk)
@@ -1551,7 +1483,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
int t_def;
target_count =
1 + battle_counttargeted(target, src,
- ATK(battle_config.vit_penaly_count_lv)); // FIXME
+ battle_config.vit_penaly_count_lv);
if (battle_config.vit_penaly_type > 0)
{
if (target_count >= battle_config.vit_penaly_count)
@@ -1614,28 +1546,17 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
damage -= random_::in(0, vitbonusmax);
}
}
- {
- {
- damage2 = damage2 * (100 - def1) / 100;
- damage2 -= t_def;
- if (vitbonusmax > 0)
- damage2 -= random_::in(0, vitbonusmax);
- }
- }
}
}
}
// 精錬ダメージの追加
{ //DEF, VIT無視
damage += battle_get_atk2(src);
- damage2 += battle_get_atk_2(src);
}
// 0未満だった場合1に補正
if (damage < 1)
damage = 1;
- if (damage2 < 1)
- damage2 = 1;
// スキル修正2(修練系)
// 修練ダメージ(右手のみ) ソニックブロー時は別処理(1撃に付き1/8適応)
@@ -1652,7 +1573,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
hitrate = (hitrate < 5) ? 5 : hitrate;
if (type == DamageType::NORMAL && !random_::chance({hitrate, 100}))
{
- damage = damage2 = 0;
+ damage = 0;
dmg_lv = ATK::FLEE;
}
else
@@ -1662,37 +1583,6 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
if (damage < 0)
damage = 0;
- if (damage2 < 0)
- damage2 = 0;
-
- // >二刀流の左右ダメージ計算誰かやってくれぇぇぇぇえええ!
- // >map_session_data に左手ダメージ(atk,atk2)追加して
- // >pc_calcstatus()でやるべきかな?
- // map_session_data に左手武器(atk,atk2,ele,star,atkmods)追加して
- // pc_calcstatus()でデータを入力しています
-
- //左手のみ武器装備
- if (sd->weapontype1 == ItemLook::NONE
- && sd->weapontype2 != ItemLook::NONE)
- {
- damage = damage2;
- damage2 = 0;
- }
- // 右手、左手修練の適用
- if (sd->status.weapon >= ItemLook::SINGLE_HANDED_COUNT)
- { // 二刀流か?
- int dmg = damage, dmg2 = damage2;
- // 右手修練(60% 〜 100%) 右手全般
- damage = damage * 50 / 100;
- if (dmg > 0 && damage < 1)
- damage = 1;
- // 左手修練(40% 〜 80%) 左手全般
- damage2 = damage2 * 30 / 100;
- if (dmg2 > 0 && damage2 < 1)
- damage2 = 1;
- }
- else //二刀流でなければ左手ダメージは0
- damage2 = 0;
// 右手,短剣のみ
if (da)
@@ -1702,19 +1592,11 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
type = DamageType::DOUBLED;
}
- if (sd->status.weapon == ItemLook::_16)
- {
- // カタール追撃ダメージ
- damage2 = damage * 1 / 100;
- if (damage > 0 && damage2 < 1)
- damage2 = 1;
- }
-
// 完全回避の判定
if (skill_num == SkillID::ZERO && skill_lv >= 0 && tsd != nullptr && div_ < 255
&& random_::chance({battle_get_flee2(target), 1000}))
{
- damage = damage2 = 0;
+ damage = 0;
type = DamageType::FLEE2;
dmg_lv = ATK::LUCKY;
}
@@ -1725,7 +1607,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
if (skill_num == SkillID::ZERO && skill_lv >= 0 && tmd != nullptr && div_ < 255
&& random_::chance({battle_get_flee2(target), 1000}))
{
- damage = damage2 = 0;
+ damage = 0;
type = DamageType::FLEE2;
dmg_lv = ATK::LUCKY;
}
@@ -1736,35 +1618,18 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src,
{
if (damage > 0)
damage = 1;
- if (damage2 > 0)
- damage2 = 1;
}
- if (damage > 0 || damage2 > 0)
+ if (damage > 0)
{
- if (damage2 < 1) // ダメージ最終修正
+ {
damage =
battle_calc_damage(src, target, damage, div_, skill_num,
skill_lv, flag);
- else if (damage < 1) // 右手がミス?
- damage2 =
- battle_calc_damage(src, target, damage2, div_, skill_num,
- skill_lv, flag);
- else
- { // 両 手/カタールの場合はちょっと計算ややこしい
- int d1 = damage + damage2, d2 = damage2;
- damage =
- battle_calc_damage(src, target, damage + damage2, div_,
- skill_num, skill_lv, flag);
- damage2 = (d2 * 100 / d1) * damage / 100;
- if (damage > 1 && damage2 < 1)
- damage2 = 1;
- damage -= damage2;
}
}
wd.damage = damage;
- wd.damage2 = damage2;
wd.type = type;
wd.div_ = div_;
wd.amotion = battle_get_amotion(src);
@@ -1872,7 +1737,6 @@ struct Damage battle_calc_magic_attack(dumb_ptr<block_list> bl,
md.div_ = div_;
md.amotion = battle_get_amotion(bl);
md.dmotion = battle_get_dmotion(target);
- md.damage2 = 0;
md.type = DamageType::NORMAL;
md.flag = aflag;
@@ -1937,7 +1801,6 @@ struct Damage battle_calc_misc_attack(dumb_ptr<block_list> bl,
md.div_ = div_;
md.amotion = battle_get_amotion(bl);
md.dmotion = battle_get_dmotion(target);
- md.damage2 = 0;
md.type = DamageType::NORMAL;
md.flag = aflag;
return md;
@@ -2048,13 +1911,7 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
{
clif_damage(src, target, tick, wd.amotion, wd.dmotion,
- wd.damage, wd.div_, wd.type, wd.damage2);
- if (sd
- && (sd->status.weapon == ItemLook::_16
- || sd->status.weapon >= ItemLook::SINGLE_HANDED_COUNT)
- && wd.damage2 == 0)
- clif_damage(src, target, tick + 10_ms,
- wd.amotion, wd.dmotion, 0, 1, DamageType::NORMAL, 0);
+ wd.damage, wd.div_, wd.type);
}
MapBlockLock lock;
@@ -2063,9 +1920,17 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
{
IOff0 weapon_index = sd->equip_index_maybe[EQUIP::WEAPON];
ItemNameId weapon;
- if (weapon_index.ok() && sd->inventory_data[weapon_index]
- && bool(sd->status.inventory[weapon_index].equip & EPOS::WEAPON))
- weapon = sd->inventory_data[weapon_index]->nameid;
+ if (weapon_index.ok())
+ {
+ OMATCH_BEGIN_SOME (sdidw, sd->inventory_data[weapon_index])
+ {
+ if (bool(sd->status.inventory[weapon_index].equip & EPOS::WEAPON))
+ {
+ weapon = sdidw->nameid;
+ }
+ }
+ OMATCH_END ();
+ }
MAP_LOG("PC%d %s:%d,%d WPNDMG %s%d %d FOR %d WPN %d"_fmt,
sd->status_key.char_id, src->bl_m->name_, src->bl_x, src->bl_y,
@@ -2074,7 +1939,7 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
? unwrap<CharId>(target->is_player()->status_key.char_id)
: unwrap<BlockId>(target->bl_id),
battle_get_class(target),
- wd.damage + wd.damage2, weapon);
+ wd.damage, weapon);
}
if (target->bl_type == BL::PC)
@@ -2087,16 +1952,16 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
? unwrap<CharId>(src->is_player()->status_key.char_id)
: unwrap<BlockId>(src->bl_id),
battle_get_class(src),
- wd.damage + wd.damage2);
+ wd.damage);
}
- battle_damage(src, target, (wd.damage + wd.damage2), 0);
+ battle_damage(src, target, (wd.damage), 0);
if (target->bl_prev != nullptr &&
(target->bl_type != BL::PC
|| (target->bl_type == BL::PC
&& !pc_isdead(target->is_player()))))
{
- if (wd.damage > 0 || wd.damage2 > 0)
+ if (wd.damage > 0)
{
skill_additional_effect(src, target, SkillID::ZERO, 0);
}
@@ -2105,7 +1970,7 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
{
if (bool(wd.flag & BF::WEAPON)
&& src != target
- && (wd.damage > 0 || wd.damage2 > 0))
+ && (wd.damage > 0))
{
int hp = 0, sp = 0;
if (sd->hp_drain_rate && wd.damage > 0
@@ -2113,21 +1978,11 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
{
hp += (wd.damage * sd->hp_drain_per) / 100;
}
- if (sd->hp_drain_rate_ && wd.damage2 > 0
- && random_::chance({sd->hp_drain_rate_, 100}))
- {
- hp += (wd.damage2 * sd->hp_drain_per_) / 100;
- }
if (sd->sp_drain_rate && wd.damage > 0
&& random_::chance({sd->sp_drain_rate, 100}))
{
sp += (wd.damage * sd->sp_drain_per) / 100;
}
- if (sd->sp_drain_rate_ && wd.damage2 > 0
- && random_::chance({sd->sp_drain_rate_, 100}))
- {
- sp += (wd.damage2 * sd->sp_drain_per_) / 100;
- }
if (hp || sp)
pc_heal(sd, hp, sp);
}
@@ -2310,380 +2165,5 @@ int battle_check_range(dumb_ptr<block_list> src, dumb_ptr<block_list> bl,
return (path_search(&wpd, src->bl_m, src->bl_x + dx, src->bl_y + dy,
bl->bl_x - dx, bl->bl_y - dy, 0x10001) != -1) ? 1 : 0;
}
-
-Battle_Config init_battle_config()
-{
- DIAG_PUSH();
- DIAG_I(shadow);
- Battle_Config battle_config;
- DIAG_POP();
- {
- battle_config.warp_point_debug = 0;
- battle_config.enemy_critical = 0;
- battle_config.enemy_critical_rate = 100;
- battle_config.enemy_str = 1;
- battle_config.enemy_perfect_flee = 0;
- battle_config.casting_rate = 100;
- battle_config.delay_rate = 100;
- battle_config.delay_dependon_dex = 0;
- battle_config.skill_delay_attack_enable = 0;
- battle_config.monster_skill_add_range = 0;
- battle_config.player_damage_delay = 1;
- battle_config.flooritem_lifetime = std::chrono::duration_cast<std::chrono::milliseconds>(LIFETIME_FLOORITEM).count();
- battle_config.item_auto_get = 0;
- battle_config.drop_pickup_safety_zone = 20;
- battle_config.item_first_get_time = 3000;
- battle_config.item_second_get_time = 1000;
- battle_config.item_third_get_time = 1000;
-
- battle_config.base_exp_rate = 100;
- battle_config.job_exp_rate = 100;
- battle_config.death_penalty_type = 0;
- battle_config.death_penalty_base = 0;
- battle_config.death_penalty_job = 0;
- battle_config.restart_hp_rate = 0;
- battle_config.restart_sp_rate = 0;
- battle_config.monster_hp_rate = 100;
- battle_config.monster_max_aspd = 199;
- battle_config.atcommand_gm_only = 0;
- battle_config.gm_all_equipment = 0;
- battle_config.monster_active_enable = 1;
- battle_config.mob_skill_use = 1;
- battle_config.mob_count_rate = 100;
- battle_config.basic_skill_check = 1;
- battle_config.player_invincible_time = 5000;
- battle_config.skill_min_damage = 0;
- battle_config.natural_healhp_interval = 6000;
- battle_config.natural_healsp_interval = 8000;
- battle_config.natural_heal_skill_interval = 10000;
- battle_config.natural_heal_weight_rate = 50;
- battle_config.itemheal_regeneration_factor = 1;
- battle_config.arrow_decrement = 1;
- battle_config.max_aspd = 199;
- battle_config.max_hp = 32500;
- battle_config.max_sp = 32500;
- battle_config.max_lv = 99; // [MouseJstr]
- battle_config.max_parameter = 99;
- battle_config.monster_skill_log = 0;
- battle_config.battle_log = 0;
- battle_config.save_log = 0;
- battle_config.error_log = 1;
- battle_config.etc_log = 1;
- battle_config.save_clothcolor = 0;
- battle_config.undead_detect_type = 0;
- battle_config.agi_penaly_type = 0;
- battle_config.agi_penaly_count = 3;
- battle_config.agi_penaly_num = 0;
- battle_config.agi_penaly_count_lv = static_cast<int>(ATK::FLEE); // FIXME
- battle_config.vit_penaly_type = 0;
- battle_config.vit_penaly_count = 3;
- battle_config.vit_penaly_num = 0;
- battle_config.vit_penaly_count_lv = static_cast<int>(ATK::DEF); // FIXME
- battle_config.mob_changetarget_byskill = 0;
- battle_config.player_attack_direction_change = 1;
- battle_config.monster_attack_direction_change = 1;
- battle_config.display_delay_skill_fail = 1;
- battle_config.dead_branch_active = 0;
- battle_config.show_steal_in_same_party = 0;
- battle_config.hide_GM_session = 0;
- battle_config.invite_request_check = 1;
- battle_config.disp_experience = 0;
- battle_config.prevent_logout = 1; // Added by RoVeRT
- battle_config.maximum_level = 255; // Added by Valaris
- battle_config.drops_by_luk = 0; // [Valaris]
- battle_config.pk_mode = 0; // [Valaris]
- battle_config.multi_level_up = 0; // [Valaris]
- battle_config.hack_info_GM_level = 60; // added by [Yor] (default: 60, GM level)
- battle_config.any_warp_GM_min_level = 20; // added by [Yor]
- battle_config.min_hair_style = 0;
- battle_config.max_hair_style = 20;
- battle_config.min_hair_color = 0;
- battle_config.max_hair_color = 9;
- battle_config.min_cloth_color = 0;
- battle_config.max_cloth_color = 4;
-
- battle_config.castrate_dex_scale = 150;
-
- battle_config.area_size = 14;
-
- battle_config.chat_lame_penalty = 2;
- battle_config.chat_spam_threshold = 10;
- battle_config.chat_spam_flood = 10;
- battle_config.chat_spam_ban = 1;
- battle_config.chat_spam_warn = 8;
- battle_config.chat_maxline = 255;
-
- battle_config.packet_spam_threshold = 2;
- battle_config.packet_spam_flood = 30;
- battle_config.packet_spam_kick = 1;
-
- battle_config.mask_ip_gms = 1;
-
- battle_config.mob_splash_radius = -1;
- }
- return battle_config;
-}
-
-bool battle_config_read(ZString cfgName)
-{
- bool rv = true;
- io::ReadFile in(cfgName);
- if (!in.is_open())
- {
- PRINTF("file not found: %s\n"_fmt, cfgName);
- return false;
- }
-
- AString line;
- while (in.getline(line))
- {
-#define BATTLE_CONFIG_VAR(name) {#name##_s, &battle_config.name}
- const struct
- {
- LString str;
- int *val;
- } data[] =
- {
- BATTLE_CONFIG_VAR(warp_point_debug),
- BATTLE_CONFIG_VAR(enemy_critical),
- BATTLE_CONFIG_VAR(enemy_critical_rate),
- BATTLE_CONFIG_VAR(enemy_str),
- BATTLE_CONFIG_VAR(enemy_perfect_flee),
- BATTLE_CONFIG_VAR(casting_rate),
- BATTLE_CONFIG_VAR(delay_rate),
- BATTLE_CONFIG_VAR(delay_dependon_dex),
- BATTLE_CONFIG_VAR(skill_delay_attack_enable),
- BATTLE_CONFIG_VAR(monster_skill_add_range),
- BATTLE_CONFIG_VAR(player_damage_delay),
- BATTLE_CONFIG_VAR(flooritem_lifetime),
- BATTLE_CONFIG_VAR(item_auto_get),
- BATTLE_CONFIG_VAR(drop_pickup_safety_zone),
- BATTLE_CONFIG_VAR(item_first_get_time),
- BATTLE_CONFIG_VAR(item_second_get_time),
- BATTLE_CONFIG_VAR(item_third_get_time),
- BATTLE_CONFIG_VAR(base_exp_rate),
- BATTLE_CONFIG_VAR(job_exp_rate),
- BATTLE_CONFIG_VAR(death_penalty_type),
- BATTLE_CONFIG_VAR(death_penalty_base),
- BATTLE_CONFIG_VAR(death_penalty_job),
- BATTLE_CONFIG_VAR(restart_hp_rate),
- BATTLE_CONFIG_VAR(restart_sp_rate),
- BATTLE_CONFIG_VAR(monster_hp_rate),
- BATTLE_CONFIG_VAR(monster_max_aspd),
- BATTLE_CONFIG_VAR(atcommand_gm_only),
- BATTLE_CONFIG_VAR(atcommand_spawn_quantity_limit),
- BATTLE_CONFIG_VAR(gm_all_equipment),
- BATTLE_CONFIG_VAR(monster_active_enable),
- BATTLE_CONFIG_VAR(mob_skill_use),
- BATTLE_CONFIG_VAR(mob_count_rate),
- BATTLE_CONFIG_VAR(basic_skill_check),
- BATTLE_CONFIG_VAR(player_invincible_time),
- BATTLE_CONFIG_VAR(skill_min_damage),
- BATTLE_CONFIG_VAR(natural_healhp_interval),
- BATTLE_CONFIG_VAR(natural_healsp_interval),
- BATTLE_CONFIG_VAR(natural_heal_skill_interval),
- BATTLE_CONFIG_VAR(natural_heal_weight_rate),
- BATTLE_CONFIG_VAR(itemheal_regeneration_factor),
- BATTLE_CONFIG_VAR(arrow_decrement),
- BATTLE_CONFIG_VAR(max_aspd),
- BATTLE_CONFIG_VAR(max_hp),
- BATTLE_CONFIG_VAR(max_sp),
- BATTLE_CONFIG_VAR(max_lv),
- BATTLE_CONFIG_VAR(max_parameter),
- BATTLE_CONFIG_VAR(monster_skill_log),
- BATTLE_CONFIG_VAR(battle_log),
- BATTLE_CONFIG_VAR(save_log),
- BATTLE_CONFIG_VAR(error_log),
- BATTLE_CONFIG_VAR(etc_log),
- BATTLE_CONFIG_VAR(save_clothcolor),
- BATTLE_CONFIG_VAR(undead_detect_type),
- BATTLE_CONFIG_VAR(agi_penaly_type),
- BATTLE_CONFIG_VAR(agi_penaly_count),
- BATTLE_CONFIG_VAR(agi_penaly_num),
- BATTLE_CONFIG_VAR(agi_penaly_count_lv),
- BATTLE_CONFIG_VAR(vit_penaly_type),
- BATTLE_CONFIG_VAR(vit_penaly_count),
- BATTLE_CONFIG_VAR(vit_penaly_num),
- BATTLE_CONFIG_VAR(vit_penaly_count_lv),
- BATTLE_CONFIG_VAR(mob_changetarget_byskill),
- BATTLE_CONFIG_VAR(player_attack_direction_change),
- BATTLE_CONFIG_VAR(monster_attack_direction_change),
- BATTLE_CONFIG_VAR(display_delay_skill_fail),
- BATTLE_CONFIG_VAR(dead_branch_active),
- BATTLE_CONFIG_VAR(show_steal_in_same_party),
- BATTLE_CONFIG_VAR(hide_GM_session),
- BATTLE_CONFIG_VAR(invite_request_check),
- BATTLE_CONFIG_VAR(disp_experience),
- BATTLE_CONFIG_VAR(prevent_logout), // Added by RoVeRT
- BATTLE_CONFIG_VAR(alchemist_summon_reward), // [Valaris]
- BATTLE_CONFIG_VAR(maximum_level), // [Valaris]
- BATTLE_CONFIG_VAR(drops_by_luk), // [Valaris]
- BATTLE_CONFIG_VAR(monsters_ignore_gm), // [Valaris]
- BATTLE_CONFIG_VAR(pk_mode), // [Valaris]
- BATTLE_CONFIG_VAR(multi_level_up), // [Valaris]
- BATTLE_CONFIG_VAR(hack_info_GM_level), // added by [Yor]
- BATTLE_CONFIG_VAR(any_warp_GM_min_level), // added by [Yor]
- BATTLE_CONFIG_VAR(min_hair_style), // added by [MouseJstr]
- BATTLE_CONFIG_VAR(max_hair_style), // added by [MouseJstr]
- BATTLE_CONFIG_VAR(min_hair_color), // added by [MouseJstr]
- BATTLE_CONFIG_VAR(max_hair_color), // added by [MouseJstr]
- BATTLE_CONFIG_VAR(min_cloth_color), // added by [MouseJstr]
- BATTLE_CONFIG_VAR(max_cloth_color), // added by [MouseJstr]
- BATTLE_CONFIG_VAR(castrate_dex_scale), // added by [MouseJstr]
- BATTLE_CONFIG_VAR(area_size), // added by [MouseJstr]
- BATTLE_CONFIG_VAR(chat_lame_penalty),
- BATTLE_CONFIG_VAR(chat_spam_threshold),
- BATTLE_CONFIG_VAR(chat_spam_flood),
- BATTLE_CONFIG_VAR(chat_spam_ban),
- BATTLE_CONFIG_VAR(chat_spam_warn),
- BATTLE_CONFIG_VAR(chat_maxline),
- BATTLE_CONFIG_VAR(packet_spam_threshold),
- BATTLE_CONFIG_VAR(packet_spam_flood),
- BATTLE_CONFIG_VAR(packet_spam_kick),
- BATTLE_CONFIG_VAR(mask_ip_gms),
- BATTLE_CONFIG_VAR(mob_splash_radius),
- };
-
- if (is_comment(line))
- continue;
- XString w1;
- ZString w2;
- if (!config_split(line, &w1, &w2))
- {
- PRINTF("Bad config line: %s\n"_fmt, line);
- rv = false;
- continue;
- }
-
- if (w1 == "import"_s)
- {
- battle_config_read(w2);
- continue;
- }
-
- for (auto datum : data)
- if (w1 == datum.str)
- {
- *datum.val = config_switch(w2);
- goto continue_outer;
- }
-
- PRINTF("WARNING: unknown battle conf key: %s\n"_fmt, AString(w1));
- rv = false;
-
- continue_outer:
- ;
- }
-
- return rv;
-}
-
-void battle_config_check()
-{
- {
- if (static_cast<interval_t>(battle_config.flooritem_lifetime) < 1_s)
- battle_config.flooritem_lifetime = std::chrono::duration_cast<std::chrono::milliseconds>(LIFETIME_FLOORITEM).count();
- if (battle_config.restart_hp_rate < 0)
- battle_config.restart_hp_rate = 0;
- else if (battle_config.restart_hp_rate > 100)
- battle_config.restart_hp_rate = 100;
- if (battle_config.restart_sp_rate < 0)
- battle_config.restart_sp_rate = 0;
- else if (battle_config.restart_sp_rate > 100)
- battle_config.restart_sp_rate = 100;
- if (battle_config.natural_healhp_interval < NATURAL_HEAL_INTERVAL.count())
- battle_config.natural_healhp_interval = NATURAL_HEAL_INTERVAL.count();
- if (battle_config.natural_healsp_interval < NATURAL_HEAL_INTERVAL.count())
- battle_config.natural_healsp_interval = NATURAL_HEAL_INTERVAL.count();
- if (battle_config.natural_heal_skill_interval < NATURAL_HEAL_INTERVAL.count())
- battle_config.natural_heal_skill_interval = NATURAL_HEAL_INTERVAL.count();
- if (battle_config.natural_heal_weight_rate < 50)
- battle_config.natural_heal_weight_rate = 50;
- if (battle_config.natural_heal_weight_rate > 101)
- battle_config.natural_heal_weight_rate = 101;
- battle_config.monster_max_aspd =
- 2000 - battle_config.monster_max_aspd * 10;
- if (battle_config.monster_max_aspd < 10)
- battle_config.monster_max_aspd = 10;
- if (battle_config.monster_max_aspd > 1000)
- battle_config.monster_max_aspd = 1000;
- battle_config.max_aspd = 2000 - battle_config.max_aspd * 10;
- if (battle_config.max_aspd < 10)
- battle_config.max_aspd = 10;
- if (battle_config.max_aspd > 1000)
- battle_config.max_aspd = 1000;
- if (battle_config.max_hp > 1000000)
- battle_config.max_hp = 1000000;
- if (battle_config.max_hp < 100)
- battle_config.max_hp = 100;
- if (battle_config.max_sp > 1000000)
- battle_config.max_sp = 1000000;
- if (battle_config.max_sp < 100)
- battle_config.max_sp = 100;
- if (battle_config.max_parameter < 10)
- battle_config.max_parameter = 10;
- if (battle_config.max_parameter > 10000)
- battle_config.max_parameter = 10000;
-
- if (battle_config.agi_penaly_count < 2)
- battle_config.agi_penaly_count = 2;
- if (battle_config.vit_penaly_count < 2)
- battle_config.vit_penaly_count = 2;
-
- if (battle_config.hack_info_GM_level < 0) // added by [Yor]
- battle_config.hack_info_GM_level = 0;
- else if (battle_config.hack_info_GM_level > 100)
- battle_config.hack_info_GM_level = 100;
-
- if (battle_config.any_warp_GM_min_level < 0) // added by [Yor]
- battle_config.any_warp_GM_min_level = 0;
- else if (battle_config.any_warp_GM_min_level > 100)
- battle_config.any_warp_GM_min_level = 100;
-
- if (battle_config.chat_spam_ban < 0)
- battle_config.chat_spam_ban = 0;
- else if (battle_config.chat_spam_ban > 32767)
- battle_config.chat_spam_ban = 32767;
-
- if (battle_config.chat_spam_flood < 0)
- battle_config.chat_spam_flood = 0;
- else if (battle_config.chat_spam_flood > 32767)
- battle_config.chat_spam_flood = 32767;
-
- if (battle_config.chat_spam_warn < 0)
- battle_config.chat_spam_warn = 0;
- else if (battle_config.chat_spam_warn > 32767)
- battle_config.chat_spam_warn = 32767;
-
- if (battle_config.chat_spam_threshold < 0)
- battle_config.chat_spam_threshold = 0;
- else if (battle_config.chat_spam_threshold > 32767)
- battle_config.chat_spam_threshold = 32767;
-
- if (battle_config.chat_maxline < 1)
- battle_config.chat_maxline = 1;
- else if (battle_config.chat_maxline > 512)
- battle_config.chat_maxline = 512;
-
- if (battle_config.packet_spam_threshold < 0)
- battle_config.packet_spam_threshold = 0;
- else if (battle_config.packet_spam_threshold > 32767)
- battle_config.packet_spam_threshold = 32767;
-
- if (battle_config.packet_spam_flood < 0)
- battle_config.packet_spam_flood = 0;
- else if (battle_config.packet_spam_flood > 32767)
- battle_config.packet_spam_flood = 32767;
-
- if (battle_config.packet_spam_kick < 0)
- battle_config.packet_spam_kick = 0;
- else if (battle_config.packet_spam_kick > 1)
- battle_config.packet_spam_kick = 1;
-
- if (battle_config.mask_ip_gms < 0)
- battle_config.mask_ip_gms = 0;
- else if (battle_config.mask_ip_gms > 1)
- battle_config.mask_ip_gms = 1;
- }
-}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/battle.hpp b/src/map/battle.hpp
index 97a4a86..1a13420 100644
--- a/src/map/battle.hpp
+++ b/src/map/battle.hpp
@@ -20,27 +20,25 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "fwd.hpp"
-
#include "battle.t.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
+#include "fwd.hpp"
#include "../net/timer.t.hpp"
-#include "clif.t.hpp"
+#include "../mmo/clif.t.hpp"
#include "map.t.hpp"
-#include "skill.t.hpp"
+#include "../mmo/skill.t.hpp"
namespace tmwa
{
+namespace map
+{
// ダメージ
struct Damage
{
- int damage, damage2;
+ int damage;
DamageType type;
int div_;
interval_t amotion, dmotion;
@@ -62,7 +60,6 @@ int battle_heal(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, int hp,
// 攻撃や移動を止める
int battle_stopattack(dumb_ptr<block_list> bl);
-int battle_stopwalking(dumb_ptr<block_list> bl, int type);
// 通常攻撃処理まとめ
ATK battle_weapon_attack(dumb_ptr<block_list> bl, dumb_ptr<block_list> target,
@@ -101,128 +98,15 @@ MobMode battle_get_mode(dumb_ptr<block_list> bl);
int battle_get_stat(SP stat_id, dumb_ptr<block_list> bl);
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> battle_get_sc_data(dumb_ptr<block_list> bl);
-short *battle_get_sc_count(dumb_ptr<block_list> bl);
Opt1 *battle_get_opt1(dumb_ptr<block_list> bl);
Opt2 *battle_get_opt2(dumb_ptr<block_list> bl);
Opt3 *battle_get_opt3(dumb_ptr<block_list> bl);
-Option *battle_get_option(dumb_ptr<block_list> bl);
+Opt0 *battle_get_option(dumb_ptr<block_list> bl);
bool battle_check_undead(Race race, Element element);
int battle_check_target(dumb_ptr<block_list> src, dumb_ptr<block_list> target,
BCT flag);
int battle_check_range(dumb_ptr<block_list> src, dumb_ptr<block_list> bl,
int range);
-
-extern struct Battle_Config
-{
- int warp_point_debug;
- int enemy_critical;
- int enemy_critical_rate;
- int enemy_str;
- int enemy_perfect_flee;
- int casting_rate, delay_rate, delay_dependon_dex;
- int skill_delay_attack_enable;
- int monster_skill_add_range;
- int player_damage_delay;
- int flooritem_lifetime;
- int item_auto_get;
- int item_first_get_time;
- int item_second_get_time;
- int item_third_get_time;
- int base_exp_rate, job_exp_rate;
- int death_penalty_type;
- int death_penalty_base, death_penalty_job;
- int restart_hp_rate;
- int restart_sp_rate;
- int monster_hp_rate;
- int monster_max_aspd;
- int atcommand_gm_only;
- int atcommand_spawn_quantity_limit;
- int gm_all_equipment;
- int monster_active_enable;
- int mob_skill_use;
- int mob_count_rate;
- int basic_skill_check;
- int player_invincible_time;
- int skill_min_damage;
- int natural_healhp_interval;
- int natural_healsp_interval;
- int natural_heal_skill_interval;
- int natural_heal_weight_rate;
- int arrow_decrement;
- int max_aspd;
- int max_hp;
- int max_sp;
- int max_lv;
- int max_parameter;
- int monster_skill_log;
- int battle_log;
- int save_log;
- int error_log;
- int etc_log;
- int save_clothcolor;
- int undead_detect_type;
- int agi_penaly_type;
- int agi_penaly_count;
- int agi_penaly_num;
- int vit_penaly_type;
- int vit_penaly_count;
- int vit_penaly_num;
- int mob_changetarget_byskill;
- int player_attack_direction_change;
- int monster_attack_direction_change;
- int display_delay_skill_fail;
- int dead_branch_active;
- int show_steal_in_same_party;
-
- int prevent_logout;
-
- int alchemist_summon_reward;
- int maximum_level;
- int drops_by_luk;
- int monsters_ignore_gm;
- int multi_level_up;
- int pk_mode;
-
- int agi_penaly_count_lv;
- int vit_penaly_count_lv;
-
- int hide_GM_session;
- int invite_request_check;
- int disp_experience;
-
- int hack_info_GM_level;
- int any_warp_GM_min_level;
-
- int min_hair_style;
- int max_hair_style;
- int min_hair_color;
- int max_hair_color;
- int min_cloth_color;
- int max_cloth_color;
-
- int castrate_dex_scale;
- int area_size;
-
- int chat_lame_penalty;
- int chat_spam_threshold;
- int chat_spam_flood;
- int chat_spam_ban;
- int chat_spam_warn;
- int chat_maxline;
-
- int packet_spam_threshold;
- int packet_spam_flood;
- int packet_spam_kick;
-
- int mask_ip_gms;
-
- int drop_pickup_safety_zone;
- int itemheal_regeneration_factor;
-
- int mob_splash_radius;
-} battle_config;
-
-bool battle_config_read(ZString cfgName);
-void battle_config_check();
+} // namespace map
} // namespace tmwa
diff --git a/src/map/battle.t.hpp b/src/map/battle.t.hpp
index 53c34ff..4759b68 100644
--- a/src/map/battle.t.hpp
+++ b/src/map/battle.t.hpp
@@ -29,6 +29,8 @@
namespace tmwa
{
+namespace map
+{
namespace e
{
enum class BF : uint16_t
@@ -241,4 +243,5 @@ earray<Races, Race, Race::COUNT> race_shift //=
Races::boss,
Races::other,
}};
+} // namespace map
} // namespace tmwa
diff --git a/src/map/chrif.cpp b/src/map/chrif.cpp
index 0748f43..2606911 100644
--- a/src/map/chrif.cpp
+++ b/src/map/chrif.cpp
@@ -29,21 +29,25 @@
#include "../io/cxxstdio.hpp"
#include "../net/ip.hpp"
-#include "../net/packets.hpp"
#include "../net/socket.hpp"
#include "../net/timer.hpp"
+#include "../net/timestamp-utils.hpp"
#include "../proto2/char-map.hpp"
#include "../mmo/human_time_diff.hpp"
-#include "../mmo/mmo.hpp"
-#include "../mmo/utils.hpp"
+#include "../high/mmo.hpp"
+
+#include "../wire/packets.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "intif.hpp"
#include "itemdb.hpp"
#include "map.hpp"
+#include "map_conf.hpp"
#include "npc.hpp"
#include "pc.hpp"
#include "storage.hpp"
@@ -53,60 +57,8 @@
namespace tmwa
{
-Session *char_session;
-static
-IP4Address char_ip;
-static
-int char_port = 6121;
-static
-AccountName userid;
-static
-AccountPass passwd;
-static
-int chrif_state;
-
-// 設定ファイル読み込み関係
-/*==========================================
- *
- *------------------------------------------
- */
-void chrif_setuserid(AccountName id)
-{
- userid = id;
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-void chrif_setpasswd(AccountPass pwd)
-{
- passwd = pwd;
-}
-
-AccountPass chrif_getpasswd(void)
-{
- return passwd;
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-void chrif_setip(IP4Address ip)
+namespace map
{
- char_ip = ip;
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-void chrif_setport(int port)
-{
- char_port = port;
-}
-
/*==========================================
*
*------------------------------------------
@@ -151,11 +103,11 @@ static
int chrif_connect(Session *s)
{
Packet_Fixed<0x2af8> fixed_f8;
- fixed_f8.account_name = userid;
- fixed_f8.account_pass = passwd;
+ fixed_f8.account_name = map_conf.userid;
+ fixed_f8.account_pass = map_conf.passwd;
fixed_f8.unused = 0;
- fixed_f8.ip = clif_getip();
- fixed_f8.port = clif_getport();
+ fixed_f8.ip = map_conf.map_ip;
+ fixed_f8.port = map_conf.map_port;
send_fpacket<0x2af8, 60>(s, fixed_f8);
return 0;
@@ -266,7 +218,7 @@ int chrif_changemapserverack(Session *, const Packet_Fixed<0x2b06>& fixed)
if (fixed.error == 1)
{
if (battle_config.error_log)
- PRINTF("map server change failed.\n"_fmt);
+ PRINTF("Changing the map server failed.\n"_fmt);
pc_authfail(sd->status_key.account_id);
return 0;
}
@@ -289,7 +241,7 @@ int chrif_connectack(Session *s, const Packet_Fixed<0x2af9>& fixed)
{
if (fixed.code)
{
- PRINTF("Connected to char-server failed %d.\n"_fmt, fixed.code);
+ PRINTF("Connecting to char-server failed %d.\n"_fmt, fixed.code);
exit(1);
}
PRINTF("Connected to char-server (connection #%d).\n"_fmt, s);
@@ -297,11 +249,6 @@ int chrif_connectack(Session *s, const Packet_Fixed<0x2af9>& fixed)
chrif_sendmap(s);
- PRINTF("chrif: OnCharIfInit event done. (%d events)\n"_fmt,
- npc_event_doall(stringish<ScriptLabel>("OnCharIfInit"_s)));
- PRINTF("chrif: OnInterIfInit event done. (%d events)\n"_fmt,
- npc_event_doall(stringish<ScriptLabel>("OnInterIfInit"_s)));
-
return 0;
}
@@ -314,13 +261,11 @@ int chrif_sendmapack(Session *, Packet_Fixed<0x2afb> fixed)
{
if (fixed.unknown) //impossible
{
- PRINTF("chrif : send map list to char server failed %d\n"_fmt,
+ PRINTF("chrif: sending the map list to char-server failed %d\n"_fmt,
fixed.unknown);
exit(1);
}
- wisp_server_name = fixed.whisper_name;
-
chrif_state = 2;
return 0;
@@ -395,23 +340,6 @@ int chrif_charselectreq(dumb_ptr<map_session_data> sd)
}
/*==========================================
- * GMに変化要求
- *------------------------------------------
- */
-void chrif_changegm(AccountId id, ZString pass)
-{
- if (!char_session)
- return;
-
- if (battle_config.etc_log)
- PRINTF("chrif_changegm: account: %d, password: '%s'.\n"_fmt, id, pass);
-
- Packet_Head<0x2b0a> head_0a;
- head_0a.account_id = id;
- send_vpacket<0x2b0a, 8, 1>(char_session, head_0a, pass);
-}
-
-/*==========================================
* Change Email
*------------------------------------------
*/
@@ -455,7 +383,7 @@ void chrif_char_ask_name(AccountId id, CharName character_name, short operation_
fixed_0e.operation = operation_type; // type of operation
if (operation_type == 2)
fixed_0e.ban_add = modif;
- PRINTF("chrif : sended 0x2b0e\n"_fmt);
+ PRINTF("chrif: sent 0x2b0e\n"_fmt);
send_fpacket<0x2b0e, 44>(char_session, fixed_0e);
}
@@ -469,7 +397,7 @@ void chrif_char_ask_name(AccountId id, CharName character_name, short operation_
* 4: unban
* 5: changesex
* type of answer:
- * 0: login-server resquest done
+ * 0: login-server request done
* 1: player not found
* 2: gm level too low
* 3: login-server offline
@@ -486,7 +414,7 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed)
{
AString output;
if (fixed.error == 1) // player not found
- output = STRPRINTF("The player '%s' doesn't exist."_fmt,
+ output = STRPRINTF("The player, '%s,' doesn't exist."_fmt,
player_name);
else
{
@@ -495,20 +423,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed)
case 1: // block
switch (fixed.error)
{
- case 0: // login-server resquest done
+ case 0: // login-server request done
output = STRPRINTF(
- "Login-server has been asked to block the player '%s'."_fmt,
+ "Login-server has been asked to block '%s'."_fmt,
player_name);
break;
//case 1: // player not found
case 2: // gm level too low
output = STRPRINTF(
- "Your GM level don't authorise you to block the player '%s'."_fmt,
+ "Your GM level doesn't authorize you to block the player '%s'."_fmt,
player_name);
break;
case 3: // login-server offline
output = STRPRINTF(
- "Login-server is offline. Impossible to block the the player '%s'."_fmt,
+ "Login-server is offline, so it's impossible to block '%s'."_fmt,
player_name);
break;
}
@@ -516,20 +444,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed)
case 2: // ban
switch (fixed.error)
{
- case 0: // login-server resquest done
+ case 0: // login-server request done
output = STRPRINTF(
- "Login-server has been asked to ban the player '%s'."_fmt,
+ "Login-server has been asked to ban '%s'."_fmt,
player_name);
break;
//case 1: // player not found
case 2: // gm level too low
output = STRPRINTF(
- "Your GM level don't authorise you to ban the player '%s'."_fmt,
+ "Your GM level doesn't authorize you to ban '%s'."_fmt,
player_name);
break;
case 3: // login-server offline
output = STRPRINTF(
- "Login-server is offline. Impossible to ban the the player '%s'."_fmt,
+ "Login-server is offline, so it's impossible to ban '%s'."_fmt,
player_name);
break;
}
@@ -537,20 +465,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed)
case 3: // unblock
switch (fixed.error)
{
- case 0: // login-server resquest done
+ case 0: // login-server request done
output = STRPRINTF(
- "Login-server has been asked to unblock the player '%s'."_fmt,
+ "Login-server has been asked to unblock '%s'."_fmt,
player_name);
break;
//case 1: // player not found
case 2: // gm level too low
output = STRPRINTF(
- "Your GM level don't authorise you to unblock the player '%s'."_fmt,
+ "Your GM level doesn't authorize you to unblock '%s'."_fmt,
player_name);
break;
case 3: // login-server offline
output = STRPRINTF(
- "Login-server is offline. Impossible to unblock the the player '%s'."_fmt,
+ "Login-server is offline, so it's impossible to unblock '%s'."_fmt,
player_name);
break;
}
@@ -558,20 +486,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed)
case 4: // unban
switch (fixed.error)
{
- case 0: // login-server resquest done
+ case 0: // login-server request done
output = STRPRINTF(
- "Login-server has been asked to unban the player '%s'."_fmt,
+ "Login-server has been asked to unban '%s'."_fmt,
player_name);
break;
//case 1: // player not found
case 2: // gm level too low
output = STRPRINTF(
- "Your GM level don't authorise you to unban the player '%s'."_fmt,
+ "Your GM level doesn't authorize you to unban '%s'."_fmt,
player_name);
break;
case 3: // login-server offline
output = STRPRINTF(
- "Login-server is offline. Impossible to unban the the player '%s'."_fmt,
+ "Login-server is offline, so it's impossible to unban '%s'."_fmt,
player_name);
break;
}
@@ -579,20 +507,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed)
case 5: // changesex
switch (fixed.error)
{
- case 0: // login-server resquest done
+ case 0: // login-server request done
output = STRPRINTF(
- "Login-server has been asked to change the sex of the player '%s'."_fmt,
+ "Login-server has been asked to change the sex of '%s'."_fmt,
player_name);
break;
//case 1: // player not found
case 2: // gm level too low
output = STRPRINTF(
- "Your GM level don't authorise you to change the sex of the player '%s'."_fmt,
+ "Your GM level doesn't authorize you to change the sex of '%s'."_fmt,
player_name);
break;
case 3: // login-server offline
output = STRPRINTF(
- "Login-server is offline. Impossible to change the sex of the the player '%s'."_fmt,
+ "Login-server is offline, so it's impossible to change the sex of '%s'."_fmt,
player_name);
break;
}
@@ -603,36 +531,12 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed)
clif_displaymessage(sd->sess, output);
}
else
- PRINTF("chrif_char_ask_name_answer failed - player not online.\n"_fmt);
+ PRINTF("chrif_char_ask_name_answer failed because the player is not online.\n"_fmt);
return 0;
}
/*==========================================
- * End of GM change(@GM) (modified by Yor)
- *------------------------------------------
- */
-static
-void chrif_changedgm(Session *, const Packet_Fixed<0x2b0b>& fixed)
-{
- AccountId acc = fixed.account_id;
- GmLevel level = fixed.gm_level;
-
- dumb_ptr<map_session_data> sd = map_id2sd(account_to_block(acc));
-
- if (battle_config.etc_log)
- PRINTF("chrif_changedgm: account: %d, GM level 0 -> %d.\n"_fmt, acc,
- level);
- if (sd != nullptr)
- {
- if (level)
- clif_displaymessage(sd->sess, "GM modification success."_s);
- else
- clif_displaymessage(sd->sess, "Failure of GM modification."_s);
- }
-}
-
-/*==========================================
* 性別変化終了 (modified by Yor)
*------------------------------------------
*/
@@ -659,15 +563,14 @@ void chrif_changedsex(Session *, const Packet_Fixed<0x2b0d>& fixed)
{
if (sd->status.inventory[i].nameid
&& bool(sd->status.inventory[i].equip))
- pc_unequipitem(sd, i, CalcStatus::NOW);
+ pc_unequipitem(sd, i, CalcStatus::LATER);
}
+ pc_calcstatus(sd, 0);
// 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
// do same modify in login-server for the account, but no in char-server (it ask again login_id1 to login, and don't remember it)
- clif_displaymessage(sd->sess,
- "Your sex has been changed (need disconexion by the server)..."_s);
- clif_setwaitclose(sd->sess); // forced to disconnect for the change
+ clif_fixpcpos(sd); // use clif_set0078_main_1d8 to send new sex to the client
}
}
else
@@ -750,18 +653,13 @@ int chrif_divorce(CharId char_id, CharId partner_id)
if (sd && sd->status.partner_id == partner_id)
{
sd->status.partner_id = CharId();
-
- if (sd->npc_flags.divorce)
- {
- sd->npc_flags.divorce = 0;
- map_scriptcont(sd, sd->npc_id);
- }
}
sd = map_nick2sd(map_charid2nick(partner_id));
- nullpo_retz(sd);
- if (sd->status.partner_id == char_id)
+ if (sd && sd->status.partner_id == char_id)
+ {
sd->status.partner_id = CharId();
+ }
return 0;
}
@@ -801,14 +699,14 @@ int chrif_accountdeletion(Session *, const Packet_Fixed<0x2b13>& fixed)
{
sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters
clif_displaymessage(sd->sess,
- "Your account has been deleted (disconnection)..."_s);
+ "Your account has been deleted. You will now be disconnected..."_s);
clif_setwaitclose(sd->sess); // forced to disconnect for the change
}
}
else
{
if (sd != nullptr)
- PRINTF("chrif_accountdeletion failed - player not online.\n"_fmt);
+ PRINTF("chrif_accountdeletion failed because the player is not online.\n"_fmt);
}
return 0;
@@ -838,11 +736,11 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed)
{ // status or final date of a banishment
case 1: // 0 = Unregistered ID
clif_displaymessage(sd->sess,
- "Your account has 'Unregistered'."_s);
+ "Your account has an unregistered ID."_s);
break;
case 2: // 1 = Incorrect Password
clif_displaymessage(sd->sess,
- "Your account has an 'Incorrect Password'..."_s);
+ "Your password is incorrect."_s);
break;
case 3: // 2 = This ID is expired
clif_displaymessage(sd->sess,
@@ -850,7 +748,7 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed)
break;
case 4: // 3 = Rejected from Server
clif_displaymessage(sd->sess,
- "Your account has been rejected from server."_s);
+ "Your account has been rejected by the server."_s);
break;
case 5: // 4 = You have been blocked by the GM Team
clif_displaymessage(sd->sess,
@@ -858,19 +756,19 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed)
break;
case 6: // 5 = Your Game's EXE file is not the latest version
clif_displaymessage(sd->sess,
- "Your Game's EXE file is not the latest version."_s);
+ "You need to update your client."_s);
break;
case 7: // 6 = Your are Prohibited to log in until %s
clif_displaymessage(sd->sess,
- "Your account has been prohibited to log in."_s);
+ "Your account has been prohibited from logging in."_s);
break;
case 8: // 7 = Server is jammed due to over populated
clif_displaymessage(sd->sess,
- "Server is jammed due to over populated."_s);
+ "The server is overpopulated."_s);
break;
case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this)
clif_displaymessage(sd->sess,
- "Your account has not more authorised."_s);
+ "Your account must be authorized."_s);
break;
case 100: // 99 = This ID has been totally erased
clif_displaymessage(sd->sess,
@@ -878,7 +776,7 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed)
break;
default:
clif_displaymessage(sd->sess,
- "Your account has not more authorised."_s);
+ "Your account must be authorized."_s);
break;
}
}
@@ -897,7 +795,7 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed)
else
{
if (sd != nullptr)
- PRINTF("chrif_accountban failed - player not online.\n"_fmt);
+ PRINTF("chrif_accountban failed because the player is not online.\n"_fmt);
}
return 0;
@@ -910,137 +808,17 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed)
static
int chrif_recvgmaccounts(Session *s, const std::vector<Packet_Repeat<0x2b15>>& repeat)
{
- PRINTF("From login-server: receiving of %d GM accounts information.\n"_fmt,
+ PRINTF("Receiving information on %d GM accounts from login-server.\n"_fmt,
pc_read_gm_account(s, repeat));
return 0;
}
-/*==========================================
- * Request to reload GM accounts and their levels: send to char-server by [Yor]
- *------------------------------------------
- */
-int chrif_reloadGMdb(void)
-{
- if (!char_session)
- return -1;
-
- Packet_Fixed<0x2af7> fixed_f7;
- send_fpacket<0x2af7, 2>(char_session, fixed_f7);
-
- return 0;
-}
-
-/*========================================
- * Map item IDs
- *----------------------------------------
- */
-
-static
-void ladmin_itemfrob_fix_item(ItemNameId source, ItemNameId dest, Item *item)
-{
- if (item && item->nameid == source)
- {
- item->nameid = dest;
- item->equip = EPOS::ZERO;
- }
-}
-
-static
-void ladmin_itemfrob_c2(dumb_ptr<block_list> bl, ItemNameId source_id, ItemNameId dest_id)
-{
-#define IFIX(v) if (v == source_id) {v = dest_id; }
-#define FIX(item) ladmin_itemfrob_fix_item(source_id, dest_id, &item)
-
- if (!bl)
- return;
-
- switch (bl->bl_type)
- {
- case BL::PC:
- {
- dumb_ptr<map_session_data> pc = bl->is_player();
- Storage *stor = account2storage2(pc->status_key.account_id);
-
- for (IOff0 j : IOff0::iter())
- IFIX(pc->status.inventory[j].nameid);
- // cart is no longer supported
- // IFIX(pc->status.weapon);
- IFIX(pc->status.shield);
- IFIX(pc->status.head_top);
- IFIX(pc->status.head_mid);
- IFIX(pc->status.head_bottom);
-
- if (stor)
- {
- for (SOff0 j : SOff0::iter())
- FIX(stor->storage_[j]);
- }
-
- for (IOff0 j : IOff0::iter())
- {
- struct item_data *item = pc->inventory_data[j];
- if (item && item->nameid == source_id)
- {
- item->nameid = dest_id;
- if (bool(item->equip))
- pc_unequipitem(pc, j, CalcStatus::NOW);
- item->nameid = dest_id;
- }
- }
-
- break;
- }
-
- case BL::MOB:
- {
- dumb_ptr<mob_data> mob = bl->is_mob();
- for (Item& itm : mob->lootitemv)
- FIX(itm);
- break;
- }
-
- case BL::ITEM:
- {
- dumb_ptr<flooritem_data> item = bl->is_item();
- FIX(item->item_data);
- break;
- }
- }
-#undef FIX
-#undef IFIX
-}
-
-static
-void ladmin_itemfrob_c(dumb_ptr<block_list> bl, ItemNameId source_id, ItemNameId dest_id)
-{
- ladmin_itemfrob_c2(bl, source_id, dest_id);
-}
-
-static
-void ladmin_itemfrob(Session *, const Packet_Fixed<0x2afa>& fixed)
-{
- ItemNameId source_id = fixed.source_item_id;
- ItemNameId dest_id = fixed.dest_item_id;
- dumb_ptr<block_list> bl = map_get_first_session();
-
- // flooritems
- map_foreachobject(std::bind(ladmin_itemfrob_c, ph::_1, source_id, dest_id),
- BL::NUL /* any object */);
-
- // player characters (and, hopefully, mobs)
- while (bl->bl_next)
- {
- ladmin_itemfrob_c2(bl, source_id, dest_id);
- bl = bl->bl_next;
- }
-}
-
static
void chrif_delete(Session *s)
{
assert (s == char_session);
- PRINTF("Map-server can't connect to char-server (connection #%d).\n"_fmt,
+ PRINTF("map-server can't connect to char-server (connection #%d).\n"_fmt,
s);
char_session = nullptr;
}
@@ -1070,16 +848,6 @@ void chrif_parse(Session *s)
chrif_connectack(s, fixed);
break;
}
- case 0x2afa:
- {
- Packet_Fixed<0x2afa> fixed;
- rv = recv_fpacket<0x2afa, 10>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- ladmin_itemfrob(s, fixed);
- break;
- }
case 0x2afb:
{
Packet_Fixed<0x2afb> fixed;
@@ -1099,12 +867,11 @@ void chrif_parse(Session *s)
AccountId id = payload.account_id;
int login_id2 = payload.login_id2;
- TimeT connect_until_time = payload.connect_until;
short tmw_version = payload.packet_tmw_version;
CharKey st_key = payload.char_key;
CharData st_data = payload.char_data;
pc_authok(id, login_id2,
- connect_until_time, tmw_version,
+ tmw_version,
&st_key, &st_data);
break;
}
@@ -1159,16 +926,6 @@ void chrif_parse(Session *s)
chrif_changemapserverack(s, fixed);
break;
}
- case 0x2b0b:
- {
- Packet_Fixed<0x2b0b> fixed;
- rv = recv_fpacket<0x2b0b, 10>(s, fixed);
- if (rv != RecvResult::Complete)
- break;
-
- chrif_changedgm(s, fixed);
- break;
- }
case 0x2b0d:
{
Packet_Fixed<0x2b0d> fixed;
@@ -1250,7 +1007,7 @@ void chrif_parse(Session *s)
return;
if (battle_config.error_log)
- PRINTF("chrif_parse : unknown packet %d %d\n"_fmt, s,
+ PRINTF("chrif_parse: unknown packet %d %d\n"_fmt, s,
packet_id);
s->set_eof();
return;
@@ -1283,7 +1040,7 @@ void send_users_tochar(TimerData *, tick_t)
if (sd && sd->state.auth &&
!((battle_config.hide_GM_session
|| sd->state.shroud_active
- || bool(sd->status.option & Option::HIDE)) && pc_isGM(sd)))
+ || bool(sd->status.option & Opt0::HIDE)) && pc_isGM(sd)))
{
Packet_Repeat<0x2aff> info;
info.char_id = sd->status_key.char_id;
@@ -1304,9 +1061,9 @@ void check_connect_char_server(TimerData *, tick_t)
{
if (!char_session)
{
- PRINTF("Attempt to connect to char-server...\n"_fmt);
+ PRINTF("Attempting to connect to char-server...\n"_fmt);
chrif_state = 0;
- char_session = make_connection(char_ip, char_port,
+ char_session = make_connection(map_conf.char_ip, map_conf.char_port,
SessionParsers{.func_parse= chrif_parse, .func_delete= chrif_delete});
if (!char_session)
return;
@@ -1331,4 +1088,5 @@ void do_init_chrif(void)
5_s
).detach();
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/chrif.hpp b/src/map/chrif.hpp
index 4711bc5..655103d 100644
--- a/src/map/chrif.hpp
+++ b/src/map/chrif.hpp
@@ -22,24 +22,11 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
-
-#include "../net/fwd.hpp"
-
-#include "../mmo/fwd.hpp"
-
namespace tmwa
{
-void chrif_setuserid(AccountName);
-void chrif_setpasswd(AccountPass);
-AccountPass chrif_getpasswd(void);
-
-void chrif_setip(IP4Address);
-void chrif_setport(int);
-
+namespace map
+{
int chrif_isconnect(void);
int chrif_authreq(dumb_ptr<map_session_data>);
@@ -55,12 +42,8 @@ void chrif_changeemail(AccountId id, AccountEmail actual_email, AccountEmail new
void chrif_char_ask_name(AccountId id, CharName character_name, short operation_type,
HumanTimeDiff modif);
int chrif_saveaccountreg2(dumb_ptr<map_session_data> sd);
-int chrif_reloadGMdb(void);
int chrif_send_divorce(CharId char_id);
void do_init_chrif(void);
-
-// only used by intif.cpp
-// and clif.cpp for the new on_delete stuff ...
-extern Session *char_session;
+} // namespace map
} // namespace tmwa
diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index b47bf28..243ffaf 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -36,30 +36,36 @@
#include "../strings/xstring.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+#include "../io/extract.hpp"
#include "../io/write.hpp"
#include "../net/ip.hpp"
-#include "../net/packets.hpp"
#include "../net/socket.hpp"
#include "../net/timer.hpp"
+#include "../net/timestamp-utils.hpp"
#include "../proto2/any-user.hpp"
#include "../proto2/char-map.hpp"
#include "../proto2/map-user.hpp"
-#include "../mmo/md5more.hpp"
-#include "../mmo/utils.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
#include "../mmo/version.hpp"
+#include "../high/md5more.hpp"
+
+#include "../wire/packets.hpp"
+
#include "atcommand.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "chrif.hpp"
+#include "globals.hpp"
#include "intif.hpp"
#include "itemdb.hpp"
#include "magic.hpp"
#include "magic-stmt.hpp"
#include "map.hpp"
+#include "map_conf.hpp"
#include "npc.hpp"
#include "party.hpp"
#include "pc.hpp"
@@ -73,6 +79,8 @@
namespace tmwa
{
+namespace map
+{
constexpr int EMOTE_IGNORED = 0x0e;
// functions list. Rate is how many milliseconds are required between
@@ -117,11 +125,6 @@ enum class SendWho
};
static
-IP4Address map_ip;
-static
-int map_port = 5121;
-
-static
int clif_changelook_towards(dumb_ptr<block_list> bl, LOOK type, int val,
dumb_ptr<map_session_data> dstsd);
static
@@ -149,42 +152,6 @@ void clif_delete(Session *s)
/*==========================================
- * map鯖のip設定
- *------------------------------------------
- */
-void clif_setip(IP4Address ip)
-{
- map_ip = ip;
-}
-
-/*==========================================
- * map鯖のport設定
- *------------------------------------------
- */
-void clif_setport(int port)
-{
- map_port = port;
-}
-
-/*==========================================
- * map鯖のip読み出し
- *------------------------------------------
- */
-IP4Address clif_getip(void)
-{
- return map_ip;
-}
-
-/*==========================================
- * map鯖のport読み出し
- *------------------------------------------
- */
-int clif_getport(void)
-{
- return map_port;
-}
-
-/*==========================================
*
*------------------------------------------
*/
@@ -231,11 +198,6 @@ int is_deaf(dumb_ptr<block_list> bl)
return sd->special_state.deaf;
}
-static
-void clif_emotion_towards(dumb_ptr<block_list> bl,
- dumb_ptr<block_list> target, int type);
-
-
enum class ChatType
{
Party,
@@ -295,7 +257,6 @@ void clif_send_sub(dumb_ptr<block_list> bl, const Buffer& buf,
static
int clif_send(const Buffer& buf, dumb_ptr<block_list> bl, SendWho type)
{
- PartyPair p;
int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
if (type != SendWho::ALL_CLIENT)
@@ -305,7 +266,7 @@ int clif_send(const Buffer& buf, dumb_ptr<block_list> bl, SendWho type)
if (bl->bl_type == BL::PC)
{
dumb_ptr<map_session_data> sd2 = bl->is_player();
- if (bool(sd2->status.option & Option::INVISIBILITY))
+ if (bool(sd2->status.option & Opt0::INVISIBILITY))
{
// Obscure hidden GMs
@@ -384,20 +345,22 @@ int clif_send(const Buffer& buf, dumb_ptr<block_list> bl, SendWho type)
case SendWho::PARTY_WOS: // 自分以外の全パーティーメンバに送信
case SendWho::PARTY_SAMEMAP: // 同じマップの全パーティーメンバに送信
case SendWho::PARTY_SAMEMAP_WOS: // 自分以外の同じマップの全パーティーメンバに送信
+ {
+ Option<PartyPair> p_ = None;
if (bl->bl_type == BL::PC)
{
dumb_ptr<map_session_data> sd = bl->is_player();
if (sd->partyspy)
{
- p = party_search(sd->partyspy);
+ p_ = party_search(sd->partyspy);
}
else
{
if (sd->status.party_id)
- p = party_search(sd->status.party_id);
+ p_ = party_search(sd->status.party_id);
}
}
- if (p)
+ OMATCH_BEGIN_SOME (p, p_)
{
for (int i = 0; i < MAX_PARTY; i++)
{
@@ -436,6 +399,8 @@ int clif_send(const Buffer& buf, dumb_ptr<block_list> bl, SendWho type)
}
}
}
+ OMATCH_END ();
+ }
break;
case SendWho::SELF:
{
@@ -617,37 +582,6 @@ int clif_clearchar(dumb_ptr<block_list> bl, BeingRemoveWhy type)
return 0;
}
-static
-void clif_clearchar_delay_sub(TimerData *, tick_t,
- dumb_ptr<block_list> bl, BeingRemoveWhy type)
-{
- clif_clearchar(bl, type);
- MapBlockLock::freeblock(bl);
-}
-
-int clif_clearchar_delay(tick_t tick,
- dumb_ptr<block_list> bl, BeingRemoveWhy type)
-{
- dumb_ptr<block_list> tmpbl;
- tmpbl.new_();
-
- // yikes!
- tmpbl->bl_next = bl->bl_next;
- tmpbl->bl_prev = bl->bl_prev;
- tmpbl->bl_id = bl->bl_id;
- tmpbl->bl_m = bl->bl_m;
- tmpbl->bl_x = bl->bl_x;
- tmpbl->bl_y = bl->bl_y;
- tmpbl->bl_type = bl->bl_type;
-
- Timer(tick,
- std::bind(clif_clearchar_delay_sub, ph::_1, ph::_2,
- tmpbl, type)
- ).detach();
-
- return 0;
-}
-
/*==========================================
*
*------------------------------------------
@@ -684,14 +618,14 @@ void clif_set0078_main_1d8(dumb_ptr<map_session_data> sd, Buffer& buf)
fixed_1d8.weapon = sd->attack_spell_look_override;
else
{
- if (widx.ok() && sd->inventory_data[widx])
+ if (widx.ok() && sd->inventory_data[widx].is_some())
{
fixed_1d8.weapon = sd->status.inventory[widx].nameid;
}
else
fixed_1d8.weapon = ItemNameId();
}
- if (sidx.ok() && sidx != widx && sd->inventory_data[sidx])
+ if (sidx.ok() && sidx != widx && sd->inventory_data[sidx].is_some())
{
fixed_1d8.shield = sd->status.inventory[sidx].nameid;
}
@@ -738,14 +672,14 @@ void clif_set0078_alt_1d9(dumb_ptr<map_session_data> sd, Buffer& buf)
fixed_1d8.weapon = sd->attack_spell_look_override;
else
{
- if (widx.ok() && sd->inventory_data[widx])
+ if (widx.ok() && sd->inventory_data[widx].is_some())
{
fixed_1d8.weapon = sd->status.inventory[widx].nameid;
}
else
fixed_1d8.weapon = ItemNameId();
}
- if (sidx.ok() && sidx != widx && sd->inventory_data[sidx])
+ if (sidx.ok() && sidx != widx && sd->inventory_data[sidx].is_some())
{
fixed_1d8.shield = sd->status.inventory[sidx].nameid;
}
@@ -791,13 +725,13 @@ void clif_set007b(dumb_ptr<map_session_data> sd, Buffer& buf)
fixed_1da.hair_style = sd->status.hair;
IOff0 widx = sd->equip_index_maybe[EQUIP::WEAPON];
IOff0 sidx = sd->equip_index_maybe[EQUIP::SHIELD];
- if (widx.ok() && sd->inventory_data[widx])
+ if (widx.ok() && sd->inventory_data[widx].is_some())
{
fixed_1da.weapon = sd->status.inventory[widx].nameid;
}
else
fixed_1da.weapon = ItemNameId();
- if (sidx.ok() && sidx != widx && sd->inventory_data[sidx])
+ if (sidx.ok() && sidx != widx && sd->inventory_data[sidx].is_some())
{
fixed_1da.shield = sd->status.inventory[sidx].nameid;
}
@@ -969,7 +903,7 @@ int clif_spawnnpc(dumb_ptr<npc_data> nd)
{
nullpo_retz(nd);
- if (nd->npc_class == NEGATIVE_SPECIES || nd->flag & 1 || nd->npc_class == INVISIBLE_CLASS)
+ if (nd->flag & 1 || nd->npc_class == INVISIBLE_CLASS)
return 0;
Packet_Fixed<0x007c> fixed_7c;
@@ -1002,7 +936,7 @@ int clif_spawn_fake_npc_for_player(dumb_ptr<map_session_data> sd, BlockId fake_n
fixed_7c.speed = interval_t();
fixed_7c.opt1 = Opt1::ZERO;
fixed_7c.opt2 = Opt2::ZERO;
- fixed_7c.option = Option::ZERO;
+ fixed_7c.option = Opt0::ZERO;
fixed_7c.species = FAKE_NPC_CLASS;
fixed_7c.pos.x = sd->bl_x;
fixed_7c.pos.y = sd->bl_y;
@@ -1013,7 +947,7 @@ int clif_spawn_fake_npc_for_player(dumb_ptr<map_session_data> sd, BlockId fake_n
fixed_78.speed = interval_t();
fixed_78.opt1 = Opt1::ZERO;
fixed_78.opt2 = Opt2::ZERO;
- fixed_78.option = Option::ZERO;
+ fixed_78.option = Opt0::ZERO;
fixed_78.species = FAKE_NPC_CLASS;
fixed_78.unused_head_bottom_or_species_again = unwrap<Species>(FAKE_NPC_CLASS);
fixed_78.pos.x = sd->bl_x;
@@ -1224,7 +1158,6 @@ int clif_npcbuysell(dumb_ptr<map_session_data> sd, BlockId id)
*/
int clif_buylist(dumb_ptr<map_session_data> sd, dumb_ptr<npc_data_shop> nd)
{
- struct item_data *id;
int i, val;
nullpo_retz(sd);
@@ -1234,7 +1167,7 @@ int clif_buylist(dumb_ptr<map_session_data> sd, dumb_ptr<npc_data_shop> nd)
std::vector<Packet_Repeat<0x00c6>> repeat_c6(nd->shop_items.size());
for (i = 0; i < nd->shop_items.size(); i++)
{
- id = itemdb_search(nd->shop_items[i].nameid);
+ P<struct item_data> id = itemdb_search(nd->shop_items[i].nameid);
val = nd->shop_items[i].value;
repeat_c6[i].base_price = val; // base price
repeat_c6[i].actual_price = val; // actual price
@@ -1258,9 +1191,11 @@ int clif_selllist(dumb_ptr<map_session_data> sd)
std::vector<Packet_Repeat<0x00c7>> repeat_c7;
for (IOff0 i : IOff0::iter())
{
- if (sd->status.inventory[i].nameid && sd->inventory_data[i])
+ if (!sd->status.inventory[i].nameid)
+ continue;
+ OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[i])
{
- int val = sd->inventory_data[i]->value_sell;
+ int val = sdidi->value_sell;
if (val < 0)
continue;
Packet_Repeat<0x00c7> info;
@@ -1269,6 +1204,7 @@ int clif_selllist(dumb_ptr<map_session_data> sd)
info.actual_price = val;
repeat_c7.push_back(info);
}
+ OMATCH_END ();
}
send_packet_repeatonly<0x00c7, 4, 10>(s, repeat_c7);
@@ -1379,9 +1315,9 @@ int clif_additem(dumb_ptr<map_session_data> sd, IOff0 n, int amount, PickupFail
}
else
{
- if (!n.ok() || !sd->status.inventory[n].nameid
- || sd->inventory_data[n] == nullptr)
+ if (!n.ok() || !sd->status.inventory[n].nameid)
return 1;
+ auto sdidn = TRY_UNWRAP(sd->inventory_data[n], return 1);
fixed_a0.ioff2 = n.shift();
fixed_a0.amount = amount;
@@ -1396,9 +1332,9 @@ int clif_additem(dumb_ptr<map_session_data> sd, IOff0 n, int amount, PickupFail
fixed_a0.card3 = 0;
}
fixed_a0.epos = pc_equippoint(sd, n);
- fixed_a0.item_type = (sd->inventory_data[n]->type == ItemType::_7
+ fixed_a0.item_type = (sdidn->type == ItemType::_7
? ItemType::WEAPON
- : sd->inventory_data[n]->type);
+ : sdidn->type);
fixed_a0.pickup_fail = fail;
}
@@ -1435,17 +1371,18 @@ void clif_itemlist(dumb_ptr<map_session_data> sd)
std::vector<Packet_Repeat<0x01ee>> repeat_1ee;
for (IOff0 i : IOff0::iter())
{
- if (!sd->status.inventory[i].nameid
- || sd->inventory_data[i] == nullptr
- || itemdb_isequip2(sd->inventory_data[i]))
+ if (!sd->status.inventory[i].nameid)
+ continue;
+ auto sdidi = TRY_UNWRAP(sd->inventory_data[i], continue);
+ if (itemdb_isequip2(sdidi))
continue;
Packet_Repeat<0x01ee> info;
info.ioff2 = i.shift();
info.name_id = sd->status.inventory[i].nameid;
- info.item_type = sd->inventory_data[i]->type;
+ info.item_type = sdidi->type;
info.identify = 1;
info.amount = sd->status.inventory[i].amount;
- if (sd->inventory_data[i]->equip == EPOS::ARROW)
+ if (sdidi->equip == EPOS::ARROW)
{
info.epos = EPOS::ARROW;
if (bool(sd->status.inventory[i].equip))
@@ -1479,17 +1416,18 @@ void clif_equiplist(dumb_ptr<map_session_data> sd)
std::vector<Packet_Repeat<0x00a4>> repeat_a4;
for (IOff0 i : IOff0::iter())
{
- if (!sd->status.inventory[i].nameid
- || sd->inventory_data[i] == nullptr
- || !itemdb_isequip2(sd->inventory_data[i]))
+ if (!sd->status.inventory[i].nameid)
+ continue;
+ P<struct item_data> sdidi = TRY_UNWRAP(sd->inventory_data[i], continue);
+ if (!itemdb_isequip2(sdidi))
continue;
Packet_Repeat<0x00a4> info;
info.ioff2 = i.shift();
info.name_id = sd->status.inventory[i].nameid;
info.item_type = (
- sd->inventory_data[i]->type == ItemType::_7
+ sdidi->type == ItemType::_7
? ItemType::WEAPON
- : sd->inventory_data[i]->type);
+ : sdidi->type);
info.identify = 0;
info.epos_pc = pc_equippoint(sd, i);
info.epos_inv = sd->status.inventory[i].equip;
@@ -1513,10 +1451,9 @@ void clif_equiplist(dumb_ptr<map_session_data> sd)
* カプラさんに預けてある消耗品&収集品リスト
*------------------------------------------
*/
-int clif_storageitemlist(dumb_ptr<map_session_data> sd, Storage *stor)
+int clif_storageitemlist(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor)
{
nullpo_retz(sd);
- nullpo_retz(stor);
Session *s = sd->sess;
std::vector<Packet_Repeat<0x01f0>> repeat_1f0;
@@ -1525,9 +1462,7 @@ int clif_storageitemlist(dumb_ptr<map_session_data> sd, Storage *stor)
if (!stor->storage_[i].nameid)
continue;
- struct item_data *id;
- id = itemdb_search(stor->storage_[i].nameid);
- nullpo_retz(id);
+ P<struct item_data> id = itemdb_search(stor->storage_[i].nameid);
if (itemdb_isequip2(id))
continue;
@@ -1555,10 +1490,9 @@ int clif_storageitemlist(dumb_ptr<map_session_data> sd, Storage *stor)
* カプラさんに預けてある装備リスト
*------------------------------------------
*/
-int clif_storageequiplist(dumb_ptr<map_session_data> sd, Storage *stor)
+int clif_storageequiplist(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor)
{
nullpo_retz(sd);
- nullpo_retz(stor);
Session *s = sd->sess;
std::vector<Packet_Repeat<0x00a6>> repeat_a6;
@@ -1567,9 +1501,7 @@ int clif_storageequiplist(dumb_ptr<map_session_data> sd, Storage *stor)
if (!stor->storage_[i].nameid)
continue;
- struct item_data *id;
- id = itemdb_search(stor->storage_[i].nameid);
- nullpo_retz(id);
+ P<struct item_data> id = itemdb_search(stor->storage_[i].nameid);
if (!itemdb_isequip2(id))
continue;
Packet_Repeat<0x00a6> info;
@@ -1831,7 +1763,7 @@ int clif_changelook_towards(dumb_ptr<block_list> bl, LOOK type, int val,
if (bl->bl_type == BL::PC)
sd = bl->is_player();
- if (sd && bool(sd->status.option & Option::INVISIBILITY))
+ if (sd && bool(sd->status.option & Opt0::INVISIBILITY))
return 0;
if (sd
@@ -1845,7 +1777,7 @@ int clif_changelook_towards(dumb_ptr<block_list> bl, LOOK type, int val,
fixed_1d7.look_type = type;
IOff0 idx = sd->equip_index_maybe[equip_point];
- if (idx.ok() && sd->inventory_data[idx])
+ if (idx.ok() && sd->inventory_data[idx].is_some())
{
fixed_1d7.weapon_or_name_id_or_value = unwrap<ItemNameId>(sd->status.inventory[idx].nameid);
}
@@ -1862,14 +1794,14 @@ int clif_changelook_towards(dumb_ptr<block_list> bl, LOOK type, int val,
fixed_1d7.weapon_or_name_id_or_value = unwrap<ItemNameId>(sd->attack_spell_look_override);
else
{
- if (widx.ok() && sd->inventory_data[widx])
+ if (widx.ok() && sd->inventory_data[widx].is_some())
{
fixed_1d7.weapon_or_name_id_or_value = unwrap<ItemNameId>(sd->status.inventory[widx].nameid);
}
else
fixed_1d7.weapon_or_name_id_or_value = unwrap<ItemNameId>(ItemNameId());
}
- if (sidx.ok() && sidx != widx && sd->inventory_data[sidx])
+ if (sidx.ok() && sidx != widx && sd->inventory_data[sidx].is_some())
{
fixed_1d7.shield = sd->status.inventory[sidx].nameid;
}
@@ -2074,7 +2006,7 @@ int clif_changeoption(dumb_ptr<block_list> bl)
nullpo_retz(bl);
- Option option = *battle_get_option(bl);
+ Opt0 option = *battle_get_option(bl);
sc_data = battle_get_sc_data(bl);
Packet_Fixed<0x0119> fixed_119;
@@ -2263,10 +2195,9 @@ int clif_tradecompleted(dumb_ptr<map_session_data> sd, int fail)
*------------------------------------------
*/
int clif_updatestorageamount(dumb_ptr<map_session_data> sd,
- Storage *stor)
+ Borrowed<Storage> stor)
{
nullpo_retz(sd);
- nullpo_retz(stor);
Session *s = sd->sess;
Packet_Fixed<0x00f2> fixed_f2;
@@ -2281,11 +2212,10 @@ int clif_updatestorageamount(dumb_ptr<map_session_data> sd,
* カプラ倉庫にアイテムを追加する
*------------------------------------------
*/
-int clif_storageitemadded(dumb_ptr<map_session_data> sd, Storage *stor,
+int clif_storageitemadded(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor,
SOff0 index, int amount)
{
nullpo_retz(sd);
- nullpo_retz(stor);
Session *s = sd->sess;
Packet_Fixed<0x00f4> fixed_f4;
@@ -2357,7 +2287,7 @@ static
void clif_getareachar_pc(dumb_ptr<map_session_data> sd,
dumb_ptr<map_session_data> dstsd)
{
- if (bool(dstsd->status.option & Option::INVISIBILITY))
+ if (bool(dstsd->status.option & Opt0::INVISIBILITY))
return;
nullpo_retv(sd);
@@ -2392,7 +2322,7 @@ void clif_getareachar_npc(dumb_ptr<map_session_data> sd, dumb_ptr<npc_data> nd)
nullpo_retv(sd);
nullpo_retv(nd);
- if (nd->npc_class == NEGATIVE_SPECIES || nd->flag & 1 || nd->npc_class == INVISIBLE_CLASS)
+ if (nd->flag & 1 || nd->npc_class == INVISIBLE_CLASS)
return;
Buffer buf;
@@ -2470,7 +2400,7 @@ int clif_fixpcpos(dumb_ptr<map_session_data> sd)
*/
int clif_damage(dumb_ptr<block_list> src, dumb_ptr<block_list> dst,
tick_t tick, interval_t sdelay, interval_t ddelay, int damage,
- int div, DamageType type, int damage2)
+ int div, DamageType type)
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
@@ -2488,7 +2418,7 @@ int clif_damage(dumb_ptr<block_list> src, dumb_ptr<block_list> dst,
fixed_8a.damage = (damage > 0x7fff) ? 0x7fff : damage;
fixed_8a.div = div;
fixed_8a.damage_type = type;
- fixed_8a.damage2 = damage2;
+ fixed_8a.damage2 = 0;
Buffer buf = create_fpacket<0x008a, 29>(fixed_8a);
clif_send(buf, src, SendWho::AREA);
@@ -2686,43 +2616,6 @@ void clif_mobinsight(dumb_ptr<block_list> bl, dumb_ptr<mob_data> md)
}
/*==========================================
- *
- *------------------------------------------
- */
-int clif_skillinfo(dumb_ptr<map_session_data> sd, SkillID skillid, int type,
- int range)
-{
- nullpo_retz(sd);
-
- Session *s = sd->sess;
- if (!sd->status.skill[skillid].lv)
- return 0;
- Packet_Fixed<0x0147> fixed_147;
- fixed_147.info.skill_id = skillid;
- if (type < 0)
- fixed_147.info.type_or_inf = skill_get_inf(skillid);
- else
- fixed_147.info.type_or_inf = type;
- fixed_147.info.flags = SkillFlags::ZERO;
- fixed_147.info.level = sd->status.skill[skillid].lv;
- fixed_147.info.sp = skill_get_sp(skillid, sd->status.skill[skillid].lv);
- if (range < 0)
- {
- range = skill_get_range(skillid, sd->status.skill[skillid].lv);
- if (range < 0)
- range = battle_get_range(sd) - (range + 1);
- fixed_147.info.range = range;
- }
- else
- fixed_147.info.range = range;
- fixed_147.info.unused = ""_s;
- fixed_147.info.can_raise = sd->status.skill[skillid].lv < skill_get_max_raise(skillid);
- send_fpacket<0x0147, 39>(s, fixed_147);
-
- return 0;
-}
-
-/*==========================================
* スキルリストを送信する
*------------------------------------------
*/
@@ -2971,8 +2864,6 @@ int clif_party_info(PartyPair p, Session *s)
int i;
dumb_ptr<map_session_data> sd = nullptr;
- nullpo_retz(p);
-
Packet_Head<0x00fb> head_fb;
std::vector<Packet_Repeat<0x00fb>> repeat_fb;
head_fb.party_name = p->name;
@@ -3018,15 +2909,12 @@ int clif_party_info(PartyPair p, Session *s)
void clif_party_invite(dumb_ptr<map_session_data> sd,
dumb_ptr<map_session_data> tsd)
{
- PartyPair p;
-
nullpo_retv(sd);
nullpo_retv(tsd);
Session *s = tsd->sess;
- if (!(p = party_search(sd->status.party_id)))
- return;
+ PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return);
Packet_Fixed<0x00fe> fixed_fe;
fixed_fe.account_id = sd->status_key.account_id;
@@ -3068,8 +2956,6 @@ void clif_party_inviteack(dumb_ptr<map_session_data> sd, CharName nick, int flag
*/
void clif_party_option(PartyPair p, dumb_ptr<map_session_data> sd, int flag)
{
- nullpo_retv(p);
-
if (sd == nullptr && flag == 0)
{
int i;
@@ -3102,8 +2988,6 @@ void clif_party_leaved(PartyPair p, dumb_ptr<map_session_data> sd,
{
int i;
- nullpo_retv(p);
-
Packet_Fixed<0x0105> fixed_105;
fixed_105.account_id = account_id;
fixed_105.char_name = name;
@@ -3140,8 +3024,6 @@ void clif_party_message(PartyPair p, AccountId account_id, XString mes)
dumb_ptr<map_session_data> sd = nullptr;
int i;
- nullpo_retv(p);
-
for (i = 0; i < MAX_PARTY; i++)
{
sd = dumb_ptr<map_session_data>(p->member[i].sd);
@@ -3214,21 +3096,6 @@ int clif_movetoattack(dumb_ptr<map_session_data> sd, dumb_ptr<block_list> bl)
}
/*==========================================
- * MVPエフェクト
- *------------------------------------------
- */
-int clif_mvp_effect(dumb_ptr<map_session_data> sd)
-{
- nullpo_retz(sd);
-
- Packet_Fixed<0x010c> fixed_10c;
- fixed_10c.block_id = sd->bl_id;
- Buffer buf = create_fpacket<0x010c, 6>(fixed_10c);
- clif_send(buf, sd, SendWho::AREA);
- return 0;
-}
-
-/*==========================================
* エモーション
*------------------------------------------
*/
@@ -3243,7 +3110,6 @@ void clif_emotion(dumb_ptr<block_list> bl, int type)
clif_send(buf, bl, SendWho::AREA);
}
-static
void clif_emotion_towards(dumb_ptr<block_list> bl,
dumb_ptr<block_list> target, int type)
{
@@ -3442,9 +3308,9 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd)
// 119
// 78
- if (battle_config.player_invincible_time > 0)
+ if (battle_config.player_invincible_time > interval_t::zero())
{
- pc_setinvincibletimer(sd, static_cast<interval_t>(battle_config.player_invincible_time));
+ pc_setinvincibletimer(sd, battle_config.player_invincible_time);
}
map_addblock(sd); // ブロック登録
@@ -3467,7 +3333,6 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd)
std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2,
sd->bl_id));
sd->pvp_rank = 0;
- sd->pvp_lastusers = 0;
sd->pvp_point = 5;
}
}
@@ -3642,16 +3507,20 @@ RecvResult clif_parse_GetCharNameRequest(Session *s, dumb_ptr<map_session_data>
fixed_95.char_name = ssd->status_key.name;
send_fpacket<0x0095, 30>(s, fixed_95);
- PartyPair p;
-
PartyName party_name;
int send = 0;
- if (ssd->status.party_id && (p = party_search(ssd->status.party_id)))
+ if (ssd->status.party_id)
{
- party_name = p->name;
- send = 1;
+ Option<PartyPair> p_ = party_search(ssd->status.party_id);
+
+ OMATCH_BEGIN_SOME (p, p_)
+ {
+ party_name = p->name;
+ send = 1;
+ }
+ OMATCH_END ();
}
if (send)
@@ -3665,7 +3534,7 @@ RecvResult clif_parse_GetCharNameRequest(Session *s, dumb_ptr<map_session_data>
send_fpacket<0x0195, 102>(s, fixed_195);
}
- if (pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.hack_info_GM_level))))
+ if (pc_isGM(sd).satisfies(battle_config.hack_info_GM_level))
{
IP4Address ip = ssd->get_ip();
Packet_Fixed<0x020c> fixed_20c;
@@ -3853,25 +3722,6 @@ RecvResult clif_parse_Emotion(Session *s, dumb_ptr<map_session_data> sd)
*------------------------------------------
*/
static
-RecvResult clif_parse_HowManyConnections(Session *s, dumb_ptr<map_session_data>)
-{
- Packet_Fixed<0x00c1> fixed;
- RecvResult rv = recv_fpacket<0x00c1, 2>(s, fixed);
- if (rv != RecvResult::Complete)
- return rv;
-
- Packet_Fixed<0x00c2> fixed_c2;
- fixed_c2.users = map_getusers();
- send_fpacket<0x00c2, 6>(s, fixed_c2);
-
- return rv;
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
RecvResult clif_parse_ActionRequest(Session *s, dumb_ptr<map_session_data> sd)
{
Packet_Fixed<0x0089> fixed;
@@ -3904,7 +3754,7 @@ RecvResult clif_parse_ActionRequest(Session *s, dumb_ptr<map_session_data> sd)
{
case DamageType::NORMAL:
case DamageType::CONTINUOUS:
- if (bool(sd->status.option & Option::HIDE))
+ if (bool(sd->status.option & Opt0::HIDE))
return rv;
if (!battle_config.skill_delay_attack_enable)
{
@@ -4046,7 +3896,7 @@ RecvResult clif_parse_Wis(Session *s, dumb_ptr<map_session_data> sd)
if (dstsd->sess == s)
{
ZString mes = "You cannot page yourself."_s;
- clif_wis_message(s, wisp_server_name, mes);
+ clif_wis_message(s, WISP_SERVER_NAME, mes);
}
else
{
@@ -4199,15 +4049,16 @@ RecvResult clif_parse_EquipItem(Session *s, dumb_ptr<map_session_data> sd)
if (sd->npc_id)
return rv;
- if (sd->inventory_data[index])
+ OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[index])
{
EPOS epos = fixed.epos_ignored;
- if (sd->inventory_data[index]->type == ItemType::ARROW)
+ if (sdidi->type == ItemType::ARROW)
epos = EPOS::ARROW;
// Note: the EPOS argument to pc_equipitem is actually ignored
pc_equipitem(sd, index, epos);
}
+ OMATCH_END ();
return rv;
}
@@ -4816,6 +4667,60 @@ RecvResult clif_parse_PartyMessage(Session *s, dumb_ptr<map_session_data> sd)
return rv;
}
+void clif_sendallquest(dumb_ptr<map_session_data> sd)
+{
+ int i;
+ QuestId questid;
+ if (!sd)
+ return;
+
+ if (!sd->sess)
+ return;
+
+ Session *s = sd->sess;
+ Packet_Head<0x0215> head_215;
+ std::vector<Packet_Repeat<0x0215>> repeat_215;
+
+ assert (sd->status.global_reg_num < GLOBAL_REG_NUM);
+ for (QuestId q = wrap<QuestId>(0); q < wrap<QuestId>(-1); q = next(q))
+ {
+ P<struct quest_data> quest_data_ = TRY_UNWRAP(questdb_exists(q), continue);
+ for (i = 0; i < sd->status.global_reg_num; i++)
+ {
+ if (sd->status.global_reg[i].str == quest_data_->quest_vr)
+ {
+ int val = ((sd->status.global_reg[i].value & (((1 << quest_data_->quest_mask) - 1) << (quest_data_->quest_shift * quest_data_->quest_mask))) >> (quest_data_->quest_shift * quest_data_->quest_mask));
+ Packet_Repeat<0x0215> info;
+ info.variable = unwrap<QuestId>(quest_data_->questid);
+ info.value = val;
+ repeat_215.push_back(info);
+ break;
+ }
+ }
+ }
+
+ send_vpacket<0x0215, 4, 6>(s, head_215, repeat_215);
+ return;
+}
+
+void clif_sendquest(dumb_ptr<map_session_data> sd, QuestId questid, int value)
+{
+ if (!sd)
+ return;
+
+ if (!sd->sess)
+ return;
+
+ Session *s = sd->sess;
+
+ Packet_Fixed<0x0214> fixed;
+ fixed.variable = unwrap<QuestId>(questid);
+ fixed.value = value;
+ send_fpacket<0x0214, 8>(s, fixed);
+ return;
+}
+
+
func_table clif_parse_func_table[0x0220] =
{
{0, 10, nullptr, }, // 0x0000
@@ -5011,7 +4916,7 @@ func_table clif_parse_func_table[0x0220] =
{0, 5, nullptr, }, // 0x00be
{1000, 3, clif_parse_Emotion, }, // 0x00bf
{0, 7, nullptr, }, // 0x00c0
- {0, 2, clif_parse_HowManyConnections, }, // 0x00c1
+ {0, 2, nullptr, }, // 0x00c1
{0, 6, nullptr, }, // 0x00c2
{0, 8, nullptr, }, // 0x00c3
{0, 6, nullptr, }, // 0x00c4
@@ -5431,12 +5336,10 @@ uint16_t clif_check_packet_flood(Session *s, int cmd)
// They are flooding
if (tick < sd->flood_rates[cmd] + rate)
{
- TimeT now = TimeT::now();
-
// If it's a nasty flood we log and possibly kick
- if (now > sd->packet_flood_reset_due)
+ if (tick > sd->packet_flood_reset_due)
{
- sd->packet_flood_reset_due = static_cast<time_t>(now) + battle_config.packet_spam_threshold;
+ sd->packet_flood_reset_due = tick + battle_config.packet_spam_threshold;
sd->packet_flood_in = 0;
}
@@ -5638,7 +5541,6 @@ unknown_packet:
PRINTF("\nclif_parse: session #%d, packet 0x%x, lenght %zu\n"_fmt,
s, packet_id, packet_avail(s));
{
- ZString packet_txt = "save/packet.txt"_s;
if (sd && sd->state.auth)
{
PRINTF("Unknown packet: Account ID %d, character ID %d, player name %s.\n"_fmt,
@@ -5650,35 +5552,27 @@ unknown_packet:
else
PRINTF("Unknown packet (unknown)\n"_fmt);
- io::AppendFile fp(packet_txt);
- if (!fp.is_open())
- {
- PRINTF("clif.c: cant write [%s] !!! data is lost !!!\n"_fmt,
- packet_txt);
- return;
- }
- else
{
timestamp_seconds_buffer now;
stamp_time(now);
if (sd && sd->state.auth)
{
- FPRINTF(fp,
+ FPRINTF(stderr,
"%s\nPlayer with account ID %d (character ID %d, player name %s) sent wrong packet:\n"_fmt,
now,
sd->status_key.account_id,
sd->status_key.char_id, sd->status_key.name);
}
else if (sd) // not authentified! (refused by char-server or disconnect before to be authentified)
- FPRINTF(fp,
+ FPRINTF(stderr,
"%s\nUnauthenticated player with account ID %d sent wrong packet:\n"_fmt,
now, sd->bl_id);
else
- FPRINTF(fp,
+ FPRINTF(stderr,
"%s\nUnknown connection sent wrong packet:\n"_fmt,
now);
- packet_dump(fp, s);
+ packet_dump(s);
}
}
}
@@ -5687,6 +5581,7 @@ unknown_packet:
void do_init_clif(void)
{
- make_listen_port(map_port, SessionParsers{.func_parse= clif_parse, .func_delete= clif_delete});
+ make_listen_port(map_conf.map_port, SessionParsers{.func_parse= clif_parse, .func_delete= clif_delete});
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/clif.hpp b/src/map/clif.hpp
index adb4889..5117ff3 100644
--- a/src/map/clif.hpp
+++ b/src/map/clif.hpp
@@ -20,34 +20,26 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "fwd.hpp"
+#include "../mmo/clif.t.hpp"
-#include "clif.t.hpp"
+#include "fwd.hpp"
#include <functional>
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
+#include "../high/mmo.hpp"
#include "../net/timer.t.hpp"
-#include "../mmo/fwd.hpp"
-#include "../mmo/mmo.hpp"
-
#include "battle.t.hpp"
#include "map.t.hpp"
#include "pc.t.hpp"
-#include "skill.t.hpp"
+#include "../mmo/skill.t.hpp"
namespace tmwa
{
-void clif_setip(IP4Address);
-void clif_setport(int);
-
-IP4Address clif_getip(void);
-int clif_getport(void);
+namespace map
+{
int clif_countusers(void);
void clif_setwaitclose(Session *);
@@ -57,7 +49,6 @@ int clif_charselectok(BlockId);
int clif_dropflooritem(dumb_ptr<flooritem_data>);
int clif_clearflooritem(dumb_ptr<flooritem_data>, Session *);
int clif_clearchar(dumb_ptr<block_list>, BeingRemoveWhy); // area or fd
-int clif_clearchar_delay(tick_t, dumb_ptr<block_list>, BeingRemoveWhy);
void clif_clearchar_id(BlockId, BeingRemoveWhy, Session *);
int clif_spawnpc(dumb_ptr<map_session_data>); //area
int clif_spawnnpc(dumb_ptr<npc_data>); // area
@@ -87,11 +78,11 @@ void clif_delitem(dumb_ptr<map_session_data>, IOff0, int); //self
int clif_updatestatus(dumb_ptr<map_session_data>, SP); //self
int clif_damage(dumb_ptr<block_list>, dumb_ptr<block_list>,
tick_t, interval_t, interval_t,
- int, int, DamageType, int); // area
+ int, int, DamageType); // area
inline
int clif_takeitem(dumb_ptr<block_list> src, dumb_ptr<block_list> dst)
{
- return clif_damage(src, dst, tick_t(), interval_t::zero(), interval_t::zero(), 0, 0, DamageType::TAKEITEM, 0);
+ return clif_damage(src, dst, tick_t(), interval_t::zero(), interval_t::zero(), 0, 0, DamageType::TAKEITEM);
}
int clif_changelook(dumb_ptr<block_list>, LOOK, int); // area
void clif_changelook_accessories(dumb_ptr<block_list> bl, dumb_ptr<map_session_data> dst); // area or target; list gloves, boots etc.
@@ -105,6 +96,7 @@ int clif_changeoption(dumb_ptr<block_list>); // area
int clif_useitemack(dumb_ptr<map_session_data>, IOff0, int, int); // self
void clif_emotion(dumb_ptr<block_list> bl, int type);
+void clif_emotion_towards(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, int type);
void clif_sitting(Session *, dumb_ptr<map_session_data> sd);
// trade
@@ -119,12 +111,12 @@ int clif_tradecancelled(dumb_ptr<map_session_data> sd);
int clif_tradecompleted(dumb_ptr<map_session_data> sd, int fail);
// storage
-int clif_storageitemlist(dumb_ptr<map_session_data> sd, Storage *stor);
+int clif_storageitemlist(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor);
int clif_storageequiplist(dumb_ptr<map_session_data> sd,
- Storage *stor);
+ Borrowed<Storage> stor);
int clif_updatestorageamount(dumb_ptr<map_session_data> sd,
- Storage *stor);
-int clif_storageitemadded(dumb_ptr<map_session_data> sd, Storage *stor,
+ Borrowed<Storage> stor);
+int clif_storageitemadded(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor,
SOff0 index, int amount);
int clif_storageitemremoved(dumb_ptr<map_session_data> sd, SOff0 index,
int amount);
@@ -136,8 +128,6 @@ void clif_pcoutsight(dumb_ptr<block_list>, dumb_ptr<map_session_data>);
void clif_mobinsight(dumb_ptr<block_list>, dumb_ptr<mob_data>);
void clif_moboutsight(dumb_ptr<block_list>, dumb_ptr<mob_data>);
-int clif_skillinfo(dumb_ptr<map_session_data> sd, SkillID skillid, int type,
- int range);
void clif_skillinfoblock(dumb_ptr<map_session_data> sd);
int clif_skillup(dumb_ptr<map_session_data> sd, SkillID skill_num);
@@ -157,8 +147,6 @@ void clif_wis_end(Session *s, int flag);
void clif_itemlist(dumb_ptr<map_session_data> sd);
void clif_equiplist(dumb_ptr<map_session_data> sd);
-int clif_mvp_effect(dumb_ptr<map_session_data> sd);
-
int clif_movetoattack(dumb_ptr<map_session_data> sd, dumb_ptr<block_list> bl);
// party
@@ -187,6 +175,10 @@ int clif_GM_kick(dumb_ptr<map_session_data> sd, dumb_ptr<map_session_data> tsd,
int type);
int clif_foreachclient(std::function<void(dumb_ptr<map_session_data>)>);
+// quest
+void clif_sendallquest(dumb_ptr<map_session_data> sd);
+void clif_sendquest(dumb_ptr<map_session_data> sd, QuestId questid, int value);
void do_init_clif(void);
+} // namespace map
} // namespace tmwa
diff --git a/src/generic/intern-pool.cpp b/src/map/consts.hpp
index f45b098..a68d8e3 100644
--- a/src/generic/intern-pool.cpp
+++ b/src/map/consts.hpp
@@ -1,5 +1,5 @@
-#include "intern-pool.hpp"
-// intern-pool.cpp - Cached integer/string lookups.
+#pragma once
+// consts.hpp - Constants for tmwa-map.
//
// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
@@ -18,9 +18,17 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "fwd.hpp"
+
+#include "../ints/udl.hpp"
+
+#include "../mmo/ids.hpp"
namespace tmwa
{
+namespace map
+{
+constexpr BlockId MAX_FLOORITEM = wrap<BlockId>(500000_u32);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp
index 79bbbcd..911d566 100644
--- a/src/map/fwd.hpp
+++ b/src/map/fwd.hpp
@@ -20,11 +20,38 @@
#include "../sanity.hpp"
+#include <cstdint>
+
+#include "../ints/fwd.hpp" // rank 1
+#include "../range/fwd.hpp" // rank 1
+#include "../strings/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp" // rank 2
+#include "../generic/fwd.hpp" // rank 3
+#include "../io/fwd.hpp" // rank 4
+#include "../net/fwd.hpp" // rank 5
+#include "../sexpr/fwd.hpp" // rank 5
+#include "../mmo/fwd.hpp" // rank 6
+#include "../proto2/fwd.hpp" // rank 8
+#include "../high/fwd.hpp" // rank 9
+#include "../wire/fwd.hpp" // rank 9
+#include "../ast/fwd.hpp" // rank 10
+// map/fwd.hpp is rank ∞ because it is an executable
+
namespace tmwa
{
+namespace map
+{
// meh, add more when I feel like it
-class BlockId;
+struct BattleConf;
+struct MapConf;
+
+struct charid2nick;
+struct map_abstract;
+struct mob_db_;
+struct skill_db_;
+struct event_data;
+
struct block_list;
struct map_session_data;
struct npc_data;
@@ -36,9 +63,13 @@ class npc_data_script;
class npc_data_shop;
class npc_data_warp;
class npc_data_message;
-struct NpcEvent;
struct item_data;
+struct quest_data;
+
+struct ScriptState;
+struct str_data_t;
+class SIR;
namespace magic
{
@@ -55,5 +86,7 @@ struct env_t;
struct magic_conf_t;
struct component_t;
struct effect_set_t;
+struct proc_t;
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/globals.cpp b/src/map/globals.cpp
new file mode 100644
index 0000000..dce3906
--- /dev/null
+++ b/src/map/globals.cpp
@@ -0,0 +1,149 @@
+#include "globals.hpp"
+// globals.cpp - Evil global variables for tmwa-map.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../generic/intern-pool.hpp"
+
+#include "../io/write.hpp"
+
+#include "../proto2/net-Storage.hpp"
+
+#include "battle_conf.hpp"
+#include "itemdb.hpp"
+#include "quest.hpp"
+#include "magic-interpreter.hpp"
+#include "map_conf.hpp"
+#include "mob.hpp"
+#include "npc-internal.hpp"
+#include "script-parse-internal.hpp"
+#include "skill.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+ namespace map
+ {
+ BattleConf battle_config;
+ MapConf map_conf;
+
+ // only used by intif.cpp
+ // and clif.cpp for the new on_delete stuff ...
+ Session *char_session;
+ int chrif_state;
+ std::map<MapName, RString> resnametable;
+ Map<ItemNameId, item_data> item_db;
+ Map<QuestId, quest_data> quest_db;
+ namespace magic
+ {
+ // Global magic conf
+ magic_conf_t magic_conf;
+ env_t magic_default_env = { &magic_conf, nullptr };
+ namespace magic_v2
+ {
+ std::map<RString, proc_t> procs;
+ std::map<RString, val_t> const_defm;
+ } // namespace magic_v2
+ } // namespace magic
+
+ DMap<BlockId, dumb_ptr<block_list>> id_db;
+ UPMap<MapName, map_abstract> maps_db;
+ DMap<CharName, dumb_ptr<map_session_data>> nick_db;
+ Map<CharId, charid2nick> charid_db;
+ int world_user_count = 0;
+ Array<dumb_ptr<block_list>, unwrap<BlockId>(MAX_FLOORITEM)> object;
+ BlockId first_free_object_id = BlockId();
+ int save_settings = 0xFFFF;
+ int block_free_lock = 0;
+ std::vector<dumb_ptr<block_list>> block_free;
+ /// This is a dummy entry that is shared by all the linked lists,
+ /// so that any entry can unlink itself without worrying about
+ /// whether it was the the head of the list.
+ block_list bl_head;
+ std::unique_ptr<io::AppendFile> map_logfile;
+ long map_logfile_index;
+ mob_db_ mob_db[2001];
+ std::list<AString> npc_srcs;
+ int npc_warp, npc_shop, npc_script, npc_mob;
+ BlockId npc_id = START_NPC_NUM;
+ Map<NpcEvent, struct event_data> ev_db;
+ DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
+ // used for clock-based event triggers
+ // only tm_min, tm_hour, and tm_mday are used
+ tm ev_tm_b =
+ {
+ .tm_sec= 0,
+ .tm_min= -1,
+ .tm_hour= -1,
+ .tm_mday= -1,
+ .tm_mon= 0,
+ .tm_year= 0,
+ .tm_wday= 0,
+ .tm_yday= 0,
+ .tm_isdst= 0,
+ };
+ Map<PartyId, PartyMost> party_db;
+ std::map<AccountId, GmLevel> gm_accountm;
+ tick_t natural_heal_tick, natural_heal_prev_tick;
+ interval_t natural_heal_diff_tick;
+ int last_save_fd;
+ bool save_flag;
+ Map<AccountId, Storage> storage_db;
+
+ Map<RString, str_data_t> str_datam;
+ str_data_t LABEL_NEXTLINE_;
+ Map<ScriptLabel, int> scriptlabel_db;
+ std::set<ScriptLabel> probable_labels;
+ UPMap<RString, const ScriptBuffer> userfunc_db;
+ int parse_cmd_if = 0;
+ Option<Borrowed<str_data_t>> parse_cmdp = None;
+ InternPool variable_names;
+ // TODO: replace this whole mess with some sort of input stream that works
+ // a line at a time.
+ ZString startptr;
+ int startline;
+ int script_errors = 0;
+ DMap<SIR, int> mapreg_db;
+ Map<SIR, RString> mapregstr_db;
+ int mapreg_dirty = -1;
+
+ std::vector<SkillID> skill_pool_skills;
+ earray<skill_db_, SkillID, SkillID::MAX_SKILL_DB> skill_db;
+ // these variables are set in the 'else' branches,
+ // and used in the (recursive) 'if' branch
+ // TODO kill it, kill it with fire.
+ BlockId skill_area_temp_id;
+ int skill_area_temp_hp;
+
+ // Some other globals are not moved here, because they are
+ // large and initialized in-place and then *mostly* unmodified.
+ //
+ // src/map/atcommand.cpp:
+ // Map<XString, AtCommandInfo> atcommand_info;
+ // src/map/script-fun.cpp:
+ // BuiltinFunction builtin_functions[];
+ // src/map/clif.cpp:
+ // func_table clif_parse_func_table[0x0220];
+ // src/map/magic-expr.cpp:
+ // std::map<ZString, fun_t> functions;
+ // src/map/magic-stmt.cpp:
+ // std::map<ZString, op_t> operations;
+ } // namespace map
+} // namespace tmwa
diff --git a/src/map/globals.hpp b/src/map/globals.hpp
new file mode 100644
index 0000000..b457b4e
--- /dev/null
+++ b/src/map/globals.hpp
@@ -0,0 +1,109 @@
+#pragma once
+// globals.hpp - Evil global variables for tmwa-map.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include <ctime>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include "../ints/wrap.hpp"
+
+#include "../net/timer.t.hpp"
+
+#include "../mmo/skill.t.hpp"
+
+#include "consts.hpp"
+#include "script-buffer.hpp"
+
+
+namespace tmwa
+{
+ namespace map
+ {
+ extern BattleConf battle_config;
+ extern MapConf map_conf;
+ extern Session *char_session;
+ extern int chrif_state;
+ extern std::map<MapName, RString> resnametable;
+ extern Map<ItemNameId, item_data> item_db;
+ extern Map<QuestId, quest_data> quest_db;
+ namespace magic
+ {
+ // Global magic conf
+ extern magic_conf_t magic_conf;
+ extern env_t magic_default_env;
+ namespace magic_v2
+ {
+ extern std::map<RString, proc_t> procs;
+ extern std::map<RString, val_t> const_defm;
+ } // namespace magic_v2
+ } // namespace magic
+ extern DMap<BlockId, dumb_ptr<block_list>> id_db;
+ extern UPMap<MapName, map_abstract> maps_db;
+ extern DMap<CharName, dumb_ptr<map_session_data>> nick_db;
+ extern Map<CharId, charid2nick> charid_db;
+ extern int world_user_count;
+ extern Array<dumb_ptr<block_list>, unwrap<BlockId>(MAX_FLOORITEM)> object;
+ extern BlockId first_free_object_id;
+ extern int save_settings;
+ extern int block_free_lock;
+ extern std::vector<dumb_ptr<block_list>> block_free;
+ extern block_list bl_head;
+ extern std::unique_ptr<io::AppendFile> map_logfile;
+ extern long map_logfile_index;
+ extern mob_db_ mob_db[2001];
+ extern std::list<AString> npc_srcs;
+ extern int npc_warp, npc_shop, npc_script, npc_mob;
+ extern BlockId npc_id;
+ extern Map<NpcEvent, event_data> ev_db;
+ extern DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
+ extern tm ev_tm_b;
+ extern Map<PartyId, PartyMost> party_db;
+ extern std::map<AccountId, GmLevel> gm_accountm;
+ extern tick_t natural_heal_tick, natural_heal_prev_tick;
+ extern interval_t natural_heal_diff_tick;
+ extern int last_save_fd;
+ extern bool save_flag;
+ extern Map<AccountId, Storage> storage_db;
+ extern Map<RString, str_data_t> str_datam;
+ extern str_data_t LABEL_NEXTLINE_;
+ extern Map<ScriptLabel, int> scriptlabel_db;
+ extern std::set<ScriptLabel> probable_labels;
+ extern UPMap<RString, const ScriptBuffer> userfunc_db;
+ extern int parse_cmd_if;
+ extern Option<Borrowed<str_data_t>> parse_cmdp;
+ extern InternPool variable_names;
+ extern ZString startptr;
+ extern int startline;
+ extern int script_errors;
+ extern DMap<SIR, int> mapreg_db;
+ extern Map<SIR, RString> mapregstr_db;
+ extern int mapreg_dirty;
+ extern std::vector<SkillID> skill_pool_skills;
+ extern earray<skill_db_, SkillID, SkillID::MAX_SKILL_DB> skill_db;
+ extern BlockId skill_area_temp_id;
+ extern int skill_area_temp_hp;
+ } // namespace map
+} // namespace tmwa
diff --git a/src/map/grfio.cpp b/src/map/grfio.cpp
index 4a1656b..3475108 100644
--- a/src/map/grfio.cpp
+++ b/src/map/grfio.cpp
@@ -33,19 +33,21 @@
#include "../strings/zstring.hpp"
#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
#include "../io/read.hpp"
-#include "../mmo/extract.hpp"
-#include "../mmo/mmo.hpp"
+#include "../high/extract_mmo.hpp"
+#include "../high/mmo.hpp"
+
+#include "globals.hpp"
#include "../poison.hpp"
namespace tmwa
{
-static
-std::map<MapName, RString> resnametable;
-
+namespace map
+{
bool load_resnametable(ZString filename)
{
io::ReadFile in(filename);
@@ -106,4 +108,5 @@ std::vector<uint8_t> grfio_reads(MapName rname)
close(fd);
return buffer;
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/grfio.hpp b/src/map/grfio.hpp
index d2ab058..25e27ef 100644
--- a/src/map/grfio.hpp
+++ b/src/map/grfio.hpp
@@ -26,17 +26,16 @@
#include <vector>
-#include "../strings/fwd.hpp"
-
-#include "../mmo/fwd.hpp"
-
namespace tmwa
{
+namespace map
+{
bool load_resnametable(ZString filename);
/// Load a resource into memory, subject to data/resnametable.txt.
/// Normally, resourcename is xxx-y.gat and the file is xxx-y.wlk.
/// Currently there is exactly one .wlk per .gat, but multiples are fine.
std::vector<uint8_t> grfio_reads(MapName resourcename);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/intif.cpp b/src/map/intif.cpp
index 314db24..a5709ef 100644
--- a/src/map/intif.cpp
+++ b/src/map/intif.cpp
@@ -29,16 +29,19 @@
#include "../io/cxxstdio.hpp"
-#include "../net/packets.hpp"
#include "../net/socket.hpp"
-#include "../mmo/mmo.hpp"
+#include "../high/mmo.hpp"
#include "../proto2/char-map.hpp"
+#include "../wire/packets.hpp"
+
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "chrif.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "map.hpp"
#include "party.hpp"
#include "pc.hpp"
@@ -49,6 +52,8 @@
namespace tmwa
{
+namespace map
+{
//-----------------------------------------------------------------
// inter serverへの送信
@@ -156,9 +161,8 @@ void intif_request_storage(AccountId account_id)
}
// 倉庫データ送信
-void intif_send_storage(Storage *stor)
+void intif_send_storage(Borrowed<Storage> stor)
{
- nullpo_retv(stor);
if (!char_session)
return;
@@ -386,7 +390,6 @@ int intif_parse_AccountReg(Session *, const Packet_Head<0x3804>& head, const std
static
int intif_parse_LoadStorage(Session *, const Packet_Payload<0x3810>& payload)
{
- Storage *stor;
dumb_ptr<map_session_data> sd;
sd = map_id2sd(account_to_block(payload.account_id));
@@ -397,7 +400,7 @@ int intif_parse_LoadStorage(Session *, const Packet_Payload<0x3810>& payload)
payload.account_id);
return 1;
}
- stor = account2storage(payload.account_id);
+ P<Storage> stor = account2storage(payload.account_id);
if (stor->storage_status == 1)
{ // Already open.. lets ignore this update
if (battle_config.error_log)
@@ -463,9 +466,7 @@ void intif_parse_PartyInfo(Session *, const Packet_Head<0x3821>& head, bool has_
PartyId pi = head.party_id;
PartyMost pm = option.party_most;
- PartyPair pp;
- pp.party_id = pi;
- pp.party_most = &pm;
+ PartyPair pp{pi, borrow(pm)};
party_recv_info(pp);
}
@@ -697,4 +698,5 @@ RecvResult intif_parse(Session *s, uint16_t packet_id)
}
return rv;
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/intif.hpp b/src/map/intif.hpp
index 5be61a9..ac68040 100644
--- a/src/map/intif.hpp
+++ b/src/map/intif.hpp
@@ -22,17 +22,11 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
-
-#include "../net/fwd.hpp"
-
-#include "../mmo/fwd.hpp"
-
namespace tmwa
{
+namespace map
+{
RecvResult intif_parse(Session *, uint16_t packet_id);
void intif_GMmessage(XString mes);
@@ -44,7 +38,7 @@ void intif_saveaccountreg(dumb_ptr<map_session_data> sd);
void intif_request_accountreg(dumb_ptr<map_session_data> sd);
void intif_request_storage(AccountId account_id);
-void intif_send_storage(Storage *stor);
+void intif_send_storage(Borrowed<Storage> stor);
void intif_create_party(dumb_ptr<map_session_data> sd, PartyName name);
void intif_request_partyinfo(PartyId party_id);
@@ -55,4 +49,5 @@ void intif_party_leave(PartyId party_id, AccountId accound_id);
void intif_party_changemap(dumb_ptr<map_session_data> sd, int online);
void intif_party_message(PartyId party_id, AccountId account_id, XString mes);
void intif_party_checkconflict(PartyId party_id, AccountId account_id, CharName nick);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp
index 50cc5c4..7dd725e 100644
--- a/src/map/itemdb.cpp
+++ b/src/map/itemdb.cpp
@@ -29,20 +29,24 @@
#include "../generic/db.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/read.hpp"
+#include "../io/extract.hpp"
+#include "../io/line.hpp"
#include "../mmo/config_parse.hpp"
-#include "../mmo/extract.hpp"
#include "../mmo/extract_enums.hpp"
+#include "../ast/item.hpp"
+
+#include "globals.hpp"
+#include "script-parse.hpp"
+
#include "../poison.hpp"
namespace tmwa
{
-static
-Map<ItemNameId, struct item_data> item_db;
-
+namespace map
+{
// Function declarations
/*==========================================
@@ -51,24 +55,24 @@ Map<ItemNameId, struct item_data> item_db;
*/
// name = item alias, so we should find items aliases first. if not found then look for "jname" (full name)
static
-void itemdb_searchname_sub(struct item_data *item, ItemName str, struct item_data **dst)
+void itemdb_searchname_sub(Borrowed<struct item_data> item, ItemName str, Borrowed<Option<Borrowed<struct item_data>>> dst)
{
if (item->name == str)
- *dst = item;
+ *dst = Some(item);
}
/*==========================================
* 名前で検索
*------------------------------------------
*/
-struct item_data *itemdb_searchname(XString str_)
+Option<Borrowed<struct item_data>> itemdb_searchname(XString str_)
{
ItemName str = stringish<ItemName>(str_);
if (XString(str) != str_)
- return nullptr;
- struct item_data *item = nullptr;
+ return None;
+ Option<P<struct item_data>> item = None;
for (auto& pair : item_db)
- itemdb_searchname_sub(&pair.second, str, &item);
+ itemdb_searchname_sub(borrow(pair.second), str, borrow(item));
return item;
}
@@ -76,7 +80,7 @@ struct item_data *itemdb_searchname(XString str_)
* DBの存在確認
*------------------------------------------
*/
-struct item_data *itemdb_exists(ItemNameId nameid)
+Option<Borrowed<struct item_data>> itemdb_exists(ItemNameId nameid)
{
return item_db.search(nameid);
}
@@ -85,13 +89,16 @@ struct item_data *itemdb_exists(ItemNameId nameid)
* DBの検索
*------------------------------------------
*/
-struct item_data *itemdb_search(ItemNameId nameid)
+Borrowed<struct item_data> itemdb_search(ItemNameId nameid)
{
- struct item_data *id = item_db.search(nameid);
- if (id)
+ Option<P<struct item_data>> id_ = item_db.search(nameid);
+ OMATCH_BEGIN_SOME (id, id_)
+ {
return id;
+ }
+ OMATCH_END ();
- id = item_db.init(nameid);
+ P<struct item_data> id = item_db.init(nameid);
id->nameid = nameid;
id->value_buy = 10;
@@ -123,10 +130,8 @@ int itemdb_isequip(ItemNameId nameid)
*
*------------------------------------------
*/
-int itemdb_isequip2(struct item_data *data)
+bool itemdb_isequip2(Borrowed<struct item_data> data)
{
- if (!data)
- return false;
ItemType type = data->type;
return !(type == ItemType::USE
|| type == ItemType::_2
@@ -149,99 +154,64 @@ int itemdb_isequip3(ItemNameId nameid)
bool itemdb_readdb(ZString filename)
{
- bool rv = true;
-
- int ln = 0, lines = 0;
+ io::LineCharReader in(filename);
+ if (!in.is_open())
{
- io::ReadFile in(filename);
-
- if (!in.is_open())
- {
- PRINTF("can't read %s\n"_fmt, filename);
- return false;
- }
+ PRINTF("can't read %s\n"_fmt, filename);
+ return false;
+ }
- lines = 0;
+ int ln = 0;
- AString line;
- while (in.getline(line))
+ while (true)
+ {
+ auto res = TRY_UNWRAP(ast::item::parse_item(in),
+ {
+ PRINTF("read %s done (count=%d)\n"_fmt, filename, ln);
+ return true;
+ });
+ if (res.get_failure())
+ PRINTF("%s\n"_fmt, res.get_failure());
+ ast::item::ItemOrComment ioc = TRY_UNWRAP(std::move(res.get_success()), return false);
+
+ MATCH_BEGIN (ioc)
{
- lines++;
- if (is_comment(line))
- continue;
- // a line is 17 normal fields followed by 2 {} fields
- // the fields are separated by ", *", but there may be ,
- // in the {}.
-
- auto it = std::find(line.begin(), line.end(), '{');
- XString main_part = line.xislice_h(it).rstrip();
- // According to the code, tail_part may be empty. See later.
- ZString tail_part = line.xislice_t(it);
-
- XString unused_slot_count;
- item_data idv {};
- if (!extract(
- main_part, record<','>(
- &idv.nameid,
- lstripping(&idv.name),
- lstripping(&idv.jname),
- lstripping(&idv.type),
- lstripping(&idv.value_buy),
- lstripping(&idv.value_sell),
- lstripping(&idv.weight),
- lstripping(&idv.atk),
- lstripping(&idv.def),
- lstripping(&idv.range),
- lstripping(&idv.magic_bonus),
- lstripping(&unused_slot_count),
- lstripping(&idv.sex),
- lstripping(&idv.equip),
- lstripping(&idv.wlv),
- lstripping(&idv.elv),
- lstripping(&idv.look)
- )
- )
- )
- {
- PRINTF("%s:%d: error: bad item line: %s\n"_fmt,
- filename, lines, line);
- rv = false;
- continue;
- }
-
- ln++;
-
- struct item_data *id = itemdb_search(idv.nameid);
- *id = std::move(idv);
- if (id->value_buy == 0 && id->value_sell == 0)
+ MATCH_CASE (const ast::item::Comment&, c)
{
+ (void)c;
}
- else if (id->value_buy == 0)
+ MATCH_CASE (const ast::item::Item&, item)
{
- id->value_buy = id->value_sell * 2;
+ ln++;
+
+ item_data idv {};
+ idv.nameid = item.id.data;
+ idv.name = item.name.data;
+ idv.jname = item.jname.data;
+ idv.type = item.type.data;
+ idv.value_buy = item.buy_price.data ?: item.sell_price.data * 2;
+ idv.value_sell = item.sell_price.data ?: item.buy_price.data / 2;
+ idv.weight = item.weight.data;
+ idv.atk = item.atk.data;
+ idv.def = item.def.data;
+ idv.range = item.range.data;
+ idv.magic_bonus = item.magic_bonus.data;
+ idv.sex = item.gender.data;
+ idv.equip = item.loc.data;
+ idv.wlv = item.wlv.data;
+ idv.elv = item.elv.data;
+ idv.look = item.view.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);
+
+ Borrowed<struct item_data> id = itemdb_search(idv.nameid);
+ *id = std::move(idv);
}
- else if (id->value_sell == 0)
- {
- id->value_sell = id->value_buy / 2;
- }
-
- id->use_script = nullptr;
- id->equip_script = nullptr;
-
- if (!tail_part)
- continue;
- id->use_script = parse_script(tail_part, lines, true);
-
- tail_part = tail_part.xislice_t(std::find(tail_part.begin() + 1, tail_part.end(), '{'));
- if (!tail_part)
- continue;
- id->equip_script = parse_script(tail_part, lines, true);
}
- PRINTF("read %s done (count=%d)\n"_fmt, filename, ln);
+ MATCH_END ();
}
-
- return rv;
}
/*==========================================
@@ -265,4 +235,5 @@ void do_final_itemdb(void)
itemdb_final(&pair.second);
item_db.clear();
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp
index 25b4dad..5e19c0b 100644
--- a/src/map/itemdb.hpp
+++ b/src/map/itemdb.hpp
@@ -23,14 +23,16 @@
#include "fwd.hpp"
#include "../mmo/ids.hpp"
-#include "../mmo/mmo.hpp"
+#include "../high/mmo.hpp"
#include "map.t.hpp"
-#include "script.hpp"
+#include "script-buffer.hpp"
namespace tmwa
{
+namespace map
+{
struct item_data
{
ItemNameId nameid;
@@ -59,11 +61,11 @@ struct random_item_data
};
inline
-struct item_data *itemdb_searchname(ItemName) = delete;
-struct item_data *itemdb_searchname(XString name);
+Option<Borrowed<struct item_data>> itemdb_searchname(ItemName) = delete;
+Option<Borrowed<struct item_data>> itemdb_searchname(XString name);
// TODO this function should die
-struct item_data *itemdb_search(ItemNameId nameid);
-struct item_data *itemdb_exists(ItemNameId nameid);
+Borrowed<struct item_data> itemdb_search(ItemNameId nameid);
+Option<Borrowed<struct item_data>> itemdb_exists(ItemNameId nameid);
inline
ItemType itemdb_type(ItemNameId n)
@@ -97,11 +99,12 @@ int itemdb_value_sell(ItemNameId n)
}
int itemdb_isequip(ItemNameId);
-int itemdb_isequip2(struct item_data *);
+bool itemdb_isequip2(Borrowed<struct item_data>);
int itemdb_isequip3(ItemNameId);
void itemdb_reload(void);
void do_final_itemdb(void);
bool itemdb_readdb(ZString filename);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-expr-eval.hpp b/src/map/magic-expr-eval.hpp
index 4529c04..e8ed4aa 100644
--- a/src/map/magic-expr-eval.hpp
+++ b/src/map/magic-expr-eval.hpp
@@ -28,6 +28,8 @@
namespace tmwa
{
+namespace map
+{
namespace magic
{
// TODO soon kill this unlike I killed VAR
@@ -48,4 +50,5 @@ namespace magic
#define ARG_MAY_BE_AREA(x) (args[x].is<ValArea>() || args[x].is<ValArea>())
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp
index 674c850..197727e 100644
--- a/src/map/magic-expr.cpp
+++ b/src/map/magic-expr.cpp
@@ -33,7 +33,8 @@
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+
+#include "../mmo/cxxstdio_enums.hpp"
#include "battle.hpp"
#include "itemdb.hpp"
@@ -42,12 +43,15 @@
#include "magic-interpreter-base.hpp"
#include "npc.hpp"
#include "pc.hpp"
+#include "script-call.hpp"
#include "../poison.hpp"
namespace tmwa
{
+namespace map
+{
namespace magic
{
static
@@ -56,14 +60,15 @@ void free_area(dumb_ptr<area_t> area)
if (!area)
return;
- MATCH (*area)
+ MATCH_BEGIN (*area)
{
- CASE (const AreaUnion&, a)
+ MATCH_CASE (const AreaUnion&, a)
{
free_area(a.a_union[0]);
free_area(a.a_union[1]);
}
}
+ MATCH_END ();
area.delete_();
}
@@ -71,109 +76,111 @@ void free_area(dumb_ptr<area_t> area)
static
dumb_ptr<area_t> dup_area(dumb_ptr<area_t> area)
{
- MATCH (*area)
+ MATCH_BEGIN (*area)
{
- CASE (const location_t&, loc)
+ MATCH_CASE (const location_t&, loc)
{
return dumb_ptr<area_t>::make(loc);
}
- CASE (const AreaUnion&, a)
+ MATCH_CASE (const AreaUnion&, a)
{
AreaUnion u;
u.a_union[0] = dup_area(a.a_union[0]);
u.a_union[1] = dup_area(a.a_union[1]);
return dumb_ptr<area_t>::make(u);
}
- CASE (const AreaRect&, rect)
+ MATCH_CASE (const AreaRect&, rect)
{
return dumb_ptr<area_t>::make(rect);
}
- CASE (const AreaBar&, bar)
+ MATCH_CASE (const AreaBar&, bar)
{
return dumb_ptr<area_t>::make(bar);
}
}
+ MATCH_END ();
abort();
}
void magic_copy_var(val_t *dest, const val_t *src)
{
- MATCH (*src)
+ MATCH_BEGIN (*src)
{
- // mumble mumble not a public API ...
- default:
+ MATCH_DEFAULT ()
{
abort();
}
- CASE (const ValUndef&, s)
+ MATCH_CASE (const ValUndef&, s)
{
*dest = s;
}
- CASE (const ValInt&, s)
+ MATCH_CASE (const ValInt&, s)
{
*dest = s;
}
- CASE (const ValDir&, s)
+ MATCH_CASE (const ValDir&, s)
{
*dest = s;
}
- CASE (const ValString&, s)
+ MATCH_CASE (const ValString&, s)
{
*dest = ValString{s.v_string};
}
- CASE (const ValEntityInt&, s)
+ MATCH_CASE (const ValEntityInt&, s)
{
*dest = s;
}
- CASE (const ValEntityPtr&, s)
+ MATCH_CASE (const ValEntityPtr&, s)
{
*dest = s;
}
- CASE (const ValLocation&, s)
+ MATCH_CASE (const ValLocation&, s)
{
*dest = s;
}
- CASE (const ValArea&, s)
+ MATCH_CASE (const ValArea&, s)
{
*dest = ValArea{dup_area(s.v_area)};
}
- CASE (const ValSpell&, s)
+ MATCH_CASE (const ValSpell&, s)
{
*dest = s;
}
- CASE (const ValInvocationInt&, s)
+ MATCH_CASE (const ValInvocationInt&, s)
{
*dest = s;
}
- CASE (const ValInvocationPtr&, s)
+ MATCH_CASE (const ValInvocationPtr&, s)
{
*dest = s;
}
- CASE (const ValFail&, s)
+ MATCH_CASE (const ValFail&, s)
{
*dest = s;
}
- CASE (const ValNegative1&, s)
+ MATCH_CASE (const ValNegative1&, s)
{
*dest = s;
}
}
+ MATCH_END ();
}
void magic_clear_var(val_t *v)
{
- MATCH (*v)
+ MATCH_BEGIN (*v)
{
- CASE (ValString&, s)
+ MATCH_CASE (ValString&, s)
{
(void)s;
}
- CASE (const ValArea&, a)
+ MATCH_CASE (const ValArea&, a)
{
free_area(a.v_area);
}
}
+ MATCH_END ();
}
static
@@ -212,63 +219,64 @@ void stringify(val_t *v)
}};
AString buf;
- MATCH (*v)
+ MATCH_BEGIN (*v)
{
- default:
+ MATCH_DEFAULT ()
{
abort();
}
- CASE (const ValUndef&, x)
+ MATCH_CASE (const ValUndef&, x)
{
(void)x;
buf = "UNDEF"_s;
}
- CASE (const ValInt&, x)
+ MATCH_CASE (const ValInt&, x)
{
buf = STRPRINTF("%i"_fmt, x.v_int);
}
- CASE (const ValString&, x)
+ MATCH_CASE (const ValString&, x)
{
(void)x;
return;
}
- CASE (const ValDir&, x)
+ MATCH_CASE (const ValDir&, x)
{
buf = dirs[x.v_dir];
}
- CASE (const ValEntityPtr&, x)
+ MATCH_CASE (const ValEntityPtr&, x)
{
buf = show_entity(x.v_entity);
}
- CASE (const ValLocation&, x)
+ MATCH_CASE (const ValLocation&, x)
{
buf = STRPRINTF("<\"%s\", %d, %d>"_fmt,
x.v_location.m->name_,
x.v_location.x,
x.v_location.y);
}
- CASE (const ValArea&, x)
+ MATCH_CASE (const ValArea&, x)
{
buf = "%area"_s;
free_area(x.v_area);
}
- CASE (const ValSpell&, x)
+ MATCH_CASE (const ValSpell&, x)
{
buf = x.v_spell->name;
}
- CASE (const ValInvocationInt&, x)
+ MATCH_CASE (const ValInvocationInt&, x)
{
dumb_ptr<invocation> invocation_ =
map_id2bl(x.v_iid)->is_spell();
buf = invocation_->spell->name;
}
- CASE (const ValInvocationPtr&, x)
+ MATCH_CASE (const ValInvocationPtr&, x)
{
dumb_ptr<invocation> invocation_ =
x.v_invocation;
buf = invocation_->spell->name;
}
}
+ MATCH_END ();
*v = ValString{buf};
}
@@ -310,14 +318,15 @@ void make_location(val_t *v)
{
if (ValArea *a = v->get_if<ValArea>())
{
- MATCH (*a->v_area)
+ MATCH_BEGIN (*a->v_area)
{
- CASE (const location_t&, location)
+ MATCH_CASE (const location_t&, location)
{
free_area(a->v_area);
*v = ValLocation{location};
}
}
+ MATCH_END ();
}
}
@@ -582,39 +591,41 @@ int fun_if_then_else(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
return 0;
}
-void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height,
+Borrowed<map_local> magic_area_rect(int *x, int *y, int *width, int *height,
area_t& area_)
{
- MATCH (area_)
+ MATCH_BEGIN (area_)
{
- CASE (const AreaUnion&, a)
+ MATCH_CASE (const AreaUnion&, a)
{
(void)a;
abort();
}
- CASE (const location_t&, a_loc)
+ MATCH_CASE (const location_t&, a_loc)
{
- *m = a_loc.m;
+ P<map_local> m = a_loc.m;
*x = a_loc.x;
*y = a_loc.y;
*width = 1;
*height = 1;
+ return m;
}
- CASE (const AreaRect&, a_rect)
+ MATCH_CASE (const AreaRect&, a_rect)
{
- *m = a_rect.loc.m;
+ P<map_local> m = a_rect.loc.m;
*x = a_rect.loc.x;
*y = a_rect.loc.y;
*width = a_rect.width;
*height = a_rect.height;
+ return m;
}
- CASE (const AreaBar&, a_bar)
+ MATCH_CASE (const AreaBar&, a_bar)
{
int tx = a_bar.loc.x;
int ty = a_bar.loc.y;
int twidth = a_bar.width;
int tdepth = a_bar.width;
- *m = a_bar.loc.m;
+ P<map_local> m = a_bar.loc.m;
switch (a_bar.dir)
{
@@ -653,53 +664,54 @@ void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height,
*y = ty;
*width = *height = 1;
}
+ return m;
}
}
+ MATCH_END ();
+ abort();
}
-int magic_location_in_area(map_local *m, int x, int y, dumb_ptr<area_t> area)
+int magic_location_in_area(Borrowed<map_local> m, int x, int y, dumb_ptr<area_t> area)
{
- MATCH (*area)
+ MATCH_BEGIN (*area)
{
- CASE (const AreaUnion&, a)
+ MATCH_CASE (const AreaUnion&, a)
{
return magic_location_in_area(m, x, y, a.a_union[0])
|| magic_location_in_area(m, x, y, a.a_union[1]);
}
- CASE (const location_t&, a_loc)
+ MATCH_CASE (const location_t&, a_loc)
{
(void)a_loc;
// TODO this can be simplified
- map_local *am;
int ax, ay, awidth, aheight;
- magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area);
+ P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area);
return (am == m
&& (x >= ax) && (y >= ay)
&& (x < ax + awidth) && (y < ay + aheight));
}
- CASE (const AreaRect&, a_rect)
+ MATCH_CASE (const AreaRect&, a_rect)
{
(void)a_rect;
// TODO this is too complicated
- map_local *am;
int ax, ay, awidth, aheight;
- magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area);
+ P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area);
return (am == m
&& (x >= ax) && (y >= ay)
&& (x < ax + awidth) && (y < ay + aheight));
}
- CASE (const AreaBar&, a_bar)
+ MATCH_CASE (const AreaBar&, a_bar)
{
(void)a_bar;
// TODO this is wrong
- map_local *am;
int ax, ay, awidth, aheight;
- magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area);
+ P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area);
return (am == m
&& (x >= ax) && (y >= ay)
&& (x < ax + awidth) && (y < ay + aheight));
}
}
+ MATCH_END ();
abort();
}
@@ -872,18 +884,17 @@ int fun_hash_entity(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
// ret -1: not a string, ret 1: no such item, ret 0: OK
int magic_find_item(Slice<val_t> args, int index, Item *item_, int *stackable)
{
- struct item_data *item_data;
+ Option<P<struct item_data>> item_data_ = None;
int must_add_sequentially;
if (args[index].is<ValInt>())
- item_data = itemdb_exists(wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(index))));
+ item_data_ = itemdb_exists(wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(index))));
else if (args[index].is<ValString>())
- item_data = itemdb_searchname(ARGSTR(index));
+ item_data_ = itemdb_searchname(ARGSTR(index));
else
return -1;
- if (!item_data)
- return 1;
+ P<struct item_data> item_data = TRY_UNWRAP(item_data_, return 1);
// Very elegant.
must_add_sequentially = (
@@ -1082,22 +1093,21 @@ int fun_line_of_sight(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
void magic_random_location(location_t *dest, dumb_ptr<area_t> area)
{
- MATCH (*area)
+ MATCH_BEGIN (*area)
{
- CASE (const AreaUnion&, a)
+ MATCH_CASE (const AreaUnion&, a)
{
if (random_::chance({a.a_union[0]->size, area->size}))
magic_random_location(dest, a.a_union[0]);
else
magic_random_location(dest, a.a_union[1]);
}
- CASE (const location_t&, a_loc)
+ MATCH_CASE (const location_t&, a_loc)
{
(void)a_loc;
// TODO this can be simplified
- map_local *m;
int x, y, w, h;
- magic_area_rect(&m, &x, &y, &w, &h, *area);
+ P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area);
if (w <= 1)
w = 1;
@@ -1113,13 +1123,12 @@ void magic_random_location(location_t *dest, dumb_ptr<area_t> area)
dest->x = pair.first;
dest->y = pair.second;
}
- CASE (const AreaRect&, a_rect)
+ MATCH_CASE (const AreaRect&, a_rect)
{
(void)a_rect;
// TODO this can be simplified
- map_local *m;
int x, y, w, h;
- magic_area_rect(&m, &x, &y, &w, &h, *area);
+ P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area);
if (w <= 1)
w = 1;
@@ -1135,13 +1144,12 @@ void magic_random_location(location_t *dest, dumb_ptr<area_t> area)
dest->x = pair.first;
dest->y = pair.second;
}
- CASE (const AreaBar&, a_bar)
+ MATCH_CASE (const AreaBar&, a_bar)
{
(void)a_bar;
// TODO this is wrong
- map_local *m;
int x, y, w, h;
- magic_area_rect(&m, &x, &y, &w, &h, *area);
+ P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area);
if (w <= 1)
w = 1;
@@ -1158,6 +1166,7 @@ void magic_random_location(location_t *dest, dumb_ptr<area_t> area)
dest->y = pair.second;
}
}
+ MATCH_END ();
}
static
@@ -1228,7 +1237,7 @@ int fun_running_status_update(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
static
int fun_status_option(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
{
- *result = ValInt{(bool((ARGPC(0))->status.option & static_cast<Option>(ARGINT(1))))};
+ *result = ValInt{(bool((ARGPC(0))->status.option & static_cast<Opt0>(ARGINT(1))))};
return 0;
}
@@ -1511,10 +1520,8 @@ int eval_location(dumb_ptr<env_t> env, location_t *dest, const e_location_t *exp
&& x.is<ValInt>() && y.is<ValInt>())
{
MapName name = VString<15>(ZString(m.get_if<ValString>()->v_string));
- map_local *map_id = map_mapname2mapid(name);
magic_clear_var(&m);
- if (!map_id)
- return 1;
+ P<map_local> map_id = TRY_UNWRAP(map_mapname2mapid(name), return 1);
dest->m = map_id;
dest->x = x.get_if<ValInt>()->v_int;
dest->y = y.get_if<ValInt>()->v_int;
@@ -1532,9 +1539,9 @@ int eval_location(dumb_ptr<env_t> env, location_t *dest, const e_location_t *exp
static
dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_)
{
- MATCH (expr_)
+ MATCH_BEGIN (expr_)
{
- CASE (const e_location_t&, a_loc)
+ MATCH_CASE (const e_location_t&, a_loc)
{
location_t loc;
if (eval_location(env, &loc, &a_loc))
@@ -1546,7 +1553,7 @@ dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_)
return dumb_ptr<area_t>::make(loc);
}
}
- CASE (const ExprAreaUnion&, a)
+ MATCH_CASE (const ExprAreaUnion&, a)
{
AreaUnion u;
bool fail = false;
@@ -1568,7 +1575,7 @@ dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_)
}
return dumb_ptr<area_t>::make(u);
}
- CASE (const ExprAreaRect&, a_rect)
+ MATCH_CASE (const ExprAreaRect&, a_rect)
{
val_t width, height;
magic_eval(env, &width, a_rect.width);
@@ -1594,7 +1601,7 @@ dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_)
return nullptr;
}
}
- CASE (const ExprAreaBar&, a_bar)
+ MATCH_CASE (const ExprAreaBar&, a_bar)
{
val_t width, depth, dir;
magic_eval(env, &width, a_bar.width);
@@ -1626,6 +1633,7 @@ dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_)
}
}
}
+ MATCH_END ();
abort();
}
@@ -1744,14 +1752,14 @@ int magic_signature_check(ZString opname, ZString funname, ZString signature,
void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr)
{
- MATCH (*expr)
+ MATCH_BEGIN (*expr)
{
- CASE (const val_t&, e_val)
+ MATCH_CASE (const val_t&, e_val)
{
magic_copy_var(dest, &e_val);
}
- CASE (const e_location_t&, e_location)
+ MATCH_CASE (const e_location_t&, e_location)
{
location_t loc;
if (eval_location(env, &loc, &e_location))
@@ -1759,14 +1767,14 @@ void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr)
else
*dest = ValLocation{loc};
}
- CASE (const e_area_t&, e_area)
+ MATCH_CASE (const e_area_t&, e_area)
{
if (dumb_ptr<area_t> area = eval_area(env, e_area))
*dest = ValArea{area};
else
*dest = ValFail();
}
- CASE (const ExprFunApp&, e_funapp)
+ MATCH_CASE (const ExprFunApp&, e_funapp)
{
val_t arguments[MAX_ARGS];
int args_nr = e_funapp.args_nr;
@@ -1800,12 +1808,12 @@ void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr)
for (i = 0; i < args_nr; ++i)
magic_clear_var(&arguments[i]);
}
- CASE (const ExprId&, e)
+ MATCH_CASE (const ExprId&, e)
{
val_t& v = env->VAR(e.e_id);
magic_copy_var(dest, &v);
}
- CASE (const ExprField&, e_field)
+ MATCH_CASE (const ExprField&, e_field)
{
val_t v;
int id = e_field.id;
@@ -1833,6 +1841,7 @@ void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr)
}
}
}
+ MATCH_END ();
}
int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr)
@@ -1861,4 +1870,5 @@ AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr)
return result.get_if<ValString>()->v_string;
}
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-expr.hpp b/src/map/magic-expr.hpp
index 294e665..055f37b 100644
--- a/src/map/magic-expr.hpp
+++ b/src/map/magic-expr.hpp
@@ -21,20 +21,16 @@
#include "fwd.hpp"
-#include "../generic/fwd.hpp"
-
-#include "../range/fwd.hpp"
-
#include "../strings/zstring.hpp"
#include "../strings/literal.hpp"
-#include "../mmo/fwd.hpp"
-
#include "magic-interpreter.t.hpp"
namespace tmwa
{
+namespace map
+{
namespace magic
{
/*
@@ -97,14 +93,15 @@ int magic_find_item(Slice<val_t> args, int index, Item *item, int *stackable);
default: break; \
}
-int magic_location_in_area(map_local *m, int x, int y, dumb_ptr<area_t> area);
+int magic_location_in_area(Borrowed<map_local> m, int x, int y, dumb_ptr<area_t> area);
/* Helper definitions for dealing with functions and operations */
int magic_signature_check(ZString opname, ZString funname, ZString signature,
Slice<val_t> args, int line, int column);
-void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height,
+Borrowed<map_local> magic_area_rect(int *x, int *y, int *width, int *height,
area_t& area);
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-expr.py b/src/map/magic-expr.py
index 0d9db55..f53ddc8 100644
--- a/src/map/magic-expr.py
+++ b/src/map/magic-expr.py
@@ -1,7 +1,7 @@
class fun_t(object):
__slots__ = ('_value')
- name = 'tmwa::magic::fun_t'
+ name = 'tmwa::map::magic::fun_t'
depth = 1
enabled = True
@@ -31,8 +31,8 @@ class fun_t(object):
'''
tests = [
- ('static_cast<tmwa::magic::fun_t *>(nullptr)',
+ ('static_cast<tmwa::map::magic::fun_t *>(nullptr)',
'(fun_t *) nullptr'),
- ('new tmwa::magic::fun_t{"name"_s, "sig"_s, \'\\0\', nullptr}',
- 'regex:\(fun_t \*\) = \{->name = "name", ->signature = "sig", ->ret_ty = 0 \'\\\\000\', ->fun = (0x)?0}'),
+ ('new tmwa::map::magic::fun_t{"name"_s, "sig"_s, \'\\0\', nullptr}',
+ '(fun_t *) = {->name = "name", ->signature = "sig", ->ret_ty = 0 \'\\000\', ->fun = nullptr}'),
]
diff --git a/src/map/magic-interpreter-base.cpp b/src/map/magic-interpreter-base.cpp
index be9a61a..c2be363 100644
--- a/src/map/magic-interpreter-base.cpp
+++ b/src/map/magic-interpreter-base.cpp
@@ -25,10 +25,12 @@
#include "../strings/xstring.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+
+#include "../mmo/cxxstdio_enums.hpp"
#include "../net/timer.hpp"
+#include "globals.hpp"
#include "magic.hpp"
#include "magic-expr.hpp"
#include "magic-interpreter.hpp"
@@ -39,6 +41,8 @@
namespace tmwa
{
+namespace map
+{
namespace magic
{
static
@@ -78,9 +82,6 @@ void set_spell(val_t *v, dumb_ptr<spell_t> x)
*v = ValSpell{x};
}
-magic_conf_t magic_conf; /* Global magic conf */
-env_t magic_default_env = { &magic_conf, nullptr };
-
AString magic_find_invocation(XString spellname)
{
auto it = magic_conf.spells_by_name.find(spellname);
@@ -313,22 +314,22 @@ const effect_set_t *spellguard_check_sub(spellguard_check_t *check,
if (guard == nullptr)
return nullptr;
- MATCH (*guard)
+ MATCH_BEGIN (*guard)
{
- CASE (const GuardCondition&, s)
+ MATCH_CASE (const GuardCondition&, s)
{
if (!magic_eval_int(env, s.s_condition))
return nullptr;
}
- CASE (const GuardComponents&, s)
+ MATCH_CASE (const GuardComponents&, s)
{
copy_components(&check->components, s.s_components);
}
- CASE (const GuardCatalysts&, s)
+ MATCH_CASE (const GuardCatalysts&, s)
{
copy_components(&check->catalysts, s.s_catalysts);
}
- CASE (const GuardChoice&, s)
+ MATCH_CASE (const GuardChoice&, s)
{
spellguard_check_t altcheck = *check;
const effect_set_t *retval;
@@ -350,15 +351,15 @@ const effect_set_t *spellguard_check_sub(spellguard_check_t *check,
return spellguard_check_sub(check, s.s_alt, caster,
env, near_miss);
}
- CASE (const GuardMana&, s)
+ MATCH_CASE (const GuardMana&, s)
{
check->mana += magic_eval_int(env, s.s_mana);
}
- CASE (const GuardCastTime&, s)
+ MATCH_CASE (const GuardCastTime&, s)
{
check->casttime += static_cast<interval_t>(magic_eval_int(env, s.s_casttime));
}
- CASE (const effect_set_t&, s_effect)
+ MATCH_CASE (const effect_set_t&, s_effect)
{
if (spellguard_can_satisfy(check, caster, env, near_miss))
return &s_effect;
@@ -366,6 +367,7 @@ const effect_set_t *spellguard_check_sub(spellguard_check_t *check,
return nullptr;
}
}
+ MATCH_END ();
return spellguard_check_sub(check, guard->next, caster, env, near_miss);
}
@@ -547,4 +549,5 @@ int spell_unbind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invoca
return 1;
}
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-interpreter-base.hpp b/src/map/magic-interpreter-base.hpp
index 4bb41a0..7c00db0 100644
--- a/src/map/magic-interpreter-base.hpp
+++ b/src/map/magic-interpreter-base.hpp
@@ -21,20 +21,13 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
-
-#include "../mmo/fwd.hpp"
-
namespace tmwa
{
+namespace map
+{
namespace magic
{
-extern magic_conf_t magic_conf; /* Global magic conf */
-extern env_t magic_default_env; /* Fake default environment */
-
/**
* Adds a component selection to a component holder (which may initially be nullptr)
*/
@@ -87,4 +80,5 @@ dumb_ptr<spell_t> magic_find_spell(XString invocation);
void spell_update_location(dumb_ptr<invocation> invocation);
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp
index 3bb600c..cbd92a9 100644
--- a/src/map/magic-interpreter.hpp
+++ b/src/map/magic-interpreter.hpp
@@ -19,39 +19,43 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "fwd.hpp"
-
#include "magic-interpreter.t.hpp"
+#include "fwd.hpp"
+
#include <cassert>
#include <memory>
-#include "../strings/fwd.hpp"
#include "../strings/rstring.hpp"
-#include "../generic/fwd.hpp"
-
#include "../sexpr/variant.hpp"
#include "../net/timer.t.hpp"
#include "../mmo/ids.hpp"
-#include "../mmo/utils.hpp"
#include "map.hpp"
-#include "script.hpp"
-#include "skill.t.hpp"
+#include "script-buffer.hpp"
+#include "../mmo/skill.t.hpp"
namespace tmwa
{
+namespace map
+{
namespace magic
{
struct location_t
{
- map_local *m;
+ Borrowed<map_local> m;
int x, y;
+
+ // This constructor exists solely to work around the design constraints
+ // of sexpr::Variant<>. See comments in variant.tcc for future plans.
+ __attribute__((deprecated))
+ location_t() noexcept : m(borrow(undefined_gat)), x(), y() {}
+ location_t(Borrowed<map_local> m_, int x_, int y_) : m(m_), x(x_), y(y_) {}
};
struct AreaUnion
@@ -622,4 +626,5 @@ struct proc_t
{}
};
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-interpreter.py b/src/map/magic-interpreter.py
index cf17b1c..520ab37 100644
--- a/src/map/magic-interpreter.py
+++ b/src/map/magic-interpreter.py
@@ -1,6 +1,6 @@
class AreaUnion(object):
__slots__ = ('_value')
- name = 'tmwa::magic::AreaUnion'
+ name = 'tmwa::map::magic::AreaUnion'
enabled = True
def __init__(self, value):
@@ -27,23 +27,23 @@ class area_t(object):
using tmwa::operator "" _s;
inline
- tmwa::map_local *fake_map_local_x_dup_for_area_t(tmwa::ZString name)
+ tmwa::Borrowed<tmwa::map::map_local> fake_map_local_x_dup_for_area_t(tmwa::ZString name)
{
- auto *p = new tmwa::map_local{};
+ auto *p = new tmwa::map::map_local{};
p->name_ = tmwa::stringish<tmwa::MapName>(name);
- return p;
+ return tmwa::borrow(*p);
}
'''
tests = [
- ('tmwa::magic::area_t(tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456})',
- '{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}'),
- ('tmwa::magic::area_t(tmwa::magic::AreaUnion{{tmwa::dumb_ptr<tmwa::magic::area_t>::make(tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}), tmwa::dumb_ptr<tmwa::magic::area_t>::make(tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 321, 654})}})',
- '{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::AreaUnion) = {{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}, {<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 321, y = 654}}, size = 1}}}, size = 2}'),
- ('tmwa::magic::area_t(tmwa::magic::AreaRect{tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}, 789, 102})',
- '{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::AreaRect) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}, width = 789, height = 102}}, size = 80478}'),
- ('tmwa::magic::area_t(tmwa::magic::AreaBar{tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 42, 43}, 123, 456, tmwa::DIR::NW})',
- '{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::AreaBar) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 43}, width = 123, depth = 456, dir = tmwa::DIR::NW}}, size = 112632}'),
+ ('tmwa::map::magic::area_t(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}'),
+ ('tmwa::map::magic::area_t(tmwa::map::magic::AreaUnion{{tmwa::dumb_ptr<tmwa::map::magic::area_t>::make(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}), tmwa::dumb_ptr<tmwa::map::magic::area_t>::make(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 321, 654})}})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaUnion) = {{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}, {<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 321, y = 654}}, size = 1}}}, size = 2}'),
+ ('tmwa::map::magic::area_t(tmwa::map::magic::AreaRect{tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}, 789, 102})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaRect) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}, width = 789, height = 102}}, size = 80478}'),
+ ('tmwa::map::magic::area_t(tmwa::map::magic::AreaBar{tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 42, 43}, 123, 456, tmwa::DIR::NW})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaBar) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 43}, width = 123, depth = 456, dir = tmwa::DIR::NW}}, size = 112632}'),
]
@@ -55,47 +55,47 @@ class val_t(object):
using tmwa::operator "" _s;
inline
- tmwa::map_local *fake_map_local_x_dup_for_val_t(tmwa::ZString name)
+ tmwa::Borrowed<tmwa::map::map_local> fake_map_local_x_dup_for_val_t(tmwa::ZString name)
{
- auto *p = new tmwa::map_local{};
+ auto *p = new tmwa::map::map_local{};
p->name_ = tmwa::stringish<tmwa::MapName>(name);
- return p;
+ return tmwa::borrow(*p);
}
'''
tests = [
- ('tmwa::magic::val_t(tmwa::magic::ValUndef{})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValUndef) = {<No data fields>}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValInt{42})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValInt) = {v_int = 42}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValDir{tmwa::DIR::NW})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValDir) = {v_dir = tmwa::DIR::NW}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValString{"Hello"_s})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValString) = {v_string = "Hello"}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValEntityInt{tmwa::wrap<tmwa::BlockId>(123)})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValEntityInt) = {v_eid = 123}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValEntityPtr{tmwa::dumb_ptr<tmwa::block_list>()})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValEntityPtr) = {v_entity = 0x0}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValLocation{tmwa::magic::location_t{fake_map_local_x_dup_for_val_t("map"_s), 42, 123}})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValLocation) = {v_location = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 123}}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValArea{tmwa::dumb_ptr<tmwa::magic::area_t>()})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValArea) = {v_area = 0x0}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValSpell{tmwa::dumb_ptr<tmwa::magic::spell_t>()})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValSpell) = {v_spell = 0x0}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValInvocationInt{tmwa::wrap<tmwa::BlockId>(123)})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValInvocationInt) = {v_iid = 123}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValInvocationPtr{})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValInvocationPtr) = {v_invocation = 0x0}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValFail{})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValFail) = {<No data fields>}}, <No data fields>}'),
- ('tmwa::magic::val_t(tmwa::magic::ValNegative1{})',
- '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValNegative1) = {<No data fields>}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValUndef{})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValUndef) = {<No data fields>}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValInt{42})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInt) = {v_int = 42}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValDir{tmwa::DIR::NW})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValDir) = {v_dir = tmwa::DIR::NW}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValString{"Hello"_s})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValString) = {v_string = "Hello"}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValEntityInt{tmwa::wrap<tmwa::BlockId>(123)})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValEntityInt) = {v_eid = 123}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValEntityPtr{tmwa::dumb_ptr<tmwa::map::block_list>()})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValEntityPtr) = {v_entity = 0x0}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValLocation{tmwa::map::magic::location_t{fake_map_local_x_dup_for_val_t("map"_s), 42, 123}})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValLocation) = {v_location = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 123}}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValArea{tmwa::dumb_ptr<tmwa::map::magic::area_t>()})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValArea) = {v_area = 0x0}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValSpell{tmwa::dumb_ptr<tmwa::map::magic::spell_t>()})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValSpell) = {v_spell = 0x0}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValInvocationInt{tmwa::wrap<tmwa::BlockId>(123)})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInvocationInt) = {v_iid = 123}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValInvocationPtr{})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInvocationPtr) = {v_invocation = 0x0}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValFail{})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValFail) = {<No data fields>}}, <No data fields>}'),
+ ('tmwa::map::magic::val_t(tmwa::map::magic::ValNegative1{})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValNegative1) = {<No data fields>}}, <No data fields>}'),
]
class ExprAreaUnion(object):
__slots__ = ('_value')
- name = 'tmwa::magic::ExprAreaUnion'
+ name = 'tmwa::map::magic::ExprAreaUnion'
enabled = True
def __init__(self, value):
@@ -119,14 +119,14 @@ class e_area_t(object):
enabled = True
tests = [
- ('tmwa::magic::e_area_t(tmwa::magic::e_location_t())',
- '{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'),
- ('tmwa::magic::e_area_t(tmwa::magic::ExprAreaUnion{{tmwa::dumb_ptr<tmwa::magic::e_area_t>::make(tmwa::magic::e_location_t()), tmwa::dumb_ptr<tmwa::magic::e_area_t>::make(tmwa::magic::e_location_t())}})',
- '{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::ExprAreaUnion) = {{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}, {<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}}, <No data fields>}'),
- ('tmwa::magic::e_area_t(tmwa::magic::ExprAreaRect{tmwa::magic::e_location_t(), tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::expr_t>()})',
- '{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::ExprAreaRect) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, height = 0x0}}, <No data fields>}'),
- ('tmwa::magic::e_area_t(tmwa::magic::ExprAreaBar{tmwa::magic::e_location_t(), tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::expr_t>()})',
- '{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::ExprAreaBar) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, depth = 0x0, dir = 0x0}}, <No data fields>}'),
+ ('tmwa::map::magic::e_area_t(tmwa::map::magic::e_location_t())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'),
+ ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaUnion{{tmwa::dumb_ptr<tmwa::map::magic::e_area_t>::make(tmwa::map::magic::e_location_t()), tmwa::dumb_ptr<tmwa::map::magic::e_area_t>::make(tmwa::map::magic::e_location_t())}})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaUnion) = {{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}, {<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}}, <No data fields>}'),
+ ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaRect{tmwa::map::magic::e_location_t(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>()})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaRect) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, height = 0x0}}, <No data fields>}'),
+ ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaBar{tmwa::map::magic::e_location_t(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>()})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaBar) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, depth = 0x0, dir = 0x0}}, <No data fields>}'),
]
@@ -135,18 +135,18 @@ class expr_t(object):
enabled = True
tests = [
- ('tmwa::magic::expr_t(tmwa::magic::val_t(tmwa::magic::ValUndef()))',
- '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::val_t) = {<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValUndef) = {<No data fields>}}, <No data fields>}}, <No data fields>}'),
- ('tmwa::magic::expr_t(tmwa::magic::e_location_t())',
- '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'),
- ('tmwa::magic::expr_t(tmwa::magic::e_area_t(tmwa::magic::e_location_t()))',
- '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::e_area_t) = {<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}, <No data fields>}'),
- ('tmwa::magic::expr_t(tmwa::magic::ExprFunApp())',
- '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::ExprFunApp) = {funp = (fun_t *) nullptr, line_nr = 0, column = 0, args_nr = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, <No data fields>}'),
- ('tmwa::magic::expr_t(tmwa::magic::ExprId{123})',
- '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::ExprId) = {e_id = 123}}, <No data fields>}'),
- ('tmwa::magic::expr_t(tmwa::magic::ExprField{tmwa::dumb_ptr<tmwa::magic::expr_t>(), 42})',
- '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::ExprField) = {expr = 0x0, id = 42}}, <No data fields>}'),
+ ('tmwa::map::magic::expr_t(tmwa::map::magic::val_t(tmwa::map::magic::ValUndef()))',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::val_t) = {<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValUndef) = {<No data fields>}}, <No data fields>}}, <No data fields>}'),
+ ('tmwa::map::magic::expr_t(tmwa::map::magic::e_location_t())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'),
+ ('tmwa::map::magic::expr_t(tmwa::map::magic::e_area_t(tmwa::map::magic::e_location_t()))',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::e_area_t) = {<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}, <No data fields>}'),
+ ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprFunApp())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprFunApp) = {funp = (fun_t *) nullptr, line_nr = 0, column = 0, args_nr = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, <No data fields>}'),
+ ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprId{123})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprId) = {e_id = 123}}, <No data fields>}'),
+ ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprField{tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), 42})',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprField) = {expr = 0x0, id = 42}}, <No data fields>}'),
]
@@ -154,30 +154,30 @@ class effect_t(object):
enabled = True
tests = [
- ('tmwa::magic::effect_t(tmwa::magic::EffectSkip{}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectSkip) = {<No data fields>}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectAbort{}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectAbort) = {<No data fields>}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectAssign{42, tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectAssign) = {id = 42, expr = 0x0}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectForEach{123, tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::magic::FOREACH_FILTER::PC}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectForEach) = {id = 123, area = 0x0, body = 0x0, filter = tmwa::magic::FOREACH_FILTER::PC}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectFor{42, tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectFor) = {id = 42, start = 0x0, stop = 0x0, body = 0x0}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectIf{tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectIf) = {cond = 0x0, true_branch = 0x0, false_branch = 0x0}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectSleep{tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectSleep) = {e_sleep = 0x0}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectScript{tmwa::dumb_ptr<const tmwa::ScriptBuffer>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectScript) = {e_script = 0x0}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectBreak{}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectBreak) = {<No data fields>}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectOp(), tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectOp) = {opp = (op_t *) nullptr, args_nr = 0, line_nr = 0, column = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectEnd{}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectEnd) = {<No data fields>}}, next = 0x0}'),
- ('tmwa::magic::effect_t(tmwa::magic::EffectCall{nullptr, tmwa::dumb_ptr<std::vector<tmwa::dumb_ptr<tmwa::magic::expr_t>>>(), tmwa::dumb_ptr<tmwa::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectCall) = {formalv = 0x0, actualvp = 0x0, body = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectSkip{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectSkip) = {<No data fields>}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectAbort{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectAbort) = {<No data fields>}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectAssign{42, tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectAssign) = {id = 42, expr = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectForEach{123, tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::map::magic::FOREACH_FILTER::PC}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectForEach) = {id = 123, area = 0x0, body = 0x0, filter = tmwa::map::magic::FOREACH_FILTER::PC}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectFor{42, tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectFor) = {id = 42, start = 0x0, stop = 0x0, body = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectIf{tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectIf) = {cond = 0x0, true_branch = 0x0, false_branch = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectSleep{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectSleep) = {e_sleep = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectScript{tmwa::dumb_ptr<const tmwa::map::ScriptBuffer>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectScript) = {e_script = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectBreak{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectBreak) = {<No data fields>}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectOp(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectOp) = {opp = (op_t *) nullptr, args_nr = 0, line_nr = 0, column = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectEnd{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectEnd) = {<No data fields>}}, next = 0x0}'),
+ ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectCall{nullptr, tmwa::dumb_ptr<std::vector<tmwa::dumb_ptr<tmwa::map::magic::expr_t>>>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectCall) = {formalv = nullptr, actualvp = 0x0, body = 0x0}}, next = 0x0}'),
]
@@ -185,20 +185,20 @@ class spellguard_t(object):
enabled = True
tests = [
- ('tmwa::magic::spellguard_t(tmwa::magic::GuardCondition{tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardCondition) = {s_condition = 0x0}}, next = 0x0}'),
- ('tmwa::magic::spellguard_t(tmwa::magic::GuardMana{tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardMana) = {s_mana = 0x0}}, next = 0x0}'),
- ('tmwa::magic::spellguard_t(tmwa::magic::GuardCastTime{tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardCastTime) = {s_casttime = 0x0}}, next = 0x0}'),
- ('tmwa::magic::spellguard_t(tmwa::magic::GuardComponents{tmwa::dumb_ptr<tmwa::magic::component_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardComponents) = {s_components = 0x0}}, next = 0x0}'),
- ('tmwa::magic::spellguard_t(tmwa::magic::GuardCatalysts{tmwa::dumb_ptr<tmwa::magic::component_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardCatalysts) = {s_catalysts = 0x0}}, next = 0x0}'),
- ('tmwa::magic::spellguard_t(tmwa::magic::GuardChoice{tmwa::dumb_ptr<tmwa::magic::spellguard_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardChoice) = {s_alt = 0x0}}, next = 0x0}'),
- ('tmwa::magic::spellguard_t(tmwa::magic::effect_set_t{tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::effect_set_t) = {effect = 0x0, at_trigger = 0x0, at_end = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCondition{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCondition) = {s_condition = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardMana{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardMana) = {s_mana = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCastTime{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCastTime) = {s_casttime = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardComponents{tmwa::dumb_ptr<tmwa::map::magic::component_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardComponents) = {s_components = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCatalysts{tmwa::dumb_ptr<tmwa::map::magic::component_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCatalysts) = {s_catalysts = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardChoice{tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardChoice) = {s_alt = 0x0}}, next = 0x0}'),
+ ('tmwa::map::magic::spellguard_t(tmwa::map::magic::effect_set_t{tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::effect_set_t) = {effect = 0x0, at_trigger = 0x0, at_end = 0x0}}, next = 0x0}'),
]
@@ -206,10 +206,10 @@ class cont_activation_record_t(object):
enabled = True
tests = [
- ('tmwa::magic::cont_activation_record_t(tmwa::magic::CarForEach{42, true, tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::dumb_ptr<std::vector<tmwa::BlockId>>(), 123}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::CarForEach, tmwa::magic::CarFor, tmwa::magic::CarProc>> = {(tmwa::magic::CarForEach) = {id = 42, ty_is_spell_not_entity = true, body = 0x0, entities_vp = 0x0, index = 123}}, return_location = 0x0}'),
- ('tmwa::magic::cont_activation_record_t(tmwa::magic::CarFor{42, tmwa::dumb_ptr<tmwa::magic::effect_t>(), 123, 456}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::CarForEach, tmwa::magic::CarFor, tmwa::magic::CarProc>> = {(tmwa::magic::CarFor) = {id = 42, body = 0x0, current = 123, stop = 456}}, return_location = 0x0}'),
- ('tmwa::magic::cont_activation_record_t(tmwa::magic::CarProc{123, nullptr, tmwa::dumb_ptr<tmwa::magic::val_t[]>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())',
- '{<tmwa::sexpr::Variant<tmwa::magic::CarForEach, tmwa::magic::CarFor, tmwa::magic::CarProc>> = {(tmwa::magic::CarProc) = {args_nr = 123, formalap = 0x0, old_actualpa = 0x0 = {sz = 0}}}, return_location = 0x0}'),
+ ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarForEach{42, true, tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<std::vector<tmwa::BlockId>>(), 123}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarForEach) = {id = 42, ty_is_spell_not_entity = true, body = 0x0, entities_vp = 0x0, index = 123}}, return_location = 0x0}'),
+ ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarFor{42, tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), 123, 456}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarFor) = {id = 42, body = 0x0, current = 123, stop = 456}}, return_location = 0x0}'),
+ ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarProc{123, nullptr, tmwa::dumb_ptr<tmwa::map::magic::val_t[]>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
+ '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarProc) = {args_nr = 123, formalap = nullptr, old_actualpa = 0x0 = {sz = 0}}}, return_location = 0x0}'),
]
diff --git a/src/map/magic-interpreter.t.hpp b/src/map/magic-interpreter.t.hpp
index ab151fc..e302354 100644
--- a/src/map/magic-interpreter.t.hpp
+++ b/src/map/magic-interpreter.t.hpp
@@ -26,6 +26,8 @@
namespace tmwa
{
+namespace map
+{
namespace magic
{
enum class SPELLARG : uint8_t
@@ -79,4 +81,5 @@ ENUM_BITWISE_OPERATORS(INVOCATION_FLAG)
}
using e::INVOCATION_FLAG;
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp
index 2a657fa..4d8330a 100644
--- a/src/map/magic-stmt.cpp
+++ b/src/map/magic-stmt.cpp
@@ -29,7 +29,8 @@
#include "../generic/random2.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+
+#include "../mmo/cxxstdio_enums.hpp"
#include "../net/timer.hpp"
@@ -42,7 +43,9 @@
#include "magic-interpreter-base.hpp"
#include "mob.hpp"
#include "npc.hpp"
+#include "npc-parse.hpp"
#include "pc.hpp"
+#include "script-call.hpp"
#include "skill.hpp"
#include "../poison.hpp"
@@ -50,6 +53,8 @@
namespace tmwa
{
+namespace map
+{
namespace magic
{
/* used for local spell effects */
@@ -58,17 +63,18 @@ constexpr Species INVISIBLE_NPC = wrap<Species>(127);
static
void clear_activation_record(cont_activation_record_t *ar)
{
- MATCH (*ar)
+ MATCH_BEGIN (*ar)
{
- CASE (CarForEach&, c_foreach)
+ MATCH_CASE (CarForEach&, c_foreach)
{
c_foreach.entities_vp.delete_();
}
- CASE (CarProc&, c_proc)
+ MATCH_CASE (CarProc&, c_proc)
{
c_proc.old_actualpa.delete_();
}
}
+ MATCH_END ();
}
static
@@ -218,7 +224,7 @@ BlockId trigger_spell(BlockId subject, BlockId spell)
}
static
-void entity_warp(dumb_ptr<block_list> target, map_local *destm, int destx, int desty);
+void entity_warp(dumb_ptr<block_list> target, Borrowed<map_local> destm, int destx, int desty);
static
void char_update(dumb_ptr<map_session_data> character)
@@ -261,7 +267,7 @@ void timer_callback_effect_npc_delete(TimerData *, tick_t, BlockId npc_id)
}
static
-dumb_ptr<npc_data> local_spell_effect(map_local *m, int x, int y, int effect,
+dumb_ptr<npc_data> local_spell_effect(Borrowed<map_local> m, int x, int y, int effect,
interval_t tdelay)
{
/* 1 minute should be enough for all interesting spell effects, I hope */
@@ -421,7 +427,7 @@ int op_messenger_npc(dumb_ptr<env_t>, Slice<val_t> args)
}
static
-void entity_warp(dumb_ptr<block_list> target, map_local *destm, int destx, int desty)
+void entity_warp(dumb_ptr<block_list> target, Borrowed<map_local> destm, int destx, int desty)
{
if (target->bl_type == BL::PC || target->bl_type == BL::MOB)
{
@@ -767,8 +773,8 @@ int op_injure(dumb_ptr<env_t> env, Slice<val_t> args)
if (target->bl_type == BL::PC
&& !target->bl_m->flag.get(MapFlag::PVP)
- && !target->is_player()->special_state.killable
- && (caster->bl_type != BL::PC || !caster->is_player()->special_state.killer))
+ && (caster->bl_type == BL::PC)
+ && ((caster->is_player()->state.pvpchannel > 1) && (target->is_player()->state.pvpchannel != caster->is_player()->state.pvpchannel)))
return 0; /* Cannot damage other players outside of pvp */
if (target != caster)
@@ -786,7 +792,7 @@ int op_injure(dumb_ptr<env_t> env, Slice<val_t> args)
// display damage first, because dealing damage may deallocate the target.
clif_damage(caster, target,
gettick(), interval_t::zero(), interval_t::zero(),
- damage_caused, 0, DamageType::NORMAL, 0);
+ damage_caused, 0, DamageType::NORMAL);
if (caster->bl_type == BL::PC)
{
@@ -995,9 +1001,9 @@ dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_)
{
cont_activation_record_t *ar =
&invocation_->stack.back();
- MATCH (*ar)
+ MATCH_BEGIN (*ar)
{
- CASE (const CarProc&, c_proc)
+ MATCH_CASE (const CarProc&, c_proc)
{
dumb_ptr<effect_t> ret = ar->return_location;
for (int i = 0; i < c_proc.args_nr; i++)
@@ -1014,7 +1020,7 @@ dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_)
return ret;
}
- CASE (CarForEach&, c_foreach)
+ MATCH_CASE (CarForEach&, c_foreach)
{
BlockId entity_id;
val_t *var = &invocation_->env->varu[c_foreach.id];
@@ -1045,7 +1051,7 @@ dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_)
return c_foreach.body;
}
- CASE (CarFor&, c_for)
+ MATCH_CASE (CarFor&, c_for)
{
if (c_for.current > c_for.stop)
{
@@ -1062,6 +1068,7 @@ dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_)
return c_for.body;
}
}
+ MATCH_END ();
abort();
}
}
@@ -1133,46 +1140,43 @@ void find_entities_in_area(area_t& area_,
std::vector<BlockId> *entities_vp,
FOREACH_FILTER filter)
{
- MATCH (area_)
+ MATCH_BEGIN (area_)
{
- CASE (const AreaUnion&, a)
+ MATCH_CASE (const AreaUnion&, a)
{
find_entities_in_area(*a.a_union[0], entities_vp, filter);
find_entities_in_area(*a.a_union[1], entities_vp, filter);
}
- CASE (const location_t&, a_loc)
+ MATCH_CASE (const location_t&, a_loc)
{
(void)a_loc;
// TODO this can be simplified
- map_local *m;
int x, y, width, height;
- magic_area_rect(&m, &x, &y, &width, &height, area_);
+ Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_);
map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
m,
x, y,
x + width, y + height,
BL::NUL /* filter elsewhere */);
}
- CASE (const AreaRect&, a_rect)
+ MATCH_CASE (const AreaRect&, a_rect)
{
(void)a_rect;
// TODO this can be simplified
- map_local *m;
int x, y, width, height;
- magic_area_rect(&m, &x, &y, &width, &height, area_);
+ Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_);
map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
m,
x, y,
x + width, y + height,
BL::NUL /* filter elsewhere */);
}
- CASE (const AreaBar&, a_bar)
+ MATCH_CASE (const AreaBar&, a_bar)
{
(void)a_bar;
// TODO this is wrong
- map_local *m;
int x, y, width, height;
- magic_area_rect(&m, &x, &y, &width, &height, area_);
+ Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_);
map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
m,
x, y,
@@ -1180,6 +1184,7 @@ void find_entities_in_area(area_t& area_,
BL::NUL /* filter elsewhere */);
}
}
+ MATCH_END ();
}
static
@@ -1312,13 +1317,13 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
dumb_ptr<effect_t> next = e->next;
int i;
- MATCH (*e)
+ MATCH_BEGIN (*e)
{
- CASE (const EffectSkip&, e_)
+ MATCH_CASE (const EffectSkip&, e_)
{
(void)e_;
}
- CASE (const EffectAbort&, e_)
+ MATCH_CASE (const EffectAbort&, e_)
{
(void)e_;
invocation_->flags |= INVOCATION_FLAG::ABORTED;
@@ -1326,34 +1331,34 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
clear_stack(invocation_);
next = nullptr;
}
- CASE (const EffectEnd&, e_)
+ MATCH_CASE (const EffectEnd&, e_)
{
(void)e_;
clear_stack(invocation_);
next = nullptr;
}
- CASE (const EffectAssign&, e_assign)
+ MATCH_CASE (const EffectAssign&, e_assign)
{
magic_eval(invocation_->env,
&invocation_->env->varu[e_assign.id],
e_assign.expr);
}
- CASE (const EffectForEach&, e_foreach)
+ MATCH_CASE (const EffectForEach&, e_foreach)
{
next = run_foreach(invocation_, &e_foreach, next);
}
- CASE (const EffectFor&, e_for)
+ MATCH_CASE (const EffectFor&, e_for)
{
next = run_for (invocation_, &e_for, next);
}
- CASE (const EffectIf&, e_if)
+ MATCH_CASE (const EffectIf&, e_if)
{
if (magic_eval_int(invocation_->env, e_if.cond))
next = e_if.true_branch;
else
next = e_if.false_branch;
}
- CASE (const EffectSleep&, e_)
+ MATCH_CASE (const EffectSleep&, e_)
{
interval_t sleeptime = static_cast<interval_t>(
magic_eval_int(invocation_->env, e_.e_sleep));
@@ -1361,7 +1366,7 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
if (sleeptime > interval_t::zero())
return sleeptime;
}
- CASE (const EffectScript&, e_)
+ MATCH_CASE (const EffectScript&, e_)
{
dumb_ptr<map_session_data> caster = map_id_is_player(invocation_->caster);
if (caster)
@@ -1391,7 +1396,7 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
// dealing with an NPC
int newpos = run_script_l(
- ScriptPointer(&*e_.e_script, invocation_->script_pos),
+ ScriptPointer(borrow(*e_.e_script), invocation_->script_pos),
message_recipient, invocation_->bl_id,
arg);
/* Returns the new script position, or -1 once the script is finished */
@@ -1408,12 +1413,12 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
}
REFRESH_INVOCATION; // Script may have killed the caster
}
- CASE (const EffectBreak&, e_)
+ MATCH_CASE (const EffectBreak&, e_)
{
(void)e_;
next = return_to_stack(invocation_);
}
- CASE (const EffectOp&, e_op)
+ MATCH_CASE (const EffectOp&, e_op)
{
op_t *op = e_op.opp;
val_t args[MAX_ARGS];
@@ -1432,11 +1437,12 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
REFRESH_INVOCATION; // Effect may have killed the caster
}
- CASE (const EffectCall&, e_call)
+ MATCH_CASE (const EffectCall&, e_call)
{
next = run_call(invocation_, &e_call, next);
}
}
+ MATCH_END ();
break_match:
if (!next)
@@ -1535,4 +1541,5 @@ int spell_attack(BlockId caster_id, BlockId target_id)
return 1;
}
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-stmt.hpp b/src/map/magic-stmt.hpp
index 0385858..3b04fe3 100644
--- a/src/map/magic-stmt.hpp
+++ b/src/map/magic-stmt.hpp
@@ -21,17 +21,15 @@
#include "fwd.hpp"
-#include "../range/fwd.hpp"
-
#include "../strings/zstring.hpp"
-#include "../generic/fwd.hpp"
-
-#include "skill.t.hpp"
+#include "../mmo/skill.t.hpp"
namespace tmwa
{
+namespace map
+{
namespace magic
{
struct op_t
@@ -91,4 +89,5 @@ int spell_attack(BlockId caster, BlockId target);
void spell_free_invocation(dumb_ptr<invocation> invocation);
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-stmt.py b/src/map/magic-stmt.py
index 14289ef..7cc43d0 100644
--- a/src/map/magic-stmt.py
+++ b/src/map/magic-stmt.py
@@ -1,7 +1,7 @@
class op_t(object):
__slots__ = ('_value')
- name = 'tmwa::magic::op_t'
+ name = 'tmwa::map::magic::op_t'
depth = 1
enabled = True
@@ -30,8 +30,8 @@ class op_t(object):
'''
tests = [
- ('static_cast<tmwa::magic::op_t *>(nullptr)',
+ ('static_cast<tmwa::map::magic::op_t *>(nullptr)',
'(op_t *) nullptr'),
- ('new tmwa::magic::op_t{"name"_s, "sig"_s, nullptr}',
- 'regex:\(op_t \*\) = \{->name = "name", ->signature = "sig", ->op = (0x)?0}'),
+ ('new tmwa::map::magic::op_t{"name"_s, "sig"_s, nullptr}',
+ '(op_t *) = {->name = "name", ->signature = "sig", ->op = nullptr}'),
]
diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp
index 5b375b2..52b1b8f 100644
--- a/src/map/magic-v2.cpp
+++ b/src/map/magic-v2.cpp
@@ -24,6 +24,8 @@
#include <map>
#include <set>
+#include "../range/slice.hpp"
+
#include "../strings/rstring.hpp"
#include "../strings/literal.hpp"
@@ -34,27 +36,28 @@
#include "../sexpr/parser.hpp"
+#include "../ast/script.hpp"
+
+#include "globals.hpp"
#include "itemdb.hpp"
#include "magic-expr.hpp"
#include "magic-interpreter.hpp"
#include "magic-interpreter-base.hpp"
#include "magic-stmt.hpp"
+#include "script-parse.hpp"
#include "../poison.hpp"
namespace tmwa
{
+namespace map
+{
namespace magic
{
namespace magic_v2
{
static
- std::map<RString, proc_t> procs;
- static
- std::map<RString, val_t> const_defm;
-
- static
size_t intern_id(ZString id_name)
{
// TODO use InternPool
@@ -134,14 +137,15 @@ namespace magic_v2
/* For FOR and FOREACH, we use special stack handlers and thus don't have to set
* the continuation. It's only IF that we need to handle in this fashion. */
- MATCH (*src)
+ MATCH_BEGIN (*src)
{
- CASE (EffectIf&, e_if)
+ MATCH_CASE (EffectIf&, e_if)
{
set_effect_continuation(e_if.true_branch, continuation);
set_effect_continuation(e_if.false_branch, continuation);
}
}
+ MATCH_END ();
if (src->next)
set_effect_continuation(src->next, continuation);
@@ -172,13 +176,14 @@ namespace magic_v2
}
/* If the premise is a disjunction, b is the continuation of _all_ branches */
- MATCH (*a)
+ MATCH_BEGIN (*a)
{
- CASE(const GuardChoice&, s)
+ MATCH_CASE (const GuardChoice&, s)
{
spellguard_implication(s.s_alt, b);
}
}
+ MATCH_END ();
if (a->next)
spellguard_implication(a->next, b);
@@ -575,9 +580,9 @@ namespace magic_v2
{
count = 1;
- item_data *item = itemdb_searchname(s._str);
- if (!item)
- return fail(s, "no such item"_s);
+ Borrowed<item_data> item = TRY_UNWRAP(itemdb_searchname(s._str),
+ return fail(s, "no such item"_s)
+ );
id = item->nameid;
return true;
}
@@ -591,9 +596,9 @@ namespace magic_v2
if (s._list[1]._type != sexpr::STRING)
return fail(s._list[1], "item pair second not name"_s);
- item_data *item = itemdb_searchname(s._list[1]._str);
- if (!item)
- return fail(s, "no such item"_s);
+ Borrowed<item_data> item = TRY_UNWRAP(itemdb_searchname(s._list[1]._str),
+ return fail(s, "no such item"_s)
+ );
id = item->nameid;
return true;
}
@@ -782,7 +787,20 @@ namespace magic_v2
if (s._list[1]._type != sexpr::STRING)
return fail(s._list[1], "not string"_s);
ZString body = s._list[1]._str;
- std::unique_ptr<const ScriptBuffer> script = parse_script(body, s._list[1]._span.begin.line, true);
+ auto begin = s._list[1]._span.begin;
+ io::LineCharReader lr(io::from_string, begin.filename, body, begin.line, begin.column);
+ ast::script::ScriptOptions opt;
+ opt.implicit_start = true;
+ opt.implicit_end = true;
+ opt.no_event = true;
+ auto code_res = ast::script::parse_script_body(lr, opt);
+ if (code_res.get_failure())
+ {
+ PRINTF("%s\n"_fmt, code_res.get_failure());
+ }
+ auto code = TRY_UNWRAP(code_res.get_success(),
+ return fail(s._list[1], "script does not compile"_s));
+ std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script magic %s:%d"_fmt, begin.filename, begin.line), code, true);
if (!script)
return fail(s._list[1], "script does not compile"_s);
EffectScript e;
@@ -1273,4 +1291,5 @@ bool load_magic_file_v2(ZString filename)
return rv;
}
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic-v2.hpp b/src/map/magic-v2.hpp
index 9ad44a9..fac2773 100644
--- a/src/map/magic-v2.hpp
+++ b/src/map/magic-v2.hpp
@@ -25,10 +25,13 @@
namespace tmwa
{
+namespace map
+{
namespace magic
{
bool magic_init0();
// must be called after itemdb initialization
bool load_magic_file_v2(ZString filename);
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic.cpp b/src/map/magic.cpp
index a0238b5..418312a 100644
--- a/src/map/magic.cpp
+++ b/src/map/magic.cpp
@@ -28,6 +28,7 @@
#include "../io/cxxstdio.hpp"
+#include "globals.hpp"
#include "magic-expr.hpp"
#include "magic-interpreter.hpp"
#include "magic-interpreter-base.hpp"
@@ -40,6 +41,8 @@
namespace tmwa
{
+namespace map
+{
namespace magic
{
/// Return a pair of strings, {spellname, parameter}
@@ -71,7 +74,7 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
{
if (pc_isdead(caster))
return 0;
- if (bool(caster->status.option & Option::HIDE))
+ if (bool(caster->status.option & Opt0::HIDE))
return 0; // No spellcasting while hidden
int power = caster->matk1;
@@ -123,4 +126,5 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
return 0; /* Not a spell */
}
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/magic.hpp b/src/map/magic.hpp
index a420872..70d40dc 100644
--- a/src/map/magic.hpp
+++ b/src/map/magic.hpp
@@ -21,16 +21,14 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
-
#include "map.t.hpp"
-#include "skill.t.hpp"
+#include "../mmo/skill.t.hpp"
namespace tmwa
{
+namespace map
+{
namespace magic
{
/**
@@ -46,4 +44,5 @@ namespace magic
*/
int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation);
} // namespace magic
+} // namespace map
} // namespace tmwa
diff --git a/src/map/main.cpp b/src/map/main.cpp
index c16f642..f19272d 100644
--- a/src/map/main.cpp
+++ b/src/map/main.cpp
@@ -17,7 +17,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../mmo/core.hpp"
+#include "../high/core.hpp"
#include "map.hpp"
@@ -26,6 +26,9 @@
namespace tmwa
{
+namespace map
+{
+} // namespace map
} // namespace tmwa
int main(int argc, char **argv)
diff --git a/src/map/map.cpp b/src/map/map.cpp
index 4a25029..c1d760a 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -45,34 +45,41 @@
#include "../generic/random2.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+#include "../io/extract.hpp"
#include "../io/read.hpp"
+#include "../io/span.hpp"
#include "../io/tty.hpp"
#include "../io/write.hpp"
#include "../net/socket.hpp"
#include "../net/timer.hpp"
+#include "../net/timestamp-utils.hpp"
#include "../mmo/config_parse.hpp"
-#include "../mmo/core.hpp"
-#include "../mmo/extract.hpp"
-#include "../mmo/utils.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
#include "../mmo/version.hpp"
+#include "../high/core.hpp"
+
#include "atcommand.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "chrif.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "grfio.hpp"
#include "itemdb.hpp"
#include "magic-interpreter.hpp" // for is_spell inline body
#include "magic-stmt.hpp"
#include "magic-v2.hpp"
+#include "map_conf.hpp"
#include "mob.hpp"
+#include "quest.hpp"
#include "npc.hpp"
+#include "npc-parse.hpp"
#include "party.hpp"
#include "pc.hpp"
-#include "script.hpp"
+#include "script-startup.hpp"
#include "skill.hpp"
#include "storage.hpp"
#include "trade.hpp"
@@ -82,53 +89,16 @@
namespace tmwa
{
-DMap<BlockId, dumb_ptr<block_list>> id_db;
-
-UPMap<MapName, map_abstract> maps_db;
-
-static
-DMap<CharName, dumb_ptr<map_session_data>> nick_db;
-
-struct charid2nick
-{
- CharName nick;
- int req_id;
-};
-
-static
-Map<CharId, struct charid2nick> charid_db;
-
-static
-int users = 0;
-static
-Array<dumb_ptr<block_list>, unwrap<BlockId>(MAX_FLOORITEM)> object;
-static
-BlockId first_free_object_id = BlockId();
-
-interval_t autosave_time = DEFAULT_AUTOSAVE_INTERVAL;
-int save_settings = 0xFFFF;
-
-AString motd_txt = "conf/motd.txt"_s;
-
-CharName wisp_server_name = stringish<CharName>("Server"_s); // can be modified in char-server configuration file
-
-static
-void map_delmap(MapName mapname);
-
void SessionDeleter::operator()(SessionData *sd)
{
- really_delete1 static_cast<map_session_data *>(sd);
+ really_delete1 static_cast<map::map_session_data *>(sd);
}
-VString<49> convert_for_printf(NpcEvent ev)
+namespace map
{
- return STRNPRINTF(50, "%s::%s"_fmt, ev.npc, ev.label);
-}
-bool extract(XString str, NpcEvent *ev)
-{
- XString mid;
- return extract(str, record<':'>(&ev->npc, &mid, &ev->label)) && !mid;
-}
+const CharName WISP_SERVER_NAME = stringish<CharName>("Server"_s);
+
+map_local undefined_gat = [](){ map_local rv {}; rv.name_ = stringish<MapName>("undefined.gat"_s); return rv; }();
/*==========================================
* 全map鯖総計での接続数設定
@@ -137,7 +107,7 @@ bool extract(XString str, NpcEvent *ev)
*/
void map_setusers(int n)
{
- users = n;
+ world_user_count = n;
}
/*==========================================
@@ -146,14 +116,9 @@ void map_setusers(int n)
*/
int map_getusers(void)
{
- return users;
+ return world_user_count;
}
-static
-int block_free_lock = 0;
-static
-std::vector<dumb_ptr<block_list>> block_free;
-
void MapBlockLock::freeblock(dumb_ptr<block_list> bl)
{
if (block_free_lock == 0)
@@ -178,12 +143,6 @@ MapBlockLock::~MapBlockLock()
}
}
-/// This is a dummy entry that is shared by all the linked lists,
-/// so that any entry can unlink itself without worrying about
-/// whether it was the the head of the list.
-static
-struct block_list bl_head;
-
/*==========================================
* map[]のblock_listに追加
* mobは数が多いので別リスト
@@ -202,10 +161,10 @@ int map_addblock(dumb_ptr<block_list> bl)
return 0;
}
- map_local *m = bl->bl_m;
+ P<map_local> m = bl->bl_m;
int x = bl->bl_x;
int y = bl->bl_y;
- if (!m ||
+ if (m == borrow(undefined_gat) ||
x < 0 || x >= m->xs || y < 0 || y >= m->ys)
return 1;
@@ -283,7 +242,7 @@ int map_delblock(dumb_ptr<block_list> bl)
* セル上のPCとMOBの数を数える (グランドクロス用)
*------------------------------------------
*/
-int map_count_oncell(map_local *m, int x, int y)
+int map_count_oncell(Borrowed<map_local> m, int x, int y)
{
int bx, by;
dumb_ptr<block_list> bl = nullptr;
@@ -318,14 +277,17 @@ int map_count_oncell(map_local *m, int x, int y)
*------------------------------------------
*/
void map_foreachinarea(std::function<void(dumb_ptr<block_list>)> func,
- map_local *m,
+ Borrowed<map_local> m,
int x0, int y0, int x1, int y1,
BL type)
{
std::vector<dumb_ptr<block_list>> bl_list;
- if (!m)
- return;
+ // there are some broadcasts during startup
+ // disable then
+ if (m == borrow(undefined_gat))
+ abort();
+
if (x0 < 0)
x0 = 0;
if (y0 < 0)
@@ -381,7 +343,7 @@ void map_foreachinarea(std::function<void(dumb_ptr<block_list>)> func,
*------------------------------------------
*/
void map_foreachinmovearea(std::function<void(dumb_ptr<block_list>)> func,
- map_local *m,
+ Borrowed<map_local> m,
int x0, int y0, int x1, int y1,
int dx, int dy,
BL type)
@@ -501,7 +463,7 @@ void map_foreachinmovearea(std::function<void(dumb_ptr<block_list>)> func,
// area radius - may be more useful in some instances)
//
void map_foreachincell(std::function<void(dumb_ptr<block_list>)> func,
- map_local *m,
+ Borrowed<map_local> m,
int x, int y,
BL type)
{
@@ -674,7 +636,7 @@ void map_clearflooritem_timer(TimerData *tid, tick_t, BlockId id)
map_delobject(fitem->bl_id, BL::ITEM);
}
-std::pair<uint16_t, uint16_t> map_randfreecell(map_local *m,
+std::pair<uint16_t, uint16_t> map_randfreecell(Borrowed<map_local> m,
uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
for (int itr : random_::iterator(w * h))
@@ -689,7 +651,7 @@ std::pair<uint16_t, uint16_t> map_randfreecell(map_local *m,
/// Return a randomly selected passable cell within a given range.
static
-std::pair<uint16_t, uint16_t> map_searchrandfreecell(map_local *m, int x, int y, int range)
+std::pair<uint16_t, uint16_t> map_searchrandfreecell(Borrowed<map_local> m, int x, int y, int range)
{
int whole_range = 2 * range + 1;
return map_randfreecell(m, x - range, y - range, whole_range, whole_range);
@@ -702,7 +664,7 @@ std::pair<uint16_t, uint16_t> map_searchrandfreecell(map_local *m, int x, int y,
*------------------------------------------
*/
BlockId map_addflooritem_any(Item *item_data, int amount,
- map_local *m, int x, int y,
+ Borrowed<map_local> m, int x, int y,
dumb_ptr<map_session_data> *owners, interval_t *owner_protection,
interval_t lifetime, int dispersal)
{
@@ -767,7 +729,7 @@ BlockId map_addflooritem_any(Item *item_data, int amount,
}
BlockId map_addflooritem(Item *item_data, int amount,
- map_local *m, int x, int y,
+ Borrowed<map_local> m, int x, int y,
dumb_ptr<map_session_data> first_sd,
dumb_ptr<map_session_data> second_sd,
dumb_ptr<map_session_data> third_sd)
@@ -776,14 +738,14 @@ BlockId map_addflooritem(Item *item_data, int amount,
interval_t owner_protection[3];
{
- owner_protection[0] = static_cast<interval_t>(battle_config.item_first_get_time);
- owner_protection[1] = owner_protection[0] + static_cast<interval_t>(battle_config.item_second_get_time);
- owner_protection[2] = owner_protection[1] + static_cast<interval_t>(battle_config.item_third_get_time);
+ owner_protection[0] = battle_config.item_first_get_time;
+ owner_protection[1] = owner_protection[0] + battle_config.item_second_get_time;
+ owner_protection[2] = owner_protection[1] + battle_config.item_third_get_time;
}
return map_addflooritem_any(item_data, amount, m, x, y,
owners, owner_protection,
- static_cast<interval_t>(battle_config.flooritem_lifetime), 1);
+ battle_config.flooritem_lifetime, 1);
}
/*==========================================
@@ -792,9 +754,7 @@ BlockId map_addflooritem(Item *item_data, int amount,
*/
void map_addchariddb(CharId charid, CharName name)
{
- struct charid2nick *p = charid_db.search(charid);
- if (p == nullptr)
- p = charid_db.init(charid);
+ P<struct charid2nick> p = charid_db.init(charid);
p->nick = name;
p->req_id = 0;
@@ -929,13 +889,17 @@ dumb_ptr<map_session_data> map_id2sd(BlockId id)
*/
CharName map_charid2nick(CharId id)
{
- struct charid2nick *p = charid_db.search(id);
+ Option<P<struct charid2nick>> p_ = charid_db.search(id);
- if (p == nullptr)
- return CharName();
- if (p->req_id != 0)
- return CharName();
- return p->nick;
+ return p_.cmap(
+ [](P<struct charid2nick> p)
+ {
+ return p->req_id == 0;
+ },
+ [](P<struct charid2nick> p)
+ {
+ return p->nick;
+ }).move_or(CharName());
}
/*========================================*/
@@ -1047,11 +1011,9 @@ dumb_ptr<block_list> map_id2bl(BlockId id)
* map.npcへ追加 (warp等の領域持ちのみ)
*------------------------------------------
*/
-int map_addnpc(map_local *m, dumb_ptr<npc_data> nd)
+int map_addnpc(Borrowed<map_local> m, dumb_ptr<npc_data> nd)
{
int i;
- if (!m)
- return -1;
for (i = 0; i < m->npc_num && i < MAX_NPC_PER_MAP; i++)
if (m->npc[i] == nullptr)
break;
@@ -1109,27 +1071,39 @@ void map_removenpc(void)
* map名からmap番号へ変換
*------------------------------------------
*/
-map_local *map_mapname2mapid(MapName name)
+Option<Borrowed<map_local>> map_mapname2mapid(MapName name)
{
- map_abstract *md = maps_db.get(name);
- if (md == nullptr || md->gat == nullptr)
- return nullptr;
- return static_cast<map_local *>(md);
+ Option<P<map_abstract>> md_ = maps_db.get(name);
+ return md_.cmap(
+ [](P<map_abstract> md)
+ {
+ return bool(md->gat);
+ },
+ [](P<map_abstract> md)
+ {
+ return md.downcast_to<map_local>();
+ });
}
/*==========================================
* 他鯖map名からip,port変換
*------------------------------------------
*/
-int map_mapname2ipport(MapName name, IP4Address *ip, int *port)
+int map_mapname2ipport(MapName name, Borrowed<IP4Address> ip, Borrowed<int> port)
{
- map_abstract *md = maps_db.get(name);
- if (md == nullptr || md->gat)
- return -1;
- map_remote *mdos = static_cast<map_remote *>(md);
- *ip = mdos->ip;
- *port = mdos->port;
- return 0;
+ Option<P<map_abstract>> md_ = maps_db.get(name);
+ return md_.cmap(
+ [](P<map_abstract> md)
+ {
+ return !md->gat;
+ },
+ [ip, port](P<map_abstract> md)
+ {
+ auto mdos = md.downcast_to<map_remote>();
+ *ip = mdos->ip;
+ *port = mdos->port;
+ return 0;
+ }).copy_or(-1);
}
/// Check compatibility of directions.
@@ -1208,7 +1182,7 @@ DIR map_calc_dir(dumb_ptr<block_list> src, int x, int y)
* (m,x,y)の状態を調べる
*------------------------------------------
*/
-MapCell map_getcell(map_local *m, int x, int y)
+MapCell map_getcell(Borrowed<map_local> m, int x, int y)
{
if (x < 0 || x >= m->xs - 1 || y < 0 || y >= m->ys - 1)
return MapCell::UNWALKABLE;
@@ -1219,7 +1193,7 @@ MapCell map_getcell(map_local *m, int x, int y)
* (m,x,y)の状態をtにする
*------------------------------------------
*/
-void map_setcell(map_local *m, int x, int y, MapCell t)
+void map_setcell(Borrowed<map_local> m, int x, int y, MapCell t)
{
if (x < 0 || x >= m->xs || y < 0 || y >= m->ys)
return;
@@ -1232,37 +1206,41 @@ void map_setcell(map_local *m, int x, int y, MapCell t)
*/
int map_setipport(MapName name, IP4Address ip, int port)
{
- map_abstract *md = maps_db.get(name);
- if (md == nullptr)
+ Option<P<map_abstract>> md_ = maps_db.get(name);
+ OMATCH_BEGIN (md_)
{
- // not exist -> add new data
- auto mdos = make_unique<map_remote>();
- mdos->name_ = name;
- mdos->gat = nullptr;
- mdos->ip = ip;
- mdos->port = port;
- maps_db.put(mdos->name_, std::move(mdos));
- }
- else
- {
- if (md->gat)
+ OMATCH_CASE_SOME (md)
{
- // local -> check data
- if (ip != clif_getip() || port != clif_getport())
+ if (md->gat)
+ {
+ // local -> check data
+ if (ip != map_conf.map_ip || port != map_conf.map_port)
+ {
+ PRINTF("from char server : %s -> %s:%d\n"_fmt,
+ name, ip, port);
+ return 1;
+ }
+ }
+ else
{
- PRINTF("from char server : %s -> %s:%d\n"_fmt,
- name, ip, port);
- return 1;
+ // update
+ P<map_remote> mdos = md.downcast_to<map_remote>();
+ mdos->ip = ip;
+ mdos->port = port;
}
}
- else
+ OMATCH_CASE_NONE ()
{
- // update
- map_remote *mdos = static_cast<map_remote *>(md);
+ // not exist -> add new data
+ auto mdos = make_unique<map_remote>();
+ mdos->name_ = name;
+ mdos->gat = nullptr;
mdos->ip = ip;
mdos->port = port;
+ maps_db.put(mdos->name_, std::move(mdos));
}
}
+ OMATCH_END ();
return 0;
}
@@ -1281,7 +1259,7 @@ bool map_readmap(map_local *m, size_t num, MapName fn)
int xs = m->xs = gat_v[0] | gat_v[1] << 8;
int ys = m->ys = gat_v[2] | gat_v[3] << 8;
- PRINTF("\rLoading Maps [%zu/%zu]: %-30s (%i, %i)"_fmt,
+ PRINTF("Loading Maps [%zu/%zu]: %-30s (%i, %i)\r"_fmt,
num, maps_db.size(),
fn, xs, ys);
fflush(stdout);
@@ -1334,7 +1312,7 @@ bool map_readallmap(void)
}
}
- PRINTF("\rMaps Loaded: %-65zu\n"_fmt, maps_db.size());
+ PRINTF("Maps Loaded: %-65zu\n"_fmt, maps_db.size());
if (maps_removed)
{
PRINTF("Cowardly refusing to keep going after removing %d maps.\n"_fmt,
@@ -1349,7 +1327,6 @@ bool map_readallmap(void)
* 読み込むmapを追加する
*------------------------------------------
*/
-static
void map_addmap(MapName mapname)
{
if (mapname == "clear"_s)
@@ -1384,18 +1361,11 @@ void map_delmap(MapName mapname)
constexpr int LOGFILE_SECONDS_PER_CHUNK_SHIFT = 10;
static
-std::unique_ptr<io::AppendFile> map_logfile;
-static
-AString map_logfile_name;
-static
-long map_logfile_index;
-
-static
void map_close_logfile(void)
{
if (map_logfile)
{
- AString filename = STRPRINTF("%s.%ld"_fmt, map_logfile_name, map_logfile_index);
+ AString filename = STRPRINTF("%s.%ld"_fmt, map_conf.log_file, map_logfile_index);
const char *args[] =
{
"gzip",
@@ -1423,22 +1393,23 @@ void map_start_logfile(long index)
AString filename_buf = STRPRINTF(
"%s.%ld"_fmt,
- map_logfile_name,
+ map_conf.log_file,
map_logfile_index);
map_logfile = make_unique<io::AppendFile>(filename_buf);
if (!map_logfile->is_open())
{
map_logfile.reset();
- perror(map_logfile_name.c_str());
+ perror(map_conf.log_file.c_str());
}
}
static
-void map_set_logfile(AString filename)
+void map_set_logfile()
{
- struct timeval tv;
+ if (!map_conf.log_file)
+ return;
- map_logfile_name = std::move(filename);
+ struct timeval tv;
gettimeofday(&tv, nullptr);
map_start_logfile(tv.tv_sec >> LOGFILE_SECONDS_PER_CHUNK_SHIFT);
@@ -1463,147 +1434,6 @@ void map_log(XString line)
log_with_timestamp(*map_logfile, line);
}
-/*==========================================
- * 設定ファイルを読み込む
- *------------------------------------------
- */
-static
-bool map_config_read(ZString cfgName)
-{
- struct hostent *h = nullptr;
-
- io::ReadFile in(cfgName);
- if (!in.is_open())
- {
- PRINTF("Map configuration file not found at: %s\n"_fmt, cfgName);
- return false;
- }
-
- bool rv = true;
- AString line;
- while (in.getline(line))
- {
- if (is_comment(line))
- continue;
- XString w1;
- ZString w2;
- if (!config_split(line, &w1, &w2))
- {
- PRINTF("Bad config line: %s\n"_fmt, line);
- rv = false;
- continue;
- }
- if (w1 == "userid"_s)
- {
- AccountName name = stringish<AccountName>(w2);
- chrif_setuserid(name);
- }
- else if (w1 == "passwd"_s)
- {
- AccountPass pass = stringish<AccountPass>(w2);
- chrif_setpasswd(pass);
- }
- else if (w1 == "char_ip"_s)
- {
- h = gethostbyname(w2.c_str());
- IP4Address w2ip;
- if (h != nullptr)
- {
- w2ip = IP4Address({
- static_cast<uint8_t>(h->h_addr[0]),
- static_cast<uint8_t>(h->h_addr[1]),
- static_cast<uint8_t>(h->h_addr[2]),
- static_cast<uint8_t>(h->h_addr[3]),
- });
- PRINTF("Character server IP address : %s -> %s\n"_fmt,
- w2, w2ip);
- }
- else
- {
- PRINTF("Bad IP value: %s\n"_fmt, line);
- return false;
- }
- chrif_setip(w2ip);
- }
- else if (w1 == "char_port"_s)
- {
- chrif_setport(atoi(w2.c_str()));
- }
- else if (w1 == "map_ip"_s)
- {
- h = gethostbyname(w2.c_str());
- IP4Address w2ip;
- if (h != nullptr)
- {
- w2ip = IP4Address({
- static_cast<uint8_t>(h->h_addr[0]),
- static_cast<uint8_t>(h->h_addr[1]),
- static_cast<uint8_t>(h->h_addr[2]),
- static_cast<uint8_t>(h->h_addr[3]),
- });
- PRINTF("Map server IP address : %s -> %s\n"_fmt,
- w2, w2ip);
- }
- else
- {
- PRINTF("Bad IP value: %s\n"_fmt, line);
- return false;
- }
- clif_setip(w2ip);
- }
- else if (w1 == "map_port"_s)
- {
- clif_setport(atoi(w2.c_str()));
- }
- else if (w1 == "map"_s)
- {
- MapName name = VString<15>(w2);
- map_addmap(name);
- }
- else if (w1 == "delmap"_s)
- {
- MapName name = VString<15>(w2);
- map_delmap(name);
- }
- else if (w1 == "npc"_s)
- {
- npc_addsrcfile(w2);
- }
- else if (w1 == "delnpc"_s)
- {
- npc_delsrcfile(w2);
- }
- else if (w1 == "autosave_time"_s)
- {
- autosave_time = std::chrono::seconds(atoi(w2.c_str()));
- if (autosave_time <= interval_t::zero())
- autosave_time = DEFAULT_AUTOSAVE_INTERVAL;
- }
- else if (w1 == "motd_txt"_s)
- {
- motd_txt = w2;
- }
- else if (w1 == "mapreg_txt"_s)
- {
- mapreg_txt = w2;
- }
- else if (w1 == "gm_log"_s)
- {
- gm_log = std::move(w2);
- }
- else if (w1 == "log_file"_s)
- {
- map_set_logfile(w2);
- }
- else if (w1 == "import"_s)
- {
- rv &= map_config_read(w2);
- }
- }
-
- return rv;
-}
-
static
void cleanup_sub(dumb_ptr<block_list> bl)
{
@@ -1629,17 +1459,86 @@ void cleanup_sub(dumb_ptr<block_list> bl)
}
}
+int compare_item(Item *a, Item *b)
+{
+ return (a->nameid == b->nameid);
+}
+
+static
+bool map_config(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ return parse_map_conf(map_conf, key, value);
+}
+
+static
+bool battle_config_(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ return parse_battle_conf(battle_config, key, value);
+}
+
+static
+bool map_confs(io::Spanned<XString> key, io::Spanned<ZString> value)
+{
+ if (key.data == "map_conf"_s)
+ return load_config_file(value.data, map_config);
+ if (key.data == "battle_conf"_s)
+ return load_config_file(value.data, battle_config_);
+ if (key.data == "atcommand_conf"_s)
+ return atcommand_config_read(value.data);
+
+ if (key.data == "item_db"_s)
+ return itemdb_readdb(value.data);
+ if (key.data == "mob_db"_s)
+ return mob_readdb(value.data);
+ if (key.data == "quest_db"_s)
+ return quest_readdb(value.data);
+ if (key.data == "mob_skill_db"_s)
+ return mob_readskilldb(value.data);
+ if (key.data == "skill_db"_s)
+ return skill_readdb(value.data);
+ if (key.data == "magic_conf"_s)
+ return magic::load_magic_file_v2(value.data);
+
+ if (key.data == "resnametable"_s)
+ return load_resnametable(value.data);
+ if (key.data == "const_db"_s)
+ return read_constdb(value.data);
+ key.span.error("Unknown meta-key for map server"_s);
+ return false;
+}
+
+int map_scriptcont(dumb_ptr<map_session_data> sd, BlockId id)
+{
+ dumb_ptr<block_list> bl = map_id2bl(id);
+
+ if (!bl)
+ return 0;
+
+ switch (bl->bl_type)
+ {
+ case BL::NPC:
+ return npc_scriptcont(sd, id);
+ case BL::SPELL:
+ magic::spell_execute_script(bl->is_spell());
+ break;
+ }
+
+ return 0;
+}
+} // namespace map
+
/*==========================================
* map鯖終了時処理
*------------------------------------------
*/
void term_func(void)
{
+ using namespace tmwa::map;
for (auto& mit : maps_db)
{
if (!mit.second->gat)
continue;
- map_local *map_id = static_cast<map_local *>(mit.second.get());
+ P<map_local> map_id = borrow(*mit.second).downcast_to<map_local>();
map_foreachinarea(cleanup_sub,
map_id,
@@ -1662,46 +1561,14 @@ void term_func(void)
map_close_logfile();
}
-int compare_item(Item *a, Item *b)
-{
- return (a->nameid == b->nameid);
-}
-
-static
-bool map_confs(XString key, ZString value)
-{
- if (key == "map_conf"_s)
- return map_config_read(value);
- if (key == "battle_conf"_s)
- return battle_config_read(value);
- if (key == "atcommand_conf"_s)
- return atcommand_config_read(value);
-
- if (key == "item_db"_s)
- return itemdb_readdb(value);
- if (key == "mob_db"_s)
- return mob_readdb(value);
- if (key == "mob_skill_db"_s)
- return mob_readskilldb(value);
- if (key == "skill_db"_s)
- return skill_readdb(value);
- if (key == "magic_conf"_s)
- return magic::load_magic_file_v2(value);
-
- if (key == "resnametable"_s)
- return load_resnametable(value);
- if (key == "const_db"_s)
- return read_constdb(value);
- PRINTF("unknown map conf key: %s\n"_fmt, AString(key));
- return false;
-}
-
/*======================================================
* Map-Server Init and Command-line Arguments [Valaris]
*------------------------------------------------------
*/
int do_init(Slice<ZString> argv)
{
+ using namespace tmwa::map;
+
ZString argv0 = argv.pop_front();
runflag &= magic::magic_init0();
@@ -1749,7 +1616,8 @@ int do_init(Slice<ZString> argv)
if (!loaded_config_yet)
runflag &= load_config_file("conf/tmwa-map.conf"_s, map_confs);
- battle_config_check();
+ map_set_logfile();
+
runflag &= map_readallmap();
do_init_chrif();
@@ -1767,26 +1635,7 @@ int do_init(Slice<ZString> argv)
PRINTF("The server is running in " SGR_BOLD SGR_RED "PK Mode" SGR_RESET "\n"_fmt);
PRINTF("The map-server is " SGR_BOLD SGR_GREEN "ready" SGR_RESET " (Server is listening on the port %d).\n\n"_fmt,
- clif_getport());
-
- return 0;
-}
-
-int map_scriptcont(dumb_ptr<map_session_data> sd, BlockId id)
-{
- dumb_ptr<block_list> bl = map_id2bl(id);
-
- if (!bl)
- return 0;
-
- switch (bl->bl_type)
- {
- case BL::NPC:
- return npc_scriptcont(sd, id);
- case BL::SPELL:
- magic::spell_execute_script(bl->is_spell());
- break;
- }
+ map_conf.map_port);
return 0;
}
diff --git a/src/map/map.hpp b/src/map/map.hpp
index d88ff54..f57dcee 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -20,17 +20,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "fwd.hpp"
-
#include "map.t.hpp"
+#include "fwd.hpp"
+
#include <chrono>
#include <functional>
#include <list>
#include "../ints/udl.hpp"
-#include "../strings/fwd.hpp"
#include "../strings/rstring.hpp"
#include "../strings/astring.hpp"
#include "../strings/vstring.hpp"
@@ -42,18 +41,19 @@
#include "../net/socket.hpp"
#include "../net/timer.t.hpp"
-#include "../mmo/utils.hpp"
-
#include "battle.t.hpp"
-#include "clif.t.hpp"
+#include "../mmo/clif.t.hpp"
#include "mapflag.hpp"
#include "mob.t.hpp"
-#include "script.hpp" // change to script.t.hpp
-#include "skill.t.hpp"
+#include "script-buffer.hpp"
+#include "script-persist.hpp"
+#include "../mmo/skill.t.hpp"
namespace tmwa
{
+namespace map
+{
constexpr int MAX_NPC_PER_MAP = 512;
constexpr int BLOCK_SIZE = 8;
#define AREA_SIZE battle_config.area_size
@@ -61,47 +61,19 @@ constexpr std::chrono::seconds LIFETIME_FLOORITEM = 1_min;
constexpr int MAX_SKILL_LEVEL = 100;
constexpr int MAX_EVENTTIMER = 32;
constexpr interval_t NATURAL_HEAL_INTERVAL = 500_ms;
-constexpr BlockId MAX_FLOORITEM = wrap<BlockId>(500000_u32);
constexpr int MAX_LEVEL = 255;
constexpr int MAX_WALKPATH = 48;
constexpr int MAX_DROP_PER_MAP = 48;
-constexpr interval_t DEFAULT_AUTOSAVE_INTERVAL = 1_min;
-
-// formerly VString<49>, as name::label
-struct NpcEvent
-{
- NpcName npc;
- ScriptLabel label;
+constexpr std::chrono::seconds DEFAULT_AUTOSAVE_INTERVAL = 1_min;
- explicit operator bool()
- {
- return npc || label;
- }
- bool operator !()
- {
- return !bool(*this);
- }
-
- friend bool operator == (const NpcEvent& l, const NpcEvent& r)
- {
- return l.npc == r.npc && l.label == r.label;
- }
-
- friend bool operator < (const NpcEvent& l, const NpcEvent& r)
- {
- return l.npc < r.npc || (l.npc == r.npc && l.label < r.label);
- }
-
- friend VString<49> convert_for_printf(NpcEvent ev);
-};
-bool extract(XString str, NpcEvent *ev);
+extern map_local undefined_gat;
struct block_list
{
dumb_ptr<block_list> bl_next, bl_prev;
BlockId bl_id;
- map_local *bl_m;
+ Borrowed<map_local> bl_m = borrow(undefined_gat);
short bl_x, bl_y;
BL bl_type;
@@ -156,7 +128,7 @@ struct map_session_data : block_list, SessionData
unsigned dead_sit:2;
unsigned skillcastcancel:1;
unsigned waitingdisconnect:1;
- unsigned lr_flag:2;
+ unsigned lr_flag_is_arrow_2:1;
unsigned connect_new:1;
unsigned arrow_atk:1;
BF attack_type;//:3;
@@ -170,11 +142,10 @@ struct map_session_data : block_list, SessionData
unsigned shroud_disappears_on_pickup:1;
unsigned shroud_disappears_on_talk:1;
unsigned seen_motd:1;
+ unsigned pvpchannel;
} state;
struct
{
- unsigned killer:1;
- unsigned killable:1;
unsigned unbreakable_weapon:1;
unsigned unbreakable_armor:1;
unsigned deaf:1;
@@ -185,7 +156,19 @@ struct map_session_data : block_list, SessionData
unsigned char tmw_version; // tmw client version
CharKey status_key;
CharData status;
- GenericArray<struct item_data *, InventoryIndexing<IOff0, MAX_INVENTORY>> inventory_data;
+ GenericArray<Option<Borrowed<struct item_data>>, InventoryIndexing<IOff0, MAX_INVENTORY>> inventory_data =
+ {{
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ None, None, None, None, None, None, None, None, None, None,
+ }}; // explicit is better than implicit
earray<IOff0, EQUIP, EQUIP::COUNT> equip_index_maybe;
int weight, max_weight;
MapName mapname_;
@@ -205,13 +188,12 @@ struct map_session_data : block_list, SessionData
int npc_amount;
// I have no idea exactly what these are doing ...
// but one should probably be replaced with a ScriptPointer ???
- const ScriptBuffer *npc_script, *npc_scriptroot;
+ Option<Borrowed<const ScriptBuffer>> npc_script = None, npc_scriptroot = None;
std::vector<struct script_data> npc_stackbuf;
RString npc_str;
struct
{
unsigned storage:1;
- unsigned divorce:1;
} npc_flags;
Timer attacktimer;
@@ -235,7 +217,7 @@ struct map_session_data : block_list, SessionData
short spellpower_bonus_target, spellpower_bonus_current; // [Fate] Spellpower boni. _current is the active one.
//_current slowly approximates _target, and _target is determined by equipment.
- short attackrange, attackrange_;
+ short attackrange;
// [Fate] Used for gradual healing; amount of enqueued regeneration
struct quick_regeneration quick_regeneration_hp, quick_regeneration_sp;
@@ -249,18 +231,17 @@ struct map_session_data : block_list, SessionData
interval_t hp_sub, sp_sub;
interval_t inchealhptick, inchealsptick;
- ItemLook weapontype1, weapontype2;
+ ItemLook weapontype1;
earray<int, ATTR, ATTR::COUNT> paramb, paramc, parame, paramcard;
int hit, flee, flee2;
interval_t aspd, amotion, dmotion;
int watk, watk2;
int def, def2, mdef, mdef2, critical, matk1, matk2;
int hprate, sprate, dsprate;
- int watk_, watk_2;
int base_atk, atk_rate;
int arrow_atk;
int arrow_cri, arrow_hit, arrow_range;
- int nhealhp, nhealsp, nshealhp, nshealsp, nsshealhp, nsshealsp;
+ int nhealhp, nhealsp;
int aspd_rate, speed_rate, hprecov_rate, sprecov_rate, critical_def,
double_rate;
int matk_rate;
@@ -269,9 +250,6 @@ struct map_session_data : block_list, SessionData
mdef_rate, mdef2_rate;
int double_add_rate, speed_add_rate, aspd_add_rate, perfect_hit_add;
short hp_drain_rate, hp_drain_per, sp_drain_rate, sp_drain_per;
- short hp_drain_rate_, hp_drain_per_, sp_drain_rate_, sp_drain_per_;
- short break_weapon_rate, break_armor_rate;
- short add_steal_rate;
int die_counter;
@@ -283,7 +261,6 @@ struct map_session_data : block_list, SessionData
Map<SIR, RString> regstrm;
earray<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
- short sc_count;
AccountId trade_partner;
Array<IOff2, TRADE_MAX> deal_item_index;
@@ -298,11 +275,8 @@ struct map_session_data : block_list, SessionData
PartyId partyspy; // [Syrus22]
- int catch_target_class;
-
int pvp_point, pvp_rank;
Timer pvp_timer;
- int pvp_lastusers;
std::list<NpcEvent> eventqueuel;
Array<Timer, MAX_EVENTTIMER> eventtimer;
@@ -312,14 +286,14 @@ struct map_session_data : block_list, SessionData
unsigned in_progress:1;
} auto_ban_info;
- TimeT chat_reset_due;
- TimeT chat_repeat_reset_due;
+ tick_t chat_reset_due;
+ tick_t chat_repeat_reset_due;
int chat_lines_in;
int chat_total_repeats;
RString chat_lastmsg;
tick_t flood_rates[0x220];
- TimeT packet_flood_reset_due;
+ tick_t packet_flood_reset_due;
int packet_flood_in;
IP4Address get_ip()
@@ -355,7 +329,7 @@ struct npc_data : block_list
Opt1 opt1;
Opt2 opt2;
Opt3 opt3;
- Option option;
+ Opt0 option;
short flag;
std::list<RString> eventqueuel;
@@ -383,6 +357,7 @@ public:
std::unique_ptr<const ScriptBuffer> script;
// Diameter.
short xs, ys;
+ bool event_needs_map;
// Whether the timer advances if not beyond end.
bool timer_active;
@@ -439,7 +414,7 @@ struct mob_data : block_list
MobMode mode;
struct
{
- map_local *m;
+ Borrowed<map_local> m = borrow(undefined_gat);
short x0, y0, xs, ys;
interval_t delay1, delay2;
} spawn;
@@ -478,11 +453,10 @@ struct mob_data : block_list
std::vector<Item> lootitemv;
earray<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
- short sc_count;
Opt1 opt1;
Opt2 opt2;
Opt3 opt3;
- Option option;
+ Opt0 option;
short min_chase;
Timer deletetimer;
@@ -514,10 +488,10 @@ struct map_abstract
// gat is nullptr for map_remote and non-nullptr for map_local
std::unique_ptr<MapCell[]> gat;
+ map_abstract() = default;
+ map_abstract(map_abstract&&) = default;
virtual ~map_abstract() {}
};
-extern
-UPMap<MapName, map_abstract> maps_db;
struct map_local : map_abstract
{
@@ -538,7 +512,7 @@ struct map_remote : map_abstract
};
inline
-MapCell read_gatp(map_local *m, int x, int y)
+MapCell read_gatp(Borrowed<map_local> m, int x, int y)
{
assert (0 <= x && x < m->xs);
assert (0 <= y && y < m->ys);
@@ -554,12 +528,7 @@ struct flooritem_data : block_list
Item item_data;
};
-extern interval_t autosave_time;
-extern int save_settings;
-
-extern AString motd_txt;
-
-extern CharName wisp_server_name;
+extern const CharName WISP_SERVER_NAME;
// 鯖全体情報
void map_setusers(int);
@@ -580,21 +549,21 @@ public:
int map_addblock(dumb_ptr<block_list>);
int map_delblock(dumb_ptr<block_list>);
void map_foreachinarea(std::function<void(dumb_ptr<block_list>)>,
- map_local *,
+ Borrowed<map_local>,
int, int, int, int,
BL);
// -- moonsoul (added map_foreachincell)
void map_foreachincell(std::function<void(dumb_ptr<block_list>)>,
- map_local *,
+ Borrowed<map_local>,
int, int,
BL);
void map_foreachinmovearea(std::function<void(dumb_ptr<block_list>)>,
- map_local *,
+ Borrowed<map_local>,
int, int, int, int,
int, int,
BL);
//block関連に追加
-int map_count_oncell(map_local *m, int x, int y);
+int map_count_oncell(Borrowed<map_local> m, int x, int y);
// 一時的object関連
BlockId map_addobject(dumb_ptr<block_list>);
void map_delobject(BlockId, BL type);
@@ -604,7 +573,7 @@ void map_foreachobject(std::function<void(dumb_ptr<block_list>)>,
//
void map_quit(dumb_ptr<map_session_data>);
// npc
-int map_addnpc(map_local *, dumb_ptr<npc_data>);
+int map_addnpc(Borrowed<map_local>, dumb_ptr<npc_data>);
void map_log(XString line);
#define MAP_LOG(format, ...) \
@@ -612,7 +581,7 @@ void map_log(XString line);
#define MAP_LOG_PC(sd, fmt, ...) \
MAP_LOG("PC%d %s:%d,%d " fmt, \
- sd->status_key.char_id, (sd->bl_m ? sd->bl_m->name_ : stringish<MapName>("undefined.gat"_s)), sd->bl_x, sd->bl_y, ## __VA_ARGS__)
+ sd->status_key.char_id, (sd->bl_m->name_), sd->bl_x, sd->bl_y, ## __VA_ARGS__)
// 床アイテム関連
void map_clearflooritem_timer(TimerData *, tick_t, BlockId);
@@ -622,17 +591,15 @@ void map_clearflooritem(BlockId id)
map_clearflooritem_timer(nullptr, tick_t(), id);
}
BlockId map_addflooritem_any(Item *, int amount,
- map_local *m, int x, int y,
+ Borrowed<map_local> m, int x, int y,
dumb_ptr<map_session_data> *owners, interval_t *owner_protection,
interval_t lifetime, int dispersal);
BlockId map_addflooritem(Item *, int,
- map_local *, int, int,
+ Borrowed<map_local>, int, int,
dumb_ptr<map_session_data>, dumb_ptr<map_session_data>,
dumb_ptr<map_session_data>);
// キャラid=>キャラ名 変換関連
-extern
-DMap<BlockId, dumb_ptr<block_list>> id_db;
void map_addchariddb(CharId charid, CharName name);
CharName map_charid2nick(CharId);
@@ -671,8 +638,8 @@ dumb_ptr<magic::invocation> map_id_is_spell(BlockId id)
}
-map_local *map_mapname2mapid(MapName);
-int map_mapname2ipport(MapName, IP4Address *, int *);
+Option<Borrowed<map_local>> map_mapname2mapid(MapName);
+int map_mapname2ipport(MapName, Borrowed<IP4Address>, Borrowed<int>);
int map_setipport(MapName name, IP4Address ip, int port);
void map_addiddb(dumb_ptr<block_list>);
void map_deliddb(dumb_ptr<block_list> bl);
@@ -689,14 +656,14 @@ dumb_ptr<map_session_data> map_get_prev_session(
dumb_ptr<map_session_data> current);
// gat関連
-MapCell map_getcell(map_local *, int, int);
-void map_setcell(map_local *, int, int, MapCell);
+MapCell map_getcell(Borrowed<map_local>, int, int);
+void map_setcell(Borrowed<map_local>, int, int, MapCell);
// その他
bool map_check_dir(DIR s_dir, DIR t_dir);
DIR map_calc_dir(dumb_ptr<block_list> src, int x, int y);
-std::pair<uint16_t, uint16_t> map_randfreecell(map_local *m,
+std::pair<uint16_t, uint16_t> map_randfreecell(Borrowed<map_local> m,
uint16_t x, uint16_t y, uint16_t w, uint16_t h);
inline dumb_ptr<map_session_data> block_list::as_player() { return dumb_ptr<map_session_data>(static_cast<map_session_data *>(this)) ; }
@@ -722,4 +689,14 @@ inline dumb_ptr<npc_data_script> npc_data::is_script() { return npc_subtype == N
inline dumb_ptr<npc_data_shop> npc_data::is_shop() { return npc_subtype == NpcSubtype::SHOP ? as_shop() : nullptr ; }
inline dumb_ptr<npc_data_warp> npc_data::is_warp() { return npc_subtype == NpcSubtype::WARP ? as_warp() : nullptr ; }
inline dumb_ptr<npc_data_message> npc_data::is_message() { return npc_subtype == NpcSubtype::MESSAGE ? as_message() : nullptr ; }
+
+void map_addmap(MapName mapname);
+void map_delmap(MapName mapname);
+
+struct charid2nick
+{
+ CharName nick;
+ int req_id;
+};
+} // namespace map
} // namespace tmwa
diff --git a/src/map/map.py b/src/map/map.py
index cb29d53..c7adf56 100644
--- a/src/map/map.py
+++ b/src/map/map.py
@@ -1,7 +1,7 @@
class map_local(object):
__slots__ = ('_value')
- name = 'tmwa::map_local'
+ name = 'tmwa::map::map_local'
depth = 1
enabled = True
@@ -26,14 +26,14 @@ class map_local(object):
yield '->ys', value['ys']
tests = [
- ('static_cast<tmwa::map_local *>(nullptr)', '(map_local *) nullptr'),
+ ('static_cast<tmwa::map::map_local *>(nullptr)', '(map_local *) nullptr'),
('fake_map_local("map"_s, 42, 404)', '(map_local *) = {->name = "map", ->xs = 42, ->ys = 404}'),
]
class map_remote(object):
__slots__ = ('_value')
- name = 'tmwa::map_remote'
+ name = 'tmwa::map::map_remote'
depth = 1
enabled = True
@@ -58,14 +58,14 @@ class map_remote(object):
yield '->port', value['port']
tests = [
- ('static_cast<tmwa::map_remote *>(nullptr)', '(map_remote *) nullptr'),
+ ('static_cast<tmwa::map::map_remote *>(nullptr)', '(map_remote *) nullptr'),
('fake_map_remote("map"_s, tmwa::IP4Address({8, 8, 8, 8}), 6667)', '(map_remote *) = {->name = "map", ->ip = 8.8.8.8, ->port = 6667}'),
]
class map_abstract(object):
__slots__ = ('_value')
- name = 'tmwa::map_abstract'
+ name = 'tmwa::map::map_abstract'
depth = 1
enabled = True
@@ -79,19 +79,19 @@ class map_abstract(object):
if value is None:
return '(map_abstract *) nullptr'
gat = value.dereference()['gat']
- gat = gat.address.cast(gdb.lookup_type('tmwa::map_abstract').pointer().pointer()).dereference()
+ gat = gat.address.cast(gdb.lookup_type('tmwa::map::map_abstract').pointer().pointer()).dereference()
if gat:
- return value.cast(gdb.lookup_type('tmwa::map_local').pointer())
+ return value.cast(gdb.lookup_type('tmwa::map::map_local').pointer())
else:
- return value.cast(gdb.lookup_type('tmwa::map_remote').pointer())
+ return value.cast(gdb.lookup_type('tmwa::map::map_remote').pointer())
tests = [
- ('static_cast<tmwa::map_abstract *>(nullptr)', '(map_abstract *) nullptr'),
+ ('static_cast<tmwa::map::map_abstract *>(nullptr)', '(map_abstract *) nullptr'),
] + [
- ('static_cast<tmwa::map_abstract *>(%s); value->gat.reset(new tmwa::MapCell[1])' % expr, expected)
+ ('static_cast<tmwa::map::map_abstract *>(%s); value->gat.reset(new tmwa::map::MapCell[1])' % expr, expected)
for (expr, expected) in map_local.tests[1:]
] + [
- ('static_cast<tmwa::map_abstract *>(%s)' % expr, expected)
+ ('static_cast<tmwa::map::map_abstract *>(%s)' % expr, expected)
for (expr, expected) in map_remote.tests[1:]
]
@@ -99,9 +99,9 @@ class map_abstract(object):
using tmwa::operator "" _s;
inline
- tmwa::map_local *fake_map_local(tmwa::ZString name, int xs, int ys)
+ tmwa::map::map_local *fake_map_local(tmwa::ZString name, int xs, int ys)
{
- auto *p = new tmwa::map_local{};
+ auto *p = new tmwa::map::map_local{};
p->name_ = tmwa::stringish<tmwa::MapName>(name);
p->xs = xs;
p->ys = ys;
@@ -109,17 +109,17 @@ class map_abstract(object):
}
inline
- tmwa::map_remote *fake_map_remote(tmwa::ZString name, tmwa::IP4Address ip, uint16_t port)
+ tmwa::map::map_remote *fake_map_remote(tmwa::ZString name, tmwa::IP4Address ip, uint16_t port)
{
- auto *p = new tmwa::map_remote{};
+ auto *p = new tmwa::map::map_remote{};
p->name_ = tmwa::stringish<tmwa::MapName>(name);
p->ip = ip;
p->port = port;
return p;
}
- void fake_delete(tmwa::map_abstract *);
- void fake_delete(tmwa::map_abstract *map)
+ void fake_delete(tmwa::map::map_abstract *);
+ void fake_delete(tmwa::map::map_abstract *map)
{
delete map;
}
diff --git a/src/map/map.t.hpp b/src/map/map.t.hpp
index b475f9b..267c049 100644
--- a/src/map/map.t.hpp
+++ b/src/map/map.t.hpp
@@ -29,11 +29,13 @@
#include "../generic/enum.hpp"
#include "../mmo/ids.hpp"
-#include "../mmo/mmo.hpp"
+#include "../high/mmo.hpp"
namespace tmwa
{
+namespace map
+{
enum class BL : uint8_t
{
NUL,
@@ -191,13 +193,9 @@ ENUM_BITWISE_OPERATORS(MapCell)
}
using e::MapCell;
-struct MobName : VString<23> {};
-struct NpcName : VString<23> {};
-struct ScriptLabel : VString<23> {};
-struct ItemName : VString<23> {};
-
inline
BlockId account_to_block(AccountId a) { return wrap<BlockId>(unwrap<AccountId>(a)); }
inline
AccountId block_to_account(BlockId b) { return wrap<AccountId>(unwrap<BlockId>(b)); }
+} // namespace map
} // namespace tmwa
diff --git a/src/map/mapflag.cpp b/src/map/mapflag.cpp
index be2ae67..9f3c9ab 100644
--- a/src/map/mapflag.cpp
+++ b/src/map/mapflag.cpp
@@ -18,11 +18,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
+#include "../strings/xstring.hpp"
+
#include "../poison.hpp"
namespace tmwa
{
+namespace map
+{
// because bitfields, that's why
bool MapFlags::get(MapFlag mf) const
@@ -38,7 +42,7 @@ void MapFlags::set(MapFlag mf, bool val)
flags &=~ static_cast<unsigned>(mf);
}
-bool extract(XString str, MapFlag *mf)
+bool impl_extract(XString str, MapFlag *mf)
{
const struct
{
@@ -91,4 +95,5 @@ MapFlag map_flag_from_int(int shift)
{
return static_cast<MapFlag>(1 << shift);
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/mapflag.hpp b/src/map/mapflag.hpp
index 6d046fa..3538c56 100644
--- a/src/map/mapflag.hpp
+++ b/src/map/mapflag.hpp
@@ -22,11 +22,11 @@
#include <cstdint>
-#include "../mmo/extract.hpp" // TODO remove this (requires specializing the *other* half)
-
namespace tmwa
{
+namespace map
+{
// originally from script.cpp
// These are part of the script API, so they can't change ever,
// even though they are silly.
@@ -77,7 +77,8 @@ public:
void set(MapFlag, bool);
};
-bool extract(XString str, MapFlag *mf);
+bool impl_extract(XString str, MapFlag *mf);
MapFlag map_flag_from_int(int shift);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/mapflag.py b/src/map/mapflag.py
index fe5b016..b0a2f24 100644
--- a/src/map/mapflag.py
+++ b/src/map/mapflag.py
@@ -1,6 +1,6 @@
class MapFlags(object):
__slots__ = ('_value')
- name = 'tmwa::MapFlags'
+ name = 'tmwa::map::MapFlags'
enabled = True
def __init__(self, value):
@@ -52,12 +52,12 @@ class MapFlags(object):
('RESAVE', 30),
]
tests = [
- ('reinterpret_cast<const tmwa::MapFlags&>(static_cast<const unsigned int&>(0x80000000))', 'MapFlags(0x80000000)'),
- ('reinterpret_cast<const tmwa::MapFlags&>(static_cast<const unsigned int&>(0xf0000000))', 'MapFlags(TOWN | OUTSIDE | RESAVE | 0x80000000)'),
+ ('reinterpret_cast<const tmwa::map::MapFlags&>(static_cast<const unsigned int&>(0x80000000))', 'MapFlags(0x80000000)'),
+ ('reinterpret_cast<const tmwa::map::MapFlags&>(static_cast<const unsigned int&>(0xf0000000))', 'MapFlags(TOWN | OUTSIDE | RESAVE | 0x80000000)'),
] + [
- ('tmwa::MapFlags(); value.set(tmwa::MapFlag::%s, true)' % n, 'MapFlags(%s)' % n)
+ ('tmwa::map::MapFlags(); value.set(tmwa::map::MapFlag::%s, true)' % n, 'MapFlags(%s)' % n)
for (n, _) in junk
] + [
- ('reinterpret_cast<const tmwa::MapFlags&>(static_cast<const unsigned int&>(1 << %d))' % i, 'MapFlags(%s)' % n)
+ ('reinterpret_cast<const tmwa::map::MapFlags&>(static_cast<const unsigned int&>(1 << %d))' % i, 'MapFlags(%s)' % n)
for (n, i) in junk
]
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
index dd061d0..539b547 100644
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -36,18 +36,20 @@
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+#include "../io/extract.hpp"
#include "../io/read.hpp"
#include "../net/socket.hpp"
#include "../net/timer.hpp"
#include "../mmo/config_parse.hpp"
-#include "../mmo/extract.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
#include "../mmo/extract_enums.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "itemdb.hpp"
#include "map.hpp"
#include "npc.hpp"
@@ -61,6 +63,8 @@
namespace tmwa
{
+namespace map
+{
constexpr interval_t MIN_MOBTHINKTIME = 100_ms;
// Move probability in the negligent mode MOB (rate of 1000 minute)
@@ -68,8 +72,6 @@ constexpr random_::Fraction MOB_LAZYMOVEPERC {50, 1000};
// Warp probability in the negligent mode MOB (rate of 1000 minute)
constexpr random_::Fraction MOB_LAZYWARPPERC {20, 1000};
-static
-struct mob_db_ mob_db[2001];
struct mob_db_& get_mob_db(Species s)
{
return mob_db[unwrap<Species>(s)];
@@ -318,12 +320,12 @@ int mob_gen_exp(mob_db_ *mob)
(2 * mob->attrs[ATTR::LUK] * mob->max_hp / mod_def);
double attack_factor =
(mob->atk1 + mob->atk2 + mob->attrs[ATTR::STR] / 3.0 + mob->attrs[ATTR::DEX] / 2.0 +
- mob->attrs[ATTR::LUK]) * (1872.0 / mob->adelay) / 4;
+ mob->attrs[ATTR::LUK]) * (1872.0 / mob->adelay.count()) / 4;
double dodge_factor =
pow(mob->lv + mob->attrs[ATTR::AGI] + mob->attrs[ATTR::LUK] / 2.0, 4.0 / 3.0);
// TODO s/persuit/pursuit/g sometime when I'm not worried about diffs
double persuit_factor =
- (3 + mob->range) * bool(mob->mode & MobMode::CAN_MOVE) * 1000 / mob->speed;
+ (3 + mob->range) * bool(mob->mode & MobMode::CAN_MOVE) * 1000 / mob->speed.count();
double aggression_factor =
bool(mob->mode & MobMode::AGGRESSIVE)
? 10.0 / 9.0
@@ -331,8 +333,7 @@ int mob_gen_exp(mob_db_ *mob)
int xp = floor(effective_hp * pow(sqrt(attack_factor)
+ sqrt(dodge_factor)
+ sqrt(persuit_factor) + 55, 3)
- * aggression_factor / 2000000.0
- * static_cast<double>(battle_config.base_exp_rate) / 100.);
+ * aggression_factor / 2000000.0);
if (xp < 1)
xp = 1;
PRINTF("Exp for mob '%s' generated: %d\n"_fmt, mob->name, xp);
@@ -357,10 +358,10 @@ void mob_init(dumb_ptr<mob_data> md)
md->stats[mob_stat::LUK] = get_mob_db(mob_class).attrs[ATTR::LUK];
md->stats[mob_stat::ATK1] = get_mob_db(mob_class).atk1;
md->stats[mob_stat::ATK2] = get_mob_db(mob_class).atk2;
- md->stats[mob_stat::ADELAY] = get_mob_db(mob_class).adelay;
+ 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::SPEED] = get_mob_db(mob_class).speed;
+ md->stats[mob_stat::SPEED] = get_mob_db(mob_class).speed.count();
md->stats[mob_stat::XP_BONUS] = MOB_XP_BONUS_BASE;
for (i = 0; i < mutations_nr; i++)
@@ -410,15 +411,15 @@ BlockId mob_once_spawn(dumb_ptr<map_session_data> sd,
NpcEvent event)
{
dumb_ptr<mob_data> md = nullptr;
- map_local *m;
int count;
- if (sd && mapname == MOB_THIS_MAP)
- m = sd->bl_m;
- else
- m = map_mapname2mapid(mapname);
+ P<map_local> m = (
+ (sd && mapname == MOB_THIS_MAP)
+ ? sd->bl_m
+ : TRY_UNWRAP(map_mapname2mapid(mapname), return BlockId())
+ );
- if (m == nullptr || amount <= 0 || mobdb_checkid(mob_class) == Species())
+ if (amount <= 0 || mobdb_checkid(mob_class) == Species())
return BlockId();
if (sd)
@@ -470,18 +471,18 @@ BlockId mob_once_spawn_area(dumb_ptr<map_session_data> sd,
{
int x, y, i, max, lx = -1, ly = -1;
BlockId id;
- map_local *m;
- if (mapname == MOB_THIS_MAP)
- m = sd->bl_m;
- else
- m = map_mapname2mapid(mapname);
+ P<map_local> m = (
+ (mapname == MOB_THIS_MAP)
+ ? sd->bl_m
+ : TRY_UNWRAP(map_mapname2mapid(mapname), return BlockId())
+ );
max = (y1 - y0 + 1) * (x1 - x0 + 1) * 3;
if (max > 1000)
max = 1000;
- if (m == nullptr || amount <= 0 || (mobdb_checkid(mob_class) == Species())) // A summon is stopped if a value is unusual
+ if (amount <= 0 || (mobdb_checkid(mob_class) == Species())) // A summon is stopped if a value is unusual
return BlockId();
for (i = 0; i < amount; i++)
@@ -1148,7 +1149,7 @@ int mob_spawn(BlockId id)
mob_init(md);
if (!md->stats[mob_stat::SPEED])
- md->stats[mob_stat::SPEED] = get_mob_db(md->mob_class).speed;
+ md->stats[mob_stat::SPEED] = get_mob_db(md->mob_class).speed.count();
md->def_ele = get_mob_db(md->mob_class).element;
md->master_id = BlockId();
md->master_dist = 0;
@@ -1178,11 +1179,10 @@ int mob_spawn(BlockId id)
assert (!md->sc_data[i].timer);
md->sc_data[i].val1 = 0;
}
- md->sc_count = 0;
md->opt1 = Opt1::ZERO;
md->opt2 = Opt2::ZERO;
md->opt3 = Opt3::ZERO;
- md->option = Option::ZERO;
+ md->option = Opt0::ZERO;
md->hp = battle_get_max_hp(md);
if (md->hp <= 0)
@@ -1342,7 +1342,7 @@ int mob_target(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl, int dist)
nullpo_retz(bl);
sc_data = battle_get_sc_data(bl);
- Option *option = battle_get_option(bl);
+ Opt0 *option = battle_get_option(bl);
Race race = get_mob_db(md->mob_class).race;
if (md->mode == MobMode::ZERO)
@@ -1572,7 +1572,7 @@ int mob_ai_sub_hard_slavemob(dumb_ptr<mob_data> md, tick_t tick)
// Since it is in the map on which the master is not, teleport is carried out and it pursues.
if (mmd->bl_m != md->bl_m)
{
- mob_warp(md, mmd->bl_m, mmd->bl_x, mmd->bl_y, BeingRemoveWhy::WARPED);
+ mob_warp(md, Some(mmd->bl_m), mmd->bl_x, mmd->bl_y, BeingRemoveWhy::WARPED);
md->state.master_check = 1;
return 0;
}
@@ -1584,7 +1584,7 @@ int mob_ai_sub_hard_slavemob(dumb_ptr<mob_data> md, tick_t tick)
// Since the master was in near immediately before, teleport is carried out and it pursues.
if (old_dist < 10 && md->master_dist > 18)
{
- mob_warp(md, nullptr, mmd->bl_x, mmd->bl_y, BeingRemoveWhy::WARPED);
+ mob_warp(md, None, mmd->bl_x, mmd->bl_y, BeingRemoveWhy::WARPED);
md->state.master_check = 1;
return 0;
}
@@ -2177,7 +2177,7 @@ void mob_ai_lazy(TimerData *, tick_t tick)
*/
struct delay_item_drop
{
- map_local *m;
+ Borrowed<map_local> m = borrow(undefined_gat);
int x, y;
ItemNameId nameid;
int amount;
@@ -2186,7 +2186,7 @@ struct delay_item_drop
struct delay_item_drop2
{
- map_local *m;
+ Borrowed<map_local> m = borrow(undefined_gat);
int x, y;
Item item_data;
dumb_ptr<map_session_data> first_sd, second_sd, third_sd;
@@ -2541,7 +2541,6 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
int base_exp, job_exp, flag = 1;
double per;
- PartyPair p;
// [Fate] The above is the old formula. We do a more involved computation below.
// [o11c] Look in git history for old code, you idiot!
@@ -2592,16 +2591,17 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
);
if (it == ptv.end())
{
- p = party_search(pid);
- if (p && p->exp != 0)
+ Option<PartyPair> p_ = party_search(pid);
+ OMATCH_BEGIN_SOME (p, p_)
{
- DmgLogParty pn {};
- pn.p = p;
- pn.base_exp = base_exp;
- pn.job_exp = job_exp;
- ptv.push_back(pn);
- flag = 0;
+ if (p->exp != 0)
+ {
+ DmgLogParty pn{p, base_exp, job_exp};
+ ptv.push_back(pn);
+ flag = 0;
+ }
}
+ OMATCH_END ();
}
else
{
@@ -2763,7 +2763,7 @@ int mob_warpslave(dumb_ptr<mob_data> md, int x, int y)
* mobワープ
*------------------------------------------
*/
-int mob_warp(dumb_ptr<mob_data> md, map_local *m, int x, int y, BeingRemoveWhy type)
+int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m_, int x, int y, BeingRemoveWhy type)
{
int i = 0, xs = 0, ys = 0, bx = x, by = y;
@@ -2772,8 +2772,7 @@ int mob_warp(dumb_ptr<mob_data> md, map_local *m, int x, int y, BeingRemoveWhy t
if (md->bl_prev == nullptr)
return 0;
- if (m == nullptr)
- m = md->bl_m;
+ P<map_local> m = m_.copy_or(md->bl_m);
if (type != BeingRemoveWhy::NEGATIVE1)
{
@@ -2892,10 +2891,10 @@ int mob_summonslave(dumb_ptr<mob_data> md2, int *value_, int amount, int flag)
bx = md2->bl_x;
by = md2->bl_y;
- map_local *m = md2->bl_m;
+ P<map_local> m = md2->bl_m;
Species values[5];
- for (count = 0; count < 5 && values[count] != Species(); ++count)
+ for (count = 0; count < 5 && value_[count]; ++count)
values[count] = wrap<Species>(value_[count]);
if (count < 1)
return 0;
@@ -3417,10 +3416,10 @@ int mob_makedummymobdb(Species mob_class)
get_mob_db(mob_class).race = Race::formless;
get_mob_db(mob_class).element = LevelElement{0, Element::neutral};
get_mob_db(mob_class).mode = MobMode::ZERO;
- get_mob_db(mob_class).speed = 300;
- get_mob_db(mob_class).adelay = 1000;
- get_mob_db(mob_class).amotion = 500;
- get_mob_db(mob_class).dmotion = 500;
+ get_mob_db(mob_class).speed = 300_ms;
+ get_mob_db(mob_class).adelay = 1000_ms;
+ get_mob_db(mob_class).amotion = 500_ms;
+ get_mob_db(mob_class).dmotion = 500_ms;
for (i = 0; i < 8; i++)
{
get_mob_db(mob_class).dropitem[i].nameid = ItemNameId();
@@ -3430,7 +3429,7 @@ int mob_makedummymobdb(Species mob_class)
}
static
-bool extract(XString str, LevelElement *le)
+bool impl_extract(XString str, LevelElement *le)
{
int tmp;
if (extract(str, &tmp))
@@ -3531,30 +3530,33 @@ bool mob_readdb(ZString filename)
continue;
}
- // TODO move this lower
- get_mob_db(mob_class) = std::move(mdbv);
-
if (get_mob_db(mob_class).base_exp < 0)
- get_mob_db(mob_class).base_exp = 0;
- else if (get_mob_db(mob_class).base_exp > 0
- && (get_mob_db(mob_class).base_exp *
- battle_config.base_exp_rate / 100 > 1000000000
- || get_mob_db(mob_class).base_exp *
- battle_config.base_exp_rate / 100 < 0))
- get_mob_db(mob_class).base_exp = 1000000000;
- else
- get_mob_db(mob_class).base_exp = get_mob_db(mob_class).base_exp * battle_config.base_exp_rate / 100;
-
+ {
+ PRINTF("bad mob line: Xp needs to be greater than 0. %s\n"_fmt, line);
+ rv = false;
+ continue;
+ }
+ if (get_mob_db(mob_class).base_exp > 1000000000)
+ {
+ PRINTF("bad mob line: Xp needs to be less than 1000000000. %s\n"_fmt, line);
+ rv = false;
+ continue;
+ }
if (get_mob_db(mob_class).job_exp < 0)
- get_mob_db(mob_class).job_exp = 0;
- else if (get_mob_db(mob_class).job_exp > 0
- && (get_mob_db(mob_class).job_exp * battle_config.job_exp_rate /
- 100 > 1000000000
- || get_mob_db(mob_class).job_exp *
- battle_config.job_exp_rate / 100 < 0))
- get_mob_db(mob_class).job_exp = 1000000000;
- else
- get_mob_db(mob_class).job_exp = get_mob_db(mob_class).job_exp * battle_config.job_exp_rate / 100;
+ {
+ PRINTF("bad mob line: Job Xp needs to be greater than 0. %s\n"_fmt, line);
+ rv = false;
+ continue;
+ }
+ if (get_mob_db(mob_class).job_exp > 1000000000)
+ {
+ PRINTF("bad mob line: Job Xp needs to be less than 1000000000. %s\n"_fmt, line);
+ rv = false;
+ continue;
+ }
+
+ // TODO move this lower
+ get_mob_db(mob_class) = std::move(mdbv);
for (int i = 0; i < 8; i++)
{
@@ -3585,7 +3587,7 @@ bool mob_readdb(ZString filename)
}
static
-bool extract(XString str, MobSkillCondition *msc)
+bool impl_extract(XString str, MobSkillCondition *msc)
{
const struct
{
@@ -3609,7 +3611,7 @@ bool extract(XString str, MobSkillCondition *msc)
}
static
-bool extract(XString str, MobSkillState *mss)
+bool impl_extract(XString str, MobSkillState *mss)
{
const struct
{
@@ -3632,7 +3634,7 @@ bool extract(XString str, MobSkillState *mss)
}
static
-bool extract(XString str, MobSkillTarget *mst)
+bool impl_extract(XString str, MobSkillTarget *mst)
{
const struct
{
@@ -3745,4 +3747,5 @@ void do_init_mob2(void)
MIN_MOBTHINKTIME * 10
).detach();
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/mob.hpp b/src/map/mob.hpp
index d0cc07a..6d87228 100644
--- a/src/map/mob.hpp
+++ b/src/map/mob.hpp
@@ -20,24 +20,25 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "fwd.hpp"
-
#include "mob.t.hpp"
-#include "../generic/fwd.hpp"
+#include "fwd.hpp"
+
#include "../generic/enum.hpp"
#include "../generic/random.t.hpp"
#include "../net/timer.t.hpp"
#include "battle.t.hpp"
-#include "clif.t.hpp"
+#include "../mmo/clif.t.hpp"
#include "map.hpp"
-#include "skill.t.hpp"
+#include "../mmo/skill.t.hpp"
namespace tmwa
{
+namespace map
+{
#define ENGLISH_NAME stringish<MobName>("--en--"_s)
#define JAPANESE_NAME stringish<MobName>("--ja--"_s)
#define MOB_THIS_MAP stringish<MapName>("this"_s)
@@ -72,7 +73,7 @@ struct mob_db_
Race race;
LevelElement element;
MobMode mode;
- int speed, adelay, amotion, dmotion;
+ interval_t speed, adelay, amotion, dmotion;
int mutations_nr, mutation_power;
struct
{
@@ -127,7 +128,7 @@ int mob_deleteslave(dumb_ptr<mob_data> md);
int mob_counttargeted(dumb_ptr<mob_data> md, dumb_ptr<block_list> src,
ATK target_lv);
-int mob_warp(dumb_ptr<mob_data> md, map_local *m, int x, int y, BeingRemoveWhy type);
+int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m, int x, int y, BeingRemoveWhy type);
int mobskill_use(dumb_ptr<mob_data> md, tick_t tick, MobSkillCondition event);
int mobskill_event(dumb_ptr<mob_data> md, BF flag);
@@ -136,4 +137,5 @@ void mobskill_castend_pos(TimerData *tid, tick_t tick, BlockId id);
int mob_summonslave(dumb_ptr<mob_data> md2, int *value, int amount, int flag);
void mob_reload(void);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/mob.t.hpp b/src/map/mob.t.hpp
index 160a8a3..54e7ebe 100644
--- a/src/map/mob.t.hpp
+++ b/src/map/mob.t.hpp
@@ -27,6 +27,8 @@
namespace tmwa
{
+namespace map
+{
enum class MobSkillTarget
{
MST_TARGET = 0,
@@ -61,4 +63,5 @@ enum class MobSkillState : uint8_t
MSS_LOOT,
MSS_CHASE,
};
+} // namespace map
} // namespace tmwa
diff --git a/src/compat/rawmem.cpp b/src/map/npc-internal.hpp
index d322437..993263f 100644
--- a/src/compat/rawmem.cpp
+++ b/src/map/npc-internal.hpp
@@ -1,7 +1,9 @@
-#include "rawmem.hpp"
-// rawmem.cpp - Ignore poisoning and really frob this memory unsafely.
+#pragma once
+// npc-internal.hpp - Noncombatants.
//
-// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -18,9 +20,18 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "npc.hpp"
+#include "fwd.hpp"
namespace tmwa
{
+namespace map
+{
+struct event_data
+{
+ dumb_ptr<npc_data_script> nd;
+ int pos;
+};
+} // namespace map
} // namespace tmwa
diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp
new file mode 100644
index 0000000..4d9fcbd
--- /dev/null
+++ b/src/map/npc-parse.cpp
@@ -0,0 +1,807 @@
+#include "npc-parse.hpp"
+// npc-parse.cpp - Noncombatants.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <list>
+
+#include "../compat/nullpo.hpp"
+
+#include "../strings/astring.hpp"
+#include "../strings/xstring.hpp"
+#include "../strings/literal.hpp"
+
+#include "../generic/enum.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
+#include "../io/line.hpp"
+
+#include "../mmo/config_parse.hpp"
+
+#include "../high/extract_mmo.hpp"
+
+#include "../ast/npc.hpp"
+
+#include "battle.hpp"
+#include "battle_conf.hpp"
+#include "clif.hpp"
+#include "globals.hpp"
+#include "itemdb.hpp"
+#include "map.hpp"
+#include "mob.hpp"
+#include "npc-internal.hpp"
+#include "script-parse.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+static
+void npc_clearsrcfile(void)
+{
+ npc_srcs.clear();
+}
+
+void npc_addsrcfile(AString name)
+{
+ if (name == "clear"_s)
+ {
+ npc_clearsrcfile();
+ return;
+ }
+
+ npc_srcs.push_back(name);
+}
+
+void npc_delsrcfile(XString name)
+{
+ if (name == "all"_s)
+ {
+ npc_clearsrcfile();
+ return;
+ }
+
+ for (auto it = npc_srcs.begin(); it != npc_srcs.end(); ++it)
+ {
+ if (*it == name)
+ {
+ npc_srcs.erase(it);
+ return;
+ }
+ }
+}
+
+static
+void register_npc_name(dumb_ptr<npc_data> nd)
+{
+ earray<LString, NpcSubtype, NpcSubtype::COUNT> types //=
+ {{
+ "WARP"_s,
+ "SHOP"_s,
+ "SCRIPT"_s,
+ "MESSAGE"_s,
+ }};
+ if (!nd->name)
+ {
+ if (nd->npc_subtype == NpcSubtype::MESSAGE)
+ return;
+ PRINTF("WARNING: npc with no name:\n%s @ %s,%d,%d\n"_fmt,
+ types[nd->npc_subtype],
+ nd->bl_m->name_, nd->bl_x, nd->bl_y);
+ return;
+ }
+ if (dumb_ptr<npc_data> nd_old = npcs_by_name.get(nd->name))
+ {
+ if (nd->npc_subtype != NpcSubtype::WARP
+ || nd_old->npc_subtype != NpcSubtype::WARP)
+ {
+ PRINTF("WARNING: replacing npc with name: %s\n"_fmt, nd->name);
+ PRINTF("old: %s @ %s,%d,%d\n"_fmt,
+ types[nd_old->npc_subtype],
+ nd_old->bl_m->name_, nd_old->bl_x, nd_old->bl_y);
+ PRINTF("new: %s @ %s,%d,%d\n"_fmt,
+ types[nd->npc_subtype],
+ nd->bl_m->name_, nd->bl_x, nd->bl_y);
+ }
+ }
+ // TODO also check #s ?
+ npcs_by_name.put(nd->name, nd);
+}
+
+// extern for atcommand @addwarp
+bool npc_load_warp(ast::npc::Warp& warp)
+{
+ MapName mapname = warp.m.data;
+ int x = warp.x.data, y = warp.y.data;
+
+ int xs = warp.xs.data, ys = warp.ys.data;
+ MapName to_mapname = warp.to_m.data;
+ int to_x = warp.to_x.data, to_y = warp.to_y.data;
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort());
+
+ dumb_ptr<npc_data_warp> nd;
+ nd.new_();
+ nd->bl_id = npc_get_new_npc_id();
+ nd->n = map_addnpc(m, nd);
+
+ nd->bl_prev = nd->bl_next = nullptr;
+ nd->bl_m = m;
+ nd->bl_x = x;
+ nd->bl_y = y;
+ nd->dir = DIR::S;
+ nd->flag = 0;
+ nd->name = warp.name.data;
+
+ nd->npc_class = WARP_CLASS;
+ nd->speed = 200_ms;
+ nd->option = Opt0::ZERO;
+ nd->opt1 = Opt1::ZERO;
+ nd->opt2 = Opt2::ZERO;
+ nd->opt3 = Opt3::ZERO;
+ nd->warp.name = to_mapname;
+ nd->warp.x = to_x;
+ nd->warp.y = to_y;
+ nd->warp.xs = xs;
+ nd->warp.ys = ys;
+
+ for (int i = 0; i < ys; i++)
+ {
+ for (int j = 0; j < xs; j++)
+ {
+ int x_lo = x - xs / 2;
+ int y_lo = y - ys / 2;
+ int xc = x_lo + j;
+ int yc = y_lo + i;
+ MapCell t = map_getcell(m, xc, yc);
+ if (bool(t & MapCell::UNWALKABLE))
+ continue;
+ map_setcell(m, xc, yc, t | MapCell::NPC_NEAR);
+ }
+ }
+
+ npc_warp++;
+ nd->bl_type = BL::NPC;
+ nd->npc_subtype = NpcSubtype::WARP;
+ map_addblock(nd);
+ clif_spawnnpc(nd);
+ register_npc_name(nd);
+
+ return true;
+}
+
+static
+bool npc_load_shop(ast::npc::Shop& shop)
+{
+ MapName mapname = shop.m.data;
+ int x = shop.x.data, y = shop.y.data;
+ DIR dir = shop.d.data;
+ dumb_ptr<npc_data_shop> nd;
+ Species npc_class = shop.npc_class.data;
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort());
+
+ nd.new_();
+ nd->shop_items.reserve(shop.items.data.size());
+ for (auto& it : shop.items.data)
+ {
+ nd->shop_items.emplace_back();
+ auto& back = nd->shop_items.back();
+ P<item_data> id = ((extract(it.data.name.data, &back.nameid) && back.nameid)
+ ? ({
+ P<item_data> id_ = TRY_UNWRAP(itemdb_exists(back.nameid), { it.data.name.span.error("No item with this numerical id"_s); return false; });
+ id_;
+ })
+ : ({
+ P<item_data> id_ = TRY_UNWRAP(itemdb_searchname(XString(it.data.name.data)), { it.data.name.span.error("No item with this name"_s); return false; });
+ back.nameid = id_->nameid;
+ id_;
+ }));
+
+ back.value = it.data.value.data;
+ if (it.data.value_multiply)
+ {
+ back.value = id->value_buy * back.value;
+ }
+ }
+
+ nd->bl_prev = nd->bl_next = nullptr;
+ nd->bl_m = m;
+ nd->bl_x = x;
+ nd->bl_y = y;
+ nd->bl_id = npc_get_new_npc_id();
+ nd->dir = dir;
+ nd->flag = 0;
+ nd->name = shop.name.data;
+ nd->npc_class = npc_class;
+ nd->speed = 200_ms;
+ nd->option = Opt0::ZERO;
+ nd->opt1 = Opt1::ZERO;
+ nd->opt2 = Opt2::ZERO;
+ nd->opt3 = Opt3::ZERO;
+
+ npc_shop++;
+ nd->bl_type = BL::NPC;
+ nd->npc_subtype = NpcSubtype::SHOP;
+ nd->n = map_addnpc(m, nd);
+ map_addblock(nd);
+ clif_spawnnpc(nd);
+ register_npc_name(nd);
+
+ return true;
+}
+
+static
+bool npc_load_monster(ast::npc::Monster& monster)
+{
+ MapName mapname = monster.m.data;
+ int x = monster.x.data, y = monster.y.data;
+ int xs = monster.xs.data, ys = monster.ys.data;
+
+ Species mob_class = monster.mob_class.data;
+ int num = monster.num.data;
+ interval_t delay1 = monster.delay1.data;
+ interval_t delay2 = monster.delay2.data;
+ NpcEvent eventname = monster.event.data;
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort());
+
+ if (num > 1 && battle_config.mob_count_rate != 100)
+ {
+ num = num * battle_config.mob_count_rate / 100;
+ if (num < 1)
+ num = 1;
+ }
+
+ for (int i = 0; i < num; i++)
+ {
+ dumb_ptr<mob_data> md;
+ md.new_();
+
+ md->bl_prev = nullptr;
+ md->bl_next = nullptr;
+ md->bl_m = m;
+ md->bl_x = x;
+ md->bl_y = y;
+ MobName expected = get_mob_db(mob_class).jname;
+ if (monster.name.data != expected)
+ {
+ monster.name.span.warning(STRPRINTF("Visible label/jname should match: %s"_fmt, expected));
+ }
+ if (monster.name.data == ENGLISH_NAME)
+ md->name = get_mob_db(mob_class).name;
+ else if (monster.name.data == JAPANESE_NAME)
+ md->name = get_mob_db(mob_class).jname;
+ else
+ md->name = monster.name.data;
+
+ md->n = i;
+ md->mob_class = mob_class;
+ md->bl_id = npc_get_new_npc_id();
+ md->spawn.m = m;
+ md->spawn.x0 = x;
+ md->spawn.y0 = y;
+ md->spawn.xs = xs;
+ md->spawn.ys = ys;
+ md->spawn.delay1 = delay1;
+ md->spawn.delay2 = delay2;
+
+ really_memzero_this(&md->state);
+ // md->timer = nullptr;
+ md->target_id = BlockId();
+ md->attacked_id = BlockId();
+
+ md->lootitemv.clear();
+
+ md->npc_event = eventname;
+
+ md->bl_type = BL::MOB;
+ map_addiddb(md);
+ mob_spawn(md->bl_id);
+
+ npc_mob++;
+ }
+
+ return true;
+}
+
+static
+bool npc_load_mapflag(ast::npc::MapFlag& mapflag)
+{
+ MapName mapname = mapflag.m.data;
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort());
+
+ MapFlag mf;
+ if (!extract(mapflag.name.data, &mf))
+ {
+ mapflag.name.span.error("No such mapflag"_s);
+ return false;
+ }
+
+ if (mf == MapFlag::NOPVP)
+ {
+ if (mapflag.vec_extra.data.size())
+ {
+ mapflag.vec_extra.span.error("No extra argument expected for mapflag 'nopvp'"_s);
+ return false;
+ }
+ m->flag.set(MapFlag::NOPVP, 1);
+ m->flag.set(MapFlag::PVP, 0);
+ return true;
+ }
+
+ MapName savemap;
+ int savex, savey;
+
+ if (mf == MapFlag::NOSAVE)
+ {
+ if (mapflag.vec_extra.data.size() == 3
+ && extract(mapflag.vec_extra.data[0].data, &savemap)
+ && extract(mapflag.vec_extra.data[1].data, &savex)
+ && extract(mapflag.vec_extra.data[2].data, &savey)
+ && map_mapname2mapid(savemap).is_some())
+ {
+ m->save.map_ = savemap;
+ m->save.x = savex;
+ m->save.y = savey;
+ }
+ else
+ {
+ mapflag.vec_extra.span.error("Unable to extract nosave savepoint"_s);
+ return false;
+ }
+ }
+ else if (mf == MapFlag::RESAVE)
+ {
+ if (mapflag.vec_extra.data.size() == 3
+ && extract(mapflag.vec_extra.data[0].data, &savemap)
+ && extract(mapflag.vec_extra.data[1].data, &savex)
+ && extract(mapflag.vec_extra.data[2].data, &savey)
+ && map_mapname2mapid(savemap).is_some())
+ {
+ m->resave.map_ = savemap;
+ m->resave.x = savex;
+ m->resave.y = savey;
+ }
+ else
+ {
+ mapflag.vec_extra.span.error("Unable to extract resave savepoint"_s);
+ return false;
+ }
+ }
+ else
+ {
+ if (mapflag.vec_extra.data.size())
+ {
+ mapflag.vec_extra.span.error("No extra argument expected for mapflag"_s);
+ return false;
+ }
+ }
+ m->flag.set(mf, true);
+
+ return true;
+}
+
+static
+void npc_convertlabel_db(ScriptLabel lname, int pos, dumb_ptr<npc_data_script> nd)
+{
+ nullpo_retv(nd);
+
+ struct npc_label_list eln {};
+ eln.name = lname;
+ eln.pos = pos;
+ nd->scr.label_listv.push_back(std::move(eln));
+}
+
+static
+bool npc_load_script_function(ast::script::ScriptBody& body, ast::npc::ScriptFunction& script_function)
+{
+ std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script function \"%s\""_fmt, script_function.name.data), body, false);
+ if (script == nullptr)
+ return false;
+
+ userfunc_db.put(script_function.name.data, std::move(script));
+
+ return true;
+}
+
+static
+bool npc_load_script_none(ast::script::ScriptBody& body, ast::npc::ScriptNone& script_none)
+{
+ std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script npc \"%s\""_fmt, script_none.name.data), body, false);
+ if (script == nullptr)
+ return false;
+
+ dumb_ptr<npc_data_script> nd;
+ nd.new_();
+ nd->scr.event_needs_map = false;
+
+ nd->name = script_none.name.data;
+
+ nd->bl_prev = nd->bl_next = nullptr;
+ nd->bl_m = borrow(undefined_gat);
+ nd->bl_x = 0;
+ nd->bl_y = 0;
+ nd->bl_id = npc_get_new_npc_id();
+ nd->dir = DIR::S;
+ nd->flag = 0;
+ nd->npc_class = INVISIBLE_CLASS;
+ nd->speed = 200_ms;
+ nd->scr.script = std::move(script);
+ nd->option = Opt0::ZERO;
+ nd->opt1 = Opt1::ZERO;
+ nd->opt2 = Opt2::ZERO;
+ nd->opt3 = Opt3::ZERO;
+
+ npc_script++;
+ nd->bl_type = BL::NPC;
+ nd->npc_subtype = NpcSubtype::SCRIPT;
+
+ register_npc_name(nd);
+
+ for (auto& pair : scriptlabel_db)
+ npc_convertlabel_db(pair.first, pair.second, nd);
+
+ for (npc_label_list& el : nd->scr.label_listv)
+ {
+ ScriptLabel lname = el.name;
+ int pos = el.pos;
+
+ if (lname.startswith("On"_s))
+ {
+ struct event_data ev {};
+ ev.nd = nd;
+ ev.pos = pos;
+ NpcEvent buf;
+ buf.npc = nd->name;
+ buf.label = lname;
+ ev_db.insert(buf, ev);
+ }
+ }
+
+ for (npc_label_list& el : nd->scr.label_listv)
+ {
+ int t_ = 0;
+ ScriptLabel lname = el.name;
+ int pos = el.pos;
+ if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0)
+ {
+ interval_t t = static_cast<interval_t>(t_);
+
+ npc_timerevent_list tel {};
+ tel.timer = t;
+ tel.pos = pos;
+
+ auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel,
+ [](const npc_timerevent_list& l, const npc_timerevent_list& r)
+ {
+ return l.timer < r.timer;
+ }
+ );
+ assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer);
+
+ nd->scr.timer_eventv.insert(it, std::move(tel));
+ }
+ }
+ // The counter starts stopped with 0 ticks, which is the first event,
+ // unless there is none, in which case begin == end.
+ nd->scr.timer = interval_t::zero();
+ nd->scr.next_event = nd->scr.timer_eventv.begin();
+ // nd->scr.timerid = nullptr;
+
+ return true;
+}
+
+static
+bool npc_load_script_map(ast::script::ScriptBody& body, ast::npc::ScriptMap& script_map)
+{
+ MapName mapname = script_map.m.data;
+ int x = script_map.x.data, y = script_map.y.data;
+ DIR dir = script_map.d.data;
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname),
+ {
+ script_map.m.span.error("No such map"_s);
+ return false;
+ });
+
+ std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script npc \"%s\""_fmt, script_map.name.data), body, false);
+ if (script == nullptr)
+ return false;
+
+
+ dumb_ptr<npc_data_script> nd;
+ nd.new_();
+
+ Species npc_class = script_map.npc_class.data;
+ int xs = script_map.xs.data, ys = script_map.ys.data;
+
+ {
+ for (int i = 0; i < ys; i++)
+ {
+ for (int j = 0; j < xs; j++)
+ {
+ int x_lo = x - xs / 2;
+ int y_lo = y - ys / 2;
+ int xc = x_lo + j;
+ int yc = y_lo + i;
+ MapCell t = map_getcell(m, xc, yc);
+ if (bool(t & MapCell::UNWALKABLE))
+ continue;
+ map_setcell(m, xc, yc, t | MapCell::NPC_NEAR);
+ }
+ }
+
+ nd->scr.xs = xs;
+ nd->scr.ys = ys;
+ nd->scr.event_needs_map = true;
+ }
+
+ nd->name = script_map.name.data;
+
+ nd->bl_prev = nd->bl_next = nullptr;
+ nd->bl_m = m;
+ nd->bl_x = x;
+ nd->bl_y = y;
+ nd->bl_id = npc_get_new_npc_id();
+ nd->dir = dir;
+ nd->flag = 0;
+ nd->npc_class = npc_class;
+ nd->speed = 200_ms;
+ nd->scr.script = std::move(script);
+ nd->option = Opt0::ZERO;
+ nd->opt1 = Opt1::ZERO;
+ nd->opt2 = Opt2::ZERO;
+ nd->opt3 = Opt3::ZERO;
+
+ npc_script++;
+ nd->bl_type = BL::NPC;
+ nd->npc_subtype = NpcSubtype::SCRIPT;
+
+ nd->n = map_addnpc(m, nd);
+ map_addblock(nd);
+
+ clif_spawnnpc(nd);
+
+ register_npc_name(nd);
+
+ for (auto& pair : scriptlabel_db)
+ npc_convertlabel_db(pair.first, pair.second, nd);
+
+ for (npc_label_list& el : nd->scr.label_listv)
+ {
+ ScriptLabel lname = el.name;
+ int pos = el.pos;
+
+ if (lname.startswith("On"_s))
+ {
+ struct event_data ev {};
+ ev.nd = nd;
+ ev.pos = pos;
+ NpcEvent buf;
+ buf.npc = nd->name;
+ buf.label = lname;
+ ev_db.insert(buf, ev);
+ }
+ }
+
+ for (npc_label_list& el : nd->scr.label_listv)
+ {
+ int t_ = 0;
+ ScriptLabel lname = el.name;
+ int pos = el.pos;
+ if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0)
+ {
+ interval_t t = static_cast<interval_t>(t_);
+
+ npc_timerevent_list tel {};
+ tel.timer = t;
+ tel.pos = pos;
+
+ auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel,
+ [](const npc_timerevent_list& l, const npc_timerevent_list& r)
+ {
+ return l.timer < r.timer;
+ }
+ );
+ assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer);
+
+ nd->scr.timer_eventv.insert(it, std::move(tel));
+ }
+ }
+ // The counter starts stopped with 0 ticks, which is the first event,
+ // unless there is none, in which case begin == end.
+ nd->scr.timer = interval_t::zero();
+ nd->scr.next_event = nd->scr.timer_eventv.begin();
+ // nd->scr.timerid = nullptr;
+
+ return true;
+}
+
+static
+bool npc_load_script_any(ast::npc::Script *script)
+{
+ MATCH_BEGIN (*script)
+ {
+ MATCH_CASE (ast::npc::ScriptFunction&, script_function)
+ {
+ return npc_load_script_function(script->body, script_function);
+ }
+ MATCH_CASE (ast::npc::ScriptNone&, script_none)
+ {
+ return npc_load_script_none(script->body, script_none);
+ }
+ MATCH_CASE (ast::npc::ScriptMap&, script_map)
+ {
+ auto& mapname = script_map.m;
+ Option<P<map_local>> m = map_mapname2mapid(mapname.data);
+ if (m.is_none())
+ {
+ mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data));
+ return false;
+ }
+ return npc_load_script_map(script->body, script_map);
+ }
+ }
+ MATCH_END ();
+ abort();
+}
+
+dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y,
+ Species npc_class, NpcName name, AString message)
+{
+ dumb_ptr<npc_data_message> retval;
+ retval.new_();
+ retval->bl_id = npc_get_new_npc_id();
+ retval->bl_x = x;
+ retval->bl_y = y;
+ retval->bl_m = m;
+ retval->bl_type = BL::NPC;
+ retval->npc_subtype = NpcSubtype::MESSAGE;
+
+ retval->name = name;
+ if (message)
+ retval->message = message;
+
+ retval->npc_class = npc_class;
+ retval->speed = 200_ms;
+
+ clif_spawnnpc(retval);
+ map_addblock(retval);
+ map_addiddb(retval);
+ register_npc_name(retval);
+
+ return retval;
+}
+
+static
+bool load_one_npc(io::LineCharReader& fp, bool& done)
+{
+ auto res = TRY_UNWRAP(ast::npc::parse_top(fp), { done = true; return true; });
+ if (res.get_failure())
+ PRINTF("%s\n"_fmt, res.get_failure());
+ ast::npc::TopLevel tl = TRY_UNWRAP(std::move(res.get_success()), return false);
+
+ MATCH_BEGIN (tl)
+ {
+ MATCH_CASE (ast::npc::Comment&, c)
+ {
+ (void)c;
+ return true;
+ }
+ MATCH_CASE (ast::npc::Warp&, warp)
+ {
+ auto& mapname = warp.m;
+ Option<P<map_local>> m = map_mapname2mapid(mapname.data);
+ if (m.is_none())
+ {
+ mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data));
+ return false;
+ }
+ return npc_load_warp(warp);
+ }
+ MATCH_CASE (ast::npc::Shop&, shop)
+ {
+ auto& mapname = shop.m;
+ Option<P<map_local>> m = map_mapname2mapid(mapname.data);
+ if (m.is_none())
+ {
+ mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data));
+ return false;
+ }
+ return npc_load_shop(shop);
+ }
+ MATCH_CASE (ast::npc::Monster&, monster)
+ {
+ auto& mapname = monster.m;
+ Option<P<map_local>> m = map_mapname2mapid(mapname.data);
+ if (m.is_none())
+ {
+ mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data));
+ return false;
+ }
+ return npc_load_monster(monster);
+ }
+ MATCH_CASE (ast::npc::MapFlag&, mapflag)
+ {
+ auto& mapname = mapflag.m;
+ Option<P<map_local>> m = map_mapname2mapid(mapname.data);
+ if (m.is_none())
+ {
+ mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data));
+ return false;
+ }
+ return npc_load_mapflag(mapflag);
+ }
+ MATCH_CASE (ast::npc::Script&, script)
+ {
+ return npc_load_script_any(&script);
+ }
+ }
+ MATCH_END ();
+ abort();
+}
+
+static
+bool load_npc_file(ZString nsl)
+{
+ io::LineCharReader fp(nsl);
+ if (!fp.is_open())
+ {
+ PRINTF("file not found : %s\n"_fmt, nsl);
+ return false;
+ }
+ PRINTF("Loading NPCs [%d]: %-54s\r"_fmt, unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM),
+ nsl);
+
+ bool done = false;
+ while (!done)
+ {
+ if (!load_one_npc(fp, done))
+ return false;
+ }
+ return true;
+}
+
+bool do_init_npc(void)
+{
+ bool rv = true;
+
+ for (; !npc_srcs.empty(); npc_srcs.pop_front())
+ {
+ AString nsl = npc_srcs.front();
+ rv &= load_npc_file(nsl);
+ }
+ PRINTF("NPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d] %20s\n"_fmt,
+ unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), npc_warp, npc_shop, npc_script, npc_mob, ""_s);
+
+ if (script_errors)
+ {
+ PRINTF("Cowardly refusing to continue after %d errors\n"_fmt, script_errors);
+ rv = false;
+ }
+ return rv;
+}
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/npc-parse.hpp b/src/map/npc-parse.hpp
new file mode 100644
index 0000000..9bc3448
--- /dev/null
+++ b/src/map/npc-parse.hpp
@@ -0,0 +1,44 @@
+#pragma once
+// npc-parse.hpp - Noncombatants.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+bool npc_load_warp(ast::npc::Warp& warp);
+
+/**
+ * Spawns and installs a talk-only NPC
+ *
+ * \param message The message to speak. If message is nullptr, the NPC will not do anything at all.
+ */
+dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y,
+ Species class_, NpcName name, AString message);
+
+void npc_addsrcfile(AString name);
+void npc_delsrcfile(XString name);
+bool do_init_npc(void);
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index a6427d6..4296432 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -1,4 +1,4 @@
-#include "npc.hpp"
+#include "npc-internal.hpp"
// npc.cpp - Noncombatants.
//
// Copyright © ????-2004 Athena Dev Teams
@@ -38,23 +38,20 @@
#include "../generic/db.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/read.hpp"
+#include "../io/extract.hpp"
#include "../net/timer.hpp"
-#include "../mmo/config_parse.hpp"
-#include "../mmo/extract.hpp"
-#include "../mmo/utils.hpp"
-
#include "../proto2/map-user.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "itemdb.hpp"
#include "map.hpp"
-#include "mob.hpp"
#include "pc.hpp"
-#include "script.hpp"
+#include "script-call.hpp"
#include "skill.hpp"
#include "../poison.hpp"
@@ -62,14 +59,8 @@
namespace tmwa
{
-static
-std::list<AString> npc_srcs;
-
-static
-BlockId npc_id = START_NPC_NUM;
-static
-int npc_warp, npc_shop, npc_script, npc_mob;
-
+namespace map
+{
BlockId npc_get_new_npc_id(void)
{
BlockId rv = npc_id;
@@ -77,21 +68,6 @@ BlockId npc_get_new_npc_id(void)
return rv;
}
-struct event_data
-{
- dumb_ptr<npc_data_script> nd;
- int pos;
-};
-static
-Map<NpcEvent, struct event_data> ev_db;
-static
-DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
-
-// used for clock-based event triggers
-// only tm_min, tm_hour, and tm_mday are used
-static
-struct tm ev_tm_b;
-
/*==========================================
* NPCの無効化/有効化
* npc_enable
@@ -206,22 +182,6 @@ int npc_delete(dumb_ptr<npc_data> nd)
return 0;
}
-void npc_timer_event(NpcEvent eventname)
-{
- struct event_data *ev = ev_db.search(eventname);
- dumb_ptr<npc_data_script> nd;
-// int xs,ys;
-
- if ((ev == nullptr || (nd = ev->nd) == nullptr))
- {
- PRINTF("npc_event: event not found [%s]\n"_fmt,
- eventname);
- return;
- }
-
- run_script(ScriptPointer(nd->scr.script.get(), ev->pos), nd->bl_id, nd->bl_id);
-}
-
/*==========================================
* 全てのNPCのOn*イベント実行
*------------------------------------------
@@ -236,7 +196,7 @@ void npc_event_doall_sub(NpcEvent key, struct event_data *ev,
if (name == p)
{
- run_script_l(ScriptPointer(ev->nd->scr.script.get(), ev->pos), rid, ev->nd->bl_id,
+ run_script_l(ScriptPointer(borrow(*ev->nd->scr.script), ev->pos), rid, ev->nd->bl_id,
argv);
(*c)++;
}
@@ -259,7 +219,7 @@ void npc_event_do_sub(NpcEvent key, struct event_data *ev,
if (name == key)
{
- run_script_l(ScriptPointer(ev->nd->scr.script.get(), ev->pos), rid, ev->nd->bl_id,
+ run_script_l(ScriptPointer(borrow(*ev->nd->scr.script), ev->pos), rid, ev->nd->bl_id,
argv);
(*c)++;
}
@@ -353,7 +313,7 @@ void npc_timerevent(TimerData *, tick_t tick, BlockId id, interval_t data)
id, next));
}
- run_script(ScriptPointer(nd->scr.script.get(), te->pos), BlockId(), nd->bl_id);
+ run_script(ScriptPointer(borrow(*nd->scr.script), te->pos), BlockId(), nd->bl_id);
}
/// Start (or resume) counting ticks to the next npc_timerevent.
@@ -465,39 +425,36 @@ void npc_settimerevent_tick(dumb_ptr<npc_data_script> nd, interval_t newtimer)
int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
int mob_kill)
{
- struct event_data *ev = ev_db.search(eventname);
+ Option<P<struct event_data>> ev_ = ev_db.search(eventname);
dumb_ptr<npc_data_script> nd;
- int xs, ys;
if (sd == nullptr)
{
PRINTF("npc_event nullpo?\n"_fmt);
}
- if (ev == nullptr && eventname.label == stringish<ScriptLabel>("OnTouch"_s))
+ if (ev_.is_none() && eventname.label == stringish<ScriptLabel>("OnTouch"_s))
return 1;
- if (ev == nullptr || (nd = ev->nd) == nullptr)
+ P<struct event_data> ev = TRY_UNWRAP(ev_,
{
- if (mob_kill)
- {
- {
- return 0;
- }
- }
- else
- {
- if (battle_config.error_log)
- PRINTF("npc_event: event not found [%s]\n"_fmt,
- eventname);
- return 0;
- }
+ if (!mob_kill && battle_config.error_log)
+ PRINTF("npc_event: event not found [%s]\n"_fmt,
+ eventname);
+ return 0;
+ });
+ if ((nd = ev->nd) == nullptr)
+ {
+ if (!mob_kill && battle_config.error_log)
+ PRINTF("npc_event: event not found [%s]\n"_fmt,
+ eventname);
+ return 0;
}
- xs = nd->scr.xs;
- ys = nd->scr.ys;
- if (xs >= 0 && ys >= 0)
+ if (nd->scr.event_needs_map)
{
+ int xs = nd->scr.xs;
+ int ys = nd->scr.ys;
if (nd->bl_m != sd->bl_m)
return 1;
if (xs > 0
@@ -521,28 +478,7 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
sd->npc_id = nd->bl_id;
sd->npc_pos =
- run_script(ScriptPointer(nd->scr.script.get(), ev->pos), sd->bl_id, nd->bl_id);
- return 0;
-}
-
-static
-void npc_command_sub(NpcEvent key, struct event_data *ev, NpcName npcname, XString command)
-{
- if (ev->nd->name == npcname
- && key.label.startswith("OnCommand"_s))
- {
- XString temp = key.label.xslice_t(9);
-
- if (command == temp)
- run_script(ScriptPointer(ev->nd->scr.script.get(), ev->pos), BlockId(), ev->nd->bl_id);
- }
-}
-
-int npc_command(dumb_ptr<map_session_data>, NpcName npcname, XString command)
-{
- for (auto& pair : ev_db)
- npc_command_sub(pair.first, &pair.second, npcname, command);
-
+ run_script(ScriptPointer(borrow(*nd->scr.script), ev->pos), sd->bl_id, nd->bl_id);
return 0;
}
@@ -550,7 +486,7 @@ int npc_command(dumb_ptr<map_session_data>, NpcName npcname, XString command)
* 接触型のNPC処理
*------------------------------------------
*/
-int npc_touch_areanpc(dumb_ptr<map_session_data> sd, map_local *m, int x, int y)
+int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int x, int y)
{
int i, f = 1;
int xs, ys;
@@ -647,7 +583,7 @@ int npc_checknear(dumb_ptr<map_session_data> sd, BlockId id)
if (nd->bl_type != BL::NPC)
return 1;
- if (nd->npc_class == NEGATIVE_SPECIES)
+ if (nd->npc_class == INVISIBLE_CLASS)
return 0;
// エリア判定
@@ -696,7 +632,7 @@ int npc_click(dumb_ptr<map_session_data> sd, BlockId id)
npc_event_dequeue(sd);
break;
case NpcSubtype::SCRIPT:
- sd->npc_pos = run_script(ScriptPointer(nd->is_script()->scr.script.get(), 0), sd->bl_id, id);
+ sd->npc_pos = run_script(ScriptPointer(borrow(*nd->is_script()->scr.script), 0), sd->bl_id, id);
break;
case NpcSubtype::MESSAGE:
if (nd->is_message()->message)
@@ -737,7 +673,7 @@ int npc_scriptcont(dumb_ptr<map_session_data> sd, BlockId id)
return 0;
}
- sd->npc_pos = run_script(ScriptPointer(nd->is_script()->scr.script.get(), sd->npc_pos), sd->bl_id, id);
+ sd->npc_pos = run_script(ScriptPointer(borrow(*nd->is_script()->scr.script), sd->npc_pos), sd->bl_id, id);
return 0;
}
@@ -848,8 +784,7 @@ int npc_buylist(dumb_ptr<map_session_data> sd,
const uint16_t& item_l_count = item_list[i].count;
const ItemNameId& item_l_id = item_list[i].name_id;
- struct item_data *item_data;
- if ((item_data = itemdb_exists(item_l_id)) != nullptr)
+ P<struct item_data> item_data = TRY_UNWRAP(itemdb_exists(item_l_id), continue);
{
int amount = item_l_count;
Item item_tmp {};
@@ -918,718 +853,6 @@ int npc_selllist(dumb_ptr<map_session_data> sd,
}
-//
-// 初期化関係
-//
-
-/*==========================================
- * 読み込むnpcファイルのクリア
- *------------------------------------------
- */
-static
-void npc_clearsrcfile(void)
-{
- npc_srcs.clear();
-}
-
-/*==========================================
- * 読み込むnpcファイルの追加
- *------------------------------------------
- */
-void npc_addsrcfile(AString name)
-{
- if (name == "clear"_s)
- {
- npc_clearsrcfile();
- return;
- }
-
- npc_srcs.push_back(name);
-}
-
-/*==========================================
- * 読み込むnpcファイルの削除
- *------------------------------------------
- */
-void npc_delsrcfile(XString name)
-{
- if (name == "all"_s)
- {
- npc_clearsrcfile();
- return;
- }
-
- for (auto it = npc_srcs.begin(); it != npc_srcs.end(); ++it)
- {
- if (*it == name)
- {
- npc_srcs.erase(it);
- return;
- }
- }
-}
-
-static
-void register_npc_name(dumb_ptr<npc_data> nd)
-{
- earray<LString, NpcSubtype, NpcSubtype::COUNT> types //=
- {{
- "WARP"_s,
- "SHOP"_s,
- "SCRIPT"_s,
- "MESSAGE"_s,
- }};
- if (!nd->name)
- {
- if (nd->npc_subtype == NpcSubtype::MESSAGE)
- return;
- PRINTF("WARNING: npc with no name:\n%s @ %s,%d,%d\n"_fmt,
- types[nd->npc_subtype],
- nd->bl_m->name_, nd->bl_x, nd->bl_y);
- return;
- }
- if (dumb_ptr<npc_data> nd_old = npcs_by_name.get(nd->name))
- {
- if (nd->npc_subtype != NpcSubtype::WARP
- || nd_old->npc_subtype != NpcSubtype::WARP)
- {
- PRINTF("WARNING: replacing npc with name: %s\n"_fmt, nd->name);
- PRINTF("old: %s @ %s,%d,%d\n"_fmt,
- types[nd_old->npc_subtype],
- nd_old->bl_m->name_, nd_old->bl_x, nd_old->bl_y);
- PRINTF("new: %s @ %s,%d,%d\n"_fmt,
- types[nd->npc_subtype],
- nd->bl_m->name_, nd->bl_x, nd->bl_y);
- }
- }
- // TODO also check #s ?
- npcs_by_name.put(nd->name, nd);
-}
-
-/*==========================================
- * warp行解析
- *------------------------------------------
- */
-int npc_parse_warp(XString w1, XString, NpcName w3, XString w4)
-{
- int x, y, xs, ys, to_x, to_y;
- int i, j;
- MapName mapname, to_mapname;
- dumb_ptr<npc_data_warp> nd;
-
- if (!extract(w1, record<','>(&mapname, &x, &y)) ||
- !extract(w4, record<','>(&xs, &ys, &to_mapname, &to_x, &to_y)))
- {
- PRINTF("bad warp line : %s\n"_fmt, w3);
- return 1;
- }
-
- map_local *m = map_mapname2mapid(mapname);
-
- nd.new_();
- nd->bl_id = npc_get_new_npc_id();
- nd->n = map_addnpc(m, nd);
-
- nd->bl_prev = nd->bl_next = nullptr;
- nd->bl_m = m;
- nd->bl_x = x;
- nd->bl_y = y;
- nd->dir = DIR::S;
- nd->flag = 0;
- nd->name = w3;
-
- if (!battle_config.warp_point_debug)
- nd->npc_class = WARP_CLASS;
- else
- nd->npc_class = WARP_DEBUG_CLASS;
- nd->speed = 200_ms;
- nd->option = Option::ZERO;
- nd->opt1 = Opt1::ZERO;
- nd->opt2 = Opt2::ZERO;
- nd->opt3 = Opt3::ZERO;
- nd->warp.name = to_mapname;
- xs += 2;
- ys += 2;
- nd->warp.x = to_x;
- nd->warp.y = to_y;
- nd->warp.xs = xs;
- nd->warp.ys = ys;
-
- for (i = 0; i < ys; i++)
- {
- for (j = 0; j < xs; j++)
- {
- int x_lo = x - xs / 2;
- int y_lo = y - ys / 2;
- int xc = x_lo + j;
- int yc = y_lo + i;
- MapCell t = map_getcell(m, xc, yc);
- if (bool(t & MapCell::UNWALKABLE))
- continue;
- map_setcell(m, xc, yc, t | MapCell::NPC_NEAR);
- }
- }
-
- npc_warp++;
- nd->bl_type = BL::NPC;
- nd->npc_subtype = NpcSubtype::WARP;
- map_addblock(nd);
- clif_spawnnpc(nd);
- register_npc_name(nd);
-
- return 0;
-}
-
-static
-bool extract(XString xs, npc_item_list *itv)
-{
- XString name_or_id;
- if (!extract(xs, record<':'>(&name_or_id, &itv->value)))
- return false;
- struct item_data *id = nullptr;
- if (extract(name_or_id, &itv->nameid) && itv->nameid)
- goto return_true;
-
- id = itemdb_searchname(name_or_id.rstrip());
- if (id == nullptr)
- return false;
- itv->nameid = id->nameid;
- goto return_true;
-
-return_true:
- if (itv->value < 0)
- {
- if (id == nullptr)
- id = itemdb_search(itv->nameid);
- itv->value = id->value_buy * abs(itv->value);
- }
- return true;
-}
-
-/*==========================================
- * shop行解析
- *------------------------------------------
- */
-static
-int npc_parse_shop(XString w1, XString, NpcName w3, ZString w4a)
-{
- int x, y;
- DIR dir;
- MapName mapname;
- dumb_ptr<npc_data_shop> nd;
- ZString::iterator w4comma;
- Species npc_class;
-
- int dir_; // TODO use enum directly in extract
- if (!extract(w1, record<','>(&mapname, &x, &y, &dir_))
- || dir_ < 0 || dir_ >= 8
- || (w4comma = std::find(w4a.begin(), w4a.end(), ',')) == w4a.end()
- || !extract(w4a.xislice_h(w4comma), &npc_class))
- {
- PRINTF("bad shop line : %s\n"_fmt, w3);
- return 1;
- }
- dir = static_cast<DIR>(dir_);
- map_local *m = map_mapname2mapid(mapname);
-
- nd.new_();
- ZString w4b = w4a.xislice_t(w4comma + 1);
-
- if (!extract(w4b, vrec<','>(&nd->shop_items)))
- {
- PRINTF("bad shop items : %s\n"_fmt, w3);
- PRINTF(" somewhere --> %s\n"_fmt, w4b);
- nd->shop_items.clear();
- }
-
- if (nd->shop_items.empty())
- {
- nd.delete_();
- return 1;
- }
-
- nd->bl_prev = nd->bl_next = nullptr;
- nd->bl_m = m;
- nd->bl_x = x;
- nd->bl_y = y;
- nd->bl_id = npc_get_new_npc_id();
- nd->dir = dir;
- nd->flag = 0;
- nd->name = w3;
- nd->npc_class = npc_class;
- nd->speed = 200_ms;
- nd->option = Option::ZERO;
- nd->opt1 = Opt1::ZERO;
- nd->opt2 = Opt2::ZERO;
- nd->opt3 = Opt3::ZERO;
-
- npc_shop++;
- nd->bl_type = BL::NPC;
- nd->npc_subtype = NpcSubtype::SHOP;
- nd->n = map_addnpc(m, nd);
- map_addblock(nd);
- clif_spawnnpc(nd);
- register_npc_name(nd);
-
- return 0;
-}
-
-/*==========================================
- * NPCのラベルデータコンバート
- *------------------------------------------
- */
-static
-void npc_convertlabel_db(ScriptLabel lname, int pos, dumb_ptr<npc_data_script> nd)
-{
- nullpo_retv(nd);
-
- struct npc_label_list eln {};
- eln.name = lname;
- eln.pos = pos;
- nd->scr.label_listv.push_back(std::move(eln));
-}
-
-/*==========================================
- * script行解析
- *------------------------------------------
- */
-static
-int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4,
- XString first_line, io::ReadFile& fp, int *lines)
-{
- int x, y;
- DIR dir = DIR::S;
- map_local *m;
- int xs = 0, ys = 0; // [Valaris] thanks to fov
- Species npc_class;
- MapName mapname;
- std::unique_ptr<const ScriptBuffer> script = nullptr;
- dumb_ptr<npc_data_script> nd;
- int evflag = 0;
-
- if (w1 == "-"_s)
- {
- x = 0;
- y = 0;
- m = nullptr;
- }
- else
- {
- int dir_; // TODO use enum directly in extract
- if (!extract(w1, record<','>(&mapname, &x, &y, &dir_))
- || dir_ < 0 || dir_ >= 8
- || (w2 == "script"_s && !w4.contains(',')))
- {
- PRINTF("bad script line : %s\n"_fmt, w3);
- return 1;
- }
- dir = static_cast<DIR>(dir_);
- m = map_mapname2mapid(mapname);
- }
-
- if (w2 == "script"_s)
- {
- // may be empty
- MString srcbuf;
- srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{'));
- // Note: it was a bug that this was missing. I think.
- int startline = *lines;
-
- // while (!srcbuf.rstrip().endswith('}'))
- while (true)
- {
- auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; });
- if (it != srcbuf.rend() && *it == '}')
- break;
-
- AString line;
- if (!fp.getline(line))
- // eof
- break;
- (*lines)++;
- if (!srcbuf)
- {
- // may be a no-op
- srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{'));
- // safe to execute more than once
- // But will usually only happen once
- startline = *lines;
- }
- else
- srcbuf += line;
- srcbuf += '\n';
- }
- script = parse_script(AString(srcbuf), startline, false);
- if (script == nullptr)
- // script parse error?
- return 1;
- }
- else
- {
- assert(0 && "duplicate() is no longer supported!\n"_s);
- return 0;
- }
-
- nd.new_();
-
- if (m == nullptr)
- {
- }
- else if (extract(w4, record<','>(&npc_class, &xs, &ys)))
- {
- if (xs >= 0)
- xs = xs * 2 + 1;
- if (ys >= 0)
- ys = ys * 2 + 1;
-
- if (npc_class != NEGATIVE_SPECIES)
- {
-
- for (int i = 0; i < ys; i++)
- {
- for (int j = 0; j < xs; j++)
- {
- int x_lo = x - xs / 2;
- int y_lo = y - ys / 2;
- int xc = x_lo + j;
- int yc = y_lo + i;
- MapCell t = map_getcell(m, xc, yc);
- if (bool(t & MapCell::UNWALKABLE))
- continue;
- map_setcell(m, xc, yc, t | MapCell::NPC_NEAR);
- }
- }
- }
-
- nd->scr.xs = xs;
- nd->scr.ys = ys;
- }
- else
- {
- XString w4x = w4;
- if (w4x.endswith(','))
- w4x = w4x.xrslice_h(1);
- if (!extract(w4x, &npc_class))
- abort();
- nd->scr.xs = 0;
- nd->scr.ys = 0;
- }
-
- if (npc_class == NEGATIVE_SPECIES && m != nullptr)
- {
- evflag = 1;
- }
-
- if (w3.contains(':'))
- {
- assert(false && "feature removed"_s);
- abort();
- }
-
- {
- nd->name = w3;
- }
-
- nd->bl_prev = nd->bl_next = nullptr;
- nd->bl_m = m;
- nd->bl_x = x;
- nd->bl_y = y;
- nd->bl_id = npc_get_new_npc_id();
- nd->dir = dir;
- nd->flag = 0;
- nd->npc_class = npc_class;
- nd->speed = 200_ms;
- nd->scr.script = std::move(script);
- nd->option = Option::ZERO;
- nd->opt1 = Opt1::ZERO;
- nd->opt2 = Opt2::ZERO;
- nd->opt3 = Opt3::ZERO;
-
- npc_script++;
- nd->bl_type = BL::NPC;
- nd->npc_subtype = NpcSubtype::SCRIPT;
- if (m != nullptr)
- {
- nd->n = map_addnpc(m, nd);
- map_addblock(nd);
-
- if (evflag)
- {
- struct event_data ev {};
- ev.nd = nd;
- ev.pos = 0;
- NpcEvent npcev;
- npcev.npc = nd->name;
- npcev.label = ScriptLabel();
- ev_db.insert(npcev, ev);
- }
- else
- clif_spawnnpc(nd);
- }
- register_npc_name(nd);
-
- for (auto& pair : scriptlabel_db)
- npc_convertlabel_db(pair.first, pair.second, nd);
-
- for (npc_label_list& el : nd->scr.label_listv)
- {
- ScriptLabel lname = el.name;
- int pos = el.pos;
-
- if (lname.startswith("On"_s))
- {
- struct event_data ev {};
- ev.nd = nd;
- ev.pos = pos;
- NpcEvent buf;
- buf.npc = nd->name;
- buf.label = lname;
- ev_db.insert(buf, ev);
- }
- }
-
- //-----------------------------------------
- // ラベルデータからタイマーイベント取り込み
- for (npc_label_list& el : nd->scr.label_listv)
- {
- int t_ = 0;
- ScriptLabel lname = el.name;
- int pos = el.pos;
- if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0)
- {
- interval_t t = static_cast<interval_t>(t_);
-
- npc_timerevent_list tel {};
- tel.timer = t;
- tel.pos = pos;
-
- auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel,
- [](const npc_timerevent_list& l, const npc_timerevent_list& r)
- {
- return l.timer < r.timer;
- }
- );
- assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer);
-
- nd->scr.timer_eventv.insert(it, std::move(tel));
- }
- }
- // The counter starts stopped with 0 ticks, which is the first event,
- // unless there is none, in which case begin == end.
- nd->scr.timer = interval_t::zero();
- nd->scr.next_event = nd->scr.timer_eventv.begin();
- // nd->scr.timerid = nullptr;
-
- return 0;
-}
-
-/*==========================================
- * function行解析
- *------------------------------------------
- */
-static
-int npc_parse_function(XString, XString, XString w3, ZString,
- XString first_line, io::ReadFile& fp, int *lines)
-{
- MString srcbuf;
- srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{'));
- int startline = *lines;
-
- while (true)
- {
- auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; });
- if (it != srcbuf.rend() && *it == '}')
- break;
-
- AString line;
- if (!fp.getline(line))
- break;
- (*lines)++;
- if (!srcbuf)
- {
- srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{'));
- startline = *lines;
- }
- else
- srcbuf += line;
- srcbuf += '\n';
- }
- std::unique_ptr<const ScriptBuffer> script = parse_script(AString(srcbuf), startline, false);
- if (script == nullptr)
- {
- // script parse error?
- return 1;
- }
-
- userfunc_db.put(w3, std::move(script));
-
- return 0;
-}
-
-/*==========================================
- * mob行解析
- *------------------------------------------
- */
-static
-int npc_parse_mob(XString w1, XString, MobName w3, ZString w4)
-{
- int x, y, xs, ys, num;
- Species mob_class;
- int i;
- MapName mapname;
- NpcEvent eventname;
- dumb_ptr<mob_data> md;
-
- xs = ys = 0;
- int delay1_ = 0, delay2_ = 0;
- if (!extract(w1, record<',', 3>(&mapname, &x, &y, &xs, &ys)) ||
- !extract(w4, record<',', 2>(&mob_class, &num, &delay1_, &delay2_, &eventname)))
- {
- PRINTF("bad monster line : %s\n"_fmt, w3);
- return 1;
- }
- interval_t delay1 = std::chrono::milliseconds(delay1_);
- interval_t delay2 = std::chrono::milliseconds(delay2_);
-
- map_local *m = map_mapname2mapid(mapname);
-
- if (num > 1 && battle_config.mob_count_rate != 100)
- {
- if ((num = num * battle_config.mob_count_rate / 100) < 1)
- num = 1;
- }
-
- for (i = 0; i < num; i++)
- {
- md.new_();
-
- md->bl_prev = nullptr;
- md->bl_next = nullptr;
- md->bl_m = m;
- md->bl_x = x;
- md->bl_y = y;
- if (w3 == ENGLISH_NAME)
- md->name = get_mob_db(mob_class).name;
- else if (w3 == JAPANESE_NAME)
- md->name = get_mob_db(mob_class).jname;
- else
- md->name = w3;
-
- md->n = i;
- md->mob_class = mob_class;
- md->bl_id = npc_get_new_npc_id();
- md->spawn.m = m;
- md->spawn.x0 = x;
- md->spawn.y0 = y;
- md->spawn.xs = xs;
- md->spawn.ys = ys;
- md->spawn.delay1 = delay1;
- md->spawn.delay2 = delay2;
-
- really_memzero_this(&md->state);
- // md->timer = nullptr;
- md->target_id = BlockId();
- md->attacked_id = BlockId();
-
- md->lootitemv.clear();
-
- md->npc_event = eventname;
-
- md->bl_type = BL::MOB;
- map_addiddb(md);
- mob_spawn(md->bl_id);
-
- npc_mob++;
- }
-
- return 0;
-}
-
-/*==========================================
- * マップフラグ行の解析
- *------------------------------------------
- */
-static
-int npc_parse_mapflag(XString w1, XString, XString w3, ZString w4)
-{
- MapName mapname, savemap;
- int savex, savey;
-
- mapname = stringish<MapName>(w1);
- if (!mapname)
- return 1;
-
- map_local *m = map_mapname2mapid(mapname);
- if (m == nullptr)
- return 1;
-
- MapFlag mf;
- if (!extract(w3, &mf))
- return 1;
-
- if (battle_config.pk_mode && mf == MapFlag::NOPVP)
- {
- m->flag.set(MapFlag::NOPVP, 1);
- m->flag.set(MapFlag::PVP, 0);
- return 0;
- }
-
- if (mf == MapFlag::NOSAVE)
- {
- if (w4 == "SavePoint"_s)
- {
- m->save.map_ = stringish<MapName>("SavePoint"_s);
- m->save.x = -1;
- m->save.y = -1;
- }
- else if (extract(w4, record<','>(&savemap, &savex, &savey)))
- {
- m->save.map_ = savemap;
- m->save.x = savex;
- m->save.y = savey;
- }
- }
- if (mf == MapFlag::RESAVE)
- {
- if (extract(w4, record<','>(&savemap, &savex, &savey)))
- {
- m->resave.map_ = savemap;
- m->resave.x = savex;
- m->resave.y = savey;
- }
- }
- m->flag.set(mf, true);
-
- return 0;
-}
-
-dumb_ptr<npc_data> npc_spawn_text(map_local *m, int x, int y,
- Species npc_class, NpcName name, AString message)
-{
- dumb_ptr<npc_data_message> retval;
- retval.new_();
- retval->bl_id = npc_get_new_npc_id();
- retval->bl_x = x;
- retval->bl_y = y;
- retval->bl_m = m;
- retval->bl_type = BL::NPC;
- retval->npc_subtype = NpcSubtype::MESSAGE;
-
- retval->name = name;
- if (message)
- retval->message = message;
-
- retval->npc_class = npc_class;
- retval->speed = 200_ms;
-
- clif_spawnnpc(retval);
- map_addblock(retval);
- map_addiddb(retval);
- register_npc_name(retval);
-
- return retval;
-}
-
static
void npc_free_internal(dumb_ptr<npc_data> nd_)
{
@@ -1675,116 +898,5 @@ void npc_free(dumb_ptr<npc_data> nd)
map_delblock(nd);
npc_free_internal(nd);
}
-
-/*==========================================
- * npc初期化
- *------------------------------------------
- */
-bool do_init_npc(void)
-{
- bool rv = true;
- // other fields unused
- ev_tm_b.tm_min = -1;
- ev_tm_b.tm_hour = -1;
- ev_tm_b.tm_mday = -1;
-
- for (; !npc_srcs.empty(); npc_srcs.pop_front())
- {
- AString nsl = npc_srcs.front();
- io::ReadFile fp(nsl);
- if (!fp.is_open())
- {
- PRINTF("file not found : %s\n"_fmt, nsl);
- rv = false;
- continue;
- }
- PRINTF("\rLoading NPCs [%d]: %-54s"_fmt, unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM),
- nsl);
- int lines = 0;
- AString zline;
- while (fp.getline(zline))
- {
- XString w1, w2, w3, w4x;
- ZString w4z;
- lines++;
-
- if (is_comment(zline))
- continue;
-
- if (!extract(zline, record<'|', 3>(&w1, &w2, &w3, &w4x)) || !w1 || !w2 || !w3)
- {
- FPRINTF(stderr, "%s:%d: Broken script line: %s\n"_fmt, nsl, lines, zline);
- rv = false;
- continue;
- }
- if (&*w4x.end() == &*zline.end())
- {
- w4z = zline.xrslice_t(w4x.size());
- }
- assert(bool(w4x) == bool(w4z));
-
- if (w1 != "-"_s && w1 != "function"_s)
- {
- auto comma = std::find(w1.begin(), w1.end(), ',');
- MapName mapname = stringish<MapName>(w1.xislice_h(comma));
- map_local *m = map_mapname2mapid(mapname);
- if (m == nullptr)
- {
- // "mapname" is not assigned to this server
- FPRINTF(stderr, "%s:%d: Map not found: %s\n"_fmt, nsl, lines, mapname);
- rv = false;
- continue;
- }
- }
- if (w2 == "warp"_s)
- {
- NpcName npcname = stringish<NpcName>(w3);
- npc_parse_warp(w1, w2, npcname, w4z);
- }
- else if (w2 == "shop"_s)
- {
- NpcName npcname = stringish<NpcName>(w3);
- npc_parse_shop(w1, w2, npcname, w4z);
- }
- else if (w2 == "script"_s)
- {
- if (w1 == "function"_s)
- {
- npc_parse_function(w1, w2, w3, w4z,
- w4x, fp, &lines);
- }
- else
- {
- NpcName npcname = stringish<NpcName>(w3);
- npc_parse_script(w1, w2, npcname, w4z,
- w4x, fp, &lines);
- }
- }
- else if (w2 == "monster"_s)
- {
- MobName mobname = stringish<MobName>(w3);
- npc_parse_mob(w1, w2, mobname, w4z);
- }
- else if (w2 == "mapflag"_s)
- {
- npc_parse_mapflag(w1, w2, w3, w4z);
- }
- else
- {
- PRINTF("odd script line: %s\n"_fmt, zline);
- script_errors++;
- }
- }
- fflush(stdout);
- }
- PRINTF("\rNPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d] %20s\n"_fmt,
- unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), npc_warp, npc_shop, npc_script, npc_mob, ""_s);
-
- if (script_errors)
- {
- PRINTF("Cowardly refusing to continue after %d errors\n"_fmt, script_errors);
- rv = false;
- }
- return rv;
-}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/npc.hpp b/src/map/npc.hpp
index 33dd378..b587f5f 100644
--- a/src/map/npc.hpp
+++ b/src/map/npc.hpp
@@ -24,38 +24,33 @@
#include <cstdint>
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
+#include "../range/slice.hpp"
#include "../net/timer.t.hpp"
-#include "../proto2/fwd.hpp"
-
#include "map.hpp"
+#include "script-call.t.hpp"
namespace tmwa
{
+namespace map
+{
constexpr BlockId START_NPC_NUM = wrap<BlockId>(110000000);
// TODO make these species, see npc_class in npc_data
constexpr Species WARP_CLASS = wrap<Species>(45);
constexpr Species FAKE_NPC_CLASS = wrap<Species>(127);
-constexpr Species WARP_DEBUG_CLASS = wrap<Species>(722);
constexpr Species INVISIBLE_CLASS = wrap<Species>(32767);
int npc_event_dequeue(dumb_ptr<map_session_data> sd);
int npc_event(dumb_ptr<map_session_data> sd, NpcEvent npcname, int);
-void npc_timer_event(NpcEvent eventname); // Added by RoVeRT
-int npc_command(dumb_ptr<map_session_data> sd, NpcName npcname, XString command);
-int npc_touch_areanpc(dumb_ptr<map_session_data>, map_local *, int, int);
+int npc_touch_areanpc(dumb_ptr<map_session_data>, Borrowed<map_local>, int, int);
int npc_click(dumb_ptr<map_session_data>, BlockId);
int npc_scriptcont(dumb_ptr<map_session_data>, BlockId);
int npc_buysellsel(dumb_ptr<map_session_data>, BlockId, int);
int npc_buylist(dumb_ptr<map_session_data>, const std::vector<Packet_Repeat<0x00c8>>&);
int npc_selllist(dumb_ptr<map_session_data>, const std::vector<Packet_Repeat<0x00c9>>&);
-int npc_parse_warp(XString w1, XString, NpcName w3, XString w4);
int npc_enable(NpcName name, bool flag);
dumb_ptr<npc_data> npc_name2id(NpcName name);
@@ -63,21 +58,10 @@ dumb_ptr<npc_data> npc_name2id(NpcName name);
BlockId npc_get_new_npc_id(void);
/**
- * Spawns and installs a talk-only NPC
- *
- * \param message The message to speak. If message is nullptr, the NPC will not do anything at all.
- */
-dumb_ptr<npc_data> npc_spawn_text(map_local *m, int x, int y,
- Species class_, NpcName name, AString message);
-
-/**
* Uninstalls and frees an NPC
*/
void npc_free(dumb_ptr<npc_data> npc);
-void npc_addsrcfile(AString);
-void npc_delsrcfile(XString);
-bool do_init_npc(void);
int npc_event_do_oninit(void);
int npc_event_doall_l(ScriptLabel name, BlockId rid, Slice<argrec_t> argv);
@@ -98,4 +82,5 @@ void npc_timerevent_stop(dumb_ptr<npc_data_script> nd);
interval_t npc_gettimerevent_tick(dumb_ptr<npc_data_script> nd);
void npc_settimerevent_tick(dumb_ptr<npc_data_script> nd, interval_t newtimer);
int npc_delete(dumb_ptr<npc_data> nd);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/party.cpp b/src/map/party.cpp
index 8713c60..ccbfd75 100644
--- a/src/map/party.cpp
+++ b/src/map/party.cpp
@@ -32,10 +32,12 @@
#include "../net/timer.hpp"
#include "../mmo/ids.hpp"
-#include "../mmo/mmo.hpp"
+#include "../high/mmo.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "intif.hpp"
#include "map.hpp"
#include "pc.hpp"
@@ -45,13 +47,12 @@
namespace tmwa
{
+namespace map
+{
// 座標やHP送信の間隔
constexpr interval_t PARTY_SEND_XYHP_INVERVAL = 1_s;
static
-Map<PartyId, PartyMost> party_db;
-
-static
void party_check_conflict(dumb_ptr<map_session_data> sd);
static
void party_send_xyhp_timer(TimerData *tid, tick_t tick);
@@ -66,32 +67,30 @@ void do_init_party(void)
}
// 検索
-PartyPair party_search(PartyId party_id)
+Option<PartyPair> party_search(PartyId party_id)
{
- PartyPair p;
- p.party_most = party_db.search(party_id);
- if (p)
- p.party_id = party_id;
- return p;
+ Option<P<PartyMost>> party_most_ = party_db.search(party_id);
+ return party_most_.map([party_id](P<PartyMost> party_most)
+ {
+ return PartyPair{party_id, party_most};
+ });
}
static
-void party_searchname_sub(PartyPair p, PartyName str, PartyPair *dst)
+void party_searchname_sub(PartyPair p, PartyName str, Borrowed<Option<PartyPair>> dst)
{
if (p->name == str)
- *dst = p;
+ *dst = Some(p);
}
// パーティ名検索
-PartyPair party_searchname(PartyName str)
+Option<PartyPair> party_searchname(PartyName str)
{
- PartyPair p;
+ Option<PartyPair> p = None;
for (auto& pair : party_db)
{
- PartyPair tmp;
- tmp.party_id = pair.first;
- tmp.party_most = &pair.second;
- party_searchname_sub(tmp, str, &p);
+ PartyPair tmp{pair.first, borrow(pair.second)};
+ party_searchname_sub(tmp, str, borrow(p));
}
return p;
}
@@ -129,15 +128,13 @@ void party_created(AccountId account_id, int fail, PartyId party_id, PartyName n
{
sd->status.party_id = party_id;
- PartyPair p = party_search(party_id);
- if (p)
+ if (party_search(party_id).is_some())
{
PRINTF("party_created(): ID already exists!\n"_fmt);
exit(1);
}
- p.party_most = party_db.init(party_id);
- p.party_id = party_id;
+ Borrowed<PartyMost> p = party_db.init(party_id);
p->name = name;
/* The party was created successfully. */
@@ -158,8 +155,6 @@ void party_request_info(PartyId party_id)
static
int party_check_member(PartyPair p)
{
- nullpo_retz(p);
-
for (io::FD i : iter_fds())
{
Session *s = get_session(i);
@@ -215,24 +210,32 @@ int party_recv_noinfo(PartyId party_id)
return 0;
}
-// 情報所得
-int party_recv_info(const PartyPair sp)
+static
+PartyPair handle_info(const PartyPair sp)
{
- int i;
-
- nullpo_retz(sp);
-
- PartyPair p = party_search(sp.party_id);
- if (!p)
+ Option<PartyPair> p_ = party_search(sp.party_id);
+ OMATCH_BEGIN_SOME (p, p_)
+ {
+ *p.party_most = *sp.party_most;
+ return p;
+ }
+ OMATCH_END ();
{
- p.party_most = party_db.init(sp.party_id);
+ PartyPair p{sp.party_id, party_db.init(sp.party_id)};
// 最初のロードなのでユーザーのチェックを行う
*p.party_most = *sp.party_most;
party_check_member(p);
+ return p;
}
- else
- *p.party_most = *sp.party_most;
+}
+
+// 情報所得
+int party_recv_info(const PartyPair sp)
+{
+ int i;
+
+ PartyPair p = handle_info(sp);
for (i = 0; i < MAX_PARTY; i++)
{ // sdの設定
@@ -261,13 +264,13 @@ int party_recv_info(const PartyPair sp)
int party_invite(dumb_ptr<map_session_data> sd, AccountId account_id)
{
dumb_ptr<map_session_data> tsd = map_id2sd(account_to_block(account_id));
- PartyPair p = party_search(sd->status.party_id);
+ PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0);
int i;
int full = 1; /* Indicates whether or not there's room for one more. */
nullpo_retz(sd);
- if (!tsd || !p || !tsd->sess)
+ if (!tsd || !tsd->sess)
return 0;
if (!battle_config.invite_request_check)
@@ -359,7 +362,6 @@ int party_reply_invite(dumb_ptr<map_session_data> sd, AccountId account_id, int
int party_member_added(PartyId party_id, AccountId account_id, int flag)
{
dumb_ptr<map_session_data> sd = map_id2sd(account_to_block(account_id)), sd2;
- PartyPair p = party_search(party_id);
if (sd == nullptr)
{
@@ -376,12 +378,12 @@ int party_member_added(PartyId party_id, AccountId account_id, int flag)
sd->party_invite = PartyId();
sd->party_invite_account = AccountId();
- if (!p)
+ PartyPair p = TRY_UNWRAP(party_search(party_id),
{
PRINTF("party_member_added: party %d not found.\n"_fmt, party_id);
intif_party_leave(party_id, account_id);
return 0;
- }
+ });
if (flag == 1)
{ // 失敗
@@ -408,13 +410,11 @@ int party_member_added(PartyId party_id, AccountId account_id, int flag)
// パーティ除名要求
int party_removemember(dumb_ptr<map_session_data> sd, AccountId account_id)
{
- PartyPair p;
int i;
nullpo_retz(sd);
- if (!(p = party_search(sd->status.party_id)))
- return 0;
+ PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0);
for (i = 0; i < MAX_PARTY; i++)
{ // リーダーかどうかチェック
@@ -439,13 +439,11 @@ int party_removemember(dumb_ptr<map_session_data> sd, AccountId account_id)
// パーティ脱退要求
int party_leave(dumb_ptr<map_session_data> sd)
{
- PartyPair p;
int i;
nullpo_retz(sd);
- if (!(p = party_search(sd->status.party_id)))
- return 0;
+ PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0);
for (i = 0; i < MAX_PARTY; i++)
{ // 所属しているか
@@ -462,8 +460,8 @@ int party_leave(dumb_ptr<map_session_data> sd)
int party_member_leaved(PartyId party_id, AccountId account_id, CharName name)
{
dumb_ptr<map_session_data> sd = map_id2sd(account_to_block(account_id));
- PartyPair p = party_search(party_id);
- if (p)
+ Option<PartyPair> p_ = party_search(party_id);
+ OMATCH_BEGIN_SOME (p, p_)
{
int i;
for (i = 0; i < MAX_PARTY; i++)
@@ -474,6 +472,7 @@ int party_member_leaved(PartyId party_id, AccountId account_id, CharName name)
p->member[i].sd = nullptr;
}
}
+ OMATCH_END ();
if (sd != nullptr && sd->status.party_id == party_id)
{
sd->status.party_id = PartyId();
@@ -485,10 +484,8 @@ int party_member_leaved(PartyId party_id, AccountId account_id, CharName name)
// パーティ解散通知
int party_broken(PartyId party_id)
{
- PartyPair p;
int i;
- if (!(p = party_search(party_id)))
- return 0;
+ PartyPair p = TRY_UNWRAP(party_search(party_id), return 0);
for (i = 0; i < MAX_PARTY; i++)
{
@@ -508,12 +505,11 @@ int party_broken(PartyId party_id)
// パーティの設定変更要求
int party_changeoption(dumb_ptr<map_session_data> sd, int exp, int item)
{
- PartyPair p;
-
nullpo_retz(sd);
- if (!sd->status.party_id
- || !(p = party_search(sd->status.party_id)))
+ if (!sd->status.party_id)
+ return 0;
+ if (party_search(sd->status.party_id).is_none())
return 0;
intif_party_changeoption(sd->status.party_id, sd->status_key.account_id, exp,
item);
@@ -524,10 +520,8 @@ int party_changeoption(dumb_ptr<map_session_data> sd, int exp, int item)
int party_optionchanged(PartyId party_id, AccountId account_id, int exp, int item,
int flag)
{
- PartyPair p;
dumb_ptr<map_session_data> sd = map_id2sd(account_to_block(account_id));
- if (!(p = party_search(party_id)))
- return 0;
+ PartyPair p = TRY_UNWRAP(party_search(party_id), return 0);
if (!(flag & 0x01))
p->exp = exp;
@@ -541,10 +535,8 @@ int party_optionchanged(PartyId party_id, AccountId account_id, int exp, int ite
void party_recv_movemap(PartyId party_id, AccountId account_id, MapName mapname,
int online, int lv)
{
- PartyPair p;
int i;
- if (!(p = party_search(party_id)))
- return;
+ PartyPair p = TRY_UNWRAP(party_search(party_id), return);
for (i = 0; i < MAX_PARTY; i++)
{
PartyMember *m = &p->member[i];
@@ -584,8 +576,6 @@ void party_recv_movemap(PartyId party_id, AccountId account_id, MapName mapname,
// パーティメンバの移動
int party_send_movemap(dumb_ptr<map_session_data> sd)
{
- PartyPair p;
-
nullpo_retz(sd);
if (!sd->status.party_id)
@@ -599,7 +589,7 @@ int party_send_movemap(dumb_ptr<map_session_data> sd)
party_check_conflict(sd);
// あるならパーティ情報送信
- if ((p = party_search(sd->status.party_id)))
+ PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0);
{
party_check_member(p); // 所属を確認する
if (sd->status.party_id == p.party_id)
@@ -616,15 +606,13 @@ int party_send_movemap(dumb_ptr<map_session_data> sd)
// パーティメンバのログアウト
int party_send_logout(dumb_ptr<map_session_data> sd)
{
- PartyPair p;
-
nullpo_retz(sd);
if (sd->status.party_id)
intif_party_changemap(sd, 0);
// sdが無効になるのでパーティ情報から削除
- if ((p = party_search(sd->status.party_id)))
+ PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0);
{
int i;
for (i = 0; i < MAX_PARTY; i++)
@@ -646,9 +634,7 @@ void party_send_message(dumb_ptr<map_session_data> sd, XString mes)
// パーティメッセージ受信
void party_recv_message(PartyId party_id, AccountId account_id, XString mes)
{
- PartyPair p;
- if (!(p = party_search(party_id)))
- return;
+ PartyPair p = TRY_UNWRAP(party_search(party_id), return);
clif_party_message(p, account_id, mes);
}
@@ -667,8 +653,6 @@ void party_send_xyhp_timer_sub(PartyPair p)
{
int i;
- nullpo_retv(p);
-
for (i = 0; i < MAX_PARTY; i++)
{
dumb_ptr<map_session_data> sd = dumb_ptr<map_session_data>(p->member[i].sd);
@@ -697,9 +681,7 @@ void party_send_xyhp_timer(TimerData *, tick_t)
{
for (auto& pair : party_db)
{
- PartyPair tmp;
- tmp.party_id = pair.first;
- tmp.party_most = &pair.second;
+ PartyPair tmp{pair.first, borrow(pair.second)};
party_send_xyhp_timer_sub(tmp);
}
}
@@ -709,8 +691,6 @@ void party_send_xy_clear(PartyPair p)
{
int i;
- nullpo_retv(p);
-
for (i = 0; i < MAX_PARTY; i++)
{
dumb_ptr<map_session_data> sd = dumb_ptr<map_session_data>(p->member[i].sd);
@@ -739,13 +719,11 @@ void party_send_hp_check(dumb_ptr<block_list> bl, PartyId party_id, int *flag)
}
// 経験値公平分配
-int party_exp_share(PartyPair p, map_local *mapid, int base_exp, int job_exp)
+int party_exp_share(PartyPair p, Borrowed<map_local> mapid, int base_exp, int job_exp)
{
dumb_ptr<map_session_data> sd;
int i, c;
- nullpo_retz(p);
-
for (i = c = 0; i < MAX_PARTY; i++)
{
sd = dumb_ptr<map_session_data>(p->member[i].sd);
@@ -770,7 +748,6 @@ int party_exp_share(PartyPair p, map_local *mapid, int base_exp, int job_exp)
void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func,
dumb_ptr<map_session_data> sd, int type)
{
- PartyPair p;
int i;
int x0, y0, x1, y1;
dumb_ptr<map_session_data> list[MAX_PARTY];
@@ -778,8 +755,7 @@ void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func,
nullpo_retv(sd);
- if (!(p = party_search(sd->status.party_id)))
- return;
+ PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return);
x0 = sd->bl_x - AREA_SIZE;
y0 = sd->bl_y - AREA_SIZE;
@@ -807,4 +783,5 @@ void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func,
if (list[i]->bl_prev) // 有効かどうかチェック
func(list[i]);
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/party.hpp b/src/map/party.hpp
index 01a8125..669857e 100644
--- a/src/map/party.hpp
+++ b/src/map/party.hpp
@@ -24,18 +24,14 @@
#include <functional>
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
-
-#include "../mmo/fwd.hpp"
-
namespace tmwa
{
+namespace map
+{
void do_init_party(void);
-PartyPair party_search(PartyId party_id);
-PartyPair party_searchname(PartyName str);
+Option<PartyPair> party_search(PartyId party_id);
+Option<PartyPair> party_searchname(PartyName str);
int party_create(dumb_ptr<map_session_data> sd, PartyName name);
void party_created(AccountId account_id, int fail, PartyId party_id, PartyName name);
@@ -65,8 +61,9 @@ void party_recv_message(PartyId party_id, AccountId account_id, XString mes);
void party_send_xy_clear(PartyPair p);
void party_send_hp_check(dumb_ptr<block_list> bl, PartyId party_id, int *flag);
-int party_exp_share(PartyPair p, map_local *map, int base_exp, int job_exp);
+int party_exp_share(PartyPair p, Borrowed<map_local> map, int base_exp, int job_exp);
void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func,
dumb_ptr<map_session_data> sd, int type);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/path.cpp b/src/map/path.cpp
index 6950797..52d20ad 100644
--- a/src/map/path.cpp
+++ b/src/map/path.cpp
@@ -31,7 +31,7 @@
#include "../io/cxxstdio.hpp"
-#include "clif.t.hpp"
+#include "../mmo/clif.t.hpp"
#include "map.hpp"
#include "../poison.hpp"
@@ -39,6 +39,8 @@
namespace tmwa
{
+namespace map
+{
constexpr int MAX_HEAP = 150;
struct tmp_path
{
@@ -212,10 +214,8 @@ int add_path(int *heap, struct tmp_path *tp, int x, int y, int dist,
*------------------------------------------
*/
static
-bool can_place(struct map_local *m, int x, int y)
+bool can_place(Borrowed<struct map_local> m, int x, int y)
{
- nullpo_retz(m);
-
return !bool(read_gatp(m, x, y) & MapCell::UNWALKABLE);
}
@@ -224,10 +224,8 @@ bool can_place(struct map_local *m, int x, int y)
*------------------------------------------
*/
static
-int can_move(struct map_local *m, int x0, int y0, int x1, int y1)
+int can_move(Borrowed<struct map_local> m, int x0, int y0, int x1, int y1)
{
- nullpo_retz(m);
-
if (x0 - x1 < -1 || x0 - x1 > 1 || y0 - y1 < -1 || y0 - y1 > 1)
return 0;
if (x1 < 0 || y1 < 0 || x1 >= m->xs || y1 >= m->ys)
@@ -247,7 +245,7 @@ int can_move(struct map_local *m, int x0, int y0, int x1, int y1)
* path探索 (x0,y0)->(x1,y1)
*------------------------------------------
*/
-int path_search(struct walkpath_data *wpd, map_local *m, int x0, int y0, int x1, int y1, int flag)
+int path_search(struct walkpath_data *wpd, Borrowed<map_local> m, int x0, int y0, int x1, int y1, int flag)
{
int heap[MAX_HEAP + 1];
int i, rp, x, y;
@@ -256,7 +254,7 @@ int path_search(struct walkpath_data *wpd, map_local *m, int x0, int y0, int x1,
nullpo_retz(wpd);
assert (m->gat);
- map_local *md = m;
+ P<map_local> md = m;
if (x1 < 0 || x1 >= md->xs || y1 < 0 || y1 >= md->ys
|| bool(read_gatp(md, x1, y1) & MapCell::UNWALKABLE))
return -1;
@@ -361,4 +359,5 @@ int path_search(struct walkpath_data *wpd, map_local *m, int x0, int y0, int x1,
return -1;
}
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/path.hpp b/src/map/path.hpp
index 3619e2e..f16baaa 100644
--- a/src/map/path.hpp
+++ b/src/map/path.hpp
@@ -25,5 +25,8 @@
namespace tmwa
{
-int path_search(struct walkpath_data *, map_local *, int, int, int, int, int);
+namespace map
+{
+int path_search(struct walkpath_data *, Borrowed<map_local>, int, int, int, int, int);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index ada5b9f..6fa35b0 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -37,36 +37,42 @@
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
#include "../io/read.hpp"
-#include "../net/timer.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
-#include "../mmo/utils.hpp"
+#include "../net/timer.hpp"
+#include "../net/timestamp-utils.hpp"
#include "../proto2/char-map.hpp"
#include "atcommand.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "chrif.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "intif.hpp"
#include "itemdb.hpp"
#include "magic-stmt.hpp"
#include "map.hpp"
+#include "map_conf.hpp"
#include "npc.hpp"
#include "party.hpp"
#include "path.hpp"
-#include "script.hpp"
+#include "script-call.hpp"
#include "skill.hpp"
#include "storage.hpp"
#include "trade.hpp"
+#include "quest.hpp"
#include "../poison.hpp"
namespace tmwa
{
+namespace map
+{
// PVP順位計算の間隔
constexpr std::chrono::milliseconds PVP_CALCRANK_INTERVAL =
1_s;
@@ -120,7 +126,7 @@ int sp_coefficient_0 = 100;
// coefficients for each weapon type
// (not all used)
static //const
-earray<interval_t, ItemLook, ItemLook::SINGLE_HANDED_COUNT> aspd_base_0 //=
+earray<interval_t, ItemLook, ItemLook::COUNT> aspd_base_0 //=
{{
650_ms,
700_ms,
@@ -254,10 +260,6 @@ earray<EPOS, EQUIP, EQUIP::COUNT> equip_pos //=
EPOS::ARROW,
}};
-// TODO use DMap<>
-static
-std::map<AccountId, GmLevel> gm_accountm;
-
static
int pc_checkoverhp(dumb_ptr<map_session_data> sd);
static
@@ -287,16 +289,12 @@ int pc_iskiller(dumb_ptr<map_session_data> src,
{
nullpo_retz(src);
- if (src->bl_type != BL::PC)
+ if (src->bl_type != BL::PC || target->bl_type != BL::PC)
return 0;
- if (src->special_state.killer)
+ if ((src->state.pvpchannel == 1) && (target->state.pvpchannel == 1) && !src->bl_m->flag.get(MapFlag::NOPVP))
return 1;
-
- if (target->bl_type != BL::PC)
- return 0;
- if (target->special_state.killable)
+ if ((src->state.pvpchannel > 1) && (target->state.pvpchannel == src->state.pvpchannel)) // this one does not respect NOPVP
return 1;
-
return 0;
}
@@ -319,6 +317,33 @@ int distance(int x0, int y0, int x1, int y1)
}
static
+void pc_pvp_timer(TimerData *, tick_t, BlockId id)
+{
+ dumb_ptr<map_session_data> sd = map_id2sd(id);
+
+ assert (sd != nullptr);
+ assert (sd->bl_type == BL::PC);
+}
+
+int pc_setpvptimer(dumb_ptr<map_session_data> sd, interval_t val)
+{
+ nullpo_retz(sd);
+
+ sd->pvp_timer = Timer(gettick() + val,
+ std::bind(pc_pvp_timer, ph::_1, ph::_2,
+ sd->bl_id));
+ return 0;
+}
+
+int pc_delpvptimer(dumb_ptr<map_session_data> sd)
+{
+ nullpo_retz(sd);
+
+ sd->pvp_timer.cancel();
+ return 0;
+}
+
+static
void pc_invincible_timer(TimerData *, tick_t, BlockId id)
{
dumb_ptr<map_session_data> sd = map_id2sd(id);
@@ -377,7 +402,6 @@ int pc_setrestartvalue(dumb_ptr<map_session_data> sd, int type)
clif_updatestatus(sd, SP::SP);
sd->heal_xp = 0; // [Fate] Set gainable xp for healing this player to 0
-
return 0;
}
@@ -464,7 +488,7 @@ void pc_makesavestatus(dumb_ptr<map_session_data> sd)
// セーブ禁止マップだったので指定位置に移動
if (sd->bl_m->flag.get(MapFlag::NOSAVE))
{
- map_local *m = sd->bl_m;
+ P<map_local> m = sd->bl_m;
if (m->save.map_ == "SavePoint"_s)
sd->status.last_point = sd->status.save_point;
else
@@ -505,12 +529,7 @@ EPOS pc_equippoint(dumb_ptr<map_session_data> sd, IOff0 n)
{
nullpo_retr(EPOS::ZERO, sd);
- if (!sd->inventory_data[n])
- return EPOS::ZERO;
-
- EPOS ep = sd->inventory_data[n]->equip;
-
- return ep;
+ return sd->inventory_data[n].pmd_pget(&item_data::equip).copy_or(EPOS::ZERO);
}
static
@@ -521,7 +540,11 @@ int pc_setinventorydata(dumb_ptr<map_session_data> sd)
for (IOff0 i : IOff0::iter())
{
ItemNameId id = sd->status.inventory[i].nameid;
- sd->inventory_data[i] = itemdb_search(id);
+ // If you think you understand this line, you're wrong.
+ // It does not do what you think it does. Rather, you need to
+ // understand it in the context in which it is used. Despite this,
+ // it is quite common for elements to be None.
+ sd->inventory_data[i] = Some(itemdb_search(id));
}
return 0;
}
@@ -531,40 +554,8 @@ int pc_calcweapontype(dumb_ptr<map_session_data> sd)
{
nullpo_retz(sd);
- if (sd->weapontype1 != ItemLook::NONE
- && sd->weapontype2 == ItemLook::NONE)
- sd->status.weapon = sd->weapontype1;
- if (sd->weapontype1 == ItemLook::NONE
- && sd->weapontype2 != ItemLook::NONE)
- sd->status.weapon = sd->weapontype2;
- else if (sd->weapontype1 == ItemLook::BLADE
- && sd->weapontype2 == ItemLook::BLADE)
- sd->status.weapon = ItemLook::DUAL_BLADE;
- else if (sd->weapontype1 == ItemLook::_2
- && sd->weapontype2 == ItemLook::_2)
- sd->status.weapon = ItemLook::DUAL_2;
- else if (sd->weapontype1 == ItemLook::_6
- && sd->weapontype2 == ItemLook::_6)
- sd->status.weapon = ItemLook::DUAL_6;
- else if ((sd->weapontype1 == ItemLook::BLADE
- && sd->weapontype2 == ItemLook::_2)
- || (sd->weapontype1 == ItemLook::_2
- && sd->weapontype2 == ItemLook::BLADE))
- sd->status.weapon = ItemLook::DUAL_12;
- else if (
- (sd->weapontype1 == ItemLook::BLADE
- && sd->weapontype2 == ItemLook::_6)
- || (sd->weapontype1 == ItemLook::_6
- && sd->weapontype2 == ItemLook::BLADE))
- sd->status.weapon = ItemLook::DUAL_16;
- else if (
- (sd->weapontype1 == ItemLook::_2
- && sd->weapontype2 == ItemLook::_6)
- || (sd->weapontype1 == ItemLook::_6
- && sd->weapontype2 == ItemLook::_2))
- sd->status.weapon = ItemLook::DUAL_26;
- else
- sd->status.weapon = sd->weapontype1;
+ // TODO now that there is no calculation here, store only once
+ sd->status.weapon = sd->weapontype1;
return 0;
}
@@ -588,27 +579,30 @@ int pc_setequipindex(dumb_ptr<map_session_data> sd)
sd->equip_index_maybe[j] = i;
if (bool(sd->status.inventory[i].equip & EPOS::WEAPON))
{
- if (sd->inventory_data[i])
- sd->weapontype1 = sd->inventory_data[i]->look;
- else
- sd->weapontype1 = ItemLook::NONE;
+ OMATCH_BEGIN (sd->inventory_data[i])
+ {
+ OMATCH_CASE_SOME (sdidi)
+ {
+ sd->weapontype1 = sdidi->look;
+ }
+ OMATCH_CASE_NONE ()
+ {
+ sd->weapontype1 = ItemLook::NONE;
+ }
+ }
+ OMATCH_END ();
}
if (bool(sd->status.inventory[i].equip & EPOS::SHIELD))
{
- if (sd->inventory_data[i])
+ OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[i])
{
- if (sd->inventory_data[i]->type == ItemType::WEAPON)
+ if (sdidi->type == ItemType::WEAPON)
{
if (sd->status.inventory[i].equip == EPOS::SHIELD)
- sd->weapontype2 = sd->inventory_data[i]->look;
- else
- sd->weapontype2 = ItemLook::NONE;
+ assert(0 && "unreachable - offhand weapons are not supported");
}
- else
- sd->weapontype2 = ItemLook::NONE;
}
- else
- sd->weapontype2 = ItemLook::NONE;
+ OMATCH_END ();
}
}
}
@@ -620,21 +614,18 @@ int pc_setequipindex(dumb_ptr<map_session_data> sd)
static
int pc_isequip(dumb_ptr<map_session_data> sd, IOff0 n)
{
- struct item_data *item;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
//転生や養子の場合の元の職業を算出する
nullpo_retz(sd);
- item = sd->inventory_data[n];
sc_data = battle_get_sc_data(sd);
- GmLevel gm_all_equipment = GmLevel::from(static_cast<uint32_t>(battle_config.gm_all_equipment));
+ GmLevel gm_all_equipment = battle_config.gm_all_equipment;
if (gm_all_equipment && pc_isGM(sd).satisfies(gm_all_equipment))
return 1;
- if (item == nullptr)
- return 0;
+ P<struct item_data> item = TRY_UNWRAP(sd->inventory_data[n], return 0);
if (item->sex != SEX::NEUTRAL && sd->status.sex != item->sex)
return 0;
if (item->elv > 0 && sd->status.base_level < item->elv)
@@ -648,12 +639,11 @@ int pc_isequip(dumb_ptr<map_session_data> sd, IOff0 n)
* char鯖から送られてきたステータスを設定
*------------------------------------------
*/
-int pc_authok(AccountId id, int login_id2, TimeT connect_until_time,
+int pc_authok(AccountId id, int login_id2,
short tmw_version, const CharKey *st_key, const CharData *st_data)
{
dumb_ptr<map_session_data> sd = nullptr;
- PartyPair p;
tick_t tick = gettick();
sd = map_id2sd(account_to_block(id));
@@ -681,7 +671,7 @@ int pc_authok(AccountId id, int login_id2, TimeT connect_until_time,
sd->state.connect_new = 1;
sd->bl_prev = sd->bl_next = nullptr;
- sd->weapontype1 = sd->weapontype2 = ItemLook::NONE;
+ sd->weapontype1 = ItemLook::NONE;
sd->speed = DEFAULT_WALK_SPEED;
sd->state.dead_sit = 0;
sd->dir = DIR::S;
@@ -727,21 +717,6 @@ int pc_authok(AccountId id, int login_id2, TimeT connect_until_time,
// sd->sc_data[i].timer = nullptr;
sd->sc_data[i].val1 = 0;
}
- sd->sc_count = 0;
- {
- Option old_option = sd->status.option;
- sd->status.option = Option::ZERO;
-
- // This would leak information.
- // It's better to make it obvious that players can see you.
- if (false && bool(old_option & Option::INVISIBILITY))
- is_atcommand(sd->sess, sd, "@invisible"_s, GmLevel());
-
- if (bool(old_option & Option::HIDE))
- is_atcommand(sd->sess, sd, "@hide"_s, GmLevel());
- // atcommand_hide might already send it, but also might not
- clif_changeoption(sd);
- }
// パーティー関係の初期化
sd->party_sended = 0;
@@ -757,9 +732,24 @@ int pc_authok(AccountId id, int login_id2, TimeT connect_until_time,
pc_setpos(sd, sd->status.last_point.map_, sd->status.last_point.x,
sd->status.last_point.y, BeingRemoveWhy::GONE);
+ {
+ Opt0 old_option = sd->status.option;
+ sd->status.option = Opt0::ZERO;
+
+ // This would leak information.
+ // It's better to make it obvious that players can see you.
+ if (false && bool(old_option & Opt0::INVISIBILITY))
+ is_atcommand(sd->sess, sd, "@invisible"_s, GmLevel());
+
+ if (bool(old_option & Opt0::HIDE))
+ is_atcommand(sd->sess, sd, "@hide"_s, GmLevel());
+ // atcommand_hide might already send it, but also might not
+ clif_changeoption(sd);
+ }
+
// パーティ、ギルドデータの要求
if (sd->status.party_id
- && !(p = party_search(sd->status.party_id)))
+ && party_search(sd->status.party_id).is_none())
party_request_info(sd->status.party_id);
// pvpの設定
@@ -793,27 +783,21 @@ int pc_authok(AccountId id, int login_id2, TimeT connect_until_time,
sd->auto_ban_info.in_progress = 0;
// Initialize antispam vars
- sd->chat_reset_due = TimeT();
+ sd->chat_reset_due = tick_t();
sd->chat_lines_in = sd->chat_total_repeats = 0;
- sd->chat_repeat_reset_due = TimeT();
+ sd->chat_repeat_reset_due = tick_t();
sd->chat_lastmsg = RString();
for (tick_t& t : sd->flood_rates)
t = tick_t();
- sd->packet_flood_reset_due = TimeT();
+ sd->packet_flood_reset_due = tick_t();
sd->packet_flood_in = 0;
- // message of the limited time of the account
- if (connect_until_time)
- {
- timestamp_seconds_buffer buffer;
- stamp_time(buffer, &connect_until_time);
- AString tmpstr = STRPRINTF("Your account time limit is: %s"_fmt, buffer);
-
- clif_wis_message(sd->sess, wisp_server_name, tmpstr);
- }
pc_calcstatus(sd, 1);
+ npc_event_doall_l(stringish<ScriptLabel>("OnPCLoginEvent"_s), sd->bl_id, nullptr);
+ // Init Quest Log
+ clif_sendallquest(sd);
return 0;
}
@@ -830,7 +814,7 @@ void pc_show_motd(dumb_ptr<map_session_data> sd)
clif_displaymessage(sd->sess, "This server is Free Software, for details type @source in chat or use the tmwa-source tool"_s);
sd->state.seen_motd = true;
- io::ReadFile in(motd_txt);
+ io::ReadFile in(map_conf.motd_txt);
if (in.is_open())
{
AString buf;
@@ -865,7 +849,8 @@ int pc_calc_skillpoint(dumb_ptr<map_session_data> sd)
nullpo_retz(sd);
- for (i = 0; i < skill_pool_skills_size; i++) {
+ for (i = 0; i < skill_pool_skills.size(); i++)
+ {
int lv = sd->status.skill[skill_pool_skills[i]].lv;
if (lv)
skill_points += ((lv * (lv - 1)) >> 1) - 1;
@@ -940,6 +925,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
int bl;
int aspd_rate, refinedef = 0;
int str, dstr, dex;
+ int b_pvpchannel = 0;
nullpo_retz(sd);
@@ -968,6 +954,8 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
b_mdef = sd->mdef;
b_mdef2 = sd->mdef2;
b_base_atk = sd->base_atk;
+ if (!pc_isdead(sd) && sd->state.pvpchannel == 1)
+ b_pvpchannel = sd->state.pvpchannel;
sd->max_weight = max_weight_base_0 + sd->status.attrs[ATTR::STR] * 300;
@@ -976,11 +964,11 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->weight = 0;
for (IOff0 i : IOff0::iter())
{
- if (!sd->status.inventory[i].nameid
- || sd->inventory_data[i] == nullptr)
+ if (!sd->status.inventory[i].nameid)
continue;
+ P<struct item_data> sdidi = TRY_UNWRAP(sd->inventory_data[i], continue);
sd->weight +=
- sd->inventory_data[i]->weight *
+ sdidi->weight *
sd->status.inventory[i].amount;
}
// used to fill cart
@@ -1005,7 +993,6 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->status.max_hp = 0;
sd->status.max_sp = 0;
sd->attackrange = 0;
- sd->attackrange_ = 0;
sd->matk1 = 0;
sd->matk2 = 0;
sd->speed = DEFAULT_WALK_SPEED;
@@ -1016,13 +1003,9 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->arrow_atk = 0;
sd->arrow_hit = 0;
sd->arrow_range = 0;
- sd->nhealhp = sd->nhealsp = sd->nshealhp = sd->nshealsp = sd->nsshealhp =
- sd->nsshealsp = 0;
+ sd->nhealhp = sd->nhealsp = 0;
really_memzero_this(&sd->special_state);
- sd->watk_ = 0; //二刀流用(仮)
- sd->watk_2 = 0;
-
sd->aspd_rate = 100;
sd->speed_rate = 100;
sd->hprecov_rate = 100;
@@ -1038,8 +1021,6 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->double_add_rate = sd->perfect_hit_add = 0;
sd->hp_drain_rate = sd->hp_drain_per = sd->sp_drain_rate =
sd->sp_drain_per = 0;
- sd->hp_drain_rate_ = sd->hp_drain_per_ = sd->sp_drain_rate_ =
- sd->sp_drain_per_ = 0;
sd->spellpower_bonus_target = 0;
@@ -1057,13 +1038,14 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
|| sd->equip_index_maybe[EQUIP::LEGS] == index))
continue;
- if (sd->inventory_data[index])
+ OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[index])
{
sd->spellpower_bonus_target +=
- sd->inventory_data[index]->magic_bonus;
+ sdidi->magic_bonus;
// used to apply cards
}
+ OMATCH_END ();
}
#ifdef USE_ASTRAL_SOUL_SKILL
@@ -1092,31 +1074,15 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
&& (sd->equip_index_maybe[EQUIP::TORSO] == index
|| sd->equip_index_maybe[EQUIP::LEGS] == index))
continue;
- if (sd->inventory_data[index])
+ OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[index])
{
- sd->def += sd->inventory_data[index]->def;
- if (sd->inventory_data[index]->type == ItemType::WEAPON)
+ sd->def += sdidi->def;
+ if (sdidi->type == ItemType::WEAPON)
{
if (i == EQUIP::SHIELD
&& sd->status.inventory[index].equip == EPOS::SHIELD)
{
- //二刀流用データ入力
- sd->watk_ += sd->inventory_data[index]->atk;
- sd->watk_2 = 0;
-
- sd->attackrange_ += sd->inventory_data[index]->range;
- sd->state.lr_flag = 1;
- {
- argrec_t arg[2] =
- {
- {"@slotId"_s, static_cast<int>(i)},
- {"@itemId"_s, unwrap<ItemNameId>(sd->inventory_data[index]->nameid)},
- };
- run_script_l(ScriptPointer(sd->inventory_data[index]->equip_script.get(), 0),
- sd->bl_id, BlockId(),
- arg);
- }
- sd->state.lr_flag = 0;
+ assert(0 && "unreachable - offhand weapons are not supported");
}
else
{
@@ -1124,66 +1090,62 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
argrec_t arg[2] =
{
{"@slotId"_s, static_cast<int>(i)},
- {"@itemId"_s, unwrap<ItemNameId>(sd->inventory_data[index]->nameid)},
+ {"@itemId"_s, unwrap<ItemNameId>(sdidi->nameid)},
};
- sd->watk += sd->inventory_data[index]->atk;
+ sd->watk += sdidi->atk;
- sd->attackrange += sd->inventory_data[index]->range;
- run_script_l(ScriptPointer(sd->inventory_data[index]->equip_script.get(), 0),
+ sd->attackrange += sdidi->range;
+ run_script_l(ScriptPointer(borrow(*sdidi->equip_script), 0),
sd->bl_id, BlockId(),
arg);
}
}
- else if (sd->inventory_data[index]->type == ItemType::ARMOR)
+ else if (sdidi->type == ItemType::ARMOR)
{
argrec_t arg[2] =
{
{"@slotId"_s, static_cast<int>(i)},
- {"@itemId"_s, unwrap<ItemNameId>(sd->inventory_data[index]->nameid)},
+ {"@itemId"_s, unwrap<ItemNameId>(sdidi->nameid)},
};
- sd->watk += sd->inventory_data[index]->atk;
- run_script_l(ScriptPointer(sd->inventory_data[index]->equip_script.get(), 0),
+ sd->watk += sdidi->atk;
+ run_script_l(ScriptPointer(borrow(*sdidi->equip_script), 0),
sd->bl_id, BlockId(),
arg);
}
}
+ OMATCH_END ();
}
if (battle_is_unarmed(sd))
{
sd->watk += skill_power(sd, SkillID::TMW_BRAWLING) / 3; // +66 for 200
sd->watk2 += skill_power(sd, SkillID::TMW_BRAWLING) >> 3; // +25 for 200
- sd->watk_ += skill_power(sd, SkillID::TMW_BRAWLING) / 3; // +66 for 200
- sd->watk_2 += skill_power(sd, SkillID::TMW_BRAWLING) >> 3; // +25 for 200
}
IOff0 aidx = sd->equip_index_maybe[EQUIP::ARROW];
if (aidx.ok())
{
IOff0 index = aidx;
- if (sd->inventory_data[index])
+ OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[index])
{ //まだ属性が入っていない
argrec_t arg[2] =
{
{"@slotId"_s, static_cast<int>(EQUIP::ARROW)},
- {"@itemId"_s, unwrap<ItemNameId>(sd->inventory_data[index]->nameid)},
+ {"@itemId"_s, unwrap<ItemNameId>(sdidi->nameid)},
};
- sd->state.lr_flag = 2;
- run_script_l(ScriptPointer(sd->inventory_data[index]->equip_script.get(), 0),
+ sd->state.lr_flag_is_arrow_2 = 1;
+ run_script_l(ScriptPointer(borrow(*sdidi->equip_script), 0),
sd->bl_id, BlockId(),
arg);
- sd->state.lr_flag = 0;
- sd->arrow_atk += sd->inventory_data[index]->atk;
+ sd->state.lr_flag_is_arrow_2 = 0;
+ sd->arrow_atk += sdidi->atk;
}
+ OMATCH_END ();
}
sd->def += (refinedef + 50) / 100;
if (sd->attackrange < 1)
sd->attackrange = 1;
- if (sd->attackrange_ < 1)
- sd->attackrange_ = 1;
- if (sd->attackrange < sd->attackrange_)
- sd->attackrange = sd->attackrange_;
if (sd->status.weapon == ItemLook::BOW)
sd->attackrange += sd->arrow_range;
sd->double_rate += sd->double_add_rate;
@@ -1201,9 +1163,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
- || sd->status.weapon == ItemLook::_13
- || sd->status.weapon == ItemLook::_14)
+ if (sd->status.weapon == ItemLook::BOW)
{
str = sd->paramc[ATTR::DEX];
dex = sd->paramc[ATTR::STR];
@@ -1291,20 +1251,11 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
sd->mdef2 = 1;
// 二刀流 ASPD 修正
- if (sd->status.weapon < ItemLook::SINGLE_HANDED_COUNT)
+ {
sd->aspd += aspd_base_0[sd->status.weapon]
- (sd->paramc[ATTR::AGI] * 4 + sd->paramc[ATTR::DEX])
* aspd_base_0[sd->status.weapon] / 1000;
- else
- sd->aspd += (
- (aspd_base_0[sd->weapontype1]
- - (sd->paramc[ATTR::AGI] * 4 + sd->paramc[ATTR::DEX])
- * aspd_base_0[sd->weapontype1] / 1000)
- + (aspd_base_0[sd->weapontype2]
- - (sd->paramc[ATTR::AGI] * 4 + sd->paramc[ATTR::DEX])
- * aspd_base_0[sd->weapontype2] / 1000)
- )
- * 140 / 200;
+ }
aspd_rate = sd->aspd_rate;
@@ -1366,7 +1317,6 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
}
// スキルやステータス異常による残りのパラメータ補正
- if (sd->sc_count)
{
// ATK/DEF変化形
if (sd->sc_data[StatusChange::SC_POISON].timer) // 毒状態
@@ -1401,7 +1351,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
if (sd->attack_spell_override)
sd->aspd = sd->attack_spell_delay;
- sd->aspd = std::max(sd->aspd, static_cast<interval_t>(battle_config.max_aspd));
+ sd->aspd = std::max(sd->aspd, battle_config.max_aspd);
sd->amotion = sd->aspd;
sd->dmotion = std::chrono::milliseconds(800 - sd->paramc[ATTR::AGI] * 4);
sd->dmotion = std::max(sd->dmotion, 400_ms);
@@ -1478,6 +1428,8 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first)
clif_updatestatus(sd, SP::HP);
if (b_sp != sd->status.sp)
clif_updatestatus(sd, SP::SP);
+ if (b_pvpchannel != sd->state.pvpchannel)
+ sd->state.pvpchannel = b_pvpchannel;
return 0;
}
@@ -1498,122 +1450,116 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val)
case SP::INT:
case SP::DEX:
case SP::LUK:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->parame[sp_to_attr(type)] += val;
break;
#if 0
case SP::ATK1:
- if (!sd->state.lr_flag)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->watk += val;
- else if (sd->state.lr_flag == 1)
- sd->watk_ += val;
break;
#endif
#if 0
case SP::ATK2:
- if (!sd->state.lr_flag)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->watk2 += val;
- else if (sd->state.lr_flag == 1)
- sd->watk_2 += val;
break;
#endif
#if 0
case SP::BASE_ATK:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->base_atk += val;
break;
#endif
#if 0
case SP::MATK1:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->matk1 += val;
break;
#endif
#if 0
case SP::MATK2:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->matk2 += val;
break;
#endif
#if 0
case SP::DEF1:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->def += val;
break;
#endif
case SP::MDEF1:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->mdef += val;
break;
#if 0
case SP::MDEF2:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->mdef += val;
break;
#endif
case SP::HIT:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->hit += val;
else
sd->arrow_hit += val;
break;
case SP::FLEE1:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->flee += val;
break;
#if 0
case SP::FLEE2:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->flee2 += val * 10;
break;
#endif
case SP::CRITICAL:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->critical += val * 10;
else
sd->arrow_cri += val * 10;
break;
case SP::MAXHP:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->status.max_hp += val;
break;
case SP::MAXSP:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->status.max_sp += val;
break;
case SP::MAXHPRATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->hprate += val;
break;
#if 0
case SP::MAXSPRATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->sprate += val;
break;
#endif
#if 0
case SP::SPRATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->dsprate += val;
break;
#endif
case SP::ATTACKRANGE:
- if (!sd->state.lr_flag)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->attackrange += val;
- else if (sd->state.lr_flag == 1)
- sd->attackrange_ += val;
- else if (sd->state.lr_flag == 2)
+ else
sd->arrow_range += val;
break;
#if 0
case SP::ADD_SPEED:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->speed -= val;
break;
#endif
#if 0
case SP::SPEED_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
{
if (sd->speed_rate > 100 - val)
sd->speed_rate = 100 - val;
@@ -1621,17 +1567,17 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val)
break;
#endif
case SP::SPEED_ADDRATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->speed_add_rate = sd->speed_add_rate * (100 - val) / 100;
break;
#if 0
case SP::ASPD:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->aspd -= val * 10;
break;
#endif
case SP::ASPD_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
{
if (sd->aspd_rate > 100 - val)
sd->aspd_rate = 100 - val;
@@ -1639,99 +1585,99 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val)
break;
#if 0
case SP::ASPD_ADDRATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->aspd_add_rate = sd->aspd_add_rate * (100 - val) / 100;
break;
#endif
case SP::HP_RECOV_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->hprecov_rate += val;
break;
#if 0
case SP::SP_RECOV_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->sprecov_rate += val;
break;
#endif
case SP::CRITICAL_DEF:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->critical_def += val;
break;
#if 0
case SP::DOUBLE_RATE:
- if (sd->state.lr_flag == 0 && sd->double_rate < val)
+ if (!sd->state.lr_flag_is_arrow_2 && sd->double_rate < val)
sd->double_rate = val;
break;
#endif
case SP::DOUBLE_ADD_RATE:
- if (sd->state.lr_flag == 0)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->double_add_rate += val;
break;
#if 0
case SP::MATK_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->matk_rate += val;
break;
#endif
#if 0
case SP::ATK_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->atk_rate += val;
break;
#endif
#if 0
case SP::PERFECT_HIT_RATE:
- if (sd->state.lr_flag != 2 && sd->perfect_hit < val)
+ if (!sd->state.lr_flag_is_arrow_2 && sd->perfect_hit < val)
sd->perfect_hit = val;
break;
#endif
#if 0
case SP::PERFECT_HIT_ADD_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->perfect_hit_add += val;
break;
#endif
#if 0
case SP::CRITICAL_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->critical_rate += val;
break;
#endif
#if 0
case SP::HIT_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->hit_rate += val;
break;
#endif
#if 0
case SP::FLEE_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->flee_rate += val;
break;
#endif
#if 0
case SP::FLEE2_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->flee2_rate += val;
break;
#endif
case SP::DEF_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->def_rate += val;
break;
case SP::DEF2_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->def2_rate += val;
break;
#if 0
case SP::MDEF_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->mdef_rate += val;
break;
#endif
#if 0
case SP::MDEF2_RATE:
- if (sd->state.lr_flag != 2)
+ if (!sd->state.lr_flag_is_arrow_2)
sd->mdef2_rate += val;
break;
#endif
@@ -1758,29 +1704,19 @@ int pc_bonus2(dumb_ptr<map_session_data> sd, SP type, int type2, int val)
switch (type)
{
case SP::HP_DRAIN_RATE:
- if (!sd->state.lr_flag)
+ if (!sd->state.lr_flag_is_arrow_2)
{
sd->hp_drain_rate += type2;
sd->hp_drain_per += val;
}
- else if (sd->state.lr_flag == 1)
- {
- sd->hp_drain_rate_ += type2;
- sd->hp_drain_per_ += val;
- }
break;
#if 0
case SP::SP_DRAIN_RATE:
- if (!sd->state.lr_flag)
+ if (!sd->state.lr_flag_is_arrow_2)
{
sd->sp_drain_rate += type2;
sd->sp_drain_per += val;
}
- else if (sd->state.lr_flag == 1)
- {
- sd->sp_drain_rate_ += type2;
- sd->sp_drain_per_ += val;
- }
break;
#endif
default:
@@ -1971,7 +1907,6 @@ int pc_remove_items(dumb_ptr<map_session_data> player, ItemNameId item_id, int c
PickupFail pc_additem(dumb_ptr<map_session_data> sd, Item *item_data,
int amount)
{
- struct item_data *data;
int w;
MAP_LOG_PC(sd, "PICKUP %d %d"_fmt, item_data->nameid, amount);
@@ -1981,7 +1916,7 @@ PickupFail pc_additem(dumb_ptr<map_session_data> sd, Item *item_data,
if (!item_data->nameid || amount <= 0)
return PickupFail::BAD_ITEM;
- data = itemdb_search(item_data->nameid);
+ P<struct item_data> data = itemdb_search(item_data->nameid);
if ((w = data->weight * amount) + sd->weight > sd->max_weight)
return PickupFail::TOO_HEAVY;
@@ -2013,7 +1948,7 @@ PickupFail pc_additem(dumb_ptr<map_session_data> sd, Item *item_data,
sd->status.inventory[i].equip = EPOS::ZERO;
sd->status.inventory[i].amount = amount;
- sd->inventory_data[i] = data;
+ sd->inventory_data[i] = Some(data);
clif_additem(sd, i, amount, PickupFail::OKAY);
}
else
@@ -2037,18 +1972,18 @@ int pc_delitem(dumb_ptr<map_session_data> sd, IOff0 n, int amount, int type)
trade_tradecancel(sd);
if (!sd->status.inventory[n].nameid || amount <= 0
- || sd->status.inventory[n].amount < amount
- || sd->inventory_data[n] == nullptr)
+ || sd->status.inventory[n].amount < amount)
return 1;
+ P<struct item_data> sdidn = TRY_UNWRAP(sd->inventory_data[n], return 1);
sd->status.inventory[n].amount -= amount;
- sd->weight -= sd->inventory_data[n]->weight * amount;
+ sd->weight -= sdidn->weight * amount;
if (sd->status.inventory[n].amount <= 0)
{
if (bool(sd->status.inventory[n].equip))
pc_unequipitem(sd, n, CalcStatus::NOW);
sd->status.inventory[n] = Item{};
- sd->inventory_data[n] = nullptr;
+ sd->inventory_data[n] = None;
}
if (!(type & 1))
clif_delitem(sd, n, amount);
@@ -2097,8 +2032,6 @@ int pc_dropitem(dumb_ptr<map_session_data> sd, IOff0 n, int amount)
static
int can_pick_item_up_from(dumb_ptr<map_session_data> self, BlockId other_id)
{
- PartyPair p = party_search(self->status.party_id);
-
/* From ourselves or from no-one? */
if (!self || self->bl_id == other_id || !other_id)
return 1;
@@ -2114,9 +2047,10 @@ int can_pick_item_up_from(dumb_ptr<map_session_data> self, BlockId other_id)
return 1;
/* From a party member? */
+ Option<PartyPair> p = party_search(self->status.party_id);
if (self->status.party_id
&& self->status.party_id == other->status.party_id
- && p && p->item != 0)
+ && p.pmd_pget(&PartyMost::item).copy_or(0) != 0)
return 1;
/* From someone who is far away? */
@@ -2192,16 +2126,13 @@ int pc_takeitem(dumb_ptr<map_session_data> sd, dumb_ptr<flooritem_data> fitem)
static
int pc_isUseitem(dumb_ptr<map_session_data> sd, IOff0 n)
{
- struct item_data *item;
ItemNameId nameid;
nullpo_retz(sd);
- item = sd->inventory_data[n];
+ P<struct item_data> item = TRY_UNWRAP(sd->inventory_data[n], return 0);
nameid = sd->status.inventory[n].nameid;
- if (item == nullptr)
- return 0;
if (itemdb_type(nameid) != ItemType::USE)
return 0;
@@ -2223,7 +2154,9 @@ int pc_useitem(dumb_ptr<map_session_data> sd, IOff0 n)
nullpo_retr(1, sd);
- if (n.ok() && sd->inventory_data[n])
+ if (!n.ok())
+ return 0;
+ OMATCH_BEGIN_SOME (sdidn, sd->inventory_data[n])
{
amount = sd->status.inventory[n].amount;
if (!sd->status.inventory[n].nameid
@@ -2234,12 +2167,13 @@ int pc_useitem(dumb_ptr<map_session_data> sd, IOff0 n)
return 1;
}
- const ScriptBuffer *script = sd->inventory_data[n]->use_script.get();
+ P<const ScriptBuffer> script = borrow(*sdidn->use_script);
clif_useitemack(sd, n, amount - 1, 1);
pc_delitem(sd, n, 1, 1);
run_script(ScriptPointer(script, 0), sd->bl_id, BlockId());
}
+ OMATCH_END ();
return 0;
}
@@ -2277,18 +2211,19 @@ int pc_setpos(dumb_ptr<map_session_data> sd,
mapname_ = mapname_org;
- map_local *m = map_mapname2mapid(mapname_);
- if (!m)
+ Option<P<map_local>> m_ = map_mapname2mapid(mapname_);
+ if (m_.is_none())
{
if (sd->mapname_)
{
IP4Address ip;
int port;
- if (map_mapname2ipport(mapname_, &ip, &port) == 0)
+ if (map_mapname2ipport(mapname_, borrow(ip), borrow(port)) == 0)
{
skill_stop_dancing(sd, 1);
clif_clearchar(sd, clrtype);
map_delblock(sd);
+ // *cringe*
sd->mapname_ = mapname_;
sd->bl_x = x;
sd->bl_y = y;
@@ -2310,6 +2245,7 @@ int pc_setpos(dumb_ptr<map_session_data> sd,
#endif
return 1;
}
+ P<map_local> m = TRY_UNWRAP(m_, abort());
if (x < 0 || x >= m->xs || y < 0 || y >= m->ys)
x = y = 0;
@@ -2353,35 +2289,6 @@ int pc_setpos(dumb_ptr<map_session_data> sd,
}
/*==========================================
- * PCのランダムワープ
- *------------------------------------------
- */
-int pc_randomwarp(dumb_ptr<map_session_data> sd, BeingRemoveWhy type)
-{
- int x, y, i = 0;
-
- nullpo_retz(sd);
-
- map_local *m = sd->bl_m;
-
- if (sd->bl_m->flag.get(MapFlag::NOTELEPORT)) // テレポート禁止
- return 0;
-
- do
- {
- x = random_::in(1, m->xs - 2);
- y = random_::in(1, m->ys - 2);
- }
- while (bool(read_gatp(m, x, y) & MapCell::UNWALKABLE)
- && (i++) < 1000);
-
- if (i < 1000)
- pc_setpos(sd, m->name_, x, y, type);
-
- return 0;
-}
-
-/*==========================================
*
*------------------------------------------
*/
@@ -2504,8 +2411,8 @@ void pc_walk(TimerData *, tick_t tick, BlockId id, unsigned char data)
if (sd->status.party_id)
{ // パーティのHP情報通知検査
- PartyPair p = party_search(sd->status.party_id);
- if (p)
+ Option<PartyPair> p = party_search(sd->status.party_id);
+ if (p.is_some())
{
int p_flag = 0;
map_foreachinmovearea(std::bind(party_send_hp_check, ph::_1, sd->status.party_id, &p_flag),
@@ -2631,72 +2538,6 @@ void pc_touch_all_relevant_npcs(dumb_ptr<map_session_data> sd)
sd->areanpc_id = BlockId();
}
-/*==========================================
- *
- *------------------------------------------
- */
-int pc_movepos(dumb_ptr<map_session_data> sd, int dst_x, int dst_y)
-{
- int moveblock;
- int dx, dy;
-
- struct walkpath_data wpd;
-
- nullpo_retz(sd);
-
- if (path_search(&wpd, sd->bl_m, sd->bl_x, sd->bl_y, dst_x, dst_y, 0))
- return 1;
-
- sd->dir = sd->head_dir = map_calc_dir(sd, dst_x, dst_y);
-
- dx = dst_x - sd->bl_x;
- dy = dst_y - sd->bl_y;
-
- moveblock = (sd->bl_x / BLOCK_SIZE != dst_x / BLOCK_SIZE
- || sd->bl_y / BLOCK_SIZE != dst_y / BLOCK_SIZE);
-
- map_foreachinmovearea(std::bind(clif_pcoutsight, ph::_1, sd),
- sd->bl_m,
- sd->bl_x - AREA_SIZE, sd->bl_y - AREA_SIZE,
- sd->bl_x + AREA_SIZE, sd->bl_y + AREA_SIZE,
- dx, dy,
- BL::NUL);
-
- if (moveblock)
- map_delblock(sd);
- sd->bl_x = dst_x;
- sd->bl_y = dst_y;
- if (moveblock)
- map_addblock(sd);
-
- map_foreachinmovearea(std::bind(clif_pcinsight, ph::_1, sd),
- sd->bl_m,
- sd->bl_x - AREA_SIZE, sd->bl_y - AREA_SIZE,
- sd->bl_x + AREA_SIZE, sd->bl_y + AREA_SIZE,
- -dx, -dy,
- BL::NUL);
-
- if (sd->status.party_id)
- { // パーティのHP情報通知検査
- PartyPair p = party_search(sd->status.party_id);
- if (p)
- {
- int flag = 0;
- map_foreachinmovearea(std::bind(party_send_hp_check, ph::_1, sd->status.party_id, &flag),
- sd->bl_m,
- sd->bl_x - AREA_SIZE, sd->bl_y - AREA_SIZE,
- sd->bl_x + AREA_SIZE, sd->bl_y + AREA_SIZE,
- -dx, -dy,
- BL::PC);
- if (flag)
- sd->party_hp = -1;
- }
- }
-
- pc_touch_all_relevant_npcs(sd);
- return 0;
-}
-
//
// 武器戦闘
//
@@ -2764,8 +2605,8 @@ void pc_attack_timer(TimerData *, tick_t tick, BlockId id)
if (sd->opt1 != Opt1::ZERO)
return;
- Option *opt = battle_get_option(bl);
- if (opt != nullptr && bool(*opt & Option::REAL_ANY_HIDE))
+ Opt0 *opt = battle_get_option(bl);
+ if (opt != nullptr && bool(*opt & Opt0::REAL_ANY_HIDE))
return;
if (!battle_config.skill_delay_attack_enable)
@@ -2824,7 +2665,7 @@ void pc_attack_timer(TimerData *, tick_t tick, BlockId id)
sd->attackabletime = tick + (sd->aspd * 2);
}
if (sd->attackabletime <= tick)
- sd->attackabletime = tick + static_cast<interval_t>(battle_config.max_aspd) * 2;
+ sd->attackabletime = tick + battle_config.max_aspd * 2;
}
}
@@ -3017,6 +2858,12 @@ int pc_gainexp_reason(dumb_ptr<map_session_data> sd, int base_exp, int job_exp,
}
}
+ // Double Xp Weekends
+ base_exp = (base_exp * static_cast<double>(battle_config.base_exp_rate) / 100.);
+ if (base_exp <= 0)
+ base_exp = 0;
+ else if (base_exp > 1000000000)
+ base_exp = 1000000000;
sd->status.base_exp += base_exp;
// [Fate] Adjust experience points that healers can extract from this character
@@ -3024,7 +2871,6 @@ int pc_gainexp_reason(dumb_ptr<map_session_data> sd, int base_exp, int job_exp,
{
const int max_heal_xp =
20 + (sd->status.base_level * sd->status.base_level);
-
sd->heal_xp += base_exp;
if (sd->heal_xp > max_heal_xp)
sd->heal_xp = max_heal_xp;
@@ -3047,6 +2893,12 @@ int pc_gainexp_reason(dumb_ptr<map_session_data> sd, int base_exp, int job_exp,
}
}
+ // Double Xp Weekends
+ job_exp = (job_exp * static_cast<double>(battle_config.job_exp_rate) / 100.);
+ if (job_exp <= 0)
+ job_exp = 0;
+ else if (job_exp > 1000000000)
+ job_exp = 1000000000;
sd->status.job_exp += job_exp;
if (sd->status.job_exp < 0)
sd->status.job_exp = 0;
@@ -3247,94 +3099,6 @@ int pc_skillup(dumb_ptr<map_session_data> sd, SkillID skill_num)
}
/*==========================================
- * /resetlvl
- *------------------------------------------
- */
-int pc_resetlvl(dumb_ptr<map_session_data> sd, int type)
-{
- nullpo_retz(sd);
-
- for (SkillID i : erange(SkillID(1), MAX_SKILL))
- {
- sd->status.skill[i].lv = 0;
- }
-
- if (type == 1)
- {
- sd->status.skill_point = 0;
- sd->status.base_level = 1;
- sd->status.job_level = 1;
- sd->status.base_exp = 0;
- sd->status.job_exp = 0;
- sd->status.option = Option::ZERO;
-
- for (ATTR attr : ATTRs)
- sd->status.attrs[attr] = 1;
- }
-
- if (type == 2)
- {
- sd->status.skill_point = 0;
- sd->status.base_level = 1;
- sd->status.job_level = 1;
- sd->status.base_exp = 0;
- sd->status.job_exp = 0;
- }
- if (type == 3)
- {
- sd->status.base_level = 1;
- sd->status.base_exp = 0;
- }
- if (type == 4)
- {
- sd->status.job_level = 1;
- sd->status.job_exp = 0;
- }
-
- clif_updatestatus(sd, SP::STATUSPOINT);
- clif_updatestatus(sd, SP::STR);
- clif_updatestatus(sd, SP::AGI);
- clif_updatestatus(sd, SP::VIT);
- clif_updatestatus(sd, SP::INT);
- clif_updatestatus(sd, SP::DEX);
- clif_updatestatus(sd, SP::LUK);
- clif_updatestatus(sd, SP::BASELEVEL);
- clif_updatestatus(sd, SP::JOBLEVEL);
- clif_updatestatus(sd, SP::STATUSPOINT);
- clif_updatestatus(sd, SP::NEXTBASEEXP);
- clif_updatestatus(sd, SP::NEXTJOBEXP);
- clif_updatestatus(sd, SP::SKILLPOINT);
-
- clif_updatestatus(sd, SP::USTR); // Updates needed stat points - Valaris
- clif_updatestatus(sd, SP::UAGI);
- clif_updatestatus(sd, SP::UVIT);
- clif_updatestatus(sd, SP::UINT);
- clif_updatestatus(sd, SP::UDEX);
- clif_updatestatus(sd, SP::ULUK); // End Addition
-
- for (EQUIP i : EQUIPs)
- {
- // unequip items that can't be equipped by base 1 [Valaris]
- IOff0 *idx = &sd->equip_index_maybe[i];
- if ((*idx).ok())
- {
- if (!pc_isequip(sd, *idx))
- {
- pc_unequipitem(sd, *idx, CalcStatus::LATER);
- *idx = IOff0::from(-1);
- }
- }
- }
-
- clif_skillinfoblock(sd);
- pc_calcstatus(sd, 0);
-
- MAP_LOG_STATS(sd, "STATRESET"_fmt);
-
- return 0;
-}
-
-/*==========================================
* /resetstate
*------------------------------------------
*/
@@ -3435,9 +3199,12 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd,
if (sd->status.party_id)
{ // on-the-fly party hp updates [Valaris]
- PartyPair p = party_search(sd->status.party_id);
- if (p)
+ Option<PartyPair> p_ = party_search(sd->status.party_id);
+ OMATCH_BEGIN_SOME (p, p_)
+ {
clif_party_hp(p, sd);
+ }
+ OMATCH_END ();
} // end addition [Valaris]
return 0;
@@ -3728,8 +3495,7 @@ int pc_setparam(dumb_ptr<map_session_data> sd, SP type, int val)
}
break;
case SP::SEX:
- // this is a really bad idea
- sd->sex = static_cast<SEX>(val);
+ chrif_char_ask_name(AccountId(), sd->status_key.name, 5, HumanTimeDiff());
break;
case SP::WEIGHT:
sd->weight = val;
@@ -3755,7 +3521,7 @@ int pc_setparam(dumb_ptr<map_session_data> sd, SP type, int val)
case SP::INT:
case SP::DEX:
case SP::LUK:
- sd->status.attrs[sp_to_attr(type)] = val;
+ pc_statusup2(sd, type, (val - sd->status.attrs[sp_to_attr(type)]));
break;
}
clif_updatestatus(sd, type);
@@ -3803,9 +3569,12 @@ int pc_heal(dumb_ptr<map_session_data> sd, int hp, int sp)
if (sd->status.party_id)
{ // on-the-fly party hp updates [Valaris]
- PartyPair p = party_search(sd->status.party_id);
- if (p)
+ Option<PartyPair> p_ = party_search(sd->status.party_id);
+ OMATCH_BEGIN_SOME (p, p_)
+ {
clif_party_hp(p, sd);
+ }
+ OMATCH_END ();
} // end addition [Valaris]
return hp + sp;
@@ -3933,75 +3702,6 @@ int pc_itemheal_effect(dumb_ptr<map_session_data> sd, int hp, int sp)
}
/*==========================================
- * HP/SP回復
- *------------------------------------------
- */
-int pc_percentheal(dumb_ptr<map_session_data> sd, int hp, int sp)
-{
- nullpo_retz(sd);
-
- if (pc_checkoverhp(sd))
- {
- if (hp > 0)
- hp = 0;
- }
- if (pc_checkoversp(sd))
- {
- if (sp > 0)
- sp = 0;
- }
- if (hp)
- {
- if (hp >= 100)
- {
- sd->status.hp = sd->status.max_hp;
- }
- else if (hp <= -100)
- {
- sd->status.hp = 0;
- pc_damage(nullptr, sd, 1);
- }
- else
- {
- sd->status.hp += sd->status.max_hp * hp / 100;
- if (sd->status.hp > sd->status.max_hp)
- sd->status.hp = sd->status.max_hp;
- if (sd->status.hp <= 0)
- {
- sd->status.hp = 0;
- pc_damage(nullptr, sd, 1);
- hp = 0;
- }
- }
- }
- if (sp)
- {
- if (sp >= 100)
- {
- sd->status.sp = sd->status.max_sp;
- }
- else if (sp <= -100)
- {
- sd->status.sp = 0;
- }
- else
- {
- sd->status.sp += sd->status.max_sp * sp / 100;
- if (sd->status.sp > sd->status.max_sp)
- sd->status.sp = sd->status.max_sp;
- if (sd->status.sp < 0)
- sd->status.sp = 0;
- }
- }
- if (hp)
- clif_updatestatus(sd, SP::HP);
- if (sp)
- clif_updatestatus(sd, SP::SP);
-
- return 0;
-}
-
-/*==========================================
* 見た目変更
*------------------------------------------
*/
@@ -4044,21 +3744,6 @@ int pc_changelook(dumb_ptr<map_session_data> sd, LOOK type, int val)
}
/*==========================================
- * 付属品(鷹,ペコ,カート)設定
- *------------------------------------------
- */
-int pc_setoption(dumb_ptr<map_session_data> sd, Option type)
-{
- nullpo_retz(sd);
-
- sd->status.option = type;
- clif_changeoption(sd);
- pc_calcstatus(sd, 0);
-
- return 0;
-}
-
-/*==========================================
* script用変数の値を読む
*------------------------------------------
*/
@@ -4088,11 +3773,8 @@ ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg)
{
nullpo_retr(ZString(), sd);
- RString *s = sd->regstrm.search(reg);
- if (s)
- return *s;
-
- return ZString();
+ Option<P<RString>> s = sd->regstrm.search(reg);
+ return s.map([](P<RString> s_) -> ZString { return *s_; }).copy_or(""_s);
}
/*==========================================
@@ -4119,14 +3801,37 @@ void pc_setregstr(dumb_ptr<map_session_data> sd, SIR reg, RString str)
int pc_readglobalreg(dumb_ptr<map_session_data> sd, VarName reg)
{
int i;
-
+ int quest_shift = 0;
+ int quest_mask = 0;
nullpo_retz(sd);
+ QuestId questid;
+ XString var = reg;
+ VarName vr;
assert (sd->status.global_reg_num < GLOBAL_REG_NUM);
+ Option<P<struct quest_data>> quest_data_ = questdb_searchname(var);
+ OMATCH_BEGIN_SOME(quest_data, quest_data_)
+ {
+ questid = quest_data->questid;
+ reg = quest_data->quest_vr;
+ vr = quest_data->quest_var;
+ quest_shift = quest_data->quest_shift;
+ quest_mask = quest_data->quest_mask;
+ }
+ OMATCH_END ();
for (i = 0; i < sd->status.global_reg_num; i++)
{
if (sd->status.global_reg[i].str == reg)
- return sd->status.global_reg[i].value;
+ {
+ if (questid)
+ {
+ return ((sd->status.global_reg[i].value & (((1 << quest_mask) - 1) << (quest_shift * quest_mask))) >> (quest_shift * quest_mask));
+ }
+ else
+ {
+ return sd->status.global_reg[i].value;
+ }
+ }
}
return 0;
@@ -4139,8 +3844,13 @@ int pc_readglobalreg(dumb_ptr<map_session_data> sd, VarName reg)
int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val)
{
int i;
-
+ int quest_shift = 0;
+ int quest_mask = 0;
+ int bitval = val;
nullpo_retz(sd);
+ QuestId questid;
+ XString var = reg;
+ VarName vr;
//PC_DIE_COUNTERがスクリプトなどで変更された時の処理
if (reg == stringish<VarName>("PC_DIE_COUNTER"_s) && sd->die_counter != val)
@@ -4148,6 +3858,17 @@ int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val)
sd->die_counter = val;
pc_calcstatus(sd, 0);
}
+ Option<P<struct quest_data>> quest_data_ = questdb_searchname(var);
+ OMATCH_BEGIN_SOME(quest_data, quest_data_)
+ {
+ questid = quest_data->questid;
+ reg = quest_data->quest_vr;
+ vr = quest_data->quest_var;
+ quest_shift = quest_data->quest_shift;
+ quest_mask = quest_data->quest_mask;
+ assert (((1 << quest_mask) - 1) >= val);
+ }
+ OMATCH_END ();
assert (sd->status.global_reg_num < GLOBAL_REG_NUM);
if (val == 0)
{
@@ -4155,9 +3876,18 @@ int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val)
{
if (sd->status.global_reg[i].str == reg)
{
- sd->status.global_reg[i] =
- sd->status.global_reg[sd->status.global_reg_num - 1];
- sd->status.global_reg_num--;
+ if (questid)
+ {
+ bitval = ((sd->status.global_reg[i].value & ~(((1 << quest_mask) - 1) << (quest_shift * quest_mask))) | (val << (quest_shift * quest_mask)));
+ clif_sendquest(sd, questid, val);
+ }
+ sd->status.global_reg[i].value = bitval;
+ if (sd->status.global_reg[i].value == 0)
+ {
+ sd->status.global_reg[i] =
+ sd->status.global_reg[sd->status.global_reg_num - 1];
+ sd->status.global_reg_num--;
+ }
break;
}
}
@@ -4167,14 +3897,24 @@ int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val)
{
if (sd->status.global_reg[i].str == reg)
{
- sd->status.global_reg[i].value = val;
+ if (questid)
+ {
+ bitval = ((sd->status.global_reg[i].value & ~(((1 << quest_mask) - 1) << (quest_shift * quest_mask))) | (val << (quest_shift * quest_mask)));
+ clif_sendquest(sd, questid, val);
+ }
+ sd->status.global_reg[i].value = bitval;
return 0;
}
}
if (sd->status.global_reg_num < GLOBAL_REG_NUM)
{
sd->status.global_reg[i].str = reg;
- sd->status.global_reg[i].value = val;
+ if (questid)
+ {
+ bitval = ((sd->status.global_reg[i].value & ~(((1 << quest_mask) - 1) << (quest_shift * quest_mask))) | (val << (quest_shift * quest_mask)));
+ clif_sendquest(sd, questid, val);
+ }
+ sd->status.global_reg[i].value = bitval;
sd->status.global_reg_num++;
return 0;
}
@@ -4400,7 +4140,6 @@ int pc_signal_advanced_equipment_change(dumb_ptr<map_session_data> sd, IOff0 n)
int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS)
{
ItemNameId nameid;
- struct item_data *id;
//ソス]ソスソスソスソスソス{ソスqソスフ場合ソスフ鯉ソスソスフ職ソスニゑソスソスZソスoソスソスソスソス
nullpo_retz(sd);
@@ -4412,9 +4151,8 @@ int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS)
}
nameid = sd->status.inventory[n].nameid;
- id = sd->inventory_data[n];
- if (!id) // can't actually happen - the only caller checks this.
- return 0;
+ // can't actually happen - the only caller checks this.
+ P<struct item_data> id = TRY_UNWRAP(sd->inventory_data[n], return 0);
EPOS pos = pc_equippoint(sd, n);
if (battle_config.battle_log)
@@ -4475,17 +4213,18 @@ int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS)
ItemNameId view_i;
ItemLook view_l = ItemLook::NONE;
// TODO: This is ugly.
- if (sd->inventory_data[n])
+ OMATCH_BEGIN_SOME (sdidn, sd->inventory_data[n])
{
- bool look_not_weapon = sd->inventory_data[n]->look == ItemLook::NONE;
+ bool look_not_weapon = sdidn->look == ItemLook::NONE;
bool equip_is_weapon = bool(sd->status.inventory[n].equip & EPOS::WEAPON);
assert (look_not_weapon != equip_is_weapon);
if (look_not_weapon)
- view_i = sd->inventory_data[n]->nameid;
+ view_i = sdidn->nameid;
else
- view_l = sd->inventory_data[n]->look;
+ view_l = sdidn->look;
}
+ OMATCH_END ();
if (bool(sd->status.inventory[n].equip & EPOS::WEAPON))
{
@@ -4495,25 +4234,27 @@ int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS)
}
if (bool(sd->status.inventory[n].equip & EPOS::SHIELD))
{
- if (sd->inventory_data[n])
+ OMATCH_BEGIN (sd->inventory_data[n])
{
- if (sd->inventory_data[n]->type == ItemType::WEAPON)
+ OMATCH_CASE_SOME (sdidn)
{
- sd->status.shield = ItemNameId();
- if (sd->status.inventory[n].equip == EPOS::SHIELD)
- sd->weapontype2 = view_l;
+ if (sdidn->type == ItemType::WEAPON)
+ {
+ sd->status.shield = ItemNameId();
+ if (sd->status.inventory[n].equip == EPOS::SHIELD)
+ assert(0 && "unreachable - offhand weapons are not supported");
+ }
+ else if (sdidn->type == ItemType::ARMOR)
+ {
+ sd->status.shield = view_i;
+ }
}
- else if (sd->inventory_data[n]->type == ItemType::ARMOR)
+ OMATCH_CASE_NONE ()
{
- sd->status.shield = view_i;
- sd->weapontype2 = ItemLook::NONE;
+ sd->status.shield = ItemNameId();
}
}
- else
- {
- sd->status.shield = ItemNameId();
- sd->weapontype2 = ItemLook::NONE;
- }
+ OMATCH_END ();
pc_calcweapontype(sd);
clif_changelook(sd, LOOK::SHIELD, unwrap<ItemNameId>(sd->status.shield));
}
@@ -4563,14 +4304,14 @@ 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->status.weapon = sd->weapontype2;
+ // when reading the diff, think twice about this
+ sd->status.weapon = ItemLook::NONE;
pc_calcweapontype(sd);
pc_set_weapon_look(sd);
}
if (bool(sd->status.inventory[n].equip & EPOS::SHIELD))
{
sd->status.shield = ItemNameId();
- sd->weapontype2 = ItemLook::NONE;
pc_calcweapontype(sd);
clif_changelook(sd, LOOK::SHIELD, unwrap<ItemNameId>(sd->status.shield));
}
@@ -4728,8 +4469,7 @@ void pc_calc_pvprank_sub(dumb_ptr<block_list> bl, dumb_ptr<map_session_data> sd2
int pc_calc_pvprank(dumb_ptr<map_session_data> sd)
{
nullpo_retz(sd);
- map_local *m = sd->bl_m;
- nullpo_retz(m);
+ P<map_local> m = sd->bl_m;
if (!(m->flag.get(MapFlag::PVP)))
return 0;
@@ -4816,15 +4556,12 @@ int pc_divorce(dumb_ptr<map_session_data> sd)
}
p_sd->status.partner_id = CharId();
sd->status.partner_id = CharId();
-
- if (sd->npc_flags.divorce)
- {
- sd->npc_flags.divorce = 0;
- map_scriptcont(sd, sd->npc_id);
- }
}
else
+ {
+ sd->status.partner_id = CharId();
chrif_send_divorce(sd->status_key.char_id);
+ }
return 0;
}
@@ -4857,10 +4594,6 @@ dumb_ptr<map_session_data> pc_get_partner(dumb_ptr<map_session_data> sd)
* SP回復量計算
*------------------------------------------
*/
-static
-tick_t natural_heal_tick, natural_heal_prev_tick;
-static
-interval_t natural_heal_diff_tick;
static
interval_t pc_spheal(dumb_ptr<map_session_data> sd)
@@ -4918,12 +4651,12 @@ int pc_natural_heal_hp(dumb_ptr<map_session_data> sd)
return 0;
}
- if (sd->hp_sub >= static_cast<interval_t>(battle_config.natural_healhp_interval))
+ if (sd->hp_sub >= battle_config.natural_healhp_interval)
{
bonus = sd->nhealhp;
- while (sd->hp_sub >= static_cast<interval_t>(battle_config.natural_healhp_interval))
+ while (sd->hp_sub >= battle_config.natural_healhp_interval)
{
- sd->hp_sub -= static_cast<interval_t>(battle_config.natural_healhp_interval);
+ sd->hp_sub -= battle_config.natural_healhp_interval;
if (sd->status.hp + bonus <= sd->status.max_hp)
sd->status.hp += bonus;
else
@@ -4936,28 +4669,7 @@ int pc_natural_heal_hp(dumb_ptr<map_session_data> sd)
if (bhp != sd->status.hp)
clif_updatestatus(sd, SP::HP);
- if (sd->nshealhp > 0)
- {
- if (sd->inchealhptick >= static_cast<interval_t>(battle_config.natural_heal_skill_interval)
- && sd->status.hp < sd->status.max_hp)
- {
- bonus = sd->nshealhp;
- while (sd->inchealhptick >= static_cast<interval_t>(battle_config.natural_heal_skill_interval))
- {
- sd->inchealhptick -= static_cast<interval_t>(battle_config.natural_heal_skill_interval);
- if (sd->status.hp + bonus <= sd->status.max_hp)
- sd->status.hp += bonus;
- else
- {
- bonus = sd->status.max_hp - sd->status.hp;
- sd->status.hp = sd->status.max_hp;
- sd->hp_sub = sd->inchealhptick = interval_t::zero();
- }
- }
- }
- }
- else
- sd->inchealhptick = interval_t::zero();
+ sd->inchealhptick = interval_t::zero();
return 0;
}
@@ -4985,12 +4697,12 @@ int pc_natural_heal_sp(dumb_ptr<map_session_data> sd)
else
sd->inchealsptick = interval_t::zero();
- if (sd->sp_sub >= static_cast<interval_t>(battle_config.natural_healsp_interval))
+ if (sd->sp_sub >= battle_config.natural_healsp_interval)
{
bonus = sd->nhealsp;
- while (sd->sp_sub >= static_cast<interval_t>(battle_config.natural_healsp_interval))
+ while (sd->sp_sub >= battle_config.natural_healsp_interval)
{
- sd->sp_sub -= static_cast<interval_t>(battle_config.natural_healsp_interval);
+ sd->sp_sub -= battle_config.natural_healsp_interval;
if (sd->status.sp + bonus <= sd->status.max_sp)
sd->status.sp += bonus;
else
@@ -5004,28 +4716,7 @@ int pc_natural_heal_sp(dumb_ptr<map_session_data> sd)
if (bsp != sd->status.sp)
clif_updatestatus(sd, SP::SP);
- if (sd->nshealsp > 0)
- {
- if (sd->inchealsptick >= static_cast<interval_t>(battle_config.natural_heal_skill_interval)
- && sd->status.sp < sd->status.max_sp)
- {
- bonus = sd->nshealsp;
- while (sd->inchealsptick >= static_cast<interval_t>(battle_config.natural_heal_skill_interval))
- {
- sd->inchealsptick -= static_cast<interval_t>(battle_config.natural_heal_skill_interval);
- if (sd->status.sp + bonus <= sd->status.max_sp)
- sd->status.sp += bonus;
- else
- {
- bonus = sd->status.max_sp - sd->status.sp;
- sd->status.sp = sd->status.max_sp;
- sd->sp_sub = sd->inchealsptick = interval_t::zero();
- }
- }
- }
- }
- else
- sd->inchealsptick = interval_t::zero();
+ sd->inchealsptick = interval_t::zero();
return 0;
}
@@ -5147,8 +4838,6 @@ void pc_setsavepoint(dumb_ptr<map_session_data> sd, MapName mapname, int x, int
*------------------------------------------
*/
static
-int last_save_fd, save_flag;
-static
void pc_autosave_sub(dumb_ptr<map_session_data> sd)
{
nullpo_retv(sd);
@@ -5175,7 +4864,7 @@ void pc_autosave(TimerData *, tick_t)
if (save_flag == 0)
last_save_fd = -1;
- interval_t interval = autosave_time / (clif_countusers() + 1);
+ interval_t interval = map_conf.autosave_time / (clif_countusers() + 1);
if (interval <= interval_t::zero())
interval = 1_ms;
Timer(gettick() + interval,
@@ -5228,7 +4917,7 @@ void do_init_pc(void)
pc_natural_heal,
NATURAL_HEAL_INTERVAL
).detach();
- Timer(gettick() + autosave_time,
+ Timer(gettick() + map_conf.autosave_time,
pc_autosave
).detach();
}
@@ -5240,15 +4929,15 @@ void pc_cleanup(dumb_ptr<map_session_data> sd)
void pc_invisibility(dumb_ptr<map_session_data> sd, int enabled)
{
- if (enabled && !bool(sd->status.option & Option::INVISIBILITY))
+ if (enabled && !bool(sd->status.option & Opt0::INVISIBILITY))
{
clif_clearchar(sd, BeingRemoveWhy::WARPED);
- sd->status.option |= Option::INVISIBILITY;
+ sd->status.option |= Opt0::INVISIBILITY;
clif_status_change(sd, StatusChange::CLIF_OPTION_SC_INVISIBILITY, 1);
}
else if (!enabled)
{
- sd->status.option &= ~Option::INVISIBILITY;
+ sd->status.option &= ~Opt0::INVISIBILITY;
clif_status_change(sd, StatusChange::CLIF_OPTION_SC_INVISIBILITY, 0);
pc_setpos(sd, sd->bl_m->name_, sd->bl_x, sd->bl_y, BeingRemoveWhy::WARPED);
}
@@ -5280,4 +4969,5 @@ int pc_logout(dumb_ptr<map_session_data> sd) // [fate] Player logs out
MAP_LOG_STATS(sd, "LOGOUT"_fmt);
return 0;
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/pc.hpp b/src/map/pc.hpp
index 3187cd9..d100938 100644
--- a/src/map/pc.hpp
+++ b/src/map/pc.hpp
@@ -20,24 +20,21 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "fwd.hpp"
-
#include "pc.t.hpp"
-#include "../strings/fwd.hpp"
+#include "fwd.hpp"
#include "../generic/dumb_ptr.hpp"
-#include "../mmo/utils.hpp"
-
-#include "../proto2/fwd.hpp"
-
-#include "clif.t.hpp"
+#include "../mmo/clif.t.hpp"
#include "map.hpp"
+#include "quest.hpp"
namespace tmwa
{
+namespace map
+{
inline
void pc_setsit(dumb_ptr<map_session_data> sd)
{
@@ -62,7 +59,7 @@ void pc_setdir(dumb_ptr<map_session_data> sd, DIR b)
inline
bool pc_isinvisible(dumb_ptr<map_session_data> sd)
{
- return bool(sd->status.option & Option::HIDE);
+ return bool(sd->status.option & Opt0::HIDE);
}
inline
bool pc_is90overweight(dumb_ptr<map_session_data> sd)
@@ -83,7 +80,7 @@ int pc_counttargeted(dumb_ptr<map_session_data> sd, dumb_ptr<block_list> src,
int pc_setrestartvalue(dumb_ptr<map_session_data> sd, int type);
void pc_makesavestatus(dumb_ptr<map_session_data>);
int pc_setnewpc(dumb_ptr<map_session_data>, AccountId, CharId, int, uint32_t /*tick_t*/, SEX);
-int pc_authok(AccountId, int, TimeT, short tmw_version, const CharKey *, const CharData *);
+int pc_authok(AccountId, int, short tmw_version, const CharKey *, const CharData *);
int pc_authfail(AccountId accid);
EPOS pc_equippoint(dumb_ptr<map_session_data> sd, IOff0 n);
@@ -93,10 +90,8 @@ IOff0 pc_checkequip(dumb_ptr<map_session_data> sd, EPOS pos);
int pc_walktoxy(dumb_ptr<map_session_data>, int, int);
int pc_stop_walking(dumb_ptr<map_session_data>, int);
-int pc_movepos(dumb_ptr<map_session_data>, int, int);
int pc_setpos(dumb_ptr<map_session_data>, MapName, int, int, BeingRemoveWhy);
void pc_setsavepoint(dumb_ptr<map_session_data>, MapName, int, int);
-int pc_randomwarp(dumb_ptr<map_session_data> sd, BeingRemoveWhy type);
ADDITEM pc_checkadditem(dumb_ptr<map_session_data>, ItemNameId, int);
int pc_inventoryblank(dumb_ptr<map_session_data>);
@@ -133,7 +128,6 @@ int pc_need_status_point(dumb_ptr<map_session_data>, SP);
int pc_statusup(dumb_ptr<map_session_data>, SP);
int pc_statusup2(dumb_ptr<map_session_data>, SP, int);
int pc_skillup(dumb_ptr<map_session_data>, SkillID);
-int pc_resetlvl(dumb_ptr<map_session_data>, int type);
int pc_resetstate(dumb_ptr<map_session_data>);
int pc_resetskill(dumb_ptr<map_session_data>);
int pc_equipitem(dumb_ptr<map_session_data>, IOff0, EPOS);
@@ -144,8 +138,6 @@ int pc_useitem(dumb_ptr<map_session_data>, IOff0);
int pc_damage(dumb_ptr<block_list>, dumb_ptr<map_session_data>, int);
int pc_heal(dumb_ptr<map_session_data>, int, int);
int pc_itemheal(dumb_ptr<map_session_data> sd, int hp, int sp);
-int pc_percentheal(dumb_ptr<map_session_data> sd, int, int);
-int pc_setoption(dumb_ptr<map_session_data>, Option);
int pc_changelook(dumb_ptr<map_session_data>, LOOK, int);
int pc_readparam(dumb_ptr<map_session_data>, SP);
@@ -154,6 +146,8 @@ int pc_readreg(dumb_ptr<map_session_data>, SIR);
void pc_setreg(dumb_ptr<map_session_data>, SIR, int);
ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg);
void pc_setregstr(dumb_ptr<map_session_data> sd, SIR reg, RString str);
+void update_quest(dumb_ptr<map_session_data> sd, VarName quest_var, int value);
+void update_allquest(dumb_ptr<map_session_data> sd);
int pc_readglobalreg(dumb_ptr<map_session_data>, VarName );
int pc_setglobalreg(dumb_ptr<map_session_data>, VarName , int);
int pc_readaccountreg(dumb_ptr<map_session_data>, VarName );
@@ -177,6 +171,8 @@ void pc_setstand(dumb_ptr<map_session_data> sd);
void pc_cleanup(dumb_ptr<map_session_data> sd); // [Fate] Clean up after a logged-out PC
int pc_read_gm_account(Session *, const std::vector<Packet_Repeat<0x2b15>>&);
+int pc_setpvptimer(dumb_ptr<map_session_data> sd, interval_t);
+int pc_delpvptimer(dumb_ptr<map_session_data> sd);
int pc_setinvincibletimer(dumb_ptr<map_session_data> sd, interval_t);
int pc_delinvincibletimer(dumb_ptr<map_session_data> sd);
int pc_logout(dumb_ptr<map_session_data> sd); // [fate] Player logs out
@@ -184,4 +180,5 @@ int pc_logout(dumb_ptr<map_session_data> sd); // [fate] Player logs out
void pc_show_motd(dumb_ptr<map_session_data> sd);
void do_init_pc(void);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/pc.t.hpp b/src/map/pc.t.hpp
index 427e8c3..c9235fa 100644
--- a/src/map/pc.t.hpp
+++ b/src/map/pc.t.hpp
@@ -28,6 +28,8 @@
namespace tmwa
{
+namespace map
+{
enum class PC_GAINEXP_REASON
{
KILLING = 0,
@@ -54,4 +56,5 @@ enum class CalcStatus
NOW,
LATER,
};
+} // namespace map
} // namespace tmwa
diff --git a/src/map/quest.cpp b/src/map/quest.cpp
new file mode 100644
index 0000000..dfe19ff
--- /dev/null
+++ b/src/map/quest.cpp
@@ -0,0 +1,135 @@
+#include "quest.hpp"
+// quest.cpp - Quest Log.
+//
+// Copyright © 2015 Ed Pasek <pasekei@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <algorithm>
+
+#include "../strings/astring.hpp"
+#include "../strings/zstring.hpp"
+#include "../strings/xstring.hpp"
+
+#include "../generic/db.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
+#include "../io/line.hpp"
+
+#include "../mmo/config_parse.hpp"
+#include "../mmo/extract_enums.hpp"
+
+#include "../ast/quest.hpp"
+
+#include "../poison.hpp"
+#include "globals.hpp"
+#include "script-parse.hpp"
+
+namespace tmwa
+{
+namespace map
+{
+// Function declarations
+
+static
+void questdb_searchname_sub(Borrowed<struct quest_data> quest, VarName str, Borrowed<Option<Borrowed<struct quest_data>>> dst)
+{
+ if (quest->quest_var == str)
+ *dst = Some(quest);
+}
+
+Option<Borrowed<struct quest_data>> questdb_searchname(XString str_)
+{
+ VarName str = stringish<VarName>(str_);
+ if (XString(str) != str_)
+ return None;
+ Option<P<struct quest_data>> quest = None;
+ for (auto& pair : quest_db)
+ questdb_searchname_sub(borrow(pair.second), str, borrow(quest));
+ return quest;
+}
+
+Borrowed<struct quest_data> questdb_search(QuestId questid)
+{
+ Option<P<struct quest_data>> id_ = quest_db.search(questid);
+ OMATCH_BEGIN_SOME (id, id_)
+ {
+ return id;
+ }
+ OMATCH_END ();
+
+ P<struct quest_data> id = quest_db.init(questid);
+
+ id->questid = questid;
+
+ return id;
+}
+
+Option<Borrowed<struct quest_data>> questdb_exists(QuestId questid)
+{
+ return quest_db.search(questid);
+}
+
+bool quest_readdb(ZString filename)
+{
+ io::LineCharReader in(filename);
+
+ if (!in.is_open())
+ {
+ PRINTF("can't read %s\n"_fmt, filename);
+ return false;
+ }
+
+ int ln = 0;
+
+ while (true)
+ {
+ auto res = TRY_UNWRAP(ast::quest::parse_quest(in),
+ {
+ PRINTF("read %s done (count=%d)\n"_fmt, filename, ln);
+ return true;
+ });
+ if (res.get_failure())
+ PRINTF("%s\n"_fmt, res.get_failure());
+ ast::quest::QuestOrComment ioc = TRY_UNWRAP(std::move(res.get_success()), return false);
+
+ MATCH_BEGIN (ioc)
+ {
+ MATCH_CASE (const ast::quest::Comment&, c)
+ {
+ (void)c;
+ }
+ MATCH_CASE (const ast::quest::Quest&, quest)
+ {
+ ln++;
+
+ quest_data qdv {};
+ qdv.questid = quest.questid.data;
+ qdv.quest_var = quest.quest_var.data;
+ qdv.quest_vr = quest.quest_vr.data;
+ qdv.quest_shift = quest.quest_shift.data;
+ qdv.quest_mask = quest.quest_mask.data;
+
+ Borrowed<struct quest_data> id = questdb_search(qdv.questid);
+ *id = std::move(qdv);
+ }
+ }
+ MATCH_END ();
+ }
+}
+} // namespace map
+} // namespace tmwa
diff --git a/src/sexpr/bind.cpp b/src/map/quest.hpp
index d8d0caa..65e6f4e 100644
--- a/src/sexpr/bind.cpp
+++ b/src/map/quest.hpp
@@ -1,7 +1,7 @@
-#include "bind.hpp"
-// bind.cpp - Just include the header file.
+#pragma once
+// quest.hpp - Quest Log.
//
-// Copyright © 2012 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2015 Ed Pasek <pasekei@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -18,12 +18,34 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "fwd.hpp"
+#include "../mmo/ids.hpp"
+#include "../high/mmo.hpp"
+
+#include "map.hpp"
+#include "script-buffer.hpp"
namespace tmwa
{
-namespace sexpr
+namespace map
+{
+constexpr int MAX_QUEST_DB (60355+1);
+struct quest_data
{
-} // namespace sexpr
+ QuestId questid;
+ VarName quest_var;
+ VarName quest_vr;
+ int quest_shift;
+ int quest_mask;
+};
+inline
+Option<Borrowed<struct quest_data>> questdb_searchname(VarName) = delete;
+Option<Borrowed<struct quest_data>> questdb_searchname(XString quest_var);
+Borrowed<struct quest_data> questdb_search(QuestId questid);
+Option<Borrowed<struct quest_data>> questdb_exists(QuestId questid);
+
+// get quest var by quest name / mask / bit
+bool quest_readdb(ZString filename);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/script-buffer.hpp b/src/map/script-buffer.hpp
new file mode 100644
index 0000000..e720044
--- /dev/null
+++ b/src/map/script-buffer.hpp
@@ -0,0 +1,45 @@
+#pragma once
+// script-buffer.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include <memory>
+
+
+namespace tmwa
+{
+namespace map
+{
+class ScriptBuffer;
+} // namespace map
+} // namespace tmwa
+
+namespace std
+{
+template<>
+struct default_delete<const tmwa::map::ScriptBuffer>
+{
+ default_delete() {}
+ default_delete(default_delete<tmwa::map::ScriptBuffer>) {}
+ void operator()(const tmwa::map::ScriptBuffer *sd);
+};
+} // namespace std
diff --git a/src/map/script-call-internal.hpp b/src/map/script-call-internal.hpp
new file mode 100644
index 0000000..b9b3a9f
--- /dev/null
+++ b/src/map/script-call-internal.hpp
@@ -0,0 +1,100 @@
+#pragma once
+// script-call-internal.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "script-call.hpp"
+#include "fwd.hpp"
+
+#include "../mmo/ids.hpp"
+
+#include "script-persist.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+enum class VariableCode : uint8_t
+{
+ PARAM,
+ VARIABLE,
+};
+
+struct script_stack
+{
+ std::vector<struct script_data> stack_datav;
+};
+
+enum class ScriptEndState;
+// future improvements coming!
+class ScriptState
+{
+public:
+ struct script_stack *stack;
+ int start, end;
+ ScriptEndState state;
+ BlockId rid, oid;
+ ScriptPointer scriptp, new_scriptp;
+ int defsp, new_defsp, freeloop;
+};
+
+void run_func(ScriptState *st);
+
+enum class ScriptEndState
+{
+ ZERO,
+ STOP,
+ END,
+ RERUNLINE,
+ GOTO,
+ RETFUNC,
+};
+
+dumb_ptr<map_session_data> script_rid2sd(ScriptState *st);
+void get_val(dumb_ptr<map_session_data> sd, struct script_data *data);
+__attribute__((deprecated))
+void get_val(ScriptState *st, struct script_data *data);
+struct script_data get_val2(ScriptState *st, SIR reg);
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd);
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id);
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd);
+__attribute__((warn_unused_result))
+RString conv_str(ScriptState *st, struct script_data *data);
+__attribute__((warn_unused_result))
+int conv_num(ScriptState *st, struct script_data *data);
+__attribute__((warn_unused_result))
+Borrowed<const ScriptBuffer> conv_script(ScriptState *st, struct script_data *data);
+
+template<class T>
+void push_int(struct script_stack *stack, int val);
+template<class T>
+void push_reg(struct script_stack *stack, SIR reg);
+template<class T>
+void push_script(struct script_stack *stack, Borrowed<const ScriptBuffer> code);
+template<class T>
+void push_str(struct script_stack *stack, RString str);
+
+void push_copy(struct script_stack *stack, int pos_);
+void pop_stack(struct script_stack *stack, int start, int end);
+} // namespace map
+} // namespace tmwa
+
+#include "script-call-internal.tcc"
diff --git a/src/map/script-call-internal.tcc b/src/map/script-call-internal.tcc
new file mode 100644
index 0000000..e10b69c
--- /dev/null
+++ b/src/map/script-call-internal.tcc
@@ -0,0 +1,79 @@
+// script-call-internal.tcc - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "script-persist.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+template<class D>
+bool first_type_is_any()
+{
+ return false;
+}
+
+template<class D, class F, class... R>
+constexpr
+bool first_type_is_any()
+{
+ return std::is_same<D, F>::value || first_type_is_any<D, R...>();
+}
+
+
+template<class T>
+void push_int(struct script_stack *stack, int val)
+{
+ static_assert(first_type_is_any<T, ScriptDataPos, ScriptDataInt, ScriptDataArg, ScriptDataFuncRef>(), "not int type");
+
+ script_data nsd = T{.numi= val};
+ stack->stack_datav.push_back(nsd);
+}
+
+template<class T>
+void push_reg(struct script_stack *stack, SIR reg)
+{
+ static_assert(first_type_is_any<T, ScriptDataParam, ScriptDataVariable>(), "not reg type");
+
+ script_data nsd = T{.reg= reg};
+ stack->stack_datav.push_back(nsd);
+}
+
+template<class T>
+void push_script(struct script_stack *stack, Borrowed<const ScriptBuffer> code)
+{
+ static_assert(first_type_is_any<T, ScriptDataRetInfo>(), "not scriptbuf type");
+
+ script_data nsd = T{.script= code};
+ stack->stack_datav.push_back(nsd);
+}
+
+template<class T>
+void push_str(struct script_stack *stack, RString str)
+{
+ static_assert(first_type_is_any<T, ScriptDataStr>(), "not str type");
+
+ script_data nsd = T{.str= str};
+ stack->stack_datav.push_back(nsd);
+}
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
new file mode 100644
index 0000000..c3c6aa1
--- /dev/null
+++ b/src/map/script-call.cpp
@@ -0,0 +1,926 @@
+#include "script-call-internal.hpp"
+// script-call.cpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011 Chuck Miller
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2013 wushin
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../generic/intern-pool.hpp"
+
+#include "../io/cxxstdio.hpp"
+
+#include "../mmo/cxxstdio_enums.hpp"
+
+#include "battle.hpp"
+#include "battle_conf.hpp"
+#include "globals.hpp"
+#include "map.hpp"
+#include "npc.hpp"
+#include "pc.hpp"
+#include "script-fun.hpp"
+#include "script-parse-internal.hpp"
+#include "script-persist.hpp"
+#include "script-startup-internal.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+constexpr bool DEBUG_RUN = false;
+
+static
+struct ScriptConfigRun
+{
+ static const
+ int check_cmdcount = 8192;
+ static const
+ int check_gotocount = 512;
+} script_config;
+
+
+/*==========================================
+ * ridからsdへの解決
+ *------------------------------------------
+ */
+dumb_ptr<map_session_data> script_rid2sd(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
+ if (!sd)
+ {
+ PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt);
+ }
+ return sd;
+}
+
+/*==========================================
+ * 変数の読み取り
+ *------------------------------------------
+ */
+void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
+{
+ MATCH_BEGIN (*data)
+ {
+ MATCH_CASE (const ScriptDataParam&, u)
+ {
+ if (sd == nullptr)
+ PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp());
+ int numi = 0;
+ if (sd)
+ numi = pc_readparam(sd, u.reg.sp());
+ *data = ScriptDataInt{numi};
+ }
+ MATCH_CASE (const ScriptDataVariable&, u)
+ {
+ ZString name_ = variable_names.outtern(u.reg.base());
+ VarName name = stringish<VarName>(name_);
+ char prefix = name.front();
+ char postfix = name.back();
+
+ if (prefix != '$')
+ {
+ if (sd == nullptr)
+ PRINTF("get_val error name?:%s\n"_fmt, name);
+ }
+ if (postfix == '$')
+ {
+ RString str;
+ if (prefix == '@')
+ {
+ if (sd)
+ str = pc_readregstr(sd, u.reg);
+ }
+ else if (prefix == '$')
+ {
+ Option<P<RString>> s_ = mapregstr_db.search(u.reg);
+ OMATCH_BEGIN_SOME (s, s_)
+ {
+ str = *s;
+ }
+ OMATCH_END ();
+ }
+ else
+ {
+ PRINTF("script: get_val: illegal scope string variable.\n"_fmt);
+ str = "!!ERROR!!"_s;
+ }
+ *data = ScriptDataStr{str};
+ }
+ else
+ {
+ int numi = 0;
+ if (prefix == '@')
+ {
+ if (sd)
+ numi = pc_readreg(sd, u.reg);
+ }
+ else if (prefix == '$')
+ {
+ numi = mapreg_db.get(u.reg);
+ }
+ else if (prefix == '#')
+ {
+ if (name[1] == '#')
+ {
+ if (sd)
+ numi = pc_readaccountreg2(sd, name);
+ }
+ else
+ {
+ if (sd)
+ numi = pc_readaccountreg(sd, name);
+ }
+ }
+ else
+ {
+ if (sd)
+ numi = pc_readglobalreg(sd, name);
+ }
+ *data = ScriptDataInt{numi};
+ }
+ }
+ }
+ MATCH_END ();
+}
+
+void get_val(ScriptState *st, struct script_data *data)
+{
+ dumb_ptr<map_session_data> sd = st->rid ? map_id2sd(st->rid) : nullptr;
+ get_val(sd, data);
+}
+
+/*==========================================
+ * 変数の読み取り2
+ *------------------------------------------
+ */
+struct script_data get_val2(ScriptState *st, SIR reg)
+{
+ struct script_data dat = ScriptDataVariable{reg};
+ get_val(st, &dat);
+ return dat;
+}
+
+/*==========================================
+ * 変数設定用
+ *------------------------------------------
+ */
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd)
+{
+ if (type == VariableCode::PARAM)
+ {
+ int val = vd.get_if<ScriptDataInt>()->numi;
+ pc_setparam(sd, reg.sp(), val);
+ return;
+ }
+ assert (type == VariableCode::VARIABLE);
+
+ ZString name_ = variable_names.outtern(reg.base());
+ VarName name = stringish<VarName>(name_);
+ char prefix = name.front();
+ char postfix = name.back();
+
+ if (postfix == '$')
+ {
+ RString str = vd.get_if<ScriptDataStr>()->str;
+ if (prefix == '@')
+ {
+ pc_setregstr(sd, reg, str);
+ }
+ else if (prefix == '$')
+ {
+ mapreg_setregstr(reg, str);
+ }
+ else
+ {
+ PRINTF("script: set_reg: illegal scope string variable !"_fmt);
+ }
+ }
+ else
+ {
+ int val = vd.get_if<ScriptDataInt>()->numi;
+ if (prefix == '@')
+ {
+ pc_setreg(sd, reg, val);
+ }
+ else if (prefix == '$')
+ {
+ mapreg_setreg(reg, val);
+ }
+ else if (prefix == '#')
+ {
+ if (name[1] == '#')
+ pc_setaccountreg2(sd, name, val);
+ else
+ pc_setaccountreg(sd, name, val);
+ }
+ else
+ {
+ pc_setglobalreg(sd, name, val);
+ }
+ }
+}
+
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id)
+{
+ struct script_data vd = ScriptDataInt{id};
+ set_reg(sd, type, reg, vd);
+}
+
+void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd)
+{
+ struct script_data vd = ScriptDataStr{zd};
+ set_reg(sd, type, reg, vd);
+}
+
+/*==========================================
+ * 文字列への変換
+ *------------------------------------------
+ */
+RString conv_str(ScriptState *st, struct script_data *data)
+{
+ get_val(st, data);
+ assert (!data->is<ScriptDataRetInfo>());
+ if (auto *u = data->get_if<ScriptDataInt>())
+ {
+ AString buf = STRPRINTF("%d"_fmt, u->numi);
+ *data = ScriptDataStr{buf};
+ }
+ return data->get_if<ScriptDataStr>()->str;
+}
+
+/*==========================================
+ * 数値へ変換
+ *------------------------------------------
+ */
+int conv_num(ScriptState *st, struct script_data *data)
+{
+ int rv = 0;
+ get_val(st, data);
+ assert (!data->is<ScriptDataRetInfo>());
+ MATCH_BEGIN (*data)
+ {
+ MATCH_DEFAULT ()
+ {
+ abort();
+ }
+ MATCH_CASE (const ScriptDataStr&, u)
+ {
+ RString p = u.str;
+ rv = atoi(p.c_str());
+ }
+ MATCH_CASE (const ScriptDataInt&, u)
+ {
+ return u.numi;
+ }
+ MATCH_CASE (const ScriptDataPos&, u)
+ {
+ return u.numi;
+ }
+ }
+ MATCH_END ()
+ *data = ScriptDataInt{rv};
+ return rv;
+}
+
+Borrowed<const ScriptBuffer> conv_script(ScriptState *st, struct script_data *data)
+{
+ get_val(st, data);
+ return data->get_if<ScriptDataRetInfo>()->script;
+}
+
+void push_copy(struct script_stack *stack, int pos_)
+{
+ script_data csd = stack->stack_datav[pos_];
+ stack->stack_datav.push_back(csd);
+}
+
+void pop_stack(struct script_stack *stack, int start, int end)
+{
+ auto it = stack->stack_datav.begin();
+ stack->stack_datav.erase(it + start, it + end);
+}
+
+//
+// 実行部main
+//
+/*==========================================
+ * コマンドの読み取り
+ *------------------------------------------
+ */
+static
+ByteCode get_com(ScriptPointer *script)
+{
+ if (static_cast<uint8_t>(script->peek()) >= 0x80)
+ {
+ // synthetic! Does not advance pos yet.
+ return ByteCode::INT;
+ }
+ return script->pop();
+}
+
+/*==========================================
+ * 数値の所得
+ *------------------------------------------
+ */
+static
+int get_num(ScriptPointer *scr)
+{
+ int i = 0;
+ int j = 0;
+ uint8_t val;
+ do
+ {
+ val = static_cast<uint8_t>(scr->pop());
+ i += (val & 0x7f) << j;
+ j += 6;
+ }
+ while (val >= 0xc0);
+ return i;
+}
+
+/*==========================================
+ * スタックから値を取り出す
+ *------------------------------------------
+ */
+static
+int pop_val(ScriptState *st)
+{
+ if (st->stack->stack_datav.empty())
+ return 0;
+ script_data& back = st->stack->stack_datav.back();
+ get_val(st, &back);
+ int rv = 0;
+ if (auto *u = back.get_if<ScriptDataInt>())
+ rv = u->numi;
+ st->stack->stack_datav.pop_back();
+ return rv;
+}
+
+static
+bool isstr(struct script_data& c)
+{
+ return c.is<ScriptDataStr>();
+}
+
+/*==========================================
+ * 加算演算子
+ *------------------------------------------
+ */
+static
+void op_add(ScriptState *st)
+{
+ get_val(st, &st->stack->stack_datav.back());
+ script_data back = st->stack->stack_datav.back();
+ st->stack->stack_datav.pop_back();
+
+ script_data& back1 = st->stack->stack_datav.back();
+ get_val(st, &back1);
+
+ if (!(isstr(back) || isstr(back1)))
+ {
+ back1.get_if<ScriptDataInt>()->numi += back.get_if<ScriptDataInt>()->numi;
+ }
+ else
+ {
+ RString sb = conv_str(st, &back);
+ RString sb1 = conv_str(st, &back1);
+ MString buf;
+ buf += sb1;
+ buf += sb;
+ back1 = ScriptDataStr{.str= AString(buf)};
+ }
+}
+
+/*==========================================
+ * 二項演算子(文字列)
+ *------------------------------------------
+ */
+static
+void op_2str(ScriptState *st, ByteCode op, ZString s1, ZString s2)
+{
+ int a = 0;
+
+ switch (op)
+ {
+ case ByteCode::EQ:
+ a = s1 == s2;
+ break;
+ case ByteCode::NE:
+ a = s1 != s2;
+ break;
+ case ByteCode::GT:
+ a = s1 > s2;
+ break;
+ case ByteCode::GE:
+ a = s1 >= s2;
+ break;
+ case ByteCode::LT:
+ a = s1 < s2;
+ break;
+ case ByteCode::LE:
+ a = s1 <= s2;
+ break;
+ default:
+ PRINTF("illegal string operater\n"_fmt);
+ break;
+ }
+
+ push_int<ScriptDataInt>(st->stack, a);
+}
+
+/*==========================================
+ * 二項演算子(数値)
+ *------------------------------------------
+ */
+static
+void op_2num(ScriptState *st, ByteCode op, int i1, int i2)
+{
+ switch (op)
+ {
+ case ByteCode::SUB:
+ i1 -= i2;
+ break;
+ case ByteCode::MUL:
+ i1 *= i2;
+ break;
+ case ByteCode::DIV:
+ i1 /= i2;
+ break;
+ case ByteCode::MOD:
+ i1 %= i2;
+ break;
+ case ByteCode::AND:
+ i1 &= i2;
+ break;
+ case ByteCode::OR:
+ i1 |= i2;
+ break;
+ case ByteCode::XOR:
+ i1 ^= i2;
+ break;
+ case ByteCode::LAND:
+ i1 = i1 && i2;
+ break;
+ case ByteCode::LOR:
+ i1 = i1 || i2;
+ break;
+ case ByteCode::EQ:
+ i1 = i1 == i2;
+ break;
+ case ByteCode::NE:
+ i1 = i1 != i2;
+ break;
+ case ByteCode::GT:
+ i1 = i1 > i2;
+ break;
+ case ByteCode::GE:
+ i1 = i1 >= i2;
+ break;
+ case ByteCode::LT:
+ i1 = i1 < i2;
+ break;
+ case ByteCode::LE:
+ i1 = i1 <= i2;
+ break;
+ case ByteCode::R_SHIFT:
+ i1 = i1 >> i2;
+ break;
+ case ByteCode::L_SHIFT:
+ i1 = i1 << i2;
+ break;
+ }
+ push_int<ScriptDataInt>(st->stack, i1);
+}
+
+/*==========================================
+ * 二項演算子
+ *------------------------------------------
+ */
+static
+void op_2(ScriptState *st, ByteCode op)
+{
+ // pop_val has unfortunate implications here
+ script_data d2 = st->stack->stack_datav.back();
+ st->stack->stack_datav.pop_back();
+ get_val(st, &d2);
+ script_data d1 = st->stack->stack_datav.back();
+ st->stack->stack_datav.pop_back();
+ get_val(st, &d1);
+
+ if (isstr(d1) && isstr(d2))
+ {
+ // ss => op_2str
+ op_2str(st, op, d1.get_if<ScriptDataStr>()->str, d2.get_if<ScriptDataStr>()->str);
+ }
+ else if (!(isstr(d1) || isstr(d2)))
+ {
+ // ii => op_2num
+ op_2num(st, op, d1.get_if<ScriptDataInt>()->numi, d2.get_if<ScriptDataInt>()->numi);
+ }
+ else
+ {
+ // si,is => error
+ PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt);
+ push_int<ScriptDataInt>(st->stack, 0);
+ }
+}
+
+/*==========================================
+ * 単項演算子
+ *------------------------------------------
+ */
+static
+void op_1num(ScriptState *st, ByteCode op)
+{
+ int i1;
+ i1 = pop_val(st);
+ switch (op)
+ {
+ case ByteCode::NEG:
+ i1 = -i1;
+ break;
+ case ByteCode::NOT:
+ i1 = ~i1;
+ break;
+ case ByteCode::LNOT:
+ i1 = !i1;
+ break;
+ }
+ push_int<ScriptDataInt>(st->stack, i1);
+}
+
+/*==========================================
+ * 関数の実行
+ *------------------------------------------
+ */
+void run_func(ScriptState *st)
+{
+ size_t end_sp = st->stack->stack_datav.size();
+ size_t start_sp = end_sp - 1;
+ while (!st->stack->stack_datav[start_sp].is<ScriptDataArg>())
+ {
+ start_sp--;
+ if (start_sp == 0)
+ {
+ if (battle_config.error_log)
+ PRINTF("function not found\n"_fmt);
+ st->state = ScriptEndState::END;
+ return;
+ }
+ }
+ // the func is before the arg
+ start_sp--;
+ st->start = start_sp;
+ st->end = end_sp;
+
+ if (!st->stack->stack_datav[st->start].is<ScriptDataFuncRef>())
+ {
+ PRINTF("run_func: not function and command! \n"_fmt);
+ st->state = ScriptEndState::END;
+ return;
+ }
+ size_t func = st->stack->stack_datav[st->start].get_if<ScriptDataFuncRef>()->numi;
+
+ if (DEBUG_RUN && battle_config.etc_log)
+ {
+ PRINTF("run_func : %s\n"_fmt,
+ builtin_functions[func].name);
+ PRINTF("stack dump :"_fmt);
+ for (script_data& d : st->stack->stack_datav)
+ {
+ MATCH_BEGIN (d)
+ {
+ MATCH_CASE (const ScriptDataInt&, u)
+ {
+ PRINTF(" int(%d)"_fmt, u.numi);
+ }
+ MATCH_CASE (const ScriptDataRetInfo&, u)
+ {
+ PRINTF(" retinfo(%p)"_fmt, static_cast<const void *>(&*u.script));
+ }
+ MATCH_CASE (const ScriptDataParam&, u)
+ {
+ PRINTF(" param(%d)"_fmt, u.reg.sp());
+ }
+ MATCH_CASE (const ScriptDataVariable&, u)
+ {
+ PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index());
+ }
+ MATCH_CASE (const ScriptDataArg&, u)
+ {
+ (void)u;
+ PRINTF(" arg"_fmt);
+ }
+ MATCH_CASE (const ScriptDataPos&, u)
+ {
+ (void)u;
+ PRINTF(" pos(%d)"_fmt, u.numi);
+ }
+ MATCH_CASE (const ScriptDataStr&, u)
+ {
+ (void)u;
+ PRINTF(" str(%s)"_fmt, u.str);
+ }
+ MATCH_CASE (const ScriptDataFuncRef&, u)
+ {
+ (void)u;
+ PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name);
+ }
+ }
+ MATCH_END ();
+ }
+ PRINTF("\n"_fmt);
+ }
+ builtin_functions[func].func(st);
+
+ pop_stack(st->stack, start_sp, end_sp);
+
+ if (st->state == ScriptEndState::RETFUNC)
+ {
+ // ユーザー定義関数からの復帰
+ int olddefsp = st->defsp;
+
+ pop_stack(st->stack, st->defsp, start_sp); // 復帰に邪魔なスタック削除
+ if (st->defsp < 4
+ || !st->stack->stack_datav[st->defsp - 1].is<ScriptDataRetInfo>())
+ {
+ PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt);
+ st->state = ScriptEndState::END;
+ return;
+ }
+ assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet
+ st->scriptp.code = Some(conv_script(st, &st->stack->stack_datav[olddefsp - 1])); // スクリプトを復元
+ st->scriptp.pos = conv_num(st, &st->stack->stack_datav[olddefsp - 2]); // スクリプト位置の復元
+ st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元
+ // Number of arguments.
+ int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得
+ assert (i == 0);
+
+ pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除
+
+ st->state = ScriptEndState::GOTO;
+ }
+}
+
+/*==========================================
+ * スクリプトの実行メイン部分
+ *------------------------------------------
+ */
+static
+void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript)
+{
+ int cmdcount = script_config.check_cmdcount;
+ int gotocount = script_config.check_gotocount;
+ struct script_stack *stack = st->stack;
+
+ st->defsp = stack->stack_datav.size();
+
+ int rerun_pos = st->scriptp.pos;
+ st->state = ScriptEndState::ZERO;
+ while (st->state == ScriptEndState::ZERO)
+ {
+ switch (ByteCode c = get_com(&st->scriptp))
+ {
+ case ByteCode::EOL:
+ if (stack->stack_datav.size() != st->defsp)
+ {
+ if (true)
+ PRINTF("stack.sp (%zu) != default (%d)\n"_fmt,
+ stack->stack_datav.size(),
+ st->defsp);
+ abort();
+ }
+ rerun_pos = st->scriptp.pos;
+ break;
+ case ByteCode::INT:
+ // synthesized!
+ push_int<ScriptDataInt>(stack, get_num(&st->scriptp));
+ break;
+
+ case ByteCode::POS:
+ case ByteCode::VARIABLE:
+ case ByteCode::FUNC_REF:
+ case ByteCode::PARAM:
+ // Note that these 3 have *very* different meanings,
+ // despite being encoded similarly.
+ {
+ int arg = 0;
+ arg |= static_cast<uint8_t>(st->scriptp.pop()) << 0;
+ arg |= static_cast<uint8_t>(st->scriptp.pop()) << 8;
+ arg |= static_cast<uint8_t>(st->scriptp.pop()) << 16;
+ switch(c)
+ {
+ case ByteCode::POS:
+ push_int<ScriptDataPos>(stack, arg);
+ break;
+ case ByteCode::VARIABLE:
+ push_reg<ScriptDataVariable>(stack, SIR::from(arg));
+ break;
+ case ByteCode::FUNC_REF:
+ push_int<ScriptDataFuncRef>(stack, arg);
+ break;
+ case ByteCode::PARAM:
+ SP arg_sp = static_cast<SP>(arg);
+ push_reg<ScriptDataParam>(stack, SIR::from(arg_sp));
+ break;
+ }
+ }
+ break;
+ case ByteCode::ARG:
+ push_int<ScriptDataArg>(stack, 0);
+ break;
+ case ByteCode::STR:
+ push_str<ScriptDataStr>(stack, st->scriptp.pops());
+ break;
+ case ByteCode::FUNC:
+ run_func(st);
+ if (st->state == ScriptEndState::GOTO)
+ {
+ rerun_pos = st->scriptp.pos;
+ st->state = ScriptEndState::ZERO;
+ if (st->freeloop != 1 && gotocount > 0 && (--gotocount) <= 0)
+ {
+ PRINTF("run_script: infinity loop !\n"_fmt);
+ st->state = ScriptEndState::END;
+ }
+ }
+ break;
+
+ case ByteCode::ADD:
+ op_add(st);
+ break;
+
+ case ByteCode::SUB:
+ case ByteCode::MUL:
+ case ByteCode::DIV:
+ case ByteCode::MOD:
+ case ByteCode::EQ:
+ case ByteCode::NE:
+ case ByteCode::GT:
+ case ByteCode::GE:
+ case ByteCode::LT:
+ case ByteCode::LE:
+ case ByteCode::AND:
+ case ByteCode::OR:
+ case ByteCode::XOR:
+ case ByteCode::LAND:
+ case ByteCode::LOR:
+ case ByteCode::R_SHIFT:
+ case ByteCode::L_SHIFT:
+ op_2(st, c);
+ break;
+
+ case ByteCode::NEG:
+ case ByteCode::NOT:
+ case ByteCode::LNOT:
+ op_1num(st, c);
+ break;
+
+ case ByteCode::NOP:
+ st->state = ScriptEndState::END;
+ break;
+
+ default:
+ if (battle_config.error_log)
+ PRINTF("unknown command : %d @ %zu\n"_fmt,
+ c, st->scriptp.pos);
+ st->state = ScriptEndState::END;
+ break;
+ }
+ if (st->freeloop != 1 && cmdcount > 0 && (--cmdcount) <= 0)
+ {
+ PRINTF("run_script: infinity loop !\n"_fmt);
+ st->state = ScriptEndState::END;
+ }
+ }
+ switch (st->state)
+ {
+ case ScriptEndState::STOP:
+ break;
+ case ScriptEndState::END:
+ {
+ dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
+ st->scriptp.code = None;
+ st->scriptp.pos = -1;
+ if (sd && sd->npc_id == st->oid)
+ npc_event_dequeue(sd);
+ }
+ break;
+ case ScriptEndState::RERUNLINE:
+ st->scriptp.pos = rerun_pos;
+ break;
+ }
+
+ if (st->state != ScriptEndState::END)
+ {
+ // 再開するためにスタック情報を保存
+ dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
+ if (sd)
+ {
+ sd->npc_stackbuf = stack->stack_datav;
+ sd->npc_script = st->scriptp.code;
+ // sd->npc_pos is set later ... ???
+ sd->npc_scriptroot = Some(rootscript);
+ }
+ }
+}
+
+/*==========================================
+ * スクリプトの実行
+ *------------------------------------------
+ */
+int run_script(ScriptPointer sp, BlockId rid, BlockId oid)
+{
+ return run_script_l(sp, rid, oid, nullptr);
+}
+
+int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid,
+ Slice<argrec_t> args)
+{
+ struct script_stack stack;
+ ScriptState st;
+ dumb_ptr<map_session_data> sd = map_id2sd(rid);
+ P<const ScriptBuffer> rootscript = TRY_UNWRAP(sp.code, return -1);
+ int i;
+ if (sp.pos >> 24)
+ return -1;
+
+ if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == Some(rootscript))
+ {
+ // 前回のスタックを復帰
+ sp.code = sd->npc_script;
+ stack.stack_datav = std::move(sd->npc_stackbuf);
+ }
+ st.stack = &stack;
+ st.scriptp = sp;
+ st.rid = rid;
+ st.oid = oid;
+ for (i = 0; i < args.size(); i++)
+ {
+ if (args[i].name.back() == '$')
+ pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s);
+ else
+ pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i);
+ }
+ run_script_main(&st, rootscript);
+
+ stack.stack_datav.clear();
+ return st.scriptp.pos;
+}
+
+void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val)
+{
+ size_t k = variable_names.intern(var);
+ SIR reg = SIR::from(k, e);
+ set_reg(sd, VariableCode::VARIABLE, reg, val);
+}
+void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val)
+{
+ size_t k = variable_names.intern(var);
+ SIR reg = SIR::from(k, e);
+ set_reg(sd, VariableCode::VARIABLE, reg, val);
+}
+int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e)
+{
+ size_t k = variable_names.intern(var);
+ SIR reg = SIR::from(k, e);
+ struct script_data dat = ScriptDataVariable{.reg= reg};
+ get_val(sd, &dat);
+ if (auto *u = dat.get_if<ScriptDataInt>())
+ return u->numi;
+ PRINTF("Warning: you lied about the type and I'm too lazy to fix it!"_fmt);
+ return 0;
+}
+ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e)
+{
+ size_t k = variable_names.intern(var);
+ SIR reg = SIR::from(k, e);
+ struct script_data dat = ScriptDataVariable{.reg= reg};
+ get_val(sd, &dat);
+ if (auto *u = dat.get_if<ScriptDataStr>())
+ // this is almost certainly a memory leak after CONSTSTR removal
+ return u->str;
+ PRINTF("Warning: you lied about the type and I can't fix it!"_fmt);
+ return ZString();
+}
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/script-call.hpp b/src/map/script-call.hpp
new file mode 100644
index 0000000..d494326
--- /dev/null
+++ b/src/map/script-call.hpp
@@ -0,0 +1,67 @@
+#pragma once
+// script-call.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "script-call.t.hpp"
+
+#include "fwd.hpp"
+
+#include "../compat/borrow.hpp"
+
+#include "script-buffer.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+enum class ByteCode : uint8_t;
+
+// implemented in script-parse.cpp because reasons
+struct ScriptPointer
+{
+ Option<Borrowed<const ScriptBuffer>> code;
+ size_t pos;
+
+ ScriptPointer()
+ : code(None)
+ , pos()
+ {}
+ ScriptPointer(Borrowed<const ScriptBuffer> c, size_t p)
+ : code(Some(c))
+ , pos(p)
+ {}
+
+ ByteCode peek() const;
+ ByteCode pop();
+ ZString pops();
+};
+
+int run_script_l(ScriptPointer, BlockId, BlockId, Slice<argrec_t> args);
+int run_script(ScriptPointer, BlockId, BlockId);
+
+void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val);
+void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val);
+
+int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e);
+ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e);
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/script-call.t.hpp b/src/map/script-call.t.hpp
new file mode 100644
index 0000000..5ef7de6
--- /dev/null
+++ b/src/map/script-call.t.hpp
@@ -0,0 +1,48 @@
+#pragma once
+// script-call.t.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../strings/zstring.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+struct argrec_t
+{
+ ZString name;
+ union _aru
+ {
+ int i;
+ ZString s;
+
+ _aru(int n) : i(n) {}
+ _aru(ZString z) : s(z) {}
+ } v;
+
+ argrec_t(ZString n, int i) : name(n), v(i) {}
+ argrec_t(ZString n, ZString z) : name(n), v(z) {}
+};
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
new file mode 100644
index 0000000..3291cfc
--- /dev/null
+++ b/src/map/script-fun.cpp
@@ -0,0 +1,3164 @@
+#include "script-fun.hpp"
+// script-fun.cpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011 Chuck Miller
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2013 wushin
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../compat/fun.hpp"
+
+#include "../generic/db.hpp"
+#include "../generic/dumb_ptr.hpp"
+#include "../generic/intern-pool.hpp"
+#include "../generic/random.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
+
+#include "../net/timer.hpp"
+
+#include "../proto2/net-HumanTimeDiff.hpp"
+
+#include "../high/core.hpp"
+#include "../high/extract_mmo.hpp"
+
+#include "atcommand.hpp"
+#include "battle.hpp"
+#include "battle_conf.hpp"
+#include "chrif.hpp"
+#include "clif.hpp"
+#include "globals.hpp"
+#include "intif.hpp"
+#include "itemdb.hpp"
+#include "magic-interpreter-base.hpp"
+#include "map.hpp"
+#include "mob.hpp"
+#include "npc.hpp"
+#include "party.hpp"
+#include "pc.hpp"
+#include "script-call-internal.hpp"
+#include "script-parse-internal.hpp"
+#include "script-persist.hpp"
+#include "skill.hpp"
+#include "storage.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+static
+Array<LString, 11> pos_str //=
+{{
+ "Head"_s,
+ "Body"_s,
+ "Left hand"_s,
+ "Right hand"_s,
+ "Robe"_s,
+ "Shoes"_s,
+ "Accessory 1"_s,
+ "Accessory 2"_s,
+ "Head 2"_s,
+ "Head 3"_s,
+ "Not Equipped"_s,
+}};
+
+#define AARG(n) (st->stack->stack_datav[st->start + 2 + (n)])
+#define HARG(n) (st->end > st->start + 2 + (n))
+
+//
+// 埋め込み関数
+//
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_mes(ScriptState *st)
+{
+ RString mes = conv_str(st, &AARG(0));
+ clif_scriptmes(script_rid2sd(st), st->oid, mes);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_goto(ScriptState *st)
+{
+ if (!AARG(0).is<ScriptDataPos>())
+ {
+ PRINTF("script: goto: that's not a label!\n"_fmt);
+ st->state = ScriptEndState::END;
+ return;
+ }
+
+ st->scriptp.pos = conv_num(st, &AARG(0));
+ st->state = ScriptEndState::GOTO;
+}
+
+/*==========================================
+ * ユーザー定義関数の呼び出し
+ *------------------------------------------
+ */
+static
+void builtin_callfunc(ScriptState *st)
+{
+ RString str = conv_str(st, &AARG(0));
+ Option<P<const ScriptBuffer>> scr_ = userfunc_db.get(str);
+
+ OMATCH_BEGIN (scr_)
+ {
+ OMATCH_CASE_SOME (scr)
+ {
+ int j = 0;
+ assert (st->start + 3 == st->end);
+#if 0
+ for (int i = st->start + 3; i < st->end; i++, j++)
+ push_copy(st->stack, i);
+#endif
+
+ push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ
+ push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ
+ push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ
+ push_script<ScriptDataRetInfo>(st->stack, TRY_UNWRAP(st->scriptp.code, abort())); // 現在のスクリプトをプッシュ
+
+ st->scriptp = ScriptPointer(scr, 0);
+ st->defsp = st->start + 4 + j;
+ st->state = ScriptEndState::GOTO;
+ }
+ OMATCH_CASE_NONE ()
+ {
+ PRINTF("script: callfunc: function not found! [%s]\n"_fmt, str);
+ st->state = ScriptEndState::END;
+ }
+ }
+ OMATCH_END ();
+}
+
+/*==========================================
+ * サブルーティンの呼び出し
+ *------------------------------------------
+ */
+static
+void builtin_callsub(ScriptState *st)
+{
+ int pos_ = conv_num(st, &AARG(0));
+ int j = 0;
+ assert (st->start + 3 == st->end);
+#if 0
+ for (int i = st->start + 3; i < st->end; i++, j++)
+ push_copy(st->stack, i);
+#endif
+
+ push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ
+ push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ
+ push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ
+ push_script<ScriptDataRetInfo>(st->stack, TRY_UNWRAP(st->scriptp.code, abort())); // 現在のスクリプトをプッシュ
+
+ st->scriptp.pos = pos_;
+ st->defsp = st->start + 4 + j;
+ st->state = ScriptEndState::GOTO;
+}
+
+/*==========================================
+ * サブルーチン/ユーザー定義関数の終了
+ *------------------------------------------
+ */
+static
+void builtin_return(ScriptState *st)
+{
+#if 0
+ if (HARG(0))
+ { // 戻り値有り
+ push_copy(st->stack, st->start + 2);
+ }
+#endif
+ st->state = ScriptEndState::RETFUNC;
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_next(ScriptState *st)
+{
+ st->state = ScriptEndState::STOP;
+ clif_scriptnext(script_rid2sd(st), st->oid);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_close(ScriptState *st)
+{
+ st->state = ScriptEndState::END;
+ clif_scriptclose(script_rid2sd(st), st->oid);
+}
+
+static
+void builtin_close2(ScriptState *st)
+{
+ st->state = ScriptEndState::STOP;
+ clif_scriptclose(script_rid2sd(st), st->oid);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_menu(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ if (sd->state.menu_or_input == 0)
+ {
+ // First half: show menu.
+ st->state = ScriptEndState::RERUNLINE;
+ sd->state.menu_or_input = 1;
+
+ MString buf;
+ for (int i = 0; i < (st->end - (st->start + 2)) / 2; i++)
+ {
+ RString choice_str = conv_str(st, &AARG(i * 2 + 0));
+ if (!choice_str)
+ break;
+ buf += choice_str;
+ buf += ':';
+ }
+
+ clif_scriptmenu(script_rid2sd(st), st->oid, AString(buf));
+ }
+ else
+ {
+ // Rerun: item is chosen from menu.
+ if (sd->npc_menu == 0xff)
+ {
+ // cancel
+ sd->state.menu_or_input = 0;
+ st->state = ScriptEndState::END;
+ return;
+ }
+
+ // Actually jump to the label.
+ // Logic change: menu_choices is the *total* number of labels,
+ // not just the displayed number that ends with the "".
+ // (Would it be better to pop the stack before rerunning?)
+ int menu_choices = (st->end - (st->start + 2)) / 2;
+ pc_setreg(sd, SIR::from(variable_names.intern("@menu"_s)), sd->npc_menu);
+ sd->state.menu_or_input = 0;
+ if (sd->npc_menu > 0 && sd->npc_menu <= menu_choices)
+ {
+ int arg_index = (sd->npc_menu - 1) * 2 + 1;
+ if (!AARG(arg_index).is<ScriptDataPos>())
+ {
+ st->state = ScriptEndState::END;
+ return;
+ }
+ st->scriptp.pos = AARG(arg_index).get_if<ScriptDataPos>()->numi;
+ st->state = ScriptEndState::GOTO;
+ }
+ }
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_rand(ScriptState *st)
+{
+ if (HARG(1))
+ {
+ int min = conv_num(st, &AARG(0));
+ int max = conv_num(st, &AARG(1));
+ if (min > max)
+ std::swap(max, min);
+ push_int<ScriptDataInt>(st->stack, random_::in(min, max));
+ }
+ else
+ {
+ int range = conv_num(st, &AARG(0));
+ push_int<ScriptDataInt>(st->stack, range <= 0 ? 0 : random_::to(range));
+ }
+}
+
+/*==========================================
+ * Check whether the PC is at the specified location
+ *------------------------------------------
+ */
+static
+void builtin_isat(ScriptState *st)
+{
+ int x, y;
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x = conv_num(st, &AARG(1));
+ y = conv_num(st, &AARG(2));
+
+ if (!sd)
+ return;
+
+ push_int<ScriptDataInt>(st->stack,
+ (x == sd->bl_x) && (y == sd->bl_y)
+ && (str == sd->bl_m->name_));
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_warp(ScriptState *st)
+{
+ int x, y;
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x = conv_num(st, &AARG(1));
+ y = conv_num(st, &AARG(2));
+ pc_setpos(sd, str, x, y, BeingRemoveWhy::GONE);
+}
+
+/*==========================================
+ * エリア指定ワープ
+ *------------------------------------------
+ */
+static
+void builtin_areawarp_sub(dumb_ptr<block_list> bl, MapName mapname, int x, int y)
+{
+ dumb_ptr<map_session_data> sd = bl->is_player();
+ pc_setpos(sd, mapname, x, y, BeingRemoveWhy::GONE);
+}
+
+static
+void builtin_areawarp(ScriptState *st)
+{
+ int x, y;
+ int x0, y0, x1, y1;
+
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x0 = conv_num(st, &AARG(1));
+ y0 = conv_num(st, &AARG(2));
+ x1 = conv_num(st, &AARG(3));
+ y1 = conv_num(st, &AARG(4));
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(5))));
+ x = conv_num(st, &AARG(6));
+ y = conv_num(st, &AARG(7));
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+
+ map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y),
+ m,
+ x0, y0,
+ x1, y1,
+ BL::PC);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_heal(ScriptState *st)
+{
+ int hp, sp;
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ hp = conv_num(st, &AARG(0));
+ sp = conv_num(st, &AARG(1));
+
+ if(sd != nullptr && (sd->status.hp < 1 && hp > 0)){
+ pc_setstand(sd);
+ if (battle_config.player_invincible_time > interval_t::zero())
+ pc_setinvincibletimer(sd, battle_config.player_invincible_time);
+ clif_resurrection(sd, 1);
+ }
+
+ if(HARG(2) && bool(conv_num(st, &AARG(2))) && hp > 0)
+ pc_itemheal(sd, hp, sp);
+ else
+ pc_heal(sd, hp, sp);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_input(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = nullptr;
+ script_data& scrd = AARG(0);
+ assert (scrd.is<ScriptDataVariable>());
+
+ SIR reg = scrd.get_if<ScriptDataVariable>()->reg;
+ ZString name = variable_names.outtern(reg.base());
+// char prefix = name.front();
+ char postfix = name.back();
+
+ sd = script_rid2sd(st);
+ if (sd->state.menu_or_input)
+ {
+ // Second time (rerun)
+ sd->state.menu_or_input = 0;
+ if (postfix == '$')
+ {
+ set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_str);
+ }
+ else
+ {
+ //commented by Lupus (check Value Number Input fix in clif.c)
+ //** Fix by fritz :X keeps people from abusing old input bugs
+ // wtf?
+ if (sd->npc_amount < 0) //** If input amount is less then 0
+ {
+ clif_tradecancelled(sd); // added "Deal has been cancelled" message by Valaris
+ builtin_close(st); //** close
+ }
+
+ set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_amount);
+ }
+ }
+ else
+ {
+ // First time - send prompt to client, then wait
+ st->state = ScriptEndState::RERUNLINE;
+ if (postfix == '$')
+ clif_scriptinputstr(sd, st->oid);
+ else
+ clif_scriptinput(sd, st->oid);
+ sd->state.menu_or_input = 1;
+ }
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_if (ScriptState *st)
+{
+ int sel, i;
+
+ sel = conv_num(st, &AARG(0));
+ if (!sel)
+ return;
+
+ // 関数名をコピー
+ push_copy(st->stack, st->start + 3);
+ // 間に引数マーカを入れて
+ push_int<ScriptDataArg>(st->stack, 0);
+ // 残りの引数をコピー
+ for (i = st->start + 4; i < st->end; i++)
+ {
+ push_copy(st->stack, i);
+ }
+ run_func(st);
+}
+
+/*==========================================
+ * 変数設定
+ *------------------------------------------
+ */
+static
+void builtin_set(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = nullptr;
+ if (auto *u = AARG(0).get_if<ScriptDataParam>())
+ {
+ SIR reg = u->reg;
+ sd = script_rid2sd(st);
+
+ int val = conv_num(st, &AARG(1));
+ set_reg(sd, VariableCode::PARAM, reg, val);
+ return;
+ }
+
+ SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
+
+ ZString name = variable_names.outtern(reg.base());
+ char prefix = name.front();
+ char postfix = name.back();
+
+ if (prefix != '$')
+ sd = script_rid2sd(st);
+
+ if (postfix == '$')
+ {
+ // 文字列
+ RString str = conv_str(st, &AARG(1));
+ set_reg(sd, VariableCode::VARIABLE, reg, str);
+ }
+ else
+ {
+ // 数値
+ int val = conv_num(st, &AARG(1));
+ set_reg(sd, VariableCode::VARIABLE, reg, val);
+ }
+
+}
+
+/*==========================================
+ * 配列変数設定
+ *------------------------------------------
+ */
+static
+void builtin_setarray(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = nullptr;
+ SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
+ ZString name = variable_names.outtern(reg.base());
+ char prefix = name.front();
+ char postfix = name.back();
+
+ if (prefix != '$' && prefix != '@')
+ {
+ PRINTF("builtin_setarray: illegal scope!\n"_fmt);
+ return;
+ }
+ if (prefix != '$')
+ sd = script_rid2sd(st);
+
+ for (int j = 0, i = 1; i < st->end - st->start - 2 && j < 256; i++, j++)
+ {
+ if (postfix == '$')
+ set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i)));
+ else
+ set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARG(i)));
+ }
+}
+
+/*==========================================
+ * 配列変数クリア
+ *------------------------------------------
+ */
+static
+void builtin_cleararray(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = nullptr;
+ SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
+ ZString name = variable_names.outtern(reg.base());
+ char prefix = name.front();
+ char postfix = name.back();
+ int sz = conv_num(st, &AARG(2));
+
+ if (prefix != '$' && prefix != '@')
+ {
+ PRINTF("builtin_cleararray: illegal scope!\n"_fmt);
+ return;
+ }
+ if (prefix != '$')
+ sd = script_rid2sd(st);
+
+ for (int i = 0; i < sz; i++)
+ {
+ if (postfix == '$')
+ set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1)));
+ else
+ set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARG(1)));
+ }
+
+}
+
+/*==========================================
+ * 配列変数のサイズ所得
+ *------------------------------------------
+ */
+static
+int getarraysize(ScriptState *st, SIR reg)
+{
+ int i = reg.index(), c = i;
+ for (; i < 256; i++)
+ {
+ struct script_data vd = get_val2(st, reg.iplus(i));
+ MATCH_BEGIN (vd)
+ {
+ MATCH_CASE (const ScriptDataStr&, u)
+ {
+ if (u.str[0])
+ c = i;
+ continue;
+ }
+ MATCH_CASE (const ScriptDataInt&, u)
+ {
+ if (u.numi)
+ c = i;
+ continue;
+ }
+ }
+ MATCH_END ();
+ abort();
+ }
+ return c + 1;
+}
+
+static
+void builtin_getarraysize(ScriptState *st)
+{
+ SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
+ ZString name = variable_names.outtern(reg.base());
+ char prefix = name.front();
+
+ if (prefix != '$' && prefix != '@')
+ {
+ PRINTF("builtin_copyarray: illegal scope!\n"_fmt);
+ return;
+ }
+
+ push_int<ScriptDataInt>(st->stack, getarraysize(st, reg));
+}
+
+/*==========================================
+ * 指定要素を表す値(キー)を所得する
+ *------------------------------------------
+ */
+static
+void builtin_getelementofarray(ScriptState *st)
+{
+ if (auto *u = AARG(0).get_if<ScriptDataVariable>())
+ {
+ int i = conv_num(st, &AARG(1));
+ if (i > 255 || i < 0)
+ {
+ PRINTF("script: getelementofarray (operator[]): param2 illegal number: %d\n"_fmt,
+ i);
+ push_int<ScriptDataInt>(st->stack, 0);
+ }
+ else
+ {
+ push_reg<ScriptDataVariable>(st->stack,
+ u->reg.iplus(i));
+ }
+ }
+ else
+ {
+ PRINTF("script: getelementofarray (operator[]): param1 not named!\n"_fmt);
+ push_int<ScriptDataInt>(st->stack, 0);
+ }
+}
+
+static
+void builtin_wgm(ScriptState *st)
+{
+ ZString message = ZString(conv_str(st, &AARG(0)));
+
+ intif_wis_message_to_gm(WISP_SERVER_NAME,
+ battle_config.hack_info_GM_level,
+ STRPRINTF("[GM] %s"_fmt, message));
+}
+
+static
+void builtin_gmlog(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ ZString message = ZString(conv_str(st, &AARG(0)));
+ log_atcommand(sd, STRPRINTF("{SCRIPT} %s"_fmt, message));
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_setlook(ScriptState *st)
+{
+ LOOK type = LOOK(conv_num(st, &AARG(0)));
+ int val = conv_num(st, &AARG(1));
+
+ pc_changelook(script_rid2sd(st), type, val);
+
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_countitem(ScriptState *st)
+{
+ ItemNameId nameid;
+ int count = 0;
+ dumb_ptr<map_session_data> sd;
+
+ struct script_data *data;
+
+ sd = script_rid2sd(st);
+
+ data = &AARG(0);
+ get_val(st, data);
+ if (data->is<ScriptDataStr>())
+ {
+ ZString name = ZString(conv_str(st, data));
+ Option<P<struct item_data>> item_data_ = itemdb_searchname(name);
+ OMATCH_BEGIN_SOME (item_data, item_data_)
+ {
+ nameid = item_data->nameid;
+ }
+ OMATCH_END ();
+ }
+ else
+ nameid = wrap<ItemNameId>(conv_num(st, data));
+
+ if (nameid)
+ {
+ for (IOff0 i : IOff0::iter())
+ {
+ if (sd->status.inventory[i].nameid == nameid)
+ count += sd->status.inventory[i].amount;
+ }
+ }
+ else
+ {
+ if (battle_config.error_log)
+ PRINTF("wrong item ID: countitem (%i)\n"_fmt, nameid);
+ }
+ push_int<ScriptDataInt>(st->stack, count);
+
+}
+
+/*==========================================
+ * 重量チェック
+ *------------------------------------------
+ */
+static
+void builtin_checkweight(ScriptState *st)
+{
+ ItemNameId nameid;
+ int amount;
+ dumb_ptr<map_session_data> sd;
+ struct script_data *data;
+
+ sd = script_rid2sd(st);
+
+ data = &AARG(0);
+ get_val(st, data);
+ if (data->is<ScriptDataStr>())
+ {
+ ZString name = ZString(conv_str(st, data));
+ Option<P<struct item_data>> item_data_ = itemdb_searchname(name);
+ OMATCH_BEGIN_SOME (item_data, item_data_)
+ {
+ nameid = item_data->nameid;
+ }
+ OMATCH_END ();
+ }
+ else
+ nameid = wrap<ItemNameId>(conv_num(st, data));
+
+ amount = conv_num(st, &AARG(1));
+ if (amount <= 0 || !nameid)
+ {
+ //If it gets the wrong item ID or the amount<=0, don't count its weight (assume it's a non-existent item)
+ push_int<ScriptDataInt>(st->stack, 0);
+ return;
+ }
+
+ if (itemdb_weight(nameid) * amount + sd->weight > sd->max_weight)
+ {
+ push_int<ScriptDataInt>(st->stack, 0);
+ }
+ else
+ {
+ push_int<ScriptDataInt>(st->stack, 1);
+ }
+
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_getitem(ScriptState *st)
+{
+ ItemNameId nameid;
+ int amount;
+ dumb_ptr<map_session_data> sd;
+ struct script_data *data;
+
+ sd = script_rid2sd(st);
+
+ data = &AARG(0);
+ get_val(st, data);
+ if (data->is<ScriptDataStr>())
+ {
+ ZString name = ZString(conv_str(st, data));
+ Option<P<struct item_data>> item_data_ = itemdb_searchname(name);
+ OMATCH_BEGIN_SOME (item_data, item_data_)
+ {
+ nameid = item_data->nameid;
+ }
+ OMATCH_END ();
+ }
+ else
+ nameid = wrap<ItemNameId>(conv_num(st, data));
+
+ if ((amount =
+ conv_num(st, &AARG(1))) <= 0)
+ {
+ return; //return if amount <=0, skip the useles iteration
+ }
+
+ if (nameid)
+ {
+ Item item_tmp {};
+ item_tmp.nameid = nameid;
+ if (HARG(3)) //アイテムを指定したIDに渡す
+ sd = map_id2sd(wrap<BlockId>(conv_num(st, &AARG(3))));
+ if (sd == nullptr) //アイテムを渡す相手がいなかったらお帰り
+ return;
+ PickupFail flag;
+ if ((flag = pc_additem(sd, &item_tmp, amount)) != PickupFail::OKAY)
+ {
+ clif_additem(sd, IOff0::from(0), 0, flag);
+ map_addflooritem(&item_tmp, amount,
+ sd->bl_m, sd->bl_x, sd->bl_y,
+ nullptr, nullptr, nullptr);
+ }
+ }
+
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_makeitem(ScriptState *st)
+{
+ ItemNameId nameid;
+ int amount;
+ int x, y;
+ dumb_ptr<map_session_data> sd;
+ struct script_data *data;
+
+ sd = script_rid2sd(st);
+
+ data = &AARG(0);
+ get_val(st, data);
+ if (data->is<ScriptDataStr>())
+ {
+ ZString name = ZString(conv_str(st, data));
+ Option<P<struct item_data>> item_data_ = itemdb_searchname(name);
+ OMATCH_BEGIN_SOME (item_data, item_data_)
+ {
+ nameid = item_data->nameid;
+ }
+ OMATCH_END ();
+ }
+ else
+ nameid = wrap<ItemNameId>(conv_num(st, data));
+
+ amount = conv_num(st, &AARG(1));
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(2))));
+ x = conv_num(st, &AARG(3));
+ y = conv_num(st, &AARG(4));
+
+ P<map_local> m = ((sd && mapname == MOB_THIS_MAP)
+ ? sd->bl_m
+ : TRY_UNWRAP(map_mapname2mapid(mapname), return));
+
+ if (nameid)
+ {
+ Item item_tmp {};
+ item_tmp.nameid = nameid;
+
+ map_addflooritem(&item_tmp, amount, m, x, y, nullptr, nullptr, nullptr);
+ }
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_delitem(ScriptState *st)
+{
+ ItemNameId nameid;
+ int amount;
+ dumb_ptr<map_session_data> sd;
+ struct script_data *data;
+
+ sd = script_rid2sd(st);
+
+ data = &AARG(0);
+ get_val(st, data);
+ if (data->is<ScriptDataStr>())
+ {
+ ZString name = ZString(conv_str(st, data));
+ Option<P<struct item_data>> item_data_ = itemdb_searchname(name);
+ OMATCH_BEGIN_SOME (item_data, item_data_)
+ {
+ nameid = item_data->nameid;
+ }
+ OMATCH_END ();
+ }
+ else
+ nameid = wrap<ItemNameId>(conv_num(st, data));
+
+ amount = conv_num(st, &AARG(1));
+
+ if (!nameid || amount <= 0)
+ {
+ //By Lupus. Don't run FOR if you've got the wrong item ID or amount<=0
+ return;
+ }
+
+ for (IOff0 i : IOff0::iter())
+ {
+ if (sd->status.inventory[i].nameid == nameid)
+ {
+ if (sd->status.inventory[i].amount >= amount)
+ {
+ pc_delitem(sd, i, amount, 0);
+ break;
+ }
+ else
+ {
+ amount -= sd->status.inventory[i].amount;
+ if (amount == 0)
+ amount = sd->status.inventory[i].amount;
+ pc_delitem(sd, i, amount, 0);
+ break;
+ }
+ }
+ }
+
+}
+
+/*==========================================
+ *キャラ関係のID取得
+ *------------------------------------------
+ */
+static
+void builtin_getcharid(ScriptState *st)
+{
+ int num;
+ dumb_ptr<map_session_data> sd;
+
+ num = conv_num(st, &AARG(0));
+ if (HARG(1))
+ sd = map_nick2sd(stringish<CharName>(ZString(conv_str(st, &AARG(1)))));
+ else
+ sd = script_rid2sd(st);
+ if (sd == nullptr)
+ {
+ push_int<ScriptDataInt>(st->stack, -1);
+ return;
+ }
+ if (num == 0)
+ push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status_key.char_id));
+ if (num == 1)
+ push_int<ScriptDataInt>(st->stack, unwrap<PartyId>(sd->status.party_id));
+ if (num == 2)
+ push_int<ScriptDataInt>(st->stack, 0/*guild_id*/);
+ if (num == 3)
+ push_int<ScriptDataInt>(st->stack, unwrap<AccountId>(sd->status_key.account_id));
+}
+
+/*==========================================
+ *指定IDのPT名取得
+ *------------------------------------------
+ */
+static
+RString builtin_getpartyname_sub(PartyId party_id)
+{
+ Option<PartyPair> p = party_search(party_id);
+
+ return p.pmd_pget(&PartyMost::name).copy_or(PartyName());
+}
+
+/*==========================================
+ * キャラクタの名前
+ *------------------------------------------
+ */
+static
+void builtin_strcharinfo(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd;
+ int num;
+
+ sd = script_rid2sd(st);
+ num = conv_num(st, &AARG(0));
+ if (num == 0)
+ {
+ RString buf = sd->status_key.name.to__actual();
+ push_str<ScriptDataStr>(st->stack, buf);
+ }
+ if (num == 1)
+ {
+ RString buf = builtin_getpartyname_sub(sd->status.party_id);
+ if (buf)
+ push_str<ScriptDataStr>(st->stack, buf);
+ else
+ push_str<ScriptDataStr>(st->stack, ""_s);
+ }
+ if (num == 2)
+ {
+ // was: guild name
+ push_str<ScriptDataStr>(st->stack, ""_s);
+ }
+
+}
+
+// indexed by the equip_* in db/const.txt
+// TODO change to use EQUIP
+static
+Array<EPOS, 11> equip //=
+{{
+ EPOS::HAT,
+ EPOS::MISC1,
+ EPOS::SHIELD,
+ EPOS::WEAPON,
+ EPOS::GLOVES,
+ EPOS::SHOES,
+ EPOS::CAPE,
+ EPOS::MISC2,
+ EPOS::TORSO,
+ EPOS::LEGS,
+ EPOS::ARROW,
+}};
+
+/*==========================================
+ * GetEquipID(Pos); Pos: 1-10
+ *------------------------------------------
+ */
+static
+void builtin_getequipid(ScriptState *st)
+{
+ int num;
+ dumb_ptr<map_session_data> sd;
+
+ sd = script_rid2sd(st);
+ if (sd == nullptr)
+ {
+ PRINTF("getequipid: sd == nullptr\n"_fmt);
+ return;
+ }
+ num = conv_num(st, &AARG(0));
+ IOff0 i = pc_checkequip(sd, equip[num - 1]);
+ if (i.ok())
+ {
+ Option<P<struct item_data>> item_ = sd->inventory_data[i];
+ OMATCH_BEGIN (item_)
+ {
+ OMATCH_CASE_SOME (item)
+ {
+ push_int<ScriptDataInt>(st->stack, unwrap<ItemNameId>(item->nameid));
+ }
+ OMATCH_CASE_NONE ()
+ {
+ push_int<ScriptDataInt>(st->stack, 0);
+ }
+ }
+ OMATCH_END ();
+ }
+ else
+ {
+ push_int<ScriptDataInt>(st->stack, -1);
+ }
+}
+
+/*==========================================
+ * freeloop
+ *------------------------------------------
+ */
+static
+void builtin_freeloop(ScriptState *st)
+{
+ int num;
+ num = conv_num(st, &AARG(0));
+ if(num == 1)
+ {
+ st->freeloop = 1;
+ }
+ else
+ {
+ st->freeloop = 0;
+ }
+}
+
+/*==========================================
+ * 装備名文字列(精錬メニュー用)
+ *------------------------------------------
+ */
+static
+void builtin_getequipname(ScriptState *st)
+{
+ int num;
+ dumb_ptr<map_session_data> sd;
+
+ AString buf;
+
+ sd = script_rid2sd(st);
+ num = conv_num(st, &AARG(0));
+ IOff0 i = pc_checkequip(sd, equip[num - 1]);
+ if (i.ok())
+ {
+ Option<P<struct item_data>> item_ = sd->inventory_data[i];
+ OMATCH_BEGIN (item_)
+ {
+ OMATCH_CASE_SOME (item)
+ {
+ buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], item->jname);
+ }
+ OMATCH_CASE_NONE ()
+ {
+ buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]);
+ }
+ }
+ OMATCH_END ();
+ }
+ else
+ {
+ buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]);
+ }
+ push_str<ScriptDataStr>(st->stack, buf);
+
+}
+
+/*==========================================
+ * 装備品による能力値ボーナス
+ *------------------------------------------
+ */
+static
+void builtin_bonus(ScriptState *st)
+{
+ SP type = SP(conv_num(st, &AARG(0)));
+ int val = conv_num(st, &AARG(1));
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ pc_bonus(sd, type, val);
+
+}
+
+/*==========================================
+ * 装備品による能力値ボーナス
+ *------------------------------------------
+ */
+static
+void builtin_bonus2(ScriptState *st)
+{
+ SP type = SP(conv_num(st, &AARG(0)));
+ int type2 = conv_num(st, &AARG(1));
+ int val = conv_num(st, &AARG(2));
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ pc_bonus2(sd, type, type2, val);
+
+}
+
+/*==========================================
+ * スキル所得
+ *------------------------------------------
+ */
+static
+void builtin_skill(ScriptState *st)
+{
+ int level, flag = 1;
+ dumb_ptr<map_session_data> sd;
+
+ SkillID id = SkillID(conv_num(st, &AARG(0)));
+ level = conv_num(st, &AARG(1));
+ if (HARG(2))
+ flag = conv_num(st, &AARG(2));
+ sd = script_rid2sd(st);
+ pc_skill(sd, id, level, flag);
+ clif_skillinfoblock(sd);
+
+}
+
+/*==========================================
+ * [Fate] Sets the skill level permanently
+ *------------------------------------------
+ */
+static
+void builtin_setskill(ScriptState *st)
+{
+ int level;
+ dumb_ptr<map_session_data> sd;
+
+ SkillID id = static_cast<SkillID>(conv_num(st, &AARG(0)));
+ level = conv_num(st, &AARG(1));
+ sd = script_rid2sd(st);
+
+ level = std::min(level, MAX_SKILL_LEVEL);
+ level = std::max(level, 0);
+ sd->status.skill[id].lv = level;
+ clif_skillinfoblock(sd);
+}
+
+/*==========================================
+ * スキルレベル所得
+ *------------------------------------------
+ */
+static
+void builtin_getskilllv(ScriptState *st)
+{
+ SkillID id = SkillID(conv_num(st, &AARG(0)));
+ push_int<ScriptDataInt>(st->stack, pc_checkskill(script_rid2sd(st), id));
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_getgmlevel(ScriptState *st)
+{
+ push_int<ScriptDataInt>(st->stack, pc_isGM(script_rid2sd(st)).get_all_bits());
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_end(ScriptState *st)
+{
+ st->state = ScriptEndState::END;
+}
+
+/*==========================================
+ * [Freeyorp] Return the current opt2
+ *------------------------------------------
+ */
+
+static
+void builtin_getopt2(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd;
+
+ sd = script_rid2sd(st);
+
+ push_int<ScriptDataInt>(st->stack, static_cast<uint16_t>(sd->opt2));
+
+}
+
+/*==========================================
+ * [Freeyorp] Sets opt2
+ *------------------------------------------
+ */
+
+static
+void builtin_setopt2(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd;
+
+ Opt2 new_opt2 = Opt2(conv_num(st, &AARG(0)));
+ sd = script_rid2sd(st);
+ if (new_opt2 == sd->opt2)
+ return;
+ sd->opt2 = new_opt2;
+ clif_changeoption(sd);
+ pc_calcstatus(sd, 0);
+
+}
+
+/*==========================================
+ * セーブポイントの保存
+ *------------------------------------------
+ */
+static
+void builtin_savepoint(ScriptState *st)
+{
+ int x, y;
+
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x = conv_num(st, &AARG(1));
+ y = conv_num(st, &AARG(2));
+ pc_setsavepoint(script_rid2sd(st), str, x, y);
+}
+
+/*==========================================
+ * gettimetick(type)
+ *
+ * type The type of time measurement.
+ * Specify 0 for the system tick, 1 for
+ * seconds elapsed today, or 2 for seconds
+ * since Unix epoch. Defaults to 0 for any
+ * other value.
+ *------------------------------------------
+ */
+static
+void builtin_gettimetick(ScriptState *st) /* Asgard Version */
+{
+ int type;
+ type = conv_num(st, &AARG(0));
+
+ switch (type)
+ {
+ /* Number of seconds elapsed today(0-86399, 00:00:00-23:59:59). */
+ case 1:
+ {
+ struct tm t = TimeT::now();
+ push_int<ScriptDataInt>(st->stack,
+ t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec);
+ break;
+ }
+ /* Seconds since Unix epoch. */
+ case 2:
+ push_int<ScriptDataInt>(st->stack, static_cast<time_t>(TimeT::now()));
+ break;
+ /* System tick(unsigned int, and yes, it will wrap). */
+ case 0:
+ default:
+ push_int<ScriptDataInt>(st->stack, gettick().time_since_epoch().count());
+ break;
+ }
+}
+
+/*==========================================
+ * GetTime(Type);
+ * 1: Sec 2: Min 3: Hour
+ * 4: WeekDay 5: MonthDay 6: Month
+ * 7: Year
+ *------------------------------------------
+ */
+static
+void builtin_gettime(ScriptState *st) /* Asgard Version */
+{
+ int type = conv_num(st, &AARG(0));
+
+ struct tm t = TimeT::now();
+
+ switch (type)
+ {
+ case 1: //Sec(0~59)
+ push_int<ScriptDataInt>(st->stack, t.tm_sec);
+ break;
+ case 2: //Min(0~59)
+ push_int<ScriptDataInt>(st->stack, t.tm_min);
+ break;
+ case 3: //Hour(0~23)
+ push_int<ScriptDataInt>(st->stack, t.tm_hour);
+ break;
+ case 4: //WeekDay(0~6)
+ push_int<ScriptDataInt>(st->stack, t.tm_wday);
+ break;
+ case 5: //MonthDay(01~31)
+ push_int<ScriptDataInt>(st->stack, t.tm_mday);
+ break;
+ case 6: //Month(01~12)
+ push_int<ScriptDataInt>(st->stack, t.tm_mon + 1);
+ break;
+ case 7: //Year(20xx)
+ push_int<ScriptDataInt>(st->stack, t.tm_year + 1900);
+ break;
+ default: //(format error)
+ push_int<ScriptDataInt>(st->stack, -1);
+ break;
+ }
+}
+
+/*==========================================
+ * カプラ倉庫を開く
+ *------------------------------------------
+ */
+static
+void builtin_openstorage(ScriptState *st)
+{
+// int sync = 0;
+// if (st->end >= 3) sync = conv_num(st,& (st->stack->stack_data[st->start+2]));
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+// if (sync) {
+ st->state = ScriptEndState::STOP;
+ sd->npc_flags.storage = 1;
+// } else st->state = ScriptEndState::END;
+
+ storage_storageopen(sd);
+}
+
+/*==========================================
+ * NPCで経験値上げる
+ *------------------------------------------
+ */
+static
+void builtin_getexp(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ int base = 0, job = 0;
+
+ base = conv_num(st, &AARG(0));
+ job = conv_num(st, &AARG(1));
+ if (base < 0 || job < 0)
+ return;
+ if (sd)
+ pc_gainexp_reason(sd, base, job, PC_GAINEXP_REASON::SCRIPT);
+
+}
+
+/*==========================================
+ * モンスター発生
+ *------------------------------------------
+ */
+static
+void builtin_monster(ScriptState *st)
+{
+ Species mob_class;
+ int amount, x, y;
+ NpcEvent event;
+
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x = conv_num(st, &AARG(1));
+ y = conv_num(st, &AARG(2));
+ MobName str = stringish<MobName>(ZString(conv_str(st, &AARG(3))));
+ mob_class = wrap<Species>(conv_num(st, &AARG(4)));
+ amount = conv_num(st, &AARG(5));
+ if (HARG(6))
+ extract(ZString(conv_str(st, &AARG(6))), &event);
+
+ mob_once_spawn(map_id2sd(st->rid), mapname, x, y, str, mob_class, amount,
+ event);
+}
+
+/*==========================================
+ * モンスター発生
+ *------------------------------------------
+ */
+static
+void builtin_areamonster(ScriptState *st)
+{
+ Species mob_class;
+ int amount, x0, y0, x1, y1;
+ NpcEvent event;
+
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x0 = conv_num(st, &AARG(1));
+ y0 = conv_num(st, &AARG(2));
+ x1 = conv_num(st, &AARG(3));
+ y1 = conv_num(st, &AARG(4));
+ MobName str = stringish<MobName>(ZString(conv_str(st, &AARG(5))));
+ mob_class = wrap<Species>(conv_num(st, &AARG(6)));
+ amount = conv_num(st, &AARG(7));
+ if (HARG(8))
+ extract(ZString(conv_str(st, &AARG(8))), &event);
+
+ mob_once_spawn_area(map_id2sd(st->rid), mapname, x0, y0, x1, y1, str, mob_class,
+ amount, event);
+}
+
+/*==========================================
+ * モンスター削除
+ *------------------------------------------
+ */
+static
+void builtin_killmonster_sub(dumb_ptr<block_list> bl, NpcEvent event)
+{
+ dumb_ptr<mob_data> md = bl->is_mob();
+ if (event)
+ {
+ if (event == md->npc_event)
+ mob_delete(md);
+ return;
+ }
+ else if (!event)
+ {
+ if (md->spawn.delay1 == static_cast<interval_t>(-1)
+ && md->spawn.delay2 == static_cast<interval_t>(-1))
+ mob_delete(md);
+ return;
+ }
+}
+
+static
+void builtin_killmonster(ScriptState *st)
+{
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ ZString event_ = ZString(conv_str(st, &AARG(1)));
+ NpcEvent event;
+ if (event_ != "All"_s)
+ extract(event_, &event);
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+ map_foreachinarea(std::bind(builtin_killmonster_sub, ph::_1, event),
+ m,
+ 0, 0,
+ m->xs, m->ys,
+ BL::MOB);
+}
+
+/*==========================================
+ * NPC主体イベント実行
+ *------------------------------------------
+ */
+static
+void builtin_donpcevent(ScriptState *st)
+{
+ ZString event_ = ZString(conv_str(st, &AARG(0)));
+ NpcEvent event;
+ extract(event_, &event);
+ npc_event_do(event);
+}
+
+/*==========================================
+ * イベントタイマー追加
+ *------------------------------------------
+ */
+static
+void builtin_addtimer(ScriptState *st)
+{
+ interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(0)));
+ ZString event_ = ZString(conv_str(st, &AARG(1)));
+ NpcEvent event;
+ extract(event_, &event);
+ pc_addeventtimer(script_rid2sd(st), tick, event);
+}
+
+/*==========================================
+ * NPCタイマー初期化
+ *------------------------------------------
+ */
+static
+void builtin_initnpctimer(ScriptState *st)
+{
+ dumb_ptr<npc_data> nd_;
+ if (HARG(0))
+ nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0)))));
+ else
+ nd_ = map_id_is_npc(st->oid);
+ assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
+ dumb_ptr<npc_data_script> nd = nd_->is_script();
+
+ npc_settimerevent_tick(nd, interval_t::zero());
+ npc_timerevent_start(nd);
+}
+
+/*==========================================
+ * NPCタイマー開始
+ *------------------------------------------
+ */
+static
+void builtin_startnpctimer(ScriptState *st)
+{
+ dumb_ptr<npc_data> nd_;
+ if (HARG(0))
+ nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0)))));
+ else
+ nd_ = map_id_is_npc(st->oid);
+ assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
+ dumb_ptr<npc_data_script> nd = nd_->is_script();
+
+ npc_timerevent_start(nd);
+}
+
+/*==========================================
+ * NPCタイマー停止
+ *------------------------------------------
+ */
+static
+void builtin_stopnpctimer(ScriptState *st)
+{
+ dumb_ptr<npc_data> nd_;
+ if (HARG(0))
+ nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0)))));
+ else
+ nd_ = map_id_is_npc(st->oid);
+ assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
+ dumb_ptr<npc_data_script> nd = nd_->is_script();
+
+ npc_timerevent_stop(nd);
+}
+
+/*==========================================
+ * NPCタイマー情報所得
+ *------------------------------------------
+ */
+static
+void builtin_getnpctimer(ScriptState *st)
+{
+ dumb_ptr<npc_data> nd_;
+ int type = conv_num(st, &AARG(0));
+ int val = 0;
+ if (HARG(1))
+ nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(1)))));
+ else
+ nd_ = map_id_is_npc(st->oid);
+ assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
+ dumb_ptr<npc_data_script> nd = nd_->is_script();
+
+ switch (type)
+ {
+ case 0:
+ val = npc_gettimerevent_tick(nd).count();
+ break;
+ case 1:
+ val = nd->scr.timer_active;
+ break;
+ case 2:
+ val = nd->scr.timer_eventv.size();
+ break;
+ }
+ push_int<ScriptDataInt>(st->stack, val);
+}
+
+/*==========================================
+ * NPCタイマー値設定
+ *------------------------------------------
+ */
+static
+void builtin_setnpctimer(ScriptState *st)
+{
+ dumb_ptr<npc_data> nd_;
+ interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(0)));
+ if (HARG(1))
+ nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(1)))));
+ else
+ nd_ = map_id_is_npc(st->oid);
+ assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
+ dumb_ptr<npc_data_script> nd = nd_->is_script();
+
+ npc_settimerevent_tick(nd, tick);
+}
+
+/*==========================================
+ * 天の声アナウンス
+ *------------------------------------------
+ */
+static
+void builtin_announce(ScriptState *st)
+{
+ int flag;
+ ZString str = ZString(conv_str(st, &AARG(0)));
+ flag = conv_num(st, &AARG(1));
+
+ if (flag & 0x0f)
+ {
+ dumb_ptr<block_list> bl;
+ if (flag & 0x08)
+ bl = map_id2bl(st->oid);
+ else
+ bl = script_rid2sd(st);
+ clif_GMmessage(bl, str, flag);
+ }
+ else
+ intif_GMmessage(str);
+}
+
+/*==========================================
+ * 天の声アナウンス(特定マップ)
+ *------------------------------------------
+ */
+static
+void builtin_mapannounce_sub(dumb_ptr<block_list> bl, XString str, int flag)
+{
+ clif_GMmessage(bl, str, flag | 3);
+}
+
+static
+void builtin_mapannounce(ScriptState *st)
+{
+ int flag;
+
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ ZString str = ZString(conv_str(st, &AARG(1)));
+ flag = conv_num(st, &AARG(2));
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+ map_foreachinarea(std::bind(builtin_mapannounce_sub, ph::_1, str, flag & 0x10),
+ m,
+ 0, 0,
+ m->xs, m->ys,
+ BL::PC);
+}
+
+/*==========================================
+ * ユーザー数所得
+ *------------------------------------------
+ */
+static
+void builtin_getusers(ScriptState *st)
+{
+ int flag = conv_num(st, &AARG(0));
+ dumb_ptr<block_list> bl = map_id2bl((flag & 0x08) ? st->oid : st->rid);
+ int val = 0;
+ switch (flag & 0x07)
+ {
+ case 0:
+ val = bl->bl_m->users;
+ break;
+ case 1:
+ val = map_getusers();
+ break;
+ }
+ push_int<ScriptDataInt>(st->stack, val);
+}
+
+/*==========================================
+ * マップ指定ユーザー数所得
+ *------------------------------------------
+ */
+static
+void builtin_getmapusers(ScriptState *st)
+{
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str),
+ {
+ push_int<ScriptDataInt>(st->stack, -1);
+ return;
+ });
+ push_int<ScriptDataInt>(st->stack, m->users);
+}
+
+/*==========================================
+ * エリア指定ユーザー数所得
+ *------------------------------------------
+ */
+static
+void builtin_getareausers_sub(dumb_ptr<block_list> bl, int *users)
+{
+ if (bool(bl->is_player()->status.option & Opt0::HIDE))
+ return;
+ (*users)++;
+}
+
+static
+void builtin_getareausers_living_sub(dumb_ptr<block_list> bl, int *users)
+{
+ if (bool(bl->is_player()->status.option & Opt0::HIDE))
+ return;
+ if (!pc_isdead(bl->is_player()))
+ (*users)++;
+}
+
+static
+void builtin_getareausers(ScriptState *st)
+{
+ int x0, y0, x1, y1, users = 0;
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x0 = conv_num(st, &AARG(1));
+ y0 = conv_num(st, &AARG(2));
+ x1 = conv_num(st, &AARG(3));
+ y1 = conv_num(st, &AARG(4));
+
+ int living = 0;
+ if (HARG(5))
+ {
+ living = conv_num(st, &AARG(5));
+ }
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str),
+ {
+ push_int<ScriptDataInt>(st->stack, -1);
+ return;
+ });
+ map_foreachinarea(std::bind(living ? builtin_getareausers_living_sub: builtin_getareausers_sub, ph::_1, &users),
+ m,
+ x0, y0,
+ x1, y1,
+ BL::PC);
+ push_int<ScriptDataInt>(st->stack, users);
+}
+
+/*==========================================
+ * エリア指定ドロップアイテム数所得
+ *------------------------------------------
+ */
+static
+void builtin_getareadropitem_sub(dumb_ptr<block_list> bl, ItemNameId item, int *amount)
+{
+ dumb_ptr<flooritem_data> drop = bl->is_item();
+
+ if (drop->item_data.nameid == item)
+ (*amount) += drop->item_data.amount;
+
+}
+
+static
+void builtin_getareadropitem_sub_anddelete(dumb_ptr<block_list> bl, ItemNameId item, int *amount)
+{
+ dumb_ptr<flooritem_data> drop = bl->is_item();
+
+ if (drop->item_data.nameid == item)
+ {
+ (*amount) += drop->item_data.amount;
+ clif_clearflooritem(drop, nullptr);
+ map_delobject(drop->bl_id, drop->bl_type);
+ }
+}
+
+static
+void builtin_getareadropitem(ScriptState *st)
+{
+ ItemNameId item;
+ int x0, y0, x1, y1, amount = 0, delitems = 0;
+ struct script_data *data;
+
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x0 = conv_num(st, &AARG(1));
+ y0 = conv_num(st, &AARG(2));
+ x1 = conv_num(st, &AARG(3));
+ y1 = conv_num(st, &AARG(4));
+
+ data = &AARG(5);
+ get_val(st, data);
+ if (data->is<ScriptDataStr>())
+ {
+ ZString name = ZString(conv_str(st, data));
+ Option<P<struct item_data>> item_data_ = itemdb_searchname(name);
+ OMATCH_BEGIN_SOME (item_data, item_data_)
+ {
+ item = item_data->nameid;
+ }
+ OMATCH_END ();
+ }
+ else
+ item = wrap<ItemNameId>(conv_num(st, data));
+
+ if (HARG(6))
+ delitems = conv_num(st, &AARG(6));
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str),
+ {
+ push_int<ScriptDataInt>(st->stack, -1);
+ return;
+ });
+ if (delitems)
+ map_foreachinarea(std::bind(builtin_getareadropitem_sub_anddelete, ph::_1, item, &amount),
+ m,
+ x0, y0,
+ x1, y1,
+ BL::ITEM);
+ else
+ map_foreachinarea(std::bind(builtin_getareadropitem_sub, ph::_1, item, &amount),
+ m,
+ x0, y0,
+ x1, y1,
+ BL::ITEM);
+
+ push_int<ScriptDataInt>(st->stack, amount);
+}
+
+/*==========================================
+ * NPCの有効化
+ *------------------------------------------
+ */
+static
+void builtin_enablenpc(ScriptState *st)
+{
+ NpcName str = stringish<NpcName>(ZString(conv_str(st, &AARG(0))));
+ npc_enable(str, 1);
+}
+
+/*==========================================
+ * NPCの無効化
+ *------------------------------------------
+ */
+static
+void builtin_disablenpc(ScriptState *st)
+{
+ NpcName str = stringish<NpcName>(ZString(conv_str(st, &AARG(0))));
+ npc_enable(str, 0);
+}
+
+/*==========================================
+ * 状態異常にかかる
+ *------------------------------------------
+ */
+static
+void builtin_sc_start(ScriptState *st)
+{
+ dumb_ptr<block_list> bl;
+ int val1;
+ StatusChange type = static_cast<StatusChange>(conv_num(st, &AARG(0)));
+ interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(1)));
+ if (tick < 1_s)
+ // work around old behaviour of:
+ // speed potion
+ // atk potion
+ // matk potion
+ //
+ // which used to use seconds
+ // all others used milliseconds
+ tick *= 1000;
+ val1 = conv_num(st, &AARG(2));
+ if (HARG(3)) //指定したキャラを状態異常にする
+ bl = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(3))));
+ else
+ bl = map_id2bl(st->rid);
+ skill_status_change_start(bl, type, val1, tick);
+}
+
+/*==========================================
+ * 状態異常が直る
+ *------------------------------------------
+ */
+static
+void builtin_sc_end(ScriptState *st)
+{
+ dumb_ptr<block_list> bl;
+ StatusChange type = StatusChange(conv_num(st, &AARG(0)));
+ bl = map_id2bl(st->rid);
+ skill_status_change_end(bl, type, nullptr);
+}
+
+static
+void builtin_sc_check(ScriptState *st)
+{
+ dumb_ptr<block_list> bl;
+ StatusChange type = StatusChange(conv_num(st, &AARG(0)));
+ bl = map_id2bl(st->rid);
+
+ push_int<ScriptDataInt>(st->stack, skill_status_change_active(bl, type));
+
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_debugmes(ScriptState *st)
+{
+ RString mes = conv_str(st, &AARG(0));
+ PRINTF("script debug: %d %d: '%s'\n"_fmt,
+ st->rid, st->oid, mes);
+}
+
+/*==========================================
+ * ステータスリセット
+ *------------------------------------------
+ */
+static
+void builtin_resetstatus(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd;
+ sd = script_rid2sd(st);
+ pc_resetstate(sd);
+}
+
+/*==========================================
+ * RIDのアタッチ
+ *------------------------------------------
+ */
+static
+void builtin_attachrid(ScriptState *st)
+{
+ st->rid = wrap<BlockId>(conv_num(st, &AARG(0)));
+ push_int<ScriptDataInt>(st->stack, (map_id2sd(st->rid) != nullptr));
+}
+
+/*==========================================
+ * RIDのデタッチ
+ *------------------------------------------
+ */
+static
+void builtin_detachrid(ScriptState *st)
+{
+ st->rid = BlockId();
+}
+
+/*==========================================
+ * 存在チェック
+ *------------------------------------------
+ */
+static
+void builtin_isloggedin(ScriptState *st)
+{
+ push_int<ScriptDataInt>(st->stack,
+ map_id2sd(wrap<BlockId>(conv_num(st, &AARG(0)))) != nullptr);
+}
+
+static
+void builtin_setmapflag(ScriptState *st)
+{
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ int i = conv_num(st, &AARG(1));
+ MapFlag mf = map_flag_from_int(i);
+ Option<P<map_local>> m_ = map_mapname2mapid(str);
+ OMATCH_BEGIN_SOME (m, m_)
+ {
+ m->flag.set(mf, 1);
+ }
+ OMATCH_END ();
+}
+
+static
+void builtin_removemapflag(ScriptState *st)
+{
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ int i = conv_num(st, &AARG(1));
+ MapFlag mf = map_flag_from_int(i);
+ Option<P<map_local>> m_ = map_mapname2mapid(str);
+ OMATCH_BEGIN_SOME (m, m_)
+ {
+ m->flag.set(mf, 0);
+ }
+ OMATCH_END ();
+}
+
+static
+void builtin_getmapflag(ScriptState *st)
+{
+ int r = -1;
+
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ int i = conv_num(st, &AARG(1));
+ MapFlag mf = map_flag_from_int(i);
+ Option<P<map_local>> m_ = map_mapname2mapid(str);
+ OMATCH_BEGIN_SOME (m, m_)
+ {
+ r = m->flag.get(mf);
+ }
+ OMATCH_END ();
+
+ push_int<ScriptDataInt>(st->stack, r);
+}
+
+static
+void builtin_pvpon(ScriptState *st)
+{
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str), return);
+ if (!m->flag.get(MapFlag::PVP) && !m->flag.get(MapFlag::NOPVP))
+ {
+ m->flag.set(MapFlag::PVP, 1);
+
+ if (battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris]
+ return;
+
+ for (io::FD i : iter_fds())
+ {
+ Session *s = get_session(i);
+ if (!s)
+ continue;
+ map_session_data *pl_sd = static_cast<map_session_data *>(s->session_data.get());
+ if (pl_sd && pl_sd->state.auth)
+ {
+ if (m == pl_sd->bl_m && !pl_sd->pvp_timer)
+ {
+ pl_sd->pvp_timer = Timer(gettick() + 200_ms,
+ std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2,
+ pl_sd->bl_id));
+ pl_sd->pvp_rank = 0;
+ pl_sd->pvp_point = 5;
+ }
+ }
+ }
+ }
+}
+
+static
+void builtin_pvpoff(ScriptState *st)
+{
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str), return);
+ if (m->flag.get(MapFlag::PVP) && m->flag.get(MapFlag::NOPVP))
+ {
+ m->flag.set(MapFlag::PVP, 0);
+
+ if (battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris]
+ return;
+
+ for (io::FD i : iter_fds())
+ {
+ Session *s = get_session(i);
+ if (!s)
+ continue;
+ map_session_data *pl_sd = static_cast<map_session_data *>(s->session_data.get());
+ if (pl_sd && pl_sd->state.auth)
+ {
+ if (m == pl_sd->bl_m)
+ {
+ pl_sd->pvp_timer.cancel();
+ }
+ }
+ }
+ }
+}
+
+static
+void builtin_setpvpchannel(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ int flag;
+ flag = conv_num(st, &AARG(0));
+ if (flag < 1)
+ flag = 0;
+
+ sd->state.pvpchannel = flag;
+}
+
+static
+void builtin_getpvpflag(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ int num = conv_num(st, &AARG(0));
+ int flag = 0;
+
+ switch (num){
+ case 0:
+ flag = sd->state.pvpchannel;
+ break;
+ case 1:
+ flag = bool(sd->status.option & Opt0::HIDE);
+ break;
+ }
+
+ push_int<ScriptDataInt>(st->stack, flag);
+}
+
+/*==========================================
+ * NPCエモーション
+ *------------------------------------------
+ */
+
+static
+void builtin_emotion(ScriptState *st)
+{
+ ZString str;
+ dumb_ptr<map_session_data> pl_sd = nullptr;
+ int type = conv_num(st, &AARG(0));
+ if (HARG(1)) {
+ str = ZString(conv_str(st, &AARG(1)));
+ CharName player = stringish<CharName>(str);
+ pl_sd = map_nick2sd(player);
+ }
+ if (type < 0 || type > 200)
+ return;
+ if (pl_sd != nullptr)
+ clif_emotion_towards(map_id2bl(st->oid), pl_sd, type);
+ else if (st->rid && str == "self"_s)
+ clif_emotion(map_id2sd(st->rid), type);
+ else
+ clif_emotion(map_id2bl(st->oid), type);
+}
+
+static
+void builtin_mapwarp(ScriptState *st) // Added by RoVeRT
+{
+ int x, y;
+ int x0, y0, x1, y1;
+
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x0 = 0;
+ y0 = 0;
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+ x1 = m->xs;
+ y1 = m->ys;
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(1))));
+ x = conv_num(st, &AARG(2));
+ y = conv_num(st, &AARG(3));
+
+ map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y),
+ m,
+ x0, y0,
+ x1, y1,
+ BL::PC);
+}
+
+static
+void builtin_mobcount_sub(dumb_ptr<block_list> bl, NpcEvent event, int *c)
+{
+ if (event == bl->is_mob()->npc_event)
+ (*c)++;
+}
+
+static
+void builtin_mobcount(ScriptState *st) // Added by RoVeRT
+{
+ int c = 0;
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ ZString event_ = ZString(conv_str(st, &AARG(1)));
+ NpcEvent event;
+ extract(event_, &event);
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname),
+ {
+ push_int<ScriptDataInt>(st->stack, -1);
+ return;
+ });
+ map_foreachinarea(std::bind(builtin_mobcount_sub, ph::_1, event, &c),
+ m,
+ 0, 0,
+ m->xs, m->ys,
+ BL::MOB);
+
+ push_int<ScriptDataInt>(st->stack, (c - 1));
+
+}
+
+static
+void builtin_marriage(ScriptState *st)
+{
+ CharName partner = stringish<CharName>(ZString(conv_str(st, &AARG(0))));
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ dumb_ptr<map_session_data> p_sd = map_nick2sd(partner);
+
+ if (sd == nullptr || p_sd == nullptr || pc_marriage(sd, p_sd) < 0)
+ {
+ push_int<ScriptDataInt>(st->stack, 0);
+ return;
+ }
+ push_int<ScriptDataInt>(st->stack, 1);
+}
+
+static
+void builtin_divorce(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ if (sd == nullptr || pc_divorce(sd) < 0)
+ {
+ push_int<ScriptDataInt>(st->stack, 0);
+ return;
+ }
+
+ push_int<ScriptDataInt>(st->stack, 1);
+}
+
+/*==========================================
+ * IDからItem名
+ *------------------------------------------
+ */
+static
+void builtin_getitemname(ScriptState *st)
+{
+ Option<P<struct item_data>> i_data = None;
+ struct script_data *data;
+
+ data = &AARG(0);
+ get_val(st, data);
+ if (data->is<ScriptDataStr>())
+ {
+ ZString name = ZString(conv_str(st, data));
+ i_data = itemdb_searchname(name);
+ }
+ else
+ {
+ ItemNameId item_id = wrap<ItemNameId>(conv_num(st, data));
+ i_data = Some(itemdb_search(item_id));
+ }
+
+ RString item_name = i_data.pmd_pget(&item_data::jname).copy_or(stringish<ItemName>("Unknown Item"_s));
+
+ push_str<ScriptDataStr>(st->stack, item_name);
+}
+
+static
+void builtin_getitemlink(ScriptState *st)
+{
+ struct script_data *data;
+ AString buf;
+ data = &AARG(0);
+ ZString name = conv_str(st, data);
+
+ ItemNameId item_id;
+ Option<P<struct item_data>> item_data_ = itemdb_searchname(name);
+ OMATCH_BEGIN (item_data_)
+ {
+ OMATCH_CASE_SOME (item_data)
+ {
+ buf = STRPRINTF("@@%d|@@"_fmt, item_data->nameid);
+ }
+ OMATCH_CASE_NONE ()
+ {
+ buf = "Unknown Item"_s;
+ }
+ }
+ OMATCH_END ();
+ push_str<ScriptDataStr>(st->stack, buf);
+}
+
+static
+void builtin_getspellinvocation(ScriptState *st)
+{
+ RString name = conv_str(st, &AARG(0));
+
+ AString invocation = magic::magic_find_invocation(name);
+ if (!invocation)
+ invocation = "..."_s;
+
+ push_str<ScriptDataStr>(st->stack, invocation);
+}
+
+static
+void builtin_getpartnerid2(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status.partner_id));
+}
+
+/*==========================================
+ * PCの所持品情報読み取り
+ *------------------------------------------
+ */
+static
+void builtin_getinventorylist(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ int j = 0;
+ if (!sd)
+ return;
+ for (IOff0 i : IOff0::iter())
+ {
+ if (sd->status.inventory[i].nameid
+ && sd->status.inventory[i].amount > 0)
+ {
+ pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_id"_s), j),
+ unwrap<ItemNameId>(sd->status.inventory[i].nameid));
+ pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_amount"_s), j),
+ sd->status.inventory[i].amount);
+ pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_equip"_s), j),
+ static_cast<uint16_t>(sd->status.inventory[i].equip));
+ j++;
+ }
+ }
+ pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_count"_s)), j);
+}
+
+static
+void builtin_getactivatedpoolskilllist(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ SkillID pool_skills[MAX_SKILL_POOL];
+ int skill_pool_size = skill_pool(sd, pool_skills);
+ int i, count = 0;
+
+ if (!sd)
+ return;
+
+ for (i = 0; i < skill_pool_size; i++)
+ {
+ SkillID skill_id = pool_skills[i];
+
+ if (sd->status.skill[skill_id].lv)
+ {
+ pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count),
+ static_cast<uint16_t>(skill_id));
+ pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count),
+ sd->status.skill[skill_id].lv);
+ pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count),
+ static_cast<uint16_t>(sd->status.skill[skill_id].flags));
+ pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count),
+ skill_name(skill_id));
+ ++count;
+ }
+ }
+ pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count);
+
+}
+
+static
+void builtin_getunactivatedpoolskilllist(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ int i, count = 0;
+
+ if (!sd)
+ return;
+
+ for (i = 0; i < skill_pool_skills.size(); i++)
+ {
+ SkillID skill_id = skill_pool_skills[i];
+
+ if (sd->status.skill[skill_id].lv
+ && !bool(sd->status.skill[skill_id].flags & SkillFlags::POOL_ACTIVATED))
+ {
+ pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count),
+ static_cast<uint16_t>(skill_id));
+ pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count),
+ sd->status.skill[skill_id].lv);
+ pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count),
+ static_cast<uint16_t>(sd->status.skill[skill_id].flags));
+ pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count),
+ skill_name(skill_id));
+ ++count;
+ }
+ }
+ pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count);
+}
+
+static
+void builtin_poolskill(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ SkillID skill_id = SkillID(conv_num(st, &AARG(0)));
+
+ skill_pool_activate(sd, skill_id);
+ clif_skillinfoblock(sd);
+
+}
+
+static
+void builtin_unpoolskill(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ SkillID skill_id = SkillID(conv_num(st, &AARG(0)));
+
+ skill_pool_deactivate(sd, skill_id);
+ clif_skillinfoblock(sd);
+
+}
+
+/*==========================================
+ * NPCから発生するエフェクト
+ * misceffect(effect, [target])
+ *
+ * effect The effect type/ID.
+ * target The player name or being ID on
+ * which to display the effect. If not
+ * specified, it attempts to default to
+ * the current NPC or invoking PC.
+ *------------------------------------------
+ */
+static
+void builtin_misceffect(ScriptState *st)
+{
+ int type;
+ BlockId id;
+ CharName name;
+ dumb_ptr<block_list> bl = nullptr;
+
+ type = conv_num(st, &AARG(0));
+
+ if (HARG(1))
+ {
+ struct script_data *sdata = &AARG(1);
+
+ get_val(st, sdata);
+
+ if (sdata->is<ScriptDataStr>())
+ name = stringish<CharName>(ZString(conv_str(st, sdata)));
+ else
+ id = wrap<BlockId>(conv_num(st, sdata));
+ }
+
+ if (name.to__actual())
+ {
+ dumb_ptr<map_session_data> sd = map_nick2sd(name);
+ if (sd)
+ bl = sd;
+ }
+ else if (id)
+ bl = map_id2bl(id);
+ else if (st->oid)
+ bl = map_id2bl(st->oid);
+ else
+ {
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ if (sd)
+ bl = sd;
+ }
+
+ if (bl)
+ clif_misceffect(bl, type);
+
+}
+
+/*==========================================
+ * Special effects [Valaris]
+ *------------------------------------------
+ */
+static
+void builtin_specialeffect(ScriptState *st)
+{
+ dumb_ptr<block_list> bl = map_id2bl(st->oid);
+
+ if (bl == nullptr)
+ return;
+
+ clif_specialeffect(bl,
+ conv_num(st,
+ &AARG(0)),
+ 0);
+
+}
+
+static
+void builtin_specialeffect2(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ if (sd == nullptr)
+ return;
+
+ clif_specialeffect(sd,
+ conv_num(st,
+ &AARG(0)),
+ 0);
+
+}
+
+/*==========================================
+ * Nude [Valaris]
+ *------------------------------------------
+ */
+
+static
+void builtin_nude(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ if (sd == nullptr)
+ return;
+
+ for (EQUIP i : EQUIPs)
+ {
+ IOff0 idx = sd->equip_index_maybe[i];
+ if (idx.ok())
+ pc_unequipitem(sd, idx, CalcStatus::LATER);
+ }
+ pc_calcstatus(sd, 0);
+
+}
+
+/*==========================================
+ * UnequipById [Freeyorp]
+ *------------------------------------------
+ */
+
+static
+void builtin_unequipbyid(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ if (sd == nullptr)
+ return;
+
+ EQUIP slot_id = EQUIP(conv_num(st, &AARG(0)));
+
+ if (slot_id >= EQUIP() && slot_id < EQUIP::COUNT)
+ {
+ IOff0 idx = sd->equip_index_maybe[slot_id];
+ if (idx.ok())
+ pc_unequipitem(sd, idx, CalcStatus::LATER);
+ }
+
+ pc_calcstatus(sd, 0);
+
+}
+
+/*==========================================
+ * npcwarp [remoitnane]
+ * Move NPC to a new position on the same map.
+ *------------------------------------------
+ */
+static
+void builtin_npcwarp(ScriptState *st)
+{
+ int x, y;
+ dumb_ptr<npc_data> nd = nullptr;
+
+ x = conv_num(st, &AARG(0));
+ y = conv_num(st, &AARG(1));
+ NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(2))));
+ nd = npc_name2id(npc);
+
+ if (!nd)
+ {
+ PRINTF("builtin_npcwarp: no such npc: '%s'\n"_fmt, npc);
+ return;
+ }
+
+ P<map_local> m = nd->bl_m;
+
+ /* Crude sanity checks. */
+ if (!nd->bl_prev
+ || x < 0 || x > m->xs -1
+ || y < 0 || y > m->ys - 1)
+ return;
+
+ npc_enable(npc, 0);
+ map_delblock(nd); /* [Freeyorp] */
+ nd->bl_x = x;
+ nd->bl_y = y;
+ map_addblock(nd);
+ npc_enable(npc, 1);
+
+}
+
+/*==========================================
+ * npcareawarp [remoitnane] [wushin]
+ * Move NPC to a new area on the same map.
+ *------------------------------------------
+ */
+static
+void builtin_npcareawarp(ScriptState *st)
+{
+ int x0, y0, x1, y1, x, y, max, cb, lx = -1, ly = -1, j = 0;
+ dumb_ptr<npc_data> nd = nullptr;
+
+ NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(5))));
+ nd = npc_name2id(npc);
+
+ x0 = conv_num(st, &AARG(0));
+ y0 = conv_num(st, &AARG(1));
+ x1 = conv_num(st, &AARG(2));
+ y1 = conv_num(st, &AARG(3));
+ cb = conv_num(st, &AARG(4));
+
+ if (!nd)
+ {
+ PRINTF("builtin_npcareawarp: no such npc: '%s'\n"_fmt, npc);
+ return;
+ }
+
+ max = (y1 - y0 + 1) * (x1 - x0 + 1) * 3;
+ if (max > 1000)
+ max = 1000;
+
+ P<map_local> m = nd->bl_m;
+ if (cb) {
+ do
+ {
+ x = random_::in(x0, x1);
+ y = random_::in(y0, y1);
+ }
+ while (bool(map_getcell(m, x, y) & MapCell::UNWALKABLE)
+ && (++j) < max);
+ if (j >= max)
+ {
+ if (lx >= 0)
+ { // Since reference went wrong, the place which boiled before is used.
+ x = lx;
+ y = ly;
+ }
+ else
+ return; // Since reference of the place which boils first went wrong, it stops.
+ }
+ }
+ else
+ x = random_::in(x0, x1);
+ y = random_::in(y0, y1);
+
+ npc_enable(npc, 0);
+ map_delblock(nd); /* [Freeyorp] */
+ nd->bl_x = x;
+ nd->bl_y = y;
+ map_addblock(nd);
+ npc_enable(npc, 1);
+
+}
+
+/*==========================================
+ * message [MouseJstr]
+ *------------------------------------------
+ */
+
+static
+void builtin_message(ScriptState *st)
+{
+ CharName player = stringish<CharName>(ZString(conv_str(st, &AARG(0))));
+ ZString msg = ZString(conv_str(st, &AARG(1)));
+
+ dumb_ptr<map_session_data> pl_sd = map_nick2sd(player);
+ if (pl_sd == nullptr)
+ return;
+ clif_displaymessage(pl_sd->sess, msg);
+
+}
+
+/*==========================================
+ * npctalk (sends message to surrounding
+ * area) [Valaris]
+ *------------------------------------------
+ */
+
+static
+void builtin_npctalk(ScriptState *st)
+{
+ dumb_ptr<npc_data> nd = map_id_is_npc(st->oid);
+ RString str = conv_str(st, &AARG(0));
+
+ if (nd)
+ {
+ clif_message(nd, str);
+ }
+}
+
+/*==========================================
+ * getlook char info. getlook(arg)
+ *------------------------------------------
+ */
+static
+void builtin_getlook(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ LOOK type = LOOK(conv_num(st, &AARG(0)));
+ int val = -1;
+ switch (type)
+ {
+ case LOOK::HAIR: //1
+ val = sd->status.hair;
+ break;
+ case LOOK::WEAPON: //2
+ val = static_cast<uint16_t>(sd->status.weapon);
+ break;
+ case LOOK::HEAD_BOTTOM: //3
+ val = unwrap<ItemNameId>(sd->status.head_bottom);
+ break;
+ case LOOK::HEAD_TOP: //4
+ val = unwrap<ItemNameId>(sd->status.head_top);
+ break;
+ case LOOK::HEAD_MID: //5
+ val = unwrap<ItemNameId>(sd->status.head_mid);
+ break;
+ case LOOK::HAIR_COLOR: //6
+ val = sd->status.hair_color;
+ break;
+ case LOOK::CLOTHES_COLOR: //7
+ val = sd->status.clothes_color;
+ break;
+ case LOOK::SHIELD: //8
+ val = unwrap<ItemNameId>(sd->status.shield);
+ break;
+ case LOOK::SHOES: //9
+ break;
+ }
+
+ push_int<ScriptDataInt>(st->stack, val);
+}
+
+/*==========================================
+ * get char save point. argument: 0- map name, 1- x, 2- y
+ *------------------------------------------
+*/
+static
+void builtin_getsavepoint(ScriptState *st)
+{
+ int x, y, type;
+ dumb_ptr<map_session_data> sd;
+
+ sd = script_rid2sd(st);
+
+ type = conv_num(st, &AARG(0));
+
+ x = sd->status.save_point.x;
+ y = sd->status.save_point.y;
+ switch (type)
+ {
+ case 0:
+ {
+ RString mapname = sd->status.save_point.map_;
+ push_str<ScriptDataStr>(st->stack, mapname);
+ }
+ break;
+ case 1:
+ push_int<ScriptDataInt>(st->stack, x);
+ break;
+ case 2:
+ push_int<ScriptDataInt>(st->stack, y);
+ break;
+ }
+}
+
+/*==========================================
+ * areatimer
+ *------------------------------------------
+ */
+static
+void builtin_areatimer_sub(dumb_ptr<block_list> bl, interval_t tick, NpcEvent event)
+{
+ pc_addeventtimer(bl->is_player(), tick, event);
+}
+
+static
+void builtin_areatimer(ScriptState *st)
+{
+ int x0, y0, x1, y1;
+
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x0 = conv_num(st, &AARG(1));
+ y0 = conv_num(st, &AARG(2));
+ x1 = conv_num(st, &AARG(3));
+ y1 = conv_num(st, &AARG(4));
+ interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(5)));
+ ZString event_ = ZString(conv_str(st, &AARG(6)));
+ NpcEvent event;
+ extract(event_, &event);
+
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+
+ map_foreachinarea(std::bind(builtin_areatimer_sub, ph::_1, tick, event),
+ m,
+ x0, y0,
+ x1, y1,
+ BL::PC);
+}
+
+/*==========================================
+ * Check whether the PC is in the specified rectangle
+ *------------------------------------------
+ */
+static
+void builtin_isin(ScriptState *st)
+{
+ int x1, y1, x2, y2;
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ x1 = conv_num(st, &AARG(1));
+ y1 = conv_num(st, &AARG(2));
+ x2 = conv_num(st, &AARG(3));
+ y2 = conv_num(st, &AARG(4));
+
+ if (!sd)
+ return;
+
+ push_int<ScriptDataInt>(st->stack,
+ (sd->bl_x >= x1 && sd->bl_x <= x2)
+ && (sd->bl_y >= y1 && sd->bl_y <= y2)
+ && (str == sd->bl_m->name_));
+}
+
+/*==========================================
+ * Check whether the coords are collision
+ *------------------------------------------
+ */
+static
+void builtin_iscollision(ScriptState *st)
+{
+ int x, y;
+ MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+ P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+
+ x = conv_num(st, &AARG(1));
+ y = conv_num(st, &AARG(2));
+
+ push_int<ScriptDataInt>(st->stack,
+ bool(map_getcell(m, x, y) & MapCell::UNWALKABLE));
+}
+
+// Trigger the shop on a (hopefully) nearby shop NPC
+static
+void builtin_shop(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+ dumb_ptr<npc_data> nd;
+
+ if (!sd)
+ return;
+
+ NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0))));
+ nd = npc_name2id(name);
+ if (!nd)
+ {
+ PRINTF("builtin_shop: no such npc: '%s'\n"_fmt, name);
+ return;
+ }
+
+ builtin_close(st);
+ clif_npcbuysell(sd, nd->bl_id);
+}
+
+/*==========================================
+ * Check whether the PC is dead
+ *------------------------------------------
+ */
+static
+void builtin_isdead(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ push_int<ScriptDataInt>(st->stack, pc_isdead(sd));
+}
+
+/*========================================
+ * Changes a NPC name, and sprite
+ *----------------------------------------
+ */
+static
+void builtin_fakenpcname(ScriptState *st)
+{
+ NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0))));
+ NpcName newname = stringish<NpcName>(ZString(conv_str(st, &AARG(1))));
+ Species newsprite = wrap<Species>(static_cast<uint16_t>(conv_num(st, &AARG(2))));
+ dumb_ptr<npc_data> nd = npc_name2id(name);
+ if (!nd)
+ {
+ PRINTF("builtin_fakenpcname: no such npc: '%s'\n"_fmt, name);
+ return;
+ }
+ nd->name = newname;
+ nd->npc_class = newsprite;
+
+ // Refresh this npc
+ npc_enable(name, 0);
+ npc_enable(name, 1);
+
+}
+
+/*============================
+ * Gets the PC's x pos
+ *----------------------------
+ */
+static
+void builtin_getx(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ push_int<ScriptDataInt>(st->stack, sd->bl_x);
+}
+
+/*============================
+ * Gets the PC's y pos
+ *----------------------------
+ */
+static
+void builtin_gety(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ push_int<ScriptDataInt>(st->stack, sd->bl_y);
+}
+
+/*
+ * Get the PC's current map's name
+ */
+static
+void builtin_getmap(ScriptState *st)
+{
+ dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+ push_str<ScriptDataStr>(st->stack, sd->bl_m->name_);
+}
+
+/*
+ * Get the NPC's info
+ */
+static
+void builtin_strnpcinfo(ScriptState *st)
+{
+ int num = conv_num(st, &AARG(0));
+ RString name;
+ dumb_ptr<npc_data> nd;
+
+ if(HARG(1)){
+ NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(1))));
+ nd = npc_name2id(npc);
+ if (!nd)
+ {
+ PRINTF("builtin_strnpcinfo: no such npc: '%s'\n"_fmt, npc);
+ return;
+ }
+ } else {
+ nd = map_id_is_npc(st->oid);
+ }
+
+ switch(num)
+ {
+ case 0:
+ name = nd->name;
+ break;
+ case 1:
+ name = nd->name.xislice_h(std::find(nd->name.begin(), nd->name.end(), '#'));
+ break;
+ case 2:
+ name = nd->name.xislice_t(std::find(nd->name.begin(), nd->name.end(), '#'));
+ break;
+ case 3:
+ name = nd->bl_m->name_;
+ break;
+ }
+
+ push_str<ScriptDataStr>(st->stack, name);
+}
+
+/*============================
+ * Gets the NPC's x pos
+ *----------------------------
+ */
+static
+void builtin_getnpcx(ScriptState *st)
+{
+ dumb_ptr<npc_data> nd;
+
+ if(HARG(0)){
+ NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0))));
+ nd = npc_name2id(name);
+ if (!nd)
+ {
+ PRINTF("builtin_getnpcx: no such npc: '%s'\n"_fmt, name);
+ return;
+ }
+ } else {
+ nd = map_id_is_npc(st->oid);
+ }
+
+ push_int<ScriptDataInt>(st->stack, nd->bl_x);
+}
+
+/*============================
+ * Gets the NPC's y pos
+ *----------------------------
+ */
+static
+void builtin_getnpcy(ScriptState *st)
+{
+ dumb_ptr<npc_data> nd;
+
+ if(HARG(0)){
+ NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0))));
+ nd = npc_name2id(name);
+ if (!nd)
+ {
+ PRINTF("builtin_getnpcy: no such npc: '%s'\n"_fmt, name);
+ return;
+ }
+ } else {
+ nd = map_id_is_npc(st->oid);
+ }
+
+ push_int<ScriptDataInt>(st->stack, nd->bl_y);
+}
+
+static
+void builtin_mapexit(ScriptState *)
+{
+ runflag = 0;
+}
+
+
+#define BUILTIN(func, args, ret) \
+{builtin_##func, #func ## _s, args, ret}
+
+BuiltinFunction builtin_functions[] =
+{
+ BUILTIN(mes, "s"_s, '\0'),
+ BUILTIN(goto, "L"_s, '\0'),
+ BUILTIN(callfunc, "F"_s, '\0'),
+ BUILTIN(callsub, "L"_s, '\0'),
+ BUILTIN(return, ""_s, '\0'),
+ BUILTIN(next, ""_s, '\0'),
+ BUILTIN(close, ""_s, '\0'),
+ BUILTIN(close2, ""_s, '\0'),
+ BUILTIN(menu, "sL**"_s, '\0'),
+ BUILTIN(rand, "i?"_s, 'i'),
+ BUILTIN(isat, "Mxy"_s, 'i'),
+ BUILTIN(warp, "Mxy"_s, '\0'),
+ BUILTIN(areawarp, "MxyxyMxy"_s, '\0'),
+ BUILTIN(heal, "ii?"_s, '\0'),
+ BUILTIN(input, "N"_s, '\0'),
+ BUILTIN(if, "iF*"_s, '\0'),
+ BUILTIN(set, "Ne"_s, '\0'),
+ BUILTIN(setarray, "Ne*"_s, '\0'),
+ BUILTIN(cleararray, "Nei"_s, '\0'),
+ BUILTIN(getarraysize, "N"_s, 'i'),
+ BUILTIN(getelementofarray, "Ni"_s, '.'),
+ BUILTIN(setlook, "ii"_s, '\0'),
+ BUILTIN(countitem, "I"_s, 'i'),
+ BUILTIN(checkweight, "Ii"_s, 'i'),
+ BUILTIN(getitem, "Ii??"_s, '\0'),
+ BUILTIN(makeitem, "IiMxy"_s, '\0'),
+ BUILTIN(delitem, "Ii"_s, '\0'),
+ BUILTIN(getcharid, "i?"_s, 'i'),
+ BUILTIN(strcharinfo, "i"_s, 's'),
+ BUILTIN(getequipid, "i"_s, 'i'),
+ BUILTIN(getequipname, "i"_s, 's'),
+ BUILTIN(bonus, "ii"_s, '\0'),
+ BUILTIN(bonus2, "iii"_s, '\0'),
+ BUILTIN(skill, "ii?"_s, '\0'),
+ BUILTIN(setskill, "ii"_s, '\0'),
+ BUILTIN(getskilllv, "i"_s, 'i'),
+ BUILTIN(getgmlevel, ""_s, 'i'),
+ BUILTIN(end, ""_s, '\0'),
+ BUILTIN(getopt2, ""_s, 'i'),
+ BUILTIN(setopt2, "i"_s, '\0'),
+ BUILTIN(savepoint, "Mxy"_s, '\0'),
+ BUILTIN(gettimetick, "i"_s, 'i'),
+ BUILTIN(gettime, "i"_s, 'i'),
+ BUILTIN(openstorage, ""_s, '\0'),
+ BUILTIN(getexp, "ii"_s, '\0'),
+ BUILTIN(monster, "Mxysmi?"_s, '\0'),
+ BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'),
+ BUILTIN(killmonster, "ME"_s, '\0'),
+ BUILTIN(donpcevent, "E"_s, '\0'),
+ BUILTIN(addtimer, "tE"_s, '\0'),
+ BUILTIN(initnpctimer, "?"_s, '\0'),
+ BUILTIN(startnpctimer, "?"_s, '\0'),
+ BUILTIN(stopnpctimer, "?"_s, '\0'),
+ BUILTIN(getnpctimer, "i?"_s, 'i'),
+ BUILTIN(setnpctimer, "i?"_s, '\0'),
+ BUILTIN(announce, "si"_s, '\0'),
+ BUILTIN(mapannounce, "Msi"_s, '\0'),
+ BUILTIN(getusers, "i"_s, 'i'),
+ BUILTIN(getmapusers, "M"_s, 'i'),
+ BUILTIN(getareausers, "Mxyxy?"_s, 'i'),
+ BUILTIN(getareadropitem, "Mxyxyi?"_s, 'i'),
+ BUILTIN(enablenpc, "s"_s, '\0'),
+ BUILTIN(disablenpc, "s"_s, '\0'),
+ BUILTIN(sc_start, "iTi?"_s, '\0'),
+ BUILTIN(sc_end, "i"_s, '\0'),
+ BUILTIN(sc_check, "i"_s, 'i'),
+ BUILTIN(debugmes, "s"_s, '\0'),
+ BUILTIN(wgm, "s"_s, '\0'),
+ BUILTIN(gmlog, "s"_s, '\0'),
+ BUILTIN(resetstatus, ""_s, '\0'),
+ BUILTIN(attachrid, "i"_s, 'i'),
+ BUILTIN(detachrid, ""_s, '\0'),
+ BUILTIN(isloggedin, "i"_s, 'i'),
+ BUILTIN(setmapflag, "Mi"_s, '\0'),
+ BUILTIN(removemapflag, "Mi"_s, '\0'),
+ BUILTIN(getmapflag, "Mi"_s, 'i'),
+ BUILTIN(pvpon, "M"_s, '\0'),
+ BUILTIN(pvpoff, "M"_s, '\0'),
+ BUILTIN(setpvpchannel, "i"_s, '\0'),
+ BUILTIN(getpvpflag, "i"_s, 'i'),
+ BUILTIN(emotion, "i?"_s, '\0'),
+ BUILTIN(mapwarp, "MMxy"_s, '\0'),
+ BUILTIN(mobcount, "ME"_s, 'i'),
+ BUILTIN(marriage, "P"_s, 'i'),
+ BUILTIN(divorce, ""_s, 'i'),
+ BUILTIN(getitemname, "I"_s, 's'),
+ BUILTIN(getitemlink, "I"_s, 's'),
+ BUILTIN(getspellinvocation, "s"_s, 's'),
+ BUILTIN(getpartnerid2, ""_s, 'i'),
+ BUILTIN(getinventorylist, ""_s, '\0'),
+ BUILTIN(getactivatedpoolskilllist, ""_s, '\0'),
+ BUILTIN(getunactivatedpoolskilllist, ""_s, '\0'),
+ BUILTIN(poolskill, "i"_s, '\0'),
+ BUILTIN(unpoolskill, "i"_s, '\0'),
+ BUILTIN(misceffect, "i?"_s, '\0'),
+ BUILTIN(specialeffect, "i"_s, '\0'),
+ BUILTIN(specialeffect2, "i"_s, '\0'),
+ BUILTIN(nude, ""_s, '\0'),
+ BUILTIN(unequipbyid, "i"_s, '\0'),
+ BUILTIN(npcwarp, "xys"_s, '\0'),
+ BUILTIN(npcareawarp, "xyxyis"_s, '\0'),
+ BUILTIN(message, "Ps"_s, '\0'),
+ BUILTIN(npctalk, "s"_s, '\0'),
+ BUILTIN(getlook, "i"_s, 'i'),
+ BUILTIN(getsavepoint, "i"_s, '.'),
+ BUILTIN(areatimer, "MxyxytE"_s, '\0'),
+ BUILTIN(isin, "Mxyxy"_s, 'i'),
+ BUILTIN(iscollision, "Mxy"_s, 'i'),
+ BUILTIN(shop, "s"_s, '\0'),
+ BUILTIN(isdead, ""_s, 'i'),
+ BUILTIN(fakenpcname, "ssi"_s, '\0'),
+ BUILTIN(getx, ""_s, 'i'),
+ BUILTIN(gety, ""_s, 'i'),
+ BUILTIN(getnpcx, "?"_s, 'i'),
+ BUILTIN(getnpcy, "?"_s, 'i'),
+ BUILTIN(strnpcinfo, "i?"_s, 's'),
+ BUILTIN(getmap, ""_s, 's'),
+ BUILTIN(mapexit, ""_s, '\0'),
+ BUILTIN(freeloop, "i"_s, '\0'),
+ {nullptr, ""_s, ""_s, '\0'},
+};
+} // namespace map
+} // namespace tmwa
diff --git a/src/sexpr/variant.cpp b/src/map/script-fun.hpp
index b1f500a..81d68fe 100644
--- a/src/sexpr/variant.cpp
+++ b/src/map/script-fun.hpp
@@ -1,7 +1,9 @@
-#include "variant.hpp"
-// variant.cpp - Just include the header file.
+#pragma once
+// script-fun.hpp - EAthena script frontend, engine, and library.
//
-// Copyright © 2012 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -18,23 +20,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "fwd.hpp"
+#include "../strings/literal.hpp"
namespace tmwa
{
-namespace sexpr
+namespace map
{
-namespace
+struct BuiltinFunction
{
- struct Foo
- {
- Foo() {}
- ~Foo() {}
- Foo(Foo&&) {}
- Foo& operator = (Foo&&) { return *this; }
- };
-} // anonymous namespace
- static Variant<int, Foo> v;
-} // namespace sexpr
+ void (*func)(ScriptState *);
+ LString name;
+ LString arg;
+ char ret;
+};
+
+extern BuiltinFunction builtin_functions[];
+} // namespace map
} // namespace tmwa
diff --git a/src/map/script-parse-internal.hpp b/src/map/script-parse-internal.hpp
new file mode 100644
index 0000000..ddaeef0
--- /dev/null
+++ b/src/map/script-parse-internal.hpp
@@ -0,0 +1,67 @@
+#pragma once
+// script-parse-internal.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "script-parse.hpp"
+#include "fwd.hpp"
+
+#include "../strings/rstring.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+enum class StringCode : uint8_t
+{
+ NOP, POS, INT, PARAM, FUNC,
+ VARIABLE,
+};
+enum class ByteCode : uint8_t
+{
+ // types and specials
+ // Note that 'INT' is synthetic, and does not occur in the data stream
+ NOP, POS, INT, PARAM, FUNC, STR, ARG,
+ VARIABLE, EOL,
+
+ // unary and binary operators
+ LOR, LAND, LE, LT, GE, GT, EQ, NE,
+ XOR, OR, AND, ADD, SUB, MUL, DIV, MOD,
+ NEG, LNOT, NOT, R_SHIFT, L_SHIFT,
+
+ // additions
+ // needed because FUNC is used for the actual call
+ FUNC_REF,
+};
+
+struct str_data_t
+{
+ StringCode type;
+ RString strs;
+ int backpatch;
+ int label_;
+ int val;
+};
+
+Option<Borrowed<str_data_t>> search_strp(XString p);
+Borrowed<str_data_t> add_strp(XString p);
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/script-parse.cpp b/src/map/script-parse.cpp
new file mode 100644
index 0000000..fb306c5
--- /dev/null
+++ b/src/map/script-parse.cpp
@@ -0,0 +1,851 @@
+#include "script-parse-internal.hpp"
+// script-parse.cpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011 Chuck Miller
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2013 wushin
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <set>
+
+#include "../generic/array.hpp"
+#include "../generic/db.hpp"
+#include "../generic/intern-pool.hpp"
+
+#include "../strings/rstring.hpp"
+
+#include "../io/cxxstdio.hpp"
+
+#include "../mmo/cxxstdio_enums.hpp"
+
+#include "../ast/script.hpp"
+
+#include "globals.hpp"
+#include "map.t.hpp"
+#include "script-buffer.hpp"
+#include "script-call.hpp"
+#include "script-fun.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+constexpr bool DEBUG_DISP = false;
+
+class ScriptBuffer
+{
+ typedef ZString::iterator ZSit;
+
+ std::vector<ByteCode> script_buf;
+ RString debug_name;
+ std::vector<std::pair<ScriptLabel, size_t>> debug_labels;
+public:
+ ScriptBuffer(RString name) : debug_name(std::move(name)) {}
+
+ // construction methods
+ void add_scriptc(ByteCode a);
+ void add_scriptb(uint8_t a);
+ void add_scripti(uint32_t a);
+ void add_scriptl(Borrowed<str_data_t> a);
+ void set_label(Borrowed<str_data_t> ld, int pos_);
+ ZSit parse_simpleexpr(ZSit p);
+ ZSit parse_subexpr(ZSit p, int limit);
+ ZSit parse_expr(ZSit p);
+ ZSit parse_line(ZSit p, bool *canstep);
+ void parse_script(ZString src, int line, bool implicit_end);
+
+ // consumption methods
+ ByteCode operator[](size_t i) const { return script_buf[i]; }
+ ZString get_str(size_t i) const
+ {
+ return ZString(strings::really_construct_from_a_pointer, reinterpret_cast<const char *>(&script_buf[i]), nullptr);
+ }
+};
+} // namespace map
+} // namespace tmwa
+
+void std::default_delete<const tmwa::map::ScriptBuffer>::operator()(const tmwa::map::ScriptBuffer *sd)
+{
+ really_delete1 sd;
+}
+
+namespace tmwa
+{
+namespace map
+{
+// implemented for script-call.hpp because reasons
+ByteCode ScriptPointer::peek() const { return (*TRY_UNWRAP(code, abort()))[pos]; }
+ByteCode ScriptPointer::pop() { return (*TRY_UNWRAP(code, abort()))[pos++]; }
+ZString ScriptPointer::pops()
+{
+ ZString rv = TRY_UNWRAP(code, abort())->get_str(pos);
+ pos += rv.size();
+ ++pos;
+ return rv;
+}
+
+static
+struct ScriptConfigParse
+{
+ static const
+ int warn_func_no_comma = 1;
+ static const
+ int warn_cmd_no_comma = 1;
+ static const
+ int warn_func_mismatch_paramnum = 1;
+ static const
+ int warn_cmd_mismatch_paramnum = 1;
+} script_config;
+
+
+Option<Borrowed<str_data_t>> search_strp(XString p)
+{
+ return str_datam.search(p);
+}
+
+Borrowed<str_data_t> add_strp(XString p)
+{
+ Option<P<str_data_t>> rv_ = search_strp(p);
+ OMATCH_BEGIN_SOME (rv, rv_)
+ {
+ return rv;
+ }
+ OMATCH_END ();
+
+ RString p2 = p;
+ P<str_data_t> datum = str_datam.init(p2);
+ datum->type = StringCode::NOP;
+ datum->strs = p2;
+ datum->backpatch = -1;
+ datum->label_ = -1;
+ return datum;
+}
+
+/*==========================================
+ * スクリプトバッファに1バイト書き込む
+ *------------------------------------------
+ */
+void ScriptBuffer::add_scriptc(ByteCode a)
+{
+ script_buf.push_back(a);
+}
+
+/*==========================================
+ * スクリプトバッファにデータタイプを書き込む
+ *------------------------------------------
+ */
+void ScriptBuffer::add_scriptb(uint8_t a)
+{
+ add_scriptc(static_cast<ByteCode>(a));
+}
+
+/*==========================================
+ * スクリプトバッファに整数を書き込む
+ *------------------------------------------
+ */
+void ScriptBuffer::add_scripti(uint32_t a)
+{
+ while (a >= 0x40)
+ {
+ add_scriptb(a | 0xc0);
+ a = (a - 0x40) >> 6;
+ }
+ add_scriptb(a | 0x80);
+}
+
+/*==========================================
+ * スクリプトバッファにラベル/変数/関数を書き込む
+ *------------------------------------------
+ */
+// 最大16Mまで
+void ScriptBuffer::add_scriptl(P<str_data_t> ld)
+{
+ int backpatch = ld->backpatch;
+
+ switch (ld->type)
+ {
+ case StringCode::POS:
+ add_scriptc(ByteCode::POS);
+ add_scriptb(static_cast<uint8_t>(ld->label_));
+ add_scriptb(static_cast<uint8_t>(ld->label_ >> 8));
+ add_scriptb(static_cast<uint8_t>(ld->label_ >> 16));
+ break;
+ case StringCode::NOP:
+ // need to set backpatch, because it might become a label later
+ add_scriptc(ByteCode::VARIABLE);
+ ld->backpatch = script_buf.size();
+ add_scriptb(static_cast<uint8_t>(backpatch));
+ add_scriptb(static_cast<uint8_t>(backpatch >> 8));
+ add_scriptb(static_cast<uint8_t>(backpatch >> 16));
+ break;
+ case StringCode::INT:
+ add_scripti(ld->val);
+ break;
+ case StringCode::FUNC:
+ add_scriptc(ByteCode::FUNC_REF);
+ add_scriptb(static_cast<uint8_t>(ld->val));
+ add_scriptb(static_cast<uint8_t>(ld->val >> 8));
+ add_scriptb(static_cast<uint8_t>(ld->val >> 16));
+ break;
+ case StringCode::PARAM:
+ add_scriptc(ByteCode::PARAM);
+ add_scriptb(static_cast<uint8_t>(ld->val));
+ add_scriptb(static_cast<uint8_t>(ld->val >> 8));
+ add_scriptb(static_cast<uint8_t>(ld->val >> 16));
+ break;
+ default:
+ abort();
+ }
+}
+
+/*==========================================
+ * ラベルを解決する
+ *------------------------------------------
+ */
+void ScriptBuffer::set_label(Borrowed<str_data_t> ld, int pos_)
+{
+ int next;
+
+ ld->type = StringCode::POS;
+ ld->label_ = pos_;
+ for (int i = ld->backpatch; i >= 0 && i != 0x00ffffff; i = next)
+ {
+ next = 0;
+ // woot! no longer endian-dependent!
+ next |= static_cast<uint8_t>(script_buf[i + 0]) << 0;
+ next |= static_cast<uint8_t>(script_buf[i + 1]) << 8;
+ next |= static_cast<uint8_t>(script_buf[i + 2]) << 16;
+ script_buf[i - 1] = ByteCode::POS;
+ script_buf[i] = static_cast<ByteCode>(pos_);
+ script_buf[i + 1] = static_cast<ByteCode>(pos_ >> 8);
+ script_buf[i + 2] = static_cast<ByteCode>(pos_ >> 16);
+ }
+}
+
+/*==========================================
+ * スペース/コメント読み飛ばし
+ *------------------------------------------
+ */
+static
+ZString::iterator skip_space(ZString::iterator p)
+{
+ while (1)
+ {
+ while (isspace(*p))
+ p++;
+ if (p[0] == '/' && p[1] == '/')
+ {
+ while (*p && *p != '\n')
+ p++;
+ }
+ else if (p[0] == '/' && p[1] == '*')
+ {
+ p++;
+ while (*p && (p[-1] != '*' || p[0] != '/'))
+ p++;
+ if (*p)
+ p++;
+ }
+ else
+ break;
+ }
+ return p;
+}
+
+/*==========================================
+ * 1単語スキップ
+ *------------------------------------------
+ */
+static
+ZString::iterator skip_word(ZString::iterator p)
+{
+ // prefix
+ if (*p == '$')
+ p++; // MAP鯖内共有変数用
+ if (*p == '@')
+ p++; // 一時的変数用(like weiss)
+ if (*p == '#')
+ p++; // account変数用
+ if (*p == '#')
+ p++; // ワールドaccount変数用
+
+ while (isalnum(*p) || *p == '_')
+ p++;
+
+ // postfix
+ if (*p == '$')
+ p++; // 文字列変数
+
+ return p;
+}
+
+/*==========================================
+ * エラーメッセージ出力
+ *------------------------------------------
+ */
+static
+void disp_error_message(ZString mes, ZString::iterator pos_)
+{
+ script_errors++;
+
+ assert (startptr.begin() <= pos_ && pos_ <= startptr.end());
+
+ int line;
+ ZString::iterator p;
+
+ for (line = startline, p = startptr.begin(); p != startptr.end(); line++)
+ {
+ ZString::iterator linestart = p;
+ ZString::iterator lineend = std::find(p, startptr.end(), '\n');
+ if (pos_ < lineend)
+ {
+ PRINTF("\n%s\nline %d : "_fmt, mes, line);
+ for (int i = 0; linestart + i != lineend; i++)
+ {
+ if (linestart + i != pos_)
+ PRINTF("%c"_fmt, linestart[i]);
+ else
+ PRINTF("\'%c\'"_fmt, linestart[i]);
+ }
+ PRINTF("\a\n"_fmt);
+ return;
+ }
+ p = lineend + 1;
+ }
+}
+
+/*==========================================
+ * 項の解析
+ *------------------------------------------
+ */
+ZString::iterator ScriptBuffer::parse_simpleexpr(ZString::iterator p)
+{
+ p = skip_space(p);
+
+ if (*p == ';' || *p == ',')
+ {
+ disp_error_message("unexpected expr end"_s, p);
+ exit(1);
+ }
+ if (*p == '(')
+ {
+
+ p = parse_subexpr(p + 1, -1);
+ p = skip_space(p);
+ if ((*p++) != ')')
+ {
+ disp_error_message("unmatch ')'"_s, p);
+ exit(1);
+ }
+ }
+ else if (isdigit(*p) || ((*p == '-' || *p == '+') && isdigit(p[1])))
+ {
+ char *np;
+ int i = strtoul(&*p, &np, 0);
+ add_scripti(i);
+ p += np - &*p;
+ }
+ else if (*p == '"')
+ {
+ add_scriptc(ByteCode::STR);
+ p++;
+ while (*p && *p != '"')
+ {
+ if (*p == '\\')
+ p++;
+ else if (*p == '\n')
+ {
+ disp_error_message("unexpected newline @ string"_s, p);
+ exit(1);
+ }
+ add_scriptb(*p++);
+ }
+ if (!*p)
+ {
+ disp_error_message("unexpected eof @ string"_s, p);
+ exit(1);
+ }
+ add_scriptb(0);
+ p++; //'"'
+ }
+ else
+ {
+ // label , register , function etc
+ ZString::iterator p2 = skip_word(p);
+ if (p2 == p)
+ {
+ disp_error_message("unexpected character"_s, p);
+ exit(1);
+ }
+ XString word(&*p, &*p2, nullptr);
+ if (word.startswith("On"_s) || word.startswith("L_"_s) || word.startswith("S_"_s))
+ probable_labels.insert(stringish<ScriptLabel>(word));
+ if (parse_cmd_if && (word == "callsub"_s || word == "callfunc"_s || word == "return"_s))
+ {
+ disp_error_message("Sorry, callsub/callfunc/return have never worked properly in an if statement."_s, p);
+ }
+ P<str_data_t> ld = add_strp(word);
+
+ parse_cmdp = Some(ld); // warn_*_mismatch_paramnumのために必要
+ // why not just check l->str == "if"_s or std::string(p, p2) == "if"_s?
+ if (Some(ld) == search_strp("if"_s)) // warn_cmd_no_commaのために必要
+ parse_cmd_if++;
+ p = p2;
+
+ if (ld->type != StringCode::FUNC && *p == '[')
+ {
+ // array(name[i] => getelementofarray(name,i) )
+ add_scriptl(TRY_UNWRAP(search_strp("getelementofarray"_s), abort()));
+ add_scriptc(ByteCode::ARG);
+ add_scriptl(ld);
+ p = parse_subexpr(p + 1, -1);
+ p = skip_space(p);
+ if (*p != ']')
+ {
+ disp_error_message("unmatch ']'"_s, p);
+ exit(1);
+ }
+ p++;
+ add_scriptc(ByteCode::FUNC);
+ }
+ else
+ add_scriptl(ld);
+
+ }
+
+ return p;
+}
+
+/*==========================================
+ * 式の解析
+ *------------------------------------------
+ */
+ZString::iterator ScriptBuffer::parse_subexpr(ZString::iterator p, int limit)
+{
+ ByteCode op;
+ int opl, len;
+
+ p = skip_space(p);
+
+ if (*p == '-')
+ {
+ ZString::iterator tmpp = skip_space(p + 1);
+ if (*tmpp == ';' || *tmpp == ',')
+ {
+ --script_errors; disp_error_message("deprecated: implicit 'next statement' label"_s, p);
+ add_scriptl(borrow(LABEL_NEXTLINE_));
+ p++;
+ return p;
+ }
+ }
+ ZString::iterator tmpp = p;
+ if ((op = ByteCode::NEG, *p == '-') || (op = ByteCode::LNOT, *p == '!')
+ || (op = ByteCode::NOT, *p == '~'))
+ {
+ p = parse_subexpr(p + 1, 100);
+ add_scriptc(op);
+ }
+ else
+ p = parse_simpleexpr(p);
+ p = skip_space(p);
+ while (((op = ByteCode::ADD, opl = 6, len = 1, *p == '+') ||
+ (op = ByteCode::SUB, opl = 6, len = 1, *p == '-') ||
+ (op = ByteCode::MUL, opl = 7, len = 1, *p == '*') ||
+ (op = ByteCode::DIV, opl = 7, len = 1, *p == '/') ||
+ (op = ByteCode::MOD, opl = 7, len = 1, *p == '%') ||
+ (op = ByteCode::FUNC, opl = 8, len = 1, *p == '(') ||
+ (op = ByteCode::LAND, opl = 1, len = 2, *p == '&' && p[1] == '&') ||
+ (op = ByteCode::AND, opl = 5, len = 1, *p == '&') ||
+ (op = ByteCode::LOR, opl = 0, len = 2, *p == '|' && p[1] == '|') ||
+ (op = ByteCode::OR, opl = 4, len = 1, *p == '|') ||
+ (op = ByteCode::XOR, opl = 3, len = 1, *p == '^') ||
+ (op = ByteCode::EQ, opl = 2, len = 2, *p == '=' && p[1] == '=') ||
+ (op = ByteCode::NE, opl = 2, len = 2, *p == '!' && p[1] == '=') ||
+ (op = ByteCode::R_SHIFT, opl = 5, len = 2, *p == '>' && p[1] == '>') ||
+ (op = ByteCode::GE, opl = 2, len = 2, *p == '>' && p[1] == '=') ||
+ (op = ByteCode::GT, opl = 2, len = 1, *p == '>') ||
+ (op = ByteCode::L_SHIFT, opl = 5, len = 2, *p == '<' && p[1] == '<') ||
+ (op = ByteCode::LE, opl = 2, len = 2, *p == '<' && p[1] == '=') ||
+ (op = ByteCode::LT, opl = 2, len = 1, *p == '<')) && opl > limit)
+ {
+ p += len;
+ if (op == ByteCode::FUNC)
+ {
+ int i = 0;
+ P<str_data_t> funcp = TRY_UNWRAP(parse_cmdp, abort());
+ Array<ZString::iterator, 128> plist;
+
+ if (funcp->type != StringCode::FUNC)
+ {
+ disp_error_message("expect function"_s, tmpp);
+ exit(0);
+ }
+
+ add_scriptc(ByteCode::ARG);
+ while (*p && *p != ')' && i < 128)
+ {
+ plist[i] = p;
+ p = parse_subexpr(p, -1);
+ p = skip_space(p);
+ if (*p == ',')
+ p++;
+ else if (*p != ')' && script_config.warn_func_no_comma)
+ {
+ disp_error_message("expect ',' or ')' at func params"_s,
+ p);
+ }
+ p = skip_space(p);
+ i++;
+ }
+ if (i == 128)
+ {
+ disp_error_message("PANIC: unrecoverable error in function argument list"_s, p);
+ abort();
+ }
+ plist[i] = p;
+ if (*p != ')')
+ {
+ disp_error_message("func request '(' ')'"_s, p);
+ exit(1);
+ }
+ p++;
+
+ if (funcp->type == StringCode::FUNC
+ && script_config.warn_func_mismatch_paramnum)
+ {
+ ZString arg = builtin_functions[funcp->val].arg;
+ int j = 0;
+ // TODO handle ? and multiple * correctly
+ for (j = 0; arg[j]; j++)
+ if (arg[j] == '*' || arg[j] == '?')
+ break;
+ if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j))
+ {
+ disp_error_message("illegal number of parameters"_s,
+ plist[std::min(i, j)]);
+ }
+ if (!builtin_functions[funcp->val].ret)
+ {
+ disp_error_message("statement in function context"_s, tmpp);
+ }
+ }
+ }
+ else // not op == ByteCode::FUNC
+ {
+ p = parse_subexpr(p, opl);
+ }
+ add_scriptc(op);
+ p = skip_space(p);
+ }
+ return p; /* return first untreated operator */
+}
+
+/*==========================================
+ * 式の評価
+ *------------------------------------------
+ */
+ZString::iterator ScriptBuffer::parse_expr(ZString::iterator p)
+{
+ switch (*p)
+ {
+ case ')':
+ case ';':
+ case ':':
+ case '[':
+ case ']':
+ case '}':
+ disp_error_message("unexpected char"_s, p);
+ exit(1);
+ }
+ p = parse_subexpr(p, -1);
+ return p;
+}
+
+/*==========================================
+ * 行の解析
+ *------------------------------------------
+ */
+ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step)
+{
+ int i = 0;
+ Array<ZString::iterator, 128> plist;
+
+ p = skip_space(p);
+ if (*p == ';')
+ {
+ disp_error_message("Double semi-colon"_s, p);
+ ++p;
+ return p;
+ }
+
+ parse_cmd_if = 0; // warn_cmd_no_commaのために必要
+
+ // 最初は関数名
+ ZString::iterator p2 = p;
+ p = parse_simpleexpr(p);
+ p = skip_space(p);
+
+ P<str_data_t> cmd = TRY_UNWRAP(parse_cmdp, abort());
+ if (cmd->type != StringCode::FUNC)
+ {
+ disp_error_message("expect command"_s, p2);
+ }
+
+ {
+ // TODO should be LString, but no heterogenous lookup yet
+ static
+ std::set<ZString> terminators =
+ {
+ "goto"_s,
+ "return"_s,
+ "close"_s,
+ "menu"_s,
+ "end"_s,
+ "mapexit"_s,
+ "shop"_s,
+ };
+ *can_step = terminators.count(cmd->strs) == 0;
+ }
+
+ add_scriptc(ByteCode::ARG);
+ while (*p && *p != ';' && i < 128)
+ {
+ plist[i] = p;
+
+ p = parse_expr(p);
+ p = skip_space(p);
+ // 引数区切りの,処理
+ if (*p == ',')
+ p++;
+ else if (*p != ';' && script_config.warn_cmd_no_comma
+ && parse_cmd_if * 2 <= i)
+ {
+ disp_error_message("expect ',' or ';' at cmd params"_s, p);
+ }
+ p = skip_space(p);
+ i++;
+ }
+ if (i == 128)
+ {
+ disp_error_message("PANIC: unknown error in command argument list"_s, p);
+ abort();
+ }
+ plist[i] = p;
+ if (*(p++) != ';')
+ {
+ disp_error_message("need ';'"_s, p);
+ exit(1);
+ }
+ add_scriptc(ByteCode::FUNC);
+
+ if (cmd->type == StringCode::FUNC
+ && script_config.warn_cmd_mismatch_paramnum)
+ {
+ ZString arg = builtin_functions[cmd->val].arg;
+ int j = 0;
+ // TODO see above
+ for (j = 0; arg[j]; j++)
+ if (arg[j] == '*' || arg[j] == '?')
+ break;
+ if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j))
+ {
+ disp_error_message("illegal number of parameters"_s,
+ plist[std::min(i, j)]);
+ }
+ if (builtin_functions[cmd->val].ret)
+ {
+ disp_error_message("function in statement context"_s, p2);
+ }
+ }
+
+ return p;
+}
+
+/*==========================================
+ * 組み込み関数の追加
+ *------------------------------------------
+ */
+static
+void add_builtin_functions(void)
+{
+ for (int i = 0; builtin_functions[i].func; i++)
+ {
+ P<str_data_t> n = add_strp(builtin_functions[i].name);
+ n->type = StringCode::FUNC;
+ n->val = i;
+ }
+}
+
+std::unique_ptr<const ScriptBuffer> compile_script(RString debug_name, const ast::script::ScriptBody& body, bool implicit_end)
+{
+ auto script_buf = make_unique<ScriptBuffer>(std::move(debug_name));
+ script_buf->parse_script(body.braced_body, body.span.begin.line, implicit_end);
+ return std::move(script_buf);
+}
+
+/*==========================================
+ * スクリプトの解析
+ *------------------------------------------
+ */
+void ScriptBuffer::parse_script(ZString src, int line, bool implicit_end)
+{
+ static int first = 1;
+
+ if (first)
+ {
+ add_builtin_functions();
+ }
+ first = 0;
+ LABEL_NEXTLINE_.type = StringCode::NOP;
+ LABEL_NEXTLINE_.backpatch = -1;
+ LABEL_NEXTLINE_.label_ = -1;
+ for (auto& pair : str_datam)
+ {
+ str_data_t& dit = pair.second;
+ if (dit.type == StringCode::POS || dit.type == StringCode::VARIABLE)
+ {
+ dit.type = StringCode::NOP;
+ dit.backpatch = -1;
+ dit.label_ = -1;
+ }
+ }
+
+ // 外部用label dbの初期化
+ scriptlabel_db.clear();
+
+ // for error message
+ startptr = src;
+ startline = line;
+
+ bool can_step = true;
+
+ ZString::iterator p = src.begin();
+ p = skip_space(p);
+ if (*p != '{')
+ {
+ disp_error_message("not found '{'"_s, p);
+ abort();
+ }
+ for (p++; *p && *p != '}';)
+ {
+ p = skip_space(p);
+ if (*skip_space(skip_word(p)) == ':')
+ {
+ if (can_step)
+ {
+ --script_errors; disp_error_message("deprecated: implicit fallthrough"_s, p);
+ }
+ can_step = true;
+
+ ZString::iterator tmpp = skip_word(p);
+ XString str(&*p, &*tmpp, nullptr);
+ P<str_data_t> ld = add_strp(str);
+ bool e1 = ld->type != StringCode::NOP;
+ bool e2 = ld->type == StringCode::POS;
+ bool e3 = ld->label_ != -1;
+ assert (e1 == e2 && e2 == e3);
+ if (e3)
+ {
+ disp_error_message("dup label "_s, p);
+ exit(1);
+ }
+ set_label(ld, script_buf.size());
+ scriptlabel_db.insert(stringish<ScriptLabel>(str), script_buf.size());
+ debug_labels.push_back(std::make_pair(stringish<ScriptLabel>(str), script_buf.size()));
+ p = tmpp + 1;
+ continue;
+ }
+
+ if (!can_step)
+ {
+ --script_errors; disp_error_message("deprecated: unreachable statement"_s, p);
+ }
+ // 他は全部一緒くた
+ p = parse_line(p, &can_step);
+ p = skip_space(p);
+ add_scriptc(ByteCode::EOL);
+
+ set_label(borrow(LABEL_NEXTLINE_), script_buf.size());
+ LABEL_NEXTLINE_.type = StringCode::NOP;
+ LABEL_NEXTLINE_.backpatch = -1;
+ LABEL_NEXTLINE_.label_ = -1;
+ }
+
+ if (can_step && !implicit_end)
+ {
+ --script_errors; disp_error_message("deprecated: implicit end"_s, p);
+ }
+ add_scriptc(ByteCode::NOP);
+
+ // resolve the unknown labels
+ for (auto& pair : str_datam)
+ {
+ str_data_t& sit = pair.second;
+ if (sit.type == StringCode::NOP)
+ {
+ sit.type = StringCode::VARIABLE;
+ sit.label_ = 0; // anything but -1. Shouldn't matter, but helps asserts.
+ size_t pool_index = variable_names.intern(sit.strs);
+ for (int next, j = sit.backpatch; j >= 0 && j != 0x00ffffff; j = next)
+ {
+ next = 0;
+ next |= static_cast<uint8_t>(script_buf[j + 0]) << 0;
+ next |= static_cast<uint8_t>(script_buf[j + 1]) << 8;
+ next |= static_cast<uint8_t>(script_buf[j + 2]) << 16;
+ script_buf[j] = static_cast<ByteCode>(pool_index);
+ script_buf[j + 1] = static_cast<ByteCode>(pool_index >> 8);
+ script_buf[j + 2] = static_cast<ByteCode>(pool_index >> 16);
+ }
+ }
+ }
+
+ for (const auto& pair : scriptlabel_db)
+ {
+ ScriptLabel key = pair.first;
+ if (key.startswith("On"_s))
+ continue;
+ if (!(key.startswith("L_"_s) || key.startswith("S_"_s)))
+ PRINTF("Warning: ugly label: %s\n"_fmt, key);
+ else if (!probable_labels.count(key))
+ PRINTF("Warning: unused label: %s\n"_fmt, key);
+ }
+ for (ScriptLabel used : probable_labels)
+ {
+ if (scriptlabel_db.search(used).is_none())
+ PRINTF("Warning: no such label: %s\n"_fmt, used);
+ }
+ probable_labels.clear();
+
+ if (!DEBUG_DISP)
+ return;
+ for (size_t i = 0; i < script_buf.size(); i++)
+ {
+ if ((i & 15) == 0)
+ PRINTF("%04zx : "_fmt, i);
+ PRINTF("%02x "_fmt, script_buf[i]);
+ if ((i & 15) == 15)
+ PRINTF("\n"_fmt);
+ }
+ PRINTF("\n"_fmt);
+}
+} // namespace map
+} // namespace tmwa
diff --git a/src/tests/test.cpp b/src/map/script-parse.hpp
index d731d7c..e08c470 100644
--- a/src/tests/test.cpp
+++ b/src/map/script-parse.hpp
@@ -1,6 +1,9 @@
-// test.cpp - Driver for testwuite
+#pragma once
+// script-parse.hpp - EAthena script frontend, engine, and library.
//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -17,17 +20,17 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include <gtest/gtest.h>
+#include "fwd.hpp"
-#include "../poison.hpp"
+#include <memory>
+
+#include "script-buffer.hpp"
namespace tmwa
{
-} // namespace tmwa
-
-int main(int argc, char **argv)
+namespace map
{
- ::testing::InitGoogleTest(&argc, argv);
- return RUN_ALL_TESTS();
-}
+std::unique_ptr<const ScriptBuffer> compile_script(RString debug_name, const ast::script::ScriptBody& body, bool implicit_end);
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/script-parse.py b/src/map/script-parse.py
new file mode 100644
index 0000000..0309f54
--- /dev/null
+++ b/src/map/script-parse.py
@@ -0,0 +1,553 @@
+class ScriptBuffer(object):
+ __slots__ = ('_value')
+ name = 'tmwa::map::ScriptBuffer'
+ enabled = True
+
+ def __init__(self, value):
+ self._value = value
+
+ def to_string(self):
+ return self._value['debug_name']
+
+ def get_com(self, b, i, r, labels_dict):
+ # see script-parse-internal.hpp:ByteCode and script-call.cpp:get_com
+ ops = '''
+ NOP, POS, INT, PARAM, FUNC, STR, ARG,
+ VARIABLE, EOL,
+ LOR, LAND, LE, LT, GE, GT, EQ, NE,
+ XOR, OR, AND, ADD, SUB, MUL, DIV, MOD,
+ NEG, LNOT, NOT, R_SHIFT, L_SHIFT,
+ FUNC_REF,
+ '''.replace(',', '').split()
+ ci = int(b[i])
+ if ci >= 0x80:
+ rv = 0
+ sh = 0
+ # Because of how the python iterator up a frame consumed the i already,
+ # have to manually unroll the first iteration of the C loop.
+ # TODO itertools.chain([i], r) or something?
+ if True:
+ if True:
+ rv += (ci & 0x7f) << sh
+ sh += 6
+ if ci >= 0xc0:
+ while True:
+ i = next(r)
+ ci = int(b[i])
+
+ rv += (ci & 0x7f) << sh
+ sh += 6
+ if not (ci >= 0xc0):
+ break
+ return 'INT %d' % rv
+
+ cs = ops[ci]
+ if cs in {'POS', 'VARIABLE', 'FUNC_REF', 'PARAM'}:
+ ai = 0
+ ai |= (int(b[next(r)]) << 0)
+ ai |= (int(b[next(r)]) << 8)
+ ai |= (int(b[next(r)]) << 16)
+ if cs == 'POS':
+ ln = labels_dict.get(ai)
+ if ln is not None:
+ return 'POS %d %s' % (ai, ln[0])
+ elif cs == 'VARIABLE':
+ global rstring_disable_children
+ rstring_disable_children = True
+ try:
+ rv = 'VARIABLE %s' % gdb.parse_and_eval('tmwa::map::variable_names.names._M_impl._M_start[{ai}]'.format(ai=ai))
+ finally:
+ rstring_disable_children = False
+ return rv
+ elif cs == 'FUNC_REF':
+ return 'FUNC_REF %s' % gdb.parse_and_eval('tmwa::map::builtin_functions[{ai}].name'.format(ai=ai))
+ elif cs == 'PARAM':
+ # https://sourceware.org/bugzilla/show_bug.cgi?id=17568
+ try:
+ # this should work
+ SP = gdb.lookup_type('tmwa::SP')
+ except gdb.error:
+ # this should not work
+ SP = gdb.parse_and_eval('tmwa::SP').type
+ return 'PARAM %s' % gdb.Value(ai).cast(SP)
+ else:
+ assert False
+ return '%s %d' % (cs, ai)
+ elif cs == 'STR':
+ buf = bytearray()
+ while True:
+ i = next(r)
+ ci = int(b[i])
+ if ci == 0:
+ break
+ buf.append(ci)
+ return 'STR "%s"' % str(buf).replace('\\', '\\\\').replace('"', '\\"')
+ elif cs == 'EOL':
+ return cs + '\n'
+ return cs
+
+ def children(self):
+ labels = self._value['debug_labels']
+ labels_begin = labels['_M_impl']['_M_start']
+ labels_end = labels['_M_impl']['_M_finish']
+ labels_size = int(labels_end - labels_begin)
+ labels_list = [labels_begin[i] for i in range(labels_size)]
+ labels_dict = {}
+ char_ptr = gdb.lookup_type('char').pointer()
+ for e in labels_list:
+ offset = int(e['second'])
+ label_name = str(e['first'].address.cast(char_ptr))
+ labels_dict.setdefault(offset, []).append(label_name)
+ code = self._value['script_buf']
+ code_begin = code['_M_impl']['_M_start']
+ code_end = code['_M_impl']['_M_finish']
+ code_size = int(code_end - code_begin)
+ r = iter(range(code_size))
+ for i in r:
+ buf = []
+ for label in labels_dict.get(i, []):
+ if label.startswith('On'):
+ yield 'blah', 'event %s:' % label
+ else:
+ yield 'blah', 'label %s:' % label
+ c = self.get_com(code_begin, i, r, labels_dict)
+ yield 'blah', '%6d: %s' % (i, c)
+
+
+ def display_hint(self):
+ return 'array'
+
+ src = '''
+ {
+ end;
+ S_Sub:
+ return;
+ OnFoo:
+ callsub S_Sub;
+ setarray @a,
+ -1, 0, 1,
+ 0x0, 0x1, 0x2,
+ 0x1, 0x2, 0x3,
+ 0x3, 0x4, 0x5,
+ 0x7, 0x8, 0x9,
+ 0xf, 0x10, 0x11,
+ 0x1f, 0x20, 0x21,
+ 0x3f, 0x40, 0x41,
+ 0x7f, 0x80, 0x81,
+ 0xff, 0x100, 0x101,
+ 0x1ff, 0x200, 0x201,
+ 0x3ff, 0x400, 0x401,
+ 0x7ff, 0x800, 0x801,
+ 0xfff, 0x1000, 0x1001,
+ 0x1fff, 0x2000, 0x2001,
+ 0x3fff, 0x4000, 0x4001,
+ 0x7fff, 0x8000, 0x8001,
+ 0xffff, 0x10000, 0x10001,
+ 0x1ffff, 0x20000, 0x20001,
+ 0x3ffff, 0x40000, 0x40001,
+ 0x7ffff, 0x80000, 0x80001,
+ 0xfffff, 0x100000, 0x100001,
+ 0x1fffff, 0x200000, 0x200001,
+ 0x3fffff, 0x400000, 0x400001,
+ 0x7fffff, 0x800000, 0x800001,
+ 0xffffff, 0x1000000, 0x1000001,
+ 0x1ffffff, 0x2000000, 0x2000001,
+ 0x3ffffff, 0x4000000, 0x4000001,
+ 0x7ffffff, 0x8000000, 0x8000001,
+ 0xfffffff, 0x10000000, 0x10000001,
+ 0x1fffffff, 0x20000000, 0x20000001,
+ 0x3fffffff, 0x40000000, 0x40000001,
+ 0x7fffffff, 0x80000000, 0x80000001,
+ 0xffffffff;
+ set TEST_FAKE_PARAM_BASELEVEL, TEST_FAKE_CONSTANT;
+ set @s$, "hello";
+ set @i, a || b;
+ set @i, a && b;
+ set @i, a <= b;
+ set @i, a < b;
+ set @i, a >= b;
+ set @i, a > b;
+ set @i, a == b;
+ set @i, a != b;
+ set @i, a ^ b;
+ set @i, a | b;
+ set @i, a & b;
+ set @i, a + b;
+ set @i, a - b;
+ set @i, a * b;
+ set @i, a / b;
+ set @i, a % b;
+ set @i, - b;
+ set @i, ! b;
+ set @i, ~ b;
+ set @i, a >> b;
+ set @i, a << b;
+ }
+ '''.replace('\n', ' ')
+
+ asm = ''\
+ +''' 0: FUNC_REF "end",
+ 4: ARG,
+ 5: FUNC,
+ 6: EOL
+ ,
+ label "S_Sub":,
+ 7: FUNC_REF "return",
+ 11: ARG,
+ 12: FUNC,
+ 13: EOL
+ ,
+ label "OnFoo":,
+ 14: FUNC_REF "callsub",
+ 18: ARG,
+ 19: POS 7 "S_Sub",
+ 23: FUNC,
+ 24: EOL
+ ,
+ 25: FUNC_REF "setarray",
+ 29: ARG,
+ 30: VARIABLE "@a",
+ 34: INT 1,
+ 35: NEG,
+ 36: INT 0,
+ 37: INT 1,
+ 38: INT 0,
+ 39: INT 1,
+ 40: INT 2,
+ 41: INT 1,
+ 42: INT 2,
+ 43: INT 3,
+ 44: INT 3,
+ 45: INT 4,
+ 46: INT 5,
+ 47: INT 7,
+ 48: INT 8,
+ 49: INT 9,
+ 50: INT 15,
+ 51: INT 16,
+ 52: INT 17,
+ 53: INT 31,
+ 54: INT 32,
+ 55: INT 33,
+ 56: INT 63,
+ 57: INT 64,
+ 59: INT 65,
+ 61: INT 127,
+ 63: INT 128,
+ 65: INT 129,
+ 67: INT 255,
+ 69: INT 256,
+ 71: INT 257,
+ 73: INT 511,
+ 75: INT 512,
+ 77: INT 513,
+ 79: INT 1023,
+ 81: INT 1024,
+ 83: INT 1025,
+ 85: INT 2047,
+ 87: INT 2048,
+ 89: INT 2049,
+ 91: INT 4095,
+ 93: INT 4096,
+ 95: INT 4097,
+ 97: INT 8191,
+ 100: INT 8192,
+ 103: INT 8193,
+ 106: INT 16383,
+ 109: INT 16384,
+ 112: INT 16385,
+ 115: INT 32767,
+ 118: INT 32768,
+ 121: INT 32769,
+ 124: INT 65535,
+ 127: INT 65536,
+ 130: INT 65537,
+ 133: INT 131071,
+ 136: INT 131072,
+ 139: INT 131073,
+ 142: INT 262143,
+ 145: INT 262144,
+ 148: INT 262145,
+ 151: INT 524287,
+ 155: INT 524288,
+ 159: INT 524289,
+ 163: INT 1048575,
+ 167: INT 1048576,
+ 171: INT 1048577,
+ 175: INT 2097151,
+ 179: INT 2097152,
+ 183: INT 2097153,
+ 187: INT 4194303,
+ 191: INT 4194304,
+ 195: INT 4194305,
+ 199: INT 8388607,
+ 203: INT 8388608,
+ 207: INT 8388609,
+ 211: INT 16777215,
+ 215: INT 16777216,
+ 219: INT 16777217,
+ 223: INT 33554431,
+ 228: INT 33554432,
+ 233: INT 33554433,
+ 238: INT 67108863,
+ 243: INT 67108864,
+ 248: INT 67108865,
+ 253: INT 134217727,
+ 258: INT 134217728,
+ 263: INT 134217729,
+ 268: INT 268435455,
+ 273: INT 268435456,
+ 278: INT 268435457,
+ 283: INT 536870911,
+ 288: INT 536870912,
+ 293: INT 536870913,
+ 298: INT 1073741823,
+ 303: INT 1073741824,
+ 308: INT 1073741825,
+ 313: INT 2147483647,
+ 319: INT 2147483648,
+ 325: INT 2147483649,
+ 331: INT 4294967295,
+ 337: FUNC,
+ 338: EOL
+ ,
+ 339: FUNC_REF "set",
+ 343: ARG,
+ 344: PARAM tmwa::SP::BASELEVEL,
+ 348: INT 42,
+ 349: FUNC,
+ 350: EOL
+ ,
+ 351: FUNC_REF "set",
+ 355: ARG,
+ 356: VARIABLE "@s$",
+ 360: STR "hello",
+ 367: FUNC,
+ 368: EOL
+ ,
+ 369: FUNC_REF "set",
+ 373: ARG,
+ 374: VARIABLE "@i",
+ 378: VARIABLE "a",
+ 382: VARIABLE "b",
+ 386: LOR,
+ 387: FUNC,
+ 388: EOL
+ ,
+ 389: FUNC_REF "set",
+ 393: ARG,
+ 394: VARIABLE "@i",
+ 398: VARIABLE "a",
+ 402: VARIABLE "b",
+ 406: LAND,
+ 407: FUNC,
+ 408: EOL
+ ,
+ 409: FUNC_REF "set",
+ 413: ARG,
+ 414: VARIABLE "@i",
+ 418: VARIABLE "a",
+ 422: VARIABLE "b",
+ 426: LE,
+ 427: FUNC,
+ 428: EOL
+ ,
+ 429: FUNC_REF "set",
+ 433: ARG,
+ 434: VARIABLE "@i",
+ 438: VARIABLE "a",
+ 442: VARIABLE "b",
+ 446: LT,
+ 447: FUNC,
+ 448: EOL
+ ,
+ 449: FUNC_REF "set",
+ 453: ARG,
+ 454: VARIABLE "@i",
+ 458: VARIABLE "a",
+ 462: VARIABLE "b",
+ 466: GE,
+ 467: FUNC,
+ 468: EOL
+ ,
+ 469: FUNC_REF "set",
+ 473: ARG,
+ 474: VARIABLE "@i",
+ 478: VARIABLE "a",
+ 482: VARIABLE "b",
+ 486: GT,
+ 487: FUNC,
+ 488: EOL
+ ,
+ 489: FUNC_REF "set",
+ 493: ARG,
+ 494: VARIABLE "@i",
+ 498: VARIABLE "a",
+ 502: VARIABLE "b",
+ 506: EQ,
+ 507: FUNC,
+ 508: EOL
+ ,
+ 509: FUNC_REF "set",
+ 513: ARG,
+ 514: VARIABLE "@i",
+ 518: VARIABLE "a",
+ 522: VARIABLE "b",
+ 526: NE,
+ 527: FUNC,
+ 528: EOL
+ ,
+ 529: FUNC_REF "set",
+ 533: ARG,
+ 534: VARIABLE "@i",
+ 538: VARIABLE "a",
+ 542: VARIABLE "b",
+ 546: XOR,
+ 547: FUNC,
+ 548: EOL
+ ,
+ 549: FUNC_REF "set",
+ 553: ARG,
+ 554: VARIABLE "@i",
+ 558: VARIABLE "a",
+ 562: VARIABLE "b",
+ 566: OR,
+ 567: FUNC,
+ 568: EOL
+ ,
+ 569: FUNC_REF "set",
+ 573: ARG,
+ 574: VARIABLE "@i",
+ 578: VARIABLE "a",
+ 582: VARIABLE "b",
+ 586: AND,
+ 587: FUNC,
+ 588: EOL
+ ,
+ 589: FUNC_REF "set",
+ 593: ARG,
+ 594: VARIABLE "@i",
+ 598: VARIABLE "a",
+ 602: VARIABLE "b",
+ 606: ADD,
+ 607: FUNC,
+ 608: EOL
+ ,
+ 609: FUNC_REF "set",
+ 613: ARG,
+ 614: VARIABLE "@i",
+ 618: VARIABLE "a",
+ 622: VARIABLE "b",
+ 626: SUB,
+ 627: FUNC,
+ 628: EOL
+ ,
+ 629: FUNC_REF "set",
+ 633: ARG,
+ 634: VARIABLE "@i",
+ 638: VARIABLE "a",
+ 642: VARIABLE "b",
+ 646: MUL,
+ 647: FUNC,
+ 648: EOL
+ ,
+ 649: FUNC_REF "set",
+ 653: ARG,
+ 654: VARIABLE "@i",
+ 658: VARIABLE "a",
+ 662: VARIABLE "b",
+ 666: DIV,
+ 667: FUNC,
+ 668: EOL
+ ,
+ 669: FUNC_REF "set",
+ 673: ARG,
+ 674: VARIABLE "@i",
+ 678: VARIABLE "a",
+ 682: VARIABLE "b",
+ 686: MOD,
+ 687: FUNC,
+ 688: EOL
+ ,
+ 689: FUNC_REF "set",
+ 693: ARG,
+ 694: VARIABLE "@i",
+ 698: VARIABLE "b",
+ 702: NEG,
+ 703: FUNC,
+ 704: EOL
+ ,
+ 705: FUNC_REF "set",
+ 709: ARG,
+ 710: VARIABLE "@i",
+ 714: VARIABLE "b",
+ 718: LNOT,
+ 719: FUNC,
+ 720: EOL
+ ,
+ 721: FUNC_REF "set",
+ 725: ARG,
+ 726: VARIABLE "@i",
+ 730: VARIABLE "b",
+ 734: NOT,
+ 735: FUNC,
+ 736: EOL
+ ,
+ 737: FUNC_REF "set",
+ 741: ARG,
+ 742: VARIABLE "@i",
+ 746: VARIABLE "a",
+ 750: VARIABLE "b",
+ 754: R_SHIFT,
+ 755: FUNC,
+ 756: EOL
+ ,
+ 757: FUNC_REF "set",
+ 761: ARG,
+ 762: VARIABLE "@i",
+ 766: VARIABLE "a",
+ 770: VARIABLE "b",
+ 774: L_SHIFT,
+ 775: FUNC,
+ 776: EOL
+ ,
+ 777: NOP'''.replace('''
+ ''', '\n')
+
+ test_extra = ('''
+ #include "../compat/borrow.hpp"
+ #include "../io/line.hpp"
+ #include "../mmo/clif.t.hpp"
+ #include "../ast/script.hpp"
+ #include "../map/script-parse-internal.hpp"
+
+ using tmwa::operator "" _s;
+
+ static
+ const tmwa::map::ScriptBuffer& test_script_buffer(tmwa::LString source)
+ {
+ auto p = tmwa::map::add_strp("TEST_FAKE_PARAM_BASELEVEL"_s);
+ p->type = tmwa::map::StringCode::PARAM;
+ p->val = static_cast<uint16_t>(tmwa::SP::BASELEVEL);
+ p = tmwa::map::add_strp("TEST_FAKE_CONSTANT"_s);
+ p->type = tmwa::map::StringCode::INT;
+ p->val = 42;
+
+ tmwa::io::LineCharReader lr(tmwa::io::from_string, "<script debug print test>"_s, source);
+ tmwa::ast::script::ScriptOptions opt;
+ opt.implicit_start = true;
+ opt.implicit_end = true;
+ auto code_res = tmwa::ast::script::parse_script_body(lr, opt);
+ auto code = TRY_UNWRAP(code_res.get_success(), abort());
+ auto ups = tmwa::map::compile_script("script debug print test"_s, code, opt.implicit_end);
+ assert(ups);
+ return *ups.release();
+ }
+ ''')
+
+ # the pretty-printer is designed to be run with 'set print array on'
+ # but the testsuite runs with all settings default, in this case off.
+ tests = [
+ ('test_script_buffer("' + src.replace('\\', '\\\\').replace('"', '\\"') + '"_s)',
+ '"script debug print test" = {' + asm.replace('\n', ' ').replace('EOL ,', 'EOL\n,') + '}'),
+ ]
diff --git a/src/map/script-persist.hpp b/src/map/script-persist.hpp
new file mode 100644
index 0000000..45c2761
--- /dev/null
+++ b/src/map/script-persist.hpp
@@ -0,0 +1,128 @@
+#pragma once
+// script-persist.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../compat/borrow.hpp"
+
+#include "../strings/rstring.hpp"
+
+#include "../sexpr/variant.hpp"
+
+#include "../mmo/clif.t.hpp"
+
+#include "script-buffer.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+class SIR
+{
+ uint32_t impl;
+ SIR(SP v)
+ : impl(static_cast<uint32_t>(v))
+ {}
+ SIR(unsigned v, uint8_t i)
+ : impl((i << 24) | v)
+ {}
+public:
+ SIR() : impl() {}
+
+ unsigned base() const { return impl & 0x00ffffff; }
+ uint8_t index() const { return impl >> 24; }
+ SIR iplus(uint8_t i) const { return SIR(base(), index() + i); }
+ static SIR from(unsigned v, uint8_t i=0) { return SIR(v, i); }
+
+ SP sp() const { return static_cast<SP>(impl); }
+ static SIR from(SP v) { return SIR(v); }
+
+ friend bool operator == (SIR l, SIR r) { return l.impl == r.impl; }
+ friend bool operator < (SIR l, SIR r) { return l.impl < r.impl; }
+};
+
+struct ScriptDataPos
+{
+ int numi;
+};
+struct ScriptDataInt
+{
+ int numi;
+};
+struct ScriptDataParam
+{
+ SIR reg;
+};
+struct ScriptDataStr
+{
+ RString str;
+};
+struct ScriptDataArg
+{
+ int numi;
+};
+struct ScriptDataVariable
+{
+ SIR reg;
+};
+struct ScriptDataRetInfo
+{
+ // Not a ScriptPointer - pos is stored in a separate slot,
+ // to avoid exploding the struct for everyone.
+ Borrowed<const ScriptBuffer> script;
+};
+struct ScriptDataFuncRef
+{
+ int numi;
+};
+
+using ScriptDataVariantBase = Variant<
+ ScriptDataPos,
+ ScriptDataInt,
+ ScriptDataParam,
+ ScriptDataStr,
+ ScriptDataArg,
+ ScriptDataVariable,
+ ScriptDataRetInfo,
+ ScriptDataFuncRef
+>;
+struct script_data : ScriptDataVariantBase
+{
+ script_data() = delete;
+ // TODO see if I can delete the copy ctor/assign instead of defaulting
+ script_data(script_data&&) = default;
+ script_data(const script_data&) = default /*delete*/;
+ script_data& operator = (script_data&&) = default;
+ script_data& operator = (const script_data&) = default /*delete*/;
+
+ script_data(ScriptDataPos v) : ScriptDataVariantBase(std::move(v)) {}
+ script_data(ScriptDataInt v) : ScriptDataVariantBase(std::move(v)) {}
+ script_data(ScriptDataParam v) : ScriptDataVariantBase(std::move(v)) {}
+ script_data(ScriptDataStr v) : ScriptDataVariantBase(std::move(v)) {}
+ script_data(ScriptDataArg v) : ScriptDataVariantBase(std::move(v)) {}
+ script_data(ScriptDataVariable v) : ScriptDataVariantBase(std::move(v)) {}
+ script_data(ScriptDataRetInfo v) : ScriptDataVariantBase(std::move(v)) {}
+ script_data(ScriptDataFuncRef v) : ScriptDataVariantBase(std::move(v)) {}
+};
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/script-persist.py b/src/map/script-persist.py
new file mode 100644
index 0000000..3181344
--- /dev/null
+++ b/src/map/script-persist.py
@@ -0,0 +1,37 @@
+class script_data(object):
+ enabled = True
+
+ test_extra = '''
+ #include "../strings/literal.hpp"
+ using tmwa::operator "" _s;
+
+ #include "../map/script-parse-internal.hpp"
+
+ static
+ tmwa::Borrowed<const tmwa::map::ScriptBuffer> fake_script()
+ {
+ static
+ const std::vector<tmwa::map::ByteCode> buffer;
+ return tmwa::borrow(reinterpret_cast<const tmwa::map::ScriptBuffer&>(buffer));
+ }
+
+ '''
+
+ tests = [
+ ('tmwa::map::script_data(tmwa::map::ScriptDataPos{42})',
+ '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataPos) = {numi = 42}}, <No data fields>}'),
+ ('tmwa::map::script_data(tmwa::map::ScriptDataInt{123})',
+ '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataInt) = {numi = 123}}, <No data fields>}'),
+ ('tmwa::map::script_data(tmwa::map::ScriptDataParam{tmwa::map::SIR()})',
+ '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataParam) = {reg = {impl = 0}}}, <No data fields>}'),
+ ('tmwa::map::script_data(tmwa::map::ScriptDataStr{"Hello"_s})',
+ '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataStr) = {str = "Hello"}}, <No data fields>}'),
+ ('tmwa::map::script_data(tmwa::map::ScriptDataArg{0})',
+ '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataArg) = {numi = 0}}, <No data fields>}'),
+ ('tmwa::map::script_data(tmwa::map::ScriptDataVariable{tmwa::map::SIR()})',
+ '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataVariable) = {reg = {impl = 0}}}, <No data fields>}'),
+ ('tmwa::map::script_data(tmwa::map::ScriptDataRetInfo{fake_script()})',
+ '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataRetInfo) = {script = <fake_script()::buffer>}}, <No data fields>}'),
+ ('tmwa::map::script_data(tmwa::map::ScriptDataFuncRef{404})',
+ '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataFuncRef) = {numi = 404}}, <No data fields>}'),
+ ]
diff --git a/src/map/script-startup-internal.hpp b/src/map/script-startup-internal.hpp
new file mode 100644
index 0000000..91ce09b
--- /dev/null
+++ b/src/map/script-startup-internal.hpp
@@ -0,0 +1,36 @@
+#pragma once
+// script-startup-internal.hpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "script-startup.hpp"
+#include "fwd.hpp"
+
+#include "script-persist.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+void mapreg_setreg(SIR reg, int val);
+void mapreg_setregstr(SIR reg, XString str);
+} // namespace map
+} // namespace tmwa
diff --git a/src/map/script-startup.cpp b/src/map/script-startup.cpp
new file mode 100644
index 0000000..ad809db
--- /dev/null
+++ b/src/map/script-startup.cpp
@@ -0,0 +1,265 @@
+#include "script-startup-internal.hpp"
+// script-startup.cpp - EAthena script frontend, engine, and library.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011 Chuck Miller
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © 2013 wushin
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "../strings/zstring.hpp"
+
+#include "../generic/db.hpp"
+#include "../generic/intern-pool.hpp"
+
+#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
+#include "../io/read.hpp"
+#include "../io/lock.hpp"
+
+#include "../net/timer.hpp"
+
+#include "globals.hpp"
+#include "map.hpp"
+#include "map_conf.hpp"
+#include "script-parse-internal.hpp"
+#include "script-persist.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+namespace map
+{
+constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = 10_s;
+
+bool read_constdb(ZString filename)
+{
+ io::ReadFile in(filename);
+ if (!in.is_open())
+ {
+ PRINTF("can't read %s\n"_fmt, filename);
+ return false;
+ }
+
+ bool rv = true;
+ AString line_;
+ while (in.getline(line_))
+ {
+ // is_comment only works for whole-line comments
+ // that could change once the Z dependency is dropped ...
+ LString comment = "//"_s;
+ XString line = line_.xislice_h(std::search(line_.begin(), line_.end(), comment.begin(), comment.end())).rstrip();
+ if (!line)
+ continue;
+ // "%m[A-Za-z0-9_] %i %i"
+
+ // TODO promote either qsplit() or asplit()
+ auto _it = std::find(line.begin(), line.end(), ' ');
+ auto name = line.xislice_h(_it);
+ auto _rest = line.xislice_t(_it);
+ while (_rest.startswith(' '))
+ _rest = _rest.xslice_t(1);
+ auto _it2 = std::find(_rest.begin(), _rest.end(), ' ');
+ auto val_ = _rest.xislice_h(_it2);
+ auto type_ = _rest.xislice_t(_it2);
+ while (type_.startswith(' '))
+ type_ = type_.xslice_t(1);
+ // yes, the above actually DTRT even for underlength input
+
+ int val;
+ int type = 0;
+ // Note for future archeaologists: this code is indented correctly
+ if (std::find_if_not(name.begin(), name.end(),
+ [](char c)
+ {
+ return ('0' <= c && c <= '9')
+ || ('A' <= c && c <= 'Z')
+ || ('a' <= c && c <= 'z')
+ || (c == '_');
+ }) != name.end()
+ || !extract(val_, &val)
+ || (!extract(type_, &type) && type_))
+ {
+ PRINTF("Bad const line: %s\n"_fmt, line_);
+ rv = false;
+ continue;
+ }
+ P<str_data_t> n = add_strp(name);
+ n->type = type ? StringCode::PARAM : StringCode::INT;
+ n->val = val;
+ }
+ return rv;
+}
+
+/*==========================================
+ * マップ変数の変更
+ *------------------------------------------
+ */
+void mapreg_setreg(SIR reg, int val)
+{
+ mapreg_db.put(reg, val);
+
+ mapreg_dirty = 1;
+}
+
+/*==========================================
+ * 文字列型マップ変数の変更
+ *------------------------------------------
+ */
+void mapreg_setregstr(SIR reg, XString str)
+{
+ if (!str)
+ mapregstr_db.erase(reg);
+ else
+ mapregstr_db.insert(reg, str);
+
+ mapreg_dirty = 1;
+}
+
+/*==========================================
+ * 永続的マップ変数の読み込み
+ *------------------------------------------
+ */
+static
+void script_load_mapreg(void)
+{
+ io::ReadFile in(map_conf.mapreg_txt);
+
+ if (!in.is_open())
+ return;
+
+ AString line;
+ while (in.getline(line))
+ {
+ XString buf1, buf2;
+ int index = 0;
+ if (extract(line,
+ record<'\t'>(
+ record<','>(&buf1, &index),
+ &buf2))
+ || extract(line,
+ record<'\t'>(
+ record<','>(&buf1),
+ &buf2)))
+ {
+ int s = variable_names.intern(buf1);
+ SIR key = SIR::from(s, index);
+ if (buf1.back() == '$')
+ {
+ mapregstr_db.insert(key, buf2);
+ }
+ else
+ {
+ int v;
+ if (!extract(buf2, &v))
+ goto borken;
+ mapreg_db.put(key, v);
+ }
+ }
+ else
+ {
+ borken:
+ PRINTF("%s: %s broken data !\n"_fmt, map_conf.mapreg_txt, AString(buf1));
+ continue;
+ }
+ }
+ mapreg_dirty = 0;
+}
+
+/*==========================================
+ * 永続的マップ変数の書き込み
+ *------------------------------------------
+ */
+static
+void script_save_mapreg_intsub(SIR key, int data, io::WriteFile& fp)
+{
+ int num = key.base(), i = key.index();
+ ZString name = variable_names.outtern(num);
+ if (name[1] != '@')
+ {
+ if (i == 0)
+ FPRINTF(fp, "%s\t%d\n"_fmt, name, data);
+ else
+ FPRINTF(fp, "%s,%d\t%d\n"_fmt, name, i, data);
+ }
+}
+
+static
+void script_save_mapreg_strsub(SIR key, ZString data, io::WriteFile& fp)
+{
+ int num = key.base(), i = key.index();
+ ZString name = variable_names.outtern(num);
+ if (name[1] != '@')
+ {
+ if (i == 0)
+ FPRINTF(fp, "%s\t%s\n"_fmt, name, data);
+ else
+ FPRINTF(fp, "%s,%d\t%s\n"_fmt, name, i, data);
+ }
+}
+
+static
+void script_save_mapreg(void)
+{
+ io::WriteLock fp(map_conf.mapreg_txt);
+ if (!fp.is_open())
+ return;
+ for (auto& pair : mapreg_db)
+ script_save_mapreg_intsub(pair.first, pair.second, fp);
+ for (auto& pair : mapregstr_db)
+ script_save_mapreg_strsub(pair.first, pair.second, fp);
+ mapreg_dirty = 0;
+}
+
+static
+void script_autosave_mapreg(TimerData *, tick_t)
+{
+ if (mapreg_dirty)
+ script_save_mapreg();
+}
+
+void do_final_script(void)
+{
+ if (mapreg_dirty >= 0)
+ script_save_mapreg();
+
+ mapreg_db.clear();
+ mapregstr_db.clear();
+ scriptlabel_db.clear();
+ userfunc_db.clear();
+
+ str_datam.clear();
+}
+
+/*==========================================
+ * 初期化
+ *------------------------------------------
+ */
+void do_init_script(void)
+{
+ script_load_mapreg();
+
+ Timer(gettick() + MAPREG_AUTOSAVE_INTERVAL,
+ script_autosave_mapreg,
+ MAPREG_AUTOSAVE_INTERVAL
+ ).detach();
+}
+} // namespace map
+} // namespace tmwa
diff --git a/src/compat/iter.cpp b/src/map/script-startup.hpp
index b6d6b63..1691c94 100644
--- a/src/compat/iter.cpp
+++ b/src/map/script-startup.hpp
@@ -1,7 +1,9 @@
-#include "iter.hpp"
-// iter.cpp - tools for dealing with iterators
+#pragma once
+// script-startup.hpp - EAthena script frontend, engine, and library.
//
-// Copyright © 2012-2014 Ben Longbons <b.r.longbons@gmail.com>
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
//
// This file is part of The Mana World (Athena server)
//
@@ -18,9 +20,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
-
+#include "fwd.hpp"
namespace tmwa
{
+namespace map
+{
+void do_init_script(void);
+void do_final_script(void);
+
+bool read_constdb(ZString filename);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/script.cpp b/src/map/script.cpp
deleted file mode 100644
index 40dfc0e..0000000
--- a/src/map/script.cpp
+++ /dev/null
@@ -1,5100 +0,0 @@
-#include "script.hpp"
-// script.cpp - EAthena script frontend, engine, and library.
-//
-// Copyright © ????-2004 Athena Dev Teams
-// Copyright © 2004-2011 The Mana World Development Team
-// Copyright © 2011 Chuck Miller
-// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-// Copyright © 2013 wushin
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include <cassert>
-#include <cmath>
-#include <cstdlib>
-#include <ctime>
-
-#include <algorithm>
-#include <set>
-
-#include "../compat/fun.hpp"
-
-#include "../strings/mstring.hpp"
-#include "../strings/rstring.hpp"
-#include "../strings/astring.hpp"
-#include "../strings/zstring.hpp"
-#include "../strings/xstring.hpp"
-#include "../strings/literal.hpp"
-
-#include "../generic/db.hpp"
-#include "../generic/intern-pool.hpp"
-#include "../generic/random.hpp"
-
-#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
-#include "../io/lock.hpp"
-#include "../io/read.hpp"
-#include "../io/write.hpp"
-
-#include "../net/socket.hpp"
-#include "../net/timer.hpp"
-
-#include "../mmo/core.hpp"
-#include "../mmo/extract.hpp"
-#include "../mmo/human_time_diff.hpp"
-#include "../mmo/utils.hpp"
-
-#include "atcommand.hpp"
-#include "battle.hpp"
-#include "chrif.hpp"
-#include "clif.hpp"
-#include "intif.hpp"
-#include "itemdb.hpp"
-#include "magic-interpreter-base.hpp"
-#include "map.hpp"
-#include "mob.hpp"
-#include "npc.hpp"
-#include "party.hpp"
-#include "pc.hpp"
-#include "skill.hpp"
-#include "storage.hpp"
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-constexpr bool DEBUG_DISP = false;
-constexpr bool DEBUG_RUN = false;
-
-struct str_data_t
-{
- StringCode type;
- RString strs;
- int backpatch;
- int label_;
- int val;
-};
-static
-Map<RString, str_data_t> str_datam;
-static
-str_data_t LABEL_NEXTLINE_;
-
-static
-DMap<SIR, int> mapreg_db;
-static
-Map<SIR, RString> mapregstr_db;
-static
-int mapreg_dirty = -1;
-AString mapreg_txt = "save/mapreg.txt"_s;
-constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = 10_s;
-
-Map<ScriptLabel, int> scriptlabel_db;
-static
-std::set<ScriptLabel> probable_labels;
-UPMap<RString, const ScriptBuffer> userfunc_db;
-
-static
-Array<LString, 11> pos_str //=
-{{
- "Head"_s,
- "Body"_s,
- "Left hand"_s,
- "Right hand"_s,
- "Robe"_s,
- "Shoes"_s,
- "Accessory 1"_s,
- "Accessory 2"_s,
- "Head 2"_s,
- "Head 3"_s,
- "Not Equipped"_s,
-}};
-
-static
-struct Script_Config
-{
- static const
- int warn_func_no_comma = 1;
- static const
- int warn_cmd_no_comma = 1;
- static const
- int warn_func_mismatch_paramnum = 1;
- static const
- int warn_cmd_mismatch_paramnum = 1;
- static const
- int check_cmdcount = 8192;
- static const
- int check_gotocount = 512;
-} script_config;
-
-static
-int parse_cmd_if = 0;
-static
-str_data_t *parse_cmdp;
-
-static
-void run_func(ScriptState *st);
-
-static
-void mapreg_setreg(SIR num, int val);
-static
-void mapreg_setregstr(SIR num, XString str);
-
-struct BuiltinFunction
-{
- void (*func)(ScriptState *);
- LString name;
- LString arg;
- char ret;
-};
-// defined later
-extern BuiltinFunction builtin_functions[];
-
-static
-InternPool variable_names;
-
-
-template<class D>
-bool first_type_is_any()
-{
- return false;
-}
-
-template<class D, class F, class... R>
-constexpr
-bool first_type_is_any()
-{
- return std::is_same<D, F>::value || first_type_is_any<D, R...>();
-}
-
-
-static
-str_data_t *search_strp(XString p)
-{
- return str_datam.search(p);
-}
-
-static
-str_data_t *add_strp(XString p)
-{
- if (str_data_t *rv = search_strp(p))
- return rv;
-
- RString p2 = p;
- str_data_t *datum = str_datam.init(p2);
- datum->type = StringCode::NOP;
- datum->strs = p2;
- datum->backpatch = -1;
- datum->label_ = -1;
- return datum;
-}
-
-/*==========================================
- * スクリプトバッファに1バイト書き込む
- *------------------------------------------
- */
-void ScriptBuffer::add_scriptc(ByteCode a)
-{
- script_buf.push_back(a);
-}
-
-/*==========================================
- * スクリプトバッファにデータタイプを書き込む
- *------------------------------------------
- */
-void ScriptBuffer::add_scriptb(uint8_t a)
-{
- add_scriptc(static_cast<ByteCode>(a));
-}
-
-/*==========================================
- * スクリプトバッファに整数を書き込む
- *------------------------------------------
- */
-void ScriptBuffer::add_scripti(uint32_t a)
-{
- while (a >= 0x40)
- {
- add_scriptb(a | 0xc0);
- a = (a - 0x40) >> 6;
- }
- add_scriptb(a | 0x80);
-}
-
-/*==========================================
- * スクリプトバッファにラベル/変数/関数を書き込む
- *------------------------------------------
- */
-// 最大16Mまで
-void ScriptBuffer::add_scriptl(str_data_t *ld)
-{
- int backpatch = ld->backpatch;
-
- switch (ld->type)
- {
- case StringCode::POS:
- add_scriptc(ByteCode::POS);
- add_scriptb(static_cast<uint8_t>(ld->label_));
- add_scriptb(static_cast<uint8_t>(ld->label_ >> 8));
- add_scriptb(static_cast<uint8_t>(ld->label_ >> 16));
- break;
- case StringCode::NOP:
- // need to set backpatch, because it might become a label later
- add_scriptc(ByteCode::VARIABLE);
- ld->backpatch = script_buf.size();
- add_scriptb(static_cast<uint8_t>(backpatch));
- add_scriptb(static_cast<uint8_t>(backpatch >> 8));
- add_scriptb(static_cast<uint8_t>(backpatch >> 16));
- break;
- case StringCode::INT:
- add_scripti(ld->val);
- break;
- case StringCode::FUNC:
- add_scriptc(ByteCode::FUNC_REF);
- add_scriptb(static_cast<uint8_t>(ld->val));
- add_scriptb(static_cast<uint8_t>(ld->val >> 8));
- add_scriptb(static_cast<uint8_t>(ld->val >> 16));
- break;
- case StringCode::PARAM:
- add_scriptc(ByteCode::PARAM);
- add_scriptb(static_cast<uint8_t>(ld->val));
- add_scriptb(static_cast<uint8_t>(ld->val >> 8));
- add_scriptb(static_cast<uint8_t>(ld->val >> 16));
- break;
- default:
- abort();
- }
-}
-
-/*==========================================
- * ラベルを解決する
- *------------------------------------------
- */
-void ScriptBuffer::set_label(str_data_t *ld, int pos_)
-{
- int next;
-
- ld->type = StringCode::POS;
- ld->label_ = pos_;
- for (int i = ld->backpatch; i >= 0 && i != 0x00ffffff; i = next)
- {
- next = 0;
- // woot! no longer endian-dependent!
- next |= static_cast<uint8_t>(script_buf[i + 0]) << 0;
- next |= static_cast<uint8_t>(script_buf[i + 1]) << 8;
- next |= static_cast<uint8_t>(script_buf[i + 2]) << 16;
- script_buf[i - 1] = ByteCode::POS;
- script_buf[i] = static_cast<ByteCode>(pos_);
- script_buf[i + 1] = static_cast<ByteCode>(pos_ >> 8);
- script_buf[i + 2] = static_cast<ByteCode>(pos_ >> 16);
- }
-}
-
-/*==========================================
- * スペース/コメント読み飛ばし
- *------------------------------------------
- */
-static
-ZString::iterator skip_space(ZString::iterator p)
-{
- while (1)
- {
- while (isspace(*p))
- p++;
- if (p[0] == '/' && p[1] == '/')
- {
- while (*p && *p != '\n')
- p++;
- }
- else if (p[0] == '/' && p[1] == '*')
- {
- p++;
- while (*p && (p[-1] != '*' || p[0] != '/'))
- p++;
- if (*p)
- p++;
- }
- else
- break;
- }
- return p;
-}
-
-/*==========================================
- * 1単語スキップ
- *------------------------------------------
- */
-static
-ZString::iterator skip_word(ZString::iterator p)
-{
- // prefix
- if (*p == '$')
- p++; // MAP鯖内共有変数用
- if (*p == '@')
- p++; // 一時的変数用(like weiss)
- if (*p == '#')
- p++; // account変数用
- if (*p == '#')
- p++; // ワールドaccount変数用
-
- while (isalnum(*p) || *p == '_')
- p++;
-
- // postfix
- if (*p == '$')
- p++; // 文字列変数
-
- return p;
-}
-
-// TODO: replace this whole mess with some sort of input stream that works
-// a line at a time.
-static
-ZString startptr;
-static
-int startline;
-
-int script_errors = 0;
-/*==========================================
- * エラーメッセージ出力
- *------------------------------------------
- */
-static
-void disp_error_message(ZString mes, ZString::iterator pos_)
-{
- script_errors++;
-
- assert (startptr.begin() <= pos_ && pos_ <= startptr.end());
-
- int line;
- ZString::iterator p;
-
- for (line = startline, p = startptr.begin(); p != startptr.end(); line++)
- {
- ZString::iterator linestart = p;
- ZString::iterator lineend = std::find(p, startptr.end(), '\n');
- if (pos_ < lineend)
- {
- PRINTF("\n%s\nline %d : "_fmt, mes, line);
- for (int i = 0; linestart + i != lineend; i++)
- {
- if (linestart + i != pos_)
- PRINTF("%c"_fmt, linestart[i]);
- else
- PRINTF("\'%c\'"_fmt, linestart[i]);
- }
- PRINTF("\a\n"_fmt);
- return;
- }
- p = lineend + 1;
- }
-}
-
-/*==========================================
- * 項の解析
- *------------------------------------------
- */
-ZString::iterator ScriptBuffer::parse_simpleexpr(ZString::iterator p)
-{
- p = skip_space(p);
-
- if (*p == ';' || *p == ',')
- {
- disp_error_message("unexpected expr end"_s, p);
- exit(1);
- }
- if (*p == '(')
- {
-
- p = parse_subexpr(p + 1, -1);
- p = skip_space(p);
- if ((*p++) != ')')
- {
- disp_error_message("unmatch ')'"_s, p);
- exit(1);
- }
- }
- else if (isdigit(*p) || ((*p == '-' || *p == '+') && isdigit(p[1])))
- {
- char *np;
- int i = strtoul(&*p, &np, 0);
- add_scripti(i);
- p += np - &*p;
- }
- else if (*p == '"')
- {
- add_scriptc(ByteCode::STR);
- p++;
- while (*p && *p != '"')
- {
- if (*p == '\\')
- p++;
- else if (*p == '\n')
- {
- disp_error_message("unexpected newline @ string"_s, p);
- exit(1);
- }
- add_scriptb(*p++);
- }
- if (!*p)
- {
- disp_error_message("unexpected eof @ string"_s, p);
- exit(1);
- }
- add_scriptb(0);
- p++; //'"'
- }
- else
- {
- // label , register , function etc
- ZString::iterator p2 = skip_word(p);
- if (p2 == p)
- {
- disp_error_message("unexpected character"_s, p);
- exit(1);
- }
- XString word(&*p, &*p2, nullptr);
- if (word.startswith("On"_s) || word.startswith("L_"_s) || word.startswith("S_"_s))
- probable_labels.insert(stringish<ScriptLabel>(word));
- if (parse_cmd_if && (word == "callsub"_s || word == "callfunc"_s || word == "return"_s))
- {
- disp_error_message("Sorry, callsub/callfunc/return have never worked properly in an if statement."_s, p);
- }
- str_data_t *ld = add_strp(word);
-
- parse_cmdp = ld; // warn_*_mismatch_paramnumのために必要
- // why not just check l->str == "if"_s or std::string(p, p2) == "if"_s?
- if (ld == search_strp("if"_s)) // warn_cmd_no_commaのために必要
- parse_cmd_if++;
- p = p2;
-
- if (ld->type != StringCode::FUNC && *p == '[')
- {
- // array(name[i] => getelementofarray(name,i) )
- add_scriptl(search_strp("getelementofarray"_s));
- add_scriptc(ByteCode::ARG);
- add_scriptl(ld);
- p = parse_subexpr(p + 1, -1);
- p = skip_space(p);
- if (*p != ']')
- {
- disp_error_message("unmatch ']'"_s, p);
- exit(1);
- }
- p++;
- add_scriptc(ByteCode::FUNC);
- }
- else
- add_scriptl(ld);
-
- }
-
- return p;
-}
-
-/*==========================================
- * 式の解析
- *------------------------------------------
- */
-ZString::iterator ScriptBuffer::parse_subexpr(ZString::iterator p, int limit)
-{
- ByteCode op;
- int opl, len;
-
- p = skip_space(p);
-
- if (*p == '-')
- {
- ZString::iterator tmpp = skip_space(p + 1);
- if (*tmpp == ';' || *tmpp == ',')
- {
- --script_errors; disp_error_message("deprecated: implicit 'next statement' label"_s, p);
- add_scriptl(&LABEL_NEXTLINE_);
- p++;
- return p;
- }
- }
- ZString::iterator tmpp = p;
- if ((op = ByteCode::NEG, *p == '-') || (op = ByteCode::LNOT, *p == '!')
- || (op = ByteCode::NOT, *p == '~'))
- {
- p = parse_subexpr(p + 1, 100);
- add_scriptc(op);
- }
- else
- p = parse_simpleexpr(p);
- p = skip_space(p);
- while (((op = ByteCode::ADD, opl = 6, len = 1, *p == '+') ||
- (op = ByteCode::SUB, opl = 6, len = 1, *p == '-') ||
- (op = ByteCode::MUL, opl = 7, len = 1, *p == '*') ||
- (op = ByteCode::DIV, opl = 7, len = 1, *p == '/') ||
- (op = ByteCode::MOD, opl = 7, len = 1, *p == '%') ||
- (op = ByteCode::FUNC, opl = 8, len = 1, *p == '(') ||
- (op = ByteCode::LAND, opl = 1, len = 2, *p == '&' && p[1] == '&') ||
- (op = ByteCode::AND, opl = 5, len = 1, *p == '&') ||
- (op = ByteCode::LOR, opl = 0, len = 2, *p == '|' && p[1] == '|') ||
- (op = ByteCode::OR, opl = 4, len = 1, *p == '|') ||
- (op = ByteCode::XOR, opl = 3, len = 1, *p == '^') ||
- (op = ByteCode::EQ, opl = 2, len = 2, *p == '=' && p[1] == '=') ||
- (op = ByteCode::NE, opl = 2, len = 2, *p == '!' && p[1] == '=') ||
- (op = ByteCode::R_SHIFT, opl = 5, len = 2, *p == '>' && p[1] == '>') ||
- (op = ByteCode::GE, opl = 2, len = 2, *p == '>' && p[1] == '=') ||
- (op = ByteCode::GT, opl = 2, len = 1, *p == '>') ||
- (op = ByteCode::L_SHIFT, opl = 5, len = 2, *p == '<' && p[1] == '<') ||
- (op = ByteCode::LE, opl = 2, len = 2, *p == '<' && p[1] == '=') ||
- (op = ByteCode::LT, opl = 2, len = 1, *p == '<')) && opl > limit)
- {
- p += len;
- if (op == ByteCode::FUNC)
- {
- int i = 0;
- str_data_t *funcp = parse_cmdp;
- ZString::iterator plist[128];
-
- if (funcp->type != StringCode::FUNC)
- {
- disp_error_message("expect function"_s, tmpp);
- exit(0);
- }
-
- add_scriptc(ByteCode::ARG);
- while (*p && *p != ')' && i < 128)
- {
- plist[i] = p;
- p = parse_subexpr(p, -1);
- p = skip_space(p);
- if (*p == ',')
- p++;
- else if (*p != ')' && script_config.warn_func_no_comma)
- {
- disp_error_message("expect ',' or ')' at func params"_s,
- p);
- }
- p = skip_space(p);
- i++;
- }
- plist[i] = p;
- if (*p != ')')
- {
- disp_error_message("func request '(' ')'"_s, p);
- exit(1);
- }
- p++;
-
- if (funcp->type == StringCode::FUNC
- && script_config.warn_func_mismatch_paramnum)
- {
- ZString arg = builtin_functions[funcp->val].arg;
- int j = 0;
- // TODO handle ? and multiple * correctly
- for (j = 0; arg[j]; j++)
- if (arg[j] == '*' || arg[j] == '?')
- break;
- if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j))
- {
- disp_error_message("illegal number of parameters"_s,
- plist[std::min(i, j)]);
- }
- if (!builtin_functions[funcp->val].ret)
- {
- disp_error_message("statement in function context"_s, tmpp);
- }
- }
- }
- else // not op == ByteCode::FUNC
- {
- p = parse_subexpr(p, opl);
- }
- add_scriptc(op);
- p = skip_space(p);
- }
- return p; /* return first untreated operator */
-}
-
-/*==========================================
- * 式の評価
- *------------------------------------------
- */
-ZString::iterator ScriptBuffer::parse_expr(ZString::iterator p)
-{
- switch (*p)
- {
- case ')':
- case ';':
- case ':':
- case '[':
- case ']':
- case '}':
- disp_error_message("unexpected char"_s, p);
- exit(1);
- }
- p = parse_subexpr(p, -1);
- return p;
-}
-
-/*==========================================
- * 行の解析
- *------------------------------------------
- */
-ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step)
-{
- int i = 0;
- ZString::iterator plist[128];
-
- p = skip_space(p);
- if (*p == ';')
- return p;
-
- parse_cmd_if = 0; // warn_cmd_no_commaのために必要
-
- // 最初は関数名
- ZString::iterator p2 = p;
- p = parse_simpleexpr(p);
- p = skip_space(p);
-
- str_data_t *cmd = parse_cmdp;
- if (cmd->type != StringCode::FUNC)
- {
- disp_error_message("expect command"_s, p2);
- }
-
- {
- // TODO should be LString, but no heterogenous lookup yet
- static
- std::set<ZString> terminators =
- {
- "goto"_s,
- "return"_s,
- "close"_s,
- "menu"_s,
- "end"_s,
- "mapexit"_s,
- "shop"_s,
- };
- *can_step = terminators.count(cmd->strs) == 0;
- }
-
- add_scriptc(ByteCode::ARG);
- while (*p && *p != ';' && i < 128)
- {
- plist[i] = p;
-
- p = parse_expr(p);
- p = skip_space(p);
- // 引数区切りの,処理
- if (*p == ',')
- p++;
- else if (*p != ';' && script_config.warn_cmd_no_comma
- && parse_cmd_if * 2 <= i)
- {
- disp_error_message("expect ',' or ';' at cmd params"_s, p);
- }
- p = skip_space(p);
- i++;
- }
- plist[i] = p;
- if (*(p++) != ';')
- {
- disp_error_message("need ';'"_s, p);
- exit(1);
- }
- add_scriptc(ByteCode::FUNC);
-
- if (cmd->type == StringCode::FUNC
- && script_config.warn_cmd_mismatch_paramnum)
- {
- ZString arg = builtin_functions[cmd->val].arg;
- int j = 0;
- // TODO see above
- for (j = 0; arg[j]; j++)
- if (arg[j] == '*' || arg[j] == '?')
- break;
- if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j))
- {
- disp_error_message("illegal number of parameters"_s,
- plist[std::min(i, j)]);
- }
- if (builtin_functions[cmd->val].ret)
- {
- disp_error_message("function in statement context"_s, p2);
- }
- }
-
- return p;
-}
-
-/*==========================================
- * 組み込み関数の追加
- *------------------------------------------
- */
-static
-void add_builtin_functions(void)
-{
- for (int i = 0; builtin_functions[i].func; i++)
- {
- str_data_t *n = add_strp(builtin_functions[i].name);
- n->type = StringCode::FUNC;
- n->val = i;
- }
-}
-
-bool read_constdb(ZString filename)
-{
- io::ReadFile in(filename);
- if (!in.is_open())
- {
- PRINTF("can't read %s\n"_fmt, filename);
- return false;
- }
-
- bool rv = true;
- AString line_;
- while (in.getline(line_))
- {
- // is_comment only works for whole-line comments
- // that could change once the Z dependency is dropped ...
- LString comment = "//"_s;
- XString line = line_.xislice_h(std::search(line_.begin(), line_.end(), comment.begin(), comment.end())).rstrip();
- if (!line)
- continue;
- // "%m[A-Za-z0-9_] %i %i"
-
- // TODO promote either qsplit() or asplit()
- auto _it = std::find(line.begin(), line.end(), ' ');
- auto name = line.xislice_h(_it);
- auto _rest = line.xislice_t(_it);
- while (_rest.startswith(' '))
- _rest = _rest.xslice_t(1);
- auto _it2 = std::find(_rest.begin(), _rest.end(), ' ');
- auto val_ = _rest.xislice_h(_it2);
- auto type_ = _rest.xislice_t(_it2);
- while (type_.startswith(' '))
- type_ = type_.xslice_t(1);
- // yes, the above actually DTRT even for underlength input
-
- int val;
- int type = 0;
- // Note for future archeaologists: this code is indented correctly
- if (std::find_if_not(name.begin(), name.end(),
- [](char c)
- {
- return ('0' <= c && c <= '9')
- || ('A' <= c && c <= 'Z')
- || ('a' <= c && c <= 'z')
- || (c == '_');
- }) != name.end()
- || !extract(val_, &val)
- || (!extract(type_, &type) && type_))
- {
- PRINTF("Bad const line: %s\n"_fmt, line_);
- rv = false;
- continue;
- }
- str_data_t *n = add_strp(name);
- n->type = type ? StringCode::PARAM : StringCode::INT;
- n->val = val;
- }
- return rv;
-}
-
-std::unique_ptr<const ScriptBuffer> parse_script(ZString src, int line, bool implicit_end)
-{
- auto script_buf = make_unique<ScriptBuffer>();
- script_buf->parse_script(src, line, implicit_end);
- return std::move(script_buf);
-}
-
-/*==========================================
- * スクリプトの解析
- *------------------------------------------
- */
-void ScriptBuffer::parse_script(ZString src, int line, bool implicit_end)
-{
- static int first = 1;
-
- if (first)
- {
- add_builtin_functions();
- }
- first = 0;
- LABEL_NEXTLINE_.type = StringCode::NOP;
- LABEL_NEXTLINE_.backpatch = -1;
- LABEL_NEXTLINE_.label_ = -1;
- for (auto& pair : str_datam)
- {
- str_data_t& dit = pair.second;
- if (dit.type == StringCode::POS || dit.type == StringCode::VARIABLE)
- {
- dit.type = StringCode::NOP;
- dit.backpatch = -1;
- dit.label_ = -1;
- }
- }
-
- // 外部用label dbの初期化
- scriptlabel_db.clear();
-
- // for error message
- startptr = src;
- startline = line;
-
- bool can_step = true;
-
- ZString::iterator p = src.begin();
- p = skip_space(p);
- if (*p != '{')
- {
- disp_error_message("not found '{'"_s, p);
- abort();
- }
- for (p++; *p && *p != '}';)
- {
- p = skip_space(p);
- if (*skip_space(skip_word(p)) == ':')
- {
- if (can_step)
- {
- --script_errors; disp_error_message("deprecated: implicit fallthrough"_s, p);
- }
- can_step = true;
-
- ZString::iterator tmpp = skip_word(p);
- XString str(&*p, &*tmpp, nullptr);
- str_data_t *ld = add_strp(str);
- bool e1 = ld->type != StringCode::NOP;
- bool e2 = ld->type == StringCode::POS;
- bool e3 = ld->label_ != -1;
- assert (e1 == e2 && e2 == e3);
- if (e3)
- {
- disp_error_message("dup label "_s, p);
- exit(1);
- }
- set_label(ld, script_buf.size());
- scriptlabel_db.insert(stringish<ScriptLabel>(str), script_buf.size());
- p = tmpp + 1;
- continue;
- }
-
- if (!can_step)
- {
- --script_errors; disp_error_message("deprecated: unreachable statement"_s, p);
- }
- // 他は全部一緒くた
- p = parse_line(p, &can_step);
- p = skip_space(p);
- add_scriptc(ByteCode::EOL);
-
- set_label(&LABEL_NEXTLINE_, script_buf.size());
- LABEL_NEXTLINE_.type = StringCode::NOP;
- LABEL_NEXTLINE_.backpatch = -1;
- LABEL_NEXTLINE_.label_ = -1;
- }
-
- if (can_step && !implicit_end)
- {
- --script_errors; disp_error_message("deprecated: implicit end"_s, p);
- }
- add_scriptc(ByteCode::NOP);
-
- // resolve the unknown labels
- for (auto& pair : str_datam)
- {
- str_data_t& sit = pair.second;
- if (sit.type == StringCode::NOP)
- {
- sit.type = StringCode::VARIABLE;
- sit.label_ = 0; // anything but -1. Shouldn't matter, but helps asserts.
- size_t pool_index = variable_names.intern(sit.strs);
- for (int next, j = sit.backpatch; j >= 0 && j != 0x00ffffff; j = next)
- {
- next = 0;
- next |= static_cast<uint8_t>(script_buf[j + 0]) << 0;
- next |= static_cast<uint8_t>(script_buf[j + 1]) << 8;
- next |= static_cast<uint8_t>(script_buf[j + 2]) << 16;
- script_buf[j] = static_cast<ByteCode>(pool_index);
- script_buf[j + 1] = static_cast<ByteCode>(pool_index >> 8);
- script_buf[j + 2] = static_cast<ByteCode>(pool_index >> 16);
- }
- }
- }
-
- for (const auto& pair : scriptlabel_db)
- {
- ScriptLabel key = pair.first;
- if (key.startswith("On"_s))
- continue;
- if (!(key.startswith("L_"_s) || key.startswith("S_"_s)))
- PRINTF("Warning: ugly label: %s\n"_fmt, key);
- else if (!probable_labels.count(key))
- PRINTF("Warning: unused label: %s\n"_fmt, key);
- }
- for (ScriptLabel used : probable_labels)
- {
- if (!scriptlabel_db.search(used))
- PRINTF("Warning: no such label: %s\n"_fmt, used);
- }
- probable_labels.clear();
-
- if (!DEBUG_DISP)
- return;
- for (size_t i = 0; i < script_buf.size(); i++)
- {
- if ((i & 15) == 0)
- PRINTF("%04zx : "_fmt, i);
- PRINTF("%02x "_fmt, script_buf[i]);
- if ((i & 15) == 15)
- PRINTF("\n"_fmt);
- }
- PRINTF("\n"_fmt);
-}
-
-//
-// 実行系
-//
-enum class ScriptEndState
-{
- ZERO,
- STOP,
- END,
- RERUNLINE,
- GOTO,
- RETFUNC,
-};
-
-/*==========================================
- * ridからsdへの解決
- *------------------------------------------
- */
-static
-dumb_ptr<map_session_data> script_rid2sd(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
- if (!sd)
- {
- PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt);
- }
- return sd;
-}
-
-/*==========================================
- * 変数の読み取り
- *------------------------------------------
- */
-static
-void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
-{
- MATCH (*data)
- {
- CASE (const ScriptDataParam&, u)
- {
- if (sd == nullptr)
- PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp());
- int numi = 0;
- if (sd)
- numi = pc_readparam(sd, u.reg.sp());
- *data = ScriptDataInt{numi};
- }
- CASE (const ScriptDataVariable&, u)
- {
- ZString name_ = variable_names.outtern(u.reg.base());
- VarName name = stringish<VarName>(name_);
- char prefix = name.front();
- char postfix = name.back();
-
- if (prefix != '$')
- {
- if (sd == nullptr)
- PRINTF("get_val error name?:%s\n"_fmt, name);
- }
- if (postfix == '$')
- {
- RString str;
- if (prefix == '@')
- {
- if (sd)
- str = pc_readregstr(sd, u.reg);
- }
- else if (prefix == '$')
- {
- RString *s = mapregstr_db.search(u.reg);
- if (s)
- str = *s;
- }
- else
- {
- PRINTF("script: get_val: illegal scope string variable.\n"_fmt);
- str = "!!ERROR!!"_s;
- }
- *data = ScriptDataStr{str};
- }
- else
- {
- int numi = 0;
- if (prefix == '@')
- {
- if (sd)
- numi = pc_readreg(sd, u.reg);
- }
- else if (prefix == '$')
- {
- numi = mapreg_db.get(u.reg);
- }
- else if (prefix == '#')
- {
- if (name[1] == '#')
- {
- if (sd)
- numi = pc_readaccountreg2(sd, name);
- }
- else
- {
- if (sd)
- numi = pc_readaccountreg(sd, name);
- }
- }
- else
- {
- if (sd)
- numi = pc_readglobalreg(sd, name);
- }
- *data = ScriptDataInt{numi};
- }
- }
- }
-}
-
-static __attribute__((deprecated))
-void get_val(ScriptState *st, struct script_data *data)
-{
- dumb_ptr<map_session_data> sd = st->rid ? map_id2sd(st->rid) : nullptr;
- get_val(sd, data);
-}
-
-/*==========================================
- * 変数の読み取り2
- *------------------------------------------
- */
-static
-struct script_data get_val2(ScriptState *st, SIR reg)
-{
- struct script_data dat = ScriptDataVariable{reg};
- get_val(st, &dat);
- return dat;
-}
-
-/*==========================================
- * 変数設定用
- *------------------------------------------
- */
-static
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd)
-{
- if (type == VariableCode::PARAM)
- {
- int val = vd.get_if<ScriptDataInt>()->numi;
- pc_setparam(sd, reg.sp(), val);
- return;
- }
- assert (type == VariableCode::VARIABLE);
-
- ZString name_ = variable_names.outtern(reg.base());
- VarName name = stringish<VarName>(name_);
- char prefix = name.front();
- char postfix = name.back();
-
- if (postfix == '$')
- {
- RString str = vd.get_if<ScriptDataStr>()->str;
- if (prefix == '@')
- {
- pc_setregstr(sd, reg, str);
- }
- else if (prefix == '$')
- {
- mapreg_setregstr(reg, str);
- }
- else
- {
- PRINTF("script: set_reg: illegal scope string variable !"_fmt);
- }
- }
- else
- {
- int val = vd.get_if<ScriptDataInt>()->numi;
- if (prefix == '@')
- {
- pc_setreg(sd, reg, val);
- }
- else if (prefix == '$')
- {
- mapreg_setreg(reg, val);
- }
- else if (prefix == '#')
- {
- if (name[1] == '#')
- pc_setaccountreg2(sd, name, val);
- else
- pc_setaccountreg(sd, name, val);
- }
- else
- {
- pc_setglobalreg(sd, name, val);
- }
- }
-}
-
-static
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id)
-{
- struct script_data vd = ScriptDataInt{id};
- set_reg(sd, type, reg, vd);
-}
-
-static
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd)
-{
- struct script_data vd = ScriptDataStr{zd};
- set_reg(sd, type, reg, vd);
-}
-
-/*==========================================
- * 文字列への変換
- *------------------------------------------
- */
-static __attribute__((warn_unused_result))
-RString conv_str(ScriptState *st, struct script_data *data)
-{
- get_val(st, data);
- assert (!data->is<ScriptDataRetInfo>());
- if (auto *u = data->get_if<ScriptDataInt>())
- {
- AString buf = STRPRINTF("%d"_fmt, u->numi);
- *data = ScriptDataStr{buf};
- }
- return data->get_if<ScriptDataStr>()->str;
-}
-
-/*==========================================
- * 数値へ変換
- *------------------------------------------
- */
-static __attribute__((warn_unused_result))
-int conv_num(ScriptState *st, struct script_data *data)
-{
- int rv = 0;
- get_val(st, data);
- assert (!data->is<ScriptDataRetInfo>());
- MATCH (*data)
- {
- default:
- abort();
- CASE (const ScriptDataStr&, u)
- {
- RString p = u.str;
- rv = atoi(p.c_str());
- }
- CASE (const ScriptDataInt&, u)
- {
- return u.numi;
- }
- CASE (const ScriptDataPos&, u)
- {
- return u.numi;
- }
- }
- *data = ScriptDataInt{rv};
- return rv;
-}
-
-static __attribute__((warn_unused_result))
-const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data)
-{
- get_val(st, data);
- return data->get_if<ScriptDataRetInfo>()->script;
-}
-
-
-template<class T>
-static
-void push_int(struct script_stack *stack, int val)
-{
- static_assert(first_type_is_any<T, ScriptDataPos, ScriptDataInt, ScriptDataArg, ScriptDataFuncRef>(), "not int type");
-
- script_data nsd = T{.numi= val};
- stack->stack_datav.push_back(nsd);
-}
-
-template<class T>
-static
-void push_reg(struct script_stack *stack, SIR reg)
-{
- static_assert(first_type_is_any<T, ScriptDataParam, ScriptDataVariable>(), "not reg type");
-
- script_data nsd = T{.reg= reg};
- stack->stack_datav.push_back(nsd);
-}
-
-template<class T>
-static
-void push_script(struct script_stack *stack, const ScriptBuffer *code)
-{
- static_assert(first_type_is_any<T, ScriptDataRetInfo>(), "not scriptbuf type");
-
- script_data nsd = T{.script= code};
- stack->stack_datav.push_back(nsd);
-}
-
-template<class T>
-static
-void push_str(struct script_stack *stack, RString str)
-{
- static_assert(first_type_is_any<T, ScriptDataStr>(), "not str type");
-
- script_data nsd = T{.str= str};
- stack->stack_datav.push_back(nsd);
-}
-
-static
-void push_copy(struct script_stack *stack, int pos_)
-{
- script_data csd = stack->stack_datav[pos_];
- stack->stack_datav.push_back(csd);
-}
-
-static
-void pop_stack(struct script_stack *stack, int start, int end)
-{
- auto it = stack->stack_datav.begin();
- stack->stack_datav.erase(it + start, it + end);
-}
-
-
-#define AARGO2(n) (st->stack->stack_datav[st->start + (n)])
-#define HARGO2(n) (st->end > st->start + (n))
-
-//
-// 埋め込み関数
-//
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_mes(ScriptState *st)
-{
- RString mes = conv_str(st, &AARGO2(2));
- clif_scriptmes(script_rid2sd(st), st->oid, mes);
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_goto(ScriptState *st)
-{
- if (!AARGO2(2).is<ScriptDataPos>())
- {
- PRINTF("script: goto: not label !\n"_fmt);
- st->state = ScriptEndState::END;
- return;
- }
-
- st->scriptp.pos = conv_num(st, &AARGO2(2));
- st->state = ScriptEndState::GOTO;
-}
-
-/*==========================================
- * ユーザー定義関数の呼び出し
- *------------------------------------------
- */
-static
-void builtin_callfunc(ScriptState *st)
-{
- RString str = conv_str(st, &AARGO2(2));
- const ScriptBuffer *scr = userfunc_db.get(str);
-
- if (scr)
- {
- int j = 0;
- assert (st->start + 3 == st->end);
-#if 0
- for (int i = st->start + 3; i < st->end; i++, j++)
- push_copy(st->stack, i);
-#endif
-
- push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ
- push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ
- push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ
- push_script<ScriptDataRetInfo>(st->stack, st->scriptp.code); // 現在のスクリプトをプッシュ
-
- st->scriptp = ScriptPointer(scr, 0);
- st->defsp = st->start + 4 + j;
- st->state = ScriptEndState::GOTO;
- }
- else
- {
- PRINTF("script:callfunc: function not found! [%s]\n"_fmt, str);
- st->state = ScriptEndState::END;
- }
-}
-
-/*==========================================
- * サブルーティンの呼び出し
- *------------------------------------------
- */
-static
-void builtin_callsub(ScriptState *st)
-{
- int pos_ = conv_num(st, &AARGO2(2));
- int j = 0;
- assert (st->start + 3 == st->end);
-#if 0
- for (int i = st->start + 3; i < st->end; i++, j++)
- push_copy(st->stack, i);
-#endif
-
- push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ
- push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ
- push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ
- push_script<ScriptDataRetInfo>(st->stack, st->scriptp.code); // 現在のスクリプトをプッシュ
-
- st->scriptp.pos = pos_;
- st->defsp = st->start + 4 + j;
- st->state = ScriptEndState::GOTO;
-}
-
-/*==========================================
- * サブルーチン/ユーザー定義関数の終了
- *------------------------------------------
- */
-static
-void builtin_return(ScriptState *st)
-{
-#if 0
- if (HARGO2(2))
- { // 戻り値有り
- push_copy(st->stack, st->start + 2);
- }
-#endif
- st->state = ScriptEndState::RETFUNC;
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_next(ScriptState *st)
-{
- st->state = ScriptEndState::STOP;
- clif_scriptnext(script_rid2sd(st), st->oid);
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_close(ScriptState *st)
-{
- st->state = ScriptEndState::END;
- clif_scriptclose(script_rid2sd(st), st->oid);
-}
-
-static
-void builtin_close2(ScriptState *st)
-{
- st->state = ScriptEndState::STOP;
- clif_scriptclose(script_rid2sd(st), st->oid);
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_menu(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- if (sd->state.menu_or_input == 0)
- {
- // First half: show menu.
- st->state = ScriptEndState::RERUNLINE;
- sd->state.menu_or_input = 1;
-
- MString buf;
- for (int i = st->start + 2; i < st->end; i += 2)
- {
- RString choice_str = conv_str(st, &AARGO2(i - st->start));
- if (!choice_str)
- break;
- buf += choice_str;
- buf += ':';
- }
-
- clif_scriptmenu(script_rid2sd(st), st->oid, AString(buf));
- }
- else
- {
- // Rerun: item is chosen from menu.
- if (sd->npc_menu == 0xff)
- {
- // cancel
- sd->state.menu_or_input = 0;
- st->state = ScriptEndState::END;
- return;
- }
-
- // Actually jump to the label.
- // Logic change: menu_choices is the *total* number of labels,
- // not just the displayed number that ends with the "".
- // (Would it be better to pop the stack before rerunning?)
- int menu_choices = (st->end - (st->start + 2)) / 2;
- pc_setreg(sd, SIR::from(variable_names.intern("@menu"_s)), sd->npc_menu);
- sd->state.menu_or_input = 0;
- if (sd->npc_menu > 0 && sd->npc_menu <= menu_choices)
- {
- int arg_index = (sd->npc_menu - 1) * 2 + 1;
- if (!AARGO2(arg_index + 2).is<ScriptDataPos>())
- {
- st->state = ScriptEndState::END;
- return;
- }
- st->scriptp.pos = AARGO2(arg_index + 2).get_if<ScriptDataPos>()->numi;
- st->state = ScriptEndState::GOTO;
- }
- }
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_rand(ScriptState *st)
-{
- if (HARGO2(3))
- {
- int min = conv_num(st, &AARGO2(2));
- int max = conv_num(st, &AARGO2(3));
- if (min > max)
- std::swap(max, min);
- push_int<ScriptDataInt>(st->stack, random_::in(min, max));
- }
- else
- {
- int range = conv_num(st, &AARGO2(2));
- push_int<ScriptDataInt>(st->stack, range <= 0 ? 0 : random_::to(range));
- }
-}
-
-/*==========================================
- * Check whether the PC is at the specified location
- *------------------------------------------
- */
-static
-void builtin_isat(ScriptState *st)
-{
- int x, y;
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x = conv_num(st, &AARGO2(3));
- y = conv_num(st, &AARGO2(4));
-
- if (!sd)
- return;
-
- push_int<ScriptDataInt>(st->stack,
- (x == sd->bl_x) && (y == sd->bl_y)
- && (str == sd->bl_m->name_));
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_warp(ScriptState *st)
-{
- int x, y;
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x = conv_num(st, &AARGO2(3));
- y = conv_num(st, &AARGO2(4));
- if (str == "Random"_s)
- pc_randomwarp(sd, BeingRemoveWhy::WARPED);
- else if (str == "SavePoint"_s or str == "Save"_s)
- {
- if (sd->bl_m->flag.get(MapFlag::NORETURN))
- return;
-
- pc_setpos(sd, sd->status.save_point.map_, sd->status.save_point.x, sd->status.save_point.y,
- BeingRemoveWhy::WARPED);
- }
- else
- pc_setpos(sd, str, x, y, BeingRemoveWhy::GONE);
-}
-
-/*==========================================
- * エリア指定ワープ
- *------------------------------------------
- */
-static
-void builtin_areawarp_sub(dumb_ptr<block_list> bl, MapName mapname, int x, int y)
-{
- dumb_ptr<map_session_data> sd = bl->is_player();
- if (mapname == "Random"_s)
- pc_randomwarp(sd, BeingRemoveWhy::WARPED);
- else
- pc_setpos(sd, mapname, x, y, BeingRemoveWhy::GONE);
-}
-
-static
-void builtin_areawarp(ScriptState *st)
-{
- int x, y;
- int x0, y0, x1, y1;
-
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x0 = conv_num(st, &AARGO2(3));
- y0 = conv_num(st, &AARGO2(4));
- x1 = conv_num(st, &AARGO2(5));
- y1 = conv_num(st, &AARGO2(6));
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(7))));
- x = conv_num(st, &AARGO2(8));
- y = conv_num(st, &AARGO2(9));
-
- map_local *m = map_mapname2mapid(mapname);
- if (m == nullptr)
- return;
-
- map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y),
- m,
- x0, y0,
- x1, y1,
- BL::PC);
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_heal(ScriptState *st)
-{
- int hp, sp;
-
- hp = conv_num(st, &AARGO2(2));
- sp = conv_num(st, &AARGO2(3));
- pc_heal(script_rid2sd(st), hp, sp);
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_itemheal(ScriptState *st)
-{
- int hp, sp;
-
- hp = conv_num(st, &AARGO2(2));
- sp = conv_num(st, &AARGO2(3));
- pc_itemheal(script_rid2sd(st), hp, sp);
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_percentheal(ScriptState *st)
-{
- int hp, sp;
-
- hp = conv_num(st, &AARGO2(2));
- sp = conv_num(st, &AARGO2(3));
- pc_percentheal(script_rid2sd(st), hp, sp);
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_input(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = nullptr;
- script_data& scrd = AARGO2(2);
- assert (scrd.is<ScriptDataVariable>());
-
- SIR reg = scrd.get_if<ScriptDataVariable>()->reg;
- ZString name = variable_names.outtern(reg.base());
-// char prefix = name.front();
- char postfix = name.back();
-
- sd = script_rid2sd(st);
- if (sd->state.menu_or_input)
- {
- // Second time (rerun)
- sd->state.menu_or_input = 0;
- if (postfix == '$')
- {
- set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_str);
- }
- else
- {
- //commented by Lupus (check Value Number Input fix in clif.c)
- //** Fix by fritz :X keeps people from abusing old input bugs
- // wtf?
- if (sd->npc_amount < 0) //** If input amount is less then 0
- {
- clif_tradecancelled(sd); // added "Deal has been cancelled" message by Valaris
- builtin_close(st); //** close
- }
-
- set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_amount);
- }
- }
- else
- {
- // First time - send prompt to client, then wait
- st->state = ScriptEndState::RERUNLINE;
- if (postfix == '$')
- clif_scriptinputstr(sd, st->oid);
- else
- clif_scriptinput(sd, st->oid);
- sd->state.menu_or_input = 1;
- }
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_if (ScriptState *st)
-{
- int sel, i;
-
- sel = conv_num(st, &AARGO2(2));
- if (!sel)
- return;
-
- // 関数名をコピー
- push_copy(st->stack, st->start + 3);
- // 間に引数マーカを入れて
- push_int<ScriptDataArg>(st->stack, 0);
- // 残りの引数をコピー
- for (i = st->start + 4; i < st->end; i++)
- {
- push_copy(st->stack, i);
- }
- run_func(st);
-}
-
-/*==========================================
- * 変数設定
- *------------------------------------------
- */
-static
-void builtin_set(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = nullptr;
- if (auto *u = AARGO2(2).get_if<ScriptDataParam>())
- {
- SIR reg = u->reg;
- sd = script_rid2sd(st);
-
- int val = conv_num(st, &AARGO2(3));
- set_reg(sd, VariableCode::PARAM, reg, val);
- return;
- }
-
- SIR reg = AARGO2(2).get_if<ScriptDataVariable>()->reg;
-
- ZString name = variable_names.outtern(reg.base());
- char prefix = name.front();
- char postfix = name.back();
-
- if (prefix != '$')
- sd = script_rid2sd(st);
-
- if (postfix == '$')
- {
- // 文字列
- RString str = conv_str(st, &AARGO2(3));
- set_reg(sd, VariableCode::VARIABLE, reg, str);
- }
- else
- {
- // 数値
- int val = conv_num(st, &AARGO2(3));
- set_reg(sd, VariableCode::VARIABLE, reg, val);
- }
-
-}
-
-/*==========================================
- * 配列変数設定
- *------------------------------------------
- */
-static
-void builtin_setarray(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = nullptr;
- SIR reg = AARGO2(2).get_if<ScriptDataVariable>()->reg;
- ZString name = variable_names.outtern(reg.base());
- char prefix = name.front();
- char postfix = name.back();
-
- if (prefix != '$' && prefix != '@')
- {
- PRINTF("builtin_setarray: illegal scope !\n"_fmt);
- return;
- }
- if (prefix != '$')
- sd = script_rid2sd(st);
-
- for (int j = 0, i = st->start + 3; i < st->end && j < 256; i++, j++)
- {
- if (postfix == '$')
- set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARGO2(i - st->start)));
- else
- set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARGO2(i - st->start)));
- }
-}
-
-/*==========================================
- * 配列変数クリア
- *------------------------------------------
- */
-static
-void builtin_cleararray(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = nullptr;
- SIR reg = AARGO2(2).get_if<ScriptDataVariable>()->reg;
- ZString name = variable_names.outtern(reg.base());
- char prefix = name.front();
- char postfix = name.back();
- int sz = conv_num(st, &AARGO2(4));
-
- if (prefix != '$' && prefix != '@')
- {
- PRINTF("builtin_cleararray: illegal scope !\n"_fmt);
- return;
- }
- if (prefix != '$')
- sd = script_rid2sd(st);
-
- for (int i = 0; i < sz; i++)
- {
- if (postfix == '$')
- set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARGO2(3)));
- else
- set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARGO2(3)));
- }
-
-}
-
-/*==========================================
- * 配列変数のサイズ所得
- *------------------------------------------
- */
-static
-int getarraysize(ScriptState *st, SIR reg)
-{
- int i = reg.index(), c = i;
- for (; i < 256; i++)
- {
- struct script_data vd = get_val2(st, reg.iplus(i));
- MATCH (vd)
- {
- CASE (const ScriptDataStr&, u)
- {
- if (u.str[0])
- c = i;
- goto continue_outer;
- }
- CASE (const ScriptDataInt&, u)
- {
- if (u.numi)
- c = i;
- goto continue_outer;
- }
- }
- abort();
- continue_outer:
- ;
- }
- return c + 1;
-}
-
-static
-void builtin_getarraysize(ScriptState *st)
-{
- SIR reg = AARGO2(2).get_if<ScriptDataVariable>()->reg;
- ZString name = variable_names.outtern(reg.base());
- char prefix = name.front();
-
- if (prefix != '$' && prefix != '@')
- {
- PRINTF("builtin_copyarray: illegal scope !\n"_fmt);
- return;
- }
-
- push_int<ScriptDataInt>(st->stack, getarraysize(st, reg));
-}
-
-/*==========================================
- * 指定要素を表す値(キー)を所得する
- *------------------------------------------
- */
-static
-void builtin_getelementofarray(ScriptState *st)
-{
- if (auto *u = AARGO2(2).get_if<ScriptDataVariable>())
- {
- int i = conv_num(st, &AARGO2(3));
- if (i > 255 || i < 0)
- {
- PRINTF("script: getelementofarray (operator[]): param2 illegal number %d\n"_fmt,
- i);
- push_int<ScriptDataInt>(st->stack, 0);
- }
- else
- {
- push_reg<ScriptDataVariable>(st->stack,
- u->reg.iplus(i));
- }
- }
- else
- {
- PRINTF("script: getelementofarray (operator[]): param1 not name !\n"_fmt);
- push_int<ScriptDataInt>(st->stack, 0);
- }
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_setlook(ScriptState *st)
-{
- LOOK type = LOOK(conv_num(st, &AARGO2(2)));
- int val = conv_num(st, &AARGO2(3));
-
- pc_changelook(script_rid2sd(st), type, val);
-
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_countitem(ScriptState *st)
-{
- ItemNameId nameid;
- int count = 0;
- dumb_ptr<map_session_data> sd;
-
- struct script_data *data;
-
- sd = script_rid2sd(st);
-
- data = &AARGO2(2);
- get_val(st, data);
- if (data->is<ScriptDataStr>())
- {
- ZString name = ZString(conv_str(st, data));
- struct item_data *item_data = itemdb_searchname(name);
- if (item_data != nullptr)
- nameid = item_data->nameid;
- }
- else
- nameid = wrap<ItemNameId>(conv_num(st, data));
-
- if (nameid)
- {
- for (IOff0 i : IOff0::iter())
- {
- if (sd->status.inventory[i].nameid == nameid)
- count += sd->status.inventory[i].amount;
- }
- }
- else
- {
- if (battle_config.error_log)
- PRINTF("wrong item ID : countitem (%i)\n"_fmt, nameid);
- }
- push_int<ScriptDataInt>(st->stack, count);
-
-}
-
-/*==========================================
- * 重量チェック
- *------------------------------------------
- */
-static
-void builtin_checkweight(ScriptState *st)
-{
- ItemNameId nameid;
- int amount;
- dumb_ptr<map_session_data> sd;
- struct script_data *data;
-
- sd = script_rid2sd(st);
-
- data = &AARGO2(2);
- get_val(st, data);
- if (data->is<ScriptDataStr>())
- {
- ZString name = ZString(conv_str(st, data));
- struct item_data *item_data = itemdb_searchname(name);
- if (item_data)
- nameid = item_data->nameid;
- }
- else
- nameid = wrap<ItemNameId>(conv_num(st, data));
-
- amount = conv_num(st, &AARGO2(3));
- if (amount <= 0 || !nameid)
- {
- //if get wrong item ID or amount<=0, don't count weight of non existing items
- push_int<ScriptDataInt>(st->stack, 0);
- return;
- }
-
- if (itemdb_weight(nameid) * amount + sd->weight > sd->max_weight)
- {
- push_int<ScriptDataInt>(st->stack, 0);
- }
- else
- {
- push_int<ScriptDataInt>(st->stack, 1);
- }
-
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_getitem(ScriptState *st)
-{
- ItemNameId nameid;
- int amount;
- dumb_ptr<map_session_data> sd;
- struct script_data *data;
-
- sd = script_rid2sd(st);
-
- data = &AARGO2(2);
- get_val(st, data);
- if (data->is<ScriptDataStr>())
- {
- ZString name = ZString(conv_str(st, data));
- struct item_data *item_data = itemdb_searchname(name);
- if (item_data != nullptr)
- nameid = item_data->nameid;
- }
- else
- nameid = wrap<ItemNameId>(conv_num(st, data));
-
- if ((amount =
- conv_num(st, &AARGO2(3))) <= 0)
- {
- return; //return if amount <=0, skip the useles iteration
- }
-
- if (nameid)
- {
- Item item_tmp {};
- item_tmp.nameid = nameid;
- if (HARGO2(5)) //アイテムを指定したIDに渡す
- sd = map_id2sd(wrap<BlockId>(conv_num(st, &AARGO2(5))));
- if (sd == nullptr) //アイテムを渡す相手がいなかったらお帰り
- return;
- PickupFail flag;
- if ((flag = pc_additem(sd, &item_tmp, amount)) != PickupFail::OKAY)
- {
- clif_additem(sd, IOff0::from(0), 0, flag);
- map_addflooritem(&item_tmp, amount,
- sd->bl_m, sd->bl_x, sd->bl_y,
- nullptr, nullptr, nullptr);
- }
- }
-
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_makeitem(ScriptState *st)
-{
- ItemNameId nameid;
- int amount;
- int x, y;
- dumb_ptr<map_session_data> sd;
- struct script_data *data;
-
- sd = script_rid2sd(st);
-
- data = &AARGO2(2);
- get_val(st, data);
- if (data->is<ScriptDataStr>())
- {
- ZString name = ZString(conv_str(st, data));
- struct item_data *item_data = itemdb_searchname(name);
- if (item_data)
- nameid = item_data->nameid;
- }
- else
- nameid = wrap<ItemNameId>(conv_num(st, data));
-
- amount = conv_num(st, &AARGO2(3));
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(4))));
- x = conv_num(st, &AARGO2(5));
- y = conv_num(st, &AARGO2(6));
-
- map_local *m;
- if (sd && mapname == MOB_THIS_MAP)
- m = sd->bl_m;
- else
- m = map_mapname2mapid(mapname);
-
- if (nameid)
- {
- Item item_tmp {};
- item_tmp.nameid = nameid;
-
- map_addflooritem(&item_tmp, amount, m, x, y, nullptr, nullptr, nullptr);
- }
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_delitem(ScriptState *st)
-{
- ItemNameId nameid;
- int amount;
- dumb_ptr<map_session_data> sd;
- struct script_data *data;
-
- sd = script_rid2sd(st);
-
- data = &AARGO2(2);
- get_val(st, data);
- if (data->is<ScriptDataStr>())
- {
- ZString name = ZString(conv_str(st, data));
- struct item_data *item_data = itemdb_searchname(name);
- if (item_data)
- nameid = item_data->nameid;
- }
- else
- nameid = wrap<ItemNameId>(conv_num(st, data));
-
- amount = conv_num(st, &AARGO2(3));
-
- if (!nameid || amount <= 0)
- {
- //by Lupus. Don't run FOR if u got wrong item ID or amount<=0
- return;
- }
-
- for (IOff0 i : IOff0::iter())
- {
- if (sd->status.inventory[i].nameid == nameid)
- {
- if (sd->status.inventory[i].amount >= amount)
- {
- pc_delitem(sd, i, amount, 0);
- break;
- }
- else
- {
- amount -= sd->status.inventory[i].amount;
- if (amount == 0)
- amount = sd->status.inventory[i].amount;
- pc_delitem(sd, i, amount, 0);
- break;
- }
- }
- }
-
-}
-
-/*==========================================
- *キャラ関係のパラメータ取得
- *------------------------------------------
- */
-static
-void builtin_readparam(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd;
-
- SP type = SP(conv_num(st, &AARGO2(2)));
- if (HARGO2(3))
- sd = map_nick2sd(stringish<CharName>(ZString(conv_str(st, &AARGO2(3)))));
- else
- sd = script_rid2sd(st);
-
- if (sd == nullptr)
- {
- push_int<ScriptDataInt>(st->stack, -1);
- return;
- }
-
- push_int<ScriptDataInt>(st->stack, pc_readparam(sd, type));
-
-}
-
-/*==========================================
- *キャラ関係のID取得
- *------------------------------------------
- */
-static
-void builtin_getcharid(ScriptState *st)
-{
- int num;
- dumb_ptr<map_session_data> sd;
-
- num = conv_num(st, &AARGO2(2));
- if (HARGO2(3))
- sd = map_nick2sd(stringish<CharName>(ZString(conv_str(st, &AARGO2(3)))));
- else
- sd = script_rid2sd(st);
- if (sd == nullptr)
- {
- push_int<ScriptDataInt>(st->stack, -1);
- return;
- }
- if (num == 0)
- push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status_key.char_id));
- if (num == 1)
- push_int<ScriptDataInt>(st->stack, unwrap<PartyId>(sd->status.party_id));
- if (num == 2)
- push_int<ScriptDataInt>(st->stack, 0/*guild_id*/);
- if (num == 3)
- push_int<ScriptDataInt>(st->stack, unwrap<AccountId>(sd->status_key.account_id));
-}
-
-/*==========================================
- *指定IDのPT名取得
- *------------------------------------------
- */
-static
-RString builtin_getpartyname_sub(PartyId party_id)
-{
- PartyPair p = party_search(party_id);
-
- if (p)
- return p->name;
-
- return RString();
-}
-
-/*==========================================
- * キャラクタの名前
- *------------------------------------------
- */
-static
-void builtin_strcharinfo(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd;
- int num;
-
- sd = script_rid2sd(st);
- num = conv_num(st, &AARGO2(2));
- if (num == 0)
- {
- RString buf = sd->status_key.name.to__actual();
- push_str<ScriptDataStr>(st->stack, buf);
- }
- if (num == 1)
- {
- RString buf = builtin_getpartyname_sub(sd->status.party_id);
- if (buf)
- push_str<ScriptDataStr>(st->stack, buf);
- else
- push_str<ScriptDataStr>(st->stack, ""_s);
- }
- if (num == 2)
- {
- // was: guild name
- push_str<ScriptDataStr>(st->stack, ""_s);
- }
-
-}
-
-// indexed by the equip_* in db/const.txt
-// TODO change to use EQUIP
-static
-Array<EPOS, 11> equip //=
-{{
- EPOS::HAT,
- EPOS::MISC1,
- EPOS::SHIELD,
- EPOS::WEAPON,
- EPOS::GLOVES,
- EPOS::SHOES,
- EPOS::CAPE,
- EPOS::MISC2,
- EPOS::TORSO,
- EPOS::LEGS,
- EPOS::ARROW,
-}};
-
-/*==========================================
- * GetEquipID(Pos); Pos: 1-10
- *------------------------------------------
- */
-static
-void builtin_getequipid(ScriptState *st)
-{
- int num;
- dumb_ptr<map_session_data> sd;
- struct item_data *item;
-
- sd = script_rid2sd(st);
- if (sd == nullptr)
- {
- PRINTF("getequipid: sd == nullptr\n"_fmt);
- return;
- }
- num = conv_num(st, &AARGO2(2));
- IOff0 i = pc_checkequip(sd, equip[num - 1]);
- if (i.ok())
- {
- item = sd->inventory_data[i];
- if (item)
- push_int<ScriptDataInt>(st->stack, unwrap<ItemNameId>(item->nameid));
- else
- push_int<ScriptDataInt>(st->stack, 0);
- }
- else
- {
- push_int<ScriptDataInt>(st->stack, -1);
- }
-}
-
-/*==========================================
- * 装備名文字列(精錬メニュー用)
- *------------------------------------------
- */
-static
-void builtin_getequipname(ScriptState *st)
-{
- int num;
- dumb_ptr<map_session_data> sd;
- struct item_data *item;
-
- AString buf;
-
- sd = script_rid2sd(st);
- num = conv_num(st, &AARGO2(2));
- IOff0 i = pc_checkequip(sd, equip[num - 1]);
- if (i.ok())
- {
- item = sd->inventory_data[i];
- if (item)
- buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], item->jname);
- else
- buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]);
- }
- else
- {
- buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]);
- }
- push_str<ScriptDataStr>(st->stack, buf);
-
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_statusup2(ScriptState *st)
-{
- SP type = SP(conv_num(st, &AARGO2(2)));
- int val = conv_num(st, &AARGO2(3));
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- pc_statusup2(sd, type, val);
-
-}
-
-/*==========================================
- * 装備品による能力値ボーナス
- *------------------------------------------
- */
-static
-void builtin_bonus(ScriptState *st)
-{
- SP type = SP(conv_num(st, &AARGO2(2)));
- int val = conv_num(st, &AARGO2(3));
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- pc_bonus(sd, type, val);
-
-}
-
-/*==========================================
- * 装備品による能力値ボーナス
- *------------------------------------------
- */
-static
-void builtin_bonus2(ScriptState *st)
-{
- SP type = SP(conv_num(st, &AARGO2(2)));
- int type2 = conv_num(st, &AARGO2(3));
- int val = conv_num(st, &AARGO2(4));
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- pc_bonus2(sd, type, type2, val);
-
-}
-
-/*==========================================
- * スキル所得
- *------------------------------------------
- */
-static
-void builtin_skill(ScriptState *st)
-{
- int level, flag = 1;
- dumb_ptr<map_session_data> sd;
-
- SkillID id = SkillID(conv_num(st, &AARGO2(2)));
- level = conv_num(st, &AARGO2(3));
- if (HARGO2(4))
- flag = conv_num(st, &AARGO2(4));
- sd = script_rid2sd(st);
- pc_skill(sd, id, level, flag);
- clif_skillinfoblock(sd);
-
-}
-
-/*==========================================
- * [Fate] Sets the skill level permanently
- *------------------------------------------
- */
-static
-void builtin_setskill(ScriptState *st)
-{
- int level;
- dumb_ptr<map_session_data> sd;
-
- SkillID id = static_cast<SkillID>(conv_num(st, &AARGO2(2)));
- level = conv_num(st, &AARGO2(3));
- sd = script_rid2sd(st);
-
- level = std::min(level, MAX_SKILL_LEVEL);
- level = std::max(level, 0);
- sd->status.skill[id].lv = level;
- clif_skillinfoblock(sd);
-}
-
-/*==========================================
- * スキルレベル所得
- *------------------------------------------
- */
-static
-void builtin_getskilllv(ScriptState *st)
-{
- SkillID id = SkillID(conv_num(st, &AARGO2(2)));
- push_int<ScriptDataInt>(st->stack, pc_checkskill(script_rid2sd(st), id));
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_getgmlevel(ScriptState *st)
-{
- push_int<ScriptDataInt>(st->stack, pc_isGM(script_rid2sd(st)).get_all_bits());
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_end(ScriptState *st)
-{
- st->state = ScriptEndState::END;
-}
-
-/*==========================================
- * [Freeyorp] Return the current opt2
- *------------------------------------------
- */
-
-static
-void builtin_getopt2(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd;
-
- sd = script_rid2sd(st);
-
- push_int<ScriptDataInt>(st->stack, static_cast<uint16_t>(sd->opt2));
-
-}
-
-/*==========================================
- * [Freeyorp] Sets opt2
- *------------------------------------------
- */
-
-static
-void builtin_setopt2(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd;
-
- Opt2 new_opt2 = Opt2(conv_num(st, &AARGO2(2)));
- sd = script_rid2sd(st);
- if (new_opt2 == sd->opt2)
- return;
- sd->opt2 = new_opt2;
- clif_changeoption(sd);
- pc_calcstatus(sd, 0);
-
-}
-
-/*==========================================
- * セーブポイントの保存
- *------------------------------------------
- */
-static
-void builtin_savepoint(ScriptState *st)
-{
- int x, y;
-
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x = conv_num(st, &AARGO2(3));
- y = conv_num(st, &AARGO2(4));
- pc_setsavepoint(script_rid2sd(st), str, x, y);
-}
-
-/*==========================================
- * gettimetick(type)
- *
- * type The type of time measurement.
- * Specify 0 for the system tick, 1 for
- * seconds elapsed today, or 2 for seconds
- * since Unix epoch. Defaults to 0 for any
- * other value.
- *------------------------------------------
- */
-static
-void builtin_gettimetick(ScriptState *st) /* Asgard Version */
-{
- int type;
- type = conv_num(st, &AARGO2(2));
-
- switch (type)
- {
- /* Number of seconds elapsed today(0-86399, 00:00:00-23:59:59). */
- case 1:
- {
- struct tm t = TimeT::now();
- push_int<ScriptDataInt>(st->stack,
- t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec);
- break;
- }
- /* Seconds since Unix epoch. */
- case 2:
- push_int<ScriptDataInt>(st->stack, static_cast<time_t>(TimeT::now()));
- break;
- /* System tick(unsigned int, and yes, it will wrap). */
- case 0:
- default:
- push_int<ScriptDataInt>(st->stack, gettick().time_since_epoch().count());
- break;
- }
-}
-
-/*==========================================
- * GetTime(Type);
- * 1: Sec 2: Min 3: Hour
- * 4: WeekDay 5: MonthDay 6: Month
- * 7: Year
- *------------------------------------------
- */
-static
-void builtin_gettime(ScriptState *st) /* Asgard Version */
-{
- int type = conv_num(st, &AARGO2(2));
-
- struct tm t = TimeT::now();
-
- switch (type)
- {
- case 1: //Sec(0~59)
- push_int<ScriptDataInt>(st->stack, t.tm_sec);
- break;
- case 2: //Min(0~59)
- push_int<ScriptDataInt>(st->stack, t.tm_min);
- break;
- case 3: //Hour(0~23)
- push_int<ScriptDataInt>(st->stack, t.tm_hour);
- break;
- case 4: //WeekDay(0~6)
- push_int<ScriptDataInt>(st->stack, t.tm_wday);
- break;
- case 5: //MonthDay(01~31)
- push_int<ScriptDataInt>(st->stack, t.tm_mday);
- break;
- case 6: //Month(01~12)
- push_int<ScriptDataInt>(st->stack, t.tm_mon + 1);
- break;
- case 7: //Year(20xx)
- push_int<ScriptDataInt>(st->stack, t.tm_year + 1900);
- break;
- default: //(format error)
- push_int<ScriptDataInt>(st->stack, -1);
- break;
- }
-}
-
-/*==========================================
- * カプラ倉庫を開く
- *------------------------------------------
- */
-static
-void builtin_openstorage(ScriptState *st)
-{
-// int sync = 0;
-// if (st->end >= 3) sync = conv_num(st,& (st->stack->stack_data[st->start+2]));
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
-// if (sync) {
- st->state = ScriptEndState::STOP;
- sd->npc_flags.storage = 1;
-// } else st->state = ScriptEndState::END;
-
- storage_storageopen(sd);
-}
-
-/*==========================================
- * NPCで経験値上げる
- *------------------------------------------
- */
-static
-void builtin_getexp(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- int base = 0, job = 0;
-
- base = conv_num(st, &AARGO2(2));
- job = conv_num(st, &AARGO2(3));
- if (base < 0 || job < 0)
- return;
- if (sd)
- pc_gainexp_reason(sd, base, job, PC_GAINEXP_REASON::SCRIPT);
-
-}
-
-/*==========================================
- * モンスター発生
- *------------------------------------------
- */
-static
-void builtin_monster(ScriptState *st)
-{
- Species mob_class;
- int amount, x, y;
- NpcEvent event;
-
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x = conv_num(st, &AARGO2(3));
- y = conv_num(st, &AARGO2(4));
- MobName str = stringish<MobName>(ZString(conv_str(st, &AARGO2(5))));
- mob_class = wrap<Species>(conv_num(st, &AARGO2(6)));
- amount = conv_num(st, &AARGO2(7));
- if (HARGO2(8))
- extract(ZString(conv_str(st, &AARGO2(8))), &event);
-
- mob_once_spawn(map_id2sd(st->rid), mapname, x, y, str, mob_class, amount,
- event);
-}
-
-/*==========================================
- * モンスター発生
- *------------------------------------------
- */
-static
-void builtin_areamonster(ScriptState *st)
-{
- Species mob_class;
- int amount, x0, y0, x1, y1;
- NpcEvent event;
-
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x0 = conv_num(st, &AARGO2(3));
- y0 = conv_num(st, &AARGO2(4));
- x1 = conv_num(st, &AARGO2(5));
- y1 = conv_num(st, &AARGO2(6));
- MobName str = stringish<MobName>(ZString(conv_str(st, &AARGO2(7))));
- mob_class = wrap<Species>(conv_num(st, &AARGO2(8)));
- amount = conv_num(st, &AARGO2(9));
- if (HARGO2(10))
- extract(ZString(conv_str(st, &AARGO2(10))), &event);
-
- mob_once_spawn_area(map_id2sd(st->rid), mapname, x0, y0, x1, y1, str, mob_class,
- amount, event);
-}
-
-/*==========================================
- * モンスター削除
- *------------------------------------------
- */
-static
-void builtin_killmonster_sub(dumb_ptr<block_list> bl, NpcEvent event)
-{
- dumb_ptr<mob_data> md = bl->is_mob();
- if (event)
- {
- if (event == md->npc_event)
- mob_delete(md);
- return;
- }
- else if (!event)
- {
- if (md->spawn.delay1 == static_cast<interval_t>(-1)
- && md->spawn.delay2 == static_cast<interval_t>(-1))
- mob_delete(md);
- return;
- }
-}
-
-static
-void builtin_killmonster(ScriptState *st)
-{
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- ZString event_ = ZString(conv_str(st, &AARGO2(3)));
- NpcEvent event;
- if (event_ != "All"_s)
- extract(event_, &event);
-
- map_local *m = map_mapname2mapid(mapname);
- if (m == nullptr)
- return;
- map_foreachinarea(std::bind(builtin_killmonster_sub, ph::_1, event),
- m,
- 0, 0,
- m->xs, m->ys,
- BL::MOB);
-}
-
-static
-void builtin_killmonsterall_sub(dumb_ptr<block_list> bl)
-{
- mob_delete(bl->is_mob());
-}
-
-static
-void builtin_killmonsterall(ScriptState *st)
-{
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
-
- map_local *m = map_mapname2mapid(mapname);
- if (m == nullptr)
- return;
- map_foreachinarea(builtin_killmonsterall_sub,
- m,
- 0, 0,
- m->xs, m->ys,
- BL::MOB);
-}
-
-/*==========================================
- * NPC主体イベント実行
- *------------------------------------------
- */
-static
-void builtin_donpcevent(ScriptState *st)
-{
- ZString event_ = ZString(conv_str(st, &AARGO2(2)));
- NpcEvent event;
- extract(event_, &event);
- npc_event_do(event);
-}
-
-/*==========================================
- * イベントタイマー追加
- *------------------------------------------
- */
-static
-void builtin_addtimer(ScriptState *st)
-{
- interval_t tick = static_cast<interval_t>(conv_num(st, &AARGO2(2)));
- ZString event_ = ZString(conv_str(st, &AARGO2(3)));
- NpcEvent event;
- extract(event_, &event);
- pc_addeventtimer(script_rid2sd(st), tick, event);
-}
-
-/*==========================================
- * NPCタイマー初期化
- *------------------------------------------
- */
-static
-void builtin_initnpctimer(ScriptState *st)
-{
- dumb_ptr<npc_data> nd_;
- if (HARGO2(2))
- nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(2)))));
- else
- nd_ = map_id_is_npc(st->oid);
- assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
- dumb_ptr<npc_data_script> nd = nd_->is_script();
-
- npc_settimerevent_tick(nd, interval_t::zero());
- npc_timerevent_start(nd);
-}
-
-/*==========================================
- * NPCタイマー開始
- *------------------------------------------
- */
-static
-void builtin_startnpctimer(ScriptState *st)
-{
- dumb_ptr<npc_data> nd_;
- if (HARGO2(2))
- nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(2)))));
- else
- nd_ = map_id_is_npc(st->oid);
- assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
- dumb_ptr<npc_data_script> nd = nd_->is_script();
-
- npc_timerevent_start(nd);
-}
-
-/*==========================================
- * NPCタイマー停止
- *------------------------------------------
- */
-static
-void builtin_stopnpctimer(ScriptState *st)
-{
- dumb_ptr<npc_data> nd_;
- if (HARGO2(2))
- nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(2)))));
- else
- nd_ = map_id_is_npc(st->oid);
- assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
- dumb_ptr<npc_data_script> nd = nd_->is_script();
-
- npc_timerevent_stop(nd);
-}
-
-/*==========================================
- * NPCタイマー情報所得
- *------------------------------------------
- */
-static
-void builtin_getnpctimer(ScriptState *st)
-{
- dumb_ptr<npc_data> nd_;
- int type = conv_num(st, &AARGO2(2));
- int val = 0;
- if (HARGO2(3))
- nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(3)))));
- else
- nd_ = map_id_is_npc(st->oid);
- assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
- dumb_ptr<npc_data_script> nd = nd_->is_script();
-
- switch (type)
- {
- case 0:
- val = npc_gettimerevent_tick(nd).count();
- break;
- case 1:
- val = nd->scr.timer_active;
- break;
- case 2:
- val = nd->scr.timer_eventv.size();
- break;
- }
- push_int<ScriptDataInt>(st->stack, val);
-}
-
-/*==========================================
- * NPCタイマー値設定
- *------------------------------------------
- */
-static
-void builtin_setnpctimer(ScriptState *st)
-{
- dumb_ptr<npc_data> nd_;
- interval_t tick = static_cast<interval_t>(conv_num(st, &AARGO2(2)));
- if (HARGO2(3))
- nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(3)))));
- else
- nd_ = map_id_is_npc(st->oid);
- assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT);
- dumb_ptr<npc_data_script> nd = nd_->is_script();
-
- npc_settimerevent_tick(nd, tick);
-}
-
-/*==========================================
- * 天の声アナウンス
- *------------------------------------------
- */
-static
-void builtin_announce(ScriptState *st)
-{
- int flag;
- ZString str = ZString(conv_str(st, &AARGO2(2)));
- flag = conv_num(st, &AARGO2(3));
-
- if (flag & 0x0f)
- {
- dumb_ptr<block_list> bl;
- if (flag & 0x08)
- bl = map_id2bl(st->oid);
- else
- bl = script_rid2sd(st);
- clif_GMmessage(bl, str, flag);
- }
- else
- intif_GMmessage(str);
-}
-
-/*==========================================
- * 天の声アナウンス(特定マップ)
- *------------------------------------------
- */
-static
-void builtin_mapannounce_sub(dumb_ptr<block_list> bl, XString str, int flag)
-{
- clif_GMmessage(bl, str, flag | 3);
-}
-
-static
-void builtin_mapannounce(ScriptState *st)
-{
- int flag;
-
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- ZString str = ZString(conv_str(st, &AARGO2(3)));
- flag = conv_num(st, &AARGO2(4));
-
- map_local *m = map_mapname2mapid(mapname);
- if (m == nullptr)
- return;
- map_foreachinarea(std::bind(builtin_mapannounce_sub, ph::_1, str, flag & 0x10),
- m,
- 0, 0,
- m->xs, m->ys,
- BL::PC);
-}
-
-/*==========================================
- * ユーザー数所得
- *------------------------------------------
- */
-static
-void builtin_getusers(ScriptState *st)
-{
- int flag = conv_num(st, &AARGO2(2));
- dumb_ptr<block_list> bl = map_id2bl((flag & 0x08) ? st->oid : st->rid);
- int val = 0;
- switch (flag & 0x07)
- {
- case 0:
- val = bl->bl_m->users;
- break;
- case 1:
- val = map_getusers();
- break;
- }
- push_int<ScriptDataInt>(st->stack, val);
-}
-
-/*==========================================
- * マップ指定ユーザー数所得
- *------------------------------------------
- */
-static
-void builtin_getmapusers(ScriptState *st)
-{
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- map_local *m = map_mapname2mapid(str);
- if (m == nullptr)
- {
- push_int<ScriptDataInt>(st->stack, -1);
- return;
- }
- push_int<ScriptDataInt>(st->stack, m->users);
-}
-
-/*==========================================
- * エリア指定ユーザー数所得
- *------------------------------------------
- */
-static
-void builtin_getareausers_sub(dumb_ptr<block_list> bl, int *users)
-{
- if (bool(bl->is_player()->status.option & Option::HIDE))
- return;
- (*users)++;
-}
-
-static
-void builtin_getareausers_living_sub(dumb_ptr<block_list> bl, int *users)
-{
- if (bool(bl->is_player()->status.option & Option::HIDE))
- return;
- if (!pc_isdead(bl->is_player()))
- (*users)++;
-}
-
-static
-void builtin_getareausers(ScriptState *st)
-{
- int x0, y0, x1, y1, users = 0;
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x0 = conv_num(st, &AARGO2(3));
- y0 = conv_num(st, &AARGO2(4));
- x1 = conv_num(st, &AARGO2(5));
- y1 = conv_num(st, &AARGO2(6));
-
- int living = 0;
- if (HARGO2(7))
- {
- living = conv_num(st, &AARGO2(7));
- }
- map_local *m = map_mapname2mapid(str);
- if (m == nullptr)
- {
- push_int<ScriptDataInt>(st->stack, -1);
- return;
- }
- map_foreachinarea(std::bind(living ? builtin_getareausers_living_sub: builtin_getareausers_sub, ph::_1, &users),
- m,
- x0, y0,
- x1, y1,
- BL::PC);
- push_int<ScriptDataInt>(st->stack, users);
-}
-
-/*==========================================
- * エリア指定ドロップアイテム数所得
- *------------------------------------------
- */
-static
-void builtin_getareadropitem_sub(dumb_ptr<block_list> bl, ItemNameId item, int *amount)
-{
- dumb_ptr<flooritem_data> drop = bl->is_item();
-
- if (drop->item_data.nameid == item)
- (*amount) += drop->item_data.amount;
-
-}
-
-static
-void builtin_getareadropitem_sub_anddelete(dumb_ptr<block_list> bl, ItemNameId item, int *amount)
-{
- dumb_ptr<flooritem_data> drop = bl->is_item();
-
- if (drop->item_data.nameid == item)
- {
- (*amount) += drop->item_data.amount;
- clif_clearflooritem(drop, nullptr);
- map_delobject(drop->bl_id, drop->bl_type);
- }
-}
-
-static
-void builtin_getareadropitem(ScriptState *st)
-{
- ItemNameId item;
- int x0, y0, x1, y1, amount = 0, delitems = 0;
- struct script_data *data;
-
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x0 = conv_num(st, &AARGO2(3));
- y0 = conv_num(st, &AARGO2(4));
- x1 = conv_num(st, &AARGO2(5));
- y1 = conv_num(st, &AARGO2(6));
-
- data = &AARGO2(7);
- get_val(st, data);
- if (data->is<ScriptDataStr>())
- {
- ZString name = ZString(conv_str(st, data));
- struct item_data *item_data = itemdb_searchname(name);
- if (item_data)
- item = item_data->nameid;
- }
- else
- item = wrap<ItemNameId>(conv_num(st, data));
-
- if (HARGO2(8))
- delitems = conv_num(st, &AARGO2(8));
-
- map_local *m = map_mapname2mapid(str);
- if (m == nullptr)
- {
- push_int<ScriptDataInt>(st->stack, -1);
- return;
- }
- if (delitems)
- map_foreachinarea(std::bind(builtin_getareadropitem_sub_anddelete, ph::_1, item, &amount),
- m,
- x0, y0,
- x1, y1,
- BL::ITEM);
- else
- map_foreachinarea(std::bind(builtin_getareadropitem_sub, ph::_1, item, &amount),
- m,
- x0, y0,
- x1, y1,
- BL::ITEM);
-
- push_int<ScriptDataInt>(st->stack, amount);
-}
-
-/*==========================================
- * NPCの有効化
- *------------------------------------------
- */
-static
-void builtin_enablenpc(ScriptState *st)
-{
- NpcName str = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2))));
- npc_enable(str, 1);
-}
-
-/*==========================================
- * NPCの無効化
- *------------------------------------------
- */
-static
-void builtin_disablenpc(ScriptState *st)
-{
- NpcName str = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2))));
- npc_enable(str, 0);
-}
-
-/*==========================================
- * 状態異常にかかる
- *------------------------------------------
- */
-static
-void builtin_sc_start(ScriptState *st)
-{
- dumb_ptr<block_list> bl;
- int val1;
- StatusChange type = static_cast<StatusChange>(conv_num(st, &AARGO2(2)));
- interval_t tick = static_cast<interval_t>(conv_num(st, &AARGO2(3)));
- if (tick < 1_s)
- // work around old behaviour of:
- // speed potion
- // atk potion
- // matk potion
- //
- // which used to use seconds
- // all others used milliseconds
- tick *= 1000;
- val1 = conv_num(st, &AARGO2(4));
- if (HARGO2(5)) //指定したキャラを状態異常にする
- bl = map_id2bl(wrap<BlockId>(conv_num(st, &AARGO2(5))));
- else
- bl = map_id2bl(st->rid);
- skill_status_change_start(bl, type, val1, tick);
-}
-
-/*==========================================
- * 状態異常が直る
- *------------------------------------------
- */
-static
-void builtin_sc_end(ScriptState *st)
-{
- dumb_ptr<block_list> bl;
- StatusChange type = StatusChange(conv_num(st, &AARGO2(2)));
- bl = map_id2bl(st->rid);
- skill_status_change_end(bl, type, nullptr);
-}
-
-static
-void builtin_sc_check(ScriptState *st)
-{
- dumb_ptr<block_list> bl;
- StatusChange type = StatusChange(conv_num(st, &AARGO2(2)));
- bl = map_id2bl(st->rid);
-
- push_int<ScriptDataInt>(st->stack, skill_status_change_active(bl, type));
-
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_debugmes(ScriptState *st)
-{
- RString mes = conv_str(st, &AARGO2(2));
- PRINTF("script debug : %d %d : %s\n"_fmt,
- st->rid, st->oid, mes);
-}
-
-/*==========================================
- * ステータスリセット
- *------------------------------------------
- */
-static
-void builtin_resetstatus(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd;
- sd = script_rid2sd(st);
- pc_resetstate(sd);
-}
-
-/*==========================================
- * 性別変換
- *------------------------------------------
- */
-static
-void builtin_changesex(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = nullptr;
- sd = script_rid2sd(st);
-
- chrif_char_ask_name(AccountId(), sd->status_key.name, 5, HumanTimeDiff()); // type: 5 - changesex
- chrif_save(sd);
-}
-
-/*==========================================
- * RIDのアタッチ
- *------------------------------------------
- */
-static
-void builtin_attachrid(ScriptState *st)
-{
- st->rid = wrap<BlockId>(conv_num(st, &AARGO2(2)));
- push_int<ScriptDataInt>(st->stack, (map_id2sd(st->rid) != nullptr));
-}
-
-/*==========================================
- * RIDのデタッチ
- *------------------------------------------
- */
-static
-void builtin_detachrid(ScriptState *st)
-{
- st->rid = BlockId();
-}
-
-/*==========================================
- * 存在チェック
- *------------------------------------------
- */
-static
-void builtin_isloggedin(ScriptState *st)
-{
- push_int<ScriptDataInt>(st->stack,
- map_id2sd(wrap<BlockId>(conv_num(st, &AARGO2(2)))) != nullptr);
-}
-
-static
-void builtin_setmapflag(ScriptState *st)
-{
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- int i = conv_num(st, &AARGO2(3));
- MapFlag mf = map_flag_from_int(i);
- map_local *m = map_mapname2mapid(str);
- if (m != nullptr)
- {
- m->flag.set(mf, 1);
- }
-}
-
-static
-void builtin_removemapflag(ScriptState *st)
-{
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- int i = conv_num(st, &AARGO2(3));
- MapFlag mf = map_flag_from_int(i);
- map_local *m = map_mapname2mapid(str);
- if (m != nullptr)
- {
- m->flag.set(mf, 0);
- }
-}
-
-static
-void builtin_getmapflag(ScriptState *st)
-{
- int r = -1;
-
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- int i = conv_num(st, &AARGO2(3));
- MapFlag mf = map_flag_from_int(i);
- map_local *m = map_mapname2mapid(str);
- if (m != nullptr)
- {
- r = m->flag.get(mf);
- }
-
- push_int<ScriptDataInt>(st->stack, r);
-}
-
-static
-void builtin_pvpon(ScriptState *st)
-{
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- map_local *m = map_mapname2mapid(str);
- if (m != nullptr && !m->flag.get(MapFlag::PVP) && !m->flag.get(MapFlag::NOPVP))
- {
- m->flag.set(MapFlag::PVP, 1);
-
- if (battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris]
- return;
-
- for (io::FD i : iter_fds())
- {
- Session *s = get_session(i);
- if (!s)
- continue;
- map_session_data *pl_sd = static_cast<map_session_data *>(s->session_data.get());
- if (pl_sd && pl_sd->state.auth)
- {
- if (m == pl_sd->bl_m && !pl_sd->pvp_timer)
- {
- pl_sd->pvp_timer = Timer(gettick() + 200_ms,
- std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2,
- pl_sd->bl_id));
- pl_sd->pvp_rank = 0;
- pl_sd->pvp_lastusers = 0;
- pl_sd->pvp_point = 5;
- }
- }
- }
- }
-
-}
-
-static
-void builtin_pvpoff(ScriptState *st)
-{
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- map_local *m = map_mapname2mapid(str);
- if (m != nullptr && m->flag.get(MapFlag::PVP) && m->flag.get(MapFlag::NOPVP))
- {
- m->flag.set(MapFlag::PVP, 0);
-
- if (battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris]
- return;
-
- for (io::FD i : iter_fds())
- {
- Session *s = get_session(i);
- if (!s)
- continue;
- map_session_data *pl_sd = static_cast<map_session_data *>(s->session_data.get());
- if (pl_sd && pl_sd->state.auth)
- {
- if (m == pl_sd->bl_m)
- {
- pl_sd->pvp_timer.cancel();
- }
- }
- }
- }
-
-}
-
-/*==========================================
- * NPCエモーション
- *------------------------------------------
- */
-
-static
-void builtin_emotion(ScriptState *st)
-{
- int type;
- type = conv_num(st, &AARGO2(2));
- if (type < 0 || type > 100)
- return;
- clif_emotion(map_id2bl(st->oid), type);
-}
-
-static
-void builtin_mapwarp(ScriptState *st) // Added by RoVeRT
-{
- int x, y;
- int x0, y0, x1, y1;
-
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x0 = 0;
- y0 = 0;
- map_local *m = map_mapname2mapid(mapname);
- x1 = m->xs;
- y1 = m->ys;
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(3))));
- x = conv_num(st, &AARGO2(4));
- y = conv_num(st, &AARGO2(5));
-
- if (m == nullptr)
- return;
-
- map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y),
- m,
- x0, y0,
- x1, y1,
- BL::PC);
-}
-
-static
-void builtin_cmdothernpc(ScriptState *st) // Added by RoVeRT
-{
- NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2))));
- ZString command = ZString(conv_str(st, &AARGO2(3)));
-
- npc_command(map_id2sd(st->rid), npc, command);
-}
-
-static
-void builtin_mobcount_sub(dumb_ptr<block_list> bl, NpcEvent event, int *c)
-{
- if (event == bl->is_mob()->npc_event)
- (*c)++;
-}
-
-static
-void builtin_mobcount(ScriptState *st) // Added by RoVeRT
-{
- int c = 0;
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- ZString event_ = ZString(conv_str(st, &AARGO2(3)));
- NpcEvent event;
- extract(event_, &event);
-
- map_local *m = map_mapname2mapid(mapname);
- if (m == nullptr)
- {
- push_int<ScriptDataInt>(st->stack, -1);
- return;
- }
- map_foreachinarea(std::bind(builtin_mobcount_sub, ph::_1, event, &c),
- m,
- 0, 0,
- m->xs, m->ys,
- BL::MOB);
-
- push_int<ScriptDataInt>(st->stack, (c - 1));
-
-}
-
-static
-void builtin_marriage(ScriptState *st)
-{
- CharName partner = stringish<CharName>(ZString(conv_str(st, &AARGO2(2))));
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- dumb_ptr<map_session_data> p_sd = map_nick2sd(partner);
-
- if (sd == nullptr || p_sd == nullptr || pc_marriage(sd, p_sd) < 0)
- {
- push_int<ScriptDataInt>(st->stack, 0);
- return;
- }
- push_int<ScriptDataInt>(st->stack, 1);
-}
-
-static
-void builtin_divorce(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- st->state = ScriptEndState::STOP; // rely on pc_divorce to restart
-
- sd->npc_flags.divorce = 1;
-
- if (sd == nullptr || pc_divorce(sd) < 0)
- {
- push_int<ScriptDataInt>(st->stack, 0);
- return;
- }
-
- push_int<ScriptDataInt>(st->stack, 1);
-}
-
-/*==========================================
- * IDからItem名
- *------------------------------------------
- */
-static
-void builtin_getitemname(ScriptState *st)
-{
- struct item_data *i_data;
- struct script_data *data;
-
- data = &AARGO2(2);
- get_val(st, data);
- if (data->is<ScriptDataStr>())
- {
- ZString name = ZString(conv_str(st, data));
- i_data = itemdb_searchname(name);
- }
- else
- {
- ItemNameId item_id = wrap<ItemNameId>(conv_num(st, data));
- i_data = itemdb_search(item_id);
- }
-
- RString item_name;
- if (i_data)
- item_name = i_data->jname;
- else
- item_name = "Unknown Item"_s;
-
- push_str<ScriptDataStr>(st->stack, item_name);
-}
-
-static
-void builtin_getspellinvocation(ScriptState *st)
-{
- RString name = conv_str(st, &AARGO2(2));
-
- AString invocation = magic::magic_find_invocation(name);
- if (!invocation)
- invocation = "..."_s;
-
- push_str<ScriptDataStr>(st->stack, invocation);
-}
-
-static
-void builtin_getpartnerid2(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status.partner_id));
-}
-
-/*==========================================
- * PCの所持品情報読み取り
- *------------------------------------------
- */
-static
-void builtin_getinventorylist(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- int j = 0;
- if (!sd)
- return;
- for (IOff0 i : IOff0::iter())
- {
- if (sd->status.inventory[i].nameid
- && sd->status.inventory[i].amount > 0)
- {
- pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_id"_s), j),
- unwrap<ItemNameId>(sd->status.inventory[i].nameid));
- pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_amount"_s), j),
- sd->status.inventory[i].amount);
- pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_equip"_s), j),
- static_cast<uint16_t>(sd->status.inventory[i].equip));
- j++;
- }
- }
- pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_count"_s)), j);
-}
-
-static
-void builtin_getactivatedpoolskilllist(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- SkillID pool_skills[MAX_SKILL_POOL];
- int skill_pool_size = skill_pool(sd, pool_skills);
- int i, count = 0;
-
- if (!sd)
- return;
-
- for (i = 0; i < skill_pool_size; i++)
- {
- SkillID skill_id = pool_skills[i];
-
- if (sd->status.skill[skill_id].lv)
- {
- pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count),
- static_cast<uint16_t>(skill_id));
- pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count),
- sd->status.skill[skill_id].lv);
- pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count),
- static_cast<uint16_t>(sd->status.skill[skill_id].flags));
- pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count),
- skill_name(skill_id));
- ++count;
- }
- }
- pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count);
-
-}
-
-static
-void builtin_getunactivatedpoolskilllist(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- int i, count = 0;
-
- if (!sd)
- return;
-
- for (i = 0; i < skill_pool_skills_size; i++)
- {
- SkillID skill_id = skill_pool_skills[i];
-
- if (sd->status.skill[skill_id].lv
- && !bool(sd->status.skill[skill_id].flags & SkillFlags::POOL_ACTIVATED))
- {
- pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count),
- static_cast<uint16_t>(skill_id));
- pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count),
- sd->status.skill[skill_id].lv);
- pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count),
- static_cast<uint16_t>(sd->status.skill[skill_id].flags));
- pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count),
- skill_name(skill_id));
- ++count;
- }
- }
- pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count);
-}
-
-static
-void builtin_poolskill(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- SkillID skill_id = SkillID(conv_num(st, &AARGO2(2)));
-
- skill_pool_activate(sd, skill_id);
- clif_skillinfoblock(sd);
-
-}
-
-static
-void builtin_unpoolskill(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- SkillID skill_id = SkillID(conv_num(st, &AARGO2(2)));
-
- skill_pool_deactivate(sd, skill_id);
- clif_skillinfoblock(sd);
-
-}
-
-/*==========================================
- * NPCから発生するエフェクト
- * misceffect(effect, [target])
- *
- * effect The effect type/ID.
- * target The player name or being ID on
- * which to display the effect. If not
- * specified, it attempts to default to
- * the current NPC or invoking PC.
- *------------------------------------------
- */
-static
-void builtin_misceffect(ScriptState *st)
-{
- int type;
- BlockId id;
- CharName name;
- dumb_ptr<block_list> bl = nullptr;
-
- type = conv_num(st, &AARGO2(2));
-
- if (HARGO2(3))
- {
- struct script_data *sdata = &AARGO2(3);
-
- get_val(st, sdata);
-
- if (sdata->is<ScriptDataStr>())
- name = stringish<CharName>(ZString(conv_str(st, sdata)));
- else
- id = wrap<BlockId>(conv_num(st, sdata));
- }
-
- if (name.to__actual())
- {
- dumb_ptr<map_session_data> sd = map_nick2sd(name);
- if (sd)
- bl = sd;
- }
- else if (id)
- bl = map_id2bl(id);
- else if (st->oid)
- bl = map_id2bl(st->oid);
- else
- {
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- if (sd)
- bl = sd;
- }
-
- if (bl)
- clif_misceffect(bl, type);
-
-}
-
-/*==========================================
- * Special effects [Valaris]
- *------------------------------------------
- */
-static
-void builtin_specialeffect(ScriptState *st)
-{
- dumb_ptr<block_list> bl = map_id2bl(st->oid);
-
- if (bl == nullptr)
- return;
-
- clif_specialeffect(bl,
- conv_num(st,
- &AARGO2(2)),
- 0);
-
-}
-
-static
-void builtin_specialeffect2(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- if (sd == nullptr)
- return;
-
- clif_specialeffect(sd,
- conv_num(st,
- &AARGO2(2)),
- 0);
-
-}
-
-/*==========================================
- * Nude [Valaris]
- *------------------------------------------
- */
-
-static
-void builtin_nude(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- if (sd == nullptr)
- return;
-
- for (EQUIP i : EQUIPs)
- {
- IOff0 idx = sd->equip_index_maybe[i];
- if (idx.ok())
- pc_unequipitem(sd, idx, CalcStatus::LATER);
- }
- pc_calcstatus(sd, 0);
-
-}
-
-/*==========================================
- * UnequipById [Freeyorp]
- *------------------------------------------
- */
-
-static
-void builtin_unequipbyid(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- if (sd == nullptr)
- return;
-
- EQUIP slot_id = EQUIP(conv_num(st, &AARGO2(2)));
-
- if (slot_id >= EQUIP() && slot_id < EQUIP::COUNT)
- {
- IOff0 idx = sd->equip_index_maybe[slot_id];
- if (idx.ok())
- pc_unequipitem(sd, idx, CalcStatus::LATER);
- }
-
- pc_calcstatus(sd, 0);
-
-}
-
-/*==========================================
- * gmcommand [MouseJstr]
- *
- * suggested on the forums...
- *------------------------------------------
- */
-
-static
-void builtin_gmcommand(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd;
-
- sd = script_rid2sd(st);
- RString cmd = conv_str(st, &AARGO2(2));
-
- is_atcommand(sd->sess, sd, cmd, GmLevel::from(-1U));
-
-}
-
-/*==========================================
- * npcwarp [remoitnane]
- * Move NPC to a new position on the same map.
- *------------------------------------------
- */
-static
-void builtin_npcwarp(ScriptState *st)
-{
- int x, y;
- dumb_ptr<npc_data> nd = nullptr;
-
- x = conv_num(st, &AARGO2(2));
- y = conv_num(st, &AARGO2(3));
- NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARGO2(4))));
- nd = npc_name2id(npc);
-
- if (!nd)
- {
- PRINTF("builtin_npcwarp: no such npc: %s\n"_fmt, npc);
- return;
- }
-
- map_local *m = nd->bl_m;
-
- /* Crude sanity checks. */
- if (m == nullptr || !nd->bl_prev
- || x < 0 || x > m->xs -1
- || y < 0 || y > m->ys - 1)
- return;
-
- npc_enable(npc, 0);
- map_delblock(nd); /* [Freeyorp] */
- nd->bl_x = x;
- nd->bl_y = y;
- map_addblock(nd);
- npc_enable(npc, 1);
-
-}
-
-/*==========================================
- * message [MouseJstr]
- *------------------------------------------
- */
-
-static
-void builtin_message(ScriptState *st)
-{
- CharName player = stringish<CharName>(ZString(conv_str(st, &AARGO2(2))));
- ZString msg = ZString(conv_str(st, &AARGO2(3)));
-
- dumb_ptr<map_session_data> pl_sd = map_nick2sd(player);
- if (pl_sd == nullptr)
- return;
- clif_displaymessage(pl_sd->sess, msg);
-
-}
-
-/*==========================================
- * npctalk (sends message to surrounding
- * area) [Valaris]
- *------------------------------------------
- */
-
-static
-void builtin_npctalk(ScriptState *st)
-{
- dumb_ptr<npc_data> nd = map_id_is_npc(st->oid);
- RString str = conv_str(st, &AARGO2(2));
-
- if (nd)
- {
- clif_message(nd, str);
- }
-}
-
-/*==========================================
- * getlook char info. getlook(arg)
- *------------------------------------------
- */
-static
-void builtin_getlook(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- LOOK type = LOOK(conv_num(st, &AARGO2(2)));
- int val = -1;
- switch (type)
- {
- case LOOK::HAIR: //1
- val = sd->status.hair;
- break;
- case LOOK::WEAPON: //2
- val = static_cast<uint16_t>(sd->status.weapon);
- break;
- case LOOK::HEAD_BOTTOM: //3
- val = unwrap<ItemNameId>(sd->status.head_bottom);
- break;
- case LOOK::HEAD_TOP: //4
- val = unwrap<ItemNameId>(sd->status.head_top);
- break;
- case LOOK::HEAD_MID: //5
- val = unwrap<ItemNameId>(sd->status.head_mid);
- break;
- case LOOK::HAIR_COLOR: //6
- val = sd->status.hair_color;
- break;
- case LOOK::CLOTHES_COLOR: //7
- val = sd->status.clothes_color;
- break;
- case LOOK::SHIELD: //8
- val = unwrap<ItemNameId>(sd->status.shield);
- break;
- case LOOK::SHOES: //9
- break;
- }
-
- push_int<ScriptDataInt>(st->stack, val);
-}
-
-/*==========================================
- * get char save point. argument: 0- map name, 1- x, 2- y
- *------------------------------------------
-*/
-static
-void builtin_getsavepoint(ScriptState *st)
-{
- int x, y, type;
- dumb_ptr<map_session_data> sd;
-
- sd = script_rid2sd(st);
-
- type = conv_num(st, &AARGO2(2));
-
- x = sd->status.save_point.x;
- y = sd->status.save_point.y;
- switch (type)
- {
- case 0:
- {
- RString mapname = sd->status.save_point.map_;
- push_str<ScriptDataStr>(st->stack, mapname);
- }
- break;
- case 1:
- push_int<ScriptDataInt>(st->stack, x);
- break;
- case 2:
- push_int<ScriptDataInt>(st->stack, y);
- break;
- }
-}
-
-/*==========================================
- * areatimer
- *------------------------------------------
- */
-static
-void builtin_areatimer_sub(dumb_ptr<block_list> bl, interval_t tick, NpcEvent event)
-{
- pc_addeventtimer(bl->is_player(), tick, event);
-}
-
-static
-void builtin_areatimer(ScriptState *st)
-{
- int x0, y0, x1, y1;
-
- MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x0 = conv_num(st, &AARGO2(3));
- y0 = conv_num(st, &AARGO2(4));
- x1 = conv_num(st, &AARGO2(5));
- y1 = conv_num(st, &AARGO2(6));
- interval_t tick = static_cast<interval_t>(conv_num(st, &AARGO2(7)));
- ZString event_ = ZString(conv_str(st, &AARGO2(8)));
- NpcEvent event;
- extract(event_, &event);
-
- map_local *m = map_mapname2mapid(mapname);
- if (m == nullptr)
- return;
-
- map_foreachinarea(std::bind(builtin_areatimer_sub, ph::_1, tick, event),
- m,
- x0, y0,
- x1, y1,
- BL::PC);
-}
-
-/*==========================================
- * Check whether the PC is in the specified rectangle
- *------------------------------------------
- */
-static
-void builtin_isin(ScriptState *st)
-{
- int x1, y1, x2, y2;
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2))));
- x1 = conv_num(st, &AARGO2(3));
- y1 = conv_num(st, &AARGO2(4));
- x2 = conv_num(st, &AARGO2(5));
- y2 = conv_num(st, &AARGO2(6));
-
- if (!sd)
- return;
-
- push_int<ScriptDataInt>(st->stack,
- (sd->bl_x >= x1 && sd->bl_x <= x2)
- && (sd->bl_y >= y1 && sd->bl_y <= y2)
- && (str == sd->bl_m->name_));
-}
-
-// Trigger the shop on a (hopefully) nearby shop NPC
-static
-void builtin_shop(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
- dumb_ptr<npc_data> nd;
-
- if (!sd)
- return;
-
- NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2))));
- nd = npc_name2id(name);
- if (!nd)
- {
- PRINTF("builtin_shop: no such npc: %s\n"_fmt, name);
- return;
- }
-
- builtin_close(st);
- clif_npcbuysell(sd, nd->bl_id);
-}
-
-/*==========================================
- * Check whether the PC is dead
- *------------------------------------------
- */
-static
-void builtin_isdead(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- push_int<ScriptDataInt>(st->stack, pc_isdead(sd));
-}
-
-/*========================================
- * Changes a NPC name, and sprite
- *----------------------------------------
- */
-static
-void builtin_fakenpcname(ScriptState *st)
-{
- NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2))));
- NpcName newname = stringish<NpcName>(ZString(conv_str(st, &AARGO2(3))));
- Species newsprite = wrap<Species>(static_cast<uint16_t>(conv_num(st, &AARGO2(4))));
- dumb_ptr<npc_data> nd = npc_name2id(name);
- if (!nd)
- {
- PRINTF("builtin_fakenpcname: no such npc: %s\n"_fmt, name);
- return;
- }
- nd->name = newname;
- nd->npc_class = newsprite;
-
- // Refresh this npc
- npc_enable(name, 0);
- npc_enable(name, 1);
-
-}
-
-/*============================
- * Gets the PC's x pos
- *----------------------------
- */
-static
-void builtin_getx(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- push_int<ScriptDataInt>(st->stack, sd->bl_x);
-}
-
-/*============================
- * Gets the PC's y pos
- *----------------------------
- */
-static
-void builtin_gety(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- push_int<ScriptDataInt>(st->stack, sd->bl_y);
-}
-
-/*
- * Get the PC's current map's name
- */
-static
-void builtin_getmap(ScriptState *st)
-{
- dumb_ptr<map_session_data> sd = script_rid2sd(st);
-
- push_str<ScriptDataStr>(st->stack, sd->bl_m->name_);
-}
-
-static
-void builtin_mapexit(ScriptState *)
-{
- runflag = 0;
-}
-
-
-//
-// 実行部main
-//
-/*==========================================
- * コマンドの読み取り
- *------------------------------------------
- */
-static
-ByteCode get_com(ScriptPointer *script)
-{
- if (static_cast<uint8_t>(script->peek()) >= 0x80)
- {
- // synthetic! Does not advance pos yet.
- return ByteCode::INT;
- }
- return script->pop();
-}
-
-/*==========================================
- * 数値の所得
- *------------------------------------------
- */
-static
-int get_num(ScriptPointer *scr)
-{
- int i = 0;
- int j = 0;
- uint8_t val;
- do
- {
- val = static_cast<uint8_t>(scr->pop());
- i += (val & 0x7f) << j;
- j += 6;
- }
- while (val >= 0xc0);
- return i;
-}
-
-/*==========================================
- * スタックから値を取り出す
- *------------------------------------------
- */
-static
-int pop_val(ScriptState *st)
-{
- if (st->stack->stack_datav.empty())
- return 0;
- script_data& back = st->stack->stack_datav.back();
- get_val(st, &back);
- int rv = 0;
- if (auto *u = back.get_if<ScriptDataInt>())
- rv = u->numi;
- st->stack->stack_datav.pop_back();
- return rv;
-}
-
-static
-bool isstr(struct script_data& c)
-{
- return c.is<ScriptDataStr>();
-}
-
-/*==========================================
- * 加算演算子
- *------------------------------------------
- */
-static
-void op_add(ScriptState *st)
-{
- get_val(st, &st->stack->stack_datav.back());
- script_data back = st->stack->stack_datav.back();
- st->stack->stack_datav.pop_back();
-
- script_data& back1 = st->stack->stack_datav.back();
- get_val(st, &back1);
-
- if (!(isstr(back) || isstr(back1)))
- {
- back1.get_if<ScriptDataInt>()->numi += back.get_if<ScriptDataInt>()->numi;
- }
- else
- {
- RString sb = conv_str(st, &back);
- RString sb1 = conv_str(st, &back1);
- MString buf;
- buf += sb1;
- buf += sb;
- back1 = ScriptDataStr{.str= AString(buf)};
- }
-}
-
-/*==========================================
- * 二項演算子(文字列)
- *------------------------------------------
- */
-static
-void op_2str(ScriptState *st, ByteCode op, ZString s1, ZString s2)
-{
- int a = 0;
-
- switch (op)
- {
- case ByteCode::EQ:
- a = s1 == s2;
- break;
- case ByteCode::NE:
- a = s1 != s2;
- break;
- case ByteCode::GT:
- a = s1 > s2;
- break;
- case ByteCode::GE:
- a = s1 >= s2;
- break;
- case ByteCode::LT:
- a = s1 < s2;
- break;
- case ByteCode::LE:
- a = s1 <= s2;
- break;
- default:
- PRINTF("illegal string operater\n"_fmt);
- break;
- }
-
- push_int<ScriptDataInt>(st->stack, a);
-}
-
-/*==========================================
- * 二項演算子(数値)
- *------------------------------------------
- */
-static
-void op_2num(ScriptState *st, ByteCode op, int i1, int i2)
-{
- switch (op)
- {
- case ByteCode::SUB:
- i1 -= i2;
- break;
- case ByteCode::MUL:
- i1 *= i2;
- break;
- case ByteCode::DIV:
- i1 /= i2;
- break;
- case ByteCode::MOD:
- i1 %= i2;
- break;
- case ByteCode::AND:
- i1 &= i2;
- break;
- case ByteCode::OR:
- i1 |= i2;
- break;
- case ByteCode::XOR:
- i1 ^= i2;
- break;
- case ByteCode::LAND:
- i1 = i1 && i2;
- break;
- case ByteCode::LOR:
- i1 = i1 || i2;
- break;
- case ByteCode::EQ:
- i1 = i1 == i2;
- break;
- case ByteCode::NE:
- i1 = i1 != i2;
- break;
- case ByteCode::GT:
- i1 = i1 > i2;
- break;
- case ByteCode::GE:
- i1 = i1 >= i2;
- break;
- case ByteCode::LT:
- i1 = i1 < i2;
- break;
- case ByteCode::LE:
- i1 = i1 <= i2;
- break;
- case ByteCode::R_SHIFT:
- i1 = i1 >> i2;
- break;
- case ByteCode::L_SHIFT:
- i1 = i1 << i2;
- break;
- }
- push_int<ScriptDataInt>(st->stack, i1);
-}
-
-/*==========================================
- * 二項演算子
- *------------------------------------------
- */
-static
-void op_2(ScriptState *st, ByteCode op)
-{
- // pop_val has unfortunate implications here
- script_data d2 = st->stack->stack_datav.back();
- st->stack->stack_datav.pop_back();
- get_val(st, &d2);
- script_data d1 = st->stack->stack_datav.back();
- st->stack->stack_datav.pop_back();
- get_val(st, &d1);
-
- if (isstr(d1) && isstr(d2))
- {
- // ss => op_2str
- op_2str(st, op, d1.get_if<ScriptDataStr>()->str, d2.get_if<ScriptDataStr>()->str);
- }
- else if (!(isstr(d1) || isstr(d2)))
- {
- // ii => op_2num
- op_2num(st, op, d1.get_if<ScriptDataInt>()->numi, d2.get_if<ScriptDataInt>()->numi);
- }
- else
- {
- // si,is => error
- PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt);
- push_int<ScriptDataInt>(st->stack, 0);
- }
-}
-
-/*==========================================
- * 単項演算子
- *------------------------------------------
- */
-static
-void op_1num(ScriptState *st, ByteCode op)
-{
- int i1;
- i1 = pop_val(st);
- switch (op)
- {
- case ByteCode::NEG:
- i1 = -i1;
- break;
- case ByteCode::NOT:
- i1 = ~i1;
- break;
- case ByteCode::LNOT:
- i1 = !i1;
- break;
- }
- push_int<ScriptDataInt>(st->stack, i1);
-}
-
-/*==========================================
- * 関数の実行
- *------------------------------------------
- */
-void run_func(ScriptState *st)
-{
- size_t end_sp = st->stack->stack_datav.size();
- size_t start_sp = end_sp - 1;
- while (!st->stack->stack_datav[start_sp].is<ScriptDataArg>())
- {
- start_sp--;
- if (start_sp == 0)
- {
- if (battle_config.error_log)
- PRINTF("function not found\n"_fmt);
- st->state = ScriptEndState::END;
- return;
- }
- }
- // the func is before the arg
- start_sp--;
- st->start = start_sp;
- st->end = end_sp;
-
- if (!st->stack->stack_datav[st->start].is<ScriptDataFuncRef>())
- {
- PRINTF("run_func: not function and command! \n"_fmt);
- st->state = ScriptEndState::END;
- return;
- }
- size_t func = st->stack->stack_datav[st->start].get_if<ScriptDataFuncRef>()->numi;
-
- if (DEBUG_RUN && battle_config.etc_log)
- {
- PRINTF("run_func : %s\n"_fmt,
- builtin_functions[func].name);
- PRINTF("stack dump :"_fmt);
- for (script_data& d : st->stack->stack_datav)
- {
- MATCH (d)
- {
- CASE (const ScriptDataInt&, u)
- {
- PRINTF(" int(%d)"_fmt, u.numi);
- }
- CASE (const ScriptDataRetInfo&, u)
- {
- PRINTF(" retinfo(%p)"_fmt, static_cast<const void *>(u.script));
- }
- CASE (const ScriptDataParam&, u)
- {
- PRINTF(" param(%d)"_fmt, u.reg.sp());
- }
- CASE (const ScriptDataVariable&, u)
- {
- PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index());
- }
- CASE (const ScriptDataArg&, u)
- {
- (void)u;
- PRINTF(" arg"_fmt);
- }
- CASE (const ScriptDataPos&, u)
- {
- (void)u;
- PRINTF(" pos(%d)"_fmt, u.numi);
- }
- CASE (const ScriptDataStr&, u)
- {
- (void)u;
- PRINTF(" str(%s)"_fmt, u.str);
- }
- CASE (const ScriptDataFuncRef&, u)
- {
- (void)u;
- PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name);
- }
- }
- }
- PRINTF("\n"_fmt);
- }
- builtin_functions[func].func(st);
-
- pop_stack(st->stack, start_sp, end_sp);
-
- if (st->state == ScriptEndState::RETFUNC)
- {
- // ユーザー定義関数からの復帰
- int olddefsp = st->defsp;
-
- pop_stack(st->stack, st->defsp, start_sp); // 復帰に邪魔なスタック削除
- if (st->defsp < 4
- || !st->stack->stack_datav[st->defsp - 1].is<ScriptDataRetInfo>())
- {
- PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt);
- st->state = ScriptEndState::END;
- return;
- }
- assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet
- st->scriptp.code = conv_script(st, &st->stack->stack_datav[olddefsp - 1]); // スクリプトを復元
- st->scriptp.pos = conv_num(st, &st->stack->stack_datav[olddefsp - 2]); // スクリプト位置の復元
- st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元
- // Number of arguments.
- int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得
- assert (i == 0);
-
- pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除
-
- st->state = ScriptEndState::GOTO;
- }
-}
-
-// pretend it's external so this can be called in the debugger
-void dump_script(const ScriptBuffer *script);
-void dump_script(const ScriptBuffer *script)
-{
- ScriptPointer scriptp(script, 0);
- while (scriptp.pos < reinterpret_cast<const std::vector<ByteCode> *>(script)->size())
- {
- PRINTF("%6zu: "_fmt, scriptp.pos);
- switch (ByteCode c = get_com(&scriptp))
- {
- case ByteCode::EOL:
- PRINTF("EOL\n"_fmt); // extra newline between functions
- break;
- case ByteCode::INT:
- // synthesized!
- PRINTF("INT %d"_fmt, get_num(&scriptp));
- break;
-
- case ByteCode::POS:
- case ByteCode::VARIABLE:
- case ByteCode::FUNC_REF:
- case ByteCode::PARAM:
- {
- int arg = 0;
- arg |= static_cast<uint8_t>(scriptp.pop()) << 0;
- arg |= static_cast<uint8_t>(scriptp.pop()) << 8;
- arg |= static_cast<uint8_t>(scriptp.pop()) << 16;
- switch(c)
- {
- case ByteCode::POS:
- PRINTF("POS %d"_fmt, arg);
- break;
- case ByteCode::VARIABLE:
- PRINTF("VARIABLE %s"_fmt, variable_names.outtern(arg));
- break;
- case ByteCode::FUNC_REF:
- PRINTF("FUNC_REF %s"_fmt, builtin_functions[arg].name);
- break;
- case ByteCode::PARAM:
- PRINTF("PARAM SP::#%d (sorry)"_fmt, arg);
- break;
- }
- }
- break;
- case ByteCode::ARG:
- PRINTF("ARG"_fmt);
- break;
- case ByteCode::STR:
- PRINTF("STR \"%s\""_fmt, scriptp.pops());
- break;
- case ByteCode::FUNC:
- PRINTF("FUNC"_fmt);
- break;
-
- case ByteCode::ADD:
- PRINTF("ADD"_fmt);
- break;
- case ByteCode::SUB:
- PRINTF("SUB"_fmt);
- break;
- case ByteCode::MUL:
- PRINTF("MUL"_fmt);
- break;
- case ByteCode::DIV:
- PRINTF("DIV"_fmt);
- break;
- case ByteCode::MOD:
- PRINTF("MOD"_fmt);
- break;
- case ByteCode::EQ:
- PRINTF("EQ"_fmt);
- break;
- case ByteCode::NE:
- PRINTF("NE"_fmt);
- break;
- case ByteCode::GT:
- PRINTF("GT"_fmt);
- break;
- case ByteCode::GE:
- PRINTF("GE"_fmt);
- break;
- case ByteCode::LT:
- PRINTF("LT"_fmt);
- break;
- case ByteCode::LE:
- PRINTF("LE"_fmt);
- break;
- case ByteCode::AND:
- PRINTF("AND"_fmt);
- break;
- case ByteCode::OR:
- PRINTF("OR"_fmt);
- break;
- case ByteCode::XOR:
- PRINTF("XOR"_fmt);
- break;
- case ByteCode::LAND:
- PRINTF("LAND"_fmt);
- break;
- case ByteCode::LOR:
- PRINTF("LOR"_fmt);
- break;
- case ByteCode::R_SHIFT:
- PRINTF("R_SHIFT"_fmt);
- break;
- case ByteCode::L_SHIFT:
- PRINTF("L_SHIFT"_fmt);
- break;
- case ByteCode::NEG:
- PRINTF("NEG"_fmt);
- break;
- case ByteCode::NOT:
- PRINTF("NOT"_fmt);
- break;
- case ByteCode::LNOT:
- PRINTF("LNOT"_fmt);
- break;
-
- case ByteCode::NOP:
- PRINTF("NOP"_fmt);
- break;
-
- default:
- PRINTF("??? %d"_fmt, c);
- break;
- }
- PRINTF("\n"_fmt);
- }
-}
-
-/*==========================================
- * スクリプトの実行メイン部分
- *------------------------------------------
- */
-static
-void run_script_main(ScriptState *st, const ScriptBuffer *rootscript)
-{
- int cmdcount = script_config.check_cmdcount;
- int gotocount = script_config.check_gotocount;
- struct script_stack *stack = st->stack;
-
- st->defsp = stack->stack_datav.size();
-
- int rerun_pos = st->scriptp.pos;
- st->state = ScriptEndState::ZERO;
- while (st->state == ScriptEndState::ZERO)
- {
- switch (ByteCode c = get_com(&st->scriptp))
- {
- case ByteCode::EOL:
- if (stack->stack_datav.size() != st->defsp)
- {
- if (true)
- PRINTF("stack.sp (%zu) != default (%d)\n"_fmt,
- stack->stack_datav.size(),
- st->defsp);
- abort();
- }
- rerun_pos = st->scriptp.pos;
- break;
- case ByteCode::INT:
- // synthesized!
- push_int<ScriptDataInt>(stack, get_num(&st->scriptp));
- break;
-
- case ByteCode::POS:
- case ByteCode::VARIABLE:
- case ByteCode::FUNC_REF:
- case ByteCode::PARAM:
- // Note that these 3 have *very* different meanings,
- // despite being encoded similarly.
- {
- int arg = 0;
- arg |= static_cast<uint8_t>(st->scriptp.pop()) << 0;
- arg |= static_cast<uint8_t>(st->scriptp.pop()) << 8;
- arg |= static_cast<uint8_t>(st->scriptp.pop()) << 16;
- switch(c)
- {
- case ByteCode::POS:
- push_int<ScriptDataPos>(stack, arg);
- break;
- case ByteCode::VARIABLE:
- push_reg<ScriptDataVariable>(stack, SIR::from(arg));
- break;
- case ByteCode::FUNC_REF:
- push_int<ScriptDataFuncRef>(stack, arg);
- break;
- case ByteCode::PARAM:
- SP arg_sp = static_cast<SP>(arg);
- push_reg<ScriptDataParam>(stack, SIR::from(arg_sp));
- break;
- }
- }
- break;
- case ByteCode::ARG:
- push_int<ScriptDataArg>(stack, 0);
- break;
- case ByteCode::STR:
- push_str<ScriptDataStr>(stack, st->scriptp.pops());
- break;
- case ByteCode::FUNC:
- run_func(st);
- if (st->state == ScriptEndState::GOTO)
- {
- rerun_pos = st->scriptp.pos;
- st->state = ScriptEndState::ZERO;
- if (gotocount > 0 && (--gotocount) <= 0)
- {
- PRINTF("run_script: infinity loop !\n"_fmt);
- st->state = ScriptEndState::END;
- }
- }
- break;
-
- case ByteCode::ADD:
- op_add(st);
- break;
-
- case ByteCode::SUB:
- case ByteCode::MUL:
- case ByteCode::DIV:
- case ByteCode::MOD:
- case ByteCode::EQ:
- case ByteCode::NE:
- case ByteCode::GT:
- case ByteCode::GE:
- case ByteCode::LT:
- case ByteCode::LE:
- case ByteCode::AND:
- case ByteCode::OR:
- case ByteCode::XOR:
- case ByteCode::LAND:
- case ByteCode::LOR:
- case ByteCode::R_SHIFT:
- case ByteCode::L_SHIFT:
- op_2(st, c);
- break;
-
- case ByteCode::NEG:
- case ByteCode::NOT:
- case ByteCode::LNOT:
- op_1num(st, c);
- break;
-
- case ByteCode::NOP:
- st->state = ScriptEndState::END;
- break;
-
- default:
- if (battle_config.error_log)
- PRINTF("unknown command : %d @ %zu\n"_fmt,
- c, st->scriptp.pos);
- st->state = ScriptEndState::END;
- break;
- }
- if (cmdcount > 0 && (--cmdcount) <= 0)
- {
- PRINTF("run_script: infinity loop !\n"_fmt);
- st->state = ScriptEndState::END;
- }
- }
- switch (st->state)
- {
- case ScriptEndState::STOP:
- break;
- case ScriptEndState::END:
- {
- dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
- st->scriptp.code = nullptr;
- st->scriptp.pos = -1;
- if (sd && sd->npc_id == st->oid)
- npc_event_dequeue(sd);
- }
- break;
- case ScriptEndState::RERUNLINE:
- st->scriptp.pos = rerun_pos;
- break;
- }
-
- if (st->state != ScriptEndState::END)
- {
- // 再開するためにスタック情報を保存
- dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
- if (sd)
- {
- sd->npc_stackbuf = stack->stack_datav;
- sd->npc_script = st->scriptp.code;
- // sd->npc_pos is set later ... ???
- sd->npc_scriptroot = rootscript;
- }
- }
-}
-
-/*==========================================
- * スクリプトの実行
- *------------------------------------------
- */
-int run_script(ScriptPointer sp, BlockId rid, BlockId oid)
-{
- return run_script_l(sp, rid, oid, nullptr);
-}
-
-int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid,
- Slice<argrec_t> args)
-{
- struct script_stack stack;
- ScriptState st;
- dumb_ptr<map_session_data> sd = map_id2sd(rid);
- const ScriptBuffer *rootscript = sp.code;
- int i;
- if (sp.code == nullptr || sp.pos >> 24)
- return -1;
-
- if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == rootscript)
- {
- // 前回のスタックを復帰
- sp.code = sd->npc_script;
- stack.stack_datav = std::move(sd->npc_stackbuf);
- }
- st.stack = &stack;
- st.scriptp = sp;
- st.rid = rid;
- st.oid = oid;
- for (i = 0; i < args.size(); i++)
- {
- if (args[i].name.back() == '$')
- pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s);
- else
- pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i);
- }
- run_script_main(&st, rootscript);
-
- stack.stack_datav.clear();
- return st.scriptp.pos;
-}
-
-/*==========================================
- * マップ変数の変更
- *------------------------------------------
- */
-void mapreg_setreg(SIR reg, int val)
-{
- mapreg_db.put(reg, val);
-
- mapreg_dirty = 1;
-}
-
-/*==========================================
- * 文字列型マップ変数の変更
- *------------------------------------------
- */
-void mapreg_setregstr(SIR reg, XString str)
-{
- if (!str)
- mapregstr_db.erase(reg);
- else
- mapregstr_db.insert(reg, str);
-
- mapreg_dirty = 1;
-}
-
-/*==========================================
- * 永続的マップ変数の読み込み
- *------------------------------------------
- */
-static
-void script_load_mapreg(void)
-{
- io::ReadFile in(mapreg_txt);
-
- if (!in.is_open())
- return;
-
- AString line;
- while (in.getline(line))
- {
- XString buf1, buf2;
- int index = 0;
- if (extract(line,
- record<'\t'>(
- record<','>(&buf1, &index),
- &buf2))
- || extract(line,
- record<'\t'>(
- record<','>(&buf1),
- &buf2)))
- {
- int s = variable_names.intern(buf1);
- SIR key = SIR::from(s, index);
- if (buf1.back() == '$')
- {
- mapregstr_db.insert(key, buf2);
- }
- else
- {
- int v;
- if (!extract(buf2, &v))
- goto borken;
- mapreg_db.put(key, v);
- }
- }
- else
- {
- borken:
- PRINTF("%s: %s broken data !\n"_fmt, mapreg_txt, AString(buf1));
- continue;
- }
- }
- mapreg_dirty = 0;
-}
-
-/*==========================================
- * 永続的マップ変数の書き込み
- *------------------------------------------
- */
-static
-void script_save_mapreg_intsub(SIR key, int data, io::WriteFile& fp)
-{
- int num = key.base(), i = key.index();
- ZString name = variable_names.outtern(num);
- if (name[1] != '@')
- {
- if (i == 0)
- FPRINTF(fp, "%s\t%d\n"_fmt, name, data);
- else
- FPRINTF(fp, "%s,%d\t%d\n"_fmt, name, i, data);
- }
-}
-
-static
-void script_save_mapreg_strsub(SIR key, ZString data, io::WriteFile& fp)
-{
- int num = key.base(), i = key.index();
- ZString name = variable_names.outtern(num);
- if (name[1] != '@')
- {
- if (i == 0)
- FPRINTF(fp, "%s\t%s\n"_fmt, name, data);
- else
- FPRINTF(fp, "%s,%d\t%s\n"_fmt, name, i, data);
- }
-}
-
-static
-void script_save_mapreg(void)
-{
- io::WriteLock fp(mapreg_txt);
- if (!fp.is_open())
- return;
- for (auto& pair : mapreg_db)
- script_save_mapreg_intsub(pair.first, pair.second, fp);
- for (auto& pair : mapregstr_db)
- script_save_mapreg_strsub(pair.first, pair.second, fp);
- mapreg_dirty = 0;
-}
-
-static
-void script_autosave_mapreg(TimerData *, tick_t)
-{
- if (mapreg_dirty)
- script_save_mapreg();
-}
-
-void do_final_script(void)
-{
- if (mapreg_dirty >= 0)
- script_save_mapreg();
-
- mapreg_db.clear();
- mapregstr_db.clear();
- scriptlabel_db.clear();
- userfunc_db.clear();
-
- str_datam.clear();
-}
-
-/*==========================================
- * 初期化
- *------------------------------------------
- */
-void do_init_script(void)
-{
- script_load_mapreg();
-
- Timer(gettick() + MAPREG_AUTOSAVE_INTERVAL,
- script_autosave_mapreg,
- MAPREG_AUTOSAVE_INTERVAL
- ).detach();
-}
-
-#define BUILTIN(func, args, ret) \
-{builtin_##func, #func ## _s, args, ret}
-
-BuiltinFunction builtin_functions[] =
-{
- BUILTIN(mes, "s"_s, '\0'),
- BUILTIN(goto, "L"_s, '\0'),
- BUILTIN(callfunc, "F"_s, '\0'),
- BUILTIN(callsub, "L"_s, '\0'),
- BUILTIN(return, ""_s, '\0'),
- BUILTIN(next, ""_s, '\0'),
- BUILTIN(close, ""_s, '\0'),
- BUILTIN(close2, ""_s, '\0'),
- BUILTIN(menu, "sL**"_s, '\0'),
- BUILTIN(rand, "i?"_s, 'i'),
- BUILTIN(isat, "Mxy"_s, 'i'),
- BUILTIN(warp, "Mxy"_s, '\0'),
- BUILTIN(areawarp, "MxyxyMxy"_s, '\0'),
- BUILTIN(heal, "ii"_s, '\0'),
- BUILTIN(itemheal, "ii"_s, '\0'),
- BUILTIN(percentheal, "ii"_s, '\0'),
- BUILTIN(input, "N"_s, '\0'),
- BUILTIN(if, "iF*"_s, '\0'),
- BUILTIN(set, "Ne"_s, '\0'),
- BUILTIN(setarray, "Ne*"_s, '\0'),
- BUILTIN(cleararray, "Nei"_s, '\0'),
- BUILTIN(getarraysize, "N"_s, 'i'),
- BUILTIN(getelementofarray, "Ni"_s, '.'),
- BUILTIN(setlook, "ii"_s, '\0'),
- BUILTIN(countitem, "I"_s, 'i'),
- BUILTIN(checkweight, "Ii"_s, 'i'),
- BUILTIN(getitem, "Ii??"_s, '\0'),
- BUILTIN(makeitem, "IiMxy"_s, '\0'),
- BUILTIN(delitem, "Ii"_s, '\0'),
- BUILTIN(readparam, "i?"_s, 'i'),
- BUILTIN(getcharid, "i?"_s, 'i'),
- BUILTIN(strcharinfo, "i"_s, 's'),
- BUILTIN(getequipid, "i"_s, 'i'),
- BUILTIN(getequipname, "i"_s, 's'),
- BUILTIN(statusup2, "ii"_s, '\0'),
- BUILTIN(bonus, "ii"_s, '\0'),
- BUILTIN(bonus2, "iii"_s, '\0'),
- BUILTIN(skill, "ii?"_s, '\0'),
- BUILTIN(setskill, "ii"_s, '\0'),
- BUILTIN(getskilllv, "i"_s, 'i'),
- BUILTIN(getgmlevel, ""_s, 'i'),
- BUILTIN(end, ""_s, '\0'),
- BUILTIN(getopt2, ""_s, 'i'),
- BUILTIN(setopt2, "i"_s, '\0'),
- BUILTIN(savepoint, "Mxy"_s, '\0'),
- BUILTIN(gettimetick, "i"_s, 'i'),
- BUILTIN(gettime, "i"_s, 'i'),
- BUILTIN(openstorage, ""_s, '\0'),
- BUILTIN(getexp, "ii"_s, '\0'),
- BUILTIN(monster, "Mxysmi?"_s, '\0'),
- BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'),
- BUILTIN(killmonster, "ME"_s, '\0'),
- BUILTIN(killmonsterall, "M"_s, '\0'),
- BUILTIN(donpcevent, "E"_s, '\0'),
- BUILTIN(addtimer, "tE"_s, '\0'),
- BUILTIN(initnpctimer, ""_s, '\0'),
- BUILTIN(startnpctimer, "?"_s, '\0'),
- BUILTIN(stopnpctimer, ""_s, '\0'),
- BUILTIN(getnpctimer, "i"_s, 'i'),
- BUILTIN(setnpctimer, "i"_s, '\0'),
- BUILTIN(announce, "si"_s, '\0'),
- BUILTIN(mapannounce, "Msi"_s, '\0'),
- BUILTIN(getusers, "i"_s, 'i'),
- BUILTIN(getmapusers, "M"_s, 'i'),
- BUILTIN(getareausers, "Mxyxy?"_s, 'i'),
- BUILTIN(getareadropitem, "Mxyxyi?"_s, 'i'),
- BUILTIN(enablenpc, "s"_s, '\0'),
- BUILTIN(disablenpc, "s"_s, '\0'),
- BUILTIN(sc_start, "iTi?"_s, '\0'),
- BUILTIN(sc_end, "i"_s, '\0'),
- BUILTIN(sc_check, "i"_s, 'i'),
- BUILTIN(debugmes, "s"_s, '\0'),
- BUILTIN(resetstatus, ""_s, '\0'),
- BUILTIN(changesex, ""_s, '\0'),
- BUILTIN(attachrid, "i"_s, 'i'),
- BUILTIN(detachrid, ""_s, '\0'),
- BUILTIN(isloggedin, "i"_s, 'i'),
- BUILTIN(setmapflag, "Mi"_s, '\0'),
- BUILTIN(removemapflag, "Mi"_s, '\0'),
- BUILTIN(getmapflag, "Mi"_s, 'i'),
- BUILTIN(pvpon, "M"_s, '\0'),
- BUILTIN(pvpoff, "M"_s, '\0'),
- BUILTIN(emotion, "i"_s, '\0'),
- BUILTIN(mapwarp, "MMxy"_s, '\0'),
- BUILTIN(cmdothernpc, "ss"_s, '\0'),
- BUILTIN(mobcount, "ME"_s, 'i'),
- BUILTIN(marriage, "P"_s, 'i'),
- BUILTIN(divorce, ""_s, 'i'),
- BUILTIN(getitemname, "I"_s, 's'),
- BUILTIN(getspellinvocation, "s"_s, 's'),
- BUILTIN(getpartnerid2, ""_s, 'i'),
- BUILTIN(getinventorylist, ""_s, '\0'),
- BUILTIN(getactivatedpoolskilllist, ""_s, '\0'),
- BUILTIN(getunactivatedpoolskilllist, ""_s, '\0'),
- BUILTIN(poolskill, "i"_s, '\0'),
- BUILTIN(unpoolskill, "i"_s, '\0'),
- BUILTIN(misceffect, "i?"_s, '\0'),
- BUILTIN(specialeffect, "i"_s, '\0'),
- BUILTIN(specialeffect2, "i"_s, '\0'),
- BUILTIN(nude, ""_s, '\0'),
- BUILTIN(unequipbyid, "i"_s, '\0'),
- BUILTIN(gmcommand, "s"_s, '\0'),
- BUILTIN(npcwarp, "xys"_s, '\0'),
- BUILTIN(message, "Ps"_s, '\0'),
- BUILTIN(npctalk, "s"_s, '\0'),
- BUILTIN(getlook, "i"_s, 'i'),
- BUILTIN(getsavepoint, "i"_s, '.'),
- BUILTIN(areatimer, "MxyxytE"_s, '\0'),
- BUILTIN(isin, "Mxyxy"_s, 'i'),
- BUILTIN(shop, "s"_s, '\0'),
- BUILTIN(isdead, ""_s, 'i'),
- BUILTIN(fakenpcname, "ssi"_s, '\0'),
- BUILTIN(getx, ""_s, 'i'),
- BUILTIN(gety, ""_s, 'i'),
- BUILTIN(getmap, ""_s, 's'),
- BUILTIN(mapexit, ""_s, '\0'),
- {nullptr, ""_s, ""_s, '\0'},
-};
-
-void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val)
-{
- size_t k = variable_names.intern(var);
- SIR reg = SIR::from(k, e);
- set_reg(sd, VariableCode::VARIABLE, reg, val);
-}
-void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val)
-{
- size_t k = variable_names.intern(var);
- SIR reg = SIR::from(k, e);
- set_reg(sd, VariableCode::VARIABLE, reg, val);
-}
-int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e)
-{
- size_t k = variable_names.intern(var);
- SIR reg = SIR::from(k, e);
- struct script_data dat = ScriptDataVariable{.reg= reg};
- get_val(sd, &dat);
- if (auto *u = dat.get_if<ScriptDataInt>())
- return u->numi;
- PRINTF("Warning: you lied about the type and I'm too lazy to fix it!"_fmt);
- return 0;
-}
-ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e)
-{
- size_t k = variable_names.intern(var);
- SIR reg = SIR::from(k, e);
- struct script_data dat = ScriptDataVariable{.reg= reg};
- get_val(sd, &dat);
- if (auto *u = dat.get_if<ScriptDataStr>())
- // this is almost certainly a memory leak after CONSTSTR removal
- return u->str;
- PRINTF("Warning: you lied about the type and I can't fix it!"_fmt);
- return ZString();
-}
-} // namespace tmwa
diff --git a/src/map/script.hpp b/src/map/script.hpp
deleted file mode 100644
index d5200a6..0000000
--- a/src/map/script.hpp
+++ /dev/null
@@ -1,277 +0,0 @@
-#pragma once
-// script.hpp - EAthena script frontend, engine, and library.
-//
-// Copyright © ????-2004 Athena Dev Teams
-// Copyright © 2004-2011 The Mana World Development Team
-// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-#include <cstdint>
-
-#include <vector>
-
-#include "../range/slice.hpp"
-
-#include "../strings/zstring.hpp"
-
-#include "../generic/db.hpp"
-#include "../generic/dumb_ptr.hpp"
-
-#include "../sexpr/variant.hpp"
-
-#include "../mmo/ids.hpp"
-
-#include "clif.t.hpp"
-#include "map.t.hpp"
-
-
-namespace tmwa
-{
-enum class VariableCode : uint8_t
-{
- PARAM,
- VARIABLE,
-};
-
-enum class StringCode : uint8_t
-{
- NOP, POS, INT, PARAM, FUNC,
- VARIABLE,
-};
-
-enum class ByteCode : uint8_t
-{
- // types and specials
- // Note that 'INT' is synthetic, and does not occur in the data stream
- NOP, POS, INT, PARAM, FUNC, STR, ARG,
- VARIABLE, EOL,
-
- // unary and binary operators
- LOR, LAND, LE, LT, GE, GT, EQ, NE,
- XOR, OR, AND, ADD, SUB, MUL, DIV, MOD,
- NEG, LNOT, NOT, R_SHIFT, L_SHIFT,
-
- // additions
- // needed because FUNC is used for the actual call
- FUNC_REF,
-};
-
-struct str_data_t;
-
-class ScriptBuffer
-{
- typedef ZString::iterator ZSit;
-
- std::vector<ByteCode> script_buf;
-public:
- // construction methods used only by script.cpp
- void add_scriptc(ByteCode a);
- void add_scriptb(uint8_t a);
- void add_scripti(uint32_t a);
- void add_scriptl(str_data_t *a);
- void set_label(str_data_t *ld, int pos_);
- ZSit parse_simpleexpr(ZSit p);
- ZSit parse_subexpr(ZSit p, int limit);
- ZSit parse_expr(ZSit p);
- ZSit parse_line(ZSit p, bool *canstep);
- void parse_script(ZString src, int line, bool implicit_end);
-
- // consumption methods used only by script.cpp
- ByteCode operator[](size_t i) const { return script_buf[i]; }
- ZString get_str(size_t i) const
- {
- return ZString(strings::really_construct_from_a_pointer, reinterpret_cast<const char *>(&script_buf[i]), nullptr);
- }
-
- // method used elsewhere
-};
-
-struct ScriptPointer
-{
- const ScriptBuffer *code;
- size_t pos;
-
- ScriptPointer()
- : code()
- , pos()
- {}
-
- ScriptPointer(const ScriptBuffer *c, size_t p)
- : code(c)
- , pos(p)
- {}
-
- ByteCode peek() const { return (*code)[pos]; }
- ByteCode pop() { return (*code)[pos++]; }
- ZString pops()
- {
- ZString rv = code->get_str(pos);
- pos += rv.size();
- ++pos;
- return rv;
- }
-};
-
-// internal
-class SIR
-{
- uint32_t impl;
- SIR(SP v)
- : impl(static_cast<uint32_t>(v))
- {}
- SIR(unsigned v, uint8_t i)
- : impl((i << 24) | v)
- {}
-public:
- SIR() : impl() {}
-
- unsigned base() const { return impl & 0x00ffffff; }
- uint8_t index() const { return impl >> 24; }
- SIR iplus(uint8_t i) const { return SIR(base(), index() + i); }
- static SIR from(unsigned v, uint8_t i=0) { return SIR(v, i); }
-
- SP sp() const { return static_cast<SP>(impl); }
- static SIR from(SP v) { return SIR(v); }
-
- friend bool operator == (SIR l, SIR r) { return l.impl == r.impl; }
- friend bool operator < (SIR l, SIR r) { return l.impl < r.impl; }
-};
-
-struct ScriptDataPos
-{
- int numi;
-};
-struct ScriptDataInt
-{
- int numi;
-};
-struct ScriptDataParam
-{
- SIR reg;
-};
-struct ScriptDataStr
-{
- RString str;
-};
-struct ScriptDataArg
-{
- int numi;
-};
-struct ScriptDataVariable
-{
- SIR reg;
-};
-struct ScriptDataRetInfo
-{
- // Not a ScriptPointer - pos is stored in a separate slot,
- // to avoid exploding the struct for everyone.
- const ScriptBuffer *script;
-};
-struct ScriptDataFuncRef
-{
- int numi;
-};
-
-using ScriptDataVariantBase = Variant<
- ScriptDataPos,
- ScriptDataInt,
- ScriptDataParam,
- ScriptDataStr,
- ScriptDataArg,
- ScriptDataVariable,
- ScriptDataRetInfo,
- ScriptDataFuncRef
->;
-struct script_data : ScriptDataVariantBase
-{
- script_data() = delete;
- // TODO see if I can delete the copy ctor/assign instead of defaulting
- script_data(script_data&&) = default;
- script_data(const script_data&) = default /*delete*/;
- script_data& operator = (script_data&&) = default;
- script_data& operator = (const script_data&) = default /*delete*/;
-
- script_data(ScriptDataPos v) : ScriptDataVariantBase(std::move(v)) {}
- script_data(ScriptDataInt v) : ScriptDataVariantBase(std::move(v)) {}
- script_data(ScriptDataParam v) : ScriptDataVariantBase(std::move(v)) {}
- script_data(ScriptDataStr v) : ScriptDataVariantBase(std::move(v)) {}
- script_data(ScriptDataArg v) : ScriptDataVariantBase(std::move(v)) {}
- script_data(ScriptDataVariable v) : ScriptDataVariantBase(std::move(v)) {}
- script_data(ScriptDataRetInfo v) : ScriptDataVariantBase(std::move(v)) {}
- script_data(ScriptDataFuncRef v) : ScriptDataVariantBase(std::move(v)) {}
-};
-
-struct script_stack
-{
- std::vector<struct script_data> stack_datav;
-};
-
-enum class ScriptEndState;
-// future improvements coming!
-class ScriptState
-{
-public:
- struct script_stack *stack;
- int start, end;
- ScriptEndState state;
- BlockId rid, oid;
- ScriptPointer scriptp, new_scriptp;
- int defsp, new_defsp;
-};
-
-std::unique_ptr<const ScriptBuffer> parse_script(ZString, int, bool implicit_end);
-
-struct argrec_t
-{
- ZString name;
- union _aru
- {
- int i;
- ZString s;
-
- _aru(int n) : i(n) {}
- _aru(ZString z) : s(z) {}
- } v;
-
- argrec_t(ZString n, int i) : name(n), v(i) {}
- argrec_t(ZString n, ZString z) : name(n), v(z) {}
-};
-int run_script_l(ScriptPointer, BlockId, BlockId, Slice<argrec_t> args);
-int run_script(ScriptPointer, BlockId, BlockId);
-
-extern
-Map<ScriptLabel, int> scriptlabel_db;
-extern
-UPMap<RString, const ScriptBuffer> userfunc_db;
-
-void do_init_script(void);
-void do_final_script(void);
-
-extern AString mapreg_txt;
-
-extern int script_errors;
-
-bool read_constdb(ZString filename);
-
-void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val);
-void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val);
-
-int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e);
-ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e);
-} // namespace tmwa
diff --git a/src/map/script.py b/src/map/script.py
deleted file mode 100644
index a5010cd..0000000
--- a/src/map/script.py
+++ /dev/null
@@ -1,25 +0,0 @@
-class script_data(object):
- enabled = True
-
- test_extra = '''
- using tmwa::operator "" _s;
- '''
-
- tests = [
- ('tmwa::script_data(tmwa::ScriptDataPos{42})',
- '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataPos) = {numi = 42}}, <No data fields>}'),
- ('tmwa::script_data(tmwa::ScriptDataInt{123})',
- '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataInt) = {numi = 123}}, <No data fields>}'),
- ('tmwa::script_data(tmwa::ScriptDataParam{tmwa::SIR()})',
- '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataParam) = {reg = {impl = 0}}}, <No data fields>}'),
- ('tmwa::script_data(tmwa::ScriptDataStr{"Hello"_s})',
- '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataStr) = {str = "Hello"}}, <No data fields>}'),
- ('tmwa::script_data(tmwa::ScriptDataArg{0})',
- '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataArg) = {numi = 0}}, <No data fields>}'),
- ('tmwa::script_data(tmwa::ScriptDataVariable{tmwa::SIR()})',
- '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataVariable) = {reg = {impl = 0}}}, <No data fields>}'),
- ('tmwa::script_data(tmwa::ScriptDataRetInfo{static_cast<const tmwa::ScriptBuffer *>(nullptr)})',
- '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataRetInfo) = {script = 0x0}}, <No data fields>}'),
- ('tmwa::script_data(tmwa::ScriptDataFuncRef{404})',
- '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataFuncRef) = {numi = 404}}, <No data fields>}'),
- ]
diff --git a/src/map/skill-pools.cpp b/src/map/skill-pools.cpp
index 89bf426..dfc70b0 100644
--- a/src/map/skill-pools.cpp
+++ b/src/map/skill-pools.cpp
@@ -21,9 +21,12 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+
+#include "../mmo/cxxstdio_enums.hpp"
#include "battle.hpp"
+#include "consts.hpp"
+#include "globals.hpp"
#include "pc.hpp"
#include "../poison.hpp"
@@ -31,26 +34,18 @@
namespace tmwa
{
-Array<SkillID, MAX_POOL_SKILLS> skill_pool_skills;
-int skill_pool_skills_size = 0;
-
+namespace map
+{
void skill_pool_register(SkillID id)
{
- if (skill_pool_skills_size + 1 >= MAX_POOL_SKILLS)
- {
- FPRINTF(stderr,
- "Too many pool skills! Increase MAX_POOL_SKILLS and recompile."_fmt);
- return;
- }
-
- skill_pool_skills[skill_pool_skills_size++] = id;
+ skill_pool_skills.push_back(id);
}
int skill_pool(dumb_ptr<map_session_data> sd, SkillID *skills)
{
int i, count = 0;
- for (i = 0; count < MAX_SKILL_POOL && i < skill_pool_skills_size; i++)
+ for (i = 0; count < MAX_SKILL_POOL && i < skill_pool_skills.size(); i++)
{
SkillID skill_id = skill_pool_skills[i];
if (bool(sd->status.skill[skill_id].flags & SkillFlags::POOL_ACTIVATED))
@@ -147,4 +142,5 @@ int skill_power_bl(dumb_ptr<block_list> bl, SkillID skill)
else
return 0;
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/skill-pools.hpp b/src/map/skill-pools.hpp
index c8890e8..0f75e8e 100644
--- a/src/map/skill-pools.hpp
+++ b/src/map/skill-pools.hpp
@@ -23,4 +23,7 @@
namespace tmwa
{
+namespace map
+{
+} // namespace map
} // namespace tmwa
diff --git a/src/map/skill.cpp b/src/map/skill.cpp
index add6a42..8a397a3 100644
--- a/src/map/skill.cpp
+++ b/src/map/skill.cpp
@@ -39,16 +39,18 @@
#include "../generic/random.hpp"
#include "../io/cxxstdio.hpp"
-#include "../io/cxxstdio_enums.hpp"
+#include "../io/extract.hpp"
#include "../io/read.hpp"
#include "../net/timer.hpp"
-#include "../mmo/extract.hpp"
+#include "../mmo/cxxstdio_enums.hpp"
#include "../mmo/extract_enums.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "magic-stmt.hpp"
#include "mob.hpp"
#include "pc.hpp"
@@ -58,7 +60,10 @@
namespace tmwa
{
-struct skill_name_db skill_names[] =
+namespace map
+{
+static
+skill_name_db skill_names[] =
{
{SkillID::AC_OWL, "OWL"_s, "Owl's_Eye"_s},
@@ -90,9 +95,6 @@ struct skill_name_db skill_names[] =
{SkillID::ZERO, ""_s, ""_s}
};
-earray<skill_db_, SkillID, SkillID::MAX_SKILL_DB> skill_db;
-
-
static
int skill_attack(BF attack_type, dumb_ptr<block_list> src,
dumb_ptr<block_list> dsrc, dumb_ptr<block_list> bl,
@@ -286,7 +288,7 @@ int skill_attack(BF attack_type, dumb_ptr<block_list> src,
lv = flag.level;
dmg = battle_calc_attack(attack_type, src, bl, skillid, skilllv, flag.lo); //ダメージ計算
- damage = dmg.damage + dmg.damage2;
+ damage = dmg.damage;
if (lv == 15)
lv = -1;
@@ -347,26 +349,16 @@ int skill_attack(BF attack_type, dumb_ptr<block_list> src,
{
hp += (dmg.damage * sd->hp_drain_per) / 100;
}
- if (sd->hp_drain_rate_ && dmg.damage2 > 0
- && random_::chance({sd->hp_drain_rate_, 100}))
- {
- hp += (dmg.damage2 * sd->hp_drain_per_) / 100;
- }
if (sd->sp_drain_rate > 0 && dmg.damage > 0
&& random_::chance({sd->sp_drain_rate, 100}))
{
sp += (dmg.damage * sd->sp_drain_per) / 100;
}
- if (sd->sp_drain_rate_ > 0 && dmg.damage2 > 0
- && random_::chance({sd->sp_drain_rate_, 100}))
- {
- sp += (dmg.damage2 * sd->sp_drain_per_) / 100;
- }
if (hp || sp)
pc_heal(sd, hp, sp);
}
- return (dmg.damage + dmg.damage2); /* 与ダメを返す */
+ return (dmg.damage); /* 与ダメを返す */
}
typedef int(*SkillFunc)(dumb_ptr<block_list>, dumb_ptr<block_list>,
@@ -388,17 +380,6 @@ void skill_area_sub(dumb_ptr<block_list> bl,
}
-/* 範囲スキル使用処理小分けここまで
- * -------------------------------------------------------------------------
- */
-
-// these variables are set in the 'else' branches,
-// and used in the (recursive) 'if' branch
-// TODO kill it, kill it with fire.
-static BlockId skill_area_temp_id;
-static int skill_area_temp_hp;
-
-
/*==========================================
* スキル使用(詠唱完了、ID指定攻撃系)
* (スパゲッティに向けて1歩前進!(ダメポ))
@@ -712,8 +693,7 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
int opt_flag = 0, calc_flag = 0;
- short *sc_count;
- Option *option;
+ Opt0 *option;
Opt1 *opt1;
Opt2 *opt2;
Opt3 *opt3;
@@ -728,8 +708,6 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa
sc_data = battle_get_sc_data(bl);
if (not sc_data)
return;
- sc_count = battle_get_sc_count(bl);
- nullpo_retv(sc_count);
option = battle_get_option(bl);
nullpo_retv(option);
opt1 = battle_get_opt1(bl);
@@ -753,8 +731,6 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa
// whether we are the timer or a cancel no longer matters
assert (!sc_data[type].timer);
- assert ((*sc_count) > 0);
- (*sc_count)--;
switch (type)
{ /* 異常の種類ごとの処理 */
@@ -835,7 +811,6 @@ void skill_status_change_timer(TimerData *tid, tick_t tick, BlockId id, StatusCh
dumb_ptr<block_list> bl;
dumb_ptr<map_session_data> sd = nullptr;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
- //short *sc_count; //使ってない?
if ((bl = map_id2bl(id)) == nullptr)
return;
@@ -847,8 +822,6 @@ void skill_status_change_timer(TimerData *tid, tick_t tick, BlockId id, StatusCh
if (bl->bl_type == BL::PC)
sd = bl->is_player();
- //sc_count=battle_get_sc_count(bl); //使ってない?
-
if (sc_data[type].spell_invocation)
{ // Must report termination
magic::spell_effect_report_termination(sc_data[type].spell_invocation,
@@ -936,8 +909,7 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
{
dumb_ptr<map_session_data> sd = nullptr;
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
- short *sc_count;
- Option *option;
+ Opt0 *option;
Opt1 *opt1;
Opt2 *opt2;
Opt3 *opt3;
@@ -949,8 +921,6 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
sc_data = battle_get_sc_data(bl);
if (not sc_data)
return 0;
- sc_count = battle_get_sc_count(bl);
- nullpo_retz(sc_count);
option = battle_get_option(bl);
nullpo_retz(option);
opt1 = battle_get_opt1(bl);
@@ -994,7 +964,6 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
/* 継ぎ足しができない状態異常である時は状態異常を行わない */
{
- (*sc_count)--;
sc_data[type].timer.cancel();
}
}
@@ -1080,8 +1049,6 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
if (opt_flag) /* optionの変更 */
clif_changeoption(bl);
- (*sc_count)++; /* ステータス異常の数 */
-
sc_data[type].val1 = val1;
if (sc_data[type].spell_invocation) // Supplant by newer spell
magic::spell_effect_report_termination(sc_data[type].spell_invocation,
@@ -1110,8 +1077,7 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
int skill_status_change_clear(dumb_ptr<block_list> bl, int type)
{
eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
- short *sc_count;
- Option *option;
+ Opt0 *option;
Opt1 *opt1;
Opt2 *opt2;
Opt3 *opt3;
@@ -1120,8 +1086,6 @@ int skill_status_change_clear(dumb_ptr<block_list> bl, int type)
sc_data = battle_get_sc_data(bl);
if (not sc_data)
return 0;
- sc_count = battle_get_sc_count(bl);
- nullpo_retz(sc_count);
option = battle_get_option(bl);
nullpo_retz(option);
opt1 = battle_get_opt1(bl);
@@ -1131,18 +1095,15 @@ int skill_status_change_clear(dumb_ptr<block_list> bl, int type)
opt3 = battle_get_opt3(bl);
nullpo_retz(opt3);
- if (*sc_count == 0)
- return 0;
for (StatusChange i : erange(StatusChange(), StatusChange::MAX_STATUSCHANGE))
{
if (sc_data[i].timer)
skill_status_change_end(bl, i, nullptr);
}
- *sc_count = 0;
*opt1 = Opt1::ZERO;
*opt2 = Opt2::ZERO;
*opt3 = Opt3::ZERO;
- *option = Option::ZERO;
+ *option = Opt0::ZERO;
if (type == 0 || type & 2)
clif_changeoption(bl);
@@ -1319,4 +1280,5 @@ skill_name_db& skill_lookup_by_name(XString name)
return ner;
return skill_names[num_names - 1];
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/skill.hpp b/src/map/skill.hpp
index ec353ce..23881d4 100644
--- a/src/map/skill.hpp
+++ b/src/map/skill.hpp
@@ -20,16 +20,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
+#include "../mmo/skill.t.hpp"
+
#include "fwd.hpp"
-#include "skill.t.hpp"
#include "skill-pools.hpp"
-#include "../strings/fwd.hpp"
#include "../strings/rstring.hpp"
#include "../strings/literal.hpp"
-#include "../generic/fwd.hpp"
#include "../generic/array.hpp"
#include "map.hpp"
@@ -37,6 +36,8 @@
namespace tmwa
{
+namespace map
+{
constexpr int MAX_SKILL_PRODUCE_DB = 150;
constexpr int MAX_SKILL_ARROW_DB = 150;
constexpr int MAX_SKILL_ABRA_DB = 350;
@@ -60,8 +61,6 @@ struct skill_db_
int weapon;
Array<int, MAX_SKILL_LEVEL> castnodex;
};
-extern
-earray<skill_db_, SkillID, SkillID::MAX_SKILL_DB> skill_db;
struct skill_name_db
{
@@ -75,9 +74,6 @@ struct skill_name_db
{}
};
-// used only by @skillid for iteration - should be depublicized
-extern struct skill_name_db skill_names[];
-
skill_name_db& skill_lookup_by_id(SkillID id);
skill_name_db& skill_lookup_by_name(XString name);
@@ -135,11 +131,6 @@ void skill_reload(void);
// Max. # of active entries in the skill pool
constexpr int MAX_SKILL_POOL = 3;
-// Max. # of skills that may be classified as pool skills in db/skill_db.txt
-constexpr int MAX_POOL_SKILLS = 128;
-
-extern Array<SkillID, MAX_POOL_SKILLS> skill_pool_skills; // All pool skills
-extern int skill_pool_skills_size; // Number of entries in skill_pool_skills
// Yields all active skills in the skill pool; no more than MAX_SKILL_POOL. Return is number of skills.
int skill_pool(dumb_ptr<map_session_data> sd, SkillID *skills);
@@ -166,4 +157,5 @@ int skill_power_bl(dumb_ptr<block_list> bl, SkillID skill);
// [Fate] Remember that a certain skill ID belongs to a pool skill
void skill_pool_register(SkillID id);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/storage.cpp b/src/map/storage.cpp
index a6e6a0d..1327146 100644
--- a/src/map/storage.cpp
+++ b/src/map/storage.cpp
@@ -25,10 +25,11 @@
#include "../generic/db.hpp"
#include "../mmo/ids.hpp"
-#include "../mmo/mmo.hpp"
+#include "../high/mmo.hpp"
#include "chrif.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "intif.hpp"
#include "itemdb.hpp"
#include "map.hpp"
@@ -39,27 +40,22 @@
namespace tmwa
{
-static
-Map<AccountId, Storage> storage_db;
-
+namespace map
+{
void do_final_storage(void)
{
storage_db.clear();
}
-Storage *account2storage(AccountId account_id)
+Borrowed<Storage> account2storage(AccountId account_id)
{
- Storage *stor = storage_db.search(account_id);
- if (stor == nullptr)
- {
- stor = storage_db.init(account_id);
- stor->account_id = account_id;
- }
+ P<Storage> stor = storage_db.init(account_id);
+ stor->account_id = account_id;
return stor;
}
// Just to ask storage, without creation
-Storage *account2storage2(AccountId account_id)
+Option<Borrowed<Storage>> account2storage2(AccountId account_id)
{
return storage_db.search(account_id);
}
@@ -81,12 +77,11 @@ int storage_storageopen(dumb_ptr<map_session_data> sd)
if (sd->state.storage_open)
return 1; //Already open?
- Storage *stor = storage_db.search(sd->status_key.account_id);
- if (stor == nullptr)
+ P<Storage> stor = TRY_UNWRAP(storage_db.search(sd->status_key.account_id),
{ //Request storage.
intif_request_storage(sd->status_key.account_id);
return 1;
- }
+ });
if (stor->storage_status)
return 1; //Already open/player already has it open...
@@ -104,15 +99,13 @@ int storage_storageopen(dumb_ptr<map_session_data> sd)
*------------------------------------------
*/
static
-int storage_additem(dumb_ptr<map_session_data> sd, Storage *stor,
+int storage_additem(dumb_ptr<map_session_data> sd, P<Storage> stor,
Item *item_data, int amount)
{
- struct item_data *data;
-
if (!item_data->nameid || amount <= 0)
return 1;
- data = itemdb_search(item_data->nameid);
+ P<struct item_data> data = itemdb_search(item_data->nameid);
if (!itemdb_isequip2(data))
{ //Stackable
@@ -152,7 +145,7 @@ int storage_additem(dumb_ptr<map_session_data> sd, Storage *stor,
*------------------------------------------
*/
static
-int storage_delitem(dumb_ptr<map_session_data> sd, Storage *stor,
+int storage_delitem(dumb_ptr<map_session_data> sd, P<Storage> stor,
SOff0 n, int amount)
{
@@ -178,11 +171,8 @@ int storage_delitem(dumb_ptr<map_session_data> sd, Storage *stor,
*/
int storage_storageadd(dumb_ptr<map_session_data> sd, IOff0 index, int amount)
{
- Storage *stor;
-
nullpo_retz(sd);
- stor = account2storage2(sd->status_key.account_id);
- nullpo_retz(stor);
+ P<Storage> stor = TRY_UNWRAP(account2storage2(sd->status_key.account_id), return 0);
if ((stor->storage_amount > MAX_STORAGE) || !stor->storage_status)
return 0; // storage full / storage closed
@@ -213,12 +203,10 @@ int storage_storageadd(dumb_ptr<map_session_data> sd, IOff0 index, int amount)
*/
int storage_storageget(dumb_ptr<map_session_data> sd, SOff0 index, int amount)
{
- Storage *stor;
PickupFail flag;
nullpo_retz(sd);
- stor = account2storage2(sd->status_key.account_id);
- nullpo_retz(stor);
+ P<Storage> stor = TRY_UNWRAP(account2storage2(sd->status_key.account_id), return 0);
if (!index.ok())
return 0;
@@ -243,11 +231,8 @@ int storage_storageget(dumb_ptr<map_session_data> sd, SOff0 index, int amount)
*/
int storage_storageclose(dumb_ptr<map_session_data> sd)
{
- Storage *stor;
-
nullpo_retz(sd);
- stor = account2storage2(sd->status_key.account_id);
- nullpo_retz(stor);
+ P<Storage> stor = TRY_UNWRAP(account2storage2(sd->status_key.account_id), return 0);
clif_storageclose(sd);
if (stor->storage_status)
@@ -275,12 +260,9 @@ int storage_storageclose(dumb_ptr<map_session_data> sd)
*/
int storage_storage_quit(dumb_ptr<map_session_data> sd)
{
- Storage *stor;
-
nullpo_retz(sd);
- stor = account2storage2(sd->status_key.account_id);
- if (stor)
+ P<Storage> stor = TRY_UNWRAP(account2storage2(sd->status_key.account_id), return 0);
{
chrif_save(sd); //Invokes the storage saving as well.
stor->storage_status = 0;
@@ -292,11 +274,7 @@ int storage_storage_quit(dumb_ptr<map_session_data> sd)
int storage_storage_save(AccountId account_id, int final)
{
- Storage *stor;
-
- stor = account2storage2(account_id);
- if (!stor)
- return 0;
+ P<Storage> stor = TRY_UNWRAP(account2storage2(account_id), return 0);
if (stor->dirty)
{
@@ -320,9 +298,8 @@ int storage_storage_save(AccountId account_id, int final)
//Ack from Char-server indicating the storage was saved. [Skotlex]
int storage_storage_saved(AccountId account_id)
{
- Storage *stor = account2storage2(account_id);
+ P<Storage> stor = TRY_UNWRAP(account2storage2(account_id), return 0);
- if (stor)
{
//Only mark it clean if it's not in use. [Skotlex]
if (stor->dirty && stor->storage_status == 0)
@@ -334,4 +311,5 @@ int storage_storage_saved(AccountId account_id)
}
return 0;
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/storage.hpp b/src/map/storage.hpp
index f3875c8..5f118c3 100644
--- a/src/map/storage.hpp
+++ b/src/map/storage.hpp
@@ -22,23 +22,24 @@
#include "fwd.hpp"
-#include "../generic/fwd.hpp"
+#include "../proto2/net-Storage.hpp"
-#include "../mmo/fwd.hpp"
-
-#include "clif.t.hpp"
+#include "../mmo/clif.t.hpp"
namespace tmwa
{
+namespace map
+{
int storage_storageopen(dumb_ptr<map_session_data> sd);
int storage_storageadd(dumb_ptr<map_session_data> sd, IOff0 index, int amount);
int storage_storageget(dumb_ptr<map_session_data> sd, SOff0 index, int amount);
int storage_storageclose(dumb_ptr<map_session_data> sd);
void do_final_storage(void);
-Storage *account2storage(AccountId account_id);
-Storage *account2storage2(AccountId account_id);
+Borrowed<Storage> account2storage(AccountId account_id);
+Option<Borrowed<Storage>> account2storage2(AccountId account_id);
int storage_storage_quit(dumb_ptr<map_session_data> sd);
int storage_storage_save(AccountId account_id, int final);
int storage_storage_saved(AccountId account_id);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/tmw.cpp b/src/map/tmw.cpp
index 60b5027..df76720 100644
--- a/src/map/tmw.cpp
+++ b/src/map/tmw.cpp
@@ -28,13 +28,16 @@
#include "../io/cxxstdio.hpp"
+#include "../net/timer.hpp"
+
#include "../mmo/human_time_diff.hpp"
-#include "../mmo/utils.hpp"
#include "atcommand.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "chrif.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "intif.hpp"
#include "map.hpp"
#include "pc.hpp"
@@ -44,8 +47,10 @@
namespace tmwa
{
+namespace map
+{
static
-void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, int length);
+void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, std::chrono::hours length);
static
bool tmw_CheckChatLameness(dumb_ptr<map_session_data> sd, XString message);
@@ -53,21 +58,21 @@ bool tmw_CheckChatLameness(dumb_ptr<map_session_data> sd, XString message);
int tmw_CheckChatSpam(dumb_ptr<map_session_data> sd, XString message)
{
nullpo_retr(1, sd);
- TimeT now = TimeT::now();
+ tick_t now = gettick();
if (pc_isGM(sd))
return 0;
if (now > sd->chat_reset_due)
{
- sd->chat_reset_due = static_cast<time_t>(now) + battle_config.chat_spam_threshold;
+ sd->chat_reset_due = now + battle_config.chat_spam_threshold;
sd->chat_lines_in = 0;
}
if (now > sd->chat_repeat_reset_due)
{
sd->chat_repeat_reset_due =
- static_cast<time_t>(now) + (battle_config.chat_spam_threshold * 60);
+ now + (battle_config.chat_spam_threshold * 60);
sd->chat_total_repeats = 0;
}
@@ -100,20 +105,20 @@ int tmw_CheckChatSpam(dumb_ptr<map_session_data> sd, XString message)
return 1;
}
- if (battle_config.chat_spam_ban &&
+ if (battle_config.chat_spam_ban != std::chrono::hours::zero() &&
(sd->chat_lines_in >= battle_config.chat_spam_warn
|| sd->chat_total_repeats >= battle_config.chat_spam_warn))
{
- clif_displaymessage(sd->sess, "WARNING: You are about to be automatically banned for spam!"_s);
- clif_displaymessage(sd->sess, "WARNING: Please slow down, do not repeat, and do not SHOUT!"_s);
+ clif_displaymessage(sd->sess, "##1WARNING : ##BYou are about to be automatically banned for spam!"_s);
+ clif_displaymessage(sd->sess, "##1WARNING : ##BPlease slow down, do not repeat, and do not SHOUT!"_s);
}
return 0;
}
-void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, int length)
+void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, std::chrono::hours length)
{
- if (length == 0 || sd->auto_ban_info.in_progress)
+ if (length == std::chrono::hours::zero() || sd->auto_ban_info.in_progress)
return;
sd->auto_ban_info.in_progress = 1;
@@ -124,7 +129,7 @@ void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, int length)
tmw_GmHackMsg(hack_msg);
AString fake_command = STRPRINTF("@autoban %s %dh (%s spam)"_fmt,
- sd->status_key.name, length, reason);
+ sd->status_key.name, static_cast<uint16_t>(length.count()), reason);
log_atcommand(sd, fake_command);
AString anotherbuf = STRPRINTF("You have been banned for %s spamming. Please do not spam."_fmt,
@@ -133,7 +138,7 @@ void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, int length)
clif_displaymessage(sd->sess, anotherbuf);
/* type: 2 - ban(year, month, day, hour, minute, second) */
HumanTimeDiff ban_len {};
- ban_len.hour = length;
+ ban_len.hour = length.count();
chrif_char_ask_name(AccountId(), sd->status_key.name, 2, ban_len);
clif_setwaitclose(sd->sess);
}
@@ -162,8 +167,9 @@ bool tmw_CheckChatLameness(dumb_ptr<map_session_data>, XString message)
// Sends a whisper to all GMs
void tmw_GmHackMsg(ZString line)
{
- intif_wis_message_to_gm(wisp_server_name,
- GmLevel::from(static_cast<uint32_t>(battle_config.hack_info_GM_level)),
+ intif_wis_message_to_gm(WISP_SERVER_NAME,
+ battle_config.hack_info_GM_level,
line);
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/tmw.hpp b/src/map/tmw.hpp
index ffd6f7f..bc0043c 100644
--- a/src/map/tmw.hpp
+++ b/src/map/tmw.hpp
@@ -21,13 +21,12 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
-#include "../generic/fwd.hpp"
-
namespace tmwa
{
+namespace map
+{
int tmw_CheckChatSpam(dumb_ptr<map_session_data> sd, XString message);
void tmw_GmHackMsg(ZString line);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/trade.cpp b/src/map/trade.cpp
index c52377d..c03609c 100644
--- a/src/map/trade.cpp
+++ b/src/map/trade.cpp
@@ -25,7 +25,9 @@
#include "../io/cxxstdio.hpp"
#include "battle.hpp"
+#include "battle_conf.hpp"
#include "clif.hpp"
+#include "globals.hpp"
#include "itemdb.hpp"
#include "map.hpp"
#include "npc.hpp"
@@ -37,6 +39,8 @@
namespace tmwa
{
+namespace map
+{
/*==========================================
* 取引要請を相手に送る
*------------------------------------------
@@ -129,7 +133,6 @@ void trade_tradeack(dumb_ptr<map_session_data> sd, int type)
void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount)
{
dumb_ptr<map_session_data> target_sd;
- struct item_data *id;
int trade_i;
int trade_weight = 0;
int free_ = 0;
@@ -156,7 +159,7 @@ void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount)
for (IOff0 i : IOff0::iter())
{
if (!target_sd->status.inventory[i].nameid
- && target_sd->inventory_data[i] == nullptr)
+ && target_sd->inventory_data[i].is_none())
free_++;
}
for (trade_i = 0; trade_i < TRADE_MAX; trade_i++)
@@ -164,26 +167,31 @@ void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount)
if (sd->deal_item_amount[trade_i] == 0)
{
// calculate trade weight
+ // note: 'abort' branch is protected by 'amount' check above
trade_weight +=
- sd->inventory_data[index.unshift()]->weight * amount;
+ TRY_UNWRAP(sd->inventory_data[index.unshift()], abort())->weight * amount;
// determine if item is a stackable already in receivers inventory, and up free count
for (IOff0 i : IOff0::iter())
{
- if (target_sd->status.inventory[i].nameid ==
- sd->status.inventory[index.unshift()].nameid
- && target_sd->inventory_data[i] != nullptr)
+ if (target_sd->status.inventory[i].nameid !=
+ sd->status.inventory[index.unshift()].nameid)
+ continue;
+
+ OMATCH_BEGIN_SOME (id, target_sd->inventory_data[i])
{
- id = target_sd->inventory_data[i];
if (id->type != ItemType::WEAPON
&& id->type != ItemType::ARMOR
&& id->type != ItemType::_7
&& id->type != ItemType::_8)
{
free_++;
- break;
+ goto break_outer1;
}
}
+ OMATCH_END ();
+ break_outer1:
+ break;
}
if (target_sd->weight + trade_weight >
@@ -218,28 +226,32 @@ void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount)
else
{
// calculate weight for stored deal
+ // note: 'abort' branch is protected by 'amount' check above
trade_weight +=
- sd->inventory_data[sd->deal_item_index[trade_i].unshift()
- ]->weight *
+ TRY_UNWRAP(sd->inventory_data[sd->deal_item_index[trade_i].unshift()
+ ], abort())->weight *
sd->deal_item_amount[trade_i];
// count free stackables in stored deal
for (IOff0 i : IOff0::iter())
{
- if (target_sd->status.inventory[i].nameid ==
+ if (target_sd->status.inventory[i].nameid !=
sd->status.
- inventory[sd->deal_item_index[trade_i].unshift()].nameid
- && target_sd->inventory_data[i] != nullptr)
+ inventory[sd->deal_item_index[trade_i].unshift()].nameid)
+ continue;
+ OMATCH_BEGIN_SOME (id, target_sd->inventory_data[i])
{
- id = target_sd->inventory_data[i];
if (id->type != ItemType::WEAPON
&& id->type != ItemType::ARMOR
&& id->type != ItemType::_7
&& id->type != ItemType::_8)
{
free_++;
- break;
+ goto break_outer2;
}
}
+ OMATCH_END ();
+ break_outer2:
+ break;
}
}
// used a slot, but might be cancelled out by stackable checks above
@@ -465,4 +477,5 @@ void trade_verifyzeny(dumb_ptr<map_session_data> sd)
}
}
}
+} // namespace map
} // namespace tmwa
diff --git a/src/map/trade.hpp b/src/map/trade.hpp
index 91ed954..569524b 100644
--- a/src/map/trade.hpp
+++ b/src/map/trade.hpp
@@ -22,13 +22,13 @@
#include "fwd.hpp"
-#include "../generic/fwd.hpp"
-
-#include "clif.t.hpp"
+#include "../mmo/clif.t.hpp"
namespace tmwa
{
+namespace map
+{
void trade_traderequest(dumb_ptr<map_session_data> sd, BlockId target_id);
void trade_tradeack(dumb_ptr<map_session_data> sd, int type);
void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount);
@@ -36,4 +36,5 @@ void trade_tradeok(dumb_ptr<map_session_data> sd);
void trade_tradecancel(dumb_ptr<map_session_data> sd);
void trade_tradecommit(dumb_ptr<map_session_data> sd);
void trade_verifyzeny(dumb_ptr<map_session_data> sd);
+} // namespace map
} // namespace tmwa
diff --git a/src/map/clif.t.hpp b/src/mmo/clif.t.hpp
index 1789ee8..0a51523 100644
--- a/src/map/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -30,8 +30,8 @@
#include "../generic/enum.hpp"
-#include "../mmo/consts.hpp"
-#include "../mmo/enums.hpp"
+#include "consts.hpp"
+#include "enums.hpp"
namespace tmwa
@@ -40,7 +40,7 @@ namespace e
{
// [Fate] status.option properties. These are persistent status changes.
// IDs that are not listed are not used in the code (to the best of my knowledge)
-enum class Option : uint16_t
+enum class Opt0 : uint16_t
{
ZERO = 0x0000,
@@ -88,11 +88,11 @@ enum class Opt3 : uint16_t
_assumptio = 0x0800,
};
-ENUM_BITWISE_OPERATORS(Option)
+ENUM_BITWISE_OPERATORS(Opt0)
ENUM_BITWISE_OPERATORS(Opt2)
ENUM_BITWISE_OPERATORS(Opt3)
}
-using e::Option;
+using e::Opt0;
using e::Opt1;
using e::Opt2;
using e::Opt3;
diff --git a/src/mmo/config_parse.cpp b/src/mmo/config_parse.cpp
index 8362810..6e43471 100644
--- a/src/mmo/config_parse.cpp
+++ b/src/mmo/config_parse.cpp
@@ -23,7 +23,10 @@
#include "../strings/xstring.hpp"
#include "../strings/zstring.hpp"
+#include "../compat/borrow.hpp"
+
#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
#include "../io/line.hpp"
#include "version.hpp"
@@ -39,39 +42,138 @@ bool is_comment(XString line)
}
template<class ZS>
+static
+bool do_split(io::Spanned<ZS> line, io::Spanned<XString> *key, io::Spanned<ZS> *value)
+{
+ typename ZS::iterator colon = std::find(line.data.begin(), line.data.end(), ':');
+ if (colon == line.data.end())
+ return false;
+ key->data = line.data.xislice_h(colon);
+ key->span = line.span;
+ key->span.end.column = key->span.begin.column + key->data.size() - 1;
+ ++colon;
+ value->data = line.data.xislice_t(colon);
+ value->span = line.span;
+ value->span.begin.column = value->span.end.column - value->data.size() + 1;
+ return true;
+}
+
+template<class ZS>
+static
+io::Spanned<ZS> do_lstrip(io::Spanned<ZS> value)
+{
+ io::Spanned<ZS> rv;
+ rv.data = value.data.lstrip();
+ rv.span = value.span;
+ rv.span.begin.column += (value.data.size() - rv.data.size());
+ return rv;
+}
+
+static
+io::Spanned<XString> do_rstrip(io::Spanned<XString> value)
+{
+ io::Spanned<XString> rv;
+ rv.data = value.data.rstrip();
+ rv.span = value.span;
+ rv.span.end.column -= (value.data.size() - rv.data.size());
+ return rv;
+}
+
+static
+io::Spanned<XString> do_strip(io::Spanned<XString> value)
+{
+ return do_lstrip(do_rstrip(value));
+}
+
+template<class ZS>
inline
-bool config_split_impl(ZS line, XString *key, ZS *value)
+bool config_split_impl(io::Spanned<ZS> line, io::Spanned<XString> *key, io::Spanned<ZS> *value)
{
// unconditionally fail if line contains control characters
- if (std::find_if(line.begin(), line.end(),
+ if (std::find_if(line.data.begin(), line.data.end(),
[](unsigned char c) { return c < ' '; }
- ) != line.end())
- return false;
- // try to find a colon, fail if not found
- typename ZS::iterator colon = std::find(line.begin(), line.end(), ':');
- if (colon == line.end())
+ ) != line.data.end())
return false;
- *key = line.xislice_h(colon).strip();
- // move past the colon and any spaces
- ++colon;
- *value = line.xislice_t(colon).lstrip();
+ if (!do_split(line, key, value))
+ return false;
+ *key = do_strip(*key);
+ *value = do_lstrip(*value);
return true;
}
// eventually this should go away
-bool config_split(ZString line, XString *key, ZString *value)
+// currently the only real offenders are io::FD::open and *PRINTF
+bool config_split(io::Spanned<ZString> line, io::Spanned<XString> *key, io::Spanned<ZString> *value)
{
return config_split_impl(line, key, value);
}
-// and use this instead
-bool config_split(XString line, XString *key, XString *value)
+
+static
+bool check_version(io::Spanned<XString> avers, Borrowed<bool> valid)
{
- return config_split_impl(line, key, value);
+ enum
+ {
+ GE, LE, GT, LT
+ } cmp;
+
+ if (avers.data.startswith(">="_s))
+ {
+ cmp = GE;
+ avers.data = avers.data.xslice_t(2);
+ avers.span.begin.column += 2;
+ }
+ else if (avers.data.startswith('>'))
+ {
+ cmp = GT;
+ avers.data = avers.data.xslice_t(1);
+ avers.span.begin.column += 1;
+ }
+ else if (avers.data.startswith("<="_s))
+ {
+ cmp = LE;
+ avers.data = avers.data.xslice_t(2);
+ avers.span.begin.column += 2;
+ }
+ else if (avers.data.startswith('<'))
+ {
+ cmp = LT;
+ avers.data = avers.data.xslice_t(1);
+ avers.span.begin.column += 1;
+ }
+ else
+ {
+ avers.span.error("Version check must begin with one of: '>=', '>', '<=', '<'"_s);
+ *valid = false;
+ return false;
+ }
+
+ Version vers;
+ if (!extract(avers.data, &vers))
+ {
+ avers.span.error("Bad value"_s);
+ *valid = false;
+ return false;
+ }
+ switch (cmp)
+ {
+ case GE:
+ return CURRENT_VERSION >= vers;
+ case GT:
+ return CURRENT_VERSION > vers;
+ case LE:
+ return CURRENT_VERSION <= vers;
+ case LT:
+ return CURRENT_VERSION < vers;
+ }
+ abort();
}
/// Master config parser. This handles 'import' and 'version-ge' etc.
/// Then it defers to the inferior parser for a line it does not understand.
+///
+/// Note: old-style 'version-ge: 1.2.3' etc apply to the rest of the file, but
+/// new-style 'version: >= 1.2.3' apply only up to the next 'version:'
bool load_config_file(ZString filename, ConfigItemParser slave)
{
io::LineReader in(filename);
@@ -80,30 +182,66 @@ bool load_config_file(ZString filename, ConfigItemParser slave)
PRINTF("Unable to open file: %s\n"_fmt, filename);
return false;
}
- io::Line line;
+ io::Line line_;
bool rv = true;
- while (in.read_line(line))
+ bool good_version = true;
+ while (in.read_line(line_))
{
- if (is_comment(line.text))
+ if (is_comment(line_.text))
continue;
- XString key;
- ZString value;
- if (!config_split(line.text, &key, &value))
+ auto line = io::respan(line_.to_span(), ZString(line_.text));
+ io::Spanned<XString> key;
+ io::Spanned<ZString> value;
+ if (!config_split(line, &key, &value))
{
- line.error("Bad config line"_s);
+ line.span.error("Bad config line"_s);
rv = false;
continue;
}
- if (key == "import"_s)
+ if (key.data == "version"_s)
+ {
+ if (value.data == "all"_s)
+ {
+ good_version = true;
+ }
+ else
+ {
+ good_version = true;
+ while (good_version && value.data)
+ {
+ ZString::iterator it = std::find(value.data.begin(), value.data.end(), ' ');
+ io::Spanned<XString> value_head;
+ value_head.data = value.data.xislice_h(it);
+ value_head.span = value.span;
+ value.data = value.data.xislice_t(it).lstrip();
+
+ value_head.span.end.column = value_head.span.begin.column + value_head.data.size() - 1;
+ value.span.begin.column = value.span.end.column - value.data.size() + 1;
+
+ good_version &= check_version(value_head, borrow(rv));
+ }
+ }
+ continue;
+ }
+ if (!good_version)
{
- rv &= load_config_file(value, slave);
continue;
}
- else if (key == "version-lt"_s)
+ if (key.data == "import"_s)
+ {
+ if (!load_config_file(value.data, slave))
+ {
+ value.span.error("Failed to include file"_s);
+ rv = false;
+ }
+ continue;
+ }
+ else if (key.data == "version-lt"_s)
{
Version vers;
- if (!extract(value, &vers))
+ if (!extract(value.data, &vers))
{
+ value.span.error("Bad value"_s);
rv = false;
continue;
}
@@ -111,47 +249,48 @@ bool load_config_file(ZString filename, ConfigItemParser slave)
continue;
break;
}
- else if (key == "version-le"_s)
+ else if (key.data == "version-le"_s)
{
Version vers;
- if (!extract(value, &vers))
+ if (!extract(value.data, &vers))
{
rv = false;
+ value.span.error("Bad value"_s);
continue;
}
if (CURRENT_VERSION <= vers)
continue;
break;
}
- else if (key == "version-gt"_s)
+ else if (key.data == "version-gt"_s)
{
Version vers;
- if (!extract(value, &vers))
+ if (!extract(value.data, &vers))
{
rv = false;
+ value.span.error("Bad value"_s);
continue;
}
if (CURRENT_VERSION > vers)
continue;
break;
}
- else if (key == "version-ge"_s)
+ else if (key.data == "version-ge"_s)
{
Version vers;
- if (!extract(value, &vers))
+ if (!extract(value.data, &vers))
{
rv = false;
+ value.span.error("Bad value"_s);
continue;
}
if (CURRENT_VERSION >= vers)
continue;
break;
}
- else if (!slave(key, value))
+ else
{
- line.error("Bad config key or value"_s);
- rv = false;
- continue;
+ rv &= slave(key, value);
}
// nothing to see here, move along
}
diff --git a/src/mmo/config_parse.hpp b/src/mmo/config_parse.hpp
index 50a115e..06432ba 100644
--- a/src/mmo/config_parse.hpp
+++ b/src/mmo/config_parse.hpp
@@ -20,16 +20,13 @@
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
namespace tmwa
{
-typedef bool (*ConfigItemParser)(XString key, ZString value);
+using ConfigItemParser = bool(io::Spanned<XString> key, io::Spanned<ZString> value);
bool is_comment(XString line);
-bool config_split(ZString line, XString *key, ZString *value);
-bool config_split(XString line, XString *key, XString *value);
+bool config_split(io::Spanned<ZString> line, io::Spanned<XString> *key, io::Spanned<ZString> *value);
/// Master config parser. This handles 'import' and 'version-ge' etc.
/// Then it defers to the inferior parser for a line it does not understand.
diff --git a/src/mmo/config_parse_test.cpp b/src/mmo/config_parse_test.cpp
new file mode 100644
index 0000000..e1170cb
--- /dev/null
+++ b/src/mmo/config_parse_test.cpp
@@ -0,0 +1,60 @@
+#include "config_parse.hpp"
+// config_parse_test.cpp - Testsuite for config parsers
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <gtest/gtest.h>
+
+#include "../strings/literal.hpp"
+#include "../strings/rstring.hpp"
+
+#include "../io/span.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+#define EXPECT_SPAN(span, bl,bc, el,ec) \
+ ({ \
+ EXPECT_EQ((span).begin.line, bl); \
+ EXPECT_EQ((span).begin.column, bc); \
+ EXPECT_EQ((span).end.line, el); \
+ EXPECT_EQ((span).end.column, ec); \
+ })
+TEST(configparse, keyvalue)
+{
+ // 0 1 2 3
+ // 123456789012345678901234567890
+ RString data = " key : value "_s;
+
+ io::Spanned<ZString> input, value;
+ io::Spanned<XString> key;
+ input.data = data;
+ input.span.begin.text = data;
+ input.span.begin.filename = "<config parse key/value test>"_s;
+ input.span.begin.line = 1;
+ input.span.begin.column = 1;
+ input.span.end = input.span.begin;
+ input.span.end.column = data.size();
+ EXPECT_EQ(data.size(), 30);
+ ASSERT_TRUE(config_split(input, &key, &value));
+ EXPECT_SPAN(key.span, 1,3, 1,5);
+ EXPECT_SPAN(value.span, 1,18, 1,30);
+}
+} // namespace tmwa
diff --git a/src/mmo/consts.hpp b/src/mmo/consts.hpp
index c1a7eb6..5533446 100644
--- a/src/mmo/consts.hpp
+++ b/src/mmo/consts.hpp
@@ -54,6 +54,11 @@ constexpr int MAX_PARTY = 12;
#define MIN_CLOTH_COLOR battle_config.min_cloth_color
#define MAX_CLOTH_COLOR battle_config.max_cloth_color
+namespace map
+{
+ struct map_session_data;
+}
+
// WTF is this doing here?
struct PartyMember
{
@@ -61,6 +66,6 @@ struct PartyMember
CharName name;
MapName map;
int leader, online, lv;
- struct map_session_data *sd;
+ map::map_session_data *sd;
};
} // namespace tmwa
diff --git a/src/io/cxxstdio_enums.hpp b/src/mmo/cxxstdio_enums.hpp
index 05cdcae..28a8a14 100644
--- a/src/io/cxxstdio_enums.hpp
+++ b/src/mmo/cxxstdio_enums.hpp
@@ -29,49 +29,58 @@ namespace tmwa
{
namespace e
{
-enum class BF : uint16_t;
enum class EPOS : uint16_t;
-enum class MapCell : uint8_t;
-enum class Option : uint16_t;
+enum class Opt0 : uint16_t;
inline
-auto decay_for_printf(BF v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
-inline
auto decay_for_printf(EPOS v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
inline
-auto decay_for_printf(MapCell v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
+auto decay_for_printf(Opt0 v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
+} // namespace e
+
+enum class ItemLook : uint16_t;
+enum class SP : uint16_t;
+enum class SkillID : uint16_t;
+enum class StatusChange : uint16_t;
+
+inline
+auto decay_for_printf(ItemLook v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
+inline
+auto decay_for_printf(SP v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
inline
-auto decay_for_printf(Option v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
-}
+auto decay_for_printf(SkillID v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
+inline
+auto decay_for_printf(StatusChange v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
+
+namespace map
+{
+namespace e
+{
+enum class BF : uint16_t;
+enum class MapCell : uint8_t;
+inline
+auto decay_for_printf(BF v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
+inline
+auto decay_for_printf(MapCell v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
+} // namespace map::e
namespace magic
{
enum class SPELLARG : uint8_t;
inline
auto decay_for_printf(SPELLARG v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
-}
+} // namespace map::magic
enum class BL : uint8_t;
enum class ByteCode : uint8_t;
-enum class ItemLook : uint16_t;
enum class MS : uint8_t;
-enum class SP : uint16_t;
-enum class SkillID : uint16_t;
-enum class StatusChange : uint16_t;
inline
auto decay_for_printf(BL v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
inline
auto decay_for_printf(ByteCode v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
inline
-auto decay_for_printf(ItemLook v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
-inline
auto decay_for_printf(MS v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
-inline
-auto decay_for_printf(SP v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
-inline
-auto decay_for_printf(SkillID v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
-inline
-auto decay_for_printf(StatusChange v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
+} // namespace map
} // namespace tmwa
diff --git a/src/mmo/enums.cpp b/src/mmo/enums.cpp
deleted file mode 100644
index d05be91..0000000
--- a/src/mmo/enums.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "enums.hpp"
-// enums.cpp - Common enumerated types
-//
-// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/mmo/enums.hpp b/src/mmo/enums.hpp
index bf8a75c..caecc13 100644
--- a/src/mmo/enums.hpp
+++ b/src/mmo/enums.hpp
@@ -66,14 +66,14 @@ enum class SkillFlags : uint16_t;
}
using e::SkillFlags;
-// Option and Opt1..3 in map.hpp
+// Opt0 and Opt1..3 in map.hpp
namespace e
{
-enum class Option : uint16_t;
-constexpr Option get_enum_min_value(Option) { return Option(0x0000); }
-constexpr Option get_enum_max_value(Option) { return Option(0xffff); }
+enum class Opt0 : uint16_t;
+constexpr Opt0 get_enum_min_value(Opt0) { return Opt0(0x0000); }
+constexpr Opt0 get_enum_max_value(Opt0) { return Opt0(0xffff); }
}
-using e::Option;
+using e::Opt0;
enum class ATTR
{
@@ -101,22 +101,10 @@ enum class ItemLook : uint16_t
{
NONE = 0,
BLADE = 1, // or some other common weapons
- _2,
SETZER_AND_SCYTHE = 3,
- _6,
STAFF = 10,
BOW = 11,
- _13 = 13,
- _14 = 14,
- _16 = 16,
- SINGLE_HANDED_COUNT = 17,
-
- DUAL_BLADE = 0x11,
- DUAL_2 = 0x12,
- DUAL_6 = 0x13,
- DUAL_12 = 0x14,
- DUAL_16 = 0x15,
- DUAL_26 = 0x16,
+ COUNT = 17,
};
enum class SEX : uint8_t
@@ -124,6 +112,7 @@ enum class SEX : uint8_t
FEMALE = 0,
MALE = 1,
// For items. This is also used as error, sometime.
+ // TODO switch to Option<SEX> where appropriate.
NEUTRAL = 2,
};
inline
diff --git a/src/mmo/extract_enums.cpp b/src/mmo/extract_enums.cpp
index f906179..efafa39 100644
--- a/src/mmo/extract_enums.cpp
+++ b/src/mmo/extract_enums.cpp
@@ -23,4 +23,37 @@
namespace tmwa
{
+bool impl_extract(XString str, DIR *d)
+{
+ unsigned di;
+ if (extract(str, &di) && di < 8)
+ {
+ *d = static_cast<DIR>(di);
+ return true;
+ }
+ const struct
+ {
+ LString str;
+ DIR d;
+ } dirs[] =
+ {
+ {"S"_s, DIR::S},
+ {"SW"_s, DIR::SW},
+ {"W"_s, DIR::W},
+ {"NW"_s, DIR::NW},
+ {"N"_s, DIR::N},
+ {"NE"_s, DIR::NE},
+ {"E"_s, DIR::E},
+ {"SE"_s, DIR::SE},
+ };
+ for (auto& pair : dirs)
+ {
+ if (str == pair.str)
+ {
+ *d = pair.d;
+ return true;
+ }
+ }
+ return false;
+}
} // namespace tmwa
diff --git a/src/mmo/extract_enums.hpp b/src/mmo/extract_enums.hpp
index 613fae9..0e8ac4c 100644
--- a/src/mmo/extract_enums.hpp
+++ b/src/mmo/extract_enums.hpp
@@ -22,7 +22,9 @@
#include <cstdint>
-#include "extract.hpp"
+#include "../io/extract.hpp"
+
+#include "clif.t.hpp"
namespace tmwa
@@ -30,40 +32,54 @@ namespace tmwa
namespace e
{
enum class EPOS : uint16_t;
-enum class MobMode : uint16_t;
enum class Opt1 : uint16_t;
enum class Opt2 : uint16_t;
-enum class Option : uint16_t;
+enum class Opt0 : uint16_t;
inline
-bool extract(XString str, EPOS *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, EPOS *iv) { return extract_as_int(str, iv); }
inline
-bool extract(XString str, MobMode *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, Opt1 *iv) { return extract_as_int(str, iv); }
inline
-bool extract(XString str, Opt1 *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, Opt2 *iv) { return extract_as_int(str, iv); }
inline
-bool extract(XString str, Opt2 *iv) { return extract_as_int(str, iv); }
-inline
-bool extract(XString str, Option *iv) { return extract_as_int(str, iv); }
-}
+bool impl_extract(XString str, Opt0 *iv) { return extract_as_int(str, iv); }
+} // namespace e
enum class ItemLook : uint16_t;
enum class ItemType : uint8_t;
-enum class Race : uint8_t;
enum class SEX : uint8_t;
enum class SkillID : uint16_t;
enum class StatusChange : uint16_t;
inline
-bool extract(XString str, ItemLook *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, ItemLook *iv) { return extract_as_int(str, iv); }
inline
-bool extract(XString str, ItemType *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, ItemType *iv) { return extract_as_int(str, iv); }
inline
-bool extract(XString str, Race *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, SEX *iv) { return extract_as_int(str, iv); }
inline
-bool extract(XString str, SEX *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, SkillID *iv) { return extract_as_int(str, iv); }
+inline
+bool impl_extract(XString str, StatusChange *iv) { return extract_as_int(str, iv); }
+
+bool impl_extract(XString, DIR *);
+
+namespace map
+{
+namespace e
+{
+enum class MobMode : uint16_t;
+
+inline
+bool impl_extract(XString str, MobMode *iv) { return extract_as_int(str, iv); }
+} // namespace map::e
+enum class Race : uint8_t;
+enum class ATK;
+
inline
-bool extract(XString str, SkillID *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, Race *iv) { return extract_as_int(str, iv); }
inline
-bool extract(XString str, StatusChange *iv) { return extract_as_int(str, iv); }
+bool impl_extract(XString str, ATK *iv) { return extract_as_int(str, iv); }
+} // namespace map
} // namespace tmwa
diff --git a/src/mmo/fwd.hpp b/src/mmo/fwd.hpp
index 3b56bfb..434885e 100644
--- a/src/mmo/fwd.hpp
+++ b/src/mmo/fwd.hpp
@@ -20,21 +20,29 @@
#include "../sanity.hpp"
+#include "../ints/fwd.hpp" // rank 1
+#include "../strings/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp" // rank 2
+#include "../generic/fwd.hpp" // rank 3
+#include "../io/fwd.hpp" // rank 4
+#include "../net/fwd.hpp" // rank 5
+// mmo/fwd.hpp is rank 6
+
namespace tmwa
{
// meh, add more when I feel like it
class MapName;
class CharName;
-class CharPair;
class HumanTimeDiff;
+class Species;
class AccountId;
class CharId;
class PartyId;
-class ItemUnkId;
class ItemNameId;
+class BlockId;
class GmLevel;
class AccountName;
@@ -43,26 +51,14 @@ class AccountCrypt;
class AccountEmail;
class ServerName;
class PartyName;
+class QuestId;
class VarName;
class MapName;
class CharName;
-class Item;
-#if 0
-class Point;
-class SkillValue;
-#endif
-class GlobalReg;
-#if 0
-class CharKey;
-class CharData;
-class CharPair;
-#endif
-class Storage;
-#if 0
-class GM_Account;
-class PartyMember;
-#endif
-class PartyMost;
-class PartyPair;
+struct MobName;
+struct NpcName;
+struct ScriptLabel;
+struct ItemName;
+struct NpcEvent;
} // namespace tmwa
diff --git a/src/mmo/human_time_diff.cpp b/src/mmo/human_time_diff.cpp
index 49a7664..aaa7928 100644
--- a/src/mmo/human_time_diff.cpp
+++ b/src/mmo/human_time_diff.cpp
@@ -18,9 +18,51 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
+#include "../io/extract.hpp"
+
#include "../poison.hpp"
namespace tmwa
{
+bool impl_extract(XString str, HumanTimeDiff *iv)
+{
+ // str is a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s)
+ // there are NO spaces here
+ // parse by counting the number starts
+ auto is_num = [](char c)
+ { return c == '-' || c == '+' || ('0' <= c && c <= '9'); };
+ if (!str || !is_num(str.front()))
+ return false;
+ *iv = HumanTimeDiff{};
+ while (str)
+ {
+ auto it = std::find_if_not(str.begin(), str.end(), is_num);
+ auto it2 = std::find_if(it, str.end(), is_num);
+ XString number = str.xislice_h(it);
+ XString suffix = str.xislice(it, it2);
+ str = str.xislice_t(it2);
+
+ short *ptr = nullptr;
+ if (suffix == "y"_s || suffix == "a"_s)
+ ptr = &iv->year;
+ else if (suffix == "m"_s)
+ ptr = &iv->month;
+ else if (suffix == "j"_s || suffix == "d"_s)
+ ptr = &iv->day;
+ else if (suffix == "h"_s)
+ ptr = &iv->hour;
+ else if (suffix == "mn"_s)
+ ptr = &iv->minute;
+ else if (suffix == "s"_s)
+ ptr = &iv->second;
+ else
+ return false;
+ if (number.startswith('+') && !number.startswith("+-"_s))
+ number = number.xslice_t(1);
+ if (*ptr || !extract(number, ptr))
+ return false;
+ }
+ return true;
+}
} // namespace tmwa
diff --git a/src/mmo/human_time_diff.hpp b/src/mmo/human_time_diff.hpp
index b5c19fb..92b3288 100644
--- a/src/mmo/human_time_diff.hpp
+++ b/src/mmo/human_time_diff.hpp
@@ -24,8 +24,6 @@
#include "../strings/xstring.hpp"
-#include "extract.hpp"
-
namespace tmwa
{
@@ -44,46 +42,5 @@ struct HumanTimeDiff
return !bool(*this);
}
};
-
-inline
-bool extract(XString str, HumanTimeDiff *iv)
-{
- // str is a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s)
- // there are NO spaces here
- // parse by counting the number starts
- auto is_num = [](char c)
- { return c == '-' || c == '+' || ('0' <= c && c <= '9'); };
- if (!str || !is_num(str.front()))
- return false;
- *iv = HumanTimeDiff{};
- while (str)
- {
- auto it = std::find_if_not(str.begin(), str.end(), is_num);
- auto it2 = std::find_if(it, str.end(), is_num);
- XString number = str.xislice_h(it);
- XString suffix = str.xislice(it, it2);
- str = str.xislice_t(it2);
-
- short *ptr = nullptr;
- if (suffix == "y"_s || suffix == "a"_s)
- ptr = &iv->year;
- else if (suffix == "m"_s)
- ptr = &iv->month;
- else if (suffix == "j"_s || suffix == "d"_s)
- ptr = &iv->day;
- else if (suffix == "h"_s)
- ptr = &iv->hour;
- else if (suffix == "mn"_s)
- ptr = &iv->minute;
- else if (suffix == "s"_s)
- ptr = &iv->second;
- else
- return false;
- if (number.startswith('+') && !number.startswith("+-"_s))
- number = number.xslice_t(1);
- if (*ptr || !extract(number, ptr))
- return false;
- }
- return true;
-}
+bool impl_extract(XString str, HumanTimeDiff *iv);
} // namespace tmwa
diff --git a/src/mmo/human_time_diff_test.cpp b/src/mmo/human_time_diff_test.cpp
index c18599d..08a75bf 100644
--- a/src/mmo/human_time_diff_test.cpp
+++ b/src/mmo/human_time_diff_test.cpp
@@ -20,6 +20,8 @@
#include <gtest/gtest.h>
+#include "../io/extract.hpp"
+
#include "../poison.hpp"
diff --git a/src/mmo/ids.cpp b/src/mmo/ids.cpp
index d40d5c3..65c470b 100644
--- a/src/mmo/ids.cpp
+++ b/src/mmo/ids.cpp
@@ -18,9 +18,25 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
+#include "../io/extract.hpp"
+
#include "../poison.hpp"
namespace tmwa
{
+bool impl_extract(XString str, Species *w)
+{
+ // lots of data files use this
+ if (str == "-1"_s)
+ {
+ *w = NEGATIVE_SPECIES;
+ return true;
+ }
+ return extract(str, &w->_value);
+}
+bool impl_extract(XString str, GmLevel *lvl)
+{
+ return extract(str, &lvl->bits);
+}
} // namespace tmwa
diff --git a/src/mmo/ids.hpp b/src/mmo/ids.hpp
index 4e2b97c..28b146a 100644
--- a/src/mmo/ids.hpp
+++ b/src/mmo/ids.hpp
@@ -23,8 +23,6 @@
#include "../ints/little.hpp"
#include "../ints/wrap.hpp"
-#include "extract.hpp"
-
namespace tmwa
{
@@ -32,17 +30,7 @@ class Species : public Wrapped<uint16_t> { public: explicit operator bool() cons
constexpr Species NEGATIVE_SPECIES = Species();
-inline
-bool extract(XString str, Species *w)
-{
- // lots of data files use this
- if (str == "-1"_s)
- {
- *w = NEGATIVE_SPECIES;
- return true;
- }
- return extract(str, &w->_value);
-}
+bool impl_extract(XString str, Species *w);
class AccountId : public Wrapped<uint32_t> { public: constexpr AccountId() : Wrapped<uint32_t>() {} protected: constexpr explicit AccountId(uint32_t a) : Wrapped<uint32_t>(a) {} };
@@ -52,12 +40,14 @@ class PartyId : public Wrapped<uint32_t> { public: constexpr PartyId() : Wrapped
class ItemNameId : public Wrapped<uint16_t> { public: constexpr ItemNameId() : Wrapped<uint16_t>() {} protected: constexpr explicit ItemNameId(uint16_t a) : Wrapped<uint16_t>(a) {} };
class BlockId : public Wrapped<uint32_t> { public: constexpr BlockId() : Wrapped<uint32_t>() {} protected: constexpr explicit BlockId(uint32_t a) : Wrapped<uint32_t>(a) {} };
+class QuestId : public Wrapped<uint16_t> { public: constexpr QuestId() : Wrapped<uint16_t>() {} protected: constexpr explicit QuestId(uint16_t a) : Wrapped<uint16_t>(a) {} };
+bool impl_extract(XString str, GmLevel *lvl);
class GmLevel
{
uint32_t bits;
- friend bool extract(XString str, GmLevel *lvl) { return extract(str, &lvl->bits); }
+ friend bool impl_extract(XString str, GmLevel *lvl);
constexpr explicit
GmLevel(uint32_t b) : bits(b) {}
constexpr explicit
diff --git a/src/mmo/ids.py b/src/mmo/ids.py
index 89392b1..a98920f 100644
--- a/src/mmo/ids.py
+++ b/src/mmo/ids.py
@@ -5,6 +5,7 @@ for s in [
'PartyId',
'ItemNameId',
'BlockId',
+ 'QuestId',
]:
class OtherId(object):
__slots__ = ('_value')
diff --git a/src/mmo/login.t.hpp b/src/mmo/login.t.hpp
new file mode 100644
index 0000000..f2c775a
--- /dev/null
+++ b/src/mmo/login.t.hpp
@@ -0,0 +1,44 @@
+#pragma once
+// login.t.hpp - externally useful types from login
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include <cstdint>
+
+#include "../generic/enum.hpp"
+
+
+namespace tmwa
+{
+namespace e
+{
+enum class VERSION_2 : uint8_t
+{
+ /// client supports updatehost
+ UPDATEHOST = 0x01,
+ /// send servers in forward order
+ SERVERORDER = 0x02,
+};
+ENUM_BITWISE_OPERATORS(VERSION_2)
+}
+using e::VERSION_2;
+} // namespace tmwa
diff --git a/src/mmo/mmo.cpp b/src/mmo/mmo.cpp
deleted file mode 100644
index aafa431..0000000
--- a/src/mmo/mmo.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "mmo.hpp"
-// mmo.cpp - dummy file to make Make dependencies work
-//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/map/skill.t.hpp b/src/mmo/skill.t.hpp
index d0e3926..21e4059 100644
--- a/src/map/skill.t.hpp
+++ b/src/mmo/skill.t.hpp
@@ -77,6 +77,9 @@ enum class SkillID : uint16_t
// TODO: Remove these!
NEGATIVE = 0xffff,
ZERO = 0x0000,
+ // this is probably the remains of the 'basic' skill,
+ // which has since been partially split into emote, trade, and party,
+ // but the confusion is caused by the fact that it also covered attacks.
ONE = 0x0001,
// Basic skills.
diff --git a/src/mmo/strs.cpp b/src/mmo/strs.cpp
index 71dceec..d780702 100644
--- a/src/mmo/strs.cpp
+++ b/src/mmo/strs.cpp
@@ -18,9 +18,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
+#include "../io/cxxstdio.hpp"
+
#include "../poison.hpp"
namespace tmwa
{
+VString<49> convert_for_printf(NpcEvent ev)
+{
+ return STRNPRINTF(50, "%s::%s"_fmt, ev.npc, ev.label);
+}
} // namespace tmwa
diff --git a/src/mmo/strs.hpp b/src/mmo/strs.hpp
index fea0c98..6a132c2 100644
--- a/src/mmo/strs.hpp
+++ b/src/mmo/strs.hpp
@@ -123,4 +123,37 @@ CharName stringish<CharName>(VString<23> iv)
{
return CharName(iv);
}
+
+struct MobName : VString<23> {};
+struct NpcName : VString<23> {};
+struct ScriptLabel : VString<23> {};
+struct ItemName : VString<23> {};
+
+// formerly VString<49>, as name::label
+struct NpcEvent
+{
+ NpcName npc;
+ ScriptLabel label;
+
+ explicit operator bool()
+ {
+ return npc || label;
+ }
+ bool operator !()
+ {
+ return !bool(*this);
+ }
+
+ friend bool operator == (const NpcEvent& l, const NpcEvent& r)
+ {
+ return l.npc == r.npc && l.label == r.label;
+ }
+
+ friend bool operator < (const NpcEvent& l, const NpcEvent& r)
+ {
+ return l.npc < r.npc || (l.npc == r.npc && l.label < r.label);
+ }
+
+ friend VString<49> convert_for_printf(NpcEvent ev);
+};
} // namespace tmwa
diff --git a/src/mmo/utils.hpp b/src/mmo/utils.hpp
deleted file mode 100644
index fc3ea74..0000000
--- a/src/mmo/utils.hpp
+++ /dev/null
@@ -1,167 +0,0 @@
-#pragma once
-// utils.hpp - Useful stuff that hasn't been categorized.
-//
-// Copyright © ????-2004 Athena Dev Teams
-// Copyright © 2004-2011 The Mana World Development Team
-// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-#include <cstring>
-#include <ctime>
-
-#include <type_traits>
-
-#include "../ints/little.hpp"
-
-#include "../strings/fwd.hpp"
-#include "../strings/vstring.hpp"
-
-#include "../generic/operators.hpp"
-
-#include "../io/fwd.hpp"
-
-
-namespace tmwa
-{
-template<class T>
-struct is_trivially_copyable
-: std::integral_constant<bool,
- // come back when GCC actually implements the public traits properly
- __has_trivial_copy(T)
- && __has_trivial_assign(T)
- && __has_trivial_destructor(T)>
-{};
-
-bool e_mail_check(XString email);
-int config_switch (ZString str);
-
-template<class T>
-void really_memzero_this(T *v)
-{
- static_assert(is_trivially_copyable<T>::value, "only for mostly-pod types");
- static_assert(std::is_class<T>::value || std::is_union<T>::value, "Only for user-defined structures (for now)");
- memset(v, '\0', sizeof(*v));
-}
-template<class T, size_t n>
-void really_memzero_this(T (&)[n]) = delete;
-
-// Exists in place of time_t, to give it a predictable printf-format.
-// (on x86 and amd64, time_t == long, but not on x32)
-static_assert(sizeof(long long) >= sizeof(time_t), "long long >= time_t");
-struct TimeT : Comparable
-{
- long long value;
-
- // conversion
- TimeT(time_t t=0) : value(t) {}
- TimeT(struct tm t) : value(timegm(&t)) {}
- operator time_t() const { return value; }
- operator struct tm() const { time_t v = value; return *gmtime(&v); }
-
- explicit operator bool() const { return value; }
- bool operator !() const { return !value; }
-
- // prevent surprises
- template<class T>
- TimeT(T) = delete;
- template<class T>
- operator T() const = delete;
-
- static
- TimeT now()
- {
- // poisoned, but this is still in header-land
- return time(nullptr);
- }
-
- bool error() const
- {
- return value == -1;
- }
- bool okay() const
- {
- return !error();
- }
-};
-
-inline
-long long convert_for_printf(TimeT t)
-{
- return t.value;
-}
-
-// 2038 problem
-inline __attribute__((warn_unused_result))
-bool native_to_network(Little32 *net, TimeT nat)
-{
- time_t tmp = nat;
- return native_to_network(net, static_cast<uint32_t>(tmp));
-}
-
-inline __attribute__((warn_unused_result))
-bool network_to_native(TimeT *nat, Little32 net)
-{
- uint32_t tmp;
- bool rv = network_to_native(&tmp, net);
- *nat = static_cast<time_t>(tmp);
- return rv;
-}
-
-inline __attribute__((warn_unused_result))
-bool native_to_network(Little64 *net, TimeT nat)
-{
- time_t tmp = nat;
- return native_to_network(net, static_cast<uint64_t>(tmp));
-}
-
-inline __attribute__((warn_unused_result))
-bool network_to_native(TimeT *nat, Little64 net)
-{
- uint64_t tmp;
- bool rv = network_to_native(&tmp, net);
- *nat = static_cast<time_t>(tmp);
- return rv;
-}
-
-
-struct timestamp_seconds_buffer : VString<19> {};
-struct timestamp_milliseconds_buffer : VString<23> {};
-void stamp_time(timestamp_seconds_buffer&, const TimeT *t=nullptr);
-void stamp_time(timestamp_milliseconds_buffer&);
-
-void log_with_timestamp(io::WriteFile& out, XString line);
-
-// TODO VString?
-#define TIMESTAMP_DUMMY "YYYY-MM-DD HH:MM:SS"
-static_assert(sizeof(TIMESTAMP_DUMMY) == sizeof(timestamp_seconds_buffer),
- "timestamp size");
-#define WITH_TIMESTAMP(str) str TIMESTAMP_DUMMY
-// str: prefix: YYYY-MM-DD HH:MM:SS
-// sizeof: 01234567890123456789012345678
-// str + sizeof: ^
-// -1: ^
-// there's probably a better way to do this now
-#define REPLACE_TIMESTAMP(str, t) \
- stamp_time( \
- reinterpret_cast<timestamp_seconds_buffer *>( \
- str + sizeof(str) \
- )[-1], \
- &t \
- )
-} // namespace tmwa
diff --git a/src/mmo/version.cpp b/src/mmo/version.cpp
index 2e337c1..f91b748 100644
--- a/src/mmo/version.cpp
+++ b/src/mmo/version.cpp
@@ -24,7 +24,7 @@
#include "../strings/xstring.hpp"
-#include "extract.hpp"
+#include "../io/extract.hpp"
#include "../poison.hpp"
@@ -66,11 +66,36 @@ Version CURRENT_MAP_SERVER_VERSION =
LString CURRENT_VERSION_STRING = VERSION_STRING;
-bool extract(XString str, Version *vers)
+bool impl_extract(XString str, Version *vers)
{
*vers = {};
- // TODO should I try to extract dev and vend also?
- // It would've been useful during the magic migration.
+ // versions look like:
+ // 1.2.3 (release)
+ // 1.2.3+5 (vendor patches)
+ // 1.2.3-4 (dev patches)
+ // 1.2.3-4+5 (dev patches + vendor patches)
+ XString a, b;
+ if (extract(str, record<'+'>(&a, &b)))
+ {
+ if (!extract(b, &vers->vend))
+ {
+ return false;
+ }
+ str = a;
+ }
+ if (extract(str, record<'-'>(&a, &b)))
+ {
+ if (!extract(b, &vers->devel))
+ {
+ return false;
+ }
+ str = a;
+ }
return extract(str, record<'.'>(&vers->major, &vers->minor, &vers->patch));
}
+
+LString VERSION_INFO_HEADER = "This server code consists of Free Software under GPL3&AGPL3"_s;
+LString VERSION_INFO_COMMIT = "This is commit " VERSION_HASH ", also known as " VERSION_FULL ""_s;
+LString VERSION_INFO_NUMBER = "The version is " VERSION_STRING ""_s;
+LString VERSION_INFO_URL = "For source, see [@@" VENDOR_SOURCE "|" VENDOR_SOURCE "@@]"_s;
} // namespace tmwa
diff --git a/src/mmo/version.hpp b/src/mmo/version.hpp
index 440dce6..a09953f 100644
--- a/src/mmo/version.hpp
+++ b/src/mmo/version.hpp
@@ -24,8 +24,6 @@
#include <cstdint>
-#include "../strings/fwd.hpp"
-
namespace tmwa
{
@@ -89,5 +87,10 @@ extern Version CURRENT_MAP_SERVER_VERSION;
extern LString CURRENT_VERSION_STRING;
-bool extract(XString str, Version *vers);
+bool impl_extract(XString str, Version *vers);
+
+extern LString VERSION_INFO_HEADER;
+extern LString VERSION_INFO_COMMIT;
+extern LString VERSION_INFO_NUMBER;
+extern LString VERSION_INFO_URL;
} // namespace tmwa
diff --git a/src/monitor/main.cpp b/src/monitor/main.cpp
deleted file mode 100644
index ec1139a..0000000
--- a/src/monitor/main.cpp
+++ /dev/null
@@ -1,257 +0,0 @@
-// monitor/main.cpp - Old daemon to restart servers when they crashed.
-//
-// Copyright © ???? Bartosz Waszak <waszi@evil.org.pl>
-// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include <sys/wait.h>
-
-#include <fcntl.h>
-#include <unistd.h>
-
-#include <csignal>
-#include <cstdlib>
-
-#include "../strings/mstring.hpp"
-#include "../strings/astring.hpp"
-#include "../strings/zstring.hpp"
-#include "../strings/xstring.hpp"
-#include "../strings/literal.hpp"
-
-#include "../io/cxxstdio.hpp"
-#include "../io/fd.hpp"
-#include "../io/read.hpp"
-
-#include "../mmo/config_parse.hpp"
-#include "../mmo/utils.hpp"
-
-#include "../poison.hpp"
-
-#define LOGIN_SERVER "./login-server"_s
-#define MAP_SERVER "./map-server"_s
-#define CHAR_SERVER "./char-server"_s
-#define CONFIG "conf/eathena-monitor.conf"_s
-
-
-namespace tmwa
-{
-// initialiized to $HOME/tmwserver
-static
-AString workdir;
-//the rest are relative to workdir
-static
-AString login_server = LOGIN_SERVER;
-static
-AString map_server = MAP_SERVER;
-static
-AString char_server = CHAR_SERVER;
-
-static
-pid_t pid_login, pid_map, pid_char;
-
-static
-AString make_path(XString base, XString path)
-{
- MString m;
- m += base;
- m += '/';
- m += path;
- return AString(m);
-}
-
-static
-bool parse_option(XString name, ZString value)
-{
- if (name == "login_server"_s)
- login_server = value;
- else if (name == "map_server"_s)
- map_server = value;
- else if (name == "char_server"_s)
- char_server = value;
- else if (name == "workdir"_s)
- workdir = value;
- else
- {
- FPRINTF(stderr, "WARNING: ingnoring invalid option '%s' : '%s'\n"_fmt,
- AString(name), value);
- return false;
- }
- return true;
-}
-
-static
-bool read_config(ZString filename)
-{
- bool rv = true;
- io::ReadFile in(filename);
- if (!in.is_open())
- {
- FPRINTF(stderr, "Monitor config file not found: %s\n"_fmt, filename);
- exit(1);
- }
-
- AString line;
- while (in.getline(line))
- {
- if (is_comment(line))
- continue;
- XString name;
- ZString value;
- if (!config_split(line, &name, &value))
- {
- PRINTF("Bad line: %s\n"_fmt, line);
- rv = false;
- continue;
- }
-
- if (!parse_option(name, value))
- {
- PRINTF("Bad key/value: %s\n"_fmt, line);
- rv = false;
- continue;
- }
- }
- return rv;
-}
-
-static
-pid_t start_process(ZString exec)
-{
- const char *args[2] = {exec.c_str(), nullptr};
- pid_t pid = fork();
- if (pid == -1)
- {
- FPRINTF(stderr, "Failed to fork"_fmt);
- return 0;
- }
- if (pid == 0)
- {
- DIAG_PUSH();
- DIAG_I(cast_qual);
- execv(exec.c_str(), const_cast<char **>(args));
- DIAG_POP();
- perror("Failed to exec");
- kill(getppid(), SIGABRT);
- exit(1);
- }
- return pid;
-}
-
-// Kill all children with the same signal we got, then ourself.
-static
-void stop_process(int sig)
-{
- if (pid_map)
- kill(pid_map, sig);
- if (pid_login)
- kill(pid_login, sig);
- if (pid_char)
- kill(pid_char, sig);
- DIAG_PUSH();
- DIAG_I(old_style_cast);
- DIAG_I(zero_as_null_pointer_constant);
- signal(sig, SIG_DFL);
- DIAG_POP();
- raise(sig);
-}
-} // namespace tmwa
-
-int main(int argc, char *argv[])
-{
- using namespace tmwa;
- // These are all the signals we are likely to get
- // The shell handles stop/cont
- signal(SIGTERM, stop_process);
- signal(SIGINT, stop_process);
- signal(SIGQUIT, stop_process);
- signal(SIGABRT, stop_process);
-
- workdir = make_path(ZString(strings::really_construct_from_a_pointer, getenv("HOME"), nullptr), "tmwserver"_s);
-
- ZString config = CONFIG;
- if (argc > 1)
- config = ZString(strings::really_construct_from_a_pointer, argv[1], nullptr);
- read_config(config);
-
- if (chdir(workdir.c_str()) < 0)
- {
- perror("Failed to change directory");
- exit(1);
- }
-
- PRINTF("Starting:\n"_fmt);
- PRINTF("* workdir: %s\n"_fmt, workdir);
- PRINTF("* login_server: %s\n"_fmt, login_server);
- PRINTF("* char_server: %s\n"_fmt, char_server);
- PRINTF("* map_server: %s\n"_fmt, map_server);
- {
- //make sure all possible file descriptors are free for use by the servers
- //if there are file descriptors higher than the max open from before the limit dropped, that's not our problem
- io::FD fd = io::FD::sysconf_SC_OPEN_MAX();
- while ((fd = fd.prev()) > io::FD::stderr())
- {
- if (fd.close() == 0)
- FPRINTF(stderr, "close fd %d\n"_fmt, fd.uncast_dammit());
- }
- fd = io::FD::open("/dev/null"_s, O_RDWR);
- if (fd == io::FD())
- {
- perror("open /dev/null");
- exit(1);
- }
- fd.dup2(io::FD::stdin());
- fd.dup2(io::FD::stdout());
- fd.close();
- }
- while (1)
- {
- // write stuff to stderr
- timestamp_seconds_buffer timestamp;
- stamp_time(timestamp);
-
- if (!pid_login)
- {
- pid_login = start_process(login_server);
- FPRINTF(stderr, "[%s] forked login server: %lu\n"_fmt,
- timestamp, static_cast<unsigned long>(pid_login));
- }
- if (!pid_char)
- {
- pid_char = start_process(char_server);
- FPRINTF(stderr, "[%s] forked char server: %lu\n"_fmt,
- timestamp, static_cast<unsigned long>(pid_char));
- }
- if (!pid_map)
- {
- pid_map = start_process(map_server);
- FPRINTF(stderr, "[%s] forked map server: %lu\n"_fmt,
- timestamp, static_cast<unsigned long>(pid_map));
- }
- pid_t dead = wait(nullptr);
- if (dead == -1)
- {
- perror("Failed to wait for child");
- exit(1);
- }
- if (pid_login == dead)
- pid_login = 0;
- if (pid_char == dead)
- pid_char = 0;
- if (pid_map == dead)
- pid_map = 0;
- }
-}
diff --git a/src/net/fwd.hpp b/src/net/fwd.hpp
index 2097772..5de8450 100644
--- a/src/net/fwd.hpp
+++ b/src/net/fwd.hpp
@@ -20,6 +20,13 @@
#include "../sanity.hpp"
+#include "../ints/fwd.hpp" // rank 1
+#include "../strings/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp" // rank 2
+#include "../generic/fwd.hpp" // rank 3
+#include "../io/fwd.hpp" // rank 4
+// net/fwd.hpp is rank 5
+
namespace tmwa
{
@@ -28,6 +35,4 @@ class Session;
class IP4Address;
class TimerData;
-
-enum class RecvResult;
} // namespace tmwa
diff --git a/src/net/ip.cpp b/src/net/ip.cpp
index bfc2028..bedbca8 100644
--- a/src/net/ip.cpp
+++ b/src/net/ip.cpp
@@ -22,15 +22,14 @@
#include "../strings/vstring.hpp"
#include "../io/cxxstdio.hpp"
-
-#include "../mmo/extract.hpp"
+#include "../io/extract.hpp"
#include "../poison.hpp"
namespace tmwa
{
-bool extract(XString str, IP4Address *rv)
+bool impl_extract(XString str, IP4Address *rv)
{
if (str.endswith('.'))
return false;
@@ -43,7 +42,7 @@ bool extract(XString str, IP4Address *rv)
return false;
}
-bool extract(XString str, IP4Mask *rv)
+bool impl_extract(XString str, IP4Mask *rv)
{
IP4Address a, m;
unsigned b;
@@ -106,6 +105,33 @@ bool extract(XString str, IP4Mask *rv)
return true;
}
+bool impl_extract(XString str, std::vector<IP4Mask> *iv)
+{
+ if (str == "all"_s)
+ {
+ iv->clear();
+ iv->push_back(IP4Mask());
+ return true;
+ }
+ if (str == "clear"_s)
+ {
+ iv->clear();
+ return true;
+ }
+ // don't add if already 'all'
+ if (iv->size() == 1 && iv->front().mask() == IP4Address())
+ {
+ return true;
+ }
+ IP4Mask mask;
+ if (extract(str, &mask))
+ {
+ iv->push_back(mask);
+ return true;
+ }
+ return false;
+}
+
VString<15> convert_for_printf(IP4Address a_)
{
const uint8_t *a = a_.bytes();
diff --git a/src/net/ip.hpp b/src/net/ip.hpp
index e9e71f4..7508c08 100644
--- a/src/net/ip.hpp
+++ b/src/net/ip.hpp
@@ -25,7 +25,7 @@
#include <cstddef>
#include <cstdint>
-#include "../strings/fwd.hpp"
+#include <vector>
namespace tmwa
@@ -160,7 +160,7 @@ IP4Address IP4_BROADCAST({255, 255, 255, 255});
VString<15> convert_for_printf(IP4Address a);
VString<31> convert_for_printf(IP4Mask m);
-bool extract(XString str, IP4Address *iv);
-
-bool extract(XString str, IP4Mask *iv);
+bool impl_extract(XString str, IP4Address *iv);
+bool impl_extract(XString str, IP4Mask *iv);
+bool impl_extract(XString str, std::vector<IP4Mask> *iv);
} // namespace tmwa
diff --git a/src/net/ip_test.cpp b/src/net/ip_test.cpp
index 419dc03..2b9bcad 100644
--- a/src/net/ip_test.cpp
+++ b/src/net/ip_test.cpp
@@ -24,6 +24,7 @@
#include "../strings/literal.hpp"
#include "../io/cxxstdio.hpp"
+#include "../io/extract.hpp"
#include "../poison.hpp"
diff --git a/src/net/socket.cpp b/src/net/socket.cpp
index a01cd81..fce45fb 100644
--- a/src/net/socket.cpp
+++ b/src/net/socket.cpp
@@ -34,10 +34,6 @@
#include "../io/cxxstdio.hpp"
-// TODO get rid of ordering violations
-#include "../mmo/utils.hpp"
-#include "../mmo/core.hpp"
-
#include "timer.hpp"
#include "../poison.hpp"
@@ -399,7 +395,7 @@ void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size)
}
}
-void do_sendrecv(interval_t next_ms)
+bool do_sendrecv(interval_t next_ms)
{
bool any = false;
io::FD_Set rfd = readfds, wfd;
@@ -419,9 +415,9 @@ void do_sendrecv(interval_t next_ms)
{
PRINTF("Shutting down - nothing to do\n"_fmt);
// TODO hoist this
- runflag = false;
+ return false;
}
- return;
+ return true;
}
struct timeval timeout;
{
@@ -431,7 +427,7 @@ void do_sendrecv(interval_t next_ms)
timeout.tv_usec = next_us.count();
}
if (io::FD_Set::select(fd_max, &rfd, &wfd, nullptr, &timeout) <= 0)
- return;
+ return true;
for (io::FD i : iter_fds())
{
Session *s = get_session(i);
@@ -451,9 +447,10 @@ void do_sendrecv(interval_t next_ms)
s->func_recv(s);
}
}
+ return true;
}
-void do_parsepacket(void)
+bool do_parsepacket(void)
{
for (io::FD i : iter_fds())
{
@@ -483,5 +480,6 @@ void do_parsepacket(void)
/// Reclaim buffer space for what was read
RFIFOFLUSH(s);
}
+ return true;
}
} // namespace tmwa
diff --git a/src/net/socket.hpp b/src/net/socket.hpp
index 576ef85..d6caefd 100644
--- a/src/net/socket.hpp
+++ b/src/net/socket.hpp
@@ -22,20 +22,19 @@
#include "fwd.hpp"
-#include <algorithm>
-
#include <sys/select.h>
+#include <algorithm>
#include <memory>
-#include "../compat/iter.hpp"
-#include "../compat/rawmem.hpp"
-#include "../compat/time_t.hpp"
-
#include "../strings/astring.hpp"
#include "../strings/vstring.hpp"
#include "../strings/xstring.hpp"
+#include "../compat/iter.hpp"
+#include "../compat/rawmem.hpp"
+#include "../compat/time_t.hpp"
+
#include "../generic/dumb_ptr.hpp"
#include "../io/fd.hpp"
@@ -125,8 +124,8 @@ public:
io::FD fd;
- friend void do_sendrecv(interval_t next);
- friend void do_parsepacket(void);
+ friend bool do_sendrecv(interval_t next);
+ friend bool do_parsepacket(void);
friend void delete_session(Session *);
};
@@ -171,7 +170,7 @@ void delete_session(Session *);
/// Make a the internal queues bigger
void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size);
/// Update all sockets that can be read/written from the queues
-void do_sendrecv(interval_t next);
+bool do_sendrecv(interval_t next);
/// Call the parser function for every socket that has read data
-void do_parsepacket(void);
+bool do_parsepacket(void);
} // namespace tmwa
diff --git a/src/net/timer.hpp b/src/net/timer.hpp
index 338e339..5e7cc90 100644
--- a/src/net/timer.hpp
+++ b/src/net/timer.hpp
@@ -21,11 +21,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "timer.t.hpp"
-
#include "fwd.hpp"
-#include "../strings/fwd.hpp"
-
namespace tmwa
{
diff --git a/src/net/timer.py b/src/net/timer.py
new file mode 100644
index 0000000..2ccb3bb
--- /dev/null
+++ b/src/net/timer.py
@@ -0,0 +1,117 @@
+class duration(object):
+ __slots__ = ('_whole', '_frac', '_units')
+ name = 'std::chrono::duration'
+ enabled = True
+
+ def __init__(self, value):
+ from decimal import Decimal
+
+ rep = int(value['__r'])
+ ratio = value.type.template_argument(1)
+ num = int(ratio.template_argument(0))
+ den = int(ratio.template_argument(1))
+ # this will fail on duration<float>
+ value = Decimal(rep) * num / den
+ whole = int(value)
+ self._whole = whole
+ self._frac = value - whole
+ units = {
+ (1, 1000*1000*1000): ('nanoseconds', '_ns'),
+ (1, 1000*1000): ('microseconds', '_us'),
+ (1, 1000): ('milliseconds', '_ms'),
+ (1, 1): ('seconds', '_s'),
+ (60, 1): ('minutes', '_min'),
+ (60*60, 1): ('hours', '_h'),
+ (24*60*60, 1): ('duration<int, ratio<%d, %d>>', '_d'),
+ # days don't exist (probably because of leap seconds)
+ }
+ self._units = units.get((num, den)) or ('duration<???, ratio<%d, %d>>' % (num, den), '_?')
+
+ def to_string(self):
+ whole = self._whole
+ frac = self._frac
+ cu, su = self._units
+ if not whole and not frac:
+ return '0%s' % su
+ s = whole
+ min = s // 60
+ s %= 60
+ h = min // 60
+ min %= 60
+ d = h // 24
+ h %= 24
+ msx = frac * 1000
+ ms = int(msx)
+ usx = (msx - ms) * 1000
+ us = int(usx)
+ nsx = (usx - us) * 1000
+ ns = int(nsx)
+ bits = [
+ '%d_d' % d if d else None,
+ '%d_h' % h if h else None,
+ '%d_min' % min if min else None,
+ '%d_s' % s if s else None,
+ '%d_ms' % ms if ms else None,
+ '%d_us' % us if us else None,
+ '%d_ns' % ns if ns else None,
+ ]
+ body = ' + '.join(b for b in bits if b is not None)
+ if not body.endswith(su):
+ body = '%s(%s)' % (cu, body)
+ elif '+' in body:
+ body = '(%s)' % body
+ return body
+
+ tests = [
+ ('std::chrono::nanoseconds(0)', '0_ns'),
+ ('std::chrono::microseconds(0)', '0_us'),
+ ('std::chrono::milliseconds(0)', '0_ms'),
+ ('std::chrono::seconds(0)', '0_s'),
+ ('std::chrono::minutes(0)', '0_min'),
+ ('std::chrono::hours(0)', '0_h'),
+ ('std::chrono::duration<int, std::ratio<60*60*24>>(0)', '0_d'),
+
+ ('std::chrono::nanoseconds(1)', '1_ns'),
+ ('std::chrono::microseconds(1)', '1_us'),
+ ('std::chrono::milliseconds(1)', '1_ms'),
+ ('std::chrono::seconds(1)', '1_s'),
+ ('std::chrono::minutes(1)', '1_min'),
+ ('std::chrono::hours(1)', '1_h'),
+ ('std::chrono::duration<int, std::ratio<60*60*24>>(1)', '1_d'),
+
+ ('std::chrono::nanoseconds(1)', '1_ns'),
+ ('std::chrono::microseconds(1)', '1_us'),
+ ('std::chrono::milliseconds(1)', '1_ms'),
+ ('std::chrono::seconds(1)', '1_s'),
+ ('std::chrono::minutes(1)', '1_min'),
+ ('std::chrono::hours(1)', '1_h'),
+ ('std::chrono::duration<int, std::ratio<60*60*24>>(1)', '1_d'),
+
+ ('std::chrono::nanoseconds(3)', '3_ns'),
+ ('std::chrono::microseconds(3)', '3_us'),
+ ('std::chrono::milliseconds(3)', '3_ms'),
+ ('std::chrono::seconds(3)', '3_s'),
+ ('std::chrono::minutes(3)', '3_min'),
+ ('std::chrono::hours(3)', '3_h'),
+ ('std::chrono::duration<int, std::ratio<60*60*24>>(3)', '3_d'),
+
+ ('std::chrono::nanoseconds(1000)', 'nanoseconds(1_us)'),
+ ('std::chrono::microseconds(1000)', 'microseconds(1_ms)'),
+ ('std::chrono::milliseconds(1000)', 'milliseconds(1_s)'),
+ ('std::chrono::seconds(60)', 'seconds(1_min)'),
+ ('std::chrono::minutes(60)', 'minutes(1_h)'),
+ ('std::chrono::hours(24)', 'hours(1_d)'),
+
+ ('std::chrono::nanoseconds(1001)', '(1_us + 1_ns)'),
+ ('std::chrono::microseconds(1001)', '(1_ms + 1_us)'),
+ ('std::chrono::milliseconds(1001)', '(1_s + 1_ms)'),
+ ('std::chrono::seconds(61)', '(1_min + 1_s)'),
+ ('std::chrono::minutes(61)', '(1_h + 1_min)'),
+ ('std::chrono::hours(25)', '(1_d + 1_h)'),
+
+ ('std::chrono::nanoseconds(1001*1000)', 'nanoseconds(1_ms + 1_us)'),
+ ('std::chrono::microseconds(1001*1000)', 'microseconds(1_s + 1_ms)'),
+ ('std::chrono::milliseconds(61*1000)', 'milliseconds(1_min + 1_s)'),
+ ('std::chrono::seconds(61*60)', 'seconds(1_h + 1_min)'),
+ ('std::chrono::minutes(25*60)', 'minutes(1_d + 1_h)'),
+ ]
diff --git a/src/net/timestamp-utils.cpp b/src/net/timestamp-utils.cpp
new file mode 100644
index 0000000..b5873ca
--- /dev/null
+++ b/src/net/timestamp-utils.cpp
@@ -0,0 +1,72 @@
+#include "timestamp-utils.hpp"
+// timestamp-utils.cpp - Useful stuff that hasn't been categorized.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include <sys/time.h>
+
+#include <algorithm>
+
+#include "../strings/xstring.hpp"
+
+#include "../compat/time_t.hpp"
+
+#include "../io/write.hpp"
+
+#include "../poison.hpp"
+
+
+namespace tmwa
+{
+static_assert(sizeof(timestamp_seconds_buffer) == 20, "seconds buffer");
+static_assert(sizeof(timestamp_milliseconds_buffer) == 24, "millis buffer");
+
+void stamp_time(timestamp_seconds_buffer& out, const TimeT *t)
+{
+ struct tm when = t ? *t : TimeT::now();
+ char buf[20];
+ strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when);
+ out = stringish<timestamp_seconds_buffer>(VString<19>(strings::really_construct_from_a_pointer, buf));
+}
+void stamp_time(timestamp_milliseconds_buffer& out)
+{
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ struct tm when = TimeT(tv.tv_sec);
+ char buf[24];
+ strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when);
+ sprintf(buf + 19, ".%03d", static_cast<int>(tv.tv_usec / 1000));
+ out = stringish<timestamp_milliseconds_buffer>(VString<23>(strings::really_construct_from_a_pointer, buf));
+}
+
+void log_with_timestamp(io::WriteFile& out, XString line)
+{
+ if (!line)
+ {
+ out.put('\n');
+ return;
+ }
+ timestamp_milliseconds_buffer tmpstr;
+ stamp_time(tmpstr);
+ out.really_put(tmpstr.data(), tmpstr.size());
+ out.really_put(": ", 2);
+ out.put_line(line);
+}
+} // namespace tmwa
diff --git a/src/net/timestamp-utils.hpp b/src/net/timestamp-utils.hpp
new file mode 100644
index 0000000..f5b5dce
--- /dev/null
+++ b/src/net/timestamp-utils.hpp
@@ -0,0 +1,54 @@
+#pragma once
+// timestamp-utils.hpp - Useful stuff that hasn't been categorized.
+//
+// Copyright © ????-2004 Athena Dev Teams
+// Copyright © 2004-2011 The Mana World Development Team
+// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../strings/vstring.hpp"
+
+
+namespace tmwa
+{
+struct timestamp_seconds_buffer : VString<19> {};
+struct timestamp_milliseconds_buffer : VString<23> {};
+void stamp_time(timestamp_seconds_buffer&, const TimeT *t=nullptr);
+void stamp_time(timestamp_milliseconds_buffer&);
+
+void log_with_timestamp(io::WriteFile& out, XString line);
+
+// TODO VString?
+#define TIMESTAMP_DUMMY "YYYY-MM-DD HH:MM:SS"
+static_assert(sizeof(TIMESTAMP_DUMMY) == sizeof(timestamp_seconds_buffer),
+ "timestamp size");
+#define WITH_TIMESTAMP(str) str TIMESTAMP_DUMMY
+// str: prefix: YYYY-MM-DD HH:MM:SS
+// sizeof: 01234567890123456789012345678
+// str + sizeof: ^
+// -1: ^
+// there's probably a better way to do this now
+#define REPLACE_TIMESTAMP(str, t) \
+ stamp_time( \
+ reinterpret_cast<timestamp_seconds_buffer *>( \
+ str + sizeof(str) \
+ )[-1], \
+ &t \
+ )
+} // namespace tmwa
diff --git a/src/generic/array.cpp b/src/proto-base/fwd.hpp
index 3063569..1790717 100644
--- a/src/generic/array.cpp
+++ b/src/proto-base/fwd.hpp
@@ -1,5 +1,5 @@
-#include "array.hpp"
-// array.cpp - A simple bounds-checked array.
+#pragma once
+// proto-base/fwd.hpp - list of type names for network protocol base
//
// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
@@ -18,9 +18,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "../sanity.hpp"
+
+#include "../strings/fwd.hpp" // rank 1
+#include "../generic/fwd.hpp" // rank 3
+#include "../mmo/fwd.hpp" // rank 6
+// proto-base/fwd.hpp is rank 7
namespace tmwa
{
+// meh, add more when I feel like it
} // namespace tmwa
diff --git a/src/proto-base/net-array.hpp b/src/proto-base/net-array.hpp
new file mode 100644
index 0000000..814d257
--- /dev/null
+++ b/src/proto-base/net-array.hpp
@@ -0,0 +1,53 @@
+#pragma once
+// proto2/net-array.hpp - Special logic for fixed-size arrays in packets.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../generic/array.hpp"
+
+
+namespace tmwa
+{
+template<class T, size_t N>
+struct NetArray
+{
+ T data[N];
+};
+template<class T, class U, class I>
+bool native_to_network(NetArray<T, I::alloc_size> *network, GenericArray<U, I> native)
+{
+ for (size_t i = 0; i < I::alloc_size; ++i)
+ {
+ if (!native_to_network(&(*network).data[i], native[I::offset_to_index(i)]))
+ return false;
+ }
+ return true;
+}
+template<class T, class U, class I>
+bool network_to_native(GenericArray<U, I> *native, NetArray<T, I::alloc_size> network)
+{
+ for (size_t i = 0; i < I::alloc_size; ++i)
+ {
+ if (!network_to_native(&(*native)[I::offset_to_index(i)], network.data[i]))
+ return false;
+ }
+ return true;
+}
+} // namespace tmwa
diff --git a/src/proto-base/net-neutral.hpp b/src/proto-base/net-neutral.hpp
new file mode 100644
index 0000000..523748c
--- /dev/null
+++ b/src/proto-base/net-neutral.hpp
@@ -0,0 +1,38 @@
+#pragma once
+// proto2/net-neutral.hpp - Convert nothing across the network
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+
+namespace tmwa
+{
+template<class T>
+bool native_to_network(T *network, T native)
+{
+ *network = native;
+ return true;
+}
+template<class T>
+bool network_to_native(T *native, T network)
+{
+ *native = network;
+ return true;
+}
+} // namespace tmwa
diff --git a/src/proto-base/net-skewed-length.hpp b/src/proto-base/net-skewed-length.hpp
new file mode 100644
index 0000000..84af508
--- /dev/null
+++ b/src/proto-base/net-skewed-length.hpp
@@ -0,0 +1,46 @@
+#pragma once
+// proto2/net-skewed-length.hpp - Deprecated logic for skewed-size packets.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include <cstddef>
+
+
+namespace tmwa
+{
+template<class T, size_t N>
+struct SkewedLength
+{
+ T data;
+};
+template<class T, size_t N, class U>
+bool native_to_network(SkewedLength<T, N> *network, U native)
+{
+ native -= N;
+ return native_to_network(&network->data, native);
+}
+template<class T, size_t N, class U>
+bool network_to_native(U *native, SkewedLength<T, N> network)
+{
+ bool rv = network_to_native(native, network.data);
+ *native += N;
+ return rv;
+}
+} // namespace tmwa
diff --git a/src/proto-base/net-string.hpp b/src/proto-base/net-string.hpp
new file mode 100644
index 0000000..a9a120d
--- /dev/null
+++ b/src/proto-base/net-string.hpp
@@ -0,0 +1,87 @@
+#pragma once
+// proto2/net-string.hpp - Special logic for fixed-size strings in packets.
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#include "fwd.hpp"
+
+#include "../strings/vstring.hpp"
+
+#include "../mmo/strs.hpp"
+
+
+namespace tmwa
+{
+template<size_t N>
+struct NetString
+{
+ char data[N];
+};
+template<size_t N>
+bool native_to_network(NetString<N> *network, VString<N-1> native)
+{
+ // basically WBUF_STRING
+ char *const begin = network->data;
+ char *const end = begin + N;
+ char *const mid = std::copy(native.begin(), native.end(), begin);
+ std::fill(mid, end, '\0');
+ return true;
+}
+template<size_t N>
+bool network_to_native(VString<N-1> *native, NetString<N> network)
+{
+ // basically RBUF_STRING
+ const char *const begin = network.data;
+ const char *const end = begin + N;
+ const char *const mid = std::find(begin, end, '\0');
+ *native = XString(begin, mid, nullptr);
+ return true;
+}
+
+inline
+bool native_to_network(NetString<24> *network, CharName native)
+{
+ VString<23> tmp = native.to__actual();
+ bool rv = native_to_network(network, tmp);
+ return rv;
+}
+inline
+bool network_to_native(CharName *native, NetString<24> network)
+{
+ VString<23> tmp;
+ bool rv = network_to_native(&tmp, network);
+ *native = stringish<CharName>(tmp);
+ return rv;
+}
+
+inline
+bool native_to_network(NetString<16> *network, MapName native)
+{
+ XString tmp = native;
+ bool rv = native_to_network(network, VString<15>(tmp));
+ return rv;
+}
+inline
+bool network_to_native(MapName *native, NetString<16> network)
+{
+ VString<15> tmp;
+ bool rv = network_to_native(&tmp, network);
+ *native = stringish<MapName>(tmp);
+ return rv;
+}
+} // namespace tmwa
diff --git a/src/range/fwd.hpp b/src/range/fwd.hpp
index 646eadb..4bad327 100644
--- a/src/range/fwd.hpp
+++ b/src/range/fwd.hpp
@@ -20,6 +20,8 @@
#include "../sanity.hpp"
+// range/fwd.hpp is rank 1
+
namespace tmwa
{
diff --git a/src/range/slice.cpp b/src/range/slice.cpp
deleted file mode 100644
index f93c19f..0000000
--- a/src/range/slice.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "slice.hpp"
-// slice.cpp - dummy file to make Make dependencies work
-//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/sanity.hpp b/src/sanity.hpp
index c00d9b2..455990a 100644
--- a/src/sanity.hpp
+++ b/src/sanity.hpp
@@ -41,10 +41,10 @@ namespace tmwa
#endif // __GNUC__ == 4
#if not defined(__i386__) and not defined(__x86_64__)
-// Known platform dependencies:
-// endianness for the [RW]FIFO.* macros
-// possibly, some signal-handling
-// some integer sizes (partially fixed for the x32 ABI)
-# error "Unsupported platform, we use x86 / amd64 only"
+// There are no longer any *known* platform-independent bits,
+// but nothing has been tested yet. It compiles, though, so ship it!
+# ifndef QUIET
+# warning "Unsupported platform, upstream uses x86 / amd64 only"
+# endif
#endif // not __i386__
} // namespace tmwa
diff --git a/src/sexpr/fwd.hpp b/src/sexpr/fwd.hpp
index 580b322..b86d9fb 100644
--- a/src/sexpr/fwd.hpp
+++ b/src/sexpr/fwd.hpp
@@ -20,6 +20,10 @@
#include "../sanity.hpp"
+#include "../strings/fwd.hpp" // rank 1
+#include "../io/fwd.hpp" // rank 4
+// sexpr/fwd.hpp is rank 5
+
namespace tmwa
{
diff --git a/src/sexpr/lexer.hpp b/src/sexpr/lexer.hpp
index 63be72d..9b198a0 100644
--- a/src/sexpr/lexer.hpp
+++ b/src/sexpr/lexer.hpp
@@ -22,7 +22,6 @@
#include <vector>
-#include "../strings/fwd.hpp"
#include "../strings/astring.hpp"
#include "../strings/zstring.hpp"
@@ -59,10 +58,13 @@ namespace sexpr
Lexer(ZString filename)
: _in(filename), _current(TOK_EOF), _span(), _depth()
{ adv(); }
- // for unit tests
- Lexer(ZString fake, io::FD fd)
- : _in(fake, fd), _current(TOK_EOF), _span(), _depth()
+ Lexer(io::read_file_from_string, ZString name, XString str)
+ : _in(io::from_string, name, str), _current(TOK_EOF), _span(), _depth()
{ adv(); }
+ Lexer(io::read_file_from_string, ZString name, LString str)
+ : _in(io::from_string, name, str), _current(TOK_EOF), _span(), _depth()
+ { adv(); }
+
Lexeme peek() { return _current; }
void adv() { _current = _adv(); }
ZString val_string() { return _string; }
diff --git a/src/sexpr/lexer_test.cpp b/src/sexpr/lexer_test.cpp
index fdb47f2..d84312e 100644
--- a/src/sexpr/lexer_test.cpp
+++ b/src/sexpr/lexer_test.cpp
@@ -29,22 +29,6 @@
namespace tmwa
{
-static
-io::FD string_pipe(ZString sz)
-{
- io::FD rfd, wfd;
- if (-1 == io::FD::pipe(rfd, wfd))
- return io::FD();
- if (sz.size() != wfd.write(sz.c_str(), sz.size()))
- {
- rfd.close();
- wfd.close();
- return io::FD();
- }
- wfd.close();
- return rfd;
-}
-
TEST(sexpr, escape)
{
EXPECT_EQ(sexpr::escape('\0'), "\\x00"_s);
@@ -73,24 +57,24 @@ TEST(sexpr, escape)
TEST(sexpr, lexer)
{
io::LineSpan span;
- sexpr::Lexer lexer("<lexer-test1>"_s, string_pipe(" foo( ) 123\"\" \n"_s));
+ sexpr::Lexer lexer(io::from_string, "<lexer-test1>"_s, " foo( ) 123\"\" \n"_s);
EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN);
EXPECT_EQ(lexer.val_string(), "foo"_s);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:2: error: test\n"
" foo( ) 123\"\" \n"
" ^~~\n"_s
);
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:5: error: test\n"
" foo( ) 123\"\" \n"
" ^\n"_s
);
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_CLOSE);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:7: error: test\n"
" foo( ) 123\"\" \n"
" ^\n"_s
@@ -98,7 +82,7 @@ TEST(sexpr, lexer)
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN);
EXPECT_EQ(lexer.val_string(), "123"_s);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:9: error: test\n"
" foo( ) 123\"\" \n"
" ^~~\n"_s
@@ -106,7 +90,7 @@ TEST(sexpr, lexer)
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_STRING);
EXPECT_EQ(lexer.val_string(), ""_s);
- EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s),
+ EXPECT_EQ(lexer.span().error_str("test"_s),
"<lexer-test1>:1:12: error: test\n"
" foo( ) 123\"\" \n"
" ^~\n"_s
@@ -120,7 +104,7 @@ TEST(sexpr, lexbad)
QuietFd q;
{
io::LineSpan span;
- sexpr::Lexer lexer("<lexer-bad>"_s, string_pipe("(\n"_s));
+ sexpr::Lexer lexer(io::from_string, "<lexer-bad>"_s, "(\n"_s);
EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN);
lexer.adv();
EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR);
@@ -135,7 +119,7 @@ TEST(sexpr, lexbad)
})
{
io::LineSpan span;
- sexpr::Lexer lexer("<lexer-bad>"_s, string_pipe(bad));
+ sexpr::Lexer lexer(io::from_string, "<lexer-bad>"_s, bad);
EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR);
}
}
diff --git a/src/sexpr/parser.hpp b/src/sexpr/parser.hpp
index feed636..cfe8212 100644
--- a/src/sexpr/parser.hpp
+++ b/src/sexpr/parser.hpp
@@ -22,8 +22,6 @@
#include <cstdlib>
-#include "../strings/fwd.hpp"
-
#include "../io/line.hpp"
#include "lexer.hpp"
diff --git a/src/sexpr/parser_test.cpp b/src/sexpr/parser_test.cpp
index 846d425..bbaf5eb 100644
--- a/src/sexpr/parser_test.cpp
+++ b/src/sexpr/parser_test.cpp
@@ -20,32 +20,18 @@
#include <gtest/gtest.h>
+#include "../tests/fdhack.hpp"
+
#include "../poison.hpp"
namespace tmwa
{
-static
-io::FD string_pipe(ZString sz)
-{
- io::FD rfd, wfd;
- if (-1 == io::FD::pipe(rfd, wfd))
- return io::FD();
- if (sz.size() != wfd.write(sz.c_str(), sz.size()))
- {
- rfd.close();
- wfd.close();
- return io::FD();
- }
- wfd.close();
- return rfd;
-}
-
TEST(sexpr, parser)
{
sexpr::SExpr s;
io::LineSpan span;
- sexpr::Lexer lexer("<parser-test1>"_s, string_pipe(" foo( ) 123\"\" \n"_s));
+ sexpr::Lexer lexer(io::from_string, "<parser-test1>"_s, " foo( ) 123\"\" \n"_s);
EXPECT_TRUE(sexpr::parse(lexer, s));
EXPECT_EQ(s._type, sexpr::TOKEN);
@@ -70,7 +56,7 @@ TEST(sexpr, parser)
TEST(sexpr, parselist)
{
sexpr::SExpr s;
- sexpr::Lexer lexer("<parser-test1>"_s, string_pipe("(foo)(bar)\n"_s));
+ sexpr::Lexer lexer(io::from_string, "<parser-test1>"_s, "(foo)(bar)\n"_s);
EXPECT_TRUE(sexpr::parse(lexer, s));
EXPECT_EQ(s._type, sexpr::LIST);
@@ -90,6 +76,7 @@ TEST(sexpr, parselist)
TEST(sexpr, parsebad)
{
+ QuietFd q;
for (LString bad : {
"(\n"_s,
")\n"_s,
@@ -105,7 +92,7 @@ TEST(sexpr, parsebad)
{
sexpr::SExpr s;
io::LineSpan span;
- sexpr::Lexer lexer("<parse-bad>"_s, string_pipe(bad));
+ sexpr::Lexer lexer(io::from_string, "<parse-bad>"_s, bad);
EXPECT_FALSE(sexpr::parse(lexer, s));
EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR);
}
diff --git a/src/sexpr/union.cpp b/src/sexpr/union_test.cpp
index 6f65012..ca60b49 100644
--- a/src/sexpr/union.cpp
+++ b/src/sexpr/union_test.cpp
@@ -1,5 +1,5 @@
#include "union.hpp"
-// union.cpp - Just include the header file and try to instantiate.
+// union_test.cpp - Just include the header file and try to instantiate.
//
// Copyright © 2012 Ben Longbons <b.r.longbons@gmail.com>
//
diff --git a/src/sexpr/variant.hpp b/src/sexpr/variant.hpp
index fbf9345..0eccc5a 100644
--- a/src/sexpr/variant.hpp
+++ b/src/sexpr/variant.hpp
@@ -33,19 +33,42 @@ namespace tmwa
{
namespace sexpr
{
-#define JOIN(a, b) a##b
-
-#define WITH_VAR(ty, var, expr) \
- for (bool JOIN(var, _guard) = true; JOIN(var, _guard); ) \
- for (ty var = expr; JOIN(var, _guard); JOIN(var, _guard) = false)
-#define MATCH(expr) \
- WITH_VAR(auto&&, _match_var, expr) \
- switch (tmwa::sexpr::VariantFriend::get_state(_match_var))
-#define TYPED_CASE(ty, var, look) \
- break; \
- case tmwa::sexpr::VariantFriend::get_state_for<look, decltype(_match_var)>(): \
- WITH_VAR(ty, var, tmwa::sexpr::VariantFriend::unchecked_get<look>(_match_var))
-#define CASE(ty, var) TYPED_CASE(ty, var, std::remove_const<std::remove_reference<ty>::type>::type)
+#define MATCH_BEGIN(expr) \
+ { \
+ auto&& _match_var = (expr); \
+ switch (tmwa::sexpr::VariantFriend::get_state(_match_var)) \
+ { \
+ { \
+ { \
+ /* }}}} */
+#define MATCH_END() \
+ /* {{{{ */ \
+ } \
+ } \
+ } \
+ (void) _match_var; \
+ }
+
+#define MATCH_CASE(ty, v) \
+ /* {{{{ */ \
+ } \
+ break; \
+ } \
+ { \
+ using _match_case_type = std::remove_const<std::remove_reference<ty>::type>::type; \
+ case tmwa::sexpr::VariantFriend::get_state_for<_match_case_type, decltype(_match_var)>(): \
+ { \
+ ty v = tmwa::sexpr::VariantFriend::unchecked_get<_match_case_type>(_match_var);
+ /* }}}} */
+#define MATCH_DEFAULT() \
+ /* {{{{ */ \
+ } \
+ break; \
+ } \
+ { \
+ default: \
+ { \
+ /* }}}} */
template<class... T>
class Variant
diff --git a/src/sexpr/variant.tcc b/src/sexpr/variant.tcc
index 1f7df03..c370fa4 100644
--- a/src/sexpr/variant.tcc
+++ b/src/sexpr/variant.tcc
@@ -120,6 +120,9 @@ namespace sexpr
}
catch (...)
{
+ // TODO switch from requiring nothrow default construct, to
+ // instead require nothrow moves, and offer the strong exception
+ // guarantee (which is actually easier that the basic one)
#if GCC != 407 // apparent compiler bug, not reduced
// 4.7.2 from wheezy is bad
// 4.7.3 from jessie is good
diff --git a/src/sexpr/variant_test.cpp b/src/sexpr/variant_test.cpp
index bc378aa..c671264 100644
--- a/src/sexpr/variant_test.cpp
+++ b/src/sexpr/variant_test.cpp
@@ -77,42 +77,46 @@ TEST(variant, match)
: sexpr::Variant<Foo, Bar>(Foo(1))
{}
};
+
Sub v1;
- MATCH (v1)
+ MATCH_BEGIN (v1)
{
- // This is not a public API, it's just for testing.
- default:
- FAIL();
-
- CASE(Foo, f)
+ MATCH_DEFAULT ()
+ {
+ FAIL();
+ }
+ MATCH_CASE (Foo, f)
{
(void)f;
SUCCEED();
}
- CASE(Bar, b)
+ MATCH_CASE (Bar, b)
{
(void)b;
FAIL();
}
}
+ MATCH_END ();
+
v1.emplace<Bar>(2);
- MATCH (v1)
+ MATCH_BEGIN (v1)
{
- // This is not a public API, it's just for testing.
- default:
- FAIL();
-
- CASE(Foo, f)
+ MATCH_DEFAULT ()
+ {
+ FAIL();
+ }
+ MATCH_CASE (Foo, f)
{
(void)f;
FAIL();
}
- CASE(Bar, b)
+ MATCH_CASE (Bar, b)
{
(void)b;
SUCCEED();
}
}
+ MATCH_END ();
}
TEST(variant, copymove1)
diff --git a/src/sexpr/void.cpp b/src/sexpr/void.cpp
deleted file mode 100644
index 9f0eeb5..0000000
--- a/src/sexpr/void.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "void.hpp"
-// void.cpp - Just include the header file.
-//
-// Copyright © 2012 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-namespace sexpr
-{
-} // namespace sexpr
-} // namespace tmwa
diff --git a/src/strings/astring.py b/src/strings/astring.py
index f4cbf66..a3306d9 100644
--- a/src/strings/astring.py
+++ b/src/strings/astring.py
@@ -25,7 +25,7 @@ class AString(object):
test_extra = '''
using tmwa::operator "" _s;
- #include "../src/strings/zstring.hpp"
+ #include "../strings/zstring.hpp"
'''
tests = [
diff --git a/src/strings/fwd.hpp b/src/strings/fwd.hpp
index b1b8266..29762f9 100644
--- a/src/strings/fwd.hpp
+++ b/src/strings/fwd.hpp
@@ -23,6 +23,8 @@
#include <cstddef>
#include <cstdint>
+// strings/fwd.hpp is rank 1
+
namespace tmwa
{
diff --git a/src/strings/rstring.cpp b/src/strings/rstring.cpp
index e74d1d5..aaf0ba0 100644
--- a/src/strings/rstring.cpp
+++ b/src/strings/rstring.cpp
@@ -36,7 +36,7 @@ namespace tmwa
{
namespace strings
{
- RString::RString()
+ RString::RString() noexcept
: u{.begin= ""}, maybe_end(u.begin)
{
}
@@ -58,13 +58,18 @@ namespace strings
}
RString& RString::operator = (const RString& r)
{
- // order important for self-assign
- if (!r.maybe_end)
- r.u.owned->count++;
- if (!maybe_end && !u.owned->count--)
- ::operator delete(u.owned);
- u = r.u;
- maybe_end = r.maybe_end;
+ // this turns out to be a win
+ // certain callers end up needing to do self-assignment a *lot*,
+ // leading to pointless ++,--s
+ if (this->u.owned != r.u.owned)
+ {
+ if (!r.maybe_end)
+ r.u.owned->count++;
+ if (!maybe_end && !u.owned->count--)
+ ::operator delete(u.owned);
+ u = r.u;
+ maybe_end = r.maybe_end;
+ }
return *this;
}
RString& RString::operator = (RString&& r)
diff --git a/src/strings/rstring.hpp b/src/strings/rstring.hpp
index ad44beb..62f74fa 100644
--- a/src/strings/rstring.hpp
+++ b/src/strings/rstring.hpp
@@ -48,7 +48,7 @@ namespace strings
const char *maybe_end;
public:
- RString();
+ RString() noexcept;
RString(LString s);
RString(const RString&);
RString(RString&&);
diff --git a/src/strings/rstring.py b/src/strings/rstring.py
index 61603d8..75fe2db 100644
--- a/src/strings/rstring.py
+++ b/src/strings/rstring.py
@@ -1,3 +1,6 @@
+# used by other pretty-printers
+rstring_disable_children = False
+
class RString(object):
__slots__ = ('_value')
name = 'tmwa::strings::RString'
@@ -21,6 +24,8 @@ class RString(object):
return b.lazy_string(length=d)
def children(self):
+ if rstring_disable_children:
+ return
v = self._value
if v['maybe_end']:
pass
@@ -31,7 +36,7 @@ class RString(object):
test_extra = '''
using tmwa::operator "" _s;
- #include "../src/strings/zstring.hpp"
+ #include "../strings/zstring.hpp"
'''
tests = [
diff --git a/src/strings/strings2_test.cpp b/src/strings/strings2_test.cpp
index 8ac8482..8b91306 100644
--- a/src/strings/strings2_test.cpp
+++ b/src/strings/strings2_test.cpp
@@ -228,4 +228,13 @@ TEST(StringTests, rlong)
EXPECT_EQ(&*r.begin(), &*r3.begin());
EXPECT_EQ(&*a.begin(), &*a3.begin());
}
+
+TEST(StringTest, rself)
+{
+ // force dynamic allocation; valgrind will check for memory errors
+ RString r = XString("foo bar baz"_s);
+ RString r2 = r;
+ r = r;
+ r = r2;
+}
} // namespace tmwa
diff --git a/src/strings/vstring.cpp b/src/strings/vstring.cpp
deleted file mode 100644
index 1cb313a..0000000
--- a/src/strings/vstring.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "vstring.hpp"
-// strings/vstring.cpp - Functions for vstring.hpp
-//
-// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-namespace strings
-{
-} // namespace strings
-} // namespace tmwa
diff --git a/src/strings/xstring.py b/src/strings/xstring.py
index b2e33bb..ae764df 100644
--- a/src/strings/xstring.py
+++ b/src/strings/xstring.py
@@ -22,10 +22,10 @@ class XString(object):
'''
tests = [
- ('tmwa::XString(""_s)', '"" = {base = 0x0}'),
- ('tmwa::XString("Hello"_s)', '"Hello" = {base = 0x0}'),
- ('tmwa::XString("' + str256[:-2] + '"_s)', '"' + str256[:-2] + '" = {base = 0x0}'),
- ('tmwa::XString("' + str256[:-1] + '"_s)', '"' + str256[:-1] + '" = {base = 0x0}'),
- ('tmwa::XString("' + str256 + '"_s)', '"' + str256 + '" = {base = 0x0}'),
- ('tmwa::XString("' + str256 + 'x"_s)', '"' + str256 + 'x" = {base = 0x0}'),
+ ('tmwa::XString(""_s)', '"" = {base = nullptr}'),
+ ('tmwa::XString("Hello"_s)', '"Hello" = {base = nullptr}'),
+ ('tmwa::XString("' + str256[:-2] + '"_s)', '"' + str256[:-2] + '" = {base = nullptr}'),
+ ('tmwa::XString("' + str256[:-1] + '"_s)', '"' + str256[:-1] + '" = {base = nullptr}'),
+ ('tmwa::XString("' + str256 + '"_s)', '"' + str256 + '" = {base = nullptr}'),
+ ('tmwa::XString("' + str256 + 'x"_s)', '"' + str256 + 'x" = {base = nullptr}'),
]
diff --git a/src/strings/zstring.py b/src/strings/zstring.py
index f57252f..570c8f1 100644
--- a/src/strings/zstring.py
+++ b/src/strings/zstring.py
@@ -22,10 +22,10 @@ class ZString(object):
'''
tests = [
- ('tmwa::ZString(""_s)', '"" = {base = 0x0}'),
- ('tmwa::ZString("Hello"_s)', '"Hello" = {base = 0x0}'),
- ('tmwa::ZString("' + str256[:-2] + '"_s)', '"' + str256[:-2] + '" = {base = 0x0}'),
- ('tmwa::ZString("' + str256[:-1] + '"_s)', '"' + str256[:-1] + '" = {base = 0x0}'),
- ('tmwa::ZString("' + str256 + '"_s)', '"' + str256 + '" = {base = 0x0}'),
- ('tmwa::ZString("' + str256 + 'x"_s)', '"' + str256 + 'x" = {base = 0x0}'),
+ ('tmwa::ZString(""_s)', '"" = {base = nullptr}'),
+ ('tmwa::ZString("Hello"_s)', '"Hello" = {base = nullptr}'),
+ ('tmwa::ZString("' + str256[:-2] + '"_s)', '"' + str256[:-2] + '" = {base = nullptr}'),
+ ('tmwa::ZString("' + str256[:-1] + '"_s)', '"' + str256[:-1] + '" = {base = nullptr}'),
+ ('tmwa::ZString("' + str256 + '"_s)', '"' + str256 + '" = {base = nullptr}'),
+ ('tmwa::ZString("' + str256 + 'x"_s)', '"' + str256 + 'x" = {base = nullptr}'),
]
diff --git a/src/tests/fdhack.cpp b/src/tests/fdhack.cpp
deleted file mode 100644
index 7a95431..0000000
--- a/src/tests/fdhack.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "fdhack.hpp"
-// fdhack.cpp - Move file descriptors around.
-//
-// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-// This file is part of The Mana World (Athena server)
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-} // namespace tmwa
diff --git a/src/tests/fwd.hpp b/src/tests/fwd.hpp
index 48627da..3ee52ff 100644
--- a/src/tests/fwd.hpp
+++ b/src/tests/fwd.hpp
@@ -20,6 +20,10 @@
#include "../sanity.hpp"
+#include "../strings/fwd.hpp" // rank 1
+#include "../io/fwd.hpp" // rank 4
+// tests/fwd.hpp is rank 5, but gtests do not require rank
+
namespace tmwa
{
diff --git a/src/warnings.hpp b/src/warnings.hpp
index 9389766..884857f 100644
--- a/src/warnings.hpp
+++ b/src/warnings.hpp
@@ -161,7 +161,10 @@ DIAG_E(trigraphs);
DIAG_E(type_limits);
DIAG_E(undef);
DIAG_E(uninitialized);
+// clang bug, fixed in 3.5
+#ifndef GENERATING_DEPENDENCIES
DIAG_E(unknown_pragmas);
+#endif
DIAG_W(unreachable_code);
DIAG_X(unsafe_loop_optimizations);
DIAG_E(unused_but_set_parameter);
diff --git a/src/generic/enum.cpp b/src/wire/fwd.hpp
index 49402e9..83d5b20 100644
--- a/src/generic/enum.cpp
+++ b/src/wire/fwd.hpp
@@ -1,5 +1,5 @@
-#include "enum.hpp"
-// enum.cpp - Safe building blocks for enumerated types.
+#pragma once
+// wire/fwd.hpp - list of type names for network packets
//
// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
@@ -18,9 +18,17 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include "../poison.hpp"
+#include "../sanity.hpp"
+#include "../ints/fwd.hpp" // rank 1
+#include "../compat/fwd.hpp" // rank 2
+#include "../io/fwd.hpp" // rank 4
+#include "../net/fwd.hpp" // rank 5
+#include "../proto2/fwd.hpp" // rank 8
+// wire/fwd.hpp is rank 9
namespace tmwa
{
+enum class RecvResult;
+// meh, add more when I feel like it
} // namespace tmwa
diff --git a/src/net/packets.cpp b/src/wire/packets.cpp
index 3cba856..be06283 100644
--- a/src/net/packets.cpp
+++ b/src/wire/packets.cpp
@@ -65,29 +65,29 @@ bool packet_send(Session *s, const Byte *data, size_t sz)
return true;
}
-void packet_dump(io::WriteFile& logfp, Session *s)
+void packet_dump(Session *s)
{
- FPRINTF(logfp,
+ FPRINTF(stderr,
"---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F\n"_fmt);
char tmpstr[16 + 1] {};
int i;
for (i = 0; i < packet_avail(s); i++)
{
if ((i & 15) == 0)
- FPRINTF(logfp, "%04X "_fmt, i);
+ FPRINTF(stderr, "%04X "_fmt, i);
Byte rfifob_ib;
packet_fetch(s, i, &rfifob_ib, 1);
uint8_t rfifob_i = rfifob_ib.value;
- FPRINTF(logfp, "%02x "_fmt, rfifob_i);
+ FPRINTF(stderr, "%02x "_fmt, rfifob_i);
if (rfifob_i > 0x1f)
tmpstr[i % 16] = rfifob_i;
else
tmpstr[i % 16] = '.';
if ((i - 7) % 16 == 0) // -8 + 1
- FPRINTF(logfp, " "_fmt);
+ FPRINTF(stderr, " "_fmt);
else if ((i + 1) % 16 == 0)
{
- FPRINTF(logfp, " %s\n"_fmt, tmpstr);
+ FPRINTF(stderr, " %s\n"_fmt, tmpstr);
std::fill(tmpstr + 0, tmpstr + 17, '\0');
}
}
@@ -95,12 +95,12 @@ void packet_dump(io::WriteFile& logfp, Session *s)
{
for (int j = i; j % 16 != 0; j++)
{
- FPRINTF(logfp, " "_fmt);
+ FPRINTF(stderr, " "_fmt);
if ((j - 7) % 16 == 0) // -8 + 1
- FPRINTF(logfp, " "_fmt);
+ FPRINTF(stderr, " "_fmt);
}
- FPRINTF(logfp, " %s\n"_fmt, tmpstr);
+ FPRINTF(stderr, " %s\n"_fmt, tmpstr);
}
- FPRINTF(logfp, "\n"_fmt);
+ FPRINTF(stderr, "\n"_fmt);
}
} // namespace tmwa
diff --git a/src/net/packets.hpp b/src/wire/packets.hpp
index 5cc377c..82cc919 100644
--- a/src/net/packets.hpp
+++ b/src/wire/packets.hpp
@@ -22,16 +22,13 @@
#include <vector>
-#include "../compat/cast.hpp"
-
#include "../ints/little.hpp"
-#include "../io/fwd.hpp"
+#include "../compat/cast.hpp"
-// TODO ordering violation, should invert
#include "../proto2/fwd.hpp"
-#include "socket.hpp"
+#include "../net/socket.hpp"
namespace tmwa
@@ -56,7 +53,7 @@ enum class SendResult
size_t packet_avail(Session *s);
-void packet_dump(io::WriteFile& out, Session *s);
+void packet_dump(Session *s);
bool packet_fetch(Session *s, size_t offset, Byte *data, size_t sz);
void packet_discard(Session *s, size_t sz);
diff --git a/tools/colorize b/tools/colorize
index ae4cb56..ce6f410 100755
--- a/tools/colorize
+++ b/tools/colorize
@@ -12,6 +12,10 @@ def color(i):
return '\x1b[%dm' % (90 + (i - 8))
def main(argv):
+ # can't change buffering on sys.stdout after creation using python APIs
+ # so do our own buffering
+ buffer = []
+
colors = {}
while argv:
arg0 = argv[0]
@@ -26,12 +30,13 @@ def main(argv):
arg = argv[0]
c = colors.get('', '')
e = c and '\x1b[m'
- print(c, arg, e, sep='', end=''),
+ buffer.extend([c, arg, e])
for arg in argv[1:]:
c = colors.get(arg, '')
e = c and '\x1b[m'
- print(' ', c, arg, e, sep='', end=''),
- print()
+ buffer.extend([' ', c, arg, e])
+ buffer.append('\n')
+ sys.stdout.write(''.join(buffer))
sys.stdout.flush()
os.execvp(argv[0], argv)
diff --git a/tools/config.py b/tools/config.py
new file mode 100755
index 0000000..aeb4e99
--- /dev/null
+++ b/tools/config.py
@@ -0,0 +1,684 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# config.py - generator for config file parsers
+#
+# Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+#
+# This file is part of The Mana World (Athena server)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+import glob
+import os
+
+from protocol import OpenWrite
+
+
+generated = '// This is a generated file, edit %s instead\n' % __file__
+
+copyright = '''// {filename} - {description}
+//
+// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
+//
+// This file is part of The Mana World (Athena server)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+'''
+
+
+class AnyHeader(object):
+ __slots__ = ('name')
+
+ def __init__(self, name):
+ self.name = name
+
+class SystemHeader(AnyHeader):
+ __slots__ = ()
+ meta = 0
+
+ def relative_to(self, path):
+ return '<%s>' % self.name
+
+class Header(AnyHeader):
+ __slots__ = ()
+ meta = 1
+
+ def relative_to(self, path):
+ return '"%s"' % os.path.relpath(self.name, path)
+
+
+class ConfigType(object):
+ __slots__ = ()
+
+class SimpleType(ConfigType):
+ __slots__ = ('name', 'headers')
+
+ def __init__(self, name, headers):
+ self.name = name
+ self.headers = frozenset(headers)
+
+ def __repr__(self):
+ return 'SimpleType(%r, %r)' % (self.name, self.headers)
+
+ def type_name(self):
+ return self.name
+
+ def dump_extract(self, cpp, var):
+ cpp.write(
+'''
+ if (!extract(value.data, &{var}))
+ {{
+ value.span.error("Failed to extract value"_s);
+ return false;
+ }}
+'''.lstrip('\n').format(var=var))
+
+class PhonyType(ConfigType):
+ __slots__ = ('type', 'name', 'call', 'headers')
+
+ def __init__(self, type, name, call, extra_headers):
+ self.type = type
+ self.name = name
+ self.call = call
+ self.headers = type.headers | extra_headers
+
+ def __repr__(self):
+ return 'PhonyType(%r, %r, %r, %r)' % (self.type, self.name, self.call, self.headers)
+
+ def type_name(self):
+ return '// special %s' % self.type.type_name()
+
+ def dump_extract(self, cpp, var):
+ cpp.write(' %s %s;\n' % (self.type.type_name(), self.name))
+ self.type.dump_extract(cpp, self.name)
+ cpp.write(' %s\n' % self.call)
+
+class TransformedType(ConfigType):
+ __slots__ = ('type', 'transform', 'headers')
+
+ def __init__(self, type, transform, extra_headers=set()):
+ self.type = type
+ self.transform = transform
+ self.headers = type.headers | extra_headers
+
+ def __repr__(self):
+ return 'TransformedType(%r, %r)' % (self.type, self.transform)
+
+ def type_name(self):
+ return self.type.type_name()
+
+ def dump_extract(self, cpp, var):
+ self.type.dump_extract(cpp, var)
+ cpp.write(' %s;\n' % self.transform)
+
+class BoundedType(ConfigType):
+ __slots__ = ('type', 'low', 'high', 'headers')
+
+ def __init__(self, type, low, high, extra_headers=set()):
+ assert isinstance(type, ConfigType)
+ self.type = type
+ self.low = low
+ self.high = high
+ self.headers = type.headers | extra_headers
+
+ def __repr__(self):
+ return 'BoundedType(%r, %r, %r, %r)' % (self.type, self.low, self.high, self.headers)
+
+ def type_name(self):
+ return self.type.type_name()
+
+ def dump_extract(self, cpp, var):
+ self.type.dump_extract(cpp, var)
+ cpp.write(
+'''
+ if (!({low} <= {var} && {var} <= {high}))
+ {{
+ value.span.error("Value of {name} not in range [{low}, {high}]"_s);
+ return false;
+ }}
+'''.format(low=self.low, high=self.high, var=var, name=var.split('.')[-1]))
+
+class MinBoundedType(ConfigType):
+ __slots__ = ('type', 'low', 'headers')
+
+ def __init__(self, type, low, extra_headers=set()):
+ assert isinstance(type, ConfigType)
+ self.type = type
+ self.low = low
+ self.headers = type.headers | extra_headers
+
+ def __repr__(self):
+ return 'MinBoundedType(%r, %r, %r, %r)' % (self.type, self.low, self.headers)
+
+ def type_name(self):
+ return self.type.type_name()
+
+ def dump_extract(self, cpp, var):
+ self.type.dump_extract(cpp, var)
+ cpp.write(
+'''
+ if (!({low} <= {var}))
+ {{
+ value.span.error("Value of {name} not at least {low}"_s);
+ return false;
+ }}
+'''.format(low=self.low, var=var, name=var.split('.')[-1]))
+
+class Option(object):
+ __slots__ = ('name', 'type', 'default', 'headers')
+
+ def __init__(self, name, type, default, extra_headers=set()):
+ self.name = name
+ self.type = type
+ self.default = default
+ self.headers = type.headers | extra_headers
+
+ def dump1(self, hpp):
+ hpp.write(' %s %s = %s;\n' % (self.type.type_name(), self.name, self.default))
+ def dump2(self, cpp, x):
+ # NOTE about hashing
+ # dead simple hash: pack 6 bits of first 5 letters into an int
+ y = self.name[:5]
+ if x != y:
+ if x is not None:
+ cpp.write(' break;\n')
+ c0 = y[0] if len(y) > 0 else '\\0'
+ c1 = y[1] if len(y) > 1 else '\\0'
+ c2 = y[2] if len(y) > 2 else '\\0'
+ c3 = y[3] if len(y) > 3 else '\\0'
+ c4 = y[4] if len(y) > 4 else '\\0'
+ assert len(y) >= 3, '<-- change this number in the source file for: %r' % self.name
+ cpp.write(" case (('%s' << 24) | ('%s' << 18) | ('%s' << 12) | ('%s' << 6) | ('%s' << 0)):\n" % (c0, c1, c2, c3, c4))
+ cpp.write(' if (key.data == "{name}"_s)\n'.format(name=self.name))
+ cpp.write(' {\n')
+ self.type.dump_extract(cpp, 'conf.%s' % self.name)
+ cpp.write(' return true;\n')
+ cpp.write(' }\n')
+ return y
+
+class Group(object):
+ __slots__ = ('name', 'options', 'extra_headers')
+
+ def __init__(self, name):
+ self.name = name
+ self.options = {}
+ self.extra_headers = []
+
+ def extra(self, h):
+ self.extra_headers.append(h)
+
+ def opt(self, name, type, default, extra_headers=set(), pre=None, post=None, min=None, max=None):
+ assert name not in self.options, 'Duplicate option name: %s' % name
+ assert isinstance(default, str)
+ if pre is not None:
+ type = TransformedType(type, pre)
+ if min is not None:
+ if max is not None:
+ type = BoundedType(type, min, max)
+ else:
+ type = MinBoundedType(type, min)
+ else:
+ assert max is None
+ if post is not None:
+ type = TransformedType(type, post)
+ self.options[name] = rv = Option(name, type, default, extra_headers)
+ return rv
+
+ def dump_in(self, path, namespace_name):
+ if namespace_name == 'char':
+ namespace_name += '_'
+ var_name = '%s_conf' % self.name
+ class_name = var_name.replace('_', ' ').title().replace(' ', '')
+ short_hpp_name = '%s.hpp' % var_name
+ hpp_name = os.path.join(path, short_hpp_name)
+ short_cpp_name = '%s.cpp' % var_name
+ cpp_name = os.path.join(path, short_cpp_name)
+
+ values = sorted(self.options.values(), key=lambda o: o.name)
+
+ desc = 'Config for %s::%s' % (namespace_name, self.name)
+ with OpenWrite(hpp_name) as hpp, \
+ OpenWrite(cpp_name) as cpp:
+ hpp.write('#pragma once\n')
+ hpp.write(copyright.format(filename=short_hpp_name, description=desc))
+ hpp.write('\n')
+ hpp.write(generated)
+ cpp.write('#include "%s"\n' % short_hpp_name)
+ cpp.write(copyright.format(filename=short_cpp_name, description=desc))
+ cpp.write('\n')
+ cpp.write(generated)
+ headers = {
+ Header('src/io/fwd.hpp'),
+ Header('src/strings/fwd.hpp')
+ }
+ for o in values:
+ headers |= o.headers
+
+ hpp.write('\n')
+ hpp.write('#include "fwd.hpp"\n')
+ for h in sorted(headers, key=lambda h: (h.meta, h.name)):
+ hpp.write('#include %s\n' % h.relative_to(path))
+ hpp.write('\n')
+ cpp.write('\n')
+ for h in [
+ SystemHeader('bitset'),
+ Header('src/io/extract.hpp'),
+ Header('src/io/span.hpp'),
+ Header('src/mmo/extract_enums.hpp'),
+ Header('src/high/extract_mmo.hpp'),
+ ] + self.extra_headers:
+ cpp.write('#include %s\n' % h.relative_to(path))
+ cpp.write('\n')
+ cpp.write('#include "../poison.hpp"\n')
+ cpp.write('\n')
+
+ hpp.write('namespace tmwa\n{\n')
+ cpp.write('namespace tmwa\n{\n')
+ cpp.write('''
+static __attribute__((unused))
+bool extract(XString str, bool *v)
+{
+ if (str == "true"_s || str == "on"_s || str == "yes"_s
+ || str == "oui"_s || str == "ja"_s
+ || str == "si"_s || str == "1"_s)
+ {
+ *v = 1;
+ return true;
+ }
+ if (str == "false"_s || str == "off"_s || str == "no"_s
+ || str == "non"_s || str == "nein"_s || str == "0"_s)
+ {
+ *v = 0;
+ return true;
+ }
+ return false;
+}
+
+static __attribute__((unused))
+bool extract(XString str, std::bitset<256> *v)
+{
+ if (!str)
+ {
+ v->reset();
+ return true;
+ }
+ for (uint8_t c : str)
+ {
+ (*v)[c] = true;
+ }
+ return true;
+}
+''')
+ hpp.write('namespace %s\n{\n' % namespace_name)
+ cpp.write('namespace %s\n{\n' % namespace_name)
+ hpp.write('struct %s\n{\n' % class_name)
+ for o in values:
+ o.dump1(hpp)
+ hpp.write('}; // struct %s\n' % class_name)
+ hpp.write('bool parse_%s(%s& conf, io::Spanned<XString> key, io::Spanned<ZString> value);\n' % (var_name, class_name))
+ hpp.write('} // namespace %s\n' % namespace_name)
+ hpp.write('} // namespace tmwa\n')
+ cpp.write('bool parse_%s(%s& conf, io::Spanned<XString> key, io::Spanned<ZString> value)\n{\n' % (var_name, class_name))
+ # see NOTE about hashing in Option.dump2
+ cpp.write(' int key_hash = 0;\n')
+ cpp.write(' if (key.data.size() > 0)\n')
+ cpp.write(' key_hash |= key.data[0] << 24;\n')
+ cpp.write(' if (key.data.size() > 1)\n')
+ cpp.write(' key_hash |= key.data[1] << 18;\n')
+ cpp.write(' if (key.data.size() > 2)\n')
+ cpp.write(' key_hash |= key.data[2] << 12;\n')
+ cpp.write(' if (key.data.size() > 3)\n')
+ cpp.write(' key_hash |= key.data[3] << 6;\n')
+ cpp.write(' if (key.data.size() > 4)\n')
+ cpp.write(' key_hash |= key.data[4] << 0;\n')
+ cpp.write(' switch (key_hash)\n')
+ cpp.write(' {\n')
+ x = None
+ for o in values:
+ x = o.dump2(cpp, x)
+ cpp.write(' break;\n')
+ cpp.write(' } // switch\n')
+ cpp.write(' key.span.error("Unknown config key"_s);\n')
+ cpp.write(' return false;\n')
+ cpp.write('} // fn parse_%s_conf()\n' % var_name)
+ cpp.write('} // namespace %s\n' % namespace_name)
+ cpp.write('} // namespace tmwa\n')
+
+class Realm(object):
+ __slots__ = ('path', 'groups')
+
+ def __init__(self, path):
+ self.path = path
+ self.groups = {}
+
+ def conf(self, name=None):
+ if not name:
+ name = self.path.split('/')[-1]
+ assert name not in self.groups, 'Duplicate group name: %s' % name
+ self.groups[name] = rv = Group(name)
+ return rv
+
+ def dump(self):
+ for g in self.groups.values():
+ g.dump_in(self.path, self.path.split('/')[-1])
+
+class Everything(object):
+ __slots__ = ('realms')
+
+ def __init__(self):
+ self.realms = {}
+
+ def realm(self, path):
+ assert path not in self.realms, 'Duplicate realm path: %s' % path
+ self.realms[path] = rv = Realm(path)
+ return rv
+
+ def dump(self):
+ for g in glob.glob('src/*/*_conf.[ch]pp'):
+ os.rename(g, g + '.old')
+ for v in self.realms.values():
+ v.dump()
+ for g in glob.glob('src/*/*_conf.[ch]pp.old'):
+ print('Obsolete: %s' % g)
+ os.remove(g)
+
+
+def lit(s):
+ return '"%s"_s' % s.replace('\\', '\\\\').replace('"', '\\"')
+
+def build_config():
+ rv = Everything()
+
+ # realms
+ login_realm = rv.realm('src/login')
+ admin_realm = rv.realm('src/admin')
+ char_realm = rv.realm('src/char')
+ map_realm = rv.realm('src/map')
+
+ # confs
+ login_conf = login_realm.conf()
+ login_lan_conf = login_realm.conf('login_lan')
+
+ admin_conf = admin_realm.conf()
+
+ char_conf = char_realm.conf()
+ char_lan_conf = char_realm.conf('char_lan')
+ inter_conf = char_realm.conf('inter')
+
+ map_conf = map_realm.conf()
+ battle_conf = map_realm.conf('battle')
+
+ # headers
+ cstdint_sys = SystemHeader('cstdint')
+ vector_sys = SystemHeader('vector')
+ bitset_sys = SystemHeader('bitset')
+
+ ip_h = Header('src/net/ip.hpp')
+ rstring_h = Header('src/strings/rstring.hpp')
+ literal_h = Header('src/strings/literal.hpp')
+ ids_h = Header('src/mmo/ids.hpp')
+ strs_h = Header('src/mmo/strs.hpp')
+ timer_th = Header('src/net/timer.t.hpp')
+ login_th = Header('src/login/login.t.hpp')
+ udl_h = Header('src/ints/udl.hpp')
+ net_point_h = Header('src/proto2/net-Point.hpp')
+ char_h = Header('src/char/char.hpp')
+ map_h = Header('src/map/map.hpp')
+ map_th = Header('src/map/map.t.hpp')
+ npc_h = Header('src/map/npc.hpp')
+ npc_parse_h = Header('src/map/npc-parse.hpp')
+
+ map_conf.extra(npc_parse_h)
+
+ # types
+ bool = SimpleType('bool', set())
+ #double = SimpleType('double', set())
+ u8 = SimpleType('uint8_t', {cstdint_sys})
+ u16 = SimpleType('uint16_t', {cstdint_sys})
+ u32 = SimpleType('uint32_t', {cstdint_sys})
+ u64 = SimpleType('uint64_t', {cstdint_sys})
+ i8 = SimpleType('int8_t', {cstdint_sys})
+ i16 = SimpleType('int16_t', {cstdint_sys})
+ i32 = SimpleType('int32_t', {cstdint_sys})
+ i64 = SimpleType('int64_t', {cstdint_sys})
+
+ percent = i32
+ perk = i32
+ per10k = i32
+ #per10kd = double
+ per10kd = i32
+
+ IP4Address = SimpleType('IP4Address', {ip_h})
+ IP4Mask = SimpleType('IP4Mask', {ip_h})
+ IpSet = SimpleType('std::vector<IP4Mask>', {vector_sys, ip_h})
+ RString = SimpleType('RString', {rstring_h, literal_h})
+ GmLevel = SimpleType('GmLevel', {ids_h})
+ hours = SimpleType('std::chrono::hours', {timer_th})
+ seconds = SimpleType('std::chrono::seconds', {timer_th})
+ milliseconds = SimpleType('std::chrono::milliseconds', {timer_th})
+ ACO = SimpleType('ACO', {login_th})
+ ServerName = SimpleType('ServerName', {strs_h})
+ AccountName = SimpleType('AccountName', {strs_h})
+ AccountPass = SimpleType('AccountPass', {strs_h})
+ Point = SimpleType('Point', {net_point_h})
+ CharName = SimpleType('CharName', {strs_h})
+ CharBitset = SimpleType('std::bitset<256>', {bitset_sys})
+ MapName = SimpleType('MapName', {strs_h})
+ ATK = SimpleType('ATK', {map_th})
+
+ addmap = PhonyType(MapName, 'name', 'map_addmap(name);', {map_h})
+ delmap = PhonyType(MapName, 'name', 'map_delmap(name);', {map_h})
+ addnpc = PhonyType(RString, 'npc', 'npc_addsrcfile(npc);', {npc_h})
+ delnpc = PhonyType(RString, 'npc', 'npc_delsrcfile(npc);', {npc_h})
+
+
+ # options
+ login_lan_conf.opt('lan_char_ip', IP4Address, 'IP4_LOCALHOST')
+ login_lan_conf.opt('lan_subnet', IP4Mask, 'IP4Mask(IP4_LOCALHOST, IP4_BROADCAST)')
+
+ login_conf.opt('admin_state', bool, 'false')
+ login_conf.opt('admin_pass', AccountPass, '{}')
+ login_conf.opt('ladminallowip', IpSet, '{}')
+ login_conf.opt('gm_pass', RString, '{}')
+ login_conf.opt('level_new_gm', GmLevel, 'GmLevel::from(60_u32)', {udl_h})
+ login_conf.opt('new_account', bool, 'false')
+ login_conf.opt('login_port', u16, '6901', min='1024')
+ login_conf.opt('account_filename', RString, lit('save/account.txt'))
+ login_conf.opt('gm_account_filename', RString, lit('save/gm_account.txt'))
+ login_conf.opt('gm_account_filename_check_timer', seconds, '15_s')
+ login_conf.opt('login_log_filename', RString, lit('log/login.log'))
+ login_conf.opt('display_parse_login', bool, 'false')
+ login_conf.opt('display_parse_admin', bool, 'false')
+ login_conf.opt('display_parse_fromchar', i32, '0', min='0', max='2')
+ login_conf.opt('min_level_to_connect', GmLevel, 'GmLevel::from(0_u32)', {udl_h})
+ login_conf.opt('order', ACO, 'ACO::DENY_ALLOW')
+ login_conf.opt('allow', IpSet, '{}')
+ login_conf.opt('deny', IpSet, '{}')
+ login_conf.opt('anti_freeze_enable', bool, 'false')
+ login_conf.opt('anti_freeze_interval', seconds, '15_s')
+ login_conf.opt('update_host', RString, '{}')
+ login_conf.opt('main_server', ServerName, '{}')
+ login_conf.opt('userid', AccountName, '{}')
+ login_conf.opt('passwd', AccountPass, '{}')
+
+
+ admin_conf.opt('login_ip', IP4Address, 'IP4_LOCALHOST')
+ admin_conf.opt('login_port', u16, '6901', min='1024')
+ admin_conf.opt('admin_pass', AccountPass, 'stringish<AccountPass>("admin"_s)')
+ admin_conf.opt('ladmin_log_filename', RString, lit('log/ladmin.log'))
+
+
+ char_lan_conf.opt('lan_map_ip', IP4Address, 'IP4_LOCALHOST')
+ char_lan_conf.opt('lan_subnet', IP4Mask, 'IP4Mask(IP4_LOCALHOST, IP4_BROADCAST)')
+
+ char_conf.opt('userid', AccountName, '{}')
+ char_conf.opt('passwd', AccountPass, '{}')
+ char_conf.opt('server_name', ServerName, '{}')
+ char_conf.opt('login_ip', IP4Address, '{}')
+ char_conf.opt('login_port', u16, '6901', min='1024')
+ char_conf.opt('char_ip', IP4Address, '{}')
+ char_conf.opt('char_port', u16, '6121', min='1024')
+ char_conf.opt('char_txt', RString, '{}')
+ char_conf.opt('max_connect_user', u32, '0')
+ char_conf.opt('autosave_time', seconds, 'DEFAULT_AUTOSAVE_INTERVAL', {char_h}, min='1_s')
+ char_conf.opt('start_point', Point, '{ {"001-1.gat"_s}, 273, 354 }')
+ char_conf.opt('unknown_char_name', CharName, 'stringish<CharName>("Unknown"_s)')
+ char_conf.opt('char_log_filename', RString, lit('log/char.log'))
+ char_conf.opt('char_name_letters', CharBitset, '{}')
+ char_conf.opt('online_txt_filename', RString, lit('online.txt'))
+ char_conf.opt('online_html_filename', RString, lit('online.html'))
+ char_conf.opt('online_gm_display_min_level', GmLevel, 'GmLevel::from(20_u32)', {udl_h})
+ char_conf.opt('online_refresh_html', u32, '20', min=1)
+ char_conf.opt('anti_freeze_enable', bool, 'false')
+ char_conf.opt('anti_freeze_interval', seconds, '6_s', min='5_s')
+
+ inter_conf.opt('storage_txt', RString, lit('save/storage.txt'))
+ inter_conf.opt('party_txt', RString, lit('save/party.txt'))
+ inter_conf.opt('accreg_txt', RString, lit('save/accreg.txt'))
+ inter_conf.opt('party_share_level', u32, '10')
+
+ map_conf.opt('userid', AccountName, '{}')
+ map_conf.opt('passwd', AccountPass, '{}')
+ map_conf.opt('char_ip', IP4Address, '{}')
+ map_conf.opt('char_port', u16, '6121', min='1024')
+ map_conf.opt('map_ip', IP4Address, '{}')
+ map_conf.opt('map_port', u16, '5121', min='1024')
+ map_conf.opt('map', addmap, '{}')
+ map_conf.opt('delmap', delmap, '{}')
+ map_conf.opt('npc', addnpc, '{}')
+ map_conf.opt('delnpc', delnpc, '{}')
+ map_conf.opt('autosave_time', seconds, 'DEFAULT_AUTOSAVE_INTERVAL', {map_h})
+ map_conf.opt('motd_txt', RString, lit('conf/motd.txt'))
+ map_conf.opt('mapreg_txt', RString, lit('save/mapreg.txt'))
+ map_conf.opt('gm_log', RString, '{}')
+ map_conf.opt('log_file', RString, '{}')
+
+ battle_conf.opt('enemy_critical', bool, 'false')
+ battle_conf.opt('enemy_critical_rate', percent, '100')
+ battle_conf.opt('enemy_str', bool, 'true')
+ battle_conf.opt('enemy_perfect_flee', bool, 'false')
+ battle_conf.opt('casting_rate', percent, '100')
+ battle_conf.opt('delay_rate', percent, '100')
+ battle_conf.opt('delay_dependon_dex', bool, 'false')
+ battle_conf.opt('skill_delay_attack_enable', bool, 'false')
+ battle_conf.opt('monster_skill_add_range', i32, '0')
+ battle_conf.opt('player_damage_delay', bool, '1')
+ battle_conf.opt('flooritem_lifetime', milliseconds, 'LIFETIME_FLOORITEM', min='1_s')
+ battle_conf.opt('item_auto_get', bool, 'false')
+ battle_conf.opt('item_first_get_time', milliseconds, '3_s')
+ battle_conf.opt('item_second_get_time', milliseconds, '1_s')
+ battle_conf.opt('item_third_get_time', milliseconds, '1_s')
+ battle_conf.opt('base_exp_rate', percent, '100')
+ battle_conf.opt('job_exp_rate', percent, '100')
+ battle_conf.opt('death_penalty_type', i32, '0', min='0', max='2')
+ battle_conf.opt('death_penalty_base', per10kd, '0')
+ battle_conf.opt('death_penalty_job', per10kd, '0')
+ battle_conf.opt('restart_hp_rate', percent, '0', min='0', max='100')
+ battle_conf.opt('restart_sp_rate', percent, '0', min='0', max='100')
+ battle_conf.opt('monster_hp_rate', percent, '0')
+ battle_conf.opt('monster_max_aspd', milliseconds, '199_ms', pre='conf.monster_max_aspd = 2000_ms - conf.monster_max_aspd * 10;', min='10_ms', max='1000_ms')
+ battle_conf.opt('atcommand_gm_only', bool, '0')
+ battle_conf.opt('atcommand_spawn_quantity_limit', i32, '{}')
+ battle_conf.opt('gm_all_equipment', GmLevel, 'GmLevel::from(0_u32)', {udl_h})
+ battle_conf.opt('monster_active_enable', bool, 'true')
+ battle_conf.opt('mob_skill_use', bool, 'true')
+ battle_conf.opt('mob_count_rate', percent, '100')
+ battle_conf.opt('basic_skill_check', bool, 'true')
+ battle_conf.opt('player_invincible_time', milliseconds, '5_s')
+ battle_conf.opt('player_pvp_time', milliseconds, '5_s')
+ battle_conf.opt('skill_min_damage', bool, 'false')
+ battle_conf.opt('natural_healhp_interval', milliseconds, '6_s', {map_h}, min='NATURAL_HEAL_INTERVAL')
+ battle_conf.opt('natural_healsp_interval', milliseconds, '8_s', {map_h}, min='NATURAL_HEAL_INTERVAL')
+ battle_conf.opt('natural_heal_weight_rate', percent, '50', min='50', max='101')
+ battle_conf.opt('arrow_decrement', bool, 'true')
+ battle_conf.opt('max_aspd', milliseconds, '199_ms', pre='conf.max_aspd = 2000_ms - conf.max_aspd * 10;', min='10_ms', max='1000_ms')
+ battle_conf.opt('max_hp', i32, '32500', min='100', max='1000000')
+ battle_conf.opt('max_sp', i32, '32500', min='100', max='1000000')
+ battle_conf.opt('max_lv', i32, '99')
+ battle_conf.opt('max_parameter', i32, '99', min='10', max='10000')
+ battle_conf.opt('monster_skill_log', bool, 'false')
+ battle_conf.opt('battle_log', bool, 'false')
+ battle_conf.opt('save_log', bool, 'false')
+ battle_conf.opt('error_log', bool, 'true')
+ battle_conf.opt('etc_log', bool, 'true')
+ battle_conf.opt('save_clothcolor', bool, 'false')
+ battle_conf.opt('undead_detect_type', i32, '0', min='0', max='2')
+ battle_conf.opt('agi_penaly_type', i32, '0', min='0', max='2')
+ battle_conf.opt('agi_penaly_count', i32, '3', min='2')
+ battle_conf.opt('agi_penaly_num', i32, '0')
+ battle_conf.opt('vit_penaly_type', i32, '0', min='0', max='2')
+ battle_conf.opt('vit_penaly_count', i32, '3', min='2')
+ battle_conf.opt('vit_penaly_num', i32, '0')
+ battle_conf.opt('mob_changetarget_byskill', bool, 'false')
+ battle_conf.opt('player_attack_direction_change', bool, 'true')
+ battle_conf.opt('monster_attack_direction_change', bool, 'true')
+ battle_conf.opt('display_delay_skill_fail', bool, 'true')
+ battle_conf.opt('prevent_logout', bool, 'true')
+ battle_conf.opt('alchemist_summon_reward', bool, '{}')
+ battle_conf.opt('maximum_level', i32, '255')
+ battle_conf.opt('drops_by_luk', percent, '0')
+ battle_conf.opt('monsters_ignore_gm', bool, '{}')
+ battle_conf.opt('multi_level_up', bool, 'false')
+ battle_conf.opt('pk_mode', bool, 'false')
+ battle_conf.opt('agi_penaly_count_lv', ATK, 'ATK::FLEE')
+ battle_conf.opt('vit_penaly_count_lv', ATK, 'ATK::DEF')
+ battle_conf.opt('hide_GM_session', bool, 'false')
+ battle_conf.opt('invite_request_check', bool, 'true')
+ battle_conf.opt('disp_experience', bool, '0')
+ battle_conf.opt('hack_info_GM_level', GmLevel, 'GmLevel::from(60_u32)', {udl_h})
+ battle_conf.opt('any_warp_GM_min_level', GmLevel, 'GmLevel::from(20_u32)', {udl_h})
+ battle_conf.opt('min_hair_style', i32, '0')
+ battle_conf.opt('max_hair_style', i32, '20')
+ battle_conf.opt('min_hair_color', i32, '0')
+ battle_conf.opt('max_hair_color', i32, '9')
+ battle_conf.opt('min_cloth_color', i32, '0')
+ battle_conf.opt('max_cloth_color', i32, '4')
+ battle_conf.opt('castrate_dex_scale', i32, '150')
+ battle_conf.opt('area_size', i32, '14')
+ battle_conf.opt('chat_lame_penalty', i32, '2')
+ battle_conf.opt('chat_spam_threshold', seconds, '10_s', min='0_s', max='32767_s')
+ battle_conf.opt('chat_spam_flood', i32, '10', min='0', max='32767')
+ battle_conf.opt('chat_spam_ban', hours, '1_h', min='0_h', max='32767_h')
+ battle_conf.opt('chat_spam_warn', i32, '8', min='0', max='32767')
+ battle_conf.opt('chat_maxline', i32, '255', min='1', max='512')
+ battle_conf.opt('packet_spam_threshold', seconds, '2_s', min='0_s', max='32767_s')
+ battle_conf.opt('packet_spam_flood', i32, '30', min='0', max='32767')
+ battle_conf.opt('packet_spam_kick', bool, 'true')
+ battle_conf.opt('mask_ip_gms', bool, 'true')
+ battle_conf.opt('drop_pickup_safety_zone', i32, '20')
+ battle_conf.opt('itemheal_regeneration_factor', i32, '1')
+ battle_conf.opt('mob_splash_radius', i32, '-1', min='-1')
+
+ return rv
+
+def main():
+ cfg = build_config()
+ cfg.dump()
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/debug-debug-scripts b/tools/debug-debug-scripts
index e5eeb6c..2112a6e 100755
--- a/tools/debug-debug-scripts
+++ b/tools/debug-debug-scripts
@@ -21,12 +21,15 @@ copyright = '''
// along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
+import glob
import itertools
import os
import subprocess
import sys
import tempfile
+import protocol
+
error = False
def eprint(s):
@@ -63,58 +66,74 @@ def get_classes_from_file(a):
def c_quote(s):
s = s.replace('\\', '\\\\')
+ s = s.replace('\n', '\\n')
s = s.replace('"', '\\"')
return '"' + s + '"'
-def gen_test(name, expr, expected):
- print('static')
- print('void %s()' % name)
- print('{')
- print(' auto value = %s;' % expr)
- print(' const char *expected = %s;' % c_quote(expected))
- print(' do_breakpoint(value, expected);')
- print('}')
+def gen_test(name, expr, expected, w):
+ print('static', file=w)
+ print('void %s()' % name, file=w)
+ print('{', file=w)
+ print(' auto&& value = %s;' % expr, file=w)
+ print(' const char *expected = %s;' % c_quote(expected), file=w)
+ print(' do_breakpoint(value, expected);', file=w)
+ print('}', file=w)
def main(args):
- print('// test.cpp - generated by', __file__)
- print(copyright)
- print('#include <cstdio>')
- print('// just mention "fwd.hpp" and "../poison.hpp" to make formatter happy')
- print('namespace tmwa')
- print('{')
- print('} // namespace tmwa')
- print()
- print('template<class T>')
- print('__attribute__((noinline))')
- print('void do_breakpoint(const T& value, const char *expected)')
- print('{')
- print(' (void)value;')
- print(' (void)expected;')
- print(' if (!expected) printf("printer test: %p = %s\\n", &value, expected);')
- print('}')
-
- names = []
+ outdir = args[0]
+ args = args[1:]
+
+ for g in glob.glob(os.path.join(outdir, '*.[ch]pp')):
+ os.rename(g, g + '.old')
+
for a in args:
+ names = []
basename, ext = os.path.splitext(a)
assert ext == '.py'
- print()
- print('// Tests from', a)
- header = basename + '.hpp'
- print('#include "%s"' % header)
-
- for (k, tests, extra) in get_classes_from_file(a):
- print()
- print('// Tests for', k)
- print(extra)
- for (i, (expr, expected)) in enumerate(tests):
- name = 'testset_%s_subtest_%d' % (k, i)
- gen_test(name, expr, expected)
- names.append(name)
- print('int main()')
- print('{')
- for n in names:
- print(' %s();' % n)
- print('}')
+ newbase = basename.split('src/')[1].replace('/', '-')
+ out = os.path.join(outdir, newbase + '.cpp')
+ with protocol.OpenWrite(out) as w:
+ print('// %s.cpp - generated by %s from %s' % (newbase, __file__, a), file=w)
+ print(copyright, file=w)
+ print('#include <cstdio>', file=w)
+ print('// just mention "fwd.hpp" and "../poison.hpp" to make formatter happy', file=w)
+ print('namespace tmwa', file=w)
+ print('{', file=w)
+ print(' void do_nothing_asan_is_funny_global_constructor();', file=w)
+ print(' void do_nothing_asan_is_funny_global_constructor() {}', file=w)
+ print('} // namespace tmwa', file=w)
+ print(file=w)
+ print('template<class T>', file=w)
+ print('__attribute__((noinline))', file=w)
+ print('void do_breakpoint(const T& value, const char *expected)', file=w)
+ print('{', file=w)
+ print(' (void)value;', file=w)
+ print(' (void)expected;', file=w)
+ print(' if (!expected) printf("printer test: %p = %s\\n", &value, expected);', file=w)
+ print('}', file=w)
+ print(file=w)
+ print('// Tests from', a, file=w)
+ header = basename + '.hpp'
+ print('#include "%s"' % header, file=w)
+
+ for (k, tests, extra) in get_classes_from_file(a):
+ print(file=w)
+ print('// Tests for', k, file=w)
+ print(extra, file=w)
+ for (i, (expr, expected)) in enumerate(tests):
+ name = 'testset_%s_subtest_%d' % (k, i)
+ gen_test(name, expr, expected, w)
+ names.append(name)
+ print('int main()', file=w)
+ print('{', file=w)
+ for n in names:
+ print(' %s();' % n, file=w)
+ print('}', file=w)
+
+ for g in glob.glob(os.path.join(outdir, '*.old')):
+ print('Obsolete: %s' % g)
+ os.remove(g)
+
if error and not os.getenv('TMWA_FORCE_GENERATE'):
sys.exit(1)
diff --git a/tools/debug-debug.gdb b/tools/debug-debug.gdb
index 72a45e1..79046c6 100644
--- a/tools/debug-debug.gdb
+++ b/tools/debug-debug.gdb
@@ -5,8 +5,8 @@ try:
gdb.execute('set auto-load safe-path /')
except:
pass
+gdb.execute('file %s' % file_to_load)
end
-file bin/test-debug-debug
set logging file /dev/null
set logging redirect on
set logging off
@@ -19,7 +19,7 @@ def hit_breakpoint():
sys.stdout.write('.')
value = str(gdb.parse_and_eval('*&value'))
expected = gdb.parse_and_eval('expected').string()
- if expected.startswith('regex:'):
+ if False and expected.startswith('regex:'):
def compare(value, expected):
m = re.match(expected[6:], value)
return m and m.end() == m.endpos
@@ -39,6 +39,7 @@ end
set print static-members off
set print elements 9999
set print frame-arguments none
+set python print-stack full
set logging on
rbreak do_breakpoint
diff --git a/tools/nightly b/tools/nightly
index e20cdd5..1f7ecc7 100755
--- a/tools/nightly
+++ b/tools/nightly
@@ -1,61 +1,126 @@
#!/bin/bash -e
+#
+# The buildbot script is only intended to run on Debian amd64
+#
WWW=${HOME}/www/
-ALL_CONFIGS=(
- config64
- config32
+BUILD=x86_64-linux-gnu
+
+ALL_HOSTS=(
+ x86_64-linux-gnu
+ i586-linux-gnu
+# x32 requires a recent kernel with appropriate flags.
+# On Debian, boot with 'syscall.x32=y' on the kernel command line
+# (see GRUB_CMDLINE_LINUX in /etc/default/grub)
+ #x86_64-linux-gnux32
+# Cross arches can be built by enabling multiarch, then installing
+# the g++-$ARCH package.
+# Executables can be run via qemu-user, but gdb tests must be disabled.
+# Bug 762073 notes impossible coinstallation of mips, mipsel, and powerpc.
+# dpkg-divert can only fix *one* of them.
+ #aarch64-linux-gnu
+ #arm-linux-gnueabi
+ #arm-linux-gnueabihf
+ #mips-linux-gnu
+ #mipsel-linux-gnu
+ #powerpc-linux-gnu
)
-# need a special kernel for configx32
+config---help() {
+ set +x
+ echo 'Usage: tools/nightly <list of arches>'
+ echo 'Arches are:'
+ echo x86_64-linux-gnu
+ echo i586-linux-gnu
+ echo x86_64-linux-gnux32
+ echo aarch64-linux-gnu
+ echo arm-linux-gnueabi
+ echo arm-linux-gnueabihf
+ echo mips-linux-gnu
+ echo mipsel-linux-gnu
+ echo powerpc-linux-gnu
+ exit
+}
+
+common-config() {
+ # HOST is set by the calling function
+ CXX=$HOST-g++
+ EXTRA_LIBS=(
+ /lib/$HOST/libc.so.6
+ /lib/$HOST/libm.so.6
+ /lib/$HOST/libgcc_s.so.1
+ /usr/lib/$HOST/libstdc++.so.6
+ )
+ GDB=/bin/true
+}
-config32() {
+
+config-x86_64-linux-gnu () {
+ common-config
+ GDB=gdb
+}
+
+config-i586-linux-gnu () {
# No one knows what number this is supposed to be:
# - the lib directory is called i386-linux-gnu
# - the 32-bit files are called i486-linux-gnu
# - the cross-32 configury says i586
- HOST=i586-linux-gnu
- CXX='g++ -m32'
+ CXX=$BUILD-'g++ -m32'
EXTRA_LIBS=(
/lib32/libc.so.6
/lib32/libm.so.6
/usr/lib32/libgcc_s.so.1
/usr/lib32/libstdc++.so.6
)
+ GDB=gdb
}
-configx32() {
- HOST=x86_64-linux-gnux32
- CXX='g++ -mx32'
+config-x86_64-linux-gnux32 () {
+ CXX=$BUILD-'g++ -mx32'
EXTRA_LIBS=(
/libx32/libc.so.6
/libx32/libm.so.6
/usr/libx32/libgcc_s.so.1
/usr/libx32/libstdc++.so.6
)
+ GDB=gdb
}
-config64() {
- HOST=x86_64-linux-gnu
- CXX='g++ -m64'
- EXTRA_LIBS=(
- /lib/x86_64-linux-gnu/libc.so.6
- /lib/x86_64-linux-gnu/libm.so.6
- /lib/x86_64-linux-gnu/libgcc_s.so.1
- /usr/lib/x86_64-linux-gnu/libstdc++.so.6
- )
+config-aarch64-linux-gnu () {
+ common-config
+}
+
+config-arm-linux-gnueabi () {
+ common-config
+}
+
+config-arm-linux-gnueabihf () {
+ common-config
+}
+
+config-mips-linux-gnu () {
+ common-config
+}
+
+config-mipsel-linux-gnu () {
+ common-config
+}
+
+config-powerpc-linux-gnu () {
+ common-config
}
first=true
set -x
-for config in ${ALL_CONFIGS[@]}
+for HOST in ${@:-${ALL_HOSTS[@]}}
do
- $config
+ config-$HOST
mkdir -p build-$HOST
cd build-$HOST
- ../configure --host=$HOST CXX="$CXX -Wno-deprecated-declarations" --prefix=/. --enable-rpath=relative
- rm -rf bin lib
+ ../configure --build=$BUILD --host=$HOST CXX="$CXX -Wno-deprecated-declarations -DQUIET" --prefix=/. --enable-rpath=relative GDB=$GDB
+ rm -rf bin lib stamp
make -j3 all
make -j3 test
if $first
diff --git a/tools/protocol.py b/tools/protocol.py
index 3927b0c..7547bc6 100755
--- a/tools/protocol.py
+++ b/tools/protocol.py
@@ -20,11 +20,40 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from __future__ import print_function
+
import glob
import itertools
import os
from pipes import quote
from posixpath import relpath
+from weakref import ref as wr
+
+try:
+ unicode
+except NameError:
+ unicode = str
+
+## For various reasons this is all one file, so let's navigate with a
+##
+## Table of Contents
+##
+## TOC_HEAD
+## TOC_TYPES
+## TOC_EVENT
+## TOC_CHAN
+## TOC_DATA
+## TOC_GENTYPE
+## TOC_CLIENT
+## TOC_LOGINCHAR
+## TOC_CHARMAP
+## TOC_INTERMAP
+## TOC_MISC
+## TOC_LOGINADMIN
+## TOC_NEW
+## TOC_DRAWING
+## TOC_MAIN
+##
# The following code should be relatively easy to understand, but please
# keep your sanity fastened and your arms and legs inside at all times.
@@ -32,6 +61,9 @@ from posixpath import relpath
# important note: all numbers in this file will make a lot more sense in
# decimal, but they're written in hex.
+
+# TOC_HEAD
+
generated = '// This is a generated file, edit %s instead\n' % __file__
copyright = '''// {filename} - {description}
@@ -67,9 +99,9 @@ class OpenWrite(object):
if ty is not None:
return
frag = '''
- if cmp {0}.tmp {0}.old
+ if cmp -s {0}.tmp {0}.old
then
- echo Unchanged: {0}
+ : echo Unchanged: {0}
rm {0}.tmp
mv {0}.old {0}
else
@@ -80,14 +112,210 @@ class OpenWrite(object):
'''.format(quote(self.filename))
os.system(frag)
+
+# TOC_
+
+def gvq(s):
+ return u'"%s"' % s.replace(u'"', u'\\"')
+
+def gva(d):
+ if d:
+ return u' [%s]' % u', '.join(u'%s=%s' % (ak, gvq(av)) for (ak, av) in sorted(d.items()))
+ return u''
+
+class Attributes(object):
+ __slots__ = (u'_attributes')
+
+ def __init__(self):
+ self._attributes = {}
+
+ def __getitem__(self, k):
+ assert isinstance(k, unicode)
+ return self._attributes[k]
+
+ def __setitem__(self, k, v):
+ assert isinstance(k, unicode)
+ assert isinstance(v, unicode)
+ self._attributes[k] = v
+
+ def __delitem__(self, k):
+ assert isinstance(k, unicode)
+ del self._attributes[k]
+
+ def merge(self, *others):
+ for other in others:
+ assert self.__class__ == other.__class__
+ # if an attribute is present on multiple inputs, prefer
+ # the last one. This is not necessarily what you want, though.
+ self._attributes.update(other._attributes)
+
+class Graph(Attributes):
+ __slots__ = (u'default_vertex', u'default_edge', u'_vertices', u'_edges', u'_vertex_lookup')
+
+ def __init__(self):
+ Attributes.__init__(self)
+ self.default_vertex = Attributes()
+ self.default_edge = Attributes()
+ self._vertices = set()
+ self._edges = {}
+ self._vertex_lookup = {}
+
+ def vertex(self, name, insert=True):
+ assert isinstance(name, unicode)
+ vert = self._vertex_lookup.get(name)
+ if insert and vert is None:
+ vert = Vertex(name)
+ self._vertex_lookup[name] = vert
+ self._vertices.add(vert)
+ return vert
+
+ def _fix_vertex(self, v, insert=True):
+ if isinstance(v, Vertex):
+ return v
+ return self.vertex(v, insert)
+
+ def edge(self, v1, v2, insert=True):
+ v1 = self._fix_vertex(v1, insert)
+ if v1 is None:
+ return None
+ v2 = self._fix_vertex(v2, insert)
+ if v2 is None:
+ return None
+ ek = (v1, v2)
+ edge = self._edges.get(ek)
+ if insert and edge is None:
+ edge = Edge(v1, v2)
+ v1._post.add(wr(v2))
+ v2._pre.add(wr(v1))
+ self._edges[ek] = edge
+ return edge
+
+ def del_edge(self, v1, v2):
+ edge = self.edge(v1, v2, False)
+ if edge is None:
+ return
+ v1 = edge._from
+ v2 = edge._to
+ ek = (v1, v2)
+ v1._post.remove(wr(v2))
+ v2._pre.remove(wr(v1))
+ del self._edges[ek]
+
+ def del_vertex(self, v):
+ v = self._fix_vertex(v, False)
+ if v is None:
+ return
+ pre = list(v._pre)
+ for vp in pre:
+ vp = vp()
+ self.del_edge(vp, v)
+ post = list(v._post)
+ for vp in post:
+ vp = vp()
+ self.del_edge(v, vp)
+ del self._vertex_lookup[v._key]
+ self._vertices.remove(v)
+
+ def splice_out_vertex(self, v):
+ v = self._fix_vertex(v, False)
+ if v is None:
+ return
+ self.del_edge(v, v)
+ for vp in v._pre:
+ vp = vp()
+ ep = self.edge(vp, v, False)
+ for vn in v._post:
+ vn = vn()
+ en = self.edge(v, vn, False)
+ self.edge(vp, vn).merge(ep, en)
+ self.del_vertex(v)
+
+ def dot(self, out, close):
+ if close:
+ with out:
+ self.dot(out, False)
+ return
+
+ def p(*args):
+ for x in args:
+ out.write(unicode(x))
+ out.write(u'\n')
+ p(u'digraph')
+ p(u'{')
+ for ak, av in sorted(self._attributes.items()):
+ p(u' ', ak, u'=', gvq(av), u';')
+ for ak, av in sorted(self.default_vertex._attributes.items()):
+ p(u' node [', ak, u'=', gvq(av), u'];')
+ for ak, av in sorted(self.default_edge._attributes.items()):
+ p(u' edge [', ak, u'=', gvq(av), u'];')
+ for n in sorted(self._vertices):
+ p(u' ', n)
+ for _, e in sorted(self._edges.items()):
+ p(u' ', e)
+ p(u'}')
+
+ def dot_str(self):
+ from io import StringIO
+ out = StringIO()
+ self.dot(out, False)
+ return out.getvalue()
+
+ def dot_file(self, name):
+ with open(name, u'w') as f:
+ self.dot(f, False)
+
+ def preview(self, block):
+ from subprocess import Popen, PIPE
+ proc = Popen([u'dot', u'-Txlib', u'/dev/stdin'], stdin=PIPE, universal_newlines=True)
+ self.dot(proc.stdin, True)
+ if block:
+ proc.wait()
+
+class Vertex(Attributes):
+ __slots__ = (u'_key', u'_post', u'_pre', u'__weakref__')
+
+ def __init__(self, key):
+ Attributes.__init__(self)
+ self._key = key
+ self._post = set()
+ self._pre = set()
+
+ def __str__(self):
+ return u'%s%s;' % (gvq(self._key), gva(self._attributes))
+
+ def __lt__(self, other):
+ return self._key < other._key
+
+class Edge(Attributes):
+ __slots__ = (u'_from', u'_to')
+
+ def __init__(self, f, t):
+ Attributes.__init__(self)
+ self._from = f
+ self._to = t
+
+ def __str__(self):
+ return u'%s -> %s%s;' % (gvq(self._from._key), gvq(self._to._key), gva(self._attributes))
+
+
+# TOC_TYPES
+
class LowType(object):
- __slots__ = ()
+ __slots__ = ('includes')
+
+ def __repr__(self):
+ return '%s(%s)' % (type(self).__name__, self.name)
class NativeType(LowType):
__slots__ = ('name')
- def __init__(self, name):
+ def __init__(self, name, include):
self.name = name
+ self.includes = frozenset({include}) if include else frozenset()
+
+ def __repr__(self):
+ return 'NativeType(%r)' % (self.name)
+
def a_tag(self):
return self.name
@@ -95,21 +323,41 @@ class NativeType(LowType):
class NetworkType(LowType):
__slots__ = ('name')
- def __init__(self, name):
+ def __init__(self, name, include):
self.name = name
+ self.includes = frozenset({include}) if include else frozenset()
+
+ def __repr__(self):
+ return 'NetworkType(%r)' % (self.name)
+
def e_tag(self):
return self.name
class Type(object):
- __slots__ = ()
+ __slots__ = ('includes', 'do_dump')
+
+ def __new__(cls, *args):
+ rv = object.__new__(cls)
+ rv.do_dump = True
+ return rv
class NeutralType(Type):
__slots__ = ('name')
- def __init__(self, name):
+ def __init__(self, name, include):
self.name = name
+ identity = '#include "../proto-base/net-neutral.hpp"\n'
+ self.includes = frozenset({include, identity}) if include else frozenset({identity})
+
+ def __repr__(self):
+ return 'NeutralType(%r)' % (self.name)
+
+
+ def __repr__(self):
+ return 'NeutralType(%r)' % (self.name)
+
def native_tag(self):
return self.name
@@ -119,11 +367,52 @@ class NeutralType(Type):
e_tag = network_tag
+class PolyFakeType(object):
+ __slots__ = ('already', 'do_dump', 'nat_tag')
+
+ def __init__(self, already):
+ self.already = already = tuple(already)
+ self.do_dump = True
+
+ assert len(already) >= 2
+ self.nat_tag = nat_tag = already[0].native_tag()
+ for a in self.already:
+ a.do_dump = False
+ assert nat_tag == a.native_tag()
+
+ def __repr__(self):
+ return 'PolyFakeType(*%r)' % (self.already,)
+
+
+ def native_tag(self):
+ return self.nat_tag
+
+ def add_headers_to(self, headers):
+ for a in self.already:
+ a.add_headers_to(headers)
+
+ def dump_fwd(self, f):
+ for a in self.already:
+ a.dump_fwd(f)
+
+ def dump(self, f):
+ for a in self.already:
+ a.dump(f)
+
class StringType(Type):
__slots__ = ('native')
def __init__(self, native):
self.native = native
+ self.includes = native.includes | {'#include "../proto-base/net-string.hpp"\n'}
+
+ def __repr__(self):
+ return 'StringType(%r)' % (self.native)
+
+
+ def __repr__(self):
+ return 'StringType(%r)' % self.native
+
def native_tag(self):
return self.native.a_tag()
@@ -131,9 +420,9 @@ class StringType(Type):
def network_tag(self):
return 'NetString<sizeof(%s)>' % self.native.a_tag()
- def dump(self, f):
- # not implemented properly, uses a template in the meta instead
- pass
+ # not implemented properly, uses a template in net-string.hpp instead
+ #do_dump = False
+ # in Context.string() instead because reasons
class ProvidedType(Type):
__slots__ = ('native', 'network')
@@ -141,6 +430,15 @@ class ProvidedType(Type):
def __init__(self, native, network):
self.native = native
self.network = network
+ self.includes = native.includes | network.includes
+
+ def __repr__(self):
+ return 'ProvidedType(%r, %r)' % (self.native, self.network)
+
+
+ def __repr__(self):
+ return 'ProvidedType(native=%r, network=%r)' % (self.native, self.network)
+
def native_tag(self):
return self.native.a_tag()
@@ -154,6 +452,16 @@ class EnumType(Type):
def __init__(self, native, under):
self.native = native
self.under = under
+ name = native.a_tag()
+ self.includes = frozenset({'#include "net-%s.hpp"\n' % name})
+
+ def __repr__(self):
+ return 'EnumType(%r, %r)' % (self.native, self.under)
+
+
+ def __repr__(self):
+ return 'EnumType(%r, %r)' % (self.native, self.under)
+
def native_tag(self):
return self.native.a_tag()
@@ -161,6 +469,14 @@ class EnumType(Type):
def network_tag(self):
return self.under.network_tag()
+ def add_headers_to(self, headers):
+ headers.update(self.native.includes)
+ headers.update(self.under.includes)
+
+ def dump_fwd(self, f):
+ # nothing to see here
+ pass
+
def dump(self, f):
native = self.native_tag()
under = self.under.native_tag()
@@ -189,6 +505,16 @@ class WrappedType(Type):
def __init__(self, native, under):
self.native = native
self.under = under
+ name = self.native.a_tag()
+ self.includes = native.includes | under.includes | {'#include "net-%s.hpp"\n' % name}
+
+ def __repr__(self):
+ return 'WrappedType(%r, %r)' % (self.native, self.under)
+
+
+ def __repr__(self):
+ return 'WrappedType(%r, %r)' % (self.native, self.under)
+
def native_tag(self):
return self.native.a_tag()
@@ -196,6 +522,14 @@ class WrappedType(Type):
def network_tag(self):
return self.under.network_tag()
+ def add_headers_to(self, headers):
+ headers.update(self.native.includes)
+ headers.update(self.under.includes)
+
+ def dump_fwd(self, f):
+ # nothing to see here
+ pass
+
def dump(self, f):
native = self.native_tag()
under = self.under.native_tag()
@@ -223,6 +557,11 @@ class SkewLengthType(Type):
def __init__(self, ty, skew):
self.type = ty
self.skew = skew
+ self.includes = ty.includes | {'#include "../proto-base/net-skewed-length.hpp"\n'}
+
+ def __repr__(self):
+ return 'SkewLengthType(%r, %r)' % (self.ty, self.skew)
+
def native_tag(self):
return self.type.native_tag()
@@ -230,6 +569,10 @@ class SkewLengthType(Type):
def network_tag(self):
return 'SkewedLength<%s, %d>' % (self.type.network_tag(), self.skew)
+ def dumpx_fwd(self, f):
+ # nothing to see here
+ pass
+
def dumpx(self):
# not registered properly, so the method name is wrong
# TODO remind myself to kill this code with fire before release
@@ -246,6 +589,17 @@ class StructType(Type):
self.fields = fields
self.size = size
self.ctor = ctor
+ # only used if id is None
+ # internally uses add_headers_to()
+ self.includes = frozenset({'#include "net-%s.hpp"\n' % name})
+
+ def __repr__(self):
+ return '<StructType(%r) with %d fields>' % (self.name, len(self.fields))
+
+
+ def __repr__(self):
+ return '<StructType(id=%r, name=%r, size=%r, ctor=%r) with %d fields>' % (self.id, self.name, self.size, self.ctor, len(self.fields))
+
def native_tag(self):
return self.name
@@ -253,6 +607,13 @@ class StructType(Type):
def network_tag(self):
return 'Net' + self.name
+ def add_headers_to(self, headers):
+ # only used directly if id is not None
+ # used indirectly if id is None
+ for (o, l, n) in self.fields:
+ headers.update(l.includes)
+ headers.add('#include <cstddef>\n') # for offsetof
+
def dump(self, f):
self.dump_native(f)
self.dump_network(f)
@@ -262,7 +623,9 @@ class StructType(Type):
def dump_fwd(self, fwd):
if self.id is not None:
fwd.write('template<>\n')
+ assert False
fwd.write('struct %s;\n' % self.name)
+ return
if self.id is not None:
fwd.write('template<>\n')
fwd.write('struct Net%s;\n' % self.name)
@@ -331,6 +694,16 @@ class PartialStructType(Type):
def __init__(self, native, body):
self.native = native
self.body = body
+ name = native.a_tag()
+ self.includes = frozenset({'#include "net-%s.hpp"\n' % name})
+
+ def __repr__(self):
+ return '<PartialStructType(%r) with %d fields>' % (self.native, len(self.body))
+
+
+ def __repr__(self):
+ return '<PartialStructType(native=%r) with %d pieces of a body>' % (self.native, len(self.body))
+
def native_tag(self):
return self.native.a_tag()
@@ -338,6 +711,15 @@ class PartialStructType(Type):
def network_tag(self):
return 'Net%s' % self.native_tag()
+ def add_headers_to(self, headers):
+ headers.update(self.native.includes)
+ for n, t in self.body:
+ headers.update(t.includes)
+
+ def dump_fwd(self, f):
+ # nothing to see here
+ pass
+
def dump(self, f):
f.write('struct %s\n{\n' % self.network_tag())
for n, t in self.body:
@@ -371,6 +753,11 @@ class ArrayType(Type):
def __init__(self, element, count):
self.element = element
self.count = count
+ self.includes = element.includes | {'#include "../proto-base/net-array.hpp"\n'}
+
+ def __repr__(self):
+ return 'ArrayType(%r, %r)' % (self.element, self.count)
+
def native_tag(self):
return 'Array<%s, %s>' % (self.element.native_tag(), self.count)
@@ -385,6 +772,11 @@ class EArrayType(Type):
self.element = element
self.index = index
self.count = count
+ self.includes = element.includes | {'#include "../proto-base/net-array.hpp"\n'}
+
+ def __repr__(self):
+ return 'EArrayType(%r, %r)' % (self.element, self.index, self.count)
+
def native_tag(self):
return 'earray<%s, %s, %s>' % (self.element.native_tag(), self.index, self.count)
@@ -399,6 +791,15 @@ class InvArrayType(Type):
self.element = element
self.index = index
self.count = count
+ self.includes = element.includes | {
+ '#include "../proto-base/net-array.hpp"\n',
+ '#include "../mmo/clif.t.hpp"\n',
+ '#include "../mmo/consts.hpp"\n',
+ }
+
+ def __repr__(self):
+ return 'InvArrayType(%r, %r)' % (self.element, self.index, self.count)
+
def native_tag(self):
return 'GenericArray<%s, InventoryIndexing<%s, %s>>' % (self.element.native_tag(), self.index, self.count)
@@ -407,6 +808,47 @@ class InvArrayType(Type):
return 'NetArray<%s, %s>' % (self.element.network_tag(), self.count)
+# TOC_EVENT
+# special event origins
+class SpecialEventOrigin(object):
+ __slots__ = ('name')
+
+ def __init__(self, name):
+ self.name = name
+
+ def __repr__(self):
+ return self.name
+
+ADMIN = SpecialEventOrigin('ADMIN')
+BOOT = SpecialEventOrigin('BOOT')
+FINISH = SpecialEventOrigin('FINISH')
+GM = SpecialEventOrigin('GM')
+HUMAN = SpecialEventOrigin('HUMAN')
+IDLE = SpecialEventOrigin('IDLE')
+MAGIC = SpecialEventOrigin('MAGIC')
+NOTHING = SpecialEventOrigin('NOTHING')
+OTHER = SpecialEventOrigin('OTHER')
+PRETTY = SpecialEventOrigin('PRETTY')
+SCRIPT = SpecialEventOrigin('SCRIPT')
+TIMER = SpecialEventOrigin('TIMER')
+
+def event_name(p):
+ if isinstance(p, SpecialEventOrigin):
+ return p.name
+ if isinstance(p, int):
+ return 'packet 0x%04x' % p
+ assert False, 'Unknown event: %r' % p
+
+def event_link(p):
+ if isinstance(p, SpecialEventOrigin):
+ return p.name
+ if isinstance(p, int):
+ return '[[Packet 0x%04x]]' % p
+ assert False, 'Unknown event: %r' % p
+
+
+# TOC_CHAN
+
class Include(object):
__slots__ = ('path', '_types')
@@ -414,6 +856,10 @@ class Include(object):
self.path = path
self._types = []
+ def __repr__(self):
+ return '<Include(%r) with %d types>' % (self.path, len(self._types))
+
+
def testcase(self, outdir):
basename = os.path.basename(self.path.strip('<">'))
root = os.path.splitext(basename)[0]
@@ -426,41 +872,133 @@ class Include(object):
f.write('\n')
f.write('#include "%s"\n\nnamespace tmwa\n{\n' % poison)
- for t in self._types:
- f.write('using %s = %s;\n' % ('Test_' + ident(t.name), t.name))
- f.write('} // namespace tmwa\n')
def pp(self, n):
return '#%*sinclude %s\n' % (n, '', self.path)
-
def native(self, name):
- ty = NativeType(name)
+ ty = NativeType(name, self.pp(0))
self._types.append(ty)
return ty
def network(self, name):
- ty = NetworkType(name)
+ ty = NetworkType(name, self.pp(0))
self._types.append(ty)
return ty
def neutral(self, name):
- ty = NeutralType(name)
+ ty = NeutralType(name, self.pp(0))
self._types.append(ty)
return ty
+class BasePacket(object):
+ __slots__ = ('id', 'name', 'define', 'pre', 'post', 'xpost', 'desc')
+
+ def __init__(self, **kwargs):
+ for s in BasePacket.__slots__:
+ setattr(self, s, kwargs[s])
+ del kwargs[s]
+ assert not kwargs, 'Unknown kwargs: %s' % repr(kwargs)
+
+ def base_repr_fragment(self):
+ return ', '.join('%s=%r' % (s, getattr(self, s)) for s in BasePacket.__slots__)
-class FixedPacket(object):
+ def comment_doc(self):
+ id = self.id
+ name = self.name
+ define = self.define
+ desc = self.desc
+ pre = self.pre
+ post = self.post
+
+ comment = 'Packet 0x%04x: "%s"\n' % (id, name)
+ if define:
+ comment += 'define: ' + define + '\n'
+ if True:
+ prestr = ', '.join(event_name(x) for x in pre) or 'none'
+ poststr = ', '.join(event_name(x) for x in post) or 'none'
+ comment += 'pre: ' + prestr + '\n'
+ comment += 'post: ' + poststr + '\n'
+ comment += desc
+ comment = ''.join('// ' + c + '\n' if c else '//\n' for c in comment.split('\n'))
+ return comment
+
+ def wiki_doc(self):
+ # TODO do markdown magic
+ id = self.id
+ name = self.name
+ desc = self.desc
+ pre = self.pre
+ post = self.post
+ xpost = self.xpost
+
+ wiki = 'Packet 0x%04x: "%s"\n\n' % (id, name)
+ if True:
+ prestr = ', '.join(event_link(x) for x in pre) or 'none'
+ poststr = ', '.join(event_link(x) for x in fix_sort(post + xpost)) or 'none'
+ wiki += 'pre: ' + prestr + '\n\n'
+ wiki += 'post: ' + poststr + '\n\n'
+ wiki += desc
+ wiki += '\n\n'
+ wiki += '![](packets-around-0x%04x.png)\n' % id
+ return wiki
+
+ def pre_set(self, d, n=float('inf'), accum=None):
+ if accum is None:
+ accum = set()
+ if self.id in accum:
+ return accum
+ accum.add(self.id)
+ if not n:
+ return accum
+ for p in self.pre:
+ # ignore specials
+ if isinstance(p, SpecialEventOrigin):
+ continue
+ # ignore weak links
+ if self.id not in d[p].post:
+ continue
+ d[p].pre_set(d, n - 1 + (len([z for z in self.pre if isinstance(z, SpecialEventOrigin) or self.id in d[z].post]) == 1), accum)
+ return accum
+
+ def post_set(self, d, n=float('inf'), accum=None):
+ if accum is None:
+ accum = set()
+ if self.id in accum:
+ return accum
+ accum.add(self.id)
+ if not n:
+ return accum
+ for p in self.post:
+ # ignore specials
+ if isinstance(p, SpecialEventOrigin):
+ continue
+ # weak links are in xpost
+
+ d[p].post_set(d, n - 1 + (len(self.post) == 1), accum)
+ return accum
+
+
+class FixedPacket(BasePacket):
__slots__ = ('fixed_struct')
- def __init__(self, fixed_struct):
+ def __init__(self, fixed_struct, **kwargs):
+ BasePacket.__init__(self, **kwargs)
self.fixed_struct = fixed_struct
+ def __repr__(self):
+ return 'FixedPacket(%r, %s)' % (self.fixed_struct, self.base_repr_fragment())
+
+
+ def add_headers_to(self, headers):
+ self.fixed_struct.add_headers_to(headers)
+
def dump_fwd(self, fwd):
self.fixed_struct.dump_fwd(fwd)
fwd.write('\n')
def dump_native(self, f):
+ f.write(self.comment_doc())
self.fixed_struct.dump_native(f)
f.write('\n')
@@ -472,19 +1010,29 @@ class FixedPacket(object):
self.fixed_struct.dump_convert(f)
f.write('\n')
-class VarPacket(object):
+class VarPacket(BasePacket):
__slots__ = ('head_struct', 'repeat_struct')
- def __init__(self, head_struct, repeat_struct):
+ def __init__(self, head_struct, repeat_struct, **kwargs):
+ BasePacket.__init__(self, **kwargs)
self.head_struct = head_struct
self.repeat_struct = repeat_struct
+ def __repr__(self):
+ return 'VarPacket(%r, %r, %s)' % (self.head_struct, self.repeat_struct, self.base_repr_fragment())
+
+
+ def add_headers_to(self, headers):
+ self.head_struct.add_headers_to(headers)
+ self.repeat_struct.add_headers_to(headers)
+
def dump_fwd(self, fwd):
self.head_struct.dump_fwd(fwd)
self.repeat_struct.dump_fwd(fwd)
fwd.write('\n')
def dump_native(self, f):
+ f.write(self.comment_doc())
self.head_struct.dump_native(f)
self.repeat_struct.dump_native(f)
f.write('\n')
@@ -499,40 +1047,76 @@ class VarPacket(object):
self.repeat_struct.dump_convert(f)
f.write('\n')
-def packet(id, name,
+def sanitize_line(line, n):
+ if not line:
+ return line
+ m = len(line) - len(line.lstrip(' '))
+ assert m >= n, 'not %d: %r' % (n, line)
+ return line[n:]
+
+def sanitize_multiline(text):
+ text = text.strip('\n').rstrip(' ')
+ assert '\r' not in text
+ assert '\t' not in text
+ n = len(text) - len(text.lstrip(' '))
+ return '\n'.join(sanitize_line(l, n) for l in text.split('\n'))
+
+def packet(id, name, define=None,
fixed=None, fixed_size=None,
payload=None, payload_size=None,
head=None, head_size=None,
repeat=None, repeat_size=None,
option=None, option_size=None,
+ pre=None, post=None, xpost=None, desc=None,
):
assert (fixed is None) <= (fixed_size is None)
assert (payload is None) <= (payload_size is None)
assert (head is None) <= (head_size is None)
assert (repeat is None) <= (repeat_size is None)
assert (option is None) <= (option_size is None)
+ assert (pre is not None) and (post is not None) and desc.strip()
+ if xpost is None:
+ xpost = []
+
+ desc = sanitize_multiline(desc)
if fixed is not None:
assert not head and not repeat and not option and not payload
return FixedPacket(
- StructType(id, 'Packet_Fixed<0x%04x>' % id, fixed, fixed_size))
+ StructType(id, 'Packet_Fixed<0x%04x>' % id, fixed, fixed_size),
+ id=id, name=name, define=define, pre=pre, post=post, xpost=xpost, desc=desc,
+ )
elif payload is not None:
assert not head and not repeat and not option
return FixedPacket(
- StructType(id, 'Packet_Payload<0x%04x>' % id, payload, payload_size))
+ StructType(id, 'Packet_Payload<0x%04x>' % id, payload, payload_size),
+ id=id, name=name, define=define, pre=pre, post=post, xpost=xpost, desc=desc,
+ )
else:
assert head
if option:
+ assert not repeat
return VarPacket(
StructType(id, 'Packet_Head<0x%04x>' % id, head, head_size),
- StructType(id, 'Packet_Option<0x%04x>' % id, option, option_size))
+ StructType(id, 'Packet_Option<0x%04x>' % id, option, option_size),
+ id=id, name=name, define=define, pre=pre, post=post, xpost=xpost, desc=desc,
+ )
else:
assert repeat
return VarPacket(
StructType(id, 'Packet_Head<0x%04x>' % id, head, head_size),
- StructType(id, 'Packet_Repeat<0x%04x>' % id, repeat, repeat_size))
-
-
+ StructType(id, 'Packet_Repeat<0x%04x>' % id, repeat, repeat_size),
+ id=id, name=name, define=define, pre=pre, post=post, xpost=xpost, desc=desc,
+ )
+
+
+# TODO this whole idea is wrong
+# in particular, 'any'.
+# instead, have just one channel for all packets, but store send/recv/type
+# with each packet.
+# Then during codegen, emit:
+# #if PACKET_LOGIN || PACKET_CHAR || PACKET_MAP || PACKET_ADMIN || PACKET_USER
+# (for an 'all' packet) around the send/recv portions separately
class Channel(object):
__slots__ = ('server', 'client', 'packets')
@@ -541,18 +1125,29 @@ class Channel(object):
self.client = client
self.packets = []
+ def __repr__(self):
+ return '<Channel(%r, %r) with %d packets>' % (
+ self.server,
+ self.client,
+ len(self.packets),
+ )
+
+
def x(self, id, name, **kwargs):
self.packets.append(packet(id, name, **kwargs))
r = x
s = x
- def dump(self, outdir, fwd):
+ def dump(self, outdir):
server = self.server
client = self.client
header = '%s-%s.hpp' % (server, client)
- test = '%s-%s_test.cpp' % (server, client)
desc = 'TMWA network protocol: %s/%s' % (server, client)
with OpenWrite(os.path.join(outdir, header)) as f:
+ type_headers = set()
+ for p in self.packets:
+ p.add_headers_to(type_headers)
+ type_headers = sorted(type_headers)
proto2 = relpath(outdir, 'src')
f.write('#pragma once\n')
f.write(copyright.format(filename=header, description=desc))
@@ -560,7 +1155,9 @@ class Channel(object):
f.write(generated)
f.write('\n')
f.write('#include "fwd.hpp"\n\n')
- f.write('#include "types.hpp"\n')
+ f.write('// sorry these are not ordered by hierarchy\n')
+ for h in type_headers:
+ f.write(h)
f.write('\n')
f.write('namespace tmwa\n{\n')
if client == 'user':
@@ -569,9 +1166,6 @@ class Channel(object):
f.write('// This is an internal protocol, and can be changed without notice\n')
f.write('\n')
for p in self.packets:
- p.dump_fwd(fwd)
- fwd.write('\n')
- for p in self.packets:
p.dump_native(f)
f.write('\n')
for p in self.packets:
@@ -581,16 +1175,6 @@ class Channel(object):
p.dump_convert(f)
f.write('} // namespace tmwa\n')
- with OpenWrite(os.path.join(outdir, test)) as f:
- poison = relpath('src/poison.hpp', outdir)
- f.write('#include "%s"\n' % header)
- f.write(copyright.format(filename=test, description=desc))
- f.write('\n')
- f.write(generated)
- f.write('\n')
- f.write('#include "%s"\n\nnamespace tmwa\n{\n' % poison)
- f.write('} // namespace tmwa\n')
-
ident_translation = ''.join(chr(c) if chr(c).isalnum() else '_' for c in range(256))
@@ -615,6 +1199,14 @@ class Context(object):
self._channels = []
self._types = []
+ def __repr__(self):
+ return '<Context(%r) with %d _includes, %d _channels, %d _types>' % (
+ self.outdir,
+ len(self._includes),
+ len(self._channels),
+ len(self._types),
+ )
+
def sysinclude(self, name):
rv = Include('<%s>' % name)
@@ -638,13 +1230,25 @@ class Context(object):
proto2 = relpath(outdir, 'src')
with OpenWrite(os.path.join(outdir, 'fwd.hpp')) as f:
header = '%s/fwd.hpp' % proto2
- desc = 'Forward declarations of network packets'
+ desc = 'Forward declarations of network packet body structs'
sanity = relpath('src/sanity.hpp', outdir)
+
f.write('#pragma once\n')
f.write(copyright.format(filename=header, description=desc))
f.write('\n')
+
f.write('#include "%s"\n\n' % sanity)
f.write('#include <cstdint>\n\n')
+
+ f.write('#include "../ints/fwd.hpp" // rank 1\n')
+ f.write('#include "../strings/fwd.hpp" // rank 1\n')
+ f.write('#include "../compat/fwd.hpp" // rank 2\n')
+ f.write('#include "../net/fwd.hpp" // rank 5\n')
+ f.write('#include "../mmo/fwd.hpp" // rank 6\n')
+ f.write('#include "../proto-base/fwd.hpp" // rank 7\n')
+ f.write('// proto2/fwd.hpp is rank 8\n')
+ f.write('\n\n')
+
f.write('namespace tmwa\n{\n')
for b in ['Fixed', 'Payload', 'Head', 'Repeat', 'Option']:
c = 'Packet_' + b
@@ -652,143 +1256,57 @@ class Context(object):
f.write('template<uint16_t PACKET_ID> class Net%s;\n' % c)
f.write('\n')
+ for ty in self._types:
+ if not ty.do_dump:
+ continue
+ ty.dump_fwd(f)
for ch in self._channels:
- ch.dump(outdir, f)
+ ch.dump(outdir)
f.write('} // namespace tmwa\n')
- with OpenWrite(os.path.join(outdir, 'types.hpp')) as f:
- header = '%s/types.hpp' % proto2
- desc = 'Forward declarations of packet component types'
- f.write('#pragma once\n')
- f.write(copyright.format(filename=header, description=desc))
- f.write('\n')
- f.write('#include "fwd.hpp"\n\n')
- f.write('#include "../generic/array.hpp"\n')
- f.write('#include "../mmo/consts.hpp"\n')
-
- f.write('\n//TODO split the includes\n')
- for inc in self._includes:
- f.write(inc.pp(0))
- # this is writing another file
- inc.testcase(outdir)
- f.write('\n')
- f.write('namespace tmwa\n{\n')
-
- f.write('template<class T>\n')
- f.write('bool native_to_network(T *network, T native)\n{\n')
- f.write(' *network = native;\n')
- f.write(' return true;\n')
- f.write('}\n')
- f.write('template<class T>\n')
- f.write('bool network_to_native(T *native, T network)\n{\n')
- f.write(' *native = network;\n')
- f.write(' return true;\n')
- f.write('}\n')
-
- f.write('template<class T, size_t N>\n')
- f.write('struct NetArray\n{\n')
- f.write(' T data[N];\n')
- f.write('};\n')
- f.write('template<class T, class U, class I>\n')
- f.write('bool native_to_network(NetArray<T, I::alloc_size> *network, GenericArray<U, I> native)\n{\n')
- f.write(' for (size_t i = 0; i < I::alloc_size; ++i)\n')
- f.write(' {\n')
- f.write(' if (!native_to_network(&(*network).data[i], native[I::offset_to_index(i)]))\n')
- f.write(' return false;\n')
- f.write(' }\n')
- f.write(' return true;\n')
- f.write('}\n')
- f.write('template<class T, class U, class I>\n')
- f.write('bool network_to_native(GenericArray<U, I> *native, NetArray<T, I::alloc_size> network)\n{\n')
- f.write(' for (size_t i = 0; i < I::alloc_size; ++i)\n')
- f.write(' {\n')
- f.write(' if (!network_to_native(&(*native)[I::offset_to_index(i)], network.data[i]))\n')
- f.write(' return false;\n')
- f.write(' }\n')
- f.write(' return true;\n')
- f.write('}\n')
- f.write('\n')
-
- f.write('template<size_t N>\n')
- f.write('struct NetString\n{\n')
- f.write(' char data[N];\n')
- f.write('};\n')
- f.write('template<size_t N>\n')
- f.write('bool native_to_network(NetString<N> *network, VString<N-1> native)\n{\n')
- f.write(' // basically WBUF_STRING\n')
- f.write(' char *const begin = network->data;\n')
- f.write(' char *const end = begin + N;\n')
- f.write(' char *const mid = std::copy(native.begin(), native.end(), begin);\n')
- f.write(' std::fill(mid, end, \'\\0\');\n')
- f.write(' return true;\n')
- f.write('}\n')
- f.write('template<size_t N>\n')
- f.write('bool network_to_native(VString<N-1> *native, NetString<N> network)\n{\n')
- f.write(' // basically RBUF_STRING\n')
- f.write(' const char *const begin = network.data;\n')
- f.write(' const char *const end = begin + N;\n')
- f.write(' const char *const mid = std::find(begin, end, \'\\0\');\n')
- f.write(' *native = XString(begin, mid, nullptr);\n')
- f.write(' return true;\n')
- f.write('}\n')
- f.write('\n')
- f.write('inline\n')
- f.write('bool native_to_network(NetString<24> *network, CharName native)\n{\n')
- f.write(' VString<23> tmp = native.to__actual();\n')
- f.write(' bool rv = native_to_network(network, tmp);\n')
- f.write(' return rv;\n')
- f.write('}\n')
- f.write('inline\n')
- f.write('bool network_to_native(CharName *native, NetString<24> network)\n{\n')
- f.write(' VString<23> tmp;\n')
- f.write(' bool rv = network_to_native(&tmp, network);\n')
- f.write(' *native = stringish<CharName>(tmp);\n')
- f.write(' return rv;\n')
- f.write('}\n')
- f.write('\n')
- f.write('inline\n')
- f.write('bool native_to_network(NetString<16> *network, MapName native)\n{\n')
- f.write(' XString tmp = native;\n')
- f.write(' bool rv = native_to_network(network, VString<15>(tmp));\n')
- f.write(' return rv;\n')
- f.write('}\n')
- f.write('inline\n')
- f.write('bool network_to_native(MapName *native, NetString<16> network)\n{\n')
- f.write(' VString<15> tmp;\n')
- f.write(' bool rv = network_to_native(&tmp, network);\n')
- f.write(' *native = stringish<MapName>(tmp);\n')
- f.write(' return rv;\n')
- f.write('}\n')
- f.write('\n')
-
- f.write('template<class T, size_t N>\n')
- f.write('struct SkewedLength\n{\n')
- f.write(' T data;\n')
- f.write('};\n')
- f.write('template<class T, size_t N, class U>\n')
- f.write('bool native_to_network(SkewedLength<T, N> *network, U native)\n{\n')
- f.write(' native -= N;\n')
- f.write(' return native_to_network(&network->data, native);\n')
- f.write('}\n')
- f.write('template<class T, size_t N, class U>\n')
- f.write('bool network_to_native(U *native, SkewedLength<T, N> network)\n{\n')
- f.write(' bool rv = network_to_native(native, network.data);\n')
- f.write(' *native += N;\n')
- f.write(' return rv;\n')
- f.write('}\n')
- f.write('\n')
-
- for ty in self._types:
+ for ty in self._types:
+ header = 'net-%s.hpp' % ty.native_tag()
+ if not ty.do_dump:
+ continue
+ type_headers = set()
+ ty.add_headers_to(type_headers)
+ type_headers = sorted(type_headers)
+ with OpenWrite(os.path.join(outdir, header)) as f:
+ header = '%s/%s' % (proto2, header)
+ desc = 'Packet structures and conversions'
+ f.write('#pragma once\n')
+ f.write(copyright.format(filename=header, description=desc))
+ f.write('\n')
+ f.write('#include "fwd.hpp"\n\n')
+
+ for inc in type_headers:
+ f.write(inc)
+ f.write('\n')
+ f.write('namespace tmwa\n{\n')
ty.dump(f)
- f.write('} // namespace tmwa\n')
+ f.write('} // namespace tmwa\n')
for g in glob.glob(os.path.join(outdir, '*.old')):
print('Obsolete: %s' % g)
os.remove(g)
+ def finish_types(self):
+ s = set()
+ for t in self._types:
+ if not t.do_dump:
+ continue
+ n = t.native_tag()
+ assert n not in s, n
+ s.add(n)
+
# types
+ def poly(self, *already):
+ rv = PolyFakeType(already)
+ self._types.append(rv)
+ return rv
+
def provided(self, native, network):
# the whole point of 'provided' is to not be implemented
return ProvidedType(native, network)
@@ -805,6 +1323,7 @@ class Context(object):
def string(self, native):
rv = StringType(native)
+ rv.do_dump = False
self._types.append(rv)
return rv
@@ -833,7 +1352,9 @@ class Context(object):
return rv
-def main():
+# TOC_DATA
+
+def build_context():
## setup
@@ -848,6 +1369,8 @@ def main():
vstring_h = ctx.include('src/strings/vstring.hpp')
+ timet_h = ctx.include('src/compat/time_t.hpp')
+
ip_h = ctx.include('src/net/ip.hpp')
timer_th = ctx.include('src/net/timer.t.hpp')
@@ -856,17 +1379,17 @@ def main():
human_time_diff_h = ctx.include('src/mmo/human_time_diff.hpp')
ids_h = ctx.include('src/mmo/ids.hpp')
strs_h = ctx.include('src/mmo/strs.hpp')
- utils_h = ctx.include('src/mmo/utils.hpp')
+ utils_h = ctx.include('src/net/timestamp-utils.hpp')
version_h = ctx.include('src/mmo/version.hpp')
- login_th = ctx.include('src/login/login.t.hpp')
+ login_th = ctx.include('src/mmo/login.t.hpp')
- clif_th = ctx.include('src/map/clif.t.hpp')
- skill_th = ctx.include('src/map/skill.t.hpp')
+ clif_th = ctx.include('src/mmo/clif.t.hpp')
+ skill_th = ctx.include('src/mmo/skill.t.hpp')
## primitive types
- char = NeutralType('char')
- bit = NeutralType('bool')
+ char = NeutralType('char', None)
+ bit = NeutralType('bool', None)
## included types
@@ -885,7 +1408,7 @@ def main():
Little64 = endians_h.network('Little64')
SEX = enums_h.native('SEX')
- Option = enums_h.native('Option')
+ Opt0 = enums_h.native('Opt0')
EPOS = enums_h.native('EPOS')
ItemLook = enums_h.native('ItemLook')
@@ -905,7 +1428,7 @@ def main():
ip4 = ip_h.neutral('IP4Address')
- TimeT = utils_h.native('TimeT')
+ TimeT = timet_h.native('TimeT')
VString16 = vstring_h.native('VString<15>')
VString20 = vstring_h.native('VString<19>')
@@ -961,6 +1484,7 @@ def main():
interval_t = timer_th.native('interval_t')
## generated types
+ # TOC_GENTYPE
u8 = ctx.provided(uint8_t, Byte)
u16 = ctx.provided(uint16_t, Little16)
@@ -997,7 +1521,7 @@ def main():
interval16 = ctx.provided(interval_t, Little16)
sex = ctx.enum(SEX, u8)
- option = ctx.enum(Option, u16)
+ option = ctx.enum(Opt0, u16)
epos = ctx.enum(EPOS, u16)
item_look = ctx.enum(ItemLook, u16)
@@ -1007,14 +1531,19 @@ def main():
party_id = ctx.wrap(PartyId, u32)
item_name_id = ctx.wrap(ItemNameId, u16)
item_name_id4 = ctx.wrap(ItemNameId, u32)
+ ctx.poly(item_name_id, item_name_id4)
block_id = ctx.wrap(BlockId, u32)
time32 = ctx.provided(TimeT, Little32)
time64 = ctx.provided(TimeT, Little64)
+ #ctx.poly(time32, time32)
gm1 = ctx.provided(GmLevel, Byte)
gm2 = ctx.provided(GmLevel, Little16)
gm = ctx.provided(GmLevel, Little32)
+ #ctx.poly(gm1, gm2, gm)
+
+ # poly is not currently needed for ProvidedType
str16 = ctx.string(VString16)
str20 = ctx.string(VString20)
@@ -1251,6 +1780,7 @@ def main():
size=None,
)
+ # TODO move 'account id' out somehow.
storage = ctx.struct(
'Storage',
[
@@ -1263,6 +1793,8 @@ def main():
size=None,
)
+ ctx.finish_types()
+
## packet channels
@@ -1301,18 +1833,60 @@ def main():
# sized if all of its members are fixed sized.
#
# The element type of an array shall not be of implicit size.
- # The element type of an array *may* be of explicit size, in which
+ # The element type of an array *may* be of explicit size, in which case
+ # the layout is like option 1 below (see below for discussion).
#
# A string is just an array of characters, except that it may be padded
# with '\0' bytes even when it is sized.
# A map is just an array of two-element structs (key, value)
# However, strings and maps have custom classes used to represent them
- # on the sender and receiver (earray also has this).
+ # on the sender and receiver (earray also has this (but maybe shouldn't?)).
+ # Ytf is tmwa still using fixed-size arrays for this crap anyways?
+ # It probably should be a map in this case.
#
# It would probably be a good idea if everybody parsed network input as
# padded with '\0's if it is too short, and ignored the extra if it is
# too long. However, there are efficiency concerns with this, since we
- # don't want to branch everywhere.
+ # don't want to branch everywhere. But parsing in reverse might work ...
+
+ # possible array layouts:
+ # 1:
+ # | array1 size | array1 data | array2 size | array2 data |
+ # Advantage: linear writing (except final size?).
+ # Disadvantage: linear reading.
+ # This is what I originally imagined, but ...
+ # 2:
+ # | array1 size/offset | array2 size/offset | array1 data | array2 data |
+ # Advantage: random-access reading.
+ # Disadvantage: slightly larger packets, more complicated writing?.
+ # TODO verify whether you really need offset or not?
+ # TODO think about array-of-array case, it's complicated.
+ # | array0 size | array1 size | array 0.0 size | array 0.1 size | array 1.0 size | array 1.1 size | element 0.0.0 | element 0.0.1 | element 0.1.0 | element 0.1.1 | element 1.0.0 | element 1.0.1 | element 1.1.0 | element 1.1.1 |
+ # data a=(b=[d=[h=0.0.0, i=0.0.1], e=[j=0.1.0, k=0.1.1]], c=[f=[l=1.0.0, m=1.0.1], g=[n=1.1.0, o=1.1.1]])
+ # I'm not sure how this can work. If I wrote:
+ # def write_a():
+ # write_head_b() # size
+ # write_head_c() # size
+ # write_data_b()
+ # write_data_c()
+ # def write_data_b():
+ # write_head_d()
+ # write_head_e()
+ # write_data_d()
+ # write_data_e()
+ # def write_data_c():
+ # write_head_f()
+ # write_head_g()
+ # write_data_f()
+ # write_data_g()
+ # ... that would yield |s0|s1|s0.0|s0.1|d0.0.0|d0.0.1|d0.1.0|d0.1.1|s1.0|s1.1|d1.0.0|d1.0.1|d1.1.0|d1.1.1|
+ # Can't do random-access writes because you don't know the offset yet.
+ # I would have to do a bunch of 'write depth n' functions.
+ # Maybe this was a bad idea.
+ # And what exactly is the use of random-access reads? We're doing full
+ # translation for all data anyway. It's probably a bad idea for any
+ # packet to ever contain information that you don't look at.
+
login_char = ctx.chan('login', 'char')
login_admin = ctx.chan('login', 'admin')
@@ -1333,23 +1907,43 @@ def main():
## legacy packets
+ # TOC_CLIENT
# * user
- char_user.r(0x0061, 'change password request',
+ char_user.r(0x0061, 'change password',
+ define='CMSG_CHAR_PASSWORD_CHANGE',
fixed=[
at(0, u16, 'packet id'),
at(2, account_pass, 'old pass'),
at(26, account_pass, 'new pass'),
],
fixed_size=50,
+ pre=[HUMAN],
+ post=[0x2740],
+ desc='''
+ Sent by a client to the character server to request a password change.
+ ''',
)
- char_user.s(0x0062, 'change password response',
+ char_user.s(0x0062, 'change password result',
+ define='SMSG_CHAR_PASSWORD_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'status'),
],
fixed_size=3,
- )
- login_user.r(0x0063, 'update host',
+ pre=[0x2741],
+ post=[PRETTY],
+ desc='''
+ Sent by the character server with the response of a password change request.
+
+ Status:
+ 0: The account was not found.
+ 1: Success.
+ 2: The old password was incorrect.
+ 3: The new password was too short.
+ ''',
+ )
+ login_user.s(0x0063, 'update host notify',
+ define='SMSG_UPDATE_HOST',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1357,8 +1951,16 @@ def main():
head_size=4,
repeat=[at(0, u8, 'c')],
repeat_size=1,
+ pre=[0x0064],
+ post=[IDLE],
+ desc='''
+ This packet gives the client the location of the update server URL, such as http://tmwdata.org/updates/
+
+ It is only sent if an update host is specified for the server (there is one in the default configuration) and the client identifies as accepting an update host (which all supported clients do).
+ ''',
)
- login_user.r(0x0064, 'login request',
+ login_user.r(0x0064, 'account login',
+ define='CMSG_LOGIN_REGISTER',
fixed=[
at(0, u16, 'packet id'),
at(2, u32, 'unknown'),
@@ -1367,8 +1969,16 @@ def main():
at(54, version_2, 'version 2 flags'),
],
fixed_size=55,
+ pre=[HUMAN, 0x7531],
+ post=[0x0063, 0x0069, 0x006a, 0x0081],
+ desc='''
+ Authenticate a client by user/password.
+
+ All clients must now set both defined version 2 flags.
+ ''',
)
- char_user.r(0x0065, 'char-server connection request',
+ char_user.r(0x0065, 'connect char',
+ define='CMSG_CHAR_SERVER_CONNECT',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -1378,15 +1988,28 @@ def main():
at(16, sex, 'sex'),
],
fixed_size=17,
+ pre=[0x0069, 0x0092, 0x00b3],
+ post=[0x006b, 0x006c, 0x2712, 0x2716],
+ desc='''
+ Begin connection to the char server, based on keys the login
+ server gave us.
+ ''',
)
- char_user.r(0x0066, 'select character request',
+ char_user.r(0x0066, 'select character',
+ define='CMSG_CHAR_SELECT',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'code'),
],
fixed_size=3,
+ pre=[0x006b],
+ post=[FINISH, 0x0071, 0x0081],
+ desc='''
+ Choose a character to enter the map.
+ ''',
)
- char_user.r(0x0067, 'create character request',
+ char_user.r(0x0067, 'create character',
+ define='CMSG_CHAR_CREATE',
fixed=[
at(0, u16, 'packet id'),
at(2, char_name, 'char name'),
@@ -1396,16 +2019,32 @@ def main():
at(35, u16, 'hair style'),
],
fixed_size=37,
+ pre=[0x006b],
+ post=[0x006d, 0x006e],
+ desc='''
+ Create a new character.
+ ''',
)
- char_user.r(0x0068, 'delete character request',
+ char_user.r(0x0068, 'delete character',
+ define='CMSG_CHAR_DELETE',
fixed=[
at(0, u16, 'packet id'),
at(2, char_id, 'char id'),
- at(6, account_email, 'email'),
+ at(6, account_email, 'unused email'),
],
fixed_size=46,
- )
- login_user.r(0x0069, 'login data',
+ pre=[0x006b],
+ post=[0x006f, 0x0070, 0x2afe, 0x2b12, 0x3821, 0x3824, 0x3826],
+ desc='''
+ Delete an existing character.
+
+ There is no authentication on the server besides what has
+ already been performed to create the connection. "Are you sure?"
+ is solely the client's job.
+ ''',
+ )
+ login_user.s(0x0069, 'account login success',
+ define='SMSG_LOGIN_DATA',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1427,16 +2066,44 @@ def main():
at(30, u16, 'is new'),
],
repeat_size=32,
+ pre=[0x0064],
+ post=[0x0065],
+ desc='''
+ Big blob of information available once when you authenticate:
+
+ * dumb session keys
+ * sex and last login
+ * list of char server
+ ''',
)
- login_user.s(0x006a, 'login error',
+ login_user.s(0x006a, 'account login error',
+ define='SMSG_LOGIN_ERROR',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'error code'),
at(3, seconds, 'error message'),
],
fixed_size=23,
- )
- char_user.s(0x006b, 'update character list',
+ pre=[0x0064],
+ post=[PRETTY],
+ desc='''
+ Failure to log in.
+
+ Error codes:
+ * 0: unregistered id
+ * 1: incorrect password
+ * 2: expired id (unused?)
+ * 3: rejected from server (unused?)
+ * 4: permanently blocked
+ * 5: client too old (unused?)
+ * 6: temporary ban (date in 'error message' field)
+ * 7: server full
+ * 8: no message
+ * 99: id erased
+ ''',
+ )
+ char_user.s(0x006b, 'connect char success',
+ define='SMSG_CHAR_LOGIN',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1447,42 +2114,83 @@ def main():
at(0, char_select, 'char select'),
],
repeat_size=106,
+ pre=[0x0065, 0x2713],
+ post=[PRETTY],
+ xpost=[0x0066, 0x0067, 0x0068],
+ desc='''
+ List account's characters on this server.
+ ''',
)
- char_user.s(0x006c, 'login error',
+ char_user.s(0x006c, 'connect char error',
+ define='SMSG_CHAR_LOGIN_ERROR',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'code'),
],
fixed_size=3,
+ pre=[0x0065, 0x2713],
+ post=[PRETTY],
+ desc='''
+ Refuse connection.
+
+ Status:
+ 0: Overpopulated
+ 0x42: Auth failed
+ ''',
)
- char_user.s(0x006d, 'create character succeeded',
+ char_user.s(0x006d, 'create character success',
+ define='SMSG_CHAR_CREATE_SUCCEEDED',
fixed=[
at(0, u16, 'packet id'),
at(2, char_select, 'char select'),
],
fixed_size=108,
+ pre=[0x0067],
+ post=[PRETTY],
+ desc='''
+ Give information about newly-created character.
+ ''',
)
- char_user.s(0x006e, 'create character failed',
+ char_user.s(0x006e, 'create character error',
+ define='SMSG_CHAR_CREATE_FAILED',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'code'),
],
fixed_size=3,
+ pre=[0x0067],
+ post=[PRETTY],
+ desc='''
+ Failure to create a new character.
+ ''',
)
- char_user.s(0x006f, 'delete character succeeded',
+ char_user.s(0x006f, 'delete character success',
+ define='SMSG_CHAR_DELETE_SUCCEEDED',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[0x0068],
+ post=[PRETTY],
+ desc='''
+ Character successfully deleted.
+ ''',
)
- char_user.s(0x0070, 'delete character failed',
+ char_user.s(0x0070, 'delete character error',
+ define='SMSG_CHAR_DELETE_FAILED',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'code'),
],
fixed_size=3,
+ pre=[0x0068],
+ post=[PRETTY],
+ desc='''
+ Failure to delete character.
+ ''',
)
- char_user.s(0x0071, 'char-map info',
+ char_user.s(0x0071, 'select character success',
+ define='SMSG_CHAR_MAP_INFO',
fixed=[
at(0, u16, 'packet id'),
at(2, char_id, 'char id'),
@@ -1491,8 +2199,14 @@ def main():
at(26, u16, 'port'),
],
fixed_size=28,
+ pre=[0x0066],
+ post=[0x0072],
+ desc='''
+ Return character location and map server IP.
+ ''',
)
- map_user.r(0x0072, 'map server connect',
+ map_user.r(0x0072, 'connect map',
+ define='CMSG_MAP_SERVER_CONNECT',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -1502,8 +2216,15 @@ def main():
at(18, sex, 'sex'),
],
fixed_size=19,
+ pre=[0x0071, 0x0092],
+ post=[0x0081, 0x2afc],
+ desc='''
+ Begin connection to the map server, based on keys the login
+ server gave us.
+ ''',
)
- map_user.s(0x0073, 'map login succeeded',
+ map_user.s(0x0073, 'connect map success',
+ define='SMSG_MAP_LOGIN_SUCCESS',
fixed=[
at(0, u16, 'packet id'),
at(2, tick32, 'tick'),
@@ -1512,8 +2233,14 @@ def main():
at(10, u8, 'five2'),
],
fixed_size=11,
+ pre=[0x2afd],
+ post=[PRETTY, 0x007e],
+ desc='''
+ Successfully auth'd to the map server
+ ''',
)
- map_user.s(0x0078, 'being visibility',
+ map_user.s(0x0078, 'being appear notify',
+ define='SMSG_BEING_VISIBLE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -1545,8 +2272,14 @@ def main():
at(52, u16, 'level'),
],
fixed_size=54,
+ pre=[BOOT, FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00b8, 0x00b9, 0x00e6, 0x00f7, 0x0143, 0x0146, 0x01d5],
+ post=[0x0094],
+ desc='''
+ A being is stationary.
+ ''',
)
- map_user.s(0x007b, 'being move',
+ map_user.s(0x007b, 'being move notify',
+ define='SMSG_BEING_MOVE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -1577,8 +2310,14 @@ def main():
at(58, u16, 'level'),
],
fixed_size=60,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f],
+ post=[0x0094],
+ desc='''
+ A being is moving.
+ ''',
)
- map_user.s(0x007c, 'being spawn',
+ map_user.s(0x007c, 'being spawn notify',
+ define='SMSG_BEING_SPAWN',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -1601,50 +2340,95 @@ def main():
at(39, u16, 'unknown 11'),
],
fixed_size=41,
+ pre=[BOOT, FINISH, GM, MAGIC, SCRIPT, TIMER, 0x0089, 0x008c, 0x00b8, 0x00b9, 0x00e6, 0x00f7, 0x0143, 0x0146, 0x01d5],
+ post=[PRETTY],
+ desc='''
+ A being is created.
+ ''',
)
map_user.r(0x007d, 'map loaded',
+ define='CMSG_MAP_LOADED',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[0x0091],
+ post=[SCRIPT, 0x0078, 0x007b, 0x009d, 0x00a4, 0x00b0, 0x00b1, 0x00bd, 0x00fb, 0x0101, 0x010f, 0x0119, 0x013a, 0x0141, 0x019b, 0x01d7, 0x01d8, 0x01d9, 0x01da, 0x01ee, 0x3025, 0x3028],
+ xpost=[0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x013c, 0x0196, 0x01b1, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Changed map; need info.
+ ''',
)
- map_user.r(0x007e, 'client ping',
+ map_user.r(0x007e, 'ping',
+ define='CMSG_MAP_PING',
fixed=[
at(0, u16, 'packet id'),
at(2, u32, 'client tick'),
],
fixed_size=6,
+ pre=[TIMER, 0x0073],
+ post=[0x007f],
+ desc='''
+ Request ping.
+ ''',
)
- map_user.s(0x007f, 'server ping',
+ map_user.s(0x007f, 'pong',
+ define='SMSG_SERVER_PING',
fixed=[
at(0, u16, 'packet id'),
at(2, tick32, 'tick'),
],
fixed_size=6,
+ pre=[0x007e],
+ post=[NOTHING],
+ desc='''
+ Provide ping.
+ ''',
)
- map_user.s(0x0080, 'remove being',
+ map_user.s(0x0080, 'remove being notify',
+ define='SMSG_BEING_REMOVE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, being_remove_why, 'type'),
],
fixed_size=7,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x0090, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ A being disappeared.
+ ''',
)
- any_user.s(0x0081, 'connection problem',
+ any_user.s(0x0081, 'connect foo error',
+ define='SMSG_CONNECTION_PROBLEM',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'error code'),
],
fixed_size=3,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x0064, 0x0066, 0x0072, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2afe, 0x2b06, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Failed to connect to some server (multiple meanings).
+ ''',
)
- map_user.r(0x0085, 'change player destination',
+ map_user.r(0x0085, 'walk',
+ define='CMSG_PLAYER_CHANGE_DEST',
fixed=[
at(0, u16, 'packet id'),
at(2, pos1, 'pos'),
],
fixed_size=5,
+ pre=[HUMAN],
+ post=[0x0080, 0x0087, 0x01d7, 0x01da],
+ xpost=[SCRIPT, 0x0078, 0x007b, 0x0081, 0x0088, 0x0091, 0x009d, 0x00a0, 0x00a1, 0x00ac, 0x00b0, 0x00b1, 0x00b4, 0x00b6, 0x00be, 0x00c0, 0x00c4, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d8, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Start walking somewhere.
+ ''',
)
- map_user.s(0x0087, 'walk response',
+ # 0x0086 define='SMSG_BEING_MOVE2',
+ map_user.s(0x0087, 'walk success',
+ define='SMSG_WALK_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, tick32, 'tick'),
@@ -1652,8 +2436,16 @@ def main():
at(11, u8, 'zero'),
],
fixed_size=12,
+ pre=[0x0085, 0x0089],
+ post=[IDLE],
+ desc='''
+ Confirm that you did walk somewhere.
+
+ No corresponding error!
+ ''',
)
- map_user.s(0x0088, 'player stop',
+ map_user.s(0x0088, 'stop walking notify',
+ define='SMSG_PLAYER_STOP',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -1661,16 +2453,36 @@ def main():
at(8, u16, 'y'),
],
fixed_size=10,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Being stopped walking.
+ ''',
)
map_user.r(0x0089, 'player action',
+ define='CMSG_PLAYER_CHANGE_ACT',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'target id'),
at(6, damage_type, 'action'),
],
fixed_size=7,
+ pre=[HUMAN],
+ post=[MAGIC, 0x0080, 0x008a, 0x0110, 0x0139],
+ xpost=[SCRIPT, 0x0078, 0x007b, 0x007c, 0x0081, 0x0087, 0x0088, 0x0091, 0x009e, 0x00a0, 0x00a1, 0x00ac, 0x00af, 0x00b0, 0x00b1, 0x00b4, 0x00b6, 0x00be, 0x00c0, 0x00c4, 0x00e9, 0x00ee, 0x00fb, 0x00fd, 0x0101, 0x0106, 0x010f, 0x0119, 0x013a, 0x013b, 0x0141, 0x0196, 0x019b, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x01de, 0x2b01, 0x2b05, 0x3011, 0x3022, 0x3025, 0x3028],
+ desc='''
+ Perform an action.
+
+ Action:
+ * single attack
+ * continuous attack
+ * sit
+ * stand
+ * (other actions on return only - special calls)
+ ''',
)
- map_user.s(0x008a, 'being action',
+ map_user.s(0x008a, 'being action notify',
+ define='SMSG_BEING_ACTION',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'src id'),
@@ -1684,8 +2496,14 @@ def main():
at(27, u16, 'damage2'),
],
fixed_size=29,
- )
- map_user.r(0x008c, 'character chat',
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x0089, 0x009f],
+ post=[PRETTY],
+ desc='''
+ Somebody performed an action on something/somebody.
+ ''',
+ )
+ map_user.r(0x008c, 'global chat',
+ define='CMSG_CHAT_MESSAGE',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1695,8 +2513,15 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
- )
- map_user.s(0x008d, 'being chat',
+ pre=[HUMAN],
+ post=[GM, MAGIC, 0x008d, 0x008e],
+ xpost=[SCRIPT, 0x0078, 0x007b, 0x007c, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00af, 0x00b0, 0x00b1, 0x00b4, 0x00b6, 0x00be, 0x00c0, 0x00c4, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x2b0e, 0x3003, 0x3011, 0x3022],
+ desc='''
+ Talk to everyone nearby.
+ ''',
+ )
+ map_user.s(0x008d, 'global chat notify',
+ define='SMSG_BEING_CHAT',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1707,8 +2532,14 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
- )
- map_user.s(0x008e, 'player chat',
+ pre=[GM, SCRIPT, 0x008c],
+ post=[PRETTY],
+ desc='''
+ Somebody is talking (not just a player).
+ ''',
+ )
+ map_user.s(0x008e, 'global chat result',
+ define='SMSG_PLAYER_CHAT',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1718,16 +2549,33 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[OTHER, 0x008c],
+ post=[PRETTY],
+ desc='''
+ You talked.
+ ''',
)
- map_user.r(0x0090, 'chat to npc',
+ map_user.r(0x0090, 'npc click',
+ define='CMSG_NPC_TALK',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, u8, 'unused'),
],
fixed_size=7,
+ pre=[HUMAN],
+ post=[SCRIPT, 0x0080, 0x00b4, 0x00b6, 0x00c4],
+ xpost=[0x00c0],
+ desc='''
+ Click on an NPC, to invoke its script/shop/message.
+
+ Error if already talking to an NPC or too far.
+
+ No error if clicking on a warp.
+ ''',
)
- map_user.s(0x0091, 'warp player',
+ map_user.s(0x0091, 'change map notify',
+ define='SMSG_PLAYER_WARP',
fixed=[
at(0, u16, 'packet id'),
at(2, map_name, 'map name'),
@@ -1735,8 +2583,15 @@ def main():
at(20, u16, 'y'),
],
fixed_size=22,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[0x007d],
+ desc='''
+ You teleported to a new map (possible the same map), but on the
+ same server.
+ ''',
)
- map_user.s(0x0092, 'change map server',
+ map_user.s(0x0092, 'change map server notify',
+ define='SMSG_CHANGE_MAP_SERVER',
fixed=[
at(0, u16, 'packet id'),
at(2, map_name, 'map name'),
@@ -1746,23 +2601,43 @@ def main():
at(26, u16, 'port'),
],
fixed_size=28,
+ pre=[0x2b06],
+ post=[0x0065, 0x0072],
+ desc='''
+ You teleported to another server.
+ ''',
)
- map_user.r(0x0094, 'request being name',
+ map_user.r(0x0094, 'get being name',
+ define='CMSG_NAME_REQUEST',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
+ pre=[TIMER, 0x0078, 0x007b, 0x01d8, 0x01d9, 0x01da],
+ post=[0x0095, 0x0195, 0x020c],
+ desc='''
+ Request a beings name. No reply if wrong type.
+
+ Also send misc other info about players.
+ ''',
)
- map_user.s(0x0095, 'being name response',
+ map_user.s(0x0095, 'get being name result',
+ define='SMSG_BEING_NAME_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, char_name, 'char name'),
],
fixed_size=30,
- )
- map_user.r(0x0096, 'send whisper',
+ pre=[0x0094],
+ post=[PRETTY],
+ desc='''
+ Somebody has a name.
+ ''',
+ )
+ map_user.r(0x0096, 'whisper',
+ define='CMSG_CHAT_WHISPER',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1773,8 +2648,15 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[HUMAN],
+ post=[GM, 0x0097, 0x0098, 0x3001],
+ xpost=[0x2b0e, 0x3003],
+ desc='''
+ Talk to someone privately.
+ ''',
)
map_user.s(0x0097, 'receive whisper',
+ define='SMSG_WHISPER',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1785,15 +2667,28 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x0096, 0x3801, 0x3803],
+ post=[PRETTY],
+ desc='''
+ Somebody is talking to you privately.
+ '''
)
- map_user.s(0x0098, 'whisper status',
+ map_user.s(0x0098, 'whisper result',
+ define='SMSG_WHISPER_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'flag'),
],
fixed_size=3,
- )
- map_user.s(0x009a, 'gm announcement',
+ pre=[0x0096, 0x3802],
+ post=[PRETTY],
+ desc='''
+ Did you successfully talk to someone?
+ ''',
+ )
+ # 0x0099 define='CMSG_ADMIN_ANNOUNCE',
+ map_user.s(0x009a, 'gm announcement notify',
+ define='SMSG_GM_CHAT',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1803,16 +2698,28 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[GM, SCRIPT, 0x3800],
+ post=[PRETTY],
+ desc='''
+ The GMs are shouting in red.
+ ''',
)
- map_user.r(0x009b, 'change player direction',
+ map_user.r(0x009b, 'face direction',
+ define='CMSG_PLAYER_CHANGE_DIR',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'unused'),
at(4, u8, 'client dir'),
],
fixed_size=5,
+ pre=[HUMAN],
+ post=[0x009c],
+ desc='''
+ Look in a different direction, without moving.
+ ''',
)
- map_user.s(0x009c, 'being changed direction',
+ map_user.s(0x009c, 'face direction notify',
+ define='SMSG_BEING_CHANGE_DIRECTION',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -1820,8 +2727,14 @@ def main():
at(8, u8, 'client dir'),
],
fixed_size=9,
+ pre=[0x009b],
+ post=[PRETTY],
+ desc='''
+ Somebody looked in a different direction, without moving.
+ ''',
)
- map_user.s(0x009d, 'visible item',
+ map_user.s(0x009d, 'item visible notify',
+ define='SMSG_ITEM_VISIBLE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -1834,8 +2747,14 @@ def main():
at(16, u8, 'suby'),
],
fixed_size=17,
+ pre=[0x007d, 0x0085],
+ post=[PRETTY],
+ desc='''
+ An item appeared on the ground.
+ ''',
)
- map_user.s(0x009e, 'dropped item',
+ map_user.s(0x009e, 'item dropped notify',
+ define='SMSG_ITEM_DROPPED',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -1848,15 +2767,28 @@ def main():
at(15, u16, 'amount'),
],
fixed_size=17,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x0089, 0x00a2],
+ post=[PRETTY],
+ desc='''
+ An item was dropped on the ground.
+ ''',
)
- map_user.r(0x009f, 'pickup item',
+ map_user.r(0x009f, 'item pickup',
+ define='CMSG_ITEM_PICKUP',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'object id'),
],
fixed_size=6,
+ pre=[HUMAN],
+ post=[SCRIPT, 0x0080, 0x008a, 0x0091, 0x00a0, 0x00a1, 0x00b0],
+ xpost=[0x0078, 0x007b, 0x0081, 0x0088, 0x00ac, 0x00b1, 0x00b4, 0x00b6, 0x00be, 0x00c0, 0x00c4, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Hey, it's just lying around on the floor, I want it.
+ ''',
)
- map_user.s(0x00a0, 'add item to inventory',
+ map_user.s(0x00a0, 'inventory add notify',
+ define='SMSG_PLAYER_INVENTORY_ADD',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
@@ -1874,23 +2806,44 @@ def main():
at(22, pickup_fail, 'pickup fail'),
],
fixed_size=23,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Item appeared in your inventory.
+ ''',
)
- map_user.s(0x00a1, 'item removed',
+ map_user.s(0x00a1, 'flooritem delete notify',
+ define='SMSG_ITEM_REMOVE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x0085, 0x0089, 0x009f, 0x00a2],
+ post=[PRETTY],
+ desc='''
+ Item disappeared from the floor.
+ ''',
)
- map_user.r(0x00a2, 'drop an item',
+ map_user.r(0x00a2, 'drop item',
+ define='CMSG_PLAYER_INVENTORY_DROP',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
at(4, u16, 'amount'),
],
fixed_size=6,
+ pre=[HUMAN],
+ post=[0x0080, 0x009e, 0x00ac, 0x00af, 0x01d7],
+ xpost=[SCRIPT, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00a1, 0x00b0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ This is worthless, just leave it lying on the ground.
+
+ Also used by people who don't understand trades.
+ ''',
)
- map_user.s(0x00a4, 'player equipment',
+ map_user.s(0x00a4, 'inventory equipment notify',
+ define='SMSG_PLAYER_EQUIPMENT',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1911,8 +2864,14 @@ def main():
at(18, u16, 'card3'),
],
repeat_size=20,
- )
- map_user.s(0x00a6, 'storage equipment',
+ pre=[0x007d],
+ post=[PRETTY],
+ desc='''
+ Complete list of equipment in inventory.
+ ''',
+ )
+ map_user.s(0x00a6, 'storage equipment notify',
+ define='SMSG_PLAYER_STORAGE_EQUIP',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -1933,16 +2892,29 @@ def main():
at(18, u16, 'card3'),
],
repeat_size=20,
+ pre=[GM, SCRIPT, 0x3810],
+ post=[PRETTY],
+ desc='''
+ Complete list of equipment in storage.
+ ''',
)
- map_user.r(0x00a7, 'use inventory item',
+ map_user.r(0x00a7, 'use item',
+ define='CMSG_PLAYER_INVENTORY_USE',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
at(4, u32, 'unused id'),
],
fixed_size=8,
+ pre=[HUMAN],
+ post=[SCRIPT, 0x0080, 0x00a8, 0x00ac, 0x00af, 0x00b0, 0x01c8, 0x01d7],
+ xpost=[0x0081, 0x0088, 0x0091, 0x00a0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Use a consumable item in your inventory.
+ ''',
)
- map_user.s(0x00a8, 'item usage response',
+ map_user.s(0x00a8, 'use item result',
+ define='SMSG_ITEM_USE_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
@@ -1950,16 +2922,31 @@ def main():
at(6, u8, 'ok'),
],
fixed_size=7,
+ pre=[0x00a7],
+ post=[PRETTY],
+ desc='''
+ You used (or tried to use) an item in your inventory.
+
+ Currently only used in the failure case.
+ ''',
)
- map_user.r(0x00a9, 'equip an item request',
+ map_user.r(0x00a9, 'equip item',
+ define='CMSG_PLAYER_EQUIP',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
at(4, epos, 'epos ignored'),
],
fixed_size=6,
+ pre=[HUMAN],
+ post=[SCRIPT, 0x0080, 0x00aa, 0x00ac, 0x013b, 0x013c, 0x01d7],
+ xpost=[0x0081, 0x0088, 0x0091, 0x00a0, 0x00b0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Put on armor or something.
+ ''',
)
- map_user.s(0x00aa, 'item equip ack',
+ map_user.s(0x00aa, 'equip item result',
+ define='SMSG_PLAYER_EQUIP',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
@@ -1967,15 +2954,28 @@ def main():
at(6, u8, 'ok'),
],
fixed_size=7,
+ pre=[0x00a9],
+ post=[PRETTY],
+ desc='''
+ Result of trying to wear something.
+ ''',
)
- map_user.r(0x00ab, 'unequip an item',
+ map_user.r(0x00ab, 'unequip item',
+ define='CMSG_PLAYER_UNEQUIP',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
],
fixed_size=4,
+ pre=[HUMAN],
+ post=[SCRIPT, 0x0080, 0x00ac, 0x01d7],
+ xpost=[0x0081, 0x0088, 0x0091, 0x00a0, 0x00b0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Take off armor or something.
+ ''',
)
- map_user.s(0x00ac, 'unequip item ack',
+ map_user.s(0x00ac, 'unequip item result',
+ define='SMSG_PLAYER_UNEQUIP',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
@@ -1983,46 +2983,83 @@ def main():
at(6, u8, 'ok'),
],
fixed_size=7,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Result of trying to unwear something.
+ ''',
)
- map_user.s(0x00af, 'remove item from inventory',
+ map_user.s(0x00af, 'inventory delete notify',
+ define='SMSG_PLAYER_INVENTORY_REMOVE',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
at(4, u16, 'amount'),
],
fixed_size=6,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x0089, 0x008c, 0x00a2, 0x00a7, 0x00c9, 0x00ef, 0x00f3],
+ post=[PRETTY],
+ desc='''
+ An item has been deleted from your inventory.
+ ''',
)
- map_user.s(0x00b0, 'player stat update 1',
+ map_user.s(0x00b0, 'player stat update 1 notify',
+ define='SMSG_PLAYER_STAT_UPDATE_1',
fixed=[
at(0, u16, 'packet id'),
at(2, sp, 'sp type'),
at(4, u32, 'value'),
],
fixed_size=8,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ One of many player stat packets with no real difference.
+ ''',
)
- map_user.s(0x00b1, 'player stat update 2',
+ map_user.s(0x00b1, 'player stat update 2 notify',
+ define='SMSG_PLAYER_STAT_UPDATE_2',
fixed=[
at(0, u16, 'packet id'),
at(2, sp, 'sp type'),
at(4, u32, 'value'),
],
fixed_size=8,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ One of many player stat packets with no real difference.
+ ''',
)
- map_user.r(0x00b2, 'switch or respawn the character',
+ map_user.r(0x00b2, 'respawn or switch character',
+ define='CMSG_PLAYER_REBOOT',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'flag'),
],
fixed_size=3,
+ pre=[HUMAN, 0x01d8],
+ post=[0x0080, 0x0091, 0x00b0, 0x018b, 0x2b02, 0x2b05],
+ xpost=[SCRIPT, 0x0081, 0x0088, 0x00a0, 0x00ac, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x3011, 0x3022],
+ desc='''
+ If flag is 0, respawn after dying. If flag is 1, try to switch characters.
+ ''',
)
- map_user.s(0x00b3, 'character switch response',
+ map_user.s(0x00b3, 'character switch success',
+ define='SMSG_CHAR_SWITCH_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'one'),
],
fixed_size=3,
- )
- map_user.s(0x00b4, 'npc message',
+ pre=[0x2b03],
+ post=[0x0065],
+ desc='''
+ Permission granted to switch characters.
+ ''',
+ )
+ map_user.s(0x00b4, 'script message notify',
+ define='SMSG_NPC_MESSAGE',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2033,22 +3070,40 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[MAGIC, SCRIPT, 0x0085, 0x0089, 0x008c, 0x0090, 0x009f],
+ post=[PRETTY],
+ desc='''
+ An npc is droning on.
+ ''',
)
- map_user.s(0x00b5, 'npc message continues',
+ map_user.s(0x00b5, '(reverse) script next',
+ define='SMSG_NPC_NEXT',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
+ pre=[SCRIPT],
+ post=[0x00b9],
+ desc='''
+ An npc paused briefly to catch its breath.
+ ''',
)
- map_user.s(0x00b6, 'npc message ends',
+ map_user.s(0x00b6, '(reverse) script close',
+ define='SMSG_NPC_CLOSE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
- )
- map_user.s(0x00b7, 'npc prompts a choice',
+ pre=[GM, MAGIC, SCRIPT, 0x0085, 0x0089, 0x008c, 0x0090, 0x009f, 0x00b8, 0x00b9, 0x00e6, 0x00f7, 0x0143, 0x0146, 0x01d5],
+ post=[0x0146],
+ desc='''
+ An npc finally shut up.
+ ''',
+ )
+ map_user.s(0x00b7, '(reverse) script menu',
+ define='SMSG_NPC_CHOICE',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2059,31 +3114,58 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[SCRIPT],
+ post=[0x00b8],
+ desc='''
+ An npc let you choose how it will drone on.
+ ''',
)
- map_user.r(0x00b8, 'send npc response',
+ map_user.r(0x00b8, '(reverse) script menu result',
+ define='CMSG_NPC_LIST_CHOICE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'npc id'),
at(6, u8, 'menu entry'),
],
fixed_size=7,
+ pre=[0x00b7],
+ post=[MAGIC, SCRIPT],
+ xpost=[0x0078, 0x007c, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00b6, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Choose an entry from the menu (1-based), 0xff to run away.
+ ''',
)
- map_user.r(0x00b9, 'request next npc message',
+ map_user.r(0x00b9, '(reverse) script next result',
+ define='CMSG_NPC_NEXT_REQUEST',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'npc id'),
],
fixed_size=6,
+ pre=[0x00b5],
+ post=[MAGIC, SCRIPT],
+ xpost=[0x0078, 0x007c, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00b6, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Let the npc keep breathing.
+ ''',
)
- map_user.r(0x00bb, 'request a stat update',
+ map_user.r(0x00bb, 'stat increase',
+ define='CMSG_STAT_UPDATE_REQUEST',
fixed=[
at(0, u16, 'packet id'),
at(2, sp, 'asp'),
at(4, u8, 'unused'),
],
fixed_size=5,
+ pre=[HUMAN],
+ post=[0x00b0, 0x00bc, 0x00be, 0x0141],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b1, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Spend stat points.
+ ''',
)
- map_user.s(0x00bc, 'player stat update 4',
+ map_user.s(0x00bc, 'stat increase result',
+ define='SMSG_PLAYER_STAT_UPDATE_4',
fixed=[
at(0, u16, 'packet id'),
at(2, sp, 'sp type'),
@@ -2091,8 +3173,14 @@ def main():
at(5, u8, 'val'),
],
fixed_size=6,
+ pre=[SCRIPT, 0x00bb],
+ post=[PRETTY],
+ desc='''
+ Spent stat points?
+ ''',
)
- map_user.s(0x00bd, 'player stat update 5',
+ map_user.s(0x00bd, 'player stat update 5 notify',
+ define='SMSG_PLAYER_STAT_UPDATE_5',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'status point'),
@@ -2124,59 +3212,89 @@ def main():
at(42, u16, 'manner'),
],
fixed_size=44,
+ pre=[0x007d],
+ post=[PRETTY],
+ desc='''
+ Bulk notify of stats.
+ ''',
)
- map_user.s(0x00be, 'player stat update 6',
+ map_user.s(0x00be, 'stat price notify',
+ define='SMSG_PLAYER_STAT_UPDATE_6',
fixed=[
at(0, u16, 'packet id'),
at(2, sp, 'sp type'),
at(4, u8, 'value'),
],
fixed_size=5,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Cost of spending stat points.
+ ''',
)
- map_user.r(0x00bf, 'show an emote',
+ map_user.r(0x00bf, 'emote',
+ define='CMSG_PLAYER_EMOTE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'emote'),
],
fixed_size=3,
+ pre=[HUMAN, TIMER],
+ post=[0x00c0, 0x110],
+ desc='''
+ Don't act like a faceless robot.
+ ''',
)
- map_user.s(0x00c0, 'show the emote of a being',
+ map_user.s(0x00c0, 'emote notify',
+ define='SMSG_BEING_EMOTION',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, u8, 'type'),
],
fixed_size=7,
+ pre=[BOOT, FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x0090, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00bf, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d, 0x3800, 0x3821, 0x3823, 0x3824, 0x3825, 0x3826, 0x3827],
+ post=[PRETTY],
+ desc='''
+ Somebody is claiming to not be a faceless robot.
+ ''',
)
- map_user.r(0x00c1, 'request online users (unused)',
- fixed=[
- at(0, u16, 'packet id'),
- ],
- fixed_size=2,
- )
- map_user.s(0x00c2, 'online users response (unused)',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, u32, 'users'),
- ],
- fixed_size=6,
- )
- map_user.s(0x00c4, 'npc shop choice',
+ # 0x00c1 define='CMSG_CHAT_WHO', (unused by this name)
+ # 0x00c1 define='CMSG_WHO_REQUEST',
+ # 0x00c2 define='SMSG_WHO_ANSWER',
+ # 0x00c3 define='SMSG_BEING_CHANGE_LOOKS',
+ map_user.s(0x00c4, 'npc click result shop',
+ define='SMSG_NPC_BUY_SELL_CHOICE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
+ pre=[MAGIC, SCRIPT, 0x0085, 0x0089, 0x008c, 0x0090, 0x009f],
+ post=[0x00c5],
+ desc='''
+ That npc you clicked on was a shop.
+ ''',
)
- map_user.r(0x00c5, 'npc shop request',
+ map_user.r(0x00c5, 'npc shop buy/sell select',
+ define='CMSG_NPC_BUY_SELL_REQUEST',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, u8, 'type'),
],
fixed_size=7,
+ pre=[0x00c4],
+ post=[0x00c6, 0x00c7],
+ desc='''
+ Choose whether to buy or sell in a shop.
+
+ type 0: buy
+ type 1: sell
+ ''',
)
- map_user.s(0x00c6, 'npc buy prompt',
+ map_user.s(0x00c6, 'npc shop buy select result',
+ define='SMSG_NPC_BUY',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2189,8 +3307,14 @@ def main():
at(9, item_name_id, 'name id'),
],
repeat_size=11,
- )
- map_user.s(0x00c7, 'npc sell prompt',
+ pre=[0x00c5],
+ post=[0x00c8],
+ desc='''
+ List of items you can buy from the shop.
+ ''',
+ )
+ map_user.s(0x00c7, 'npc shop sell select result',
+ define='SMSG_NPC_SELL',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2202,8 +3326,17 @@ def main():
at(6, u32, 'actual price'),
],
repeat_size=10,
+ pre=[0x00c5],
+ post=[0x00c9],
+ desc='''
+ List of items you can sell to the shop.
+
+ Currently the server doesn't support customizing this list,
+ but in theory it could.
+ ''',
)
- map_user.r(0x00c8, 'npc buy request',
+ map_user.r(0x00c8, 'npc shop buy',
+ define='CMSG_NPC_BUY_REQUEST',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2214,8 +3347,15 @@ def main():
at(2, item_name_id, 'name id'),
],
repeat_size=4,
- )
- map_user.r(0x00c9, 'npc sell request',
+ pre=[0x00c6],
+ post=[0x00a0, 0x00b0, 0x00b1, 0x00ca],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00ac, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Purchase a bunch of items from a shop.
+ ''',
+ )
+ map_user.r(0x00c9, 'npc shop sell',
+ define='CMSG_NPC_SELL_REQUEST',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2226,65 +3366,127 @@ def main():
at(2, u16, 'count'),
],
repeat_size=4,
+ pre=[0x00c7],
+ post=[0x00ac, 0x00af, 0x00b0, 0x00b1, 0x00cb, 0x01d7],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Sell a bunch of items to a shop.
+ ''',
)
- map_user.s(0x00ca, 'npc buy response',
+ map_user.s(0x00ca, 'npc shop buy result',
+ define='SMSG_NPC_BUY_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'fail'),
],
fixed_size=3,
+ pre=[0x00c8],
+ post=[PRETTY],
+ desc='''
+ Result of purchasing items.
+ ''',
)
- map_user.s(0x00cb, 'npc sell response',
+ map_user.s(0x00cb, 'npc shop sell result',
+ define='SMSG_NPC_SELL_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'fail'),
],
fixed_size=3,
+ pre=[0x00c9],
+ post=[PRETTY],
+ desc='''
+ Result of purchasing items.
+ ''',
)
- map_user.s(0x00cd, 'kick status',
+ # 0x00cc define='CMSG_ADMIN_KICK',
+ map_user.s(0x00cd, 'kick result',
+ define='SMSG_ADMIN_KICK_ACK',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[GM],
+ post=[PRETTY],
+ desc='''
+ Successfully used @kick.
+ ''',
)
- map_user.r(0x00e4, 'trade request request',
+ # 0x00cf define='CMSG_IGNORE_NICK',
+ # 0x00d0 define='CMSG_IGNORE_ALL',
+ # 0x00d2 define='SMSG_IGNORE_ALL_RESPONSE',
+ map_user.r(0x00e4, 'trade please',
+ define='CMSG_TRADE_REQUEST',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
+ pre=[HUMAN],
+ post=[0x00e5, 0x00e7, 0x00ee, 0x0110],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Ask someone to trade.
+ ''',
)
map_user.s(0x00e5, 'incoming trade request',
+ define='SMSG_TRADE_REQUEST',
fixed=[
at(0, u16, 'packet id'),
at(2, char_name, 'char name'),
],
fixed_size=26,
+ pre=[GM, 0x00e4],
+ post=[0x00e6],
+ desc='''
+ Somebody wants to trade with you.
+ ''',
)
- map_user.r(0x00e6, 'incoming trade request response',
+ map_user.r(0x00e6, 'incoming trade request result',
+ define='CMSG_TRADE_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'type'),
],
fixed_size=3,
+ pre=[0x00e5],
+ post=[0x00e7],
+ xpost=[MAGIC, SCRIPT, 0x0078, 0x007c, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00b6, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00f8, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ You agree/disagree to begin a trade with someone.
+ ''',
)
- map_user.s(0x00e7, 'trade request response',
+ map_user.s(0x00e7, 'trade please result',
+ define='SMSG_TRADE_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'type'),
],
fixed_size=3,
+ pre=[GM, 0x00e4, 0x00e6],
+ post=[PRETTY],
+ desc='''
+ The original result of asking for a trade.
+ ''',
)
- map_user.r(0x00e8, 'trade item add request',
+ map_user.r(0x00e8, 'trade add',
+ define='CMSG_TRADE_ITEM_ADD_REQUEST',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'zeny or ioff2'),
at(4, u32, 'amount'),
],
fixed_size=8,
+ pre=[HUMAN],
+ post=[0x00ac, 0x00e9, 0x01b1, 0x01d7],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00b0, 0x00b1, 0x00be, 0x00c0, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Add item/zeny to your trade offer.
+ ''',
)
- map_user.s(0x00e9, 'trade item add',
+ map_user.s(0x00e9, 'trade item added notify',
+ define='SMSG_TRADE_ITEM_ADD',
fixed=[
at(0, u16, 'packet id'),
at(2, u32, 'amount'),
@@ -2298,62 +3500,125 @@ def main():
at(17, u16, 'card3'),
],
fixed_size=19,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Item successfully added.
+ ''',
)
- map_user.r(0x00eb, 'trade add complete',
+ map_user.r(0x00eb, 'trade lock',
+ define='CMSG_TRADE_ADD_COMPLETE',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[HUMAN],
+ post=[0x00ec, 0x1b1],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Indicate readiness to end trade.
+
+ Do this when the other person has added all the items you want.
+ ''',
)
- map_user.s(0x00ec, 'trade ok',
+ map_user.s(0x00ec, 'trade lock notify',
+ define='SMSG_TRADE_OK',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'fail'),
],
fixed_size=3,
+ pre=[0x00eb],
+ post=[PRETTY],
+ desc='''
+ Somebody locked their half of the trade (0=self, 1=other).
+ ''',
)
- map_user.r(0x00ed, 'trace cancel request',
+ map_user.r(0x00ed, 'trade cancel',
+ define='CMSG_TRADE_CANCEL_REQUEST',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[HUMAN],
+ post=[0x00a0, 0x00b0, 0x00ee],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00ac, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Cancel an ongoing trade.
+ ''',
)
- map_user.s(0x00ee, 'trade cancel',
+ map_user.s(0x00ee, 'trade cancel notify',
+ define='SMSG_TRADE_CANCEL',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[],
+ desc='''
+ Somebody cancelled your trade.
+ ''',
)
- map_user.r(0x00ef, 'trade ok request',
+ map_user.r(0x00ef, 'trade commit',
+ define='CMSG_TRADE_OK',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[HUMAN],
+ post=[0x00a0, 0x00af, 0x00b0, 0x00f0],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00ac, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Actually perform the trade. Requires half-lock from both sides.
+ ''',
)
- map_user.s(0x00f0, 'trade complete',
+ map_user.s(0x00f0, 'trade complete notify',
+ define='SMSG_TRADE_COMPLETE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'fail'),
],
fixed_size=3,
+ pre=[0x00ef],
+ post=[PRETTY],
+ desc='''
+ Trade is finally over.
+ ''',
)
- map_user.s(0x00f2, 'storage status',
+ map_user.s(0x00f2, 'storage size notify',
+ define='SMSG_PLAYER_STORAGE_STATUS',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'current slots'),
at(4, u16, 'max slots'),
],
fixed_size=6,
+ pre=[GM, SCRIPT, 0x00f3, 0x00f5, 0x3810],
+ post=[PRETTY],
+ desc='''
+ Update the current/max storage size.
+
+ It's easy to change the max storage on any basis, pity about
+ the inventory limit ...
+ ''',
)
- map_user.r(0x00f3, 'move item to storage request',
+ map_user.r(0x00f3, 'storage put',
+ define='CMSG_MOVE_TO_STORAGE',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
at(4, u32, 'amount'),
],
fixed_size=8,
+ pre=[HUMAN],
+ post=[0x00ac, 0x00af, 0x00b0, 0x00f2, 0x00f4, 0x01d7],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Move item from inventory to storage.
+ ''',
)
- map_user.s(0x00f4, 'move item to storage',
+ map_user.s(0x00f4, 'storage added notify',
+ define='SMSG_PLAYER_STORAGE_ADD',
fixed=[
at(0, u16, 'packet id'),
at(2, soff1, 'soff1'),
@@ -2368,50 +3633,98 @@ def main():
at(19, u16, 'card3'),
],
fixed_size=21,
+ pre=[GM, 0x00f3],
+ post=[PRETTY],
+ desc='''
+ Item was added to your storage.
+ ''',
)
- map_user.r(0x00f5, 'move item from storage request',
+ map_user.r(0x00f5, 'storage take',
+ define='CSMG_MOVE_FROM_STORAGE',
fixed=[
at(0, u16, 'packet id'),
at(2, soff1, 'soff1'),
at(4, u32, 'amount'),
],
fixed_size=8,
+ pre=[HUMAN],
+ post=[0x00a0, 0x00b0, 0x00f2, 0x00f6],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00ac, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Move item from storage to inventory.
+ ''',
)
- map_user.s(0x00f6, 'remove item from storage',
+ map_user.s(0x00f6, 'storage removed notify',
+ define='SMSG_PLAYER_STORAGE_REMOVE',
fixed=[
at(0, u16, 'packet id'),
at(2, soff1, 'soff1'),
at(4, u32, 'amount'),
],
fixed_size=8,
+ pre=[0x00f5],
+ post=[PRETTY],
+ desc='''
+ Item was removed from your storage.
+ ''',
)
- map_user.r(0x00f7, 'storage close request',
+ map_user.r(0x00f7, 'storage close',
+ define='CMSG_CLOSE_STORAGE',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[HUMAN],
+ post=[MAGIC, SCRIPT, 0x00f8, 0x2b01, 0x3011],
+ xpost=[0x0078, 0x007c, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00b6, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b05, 0x3022],
+ desc='''
+ Close your storage.
+ ''',
)
- map_user.s(0x00f8, 'storage closed',
+ map_user.s(0x00f8, 'storage closed notify',
+ define='SMSG_PLAYER_STORAGE_CLOSE',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[GM, 0x00e6, 0x00f7],
+ post=[PRETTY],
+ desc='''
+ Your storage was closed.
+ ''',
)
- map_user.r(0x00f9, 'create party request',
+ map_user.r(0x00f9, 'party create',
+ define='CMSG_PARTY_CREATE',
fixed=[
at(0, u16, 'packet id'),
at(2, party_name, 'party name'),
],
fixed_size=26,
+ pre=[HUMAN],
+ post=[0x00fa, 0x110, 0x3020],
+ desc='''
+ Create a new party with yourself.
+ ''',
)
- map_user.s(0x00fa, 'create party',
+ map_user.s(0x00fa, 'party create result',
+ define='SMSG_PARTY_CREATE',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'flag'),
],
fixed_size=3,
- )
- map_user.s(0x00fb, 'party info',
+ pre=[GM, 0x00f9, 0x3820],
+ post=[PRETTY],
+ desc='''
+ Result of creating a new party.
+
+ flag 0: success
+ flag 1: bad name
+ flag 2: already in party
+ ''',
+ )
+ map_user.s(0x00fb, 'party info notify',
+ define='SMSG_PARTY_INFO',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2426,69 +3739,130 @@ def main():
at(45, u8, 'online'),
],
repeat_size=46,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0089, 0x3821, 0x3825],
+ post=[PRETTY],
+ desc='''
+ Full info about your party.
+ ''',
)
- map_user.r(0x00fc, 'party invite request',
+ map_user.r(0x00fc, 'party invite',
+ define='CMSG_PARTY_INVITE',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[HUMAN],
+ post=[0x00fd, 0x00fe],
+ desc='''
+ Invite player to your party.
+ ''',
)
- map_user.s(0x00fd, 'party invite response',
+ map_user.s(0x00fd, 'party invite result',
+ define='SMSG_PARTY_INVITE_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, char_name, 'char name'),
at(26, u8, 'flag'),
],
fixed_size=27,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x00fc, 0x00ff, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d, 0x3822],
+ post=[PRETTY],
+ desc='''
+ Party invitation response.
+
+ flag 0: already in party
+ flag 1: reject
+ flag 2: accept
+ flag 3: party full
+ flag 4: same party
+ ''',
)
- map_user.s(0x00fe, 'party invite succeeded',
+ map_user.s(0x00fe, '(reverse) party invitation',
+ define='SMSG_PARTY_INVITED',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, party_name, 'party name'),
],
fixed_size=30,
+ pre=[0x00fc],
+ post=[0x00ff],
+ desc='''
+ You're invited to join someone's party.
+ ''',
)
- map_user.r(0x00ff, 'party join request',
+ map_user.r(0x00ff, '(reverse) party invitation result',
+ define='CMSG_PARTY_INVITED',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, u32, 'flag'),
],
fixed_size=10,
+ pre=[0x00fe],
+ post=[0x00fd, 0x0110, 0x3022],
+ desc='''
+ Reply to party invitation.
+ ''',
)
- map_user.r(0x0100, 'party leave request',
+ map_user.r(0x0100, 'party leave',
+ define='CMSG_PARTY_LEAVE',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[HUMAN],
+ post=[0x3024],
+ desc='''
+ I'm sick of your lame party.
+ ''',
)
- map_user.s(0x0101, 'party settings',
+ map_user.s(0x0101, 'party option notify',
+ define='SMSG_PARTY_SETTINGS',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'exp'),
at(4, u16, 'item'),
],
fixed_size=6,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0089, 0x3821, 0x3823],
+ post=[PRETTY],
+ desc='''
+ Party settings changed.
+ ''',
)
- map_user.r(0x0102, 'party settings request',
+ map_user.r(0x0102, 'party option',
+ define='CMSG_PARTY_SETTINGS',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'exp'),
at(4, u16, 'item'),
],
fixed_size=6,
+ pre=[HUMAN],
+ post=[0x3023],
+ desc='''
+ Please change party settings.
+ ''',
)
- map_user.r(0x0103, 'party kick request',
+ map_user.r(0x0103, 'party kick',
+ define='CMSG_PARTY_KICK',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, char_name, 'unused char name'),
],
fixed_size=30,
+ pre=[HUMAN],
+ post=[0x3024],
+ desc='''
+ Forcibly remove party member.
+ ''',
)
- map_user.s(0x0105, 'party leave',
+ # 0x0104 define='SMSG_PARTY_MOVE',
+ map_user.s(0x0105, 'party left notify',
+ define='SMSG_PARTY_LEAVE',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -2496,8 +3870,14 @@ def main():
at(30, u8, 'flag'),
],
fixed_size=31,
+ pre=[0x3824, 0x3826],
+ post=[PRETTY],
+ desc='''
+ Somebody got tired of your party.
+ ''',
)
- map_user.s(0x0106, 'update party member hp',
+ map_user.s(0x0106, 'party hp notify',
+ define='SMSG_PARTY_UPDATE_HP',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -2505,8 +3885,14 @@ def main():
at(8, u16, 'max hp'),
],
fixed_size=10,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Party member hp update.
+ ''',
)
- map_user.s(0x0107, 'update party member coords',
+ map_user.s(0x0107, 'party xy notify',
+ define='SMSG_PARTY_UPDATE_COORDS',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -2514,8 +3900,14 @@ def main():
at(8, u16, 'y'),
],
fixed_size=10,
- )
- map_user.r(0x0108, 'party message request',
+ pre=[TIMER],
+ post=[PRETTY],
+ desc='''
+ Party member location update.
+ ''',
+ )
+ map_user.r(0x0108, 'party message',
+ define='CMSG_PARTY_MESSAGE',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2525,8 +3917,14 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
- )
- map_user.s(0x0109, 'party message',
+ pre=[HUMAN],
+ post=[0x2b0e, 0x3003, 0x3027],
+ desc='''
+ Talk privately to your party.
+ ''',
+ )
+ map_user.s(0x0109, 'party message notify',
+ define='SMSG_PARTY_MESSAGE',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2536,16 +3934,17 @@ def main():
repeat=[
at(0, u8, 'c'),
],
- repeat_size=1
- )
- map_user.s(0x010c, 'MVP (unused)',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, block_id, 'block id'),
- ],
- fixed_size=6,
+ repeat_size=1,
+ pre=[0x3827],
+ post=[PRETTY],
+ desc='''
+ Somebody in your party is talking to you.
+ ''',
)
- map_user.s(0x010e, 'raise a skill',
+ # 0x010c define='SMSG_MVP',
+ map_user.s(0x010e, 'skill raise result',
+ # define='SMSG_GUILD_SKILL_UP',
+ define='SMSG_PLAYER_SKILL_UP',
fixed=[
at(0, u16, 'packet id'),
at(2, skill_id, 'skill id'),
@@ -2555,8 +3954,14 @@ def main():
at(10, u8, 'can raise'),
],
fixed_size=11,
- )
- map_user.s(0x010f, 'player skills',
+ pre=[0x0112],
+ post=[PRETTY],
+ desc='''
+ Successfully raised a skill.
+ ''',
+ )
+ map_user.s(0x010f, 'skill info notify',
+ define='SMSG_PLAYER_SKILLS',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2566,8 +3971,14 @@ def main():
at(0, skill_info, 'info'),
],
repeat_size=37,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Enumeration of your skills.
+ ''',
)
map_user.s(0x0110, 'skill failed',
+ define='SMSG_SKILL_FAILED',
fixed=[
at(0, u16, 'packet id'),
at(2, skill_id, 'skill id'),
@@ -2577,21 +3988,42 @@ def main():
at(9, u8, 'type'),
],
fixed_size=10,
+ pre=[0x0089, 0x00bf, 0x00e4, 0x00f9, 0x00ff],
+ post=[PRETTY],
+ desc='''
+ Failed to perform a skill.
+ ''',
)
- map_user.r(0x0112, 'request a skill lvl up',
+ map_user.r(0x0112, 'skill raise',
+ define='CMSG_SKILL_LEVELUP_REQUEST',
fixed=[
at(0, u16, 'packet id'),
at(2, skill_id, 'skill id'),
],
fixed_size=4,
+ pre=[HUMAN],
+ post=[0x00b0, 0x010e, 0x010f],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Spend skill points to raise your skills.
+ ''',
)
- map_user.r(0x0118, 'stop attack request',
+ # 0x0113 define='CMSG_SKILL_USE_BEING',
+ # 0x0116 define='CMSG_SKILL_USE_POSITION',
+ map_user.r(0x0118, 'attack stop',
+ define='CMSG_PLAYER_STOP_ATTACK',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[HUMAN],
+ post=[IDLE],
+ desc='''
+ Cancel a continuous attack.
+ ''',
)
- map_user.s(0x0119, 'change player status',
+ map_user.s(0x0119, 'player option notify',
+ define='SMSG_PLAYER_STATUS_CHANGE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -2601,8 +4033,16 @@ def main():
at(12, u8, 'zero'),
],
fixed_size=13,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Update 3/4ths of the option fields.
+ ''',
)
- map_user.s(0x0139, 'move player to within attack range',
+ # 0x011a define='SMSG_SKILL_NO_DAMAGE',
+ # 0x011b define='CMSG_SKILL_USE_MAP',
+ map_user.s(0x0139, 'player move attack range notify',
+ define='SMSG_PLAYER_MOVE_TO_ATTACK',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -2613,29 +4053,54 @@ def main():
at(14, u16, 'range'),
],
fixed_size=16,
+ pre=[0x0089],
+ post=[PRETTY],
+ desc='''
+ You are moving to enter attack range.
+ ''',
)
- map_user.s(0x013a, 'player attack range',
+ map_user.s(0x013a, 'player attack range notify',
+ define='SMSG_PLAYER_ATTACK_RANGE',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'attack range'),
],
fixed_size=4,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Update attack range field.
+ ''',
)
- map_user.s(0x013b, 'player arrow message',
+ map_user.s(0x013b, 'player arrow fail notify',
+ define='SMSG_PLAYER_ARROW_MESSAGE',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'type'),
],
fixed_size=4,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x0089, 0x00a9],
+ post=[PRETTY],
+ desc='''
+ Arrow status: failed (0) or equipped (3, ignored).
+ ''',
)
- map_user.s(0x013c, 'player arrow equip',
+ map_user.s(0x013c, 'player arrow equip notify',
+ define='SMSG_PLAYER_ARROW_EQUIP',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
],
fixed_size=4,
+ pre=[0x007d, 0x00a9],
+ post=[PRETTY],
+ desc='''
+ Arrow equip inventory slot.
+ ''',
)
+ # 0x013e define='SMSG_SKILL_CASTING',
map_user.s(0x0141, 'player stat update 3',
+ define='SMSG_PLAYER_STAT_UPDATE_3',
fixed=[
at(0, u16, 'packet id'),
at(2, sp, 'sp type'),
@@ -2644,65 +4109,142 @@ def main():
at(10, u32, 'value b e'),
],
fixed_size=14,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Update base and cumulative (?) stat points.
+ ''',
)
- map_user.s(0x0142, 'npc integer input',
+ map_user.s(0x0142, '(reverse) script input integer',
+ define='SMSG_NPC_INT_INPUT',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
+ pre=[SCRIPT],
+ post=[0x0143],
+ desc='''
+ Npc wants an integer.
+ ''',
)
- map_user.r(0x0143, 'npc integer response',
+ map_user.r(0x0143, '(reverse) script input integer result',
+ define='CMSG_NPC_INT_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, u32, 'input int value'),
],
fixed_size=10,
+ pre=[0x0142],
+ post=[MAGIC, SCRIPT],
+ xpost=[0x0078, 0x007c, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00b6, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Npc can have an integer.
+ ''',
)
- map_user.r(0x0146, 'npc close request',
+ map_user.r(0x0146, '(reverse) script close response',
+ define='CMSG_NPC_CLOSE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
+ pre=[0x00b6],
+ post=[MAGIC, SCRIPT],
+ xpost=[0x0078, 0x007c, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00b6, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Interactive npc chat closed, maybe finish noninteractively now.
+ ''',
)
- map_user.s(0x0147, 'single skill info (unused)',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, skill_info, 'info'),
- ],
- fixed_size=39,
- )
- map_user.s(0x0148, 'being resurrected',
+ map_user.s(0x0148, 'being resurrected notify',
+ define='SMSG_BEING_RESURRECT',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, u16, 'type'),
],
fixed_size=8,
- )
- map_user.r(0x014d, 'guild check master (unused)',
- fixed=[
- at(0, u16, 'packet id'),
- ],
- fixed_size=2,
- )
+ pre=[GM],
+ post=[PRETTY],
+ desc='''
+ It's alive!
+ ''',
+ )
+ # 0x0149 define='CMSG_ADMIN_MUTE',
+ # 0x014c define='SMSG_GUILD_ALIANCE_INFO',
+ # 0x014d define='CMSG_GUILD_CHECK_MASTER',
+ # 0x014e define='SMSG_GUILD_MASTER_OR_MEMBER',
+ # 0x014f define='CMSG_GUILD_REQUEST_INFO',
+ # 0x0151 define='CMSG_GUILD_REQUEST_EMBLEM',
+ # 0x0152 define='SMSG_GUILD_EMBLEM',
+ # 0x0153 define='CMSG_GUILD_CHANGE_EMBLEM',
+ # 0x0154 define='SMSG_GUILD_MEMBER_LIST',
+ # 0x0155 define='CMSG_GUILD_CHANGE_MEMBER_POS',
+ # 0x0156 define='SMSG_GUILD_MEMBER_POS_CHANGE',
+ # 0x0159 define='CMSG_GUILD_LEAVE',
+ # 0x015a define='SMSG_GUILD_LEAVE',
+ # 0x015b define='CMSG_GUILD_EXPULSION',
+ # 0x015c define='SMSG_GUILD_EXPULSION',
+ # 0x015d define='CMSG_GUILD_BREAK',
+ # 0x015e define='SMSG_GUILD_BROKEN',
+ # 0x0160 define='SMSG_GUILD_POS_INFO_LIST',
+ # 0x0161 define='CMSG_GUILD_CHANGE_POS_INFO',
+ # 0x0162 define='SMSG_GUILD_SKILL_INFO',
+ # 0x0163 define='SMSG_GUILD_EXPULSION_LIST',
+ # 0x0165 define='CMSG_GUILD_CREATE',
+ # 0x0166 define='SMSG_GUILD_POS_NAME_LIST',
+ # 0x0167 define='SMSG_GUILD_CREATE_RESPONSE',
+ # 0x0168 define='CMSG_GUILD_INVITE',
+ # 0x0169 define='SMSG_GUILD_INVITE_ACK',
+ # 0x016a define='SMSG_GUILD_INVITE',
+ # 0x016b define='CMSG_GUILD_INVITE_REPLY',
+ # 0x016c define='SMSG_GUILD_POSITION_INFO',
+ # 0x016d define='SMSG_GUILD_MEMBER_LOGIN',
+ # 0x016e define='CMSG_GUILD_CHANGE_NOTICE',
+ # 0x016f define='SMSG_GUILD_NOTICE',
+ # 0x0170 define='CMSG_GUILD_ALLIANCE_REQUEST',
+ # 0x0171 define='SMSG_GUILD_REQ_ALLIANCE',
+ # 0x0172 define='CMSG_GUILD_ALLIANCE_REPLY',
+ # 0x0173 define='SMSG_GUILD_REQ_ALLIANCE_ACK',
+ # 0x0174 define='SMSG_GUILD_POSITION_CHANGED',
+ # 0x017e define='CMSG_GUILD_MESSAGE',
+ # 0x017f define='SMSG_GUILD_MESSAGE',
+ # 0x0180 define='CMSG_GUILD_OPPOSITION',
+ # 0x0181 define='SMSG_GUILD_OPPOSITION_ACK',
+ # 0x0183 define='CMSG_GUILD_ALLIANCE_DELETE',
+ # 0x0184 define='SMSG_GUILD_DEL_ALLIANCE',
map_user.r(0x018a, 'client quit',
+ define='CMSG_CLIENT_QUIT',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'unused'),
],
fixed_size=4,
+ pre=[NOTHING],
+ post=[0x018b],
+ desc='''
+ Request explicit end of connection.
+ ''',
)
- map_user.s(0x018b, 'map quit response',
+ map_user.s(0x018b, 'client quit result',
+ define='SMSG_MAP_QUIT_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'okay'),
],
fixed_size=4,
+ pre=[GM, 0x00b2, 0x018a],
+ post=[PRETTY],
+ desc='''
+ Result of explicit end of connection.
+ ''',
)
- map_user.s(0x0195, 'guild party info (unused)',
+ # 0x0190 define='CMSG_SKILL_USE_POSITION_MORE',
+ # 0x0193 define='CMSG_SOLVE_CHAR_NAME',
+ # 0x0194 define='SMSG_SOLVE_CHAR_NAME',
+ map_user.s(0x0195, 'guild party info notify',
+ define='SMSG_PLAYER_GUILD_PARTY_INFO',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -2712,8 +4254,14 @@ def main():
at(78, str24, 'guild pos again'),
],
fixed_size=102,
+ pre=[0x0094],
+ post=[PRETTY],
+ desc='''
+ Name of player's party and guild.
+ ''',
)
- map_user.s(0x0196, 'being status change',
+ map_user.s(0x0196, 'being status change notify',
+ define='SMSG_BEING_STATUS_CHANGE',
fixed=[
at(0, u16, 'packet id'),
at(2, status_change, 'sc type'),
@@ -2721,16 +4269,32 @@ def main():
at(8, u8, 'flag'),
],
fixed_size=9,
- )
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Being adds/removes a persistent status effect.
+ ''',
+ )
+ # 0x0199 define='SMSG_PVP_MAP_MODE',
+ # 0x019a define='SMSG_PVP_SET',
map_user.s(0x019b, 'being effect',
+ define='SMSG_BEING_SELFEFFECT',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, u32, 'type'),
],
fixed_size=10,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0089],
+ post=[PRETTY],
+ desc='''
+ Being runs a one-shot (?) effect.
+ ''',
)
- map_user.s(0x01b1, 'trade item add response',
+ # 0x019C define='CMSG_ADMIN_LOCAL_ANNOUNCE',
+ # 0x019D define='CMSG_ADMIN_HIDE',
+ map_user.s(0x01b1, 'trade add result',
+ define='SMSG_TRADE_ITEM_ADD_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
@@ -2738,8 +4302,16 @@ def main():
at(6, u8, 'fail'),
],
fixed_size=7,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Result of trying to add an item/zeny to your trade offer.
+ ''',
)
- map_user.s(0x01c8, 'use inventory item succeeded',
+ # 0x01b6 define='SMSG_GUILD_BASIC_INFO',
+ # 0x01b9 define='SMSG_SKILL_CAST_CANCEL',
+ map_user.s(0x01c8, 'use item result',
+ define='SMSG_PLAYER_INVENTORY_USE',
fixed=[
at(0, u16, 'packet id'),
at(2, ioff2, 'ioff2'),
@@ -2749,15 +4321,29 @@ def main():
at(12, u8, 'ok'),
],
fixed_size=13,
+ pre=[0x00a7],
+ post=[PRETTY],
+ desc='''
+ You used (or tried to use) an item in your inventory.
+
+ Currently used only in the success case.
+ ''',
)
- map_user.s(0x01d4, 'npc string input',
+ map_user.s(0x01d4, '(reverse) script input string',
+ define='SMSG_NPC_STR_INPUT',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
],
fixed_size=6,
- )
- map_user.r(0x01d5, 'npc string response',
+ pre=[SCRIPT],
+ post=[0x01d5],
+ desc='''
+ Npc wants a string.
+ ''',
+ )
+ map_user.r(0x01d5, '(reverse) script input string result',
+ define='CMSG_NPC_STR_RESPONSE',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2768,8 +4354,15 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x01d4],
+ post=[MAGIC, SCRIPT],
+ xpost=[0x0078, 0x007c, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00b6, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Npc can have an integer.
+ ''',
)
- map_user.s(0x01d7, 'change being appearance (unused)',
+ map_user.s(0x01d7, 'being change look',
+ define='SMSG_BEING_CHANGE_LOOKS2',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -2778,9 +4371,18 @@ def main():
at(9, item_name_id, 'shield'),
],
fixed_size=11,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[PRETTY],
+ desc='''
+ Being change appearance.
+
+ This may be a weapon type, an item nameid, or just a value -
+ it all depends on which look type.
+ ''',
)
# very similar to, but not compatible with, 0x01d9 and 0x01da
- map_user.s(0x01d8, 'player update 1',
+ map_user.s(0x01d8, 'player appear notify',
+ define='SMSG_PLAYER_UPDATE_1',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -2811,9 +4413,17 @@ def main():
at(52, u16, 'unused'),
],
fixed_size=54,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[0x0094, 0x00b2],
+ desc='''
+ A player appeared (unmoving).
+
+ Like 0x0078 but for players, and only in the "enter area" case.
+ ''',
)
# very similar to, but not compatible with, 0x01d8 and 0x01da
- map_user.s(0x01d9, 'player update 2',
+ map_user.s(0x01d9, 'player appear notify',
+ define='SMSG_PLAYER_UPDATE_2',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -2843,9 +4453,17 @@ def main():
at(51, u16, 'unused'),
],
fixed_size=53,
+ pre=[0x007d],
+ post=[0x0094],
+ desc='''
+ A wild player appeared!
+
+ Like 0x0078 but for players, but only in the "spawn on map" case.
+ ''',
)
# very similar to, but not compatible with, 0x01d8 and 0x01d9
- map_user.s(0x01da, 'player move',
+ map_user.s(0x01da, 'player move notify',
+ define='SMSG_PLAYER_MOVE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
@@ -2877,8 +4495,17 @@ def main():
at(58, u16, 'unused'),
],
fixed_size=60,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[0x0094],
+ desc='''
+ A player appeared, moving.
+
+ Like 0x007b but for players.
+ ''',
)
map_user.s(0x01de, 'deal skill damage',
+ # define='CMSG_LOGIN_REGISTER2', with a different body ...
+ define='SMSG_SKILL_DAMAGE',
fixed=[
at(0, u16, 'packet id'),
at(2, skill_id, 'skill id'),
@@ -2893,8 +4520,14 @@ def main():
at(32, u8, 'type or hit'),
],
fixed_size=33,
- )
- map_user.s(0x01ee, 'player inventory',
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x0089],
+ post=[PRETTY],
+ desc='''
+ Took damage from a skill.
+ ''',
+ )
+ map_user.s(0x01ee, 'inventory list notify',
+ define='SMSG_PLAYER_INVENTORY',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2913,8 +4546,14 @@ def main():
at(16, u16, 'card3'),
],
repeat_size=18,
- )
- map_user.s(0x01f0, 'storage item list',
+ pre=[0x007d],
+ post=[PRETTY],
+ desc='''
+ List of all items in inventory.
+ ''',
+ )
+ map_user.s(0x01f0, 'storage list notify',
+ define='SMSG_PLAYER_STORAGE_ITEMS',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -2933,16 +4572,32 @@ def main():
at(16, u16, 'card3'),
],
repeat_size=18,
+ pre=[GM, SCRIPT, 0x3810],
+ post=[PRETTY],
+ desc='''
+ List of all items in storage.
+ ''',
)
- map_user.s(0x020c, 'set being ip',
+ map_user.s(0x020c, 'player ip notify',
+ define='SMSG_BEING_IP_RESPONSE',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'block id'),
at(6, ip4, 'ip'),
],
fixed_size=10,
+ pre=[0x0094],
+ post=[PRETTY],
+ desc='''
+ Show a player's (hashed) IP, for detecting alts.
+
+ The hash is rerandomized every restart.
+ ''',
)
+ # 0x0210 define='CMSG_ONLINE_LIST',
+ # 0x0211 define='SMSG_ONLINE_LIST',
map_user.s(0x0212, 'npc command',
+ define='SMSG_NPC_COMMAND',
fixed=[
at(0, u16, 'packet id'),
at(2, block_id, 'npc id'),
@@ -2952,16 +4607,60 @@ def main():
at(14, u16, 'y'),
],
fixed_size=16,
+ pre=[NOTHING],
+ post=[PRETTY],
+ desc='''
+ Make an npc do fancy things (for Manaplus).
+ ''',
)
-
- # login char
- login_char.r(0x2709, 'reload gm accounts request',
+ # 0x0213 define='CMSG_SET_STATUS',
+ # 0x0214 define='SMSG_QUEST_SET_VAR',
+ map_user.s(0x0214, 'send quest',
+ define='SMSG_QUEST_SET_VAR',
fixed=[
at(0, u16, 'packet id'),
+ at(2, u16, 'variable'),
+ at(4, u32, 'value'),
],
- fixed_size=2,
- )
- login_char.r(0x2710, 'add char server request',
+ fixed_size=8,
+ pre=[NOTHING],
+ post=[PRETTY],
+ desc='''
+ Set Quest Log Variable to Value.
+ ''',
+ )
+ # 0x0215 define='SMSG_QUEST_PLAYER_VARS',
+ map_user.s(0x0215, 'send all quest',
+ define='SMSG_QUEST_PLAYER_VARS',
+ head=[
+ at(0, u16, 'packet id'),
+ at(2, u16, 'packet length'),
+ ],
+ head_size=4,
+ repeat=[
+ at(0, u16, 'variable'),
+ at(2, u32, 'value'),
+ ],
+ repeat_size=6,
+ pre=[NOTHING],
+ post=[PRETTY],
+ desc='''
+ Set All Quest Log Variable to Value.
+ ''',
+ )
+ # 0x0220 define='SMSG_BEING_NAME_RESPONSE2',
+ # 0x0221 define='SMSG_CHAR_CREATE_SUCCEEDED2',
+ # 0x0222 define='CMSG_CHAT_MESSAGE2',
+ # 0x0223 define='SMSG_BEING_CHAT2',
+ # 0x0224 define='SMSG_PLAYER_CHAT2',
+ # 0x0225 define='SMSG_BEING_MOVE3',
+ # 0x0226 define='SMSG_MAP_MASK',
+ # 0x0227 define='SMSG_MAP_MUSIC',
+ # 0x0228 define='SMSG_NPC_CHANGETITLE',
+
+ # TOC_LOGINCHAR
+ # login char
+ login_char.r(0x2710, 'server connect char',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
@@ -2975,15 +4674,25 @@ def main():
at(84, u16, 'is new'),
],
fixed_size=86,
+ pre=[BOOT],
+ post=[0x2711, 0x2732],
+ desc='''
+ Become an authenticated char server.
+ ''',
)
- login_char.s(0x2711, 'add char server result',
+ login_char.s(0x2711, 'server connect char result',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'code'),
],
fixed_size=3,
+ pre=[0x2710],
+ post=[FINISH, IDLE],
+ desc='''
+ Result of auth'ing as a char server.
+ ''',
)
- login_char.r(0x2712, 'account auth request',
+ login_char.r(0x2712, 'account auth',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -2993,6 +4702,11 @@ def main():
at(15, ip4, 'ip'),
],
fixed_size=19,
+ pre=[0x0065],
+ post=[0x2713, 0x2729],
+ desc='''
+ Check whether client's cookies are okay.
+ ''',
)
login_char.s(0x2713, 'account auth result',
fixed=[
@@ -3000,35 +4714,60 @@ def main():
at(2, account_id, 'account id'),
at(6, u8, 'invalid'),
at(7, account_email, 'email'),
- at(47, time32, 'connect until'),
+ at(47, time32, 'unused connect until'),
],
fixed_size=51,
+ pre=[0x2712],
+ post=[0x006b, 0x006c],
+ desc='''
+ Send account auth status to tmwa-char.
+
+ Status:
+ 0: good
+ 1: bad
+ ''',
)
- login_char.r(0x2714, 'online count',
+ login_char.r(0x2714, 'online count notify',
fixed=[
at(0, u16, 'packet id'),
at(2, u32, 'users'),
],
fixed_size=6,
+ pre=[TIMER],
+ post=[IDLE],
+ desc='''
+ Update count of online users.
+
+ This occurs every few seconds, so is also used for antifreeze if enabled.
+ ''',
)
- login_char.r(0x2716, 'email limit request',
+ login_char.r(0x2716, 'email limit',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[0x0065],
+ post=[0x2717],
+ desc='''
+ Fetch current email (and previously, validity limit) for an account.
+ ''',
)
login_char.s(0x2717, 'email limit result',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, account_email, 'email'),
- at(46, time32, 'connect until'),
+ at(46, time32, 'unused connect until'),
],
fixed_size=50,
+ pre=[0x2716],
+ post=[IDLE],
+ desc='''
+ Yield current email (and previously, validity limit) for an account.
+ ''',
)
- # 0x2b0a
- login_char.r(0x2720, 'become gm request',
+ login_char.r(0x2720, 'become gm account',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3037,17 +4776,26 @@ def main():
head_size=8,
repeat=[at(0, u8, 'c')],
repeat_size=1,
+ pre=[0x2b0a],
+ post=[0x2721, 0x2732],
+ desc='''
+ Grant privileges by password.
+ ''',
)
- login_char.s(0x2721, 'become gm reply',
+ login_char.s(0x2721, 'become gm account reply',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, gm, 'gm level'),
],
fixed_size=10,
+ pre=[0x2720],
+ post=[0x2b0b],
+ desc='''
+ Maybe granted privileges?
+ ''',
)
- # 0x2b0c
- login_char.r(0x2722, 'account email change request',
+ login_char.r(0x2722, 'account email change',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3055,14 +4803,24 @@ def main():
at(46, account_email, 'new email'),
],
fixed_size=86,
+ pre=[0x2b0c],
+ post=[IDLE],
+ desc='''
+ Change account email.
+ ''',
)
- login_char.s(0x2723, 'changesex reply',
+ login_char.s(0x2723, 'changesex result/notify',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, sex, 'sex'),
],
fixed_size=7,
+ pre=[0x2727, 0x793c],
+ post=[0x2b0d],
+ desc='''
+ Set was flipped (if from char server) or set to an absolute value (if set from admin).
+ ''',
)
login_char.r(0x2724, 'block status',
fixed=[
@@ -3071,6 +4829,15 @@ def main():
at(6, u32, 'status'),
],
fixed_size=10,
+ pre=[0x2b0e],
+ post=[0x2731],
+ desc='''
+ Set an account's blocked status, see 0x006a but add one.
+
+ The only really useful values here are 0 ("unblock") and
+ 5 ("block"). Don't use this for state 7 ("ban") because it
+ requires a timestamp argument.
+ ''',
)
login_char.r(0x2725, 'ban add',
fixed=[
@@ -3079,6 +4846,12 @@ def main():
at(6, human_time_diff, 'ban add'),
],
fixed_size=18,
+ pre=[0x2b0e],
+ post=[0x2731],
+ desc='''
+ Adjust the time of an account's temporary ban. If the account
+ is not already banned, first set the ban to the current time.
+ ''',
)
# evil packet, see also 0x794e
login_admin.s(0x2726, 'broadcast',
@@ -3092,47 +4865,94 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x794e],
+ post=[0x3800],
+ desc='''
+ This packet is evil.
+ ''',
)
- login_char.r(0x2727, 'change sex request',
+ login_char.r(0x2727, 'change sex',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[0x2b0e],
+ post=[0x2723],
+ desc='''
+ Flip account's gender.
+ ''',
)
- # 0x2b10, 0x2b11
- for (id, cat) in [
- (0x2728, login_char.r),
- (0x2729, login_char.s),
- ]:
- cat(id, 'update account reg2',
- head=[
- at(0, u16, 'packet id'),
- at(2, u16, 'packet length'),
- at(4, account_id, 'account id'),
- ],
- head_size=8,
- repeat=[
- at(0, var_name, 'name'),
- at(32, u32, 'value'),
- ],
- repeat_size=36,
- )
- login_char.r(0x272a, 'unban request',
+ login_char.r(0x2728, 'update account reg2',
+ head=[
+ at(0, u16, 'packet id'),
+ at(2, u16, 'packet length'),
+ at(4, account_id, 'account id'),
+ ],
+ head_size=8,
+ repeat=[
+ at(0, var_name, 'name'),
+ at(32, u32, 'value'),
+ ],
+ repeat_size=36,
+ pre=[0x2b10],
+ post=[0x2729],
+ desc='''
+ Update account's login-stored variables.
+
+ These start with ## and are unused because:
+
+ 1. They didn't work at all before I started working on the server.
+ 2. We only run one char-server so we might as well store vars there.
+ 3. The meaning would be weird anyway.
+ ''',
+ )
+ login_char.s(0x2729, 'update account reg2 notify',
+ head=[
+ at(0, u16, 'packet id'),
+ at(2, u16, 'packet length'),
+ at(4, account_id, 'account id'),
+ ],
+ head_size=8,
+ repeat=[
+ at(0, var_name, 'name'),
+ at(32, u32, 'value'),
+ ],
+ repeat_size=36,
+ pre=[0x2712, 0x2728],
+ post=[0x2b11],
+ desc='''
+ Updated account's ##variables.
+
+ This is sent both in response to 0x2728 and in response to
+ 0x2712 cookie check.
+ '''
+ )
+ login_char.r(0x272a, 'unban',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[0x2b0e],
+ post=[IDLE],
+ desc='''
+ Clear an account's temporary ban date.
+ ''',
)
- login_char.s(0x2730, 'account deleted',
+ login_char.s(0x2730, 'account deleted notify',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[0x7932],
+ post=[0x2afe, 0x2b12, 0x2b13, 0x3821, 0x3824, 0x3826],
+ desc='''
+ Account just doesn't exist anymore.
+ ''',
)
- login_char.s(0x2731, 'status or ban changed',
+ login_char.s(0x2731, 'status or ban changed notify',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3140,8 +4960,13 @@ def main():
at(7, time32, 'status or ban until'),
],
fixed_size=11,
+ pre=[0x2724, 0x2725, 0x7936, 0x794a, 0x794c],
+ post=[0x2b14],
+ desc='''
+ Account has a new ban date or block status.
+ ''',
)
- login_char.s(0x2732, 'gm account list',
+ login_char.s(0x2732, 'gm account list notify',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3152,8 +4977,13 @@ def main():
at(4, gm1, 'gm level'),
],
repeat_size=5,
+ pre=[TIMER, 0x2710, 0x2720, 0x793e, 0x7955],
+ post=[0x2b15],
+ desc='''
+ Send GM accounts to all character servers.
+ ''',
)
- login_char.r(0x2740, 'change password request',
+ login_char.r(0x2740, 'change password',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3161,24 +4991,29 @@ def main():
at(30, account_pass, 'new pass'),
],
fixed_size=54,
+ pre=[0x0061],
+ post=[0x2741],
+ desc='''
+ Change password of an account.
+ ''',
)
- login_char.s(0x2741, 'change password reply',
+ login_char.s(0x2741, 'change password result',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, u8, 'status'),
],
fixed_size=7,
+ pre=[0x2740],
+ post=[0x0062],
+ desc='''
+ Password changed or not.
+ ''',
)
+ # TOC_CHARMAP
# char map
- char_map.r(0x2af7, 'reload gm db',
- fixed=[
- at(0, u16, 'packet id'),
- ],
- fixed_size=2,
- )
- char_map.r(0x2af8, 'add map server request',
+ char_map.r(0x2af8, 'server connect map',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
@@ -3188,15 +5023,27 @@ def main():
at(58, u16, 'port'),
],
fixed_size=60,
+ pre=[TIMER],
+ post=[0x2af9, 0x2b15],
+ desc='''
+ Become an authenticated map server.
+ ''',
)
- char_map.s(0x2af9, 'add map server result',
+ char_map.s(0x2af9, 'server connect map result',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'code'),
],
fixed_size=3,
+ pre=[0x2af8],
+ post=[FINISH, SCRIPT, 0x2afa],
+ desc='''
+ Did I auth?
+ ''',
)
# wtf duplicate v
+ # formerly 0x2afa, now fixed, but now sorted wrong
+ # (should go below, but don't want to break diff)
char_map.r(0x2afa, 'map list',
head=[
at(0, u16, 'packet id'),
@@ -3207,25 +5054,34 @@ def main():
at(0, map_name, 'map name'),
],
repeat_size=16,
+ pre=[0x2af9],
+ post=[0x2afb, 0x2b04],
+ desc='''
+ Tell the char server what maps I have.
+ ''',
)
# wtf duplicate ^
- char_map.s(0x2afa, 'itemfrob',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, item_name_id4, 'source item id'),
- at(6, item_name_id4, 'dest item id'),
- ],
- fixed_size=10,
- )
- char_map.s(0x2afb, 'map list ack',
+ # formerly 0x2afa, now fixed, but now sorted wrong
+ # (should go below, but don't want to break diff)
+ #
+ # (now erased)
+
+ char_map.s(0x2afb, 'map list result',
fixed=[
at(0, u16, 'packet id'),
at(2, u8, 'unknown'),
- at(3, char_name, 'whisper name'),
+ at(3, char_name, 'unused whisper name'),
],
fixed_size=27,
+ pre=[0x2afa],
+ post=[FINISH, IDLE],
+ desc='''
+ I got your maps.
+
+ (having pronoun consistency problems here)
+ ''',
)
- char_map.r(0x2afc, 'character auth request',
+ char_map.r(0x2afc, 'character auth',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3235,19 +5091,30 @@ def main():
at(18, ip4, 'ip'),
],
fixed_size=22,
+ pre=[0x0072],
+ post=[0x2afd, 0x2afe],
+ desc='''
+ Check whether client's cookies are okay.
+ ''',
)
- char_map.s(0x2afd, 'character auth and data',
+ char_map.s(0x2afd, 'character auth success',
payload=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
at(4, account_id, 'account id'),
at(8, u32, 'login id2'),
- at(12, time32, 'connect until'),
+ at(12, time32, 'unused connect until'),
at(16, u16, 'packet tmw version'),
at(18, char_key, 'char key'),
at(None, char_data, 'char data'),
],
payload_size=None,
+ pre=[0x2afc],
+ post=[0x0073, 0x0081, 0x0091, 0x3005, 0x3021],
+ xpost=[GM, SCRIPT, 0x0080, 0x0088, 0x00a0, 0x00ac, 0x00b0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d7, 0x01d8, 0x01da, 0x2b01, 0x2b05, 0x3011, 0x3022],
+ desc='''
+ Account authentication succeeded.
+ ''',
)
char_map.s(0x2afe, 'character auth error',
fixed=[
@@ -3255,6 +5122,11 @@ def main():
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[0x0068, 0x2730, 0x2afc],
+ post=[0x0081],
+ desc='''
+ Send account id to tmwa-map for disconnection.
+ ''',
)
char_map.r(0x2aff, 'user list',
head=[
@@ -3267,6 +5139,11 @@ def main():
at(0, char_id, 'char id'),
],
repeat_size=4,
+ pre=[TIMER],
+ post=[IDLE],
+ desc='''
+ Where can everybody be found?
+ ''',
)
char_map.s(0x2b00, 'total users',
fixed=[
@@ -3274,6 +5151,11 @@ def main():
at(2, u32, 'users'),
],
fixed_size=6,
+ pre=[TIMER],
+ post=[IDLE],
+ desc='''
+ How many people are there in total?
+ ''',
)
char_map.r(0x2b01, 'character save',
payload=[
@@ -3285,8 +5167,13 @@ def main():
at(None, char_data, 'char data'),
],
payload_size=None,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[IDLE],
+ desc='''
+ Full character save.
+ ''',
)
- char_map.r(0x2b02, 'char select req',
+ char_map.r(0x2b02, 'char select',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3295,16 +5182,26 @@ def main():
at(14, ip4, 'ip'),
],
fixed_size=18,
+ pre=[0x00b2],
+ post=[0x2b03],
+ desc='''
+ Client is going back to select character again.
+ ''',
)
- char_map.s(0x2b03, 'char select res',
+ char_map.s(0x2b03, 'char select result',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, u8, 'unknown'),
],
fixed_size=7,
+ pre=[0x2b02],
+ post=[0x00b3],
+ desc='''
+ Client can go back to select character again.
+ ''',
)
- char_map.s(0x2b04, 'map list broadcast',
+ char_map.s(0x2b04, 'map list notify',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3316,8 +5213,13 @@ def main():
at(0, map_name, 'map name'),
],
repeat_size=16,
+ pre=[0x2afa],
+ post=[IDLE],
+ desc='''
+ Send remote map information to everyone.
+ ''',
)
- char_map.r(0x2b05, 'change map server request',
+ char_map.r(0x2b05, 'change map server',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3333,6 +5235,11 @@ def main():
at(45, ip4, 'client ip'),
],
fixed_size=49,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[0x2b06],
+ desc='''
+ Client wants to use a remote map.
+ ''',
)
char_map.s(0x2b06, 'change map server ack',
fixed=[
@@ -3348,9 +5255,13 @@ def main():
at(42, u16, 'map port'),
],
fixed_size=44,
+ pre=[0x2b05],
+ post=[0x0081, 0x0092],
+ desc='''
+ Client can use a remote map.
+ ''',
)
- # 0x2720
- char_map.r(0x2b0a, 'become gm request',
+ char_map.r(0x2b0a, 'become gm',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3359,6 +5270,11 @@ def main():
head_size=8,
repeat=[at(0, u8, 'c')],
repeat_size=1,
+ pre=[GM],
+ post=[0x2720, 0x2b0b],
+ desc='''
+ Get a GM level from a password.
+ ''',
)
char_map.s(0x2b0b, 'become gm result',
fixed=[
@@ -3367,9 +5283,13 @@ def main():
at(6, gm, 'gm level'),
],
fixed_size=10,
+ pre=[0x2721, 0x2b0a],
+ post=[IDLE],
+ desc='''
+ Got a GM level from a password, or not.
+ ''',
)
- # 0x2722
- char_map.r(0x2b0c, 'change email request',
+ char_map.r(0x2b0c, 'change email',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3377,6 +5297,11 @@ def main():
at(46, account_email, 'new email'),
],
fixed_size=86,
+ pre=[GM],
+ post=[0x2722],
+ desc='''
+ Player used @email.
+ ''',
)
char_map.s(0x2b0d, 'sex changed notify',
fixed=[
@@ -3385,8 +5310,14 @@ def main():
at(6, sex, 'sex'),
],
fixed_size=7,
+ pre=[0x2723],
+ post=[0x00ac, 0x01d7, 0x2b01, 0x3011],
+ xpost=[SCRIPT, 0x0080, 0x0081, 0x0088, 0x0091, 0x00a0, 0x00b0, 0x00b1, 0x00be, 0x00c0, 0x00e9, 0x00ee, 0x00fd, 0x0106, 0x010f, 0x0119, 0x013a, 0x0141, 0x0196, 0x01b1, 0x01d8, 0x01da, 0x2b05, 0x3022],
+ desc='''
+ Kick someone who had a sex-change operation.
+ ''',
)
- char_map.r(0x2b0e, 'named char operation request',
+ char_map.r(0x2b0e, 'named char operation',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3395,8 +5326,13 @@ def main():
at(32, human_time_diff, 'ban add'),
],
fixed_size=44,
+ pre=[GM, SCRIPT, 0x008c, 0x0096, 0x0108],
+ post=[0x2724, 0x2725, 0x2727, 0x272a, 0x2b0f],
+ desc='''
+ Perform block/ban/unblock/unban/sexchange by name.
+ ''',
)
- char_map.r(0x2b0f, 'named char operation answer',
+ char_map.r(0x2b0f, 'named char operation result',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3405,25 +5341,48 @@ def main():
at(32, u16, 'error'),
],
fixed_size=34,
+ pre=[0x2b0e],
+ post=[IDLE],
+ desc='''
+ Maybe performed block/ban/unblock/unban/sexchange by name.
+ ''',
+ )
+ char_map.r(0x2b10, 'account reg2',
+ head=[
+ at(0, u16, 'packet id'),
+ at(2, u16, 'packet length'),
+ at(4, account_id, 'account id'),
+ ],
+ head_size=8,
+ repeat=[
+ at(0, var_name, 'name'),
+ at(32, u32, 'value'),
+ ],
+ repeat_size=36,
+ pre=[SCRIPT],
+ post=[0x2728],
+ desc='''
+ Update login-stored ##registers.
+ ''',
+ )
+ char_map.s(0x2b11, 'account reg2 notify',
+ head=[
+ at(0, u16, 'packet id'),
+ at(2, u16, 'packet length'),
+ at(4, account_id, 'account id'),
+ ],
+ head_size=8,
+ repeat=[
+ at(0, var_name, 'name'),
+ at(32, u32, 'value'),
+ ],
+ repeat_size=36,
+ pre=[0x2729],
+ post=[IDLE],
+ desc='''
+ Broadcast login-stored ##registers.
+ ''',
)
- # 0x2728, 0x2729
- for (id, cat) in [
- (0x2b10, char_map.r),
- (0x2b11, char_map.s),
- ]:
- cat(id, 'account reg2 update',
- head=[
- at(0, u16, 'packet id'),
- at(2, u16, 'packet length'),
- at(4, account_id, 'account id'),
- ],
- head_size=8,
- repeat=[
- at(0, var_name, 'name'),
- at(32, u32, 'value'),
- ],
- repeat_size=36,
- )
char_map.s(0x2b12, 'divorce notify',
fixed=[
at(0, u16, 'packet id'),
@@ -3431,6 +5390,11 @@ def main():
at(6, char_id, 'partner id'),
],
fixed_size=10,
+ pre=[0x0068, 0x2730, 0x2b16],
+ post=[IDLE],
+ desc='''
+ Somebody either performed a divorce or had it done to them.
+ ''',
)
char_map.s(0x2b13, 'account delete notify',
fixed=[
@@ -3438,6 +5402,11 @@ def main():
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[0x2730],
+ post=[IDLE],
+ desc='''
+ Disconnect player who was deleted.
+ ''',
)
char_map.s(0x2b14, 'status or ban notify',
fixed=[
@@ -3447,6 +5416,11 @@ def main():
at(7, time32, 'status or ban until'),
],
fixed_size=11,
+ pre=[0x2731],
+ post=[IDLE],
+ desc='''
+ Somebody was banned or otherwise had their status changed.
+ ''',
)
char_map.s(0x2b15, 'gm account list notify',
head=[
@@ -3459,16 +5433,29 @@ def main():
at(4, gm1, 'gm level'),
],
repeat_size=5,
+ pre=[0x2732, 0x2af8],
+ post=[IDLE],
+ desc='''
+ Update full list of GM accounts.
+ ''',
)
- char_map.r(0x2b16, 'divorce request',
+ char_map.r(0x2b16, 'divorce',
fixed=[
at(0, u16, 'packet id'),
at(2, char_id, 'char id'),
],
fixed_size=6,
+ pre=[SCRIPT],
+ post=[0x2b12],
+ desc='''
+ Live the single life again.
+ ''',
)
+ # 2bfa/2bfb are injected above
+
+ # TOC_INTERMAP
- char_map.r(0x3000, 'gm broadcast',
+ char_map.r(0x3000, 'gm broadcast begin',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3478,8 +5465,13 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[GM, SCRIPT],
+ post=[0x3800],
+ desc='''
+ Yell at everybody.
+ ''',
)
- char_map.r(0x3001, 'whisper forward',
+ char_map.r(0x3001, 'whisper remote begin',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3491,17 +5483,26 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x0096],
+ post=[0x3801, 0x3802],
+ desc='''
+ Begin forwarding a private message to a nonlocal map server.
+ ''',
)
- char_map.r(0x3002, 'whisper forward result',
+ char_map.r(0x3002, 'whisper remote forward result',
fixed=[
at(0, u16, 'packet id'),
at(2, char_id, 'char id'),
at(6, u8, 'flag'),
],
fixed_size=7,
+ pre=[0x3801],
+ post=[0x3802],
+ desc='''
+ Intermediate result of forwarding a private message to a nonlocal map server.
+ ''',
)
- # 0x3803
- char_map.r(0x3003, 'wgm forward',
+ char_map.r(0x3003, 'wgm remote begin',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3513,8 +5514,12 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[GM, 0x008c, 0x0096, 0x0108],
+ post=[0x3803],
+ desc='''
+ Begin GM whisper to all map servers.
+ ''',
)
- # 0x3804
char_map.r(0x3004, 'save account reg',
head=[
at(0, u16, 'packet id'),
@@ -3527,6 +5532,11 @@ def main():
at(32, u32, 'value'),
],
repeat_size=36,
+ pre=[MAGIC, SCRIPT],
+ post=[0x3804],
+ desc='''
+ Save all char-server-stored account registers.
+ ''',
)
char_map.r(0x3005, 'want account reg',
fixed=[
@@ -3534,15 +5544,25 @@ def main():
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[0x2afd],
+ post=[0x3804],
+ desc='''
+ Explicitly request values of char-server-stored account registers.
+ ''',
)
- char_map.r(0x3010, 'want storage',
+ char_map.r(0x3010, 'load storage',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[GM, SCRIPT],
+ post=[0x3810],
+ desc='''
+ Request access to storage.
+ ''',
)
- char_map.r(0x3011, 'got storage',
+ char_map.r(0x3011, 'save storage',
payload=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3550,8 +5570,13 @@ def main():
at(8, storage, 'storage'),
],
payload_size=None,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[0x3811],
+ desc='''
+ Transmission of changed storage.
+ ''',
)
- char_map.r(0x3020, 'create party',
+ char_map.r(0x3020, 'party create',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3561,13 +5586,23 @@ def main():
at(70, u16, 'level'),
],
fixed_size=72,
+ pre=[GM, 0x00f9],
+ post=[0x3820, 0x3821],
+ desc='''
+ Request creation of a party.
+ ''',
)
- char_map.r(0x3021, 'request party info',
+ char_map.r(0x3021, 'party needinfo',
fixed=[
at(0, u16, 'packet id'),
at(2, party_id, 'party id'),
],
fixed_size=6,
+ pre=[0x2afd],
+ post=[0x3821],
+ desc='''
+ Request full info about a party.
+ ''',
)
char_map.r(0x3022, 'party add member',
fixed=[
@@ -3579,6 +5614,11 @@ def main():
at(50, u16, 'level'),
],
fixed_size=52,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0085, 0x0089, 0x008c, 0x009f, 0x00a2, 0x00a7, 0x00a9, 0x00ab, 0x00b2, 0x00b8, 0x00b9, 0x00bb, 0x00c8, 0x00c9, 0x00e4, 0x00e6, 0x00e8, 0x00eb, 0x00ed, 0x00ef, 0x00f3, 0x00f5, 0x00f7, 0x00ff, 0x0112, 0x0143, 0x0146, 0x01d5, 0x2afd, 0x2b0d],
+ post=[0x3821, 0x3822, 0x3823],
+ desc='''
+ Request to add a party member in a slot somewhere.
+ ''',
)
char_map.r(0x3023, 'party change option',
fixed=[
@@ -3589,6 +5629,11 @@ def main():
at(12, u16, 'item'),
],
fixed_size=14,
+ pre=[0x0102],
+ post=[0x3823],
+ desc='''
+ Explicitly request a change of party settings.
+ ''',
)
char_map.r(0x3024, 'party leave',
fixed=[
@@ -3597,6 +5642,11 @@ def main():
at(6, account_id, 'account id'),
],
fixed_size=10,
+ pre=[0x0100, 0x0103, 0x3822],
+ post=[0x3821, 0x3824, 0x3826],
+ desc='''
+ Remove a member from party.
+ ''',
)
char_map.r(0x3025, 'party change map',
fixed=[
@@ -3608,8 +5658,17 @@ def main():
at(27, u16, 'level'),
],
fixed_size=29,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0089],
+ post=[0x3823, 0x3825],
+ desc='''
+ A party member has:
+
+ 1. loaded a new map
+ 2. levelled up
+ 3. logged out
+ ''',
)
- char_map.r(0x3027, 'party message',
+ char_map.r(0x3027, 'party message remote begin',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3621,6 +5680,11 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x0108],
+ post=[0x3827],
+ desc='''
+ Begin to send a party message to other map servers.
+ ''',
)
char_map.r(0x3028, 'party check conflict',
fixed=[
@@ -3630,9 +5694,16 @@ def main():
at(10, char_name, 'char name'),
],
fixed_size=34,
+ pre=[FINISH, GM, MAGIC, SCRIPT, TIMER, 0x007d, 0x0089, 0x3822],
+ post=[0x3821, 0x3824, 0x3826],
+ desc='''
+ Explicitly request a conflict check.
+
+ (I am not sure this is actually meaningful).
+ ''',
)
- char_map.s(0x3800, 'gm broadcast',
+ char_map.s(0x3800, 'gm broadcast forward',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3642,8 +5713,13 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x2726, 0x3000],
+ post=[0x009a, 0x00c0],
+ desc='''
+ Yell, reaching everybody.
+ ''',
)
- char_map.s(0x3801, 'whisper forward',
+ char_map.s(0x3801, 'whisper remote forward',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3656,17 +5732,26 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x3001],
+ post=[0x0097, 0x3002],
+ desc='''
+ Intermediate forwarding a private message to a nonlocal map server.
+ ''',
)
- char_map.s(0x3802, 'whisper result',
+ char_map.s(0x3802, 'whisper remote begin result',
fixed=[
at(0, u16, 'packet id'),
at(2, char_name, 'sender char name'),
at(26, u8, 'flag'),
],
fixed_size=27,
+ pre=[0x3001, 0x3002],
+ post=[0x0098],
+ desc='''
+ Final result of forwarding a private message to a nonlocal map server.
+ ''',
)
- # 0x3003
- char_map.s(0x3803, 'whisper gm',
+ char_map.s(0x3803, 'wgm remote forward',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3678,9 +5763,13 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x3003],
+ post=[0x0097],
+ desc='''
+ Forward of GM whisper.
+ ''',
)
- # 0x3004
- char_map.s(0x3804, 'broadcast account reg',
+ char_map.s(0x3804, 'account reg notify',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3692,8 +5781,15 @@ def main():
at(32, u32, 'value'),
],
repeat_size=36,
+ pre=[0x3004, 0x3005],
+ post=[IDLE],
+ desc='''
+ Char-server-stored account regs have changed.
+
+ Also sent on explicit request.
+ ''',
)
- char_map.s(0x3810, 'load storage',
+ char_map.s(0x3810, 'load storage result',
payload=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3701,16 +5797,26 @@ def main():
at(8, storage, 'storage'),
],
payload_size=None,
+ pre=[0x3010],
+ post=[0x00a6, 0x00f2, 0x01f0],
+ desc='''
+ Transmission of requested storage.
+ ''',
)
- char_map.s(0x3811, 'save storage ack',
+ char_map.s(0x3811, 'save storage result',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, u8, 'unknown'),
],
fixed_size=7,
+ pre=[0x3011],
+ post=[IDLE],
+ desc='''
+ Acknowledgement of saved storage.
+ ''',
)
- char_map.s(0x3820, 'party created',
+ char_map.s(0x3820, 'party create result',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
@@ -3719,8 +5825,13 @@ def main():
at(11, party_name, 'party name'),
],
fixed_size=35,
+ pre=[0x3020],
+ post=[0x00fa],
+ desc='''
+ Result of requesting party creation.
+ ''',
)
- char_map.s(0x3821, 'party info maybe',
+ char_map.s(0x3821, 'party needinfo result',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3731,8 +5842,16 @@ def main():
at(0, party_most, 'party most'),
],
option_size=None,
+ pre=[0x0068, 0x2730, 0x3020, 0x3021, 0x3022, 0x3024, 0x3028],
+ post=[0x00c0, 0x00fb, 0x0101],
+ desc='''
+ Full info about a party, it present.
+
+ Payload may be missing if no such party (*should* not
+ happen, but ...).
+ ''',
)
- char_map.s(0x3822, 'party member added',
+ char_map.s(0x3822, 'party add member result',
fixed=[
at(0, u16, 'packet id'),
at(2, party_id, 'party id'),
@@ -3740,8 +5859,15 @@ def main():
at(10, u8, 'flag'),
],
fixed_size=11,
+ pre=[0x3022],
+ post=[0x00fd, 0x3024, 0x3028],
+ desc='''
+ Result of trying to add a party member.
+
+ Flag is 1 if party is no slots or something.
+ ''',
)
- char_map.s(0x3823, 'party option changed',
+ char_map.s(0x3823, 'party change option notify',
fixed=[
at(0, u16, 'packet id'),
at(2, party_id, 'party id'),
@@ -3751,8 +5877,15 @@ def main():
at(14, u8, 'flag'),
],
fixed_size=15,
+ pre=[0x3022, 0x3023, 0x3025],
+ post=[0x00c0, 0x0101],
+ desc='''
+ Party options were changed.
+
+ Often generated implicitly.
+ ''',
)
- char_map.s(0x3824, 'party member left',
+ char_map.s(0x3824, 'party leave notify',
fixed=[
at(0, u16, 'packet id'),
at(2, party_id, 'party id'),
@@ -3760,8 +5893,13 @@ def main():
at(10, char_name, 'char name'),
],
fixed_size=34,
+ pre=[0x0068, 0x2730, 0x3024, 0x3028],
+ post=[0x00c0, 0x0105],
+ desc='''
+ Member is no longer in party.
+ ''',
)
- char_map.s(0x3825, 'party member moved',
+ char_map.s(0x3825, 'party change map notify',
fixed=[
at(0, u16, 'packet id'),
at(2, party_id, 'party id'),
@@ -3771,16 +5909,26 @@ def main():
at(27, u16, 'level'),
],
fixed_size=29,
+ pre=[0x3025],
+ post=[0x00c0, 0x00fb],
+ desc='''
+ Party member location/level/online status has changed.
+ ''',
)
- char_map.s(0x3826, 'party broken',
+ char_map.s(0x3826, 'party broken notify',
fixed=[
at(0, u16, 'packet id'),
at(2, party_id, 'party id'),
at(6, u8, 'flag'),
],
fixed_size=7,
+ pre=[BOOT, 0x0068, 0x2730, 0x3024, 0x3028],
+ post=[0x00c0, 0x0105],
+ desc='''
+ A party just isn't there anymore.
+ ''',
)
- char_map.s(0x3827, 'party message',
+ char_map.s(0x3827, 'party message remote forward',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3792,37 +5940,67 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x3027],
+ post=[0x00c0, 0x0109],
+ desc='''
+ Actually send a party message to other map servers.
+ ''',
)
+ # TOC_MISC
# any client
- any_user.r(0x7530, 'version request',
+ any_user.r(0x7530, 'version',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[ADMIN, BOOT, 0x7919],
+ post=[0x7531],
+ desc='''
+ Request from client or ladmin for server version.
+ ''',
)
- any_user.s(0x7531, 'version reply',
+ any_user.s(0x7531, 'version result',
fixed=[
at(0, u16, 'packet id'),
at(2, version, 'version'),
],
fixed_size=10,
+ pre=[0x7530],
+ post=[0x0064],
+ desc='''
+ Response to client's request for server version.
+ ''',
)
- any_user.r(0x7532, 'shutdown please',
+ any_user.r(0x7532, 'disconnect',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[HUMAN],
+ post=[FINISH],
+ desc='''
+ 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
- login_admin.r(0x7918, 'admin auth request',
+ login_admin.r(0x7918, 'admin auth',
fixed=[
at(0, u16, 'packet id'),
at(2, u16, 'encryption zero'),
at(4, account_pass, 'account pass'),
],
fixed_size=28,
+ pre=[BOOT],
+ post=[0x7919],
+ desc='''
+ Authenticate as an admin.
+ ''',
)
login_admin.s(0x7919, 'admin auth result',
fixed=[
@@ -3830,16 +6008,26 @@ def main():
at(2, u8, 'error'),
],
fixed_size=3,
+ pre=[0x7918],
+ post=[0x7530],
+ desc='''
+ Status of admin authentication.
+ ''',
)
- login_admin.r(0x7920, 'account list request',
+ login_admin.r(0x7920, 'account list',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'start account id'),
at(6, account_id, 'end account id'),
],
fixed_size=10,
+ pre=[ADMIN, 0x7921],
+ post=[0x7921],
+ desc='''
+ Request list of all accounts (between bounds if given).
+ ''',
)
- login_admin.s(0x7921, 'account list reply',
+ login_admin.s(0x7921, 'account list result',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'packet length'),
@@ -3854,22 +6042,13 @@ def main():
at(34, u32, 'status'),
],
repeat_size=38,
+ pre=[0x7920],
+ post=[IDLE, 0x7920],
+ desc='''
+ May be truncated, if so should retry with new lower bound.
+ ''',
)
- login_admin.r(0x7924, 'itemfrob',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, item_name_id4, 'source item id'),
- at(6, item_name_id4, 'dest item id'),
- ],
- fixed_size=10,
- )
- login_admin.s(0x7925, 'itemfrob ok',
- fixed=[
- at(0, u16, 'packet id'),
- ],
- fixed_size=2,
- )
- login_admin.r(0x7930, 'account create request',
+ login_admin.r(0x7930, 'account create',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
@@ -3878,6 +6057,11 @@ def main():
at(51, account_email, 'email'),
],
fixed_size=91,
+ pre=[ADMIN],
+ post=[0x7931],
+ desc='''
+ Account creation request.
+ ''',
)
login_admin.s(0x7931, 'account create result',
fixed=[
@@ -3886,29 +6070,49 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x7930],
+ post=[IDLE],
+ desc='''
+ Account creation response.
+ ''',
)
- login_admin.r(0x7932, 'account delete request',
+ login_admin.r(0x7932, 'account delete',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
],
fixed_size=26,
+ pre=[ADMIN],
+ post=[0x2730, 0x7933],
+ desc='''
+ Account deletion request.
+ ''',
)
- login_admin.s(0x7933, 'account delete reply',
+ login_admin.s(0x7933, 'account delete result',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x7932],
+ post=[IDLE],
+ desc='''
+ Account deletion response.
+ ''',
)
- login_admin.r(0x7934, 'password change request',
+ login_admin.r(0x7934, 'password change',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
at(26, account_pass, 'password'),
],
fixed_size=50,
+ pre=[ADMIN],
+ post=[0x7935],
+ desc='''
+ Change password request.
+ ''',
)
login_admin.s(0x7935, 'password change result',
fixed=[
@@ -3917,8 +6121,13 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x7934],
+ post=[IDLE],
+ desc='''
+ Change password response.
+ ''',
)
- login_admin.r(0x7936, 'account state change request',
+ login_admin.r(0x7936, 'account state change',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
@@ -3926,6 +6135,11 @@ def main():
at(30, seconds, 'error message'),
],
fixed_size=50,
+ pre=[ADMIN],
+ post=[0x2731, 0x7937],
+ desc='''
+ Account state change request.
+ ''',
)
login_admin.s(0x7937, 'account state change result',
fixed=[
@@ -3935,12 +6149,22 @@ def main():
at(30, u32, 'status'),
],
fixed_size=34,
+ pre=[0x7936],
+ post=[IDLE],
+ desc='''
+ Account state change response.
+ ''',
)
- login_admin.r(0x7938, 'server list request',
+ login_admin.r(0x7938, 'server list',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[ADMIN],
+ post=[0x7939],
+ desc='''
+ Server list and player count request.
+ ''',
)
login_admin.s(0x7939, 'server list result',
head=[
@@ -3957,14 +6181,24 @@ def main():
at(30, u16, 'is new'),
],
repeat_size=32,
+ pre=[0x7938],
+ post=[IDLE],
+ desc='''
+ Server list and player count response.
+ ''',
)
- login_admin.r(0x793a, 'password check request',
+ login_admin.r(0x793a, 'password check',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
at(26, account_pass, 'password'),
],
fixed_size=50,
+ pre=[ADMIN],
+ post=[0x793b],
+ desc='''
+ Password check request.
+ ''',
)
login_admin.s(0x793b, 'password check result',
fixed=[
@@ -3973,14 +6207,24 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x793a],
+ post=[IDLE],
+ desc='''
+ Password check response.
+ ''',
)
- login_admin.r(0x793c, 'change sex request',
+ login_admin.r(0x793c, 'change sex',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
at(26, sex_char, 'sex'),
],
fixed_size=27,
+ pre=[ADMIN],
+ post=[0x2723, 0x793d],
+ desc='''
+ Modify sex request.
+ ''',
)
login_admin.s(0x793d, 'change sex result',
fixed=[
@@ -3989,14 +6233,24 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x793c],
+ post=[IDLE],
+ desc='''
+ Modify sex response.
+ ''',
)
- login_admin.r(0x793e, 'adjust gm level request',
+ login_admin.r(0x793e, 'adjust gm level',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
at(26, gm1, 'gm level'),
],
fixed_size=27,
+ pre=[ADMIN],
+ post=[0x2732, 0x793f],
+ desc='''
+ Modify GM level request.
+ ''',
)
login_admin.s(0x793f, 'adjust gm level result',
fixed=[
@@ -4005,14 +6259,24 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x793e],
+ post=[IDLE],
+ desc='''
+ Modify GM level response.
+ ''',
)
- login_admin.r(0x7940, 'change email request',
+ login_admin.r(0x7940, 'change email',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
at(26, account_email, 'email'),
],
fixed_size=66,
+ pre=[ADMIN],
+ post=[0x7941],
+ desc='''
+ Modify e-mail request.
+ ''',
)
login_admin.s(0x7941, 'change email result',
fixed=[
@@ -4021,9 +6285,14 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x7940],
+ post=[IDLE],
+ desc='''
+ Modify e-mail response.
+ ''',
)
# this packet is insane
- login_admin.r(0x7942, 'change memo request',
+ login_admin.r(0x7942, 'change memo',
head=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
@@ -4034,6 +6303,11 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[ADMIN],
+ post=[0x7943],
+ desc='''
+ Modify memo request.
+ ''',
)
login_admin.s(0x7943, 'change memo result',
fixed=[
@@ -4042,13 +6316,23 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x7942],
+ post=[IDLE],
+ desc='''
+ Modify memo response.
+ ''',
)
- login_admin.r(0x7944, 'account id lookup request',
+ login_admin.r(0x7944, 'account id lookup',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
],
fixed_size=26,
+ pre=[ADMIN],
+ post=[0x7945],
+ desc='''
+ Find account id request.
+ ''',
)
login_admin.s(0x7945, 'account id lookup result',
fixed=[
@@ -4057,13 +6341,23 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x7944],
+ post=[IDLE],
+ desc='''
+ Find account id response.
+ ''',
)
- login_admin.r(0x7946, 'account name lookup request',
+ login_admin.r(0x7946, 'account name lookup',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[ADMIN],
+ post=[0x7947],
+ desc='''
+ Find account name request.
+ ''',
)
login_admin.s(0x7947, 'account name lookup result',
fixed=[
@@ -4072,31 +6366,24 @@ def main():
at(6, account_name, 'account name'),
],
fixed_size=30,
+ pre=[0x7946],
+ post=[IDLE],
+ desc='''
+ Find account name response.
+ ''',
)
- login_admin.r(0x7948, 'validity absolute request',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, account_name, 'account name'),
- at(26, time32, 'valid until'),
- ],
- fixed_size=30,
- )
- login_admin.s(0x7949, 'validity absolute result',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, account_id, 'account id'),
- at(6, account_name, 'account name'),
- at(30, time32, 'valid until'),
- ],
- fixed_size=34,
- )
- login_admin.r(0x794a, 'ban absolute request',
+ login_admin.r(0x794a, 'ban absolute',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
at(26, time32, 'ban until'),
],
fixed_size=30,
+ pre=[ADMIN],
+ post=[0x2731, 0x794b],
+ desc='''
+ Ban date end change request.
+ ''',
)
login_admin.s(0x794b, 'ban absolute result',
fixed=[
@@ -4106,14 +6393,24 @@ def main():
at(30, time32, 'ban until'),
],
fixed_size=34,
+ pre=[0x794a],
+ post=[IDLE],
+ desc='''
+ Ban date end change response.
+ ''',
)
- login_admin.r(0x794c, 'ban relative request',
+ login_admin.r(0x794c, 'ban relative',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
at(26, human_time_diff, 'ban add'),
],
fixed_size=38,
+ pre=[ADMIN],
+ post=[0x2731, 0x794d],
+ desc='''
+ Ban date end change request (2).
+ ''',
)
login_admin.s(0x794d, 'ban relative result',
fixed=[
@@ -4123,9 +6420,14 @@ def main():
at(30, time32, 'ban until'),
],
fixed_size=34,
+ pre=[0x794c],
+ post=[IDLE],
+ desc='''
+ Ban date end change response (2).
+ ''',
)
# evil packet (see also 0x2726)
- login_admin.r(0x794e, 'broadcast message request',
+ login_admin.r(0x794e, 'broadcast message',
head=[
at(0, u16, 'packet id'),
at(2, u16, 'unused'),
@@ -4136,6 +6438,11 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[ADMIN],
+ post=[0x2726, 0x794f],
+ desc='''
+ Send broadcast message request.
+ ''',
)
login_admin.s(0x794f, 'broadcast message result',
fixed=[
@@ -4143,30 +6450,23 @@ def main():
at(2, u16, 'error'),
],
fixed_size=4,
+ pre=[0x794e],
+ post=[IDLE],
+ desc='''
+ Send broadcast message response.
+ ''',
)
- login_admin.r(0x7950, 'validity relative request',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, account_name, 'account name'),
- at(26, human_time_diff, 'valid add'),
- ],
- fixed_size=38,
- )
- login_admin.s(0x7951, 'validity relative result',
- fixed=[
- at(0, u16, 'packet id'),
- at(2, account_id, 'account id'),
- at(6, account_name, 'account name'),
- at(30, time32, 'valid until'),
- ],
- fixed_size=34,
- )
- login_admin.r(0x7952, 'account name info request',
+ login_admin.r(0x7952, 'account name info',
fixed=[
at(0, u16, 'packet id'),
at(2, account_name, 'account name'),
],
fixed_size=26,
+ pre=[ADMIN],
+ post=[0x7953],
+ desc='''
+ Account information by name request.
+ ''',
)
# this packet is insane
login_admin.s(0x7953, 'account info result',
@@ -4182,7 +6482,7 @@ def main():
at(60, millis, 'last login string'),
at(84, str16, 'ip string'),
at(100, account_email, 'email'),
- at(140, time32, 'connect until'),
+ at(140, time32, 'unused connect until'),
at(144, time32, 'ban until'),
at(148, SkewLengthType(u16, 150), 'magic packet length'),
],
@@ -4191,33 +6491,302 @@ def main():
at(0, u8, 'c'),
],
repeat_size=1,
+ pre=[0x7952, 0x7954],
+ post=[IDLE],
+ desc='''
+ Account information by name or id response.
+ ''',
)
- login_admin.r(0x7954, 'account id info request',
+ login_admin.r(0x7954, 'account id info',
fixed=[
at(0, u16, 'packet id'),
at(2, account_id, 'account id'),
],
fixed_size=6,
+ pre=[ADMIN],
+ post=[0x7953],
+ desc='''
+ Account information by id request.
+ ''',
)
- login_admin.r(0x7955, 'reload gm signal',
+ login_admin.r(0x7955, 'reload gm',
fixed=[
at(0, u16, 'packet id'),
],
fixed_size=2,
+ pre=[ADMIN],
+ post=[0x2732],
+ desc='''
+ Reload GM file request.
+ ''',
)
+ # TOC_NEW
## new-style packets
- # general packets
- any_user.x(0x8000, 'special hold',
+ # notify packets, standalone, can occur at any time; always 'payload'
+ any_user.s(0x8000, 'special hold notify',
payload=[
at(0, u16, 'packet id'),
# packet 0x8000 is handled specially
at(2, u16, 'packet length'),
],
payload_size=4,
- )
+ pre=[OTHER],
+ post=[OTHER],
+ desc='''
+ A special packet that is handled specially.
+
+ The "is the packet complete" logic will read its given length,
+ but the "skip packet" will only skip 4 bytes, thus allowing
+ transactions. I'm still not entirely convinced that this is a
+ good idea - perhaps explicit 'being transaction buffer' and
+ 'end transaction buffer' packets? Allow nesting?
+ ''',
+ )
+ # invoke/return/error packets, threaded; always 'payload'
+ # invoke: 0x9000
+ # return: 0xA000
+ # error: 0xB000
+ # invoke: 0x9001
+ # return: 0xA001
+ # error: 0xB001
+ # ... or should it be implicit via a wrapper?
+ #
+ # add_call_packet(from=ANY, to=ANY, id='0x?001', name='version info'
+ # invoke_type=struct(), return_type=struct(), error_type=struct())
+ # for that matter, should these things be read from separate files?
+ # still a lot of ambiguous stuff here ...
+ #
+ # no, merging is a bad idea at the low level
+ # ... but for doc it is ...
+
+ return ctx
+
+# TOC_DRAWING
+
+def fix_sort(idlst):
+ idlst = set(idlst)
+ return (sorted([i for i in idlst if isinstance(i, SpecialEventOrigin)], key=lambda e: e.name)
+ + sorted([i for i in idlst if not isinstance(i, SpecialEventOrigin)]))
+
+def check(ctx):
+ d = {}
+
+ for ch in ctx._channels:
+ for p in ch.packets:
+ id = p.id
+ if id in d:
+ print('packet 0x%04x duplicated (old=%r, new=%r)' % (id, d[id], p))
+ d[id] = p
+
+ for (id, packet) in sorted(d.items()):
+ if packet.pre != fix_sort(packet.pre):
+ print('packet 0x%04x pre is not sorted' % id)
+ for pre in packet.pre:
+ if isinstance(pre, SpecialEventOrigin):
+ continue
+ if pre not in d:
+ print('packet 0x%04x pre 0x%04x does not exist' % (id, pre))
+ elif id not in d[pre].post and id not in d[pre].xpost:
+ print('packet 0x%04x pre 0x%04x does not have me in post or xpost' % (id, pre))
+ if packet.post != fix_sort(packet.post):
+ print('packet 0x%04x post is not sorted' % id)
+ if packet.xpost != fix_sort(packet.xpost):
+ print('packet 0x%04x xpost is not sorted' % id)
+ if len(set(packet.post) | set(packet.xpost)) != len(packet.post) + len(packet.xpost):
+ print('packet 0x%04x post intersects xpost' % id)
+ for post in packet.post:
+ if isinstance(post, SpecialEventOrigin):
+ continue
+ if post not in d:
+ print('packet 0x%04x post 0x%04x does not exist' % (id, post))
+ elif id not in d[post].pre:
+ print('packet 0x%04x post 0x%04x does not have me in pre' % (id, post))
+
+ return d
+
+def partition(d):
+ ''' given a directed graph in the form
+ {1: [2, 3], 2: [3, 4], 3: [], 4: [], 5: [6], 6: []}
+ return a list of sets of connected keys, in the form
+ [{1, 2, 3, 4}, {5, 6}]
+ '''
+ leaders = {k: k for k in d}
+
+ # this code looks nothing like what I was intending to write
+ changed = True
+ while changed:
+ changed = False
+ for k, vlist in d.items():
+ if vlist:
+ m = min(leaders[v] for v in vlist)
+ if m < leaders[k]:
+ changed = True
+ leaders[k] = m
+ else:
+ m = leaders[k]
+ else:
+ m = leaders[k]
+ for v in vlist:
+ if m < leaders[v]:
+ changed = True
+ leaders[v] = m
+
+ followers = {}
+ for k, v in leaders.items():
+ followers.setdefault(v, []).append(k)
+ return [set(v) for v in followers.values()]
+
+def ids_only(vpost):
+ rv = [e for e in vpost if not isinstance(e, SpecialEventOrigin)]
+ if len(rv) == len(vpost):
+ rv = vpost
+ return rv
+
+def make_dots(ctx):
+ d = check(ctx)
+ # automatically excludes xpost and does not touch pre
+ # disabled because that's still just too many packets for the 0x0063
+ #p = partition({k: ids_only(v.post) for (k, v) in d.items()})
+
+ if not os.path.exists('doc-gen'):
+ # generate.make will succeed if missing the wiki repo
+ # but 'make doc' will fail
+ return
+ for g in glob.glob('doc-gen/*.gv'):
+ os.rename(g, g + '.old')
+ for g in glob.glob('doc-gen/Packet-*.md'):
+ os.rename(g, g + '.old')
+
+ for (id, p) in d.items():
+ md = 'doc-gen/Packet-0x%04x.md' % id
+ dot = 'doc-gen/packets-around-0x%04x.gv' % id
+ with OpenWrite(md) as f:
+ f.write(p.wiki_doc())
+ with OpenWrite(dot) as f:
+ # The goal looks sort of like this:
+ #
+ # O O O O
+ # \ / \ /
+ # O O
+ # \ /
+ # \ /
+ # O
+ # / \
+ # / \
+ # O O
+ # / \ / \
+ # O O O O
+ #
+ # except that we also connect any extra connections between those
+ # in particular, this will DTRT for the common skip-case:
+ #
+ # |
+ # O
+ # |\
+ # | O
+ # | |
+ # | O
+ # |/
+ # O
+ # |
+ #
+ # regardless of which node you're drawing from.
+ #
+ # however, note that we do *not* list siblings.
+ #
+ # The above comment is wrong.
+ # Also, remember that none of this would be necessary without
+ # the overbroad dependencies!
+ #
+ # ... except that I got rid of the overbroad dependencies but
+ # had to kept this still. It's arguably nicer anyway, so I
+ # also made it the only way.
+ covered_nodes = sorted(p.pre_set(d, 2) | p.post_set(d, 2))
+ covered_edges = [(a, b) for a in covered_nodes for b in covered_nodes if b in d[a].post]
+ g = Graph()
+ g.default_vertex[u'shape'] = u'box'
+ # g[u'layout'] = u'twopi'
+ # g[u'root'] = u'0x%04x' % id
+ for n in covered_nodes:
+ v = g.vertex(u'0x%04x' % n)
+ v[u'label'] = u'Packet \\N: %s' % d[n].name
+ if n == id:
+ v[u'style'] = u'filled'
+ for (a, b) in covered_edges:
+ g.edge(u'0x%04x' % a, u'0x%04x' % b)
+ for n in covered_nodes:
+ # the center node will be covered specially below
+ if n == id:
+ continue
+ # backward links (both strong and weak)
+ count = 0
+ for p in d[n].pre:
+ # pre specials are always strong links
+ if isinstance(p, SpecialEventOrigin):
+ count += 1
+ elif n in d[p].xpost:
+ # weak backward links
+ pass
+ elif p not in covered_nodes:
+ # don't show mere siblings unless also ancestor/descendent
+ count += 1
+ if count:
+ v = g.vertex(u'0x%04x...pre' % n)
+ v[u'label'] = u'%d more' % count
+ v[u'style'] = u'dashed'
+ g.edge(v, u'0x%04x' % n)
+ # strong forward links
+ count = 0
+ for p in d[n].post:
+ if isinstance(p, SpecialEventOrigin):
+ count += 1
+ elif p not in covered_nodes:
+ count += 1
+ if count:
+ v = g.vertex(u'0x%04x...post' % n)
+ v[u'label'] = u'%d more' % count
+ v[u'style'] = u'dashed'
+ g.edge(u'0x%04x' % n, v)
+ # for the immediate node, also cover specials and weaks
+ for p in d[id].pre:
+ # (there are no weak backward specials)
+ # strong backward specials
+ if isinstance(p, SpecialEventOrigin):
+ g.edge(unicode(p.name), u'0x%04x' % id)
+ # weak backward nodes
+ elif id in d[p].xpost:
+ e = g.edge(u'0x%04x' % p, u'0x%04x' % id)
+ e[u'style']=u'dashed'
+ e[u'weight'] = u'0'
+ for p in d[id].post:
+ # strong forward specials
+ if isinstance(p, SpecialEventOrigin):
+ g.edge(u'0x%04x' % id, unicode(p.name))
+ for p in d[id].xpost:
+ # weak forward specials
+ if isinstance(p, SpecialEventOrigin):
+ e = g.edge(u'0x%04x' % id, unicode(p.name))
+ e[u'style'] = u'dashed'
+ e[u'weight'] = u'0'
+ # weak forward nodes
+ elif p not in covered_nodes:
+ e = g.edge(u'0x%04x' % id, u'0x%04x' % p)
+ e[u'style'] = u'dashed'
+ e[u'weight'] = u'0'
+ g.dot(f, False)
+
+
+ for g in glob.glob('doc-gen/*.old'):
+ print('Obsolete: %s' % g)
+ os.remove(g)
+
+# TOC_MAIN
+def main():
+ ctx = build_context()
## teardown
+ make_dots(ctx)
ctx.dump()
if __name__ == '__main__':