ТОМ2 - платформа для парсерных игр

Объявление

Открыт сайт tom2-game.ru.


Последнюю версию платформы можно скачать здесь.

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.



Плагины для ТОМа

Сообщений 1 страница 19 из 19

1

Для примера приведу полный код плагина tommci.plg.dll, который берет на себя проигрывание и управление музыкой в мышках и спелеологисте.

Код:
#include <windows.h>
typedef const wchar_t* __stdcall (*wcsFn)(const wchar_t*,const wchar_t*,const wchar_t*);
wcsFn RunText;
//==============================================================================
// музыка
//-------------------------------
bool SoundOn=true;
bool MusicOn=true;
wchar_t mciErrBuf[256]; //bufer для ошибок mci
const wchar_t*mciCommand=0;
DWORD Err;
bool LoopExit=false;
DWORD WINAPI mciLoop(LPVOID Param)
{ while(!LoopExit)
  { if(mciCommand)
    { Err=mciSendStringW(mciCommand,0,0,0);
      mciCommand=0;
    }
    Sleep(100);
  }
  return 0;
}
//-------------------------------
const wchar_t* __fastcall MCI(const wchar_t*Login,const wchar_t*Text)
{ if(!Text||!Text[0]) return L"ожидалась команда MCI";
  mciCommand=Text;
  for(int i=0; i<30 && mciCommand; i++) Sleep(100); //ждем выполнения 3 секунды
  if(Err)
  { if(Err==343) //343 - не установлено MIDI
    { SoundOn=false;
      MusicOn=false;
    }
    if(mciGetErrorStringW(Err,mciErrBuf,256))
      return mciErrBuf;
    else
      return L"неопознанная ошибка MCI";
  }
  return 0;
}
//-------------------------------
const wchar_t* __stdcall MusicFn(const wchar_t*Login,const wchar_t*Text,const wchar_t*)
{ if(MusicOn&&SoundOn) return MCI(Login,Text);
  return 0;
}
const wchar_t* __stdcall MusicOnFn(const wchar_t*Login,const wchar_t*,const wchar_t*)
{ MusicOn=true;
  if(RunText) RunText(Login,L"OnMusic(true)",0);
  return L"\nмузыка включена";
}
const wchar_t* __stdcall MusicOffFn(const wchar_t*Login,const wchar_t*,const wchar_t*)
{ MusicOn=false;
  if(RunText) RunText(Login,L"OnMusic(false)",0);
  return L"\nмузыка выключена";
}
//-------------------------------
// звуки
//-------------------------------
const wchar_t* __stdcall SoundFn(const wchar_t*Login,const wchar_t*Text,const wchar_t*)
{ if(SoundOn) return MCI(Login,Text);
  return 0;
}
const wchar_t* __stdcall SoundOnFn(const wchar_t*Login,const wchar_t*,const wchar_t*)
{ SoundOn=true;
  return L"\nзвук включен";
}
const wchar_t* __stdcall SoundOffFn(const wchar_t*Login,const wchar_t*,const wchar_t*)
{ SoundOn=false;
  return L"\nзвук выключен";
}
//==============================================================================

HINSTANCE EngineDll;
typedef int __stdcall (*wcsRegFn)(const wchar_t*,wcsFn,int);
enum{ FnInput=1, FnOutput=2, FnOther=3 };
wcsRegFn RegFunction=NULL;

