{$CLEO .cs} const // объявим константы, чтобы не потеряться в цифрах CAR_SANCHEZ = 1 // запись означает, что в дальнейшем вместо цифры 1 мы будем писать CAR_SANCHEZ там, где нам захочется ACTOR_FAT_COOK = 2 MARKER_FAT_COOK = 3 ACTOR_FRENCH_KILLA = 4 end //-------------MAIN--------------- thread 'SIMPMIS' 0AC8: 0@ = allocate_memory_size 4000 // выделение памяти для 1000 переменных. Это наша матерь-основа миссии. Тут будет храниться всё. :SimpleMis_Initial wait 0 if and Player.Defined($PLAYER_CHAR) // поскольку в дальнейшем коде мы работаем с игроком тоже, нужно проверить, существует ли этот игрок 8112: NOT wasted_or_busted // также проверим, не мертв и не арестован ли игрок в данный момент 0ADC: test_cheat "SMISSION" // маркеры использовать не комильфо(всё равно куча ограничений, лишь бы только не запороть сохранения)...поэтому лучше заюзать чит-код, при вводе которого наша миссия стартанет. jf @SimpleMis_Initial fade 0 0 // затеняем экран за 0 ms(моментально) 0247: request_model #SANCHEZ // загружаем все необходимые модели. Названия берутся из ide-файлов(в данном случае - default.ide). Вместо них можно сразу писать IDE номер, т.е. вместо "#SANCHEZ" можно написать цифру "198" 0247: request_model #CLEAVER 0247: request_model #GRENADE 0247: request_model #M4 load_special_actor 1 'chef' // прелесть специальных актеров в том, что мы можем подгрузить абсолютно любую модель, в т.ч. - свою новую, например load_special_actor 2 'courier' // но в примере заюзаны стандартные модели. Всего можно заюзать 21 спец. актера. load_requested_models // на самом деле, предыдущие опкоды не загружают модели, а лишь ставят их в очередь на загрузку, поэтому этим опкодом мы принуждаем игру загрузить все модели в очереди моментально Player.CanMove($PLAYER_CHAR) = False // блокируем управление игроком прямо сейчас, перед его телепортами и разворотами, чтобы они не "испортились", если игрок куда-то бежал 012A: put_player $PLAYER_CHAR at -691.4 1200.7 24.0 and_remove_from_car // телепорт игрока и на всякий случай выкинем его их тачки, если он в ней сидит 0171: set_player $PLAYER_CHAR z_angle_to 185.0 // установим угол поворота игрока относительно севера 03CB: load_scene -691.4 1200.7 24.1 // быстрая прогрузка местности в нужных координатах(чтобы не видеть мерзкие LODы) wait 0 // необязательная, но желательная задержка, если мы хотим телепортировать игрока точно 0055: put_player $PLAYER_CHAR at -691.4 1200.7 24.0 // т.к. при дальних телепортах игрок телепортируется неточно - спустя 0 ms повторяем телепорт в те же координаты, чтобы игрок встал ровно там, где нам нужно. Это делать необязательно, просто мне так нравится. 01B1: give_player $PLAYER_CHAR weapon 12 ammo 99 // даем игроку 99 гранат 00A5: 1@ = create_car #SANCHEZ at -674.0 1127.0 11.0 // создание транспортного средства 0175: set_car 1@ z_angle_to 0.0 // мотоцикл развернут строго на север(и в дальнейшем коде это будет повторяться) 02AC: set_car 1@ immunities BP 1 FP 1 EP 1 CP 1 MP 1 // ставим мотоциклу все иммунитеты 0129: 2@ = create_actor 4 #SPECIAL01 in_car 1@ driverseat // Создание актера сразу за рулем автомобиля. Касаемо модели: у специальных актеров модели(#SPECIAL01 и подобные, берутся опять-таки из default.ide) называются исходя из их номера. Здесь загружается специальный актер под номером 1(повар chef), соответственно. У 21 модель будет #SPECIAL21 01B2: give_actor 2@ weapon 8 ammo 1 // даем повару мясницкий топор 02AB: set_actor 2@ immunities BP 1 FP 1 EP 0 CP 1 MP 1 // даем повару иммунитеты ко всему кроме взрывов 054A: set_actor 2@ can_be_shot_in_a_car 0 // устанавливаем, чтобы в повара нельзя было попасть, пока он на мотоцикле 0187: 3@ = create_marker_above_actor 2@ // создаем маркер(3@) над поваром(2@) 0159: camera_on_ped 2@ mode 4 switchstyle 2 // ставим камеру следить за актером(режим 4. все режимы есть тут: https://gtagmodding.com/sanandreas/camera-modes/). А 2 означает, что камера "телепортируется" резко, без плавных переходов. 00AD: set_car 1@ max_speed_to 35.0 // предельная скорость, которую может развить мотоцикл(необязательно). Не в км/ч! Согласно моим замерам, нужно умножить число в этом(и других подобных опкодах) на 3.615, чтобы получить км/ч. Но некоторые говорят, что множителем является 4 ровно. Хз в общем, я доверяю своим наблюдениям... 02C2: car 1@ drive_to_point -674.0 1205.0 25.0 // устанавливаем, чтобы мотоцикл ехал в определенную точку 0AB1: call_scm_func @StoreToAlMem 3 0@ 1@ CAR_SANCHEZ // отсылаем в нашу функцию адрес выделенной памяти(0@), хэндл и номер нужной переменной. Т.е. мы записали в первую(константа CAR_SANCHEZ = 1) переменную нашей выделенной памяти(0@) хэндл мотоцикла 1@ 0AB1: call_scm_func @StoreToAlMem 3 0@ 2@ ACTOR_FAT_COOK // записываем хэндл актера 2@. 0AB1: call_scm_func @StoreToAlMem 3 0@ 3@ MARKER_FAT_COOK // записываем маркер. Теперь переменные 1@, 2@ и 3@ освободились, и мы можем использовать их для другого. 009A: 1@ = create_actor_pedtype 4 model #SPECIAL02 at -663.3 1206.5 24.0 // создание нового актера(француз-убийца), используя освободившуюся переменную 1@ 0173: set_actor 1@ z_angle_to 160.0 // устанавливаем угол поворота актера 01B2: give_actor 1@ weapon 26 ammo 9999 // даем французу m4 с 9999 патронов 01ED: clear_actor 1@ threat_search // актер ничего не боится 0223: set_actor 1@ health_to 20 // ставим актеру мало здоровья, чтобы быстрее помер 0AB1: call_scm_func @StoreToAlMem 3 0@ 1@ ACTOR_FRENCH_KILLA 0249: release_model #SANCHEZ // мы создали всё, что нам нужно, поэтому самое время выгрузить модели из памяти игры 0249: release_model #CLEAVER 0249: release_model #GRENADE 0249: release_model #M4 0296: unload_special_actor 1 // ...И не забываем выгрузить и спец. актеров тоже, ведь они уже созданы. 0296: unload_special_actor 2 02A3: enable_widescreen 1 // включаем режим "широкого экрана"(две черные полосы сверху и снизу). Необязательно, просто добавил, чтобы было:) fade 1 1500 // убираем затенение экрана за 1500 ms(1.5 секунды - оптимально, как мне кажется, но тут дело вкуса) 0006: 16@ = 0 // в игре два таймера: 16@ и 17@. Пользоваться ими легко - просто сбрасываешь на ноль и проверяешь потом их значение(они сами работают) 0006: 15@ = 1 // в переменной 15@ будет наш "счетчик событий". Это упростит код ниже. Пока записываем 1, т.к. произошло событие 0(начало миссии) :MissionMain_Cycle wait 0 // обязательная задержка в цикле if and Player.Defined($PLAYER_CHAR) 8112: NOT wasted_or_busted jf @MissionFailed // если игрока не существует или он умер/арестован - миссия провалена. // если условие одно - "if" можно не ставить. Это сэкономит кучу строк кода. 0019: 15@ > 1 // проверяем, произошло ли событие 1(в этом случае счетчик должен быть больше 1) jf @MissionEvent_01 // если нет - "прыгаем" на событие 1 0019: 15@ > 2 // проверяем, произошло ли событие 2(в этом случае счетчик должен быть больше 2) jf @MissionEvent_02 // если нет - "прыгаем" на событие 2. И так далее 0019: 15@ > 3 jf @MissionEvent_03 0019: 15@ > 4 jf @MissionEvent_04 jump @MissionEvent_05 // если больше 4, т.е. 5 - это последнее событие, поэтому без проверок "прыгаем" на него :MissionEvent_01 // начало события 1 0AB1: call_scm_func @ReadFromAlMem 2 0@ CAR_SANCHEZ 1@ // читаем из нашей выделенной памяти 0@ переменную в ячейке 1(константа CAR_SANCHEZ = 1) и записываем её в 1@ 04BA: set_car 1@ speed_instantly 30.0 // пока не прошло 2.7 секунды - пускай мотоцикл постоянно держит скорость 30... 0175: set_car 1@ z_angle_to 0.0 // и будет повернут на север(чтобы ничто не сбивало с курса) 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FAT_COOK 1@ // читаем из нашей выделенной памяти 0@ переменную в ячейке 2(константа ACTOR_FAT_COOK = 2) и записываем её в 1@ 0210: player $PLAYER_CHAR look_at_actor 1@ // разворачиваем игрока к актеру. Т.к. это находится в цикле - игрок будет всегда повернут к актеру(где бы тот не находился), пока цикл не пройдет 0019: 16@ > 2700 // если таймер превысил 2700 ms(прошло больше 2.7 секунд) jf @MissionMain_Cycle // а если нет - возвращаемя к основному циклу. Т.е. код выше постоянно повторяется, пока не пройдет 2.7 секунд, от чего запустится дальнейший год 0460: set_camera_pointing_time 0.0 1000 // ставим длительность плавного перехода в 1000 ms(1 секунду) 04C4: create_coordinate 1@ 2@ 3@ from_actor $PLAYER_ACTOR offset 4.0 -3.0 1.0 // получаем координаты актера(игрока в данном случае) со смещением на 4 метра вправо, 3 метра назад и 1 метр вверх относительно него 015F: set_camera_position 1@ 2@ 3@ rotation 0.0 0.0 0.0 // ставим камеру в наши координаты 0159: camera_on_ped $PLAYER_ACTOR mode 15 switchstyle 1 // камера указывает на игрока(15 - стандартный режим, если мы задаем координаты камеры ранее в опкоде 015F). 1 означает, что камера "телепортируется" "плавно". Т.к. мы задали плавность перехода - она переместится "плавно" в течении 1 секунды 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FAT_COOK 1@ 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FRENCH_KILLA 2@ 01CB: actor 2@ kill_actor 1@ // задаем, чтобы актер 2@(француз) атаковал актера 1@(повара) 0006: 16@ = 0 // обнуляем таймер! 0006: 15@ = 2 // событие 1 прошло, поэтому ставим 2, чтобы в дальнейшем главный цикл перешел к событию 2. jump @MissionMain_Cycle // возвращаемя к основному циклу :MissionEvent_02 // начало события 1 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FAT_COOK 1@ // снова читаем из нашей выделенной памяти 0@ переменную в ячейке 2(константа ACTOR_FAT_COOK = 2) и записываем её в 1@ 0210: player $PLAYER_CHAR look_at_actor 1@ // снова циклично разворачиваем игрока к актеру(типа, игрок активно следит за передвижением повара) 0019: 16@ > 1000 // если таймер превысил 1000 ms(прошло больше 1 секунды) jf @MissionMain_Cycle // а если нет - снова возвращаемя к основному циклу 0373: set_camera_directly_behind_player // ставим камеру позади игрока. Значение плавности пока действует, т.к. мы его еще не "сбили". Т.е. плавность по-прежнему равна 1000 мс, т.е. 1 секунде 015A: restore_camera // этот опкод используется только для камеры игрока после опкода 0373 или 03C8 02A3: enable_widescreen 0 // отключаем "широкий экран"(черные полоски) 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FRENCH_KILLA 2@ // в переменную 1@ выше мы записали повара, а в переменную 2@ запишем нашего француза 03E2: actor 1@ exit_car // заставляем его выйти из мотоцикла 01CB: actor 1@ kill_actor 2@ // заставляем его атаковать француза 0006: 16@ = 0 // обнуляем таймер. 0006: 15@ = 3 // событие 2 прошло, поэтому ставим 3, чтобы в дальнейшем главный цикл перешел к событию 3. И так далее jump @MissionMain_Cycle // опять возвращаемя к основному циклу. И так далее... :MissionEvent_03 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FAT_COOK 1@ 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FRENCH_KILLA 2@ 01CB: actor 1@ kill_actor 2@ // циклично повторяем команду на случай, если повар из-за рандома сам выпадет из мотоцикла, чтобы он в любом случае сразу бежал атаковать другого актера 0019: 16@ > 1000 jf @MissionMain_Cycle Player.CanMove($PLAYER_CHAR) = True // НАКОНЕЦ! возвращаем управление игроку 0006: 16@ = 0 0006: 15@ = 4 jump @MissionMain_Cycle :MissionEvent_04 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FAT_COOK 1@ 8118: NOT actor 1@ dead // если повар еще жив jf @MissionPassed // а если повар сдох - миссия пройдена 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FRENCH_KILLA 2@ 0118: actor 2@ dead // НО если повар жив, а француз - мертв... jf @MissionEvent_04a // пока француз не умер - нужно проверить, всё ли идет "по плану"(не "тупит" ли ИИ?) 01CA: actor 1@ kill_player $PLAYER_CHAR // ...то повар теперь бежит убивать игрока 0006: 16@ = 0 0006: 15@ = 5 jump @MissionMain_Cycle :MissionEvent_04a // из-за сбоя в работе ИИ, когда ИНОГДА актер выпадает из машины из-за рандома физики(именно из-за этого я решил сделать CarRec), необходимо циклично проверять, что происходит с поваром 00F2: actor 1@ near_actor 2@ radius 5.0 5.0 sphere 0 // проверяем, находится ли повар рядом с французом в радиусе 5 игровых юнитов(метров) jf @MissionMain_Cycle_04b // если он не рядом - переходим к другим проверкам 01CB: actor 1@ kill_actor 2@ // если повар рядом с французом - то пусть начинает его атаковать jump @MissionMain_Cycle :MissionMain_Cycle_04b 00DF: actor 1@ driving // проверим, а покинул ли повар мотоцикл вообще? jf @MissionMain_Cycle_04c // если он не в мотоцикле - перейдем к другому блоку 03E2: actor 1@ exit_car // НО если он почему-то до сих пор в мотоцикле - снова заставляем его выйти из мотоцикла... 0239: actor 1@ run_to -663.3 1206.5 // ...и бежать к точке, где находится француз jump @MissionMain_Cycle :MissionMain_Cycle_04c 0239: actor 1@ run_to -663.3 1206.5 // если он уже покинул транспорт - пусть просто бежит к точке, где находится француз jump @MissionMain_Cycle :MissionEvent_05 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FAT_COOK 1@ 0118: actor 1@ dead // в пятом событии мы просто проверяем, когда же наш повар наконец сдохнет... jf @MissionMain_Cycle // ...и возвращаемся в основной цикл, пока это не произойдет :MissionPassed // Код в случае пройденной миссии 01E3: text_1number_styled 'M_PASS' number 1000 time 5000 style 1 // MISSION PASSED! $~1~ Player.Money($PLAYER_CHAR) += 1000 // с этими двуми опкодами, думаю - всё понятно:) jump @DeleteAllStuff :MissionFailed // Код в случае проваленной миссии 00BA: text_styled 'M_FAIL' 5000 ms 1 // MISSION FAILED! :DeleteAllStuff 0AB1: call_scm_func @ReadFromAlMem 2 0@ MARKER_FAT_COOK 1@ // читаем из нашей выделенной памяти 0@ переменную в ячейке 3(константа MARKER_FAT_COOK = 3) и записываем её в 1@ 0164: disable_marker 1@ // удаляем маркер 0AB1: call_scm_func @ReadFromAlMem 2 0@ CAR_SANCHEZ 1@ 01C3: remove_references_to_car 1@ // тут уже сам выбирай - удалять объекты моментально, или "попросить" игру удалить их самой, когда объекты будут вне поля зрения. Я выбираю второе. 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FAT_COOK 1@ 034F: destroy_actor_with_fade 1@ // повара удалим путем "плавного растворения в воздухе", т.к. по-идее он должен умереть от взрыва, и так будет лучше выглядеть, на мой взгляд. 0AB1: call_scm_func @ReadFromAlMem 2 0@ ACTOR_FRENCH_KILLA 1@ 01C2: remove_references_to_actor 1@ // а в случае с французским убийцей - "попросим" игру, чтобы она сама его удалила, когда тот будет вне зоны видимости jump @SimpleMis_Initial :StoreToAlMem // В SCM-функции 3 отсылаемых значения записались в переменные 0@, 1@ и 2@ соответственно 0012: 2@ *= 4 // умножаем номер(какой бы он ни был) переменной на 4 байта, получаем offset(смещение) памяти, по которому будет располагаться эта переменная 005A: 2@ += 0@ // прибавляем сам адрес выделенной памяти 0A8C: write_memory 2@ size 4 value 1@ virtual_protect 1 // записываем хэндл 1@ в нашу выделенную память 0AB2: ret 0 :ReadFromAlMem // В SCM-функции 2 отсылаемых значения записались в переменные 0@ и 1@ соответственно 0012: 1@ *= 4 // умножаем номер(какой бы он ни был) переменной на 4 байта, получаем offset(смещение) памяти, по которому будет располагаться эта переменная 005A: 1@ += 0@ // прибавляем сам адрес выделенной памяти 0A8D: 2@ = read_memory 1@ size 4 virtual_protect 1 // читаем значение переменной в сохраненной ячейке выделенной памяти 0AB2: ret 1 2@ // отсылаем одну(1) переменную(2@) обратно