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

    Чтобы получить возможность писать на форуме, оставьте сообщение в этой теме.
    Удачи!

5. Виртуальная таблица. Пишем новый класс NPC.

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.636
Баллы
625
Общий смысл vtable
Смысл виртуальных таблиц заключается в том, что вызов функции происходит в два этапа - 1. смещение к виртуальной таблице класса, в конструкторе которого был определен указатель, 2. определение адреса реализации по смещению в таблице.
C++:
class A {
  virtual void print() { cmd << "is A" << endl; }
};

class B : public A {
  virtual void print() { cmd << "is B" << endl; }
};

void func() {
  A* a = new B(); // Указатель на vtable будет создан в конструкторе класса B.
  a->print();           // Программа обратится к виртуальной таблице класса B со смещением 0
                            // и возьмет реализацию с выводом на экран "is B".
}

Рассмотрим чуть более подробно о генерации таблиц. В коде ниже представлены два класса, где базовый имеет 5 виртуальных функций. Таблица получит 5 адресов на реализации, смещение которых будет равным порядку объявления методов. То есть при создании экземпляра от ClassBase в переменную будет записан указатель к `vtable ClassBase`.

Теперь разберемся с производным ClassA. Казалось бы, по коду у него 2 функции. В действительности их столько же, сколько у базового. Если посмотрим в таблицу этого класса, то увидим, что необъявленные функции будут вызывать реализацию от ClassBase. А func3, func4 - собственную. Получается, что создавая экземпляр ClassA в переменную запишется смещение к `vtable ClassA`.
C++:
class ClassBase {
  virtual void Func1();
  virtual void Func2();
  virtual void Func3();
  virtual void Func4();
  virtual void Func5();
};

class ClassA : public ClassBase {
  virtual void Func3();
  virtual void Func4();
};


// Виртуальные таблицы.
[vtable ClassBase]
call void ClassBase::Func1
call void ClassBase::Func2
call void ClassBase::Func3
call void ClassBase::Func4
call void ClassBase::Func5

[vtable ClassA]
call void ClassBase::Func1
call void ClassBase::Func2
call void ClassA::Func3
call void ClassA::Func4
call void ClassBase::Func5

Постановка задачи
Так вот. Чтобы пример не был пресным, реализуем 2 нативных класса, которые будут работать в движке без единого хука.
В первую очередь напишем класс oCNpcEx, унаследованный от oCNpc, и внедрим в него ряд новых возможностей:
1. Если камера находится слишком близко к NPC, то сделаем его прозрачным.
2. При удерживании ПКМ научим персонажа ускорять темп бега.
3. Регенерация здоровья, маны, стамины.
4. Чтение/Запись изменяемых данных в сохранение.
5. Добавим блокировки регенерации в заданных событиях.

Во-вторых создадим производный класс oCObjectFactoryEx от oCObjectFactory, который используется движком для создания экземпляров тех или иных классов:
1. Добавим собственную реализацию виртуальной функции CreateNpc через которую будем создавать экземпляры oCNpcEx, другие наc интересовать не будут.
2. Запишем в игровой экземпляр zfactory новое значение oCObjectFactoryEx.

I. Болванка класса oCNpcEx и реализация oCObjectFactoryEx
Чтобы игра приняла класс как свой родной, опишем внутри него интерфейс zCLASS_UNION_DECLARATION. Он добавит стандартные жизненно важные свойства и функции.
А чтобы реализация вошла в силу, в качестве определения используется zCLASS_UNION_DEFINITION.
C++:
// .h file
namespace Gothic_II_Addon {
  class oCNpcEx : public oCNpc {
  public:
    zCLASS_UNION_DECLARATION( oCNpcEx );
  };

  class oCObjectFactoryEx : public oCObjectFactory {
    zCLASS_UNION_DECLARATION( oCObjectFactoryEx )
  public:
    virtual oCNpc* CreateNpc( int index ); // Определяем единственную интересующую нас функцию для конструирования NPC
  };
}
 
// .cpp file
namespace Gothic_II_Addon {
  // В определении интерфейса нам важны первые два параметра. Целевой класс и его предок
  zCLASS_UNION_DEFINITION( oCNpcEx, oCNpc, 0, 0 );
  zCLASS_UNION_DEFINITION( oCObjectFactoryEx, oCObjectFactory, 0, 0 );

  // Реализация виртуального метода, создающего наших NPC
  oCNpc* oCObjectFactoryEx::CreateNpc( int index ) {
    oCNpc* npc = new oCNpcEx();

    if( index != zPAR_INDEX_UNDEF )
      npc->InitByScript( index, 0 );

    return npc;
  }
}

Теперь необходимо связать класс oCObjectFactoryEx с движком. Создадим функцию, которая будет присваивать указателю движка значение нового экземпляра. А вызовем мы его из Game_DefineExternals в Application.cpp.
C++:
// .cpp file
namespace Gothic_II_Addon {
    
  // [ . . . Предыдущая реализация]
    
