Перейти к содержанию

Программируем червяков

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

Работа интерпретатора в червяках

Напомним, интерпретатор - это программа, которая способна выполнять определённые программы, которые она понимает. Он даёт нам некоторые возможности, но и требует от нас определённого порядка работы.

В червяков встроен интерпретатор Lua, но так как его первоначальной целью была возможность управлять игрой из скрипта, то соответсвенно наш интерпретатор даёт некоторые только базовые возможности, которые используются в игре.

Рассмотрим, в чём же проявляются особенности интерпретатора lua в worms.

Функции интерпретатора

Интерпретатор в червях предоставляет несколько функций. Рассмотрим их.

SetData(container_name, value)

Позволяет изменять содержимое xml (или xom в Worms 3D) контейнера с помощью скрипта. Чтобы понять смысл этого, откройте любой xml файл из папки W4/data/tweak - там вы увидите данные в xml-разметке, которая очень похожа на html. В этих файлах хранится игровая информация: все параметры червя, настройки оружия и так далее. К тому же там есть контейнеры, которые на первый взгляд не имеют смысла, к примеру CommenteryPanel.Comment. Вот он (часть содержимого файла Local.xml):

<XStringResourceDetails id='CommentaryPanel.Comment'>
  <Value></Value>
  <Name>CommentaryPanel.Comment</Name>
  <Flags>96</Flags>
</XStringResourceDetails>

Странно, зачем в настройках хранить текст на панели комментариев, тем более что он постоянно меняется, а к тому же поле Value пусто? Оказывается xml файлы предназначены не только для хранения данных, но и для определения "общих рабочих ячеек" для совместной работы скрипта и хоста. Внутренние функции червяков работают с различными данными, находящимися в xml, а скрипт может их менять с помощью SetData. Обычно в червяках для совершения какого-либо действия надо сначала установить все необходимые контейнеры, а затем уже совершить действие. То есть SetData сама по себе ничего не делает. Не ждите мгновенного проявления результата после вызова SetData. Обратите внимание, SetData не изменяет xml файл на диске. Эта функция работает только с версией файла в памяти! Пример использования SetData:

SetData("CommentaryPanel.Comment", "Hello!")

Для вывода этого текста к тому же надо добавить строчку

SendMessage("CommentaryPanel.ScriptText")

SendMessage(message)

Данная функция "просит" червей выполнить какое-либо действие. К примеру можно вызвать так:

SendMessage("CommentaryPanel.ScriptText")

Это заставит червей посмотреть, что в данный момент записано в контейнере CommentaryPanel.Comment и вывести его содержимое или содержимое контейнера с таким именем на экран в панель комментариев. Поэтому обычно для вывода текста сначала необходимо в какой-либо текстовый контейнер записать сам комментарий, затем в контейнер CommentaryPanel.Comment записать имя 1-го контейнера, а затем послать сообщение CommentaryPanel.ScriptText

SetData("Text.TestComment", Text)
SetData("CommentaryPanel.Comment", "Text.TestComment")
SendMessage("CommentaryPanel.ScriptText")

SendMessage после вызова может изменять значения различных контейнеров. Это свойство используется функцией GetData. Приведём список всех известных сообщений (все они найдены в стандартных скриптах).

Для Worms 4:

AI.ExecuteActions
AI.PerformDefaultAITurn
Camera.ShakeStart
Comment.SuddenDeath
Commentary.Clear
Commentary.EnableDefault
Commentary.NoDefault
CommentaryPanel.ScriptText
CommentaryPanel.TimedText
DoubleDamage.Activated
EFMV.End
EFMV.Play
Earthquake.End
Explosion.Construct
Game.PlaceObjects
GameLogic.AboutToApplyDamage
GameLogic.AboutToWaterRise
GameLogic.ActivateNextWorm
GameLogic.AddInventory
GameLogic.ApplyDamage
GameLogic.Challenge.Failure
GameLogic.Challenge.Success
GameLogic.ClearInventories
GameLogic.CrateShower
GameLogic.CreateAirstrike
GameLogic.CreateCrate
GameLogic.CreateNuclearEffect
GameLogic.CreateRandMineFactory
GameLogic.CreateRandomMine
GameLogic.CreateRandomOildrum
GameLogic.CreateRandomTelepad
GameLogic.CreateSentryGun
GameLogic.CreateTelepad
GameLogic.CreateTrigger
GameLogic.Draw
GameLogic.DropRandomCrate
GameLogic.EndTurn
GameLogic.GetAllTeamsHadTurn
GameLogic.IncrementInventory
GameLogic.Mission.Failure
GameLogic.Mission.Success
GameLogic.PlaceObjects
GameLogic.PlaceTelepads
GameLogic.ResetCrateParameters
GameLogic.ResetTriggerParams
GameLogic.SetNoFallDamage
GameLogic.SetSpecialWeapon
GameLogic.StartMineFactory
GameLogic.Turn.Ended
GameLogic.Turn.Started
GameLogic.Tutorial.Failure
Jetpack.UpdateFuel
Land.Clear
Net.DisableAllInput
Particle.NewEmitter
RandomNumber.Get
SentryGun.FireAt
SentryGun.LookAt
String.Substitute
String.SubstituteIndirect
Team.Surrender
Timer.EndRetreatTimer
Timer.EndTurn
Timer.StartGame
Timer.StartHotSeatTimer
Timer.StartPostActivity
Timer.StartTurn
Utility.Delete
Weapon.Create
Weapon.Delete
Weapon.DisableWeaponChange
Worm.ApplyPoison
WormManager.GetActiveAlliances
WormManager.GetSurvivingTeam
WormManager.Reinitialise
WormManager.TeleportIn
WormSelect.OptionSelected
Wormpot.SetupModes

Для Worms 3D:

AI.ClearActions
AI.ExecuteActions
AI.PerformDefaultAITurn
AI.PerformFireAtTargetAction
AI.PerformMoveAction
AI.PerformSetWeaponAction
Camera.Path.Start
Camera.SetDefault
Camera.ShakeEnd
Camera.ShakeStart
Comment.SuddenDeath
Commentary.Clear
Commentary.NoDefault
CommentaryPanel.DebugText
CommentaryPanel.ScriptText
CommentaryPanel.TimedText
EFMV.End
EFMV.Start
Earthquake.End
Earthquake.Start
Explosion.Construct
FCS.QuitAttractMode
FE.InGameDialogText
GameLogic.AboutToApplyDamage
GameLogic.AddInventory
GameLogic.ApplyDamage
GameLogic.Challenge.Failure
GameLogic.Challenge.Result
GameLogic.ClearInventories
GameLogic.CrateShower
GameLogic.CreateAirstrike
GameLogic.CreateCrate
GameLogic.CreateNuclearEffect
GameLogic.CreateRandomMine
GameLogic.CreateRandomOildrum
GameLogic.CreateTrigger
GameLogic.Draw
GameLogic.DropRandomCrate
GameLogic.EndGame
GameLogic.EndTurn
GameLogic.IncrementInventory
GameLogic.Mission.Failure
GameLogic.Mission.Success
GameLogic.PauseGame
GameLogic.PlaceObjects
GameLogic.QuitGame
GameLogic.RequestWeaponIndex
GameLogic.ResetCrateParameters
GameLogic.ResetTriggerParams
GameLogic.ResumeGame
GameLogic.RoundTime.Pause
GameLogic.RoundTime.Resume
GameLogic.SetSpecialWeapon
GameLogic.Turn.Ended
GameLogic.Turn.Started
GameLogic.TurnTime.Pause
GameLogic.TurnTime.Resume
GameLogic.Tutorial.End
GameLogic.Tutorial.Failure
GameLogic.Tutorial.Success
GameStats.AddStatText
GameStats.Print
HUD.HideTurnTime
Jetpack.Kill
NinjaRope.Kill
Particle.NewEmitter
Particle.StartEvent
RandomNumber.Get
Stats.IgnoreWormDeath
Stats.UpdateAtEndOfTurn
String.Substitute
String.SubstituteIndirect
Timer.EndRetreatTimer
Timer.EndTurn
Timer.StartGame
Timer.StartHotSeatTimer
Timer.StartPostActivity
Timer.StartRetreatTimer
Timer.StartTurn
Weapon.Create
Weapon.CreateGraphic
Weapon.Delete
Weapon.DisableWeaponChange
Weapon.Selected
Worm.ApplyPoison
Worm.PendPoison
WormManager.GetActiveAlliances
WormManager.GetActiveWormCount
WormManager.GetSurvivingTeam
WormManager.Reinitialise
WormManager.TeleportIn
WormSelect.OptionSelected

