RailUnion.net


http://railunion.net/blog/TRam_/Re_%D0%9C%D1%83%D0%BB%D1%8C%D1%82%D0%B8%D0%BF%D0%BB%D0%B5%D0%B5%D1%80_%D0%B2_trainz._%D0%9F%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF._%D0%A2%D0%B5%D1%85%D0%BD%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D1%8F._%D0%9F%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB_r-549_start-10_sid-caebb8ee3dcec0cb386cdd53ff8f2603.html

Автор:  StrVL [ 31.08.2010, 19:27 ]
Тема блога:  Мультиплеер в trainz. Принцип. Технология. Протокол

Несколько дней назад я обещал опубликовать мультиплеерный протокол для trainz. Вот, момент настал, решил назначить релиз на сегодня. Однако ж прежде следует ввести в курс дела тех, кто не читал. Как такового мультиплеера в trainz никогда не было и вряд ли он появится в ближайшее время. Тем не менее, как оказалось, реализовать его своими руками вполне возможно и даже несложно (правда, как говорил Эйнштейн, все в мире относительно :vah: ). Возможности траинзэтовсского скриптового движка позволяют так или иначе контролировать весь игровой процесс. То, что они не позволяют реализовать прямо, часто поддается реализации путем различных форм извращений над скриптами. Единственная не решаемая скриптом проблема состояла в том, чтобы каким-либо образом передать данные во «внешний мир».
Немного общих слов.
Этот вопрос был решен написанием внешнего приложения, внедряющегося в адресное пространство процесса trainz.exe и обменивающимся данными со скриптом через участок его памяти. Намеренно не стану сообщать, на каком языке она написана, так как тут же появятся [strike]проповедники языка истинного[/strike] «вумные» [strike]сионисты и пасквилянты[/strike] программисты :punk: , которым никак [strike]неймется[/strike] не удается [strike]не облить грязью всех вокруг и[/strike] свой [strike]фанатизм[/strike] богатый запас знаний [strike]засунуть в одно место[/strike] донести до [strike]«быдлокодеров»[/strike] невежественной толпы. Не буду вдаваться и в подробности реализации, скажу лишь самое основное. Участок памяти представляет собой строковый массив, единожды размещаемый скриптом в памяти. При инициализации в этот массив помещается особая сигнатура (к слову, всегда одна и та же), которую впоследствии и отыскивает в памяти процесса программа среди океана байт. Считывание-запись данных осуществляется по очереди то скриптом, то программой (определяется состоянием первого байта массива). Размер участка памяти строго фиксирован (пока 10 Кбайт) и в процессе игры не изменяется (впоследствии размер будет задаваться в правилах сессии).
Кстати говоря, следует сказать пару слов по поводу следующего высказывания
ну ну Гении - влезть в чужой бинарник своими руками и без вылетов - посмотрим

