
Немного общих слов.
Этот вопрос был решен написанием внешнего приложения, внедряющегося в адресное пространство процесса trainz.exe и обменивающимся данными со скриптом через участок его памяти. Намеренно не стану сообщать, на каком языке она написана, так как тут же появятся [strike]проповедники языка истинного[/strike] «вумные» [strike]сионисты и пасквилянты[/strike] программисты

Кстати говоря, следует сказать пару слов по поводу следующего высказывания
ну ну Гении - влезть в чужой бинарник своими руками и без вылетов - посмотрим
Не спорю, ковыряться в чужом адресном пространстве не безопасно, но за время тестирования (более 50 запусков) не произошло ни одного "вылета" (до тех пор, пока не занялся голосовым чатом и не начал развешивать глобальный хук на клавиатуру

Обмен данными между экземплярами программ-клиентов по сети осуществляется через DirectPlay. Что до вумных программистов, считающих интерфейсами зла все кроме сокетов, [strike]то пусть продолжают заниматься с winsock многопоточным онанизмом[/strike] желаю им приятного времяпровождения при конструировании давно изобретенных велосипедов. Так как на моих глючных F{пи-и-ик}YouBill’ьных окошках DirectX версии 8 и более новые частично не работает (зато отлично работает то, что криво работало в «чистой» системе

На настоящий момент реализован также голосовой чат. Чтобы можно было устанавливать соединения с кем-либо, нужно назначить во вкладке «голосовой чат» соответствующую горячую клавишу. Управление чатом следующее: нажимаем на клавишу «Alt», затем не отпуская его жмем и отпускаем назначенную данному игроку клавишу (Alt не отпускаем) – всё, «исходящий канал открыт». Для закрытия канала нужно всего-навсего отпустить «Alt».
Голосовые сообщения отправляются по частям (1 часть – 0,2 мсек) также в сжатом виде. Для сжатия используется кодек speex (ох, и запыхался же с ним!


Пожалуй, довольно лелеять функционал программы-клиента. Тем более, что сама программа ещё до конца не отлажена, и то ли из-за несовместимости глобальных клавиатурных хуков с отладчиком, то ли из-за неправильного использования CopyMemory (никак не получается сыскать источник-рассадник багов), все вместе с отладчиком систематически накрывается медным тазом, сопровождаясь победоносным восклицанием Билла: «память не может быть “read”!». (знаете, порой так хочется открыть kernel32.exe в каком-нибудь PELord’е, откуда родом эта CopyMemory, и в константе подменить «read» на какое-нибудь непристойное выражение…

Протокол
Полагаю, что всех уже утомил своими пустыми разглагольствованиями, поэтому этот раздел постараюсь написать коротко и ясно. За передачу данных в скрипте от игры до игры отвечает предназначенный для этой цели статический класс 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()
В поле TypeOfIncomingPackets ОБЯЗАТЕЛЬНО должен быть установлен куид пакетов, которые обрабатываются этим обработчиком. Никакие другие пакеты в данный обработчик поступать не будут (в принципе можно указать любой куид, не обязательно, чтобы он совпадал с куидом вашего объекта).
При поступлении каждого сообщения с указанным куидом вызвается метод RecievePacket (блин, только сейчас узнал, что правильно пишется Receive

Естественно, просто так обработчик сообщений исполнять своих функций не будет. Чтобы он начал обрабатывать сообщения, ссылку на него нужно передать модему, для чего у последнего есть процедура 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);
}
};