--function printf() --end local MARKER_IN = 1 local MARKER_OUT = 2 local MARKER_IDLE = 3 --' Анимации персонажа --' мы уже находимся в необходимом состоянии? class "eva_state_mgr_animation" (property_evaluator) function eva_state_mgr_animation:__init(name, st) super (nil, name) self.st = st end function eva_state_mgr_animation:evaluate() --printf("%s [%s] == [%s]", self.object:name(), tostring(state_lib.states[self.st.target_state].animation), tostring(self.st.animation.states.current_state)) return state_lib.states[self.st.target_state].animation == self.st.animation.states.current_state end --' Отыгрываем ли мы сейчас извращенную анимацию class "eva_state_mgr_animation_play_now" (property_evaluator) function eva_state_mgr_animation_play_now:__init(name, st) super (nil, name) self.st = st end function eva_state_mgr_animation_play_now:evaluate() return self.st.animation.states.current_state ~= nil end --' мы уже не отыгрываем никакую извращенную анимацию class "eva_state_mgr_animation_none_now" (property_evaluator) function eva_state_mgr_animation_none_now:__init(name, st) super (nil, name) self.st = st end function eva_state_mgr_animation_none_now:evaluate() return self.st.animation.states.current_state == nil end --' Мы начали отыгрывать анимацию и ждем коллбека от нее class "eva_state_mgr_animation_locked" (property_evaluator) function eva_state_mgr_animation_locked:__init(name, st) super (nil, name) self.st = st end function eva_state_mgr_animation_locked:evaluate() --printf("anim_locked [%s] fast_set[%s] anim_marker[%s] sid[%s]", self.object:name(), tostring(self.st.fast_set), tostring(self.st.animation.states.anim_marker), tostring(self.st.animation.sid)) -- if self.st.fast_set == true then -- return false -- end return self.st.animation.states.anim_marker ~= nil end --' Играем входную анимацию class "act_state_mgr_animation_start" (action_base) function act_state_mgr_animation_start:__init(name, st) super (nil, name) self.st = st end function act_state_mgr_animation_start:initialize() action_base.initialize(self) self.st.animation:set_state(state_lib.states[self.st.target_state].animation) self.st.animation:set_control() end function act_state_mgr_animation_start:execute() action_base.execute(self) end function act_state_mgr_animation_start:finalize() action_base.finalize(self) end --' Играем выходную анимацию class "act_state_mgr_animation_stop" (action_base) function act_state_mgr_animation_stop:__init(name, st) super (nil, name) self.st = st end function act_state_mgr_animation_stop:initialize() action_base.initialize(self) self.st.animation:set_state(nil, self.st.fast_set or state_lib.states[self.st.target_state].fast_set) self.st.animation:set_control() end function act_state_mgr_animation_stop:execute() action_base.execute(self) end function act_state_mgr_animation_stop:finalize() action_base.finalize(self) end class "animation" function animation:__init(npc, mgr, anim_path) self.mgr = mgr self.npc = npc self.name = anim_path self.anim_path = _G[anim_path] self.sid = math.random(1000) --printf("ANIMCLASS %s sid %s", self.npc:name(), tostring(self.sid)) --callstack() self.states = { last_id = nil, current_state = nil, target_state = nil, anim_marker = nil, next_rnd = nil, seq_id = 1 } end -- Передача управления данному экземпляру класса. function animation:set_control() --printf("[%s] [%s] set control current_state[%s] marker[%s] sid[%s]", self.npc:name(), self.name, tostring(self.states.current_state), tostring(self.states.anim_marker), self.sid) -- Устанавливаем коллбек self.npc:set_callback(callback.script_animation, animation.animation_callback, self) if self.name == "state_mgr_animation_list" then self.mgr.animstate.states.anim_marker = nil end if self.states.anim_marker == nil then self:update_anim() end end -- Инициализируем выбор анимации function animation:update_anim() local anim, state = self:select_anim() if anim ~= nil then self:add_anim(anim, state) end end -- Установка цели, какую анимацию нужно отыгрывать. function animation:set_state(new_state, fast_set) --printf("[%s] [%s] set new animation [%s], current_state [%s]", self.npc:name(), self.name, tostring(new_state), tostring(self.states.current_state)) -- print_table(self.states) -- Быстрый выход, если понадобится. if fast_set == true then --printf(" FAST SET %s sid(%s)", self.npc:name(), self.sid) -- clear_animations self.npc:clear_animations() -- Детачнуть атачнутые предметы local state if self.states.anim_marker == MARKER_IN then -- Если в момент ресета мы играем входную анимацию state = self.anim_path.animations[self.states.target_state] else -- Если в момент ресета мы играем выходную анимацию или айдл state = self.anim_path.animations[self.states.current_state] end if state ~= nil and state.out ~= nil then local wpn_slot = self:weapon_slot() local anim_for_slot = self:anim_for_slot(wpn_slot, state.out) if anim_for_slot ~= nil then for k,next_anim in pairs(anim_for_slot) do if type(next_anim) == "table" then self:process_special_action(next_anim) end end end end -- перевести current_state в nil --printf("NULIFY ANIM MARKER %s", tostring(self.states.anim_marker)) self.states.anim_marker = nil self.states.current_state = new_state self.states.target_state = new_state self.states.seq_id = 1 self.states.next_rnd = time_global() return end self.states.target_state = new_state self.states.next_rnd = time_global() end -- Выбор анимации. function animation:select_anim() local states = self.states --printf("[%s] [%s] select anim [%s], current_state [%s]", self.npc:name(), self.name, tostring(states.target_state), tostring(states.current_state)) --printf(" time %s", time_global()) --printf(" current state %s", utils.to_str(states.current_state)) --printf(" target state %s", utils.to_str(states.target_state)) if states.target_state ~= states.current_state then -- Один из возможных вариантов выбора анимашки: -- 1. Нужно отыграть выход из анимации. if states.target_state == nil then --printf(" select out") local state = self.anim_path.animations[states.current_state] -- Если нет OUT анимации if state.out == nil then states.anim_marker = MARKER_OUT self:animation_callback(true) return end states.anim_marker = MARKER_OUT local wpn_slot = self:weapon_slot() local anim_for_slot = self:anim_for_slot(wpn_slot, state.out) if anim_for_slot == nil then states.anim_marker = MARKER_OUT self:animation_callback(true) return nil end local next_anim = anim_for_slot[states.seq_id] -- Если следующая анимация - спец действие if type(next_anim) == "table" then self:process_special_action(next_anim) self:animation_callback() return end return next_anim, state end -- 2. Нужно отыграть вход в анимацию if states.current_state == nil then --printf(" select into, seq_id [%s], target [%s]", tostring(states.seq_id), tostring(states.target_state)) local state = self.anim_path.animations[states.target_state] -- Если нет INTO анимации if state.into == nil then states.anim_marker = MARKER_IN self:animation_callback(true) return nil end states.anim_marker = MARKER_IN local wpn_slot = self:weapon_slot() local anim_for_slot = self:anim_for_slot(wpn_slot, state.into) if anim_for_slot == nil then --printf("anim for slot nil") states.anim_marker = MARKER_IN self:animation_callback(true) return nil end local next_anim = anim_for_slot[states.seq_id] -- Если следующая анимация - спец действие if type(next_anim) == "table" then self:process_special_action(next_anim) self:animation_callback() return nil end return next_anim, state end end -- 3. Нужно отыграть айдл анимации или рандом if states.target_state == states.current_state and states.current_state ~= nil then --printf(" select rnd") local wpn_slot = self:weapon_slot() local state = self.anim_path.animations[self.states.current_state] local anim if state.rnd ~= nil then anim = self:select_rnd(state, wpn_slot, time_global() >= states.next_rnd) end -- Если не выбрали рандомную, то играем айдл if anim == nil and state.idle ~= nil then anim = self:anim_for_slot(wpn_slot, state.idle) end if anim ~= nil then states.anim_marker = MARKER_IDLE end return anim, state end end --' Возвращает текущий оружейный слот function animation:weapon_slot() local weapon = self.npc:active_item() if weapon == nil or self.npc:weapon_strapped() == true then return 0 end return weapon:animation_slot() end --' Возвращает анимацию под переданный оружейный слот function animation:anim_for_slot(slot, t) -- printf("ANIM [%s] for slot [%s]", self.name, tostring(slot)) -- print_table(t) -- printf("-------------------------") if t[slot] == nil then slot = 0 end if t[0] == nil then -- print_table(t) -- abort("cant find animation for slot %s", tonumber(slot)) end return t[slot] end -- Выборка рандомной анимации function animation:select_rnd(anim_state, wpn_slot, must_play) if not must_play and math.random(100) > self.anim_path.animations[self.states.current_state].prop.rnd then return nil end local anima = self:anim_for_slot(wpn_slot,anim_state.rnd) if anima == nil then return nil end local states = self.states local r if #anima > 1 then if states.last_id == nil then r = math.random(#anima) else r = math.random(#anima-1) if r >= states.last_id then r = r + 1 end end self.states.last_id = r else r = 1 end return anima[r] end -- Добавление анимации, установка коллбека function animation:add_anim(anim, state) local npc = self.npc local animation_props = state.prop --printf("[%s][%s] add_anim[%s] time[%s] no_rootmove[%s] %s", self.npc:name(), self.name, tostring(anim), time_global(), tostring(animation_props == nil or animation_props.moving ~= true), device():time_global()) if not (npc:weapon_unstrapped() or npc:weapon_strapped()) then --callstack() abort("Illegal call of add animation. Weapon is strapping now!!! %s", npc:name()) end if animation_props == nil or animation_props.moving ~= true then npc:add_animation(anim, true, false) return end -- Если позиций/направление анимации не заданы или они уже применились if self.mgr.animation_position == nil or self.mgr.pos_direction_applied == true then -- Анимация с передвижением. --npc:set_sight(CSightParams.eSightTypeAnimationDirection, false,false) -- printf("%s no pos", npc:name()) npc:add_animation(anim, true, true) else if self.mgr.animation_direction == nil then abort("Animation direction missing") end -- Сюда передается дирекшн (единичный вектор. нужно его перевести в вектор ротейшнов по костям) local rot_y = -math.deg(math.atan2(self.mgr.animation_direction.x, self.mgr.animation_direction.z)) --local rot_y2 = math.deg(math.acos( (v1.x*v2.x + v1.y*v2.y)/math.sqrt((v1.x*v1.x + v1.y*v1.y)*(v2.x*v2.x + v2.y*v2.y)) )) --printf("%s UGOL %s", npc:name(), rot_y) -- Нижеследующая строка - это когда нужно отыграть анимацию в глобальных координатах. npc:add_animation (anim, true, self.mgr.animation_position, vector():set(0, rot_y, 0), false) self.mgr.pos_direction_applied = true end end -- Вызов коллбека, обычно в этот момент нужен выбор новой анимации. function animation:animation_callback(skip_multianim_check) --printf("[%s][%s] ANIM CALLBACK time[%s] count[%s]", self.npc:name(), self.name, time_global(), self.npc:animation_count()) if(self.npc:animation_count()~=0) then return end local states = self.states -- 1. Отыграли INTO анимацию if states.anim_marker == MARKER_IN then states.anim_marker = nil -- Если INTO анимаций несколько if skip_multianim_check ~= true then local into_table = {} local target_anims = self.anim_path.animations[states.target_state] if target_anims ~= nil and target_anims.into ~= nil then into_table = self:anim_for_slot(self:weapon_slot(), target_anims.into) end if into_table ~= nil and #into_table > states.seq_id then states.seq_id = states.seq_id + 1 self:update_anim() return end end states.seq_id = 1 states.current_state = states.target_state self:update_anim() return end -- 2. Отыграли RND или IDLE if states.anim_marker == MARKER_IDLE then states.anim_marker = nil local props = self.anim_path.animations[states.current_state].prop if props.maxidle == 0 then states.next_rnd = time_global() + props.sumidle*1000 else states.next_rnd = time_global() + (props.sumidle + math.random(props.maxidle))*1000 end self:update_anim() return end -- 3. Отыграли OUT анимацию if states.anim_marker == MARKER_OUT then states.anim_marker = nil if skip_multianim_check ~= true then -- Если OUT анимаций несколько local out_table = {} if(self.anim_path.animations[states.current_state].out) then out_table = self:anim_for_slot(self:weapon_slot(), self.anim_path.animations[states.current_state].out) end if out_table ~= nil and #out_table > states.seq_id then states.seq_id = states.seq_id + 1 self:update_anim() return end end states.seq_id = 1 states.current_state = nil -- Передать управление анимстейту. if self.name == "state_mgr_animation_list" then if self.mgr.animstate ~= nil and self.mgr.animstate.set_control ~= nil then self.mgr.animstate:set_control() --self.mgr.animstate:update_anim() end end end end -- Обработка спецдействий function animation:process_special_action(action_table) --printf("[%s] process_special_action", self.npc:name()) -- Производим действия находящиеся на 1 позиции -- Аттач предмета if action_table.a ~= nil then printf("item [%s] attach", utils.to_str(action_table.a)) local obj = self.npc:object(action_table.a) if obj then obj:enable_attachable_item(true) end end -- Детач предмета if action_table.d ~= nil then printf("item [%s] detach", utils.to_str(action_table.d)) local obj = self.npc:object(action_table.d) if obj then obj:enable_attachable_item(false) end end -- Отыграть звук if action_table.s ~= nil then xr_sound.set_sound_play(self.npc:id(), action_table.s) end -- Нанести хит себе if action_table.sh ~= nil then -- наносим хит себе. local h = hit() h.power = action_table.sh h.direction = vector_rotate_y(self.npc:direction(), 90) h.draftsman = self.npc h.impulse = 200 h.type = hit.wound self.npc:hit(h) end -- Запуск любой функции if action_table.f ~= nil then --printf("called function [%s]", tostring(action_table.f)) action_table.f(self.npc) end end