#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
  switch(reason)
  { case DLL_PROCESS_ATTACH:
    { //получаем функции движка
      EngineDll=GetModuleHandle("tom.dll"); //движок уже загружен
      if(!EngineDll) break;
      RegFunction = (wcsRegFn)GetProcAddress(EngineDll,"RegFunction");
      if(!RegFunction) break;
      RunText = (wcsFn)GetProcAddress(EngineDll,"RunText");
      //регистрируем свои функции
      RegFunction(L"music",           MusicFn,   FnOutput);
      RegFunction(L"включить музыку", MusicOnFn, FnOutput);
      RegFunction(L"вкл музыку",      MusicOnFn, FnOutput);
      RegFunction(L"музыка",          MusicOnFn, FnOutput);
      RegFunction(L"выключить музыку",MusicOffFn,FnOutput);
      RegFunction(L"выкл музыку",     MusicOffFn,FnOutput);
      RegFunction(L"без музыки",      MusicOffFn,FnOutput);

      RegFunction(L"sound",          SoundFn,   FnOutput);
      RegFunction(L"media",          SoundFn,   FnOutput); //альтернативное название для совместимости c 0.9.3.1 beta
      RegFunction(L"включить звук",  SoundOnFn, FnOutput);
      RegFunction(L"включить звуки", SoundOnFn, FnOutput);
      RegFunction(L"вкл звук",       SoundOnFn, FnOutput);
      RegFunction(L"вкл звуки",      SoundOnFn, FnOutput);
      RegFunction(L"звук",           SoundOnFn, FnOutput);
      RegFunction(L"звуки",          SoundOnFn, FnOutput);
      RegFunction(L"выключить звук", SoundOffFn,FnOutput);
      RegFunction(L"выключить звуки",SoundOffFn,FnOutput);
      RegFunction(L"выкл звук",      SoundOffFn,FnOutput);
      RegFunction(L"выкл звуки",     SoundOffFn,FnOutput);
      RegFunction(L"без звука",      SoundOffFn,FnOutput);
      RegFunction(L"без звуков",     SoundOffFn,FnOutput);
      //запускаем цикл
      DWORD ThreadId;
      HANDLE LoopThread=CreateThread(0,0,mciLoop,0,CREATE_SUSPENDED,&ThreadId);
      ResumeThread(LoopThread);
    } break;
    case DLL_PROCESS_DETACH:
      //останавливаем цикл
      LoopExit=true;
      break;
  }
  return 1;
}

2

Полный листинг кода плагина speech.plg.dll
Позволяет читать выводимый текст синтезированным голосом через MS SAPI.

Код:
#include <windows.h>
#define _ATL_APARTMENT_THREADED
#include <sapi.h>

//==============================================================================
// голос
//-------------------------------
bool SpeechOn=true;
//--------------------------------------------------------------------------
const wchar_t*TextForSpeech=0;
HANDLE LoopThread; //цикл
HANDLE LoopFinish; //по этому событию прерывается цикл
DWORD WINAPI SpeechLoop(LPVOID)
{ //инициализируем COM
  if(FAILED(::CoInitialize(NULL))) return 0;
  //запускаем цикл
  ISpVoice*pVoice=0;
  while(WAIT_TIMEOUT==WaitForSingleObject(LoopFinish,100)) //10 раз в секунду
  {
    if(TextForSpeech)
    { if(pVoice)
      { pVoice->Release();
        pVoice=0;
      }
      HRESULT hr=CoCreateInstance(CLSID_SpVoice,NULL,CLSCTX_ALL,IID_ISpVoice,(void**)&pVoice);
      if(SUCCEEDED(hr))
      { if(pVoice->Speak(TextForSpeech,SPF_ASYNC,NULL)!=S_OK)
        {
           //что будем делать с ошибкой?
        }
      }
      TextForSpeech=0;
    }
  }
  //освобождаем голос
  if(pVoice) pVoice->Release();
  //освобождаем COM
  ::CoUninitialize();
  return 0;
}
//-------------------------------
const wchar_t* __stdcall SpeechFn(const wchar_t*Login,const wchar_t*Text,const wchar_t*)
{ if(!SpeechOn) return 0; //звук выключен
  if(!Text||!Text[0]) return L"ожидался текст для чтения голосом";
  TextForSpeech=Text;
  return 0;
}
//-------------------------------
const wchar_t* __stdcall SpeechOnFn(const wchar_t*Login,const wchar_t*,const wchar_t*)
{ SpeechOn=true;
  return L"\nголос включен";
}
const wchar_t* __stdcall SpeechOffFn(const wchar_t*Login,const wchar_t*,const wchar_t*)
{ SpeechOn=false;
  TextForSpeech=L"голос выключен";
  return L"\nголос выключен";
}
//==============================================================================
bool InitFlaf=false; //Init() выполняем только 1 раз
extern "C" __declspec(dllexport) bool __stdcall Init(void)
{ //запускаем голосовой движок короткой фразой при подключении
  //(потом тормозит меньше, т.к. уже разогрет)
  if(InitFlaf) return false;
  InitFlaf=true;
  SpeechFn(L"adm",L"привет",0);
  return true;
}

extern "C" __declspec(dllexport) bool __stdcall Exit(void)
{ //останавливаем цикл
  SetEvent(LoopFinish);
  //ждём завершения цикла
  if(WAIT_OBJECT_0==WaitForSingleObject(LoopThread, 1000)) //ждём 1 секунду
    CloseHandle(LoopThread);
  else //ошибка
    TerminateThread(LoopThread, 3);
  return true;
}

