• Уважаемые гости и новички, приветствуем Вас на нашем форуме
    Здесь вы можете найти ответы практически на все свои вопросы о серии игр «Готика» (в том числе различных модах на нее), «Ведьмак», «Ризен», «Древние свитки», «Эра дракона» и о многих других играх. Можете также узнать свежие новости о разработке новых проектов, восхититься творчеством наших форумчан, либо самим показать, что вы умеете. Ну и наконец, можете обсудить общие увлечения или просто весело пообщаться с посетителями «Таверны».

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!
  • Друзья, доброго времени суток!
    Стартовал новый литературный конкурс от "Ордена Хранителей" - "Пираты Миртанского моря".
    Каждый может принять в нём участие и снискать славу и уважение, а в случае занятия призового места ещё и получить награду. Дерзайте
  • Дорогие друзья, год подходит к концу, и пришло время подвести его итоги и наградить достойных

    Не ленитесь, голосуйте в этой теме за тех форумчан, которые по вашему мнению больше всех проявили себя в этом году
    По желанию, аргументировать свой выбор можете в теме обсуждения голосования.

4. Работа с Gothic API. Кроссплатформенный плагин. Выводим текст на экран.

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625
  • Первое сообщение
  • #1

Русский English


  • * В этом туторе будут использоваться препроцессорные команды для раздельной компиляции кроссплатформенного кода. Если это для Вас слишком сложно, добавлю внизу поста пример кода без них. Однако настоятельно рекомендую вникнуть в реализацию с использованием препроцессора.

    Для начала работы возьмем предыдущий плагин. Тестировать работу будем на Gothic II: NoTR, а затем распишем код для остальных.
    Установим конфигурацию проекта на G2A Release.

    Подготовка рабочего пространства
    Для удобства создадим новый исходный файл под каталогом Plugin:
    73955
    Подключим заголовок UnionAfx.h, а далее очень важно.

    Каждый интерфейс движка обернут под пространства имен (Gothic_I_Classic, Gothic_I_Addon, Gothic_II_Classic, Gothic_II_Addon соответственно). Поскольку мы пишем тестовый код под Gothic II NoTR, то определим пространство Gothic_II_Addon.

    В итоге получим файл примерно с такой начинкой:
    C++:
    #include <UnionAfx.h>
    
    namespace Gothic_II_Addon {
    
    }

    Однако если мы захотим изменить конфигурацию проекта под другой движок, компилятор выбьет ошибку, что код внутри Gothic_II_Addon ему непонятен.
    Поверх пространства допишем команду #ifdef __G2A. Благодаря этому компилятор сможет исключать из построения весь лишний код, когда тот будет не нужен.
    Эта конструкция будет базовой для всех кроссплатформенных проектов:
    C++:
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
    }
    #endif



    Добавление циклической функции
    Самый простой способ вызывать функции из разных файлов исходного кода - ключевое слово extern. В конце нашего файла создадим функцию Loop, а в Application.cpp объявим ее через extern. Вызовем функцию из предопределенной функции Game_Loop.
    C++:
    // Example.cpp
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
    }
    #endif
    
    void Loop() {
    
    }
    C++:
    // Application.cpp
    extern void Loop();
    
    void Game_Loop() {
      Loop();
    }



    Вывод текста на экран
    В Example.cpp в Gothic_II_Addon добавим функцию вывода текста. Текст, как можно догадаться, будет выводиться циклически вызовом из функции Loop.
    Вывод будем производить на вьюпорт screen.
    screen - это экземпляр zCView (можно посмотреть в файлах zView.h). Он имеет метод Print для вывода текста по координатам, PrintCX выравнивающий текст по горизонтали, PrintCY по вертикали и PrintCXY рисующий текст в центре экрана. Возьмем последнюю.
    Example: screen->PrintCXY( "Hello" );

    В качестве текста возьмем информацию об имени персонажа в фокусе игрока.
    Игрок определяется глобальной переменной player класса oCNpc.
    Класс имеет два подходящих метода GetFocusVob - берет любую сущность в фокусе и GetFocusNpc - берет конкретно нпс. Она нам и нужна.
    Example: oCNpc* focusNpc = playerGetFocusNpc();

    Далее необходимо получить имя npc. Вспоминаем скрипты - нпс имеет массив имен из 5 строк. Нам нужна первая. Воспользуемся методом GetName указав индекс строки 0.
    Example: zSTRING npcName = focusNpc ->GetName( 0 )

    Теперь собираем все воедино. Не забываем учесть, что npc не всегда присутствует в фокусе, по этому вывод необходимо производить когда указатель на focusNpc не равен null. Поэтому все операции вписываем под блок if( focusNpc )

    Код на данном этапе должен иметь такой вид:
    C++:
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    #endif
    
    void Loop() {
    
    }



    Расширение кода на все движки:
    Копируем код, меняя в нем пространство имен и определение препроцессора:
    73956

    В функции Loop добавляем код вызова функций PrintScreen.
    Чтобы вызывать только те функции, которые относятся к соответствующему движку - обратимся к экземпляру Union и запросим текущую версию движка методом GetEngineVersion.
    TEngineVersion engineVersion = Union.GetEngineVersion();

    Поставим проверки на соответствие и вызовем функции.
    C++:
    if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();

    Но как говорилось ранее, собирая проект для одного движка - остальные будут выдавать ошибку:
    73957


    Поэтому также обернем вызовы под #ifdef, исключив из построения ненужные вызовы:
    C++:
    void Loop() {
      TEngineVersion engineVersion = Union.GetEngineVersion();
    
    #ifdef __G1
      if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();
    #endif
    
    #ifdef __G1A
      if( engineVersion == Engine_G1A )
        Gothic_I_Addon::PrintScreen();
    #endif
    
    #ifdef __G2
      if( engineVersion == Engine_G2 )
        Gothic_II_Classic::PrintScreen();
    #endif
    
    #ifdef __G2A
      if( engineVersion == Engine_G2A )
        Gothic_II_Addon::PrintScreen();
    #endif
    }

    Тестовая компиляция и запуск
    Компилируем плагин в конфигурации G2A Release. Проект должен быстро собраться, поскольку отключены 3 движка. Запускаем плагин, заходим в игру и проверяем, чтобы при наведении фокуса на npc выводилось его имя.
    73958
    В центре экрана выводится дублирующее имя. Значит все работает исправно.



    Финальная компиляция и запуск:
    Включаем конфигурацию сборки Release. Замечаем, что стали активными все блоки, обернутые ранее под команду #ifdef. Это значит, что в игру вступили все движки и они готовы к работе в кроссплатформенном режиме. Компилируем проект, это займет значительно больше времени, чем при тестовом запуске.

    Результаты:
    Gothic I Classic
    73959

    Gothic I Addon
    73960

    Gothic II Classic
    73961

    Gothic II Addon
    73962





    Код без препроцессорных команд:
    C++:
    namespace Gothic_I_Classic {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_I_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_II_Classic {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_II_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    void Loop() {
      TEngineVersion engineVersion = Union.GetEngineVersion();
    
      if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();
    
      if( engineVersion == Engine_G1A )
        Gothic_I_Addon::PrintScreen();
    
      if( engineVersion == Engine_G2 )
        Gothic_II_Classic::PrintScreen();
    
      if( engineVersion == Engine_G2A )
        Gothic_II_Addon::PrintScreen();
    }

  • * This tutorial will use preprocessor instructions to separately compile cross-platform code. If this is too complicated for you, I’ll add an example code without them at the bottom of the post. However, I strongly recommend that you delve into the implementation using a preprocessor.

    To get started, take the previous plugin. We will test the work on Gothic II: NoTR, and then we will write the code for the rest.
    Set the project configuration to G2A Release.

    Workspace preparation
    For convenience, create a new source file under the directory Plugin:
    73955
    We will include the header UnionAfx.h, and then it is very important.

    Each engine interface is wrapped under namespaces (Gothic_I_Classic, Gothic_I_Addon, Gothic_II_Classic, Gothic_II_Addon ). Because we are writing test code for Gothic II NoTR, we define the space Gothic_II_Addon.

    As a result, we get a file with something like this:
    C++:
    #include <UnionAfx.h>
    
    namespace Gothic_II_Addon {
    
    }

    However, if we want to change the configuration of the project for a different engine, the compiler will throw out an error that the code inside Gothic_II_Addon is unknown to it.
    We’ll add a command over namespace #ifdef __G2A. Due to it, the compiler will be able to exclude all unnecessary code from construction when it will not be needed.
    This code will be the base for all cross-platform projects:
    C++:
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
    }
    #endif



    Adding a loop Function
    The easiest way to call functions from different source code files is to use the keyword extern. At the end of our file, create a function Loop, and in Application.cpp define it via extern. Call the function from a predefined function Game_Loop.
    C++:
    // Example.cpp
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
    }
    #endif
    
    void Loop() {
    
    }
    C++:
    // Application.cpp
    extern void Loop();
    
    void Game_Loop() {
      Loop();
    }



    Display text
    In Example.cpp in Gothic_II_Addon let's add text output function. Текст, как можно догадаться, будет the text, as you might guess, will be displayed cyclically by calling from the function Loop.
    The output will be produced via the viewport.screen.
    screen - is an instance of zCView (you can see it in the file zView.h). It has the method Print for printing text with coordinates, PrintCX with text horizontal align, PrintCY with vertical align and PrintCXY text in the screen center. Let's use the last one.
    Example: screen->PrintCXY( "Hello" );

    As the text, we take information about the name of the character in the focus of the player.
    The player is defined in global variable player of the class oCNpc.
    The class has two methods GetFocusVob - any object in focus and GetFocusNpc - npc in focus
    Example: oCNpc* focusNpc = playerGetFocusNpc();

    Next, you need to get the name npc. NPC has an array of names of 5 lines. We need the first one. We use the method GetName with index 0.
    Example: zSTRING npcName = focusNpc ->GetName( 0 )

    Now we are putting it all together. Do not forget to take into account that npc is not always present in focus, therefore, the conclusion must be made when the pointer to focusNpc no equal null. Therefore, we write all operations under the blockif( focusNpc )

    The code at this stage should look like this:
    C++:
    #include <UnionAfx.h>
    
    #ifdef __G2A
    namespace Gothic_II_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    #endif
    
    void Loop() {
    
    }



    Code extension to all the engines
    We copy the code, changing the namespace and the definition of the preprocessor in it:
    73956

    In function Loop add function call codePrintScreen.
    To call only those functions that relate to the corresponding engine, we use the instance of Union and request the current version of the engine using the method GetEngineVersion.
    TEngineVersion engineVersion = Union.GetEngineVersion();

    We put the checks for compliance and call the function.
    C++:
    if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();

    But as mentioned earlier, when collecting a project for one engine, the rest will give an error:
    73957


    Therefore, we also wrap the calls under #ifdef, eliminating unnecessary calls from the construction:
    C++:
    void Loop() {
      TEngineVersion engineVersion = Union.GetEngineVersion();
    
    #ifdef __G1
      if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();
    #endif
    
    #ifdef __G1A
      if( engineVersion == Engine_G1A )
        Gothic_I_Addon::PrintScreen();
    #endif
    
    #ifdef __G2
      if( engineVersion == Engine_G2 )
        Gothic_II_Classic::PrintScreen();
    #endif
    
    #ifdef __G2A
      if( engineVersion == Engine_G2A )
        Gothic_II_Addon::PrintScreen();
    #endif
    }

    Test compilation and launch
    We compile the plugin in the G2A Release configuration. The project should be quickly assembled, since 3 engines are disabled. We launch the plugin, go into the game and check that when focusing on npc its name is displayed.
    73958
    A duplicate name is displayed in the center of the screen. So everything is working properly.



    Final compilation and launch:
    Enable build configuration Release. We notice that all blocks wrapped previously under the command have become active #ifdef. This means that all the engines have entered the game and they are ready to work in cross-platform mode. We compile the project, it will take much longer than with a test run.

    Results:
    Gothic I Classic
    73959

    Gothic I Addon
    73960

    Gothic II Classic
    73961

    Gothic II Addon
    73962





    Code without preprocessor commands:
    C++:
    namespace Gothic_I_Classic {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_I_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_II_Classic {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    namespace Gothic_II_Addon {
    
      void PrintScreen() {
       
        oCNpc* focusNpc = player->GetFocusNpc();
        if( focusNpc ) {
    
          zSTRING npcName = focusNpc->GetName( 0 );
          screen->PrintCXY( npcName );
        }
      }
    
    }
    
    void Loop() {
      TEngineVersion engineVersion = Union.GetEngineVersion();
    
      if( engineVersion == Engine_G1 )
        Gothic_I_Classic::PrintScreen();
    
      if( engineVersion == Engine_G1A )
        Gothic_I_Addon::PrintScreen();
    
      if( engineVersion == Engine_G2 )
        Gothic_II_Classic::PrintScreen();
    
      if( engineVersion == Engine_G2A )
        Gothic_II_Addon::PrintScreen();
    }
 
Последнее редактирование модератором:

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625
How can I make function call every second? I want to create mana regeneration plugin.
Not frame, but second? For example:
C++:
void YourLoop() {
    static CTimer timer;
    timer.Attach();
 
    if( timer( 0, 1000 ) ) { // ID 0, delay 1000ms (1s)
     
        // TO DO example
        int& mana_max      = player->attribute[NPC_ATR_MANAMAX];
        int& mana_current = player->attribute[NPC_ATR_MANA];
        if( mana_current < mana_max )
          mana_current++;
    }
}
 
Последнее редактирование:

JKow

Участник форума
Регистрация
5 Июл 2019
Сообщения
5
Благодарности
0
Баллы
95
Thanks, it works well. I have one more question. To detect if game is paused I defined global bool variable shared between OnGameLoop, OnGamePause, OnGameUnpause. It works, but I was wondering if there is another solution.

C++:
//ManaRegeneration.cpp
#include "UnionAfx.h"

bool gamePaused = false;

#ifdef __G2A
namespace Gothic_II_Addon {

    void RegenerateMana() {
        if (player->attribute[NPC_ATR_MANA] < player->attribute[NPC_ATR_MANAMAX]) {
            player->ChangeAttribute(NPC_ATR_MANA, int(player->attribute[NPC_ATR_MANAMAX] * 0.02));
        }
    }
}
#endif

void OnGameLoop() {
    static CTimer timer;
    timer.Attach();

    if (timer(0, 1000) && !gamePaused) {
        Gothic_II_Addon::RegenerateMana();
    }
}

void OnGamePause() {
    gamePaused = true;
}

void OnGameUnpause() {
    gamePaused = false;
}

C++:
//Application.cpp
...
extern void OnGameLoop();
extern void OnGamePause();
extern void OnGameUnpause();

void Game_Loop() {
    OnGameLoop();
}
...
void Game_Pause() {
    OnGamePause();
}

void Game_Unpause() {
    OnGameUnpause();
}
 
Последнее редактирование:

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625
JKow, sure.
oCGame::singleStep -> True if game on pause.
CTimer::Suspend -> pauses the timer. If True - loop will not be called. Else - on resume it will continue where stopped.
+ example if u works with several NPCs. For everyone u can assign their own timer, if u give them an ID equal to their pointer.

C++:
#ifdef __G2A
namespace Gothic_II_Addon {

  void RegenerateMana() {
    static CTimer timer;
    timer.Attach();

    // For a more specific calculation - use the timer ID from pointer to NPC
    // if u work with NOT ONLY player. For each NPC delay can be calculating
    // personally for everyone. Else u can write 0 instead ID.
    uint ID = (uint)player;

    // suspend timer cycle by ID, where ogame->singleStep is the game pause state
    timer.Suspend( ID, ogame->singleStep );

    // Block will be work if ogame->singleStep != 0
    if( timer( ID, 1000 ) )
      if( player->attribute[NPC_ATR_MANA] < player->attribute[NPC_ATR_MANAMAX] )
        player->ChangeAttribute( NPC_ATR_MANA, int( player->attribute[NPC_ATR_MANAMAX] * 0.02 ) );
  }
}
#endif

void OnGameLoop() {
TEngineVersion engineVersion = Union.GetEngineVersion();
#ifdef __G2A
  if( engineVersion == Engine_G2A )
    Gothic_II_Addon::RegenerateMana();
#endif
}
 
Последнее редактирование:

Slavemaster


Модостроитель
Регистрация
10 Июн 2019
Сообщения
1.086
Благодарности
1.904
Баллы
320
For everyone u can assign their own timer, if u give them an ID equal to their pointer.
До каких пор указатель на НПС остается валидным? Мне, например, надо ДОТ от горения снять, когда перс выгружается, иначе происходит вылет. Для этого я выполняю проверку:
C++:
        if (!info->target || !info->target->globalVobTreeNode)
        {
            delete this;
            return;
        }
Насколько это корректно? Может ли оказаться так, что деструктор для этого непися уже был выполнен?
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625
До каких пор указатель на НПС остается валидным?
Валидным он остается до тех пор, пока количество ссылок на него > 0.
Если воб используется процедурой, ему назначается +1 ссылка методом AddRef. Это гарантирует, что он не будет удален из памяти до конца вычислений. А при завершении вызывается Release, и когда количество ссылок падает до 0, то объект удаляется из памяти.

Насколько это корректно? Может ли оказаться так, что деструктор для этого непися уже был выполнен?
Какого рода эффект используешь?
 

Slavemaster


Модостроитель
Регистрация
10 Июн 2019
Сообщения
1.086
Благодарности
1.904
Баллы
320
Какого рода эффект используешь?
Сам дот тупо значение хп меняет в GameLoop. Плюс костыль на добивание:
C++:
    void TDamageInfo::DoDamageUnhooked(int damage)
    {
        oCNpc::oSDamageDescriptor desc;
        ZeroMemory(&desc, sizeof(desc));
        desc.pVobAttacker = npcAttacker;
        desc.pNpcAttacker = npcAttacker;
        desc.aryDamage[oEDamageIndex::oEDamageIndex_Fall] = damage + target->protection[oEDamageIndex::oEDamageIndex_Fall];
        desc.fDamageTotal = damage;
        desc.enuModeWeapon = NPC_WEAPON_MAX;
        desc.bFinished = 1;
        desc.fDamageMultiplier = 1;
        desc.enuModeDamage = 128;
        desc.bIsDead = 1;
        desc.aryDamage[0] = TDamageInfo::DISABLE_HOOK;

#if defined(COMPILE_G2) || defined(COMPILE_G2A)
        desc.bDamageDontKill = mustNotKill;
#endif

        desc.dwFieldsValid |= oCNpc::oEDescDamageFlags::oEDamageDescFlag_Attacker;
        desc.dwFieldsValid |= oCNpc::oEDescDamageFlags::oEDamageDescFlag_Damage;
        //desc.dwFieldsValid |= oCNpc::oEDescDamageFlags::oEDamageDescFlag_DamageType;

        target->OnDamage(desc);
        int health = target->GetAttribute(NPC_ATR_HITPOINTS);

        if (health <= 0)
        {
            target->ResetPos(target->GetPositionWorld());
            target->SetAttribute(NPC_ATR_HITPOINTS, health);
        }
    }
Валидным он остается до тех пор, пока количество ссылок на него > 0.
Я же не могу количество ссылок у потенциально невалидного объекта проверять.
На всякий, код дота:
C++:
#include <functional>
#include <algorithm>

namespace NAMESPACE
{
    void CDotDamage::OnLoop()
    {
        if (ogame->singleStep)
        {
            return;
        }

        if (!info->target || !info->target->globalVobTreeNode)
        {
            delete this;
            return;
        }

        if (info->target->GetAttribute(NPC_ATR_HITPOINTS) <= 0)
        {
            delete this;
            return;
        }

        if (info->mustNotKill && info->target->IsUnconscious())
        {
            delete this;
            return;
        }

        float currentTime = ztimer->totalTimeFloat / 1000.0f;
        float needDamage = min(currentTime - creationTimeInSecs, durationInSecs) / durationInSecs * damage;
        int debt = (int)(needDamage + 0.5f) - damageDone;

        if (debt)
        {
            DoDamage(debt);
            damageDone += debt;
        }

        if (damageDone >= damage)
        {
            delete this;
            return;
        }
    }

    void CDotDamage::DoDamage(int debt)
    {
        if(info->target->HasFlag(NPC_FLAG_IMMORTAL) || info->target->IsSelfPlayer() && oCNpc::godmode)
        {
            return;
        }

        int maxDamage = info->target->GetAttribute(NPC_ATR_HITPOINTS) - 1;

        if (info->mustNotKill && maxDamage > 0)
        {
            maxDamage -= 1;
        }

        int realDamage = min(maxDamage, debt);

        info->target->SetAttribute(NPC_ATR_HITPOINTS, info->target->GetAttribute(NPC_ATR_HITPOINTS) - realDamage);

        info->rawDamage[(int)damageIndex] += debt;
        info->effectiveDamage[(int)damageIndex] += debt;
        info->totalDamage += debt;
        info->realDamage += realDamage;

        if (realDamage < debt)
        {
            info->realDamage += 1;
            info->DoDamageUnhooked(1);
        }
    }

    CDotDamage::CDotDamage(std::shared_ptr<TDamageInfo> info, oEDamageIndex damageIndex, int damage, float durationInSecs):
        info(info),
        damageIndex(damageIndex),
        damage(damage),
        durationInSecs(durationInSecs),
        damageDone(0)
    {
        onLoop = new CGlobalEventSubscription(CGlobalEventPublisher::GlobalEvent::LOOP, std::bind(&CDotDamage::OnLoop, this));
        creationTimeInSecs = ztimer->totalTimeFloat / 1000.0f;
        info->dots.push_back(this);
    }

    CDotDamage::~CDotDamage()
    {
        delete onLoop;
        auto it = std::find(info->dots.begin(), info->dots.end(), this);
        info->dots.erase(it);
    }
}
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625
Я же не могу количество ссылок у потенциально невалидного объекта проверять.
Не можешь. Поэтому все события очистки AI происходят в деструкторе NPC. Например визуальные эффекты, контейнер предметов, очередь ai месседжей и тд уничтожаются там.
Как вариант, похукай деструктор, либо сделай класс наследником oCVisualFX. Так твой эффект будет четко привязан к необходимому экземпляру.
Адрес: 0x0072E6A0
 

Slavemaster


Модостроитель
Регистрация
10 Июн 2019
Сообщения
1.086
Благодарности
1.904
Баллы
320
Поэтому все события очистки AI происходят в деструкторе NPC.
А может движок внезапно вызвать метод Unarchive, превратив одного НПС в совершенно другого?
Как вариант, похукай деструктор, либо сделай класс наследником oCVisualFX.
Посмотрю, что это за зверь.

Вообще, напрашивается смартпоинтер, который никогда не отдаст указатель на невалидный объект.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625
А может движок внезапно вызвать метод Unarchive, превратив одного НПС в совершенно другого?
Не думаю. В случае NPC, этот метод вызывается после конструирования экземпляра для инициализации его значений. Могу лишь предположить, что по данному адресу, после его высвобождения, возможна запись нового объекта oCNpc. Но такое скорее действительно в рамках экземпляров, хранящихся в пулах памяти.
 

Slavemaster


Модостроитель
Регистрация
10 Июн 2019
Сообщения
1.086
Благодарности
1.904
Баллы
320
Деструктор не вызывается. Вылет происходит при первом взаимодействии с нпс:
C++:
if (enableLogging) Message::Error("1");

        if (info->target->GetAttribute(NPC_ATR_HITPOINTS) <= 0)
        {
            if (enableLogging) Message::Error("1.1");
Предположу, что вызов методов НПС, удаленного из глобального дерева вобов, не допускается.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625
Деструктор не вызывается. Вылет происходит при первом взаимодействии с нпс:
C++:
if (enableLogging) Message::Error("1");

        if (info->target->GetAttribute(NPC_ATR_HITPOINTS) <= 0)
        {
            if (enableLogging) Message::Error("1.1");
Предположу, что вызов методов НПС, удаленного из глобального дерева вобов, не допускается.
C++:
namespace Gothic_II_Addon {

  int __fastcall oCNpc_Destructor( oCNpc* _this );

  CInvoke<int( __thiscall* )( oCNpc* )> Ivk_oCNpc_Destructor( 0x0072E6A0, oCNpc_Destructor );

  int __fastcall oCNpc_Destructor( oCNpc* _this ) {
    cmd << _this->GetName( 0 ) << " destruct" << endl;
    return Ivk_oCNpc_Destructor( _this );
  }
}
Далее следует вход в мир и загрузка чего-либо:
75784
 

Slavemaster


Модостроитель
Регистрация
10 Июн 2019
Сообщения
1.086
Благодарности
1.904
Баллы
320
и загрузка чего-либо:
Ключевое слово "загрузка". Если удалиться от нпс, его деструктор не будет вызван. По крайней мере можно будет из GameLoop зафиксировать, что деструктор еще не вызван, а объект из мира удален.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625
Ключевое слово "загрузка". Если удалиться от нпс, его деструктор не будет вызван. По крайней мере можно будет из GameLoop зафиксировать, что деструктор еще не вызван, а объект из мира удален.
Верно, но в этот момент NPC никуда не выгружается. Когда удаляешься от объекта, он переходит в заморозку - zCVob::sleepingMode = zTVobSleepingMode::zVOB_SLEEPING.
При этом указатель на oCNpc::homeWorld становится Null
 

Slavemaster


Модостроитель
Регистрация
10 Июн 2019
Сообщения
1.086
Благодарности
1.904
Баллы
320
Gratt, а вообще его деструктор будет вызван, если не загружаться и тп? И вообще как zObject уничтожить?
delete new oCItem(); // ошибка
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.638
Баллы
625

СырGuy

Участник форума
Регистрация
4 Мар 2022
Сообщения
29
Благодарности
5
Баллы
45
Добрый вечер. В этой теме затронулся класс oCMobContainer и назрела пара вопросов. Какой метод нужно хукать при переносе итемов в контейнер? Есть ли возможность уменьшить количество отображаемых ячеек контейнера?
 
Последнее редактирование:
Сверху Снизу