Кроме того, открыв EXE файл игры в текстовом редакторе можно обнаружить много текстовых строк, некоторая часть которых означает имена сообщений (также там есть имена функций и имена контейнеров). Мы не будем отдельно заострять внимание на каждом сообщении, потому что большинство из них можно найти в стандартных скриптах и понять как и зачем их применять.

Comment
Comment.10Kill
Comment.10Mins
Comment.11Kill
Comment.12Kill
Comment.13Kill
Comment.14Kill
Comment.15Kill
Comment.1Min
Comment.2Kill
Comment.3Kill
Comment.4Kill
Comment.5Kill
Comment.5Mins
Comment.6Kill
Comment.7Kill
Comment.8Kill
Comment.9Kill
Comment.Airstrike
Comment.Baseball
Comment.Construction
Comment.Crate
Comment.Demolition
Comment.Donkey.drop
Comment.Donkey.explode
Comment.Donkey.wait
Comment.Draw
Comment.Health
Comment.Kill
Comment.LandDeath
Comment.LowTime
Comment.Mystery
Comment.Radiation
Comment.Reinforce
Comment.Scales
Comment.Sdeath
Comment.Sickness
Comment.StartTurn
Comment.SuddenDeath
Comment.TeamDeath
Comment.TeamWin
Comment.Utility
Comment.WaterDeath

SendIntMessage(message, number)

Эта функция - аналог SendMessage, однако позволяет передавать числовой параметр. Список допустимых сообщений:

Имя сообщения message Параметр number
Crate.Delete индекс ящика или триггера, который удаляем
Crate.RadarHide индекс ящика, который надо скрыть
GameLogic.CreateTeamStatues индекс команды, для которой надо создать статуи
GameLogic.DestroyTrigger индекс триггера, который надо уничтожить
GameLogic.Win индекс команды, которая объявляется победителем
Particle.DelGraphicalEmitter индекс эмиттера
SpawnVolume.Remap индекс команды
WXWormManager.UnspawnWorm индекс червя, которого убрать
Worm.DieQuietly индекс червя, которого
Worm.Poison индекс червя, которого заражаем
Worm.QueueAnim индекс червя
Worm.ResetAnim индекс червя
Worm.Respawn индекс червя, которого оживляем

SendStringMessage(message,text)

Эта функция - аналог SendMessage, однако позволяет передавать текстовый параметр. Список сообщений:

WXMsg.EasterEggFound строка вида "Lock.EasterEgg.номер"
Land.GetLandRemaining код куска земли
Land.EnablePointLight имя эмиттера света
GameLogic.PlaceMine имя эмиттера мины
GameLogic.CreateBriefingBox тип ящика

GetData(container_name)

Обратное действие к SetData. Позволяет считать содержимое xml (или xom в Worms 3D) контейнера с помощью скрипта. Чаще всего вызов GetData производится при каком-то событии, чтобы узнать что именно произошло. Понятие события вы поймёте дальше, а сейчас надо понять такой пример:

function Worm_Died()
  DeadWorm = GetData("DeadWorm.Id")   --узнаём, кто умер
end

При смерти червя интерпретатор вызовет событие - Worm_Died(). При этом мы можем узнать номер умершего червя. В другом, менее распространённом случае, мы сначала с помощью SendMessage "просим" червей вернуть нам какое-либо значение, а потом мы его считываем из контейнера, например:

SendMessage("RandomNumber.Get")                -- запрашиваем случайное число
local RawRand = GetData("RandomNumber.Uint")   -- "забираем" число из контейнера

QueryContainer(container_name)

Эта функция похожа на GetData. Она принимает на вход имя контенера из xml (или xom) файла и возвращает значение, однако QueryContainer используется немного для других целей, а именно для извлечения сложных контенерных типов данных. Вот пример таких данных:

<XContainerResourceDetails id='Worm.Data00'>
  <Value href='Worm.Data00-0'/>
  <Name>Worm.Data00</Name>
  <Flags>369</Flags>
</XContainerResourceDetails>
<WormDataContainer id='Worm.Data00-0'>
  <Name></Name>
  <Active>false</Active>
  <PlayedInGame>false</PlayedInGame>
  <Position x='0' y='0' z='0' />
  <ForcedCameraOffset x='0' y='0' z='0' />
  <Velocity x='0' y='0' z='0' />
  <Aftertouch x='0' y='0' z='0' />
  <InputImpulse x='0' y='0' z='0' />
  <Acceleration x='0' y='0' z='0' />
  <SupportNormal x='0' y='0' z='0' />
  <Orientation x='0' y='0' z='0' />
  <AngularVelocity x='0' y='0' z='0' />
  <ControlX>0</ControlX>
  <ControlY>0</ControlY>
  <LastLogicalUpdate>0</LastLogicalUpdate>
  <SupportFrame>0</SupportFrame>
  <SupportVoxel>0</SupportVoxel>
  <WeaponAngle>0.6</WeaponAngle>
  <WeaponFuse>3</WeaponFuse>
  <WeaponIsBounceMax>false</WeaponIsBounceMax>
  <WeaponHerd>3</WeaponHerd>
  <TeamIndex>0</TeamIndex>
  <PositionInTeam>0</PositionInTeam>
  <PhysicsOverride>0</PhysicsOverride>
  <Flags>0</Flags>
  <PhysicsState>8</PhysicsState>
  <WeaponIndex>0</WeaponIndex>
  <InitialEnergy>0</InitialEnergy>
  <Energy>0</Energy>
  <CPUFixedWeapon>0</CPUFixedWeapon>
  <CPUActionRadius>0</CPUActionRadius>
  <ArtilleryMode>false</ArtilleryMode>
  <PoisonRate>0</PoisonRate>
  <PendingPoison>0</PendingPoison>
  <PlaceWormAtPosition>false</PlaceWormAtPosition>
  <SfxBankName></SfxBankName>
  <Spawn></Spawn>
  <IsParachuteSpawn>false</IsParachuteSpawn>
  <IsAllowedToTakeTurn>true</IsAllowedToTakeTurn>
  <GunWobblePitch>0</GunWobblePitch>
  <GunWobbleYaw>0</GunWobbleYaw>
  <LipSynchBank>255</LipSynchBank>
  <ATT_Hat></ATT_Hat>
  <ATT_Glasses></ATT_Glasses>
  <ATT_Gloves></ATT_Gloves>
  <ATT_Tash></ATT_Tash>
  <MovedByImpulse>true</MovedByImpulse>
  <GraphicalOrientation x='0' y='0' z='0' />
  <Scale x='0' y='0' z='0' />
  <LastCollisionNormal x='0' y='0' z='0' />
  <LogicAnimState>0</LogicAnimState>
  <SlopeAngle>0</SlopeAngle>
  <DamagePending>0</DamagePending>
  <CurrentEnergy>0</CurrentEnergy>
  <IsAfterTouching>false</IsAfterTouching>
  <AfterTouchVector x='0' y='0' z='0' />
  <IsHatWearer>false</IsHatWearer>
  <IsQuickWalking>false</IsQuickWalking>
  <AllowBazooka>1</AllowBazooka>
  <AllowGrenade>1</AllowGrenade>
  <AllowClusterGrenade>1</AllowClusterGrenade>
  <AllowAirstrike>1</AllowAirstrike>
  <AllowDynamite>1</AllowDynamite>
  <AllowHolyHandGrenade>1</AllowHolyHandGrenade>
  <AllowBananaBomb>1</AllowBananaBomb>
  <AllowLandmine>1</AllowLandmine>
  <AllowShotgun>1</AllowShotgun>
  <AllowBaseballBat>1</AllowBaseballBat>
  <AllowProd>1</AllowProd>
  <AllowFirePunch>1</AllowFirePunch>
  <AllowHomingMissile>1</AllowHomingMissile>
  <AllowFlood>1</AllowFlood>
  <AllowSheep>1</AllowSheep>
  <AllowGasCanister>1</AllowGasCanister>
  <AllowOldWoman>1</AllowOldWoman>
  <AllowConcreteDonkey>1</AllowConcreteDonkey>
  <AllowSuperSheep>1</AllowSuperSheep>
  <AllowGirder>1</AllowGirder>
  <AllowBridgeKit>1</AllowBridgeKit>
  <AllowNinjaRope>1</AllowNinjaRope>
  <AllowParachute>1</AllowParachute>
  <AllowLowGravity>1</AllowLowGravity>
  <AllowTeleport>1</AllowTeleport>
  <AllowJetpack>1</AllowJetpack>
  <AllowSkipGo>1</AllowSkipGo>
  <AllowSurrender>1</AllowSurrender>
  <AllowChangeWorm>1</AllowChangeWorm>
  <AllowRedbull>1</AllowRedbull>
  <AllowArmour>1</AllowArmour>
  <AllowWeaponFactoryWeapon>1</AllowWeaponFactoryWeapon>
  <AllowStarburst>1</AllowStarburst>
  <AllowAlienAbduction>1</AllowAlienAbduction>
  <AllowFatkins>1</AllowFatkins>
  <AllowScouser>1</AllowScouser>
  <AllowNoMoreNails>1</AllowNoMoreNails>
  <AllowPipe>1</AllowPipe>
  <AllowPoisonArrow>1</AllowPoisonArrow>
  <AllowSentryGun>1</AllowSentryGun>
  <AllowSniperRifle>1</AllowSniperRifle>
  <AllowSuperAirstrike>1</AllowSuperAirstrike>
  <AllowBubbleTrouble>1</AllowBubbleTrouble>
  <TeleportIn>false</TeleportIn>
  <IsEmotional>true</IsEmotional>
  <HasDrunkRedbull>false</HasDrunkRedbull>
  <Armoured>false</Armoured>