  void OnInitFactory() {
    zfactory = new oCObjectFactoryEx();
  }
}

// global namespace
void GameGlobal_OnInitFactory() {
  Gothic_II_Addon::OnInitFactory();
}

// Application.cpp
extern void GameGlobal_OnInitFactory();
void Game_DefineExternals() {
  GameGlobal_OnInitFactory();
}

Данный код уже можно скомпилировать. Во время старта новой игры будут создаваться экземпляры NPC от класса oCNpcEx через функцию oCObjectFactoryEx::CreateNpc.
Также после сохранения движок запомнит новый класс и будет подгружать его из сейва.

II. Функция отсечения NPC камерой
74733
Поскольку в игре УЖЕ работают наши NPC, то и делать с ними можно все что захотим :). Для начала реализуем алгоритм, при котором NPC, мешающийся в фокусе, будет отсекаться путем изменения прозрачности модели.
В класс oCNpcEx добавим виртуальный метод ProcessNpc. Он вызывается каждую отрисовку кадра для всех NPC.
C++:
// .h file
  class oCNpcEx : public oCNpc {
  public:
    zCLASS_UNION_DECLARATION( oCNpcEx );
    CTimer TimerAI;
    
    virtual void ProcessNpc();
  };

Формула отсечения будет такой: Если камера входит в область равную 0.8 от радиуса bbox'а, то модель начинает выцветать с продолжительностью до 0.5 от точки начала выцветания.
Также исключим из алгоритма главного героя.
C++:
void oCNpcEx::ProcessNpc() {
    // Привязка таймера к циклу NPC
    TimerAI.Attach();

    if( this != player ) {
      // Определяем bbox модели. Вычисляем его фактический центр
      // суммированием локального центра с положенем NPC в мире.
      zTBBox3D BBox3D                  = GetModel()->bbox3D;
      zVEC3       VobCenter               = GetPositionWorld() + BBox3D.GetCenter();
      zVEC3       CameraPosition       = ogame->GetCameraVob()->GetPositionWorld();
      float          DistanceToCamera  = VobCenter.Distance( CameraPosition );

      // Далее вычисляем общий радиус модели через длину разницы
      // максимума и минимума координат. По условию нас интересует
      // 0.8 от длины и 0.5 от предыдущего результата.
      float FadeDistanceBegin = ( BBox3D.maxs - BBox3D.mins ).Length() * 0.8f;
      float FadeDistanceEnd    = FadeDistanceBegin * 0.5f;

      // Условие выключения альфы, если персонаж не мешается.
      if( DistanceToCamera > FadeDistanceBegin ) {
        if( visualAlphaEnabled ) {
          visualAlpha = 1.0f;
          visualAlphaEnabled = False;
        }
      }
      else {
        if( !visualAlphaEnabled )
          visualAlphaEnabled = True;

        // Если камера находится внутри ближней к NPC границе отсечения,
        // то прозрачность персонажа всегда 0
        if( DistanceToCamera < FadeDistanceEnd )
          visualAlpha = 0.0f;

        // Иначе работаем по формулам отношений, где определяем
        // количество прозрачности в заданном положении камеры.
        else {
          float FadeLengthMax = FadeDistanceBegin - FadeDistanceEnd;
          float FadeLength        = DistanceToCamera  - FadeDistanceEnd;
          visualAlpha                = 1.0f / FadeLengthMax * FadeLength;
        }
      }
    }
    // После работы нашего алгоритма
    // также можем выполнить и родной.
    oCNpc::ProcessNpc();
  }

III. Спринт
Данная функция будет использоваться только в рамках главного героя. Создадим невиртуальную функцию ProcessSprint, которую будем вызывать из предыдущей ProcessNpc.
По условию нам необходимо зажать ПКМ для активации спритна. Сам спринт будет ускорять штатную анимацию бега в 1.5 раза.
C++:
// .h file
  class oCNpcEx : public oCNpc {
  public:
    zCLASS_UNION_DECLARATION( oCNpcEx );

    CTimer TimerAI;

    int LockRegenStaminaTime; // Время в секундах, во время которого восстановление стамины невозможно

    bool32 SprintEnabled; // Определяет находится ли персонаж в спринте
    int        StaminaMax;    // Определяет максимальный показатель выносливости
    int        Stamina;          // Определяет текущий показатель выносливости

    oCNpcEx();                // Добавим конструктор класса для определения значений по умолчанию
    void ProcessSprint(); // Обработчик спринта
    virtual void ProcessNpc();
  };