//------------------------------------------------------------

HINSTANCE EngineDll;
typedef const wchar_t* __stdcall (*wcsFn)(const wchar_t*,const wchar_t*,const wchar_t*);
typedef int __stdcall (*wcsRegFn)(const wchar_t*,wcsFn,int);
enum{ FnInput=1, FnOutput=2, FnOther=3 };
wcsRegFn RegFunction=NULL;
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
  switch(reason)
  { case DLL_PROCESS_ATTACH:
    { //получаем функции движка
      EngineDll=GetModuleHandle("tom.dll"); //движок уже загружен
      if(!EngineDll) break;
      RegFunction = (wcsRegFn)GetProcAddress(EngineDll,"RegFunction");
      if(!RegFunction) break;
      //регистрируем свои функции
      RegFunction(L"speech",         SpeechFn,   FnOutput);
      RegFunction(L"включить голос", SpeechOnFn, FnOther);
      RegFunction(L"включить речь",  SpeechOnFn, FnOther);
      RegFunction(L"вкл голос",      SpeechOnFn, FnOther);
      RegFunction(L"вкл речь",       SpeechOnFn, FnOther);
      RegFunction(L"голос",          SpeechOnFn, FnOther);
      RegFunction(L"речь",           SpeechOnFn, FnOther);
      RegFunction(L"выключить голос",SpeechOffFn,FnOther);
      RegFunction(L"выключить речь" ,SpeechOffFn,FnOther);
      RegFunction(L"выкл голос",     SpeechOffFn,FnOther);
      RegFunction(L"выкл речь",      SpeechOffFn,FnOther);
      RegFunction(L"без голоса",     SpeechOffFn,FnOther);
      RegFunction(L"без речи",       SpeechOffFn,FnOther);
      //запускаем цикл
      LoopFinish=CreateEvent(0,true,false,0);
      LoopThread=CreateThread(0,0,SpeechLoop,0,CREATE_SUSPENDED,0);
      ResumeThread(LoopThread);
    } break;
  }
  return 1;
}
//---------------------------------------------------------------------------

UPDATE: заменил код на актуальный

Отредактировано ASBer (2011-01-12 23:25:00)

3

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

При подключении/освобождении dll всегда вызывается функция DllEntryPoint()
При подключении (reason==DLL_PROCESS_ATTACH) необходимо выполнить следующие действия:

Код:
EngineDll=GetModuleHandle("tom.dll");
if(!EngineDll) break;

Получаем HINSTANCE движка tom.dll
В момент подключения плагина tom.dll уже должен быть подключен.

Код:
RegFunction = (wcsRegFn)GetProcAddress(EngineDll,"RegFunction");
if(!RegFunction) break;

Получаем указатель на фукцию движка RegFunction();
Эта функция нам необходима для регистрации в ТОМе внешних функций, определенных внутри нашего плагина.

Код:
RegFunction(L"speech",         SpeechFn,   FnOutput);
RegFunction(L"включить голос", SpeechOnFn, FnOther);
RegFunction(L"вкл голос",      SpeechOnFn, FnOther);
RegFunction(L"голос",          SpeechOnFn, FnOther);
RegFunction(L"выключить голос",SpeechOffFn,FnOther);
RegFunction(L"выкл голос",     SpeechOffFn,FnOther);
RegFunction(L"без голоса",     SpeechOffFn,FnOther);

Далее с помощью этой функции регистрируем свои собственные функции, которые расширяют набор стандартных функций ТОМа.

Теперь если в командной строке набрать speech привет или в коде игры написать speech("привет") будет вызвана функция SpeechFn() из нашего плагина.

4

Ну теперь всё гораздо понятнее. Решил сделать какой-нибудь пробный плагин на Delphi. В результате 2-х часовой пытки гугла удалось по кусочкам сложить что-то почти работающее для пробы. Т.к. "Сейчас подгружаются только dll-и с определенными именами", то опыты решил ставить на speech.dll, как на самом ненужном из двух :) (или hge.dll - это тоже плагин?).
Вот что получилось (сразу предупреждаю, код НЕ работает как надо!):

Код:
library speech;

uses
  Windows, SysUtils,
  Classes, Dialogs;

{$R *.res}

const
  FnInput=1;
  FnOutput=2;
  FnOther=3;