</WormDataContainer>

Этот контейнер описывает червя. При извлечении этого контейнера из xml с помощью QueryContainer мы получим таблицу, содержащую все элементы xml структуры:

local worm = QueryContainer("Worm.Data00")      -- worm - таблица
message(worm.Energy)                            -- выведет оставшуюсь жизнь червяка
message(worm.Armoured)                          -- есть ли броня?

                                                -- поскольку worm-таблица, то можно и так:
message(worm["Energy"])

EditContainer(container_name)

Как и прошлая функция, работает с контейнерами в xml или xom, но она позволяет менять их содержимое. Эта функция возвращает два значения. Первое - "блокировка" - необходима для того, чтобы после изменения контейнера освободить его. Второе - сама таблица с контейнером, которую можно изменять. EditContainer всегда используется вместе с CloseContainer. Пример (запрет на использование оружия наводнение):

lock, scheme = EditContainer("GM.SchemeData")       -- блокируем контейнер и начинаем редактирование
scheme.FloodMystery.Crate = 0
scheme.Flood.Crate = 0
scheme.Flood.Ammo = 0
CloseContainer(lock)                                -- разблокируем и даём червям сигнал применить изменения

Как видите, обязательно после всех изменений надо закрыть контейнер и снять блокировку.

CloseContainer(lock)