// .cpp file
  oCNpcEx::oCNpcEx() : oCNpc() {
    LockRegenStaminaTime  = 0;
    SprintEnabled                 = False;
    StaminaMax = Stamina   = 40;
  }


  void oCNpcEx::ProcessSprint() {
    // ID таймера траты стамины
    static const uint SpendStaminaID = 0;
    if( this != player )
      return;
    
    // Получаем экземпляр анимации бега для текущего боевого состояния 
    // (fmode) из списка анимаций anictrl, где s_runl - массив идентификаторов.
    // Далее проверяем активна ли анимация текущего fmode. А также определяем
    // возможность наложения спринта.
    zCModelAni* RunAni       = GetModel()->GetAniFromAniID( anictrl->s_runl[fmode] );
    bool32          AniIsActive = GetModel()->IsStateActive( RunAni );
    bool32          CanSprint   = zinput->GetMouseButtonPressedRight() && Stamina && AniIsActive;

    // Активация спринта
    if( CanSprint && !SprintEnabled ) {
      RunAni->fpsRate = RunAni->fpsRateSource * 1.5;
      SprintEnabled = True;
    }
    // Деактивация спринта
    else if( !CanSprint && SprintEnabled ) {
      RunAni->fpsRate = RunAni->fpsRateSource;
      SprintEnabled = False;
    }

    // Привязываем таймер к игровому процессу. Он будет
    // приостанавливаться, если игра будет на паузе.
    TimerAI.Suspend( SpendStaminaID, ogame->singleStep );

    // А трата стамины будет тратиться 
    // по нажатию ПКМ и далее каждые 100мс
    if( SprintEnabled ) {
      LockRegenStaminaTime = 2;
      if( Stamina && TimerAI( SpendStaminaID, 100, TM_PRIMARY ) )
        Stamina--;
    }

   
    // Поскольку писать новый статус не хотелось,
    // возпользуемся готовым баром задержки дыхания.
    // В него выведем выносливость.
    if( Stamina != StaminaMax ) {

      // Вставляем бар во вьюпорт
      screen->InsertItem( ogame->swimBar );
      
      // Указываем значения стамины
      ogame->swimBar->SetMaxRange( 0, StaminaMax );
      ogame->swimBar->SetRange       ( 0, StaminaMax );
      ogame->swimBar->SetPreview     ( Stamina );
      ogame->swimBar->SetValue        ( Stamina );

      // Принудительно рендерим бар 
      // и удаляем из вьюпорта
      ogame->swimBar->Render();
      screen->RemoveItem( ogame->swimBar );
    }
  }

  void oCNpcEx::ProcessNpc() {
    // [. . . Предыдущий код]
    ProcessSprint();
  }



IV. Регенерация атрибутов
И теперь к восстановлению показателей. Доопределим оставшиеся поля класса.
Введем 2 функции: ProcessRegen для процедуры регенерации и OnDamage, которая срабатывает в момент получения урона. В ней будем приостанавливать регенерацию здоровья на 5 секунд и маны на 2 секунды.
C++:
// .h file
  class oCNpcEx : public oCNpc {
  public:
    zCLASS_UNION_DECLARATION( oCNpcEx );

    CTimer TimerAI;
    
    // Интенсивности показывают количество
    // восстанавливаемых единиц в секунду
    float  RegenHpIntensity;
    float  RegenManaIntensity;
    float  RegenStaminaIntensity;
    
    // Блокировки показывают в течении скольки
    // секунд регенерации не будут действовать
    int LockRegenHpTime;
    int LockRegenManaTime;
    int LockRegenStaminaTime;

    bool32 SprintEnabled;
    int        StaminaMax;
    int        Stamina;

    oCNpcEx();
    void ProcessSprint();
    void ProcessRegen(); // Добавляем еще одну невиртуальную функцию для регенерации
    virtual void ProcessNpc();
    virtual void OnDamage( oSDamageDescriptor& damage ); // Срабатывает в момент нанесения урона персонажу
  };
 
