Информация: Уважаемые посетители! В течение нескольких месяцев на форуме существовала проблема с регистрацией новых пользователей, о которой администрации стало известно недавно. Если вы ранее пытались зарегистрироваться на форуме, но не получили на ваш e-mail письмо с ссылкой для подтверждения регистрации, просим вас зарегистрироваться повторно. Приносим извинения за доставленные неудобства. Если вы все еще испытываете проблемы с регистрацией на форуме, обратитесь за помощью на e-mail: mr.angelo@railroadsim.net

StrVL
 
Сообщения: 43
Зарегистрирован: 20.11.2009, 08:17
Откуда: Иркутск
Играю в: Auran Trainz
Блог: Просмотр блога (2)
Поиск в блогах

1 из 52 из 53 из 54 из 55 из 5

Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка StrVL 31.08.2010, 19:27

Несколько дней назад я обещал опубликовать мультиплеерный протокол для 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);
}
};
Последний раз редактировалось StrVL 31.08.2010, 22:29, всего редактировалось 1 раз.

19 комментариев 76592 просмотров
Комментарии
След.

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка TRam_ 31.08.2010, 21:33

>Размер участка памяти строго фиксирован (пока 10 Кбайт) и в процессе игры не изменяется (впоследствии размер будет задаваться в правилах сессии).

о, я такое делал :). Ты б знал, сколько меня ругали за то, что "размер области фиксирован".
в z7 всё можно, а что нельзя - можно в sU
Аватара пользователя
TRam_
 
Сообщения: 1904
Зарегистрирован: 30.11.2007, 20:14
Играю в: Auran Trainz
Роль: Разработчик
Имя: Владимир
Блог: Просмотр блога (0)

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка UTUBE 01.09.2010, 00:22

Всем добрый вечер.Ну а как этим всем пользоваться,или ещё рано пока?
Спасибо.
Не ту страну назвали Гондурасом.
AMD FX(tm)-4100 Quad-Core Processor 3.61GHz (озу)16.0 ГБ Video Zotac 450 Sli_2
Аватара пользователя
UTUBE
 
Сообщения: 574
Зарегистрирован: 24.09.2008, 13:09
Откуда: СССР
Играю в: Auran Trainz
Роль: Пассажир :)
Имя: Геннадий
Блог: Просмотр блога (0)

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка kemal 01.09.2010, 20:31

А обязательно писать Modem.AddHandler(me); ? Или можно сделать так, чтобы приёмом сообщений занимался отдельный класс?
<kuid:216981:******>
куид.ру не нужен
Аватара пользователя
kemal
 
Сообщения: 374
Зарегистрирован: 10.02.2006, 18:21
Откуда: Казань
Играю в: Auran Trainz
Роль: Разработчик
Имя: Камиль
Блог: Просмотр блога (4)

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка AlexanderG 01.09.2010, 21:11

>В поле DestPlayerNum может принимать не только значения номеров игроков получателя, но и дополнительные 2 специальных значения:
-1 – отправка служебного сообщения программе (для отправки специальных сообщений, например, управляющих голосовым чатом)
-2 – отправка широковещательного сообщения (сообщения, отправляемого всем игрокам одновременно)<

Лучше не использовать -1, это общепринятый аналог отсутствующего/ошибочного значения.
Join Dropbox and SHARE YOUR SHIT FOR FREE!
AlexanderG
 
Сообщения: 762
Зарегистрирован: 27.06.2009, 16:54
Имя: Александр
Блог: Просмотр блога (2)

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка StrVL 01.09.2010, 21:24

kemal писал(а):Или можно сделать так, чтобы приёмом сообщений занимался отдельный класс?

В смысле, нужно ли делать обработчиком класс, представляющий реальный игровой объект (т. е. наследуемый от какого-нибудь Buildable/BaseIndustry и прочих и указываемый в конфиге)? Нет, не обязательно, можно написать отдельный класс-обработчик, но AddHandler вызывать все же необходимо:
Код: Выделить всё
class MyMessageHandler isclass Handler {
//…
};
class MyObject isclass Buildable {
//…
MyMessageHandler A=new MyMessageHandler();
A.TypeOfIncomingPackets=GetAsset().GetKUID();
Modem.AddHandler(A);
//...
};