Не спорю, ковыряться в чужом адресном пространстве не безопасно, но за время тестирования (более 50 запусков) не произошло ни одного "вылета" (до тех пор, пока не занялся голосовым чатом и не начал развешивать глобальный хук на клавиатуру :D ).
Обмен данными между экземплярами программ-клиентов по сети осуществляется через DirectPlay. Что до вумных программистов, считающих интерфейсами зла все кроме сокетов, [strike]то пусть продолжают заниматься с winsock многопоточным онанизмом[/strike] желаю им приятного времяпровождения при конструировании давно изобретенных велосипедов. Так как на моих глючных F{пи-и-ик}YouBill’ьных окошках DirectX версии 8 и более новые частично не работает (зато отлично работает то, что криво работало в «чистой» системе :D ), пришлось довольствоваться DP из состава DirectX 7. Данные по сети передаются в сжатом с помощью библиотеки zlib виде.
На настоящий момент реализован также голосовой чат. Чтобы можно было устанавливать соединения с кем-либо, нужно назначить во вкладке «голосовой чат» соответствующую горячую клавишу. Управление чатом следующее: нажимаем на клавишу «Alt», затем не отпуская его жмем и отпускаем назначенную данному игроку клавишу (Alt не отпускаем) – всё, «исходящий канал открыт». Для закрытия канала нужно всего-навсего отпустить «Alt».
Голосовые сообщения отправляются по частям (1 часть – 0,2 мсек) также в сжатом виде. Для сжатия используется кодек speex (ох, и запыхался же с ним! :beak: ). Битрейт, однако же, получается просто неблагопристойно большой – 15-20 Кбайт/сек на сообщение :o (несмотря на то, что кодеку назначено сжимать до 15 Кбит/сек). Поддерживается только одно одновременное исходящее соединение (при этом оно может быть широковещательным и транслироваться сразу всем) и теоретически неограниченное количество входящих.
Пожалуй, довольно лелеять функционал программы-клиента. Тем более, что сама программа ещё до конца не отлажена, и то ли из-за несовместимости глобальных клавиатурных хуков с отладчиком, то ли из-за неправильного использования CopyMemory (никак не получается сыскать источник-рассадник багов), все вместе с отладчиком систематически накрывается медным тазом, сопровождаясь победоносным восклицанием Билла: «память не может быть “read”!». (знаете, порой так хочется открыть kernel32.exe в каком-нибудь PELord’е, откуда родом эта CopyMemory, и в константе подменить «read» на какое-нибудь непристойное выражение… <angry> )
Протокол
Полагаю, что всех уже утомил своими пустыми разглагольствованиями, поэтому этот раздел постараюсь написать коротко и ясно. За передачу данных в скрипте от игры до игры отвечает предназначенный для этой цели статический класс Modem. Данные передаются вот такими пакетами:
Код: Выделить всё
class Packet {
   public string major; //первое поле данных, полностью в вашем распоряжении
   public string minor; //второе поле данных, также полностью в вашем распоряжении
   public string type;//тип пакета (об этом далее)
   public int DestPlayerNum;//номер игрока, кому сообщение должно быть доставлено (нумерация с нуля) (ставьте -2 для отправки широковещательного сообщения)
   public int SrcPlayerNum;//номер текущего игрока
}

Пара слов о типе. Много разных дополнений могут одновременно посылать и получать пакеты. Чтобы различные объекты не путали пакеты, и таким образом не воцарился хаос, каждому пакету назначен тип, определяющий, какой объект его породил. При отправке пакета в поле type нужно ОБЯЗАТЕЛЬНО занести kuid вашего объекта. Можно, конечно, указать чужой kuid, но во избежание путаницы этого лучше не делать.
В поле DestPlayerNum может принимать не только значения номеров игроков получателя, но и дополнительные 2 специальных значения:
-1 – отправка служебного сообщения программе (для отправки специальных сообщений, например, управляющих голосовым чатом)
-2 – отправка широковещательного сообщения (сообщения, отправляемого всем игрокам одновременно)
Поле SrcPlayerNum можно не указывать: оно будет назначено при отправке сообщения.
Так как фрагментация пакетов не поддерживается, длина пакета ограничена размером выходного буфера, и вам следует следить, чтобы она не была превышена (иначе пакет не будет помещен в очередь). Для этого у класса Modem есть метод public bool VeryBigPacket(Packet p), возвращающий истину, если пакет не умещается в буфере отправки.
У класса Modem есть много различных public-методов, но в вашем распоряжении есть только следующие (остальные, что называется «системные»):
  • public int MaxPacketSize() – получение максимально возможной длины исходящего пакета
  • public bool ConnectionStatus() - возвращает истину, если соединение установлено
  • public int GetCurrentPlayerNum() – получение порядкового номера текущего игрока (нумерация начинается с нуля) на данный момент
  • public int GetTotalPlayers() – метод для получения общего количества игроков в игре на данный момент
  • public bool VeryBigPacket(Packet p) – метод, проверяющий, уместится ли пакет в буфере отправки
  • public void PushPacket(Packet p) – метод для отправки пакета. Пакет помещается в конец очереди отправки пакетов.
  • public void AddHandler(Handler h) – добавление обработчика сообщений (об этом ниже)
  • public void RemoveHandler(Handler h) – удаление обработчика сообщений