// .cpp file
  oCNpcEx::oCNpcEx() : oCNpc() {
    RegenHpIntensity           = 1.0f;
    RegenManaIntensity       = 1.0f;
    RegenStaminaIntensity   = 1.0f;
    LockRegenHpTime          = 0;
    LockRegenManaTime      = 0;
    LockRegenStaminaTime  = 0;
    SprintEnabled                 = False;
    StaminaMax = Stamina   = 40;
  }

  void oCNpcEx::ProcessRegen() {
    // ID таймеров регенерации
    static const uint RegenHpID         = 1;
    static const uint RegenManaID     = 2;
    static const uint RegenStaminaID = 3;
    static const uint UnlockID             = 4;

    // Если игра поставится на паузу,
    // таймеры тоже будут приостановлены
    TimerAI.Suspend( RegenHpID,          ogame->singleStep );
    TimerAI.Suspend( RegenManaID,      ogame->singleStep );
    TimerAI.Suspend( RegenStaminaID, ogame->singleStep );
    TimerAI.Suspend( UnlockID,              ogame->singleStep );

    // Для каждого блока проверяется, не стоит ли блокировка на 
    // регенерацию и меньше ли параметр чем достигаемое значение
    if( !LockRegenHpTime && attribute[NPC_ATR_HITPOINTS] < attribute[NPC_ATR_HITPOINTSMAX] ) {
      // Далее вычисляется задержка, соответствующая 
      // количеству единиц, восстанавливаемых за секунду.
      int HpIntensity = ( 1000.0f / RegenHpIntensity );
      if( TimerAI( RegenHpID, HpIntensity ) )
        attribute[NPC_ATR_HITPOINTS]++;
    }
    
    // Аналогично
    if( !LockRegenManaTime && attribute[NPC_ATR_MANA] < attribute[NPC_ATR_MANAMAX] ) {
      int ManaIntensity = ( 1000.0f / RegenManaIntensity );
      if( TimerAI( RegenManaID, ManaIntensity ) )
        attribute[NPC_ATR_MANA]++;
    }
    
    // Аналогично
    if( !LockRegenStaminaTime && Stamina < StaminaMax ) {
      int StaminaIntensity = ( 1000.0f / RegenStaminaIntensity );
      if( TimerAI( RegenStaminaID, StaminaIntensity ) )
        Stamina++;
    }
    
    // А этот таймер снимает блокировки с регенов.
    // Каждую секунду он понижает блокировки на 1.
    // И когда значение становится 0 - регенерация
    // снова начинает работать.
    if( TimerAI( UnlockID, 1000 ) ) {
      if( LockRegenHpTime )
        LockRegenHpTime--;

      if( LockRegenManaTime )
        LockRegenManaTime--;

      if( LockRegenStaminaTime )
        LockRegenStaminaTime--;
    }
  }

  void oCNpcEx::OnDamage( oSDamageDescriptor& damage ) {
    oCNpc::OnDamage( damage ); // Вызываем оригинальный обработчик урона в классе oCNpc
    LockRegenHpTime    = 5;         // Приостановка регенерации здоровья на 5 секунд
    LockRegenManaTime = 2;        // Приостановка регенерации здоровья на 2 секунды

  }

  void oCNpcEx::ProcessNpc() {
    // [. . . Предыдущий код]
    ProcessRegen();
  }



V. Сохранение и загрузка значений
Новые поля классов являются динамическими и могут меняться непрерывно в течение всего игрового процесса. Будет правильно, если мы будем записывать их состояния в файл сохранения.
Добавляем 2 виртуальный метода Archive и Unarchive.
C++:
// .h file
  class oCNpcEx : public oCNpc {
  public:
    zCLASS_UNION_DECLARATION( oCNpcEx );

    CTimer TimerAI;
    float  RegenHpIntensity;
    float  RegenManaIntensity;
    float  RegenStaminaIntensity;
    
    int LockRegenHpTime;
    int LockRegenManaTime;
    int LockRegenStaminaTime;

    bool32 SprintEnabled;
    int        StaminaMax;
    int        Stamina;


    oCNpcEx();
    void ProcessSprint();
    void ProcessRegen();
    virtual void ProcessNpc();
    virtual void OnDamage( oSDamageDescriptor& damage );
    virtual void Archive( zCArchiver& ar );      // Вызывается при сохранении NPC в сейв
    virtual void Unarchive( zCArchiver& ar );  // Вызывается при чтении NPC из сейва
  };

// .cpp file
  void oCNpcEx::Archive( zCArchiver& ar ) {
    // Сохраняем оригинальные данные NPC в сейв
    oCNpc::Archive( ar );

    // Сохраняем наши данные в сейв
    ar.WriteFloat( "REGENHPINTENSITY",           RegenHpIntensity );
    ar.WriteFloat( "REGENMANAINTENSITY",      RegenManaIntensity );
    ar.WriteFloat( "REGENSTAMINAINTENSITY", RegenStaminaIntensity );
    ar.WriteInt    ( "LOCKREGENHPTIME",            LockRegenHpTime );
    ar.WriteInt    ( "LOCKREGENMANATIME",       LockRegenManaTime );
    ar.WriteInt    ( "LOCKREGENSTAMINATIME",  LockRegenStaminaTime );
    ar.WriteInt    ( "STAMINAMAX",                      StaminaMax );
    ar.WriteInt    ( "STAMINA",                              Stamina );
  }

  void oCNpcEx::Unarchive( zCArchiver& ar ) {
    // Читаем оригинальные данные NPC из сейва
    oCNpc::Unarchive( ar );
    
    // Читаем наши данные NPC из сейва
    ar.ReadFloat( "REGENHPINTENSITY",           RegenHpIntensity );
    ar.ReadFloat( "REGENMANAINTENSITY",      RegenManaIntensity );
    ar.ReadFloat( "REGENSTAMINAINTENSITY", RegenStaminaIntensity );
    ar.ReadInt    ( "LOCKREGENHPTIME",            LockRegenHpTime );
    ar.ReadInt    ( "LOCKREGENMANATIME",       LockRegenManaTime );
    ar.ReadInt    ( "LOCKREGENSTAMINATIME",  LockRegenStaminaTime );
    ar.ReadInt    ( "STAMINAMAX",                      StaminaMax );
    ar.ReadInt    ( "STAMINA",                              Stamina );
  }
Все. Компилируем и начинаем новую игру.