type
 // typedef const wchar_t* __stdcall (*wcsFn)(const wchar_t*,const wchar_t*,const wchar_t*);
 // typedef int __stdcall (*wcsRegFn)(const wchar_t*,wcsFn,int);
  wcsFn = function (const par1:PChar; const par2:PChar; const par3:PChar):PChar;   stdcall;
  wcsRegFn = function (const Name:PChar; Func:wcsFn; DP:Integer):Integer;    stdcall;

var EngineDll:THandle;

function SpeechFn(const par1:PChar; const par2:PChar; const par3:PChar):PChar;   stdcall;
begin
  ShowMessage('par1='+String(par1)+#13#10+
              'par2='+String(par2)+#13#10+
              'par3='+String(par3));
  Result:='';
end;

function Test(const par1:PChar; const par2:PChar; const par3:PChar):PChar;   stdcall;
begin
  ShowMessage('Hello world!');
  Result:='hello world!!!';
end;

procedure DLLEntryPoint(i: Integer);
var RegFunc:wcsRegFn;
begin
  case i of
    DLL_PROCESS_ATTACH:begin
      //получаем функции движка
      ShowMessage('Attach');
      EngineDll:=GetModuleHandle(PChar('tom.dll'));  //движок уже загружен
      if EngineDll=0 then begin
        MessageDlg('Ошибка подключения speech.dll. Не найден движок tom.dll', mtError, [mbOK], 0);
        Exit;
      end;
      ShowMessage('Движок найден');
      @RegFunc := GetProcAddress(EngineDll, 'RegFunction');
      if Assigned(@RegFunc) then begin
        ShowMessage(IntToStr(RegFunc('speech', SpeechFn, FnOutput)));
        RegFunc('тест', Test, FnOther);
        ShowMessage('Функция зарегана');
      end else
        MessageDlg('Не найдена функция регистрации в tom.dll', mtError, [mbOK], 0);
    end;
    DLL_PROCESS_DETACH:begin
    end;
   // DLL_THREAD_ATTACH: {...};
   // DLL_THREAD_DETACH: {...};
  end;
end;

begin
  DLLProc := @DLLEntryPoint;
  DLLEntryPoint(DLL_PROCESS_ATTACH);
end.

Подменив speech.dll на свою и запустив ТОМ, можно наблюдать последовательный показ окошек с надписями "Attach", "Движок найден", "0", "Функция зарегана", что говорит о правильно функционирующем коде и инициализации функций. Но при наборе в ТОМе "speech" или "тест" он не понимает этих слов. Вопрос: почему?
Есть подозрения на неправильный формат строк при передаче параметров в функции регистрации (RegFunc). Гугл мне намекал, что wchar_t - это что-то рядом с Юникодом, а я туды ANSI пихаю :insane: .
Может кто-нибудь помочь найти проблему? Вообще, есть среди нас люди, одновременно знающие и Си и Делфи?

5

Alexandr написал(а):

Гугл мне намекал, что wchar_t - это что-то рядом с Юникодом, а я туды ANSI пихаю

В ТОМе предусмотрен и вариант для char*.
Всё тоже самое только имя регистрирующей функции:
@RegFunc := GetProcAddress(EngineDll, 'RegFunctionANSI');
При этом ТОМ берет на себя конвертацию UNICODE -> ANSI и обратно при вызове такой внешней функции.

6

Alexandr написал(а):

hge.dll - это тоже плагин?

Нет, это графический движок для интерфейса )))))

7

ASBer написал(а):

@RegFunc := GetProcAddress(EngineDll, 'RegFunctionANSI');

Заработало! Я и не надеялся на такое простое решение.
Ух, сейчас что-нибудь напишу! :writing:

ASBer написал(а):

в будущем будет подгрузка всех плагинов из каталога движка по определенному расширению.

Очень жду. :)

8

Alexandr написал(а):

Заработало!

Грац!  :jumping:

9

А можно поподробнее об "enum{ FnInput=1, FnOutput=2, FnOther=3 };"? Зачем нужен третий параметр функции RegFunction?

И о вызове функций.
const wchar_t* __stdcall SoundFn(const wchar_t*Login,const wchar_t*Text,const wchar_t*)
Назначение параметров Login, Text и... безымянного (мне страшно :confused: ).
В Login ТОМ помещает "adm". Что это?

В свои функции нельзя добавлять больше 2х параметров? Тесты показали, что при вызове
speech("hello", "hi", "3", "четвёртый")
в параметрах передаётся:
Login = adm
Text = hello
3й = hi
остальные пропали...

P.S. Ох замучаю я сейчас вопросами...

10

Такс... поехали  :mybb:

const wchar_t* __stdcall Fn(const wchar_t*Login, const wchar_t*Arg1, const wchar_t*Arg2)
все внешние функции должны иметь такой формат.
const char* __stdcall Fn(const char*Login, const char*Arg1, const char*Arg2)
ну или такой вариант для ANSI

Login - это ID сессии, которая вызывает функцию. Обязательно пригодится в будущем.
При инициализации ТОМа по умолчанию создаётся сессия "adm". В простых однопользовательских играх других сессий вероятно никогда и не будет.
Сейчас многопользовательский режим в стадии разработки.

Arg1 - строковый аргумент функции. Если нужно более одного аргумента, их придется склеивать в строку - "Arg1; Arg2; Arg3" и затем парсить... либо обходить ограничение по-другому.
Обычно одной строки вполне достаточно.

Arg2- дополнительный строковый аргумент функции. Используется редко, если одной строки ну никак не хватает...

Типы внешних функций:
enum{ FnInput=1, FnOutput=2, FnOther=3 }

FnOutput - функция что-нибудь выводит. Печатает текст на экран, воспроизводит звук, читает текст, рисует картинки и т.д.
При использовании эти функции не вызываются сразу, а накапливаются в пул. Это нужно для сохранения последовательности вывода текста, музыки, звуков и т.д. А также для пост-обработки накопленного выводимого текста целиком, а не обрывками.

FnInput - функция что-нибудь запрашивает. Например ввод командной строки или ответ на вопрос.
Перед выполнением такой функции сначала прокручивается и очищается пул функций FnOutput, а затем вызывается сама функция ввода.

FnOther функция выполняется незаметно для пользователя. Ничего не выводит и ничего не просит. Выполняется сразу.

11

Есть несколько специальных внешних функций, зарезервированных для интерфейса:
print (FnOutput)
input (FnInput)
error (FnOther)
debug (FnOther)
speech (FnOutput)
error и debug вообще-то выводят текст, но вне очереди...
обязательными для работы ТОМа являются только print и input
как и все внешние функции эти тоже можно вызывать из командной строки и из кода игры, хотя особого смысла в этом нет - движок их сам вызывает когда нужно.

12

ASBer написал(а):

Плагин - это всегда dll

Alexandr написал(а):

hge.dll - это тоже плагин?

Чтобы не путать в дальнейшем, можно для плагинов ввести свое расширение (например, .tp), и переименовывать dll-ки-плагины. Также/Или их можно поместить в отдельную папку, скажем, Plugins.

13

Apromix написал(а):

Чтобы не путать в дальнейшем, можно для плагинов ввести свое расширение (например, .tp)

ASBer написал(а):

Сейчас подгружаются только dll-и с определенными именами, в будущем будет подгрузка всех плагинов из каталога движка по определенному расширению.

Ждём будущего. :)
На счёт папки, согласен. Можно вынести в отдельную, чтоб не загромождать корневую папку. Но лично для меня это не принципиально. Главное - расширение или префикс.

Apromix, добро пожаловать на форум. Рады новому пользователю.

14

Alexandr написал(а):

На счёт папки, согласен. Можно вынести в отдельную, чтоб не загромождать корневую папку.

Нее... в папке с движком не должно быть много файлов с плагинами. Музыка и речь - это всё что я хотел добавить.
Другие плагины вероятно будут делаться к конкретным играм по необходимости. Их логично размещать в папках с играми, а для подключения использовать команду "include"

15

Автору виднее. Еще могут быть плагины для редактора и других инструментов.

16

Apromix написал(а):

Чтобы не путать в дальнейшем, можно для плагинов ввести свое расширение (например, .tp)

Введено расширение для плагинов - .plg.dll  :cool:

17

ASBer написал(а):

Введено расширение для плагинов - .plg.dll

То есть, например, plugin_name.plg.dll? Интересно :) А почему не просто .plg или tpg? Ведь плагины сами по себе dll. А есть уже руководство по плагинам к ТОМу?

18

Apromix написал(а):

А почему не просто .plg или tpg? Ведь плагины сами по себе dll

Именно потому что "плагины сами по себе dll". Зачем же специально это маскировать? ;)

Apromix написал(а):

А есть уже руководство по плагинам к ТОМу?

Наиболее полное руководство по плагинам с примерами находится в этой теме.
На википедии еще немного расписал, но там в детали не углублялся...  :writing:

19

Обновил листинг кода speech.plg.dll (2 пост в этой теме).