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

Timed actions в Build 42: очередь и жизненный цикл

Разбери очередь timed actions, её жизненный цикл и отмену, длительность и различие perform/complete в мультиплеере.

Timed action — это действие, которое персонаж выполняет некоторое время: например, ест, режет или чинит. В руководстве она показана как таблица Lua, производная от ISBaseTimedAction и связанная с Java-классом zombie.characters.CharacterTimedActions.LuaTimedActionNew. Автор указывает каталоги действий игры shared/TimedActions/ (143 файла) и client/TimedActions/ (21 файл); числа и пути относятся к его снимку.

Действия добавляются в очередь и не запускаются немедленно.

ISTimedActionQueue.add(MiAccion:new(player, item))

Файл ISTimedActionQueue.lua выполняет действия по очереди. Источник также упоминает addAfter(anterior, accion), clear(character), hasActionType(character, "ISEatFoodAction") и isPlayerDoingAction(player). Имена API сохранены как в оригинале.

Жизненный цикл

Согласно описанию автора, Java вызывает функции в следующем порядке:

new() ← вызывается при создании: сохраняет данные и вычисляет maxTime │ isValidStart() ← можно ли начать? waitToStart() ← возвращает true, пока нужно ждать, например поворота start() ← один раз: анимация, звук, модели в руках update() ← каждый tick: прогресс и проверки isValid() ← каждый tick: false отменяет действие │ ├── при отмене (движение, персонажа атакуют, ошибка isValid) → stop() │ └── при завершении → perform() (видимая очистка на клиенте) complete() (эффект; см. раздел о мультиплеере)

new() сохраняет данные и вычисляет maxTime; isValidStart() решает, можно ли начать; waitToStart() возвращает true, пока нужно подождать, например чтобы повернуться; start() один раз задаёт анимацию, звук и предметы в руках; update() вызывается каждый tick для прогресса и проверок; isValid() проверяется каждый tick, и false отменяет действие через stop(). При завершении вызываются perform() (видимая очистка на клиенте) и complete() (сам игровой эффект, согласно разделу о мультиплеере).

В ISBaseTimedAction:new можно менять stopOnWalk, stopOnRun и stopOnAim (по умолчанию true), caloriesModifier и maxTime. Java-класс LuaTimedActionNew также читает поля useProgressBar, forceProgressBar, loopedAction и allowedWhileDraggingCorpses, если они заданы. В примере еды stopOnWalk = false позволяет есть на ходу.

Пример Pumpkin Z DIY

Автор показывает PumpkinZCarveAction.lua из своего мода Pumpkin Z DIY как короткое действие для B42. Это ориентир, а не готовый код: фрагмент намеренно опускает замену тыквы и сетевую синхронизацию (многоточие).

PumpkinZCarveAction = ISBaseTimedAction:derive("PumpkinZCarveAction") function PumpkinZCarveAction:isValid() -- ¿todavía tengo la calabaza y el cuchillo? local inv = self.character:getInventory() return self.pumpkin and self.knife and inv:contains(self.pumpkin) and inv:contains(self.knife) and PumpkinZ.isValidCarve(self.carve) end function PumpkinZCarveAction:update() self.pumpkin:setJobDelta(self:getJobDelta()) -- la barrita sobre el ícono del ítem end function PumpkinZCarveAction:start() self.pumpkin:setJobType(getText("IGUI_PumpkinZ_Carving")) self:setActionAnim("Craft") -- animación del personaje self:setOverrideHandModels(self.knife, self.pumpkin) -- qué tiene en cada mano self.sound = self.character:playSound("SliceFood") end function PumpkinZCarveAction:stop() -- cancelada self:stopSound() self.pumpkin:setJobDelta(0.0) ISBaseTimedAction.stop(self) -- ¡siempre llamar al padre! end function PumpkinZCarveAction:perform() -- terminó (lado visible) self:stopSound() self.pumpkin:setJobDelta(0.0) ISBaseTimedAction.perform(self) -- ¡siempre! avisa a la cola que siga end function PumpkinZCarveAction:complete() -- terminó (el efecto) ... quita la calabaza, agrega la tallada, sincroniza ... return true end function PumpkinZCarveAction:getDuration() if self.character:isTimedActionInstant() then return 1 end -- modo debug "acciones instantáneas" return math.min(1800, 90 + PumpkinZ.countCuts(self.carve) * 8) end function PumpkinZCarveAction:new(character, pumpkin, knife, carve) local o = ISBaseTimedAction.new(self, character) o.pumpkin, o.knife, o.carve = pumpkin, knife, carve o.maxTime = o:getDuration() return o end

В примере isValid() повторно проверяет наличие тыквы и ножа и их наличие в инвентаре, а также допустимость резьбы. update() обновляет полосу прогресса значка. start() задаёт название задания, анимацию Craft, предметы в руках и звук SliceFood. stop() останавливает звук, обнуляет прогресс и вызывает метод базового класса.

perform() очищает звук и прогресс и обязательно вызывает ISBaseTimedAction.perform(self), чтобы очередь продолжилась. В complete() должен находиться эффект: убрать тыкву, добавить вырезанную и синхронизировать; метод возвращает true. getDuration() возвращает 1 для мгновенных действий, иначе ограничивает значением 1800 выражение 90 + countCuts(carve) × 8. new() сохраняет предметы и вычисляет maxTime через getDuration().