VI. Заключение
Следуя данному примеру можно реализовать собственные классы от любого другого. Будь то oCItem или zCRenderer. Да, фактически рендер тоже можно повесить на другие рельсы, будь время и желание. А пока такой вот простенький класс, с которым можно делать что угодно и как угодно.
Собственно главная мысль, которую я пытался передать, - демонстрация нативности инструментов. Без костылей, без патчинга памяти и даже без хуков. так что дерзайте. Исходники выложу ниже.
 

Вложения

  • Sources.7z
    3,1 KB · Просмотры: 152
Последнее редактирование:

OsmithREV

Участник форума
Регистрация
17 Мар 2016
Сообщения
117
Благодарности
183
Баллы
230
Можете объяснить что я делаю не так? p.s. класс oCNpcEx добавил для примера, что вот он работает, а мой - нет, хотя все вроде как идентично

.h
Безымянный.png


.cpp
2.png
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.636
Баллы
625
OsmithREV, Потому что этот класс не является наследником от zCObject. oCGame представляет собой информацию об игровой сессии - специальных деклараторов не требует.
Смысл zCLASS_UNION_DECLARATION / zCLASS_UNION_DEFINITION заключается создании программируемых конструкторов классов, которые могут создавать необходимые экземпляры по любому капризу архиваторов. То бишь для сохранения и загрузки объектов из сейва. В число таких входят множество типа вобов, нпс, эффектов и тд.

Конечно можно реализовать zCObject деклараторы на твоем oCGameEx, если определить множественное наследование, но эта операция совершенно бессмысленна в отношении спецификации этого класса.

1567718938133.png
 

Cbrhex

Участник форума
Регистрация
5 Окт 2019
Сообщения
42
Благодарности
2
Баллы
100
Поскольку писать новый статус не хотелось

А где можно посмотреть реализацию swimBar что бы на его основание создавать свои?

Или пример как устанавливать SetTextures (Интуитивно понятно. Параметры это названия текстур типа: "BAR_BACK.TGA")

SetTextures("BAR_BACK.TGA", "BAR_TEMPMAX.TGA", "BAR_HEALTH.TGA") - так вроде норм

и экстенд: new oCViewStatusBar(*ogame->hpBar)
 
Последнее редактирование:

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.636
Баллы
625
Cbrhex,
Стандартный этап инициализации:
C++:
// Задать интервал значений
manaBar->SetMaxRange( 0, 30 );

// задать текстуру элементов бара
swimBar->SetTextures( "BAR_back.tga", "BAR_tempmax.tga", "BAR_misc.tga" );

// Взять размер бара
focusBar->GetSize( xs, ys );

// Установить бар в центр экрана
focusBar->SetPos( ( 8192 - xs ) / 2.0f , screen->any( 10 ) );
Манипуляции по отрисовке бара происходят в функции oCGame::UpdatePlayerStatus. Если хочешь создать производную, то ее экземпляр можешь перезаписать в функции Game_Init плагина как ogame->swimBar = new <your class>;. Виртуальные функции посмотришь в классе.
 

Cbrhex

Участник форума
Регистрация
5 Окт 2019
Сообщения
42
Благодарности
2
Баллы
100
Gratt, А почему нельзя так сделать:

Код:
oCViewStatusBar* newHpBar = new oCViewStatusBar();

newHpBar->SetPos(( 8192 - xs ) / 2.0f , screen->any( 10 ));
newHpBar->GetSize( xs, ys );
newHpBar->SetMaxRange(0, 30);
newHpBar->SetTextures("BAR_BACK.TGA", "BAR_TEMPMAX.TGA", "BAR_HEALTH.TGA");

screen->InsertItem(newHpBar);
newHpBar->Render();
screen->RemoveItem(newHpBar);

Во время загрузки вылет.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.636
Баллы
625
Cbrhex, Я смогу ответить на этот вопрос, когда ты приложишь полный код. Данная реализация может вести себя по разному в зависимости от точки ее вызова. И на всякий случай укажи к какому движку относится плагин.
 

Cbrhex

Участник форума
Регистрация
5 Окт 2019
Сообщения
42
Благодарности
2
Баллы
100
Gratt,

Код:
#include <UnionAfx.h>

#ifdef __G1
namespace Gothic_I_Classic {

    oCViewStatusBar* newHpBar;
    int sx, sy = 0;

    void GameInit() {
        newHpBar = new oCViewStatusBar();
        ogame->hpBar->GetSize(sx, sy);
    }

    void PrintScreen() {
        newHpBar->SetPos(500, 0);
        newHpBar->SetSize(sx, sy);
        newHpBar->SetValue(50);
        newHpBar->SetMaxRange(0, 100);
        newHpBar->SetTextures("BAR_BACK.TGA", "BAR_TEMPMAX.TGA", "BAR_HEALTH.TGA");

        screen->InsertItem(newHpBar);
        newHpBar->Render();
        screen->RemoveItem(newHpBar);
    }
}
#endif

void Init() {
    Gothic_I_Classic::GameInit();
}

