Template:Ru: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
(fixing)
Tag: Replaced
Line 1: Line 1:
{{lang|{{PAGENAME}}}}{{TF2 topicon}}
<onlyinclude>{{New flag icon|{{{1}}}|suf=:ru|ico=Ru.png|link1={{{link1|}}}|link2={{{link2|}}}|link3={{{link3|}}}|link4={{{link4|}}}|link5={{{link5|}}}|link6={{{link6|}}}|link7={{{link7|}}}|link8={{{link8|}}}|name=Русский|c=Russian}}</onlyinclude>[[Category:Flag icons]][[Category:Languages]]
{{tf2}} Эта страница содержит примеры скриптов: [[Vscript|vscripts]] для {{tf2|3}}.
 
==Итерация по сущностям==
С помощью цикла <code>while</code> и функции <tt>[[List of L4D2 Script Functions#CEntities|Entities]].FindByClassname()</tt> вы можете перебрать все сущности с подходящим именем класса, основываясь на ваших аргументах.
 
Первый параметр <tt>Entities.FindByClassname()</tt> называется 'previous' (предыдущий), который принимает хэндл скрипта (если сущность наследует 'CBaseEntity'), который проверяет, имеет ли найденная сущность индекс сущности выше, чем текущий в аргументе 'previous' (предыдущий). Если это не так, то он игнорируется.
<source lang=js>
local ent = null
while( ent = Entities.FindByClassname(ent, "prop_physics") )
{
  printl(ent)
}</source>}}
 
