Ил-2 Штурмовик: Битва за Британию. Скрипты. Скрипты для чайников. Часть 3 - Загрузка подмиссий — различия между версиями

Материал из АвиаВики
Перейти к: навигация, поиск
(Загрузка подмиссий (автор FG28_Kodiak))
 
(не показано 11 промежуточных версии ещё одного участника)
Строка 1: Строка 1:
 
:'''Автор:''' FG28_Kodiak
 
:'''Автор:''' FG28_Kodiak
 
:'''Ссылка:''' [http://translate.googleusercontent.com/translate_c?hl=en&rurl=translate.google.com&sl=de&tl=en&twu=1&u=http://forum.sturmovik.de/index.php/topic,721.0.html%3FPHPSESSID%3Dac198c6ea43133020cb39e5f138fa85a&usg=ALkJrhiUPU7IvyTBPlLaOn7JmUlwfZx2pw Перейти (перевод Google)]
 
:'''Ссылка:''' [http://translate.googleusercontent.com/translate_c?hl=en&rurl=translate.google.com&sl=de&tl=en&twu=1&u=http://forum.sturmovik.de/index.php/topic,721.0.html%3FPHPSESSID%3Dac198c6ea43133020cb39e5f138fa85a&usg=ALkJrhiUPU7IvyTBPlLaOn7JmUlwfZx2pw Перейти (перевод Google)]
 +
 +
Скачать оригиналы миссий:
 
:[http://forum.sturmovik.de/index.php?action=dlattach;topic=721.0;attach=218 Samples.zip]
 
:[http://forum.sturmovik.de/index.php?action=dlattach;topic=721.0;attach=218 Samples.zip]
 +
Скачать примеры с Sukhoi.ru:
 +
:[http://www.sukhoi.ru/forum/attachment.php?attachmentid=142554&d=1318334365 Samples.zip]
  
Having in the past two lessons on the topic, the missions have loaded at the beginning in our ongoing mission, we will make it in today's time-controlled. To this end, the game presents us with the method public virtual void OnTickGame () are available, these can be for our purposes to write about.
 
  
So
+
В предыдущих двух миссиях мы загружали под-миссии после начала миссии-хоста. В этой части мы научимся контролировать загрузку миссиий при помощи метода '''public virtual void OnTickGame ()'''
public override void OnTickGame ()  
+
This is the game about 34 times OnTickGame calls per second, depending on computer usage may be more or less time.
+
Example:
+
  
using System;
+
'''public override void OnTickGame()'''
using maddox.game;
+
using maddox.game.world;
+
  
public class Mission : AMission
+
Метод '''OnTickGame()''' направляет вызовы с частотой 34 ''тика'' в секунду, в зависимости от компьютера может быть больше или меньше времени.
{
+
public override void OnTickGame()  
+
{
+
double WievielSekunden = 0;
+
  
if (Time.tickCounter() == 334)
+
Например:  
{
+
WievielSekunden = Time.TicksToSecs(Time.tickCounter());
+
GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
+
}
+
if (Time.tickCounter() == 667)
+
{
+
WievielSekunden = Time.TicksToSecs(Time.tickCounter());
+
GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
+
}
+
if (Time.tickCounter() == 1000)
+
{
+
WievielSekunden = Time.TicksToSecs(Time.tickCounter());
+
GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
+
}
+
}
+
}
+
  
The example does nothing more than after 10, 20 and 30 seconds to write a short message in the chat bar.  
+
using System;
Time.tickCounter () contains the number of ticks since the mission was launched and with the help of Time.TicksToSecs (number of ticks), we can convert this number of seconds.  
+
using maddox.game;
GamePlay.gpLogServer (null, "Message to {0} {1} ticks are the sec \ n"., New object [] {Time.tickCounter (), WievielSekunden});  
+
using maddox.game.world;
then leads to the following issue:  
+
public class Mission : AMission
 +
{
 +
    public override void OnTickGame()
 +
    {
 +
        double WievielSekunden = 0;
 +
       
 +
        if (Time.tickCounter() == 334)
 +
        {
 +
            WievielSekunden = Time.TicksToSecs(Time.tickCounter());
 +
            GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
 +
        }
 +
        if (Time.tickCounter() == 667)
 +
        {
 +
            WievielSekunden = Time.TicksToSecs(Time.tickCounter());
 +
            GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
 +
        }
 +
        if (Time.tickCounter() == 1000)
 +
        {
 +
            WievielSekunden = Time.TicksToSecs(Time.tickCounter());
 +
            GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
 +
        }
 +
  }
 +
}
 +
 
 +
Данный пример ничего не делает, кроме как отправляет сообщение в чат сервера каждые 10, 20, 30 секунд.
 +
 
 +
'''Time.tickCounter()''' - содержит количество тиков, прошедших с момента начала мисиии
 +
'''Time.TicksToSecs''' - переводит такты в секунды
 +
 
 +
Далее выводим сообщение на экран:
 +
GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
 +
 
 +
В игре это будет выглядеть так:  
  
  
Строка 47: Строка 57:
  
  
You can use OnTickGame () also, if you want to spend some cyclically.
+
Также вы можете использовать '''OnTickGame()''' для задания циклов, зависящих от времени, например:
Example:  
+
  
using System;  
+
using System;
using maddox.game;  
+
using maddox.game;
using maddox.game.world;  
+
using maddox.game.world;
 +
public class Mission : AMission
 +
{
 +
    public override void OnTickGame()
 +
    {
 +
        double WievielSekunden = 0;
 +
       
 +
        if (Time.tickCounter() % 334 == 0)
 +
        {
 +
            WievielSekunden = Time.TicksToSecs(Time.tickCounter());
 +
            GamePlay.gpLogServer (null, "Erste Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
 +
        }
 +
        if (Time.tickCounter() % 509 == 0)
 +
        {
 +
            WievielSekunden = Time.TicksToSecs(Time.tickCounter());
 +
            GamePlay.gpLogServer (null, "Zweite Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
 +
        }
 +
    }
 +
}
  
public class Mission : AMission
+
Для вычисления остатка при целочисленном делении в языке С# используется оператор деления по модулю %. Он применяется таким же образом, что и оператор деления, но в результате вычисляется не частное, а остаток.
{
+
public override void OnTickGame()
+
{
+
double WievielSekunden = 0;
+
  
if (Time.tickCounter() % 334 == 0)
+
3%3  результат: 0
{
+
3%2  результат: 1
WievielSekunden = Time.TicksToSecs(Time.tickCounter());
+
5%3  результат: 2
GamePlay.gpLogServer (null, "Erste Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
+
}
+
if (Time.tickCounter() % 509 == 0)
+
{
+
WievielSekunden = Time.TicksToSecs(Time.tickCounter());
+
GamePlay.gpLogServer (null, "Zweite Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
+
}
+
}
+
}
+
  
To spend our messages periodically, we use here the modulo operator in C # so%. The Modulo returns the integer remainder of a division. 3% 3 would therefore be 0, 3% 2 would be 1, 5% 3 would be 2 (for details in http://en.wikipedia.org/wiki/Modulo_operation )  
+
:Подробнее можно прочитать здесь: [http://en.wikipedia.org/wiki/Modulo_operation Деление с остатком] )  
The query Time.tickCounter ()% 334 == 0 is always true if the value is divisible in tick counter () with no residue, and then issues a message (about every 10 sec.). The 2nd query then after about 15sec (Well just about  ) A message. Both equal to 0, by starting to appear as something no rest.
+
  
 +
Условие '''Time.tickCounter() % 334 == 0''' всегда выполняется когда значение'''tickCounter()''' равно 334 тика (то есть остаток  от деления 334%334 равен нулю и отправляет сообщения в чат через каждые 10 секунд.
  
[[Файл:Sfdp3-2.jpg|800px]]
+
Условие '''Time.tickCounter() % 509 == 0''' всегда выполняется когда значение'''tickCounter()''' равно 509 тиков (то есть остаток  от деления 509%509 равен нулю и отправляет сообщения в чат через каждые 15 секунд.  
  
  
If you want to no message (or other) at the beginning of the mission you are instead of zero to a value of not greater than or equal to the divisor is.  
+
[[Файл:Sfdp3-2.jpg|800px]]
Bad example if (Time.tickCounter ()% 334 == 334) would this query could never be TRUE. Nor as if (Time.tickCounter ()% 334 == 335). Since there can never be a residual value of these reaches.
+
Example should be like:
+
  
  
using System;
+
Если вы не хотите выводить сообщение в начале миссии, можете использовать задержку изменив ноль в предыдущем примере на значение '''меньше''', чем делитель. Вот примеры неправильных условий:
using maddox.game;
+
using maddox.game.world;
+
  
public class Mission : AMission
+
if (Time.tickCounter() % 334 == '''334''')
{
+
if (Time.tickCounter() % 334 == '''335''')
public override void OnTickGame()  
+
Эти условия никогда не будут выполнены и сообщение не появится на экране.
{
+
double WievielSekunden = 0;
+
Зададим конкретное время вывода сообщения:
  
if (Time.tickCounter() % 334 == 333)  
+
using System;
{  
+
using maddox.game;
WievielSekunden = Time.TicksToSecs(Time.tickCounter());  
+
using maddox.game.world;
GamePlay.gpLogServer (null, "Erste Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});  
+
public class Mission : AMission
}  
+
{
if (Time.tickCounter() % 509 == 508)  
+
    public override void OnTickGame()
{  
+
    {
WievielSekunden = Time.TicksToSecs(Time.tickCounter());  
+
        double WievielSekunden = 0;
GamePlay.gpLogServer (null, "Zweite Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});  
+
       
}  
+
        if (Time.tickCounter() % 334 == 333)  
}  
+
        {
}  
+
            WievielSekunden = Time.TicksToSecs(Time.tickCounter());
 +
            GamePlay.gpLogServer (null, "Erste Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
 +
        }
 +
        if (Time.tickCounter() % 509 == 508)  
 +
        {
 +
            WievielSekunden = Time.TicksToSecs(Time.tickCounter());
 +
            GamePlay.gpLogServer (null, "Zweite Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
 +
        }
 +
    }
 +
}  
  
Result:  
+
Результат:  
  
 
[[Файл:Sfdp3-3.jpg|800px]]
 
[[Файл:Sfdp3-3.jpg|800px]]
  
  
Like today now do anything to Broken? - But
+
В основную миссию два добавим камуфляжную палатку, она появится перед исчезновением и скроет технику.
The main mission of two, we modify by adding camouflage nets, they should appear only to disappear and hide the vehicles, but are not really necessary.  
+
 
  
 
[[Файл:Sfdp3-4.jpg|800px]]
 
[[Файл:Sfdp3-4.jpg|800px]]
  
  
This time we just need a SubMission, this involves a single vehicle that travels from one end of the runway to the other.  
+
Далее нам необходима подмиссия. В ней будет один автомобиль, который проедет от одного конца ВПП до другого.
 +
  
 
[[Файл:Sfdp3-5.jpg|800px]]
 
[[Файл:Sfdp3-5.jpg|800px]]
  
  
 +
Подмиссия будет загружаться скажем каждые 30 секунд, так мы создадим колонну грузовиков, которые будут курсировать по ВПП и станут для нас отличной мишенью. Транспортные средства будут двигаться на некотором расстоянии друг от друга. В нашей миссии автомобили буду исчезать через 75 секунд - это время, которое понадобится им, чтобы достичь конечной точки пути. Игрок должен уничтожить как можно больше автомобилей. Количество машин (точнее под-миссий) будет равно 10. В этот раз мы не будем использовать триггеры, зачет результатов атаки и сведения о поврежденной технике будут содержаться в коде скрипта.
  
The submission should be charged periodically, say every 30 sec. giving the impression that the vehicles some distance behind the other reverse actions. Especially with online servers occurs over time, but with the problem that inhabit the ever more vehicles and planes, the card, which can lead to performance problems or can result. In our mission, we therefore allow the vehicle to disappear after 75 seconds, which is the time that the vehicle needs to come from a camouflage tent to another. The player should try to destroy as many vehicles as possible. The number of vehicles (or better the number of submissions that should be loaded), we use time fixed at 10, we could also allow play to continue indefinitely. This time we will not use triggers, but our victory and the condition of the damaged vehicles are put in the code.
+
Код с тестовыми сообщениями:
  
The code with test messages is as follows:
 
  
 +
using System;
 +
using maddox.game;
 +
using maddox.game.world;
 +
using System.Collections.Generic;
 +
public class Mission : AMission
 +
{   
 +
    AiAircraft PlayerPlane;
 +
    const int MaxAnzahlWellen = 10;
 +
    int AnzahlWellen = 0;
 +
    int ZerstoerteZiele = 0;   
 +
    public override void OnBattleStarted()
 +
    {
 +
        base.OnBattleStarted();
 +
        MissionNumberListener = -1;
 +
        PlayerPlane = (AiAircraft)GamePlay.gpPlayer().Place();
 +
    }
 +
    private void serverMessage(string msg)
 +
    {
 +
        GamePlay.gpLogServer (null, msg, new object [] {msg});
 +
    }   
 +
    public override void OnTickGame()
 +
    {
 +
        if (Time.tickCounter() % 1000 == 0 && (AnzahlWellen < MaxAnzahlWellen))  //ca. alle 30sek die Karte laden
 +
        {
 +
            GamePlay.gpPostMissionLoad("missions\\Single\\Samples\\TestSubmissions\\MissionNachladen5Sub1.mis");
 +
            AnzahlWellen++;
 +
            GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
 +
        }       
 +
        if (Time.tickCounter() % 11500 == 0)   
 +
        {
 +
                    GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
 +
                    GamePlay.gpHUDLogCenter("Sie haben "+ ZerstoerteZiele.ToString() + " von " + AnzahlWellen.ToString() + " Fahrzeugen zerstört" );
 +
        }
 +
    }   
 +
    public override void OnActorCreated(int missionNumber, string shortName, AiActor actor)
 +
    {
 +
        base.OnActorCreated(missionNumber, shortName, actor);
 +
        if (actor is AiGroundActor)
 +
        {
 +
            Timeout(75, () => {
 +
                if (actor != null)
 +
                {
 +
                    (actor as AiGroundActor).Destroy();
 +
                }
 +
            });
 +
        }
 +
    }   
 +
    public override void OnActorDead(int missionNumber, string shortName, AiActor actor, System.Collections.Generic.List<DamagerScore> damages)
 +
    {
 +
    base.OnActorDead(missionNumber, shortName, actor, damages);       
 +
        string KilledName;   
 +
    KilledName = missionNumber.ToString()+ ":0_Chief";   
 +
    if ((damages.Count != 0) && (PlayerPlane.Name().Equals(damages[0].initiator.Actor.Name())) && KilledName.Equals(actor.Name()))
 +
    {
 +
        ZerstoerteZiele++;
 +
        serverMessage(ZerstoerteZiele.ToString());
 +
      }
 +
    }
 +
}
  
using System;
 
using maddox.game;
 
using maddox.game.world;
 
using System.Collections.Generic;
 
  
public class Mission : AMission
+
Разберем код нашего скрипта. Во-первых мы используем новый метод:
{
+
'''using System.Collections.Generic;'''
  
AiAircraft PlayerPlane;
+
В скрипте нам понадобятся следующие переменные:
const int MaxAnzahlWellen = 10;
+
int AnzahlWellen = 0;
+
int ZerstoerteZiele = 0;
+
  
public override void OnBattleStarted()
+
'''AiAircraft PlayerPlane;''' - '''AiAircraft''' класс, в котором содержатся все доступные самолеты в игре, все самолеты за пределами этой группы являются статическими.
{
+
base.OnBattleStarted();  
+
MissionNumberListener = -1;
+
PlayerPlane = (AiAircraft)GamePlay.gpPlayer().Place();
+
}
+
  
 +
'''const int MaxAnzahlWellen = 10;''' - количество запусков под-миссий, в нашем случае равно кол-ву грузовиков (целей)
 +
'''int AnzahlWellen = 0;''' - кол-во уже запущенных под-миссий
 +
'''int ZerstoerteZiele;''' - количество уничтоженных целей
  
private void serverMessage(string msg)  
+
public override void OnBattleStarted()
{  
+
{
GamePlay.gpLogServer (null, msg, new object [] {msg});  
+
    base.OnBattleStarted();
}  
+
    MissionNumberListener = -1;
 +
    PlayerPlane = (AiAircraft)GamePlay.gpPlayer().Place();
 +
}
  
 +
В методе '''OnBattleStarted ''' установим '''MissionNumberListener = -1''', чтобы иметь доступ к объектам под-миссий.
  
public override void OnTickGame()  
+
'''PlayerPlane = (AiAircraft)GamePlay.gpPlayer().Place();'''  - определяем самолет игрока.
{
+
Используем для этого класс IGamePlay, в котором есть метод '''gpPlayer()'''. Указываем нужный нам класс '''(AiAircraft)''' куда входит игрок (так же он присутствует в классах '''AiActor, AiCart, AiAircraft..)'''. '''Place()''' определяет текущее положение игрока. Метод '''gpPlayer''' используется только в одиночных миссиях, для мультиплеера нужно использовать метод '''gpRemotePlayers'''.
  
if (Time.tickCounter() % 1000 == 0 && (AnzahlWellen < MaxAnzahlWellen)) //ca. alle 30sek die Karte laden
 
{
 
GamePlay.gpPostMissionLoad("missions\\Single\\Samples\\TestSubmissions\\MissionNachladen5Sub1.mis");
 
AnzahlWellen++;
 
GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
 
}
 
  
if (Time.tickCounter() % 11500 == 0)  
+
public override void OnTickGame()
{  
+
{
GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung  
+
    if (Time.tickCounter() % 1000 == 0 && (AnzahlWellen < MaxAnzahlWellen))  //ca. alle 30sek die Karte laden
GamePlay.gpHUDLogCenter("Sie haben "+ ZerstoerteZiele.ToString() + " von " + AnzahlWellen.ToString() + " Fahrzeugen zerstört" );  
+
    {
}  
+
        GamePlay.gpPostMissionLoad("missions\\Single\\Samples\\TestSubmissions\\MissionNachladen5Sub1.mis");
}  
+
        AnzahlWellen++;
 +
        GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
 +
    }
 +
       
 +
    if (Time.tickCounter() == 11500)   
 +
    {
 +
        GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
 +
        GamePlay.gpHUDLogCenter("Sie haben "+ ZerstoerteZiele.ToString() + " von " + AnzahlWellen.ToString() + " Fahrzeugen zerstört" );
 +
        MissionAbgeschlossen = true;
 +
    }
 +
}
  
public override void OnActorCreated(int missionNumber, string shortName, AiActor actor)
 
{
 
base.OnActorCreated(missionNumber, shortName, actor);
 
  
if (actor is AiGroundActor)  
+
'''OnTickGame ()'''
{
+
Timeout(75, () => {
+
if (actor != null)
+
{
+
(actor as AiGroundActor).Destroy();
+
}
+
});
+
}
+
}
+
  
public override void OnActorDead(int missionNumber, string shortName, AiActor actor, System.Collections.Generic.List<DamagerScore> damages)  
+
'''if (Time.tickCounter() % 1000 == 0 && (AnzahlWellen < MaxAnzahlWellen))''' - условие, которое загружает под-миссию, если прошло 30 секунд ''И'' (оператор '''''&&''''') и номер текущей подмиссии меньше переменной ''const int MaxAnzahlWellen''.
{
+
base.OnActorDead(missionNumber, shortName, actor, damages);
+
  
string KilledName;
+
'''if (Time.tickCounter() == 11500)''' - если прошло 11500 тиков (то есть время, выделенное на выполнение задания - выводится итоговый результат по количеству уничтоженных целей)
+
KilledName = missionNumber.ToString()+ ":0_Chief";
+
+
if ((damages.Count != 0) && (PlayerPlane.Name().Equals(damages[0].initiator.Actor.Name())) && KilledName.Equals(actor.Name()))
+
{
+
ZerstoerteZiele++;
+
serverMessage(ZerstoerteZiele.ToString());
+
}
+
}
+
}
+
  
So we look at the code closer
+
[[Файл:Sfdp3-6.jpg|800px]]
The first thing to using the new statement:  
+
using System.Collections.Generic; this we need the code to access the class list of C # to get the List class provides methods to insert and read this (and much more) make a list of objects. To be given more time.  
+
  
This time we need the following variables have in the entire context of validity:
 
AiAircraft player plane; The game engine provides us with the class AiAircraft available, all aircraft in the game, except the members of this group are static, so to speak.
 
const int MaxAnzahlWellen = 10; This constant specifies the number of submissions runs.
 
int count = 0 waves, wave number is used as a counter in the course of the current number of runs saved.
 
int ZerstoerteZiele; This variable contains the number of targets already destroyed.
 
  
public override void OnBattleStarted ()  
+
public override void OnActorCreated(int missionNumber, string shortName, AiActor actor)
{  
+
{
base.OnBattleStarted ();
+
    base.OnActorCreated(missionNumber, shortName, actor);
sion number listener = -1;
+
player plane = (AiAircraft) GamePlay.gpPlayer () Place ().;
+
}
+
In OnBattleStarted we establish with mission number listener = -1, that all events are taken from the missions. And with the help of:
+
player plane = (AiAircraft) GamePlay.gpPlayer () Place ();. we have to plan the player of the variables from the player occupied aircraft. As happens in this mission, only one aircraft by sitting the player can an assignment is sufficient to start the mission, several aircraft to be in the game, it makes sense to check before important the players relevant events whether it is sitting still in the early airplane and if not reassign . But certainly something we need in a later lesson. For us, the assignment IGamePlay class method gpPlayer () to disposition by the (AiAircraft) we share the game with what we mean in this case, the class AiAircraft, there are several derived classes it is necessary to inform the system that we because my (eg, the player is a member of the class AiActor, AiCart, AiAircraft ..). Place () belongs to the class and tells players where the player is currently located. Note also that you can only use gpPlayer in single missions, multiplayer games coming gpRemotePlayers used.
+
Important is currently the only one with the help of (AiAircraft) GamePlay.gpPlayer (). Place () in a single player game the player gets the plane as a return.
+
  
public override void OnTickGame ()  
+
    if (actor is AiGroundActor)
{  
+
    {
if (Time.tickCounter ()% 1000 == 0 & & (number of waves <MaxAnzahlWellen)) / / about 30sec load all the map
+
        Timeout(75, () => {
{  
+
            if (actor != null)
GamePlay.gpPostMissionLoad ("missions \ \ music \ \ samples \ \ test submissions \ \ MissionNachladen5Sub1.mis");  
+
            {  
wave number + +;
+
                (actor as AiGroundActor).Destroy();  
GamePlay.gpLogServer (null, "{0} to {1} sec. \ N", new object [] {count waves, Time.TicksToSecs (Time.tickCounter ())});// test message
+
            }
}  
+
        });
 +
    }
 +
}
  
if (Time.tickCounter () == 11500)
 
{
 
GamePlay.gpLogServer (null, "{0} to {1} sec. \ N", new object [] {count waves, Time.TicksToSecs (Time.tickCounter ())});// test message
 
GamePlay.gpHUDLogCenter ("You" + "destroyed vehicles" ZerstoerteZiele.ToString () + "from" + AnzahlWellen.ToString () +);
 
Mission completed = true;
 
}
 
}
 
  
OnTickGame ()  
+
'''OnActorCreated(int missionNumber, string shortName, AiActor actor)''' - метод вызывается каждый раз, когда создается новый '''Actor'''.
if (Time.tickCounter ()% 1000 == 0 & & (number of waves <MaxAnzahlWellen)) The query load the map every 30 seconds. and that is the number of waves <MaxAnzahlWellen long as, in our case just 10th without this "& & (count waves <MaxAnzahlWellen)" The submission would be charged indefinitely, or just until the player has no more desire. Where, if I'm considering the name and number of waves in a vehicle MaxAnzahlWellen were somewhat exaggerated  . If the condition is true our SubMission charged and thereby caused the vehicle on the road. Then increases the number of waves around an issue and issued a check in the chat bar so you can adjust the times may still, as I said the number of ticks per second can vary depending on usage. Should of course be removed from the final version.  
+
 
if (Time.tickCounter () == 11500) is our Abruchbedingung the 11 500 ticks should of course be chosen so that the last vehicle can also complete the route. This condition makes the use of a time trigger unnecessary. So the time is expired, a control message is output which outputs the number of waves and the time required to remove this message also in the final.  
+
'''if (actor is AiGroundActor)''' -  проверяем, принадлежит ли '''Actor''' к классу '''AiGroundActor'''
There is still a message on the screen:
+
в котором содержатся все "живые" наземные объекты: автомобили, танки, корабли, артиллерия. Если условие равно TRUE выполняется соответствующий код.
 +
 
 +
 
 +
'''Timeout(75, () => { Anweisungen });''' - "отсчитываем" 75 секунд после создания '''Actor''', далее проверяем существует ли он еще - '''if (actor != null)'''. Если условия выполняются - указываем, что объект должен рассматриваться как '''AiGroundActor''' и уничтожаем объект командой '''.Destroy();'''.
 +
 
 +
 
 +
'''.Destroy()''' - удаляет объект из мисии (с карты он исчезает).
 +
 
 +
public override void OnActorDead(int missionNumber, string shortName, AiActor actor, System.Collections.Generic.List<DamagerScore> damages)
 +
{
 +
    base.OnActorDead(missionNumber, shortName, actor, damages);       
 +
    string KilledName;         
 +
    KilledName = missionNumber.ToString()+ ":0_Chief";         
 +
    if ((damages.Count != 0) && (PlayerPlane.Name().Equals(damages[0].initiator.Actor.Name())) && KilledName.Equals(actor.Name()))
 +
    {
 +
        ZerstoerteZiele++;
 +
        serverMessage(ZerstoerteZiele.ToString()); //Testmeldung
 +
    }
 +
}
  
[[Файл:Sfdp3-6.jpg|800px]]
 
  
 +
Чтобы зарегистрировать уничтоженный объект в статистике переопределим метод '''OnActorDead(..)'''.
  
public override void OnActorCreated (int mission number, string ShortName, AiActor actor)
+
'''public override void OnActorDead(int missionNumber, string shortName, AiActor actor, System.Collections.Generic.List<DamagerScore> damages)'''
{
+
base.OnActorCreated (mission number, short name, actor);
+
  
if (actor is AiGroundActor)
+
Этот метод вызывается при нанесении объекту повреждений, причем два раза - первый раз для техники, второй - для экипажа. Так как мы будем считать только уничтоженные грузовики введем переменную '''KilledName'''. Переменная будет состоять из номера миссии и имени уничтоженного объекта. Имя объекта можно найти в файле мисиии '''.mis''':
{
+
Timeout (75, () => {
+
if (actor! = null)
+
{
+
(As actor AiGroundActor) Destroy ().;
+
}
+
});
+
}
+
}
+
The method OnActorCreated (int mission number, string ShortName, AiActor actor) is called each time a new Actor is created, an Actor in Cliffs of Dover is anything to either move or function (eg artillery), has something that only stupid in the area is not part of it (ie buildings, trucks static, static aircraft, etc.).
+
Also here is the first base call to make sure everything is initialized properly.
+
if (actor is AiGroundActor) here is checked whether the current actor is among the AiGroundActor class in this class are all items that are traveling on the ground, thus moving trucks, artillery, ships, etc. If the query is TRUE is next
+
(75, () => {statements}) timeout; executed. When the timeout is specified first time, after something is to be executed, in this case 75 seconds. in the curly brackets are the instructions to be executed then why is the spelling with () => a little strange, beyond the scope of this lesson, we say stop is just so  . The timeout after 75 seconds then executes instructions If the query in this query is whether the actor is also currently available (it may already have been removed elsewhere), if nothing happens, but if (as actor AiGroundActor) we put hereby determine that the actor should be treated as AiGroundActor (if we do not do this, we get an error message because the system does not know what we mean Actor) with. Destroy () then this is completely removed from the mission, then simply disappears. This makes you an "overpopulation" of the card to avoid running so essentially multiplayer maps (as is the despawn time course much larger) the number of times. But here it fits quite well purely  .  
+
  
public override void OnActorDead (int mission number, string ShortName, AiActor actor, System.Collections.Generic.List <DamagerScore> damages)
+
[Chiefs]
{
+
0_Chief Vehicle.Austin_10_Tilly gb
base.OnActorDead (mission number, short name, actor, damages);
+
  
Killed string name;
 
  
Killed missionNumber.ToString name = () + ": 0_Chief";
+
'''if ((damages.Count != 0) && (PlayerPlane.Name().Equals(damages[0].initiator.Actor.Name())) && KilledName.Equals(actor.Name()))'''
  
if ((damages.Count! = 0) & & (PlayerPlane.Name (). Equals (damages [0]. initiator.Actor.Name ())) & & KilledName.Equals (actor.Name ()))
+
- условие в котором определяется, что игрок уничтожил объект. Если выполняется - прибавляем к счетчику уничтоженных грузовиков еще один и выводим сообщение сервреа об этом.
{
+
ZerstoerteZiele + +;
+
message server (ZerstoerteZiele.ToString ()); / / test message
+
}
+
}
+
To register if we have destroyed a target, we overloaded the method OnActorDead (..)
+
public override void OnActorDead (int mission number, string ShortName, AiActor actor, System.Collections.Generic.List <DamagerScore> damages)
+
This method is used by system called each time when an actor is destroyed (or better Incapacitate is) and that this happens twice, once for the vehicle and then for the imaginary occupant, here you realize it is in the game also provided the ground vehicles and by human players can be controlled. Since we do not want our "dead-counter" is incremented twice, we introduce the variable name of type String killed. Killed name then sets up the name to be observed, because here you have to decide actor.Name () (ie the name of the destroyed target) once the value of "1:0 _Chief" and the second call "1:0 _Chief0" (one that varies depending on mission number), according to the number after the occupants of the Chief is all spent. The base name can be found in the Mis-file in our example SubMission here:
+
[Chiefs]
+
0_Chief Vehicle.Austin_10_Tilly gb
+
One can of course use the Mis-file and edit there own.
+
I signed up for "Mission Number: 0_Chief" decided, that the vehicle itself
+
if ((damages.Count! = 0) & & (PlayerPlane.Name (). Equals (damages [0]. initiator.Actor.Name ())) & & KilledName.Equals (actor.Name ()))
+
Thus the explanation for the If statement is again somewhat larger
+
! damages.Count = 0 System.Collections.Generic.List <DamagerScore> damages of the type, well actually not a type but a list, this list is by us from C #; provided (using System.Collections.Generic). This list contains elements of type DamagerScore be on this list from the game all the actuators were placed in the destruction of this object (Actor) involved. The more players than can be had even AIs. With the help of. Count one can count the number of items in the list, this value is zero, no one was involved, the query is needed because the method. Destroy () method throws the OnActorDead because we were only on the Players interested destroyed vehicles, we ignore the system simply eliminated.
+
& & (PlayerPlane.Name (). Equals (damages [0]. Initiator.Actor.Name ())) this is to ensure that even the player is the one who destroyed the vehicle has PlayerPlane.Name () contains the name of the pilot ( has fired in my case, therefore, Kodiak) and damages [0]. initiator.Actor.Name () returns the name of the person as the last of the car (or plane). damages [0] indicates that the first item in the list of damages is meant, in our case it is sufficient because only one aircraft and a player were on the road. Both will be compared and if consensus is part of this perception. With & & KilledName.Equals (actor.Name () is then queried even if the target is consistent with our desired. Thus, if the entire if statement is true, the ZerstoerteZiele counter incremented by one and spent a brief test message on the screen.  
+
  
But do not worry in the next lessons will deepen the whole still.
 
As always, I am grateful for criticism and suggestions.
 
  
The attachment contains the current instance missions.  
+
: ''Для установки миссий-примеров скопируйте их в папку:
 +
..\Documents\1C SoftClub\il-2 sturmovik cliffs of dover\missions\Single
  
 +
----
 +
=== Загрузка подмиссий (автор FG28_Kodiak) ===
 +
Оригинальные темы см. на форуме [http://translate.google.com/translate?hl=en&sl=de&tl=en&u=http%3A%2F%2Fforum.sturmovik.de%2Findex.php%2Fboard%2C17.0.html sturmovik.de](перевод от Google):
  
 +
*[[Ил-2 Штурмовик: Битва за Британию. Скрипты. Скрипты для чайников. Часть 1 - Загрузка подмиссий|Часть 1 - Загрузка подмиссий]]
 +
*[[Ил-2 Штурмовик: Битва за Британию. Скрипты. Скрипты для чайников. Часть 2 - Загрузка подмиссий|Часть 2 - Загрузка подмиссий]]
 +
*[[Ил-2 Штурмовик: Битва за Британию. Скрипты. Скрипты для чайников. Часть 4 - Загрузка подмиссий|Часть 4 - Загрузка подмиссий]]
  
 +
[[Ил-2 Штурмовик: Битва за Британию. Скрипты|Вернуться на страницу "Скрипты"]]
  
 
[[Категория:Ил-2 Штурмовик: Битва за Британию. Скрипты]]
 
[[Категория:Ил-2 Штурмовик: Битва за Британию. Скрипты]]

Текущая версия на 18:30, 6 ноября 2011

Автор: FG28_Kodiak
Ссылка: Перейти (перевод Google)

Скачать оригиналы миссий:

Samples.zip

Скачать примеры с Sukhoi.ru:

Samples.zip


В предыдущих двух миссиях мы загружали под-миссии после начала миссии-хоста. В этой части мы научимся контролировать загрузку миссиий при помощи метода public virtual void OnTickGame ()

public override void OnTickGame()

Метод OnTickGame() направляет вызовы с частотой 34 тика в секунду, в зависимости от компьютера может быть больше или меньше времени.

Например:

using System;
using maddox.game;
using maddox.game.world;
public class Mission : AMission
{
   public override void OnTickGame()
   {
       double WievielSekunden = 0;
       
       if (Time.tickCounter() == 334) 
       {
           WievielSekunden = Time.TicksToSecs(Time.tickCounter());
           GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
       }
       if (Time.tickCounter() == 667) 
       {
           WievielSekunden = Time.TicksToSecs(Time.tickCounter());
           GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
       }
       if (Time.tickCounter() == 1000) 
       {
           WievielSekunden = Time.TicksToSecs(Time.tickCounter());
           GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
       }
  }
} 

Данный пример ничего не делает, кроме как отправляет сообщение в чат сервера каждые 10, 20, 30 секунд.

Time.tickCounter() - содержит количество тиков, прошедших с момента начала мисиии Time.TicksToSecs - переводит такты в секунды

Далее выводим сообщение на экран:

GamePlay.gpLogServer (null, "Meldung nach: {0} Ticks das sind {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});

В игре это будет выглядеть так:


Sfdp3-1.jpg


Также вы можете использовать OnTickGame() для задания циклов, зависящих от времени, например:


using System;
using maddox.game;
using maddox.game.world;
public class Mission : AMission
{
   public override void OnTickGame()
   {
       double WievielSekunden = 0;
       
       if (Time.tickCounter() % 334 == 0) 
       {
           WievielSekunden = Time.TicksToSecs(Time.tickCounter());
           GamePlay.gpLogServer (null, "Erste Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
       }
       if (Time.tickCounter() % 509 == 0) 
       {
           WievielSekunden = Time.TicksToSecs(Time.tickCounter());
           GamePlay.gpLogServer (null, "Zweite Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
       }
   }
}

Для вычисления остатка при целочисленном делении в языке С# используется оператор деления по модулю %. Он применяется таким же образом, что и оператор деления, но в результате вычисляется не частное, а остаток.

3%3   результат: 0
3%2   результат: 1
5%3   результат: 2
Подробнее можно прочитать здесь: Деление с остатком )

Условие Time.tickCounter() % 334 == 0 всегда выполняется когда значениеtickCounter() равно 334 тика (то есть остаток от деления 334%334 равен нулю и отправляет сообщения в чат через каждые 10 секунд.

Условие Time.tickCounter() % 509 == 0 всегда выполняется когда значениеtickCounter() равно 509 тиков (то есть остаток от деления 509%509 равен нулю и отправляет сообщения в чат через каждые 15 секунд.


Sfdp3-2.jpg


Если вы не хотите выводить сообщение в начале миссии, можете использовать задержку изменив ноль в предыдущем примере на значение меньше, чем делитель. Вот примеры неправильных условий:

if (Time.tickCounter() % 334 == 334)
if (Time.tickCounter() % 334 == 335)
Эти условия никогда не будут выполнены и сообщение не появится на экране.

Зададим конкретное время вывода сообщения:

using System;
using maddox.game;
using maddox.game.world;
public class Mission : AMission
{
   public override void OnTickGame()
   {
       double WievielSekunden = 0;
       
       if (Time.tickCounter() % 334 == 333) 
       {
           WievielSekunden = Time.TicksToSecs(Time.tickCounter());
           GamePlay.gpLogServer (null, "Erste Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
       }
       if (Time.tickCounter() % 509 == 508) 
       {
           WievielSekunden = Time.TicksToSecs(Time.tickCounter());
           GamePlay.gpLogServer (null, "Zweite Meldung: {0} Ticks = {1} sek.\n", new object [] {Time.tickCounter(), WievielSekunden});
       }
   }
} 

Результат:

Sfdp3-3.jpg


В основную миссию два добавим камуфляжную палатку, она появится перед исчезновением и скроет технику.


Sfdp3-4.jpg


Далее нам необходима подмиссия. В ней будет один автомобиль, который проедет от одного конца ВПП до другого.


Sfdp3-5.jpg


Подмиссия будет загружаться скажем каждые 30 секунд, так мы создадим колонну грузовиков, которые будут курсировать по ВПП и станут для нас отличной мишенью. Транспортные средства будут двигаться на некотором расстоянии друг от друга. В нашей миссии автомобили буду исчезать через 75 секунд - это время, которое понадобится им, чтобы достичь конечной точки пути. Игрок должен уничтожить как можно больше автомобилей. Количество машин (точнее под-миссий) будет равно 10. В этот раз мы не будем использовать триггеры, зачет результатов атаки и сведения о поврежденной технике будут содержаться в коде скрипта.

Код с тестовыми сообщениями:


using System;
using maddox.game;
using maddox.game.world;
using System.Collections.Generic;
public class Mission : AMission
{    
   AiAircraft PlayerPlane;
   const int MaxAnzahlWellen = 10;
   int AnzahlWellen = 0;
   int ZerstoerteZiele = 0;    
   public override void OnBattleStarted()
   {
       base.OnBattleStarted();
       MissionNumberListener = -1;
       PlayerPlane = (AiAircraft)GamePlay.gpPlayer().Place();
   }	
   private void serverMessage(string msg)
   {
       GamePlay.gpLogServer (null, msg, new object [] {msg});
   }    
   public override void OnTickGame()
   {
       if (Time.tickCounter() % 1000 == 0 && (AnzahlWellen < MaxAnzahlWellen))  //ca. alle 30sek die Karte laden
       {
           GamePlay.gpPostMissionLoad("missions\\Single\\Samples\\TestSubmissions\\MissionNachladen5Sub1.mis");
           AnzahlWellen++;
           GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
       }        
       if (Time.tickCounter() % 11500 == 0)     
       {
                   GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
                   GamePlay.gpHUDLogCenter("Sie haben "+ ZerstoerteZiele.ToString() + " von " + AnzahlWellen.ToString() + " Fahrzeugen zerstört" );
       }
   }    
   public override void OnActorCreated(int missionNumber, string shortName, AiActor actor)
   {
       base.OnActorCreated(missionNumber, shortName, actor);
       if (actor is AiGroundActor)
       {
           Timeout(75, () => {
               if (actor != null)
               { 
                   (actor as AiGroundActor).Destroy(); 
               }
           });
       }
   }    
   public override void OnActorDead(int missionNumber, string shortName, AiActor actor, System.Collections.Generic.List<DamagerScore> damages)
   {
   		base.OnActorDead(missionNumber, shortName, actor, damages);        
       string KilledName;     		
    		KilledName = missionNumber.ToString()+ ":0_Chief";     		
    		if ((damages.Count != 0) && (PlayerPlane.Name().Equals(damages[0].initiator.Actor.Name())) && KilledName.Equals(actor.Name()))
    		{
    		    ZerstoerteZiele++;
    		    serverMessage(ZerstoerteZiele.ToString());
    	  }
   }
}


Разберем код нашего скрипта. Во-первых мы используем новый метод:

using System.Collections.Generic;

В скрипте нам понадобятся следующие переменные:

AiAircraft PlayerPlane; - AiAircraft класс, в котором содержатся все доступные самолеты в игре, все самолеты за пределами этой группы являются статическими.

const int MaxAnzahlWellen = 10; - количество запусков под-миссий, в нашем случае равно кол-ву грузовиков (целей) int AnzahlWellen = 0; - кол-во уже запущенных под-миссий int ZerstoerteZiele; - количество уничтоженных целей

public override void OnBattleStarted()
{
   base.OnBattleStarted();
   MissionNumberListener = -1;
   PlayerPlane = (AiAircraft)GamePlay.gpPlayer().Place();
}

В методе OnBattleStarted установим MissionNumberListener = -1, чтобы иметь доступ к объектам под-миссий.

PlayerPlane = (AiAircraft)GamePlay.gpPlayer().Place(); - определяем самолет игрока. Используем для этого класс IGamePlay, в котором есть метод gpPlayer(). Указываем нужный нам класс (AiAircraft) куда входит игрок (так же он присутствует в классах AiActor, AiCart, AiAircraft..). Place() определяет текущее положение игрока. Метод gpPlayer используется только в одиночных миссиях, для мультиплеера нужно использовать метод gpRemotePlayers.


public override void OnTickGame()
{
   if (Time.tickCounter() % 1000 == 0 && (AnzahlWellen < MaxAnzahlWellen))  //ca. alle 30sek die Karte laden
   {
       GamePlay.gpPostMissionLoad("missions\\Single\\Samples\\TestSubmissions\\MissionNachladen5Sub1.mis");
       AnzahlWellen++;
       GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
   }
       
   if (Time.tickCounter() == 11500)     
   {
       GamePlay.gpLogServer (null, "{0} nach {1} sek.\n", new object [] {AnzahlWellen, Time.TicksToSecs(Time.tickCounter())});//Testmeldung
       GamePlay.gpHUDLogCenter("Sie haben "+ ZerstoerteZiele.ToString() + " von " + AnzahlWellen.ToString() + " Fahrzeugen zerstört" );
       MissionAbgeschlossen = true;
   }
}


OnTickGame ()

if (Time.tickCounter() % 1000 == 0 && (AnzahlWellen < MaxAnzahlWellen)) - условие, которое загружает под-миссию, если прошло 30 секунд И (оператор &&) и номер текущей подмиссии меньше переменной const int MaxAnzahlWellen.

if (Time.tickCounter() == 11500) - если прошло 11500 тиков (то есть время, выделенное на выполнение задания - выводится итоговый результат по количеству уничтоженных целей)

Sfdp3-6.jpg


public override void OnActorCreated(int missionNumber, string shortName, AiActor actor)
{
   base.OnActorCreated(missionNumber, shortName, actor);
   if (actor is AiGroundActor)
   {
       Timeout(75, () => {
           if (actor != null)
           { 
               (actor as AiGroundActor).Destroy(); 
           }
       });
   }
}


OnActorCreated(int missionNumber, string shortName, AiActor actor) - метод вызывается каждый раз, когда создается новый Actor.

if (actor is AiGroundActor) - проверяем, принадлежит ли Actor к классу AiGroundActor в котором содержатся все "живые" наземные объекты: автомобили, танки, корабли, артиллерия. Если условие равно TRUE выполняется соответствующий код.


Timeout(75, () => { Anweisungen }); - "отсчитываем" 75 секунд после создания Actor, далее проверяем существует ли он еще - if (actor != null). Если условия выполняются - указываем, что объект должен рассматриваться как AiGroundActor и уничтожаем объект командой .Destroy();.


.Destroy() - удаляет объект из мисии (с карты он исчезает).

public override void OnActorDead(int missionNumber, string shortName, AiActor actor, System.Collections.Generic.List<DamagerScore> damages)
{
   base.OnActorDead(missionNumber, shortName, actor, damages);        
   string KilledName;           
   KilledName = missionNumber.ToString()+ ":0_Chief";           
   if ((damages.Count != 0) && (PlayerPlane.Name().Equals(damages[0].initiator.Actor.Name())) && KilledName.Equals(actor.Name()))
   {
       ZerstoerteZiele++;
       serverMessage(ZerstoerteZiele.ToString()); //Testmeldung
   }
}


Чтобы зарегистрировать уничтоженный объект в статистике переопределим метод OnActorDead(..).

public override void OnActorDead(int missionNumber, string shortName, AiActor actor, System.Collections.Generic.List<DamagerScore> damages)

Этот метод вызывается при нанесении объекту повреждений, причем два раза - первый раз для техники, второй - для экипажа. Так как мы будем считать только уничтоженные грузовики введем переменную KilledName. Переменная будет состоять из номера миссии и имени уничтоженного объекта. Имя объекта можно найти в файле мисиии .mis:

[Chiefs]
0_Chief Vehicle.Austin_10_Tilly gb


if ((damages.Count != 0) && (PlayerPlane.Name().Equals(damages[0].initiator.Actor.Name())) && KilledName.Equals(actor.Name())) 

- условие в котором определяется, что игрок уничтожил объект. Если выполняется - прибавляем к счетчику уничтоженных грузовиков еще один и выводим сообщение сервреа об этом.


Для установки миссий-примеров скопируйте их в папку:
..\Documents\1C SoftClub\il-2 sturmovik cliffs of dover\missions\Single

Загрузка подмиссий (автор FG28_Kodiak)

Оригинальные темы см. на форуме sturmovik.de(перевод от Google):

Вернуться на страницу "Скрипты"