void Loop() {
    Gothic_I_Classic::PrintScreen();
}

Union 1.0d
Union SDK 1.0c
Пост автоматически объединён:

Если делать так:

Код:
newHpBar = new oCViewStatusBar(*ogame->swimBar);

То загружается и появляться. Но:

hp_bar_exa.png


`SetPos` отрабатывает только для `BAR_BACK.TGA`
 
Последнее редактирование:

Cbrhex

Участник форума
Регистрация
5 Окт 2019
Сообщения
42
Благодарности
2
Баллы
100
Gratt, а можно получить NPC по имени или по ид как vob(ogame->GetWorld()->SearchVobByName("VOB_NAME"))?
Не могу найти.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.636
Баллы
625
Gratt, а можно получить NPC по имени или по ид как vob(ogame->GetWorld()->SearchVobByName("VOB_NAME"))?
Не могу найти.

По инстанции:
C++:
oCWorld* world = ogame->GetGameWorld();
  int instance = parser->GetIndex( "NONE_100_XARDAS" );
  if( instance == Invalid )
    return;

  auto vobList = world->voblist_npcs->next;
  while( vobList ) {
    oCNpc* npc = vobList->data;
    if( npc->GetInstance() == instance ) {
      Message::Box( "Xardas found!" );
      // TO DO
      break;
    }

    vobList = vobList->next;
  }

По имени:
C++:
  oCWorld* world = ogame->GetGameWorld();

  auto vobList = world->voblist_npcs->next;
  while( vobList ) {
    oCNpc* npc = vobList->data;
    if( npc->GetName(0) == "Ксардас" ) {
      Message::Box( "Xardas found!" );
      // TO DO
      break;
    }

    vobList = vobList->next;
  }

По предыдущему вопросу тебе ответит Jr13San, мне сейчас некогда пока...
 

Jr13San


Модостроитель
Регистрация
1 Апр 2010
Сообщения
449
Благодарности
266
Баллы
230
Пример индикатора для Г1. Повторяет значения здоровья.

1570887261181.png


C++:
// Indicator.cpp

#include "UnionAfx.h"