Информация
Автор отмечает типичную ошибку: забыть ISBaseTimedAction.perform(self). Тогда действие как будто заканчивается, но onCompleted очереди не вызывается и персонаж больше не продолжает другие действия.

Длительность

maxTime не измеряется в секундах. ISBaseTimedAction:create() вызывает adjustMaxTime и увеличивает длительность с учётом состояния персонажа. Пример из руководства:

maxTime = maxTime * (1 + infeliz / 4) -- nivel del moodle de infelicidad maxTime = maxTime * (1 + borracho / 4) maxTime = maxTime * (1 + dolorDeManos / 300) -- salvo que pongas ignoreHandsWounds = true maxTime = maxTime * character:getTimedActionTimeModifier() -- temperatura del cuerpo

Формула умножает maxTime на коэффициенты, связанные с уровнем moodle несчастья, опьянением, травмой рук (кроме случая ignoreHandsWounds = true) и character:getTimedActionTimeModifier() (в тексте этот параметр связан с температурой тела). Если нужна точная длительность, например совпадающая с анимацией, автор предлагает переопределить adjustMaxTime и возвращать исходный maxTime без изменений; в качестве примера он называет DanceAction из Batalla de Aura.

Мультиплеер: perform() и complete()

Руководство представляет разделение perform/complete как важное изменение B42 на основании анализа LuaTimedActionNew. Это утверждение источника для конкретной версии; его нужно независимо проверить на целевой сборке.

public void complete() { super.complete(); if (!GameClient.client) { // ← solo si NO soy un cliente LuaManager.caller.pcall(LuaManager.thread, this.table.rawget("complete"), this.table); } }

В показанном коде сначала вызывается Java-метод super.complete(). Затем Lua-функция complete вызывается только если GameClient.client равно false. При создании действия флаг useCustomRemoteTimedActionSync включается, если в метатаблице нет complete():

if (table.getMetatable().rawget("complete") == null) { this.useCustomRemoteTimedActionSync = true; // ← sin complete(): modo antiguo }

Где выполняется кодperform()complete()
Одиночная играДаДа
Клиент, подключённый к серверуДаНет
Сервер—Да

Согласно источнику, если действие определяет complete(), клиент запрашивает его у сервера, а Java управляет транзакцией через ActionManager; complete() выполняется на сервере. Автор советует размещать там эффект, чтобы решение принимал сервер. Без реальных проверок это нельзя считать универсальной гарантией защиты от читов.

Класс действия должен находиться в shared/, чтобы сервер мог создать его по имени. Если complete() не определён, применяется старый режим: действие целиком выполняется на клиенте без синхронизации. Автор считает его подходящим только для локальных действий, а не для изменений мира или инвентаря.

Изменения complete() нужно сообщать клиентам. В Pumpkin Z DIY после изменения инвентаря используются sendRemoveItemFromContainer и sendAddItemToContainer. Автор говорит, что isValid() выполняется на обеих сторонах; пример повторно проверяет isValidCarve, поскольку входящие от клиента данные нельзя считать доверенными.

Сначала подойти, затем действовать

Для действия над объектом мира сначала нужно подойти к нему. По словам автора, игра ставит в очередь ISWalkToTimedAction (52 использования) либо вызывает luautils.walkAdj(player, square) (281 использование), который ищет свободную соседнюю клетку, а затем добавляет действие:

if luautils.walkAdj(player, objeto:getSquare()) then ISTimedActionQueue.add(MiAccion:new(player, objeto)) end

Упражнения

11.1 · Поджарить sopaipilla

Реализуй CursoLab.FreirAction: имея сковороду и сырую sopaipilla в инвентаре, приготовить её и получить готовую. Нужны анимация и звук, видимая в руке сковорода, отмена при отпускании предмета и сокращение времени с ростом Cooking. Готово, когда действие работает, корректно отменяется и после отмены можно добавить другое действие без блокировки персонажа.

11.2 · Ошибка очереди

Убери ISBaseTimedAction.perform(self), добавь в очередь два последовательных действия жарки и опиши результат. Найди в ISTimedActionQueue.lua невызванную функцию и объясни причину.

11.3 · Действие над объектом мира

Создай действие «Постучать в дверь»: персонаж подходит с помощью luautils.walkAdj, стучит несколько секунд и затем издаёт шум, привлекающий зомби. Найди в игре способ вызвать шум через addSound.

11.4 · Измерить длительность

Добавь print в start и perform, измеряй реальные секунды через getTimestampMs() при maxTime = 100: сначала на обычном персонаже, затем с сильным несчастьем. Сравни результат с adjustMaxTime.

11.5 · Подготовить действие к мультиплееру

  1. Файл действия находится в shared/?
  2. Эффект выполняется в complete(), и функция возвращает true?
  3. isValid() проверяет все данные, пришедшие от клиента?
  4. Изменения инвентаря передаются через sendAddItemToContainer и sendRemoveItemFromContainer?

Реальную проверку в мультиплеере источник оставляет модулю 12 с локальным сервером.

Задача со звёздочкой · Повторяющееся действие

Реализуй действие в цикле — например, ловлю рыбы до остановки или рубку дерева, пока не заполнится инвентарь. Оно должно повторяться до условия или отмены игроком. Найди игровой шаблон; подсказка — поле loopedAction, которое читает LuaTimedActionNew.

Информация
Примеры и заявления о сетевой синхронизации, порядке вызовов и серверной стороне специфичны для версии и не проверялись в игре. Фрагменты содержат опущенную логику. Упражнения не запускались; для них требуется общий проект курса.