Обработчик сообщений – класс, принимающий поступающие сообщения. Обработчик сообщений имеет два метода и одно поле:
  • public KUID TypeOfIncomingPackets
  • public void RecievePacket(Packet p)
  • public void ConnectionOptionsChanged()
При разработке мультиплеерного дополнения вам следует переопределить этот класс для обработки собственных сообщений (при этом метод ConnectionOptionsChanged при отсутствии необходимости можно и не переопределять).
В поле TypeOfIncomingPackets ОБЯЗАТЕЛЬНО должен быть установлен куид пакетов, которые обрабатываются этим обработчиком. Никакие другие пакеты в данный обработчик поступать не будут (в принципе можно указать любой куид, не обязательно, чтобы он совпадал с куидом вашего объекта).
При поступлении каждого сообщения с указанным куидом вызвается метод RecievePacket (блин, только сейчас узнал, что правильно пишется Receive :blush: ). Метод ConnectionOptionsChanged вызывается при каких-либо изменениях состояния соединения (установлено/разорвано соединение, подключился новый/отключился существующий игрок, …).
Естественно, просто так обработчик сообщений исполнять своих функций не будет. Чтобы он начал обрабатывать сообщения, ссылку на него нужно передать модему, для чего у последнего есть процедура AddHandler. Она помещает ссылку на обработчик в особую иерархическую структуру – список обработчиков, после чего пакеты с соответствующим куидом начнут обрабатываться. На один и тот же куид может быть назначено несколько обработчиков. Можно и удалить обработчик из списка с помощью метода RemoveHandler.
Существует статический класс LocalPlayerOwnership для описания объектов, доступ к которым локальному игроку открыт. Можно использовать его методы, чтобы определить, управляет ли некоторым объектом локальный игрок:
  • public bool TrainUnderControl(Train Tr) – функция проверяет, управляется ли поезд текущим игроком
  • public bool JunctionUnderControl(string JName) – функция проверяет, управляется ли стрелка текущим игроком
  • public bool JunctionIsShared(string JName) – функция проверяет, является ли стрелка «общедоступной»
  • public bool JunctionIsMy(string JName) – функция проверяет, находится ли стрелка в «вотчине» текущего игрока
  • public bool JunctionIsOnlyMy(string JName) – функция проверяет, управляется ли стрелка текущим игроком и никем больше
И напоследок, приведу маленький примерчик скрипта мультиплеерного объекта, отправляющего и получающего пакет. Модуль с мультиплеерными классами можно скачать здесь.
Код: Выделить всё
include “Buildable.gs”
include “MultiplayerClasses.gs”
class MyObject isclass Buildable, Handler {  //можно и не buildable, хоть DriverCharacter

public void RecievePacket(Packet p) {
if (p.major==”Paket1”)
Interface.Print(“Prishol paket 1 takogo soderjanuja : ” + p.minor);
if (p.major==”Paket2”)
Interface.Print(“Prishol paket 2 takogo soderjanuja : ” + p.minor);

}

public void ConnectionOptionsChanged()
{
if (Modem.ConnectionStatus()) {
Packet p=new Packet();
p.type = TypeOfIncomingPackets.GetLogString();
p.major = “Packet1”;
p.minor = “YaYa!!!”;
p.DstPlayerNum = -2; //всем-всем …
Modem.PushPacket(p);
}
}

public void Init(void)
{
         inherited();
         TypeOfIncomingPackets=GetAsset().GetKUID();
         Modem.AddHandler(me);
}
};




{ BLOG_REPLIES }

Автор:  StrVL [ 11.09.2010, 15:13 ]

AlexanderG писал(а):Ну что, все сдохло?