namespace Gothic_I_Classic {

//***************************************
// Макросы (для удобства понимания кода)
//***************************************
// Преобразование пикселей в виртуальные координаты относительно заданного объекта "pView"
#define PixelToVirtualX(pView, value) pView->anx(value) // по оси Х
#define PixelToVirtualY(pView, value) pView->any(value) // по оси У

// Преобразование виртуальных координат в пиксели относительно заданного объекта "pView"
#define VirtualToPixelX(pView, value) pView->nax(value) // по оси Х
#define VirtualToPixelY(pView, value) pView->nay(value) // по оси У

// указатель на индикатор (по умолчанию не задан)
oCViewStatusBar* pNewBar = NULL;

// Текстура полоски индикатора
const zSTRING newBar_bar = "BAR_NEW.TGA";


// обновление позиции и размеров нового индикатора
void newBar_UpdatePosAndSizes()
{
    // размеры берём от хп бара или же указываем свои
    if (ogame && ogame->hpBar && pNewBar)
    {
        int sx, sy;

        // получаем размеры индикатора здоровья
        ogame->hpBar->GetSize(sx, sy);

        // устанавливаем такие же размеры для нового индикатора
        pNewBar->SetSize(sx, sy);

        int x, y;

        // получаем позицию индикатора здоровья
        ogame->hpBar->GetPos(x, y);
              
        // устанавливаем позицию нового индикатора (по оси Х - такую же как и у индикатора здоровья,
        // а по оси У - отступив от верхнего края индикатора здоровья сам размер индикатора и 10 пикселей,
        // т.о. создав зазор между индикаторами в 10 пикселей)
        pNewBar->SetPos(x, y - sy - PixelToVirtualY(screen, 10));
    }
}

// обновление значения индикатора
void newBar_UpdateValue()
{
    // проверяем указатели
    if (pNewBar && player)
    {
        pNewBar->SetValue(player->GetAttribute(NPC_ATR_HITPOINTS));
        pNewBar->SetMaxRange(0, player->GetAttribute(NPC_ATR_HITPOINTSMAX));
        pNewBar->SetRange(0, player->GetAttribute(NPC_ATR_HITPOINTSMAX));
    }
}

//******************************************************************
// Перехват функции смены режима отображения индикаторов (вкл/выкл)
//******************************************************************
//0x00638BE0 public: void __thiscall oCGame::SetShowPlayerStatus(int)
static void __fastcall Game_SetShowPlayerStatus(oCGame* _this, void* vt, BOOL bShow);
static CInvoke <void(__thiscall*)(oCGame*, BOOL)> pGame_SetShowPlayerStatus(0x00638BE0, Game_SetShowPlayerStatus, IVK_AUTO);
static void __fastcall Game_SetShowPlayerStatus(oCGame* _this, void* vt, BOOL bShow)
{
    // если указатель на вьюпорт и на новый индикатор есть, а также задана команда "скрыть индикаторы"
    // (например, при вызове главного меню)
    if (screen && pNewBar && !bShow)
        // удаляем индикатор из вьюпорта
        screen->RemoveItem(pNewBar);

    // и вызываем оригинальную функцию
    pGame_SetShowPlayerStatus(_this, bShow);
}

//***************************************************
// Перехват функции обновления значений индикаторов
//***************************************************
//0x00638F90 public: void __thiscall oCGame::UpdatePlayerStatus(void)
static void __fastcall Game_UpdatePlayerStatus(oCGame* _this);
static CInvoke <void(__thiscall*)(oCGame*)> pGame_UpdatePlayerStatus(0x00638F90, Game_UpdatePlayerStatus, IVK_AUTO);
static void __fastcall Game_UpdatePlayerStatus(oCGame* _this)
{
    // вызываем оригинальную функцию
    pGame_UpdatePlayerStatus(_this);

    // если указателя на игровую сессию нет или запрещено показывать индикаторы
    if (!_this || !_this->showPlayerStatus)
        // выходим
        return;
  
    // если индикатор не создан
    if (!pNewBar)
    {
        // создаём новый индикатор
        pNewBar = new oCViewStatusBar();

        // если вьюпорт есть и индикатор создан
        if (screen && pNewBar)
        {
            // добавляем индикатор на экран
            screen->InsertItem(pNewBar, FALSE);

            // инициализируем индикатор в нулевых координатах и в масштабе х1
            pNewBar->Init(0, 0, 1.0);

            // задаём текстуры для индикатора
            pNewBar->SetTextures("BAR_BACK.TGA", "BAR_TEMPMAX.TGA", newBar_bar);

            // обновляем позицию и размеры нового индикатора
            newBar_UpdatePosAndSizes();

            // после инициализации удаляем индикатор из вьюпорта
            screen->RemoveItem(pNewBar);
        }
    }

    // если указатели на вьюпорт и индикатор существуют
    if (screen && pNewBar)
    {
        // удаляем индикатор из вьюпорта
        screen->RemoveItem(pNewBar);

        // вставляем индикатор во вьюпорт, тем сымым ставя его на передний план во время рендера zCView элементов
        screen->InsertItem(pNewBar, FALSE);

        // обновляем значение индикатора
        newBar_UpdateValue();
    }
}


//*******************************************************************************************************
// Перехват функции обновления позиций и размеров различных индикаторов при изменении разрешения экрана
//*******************************************************************************************************
//0x00638C50 public: void __thiscall oCGame::UpdateScreenResolution(void)
static void __fastcall Game_UpdateScreenResolution(oCGame* _this);
static CInvoke <void(__thiscall*) (oCGame*)> pGame_UpdateScreenResolution(0x00638C50, Game_UpdateScreenResolution);
static void __fastcall Game_UpdateScreenResolution(oCGame* _this)
{
    // вызываем оригинальную функцию
    pGame_UpdateScreenResolution(_this);

    // если указатели на вьюпорт и индикатор существуют
    if (screen && pNewBar)
    {
        // добавляем индикатор на экран
        screen->InsertItem(pNewBar, FALSE);

        // инициализируем индикатор в нулевых координатах и в масштабе х1
        pNewBar->Init(0, 0, 1.0);

        // обновляем позицию и размеры нового индикатора
        newBar_UpdatePosAndSizes();

        // после инициализации удаляем индикатор из вьюпорта
        screen->RemoveItem(pNewBar);
    }
}


}
 

Вложения

  • Bar_new.tga.zip
    7,3 KB · Просмотры: 128
Последнее редактирование:

Cbrhex

Участник форума
Регистрация
5 Окт 2019
Сообщения
42
Благодарности
2
Баллы
100
Jr13San, Gratt, спасибо за ответы, все получилось.

Может у вас найдутся еще примеры работы с кастомной или дефолтной камерой? Хочу прописать координаты по которым камера должна двигатся.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.636
Баллы
625
Cbrhex, это тебе надо драконить camera.dat. А если переписывать алгоритмы, то проще либо в окно выйти, либо написать свою с нуля, чем переделать существующие zCCamera и zCAICamera. Там столько кода, что будь у тебе исходник, ты бы в нем заблудился.
 

Gratt


Модостроитель
Регистрация
14 Ноя 2014
Сообщения
3.301
Благодарности
4.636
Баллы
625
Cbrhex, А что ты еще хочешь увидеть? :) Зайди в CamInst.d, там все используемые игрой режимы...
 

Cbrhex

Участник форума
Регистрация
5 Окт 2019
Сообщения
42
Благодарности
2
Баллы
100
Gratt, Понял :D Спасибо. Буду "драконить" :)
 

Jr13San


Модостроитель
Регистрация
1 Апр 2010
Сообщения
449
Благодарности
266
Баллы
230
Может у вас найдутся еще примеры работы с кастомной или дефолтной камерой?
Есть только такой пример работы с аи-камерой:
Cam-test.jpg