AlexanderG писал(а):Лучше не использовать -1, это общепринятый аналог отсутствующего/ошибочного значения.

Не вижу смысла использовать номер игрока в качестве признака ошибочного сообщения. Да и вообще я как бы не выделял "ошибочные" сообщения (а пустые тем более). В случае ошибки (неверный номер игрока, превышение лимита по размеру сообщения, неверный тип) сообщение просто не будет помещено в очередь отправки или отброшено в процессе доставки, не доходя до получателя.
StrVL
 
Сообщения: 43
Зарегистрирован: 20.11.2009, 08:17
Откуда: Иркутск
Играю в: Auran Trainz
Блог: Просмотр блога (2)

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка agmike 06.09.2010, 16:20

Ох вау. Хочется только спросить, есть/планируется ли возможность передачи данных из ТРС в другие программы и наоборот, самое очевидное применение этому — выносной контроллер. Хотя я вряд ли стал бы это делать, меня больше волнует возможность добавить в игру новые клавиши с клавиатуры, желательно все.
The Cake is a Lie.
agmike
 
Сообщения: 1025
Зарегистрирован: 26.08.2006, 12:43
Играю в: Пульт ДНЦ
Блог: Просмотр блога (0)

RE: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка StrVL 06.09.2010, 19:19

Возможность подключения «плагинов» запланирована, но в первой версии однозначно её не будет. Что касается клавиш, то уже сейчас в программе используется отслеживание нажатия всех клавиш клавиатуры (нужно для голосового чата). Могу немного дописать код так, чтобы соответствующие события дополнительно посылались в скрипт trainz’, и в последнем рассылалось широковещательное сообщение (PostMessage) о нажатии клавиши. Кстати, есть небольшая вероятность, что небольшая модификация кода позволит также «блокировать» нажатия заданных клавиш, чтобы оное не возымело какого-нибудь стандартного действия в trainz. Небольшая потому, что скорее всего trainz работает с клавиатурой напрямую средствами DirectInput (или аналога). В этом случае, как говорится, «номер не пройдет».
Однако нужно ли это? Просто мне кажется, что большинство отнесется крайне скептически (либо лениво) к локомотиву, для работы которого потребуется где-то дополнительно скачивать и, самое главное, каждый раз запускать «какую-то» прогу.
Пара слов о стадии разработки мультиплеера (некоторых, наверное, интересует :shuffle: …). Сейчас работа ведется над совершенствованием механизма синхронности движения поездов, а также над устранением «косяков». Всплыло несколько частных случаев, в которых данный механизм дает сбой. Готова синхронная подача свистка, управление пантографами и светом лобового прожектора.
StrVL
 
Сообщения: 43
Зарегистрирован: 20.11.2009, 08:17
Откуда: Иркутск
Играю в: Auran Trainz
Блог: Просмотр блога (2)

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка agmike 06.09.2010, 22:57

За клавиши голосую, броадкастом. Блокировать ничего не надо, кейбоард.тхт легко редактируется и реально в нем используется пара десятков строк максимум.
The Cake is a Lie.
agmike
 
Сообщения: 1025
Зарегистрирован: 26.08.2006, 12:43
Играю в: Пульт ДНЦ
Блог: Просмотр блога (0)

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка AlexanderG 08.09.2010, 11:12

Насчет плагинов, у меня, когда я этим вопросом занимался, была мысль использовать сокеты — общаться с плагинами через лупбек-интерфейс. Это просто программируется и обеспечивает всю необходимую функциональность.
Join Dropbox and SHARE YOUR SHIT FOR FREE!
AlexanderG
 
Сообщения: 762
Зарегистрирован: 27.06.2009, 16:54
Имя: Александр
Блог: Просмотр блога (2)

Re: Мультиплеер в trainz. Принцип. Технология. Протокол

Постоянная ссылка AlexanderG 11.09.2010, 00:42

Ну что, все сдохло?
Join Dropbox and SHARE YOUR SHIT FOR FREE!
AlexanderG
 
Сообщения: 762
Зарегистрирован: 27.06.2009, 16:54
Имя: Александр
Блог: Просмотр блога (2)

Кто сейчас на конференции

Зарегистрированные пользователи: Alexa [Bot], Bing [Bot], Google [Bot], le Sandro, Yandex [Bot]

cron