Напротив, скоро будет готов первый «Release Candidat» (правда, стоит ли напоминать о действии давнишнего программистского обычая неумышленно выпускать глючный релиз в качестве первого…). Вчера ночью успешно завершил работу над сцеплением-расцеплением поездов, добавил возможность задавать на голосовой чат любые комбинации клавиш (специально для мазохистов Alt + F4 :D ) (не стал делать Num/Caps/Scroll Lock, Win, Power-Sleep). Предстоит ночь серьезного тестирования, и если оно пройдет успешно (в чем, у меня, честно говоря, сомнения), то останется написать только краткую справку.
Скоро надо будет тестеров набирать...
agmike писал(а):За клавиши голосую, броадкастом. Блокировать ничего не надо, кейбоард.тхт легко редактируется и реально в нем используется пара десятков строк максимум.

Сделано. При нажатии клавиши (системное событие KeyDown) рассылается широковещательное сообщение (PostMessage), major которого равен NotifyKeyPressed, а minor - виртуальый код клавиши.
AlexanderG писал(а):Насчет плагинов, у меня, когда я этим вопросом занимался, была мысль использовать сокеты — общаться с плагинами через лупбек-интерфейс. Это просто программируется и обеспечивает всю необходимую функциональность.

Если честно, о технологии всерьез пока не размышлял. Вскользь мелькала идея применить старый прадедовский механизм динамических библиотек (метод прост до крайности и известен почти всем, кому понадобится написать плагин, так зачем прибегать к чему-либо ещё?). Впрочем, всему свое время.

Автор:  agmike [ 11.09.2010, 16:06 ]

StrVL писал(а):
agmike писал(а):За клавиши голосую, броадкастом. Блокировать ничего не надо, кейбоард.тхт легко редактируется и реально в нем используется пара десятков строк максимум.

Сделано. При нажатии клавиши (системное событие KeyDown) рассылается широковещательное сообщение (PostMessage), major которого равен NotifyKeyPressed, а minor - виртуальый код клавиши.

А в случае, когда нажато несколько клавиш одновременно, мессежды о них будут высланы последовательно?

Автор:  StrVL [ 11.09.2010, 19:19 ]

agmike писал(а):А в случае, когда нажато несколько клавиш одновременно, мессежды о них будут высланы последовательно?

Прочитав сообщение, решил немного модифицировать механизм. Теперь рассылается два сообщения отдельно: NotifyKeyUp и NotifyKeyDown (нажатие и отпуск клавиши соответственно). Это позволит «отлавливать» комбинацию из нескольких одновременно нажатых клавиш.
Да, сами сообщения будут высланы последовательно в том порядке, в котором клавиши были нажаты/отпущены.

Автор:  agmike [ 11.09.2010, 23:28 ]

Отлично, спасибо.

Автор:  agmike [ 29.09.2010, 21:33 ]

StrVL, скажи что-нибудь :)

Автор:  StrVL [ 01.10.2010, 16:54 ]

Давно (относительно) хотел сообщить, да не решался, что пока разработка мультиплеера приостановлена в связи с чрезвычайной (нет, не то [ :banned: ], что вы могли подумать :D) необходимостью защиты отчета по производственной практике и кое-чего ещё :D (скорее всего, на грядущей неделе покончу со всем этим и разморожу работы). Хотя до релиза, честно говоря, оставалась пара полных дней кропотливой работы. Однако пока не до этого.

Автор:  agmike [ 03.10.2010, 20:09 ]

Ждемс.

Автор:  StrVL [ 19.10.2010, 15:45 ]

Напряженка на учебе только крепчает. Дневник практики заставили переписывать, «кое-что» хоть и проставили «авансом», тем не менее, надо досдавать. Ещё нужно прекратить кормить обещаниями преподавателя (вернее, двух – работаем на два фронта ) и написать-таки давно обещанную публикацию (две). В общем, все настолько печально, что порой хочется Изображение

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

Автор:  AlexanderG [ 20.10.2010, 00:00 ]

Ждем с нетерпением!

Часовой пояс: UTC + 4 часа

Powered by phpBB © 2002, 2006 phpBB Group
www.phpbb.com

Blogs powered by User Blog Mod © EXreaction
www.lithiumstudios.org