C++:
// Application.cpp

#include "UnionAfx.h"

namespace Gothic_I_Classic {

void Game_Loop() 
{
    // переключена ли камера в тестовый режим (true - да, false - нет)
    static bool camToggled = false;

    // указатель на камеру, движущуюся за игроком
    zCAICamera* aicam = ogame->GetCameraAI();

    // если нажата клавиша F1 (включает/выключает тестовый режим для аи-камеры)
    if (zinput->KeyToggled(KEY_F1))
    {
        // проверяем указатели на аи-камеру и её воб, по которому она выравнивается
        if (aicam && aicam->camVob)
        {
            // если камера не переключена в новый тестовый режим
            if (!camToggled)
            {
                // слипаем воб камеры, иначе он так и будет тянуться к своему привычному месту
                aicam->camVob->SetSleeping(TRUE);

                // камера переключена тестовый режим
                camToggled = true;
            }
            else // иначе, камера уже переключена в тестовый режим, выключаем его
            {
                // пробуждаем камеру
                aicam->camVob->SetSleeping(FALSE);

                // камера переключена в стандартный режим следования за игроком
                camToggled = false;
            }
        }
    }

    // Отладочный мессадж
    string cam_text;
    if (camToggled)
        cam_text = "работает";
    else
        cam_text = "выключена";

    screen->Print(10, 500, (A "Тестовая камера " + cam_text).ToChar());
    


    // Управление камерой в тестовом режиме
    // Перед работой проверяем все указатели и переключена ли камера в тестовый режим
    if (aicam && aicam->camVob && camToggled)
    {
        // Вариант 1

        // проверяем указатель на игрока
        if (player)
        {
            // установить позицию камеры повыше центра тяжести ГГ на 100 см.
            aicam->camVob->SetPositionWorld(player->GetPositionWorld() + zVEC3(0, 100, 0));
        
            // установить вектор направления камеры такой же куда и смотрит модель ГГ
            aicam->camVob->SetHeadingAtWorld(player->trafoObjToWorld.GetAtVector());
        }

        // Вариант 2
        
        /*
        // если игра не на паузе
        if (ogame && !ogame->singleStep)
        {
            // двигаем камеру вверх
            aicam->camVob->MoveLocal(0, 0.2f, 0);

            // и вращаем по оси Y
            aicam->camVob->RotateLocalY(0.03f);

            // а также немного по оси X
            aicam->camVob->RotateLocalX(0.03f);
        }
        */
    }

}


void Game_Entry() {
}

void Game_Init() {
}

void Game_Exit() {
}

void Game_SaveBegin() {
}

void Game_SaveEnd() {
}

void LoadBegin() {
}

void LoadEnd() {
}

void Game_LoadBegin_NewGame() {
  LoadBegin();
}

void Game_LoadEnd_NewGame() {
 LoadEnd();
}

void Game_LoadBegin_SaveGame() {
 LoadBegin();
}

void Game_LoadEnd_SaveGame() {
 LoadEnd();
}

void Game_LoadBegin_ChangeLevel() {
 LoadBegin();
}

void Game_LoadEnd_ChangeLevel() {
 LoadEnd();
}

void Game_LoadBegin_Trigger() {
}

void Game_LoadEnd_Trigger() {
}

void Game_Pause() {
}

void Game_Unpause() {
}

void Game_DefineExternals() {
}

CApplication* lpApplication = CApplication::CreateRefApplication (
  Game_Entry,
  Game_Init,
  Game_Exit,
  Game_Loop,
  Game_SaveBegin,
  Game_SaveEnd,
  Game_LoadBegin_NewGame,
  Game_LoadEnd_NewGame,
  Game_LoadBegin_SaveGame,
  Game_LoadEnd_SaveGame,
  Game_LoadBegin_ChangeLevel,
  Game_LoadEnd_ChangeLevel,
  Game_LoadBegin_Trigger,
  Game_LoadEnd_Trigger,
  Game_Pause,
  Game_Unpause,
  Game_DefineExternals
  );

}
 

Cbrhex

Участник форума
Регистрация
5 Окт 2019
Сообщения
42
Благодарности
2
Баллы
100

Saturas


Модостроитель
Регистрация
11 Фев 2009
Сообщения
2.512
Благодарности
1.334
Баллы
315
С Аи камерой можно из шпейшера работать же
 

alexeich2019

Участник форума
Регистрация
28 Июн 2019
Сообщения
191
Благодарности
73
Баллы
175
Попробовал наследование классов на G1_Classic, сдк 1.0с, плагин 1.0е. Новая игра нормально, сейв нормально, загрузка сейва нормально. После загрузки, при заходе в инвентарь - вылет.
Если повторно зайти в игру и загрузиться - загрузится норм. В инвентарь тоже норм зайдет. Но при повторной загрузке и попытке входа в инвентарь - вылет.
Вылет происходит без ошибки.
 
Последнее редактирование:
Сверху Снизу