Эта функция закрывает открытый контейнер. В него передаётся переменная-блокировка lock, которая получена с помощью EditContainer. После этого контейнер может использоваться в других местах.

CopyContainer(from_container_name, to_container_name)

Полностью копирует содержимое одного контейнера в другой. При этом типы контейнеров должны совпадать, то есть нельзя копировать контейнер типа WormDataContainer в контейнер типа SchemeColective. Пример применения:

CopyContainer("Worm.Data05", "Worm.Data00")

Этот код скопирует содержимое червя Worm.Data05 в Worm.Data00

StartTimer(timer_function, delay)

Данная функция позволяет выполнять действие после некоторого времени. При этом в функцию передаётся имя функции, которую надо вызвать, и задержка в миллисекундах (1 секунда = 1000 миллисекунд). Функция возвращает идентификатор таймера, который потом можно использовать для отмены действия.

function SomeFunction()
  message("Timer: SomeFunction")
end

StartTimer("SomeFunction", 3000)                 -- Вызовет функцию SomeFunction через 3 секунды

CancelTimer(timer_handle)

Позволяет отменить выполнение функции, которая раньше была назначена с помощью StartTimer. Происходит это так:

function SomeFunction()
  message("Timer: SomeFunction")
end

timer = StartTimer("SomeFunction", 3000) -- Назначаем выполнение функции SomeFunction через 3 секунды ...

          -- Вдруг передумали

CancelTimer(timer) -- Отменяем. Если успели раньше 3 сек, то SomeFunction вызвана не будет.

События

Вот мы и подошли к разбору одной из самых нужных возможностей интерпретатора. При наступлении какого-либо события интерпретатор lua в червях может вызывать определённые функции, написанные в ваших скриптах. Эти функции называются обработчиками событий. Примеров событий можно придумать много: смерть червяка, ранение, начало хода или игры, сбор ящика. Рассмотрим пример использования:

function Worm_Died()
  deadworm = GetData("DeadWorm.Id")
  message(deadworm)
end

Этот код просто выведет номер погибшего червя, если он погибнет. Обработчик будет вызываться каждый раз, когда на карте будут умирать черви, при этом всю необходимую информацию можно извлечь из различных контейнеров. Тут, к примеру из DeadWorm.Id

Приведём список событий (таблица не завершена, если знаете событие, добавляйте):

Имя события Описание/назначение Worms 4 Worms 3D
Worm_Died() Обрабатывает смерть червя да да
Initialise() Инициализация, начало игры, расстановка объектов и червей да да
EFMV_Terminated() Законечен мультик да нет
Payload_Deleted() Уничтожен какой-либо объект(к примеру мина) да да
TurnEnded() Закончен ход да да
Weapon_PoweringUpStart() Вызывается, когда сила выстрела начинает рости. нет да
Weapon_Fired_Start() Вызывается, когда сила выстрела достигла придела или или нажат огонь. нет да
Weapon_Fired_End() Вызывается, после анимации выстрела, спустя задержку. нет да
Input_SomeInput() Нажатие какой либо кнопки нет да
PlayerCheck() ? нет да
CheckTurns() Проверка хода, переменная turns, содержит номер хода нет да
Worm_Damaged() Ранение червя да ?
Worm_Damaged_Current() Ранение текущего червя да да

Дополнительные функции

...

Примеры Lua скриптов