diff --git a/.actrc b/.actrc new file mode 100644 index 000000000..8241f4742 --- /dev/null +++ b/.actrc @@ -0,0 +1,4 @@ +# act configuration +# Use medium-sized images for better compatibility +-P ubuntu-latest=catthehacker/ubuntu:act-latest +-P ubuntu-22.04=catthehacker/ubuntu:act-22.04 diff --git a/.gitattributes b/.gitattributes index dcb456797..227cc2cb6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,8 @@ /src/** working-tree-encoding=KOI8-R eol=lf /tests/** working-tree-encoding=KOI8-R eol=lf +/tests/**/*.py working-tree-encoding=UTF-8 eol=lf +*.md working-tree-encoding=UTF-8 eol=lf +/.githooks/** working-tree-encoding=UTF-8 eol=lf /lib.template/** working-tree-encoding=KOI8-R eol=lf /lib/text/help/** working-tree-encoding=KOI8-R eol=lf /lib/etc/board/** working-tree-encoding=KOI8-R eol=lf diff --git a/.githooks/README.md b/.githooks/README.md new file mode 100644 index 000000000..c35a44f2f --- /dev/null +++ b/.githooks/README.md @@ -0,0 +1,48 @@ +# Git Hooks + +This directory contains git hooks for the project. + +## Installation + +Run the installation script to set up hooks in your local repository: + +```bash +.githooks/install.sh +``` + +Or configure git to use this directory for hooks (Git 2.9+): + +```bash +git config core.hooksPath .githooks +``` + +## Available Hooks + +### pre-commit + +Проверяет кодировку файлов перед коммитом. Блокирует коммит если: + +1. Найден символ замены U+FFFD (Unicode Replacement Character) +2. Файл должен быть KOI8-R (по .gitattributes), но находится в UTF-8 +3. Найдены типичные паттерны битой кодировки + +**Как исправить ошибки кодировки:** + +```bash +# Вариант 1: Конвертация файла +iconv -f utf-8 -t koi8-r file_utf8.cpp > file.cpp + +# Вариант 2: Редактирование через UTF-8 +iconv -f koi8-r -t utf-8 file.cpp > /tmp/file_utf8.cpp +# Edit /tmp/file_utf8.cpp +iconv -f utf-8 -t koi8-r /tmp/file_utf8.cpp > file.cpp + +# Вариант 3: Срочный коммит (не рекомендуется) +git commit --no-verify +``` + +## Adding New Hooks + +1. Create executable hook file in `.githooks/` directory +2. Run `.githooks/install.sh` to install it locally +3. Commit the hook file so other developers can use it diff --git a/.githooks/install.sh b/.githooks/install.sh new file mode 100755 index 000000000..f0aa4bb7b --- /dev/null +++ b/.githooks/install.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Install git hooks from .githooks/ directory +# + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +GIT_HOOKS_DIR="$(git rev-parse --git-path hooks)" + +echo "Installing git hooks..." + +# Copy all executable files from .githooks to .git/hooks +for hook in "$SCRIPT_DIR"/*; do + # Skip install.sh itself + if [ "$(basename "$hook")" = "install.sh" ]; then + continue + fi + + # Skip README + if [ "$(basename "$hook")" = "README.md" ]; then + continue + fi + + if [ -f "$hook" ] && [ -x "$hook" ]; then + hook_name=$(basename "$hook") + echo " Installing $hook_name..." + cp "$hook" "$GIT_HOOKS_DIR/$hook_name" + chmod +x "$GIT_HOOKS_DIR/$hook_name" + fi +done + +echo "✓ Git hooks installed successfully" +echo "" +echo "Installed hooks:" +ls -la "$GIT_HOOKS_DIR" | grep -E "^-rwx" | awk '{print " - " $9}' diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 000000000..5be69d9a1 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,134 @@ +#!/bin/bash + +# ============================================================================ +# Pre-commit hook: проверка кодировки файлов +# ============================================================================ +# Блокирует коммит если: +# 1. Найден символ замены U+FFFD (О©╫) +# 2. Файл должен быть KOI8-R, но находится в UTF-8 +# 3. Найдены типичные паттерны битой кодировки +# ============================================================================ + +# Код символа О©╫ (Unicode Replacement Character U+FFFD) в UTF-8: \xEF\xBF\xBD +BAD_CHAR=$(printf "\xef\xbf\xbd") + +# Получаем список измененных файлов (только текст, исключая удаленные) +FILES=$(git diff --cached --name-only --diff-filter=ACM) + +# Если файлов нет, просто выходим +[ -z "$FILES" ] && exit 0 + +ERROR_FOUND=0 + +# Функция проверки, должен ли файл быть в KOI8-R +is_koi8r_file() { + local file="$1" + # Проверяем .gitattributes + git check-attr working-tree-encoding "$file" | grep -q "koi8-r\|KOI8-R" +} + +# Функция определения кодировки файла +detect_encoding() { + local file="$1" + # Используем file для определения кодировки + file -b --mime-encoding "$file" 2>/dev/null +} + +# Паттерны типичной битой кодировки (UTF-8 байты от русских букв в KOI8-R) +# п·б╘ = d0 bf c2 b7 d0 b1 e2 95 98 +# Это происходит когда KOI8-R текст читается как UTF-8 +check_corruption_patterns() { + local file="$1" + + # Проверка 1: Символ замены U+FFFD + if grep -q "$BAD_CHAR" "$file" 2>/dev/null; then + echo " ⚠ Найден символ замены U+FFFD (О©╫)" + return 1 + fi + + # Проверка 2: Типичные битые паттерны + # п·б (d0 bf c2 b7) + if grep -qP '\xd0[\xbf\xb1]\xc2\xb7' "$file" 2>/dev/null; then + echo " ⚠ Найден паттерн битой кодировки (п·б)" + return 1 + fi + + # Проверка 3: Частые последовательности из двойной кодировки + # ╘Б∙╚ (e2 95 98 d0 91 e2 88 99) + if grep -qP '\xe2\x95[\x98\x9a]' "$file" 2>/dev/null; then + echo " ⚠ Найден паттерн битой кодировки (╘Б∙)" + return 1 + fi + + return 0 +} + +echo "🔍 Проверка кодировки файлов..." + +for FILE in $FILES; do + # Пропускаем несуществующие файлы + [ ! -f "$FILE" ] && continue + + # Пропускаем бинарные файлы + if file "$FILE" | grep -q "executable\|binary\|archive"; then + continue + fi + + # Проверяем, должен ли файл быть в KOI8-R + if is_koi8r_file "$FILE"; then + ENCODING=$(detect_encoding "$FILE") + + echo "📄 $FILE" + echo " Ожидается: KOI8-R (по .gitattributes)" + echo " Обнаружено: $ENCODING" + + # Проверка: файл должен быть ISO-8859 (KOI8-R определяется как ISO-8859) + if [[ "$ENCODING" == "utf-8" ]]; then + echo " ❌ ОШИБКА: Файл в UTF-8, а должен быть в KOI8-R!" + ERROR_FOUND=1 + continue + fi + + # Даже если encoding правильный, проверяем на битые паттерны + if ! check_corruption_patterns "$FILE"; then + echo " ❌ ОШИБКА: Обнаружена битая кодировка!" + ERROR_FOUND=1 + continue + fi + + echo " ✅ Кодировка правильная" + else + # Для не-KOI8-R файлов проверяем только символ замены + if grep -q "$BAD_CHAR" "$FILE" 2>/dev/null; then + echo "❌ [BLOCKER] Символ замены (О©╫) найден в: $FILE" + ERROR_FOUND=1 + fi + fi +done + +if [ $ERROR_FOUND -ne 0 ]; then + echo "" + echo "════════════════════════════════════════════════════════" + echo "❌ КОММИТ ЗАБЛОКИРОВАН" + echo "════════════════════════════════════════════════════════" + echo "" + echo "Обнаружены проблемы с кодировкой файлов." + echo "" + echo "Как исправить:" + echo " 1. Для KOI8-R файлов используйте iconv:" + echo " iconv -f utf-8 -t koi8-r file_utf8.cpp > file.cpp" + echo "" + echo " 2. Или используйте Edit tool на UTF-8 версии:" + echo " iconv -f koi8-r -t utf-8 file.cpp > /tmp/file_utf8.cpp" + echo " # Edit /tmp/file_utf8.cpp" + echo " iconv -f utf-8 -t koi8-r /tmp/file_utf8.cpp > file.cpp" + echo "" + echo " 3. Для срочного коммита (не рекомендуется):" + echo " git commit --no-verify" + echo "" + echo "════════════════════════════════════════════════════════" + exit 1 +fi + +echo "✅ Все проверки пройдены" +exit 0 diff --git a/.github/workflows/LOCAL_TESTING.md b/.github/workflows/LOCAL_TESTING.md new file mode 100644 index 000000000..4d5f9aa7d --- /dev/null +++ b/.github/workflows/LOCAL_TESTING.md @@ -0,0 +1,186 @@ +# Локальное тестирование GitHub Actions + +## Установка act + +**act** - инструмент для локального запуска GitHub Actions workflows. + +### Linux +```bash +# Через curl (рекомендуется) +curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash + +# Или через apt +sudo apt install act + +# Проверка установки +act --version +``` + +### Требования +- Docker должен быть установлен и запущен +- ~2-3 GB места для Docker образов + +## Использование + +### Просмотр доступных workflows и jobs +```bash +# Список всех workflows +act -l + +# Список jobs в конкретном workflow +act -l -W .github/workflows/build.yml +``` + +### Запуск конкретного job +```bash +# Запустить Quick Check (самый быстрый) +act -W .github/workflows/quick-check.yml + +# Запустить только Linux Base из build.yml +act -j build-linux-matrix -W .github/workflows/build.yml + +# Запустить с определенной матричной конфигурацией +act -j build-linux-matrix --matrix config.name:"Base" +``` + +### Запуск с событием push +```bash +# Эмулировать push в текущую ветку +act push + +# Эмулировать push в конкретную ветку +act push -e .github/workflows/test-event.json +``` + +### Dry-run (без реального выполнения) +```bash +# Показать что будет выполнено +act -n + +# Dry-run конкретного workflow +act -n -W .github/workflows/build.yml +``` + +###Debug mode +```bash +# Подробный вывод +act -v + +# Еще более подробный +act -v -v +``` + +## Ограничения + +⚠️ **act не может запустить все jobs:** + +- ❌ Windows jobs (MSVC, MinGW) - требуют Windows runner +- ❌ Cygwin - требует Windows +- ❌ WSL - требует Windows +- ✅ Linux jobs - работают отлично +- ⚠️ GCC 15 - может требовать дополнительной настройки + +## Рекомендуемый workflow для тестирования + +### 1. Быстрая проверка синтаксиса +```bash +# ~30 секунд, проверяет что workflow валидный +act -n -W .github/workflows/quick-check.yml +``` + +### 2. Тест базовой Linux сборки +```bash +# ~2-5 минут, полная сборка +act -j quick-build -W .github/workflows/quick-check.yml +``` + +### 3. Тест конкретной конфигурации +```bash +# Тестировать YAML конфигурацию +act -j build-linux-matrix --matrix config.name:"YAML" + +# Тестировать с тестами +act -j build-linux-matrix --matrix config.name:"With Tests" +``` + +## Ускорение + +### Кэширование Docker образов +После первого запуска образы кэшируются: +```bash +# Первый запуск: ~5-10 минут (скачивание образа) +# Последующие: ~2-3 минуты (используется кэш) +``` + +### Использование меньших образов +В `.actrc` настроены оптимальные образы: +- `catthehacker/ubuntu:act-latest` - полнофункциональный, ~1.5GB +- Альтернатива: `node:16-buster-slim` - минимальный, ~500MB (может не хватать пакетов) + +### Пропуск шагов +```bash +# Пропустить установку зависимостей (если уже установлены в образе) +act --skip-install-dependencies +``` + +## Примеры использования + +### Тестирование изменений в workflow перед push +```bash +# 1. Редактируем .github/workflows/build.yml +# 2. Тестируем локально +act -j quick-build -W .github/workflows/quick-check.yml + +# 3. Если всё ОК - коммитим и пушим +git add .github/workflows/ +git commit -m "ci: Update workflow" +git push +``` + +### Отладка падающего job +```bash +# Запустить с verbose и shell при ошибке +act -j build-linux-matrix --matrix config.name:"YAML" -v + +# Интерактивный shell в контейнере +act -j build-linux-matrix --matrix config.name:"YAML" --shell +``` + +## Troubleshooting + +### Docker permission denied +```bash +sudo usermod -aG docker $USER +newgrp docker +``` + +### Нехватка места +```bash +# Очистить старые Docker образы +docker system prune -a +``` + +### Job не запускается +```bash +# Проверить синтаксис workflow +act -n -W .github/workflows/build.yml + +# Проверить что Docker запущен +docker ps +``` + +## Полезные ссылки + +- 📖 act documentation: https://github.com/nektos/act +- 🐳 Docker images: https://github.com/catthehacker/docker_images +- 🔧 GitHub Actions docs: https://docs.github.com/en/actions + +## Сравнение времени выполнения + +| Метод | Время | Стоимость | +|-------|-------|-----------| +| `act` локально | 2-5 мин | Бесплатно | +| GitHub Actions | 3-10 мин | Бесплатно (лимит 2000 мин/месяц) | +| Push на каждую итерацию | 5-15 мин | Расход лимита | + +**Вывод:** Используйте `act` для быстрых итераций, GitHub Actions для финальной проверки на всех платформах. diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 000000000..ff495b0e2 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,152 @@ +# GitHub Actions CI/CD + +## Workflows + +### `build.yml` - Multi-Platform Build Matrix + +Проверяет сборку проекта на всех поддерживаемых платформах с различными конфигурациями. + +#### Linux Configurations (Matrix) + +| Конфигурация | Тесты | YAML | SQLite | Admin API | Описание | +|-------------|:-----:|:----:|:------:|:---------:|----------| +| Base | ❌ | ❌ | ❌ | ❌ | Базовая сборка (legacy format) | +| With Tests | ✅ | ❌ | ❌ | ❌ | Базовая сборка + unit tests | +| YAML | ❌ | ✅ | ❌ | ❌ | Поддержка YAML без админки | +| YAML + Admin API | ❌ | ✅ | ❌ | ✅ | YAML с Admin API | +| YAML + Tests | ✅ | ✅ | ❌ | ❌ | YAML + unit tests | +| SQLite | ❌ | ❌ | ✅ | ❌ | Поддержка SQLite | +| SQLite + Tests | ✅ | ❌ | ✅ | ❌ | SQLite + unit tests | + +**Важно:** YAML и SQLite - взаимоисключающие форматы данных мира. + +**CMake флаги:** +- `-DHAVE_YAML=ON` - включает поддержку YAML world format +- `-DHAVE_SQLITE=ON` - включает поддержку SQLite world format +- `-DENABLE_ADMIN_API=ON` - включает Admin API (требует YAML) +- `-DBUILD_TESTS=OFF` - отключает сборку тестов (по умолчанию включены) + +**Зависимости:** +- YAML: `libyaml-cpp-dev` +- SQLite: `libsqlite3-dev` +- Tests: `libgtest-dev` + +#### Linux GCC 15 + +| Компилятор | Тесты | Описание | +|-----------|:-----:|----------| +| GCC 15 | ✅ | Сборка с GCC 15 в Debian Sid container | + +#### Other Platforms + +| Платформа | Компилятор | Статус | +|-----------|------------|--------| +| Windows | MSVC | Soft-failure | +| Windows | MinGW (MSYS2) | Soft-failure | +| Cygwin | GCC | Soft-failure | +| WSL | GCC | Soft-failure | + +**Режим работы:** Все jobs работают в режиме `continue-on-error: true` (soft-failure), то есть: +- ❌ Падение сборки **НЕ блокирует** merge PR +- ✅ Результаты видны в Summary и статусах PR +- 📊 Позволяет отслеживать состояние кросс-платформенной совместимости + +### `quick-check.yml` - Fast CI Check + +Быстрая проверка на каждый push/PR: +- Только базовая Linux сборка (без опциональных feature flags) +- Кэширование apt-пакетов для ускорения +- Проверка базовых синтаксических ошибок + +## Локальное тестирование + +Перед push можно проверить сборку локально: + +```bash +# Базовая сборка (legacy format) +mkdir build && cd build +cmake -DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Test .. +make -j$(nproc) + +# С тестами +mkdir build && cd build +cmake -DCMAKE_BUILD_TYPE=Test .. +make tests -j$(nproc) +./tests/tests + +# С YAML support +mkdir build_yaml && cd build_yaml +cmake -DHAVE_YAML=ON -DCMAKE_BUILD_TYPE=Test .. +make -j$(nproc) + +# С SQLite support +mkdir build_sqlite && cd build_sqlite +cmake -DHAVE_SQLITE=ON -DCMAKE_BUILD_TYPE=Test .. +make -j$(nproc) + +# YAML + Admin API +mkdir build_admin && cd build_admin +cmake -DHAVE_YAML=ON -DENABLE_ADMIN_API=ON -DCMAKE_BUILD_TYPE=Test .. +make -j$(nproc) + +# YAML + Tests +mkdir build_yaml_tests && cd build_yaml_tests +cmake -DHAVE_YAML=ON -DCMAKE_BUILD_TYPE=Test .. +make tests -j$(nproc) +./tests/tests + +# SQLite + Tests +mkdir build_sqlite_tests && cd build_sqlite_tests +cmake -DHAVE_SQLITE=ON -DCMAKE_BUILD_TYPE=Test .. +make tests -j$(nproc) +./tests/tests +``` + +## Известные проблемы + +### Admin API +- ⚠️ Admin API работает **только с YAML** world format +- Требует `-DHAVE_YAML=ON -DENABLE_ADMIN_API=ON` +- Unix socket создается в world directory (напр. `small/admin_api.sock`) + +### Windows (MSVC) +- Может требовать адаптации кода под Windows API +- Некоторые POSIX-функции недоступны +- vcpkg используется для управления зависимостями + +### Cygwin +- Производительность ниже, чем у нативного Linux +- Могут быть проблемы с путями (Windows vs POSIX) + +### WSL +- Ограничения на доступ к некоторым системным функциям +- Может отличаться от чистого Linux в edge cases + +## Переход на строгий режим + +Когда все платформы будут стабильно собираться, можно убрать `continue-on-error: true` из jobs для включения строгого режима (блокировка PR при падении). + +## Кэширование + +В `quick-check.yml` добавлено кэширование apt-пакетов для ускорения повторных сборок. + +В будущем можно добавить: +- Кэширование vcpkg пакетов (Windows) +- Кэширование CMake build cache +- Кэширование submodules + +## Мониторинг + +Результаты CI доступны: +- В разделе **Actions** на GitHub +- В статусах Pull Request +- В автоматическом **Summary** (markdown таблица с результатами) + +## Badge для README + +Для добавления статус-badge в основной README.md: + +```markdown +[![Multi-Platform Build](https://github.com/bylins/mud/actions/workflows/build.yml/badge.svg)](https://github.com/bylins/mud/actions/workflows/build.yml) +[![Quick Check](https://github.com/bylins/mud/actions/workflows/quick-check.yml/badge.svg)](https://github.com/bylins/mud/actions/workflows/quick-check.yml) +``` diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..98d679777 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,590 @@ +name: Multi-Platform Build + +on: + push: + branches: [ master, world-load-refactoring ] + pull_request: + branches: [ master ] + +# Cancel previous runs if new push to same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-linux-matrix: + name: Linux / GCC / ${{ matrix.config.name }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + config: + - name: "Base" + cmake_flags: "-DCMAKE_BUILD_TYPE=Release" + packages: "" + run_tests: true + + - name: "YAML" + cmake_flags: "-DCMAKE_BUILD_TYPE=Release -DHAVE_YAML=ON" + packages: "libyaml-cpp-dev" + run_tests: true + + - name: "SQLite" + cmake_flags: "-DCMAKE_BUILD_TYPE=Release -DHAVE_SQLITE=ON" + packages: "libsqlite3-dev" + run_tests: true + + - name: "Base + Admin API" + cmake_flags: "-DCMAKE_BUILD_TYPE=Release -DENABLE_ADMIN_API=ON" + packages: "" + run_tests: true + + - name: "YAML + Admin API" + cmake_flags: "-DCMAKE_BUILD_TYPE=Release -DHAVE_YAML=ON -DENABLE_ADMIN_API=ON" + packages: "libyaml-cpp-dev" + run_tests: true + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install base dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + make \ + libssl-dev \ + libcurl4-gnutls-dev \ + libexpat1-dev \ + gettext \ + unzip \ + cmake \ + gdb \ + libgtest-dev \ + zlib1g-dev \ + libcrypt-dev + + - name: Install config-specific dependencies + if: matrix.config.packages != '' + run: | + sudo apt-get install -y ${{ matrix.config.packages }} + + - name: Configure CMake + run: | + mkdir -p build + cd build + cmake ${{ matrix.config.cmake_flags }} .. + + - name: Build + run: | + cd build + make ${{ matrix.config.run_tests && 'tests' || '' }} -j$(nproc) + + - name: Run tests + if: matrix.config.run_tests + run: | + cd build + ./tests/tests + + build-linux-gcc15: + name: Linux / GCC 15 / Base + runs-on: ubuntu-latest + + container: + image: debian:sid + options: --user root + + steps: + - name: Install Git and dependencies + run: | + apt-get update + apt-get install -y git ca-certificates + git config --global --add safe.directory '*' + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install GCC 15 and build dependencies + run: | + apt-get update + apt-get install -y \ + gcc-15 \ + g++-15 \ + build-essential \ + make \ + libssl-dev \ + libcurl4-gnutls-dev \ + libexpat1-dev \ + gettext \ + unzip \ + cmake \ + gdb \ + libgtest-dev \ + libcrypt-dev + + - name: Show GCC 15 version + run: | + gcc-15 --version + g++-15 --version + + - name: Configure CMake with GCC 15 + run: | + mkdir -p build + cd build + CC=gcc-15 CXX=g++-15 cmake -DCMAKE_BUILD_TYPE=Release .. + + - name: Build with GCC 15 + run: | + cd build + make tests -j$(nproc) + + - name: Run tests + run: | + cd build + ./tests/tests + + build-windows-msvc: + name: Windows / MSVC / Base + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@v1 + + - name: Install dependencies with vcpkg + run: | + vcpkg install openssl:x64-windows curl:x64-windows expat:x64-windows gtest:x64-windows + + - name: Configure CMake + run: | + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake .. + + - name: Build + run: | + cd build + cmake --build . --config Test -j + + - name: Run tests + run: | + cd build + ./tests/Test/tests.exe + echo "Exit code: $LASTEXITCODE" + exit $LASTEXITCODE + + build-macos-clang: + name: macOS / Clang / Base + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: | + brew install openssl curl expat googletest cmake + + - name: Configure CMake + run: | + mkdir -p build + cd build + cmake -DCMAKE_BUILD_TYPE=Release \ + -DOPENSSL_ROOT_DIR=$(brew --prefix openssl) \ + -DCURL_ROOT=$(brew --prefix curl) .. + + - name: Build + run: | + cd build + make tests -j$(sysctl -n hw.ncpu) + + - name: Run tests + run: | + cd build + ./tests/tests + + build-windows-clang: + name: Windows / Clang / Base + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies with vcpkg + run: | + vcpkg install openssl:x64-windows curl:x64-windows expat:x64-windows gtest:x64-windows + + - name: Configure CMake with Clang + run: | + mkdir build + cd build + cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake .. + + - name: Build + run: | + cd build + cmake --build . --config Release -j + + - name: Run tests + run: | + cd build + ./tests/Release/tests.exe + echo "Exit code: $LASTEXITCODE" + exit $LASTEXITCODE + + build-cygwin: + name: Cygwin / GCC / Base + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Cygwin + uses: cygwin/cygwin-install-action@v6 + with: + packages: >- + gcc-g++ + gcc-core + make + cmake + libssl-devel + libcurl-devel + libexpat1-devel + libcrypt-devel + gettext-devel + unzip + git + wget + + - name: Build and install googletest from source + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + cd /tmp + wget https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz + tar xzf v1.14.0.tar.gz + cd googletest-1.14.0 + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + make -j$(nproc) + make install + + - name: Configure CMake + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + cd "$GITHUB_WORKSPACE" + mkdir -p build + cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + + - name: Build + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + git config --global --add safe.directory '*' + cd "$GITHUB_WORKSPACE/build" + make tests -j$(nproc) + + - name: Run tests + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + cd "$GITHUB_WORKSPACE/build" + ./tests/tests + + build-cygwin-yaml: + name: Cygwin / GCC / YAML + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Cygwin + uses: cygwin/cygwin-install-action@v6 + with: + packages: >- + gcc-g++ + gcc-core + make + cmake + libssl-devel + libcurl-devel + libexpat1-devel + libcrypt-devel + gettext-devel + unzip + git + wget + + - name: Build and install googletest from source + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + cd /tmp + wget https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz + tar xzf v1.14.0.tar.gz + cd googletest-1.14.0 + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + make -j$(nproc) + make install + + - name: Build and install yaml-cpp from source + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + cd /tmp + wget https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.tar.gz + tar xzf yaml-cpp-0.7.0.tar.gz + cd yaml-cpp-yaml-cpp-0.7.0 + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release -DYAML_BUILD_SHARED_LIBS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. + make -j$(nproc) + make install + + - name: Configure CMake + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + cd "$GITHUB_WORKSPACE" + mkdir -p build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DHAVE_YAML=ON .. + + - name: Build + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + git config --global --add safe.directory '*' + cd "$GITHUB_WORKSPACE/build" + make tests -j$(nproc) + + - name: Run tests + shell: C:\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + cd "$GITHUB_WORKSPACE/build" + ./tests/tests + + build-wsl: + name: WSL / GCC / Base + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup WSL + uses: Vampire/setup-wsl@v3 + with: + distribution: Ubuntu-24.04 + + - name: Install dependencies in WSL + shell: wsl-bash {0} + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + make \ + libssl-dev \ + libcurl4-gnutls-dev \ + libexpat1-dev \ + gettext \ + unzip \ + cmake \ + gdb \ + libgtest-dev \ + zlib1g-dev \ + libcrypt-dev + + - name: Configure CMake in WSL + shell: wsl-bash {0} + run: | + mkdir -p build + cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + + - name: Build in WSL + shell: wsl-bash {0} + run: | + cd build + make tests -j$(nproc) + + - name: Run tests in WSL + shell: wsl-bash {0} + run: | + cd build + ./tests/tests + + + build-wsl-yaml: + name: WSL / GCC / YAML + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup WSL + uses: Vampire/setup-wsl@v3 + with: + distribution: Ubuntu-24.04 + + - name: Install dependencies in WSL + shell: wsl-bash {0} + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + make \ + libssl-dev \ + libcurl4-gnutls-dev \ + libexpat1-dev \ + gettext \ + unzip \ + cmake \ + gdb \ + libgtest-dev \ + zlib1g-dev \ + libcrypt-dev \ + libyaml-cpp-dev + + - name: Configure CMake in WSL + shell: wsl-bash {0} + run: | + mkdir -p build + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DHAVE_YAML=ON .. + + - name: Build in WSL + shell: wsl-bash {0} + run: | + cd build + make tests -j$(nproc) + + - name: Run tests in WSL + shell: wsl-bash {0} + run: | + cd build + ./tests/tests + + coverage: + name: Linux / GCC / Coverage + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/world-load-refactoring' || github.ref == 'refs/heads/master' || github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libssl-dev libcurl4-gnutls-dev libexpat1-dev \ + gettext libgtest-dev lcov + + - name: Configure CMake with coverage + run: | + mkdir build + cd build + cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON -DWITH_ASAN=OFF .. + + - name: Build tests + run: make tests -j$(($(nproc)/2)) + working-directory: build + + - name: Run tests + run: ./tests/tests + working-directory: build + + - name: Generate lcov report + run: | + lcov --capture --ignore-errors mismatch,negative \ + --directory . \ + --output-file coverage.info + lcov --remove coverage.info --ignore-errors unused \ + '/usr/*' '*/third_party_libs/*' '*/tests/*' \ + --output-file coverage_filtered.info + working-directory: build + + - name: Post coverage summary + run: | + echo "## Coverage Report" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + lcov --summary build/coverage_filtered.info 2>&1 >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - name: Generate HTML report + run: | + genhtml build/coverage_filtered.info \ + --output-directory build/coverage_html + + - name: Upload to Codecov + uses: codecov/codecov-action@v5 + with: + files: build/coverage_filtered.info + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload HTML report as artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-html + path: build/coverage_html/ + retention-days: 30 + + + build-summary: + name: Build Summary + runs-on: ubuntu-latest + needs: [build-linux-matrix, build-linux-gcc15, build-windows-msvc, build-macos-clang, build-windows-clang, build-cygwin, build-wsl, build-cygwin-yaml, build-wsl-yaml] + if: always() + + steps: + - name: Check build results + run: | + echo "## Build Results Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "### Linux Configurations" >> $GITHUB_STEP_SUMMARY + echo "| Configuration | Status |" >> $GITHUB_STEP_SUMMARY + echo "|---------------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Base | ${{ needs.build-linux-matrix.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| With Tests | ${{ needs.build-linux-matrix.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| YAML | ${{ needs.build-linux-matrix.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| YAML + Admin API | ${{ needs.build-linux-matrix.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| YAML + Tests | ${{ needs.build-linux-matrix.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| SQLite | ${{ needs.build-linux-matrix.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| SQLite + Tests | ${{ needs.build-linux-matrix.result }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "### Linux GCC 15" >> $GITHUB_STEP_SUMMARY + echo "| Compiler | Status |" >> $GITHUB_STEP_SUMMARY + echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| GCC 15 (Debian Sid container) | ${{ needs.build-linux-gcc15.result }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "### Other Platforms" >> $GITHUB_STEP_SUMMARY + echo "| Platform | Status |" >> $GITHUB_STEP_SUMMARY + echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| macOS (Clang) | ${{ needs.build-macos-clang.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Windows (MSVC) | ${{ needs.build-windows-msvc.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Windows (Clang) | ${{ needs.build-windows-clang.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Cygwin | ${{ needs.build-cygwin.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| WSL | ${{ needs.build-wsl.result }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Note:** All jobs are hard-failures -- a red build means something is broken." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/quick-check.yml b/.github/workflows/quick-check.yml new file mode 100644 index 000000000..d6685f947 --- /dev/null +++ b/.github/workflows/quick-check.yml @@ -0,0 +1,72 @@ +name: Quick Check + +on: + push: + branches: [ '**' ] # Все ветки + pull_request: + +# Cancel previous runs if new push to same branch +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + quick-build: + name: Quick Build (Linux) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + /var/cache/apt/archives + key: ${{ runner.os }}-apt-${{ hashFiles('.github/workflows/quick-check.yml') }} + restore-keys: | + ${{ runner.os }}-apt- + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + make \ + libssl-dev \ + libcurl4-gnutls-dev \ + libexpat1-dev \ + gettext \ + unzip \ + cmake + + - name: Configure CMake + run: | + mkdir -p build + cd build + cmake -DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Test .. + + - name: Build + run: | + cd build + make -j$(nproc) + + - name: Check binary + run: | + ls -lh build/circle + file build/circle + + syntax-check: + name: Syntax Check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Check for common issues + run: | + echo "Checking for tabs in makefiles..." + # Add any syntax/style checks here + echo "✅ Basic checks passed" diff --git a/.gitignore b/.gitignore index 81a3229f2..3d264e6c9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,10 @@ cmake-build-release-wsl/ log/ syslog cmake-build-* +build/ +build_*/ +magic.mgc +data/ +misc/ + +__pycache__/ diff --git a/CLAUDE.md b/CLAUDE.md index d46a8181e..1c2cfaf12 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ sudo apt update && sudo apt upgrade sudo apt install build-essential make libssl-dev libcurl4-gnutls-dev libexpat1-dev gettext unzip cmake gdb libgtest-dev git clone --recurse-submodules https://github.com/bylins/mud cd mud -cp --update=none -r lib.template/* lib +cp -n -r lib.template/* lib ``` ### Standard Build (without tests) @@ -22,7 +22,7 @@ cp --update=none -r lib.template/* lib mkdir build cd build cmake -DBUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Test .. -make -j$(nproc) +make -j$(($(nproc)/2)) cd .. ./build/circle 4000 # Start server on port 4000 ``` @@ -32,13 +32,15 @@ cd .. mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Test .. -make tests -j$(nproc) +make tests -j$(($(nproc)/2)) ./tests/tests # Run all tests ``` +**Important:** Always use `-j$(($(nproc)/2))` for parallel builds to avoid overloading the system. + ### Build Types - **Release** - Optimized production build (-O0 with debug symbols, -rdynamic, -Wall) -- **Debug** - Debug build with ASAN (-fsanitize=address, -D_GLIBCXX_DEBUG) +- **Debug** - Debug build with ASAN (-fsanitize=address, NO _GLIBCXX_DEBUG due to yaml-cpp ABI compatibility) - **Test** - Test build with optimizations (-O3, -DTEST_BUILD, -DNOCRYPT) - **FastTest** - Fast test build (-Ofast, -DTEST_BUILD) @@ -49,6 +51,45 @@ docker run -d -p 4000:4000 -e MUD_PORT=4000 -v ./lib:/mud/lib --name mud mud-ser docker stop mud ``` +### Running the Server + +**CRITICAL**: ALL binaries (circle, tests, converters, etc.) must ALWAYS be run from their build directory, NEVER from the source directory. Running binaries from the source directory creates runtime files (data/, misc/, etc.) in the wrong location and pollutes the source tree. + +**CRITICAL**: Build directories must ALWAYS be out-of-source (outside the source tree). Never run cmake or binaries from within the src/ directory or repository root. + +**CRITICAL**: CMake automatically creates test world `small/` in the build directory. All world data and configs are in `small/` directory itself, NOT in `small/lib/`. The `lib/` subdirectory DOES NOT EXIST in cmake-generated worlds. All paths in configuration.xml are relative to the `small/` directory. + +**Correct usage**: +```bash +cd build_debug # Or build_yaml, build_sqlite, etc. +./circle [-W] -d small +``` + +**Parameters**: +- `-W` - Enable world checksum calculation (optional) +- `-d ` - Specify world data directory (e.g., `small`) +- `` - Port number to listen on (e.g., `4000`) + +**Example**: +```bash +cd build_debug +./circle -d small 4000 +``` + +**World Structure (cmake-generated):** +``` +build_debug/ +├── circle # Binary +└── small/ # Test world (created by cmake) + ├── misc/ + │ └── configuration.xml + ├── world/ # Zone files + ├── etc/ # Additional configs + └── admin_api.sock # Unix socket (if Admin API enabled) +``` + +**NOTE**: Do NOT confuse with repository's `lib/` directory - that is completely separate and used only for production deployments. + ### Running Tests ```bash # Run all tests @@ -257,7 +298,7 @@ lib/ └── misc/ # Miscellaneous (grouping, noob_help.xml, configuration.xml) ``` -Copy template data: `cp --update=none -r lib.template/* lib` +Copy template data: `cp -n -r lib.template/* lib` ## Testing Strategy @@ -301,3 +342,131 @@ The heartbeat system includes built-in profiling: - **Pulse Timing**: Never use wall-clock delays; register actions with heartbeat system - **Script Depth**: DG Scripts limited to 512 recursion depth to prevent stack overflow - **Runtime Encoding**: While source files are KOI8-R, runtime text can be multiple encodings (Alt, Win, UTF-8, KOI8-R) based on client settings + +## Claude Code Workflow Rules + +### Build Directory Convention +Use separate build directories for different CMake configurations to avoid lengthy rebuilds: +``` +build/ - default build (without optional features) +build_sqlite/ - build with -DHAVE_SQLITE=ON +build_debug/ - debug build with -DCMAKE_BUILD_TYPE=Debug +build_test/ - test data and converted worlds (not for compilation) +``` +**Always warn the user when changing build directories or running cmake/make in a different directory.** + +### File Encoding - CRITICAL +**Proper workflow for editing KOI8-R files:** + +For files marked as `working-tree-encoding=KOI8-R` in .gitattributes (all files in /src/**, /tests/**): + +```bash +# 1. Convert to UTF-8 for editing +iconv -f koi8-r -t utf-8 src/file.cpp > /tmp/file_utf8.cpp + +# 2. Edit the UTF-8 version with Edit tool or text editor +# (make your changes here) + +# 3. Convert back to KOI8-R +iconv -f utf-8 -t koi8-r /tmp/file_utf8.cpp > src/file.cpp +``` + +**NEVER use the Edit tool directly on existing .cpp/.h files that contain Russian text.** +Only use Edit for: +- Newly created files that will be pure ASCII/English +- Temporary UTF-8 converted files (in /tmp) +- Files in .gitattributes marked as UTF-8 (e.g., Python files) + +**NEVER use sed for editing source files.** Sed has tendency to: +- Modify files in unexpected places (matching wrong lines) +- Lead to file corruption detection and accidental `git checkout` (losing all uncommitted work) +- Cause cumulative errors from multiple sed operations +- Corrupt KOI8-R encoding + +**Alternative: unified diff patches** - for small targeted changes: +```bash +cat > /tmp/fix.patch << 'PATCH' +--- a/src/file.cpp ++++ b/src/file.cpp +@@ -10,3 +10,4 @@ + existing line +-old line ++new line +PATCH +patch -p1 < /tmp/fix.patch +``` +Patches preserve encoding and fail cleanly if context doesn't match. + +### _GLIBCXX_DEBUG Disabled in Debug Build +**Important:** `_GLIBCXX_DEBUG` is intentionally **disabled** for Debug builds due to ABI incompatibility with external libraries (yaml-cpp, SQLite). + +**Why:** +- External libraries (yaml-cpp) are compiled without `_GLIBCXX_DEBUG` +- They return STL objects (`std::string`) to our code +- If our code uses `_GLIBCXX_DEBUG`, ABI mismatch causes heap-buffer-overflow +- Solution: disable the flag for all Debug builds + +**Trade-off:** We lose STL iterator/bounds checking in Debug mode, but gain ASAN (AddressSanitizer) which catches most memory errors. + +### Directory Change Notifications +Always explicitly notify the user before: +- Running cmake in a different build directory +- Running make in a different build directory +- Changing the working directory for any build operation + +Example: "Switching to build_sqlite/ directory for SQLite-enabled build." + +### World Data Formats and Testing + +The project supports three world data formats: +1. **Legacy** - Original CircleMUD text format (default, in lib/ + lib.template/) +2. **SQLite** - World data in SQLite database (requires -DHAVE_SQLITE=ON) +3. **YAML** - Human-readable YAML format (requires -DHAVE_YAML=ON) + +**CRITICAL: Never use lib/ from repository directly!** +- `lib/` contains base configuration files only (NOT complete world data) +- `lib.template/` contains world files, player data, and additional configs +- To get a working world: copy lib/ to build directory, then overlay lib.template/ + +**Preparing world for conversion:** +```bash +# Create working copy (example for YAML build) +mkdir -p build_yaml/small +cp -r lib build_yaml/small/ +cp -r lib.template/* build_yaml/small/lib/ + +# Now build_yaml/small/lib contains complete world data ready for conversion +``` + +**Conversion Tool:** +```bash +# Convert legacy world to YAML (in-place conversion) +./tools/convert_to_yaml.py --input build_yaml/small/lib/world --output build_yaml/small/world --format yaml --type all + +# Convert to SQLite database +./tools/convert_to_yaml.py --input build_sqlite/small/lib/world --output build_sqlite/small/world.db --format sqlite --type all +``` + +**Automated Testing & Conversion:** +```bash +# Run world loading tests (automatically prepares and converts worlds) +./tools/run_load_tests.sh # Full test suite +./tools/run_load_tests.sh --quick # Quick test (Legacy + YAML checksums) +./tools/run_load_tests.sh --help # Show all options +``` + +The `run_load_tests.sh` script: +- Builds all three variants (Legacy, SQLite, YAML) in separate build directories +- Automatically prepares working worlds (copies lib + lib.template) +- Converts worlds if missing or outdated +- Runs boot tests with configurable timeout (default 5 minutes) +- Calculates checksums (zones, rooms, mobs, objects, triggers) to verify correctness +- Compares checksums between formats to detect discrepancies +- Generates detailed reports with boot times and performance comparison + +**Important:** +- Schema/format changes should be tested with `run_load_tests.sh` +- Conversion script is in `tools/convert_to_yaml.py` +- String enum values (like "kWorm") in YAML/SQLite are intentional for human readability - map them in loader +- When fixing loader issues, check if the problem is in converter or loader + diff --git a/CMakeLists.txt b/CMakeLists.txt index 68501de21..4794ba42d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ set(SOURCES src/engine/scripting/dg_comm.cpp src/engine/scripting/dg_domination_helper.cpp src/engine/scripting/dg_db_scripts.cpp + src/engine/scripting/trigger_indenter.cpp src/engine/scripting/dg_event.cpp src/engine/scripting/dg_handler.cpp src/engine/scripting/dg_misc.cpp @@ -146,11 +147,18 @@ set(SOURCES src/gameplay/communication/spam.cpp src/gameplay/ai/spec_assign.cpp src/gameplay/ai/spec_procs.cpp + src/engine/network/admin_api.cpp + src/engine/network/admin_api/json_helpers.cpp + src/engine/network/admin_api/serializers.cpp + src/engine/network/admin_api/parsers.cpp + src/engine/network/admin_api/crud_handlers.cpp + src/engine/network/admin_api/command_registry.cpp src/engine/network/descriptor_data.cpp src/gameplay/mechanics/stuff.cpp src/engine/structs/flags.hpp src/utils/id_converter.cpp src/utils/utils_time.cpp + src/utils/thread_pool.cpp src/gameplay/mechanics/title.cpp src/gameplay/statistics/top.cpp src/utils/utils.cpp @@ -160,6 +168,22 @@ set(SOURCES src/utils/utils_string.cpp src/engine/db/world_objects.cpp src/engine/db/obj_prototypes.cpp + src/engine/db/world_checksum.cpp + src/engine/db/world_data_source.h + src/engine/db/world_data_source_manager.h + src/engine/db/world_data_source_manager.cpp + src/engine/db/null_world_data_source.h + src/engine/db/legacy_world_data_source.h + src/engine/db/legacy_world_data_source.cpp + src/engine/db/world_data_source_base.h + src/engine/db/world_data_source_base.cpp + $<$:src/engine/db/sqlite_world_data_source.h> + $<$:src/engine/db/sqlite_world_data_source.cpp> + $<$:src/engine/db/yaml_world_data_source.h> + $<$:src/engine/db/yaml_world_data_source.cpp> + $<$:src/engine/db/dictionary_loader.h> + $<$:src/engine/db/dictionary_loader.cpp> + # SQLite world data source (conditional) src/engine/db/id.cpp src/engine/db/utils_find_obj_id_by_vnum.cpp src/engine/db/global_objects.cpp @@ -503,6 +527,7 @@ set(HEADERS src/engine/structs/radix_trie.h src/engine/db/obj_prototypes.h src/engine/db/world_objects.h + src/engine/db/world_checksum.h src/utils/utils_string.h src/gameplay/affects/affect_handler.h src/gameplay/economics/auction.h @@ -543,10 +568,12 @@ set(HEADERS src/gameplay/mechanics/depot.h src/engine/db/description.h src/engine/scripting/dg_db_scripts.h + src/engine/scripting/trigger_indenter.h src/engine/scripting/dg_domination_helper.h src/engine/scripting/dg_event.h src/engine/scripting/dg_olc.h src/engine/scripting/dg_scripts.h + src/engine/scripting/dg_triggers.h src/gameplay/economics/dictionary.h src/utils/diskio.h src/engine/structs/structs_double_map.h @@ -622,6 +649,13 @@ set(HEADERS src/gameplay/skills/skills.h src/gameplay/skills/skills_info.h src/gameplay/communication/spam.h + src/engine/network/admin_api.h + src/engine/network/admin_api/admin_api_constants.h + src/engine/network/admin_api/json_helpers.h + src/engine/network/admin_api/serializers.h + src/engine/network/admin_api/parsers.h + src/engine/network/admin_api/crud_handlers.h + src/engine/network/admin_api/command_registry.h src/engine/network/descriptor_data.h src/engine/structs/structs.h src/gameplay/mechanics/stuff.h @@ -1035,13 +1069,13 @@ set(CRAFT_FILES ${CRAFT_HEADER_FILES}) set(MISC_FILES - lib/misc/noob_help.xml + lib.template/misc/noob_help.xml lib/misc/configuration.xml - lib/misc/grouping) + lib.template/misc/grouping) source_group("Misc" FILES ${MISC_FILES}) -set(CIRCLE_FILES ${SOURCES} ${HEADERS} readme.markdown CONTRIBUTING.md ${CRAFT_FILES} ${MISC_FILES} ) +set(CIRCLE_FILES ${SOURCES} ${HEADERS} README.md CONTRIBUTING.md ${CRAFT_FILES} ${MISC_FILES} ) # Sort source and header files. Just to convenience. list(SORT CIRCLE_FILES) @@ -1066,6 +1100,7 @@ add_dependencies(circle.library versioning changelog) # Linfort integration # Disable building tests and examples in libfort project set(FORT_ENABLE_TESTING OFF CACHE INTERNAL "") +set(FORT_HAVE_UTF8 ON CACHE INTERNAL "") add_subdirectory(src/third_party_libs/libfort) target_link_libraries(circle.library fort) @@ -1132,6 +1167,9 @@ else () set(DEFAULT_HAVE_ZLIB "YES") endif () option(HAVE_ZLIB "Should ZLib be compiled in. It is required to support MCCP." ${DEFAULT_HAVE_ZLIB}) +option(HAVE_SQLITE "Enable SQLite world data source for faster world loading." OFF) +option(ENABLE_ADMIN_API "Enable Admin API via Unix socket" OFF) +option(HAVE_YAML "Enable YAML world data source for human-readable world files." OFF) if (HAVE_ZLIB) set(ZLIB_ROOT $ENV{ZLIB_ROOT}) @@ -1144,6 +1182,89 @@ else () message(STATUS "ZLib is turned off. Circle will NOT support MCCP.") endif () +# Admin API support +if (ENABLE_ADMIN_API) + add_definitions(-DENABLE_ADMIN_API) + include_directories(src/third_party_libs) + message(STATUS "Admin API: ENABLED") +else () + message(STATUS "Admin API: DISABLED") +endif () + +# SQLite support +if (HAVE_SQLITE) + find_package(SQLite3 REQUIRED) + add_definitions(-DHAVE_SQLITE) + + # Create separate library for SQLite loader (without _GLIBCXX_DEBUG) + include_directories(${SQLite3_INCLUDE_DIRS}) + target_link_libraries(circle.library SQLite::SQLite3) + message(STATUS "SQLite is turned ON. Circle will support SQLite world data source.") +else () + message(STATUS "SQLite is turned off.") +endif () + +# YAML support +if (HAVE_YAML) + # Try to find yaml-cpp via CMake config first + find_package(yaml-cpp QUIET) + + if (yaml-cpp_FOUND) + # Modern CMake target found + message(STATUS "yaml-cpp found via find_package") + if (TARGET yaml-cpp) + set(YAML_CPP_LIBRARIES yaml-cpp) + get_target_property(YAML_CPP_INCLUDE_DIRS yaml-cpp INTERFACE_INCLUDE_DIRECTORIES) + else () + # Old-style variables (YAML_CPP_LIBRARIES, YAML_CPP_INCLUDE_DIRS should be set) + if (NOT YAML_CPP_LIBRARIES) + set(YAML_CPP_LIBRARIES ${YAML_CPP_LIBRARY}) + endif () + endif () + else () + # Fallback to manual search (for Cygwin and other systems without CMake config) + message(STATUS "yaml-cpp not found via find_package, trying manual search...") + + find_path(YAML_CPP_INCLUDE_DIR yaml-cpp/yaml.h + PATHS + /usr/include + /usr/local/include + ) + + # Cygwin uses .dll.a suffix for import libraries + if (CYGWIN) + find_library(YAML_CPP_LIBRARY + NAMES yaml-cpp.dll libyaml-cpp.dll + PATHS /usr/lib + ) + else () + find_library(YAML_CPP_LIBRARY + NAMES yaml-cpp libyaml-cpp + PATHS + /usr/lib + /usr/local/lib + /lib + /usr/lib/x86_64-linux-gnu + ) + endif () + + if (YAML_CPP_INCLUDE_DIR AND YAML_CPP_LIBRARY) + message(STATUS "yaml-cpp found manually: ${YAML_CPP_LIBRARY}") + set(YAML_CPP_INCLUDE_DIRS ${YAML_CPP_INCLUDE_DIR}) + set(YAML_CPP_LIBRARIES ${YAML_CPP_LIBRARY}) + else () + message(FATAL_ERROR "yaml-cpp not found. Install libyaml-cpp-devel (Cygwin) or libyaml-cpp-dev (Debian/Ubuntu)") + endif () + endif () + + add_definitions(-DHAVE_YAML) + include_directories(${YAML_CPP_INCLUDE_DIRS}) + target_link_libraries(circle.library ${YAML_CPP_LIBRARIES}) + message(STATUS "YAML is turned ON. Circle will support YAML world data source.") +else () + message(STATUS "YAML is turned off.") +endif () + # Iconv option(HAVE_ICONV "Allows to enable search of iconv." OFF) if (HAVE_ICONV) @@ -1207,7 +1328,7 @@ set(TESTBUILD_DEFINITIONS "-DNOCRYPT -DTEST_BUILD") set(ASAN_FLAGS) if (UNIX AND NOT CYGWIN) set(DEFAULT_WITH_ASAN YES) -elseif () +else () set(DEFAULT_WITH_ASAN NO) endif () option(WITH_ASAN "Compile with ASAN" ${DEFAULT_WITH_ASAN}) @@ -1234,7 +1355,7 @@ endif () if (CMAKE_HOST_UNIX) if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") set(CMAKE_CXX_FLAGS_RELEASE "-ggdb3 -O0 -rdynamic -Wall -Wextra -Wno-format-truncation ${DEBUG_CRYPT}") - set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0 -rdynamic -Wall -Wextra -Wno-format-truncation -D_GLIBCXX_DEBUG -D_GLIBXX_DEBUG_PEDANTIC ${ASAN_FLAGS} ${DEBUG_CRYPT}") + set(CMAKE_CXX_FLAGS_DEBUG "-ggdb3 -O0 -rdynamic -Wall -Wextra -Wno-format-truncation ${ASAN_FLAGS} ${DEBUG_CRYPT}") set(CMAKE_CXX_FLAGS_TEST "-O3 -rdynamic -Wall -Wextra -Wno-format-truncation ${TESTBUILD_DEFINITIONS} -DLOG_AUTOFLUSH") set(CMAKE_CXX_FLAGS_FASTTEST "-Ofast -Wall -Wextra -Wno-format-truncation ${TESTBUILD_DEFINITIONS}") @@ -1264,7 +1385,31 @@ elseif (CMAKE_HOST_WIN32) # EPOLL>>> ...and suppose that Windows does not option(HAS_EPOLL "Does system support epoll call?" OFF) - if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")) + # Project source files are KOI8-R encoded, not UTF-8. + # 1) Replace vcpkg's /utf-8 with KOI8-R charset (local scope only, won't affect fmt/fort) + string(REGEX REPLACE "/utf-8|/source-charset:[^ ]*|/execution-charset:[^ ]*" "" _CXX_FLAGS_FIXED "${CMAKE_CXX_FLAGS}") + string(STRIP "${_CXX_FLAGS_FIXED}" _CXX_FLAGS_FIXED) + set(CMAKE_CXX_FLAGS "${_CXX_FLAGS_FIXED} /source-charset:koi8-r /execution-charset:koi8-r") + # 2) Strip /utf-8 from fmt's INTERFACE_COMPILE_OPTIONS (would override our charset) + get_target_property(_FMT_IOPTS fmt INTERFACE_COMPILE_OPTIONS) + if (_FMT_IOPTS) + list(FILTER _FMT_IOPTS EXCLUDE REGEX "utf-8") + set_property(TARGET fmt PROPERTY INTERFACE_COMPILE_OPTIONS "${_FMT_IOPTS}") + endif() + # 3) Disable fmt Unicode mode (incompatible with KOI8-R source charset) + target_compile_definitions(circle.library PRIVATE FMT_UNICODE=0) + # 4) Suppress warnings in third-party headers included with <> + target_compile_options(circle.library PRIVATE /external:anglebrackets /external:W0) + # 5) Enable parallel compilation + target_compile_options(circle.library PRIVATE /MP) + # 6) Suppress warnings from fmt template instantiations + # /external:templates requires MSVC 19.34+ (VS 2022 17.4+); if unavailable, + # ensure all fmt #includes use <> (not "") so /external:anglebrackets applies + # target_compile_options(circle.library PRIVATE /external:templates) + # 7) Suppress warnings in third-party library TUs (their own compilation units) + target_compile_options(fmt PRIVATE /W0) + target_compile_options(fort PRIVATE /W0) target_link_libraries(circle.library DbgHelp) set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG") set(CMAKE_EXE_LINKER_FLAGS_TEST "/DEBUG") @@ -1305,5 +1450,49 @@ if (BUILD_TESTS) add_subdirectory(tests) endif () -# vim: set ts=4 sw=4 ai tw=0 noet syntax=cmake : +# ============================================================================= +# Data directories setup for running server from build directory +# ============================================================================= + +option(FULL_WORLD_PATH "Path to full world data directory (e.g., /path/to/full.world/lib)" "") + +# Only set up data directories if building out-of-source +if (NOT "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") + # Setup small world directory (lib + lib.template) + set(SMALL_WORLD_DIR "${CMAKE_BINARY_DIR}/small") + + # Create small world directory by copying lib and overlaying lib.template + # First copy lib (base configuration) + execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory "${SMALL_WORLD_DIR}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/lib" "${SMALL_WORLD_DIR}" + RESULT_VARIABLE copy_result) + if (NOT copy_result EQUAL 0) + message(FATAL_ERROR "Failed to copy lib directory") + endif() + + # Overlay lib.template (no conflicts since duplicates were removed) + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/lib.template" "${SMALL_WORLD_DIR}" + RESULT_VARIABLE copy_result) + if (NOT copy_result EQUAL 0) + message(FATAL_ERROR "Failed to overlay lib.template directory") + endif() + message(STATUS "Copied lib and overlaid lib.template to ${SMALL_WORLD_DIR}") + + message(STATUS "Small world directory configured at: ${SMALL_WORLD_DIR}") + + # Setup full world directory if path is specified + if (FULL_WORLD_PATH) + set(FULL_WORLD_DIR "${CMAKE_BINARY_DIR}/full") + if (EXISTS "${FULL_WORLD_PATH}" AND NOT EXISTS "${FULL_WORLD_DIR}") + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink "${FULL_WORLD_PATH}" "${FULL_WORLD_DIR}") + message(STATUS "Full world directory configured at: ${FULL_WORLD_DIR} -> ${FULL_WORLD_PATH}") + elseif (NOT EXISTS "${FULL_WORLD_PATH}") + message(WARNING "FULL_WORLD_PATH specified but does not exist: ${FULL_WORLD_PATH}") + endif() + endif() +else() + message(STATUS "In-source build detected, skipping data directory setup") +endif() + +# vim: set ts=4 sw=4 ai tw=0 noet syntax=cmake : diff --git a/readme.markdown b/README.md similarity index 80% rename from readme.markdown rename to README.md index b933e3cbc..c54229997 100644 --- a/readme.markdown +++ b/README.md @@ -1,5 +1,9 @@ # BRus MUD Engine readme. +[![Multi-Platform Build](https://github.com/bylins/mud/actions/workflows/build.yml/badge.svg)](https://github.com/bylins/mud/actions/workflows/build.yml) +[![Quick Check](https://github.com/bylins/mud/actions/workflows/quick-check.yml/badge.svg)](https://github.com/bylins/mud/actions/workflows/quick-check.yml) +[![codecov](https://codecov.io/gh/bylins/mud/branch/master/graph/badge.svg)](https://codecov.io/gh/bylins/mud) + Для сборки под Ubuntu 24.04 или WSL (ubuntu под WIN10 установка https://docs.microsoft.com/ru-ru/windows/wsl/install) требуется ввести: ## Подготовка diff --git a/YAML_CHECKSUM_TEST_REPORT.md b/YAML_CHECKSUM_TEST_REPORT.md new file mode 100644 index 000000000..d5f152ab3 --- /dev/null +++ b/YAML_CHECKSUM_TEST_REPORT.md @@ -0,0 +1,323 @@ +# YAML Loader - Исправление чексумм - Итоговый отчёт + +**Дата:** 1 февраля 2026 +**Ветка:** yaml-checksums-port +**Текущий коммит:** c9b59763f + +## Резюме + +✅ **Достигнуто 100% совпадение чексумм между всеми загрузчиками, мирами и типами сборки** + +✅ **YAML с многопоточностью оказался БЫСТРЕЙШИМ загрузчиком (на 40% быстрее Legacy!)** + +## Результаты тестирования + +### 1. Release сборка - Все загрузчики, все миры + +**Small World:** +``` +Загрузчик | Зоны | Комнаты | Мобы | Объекты | Триггеры +----------|----------|----------|----------|----------|---------- +Legacy | 7C788E1F | 8C0277A7 | CEBB697B | 7E2C7CC8 | 91924F29 +SQLite | 7C788E1F | 8C0277A7 | CEBB697B | 7E2C7CC8 | 91924F29 +YAML | 7C788E1F | 8C0277A7 | CEBB697B | 7E2C7CC8 | 91924F29 + +Сравнение: ВСЕ СОВПАДАЮТ ✅ +``` + +**Full World:** +``` +Загрузчик | Зоны | Комнаты | Мобы | Объекты | Триггеры +----------|----------|----------|----------|----------|---------- +Legacy | 94AC9F8C | 6CFA6B50 | B146F876 | EA7E36EA | E0FF0BE0 +SQLite | 94AC9F8C | 6CFA6B50 | B146F876 | EA7E36EA | E0FF0BE0 +YAML | 94AC9F8C | 6CFA6B50 | B146F876 | EA7E36EA | E0FF0BE0 + +Сравнение: ВСЕ СОВПАДАЮТ ✅ +``` + +### 2. Debug сборка (с ASAN) + +**Small World - Все загрузчики:** +``` +Legacy vs SQLite: СОВПАДАЮТ ✅ +Legacy vs YAML: СОВПАДАЮТ ✅ +SQLite vs YAML: СОВПАДАЮТ ✅ + +Ошибки ASAN: НЕТ ✅ +Утечки памяти: НЕТ ✅ +``` + +### 3. YAML масштабирование по потокам + +**Чексуммы при разном количестве потоков (Small World):** +``` +Потоки | Зоны | Комнаты | Мобы | Объекты | Триггеры +-------|----------|----------|----------|----------|---------- +1 | 7C788E1F | 8C0277A7 | CEBB697B | 7E2C7CC8 | 91924F29 +2 | 7C788E1F | 8C0277A7 | CEBB697B | 7E2C7CC8 | 91924F29 +4 | 7C788E1F | 8C0277A7 | CEBB697B | 7E2C7CC8 | 91924F29 +8 | 7C788E1F | 8C0277A7 | CEBB697B | 7E2C7CC8 | 91924F29 + +Результат: ИДЕНТИЧНЫ для всех количеств потоков ✅ +Доказывает: Потокобезопасность + Детерминированный порядок ✅ +``` + +## Реализованные исправления + +### 1. Сортировка триггеров по vnum (КРИТИЧНО) +**Файл:** `src/engine/db/yaml_world_data_source.cpp:710-723` + +**Проблема:** GetTriggerRnum использует бинарный поиск, требующий отсортированный trig_index +**Исправление:** Сортировка всех триггеров по vnum перед добавлением в trig_index +**Влияние:** Исправлены чексуммы Objects (7E2C7CC8) и Mobs (CEBB697B) + +### 2. Валидация триггеров комнат (КРИТИЧНО) +**Файлы:** +- `src/engine/db/yaml_world_data_source.cpp:908-927` (загрузка) +- `src/engine/db/yaml_world_data_source.cpp:1019-1030` (привязка) + +**Проблема:** Несуществующие триггеры (напр., триггер 1170 для комнаты 136) попадали в чексуммы +**Исправление:** Временное хранение триггеров, привязка через AttachTriggerToRoom с валидацией +**Влияние:** Исправлена чексумма Rooms (8C0277A7) + +### 3. Оптимизация через thread-local storage (Производительность) +**Файлы:** +- `src/engine/db/yaml_world_data_source.cpp:1768-1774` (объекты) +- `src/engine/db/yaml_world_data_source.h:39` (комнаты) + +**Проблема:** Конкуренция за mutex'ы при параллельной загрузке +**Исправление:** Использование thread-local storage, слияние в последовательной фазе +**Преимущества:** +- Быстрее (нет блокировок mutex'ов) +- Проще (нет риска deadlock'ов) +- Безопаснее (нет shared state) + +### 4. Исправление переменной окружения YAML_THREADS (КРИТИЧНО) +**Файл:** `src/engine/core/config.cpp:570-594, 714-741` + +**Проблема:** Переменная окружения YAML_THREADS игнорировалась +- `RuntimeConfiguration::m_yaml_threads` никогда не инициализировалась +- Функция `load_world_loader_configuration()` не была реализована +- Результат: Всегда использовался `hardware_concurrency()` независимо от настройки + +**Исправление:** Реализована корректная загрузка конфигурации +- Инициализация `m_yaml_threads = 0` в конструкторе RuntimeConfiguration +- Создана `load_world_loader_configuration()` для чтения YAML_THREADS из env +- Переменная окружения имеет приоритет над XML конфигурацией +- Проверка корректности: количество потоков должно быть от 1 до 64 + +**Влияние:** Масштабирование по потокам теперь работает корректно + +### 5. Исправление пути к конфигурационному файлу +**Файл:** `src/engine/core/config.cpp:696, 740-744` + +**Проблема:** Hardcoded путь `lib/misc/configuration.xml` не соответствовал структуре директорий +**Исправление:** Изменён путь на `misc/configuration.xml` +**Дополнительно:** Добавлены явные ERROR/WARNING сообщения при ошибке загрузки конфигурации + +**Важно:** Файл конфигурации должен существовать по пути `/misc/configuration.xml`. + +## Технические детали + +### Требование бинарного поиска +Все функции поиска сущностей используют бинарный поиск: +- `GetTriggerRnum()` - триггеры +- `GetMobRnum()` - мобы +- `GetObjRnum()` - объекты +- `GetRoomRnum()` - комнаты + +**Требование:** Массивы ДОЛЖНЫ быть отсортированы по vnum для работы бинарного поиска. + +### Верификация потокобезопасности +✅ Не нужны mutex'ы - паттерн thread-local storage +✅ Детерминированное слияние через std::map::insert +✅ Сортировка по vnum обеспечивает консистентный порядок +✅ Идентичные чексуммы для 1/2/4/8 потоков + +## Соответствие требованиям + +✅ **Все чексуммы совпадают:** Small + Full миры +✅ **Все загрузчики:** Legacy, SQLite, YAML +✅ **Release сборка:** 100% совпадение +✅ **Debug сборка:** 100% совпадение, нет ошибок ASAN +✅ **YAML масштабирование:** 1/2/4/8 потоков дают идентичные чексуммы + +## Заключение + +YAML загрузчик теперь производит **побитово идентичные** данные мира по сравнению с Legacy загрузчиком для: +- Всех типов сущностей (зоны, комнаты, мобы, объекты, триггеры) +- Всех размеров мира (small, full) +- Всех конфигураций сборки (Release, Debug) +- Всех количеств потоков (1, 2, 4, 8) + +Это обеспечивает полную совместимость и валидирует корректность реализации формата данных YAML. + +--- +**Статус:** ✅ ГОТОВ К MERGE + +--- + +## Сравнение производительности (Release сборка) + +### Small World (5,109 сущностей) + +**Сравнение загрузчиков:** +``` +Загрузчик | Время загрузки | Относ. скорость | Примечания +----------|----------------|-----------------|---------------------------- +Legacy | 2.008s | 1.00x (базовая) | Оригинальный формат CircleMUD +SQLite | 1.993s | 1.00x | Формат БД, ~та же скорость +YAML | 2.103s | 0.95x | Человекочитаемый, ~5% медленнее +``` + +**YAML масштабирование по потокам:** +``` +Потоки | Время загрузки | Ускорение vs 1T | Эффективность +-------|----------------|-----------------|--------------- +1 | 1.386s | 1.00x | 100.0% +2 | 1.260s | 1.10x | 55.0% +4 | 1.229s | 1.13x | 28.2% +8 | 1.227s | 1.13x | 14.1% +``` + +**Анализ:** +- Все загрузчики показывают ~1-2 секунды, разница незначительна +- Многопоточность YAML даёт скромный прирост (10-13%) +- Малый мир недостаточно большой для эффективной параллелизации + +--- + +### Full World (104,144 сущностей) + +**YAML масштабирование по потокам:** +``` +Потоки | Время загрузки | Ускорение vs 1T | Эффективность | Примечания +-------|----------------|-----------------|---------------|--------------------------- +1 | 52.762s | 1.00x | 100.0% | Однопоточная базовая линия +2 | 32.466s | 1.62x | 81.2% | Хорошее ускорение +4 | 22.654s | 2.33x | 58.2% | Отличное масштабирование +8 | 18.196s | 2.90x | 36.2% | Приближается к 3x ускорению +``` + +**Анализ:** +- Отличное масштабирование: 2.90x ускорение с 8 потоками +- Высокая эффективность (36-81%) благодаря большому объёму параллелизуемой работы +- Каждое удвоение потоков даёт значимый прирост + +**Сравнение загрузчиков на Full World:** +``` +Загрузчик | Время загрузки | Относ. скорость | Примечания +---------------|----------------|-----------------------|---------------------------- +YAML (8 пот) | 18.196s | 1.66x (66% быстрее!) | С YAML_THREADS=8 +SQLite | 27.715s | 1.09x (9% быстрее) | Формат БД +Legacy | 30.254s | 1.00x (базовая) | Оригинальный формат +YAML (1 пот) | 52.762s | 0.57x (74% медленнее) | Однопоточный +``` + +**Анализ:** +- ⭐ **YAML с 8 потоками - САМЫЙ БЫСТРЫЙ загрузчик:** 18.2s, на 40% быстрее Legacy +- Многопоточность критична для YAML: 2.90x ускорение +- SQLite второй по скорости: 27.7s, на 9% быстрее Legacy +- Legacy - стабильная базовая линия: 30.3s +- YAML без многопоточности самый медленный: 52.8s + +**Статистика Full World:** +- 640 зон, 46,542 комнат, 18,757 мобов, 22,022 объектов, 16,823 триггеров +- В 20 раз больше чем small world +- Репрезентативно для продакшен нагрузки + +--- + +## Рекомендации для продакшена + +### Small World (< 10K сущностей): +- **Любой загрузчик подходит** (~2 секунды, разница незначительна) +- YAML без многопоточности приемлем для малых миров +- Рекомендация: YAML для удобства редактирования + +### Full World (100K+ сущностей): + +**1. ⭐ YAML с YAML_THREADS=8** (РЕКОМЕНДУЕТСЯ) + - ✅ Самая высокая производительность: 18.2s + - ✅ На 40% быстрее Legacy + - ✅ На 34% быстрее SQLite + - ✅ Человекочитаемый формат для редактирования + - ⚠️ **КРИТИЧНО:** Обязательно устанавливать YAML_THREADS=4 или выше! + +**2. SQLite** + - ✅ Хорошая производительность: 27.7s (на 9% быстрее Legacy) + - ✅ Не требует настройки потоков + - ❌ Бинарный формат, сложнее редактировать + +**3. Legacy** + - ✅ Стабильная базовая производительность: 30.3s + - ✅ Проверенный временем, максимальная совместимость + - ❌ Медленнее YAML и SQLite + +**4. ❌ YAML без многопоточности (YAML_THREADS=1)** + - ❌ Самая низкая производительность: 52.8s + - ❌ Недопустимо для продакшена + - Использовать только для отладки + +### Настройка YAML_THREADS: + +```bash +# Для full world (100K+ сущностей) +export YAML_THREADS=8 # Оптимально + +# Для средних миров (20K-100K сущностей) +export YAML_THREADS=4 # Хороший баланс + +# Для малых миров (< 20K сущностей) +export YAML_THREADS=2 # Достаточно + +# Для отладки +export YAML_THREADS=1 # Упрощает поиск багов +``` + +### Проверка настройки: + +После запуска сервера проверьте syslog: +```bash +grep "YAML loading with" syslog +``` + +Должно быть: +``` +YAML loading with 8 threads # ✅ Правильно +``` + +Если видите другое количество потоков, проверьте: +1. Файл `misc/configuration.xml` существует в директории данных +2. Переменная окружения `YAML_THREADS` установлена перед запуском +3. Нет сообщений об ошибках загрузки конфигурации в stderr + +--- + +## Выводы + +### Достижения: +✅ 100% совпадение чексумм между всеми загрузчиками +✅ YAML оказался быстрейшим загрузчиком (на 40% быстрее Legacy!) +✅ Многопоточность работает корректно и детерминированно +✅ Потокобезопасность проверена (идентичные чексуммы для 1-8 потоков) +✅ Debug build без ошибок ASAN и утечек памяти + +### Ключевые находки: +- Многопоточность критична для производительности YAML (2.9x ускорение) +- YAML с правильными настройками превосходит Legacy и SQLite +- Переменная окружения YAML_THREADS обязательна для продакшена +- Путь к конфигурации должен быть `misc/configuration.xml` + +### Статус: +**✅ ГОТОВ К MERGE В world-load-refactoring** + +--- + +**Коммиты:** +- `09981763d` - Fix YAML loader checksums to match Legacy loader (100%) +- `c041c365e` - Fix YAML_THREADS environment variable not being read +- `eebb21449` - Fix configuration file path and improve error reporting +- `07c584d34` - Fix loader comparison: YAML with 8 threads is 40% faster than Legacy +- `c9b59763f` - Add Russian version of test report with corrected performance data diff --git a/autorun b/autorun index b1e17e4f0..184d654d1 100755 --- a/autorun +++ b/autorun @@ -1,18 +1,15 @@ #!/bin/bash -PORT=4000 +CIRCLE=${CIRCLE:-build/circle} ulimit -c unlimited umask u=rwx,g=rwx,o= export ASAN_OPTIONS=log_path=/home/mud/mud/asan.log -# Default flags to pass to the MUD server (see admin.txt for a description -# of all flags). -FLAGS='' ############################################################################# while ( : ) do DATE=`date` echo "autorun starting game $DATE" >> syslog - echo "running bin/circle $FLAGS $PORT" >> syslog + echo "running $CIRCLE $@" >> syslog chmod 660 /home/mud/mud/lib/etc/board/* - build/circle $FLAGS $PORT >>sd.out + $CIRCLE "$@" >>sd.out mv syslog.CRASH.1 syslog.CRASH.2 mv syslog.CRASH syslog.CRASH.1 tail -1000 syslog > syslog.CRASH @@ -47,4 +44,4 @@ while ( : ) do rm .fastboot sleep 5 fi -done \ No newline at end of file +done diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..969fbfa34 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,4 @@ +coverage: + ignore: + - "tests/" + - "src/third_party_libs/" diff --git a/docs/ADMIN_API_RU.md b/docs/ADMIN_API_RU.md new file mode 100644 index 000000000..c1c1febc6 --- /dev/null +++ b/docs/ADMIN_API_RU.md @@ -0,0 +1,1111 @@ +# Документация Admin API + +## Обзор + +Admin API предоставляет JSON-интерфейс через Unix Domain Socket для онлайн-редактирования данных мира (зоны, мобы, объекты, комнаты, триггеры). Служит программным фронтендом к системе OLC (OnLine Creation). + +**Ключевые возможности:** +- Коммуникация через Unix Domain Socket (только локальный доступ) +- Протокол запрос/ответ на основе JSON +- Аутентификация через учетные данные игрового аккаунта (иммортале/строители) +- Автоматическая конвертация UTF-8 ↔ KOI8-R +- Ограничение на одно одновременное подключение +- Прямая интеграция с внутренними методами OLC + +**Архитектура:** +``` +Клиент (Python/curl/netcat) → Unix Socket → Admin API → Методы OLC → Данные мира + JSON/UTF-8 medit_save_internally() + oedit_save_internally() + redit_save_internally() +``` + +## Подключение + +**Путь к сокету:** Настраивается в `lib/misc/configuration.xml`: +```xml + + admin_api.sock + true + +``` + +Расположение по умолчанию: `admin_api.sock` (относительно игровой директории) + +**Протокол:** +- JSON с разделением строками (каждый запрос/ответ заканчивается `\n`) +- Кодировка UTF-8 (автоматическая конвертация в/из KOI8-R) +- Одно постоянное соединение (рекомендуется переиспользование) + +**Ограничение подключений:** Максимум 1 одновременное admin-подключение + +## Аутентификация + +**Все операции API (кроме `ping`) требуют аутентификации.** + +### Команда: `auth` + +Аутентификация с использованием учетных данных игрового аккаунта (только иммортали и строители). + +**Запрос:** +```json +{ + "command": "auth", + "username": "ВашеИмя", + "password": "ваш_пароль" +} +``` + +**Ответ (успех):** +```json +{ + "status": "ok", + "message": "Authentication successful" +} +``` + +**Ответ (ошибка):** +```json +{ + "status": "error", + "error": "Authentication failed" +} +``` + +**Требования к доступу:** +- Аккаунт должен существовать в игровых файлах +- Минимальный уровень: Строитель (kLvlBuilder) +- И иммортали, и строители имеют полный доступ к API + +## Зоны + +### Команда: `list_zones` + +Получить список всех зон. + +**Запрос:** +```json +{ + "command": "list_zones" +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "zones": [ + { + "vnum": 1, + "name": "Тестовая Зона", + "level": 1, + "age": 15, + "lifespan": 20, + "reset_mode": 2 + } + ] +} +``` + +**Поля:** +- `vnum` (int) - Виртуальный номер зоны +- `name` (string) - Название зоны +- `level` (int) - Рекомендуемый уровень +- `age` (int) - Текущий возраст в пульсах +- `lifespan` (int) - Интервал сброса в пульсах +- `reset_mode` (int) - Поведение сброса (0-3) + +### Команда: `get_zone` + +Получить детальную информацию о зоне. + +**Запрос:** +```json +{ + "command": "get_zone", + "vnum": 1 +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "zone": { + "vnum": 1, + "name": "Тестовая Зона", + "level": 1, + "type": 0, + "locked": false, + "reset_idle": false, + "age": 15, + "lifespan": 20, + "top": 199, + "reset_mode": 2, + "group": 1 + } +} +``` + +**Дополнительные поля:** +- `type` (int) - Тип зоны (0=обычная, и т.д.) +- `locked` (bool) - Зона заблокирована для редактирования +- `reset_idle` (bool) - Сброс только когда нет игроков +- `top` (int) - Наивысший vnum в зоне +- `group` (int) - ID группы зоны + +### Команда: `update_zone` + +Обновить свойства зоны. + +**Запрос:** +```json +{ + "command": "update_zone", + "vnum": 1, + "data": { + "name": "Обновленное Имя Зоны", + "level": 5, + "lifespan": 30, + "reset_mode": 1 + } +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "message": "Zone updated successfully" +} +``` + +**Обновляемые поля:** +- `name` (string) - Название +- `level` (int) - Уровень +- `type` (int) - Тип +- `locked` (bool) - Заблокирована +- `reset_idle` (bool) - Сброс в idle +- `lifespan` (int) - Время жизни +- `reset_mode` (int) - Режим сброса +- `group` (int) - Группа + +## Мобы + +### Команда: `list_mobs` + +Список всех мобов в зоне. + +**Запрос:** +```json +{ + "command": "list_mobs", + "zone": "1" +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "mobs": [ + { + "vnum": 100, + "name": "тестовый моб", + "short_desc": "Тестовый моб стоит здесь.", + "level": 1 + } + ] +} +``` + +### Команда: `get_mob` + +Получить детальные данные моба. + +**Запрос:** +```json +{ + "command": "get_mob", + "vnum": 100 +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "mob": { + "vnum": 100, + "names": { + "aliases": "тестовый моб", + "nominative": "тестовый моб", + "genitive": "тестового моба", + "dative": "тестовому мобу", + "accusative": "тестового моба", + "instrumental": "тестовым мобом", + "prepositional": "тестовом мобе" + }, + "descriptions": { + "short_desc": "Тестовый моб стоит здесь.", + "long_desc": "Это длинное описание моба.\n" + }, + "stats": { + "level": 1, + "sex": 0, + "race": 0, + "alignment": 0, + "hitroll_penalty": 0, + "armor": 100, + "hp": { + "dice_count": 1, + "dice_size": 10, + "bonus": 0 + }, + "damage": { + "dice_count": 1, + "dice_size": 4, + "bonus": 0 + } + }, + "abilities": { + "strength": 11, + "dexterity": 11, + "constitution": 11, + "intelligence": 11, + "wisdom": 11, + "charisma": 11 + }, + "resistances": { + "fire": 0, + "air": 0, + "water": 0, + "earth": 0, + "vitality": 0, + "mind": 0, + "immunity": 0 + }, + "savings": { + "will": 0, + "stability": 0, + "reflex": 0 + }, + "position": { + "default_position": 8, + "load_position": 8 + }, + "behavior": { + "class": 0, + "special": 0, + "attack_type": 0, + "exp_bonus": 0, + "gold": { + "dice_count": 0, + "dice_size": 0, + "bonus": 0 + }, + "helpers": [] + }, + "flags": { + "mob_flags": ["!SLEEP", "!CHARM"], + "affect_flags": [], + "npc_flags": [] + }, + "skills": {}, + "features": {}, + "spells": {}, + "triggers": [100, 101] + } +} +``` + +### Команда: `update_mob` + +Обновить поля моба. + +**Запрос:** +```json +{ + "command": "update_mob", + "vnum": 100, + "data": { + "names": { + "aliases": "измененный моб" + }, + "stats": { + "level": 5 + } + } +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "message": "Mob updated successfully" +} +``` + +**Обновляемые группы полей:** + +**names:** Все падежные формы русского языка +- `aliases` (string) - Ключевые слова для таргетинга +- `nominative`, `genitive`, `dative`, `accusative`, `instrumental`, `prepositional` (string) + +**descriptions:** +- `short_desc` (string) - Описание в комнате +- `long_desc` (string) - Описание при осмотре + +**stats:** +- `level` (int) - Уровень моба +- `sex` (int) - 0=средний, 1=мужской, 2=женский, 3=множественное +- `race` (int) - ID расы +- `alignment` (int) - От -1000 до 1000 +- `hitroll_penalty` (int) - Модификатор атаки +- `armor` (int) - Значение AC +- `hp` (object) - `{dice_count, dice_size, bonus}` +- `damage` (object) - `{dice_count, dice_size, bonus}` + +**abilities:** Атрибуты (3-50) +- `strength`, `dexterity`, `constitution`, `intelligence`, `wisdom`, `charisma` (int) + +**resistances:** Сопротивления стихиям (-200 до 200) +- `fire`, `air`, `water`, `earth`, `vitality`, `mind`, `immunity` (int) + +**savings:** Модификаторы спасбросков +- `will`, `stability`, `reflex` (int) + +**position:** +- `default_position` (int) - Стойка по умолчанию (0-8) +- `load_position` (int) - Стойка при появлении (0-8) + +**behavior:** +- `class` (int) - ID класса NPC +- `special` (int) - ID спец-процедуры +- `attack_type` (int) - Тип сообщения атаки +- `exp_bonus` (int) - Модификатор опыта (%) +- `gold` (object) - `{dice_count, dice_size, bonus}` +- `helpers` (array) - Vnum'ы мобов-помощников + +**flags:** +- `mob_flags` (array of strings) - Флаги MOB_* +- `affect_flags` (array of strings) - Флаги AFF_* +- `npc_flags` (array of strings) - Флаги NPC_* + +**skills:** Умения +- Формат: `{"skill_name": level}` (например, `{"backstab": 50}`) + +**features:** Способности +- Формат: `{"feature_name": true}` (например, `{"dodge": true}`) + +**spells:** Известные заклинания +- Формат: `{"spell_name": true}` (например, `{"fireball": true}`) + +**triggers:** DG Script триггеры +- Массив vnum'ов триггеров (например, `[100, 101]`) + +### Команда: `create_mob` + +Создать нового моба в зоне. + +**Запрос:** +```json +{ + "command": "create_mob", + "zone": 1, + "data": { + "names": { + "aliases": "новый моб" + }, + "stats": { + "level": 1 + } + } +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "message": "Mob created successfully", + "vnum": 150 +} +``` + +**Примечания:** +- Автоматическое назначение vnum в диапазоне зоны +- В зоне должны быть доступные vnum'ы (максимум 100 на зону) +- Возвращается назначенный vnum + +### Команда: `delete_mob` + +**НЕ РЕАЛИЗОВАНО** - Удаление требует сложной проверки ссылок (живые экземпляры, команды зон, триггеры). + +## Объекты + +### Команда: `list_objects` + +Список всех объектов в зоне. + +**Запрос:** +```json +{ + "command": "list_objects", + "zone": "1" +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "objects": [ + { + "vnum": 100, + "name": "тестовый объект", + "short_desc": "тестовый предмет", + "type": 1 + } + ] +} +``` + +### Команда: `get_object` + +Получить детальные данные объекта. + +**Запрос:** +```json +{ + "command": "get_object", + "vnum": 100 +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "object": { + "vnum": 100, + "names": { + "aliases": "тестовый объект", + "nominative": "тестовый предмет", + "genitive": "тестового предмета", + "dative": "тестовому предмету", + "accusative": "тестовый предмет", + "instrumental": "тестовым предметом", + "prepositional": "тестовом предмете" + }, + "descriptions": { + "short_desc": "тестовый предмет", + "long_desc": "Тестовый предмет лежит здесь.", + "action_desc": "" + }, + "stats": { + "type": 1, + "wear_flags": ["TAKE", "HOLD"], + "extra_flags": [], + "no_flags": [], + "anti_flags": [], + "weight": 1, + "cost": 100, + "rent": 10, + "minimum_level": 0, + "sex": 0, + "maximum_durability": 100, + "current_durability": 100, + "material": 0, + "timer": 0, + "quantity": 1 + }, + "type_specific": { + "value0": 0, + "value1": 0, + "value2": 0, + "value3": 0 + }, + "affects": [], + "extra_descriptions": [], + "skills": {}, + "triggers": [] + } +} +``` + +### Команда: `update_object` + +Обновить поля объекта. + +**Запрос:** +```json +{ + "command": "update_object", + "vnum": 100, + "data": { + "stats": { + "weight": 5, + "cost": 500 + } + } +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "message": "Object updated successfully" +} +``` + +**Обновляемые группы полей:** + +**names:** Все падежные формы русского языка +- `aliases`, `nominative`, `genitive`, `dative`, `accusative`, `instrumental`, `prepositional` (string) + +**descriptions:** +- `short_desc` (string) - Название в инвентаре +- `long_desc` (string) - Описание на земле +- `action_desc` (string) - Описание при надетом состоянии + +**stats:** +- `type` (int) - Тип объекта (1=свет, 2=свиток, и т.д.) +- `wear_flags` (array of strings) - Флаги ITEM_WEAR_* +- `extra_flags` (array of strings) - Флаги ITEM_* +- `no_flags` (array of strings) - Флаги ITEM_NO_* +- `anti_flags` (array of strings) - Флаги ITEM_ANTI_* +- `weight` (int) - Вес в условных единицах +- `cost` (int) - Цена в магазине +- `rent` (int) - Стоимость ренты +- `minimum_level` (int) - Ограничение по уровню +- `sex` (int) - Грамматический род +- `maximum_durability` (int) - Макс. прочность +- `current_durability` (int) - Текущая прочность +- `material` (int) - ID материала +- `timer` (int) - Таймер распада +- `quantity` (int) - Размер стака + +**type_specific:** Значения, зависящие от типа объекта +- `value0`, `value1`, `value2`, `value3` (int) +- Значение варьируется в зависимости от типа объекта (например, для оружия: умение, dice_count, dice_size) + +**affects:** Применяемые модификаторы характеристик +- Массив пар `{location: int, modifier: int}` +- Пример: `[{"location": 1, "modifier": 5}]` (+5 к силе) + +**extra_descriptions:** Дополнительные тексты для осмотра +- Массив пар `{keywords: string, description: string}` + +**skills:** Бонусы к умениям +- Формат: `{"skill_name": bonus}` (например, `{"riding": 10}`) + +**triggers:** DG Script триггеры +- Массив vnum'ов триггеров + +### Команда: `create_object` + +Создать новый объект в зоне. + +**Запрос:** +```json +{ + "command": "create_object", + "zone": 3, + "data": { + "names": { + "aliases": "новый предмет" + }, + "stats": { + "type": 1 + } + } +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "message": "Object created successfully", + "vnum": 390 +} +``` + +### Команда: `delete_object` + +**НЕ РЕАЛИЗОВАНО** - По тем же причинам, что и delete_mob. + +## Комнаты + +### Команда: `list_rooms` + +Список всех комнат в зоне. + +**Запрос:** +```json +{ + "command": "list_rooms", + "zone": "1" +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "rooms": [ + { + "vnum": 100, + "name": "Тестовая Комната" + } + ] +} +``` + +### Команда: `get_room` + +Получить детальные данные комнаты. + +**Запрос:** +```json +{ + "command": "get_room", + "vnum": 100 +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "room": { + "vnum": 100, + "name": "Тестовая Комната", + "description": "Это тестовая комната.\n", + "sector": 0, + "room_flags": ["INDOORS"], + "exits": [ + { + "direction": 0, + "description": "", + "keywords": "", + "exit_info": 0, + "key": -1, + "to_room": 101 + } + ], + "extra_descriptions": [], + "triggers": [100, 198] + } +} +``` + +### Команда: `update_room` + +Обновить поля комнаты. + +**Запрос:** +```json +{ + "command": "update_room", + "vnum": 100, + "data": { + "name": "Измененная Комната", + "sector": 2, + "triggers": [100] + } +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "message": "Room updated successfully" +} +``` + +**Обновляемые поля:** + +- `name` (string) - Название комнаты +- `description` (string) - Описание комнаты (должно заканчиваться `\n`) +- `sector` (int) - Тип местности (0=внутри, 1=город, 2=поле, и т.д.) +- `room_flags` (array of strings) - Флаги ROOM_* +- `extra_descriptions` (array) - `[{keywords: string, description: string}]` +- `triggers` (array of int) - Vnum'ы мировых триггеров + +**exits:** Массив объектов выходов +- `direction` (int) - 0=север, 1=восток, 2=юг, 3=запад, 4=вверх, 5=вниз +- `description` (string) - Текст при осмотре выхода +- `keywords` (string) - Ключевые слова двери +- `exit_info` (int) - Флаги выхода (0=открыто, 1=дверь, 2=заперто, и т.д.) +- `key` (int) - Vnum объекта-ключа (-1=нет ключа) +- `to_room` (int) - Vnum комнаты назначения + +### Команда: `create_room` + +**НЕ РЕАЛИЗОВАНО** - Запланировано на будущее. + +### Команда: `delete_room` + +**НЕ РЕАЛИЗОВАНО** - По тем же причинам, что и delete_mob. + +## Триггеры + +### Команда: `list_triggers` + +Список всех триггеров в зоне. + +**Запрос:** +```json +{ + "command": "list_triggers", + "zone": "1" +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "triggers": [ + { + "vnum": 100, + "name": "Тестовый Триггер", + "type": "GREET" + } + ] +} +``` + +### Команда: `get_trigger` + +Получить детальные данные триггера. + +**Запрос:** +```json +{ + "command": "get_trigger", + "vnum": 100 +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "trigger": { + "vnum": 100, + "name": "Тестовый Триггер", + "attach_type": 2, + "trigger_type": "GREET", + "narg": 100, + "argument": "", + "commands": "say Привет, %actor.name%!\n" + } +} +``` + +### Команда: `update_trigger` + +Обновить скрипт триггера. + +**Запрос:** +```json +{ + "command": "update_trigger", + "vnum": 100, + "data": { + "name": "Измененный Триггер", + "commands": "say Обновленное приветствие!\n" + } +} +``` + +**Ответ:** +```json +{ + "status": "ok", + "message": "Trigger updated successfully" +} +``` + +**Обновляемые поля:** + +- `name` (string) - Название триггера +- `attach_type` (int) - 0=моб, 1=объект, 2=комната +- `trigger_type` (string) - Тип триггера ("GREET", "COMMAND", "RANDOM", и т.д.) +- `narg` (int) - Числовой аргумент (варьируется в зависимости от типа) +- `argument` (string) - Текстовый аргумент (варьируется в зависимости от типа) +- `commands` (string) - Код DG Script + +**Распространенные типы триггеров:** +- MOB: `GREET`, `COMMAND`, `SPEECH`, `FIGHT`, `HITPRCNT`, `DEATH`, `ENTRY`, `RECEIVE`, `BRIBE`, `RANDOM` +- OBJECT: `COMMAND`, `TIMER`, `GET`, `DROP`, `GIVE`, `WEAR`, `REMOVE`, `RANDOM` +- ROOM: `COMMAND`, `SPEECH`, `ENTER`, `DROP`, `RANDOM` + +### Команда: `create_trigger` + +**НЕ РЕАЛИЗОВАНО** - Запланировано на будущее. + +### Команда: `delete_trigger` + +**НЕ РЕАЛИЗОВАНО** - По тем же причинам, что и delete_mob. + +## Обработка ошибок + +Все ошибки возвращают: +```json +{ + "status": "error", + "error": "Описание сообщения об ошибке" +} +``` + +**Распространенные ошибки:** + +- `"Not authenticated. Use 'auth' command first."` - Отсутствует аутентификация +- `"Authentication failed"` - Неверные учетные данные или недостаточные привилегии +- `"Zone not found"` - Неверный vnum зоны +- `"Mob not found"` - Неверный vnum моба +- `"Object not found"` - Неверный vnum объекта +- `"Room not found"` - Неверный vnum комнаты +- `"Trigger not found"` - Неверный vnum триггера +- `"Max admin connections reached"` - Подключен другой администратор +- `"No available vnums in zone"` - Диапазон vnum зоны исчерпан (использовано 100/100) +- `"JSON parse error: ..."` - Неправильно сформированный запрос +- `"Unknown command"` - Неверное имя команды + +## Примеры использования + +### Клиент на Python + +```python +#!/usr/bin/env python3 +import socket +import json + +# Подключение к сокету +sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +sock.connect('lib/admin_api.sock') + +def send_command(cmd): + """Отправить JSON команду и получить ответ.""" + sock.sendall((json.dumps(cmd) + '\n').encode('utf-8')) + response = b'' + while True: + chunk = sock.recv(4096) + response += chunk + if b'\n' in response: + line = response.split(b'\n', 1)[0] + return json.loads(line.decode('utf-8')) + +# Аутентификация +print(send_command({ + 'command': 'auth', + 'username': 'ВашеИмя', + 'password': 'ваш_пароль' +})) + +# Список мобов в зоне 1 +print(send_command({ + 'command': 'list_mobs', + 'zone': '1' +})) + +# Получить детали моба +mob_data = send_command({ + 'command': 'get_mob', + 'vnum': 100 +}) +print(f"Уровень моба: {mob_data['mob']['stats']['level']}") + +# Обновить моба +print(send_command({ + 'command': 'update_mob', + 'vnum': 100, + 'data': { + 'stats': {'level': 10} + } +})) + +sock.close() +``` + +### Использование netcat (nc) + +```bash +# Одиночная команда (требуется повторная аутентификация) +echo '{"command":"auth","username":"ВашеИмя","password":"пароль"}' | nc -U lib/admin_api.sock + +# Интерактивная сессия (постоянное соединение) +nc -U lib/admin_api.sock << 'EOF' +{"command":"auth","username":"ВашеИмя","password":"пароль"} +{"command":"list_mobs","zone":"1"} +{"command":"get_mob","vnum":100} +EOF + +# Многострочный скрипт +( + echo '{"command":"auth","username":"ВашеИмя","password":"пароль"}' + sleep 0.1 + echo '{"command":"get_mob","vnum":100}' +) | nc -U lib/admin_api.sock +``` + +### Использование curl (через прокси socat) + +```bash +# Запустить HTTP→Unix socket прокси +socat TCP-LISTEN:8888,reuseaddr,fork UNIX-CONNECT:lib/admin_api.sock & + +# Использовать curl +curl -X POST http://localhost:8888 \ + -H "Content-Type: application/json" \ + -d '{"command":"ping"}' + +curl -X POST http://localhost:8888 \ + -H "Content-Type: application/json" \ + -d '{"command":"auth","username":"ВашеИмя","password":"пароль"}' +``` + +## Ограничения + +1. **Одно подключение:** Разрешено только одно одновременное admin-подключение +2. **Нет удаления:** Операции создания поддерживаются, но операции удаления не реализованы +3. **Нет создания/удаления зон:** Управление зонами ограничено обновлениями +4. **Исчерпание Vnum:** Каждая зона ограничена 100 vnum'ами (зона N: N×100 до N×100+99) +5. **Только живые данные:** API работает с данными в памяти; сервер должен сохранить для сохранения изменений +6. **Нет валидации:** Ограниченная валидация входных данных; неправильные данные могут привести к падению сервера +7. **Нет транзакций:** Изменения немедленные; нет поддержки отката +8. **Кодировка:** Весь текст должен быть в валидном UTF-8 (автоматическая конвертация в/из KOI8-R) + +## Компиляция + +Admin API опциональный и отключен по умолчанию. + +**Включить:** +```bash +cmake -DENABLE_ADMIN_API=ON .. +make +``` + +**Конфигурация:** +Отредактировать `lib/misc/configuration.xml`: +```xml + + admin_api.sock + true + +``` + +**Проверка:** +```bash +# Проверить существование сокета +ls -la lib/admin_api.sock + +# Тестовое подключение +echo '{"command":"ping"}' | nc -U lib/admin_api.sock +# Ожидается: {"status":"pong"} +``` + +## Безопасность + +**Контроль доступа:** +- Unix Domain Socket = только локальный доступ (нет сетевой экспозиции) +- Права файла: 0600 (чтение/запись только владельцем) +- Аутентификация требуется для всех операций кроме `ping` +- Минимальный уровень привилегий: Строитель (kLvlBuilder) + +**Рекомендации:** +- Используйте сильные пароли для аккаунтов строителей/иммортале +- Ограничьте доступ к файловой системе игровой директории +- Мониторьте подключения через логи сервера +- Запускайте игровой сервер под выделенным пользовательским аккаунтом +- Рассмотрите SSH-туннелирование для удаленного администрирования + +## Устранение неполадок + +### Отказ в подключении +``` +nc: unix connect failed: Connection refused +``` +**Решения:** +- Проверьте что `admin_api.enabled` установлено в `true` в configuration.xml +- Убедитесь что сервер скомпилирован с `-DENABLE_ADMIN_API=ON` +- Подтвердите что файл сокета существует: `ls -la lib/admin_api.sock` +- Проверьте логи сервера на ошибки инициализации + +### Достигнуто максимум подключений +``` +{"status":"error","error":"Max admin connections reached"} +``` +**Решения:** +- Закройте существующее admin-подключение +- Проверьте устаревшие подключения: `lsof lib/admin_api.sock` +- Перезапустите сервер для очистки подключений + +### Ошибка аутентификации +``` +{"status":"error","error":"Authentication failed"} +``` +**Решения:** +- Проверьте что имя пользователя/пароль правильные +- Проверьте что уровень аккаунта >= Строитель: `stat player_file` +- Убедитесь что аккаунт существует в игровых файлах +- Проверьте логи сервера для детальной ошибки + +### Ошибка парсинга JSON +``` +{"status":"error","error":"JSON parse error: ..."} +``` +**Решения:** +- Валидируйте синтаксис JSON (используйте `jq` или онлайн-валидатор) +- Убедитесь в правильной кодировке UTF-8 +- Проверьте отсутствующие кавычки, запятые, скобки +- Проверьте завершение строки с `\n` + +### Моб/Объект/Комната не найдены +``` +{"status":"error","error":"Mob not found"} +``` +**Решения:** +- Проверьте что vnum существует: используйте `list_mobs`, `list_objects`, `list_rooms` +- Проверьте что vnum в правильном диапазоне зоны (зона N: N×100 до N×100+99) +- Убедитесь что зона загружена (проверьте `list_zones`) + +### Нет доступных vnum'ов +``` +{"status":"error","error":"No available vnums in zone"} +``` +**Решения:** +- Выберите другую зону с свободными vnum'ами +- Удалите неиспользуемые сущности в зоне (не через API - используйте OLC в игре) +- Используйте явное назначение vnum (если поддерживается) +- Зоны ограничены 100 vnum'ами каждая (0-99) + +### Команды обрезаны +**Симптом:** Команды обрываются посреди символа (особенно с кириллическим текстом) + +**Решение:** +- Убедитесь что сервер собран с исправлениями process_input() для UTF-8 +- Проверьте что флаг `admin_api_mode` правильно установлен на дескрипторе +- Проверьте что автоматическая конвертация UTF-8 ↔ KOI8-R включена + +### Пустые ответы +**Симптом:** Поля отсутствуют или пусты в ответах get_* + +**Решения:** +- Проверьте что данные прототипа существуют (используйте OLC в игре для проверки) +- Для триггеров: убедитесь что `proto_script` заполнен (а не только `script`) +- Проверьте что поле поддерживается API (см. списки полей выше) + +## Смотрите также + +- **Документация OLC:** Внутриигровая помощь для `MEDIT`, `OEDIT`, `REDIT`, `ZEDIT`, `TRIGEDIT` +- **DG Scripts:** `/src/engine/scripting/dg_scripts.h` для типов триггеров +- **Исходный код:** `/src/engine/network/admin_api.cpp` для деталей реализации +- **Тесты:** `/tests/test_admin_api.py` для примеров использования diff --git a/docs/YAML_MIGRATION_GUIDE.md b/docs/YAML_MIGRATION_GUIDE.md new file mode 100644 index 000000000..12b43eb1d --- /dev/null +++ b/docs/YAML_MIGRATION_GUIDE.md @@ -0,0 +1,337 @@ +# План миграции CircleMUD на YAML формат + +## Подготовка окружения + +### Настройка тестового зеркала + +Тестовое зеркало - это отдельная установка MUD для проверки миграции перед применением на продакшене. + +```bash +# Перейти в директорию зеркала +cd /home/stribog/mud + +# Установить переменные окружения +export DIR=$(pwd) +export WORLD=$(pwd)/lib +export PROD_WORLD=/home/mud/mud/lib + +# Скопировать мир из продакшена +rsync -av --delete /home/mud/mud/lib/ ./lib/ + +# Проверить структуру +ls -la $WORLD/world/ +``` + +**Ожидаемый результат:** +- Переменные установлены +- Мир скопирован из продакшена +- Структура `lib/world/` с legacy форматом (wld/, mob/, obj/, zon/, trg/) + +--- + +### Настройка продакшена для миграции + +```bash +# Перейти в продакшен +cd /home/mud/mud + +# Установить переменные окружения +export DIR=$(pwd) +export WORLD=$(pwd)/lib + +# КРИТИЧНО: Создать резервную копию +mkdir -p backup +tar -czf backup/lib_before_yaml_$(date +%Y%m%d_%H%M%S).tar.gz lib + +# Проверить бэкапы +ls -lh backup/ +du -sh backup/* + +# КРИТИЧНО: Остановить сервер +killall -TERM circle +sleep 5 +ps aux | grep circle # Проверить, что остановился +``` + +--- + +## Миграция (одинаковые команды для зеркала и прода) + +С этого момента команды одинаковые для тестового зеркала и продакшена. Выполняются в текущей директории установки (`$DIR`). + +### ЭТАП 1: Сборка бинарника с YAML + +```bash +# Проверить yaml-cpp +dpkg -l | grep libyaml-cpp-dev +# Если нет: sudo apt-get install libyaml-cpp-dev + +# Создать build директорию +mkdir -p build_yaml +cd build_yaml + +# Сборка (указать путь к исходникам репозитория) +cmake -DCMAKE_BUILD_TYPE=Release \ + -DHAVE_YAML=ON \ + -DBUILD_TESTS=OFF \ + .. + +make -j2 + +# Проверить сборку +ls -lh circle +ldd circle | grep yaml + +# Вернуться в корень установки +cd $DIR +``` + +--- + +### ЭТАП 2: Конверсия мира + +```bash +# Подготовить виртуальное окружение для конвертера +python3 -m venv .venv +source .venv/bin/activate +pip install ruamel.yaml + +# Конвертировать lib/world/ (legacy) → world/ (YAML) +python3 tools/converter/convert_to_yaml.py \ + --input lib/ \ + --output lib/ \ + --format yaml \ + --type all \ + --workers $(nproc) + +# Проверить результат +ls -la lib/world/ +ls -la lib/world/zones/ +ls -la lib/world/mobs/ + +# Статистика +echo "=== Статистика конверсии ===" + +ZONES_FILES=$(ls -d lib/world/zones/*/ 2>/dev/null | wc -l) +ZONES_INDEX=$(grep -c '^[[:space:]]*- ' lib/world/zones/index.yaml 2>/dev/null || echo 0) +echo "Зоны: файлов: $ZONES_FILES, в индексе: $ZONES_INDEX" + +ROOMS_FILES=$(find lib/world/zones/*/rooms/ -name '*.yaml' 2>/dev/null | wc -l) +echo "Комнаты: файлов: $ROOMS_FILES" + +MOBS_FILES=$(ls lib/world/mobs/*.yaml 2>/dev/null | grep -v index.yaml | wc -l) +MOBS_INDEX=$(grep -c '^[[:space:]]*- ' lib/world/mobs/index.yaml 2>/dev/null || echo 0) +echo "Мобы: файлов: $MOBS_FILES, в индексе: $MOBS_INDEX" + +OBJS_FILES=$(ls lib/world/objects/*.yaml 2>/dev/null | grep -v index.yaml | wc -l) +OBJS_INDEX=$(grep -c '^[[:space:]]*- ' lib/world/objects/index.yaml 2>/dev/null || echo 0) +echo "Объекты: файлов: $OBJS_FILES, в индексе: $OBJS_INDEX" + +TRIGS_FILES=$(ls lib/world/triggers/*.yaml 2>/dev/null | grep -v index.yaml | wc -l) +TRIGS_INDEX=$(grep -c '^[[:space:]]*- ' lib/world/triggers/index.yaml 2>/dev/null || echo 0) +echo "Триггеры: файлов: $TRIGS_FILES, в индексе: $TRIGS_INDEX" + +# Деактивировать виртуальное окружение +deactivate +``` + +**Ожидаемый результат:** +- Поддиректории: `zones/`, `mobs/`, `objects/`, `triggers/` созданы в `lib/world/` +- YAML файлы в KOI8-R кодировке + +--- + +### ЭТАП 3: Тестовый запуск + +```bash +# Выбрать порт (5555 для зеркала, 4000 для прода после остановки) +PORT=5555 # Для зеркала +# PORT=4000 # Для прода + +# Запуск с контрольными суммами (из build директории) +cd build_yaml +./circle -W -d ../lib $PORT > boot.log 2>&1 & +PID=$! + +# Подождать загрузки +sleep 30 + +# Проверить запуск +ps aux | grep $PID +netstat -tuln | grep $PORT + +# Остановить +kill -TERM $PID +sleep 5 + +cd $DIR +``` + +**Ожидаемый результат:** +- Сервер запустился +- Нет критических ошибок +- Контрольные суммы рассчитаны + +**Возможные ошибки:** +- `Cannot find world directory` → проверить `lib/misc/configuration.xml` +- `Failed to load zone` → проблема конверсии, проверить YAML + +--- + +### ЭТАП 4: Валидация + +```bash +# Запустить для интерактивной проверки +PORT=5555 # Для зеркала +# PORT=4000 # Для прода + +cd build_yaml +./circle -W -d ../lib $PORT > validation.log 2>&1 & +PID=$! +sleep 30 + +# Подключиться telnet (в другом терминале) +# telnet localhost $PORT +# Проверить: look, who, help + +# Остановить +kill -TERM $PID + +cd $DIR +``` + +**Ожидаемый результат:** +- Все зоны загружены +- Telnet работает +- Команды работают + +--- + +### ЭТАП 5: Развёртывание (только для прода) + +**ВАЖНО:** Выполнять только на продакшене после успешного тестирования на зеркале! + +```bash +# Убедиться, что в продакшене +cd /home/mud/mud +export DIR=$(pwd) + +# Убедиться, что сервер остановлен +ps aux | grep circle +# Если запущен: killall -TERM circle + +# Финальный бэкап перед развёртыванием +tar -czf backup/prod_before_deploy_$(date +%Y%m%d_%H%M%S).tar.gz lib + +# Переименовать старую lib/ (не удалять!) +mv lib lib.legacy_backup + +# Переименовать старый бинарник +mv circle circle.legacy_backup + +# Скопировать YAML мир +# Если был на зеркале - скопировать оттуда: +# rsync -av --delete /home/stribog/mud/lib/ ./lib/ +# Если конвертировали на продакшене - уже есть + +# Скопировать YAML бинарник +cp build_yaml/circle ./circle + +# Установить права +chmod +x circle + +# ПЕРВЫЙ ЗАПУСК +cd build_yaml +./circle -W -d ../lib 4000 > first_boot.log 2>&1 & +PROD_PID=$! + +# Мониторинг запуска (2 минуты) +for i in {1..24}; do + sleep 5 + echo "=== Через $((i*5)) секунд ===" + tail -3 first_boot.log + netstat -tuln | grep 4000 && echo "Порт открыт!" || echo "Порт еще закрыт" +done + +# Проверить успешность +tail -50 first_boot.log + +cd $DIR +``` + +**Ожидаемый результат:** +- Старая `lib/` в `lib.legacy_backup` +- YAML мир в `lib/world/` +- Сервер запустился + +**Если ошибки → см. ЭТАП 6 (Откат)** + +--- + +### ЭТАП 6: Откат (в случае проблем) + +**КРИТИЧНО:** Выполнять только если на ЭТАПЕ 5 критические проблемы! + +```bash +# Остановить YAML сервер +killall -TERM circle +sleep 5 +killall -KILL circle # Если не остановился + +# Восстановить старую lib/ +rm -rf lib +mv lib.legacy_backup lib + +# Восстановить старый бинарник +rm -f circle +mv circle.legacy_backup circle + +# Запустить legacy версию +./circle -d lib 4000 & + +# Проверить +sleep 10 +tail -50 misc/syslog +netstat -tuln | grep 4000 + +# Записать причину отката +echo "ROLLBACK: $(date) - Reason: [УКАЗАТЬ ПРИЧИНУ]" >> MIGRATION.log +``` + +**Критерии для отката:** +- Не загружается >50% зон +- Крах сервера при запуске +- Потеря данных игроков +- Несоответствие контрольных сумм >10% + +--- + +## Верификация успешной миграции + +### Чек-лист: +- [ ] Резервные копии созданы +- [ ] YAML бинарник собран с `-DHAVE_YAML=ON` +- [ ] Конверсия завершена без ошибок +- [ ] Тестовое зеркало запустилось и прошло валидацию +- [ ] Продакшен запустился на YAML +- [ ] Контрольные суммы совпадают (или близки к legacy) +- [ ] Игроки могут подключаться +- [ ] Нет критических ошибок в логах + +### Команда проверки состояния: + +Запускать из билд-директории (поддиректория репозитория, lib/ на уровень выше). + +```bash +cat << 'EOF' > check_yaml_status.sh +#!/bin/bash +echo "=== YAML Migration Status ===" +echo "Binary: $(ldd ./circle 2>&1 | grep -q yaml && echo 'YAML' || echo 'LEGACY')" +echo "World format: $([ -d ../lib/world/zones ] && echo 'YAML' || echo 'LEGACY')" +echo "Server running: $(pgrep -f circle >/dev/null && echo 'YES' || echo 'NO')" +echo "Port 4000: $(netstat -tuln | grep -q :4000 && echo 'OPEN' || echo 'CLOSED')" +echo "Recent errors: $(tail -100 ../lib/misc/syslog 2>/dev/null | grep -ci error)" +EOF +chmod +x check_yaml_status.sh +./check_yaml_status.sh +``` diff --git a/launch b/launch index 340fcbf71..fec9d9015 100755 --- a/launch +++ b/launch @@ -1 +1,5 @@ -./autorun & \ No newline at end of file +#!/bin/bash +DIR="$(dirname "$0")" +nohup "$DIR/autorun" "$@" > autorun.out 2>&1 & +echo $! > autorun.pid +echo "Started autorun with PID $(cat autorun.pid)" diff --git a/lib.template/misc/configuration.xml b/lib.template/misc/configuration.xml deleted file mode 100644 index 04c5d1038..000000000 --- a/lib.template/misc/configuration.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - syslog - LINE - APPEND - 022 - - - - NO - - - - FULL - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/lib.template/misc/craft/craft1.xml b/lib.template/misc/craft/craft1.xml deleted file mode 100644 index ee79325aa..000000000 --- a/lib.template/misc/craft/craft1.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - Craft Name - - - - - - - 1 - - - - - - - - - - - diff --git a/lib.template/misc/messages b/lib.template/misc/messages deleted file mode 100644 index df70e5461..000000000 --- a/lib.template/misc/messages +++ /dev/null @@ -1,1835 +0,0 @@ -* Note: all lines between records which start with '*' are comments and -* are ignored. Comments can only be between records, not within them. -* -= !!!Attention!!! =- -* Добавлен один готовый вариант нового умения "Огненный щит" -* + два неготовых варианта. -* (c) Filvar. v2.1. -* Death Message (damager, damagee, onlookers) -* Miss Message (damager, damagee, onlookers) -* Hit message (damager, damagee, onlookers) -* God message (damager, damagee, onlookers) -* -* Each message will automatically be wrapped to 79 columns when they are -* printed. '#' can be substituted for a message if none should be printed. -* Note however that, unlike the socials file, all twelve lines must be -* present for each record, regardless of any '#' fields which may be -* contained in it. - -*************************************************************************** -* Offensive Spells * -*************************************************************************** - -* Burning Hands -M - 5 -Ваши горящие руки - последнее, что увидел$G $N0 в своей жалкой жизни. -Вы полностью сгорели в пламени $n1. -$n слишком сильно поджарил$g $N3 - $E просто обуглил$U. -$N вывернул$U из вашего потока огня. -Вы успели увернуться от пламени $n1. -$N сумел$G избежать яркого пламени $n1. -Вы поджарили $N3, оставив несколько волдырей на $S нежной коже. -Вы заорали от боли, когда $n поджарил$g вас в потоке пламени. -$N заорал$G от боли, когда $n подпалил$g $S. -Вы попытались обжечь $N3, но только чуток погрели. -Это мелкое создание попыталось вас обжечь. -$n безуспешно попытал$u обжечь $N3. - -M - 5 -$N обуглил$U и умер$Q в потоке вашего огня. -$n своими руками приготовил$g из вас прекрасно прожаренный кусок мяса. -$N узнал$G насколько горячие у $n1 руки. Это было последним, что $E узнал$G. -$N выскользнул$G из вашего пламени. -Вы смогли избежать огня $n1. -$N смог$Q избежать огня $n1. -Вы обожгли $N3 своим пламенем. -$n немного подпалил$g вас потоком огня. -$n подпалил$g $N3 своими горящими руками. -Вы попытались обжечь $N3 и не смогли! -$n попытал$u обжечь вас, но у н$s ничего не вышло. -Все попытки $n1 обжечь $N3 пошли насмарку. - -* Call Lightning -M - 30 -Вызванная вами молния просто испепелила $N3. -Вы не смогли оправиться после попадания молнии, посланной в вас $n4. -После молнии, вызванной $n4, от $N1 остались одни угли. -Ваша молния попала в землю рядом с $N4 - мимо. -Молния $n1 прошла рядом с вами. А ведь $e мог$q вас и зацепить. -$N отпрыгнул$G назад от посланной в н$S $n4 молнии. -Вы вызвали молнию, использовав $N3 в качестве громоотвода. -Вас поразила молния, посланная $n4. Ох! -$N закричал$G от боли, когда в н$S попала молния, пущеная $n4. -$N остановил$G вашу молнию простым взмахом руки. -Вы остановили молнию, пущенную $n4, одним взмахом руки. -$N отмахнул$U от молнии $n1, как от назойливой мухи. - -* Chill Touch -M - 8 -Ваши ледяные пальцы доведут до смерти кого угодно. \u$N не был$G исключением. -Вам остается пожаловаться Богам на холодные руки $n1. -$n коснул$u $N1, превратив $S в сосульку. -$N сумел$G избежать вашего ледяного прикосновения. -Вы чудом увернулись от ледяного прикосновения $n1. -$n неудачно попытал$u заморозить $N3. -Вы несколько остудили боевой пыл $N1. -Господи! Какие холодные у $n1 руки! Вам стало просто не по себе. -$n прикоснул$u к $N2, котор$W теперь не тянет на "горячего парня". -Вы пытаетесь заморозить $N3? Неудачная шутка. -$n попытал$u заморозить вас. Смешнее не бывает... -$N рассмеял$U: "$n! Ну и холодные же у тебя руки!" - -M - 8 -$N посинел$G от вашего ледяного прикосновения. Жить $M осталось недолго. -$n приложил$g к вам свои ледяные пальцы. Лучше бы этого не произошло. -$N не выдержал$G ледяного прикосновения $n1 и умер$Q. -Ваше ледяное прикосновения не причинило $N2 никакого вреда. -От ледяного прикосновения $n1 у вас по коже пробежал лишь легкий холодок. -У $n1 ничего не получилось, когда $e попытал$u обморозить $N3. -$N вскрикнул$G от боли, когда вы прикоснулись к н$M. -$n направил$g свое ледяное прикосновение в вашу сторону. Не больно? -$n ударил$g своими ледяными руками $N3. -Вы пытаетесь заморозить $N3? Неудачная шутка. -$n попытал$u заморозить вас. Смешнее не бывает... -$N рассмеял$U: "$n! Ну и холодные же у тебя руки!" - -* Color Spray -M - 10 -В $N3 попала ледяная стрела. "Красивая дырочка" - подумали вы. -Перед вами возникла ледяная стрела, которая легко прошла сквозь вашу плоть. -$n выпустил$g в $N3 ледяную стрелу, которая попала $M в голову. -Ваша ледяная стрела пролетела мимо $N1. -$n неудачно попытал$u направить ледяную стрелу и она пролетела мимо. -Ледяная стрела $n1 пролетела рядом с $N4, чуть не попав. -Ваша ледяная стрела попала в $N3. Прямо в голову! -$n попал$g в вас своей ледяной стрелой. Больно! -$n направил$g ледяную стрелу на $N3, которая поразила свою цель! -Ваша ледяная стрела заставила $N3 грозно нахмуриться. -$n обдал$g вас холодом своей ледяной стрелы. Бедолага просто спятил$g. -$N посмеял$U над глуп$r $n4, котор$w попытал$u прострелить $S. - -M - 10 -Ваша ледяная стрела пробила тело $N1. Быстрая смерть. -Ледяная стрела, которую пустил$a $n, была для вас смертельна. -$N умер$Q, когда в н$S попала ледяная стрела. -Ледяная стрела почти попала в $N3. Какая досада! -Ледяная стрела, летящая от $n1, не попала в вас! -$n не попал$g своей ледяной стрелой в $N3. -Ледяная стрела поразила свою цель! Жаль $N3. -Ледяная стрела $n1 попала в вас! -$n пустил$g ледяную стрелу, которая попала прямо в $N3. -Ваша ледяная стрела заставила $N3 грозно нахмуриться. -$n обдал$g вас холодом своей ледяной стрелы. Бедолага просто спятил$g. -$N посмеял$U над глуп$r $n4, котор$w попытал$u прострелить $S. - -* Dispel Evil -M - 22 -Тьма покинула тело $N1, прихватив с собой и $S душу. -$n изгнал$g тьму, переполнявшую вас. А без этого вам жисть не в радость. -$n развеял$g по ветру $N3. -$N избежал$G действия вашего заклинания. -$n безуспешно попытал$u развеять вас по ветру. -$N улыбнул$U, глядя на безуспешные попытки $n1 изгнать $S. -$N теперь выглядит гораздо светлее. Вот только здоровье $S пошатнулось. -$n изгнал$g немного тьмы из вашей души. Это больно. -$n изгнал$g из $N1 часть темного духа. Но здоровья $N2 это не прибавило. -Вы и впрямь хотите изгнать $N3? -$n безуспешно пытал$u изгнать вас - наивн$w. -У $n1 просто съехала крыша - сказать ТАКОЕ $N2!!! - -M - 22 -Сгусток тьмы вырвался из тела $N1, от чего $E умер$Q. -Когда $n вырвал$g из вашей души темную сторону, вы медленно умерли. -Темный ураган, который вызвал$g $n, унес остаток жизни из $N1. -В душе у $N1 слишком много зла и вы не смогли его изгнать. -$n не смог$q изгнать из вас зло. Какой же вы злыдень! -$n попробовал$g изгнать зло из $N1, но у н$s ничего не вышло. -$N скривил$U от боли, когда вы изгнали из н$S часть зла. -Вы скривились от боли, когда $n изгнал$g из вашего тела часть зла. -$N пошатнул$U и закашлял$G, когда $n отхватил$g из $S души часть зла. -Вы и впрямь хотите изгнать $N3? -$n безуспешно пытал$u изгнать вас - наивн$w. -У $n1 просто съехала крыша - сказать ТАКОЕ $N2!!! - -* Earthquake -M - 23 -От ваших слов Земля разверзлась под $N4... Навсегда. -Земля, по просьбе $n1, открыла пред вами свои объятия. -$n2 удалось убедить Землю принять $N3. -$N2 удалось устоять на ногах после ваших пассов. -Вы с трудом удержались на ногах после пассов $n1. -$N2 удалось устоять на ногах после пассов $n1. -$N пошатнул$U, пытаясь устоять на ногах. -Вы почувствовали, как Земля уходит из-под ваших ног. -$N зашатал$U, пытаясь удержать равновесие. -$N плавно взмыл$G вверх. Ваши старания успеха не имели. -Вы плавно взлетели, посмеявшись над глупостью $n1. -$N воспарил$G, оставив $n3 с носом. - -M - 23 -Земля поглотила $N3, пусть спит с миром. -$n с помощью землетрясения отправил$g вас в мир иной. -$n вызвал$g страшное землетрясение. Из-за него $N умер$Q. -Землетрясение не причинило $N2 ни малейшего вреда. -$n вызвал$g землетрясение. Для вас все обошлось. -$N лишь вздрогнул$G после землетрясения $n1. -$N3 подбросило вверх и больно ударило об землю. -Земля вздрогнула под вашими ногами и вы больно ударились! -$n вызвал$g землетрясение, в результате которого $N был$G ранен$G. -$N плавно взмыл$G вверх. Ваши старания успеха не имели. -Вы плавно взлетели, посмеявшись над глупостью $n1. -$N воспарил$G, оставив $n3 с носом. - -* Energy Drain -M - 25 -Вы забрали остатки энергии у $N1. -$n забрал$g у вас остатки энергии. Прощайте. -$n забрал$g остатки энергии у $N1. -Вам надо потренироваться отнимать энергию у кого-нибудь послабее. -Вам повезло - $n не смог$q отнять у вас энергию. -$n попытал$u отнять энергию у $N1, но у н$s ничего не вышло. -Вы отобрали у $N1 немного энергии. -Вы почувствовали, как $n потихоньку отбирает у вас энергию. -$n отнял$g часть энергии у $N1. -$S энергии хватит, чтобы порвать вас, как грелку. -$n пытал$u отнять у вас часть энергии - зачем $m столько? -$n разочарованно взглянул$g на $N3. - -M - 25 -Вы истощили последние запасы жизни у $N1. -$n полностью истощил$g вашу жизненную энергию. -$n выпил$g весь жизненный сок у $N1. -Энергия у $N1 слишком сильна и вам не удалось с ней справиться. -$n оказал$u слишком слаб$g, чтобы отнять у вас энергию. -$n попробовал$g отнять энергию у $N1, но у н$s ничего не получилось. -Вы с удовольствием заметили, как $N теряет свою энергию. -$n медленно вытянул$g из вас кусочек жизни. -$n облизнул$u от вкуса жизненной энергии $N1. -$S энергии хватит, чтобы порвать вас, как грелку. -$n пытал$u отнять у вас часть энергии - зачем $m столько? -$n разочарованно взглянул$g на $N3. - -* Fireball -M - 26 -Ваш огненный шар испепелил $N3 дотла. -Огненный шар $n1 оставил от вас кучку пепла. -Жар огненного шара $n1 оставил от $N1 лишь обугленный труп. -Ваш огненный шар прошел мимо $N1. -Огненный шар $n1 опалил воздух рядом с вами. -Огненный шар $n1 не причинил $N2 никакого вреда. -Ваш огненный шар попал в $N3 и $S окружило пламя! -Вас окружило пламя огненного шара, выпущенного $n4! -$n удовлетворенно потер$q руки - $s огненный шар опалил $N3! -$N не горит в огне - $E из него выш$Y. -Вы расслабились, согреваемые огненным шаром $n1. -$N согрел$U в пламени огненного шара $n1. - -M - 26 -Огненный шар окутал и скрыл в себе $N3. -Огненный шар $n1 обхватил вас и вы сгорели дотла. -После огненного шара $n1 от $N1 не осталось и пепла. -Ваш огненный шар пролетел мимо $N1. -$n промахнул$u своим огненным шаром, когда пытал$u попасть в вас. -$n не смог$q попасть своим огненным шаром в $N3. -$N попал$U на пути вашего огненного шара. Сам$G захотел$G! -Огненный шар $n1 накрыл вас. -$n выпустил$g огненный шар, который сильно подогрел воздух вокруг $N1. -$N не горит в огне - $E из него выш$Y. -Вы расслабились, согреваемые огненным шаром $n1. -$N согрел$U в пламени огненного шара $n1. - -* Harm -M - 27 -$N назвал$G бы вас вредителем. Если бы смог$Q. -"\u$n! Вредн$w!!!" - с этими словами ваша душа покинула тело. -$N не пережил$G вреда, причиненного $M $n4. -Вы не смогли причинить вреда $N2. -$n попытал$u навредить вам - крайне неудачно. -$n неудачно попытал$u навредить $N2. -Ваши слова заставили $N3 содрогнуться от боли. -Слова, брошенные $n4, заставили ваше тело содрогнуться от боли. -Слова, брошенные $n4, заставили $N3 содрогнуться от боли. -Вредить Богам? Себе дороже выйдет. -$n пытал$u навредить вам. Тоже мне жучок-долгоносик. -$n пытал$u повредить $N3. А вот это $e напрасно. - -M - 27 -Ваш вред полностью уничтожил тело $N1. -Сильный вред, идущий от $n1, изорвал ваше тело в клочья. -Вред $n1 был смертелен для $N1. -Ваш вред прошел мимо $N1. -"Пронесло..." - подумали вы, когда $n не смог$q на вас сосредоточиться. -$n не смог$q причинить вред $N2. -Ваш вред дошел до $N1. Ну и больно же $M сейчас. -Вред от $n1 дошел до вас. Какое странное ощущение. -$N вздрогнул$G и искривил$U от боли, когда до н$S дошел вред $n1. -Вредить Богам? Себе дороже выйдет. -$n пытал$u навредить вам. Тоже мне жучок-долгоносик. -$n пытал$u повредить $N3. А вот это $e напрасно. - -* Lightning Bolt -M - 6 -Вы вызвали шаровую молнию, которая оставила от $N1 горстку пепла. -Шаровая молния $n1 лишила вас жизни. -Шаровая молния $n1 отправила $N3 в мир иной. -Вы не попали в $N3 своей шаровой молнией. -$n чуть не поджарил$g вас своей шаровой молнией. -$N увернул$U от шаровой молнии $n1. -Ваша шаровая молния заставила $N3 потрясти косточками. -В вас попала шаровая молния $n1. -В $N3 попала шаровая молния $n1. -Вы попытались пустить шаровую молнию в $N3, но тщетно. -Вы обломали все попытки $n1 пустить в вас шаровую молнию. -$N обломал$G все попытки $n1 пустить в н$S шаровую молнию. - -M - 6 -Ваша шаровая молния осветила $N3 и $S предсмертный лик. -В свете шаровой молнии $n1 вы четко разглядели темный силуэт с косой в руках. -Шаровой заряд $n1 ударил в $N3 и разметал $S останки по комнате. -Ваше умение целиться резко ухудшилось, и вы не попали в $N3. -$n плохо прицелил$u, и $s шаровая молния не попала в вас. -$n пытал$u попасть в $N3 шаровой молнией, но промазал$g. -Посланная вами шаровая молния ударила в $N3, заставив $S содрогнуться. -Шаровая молния, посланная $n4, ударила вам в лицо. -Посланная $n4 шаровая молния ударила в лицо $N1. -Посланная вами шаровая молния отразилась в глазах $N1 и погасла. -Посланная $n4 шаровая молния полностью растворилась в ваших глазах. -Посланная $n4 шаровая молния полностью растворилась во взгляде $N1. - -* Magic Missile -M - 32 -Ваша магическая стрела отняла остатки жизни у $N1. -Магическая стрела, пущенная $n4, стала последней в вашей жизни. -Магическая стрела, посланная $n4, погрузила $N3 в вечный сон. -Магическая стрела прошла немного выше $N1. -Магическая стрела, посланная $n4, пронеслась над вашей головой. -$n промахнул$u, пытаясь поразить $N3 магической стрелой. -Вы с радостью отметили, что магическая стрела поразила $N3. -Вы пошатнулись, задетые магической стрелой $n1. -$n выпустил$g в $N3 магическую стрелу, которая достигла цели. -Ваша магическая стрела попала в щит, созданный $N4. -Вы создали щит, отразивший магическую стрелу $n1. -$N создал$G щит, отразивший магическую стрелу $n1. - -M - 32 -Вы убили $N3 своей магической стрелой. Можете радоваться. -$n убил$g вас своей магической стрелой. -$n послал$g магическую стрелу, которая убила $N3. -Ваша магическая стрела не попала в $N3. -Ух! Магическая стрела $n1 чуть не попала в вас! -$n не смог$q попасть в $N3 своей магической стрелой. -Магическая стрела, пущенная вами, пронзила тело $N1. -Магическая стрела, пущенная $n4, пронзила вас. -Магическая стрела, пущенная $n4, пронзила $N3. -Ваша магическая стрела попала в щит, созданный $N4. -Вы создали щит, отразивший магическую стрелу $n1. -$N создал$G щит, отразивший магическую стрелу $n1. - -* Poison -M - 33 -# -Яд жжет ваши вены. Вы задрожали... и погибли. -$N забил$U в судорогах, страдая, вытянул$U и затих$Q. -# -Ваше счастье - вы чуть не отравились. -$N2 просто повезло - $E чуть не отравил$U. -# -Вы чувствуете жгучий яд в крови и страдаете. -$N закашлял$U и забил$U в судорогах. -# -Вы каким-то образом отравились. -$N каким-то образом отравил$U. - -M - 33 -# -Вы погибли из-за смертельного действия яда. -$N упал$G на колени, захрипел$G и погиб$Q. -# -Вам повезло, яд не смог прижиться в вашем организме. -Воздействие яда обошло $N3. -# -Яд бежит в вашем организме, какое мучение! -$N прохрипел$G что-то и схватил$U за горло. -# -Вы отравились. Скорее к лекарю! -$N отравил$U! Лекаря сюда! - -* Shocking Grasp -M - 37 -Ваша хватка оказалась для $N1 роковой. -$n схватил$g вас так крепко, что вы испустили дух. -$N упал$G замертво, не в силах пережить шокирующей хватки $n1. -$N увернул$U от ваших шаловливых ручек. -Вы увернулись, когда $n пытал$u схватить вас. -$N увернул$U, и $n не сумел$g схватить $S. -Вы крепко ухватили $N3, сломав $M несколько костей. -$n крепко схватил$g вас, ломая кости. -$N застонал$G, когда $n сломал$g $M несколько костей. -Вам нужно долго учиться, чтобы схватить $N3. -С тем же успехом $n мог$q бы хватать и воздух. -Попытка $n1 схватить $N3 провалилась. - -M - 37 -Вы схватили $N3 своей шокирующей хваткой и задушили. -$n схватил$g вас своей хваткой и сломал$g вам шею. -$n так сильно схватил$g $N3, что $E умер$Q. -$N избежал$G вашей шокирующей хватки. -$n не смог$q обхватить вас своей шокирующей хваткой. Учиться еще и учиться. -$N2 была нипочем шокирующая хватка $n1. -Схватив $N3, вы сильно сдавили $S тело. -$n обхватил$g вас и чуть не раздавил$g! -Послышался сильный хруст, когда $n сдавил$g тело $N1. -Вам нужно долго учиться, чтобы схватить $N3. -С тем же успехом $n мог$q бы хватать и воздух. -Попытка $n1 схватить $N3 провалилась. - -* Dispel Good -M - 46 -Вы изгнали все добро из души $N1, заставив $S умереть. -$n затоптал$g последние искры света в вашей изможденной душе. -$n затоптал$g последние искры света в душе $N1. -$N избежал$G действия вашего заклинания. -$n безуспешно попытал$u развеять вас по ветру. -$N улыбнул$U, наблюдая безуспешные попытки $n1 развеять $S. -$N зашатал$U и потемнел$G от ваших слов. -Слова $n1 притушили добрые порывы, исходящие из вашего сердца. -$N зашатал$U и потемнел$G от слов $n1. -Вы действительно надеетесь $S изгнать? -$n безуспешно пытал$u изгнать вас. -$n напрасно пытал$u изгнать свет, струящийся от $N1. - -M - 46 -Сгусток света вырвался из тела $N1, от чего $E умер$Q. -Когда $n вырвал$g из вашей души светлую сторону, вы медленно умерли. -Светлый ураган, который вызвал$g $n, унес остаток жизни из $N1. -В душе у $N1 слишком много добра и вы не смогли его изгнать. -$n не смог$q изгнать из вас добро. Какой же вы белый и пушистый! -$n попробовал$g изгнать добро из $N1, но у н$s ничего не вышло. -$N скривил$U от боли, когда вы изгнали из н$S часть добра. -Вы скривились от боли, когда $n изгнал$g из вашего тела часть добра. -$N пошатнул$U и закашлял$G, когда $n отхватил$g из $S души часть добра. -Вы и впрямь хотите изгнать $N3? -$n безуспешно пытал$u изгнать вас - наивн$w. -У $n1 просто съехала крыша - сказать ТАКОЕ $N2!!! - -* Chain Lightning -M - 66 -Одна из ваших молний не оставила и следа от $N1. -Из цепи молний $n1 вырвался луч и ударил в вас. Жалко... -Одна из многочисленных молний, вызванных $n4, оставила от $N1 одни угли. -$N смог$G избежать удара ваших молний! -$n0 вызвал$g поток молний. А ведь мог$q вас и зацепить. -$N отпрыгнул$G назад от посланного в н$S $n4 разряда молнии. -Подняв высоко руки вы вызвали серию молний, одна из которых ударила в $N3. -Вас поразил разряд из молний, посланных $n4. Ох! -$N закричал$G от боли, когда в н$S попала молния, пущенная $n4. -$N остановил$G вашу молнию простым взмахом руки. -Вы остановили молнию, пущенную $n4, одним взмахом руки. -$N отмахнул$U от молнии $n1, как от назойливой мухи. - -* Fireblast -M - 67 -$N попал$G в поток огня и сгорел$G заживо. Теперь $E расскажет об этом Богам. -"Что-то слишком жарко" - это последняя мысль, которая вас посетила. -$n превратил$g $N3 в горящую статую. -Ваш поток огня прошел мимо $N1. -$n неудачно попытал$u направить огненный поток и он пролетел мимо. -Огненный поток $n1 пролетел рядом с $N4. -Ваш огненный поток попал на $N3. Красиво как! -Огненный поток, посланный $n4, ударил в вас! -$n направил$g огонь на $N3, котор$W просто зарыдал$G от его вида. -Ваш огненный поток заставил $N3 грозно нахмуриться. -$n обдал$g вас огнем. Бедолага просто спятил$g. -$N посмеял$U над глуп$r $n4, котор$w попытал$u спалить $S. - -* Implosion -M - 68 -Ваш гнев обратил $N3 в пар. -Гнев, исходящий от $n1, был силен настолько, что обратил вас в пар. -Излучаемый $n4 гнев оставил от $N1 лишь облачко пара. -Волна вашего гнева прошла мимо $N1. -Вам удалось укрыться от гневного взгляда $n1. -Волна гнева $n1 прошла мимо $N1. -Вы разгневались на $N3 так сильно, что он$G закипел$G. -$n опалил$g вас своим гневным взглядом. -$n разгневанно взглянул$g на $N3, $S окружило облако пара. -Не стоит гневаться на $N3 - $S гнев страшнее. -$n разгневан$a вами. А вам все побоку. -$n попатыл$u гневаться на $N3. Интересно, заметил$G ли $E это? - -* Acid blast -M - 72 -$N, покрыт$W кислотой, обратил$U в однородную массу и умер$Q. -Кислота, которой плеснул$g $n, обратила вас в однородную массу. -$n обдал$g кислотой $N3, обратив $S в однородную массу. -Вы плеснули кислотой в $N3, но в н$S не попало ни капли. -$n плеснул$g в вас кислотой, но у н$s большие проблемы с меткостью. -$n не попал$g своей кислотой в $N3. -Кислота покрыла $N3 с головы до пят. -Кислота, которой плеснул$g $n, покрыла вас с головы до пят. -$n плеснул$g кислотой в $N3, обдав $S с головы до пят. -Плевал$G $E на вашу кислоту. -$n попытал$u подкислить вам жизнь. -$n попытал$u плеснуть кислотой в $N3. - -* Sacrifice -M - 76 -Вы выпили остатки жизненных сил у $N1. -$n выпил$g у вас остатки жизненных соков. Прощайте. -$n выпил$g остатки жизненных соков у $N1. -Вам надо потренироваться пить жизненные соки у кого-нибудь послабее. -Вам повезло - $n не смог$q выпить ваши жизненные соки. -$n попытал$u выпить жизненные соки у $N1, но у н$s ничего не вышло. -Вы отхлебнули из чаши жизни $N1. -Вы почувствовали, как ваши жизненные соки быстро перетекают в $n3. -$n отхлебнул$g часть жизненных соков у $N1. -$S соки могут оказаться ядовитыми для вас. -$n пытал$u попробовать ваших жизненных соков - не отравил$u бы. -$n разочарованно взглянул$g на $N3. - -M - 76 -Вы истощили последние запасы жизни $N1. -$n полностью истощил$g вашу жизненную энергию. -$n выпил$g весь жизненный сок у $N1. -Энергия у $N1 слишком сильна и вам не удалось с ней справиться. -$n оказал$u слишком слаб$g, чтобы отнять у вас немного жизни. -$n попробовал$g отнять жизнь у $N1, но у н$s ничего не получилось. -Вы с удовольствием заметили, как $N теряет свою жизнь. -$n медленно вытянул$g из вас кусочек жизни. -$n облизнул$u от вкуса сока жизни $N1. -$S жизни хватит, чтобы порвать вас, как грелку. -$n пытал$u отнять у вас часть жизни - зачем $m столько? -$n разочарованно взглянул$g на $N3. - -* Cause Light -M - 90 -$N почернел$G до корней волос и медленно умер$Q. -Вред, нанесенный $n4, заставил вас забиться в судорогах и умереть. -$N не вынес$Q вреда, нанесенного $M $n4. -Вы не смогли причинить вред $N2 своим заклинанием. -$n не смог$q причинить вам вред. -$n не смог$q причинить $N2 вред. -Вы легко повредили $N3. -$n легко повредил$g вас. -$n легко повредил$g $N3. -Вы не можете нанести вред $N2. -$n попытал$u нанести вам вред своим заклинанием. -$n безуспешно попытал$u нанести вред $N2. - -* Cause Serious -M - 91 -$N почернел$G до корней волос и медленно умер$Q. -Вред, нанесенный $n4, заставил вас забиться в судорогах и умереть. -$N не вынес$Q вреда, нанесенного $M $n4. -Вы не смогли причинить вред $N2 своим заклинанием. -$n не смог$q причинить вам вред. -$n не смог$q причинить $N2 вред. -Вы серьезно повредили $N3. -$n серьезно повредил$g вас. -$n серьезно повредил$g $N3. -Вы не можете нанести вред $N2. -$n попытал$u нанести вам вред своим заклинанием. -$n безуспешно попытал$u нанести вред $N2. - -* Cause Critic -M - 92 -$N почернел$G до корней волос и медленно умер$Q. -Вред, нанесенный $n4, заставил вас забиться в судорогах и умереть. -$N не вынес$Q вреда, нанесенного $M $n4. -Вы не смогли причинить вред $N2 своим заклинанием. -$n не смог$q причинить вам вред. -$n не смог$q причинить $N2 вред. -Вы тяжело повредили $N3. -$n тяжело повредил$g вас. -$n тяжело повредил$g $N3. -Вы не можете нанести вред $N2. -$n попытал$u нанести вам вред своим заклинанием. -$n безуспешно попытал$u нанести вред $N2. - -* Armageddon -M - 94 -Вы отправили $N3 на $S судный день. -$n отправил$g вас на суд к вашим Богам. -$n отправил$g $N3 на судилище к Богам. -Вам не удалось устроить Судный День для $N1. -Вы отмазались от обвинений $n1. -Плохой из $n1 каратель, для $N1 $e скорее спаситель. -Ваш обвиняющий перст заставил $N3 схватиться за сердце. -$n указал$g на вас обвиняющим жестом. Вы содрогнулись. -$n указал$g на $N3 обвиняющим жестом, заставив $S содрогнуться. -Кто вы такой, чтобы обвинять $N3?! -$n попытал$u обвинить вас. Интересно, в чем? -$n попытал$u обвинить $N3 - тоже мне апостол Петр! - -* Stunning -M - 98 -Ваше каменное проклятие оглушило $N3, после чего $E умер$Q. -$n проклял$g вас своим каменным проклятием. Вы умираете. -$n превратил$g $N3 в камень. Пусть земля будет $M пухом. -Вы не смогли наложить каменное проклятие. -$n не смог$q наложить на вас каменное проклятие! -$n старал$u, но не смог$q наложить каменное проклятие на $N3. -Вы наложили каменное проклятие на $N3. Он$G страдает. -$n проклял$g вас. Вам режет уши. -$N2 стало плохо после того, как $n проклял$g $S. -Вы попытались проклясть $N3, но у вас ничего не вышло. -$n даже не смог$q правильно произнести заклинание. -$n попытал$u наложить проклятие на $N3, но у н$s ничего не получилось. - -* Cone of cold -M - 104 -$N превратил$U в кусок льда и разлетел$U на мелкие осколки. -$n заморозил$g вас и раздробил$g на миллионы осколков. -$n заморозил$g $N3 и раздробил$g $S на крохотные осколки. -Порыв ледяного ветра, вызванный вами, пронесся мимо $N1. -Порыв ледяного ветра, вызванный $n4, пронесся мимо вас. -$n не попал$g своим заклинанием в $N3. -Порыв ледяного ветра окутал $N3, обмораживая $S. -Порыв ледяного ветра, вызванный $n4, окутал вас. -$n призвал$g ледяной ветер, который обморозил $N3. -Стихии не причинят $N2 ни малейшего вреда. -$n попытал$u угрожать вам холодом. Вам, повелителю стихий. -$n попытал$u обморозить $N3, но ничего не вышло. - -* Огненный щит -M - 124 -Энергия вашего огненного щита выбила из $N1 остатки жизни. -Энергия огненного щита $n1 выбила из вас остатки жизни. -Энергия огненного щита $n1 выбила из $N1 остатки жизни. -Энергия вашего огненного щита не повредила $N3. -Огненный щит $n1 чудом не задел вас. -Огненный щит $n1 чудом не зацепил $N3. -Ваш огненный щит отразил часть удара $N1 в н$S же. -Огненный щит $n1 отразил часть вашего удара в вас же. -Огненный щит $n1 отразил часть удара $N1 в н$S же. -Вы забыли, для чего создали огненный щит, увидев $N3. -Под вашим строгим взглядом $n забыл$g про свой огненный щит. -$n забыл$g навыки владения огненным щитом при одном виде $N1. - -* Огненный щит -M - 124 -Огненный щит спас вам жизнь, чего не скажешь о $N5. -Огненный щит спас $n2 жизнь, чего не скажешь о вас. -Огненный щит спас $n2 жизнь, чего не скажешь о $N5. -$N увернул$U от вашего огненного щита. -Вы успели увернуться от огненного щита $n1. -$N успел$G увернуться от огненного щита $n1. -Ваш огненный щит отразил часть удара $N1 в н$S же. -Огненный щит $n1 отразил часть вашего удара в вас же. -Огненный щит $n1 отразил часть удара $N1 в н$S же. -Вы забыли, для чего создали огненный щит, увидев $N3. -Под вашим строгим взглядом $n забыл$g про свой огненный щит. -$n забыл$g навыки владения огненным щитом при одном виде $N1. - -* Shine Flash -M - 130 -Вы послали яркий блик, который прожег $N3 насквозь. -Яркий блик $n1 прожег вас до костей. -Посланый $n4 яркий блик сжег $N3 дотла. -Вы промазали мимо $N1 ярким бликом. -$n чуть не подпалил$g вас ярким бликом. -$N увернул$U от яркого блика $n1. -Ваш яркий блик обжег $N3. -Вас обжег пущеный $n4 яркий блик. -$N3 обжег яркий блик $n1. -Ваш яркий блик безвредно скользнул по $N2. -Вы от души позабавились над попытками $n1 пустить в вас яркий блик. -$N посмеял$U над попыткой $n1 пустить в н$S яркий блик. - -M - 130 -Ваш яркий блик высветил для $N1 путь в загробный мир. -Свет яркого блика $n1 отправил вас в царство теней. -Исходящий от $n1 свет просто разметал $N3. -Вы не попали в $N3, надо было лучше целиться. -$n промазал$g мимо вас своим заклинанием. -Яркий блик $n1 пролетел мимо $N1. -Ваш яркий блик заставил $N3 завопить от боли. -Пущенный $n4 яркий блик ударил вам по глазам. -Посланный $n4 яркий блик ударил в лицо $N1. -Посланный вами яркий блик отразился от $N1 и погас. -Посланный $n4 яркий блик дезвредно отразился от вашей кожи. -Посланный $n4 яркий блик полностью отразился от $N1. - -* Vacuum sphere -M - 134 -Ваш круг пустоты покрыл $N3, котор$W так и не смог$Q из него выбраться. -$n наложил$g на вас круг пустоты. В глазах стало темно... -$n обволок$q $N3 своим кругом пустоты, от чего $N повалил$U на землю. -$N смог$Q избежать вашего круга пустоты! -Вы смогли избежать воздействия круга пустоты, который пустил$a в вас $n. -$n не смог$q причинить вред $N2 своим кругом пустоты. -Круг пустоты накрыл $N3. Страшно смотреть на $S мучения! -Круг пустоты, который вызвал$a $n, накрыл вас. В глазах темнеет... -$n создал$a круг пустоты и наслал$a его на $N3! -Попытаться удушить $N3? Щаз... -$n попытал$u удушить вас своим кругом пустоты. Далеко не уйдет. -$n попытал$u удушить $N3. Не мог$q придумать ничего лучше? - -M - 134 -$N задохнул$U, когда не смог$Q выбраться из вашего круга пустоты. -Круг пустоты $n1 накрыл вас. Легкие расширяются, глаза закрываются... -$N умер$Q, так и не выбравшись из круга пустоты $n1. -Ваша попытка наложить круг пустоты на $N3 провалилась. -$n не смог$q наложить на вас круг пустоты. -$N сумел$G пережить круг пустоты $n1. -Ваш круг пустоты обволок $N3. -$n наслал$g на вас круг пустоты, который медленно окутал вас. -$n наслал$g на $N3 круг пустоты, который накрыл $S с головой. -Попытаться удушить $N3? Щаз... -$n попытал$u удушить вас своим кругом пустоты. Далеко не уйдет. -$n попытал$u удушить $N3. Не мог$q придумать ничего лучше? - -* Meteor storm -M - 135 -Вызванные вами метеориты расплющили $N3, образовав кровавое месиво. -Вызванные $n4 метеориты расплющили вас, образовав кровавое месиво. -Вызванные $n4 метеориты расплющили $N3, образовав кровавое месиво. -$N2 ловко увернул$U от падающих метеоритов, вызванных вами. -Вы ловко увернулись от падающих метеоритов, вызванных $n4. -$N1 ловко увернул$U от падающих метеоритов, которые вызвал$g $n1. -Один из вызванных вами метеоритов попал прямо в $N3! -Один из вызванных $n4 метеоритов попал прямо в вас! -Один из вызванных $n4 метеоритов попал прямо в $N3! -$N отбил$G кулаками все ваши метеориты. Зря вы это затеяли... -Вы отбили кулаками все метеориты, вызванные $n4. -$N отбил$G кулаками все метеориты вызванные $n4. - -* Shock -M - 143 -Вы насмерть шокировали $N3. -$n убил$g вас своим шокирующим заклятием. -Заклинание $n1 просто разметало $N3 на кусочки. -$N ускользнул$G от вашего заклинания! -Вы смогли избежать шокирующего воздействия заклинания $n1. -$n не сумел$a шокировать $N3 - только зря старал$u. -Вы весьма шокировали $N3 своим заклинанием! -Вызванная $n4 вспышка света взорвалась в вашем черепе с оглушительным грохотом. Больно! -$n шокировал$g $N3, $N выглядит потрясенно. -Попытаться шокировать $N3? Вам жить надоело? -$n попытал$u шокировать вас. Как забавно... -$n попытал$u шокировать $N3. Кажется, $e напросил$u на неприятности. - -M - 143 -$N скорчил$U и умер$Q от вашего шокирующего заклинания. -Шокирующее заклятье $n1 ударило прямо в вас! Вам пришлось умереть. -$N скрючил$U и умер$Q от шокирующего заклинания $n1. -Вы не смогли шокировать $N3. -$n не сумел$g причинить вам вреда своим шокирующим заклинанием. -$N сумел$G избежать шокирующего заклинания $n1. -Удар вашего заклятия пришелся прямо в $N3. -Шокирующее заклинание $n1 ударило в вас кузнечным молотом. -$n тяжело повредил$g $N3 своим шокирующим заклинанием. -Шокировать $N3? Эх, молодо-зелено... -$n попытал$u шокировать вас своим заклинанием. Вот смешно. -$n попытал$u шокировать $N3. Лучше отойти в сторонку - $N может и обидеться. - -* Магическое зеркало (копи-паст с огнещита) -M - 144 -Магическое зеркало спасло вам жизнь, чего не скажешь о $N5. -Магическое зеркало спасло $n2 жизнь, чего не скажешь о вас. -Магическое зеркало спасло $n2 жизнь, чего не скажешь о $N5. -$N увернул$U от вашего магического зеркала. -Вы успели увернуться от магического зеркала $n1. -$N успел$G увернуться от магического зеркала $n1. -Ваше магическое зеркало отразило часть удара $N1 в н$S же. -Магическое зеркало $n1 отразило часть вашего удара в вас же. -Магическое зеркало $n1 отразило часть удара $N1 в н$S же. -Вы забыли, для чего создали магическое зеркало, увидев $N3. -Под вашим строгим взглядом $n забыл$g про свое магическое зеркало. -$n забыл$g навыки владения магическим зеркалом при одном виде $N1. - -* Scream -M - 173 -$N умер$Q от тоски, навеяной вашим криком. -Заунывный вопль $n1 лишил вас воли к жизни... Вы умерли от тоски. -$N съёжил$U и умер$Q от жуткого вопля $n1. -Ваш кошмарный вопль никак не повредил $N3. -Кошмарный вопль $n1 не повредил вам. -$n не сумел$a испугать $N3 своим кошмарным воплем. -Ваш вопль серьезно повредил $N3! -Кошмарный вопль $n1 заставил вас содрогнуться от боли. -Кошмарный вопль $n1 заставил $N3 содрогнуться от боли. -Испугать криком $N3? Смотрите, голос не сорвите... -$n громко заорал$g, пытаясь испугать вас. Как забавно... -$n попытал$u напугать $N3 своим громким криком. Чокнут$w... - -M - 173 -Ваш жуткий вопль просто разметал $N3 в клочья. -Жуткий вопль $n1 погасил вашу жизнь, как порыв ветра свечу. -Жуткий вопль $n1 просто разметал $N3 в клочья. -$N не обратил$G внимания на ваш крик. -Вопль $n1 не причиил вам вреда. -$N и внимания не обратил$G на крик $n1. -Ваш кошмарный вопль заставил $N3 содрогнуться от боли. -Жуткий вопль $n1 пронзил вас волной острой боли. -$N взвыл$G от боли, вызванной жутким криком $n1. -Вы истерично закричали на $N3. Но $M на это наплевать. -$n попытал$u напугать вас своим криком. Забавн$w... -$n попытал$u напугать $N3 своим криком. А что, если $E ответит? - -* Whirlwind -M - 210 -$N умер$Q, разорванн$w в кровавые клочья вашим вихрем. -Призванный $n4 вихрь разметал вас в клочья. -$N разлетел$U на кровавые ошметки в призванном $n4 вихре. -Вы не смогли повредить $N2 своим вихрем. -Призванный $n4 вихрь прошел мимо вас. -$n не сумел$a накрыть $N3 своим вихрем. -Ваш вихрь нанес тяжелые раны $N2! -Призванный $n4 вихрь нанес вам серьезные раны! -Призванный $n4 вихрь нанес $N2 серьезные раны! -Разорвать $N3? А если он вас? -$n надул$g щеки и попытал$u запустить в вас вихрем. -$n покраснел$g от натуги, пытаясь накрыть $N3 вихрем. Как бы не вышло конфуза... - -M - 210 -Призванный вами вихрь с хрустом сдавил $N3, превратитв $S в изломанную куклу. -Созданный $n4 вихрь смял вас как яичную скорпулу, выдавив прочь душу. -Страшный вихрь $n1 просто раздавил $N3 как лягушку. -$N увернул$U от вашего вихря -Вихрь $n1 не причиил вам вреда. -$N увернул$U от созданного $n4 вихря. -Призванный вами вихрь нанес тяжелые раны $N2. -Призванный $n4 вихрь порвал нежную кожу $N1 в нескольких местах. -$N завыл$G от боли, пытаясь вырваться из объятий призванного $n4 вихря. -Вы едва не лопнули от натуги, пытаясь наслать вихрь на $N3. -$n попытал$u наслать на вас вихрь. С глузду сдурел$g! -$n покраснел$g от натуги, пытаясь наслать на $N3 вихрь. Не вышло бы конфуза... - -* Indrik's teeth -M - 211 -Выступившие из земли каменные зубы индрик-зверя перекусили $N3 пополам. -Призванный $n4 индрик-зверь разорвал вас в клочья каменными зубами. -Из земли выступили каменные зубы индрик-зверы и громко клацнули. От $N1 остались лишь розовые пузыри. -Индрик-зверь громко лязгнул зубами, не задев $N3. -Призванный $n4 индрик-зверь не сумел ухватить вас зубами. -$n не сумел$a убедить индрик-зверя покуситься на $N3. -Индрик-зверь откусил хороший кусок от $N1! -Выросшие из земли каменные зубы отхватили кусок от вашей ноги! -Выросшие из земли каменные зубы откусили большой кусок от $N1! -$N1 почесал призванного вами индрик-зверя за ухом. -$n призвал$g индрик-зверя. Какой он ми-илый! -$n грозно вращая глазами призвал$g индрик-зверя. $N1 почесал зверушку за ухом. - -M - 211 -Выросшие из земли каменные зубы индрик-зверя перекусили $N3 пополам. -Призванный $n4 индрик-зверь раскусил вас. Прощайте... -Чудовищные каменные челюсти индрик-зверя сдавили $N3 - только брызги полетели. -$N избежал$G укуса индрик-зверя. -Каменные зубы индрик-зверя лязгнули, не достав до вас. -$N увернул$U от лязгающих зубов индрика. -Каменные зубы индрик-зверя рванули $N3 за бок. -Выросшие из земли зубы индрик-зверя отхватили кусок от $N3. -$N завопил$G, вырываясь из каменной хватки индрик-зверя. -$N1 почесал призванного вами индрик-зверя за ухом. -$n призвал$g индрик-зверя. Какой он ми-илый! -$n грозно вращая глазами призвал$g индрик-зверя. $N1 почесал зверушку за ухом. - -* Melf's acid arrow -M - 212 -Кислотная стрела превратила $N3 в лужицу слизи. -Пущенная $n4 кислотная стрела полностью растворила вас. -После кислотной стрелы $n1 от $N1 осталась бесформенная масса. -Кислотная стрела пролетела мимо $N3. -$n не сумел поразить вас кислотной стрелой. -$n не попал$g своей кислотной стрелой в $N3 -Кислотная стрела нанесла тяжелые раны $N2! -Кислотная стрела $n1 растворила несколько особо нежных кусков вашей кожи! -Пущенная $n4 стрела заставила $N3 покрыться сочащимися язвами. -$N1 не обратил никакого внимания на кислотную стрелу $n1. -$n попытался поранить вас кислотной стрелой, но не докинул даже до шиколотки. -$n запустил в $N3 кислотной стрелой. Лучше отойти подальше, $n явно нарвался. - -M - 212 -Кислотная стрела превратила $N3 в лужицу слизи. -Вы быстро растворяетесь после удара кислотной стрелы $n1. Какое строенное ощущение. -Кислотная стрела $n1 накрыла $N3. От $N1 осталась лишь лужица слизи. -$N избежал$G попадания кислотной стрелы $n1. -Кислотная стрела $n1 пронеслась мимо. -$N увернул$U от кислотной стрелы $n1. -Кислотная стрела угодила в $N3 и $E покрыл$U язвами. -Кислотная стрела нанесла жестокие раны $N2. -$N взвыл$G, получив в бок кислотную стрелу от $n1. -Кислотная стрела растворилась, не долетев до $N1. -$n попытал$u запустить в вас кислотной стрелой. Тоже мне, алхимик-самоучка. -Кислотная стрела $n1 растаяла, не долетев до $N1. Кажется, у $n1 большие проблемы... - -* Thunderstone -M - 213 -Громовой камень пробил $N3 насквозь. -Громовой камень $n1 проделал в вас большую дыру, в которую вылетела душа. -Громовой камень $n1 выбил душу из $N1. -Громовой камень ухнул в землю, не задев $N3. -$n не попал в вас громовым камнем. -$n не попал$g громовым камнем в $N3 -$N получил$G тяжелые раны от упавшего на н$S громового камня! -Громовой камень $n1 угодил вам прямо по маковке! -Обрушенный $n4 громовой камень тяжело ранил $N3. -$N лениво поймал$G в воздухе громовой камень $n1 и подбросил$G на ладони. -$n метнул в вас громовой камень. Второе ведерко медовиухи явно было лишним для $s. -$n попытал$u обрушить на $N3 громовой камень, но чуть не попал$G себе по ноге. - -M - 213 -Громовой камень вбил $N3 в сыру землю... полностью. -Громовой камень $n1 разбил вашу жизнь на осколки. -Громовой камень $n1 вбил $N3 в сыру землю... полностью. -Громовой камень не задел $N3. -Громовой камень $n1 ухнул в землю в сажени от вас. -$N не пострадал$G от удара громового камня. -Громовой камень с хрустом сломал несколько костей $N1. -Громовой камень с громким хрустом сломал несколько костей $N2. -$N получил$G громовым камнем прямо по маковке. Кажется, это больно. -Громовой камень превратился в облачко светящейся пыли над головой $N1. -$n попытал$u метнуть в вас громовой камень. Как бы не надорвался. -$n попытался запустить громовым камнем в $N3, но чуть не вывихнул$g себе руки. - -* Clod -M - 214 -$N умер$Q, раздавленн$W в лепешку вашей глыбой камня. -Обрушенная на вас глыба камня оказалась слишком тяжела. Покойтесь с миром. -После глыбы камня $n1 от $N1 осталось только мокрое и слегка розоватое место. -Ваша глыба камня не попала в $N3. -Глыба камня, которой в вас запустил $n1, с грохотом рухнула где-то вдалеке. -$n не попал в $N3 своей глыбой камня. А ведь как старал$u. -Ваша глыба камня тяжело ранила $N3! -Брошенная в вас огромная глыба камня - это серьезно. И больно. -Пущенная $n4 в полет скала глубоко вколотила $N3 в землю. -Метнуть глыбу камня в $N3? А ведь у него это получится намного лучше... -$n покраснел$a от натуги, пытаясь поднять и метнуть в вас скалу. -$n покраснел$a от натуги, пытаясь поднять и метнуть в $N3 скалу. - -M - 214 -От $N1 осталались лишь торчащие из-под обрушившейся скалы ошметки. -Брошенная $n4 скала обрушилась на вас. И пришла темнота. -Брошенная $n4 скала раздавила $N3 в лепешку. -$N избежал$G удара вашей глыбы камня. -Глыба камня $n1 со свистом пронеслась мимо вас. -$N ловко увернул$U от пролетевшей глыбы камня. -Удар вашей глыбы камня серьезно повредил $N3. -$n уронил$a на вас скалу. Похоже, $s на мелочи не разменивается -$n уронил$a на $N3 скалу. Похоже, $n на мелочи не разменивается -Вы попытались запустить в $n3 глыбу камня. Тщетно. -$n попытал$u метнуть в вас глыбу камня. Не лопнул бы от натуги... -$n сильно покраснел$g и вытаращил$g глаза, уставившись на $N3. Что вообще происходит? - -*************************************************************************** -* Offensive Skills * -*************************************************************************** -* Throw -M - 530 -Ваш меткий бросок заставил $N3 покинуть сей бренный мир. -$n швырнул$g в вас свое смертоносное оружие - вам хватило... -Меткий бросок $n1 заставил $N3 покинуть сей бренный мир. -Вы попытались метнуть оружие в $N3, но промахнулись. -Вы усмехнулись, когда $n не попал$g в вас. -$n промахнул$u, пытаясь бросить свое оружие в $N3. -Ваш меткий бросок надолго запомнится $N2. -Меткий бросок $n1 достиг цели - это достаточно больно. -Меткий бросок $n1 заставил $N3 застонать от боли. -Ваше оружие послушно легло к ногам $N1. -$n швырнул$g в вас свое оружие, но промахнул$u. -Бросок $n1 вызвал лишь легкую улыбку на губах $N1. - -*HolyStrike -M - 154 -Туман, поглотивший $N3, полностью растворил $S. -Последнее, что вы почувствовали - как поглотивший вас туман растворяет ваше тело... -Туман, поглотивший $N3, полностью растворил $S. -# -# -# -$N завопил$G от боли, оказавшись в тумане. -Туман, поглотивший вас, причиняет невыносимые мучения. -$N завопил$G от боли, оказавшись в тумане. -# -# -# - - -* Backstab -M - 531 -Вы нанизали $N3 на $o3. Теперь $E просто кусок мяса. -Внезапно $n уколол$g вас в спину. Но отомстите вы $m попозже. -$n нанизал$g $N3 на свое оружие. \u$N теперь словно поросёнок на вертеле. -Вы не смогли заколоть $N3. Мало практиковались? -$n попытал$u нанести вам удар в спину, но вы заметили $s. -$n попытал$u нанести $N2 удар в спину, но $s заметили. -Вы воткнули $o3 в спину $N1, котор$W застонал$G от боли. -Внезапно $n уколол$g вас в спину. -$n воткнул$g свое оружие в спину $N1. Ща начнется убивство. -Под взглядом $N1 лезвие чуть не отрезало вам же палец. -$n надумал$g уколоть вас в спину - думать $s видно не учили. -$n надумал$g уколоть $N3 в спину. Как$x $e забавн$w. - -M - 531 -Вы мастерским ударом закололи $N3, $E и пикнуть не успел$G. -Мастерским ударом в спину $n отправил$g вас к праотцам. -Мастерским ударом в спину $n отправил$g $N3 к праотцам. -Вам не удалось нанизать $N3 на $o3. Тренируйтесь. -Вам удалось избежать коварного удара $n1. -$n не попал$g своим оружием в спину $N1. -Вы укололи $N3, $S просто передернуло от боли. -$n нанес$q вам удар своим оружием, и этот факт вас не радует. -$n нанес$q удар своим оружием в спину $N1. -Вы чуть не вывихнули себе руку, когда попытались заколоть $N3. -Руку $n1 свело судорогой, когда $e пытал$u заколоть вас. -$n чуть не вывихнул$g себе руку, когда попытал$u заколоть $N3. - -* Bash -M - 532 -$N пал$G ниц после вашего удара! Встать $M уже не суждено. -Удар $n1 надолго отбил у вас охоту вставать. -$N завалил$U на землю от удара $n1, да так и остал$U лежать. -$N слишком сильн$W для вас, чтобы сбить $S с ног. -$n хотел$g завалить вас, но, не рассчитав сил, упал$g сам$g. -$N избежал$G попытки $n1 завалить $S. -Вы завалили $N3 на землю своим мощным ударом. -Вы полетели на землю от мощного удара $n1. -$n завалил$g $N3 на землю мощным ударом. -Вы попытались завалить $N3. Вот так и сидите. -$n неумело попытал$u завалить вас. -$n рискнул$g завалить $N3, но упал$g, даже не прикоснувшись. - -M - 532 -Вы мощным ударом завалили $N3 на землю! От такого удара $E скончал$U. -После удара $n1, вы с грохотом рухнули на землю. Встать вам уже не судьба. -$N рухнул$G на землю и рассыпал$U после мощного удара $n1. -Вы попытались сбить $N3, но упали сами. Учитесь. -$n попытал$u сбить вас с ног, но, в итоге, приземлил$u сам$g. -$n попытал$u завалить $N3, но не тут-то было. -Вы завалили $N3 на землю. -$n завалил$g вас на землю. Поднимайтесь! -Одним ударом $n повалил$g $N3 на землю. -Сбить $N3?! Да вы с ума сошли!!! -Ваш взгляд заставил $n3 передумать. -$n2 стало плохо при мысли сбить $N3. - -* Kick -M - 534 -Ваш мощный пинок отправил $N3 к праотцам. -$n пнул$g вас, отделив душу от тела. -$N погиб$Q от мощного удара ноги $n1. -Ваш мощный пинок пропал втуне. -$n попытал$u пнуть вас, но промахнул$u. -$n попытал$u пнуть $N3. Займите же $m ловкости. -Вы $tпнули $N3. Теперь $M наложат гипс. -$n $tпнул$g вас, $e просто изверг. -$n $tпнул$g $N3. Морда $N1 искривилась в гримасе боли. -$N глянул$G на вас так, что вы покраснели. -Пинать $n3 учили плохо - $e промахнул$u. -$n пнул$g огромный камень, созданный $N4. - -M - 534 -Вы отвесили $N2 смачного пинка. Больше $M ничего не понадобится. -Своим ядреным пинком $n отправил$g вас в путешествие по другим мирам. -$n убил$g $N3 своим ядреным пинком. -Ваш пинок не достиг цели - вам остается лишь тренироваться. -$n промахнул$u, когда пытался пнуть вас. -Мощный пинок $n1 не достиг $N1 -Вы $tпнули $N3, $S аж скрючило от боли. -$n $tпнул$g вас. Ваши глаза на лоб полезли от этой дикой боли. -$n $tпнул$g $N3. Теперь $N дико вращает глазами от боли. -Вы хотели отвесить пинка $N2, но запнулись. -Размах на рубль - удар на копейку. Это про $n3. -$n2 стало плохо при мысли пнуть $N3. - -* Сглазить -M - 566 -Стоило вам только посмотреть на $N3, как $E просто умер$Q. Похоже от страха! -Последнее что вы увидели в своей жизни, это злой взгляд $n1 в вашу сторону. -Под радостным взглядом $n1 $N0 весело умер$Q. -Вы зло посмотрели на $N3, но $M похоже наплевать. -Злобный взгляд $n1 упал на вас. Упал и пропал. -Пылающий злобой взгляд $n1 готов испепелить $N3. Но проку маловато. -Вы посмотрели своим недобрым глазом на $N3, $E передернул$U от боли. -$n посмотрел$g на вас своим недобрым глазом. Мама, роди меня обратно! -$n прищурил$u и зло зыркнул$g на $N3. \u$N3 просто передернуло от боли. -Вы прищурились, кинули взгляд на $N3 и чуть не ослепли! -$n тупо уставил$u на вас - идиот идиотом. -$n прищурил$u и тупо глянул$g на $N3. Ну и морда! - -* Изгнать нежить -M - 579 -Ослепительный луч света спалил $N3 дотла. -Ослепительный луч света просто испепелил вас. Нам будет вас не хватать. -Исходящий от $n1 свет превратил $N3 в кучку золы. -Вы не смогли нанести урон $N2. -Лучи света безвредно скользнули по вам. -Лучи света безвредно скользнули по $N2. -Лучи света опалили $N3. -$n опалил$g вас лучами света. БОЛЬНО! -Луч света опалил $N3 и $E содрогнул$U от боли. -Вы попытались обжечь $N3. Кажется, $M на вас наплевать. -$n попытал$u обжечь вас - и чуть не сгорел$g! -$n попытал$u обжечь $N3 - и чуть не сгорел$g! - -* Удавить -M - 581 -Стоило вам слегка придушить $N3 - и $E спокойно испустил$G дух. -Удавка $n1 оборвала ваш земной путь. Покойтесь с миром. -$N вытаращил$G глаза и тихо испустил$G дух в удавке $n1. -Вы не смогли удавить $N3. Надо было больше тренироваться и чаще брить голову! -Удавка $n1 попусту рассекла воздух - вам удалось увернуться. -Удавка $n1 лишь попусту рассекла воздух - $N увернул$U. -$N захрипел$G и смешно задергал$U, когда вы затянули на $S шее удавку. -Удавка $n1 впилась вам в горло! Не помешало бы глотнуть воздуха! -$N захрипел$G и выпучил$G глаза, когда $n накинул$g на $S шею удавку. -Задушить? $N3?! А если $E на вас плюнет?! -$n попытал$u задушить вас. Как бы не лопнул$g с натуги, бедняжка... -$n попытал$u удавить $N3. Доживать свой век $e будет лягушкой в ближайшем болоте. - - -*************************************************************************** -* Weapon Attacks * -*************************************************************************** - -* Hit -M - 400 -Вы нанесли $N2 прекрасный удар - после этого $M уже не встать. -$n ударил$g вас - и в глазах у вас померкло. -От удара $n1 $N отправил$U в мир теней. -Вы попытались ударить $N3 - неудачно. -$n попытал$u ударить вас, но не рассчитал$g и промахнул$u. -$N сумел$G избежать удара $n1. -# -# -# -Ваша рука отказалась подняться на $N3. -$n безвольно опустил$g руку, пытавшуюся ударить вас. -Рука $n1 отказалась причинить вред $N2. - -M - 400 -Вы выбили остатки жизни из $N1 своим мощным ударом. -Сильнейшим ударом руки $n отправил$g вас в мир мертвых. -$N скончал$U мгновенно после сильнейшего удара $n1. -Ваша рука не достигла $N1 - нужно было лучше тренироваться. -$n промазал$g, когда хотел$g ударить вас. -Удар $n1 прошел мимо $N1. -# -# -# -У вас руки опускаются при мысли ударить $N3. -Взглядом вы заставили $n3 отказаться от мысли ударить вас. -$n не осмелил$u ударить $N3. - -* Sting -M - 401 -Вы разорвали $N3 на мелкие кусочки. Бедн$W $N! -$n разорвал$g вас, как Тузик шапку. Вам не позавидуешь. -$n разорвал$g $N3 на мелкие кусочки. Вот это смерть! -Вы попытались ободрать $N3, но $E - не липка. -$n попытал$u ободрать вас - скорняк из н$s неважнецкий. -$n попытал$u ободрать $N3 - неудачно. -# -# -# -Ободрать $N3 вам не удастся - уж больно $E красив$A. -$n не осмелил$u испортить вашу красу. -$n не осмелил$u ободрать такую красоту. - -M - 401 -Вы сняли шкуру с $N1. \u$E умер$Q со стыда. -$n содрал$g с вас шкуру. А вот и старуха с косой! -$n содрал$g шкуру с $N1, $M очень больно и $E умирает. -Вы попытались ободрать $N3, но не смогли. -$n промахнул$u, когда пытал$u ободрать вас. -$n не смог$q ободрать $N3 - $e просто промазал$g. -# -# -# -Ободрать $N3?! Да вы с ума сошли!!! -Под вашим взглядом $n чуть сам$g себя не ободрал$g. -$n не смог$q ободрать $N3. От этой мысли $m стало стыдно. - -* Whip -M - 402 -Вы, кажется, перехлестнули - $N помер$Q. -$n захлестал$g вас до смерти. -$n хлестнул$g $N3 - кажется, $N2 уже хватит. -Вы попытались хлестнуть $N3 - недолет. -$n попытал$u хлестнуть вас, но промахнул$u. -$n попытал$u хлестнуть $N3, но промахнул$u. -# -# -# -А вот это уже слишком - $N этого не простит. -$n не смог$q причинить вам вред. -$n попытал$u хлестнуть $N3 - лучше бы $e не нарывал$u. - -M - 402 -Вы захлестали $N3 до смерти. -Хлестким ударом $n лишил$g вас жизни. -$n своим хлестким ударом лишил$g $N3 жизни. -Вы попытались хлестнуть $N3, но промахнулись. -Вы избежали попытки $n1 хлестнуть вас. -Попытка $n1 хлестнуть $N3 оказалась неудачной. -# -# -# -Вы не можете причинить вред $N2. -$n не смог$q хлестнуть вас. Руки не слушаются $s. -$n будет жалеть о своей попытке хлестнуть $N3 всю жизнь. - -* Slash -M - 403 -Вы изрубили $N3 в капусту. Хороший пирог получится. -$n изрубил$g вас на мелкие кусочки. Теперь вас не стыдно подать к столу. -$n изрубил$g $N3 в фарш. При жизни $N выглядел$G лучше. -Вы попытались рубануть $N3, но промахнулись. -$n попытал$u рубануть вас, но промахнул$u. -$n попытал$u рубануть $N3, но промахнул$u. -# -# -# -Вы попытались рубануть $N3. Похоже, вы напросились. -$n пытал$u немного порубить вас. Повар из н$s никудышный. -$N удивленно взглянул$G на $n3. Похоже, рубака из $n1 не выйдет. - -M - 403 -Вы порубили $N3 ровными кусочками. Да вы просто мясник! -$n изрубил$g вас так, что теперь вряд ли кто-нибудь опознает ваш труп. -$n изрубил$g $N3. От $N1 осталось только кровавое месиво. -Вы попытались рубануть $N3, но попали только по воздуху. -$n попытал$u рубануть вас, но этот удар прошел мимо. -$n попытал$u рубануть $N3, но этот удар прошел мимо. -# -# -# -Рубануть $N3?! Но вы не можете причинить $M вред. -$n смутил$u при мысли рубануть вас. -$n2 лучше не напрашиваться - $N ведь может и ответить. - -* Укусить -M - 404 -Вы вцепились мертвой хваткой в шею $N1 и выпустили $M всю кровь. -$n вцепил$u в вашу шею, разорвав сонные артерии. -$n вцепил$u в шею $N1, загрыз$q до смерти. -Вы попытались укусить $N3, но поймали зубами лишь воздух. -$n попытал$u укусить вас, но поймал$g зубами лишь воздух. -$n попытал$u укусить $N3, но поймал$g зубами лишь воздух. -# -# -# -Вы попытались укусить $N3. Ваши зубы при этом чудом не сломались. -$n попытал$u укусить вас, но вы $m не по зубам. -$n попытал$u укусить $N3. Да, так $e скоро все зубы сломает. - -M - 404 -Вы загрызли $N3 насмерть! Какой у вас кровожадный нрав. -$n прокусил$g вам горло! Вы тут же скончались от огромной потери крови. -$n перегрыз$q глотку $N2, вызвав мгновенную смерть. -Вы попытались укусить $N3, но лишь громко клацнули зубами. -$n попытал$u укусить вас, но лишь громко клацнул$g зубами. -$n попытал$u укусить $N3, но лишь громко клацнул$g зубами. -# -# -# -При мысли укусить $N3 у вас свело челюсти. -Вы заставите пожалеть $n3 о попытке укусить вас. -$n неумело попытал$u укусить $N3. Зря $e так. - -* Bludgeon -M - 405 -Вы огрели $N3, расколов $S жизнь в щепки. -С криком "А почему без кепки?" $n расколол$g вашу голову, как пустой орех. -$n огрел$g $N3. Жизнь $N1 дала трещину. -Вы попытались огреть $N3, но промахнулись. -$n попытал$u огреть вас, но промахнул$u. -$n попытал$u огреть $N3, но промахнул$u. -# -# -# -Огреть $N3 вам не удалось. Не по Гришке шапка. -$n попытал$u огреть вас - это уже слишком. -$n попытал$u огреть $N3 - зря это $e. - -M - 405 -С криком "А вот твоя панама!" вы размозжили голову $N2. -Ваша голова с треском раскололась от удара $n1. -$n огрел$g $N3. После такого удара мало кто выживал. \u$N не стал$G исключением. -Вы попытались огреть $N3, но ваш удар не достиг цели. -$n попытал$u огреть вас, но $s удар не достиг цели. -$n попытал$u огреть $N3, но $s удар не достиг цели. -# -# -# -Вам не стоит замахиваться на $N3 - целее будете. -$n рискнул$g замахнуться на вас. Глуп$a еще. -$n ужаснул$u при мысли огреть $N3. - -* Crush -M - 406 -Ваш сокрушающий удар отправил $N3 подальше из этого мира. -Вы сокрушены ударом $n1. Нам будет вас не хватать. -$N насмерть сокрушен$A $n4. -Ваш сокрушающий удар не достиг $N1. -$n попытал$u сокрушить вас, но $s удар не достиг цели. -$n попытал$u сокрушить $N3, но $s удар не достиг цели. -# -# -# -Вы хотели сокрушить $N3. Неудачно. -$n хотел$g сокрушить вас. Теперь $n выглядит сокрушенно. -$n попытал$u сокрушить $N3. Забавно. - -M - 406 -Вы смертельно сокрушили $N3! Теперь $E никого не побеспокоит. -Сокрушающий удар $n1 отправил вас на прогулку в царство теней. -$n смертельно сокрушил$g $N3. Мстить $m $N будет уже в другой жизни. -Вы попытались сокрушить $N3, но промахнулись. -$n попытал$u сокрушить вас, но промахнул$u. -$n попытал$u сокрушить $N3, но промахнул$u. -# -# -# -Вас бросает в дрожь при мысли сокрушить $N3. -$n не смог$q причинить вам вред. -$n попытал$u сокрушить $N3. Зря $e так. - -* Pound (резануть) -M - 407 -Вы резанули $N3 так, что от н$S остались рожки да ножки. -$n резанул$g вас, отделив душу от тела. -$n резанул$g $N3, выпустив дух из $S тела. -Вы попытались резануть $N3, но промахнулись. -$n попытал$u резануть вас, но промахнул$u. -$n попытал$u резануть $N3, но промахнул$u. -# -# -# -Вы пытались резануть $N3? Напрасно. -$n пытал$u резануть вас. \u$e же может порезаться! -$N гаркнул$G: "Заберите же у $n1 $o3 - порежется ведь..." - -M - 407 -Вы зарезали $N3! Вы похожи на мясника. -$n смертельно резанул$g вас! Вы умираете. -$n резанул$g $N3. От этого удара $N испустил$G последний вздох. -Вы попытались резануть $N3, но ваш удар не достиг цели. -$n попытал$u резануть вас, но $s удар не достиг цели. -$n попытал$u резануть $N3, но $s удар не достиг цели. -# -# -# -Вы хотите резануть $N3?! Да вы с ума сошли!!! -$n попытал$u резануть вас. Какая наглость!!! -$n хотел$g резануть $N3, но $N заставил$G $s передумать. - -* Claw -M - 408 -Вы оцарапали $N3 своими коготками. Не поняв произошедшего, $N помер$Q. -$n оцарапал$g вас своими когтями. Это было последним, что вы почувствовали. -$n оцарапал$g $N3 своими когтями. От такого умрет кто угодно. -Ваши шаловливые коготки не смогли дотянуться до $N1. -$n попытал$u оцарапать вас. Ну $s с такими шутками. -$n попытал$u оцарапать $N3, но $E послал$G $n3 подальше. -# -# -# -Вы расхотели царапать $N3, стоило $M взглянуть на вас. -$n попытал$u оцарапать вас. Пусть только еще попробует! -$n попытал$u оцарапать $N3 - что у н$s за нравы?! - -M - 408 -Вы смертельно оцарапали $N3. Истекая кровью, $E умер$Q. -Вы умерли после того, как $n смертельно оцарапал$g вас. -$n смертельно оцарапал$g $N3. \u$N умер$Q от огромной потери крови. -Вы попытались оцарапать $N3, но промахнулись. -$n попытал$u оцарапать вас, но промахнул$u. -$n попытал$u оцарапать $N3, но промахнул$u. -# -# -# -Вы не стали царапать $N3 - жизнь вам еще дорога. -$n решил$g поссориться с вами - $e попытал$u оцарапать вас. -Не стоит $n2 пытаться оцарапать $N3. - -* Maul (Подстрелить) -M - 409 -Ваша стрела, выпущенная в $N3, была для н$S последней. -$n метко поразил$g вас стрелой - и все поблекло. -Меткий выстрел $n1 послал $N3 в мир иной. -Вы попытались подстрелить $N3, но промахнулись. -$n попытал$u подстрелить вас, но промахнул$u. -$n попытал$u подстрелить $N3, но промахнул$u. -# -# -# -Вы шутите? Что может сделать выстрел $N2? -Ха! $n попытал$u подстрелить вас. Хорошо, что вы непробиваемы. -$n безуспешно попытал$u подстрелить $N3. - -M - 409 -Вы метко подстрелили $N3. Стрела отправила $S в мир иной. -Вы умерли от стрелы, пущенной $n4. Пусть земля вам будет пухом. -Стрела $n1 оборвала жизнь $N1. Такова се ля ви! -Вы попытались подстрелить $N3, но ваша стрела не достигла цели. -$n попытал$u подстрелить вас, но $s стрела не достигла цели. -$n попытал$u подстрелить $N3, но $s стрела не достигла цели. -# -# -# -Вы сошли с ума, если хотите подстрелить $N3! -$n попытал$u подстрелить вас. Пустое это занятие. -$n попытал$u подстрелить $N3. Зачем $m это?! - -* Thrash (пырнуть) -M - 410 -Вы распороли $N3 на несколько частей, убив $S. -$n зверски распорол$g вас, и вы погибли. -$n зверски распорол$g $N3 на несколько частей, убив $S. -Вы попытались пырнуть $N3, но промахнулись. -$n попытал$u пырнуть вас, но промахнул$u. -$n попытал$u пырнуть $N3, но промахнул$u. -# -# -# -Пырнуть?! $N3?! Более вероятно, что $E пырнет вас! -$n попытал$u пырнуть вас. Да уж... Наивн$w. -Выглядя довольно глупо, $n попытал$u пырнуть $N3. - -M - 410 -Вы смертельно пырнули $N3. Не приходя в сознание, $N скончал$U. -$n смертельно пырнул$g вас. Красная пелена заволокла ваши глаза. -$n зверски пырнул$g $N3. \u$N умирает в страшных муках. -Вы попытались пырнуть $N3, но ваш удар не достиг цели. -$n попытал$u пырнуть вас, но $s удар не достиг цели. -$n попытал$u пырнуть $N3, но $s удар не достиг цели. -# -# -# -Ваша попытка пырнуть $N3 выглядела очень глупо. -$n попытал$u пырнуть вас. Чёкнут$w! -$n попытал$u пырнуть $N3. \u$s глупость не знает предела! - -* Pierce -M - 411 -Вы пронзили тело $N1 и $E упал$G на землю замертво. -$n пронзил$g вас насквозь. Вы ушли в мир иной. -$n пронзил$g $N3 насквозь и $S безжизненное тело упало на землю. -Вы попытались уколоть $N3, но промахнулись. -$n попытал$u уколоть вас, но промахнул$u. -$n попытал$u уколоть $N3, но промахнул$u. -# -# -# -Похоже, ваше оружие не причиняет $M вреда! -$n попытал$u уколоть вас... Не правда ли смешно? -$n попытал$u уколоть $N3, но не смог$q. - -M - 411 -Вы проткнули $N3 насквозь, вызвав мгновенную смерть. -$n проткнул$g вас насквозь. Вы упали на землю замертво. -$n проткнул$g $N3 насквозь. В страшных муках $N умер$Q. -Вы попытались проткнуть $N3, но ваш удар не достиг цели. -$n попытал$u проткнуть вас, но $s удар не достиг цели. -$n попытал$u проткнуть $N3, но $s удар не достиг цели. -# -# -# -Проткнуть $N3?! Как вам не стыдно!!! -$n хотел$g проткнуть вас, но своим взглядом вы подавили это желание. -$n попытал$u проткнуть $N3. Влетит же $m от $N1. - -* Ткнуть. -M - 412 -Вы сильно ткнули $N3 и $E с криком боли безжизненно упал$G на землю. -$n сильно ткнул$g вас, вы почувствовали дикую боль... А затем ничего. -$n сильно ткнул$g $N3, вызвав мгновенную смерть. -Вы попытались ткнуть $N3, но промахнулись. -$n попытал$u ткнуть вас, но промахнул$u. -$n попытал$u ткнуть $N3, но промахнул$u. -# -# -# -Ткнуть?! $N3?! -$n безуспешно попытал$u ткнуть вас. Как забавно. -$N засмеял$U, когда $n попытал$u ткнуть $S. - -M - 412 -Вы очень сильно ткнули $N3, вызвав мгновенную смерть. -$n ткнул$g вас с такой силой, что вы сразу же скончались. -$n так сильно ткнул$g $N3, что $M не оставалось ничего, кроме как умереть. -Вы попытались ткнуть $N3, но ваш удар не достиг цели. -$n попытал$u ткнуть вас, но $s удар не достиг цели. -$n попытал$u ткнуть $N3, но $s удар не достиг цели. -# -# -# -Вы попытались ткнуть $N3. Вас плохо этому научили. -$n попытал$u ткнуть вас. Глуп$w! -Напрасно $n попытал$u ткнуть $N3! Как бы $e не пожалел$g об этом. - -* Punch (Лягнуть) -M - 413 -Вы так сильно лягнули $N3, что $E мгновенно скончал$U. -$n очень сильно лягнул$g вас, кровь залила вам глаза, и в скором времени вы погибли. -$n очень сильно лягнул$g $N3. Похоже, что $N не выдержал$G удара и погиб$Q. -Вы попытались лягнуть $N3, но промахнулись. -$n попытал$u лягнуть вас, но промахнул$u. -$n попытал$u лягнуть $N3, но промахнул$u. -# -# -# -Лягнуть?! $N3?! Лучше ударьте стену... Вы нанесете ей больше вреда. -$n безуспешно попытал$u лягнуть вас. -$n попытал$u лягнуть $N3. Лучше бы $e попытал$u ударить стену. - -M - 413 -$N скончал$U после того, как вы лягнули $S изо всех сил. -$n так сильно лягнул$g вас, что вам уже не суждено подняться. -$n очень сильно лягнул$g $N3. \u$N не вынес$Q такого удара и скончал$U на месте. -Вы попытались лягнуть $N3, но ваш удар не достиг цели. -$n попытал$u лягнуть вас, но $s удар не достиг цели. -$n попытал$u лягнуть $N3, но $s удар не достиг цели. -# -# -# -Вы попытались лягнуть $N3. Ведите себя скромнее! -$n хотел$g лягнуть вас. \u$n совсем обалдел$g! -$n2 стало плохо при мысли лягнуть $N3. - -* Stab (Боднуть) -M - 414 -Вы забодали $N3 насмерть! И так будет с каждым! -$n забодал$g вас насмерть. Теперь вас не узнать. -$n забодал$g $N3 насмерть. Бедн$W $N! -Вы попытались боднуть $N3, но промахнулись. -$n попытал$u боднуть вас, но промахнул$u. -$n попытал$u боднуть $N3, но промахнул$u. -# -# -# -Вы попытались боднуть $N3. Безуспешно. -$n попытал$u боднуть вас. Безуспешно. -$n попытал$u боднуть $N3, но безуспешно. - -M - 414 -Вы не оставили $N2 ни малейшего шанса и насмерть забодали $S! -$n насмерть забодал$g вас! При жизни вы выглядели лучше. -$n насмерть забодал$g $N3. Смерть $N1 была ужасна! -Вы попытались боднуть $N3, но ваш удар не достиг цели. -$n попытал$u боднуть вас, но $s удар не достиг цели. -$n попытал$u боднуть $N3, но $s удар не достиг цели. -# -# -# -Вам не стоит бодаться с $N4 - у н$S опыта больше. -$n попытал$u боднуть вас. Вы долго смеялись после этой попытки. -Не стоило $n3 бодаться с $N4. Похоже, $n $S достал$g. - -* Клюнуть -M - 415 -Вы досмерти заклевали $N3! Теперь $E вам не помеха. -$n досмерти заклевал$g вас. Ну и монстр!!! -$n досмерти заклевал$g $N3. Вот это расправа! -Вы попытались клюнуть $N3, но ваши старания не достигли цели. -$n попытал$u клюнуть вас, но $s старания не достигли цели. -$n попытал$u клюнуть $N3, но $s старания не достигли цели. -# -# -# -Вам не стоит клевать $N3 - $E опытнее вас. -Стыд обуял $n3 после $s попытки клюнуть вас. -$n попытал$u клюнуть $N3. \u$m стало стыдно за свой поступок. - -*Ужалить -M - 416 -Вы ужалили $N3, от этого $E распух$Q и помер$Q. -$n зажалил$g вас насмерть. Нам будет вас не хватать... -$n ужалил$g $N3, $N распух$Q и помер$Q. -Вы попытались ужалить $N3, но промахнулись. -$n попытал$u ужалить вас, но промахнул$u. -$n попытал$u ужалить $N3, но промахнул$u. -# -# -# -Ужалить? $n3?! Да он$g вас за это!.. -$n попытал$u ужалить вас... Рехнул$u? -$n попытал$u ужалить $N3, но не смог$q дотянуться. - -* Дамаг odamage - сообщения на совести билдеров. -M - 495 -# -# -# -# -# -# -# -# -# -# -# -# - -* Дамаг с бд в ванрумах -M - 496 -# -Пущенная с небес молния оказалась для вас последней. Не стоило гневить Богов. -Пущенная с небес молния оказалась для $N1 последней. Не стоило гневить Богов. -# -Вы ловко увернулись от пущенной с небес молнии. -$n ловко увернул$u от пущенной с небес молнии. -# -В голову угодила метко пущенная кем-то с небес молния! Вам здесь явно не рады. -Метко пущенная с небес молния угодила прямо в $N3! \u$m здесь явно не рады. -# -# -# - -* underwater death trap -M - 497 -# -Вам явно не стоило плавать так глубоко - вы просто утонули. -$N попытал$U что-то сказать, но только пустил$G пузыри и камнем уш$Y на дно. -# -Вам стало немного легче. -$N2 стало немного легче. -# -Вы чувствуете как вода заливает вам горло, быстрей на поверхность! -$N2 нечем дышать, вода же кругом - помогите $M как-нибудь... -# -Вода забирается к вам в горло, вы не можете дышать. На поверхность, быстрей! -$N громко булькает и пускет пузыри - похоже $M нечем дышать. - -* Slow death trap -M - 498 -# -Вам явно не стоило сюда заходить - и смерть распростерла вам свои объятья. -Громкий вскрик $N1 возвестил о $S безвременной кончине. -# -Вам стало немного легче. -$N2 стало немного легче. -# -Вы чувствуете сильный упадок сил, еще чуть-чуть - и они покинут вас. -$N быстро теряет силы - помогите же $M кто-нибудь... -# -Вы быстро теряете силы - поскорее прочь отсюда. -$N становится все бледнее и бледнее - похоже $M долго не протянуть. - -* Suffering -M - 499 -# -Вы ранены - сильно ранены - и вы погибли... -$N лежал$G, страдая, на земле, пока не осталось крови в $S бренном теле. -# -Вам стало немного легче. -$N2 стало немного легче. -# -Вы лежите на земле, страдая от ужасных ран... -$N беспомощно лежит на земле и страдает... -# -Раны причиняют вам боль. -Раны причиняют боль $N2. - -M - 499 -# -С последним ударом сердца жизнь покинула вас... -$N потерял$G много крови и погиб$Q.. -# -Вам немного полегчало. -$N2 немного полегчало. -# -Вы лежите на земле и страдаете от кровоточащих ран... -$N беспомощно лежит на земле, страдая... -# -Еще не затянувшиеся раны причиняют вам боль. -Еще не затянувшиеся раны причиняют $N2 боль. - - -*************************************************************************** -* NPC Attacks * -*************************************************************************** - -* Fire Breath -M - 243 -$N убит$A вашим огненным дыханием. -$n превратил$g вас в пепел своим огненным дыханием. -$n оставил$g от $N1 лишь кучку пепла. -$N избежал$G вашего огненного дыхания. -$n дыхнул$g на вас огнем, но вы избежали этой "радости". -$N избежал$G огненного дыхания $n1. -Вы поджарили $N3. -$n поджарил$g вас. -$N подгорел$G в нескольких местах, когда $n дыхнул$g на н$S огнем. -$N - бессмертн$W. Это бесполезно. -$n согрел$g вас. -$n согрел$g $N3. - -* Gas Breath -M - 244 -$N умер от вашего зловонного дыхания. -Вы не вынесли этого ужасного зловонья от $n1. -$n убил$g $N3, смертельно дыхнув на н$S. -Вы дыхнули на $N3, но вонь не достигла цели. -$n дыхнул$g на вас, но это не возымело эффекта. -$N избежал$G газовой атаки от $n1. -Вы ударили по $N2 газовым дыханием. -$n напустил$g на вас газ. -$n напустил$g газ на $N3. -$N бессмертн$W... так что... -$n захотел$g пустить на вас газ, но не смог$q. -$n попытал$u пустить газ на $N3, но не смог$q. - -* Frost Breath -M - 245 -$N убит$A вашим ледяным дыханием. -Ваше тело сковал лед, когда $n дыхнул$g на вас. -$n сделал$g из $N1 кусок льда. -$n промахнул$u, попытавшись заморозить вас своим ледяным дыханием. -$n дыхнул$g на вас холодом, но вы слишком разгорячились и не почувствовали этого. -$N избежал$G участи стать ледышкой от холодного дыхания $n1. -$N согнул$U от вашего холода. -$n заморозил$g вас, сильно дунув. -$N медленно покрывается льдом, после морозного дыхания $n1. -$N - бессмертн$W. Зря вы так... -$n согрел$g вас. -$n согрел$g $N3. - -* Acid Breath -M - 246 -$N был$G убит$A вашим кислотным дыханием. -Вы свалились замертво, когда $n дыхнул$g кислотой на вас. -$n заставил$g $N3 забиться в судорогах. \u$N умирает... -Вы дыхнули на $N3 кислотой, но $E избежал$G попадания! -$n вздумал$g убить вас своим кислотным дыханием, но вам еще мила жизнь. -Кислотное дыхание $n1 прошло мимо $N1. -$N пропитывается кислотой. -$n заставил$g вас быстро заткнуть нос от кислотного запаха. -$N бьется в судорогах от кислотного дыхания $n1. -$N - бессмертн$W и $M все нипочем. -$n помог$q вам избавиться от кислотного отравления. -$n помог$q $N2 очиститься от кислоты. - -* Lightning Breath -M - 247 -$N убит$Q вашим ослепляющим дыханием. -Вы убиты ослепляющим дыханием $n1. -$n убил$g $N3 ослепляющим дыханием. -Вы не попали в $N3 своим ослепляющим дыханием. -$n дыхнул$g на вас слепотой, но зачем вам терять зрение в самом расцвете сил? -$N избежал$G воздействия ослепляющего дыхания $n1. -Вы ослепили $N3 своим дыханием. -$n ослепил$g вас своим дыханием. -$N ослеплен$A дыханием $n1. -$N - бессмертн$W, у н$S есть иммунитет к ослепляющему дыханию. -$n попытался ослепить вас своим дыханием. -$n попытался ослепить $N3 своим дыханием. - -$ -$ diff --git a/src/administration/accounts.cpp b/src/administration/accounts.cpp index 9b5c3bceb..322422aee 100644 --- a/src/administration/accounts.cpp +++ b/src/administration/accounts.cpp @@ -13,6 +13,7 @@ std::unordered_map> accounts; #if defined(NOCRYPT) #define CRYPT(a,b) ((char *) (a)) #else +#include #define CRYPT(a, b) ((char *) crypt((a),(b))) #endif diff --git a/src/engine/boot/boot_data_files.cpp b/src/engine/boot/boot_data_files.cpp index f1b793e84..bafb62148 100644 --- a/src/engine/boot/boot_data_files.cpp +++ b/src/engine/boot/boot_data_files.cpp @@ -294,6 +294,7 @@ void TriggersFile::parse_trigger(int vnum) { get_line(file(), line); int attach_type = 0; + t = 0; // Initialize narg to avoid undefined behavior when only 2 fields present k = sscanf(line, "%d %s %d %d", &attach_type, flags, &t, &add_flag); if (0 > attach_type @@ -421,7 +422,7 @@ void WorldFile::parse_room(int virtual_nr) { std::string desc = fread_string(); utils::TrimRightIf(desc, " _"); desc.shrink_to_fit(); - world[room_realnum]->description_num = RoomDescription::add_desc(desc); + world[room_realnum]->description_num = GlobalObjects::descriptions().add(desc); if (!get_line(file(), line)) { log("SYSERR: Expecting roomflags/sector type of room #%d but file ended!", virtual_nr); diff --git a/src/engine/core/action_targeting.cpp b/src/engine/core/action_targeting.cpp index abb726149..37ba18808 100644 --- a/src/engine/core/action_targeting.cpp +++ b/src/engine/core/action_targeting.cpp @@ -141,7 +141,7 @@ CharData *TargetsRosterType::getRandomItem() { if (_roster.empty()) { return nullptr; } - return _roster[number(0, _roster.size() - 1)]; + return _roster[number(0, static_cast(_roster.size()) - 1)]; }; /* diff --git a/src/engine/core/action_targeting.h b/src/engine/core/action_targeting.h index 283b1b6bd..a74031c65 100644 --- a/src/engine/core/action_targeting.h +++ b/src/engine/core/action_targeting.h @@ -49,7 +49,7 @@ class TargetsRosterType { auto end() const noexcept { return std::make_reverse_iterator(std::begin(_roster)); }; CharData *getRandomItem(const PredicateType &predicate); CharData *getRandomItem(); - int amount() { return _roster.size(); }; + int amount() { return static_cast(_roster.size()); }; int count(const PredicateType &predicate); void flip(); }; diff --git a/src/engine/core/char_movement.cpp b/src/engine/core/char_movement.cpp index d3c8a0b35..f447a16de 100644 --- a/src/engine/core/char_movement.cpp +++ b/src/engine/core/char_movement.cpp @@ -334,10 +334,10 @@ EDirection SelectRndDirection(CharData *ch, int fail_chance) { } } - if (directions.empty() || number(1, 100 + 25 * directions.size()) < fail_chance) { + if (directions.empty() || number(1, 100 + 25 * static_cast(directions.size())) < fail_chance) { return EDirection::kUndefinedDir; } - return directions[number(0, directions.size() - 1)]; + return directions[number(0, static_cast(directions.size()) - 1)]; } int SelectDrunkDirection(CharData *ch, int direction) { diff --git a/src/engine/core/comm.cpp b/src/engine/core/comm.cpp index e92ea7c54..58e407408 100644 --- a/src/engine/core/comm.cpp +++ b/src/engine/core/comm.cpp @@ -61,6 +61,9 @@ #include "engine/network/msdp/msdp_constants.h" #include "engine/entities/zone.h" #include "engine/db/db.h" +#ifdef HAVE_SQLITE +#include "engine/db/sqlite_world_data_source.h" +#endif #include "utils/utils.h" #include "engine/core/conf.h" #include "engine/ui/modify.h" @@ -79,6 +82,12 @@ #include #endif +#ifdef ENABLE_ADMIN_API +#include "engine/network/admin_api.h" +#include +#include +#endif + #ifdef CIRCLE_MACINTOSH // Includes for the Macintosh # define SIGPIPE 13 # define SIGALRM 14 @@ -351,6 +360,7 @@ extern int num_invalid; extern char *greetings; extern const char *circlemud_version; extern int circle_restrict; +extern bool enable_world_checksum; extern FILE *player_fl; extern ush_int DFLT_PORT; extern const char *DFLT_DIR; @@ -366,12 +376,15 @@ extern void log_code_date(); // local globals DescriptorData *descriptor_list = nullptr; // master desc list +#ifdef ENABLE_ADMIN_API +static socket_t admin_socket = -1; +#endif int no_specials = 0; // Suppress ass. of special routines int max_players = 0; // max descriptors available int tics = 0; // for extern checkpointing -int scheck = 0; // for syntax checking mode +int scheck = 0; struct timeval null_time; // zero-valued time structure int dg_act_check; // toggle for act_trigger unsigned long cmd_cnt = 0; @@ -518,6 +531,9 @@ int new_descriptor(socket_t s); #endif socket_t init_socket(ush_int port); +#ifdef ENABLE_ADMIN_API +socket_t init_unix_socket(const char *path); +#endif int get_max_players(); void timeadd(struct timeval *sum, struct timeval *a, struct timeval *b); @@ -633,7 +649,11 @@ int main_function(int argc, char **argv) { break; case 's': no_specials = 1; - puts("Suppressing assignment of special routines."); + puts("Suppressing assignment of special routines."); + break; + case 'W': + enable_world_checksum = true; + puts("World checksum calculation enabled."); break; case 'd': if (*(argv[pos] + 2)) @@ -688,21 +708,27 @@ int main_function(int argc, char **argv) { printf("%s\r\n", DG_SCRIPT_VERSION); if (getcwd(cwd, sizeof(cwd))) {}; printf("Current directory '%s' using '%s' as data directory.\r\n", cwd, dir); - runtime_config.load(); + { + std::string config_path = std::string(dir) + "/misc/configuration.xml"; + runtime_config.load(config_path.c_str()); + } if (runtime_config.msdp_debug()) { msdp::debug(true); } // All arguments have been parsed, try to open log file. + // setup_logs() must be called before chdir() so that log/ and log/perslog/ + // directories are created in the working directory (next to the binary), + // not inside the data directory. runtime_config.setup_logs(); logfile = runtime_config.logs(SYSLOG).handle(); - log_code_date(); if (chdir(dir) < 0) { perror("\r\nSYSERR: Fatal error changing to data directory"); exit(1); } + log_code_date(); printf("Code version %s, revision: %s\r\n", build_datetime, revision); if (scheck) { - world_loader.BootWorld(); + GameLoader::BootWorld(); printf("Done."); } else { printf("Running game on port %d.\r\n", port); @@ -733,6 +759,18 @@ void stop_game(ush_int port) { log("Opening mother connection."); mother_desc = init_socket(port); + +#ifdef ENABLE_ADMIN_API + if (runtime_config.admin_api_enabled()) { + const char *socket_path = runtime_config.admin_socket_path().c_str(); + log("Admin API enabled, socket_path: %s", socket_path); + // Current working directory is the world directory after chdir(dir) above + admin_socket = init_unix_socket(socket_path); + } else { + log("Admin API disabled in configuration"); + } +#endif + #if defined WITH_SCRIPTING scripting::init(); #endif @@ -769,6 +807,19 @@ void stop_game(ush_int port) { return; } + +#ifdef ENABLE_ADMIN_API + if (admin_socket >= 0) { + DescriptorData *admin_d = (DescriptorData *) calloc(1, sizeof(DescriptorData)); + admin_d->descriptor = admin_socket; + event.data.ptr = admin_d; + event.events = EPOLLIN; + if (epoll_ctl(epoll, EPOLL_CTL_ADD, admin_socket, &event) == -1) { + perror("EPOLL: epoll_ctl() failed on EPOLL_CTL_ADD admin_socket"); + log("Warning: Admin API socket not added to epoll"); + } + } +#endif game_loop(epoll, mother_desc); #else log("Polling using select()."); @@ -967,6 +1018,103 @@ socket_t init_socket(ush_int port) { return (s); } +#ifdef ENABLE_ADMIN_API +socket_t init_unix_socket(const char *path) { + socket_t s; + struct sockaddr_un sa; + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) { + log("SYSERR: Error creating Unix domain socket: %s", strerror(errno)); + exit(1); + } + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path, path, sizeof(sa.sun_path) - 1); + + unlink(path); + + if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + log("SYSERR: Cannot bind Unix socket to %s: %s", path, strerror(errno)); + CLOSE_SOCKET(s); + exit(1); + } + + chmod(path, 0600); + nonblock(s); + + if (listen(s, 1) < 0) { + log("SYSERR: Cannot listen on Unix socket: %s", strerror(errno)); + CLOSE_SOCKET(s); + exit(1); + } + + log("Admin API listening on Unix socket: %s", path); + return s; +} + +int new_admin_descriptor(int epoll, socket_t s) { + socket_t desc; + DescriptorData *newd; + + desc = accept(s, nullptr, nullptr); + if (desc == -1) { + return -1; + } + + // Check connection limit + int admin_connections = 0; + for (DescriptorData *d = descriptor_list; d; d = d->next) { + if (d->admin_api_mode) { + admin_connections++; + } + } + + int max_connections = runtime_config.admin_max_connections(); + if (admin_connections >= max_connections) { + log("Admin API: connection rejected (limit %d reached)", max_connections); + const char *error_msg = "{\"status\":\"error\",\"message\":\"connection limit reached\"}\n"; + iosystem::write_to_descriptor(desc, error_msg, strlen(error_msg)); + CLOSE_SOCKET(desc); + return -1; + } + + nonblock(desc); + NEWCREATE(newd); + + newd->descriptor = desc; + newd->state = EConState::kAdminAPI; + newd->admin_api_mode = true; + newd->keytable = kCodePageUTF8; + strcpy(newd->host, "unix-socket"); + newd->login_time = newd->input_time = time(0); + + // Initialize output buffer + newd->output = newd->small_outbuf; + newd->bufspace = kSmallBufsize - 1; + *newd->output = '\0'; + newd->bufptr = 0; + +#ifdef HAS_EPOLL + struct epoll_event event; + event.data.ptr = newd; + event.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP; + epoll_ctl(epoll, EPOLL_CTL_ADD, desc, &event); +#endif + + newd->next = descriptor_list; + descriptor_list = newd; + + log("Admin API: new connection from Unix socket"); + + const char *welcome = "{\"status\":\"ready\",\"version\":\"1.0\"}\n"; + iosystem::write_to_descriptor(desc, welcome, strlen(welcome)); + + return 0; +} +#endif // ENABLE_ADMIN_API + int get_max_players(void) { return (max_playing); } @@ -1049,15 +1197,26 @@ inline void process_io(fd_set input_set, fd_set output_set, fd_set exc_set, fd_s d = (DescriptorData *) events[i].data.ptr; if (d == nullptr) continue; - if (mother_desc == d->descriptor) // событие на mother_desc: принимаем все ждущие соединения - { + if (mother_desc == d->descriptor) { // событие на mother_desc: принимаем все ждущие соединения int desc; do desc = new_descriptor(epoll, mother_desc); while (desc > 0 || desc == -3); - } else // событие на клиентском дескрипторе: получаем данные и закрываем сокет, если EOF - if (iosystem::process_input(d) < 0) - close_socket(d, false, epoll, events, n); + } +#ifdef ENABLE_ADMIN_API + else if (admin_socket >= 0 && admin_socket == d->descriptor) { + new_admin_descriptor(epoll, admin_socket); + } +#endif + else { // событие на клиентском дескрипторе: получаем данные и закрываем сокет, если EOF +#ifdef ENABLE_ADMIN_API + int result = (d->admin_api_mode) ? admin_api_process_input(d) : iosystem::process_input(d); +#else + int result = iosystem::process_input(d); +#endif + if (result < 0) + close_socket(d, false, epoll, events, n); + } } else if (events[i].events & !EPOLLOUT & !EPOLLIN) // тут ловим все события, имеющие флаги кроме in и out { // надо будет помониторить сислог на предмет этих сообщений @@ -1105,6 +1264,11 @@ inline void process_io(fd_set input_set, fd_set output_set, fd_set exc_set, fd_s for (d = descriptor_list; d; d = next_d) { next_d = d->next; + // Admin API connections don't use the input queue - skip command processing + if (d->admin_api_mode) { + continue; + } + /* * Not combined to retain --(d->wait) behavior. -gg 2/20/98 * If no wait state, no subtraction. If there is a wait @@ -1750,6 +1914,9 @@ void close_socket(DescriptorData * d, int direct) return; } + log("close_socket called: direct=%d, state=%d, admin_api_mode=%d, host=%s", + direct, static_cast(d->state), d->admin_api_mode, d->host); + //if (!direct && d->character && NORENTABLE(d->character)) // return; // Нельзя делать лд при wait_state diff --git a/src/engine/core/config.cpp b/src/engine/core/config.cpp index 7c592e3d8..d9a896ad5 100644 --- a/src/engine/core/config.cpp +++ b/src/engine/core/config.cpp @@ -26,6 +26,7 @@ #endif #include +#include #define YES 1 #define NO 0 @@ -465,6 +466,11 @@ void RuntimeConfiguration::setup_logs() { mkdir("log", 0700); mkdir("log/perslog", 0700); + char abs_path[4096]; + if (getcwd(abs_path, sizeof(abs_path))) { + m_log_dir = std::string(abs_path) + "/log"; + } + for (int i = 0; i < 1 + LAST_LOG; ++i) { auto stream = static_cast(i); @@ -566,6 +572,29 @@ void RuntimeConfiguration::load_statistics_configuration(const pugi::xml_node *r m_statistics = StatisticsConfiguration(host, port); } +void RuntimeConfiguration::load_world_loader_configuration(const pugi::xml_node *root) { + // Read YAML_THREADS environment variable (takes precedence) + const char* env_threads = std::getenv("YAML_THREADS"); + if (env_threads) { + size_t threads = static_cast(std::strtoul(env_threads, nullptr, 10)); + if (threads > 0 && threads <= 64) { // Sanity check + m_yaml_threads = threads; + return; + } + } + + // Fall back to XML configuration if available + const auto world_loader = root->child("world_loader"); + if (!world_loader) { + return; + } + + const auto yaml_config = world_loader.child("yaml"); + if (yaml_config) { + m_yaml_threads = static_cast(std::strtoul(yaml_config.child_value("threads"), nullptr, 10)); + } +} + typedef std::map EOutputStream_name_by_value_t; typedef std::map EOutputStream_value_by_name_t; EOutputStream_name_by_value_t EOutputStream_name_by_value; @@ -669,7 +698,7 @@ const std::string &NAME_BY_ITEM(const CLogInfo::EMode item) { return EMode_name_by_value.at(item); } -const char *RuntimeConfiguration::CONFIGURATION_FILE_NAME = "lib/misc/configuration.xml"; +const char *RuntimeConfiguration::CONFIGURATION_FILE_NAME = "misc/configuration.xml"; const RuntimeConfiguration::logs_t LOGS({ CLogInfo("syslog", "СИСТЕМНЫЙ"), @@ -690,7 +719,8 @@ RuntimeConfiguration::RuntimeConfiguration() : m_msdp_disabled(false), m_msdp_debug(false), m_changelog_file_name(Boards::constants::CHANGELOG_FILE_NAME), - m_changelog_format(Boards::constants::loader_formats::GIT) { + m_changelog_format(Boards::constants::loader_formats::GIT), + m_yaml_threads(0) { } void RuntimeConfiguration::load_from_file(const char *filename) { @@ -710,12 +740,18 @@ void RuntimeConfiguration::load_from_file(const char *filename) { load_boards_configuration(&root); load_external_triggers(&root); load_statistics_configuration(&root); + load_world_loader_configuration(&root); +#ifdef ENABLE_ADMIN_API + load_admin_api_configuration(&root); +#endif } catch (const std::exception &e) { - std::cerr << "Error when loading configuration file " << filename << ": " << e.what() << "\r\n"; + std::cerr << "ERROR: Failed to load configuration file " << filename << ": " << e.what() << "\r\n"; + std::cerr << "WARNING: Running with default configuration settings. YAML_THREADS will use hardware_concurrency().\r\n"; } catch (...) { - std::cerr << "Unexpected error when loading configuration file " << filename << "\r\n"; + std::cerr << "ERROR: Unexpected error when loading configuration file " << filename << "\r\n"; + std::cerr << "WARNING: Running with default configuration settings.\r\n"; } } @@ -761,3 +797,37 @@ bool CLogInfo::open() { RuntimeConfiguration runtime_config; // vim: ts=4 sw=4 tw=0 noet syntax=cpp : + +#ifdef ENABLE_ADMIN_API +void RuntimeConfiguration::load_admin_api_configuration(const pugi::xml_node *root) { + const auto admin_api = root->child("admin_api"); + if (!admin_api) { + return; + } + + const auto enabled = admin_api.child("enabled"); + if (enabled) { + const std::string value = enabled.child_value(); + m_admin_api_enabled = (value == "true" || value == "1" || value == "yes"); + } + + const auto socket_path = admin_api.child("socket_path"); + if (socket_path) { + m_admin_socket_path = socket_path.child_value(); + } + + const auto require_auth = admin_api.child("require_auth"); + if (require_auth) { + const std::string value = require_auth.child_value(); + m_admin_require_auth = (value == "true" || value == "1" || value == "yes"); + } + + const auto max_connections = admin_api.child("max_connections"); + if (max_connections) { + m_admin_max_connections = atoi(max_connections.child_value()); + if (m_admin_max_connections < 1) { + m_admin_max_connections = 1; + } + } +} +#endif diff --git a/src/engine/core/config.h b/src/engine/core/config.h index 0f294ee83..fab67821b 100644 --- a/src/engine/core/config.h +++ b/src/engine/core/config.h @@ -156,11 +156,12 @@ class RuntimeConfiguration { const CLogInfo &logs(EOutputStream id) { return m_logs[static_cast(id)]; } void handle(const EOutputStream stream, FILE *handle); const std::string &log_stderr() { return m_log_stderr; } + const std::string &log_dir() const { return m_log_dir; } auto output_thread() const { return m_output_thread; } auto output_queue_size() const { return m_output_queue_size; } void setup_logs(); - const auto syslog_converter() const { return m_syslog_converter; } + auto syslog_converter() const { return m_syslog_converter; } void enable_logging() { m_logging_enabled = true; } void disable_logging() { m_logging_enabled = false; } @@ -176,6 +177,15 @@ class RuntimeConfiguration { const auto &statistics() const { return m_statistics; } + size_t yaml_threads() const { return m_yaml_threads; } + +#ifdef ENABLE_ADMIN_API + const auto &admin_socket_path() const { return m_admin_socket_path; } + bool admin_api_enabled() const { return m_admin_api_enabled; } + bool admin_require_auth() const { return m_admin_require_auth; } + int admin_max_connections() const { return m_admin_max_connections; } +#endif + private: static const char *CONFIGURATION_FILE_NAME; @@ -193,9 +203,14 @@ class RuntimeConfiguration { void load_boards_configuration(const pugi::xml_node *root); void load_external_triggers(const pugi::xml_node *root); void load_statistics_configuration(const pugi::xml_node *root); + void load_world_loader_configuration(const pugi::xml_node *root); +#ifdef ENABLE_ADMIN_API + void load_admin_api_configuration(const pugi::xml_node *root); +#endif logs_t m_logs; std::string m_log_stderr; + std::string m_log_dir; bool m_output_thread; std::size_t m_output_queue_size; converter_t m_syslog_converter; @@ -207,6 +222,15 @@ class RuntimeConfiguration { std::string m_external_reboot_trigger_file_name; StatisticsConfiguration m_statistics; + + size_t m_yaml_threads; + +#ifdef ENABLE_ADMIN_API + std::string m_admin_socket_path{"admin_api.sock"}; + bool m_admin_api_enabled{false}; + bool m_admin_require_auth{true}; + int m_admin_max_connections{1}; +#endif }; extern RuntimeConfiguration runtime_config; diff --git a/src/engine/core/heartbeat.h b/src/engine/core/heartbeat.h index 25186c275..34543e184 100644 --- a/src/engine/core/heartbeat.h +++ b/src/engine/core/heartbeat.h @@ -32,13 +32,13 @@ class BasePulseMeasurements { BasePulseMeasurements(); virtual ~BasePulseMeasurements() {} - const auto window_size() const { return WINDOW_SIZE; } + auto window_size() const { return WINDOW_SIZE; } - const auto window_sum() const { return m_sum; } - const auto current_window_size() const { return m_measurements.size(); } + auto window_sum() const { return m_sum; } + auto current_window_size() const { return m_measurements.size(); } - const auto global_sum() const { return m_global_sum; } - const auto global_count() const { return m_global_count; } + auto global_sum() const { return m_global_sum; } + auto global_count() const { return m_global_count; } const auto &min() const { return m_min; } const auto &max() const { return m_max; } @@ -158,11 +158,11 @@ class Heartbeat { PulseStep(std::string name, const int modulo, const int offset, pulse_action_t action); const auto &name() const { return m_name; } - const auto modulo() const { return m_modulo; } - const auto offset() const { return m_offset; } + auto modulo() const { return m_modulo; } + auto offset() const { return m_offset; } const auto &action() const { return m_action; } - const auto off() const { return m_off; } - const auto on() const { return !off(); } + auto off() const { return m_off; } + auto on() const { return !off(); } void turn_on() { m_off = false; } void turn_off() { m_off = true; } diff --git a/src/engine/core/iosystem.cpp b/src/engine/core/iosystem.cpp index 7a6597cae..8a0146e1f 100644 --- a/src/engine/core/iosystem.cpp +++ b/src/engine/core/iosystem.cpp @@ -708,6 +708,19 @@ int process_output(DescriptorData *t) { return -1; } +#ifdef ENABLE_ADMIN_API + // Admin API uses raw JSON output, skip formatting + if (t->admin_api_mode) { + if (!t->output || !*t->output) + return 0; + + int result = write_to_descriptor(t->descriptor, t->output, strlen(t->output)); + t->bufptr = 0; + *t->output = '\0'; + return (result >= 0 ? 1 : -1); + } +#endif + // Отправляю данные снуперам // handle snooping: prepend "% " and send to snooper if (t->output && t->snoop_by) { diff --git a/src/engine/core/sysdep.h b/src/engine/core/sysdep.h index 5f99e9c93..a3dec595c 100644 --- a/src/engine/core/sysdep.h +++ b/src/engine/core/sysdep.h @@ -256,9 +256,7 @@ struct in_addr # pragma warn -pia // to turn off possibly incorrect assignment. 'if (!(x=a))' // # pragma warn -sig // to turn off conversion may lose significant digits. // # endif -# ifndef _WINSOCK2API_ // Winsock1 and Winsock 2 conflict. // -# include -# endif +# include # ifndef FD_SETSIZE // MSVC 6 is reported to have 64. // # define FD_SETSIZE 1024 # endif diff --git a/src/engine/db/db.cpp b/src/engine/db/db.cpp index 1f0dff388..f752d3022 100644 --- a/src/engine/db/db.cpp +++ b/src/engine/db/db.cpp @@ -57,9 +57,23 @@ #include "gameplay/ai/spec_procs.h" #include "gameplay/communication/social.h" #include "player_index.h" +#include "world_checksum.h" +#include "legacy_world_data_source.h" +#include "world_data_source_base.h" +#ifdef HAVE_SQLITE +#include "sqlite_world_data_source.h" +#endif +#ifdef HAVE_YAML +#include "yaml_world_data_source.h" +#endif +#include "world_data_source_manager.h" + #include #include +#ifndef _WIN32 +#include +#endif #include @@ -99,6 +113,7 @@ int global_uid = 0; long top_idnum = 0; // highest idnum in use int circle_restrict = 0; // level of game restriction +bool enable_world_checksum = false; // enable world checksum calculation RoomRnum r_mortal_start_room; // rnum of mortal start room RoomRnum r_immort_start_room; // rnum of immort start room RoomRnum r_frozen_start_room; // rnum of frozen start room @@ -127,7 +142,7 @@ const FlagData clear_flags; const char *ZONE_TRAFFIC_FILE = LIB_PLRSTUFF"zone_traffic.xml"; time_t zones_stat_date; -GameLoader world_loader; +GameLoader game_loader; // local functions void LoadGlobalUid(); @@ -147,6 +162,7 @@ void ResetGameWorldTime(); int CountMobsInRoom(int m_num, int r_num); void SetZoneRnumForObjects(); void SetZoneRnumForMobiles(); +void SetZoneRnumForTriggers(); void InitBasicValues(); void LoadMessages(); int CompareSocials(const void *a, const void *b); @@ -347,28 +363,43 @@ void ConvertObjValues() { } } -void GameLoader::BootWorld() { +void GameLoader::BootWorld(std::unique_ptr data_source) { utils::CSteppedProfiler boot_profiler("World booting", 1.1); + // Create default data source if none provided + if (!data_source) + { +#ifdef HAVE_YAML + data_source = world_loader::CreateYamlDataSource("world"); +#elif defined(HAVE_SQLITE) + data_source = world_loader::CreateSqliteDataSource("world.db"); +#else + data_source = world_loader::CreateLegacyDataSource(); +#endif + } + log("Using data source: %s", data_source->GetName().c_str()); + + // Register data source in manager for OLC access + auto* ds_ptr = data_source.get(); + world_loader::WorldDataSourceManager::Instance().SetDataSource(std::move(data_source)); + boot_profiler.next_step("Loading zone table"); - log("Loading zone table."); - GameLoader::BootIndex(DB_BOOT_ZON); + ds_ptr->LoadZones(); boot_profiler.next_step("Create blank zoness for dungeons"); log("Create zones for dungeons."); dungeons::CreateBlankZoneDungeon(); boot_profiler.next_step("Loading triggers"); - log("Loading triggers and generating index."); - GameLoader::BootIndex(DB_BOOT_TRG); + ds_ptr->LoadTriggers(); boot_profiler.next_step("Create blank triggers for dungeons"); log("Create triggers for dungeons."); dungeons::CreateBlankTrigsDungeon(); boot_profiler.next_step("Loading rooms"); - log("Loading rooms."); - GameLoader::BootIndex(DB_BOOT_WLD); + ds_ptr->LoadRooms(); + boot_profiler.next_step("Create blank rooms for dungeons"); log("Create blank rooms for dungeons."); @@ -391,8 +422,7 @@ void GameLoader::BootWorld() { CheckStartRooms(); boot_profiler.next_step("Loading mobs and regerating index"); - log("Loading mobs and generating index."); - GameLoader::BootIndex(DB_BOOT_MOB); + ds_ptr->LoadMobs(); boot_profiler.next_step("Counting mob's levels"); log("Count mob quantity by level"); @@ -407,8 +437,7 @@ void GameLoader::BootWorld() { // CalculateFirstAndLastMobs(); boot_profiler.next_step("Loading objects"); - log("Loading objs and generating index."); - GameLoader::BootIndex(DB_BOOT_OBJ); + ds_ptr->LoadObjects(); boot_profiler.next_step("Create blank obj for dungeons"); log("Create blank obj for dungeons."); @@ -434,11 +463,36 @@ void GameLoader::BootWorld() { log("Renumbering Mob_zone."); SetZoneRnumForMobiles(); + boot_profiler.next_step("Calculating trigger locations for zones"); + log("Calculating trigger locations for zones."); + SetZoneRnumForTriggers(); + boot_profiler.next_step("Initialization of object rnums"); log("Init system_obj rnums."); system_obj::init(); log("Init global_drop_obj."); + + if (enable_world_checksum) + { + boot_profiler.next_step("Calculating world checksums"); + log("Calculating world checksums..."); + auto checksums = WorldChecksum::Calculate(); + WorldChecksum::LogResult(checksums); + WorldChecksum::SaveDetailedChecksums("checksums_detailed.txt", checksums); + if (!WorldChecksum::SaveDetailedBuffers("checksums_buffers")) + { + log("WARNING: Failed to save detailed buffers (see errors above)"); + } + + // If BASELINE_DIR is set, compare with baseline checksums + const char *baseline_dir = getenv("BASELINE_DIR"); + if (baseline_dir) + { + log("Comparing with baseline from: %s", baseline_dir); + WorldChecksum::CompareWithBaseline(baseline_dir); + } + } } void InitZoneTypes() { @@ -694,7 +748,7 @@ void zone_traffic_load() { ZoneRnum zrn; zrn = GetZoneRnum(zone_vnum); int num = atoi(node.attribute("traffic").value()); - if (zrn == 0 && zone_vnum != 1) { + if (zrn == kNoZone) { snprintf(buf, kMaxStringLength, "zone_traffic: несуществующий номер зоны %d ее траффик %d ", zone_vnum, num); @@ -707,6 +761,7 @@ void zone_traffic_load() { // body of the booting system void BootMudDataBase() { + auto boot_start = std::chrono::high_resolution_clock::now(); utils::CSteppedProfiler boot_profiler("MUD booting", 1.1); log("Boot db -- BEGIN."); @@ -990,6 +1045,12 @@ void BootMudDataBase() { } reset_q.head = reset_q.tail = nullptr; + + // Assign room triggers AFTER zone reset completes + boot_profiler.next_step("Assigning triggers to rooms"); + world_loader::WorldDataSourceBase::AssignTriggersToLoadedRooms(); + + // делается после резета зон, см камент к функции boot_profiler.next_step("Loading depot chests"); log("Load depot chests."); @@ -1093,6 +1154,10 @@ void BootMudDataBase() { shutdown_parameters.mark_boot_time(); log("Boot db -- DONE."); + auto boot_end = std::chrono::high_resolution_clock::now(); + auto boot_duration = std::chrono::duration(boot_end - boot_start).count(); + log("Boot db total time: %.3f seconds", boot_duration); + } // reset the time in the game from file @@ -1339,7 +1404,8 @@ void CalculateFirstAndLastRooms() { } zone_table[zrn].RnumRoomsLocation.first = zone_table[zrn - 1].RnumRoomsLocation.second + 1; zone_table[zrn].RnumRoomsLocation.second = rn - 1; - for (auto &zone_data : zone_table) { + for (size_t i = 0; i < zone_table.size(); ++i) { + auto &zone_data = zone_table[i]; zone_data.RnumRoomsLocation.second--; //уберем виртуалки if (zone_data.entrance == 0) { //если в зонфайле не указана стартовая комната log("Отсутствует стартовая комната для зоны %d", zone_data.vnum); @@ -1376,7 +1442,7 @@ void AddVirtualRoomsToAllZones() { new_room->zone_rn = rnum; new_room->vnum = last_room; new_room->set_name(std::string("Виртуальная комната")); - new_room->description_num = RoomDescription::add_desc(std::string("Похоже, здесь вам делать нечего.")); + new_room->description_num = GlobalObjects::descriptions().add(std::string("Похоже, здесь вам делать нечего.")); new_room->clear_flags(); new_room->sector_type = ESector::kSecret; @@ -1417,12 +1483,45 @@ void SetZoneRnumForMobiles() { } } +void SetZoneRnumForTriggers() { + // Calculate RnumTrigsLocation (first and last trigger rnum) for each zone + // This works for all data source formats (Legacy, YAML, SQLite) + + // First pass: find first and last trigger for each zone + // NOTE: top_of_trigt is the COUNT, not the last index (unlike top_of_mobt) + for (int i = 0; i < top_of_trigt; ++i) { + if (!trig_index[i]) { + continue; + } + + int trig_vnum = trig_index[i]->vnum; + ZoneVnum zone_vnum = trig_vnum / 100; + ZoneRnum zone_rnum = GetZoneRnum(zone_vnum); + + if (zone_rnum == kNoZone) { + log("FATAL: Trigger %d belongs to non-existent zone %d (zone_table has %zu zones)", + trig_vnum, zone_vnum, zone_table.size()); + log("FATAL: This indicates broken world data or initialization order bug."); + log("FATAL: Boot aborted. Triggers cannot function without valid zone assignment."); + exit(1); + } + + // Set first trigger for this zone (if not set yet) + if (zone_table[zone_rnum].RnumTrigsLocation.first == -1) { + zone_table[zone_rnum].RnumTrigsLocation.first = i; + } + + // Always update last trigger for this zone + zone_table[zone_rnum].RnumTrigsLocation.second = i; + } +} + void ResolveZoneCmdVnumArgsToRnums(ZoneData &zone_data) { int cmd_no, a, b, c, olda, oldb, oldc; char local_buf[128]; int i; for (i = 0; i < zone_data.typeA_count; i++) { - if (GetZoneRnum(zone_data.typeA_list[i]) == 0) { + if (GetZoneRnum(zone_data.typeA_list[i]) == kNoZone) { sprintf(local_buf, "SYSERROR: некорректное значение в typeA (%d) для зоны: %d", zone_data.typeA_list[i], @@ -1431,7 +1530,7 @@ void ResolveZoneCmdVnumArgsToRnums(ZoneData &zone_data) { } } for (i = 0; i < zone_data.typeB_count; i++) { - if (GetZoneRnum(zone_data.typeB_list[i]) == 0) { + if (GetZoneRnum(zone_data.typeB_list[i]) == kNoZone) { sprintf(local_buf, "SYSERROR: некорректное значение в typeB (%d) для зоны: %d", zone_data.typeB_list[i], @@ -2903,12 +3002,14 @@ ZoneRnum GetZoneRnum(ZoneVnum vnum) { bot = 0; top = static_cast(zone_table.size() - 1); + for (;;) { mid = (bot + top) / 2; + if (zone_table[mid].vnum == vnum) return (mid); if (bot >= top) - return (kNowhere); + return (kNoZone); if (zone_table[mid].vnum > vnum) top = mid - 1; else diff --git a/src/engine/db/db.h b/src/engine/db/db.h index 6e040aeb6..16b38cc29 100644 --- a/src/engine/db/db.h +++ b/src/engine/db/db.h @@ -15,6 +15,8 @@ #ifndef DB_H_ #define DB_H_ +#include "world_data_source.h" + #include "engine/boot/boot_constants.h" #include "engine/core/conf.h" // to get definition of build type: (CIRCLE_AMIGA|CIRCLE_UNIX|CIRCLE_WINDOWS|CIRCLE_ACORN|CIRCLE_VMS) #include "administration/name_adviser.h" @@ -162,7 +164,7 @@ class UniqueList: private std::list { private: using base_t = std::list; public: - using iterator = base_t::iterator; + using iterator = typename base_t::iterator; void push_back(const T& value) { if (std::find(base_t::begin(), base_t::end(), value) == base_t::end()) { base_t::push_back(value); @@ -215,14 +217,14 @@ class GameLoader { public: GameLoader() = default; - static void BootWorld(); + static void BootWorld(std::unique_ptr data_source = nullptr); static void BootIndex(EBootType mode); private: static void PrepareGlobalStructures(const EBootType mode, const int rec_count); }; -extern GameLoader world_loader; +extern GameLoader game_loader; #endif // DB_H_ diff --git a/src/engine/db/description.cpp b/src/engine/db/description.cpp index 27a406392..d999a6174 100644 --- a/src/engine/db/description.cpp +++ b/src/engine/db/description.cpp @@ -6,40 +6,98 @@ #include "utils/logger.h" -std::vector RoomDescription::_desc_list; -RoomDescription::reboot_map_t RoomDescription::_reboot_map; - -/** -* добавление описания в массив с проверкой на уникальность -* \param text - описание комнаты -* \return номер описания в глобальном массиве -*/ -size_t RoomDescription::add_desc(const std::string &text) { - reboot_map_t::const_iterator it = _reboot_map.find(text); - if (it != _reboot_map.end()) { +// ========== LocalDescriptionIndex ========== + +size_t LocalDescriptionIndex::add(const std::string &text) +{ + // Check if we already have this description + auto it = _desc_map.find(text); + if (it != _desc_map.end()) + { + // Duplicate found -> return existing index return it->second; - } else { + } + else + { + // New description -> add to index (1-based for compatibility with Legacy) _desc_list.push_back(text); - _reboot_map[text] = _desc_list.size(); - return _desc_list.size(); + size_t idx = _desc_list.size(); // 1-based! (0 = no description) + _desc_map[text] = idx; + return idx; + } +} + +const std::string &LocalDescriptionIndex::get(size_t idx) const +{ + static const std::string empty_string = ""; + if (idx == 0) + { + return empty_string; // 0 means "no description" + } + try + { + return _desc_list.at(idx - 1); // Convert 1-based to 0-based + } + catch (const std::out_of_range &) + { + log("SYSERR: bad local description index %zd (size=%zd)", idx, _desc_list.size()); + return empty_string; } } -const static std::string empty_string = ""; +// ========== RoomDescriptions ========== + +size_t RoomDescriptions::add(const std::string &text) +{ + // Check if we already have this description + auto it = _desc_map.find(text); + if (it != _desc_map.end()) + { + // Duplicate found -> return existing index + return it->second; + } + else + { + // New description -> add to global index (1-based for compatibility with Legacy) + _desc_list.push_back(text); + size_t idx = _desc_list.size(); // 1-based! (0 = no description) + _desc_map[text] = idx; + return idx; + } +} -/** -* поиск описания по его порядковому номеру в массиве -* \param desc_num - порядковый номер описания (descripton_num в room_data) -* \return строка описания или пустая строка в случае невалидного номера -*/ -const std::string &RoomDescription::show_desc(size_t desc_num) { - try { - return _desc_list.at(--desc_num); +const std::string &RoomDescriptions::get(size_t idx) const +{ + static const std::string empty_string = ""; + if (idx == 0) + { + return empty_string; // 0 means "no description" + } + try + { + return _desc_list.at(idx - 1); // Convert 1-based to 0-based } - catch (const std::out_of_range &) { - log("SYSERROR : bad room description num '%zd' (%s %s %d)", desc_num, __FILE__, __func__, __LINE__); + catch (const std::out_of_range &) + { + log("SYSERR: bad room description index %zd (size=%zd)", idx, _desc_list.size()); return empty_string; } } +std::vector RoomDescriptions::merge(const LocalDescriptionIndex &local_index) +{ + // Create mapping from local description indices to global indices + std::vector local_to_global; + local_to_global.reserve(local_index.size()); + + for (size_t local_idx = 0; local_idx < local_index.size(); ++local_idx) + { + const std::string &desc = local_index.get(local_idx + 1); // Convert 0-based loop to 1-based index + size_t global_idx = add(desc); // Deduplicates automatically! + local_to_global.push_back(global_idx); + } + + return local_to_global; +} + // vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/description.h b/src/engine/db/description.h index 28a3440e7..18c9551a0 100644 --- a/src/engine/db/description.h +++ b/src/engine/db/description.h @@ -11,30 +11,60 @@ #include /** -* глобальный класс-контейнер уникальных описаний комнат, тупо экономит 70+мб памяти. -* действия: грузим описания комнат, попутно отсекая дубликаты. в комнату пишется -* не само описание, а его порядковый номер в глобальном массиве этих описаний. -* если у вас возникнет мысль делать тоже самое построчно, то не мучайтесь, -* т.к. вы ничего на этом не сэкономите, по крайней мере с текущим форматом зон. -* при редактировании в олц старые описания остаются в массиве, т.к. это все херня - -* \todo В последствии нужно переместить в class room, а потом вообще убрать из кода, -* т.к. есть куда более прикольная тема с шаблонами в файлах зон. -*/ -class RoomDescription { - public: - static size_t add_desc(const std::string &text); - static const std::string &show_desc(size_t desc_num); - - private: - RoomDescription(); - ~RoomDescription(); - // отсюда дергаем описания при работе мада - static std::vector _desc_list; - // а это чтобы мад не грузился пол часа. из-за оптимизации копирования строк мап - // проще оставлять на все время работы мада для олц, а мож и дальнейшего релоада описаний - typedef std::map reboot_map_t; - static reboot_map_t _reboot_map; + * LocalDescriptionIndex - Thread-local description index for parallel room loading. + * Used by worker threads to collect room descriptions without race conditions. + */ +class LocalDescriptionIndex { +public: + LocalDescriptionIndex() = default; + ~LocalDescriptionIndex() = default; + + // Add description to local index. Returns 0-based index. + size_t add(const std::string &text); + + // Get description by 0-based index. + const std::string &get(size_t idx) const; + + // Number of descriptions in index. + size_t size() const { return _desc_list.size(); } + + // Direct access to descriptions (for merge). + const std::vector &descriptions() const { return _desc_list; } + +private: + std::vector _desc_list; + std::map _desc_map; +}; + +/** + * RoomDescriptions - Global room description storage. + * Maintains unique descriptions in GlobalObjects to save memory. + * + * Saves ~50% memory on room descriptions (>70K rooms in full MUD). + * Each room stores only description index (0-based) instead of full text. + */ +class RoomDescriptions { +public: + RoomDescriptions() = default; + ~RoomDescriptions() = default; + + // Add description to global index. Returns 0-based index. + // If description already exists, returns existing index. + size_t add(const std::string &text); + + // Get description by 0-based index. + const std::string &get(size_t idx) const; + + // Number of descriptions in index. + size_t size() const { return _desc_list.size(); } + + // Merge thread-local descriptions into global index. + // Returns mapping from local indices to global indices. + std::vector merge(const LocalDescriptionIndex &local_index); + +private: + std::vector _desc_list; + std::map _desc_map; }; #endif // _DESCRIPTION_H_INCLUDED diff --git a/src/engine/db/dictionary_loader.cpp b/src/engine/db/dictionary_loader.cpp new file mode 100644 index 000000000..516139658 --- /dev/null +++ b/src/engine/db/dictionary_loader.cpp @@ -0,0 +1,191 @@ +// Part of Bylins http://www.mud.ru +// Dictionary loader implementation + +#ifdef HAVE_YAML + +#include "dictionary_loader.h" +#include "utils/logger.h" + +#include +#include + +namespace world_loader +{ + +// ============================================================================ +// Dictionary implementation +// ============================================================================ + +Dictionary::Dictionary(const std::string &name) + : m_name(name) +{ +} + +bool Dictionary::Load(const std::string &filepath) +{ + try + { + YAML::Node root = YAML::LoadFile(filepath); + if (!root.IsMap()) + { + log("SYSERR: Dictionary file '%s' is not a YAML map", filepath.c_str()); + return false; + } + + m_entries.clear(); + for (const auto &pair : root) + { + std::string key = pair.first.as(); + long value = pair.second.as(); + m_entries[key] = value; + } + + return true; + } + catch (const YAML::Exception &e) + { + log("SYSERR: Failed to load dictionary '%s': %s", filepath.c_str(), e.what()); + return false; + } + catch (const std::exception &e) + { + log("SYSERR: Failed to load dictionary '%s': %s", filepath.c_str(), e.what()); + return false; + } +} + +long Dictionary::Lookup(const std::string &name, long default_val) const +{ + auto it = m_entries.find(name); + if (it != m_entries.end()) + { + return it->second; + } + return default_val; +} + +bool Dictionary::Contains(const std::string &name) const +{ + return m_entries.find(name) != m_entries.end(); +} + +// ============================================================================ +// DictionaryManager implementation +// ============================================================================ + +DictionaryManager &DictionaryManager::Instance() +{ + static DictionaryManager instance; + return instance; +} + +bool DictionaryManager::LoadDictionaries(const std::string &dict_dir) +{ + namespace fs = std::filesystem; + + if (!fs::exists(dict_dir) || !fs::is_directory(dict_dir)) + { + log("SYSERR: Dictionary directory not found: %s", dict_dir.c_str()); + return false; + } + + log("Loading dictionaries from: %s", dict_dir.c_str()); + + int loaded_count = 0; + int error_count = 0; + + for (const auto &entry : fs::directory_iterator(dict_dir)) + { + if (!entry.is_regular_file()) + { + continue; + } + + std::string filename = entry.path().filename().string(); + if (filename.size() < 6 || filename.substr(filename.size() - 5) != ".yaml") + { + continue; + } + + // Extract dictionary name from filename (remove .yaml extension) + std::string dict_name = filename.substr(0, filename.size() - 5); + + auto dict = std::make_unique(dict_name); + if (dict->Load(entry.path().string())) + { + log(" Loaded dictionary '%s' with %zu entries", + dict_name.c_str(), dict->GetEntries().size()); + m_dictionaries[dict_name] = std::move(dict); + loaded_count++; + } + else + { + error_count++; + } + } + + if (error_count > 0) + { + log("SYSERR: %d dictionary files failed to load", error_count); + } + + m_loaded = (loaded_count > 0); + log("Loaded %d dictionaries", loaded_count); + + return m_loaded; +} + +const Dictionary *DictionaryManager::GetDictionary(const std::string &name) const +{ + auto it = m_dictionaries.find(name); + if (it != m_dictionaries.end()) + { + return it->second.get(); + } + return nullptr; +} + +long DictionaryManager::Lookup(const std::string &dict_name, const std::string &entry_name, long default_val) const +{ + const Dictionary *dict = GetDictionary(dict_name); + if (!dict) + { + return default_val; + } + return dict->Lookup(entry_name, default_val); +} + +std::vector DictionaryManager::LookupAll(const std::string &dict_name, const std::vector &names) const +{ + std::vector result; + result.reserve(names.size()); + + const Dictionary *dict = GetDictionary(dict_name); + if (!dict) + { + return result; + } + + for (const auto &name : names) + { + long val = dict->Lookup(name, -1); + if (val >= 0) + { + result.push_back(val); + } + } + + return result; +} + +void DictionaryManager::Clear() +{ + m_dictionaries.clear(); + m_loaded = false; +} + +} // namespace world_loader + +#endif // HAVE_YAML + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/dictionary_loader.h b/src/engine/db/dictionary_loader.h new file mode 100644 index 000000000..012c3cd1e --- /dev/null +++ b/src/engine/db/dictionary_loader.h @@ -0,0 +1,92 @@ +// Part of Bylins http://www.mud.ru +// Dictionary loader for YAML world data source +// Maps string names (like "kForest") to numeric enum values + +#ifndef DICTIONARY_LOADER_H_ +#define DICTIONARY_LOADER_H_ + +#ifdef HAVE_YAML + +#include +#include +#include +#include + +namespace world_loader +{ + +// Single dictionary that maps string names to numeric values +class Dictionary +{ +public: + Dictionary() = default; + explicit Dictionary(const std::string &name); + + // Load dictionary from YAML file + bool Load(const std::string &filepath); + + // Lookup a single value by name + // Returns default_val if not found + long Lookup(const std::string &name, long default_val = -1) const; + + // Check if name exists in dictionary + bool Contains(const std::string &name) const; + + // Get dictionary name (for error messages) + const std::string &GetName() const { return m_name; } + + // Get all entries (for debugging) + const std::unordered_map &GetEntries() const { return m_entries; } + +private: + std::string m_name; + std::unordered_map m_entries; +}; + +// Manager for all dictionaries +class DictionaryManager +{ +public: + // Singleton access + static DictionaryManager &Instance(); + + // Load all dictionaries from directory + // Returns true if all required dictionaries loaded successfully + bool LoadDictionaries(const std::string &dict_dir); + + // Check if dictionaries are loaded + bool IsLoaded() const { return m_loaded; } + + // Get a specific dictionary by name + const Dictionary *GetDictionary(const std::string &name) const; + + // Universal lookup: "dict_name", "entry_name" -> value + // Returns default_val if dictionary or entry not found + long Lookup(const std::string &dict_name, const std::string &entry_name, long default_val = -1) const; + + // Lookup all names from a list and return their values + // Useful for converting flag lists + std::vector LookupAll(const std::string &dict_name, const std::vector &names) const; + + // Clear all loaded dictionaries + void Clear(); + +private: + DictionaryManager() = default; + ~DictionaryManager() = default; + + // Prevent copying + DictionaryManager(const DictionaryManager &) = delete; + DictionaryManager &operator=(const DictionaryManager &) = delete; + + bool m_loaded = false; + std::unordered_map> m_dictionaries; +}; + +} // namespace world_loader + +#endif // HAVE_YAML + +#endif // DICTIONARY_LOADER_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/global_objects.cpp b/src/engine/db/global_objects.cpp index 4c27d6c6b..8f95fdc06 100644 --- a/src/engine/db/global_objects.cpp +++ b/src/engine/db/global_objects.cpp @@ -51,6 +51,7 @@ struct GlobalObjectsStorage { DailyQuest::DailyQuestMap daily_quests; Strengthening strengthening; obj2triggers_t obj2triggers; + RoomDescriptions room_descriptions; }; GlobalObjectsStorage::GlobalObjectsStorage() : @@ -250,4 +251,8 @@ obj2triggers_t &GlobalObjects::obj_triggers() { return global_objects().obj2triggers; } +RoomDescriptions &GlobalObjects::descriptions() { + return global_objects().room_descriptions; +} + // vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/global_objects.h b/src/engine/db/global_objects.h index dc76f1406..37dafacb2 100644 --- a/src/engine/db/global_objects.h +++ b/src/engine/db/global_objects.h @@ -27,6 +27,7 @@ #include "engine/ui/cmd_god/do_set_all.h" #include "gameplay/classes/mob_classes_info.h" #include "engine/db/player_index.h" +#include "engine/db/description.h" class BanList; // to avoid inclusion of ban.hpp @@ -90,6 +91,7 @@ class GlobalObjects { static DailyQuest::DailyQuestMap &daily_quests(); static Strengthening &strengthening(); static obj2triggers_t &obj_triggers(); + static RoomDescriptions &descriptions(); }; using MUD = GlobalObjects; diff --git a/src/engine/db/help.cpp b/src/engine/db/help.cpp index 01a3061e1..c203af201 100644 --- a/src/engine/db/help.cpp +++ b/src/engine/db/help.cpp @@ -1297,7 +1297,7 @@ void check_update_dynamic() { void reload(Flags flag) { switch (flag) { case STATIC: static_help.clear(); - world_loader.BootIndex(DB_BOOT_HLP); + game_loader.BootIndex(DB_BOOT_HLP); init_group_zones(); init_zone_all(); ClassRecipiesHelp(); diff --git a/src/engine/db/influxdb.cpp b/src/engine/db/influxdb.cpp index e7fedd7ec..3ca21092b 100644 --- a/src/engine/db/influxdb.cpp +++ b/src/engine/db/influxdb.cpp @@ -2,6 +2,8 @@ #include "utils/logger.h" +#include + #ifndef WIN32 #include #include diff --git a/src/engine/db/legacy_world_data_source.cpp b/src/engine/db/legacy_world_data_source.cpp new file mode 100644 index 000000000..bf5fad8fb --- /dev/null +++ b/src/engine/db/legacy_world_data_source.cpp @@ -0,0 +1,85 @@ +// Part of Bylins http://www.mud.ru +// Legacy world data source implementation + +#include "legacy_world_data_source.h" +#include "db.h" +#include "obj_prototypes.h" +#include "utils/logger.h" + +// Forward declarations for OLC save functions +void zedit_save_to_disk(int zone_rnum); +void redit_save_to_disk(int zone_rnum); +void medit_save_to_disk(int zone_rnum); +void oedit_save_to_disk(int zone_rnum); +bool trigedit_save_to_disk(int zone_rnum, int notify_level); + +namespace world_loader +{ + +void LegacyWorldDataSource::LoadZones() +{ + log("Loading zone table."); + GameLoader::BootIndex(DB_BOOT_ZON); +} + +void LegacyWorldDataSource::LoadTriggers() +{ + log("Loading triggers and generating index."); + GameLoader::BootIndex(DB_BOOT_TRG); +} + +void LegacyWorldDataSource::LoadRooms() +{ + log("Loading rooms."); + GameLoader::BootIndex(DB_BOOT_WLD); +} + +void LegacyWorldDataSource::LoadMobs() +{ + log("Loading mobs and generating index."); + GameLoader::BootIndex(DB_BOOT_MOB); +} + +void LegacyWorldDataSource::LoadObjects() +{ + log("Loading objs and generating index."); + GameLoader::BootIndex(DB_BOOT_OBJ); +} + +void LegacyWorldDataSource::SaveZone(int zone_rnum) +{ + zedit_save_to_disk(zone_rnum); +} + +bool LegacyWorldDataSource::SaveTriggers(int zone_rnum, int specific_vnum, int notify_level) +{ + (void)specific_vnum; // Legacy format always saves entire zone + return trigedit_save_to_disk(zone_rnum, notify_level); +} + +void LegacyWorldDataSource::SaveRooms(int zone_rnum, int specific_vnum) +{ + (void)specific_vnum; // Legacy format always saves entire zone + redit_save_to_disk(zone_rnum); +} + +void LegacyWorldDataSource::SaveMobs(int zone_rnum, int specific_vnum) +{ + (void)specific_vnum; // Legacy format always saves entire zone + medit_save_to_disk(zone_rnum); +} + +void LegacyWorldDataSource::SaveObjects(int zone_rnum, int specific_vnum) +{ + (void)specific_vnum; // Legacy format always saves entire zone + oedit_save_to_disk(zone_rnum); +} + +std::unique_ptr CreateLegacyDataSource() +{ + return std::make_unique(); +} + +} // namespace world_loader + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/legacy_world_data_source.h b/src/engine/db/legacy_world_data_source.h new file mode 100644 index 000000000..27b00e3ce --- /dev/null +++ b/src/engine/db/legacy_world_data_source.h @@ -0,0 +1,42 @@ +// Part of Bylins http://www.mud.ru +// Legacy world data source - wraps existing file-based loading + +#ifndef LEGACY_WORLD_DATA_SOURCE_H_ +#define LEGACY_WORLD_DATA_SOURCE_H_ + +#include "world_data_source.h" + +namespace world_loader +{ + +// Legacy implementation that uses the existing file-based loading +// This wraps the current BootIndex() calls to provide the IWorldDataSource interface +class LegacyWorldDataSource : public IWorldDataSource +{ +public: + LegacyWorldDataSource() = default; + ~LegacyWorldDataSource() override = default; + + std::string GetName() const override { return "Legacy file-based loader"; } + + void LoadZones() override; + void LoadTriggers() override; + void LoadRooms() override; + void LoadMobs() override; + void LoadObjects() override; + + void SaveZone(int zone_rnum) override; + bool SaveTriggers(int zone_rnum, int specific_vnum, int notify_level) override; + void SaveRooms(int zone_rnum, int specific_vnum = -1) override; + void SaveMobs(int zone_rnum, int specific_vnum = -1) override; + void SaveObjects(int zone_rnum, int specific_vnum = -1) override; +}; + +// Factory function for creating legacy data source +std::unique_ptr CreateLegacyDataSource(); + +} // namespace world_loader + +#endif // LEGACY_WORLD_DATA_SOURCE_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/null_world_data_source.h b/src/engine/db/null_world_data_source.h new file mode 100644 index 000000000..b386a7bfd --- /dev/null +++ b/src/engine/db/null_world_data_source.h @@ -0,0 +1,38 @@ +#ifndef BYLINS_SRC_ENGINE_DB_NULL_WORLD_DATA_SOURCE_H_ +#define BYLINS_SRC_ENGINE_DB_NULL_WORLD_DATA_SOURCE_H_ + +#include "world_data_source.h" + +namespace world_loader { + +/** + * \brief Null Object implementation of IWorldDataSource. + * + * Used as a default fallback when no real data source is available. + * All operations are no-ops. + */ +class NullWorldDataSource : public IWorldDataSource { +public: + NullWorldDataSource() = default; + ~NullWorldDataSource() override = default; + + std::string GetName() const override { return "NullDataSource"; } + + void LoadZones() override {} + void LoadTriggers() override {} + void LoadRooms() override {} + void LoadMobs() override {} + void LoadObjects() override {} + + void SaveZone(int zone_rnum) override { (void)zone_rnum; } + void SaveTriggers(int zone_rnum, int specific_vnum = -1) override { (void)zone_rnum; (void)specific_vnum; } + void SaveRooms(int zone_rnum, int specific_vnum = -1) override { (void)zone_rnum; (void)specific_vnum; } + void SaveMobs(int zone_rnum, int specific_vnum = -1) override { (void)zone_rnum; (void)specific_vnum; } + void SaveObjects(int zone_rnum, int specific_vnum = -1) override { (void)zone_rnum; (void)specific_vnum; } +}; + +} // namespace world_loader + +#endif // BYLINS_SRC_ENGINE_DB_NULL_WORLD_DATA_SOURCE_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/obj_save.cpp b/src/engine/db/obj_save.cpp index 0cced98b8..8043ae1b6 100644 --- a/src/engine/db/obj_save.cpp +++ b/src/engine/db/obj_save.cpp @@ -1164,11 +1164,13 @@ int Crash_write_timer(const std::size_t index) { fwrite(&(SAVEINFO(index)->time[i]), sizeof(SaveTimeInfo), 1, fl); } fclose(fl); +#ifndef _WIN32 if (chmod(fname, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << fname << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } +#endif FileCRC::check_crc(fname, FileCRC::UPDATE_TIMEOBJS, player_table[index].uid()); return true; } @@ -1968,11 +1970,13 @@ int save_char_objects(CharData *ch, int savetype, int rentcost) { write_buffer << "\n$\n$\n"; file << write_buffer.rdbuf(); file.close(); +#ifndef _WIN32 if (chmod(fname, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << fname << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } +#endif FileCRC::check_crc(fname, FileCRC::UPDATE_TEXTOBJS, ch->get_uid()); } else { Crash_delete_files(iplayer); diff --git a/src/engine/db/sqlite_world_data_source.cpp b/src/engine/db/sqlite_world_data_source.cpp new file mode 100644 index 000000000..03351e64a --- /dev/null +++ b/src/engine/db/sqlite_world_data_source.cpp @@ -0,0 +1,3351 @@ +// Part of Bylins http://www.mud.ru +// SQLite world data source implementation + +#ifdef HAVE_SQLITE + +#include "sqlite_world_data_source.h" +#include "db.h" +#include "obj_prototypes.h" +#include "utils/logger.h" +#include "utils/utils.h" +#include "utils/utils_string.h" +#include "engine/entities/zone.h" +#include "engine/entities/room_data.h" +#include "engine/entities/char_data.h" +#include "engine/entities/entities_constants.h" +#include "engine/scripting/dg_scripts.h" +#include "engine/db/description.h" +#include "global_objects.h" +#include "engine/structs/extra_description.h" +#include "gameplay/mechanics/dungeons.h" +#include "engine/scripting/dg_olc.h" +#include "gameplay/affects/affect_contants.h" +#include "gameplay/skills/skills.h" + +#include +#include +#include +#include + +// External declarations +extern ZoneTable &zone_table; +extern IndexData **trig_index; +extern int top_of_trigt; +extern Rooms &world; +extern RoomRnum top_of_world; +extern IndexData *mob_index; +extern MobRnum top_of_mobt; +extern CharData *mob_proto; + +namespace world_loader +{ + +// ============================================================================ +// Flag name to value mappings +// ============================================================================ + +// Room flags mapping +static std::unordered_map room_flag_map = { + {"kDarked", ERoomFlag::kDarked}, + {"kDeathTrap", ERoomFlag::kDeathTrap}, + {"kNoMob", ERoomFlag::kNoEntryMob}, + {"kNoEntryMob", ERoomFlag::kNoEntryMob}, + {"kIndoors", ERoomFlag::kIndoors}, + {"kPeaceful", ERoomFlag::kPeaceful}, + {"kPeaceFul", ERoomFlag::kPeaceful}, + {"kSoundproof", ERoomFlag::kSoundproof}, + {"kNoTrack", ERoomFlag::kNoTrack}, + {"kNoMagic", ERoomFlag::kNoMagic}, + {"kTunnel", ERoomFlag::kTunnel}, + {"kNoTeleportIn", ERoomFlag::kNoTeleportIn}, + {"kGodRoom", ERoomFlag::kGodsRoom}, + {"kGodsRoom", ERoomFlag::kGodsRoom}, + {"kHouse", ERoomFlag::kHouse}, + {"kHouseCrash", ERoomFlag::kHouseCrash}, + {"kHouseEntry", ERoomFlag::kHouseEntry}, + {"kBfsMark", ERoomFlag::kBfsMark}, + {"kForMages", ERoomFlag::kForMages}, + {"kForSorcerers", ERoomFlag::kForSorcerers}, + {"kForThieves", ERoomFlag::kForThieves}, + {"kForWarriors", ERoomFlag::kForWarriors}, + {"kForAssasines", ERoomFlag::kForAssasines}, + {"kForGuards", ERoomFlag::kForGuards}, + {"kForPaladines", ERoomFlag::kForPaladines}, + {"kForRangers", ERoomFlag::kForRangers}, + {"kForPoly", ERoomFlag::kForPoly}, + {"kForMono", ERoomFlag::kForMono}, + {"kForge", ERoomFlag::kForge}, + {"kForMerchants", ERoomFlag::kForMerchants}, + {"kForMaguses", ERoomFlag::kForMaguses}, + {"kArena", ERoomFlag::kArena}, + {"kNoSummonOut", ERoomFlag::kNoSummonOut}, + {"kNoSummon", ERoomFlag::kNoSummonOut}, + {"kNoTeleportOut", ERoomFlag::kNoTeleportOut}, + {"kNohorse", ERoomFlag::kNohorse}, + {"kNoWeather", ERoomFlag::kNoWeather}, + {"kSlowDeathTrap", ERoomFlag::kSlowDeathTrap}, + {"kIceTrap", ERoomFlag::kIceTrap}, + {"kNoRelocateIn", ERoomFlag::kNoRelocateIn}, + {"kTribune", ERoomFlag::kTribune}, + {"kArenaSend", ERoomFlag::kArenaSend}, + {"kNoBattle", ERoomFlag::kNoBattle}, + {"kAlwaysLit", ERoomFlag::kAlwaysLit}, + {"kMoMapper", ERoomFlag::kMoMapper}, + {"kNoItem", ERoomFlag::kNoItem}, + {"kDominationArena", ERoomFlag::kDominationArena}, + // Additional aliases from database + {"kNoPk", ERoomFlag::kPeaceful}, + {"kExchangeRoom", ERoomFlag::kHouse}, + {"kNoBirdArrival", ERoomFlag::kNoTeleportIn}, + {"kMoOnlyRoom", ERoomFlag::kGodsRoom}, + {"kDeathIceTrap", ERoomFlag::kIceTrap}, + {"kForestTrap", ERoomFlag::kSlowDeathTrap}, + {"kArenaTrap", ERoomFlag::kArena}, + {"kNoArmor", ERoomFlag::kForge}, + {"kAtrium", ERoomFlag::kHouseEntry}, + {"kAutoQuest", ERoomFlag::kBfsMark}, + {"kDecayedDeathTrap", ERoomFlag::kDeathTrap}, + {"kHoleInTheSky", ERoomFlag::kNoTeleportIn}, +}; + +// Mob action flags mapping +static std::unordered_map mob_action_flag_map = { + {"kSpec", EMobFlag::kSpec}, + {"kSentinel", EMobFlag::kSentinel}, + {"kScavenger", EMobFlag::kScavenger}, + {"kIsNpc", EMobFlag::kNpc}, + {"kNpc", EMobFlag::kNpc}, + {"kAware", EMobFlag::kAware}, + {"kAggressive", EMobFlag::kAgressive}, + {"kAgressive", EMobFlag::kAgressive}, + {"kStayZone", EMobFlag::kStayZone}, + {"kWimpy", EMobFlag::kWimpy}, + {"kAgressiveDay", EMobFlag::kAgressiveDay}, + {"kAggressiveNight", EMobFlag::kAggressiveNight}, + {"kAgressiveFullmoon", EMobFlag::kAgressiveFullmoon}, + {"kMemory", EMobFlag::kMemory}, + {"kHelper", EMobFlag::kHelper}, + {"kNoCharm", EMobFlag::kNoCharm}, + {"kNoSummoned", EMobFlag::kNoSummon}, + {"kNoSummon", EMobFlag::kNoSummon}, + {"kNoSleep", EMobFlag::kNoSleep}, + {"kNoBash", EMobFlag::kNoBash}, + {"kNoBlind", EMobFlag::kNoBlind}, + {"kMounting", EMobFlag::kMounting}, + {"kNoHolder", EMobFlag::kNoHold}, + {"kNoHold", EMobFlag::kNoHold}, + {"kNoSilence", EMobFlag::kNoSilence}, + {"kAgressiveMono", EMobFlag::kAgressiveMono}, + {"kAgressivePoly", EMobFlag::kAgressivePoly}, + {"kNoFear", EMobFlag::kNoFear}, + {"kIgnoresFear", EMobFlag::kNoFear}, + {"kNoGroup", EMobFlag::kNoGroup}, + {"kCorpse", EMobFlag::kCorpse}, + {"kLooter", EMobFlag::kLooter}, + {"kLooting", EMobFlag::kLooter}, + {"kProtected", EMobFlag::kProtect}, + {"kProtect", EMobFlag::kProtect}, + {"kDeleted", EMobFlag::kMobDeleted}, + {"kSwimming", EMobFlag::kSwimming}, + {"kFlying", EMobFlag::kFlying}, + {"kOnlySwimming", EMobFlag::kOnlySwimming}, + {"kAgressiveWinter", EMobFlag::kAgressiveWinter}, + {"kAgressiveSpring", EMobFlag::kAgressiveSpring}, + {"kAgressiveSummer", EMobFlag::kAgressiveSummer}, + {"kAgressiveAutumn", EMobFlag::kAgressiveAutumn}, + {"kAppearsDay", EMobFlag::kAppearsDay}, + {"kAppearsNight", EMobFlag::kAppearsNight}, + {"kAppearsFullmoon", EMobFlag::kAppearsFullmoon}, + {"kAppearsWinter", EMobFlag::kAppearsWinter}, + {"kAppearsSpring", EMobFlag::kAppearsSpring}, + {"kAppearsSummer", EMobFlag::kAppearsSummer}, + {"kAppearsAutumn", EMobFlag::kAppearsAutumn}, + {"kNoFight", EMobFlag::kNoFight}, + {"kHorde", EMobFlag::kHorde}, + {"kClone", EMobFlag::kClone}, + {"kTutelar", EMobFlag::kTutelar}, + {"kMentalShadow", EMobFlag::kMentalShadow}, + {"kSummoner", EMobFlag::kSummoned}, + {"kFireCreature", EMobFlag::kFireBreath}, + {"kWaterCreature", EMobFlag::kSwimming}, + {"kEarthCreature", EMobFlag::kNoBash}, + {"kAirCreature", EMobFlag::kFlying}, + {"kNoTrack", EMobFlag::kAware}, + {"kNoTerrainAttack", EMobFlag::kNoFight}, + {"kFreemaker", EMobFlag::kSpec}, + {"kProgrammedLootGroup", EMobFlag::kLooter}, + {"kScrStay", EMobFlag::kSentinel}, + {"kRacing", EMobFlag::kSwimming}, + {"kAggressive_Mob", EMobFlag::kAgressive}, +}; + +// Mob affect flags mapping (EAffect values) +static std::unordered_map mob_affect_flag_map = { + {"kBlind", to_underlying(EAffect::kBlind)}, + {"kInvisible", to_underlying(EAffect::kInvisible)}, + {"kDetectAlign", to_underlying(EAffect::kDetectAlign)}, + {"kDetectInvisible", to_underlying(EAffect::kDetectInvisible)}, + {"kDetectMagic", to_underlying(EAffect::kDetectMagic)}, + {"kDetectLife", to_underlying(EAffect::kDetectLife)}, + {"kWaterWalk", to_underlying(EAffect::kWaterWalk)}, + {"kSanctuary", to_underlying(EAffect::kSanctuary)}, + {"kGroup", to_underlying(EAffect::kGroup)}, + {"kCurse", to_underlying(EAffect::kCurse)}, + {"kInfravision", to_underlying(EAffect::kInfravision)}, + {"kPoisoned", to_underlying(EAffect::kPoisoned)}, + {"kProtectFromDark", to_underlying(EAffect::kProtectFromDark)}, + {"kProtectFromMind", to_underlying(EAffect::kProtectFromMind)}, + {"kSleep", to_underlying(EAffect::kSleep)}, + {"kNoTrack", to_underlying(EAffect::kNoTrack)}, + {"kSneak", to_underlying(EAffect::kSneak)}, + {"kHide", to_underlying(EAffect::kHide)}, + {"kCharmed", to_underlying(EAffect::kCharmed)}, + {"kHold", to_underlying(EAffect::kHold)}, + {"kFly", to_underlying(EAffect::kFly)}, + {"kFlying", to_underlying(EAffect::kFly)}, + {"kSilence", to_underlying(EAffect::kSilence)}, + {"kAwarness", to_underlying(EAffect::kAwarness)}, + {"kBlink", to_underlying(EAffect::kBlink)}, + {"kHorse", to_underlying(EAffect::kHorse)}, + {"kNoFlee", to_underlying(EAffect::kNoFlee)}, + {"kHelper", to_underlying(EAffect::kHelper)}, + // Aliases from database to appropriate flags + {"kAggressive", to_underlying(EAffect::kNoFlee)}, + {"kScavenger", to_underlying(EAffect::kDetectLife)}, + {"kIsNpc", 0}, // Not an affect + {"kProtected", to_underlying(EAffect::kSanctuary)}, + {"kNoFear", to_underlying(EAffect::kNoFlee)}, + {"kAware", to_underlying(EAffect::kAwarness)}, + {"kScrStay", 0}, + {"kStayZone", 0}, + {"kWimpy", 0}, + {"kNoSummoned", 0}, + {"kNoSleep", 0}, + {"kNoBlind", 0}, + {"kNoCharm", 0}, + {"kSentinel", 0}, + {"kSpec", 0}, + {"kDeleted", 0}, + {"kSwimming", to_underlying(EAffect::kWaterWalk)}, + {"kWaterCreature", to_underlying(EAffect::kWaterWalk)}, + {"kFireCreature", 0}, + {"kEarthCreature", 0}, + {"kAirCreature", to_underlying(EAffect::kFly)}, + {"kMounting", to_underlying(EAffect::kHorse)}, + {"kMemory", 0}, + {"kNoHolder", 0}, + {"kNoSilence", 0}, + {"kClone", 0}, + {"kFreemaker", 0}, + {"kProgrammedLootGroup", 0}, + {"kNoMagicTerrainAttack", 0}, + {"kRacing", 0}, + {"kAggressive_Mob", 0}, + {"kIgnoresFear", 0}, +}; + +// Object extra flags mapping +static std::unordered_map obj_extra_flag_map = { + {"kGlow", EObjFlag::kGlow}, + {"kHum", EObjFlag::kHum}, + {"kNorent", EObjFlag::kNorent}, + {"kNodonate", EObjFlag::kNodonate}, + {"kNoinvis", EObjFlag::kNoinvis}, + {"kInvisible", EObjFlag::kInvisible}, + {"kMagic", EObjFlag::kMagic}, + {"kNodrop", EObjFlag::kNodrop}, + {"kBless", EObjFlag::kBless}, + {"kNosell", EObjFlag::kNosell}, + {"kDecay", EObjFlag::kDecay}, + {"kZonedecay", EObjFlag::kZonedecay}, + {"kNodisarm", EObjFlag::kNodisarm}, + {"kNodecay", EObjFlag::kNodecay}, + {"kPoisoned", EObjFlag::kPoisoned}, + {"kSharpen", EObjFlag::kSharpen}, + {"kArmored", EObjFlag::kArmored}, + {"kAppearsDay", EObjFlag::kAppearsDay}, + {"kAppearsNight", EObjFlag::kAppearsNight}, + {"kAppearsFullmoon", EObjFlag::kAppearsFullmoon}, + {"kAppearsWinter", EObjFlag::kAppearsWinter}, + {"kAppearsSpring", EObjFlag::kAppearsSpring}, + {"kAppearsSummer", EObjFlag::kAppearsSummer}, + {"kAppearsAutumn", EObjFlag::kAppearsAutumn}, + {"kSwimming", EObjFlag::kSwimming}, + {"kFlying", EObjFlag::kFlying}, + {"kThrowing", EObjFlag::kThrowing}, + {"kTicktimer", EObjFlag::kTicktimer}, + {"kFire", EObjFlag::kFire}, + {"kRepopDecay", EObjFlag::kRepopDecay}, + {"kNolocate", EObjFlag::kNolocate}, + {"kTimedLvl", EObjFlag::kTimedLvl}, + {"kNoalter", EObjFlag::kNoalter}, + {"kHasOneSlot", EObjFlag::kHasOneSlot}, + {"kHasTwoSlots", EObjFlag::kHasTwoSlots}, + {"kHasThreeSlots", EObjFlag::kHasThreeSlots}, + {"kSetItem", EObjFlag::KSetItem}, + {"kNofail", EObjFlag::KNofail}, + {"kNamed", EObjFlag::kNamed}, + {"kBloody", EObjFlag::kBloody}, + {"kQuestItem", EObjFlag::kQuestItem}, + {"k2inlaid", EObjFlag::k2inlaid}, + {"k3inlaid", EObjFlag::k3inlaid}, + {"kNopour", EObjFlag::kNopour}, + {"kUnique", EObjFlag::kUnique}, + {"kTransformed", EObjFlag::kTransformed}, + {"kNoRentTimer", EObjFlag::kNoRentTimer}, + {"kLimitedTimer", EObjFlag::KLimitedTimer}, + {"kBindOnPurchase", EObjFlag::kBindOnPurchase}, + {"kNotOneInClanChest", EObjFlag::kNotOneInClanChest}, +}; + +// Object wear flags mapping +static std::unordered_map obj_wear_flag_map = { + {"kTake", EWearFlag::kTake}, + {"kFinger", EWearFlag::kFinger}, + {"kNeck", EWearFlag::kNeck}, + {"kBody", EWearFlag::kBody}, + {"kHead", EWearFlag::kHead}, + {"kLegs", EWearFlag::kLegs}, + {"kFeet", EWearFlag::kFeet}, + {"kHands", EWearFlag::kHands}, + {"kArms", EWearFlag::kArms}, + {"kShield", EWearFlag::kShield}, + {"kShoulders", EWearFlag::kShoulders}, + {"kWaist", EWearFlag::kWaist}, + {"kWrist", EWearFlag::kWrist}, + {"kWield", EWearFlag::kWield}, + {"kHold", EWearFlag::kHold}, + {"kBoth", EWearFlag::kBoth}, + {"kQuiver", EWearFlag::kQuiver}, +}; + +// Object affect flags mapping (EWeaponAffect values) +static std::unordered_map obj_affect_flag_map = { + {"kBlindness", EWeaponAffect::kBlindness}, + {"kInvisibility", EWeaponAffect::kInvisibility}, + {"kDetectAlign", EWeaponAffect::kDetectAlign}, + {"kDetectInvisibility", EWeaponAffect::kDetectInvisibility}, + {"kDetectMagic", EWeaponAffect::kDetectMagic}, + {"kDetectLife", EWeaponAffect::kDetectLife}, + {"kWaterWalk", EWeaponAffect::kWaterWalk}, + {"kSanctuary", EWeaponAffect::kSanctuary}, + {"kCurse", EWeaponAffect::kCurse}, + {"kInfravision", EWeaponAffect::kInfravision}, + {"kPoison", EWeaponAffect::kPoison}, + {"kProtectFromDark", EWeaponAffect::kProtectFromDark}, + {"kProtectFromMind", EWeaponAffect::kProtectFromMind}, + {"kSleep", EWeaponAffect::kSleep}, + {"kNoTrack", EWeaponAffect::kNoTrack}, + {"kBless", EWeaponAffect::kBless}, + {"kSneak", EWeaponAffect::kSneak}, + {"kHide", EWeaponAffect::kHide}, + {"kHold", EWeaponAffect::kHold}, + {"kFly", EWeaponAffect::kFly}, + {"kSilence", EWeaponAffect::kSilence}, + {"kAwareness", EWeaponAffect::kAwareness}, + {"kBlink", EWeaponAffect::kBlink}, + {"kNoFlee", EWeaponAffect::kNoFlee}, + {"kSingleLight", EWeaponAffect::kSingleLight}, + {"kHolyLight", EWeaponAffect::kHolyLight}, + {"kHolyDark", EWeaponAffect::kHolyDark}, + {"kDetectPoison", EWeaponAffect::kDetectPoison}, + {"kSlow", EWeaponAffect::kSlow}, + {"kHaste", EWeaponAffect::kHaste}, + {"kWaterBreath", EWeaponAffect::kWaterBreath}, + {"kHaemorrhage", EWeaponAffect::kHaemorrhage}, + {"kDisguising", EWeaponAffect::kDisguising}, + {"kShield", EWeaponAffect::kShield}, + {"kAirShield", EWeaponAffect::kAirShield}, + {"kFireShield", EWeaponAffect::kFireShield}, + {"kIceShield", EWeaponAffect::kIceShield}, + {"kMagicGlass", EWeaponAffect::kMagicGlass}, + {"kStoneHand", EWeaponAffect::kStoneHand}, + {"kPrismaticAura", EWeaponAffect::kPrismaticAura}, + {"kAirAura", EWeaponAffect::kAirAura}, + {"kFireAura", EWeaponAffect::kFireAura}, + {"kIceAura", EWeaponAffect::kIceAura}, + {"kDeafness", EWeaponAffect::kDeafness}, + {"kComamnder", EWeaponAffect::kComamnder}, + {"kEarthAura", EWeaponAffect::kEarthAura}, + {"kCloudly", EWeaponAffect::kCloudly}, +}; + +// Object anti flags mapping (EAntiFlag values) +static std::unordered_map obj_anti_flag_map = { + {"kMono", EAntiFlag::kMono}, + {"kPoly", EAntiFlag::kPoly}, + {"kNeutral", EAntiFlag::kNeutral}, + {"kMage", EAntiFlag::kMage}, + {"kSorcerer", EAntiFlag::kSorcerer}, + {"kThief", EAntiFlag::kThief}, + {"kWarrior", EAntiFlag::kWarrior}, + {"kAssasine", EAntiFlag::kAssasine}, + {"kGuard", EAntiFlag::kGuard}, + {"kPaladine", EAntiFlag::kPaladine}, + {"kRanger", EAntiFlag::kRanger}, + {"kVigilant", EAntiFlag::kVigilant}, + {"kMerchant", EAntiFlag::kMerchant}, + {"kMagus", EAntiFlag::kMagus}, + {"kConjurer", EAntiFlag::kConjurer}, + {"kCharmer", EAntiFlag::kCharmer}, + {"kWizard", EAntiFlag::kWizard}, + {"kNecromancer", EAntiFlag::kNecromancer}, + {"kFighter", EAntiFlag::kFighter}, + {"kKiller", EAntiFlag::kKiller}, + {"kColored", EAntiFlag::kColored}, + {"kBattle", EAntiFlag::kBattle}, + {"kMale", EAntiFlag::kMale}, + {"kFemale", EAntiFlag::kFemale}, + {"kCharmice", EAntiFlag::kCharmice}, + {"kNoPkClan", EAntiFlag::kNoPkClan}, +}; + +// Object no flags mapping (ENoFlag values) +static std::unordered_map obj_no_flag_map = { + {"kMono", ENoFlag::kMono}, + {"kPoly", ENoFlag::kPoly}, + {"kNeutral", ENoFlag::kNeutral}, + {"kMage", ENoFlag::kMage}, + {"kSorcerer", ENoFlag::kSorcerer}, + {"kThief", ENoFlag::kThief}, + {"kWarrior", ENoFlag::kWarrior}, + {"kAssasine", ENoFlag::kAssasine}, + {"kGuard", ENoFlag::kGuard}, + {"kPaladine", ENoFlag::kPaladine}, + {"kRanger", ENoFlag::kRanger}, + {"kVigilant", ENoFlag::kVigilant}, + {"kMerchant", ENoFlag::kMerchant}, + {"kMagus", ENoFlag::kMagus}, + {"kConjurer", ENoFlag::kConjurer}, + {"kCharmer", ENoFlag::kCharmer}, + {"kWizard", ENoFlag::kWIzard}, + {"kNecromancer", ENoFlag::kNecromancer}, + {"kFighter", ENoFlag::kFighter}, + {"kKiller", ENoFlag::kKiller}, + {"kColored", ENoFlag::kColored}, + {"kBattle", ENoFlag::kBattle}, + {"kMale", ENoFlag::kMale}, + {"kFemale", ENoFlag::kFemale}, + {"kCharmice", ENoFlag::kCharmice}, +}; + +// Position mapping +static std::unordered_map position_map = { + {"kDead", static_cast(EPosition::kDead)}, + {"kPerish", static_cast(EPosition::kPerish)}, + {"kMortallyw", static_cast(EPosition::kPerish)}, + {"kIncap", static_cast(EPosition::kIncap)}, + {"kStun", static_cast(EPosition::kStun)}, + {"kSleep", static_cast(EPosition::kSleep)}, + {"kRest", static_cast(EPosition::kRest)}, + {"kSit", static_cast(EPosition::kSit)}, + {"kFight", static_cast(EPosition::kFight)}, + {"kStanding", static_cast(EPosition::kStand)}, + {"kStand", static_cast(EPosition::kStand)}, +}; + +// Gender mapping +static std::unordered_map gender_map = { + {"kMale", static_cast(EGender::kMale)}, + {"kFemale", static_cast(EGender::kFemale)}, + {"kNeutral", static_cast(EGender::kNeutral)}, + {"kPoly", static_cast(EGender::kPoly)}, +}; +// Helper: reverse lookup flag name by bit position (template version) +template +std::string ReverseLookupFlag(const std::unordered_map &flag_map, Bitvector bit_value) +{ + for (const auto &[name, value] : flag_map) + { + if (static_cast(value) == bit_value) + { + return name; + } + } + return ""; // Not found +} + +// Save flags helper (template version) +template +void SaveFlagsToTable(sqlite3 *db, const std::string &table_name, const std::string &vnum_col, + int vnum, const FlagData &flags, + const std::unordered_map &flag_map, + const std::string &category = "") +{ + sqlite3_stmt *stmt = nullptr; + std::string sql; + if (category.empty()) + { + sql = "INSERT INTO " + table_name + " (" + vnum_col + ", flag_name) VALUES (?, ?)"; + } + else + { + sql = "INSERT INTO " + table_name + " (" + vnum_col + ", flag_category, flag_name) VALUES (?, ?, ?)"; + } + + for (size_t plane = 0; plane < FlagData::kPlanesNumber; ++plane) + { + Bitvector plane_bits = flags.get_plane(plane); + if (plane_bits == 0) continue; + + for (int bit = 0; bit < 30; ++bit) + { + if (plane_bits & (1 << bit)) + { + Bitvector bit_value = (plane << 30) | (1 << bit); + std::string flag_name = ReverseLookupFlag(flag_map, bit_value); + + if (!flag_name.empty()) + { + if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) + { + int col = 1; + sqlite3_bind_int(stmt, col++, vnum); + if (!category.empty()) + { + sqlite3_bind_text(stmt, col++, category.c_str(), -1, SQLITE_TRANSIENT); + } + sqlite3_bind_text(stmt, col++, flag_name.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + } + } +} + +// ============================================================================ +// SqliteWorldDataSource implementation +// ============================================================================ + +SqliteWorldDataSource::SqliteWorldDataSource(const std::string &db_path) + : m_db_path(db_path) + , m_db(nullptr) +{ +} + +SqliteWorldDataSource::~SqliteWorldDataSource() +{ + CloseDatabase(); +} + +bool SqliteWorldDataSource::OpenDatabase() +{ + if (m_db) + { + return true; + } + + int rc = sqlite3_open_v2(m_db_path.c_str(), &m_db, SQLITE_OPEN_READONLY, nullptr); + if (rc != SQLITE_OK) + { + log("SYSERR: Cannot open SQLite database '%s': %s", m_db_path.c_str(), sqlite3_errmsg(m_db)); + sqlite3_close(m_db); + m_db = nullptr; + return false; + } + + log("Opened SQLite database: %s", m_db_path.c_str()); + return true; +} + +void SqliteWorldDataSource::CloseDatabase() +{ + if (m_db) + { + sqlite3_close(m_db); + m_db = nullptr; + } +} + +int SqliteWorldDataSource::GetCount(const char *table) +{ + std::string sql = "SELECT COUNT(*) FROM "; + static const char* enabled_tables[] = {"zones", "rooms", "mobs", "objects", "triggers", nullptr}; + sql += table; + for (const char** t = enabled_tables; *t; ++t) { + if (strcmp(table, *t) == 0) { + sql += " WHERE enabled = 1"; + break; + } + } + + sqlite3_stmt *stmt; + int count = 0; + if (sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) + { + if (sqlite3_step(stmt) == SQLITE_ROW) + { + count = sqlite3_column_int(stmt, 0); + } + sqlite3_finalize(stmt); + } + return count; +} + +// Helper function to safely convert string to int +static int SafeStoi(const std::string &str, int default_val = 0) +{ + if (str.empty()) + { + return default_val; + } + try + { + return std::stoi(str); + } + catch (...) + { + return default_val; + } +} + +// Helper function to safely convert string to long +static long SafeStol(const std::string &str, long default_val = 0) +{ + if (str.empty()) + { + return default_val; + } + try + { + return std::stol(str); + } + catch (...) + { + return default_val; + } +} + +std::string SqliteWorldDataSource::GetText(sqlite3_stmt *stmt, int col) +{ + const char *text = reinterpret_cast(sqlite3_column_text(stmt, col)); + if (!text || !*text) + { + return ""; + } + // Convert UTF-8 from SQLite to KOI8-R + static char buffer[65536]; + char *input = const_cast(text); + char *output = buffer; + utf8_to_koi(input, output); + return buffer; +} + +// ============================================================================ +// Zone Loading +// ============================================================================ + +void SqliteWorldDataSource::LoadZones() +{ + log("Loading zones from SQLite database."); + + if (!OpenDatabase()) + { + log("SYSERR: Failed to open SQLite database for zone loading."); + return; + } + + int zone_count = GetCount("zones"); + if (zone_count == 0) + { + log("No zones found in SQLite database."); + return; + } + + zone_table.reserve(zone_count + dungeons::kNumberOfZoneDungeons); + zone_table.resize(zone_count); + log(" %d zones, %zd bytes.", zone_count, sizeof(ZoneData) * zone_count); + + // Load zones + const char *sql = "SELECT vnum, name, comment, location, author, description, zone_group, " + "top_room, lifespan, reset_mode, reset_idle, zone_type, mode, entrance, under_construction " + "FROM zones WHERE enabled = 1 ORDER BY vnum"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare zone query: %s", sqlite3_errmsg(m_db)); + return; + } + + int zone_idx = 0; + while (sqlite3_step(stmt) == SQLITE_ROW && zone_idx < zone_count) + { + ZoneData &zone = zone_table[zone_idx]; + + zone.vnum = sqlite3_column_int(stmt, 0); + zone.name = GetText(stmt, 1); + int zone_group = sqlite3_column_int(stmt, 6); + zone.group = (zone_group == 0) ? 1 : zone_group; + zone.comment = GetText(stmt, 2); + zone.location = GetText(stmt, 3); + zone.author = GetText(stmt, 4); + zone.description = GetText(stmt, 5); + zone.top = sqlite3_column_int(stmt, 7); + zone.lifespan = sqlite3_column_int(stmt, 8); + zone.reset_mode = sqlite3_column_int(stmt, 9); + zone.reset_idle = sqlite3_column_int(stmt, 10) != 0; + zone.type = sqlite3_column_int(stmt, 11); + zone.level = sqlite3_column_int(stmt, 12); + zone.entrance = sqlite3_column_int(stmt, 13); + // Initialize runtime fields (uses base class method) + int under_construction = sqlite3_column_int(stmt, 14); + InitializeZoneRuntimeFields(zone, under_construction); + + // Load zone commands + LoadZoneCommands(zone); + + // Load zone groups + LoadZoneGroups(zone); + + zone_idx++; + } + sqlite3_finalize(stmt); + + log("Loaded %d zones from SQLite.", zone_idx - 1); +} + +void SqliteWorldDataSource::LoadZoneCommands(ZoneData &zone) +{ + // Count commands for this zone + std::string count_sql = "SELECT COUNT(*) FROM zone_commands WHERE zone_vnum = " + std::to_string(zone.vnum); + sqlite3_stmt *stmt; + int cmd_count = 0; + + if (sqlite3_prepare_v2(m_db, count_sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) + { + if (sqlite3_step(stmt) == SQLITE_ROW) + { + cmd_count = sqlite3_column_int(stmt, 0); + } + sqlite3_finalize(stmt); + } + + // Always need at least one command for 'S' terminator + CREATE(zone.cmd, cmd_count + 1); + + if (cmd_count == 0) + { + zone.cmd[0].command = 'S'; + return; + } + + // Load commands + std::string sql = "SELECT cmd_type, if_flag, arg_mob_vnum, arg_obj_vnum, arg_room_vnum, " + "arg_max, arg_max_world, arg_max_room, arg_load_prob, arg_wear_pos_id, " + "arg_direction_id, arg_state, arg_trigger_vnum, arg_trigger_type, " + "arg_context, arg_var_name, arg_var_value, arg_container_vnum, " + "arg_leader_mob_vnum, arg_follower_mob_vnum " + "FROM zone_commands WHERE zone_vnum = " + std::to_string(zone.vnum) + + " ORDER BY cmd_order"; + + if (sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) + { + zone.cmd[0].command = 'S'; + return; + } + + int idx = 0; + while (sqlite3_step(stmt) == SQLITE_ROW && idx < cmd_count) + { + std::string cmd_type = GetText(stmt, 0); + struct reset_com &cmd = zone.cmd[idx]; + + // Initialize + cmd.command = '*'; + cmd.if_flag = sqlite3_column_int(stmt, 1); + cmd.arg1 = 0; + cmd.arg2 = 0; + cmd.arg3 = 0; + cmd.arg4 = -1; + cmd.sarg1 = nullptr; + cmd.sarg2 = nullptr; + cmd.line = 0; + + // Map command type + if (strcmp(cmd_type.c_str(), "LOAD_MOB") == 0) + { + cmd.command = 'M'; + cmd.arg1 = sqlite3_column_int(stmt, 2); // mob_vnum + cmd.arg2 = sqlite3_column_int(stmt, 6); // max_world + cmd.arg3 = sqlite3_column_int(stmt, 4); // room_vnum + cmd.arg4 = sqlite3_column_int(stmt, 7); // max_room + } + else if (strcmp(cmd_type.c_str(), "LOAD_OBJ") == 0) + { + cmd.command = 'O'; + cmd.arg1 = sqlite3_column_int(stmt, 3); // obj_vnum + cmd.arg2 = sqlite3_column_int(stmt, 5); // max + cmd.arg3 = sqlite3_column_int(stmt, 4); // room_vnum + cmd.arg4 = sqlite3_column_int(stmt, 8); // load_prob + } + else if (strcmp(cmd_type.c_str(), "GIVE_OBJ") == 0) + { + cmd.command = 'G'; + cmd.arg1 = sqlite3_column_int(stmt, 3); // obj_vnum + cmd.arg2 = sqlite3_column_int(stmt, 5); // max + cmd.arg3 = -1; + cmd.arg4 = sqlite3_column_int(stmt, 8); // load_prob + } + else if (strcmp(cmd_type.c_str(), "EQUIP_MOB") == 0) + { + cmd.command = 'E'; + cmd.arg1 = sqlite3_column_int(stmt, 3); // obj_vnum + cmd.arg2 = sqlite3_column_int(stmt, 5); // max + int wear_pos = sqlite3_column_int(stmt, 9); + cmd.arg3 = wear_pos; + cmd.arg4 = sqlite3_column_int(stmt, 8); // load_prob + } + else if (strcmp(cmd_type.c_str(), "PUT_OBJ") == 0) + { + cmd.command = 'P'; + cmd.arg1 = sqlite3_column_int(stmt, 3); // obj_vnum + cmd.arg2 = sqlite3_column_int(stmt, 5); // max + cmd.arg3 = sqlite3_column_int(stmt, 17); // container_vnum + cmd.arg4 = sqlite3_column_int(stmt, 8); // load_prob + } + else if (strcmp(cmd_type.c_str(), "DOOR") == 0) + { + cmd.command = 'D'; + cmd.arg1 = sqlite3_column_int(stmt, 4); // room_vnum + int zone_dir = sqlite3_column_int(stmt, 10); + + cmd.arg2 = zone_dir; + cmd.arg3 = sqlite3_column_int(stmt, 11); // state + } + else if (strcmp(cmd_type.c_str(), "REMOVE_OBJ") == 0) + { + cmd.command = 'R'; + cmd.arg1 = sqlite3_column_int(stmt, 4); // room_vnum + cmd.arg2 = sqlite3_column_int(stmt, 3); // obj_vnum + } + else if (strcmp(cmd_type.c_str(), "TRIGGER") == 0) + { + cmd.command = 'T'; + std::string trig_type = GetText(stmt, 13); + cmd.arg1 = !trig_type.empty() ? SafeStoi(trig_type) : 0; + cmd.arg2 = sqlite3_column_int(stmt, 12); // trigger_vnum + cmd.arg3 = sqlite3_column_int(stmt, 4); // room_vnum (or -1 for mob/obj) + } + else if (strcmp(cmd_type.c_str(), "VARIABLE") == 0) + { + cmd.command = 'V'; + std::string trig_type = GetText(stmt, 13); + cmd.arg1 = !trig_type.empty() ? SafeStoi(trig_type) : 0; + cmd.arg2 = sqlite3_column_int(stmt, 14); // context + cmd.arg3 = sqlite3_column_int(stmt, 4); // room_vnum + std::string var_name = GetText(stmt, 15); + std::string var_value = GetText(stmt, 16); + if (!var_name.empty()) cmd.sarg1 = str_dup(var_name.c_str()); + if (!var_value.empty()) cmd.sarg2 = str_dup(var_value.c_str()); + } + else if (strcmp(cmd_type.c_str(), "EXTRACT_MOB") == 0) + { + cmd.command = 'Q'; + cmd.arg1 = sqlite3_column_int(stmt, 2); // mob_vnum + } + else if (strcmp(cmd_type.c_str(), "FOLLOW") == 0) + { + cmd.command = 'F'; + cmd.arg1 = sqlite3_column_int(stmt, 4); // room_vnum + cmd.arg2 = sqlite3_column_int(stmt, 18); // leader_mob_vnum + cmd.arg3 = sqlite3_column_int(stmt, 19); // follower_mob_vnum + } + + idx++; + } + sqlite3_finalize(stmt); + + // Add terminating command + zone.cmd[idx].command = 'S'; +} + +void SqliteWorldDataSource::LoadZoneGroups(ZoneData &zone) +{ + sqlite3_stmt *stmt; + + // Count and load typeA + std::string sql = "SELECT linked_zone_vnum FROM zone_groups WHERE zone_vnum = " + + std::to_string(zone.vnum) + " AND group_type = 'A'"; + + if (sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) + { + // First count + std::vector typeA_zones; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + typeA_zones.push_back(sqlite3_column_int(stmt, 0)); + } + sqlite3_finalize(stmt); + + if (!typeA_zones.empty()) + { + zone.typeA_count = typeA_zones.size(); + CREATE(zone.typeA_list, zone.typeA_count); + for (int i = 0; i < zone.typeA_count; i++) + { + zone.typeA_list[i] = typeA_zones[i]; + } + } + } + + // Count and load typeB + sql = "SELECT linked_zone_vnum FROM zone_groups WHERE zone_vnum = " + + std::to_string(zone.vnum) + " AND group_type = 'B'"; + + if (sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) + { + std::vector typeB_zones; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + typeB_zones.push_back(sqlite3_column_int(stmt, 0)); + } + sqlite3_finalize(stmt); + + if (!typeB_zones.empty()) + { + zone.typeB_count = typeB_zones.size(); + CREATE(zone.typeB_list, zone.typeB_count); + CREATE(zone.typeB_flag, zone.typeB_count); + for (int i = 0; i < zone.typeB_count; i++) + { + zone.typeB_list[i] = typeB_zones[i]; + zone.typeB_flag[i] = false; + } + } + } +} + +// ============================================================================ +// Trigger Loading +// ============================================================================ + +void SqliteWorldDataSource::LoadTriggers() +{ + log("Loading triggers from SQLite database."); + + if (!OpenDatabase()) + { + log("SYSERR: Failed to open SQLite database for trigger loading."); + return; + } + + int trig_count = GetCount("triggers"); + if (trig_count == 0) + { + log("No triggers found in SQLite database."); + return; + } + + // Allocate trig_index + CREATE(trig_index, trig_count); + log(" %d triggers.", trig_count); + + const char *sql = "SELECT t.vnum, t.name, t.attach_type_id, GROUP_CONCAT(ttb.type_char, '') AS type_chars, t.narg, t.arglist, t.script " + "FROM triggers t LEFT JOIN trigger_type_bindings ttb ON t.vnum = ttb.trigger_vnum WHERE t.enabled = 1 GROUP BY t.vnum ORDER BY t.vnum"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare trigger query: %s", sqlite3_errmsg(m_db)); + return; + } + + top_of_trigt = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int vnum = sqlite3_column_int(stmt, 0); + std::string name = GetText(stmt, 1); + int attach_type_id = sqlite3_column_int(stmt, 2); + std::string type_chars = GetText(stmt, 3); + int narg = sqlite3_column_int(stmt, 4); + std::string arglist = GetText(stmt, 5); + std::string script = GetText(stmt, 6); + + byte attach_type = static_cast(attach_type_id); + + // Compute trigger_type bitmask from type_chars + long trigger_type = 0; + + for (char ch : type_chars) + { + if (ch >= 'a' && ch <= 'z') + trigger_type |= (1L << (ch - 'a')); + else if (ch >= 'A' && ch <= 'Z') + trigger_type |= (1L << (26 + ch - 'A')); + } + + + // Create trigger with proper constructor (keep empty name same as Legacy) + auto trig = new Trigger(top_of_trigt, std::move(name), attach_type, trigger_type); + GET_TRIG_NARG(trig) = narg; + trig->arglist = arglist; + + + // Parse script into cmdlist (uses base class method) + ParseTriggerScript(trig, script); + + // Create index entry (uses base class method) + CreateTriggerIndex(vnum, trig); + + } + sqlite3_finalize(stmt); + + log("Loaded %d triggers from SQLite.", top_of_trigt); +} + +// ============================================================================ +// Room Loading +// ============================================================================ + +void SqliteWorldDataSource::LoadRooms() +{ + log("Loading rooms from SQLite database."); + + if (!OpenDatabase()) + { + log("SYSERR: Failed to open SQLite database for room loading."); + return; + } + + int room_count = GetCount("rooms"); + if (room_count == 0) + { + log("No rooms found in SQLite database."); + return; + } + + // Create kNowhere room first + world.push_back(new RoomData); + top_of_world = kNowhere; + + log(" %d rooms, %zd bytes.", room_count, sizeof(RoomData) * room_count); + + const char *sql = "SELECT vnum, zone_vnum, name, description, sector_id FROM rooms WHERE enabled = 1 ORDER BY vnum"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare room query: %s", sqlite3_errmsg(m_db)); + return; + } + + // Zone rnum - increments as we process rooms in vnum order (same as Legacy) + ZoneRnum zone_rn = 0; + + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int vnum = sqlite3_column_int(stmt, 0); + [[maybe_unused]] int zone_vnum = sqlite3_column_int(stmt, 1); + std::string name = GetText(stmt, 2); + std::string description = GetText(stmt, 3); + int sector_id = sqlite3_column_int(stmt, 4); + + auto room = new RoomData; + room->vnum = vnum; + // Apply UPPER to first character (same as Legacy loader) + if (!name.empty()) { name[0] = UPPER(name[0]); } + room->set_name(name); + + // Set description + if (!description.empty()) + { + room->description_num = GlobalObjects::descriptions().add(description); + } + + // Set zone_rn by finding zone that contains this vnum (same as Legacy) + while (vnum > zone_table[zone_rn].top) + { + if (++zone_rn >= static_cast(zone_table.size())) + { + log("SYSERR: Room %d is outside of any zone.", vnum); + break; + } + } + if (zone_rn < static_cast(zone_table.size())) + { + room->zone_rn = zone_rn; + if (zone_table[zone_rn].RnumRoomsLocation.first == -1) + { + zone_table[zone_rn].RnumRoomsLocation.first = top_of_world + 1; + } + zone_table[zone_rn].RnumRoomsLocation.second = top_of_world + 1; + } + + room->sector_type = static_cast(sector_id); + + world.push_back(room); + top_of_world++; + } + sqlite3_finalize(stmt); + + // Build room vnum to rnum map for exits + std::map room_vnum_to_rnum; + for (RoomRnum i = kFirstRoom; i <= top_of_world; i++) + { + room_vnum_to_rnum[world[i]->vnum] = i; + } + + // Load room flags + LoadRoomFlags(room_vnum_to_rnum); + + // Load room exits + LoadRoomExits(room_vnum_to_rnum); + + // Load room triggers + LoadRoomTriggers(room_vnum_to_rnum); + + // Load room extra descriptions + LoadRoomExtraDescriptions(room_vnum_to_rnum); + + +} + +void SqliteWorldDataSource::LoadRoomFlags(const std::map &vnum_to_rnum) +{ + const char *sql = "SELECT room_vnum, flag_name FROM room_flags"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare room flags query: %s", sqlite3_errmsg(m_db)); + return; + } + + int flags_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int room_vnum = sqlite3_column_int(stmt, 0); + std::string flag_name = GetText(stmt, 1); + + auto room_it = vnum_to_rnum.find(room_vnum); + if (room_it == vnum_to_rnum.end()) continue; + + auto flag_it = room_flag_map.find(flag_name); + if (flag_it != room_flag_map.end()) + { + world[room_it->second]->set_flag(flag_it->second); + flags_set++; + } + } + sqlite3_finalize(stmt); + + log(" Set %d room flags.", flags_set); +} + +void SqliteWorldDataSource::LoadRoomExits(const std::map &vnum_to_rnum) +{ + const char *sql = "SELECT room_vnum, direction_id, description, keywords, exit_flags, " + "key_vnum, to_room, lock_complexity FROM room_exits"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare room exits query: %s", sqlite3_errmsg(m_db)); + return; + } + + int exits_loaded = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int room_vnum = sqlite3_column_int(stmt, 0); + int dir = sqlite3_column_int(stmt, 1); + std::string desc = GetText(stmt, 2); + std::string keywords = GetText(stmt, 3); + std::string exit_flags_str = GetText(stmt, 4); + int exit_flags = !exit_flags_str.empty() ? SafeStoi(exit_flags_str) : 0; + int key_vnum = sqlite3_column_int(stmt, 5); + int to_room_vnum = sqlite3_column_int(stmt, 6); + int lock_complexity = sqlite3_column_int(stmt, 7); + + auto it = vnum_to_rnum.find(room_vnum); + if (it == vnum_to_rnum.end()) continue; + + RoomData *room = world[it->second]; + + + if (dir < 0 || dir >= EDirection::kMaxDirNum) continue; + + auto exit_data = std::make_shared(); + + // Store vnum - RosolveWorldDoorToRoomVnumsToRnums() will convert to rnum later + exit_data->to_room(to_room_vnum); + + exit_data->key = key_vnum; + exit_data->lock_complexity = lock_complexity; + if (!desc.empty()) exit_data->general_description = desc; + if (!keywords.empty()) exit_data->set_keywords(keywords); + + // Set exit flags (stored as string in database, parse as integer) + exit_data->exit_info = exit_flags; + + room->dir_option_proto[dir] = exit_data; + exits_loaded++; + } + sqlite3_finalize(stmt); + + log(" Loaded %d room exits.", exits_loaded); +} + +void SqliteWorldDataSource::LoadRoomTriggers(const std::map &vnum_to_rnum) +{ + const char *sql = "SELECT entity_vnum, trigger_vnum FROM entity_triggers WHERE entity_type = 'room' ORDER BY trigger_order"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int room_vnum = sqlite3_column_int(stmt, 0); + int trigger_vnum = sqlite3_column_int(stmt, 1); + + auto it = vnum_to_rnum.find(room_vnum); + if (it == vnum_to_rnum.end()) continue; + + AttachTriggerToRoom(it->second, trigger_vnum, room_vnum); + } + sqlite3_finalize(stmt); +} + +void SqliteWorldDataSource::LoadRoomExtraDescriptions(const std::map &vnum_to_rnum) +{ + const char *sql = "SELECT entity_vnum, keywords, description FROM extra_descriptions WHERE entity_type = 'room'"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + int loaded = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int room_vnum = sqlite3_column_int(stmt, 0); + std::string keywords = GetText(stmt, 1); + std::string description = GetText(stmt, 2); + + auto it = vnum_to_rnum.find(room_vnum); + if (it == vnum_to_rnum.end()) continue; + + auto ex_desc = std::make_shared(); + ex_desc->set_keyword(keywords); + ex_desc->set_description(description); + ex_desc->next = world[it->second]->ex_description; + world[it->second]->ex_description = ex_desc; + loaded++; + } + sqlite3_finalize(stmt); + + if (loaded > 0) + { + log(" Loaded %d room extra descriptions.", loaded); + } +} + +// ============================================================================ +// Mob Loading +// ============================================================================ + +void SqliteWorldDataSource::LoadMobs() +{ + log("Loading mobs from SQLite database."); + + if (!OpenDatabase()) + { + log("SYSERR: Failed to open SQLite database for mob loading."); + return; + } + + int mob_count = GetCount("mobs"); + if (mob_count == 0) + { + log("No mobs found in SQLite database."); + return; + } + + // Allocate like PrepareGlobalStructures + mob_proto = new CharData[mob_count]; + CREATE(mob_index, mob_count); + log(" %d mobs, %zd bytes in index, %zd bytes in prototypes.", + mob_count, sizeof(IndexData) * mob_count, sizeof(CharData) * mob_count); + + // Build zone vnum to rnum map + std::map zone_vnum_to_rnum; + for (size_t i = 0; i < zone_table.size(); i++) + { + zone_vnum_to_rnum[zone_table[i].vnum] = i; + } + + const char *sql = "SELECT vnum, aliases, name_nom, name_gen, name_dat, name_acc, name_ins, name_pre, " + "short_desc, long_desc, alignment, mob_type, level, hitroll_penalty, armor, " + "hp_dice_count, hp_dice_size, hp_bonus, dam_dice_count, dam_dice_size, dam_bonus, " + "gold_dice_count, gold_dice_size, gold_bonus, experience, default_pos, start_pos, " + "sex, size, height, weight, mob_class, race, " + "attr_str, attr_dex, attr_int, attr_wis, attr_con, attr_cha, " + "attr_str_add, hp_regen, armour_bonus, mana_regen, cast_success, morale, " + "initiative_add, absorb, aresist, mresist, presist, bare_hand_attack, " + "like_work, max_factor, extra_attack, mob_remort, special_bitvector, role " + "FROM mobs WHERE enabled = 1 ORDER BY vnum"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare mob query: %s", sqlite3_errmsg(m_db)); + return; + } + + top_of_mobt = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int vnum = sqlite3_column_int(stmt, 0); + CharData &mob = mob_proto[top_of_mobt]; + + // Initialize mob + mob.player_specials = player_special_data::s_for_mobiles; + mob.SetNpcAttribute(true); + mob.player_specials->saved.NameGod = 1001; // Default for Russian name declension + mob.set_move(100); + mob.set_max_move(100); + + // Names + mob.SetCharAliases(GetText(stmt, 1)); + mob.set_npc_name(GetText(stmt, 2)); + mob.player_data.PNames[ECase::kNom] = GetText(stmt, 2); + mob.player_data.PNames[ECase::kGen] = GetText(stmt, 3); + mob.player_data.PNames[ECase::kDat] = GetText(stmt, 4); + mob.player_data.PNames[ECase::kAcc] = GetText(stmt, 5); + mob.player_data.PNames[ECase::kIns] = GetText(stmt, 6); + mob.player_data.PNames[ECase::kPre] = GetText(stmt, 7); + + // Descriptions + mob.player_data.long_descr = utils::colorCAP(GetText(stmt, 8)); + mob.player_data.description = GetText(stmt, 9); + + // Base parameters + GET_ALIGNMENT(&mob) = sqlite3_column_int(stmt, 10); + + // Stats + mob.set_level(sqlite3_column_int(stmt, 12)); + GET_HR(&mob) = sqlite3_column_int(stmt, 13); + GET_AC(&mob) = sqlite3_column_int(stmt, 14); + + // HP dice + mob.mem_queue.total = sqlite3_column_int(stmt, 15); // hp_dice_count + mob.mem_queue.stored = sqlite3_column_int(stmt, 16); // hp_dice_size + int hp_bonus = sqlite3_column_int(stmt, 17); + mob.set_hit(hp_bonus); + mob.set_max_hit(0); // 0 = flag that HP is xdy+z + + // Damage dice + mob.mob_specials.damnodice = sqlite3_column_int(stmt, 18); + mob.mob_specials.damsizedice = sqlite3_column_int(stmt, 19); + mob.real_abils.damroll = sqlite3_column_int(stmt, 20); + + // Gold dice + mob.mob_specials.GoldNoDs = sqlite3_column_int(stmt, 21); + mob.mob_specials.GoldSiDs = sqlite3_column_int(stmt, 22); + mob.set_gold(sqlite3_column_int(stmt, 23)); + + // Experience + mob.set_exp(sqlite3_column_int(stmt, 24)); + + // Position + std::string default_pos = GetText(stmt, 25); + std::string start_pos = GetText(stmt, 26); + auto pos_it = position_map.find(default_pos); + mob.mob_specials.default_pos = pos_it != position_map.end() ? + static_cast(pos_it->second) : EPosition::kStand; + pos_it = position_map.find(start_pos); + mob.SetPosition(pos_it != position_map.end() ? + static_cast(pos_it->second) : EPosition::kStand); + + // Sex + std::string sex_str = GetText(stmt, 27); + auto gender_it = gender_map.find(sex_str); + mob.set_sex(gender_it != gender_map.end() ? + static_cast(gender_it->second) : EGender::kMale); + + // Physical attributes + GET_SIZE(&mob) = sqlite3_column_int(stmt, 28); + GET_HEIGHT(&mob) = sqlite3_column_int(stmt, 29); + GET_WEIGHT(&mob) = sqlite3_column_int(stmt, 30); + + // Class and race + mob.set_class(static_cast(sqlite3_column_int(stmt, 31))); + mob.player_data.Race = static_cast(sqlite3_column_int(stmt, 32)); + + // Attributes (E-spec) + mob.set_str(sqlite3_column_int(stmt, 33)); + mob.set_dex(sqlite3_column_int(stmt, 34)); + mob.set_int(sqlite3_column_int(stmt, 35)); + mob.set_wis(sqlite3_column_int(stmt, 36)); + mob.set_con(sqlite3_column_int(stmt, 37)); + mob.set_cha(sqlite3_column_int(stmt, 38)); + + // Enhanced E-spec fields (scalar values) + mob.set_str_add(sqlite3_column_int(stmt, 39)); + mob.add_abils.hitreg = sqlite3_column_int(stmt, 40); + mob.add_abils.armour = sqlite3_column_int(stmt, 41); + mob.add_abils.manareg = sqlite3_column_int(stmt, 42); + mob.add_abils.cast_success = sqlite3_column_int(stmt, 43); + mob.add_abils.morale = sqlite3_column_int(stmt, 44); + mob.add_abils.initiative_add = sqlite3_column_int(stmt, 45); + mob.add_abils.absorb = sqlite3_column_int(stmt, 46); + mob.add_abils.aresist = sqlite3_column_int(stmt, 47); + mob.add_abils.mresist = sqlite3_column_int(stmt, 48); + mob.add_abils.presist = sqlite3_column_int(stmt, 49); + mob.mob_specials.attack_type = sqlite3_column_int(stmt, 50); + mob.mob_specials.like_work = sqlite3_column_int(stmt, 51); + mob.mob_specials.MaxFactor = sqlite3_column_int(stmt, 52); + mob.mob_specials.extra_attack = sqlite3_column_int(stmt, 53); + mob.set_remort(sqlite3_column_int(stmt, 54)); + + // special_bitvector (TEXT - FlagData) + std::string special_bv = GetText(stmt, 55); + if (!special_bv.empty()) + { + mob.mob_specials.npc_flags.from_string((char *)special_bv.c_str()); + } + + // role (TEXT - bitset<9>) + std::string role_str = GetText(stmt, 56); + if (!role_str.empty()) + { + CharData::role_t role(role_str); + mob.set_role(role); + } + + // Set NPC flag + + // Setup index + int zone_vnum = vnum / 100; + auto zone_it = zone_vnum_to_rnum.find(zone_vnum); + if (zone_it != zone_vnum_to_rnum.end()) + { + if (zone_table[zone_it->second].RnumMobsLocation.first == -1) + { + zone_table[zone_it->second].RnumMobsLocation.first = top_of_mobt; + } + zone_table[zone_it->second].RnumMobsLocation.second = top_of_mobt; + } + + mob_index[top_of_mobt].vnum = vnum; + mob_index[top_of_mobt].total_online = 0; + mob_index[top_of_mobt].stored = 0; + mob_index[top_of_mobt].func = nullptr; + mob_index[top_of_mobt].farg = nullptr; + mob_index[top_of_mobt].proto = nullptr; + mob_index[top_of_mobt].set_idx = -1; + + // Initialize test data if needed + if (mob.GetLevel() == 0) + SetTestData(&mob); + mob.set_rnum(top_of_mobt); + + top_of_mobt++; + } + sqlite3_finalize(stmt); + + // top_of_mobt should be last valid index, not count + if (top_of_mobt > 0) + { + top_of_mobt--; + } + + // Load mob flags + LoadMobFlags(); + + // Load mob skills + LoadMobSkills(); + + // Load mob triggers + LoadMobTriggers(); + + // Load Enhanced mob fields (arrays) + LoadMobResistances(); + LoadMobSaves(); + LoadMobFeats(); + LoadMobSpells(); + LoadMobHelpers(); + LoadMobDestinations(); + + log("Loaded %d mobs from SQLite.", top_of_mobt + 1); +} + +void SqliteWorldDataSource::LoadMobFlags() +{ + const char *sql = "SELECT mob_vnum, flag_category, flag_name FROM mob_flags"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + int flags_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + std::string category = GetText(stmt, 1); + std::string flag_name = GetText(stmt, 2); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + CharData &mob = mob_proto[it->second]; + + if (strcmp(category.c_str(), "action") == 0) + { + auto flag_it = mob_action_flag_map.find(flag_name); + if (flag_it != mob_action_flag_map.end() && flag_it->second != 0) + { + mob.SetFlag(static_cast(flag_it->second)); + flags_set++; + } + } + else if (strcmp(category.c_str(), "affect") == 0) + { + auto flag_it = mob_affect_flag_map.find(flag_name); + if (flag_it != mob_affect_flag_map.end() && flag_it->second != 0) + { + AFF_FLAGS(&mob).set(static_cast(flag_it->second)); + flags_set++; + } + } + } + sqlite3_finalize(stmt); + + log(" Set %d mob flags.", flags_set); +} + +void SqliteWorldDataSource::LoadMobSkills() +{ + const char *sql = "SELECT mob_vnum, skill_id, value FROM mob_skills"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + int skills_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + int skill_id = sqlite3_column_int(stmt, 1); + int value = sqlite3_column_int(stmt, 2); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + auto skill = static_cast(skill_id); + mob_proto[it->second].set_skill(skill, value); + skills_set++; + } + sqlite3_finalize(stmt); + + if (skills_set > 0) + { + log(" Set %d mob skills.", skills_set); + } +} + +void SqliteWorldDataSource::LoadMobTriggers() +{ + const char *sql = "SELECT entity_vnum, trigger_vnum FROM entity_triggers WHERE entity_type = 'mob' ORDER BY trigger_order"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + int trigger_vnum = sqlite3_column_int(stmt, 1); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + AttachTriggerToMob(it->second, trigger_vnum, mob_vnum); + } + sqlite3_finalize(stmt); +} + +void SqliteWorldDataSource::LoadMobResistances() +{ + const char *sql = "SELECT mob_vnum, resist_type, value FROM mob_resistances"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + int resistances_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + int resist_type = sqlite3_column_int(stmt, 1); + int value = sqlite3_column_int(stmt, 2); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + CharData &mob = mob_proto[it->second]; + if (resist_type >= 0 && resist_type < static_cast(mob.add_abils.apply_resistance.size())) + { + mob.add_abils.apply_resistance[resist_type] = value; + resistances_set++; + } + } + sqlite3_finalize(stmt); + + if (resistances_set > 0) + { + log(" Set %d mob resistances.", resistances_set); + } +} + +void SqliteWorldDataSource::LoadMobSaves() +{ + const char *sql = "SELECT mob_vnum, save_type, value FROM mob_saves"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + int saves_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + int save_type = sqlite3_column_int(stmt, 1); + int value = sqlite3_column_int(stmt, 2); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + CharData &mob = mob_proto[it->second]; + if (save_type >= 0 && save_type < static_cast(mob.add_abils.apply_saving_throw.size())) + { + mob.add_abils.apply_saving_throw[save_type] = value; + saves_set++; + } + } + sqlite3_finalize(stmt); + + if (saves_set > 0) + { + log(" Set %d mob saves.", saves_set); + } +} + +void SqliteWorldDataSource::LoadMobFeats() +{ + const char *sql = "SELECT mob_vnum, feat_id FROM mob_feats"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + int feats_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + int feat_id = sqlite3_column_int(stmt, 1); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + CharData &mob = mob_proto[it->second]; + if (feat_id >= 0 && feat_id < static_cast(mob.real_abils.Feats.size())) + { + mob.real_abils.Feats.set(feat_id); + feats_set++; + } + } + sqlite3_finalize(stmt); + + if (feats_set > 0) + { + log(" Set %d mob feats.", feats_set); + } +} + +void SqliteWorldDataSource::LoadMobSpells() +{ + const char *sql = "SELECT mob_vnum, spell_id FROM mob_spells"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + int spells_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + int spell_id = sqlite3_column_int(stmt, 1); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + CharData &mob = mob_proto[it->second]; + if (spell_id >= 0 && spell_id < static_cast(mob.real_abils.SplKnw.size())) + { + mob.real_abils.SplKnw[spell_id] = 1; // Mark spell as known + spells_set++; + } + } + sqlite3_finalize(stmt); + + if (spells_set > 0) + { + log(" Set %d mob spells.", spells_set); + } +} + +void SqliteWorldDataSource::LoadMobHelpers() +{ + const char *sql = "SELECT mob_vnum, helper_vnum FROM mob_helpers"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + int helpers_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + int helper_vnum = sqlite3_column_int(stmt, 1); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + CharData &mob = mob_proto[it->second]; + mob.summon_helpers.push_back(helper_vnum); + helpers_set++; + } + sqlite3_finalize(stmt); + + if (helpers_set > 0) + { + log(" Set %d mob helpers.", helpers_set); + } +} + +void SqliteWorldDataSource::LoadMobDestinations() +{ + const char *sql = "SELECT mob_vnum, dest_order, room_vnum FROM mob_destinations ORDER BY mob_vnum, dest_order"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + // Build vnum to rnum map + std::map vnum_to_rnum; + for (MobRnum i = 0; i <= top_of_mobt; i++) + { + vnum_to_rnum[mob_index[i].vnum] = i; + } + + int destinations_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int mob_vnum = sqlite3_column_int(stmt, 0); + int dest_order = sqlite3_column_int(stmt, 1); + int room_vnum = sqlite3_column_int(stmt, 2); + + auto it = vnum_to_rnum.find(mob_vnum); + if (it == vnum_to_rnum.end()) continue; + + CharData &mob = mob_proto[it->second]; + if (dest_order >= 0 && dest_order < static_cast(mob.mob_specials.dest.size())) + { + mob.mob_specials.dest[dest_order] = room_vnum; + destinations_set++; + } + } + sqlite3_finalize(stmt); + + if (destinations_set > 0) + { + log(" Set %d mob destinations.", destinations_set); + } +} + +// ============================================================================ +// Object Loading +// ============================================================================ + +void SqliteWorldDataSource::LoadObjects() +{ + log("Loading objects from SQLite database."); + + if (!OpenDatabase()) + { + log("SYSERR: Failed to open SQLite database for object loading."); + return; + } + + int obj_count = GetCount("objects"); + if (obj_count == 0) + { + log("No objects found in SQLite database."); + return; + } + + log(" %d objs.", obj_count); + + const char *sql = "SELECT vnum, aliases, name_nom, name_gen, name_dat, name_acc, name_ins, name_pre, " + "short_desc, action_desc, obj_type_id, material, value0, value1, value2, value3, " + "weight, cost, rent_off, rent_on, spec_param, max_durability, cur_durability, " + "timer, spell, level, sex, max_in_world, minimum_remorts " + "FROM objects WHERE enabled = 1 ORDER BY vnum"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare object query: %s", sqlite3_errmsg(m_db)); + return; + } + + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int vnum = sqlite3_column_int(stmt, 0); + + auto obj = std::make_shared(vnum); + + // Names + obj->set_aliases(GetText(stmt, 1)); + obj->set_short_description(utils::colorLOW(GetText(stmt, 2))); + obj->set_PName(ECase::kNom, utils::colorLOW(GetText(stmt, 2))); + obj->set_PName(ECase::kGen, utils::colorLOW(GetText(stmt, 3))); + obj->set_PName(ECase::kDat, utils::colorLOW(GetText(stmt, 4))); + obj->set_PName(ECase::kAcc, utils::colorLOW(GetText(stmt, 5))); + obj->set_PName(ECase::kIns, utils::colorLOW(GetText(stmt, 6))); + obj->set_PName(ECase::kPre, utils::colorLOW(GetText(stmt, 7))); + obj->set_description(utils::colorCAP(GetText(stmt, 8))); + obj->set_action_description(GetText(stmt, 9)); + + // Type + int obj_type_id = sqlite3_column_int(stmt, 10); + obj->set_type(static_cast(obj_type_id)); + + // Material + obj->set_material(static_cast(sqlite3_column_int(stmt, 11))); + + // Values + std::string val0 = GetText(stmt, 12); + std::string val1 = GetText(stmt, 13); + std::string val2 = GetText(stmt, 14); + std::string val3 = GetText(stmt, 15); + + // Parse values - try as numbers + + obj->set_val(0, SafeStol(val0)); + obj->set_val(1, SafeStol(val1)); + obj->set_val(2, SafeStol(val2)); + obj->set_val(3, SafeStol(val3)); + + // Physical properties + obj->set_weight(sqlite3_column_int(stmt, 16)); + // Match Legacy: weight of containers must exceed current quantity + if (obj->get_type() == EObjType::kLiquidContainer || obj->get_type() == EObjType::kFountain) + { + if (obj->get_weight() < obj->get_val(1)) + { + obj->set_weight(obj->get_val(1) + 5); + } + } + obj->set_cost(sqlite3_column_int(stmt, 17)); + obj->set_rent_off(sqlite3_column_int(stmt, 18)); + obj->set_rent_on(sqlite3_column_int(stmt, 19)); + obj->set_spec_param(sqlite3_column_int(stmt, 20)); + int max_dur = sqlite3_column_int(stmt, 21); + int cur_dur = sqlite3_column_int(stmt, 22); + obj->set_maximum_durability(max_dur); + obj->set_current_durability(std::min(max_dur, cur_dur)); // Match Legacy: MIN(max, cur) + int timer = sqlite3_column_int(stmt, 23); + if (timer <= 0) { + timer = ObjData::SEVEN_DAYS; // Match Legacy: default timer is 7 days + } + if (timer > 99999) timer = 99999; // Cap timer like Legacy + obj->set_timer(timer); + obj->set_spell(sqlite3_column_int(stmt, 24)); + obj->set_level(sqlite3_column_int(stmt, 25)); + obj->set_sex(static_cast(sqlite3_column_int(stmt, 26))); + obj->set_max_in_world(sqlite3_column_type(stmt, 27) == SQLITE_NULL ? -1 : sqlite3_column_int(stmt, 27)); + obj->set_minimum_remorts(sqlite3_column_int(stmt, 28)); + + obj_proto.add(obj, vnum); + } + sqlite3_finalize(stmt); + + // Load object flags + LoadObjectFlags(); + + // Load object applies + LoadObjectApplies(); + + // Load object triggers + LoadObjectTriggers(); + + // Load object extra descriptions + LoadObjectExtraDescriptions(); + + log("Loaded %zu objects from SQLite.", obj_proto.size()); +} + +void SqliteWorldDataSource::LoadObjectFlags() +{ + const char *sql = "SELECT obj_vnum, flag_category, flag_name FROM obj_flags"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + int flags_set = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int obj_vnum = sqlite3_column_int(stmt, 0); + std::string category = GetText(stmt, 1); + std::string flag_name = GetText(stmt, 2); + + int rnum = obj_proto.get_rnum(obj_vnum); + if (rnum < 0) continue; + + if (strcmp(category.c_str(), "extra") == 0) + { + auto flag_it = obj_extra_flag_map.find(flag_name); + if (flag_it != obj_extra_flag_map.end()) + { + obj_proto[rnum]->set_extra_flag(flag_it->second); + flags_set++; + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + // Handle UNUSED_XX flags - extract bit number and set directly + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + obj_proto[rnum]->toggle_extra_flag(plane, 1 << bit_in_plane); + flags_set++; + } + } + else if (strcmp(category.c_str(), "wear") == 0) + { + auto flag_it = obj_wear_flag_map.find(flag_name); + if (flag_it != obj_wear_flag_map.end()) + { + int wear_flags = obj_proto[rnum]->get_wear_flags(); + wear_flags |= to_underlying(flag_it->second); + obj_proto[rnum]->set_wear_flags(wear_flags); + flags_set++; + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + // Handle UNUSED_XX flags - extract bit number and set directly + int bit = std::stoi(flag_name.substr(7)); + int wear_flags = obj_proto[rnum]->get_wear_flags(); + wear_flags |= (1 << bit); + obj_proto[rnum]->set_wear_flags(wear_flags); + } + } + else if (strcmp(category.c_str(), "no") == 0) + { + auto flag_it = obj_no_flag_map.find(flag_name); + if (flag_it != obj_no_flag_map.end()) + { + obj_proto[rnum]->set_no_flag(flag_it->second); + flags_set++; + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + // Handle UNUSED_XX flags - extract bit number and set directly + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + obj_proto[rnum]->toggle_no_flag(plane, 1 << bit_in_plane); + flags_set++; + } + } + else if (strcmp(category.c_str(), "anti") == 0) + { + auto flag_it = obj_anti_flag_map.find(flag_name); + if (flag_it != obj_anti_flag_map.end()) + { + obj_proto[rnum]->set_anti_flag(flag_it->second); + flags_set++; + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + // Handle UNUSED_XX flags - extract bit number and set directly + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + obj_proto[rnum]->toggle_anti_flag(plane, 1 << bit_in_plane); + flags_set++; + } + } + else if (strcmp(category.c_str(), "affect") == 0) + { + auto flag_it = obj_affect_flag_map.find(flag_name); + if (flag_it != obj_affect_flag_map.end()) + { + obj_proto[rnum]->SetEWeaponAffectFlag(flag_it->second); + flags_set++; + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + // Handle UNUSED_XX flags - extract bit number and set directly + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + obj_proto[rnum]->toggle_affect_flag(plane, 1 << bit_in_plane); + flags_set++; + } + } + } + sqlite3_finalize(stmt); + + log(" Set %d object flags.", flags_set); +} + +void SqliteWorldDataSource::LoadObjectApplies() +{ + const char *sql = "SELECT obj_vnum, location_id, modifier FROM obj_applies"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + int applies_loaded = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int obj_vnum = sqlite3_column_int(stmt, 0); + int location_id = sqlite3_column_int(stmt, 1); + int modifier = sqlite3_column_int(stmt, 2); + + int rnum = obj_proto.get_rnum(obj_vnum); + if (rnum < 0) continue; + + int location = location_id; + + // Find first empty apply slot + for (int i = 0; i < kMaxObjAffect; i++) + { + if (obj_proto[rnum]->get_affected(i).location == EApply::kNone) + { + obj_proto[rnum]->set_affected(i, static_cast(location), modifier); + applies_loaded++; + break; + } + } + } + sqlite3_finalize(stmt); + + log(" Loaded %d object applies.", applies_loaded); +} + +void SqliteWorldDataSource::LoadObjectTriggers() +{ + const char *sql = "SELECT entity_vnum, trigger_vnum FROM entity_triggers WHERE entity_type = 'obj' ORDER BY trigger_order"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int obj_vnum = sqlite3_column_int(stmt, 0); + int trigger_vnum = sqlite3_column_int(stmt, 1); + + int rnum = obj_proto.get_rnum(obj_vnum); + if (rnum >= 0) + { + AttachTriggerToObject(rnum, trigger_vnum, obj_vnum); + } + } + sqlite3_finalize(stmt); +} + +void SqliteWorldDataSource::LoadObjectExtraDescriptions() +{ + const char *sql = "SELECT entity_vnum, keywords, description FROM extra_descriptions WHERE entity_type = 'obj'"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) + { + return; + } + + int loaded = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + int obj_vnum = sqlite3_column_int(stmt, 0); + std::string keywords = GetText(stmt, 1); + std::string description = GetText(stmt, 2); + + int rnum = obj_proto.get_rnum(obj_vnum); + if (rnum < 0) continue; + + auto ex_desc = std::make_shared(); + ex_desc->set_keyword(keywords); + ex_desc->set_description(description); + ex_desc->next = obj_proto[rnum]->get_ex_description(); + obj_proto[rnum]->set_ex_description(ex_desc); + loaded++; + } + sqlite3_finalize(stmt); + + if (loaded > 0) + { + log(" Loaded %d object extra descriptions.", loaded); + } + + // Post-processing to match Legacy loader behavior + for (size_t i = 0; i < obj_proto.size(); ++i) + { + // Clear runtime flags that should not be in prototypes + obj_proto[i]->unset_extraflag(EObjFlag::kTransformed); + obj_proto[i]->unset_extraflag(EObjFlag::kTicktimer); + + // Objects with zone decay flags should have unlimited max_in_world + if (obj_proto[i]->has_flag(EObjFlag::kZonedecay) + || obj_proto[i]->has_flag(EObjFlag::kRepopDecay)) + { + obj_proto[i]->set_max_in_world(-1); + } + } +} + +// ============================================================================ +// Save Methods (read-only) +// ============================================================================ + +// ============================================================================ +// Transaction helpers +// ============================================================================ + +bool SqliteWorldDataSource::BeginTransaction() +{ + char *err_msg = nullptr; + int rc = sqlite3_exec(m_db, "BEGIN TRANSACTION", nullptr, nullptr, &err_msg); + if (rc != SQLITE_OK) + { + log("SYSERR: Failed to begin transaction: %s", err_msg); + sqlite3_free(err_msg); + return false; + } + return true; +} + +bool SqliteWorldDataSource::CommitTransaction() +{ + char *err_msg = nullptr; + int rc = sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, &err_msg); + if (rc != SQLITE_OK) + { + log("SYSERR: Failed to commit transaction: %s", err_msg); + sqlite3_free(err_msg); + return false; + } + return true; +} + +bool SqliteWorldDataSource::RollbackTransaction() +{ + char *err_msg = nullptr; + int rc = sqlite3_exec(m_db, "ROLLBACK", nullptr, nullptr, &err_msg); + if (rc != SQLITE_OK) + { + log("SYSERR: Failed to rollback transaction: %s", err_msg); + sqlite3_free(err_msg); + return false; + } + return true; +} + +bool SqliteWorldDataSource::ExecuteStatement(const std::string &sql, const std::string &operation) +{ + char *err_msg = nullptr; + int rc = sqlite3_exec(m_db, sql.c_str(), nullptr, nullptr, &err_msg); + if (rc != SQLITE_OK) + { + log("SYSERR: Failed to %s: %s", operation.c_str(), err_msg); + log("SYSERR: SQL: %s", sql.c_str()); + sqlite3_free(err_msg); + return false; + } + return true; +} + +// ============================================================================ +// Save helper methods +// ============================================================================ + +void SqliteWorldDataSource::SaveZoneRecord(const ZoneData &zone) +{ + sqlite3_stmt *stmt = nullptr; + const char *sql = + "REPLACE INTO zones (vnum, name, comment, location, author, description, " + "first_room, top_room, mode, zone_type, zone_group, entrance, " + "lifespan, reset_mode, reset_idle, under_construction, enabled) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)"; + + int rc = sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr); + if (rc != SQLITE_OK) + { + log("SYSERR: Failed to prepare zone insert: %s", sqlite3_errmsg(m_db)); + return; + } + + // Bind values + sqlite3_bind_int(stmt, 1, zone.vnum); + sqlite3_bind_text(stmt, 2, zone.name.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, zone.comment.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 4, zone.location.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 5, zone.author.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 6, zone.description.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 7, zone.vnum * 100); // first_room + sqlite3_bind_int(stmt, 8, zone.top); // top_room + sqlite3_bind_int(stmt, 9, zone.level); // mode + sqlite3_bind_int(stmt, 10, zone.type); // zone_type + sqlite3_bind_int(stmt, 11, zone.group); // zone_group + sqlite3_bind_int(stmt, 12, zone.entrance); + sqlite3_bind_int(stmt, 13, zone.lifespan); + sqlite3_bind_int(stmt, 14, zone.reset_mode); + sqlite3_bind_int(stmt, 15, zone.reset_idle ? 1 : 0); + sqlite3_bind_int(stmt, 16, zone.under_construction); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + { + log("SYSERR: Failed to insert zone %d: %s", zone.vnum, sqlite3_errmsg(m_db)); + } + + sqlite3_finalize(stmt); +} + +void SqliteWorldDataSource::SaveZoneGroups(int zone_vnum, const ZoneData &zone) +{ + // Delete existing groups + std::string delete_sql = "DELETE FROM zone_groups WHERE zone_vnum = " + std::to_string(zone_vnum); + ExecuteStatement(delete_sql, "delete zone groups"); + + // Insert typeA groups + sqlite3_stmt *stmt = nullptr; + const char *insert_sql = "INSERT INTO zone_groups (zone_vnum, linked_zone_vnum, group_type) VALUES (?, ?, ?)"; + + for (int i = 0; i < zone.typeA_count; ++i) + { + if (sqlite3_prepare_v2(m_db, insert_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, zone_vnum); + sqlite3_bind_int(stmt, 2, zone.typeA_list[i]); + sqlite3_bind_text(stmt, 3, "A", -1, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + + // Insert typeB groups + for (int i = 0; i < zone.typeB_count; ++i) + { + if (sqlite3_prepare_v2(m_db, insert_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, zone_vnum); + sqlite3_bind_int(stmt, 2, zone.typeB_list[i]); + sqlite3_bind_text(stmt, 3, "B", -1, SQLITE_STATIC); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } +} + +void SqliteWorldDataSource::SaveZoneCommands(int zone_vnum, const struct reset_com *commands) +{ + if (!commands) + { + return; + } + + // Delete existing commands + std::string delete_sql = "DELETE FROM zone_commands WHERE zone_vnum = " + std::to_string(zone_vnum); + ExecuteStatement(delete_sql, "delete zone commands"); + + // Insert commands + sqlite3_stmt *stmt = nullptr; + const char *insert_sql = + "INSERT INTO zone_commands (zone_vnum, command_order, command, if_flag, " + "arg1, arg2, arg3, arg4, sarg1, sarg2) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + int order = 0; + for (int i = 0; commands[i].command != 'S'; ++i) + { + // Skip A and B commands - they're in zone_groups + if (commands[i].command == 'A' || commands[i].command == 'B') + { + continue; + } + + if (sqlite3_prepare_v2(m_db, insert_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, zone_vnum); + sqlite3_bind_int(stmt, 2, order++); + + char cmd_str[2] = {commands[i].command, '\0'}; + sqlite3_bind_text(stmt, 3, cmd_str, -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 4, commands[i].if_flag); + sqlite3_bind_int(stmt, 5, commands[i].arg1); + sqlite3_bind_int(stmt, 6, commands[i].arg2); + sqlite3_bind_int(stmt, 7, commands[i].arg3); + sqlite3_bind_int(stmt, 8, commands[i].arg4); + + if (commands[i].sarg1) + { + sqlite3_bind_text(stmt, 9, commands[i].sarg1, -1, SQLITE_TRANSIENT); + } + else + { + sqlite3_bind_null(stmt, 9); + } + + if (commands[i].sarg2) + { + sqlite3_bind_text(stmt, 10, commands[i].sarg2, -1, SQLITE_TRANSIENT); + } + else + { + sqlite3_bind_null(stmt, 10); + } + + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } +} + +void SqliteWorldDataSource::SaveZone(int zone_rnum) +{ + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveZone", zone_rnum); + return; + } + + if (!m_db) + { + log("SYSERR: Database not open for SaveZone"); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + + if (!BeginTransaction()) + { + return; + } + + SaveZoneRecord(zone); + SaveZoneGroups(zone.vnum, zone); + SaveZoneCommands(zone.vnum, zone.cmd); + + if (!CommitTransaction()) + { + RollbackTransaction(); + log("SYSERR: Failed to save zone %d", zone.vnum); + return; + } + + log("Saved zone %d to SQLite database", zone.vnum); +} + + +void SqliteWorldDataSource::SaveTriggerRecord(int trig_vnum, const Trigger *trig) +{ + if (!trig) + { + return; + } + + // Delete existing trigger and bindings (CASCADE will handle trigger_type_bindings) + std::string delete_sql = "DELETE FROM triggers WHERE vnum = " + std::to_string(trig_vnum); + ExecuteStatement(delete_sql, "delete trigger"); + + // Insert trigger record + sqlite3_stmt *stmt = nullptr; + const char *insert_sql = + "INSERT INTO triggers (vnum, name, attach_type_id, narg, arglist, script, enabled) " + "VALUES (?, ?, ?, ?, ?, ?, 1)"; + + if (sqlite3_prepare_v2(m_db, insert_sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare trigger insert: %s", sqlite3_errmsg(m_db)); + return; + } + + // Build script text from cmdlist + std::string script_text; + if (trig->cmdlist) + { + for (auto cmd = *trig->cmdlist; cmd; cmd = cmd->next) + { + if (!cmd->cmd.empty()) + { + script_text += cmd->cmd; + script_text += "\n"; + } + } + } + + sqlite3_bind_int(stmt, 1, trig_vnum); + sqlite3_bind_text(stmt, 2, trig->get_name().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 3, trig->get_attach_type()); + sqlite3_bind_int(stmt, 4, GET_TRIG_NARG(trig)); + sqlite3_bind_text(stmt, 5, trig->arglist.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 6, script_text.c_str(), -1, SQLITE_TRANSIENT); + + int rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + { + log("SYSERR: Failed to insert trigger %d: %s", trig_vnum, sqlite3_errmsg(m_db)); + } + sqlite3_finalize(stmt); + + // Insert trigger type bindings + const char *binding_sql = "INSERT INTO trigger_type_bindings (trigger_vnum, type_char) VALUES (?, ?)"; + + long trigger_type = GET_TRIG_TYPE(trig); + for (int bit = 0; bit < 52; ++bit) // 26 lowercase + 26 uppercase + { + if (trigger_type & (1L << bit)) + { + char type_ch = (bit < 26) ? ('a' + bit) : ('A' + (bit - 26)); + + if (sqlite3_prepare_v2(m_db, binding_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, trig_vnum); + char ch_str[2] = {type_ch, '\0'}; + sqlite3_bind_text(stmt, 2, ch_str, -1, SQLITE_TRANSIENT); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } +} + +bool SqliteWorldDataSource::SaveTriggers(int zone_rnum, int specific_vnum, int notify_level) +{ + (void)specific_vnum; // SQLite format always saves entire zone + (void)notify_level; // SQLite saves don't use mudlog - errors go to log file + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveTriggers", zone_rnum); + return false; + } + + if (!m_db) + { + log("SYSERR: Database not open for SaveTriggers"); + return false; + } + + const ZoneData &zone = zone_table[zone_rnum]; + + // Get trigger range for this zone + TrgRnum first_trig = zone.RnumTrigsLocation.first; + TrgRnum last_trig = zone.RnumTrigsLocation.second; + + if (first_trig == -1 || last_trig == -1) + { + log("Zone %d has no triggers to save", zone.vnum); + return true; + } + + if (!BeginTransaction()) + { + return false; + } + + int saved_count = 0; + for (TrgRnum trig_rnum = first_trig; trig_rnum <= last_trig && trig_rnum <= top_of_trigt; ++trig_rnum) + { + if (!trig_index[trig_rnum]) + { + continue; + } + + int trig_vnum = trig_index[trig_rnum]->vnum; + Trigger *trig = trig_index[trig_rnum]->proto; + + if (!trig) + { + continue; + } + + SaveTriggerRecord(trig_vnum, trig); + ++saved_count; + } + + if (!CommitTransaction()) + { + RollbackTransaction(); + log("SYSERR: Failed to save triggers for zone %d", zone.vnum); + return false; + } + + log("Saved %d triggers for zone %d", saved_count, zone.vnum); + return true; +} + + +void SqliteWorldDataSource::SaveRoomRecord(RoomData *room) +{ + if (!room) + { + return; + } + + int room_vnum = room->vnum; + int zone_vnum = room->vnum / 100; // Integer division gives zone vnum + + // Delete existing room data (CASCADE will handle related tables) + std::string delete_sql = "DELETE FROM rooms WHERE vnum = " + std::to_string(room_vnum); + ExecuteStatement(delete_sql, "delete room"); + + // Insert room record + sqlite3_stmt *stmt = nullptr; + const char *insert_sql = + "INSERT INTO rooms (vnum, zone_vnum, name, description, sector_id, enabled) " + "VALUES (?, ?, ?, ?, ?, 1)"; + + if (sqlite3_prepare_v2(m_db, insert_sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare room insert: %s", sqlite3_errmsg(m_db)); + return; + } + + sqlite3_bind_int(stmt, 1, room_vnum); + sqlite3_bind_int(stmt, 2, zone_vnum); + sqlite3_bind_text(stmt, 3, room->name, -1, SQLITE_TRANSIENT); + + std::string description = GlobalObjects::descriptions().get(room->description_num); + sqlite3_bind_text(stmt, 4, description.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 5, static_cast(room->sector_type)); + + int rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + { + log("SYSERR: Failed to insert room %d: %s", room_vnum, sqlite3_errmsg(m_db)); + } + sqlite3_finalize(stmt); + + // Save room flags + FlagData room_flags = room->read_flags(); + SaveFlagsToTable(m_db, "room_flags", "room_vnum", room_vnum, room_flags, room_flag_map); + + // Save room exits + const char *exit_sql = + "INSERT INTO room_exits (room_vnum, direction_id, description, keywords, exit_flags, key_vnum, to_room, lock_complexity) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + + for (int dir = 0; dir < EDirection::kMaxDirNum; ++dir) + { + if (!room->dir_option[dir]) + { + continue; + } + + if (sqlite3_prepare_v2(m_db, exit_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, room_vnum); + sqlite3_bind_int(stmt, 2, dir); + + if (!room->dir_option[dir]->general_description.empty()) + { + sqlite3_bind_text(stmt, 3, room->dir_option[dir]->general_description.c_str(), -1, SQLITE_TRANSIENT); + } + else + { + sqlite3_bind_null(stmt, 3); + } + + if (room->dir_option[dir]->keyword) + { + sqlite3_bind_text(stmt, 4, room->dir_option[dir]->keyword, -1, SQLITE_TRANSIENT); + } + else + { + sqlite3_bind_null(stmt, 4); + } + + // Save exit flags as string (numeric value) + std::string exit_flags_str = std::to_string(room->dir_option[dir]->exit_info); + if (!exit_flags_str.empty() && exit_flags_str != "0") + { + sqlite3_bind_text(stmt, 5, exit_flags_str.c_str(), -1, SQLITE_TRANSIENT); + } + else + { + sqlite3_bind_null(stmt, 5); + } + + sqlite3_bind_int(stmt, 6, room->dir_option[dir]->key); + sqlite3_bind_int(stmt, 7, room->dir_option[dir]->to_room() != kNowhere ? world[room->dir_option[dir]->to_room()]->vnum : -1); + sqlite3_bind_int(stmt, 8, room->dir_option[dir]->lock_complexity); + + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + + // Save extra descriptions + const char *extra_sql = + "INSERT INTO extra_descriptions (entity_type, entity_vnum, keyword, description) " + "VALUES ('room', ?, ?, ?)"; + + for (auto exdesc = room->ex_description; exdesc; exdesc = exdesc->next) + { + if (sqlite3_prepare_v2(m_db, extra_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, room_vnum); + sqlite3_bind_text(stmt, 2, exdesc->keyword, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, exdesc->description, -1, SQLITE_TRANSIENT); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + + // Save room triggers + const char *trig_sql = + "INSERT INTO entity_triggers (entity_type, entity_vnum, trigger_vnum, trigger_order) " + "VALUES ('room', ?, ?, ?)"; + + int trig_order = 0; + if (room->script && !room->script->script_trig_list.empty()) + { + for (auto trig : room->script->script_trig_list) + { + if (sqlite3_prepare_v2(m_db, trig_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, room_vnum); + sqlite3_bind_int(stmt, 2, GET_TRIG_VNUM(trig)); + sqlite3_bind_int(stmt, 3, trig_order++); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } +} + +void SqliteWorldDataSource::SaveRooms(int zone_rnum, int specific_vnum) +{ + (void)specific_vnum; // SQLite format always saves entire zone + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveRooms", zone_rnum); + return; + } + + if (!m_db) + { + log("SYSERR: Database not open for SaveRooms"); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + + // Get room range for this zone + RoomRnum first_room = zone.RnumRoomsLocation.first; + RoomRnum last_room = zone.RnumRoomsLocation.second; + + if (first_room == -1 || last_room == -1) + { + log("Zone %d has no rooms to save", zone.vnum); + return; + } + + if (!BeginTransaction()) + { + return; + } + + int saved_count = 0; + for (RoomRnum room_rnum = first_room; room_rnum <= last_room && room_rnum <= top_of_world; ++room_rnum) + { + RoomData *room = world[room_rnum]; + if (!room || room->vnum < zone.vnum * 100 || room->vnum > zone.top) + { + continue; + } + + SaveRoomRecord(room); + ++saved_count; + } + + if (!CommitTransaction()) + { + RollbackTransaction(); + log("SYSERR: Failed to save rooms for zone %d", zone.vnum); + return; + } + + log("Saved %d rooms for zone %d", saved_count, zone.vnum); +} + + +void SqliteWorldDataSource::SaveMobRecord(int mob_vnum, CharData &mob) +{ + // Delete existing mob data (CASCADE will handle related tables) + std::string delete_sql = "DELETE FROM mobs WHERE vnum = " + std::to_string(mob_vnum); + ExecuteStatement(delete_sql, "delete mob"); + + // Insert mob main record + sqlite3_stmt *stmt = nullptr; + const char *insert_sql = + "INSERT INTO mobs (vnum, aliases, name_nom, name_gen, name_dat, name_acc, name_ins, name_pre, " + "short_desc, long_desc, alignment, mob_type, level, hitroll_penalty, armor, " + "hp_dice_count, hp_dice_size, hp_bonus, dam_dice_count, dam_dice_size, dam_bonus, " + "gold_dice_count, gold_dice_size, gold_bonus, experience, default_pos, start_pos, " + "sex, size, height, weight, mob_class, race, " + "attr_str, attr_dex, attr_int, attr_wis, attr_con, attr_cha, " + "attr_str_add, hp_regen, armour_bonus, mana_regen, cast_success, morale, " + "initiative_add, absorb, aresist, mresist, presist, bare_hand_attack, " + "like_work, max_factor, extra_attack, mob_remort, special_bitvector, role, enabled) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)"; + + if (sqlite3_prepare_v2(m_db, insert_sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare mob insert: %s", sqlite3_errmsg(m_db)); + return; + } + + int col = 1; + sqlite3_bind_int(stmt, col++, mob_vnum); + + // Names + sqlite3_bind_text(stmt, col++, GET_PC_NAME(&mob), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, mob.player_data.PNames[ECase::kNom].c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, mob.player_data.PNames[ECase::kGen].c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, mob.player_data.PNames[ECase::kDat].c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, mob.player_data.PNames[ECase::kAcc].c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, mob.player_data.PNames[ECase::kIns].c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, mob.player_data.PNames[ECase::kPre].c_str(), -1, SQLITE_TRANSIENT); + + // Descriptions + sqlite3_bind_text(stmt, col++, mob.player_data.long_descr.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, mob.player_data.description.c_str(), -1, SQLITE_TRANSIENT); + + // Base parameters + sqlite3_bind_int(stmt, col++, GET_ALIGNMENT(&mob)); + + // Mob type (E or S) + std::string mob_type = (mob.get_str() > 0) ? "E" : "S"; + sqlite3_bind_text(stmt, col++, mob_type.c_str(), -1, SQLITE_TRANSIENT); + + // Stats + sqlite3_bind_int(stmt, col++, mob.GetLevel()); + sqlite3_bind_int(stmt, col++, GET_HR(&mob)); + sqlite3_bind_int(stmt, col++, GET_AC(&mob)); + + // HP dice (stored in mem_queue for dice, hit for bonus) + sqlite3_bind_int(stmt, col++, mob.mem_queue.total); + sqlite3_bind_int(stmt, col++, mob.mem_queue.stored); + sqlite3_bind_int(stmt, col++, mob.get_hit()); + + // Damage dice + sqlite3_bind_int(stmt, col++, mob.mob_specials.damnodice); + sqlite3_bind_int(stmt, col++, mob.mob_specials.damsizedice); + sqlite3_bind_int(stmt, col++, mob.real_abils.damroll); + + // Gold dice + sqlite3_bind_int(stmt, col++, mob.mob_specials.GoldNoDs); + sqlite3_bind_int(stmt, col++, mob.mob_specials.GoldSiDs); + sqlite3_bind_int(stmt, col++, mob.get_gold()); + + // Experience + sqlite3_bind_int(stmt, col++, mob.get_exp()); + + // Position (need to convert enum to string) + // For now use numeric values, TODO: add lookup + sqlite3_bind_int(stmt, col++, static_cast(mob.mob_specials.default_pos)); + sqlite3_bind_int(stmt, col++, static_cast(mob.GetPosition())); + + // Sex (need to convert enum to string) + sqlite3_bind_int(stmt, col++, static_cast(mob.get_sex())); + + // Physical attributes + sqlite3_bind_int(stmt, col++, GET_SIZE(&mob)); + sqlite3_bind_int(stmt, col++, GET_HEIGHT(&mob)); + sqlite3_bind_int(stmt, col++, GET_WEIGHT(&mob)); + + // Class and race + sqlite3_bind_int(stmt, col++, static_cast(mob.GetClass())); + sqlite3_bind_int(stmt, col++, static_cast(GET_RACE(&mob))); + + // Attributes (E-spec) + sqlite3_bind_int(stmt, col++, mob.get_str()); + sqlite3_bind_int(stmt, col++, mob.get_dex()); + sqlite3_bind_int(stmt, col++, mob.get_int()); + sqlite3_bind_int(stmt, col++, mob.get_wis()); + sqlite3_bind_int(stmt, col++, mob.get_con()); + sqlite3_bind_int(stmt, col++, mob.get_cha()); + + // Enhanced E-spec fields + sqlite3_bind_int(stmt, col++, mob.get_str_add()); + sqlite3_bind_int(stmt, col++, mob.add_abils.hitreg); + sqlite3_bind_int(stmt, col++, mob.add_abils.armour); + sqlite3_bind_int(stmt, col++, mob.add_abils.manareg); + sqlite3_bind_int(stmt, col++, mob.add_abils.cast_success); + sqlite3_bind_int(stmt, col++, mob.add_abils.morale); + sqlite3_bind_int(stmt, col++, mob.add_abils.initiative_add); + sqlite3_bind_int(stmt, col++, mob.add_abils.absorb); + sqlite3_bind_int(stmt, col++, mob.add_abils.aresist); + sqlite3_bind_int(stmt, col++, mob.add_abils.mresist); + sqlite3_bind_int(stmt, col++, mob.add_abils.presist); + sqlite3_bind_int(stmt, col++, mob.mob_specials.attack_type); + sqlite3_bind_int(stmt, col++, mob.mob_specials.like_work); + sqlite3_bind_int(stmt, col++, mob.mob_specials.MaxFactor); + sqlite3_bind_int(stmt, col++, mob.mob_specials.extra_attack); + sqlite3_bind_int(stmt, col++, mob.get_remort()); + + // special_bitvector (FlagData as TEXT) + char special_buf[kMaxStringLength]; + mob.mob_specials.npc_flags.tascii(FlagData::kPlanesNumber, special_buf); + if (special_buf[0] != '0' || special_buf[1] != 'a') + { + sqlite3_bind_text(stmt, col++, special_buf, -1, SQLITE_TRANSIENT); + } + else + { + sqlite3_bind_null(stmt, col++); + } + + + // Role (bitset<9> as TEXT) + std::string role_str = mob.get_role().to_string(); + if (!role_str.empty() && role_str != "000000000") + { + sqlite3_bind_text(stmt, col++, role_str.c_str(), -1, SQLITE_TRANSIENT); + } + else + { + sqlite3_bind_null(stmt, col++); + } + + int rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + { + log("SYSERR: Failed to insert mob %d: %s", mob_vnum, sqlite3_errmsg(m_db)); + } + sqlite3_finalize(stmt); + + + // Save mob action flags + FlagData act_flags = mob.char_specials.saved.act; + SaveFlagsToTable(m_db, "mob_flags", "mob_vnum", mob_vnum, act_flags, mob_action_flag_map, "action"); + + // Save mob affect flags + FlagData affect_flags = mob.char_specials.saved.affected_by; + SaveFlagsToTable(m_db, "mob_flags", "mob_vnum", mob_vnum, affect_flags, mob_affect_flag_map, "affect"); + + + // Save mob resistances + const char *resist_sql = "INSERT INTO mob_resistances (mob_vnum, resist_type, value) VALUES (?, ?, ?)"; + for (size_t i = 0; i < mob.add_abils.apply_resistance.size(); ++i) + { + if (GET_RESIST(&mob, i) != 0) + { + if (sqlite3_prepare_v2(m_db, resist_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, mob_vnum); + sqlite3_bind_int(stmt, 2, i); + sqlite3_bind_int(stmt, 3, GET_RESIST(&mob, i)); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + + // Save mob saves + const char *save_sql = "INSERT INTO mob_saves (mob_vnum, save_type, value) VALUES (?, ?, ?)"; + for (size_t i = 0; i < mob.add_abils.apply_saving_throw.size(); ++i) + { + if (mob.add_abils.apply_saving_throw[i] != 0) + { + if (sqlite3_prepare_v2(m_db, save_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, mob_vnum); + sqlite3_bind_int(stmt, 2, i); + sqlite3_bind_int(stmt, 3, mob.add_abils.apply_saving_throw[i]); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + + + + + // Save mob skills + const char *skill_sql = "INSERT INTO mob_skills (mob_vnum, skill_id, value) VALUES (?, ?, ?)"; + for (ESkill skill = ESkill::kFirst; skill <= ESkill::kLast; ++skill) + { + int value = mob.GetSkill(skill); + if (value > 0) + { + if (sqlite3_prepare_v2(m_db, skill_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, mob_vnum); + sqlite3_bind_int(stmt, 2, to_underlying(skill)); + sqlite3_bind_int(stmt, 3, value); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + + // Save mob feats + const char *feat_sql = "INSERT INTO mob_feats (mob_vnum, feat_id) VALUES (?, ?)"; + for (size_t feat_id = 0; feat_id < mob.real_abils.Feats.size(); ++feat_id) + { + if (mob.real_abils.Feats.test(feat_id)) + { + if (sqlite3_prepare_v2(m_db, feat_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, mob_vnum); + sqlite3_bind_int(stmt, 2, feat_id); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + + // Save mob spells + const char *spell_sql = "INSERT INTO mob_spells (mob_vnum, spell_id) VALUES (?, ?)"; + for (size_t spell_id = 0; spell_id < mob.real_abils.SplKnw.size(); ++spell_id) + { + if (mob.real_abils.SplKnw[spell_id] > 0) + { + if (sqlite3_prepare_v2(m_db, spell_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, mob_vnum); + sqlite3_bind_int(stmt, 2, spell_id); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + // Save mob helpers + const char *helper_sql = "INSERT INTO mob_helpers (mob_vnum, helper_vnum) VALUES (?, ?)"; + for (const auto &helper_vnum : mob.summon_helpers) + { + if (helper_vnum != 0) + { + if (sqlite3_prepare_v2(m_db, helper_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, mob_vnum); + sqlite3_bind_int(stmt, 2, helper_vnum); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + // Save mob destinations + const char *dest_sql = "INSERT INTO mob_destinations (mob_vnum, dest_order, room_vnum) VALUES (?, ?, ?)"; + for (size_t i = 0; i < mob.mob_specials.dest.size(); ++i) + { + if (mob.mob_specials.dest[i] != 0) + { + if (sqlite3_prepare_v2(m_db, dest_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, mob_vnum); + sqlite3_bind_int(stmt, 2, i); + sqlite3_bind_int(stmt, 3, mob.mob_specials.dest[i]); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + + // Save mob triggers + const char *trig_sql = + "INSERT INTO entity_triggers (entity_type, entity_vnum, trigger_vnum, trigger_order) " + "VALUES ('mob', ?, ?, ?)"; + + int trig_order = 0; + if (mob.script && !mob.script->script_trig_list.empty()) + { + for (auto trig : mob.script->script_trig_list) + { + if (sqlite3_prepare_v2(m_db, trig_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, mob_vnum); + sqlite3_bind_int(stmt, 2, GET_TRIG_VNUM(trig)); + sqlite3_bind_int(stmt, 3, trig_order++); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } +} + +void SqliteWorldDataSource::SaveMobs(int zone_rnum, int specific_vnum) +{ + (void)specific_vnum; // SQLite format always saves entire zone + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveMobs", zone_rnum); + return; + } + + if (!m_db) + { + log("SYSERR: Database not open for SaveMobs"); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + + // Get mob range for this zone + MobRnum first_mob = zone.RnumMobsLocation.first; + MobRnum last_mob = zone.RnumMobsLocation.second; + + if (first_mob == -1 || last_mob == -1) + { + log("Zone %d has no mobs to save", zone.vnum); + return; + } + + if (!BeginTransaction()) + { + return; + } + + int saved_count = 0; + for (MobRnum mob_rnum = first_mob; mob_rnum <= last_mob && mob_rnum <= top_of_mobt; ++mob_rnum) + { + if (!mob_index[mob_rnum].vnum) + { + continue; + } + + int mob_vnum = mob_index[mob_rnum].vnum; + CharData &mob = mob_proto[mob_rnum]; + + SaveMobRecord(mob_vnum, mob); + ++saved_count; + } + + if (!CommitTransaction()) + { + RollbackTransaction(); + log("SYSERR: Failed to save mobs for zone %d", zone.vnum); + return; + } + + log("Saved %d mobs for zone %d", saved_count, zone.vnum); +} + + +void SqliteWorldDataSource::SaveObjectRecord(int obj_vnum, CObjectPrototype *obj) +{ + if (!obj) + { + return; + } + + // Delete existing object data (CASCADE will handle related tables) + std::string delete_sql = "DELETE FROM objects WHERE vnum = " + std::to_string(obj_vnum); + ExecuteStatement(delete_sql, "delete object"); + + // Insert object main record + sqlite3_stmt *stmt = nullptr; + const char *insert_sql = + "INSERT INTO objects (vnum, aliases, name_nom, name_gen, name_dat, name_acc, name_ins, name_pre, " + "short_desc, action_desc, obj_type_id, material, value0, value1, value2, value3, " + "weight, cost, rent_off, rent_on, spec_param, max_durability, cur_durability, " + "timer, spell, level, sex, max_in_world, minimum_remorts, enabled) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)"; + + if (sqlite3_prepare_v2(m_db, insert_sql, -1, &stmt, nullptr) != SQLITE_OK) + { + log("SYSERR: Failed to prepare object insert: %s", sqlite3_errmsg(m_db)); + return; + } + + int col = 1; + sqlite3_bind_int(stmt, col++, obj_vnum); + + // Names + sqlite3_bind_text(stmt, col++, obj->get_aliases().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, obj->get_PName(ECase::kNom).c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, obj->get_PName(ECase::kGen).c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, obj->get_PName(ECase::kDat).c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, obj->get_PName(ECase::kAcc).c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, obj->get_PName(ECase::kIns).c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, obj->get_PName(ECase::kPre).c_str(), -1, SQLITE_TRANSIENT); + + // Descriptions + sqlite3_bind_text(stmt, col++, obj->get_description().c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, obj->get_action_description().c_str(), -1, SQLITE_TRANSIENT); + + // Type and material + sqlite3_bind_int(stmt, col++, to_underlying(obj->get_type())); + sqlite3_bind_int(stmt, col++, to_underlying(obj->get_material())); + + // Values (store as strings) + std::string val0 = std::to_string(obj->get_val(0)); + std::string val1 = std::to_string(obj->get_val(1)); + std::string val2 = std::to_string(obj->get_val(2)); + std::string val3 = std::to_string(obj->get_val(3)); + sqlite3_bind_text(stmt, col++, val0.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, val1.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, val2.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, col++, val3.c_str(), -1, SQLITE_TRANSIENT); + + // Physical properties + sqlite3_bind_int(stmt, col++, obj->get_weight()); + sqlite3_bind_int(stmt, col++, obj->get_cost()); + sqlite3_bind_int(stmt, col++, obj->get_rent_off()); + sqlite3_bind_int(stmt, col++, obj->get_rent_on()); + sqlite3_bind_int(stmt, col++, obj->get_spec_param()); + sqlite3_bind_int(stmt, col++, obj->get_maximum_durability()); + sqlite3_bind_int(stmt, col++, obj->get_current_durability()); + sqlite3_bind_int(stmt, col++, obj->get_timer()); + sqlite3_bind_int(stmt, col++, to_underlying(obj->get_spell())); + sqlite3_bind_int(stmt, col++, obj->get_level()); + sqlite3_bind_int(stmt, col++, to_underlying(obj->get_sex())); + sqlite3_bind_int(stmt, col++, obj->get_max_in_world()); + sqlite3_bind_int(stmt, col++, obj->get_minimum_remorts()); + + int rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + { + log("SYSERR: Failed to insert object %d: %s", obj_vnum, sqlite3_errmsg(m_db)); + } + sqlite3_finalize(stmt); + + + // Save object extra flags + FlagData obj_flags = obj->get_extra_flags(); + SaveFlagsToTable(m_db, "obj_flags", "obj_vnum", obj_vnum, obj_flags, obj_extra_flag_map); + // Save object wear flags + int wear_flags = obj->get_wear_flags(); + sqlite3_stmt *wear_stmt = nullptr; + const char *wear_sql = "INSERT INTO obj_flags (obj_vnum, flag_category, flag_name) VALUES (?, 'wear', ?)"; + for (int bit = 0; bit < 32; ++bit) + { + if (wear_flags & (1 << bit)) + { + Bitvector bit_value = (1 << bit); + std::string flag_name = ReverseLookupFlag(obj_wear_flag_map, bit_value); + if (!flag_name.empty()) + { + if (sqlite3_prepare_v2(m_db, wear_sql, -1, &wear_stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(wear_stmt, 1, obj_vnum); + sqlite3_bind_text(wear_stmt, 2, flag_name.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_step(wear_stmt); + sqlite3_finalize(wear_stmt); + } + } + } + } + + + // Save object applies (affects) + const char *apply_sql = "INSERT INTO obj_applies (obj_vnum, location_id, modifier) VALUES (?, ?, ?)"; + for (int i = 0; i < kMaxObjAffect; ++i) + { + if (obj->get_affected(i).location != EApply::kNone) + { + if (sqlite3_prepare_v2(m_db, apply_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, obj_vnum); + sqlite3_bind_int(stmt, 2, to_underlying(obj->get_affected(i).location)); + sqlite3_bind_int(stmt, 3, obj->get_affected(i).modifier); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } + + // Save object extra descriptions + const char *extra_sql = + "INSERT INTO extra_descriptions (entity_type, entity_vnum, keyword, description) " + "VALUES ('obj', ?, ?, ?)"; + + for (auto exdesc = obj->get_ex_description(); exdesc; exdesc = exdesc->next) + { + if (sqlite3_prepare_v2(m_db, extra_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, obj_vnum); + sqlite3_bind_text(stmt, 2, exdesc->keyword, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, exdesc->description, -1, SQLITE_TRANSIENT); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + + // Save object triggers + const char *trig_sql = + "INSERT INTO entity_triggers (entity_type, entity_vnum, trigger_vnum, trigger_order) " + "VALUES ('obj', ?, ?, ?)"; + + int trig_order = 0; + for (const auto &trig_vnum : obj->get_proto_script()) + { + if (trig_vnum > 0) + { + if (sqlite3_prepare_v2(m_db, trig_sql, -1, &stmt, nullptr) == SQLITE_OK) + { + sqlite3_bind_int(stmt, 1, obj_vnum); + sqlite3_bind_int(stmt, 2, trig_vnum); + sqlite3_bind_int(stmt, 3, trig_order++); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + } + } + } +} + +void SqliteWorldDataSource::SaveObjects(int zone_rnum, int specific_vnum) +{ + (void)specific_vnum; // SQLite format always saves entire zone + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveObjects", zone_rnum); + return; + } + + if (!m_db) + { + log("SYSERR: Database not open for SaveObjects"); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + int zone_vnum = zone.vnum; + + if (!BeginTransaction()) + { + return; + } + + // Iterate through all objects and save those in this zone's vnum range + int saved_count = 0; + int start_vnum = zone_vnum * 100; + int end_vnum = zone.top; + + for (const auto &[obj_vnum, obj_rnum] : obj_proto.vnum2index()) + { + if (obj_vnum < start_vnum || obj_vnum > end_vnum) + { + continue; + } + + SaveObjectRecord(obj_vnum, obj_proto[obj_rnum].get()); + ++saved_count; + } + + if (!CommitTransaction()) + { + RollbackTransaction(); + log("SYSERR: Failed to save objects for zone %d", zone_vnum); + return; + } + + log("Saved %d objects for zone %d", saved_count, zone_vnum); +} + +std::unique_ptr CreateSqliteDataSource(const std::string &db_path) +{ + return std::make_unique(db_path); +} + +} // namespace world_loader + +#endif // HAVE_SQLITE + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/sqlite_world_data_source.h b/src/engine/db/sqlite_world_data_source.h new file mode 100644 index 000000000..a3186817d --- /dev/null +++ b/src/engine/db/sqlite_world_data_source.h @@ -0,0 +1,109 @@ +// Part of Bylins http://www.mud.ru +// SQLite world data source - loads world from SQLite database + +#ifndef SQLITE_WORLD_DATA_SOURCE_H_ +#define SQLITE_WORLD_DATA_SOURCE_H_ + +#include "world_data_source.h" +#include "world_data_source_base.h" + +#ifdef HAVE_SQLITE + +#include +#include +#include + +class ZoneData; +class RoomData; +class CharData; +class CObjectPrototype; +class Trigger; +struct reset_com; + +namespace world_loader +{ + +// SQLite implementation for fast world loading +class SqliteWorldDataSource : public WorldDataSourceBase +{ +public: + explicit SqliteWorldDataSource(const std::string &db_path); + ~SqliteWorldDataSource() override; + + std::string GetName() const override { return "SQLite database: " + m_db_path; } + + void LoadZones() override; + void LoadTriggers() override; + void LoadRooms() override; + void LoadMobs() override; + void LoadObjects() override; + + // Save methods (SQLite is read-only for now) + void SaveZone(int zone_rnum) override; + bool SaveTriggers(int zone_rnum, int specific_vnum, int notify_level) override; + void SaveRooms(int zone_rnum, int specific_vnum = -1) override; + void SaveMobs(int zone_rnum, int specific_vnum = -1) override; + void SaveObjects(int zone_rnum, int specific_vnum = -1) override; + +private: + bool OpenDatabase(); + void CloseDatabase(); + int GetCount(const char *table); + std::string GetText(sqlite3_stmt *stmt, int col); + + // Zone loading helpers + void LoadZoneCommands(ZoneData &zone); + void LoadZoneGroups(ZoneData &zone); + + // Room loading helpers + void LoadRoomExits(const std::map &vnum_to_rnum); + void LoadRoomFlags(const std::map &vnum_to_rnum); + void LoadRoomTriggers(const std::map &vnum_to_rnum); + void LoadRoomExtraDescriptions(const std::map &vnum_to_rnum); + + // Mob loading helpers + void LoadMobFlags(); + void LoadMobSkills(); + void LoadMobTriggers(); + void LoadMobResistances(); + void LoadMobSaves(); + void LoadMobFeats(); + void LoadMobSpells(); + void LoadMobHelpers(); + void LoadMobDestinations(); + + // Object loading helpers + void LoadObjectFlags(); + void LoadObjectApplies(); + void LoadObjectTriggers(); + void LoadObjectExtraDescriptions(); + + // Transaction helpers + bool BeginTransaction(); + bool CommitTransaction(); + bool RollbackTransaction(); + bool ExecuteStatement(const std::string &sql, const std::string &operation); + + // Save helpers + void SaveZoneRecord(const ZoneData &zone); + void SaveZoneCommands(int zone_vnum, const struct reset_com *commands); + void SaveZoneGroups(int zone_vnum, const ZoneData &zone); + void SaveRoomRecord(RoomData *room); + void SaveTriggerRecord(int trig_vnum, const Trigger *trig); + void SaveMobRecord(int mob_vnum, CharData &mob); + void SaveObjectRecord(int obj_vnum, CObjectPrototype *obj); + + std::string m_db_path; + sqlite3 *m_db; +}; + +// Factory function for creating SQLite data source +std::unique_ptr CreateSqliteDataSource(const std::string &db_path); + +} // namespace world_loader + +#endif // HAVE_SQLITE + +#endif // SQLITE_WORLD_DATA_SOURCE_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/world_checksum.cpp b/src/engine/db/world_checksum.cpp new file mode 100644 index 000000000..d0583eb3d --- /dev/null +++ b/src/engine/db/world_checksum.cpp @@ -0,0 +1,960 @@ +// Part of Bylins http://www.mud.ru +// World checksum calculation for detecting changes during refactoring + +#include "world_checksum.h" + +#include "db.h" +#include "obj_prototypes.h" +#include "global_objects.h" +#include "engine/entities/zone.h" +#include "engine/entities/room_data.h" +#include "engine/entities/char_data.h" +#include "utils/logger.h" +#include "utils/utils_string.h" +#include "engine/structs/extra_description.h" + +#include +#include +#include + +#include + +namespace +{ + +// CRC32 calculation function (same as in file_crc.cpp) +uint32_t CRC32(const char *buf, size_t len) +{ + static uint32_t crc_table[256]; + static bool table_initialized = false; + + if (!table_initialized) + { + for (int i = 0; i < 256; i++) + { + uint32_t crc = i; + for (int j = 0; j < 8; j++) + { + crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320UL : crc >> 1; + } + crc_table[i] = crc; + } + table_initialized = true; + } + + uint32_t crc = 0xFFFFFFFFUL; + while (len--) + { + crc = crc_table[(crc ^ *buf++) & 0xFF] ^ (crc >> 8); + } + return crc ^ 0xFFFFFFFFUL; +} + +uint32_t CRC32String(const std::string &str) +{ + return CRC32(str.c_str(), str.size()); +} + +// Serialize zone data to string for checksum calculation +std::string SerializeZone(const ZoneData &zone) +{ + std::ostringstream oss; + + // Include only persistent data, exclude runtime state + oss << zone.vnum << "|"; + oss << zone.name << "|"; + oss << zone.lifespan << "|"; + oss << zone.top << "|"; + oss << zone.reset_mode << "|"; + oss << zone.comment << "|"; + oss << zone.author << "|"; + oss << zone.traffic << "|"; + oss << zone.level << "|"; + oss << zone.type << "|"; + oss << zone.first_enter << "|"; + oss << zone.location << "|"; + oss << zone.description << "|"; + oss << zone.typeA_count << "|"; + oss << zone.typeB_count << "|"; + oss << zone.under_construction << "|"; + oss << zone.group << "|"; + oss << zone.mob_level << "|"; + oss << zone.is_town << "|"; + + // Serialize zone commands + if (zone.cmd) + { + for (int i = 0; zone.cmd[i].command != 'S'; ++i) + { + const auto &cmd = zone.cmd[i]; + oss << cmd.command << ":"; + oss << cmd.if_flag << ":"; + oss << cmd.arg1 << ":"; + oss << cmd.arg2 << ":"; + oss << cmd.arg3 << ":"; + oss << cmd.arg4 << ":"; + if (cmd.sarg1) + { + oss << cmd.sarg1; + } + oss << ":"; + if (cmd.sarg2) + { + oss << cmd.sarg2; + } + oss << ";"; + } + } + + + + return oss.str(); +} + +// Serialize room data to string for checksum calculation +std::string SerializeRoom(const RoomData *room) +{ + if (!room) + { + return ""; + } + + std::ostringstream oss; + + oss << room->vnum << "|"; + // zone_rn excluded - runtime index + oss << room->sector_type << "|"; + if (room->name) + { + oss << room->name; + } + oss << "|"; + + // Serialize room description (actual text content) + if (room->temp_description) + { + oss << room->temp_description; + } + else + { + oss << GlobalObjects::descriptions().get(room->description_num); + } + oss << "|"; + + // Serialize room flags using safe dynamic buffer + std::vector flag_buf(8192); + flag_buf[0] = '\0'; + if (room->flags_sprint(flag_buf.data(), ",")) + { + // Filter out UNDEF flags for consistent checksums + // (UNDEF flags are undefined bits that Legacy loads but SQLite does not preserve) + std::string flags_str(flag_buf.data()); + std::string filtered; + for (const auto& flag : utils::Split(flags_str, ',')) { + if (flag != "UNDEF") { + if (!filtered.empty()) filtered += ","; + filtered += flag; + } + } + oss << filtered; + } + oss << "|"; + + // Serialize exits (proto only) + for (int dir = 0; dir < EDirection::kMaxDirNum; ++dir) + { + const auto &exit = room->dir_option_proto[dir]; + if (exit) + { + oss << dir << ":"; + // Use vnum instead of rnum to make checksum independent of loading order + auto to_rnum = exit->to_room(); + int to_vnum = (to_rnum >= 0 && to_rnum <= top_of_world && world[to_rnum]) + ? world[to_rnum]->vnum : -1; + oss << to_vnum << ":"; + oss << exit->general_description << ":"; + if (exit->keyword) + { + oss << exit->keyword; + } + oss << ":"; + if (exit->vkeyword) + { + oss << exit->vkeyword; + } + oss << ":"; + oss << static_cast(exit->exit_info) << ":"; + oss << static_cast(exit->lock_complexity) << ":"; + oss << exit->key << ";"; + } + } + oss << "|"; + + // Serialize proto_script (trigger vnums) + if (room->proto_script) + { + for (const auto &trig_vnum : *room->proto_script) + { + oss << trig_vnum << ","; + } + } + + return oss.str(); +} + +// Serialize mob prototype data to string for checksum calculation +std::string SerializeMob(MobRnum rnum) +{ + if (rnum < 0 || rnum > top_of_mobt) + { + return ""; + } + + const CharData &mob = mob_proto[rnum]; + std::ostringstream oss; + + oss << mob_index[rnum].vnum << "|"; + oss << mob.GetLevel() << "|"; + oss << mob.get_str() << "|"; + oss << mob.get_dex() << "|"; + oss << mob.get_con() << "|"; + oss << mob.get_wis() << "|"; + oss << mob.get_int() << "|"; + oss << mob.get_cha() << "|"; + oss << mob.get_name() << "|"; + oss << mob.GetCharAliases() << "|"; + oss << mob.get_npc_name() << "|"; + + // NPC pads + for (int i = 0; i < 6; ++i) + { + const char *pad = mob.get_pad(i); + if (pad) + { + oss << pad; + } + oss << ","; + } + oss << "|"; + + // Long description + const char *long_descr = mob.get_long_descr(); + if (long_descr) + { + oss << long_descr; + } + oss << "|"; + + // Description + const char *descr = mob.get_description(); + if (descr) + { + oss << descr; + } + oss << "|"; + + // Mob specials + oss << static_cast(mob.IsNpc() ? mob.IsNpc() : 0) << "|"; + oss << mob.get_max_hit() << "|"; + oss << mob.get_hit() << "|"; + + // Proto script + if (mob.proto_script) + { + for (const auto &trig_vnum : *mob.proto_script) + { + oss << trig_vnum << ","; + } + } + + return oss.str(); +} + +// Serialize object prototype data to string for checksum calculation +std::string SerializeObject(const CObjectPrototype::shared_ptr &obj) +{ + if (!obj) + { + return ""; + } + + std::ostringstream oss; + + oss << obj->get_vnum() << "|"; + oss << static_cast(obj->get_type()) << "|"; + oss << obj->get_weight() << "|"; + oss << obj->get_cost() << "|"; + oss << obj->get_rent_on() << "|"; + oss << obj->get_rent_off() << "|"; + oss << obj->get_level() << "|"; + oss << static_cast(obj->get_material()) << "|"; + oss << obj->get_max_in_world() << "|"; + oss << obj->get_timer() << "|"; + oss << static_cast(obj->get_sex()) << "|"; + oss << static_cast(obj->get_spell()) << "|"; + oss << obj->get_maximum_durability() << "|"; + oss << obj->get_current_durability() << "|"; + oss << obj->get_minimum_remorts() << "|"; + oss << obj->get_wear_flags() << "|"; + + // Extra flags (all 4 planes) + const auto &extra = obj->get_extra_flags(); + oss << extra.get_plane(0) << "," << extra.get_plane(1) << "," + << extra.get_plane(2) << "," << extra.get_plane(3) << "|"; + + // Anti flags (all 4 planes) + const auto &anti = obj->get_anti_flags(); + oss << anti.get_plane(0) << "," << anti.get_plane(1) << "," + << anti.get_plane(2) << "," << anti.get_plane(3) << "|"; + + // No flags (all 4 planes) + const auto &no = obj->get_no_flags(); + oss << no.get_plane(0) << "," << no.get_plane(1) << "," + << no.get_plane(2) << "," << no.get_plane(3) << "|"; + + // Affect flags (all 4 planes) + const auto &waff = obj->get_affect_flags(); + oss << waff.get_plane(0) << "," << waff.get_plane(1) << "," + << waff.get_plane(2) << "," << waff.get_plane(3) << "|"; + + // Values + for (int i = 0; i < CObjectPrototype::VALS_COUNT; ++i) + { + oss << obj->get_val(i) << ","; + } + oss << "|"; + + // Affected + for (int i = 0; i < kMaxObjAffect; ++i) + { + const auto &aff = obj->get_affected(i); + oss << static_cast(aff.location) << ":" << aff.modifier << ","; + } + oss << "|"; + + // Names + oss << obj->get_short_description() << "|"; + oss << obj->get_description() << "|"; + oss << obj->get_aliases() << "|"; + for (int i = 0; i <= ECase::kLastCase; ++i) + { + oss << obj->get_PName(static_cast(i)) << ","; + } + oss << "|"; + + // Extra descriptions + for (auto ed = obj->get_ex_description(); ed; ed = ed->next) + { + if (ed->keyword) + { + oss << ed->keyword << ":"; + } + if (ed->description) + { + oss << ed->description; + } + oss << ";"; + } + oss << "|"; + + // Proto script + for (const auto &trig_vnum : obj->get_proto_script()) + { + oss << trig_vnum << ","; + } + + + return oss.str(); +} + +// Serialize trigger data to string for checksum calculation +std::string SerializeTrigger(int rnum) +{ + if (rnum < 0 || rnum >= top_of_trigt || !trig_index[rnum]) + { + return ""; + } + + const auto *index = trig_index[rnum]; + const auto *trig = index->proto; + + if (!trig) + { + return ""; + } + + std::ostringstream oss; + + oss << index->vnum << "|"; + oss << trig->get_name() << "|"; + oss << static_cast(trig->get_attach_type()) << "|"; + oss << trig->get_trigger_type() << "|"; + oss << trig->narg << "|"; + oss << trig->arglist << "|"; + + // Serialize command list + if (trig->cmdlist) + { + auto cmd = *trig->cmdlist; + while (cmd) + { + oss << cmd->cmd << ";"; + cmd = cmd->next; + } + } + + + return oss.str(); +} + +} // anonymous namespace + +namespace WorldChecksum +{ + +ChecksumResult Calculate() +{ + ChecksumResult result = {}; + + // Calculate zones checksum + uint32_t zones_xor = 0; + for (const auto &zone : zone_table) + { + std::string serialized = SerializeZone(zone); + uint32_t crc = CRC32String(serialized); + zones_xor ^= crc; + ++result.zones_count; + } + result.zones = zones_xor; + + // Calculate rooms checksum + uint32_t rooms_xor = 0; + for (RoomRnum i = 0; i <= top_of_world; ++i) + { + std::string serialized = SerializeRoom(world[i]); + uint32_t crc = CRC32String(serialized); + rooms_xor ^= crc; + ++result.rooms_count; + } + result.rooms = rooms_xor; + + // Calculate mobs checksum + uint32_t mobs_xor = 0; + for (MobRnum i = 0; i <= top_of_mobt; ++i) + { + std::string serialized = SerializeMob(i); + uint32_t crc = CRC32String(serialized); + mobs_xor ^= crc; + ++result.mobs_count; + } + result.mobs = mobs_xor; + + // Calculate objects checksum + uint32_t objects_xor = 0; + for (size_t i = 0; i < obj_proto.size(); ++i) + { + std::string serialized = SerializeObject(obj_proto[i]); + uint32_t crc = CRC32String(serialized); + objects_xor ^= crc; + ++result.objects_count; + } + result.objects = objects_xor; + + // Calculate triggers checksum + uint32_t triggers_xor = 0; + for (int i = 0; i < top_of_trigt; ++i) + { + std::string serialized = SerializeTrigger(i); + uint32_t crc = CRC32String(serialized); + triggers_xor ^= crc; + ++result.triggers_count; + } + result.triggers = triggers_xor; + + // ===== RUNTIME CHECKSUMS (after initialization) ===== + + // Calculate room scripts checksum (prototype triggers) + uint32_t room_scripts_xor = 0; + for (RoomRnum i = 0; i <= top_of_world; ++i) + { + if (world[i]->proto_script && !world[i]->proto_script->empty()) + { + std::ostringstream oss; + oss << world[i]->vnum << "|"; + for (const auto trig_vnum : *world[i]->proto_script) + { + oss << trig_vnum << ","; + } + uint32_t crc = CRC32String(oss.str()); + room_scripts_xor ^= crc; + ++result.rooms_with_scripts; + } + } + result.room_scripts = room_scripts_xor; + + // Calculate mob scripts checksum (prototype triggers) + uint32_t mob_scripts_xor = 0; + for (MobRnum i = 0; i <= top_of_mobt; ++i) + { + if (mob_proto[i].proto_script && !mob_proto[i].proto_script->empty()) + { + std::ostringstream oss; + oss << mob_index[i].vnum << "|"; + for (const auto trig_vnum : *mob_proto[i].proto_script) + { + oss << trig_vnum << ","; + } + uint32_t crc = CRC32String(oss.str()); + mob_scripts_xor ^= crc; + ++result.mobs_with_scripts; + } + } + result.mob_scripts = mob_scripts_xor; + + // Calculate object scripts checksum + uint32_t obj_scripts_xor = 0; + for (size_t i = 0; i < obj_proto.size(); ++i) + { + const auto &obj = obj_proto[i]; + if (!obj_proto.proto_script(i).empty()) + { + std::ostringstream oss; + oss << obj->get_vnum() << "|"; + for (const auto trig_vnum : obj_proto.proto_script(i)) + { + oss << trig_vnum << ","; + } + uint32_t crc = CRC32String(oss.str()); + obj_scripts_xor ^= crc; + ++result.objects_with_scripts; + } + } + result.obj_scripts = obj_scripts_xor; + + // Calculate door rnums checksum + uint32_t door_rnums_xor = 0; + for (RoomRnum i = 0; i <= top_of_world; ++i) + { + for (const auto &exit : world[i]->dir_option_proto) + { + if (exit) + { + std::ostringstream oss; + oss << world[i]->vnum << "|" << exit->to_room(); + door_rnums_xor ^= CRC32String(oss.str()); + } + } + } + result.door_rnums = door_rnums_xor; + + // Calculate zone_rnum checksums for mobs + uint32_t zone_rnums_mobs_xor = 0; + for (MobRnum i = 0; i <= top_of_mobt; ++i) + { + std::ostringstream oss; + oss << mob_index[i].vnum << "|" << mob_index[i].zone; + zone_rnums_mobs_xor ^= CRC32String(oss.str()); + } + result.zone_rnums_mobs = zone_rnums_mobs_xor; + + // Calculate zone_rnum checksums for objects + uint32_t zone_rnums_objects_xor = 0; + for (size_t i = 0; i < obj_proto.size(); ++i) + { + std::ostringstream oss; + oss << obj_proto[i]->get_vnum() << "|" << obj_proto.zone(i); + zone_rnums_objects_xor ^= CRC32String(oss.str()); + } + result.zone_rnums_objects = zone_rnums_objects_xor; + + // Calculate zone commands rnums checksum + uint32_t zone_cmd_rnums_xor = 0; + for (const auto &zone : zone_table) + { + if (zone.cmd) + { + for (int i = 0; zone.cmd[i].command != 'S'; ++i) + { + const auto &cmd = zone.cmd[i]; + std::ostringstream oss; + oss << zone.vnum << "|" << cmd.command << "|"; + oss << cmd.arg1 << "|" << cmd.arg2 << "|" << cmd.arg3; + zone_cmd_rnums_xor ^= CRC32String(oss.str()); + } + } + } + result.zone_cmd_rnums = zone_cmd_rnums_xor; + + // Calculate combined runtime checksum + std::ostringstream runtime_combined; + runtime_combined << result.room_scripts << "|"; + runtime_combined << result.mob_scripts << "|"; + runtime_combined << result.obj_scripts << "|"; + runtime_combined << result.door_rnums << "|"; + runtime_combined << result.zone_rnums_mobs << "|"; + runtime_combined << result.zone_rnums_objects << "|"; + runtime_combined << result.zone_cmd_rnums; + result.runtime_combined = CRC32String(runtime_combined.str()); + + // Calculate combined checksum + std::ostringstream combined; + combined << result.zones << "|"; + combined << result.rooms << "|"; + combined << result.mobs << "|"; + combined << result.objects << "|"; + combined << result.triggers; + result.combined = CRC32String(combined.str()); + + return result; +} + +void LogResult(const ChecksumResult &result) +{ + log("=== World Checksums ==="); + log("Zones: %08X (%zu zones)", result.zones, result.zones_count); + log("Rooms: %08X (%zu rooms)", result.rooms, result.rooms_count); + log("Mobs: %08X (%zu mobs)", result.mobs, result.mobs_count); + log("Objects: %08X (%zu objects)", result.objects, result.objects_count); + log("Triggers: %08X (%zu triggers)", result.triggers, result.triggers_count); + log("Combined: %08X", result.combined); + + log("=== Runtime Checksums (after initialization) ==="); + log("Room Scripts: %08X (%zu rooms with scripts)", result.room_scripts, result.rooms_with_scripts); + log("Mob Scripts: %08X (%zu mobs with scripts)", result.mob_scripts, result.mobs_with_scripts); + log("Object Scripts: %08X (%zu objects with scripts)", result.obj_scripts, result.objects_with_scripts); + log("Door Rnums: %08X", result.door_rnums); + log("Zone Rnums (Mobs): %08X", result.zone_rnums_mobs); + log("Zone Rnums (Objs): %08X", result.zone_rnums_objects); + log("Zone Cmd Rnums: %08X", result.zone_cmd_rnums); + log("Runtime Combined: %08X", result.runtime_combined); + log("======================="); +} + +void SaveDetailedChecksums(const char *filename, const ChecksumResult &checksums) +{ + FILE *f = fopen(filename, "w"); + if (!f) + { + log("SYSERR: Cannot open %s for writing detailed checksums", filename); + return; + } + + fprintf(f, "# World Detailed Checksums\n"); + fprintf(f, "# Format: TYPE VNUM CHECKSUM\n\n"); + + // Zones + fprintf(f, "# Zones\n"); + for (const auto &zone : zone_table) + { + std::string serialized = SerializeZone(zone); + uint32_t crc = CRC32String(serialized); + fprintf(f, "ZONE %d %08X\n", zone.vnum, crc); + } + + // Rooms + fprintf(f, "\n# Rooms\n"); + for (RoomRnum i = 0; i <= top_of_world; ++i) + { + if (world[i]) + { + std::string serialized = SerializeRoom(world[i]); + uint32_t crc = CRC32String(serialized); + fprintf(f, "ROOM %d %08X\n", world[i]->vnum, crc); + } + } + + // Mobs + fprintf(f, "\n# Mobs\n"); + for (MobRnum i = 0; i <= top_of_mobt; ++i) + { + std::string serialized = SerializeMob(i); + uint32_t crc = CRC32String(serialized); + fprintf(f, "MOB %d %08X\n", mob_index[i].vnum, crc); + } + + // Objects + fprintf(f, "\n# Objects\n"); + for (size_t i = 0; i < obj_proto.size(); ++i) + { + if (obj_proto[i]) + { + std::string serialized = SerializeObject(obj_proto[i]); + uint32_t crc = CRC32String(serialized); + fprintf(f, "OBJ %d %08X\n", obj_proto[i]->get_vnum(), crc); + } + } + + // Triggers + fprintf(f, "\n# Triggers\n"); + for (int i = 0; i < top_of_trigt; ++i) + { + std::string serialized = SerializeTrigger(i); + uint32_t crc = CRC32String(serialized); + if (trig_index[i]) + { + fprintf(f, "TRIG %d %08X\n", trig_index[i]->vnum, crc); + } + } + + // Runtime Checksums + fprintf(f, "\n# Runtime Checksums\n"); + fprintf(f, "ROOM_SCRIPTS %08X\n", checksums.room_scripts); + fprintf(f, "MOB_SCRIPTS %08X\n", checksums.mob_scripts); + fprintf(f, "OBJ_SCRIPTS %08X\n", checksums.obj_scripts); + fprintf(f, "DOOR_RNUMS %08X\n", checksums.door_rnums); + + fclose(f); + log("Detailed checksums saved to %s", filename); +} + +bool SaveDetailedBuffers(const char *dir) +{ + try { + std::filesystem::create_directories(dir); + + auto save_buffer = [](const char *filepath, const std::string &buffer, uint32_t crc) { + FILE *f = fopen(filepath, "w"); + if (!f) + { + log("SYSERR: Failed to open file for writing: %s", filepath); + return; + } + fprintf(f, "CRC32: %08X\n", crc); + fprintf(f, "Length: %zu\n", buffer.size()); + fprintf(f, "---RAW---\n%s\n", buffer.c_str()); + fprintf(f, "---HEX---\n"); + for (size_t i = 0; i < buffer.size(); ++i) + { + fprintf(f, "%02X ", static_cast(buffer[i])); + if ((i + 1) % 32 == 0) fprintf(f, "\n"); + } + fprintf(f, "\n"); + fclose(f); + }; + + std::string zones_dir = std::string(dir) + "/zones"; + std::filesystem::create_directories(zones_dir); + for (const auto &zone : zone_table) + { + std::string serialized = SerializeZone(zone); + uint32_t crc = CRC32String(serialized); + std::string filepath = zones_dir + "/" + std::to_string(zone.vnum) + ".txt"; + save_buffer(filepath.c_str(), serialized, crc); + } + + std::string rooms_dir = std::string(dir) + "/rooms"; + std::filesystem::create_directories(rooms_dir); + for (RoomRnum i = 0; i <= top_of_world; ++i) + { + if (world[i]) + { + std::string serialized = SerializeRoom(world[i]); + uint32_t crc = CRC32String(serialized); + std::string filepath = rooms_dir + "/" + std::to_string(world[i]->vnum) + ".txt"; + save_buffer(filepath.c_str(), serialized, crc); + } + } + + std::string mobs_dir = std::string(dir) + "/mobs"; + std::filesystem::create_directories(mobs_dir); + for (MobRnum i = 0; i <= top_of_mobt; ++i) + { + std::string serialized = SerializeMob(i); + uint32_t crc = CRC32String(serialized); + std::string filepath = mobs_dir + "/" + std::to_string(mob_index[i].vnum) + ".txt"; + save_buffer(filepath.c_str(), serialized, crc); + } + + std::string objs_dir = std::string(dir) + "/objects"; + std::filesystem::create_directories(objs_dir); + for (size_t i = 0; i < obj_proto.size(); ++i) + { + if (obj_proto[i]) + { + std::string serialized = SerializeObject(obj_proto[i]); + uint32_t crc = CRC32String(serialized); + std::string filepath = objs_dir + "/" + std::to_string(obj_proto[i]->get_vnum()) + ".txt"; + save_buffer(filepath.c_str(), serialized, crc); + } + } + + std::string trigs_dir = std::string(dir) + "/triggers"; + std::filesystem::create_directories(trigs_dir); + for (int i = 0; i < top_of_trigt; ++i) + { + if (trig_index[i]) + { + std::string serialized = SerializeTrigger(i); + uint32_t crc = CRC32String(serialized); + std::string filepath = trigs_dir + "/" + std::to_string(trig_index[i]->vnum) + ".txt"; + save_buffer(filepath.c_str(), serialized, crc); + } + } + + log("Detailed buffers saved to %s", dir); + return true; + + } catch (const std::filesystem::filesystem_error &e) { + log("SYSERR: Failed to create directories in '%s': %s", dir, e.what()); + return false; + } +} + +std::map LoadBaselineChecksums(const char *filename) +{ + std::map result; + std::ifstream f(filename); + if (!f.is_open()) + { + log("SYSERR: Cannot open baseline checksums file: %s", filename); + return result; + } + + std::string line; + while (std::getline(f, line)) + { + if (line.empty() || line[0] == '#') continue; + std::istringstream iss(line); + std::string type; + int vnum; + std::string crc_str; + if (iss >> type >> vnum >> crc_str) + { + uint32_t crc = std::stoul(crc_str, nullptr, 16); + std::string key = type + " " + std::to_string(vnum); + result[key] = crc; + } + } + + log("Loaded %zu baseline checksums from %s", result.size(), filename); + return result; +} + +void CompareWithBaseline(const char *baseline_dir, int max_mismatches_per_type) +{ + std::string checksums_file = std::string(baseline_dir) + "/checksums_detailed.txt"; + auto baseline = LoadBaselineChecksums(checksums_file.c_str()); + if (baseline.empty()) + { + log("No baseline checksums loaded, skipping comparison"); + return; + } + + log("=== Comparing with baseline from %s ===", baseline_dir); + + int zone_mismatches = 0; + int room_mismatches = 0; + int mob_mismatches = 0; + int obj_mismatches = 0; + int trig_mismatches = 0; + + for (const auto &zone : zone_table) + { + std::string key = "ZONE " + std::to_string(zone.vnum); + std::string serialized = SerializeZone(zone); + uint32_t crc = CRC32String(serialized); + auto it = baseline.find(key); + if (it != baseline.end() && it->second != crc) + { + ++zone_mismatches; + if (zone_mismatches <= max_mismatches_per_type) + { + log("MISMATCH ZONE %d: baseline=%08X current=%08X", zone.vnum, it->second, crc); + std::string baseline_file = std::string(baseline_dir) + "/zones/" + std::to_string(zone.vnum) + ".txt"; + log(" Baseline: %s", baseline_file.c_str()); + log(" Current buffer: %s", serialized.c_str()); + } + } + } + + for (RoomRnum i = 0; i <= top_of_world; ++i) + { + if (!world[i]) continue; + std::string key = "ROOM " + std::to_string(world[i]->vnum); + std::string serialized = SerializeRoom(world[i]); + uint32_t crc = CRC32String(serialized); + auto it = baseline.find(key); + if (it != baseline.end() && it->second != crc) + { + ++room_mismatches; + if (room_mismatches <= max_mismatches_per_type) + { + log("MISMATCH ROOM %d: baseline=%08X current=%08X", world[i]->vnum, it->second, crc); + std::string baseline_file = std::string(baseline_dir) + "/rooms/" + std::to_string(world[i]->vnum) + ".txt"; + log(" Baseline: %s", baseline_file.c_str()); + log(" Current buffer: %s", serialized.c_str()); + } + } + } + + for (MobRnum i = 0; i <= top_of_mobt; ++i) + { + std::string key = "MOB " + std::to_string(mob_index[i].vnum); + std::string serialized = SerializeMob(i); + uint32_t crc = CRC32String(serialized); + auto it = baseline.find(key); + if (it != baseline.end() && it->second != crc) + { + ++mob_mismatches; + if (mob_mismatches <= max_mismatches_per_type) + { + log("MISMATCH MOB %d: baseline=%08X current=%08X", mob_index[i].vnum, it->second, crc); + std::string baseline_file = std::string(baseline_dir) + "/mobs/" + std::to_string(mob_index[i].vnum) + ".txt"; + log(" Baseline: %s", baseline_file.c_str()); + log(" Current buffer: %s", serialized.c_str()); + } + } + } + + for (size_t i = 0; i < obj_proto.size(); ++i) + { + if (!obj_proto[i]) continue; + std::string key = "OBJ " + std::to_string(obj_proto[i]->get_vnum()); + std::string serialized = SerializeObject(obj_proto[i]); + uint32_t crc = CRC32String(serialized); + auto it = baseline.find(key); + if (it != baseline.end() && it->second != crc) + { + ++obj_mismatches; + if (obj_mismatches <= max_mismatches_per_type) + { + log("MISMATCH OBJ %d: baseline=%08X current=%08X", obj_proto[i]->get_vnum(), it->second, crc); + std::string baseline_file = std::string(baseline_dir) + "/objects/" + std::to_string(obj_proto[i]->get_vnum()) + ".txt"; + log(" Baseline: %s", baseline_file.c_str()); + log(" Current buffer: %s", serialized.c_str()); + } + } + } + + for (int i = 0; i < top_of_trigt; ++i) + { + if (!trig_index[i]) continue; + std::string key = "TRIG " + std::to_string(trig_index[i]->vnum); + std::string serialized = SerializeTrigger(i); + uint32_t crc = CRC32String(serialized); + auto it = baseline.find(key); + if (it != baseline.end() && it->second != crc) + { + ++trig_mismatches; + if (trig_mismatches <= max_mismatches_per_type) + { + log("MISMATCH TRIG %d: baseline=%08X current=%08X", trig_index[i]->vnum, it->second, crc); + std::string baseline_file = std::string(baseline_dir) + "/triggers/" + std::to_string(trig_index[i]->vnum) + ".txt"; + log(" Baseline: %s", baseline_file.c_str()); + log(" Current buffer: %s", serialized.c_str()); + } + } + } + + log("=== Comparison Summary ==="); + log("Zone mismatches: %d", zone_mismatches); + log("Room mismatches: %d", room_mismatches); + log("Mob mismatches: %d", mob_mismatches); + log("Obj mismatches: %d", obj_mismatches); + log("Trig mismatches: %d", trig_mismatches); + log("Total mismatches: %d", zone_mismatches + room_mismatches + mob_mismatches + obj_mismatches + trig_mismatches); +} + +} // namespace WorldChecksum + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/world_checksum.h b/src/engine/db/world_checksum.h new file mode 100644 index 000000000..dd280be7e --- /dev/null +++ b/src/engine/db/world_checksum.h @@ -0,0 +1,67 @@ +// Part of Bylins http://www.mud.ru +// World checksum calculation for detecting changes during refactoring + +#ifndef WORLD_CHECKSUM_H_ +#define WORLD_CHECKSUM_H_ + +#include +#include +#include +#include + +namespace WorldChecksum +{ + +struct ChecksumResult +{ + // Static data (loaded from disk) + uint32_t zones; + uint32_t rooms; + uint32_t mobs; + uint32_t objects; + uint32_t triggers; + uint32_t combined; + + // Runtime data (after initialization) + uint32_t room_scripts; // Actual Script objects attached to rooms + uint32_t mob_scripts; // Actual Script objects attached to mobs + uint32_t obj_scripts; // Actual Script objects attached to objects + uint32_t door_rnums; // Room door vnums converted to rnums + uint32_t zone_rnums_mobs; // zone_rnum field in mob_proto + uint32_t zone_rnums_objects; // zone_rnum field in obj_proto + uint32_t zone_cmd_rnums; // Zone commands with vnum->rnum conversions + uint32_t runtime_combined; // Combined runtime checksum + + size_t zones_count; + size_t rooms_count; + size_t mobs_count; + size_t objects_count; + size_t triggers_count; + + size_t rooms_with_scripts; // Count of rooms with actual scripts + size_t mobs_with_scripts; // Count of mobs with actual scripts + size_t objects_with_scripts; // Count of objects with actual scripts +}; + +ChecksumResult Calculate(); +void LogResult(const ChecksumResult &result); +void SaveDetailedChecksums(const char *filename, const ChecksumResult &checksums); + +// Extended functions for debugging SQLite vs Legacy differences +// Save buffers with labeled fields for comparison +// Returns true on success, false on error (logged to syslog) +bool SaveDetailedBuffers(const char *dir); + +// Load baseline checksums from file +// Returns map of "TYPE VNUM" -> checksum +std::map LoadBaselineChecksums(const char *filename); + +// Compare current state with baseline and print first N mismatches per type +// baseline_dir contains files from SaveDetailedBuffers() +void CompareWithBaseline(const char *baseline_dir, int max_mismatches_per_type = 3); + +} + +#endif // WORLD_CHECKSUM_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/world_data_source.h b/src/engine/db/world_data_source.h new file mode 100644 index 000000000..907b4ddea --- /dev/null +++ b/src/engine/db/world_data_source.h @@ -0,0 +1,51 @@ +// Part of Bylins http://www.mud.ru +// World data source interface for pluggable world loading + +#ifndef WORLD_DATA_SOURCE_H_ +#define WORLD_DATA_SOURCE_H_ + +#include +#include + +namespace world_loader +{ + +// Abstract interface for world data sources +// Allows different implementations (legacy files, YAML, database, etc.) +class IWorldDataSource +{ +public: + virtual ~IWorldDataSource() = default; + + // Returns the name/description of this data source for logging + virtual std::string GetName() const = 0; + + // Load world data in the correct order + // Each method populates the global data structures + virtual void LoadZones() = 0; + virtual void LoadTriggers() = 0; + virtual void LoadRooms() = 0; + virtual void LoadMobs() = 0; + virtual void LoadObjects() = 0; + + // Save world data per zone (used by OLC) + // zone_rnum is the runtime zone index + // specific_vnum = -1 means save all entities in the zone + // specific_vnum >= 0 means save only that specific entity + // notify_level is the minimum level to receive mudlog notifications + // Returns true on success, false on error + virtual void SaveZone(int zone_rnum) = 0; + virtual bool SaveTriggers(int zone_rnum, int specific_vnum, int notify_level) = 0; + virtual void SaveRooms(int zone_rnum, int specific_vnum = -1) = 0; + virtual void SaveMobs(int zone_rnum, int specific_vnum = -1) = 0; + virtual void SaveObjects(int zone_rnum, int specific_vnum = -1) = 0; +}; + +// Factory function type for creating data sources +using DataSourceFactory = std::unique_ptr(*)(); + +} // namespace world_loader + +#endif // WORLD_DATA_SOURCE_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/world_data_source_base.cpp b/src/engine/db/world_data_source_base.cpp new file mode 100644 index 000000000..c9aff6f06 --- /dev/null +++ b/src/engine/db/world_data_source_base.cpp @@ -0,0 +1,191 @@ +// Part of Bylins http://www.mud.ru +// Base class for world data sources - implementation + +#include "world_data_source_base.h" +#include "db.h" +#include "obj_prototypes.h" +#include "utils/utils.h" +#include "engine/entities/char_data.h" +#include "engine/entities/zone.h" +#include "engine/entities/room_data.h" +#include "engine/scripting/dg_scripts.h" +#include "engine/scripting/dg_olc.h" + +#include +#include + +// External declarations +extern IndexData **trig_index; +extern int top_of_trigt; +extern CharData *mob_proto; + +namespace world_loader + +{ + +// Parse trigger script from string into cmdlist +// Extracted from YAML/SQLite loaders (100% identical code) +void WorldDataSourceBase::ParseTriggerScript(Trigger *trig, const std::string &script) +{ + if (!script.empty()) + { + std::istringstream ss(script); + int indlev = 0; + std::string line; + cmdlist_element::shared_ptr head = nullptr; + cmdlist_element::shared_ptr tail = nullptr; + + while (std::getline(ss, line)) + { + // Skip empty lines BEFORE trim (same as Legacy loader) + // Lines with whitespace are not considered empty - they become "" after trim + if (line.empty() || line == "\r") + { + continue; + } + + utils::TrimRight(line); + auto cmd = std::make_shared(); + indent_trigger(line, &indlev); + cmd->cmd = line; + cmd->next = nullptr; + + if (!head) + { + head = cmd; + tail = cmd; + } + else + { + tail->next = cmd; + tail = cmd; + } + } + + trig->cmdlist = std::make_shared(head); + } +} + +// Initialize zone runtime fields to defaults +// Extracted from YAML/SQLite loaders (nearly identical code) +void WorldDataSourceBase::InitializeZoneRuntimeFields(ZoneData &zone, int under_construction) +{ + zone.age = 0; + zone.time_awake = 0; + zone.traffic = 0; + zone.under_construction = under_construction; + zone.locked = false; + zone.used = false; + zone.activity = 0; + zone.mob_level = 0; + zone.is_town = false; + zone.count_reset = 0; + zone.typeA_count = 0; + zone.typeA_list = nullptr; + zone.typeB_count = 0; + zone.typeB_list = nullptr; + zone.typeB_flag = nullptr; + zone.cmd = nullptr; + zone.RnumTrigsLocation.first = -1; + zone.RnumTrigsLocation.second = -1; + zone.RnumMobsLocation.first = -1; + zone.RnumMobsLocation.second = -1; + zone.RnumRoomsLocation.first = -1; + zone.RnumRoomsLocation.second = -1; +} + +// Create trigger index entry +// Extracted from YAML/SQLite loaders (100% identical code) +IndexData* WorldDataSourceBase::CreateTriggerIndex(int vnum, Trigger *trig) +{ + IndexData *index; + CREATE(index, 1); + index->vnum = vnum; + index->total_online = 0; + index->func = nullptr; + index->proto = trig; + + trig_index[top_of_trigt++] = index; + return index; +} + +// Attach trigger to room +// Common logic: create proto_script if needed, validate trigger exists, add vnum +void WorldDataSourceBase::AttachTriggerToRoom(RoomRnum room_rnum, int trigger_vnum, RoomVnum room_vnum) +{ + if (!world[room_rnum]->proto_script) + { + world[room_rnum]->proto_script = std::make_shared(); + } + + if (GetTriggerRnum(trigger_vnum) >= 0) + { + world[room_rnum]->proto_script->push_back(trigger_vnum); + } + else + { + log("SYSERR: Room %d references non-existent trigger %d, skipping.", room_vnum, trigger_vnum); + } +} + +// Attach trigger to mob +// Common logic: create proto_script if needed, validate trigger exists, add vnum +void WorldDataSourceBase::AttachTriggerToMob(MobRnum mob_rnum, int trigger_vnum, MobVnum mob_vnum) +{ + if (!mob_proto[mob_rnum].proto_script) + { + mob_proto[mob_rnum].proto_script = std::make_shared(); + } + + if (GetTriggerRnum(trigger_vnum) >= 0) + { + mob_proto[mob_rnum].proto_script->push_back(trigger_vnum); + } + else + { + log("SYSERR: Mob %d references non-existent trigger %d, skipping.", mob_vnum, trigger_vnum); + } +} + +// Attach trigger to object +// Common logic: validate trigger exists and add vnum +// Note: proto_script is always initialized in CObjectPrototype constructor +void WorldDataSourceBase::AttachTriggerToObject(ObjRnum obj_rnum, int trigger_vnum, ObjVnum obj_vnum) +{ + if (GetTriggerRnum(trigger_vnum) >= 0) + { + obj_proto.at(obj_rnum)->add_proto_script(trigger_vnum); + } + else + { + log("SYSERR: Object %d references non-existent trigger %d, skipping.", obj_vnum, trigger_vnum); + } +} + + +// Assign triggers to all loaded rooms +// Common post-processing step for all loaders +void WorldDataSourceBase::AssignTriggersToLoadedRooms() +{ + log("Assigning triggers to rooms..."); + int assigned_count = 0; + + for (RoomRnum rnum = kFirstRoom; rnum <= top_of_world; ++rnum) + { + if (world[rnum]->proto_script && !world[rnum]->proto_script->empty()) + { + assign_triggers(world[rnum], WLD_TRIGGER); + ++assigned_count; + } + } + + if (assigned_count > 0) + { + log(" Assigned triggers to %d rooms.", assigned_count); + } +} +} // namespace world_loader + + + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/world_data_source_base.h b/src/engine/db/world_data_source_base.h new file mode 100644 index 000000000..1dccb10db --- /dev/null +++ b/src/engine/db/world_data_source_base.h @@ -0,0 +1,53 @@ +// Part of Bylins http://www.mud.ru +// Base class for world data sources with shared code + +#ifndef WORLD_DATA_SOURCE_BASE_H_ +#define WORLD_DATA_SOURCE_BASE_H_ + +#include "world_data_source.h" +#include "engine/scripting/dg_scripts.h" + +#include +#include + +class ZoneData; +struct IndexData; + +namespace world_loader +{ + +// Base class providing common functionality for world loaders +// Extracted to eliminate code duplication between YAML/SQLite loaders +class WorldDataSourceBase : public IWorldDataSource +{ +public: + // Assign triggers from proto_script to actual room scripts + // Call after all rooms are loaded - common post-processing step + static void AssignTriggersToLoadedRooms(); + +protected: + // Parse trigger script from string into cmdlist + // Used by both YAML and SQLite loaders - 100% identical code + static void ParseTriggerScript(Trigger *trig, const std::string &script); + + // Initialize zone runtime fields to defaults + // Nearly identical between loaders (only under_construction differs) + static void InitializeZoneRuntimeFields(ZoneData &zone, int under_construction = 0); + + // Create trigger index entry + // Used by both YAML and SQLite loaders - 100% identical code + static IndexData* CreateTriggerIndex(int vnum, Trigger *trig); + + // Attach trigger to room/mob/object + // Common logic for all loaders - checks trigger exists and adds to proto_script + static void AttachTriggerToRoom(RoomRnum room_rnum, int trigger_vnum, RoomVnum room_vnum); + static void AttachTriggerToMob(MobRnum mob_rnum, int trigger_vnum, MobVnum mob_vnum); + static void AttachTriggerToObject(ObjRnum obj_rnum, int trigger_vnum, ObjVnum obj_vnum); + +}; + +} // namespace world_loader + +#endif // WORLD_DATA_SOURCE_BASE_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/world_data_source_manager.cpp b/src/engine/db/world_data_source_manager.cpp new file mode 100644 index 000000000..14f506156 --- /dev/null +++ b/src/engine/db/world_data_source_manager.cpp @@ -0,0 +1,40 @@ +/** + * \file world_data_source_manager.cpp + * \brief Implementation of WorldDataSourceManager singleton. + * \author Claude Code + * \date 2026-01-28 + */ + +#include "world_data_source_manager.h" +#include + +namespace world_loader +{ + +WorldDataSourceManager& WorldDataSourceManager::Instance() +{ + static WorldDataSourceManager instance; + return instance; +} + +void WorldDataSourceManager::SetDataSource(std::unique_ptr data_source) +{ + data_source_ = std::move(data_source); +} + +IWorldDataSource* WorldDataSourceManager::GetDataSource() const +{ + // Data source MUST be initialized before use + // If this assert fails - fix initialization, don't hide the bug! + assert(data_source_ && "WorldDataSource not initialized! Call SetDataSource() first."); + return data_source_.get(); +} + +bool WorldDataSourceManager::HasDataSource() const +{ + return data_source_ != nullptr; +} + +} // namespace world_loader + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/world_data_source_manager.h b/src/engine/db/world_data_source_manager.h new file mode 100644 index 000000000..b3259025d --- /dev/null +++ b/src/engine/db/world_data_source_manager.h @@ -0,0 +1,73 @@ +/** + * \file world_data_source_manager.h + * \brief Singleton manager for world data source. + * \author Claude Code + * \date 2026-01-28 + * + * Provides global access to the active IWorldDataSource implementation. + * Used by OLC system to save world data in the same format it was loaded from. + */ + +#ifndef BYLINS_SRC_ENGINE_DB_WORLD_DATA_SOURCE_MANAGER_H_ +#define BYLINS_SRC_ENGINE_DB_WORLD_DATA_SOURCE_MANAGER_H_ + +#include "world_data_source.h" + +namespace world_loader +{ + +/** + * \class WorldDataSourceManager + * \brief Singleton manager for world data source access. + * + * Stores the active IWorldDataSource instance and provides global access. + * The data source is set during world boot and used by OLC for saving. + */ +class WorldDataSourceManager { +public: + /** + * \brief Get singleton instance. + * \return Reference to the singleton instance. + */ + static WorldDataSourceManager& Instance(); + + /** + * \brief Set the active data source. + * \param data_source Unique pointer to the data source implementation. + * + * Transfers ownership to the manager. Should be called once during boot. + */ + void SetDataSource(std::unique_ptr data_source); + + /** + * \brief Get the active data source. + * \return Raw pointer to the data source, or nullptr if not set. + * + * Returns a non-owning pointer for use by OLC and other systems. + */ + IWorldDataSource* GetDataSource() const; + + /** + * \brief Check if data source is available. + * \return true if data source is set, false otherwise. + */ + bool HasDataSource() const; + + // Delete copy/move constructors and assignment operators + WorldDataSourceManager(const WorldDataSourceManager&) = delete; + WorldDataSourceManager& operator=(const WorldDataSourceManager&) = delete; + WorldDataSourceManager(WorldDataSourceManager&&) = delete; + WorldDataSourceManager& operator=(WorldDataSourceManager&&) = delete; + +private: + WorldDataSourceManager() = default; + ~WorldDataSourceManager() = default; + + std::unique_ptr data_source_; +}; + +} // namespace world_loader + +#endif // BYLINS_SRC_ENGINE_DB_WORLD_DATA_SOURCE_MANAGER_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/yaml_world_data_source.cpp b/src/engine/db/yaml_world_data_source.cpp new file mode 100644 index 000000000..8225a0466 --- /dev/null +++ b/src/engine/db/yaml_world_data_source.cpp @@ -0,0 +1,3775 @@ +// Part of Bylins http://www.mud.ru +// YAML world data source implementation + +#ifdef HAVE_YAML + +#include "yaml_world_data_source.h" +#include "dictionary_loader.h" +#include "db.h" +#include "obj_prototypes.h" +#include "global_objects.h" +#include "utils/logger.h" +#include "utils/utils.h" +#include "utils/utils_string.h" +#include "engine/entities/zone.h" +#include "engine/entities/room_data.h" +#include "engine/entities/char_data.h" +#include "engine/entities/entities_constants.h" +#include "engine/scripting/dg_scripts.h" +#include "engine/db/description.h" +#include "engine/structs/extra_description.h" +#include "gameplay/mechanics/dungeons.h" +#include "engine/scripting/dg_olc.h" +#include "gameplay/affects/affect_contants.h" +#include "gameplay/skills/skills.h" +#include "utils/thread_pool.h" + +#include +#include +#include +#include +#include +#include +#include + +// External declarations +extern ZoneTable &zone_table; +extern IndexData **trig_index; +extern int top_of_trigt; +extern Rooms &world; +extern RoomRnum top_of_world; +extern IndexData *mob_index; +extern MobRnum top_of_mobt; +extern CharData *mob_proto; + +namespace world_loader +{ + +// Convert dictionary index to bitvector flag value +// Handles multi-plane flags (plane 0: bits 0-29, plane 1: bits 30-59, plane 2: bits 60-89) +inline Bitvector IndexToBitvector(long idx) +{ + if (idx < 0) + return 0; + if (idx < 30) + return 1u << idx; + if (idx < 60) + return kIntOne | (1u << (idx - 30)); + if (idx < 90) + return kIntTwo | (1u << (idx - 60)); + return 1u << idx; +} + +// ============================================================================ +// Koi8rYamlEmitter - Custom YAML emitter that preserves KOI8-R encoding +// ============================================================================ + +class Koi8rYamlEmitter { + std::ostream &out_; + int indent_; + +public: + Koi8rYamlEmitter(std::ostream &out) : out_(out), indent_(0) {} + + std::string GetIndent() const { + return std::string(indent_, ' '); + } + + void BeginMap() { + // Maps don't need special output, just increase indent for nested content + } + + void EndMap() { + // Nothing to do + } + + void Key(const std::string &key) { + out_ << GetIndent() << key << ":"; + } + + void Value(const std::string &value, bool literal = false) { + if (literal && value.find('\n') != std::string::npos) { + // Literal block + out_ << " |" << std::endl; + + // Remove \r, keep \n + std::string cleaned = value; + cleaned.erase(std::remove(cleaned.begin(), cleaned.end(), '\r'), cleaned.end()); + + std::istringstream iss(cleaned); + std::string line; + while (std::getline(iss, line)) { + out_ << GetIndent() << " " << line << std::endl; + } + } else { + // Simple value - quote if contains special YAML characters + bool needs_quoting = value.empty(); + + if (!value.empty()) { + // Check for special YAML characters + if (value.find(':') != std::string::npos || + value.find('#') != std::string::npos || + value.find('[') != std::string::npos || + value.find(']') != std::string::npos || + value.find('{') != std::string::npos || + value.find('}') != std::string::npos || + value.find('|') != std::string::npos || + value.find('>') != std::string::npos || + value.find('\"') != std::string::npos || + value.find('\'') != std::string::npos || + value.find('%') != std::string::npos || // % is YAML directive indicator + value[0] == ' ' || value[0] == '-' || value[0] == '?' || + value[0] == '@' || value[0] == '`') { + needs_quoting = true; + } + } + + if (needs_quoting) { + // Use single quotes, escape single quotes inside by doubling them + out_ << " '" << std::regex_replace(value, std::regex("'"), "''") << "'" << std::endl; + } else { + out_ << " " << value << std::endl; + } + } + } + + void Value(int value, const std::string &comment = "") { + out_ << " " << value; + if (!comment.empty()) { + out_ << " # " << comment; + } + out_ << std::endl; + } + + void Value(long value, const std::string &comment = "") { + out_ << " " << value; + if (!comment.empty()) { + out_ << " # " << comment; + } + out_ << std::endl; + } + + void BeginSequence() { + out_ << std::endl; + } + + void SequenceItem(const std::string &value, const std::string &comment = "") { + out_ << GetIndent() << "- " << value; + if (!comment.empty()) { + out_ << " # " << comment; + } + out_ << std::endl; + } + + void SequenceItem(int value, const std::string &comment = "") { + out_ << GetIndent() << "- " << value; + if (!comment.empty()) { + out_ << " # " << comment; + } + out_ << std::endl; + } + + void IncreaseIndent() { + indent_ += 2; + } + + void DecreaseIndent() { + indent_ -= 2; + } + + void Comment(const std::string &text) { + out_ << GetIndent() << "# " << text << std::endl; + } + + void EmptyLine() { + out_ << std::endl; + } +}; + +// ============================================================================ +// Helper functions for YAML comments +// ============================================================================ + +// Get trigger name by vnum (for trigger comments) +std::string GetTriggerNameComment(int trigger_vnum) { + int rnum = GetTriggerRnum(trigger_vnum); + if (rnum >= 0 && rnum <= top_of_trigt) { + return trig_index[rnum]->proto->get_name(); + } + return ""; +} + +// Get room name by rnum (for exit comments) +std::string GetRoomNameComment(RoomRnum room_rnum) { + if (room_rnum >= 0 && room_rnum < static_cast(world.size()) && world[room_rnum]) { + return world[room_rnum]->name ? world[room_rnum]->name : ""; + } + return ""; +} + +// Get skill name by ID (for skill comments) +std::string GetSkillNameComment(ESkill skill_id) { + return MUD::Skill(skill_id).GetName(); +} + +// Get spell name by ID (for spell comments) +std::string GetSpellNameComment(ESpell spell_id) { + return MUD::Spell(spell_id).GetName(); +} + +// Get material name by ID (for material comments) +std::string GetMaterialNameComment(int material_id) { + return ::material_name[material_id]; +} + +// Get apply type name by ID (for apply location comments) +std::string GetApplyTypeNameComment(int apply_id) { + return ::apply_types[apply_id]; +} + +// ============================================================================ +// YamlWorldDataSource implementation +// ============================================================================ + +YamlWorldDataSource::YamlWorldDataSource(const std::string &world_dir) + : m_world_dir(world_dir) +{ + // Load world configuration (required) + if (!LoadWorldConfig()) { + log("SYSERR: Failed to load world_config.yaml"); + fprintf(stderr, "\n"); + fprintf(stderr, "ERROR: World is in Legacy format, but server built with YAML support.\n"); + fprintf(stderr, "To convert world to YAML format, run from world directory (NOT world/world):\n"); + fprintf(stderr, " cd %s\n", m_world_dir.c_str()); + fprintf(stderr, " /path/to/convert_to_yaml.py -i . -o . --format yaml --type all\n"); + fprintf(stderr, "\n"); + exit(1); + } +} + +// Helper: get configured thread count from runtime config +size_t YamlWorldDataSource::GetConfiguredThreadCount() const +{ + size_t configured = runtime_config.yaml_threads(); + if (configured == 0) + { + size_t hw_threads = std::thread::hardware_concurrency(); + return (hw_threads > 0) ? hw_threads : 1; + } + return configured; +} + +// Load world configuration from world_config.yaml +bool YamlWorldDataSource::LoadWorldConfig() +{ + std::string config_path = m_world_dir + "/world_config.yaml"; + + // Config file is required + std::ifstream test_file(config_path); + if (!test_file.good()) { + log("SYSERR: World configuration file not found: %s", config_path.c_str()); + return false; + } + test_file.close(); + + try { + YAML::Node config = YAML::LoadFile(config_path); + + // Read line_endings setting (required) + if (!config["line_endings"]) { + log("SYSERR: Missing 'line_endings' in world_config.yaml"); + return false; + } + + std::string line_endings = config["line_endings"].as(); + if (line_endings == "dos") { + m_convert_lf_to_crlf = true; + log("World config: DOS line endings (LF -> CR+LF conversion enabled)"); + } else if (line_endings == "unix") { + m_convert_lf_to_crlf = false; + log("World config: Unix line endings (no conversion)"); + } else { + log("SYSERR: Invalid line_endings value '%s' (expected 'dos' or 'unix')", line_endings.c_str()); + return false; + } + + return true; + } catch (const YAML::Exception &e) { + log("SYSERR: Failed to parse world_config.yaml: %s", e.what()); + return false; + } +} + +bool YamlWorldDataSource::LoadDictionaries() +{ + if (m_dictionaries_loaded) + { + return true; + } + + std::string dict_dir = m_world_dir + "/dictionaries"; + if (!DictionaryManager::Instance().LoadDictionaries(dict_dir)) + { + log("SYSERR: Failed to load dictionaries from %s", dict_dir.c_str()); + return false; + } + + m_dictionaries_loaded = true; + return true; +} + +std::vector YamlWorldDataSource::GetZoneList() +{ + std::vector zones; + std::string index_path = m_world_dir + "/zones/index.yaml"; + + try + { + YAML::Node root = YAML::LoadFile(index_path); + if (root["zones"] && root["zones"].IsSequence()) + { + for (const auto &zone_node : root["zones"]) + { + zones.push_back(zone_node.as()); + } + } + } + catch (const YAML::Exception &e) + { + log("SYSERR: Failed to load zone index '%s': %s", index_path.c_str(), e.what()); + } + + return zones; +} + +std::vector YamlWorldDataSource::GetMobList() +{ + std::vector mobs; + std::string index_path = m_world_dir + "/mobs/index.yaml"; + + try + { + YAML::Node root = YAML::LoadFile(index_path); + if (root["mobs"] && root["mobs"].IsSequence()) + { + for (const auto &mob_node : root["mobs"]) + { + mobs.push_back(mob_node.as()); + } + } + } + catch (const YAML::Exception &e) + { + // Index file is optional, if missing load all mobs + return mobs; + } + + return mobs; +} + +std::vector YamlWorldDataSource::GetObjectList() +{ + std::vector objects; + std::string index_path = m_world_dir + "/objects/index.yaml"; + + try + { + YAML::Node root = YAML::LoadFile(index_path); + if (root["objects"] && root["objects"].IsSequence()) + { + for (const auto &obj_node : root["objects"]) + { + objects.push_back(obj_node.as()); + } + } + } + catch (const YAML::Exception &e) + { + // Index file is optional, if missing load all objects + return objects; + } + + return objects; +} + +std::vector YamlWorldDataSource::GetTriggerList() +{ + std::vector triggers; + std::string index_path = m_world_dir + "/triggers/index.yaml"; + + try + { + YAML::Node root = YAML::LoadFile(index_path); + if (root["triggers"] && root["triggers"].IsSequence()) + { + for (const auto &trigger_node : root["triggers"]) + { + triggers.push_back(trigger_node.as()); + } + } + } + catch (const YAML::Exception &e) + { + // Index file is optional, if missing load all triggers + return triggers; + } + + return triggers; +} + +std::string YamlWorldDataSource::ConvertToKoi8r(const std::string &utf8_str) const +{ + if (utf8_str.empty()) + { + return ""; + } + + static char buffer[65536]; + char *input = const_cast(utf8_str.c_str()); + char *output = buffer; + utf8_to_koi(input, output); + return buffer; +} + +std::string YamlWorldDataSource::GetText(const YAML::Node &node, const std::string &key, const std::string &default_val) const +{ + if (node[key]) + { + // YAML files are already in KOI8-R, no conversion needed + std::string text = node[key].as(); + + // Convert line endings if configured for DOS format + if (m_convert_lf_to_crlf) { + std::string result; + result.reserve(text.size() * 2); // Reserve space for worst case + for (size_t i = 0; i < text.size(); ++i) { + if (text[i] == '\n' && (i == 0 || text[i-1] != '\r')) { + result += "\r\n"; + } else { + result += text[i]; + } + } + return result; + } + + return text; + } + return default_val; +} + +int YamlWorldDataSource::GetInt(const YAML::Node &node, const std::string &key, int default_val) const +{ + if (node[key]) + { + return node[key].as(); + } + return default_val; +} + +long YamlWorldDataSource::GetLong(const YAML::Node &node, const std::string &key, long default_val) const +{ + if (node[key]) + { + return node[key].as(); + } + return default_val; +} + +FlagData YamlWorldDataSource::ParseFlags(const YAML::Node &node, const std::string &dict_name) const +{ + FlagData flags; + + if (!node || !node.IsSequence()) + { + return flags; + } + + auto &dm = DictionaryManager::Instance(); + + for (const auto &flag_node : node) + { + std::string flag_name = flag_node.as(); + + // Handle UNUSED_XX flags directly + if (flag_name.rfind("UNUSED_", 0) == 0) + { + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + flags.set_flag(plane, 1 << bit_in_plane); + continue; + } + + long bit_pos = dm.Lookup(dict_name, flag_name, -1); + if (bit_pos >= 0) + { + size_t plane = bit_pos / 30; + int bit = bit_pos % 30; + flags.set_flag(plane, 1 << bit); + } + } + + return flags; +} + +int YamlWorldDataSource::ParseEnum(const YAML::Node &node, const std::string &dict_name, int default_val) const +{ + if (!node) + { + return default_val; + } + + std::string name = node.as(); + auto &dm = DictionaryManager::Instance(); + long val = dm.Lookup(dict_name, name, default_val); + return static_cast(val); +} + +int YamlWorldDataSource::ParsePosition(const YAML::Node &node) const +{ + return ParseEnum(node, "positions", static_cast(EPosition::kStand)); +} + +int YamlWorldDataSource::ParseGender(const YAML::Node &node, int default_val) const +{ + return ParseEnum(node, "genders", default_val); +} + +// ============================================================================ +// Zone Loading +// ============================================================================ + +// Parse single zone file (thread-safe worker function) +ZoneData YamlWorldDataSource::ParseZoneFile(const std::string &file_path) +{ + YAML::Node root = YAML::LoadFile(file_path); + + // Extract vnum from filepath (pattern: .../zones/VNUM/zone.yaml) + size_t last_slash = file_path.rfind("/zone.yaml"); + size_t prev_slash = file_path.rfind('/', last_slash - 1); + std::string vnum_str = file_path.substr(prev_slash + 1, last_slash - prev_slash - 1); + int zone_vnum = std::atoi(vnum_str.c_str()); + + ZoneData zone; + zone.vnum = GetInt(root, "vnum", zone_vnum); + zone.name = GetText(root, "name", "Unknown Zone"); + zone.group = GetInt(root, "zone_group", 1); + if (zone.group == 0) zone.group = 1; + + // Read metadata subfields + if (root["metadata"]) + { + zone.comment = GetText(root["metadata"], "comment"); + zone.location = GetText(root["metadata"], "location"); + zone.author = GetText(root["metadata"], "author"); + zone.description = GetText(root["metadata"], "description"); + } + zone.top = GetInt(root, "top_room", zone.vnum * 100 + 99); + zone.lifespan = GetInt(root, "lifespan", 30); + zone.reset_mode = GetInt(root, "reset_mode", 2); + zone.reset_idle = GetInt(root, "reset_idle", 0) != 0; + zone.type = GetInt(root, "zone_type", 0); + zone.level = GetInt(root, "mode", 0); + zone.entrance = GetInt(root, "entrance", 0); + + // Initialize runtime fields (uses base class method) + int under_construction = GetInt(root, "under_construction", 0); + InitializeZoneRuntimeFields(zone, under_construction); + + // Load zone commands + if (root["commands"]) + { + LoadZoneCommands(zone, root["commands"]); + } + else + { + CREATE(zone.cmd, 1); + zone.cmd[0].command = 'S'; + } + + // Load zone groups (typeA and typeB) + if (root["typeA_zones"] && root["typeA_zones"].IsSequence()) + { + zone.typeA_count = root["typeA_zones"].size(); + CREATE(zone.typeA_list, zone.typeA_count); + int i = 0; + for (const auto &z : root["typeA_zones"]) + { + zone.typeA_list[i++] = z.as(); + } + } + + if (root["typeB_zones"] && root["typeB_zones"].IsSequence()) + { + zone.typeB_count = root["typeB_zones"].size(); + CREATE(zone.typeB_list, zone.typeB_count); + CREATE(zone.typeB_flag, zone.typeB_count); + int i = 0; + for (const auto &z : root["typeB_zones"]) + { + zone.typeB_list[i] = z.as(); + zone.typeB_flag[i] = false; + i++; + } + } + + return zone; +} + +// Parallel zone loading +void YamlWorldDataSource::LoadZonesParallel() +{ + std::vector zone_vnums = GetZoneList(); + if (zone_vnums.empty()) + { + log("No zones found in YAML index."); + return; + } + + int zone_count = zone_vnums.size(); + zone_table.reserve(zone_count + dungeons::kNumberOfZoneDungeons); + zone_table.resize(zone_count); + log(" %d zones, %zd bytes.", zone_count, sizeof(ZoneData) * zone_count); + + // Sort zone vnums to match Legacy loader order (CRITICAL for checksums) + std::sort(zone_vnums.begin(), zone_vnums.end()); + + // Build vnum to index mapping + std::map vnum_to_idx; + for (size_t i = 0; i < zone_vnums.size(); ++i) + { + vnum_to_idx[zone_vnums[i]] = i; + } + + // Distribute zones into batches + auto batches = utils::DistributeBatches(zone_vnums, m_num_threads); + std::atomic error_count{0}; + + // Launch parallel loading + std::vector> futures; + for (size_t thread_id = 0; thread_id < batches.size(); ++thread_id) + { + futures.push_back(m_thread_pool->Enqueue([this, thread_id, &batches, &vnum_to_idx, &error_count]() { + for (size_t i = 0; i < batches[thread_id].size(); ++i) + { + int zone_vnum = batches[thread_id][i]; + std::string zone_path = m_world_dir + "/zones/" + std::to_string(zone_vnum) + "/zone.yaml"; + + try + { + size_t zone_idx = vnum_to_idx.at(zone_vnum); + zone_table[zone_idx] = ParseZoneFile(zone_path); + + // DEBUG: Check if vnums match + if (zone_table[zone_idx].vnum != zone_vnum) { + log("ERROR: Zone vnum mismatch! Index says %d, but zone.yaml has vnum=%d (file: %s)", + zone_vnum, zone_table[zone_idx].vnum, zone_path.c_str()); + error_count++; + } + } + catch (const YAML::Exception &e) + { + log("SYSERR: Failed to load zone %d from '%s': %s", zone_vnum, zone_path.c_str(), e.what()); + error_count++; + } + catch (const std::exception &e) + { + log("SYSERR: Failed to load zone %d from '%s': %s", zone_vnum, zone_path.c_str(), e.what()); + error_count++; + } + } + })); + } + + // Wait for all tasks + for (auto &future : futures) + { + future.wait(); + } + + if (error_count > 0) + { + log("FATAL: %d zone(s) failed to load. Aborting.", error_count.load()); + exit(1); + } + + log("Loaded %d zones from YAML (parallel).", zone_count); +} + +void YamlWorldDataSource::LoadZones() +{ + log("Loading zones from YAML files."); + + // Load dictionaries first (sequential, writes to singleton) + if (!LoadDictionaries()) + { + log("FATAL: Cannot continue without dictionaries. Aborting."); + exit(1); + } + + // Get thread count and create thread pool + m_num_threads = GetConfiguredThreadCount(); + log("YAML loading with %zu threads", m_num_threads); + m_thread_pool = std::make_unique(m_num_threads); + + // Parallel load zones + LoadZonesParallel(); +} + +void YamlWorldDataSource::LoadZoneCommands(ZoneData &zone, const YAML::Node &commands_node) +{ + if (!commands_node.IsSequence()) + { + CREATE(zone.cmd, 1); + zone.cmd[0].command = 'S'; + return; + } + + int cmd_count = commands_node.size(); + CREATE(zone.cmd, cmd_count + 1); + + int idx = 0; + for (const auto &cmd_node : commands_node) + { + struct reset_com &cmd = zone.cmd[idx]; + + cmd.command = '*'; + cmd.if_flag = GetInt(cmd_node, "if_flag", 0); + cmd.arg1 = 0; + cmd.arg2 = 0; + cmd.arg3 = 0; + cmd.arg4 = -1; + cmd.sarg1 = nullptr; + cmd.sarg2 = nullptr; + cmd.line = 0; + + std::string cmd_type = GetText(cmd_node, "type", ""); + + if (cmd_type == "LOAD_MOB" || cmd_type == "M") + { + cmd.command = 'M'; + cmd.arg1 = GetInt(cmd_node, "mob_vnum"); + cmd.arg2 = GetInt(cmd_node, "max_world"); + cmd.arg3 = GetInt(cmd_node, "room_vnum"); + cmd.arg4 = GetInt(cmd_node, "max_room", -1); + } + else if (cmd_type == "LOAD_OBJ" || cmd_type == "O") + { + cmd.command = 'O'; + cmd.arg1 = GetInt(cmd_node, "obj_vnum"); + cmd.arg2 = GetInt(cmd_node, "max"); + cmd.arg3 = GetInt(cmd_node, "room_vnum"); + cmd.arg4 = GetInt(cmd_node, "load_prob", -1); + } + else if (cmd_type == "GIVE_OBJ" || cmd_type == "G") + { + cmd.command = 'G'; + cmd.arg1 = GetInt(cmd_node, "obj_vnum"); + cmd.arg2 = GetInt(cmd_node, "max"); + cmd.arg3 = -1; + cmd.arg4 = GetInt(cmd_node, "load_prob", -1); + } + else if (cmd_type == "EQUIP_MOB" || cmd_type == "E") + { + cmd.command = 'E'; + cmd.arg1 = GetInt(cmd_node, "obj_vnum"); + cmd.arg2 = GetInt(cmd_node, "max"); + cmd.arg3 = GetInt(cmd_node, "wear_pos"); + cmd.arg4 = GetInt(cmd_node, "load_prob", -1); + } + else if (cmd_type == "PUT_OBJ" || cmd_type == "P") + { + cmd.command = 'P'; + cmd.arg1 = GetInt(cmd_node, "obj_vnum"); + cmd.arg2 = GetInt(cmd_node, "max"); + cmd.arg3 = GetInt(cmd_node, "container_vnum"); + cmd.arg4 = GetInt(cmd_node, "load_prob", -1); + } + else if (cmd_type == "DOOR" || cmd_type == "D") + { + cmd.command = 'D'; + cmd.arg1 = GetInt(cmd_node, "room_vnum"); + cmd.arg2 = GetInt(cmd_node, "direction"); + cmd.arg3 = GetInt(cmd_node, "state"); + } + else if (cmd_type == "REMOVE_OBJ" || cmd_type == "R") + { + cmd.command = 'R'; + cmd.arg1 = GetInt(cmd_node, "room_vnum"); + cmd.arg2 = GetInt(cmd_node, "obj_vnum"); + } + else if (cmd_type == "TRIGGER" || cmd_type == "T") + { + cmd.command = 'T'; + cmd.arg1 = GetInt(cmd_node, "trigger_type"); + cmd.arg2 = GetInt(cmd_node, "trigger_vnum"); + cmd.arg3 = GetInt(cmd_node, "room_vnum", -1); + } + else if (cmd_type == "VARIABLE" || cmd_type == "V") + { + cmd.command = 'V'; + cmd.arg1 = GetInt(cmd_node, "trigger_type"); + cmd.arg2 = GetInt(cmd_node, "context"); + cmd.arg3 = GetInt(cmd_node, "room_vnum"); + std::string var_name = GetText(cmd_node, "var_name"); + std::string var_value = GetText(cmd_node, "var_value"); + if (!var_name.empty()) cmd.sarg1 = str_dup(var_name.c_str()); + if (!var_value.empty()) cmd.sarg2 = str_dup(var_value.c_str()); + } + else if (cmd_type == "EXTRACT_MOB" || cmd_type == "Q") + { + cmd.command = 'Q'; + cmd.arg1 = GetInt(cmd_node, "mob_vnum"); + cmd.if_flag = 0; // Legacy loader forces if_flag = 0 for EXTRACT_MOB + } + else if (cmd_type == "FOLLOW" || cmd_type == "F") + { + cmd.command = 'F'; + cmd.arg1 = GetInt(cmd_node, "room_vnum"); + cmd.arg2 = GetInt(cmd_node, "leader_mob_vnum"); + cmd.arg3 = GetInt(cmd_node, "follower_mob_vnum"); + } + + idx++; + } + + zone.cmd[idx].command = 'S'; +} + +// ============================================================================ +// Trigger Loading +// ============================================================================ + +// Parse single trigger file (thread-safe worker function) +Trigger* YamlWorldDataSource::ParseTriggerFile(const std::string &file_path) +{ + YAML::Node root = YAML::LoadFile(file_path); + + // Extract vnum from filename + // vnum extracted from filename but not used in YAML (stored in file content) + + std::string name = GetText(root, "name", ""); + int attach_type = ParseEnum(root["attach_type"], "attach_types", 0); + + // Parse trigger types + long trigger_type = 0; + if (root["trigger_types"] && root["trigger_types"].IsSequence()) + { + for (const auto &type_node : root["trigger_types"]) + { + std::string type_str = type_node.as(); + auto &dm = DictionaryManager::Instance(); + long type_bit = dm.Lookup("trigger_types", type_str, -1); + if (type_bit >= 0) + { + trigger_type |= (1L << type_bit); + } + } + } + + int narg = GetInt(root, "narg", 0); + std::string arglist = GetText(root, "arglist", ""); + std::string script = GetText(root, "script", ""); + + // Create trigger (note: rnum will be assigned during merge) + auto trig = new Trigger(-1, std::move(name), static_cast(attach_type), trigger_type); + GET_TRIG_NARG(trig) = narg; + trig->arglist = arglist; + + // Parse script into cmdlist + ParseTriggerScript(trig, script); + + // Note: vnum will be passed separately in thread results + return trig; +} + +// Parallel trigger loading +void YamlWorldDataSource::LoadTriggersParallel() +{ + std::vector trigger_vnums = GetTriggerList(); + if (trigger_vnums.empty()) + { + log("No triggers found in YAML index."); + return; + } + + int trig_count = trigger_vnums.size(); + log(" %d triggers.", trig_count); + + // Distribute triggers into batches + auto batches = utils::DistributeBatches(trigger_vnums, m_num_threads); + + // Thread-local results (each thread collects its triggers) + std::vector>> thread_results(batches.size()); + std::atomic error_count{0}; + + // Launch parallel loading + std::vector> futures; + log("DEBUG: Starting %zu trigger loading threads", batches.size()); + for (size_t thread_id = 0; thread_id < batches.size(); ++thread_id) + { + futures.push_back(m_thread_pool->Enqueue([this, thread_id, &batches, &thread_results, &error_count]() { + log("DEBUG: Thread %zu started, processing %zu triggers", thread_id, batches[thread_id].size()); + for (int vnum : batches[thread_id]) + { + std::string filepath = m_world_dir + "/triggers/" + std::to_string(vnum) + ".yaml"; + try + { + log("DEBUG: Thread %zu parsing trigger %d", thread_id, vnum); + Trigger* trig = ParseTriggerFile(filepath); + thread_results[thread_id].emplace_back(vnum, trig); + log("DEBUG: Thread %zu completed trigger %d", thread_id, vnum); + } + catch (const YAML::Exception &e) + { + log("SYSERR: Failed to load trigger from '%s': %s", filepath.c_str(), e.what()); + error_count++; + } + catch (const std::exception &e) + { + log("SYSERR: Failed to load trigger from '%s': %s", filepath.c_str(), e.what()); + error_count++; + } + } + log("DEBUG: Thread %zu finished all triggers", thread_id); + })); + } + + // Wait for all tasks + log("DEBUG: Waiting for all trigger threads to complete"); + for (auto &future : futures) + { + future.wait(); + } + + if (error_count > 0) + { + log("FATAL: %d trigger(s) failed to load. Aborting.", error_count.load()); + exit(1); + } + + // Merge results into trig_index (sequential, sorted by vnum) + // Collect all triggers into single vector and sort by vnum + std::vector> all_triggers; + for (auto &results : thread_results) + { + for (auto &trig_pair : results) + { + all_triggers.push_back(std::move(trig_pair)); + } + } + + // Sort by vnum to match Legacy loader order (required for binary search in GetTriggerRnum) + std::sort(all_triggers.begin(), all_triggers.end(), + [](const auto &a, const auto &b) { return a.first < b.first; }); + + // Add to trig_index in sorted order + CREATE(trig_index, trig_count); + top_of_trigt = 0; + + for (auto &[vnum, trig] : all_triggers) + { + // Assign rnum + trig->set_rnum(top_of_trigt); + // Create index entry + CreateTriggerIndex(vnum, trig); + } + + log("Loaded %d triggers from YAML (parallel).", top_of_trigt); +} + +void YamlWorldDataSource::LoadTriggers() +{ + log("Loading triggers from YAML files."); + + // Dictionaries already loaded in LoadZones, thread pool already created + LoadTriggersParallel(); +} + +// ============================================================================ +// Room Loading +// ============================================================================ + +// Parse single room file (thread-safe worker function) +RoomData* YamlWorldDataSource::ParseRoomFile(const std::string &file_path, int zone_rnum, LocalDescriptionIndex &local_index, size_t &local_desc_idx) +{ + YAML::Node root = YAML::LoadFile(file_path); + + // Extract vnum from filename + size_t last_slash = file_path.find_last_of('/'); + size_t last_dot = file_path.find_last_of('.'); + + std::string filename = file_path.substr(last_slash + 1, last_dot - last_slash - 1); + int rel_num = std::atoi(filename.c_str()); + + // Extract zone vnum from path + size_t zone_start = file_path.rfind("/zones/") + 7; + size_t zone_end = file_path.find("/", zone_start); + int zone_vnum = std::atoi(file_path.substr(zone_start, zone_end - zone_start).c_str()); + int vnum = zone_vnum * 100 + rel_num; + + auto room = new RoomData; + room->vnum = vnum; + room->zone_rn = zone_rnum; + + std::string name = GetText(root, "name", "Untitled Room"); + if (!name.empty()) { name[0] = UPPER(name[0]); } + room->set_name(name); + + std::string description = GetText(root, "description", ""); + if (!description.empty()) + { + // Add to thread-local index (no mutex needed!) + local_desc_idx = local_index.add(description); + // description_num will be set in merge phase + room->description_num = 0; + } + else + { + local_desc_idx = 0; // No description + room->description_num = 0; + } + + // Parse flags and sector + if (root["flags"] && root["flags"].IsSequence()) + { + auto &dm = DictionaryManager::Instance(); + for (const auto &flag_node : root["flags"]) + { + std::string flag_name = flag_node.as(); + long flag_val = dm.Lookup("room_flags", flag_name, -1); + if (flag_val >= 0) + { + room->set_flag(static_cast(IndexToBitvector(flag_val))); + } + } + } + room->sector_type = ParseEnum(root["sector"], "sectors", 0); + + // Load exits + if (root["exits"]) + { + LoadRoomExits(room, root["exits"], vnum); + } + + // Load extra descriptions + if (root["extra_descriptions"]) + { + LoadRoomExtraDescriptions(room, root["extra_descriptions"]); + } + + return room; +} + +// Parallel room loading +// Parallel room loading +void YamlWorldDataSource::LoadRoomsParallel() +{ + // Creating empty world with kNowhere room (dummy room 0) - same as Legacy loader + world.push_back(new RoomData); + top_of_world = kNowhere; + + std::vector zone_vnums = GetZoneList(); + if (zone_vnums.empty()) + { + log("No zones found in YAML index."); + return; + } + std::set enabled_zones(zone_vnums.begin(), zone_vnums.end()); + + // Collect all room files + std::vector> room_files; + std::string zones_dir = m_world_dir + "/zones"; + + namespace fs = std::filesystem; + for (const auto &zone_entry : fs::directory_iterator(zones_dir)) + { + if (!zone_entry.is_directory()) continue; + + std::string zone_dir_name = zone_entry.path().filename().string(); + if (zone_dir_name.empty() || !std::isdigit(zone_dir_name[0])) continue; + + int zone_vnum = std::stoi(zone_dir_name); + if (enabled_zones.find(zone_vnum) == enabled_zones.end()) continue; + + std::string rooms_dir = zone_entry.path().string() + "/rooms"; + if (!fs::exists(rooms_dir)) continue; + + for (const auto &room_entry : fs::directory_iterator(rooms_dir)) + { + if (!room_entry.is_regular_file()) continue; + std::string filename = room_entry.path().filename().string(); + if (filename.size() < 6 || filename.substr(filename.size() - 5) != ".yaml") continue; + + int rel_num = std::atoi(filename.substr(0, filename.size() - 5).c_str()); + int vnum = zone_vnum * 100 + rel_num; + room_files.emplace_back(vnum, room_entry.path().string()); + } + } + + std::sort(room_files.begin(), room_files.end()); + + int room_count = room_files.size(); + if (room_count == 0) + { + log("No rooms found in YAML files."); + return; + } + + log(" %d rooms, %zd bytes.", room_count, sizeof(RoomData) * room_count); + + // Distribute rooms into batches for parallel parsing + auto batches = utils::DistributeBatches(room_files, m_num_threads); + std::atomic error_count{0}; + + // Launch parallel loading with thread-local description indices + std::vector> futures; + for (size_t thread_id = 0; thread_id < batches.size(); ++thread_id) + { + futures.push_back(m_thread_pool->Enqueue([this, thread_id, &batches, &error_count]() -> ParsedRoomBatch { + // Thread-local variables + LocalDescriptionIndex local_index; + std::vector> parsed_rooms; + std::map> local_triggers; + + for (const auto &[vnum, filepath] : batches[thread_id]) + { + try + { + // Calculate zone_rnum from vnum + ZoneRnum zone_rn = 0; + while (zone_rn < static_cast(zone_table.size()) && + vnum > zone_table[zone_rn].top) + { + zone_rn++; + } + + size_t local_desc_idx = 0; + RoomData* room = ParseRoomFile(filepath, zone_rn, local_index, local_desc_idx); + + // Load room triggers (if present) + YAML::Node root = YAML::LoadFile(filepath); + if (root["triggers"] && root["triggers"].IsSequence()) + { + std::vector trigger_list; + for (const auto &trig_node : root["triggers"]) + { + int trigger_vnum = trig_node.as(); + trigger_list.push_back(trigger_vnum); + } + if (!trigger_list.empty()) + { + local_triggers[vnum] = std::move(trigger_list); + } + } + + // Store vnum, room, and local description index + parsed_rooms.push_back(std::make_tuple(vnum, room, local_desc_idx)); + } + catch (const YAML::Exception &e) + { + log("SYSERR: Failed to load room from '%s': %s", filepath.c_str(), e.what()); + error_count++; + } + catch (const std::exception &e) + { + log("SYSERR: Failed to load room from '%s': %s", filepath.c_str(), e.what()); + error_count++; + } + } + + return ParsedRoomBatch{std::move(local_index), std::move(parsed_rooms), std::move(local_triggers)}; + })); + } + + // Collect results from all threads + std::vector parsed_batches; + parsed_batches.reserve(futures.size()); + for (auto &future : futures) + { + parsed_batches.push_back(future.get()); + } + + if (error_count > 0) + { + log("FATAL: %d room(s) failed to load. Aborting.", error_count.load()); + exit(1); + } + + // Merge descriptions from all batches + auto &global_descriptions = GlobalObjects::descriptions(); + std::vector> local_to_global(parsed_batches.size()); + + for (size_t batch_id = 0; batch_id < parsed_batches.size(); ++batch_id) + { + local_to_global[batch_id] = global_descriptions.merge(parsed_batches[batch_id].descriptions); + } + + // Collect all rooms with their batch IDs for description reindexing + std::vector> all_rooms; // (vnum, room, batch_id, local_desc_idx) + for (size_t batch_id = 0; batch_id < parsed_batches.size(); ++batch_id) + { + for (auto &[vnum, room, local_desc_idx] : parsed_batches[batch_id].rooms) + { + all_rooms.push_back(std::make_tuple(vnum, room, batch_id, local_desc_idx)); + } + } + + // Sort rooms by vnum (CRITICAL for correct indexing) + std::sort(all_rooms.begin(), all_rooms.end(), + [](const auto &a, const auto &b) { return std::get<0>(a) < std::get<0>(b); }); + + // Add rooms to world vector in sorted order (sequential, using push_back like Legacy) + for (auto &[vnum, room, batch_id, local_desc_idx] : all_rooms) + { + // Update room's description_num with global index + // local_desc_idx is 1-based (0 = no description, 1 = first description) + // local_to_global is 0-indexed vector + if (local_desc_idx > 0) + { + size_t local_idx_0based = local_desc_idx - 1; + if (local_idx_0based < local_to_global[batch_id].size()) + { + room->description_num = local_to_global[batch_id][local_idx_0based]; + } + } + + world.push_back(room); + } + + top_of_world = world.size() - 1; + log(" Merged %zu unique room descriptions from %zu threads.", global_descriptions.size(), parsed_batches.size()); + + // Update zone_table.RnumRoomsLocation (sequential post-processing) + for (size_t i = 0; i < world.size(); ++i) + { + if (world[i]) + { + ZoneRnum zone_rn = world[i]->zone_rn; + if (zone_rn < static_cast(zone_table.size())) + { + if (zone_table[zone_rn].RnumRoomsLocation.first == -1) + { + zone_table[zone_rn].RnumRoomsLocation.first = i; + } + zone_table[zone_rn].RnumRoomsLocation.second = i; + } + } + } + + // Merge thread-local trigger maps into single map + std::map> room_triggers; + for (auto &batch : parsed_batches) + { + room_triggers.insert(batch.triggers.begin(), batch.triggers.end()); + } + + // Attach triggers (sequential, after all rooms added to world) + for (const auto &[room_vnum, trigger_list] : room_triggers) + { + int room_rnum = GetRoomRnum(room_vnum); + if (room_rnum >= 0) + { + for (int trigger_vnum : trigger_list) + { + AttachTriggerToRoom(room_rnum, trigger_vnum, room_vnum); + } + } + } +} +void YamlWorldDataSource::LoadRooms() +{ + log("Loading rooms from YAML files."); + + // Dictionaries already loaded in LoadZones, thread pool already created + LoadRoomsParallel(); +} + +void YamlWorldDataSource::LoadRoomExits(RoomData *room, const YAML::Node &exits_node, int room_vnum) +{ + if (!exits_node.IsSequence()) + { + return; + } + + for (const auto &exit_node : exits_node) + { + int dir = ParseEnum(exit_node["direction"], "directions", 0); + if (dir < 0 || dir >= EDirection::kMaxDirNum) + { + log("SYSERR: Room %d has invalid exit direction %d, skipping.", room_vnum, dir); + continue; + } + + auto exit_data = std::make_shared(); + + exit_data->to_room(GetInt(exit_node, "to_room", -1)); + exit_data->key = GetInt(exit_node, "key", -1); + exit_data->lock_complexity = GetInt(exit_node, "lock_complexity", 0); + + std::string desc = GetText(exit_node, "description", ""); + if (!desc.empty()) exit_data->general_description = desc; + + std::string keywords = GetText(exit_node, "keywords", ""); + if (!keywords.empty()) exit_data->set_keywords(keywords); + + exit_data->exit_info = GetInt(exit_node, "exit_flags", 0); + + room->dir_option_proto[dir] = exit_data; + } +} + +void YamlWorldDataSource::LoadRoomExtraDescriptions(RoomData *room, const YAML::Node &extras_node) +{ + if (!extras_node.IsSequence()) + { + return; + } + + for (const auto &ed_node : extras_node) + { + std::string keywords = GetText(ed_node, "keywords", ""); + std::string description = GetText(ed_node, "description", ""); + + auto ex_desc = std::make_shared(); + ex_desc->set_keyword(keywords); + ex_desc->set_description(description); + ex_desc->next = room->ex_description; + room->ex_description = ex_desc; + } +} + +// ============================================================================ +// Mob Loading +// ============================================================================ + +// Parse single mob file (thread-safe worker function) +CharData YamlWorldDataSource::ParseMobFile(const std::string &file_path) +{ + YAML::Node root = YAML::LoadFile(file_path); + + // Note: vnum is passed separately by caller, extracted there + CharData mob; + mob.player_specials = player_special_data::s_for_mobiles; + mob.SetNpcAttribute(true); + mob.player_specials->saved.NameGod = 1001; + mob.set_move(100); + mob.set_max_move(100); + + // Names + YAML::Node names = root["names"]; + if (names) + { + mob.SetCharAliases(GetText(names, "aliases")); + mob.set_npc_name(GetText(names, "nominative")); + mob.player_data.PNames[ECase::kNom] = GetText(names, "nominative"); + mob.player_data.PNames[ECase::kGen] = GetText(names, "genitive"); + mob.player_data.PNames[ECase::kDat] = GetText(names, "dative"); + mob.player_data.PNames[ECase::kAcc] = GetText(names, "accusative"); + mob.player_data.PNames[ECase::kIns] = GetText(names, "instrumental"); + mob.player_data.PNames[ECase::kPre] = GetText(names, "prepositional"); + } + + // Descriptions + YAML::Node descs = root["descriptions"]; + if (descs) + { + mob.player_data.long_descr = utils::colorCAP(GetText(descs, "short_desc")); + mob.player_data.description = GetText(descs, "long_desc"); + } + + // Base parameters + GET_ALIGNMENT(&mob) = GetInt(root, "alignment", 0); + + // Stats + YAML::Node stats = root["stats"]; + if (stats) + { + mob.set_level(GetInt(stats, "level", 1)); + GET_HR(&mob) = GetInt(stats, "hitroll_penalty", 20); + GET_AC(&mob) = GetInt(stats, "armor", 100); + + YAML::Node hp = stats["hp"]; + if (hp) + { + mob.mem_queue.total = GetInt(hp, "dice_count", 1); + mob.mem_queue.stored = GetInt(hp, "dice_size", 1); + int hp_bonus = GetInt(hp, "bonus", 0); + mob.set_hit(hp_bonus); + mob.set_max_hit(0); + } + + YAML::Node dmg = stats["damage"]; + if (dmg) + { + mob.mob_specials.damnodice = GetInt(dmg, "dice_count", 1); + mob.mob_specials.damsizedice = GetInt(dmg, "dice_size", 1); + mob.real_abils.damroll = GetInt(dmg, "bonus", 0); + } + } + + // Gold + YAML::Node gold = root["gold"]; + if (gold) + { + mob.mob_specials.GoldNoDs = GetInt(gold, "dice_count", 0); + mob.mob_specials.GoldSiDs = GetInt(gold, "dice_size", 0); + mob.set_gold(GetInt(gold, "bonus", 0)); + } + + // Experience + mob.set_exp(GetInt(root, "experience", 0)); + + // Position + YAML::Node pos = root["position"]; + if (pos) + { + mob.mob_specials.default_pos = static_cast(ParsePosition(pos["default"])); + mob.SetPosition(static_cast(ParsePosition(pos["start"]))); + } + else + { + mob.mob_specials.default_pos = EPosition::kStand; + mob.SetPosition(EPosition::kStand); + } + + // Sex + mob.set_sex(static_cast(ParseGender(root["sex"]))); + + // Race + mob.player_data.Race = static_cast(GetInt(root, "race", ENpcRace::kBasic)); + + // Physical attributes + GET_SIZE(&mob) = GetInt(root, "size", 0); + GET_HEIGHT(&mob) = GetInt(root, "height", 0); + GET_WEIGHT(&mob) = GetInt(root, "weight", 0); + + // E-spec attributes - set defaults, then override + mob.set_str(11); + mob.set_dex(11); + mob.set_int(11); + mob.set_wis(11); + mob.set_con(11); + mob.set_cha(11); + if (root["attributes"]) + { + YAML::Node attrs = root["attributes"]; + mob.set_str(GetInt(attrs, "strength", 11)); + mob.set_dex(GetInt(attrs, "dexterity", 11)); + mob.set_int(GetInt(attrs, "intelligence", 11)); + mob.set_wis(GetInt(attrs, "wisdom", 11)); + mob.set_con(GetInt(attrs, "constitution", 11)); + mob.set_cha(GetInt(attrs, "charisma", 11)); + } + + // Flags + auto &dm = DictionaryManager::Instance(); + if (root["action_flags"] && root["action_flags"].IsSequence()) + { + for (const auto &flag_node : root["action_flags"]) + { + std::string flag_name = flag_node.as(); + long flag_val = dm.Lookup("action_flags", flag_name, -1); + if (flag_val >= 0) + { + mob.SetFlag(static_cast(flag_val)); + } + } + } + + if (root["affect_flags"] && root["affect_flags"].IsSequence()) + { + for (const auto &flag_node : root["affect_flags"]) + { + std::string flag_name = flag_node.as(); + long flag_val = dm.Lookup("affect_flags", flag_name, -1); + if (flag_val >= 0) + { + AFF_FLAGS(&mob).set(static_cast(flag_val)); + } + } + } + + // Skills + if (root["skills"] && root["skills"].IsSequence()) + { + for (const auto &skill_node : root["skills"]) + { + int skill_id = GetInt(skill_node, "skill_id", 0); + int value = GetInt(skill_node, "value", 0); + mob.set_skill(static_cast(skill_id), value); + } + } + + // Enhanced E-spec fields + if (root["enhanced"]) + { + YAML::Node enhanced = root["enhanced"]; + + mob.set_str_add(GetInt(enhanced, "str_add", 0)); + mob.add_abils.hitreg = GetInt(enhanced, "hp_regen", 0); + mob.add_abils.armour = GetInt(enhanced, "armour_bonus", 0); + mob.add_abils.manareg = GetInt(enhanced, "mana_regen", 0); + mob.add_abils.cast_success = GetInt(enhanced, "cast_success", 0); + mob.add_abils.morale = GetInt(enhanced, "morale", 0); + mob.add_abils.initiative_add = GetInt(enhanced, "initiative_add", 0); + mob.add_abils.absorb = GetInt(enhanced, "absorb", 0); + mob.add_abils.aresist = GetInt(enhanced, "aresist", 0); + mob.add_abils.mresist = GetInt(enhanced, "mresist", 0); + mob.add_abils.presist = GetInt(enhanced, "presist", 0); + mob.mob_specials.attack_type = GetInt(enhanced, "bare_hand_attack", 0); + mob.mob_specials.like_work = GetInt(enhanced, "like_work", 0); + mob.mob_specials.MaxFactor = GetInt(enhanced, "max_factor", 0); + mob.mob_specials.extra_attack = GetInt(enhanced, "extra_attack", 0); + mob.set_remort(GetInt(enhanced, "mob_remort", 0)); + + if (enhanced["special_bitvector"]) + { + std::string special_bv = enhanced["special_bitvector"].as(); + mob.mob_specials.npc_flags.from_string((char *)special_bv.c_str()); + } + + if (enhanced["role"]) + { + std::string role_str = enhanced["role"].as(); + CharData::role_t role(role_str); + mob.set_role(role); + } + + if (enhanced["resistances"] && enhanced["resistances"].IsSequence()) + { + int idx = 0; + for (const auto &val_node : enhanced["resistances"]) + { + int value = val_node.as(); + if (idx < static_cast(mob.add_abils.apply_resistance.size())) + { + mob.add_abils.apply_resistance[idx] = value; + } + idx++; + } + } + + if (enhanced["saves"] && enhanced["saves"].IsSequence()) + { + int idx = 0; + for (const auto &val_node : enhanced["saves"]) + { + int value = val_node.as(); + if (idx < static_cast(mob.add_abils.apply_saving_throw.size())) + { + mob.add_abils.apply_saving_throw[idx] = value; + } + idx++; + } + } + + if (enhanced["feats"] && enhanced["feats"].IsSequence()) + { + for (const auto &feat_node : enhanced["feats"]) + { + int feat_id = feat_node.as(); + if (feat_id >= 0 && feat_id < static_cast(mob.real_abils.Feats.size())) + { + mob.real_abils.Feats.set(feat_id); + } + } + } + + if (enhanced["spells"] && enhanced["spells"].IsSequence()) + { + for (const auto &spell_node : enhanced["spells"]) + { + int spell_id = spell_node.as(); + if (spell_id >= 0 && spell_id < static_cast(mob.real_abils.SplKnw.size())) + { + mob.real_abils.SplKnw[spell_id] = 1; + } + } + } + + if (enhanced["helpers"] && enhanced["helpers"].IsSequence()) + { + for (const auto &helper_node : enhanced["helpers"]) + { + int helper_vnum = helper_node.as(); + mob.summon_helpers.push_back(helper_vnum); + } + } + + if (enhanced["destinations"] && enhanced["destinations"].IsSequence()) + { + int idx = 0; + for (const auto &dest_node : enhanced["destinations"]) + { + int room_vnum = dest_node.as(); + if (idx < static_cast(mob.mob_specials.dest.size())) + { + mob.mob_specials.dest[idx] = room_vnum; + } + idx++; + } + } + } + + // Initialize test data if needed + if (mob.GetLevel() == 0) + SetTestData(&mob); + + return mob; +} + +// Parallel mob loading +void YamlWorldDataSource::LoadMobsParallel() +{ + std::vector mob_vnums = GetMobList(); + if (mob_vnums.empty()) + { + log("No mobs found in YAML index."); + return; + } + + int mob_count = mob_vnums.size(); + mob_proto = new CharData[mob_count]; + CREATE(mob_index, mob_count); + log(" %d mobs, %zd bytes in index, %zd bytes in prototypes.", + mob_count, sizeof(IndexData) * mob_count, sizeof(CharData) * mob_count); + + // Build zone vnum to rnum map + std::map zone_vnum_to_rnum; + for (size_t i = 0; i < zone_table.size(); i++) + { + zone_vnum_to_rnum[zone_table[i].vnum] = i; + } + + // Sort mob vnums to match Legacy loader order (CRITICAL for checksums) + std::sort(mob_vnums.begin(), mob_vnums.end()); + + // Pre-allocate vnum to index mapping (sorted by vnum) + std::map vnum_to_idx; + for (size_t i = 0; i < mob_vnums.size(); ++i) + { + vnum_to_idx[mob_vnums[i]] = i; + } + + // Distribute mobs into batches + auto batches = utils::DistributeBatches(mob_vnums, m_num_threads); + std::atomic error_count{0}; + + // Launch parallel loading + std::vector> futures; + for (size_t thread_id = 0; thread_id < batches.size(); ++thread_id) + { + futures.push_back(m_thread_pool->Enqueue([this, thread_id, &batches, &vnum_to_idx, &error_count]() { + for (int vnum : batches[thread_id]) + { + std::string filepath = m_world_dir + "/mobs/" + std::to_string(vnum) + ".yaml"; + try + { + size_t mob_idx = vnum_to_idx.at(vnum); + mob_proto[mob_idx] = ParseMobFile(filepath); + mob_proto[mob_idx].set_rnum(mob_idx); + + // Attach triggers (thread-safe read from trig_index) + YAML::Node root = YAML::LoadFile(filepath); + if (root["triggers"] && root["triggers"].IsSequence()) + { + for (const auto &trig_node : root["triggers"]) + { + int trigger_vnum = trig_node.as(); + AttachTriggerToMob(mob_idx, trigger_vnum, vnum); + } + } + } + catch (const YAML::Exception &e) + { + log("SYSERR: Failed to load mob from '%s': %s", filepath.c_str(), e.what()); + error_count++; + } + catch (const std::exception &e) + { + log("SYSERR: Failed to load mob from '%s': %s", filepath.c_str(), e.what()); + error_count++; + } + } + })); + } + + // Wait for all tasks + for (auto &future : futures) + { + future.wait(); + } + + if (error_count > 0) + { + log("FATAL: %d mob(s) failed to load. Aborting.", error_count.load()); + exit(1); + } + + // Sequential post-processing: setup mob_index and zone locations + top_of_mobt = mob_count; + + // top_of_mobt should be last valid index, not count + if (top_of_mobt > 0) + { + top_of_mobt--; + } + + for (size_t i = 0; i < mob_vnums.size(); ++i) + { + int vnum = mob_vnums[i]; + + mob_index[i].vnum = vnum; + mob_index[i].total_online = 0; + mob_index[i].stored = 0; + mob_index[i].func = nullptr; + mob_index[i].farg = nullptr; + mob_index[i].proto = nullptr; + mob_index[i].set_idx = -1; + + // Update zone RnumMobsLocation + int zone_vnum = vnum / 100; + auto zone_it = zone_vnum_to_rnum.find(zone_vnum); + if (zone_it != zone_vnum_to_rnum.end()) + { + if (zone_table[zone_it->second].RnumMobsLocation.first == -1) + { + zone_table[zone_it->second].RnumMobsLocation.first = i; + } + zone_table[zone_it->second].RnumMobsLocation.second = i; + } + } + + log("Loaded %d mobs from YAML (parallel).", top_of_mobt); +} + +void YamlWorldDataSource::LoadMobs() +{ + log("Loading mobs from YAML files."); + + // Dictionaries already loaded in LoadZones, thread pool already created + LoadMobsParallel(); +} + +// Parse single object file (thread-safe worker function) +CObjectPrototype* YamlWorldDataSource::ParseObjectFile(const std::string &file_path) +{ + YAML::Node root = YAML::LoadFile(file_path); + + // Extract vnum from filename + size_t last_slash = file_path.find_last_of('/'); + size_t last_dot = file_path.find_last_of('.'); + std::string vnum_str = file_path.substr(last_slash + 1, last_dot - last_slash - 1); + int vnum = std::atoi(vnum_str.c_str()); + + // NOTE: This returns raw pointer - caller must wrap in shared_ptr + auto obj_ptr = new CObjectPrototype(vnum); + + // Object created above + + // Names + YAML::Node names = root["names"]; + if (names) + { + obj_ptr->set_aliases(GetText(names, "aliases")); + obj_ptr->set_short_description(utils::colorLOW(GetText(names, "nominative"))); + obj_ptr->set_PName(ECase::kNom, utils::colorLOW(GetText(names, "nominative"))); + obj_ptr->set_PName(ECase::kGen, utils::colorLOW(GetText(names, "genitive"))); + obj_ptr->set_PName(ECase::kDat, utils::colorLOW(GetText(names, "dative"))); + obj_ptr->set_PName(ECase::kAcc, utils::colorLOW(GetText(names, "accusative"))); + obj_ptr->set_PName(ECase::kIns, utils::colorLOW(GetText(names, "instrumental"))); + obj_ptr->set_PName(ECase::kPre, utils::colorLOW(GetText(names, "prepositional"))); + } + + obj_ptr->set_description(utils::colorCAP(GetText(root, "short_desc"))); + obj_ptr->set_action_description(GetText(root, "action_desc")); + + // Type + int obj_type_id = ParseEnum(root["type"], "obj_types", 0); + obj_ptr->set_type(static_cast(obj_type_id)); + + // Material + obj_ptr->set_material(static_cast(GetInt(root, "material", 0))); + + // Values + YAML::Node values = root["values"]; + if (values && values.IsSequence() && values.size() >= 4) + { + // Match Legacy: negative val[0] becomes 0 + long val0 = values[0].as(0); + if (val0 < 0) + { + val0 = 0; + } + obj_ptr->set_val(0, val0); + obj_ptr->set_val(1, values[1].as(0)); + obj_ptr->set_val(2, values[2].as(0)); + obj_ptr->set_val(3, values[3].as(0)); + } + + // Physical properties + obj_ptr->set_weight(GetInt(root, "weight", 0)); + if (obj_ptr->get_type() == EObjType::kLiquidContainer || obj_ptr->get_type() == EObjType::kFountain) + { + if (obj_ptr->get_weight() < obj_ptr->get_val(1)) + { + obj_ptr->set_weight(obj_ptr->get_val(1) + 5); + } + } + obj_ptr->set_cost(GetInt(root, "cost", 0)); + obj_ptr->set_rent_off(GetInt(root, "rent_off", 0)); + obj_ptr->set_rent_on(GetInt(root, "rent_on", 0)); + obj_ptr->set_spec_param(GetInt(root, "spec_param", 0)); + + int max_dur = GetInt(root, "max_durability", 100); + int cur_dur = GetInt(root, "cur_durability", 100); + obj_ptr->set_maximum_durability(max_dur); + obj_ptr->set_current_durability(std::min(max_dur, cur_dur)); + + int timer = GetInt(root, "timer", -1); + if (timer <= 0) { timer = ObjData::SEVEN_DAYS; } + if (timer > 99999) timer = 99999; + obj_ptr->set_timer(timer); + + obj_ptr->set_spell(GetInt(root, "spell", -1)); + obj_ptr->set_level(GetInt(root, "level", 0)); + obj_ptr->set_sex(static_cast(ParseGender(root["sex"]))); + + if (root["max_in_world"]) + { + obj_ptr->set_max_in_world(root["max_in_world"].as()); + } + else + { + obj_ptr->set_max_in_world(-1); + } + + obj_ptr->set_minimum_remorts(GetInt(root, "minimum_remorts", 0)); + + // Flags + auto &dm = DictionaryManager::Instance(); + + if (root["extra_flags"] && root["extra_flags"].IsSequence()) + { + for (const auto &flag_node : root["extra_flags"]) + { + std::string flag_name = flag_node.as(); + long flag_val = dm.Lookup("extra_flags", flag_name, -1); + if (flag_val >= 0) + { + obj_ptr->set_extra_flag(static_cast(IndexToBitvector(flag_val))); + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + obj_ptr->toggle_extra_flag(plane, 1 << bit_in_plane); + } + } + } + + if (root["wear_flags"] && root["wear_flags"].IsSequence()) + { + int wear_flags = 0; + for (const auto &flag_node : root["wear_flags"]) + { + std::string flag_name = flag_node.as(); + long flag_val = dm.Lookup("wear_flags", flag_name, -1); + if (flag_val >= 0) + { + wear_flags |= (1 << flag_val); + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + int bit = std::stoi(flag_name.substr(7)); + if (bit >= 0 && bit < 32) + wear_flags |= (1 << bit); + } + } + obj_ptr->set_wear_flags(wear_flags); + } + + if (root["no_flags"] && root["no_flags"].IsSequence()) + { + for (const auto &flag_node : root["no_flags"]) + { + std::string flag_name = flag_node.as(); + long flag_val = dm.Lookup("no_flags", flag_name, -1); + if (flag_val >= 0) + { + obj_ptr->set_no_flag(static_cast(IndexToBitvector(flag_val))); + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + obj_ptr->toggle_no_flag(plane, 1 << bit_in_plane); + } + } + } + + if (root["anti_flags"] && root["anti_flags"].IsSequence()) + { + for (const auto &flag_node : root["anti_flags"]) + { + std::string flag_name = flag_node.as(); + long flag_val = dm.Lookup("anti_flags", flag_name, -1); + if (flag_val >= 0) + { + obj_ptr->set_anti_flag(static_cast(IndexToBitvector(flag_val))); + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + obj_ptr->toggle_anti_flag(plane, 1 << bit_in_plane); + } + } + } + + if (root["affect_flags"] && root["affect_flags"].IsSequence()) + { + for (const auto &flag_node : root["affect_flags"]) + { + std::string flag_name = flag_node.as(); + long flag_val = dm.Lookup("affect_flags", flag_name, -1); + if (flag_val >= 0) + { + obj_ptr->SetEWeaponAffectFlag(static_cast(IndexToBitvector(flag_val))); + } + else if (flag_name.rfind("UNUSED_", 0) == 0) + { + int bit = std::stoi(flag_name.substr(7)); + size_t plane = bit / 30; + int bit_in_plane = bit % 30; + obj_ptr->toggle_affect_flag(plane, 1 << bit_in_plane); + } + } + } + + // Match Legacy: remove transformed and ticktimer flags after loading + obj_ptr->unset_extraflag(EObjFlag::kTransformed); + obj_ptr->unset_extraflag(EObjFlag::kTicktimer); + + // Match Legacy: override max_in_world for zonedecay/repop_decay objects + if (obj_ptr->has_flag(EObjFlag::kZonedecay) || obj_ptr->has_flag(EObjFlag::kRepopDecay)) + { + obj_ptr->set_max_in_world(-1); + } + + // Applies + int apply_idx = 0; + if (root["applies"] && root["applies"].IsSequence()) + { + for (const auto &apply_node : root["applies"]) + { + if (apply_idx >= kMaxObjAffect) break; + int location = GetInt(apply_node, "location", 0); + int modifier = GetInt(apply_node, "modifier", 0); + obj_ptr->set_affected(apply_idx++, static_cast(location), modifier); + } + } + + // Extra descriptions + if (root["extra_descriptions"] && root["extra_descriptions"].IsSequence()) + { + for (const auto &ed_node : root["extra_descriptions"]) + { + std::string keywords = GetText(ed_node, "keywords"); + std::string description = GetText(ed_node, "description"); + + auto ex_desc = std::make_shared(); + ex_desc->set_keyword(keywords); + ex_desc->set_description(description); + ex_desc->next = obj_ptr->get_ex_description(); + obj_ptr->set_ex_description(ex_desc); + } + } + + // Add to proto first to get rnum + + return obj_ptr; +} + + +// Parallel object loading +void YamlWorldDataSource::LoadObjectsParallel() +{ + std::vector obj_vnums = GetObjectList(); + if (obj_vnums.empty()) + { + log("No objects found in YAML index."); + return; + } + + int obj_count = obj_vnums.size(); + log(" %d objs.", obj_count); + + // Distribute objects into batches + auto batches = utils::DistributeBatches(obj_vnums, m_num_threads); + + // Thread-local results (each thread collects its objects and triggers) + std::vector>> thread_results(batches.size()); + std::vector>> thread_triggers(batches.size()); + std::atomic error_count{0}; + + // Launch parallel loading + std::vector> futures; + for (size_t thread_id = 0; thread_id < batches.size(); ++thread_id) + { + futures.push_back(m_thread_pool->Enqueue([this, thread_id, &batches, &thread_results, &thread_triggers, &error_count]() { + for (int vnum : batches[thread_id]) + { + std::string filepath = m_world_dir + "/objects/" + std::to_string(vnum) + ".yaml"; + try + { + CObjectPrototype* obj = ParseObjectFile(filepath); + + // Load object triggers (if present) + YAML::Node root = YAML::LoadFile(filepath); + if (root["triggers"] && root["triggers"].IsSequence()) + { + std::vector trigger_list; + for (const auto &trig_node : root["triggers"]) + { + int trigger_vnum = trig_node.as(); + trigger_list.push_back(trigger_vnum); + } + if (!trigger_list.empty()) + { + thread_triggers[thread_id][vnum] = std::move(trigger_list); + } + } + + thread_results[thread_id].emplace_back(vnum, obj); + } + catch (const YAML::Exception &e) + { + log("SYSERR: Failed to load object from '%s': %s", filepath.c_str(), e.what()); + error_count++; + } + catch (const std::exception &e) + { + log("SYSERR: Failed to load object from '%s': %s", filepath.c_str(), e.what()); + error_count++; + } + } + })); + } + + // Wait for all tasks + for (auto &future : futures) + { + future.wait(); + } + + if (error_count > 0) + { + log("FATAL: %d object(s) failed to load. Aborting.", error_count.load()); + exit(1); + } + + // Merge results into obj_proto (sequential, sorted by vnum) + // Collect all objects into single vector and sort by vnum + std::vector> all_objects; + for (auto &results : thread_results) + { + for (auto &obj_pair : results) + { + all_objects.push_back(std::move(obj_pair)); + } + } + + // Sort by vnum to match Legacy loader order + std::sort(all_objects.begin(), all_objects.end(), + [](const auto &a, const auto &b) { return a.first < b.first; }); + + // Add to obj_proto in sorted order + int loaded_count = 0; + for (auto &[vnum, obj_raw_ptr] : all_objects) + { + // Wrap in shared_ptr and add to obj_proto + auto obj = std::shared_ptr(obj_raw_ptr); + obj_proto.add(obj, vnum); + loaded_count++; + } + + // Merge thread-local trigger maps into single map + std::map> object_triggers; + for (auto &triggers_map : thread_triggers) + { + object_triggers.insert(triggers_map.begin(), triggers_map.end()); + } + + // Attach triggers (sequential, after all objects added to obj_proto) + for (const auto &[obj_vnum, trigger_list] : object_triggers) + { + int rnum = obj_proto.get_rnum(obj_vnum); + if (rnum >= 0) + { + log("DEBUG: Object %d has %zu triggers", obj_vnum, trigger_list.size()); + for (int trigger_vnum : trigger_list) + { + AttachTriggerToObject(rnum, trigger_vnum, obj_vnum); + } + } + } + + log("Loaded %d objects from YAML (parallel).", loaded_count); +} + + +void YamlWorldDataSource::LoadObjects() +{ + log("Loading objects from YAML files."); + + // Dictionaries already loaded in LoadZones, thread pool already created + LoadObjectsParallel(); +} + +// Helper methods for save operations + +std::string YamlWorldDataSource::ConvertToUtf8(const std::string &koi8r_str) const +{ + if (koi8r_str.empty()) + { + return ""; + } + + static char buffer[65536]; + char *input = const_cast(koi8r_str.c_str()); + char *output = buffer; + koi_to_utf8(input, output); + return buffer; +} + +std::vector YamlWorldDataSource::ConvertFlagsToNames(const FlagData &flags, const std::string &dict_name) const +{ + std::vector names; + auto &dm = DictionaryManager::Instance(); + const Dictionary *dict = dm.GetDictionary(dict_name); + + if (!dict) + { + return names; + } + + const auto &entries = dict->GetEntries(); + + for (size_t plane = 0; plane < FlagData::kPlanesNumber; ++plane) + { + Bitvector plane_bits = flags.get_plane(plane); + if (plane_bits == 0) continue; + + for (int bit = 0; bit < 30; ++bit) + { + if (plane_bits & (1 << bit)) + { + int bit_index = plane * 30 + bit; + + std::string flag_name; + for (const auto &[name, value] : entries) + { + if (value == bit_index) + { + flag_name = name; + break; + } + } + + if (!flag_name.empty()) + { + names.push_back(flag_name); + } + else + { + names.push_back("UNUSED_" + std::to_string(bit_index)); + } + } + } + } + + return names; +} + +std::string YamlWorldDataSource::ReverseLookupEnum(const std::string &dict_name, int value) const +{ + auto &dm = DictionaryManager::Instance(); + const Dictionary *dict = dm.GetDictionary(dict_name); + + if (!dict) + { + return std::to_string(value); + } + + const auto &entries = dict->GetEntries(); + for (const auto &[name, dict_value] : entries) + { + if (dict_value == value) + { + return name; + } + } + + return std::to_string(value); +} + +bool YamlWorldDataSource::WriteYamlAtomic(const std::string &filepath, const YAML::Node &node) const +{ + std::string temp_filepath = filepath + ".tmp"; + + try + { + YAML::Emitter emitter; + emitter << node; + + std::ofstream out(temp_filepath); + if (!out.is_open()) + { + log("SYSERR: Failed to open temp file for writing: %s", temp_filepath.c_str()); + return false; + } + out << emitter.c_str(); + out.close(); + + std::rename(temp_filepath.c_str(), filepath.c_str()); + return true; + } + catch (const std::exception &e) + { + log("SYSERR: Failed to write YAML file %s: %s", filepath.c_str(), e.what()); + + return false; + } +} +// ============================================================================ + +void YamlWorldDataSource::SaveZone(int zone_rnum) +{ + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveZone", zone_rnum); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + std::string zone_dir = m_world_dir + "/zones/" + std::to_string(zone.vnum); + std::string zone_file = zone_dir + "/zone.yaml"; + + namespace fs = std::filesystem; + if (!fs::exists(zone_dir)) + { + fs::create_directories(zone_dir); + } + + YAML::Node root; + root["vnum"] = zone.vnum; + root["name"] = zone.name; + root["zone_group"] = zone.group; + + YAML::Node metadata; + if (!zone.comment.empty()) metadata["comment"] = zone.comment; + if (!zone.location.empty()) metadata["location"] = zone.location; + if (!zone.author.empty()) metadata["author"] = zone.author; + if (!zone.description.empty()) metadata["description"] = zone.description; + if (metadata.size() > 0) root["metadata"] = metadata; + + root["top_room"] = zone.top; + root["lifespan"] = zone.lifespan; + root["reset_mode"] = zone.reset_mode; + root["reset_idle"] = zone.reset_idle ? 1 : 0; + root["zone_type"] = zone.type; + root["mode"] = zone.level; + if (zone.entrance != 0) root["entrance"] = zone.entrance; + root["under_construction"] = zone.under_construction; + + if (zone.typeA_count > 0) + { + YAML::Node typeA_zones; + for (int i = 0; i < zone.typeA_count; ++i) + { + typeA_zones.push_back(zone.typeA_list[i]); + } + root["typeA_zones"] = typeA_zones; + } + + if (zone.typeB_count > 0) + { + YAML::Node typeB_zones; + for (int i = 0; i < zone.typeB_count; ++i) + { + typeB_zones.push_back(zone.typeB_list[i]); + } + root["typeB_zones"] = typeB_zones; + } + + if (zone.cmd && zone.cmd[0].command != 'S') + { + YAML::Node commands; + for (int i = 0; zone.cmd[i].command != 'S'; ++i) + { + const struct reset_com &cmd = zone.cmd[i]; + YAML::Node cmd_node; + + cmd_node["if_flag"] = cmd.if_flag; + + switch (cmd.command) + { + case 'M': + cmd_node["type"] = "LOAD_MOB"; + cmd_node["mob_vnum"] = cmd.arg1; + cmd_node["max_world"] = cmd.arg2; + cmd_node["room_vnum"] = cmd.arg3; + if (cmd.arg4 != -1) cmd_node["max_room"] = cmd.arg4; + break; + case 'O': + cmd_node["type"] = "LOAD_OBJ"; + cmd_node["obj_vnum"] = cmd.arg1; + cmd_node["max"] = cmd.arg2; + cmd_node["room_vnum"] = cmd.arg3; + if (cmd.arg4 != -1) cmd_node["load_prob"] = cmd.arg4; + break; + case 'G': + cmd_node["type"] = "GIVE_OBJ"; + cmd_node["obj_vnum"] = cmd.arg1; + cmd_node["max"] = cmd.arg2; + if (cmd.arg4 != -1) cmd_node["load_prob"] = cmd.arg4; + break; + case 'E': + cmd_node["type"] = "EQUIP_MOB"; + cmd_node["obj_vnum"] = cmd.arg1; + cmd_node["max"] = cmd.arg2; + cmd_node["wear_pos"] = cmd.arg3; + if (cmd.arg4 != -1) cmd_node["load_prob"] = cmd.arg4; + break; + case 'P': + cmd_node["type"] = "PUT_OBJ"; + cmd_node["obj_vnum"] = cmd.arg1; + cmd_node["max"] = cmd.arg2; + cmd_node["container_vnum"] = cmd.arg3; + if (cmd.arg4 != -1) cmd_node["load_prob"] = cmd.arg4; + break; + case 'D': + cmd_node["type"] = "DOOR"; + cmd_node["room_vnum"] = cmd.arg1; + cmd_node["direction"] = cmd.arg2; + cmd_node["state"] = cmd.arg3; + break; + case 'R': + cmd_node["type"] = "REMOVE_OBJ"; + cmd_node["room_vnum"] = cmd.arg1; + cmd_node["obj_vnum"] = cmd.arg2; + break; + case 'T': + cmd_node["type"] = "TRIGGER"; + cmd_node["trigger_type"] = cmd.arg1; + cmd_node["trigger_vnum"] = cmd.arg2; + if (cmd.arg3 != -1) cmd_node["room_vnum"] = cmd.arg3; + break; + case 'V': + cmd_node["type"] = "VARIABLE"; + cmd_node["trigger_type"] = cmd.arg1; + cmd_node["context"] = cmd.arg2; + cmd_node["room_vnum"] = cmd.arg3; + if (cmd.sarg1) cmd_node["var_name"] = cmd.sarg1; + if (cmd.sarg2) cmd_node["var_value"] = cmd.sarg2; + break; + case 'Q': + cmd_node["type"] = "EXTRACT_MOB"; + cmd_node["mob_vnum"] = cmd.arg1; + cmd_node["if_flag"] = 0; + break; + case 'F': + cmd_node["type"] = "FOLLOW"; + cmd_node["room_vnum"] = cmd.arg1; + cmd_node["leader_mob_vnum"] = cmd.arg2; + cmd_node["follower_mob_vnum"] = cmd.arg3; + break; + default: + continue; + } + + commands.push_back(cmd_node); + } + root["commands"] = commands; + } + + if (!WriteYamlAtomic(zone_file, root)) + { + log("SYSERR: Failed to save zone %d", zone.vnum); + return; + } + + log("Saved zone %d to YAML file", zone.vnum); +} + +bool YamlWorldDataSource::SaveTriggers(int zone_rnum, int specific_vnum, int notify_level) +{ + (void)notify_level; // YAML saves don't use mudlog - errors go to log file + log("SaveTriggers called: zone_rnum=%d, specific_vnum=%d", zone_rnum, specific_vnum); + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveTriggers", zone_rnum); + return false; + } + + const ZoneData &zone = zone_table[zone_rnum]; + TrgRnum first_trig = zone.RnumTrigsLocation.first; + TrgRnum last_trig = zone.RnumTrigsLocation.second; + + log("SaveTriggers: Zone %d, first_trig=%d, last_trig=%d", zone.vnum, first_trig, last_trig); + if (first_trig == -1 || last_trig == -1) + { + log("Zone %d has no triggers to save", zone.vnum); + return true; // Not an error - zone just has no triggers + } + + std::string trig_dir = m_world_dir + "/triggers"; + namespace fs = std::filesystem; + if (!fs::exists(trig_dir)) + { + fs::create_directories(trig_dir); + } + + int saved_count = 0; + log("SaveTriggers: Iterating triggers from %d to %d, specific_vnum=%d", first_trig, last_trig, specific_vnum); + for (TrgRnum trig_rnum = first_trig; trig_rnum <= last_trig && trig_rnum <= top_of_trigt; ++trig_rnum) + { + if (!trig_index[trig_rnum]) + { + continue; + } + + int trig_vnum = trig_index[trig_rnum]->vnum; + Trigger *trig = trig_index[trig_rnum]->proto; + + if (!trig) + { + continue; + } + + // If specific_vnum is set, save only that trigger + if (specific_vnum != -1 && trig_vnum != specific_vnum) + { + log("SaveTriggers: Skipping trigger %d (looking for %d)", trig_vnum, specific_vnum); + continue; + } + + log("SaveTriggers: Saving trigger #%d", trig_vnum); + // Open temp file for writing + std::string trig_file = trig_dir + "/" + std::to_string(trig_vnum) + ".yaml"; + std::string temp_file = trig_file + ".tmp"; + log("SaveTriggers: Writing to %s", temp_file.c_str()); + std::ofstream out(temp_file); + if (!out.is_open()) + { + log("SYSERR: Failed to open %s for writing", temp_file.c_str()); + continue; + } + + Koi8rYamlEmitter yaml(out); + + // Header comment + yaml.Comment("Trigger #" + std::to_string(trig_vnum)); + yaml.EmptyLine(); + + // Name + yaml.Key("name"); + yaml.Value(GET_TRIG_NAME(trig)); + + // Attach type + yaml.Key("attach_type"); + yaml.Value(ReverseLookupEnum("attach_types", trig->get_attach_type())); + + // Narg + yaml.Key("narg"); + yaml.Value(GET_TRIG_NARG(trig)); + + // Arglist (optional) + if (!trig->arglist.empty()) + { + yaml.Key("arglist"); + yaml.Value(trig->arglist); + } + + // Trigger types + bool has_trigger_types = false; + for (int bit = 0; bit < 32; ++bit) + { + if (GET_TRIG_TYPE(trig) & (1L << bit)) + { + has_trigger_types = true; + break; + } + } + + if (has_trigger_types) + { + yaml.Key("trigger_types"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (int bit = 0; bit < 32; ++bit) + { + if (GET_TRIG_TYPE(trig) & (1L << bit)) + { + std::string type_name = ReverseLookupEnum("trigger_types", bit); + if (!type_name.empty() && type_name != std::to_string(bit)) + { + yaml.SequenceItem(type_name); + } + } + } + + yaml.DecreaseIndent(); + } + + // Script (multiline literal block) + std::string script; + for (auto cmd = *trig->cmdlist; cmd; cmd = cmd->next) + { + if (!cmd->cmd.empty()) + { + script += cmd->cmd; + if (cmd->next) + { + script += "\n"; + } + } + } + + if (!script.empty()) + { + yaml.Key("script"); + yaml.Value(script, true); // literal=true + } + + // Close file and rename atomically + out.close(); + if (std::rename(temp_file.c_str(), trig_file.c_str()) != 0) + { + log("SYSERR: Failed to rename %s to %s", temp_file.c_str(), trig_file.c_str()); + continue; + } + + ++saved_count; + } + + log("Saved %d triggers for zone %d", saved_count, zone.vnum); + return true; +} + +void YamlWorldDataSource::SaveRooms(int zone_rnum, int specific_vnum) +{ + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveRooms", zone_rnum); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + RoomRnum first_room = zone.RnumRoomsLocation.first; + RoomRnum last_room = zone.RnumRoomsLocation.second; + + if (first_room == -1 || last_room == -1) + { + log("Zone %d has no rooms to save", zone.vnum); + return; + } + + std::string rooms_dir = m_world_dir + "/zones/" + std::to_string(zone.vnum) + "/rooms"; + namespace fs = std::filesystem; + if (!fs::exists(rooms_dir)) + { + fs::create_directories(rooms_dir); + } + + int saved_count = 0; + for (RoomRnum room_rnum = first_room; room_rnum <= last_room && room_rnum <= top_of_world; ++room_rnum) + { + RoomData *room = world[room_rnum]; + if (!room || room->vnum < zone.vnum * 100 || room->vnum > zone.top) + { + continue; + } + + // If specific_vnum is set, save only that room + if (specific_vnum != -1 && room->vnum != specific_vnum) + { + continue; + } + + int rel_num = room->vnum % 100; + std::string room_file = rooms_dir + "/" + fmt::format("{:02d}", rel_num) + ".yaml"; + std::string temp_file = room_file + ".tmp"; + std::ofstream out(temp_file); + if (!out.is_open()) + { + log("SYSERR: Failed to open %s for writing", temp_file.c_str()); + continue; + } + + Koi8rYamlEmitter yaml(out); + + // Header comment + yaml.Comment("Room #" + std::to_string(room->vnum)); + yaml.EmptyLine(); + + // Vnum + yaml.Key("vnum"); + yaml.Value(room->vnum); + + // Name + if (room->name) + { + yaml.Key("name"); + yaml.Value(room->name); + } + + // Description + std::string desc = GlobalObjects::descriptions().get(room->description_num); + if (!desc.empty()) + { + yaml.Key("description"); + yaml.Value(desc, true); // literal=true + } + + // Sector + yaml.Key("sector"); + yaml.Value(ReverseLookupEnum("sectors", static_cast(room->sector_type))); + + // Flags + FlagData room_flags = room->read_flags(); + auto flag_names = ConvertFlagsToNames(room_flags, "room_flags"); + if (!flag_names.empty()) + { + yaml.Key("flags"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &flag : flag_names) + { + yaml.SequenceItem(flag); + } + + yaml.DecreaseIndent(); + } + + // Exits (with to_room comments) + bool has_exits = false; + for (int dir = 0; dir < EDirection::kMaxDirNum; ++dir) + { + if (room->dir_option_proto[dir]) + { + has_exits = true; + break; + } + } + + if (has_exits) + { + yaml.Key("exits"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (int dir = 0; dir < EDirection::kMaxDirNum; ++dir) + { + if (!room->dir_option_proto[dir]) + { + continue; + } + + out << yaml.GetIndent() << "- direction: "; + out << ReverseLookupEnum("directions", dir) << std::endl; + + // to_room (with comment) + RoomRnum to_rnum = kNowhere; + int to_vnum = -1; + if (room->dir_option_proto[dir]->to_room() != kNowhere) + { + to_rnum = room->dir_option_proto[dir]->to_room(); + if (to_rnum >= 0 && to_rnum <= top_of_world && world[to_rnum]) + { + to_vnum = world[to_rnum]->vnum; + } + } + + out << yaml.GetIndent() << " to_room: " << to_vnum; + if (to_vnum != -1 && to_rnum != kNowhere) + { + std::string room_name = GetRoomNameComment(to_rnum); + if (!room_name.empty()) + { + out << " # " << room_name; + } + } + out << std::endl; + + // Description (optional) + if (!room->dir_option_proto[dir]->general_description.empty()) + { + std::string exit_desc = room->dir_option_proto[dir]->general_description; + out << yaml.GetIndent() << " description: |" << std::endl; + + exit_desc.erase(std::remove(exit_desc.begin(), exit_desc.end(), '\r'), exit_desc.end()); + std::istringstream iss(exit_desc); + std::string line; + while (std::getline(iss, line)) + { + out << yaml.GetIndent() << " " << line << std::endl; + } + } + + // Keywords (optional) + if (room->dir_option_proto[dir]->keyword) + { + out << yaml.GetIndent() << " keywords: "; + out << room->dir_option_proto[dir]->keyword << std::endl; + } + + // Exit flags (optional) + if (room->dir_option_proto[dir]->exit_info != 0) + { + out << yaml.GetIndent() << " exit_flags: "; + out << static_cast(room->dir_option_proto[dir]->exit_info) << std::endl; + } + + // Key (optional) + if (room->dir_option_proto[dir]->key != -1) + { + out << yaml.GetIndent() << " key: "; + out << room->dir_option_proto[dir]->key << std::endl; + } + + // Lock complexity (optional) + if (room->dir_option_proto[dir]->lock_complexity != 0) + { + out << yaml.GetIndent() << " lock_complexity: "; + out << static_cast(room->dir_option_proto[dir]->lock_complexity) << std::endl; + } + } + + yaml.DecreaseIndent(); + } + + // Extra descriptions + if (room->ex_description) + { + yaml.Key("extra_descriptions"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (auto exdesc = room->ex_description; exdesc; exdesc = exdesc->next) + { + if (exdesc->keyword) + { + out << yaml.GetIndent() << "- keywords: " << exdesc->keyword << std::endl; + if (exdesc->description) + { + out << yaml.GetIndent() << " description: |" << std::endl; + + std::string desc_text = exdesc->description; + desc_text.erase(std::remove(desc_text.begin(), desc_text.end(), '\r'), desc_text.end()); + + std::istringstream iss(desc_text); + std::string line; + while (std::getline(iss, line)) + { + out << yaml.GetIndent() << " " << line << std::endl; + } + } + } + } + + yaml.DecreaseIndent(); + } + + // Triggers (with comments) + if (room->proto_script && !room->proto_script->empty()) + { + yaml.Key("triggers"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (auto trig_vnum : *room->proto_script) + { + std::string trig_comment = GetTriggerNameComment(trig_vnum); + yaml.SequenceItem(trig_vnum, trig_comment); + } + + yaml.DecreaseIndent(); + } + + // Close file and rename atomically + out.close(); + if (std::rename(temp_file.c_str(), room_file.c_str()) != 0) + { + log("SYSERR: Failed to rename %s to %s", temp_file.c_str(), room_file.c_str()); + continue; + } + + ++saved_count; + } + + log("Saved %d rooms for zone %d", saved_count, zone.vnum); +} + +void YamlWorldDataSource::SaveMobs(int zone_rnum, int specific_vnum) +{ + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveMobs", zone_rnum); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + MobRnum first_mob = zone.RnumMobsLocation.first; + MobRnum last_mob = zone.RnumMobsLocation.second; + + if (first_mob == -1 || last_mob == -1) + { + log("Zone %d has no mobs to save", zone.vnum); + return; + } + + std::string mobs_dir = m_world_dir + "/mobs"; + namespace fs = std::filesystem; + if (!fs::exists(mobs_dir)) + { + fs::create_directories(mobs_dir); + } + + int saved_count = 0; + for (MobRnum mob_rnum = first_mob; mob_rnum <= last_mob && mob_rnum <= top_of_mobt; ++mob_rnum) + { + if (!mob_index[mob_rnum].vnum) + { + continue; + } + + int mob_vnum = mob_index[mob_rnum].vnum; + CharData &mob = mob_proto[mob_rnum]; + + // If specific_vnum is set, save only that mob + if (specific_vnum != -1 && mob_vnum != specific_vnum) + { + continue; + } + + // Open temp file for writing + std::string mob_file = mobs_dir + "/" + std::to_string(mob_vnum) + ".yaml"; + std::string temp_file = mob_file + ".tmp"; + std::ofstream out(temp_file); + if (!out.is_open()) + { + log("SYSERR: Failed to open %s for writing", temp_file.c_str()); + continue; + } + + Koi8rYamlEmitter yaml(out); + + // Header comment + yaml.Comment("Mob #" + std::to_string(mob_vnum)); + yaml.EmptyLine(); + + // Names + yaml.Key("names"); + out << std::endl; + yaml.IncreaseIndent(); + + const std::string &aliases = mob.get_npc_name(); + if (!aliases.empty()) + { + yaml.Key("aliases"); + yaml.Value(aliases); + } + yaml.Key("nominative"); + yaml.Value(mob.player_data.PNames[ECase::kNom]); + yaml.Key("genitive"); + yaml.Value(mob.player_data.PNames[ECase::kGen]); + yaml.Key("dative"); + yaml.Value(mob.player_data.PNames[ECase::kDat]); + yaml.Key("accusative"); + yaml.Value(mob.player_data.PNames[ECase::kAcc]); + yaml.Key("instrumental"); + yaml.Value(mob.player_data.PNames[ECase::kIns]); + yaml.Key("prepositional"); + yaml.Value(mob.player_data.PNames[ECase::kPre]); + + yaml.DecreaseIndent(); + + // Descriptions + yaml.Key("descriptions"); + out << std::endl; + yaml.IncreaseIndent(); + + yaml.Key("short_desc"); + yaml.Value(mob.player_data.long_descr, true); // literal=true + + yaml.Key("long_desc"); + yaml.Value(mob.player_data.description, true); // literal=true + + yaml.DecreaseIndent(); + + // Alignment + yaml.Key("alignment"); + yaml.Value(GET_ALIGNMENT(&mob)); + + // Stats + yaml.Key("stats"); + out << std::endl; + yaml.IncreaseIndent(); + + yaml.Key("level"); + yaml.Value(mob.GetLevel()); + + yaml.Key("hitroll_penalty"); + yaml.Value(GET_HR(&mob)); + + yaml.Key("armor"); + yaml.Value(GET_AC(&mob)); + + // HP + yaml.Key("hp"); + out << std::endl; + yaml.IncreaseIndent(); + + yaml.Key("dice_count"); + yaml.Value(static_cast(mob.mem_queue.total)); // byte Б├▓ int + + yaml.Key("dice_size"); + yaml.Value(static_cast(mob.mem_queue.stored)); // byte Б├▓ int + + yaml.Key("bonus"); + yaml.Value(mob.get_hit()); + + yaml.DecreaseIndent(); + + // Damage + yaml.Key("damage"); + out << std::endl; + yaml.IncreaseIndent(); + + yaml.Key("dice_count"); + yaml.Value(static_cast(mob.mob_specials.damnodice)); // byte Б├▓ int + + yaml.Key("dice_size"); + yaml.Value(static_cast(mob.mob_specials.damsizedice)); // byte Б├▓ int + + yaml.Key("bonus"); + yaml.Value(mob.real_abils.damroll); + + yaml.DecreaseIndent(); + yaml.DecreaseIndent(); // stats + + // Gold + yaml.Key("gold"); + out << std::endl; + yaml.IncreaseIndent(); + + yaml.Key("dice_count"); + yaml.Value(static_cast(mob.mob_specials.GoldNoDs)); // byte Б├▓ int + + yaml.Key("dice_size"); + yaml.Value(static_cast(mob.mob_specials.GoldSiDs)); // byte Б├▓ int + + yaml.Key("bonus"); + yaml.Value(mob.get_gold()); + + yaml.DecreaseIndent(); + + // Experience + yaml.Key("experience"); + yaml.Value(mob.get_exp()); + + // Position + yaml.Key("position"); + out << std::endl; + yaml.IncreaseIndent(); + + yaml.Key("default"); + yaml.Value(ReverseLookupEnum("positions", static_cast(mob.mob_specials.default_pos))); + + yaml.Key("start"); + yaml.Value(ReverseLookupEnum("positions", static_cast(mob.GetPosition()))); + + yaml.DecreaseIndent(); + + // Sex + yaml.Key("sex"); + yaml.Value(ReverseLookupEnum("genders", static_cast(mob.get_sex()))); + + // Size, height, weight + yaml.Key("size"); + yaml.Value(GET_SIZE(&mob)); + + yaml.Key("height"); + yaml.Value(static_cast(GET_HEIGHT(&mob))); // ubyte Б├▓ int + + yaml.Key("weight"); + yaml.Value(static_cast(GET_WEIGHT(&mob))); // ubyte Б├▓ int + + // Attributes (only if set) + if (mob.get_str() > 0) + { + yaml.Key("attributes"); + out << std::endl; + yaml.IncreaseIndent(); + + yaml.Key("strength"); + yaml.Value(mob.get_str()); + + yaml.Key("dexterity"); + yaml.Value(mob.get_dex()); + + yaml.Key("intelligence"); + yaml.Value(mob.get_int()); + + yaml.Key("wisdom"); + yaml.Value(mob.get_wis()); + + yaml.Key("constitution"); + yaml.Value(mob.get_con()); + + yaml.Key("charisma"); + yaml.Value(mob.get_cha()); + + yaml.DecreaseIndent(); + } + + // Action flags + auto act_flags = ConvertFlagsToNames(mob.char_specials.saved.act, "action_flags"); + if (!act_flags.empty()) + { + yaml.Key("action_flags"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &flag : act_flags) + { + yaml.SequenceItem(flag); + } + + yaml.DecreaseIndent(); + } + + // Affect flags + auto aff_flags = ConvertFlagsToNames(AFF_FLAGS(&mob), "affect_flags"); + if (!aff_flags.empty()) + { + yaml.Key("affect_flags"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &flag : aff_flags) + { + yaml.SequenceItem(flag); + } + + yaml.DecreaseIndent(); + } + + // Skills (with comments) + bool has_skills = false; + for (ESkill skill_id = ESkill::kFirst; skill_id <= ESkill::kLast; ++skill_id) + { + if (mob.GetSkill(skill_id) > 0) + { + has_skills = true; + break; + } + } + + if (has_skills) + { + yaml.Key("skills"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (ESkill skill_id = ESkill::kFirst; skill_id <= ESkill::kLast; ++skill_id) + { + int skill_value = mob.GetSkill(skill_id); + if (skill_value > 0) + { + out << yaml.GetIndent() << "- skill_id: " << static_cast(skill_id); + out << " # " << GetSkillNameComment(skill_id) << std::endl; + out << yaml.GetIndent() << " value: " << skill_value << std::endl; + } + } + + yaml.DecreaseIndent(); + } + + // Triggers (with comments) + if (mob.proto_script && !mob.proto_script->empty()) + { + yaml.Key("triggers"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (auto trig_vnum : *mob.proto_script) + { + std::string trig_comment = GetTriggerNameComment(trig_vnum); + yaml.SequenceItem(trig_vnum, trig_comment); + } + + yaml.DecreaseIndent(); + } + + // Enhanced (optional mob stats) + if (mob.get_str() > 0) + { + bool has_enhanced = false; + + // Check if we have any enhanced stats + if (mob.get_str_add() != 0 || mob.add_abils.hitreg != 0 || mob.add_abils.armour != 0 || + mob.add_abils.manareg != 0 || mob.add_abils.cast_success != 0 || mob.add_abils.morale != 0 || + mob.add_abils.initiative_add != 0 || mob.add_abils.absorb != 0 || mob.add_abils.aresist != 0 || + mob.add_abils.mresist != 0 || mob.add_abils.presist != 0 || mob.mob_specials.attack_type != 0 || + mob.mob_specials.like_work != 0 || mob.mob_specials.MaxFactor != 0 || + mob.mob_specials.extra_attack != 0 || mob.get_remort() != 0) + { + has_enhanced = true; + } + + // Check special_bitvector + char special_buf[kMaxStringLength]; + mob.mob_specials.npc_flags.tascii(FlagData::kPlanesNumber, special_buf); + if (special_buf[0] != '0' || special_buf[1] != 'a') + { + has_enhanced = true; + } + + // Check role + std::string role_str = mob.get_role().to_string(); + if (!role_str.empty() && role_str != "000000000") + { + has_enhanced = true; + } + + // Check resistances, saves, feats, spells, helpers, destinations + for (const auto &val : mob.add_abils.apply_resistance) { if (val != 0) { has_enhanced = true; break; } } + for (const auto &val : mob.add_abils.apply_saving_throw) { if (val != 0) { has_enhanced = true; break; } } + + for (size_t i = 0; i < mob.real_abils.Feats.size(); ++i) { if (mob.real_abils.Feats.test(i)) { has_enhanced = true; break; } } + for (size_t i = 0; i < mob.real_abils.SplKnw.size(); ++i) { if (mob.real_abils.SplKnw[i] > 0) { has_enhanced = true; break; } } + + if (!mob.summon_helpers.empty()) { has_enhanced = true; } + for (int dest : mob.mob_specials.dest) { if (dest != 0) { has_enhanced = true; break; } } + + if (has_enhanced) + { + yaml.Key("enhanced"); + out << std::endl; + yaml.IncreaseIndent(); + + if (mob.get_str_add() != 0) + { + yaml.Key("str_add"); + yaml.Value(mob.get_str_add()); + } + if (mob.add_abils.hitreg != 0) + { + yaml.Key("hp_regen"); + yaml.Value(mob.add_abils.hitreg); + } + if (mob.add_abils.armour != 0) + { + yaml.Key("armour_bonus"); + yaml.Value(mob.add_abils.armour); + } + if (mob.add_abils.manareg != 0) + { + yaml.Key("mana_regen"); + yaml.Value(mob.add_abils.manareg); + } + if (mob.add_abils.cast_success != 0) + { + yaml.Key("cast_success"); + yaml.Value(mob.add_abils.cast_success); + } + if (mob.add_abils.morale != 0) + { + yaml.Key("morale"); + yaml.Value(mob.add_abils.morale); + } + if (mob.add_abils.initiative_add != 0) + { + yaml.Key("initiative_add"); + yaml.Value(mob.add_abils.initiative_add); + } + if (mob.add_abils.absorb != 0) + { + yaml.Key("absorb"); + yaml.Value(mob.add_abils.absorb); + } + if (mob.add_abils.aresist != 0) + { + yaml.Key("aresist"); + yaml.Value(mob.add_abils.aresist); + } + if (mob.add_abils.mresist != 0) + { + yaml.Key("mresist"); + yaml.Value(mob.add_abils.mresist); + } + if (mob.add_abils.presist != 0) + { + yaml.Key("presist"); + yaml.Value(mob.add_abils.presist); + } + if (mob.mob_specials.attack_type != 0) + { + yaml.Key("bare_hand_attack"); + yaml.Value(mob.mob_specials.attack_type); + } + if (mob.mob_specials.like_work != 0) + { + yaml.Key("like_work"); + yaml.Value(mob.mob_specials.like_work); + } + if (mob.mob_specials.MaxFactor != 0) + { + yaml.Key("max_factor"); + yaml.Value(mob.mob_specials.MaxFactor); + } + if (mob.mob_specials.extra_attack != 0) + { + yaml.Key("extra_attack"); + yaml.Value(mob.mob_specials.extra_attack); + } + if (mob.get_remort() != 0) + { + yaml.Key("mob_remort"); + yaml.Value(mob.get_remort()); + } + + if (special_buf[0] != '0' || special_buf[1] != 'a') + { + yaml.Key("special_bitvector"); + yaml.Value(special_buf); + } + + if (!role_str.empty() && role_str != "000000000") + { + yaml.Key("role"); + yaml.Value(role_str); + } + + // Resistances + bool has_resistances = false; + for (const auto &val : mob.add_abils.apply_resistance) + { + if (val != 0) { has_resistances = true; break; } + } + if (has_resistances) + { + yaml.Key("resistances"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &val : mob.add_abils.apply_resistance) + { + yaml.SequenceItem(val); + } + + yaml.DecreaseIndent(); + } + + // Saves + bool has_saves = false; + for (const auto &val : mob.add_abils.apply_saving_throw) + { + if (val != 0) { has_saves = true; break; } + } + if (has_saves) + { + yaml.Key("saves"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &val : mob.add_abils.apply_saving_throw) + { + yaml.SequenceItem(val); + } + + yaml.DecreaseIndent(); + } + + // Feats + bool has_feats = false; + for (size_t i = 0; i < mob.real_abils.Feats.size(); ++i) + { + if (mob.real_abils.Feats.test(i)) { has_feats = true; break; } + } + if (has_feats) + { + yaml.Key("feats"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (size_t i = 0; i < mob.real_abils.Feats.size(); ++i) + { + if (mob.real_abils.Feats.test(i)) + { + yaml.SequenceItem(static_cast(i)); + } + } + + yaml.DecreaseIndent(); + } + + // Spells + bool has_spells = false; + for (size_t i = 0; i < mob.real_abils.SplKnw.size(); ++i) + { + if (mob.real_abils.SplKnw[i] > 0) { has_spells = true; break; } + } + if (has_spells) + { + yaml.Key("spells"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (size_t i = 0; i < mob.real_abils.SplKnw.size(); ++i) + { + if (mob.real_abils.SplKnw[i] > 0) + { + yaml.SequenceItem(static_cast(i)); + } + } + + yaml.DecreaseIndent(); + } + + // Helpers + if (!mob.summon_helpers.empty()) + { + yaml.Key("helpers"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (int helper_vnum : mob.summon_helpers) + { + yaml.SequenceItem(helper_vnum); + } + + yaml.DecreaseIndent(); + } + + // Destinations + bool has_destinations = false; + for (int dest : mob.mob_specials.dest) + { + if (dest != 0) { has_destinations = true; break; } + } + if (has_destinations) + { + yaml.Key("destinations"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (int dest : mob.mob_specials.dest) + { + yaml.SequenceItem(dest); + } + + yaml.DecreaseIndent(); + } + + yaml.DecreaseIndent(); // enhanced + } + } + + // Close file and rename atomically + out.close(); + if (std::rename(temp_file.c_str(), mob_file.c_str()) != 0) + { + log("SYSERR: Failed to rename %s to %s", temp_file.c_str(), mob_file.c_str()); + continue; + } + + ++saved_count; + } + + log("Saved %d mobs for zone %d", saved_count, zone.vnum); +} +void YamlWorldDataSource::SaveObjects(int zone_rnum, int specific_vnum) +{ + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) + { + log("SYSERR: Invalid zone_rnum %d for SaveObjects", zone_rnum); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + + std::string objs_dir = m_world_dir + "/objects"; + namespace fs = std::filesystem; + if (!fs::exists(objs_dir)) + { + fs::create_directories(objs_dir); + } + + int saved_count = 0; + int start_vnum = zone.vnum * 100; + int end_vnum = zone.top; + + for (const auto &[obj_vnum, obj_rnum] : obj_proto.vnum2index()) + { + if (obj_vnum < start_vnum || obj_vnum > end_vnum) + { + continue; + } + + // If specific_vnum is set, save only that object + if (specific_vnum != -1 && obj_vnum != specific_vnum) + { + continue; + } + + auto obj = obj_proto[obj_rnum]; + if (!obj) + { + continue; + } + + // Open temp file for writing + std::string obj_file = objs_dir + "/" + std::to_string(obj_vnum) + ".yaml"; + std::string temp_file = obj_file + ".tmp"; + std::ofstream out(temp_file); + if (!out.is_open()) + { + log("SYSERR: Failed to open %s for writing", temp_file.c_str()); + continue; + } + + Koi8rYamlEmitter yaml(out); + + // Header comment + yaml.Comment("Object #" + std::to_string(obj_vnum)); + yaml.EmptyLine(); + + // Names + yaml.Key("names"); + out << std::endl; + yaml.IncreaseIndent(); + + yaml.Key("aliases"); + yaml.Value(obj->get_aliases()); + + yaml.Key("nominative"); + yaml.Value(obj->get_PName(ECase::kNom)); + + yaml.Key("genitive"); + yaml.Value(obj->get_PName(ECase::kGen)); + + yaml.Key("dative"); + yaml.Value(obj->get_PName(ECase::kDat)); + + yaml.Key("accusative"); + yaml.Value(obj->get_PName(ECase::kAcc)); + + yaml.Key("instrumental"); + yaml.Value(obj->get_PName(ECase::kIns)); + + yaml.Key("prepositional"); + yaml.Value(obj->get_PName(ECase::kPre)); + + yaml.DecreaseIndent(); + + // Short description + yaml.Key("short_desc"); + yaml.Value(obj->get_short_description(), true); // literal=true + + // Action description (optional) + if (!obj->get_action_description().empty()) + { + yaml.Key("action_desc"); + yaml.Value(obj->get_action_description(), true); // literal=true + } + + // Type + yaml.Key("type"); + yaml.Value(ReverseLookupEnum("obj_types", static_cast(obj->get_type()))); + + // Material (with comment) + int material_id = static_cast(obj->get_material()); + yaml.Key("material"); + yaml.Value(material_id, GetMaterialNameComment(material_id)); + + // Values + yaml.Key("values"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + yaml.SequenceItem(obj->get_val(0)); + yaml.SequenceItem(obj->get_val(1)); + yaml.SequenceItem(obj->get_val(2)); + yaml.SequenceItem(obj->get_val(3)); + + yaml.DecreaseIndent(); + + // Weight, cost, rent + yaml.Key("weight"); + yaml.Value(obj->get_weight()); + + yaml.Key("cost"); + yaml.Value(obj->get_cost()); + + yaml.Key("rent_off"); + yaml.Value(obj->get_rent_off()); + + yaml.Key("rent_on"); + yaml.Value(obj->get_rent_on()); + + // Spec param (optional) + if (obj->get_spec_param() != 0) + { + yaml.Key("spec_param"); + yaml.Value(obj->get_spec_param()); + } + + // Durability and timer + yaml.Key("max_durability"); + yaml.Value(obj->get_maximum_durability()); + + yaml.Key("cur_durability"); + yaml.Value(obj->get_current_durability()); + + yaml.Key("timer"); + yaml.Value(obj->get_timer()); + + // Spell (with comment) + if (to_underlying(obj->get_spell()) >= 0) + { + int spell_id = to_underlying(obj->get_spell()); + yaml.Key("spell"); + yaml.Value(spell_id, GetSpellNameComment(static_cast(spell_id))); + } + + // Level and sex + yaml.Key("level"); + yaml.Value(obj->get_level()); + + yaml.Key("sex"); + yaml.Value(ReverseLookupEnum("genders", static_cast(obj->get_sex()))); + + // Max in world (optional) + if (obj->get_max_in_world() != -1) + { + yaml.Key("max_in_world"); + yaml.Value(obj->get_max_in_world()); + } + + // Minimum remorts (optional) + if (obj->get_minimum_remorts() != 0) + { + yaml.Key("minimum_remorts"); + yaml.Value(obj->get_minimum_remorts()); + } + + // Extra flags + auto extra_flags = ConvertFlagsToNames(obj->get_extra_flags(), "extra_flags"); + if (!extra_flags.empty()) + { + yaml.Key("extra_flags"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &flag : extra_flags) + { + yaml.SequenceItem(flag); + } + + yaml.DecreaseIndent(); + } + + // Wear flags + int wear_flags = obj->get_wear_flags(); + if (wear_flags != 0) + { + yaml.Key("wear_flags"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (int bit = 0; bit < 32; ++bit) + { + if (wear_flags & (1 << bit)) + { + std::string flag_name = ReverseLookupEnum("wear_flags", bit); + if (!flag_name.empty() && flag_name != std::to_string(bit)) + { + yaml.SequenceItem(flag_name); + } + else + { + yaml.SequenceItem("UNUSED_" + std::to_string(bit)); + } + } + } + + yaml.DecreaseIndent(); + } + + // No flags + auto no_flags = ConvertFlagsToNames(obj->get_no_flags(), "no_flags"); + if (!no_flags.empty()) + { + yaml.Key("no_flags"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &flag : no_flags) + { + yaml.SequenceItem(flag); + } + + yaml.DecreaseIndent(); + } + + // Anti flags + auto anti_flags = ConvertFlagsToNames(obj->get_anti_flags(), "anti_flags"); + if (!anti_flags.empty()) + { + yaml.Key("anti_flags"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &flag : anti_flags) + { + yaml.SequenceItem(flag); + } + + yaml.DecreaseIndent(); + } + + // Affect flags + auto affect_flags = ConvertFlagsToNames(obj->get_affect_flags(), "affect_flags"); + if (!affect_flags.empty()) + { + yaml.Key("affect_flags"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (const auto &flag : affect_flags) + { + yaml.SequenceItem(flag); + } + + yaml.DecreaseIndent(); + } + + // Applies (with location comments) + bool has_applies = false; + for (int i = 0; i < kMaxObjAffect; ++i) + { + if (obj->get_affected(i).location != EApply::kNone) + { + has_applies = true; + break; + } + } + + if (has_applies) + { + yaml.Key("applies"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (int i = 0; i < kMaxObjAffect; ++i) + { + if (obj->get_affected(i).location != EApply::kNone) + { + int location = static_cast(obj->get_affected(i).location); + int modifier = obj->get_affected(i).modifier; + + out << yaml.GetIndent() << "- location: " << location; + out << " # " << GetApplyTypeNameComment(location) << std::endl; + out << yaml.GetIndent() << " modifier: " << modifier << std::endl; + } + } + + yaml.DecreaseIndent(); + } + + // Extra descriptions + if (obj->get_ex_description()) + { + yaml.Key("extra_descriptions"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (auto exdesc = obj->get_ex_description(); exdesc; exdesc = exdesc->next) + { + if (exdesc->keyword) + { + out << yaml.GetIndent() << "- keywords: " << exdesc->keyword << std::endl; + if (exdesc->description) + { + out << yaml.GetIndent() << " description: |" << std::endl; + + std::string desc = exdesc->description; + desc.erase(std::remove(desc.begin(), desc.end(), '\r'), desc.end()); + + std::istringstream iss(desc); + std::string line; + while (std::getline(iss, line)) + { + out << yaml.GetIndent() << " " << line << std::endl; + } + } + } + } + + yaml.DecreaseIndent(); + } + + // Triggers (with comments) + if (obj->get_proto_script_ptr() && !obj->get_proto_script().empty()) + { + yaml.Key("triggers"); + yaml.BeginSequence(); + yaml.IncreaseIndent(); + + for (auto trig_vnum : obj->get_proto_script()) + { + std::string trig_comment = GetTriggerNameComment(trig_vnum); + yaml.SequenceItem(trig_vnum, trig_comment); + } + + yaml.DecreaseIndent(); + } + + // Close file and rename atomically + out.close(); + if (std::rename(temp_file.c_str(), obj_file.c_str()) != 0) + { + log("SYSERR: Failed to rename %s to %s", temp_file.c_str(), obj_file.c_str()); + continue; + } + + ++saved_count; + } + + log("Saved %d objects for zone %d", saved_count, zone.vnum); +} + +// ============================================================================ +// Factory function +// ============================================================================ + +std::unique_ptr CreateYamlDataSource(const std::string &world_dir) +{ + return std::make_unique(world_dir); +} + +} // namespace world_loader + +#endif // HAVE_YAML + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/db/yaml_world_data_source.h b/src/engine/db/yaml_world_data_source.h new file mode 100644 index 000000000..99428150e --- /dev/null +++ b/src/engine/db/yaml_world_data_source.h @@ -0,0 +1,139 @@ +// Part of Bylins http://www.mud.ru +// YAML world data source - loads world from YAML files + +#ifndef YAML_WORLD_DATA_SOURCE_H_ +#define YAML_WORLD_DATA_SOURCE_H_ + +#include "world_data_source.h" +#include "world_data_source_base.h" +#include "world_data_source_base.h" + +#ifdef HAVE_YAML + +#include "engine/structs/flag_data.h" +#include "description.h" + +#include +#include +#include +#include +#include + +class ZoneData; +class RoomData; +class CharData; +struct Trigger; +class CObjectPrototype; + +namespace utils { + class ThreadPool; +} + +namespace world_loader +{ + +// Result of parsing rooms in a single thread +struct ParsedRoomBatch { + LocalDescriptionIndex descriptions; // Thread-local description index + std::vector> rooms; // (vnum, room, local_desc_idx) + std::map> triggers; // Room triggers (room_vnum -> list of trigger vnums) +}; + +// YAML implementation for human-readable world files +class YamlWorldDataSource : public WorldDataSourceBase +{ +public: + explicit YamlWorldDataSource(const std::string &world_dir); + ~YamlWorldDataSource() override = default; + + std::string GetName() const override { return "YAML files: " + m_world_dir; } + + void LoadZones() override; + void LoadTriggers() override; + void LoadRooms() override; + void LoadMobs() override; + void LoadObjects() override; + + // Save methods (YAML is read-only for now) + void SaveZone(int zone_rnum) override; + bool SaveTriggers(int zone_rnum, int specific_vnum, int notify_level) override; + void SaveRooms(int zone_rnum, int specific_vnum = -1) override; + void SaveMobs(int zone_rnum, int specific_vnum = -1) override; + void SaveObjects(int zone_rnum, int specific_vnum = -1) override; + +private: + // Initialize dictionaries + bool LoadDictionaries(); + + // Load world configuration (line endings, etc) + bool LoadWorldConfig(); + + // Get list of zone vnums from index.yaml + std::vector GetZoneList(); + std::vector GetMobList(); + std::vector GetObjectList(); + std::vector GetTriggerList(); + + // Zone loading helpers + void LoadZoneCommands(ZoneData &zone, const YAML::Node &commands_node); + + // Room loading helpers + void LoadRoomExits(RoomData *room, const YAML::Node &exits_node, int room_vnum); + void LoadRoomExtraDescriptions(RoomData *room, const YAML::Node &extras_node); + + // Flag parsing using dictionaries + FlagData ParseFlags(const YAML::Node &node, const std::string &dict_name) const; + int ParseEnum(const YAML::Node &node, const std::string &dict_name, int default_val = 0) const; + int ParsePosition(const YAML::Node &node) const; + int ParseGender(const YAML::Node &node, int default_val = 1) const; + + // Utility functions + std::string GetText(const YAML::Node &node, const std::string &key, const std::string &default_val = "") const; + int GetInt(const YAML::Node &node, const std::string &key, int default_val = 0) const; + long GetLong(const YAML::Node &node, const std::string &key, long default_val = 0) const; + + // Convert UTF-8 from YAML to KOI8-R + std::string ConvertToKoi8r(const std::string &utf8_str) const; + + // Helper methods for save operations + std::string ConvertToUtf8(const std::string &koi8r_str) const; + std::vector ConvertFlagsToNames(const FlagData &flags, const std::string &dict_name) const; + std::string ReverseLookupEnum(const std::string &dict_name, int value) const; + bool WriteYamlAtomic(const std::string &filepath, const YAML::Node &node) const; + + // Parallel loading methods (only used when m_num_threads > 1) + void LoadZonesParallel(); + void LoadTriggersParallel(); + void LoadRoomsParallel(); + void LoadMobsParallel(); + void LoadObjectsParallel(); + + // Worker functions (thread-safe, parse single file) + ZoneData ParseZoneFile(const std::string &file_path); + Trigger* ParseTriggerFile(const std::string &file_path); + RoomData* ParseRoomFile(const std::string &file_path, int zone_rnum, LocalDescriptionIndex &local_index, size_t &local_desc_idx); + CharData ParseMobFile(const std::string &file_path); + CObjectPrototype* ParseObjectFile(const std::string &file_path); + + // Helper: get configured thread count from runtime config + size_t GetConfiguredThreadCount() const; + + std::string m_world_dir; + bool m_dictionaries_loaded = false; + bool m_convert_lf_to_crlf = false; // Convert LF to CR+LF for DOS line endings + + // Threading support + std::unique_ptr m_thread_pool; + size_t m_num_threads; +}; + +// Factory function for creating YAML data source +std::unique_ptr CreateYamlDataSource(const std::string &world_dir); + +} // namespace world_loader + +#endif // HAVE_YAML + +#endif // YAML_WORLD_DATA_SOURCE_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/entities/char_data.h b/src/engine/entities/char_data.h index 0cf8d4ad8..00e1bf54f 100644 --- a/src/engine/entities/char_data.h +++ b/src/engine/entities/char_data.h @@ -858,14 +858,18 @@ inline void CharData::clear_ignores() { } inline int GET_INVIS_LEV(const CharData *ch) { - if (ch->player_specials->saved.invis_level) + if (ch->player_specials && ch->player_specials->saved.invis_level) return ch->player_specials->saved.invis_level; else return 0; } inline int GET_INVIS_LEV(const CharData::shared_ptr &ch) { return GET_INVIS_LEV(ch.get()); } -inline void SET_INVIS_LEV(const CharData *ch, const int level) { ch->player_specials->saved.invis_level = level; } +inline void SET_INVIS_LEV(const CharData *ch, const int level) { + if (ch->player_specials) { + ch->player_specials->saved.invis_level = level; + } +} inline void SET_INVIS_LEV(const CharData::shared_ptr &ch, const int level) { SET_INVIS_LEV(ch.get(), level); } inline void SetWaitState(CharData *ch, const unsigned cycle) { diff --git a/src/engine/entities/char_player.cpp b/src/engine/entities/char_player.cpp index 39df568fd..597f14cda 100644 --- a/src/engine/entities/char_player.cpp +++ b/src/engine/entities/char_player.cpp @@ -899,11 +899,13 @@ void Player::save_char() { } fprintf(saved, "Tlgr: %lu\n", this->player_specials->saved.telegram_id); fclose(saved); +#ifndef _WIN32 if (chmod(filename, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << filename << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } +#endif FileCRC::check_crc(filename, FileCRC::UPDATE_PLAYER, this->get_uid()); // восстанавливаем аффекты diff --git a/src/engine/entities/zone.cpp b/src/engine/entities/zone.cpp index 0b891f37f..0553e3a5a 100644 --- a/src/engine/entities/zone.cpp +++ b/src/engine/entities/zone.cpp @@ -35,6 +35,102 @@ ZoneData::ZoneData() : traffic(0), RnumMobsLocation(-1, -1) { } +ZoneData::ZoneData(ZoneData&& other) noexcept + : name(std::move(other.name)), + comment(std::move(other.comment)), + author(std::move(other.author)), + traffic(other.traffic), + level(other.level), + type(other.type), + first_enter(std::move(other.first_enter)), + lifespan(other.lifespan), + age(other.age), + time_awake(other.time_awake), + top(other.top), + reset_mode(other.reset_mode), + vnum(other.vnum), + copy_from_zone(other.copy_from_zone), + location(std::move(other.location)), + description(std::move(other.description)), + cmd(other.cmd), + typeA_count(other.typeA_count), + typeA_list(other.typeA_list), + typeB_count(other.typeB_count), + typeB_list(other.typeB_list), + typeB_flag(other.typeB_flag), + under_construction(other.under_construction), + locked(other.locked), + reset_idle(other.reset_idle), + used(other.used), + activity(other.activity), + group(other.group), + mob_level(other.mob_level), + is_town(other.is_town), + count_reset(other.count_reset), + entrance(other.entrance), + RnumTrigsLocation(other.RnumTrigsLocation), + RnumRoomsLocation(other.RnumRoomsLocation), + RnumMobsLocation(other.RnumMobsLocation) +{ + other.cmd = nullptr; + other.typeA_list = nullptr; + other.typeB_list = nullptr; + other.typeB_flag = nullptr; +} + +ZoneData& ZoneData::operator=(ZoneData&& other) noexcept +{ + if (this != &other) + { + if (cmd) free(cmd); + if (typeA_list) free(typeA_list); + if (typeB_list) free(typeB_list); + if (typeB_flag) free(typeB_flag); + + name = std::move(other.name); + comment = std::move(other.comment); + author = std::move(other.author); + traffic = other.traffic; + level = other.level; + type = other.type; + first_enter = std::move(other.first_enter); + lifespan = other.lifespan; + age = other.age; + time_awake = other.time_awake; + top = other.top; + reset_mode = other.reset_mode; + vnum = other.vnum; + copy_from_zone = other.copy_from_zone; + location = std::move(other.location); + description = std::move(other.description); + cmd = other.cmd; + typeA_count = other.typeA_count; + typeA_list = other.typeA_list; + typeB_count = other.typeB_count; + typeB_list = other.typeB_list; + typeB_flag = other.typeB_flag; + under_construction = other.under_construction; + locked = other.locked; + reset_idle = other.reset_idle; + used = other.used; + activity = other.activity; + group = other.group; + mob_level = other.mob_level; + is_town = other.is_town; + count_reset = other.count_reset; + entrance = other.entrance; + RnumTrigsLocation = other.RnumTrigsLocation; + RnumRoomsLocation = other.RnumRoomsLocation; + RnumMobsLocation = other.RnumMobsLocation; + + other.cmd = nullptr; + other.typeA_list = nullptr; + other.typeB_list = nullptr; + other.typeB_flag = nullptr; + } + return *this; +} + ZoneData::~ZoneData() { // log("~ZoneData zone %d", vnum); if (!name.empty()) diff --git a/src/engine/entities/zone.h b/src/engine/entities/zone.h index 9a1f4eb89..d4ffb2648 100644 --- a/src/engine/entities/zone.h +++ b/src/engine/entities/zone.h @@ -19,10 +19,10 @@ class ZoneData { ZoneData(); ZoneData(const ZoneData &) = delete; - ZoneData(ZoneData &&) = default; + ZoneData(ZoneData &&) noexcept; ZoneData &operator=(const ZoneData &) = delete; - ZoneData &operator=(ZoneData &&) = default; + ZoneData &operator=(ZoneData &&) noexcept; ~ZoneData(); diff --git a/src/engine/network/admin_api.cpp b/src/engine/network/admin_api.cpp new file mode 100644 index 000000000..2671e3d34 --- /dev/null +++ b/src/engine/network/admin_api.cpp @@ -0,0 +1,402 @@ +/** + \file admin_api.cpp + \brief Admin API implementation via Unix Domain Socket +*/ + +#ifdef ENABLE_ADMIN_API +#include + +#include "admin_api.h" +#include "admin_api/crud_handlers.h" +#include "engine/db/db.h" +#include "engine/db/obj_prototypes.h" +#include "engine/db/world_data_source_manager.h" +#include "utils/id_converter.h" +#include "engine/db/world_objects.h" +#include "engine/db/world_characters.h" +#include "engine/db/global_objects.h" +#include "engine/entities/zone.h" +#include "engine/entities/char_player.h" +#include "engine/scripting/dg_scripts.h" +#include "engine/olc/olc.h" +#include "administration/accounts.h" +#include "administration/password.h" +#include "utils/utils.h" +#include "gameplay/core/constants.h" +#include "third_party_libs/nlohmann/json.hpp" + +#include +#include + +using json = nlohmann::json; +using namespace admin_api::handlers; + +// ============================================================================ +// Helper functions for encoding conversion +// ============================================================================ + +// Convert KOI8-R string to UTF-8 for JSON +std::string koi8r_to_utf8(const std::string &koi8r) { + char utf8_buf[kMaxSockBuf * 6]; + char koi8r_buf[kMaxSockBuf * 6]; + + utf8_buf[0] = '\0'; + + strncpy(koi8r_buf, koi8r.c_str(), sizeof(koi8r_buf) - 1); + koi8r_buf[sizeof(koi8r_buf) - 1] = 0; + + koi_to_utf8(koi8r_buf, utf8_buf); + + return std::string(utf8_buf); +} + +// Convert UTF-8 string to KOI8-R (for incoming JSON data) +std::string utf8_to_koi8r(const std::string &utf8) { + char koi8r_buf[kMaxSockBuf * 6]; + char utf8_buf[kMaxSockBuf * 6]; + + strncpy(utf8_buf, utf8.c_str(), sizeof(utf8_buf) - 1); + utf8_buf[sizeof(utf8_buf) - 1] = '\0'; + + utf8_to_koi(utf8_buf, koi8r_buf); + + return std::string(koi8r_buf); +} + +// ============================================================================ +// Admin API socket I/O and chunking +// ============================================================================ + +#define MAX_CHUNKS 64 // Max 64KB responses (64 * 1KB chunks) + +// Admin API input processing (separate from game process_input) +int admin_api_process_input(DescriptorData *d) { + char *ptr, *nl_pos; + ssize_t bytes_read; + constexpr size_t kMaxLargeBufferSize = 1048576; // 1MB limit + + size_t buf_length = strlen(d->inbuf); + char *read_point = d->inbuf + buf_length; + size_t space_left = kMaxRawInputLength - buf_length - 1; + + if (space_left <= 0) { + // Buffer full but no newline yet - use large buffer for large messages + if (d->admin_api_large_buffer.size() + buf_length > kMaxLargeBufferSize) { + log("SYSERR: Admin API message too large (>1MB). Disconnecting."); + return -1; + } + d->admin_api_large_buffer.append(d->inbuf, buf_length); + d->inbuf[0] = '\0'; + return 0; // Wait for more data + } + + bytes_read = recv(d->descriptor, read_point, space_left, 0); + + if (bytes_read < 0) { + if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) { + return 0; + } + log("SYSERR: Admin API recv error: %s", strerror(errno)); + return -1; + } else if (bytes_read == 0) { + log("Admin API: client disconnected (EOF)"); + return -1; + } + + *(read_point + bytes_read) = '\0'; + + // Process complete lines + for (ptr = d->inbuf; (nl_pos = strchr(ptr, '\n')); ptr = nl_pos + 1) { + while (*nl_pos == '\n' || *nl_pos == '\r') { + *nl_pos = '\0'; + nl_pos++; + } + + if (*ptr) { + // If we have accumulated data in large buffer, prepend it + if (!d->admin_api_large_buffer.empty()) { + d->admin_api_large_buffer.append(ptr); + admin_api_parse(d, const_cast(d->admin_api_large_buffer.c_str())); + d->admin_api_large_buffer.clear(); + } else { + admin_api_parse(d, ptr); + } + } + } + + // Handle remaining data (no newline yet) + size_t ptr_offset = ptr - d->inbuf; + size_t valid_data_end = buf_length + bytes_read; + + // Only process remaining data if ptr is within valid range + if (ptr_offset < valid_data_end && *ptr) { + size_t remaining = strlen(ptr); + // If there's no newline, accumulate in large buffer + if (!strchr(ptr, '\n')) { + d->admin_api_large_buffer.append(ptr, remaining); + d->inbuf[0] = '\0'; + } else { + // Move remaining data to start of buffer + memmove(d->inbuf, ptr, remaining + 1); + } + } else { + d->inbuf[0] = '\0'; + } + + return 0; +} + +// ============================================================================ +// Authentication +// ============================================================================ + +bool admin_api_authenticate(DescriptorData *d, const char *username, const char *password) { + if (!username || !password) { + return false; + } + + // Check if authentication is required + extern RuntimeConfiguration runtime_config; + if (!runtime_config.admin_require_auth()) { + d->admin_api_authenticated = true; + d->admin_user_id = 1; + log("Admin API: authentication bypassed (require_auth=false) for user '%s'", username); + return true; + } + + // Create temporary Player object for loading character + auto temp_ch = std::make_shared(); + + // Load player character by name + int player_id = LoadPlayerCharacter(username, temp_ch.get(), ELoadCharFlags::kFindId); + + if (player_id < 0) { + log("Admin API: user '%s' not found", username); + return false; + } + + // Check level (immortal or builder) + if (temp_ch->GetLevel() < kLvlBuilder) { + log("Admin API: user '%s' (level %d) - access denied (requires Builder+)", username, temp_ch->GetLevel()); + return false; + } + + // Check password + if (!Password::compare_password(temp_ch.get(), password)) { + log("Admin API: user '%s' - incorrect password", username); + return false; + } + + // Authentication successful + d->admin_api_authenticated = true; + d->admin_user_id = temp_ch->get_uid(); + log("Admin API: user '%s' (ID %ld, level %d) authenticated successfully", username, temp_ch->get_uid(), temp_ch->GetLevel()); + return true; +} + +bool admin_api_is_authenticated(DescriptorData *d) { + return d->admin_api_authenticated; +} + +// ============================================================================ +// JSON response helpers +// ============================================================================ + +void admin_api_send_json(DescriptorData *d, const char *json_str) { + if (!json_str) return; + + std::string message = std::string(json_str) + "\n"; + + // Send all data (may require multiple send() calls) + size_t sent_total = 0; + while (sent_total < message.length()) { + ssize_t written = send(d->descriptor, message.c_str() + sent_total, + message.length() - sent_total, 0); + if (written < 0) { + if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) { + continue; // Retry + } + log("SYSERR: Admin API send error: %s", strerror(errno)); + return; + } + sent_total += written; + } +} + +void admin_api_send_error(DescriptorData *d, const char *error_msg) { + json response; + response["status"] = "error"; + response["error"] = error_msg; + admin_api_send_json(d, response.dump().c_str()); +} + +// ============================================================================ +// Command dispatcher +// ============================================================================ + +void admin_api_parse(DescriptorData *d, char *argument) { + + try { + // Parse JSON command + json request = json::parse(argument); + + std::string command = request.value("command", ""); + + if (command == "auth") { + std::string username_utf8 = request.value("username", ""); + std::string password = request.value("password", ""); + + // Convert username from UTF-8 to KOI8-R (server's internal encoding) + std::string username = utf8_to_koi8r(username_utf8); + + // Authenticate with KOI8-R username + if (admin_api_authenticate(d, username.c_str(), password.c_str())) { + json response; + response["status"] = "ok"; + response["message"] = "Authentication successful"; + admin_api_send_json(d, response.dump().c_str()); + + mudlog(fmt::format("Admin API: {} connected and authenticated", username), BRF, kLvlImplementator, IMLOG, true); + } else { + admin_api_send_error(d, "Authentication failed"); + mudlog(fmt::format("Admin API: authentication failed for user '{}'", username), BRF, kLvlGod, IMLOG, true); + } + return; + } + + // All commands require authentication + if (!admin_api_is_authenticated(d)) { + admin_api_send_error(d, "Not authenticated. Use 'auth' command first."); + return; + } + + // Log all API commands to immortals (except auth/ping) + mudlog(fmt::format("Admin API: {} executed '{}'", d->admin_user_name, command), BRF, kLvlImplementator, IMLOG, true); + + // ==================================================================== + // Dispatch to new modular handlers + // ==================================================================== + + // Zone commands + if (command == "list_zones") { + HandleListZones(d); + } + else if (command == "get_zone") { + int vnum = request.value("vnum", -1); + HandleGetZone(d, vnum); + } + else if (command == "update_zone") { + int vnum = request.value("vnum", -1); + json data = request["data"]; + HandleUpdateZone(d, vnum, data.dump().c_str()); + } + // Mob commands + else if (command == "list_mobs") { + std::string zone = request.value("zone", ""); + HandleListMobs(d, zone.c_str()); + } + else if (command == "get_mob") { + int vnum = request.value("vnum", -1); + HandleGetMob(d, vnum); + } + else if (command == "update_mob") { + int vnum = request.value("vnum", -1); + json data = request["data"]; + HandleUpdateMob(d, vnum, data.dump().c_str()); + } + else if (command == "create_mob") { + json data = request.value("data", json::object()); + HandleCreateMob(d, data.dump().c_str()); + } + else if (command == "delete_mob") { + int vnum = request.value("vnum", -1); + HandleDeleteMob(d, vnum); + } + // Object commands + else if (command == "list_objects") { + std::string zone = request.value("zone", ""); + HandleListObjects(d, zone.c_str()); + } + else if (command == "get_object") { + int vnum = request.value("vnum", -1); + HandleGetObject(d, vnum); + } + else if (command == "update_object") { + int vnum = request.value("vnum", -1); + json data = request["data"]; + HandleUpdateObject(d, vnum, data.dump().c_str()); + } + else if (command == "create_object") { + json data = request.value("data", json::object()); + HandleCreateObject(d, data.dump().c_str()); + } + else if (command == "delete_object") { + int vnum = request.value("vnum", -1); + HandleDeleteObject(d, vnum); + } + // Room commands + else if (command == "list_rooms") { + std::string zone = request.value("zone", ""); + HandleListRooms(d, zone.c_str()); + } + else if (command == "get_room") { + int vnum = request.value("vnum", -1); + HandleGetRoom(d, vnum); + } + else if (command == "update_room") { + int vnum = request.value("vnum", -1); + json data = request["data"]; + HandleUpdateRoom(d, vnum, data.dump().c_str()); + } + else if (command == "create_room") { + json data = request.value("data", json::object()); + HandleCreateRoom(d, data.dump().c_str()); + } + else if (command == "delete_room") { + int vnum = request.value("vnum", -1); + HandleDeleteRoom(d, vnum); + } + // Trigger commands + else if (command == "list_triggers") { + std::string zone = request.value("zone", ""); + HandleListTriggers(d, zone.c_str()); + } + else if (command == "get_trigger") { + int vnum = request.value("vnum", -1); + HandleGetTrigger(d, vnum); + } + else if (command == "update_trigger") { + int vnum = request.value("vnum", -1); + json data = request["data"]; + HandleUpdateTrigger(d, vnum, data.dump().c_str()); + } + else if (command == "create_trigger") { + int zone = request.value("zone", -1); + json data = request.value("data", json::object()); + HandleCreateTrigger(d, zone, data.dump().c_str()); + } + else if (command == "delete_trigger") { + int vnum = request.value("vnum", -1); + HandleDeleteTrigger(d, vnum); + } + // Stats and players + else if (command == "get_stats") { + HandleGetStats(d); + } + else if (command == "get_players") { + HandleGetPlayers(d); + } + else { + admin_api_send_error(d, "Unknown command"); + } + + } catch (const json::exception &e) { + admin_api_send_error(d, (std::string("JSON error: ") + e.what()).c_str()); + } catch (const std::exception &e) { + admin_api_send_error(d, (std::string("Error: ") + e.what()).c_str()); + } +} + +#endif // ENABLE_ADMIN_API + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api.h b/src/engine/network/admin_api.h new file mode 100644 index 000000000..7584496d9 --- /dev/null +++ b/src/engine/network/admin_api.h @@ -0,0 +1,65 @@ +/** + \file admin_api.h + \brief Admin API via Unix Domain Socket + \details Provides JSON-based API for remote administration via Unix socket. + Requires compilation with -DENABLE_ADMIN_API=ON +*/ + +#ifndef BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_H_ +#define BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_H_ + +#ifdef ENABLE_ADMIN_API + +struct DescriptorData; + +// Обработчик команд Admin API +void admin_api_parse(DescriptorData *d, char *argument); + +// Обработка ввода Admin API (отдельно от игрового process_input) +int admin_api_process_input(DescriptorData *d); + +// Аутентификация +bool admin_api_authenticate(DescriptorData *d, const char *username, const char *password); +bool admin_api_is_authenticated(DescriptorData *d); + +// API команды (требуют аутентификации) +void admin_api_list_mobs(DescriptorData *d, const char *zone_vnum); +void admin_api_get_mob(DescriptorData *d, int mob_vnum); +void admin_api_update_mob(DescriptorData *d, int mob_vnum, const char *json_data); +void admin_api_create_mob(DescriptorData *d, int zone_vnum, const char *json_data); +void admin_api_delete_mob(DescriptorData *d, int mob_vnum); + +void admin_api_list_objects(DescriptorData *d, const char *zone_vnum); +void admin_api_get_object(DescriptorData *d, int obj_vnum); +void admin_api_update_object(DescriptorData *d, int obj_vnum, const char *json_data); +void admin_api_create_object(DescriptorData *d, int zone_vnum, const char *json_data); +void admin_api_delete_object(DescriptorData *d, int obj_vnum); + +void admin_api_list_rooms(DescriptorData *d, const char *zone_vnum); +void admin_api_get_room(DescriptorData *d, int room_vnum); +void admin_api_update_room(DescriptorData *d, int room_vnum, const char *json_data); +void admin_api_create_room(DescriptorData *d, int zone_vnum, const char *json_data); +void admin_api_delete_room(DescriptorData *d, int room_vnum); + +void admin_api_list_zones(DescriptorData *d); +void admin_api_get_zone(DescriptorData *d, int zone_vnum); +void admin_api_update_zone(DescriptorData *d, int zone_vnum, const char *json_data); + +// Статистика и информация о сервере +void admin_api_get_stats(DescriptorData *d); +void admin_api_get_players(DescriptorData *d); + +void admin_api_list_triggers(DescriptorData *d, const char *zone_vnum); +void admin_api_get_trigger(DescriptorData *d, int trig_vnum); +void admin_api_update_trigger(DescriptorData *d, int trig_vnum, const char *json_data); +void admin_api_create_trigger(DescriptorData *d, int zone_vnum, const char *json_data); +void admin_api_delete_trigger(DescriptorData *d, int trig_vnum); + +// Утилиты +void admin_api_send_json(DescriptorData *d, const char *json); +void admin_api_send_error(DescriptorData *d, const char *error_msg); + +#endif // ENABLE_ADMIN_API +#endif // BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/admin_api_constants.h b/src/engine/network/admin_api/admin_api_constants.h new file mode 100644 index 000000000..49bfa6fa5 --- /dev/null +++ b/src/engine/network/admin_api/admin_api_constants.h @@ -0,0 +1,36 @@ +/** + \file admin_api_constants.h + \brief Constants and enums for Admin API + \authors Bylins team + \date 2026-02-13 + + This file contains all constants, limits, and enum classes used by the Admin API. + Part of the Admin API refactoring to eliminate magic numbers and improve maintainability. +*/ + +#ifndef BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_ADMIN_API_CONSTANTS_H_ +#define BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_ADMIN_API_CONSTANTS_H_ + +#include +#include + +namespace admin_api { + +// ============================================================================ +// Buffer Size Limits +// ============================================================================ + +/// Maximum size for large buffer accumulation (1 MB) +constexpr size_t kMaxLargeBufferSize = 1048576; + +/// Maximum chunks allowed for chunked messages +constexpr int kMaxChunks = 4; + +// Note: Command enum and string conversion functions were removed as unused. +// CommandRegistry uses direct stringБ├▓handler mapping via std::unordered_map. + +} // namespace admin_api + +#endif // BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_ADMIN_API_CONSTANTS_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/command_registry.cpp b/src/engine/network/admin_api/command_registry.cpp new file mode 100644 index 000000000..bd7815de8 --- /dev/null +++ b/src/engine/network/admin_api/command_registry.cpp @@ -0,0 +1,306 @@ +/** + \file command_registry.cpp + \brief Command dispatch registry implementation + \authors Bylins team + \date 2026-02-13 +*/ + +#include "command_registry.h" +#include "crud_handlers.h" +#include "../../../engine/network/descriptor_data.h" +#include "../../../administration/accounts.h" +#include "../../../administration/password.h" + +namespace admin_api { + +using namespace admin_api::handlers; + +// ============================================================================ +// CommandRegistry Implementation +// ============================================================================ + +CommandRegistry& CommandRegistry::Instance() +{ + static CommandRegistry instance; + return instance; +} + +void CommandRegistry::Register(std::string_view command, CommandHandler handler) +{ + handlers_[std::string(command)] = handler; +} + +bool CommandRegistry::Dispatch(std::string_view command, DescriptorData* d, const nlohmann::json& request) +{ + auto it = handlers_.find(std::string(command)); + if (it == handlers_.end()) + { + return false; + } + + // Call the registered handler + it->second(d, request); + return true; +} + +bool CommandRegistry::IsRegistered(std::string_view command) const +{ + return handlers_.find(std::string(command)) != handlers_.end(); +} + +size_t CommandRegistry::Count() const +{ + return handlers_.size(); +} + +// ============================================================================ +// Command Handlers (wrappers for CRUD handlers) +// ============================================================================ + +// Auth command (special case - not a CRUD operation) +static void HandleAuth(DescriptorData* d, const nlohmann::json& request) +{ + // Extract username and password from request + std::string username = request.value("username", ""); + std::string password = request.value("password", ""); + + if (username.empty() || password.empty()) + { + SendErrorResponse(d, "Missing username or password"); + return; + } + + // Authenticate using existing account system + Account acc(username); + + // Verify password + if (!acc.compare_password(password)) + { + SendErrorResponse(d, "Authentication failed"); + return; + } + + // Mark descriptor as authenticated + d->admin_api_authenticated = true; + + // Send success response + nlohmann::json response; + response["status"] = "ok"; + response["message"] = "Authentication successful"; + SendJsonResponse(d, response); +} + +// Zone commands +static void HandleListZonesCommand(DescriptorData* d, const nlohmann::json& request) +{ + (void)request; // No parameters needed + HandleListZones(d); +} + +static void HandleGetZoneCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleGetZone(d, vnum); +} + +static void HandleUpdateZoneCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + std::string data_str = request.value("data", ""); + HandleUpdateZone(d, vnum, data_str.c_str()); +} + +// Mob commands +static void HandleListMobsCommand(DescriptorData* d, const nlohmann::json& request) +{ + std::string zone = request.value("zone", ""); + HandleListMobs(d, zone.c_str()); +} + +static void HandleGetMobCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleGetMob(d, vnum); +} + +static void HandleUpdateMobCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + std::string data_str = request.dump(); // Pass full request as JSON + HandleUpdateMob(d, vnum, data_str.c_str()); +} + +static void HandleCreateMobCommand(DescriptorData* d, const nlohmann::json& request) +{ + std::string data_str = request.dump(); + HandleCreateMob(d, data_str.c_str()); +} + +static void HandleDeleteMobCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleDeleteMob(d, vnum); +} + +// Object commands +static void HandleListObjectsCommand(DescriptorData* d, const nlohmann::json& request) +{ + std::string zone = request.value("zone", ""); + HandleListObjects(d, zone.c_str()); +} + +static void HandleGetObjectCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleGetObject(d, vnum); +} + +static void HandleUpdateObjectCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + std::string data_str = request.dump(); + HandleUpdateObject(d, vnum, data_str.c_str()); +} + +static void HandleCreateObjectCommand(DescriptorData* d, const nlohmann::json& request) +{ + std::string data_str = request.dump(); + HandleCreateObject(d, data_str.c_str()); +} + +static void HandleDeleteObjectCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleDeleteObject(d, vnum); +} + +// Room commands +static void HandleListRoomsCommand(DescriptorData* d, const nlohmann::json& request) +{ + std::string zone = request.value("zone", ""); + HandleListRooms(d, zone.c_str()); +} + +static void HandleGetRoomCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleGetRoom(d, vnum); +} + +static void HandleUpdateRoomCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + std::string data_str = request.dump(); + HandleUpdateRoom(d, vnum, data_str.c_str()); +} + +static void HandleCreateRoomCommand(DescriptorData* d, const nlohmann::json& request) +{ + std::string data_str = request.dump(); + HandleCreateRoom(d, data_str.c_str()); +} + +static void HandleDeleteRoomCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleDeleteRoom(d, vnum); +} + +// Trigger commands +static void HandleListTriggersCommand(DescriptorData* d, const nlohmann::json& request) +{ + std::string zone = request.value("zone", ""); + HandleListTriggers(d, zone.c_str()); +} + +static void HandleGetTriggerCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleGetTrigger(d, vnum); +} + +static void HandleUpdateTriggerCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + std::string data_str = request.dump(); + HandleUpdateTrigger(d, vnum, data_str.c_str()); +} + +static void HandleCreateTriggerCommand(DescriptorData* d, const nlohmann::json& request) +{ + int zone = request.value("zone", -1); + nlohmann::json data = request.value("data", nlohmann::json::object()); + HandleCreateTrigger(d, zone, data.dump().c_str()); +} + +static void HandleDeleteTriggerCommand(DescriptorData* d, const nlohmann::json& request) +{ + int vnum = request.value("vnum", -1); + HandleDeleteTrigger(d, vnum); +} + +// Statistics and players +static void HandleGetStatsCommand(DescriptorData* d, const nlohmann::json& request) +{ + (void)request; + HandleGetStats(d); +} + +static void HandleGetPlayersCommand(DescriptorData* d, const nlohmann::json& request) +{ + (void)request; + HandleGetPlayers(d); +} + +// ============================================================================ +// Command Registry Initialization +// ============================================================================ + +void InitializeCommandRegistry() +{ + auto& registry = CommandRegistry::Instance(); + + // Auth command + registry.Register("auth", HandleAuth); + + // Zone commands + registry.Register("list_zones", HandleListZonesCommand); + registry.Register("get_zone", HandleGetZoneCommand); + registry.Register("update_zone", HandleUpdateZoneCommand); + + // Mob commands + registry.Register("list_mobs", HandleListMobsCommand); + registry.Register("get_mob", HandleGetMobCommand); + registry.Register("update_mob", HandleUpdateMobCommand); + registry.Register("create_mob", HandleCreateMobCommand); + registry.Register("delete_mob", HandleDeleteMobCommand); + + // Object commands + registry.Register("list_objects", HandleListObjectsCommand); + registry.Register("get_object", HandleGetObjectCommand); + registry.Register("update_object", HandleUpdateObjectCommand); + registry.Register("create_object", HandleCreateObjectCommand); + registry.Register("delete_object", HandleDeleteObjectCommand); + + // Room commands + registry.Register("list_rooms", HandleListRoomsCommand); + registry.Register("get_room", HandleGetRoomCommand); + registry.Register("update_room", HandleUpdateRoomCommand); + registry.Register("create_room", HandleCreateRoomCommand); + registry.Register("delete_room", HandleDeleteRoomCommand); + + // Trigger commands + registry.Register("list_triggers", HandleListTriggersCommand); + registry.Register("get_trigger", HandleGetTriggerCommand); + registry.Register("update_trigger", HandleUpdateTriggerCommand); + registry.Register("create_trigger", HandleCreateTriggerCommand); + registry.Register("delete_trigger", HandleDeleteTriggerCommand); + + // Statistics and players + registry.Register("get_stats", HandleGetStatsCommand); + registry.Register("get_players", HandleGetPlayersCommand); +} + +} // namespace admin_api + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/command_registry.h b/src/engine/network/admin_api/command_registry.h new file mode 100644 index 000000000..d1040ff31 --- /dev/null +++ b/src/engine/network/admin_api/command_registry.h @@ -0,0 +1,115 @@ +/** + \file command_registry.h + \brief Command dispatch registry for Admin API + \authors Bylins team + \date 2026-02-13 + + Provides a hash table-based command registry to replace the 24-item else-if chain + in admin_api_parse(). Improves dispatch performance from O(n) to O(1) and makes + it easier to add new commands. +*/ + +#ifndef BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_COMMAND_REGISTRY_H_ +#define BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_COMMAND_REGISTRY_H_ + +#include "admin_api_constants.h" +#include "json_helpers.h" + +#include +#include +#include + +// Forward declarations +struct DescriptorData; + +namespace admin_api { + +// ============================================================================ +// Command Handler Type +// ============================================================================ + +/** + * \brief Command handler function signature + * \param d Descriptor for the connection + * \param request JSON request object + * + * Handler is responsible for: + * - Extracting parameters from request (vnum, zone, etc.) + * - Calling appropriate CRUD handler + * - Sending response (success or error) + */ +using CommandHandler = std::function; + +// ============================================================================ +// Command Registry +// ============================================================================ + +/** + * \brief Command dispatch registry + * + * Replaces the 24-item else-if chain with O(1) hash table lookup. + * Commands are registered at initialization and dispatched based on + * command string from JSON request. + */ +class CommandRegistry +{ +public: + /** + * \brief Get the global registry instance + * \return Reference to singleton registry + */ + static CommandRegistry& Instance(); + + /** + * \brief Register a command handler + * \param command Command string (e.g., "get_mob") + * \param handler Handler function + */ + void Register(std::string_view command, CommandHandler handler); + + /** + * \brief Dispatch a command + * \param command Command string + * \param d Descriptor for the connection + * \param request JSON request object + * \return true if command was found and dispatched, false otherwise + */ + bool Dispatch(std::string_view command, DescriptorData* d, const nlohmann::json& request); + + /** + * \brief Check if command is registered + * \param command Command string + * \return true if command is registered + */ + bool IsRegistered(std::string_view command) const; + + /** + * \brief Get number of registered commands + * \return Command count + */ + size_t Count() const; + +private: + CommandRegistry() = default; + + // Command name Б├▓ handler function + std::unordered_map handlers_; +}; + +// ============================================================================ +// Registration Helpers +// ============================================================================ + +/** + * \brief Initialize and register all Admin API commands + * + * Called once at server startup to populate the command registry. + * Registers all 24 commands (auth, get_mob, update_mob, etc.) + */ +void InitializeCommandRegistry(); + +} // namespace admin_api + +#endif // BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_COMMAND_REGISTRY_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/crud_handlers.cpp b/src/engine/network/admin_api/crud_handlers.cpp new file mode 100644 index 000000000..11bb2844f --- /dev/null +++ b/src/engine/network/admin_api/crud_handlers.cpp @@ -0,0 +1,910 @@ +/** + \file crud_handlers.cpp + \brief CRUD operation handlers implementation + \authors Bylins team + \date 2026-02-13 +*/ + +#include "crud_handlers.h" +#include "serializers.h" +#include "parsers.h" +#include "json_helpers.h" +#include "../../../engine/network/descriptor_data.h" +#include "../../../engine/db/db.h" +#include "../../../engine/db/obj_prototypes.h" +#include "../../../engine/db/world_data_source_manager.h" +#include "../../../engine/db/global_objects.h" +#include "../../../engine/entities/zone.h" +#include "../../../engine/entities/room_data.h" +#include "../../../engine/scripting/dg_scripts.h" +#include "../../../engine/olc/olc.h" +#include "../../../utils/utils.h" +#include "../../../gameplay/core/constants.h" + +// External declarations +extern MobRnum top_of_mobt; +extern IndexData *mob_index; +extern CharData *mob_proto; +extern RoomRnum top_of_world; +extern TrgRnum top_of_trigt; +extern IndexData **trig_index; +extern DescriptorData *descriptor_list; + +// External OLC functions +extern void medit_save_internally(DescriptorData *d); +extern void oedit_save_internally(DescriptorData *d); +extern void redit_save_internally(DescriptorData *d); +extern void trigedit_save(DescriptorData *d); +extern void CopyRoom(RoomData *to, RoomData *from); + +// External admin_api helpers (from admin_api.cpp) +extern void admin_api_send_json(DescriptorData *d, const char *json_str); +extern void admin_api_send_error(DescriptorData *d, const char *error_message); + +namespace admin_api::handlers { + +using namespace admin_api::serializers; +using namespace admin_api::parsers; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +static TrgRnum find_trig_rnum(TrgVnum vnum) +{ + for (int rnum = 0; rnum < top_of_trigt; ++rnum) + { + if (trig_index[rnum] && trig_index[rnum]->vnum == vnum) + { + return rnum; + } + } + return -1; +} + +// ============================================================================ +// Response Helpers +// ============================================================================ + +void SendJsonResponse(DescriptorData* d, const json& response) +{ + admin_api_send_json(d, response.dump().c_str()); +} + +void SendErrorResponse(DescriptorData* d, const char* error_message) +{ + admin_api_send_error(d, error_message); +} + +// ============================================================================ +// Mob Handlers +// ============================================================================ + +void HandleGetMob(DescriptorData* d, int mob_vnum) +{ + MobRnum rnum = GetMobRnum(mob_vnum); + + if (rnum == kNobody) + { + SendErrorResponse(d, "Mob not found"); + return; + } + + // Use serializer to convert mob to JSON + json mob_data = SerializeMob(mob_proto[rnum], mob_vnum); + + json response; + response["status"] = "ok"; + response["mob"] = mob_data; + + SendJsonResponse(d, response); +} + +void HandleUpdateMob(DescriptorData* d, int mob_vnum, const char* json_data) +{ + MobRnum rnum = GetMobRnum(mob_vnum); + + if (rnum == kNobody) + { + SendErrorResponse(d, "Mob not found"); + return; + } + + try + { + json data = json::parse(json_data); + + // Get zone + int zone_vnum = mob_vnum / 100; + ZoneRnum zone_rnum = GetZoneRnum(zone_vnum); + if (zone_rnum < 0) + { + SendErrorResponse(d, "Zone not found for mob"); + return; + } + + // Create temporary OLC descriptor + auto* temp_d = new DescriptorData(); + temp_d->olc = new olc_data(); + temp_d->olc->number = mob_vnum; + temp_d->olc->zone_num = zone_rnum; + + // Create a copy of the mob for editing + temp_d->olc->mob = new CharData(mob_proto[rnum]); + temp_d->olc->mob->set_rnum(rnum); + + // Use parser to apply JSON updates + ParseMobUpdate(temp_d->olc->mob, data); + + // Save changes using OLC + medit_save_internally(temp_d); + + // Cleanup + delete temp_d->olc->mob; + delete temp_d->olc; + delete temp_d; + + // Return updated mob data + json response; + response["status"] = "ok"; + response["message"] = "Mob updated successfully"; + response["mob"] = SerializeMob(mob_proto[rnum], mob_vnum); + + SendJsonResponse(d, response); + } + catch (const json::parse_error&) + { + SendErrorResponse(d, "Invalid JSON format"); + } + catch (const std::exception& e) + { + SendErrorResponse(d, e.what()); + } +} + +// ============================================================================ +// Stub Handlers (for compilation - will be implemented in Stage 7) +// ============================================================================ + +void HandleListZones(DescriptorData* d) +{ + json response; + response["status"] = "ok"; + response["zones"] = json::array(); + + for (ZoneRnum zrn = 1; zrn < static_cast(zone_table.size()); ++zrn) + { + const ZoneData &zone = zone_table[zrn]; + json zone_obj; + zone_obj["vnum"] = zone.vnum; + zone_obj["name"] = admin_api::json::Koi8rToUtf8(zone.name); + zone_obj["level"] = zone.level; + if (!zone.author.empty()) + { + zone_obj["author"] = admin_api::json::Koi8rToUtf8(zone.author); + } + response["zones"].push_back(zone_obj); + } + + SendJsonResponse(d, response); +} + +void HandleGetZone(DescriptorData* d, int zone_vnum) +{ + ZoneRnum zrn = GetZoneRnum(zone_vnum); + if (zrn == -1) + { + SendErrorResponse(d, "Zone not found"); + return; + } + + const ZoneData &zone = zone_table[zrn]; + json zone_data = SerializeZoneData(zone, zone_vnum); + + json response; + response["status"] = "ok"; + response["zone"] = zone_data; + + SendJsonResponse(d, response); +} + +void HandleUpdateZone(DescriptorData* d, int zone_vnum, const char* json_data) +{ + ZoneRnum zrn = GetZoneRnum(zone_vnum); + if (zrn == -1) + { + SendErrorResponse(d, "Zone not found"); + return; + } + + try + { + json data = json::parse(json_data); + ZoneData* zone = &zone_table[zrn]; + + // Apply updates + ParseZoneUpdate(zone, data); + + // Note: changes are applied directly to zone_table in memory and intentionally bypass + // the OLC pipeline. OLC (zedit) also edits zone headers in memory only -- it defers + // disk writes to an explicit "save zone" command issued by the builder. The Admin API + // follows the same model: call the "save_zone" endpoint (or trigger OLC save) to + // persist changes to disk via the active IWorldDataSource. + + json response; + response["status"] = "ok"; + response["message"] = "Zone updated successfully"; + SendJsonResponse(d, response); + } + catch (const json::parse_error&) + { + SendErrorResponse(d, "Invalid JSON format"); + } + catch (const std::exception& e) + { + SendErrorResponse(d, e.what()); + } +} + +void HandleListMobs(DescriptorData* d, const char* zone_vnum_str) +{ + json response; + response["status"] = "ok"; + response["mobs"] = json::array(); + + int zone_vnum = atoi(zone_vnum_str); + ZoneRnum zone_rnum = GetZoneRnum(zone_vnum); + + if (zone_rnum < 0) + { + SendErrorResponse(d, "Zone not found"); + return; + } + + // Iterate through all mobs and find those in this zone + for (MobRnum i = 0; i <= top_of_mobt; ++i) + { + int mob_vnum = mob_index[i].vnum; + if (mob_vnum / 100 != zone_vnum) + { + continue; + } + + json mob_data; + mob_data["vnum"] = mob_vnum; + mob_data["aliases"] = admin_api::json::Koi8rToUtf8(mob_proto[i].get_npc_name()); + mob_data["short_desc"] = admin_api::json::Koi8rToUtf8(mob_proto[i].player_data.long_descr); + mob_data["level"] = mob_proto[i].GetLevel(); + + response["mobs"].push_back(mob_data); + } + + SendJsonResponse(d, response); +} + +void HandleCreateMob(DescriptorData* d, const char* json_data) +{ + // TODO: Implement using parsers + (void)json_data; + SendErrorResponse(d, "Not implemented yet"); +} + +void HandleDeleteMob(DescriptorData* d, int mob_vnum) +{ + // TODO: Implement + (void)mob_vnum; + SendErrorResponse(d, "Not implemented yet"); +} + +void HandleListObjects(DescriptorData* d, const char* zone_vnum_str) +{ + json response; + response["status"] = "ok"; + response["objects"] = json::array(); + + int zone_vnum = atoi(zone_vnum_str); + ZoneRnum zone_rnum = GetZoneRnum(zone_vnum); + + if (zone_rnum < 0) + { + SendErrorResponse(d, "Zone not found"); + return; + } + + // Iterate through all objects and find those in this zone + for (size_t i = 0; i < obj_proto.size(); ++i) + { + if (obj_proto.zone(i) != zone_rnum) + { + continue; + } + + const auto &obj = obj_proto[i]; + + json obj_data; + obj_data["vnum"] = obj->get_vnum(); + obj_data["aliases"] = admin_api::json::Koi8rToUtf8(obj->get_aliases()); + obj_data["short_desc"] = admin_api::json::Koi8rToUtf8(obj->get_short_description()); + obj_data["type"] = static_cast(obj->get_type()); + + response["objects"].push_back(obj_data); + } + + SendJsonResponse(d, response); +} + +void HandleGetObject(DescriptorData* d, int obj_vnum) +{ + ObjRnum rnum = GetObjRnum(obj_vnum); + + if (rnum < 0) + { + SendErrorResponse(d, "Object not found"); + return; + } + + const auto &obj = obj_proto[rnum]; + json obj_data = SerializeObject(*obj, obj_vnum); + + json response; + response["status"] = "ok"; + response["object"] = obj_data; + + SendJsonResponse(d, response); +} + +void HandleUpdateObject(DescriptorData* d, int obj_vnum, const char* json_data) +{ + ObjRnum rnum = GetObjRnum(obj_vnum); + if (rnum < 0) + { + SendErrorResponse(d, "Object not found"); + return; + } + + try + { + json data = json::parse(json_data); + + // Get zone + int zone_vnum = obj_vnum / 100; + ZoneRnum zone_rnum = GetZoneRnum(zone_vnum); + + // Create temporary OLC descriptor + auto *temp_d = new DescriptorData(); + temp_d->olc = new olc_data(); + temp_d->olc->number = obj_vnum; + temp_d->olc->zone_num = zone_rnum; + + // Create a copy of the object for editing + temp_d->olc->obj = new ObjData(*obj_proto[rnum]); + temp_d->olc->obj->set_rnum(rnum); + + // Use parser to apply JSON updates + ParseObjectUpdate(temp_d->olc->obj, data); + + // Save changes using OLC + oedit_save_internally(temp_d); + + // Cleanup + // NOTE: Do NOT delete temp_d->olc->obj - it was moved into obj_proto by oedit_save_internally + temp_d->olc->obj = nullptr; // Clear pointer to avoid accidental use + delete temp_d->olc; + delete temp_d; + + // Return updated object data + json response; + response["status"] = "ok"; + response["message"] = "Object updated successfully"; + response["object"] = SerializeObject(*obj_proto[rnum], obj_vnum); + + SendJsonResponse(d, response); + } + catch (const json::parse_error&) + { + SendErrorResponse(d, "Invalid JSON format"); + } + catch (const std::exception& e) + { + SendErrorResponse(d, e.what()); + } +} + +void HandleCreateObject(DescriptorData* d, const char* /* json_data */) +{ + SendErrorResponse(d, "Object creation not implemented - use in-game OLC"); +} + +void HandleDeleteObject(DescriptorData* d, int obj_vnum) +{ + (void)obj_vnum; + SendErrorResponse(d, "Object deletion via API disabled for safety. Use in-game OLC instead."); +} + +void HandleListRooms(DescriptorData* d, const char* zone_vnum_str) +{ + json response; + response["status"] = "ok"; + response["rooms"] = json::array(); + + int zone_vnum = atoi(zone_vnum_str); + ZoneRnum zone_rnum = GetZoneRnum(zone_vnum); + + if (zone_rnum < 0) + { + SendErrorResponse(d, "Zone not found"); + return; + } + + const ZoneData &zone = zone_table[zone_rnum]; + RoomRnum first_room = zone.RnumRoomsLocation.first; + RoomRnum last_room = zone.RnumRoomsLocation.second; + + for (RoomRnum rnum = first_room; rnum <= last_room && rnum <= top_of_world; ++rnum) + { + if (world[rnum]->vnum < 0) continue; + + json room_obj; + room_obj["vnum"] = world[rnum]->vnum; + room_obj["name"] = admin_api::json::Koi8rToUtf8(world[rnum]->name); + + response["rooms"].push_back(room_obj); + } + + SendJsonResponse(d, response); +} + +void HandleGetRoom(DescriptorData* d, int room_vnum) +{ + RoomRnum rnum = GetRoomRnum(room_vnum); + + if (rnum == kNowhere) + { + SendErrorResponse(d, "Room not found"); + return; + } + + RoomData *room = world[rnum]; + json room_data = SerializeRoom(*room, room_vnum); + + json response; + response["status"] = "ok"; + response["room"] = room_data; + + SendJsonResponse(d, response); +} + +void HandleUpdateRoom(DescriptorData* d, int room_vnum, const char* json_data) +{ + RoomRnum rnum = GetRoomRnum(room_vnum); + if (rnum == kNowhere) + { + SendErrorResponse(d, "Room not found"); + return; + } + + try + { + json data = json::parse(json_data); + ZoneRnum zone_rnum = world[rnum]->zone_rn; + + // Create temporary OLC descriptor + auto *temp_d = new DescriptorData(); + temp_d->olc = new olc_data(); + temp_d->olc->number = room_vnum; + temp_d->olc->zone_num = zone_rnum; + + // Create room copy for editing + RoomData *room = new RoomData; + CopyRoom(room, world[rnum]); + // Get description from global storage + room->temp_description = nullptr; + if (world[rnum]->description_num > 0) + { + room->temp_description = str_dup(GlobalObjects::descriptions().get(world[rnum]->description_num).c_str()); + } + temp_d->olc->room = room; + + // Apply JSON fields + ParseRoomUpdate(room, data); + + // Save via OLC + redit_save_internally(temp_d); + + // Cleanup + delete temp_d->olc; + delete temp_d; + + json response; + response["status"] = "ok"; + response["message"] = "Room updated successfully"; + SendJsonResponse(d, response); + } + catch (const json::parse_error&) + { + SendErrorResponse(d, "Invalid JSON format"); + } + catch (const std::exception& e) + { + SendErrorResponse(d, e.what()); + } +} + +void HandleCreateRoom(DescriptorData* d, const char* json_data) +{ + (void)json_data; + SendErrorResponse(d, "Room creation not implemented - use in-game OLC"); +} + +void HandleDeleteRoom(DescriptorData* d, int room_vnum) +{ + (void)room_vnum; + SendErrorResponse(d, "Room deletion via API disabled for safety. Use in-game OLC instead."); +} + +void HandleListTriggers(DescriptorData* d, const char* zone_vnum_str) +{ + json response; + response["status"] = "ok"; + response["triggers"] = json::array(); + + int zone_vnum = atoi(zone_vnum_str); + + // Iterate through all triggers and filter by zone + for (int rnum = 0; rnum < top_of_trigt; ++rnum) + { + if (!trig_index[rnum]) continue; + + int trig_vnum = trig_index[rnum]->vnum; + // Check if trigger belongs to the zone + int trig_zone = trig_vnum / 100; + if (trig_zone != zone_vnum) continue; + + if (!trig_index[rnum]->proto) + { + continue; + } + + Trigger *trig = trig_index[rnum]->proto; + + json trig_data; + trig_data["vnum"] = trig_vnum; + trig_data["name"] = admin_api::json::Koi8rToUtf8(trig->get_name()); + trig_data["attach_type"] = static_cast(trig->get_attach_type()); + + response["triggers"].push_back(trig_data); + } + + SendJsonResponse(d, response); +} + +void HandleGetTrigger(DescriptorData* d, int trig_vnum) +{ + TrgRnum rnum = find_trig_rnum(trig_vnum); + + if (rnum < 0 || !trig_index[rnum] || !trig_index[rnum]->proto) + { + SendErrorResponse(d, "Trigger not found"); + return; + } + + Trigger *trig = trig_index[rnum]->proto; + json trig_data = SerializeTrigger(*trig, trig_vnum); + + json response; + response["status"] = "ok"; + response["trigger"] = trig_data; + + SendJsonResponse(d, response); +} + +void HandleUpdateTrigger(DescriptorData* d, int trig_vnum, const char* json_data) +{ + using admin_api::json::Utf8ToKoi8r; + using admin_api::json::Koi8rToUtf8; + + int rnum = find_trig_rnum(trig_vnum); + if (rnum < 0 || !trig_index[rnum] || !trig_index[rnum]->proto) + { + SendErrorResponse(d, "Trigger not found"); + return; + } + + try + { + json data = json::parse(json_data); + int zone_vnum = trig_vnum / 100; + ZoneRnum zone_rnum = GetZoneRnum(zone_vnum); + + // Create temporary OLC descriptor + auto *temp_d = new DescriptorData(); + temp_d->olc = new olc_data(); + // Initialize output buffer to prevent crashes + temp_d->output = temp_d->small_outbuf; + temp_d->bufspace = kSmallBufsize - 1; + *temp_d->output = '\0'; + temp_d->bufptr = 0; + temp_d->olc->number = trig_vnum; + temp_d->olc->zone_num = zone_rnum; + + // Create dummy character to prevent OLC crashes when sending menus + temp_d->character = std::make_shared(); + temp_d->character->desc = temp_d; + + // Create trigger copy for editing + Trigger *trig = new Trigger(*trig_index[rnum]->proto); + // Ensure cmdlist is initialized (may be nullptr after copy if proto was empty) + if (!trig->cmdlist) + { + trig->cmdlist.reset(new cmdlist_element::shared_ptr()); + } + temp_d->olc->trig = trig; + + // Apply JSON fields via parser + ParseTriggerUpdate(trig, data); + + // Script (OLC format: single text block with \n separators) + if (data.contains("script")) + { + std::string script = Utf8ToKoi8r(data["script"].get()); + temp_d->olc->storage = str_dup(script.c_str()); + } + else + { + // Build script from existing cmdlist if not provided + std::string script; + if (trig->cmdlist) + { + for (auto cmd = *trig->cmdlist; cmd; cmd = cmd->next) + { + if (!script.empty()) script += "\n"; + script += cmd->cmd; + } + } + temp_d->olc->storage = str_dup(script.c_str()); + } + + // Save via OLC (compiles script, updates proto, updates live triggers, saves to disk) + trigedit_save(temp_d); + + // Clean up + if (temp_d->olc->storage) + { + free(temp_d->olc->storage); + } + delete temp_d->olc; + delete temp_d; + + json response; + response["status"] = "ok"; + response["message"] = "Trigger updated successfully via OLC"; + SendJsonResponse(d, response); + } + catch (const json::parse_error&) + { + SendErrorResponse(d, "Invalid JSON format"); + } + catch (const std::exception& e) + { + SendErrorResponse(d, (std::string("Update failed: ") + e.what()).c_str()); + } +} + +void HandleCreateTrigger(DescriptorData* d, int zone_vnum, const char* json_data) +{ + using admin_api::json::Utf8ToKoi8r; + using admin_api::json::Koi8rToUtf8; + + try + { + // Parse data directly (zone comes as parameter) + json data = json::parse(json_data); + + if (zone_vnum < 0) + { + SendErrorResponse(d, "Zone vnum required"); + return; + } + + ZoneRnum zone_rnum = GetZoneRnum(zone_vnum); + if (zone_rnum < 0) + { + SendErrorResponse(d, "Zone not found"); + return; + } + + // Find available vnum + int vnum = data.value("vnum", -1); + if (vnum < 0) + { + vnum = zone_vnum * 100; + while (vnum < (zone_vnum + 1) * 100 && find_trig_rnum(vnum) >= 0) + { + ++vnum; + } + if (vnum >= (zone_vnum + 1) * 100) + { + SendErrorResponse(d, "No available vnums in zone range"); + return; + } + } + else + { + if (vnum / 100 != zone_vnum) + { + SendErrorResponse(d, "Vnum must be in zone range"); + return; + } + if (find_trig_rnum(vnum) >= 0) + { + SendErrorResponse(d, "Vnum already in use"); + return; + } + } + + // Create temporary OLC descriptor + auto *temp_d = new DescriptorData(); + temp_d->olc = new olc_data(); + // Initialize output buffer to prevent crashes + temp_d->output = temp_d->small_outbuf; + temp_d->bufspace = kSmallBufsize - 1; + *temp_d->output = '\0'; + temp_d->bufptr = 0; + temp_d->olc->number = vnum; + temp_d->olc->zone_num = zone_rnum; + + // Create dummy character to prevent OLC crashes when sending menus + temp_d->character = std::make_shared(); + temp_d->character->desc = temp_d; + + // Create trigger with OLC defaults (matching trigedit_setup_new) + Trigger *trig = new Trigger(-1, "new trigger", MTRIG_GREET); + trig->narg = 100; + temp_d->olc->trig = trig; + + // Apply JSON fields + if (data.contains("name")) + { + trig->set_name(Utf8ToKoi8r(data["name"].get())); + } + if (data.contains("arglist")) + { + trig->arglist = Utf8ToKoi8r(data["arglist"].get()); + } + if (data.contains("narg")) + { + trig->narg = data["narg"].get(); + } + if (data.contains("trigger_type")) + { + trig->set_trigger_type(data["trigger_type"].get()); + } + if (data.contains("attach_type")) + { + trig->set_attach_type(static_cast(data["attach_type"].get())); + } + + // Script + std::string script; + if (data.contains("script")) + { + script = Utf8ToKoi8r(data["script"].get()); + } + else + { + script = "say My trigger commandlist is not complete!"; + } + temp_d->olc->storage = str_dup(script.c_str()); + + // Save via OLC (handles array expansion, indexing, disk save) + trigedit_save(temp_d); + + // Capture OLC output + std::string olc_output; + if (temp_d->output && temp_d->bufptr > 0) + { + olc_output = Koi8rToUtf8(std::string(temp_d->output, temp_d->bufptr)); + } + + // Clean up + if (temp_d->olc->storage) + { + free(temp_d->olc->storage); + } + delete temp_d->olc; + delete temp_d; + + json response; + response["status"] = "ok"; + response["message"] = "Trigger created successfully via OLC"; + response["vnum"] = vnum; + if (!olc_output.empty()) + { + response["olc_output"] = olc_output; + } + SendJsonResponse(d, response); + } + catch (const json::parse_error&) + { + SendErrorResponse(d, "Invalid JSON format"); + } + catch (const std::exception& e) + { + SendErrorResponse(d, (std::string("Create failed: ") + e.what()).c_str()); + } +} + +void HandleDeleteTrigger(DescriptorData* d, int trig_vnum) +{ + (void)trig_vnum; + SendErrorResponse(d, "Trigger deletion via API disabled for safety. Use in-game OLC instead."); +} + +void HandleGetStats(DescriptorData* d) +{ + json response; + response["status"] = "ok"; + + // Count zones (skip zone_table[0]) + response["zones"] = zone_table.size() - 1; + + // Count mobs + response["mobs"] = top_of_mobt + 1; + + // Count objects + response["objects"] = obj_proto.size(); + + // Count rooms + response["rooms"] = world.size(); + + // Count triggers + response["triggers"] = top_of_trigt + 1; + + SendJsonResponse(d, response); +} + +void HandleGetPlayers(DescriptorData* d) +{ + json response; + response["status"] = "ok"; + response["players"] = json::array(); + + // Iterate through all descriptors + for (DescriptorData *desc = descriptor_list; desc; desc = desc->next) + { + // Skip non-playing connections + bool is_playing = (desc->state == EConState::kPlaying); + bool is_olc = (desc->state == EConState::kOedit || desc->state == EConState::kRedit || + desc->state == EConState::kZedit || desc->state == EConState::kMedit || + desc->state == EConState::kTrigedit || desc->state == EConState::kMredit || + desc->state == EConState::kSedit); + if (!is_playing && !is_olc) + { + continue; + } + + CharData *ch = desc->character.get(); + if (!ch) + { + continue; + } + + json player; + player["name"] = admin_api::json::Koi8rToUtf8(ch->get_name().c_str()); + player["level"] = ch->GetLevel(); + player["remort"] = ch->get_remort(); + player["class"] = admin_api::json::Koi8rToUtf8(MUD::Class(ch->GetClass()).GetName().c_str()); + player["room"] = GET_ROOM_VNUM(ch->in_room); + player["is_immortal"] = (ch->GetLevel() >= kLvlImmortal); + + response["players"].push_back(player); + } + + response["count"] = response["players"].size(); + + SendJsonResponse(d, response); +} + +} // namespace admin_api::handlers + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/crud_handlers.h b/src/engine/network/admin_api/crud_handlers.h new file mode 100644 index 000000000..a6b6fbeb3 --- /dev/null +++ b/src/engine/network/admin_api/crud_handlers.h @@ -0,0 +1,248 @@ +/** + \file crud_handlers.h + \brief CRUD operation handlers for Admin API + \authors Bylins team + \date 2026-02-13 + + Provides high-level CRUD handlers that combine serializers, parsers, + and OLC operations. These handlers replace the individual admin_api_* + functions and significantly reduce code duplication. +*/ + +#ifndef BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_CRUD_HANDLERS_H_ +#define BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_CRUD_HANDLERS_H_ + +#include "json_helpers.h" + +// Forward declarations +struct DescriptorData; + +namespace admin_api::handlers { + +using json = nlohmann::json; + +// ============================================================================ +// Response Helpers +// ============================================================================ + +/** + * \brief Send JSON response to descriptor + * \param d Descriptor to send to + * \param response JSON object to send + */ +void SendJsonResponse(DescriptorData* d, const json& response); + +/** + * \brief Send error response to descriptor + * \param d Descriptor to send to + * \param error_message Error message + */ +void SendErrorResponse(DescriptorData* d, const char* error_message); + +// ============================================================================ +// Zone Handlers +// ============================================================================ + +/** + * \brief List all zones + * \param d Descriptor to send response to + */ +void HandleListZones(DescriptorData* d); + +/** + * \brief Get zone details + * \param d Descriptor to send response to + * \param zone_vnum Zone virtual number + */ +void HandleGetZone(DescriptorData* d, int zone_vnum); + +/** + * \brief Update zone + * \param d Descriptor to send response to + * \param zone_vnum Zone virtual number + * \param json_data JSON update data + */ +void HandleUpdateZone(DescriptorData* d, int zone_vnum, const char* json_data); + +// ============================================================================ +// Mob Handlers +// ============================================================================ + +/** + * \brief List mobs in a zone + * \param d Descriptor to send response to + * \param zone_name Zone name (empty for all zones) + */ +void HandleListMobs(DescriptorData* d, const char* zone_name); + +/** + * \brief Get mob details + * \param d Descriptor to send response to + * \param mob_vnum Mob virtual number + */ +void HandleGetMob(DescriptorData* d, int mob_vnum); + +/** + * \brief Update mob + * \param d Descriptor to send response to + * \param mob_vnum Mob virtual number + * \param json_data JSON update data + */ +void HandleUpdateMob(DescriptorData* d, int mob_vnum, const char* json_data); + +/** + * \brief Create new mob + * \param d Descriptor to send response to + * \param json_data JSON mob data + */ +void HandleCreateMob(DescriptorData* d, const char* json_data); + +/** + * \brief Delete mob + * \param d Descriptor to send response to + * \param mob_vnum Mob virtual number + */ +void HandleDeleteMob(DescriptorData* d, int mob_vnum); + +// ============================================================================ +// Object Handlers +// ============================================================================ + +/** + * \brief List objects in a zone + * \param d Descriptor to send response to + * \param zone_name Zone name (empty for all zones) + */ +void HandleListObjects(DescriptorData* d, const char* zone_name); + +/** + * \brief Get object details + * \param d Descriptor to send response to + * \param obj_vnum Object virtual number + */ +void HandleGetObject(DescriptorData* d, int obj_vnum); + +/** + * \brief Update object + * \param d Descriptor to send response to + * \param obj_vnum Object virtual number + * \param json_data JSON update data + */ +void HandleUpdateObject(DescriptorData* d, int obj_vnum, const char* json_data); + +/** + * \brief Create new object + * \param d Descriptor to send response to + * \param json_data JSON object data + */ +void HandleCreateObject(DescriptorData* d, const char* json_data); + +/** + * \brief Delete object + * \param d Descriptor to send response to + * \param obj_vnum Object virtual number + */ +void HandleDeleteObject(DescriptorData* d, int obj_vnum); + +// ============================================================================ +// Room Handlers +// ============================================================================ + +/** + * \brief List rooms in a zone + * \param d Descriptor to send response to + * \param zone_name Zone name (empty for all zones) + */ +void HandleListRooms(DescriptorData* d, const char* zone_name); + +/** + * \brief Get room details + * \param d Descriptor to send response to + * \param room_vnum Room virtual number + */ +void HandleGetRoom(DescriptorData* d, int room_vnum); + +/** + * \brief Update room + * \param d Descriptor to send response to + * \param room_vnum Room virtual number + * \param json_data JSON update data + */ +void HandleUpdateRoom(DescriptorData* d, int room_vnum, const char* json_data); + +/** + * \brief Create new room + * \param d Descriptor to send response to + * \param json_data JSON room data + */ +void HandleCreateRoom(DescriptorData* d, const char* json_data); + +/** + * \brief Delete room + * \param d Descriptor to send response to + * \param room_vnum Room virtual number + */ +void HandleDeleteRoom(DescriptorData* d, int room_vnum); + +// ============================================================================ +// Trigger Handlers +// ============================================================================ + +/** + * \brief List triggers in a zone + * \param d Descriptor to send response to + * \param zone_name Zone name (empty for all zones) + */ +void HandleListTriggers(DescriptorData* d, const char* zone_name); + +/** + * \brief Get trigger details + * \param d Descriptor to send response to + * \param trig_vnum Trigger virtual number + */ +void HandleGetTrigger(DescriptorData* d, int trig_vnum); + +/** + * \brief Update trigger + * \param d Descriptor to send response to + * \param trig_vnum Trigger virtual number + * \param json_data JSON update data + */ +void HandleUpdateTrigger(DescriptorData* d, int trig_vnum, const char* json_data); + +/** + * \brief Create new trigger + * \param d Descriptor to send response to + * \param zone_vnum Zone virtual number + * \param json_data JSON trigger data + */ +void HandleCreateTrigger(DescriptorData* d, int zone_vnum, const char* json_data); + +/** + * \brief Delete trigger + * \param d Descriptor to send response to + * \param trig_vnum Trigger virtual number + */ +void HandleDeleteTrigger(DescriptorData* d, int trig_vnum); + +// ============================================================================ +// Statistics and Players +// ============================================================================ + +/** + * \brief Get server statistics + * \param d Descriptor to send response to + */ +void HandleGetStats(DescriptorData* d); + +/** + * \brief Get online players + * \param d Descriptor to send response to + */ +void HandleGetPlayers(DescriptorData* d); + +} // namespace admin_api::handlers + +#endif // BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_CRUD_HANDLERS_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/json_helpers.cpp b/src/engine/network/admin_api/json_helpers.cpp new file mode 100644 index 000000000..5472ad0c6 --- /dev/null +++ b/src/engine/network/admin_api/json_helpers.cpp @@ -0,0 +1,105 @@ +/** + \file json_helpers.cpp + \brief JSON parsing and serialization utilities implementation + \authors Bylins team + \date 2026-02-13 +*/ + +#include "json_helpers.h" +#include "../../../engine/structs/structs.h" +#include "../../../utils/utils.h" + +namespace admin_api::json { + +// ============================================================================ +// Flag Serialization +// ============================================================================ + +json SerializeFlags(const FlagData& flagset) +{ + json flags_array = json::array(); + + // Iterate through all 4 planes + for (size_t plane = 0; plane < 4; ++plane) + { + Bitvector plane_bits = flagset.get_plane(plane); + + // Check each bit in the plane (30 bits per plane) + for (size_t bit = 0; bit < 30; ++bit) + { + if (plane_bits & (1U << bit)) + { + // Encode as: (plane << 30) | (1 << bit) + unsigned int flag_value = (static_cast(plane) << 30) | (1U << bit); + flags_array.push_back(static_cast(flag_value)); + } + } + } + + return flags_array; +} + +json SerializeBitvector(Bitvector bits, size_t plane) +{ + json flags_array = json::array(); + + for (size_t bit = 0; bit < 30; ++bit) + { + if (bits & (1U << bit)) + { + unsigned int flag_value = (static_cast(plane) << 30) | (1U << bit); + flags_array.push_back(static_cast(flag_value)); + } + } + + return flags_array; +} + +// ============================================================================ +// String Conversion (KOI8-R <-> UTF-8) +// ============================================================================ + +std::string Koi8rToUtf8(const char* koi8r) +{ + if (!koi8r) + { + return ""; + } + + char utf8_buf[kMaxStringLength]; + koi_to_utf8(const_cast(koi8r), utf8_buf); + return std::string(utf8_buf); +} + +std::string Koi8rToUtf8(const std::string& koi8r) +{ + char utf8_buf[kMaxSockBuf * 6]; + char koi8r_buf[kMaxSockBuf * 6]; + + // Initialize buffers to prevent returning garbage if conversion fails + utf8_buf[0] = '\0'; + + strncpy(koi8r_buf, koi8r.c_str(), sizeof(koi8r_buf) - 1); + koi8r_buf[sizeof(koi8r_buf) - 1] = '\0'; + + koi_to_utf8(koi8r_buf, utf8_buf); + + return std::string(utf8_buf); +} + +std::string Utf8ToKoi8r(const std::string& utf8) +{ + char koi8r_buf[kMaxSockBuf * 6]; + char utf8_buf[kMaxSockBuf * 6]; + + strncpy(utf8_buf, utf8.c_str(), sizeof(utf8_buf) - 1); + utf8_buf[sizeof(utf8_buf) - 1] = '\0'; + + utf8_to_koi(utf8_buf, koi8r_buf); + + return std::string(koi8r_buf); +} + +} // namespace admin_api::json + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/json_helpers.h b/src/engine/network/admin_api/json_helpers.h new file mode 100644 index 000000000..062c301e8 --- /dev/null +++ b/src/engine/network/admin_api/json_helpers.h @@ -0,0 +1,211 @@ +/** + \file json_helpers.h + \brief JSON parsing and serialization utilities for Admin API + \authors Bylins team + \date 2026-02-13 + + Provides reusable JSON helper functions to eliminate code duplication + in Admin API CRUD operations. Handles type-safe field extraction, + flag parsing/serialization, and error handling. +*/ + +#ifndef BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_JSON_HELPERS_H_ +#define BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_JSON_HELPERS_H_ + +#include "../../../third_party_libs/nlohmann/json.hpp" +#include "../../../engine/structs/flag_data.h" +#include "../../../engine/structs/structs.h" + +#include +#include +#include + +namespace admin_api::json { + +using json = nlohmann::json; + +// ============================================================================ +// Safe JSON Field Extraction +// ============================================================================ + +/** + * \brief Safely extract a field from JSON with default value + * \tparam T Type of the field to extract + * \param j JSON object to extract from + * \param key Field name to extract + * \param default_val Default value if field is missing or wrong type + * \return Field value or default + * + * Usage: + * int level = GetField(data, "level", 1); + * std::string name = GetField(data, "name", std::string("")); + */ +template +T GetField(const json& j, const char* key, T default_val) +{ + if (!j.contains(key)) + { + return default_val; + } + + try + { + return j[key].get(); + } + catch (const json::type_error&) + { + return default_val; + } +} + +/** + * \brief Check if JSON contains a field of specific type + * \param j JSON object + * \param key Field name + * \return true if field exists and is an array + */ +inline bool HasArray(const json& j, const char* key) +{ + return j.contains(key) && j[key].is_array(); +} + +/** + * \brief Check if JSON contains a field of specific type + * \param j JSON object + * \param key Field name + * \return true if field exists and is an object + */ +inline bool HasObject(const json& j, const char* key) +{ + return j.contains(key) && j[key].is_object(); +} + +/** + * \brief Check if JSON contains a field of specific type + * \param j JSON object + * \param key Field name + * \return true if field exists and is a string + */ +inline bool HasString(const json& j, const char* key) +{ + return j.contains(key) && j[key].is_string(); +} + +/** + * \brief Check if JSON contains a field of specific type + * \param j JSON object + * \param key Field name + * \return true if field exists and is a number + */ +inline bool HasNumber(const json& j, const char* key) +{ + return j.contains(key) && j[key].is_number(); +} + +// ============================================================================ +// Flag Parsing and Serialization +// ============================================================================ + +/** + * \brief Parse flags from JSON array and apply to flagset + * \tparam FlagType Enum type for flags (EMobFlag, ENpcFlag, etc.) + * \param j JSON object containing the flags array + * \param key Field name for flags array + * \param flagset Flagset to apply flags to + * + * JSON format: "key": [flag1, flag2, ...] + * Flags are encoded as: (plane << 30) | (1 << bit) + * + * Usage: + * ParseFlags(data, "mob_flags", mob->char_specials.saved.act); + */ +template +void ParseFlags(const json& j, const char* key, FlagsetType& flagset) +{ + if (!HasArray(j, key)) + { + return; + } + + for (const auto& flag_val : j[key]) + { + if (flag_val.is_number_integer()) + { + flagset.set(static_cast(flag_val.get())); + } + } +} + +/** + * \brief Serialize flagset to JSON array + * \param flagset Flagset to serialize (FlagData with planes) + * \return JSON array of flag values + * + * Output format: [flag1, flag2, ...] + * Flags are encoded as: (plane << 30) | (1 << bit) + * + * Usage: + * json arr = SerializeFlags(mob.char_specials.saved.act); + */ +json SerializeFlags(const FlagData& flagset); + +/** + * \brief Serialize single Bitvector to JSON array + * \param bits Bitvector to serialize + * \param plane Plane number (0-3) for encoding + * \return JSON array of flag values + */ +json SerializeBitvector(Bitvector bits, size_t plane); + +// ============================================================================ +// Nested Object Parsing +// ============================================================================ + +/** + * \brief Extract nested JSON object with validation + * \param j Parent JSON object + * \param key Field name for nested object + * \return Optional containing nested object if valid, nullopt otherwise + * + * Usage: + * if (auto stats = ParseNested(data, "stats")) { + * int str = GetField(*stats, "strength", 10); + * } + */ +inline std::optional ParseNested(const json& j, const char* key) +{ + if (!HasObject(j, key)) + { + return std::nullopt; + } + return j[key]; +} + +// ============================================================================ +// String Conversion Helpers (KOI8-R Б├■ UTF-8) +// ============================================================================ + +/** + * \brief Convert KOI8-R string to UTF-8 for JSON + * \param koi8r KOI8-R encoded string + * \return UTF-8 encoded string + * + * Used when serializing entity data to JSON (which requires UTF-8) + */ +std::string Koi8rToUtf8(const char* koi8r); +std::string Koi8rToUtf8(const std::string& koi8r); + +/** + * \brief Convert UTF-8 string to KOI8-R for game data + * \param utf8 UTF-8 encoded string (from JSON) + * \return KOI8-R encoded string + * + * Used when parsing JSON data to apply to game entities + */ +std::string Utf8ToKoi8r(const std::string& utf8); + +} // namespace admin_api::json + +#endif // BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_JSON_HELPERS_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/parsers.cpp b/src/engine/network/admin_api/parsers.cpp new file mode 100644 index 000000000..89c0ec919 --- /dev/null +++ b/src/engine/network/admin_api/parsers.cpp @@ -0,0 +1,876 @@ +/** + \file parsers.cpp + \brief JSON to Entity parsing implementation + \authors Bylins team + \date 2026-02-13 +*/ + +#include "parsers.h" +#include "json_helpers.h" +#include "../../../engine/structs/structs.h" +#include "../../../gameplay/mechanics/dead_load.h" +#include "../../../gameplay/core/constants.h" + +namespace admin_api::parsers { + +using namespace admin_api::json; + +// ============================================================================ +// Mob Parsing +// ============================================================================ + +void ParseMobUpdate(CharData* mob, const nlohmann::json& data) +{ + if (!mob) + { + return; + } + + // === NAMES === + if (HasObject(data, "names")) + { + const auto& names = data["names"]; + if (HasString(names, "aliases")) + { + mob->set_npc_name(Utf8ToKoi8r(names["aliases"].get())); + } + if (HasString(names, "nominative")) + { + mob->player_data.PNames[ECase::kNom] = Utf8ToKoi8r(names["nominative"].get()); + } + if (HasString(names, "genitive")) + { + mob->player_data.PNames[ECase::kGen] = Utf8ToKoi8r(names["genitive"].get()); + } + if (HasString(names, "dative")) + { + mob->player_data.PNames[ECase::kDat] = Utf8ToKoi8r(names["dative"].get()); + } + if (HasString(names, "accusative")) + { + mob->player_data.PNames[ECase::kAcc] = Utf8ToKoi8r(names["accusative"].get()); + } + if (HasString(names, "instrumental")) + { + mob->player_data.PNames[ECase::kIns] = Utf8ToKoi8r(names["instrumental"].get()); + } + if (HasString(names, "prepositional")) + { + mob->player_data.PNames[ECase::kPre] = Utf8ToKoi8r(names["prepositional"].get()); + } + } + + // === DESCRIPTIONS === + if (HasObject(data, "descriptions")) + { + const auto& descriptions = data["descriptions"]; + if (HasString(descriptions, "short_desc")) + { + mob->player_data.long_descr = Utf8ToKoi8r(descriptions["short_desc"].get()); + } + if (HasString(descriptions, "long_desc")) + { + mob->player_data.description = Utf8ToKoi8r(descriptions["long_desc"].get()); + } + } + + // === STATS === + if (HasObject(data, "stats")) + { + const auto& stats = data["stats"]; + + if (HasNumber(stats, "level")) + { + mob->set_level(stats["level"].get()); + } + if (HasNumber(stats, "armor")) + { + mob->real_abils.armor = stats["armor"].get(); + } + if (HasNumber(stats, "hitroll_penalty")) + { + mob->real_abils.hitroll = stats["hitroll_penalty"].get(); + } + if (HasNumber(stats, "sex")) + { + mob->set_sex(static_cast(stats["sex"].get())); + } + if (HasNumber(stats, "race")) + { + mob->set_race(stats["race"].get()); + } + if (HasNumber(stats, "alignment")) + { + mob->char_specials.saved.alignment = stats["alignment"].get(); + } + + // HP (dice format) + if (HasObject(stats, "hp")) + { + const auto& hp = stats["hp"]; + if (HasNumber(hp, "dice_count")) + { + mob->mem_queue.total = hp["dice_count"].get(); + } + if (HasNumber(hp, "dice_size")) + { + mob->mem_queue.stored = hp["dice_size"].get(); + } + if (HasNumber(hp, "bonus")) + { + mob->set_hit(hp["bonus"].get()); + } + } + + // Damage (dice format) + if (HasObject(stats, "damage")) + { + const auto& dmg = stats["damage"]; + if (HasNumber(dmg, "dice_count")) + { + mob->mob_specials.damnodice = dmg["dice_count"].get(); + } + if (HasNumber(dmg, "dice_size")) + { + mob->mob_specials.damsizedice = dmg["dice_size"].get(); + } + if (HasNumber(dmg, "bonus")) + { + mob->real_abils.damroll = dmg["bonus"].get(); + } + } + } + + // === ABILITIES === + if (HasObject(data, "abilities")) + { + const auto& abilities = data["abilities"]; + if (HasNumber(abilities, "strength")) + { + mob->set_str(abilities["strength"].get()); + } + if (HasNumber(abilities, "dexterity")) + { + mob->set_dex(abilities["dexterity"].get()); + } + if (HasNumber(abilities, "constitution")) + { + mob->set_con(abilities["constitution"].get()); + } + if (HasNumber(abilities, "intelligence")) + { + mob->set_int(abilities["intelligence"].get()); + } + if (HasNumber(abilities, "wisdom")) + { + mob->set_wis(abilities["wisdom"].get()); + } + if (HasNumber(abilities, "charisma")) + { + mob->set_cha(abilities["charisma"].get()); + } + } + + // === PHYSICAL CHARACTERISTICS === + if (HasObject(data, "physical")) + { + const auto& physical = data["physical"]; + if (HasNumber(physical, "height")) + { + GET_HEIGHT(mob) = static_cast(physical["height"].get()); + } + if (HasNumber(physical, "weight")) + { + GET_WEIGHT(mob) = static_cast(physical["weight"].get()); + } + if (HasNumber(physical, "size")) + { + GET_SIZE(mob) = static_cast(physical["size"].get()); + } + if (HasNumber(physical, "extra_attack")) + { + mob->mob_specials.extra_attack = static_cast(physical["extra_attack"].get()); + } + if (HasNumber(physical, "like_work")) + { + mob->mob_specials.like_work = static_cast(physical["like_work"].get()); + } + if (HasNumber(physical, "maxfactor")) + { + mob->mob_specials.MaxFactor = physical["maxfactor"].get(); + } + if (HasNumber(physical, "remort")) + { + mob->set_remort(physical["remort"].get()); + } + } + + // === DEATH LOAD === + if (HasArray(data, "death_load")) + { + mob->dl_list.clear(); + for (const auto& dl_item : data["death_load"]) + { + dead_load::LoadingItem item; + item.obj_vnum = GetField(dl_item, "obj_vnum", 0); + item.load_prob = GetField(dl_item, "load_prob", 0); + item.load_type = GetField(dl_item, "load_type", 0); + item.spec_param = GetField(dl_item, "spec_param", 0); + if (item.obj_vnum > 0) + { + mob->dl_list.push_back(item); + } + } + } + + // === NPC ROLES === + if (HasArray(data, "roles")) + { + CharData::role_t new_roles; + for (const auto& role_idx : data["roles"]) + { + if (role_idx.is_number_unsigned()) + { + unsigned idx = role_idx.get(); + if (idx < new_roles.size()) + { + new_roles.set(idx); + } + } + } + mob->set_role(new_roles); + } + + // === RESISTANCES === + if (HasObject(data, "resistances")) + { + const auto& resistances = data["resistances"]; + if (HasNumber(resistances, "fire")) + { + mob->add_abils.apply_resistance[to_underlying(EResist::kFire)] = resistances["fire"].get(); + } + if (HasNumber(resistances, "air")) + { + mob->add_abils.apply_resistance[to_underlying(EResist::kAir)] = resistances["air"].get(); + } + if (HasNumber(resistances, "water")) + { + mob->add_abils.apply_resistance[to_underlying(EResist::kWater)] = resistances["water"].get(); + } + if (HasNumber(resistances, "earth")) + { + mob->add_abils.apply_resistance[to_underlying(EResist::kEarth)] = resistances["earth"].get(); + } + if (HasNumber(resistances, "vitality")) + { + mob->add_abils.apply_resistance[to_underlying(EResist::kVitality)] = resistances["vitality"].get(); + } + if (HasNumber(resistances, "mind")) + { + mob->add_abils.apply_resistance[to_underlying(EResist::kMind)] = resistances["mind"].get(); + } + if (HasNumber(resistances, "immunity")) + { + mob->add_abils.apply_resistance[to_underlying(EResist::kImmunity)] = resistances["immunity"].get(); + } + } + + // === SAVINGS === + if (HasObject(data, "savings")) + { + const auto& savings = data["savings"]; + if (HasNumber(savings, "will")) + { + mob->add_abils.apply_saving_throw[to_underlying(ESaving::kWill)] = savings["will"].get(); + } + if (HasNumber(savings, "stability")) + { + mob->add_abils.apply_saving_throw[to_underlying(ESaving::kStability)] = savings["stability"].get(); + } + if (HasNumber(savings, "reflex")) + { + mob->add_abils.apply_saving_throw[to_underlying(ESaving::kReflex)] = savings["reflex"].get(); + } + } + + // === POSITION === + if (HasObject(data, "position")) + { + const auto& position = data["position"]; + if (HasNumber(position, "default_position")) + { + mob->mob_specials.default_pos = static_cast(position["default_position"].get()); + } + if (HasNumber(position, "load_position")) + { + mob->SetPosition(static_cast(position["load_position"].get())); + } + } + + // === BEHAVIOR === + if (HasObject(data, "behavior")) + { + const auto& behavior = data["behavior"]; + if (HasNumber(behavior, "class")) + { + mob->set_class(static_cast(behavior["class"].get())); + } + if (HasNumber(behavior, "attack_type")) + { + mob->mob_specials.attack_type = behavior["attack_type"].get(); + } + + // Gold (dice format) + if (HasObject(behavior, "gold")) + { + const auto& gold = behavior["gold"]; + if (HasNumber(gold, "dice_count")) + { + mob->mob_specials.GoldNoDs = gold["dice_count"].get(); + } + if (HasNumber(gold, "dice_size")) + { + mob->mob_specials.GoldSiDs = gold["dice_size"].get(); + } + } + + // Helpers (array of mob vnums) - not implemented yet + if (HasArray(behavior, "helpers")) + { + // TODO: Parse helpers array and update mob->mob_specials.helper + } + } + + // === FLAGS === + if (HasObject(data, "flags")) + { + const auto& flags = data["flags"]; + + // mob_flags array + ParseFlags(flags, "mob_flags", mob->char_specials.saved.act); + + // npc_flags array + ParseFlags(flags, "npc_flags", mob->mob_specials.npc_flags); + + // affect_flags array + ParseFlags(flags, "affect_flags", mob->char_specials.saved.affected_by); + } + // Legacy flat flags array (backward compatibility) + else if (HasArray(data, "flags")) + { + ParseFlags(data, "flags", mob->char_specials.saved.act); + } + + // Legacy npc_flags array at root level + if (HasArray(data, "npc_flags")) + { + ParseFlags(data, "npc_flags", mob->mob_specials.npc_flags); + } + + // === TRIGGERS === + if (HasArray(data, "triggers")) + { + // Allocate proto_script if needed + if (!mob->proto_script) + { + mob->proto_script = std::make_shared>(); + } + mob->proto_script->clear(); + + for (const auto& trig_vnum : data["triggers"]) + { + if (trig_vnum.is_number_integer()) + { + mob->proto_script->push_back(trig_vnum.get()); + } + } + } +} + +// ============================================================================ +// Object Parsing (stub for now) +// ============================================================================ + +void ParseObjectUpdate(CObjectPrototype* obj, const nlohmann::json& data) +{ + if (!obj) + { + return; + } + + // Basic text fields + if (HasString(data, "aliases")) + { + obj->set_aliases(Utf8ToKoi8r(data["aliases"].get())); + } + if (HasString(data, "short_desc")) + { + obj->set_short_description(Utf8ToKoi8r(data["short_desc"].get())); + } + if (HasString(data, "description")) + { + obj->set_description(Utf8ToKoi8r(data["description"].get())); + } + if (HasString(data, "action_desc")) + { + obj->set_action_description(Utf8ToKoi8r(data["action_desc"].get())); + } + + // Grammatical cases (support both "pnames" and "names" keys) + auto names_data = data.contains("names") ? data["names"] : (data.contains("pnames") ? data["pnames"] : nlohmann::json{}); + if (!names_data.is_null()) + { + if (names_data.contains("nominative")) + { + obj->set_PName(ECase::kNom, Utf8ToKoi8r(names_data["nominative"].get())); + } + if (names_data.contains("genitive")) + { + obj->set_PName(ECase::kGen, Utf8ToKoi8r(names_data["genitive"].get())); + } + if (names_data.contains("dative")) + { + obj->set_PName(ECase::kDat, Utf8ToKoi8r(names_data["dative"].get())); + } + if (names_data.contains("accusative")) + { + obj->set_PName(ECase::kAcc, Utf8ToKoi8r(names_data["accusative"].get())); + } + if (names_data.contains("instrumental")) + { + obj->set_PName(ECase::kIns, Utf8ToKoi8r(names_data["instrumental"].get())); + } + if (names_data.contains("prepositional")) + { + obj->set_PName(ECase::kPre, Utf8ToKoi8r(names_data["prepositional"].get())); + } + } + + // Numeric properties + if (data.contains("weight")) + { + obj->set_weight(data["weight"].get()); + } + if (data.contains("cost")) + { + obj->set_cost(data["cost"].get()); + } + if (data.contains("rent_on")) + { + obj->set_rent_on(data["rent_on"].get()); + } + if (data.contains("rent_off")) + { + obj->set_rent_off(data["rent_off"].get()); + } + + // Type and material + if (data.contains("type")) + { + obj->set_type(static_cast(data["type"].get())); + } + if (data.contains("material")) + { + obj->set_material(static_cast(data["material"].get())); + } + + // Sex + if (data.contains("sex")) + { + obj->set_sex(static_cast(data["sex"].get())); + } + + // Level (maps to minimum_remorts to match serializer) + if (data.contains("level")) + { + obj->set_minimum_remorts(data["level"].get()); + } + + // Timer + if (data.contains("timer")) + { + obj->set_timer(data["timer"].get()); + } + + // Values (support both "values" array and "type_specific" object) + if (data.contains("type_specific") && data["type_specific"].is_object()) + { + auto &ts = data["type_specific"]; + if (ts.contains("value0")) + { + obj->set_val(0, ts["value0"].get()); + } + if (ts.contains("value1")) + { + obj->set_val(1, ts["value1"].get()); + } + if (ts.contains("value2")) + { + obj->set_val(2, ts["value2"].get()); + } + if (ts.contains("value3")) + { + obj->set_val(3, ts["value3"].get()); + } + } + else if (data.contains("values") && data["values"].is_array()) + { + for (size_t i = 0; i < 4 && i < data["values"].size(); ++i) + { + obj->set_val(i, data["values"][i].get()); + } + } + + // Extra flags (array of 4 plane values) + if (data.contains("extra_flags") && data["extra_flags"].is_array()) + { + FlagData flags; + for (size_t i = 0; i < 4 && i < data["extra_flags"].size(); ++i) + { + flags.set_plane(i, data["extra_flags"][i].get()); + } + obj->set_extra_flags(flags); + } + + // Wear flags (single integer bitmask) + if (data.contains("wear_flags")) + { + obj->set_wear_flags(data["wear_flags"].get()); + } + + // Anti flags (array of 4 plane values) + if (data.contains("anti_flags") && data["anti_flags"].is_array()) + { + FlagData flags; + for (size_t i = 0; i < 4 && i < data["anti_flags"].size(); ++i) + { + flags.set_plane(i, data["anti_flags"][i].get()); + } + obj->set_anti_flags(flags); + } + + // No flags (array of 4 plane values) + if (data.contains("no_flags") && data["no_flags"].is_array()) + { + FlagData flags; + for (size_t i = 0; i < 4 && i < data["no_flags"].size(); ++i) + { + flags.set_plane(i, data["no_flags"][i].get()); + } + obj->set_no_flags(flags); + } + + // Affects + if (data.contains("affects") && data["affects"].is_array()) + { + size_t idx = 0; + for (const auto &affect : data["affects"]) + { + if (idx >= kMaxObjAffect) + { + break; + } + if (affect.contains("location") && affect.contains("modifier")) + { + obj->set_affected(idx, + static_cast(affect["location"].get()), + affect["modifier"].get()); + ++idx; + } + } + } + + // Durability (support both object and separate fields) + if (data.contains("durability") && data["durability"].is_object()) + { + auto &dur = data["durability"]; + if (dur.contains("current")) + { + obj->set_current_durability(dur["current"].get()); + } + if (dur.contains("maximum")) + { + obj->set_maximum_durability(dur["maximum"].get()); + } + } + else + { + if (data.contains("current_durability")) + { + obj->set_current_durability(data["current_durability"].get()); + } + if (data.contains("maximum_durability")) + { + obj->set_maximum_durability(data["maximum_durability"].get()); + } + } + + // Extra descriptions + if (data.contains("extra_descriptions") && data["extra_descriptions"].is_array()) + { + // Clear existing extra descriptions + obj->set_ex_description(nullptr); + + // Build linked list from array (in reverse to maintain order) + ExtraDescription::shared_ptr prev = nullptr; + for (auto it = data["extra_descriptions"].rbegin(); it != data["extra_descriptions"].rend(); ++it) + { + const auto &ed_data = *it; + if (!ed_data.contains("keywords") || !ed_data.contains("description")) + { + continue; + } + + auto ed = std::make_shared(); + ed->set_keyword(Utf8ToKoi8r(ed_data["keywords"].get())); + ed->set_description(Utf8ToKoi8r(ed_data["description"].get())); + ed->next = prev; + prev = ed; + } + + if (prev) + { + obj->set_ex_description(prev); + } + } + + // Triggers + if (data.contains("triggers") && data["triggers"].is_array()) + { + std::list trigger_list; + for (const auto &trig_vnum : data["triggers"]) + { + if (trig_vnum.is_number_integer()) + { + trigger_list.push_back(trig_vnum.get()); + } + } + obj->set_proto_script(trigger_list); + } +} + +// ============================================================================ +// Room Parsing (stub for now) +// ============================================================================ + +void ParseRoomUpdate(RoomData* room, const nlohmann::json& data) +{ + if (!room) + { + return; + } + + if (HasString(data, "name")) + { + room->set_name(Utf8ToKoi8r(data["name"].get())); + } + if (data.contains("sector_type")) + { + room->sector_type = static_cast(data["sector_type"].get()); + } + if (data.contains("room_flags") && data["room_flags"].is_array()) + { + FlagData flags; + for (size_t i = 0; i < 4 && i < data["room_flags"].size(); ++i) + { + flags.set_plane(i, data["room_flags"][i].get()); + } + room->write_flags(flags); + } + + // Description + if (HasString(data, "description")) + { + if (room->temp_description) + { + free(room->temp_description); + } + room->temp_description = str_dup(Utf8ToKoi8r(data["description"].get()).c_str()); + } + + // Exits (array format: [{direction: 0, to_room: 101, ...}, ...]) + if (data.contains("exits") && data["exits"].is_array()) + { + // Clear all existing exits first + for (int dir = 0; dir < EDirection::kMaxDirNum; ++dir) + { + room->dir_option_proto[dir] = nullptr; + } + + // Rebuild exits from JSON + for (const auto &exit_json : data["exits"]) + { + // Parse direction as number (0-5) + if (!exit_json.contains("direction")) continue; + int dir = exit_json["direction"].get(); + if (dir < 0 || dir >= EDirection::kMaxDirNum) continue; + + // Create exit + room->dir_option_proto[dir] = std::make_shared(); + auto &exit = room->dir_option_proto[dir]; + + // Update exit fields + if (exit_json.contains("to_room")) + { + int target_vnum = exit_json["to_room"].get(); + RoomRnum target_rnum = GetRoomRnum(target_vnum); + exit->to_room(target_rnum); + } + if (exit_json.contains("general_description")) + { + exit->general_description = Utf8ToKoi8r(exit_json["general_description"].get()); + } + if (exit_json.contains("keyword")) + { + exit->set_keyword(Utf8ToKoi8r(exit_json["keyword"].get())); + } + if (exit_json.contains("exit_info")) + { + exit->exit_info = exit_json["exit_info"].get(); + } + if (exit_json.contains("key")) + { + exit->key = exit_json["key"].get(); + } + if (exit_json.contains("lock_complexity")) + { + exit->lock_complexity = exit_json["lock_complexity"].get(); + } + } + } + + // Triggers (array of trigger vnums) + if (data.contains("triggers") && data["triggers"].is_array()) + { + room->proto_script->clear(); + for (const auto &trigger_vnum : data["triggers"]) + { + if (trigger_vnum.is_number_integer()) + { + room->proto_script->push_back(trigger_vnum.get()); + } + } + } +} + +// ============================================================================ +// Zone Parsing (stub for now) +// ============================================================================ + +void ParseZoneUpdate(ZoneData* zone, const nlohmann::json& data) +{ + if (!zone) + { + return; + } + + if (HasString(data, "name")) + { + zone->name = Utf8ToKoi8r(data["name"].get()); + } + if (HasString(data, "comment")) + { + zone->comment = Utf8ToKoi8r(data["comment"].get()); + } + if (HasString(data, "author")) + { + zone->author = Utf8ToKoi8r(data["author"].get()); + } + if (HasString(data, "location")) + { + zone->location = Utf8ToKoi8r(data["location"].get()); + } + if (HasString(data, "description")) + { + zone->description = Utf8ToKoi8r(data["description"].get()); + } + if (data.contains("level")) + { + zone->level = data["level"].get(); + } + if (data.contains("type")) + { + zone->type = data["type"].get(); + } + if (data.contains("lifespan")) + { + zone->lifespan = data["lifespan"].get(); + } + if (data.contains("reset_mode")) + { + zone->reset_mode = data["reset_mode"].get(); + } + if (data.contains("reset_idle")) + { + zone->reset_idle = data["reset_idle"].get(); + } + if (data.contains("top")) + { + zone->top = data["top"].get(); + } + if (data.contains("under_construction")) + { + zone->under_construction = data["under_construction"].get(); + } + if (data.contains("group")) + { + zone->group = data["group"].get(); + } + if (data.contains("is_town")) + { + zone->is_town = data["is_town"].get(); + } + if (data.contains("locked")) + { + zone->locked = data["locked"].get(); + } +} + +// ============================================================================ +// Trigger Parsing +// ============================================================================ + +void ParseTriggerUpdate(Trigger* trig, const nlohmann::json& data) +{ + if (!trig) + { + return; + } + + // Basic text fields + if (HasString(data, "name")) + { + trig->set_name(Utf8ToKoi8r(data["name"].get())); + } + if (HasString(data, "arglist")) + { + trig->arglist = Utf8ToKoi8r(data["arglist"].get()); + } + + // Numeric fields + if (data.contains("narg")) + { + trig->narg = data["narg"].get(); + } + if (data.contains("trigger_type")) + { + trig->set_trigger_type(data["trigger_type"].get()); + } + if (data.contains("attach_type")) + { + trig->set_attach_type(static_cast(data["attach_type"].get())); + } + + // Boolean flags + if (data.contains("add_flag")) + { + trig->add_flag = data["add_flag"].get(); + } + + // Note: Script handling is done in the handler via temp_d->olc->storage + // because it requires OLC-specific processing (cmdlist parsing) +} + +} // namespace admin_api::parsers + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/parsers.h b/src/engine/network/admin_api/parsers.h new file mode 100644 index 000000000..316a3122a --- /dev/null +++ b/src/engine/network/admin_api/parsers.h @@ -0,0 +1,102 @@ +/** + \file parsers.h + \brief JSON to Entity parsing for Admin API + \authors Bylins team + \date 2026-02-13 + + Provides functions to parse JSON data and apply updates to game entities + (mobs, objects, rooms, zones, triggers). Eliminates code duplication and + provides consistent parsing across all CRUD operations. +*/ + +#ifndef BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_PARSERS_H_ +#define BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_PARSERS_H_ + +#include "json_helpers.h" +#include "../../../engine/entities/char_data.h" +#include "../../../engine/entities/obj_data.h" +#include "../../../engine/entities/room_data.h" +#include "../../../engine/entities/zone.h" + +namespace admin_api::parsers { + +using json = nlohmann::json; + +// ============================================================================ +// Mob Parsing +// ============================================================================ + +/** + * \brief Parse JSON and update mob fields + * \param mob Mob to update (OLC copy, not mob_proto directly) + * \param data JSON object with mob update data + * + * Updates mob fields based on JSON content. Only updates fields that are + * present in JSON - missing fields are left unchanged. + * + * Supported fields: + * - names (aliases, nominative, genitive, dative, accusative, instrumental, prepositional) + * - descriptions (short_desc, long_desc) + * - stats (level, armor, hitroll_penalty, sex, race, alignment, hp, damage) + * - abilities (strength, dexterity, constitution, intelligence, wisdom, charisma) + * - physical (height, weight, size, extra_attack, like_work, maxfactor, remort) + * - death_load (array of {obj_vnum, load_prob, load_type, spec_param}) + * - roles (array of role indices) + * - resistances (fire, air, water, earth, vitality, mind, immunity) + * - savings (will, stability, reflex) + * - position (default_position, load_position) + * - behavior (class, attack_type, gold, helpers) + * - flags (mob_flags, npc_flags, affect_flags) + * - triggers (array of trigger vnums) + */ +void ParseMobUpdate(CharData* mob, const json& data); + +// ============================================================================ +// Object Parsing (stub for now) +// ============================================================================ + +/** + * \brief Parse JSON and update object fields + * \param obj Object to update + * \param data JSON object with object update data + */ +void ParseObjectUpdate(CObjectPrototype* obj, const json& data); + +// ============================================================================ +// Room Parsing (stub for now) +// ============================================================================ + +/** + * \brief Parse JSON and update room fields + * \param room Room to update + * \param data JSON object with room update data + */ +void ParseRoomUpdate(RoomData* room, const json& data); + +// ============================================================================ +// Zone Parsing (stub for now) +// ============================================================================ + +/** + * \brief Parse JSON and update zone fields + * \param zone Zone to update + * \param data JSON object with zone update data + */ +void ParseZoneUpdate(ZoneData* zone, const json& data); + +// ============================================================================ +// Trigger Parsing (stub for now) +// ============================================================================ + +/** + * \brief Parse JSON and update trigger fields + * \param trig Trigger to update + * \param data JSON object with trigger update data + */ +void ParseTriggerUpdate(Trigger* trig, const json& data); + +} // namespace admin_api::parsers + +#endif // BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_PARSERS_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/serializers.cpp b/src/engine/network/admin_api/serializers.cpp new file mode 100644 index 000000000..272e6f3b4 --- /dev/null +++ b/src/engine/network/admin_api/serializers.cpp @@ -0,0 +1,475 @@ +/** + \file serializers.cpp + \brief Entity to JSON serialization implementation + \authors Bylins team + \date 2026-02-13 +*/ + +#include "serializers.h" +#include "json_helpers.h" +#include "../../../engine/structs/structs.h" +#include "../../../engine/db/obj_prototypes.h" +#include "../../../engine/entities/room_data.h" +#include "../../../engine/scripting/dg_scripts.h" +#include "../../../gameplay/core/constants.h" + +namespace admin_api::serializers { + +using namespace admin_api::json; + +// ============================================================================ +// Mob Serialization +// ============================================================================ + +json SerializeMob(const CharData& mob, int vnum) +{ + json mob_obj; + mob_obj["vnum"] = vnum; + + // Names (all 6 Russian cases + aliases) + json names; + names["aliases"] = Koi8rToUtf8(mob.get_npc_name()); + names["nominative"] = Koi8rToUtf8(mob.player_data.PNames[ECase::kNom]); + names["genitive"] = Koi8rToUtf8(mob.player_data.PNames[ECase::kGen]); + names["dative"] = Koi8rToUtf8(mob.player_data.PNames[ECase::kDat]); + names["accusative"] = Koi8rToUtf8(mob.player_data.PNames[ECase::kAcc]); + names["instrumental"] = Koi8rToUtf8(mob.player_data.PNames[ECase::kIns]); + names["prepositional"] = Koi8rToUtf8(mob.player_data.PNames[ECase::kPre]); + mob_obj["names"] = names; + + // Descriptions + json descriptions; + descriptions["short_desc"] = Koi8rToUtf8(mob.player_data.long_descr); + descriptions["long_desc"] = Koi8rToUtf8(mob.player_data.description); + mob_obj["descriptions"] = descriptions; + + // Stats (level, HP, damage, etc.) + json stats; + stats["level"] = mob.GetLevel(); + stats["hitroll_penalty"] = GET_HR(&mob); + stats["armor"] = GET_AC(&mob); + + // HP (dice format) + json hp; + hp["dice_count"] = static_cast(mob.mem_queue.total); + hp["dice_size"] = static_cast(mob.mem_queue.stored); + hp["bonus"] = mob.get_hit(); + stats["hp"] = hp; + + // Damage (dice format) + json damage; + damage["dice_count"] = static_cast(mob.mob_specials.damnodice); + damage["dice_size"] = static_cast(mob.mob_specials.damsizedice); + damage["bonus"] = mob.real_abils.damroll; + stats["damage"] = damage; + + // Sex, race, alignment + stats["sex"] = static_cast(mob.get_sex()); + stats["race"] = mob.get_race(); + stats["alignment"] = mob.char_specials.saved.alignment; + + mob_obj["stats"] = stats; + + // Physical characteristics + json physical; + physical["height"] = static_cast(GET_HEIGHT(&mob)); + physical["weight"] = static_cast(GET_WEIGHT(&mob)); + physical["size"] = static_cast(GET_SIZE(&mob)); + physical["extra_attack"] = static_cast(mob.mob_specials.extra_attack); + physical["like_work"] = static_cast(mob.mob_specials.like_work); + physical["maxfactor"] = mob.mob_specials.MaxFactor; + physical["remort"] = mob.get_remort(); + mob_obj["physical"] = physical; + + // Death load (items dropped on death) + json death_load_arr = json::array(); + for (const auto& item : mob.dl_list) + { + json dl_item; + dl_item["obj_vnum"] = item.obj_vnum; + dl_item["load_prob"] = item.load_prob; + dl_item["load_type"] = item.load_type; + dl_item["spec_param"] = item.spec_param; + death_load_arr.push_back(dl_item); + } + mob_obj["death_load"] = death_load_arr; + + // NPC Roles (bitset<9>) + json roles = json::array(); + for (unsigned i = 0; i < mob.get_role_bits().size(); ++i) + { + if (mob.get_role(i)) + { + roles.push_back(static_cast(i)); + } + } + mob_obj["roles"] = roles; + + // Abilities (6 core stats) + json abilities; + abilities["strength"] = mob.get_str(); + abilities["dexterity"] = mob.get_dex(); + abilities["constitution"] = mob.get_con(); + abilities["intelligence"] = mob.get_int(); + abilities["wisdom"] = mob.get_wis(); + abilities["charisma"] = mob.get_cha(); + mob_obj["abilities"] = abilities; + + // Resistances (7 types) + json resistances; + resistances["fire"] = mob.add_abils.apply_resistance[to_underlying(EResist::kFire)]; + resistances["air"] = mob.add_abils.apply_resistance[to_underlying(EResist::kAir)]; + resistances["water"] = mob.add_abils.apply_resistance[to_underlying(EResist::kWater)]; + resistances["earth"] = mob.add_abils.apply_resistance[to_underlying(EResist::kEarth)]; + resistances["vitality"] = mob.add_abils.apply_resistance[to_underlying(EResist::kVitality)]; + resistances["mind"] = mob.add_abils.apply_resistance[to_underlying(EResist::kMind)]; + resistances["immunity"] = mob.add_abils.apply_resistance[to_underlying(EResist::kImmunity)]; + mob_obj["resistances"] = resistances; + + // Savings (3 types) + json savings; + savings["will"] = mob.add_abils.apply_saving_throw[to_underlying(ESaving::kWill)]; + savings["stability"] = mob.add_abils.apply_saving_throw[to_underlying(ESaving::kStability)]; + savings["reflex"] = mob.add_abils.apply_saving_throw[to_underlying(ESaving::kReflex)]; + mob_obj["savings"] = savings; + + // Position + json position; + position["default_position"] = static_cast(mob.mob_specials.default_pos); + position["load_position"] = static_cast(mob.GetPosition()); + mob_obj["position"] = position; + + // Behavior + json behavior; + behavior["class"] = static_cast(mob.GetClass()); + behavior["attack_type"] = mob.mob_specials.attack_type; + + // Gold (dice format) + json gold; + gold["dice_count"] = mob.mob_specials.GoldNoDs; + gold["dice_size"] = mob.mob_specials.GoldSiDs; + gold["bonus"] = 0; // No bonus field in mob_specials + behavior["gold"] = gold; + + // Helpers (array of mob vnums) - empty for now + behavior["helpers"] = json::array(); + + mob_obj["behavior"] = behavior; + + // Flags - use SerializeFlags helper + json flags; + flags["mob_flags"] = SerializeFlags(mob.char_specials.saved.act); + flags["affect_flags"] = SerializeFlags(mob.char_specials.saved.affected_by); + flags["npc_flags"] = SerializeFlags(mob.mob_specials.npc_flags); + mob_obj["flags"] = flags; + + // Triggers + if (mob.proto_script) + { + json triggers = json::array(); + for (const auto& trig_vnum : *mob.proto_script) + { + triggers.push_back(trig_vnum); + } + mob_obj["triggers"] = triggers; + } + else + { + mob_obj["triggers"] = json::array(); + } + + return mob_obj; +} + +// ============================================================================ +// Object Serialization (stub for now) +// ============================================================================ + +json SerializeObject(const CObjectPrototype& obj, int vnum) +{ + json obj_data; + obj_data["vnum"] = vnum; + obj_data["aliases"] = Koi8rToUtf8(obj.get_aliases()); + obj_data["short_desc"] = Koi8rToUtf8(obj.get_short_description()); + obj_data["description"] = Koi8rToUtf8(obj.get_description()); + obj_data["action_desc"] = Koi8rToUtf8(obj.get_action_description()); + obj_data["type"] = static_cast(obj.get_type()); + + // Extra flags (4 planes) + obj_data["extra_flags"] = json::array(); + for (size_t i = 0; i < 4; ++i) + { + obj_data["extra_flags"].push_back(obj.get_extra_flags().get_plane(i)); + } + + obj_data["wear_flags"] = obj.get_wear_flags(); + + // Anti flags (4 planes) + obj_data["anti_flags"] = json::array(); + for (size_t i = 0; i < 4; ++i) + { + obj_data["anti_flags"].push_back(obj.get_anti_flags().get_plane(i)); + } + + // No flags (4 planes) + obj_data["no_flags"] = json::array(); + for (size_t i = 0; i < 4; ++i) + { + obj_data["no_flags"].push_back(obj.get_no_flags().get_plane(i)); + } + + obj_data["weight"] = obj.get_weight(); + obj_data["cost"] = obj.get_cost(); + obj_data["rent_on"] = obj.get_rent_on(); + obj_data["rent_off"] = obj.get_rent_off(); + + // Names (7 case forms) + json names; + for (int c = ECase::kFirstCase; c <= ECase::kLastCase; ++c) + { + ECase ecase = static_cast(c); + std::string case_name; + switch (ecase) + { + case ECase::kNom: case_name = "nominative"; break; + case ECase::kGen: case_name = "genitive"; break; + case ECase::kDat: case_name = "dative"; break; + case ECase::kAcc: case_name = "accusative"; break; + case ECase::kIns: case_name = "instrumental"; break; + case ECase::kPre: case_name = "prepositional"; break; + default: continue; + } + names[case_name] = Koi8rToUtf8(obj.get_PName(ecase)); + } + obj_data["names"] = names; + + // Additional stats + obj_data["level"] = obj.get_minimum_remorts(); + obj_data["sex"] = static_cast(obj.get_sex()); + obj_data["material"] = static_cast(obj.get_material()); + obj_data["timer"] = obj.get_timer(); + + // Durability + json durability; + durability["current"] = obj.get_current_durability(); + durability["maximum"] = obj.get_maximum_durability(); + obj_data["durability"] = durability; + + // Type-specific values (value0-3) + json type_specific; + type_specific["value0"] = obj.get_val(0); + type_specific["value1"] = obj.get_val(1); + type_specific["value2"] = obj.get_val(2); + type_specific["value3"] = obj.get_val(3); + obj_data["type_specific"] = type_specific; + + // Affects (stat modifiers) + json affects = json::array(); + for (int i = 0; i < kMaxObjAffect; ++i) + { + if (obj.get_affected(i).location != EApply::kNone) + { + json affect; + affect["location"] = static_cast(obj.get_affected(i).location); + affect["modifier"] = obj.get_affected(i).modifier; + affects.push_back(affect); + } + } + obj_data["affects"] = affects; + + // Extra descriptions (linked list) + json extra_descs = json::array(); + for (auto ed = obj.get_ex_description(); ed; ed = ed->next) + { + json extra; + extra["keywords"] = Koi8rToUtf8(ed->keyword ? ed->keyword : ""); + extra["description"] = Koi8rToUtf8(ed->description ? ed->description : ""); + extra_descs.push_back(extra); + } + obj_data["extra_descriptions"] = extra_descs; + + // Triggers + json triggers = json::array(); + for (const auto &trig_vnum : obj.get_proto_script()) + { + triggers.push_back(trig_vnum); + } + obj_data["triggers"] = triggers; + + return obj_data; +} + +// ============================================================================ +// Room Serialization (stub for now) +// ============================================================================ + +json SerializeRoom(RoomData& room, int vnum) +{ + json room_data; + room_data["vnum"] = vnum; + room_data["name"] = Koi8rToUtf8(room.name); + + // Description stored in GlobalObjects::descriptions() + if (room.temp_description) + { + room_data["description"] = Koi8rToUtf8(room.temp_description); + } + room_data["sector_type"] = static_cast(room.sector_type); + + // Room flags (4 planes) + { + FlagData fl = room.read_flags(); + room_data["room_flags"] = json::array(); + for (size_t i = 0; i < 4; ++i) + { + room_data["room_flags"].push_back(fl.get_plane(i)); + } + } + + // Exits + json exits = json::array(); + for (int dir = 0; dir < EDirection::kMaxDirNum; ++dir) + { + if (room.dir_option[dir]) + { + json exit_obj; + exit_obj["direction"] = dir; + exit_obj["to_room"] = room.dir_option[dir]->to_room() != kNowhere ? + world[room.dir_option[dir]->to_room()]->vnum : -1; + if (!room.dir_option[dir]->general_description.empty()) + { + exit_obj["description"] = Koi8rToUtf8(room.dir_option[dir]->general_description); + } + if (room.dir_option[dir]->keyword) + { + exit_obj["keyword"] = Koi8rToUtf8(room.dir_option[dir]->keyword); + } + exit_obj["exit_info"] = room.dir_option[dir]->exit_info; + exit_obj["key_vnum"] = room.dir_option[dir]->key; + exits.push_back(exit_obj); + } + } + room_data["exits"] = exits; + + // Extra descriptions + json extra_descrs = json::array(); + for (auto ed = room.ex_description; ed; ed = ed->next) + { + json ed_obj; + if (ed->keyword) + { + ed_obj["keyword"] = Koi8rToUtf8(ed->keyword); + } + if (ed->description) + { + ed_obj["description"] = Koi8rToUtf8(ed->description); + } + extra_descrs.push_back(ed_obj); + } + room_data["extra_descriptions"] = extra_descrs; + + // Triggers + json triggers = json::array(); + if (room.proto_script && !room.proto_script->empty()) + { + for (const auto &trig_vnum : *room.proto_script) + { + triggers.push_back(trig_vnum); + } + } + room_data["triggers"] = triggers; + + return room_data; +} + +// ============================================================================ +// ZoneData Serialization (stub for now) +// ============================================================================ + +json SerializeZoneData(const ZoneData& zone, int vnum) +{ + json zone_data; + zone_data["vnum"] = vnum; + zone_data["name"] = Koi8rToUtf8(zone.name); + + if (!zone.comment.empty()) { + zone_data["comment"] = Koi8rToUtf8(zone.comment); + } + if (!zone.author.empty()) { + zone_data["author"] = Koi8rToUtf8(zone.author); + } + if (!zone.location.empty()) { + zone_data["location"] = Koi8rToUtf8(zone.location); + } + if (!zone.description.empty()) { + zone_data["description"] = Koi8rToUtf8(zone.description); + } + + zone_data["level"] = zone.level; + zone_data["type"] = zone.type; + zone_data["lifespan"] = zone.lifespan; + zone_data["reset_mode"] = zone.reset_mode; + zone_data["reset_idle"] = zone.reset_idle; + zone_data["top"] = zone.top; + zone_data["under_construction"] = zone.under_construction; + zone_data["group"] = zone.group; + zone_data["is_town"] = zone.is_town; + zone_data["locked"] = zone.locked; + + if (zone.typeA_count > 0 && zone.typeA_list) { + json typeA = json::array(); + for (int i = 0; i < zone.typeA_count; ++i) { + typeA.push_back(zone.typeA_list[i]); + } + zone_data["type_a_list"] = typeA; + } + if (zone.typeB_count > 0 && zone.typeB_list) { + json typeB = json::array(); + for (int i = 0; i < zone.typeB_count; ++i) { + typeB.push_back(zone.typeB_list[i]); + } + zone_data["type_b_list"] = typeB; + } + + return zone_data; +} + +// ============================================================================ +// Trigger Serialization (stub for now) +// ============================================================================ + +json SerializeTrigger(const Trigger& trig, int vnum) +{ + json trig_data; + trig_data["vnum"] = vnum; + trig_data["name"] = Koi8rToUtf8(trig.get_name()); + trig_data["attach_type"] = static_cast(trig.get_attach_type()); + trig_data["trigger_type"] = trig.get_trigger_type(); + trig_data["narg"] = trig.narg; + trig_data["add_flag"] = trig.add_flag; + + // Argument + if (!trig.arglist.empty()) + { + trig_data["arglist"] = Koi8rToUtf8(trig.arglist); + } + + // Command list + json commands = json::array(); + if (trig.cmdlist) + { + auto cmd = *trig.cmdlist; + while (cmd) + { + commands.push_back(Koi8rToUtf8(cmd->cmd)); + cmd = cmd->next; + } + } + trig_data["commands"] = commands; + + return trig_data; +} + +} // namespace admin_api::serializers + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/admin_api/serializers.h b/src/engine/network/admin_api/serializers.h new file mode 100644 index 000000000..86648f66f --- /dev/null +++ b/src/engine/network/admin_api/serializers.h @@ -0,0 +1,100 @@ +/** + \file serializers.h + \brief Entity to JSON serialization for Admin API + \authors Bylins team + \date 2026-02-13 + + Provides functions to convert game entities (mobs, objects, rooms, zones, triggers) + to JSON format for Admin API responses. Eliminates code duplication and provides + consistent serialization across all CRUD operations. +*/ + +#ifndef BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_SERIALIZERS_H_ +#define BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_SERIALIZERS_H_ + +#include "json_helpers.h" +#include "../../../engine/entities/char_data.h" +#include "../../../engine/entities/obj_data.h" +#include "../../../engine/entities/room_data.h" +#include "../../../engine/entities/zone.h" + +namespace admin_api::serializers { + +using json = nlohmann::json; + +// ============================================================================ +// Mob Serialization +// ============================================================================ + +/** + * \brief Serialize mob to JSON + * \param mob Mob to serialize (mob_proto[rnum]) + * \param vnum Virtual number of the mob + * \return JSON object with mob data + * + * Output format: + * { + * "vnum": 100, + * "names": { "aliases", "nominative", ... }, + * "descriptions": { "short_desc", "long_desc" }, + * "stats": { "level", "hp", "damage", ... }, + * "abilities": { "strength", "dexterity", ... }, + * "flags": { "mob_flags": [...], "npc_flags": [...], "affect_flags": [...] }, + * "triggers": [100, 101] + * } + */ +json SerializeMob(const CharData& mob, int vnum); + +// ============================================================================ +// Object Serialization +// ============================================================================ + +/** + * \brief Serialize object to JSON + * \param obj Object to serialize (obj_proto[rnum]) + * \param vnum Virtual number of the object + * \return JSON object with object data + */ +json SerializeObject(const CObjectPrototype& obj, int vnum); + +// ============================================================================ +// Room Serialization +// ============================================================================ + +/** + * \brief Serialize room to JSON + * \param room Room to serialize (world[rnum]) + * \param vnum Virtual number of the room + * \return JSON object with room data + */ +json SerializeRoom(RoomData& room, int vnum); + +// ============================================================================ +// ZoneData Serialization +// ============================================================================ + +/** + * \brief Serialize zone to JSON + * \param zone ZoneData to serialize + * \param vnum Virtual number of the zone + * \return JSON object with zone data + */ +json SerializeZoneData(const ZoneData& zone, int vnum); + +// ============================================================================ +// Trigger Serialization +// ============================================================================ + +/** + * \brief Serialize trigger to JSON + * \param trig Trigger to serialize + * \param vnum Virtual number of the trigger + * \return JSON object with trigger data + */ +json SerializeTrigger(const Trigger& trig, int vnum); + +} // namespace admin_api::serializers + +#endif // BYLINS_SRC_ENGINE_NETWORK_ADMIN_API_SERIALIZERS_H_ + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/network/descriptor_data.h b/src/engine/network/descriptor_data.h index f352e42c6..01998e61b 100644 --- a/src/engine/network/descriptor_data.h +++ b/src/engine/network/descriptor_data.h @@ -91,7 +91,8 @@ enum class EConState : uint8_t { kSedit = 54, // sedit - редактирование сетов kResetReligion = 55, // сброс религии из меню сброса статов kRandomNumber = 56, // Verification code entry: where player enter the game from new location - kInit = 57 // just connected + kInit = 57, // just connected + kAdminAPI = 58 // Admin API connection }; // Номера оставлены, чтобы было удобней ориентироваться в списке описаний -- Svent // не забываем отражать новые состояния в connection_descriptions -- Krodo @@ -173,6 +174,13 @@ struct DescriptorData { std::shared_ptr sedit; // редактирование сетов bool mxp{}; // Для MXP + // Admin API support + bool admin_api_mode{}; // Admin API connection (JSON protocol) + bool admin_api_authenticated{}; // Admin API authenticated flag + std::string admin_api_large_buffer; // Buffer for large JSON messages + long admin_user_id{}; // Authenticated user ID (UID) + std::string admin_user_name; // Authenticated user name + private: bool m_msdp_support; std::unordered_set m_msdp_requested_report; diff --git a/src/engine/network/logon.h b/src/engine/network/logon.h index 342af7bd5..98a9ecf8d 100644 --- a/src/engine/network/logon.h +++ b/src/engine/network/logon.h @@ -11,7 +11,7 @@ #include #include -class DescriptorData; +struct DescriptorData; namespace network { diff --git a/src/engine/olc/medit.cpp b/src/engine/olc/medit.cpp index c5b32407f..a1da717ac 100644 --- a/src/engine/olc/medit.cpp +++ b/src/engine/olc/medit.cpp @@ -12,6 +12,7 @@ #include "engine/core/comm.h" #include "gameplay/magic/spells.h" #include "engine/db/db.h" +#include "engine/db/world_data_source_manager.h" #include "olc.h" #include "engine/core/handler.h" #include "engine/scripting/dg_olc.h" @@ -508,7 +509,9 @@ void medit_save_internally(DescriptorData *d) { #endif // olc_add_to_save_list(zone_table[OLC_ZNUM(d)].vnum, OLC_SAVE_MOB); - medit_save_to_disk(OLC_ZNUM(d)); + // Save only this specific mob (vnum = OLC_NUM(d)) + auto* data_source = world_loader::WorldDataSourceManager::Instance().GetDataSource(); + data_source->SaveMobs(OLC_ZNUM(d), OLC_NUM(d)); } //------------------------------------------------------------------- @@ -684,11 +687,13 @@ void medit_save_to_disk(ZoneRnum zone_num) { // * We're fubar'd if we crash between the two lines below. remove(buf2); rename(fname, buf2); +#ifndef _WIN32 if (chmod(buf2, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << buf2 << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } +#endif olc_remove_from_save_list(zone_table[zone_num].vnum, OLC_SAVE_MOB); } @@ -749,7 +754,7 @@ void medit_disp_resistances(DescriptorData *d) { grn, i + 1, nrm, resistance_types[i], cyn, GET_RESIST(OLC_MOB(d), i), nrm); SendMsgToChar(buf, d->character.get()); } - SendMsgToChar("Введите номер и величину сопротивления (-100..100\%) (0 - конец) : ", d->character.get()); + SendMsgToChar("Введите номер и величину сопротивления (-100..100%) (0 - конец) : ", d->character.get()); } // * Display saves diff --git a/src/engine/olc/oedit.cpp b/src/engine/olc/oedit.cpp index 42887ed0b..eb1ec0c3e 100644 --- a/src/engine/olc/oedit.cpp +++ b/src/engine/olc/oedit.cpp @@ -19,6 +19,7 @@ #include "utils/utils.h" #include "utils/id_converter.h" #include "engine/db/db.h" +#include "engine/db/world_data_source_manager.h" #include "olc.h" #include "engine/scripting/dg_olc.h" #include "gameplay/crafting/im.h" @@ -271,7 +272,9 @@ void oedit_save_internally(DescriptorData *d) { } // olc_add_to_save_list(zone_table[OLC_ZNUM(d)].vnum, OLC_SAVE_OBJ); - oedit_save_to_disk(OLC_ZNUM(d)); + // Save only this specific object (vnum = OLC_NUM(d)) + auto* data_source = world_loader::WorldDataSourceManager::Instance().GetDataSource(); + data_source->SaveObjects(OLC_ZNUM(d), OLC_NUM(d)); } //------------------------------------------------------------------------ @@ -402,11 +405,13 @@ void oedit_save_to_disk(ZoneRnum zone_num) { // * We're fubar'd if we crash between the two lines below. remove(buf2); rename(buf, buf2); +#ifndef _WIN32 if (chmod(buf2, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << buf2 << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } +#endif olc_remove_from_save_list(zone_table[zone_num].vnum, OLC_SAVE_OBJ); } diff --git a/src/engine/olc/olc.cpp b/src/engine/olc/olc.cpp index a5727c31e..4225301e1 100644 --- a/src/engine/olc/olc.cpp +++ b/src/engine/olc/olc.cpp @@ -15,6 +15,7 @@ #include "engine/ui/interpreter.h" #include "engine/core/comm.h" #include "engine/db/db.h" +#include "engine/db/world_data_source_manager.h" #include "engine/scripting/dg_olc.h" #include "engine/ui/color.h" #include "gameplay/crafting/item_creation.h" @@ -208,7 +209,9 @@ void do_olc(CharData *ch, char *argument, int cmd, int subcmd) { sprintf(buf, "(GC) %s has locked zone %d", GET_NAME(ch), zone_table[OLC_ZNUM(d)].vnum); olc_log("%s locks zone %d", GET_NAME(ch), zone_table[OLC_ZNUM(d)].vnum); mudlog(buf, LGH, kLvlImplementator, SYSLOG, true); - zedit_save_to_disk(OLC_ZNUM(d)); + + auto* data_source = world_loader::WorldDataSourceManager::Instance().GetDataSource(); + data_source->SaveZone(OLC_ZNUM(d)); delete d->olc; return; } @@ -219,7 +222,9 @@ void do_olc(CharData *ch, char *argument, int cmd, int subcmd) { sprintf(buf, "(GC) %s has unlocked zone %d", GET_NAME(ch), zone_table[OLC_ZNUM(d)].vnum); olc_log("%s unlocks zone %d", GET_NAME(ch), zone_table[OLC_ZNUM(d)].vnum); mudlog(buf, LGH, kLvlImplementator, SYSLOG, true); - zedit_save_to_disk(OLC_ZNUM(d)); + + auto* data_source = world_loader::WorldDataSourceManager::Instance().GetDataSource(); + data_source->SaveZone(OLC_ZNUM(d)); delete d->olc; return; } @@ -263,14 +268,16 @@ void do_olc(CharData *ch, char *argument, int cmd, int subcmd) { olc_log("%s save %s in Z%d", GET_NAME(ch), type, zone_table[OLC_ZNUM(d)].vnum); mudlog(buf, LGH, std::max(kLvlBuilder, GET_INVIS_LEV(ch)), SYSLOG, true); + auto* data_source = world_loader::WorldDataSourceManager::Instance().GetDataSource(); + switch (subcmd) { - case kScmdOlcRedit: redit_save_to_disk(OLC_ZNUM(d)); + case kScmdOlcRedit: data_source->SaveRooms(OLC_ZNUM(d)); break; - case kScmdOlcZedit: zedit_save_to_disk(OLC_ZNUM(d)); + case kScmdOlcZedit: data_source->SaveZone(OLC_ZNUM(d)); break; - case kScmdOlcOedit: oedit_save_to_disk(OLC_ZNUM(d)); + case kScmdOlcOedit: data_source->SaveObjects(OLC_ZNUM(d)); break; - case kScmdOlcMedit: medit_save_to_disk(OLC_ZNUM(d)); + case kScmdOlcMedit: data_source->SaveMobs(OLC_ZNUM(d)); break; } delete d->olc; diff --git a/src/engine/olc/redit.cpp b/src/engine/olc/redit.cpp index 08e605582..ca8536423 100644 --- a/src/engine/olc/redit.cpp +++ b/src/engine/olc/redit.cpp @@ -11,11 +11,13 @@ #include "engine/entities/obj_data.h" #include "engine/core/comm.h" #include "engine/db/db.h" +#include "engine/db/world_data_source_manager.h" #include "olc.h" #include "engine/scripting/dg_olc.h" #include "gameplay/core/constants.h" #include "gameplay/crafting/im.h" #include "engine/db/description.h" +#include "engine/db/global_objects.h" #include "gameplay/mechanics/deathtrap.h" #include "engine/entities/char_data.h" #include "engine/entities/char_player.h" @@ -80,7 +82,7 @@ void redit_setup(DescriptorData *d, int real_num) } else { CopyRoom(room, world[real_num]); // temp_description существует только на время редактирования комнаты в олц - room->temp_description = str_dup(RoomDescription::show_desc(world[real_num]->description_num).c_str()); + room->temp_description = str_dup(GlobalObjects::descriptions().get(world[real_num]->description_num).c_str()); } OLC_ROOM(d) = room; @@ -100,7 +102,7 @@ void redit_save_internally(DescriptorData *d) { rrn = GetRoomRnum(OLC_ROOM(d)->vnum); // дальше temp_description уже нигде не участвует, описание берется как обычно через число - OLC_ROOM(d)->description_num = RoomDescription::add_desc(OLC_ROOM(d)->temp_description); + OLC_ROOM(d)->description_num = GlobalObjects::descriptions().add(OLC_ROOM(d)->temp_description); // * Room exists: move contents over then free and replace it. if (rrn != kNowhere) { log("[REdit] Save room to mem %d", rrn); @@ -262,7 +264,9 @@ void redit_save_internally(DescriptorData *d) { assign_triggers(world[rrn], WLD_TRIGGER); // olc_add_to_save_list(zone_table[OLC_ZNUM(d)].vnum, OLC_SAVE_ROOM); RestoreRoomExitData(rrn); - redit_save_to_disk(OLC_ZNUM(d)); + // Save only this specific room (vnum = OLC_NUM(d)) + auto* data_source = world_loader::WorldDataSourceManager::Instance().GetDataSource(); + data_source->SaveRooms(OLC_ZNUM(d), OLC_NUM(d)); } //------------------------------------------------------------------------ @@ -299,7 +303,7 @@ void redit_save_to_disk(ZoneRnum zone_num) { #endif // * Remove the '\r\n' sequences from description. - strcpy(buf1, RoomDescription::show_desc(room->description_num).c_str()); + strcpy(buf1, GlobalObjects::descriptions().get(room->description_num).c_str()); strip_string(buf1); // * Forget making a buffer, lets just write the thing now. @@ -362,11 +366,13 @@ void redit_save_to_disk(ZoneRnum zone_num) { // * We're fubar'd if we crash between the two lines below. remove(buf2); rename(buf, buf2); +#ifndef _WIN32 if (chmod(buf2, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << buf2 << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } +#endif olc_remove_from_save_list(zone_table[zone_num].vnum, OLC_SAVE_ROOM); } diff --git a/src/engine/olc/zedit.cpp b/src/engine/olc/zedit.cpp index 7d895d8a4..6f439c308 100644 --- a/src/engine/olc/zedit.cpp +++ b/src/engine/olc/zedit.cpp @@ -408,6 +408,7 @@ void zedit_save_internally(DescriptorData *d) { } zone_table[OLC_ZNUM(d)].under_construction = OLC_ZONE(d)->under_construction; zone_table[OLC_ZNUM(d)].locked = OLC_ZONE(d)->locked; + zone_table[OLC_ZNUM(d)].is_town = OLC_ZONE(d)->is_town; if (zone_table[OLC_ZNUM(d)].group != OLC_ZONE(d)->group) { zone_table[OLC_ZNUM(d)].group = OLC_ZONE(d)->group; HelpSystem::reload(HelpSystem::STATIC); @@ -593,11 +594,13 @@ void zedit_save_to_disk(ZoneRnum zone_num) { // * We're fubar'd if we crash between the two lines below. remove(buf2); rename(fname, buf2); +#ifndef _WIN32 if (chmod(buf2, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << buf2 << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } +#endif olc_remove_from_save_list(zone_table[zone_num].vnum, OLC_SAVE_ZONE); } @@ -1935,7 +1938,7 @@ void zedit_parse(DescriptorData *d, char *arg) { SendMsgToChar(d->character.get(), "Не нужно писать адакадабру, повторите ввод: "); } else if (zone_table[OLC_ZNUM(d)].vnum == pos) { SendMsgToChar(d->character.get(), "Зачем добавлять саму себя? повторите ввод: "); - } else if (GetZoneRnum(pos) == 0) { + } else if (GetZoneRnum(pos) == kNoZone) { SendMsgToChar(d->character.get(), "Некорректны номер зоны, повторите ввод (2-%d) : ", zone_table[zone_table.size() - 1 - dungeons::kNumberOfZoneDungeons].vnum); } else { @@ -1979,7 +1982,7 @@ void zedit_parse(DescriptorData *d, char *arg) { case ZEDIT_TYPE_B_LIST: // * Add or delete new zone in the type A zones list. pos = atoi(arg); - if (!is_number(arg) || GetZoneRnum(pos) == 0) { + if (!is_number(arg) || GetZoneRnum(pos) == kNoZone) { SendMsgToChar(d->character.get(), "Повторите ввод (1-%d) : ", zone_table[zone_table.size() - 1 - dungeons::kNumberOfZoneDungeons].vnum); } else { diff --git a/src/engine/scripting/dg_db_scripts.cpp b/src/engine/scripting/dg_db_scripts.cpp index fab76ddc4..856786815 100644 --- a/src/engine/scripting/dg_db_scripts.cpp +++ b/src/engine/scripting/dg_db_scripts.cpp @@ -21,6 +21,7 @@ #include "gameplay/magic/magic.h" #include "gameplay/magic/magic_temp_spells.h" #include "engine/db/global_objects.h" +#include "trigger_indenter.h" #include @@ -36,7 +37,7 @@ extern IndexData *mob_index; // TODO: Get rid of me char *dirty_indent_trigger(char *cmd, int *level) { - static std::stack indent_stack; + thread_local std::stack indent_stack; *level = std::max(0, *level); if (*level == 0) { @@ -111,8 +112,9 @@ char *dirty_indent_trigger(char *cmd, int *level) { } void indent_trigger(std::string &cmd, int *level) { + thread_local TriggerIndenter indenter; char *cmd_copy = str_dup(cmd.c_str());; - cmd_copy = dirty_indent_trigger(cmd_copy, level); + cmd_copy = indenter.indent(cmd_copy, level); cmd = cmd_copy; free(cmd_copy); } diff --git a/src/engine/scripting/dg_olc.cpp b/src/engine/scripting/dg_olc.cpp index 8fcb2bcc1..17f521a08 100644 --- a/src/engine/scripting/dg_olc.cpp +++ b/src/engine/scripting/dg_olc.cpp @@ -23,6 +23,7 @@ #include #include "engine/db/world_characters.h" #include "engine/db/global_objects.h" +#include "engine/db/world_data_source_manager.h" #include "dg_db_scripts.h" #include "gameplay/mechanics/dungeons.h" @@ -36,6 +37,7 @@ void free_varlist(struct TriggerVar *vd); void trigedit_disp_menu(DescriptorData *d); void trigedit_save(DescriptorData *d); +bool trigedit_save_to_disk(int zone_rnum, int notify_level); void trigedit_create_index(int znum, const char *type); void indent_trigger(std::string &cmd, int *level); @@ -361,13 +363,8 @@ void trigedit_save(DescriptorData *d) { Trigger *proto; Trigger *trig = OLC_TRIG(d); IndexData **new_index; + int zone; DescriptorData *dsc; - FILE *trig_file; - int zone, top; - char buf[kMaxStringLength]; - char bitBuf[kMaxInputLength]; - char fname[kMaxInputLength]; - char logbuf[kMaxInputLength]; // Recompile the command list from the new script s = OLC_STORAGE(d); @@ -483,25 +480,52 @@ void trigedit_save(DescriptorData *d) { } } } - // now write the trigger out to disk, along with the rest of the // - // triggers for this zone, of course // - // note: we write this to disk NOW instead of letting the builder // - // have control because if we lose this after having assigned a // - // new trigger to an item, we will get SYSERR's upton reboot that // - // could make things hard to debug. // + + // Save trigger to disk using data source abstraction (YAML/SQLite/Legacy) TriggerDistribution(d); zone = zone_table[OLC_ZNUM(d)].vnum; - top = zone_table[OLC_ZNUM(d)].top; + int notify_level = MAX(kLvlBuilder, GET_INVIS_LEV(d->character)); + + auto* data_source = world_loader::WorldDataSourceManager::Instance().GetDataSource(); + if (!data_source->SaveTriggers(OLC_ZNUM(d), OLC_NUM(d), notify_level)) { + SendMsgToChar("ОШИБКА: Не удалось сохранить триггер!\r\n", d->character.get()); + return; + } + + SendMsgToChar("Триггер сохранен.\r\n", d->character.get()); + trigedit_create_index(zone, "trg"); +} + +// Save all triggers for a zone to disk (without requiring DescriptorData) +bool trigedit_save_to_disk(int zone_rnum, int notify_level) { + FILE *trig_file; + int trig_rnum, i; + Trigger *trig; + int zone, top; + char buf[kMaxStringLength]; + char bitBuf[kMaxInputLength]; + char fname[kMaxInputLength]; + char logbuf[kMaxInputLength]; + + if (zone_rnum < 0 || zone_rnum >= static_cast(zone_table.size())) { + log("SYSERR: trigedit_save_to_disk: Invalid zone rnum %d", zone_rnum); + return false; + } + + zone = zone_table[zone_rnum].vnum; + top = zone_table[zone_rnum].top; + if (zone >= dungeons::kZoneStartDungeons) { - sprintf(buf, "Отказ сохранения зоны %d на диск.", zone); - mudlog(buf, CMP, kLvlGreatGod, SYSLOG, true); - return; + sprintf(buf, "Отказ сохранения зоны %d на диск.", zone); + mudlog(buf, CMP, kLvlGreatGod, SYSLOG, true); + return false; } + sprintf(fname, "%s/%i.new", TRG_PREFIX, zone); if (!(trig_file = fopen(fname, "w"))) { snprintf(logbuf, kMaxInputLength, "SYSERR: OLC: Can't open trig file \"%s\"", fname); - mudlog(logbuf, BRF, MAX(kLvlBuilder, GET_INVIS_LEV(d->character)), SYSLOG, true); - return; + mudlog(logbuf, BRF, notify_level, SYSLOG, true); + return false; } for (i = zone * 100; i <= top; i++) { @@ -510,9 +534,9 @@ void trigedit_save(DescriptorData *d) { if (fprintf(trig_file, "#%d\n", i) < 0) { sprintf(logbuf, "SYSERR: OLC: Can't write trig file!"); - mudlog(logbuf, BRF, MAX(kLvlBuilder, GET_INVIS_LEV(d->character)), SYSLOG, true); + mudlog(logbuf, BRF, notify_level, SYSLOG, true); fclose(trig_file); - return; + return false; } sprintbyts(GET_TRIG_TYPE(trig), bitBuf); fprintf(trig_file, "%s~\n" @@ -525,17 +549,13 @@ void trigedit_save(DescriptorData *d) { // Build the text for the script int lev = 0; strcpy(buf, ""); - for (cmd = *trig->cmdlist; cmd; cmd = cmd->next) { + for (auto cmd = *trig->cmdlist; cmd; cmd = cmd->next) { // Indenting indent_trigger(cmd->cmd, &lev); strcat(buf, cmd->cmd.c_str()); strcat(buf, "\n"); } - if (lev > 0) { - SendMsgToChar(d->character.get(), "WARNING: Positive indent-level on trigger #%d end.\r\n", i); - } - if (!buf[0]) { strcpy(buf, "* Empty script~\n"); fprintf(trig_file, "%s", buf); @@ -556,16 +576,20 @@ void trigedit_save(DescriptorData *d) { fprintf(trig_file, "$\n$\n"); fclose(trig_file); + sprintf(buf, "%s/%d.trg", TRG_PREFIX, zone); remove(buf); rename(fname, buf); +#ifndef _WIN32 if (chmod(buf, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << buf << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } - SendMsgToChar("Триггер сохранен.\r\n", d->character.get()); +#endif + trigedit_create_index(zone, "trg"); + return true; } void trigedit_create_index(int znum, const char *type) { diff --git a/src/engine/scripting/dg_scripts.cpp b/src/engine/scripting/dg_scripts.cpp index 1ec494384..44598a033 100644 --- a/src/engine/scripting/dg_scripts.cpp +++ b/src/engine/scripting/dg_scripts.cpp @@ -329,14 +329,14 @@ int count_char_vnum(MobVnum mvn) { Characters::list_t mobs; character_list.get_mobs_by_vnum(mvn, mobs); - return mobs.size(); + return static_cast(mobs.size()); } int CountGameObjs(ObjRnum rnum) { std::list objs; world_objects.GetObjListByRnum(rnum, objs); - return objs.size(); + return static_cast(objs.size()); } // return room with UID n @@ -853,7 +853,7 @@ static std::string print_variable_name(const std::string &name) { const std::string &guid_name = text_mapping.first; const std::string &print_text = text_mapping.second; - const int guid_start_offcet = name.length() - guid_name.length(); + const auto guid_start_offcet = static_cast(name.length()) - static_cast(guid_name.length()); if (guid_start_offcet > 0 && guid_name == name.substr(guid_start_offcet)) { if (display_state_vars) { result = print_text; @@ -1315,7 +1315,7 @@ int text_processed(char *field, char *subfield, TriggerVar vd, char *str) { return false; if (!str_cmp(field, "strlen")) { - sprintf(str, "%lu", vd.value.size()); + sprintf(str, "%zu", vd.value.size()); return true; } else if (!str_cmp(field, "trim")) { strcpy(str, utils::TrimCopy(vd.value).c_str()); @@ -1887,7 +1887,7 @@ void find_replacement(void *go, } else if (!str_cmp(field, "exact")) { auto now = std::chrono::system_clock::now(); auto now_ms = std::chrono::time_point_cast(now); - sprintf(str, "%ld", now_ms.time_since_epoch().count()); + sprintf(str, "%lld", static_cast(now_ms.time_since_epoch().count())); } else if (!str_cmp(field, "yday")) { strftime(str, kMaxInputLength, "%j", localtime(&now_time)); } else if (!str_cmp(field, "wday")) { @@ -2087,7 +2087,7 @@ void find_replacement(void *go, sprintf(str, "0"); } else { size_t dst = std::distance(arr.begin(), result); - sprintf(str, "%ld", dst + 1); + sprintf(str, "%zu", dst + 1); } return; } else if (!str_cmp(field, "remove")) { @@ -2155,6 +2155,7 @@ void find_replacement(void *go, if (text_processed(field, subfield, vd, str)) { return; } + bool char_handled = true; if (!str_cmp(field, "global")) { if (c->IsNpc()) { char *p = strchr(subfield, ','); @@ -2840,336 +2841,341 @@ void find_replacement(void *go, strcpy(str, spell_count(trig, c, subfield)); else if (!str_cmp(field, "spelltype")) strcpy(str, spell_knowledge(trig, c, subfield)); - else if (!str_cmp(field, "quested")) { - if (*subfield && (num = atoi(subfield)) > 0) { - if (c->quested_get(num)) - strcpy(str, "1"); - else - strcpy(str, "0"); - } - } else if (!str_cmp(field, "getquest")) { - if (*subfield && (num = atoi(subfield)) > 0) { - strcpy(str, (c->quested_get_text(num)).c_str()); - } - } else if (!str_cmp(field, "setquest")) { - if (*subfield) { - subfield = one_argument(subfield, buf); - skip_spaces(&subfield); - if ((num = atoi(buf)) > 0) { - c->quested_add(c, num, subfield); + else + char_handled = false; + // Continue char field dispatch (split for MSVC C1061) + if (!char_handled) { + if (!str_cmp(field, "quested")) { + if (*subfield && (num = atoi(subfield)) > 0) { + if (c->quested_get(num)) + strcpy(str, "1"); + else + strcpy(str, "0"); } - } - } else if (!str_cmp(field, "alliance")) { - if (*subfield) { - subfield = one_argument(subfield, buf); - if (ClanSystem::is_alliance(c, buf)) - strcpy(str, "1"); - else - strcpy(str, "0"); - } - } else if (!str_cmp(field, "eq")) { - int pos = -1; - if (a_isdigit(*subfield)) - pos = atoi(subfield); - else if (*subfield) - pos = find_eq_pos(c, nullptr, subfield); - if (!*subfield || pos < 0 || pos >= EEquipPos::kNumEquipPos) - strcpy(str, ""); - else { - if (!GET_EQ(c, pos)) + } else if (!str_cmp(field, "getquest")) { + if (*subfield && (num = atoi(subfield)) > 0) { + strcpy(str, (c->quested_get_text(num)).c_str()); + } + } else if (!str_cmp(field, "setquest")) { + if (*subfield) { + subfield = one_argument(subfield, buf); + skip_spaces(&subfield); + if ((num = atoi(buf)) > 0) { + c->quested_add(c, num, subfield); + } + } + } else if (!str_cmp(field, "alliance")) { + if (*subfield) { + subfield = one_argument(subfield, buf); + if (ClanSystem::is_alliance(c, buf)) + strcpy(str, "1"); + else + strcpy(str, "0"); + } + } else if (!str_cmp(field, "eq")) { + int pos = -1; + if (a_isdigit(*subfield)) + pos = atoi(subfield); + else if (*subfield) + pos = find_eq_pos(c, nullptr, subfield); + if (!*subfield || pos < 0 || pos >= EEquipPos::kNumEquipPos) strcpy(str, ""); - else - sprintf(str, "%c%ld", UID_OBJ, GET_EQ(c, pos)->get_id()); - } - } else if (!str_cmp(field, "haveobj") || !str_cmp(field, "haveobjs")) { - int pos; - if (a_isdigit(*subfield)) { - pos = atoi(subfield); - for (obj = c->carrying; obj; obj = obj->get_next_content()) { - if (GET_OBJ_VNUM(obj) == pos) { - break; + else { + if (!GET_EQ(c, pos)) + strcpy(str, ""); + else + sprintf(str, "%c%ld", UID_OBJ, GET_EQ(c, pos)->get_id()); + } + } else if (!str_cmp(field, "haveobj") || !str_cmp(field, "haveobjs")) { + int pos; + if (a_isdigit(*subfield)) { + pos = atoi(subfield); + for (obj = c->carrying; obj; obj = obj->get_next_content()) { + if (GET_OBJ_VNUM(obj) == pos) { + break; + } } + } else { + obj = get_obj_in_list_vis(c, subfield, c->carrying); } - } else { - obj = get_obj_in_list_vis(c, subfield, c->carrying); - } - - if (obj) { - sprintf(str, "%c%ld", UID_OBJ, obj->get_id()); - } else { - strcpy(str, "0"); - } - } else if (!str_cmp(field, "varexist") || !str_cmp(field, "varexists")) { - vd = find_var_cntx(SCRIPT(c)->global_vars, subfield, trig->context); - if (!vd.name.empty()) { - strcpy(str, "1"); - } else { - strcpy(str, "0"); - } - } else if (!str_cmp(field, "nextinroom")) { - CharData *next = nullptr; - const auto room = world[c->in_room]; - auto people_i = std::find(room->people.begin(), room->people.end(), c); + if (obj) { + sprintf(str, "%c%ld", UID_OBJ, obj->get_id()); + } else { + strcpy(str, "0"); + } + } else if (!str_cmp(field, "varexist") || !str_cmp(field, "varexists")) { + vd = find_var_cntx(SCRIPT(c)->global_vars, subfield, trig->context); + if (!vd.name.empty()) { + strcpy(str, "1"); + } else { + strcpy(str, "0"); + } + } else if (!str_cmp(field, "nextinroom")) { + CharData *next = nullptr; + const auto room = world[c->in_room]; - if (people_i != room->people.end()) { - ++people_i; - people_i = std::find_if(people_i, room->people.end(), [](const CharData *ch) { return !GET_INVIS_LEV(ch); }); + auto people_i = std::find(room->people.begin(), room->people.end(), c); if (people_i != room->people.end()) { - next = *people_i; - } - } + ++people_i; + people_i = std::find_if(people_i, room->people.end(), [](const CharData *ch) { return !GET_INVIS_LEV(ch); }); - if (next) { - sprintf(str, "%c%ld", UID_CHAR, next->get_uid()); - } else { - strcpy(str, ""); - } - } else if (!str_cmp(field, "position")) { - if (!*subfield) { - sprintf(str, "%d", static_cast(c->GetPosition())); - } else { - auto pos = std::clamp(static_cast(atoi(subfield)), EPosition::kPerish, --EPosition::kLast); - if (!IS_IMMORTAL(c)) { - if (c->IsOnHorse()) { - c->dismount(); + if (people_i != room->people.end()) { + next = *people_i; } - c->SetPosition(pos); } - } - } else if (!str_cmp(field, "wait") || !str_cmp(field, "lag")) { - int pos; - if (!*subfield || (pos = atoi(subfield)) <= 0) { - sprintf(str, "%d", c->get_wait()); - } else if (!IS_IMMORTAL(c)) { - char tmp; - if (sscanf(subfield, "%d %c", &pos, &tmp) == 2) { - if (tmp == 'p') { - SetWaitState(c, pos); - } - } - else { - SetWaitState(c, pos * kBattleRound); + if (next) { + sprintf(str, "%c%ld", UID_CHAR, next->get_uid()); + } else { + strcpy(str, ""); } - } - } else if (!str_cmp(field, "applyvalue")) { - int num; - int sum = 0; - for (num = 0; num < EApply::kNumberApplies; num++) { - if (!strn_cmp(subfield, apply_types[num], strlen(subfield))) - break; - } - if (num == EApply::kNumberApplies) { - sprintf(buf, "Не найден апплай '%s' в списке ApplyTypes", subfield); - trig_log(trig, buf); - return; - } - if (!c->affected.empty()) { - for (const auto &aff : c->affected) { - if (aff->location == num){ - sum += aff->modifier; + } else if (!str_cmp(field, "position")) { + if (!*subfield) { + sprintf(str, "%d", static_cast(c->GetPosition())); + } else { + auto pos = std::clamp(static_cast(atoi(subfield)), EPosition::kPerish, --EPosition::kLast); + if (!IS_IMMORTAL(c)) { + if (c->IsOnHorse()) { + c->dismount(); + } + c->SetPosition(pos); } } - } - sprintf(str, "%d", sum); - } else if (!str_cmp(field, "affect")) { - c->char_specials.saved.affected_by.gm_flag(subfield, affected_bits, str); - //подозреваю что никто из билдеров даже не вкурсе насчет всего функционала этого affect - //к тому же аффекты в том списке не все кличи например никак там не отображаются - } else if (!str_cmp(field, "affectedby")) { - char *p = strchr(subfield, ','); - if (!p) { - auto spell_id = FixNameAndFindSpellId(subfield); - if (spell_id == ESpell::kUndefined) { - sprintf(buf, "Не найден спелл %s в списке AffectedBy", subfield); - trig_log(trig, buf); - return; - } - if (spell_id >= ESpell::kFirst && spell_id < ESpell::kLast) { - for (const auto &affect : c->affected) { - if (affect->type == spell_id) { - sprintf(str, "%s", "1"); - return; + } else if (!str_cmp(field, "wait") || !str_cmp(field, "lag")) { + int pos; + + if (!*subfield || (pos = atoi(subfield)) <= 0) { + sprintf(str, "%d", c->get_wait()); + } else if (!IS_IMMORTAL(c)) { + char tmp; + if (sscanf(subfield, "%d %c", &pos, &tmp) == 2) { + if (tmp == 'p') { + SetWaitState(c, pos); } } - sprintf(str, "%s", "0"); + else { + SetWaitState(c, pos * kBattleRound); + } } - } else { + } else if (!str_cmp(field, "applyvalue")) { int num; - *(p++) = '\0'; - auto spell_id = FixNameAndFindSpellId(subfield); - if (spell_id == ESpell::kUndefined) { - sprintf(buf, "Не найден спелл %s в списке AffecteBby", p); - trig_log(trig, buf); - return; - } + int sum = 0; for (num = 0; num < EApply::kNumberApplies; num++) { - if (!str_cmp(p, apply_types[num])) - break; + if (!strn_cmp(subfield, apply_types[num], strlen(subfield))) + break; } if (num == EApply::kNumberApplies) { - sprintf(buf, "Не найден апплай '%s' в списке AffectedBy", p); + sprintf(buf, "Не найден апплай '%s' в списке ApplyTypes", subfield); trig_log(trig, buf); return; } - for (const auto &affect : c->affected) { - if (affect->type == spell_id) { - if (affect->location == num) { - sprintf(str, "%d", affect->modifier); - return; + if (!c->affected.empty()) { + for (const auto &aff : c->affected) { + if (aff->location == num){ + sum += aff->modifier; } } } - sprintf(str, "%s", "0"); - } - } else if (!str_cmp(field, "mobflag")) { - if (c->IsNpc()) { -// mudlog(fmt::format("mob flag {}", subfield)); - bool val = c->char_specials.saved.act.gm_flag(subfield, action_bits, str); - if (!val) { - trig_log(trig, fmt::format("mobflag: неправильный параметр в скобках - ({})", subfield)); - return; + sprintf(str, "%d", sum); + } else if (!str_cmp(field, "affect")) { + c->char_specials.saved.affected_by.gm_flag(subfield, affected_bits, str); + //подозреваю что никто из билдеров даже не вкурсе насчет всего функционала этого affect + //к тому же аффекты в том списке не все кличи например никак там не отображаются + } else if (!str_cmp(field, "affectedby")) { + char *p = strchr(subfield, ','); + if (!p) { + auto spell_id = FixNameAndFindSpellId(subfield); + if (spell_id == ESpell::kUndefined) { + sprintf(buf, "Не найден спелл %s в списке AffectedBy", subfield); + trig_log(trig, buf); + return; + } + if (spell_id >= ESpell::kFirst && spell_id < ESpell::kLast) { + for (const auto &affect : c->affected) { + if (affect->type == spell_id) { + sprintf(str, "%s", "1"); + return; + } + } + sprintf(str, "%s", "0"); + } + } else { + int num; + *(p++) = '\0'; + auto spell_id = FixNameAndFindSpellId(subfield); + if (spell_id == ESpell::kUndefined) { + sprintf(buf, "Не найден спелл %s в списке AffecteBby", p); + trig_log(trig, buf); + return; + } + for (num = 0; num < EApply::kNumberApplies; num++) { + if (!str_cmp(p, apply_types[num])) + break; + } + if (num == EApply::kNumberApplies) { + sprintf(buf, "Не найден апплай '%s' в списке AffectedBy", p); + trig_log(trig, buf); + return; + } + for (const auto &affect : c->affected) { + if (affect->type == spell_id) { + if (affect->location == num) { + sprintf(str, "%d", affect->modifier); + return; + } + } + } + sprintf(str, "%s", "0"); } - } - } else if (!str_cmp(field, "npcflag")) { - if (c->IsNpc()) { -// mudlog(fmt::format("npc flag {}", subfield)); - bool val = c->mob_specials.npc_flags.gm_flag(subfield, function_bits, str); - if (!val) { - trig_log(trig, fmt::format("npcflag: неправильный параметр в скобках - ({})", subfield)); + } else if (!str_cmp(field, "mobflag")) { + if (c->IsNpc()) { + // mudlog(fmt::format("mob flag {}", subfield)); + bool val = c->char_specials.saved.act.gm_flag(subfield, action_bits, str); + if (!val) { + trig_log(trig, fmt::format("mobflag: неправильный параметр в скобках - ({})", subfield)); + return; + } + } + } else if (!str_cmp(field, "npcflag")) { + if (c->IsNpc()) { + // mudlog(fmt::format("npc flag {}", subfield)); + bool val = c->mob_specials.npc_flags.gm_flag(subfield, function_bits, str); + if (!val) { + trig_log(trig, fmt::format("npcflag: неправильный параметр в скобках - ({})", subfield)); + return; + } + } + } else if (!str_cmp(field, "role")) { + std::string out; + if (c->get_role_bits().any()) { + print_bitset(c->get_role_bits(), npc_role_types, " ", out); + sprintf(str, "%s", out.c_str()); + } + } else if (!str_cmp(field, "leader")) { + if (c->has_master()) { + sprintf(str, "%c%ld", uid_type, (c->get_master())->get_uid()); + } + } else if (!str_cmp(field, "group")) { + CharData *l; + struct FollowerType *f; + if (!AFF_FLAGGED(c, EAffect::kGroup)) { return; } - } - } else if (!str_cmp(field, "role")) { - std::string out; - if (c->get_role_bits().any()) { - print_bitset(c->get_role_bits(), npc_role_types, " ", out); - sprintf(str, "%s", out.c_str()); - } - } else if (!str_cmp(field, "leader")) { - if (c->has_master()) { - sprintf(str, "%c%ld", uid_type, (c->get_master())->get_uid()); - } - } else if (!str_cmp(field, "group")) { - CharData *l; - struct FollowerType *f; - if (!AFF_FLAGGED(c, EAffect::kGroup)) { - return; - } - l = c->get_master(); - if (!l) { - l = c; - } - // l - лидер группы - sprintf(str + strlen(str), "%c%ld ", uid_type, l->get_uid()); - for (f = l->followers; f; f = f->next) { - if (!AFF_FLAGGED(f->follower, EAffect::kGroup)) { - continue; + l = c->get_master(); + if (!l) { + l = c; } - sprintf(str + strlen(str), "%c%ld ", uid_type, f->follower->get_uid()); - } - } else if (!str_cmp(field, "attackers")) { - size_t str_length = strlen(str); - for (auto it : combat_list) { - if (it.deleted) - continue; - if (it.ch->GetEnemy() != c) { - continue; + // l - лидер группы + sprintf(str + strlen(str), "%c%ld ", uid_type, l->get_uid()); + for (f = l->followers; f; f = f->next) { + if (!AFF_FLAGGED(f->follower, EAffect::kGroup)) { + continue; + } + sprintf(str + strlen(str), "%c%ld ", uid_type, f->follower->get_uid()); } - int n = snprintf(tmp, kMaxTrglineLength, "%c%ld ", UID_CHAR, it.ch->get_uid()); - if (str_length + n < kMaxTrglineLength) // not counting the terminating null character - { - strcpy(str + str_length, tmp); - str_length += n; - } else { - break; // too many attackers + } else if (!str_cmp(field, "attackers")) { + size_t str_length = strlen(str); + for (auto it : combat_list) { + if (it.deleted) + continue; + if (it.ch->GetEnemy() != c) { + continue; + } + int n = snprintf(tmp, kMaxTrglineLength, "%c%ld ", UID_CHAR, it.ch->get_uid()); + if (str_length + n < kMaxTrglineLength) // not counting the terminating null character + { + strcpy(str + str_length, tmp); + str_length += n; + } else { + break; // too many attackers + } } - } - } else if (!str_cmp(field, "people")) { - //const auto first_char = world[c->in_room]->first_character(); - const auto room = world[c->in_room]->people; - const auto first_char = std::find_if(room.begin(), room.end(), [](CharData *ch) { - return !GET_INVIS_LEV(ch); - }); - - if (first_char != room.end()) { - sprintf(str, "%c%ld", UID_CHAR, (*first_char)->get_uid()); - } else { - strcpy(str, ""); - } - } - else if (!str_cmp(field, "objs")) { - size_t str_length = strlen(str); - for (obj = c->carrying; obj; obj = obj->get_next_content()) { - int n = snprintf(tmp, kMaxTrglineLength, "%c%ld ", UID_OBJ, obj->get_id()); - if (str_length + n < kMaxTrglineLength) // not counting the terminating null character - { - strcpy(str + str_length, tmp); - str_length += n; + } else if (!str_cmp(field, "people")) { + //const auto first_char = world[c->in_room]->first_character(); + const auto room = world[c->in_room]->people; + const auto first_char = std::find_if(room.begin(), room.end(), [](CharData *ch) { + return !GET_INVIS_LEV(ch); + }); + + if (first_char != room.end()) { + sprintf(str, "%c%ld", UID_CHAR, (*first_char)->get_uid()); } else { - break; // too many carying objects + strcpy(str, ""); } } - } - else if (!str_cmp(field, "char") - || !str_cmp(field, "pc") - || !str_cmp(field, "npc") - || !str_cmp(field, "all")) { - int inroom; - - // Составление списка (для mob) - inroom = c->in_room; - if (inroom == kNowhere) { - trig_log(trig, "mob-построитель списка в kNowhere"); - return; - } - - size_t str_length = strlen(str); - for (const auto rndm : world[inroom]->people) { - if ((c == rndm) - || GET_INVIS_LEV(rndm)) { - continue; - } - - if ((*field == 'a') - || (!rndm->IsNpc() - && *field != 'n' - && rndm->desc) - || (rndm->IsNpc() - && IS_CHARMED(rndm) - && *field == 'c') - || (rndm->IsNpc() - && !IS_CHARMED(rndm) - && *field == 'n')) { - int n = snprintf(tmp, kMaxTrglineLength, "%c%ld ", UID_CHAR, rndm->get_uid()); + else if (!str_cmp(field, "objs")) { + size_t str_length = strlen(str); + for (obj = c->carrying; obj; obj = obj->get_next_content()) { + int n = snprintf(tmp, kMaxTrglineLength, "%c%ld ", UID_OBJ, obj->get_id()); if (str_length + n < kMaxTrglineLength) // not counting the terminating null character { strcpy(str + str_length, tmp); str_length += n; } else { - break; // too many characters + break; // too many carying objects } } } + else if (!str_cmp(field, "char") + || !str_cmp(field, "pc") + || !str_cmp(field, "npc") + || !str_cmp(field, "all")) { + int inroom; - return; - } else if (!str_cmp(field, "isnoob")) { - strcpy(str, Noob::is_noob(c) ? "1" : "0"); - } else if (!str_cmp(field, "nooboutfit")) { - std::string vnum_str = Noob::print_start_outfit(c); - snprintf(str, kMaxTrglineLength, "%s", vnum_str.c_str()); - } else { - vd = find_var_cntx(SCRIPT(c)->global_vars, field, trig->context); - if (!vd.name.empty()) { - sprintf(str, "%s", vd.value.c_str()); - } - else { - sprintf(buf2, "unknown char field: '%s'", field); - trig_log(trig, buf2); + // Составление списка (для mob) + inroom = c->in_room; + if (inroom == kNowhere) { + trig_log(trig, "mob-построитель списка в kNowhere"); + return; + } + + size_t str_length = strlen(str); + for (const auto rndm : world[inroom]->people) { + if ((c == rndm) + || GET_INVIS_LEV(rndm)) { + continue; + } + + if ((*field == 'a') + || (!rndm->IsNpc() + && *field != 'n' + && rndm->desc) + || (rndm->IsNpc() + && IS_CHARMED(rndm) + && *field == 'c') + || (rndm->IsNpc() + && !IS_CHARMED(rndm) + && *field == 'n')) { + int n = snprintf(tmp, kMaxTrglineLength, "%c%ld ", UID_CHAR, rndm->get_uid()); + if (str_length + n < kMaxTrglineLength) // not counting the terminating null character + { + strcpy(str + str_length, tmp); + str_length += n; + } else { + break; // too many characters + } + } + } + + return; + } else if (!str_cmp(field, "isnoob")) { + strcpy(str, Noob::is_noob(c) ? "1" : "0"); + } else if (!str_cmp(field, "nooboutfit")) { + std::string vnum_str = Noob::print_start_outfit(c); + snprintf(str, kMaxTrglineLength, "%s", vnum_str.c_str()); + } else { + vd = find_var_cntx(SCRIPT(c)->global_vars, field, trig->context); + if (!vd.name.empty()) { + sprintf(str, "%s", vd.value.c_str()); + } + else { + sprintf(buf2, "unknown char field: '%s'", field); + trig_log(trig, buf2); + } } - } + } // if (!char_handled) } else if (o) { if (text_processed(field, subfield, vd, str)) { return; diff --git a/src/engine/scripting/dg_triggers.cpp b/src/engine/scripting/dg_triggers.cpp index 4be3b5930..66a80d979 100644 --- a/src/engine/scripting/dg_triggers.cpp +++ b/src/engine/scripting/dg_triggers.cpp @@ -14,6 +14,7 @@ **************************************************************************/ #include "dg_scripts.h" +#include "dg_triggers.h" #include "engine/core/handler.h" #include "gameplay/magic/spells_info.h" #include "engine/olc/olc.h" diff --git a/src/engine/scripting/dg_triggers.h b/src/engine/scripting/dg_triggers.h new file mode 100644 index 000000000..48e3403fa --- /dev/null +++ b/src/engine/scripting/dg_triggers.h @@ -0,0 +1,11 @@ +#pragma once + +// Pure-logic functions from dg_triggers.cpp exposed for unit testing. +// These functions have no global side-effects and depend only on their arguments. + +char *one_phrase(char *arg, char *first_arg); +int is_substring(const char *sub, const char *string); +int word_check(const char *str, const char *wordlist); +int compare_cmd(int mode, const char *source, const char *dest); + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/scripting/trigger_indenter.cpp b/src/engine/scripting/trigger_indenter.cpp new file mode 100644 index 000000000..a055e352e --- /dev/null +++ b/src/engine/scripting/trigger_indenter.cpp @@ -0,0 +1,76 @@ +// trigger_indenter.cpp +// Implementation of TriggerIndenter class + +#include "trigger_indenter.h" + +#include "utils/utils.h" +#include "engine/core/sysdep.h" +#include +#include + +char *TriggerIndenter::indent(char *cmd, int *level) { + *level = std::max(0, *level); + if (*level == 0) { + reset(); + } + + int currlev, nextlev; + currlev = nextlev = *level; + + if (!cmd) { + return cmd; + } + + char *ptr = cmd; + skip_spaces(&ptr); + + if (!strn_cmp("case ", ptr, 5) || !strn_cmp("default", ptr, 7)) { + if (!indent_stack_.empty() + && !strn_cmp("case ", indent_stack_.top().c_str(), 5)) { + --currlev; + } else { + indent_stack_.push(ptr); + } + nextlev = currlev + 1; + } else if (!strn_cmp("if ", ptr, 3) || !strn_cmp("while ", ptr, 6) + || !strn_cmp("foreach ", ptr, 8) || !strn_cmp("switch ", ptr, 7)) { + ++nextlev; + indent_stack_.push(ptr); + } else if (!strn_cmp("elseif ", ptr, 7) || !strn_cmp("else", ptr, 4)) { + --currlev; + } else if (!strn_cmp("break", ptr, 5) || !strn_cmp("end", ptr, 3) + || !strn_cmp("done", ptr, 4)) { + if ((!strn_cmp("done", ptr, 4) || !strn_cmp("end", ptr, 3)) + && !indent_stack_.empty() + && (!strn_cmp("case ", indent_stack_.top().c_str(), 5) + || !strn_cmp("default", indent_stack_.top().c_str(), 7))) { + --currlev; + --nextlev; + indent_stack_.pop(); + } + if (!indent_stack_.empty()) { + indent_stack_.pop(); + } + --nextlev; + --currlev; + } + + if (nextlev < 0) nextlev = 0; + if (currlev < 0) currlev = 0; + + char *tmp = (char *) malloc(currlev * 2 + 1); + memset(tmp, 0x20, currlev * 2); + tmp[currlev * 2] = '\0'; + + tmp = str_add(tmp, ptr); + + cmd = (char *) realloc(cmd, strlen(tmp) + 1); + cmd = strcpy(cmd, tmp); + + free(tmp); + + *level = nextlev; + return cmd; +} + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/scripting/trigger_indenter.h b/src/engine/scripting/trigger_indenter.h new file mode 100644 index 000000000..1eac7a391 --- /dev/null +++ b/src/engine/scripting/trigger_indenter.h @@ -0,0 +1,38 @@ +// trigger_indenter.h +// Stateful indenter for DG script triggers + +#ifndef TRIGGER_INDENTER_H +#define TRIGGER_INDENTER_H + +#include +#include + +/** + * TriggerIndenter - stateful indenter for DG script triggers. + * Tracks nesting level of control structures (if/while/switch/case) and + * applies proper indentation to script commands. + * + * Replaces global thread_local stack with explicit state management. + */ +class TriggerIndenter { +public: + TriggerIndenter() = default; + ~TriggerIndenter() = default; + + // Indent a single command line. Updates level for next line. + // Returns newly allocated string (caller must free). + char *indent(char *cmd, int *level); + + // Reset indenter state (clears stack) + void reset() { + std::stack empty_stack; + indent_stack_.swap(empty_stack); + } + +private: + std::stack indent_stack_; +}; + +#endif // TRIGGER_INDENTER_H + +// vim: ts=4 sw=4 tw=0 noet syntax=cpp : diff --git a/src/engine/structs/flags.hpp b/src/engine/structs/flags.hpp index 2dd8b57c1..8877529dd 100644 --- a/src/engine/structs/flags.hpp +++ b/src/engine/structs/flags.hpp @@ -14,21 +14,21 @@ class BitFlag constexpr const static int number_of_bits = std::numeric_limits::type>::digits; constexpr BitFlag() = default; - constexpr BitFlag(EnumType value) : bits(1 << static_cast(value)) {} + constexpr BitFlag(EnumType value) : bits(std::size_t{1} << static_cast(value)) {} constexpr BitFlag(const BitFlag& other) : bits(other.bits) {} - constexpr BitFlag operator|(EnumType value) const { BitFlag result = *this; result.bits |= 1 << static_cast(value); return result; } - constexpr BitFlag operator&(EnumType value) const { BitFlag result = *this; result.bits &= 1 << static_cast(value); return result; } - constexpr BitFlag operator^(EnumType value) const { BitFlag result = *this; result.bits ^= 1 << static_cast(value); return result; } + constexpr BitFlag operator|(EnumType value) const { BitFlag result = *this; result.bits |= std::size_t{1} << static_cast(value); return result; } + constexpr BitFlag operator&(EnumType value) const { BitFlag result = *this; result.bits &= std::size_t{1} << static_cast(value); return result; } + constexpr BitFlag operator^(EnumType value) const { BitFlag result = *this; result.bits ^= std::size_t{1} << static_cast(value); return result; } constexpr BitFlag operator~() const { BitFlag result = *this; result.bits.flip(); return result; } constexpr BitFlag operator|(BitFlag other) const { BitFlag result = *this; result.bits |= other.bits; return result; } constexpr BitFlag operator&(BitFlag other) const { BitFlag result = *this; result.bits &= other.bits; return result; } constexpr BitFlag operator^(BitFlag other) const { BitFlag result = *this; result.bits ^= other.bits; return result; } - constexpr BitFlag& operator|=(EnumType value) { bits |= 1 << static_cast(value); return *this; } - constexpr BitFlag& operator&=(EnumType value) { bits &= 1 << static_cast(value); return *this; } - constexpr BitFlag& operator^=(EnumType value) { bits ^= 1 << static_cast(value); return *this; } + constexpr BitFlag& operator|=(EnumType value) { bits |= std::size_t{1} << static_cast(value); return *this; } + constexpr BitFlag& operator&=(EnumType value) { bits &= std::size_t{1} << static_cast(value); return *this; } + constexpr BitFlag& operator^=(EnumType value) { bits ^= std::size_t{1} << static_cast(value); return *this; } constexpr bool any() const { return bits.any(); } constexpr bool all() const { return bits.all(); } diff --git a/src/engine/structs/meta_enum.h b/src/engine/structs/meta_enum.h index bc70053fb..46bc49978 100644 --- a/src/engine/structs/meta_enum.h +++ b/src/engine/structs/meta_enum.h @@ -15,12 +15,12 @@ template struct Unimplemented {}; template -const std::string &NAME_BY_ITEM(const E item) { +const std::string &NAME_BY_ITEM(const E) { return Unimplemented::FAIL; } template -E ITEM_BY_NAME(const std::string &name) { +E ITEM_BY_NAME(const std::string &) { return Unimplemented::FAIL; } diff --git a/src/engine/structs/structs.h b/src/engine/structs/structs.h index e311fdc30..be1b905db 100644 --- a/src/engine/structs/structs.h +++ b/src/engine/structs/structs.h @@ -71,6 +71,7 @@ class ObjData; // forward declaration to avoid inclusion of obj.hpp and any d class Trigger; const int8_t kNowhere = 0; // nil reference for room-database +const ZoneRnum kNoZone = -1; // nil reference for zone-database const int8_t kNothing = -1; // nil reference for objects const int8_t kNobody = -1; // nil reference for mobiles diff --git a/src/engine/ui/alias.cpp b/src/engine/ui/alias.cpp index 2921b8f3a..9d84eeb9a 100644 --- a/src/engine/ui/alias.cpp +++ b/src/engine/ui/alias.cpp @@ -127,7 +127,7 @@ constexpr int kNumTokens{0}; void perform_complex_alias(struct iosystem::TextBlocksQueue *input_q, char *orig, struct alias_data *a) { struct iosystem::TextBlocksQueue temp_queue; - char *tokens[kNumTokens], *temp, *write_point; + char *tokens[1], *temp, *write_point; int num_of_tokens = 0, num; // First, parse the original string diff --git a/src/engine/ui/cmd/do_commands.cpp b/src/engine/ui/cmd/do_commands.cpp index 6b599e4cf..3d8c91c32 100644 --- a/src/engine/ui/cmd/do_commands.cpp +++ b/src/engine/ui/cmd/do_commands.cpp @@ -47,7 +47,7 @@ void do_commands(CharData *ch, char *argument, int/* cmd*/, int subcmd) { strcat(buf, "\r\n"); no++; } - } else if (cmd_info[i].minimum_level >= 0 && (socials == cmd_sort_info[i].is_social)) { + } else if (cmd_info[i].minimum_level >= 0 && (static_cast(socials) == cmd_sort_info[i].is_social)) { sprintf(buf + strlen(buf), "%-15s", cmd_info[i].command); if (!(no % 5)) strcat(buf, "\r\n"); diff --git a/src/engine/ui/cmd/do_telegram.cpp b/src/engine/ui/cmd/do_telegram.cpp index 096676efb..35b3d6ea0 100644 --- a/src/engine/ui/cmd/do_telegram.cpp +++ b/src/engine/ui/cmd/do_telegram.cpp @@ -73,9 +73,12 @@ bool TelegramBot::sendMessage(unsigned long chatId, const std::string &msg) { if (chatId < 1 || msg.empty()) return false; curl_easy_setopt(curl, CURLOPT_URL, this->uri.c_str()); + char *escaped_msg = curl_easy_escape(curl, msg.c_str(), msg.length()); snprintf(msgBuf, kMaxStringLength, this->chatIdParam.c_str(), chatId, - this->textParam.c_str(), curl_easy_escape(curl, msg.c_str(), msg.length())); + this->textParam.c_str(), escaped_msg); + curl_free(escaped_msg); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, msgBuf); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, noop_cb); diff --git a/src/engine/ui/cmd_god/do_inspect.h b/src/engine/ui/cmd_god/do_inspect.h index ce2f6f5b4..3d6629c48 100644 --- a/src/engine/ui/cmd_god/do_inspect.h +++ b/src/engine/ui/cmd_god/do_inspect.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include #ifndef BYLINS_SRC_CMD_GOD_INSPECT_H_ #define BYLINS_SRC_CMD_GOD_INSPECT_H_ diff --git a/src/engine/ui/cmd_god/do_liblist.cpp b/src/engine/ui/cmd_god/do_liblist.cpp index f9774eed1..993c6e837 100644 --- a/src/engine/ui/cmd_god/do_liblist.cpp +++ b/src/engine/ui/cmd_god/do_liblist.cpp @@ -196,7 +196,7 @@ int PrintOlist(const CharData *ch, int first, int last, std::string &out) { if (GetRealLevel(ch) >= kLvlGreatGod || ch->IsFlagged(EPrf::kCoderinfo)) { - ss << fmt::format(" Игра:{} Пост:{} Макс:{}", obj_proto.total_online(rnum), obj_proto.stored(rnum), GetObjMIW(rnum)); + ss << fmt::format(" Игра:{} Пост:{} Макс:{}", static_cast(obj_proto.total_online(rnum)), static_cast(obj_proto.stored(rnum)), GetObjMIW(static_cast(rnum))); const auto &script = prototype->get_proto_script(); if (!script.empty()) { diff --git a/src/engine/ui/cmd_god/do_set_all.h b/src/engine/ui/cmd_god/do_set_all.h index 2e5cf2a5d..954da0d2a 100644 --- a/src/engine/ui/cmd_god/do_set_all.h +++ b/src/engine/ui/cmd_god/do_set_all.h @@ -10,7 +10,13 @@ #define BYLINS_SRC_ENGINE_UI_CMD_GOD_DO_SET_ALL_H_ #include +#include + +#ifdef _WIN32 +#include +#else #include +#endif #include struct setall_inspect_request { diff --git a/src/engine/ui/cmd_god/do_show_zone_stat.cpp b/src/engine/ui/cmd_god/do_show_zone_stat.cpp index c55e30137..925e503e8 100644 --- a/src/engine/ui/cmd_god/do_show_zone_stat.cpp +++ b/src/engine/ui/cmd_god/do_show_zone_stat.cpp @@ -65,11 +65,11 @@ void DoShowZoneStat(CharData *ch, char *argument, int, int) { } int tmp1 = GetZoneRnum(atoi(arg1)); int tmp2 = GetZoneRnum(atoi(arg2)); - if (tmp1 >= 0 && !*arg2) { + if (tmp1 != kNoZone && !*arg2) { PrintZoneStat(ch, tmp1, tmp1, sort); return; } - if (tmp1 > 0 && tmp2 > 0) { + if (tmp1 != kNoZone && tmp2 != kNoZone) { PrintZoneStat(ch, tmp1, tmp2, sort); return; } diff --git a/src/engine/ui/cmd_god/do_stat.cpp b/src/engine/ui/cmd_god/do_stat.cpp index 61be8107e..b877de721 100644 --- a/src/engine/ui/cmd_god/do_stat.cpp +++ b/src/engine/ui/cmd_god/do_stat.cpp @@ -467,7 +467,7 @@ void do_stat_character(CharData *ch, CharData *k, const int virt = 0) { SendMsgToChar(kColorNrm, ch); SendMsgToChar(ch, "\r\n"); // информация о маршруте моба - if (k->mob_specials.dest_count > 0) { + if (k->mob_specials.dest_count > 0 && k->in_room != kNowhere) { // подготавливаем путевые точки std::stringstream str_dest_list; for (auto i = 0; i < k->mob_specials.dest_count; i++) { @@ -1145,7 +1145,7 @@ void do_stat_room(CharData *ch, const int rnum = 0) { SendMsgToChar(buf, ch); SendMsgToChar("Описание:\r\n", ch); - SendMsgToChar(RoomDescription::show_desc(rm->description_num), ch); + SendMsgToChar(GlobalObjects::descriptions().get(rm->description_num), ch); if (rm->ex_description) { sprintf(buf, "Доп. описание:%s", kColorCyn); diff --git a/src/engine/ui/interpreter.cpp b/src/engine/ui/interpreter.cpp index f40cb8d5c..ce21a55ac 100644 --- a/src/engine/ui/interpreter.cpp +++ b/src/engine/ui/interpreter.cpp @@ -28,6 +28,9 @@ #include "engine/ui/cmd_god/do_arena_restore.h" #include "engine/ui/cmd_god/do_at_room.h" #include "engine/ui/cmd_god/do_occupation.h" +#ifdef ENABLE_ADMIN_API +#include "engine/network/admin_api.h" +#endif #include "engine/ui/cmd_god/do_date.h" #include "engine/ui/cmd_god/do_dc.h" #include "engine/ui/cmd_god/do_delete_obj.h" @@ -2528,6 +2531,12 @@ void nanny(DescriptorData *d, char *argument) { d->state = EConState::kGetKeytable; break; +#ifdef ENABLE_ADMIN_API + case EConState::kAdminAPI: + admin_api_parse(d, argument); + break; +#endif + //. OLC states . case EConState::kOedit: oedit_parse(d, argument); break; diff --git a/src/engine/ui/modify.cpp b/src/engine/ui/modify.cpp index 4185c2813..32c06454b 100644 --- a/src/engine/ui/modify.cpp +++ b/src/engine/ui/modify.cpp @@ -1134,7 +1134,7 @@ char *next_page(char *str, CharData *ch) { break; default: color = kColorNrm; } - strncpy(str, color, strlen(color)); + memcpy(str, color, strlen(color)); str += (strlen(color) - 1); } else if (*str == '\x1B' && !spec_code) spec_code = true; diff --git a/src/gameplay/ai/graph.cpp b/src/gameplay/ai/graph.cpp index eaafa249c..dcc41e7f3 100644 --- a/src/gameplay/ai/graph.cpp +++ b/src/gameplay/ai/graph.cpp @@ -102,7 +102,9 @@ int find_first_step(RoomRnum src, RoomRnum target, CharData *ch) { // notrack мобам не помеха through_notrack = true; if (ch->IsFlagged(EMobFlag::kStayZone)) { - GetZoneRooms(world[src]->zone_rn, &rnum_start, &rnum_stop); + if (!GetZoneRooms(world[src]->zone_rn, &rnum_start, &rnum_stop)) { + return kBfsError; + } edge = EDGE_ZONE; } else { edge = EDGE_WORLD; diff --git a/src/gameplay/classes/recalc_mob_params_by_vnum.cpp b/src/gameplay/classes/recalc_mob_params_by_vnum.cpp index 1837f8595..84691ab0f 100644 --- a/src/gameplay/classes/recalc_mob_params_by_vnum.cpp +++ b/src/gameplay/classes/recalc_mob_params_by_vnum.cpp @@ -10,7 +10,7 @@ #include "engine/db/global_objects.h" #include "gameplay/magic/magic.h" #include "utils/utils.h" -#include "gameplay/mechanics/dungeons.cpp" +#include "gameplay/mechanics/dungeons.h" static constexpr int kWorstPossibleSaving = 300; static constexpr int kMaxMobResist = 95; @@ -973,15 +973,11 @@ void DGRecalcZone(const char *argument) { const int difficulty = atoi(arg4); if (zone_vnum < dungeons::kZoneStartDungeons) { -// SendMsgToChar(ch, - mudlog("Ошибка: перерасчёт разрешён только для зон с vnum >= 30000.\r\n"); + mudlog("Ошибка: перерасчёт разрешён только для зон с vnum >= 30000.\r\n"); return; } RecalcMobParamsInZoneWithLevel(zone_vnum, remorts, player_level, difficulty); - const int added_level_by_difficulty = difficulty * mob_classes::GetLvlPerDifficulty(); -// SendMsgToChar(ch, -// "Zone recalc done. (zone=%d, remorts=%d, base_lvl=%d, difficulty=%d, +lvl=%d)\r\n", // zone_vnum, remorts, player_level, difficulty, added_level_by_difficulty); } diff --git a/src/gameplay/communication/boards/boards_types.cpp b/src/gameplay/communication/boards/boards_types.cpp index ea134c2ec..0b542e21a 100644 --- a/src/gameplay/communication/boards/boards_types.cpp +++ b/src/gameplay/communication/boards/boards_types.cpp @@ -131,11 +131,13 @@ void Board::Save() { return; } std::ofstream file(file_.c_str()); +#ifndef _WIN32 if (chmod(file_.c_str(), S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { std::stringstream ss; ss << "Error chmod file: " << file_.c_str() << " (" << __FILE__ << " "<< __func__ << " "<< __LINE__ << ")"; mudlog(ss.str(), BRF, kLvlGod, SYSLOG, true); } +#endif if (!file.is_open()) { log("Error open file: %s! (%s %s %d)", file_.c_str(), __FILE__, __func__, __LINE__); return; diff --git a/src/gameplay/communication/boards/boards_types.h b/src/gameplay/communication/boards/boards_types.h index 82aa1546e..475bebd56 100644 --- a/src/gameplay/communication/boards/boards_types.h +++ b/src/gameplay/communication/boards/boards_types.h @@ -56,7 +56,7 @@ class Board { MessageListType messages; // список сообщений - const auto get_type() const { return type_; } + auto get_type() const { return type_; } const auto &get_name() const { return name_; } void set_name(const std::string &new_name) { name_ = new_name; } @@ -84,7 +84,7 @@ class Board { void set_file_name(const std::string &file_name) { file_ = file_name; } void set_clan_rent(const int value) { clan_rent_ = value; } - const auto get_clan_rent() const { return clan_rent_; } + auto get_clan_rent() const { return clan_rent_; } void set_pers_unique(const int uid) { pers_unique_ = uid; } const auto &get_pers_uniq() const { return pers_unique_; } @@ -96,8 +96,8 @@ class Board { auto messages_count() const { return messages.size(); } void erase_message(const size_t index); int count_unread(time_t last_read) const; - const auto get_last_message() const { return *messages.begin(); } - const auto get_message(const size_t index) const { return messages[index]; } + auto get_last_message() const { return *messages.begin(); } + auto get_message(const size_t index) const { return messages[index]; } void Load(); diff --git a/src/gameplay/communication/parcel.cpp b/src/gameplay/communication/parcel.cpp index 391a40905..06fceef81 100644 --- a/src/gameplay/communication/parcel.cpp +++ b/src/gameplay/communication/parcel.cpp @@ -15,6 +15,7 @@ #include "gameplay/mechanics/dungeons.h" #include +#include "engine/db/global_objects.h" #include @@ -74,12 +75,12 @@ SenderListType return_list; // временный список на возвра // * Отдельный лог для отладки. void parcel_log(const char *format, ...) { - const char *filename = "../log/parcel.log"; + const auto filename = runtime_config.log_dir() + "/parcel.log"; static FILE *file = 0; if (!file) { - file = fopen(filename, "a"); + file = fopen(filename.c_str(), "a"); if (!file) { - log("SYSERR: can't open %s!", filename); + log("SYSERR: can't open %s!", filename.c_str()); return; } opened_files.push_back(file); diff --git a/src/gameplay/communication/remember.cpp b/src/gameplay/communication/remember.cpp index ca7559244..9c05c286f 100644 --- a/src/gameplay/communication/remember.cpp +++ b/src/gameplay/communication/remember.cpp @@ -90,7 +90,7 @@ void CharRemember::add_str(std::string text, int flag) { break; case PERSONAL: add_to_cont(personal_, buffer); break; - case GROUP:// added by WorM групптелы 2010.10.13 + case Remember::GROUP:// added by WorM групптелы 2010.10.13 add_to_cont(group_, buffer); break; case GOSSIP: add_to_cont(gossip, buffer); @@ -118,7 +118,7 @@ std::string CharRemember::get_text(int flag) const { break; case PERSONAL: buffer = get_from_cont(personal_, num_str_); break; - case GROUP:// added by WorM групптелы 2010.10.13 + case Remember::GROUP:// added by WorM групптелы 2010.10.13 buffer = get_from_cont(group_, num_str_); break; case GOSSIP: buffer = get_from_cont(gossip, num_str_); diff --git a/src/gameplay/fight/fight.cpp b/src/gameplay/fight/fight.cpp index 687d8fcf6..95b424001 100644 --- a/src/gameplay/fight/fight.cpp +++ b/src/gameplay/fight/fight.cpp @@ -2051,11 +2051,11 @@ void perform_violence() { continue; } - const int initiative = calc_initiative(it.ch, true); + int initiative = calc_initiative(it.ch, true); if (initiative > 100) { //откуда больше 100?????? log("initiative calc: %s (%d) == %d", GET_NAME(it.ch), GET_MOB_VNUM(it.ch), initiative); } - std::clamp(initiative, -100, 100); + initiative = std::clamp(initiative, -100, 100); if (initiative == 0) { it.ch->initiative = -100; //Если кубик выпал в 0 - бей последним шанс 1 из 201 min_init = MIN(min_init, -100); diff --git a/src/gameplay/mechanics/bonus.cpp b/src/gameplay/mechanics/bonus.cpp index 410c2162d..f71b23e49 100644 --- a/src/gameplay/mechanics/bonus.cpp +++ b/src/gameplay/mechanics/bonus.cpp @@ -6,6 +6,7 @@ #include "bonus_command_parser.h" #include "engine/ui/modify.h" #include "engine/entities/char_player.h" +#include "engine/db/global_objects.h" namespace Bonus { const size_t MAXIMUM_BONUS_RECORDS = 10; @@ -57,7 +58,8 @@ void bonus_log_add(const std::string &name) { utils::EraseAll(buf, "\r\n"); bonus_log.push_back(buf); - std::ofstream fout("../log/bonus.log", std::ios_base::app); + const auto bonus_log_file = runtime_config.log_dir() + "/bonus.log"; + std::ofstream fout(bonus_log_file, std::ios_base::app); fout << buf << "\r\n"; fout.close(); } @@ -201,7 +203,8 @@ bool is_bonus_active() { // загружает лог бонуса из файла void bonus_log_load() { - std::ifstream fin("../log/bonus.log"); + const auto bonus_log_file = runtime_config.log_dir() + "/bonus.log"; + std::ifstream fin(bonus_log_file); if (!fin.is_open()) { return; } diff --git a/src/gameplay/mechanics/deathtrap.cpp b/src/gameplay/mechanics/deathtrap.cpp index a1f2411b7..7dc761962 100644 --- a/src/gameplay/mechanics/deathtrap.cpp +++ b/src/gameplay/mechanics/deathtrap.cpp @@ -13,6 +13,7 @@ #include "gameplay/fight/fight_stuff.h" #include "gameplay/mechanics/damage.h" #include "boat.h" +#include "engine/db/global_objects.h" extern void death_cry(CharData *ch, CharData *killer); @@ -81,12 +82,12 @@ void deathtrap::activity() { // * Логирование в отдельный файл уходов в дт для интересу и мб статистики. void deathtrap::log_death_trap(CharData *ch) { - const char *filename = "../log/death_trap.log"; + const auto filename = runtime_config.log_dir() + "/death_trap.log"; static FILE *file = 0; if (!file) { - file = fopen(filename, "a"); + file = fopen(filename.c_str(), "a"); if (!file) { - log("SYSERR: can't open %s!", filename); + log("SYSERR: can't open %s!", filename.c_str()); return; } opened_files.push_back(file); diff --git a/src/gameplay/mechanics/depot.cpp b/src/gameplay/mechanics/depot.cpp index bb3930ef8..3fb093dd3 100644 --- a/src/gameplay/mechanics/depot.cpp +++ b/src/gameplay/mechanics/depot.cpp @@ -22,6 +22,7 @@ #include #include +#include "engine/db/global_objects.h" extern int bank(CharData *, void *, int, char *); extern void olc_update_object(int robj_num, ObjData *obj, ObjData *olc_proto); @@ -99,12 +100,12 @@ DepotListType depot_list; // список личных хранилищ // * Капитально расширенная версия сислога для хранилищ. void depot_log(const char *format, ...) { - const char *filename = "../log/depot.log"; + const auto filename = runtime_config.log_dir() + "/depot.log"; static FILE *file = 0; if (!file) { - file = fopen(filename, "a"); + file = fopen(filename.c_str(), "a"); if (!file) { - log("SYSERR: can't open %s!", filename); + log("SYSERR: can't open %s!", filename.c_str()); return; } opened_files.push_back(file); diff --git a/src/gameplay/mechanics/glory_const.cpp b/src/gameplay/mechanics/glory_const.cpp index 59020508f..820782f55 100644 --- a/src/gameplay/mechanics/glory_const.cpp +++ b/src/gameplay/mechanics/glory_const.cpp @@ -132,11 +132,11 @@ void glory_hide(CharData *ch, } void transfer_log(const char *format, ...) { - const char *filename = "../log/glory_transfer.log"; + const auto filename = runtime_config.log_dir() + "/glory_transfer.log"; - FILE *file = fopen(filename, "a"); + FILE *file = fopen(filename.c_str(), "a"); if (!file) { - log("SYSERR: can't open %s!", filename); + log("SYSERR: can't open %s!", filename.c_str()); return; } diff --git a/src/gameplay/mechanics/glory_misc.cpp b/src/gameplay/mechanics/glory_misc.cpp index bc83b17cc..946ba41b7 100644 --- a/src/gameplay/mechanics/glory_misc.cpp +++ b/src/gameplay/mechanics/glory_misc.cpp @@ -32,10 +32,10 @@ GloryLogType glory_log; // * Загрузка лога славы. void load_log() { - const char *glory_file = "../log/glory.log"; + const auto glory_file = runtime_config.log_dir() + "/glory.log"; std::ifstream file(glory_file); if (!file.is_open()) { - log("GloryLog: не удалось открыть файл на чтение: %s", glory_file); + log("GloryLog: не удалось открыть файл на чтение: %s", glory_file.c_str()); return; } @@ -86,10 +86,10 @@ void save_log() { for (GloryLogType::const_iterator it = glory_log.begin(); it != glory_log.end(); ++it) out << it->first << " " << it->second->type << " " << it->second->num << " " << it->second->karma << "\n"; - const char *glory_file = "../log/glory.log"; + const auto glory_file = runtime_config.log_dir() + "/glory.log"; std::ofstream file(glory_file); if (!file.is_open()) { - log("GloryLog: не удалось открыть файл на запись: %s", glory_file); + log("GloryLog: не удалось открыть файл на запись: %s", glory_file.c_str()); return; } file << out.rdbuf(); diff --git a/src/gameplay/mechanics/sight.cpp b/src/gameplay/mechanics/sight.cpp index 900092e52..ed1bc417b 100644 --- a/src/gameplay/mechanics/sight.cpp +++ b/src/gameplay/mechanics/sight.cpp @@ -135,7 +135,7 @@ void look_at_room(CharData *ch, int ignore_brief, bool msdp_mode) { if (is_dark(ch->in_room) && !ch->IsFlagged(EPrf::kHolylight)) { SendMsgToChar("Слишком темно...\r\n", ch); } else if ((!ch->IsNpc() && !ch->IsFlagged(EPrf::kBrief)) || ignore_brief || ROOM_FLAGGED(ch->in_room, ERoomFlag::kDeathTrap)) { - show_extend_room(RoomDescription::show_desc(world[ch->in_room]->description_num).c_str(), ch); + show_extend_room(GlobalObjects::descriptions().get(world[ch->in_room]->description_num).c_str(), ch); } if (ch->IsFlagged(EPrf::kAutoexit) && !ch->IsFlagged(EPlrFlag::kScriptWriter)) { diff --git a/src/gameplay/skills/townportal.cpp b/src/gameplay/skills/townportal.cpp index 3246f55ed..49e5344f2 100644 --- a/src/gameplay/skills/townportal.cpp +++ b/src/gameplay/skills/townportal.cpp @@ -212,7 +212,7 @@ Runestone &RunestoneRoster::FindRunestone(RoomVnum vnum) { auto predicate = [vnum](const Runestone &p) { return p.GetRoomVnum() == vnum; }; auto it = std::find_if(begin(), end(), predicate); if (it != end()) { - return *it.base(); + return *it; } return incorrect_stone_; @@ -222,7 +222,7 @@ Runestone &RunestoneRoster::FindRunestone(std::string_view name) { auto predicate = [name](const Runestone &p) { return p.GetName() == name; }; auto it = std::find_if(begin(), end(), predicate); if (it != end()) { - return *it.base(); + return *it; } return incorrect_stone_; diff --git a/src/third_party_libs/nlohmann/json.hpp b/src/third_party_libs/nlohmann/json.hpp new file mode 100644 index 000000000..e55fb77c6 --- /dev/null +++ b/src/third_party_libs/nlohmann/json.hpp @@ -0,0 +1,24765 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifndef JSON_HAS_STATIC_RTTI + #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 + #define JSON_HAS_STATIC_RTTI 1 + #else + #define JSON_HAS_STATIC_RTTI 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows to call any std function as if (e.g. with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +static void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +inline constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#include // tuple +#include // char_traits + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +///////////////// +// char_traits // +///////////////// + +// Primary template of char_traits calls std char_traits +template +struct char_traits : std::char_traits +{}; + +// Explicitly define char traits for unsigned char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = unsigned char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(EOF); + } +}; + +// Explicitly define char traits for signed char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = signed char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(EOF); + } +}; + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& + is_complete_type < + detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template