==Итерация по Игрокам==
Это может быть полезно для итерации только по игрокам. Однако делать это с помощью <tt>[[List of L4D2 Script Functions#CEntities|Entities]].FindByClassname()</tt> неэффективно, так как нужно перебирать каждую сущность. Для эффективного перебора игроков можно использовать причуду. Каждая сетевая сущность имеет связанный с ней 'индекс сущности', который варьируется от 0 до [[Entity_limit|MAX_EDICTS]]. Обычно они непредсказуемы, однако есть две группы сущностей, которые имеют зарезервированные индексы: [[worldspawn]] и игроки. [[Worldspawn]] is always reserved at entity index 0, and players are reserved from entity index 1 to [[Maxplayers|maxplayer всегда зарезервирован под индекс сущности 0, а игроки зарезервированы от индекса сущности 1 до [[Maxplayers|maxplayers]] + 1. Используя этот факт, игроков можно просто итерировать, как показано ниже:
 
<source lang=js>
for (local i = 1; i <= Constants.Server.MAX_PLAYERS; i++)
{
    local player = PlayerInstanceFromIndex(i)
    if (player == null) continue
    printl(player)
}
</source>
 
==Создание entity==
Этот код показывает, как создать entity, а именно ракету. Ракета будет создана перед первым доступным игроком.
 
<source lang=js>
// Код от ZooL_Smith
 
local ply = Entities.FindByClassname(null, "player")
 
local rocket = SpawnEntityFromTable("tf_projectile_rocket",
{
    // Это таблица ключевых значений, которая аналогична ключевым значениям, определенным в Hammer Editor
    // Параметр    Значение
    basevelocity = ply.EyeAngles().Forward()*250,
    teamnumber  = ply.GetTeam(),
    origin      = ply.EyePosition()+ply.EyeAngles().Forward()*32,
    angles      = ply.EyeAngles()
})
 
rocket.SetOwner(ply) // условие, чтобы ракета не сталкивалась с владельцем и давала соответствующие очки за убийство
</source>
 
==Изменение команды <tt>!activator</tt> (игрока)==
Whenever a script's code gets executed by inputs (e.g.,<code>RunScriptCode</code>/<code>CallScriptFunction</code>on an entity with an attached [[Entity Scripts|entity script]]), the <code>activator</code> and <code>caller</code> variables will be set to the handles of the input's !activator and !caller respectively, allowing you to access them in code.
 
Всякий раз, когда код скрипта выполняет input'ы (например, <code>RunScriptCode</code>/<code>CallScriptFunction</code> на entity с присоединенным скриптом сущности), переменные !activator и !caller будут установлены в хэндлы !activator и !caller входа соответственно, что позволит вам получить к ним доступ в коде.
 
Мы можем использовать это, чтобы, например, легко сделать что-то с игроками, которые идут в определенном триггере.
 
Что касается смены команды !activator, нам нужно изменить их команду в зависимости от того, в какой команде они находятся, а также изменить команду их косметических предметов на их новую команду.
 
Сначала нам нужно выяснить, в какой команде находится !activator. Для этого мы можем использовать метод <code>GetTeam()</code> на !activator (<code>activator.GetTeam()</code>), чтобы получить номерной индекс его команды. Вы можете либо использовать функцию: If для сравнения возвращаемого значения с нужным индексом команды, либо сохранить его в переменной, чтобы использовать позже. Последнее в данном случае может быть лучше для сокращения длины скрипта. Для справки: 0 — Unassigned (команды еще нет), 1 — Наблюдатели, 2 — Команда красных, 3 — Команда синих.
 
Чтобы изменить команду !activator'а, мы должны использовать метод <code>ForceChangeTeam(Int : Team index, Bool : Kill player if game is in Highlander mode + remove their dominations)</code> на них через <code>activator.ForceChangeTeam(...)</code>.
 
Чтобы изменить цвет их косметических предметов, нам нужно перебрать все ''tf_wearable'' entity, а затем изменить их команду с помощью <code>SetTeam(Int : Team index)</code>, если их носит !activator.
* Для итерации нам нужно указать нулевую переменную (например, <code>local cosmetic = null</code>), а затем передать ее в следующий цикл while:
<source lang=js>
local cosmetic = null // Присвоить переменной "cosmetic" значение null, ей будут присваиваться новые значения при переходе к косметическим предметам
while (cosmetic = Entities.FindByClassname(cosmetic, "tf_wearable"))
{
    // Здесь выполняются действия с отдельными entity — tf_wearables, переменная "cosmetic" устанавливается на текущую сущность tf_wearable, через которую мы итерируем
}</source>
* Чтобы проверить, принадлежит ли косметический предмет !activator (игроку), мы можем просто сравнить значение, возвращаемое методом <code>GetOwner()</code>, когда мы используем его на tf_wearable (например, <code>cosmetic.GetOwner()</code>), с !activator (игроком). Пример кода:
<source lang=js>
if (cosmetic.GetOwner() == activator)
{
    // Do things if the cosmetic's wearer is the !activator
}
</source>
* Чтобы изменить команду косметикого предмета (шапка, одежда), нужно использовать метод <code>SetTeam(Int : Team index)</code> на сущности косметического элемента (шапка, одежда).
 
Полный код с комментариями:
{{ExpandBox|<source lang=js>
function ActivatorSwapTeam() // Вызовите это в карте через RunScriptCode > ActivatorSwapTeam() или CallScriptFunction > ActivatorSwapTeam на logic_script, который имеет Entity Script (сценарий сущности) с этой функцией
{
    // Следующий фрагмент проверяет, был ли указан !activator и является ли он игроком
    // Если ответ на любой из вопросов отрицательный, то не выполняйте остальную часть функции
    if (activator == null || activator.IsPlayer() == false)
    {
        return
    }
   
    // В следующем фрагменте сравнивается номер команды активатора (игрока) — (от 0 до 3) с номерами, используемыми в командах Unassigned (0) и Наблюдатель (1).
    // Если они совпадают с Unassigned или Наблюдатель, мы не выполняем остальную часть функции.
    // Используется для игнорирования любых потенциальных зрительских !activator'ов
    if (activator.GetTeam() == 0 || activator.GetTeam() == 1)
    {
        return
    }
   
    // Следующий фрагмент определяет локальное значение newTeam, а затем мы устанавливаем ее в номер команды, основанный на текущем номере команды !activator
    local newTeam = 0
    if (activator.GetTeam() == 2) // Проверяет, что номер команды !activator (игрока) равен значению 2 (красной команды), и устанавливает переменную newTeam равной значению 3 (синей команды).
    { 
        newTeam = 3
    } else { // Если номер команды !activator (игрока) не равен значению 2 (красной команды), вместо этого переменная newTeam устанавливается на значение 2 (синей команды).
        newTeam = 2
    }
 
    // Следующий фрагмент вызывает метод ForceChangeTeam на !activator (игроке)
    // Первый параметр: Номер команды, на которую нужно переключиться
    // Второй параметр: Если false, игра сбросит все доминирования над противником игрока и уберёт их, если включен mp_highlander.
    activator.ForceChangeTeam(newTeam, true)
   
    local cosmetic = null // Присвойте переменной "cosmetic" значение null, ей будут присваиваться новые значения при переходе к косметическим вещам (одежда, шапки)
    // Следующий фрагмент перебирает все имеющиеся на данный момент косметические предметы и меняет их цвета на соответствующие команде, если они принадлежат !activator (игроку)
    while (cosmetic = Entities.FindByClassname(cosmetic, "tf_wearable")) // Просматривает каждый косметический предмет (шапка, одежда), выполняя приведенный ниже код
    {
        if (cosmetic.GetOwner() == activator) // Проверяет, является ли владелец текущего итерируемого косметического предмета !activator (игроком)
        {
            cosmetic.SetTeam(newTeam) // Устанавливает команду косметических вещей (шапок, одежды) на новый номер команды, который мы сохранили в newTeam
        }
    }
}
</source>
 
== Прослушивание Событий ==
Многие действия в игре вызывают события, чтобы уведомить другие части кода о том, что что-то произошло. Например, когда игрок умирает или когда здание инженера истощается. Каждое событие также может содержать данные определенного типа, например, индекс задействованного объекта.
 
VScript может перехватывать эти события, анализировать данные и выполнять некоторый код, когда они происходят (известный как 'callback'). Список доступных событий можно найти здесь: https://wiki.alliedmods.net/Team_Fortress_2_Events
{{warning|Обратные вызовы событий являются глобальными и не очищаются при перезапуске раунда. Обратные вызовы событий должны быть очищены через <code>ClearGameEventCallbacks()</code> чтобы они не накапливались между раундами.}}
 
Следующий код показывает, как прослушать событие post_inventory_application, а затем добавить uber-защиту на 2 секунды.
 
<source lang=cpp>
// Событие "post_inventory_application" отправляется, когда игрок получает новый набор предметов, а именно: прикасается к шкафчику здоровья и боеприпасов в зоне возрождения / или возрождение после смерти.
function OnGameEvent_post_inventory_application(params)
{
local player = GetPlayerFromUserID(params.userid)
 
// добавляет игроку uber-защиту на 2 секунды
player.AddCondEx(52, 2.0, null)
}
 
__CollectGameEventCallbacks(this)
</source>
== Настройка панели здоровья босса ==
Полоса боссов, появляющаяся при появлении боссов на карте (например, ГЛАЗАСТУС!), обрабатывается сущностью <code>monster_resource</code>, которая удобно существует на карте в обычном режиме. Вы можете сделать <code>[[List of TF2 Script Functions#CEntities|Entities]].FindByClassname(null, "monster_resource")</code>, чтобы получить сущность <code>monster_resource</code> в большинстве случаев, но было бы удобно хранить ее в переменной.
 
Для того чтобы изменить процент полоски здоровья, сначала нужно узнать о [[List of TF2 Script Functions#CNetPropManager|NetProps]]:
* NetProps — Это сетевые свойства сущности, которые доступны только на стороне сервера (например боссы).
* Они могут быть доступны и изменены с помощью методов класса [[List of TF2 Script Functions#CNetPropManager|NetProps]]
 
Сущность <code>monster_resource</code> имеет NetProp ''m_iBossHealthPercentageByte'', который определяет процентное состояние полоски здоровья на основе значения байта — его значение должно быть между 0 и 255, где 0 — это 0%, а 255 — это 100%.
 
Следующий код добавит полоску здоровья с 25% здоровья после выполнения.
<source lang=js>
local healthBar = Entities.FindByClassname(null, "monster_resource") // Получите объект "Полоса здоровья".
if (healthBar && healthBar.IsValid) { // Проверьте, существует ли сущность health bar и действительна ли она, на всякий случай, чтобы предотвратить ошибки или возможные сбои.
    // Следующая строка обновит процент полоски здоровья до 25%, изменив ее NetProp.
    // Обратите внимание, что поскольку это байт (0 — 255), нам нужно умножить наш процент на 255.
    NetProps.SetPropInt(healthBar, "m_iBossHealthPercentageByte", 0.25 * 255)
}
</source>
 
==Отключение элементов HUD==
Некоторые элементы HUD можно скрыть, поместив битовое значение в один из этих NetProps:
<source lang=js>
NetProps.SetPropInt(player, "m_Local.m_iHideHUD", value )
NetProps.SetPropInt(player, "localdata.m_Local.m_iHideHUD", value )
</source>
{{note|Обе функции одинаковы.}}
{{note|Значение начинается с 0.}}
Функции бита можно найти в разделе: [[List_of_TF2_Script_Functions#HideHUD|TF2 Script Functions]].
 
Например:
<source lang=js>
local HideHudValue = NetProps.GetPropInt(player, "m_Local.m_iHideHUD")
NetProps.SetPropInt(player, "m_Local.m_iHideHUD", HideHudValue | (
                                                  Constants.HideHUD.HIDEHUD_CROSSHAIR |
                                                  Constants.HideHUD.HIDEHUD_HEALTH |
                                                  Constants.HideHUD.HIDEHUD_WEAPONSELECTION)
                    ) // добавляет HUD на экране игрока
NetProps.SetPropInt(player, "m_Local.m_iHideHUD", HideHudValue & ~(
                                                  Constants.HideHUD.HIDEHUD_CROSSHAIR |
                                                  Constants.HideHUD.HIDEHUD_HEALTH |
                                                  Constants.HideHUD.HIDEHUD_WEAPONSELECTION)
                    ) // удаляет HUD на экране игрока
</source>
Строка 2 добавляет биты для скрытия перекрестия, здоровья и отключения переключения оружия от игрока.
 
В строке 3 удалены биты для отображения перекрестия, здоровья и повторного включения переключения оружия для игрока.
 
==Создание ботов, использующих Navmesh==
Следующий код показывает пример создания примитивного бота, который перемещается с помощью [[navmesh]] (навигационной сетки). При выполнении этого сценария бот будет порожден в перекрестии главного игрока. Этот бот будет просто следовать за игроком и искать путь по схеме карты. Код подробно прокомментирован, а отладочные визуализации позволяют увидеть алгоритм поиска пути в действии.
 
{{warning|Создание ботов — Задача не из легких. Этот код показывает, сколько работы требуется для создания простого бота!}}
{{ExpandBox|<source lang=js>
 
// Constrains an angle into [-180, 180] range
function NormalizeAngle(target)
{
target %= 360.0;
if (target > 180.0)
target -= 360.0;
else if (target < -180.0)
target += 360.0;
return target;
}
 
// Approaches an angle at a given speed
function ApproachAngle(target, value, speed)
{
target = NormalizeAngle(target);
value = NormalizeAngle(value);
local delta = NormalizeAngle(target - value);
if (delta > speed)
return value + speed;
else if (delta < -speed)
return value - speed;
return value;
}
 
// Converts a vector direction into angles
function VectorAngles(forward)
{
local yaw, pitch;
if ( forward.y == 0.0 && forward.x == 0.0 )
{
yaw = 0.0;
if (forward.z > 0.0)
pitch = 270.0;
else
pitch = 90.0;
}
else
{
yaw = (atan2(forward.y, forward.x) * 180.0 / Constants.Math.Pi);
if (yaw < 0.0)
yaw += 360.0;
pitch = (atan2(-forward.z, forward.Length2D()) * 180.0 / Constants.Math.Pi);
if (pitch < 0.0)
pitch += 360.0;
}
 
return QAngle(pitch, yaw, 0.0);
}
 
// Coordinate which is part of a path
class PathPoint
{
constructor(_area, _pos, _how)
{
area = _area;
pos = _pos;
how = _how;
}
 
area = null; // Which area does this point belong to?
pos = null; // Coordinates of the point
how = null; // Type of traversal. See Constants.ENavTraverseType
}
 
// The big boy that handles all our behavior
class Bot
{
function constructor(bot_ent, follow_ent)
{
bot = bot_ent;
 
move_speed = 230.0;
turn_rate = 5.0;
search_dist_z = 128.0;
search_dist_nearest = 128.0;
 
path = [];
path_index = 0;
path_reach_dist = 16.0;
path_follow_ent = follow_ent;
path_follow_ent_dist = 50.0;
path_target_pos = follow_ent.GetOrigin();
path_update_time_next = Time();
path_update_time_delay = 0.2;
path_update_force = true;
area_list = {};
 
seq_idle = bot_ent.LookupSequence("Stand_MELEE");
seq_run = bot_ent.LookupSequence("Run_MELEE");
pose_move_x = bot_ent.LookupPoseParameter("move_x");
 
debug = true;
 
// Add behavior that will run every tick
AddThinkToEnt(bot_ent, "BotThink");
}
 
function UpdatePath()
{
// Clear out the path first
ResetPath();
 
// If there is a follow entity specified, then the bot will pathfind to the entity
if (path_follow_ent && path_follow_ent.IsValid())
path_target_pos = path_follow_ent.GetOrigin();
 
// Pathfind from the bot's position to the target position
local pos_start = bot.GetOrigin();
local pos_end = path_target_pos;
 
local area_start = NavMesh.GetNavArea(pos_start, search_dist_z);
local area_end = NavMesh.GetNavArea(pos_end, search_dist_z);
 
// If either area was not found, try use the closest one
if (area_start == null)
area_start = NavMesh.GetNearestNavArea(pos_start, search_dist_nearest, false, true);
if (area_end == null)
area_end = NavMesh.GetNearestNavArea(pos_end, search_dist_nearest, false, true);
 
// If either area is still missing, then bot can't progress
if (area_start == null || area_end == null)
return false;
 
// If the start and end area is the same, one path point is enough and all the expensive path building can be skipped
if (area_start == area_end)
{
path.append(PathPoint(area_end, pos_end, Constants.ENavTraverseType.NUM_TRAVERSE_TYPES));
return true;
}
 
// Build list of areas required to get from the start to the end
if (!NavMesh.GetNavAreasFromBuildPath(area_start, area_end, pos_end, 0.0, Constants.ETFTeam.TEAM_ANY, false, area_list))
return false;
 
// No areas found? Uh oh
if (area_list.len() == 0)
return false;
 
// Now build points using the list of areas, which the bot will then follow
local area_target = area_list["area0"];
local area = area_target;
local area_count = area_list.len();
 
// Iterate through the list of areas in order and initialize points
for (local i = 0; i < area_count && area != null; i++)
{
path.append(PathPoint(area, area.GetCenter(), area.GetParentHow()));
area = area.GetParent(); // Advances to the next connected area
}
 
// Reverse the list of path points as the area list is connected backwards
path.reverse();
 
// Now compute accurate path points, using adjacent points + direction data from nav
local path_first = path[0];
local path_count = path.len();
 
// First point is simply our current position
path_first.pos = bot.GetOrigin();
path_first.how = Constants.ENavTraverseType.NUM_TRAVERSE_TYPES; // No direction specified
 
for (local i = 1; i < path_count; i++)
{
local path_from = path[i - 1];
local path_to = path[i];
 
// Computes closest point within the "portal" between adjacent areas
path_to.pos = path_from.area.ComputeClosestPointInPortal(path_to.area, path_to.how, path_from.pos);
}
 
// Add a final point so the bot can precisely move towards the end point when it reaches the final area
path.append(PathPoint(area_end, pos_end, Constants.ENavTraverseType.NUM_TRAVERSE_TYPES));
}
 
function AdvancePath()
{
// Check for valid path first
local path_len = path.len();
if (path_len == 0)
return false;
 
local path_pos = path[path_index].pos;
local bot_pos = bot.GetOrigin();
 
// Are we close enough to the path point to consider it as 'reached'?
if ((path_pos - bot_pos).Length2D() < path_reach_dist)
{
// Start moving to the next point
path_index++;
if (path_index >= path_len)
{
// End of the line!
ResetPath();
return false;
}
}
 
return true;
}
 
function ResetPath()
{
area_list.clear();
path.clear();
path_index = 0;
}
 
function Move()
{
// Recompute the path if forced to do so
if (path_update_force)
{
UpdatePath();
path_update_force = false;
}
// Recompute path to our target if present
else if (path_follow_ent && path_follow_ent.IsValid())
{
// Is it time to re-compute the path?
local time = Time();
if (path_update_time_next < time)
{
// Check if target has moved far away enough
if ((path_target_pos - path_follow_ent.GetOrigin()).Length() > path_follow_ent_dist)
{
UpdatePath();
// Don't recompute again for a moment
path_update_time_next = time + path_update_time_delay;
}
}
}
 
// Check and advance up our path
if (AdvancePath())
{
local path_pos = path[path_index].pos;
local bot_pos = bot.GetOrigin();
 
// Direction towards path point
local move_dir = (path_pos - bot_pos);
move_dir.Norm();
 
// Convert direction into angle form
local move_ang = VectorAngles(move_dir);
 
// Approach new desired angle but only on the Y axis
local bot_ang = bot.GetAbsAngles()
move_ang.x = bot_ang.x;
move_ang.y = ApproachAngle(move_ang.y, bot_ang.y, turn_rate);
move_ang.z = bot_ang.z;
 
// Set our new position and angles
// Velocity is calculated from direction times speed, and converted from per-second to per-tick time
bot.SetAbsOrigin(bot_pos + (move_dir * move_speed * FrameTime()));
bot.SetAbsAngles(move_ang);
 
return true;
}
 
return false;
}
 
function Update()
{
// Try moving
if (Move())
{
// Moving, set the run animation
if (bot.GetSequence() != seq_run)
{
bot.SetSequence(seq_run);
bot.SetPoseParameter(pose_move_x, 1.0); // Set the move_x pose to max weight
}
}
else
{
// Not moving, set the idle animation
if (bot.GetSequence() != seq_idle)
{
bot.SetSequence(seq_idle);
bot.SetPoseParameter(pose_move_x, 0.0); // Clear the move_x pose
}
}
 
// Replay animation if it has finished
if (bot.GetCycle() > 0.99)
bot.SetCycle(0.0);
 
// Run animations
bot.StudioFrameAdvance();
bot.DispatchAnimEvents(bot);
 
// Visualize current path in debug mode
if (debug)
{
// Stay around for 1 tick
// Debugoverlays are created on 1st tick but start rendering on 2nd tick, hence this must be doubled
local frame_time = FrameTime() * 2.0;
 
// Draw connected path points
local path_len = path.len();
if (path_len > 0)
{
local path_start_index = path_index;
if (path_start_index == 0)
path_start_index++;
 
for (local i = path_start_index; i < path_len; i++)
{
DebugDrawLine(path[i - 1].pos, path[i].pos, 0, 255, 0, true, frame_time);
}
}
 
// Draw areas from built path
foreach (name, area in area_list)
{
area.DebugDrawFilled(255, 0, 0, 30, frame_time, true, 0.0);
DebugDrawText(area.GetCenter(), name, false, frame_time);
}
}
 
return 0.0; // Think again next frame
}
 
function OnKilled()
{
// Change life state to "dying"
// The bot won't take any more damage, and sentries will stop targeting it
NetProps.SetPropInt(bot, "m_lifeState", 1);
// Reset health, preventing the default base_boss death behavior
bot.SetHealth(bot.GetMaxHealth() * 20);
// Custom death behavior can be added here
// For this example, turn into a ragdoll with the saved damage force
bot.BecomeRagdollOnClient(damage_force);
}
 
bot = null; // The bot entity we belong to
 
move_speed = null; // How fast to move
turn_rate = null; // How fast to turn
search_dist_z = null; // Maximum distance to look for a nav area downwards
search_dist_nearest = null; // Maximum distance to look for any nearby nav area
 
path = null; // List of BotPathPoints
path_index = null; // Current path point bot is at, -1 if none
path_reach_dist = null; // Distance to a path point to be considered as 'reached'
path_follow_ent = null; // What entity to move towards
path_follow_ent_dist = null; // Maximum distance after which the path is recomputed
// if follow entity's current position is too far from our target position
path_target_pos = null; // Position where bot wants to navigate to
path_update_time_next = null; // Timer for when to update path again
path_update_time_delay = null;  // Seconds to wait before trying to attempt to update path again
path_update_force = null; // Force path recomputation on the next tick
area_list = null; // List of areas built in path
 
seq_idle = null; // Animation to use when idle
seq_run = null; // Animation to use when running
pose_move_x = null; // Pose parameter to set for running animation
 
damage_force = null; // Damage force from the bot's last OnTakeDamage event
 
debug = null; // When true, debug visualization is enabled
 
}
 
function BotThink()
{
// Let the bot class handle all the work
return self.GetScriptScope().my_bot.Update();
}
 
function BotCreate()
{
// Find point where player is looking
local player = GetListenServerHost();
local trace =
{
start = player.EyePosition(),
end = player.EyePosition() + (player.EyeAngles().Forward() * 32768.0),
ignore = player
};
 
if (!TraceLineEx(trace))
{
printl("Invalid bot spawn location");
return null;
}
 
// Spawn bot at the end point
local bot = SpawnEntityFromTable("base_boss",
{
targetname = "bot",
origin = trace.pos,
model = "models/bots/heavy/bot_heavy.mdl",
playbackrate = 1.0, // Required for animations to be simulated
health = 300
});
 
// Add scope to the entity
bot.ValidateScriptScope();
// Append custom bot class and initialize its behavior
bot.GetScriptScope().my_bot <- Bot(bot, player);
 
return bot;
}
 
function OnScriptHook_OnTakeDamage(params)
{
local ent = params.const_entity;
local inf = params.inflictor;
    if (ent.IsPlayer() && HasBotScript(inf) && params.damage_type == 1)
    {
// Don't crush the player if a bot pushes them into a wall
        params.damage = 0;
    }
if (ent.GetClassname() == "base_boss" && HasBotScript(ent))
{
// Save the damage force into the bot's data
ent.GetScriptScope().my_bot.damage_force = params.damage_force;
}
}
 
function OnGameEvent_npc_hurt(params)
{
local ent = EntIndexToHScript(params.entindex);
if (HasBotScript(ent))
{
// Check if a bot is about to die
if ((ent.GetHealth() - params.damageamount) <= 0)
{
// Run the bot's OnKilled function
ent.GetScriptScope().my_bot.OnKilled();
}
}
}
 
function HasBotScript(ent)
{
// Return true if this entity has the my_bot script scope
return (ent.GetScriptScope() != null && ent.GetScriptScope().my_bot != null);
}
 
__CollectGameEventCallbacks(this)
BotCreate();
</source>}}
 
== Смотрите также ==
* {{tf2}} [[List of TF2 Script Functions]]
* [[List of Script Libraries]], они также могут быть полезны в качестве примеров
 
[[Category:Scripting]][[Category:Team Fortress 2]]

Revision as of 07:08, 13 January 2023

Русский