pax_global_header00006660000000000000000000000064126410413300014504gustar00rootroot0000000000000052 comment=fcc25534aa09318137cc75bc32cface356bb8aed mobf_core-2.5.1/000077500000000000000000000000001264104133000134445ustar00rootroot00000000000000mobf_core-2.5.1/.gitignore000066400000000000000000000000621264104133000154320ustar00rootroot00000000000000mobf/doc/html/* *~ *.svn */models/*.png *.project mobf_core-2.5.1/barn/000077500000000000000000000000001264104133000143665ustar00rootroot00000000000000mobf_core-2.5.1/barn/License.txt000066400000000000000000000002011264104133000165020ustar00rootroot00000000000000Licenses Everything not mentioned: CC-BY-SA 3.0, Author sapier URL: http://creativecommons.org/licenses/by-sa/3.0/de/legalcodemobf_core-2.5.1/barn/depends.txt000066400000000000000000000000261264104133000165470ustar00rootroot00000000000000default mobf intllib? mobf_core-2.5.1/barn/init.lua000066400000000000000000000230071264104133000160360ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file init.lua --! @brief barn implementation --! @copyright Sapier --! @author Sapier --! @date 2013-01-27 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- -- Boilerplate to support localized strings if intllib mod is installed. local S if (minetest.get_modpath("intllib")) then dofile(minetest.get_modpath("intllib").."/intllib.lua") S = intllib.Getter(minetest.get_current_modname()) else S = function ( s ) return s end end minetest.log("action","MOD: barn mod loading ...") local version = "0.0.13" local modpath = minetest.get_modpath("barn") barn_breedpairs_big = { { "animal_sheep:sheep","animal_sheep:sheep","animal_sheep:lamb","animal_sheep:lamb"}, { "animal_cow:cow","animal_cow:steer","animal_cow:baby_calf_m","animal_cow:baby_calf_f"}, } barn_breedpairs_small = { { "animal_chicken:chicken","animal_chicken:rooster","animal_chicken:chick_m","animal_chicken:chick_f"}, } --include debug trace functions dofile (modpath .. "/model.lua") minetest.register_craftitem("barn:barn_empty", { description = S("Barn to breed animals"), image = minetest.inventorycube("barn_3d_empty_top.png","barn_3d_empty_side.png","barn_3d_empty_side.png"), on_place = function(item, placer, pointed_thing) if pointed_thing.type == "node" then local pos = pointed_thing.above local newobject = minetest.add_entity(pos,"barn:barn_empty_ent") item:take_item() return item end end }) minetest.register_craftitem("barn:barn_small_empty", { description = S("Barn to breed small animals"), image = "barn_small.png", on_place = function(item, placer, pointed_thing) if pointed_thing.type == "node" then local pos = pointed_thing.above local newobject = minetest.add_entity(pos,"barn:barn_small_empty_ent") item:take_item() return item end end }) minetest.register_craft({ output = "barn:barn_empty 1", recipe = { {'default:stick', 'default:stick','default:stick'}, {'default:wood','default:wood','default:wood'}, } }) minetest.register_craft({ output = "barn:barn_small_empty 1", recipe = { {'default:stick', 'default:stick'}, {'default:wood','default:wood'}, } }) function is_food(name) if name == "default:leaves" then return true end if name == "default:junglegrass" then return true end return false end function breed(breedpairs,self,now) local pos = self.object:getpos() local objectlist = minetest.get_objects_inside_radius(pos,2) local le_animal1 = nil local le_animal2 = nil for index,value in pairs(objectlist) do local luaentity = value:get_luaentity() local mobname = nil if luaentity ~= nil and luaentity.data ~= nil and luaentity.data.name ~= nil and luaentity.data.modname ~= nil then mobname = luaentity.data.modname .. ":" .. luaentity.data.name end if mobname ~= nil and mobname == breedpairs[1] and luaentity ~= le_animal1 and le_animal2 == nil then le_animal2 = luaentity end if mobname ~= nil and mobname == breedpairs[2] and le_animal2 ~= luaentity then le_animal1 = luaentity end if le_animal1 ~= nil and le_animal2 ~= nil then break end end if math.random() < (0.0001 * (now - (self.last_breed_time + 30))) and self.last_breed_time > 0 and le_animal1 ~= nil and le_animal2 ~= nil then local pos1 = le_animal1.object:getpos() local pos2 = le_animal2.object:getpos() local pos = self.object:getpos() local pos_to_breed = { x = pos1.x + (pos2.x - pos1.x) /2, y = pos1.y, z = pos1.z + (pos2.z - pos1.z) /2, } --TODO check position by now this is done by spawn algorithm only local result = breedpairs[math.random(3,4)] local breeded = minetest.add_entity(pos_to_breed ,result) local breeded_lua = breeded:get_luaentity() if breeded_lua.dynamic_data.spawning == nil then breeded_lua.dynamic_data.spawning = {} end breeded_lua.dynamic_data.spawning.player_spawned = true if le_animal1.dynamic_data.spawning.spawner ~= nil then breeded_lua.dynamic_data.spawning.spawner = le_animal1.dynamic_data.spawning.spawner elseif le_animal2.dynamic_data.spawning.spawner ~= nil then breeded_lua.dynamic_data.spawning.spawner = le_animal2.dynamic_data.spawning.spawner end return true end return false end --Entity minetest.register_entity(":barn:barn_ent", { physical = true, collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, visual = "wielditem", textures = { "barn:box_filled"}, visual_size = { x=0.666,y=0.666,z=0.666}, on_step = function(self,dtime) local now = os.time(os.date('*t')) if now ~= self.last_check_time then local select = math.random(1,#barn_breedpairs_big) local breedpairs = barn_breedpairs_big[select] --print("Selected " .. select .. " --> " ..dump(breedpairs)) if breed(breedpairs,self,now) then local pos = self.object:getpos() --remove barn and add empty one self.object:remove() local barn_empty = minetest.add_entity(pos,"barn:barn_empty_ent") local barn_empty_lua = barn_empty:get_luaentity() barn_empty_lua.last_breed_time = now end self.last_check_time = now end end, on_activate = function(self,staticdata) if staticdata == nil then self.last_breed_time = os.time(os.date('*t')) else self.last_breed_time = tonumber(staticdata) end self.last_check_time = os.time(os.date('*t')) end, get_staticdata = function(self) return self.last_breed_time end, on_punch = function(self,player) player:get_inventory():add_item("main", "barn:barn_empty 1") self.object:remove() end, last_breed_time = -1, last_check_time = -1, }) minetest.register_entity(":barn:barn_empty_ent", { physical = true, collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, visual = "wielditem", textures = { "barn:box_empty"}, visual_size = { x=0.666,y=0.666,z=0.666}, on_punch = function(self,player) if player == nil then return end if not player:is_player() then return end --if player is wearing food replace by full barn local tool = player:get_wielded_item() if is_food(tool:get_name()) then local time_of_last_breed = self.last_breed_time local pos = self.object:getpos() self.object:remove() local barn = minetest.add_entity(pos,"barn:barn_ent") local barn_lua = barn:get_luaentity() barn_lua.last_breed_time = time_of_last_breed player:get_inventory():remove_item("main",tool:get_name().." 1") --else add to players inventory else player:get_inventory():add_item("main", "barn:barn_empty 1") self.object:remove() end end, on_activate = function(self, staticdata) self.last_breed_time = os.time(os.date('*t')) self.last_check_time = self.last_breed_time end, }) minetest.register_entity(":barn:barn_small_ent", { physical = true, collisionbox = {-0.5,-0.5,-0.5, 0.5,-0.2,0.5}, visual = "wielditem", textures = { "barn:box_small_filled"}, visual_size = { x=0.666,y=0.666,z=0.666}, on_step = function(self,dtime) local now = os.time(os.date('*t')) if now ~= self.last_check_time then local select = math.random(1,#barn_breedpairs_small) local breedpairs = barn_breedpairs_small[select] --print("Selected " .. select .. " --> " ..dump(breedpairs)) if breed(breedpairs,self,now) then local pos = self.object:getpos() --remove barn and add empty one self.object:remove() local barn_empty = minetest.add_entity(pos,"barn:barn_small_empty_ent") local barn_empty_lua = barn_empty:get_luaentity() barn_empty_lua.last_breed_time = now end self.last_check_time = now end end, on_activate = function(self,staticdata) if staticdata == nil then self.last_breed_time = os.time(os.date('*t')) else self.last_breed_time = tonumber(staticdata) end self.last_check_time = os.time(os.date('*t')) end, get_staticdata = function(self) return self.last_breed_time end, on_punch = function(self,player) player:get_inventory():add_item("main", "barn:barn_small_empty 1") self.object:remove() end, last_breed_time = -1, last_check_time = -1, }) minetest.register_entity(":barn:barn_small_empty_ent", { physical = true, collisionbox = {-0.5,-0.5,-0.5, 0.5,-0.2,0.5}, visual = "wielditem", textures = { "barn:box_small_empty"}, visual_size = { x=0.666,y=0.666,z=0.666}, on_punch = function(self,player) --if player is wearing food replace by full barn local tool = player:get_wielded_item() if is_food(tool:get_name()) then local time_of_last_breed = self.last_breed_time local pos = self.object:getpos() self.object:remove() local barn = minetest.add_entity(pos,"barn:barn_small_ent") local barn_lua = barn:get_luaentity() barn_lua.last_breed_time = time_of_last_breed player:get_inventory():remove_item("main",tool:get_name().." 1") --else add to players inventory else player:get_inventory():add_item("main", "barn:barn_small_empty 1") self.object:remove() end end, on_activate = function(self, staticdata) self.last_breed_time = os.time(os.date('*t')) self.last_check_time = self.last_breed_time end, }) minetest.log("action","MOD: barn mod version " .. version .. " loaded") mobf_core-2.5.1/barn/locale/000077500000000000000000000000001264104133000156255ustar00rootroot00000000000000mobf_core-2.5.1/barn/locale/de.txt000066400000000000000000000002151264104133000167540ustar00rootroot00000000000000# Translation by Xanthin Barn to breed animals = Raufe um Tiere zu zuechten Barn to breed small animals = Raufe um kleine Tiere zu zuechten mobf_core-2.5.1/barn/locale/es.txt000066400000000000000000000003711264104133000167760ustar00rootroot00000000000000# Spanish translation for Animals Modpack. # Traducción al español de Animals Modpack. # Author/Autor: Diego Martínez Barn to breed animals = Granero para criar animales Barn to breed small animals = Granero para criar animales pequenos mobf_core-2.5.1/barn/locale/template.txt000066400000000000000000000001041264104133000201740ustar00rootroot00000000000000# Template Barn to breed animals = Barn to breed small animals = mobf_core-2.5.1/barn/model.lua000066400000000000000000000112061264104133000161710ustar00rootroot00000000000000function x(val) return ((val -80) / 160) end function z(val) return ((val -80) / 160) end function y(val) return ((val + 80) / 160) end local textures_small_empty = { "barn_3d_bottom.png", } local textures_small_filled = { "barn_3d_small_top.png", "barn_3d_bottom.png", "barn_3d_bottom.png", } local textures_empty = { "barn_3d_bottom.png", "barn_3d_empty_top.png", "barn_3d_empty_side.png", "barn_3d_empty_side.png", "barn_3d_empty_side.png", "barn_3d_empty_side.png", } local textures_filled = { "barn_3d_filled_top.png", "barn_3d_bottom.png", "barn_3d_filled_side.png", "barn_3d_filled_side.png", "barn_3d_filled_side.png", "barn_3d_filled_side.png", } local box_barn_small_empty = { --floor { x(0) , y(-150), z(160), x(160), y(-160), z(0) }, { x(0) , y(-120),z(160), x(160),y(-150),z(150) }, { x(0) , y(-120),z(10), x(160),y(-150),z(0) }, { x(0) , y(-120),z(160), x(10),y(-150),z(0) }, { x(150) , y(-120),z(160), x(160),y(-150),z(0) } } local box_barn_small_filled = { --floor { x(0) , y(-150), z(160), x(160), y(-160), z(0) }, { x(0) , y(-120),z(160), x(160),y(-150),z(150) }, { x(0) , y(-120),z(10), x(160),y(-150),z(0) }, { x(0) , y(-120),z(160), x(10),y(-150),z(0) }, { x(150) , y(-120),z(160), x(160),y(-150),z(0) }, { x(10), y(-125), z(150), x(150),y(-150),z(10) } } local box_barn_empty = { --floor { x(0) , y(-150), z(160), x(160), y(-160), z(0) }, --x edge front { x(0) , y(0), z(160), x(20), y(-150), z(140) }, { x(47), y(0), z(160), x(66), y(-150), z(140) }, { x(94), y(0), z(160), x(113), y(-150), z(140) }, { x(140), y(0), z(160), x(160), y(-150), z(140) }, --x edge back { x(0), y(0), z(20), x(20), y(-150), z(0) }, { x(47), y(0), z(20), x(66), y(-150), z(0) }, { x(94), y(0), z(20), x(113), y(-150), z(0) }, { x(140), y(0), z(20), x(160), y(-150), z(0) }, -- z edge right { x(140), y(0), z(66), x(160), y(-160), z(47) }, { x(140), y(0), z(113), x(160), y(-150), z(94) }, -- z edge left { x(0), y(0), z(66), x(20), y(-150), z(47) }, { x(0), y(0), z(113), x(20), y(-150), z(94) }, --metal plates { x(10), y(-50), z(150), x(150), y(-70), z(140),}, { x(10), y(-130), z(150), x(150), y(-150), z(140)}, { x(10), y(-50), z(20), x(150), y(-70), z(10),}, { x(10), y(-130), z(20), x(150), y(-150), z(10)}, { x(10), y(-50), z(150), x(20), y(-70), z(10),}, { x(10), y(-130), z(150), x(20), y(-150), z(10),}, { x(140), y(-50), z(150), x(150), y(-70), z(10),}, { x(140), y(-130), z(150), x(150), y(-150), z(10),}, } local box_barn_filled = { --floor { x(0) , y(-150), z(160), x(160), y(-160), z(0) }, --x edge front { x(0) , y(0), z(160), x(20), y(-150), z(140) }, { x(47), y(0), z(160), x(66), y(-150), z(140) }, { x(94), y(0), z(160), x(113), y(-150), z(140) }, { x(140), y(0), z(160), x(160), y(-150), z(140) }, --x edge back { x(0), y(0), z(20), x(20), y(-150), z(0) }, { x(47), y(0), z(20), x(66), y(-150), z(0) }, { x(94), y(0), z(20), x(113), y(-150), z(0) }, { x(140), y(0), z(20), x(160), y(-150), z(0) }, -- z edge right { x(140), y(0), z(66), x(160), y(-160), z(47) }, { x(140), y(0), z(113), x(160), y(-150), z(94) }, -- z edge left { x(0), y(0), z(66), x(20), y(-150), z(47) }, { x(0), y(0), z(113), x(20), y(-150), z(94) }, --metal plates { x(10), y(-50), z(150), x(150), y(-70), z(140)}, { x(10), y(-130), z(150), x(150), y(-150), z(140)}, { x(10), y(-50), z(20), x(150), y(-70), z(10)}, { x(10), y(-130), z(20), x(150), y(-150), z(10)}, { x(10), y(-50), z(150), x(20), y(-70), z(10)}, { x(10), y(-130), z(150), x(20), y(-150), z(10)}, { x(140), y(-50), z(150), x(150), y(-70), z(10)}, { x(140), y(-130), z(150), x(150), y(-150), z(10)}, --grass { x(20), y(-30), z(140), x(140), y(-130), z(20)} } minetest.register_node("barn:box_empty", { tiles = textures_empty, drawtype = "nodebox", node_box = { type = "fixed", fixed = box_barn_empty }, }) minetest.register_node("barn:box_filled", { tiles = textures_filled, drawtype = "nodebox", node_box = { type = "fixed", fixed = box_barn_filled }, }) minetest.register_node("barn:box_small_empty", { tiles = textures_small_empty, drawtype = "nodebox", node_box = { type = "fixed", fixed = box_barn_small_empty }, }) minetest.register_node("barn:box_small_filled", { tiles = textures_small_filled, drawtype = "nodebox", node_box = { type = "fixed", fixed = box_barn_small_filled }, })mobf_core-2.5.1/barn/textures/000077500000000000000000000000001264104133000162515ustar00rootroot00000000000000mobf_core-2.5.1/barn/textures/barn_3d_bottom.png000066400000000000000000001342241264104133000216610ustar00rootroot00000000000000PNG  IHDRzsRGB pHYsaa?itIME  "Ɂ IDATxLIٖ^sν7|YbR*F10@B3bvIa 2BM K 0YU*˗qv"W8qn}ſRb9c4)03H)Q۶% 11oRD$m6yBf,""d˲ĘP{!XkUJn{M;縤H@d"U׶4+%/%o&r=RhEJ)DxBlFDRJRRk9m۵ZY&"\ RRLmۢ)E"TZkX5ϳRHZݹ$9#bJ)j彗R:H!" \DIۤ}|[oEJm@*%E!y/}K)1 wQI[4R֤4"/bmۅ{DRŘb*OOOۍ\/ڇc /9~xsΟN#"۶mIpNɹ8/K?~DĶmsmӴm[wqƮܶeb!cze攒RJ$%3>{!QJ|D!(eHK۔R^"")Bu="10R*L$}3 h%TJm(V f;D'O9g"ZJ)˲NkZo;޼y#hHD1r_yΆ|+mn>tu9{/^!L$ZǘO) |ߏc8MHvu΅6]ץ^Ruffiq )\~RRsVƘl#KH[cm{DQO8K!ĶۺyV!D[^D9k9g!@{zw5MӶ-\Q*u)E)Z*RRJv t3÷"&H.b)9rFRNy1!,ׯ:yC_Ǐ9샑7!3 z)dx>_7H HBN>}z>rۯ~՛?}6~-ڶY&y]ׇW)cWJ))%<>跒Ĵ.,xiQ b6)".><#Ҳ'b(H cRv]'ަ{=R|}6M(,k)K4뺞N'k~eYf&R RK"BSBf0 1F"1M@]d)emy,?1/.~<"J6!I#)%]ӴHGo&73r_7۴Z6|W?|bZm(̧ieY1OOOpBӳR*Q+}9iRozyCӛHj[yc.Rʶm\omksC12c۴)n%!s6)"$T)Ő2ᰇx:t\`G)v=^7M3Sιknsp>F? C9Vnm"84c}J1 R.xeA*n%2V;$Q)ksJ˥:c-W:x:\vi, ~~y\oMkO4o}4[7Ƙt|.{6JysF ftݴ۴ܧuKItk[߷]׽&He]޷e.jEh=mc4!{rmnMXklx:"|".m(OSLIE$yt][Jm[J ιPJ22|kҺƶ1OmY0J 6r^yVJBNn[;9'_^^^_6}̬J1eN]~okrocty9)m~_S:Z'^ZW1sr:u8gna[%W?q4m7͛an 1xk$ʥ}<ϥlUH?~1n_ "!ľE(SLΙiaca!\@"J%ڦ !0:T Q mP2sYmN333REt:Q׏,D9Z֦9K2FaNi*w%|?_q) B/Zkr˾4 1FmL(lkQ}Z֦mnOn/NRowo{k_"v `Ms^s8Ɛ*c4 # B{!DLm\tv0m~݇7(92ca!N_ڶ2a6A&b!"",\6MQ*UJZuֵ6kڔoP "2t)_KR (u17.Dz>_R.1rݦ$B!0McLi2)Y }?˶ +eu}Q7ǧҚJi>zHሂ$Wc*M]'6 _}%·9Sąat!Jmat>|iݶ\J|4mCIrp<C"}۾n/ϟHZ1iR,|E0\mǏi)3۽nwa^vciLJѴի8-+3 AN97m׶Ma&)4O9?v]W^۶DԶtm,Eq8mϹ,ǧGBafd^Eׯ__ӓsiҺR*DRk)PԂw9ǷaC ZݶjcXJ9'U1AR6t9s.R (8 p|y]JR)%}W.sTFWa{:|imC|1A`aq n$J.!F#ӫeSgZDZV1Ji.>F!ۍUq)u^K+h\pC]%gR}BD"Rw}ףZ#DNv17h?|p8rHn2_]}R.o^8JeYBot<-BDι7oZcu?Wo mk3tOtߝw8jn+sݮ^/o ReiTp8\^>}߾{}Ru^1MCMz8__߽G|'BcR'?c$HZ+#SJm>??_/ӗ_~YS*Dͻ.kDQuzwTU{.ͻ-mFR Χ_}$Dh12yQ nr¿_1{4-IHOOm ;OB|}߼ZemYm\a_}})"UӾ]0\גSr,ˢn0~?O4hU-~ݷm޼}}NZm~7~<[]וRbϟ><<r ~.˲ra. L)9juYr}wvt(>DIG{_|ź۶9J) 5j^0\@5ι0s?ݷCߏeǶߜ׶m!ưR~Դx<ޑx}(Q<<yY p? %|y||Z6v!jSMnw4#2 !RMcqf6RJSI<߯62v8=z7֊sn.%v R&0[Q-I%x]WfpΥb }{cJ?6]'10)ivZ3$I˵SF}/\i)4͇→W'SJ`Q*u8+Eawd"~}Z;˾ox|}ߓB`Ωn+'VJJ!m2mAF:1}J%u X)jr!mkmR\RJj_撽 \?R)%S~9~n??{sZ$yA\K OS mߓ鏇s>C߶vH>_eI)Yk麮۶-,^^^afVua~"=Ykq6=3 @"J)q}߫~;cs T Tn߇aS FVu"!P 3+%%Z$o䜢6$B>V;6m| J*rݥM i$%$Q9g!d{ 'cnB"NJkTFwGr.JI)U̩&esN%uY(U7FiFko|8K)RWCRu]˅C@m sᣒ*ŠBD1&8ՁǓk9 mY`(H 3m.K⢵j}+VSZkD!R0 s91ƜR;44m/[I)F۶iZCM.`*!Bf"!u 9.1r$ۮWp.R䝻.P9z/sIJOw'}۶BP6//cHqp8~~9#mz>Mk _IJ̈GRʘn[^8 xR)F"2Ƅkk Y p="䔛99)e;'QJGD|ݾ$ZWB,,Rz0 )&A"FB(޴!,T)% j1R*!DmR(', mߝNi ( @*!e+r8jSJ׶m0Dm X|߾]!i+_}KmOK ۶ {A6޾RIB>l6M|z}mA(@iN!y5q^Mc6(ꞅsFk蝯r?s sJ-*onu3^sJZ"-R۷S#(J%Z"h }$Q̱䬔,l۞s6J A1xRQh3: ! c6xORjIusH1KVJ3aֵwFFQR9Xx]׶m!I2$WrX iR]vs9뺆0 1&FhֻPRe㫧u]2VC¦m1D佇1R)"B= BTBښE*rF}""2ZRܾcAq\UY^!DuK-̼k`LkR#s6F A9g-"B.)%ilǘ@RJ`9#_rf "Jܦi#R~zs2 bH!?wrc R}K)*[l/! !Bp!|RJ)!B~_V:,r8!0%c.0}oaLcs9!R)(0Z]#lDu1>QDK>E!D%((FGr)v(Ja!ﻔT캮ڊ]}뚽\9Wb`}30T62买 DR,@)8]*cmR*\;"rgN)CUL?O}A!zR\iZ" I} 241r9/ZIARIٵ]4D9!(3T (@\8$'$JUrm?RĹ@Prڨb*B4U}Tc9RR)sJgfBH0RJBG dPBZ7XJDQ!TԶF*)GgFJ"" D^[ˀ,#o!墕d.W8@0]X@$JR)HD@@!TRs1E"1xB1x1Q;% 3пӂ0 J)MM JJɔsZ \՝m[Xm@p%BJ-`.ub ˆ"])8@)b4F{95)!CJ* ) (IH@I2'Mv=$ZkY0AD DZ[iԓZJVRJerJA)D:5+,YH) !d40 ]:וּ9Tѝ۶Nz]Z.T]# STJ%`ݪm3m6DT !sֶDsN9[䜷mcht-BsDTu ThmHJ)@cf ÐglD~WkRmyݕo{ڶiQJD 99`I JRF!FIdZJqާI9#`ou%W[I[ٵPi$KD%3# HcY A,""j<=E7R1Evyx<;D !0 K. (|Aa譵%\ۅRJBJCNY>6Rh!~k6XBfVJ#(! 9ƒ$!DR] \ Քұm] 304zjb-"H$R±5hUriHHSJJT.0Ʀuy9{9 IDAT5X齏1v:i1&1(X%nJ nZ#rQJwڂZ R$ EZU6[lkR2\r]>D9sMcSijZUWYkBy樂pM*raF[+%9]U#XۦJ7g+Lj16oU#ﵞiK¾>aj_T # V(EJiٶ͚?!1uF`sUVԬgRڻDs$J*", 1ZZjAj0Wa9ZˇxKSJ)V=imӊ*3q|9GD> ͅ1r)\iR !D-0XڇT-3;[9"%_a҈k `1R:=v-Nإ U>}H)Uo۶}!8PSʵy}u]MIॢqk{oYu7M'޶vSUHo4v].-R}a0ƨtZI)Pb̥d$YkJYBD.3kky)T5J)uFͥԉ!D" 5q0ϳ Uǟ\3Z6Eil|Bx}ľ묵FRQH) 0! }{-U}*AЅӏ=)EqRm aϹtm#@r^s TQJv&"jʺW m{Ja8$$ m.Jq Z6(Ƹ;#ԃo[um "^Wu46KiڶUw(XmwN޶MփB*BRxr^!Rֶ諌AB,Rh1(\ژ5fwqv])5&tO_}qxyyA (>wDݦtu'!_iAyQi"P$K_|wҾ}!D۷x)9bcI)MPrOorf`cl۵\44MKaε—R6]1QR)e΂Ppeڶn[ĺu #IS{kmd ^1&dt^[U/K)\98p_Wq̼/] 2 wtu,P7 %)RPk-\r]0hֆ m,DR?~u%58M1FHŘ}6MZ;2r/E( @<|}b9ِ@DI1!Sw}Ԫ<I; ]ܬ*XN{W^)c<|wzIbʙHmN#]ەm7Vv]o۶Ϋ]]qB~by]V"VMĘSa]z_~ιx(BH)yWz 8 0b_7!}p8ts{<.e@اǔ2":Z뇇?Q)ܴ-ᕒ1tܶZEJ)?}zx= 7wN0nwDRJ BhЍ9J.2MRbrg7ZX F!mC*qpnoz.Exz\EIU$JEJn_:B{":u(1E.lY $}]K\ff\>c*ï}PbmM}nϹ1+L1?PrG)IIyz|uߡWG$mCө%D2܏\1CkR(mI?`J)&I*}?tpL)ngk>ldSqJ)$MNYիmbL\m߅,Ĉ5_Y1+$%\`/ien4VIQTPOrΕa)u/׳4c\RR3fJU,|>rUHy>??z:2n6p?G(L]輏)cv}7"7ox8(cyC~ۦ6xu9Bua#J)\8 !v΅wÇZuۢ 1eexBI eYƔA)u>%CpUr7_~uׯ~_E+QRz|8Lo߾oܞR򢤪x8+ena}p5䒥F)E"xnZkDmeOxυ1f~uQkmYbI-mVzQ_ ui_m| OG-Uu˥θ P~$cTq 1f^kݶ~)q)iRxH9}8~7ڡ|UJxc1tx89r.$ZֿDD4X1ƜUPq~t-_ R,S"̭5[LƘs6JP۫?*?2]uOpݾ׻J*%庮>}I׉.A5cdZ0 U6M,˺άǩ[5]"A*FJ6).4߬ñǶmKI!>GvJ[1///Z%M-Y+\J*{_|5qYj 宔bJrB?l/{}ߍ1 Q\yJoiNG"YTE''8mS 1}}QRcThB`vR5ןRN e$RH 0ENy߉hNϦ"j ^AD<mۮu=?zkeyF|Nh34mLU}qݷu]JrNt}xz5n9,N[kASnrͥc-^AZkV04Mmf殔ޅۗZ4M)?kYJYJFa8*n[B,sط/Z뇇@ah}p1bNVcI=00}+>N> }w?/kL 2LJSIjmmsBg&sR~bq=Z_f9QM`T=!9RΩmۡoyZx<Ɩp5m[׵4(,aB`JښJ@D1Vk= Tj۷vn7k-p89ι|\y6@]ZkbDN)t]mbRrZ,eY! yQ$%v]I B5}c1λ۾ɾi7W/!sfFJR!$"攳Қ!F iڶK)uC_csb>xf_dB0̿jšڸ,BPL S%%BTV*e9g@0C)`E¬)2$ 1>SڶMJYȅcZj5]Y攈iVJʘBѶ$t[`JmI#o1HB9#,tJ M !GFrǜo!R %PA"`d3*}lB`M>$DرJCѪp؆b m]\mgv嬤RZ!3 `I( $K!. i@CY/`ٚi}'a%QmF圉z,?hy3DDt[6HxO@<#4^iـ%Ȗ2s%;%*5Z;m 1mylC#-Ln D)H elyQRǔs-h@fֿRM0)"Do{"4Mf [iJw)WF:m uP(RAkS[k/Jnikm,4B))@ ~Nj6NAI)3(eSI 9@L^[\Vri z tg:Lk-K!t!!5 @HpZ1i.Y #`{j(M;߱~=X8.R*"J6ꄨA*;֥""Z)6{]!  c`0()I\ &:%J)޺T*j1*~KLA5({l1 cXXFIe >8gR~^."dxfhC'!J.Ҙ޺JsJ@Z8k/E*%ވRF+H`B %$%&x!x:[RUJ PT)V2\( @ŒQ@<8:4h}, :1utyWFR~w")ڌ1dRiRTr%"DBR yBv( C+ddHB{RZ YmɥBjbomcP[cu8⦵Ď P9ai&XtmyS)e|>ћoi!Eᛯsn>|35=>>X%g$/x]JoqB"w=%u̗m[8I8I)ZR=B`GlWcH.c]U)Wb)xfg}9䝓RZkk:m̶.HԱW»ݍ6{gJ.qZKʥin9]Q 1MJ[hoHR+ش1qb!xF`ӧmVVJ{_k? x՜֘޿{B9AKBZg_$mիh[J"!"R60-eMxkpc)-{i:_P !ZaDԭi5xL 1Z:ýθvPkaoL4K)6·ޮE:ƕ777 {I1p|~ВoXk2Z{mNϖ!\Jִg"9Rs7lf"4z7 sAo獝|L0* p.fmğcZm\.A`cO=bwUeZzY"BH;m+oQ_u93:K).;o n8 Z[iyvz?ǧ< k= b-tRq1{i2Z>Z1.RZ1S)o?TdR " *5w?iRNWÎ޺ R0\js/JF7)!><|H/,-q !n+c$l@|QR^j_qŏ~/=~$z>>{e3gCJ)uZ۶zMjBTR!6FK)k."֝ux5L몵qZk)o<ϳlk-,[JRs޷-{5q0ƄqD$$acrLzn9#m- <= \׵rTRla5|:Oι!L %0B@Da۶qƨWQ_|P#rʙo1$aZ$^8ο|*?>~_oJ |>ӾB'st2ss,"\XU833ޏN)i}z:rpys9u Uqg-?@ֵؕƐdާjUz3Z)֪֢|p:p0~C4ZKr'ZqR6Vji}S:yXg 1r## ڬX!Si +l5Lh\^oޭg~|Rl)Yr9"n1ZkvBW>"eY*zZOb*Zlʙm)%{)DTjbm,Ji0\{O[lcK8C]J+! @IPG!P 1AMk$z]S"&IJx rIV;|q:R{gd9젵*1TZ!Z-BOA&)gOZy^OY!7ܽYH?Njvw?~[,F?>|za޿Fbj19t:zc hsc<ւ%"b*VC1[xe6<ɰs!8m ap9gt]fBt)6jZ"ziuD:mJ帷Ja[ 7B^FĎm~]w{ o+Lkxﻐ%RZ 1,J(U4߾yIsbz]/ۺȊ%\.1n ;9"><ӟREk[%Mt~I_qǜ31F'`oZニHZExcϗKQN>~xをvR}ZqVPBc9g):|r r3UJ"B\Sڌ1Bh) co= R"$ιjQZJ[)z[<4pb|ðhR^9o'+w=ܶMi\Bͽ kԩJԉл|99Nu֘ hY )T͙K@! }V믟߿/s\s IDATaIkm+ =R1%E$BRʸEjB|͚c/ H}Y8Nq۰#k4k)לDkNtU 2puw#%RJaqvjϥ7BJxku^ Պ 0UÇbRjv{93`ghco|zz"1nJ AKqw{f)5|RUj΅ҺqϿ?Gc6!4ϥQ9[k&jC4Q(C/RH)dUA6ZkV9LK)k˙8{a`/ɫITZ TJ7J1! [oaAn`RЫ!ƘRҺ׆iI!SZ1J:  ?wq]/4]޻>_.8tcaެ?Kk,`_' A2:mkOSڤR)?xrt.RKm9I?%di{WR;RJ8 Z@#"$ @$ _j/[a6x{b%RKɘφZ+˃Qjk\DkRp@0fFl@M psGDRkB@l|/{%㘲n$08I+#cNIb1رyryXJ8O<1PGVZL~"‡ fh,s)z u @"Ƹ,"j8|Eڰ5sJXq>/2zRJ_%]W Z qkdβ,J;uk1 @\%5̋hDD{Vb~ 4Qkę:{!?t>߿w}t:àolj}W_kr1ޕRtzvnpౠ^;礜w5& J-qF>J)떸#BPqJ܄$u.x<-q9aGc~Q)ױgk "UUKyq◒-~|{^@B [w0Fˆ#>\ v}~XцD䜒1SBsAYs G_"zC~s֍D^BZuapƘ֛sN*y:?)!ߍ1/ð_LB)q,}{8fNڶPpGr>ym) WU*ÝJ)^N/жm]K7ϻʯcu{ϥH)1+ejF; |}{+U*E R)E-$]3R2]@{$x2tlػRj۶0 ^ղ-\[+H%;"bZM(noc/SL+%X)圷d rƩtdܔ7x]iڪѢi !soHeZ.W~#}*y6 AHm۶Һ8IJy8e;L[kݎ ?L8N7w KeH(4$"!;v5"F];[4zo{ @IYؽ[o c,\cldޖRvu1|ZI|:>R m,JsKa0,Z"u=ZkgÛ{m[IoTB,K7j3ưma/֜S%"" 2}!^c>~*κRyf(Rrі"RƉaU:uFȗɧfBp7[GB)$Z5JHA1!r=֚E8qalRl5/($0ҲO< y0qV{RFb)Dļ9. WTqO8(wZ- oV+ۋ((stOXZR`&!Dn==!\, 6\F$ 0Ny|@;׏.OX @ jcL;) Ddp8? AR5 !J8ְV0706Mضr>޶-^gBA?֡.; 1\󟟟K)w9RJpI!6rEG&e]dn]?ZkU}$ҎZޅadB%,DWԂBB"vӓ^ QK!D!eM)jy],Q1J/A !jw$hF{%% 0Ç_ !D﨤RHJsfF]h'4N~i[I+rW/wFFOO+-ҶĴ J)o Q'!@+x~|ڏ77ŭ\mT[go?nV[ !0[c~ws'`1I{el7"bb)p8r.U R\p$>}ųXG|ރ5WXV֥1%9@$8qk HB%i%I!9e-7k)mwR( à"&ctpXr6!A)#"%|&"Cj|Z֪-7]O=̭hBe[WgLP Mmo޾Ҷmy{=89sY.jX8r'0mo</k6OO%'mF]{gks|L6cjk|ӧǻn@Zkf!XI*YJejGkͺmZaѽ88nVRu3t6Z/SΥ\5&!r@4Oe9xqaƃ]#!DKŽQʇm4u^[α2[Y)ф0H7̄܊x9k#d˲[Cqů 9U\mEa`L;Zks)֪;Z +*$5Is)PHRTVkPXrcŦQjDDJ UzGn>EtM5UqxϧӉsuyw<0 %6ֶJۺPPd*5F;Yw=7| \ZV 1J *!!JZι\ąW伖䔌yMG^ܔюޘ_ 1{* >,yoޛj`Y.Y۶n]H0w5 aJ5Ek^.: P)˙׭!sp/BXk4N;Da#'1Ƥ:{+@RD1YT:mۤ 5 5,agyxzZ7(W_ p8.2||ׇu9AkSJ$^J|$sɼ;7o0 DHIe b\@~7TgطmZRɵuj-Z9z%^E*)'NwPY$HborL鼬{>:f"9 eQJ@^/EktsGLJZ݇bJyg~g֘{ !8@f·Իk)u+mGDPkc`H[^='crV.떖%tqF sr7eqѭVX^#i[ҵ5N,֦jH!r)Z)%gBTƵޭD$x=g ΔZc9S~CMsfIJհK!lqB)YH֭uޏVOvXzPJ驪L4 [.zYböpݾ qYJJ;un/ PVKZIc R2=!m{ki@JTj5Y밵/6Ns"JZk(9q˺*B|5aE㬔R<DDJ$9w>?0jmR6"B )k CH#P_Kt+ 3^JB[y^NHwH m@ԥDg]MҽbΥZy@b秧cYjǒ i8qh-0Z֮{`;n&®ΧM)əl5a1#߽{11R^۔c u')ź(q1בjR%e5&Xi:ݽF0R+$I)? /;(B8ZM0a RX@ S^u aaXx^BJ_8jFOߒ=<<}{\μ~m9RRx<-m0J\Jvޅa"uY whwmLxGR ZƦmZPje6 )$2ڛ!|x>="RJ`YdkMK 6x H8ׄ$xO{ū-ջooƿfRR}CVr~8ʶ[G(0 XK̩ò\r7R|Y{o:().J+J1nuZ5NSs<.PZ犰m+@Dݪ֘-ntˇԊIJG9 s6xG}Nkb_Ga R2J.?:P9W)(%q29Nm1F {df|:Hi}>*owO'|s_eφR)E׍^w 8Nc L KzT[eYkϗ511: @* cZEiιNaԐ4 0 qI0x{yRJTgoSB%Y(u1&5!])_؞cbL9V k a6m l@UIQ)UkADJXTuJ^ɖ6 @ıZ1|wRjgܝ2%´ANۇo  ld~AۺqG{glkm7NץR)vD=W˺μk-K)RI"R vc"Kq5^vVk !ܠ cTimRi OrF|$kֆhT4{ٹ`%SIq N(|5Y܃0V󺒈+hUV}bII!G^ fv/ L!P+tJ)Ř|cl%sϫfE82?B}@@@9e>ifmBs¶bΡsw-ha;b]WkmC8|h>_.q]R5u]9{هÚuXOƘ36lUJ2 CIq4O64Ruk㖝s77w|<r3Gt&x%̡ʜz3&mbR}5Y[kmBCc|^ºnpB!t)~jIҚB&HڝJH0c$=s={omA):= a{!FJo߽[. BkeYnv˲Ӑb2Fܽ;>}Jqm;n֌t,Lo-1cLIyIւƨeIB{ģ {[Zm$0 1F}8̼\+E!ei7q<??R(ZoF*IC`߆۝TrY%b0m6H92L j(%/}DB)I"@~Ehq~QEmKփt|+Ֆck}gmLYw K0!"n5sMy眒s .V|ۢ|M RALCцr֮?=qHR_CR\kVj+(% Ƙaij9ҺY1%ΡG)(c }7;f旲~E"Hg8FKD^QGè2z)8A!T^_ߘ {o'{7m)MT|jAHcm5.i}!*[Ce> %I`]N!笕6))VSU ӆC`YHվ?0.<onnZ ǔ.1|"VZ{{w e3Ƣ.H|2 N)6C*fOթ/r4ZtzJvZZk])`Sq@5S*@HJRn/reޞ:uwS))J^FלR{*oqQGqG-5{ג4tU }#;?J.8^.ڶ,RjqKamXZ}F iZk!\'v7O~_e78}R{#-Ƹ>|-HZiI'XkJv>ZsW7шx:}T `JXkE0|ztmM}NIVals_5 n aqYczB|&^kŦ@AY[%yv<oͿec_~xxJ<#8;km[nۿ}a+[Eg۶TwZ tKJ9)/rv[޾y)NŽY+#2Ipww)"W_}0vBn7{\֚qXK?z͹%öm17O~w8O-=k}H89PM I|YC!nۺDRdw Nݛp+ܖ߱aʵadzBX?~\~|}a`enSJraljcZ_nVJm))sMt(ڔV >BZ*SY.ΪODƎ{?ۺ㸛fcL̉QMWw%R8;3'4RD5ײmR vR Ȼ,Ku݇oчw~NyBZU uZ뮈Aq6euZqZwyi~Qe.4wS{Ry>4]-sZJR n-QO)xe뺦uF (ٙp7rRָ-JOl^\ݶ*c1v;1wۍrJBޖ̪twqՓv1__P=8ֺb~H!bX{3>}43N\JεMr IDAT7oX"/oӧ~7?ߋ9M/<ϵwڶ"I=<\RjcQJ:km)aTh-ȥ ̰R0F#1{P󒳵VHYrJ.n :ZnREmisƥa@ }41EH1k]e!\J))[kj6KkweY1mJJFk8 c&9FR2N29JB\|X0BR2BKnwJHA J+BZ$Fy!XVr 1R۶_Yk|,a\c):? TҪ,,`r)Њ)# JZk%xPdJ*Pxoon$x}ǔT7_c믿v,SAcZ 2aZkS΄VK1ĸlzjvAHnPnNPXm9eT>`6 ZKDB1m˲pC 16e*))ƻ;B8笚fi0ajfgu !@t(^`DQk?N޽[G_~t]4٧|n$%0(!br]s0#4SI}E Ǿ7_U|@Ƙ($qv 1oK)W4Rڶ]1M^(Y9xu\2B< \BICňbVAز1+B1%eY9c̰D۬-9벶m 9SɨwQb`{&h۴0b6YjaEaiRmͺ.v@0swuB`QJ\ض !lb;tp^ͿඹnxC9oF͒i m-*J)q>C7 eCWWW}{``?kO<%FR$Fo M)ẈRՂ޼~=Mw"x<t#s!Br!Tr_$JqcAiU$bi20בRs%c-z30.]}s>!8R”p&e5heRJiQQ?|A)4=86z911OgzZ$[E\\&$\P-s4mkMChUZ8ase cWRH1 s_"לKJ>>>gj9(i#Puf4MJ-xJxvüd9qEHͷ?3kL1 $Cqp(HyB˲ a$ fZ mLR*bbpmJʻwePPH9g⁅Xb֍RNiZRΧ0t!mh۾OO~UQ ZCTJs8}ss [_~;v'vv?<3&aiiTֺ;Fᰏ1q·z@!L!$B[Zyv؜]O>`LOOۺX:j׶#<;'$ݶ%j5 Tfa(`i6!lEt:cCەR ђ31ZQ8gR2!XRj\Jù½ dR!_iio;ڈyzhBi|:t} !Z8u;w J)I)\(%d50s*7M|Z3BCy$=<)0mz9c 381`fKUi=sDŽ?j#\m(ugئT)_YL0F?>WrR[CvŬmcRQNϿkͩdTr׷W:s1Zyk(CX,BgmPr&<|5 }N["ԫǻ"/:0!98 .춢Zl: {uT!x(϶mFө֪d,Ku)%d)bqMj!<ZRJ7 SN ju *λi)eTp3NC5WS2}\B"TTj-~weY9B7۶OO<}!pu{y_K\Ƽ@R2m c(?_\[)u}\`L1I)Z%Zm\R"Ƨܫ Bw\ H b)Z=ҁzZ)9gLBUb`J):eA)4 dcqmT\J Zq<B bNCc(ׯ^L譵Rr<cg|Cͧ i*çC9Dɶm<WsΨqA|:I .eY ۶NӨXSJY#<>1<'Bmq `9ejmdE GQb^(^ժqOsk|R-cDmkڦ1F!n!c49Ӊ2m1M4>||&F38)-T(T o+ 1ik!x[W6TJ#vcL-w97WRa,1լbTTj0F/;VU k̈́qt[_)'%$kvvیn@%u!TQclYE5zm~&im[ &8JD=nw()3vA*r ҜRJ\%XB*םѺiԵ *u<߇R?}O#) Bۚct6~Jɥ N~<>=dOO/m6eګ0N|^+UFPjBBH.!_y||a]i](e)5,2n@y0u]%PjɥpR3D K*}mS0!T)1n+Șs1F1P!6f%FA?.0ӼFk.vq<;Ln [7V>mRjzhJ)U7otM 4MbƘiuR!RJ}s6Wo\)'> ݮi:kW7keB(MɡvMNR@5M;_TP9OJ1S!yGaim+}Ma7I.yt5mNIH|>!D*M\1RqL)^y g# 5pfw cb)e)XAHA 3FT+Tw] FOA=z=#t秧Z+wRʛWo~ý\C?mӣ:t>#Ti~:>Nm`,l6Ysi:ޚDM>zXjoJO4MZ,|7=^aRk˹LR'%BH(E bBZc.Zi0Cj?<|xI[HYK)m˔/J6 !hC(ŵ䜀ORj JR*BاK1pƤdkWRJR㲮nEBpT<"I C0m_j!h`%"AdiwCQcIIn}׵Zm[TFgI׶;9gabBKQ)xmt~Q΅R\ .f[ngf[^E)kN:Z5;mM1i~s1C(93b)&B1$R 9k2!EJc\~axCJa p8ZTkuvn NGr)RfnRLi9 B֒ӺicBZ[+᜷]"kJ:UBF)F됲x:?iڶz>?aOOG$ğ'm6g%DtiČ2&OOOhF!DHA߲iJZfgܾ_1˶>~f:dpӴ r-1a7kT)W؍)`!(osΕ@:T;/|V PxUJ]%L M朠RJUL[` Eؔ sNP]LJt: mJgȹܴPj5a,Wĸ9I))ceJwgBJ;\i(ŧ7_~s 9 LҴR::>MwwiBQqIpaM 4$8OOG!1UBuC)8cu]AuTJ̹`cynyx<"BWWTFeǏߤKk}s[)pmM*@?ƔRNa@ќkAJ圗Z9P!iZRJ*%r 1Œs ޹* гJF6N1X+"BiZE.1 ^MӾzFN(]vrذSƬeu1MOLJTeݞ ZcB9%%$!,ySJaFFyBB5PMx]ʄ`4P]lBX*J딼s c U٭J(ʹJ0Bwg-p\YW)c`LC+16VR,99BԶ-bFtňIr$!\u1:gF{9I 1R &(x@w)G_J9*wֶkr4 54ǜSa:>A}J}Itð8Ts}z >Fw00}O[ۦ6\)5Buy!!sA*5|֮} iަ29V4A[7R@ι"QJ|8wB`ju5xSN&c̲,Bhf0Ow2ݚRʅ8Wx?1F|t)uYк29gLHc lgp'bBR)R* HRjPVR2P=TrbՔ9`pK!Fh /0AB饽/rY)R 9ΥB)]Jv{y *TJUZ1i཯5CZ bK!r]S ۿQK-\(?s! <-J?12b7LRb:kRҊ)`Np\Z0%1~!H!Bn1z!te+AkRFk])&-$8PJ)&=T+SJy5m">Z6-)]y71B{0벀jqQj(r. PRyYemR9!3־u%ඁjNb9WJ-{8y.%֊ VR咄D"Ryqv;\^/$hZkM2ZJwXn24duF7[ P[1 IDAT("TaԜsk1FY 1Kigu!s͙2%EϺ~ЈcRʠ9lsn yaBRwP¹(9)5bϩ¹?RJ./E"!۶a@;K)a Ozxӵ/ 2Fj Τsu]Z񀊪sn'0]Zԧ}ow*BxzDy1PcbUT A/Npf)„@J{G9#;/Y10ιHRʶm3x !jjb{O9Aհ\UpR@!/)%04մ Y { (@zqAX ֚r?˺M1Ɣ2)TL1Qp!0)i>;06Xkm>L'uHT߿himp8p!6mZg)\0#/sb4^taBI4eGt _bCt9\jJ`j ƘZZqPs40qdM)*@۵֚RPJ#eӚRp֣1pqa?;B8𒀅B䲕cJZk9 ;x&hqLf<1cdD.B9%$4XRGk̥@gM1R жǘr"C_臟ç|_|oBZ-նVP(Ƹ ns`E),5B ۖZ_ c"_FӁI1XB61&0ƴn =Bc@Tpup[0lJ.'Rʔ.8Hjz^n|@SF1ZmV{{ 'P 朜RJ^RRƉsd2 1t>J!SF㚢 _=~j.9~[cBt:>FN|י?45BݶG&Ԫ11,K4>A.&(%7'ȹjqs777!8vZmsPrEˣgBvZC*lh5$M x`qj2"¢"˂3 J\;O1L4LWWW_ m4m6Çob(e#B<[A r\yJ.;L~ט{g&1֒b}1`W w%|qk-,K?Vln;DI (cRA9"|'(6PeCZ5 oZJ)1%0 x%m~r3 񎔲.+%gD_ay!TQɄR8Җe3Ɯ e"̔ y^JB|3]9 !rNs@yڕzB0 ӉRzsssww/ N1p@N# 1@_k,\G¿?UC 4i51hSZ󋑫 <B.Eh[x%!DH<9KΉ11.ŔSJZ E:BZg3!2/Y6`C !J(Z0Zi|J71Fq|6m7< R2&`kFcLR=@2)5 z-zAB cZ+şkۦeƐcbRB Ԧm(!LJGpr<97Z.TJŔ`"T+ I3X)"> &ژZk16R7M"0 9O%C{@<`(+bJRJ9 %(eVش5\Aq6e0ƘT+֒r~Ɣ6J% e ;1)pctMjc1- j%Vbj 4_RJV9?Z6Д~x>B1*(@Aa9cZ8B.RRkQc hJ8!Cx z)#BJ !Fneq&sf5M㼻\s,VBsN v}Zi¬֊1QBd !@A1+saȆ)v`13eV!9 $R_jEm۬R*xp훛|#hK)#lrqBjK)5e vx?+/^ /G%Bm D:1 %Z04l !N6x"/?Ϋ!mEF1^ӑ1f1tɝ^ld/lKxߕR91L8zFX}S.|^{8ܶm.ڰK.BgpK眣w.޿@b0_G2Cj[s<\4O -93JumOYkwCv~t 懇Jt2Zz:"|jgm[KQ40p%Te$쀗 ԤXR1<1Tiyx6F3˵g=aF%ix!DBBpc@C2cRB .Xs@Zj<IkKc #,#e:k>s:quO1ض|«c41Ggo}:>oMk~o޾}W q1EE腒`Zc%Yk "8/Nuѷ+FʧtYWBTӶm3s/mS@XBk-%ga/}5cKYVB ,E/,C!4#Qsqɉ*l-Q%6bc}-7""]wwOﮮ1߿O)aTcI{[LJq]_׿{`L97S#XHHY Þ+9ZA<w$X J]7P [R3o}i?7q9!8s\ 81*mQJ )eքgBTmJr-%X03'2y&CʳMf]0RJLf}CI佻8Z2z?{1{8l͗N"¸"kCD_\Rw >b>,Q2K$È@}(`sNicʂ3-i K \b [Ԃ+:%q01ZP>{1LXa~!R_2j sj2OxZ[p{u}oR1BO?Ϥ֜#1L'6T2Q_}9?˺l^~?Z{_w]7sa2mu]0B"TY/Q=D1!`.%L/5$T+R2.+B) p\93&PEZ/e,pA;@VL E0bti1f,z1@hgP۶1d1.19Ng2ư(QV6.UrR\5C5g?d}Jn7༳9%4]5R.?\|~˦1Z+!8sq3tC~0 . Qf,zyJaC`\/w- QJRs)E!Df撣%gJi%,LL9/dΥnno!xh)$L9Qqd+ٕ .8ZEN01xWOm&H0dFi!aZf-40i;w>_}s<> I9U@||| }gOwr,ڤBqZ0"7:-9g7/J7־`wDIp~<]b9Wm۶)%0!uݠ.-S~YO߼ֿ~Z)gya] rMkJq~yЄF38g13}ɹZ.iT rJSF1ʄ`FLĐOGƨ.gC95MHc`BƈI)I)JZ ڦ)0&48 e9Z(֒S^U*("Bۮ/0.˺.b3s*ExHBF j-ȌSct]ijERr1nR9&aą!b!0L8{!x)cDqM*!ZRɄѻS):[R@\JI?J/szjΩXO'2B(Dajf %b mV*BE(EPJ^K)RK(X)!% I-B{b=xZ8g1B s1A>k(!ɧcqB=Ωݿ 4ufY BL1yp`Zrypͺ9]ZK *ކB9*x6|F'Ժ:!Eu" ub}4AbR|&pB8] rNM @(u1i1z]/Bk)R.jZ`b (e!d.幠VVBc,%UJau޺+%UJ K)i8J!j)R!ծMӌkQJ. Ӷ_.h@eRr)w6⪔J1)NOODm^\-Pc aS P]z{{Kq1*riƄa?=_u]d^FN(KZjA!+B!a ׇ7] ZU1Bx.ֲm6vBp(ÆsYE6JƱQ ̚&%M{냿dR_YA JRN8>x#8J+hmڶEYklu !gF$ebwb<_z-Jޝg!ďon?߯oVO0W7M;M ih2/ثeY5]!bX#BRN)D.BJ4- Y)?k,_wiW"9>J.1s`K'eY*RwUU3ƴ1˲)s.gm8WBȪR |9WvWuU trڴݦzcla{TUI!b ͅDDiݛ_?|=Z1A05ΪZ2FJIcͺL?|ݨݯ_9on0kUm9Ͷ/=ʹy,+ctYWsmu\bʮv믿MePB en_~Cf/miYui.$UCe5'\B=WJuYsBp\<}uu;i!!1Moz/fY)F2\=JNּo;ϐTR0v8Ѫ94bJ)# HCͶi-DeuPRZuٞ'`r/R10΀onobײWQ}%61qJmoPibȻx?>>!P.gB)Sy8KJ:OOOM-w>:#ș^);%X)䊜rig"/ r>`kl mSΔ۔B)ժv3> tܼ1߽7oMӰ bR ~S >TLu1XɇDnRtΥxJSֺq=c\2VMWWQjq.RMFnjdI.)申~w6QD֬BcB( WnMʨnD/umםR2е/OOOUUvuq8 !_~4Mi0Ɔq !zy:1Ti1K_ˏO}RJ"P tVp#OgRF0S k&xOl|)Ɵ^_ޓ< !/two>x:~^B"d؅fq2fUJRLU"8iƱc%y>D9O^]7?ڮbr -,#\nv^Ou/_4KI>~4+㜣3{!Qںৢ`Q.PB۶!P|_Zn7._}/~CfR0e1iݦR9gR:iﴵF]]_0o_~y{0#w|~){YQq1 S6\F,/^QJyoތ77Vɸf'_H1~/UULq;UWu]Uy\kms>+k"DRJwao>ĄB\1cZһB |5m]C&<>XeӨo~ ﷢nB9yQV[cb(纮z㫪**jM}ugqPYkiqCw!Z}[^|7ͫwJ)f8sRz<B~!>~ ,#4 C]?|u|n6}[`2cNC}8o%KY%ԶSNe]9s)1 +0H)O}ݹd۴a/nfscb貏 !t>d~M1`B/6۫Ǐ]y1̗ZkJrSx)tN(g%h YB=j=!6XC)g&0mRvu F/Ueeb!TN`)i2z#hL9 )QOOO%um۽xp:mKxAK 8ڴ]_W~_mUUovۗ_ NYUݕއڎy8/b3Be1 Rm2JuusEb"1*V$wWW%s !`cPJNӜ JNTUU۵}eK(֫fINi~|^j1RRy>@åu,WRgSL)ɺ)”Xkۦ(1(ҭ˥T΄˅2\ :t+Bhm_>jvo!ks훦eontMAX)͆q#D *W5o]H%qXׅpgL㢵UV.cF)$,$H S@RU577[zBI|RI!ԦWc16mjj5U]B4u]9'΋zҠeuJ"ݢJ`)vHP9keG 8c* 2t>u]#pNJ~e )5˷lgInai !P%RT"9Z勛BurU?GQDg.ho1(úV޻&Լ\cR{FE `2Js@VUZK9uSL!p8xr`T!Fl%^r1|ss=#SU1TYUW7ͦ;az%J%bzY20`e.GNvRil6eJ C.M_|B`ҶmLŗueq&T !|4vc7L30B1jȺ ?><Md̩*11)[89gmLJ(Tr^0`Y᪩y!"nn^pbLp(hb ev= TOڶ>t]R(s0>T^ 1=yq9XFuJKg TC!B3e PRւsUW\2)2RJ>|N(pr olrO9WM+cv΅uLnhCdA5o۶mCN3Ylj%u]%cA5gO(|LaoRJZEk/a\N,b!Hɖe-\kcA )Ք[Rj۶RU*[_KlT+󠱾\` ?QBd \$)WyrU[TIה |>)% ɋ(~irιr~_uц@(\WWco|Zp:xI1qNv=c༷Jyc,蜯j]Wj x/EfV)E;Y\QrUKu) cyZLJd uSS 4މv!ks.XU*x8S.d@C ,TЩmۖ[g7M̅Rs! !c)g ;Rnq,u]9UHJY׵j|ڔdi{Z8o()dyO0fɪB1!E"5iL[B [ |ӧڽt3aX.eкVEvT Bht:K)YcS BHU[1Pl#U_{s*Tk%x׵sI9i1.xSSWE\ERQ19glYs2 )QZK) ާ˒]091RJ(@JIq1g$aH)Fy޽{'i R(Y*)g[:,s9/:'38.D!Uv!^ay9g\0\]_<==UqB(uՅ Ec<\^a.so]gIE);_Foʊ΅&r>cĔP|ELp11_1YgMٕB{kmI)E9+Z|@C X DyōR&셜`\g[W c!Fkg[" bC83\YE1ArfR /auMyI$R!Nsӵ4>dWs0!{8[Z;^\=?+v}וŘRƀP8XE!O<\.!$QJBD.$`rJU]wu)Vy_I~Fpc)%   ѹ"Ƅs"` EQ.RF{.,ꪍ)asST5! ƘxOJ}@Q4SJ(M2*JW-v t˲8zNX%TJC(B1Fn'54 # HLe¥ yW2 SUK3!f1i\ MӖe gI)c]W[է1oz%KXUUmm61".UyC ͳTUu!h?Pͻq<]RʟWp:(PJ a9geRH"SDdS*_d(xmŬRmL ckƙ ^kS:!E VMDƘ޿ﴳnieMj#L)s_ yJi&P&J`L C)')9ǘ@9b"1RZ1Ƅ Xk`r΂ :/Ji]ں )%}P"su] dj6JPXΨx;e)&<h1$X,.e;,@b(gE% Y(!9SFj(˝@?m"3cQ!1J)JZ!Q3g,cBrJ"!0CJ1`(uu]ex\$06{/rbLi+F)`0y=!guh~BXg)8eFcp-BH](M]HT]UjYY1H.8e]9X]UmוhC(JO:k+iѬsatc!rI1"P1!4#9@1v.))眲" Uՠl+t^1n>Pr.J۲gƔBkc*NNr*&Uck\G0啠p^E*U6BJp~'Xo1;"nE|۵PPF (! @~eUӄkŜw0b Q`Ƙmiiu{/e3T7B`<cj2Rʅsy^Sψ>*2'VnJ cTȪ7O#`Sbڶ)8E]c{?VO,Zù(bRBeĬR\t9gJ&PB|q!#~2mL}J4Mж뺚eqz. BO1Bny./k%c<ڶv}uw,ô7w4!TIJ&o۔s qa4NmVRX!SL|)%g/wF6fy֖É>k۶ly1MK)\pEUuN y(چ=!0rRe%5cZ@1j![b2#DIY|y+Pm  N9TEA9`}@"qΥ@iT]2˄? !ֵ1 ֚謔|s.B GÓݛ/hb󓵖JNisP,yhdM@tUUu]]KǹieR.tڄsLr>;m8}#ޅ\`%SxNR2b4DaDzq00c8 s  aWo09c3c)80^UUQʩRи"zhNm*}řw!PB u1B8c4},5&@`]FБsޖscX Yjficc‚7)ƒG5zKsX@b+КJ3Sq-Acu,0KA6CŸf ڧ(er[Be J)#T(!dAiNBÙ$q)1cyu-0kǦicQr@*Ƙ1I*n]9,G>ˉ F)uƈV1F(<5-ecŘ2)O {3BlB!PJ}@8M)pqUUU&,!^Bz9IENDB`mobf_core-2.5.1/barn/textures/barn_3d_empty_side.png000066400000000000000000000505351264104133000225210ustar00rootroot00000000000000PNG  IHDRg-bKGD pHYs  tIME9$tEXtCommentCreated with GIMPW IDATxĽˏl[̽yUWewh6m;Hv@H4;n @opdus#=4V̕3ʊ(V.)w"Xk1_R2MEQGJ 0 ݎinRӷ*Hep@ʲ@ky{7/nn(ꊶm :3cp!UpO.??iB{=?8 cYt]B޿O]HGI7\6Gr194܄#4949Dl[!X#( nnn@a88"D 0o,`rB ðKqZk9=?c98x =#B8ra!2BWzyaThW_RJʲ$jr%Rb( 4[,|Y0 dYɴ^%83 !,?u.t9IJe!%XLi<&RJ-^k|t]͈$s=J)veYB@kMQ( 7%_87rM F=ey4!$˲Z^`kdY,Ԯ8 Uo84MCXvB0MBH<7ϥ8C]{Q)p\|7!N?Pkbu}wc{}}M۶Heh[%}bA)5d˲\ufYv<czZF7D/oF (y&BLDV(g>̚ 01)%u]/G0KIUU(|ir!!<*1ƬRѶ?ctahq OǙZc0i1ZiǑ?&smgBJ>3YgZg~_J䳳:h^#Բ<{O`fa>,{O۶Xk麎ar7ΆUBG_nirSW\jF@4Xê* 20:K](MQU8H2#7X+!ϵKqF+u.ryDa<!,˨jOuBϒG#Mv/y_;ryaN0 !,`\#{a!samۅ89WWW"Myxשּ})g ! _J?W~ _е#a-6tMK]VL8{م' Ӆ{B!/~j^sLޭi$yȴ9Ԕ(F[R} i߷4A_8@ :Ғ[Zc_Bi4 =n8okaFå8'g) Os0 %qy49KbWSuO i 8|* 2gS8iڴ4]нgX{_15`Ϻ^J3u"cjߐԤG>0>QNUR41v Θzs1eόK VU_8&#,ɿg<^"\{.0L}0 uMQ[@SMx)OE1*4Fk!`L!%p,Fxaxjjaւ4q}}:SqQztbvdFZ8JN%9gZ<iԿD0LMiJt+_YT/H!2k5ŘE;>% S-39}(c^/xNM<;&b:88cd\򎪪V<kD`%|Sap4Ͳɪǘshu:quLΣLԆa0y8PͲ @'ҸɒlM&X(K5R RPIYA@ #EU-3X)q9^*Z(fr qBϵ%2ajH12shK i@f;8S7h s1./5/% ?'b]I)w* Ӷµ,%84[k1mnK ?*VMCi3f۬~,fGBInF5i3]kO&8ף/% H5hʵT?*KWRXg--JcuFOȹeTWZ#4\\i\}zBgF3T\{y)4 uJ9N+":1Y;"KՔD\XYOSn ι8%({[k`$@*ϛF(:ZKX;xƱ'1 & .=0L㊖2BI"g&_:G hvRp1)5Zg# h>xZMipwjykg:=^4`L"[#a(|t*`bnӛ4r+ij7_~I 3 m2 e2sa?᎝RA@P9U]CB+l?+.:OߘRJOB <_v{T[@YYK74l6svGs/~-_0q1ÿK1Y4Yng7?6zȸO( Adsk>86L`clOseԳ>%QLO6-zNjqZ\ uUq}]tt@gX)}%4H57fJe}w6Fq2Ox" qWT3c^=+ڀ QZ3IrvEM]GpyL2%%%R%ϬxjӔҺZdY0L@^dE7o)9 $BbK]Y*PJ{ 9wX!CGͻp, d-`!X7˔[P g妁S*B wza'RH%Nण< u8Y4Y҈0>8( ޼ȴ8l2 tahEeyO7:f?~a ƟSipӄnhAjL$-dB|cHAhǞ`-A 5y8@^L)ŗe3ZO^* (@k\? ۺdRR %+ x|TmqAp?7g^0)q1(b&grX'J$H4Z+TfH3 S9o,JaO(iN%ˎtcH  "p**/11Frc&NWa /KֳU <)ʌP2dy7H$BI4pʳfja|RJ|ɲ?G5JkPe i2!0Y3ۛ[D~EF޼{w7ԛ+MIL !/"F( E1%*h"e /sLccqsEȽYliOZ!_J~N!VKG2s$#g99%7蛆9Lc`tWGo9eyv;{c<M}>4&nw+F4IYf ͻ7")r77 %8O+uDt:)!B+šrCYl65C2T<'jN7 ~c8Qq|*uQ|:'⍠v>з YȌAJ0dJ1\@ (%p.5w{2fGڃɁ`23%~n,JϦ:n"?:)# E~}+zj!-E}Md遻ȋ9% /kr#P!ݷ q%My0 5&3YaF! 8g9C58ZM3͵1Y#5_Rc;%H^-FOΩ75c!mR`Lӿwo'ơ=S펾_,s04 E8O'4L qif;@pΓ9ZH{gqBR9Ivݽ?c\3!r IDATbK jT20J B@`t?5䅡Gݎ٣B+,? \0ܓ lJq@)Ъd쳆TL= q9,< PQ~x嗼[a6:7sL-(, ޲1MӁs Qeql)̟3UTSovWg>(2t^E׿" %q@9Y[dc[wğH}tvI95#Kpk+1tc{JX0 !bsqlVFR/@5͆"+tR4ǣ#^S#Rź\'IZ/X*SbqjXxQ1 )1ԥ8ӹw1XQulza|E?MQy֪u*`Ȍ»i!0&䒯YUOm׌۩瓮8"g͝=>%}SM5}KqfYDK=gb\Z_]1o٤K?ռF^2:cCeIR4t4ȹ_b8B161S?m6OMIъ.0JFQ=4ђIMӴć\WFi}yO7-q.j/F!`rK]1eEQ<6 eYZQGg $EU2L#8,s&Ϩ`=頻 Rjڶ_}懭 B(Bi f@vluI°P5y#lwWq*>~HUx7G-KNxyhA߻p)SiS&@K|$x1ihG3(촯%wI3׌&/0Yu8c%1 e<~\qpf56lՏmJv8; `<EbazEAơn q΢) .u.Fs9֫Av}7:noo !PURfh=/*UxCcNSzU#̪/mG¬~x[L fCUUu80w7Fn#N9Pbcݪ~%8|nw\y8c@xq|҇y)aN:p,!y\JHG$vK)E{4,c뒹xe}E(r_R咱IRf~E✎YY}p8pطBpX;9ܘMi̜N^Qyf<tAL?eG\F?=0 ׷`|svui'%}0ZZz:0yFHJ_Sn*ZXVŪ8Fy<7T K T0ZM(I_TD1{zvBjjnݪ~8g8pê\m<3t#Fey5G/! vf[QE-YZ{A. ~/؍=iBy498g׎S@0<@XF*-O:1UTTz48TUf}./:#SQ%^.s̩vIw1I]?}"0jF1a lYadRX6֧J]gLv5̹#Ouզ~j\xxmۙ0'7ܨŲ:t+;.ȂDIeg2 U?Lאgp^\{J=#PJIfc?r'qtbe{O5QX<JS9SV.gZ 9svȗ OGO"G3tL|?ke^3U}#n! tR0] )vwhK*f44Ap1?0MZ pLcOFq|e8׷@gR0vyNxO,a4tq~BYq8WL F?hk:>4؎^_<8G WW< cw;~ɅHR,GV;UJSnW-իuS99/ ~q)W=.\&^+J=}eXaUUTULdleGG}c&Z6^[&4shяRޭ| N/s0ƴ1S0f>N әrQWf}cVbN{=|K/-+ʳ]S3Z7|.Ƨ O!d}mB%ʙ o]ٽ;3#/Ϥ!B)cIg{^^s> {Ÿ-*/% c/-F'>j@H DE&{)ƺ˱(ȶ~i`#c1a\1v Tqm4f#8n4>#uHHnjʚVeYRUJW?cRl6eʗ1Bmۥg$4Y 4GJS8!0YVp,<4 FzKq6MC nvVD1x% 1w#axh ooAGafzVֻ⢟wDIL*{O)BTnHkS_bL}xzNӄ~)asQ02l0F+(Q1tsFREiLgŘ` 0F~1Ab"3R4:Ms0$fɤ:'tצXՊ\3 Wo8c*b ڶEj$ Ù̗p8h^c j^28KoK _>kDL F99B֍ C CGiTp`lLǭ-R<ϑ :LcOQ7P?Gқ -.y:zM x8vE1nÒa.45aPڻq plWB#X,9Pjٙq7u)j}ƔUJߔ0TdUUl@zzwwc]Q-i:tg:8?p Z;t.hb\rȿ04 a YN"6&eZ1MsYV7_~I 3 m2 eXH CTuqԟxW?v7N ZiGR`-X 'kܒzT%Za%؞۷;vO~o?\3QYÿK1Y4Yng7?6zȸO( Adwlo(2aG-s05 "4 <+%a)Z=kyw-zW@ ]] MNzVVP`'8خ)6a /5BnxG8gqbZdY񽸣\872M4NX76u8tQny#5{@d۔J xh*&.(Km\bFK<=}B`GC&JlANLJL3!0a2w=iAunZ$Jk&0, gx7B PJw92.gd{8eNgFj)&+ }Oyx0!z$[z4BP=}`Ro I|x://c2 E-`!X7 Ϸa2xifi )$Y8g ](Rizlq>)aWfڅo9C2AigJU #F T>`  )Y^2<8ⳟ&ReEYY69}s dՎ07 -Wo@< DP2C?M(13Yw1SJa$0*~Ov,[KtH%;8N@_f))Y?|5E!I `L'7MwTsȻwR<W}$q+[O'"'`\iT#()0Z#B!YY@Ի+0΁ʉ:ҮKơ!&빾Ƙ}2%AHqэqlZ WWiȫ;p|0ZLH%Nण< u8Y4Y҈ Rc:͛q@Lkь-p@Vl/x8Z07'% dZW?sO4ᥡz4A(34Rz%&'XKFhM9laQ`C8 E{c9(i8K`YKm]2N)Z)Dے[vYT<>Q*ȶ y樷 &%.9z( &grcFPH$y 4 G0BΛ'˩w ?ɒel{e1(0f>,'4ZH|x#/*,QU,Lp֢\肴VqhQ51zV((+n_}. 6-Fph躎7>!u~ ZD 0FkgJYkXdfxyޟ1RIu34z4?n׏h&G!AĹg1Z4M3%a OS+Θn7Wql!N4=nv0'Oi (St0YP~ AP[ΨV߈!*~I*; i`:3L=w|_е>|idI#9_lT 8fsÌNz=z =FG]tpD!%XtM6`]];Cߡ[#'?g>4 Ӏ}H~:__wu]!Be-;j)1D)V(Jʺº)C%=yzwALDYC*!Yn6d=j`J rfi fe( @(ISh mpjIDATs fa&wuu7gXi3nlB33UU}#.@^h!o\.W׎ͻw|SH304ۆoZgj}o2Lv[7ݱR#SyAw-A]ՄJqzP*JxTw|Yc* CH)mdD Um|%u9Q^D˝G<3[K 6] ~X6 %X/<(c1u.N/ܞ) 8?k~5[crb)&O.i퉱{a/Snn$zzib:7s]3ưmjB]()$k#%X]W%7y؛_OtZw'/^UUN`awsQ(?2 8~{^>d? prG)`->xj+eYm*ʺ4~V׬w>X)ˊq+JkLa(Ӓ4N\C@ priC a<(mM`C ?}~~ÇEOG*S]ʎ:^`mC q 4@ip=΢C>RGH*;Lטq&@z]JzHY ۷g6[ʪ)x1H<>>y1 $R m+Nla ?2N[:q(uf*}:ʺhI?y4[DHaXƒX;fipQ;𞗗'MAc,)WQk#OOOבay|ԩ/:`xr1b"-J+ÿDd點0fs{aBI~H;<ҟu6 "Jv\p^hBȦPH!A&g]SCYa9 Z). |ȱ03ZlkÕx40{ #דS }}O/UYD10#o8`risGS2 !@?Xfm{p$R3M-jSu>i;~oO3@Z-g ' Cj 3R0PPk,˒'>儒|׀aJHրaX,ޗt=L{f а1u@ (@J޼EHEp9>q\>4 8#5Oe -=G a՝h5˲d /+q(K7Pmv!xGS(`8vv=m;u#v}Ӕ8J!fǶi$E1ϲ2̙̓XWM M`qS㛯^r%\!|~As!*M=i{!E<ϙnRN1z6( u W{Vw{Eq'E./PK|&4foafjvB\w5{nZT _{@!)Ze9bF-Mת[ Rv]x"Ek*q]RL,jSU٘qƐ^j9?6wcp6ȼ^C_we6BLtߋ,ŝfථ>%v)?[2irjNŸuG#Rij]M@J. 0Y7|ۤKoߠr0 Cn/ۦneB-^y!(t㲡w}+y7'ұwlp3۸2Mf?Qa9aH/N>J)1 gK~t|71sp{˧OY'y5% Es5&Ii$6b4ǔE'3Y7SEr at""\+$=Lbs҂]S&J#(;}2vOf s9dhckuqL3޴gtePx=*{Oow'y9,3) p8. C](ݖ׳H3L3C6x>ܲp.ڶ<>lRS,oݵ֚{ N®Rqvw'G=$cIA㦰5 K͍TRXȗk3լ+ ki]u]50 q/B ,ᜋA=Lg )>GNIRi.5VvO\u+:nPJQ5lɣAV|i\g=EsoY|wyݞpbWJHO%[.]Zsb2a3UU_r,I'?bȺ91Xk2}'.L~9y:Ӄ/MC3]! +*s!KZj}ŭQ- WN=u~a^iײqTC]K5m|9}3 ,n=MY.-6w@cJ"+4ϩq-A:JW"2w%m[a?rfQIENDB`mobf_core-2.5.1/barn/textures/barn_3d_empty_top.png000066400000000000000000001301111264104133000223640ustar00rootroot00000000000000PNG  IHDRg-sRGB pHYs  tIME (HUtEXtCommentCreated with GIMPW IDATxڴImY[n>m^YYeWC\X ȞB&  f4$bbYBKBTVVe23E};nW`#kRx9{7?R]GJIUUeIJ1F#RJcZR{H3\Op3`R1 $eYs}}0"MEn$ 0L(ePJ!$|//H!HJaaU'ѬV 1Cp>|d#(1!nH)ZkXkk{Ѹ_GRhp:0F!ZK? JB@5֚aP@J,ͯ]{?ܿKw)p80O?Hͷ)o#,K"_3n*!8:޼!ZKxxx@Hs#{<=?Q%78)` l\ұg~;uMz9\jНAIΧ4Q6 e~`|Vh1m[#BDks)1JIT}L `9(R F$|Zc*ڶ:F)rӉHt)%MPQJ{|owEq8>P߿'88RLk6-⺪֢o_4 >R RJv.#Zk^:;޼{qmS%τ09OYi馑~hY!rBl$RJ/R`'|t(L%D%i]2 UUpssErR`%Ƙc6@|صq SXR4M1"{=D"p8kV)%|槿(eH%-WW->ܳ^Xk]5xƎ|ǫ7o9=18>1 MLPK~<y*Ǒ7.+\]#>x:1z aBp{{aR\~k j>Ty>G8X[s&>Éi8sleGՊwUUm6x[6ScֲZ*FH?qpn{Ru4!7XhKr۶H@8ø/wDU?메bZQ5~S҆sq{燯pS`9yg?]CO{bwa#1Fg_Uַy~z@n%m{D^EkMZL{l6J^p:^oPR!$q꺢}G^~M{GxqΡc e{GkMQtJ 4f) OT}ɸ7+q$%PR$c 1 FpS@<j)f.F)12&޾ƻ:Ƹz!^]{7M(@kKQ|\\3N,9cY#J͖'P4 8xGQ&V+,8[bLpbwLHcn端>d,^amt@i<) b:s۽xi6T%[jE1co1nr9㏾ DYKj%Ųޠ{xF#`z[!x||׼ȩ񑪮x|@۶hm=Z~*K)y1?g%'ѱ^npm4ehblnAw}a_v&PD$!egOl֔ )RkRH0E  ;q)0t= (c0V#hTY2±O] RB>x(-JDB=8(risvN|cDjCQkh=t-3(j63sYUO^/q@J$~Dr>xNHeV[UE8)d<8 eUTDh6h(xcLLn;w^3 ) @à }?2M#BąZQRӵ=0DJ_>3T"-,J s$)D24TUA ֔hCJhSQ%1&.O/V-$4/4Mt:)J+V"D>]dj1Zu((TMRH4Rϯ/{) &J"$Twos>~wwJ9Nx0!&<cۢ}li A qf)IZSì=(B!DYv2 RB6Oa RR$@LnF)(ƘyQ|c m5JkR38SʇH>rXc N߶ڠXx xx؋Rb$EV$2f@a8bቲzuҊUU ` 00i*+ /Ҋ1:,R(%PF!%QPJUa03VJm4}ߑB$J 7cnf{EvI17u4ի *j5c?bZjFST+R\%Ji!0eR"EP!%1b03P:{"&! .}"%q>FL9o[A"Z  Fs, ږزJbJLÈ&~йٟ'8Dqin?0%1y1bFj4DHČ I (bs͸xzѸ3vx'cmIC(%Ɖҙlw7v;H~r$}_ǩf3?@~@ 4iC ?BU( Lpza*ƾG*o|1|Ə qӀ4(lZ@׶E$q$ I]Z׷V*iV50PW Q?L`k1X)Z R*ؓR2$)fBUUb< 4KBd@(iqnx:1 LesOYHbnAXJ1 =y ;c\p/A[)B~9P)% _n4e!82\8Ɣ O )ܯJ}1,YQqa(˂0R3M#F9)#BI6 %ZiOyh[5ySz>Y,?JeŐ$D/xq.+SjlYНNw_O*14٘W4_|{sOzjU3eY8Zi@b C!iBgP.N-3.g,98'e>uhef{'K:¯I}J% d]it(e(z1 %df bL/˒i(HE`Z}mk~y2'LLÇ"(QFgnVD?P(ZQ}.ڒqUML.!eܓBEP*eNv16q6KDm !ɬ\v .!fSR.("aFK~hжgRT?V8X_]ўf֘a32g=H=X* !DOhmRBdGahQJB@YTD)%:lQH+gWZCgE[c!vH56!a98b^2o~= "SϿ;[,NOlwW>`z꺤jTYL g7 qDHB3BC+drTVZa@Д6sB@*ᰧ*lQdi\4~D kC.,8x@~3asu=֒fV`\t^|MIhZ"#i)Ef@V[bB EUy z&< 8N(l֚ua9Xce?\R4s C~UJQT%Jb^>X.vGb.L޵tm;OҚȫoFP< $ZiwLC2 U`A())|řqUQ b)Y"RB=?nk) d衛x|@d~Zu1% "c qJ3AKU"F,7qq'XRZQ-^,( EVu\RqZ*[2YX\ﶄHVJ)1yBJx_̟͞T{Q&#R\_ǜS+i0|)ͺ^UsÍ~NUSڬf( ,?  tAZ#D愇a`!Dp-8(3,1ƼAC R" QU5}עtGHq9N(ǹ=ڤ59v<:ܘ{q#U1@ (l}@ ~;* C#&)3BpXJeå,~s^h|my ҢMY-EHHRAJ͊"ZY̮a^hbo|1ڥSʥ 7R ''D>Pa>>|@LdEVe ]-J'.2iZ38f4eY"(1 |H!2 -8Xci\a[VvYYr:ޱ,kbsUWHg沬+lwh :T_ /;jo4Ka۰mM,_}4ד(J) E$U: $`%Ā&/&3C?R?LEْRX#͖=&0!.%HilY3t-JIڶ]nnL- CReO?0 ` YÇ%3yyxz$Gpġmd׏;gZnj>՜9ӬL=cZHR`+0l9'F1ɒ"!82p`suCYuH3` #mcgKmHnL IDAT"0N##uU dbϲm 9 z(zvw\B .0xih 4Vu}U9G]Yu+Ia5Ɨt`m)ƌ]{ˍvq: _K>IŪH1/KӶ-ۛ+RJy}Z $ePyZa ,9_Ow~L.g3 Qvϛ rLı0Fgg]5݆3uU5B "2y%YQ4͚z9liƫ0 |dYu3e\pn~h\Ǒ0C094(v_cag ^eYp>$" {@e?^7<>>. =eIQU .Zߟy~~&HU4M3oZ-HA^m{ֻQes9㞹 rfiF+D0903Vdzk[LQuŻGϐX]bDآ$DhRa@DZՎ=#eYp8G( j BgXh!OSU)?x/}}|Nҝ;-]#$"T{6C4:Yo=֔T EQn6\ZZK۶ @xzzZ 1~ >p<Zlp!z\nE|/S.jީ=WWW8疁V_ %H3^~K飼vVnyut?aIIps#n8aYmMQlRjEi̠tmE]737,f d#nP:B|\ʪPzcxz&&[S!8ցzW_}v:ܾzMןC+~0BáEM)xسxر]]stƚ,]1~e4x4*/FmȺ(8Obʖlor{[onf{]UIa B*[2⫯bm(,Mr5]?"mIY>!#EQ=*Xmf-k3xK'_yົfe e] KXmXV<== $4<(f<7o;RR?[mqv糂hG6 DxRdz\ OEy}uvb4.&jl'%Ohh䜣JDfgՐŻi6;]xH0 <{ۚ+$ƞOv蹾%*I,_nX3lSbFv={>Liۖ>dWٌJqb..eNyUUWNkv]p{nnn('6C)4-Sw(- 7)펺P|_ljFʀAYWbi.>KEif1"q&R^5L#Ha Uix{]?{GW2 16_[ₙf0z@KKx̓iQFRx|{X5 )Ϙ⇖i},MK=Bg@iK YA?̒^p8f.whG[VEjaY$%T\B߿_ˌ2gr>~v7mבHK(}z.BUUK4+>裬䙏-7}Gw(!x^vW_[֛O{G۞8GOʲi9hOτlr*zY$t_> Y&$!^\j͊ǯЅe&ie5GGԻ0g=y/9c{Z3z S>V͊0p?sq}})$E]QX'71<- f`Q4&K6/wKYmy~J,D)]_}j^#&LÀy*캎&4b5ؽt2]a«cj+Q.Du3<GQUK};BBJAUkUB~zR ^3VImc榯8OPvZ# Xj,y~K~~13u 5E_9M L{n Ļw>R"+b&3Ea*nVyRɗᠬlyV/Aib*9>?)y!Dhkq?}~38w=Zp(3L[J$V OO cL>3("u8eK.s=ϡc;,Sa hd8yP!e.t#g(!0e6mWpMT]V70E S"H)V9n9-]w^VU0ZW$i۞ҊE;N,M<>>3o43?3%9ĪbϾG' DYl`5ZlVv $@H10MFy_y詚 Ɂ.4kaE1Lc@0sL2eD&IR%)-(dJL1Y6N}VMFQe* 3X 0v^;jjŭHם\K*)%>%bpӱI]U@)EUivr3JJcm<̓- bI:C>eY=ef\Lu^qǎ80N#EY"s,BI6M) #a^k*Su"Z(1oȼݮ2$0EJ3Goe$^$Ęћ[||,YV?4 !%|X?yH( IE)˸!"ݼ8r0IdMFj %%1cHPͪPR" JY'DL*c$8O2ОN Mg5w 1ˤDE,.8>X(,ńOs,V3:?y'CudM-5qa۱~{Ö5\3Oœ` ɪ!Ơq]Gj8$+\sE#9NfW5Pc,]7%o׆j{=o#}*rOYkm) [/@?EE7Et٬W?)u=n\nRJF)+DT}q{zM{li)n޿ϛ7oDt}%5xX[ {(Kj>=~*0N\g%w#0o~_oRr\p,0{&޾b^S%üܧhhpVLL.En6WٴeA*tfG+ucR!JE‹"]?9RoP٠̼jOE%?)9O<>=wVk:S|,׷?!Ǎ7oBQ%Cb{*WL.6F#wh`;@1PUyG^A9ݟ02#uM,=5/~nr9 cH#RX\򧉯~Ӽ@S^pb"ν.rP.ܽ#}^a0{M5#FdiLքZ\dGaHF[A ,I6#HDrw'8@f~%ww{_heaHxCtYb g@1-U}¹(/8z"JF.W2,̄6b,Q& aa40(%bƁa4iO,ϱ֐bIRknd;Uk-q3Y_B gg15>Aԗ|vLyNaŒUJ\^buUQC8*6 (3=Zlq`{E2BwI CNeU$jzߌb+`e:qIɋ\QQ`Fg^^< [QLȌ4IՁqfDEaM*Zu= e-n1N Y*[kHXiM_wՀ ||_-6wB-7INJӝah;0"s!z,D&9|8N S* ٔ+w{"cU&G0,Qةe2mp<=TvWKR%Q[4MhQ8BjƢ,EW7qt}G; &0Ty}+@ټ$b~E}U=Y\ IJv\k<և(eZ1ar`bn Ņ7Qp:ێ_" c^޼~Wy9,!uԓ'O8NeN}>+u$"-7Ɛxi r˾yO -M:Ћd;rEM>ʖx<2K1n9v]ӧ ?IOYyoH0o෪k{gy ?tD':jaH84ǵF:?nj*Q,P X0Ñ@αH44cO eՃG|ٮq8qyyAF/{;)=Ad2>XzdZ;QUղ"u~~8 Co}.za&X90Ӕ8yX_jU2ݢ4>N ɓ'CߏQ >%IB<_ rlfD}>J2V㢙d%ُk HT12p%OW+ }Бg#noo9bbr 0 O>M;r}{zL%=Y͵⮛MqOI7tb[@CƀI1 @مж2:iY%#Y&kP%9ZHY6dd S˵'VP*elW8MzQO:щd*`JQ玦g<K@LSķ?ۻ;(;2>!:"4+,)p8LsyyGW%fڌx<2 3OZzqqVۘ3M "f3YoZH}Pg+"u*W+^ZN4M-Hm*q0~bx u}C 7" qVnITLd&֎im?$"P ~\C署v륞ȳ8f8`l[ZX0I"Ld,o?,qxÇMf ÀfC^$"4.}l 5e~f%,>E9ic,=A"-ۖjf`0`A@Y˜qSuh ]JE pUf!2Ra hn# WC|XxM0²KҔ$PJ)m ӌx{Ow]^z%_Bqy!/xFAeYzғy(&|+@F>.x4K-1IHl?-L۬ZcTpͺ5S(+7?䁏2z Xq_~%71,ba4ȀNqTQQgї?qN-|By Zh$YsYy忀0y%Ǐ3o-&i*_ )BivZsX+X۴T|@R`۾| 3wFF>0(JZ_Q;!IsxZel1׬oxQ3C%k9'M}/.a +r|M߶20Kqa:AGSZ%#$ 5J| a@pAi1 Ќni2i'Q%~m )W+-88ZFiDZCYdKjEhY0לqdFbOͿoR\J)Ngqqa!9ﭐ? "V"0h-q ʇ_wm%¬gey4F'4@^a~s&dx?Yk-\"xD,oHCo! 5e7#ILTdA@$8L}G*ZINE& q^IFdybu" 劚<&LPPGr:̧FQA,,Z2v0,˷N!Hܘ'{p>y͉sDZSH@$$VX;l2`2ʲ sf|4-Q6MSh%IqDm: 2Ҵl,Ck艚ښ ݳ}>u$bsuk96[$iE-ʇÁzz?oM9yɘn i=,uL .R~3jYa8 P @۴Dʽs[Bn2IJQ82 IDAT m#! xls8TmZ:2ohgcr:Y^>P{3~0/m>z ӛO8 n|1/tㄲ0L8 +Rdq^]7PBJfͧE[| >eN]*K ,+u(A7גm.\y5'}I s'o O&l~W,c} $~ka>'aUr)mߋF,PkU1/BȦyN Fi' 1T`e&"t$S7PV OK>Jz2->k,W~&{\I:$Zù #Zy!Q1Z7>~3I9~^4-7\.^(cTބ(e^~PPLڶaOGua~gV+قHKX?5|9~*~sOEiF]xbyiŽgaD=Ln#L(?$T3M}GgoK?kDhS2#uu\rwc5uhFl1lOvIQp8R,S rucBN}/.AFE-^>W/K<ߦh$jz`j2M=m[s恵4(bGr/:ϟ/|Ƙ\sS񛓓/o~)sPO4Ω$-͈9KM4ǫ۽ӄ}jV^* vQm}vK-foa*aZFy@4a.QIq΄.@O2ܴ@5q` ۬`4\)s4iJa,xwQ a*cݒ' j Mۓ$Þn"1z[]^4M< Šގq7H`EƊ_<,áIt϶MB0:ѧRT}'z:ѵg,'Rt((~k0Ɛ ֤~(E4cn.d8M^1M(|q?8vAHߏ$;+xaE` A٦8t 5 Yk9 Us$tWWW>(G{,>^}o8DNlϧq՚~5F%}5MHqd&+10xcA#fOJM549Q(h۞4Oʎ "L'$^g8 d/3(qւߊF1d( t>c|>U;vW4H+ǟ0Q_>zB׵tճZ?O+]Y)W(-04Á1DIG^ @D!: MW^䜣`lek* IN%D! #22ڞ楯?#SEi9N/Ć)*GTtҔ4IcjԨ9SW8\?],nG0*G*$/ ^%+ L ͒7EYAna%1NPո@h&C撷Vdf7LiF( 8BꝲGwwNGFuUa2'FtQ%y/ٺ>S=?_OOyBֻ ./ȵkZy`Mn%&~$|F9бKNo:'?CVqyu%YQkۓmc4!4^I)8?ɶ[ nN9( aR5:W/^ʰyMe4wU~xYBߴ?C CYT(5eV?ggYQlwkڪf=Ϳ5A(GFH!=Ǵ)ʒ;`wq''ۣuu$K3pwwGI Jx oGʲ\ #_ nZbTLitl:Xش)'ZVQtdx>m;6%Qq-㣶m ښժ$uUǑW;O>K뺦:ò %2۲f̛0Y~>OHLG\YqW‡8#,YK%ֲ wwwK֝"ϓsϿWQ:ר'q ؅>Ǵ*;o8JvKǤYN^lv`zzZ0XkUAN0CE~ApZ ̟ ~=gmkÐzMww|g( :mO'4|>!osz6Io6~;3`# I"('۴w !-LygWWWK4N>ZϞ v+#U"MV+Ii 1͆dPc'C R/EԵ,#M3JM("3d见 U uDE3n 3Jpd׵@Q\. agGQxijuz`otydi}QD6E6#Ax|ٵbyՙiD?O~yQh!Bb@Y$!{h*ˣi1ᬔd*-IAՊ/ _lo&z[ʑ):zp8xx J2JjItyE2$!W]+4s]/CoqƼjBY}k, "G}mDH뵼 9躎}z89Ţ(ZdhPDf"20(dfln8t<~%!jVtk<:©y< c$N'iYSV\'|BBBRu%e^,Q6T PdI&.wԵИ,xx4 goRs?0@~n6wVYFbm.[Q~t$ŲKd:T4ꭇe>$Y4fu8Ҹ @^Mx|>Ӷ"hq . @L䓿VfAQ@@^WzMUWd[[@OPՒlm!LBz83>}SH k2=gϧ t;Ya։Uqw#vDAP7Lm$yY13ѳJjL`3t2p,ׅpY|5*]kӴ@d2 6PVÛU$\"5(ŲuYJIz7*Y9w*IN$\^\yOR:y59[He|Q J%oˊ3vDI 隖,Xe5#IKJ% cTs}8 y^ :F8PGTjYwS8*mQӉBH[&HSaݱl c4MTj^ĦSu=8SISU"[7~vnj [2JkMӷLaDŽ4 c[%e9+.-Z\eaӎ$MBVj 3J79*4Ҝ) nQ_\\p>e4\ָu5A¨n"X CZ _g>d2<Ǒ˫+_xL/on~ f X (EiP$i&&oS'W^6&!5o^K )f++N!ä́dOӎ#uPf C8Mkqf[~uoϊ{p񀓇H7; Xt@i,4V /Qn8"saDa.8rXҼk$m;1AKS|(NA q$j;38R:#hl7DyAUWTw{vd|MvԴ8 քZ-qtH]5DQ XPT8!؎RbZk[ bx"SYb9{t͂7UU(8ZNoq_ f|n\vdĬ)f0'"BHLG(*K$(E &Mtm mC3P.//ʊlroZerpсE1$[C4-+ь1}#(fʢ1-EY|>U>+n&4{?HZi%h+u~7J ~۲m}ϟ3VfGY|(q5-m[s5 H(%ќbs첷nۆБ;78w#aQ;lݩUţrDG@'OLF.IpeD!adYQRqG>I76DZP8Cp:Fp-( ./(rqV+~ ?OAh?#׀-)yЍ㹡rlБm05-E{*bD&駉d w77(3+W:倀cLWq8|!kI٬8jXc9^t EL,͚ͪŋ\__?͏ \n=ƳXfNVW(-f(:\]-㔦m)(,,רּ.i%"&0RdYPLEϤcI\oL^q|<#L{ÄG<% ՔeAkY{@&\idUOs會<9|C&c/|ɓGr80ay"I=| ^_񭋍l6}FyNZ~|Jx"|@_矾SV\):V{y2->;M a[au>cY $kx:qV۾[!a7`A3Hg#kΙ#Q: ,B!*6l5,gἫ*IA/^ՉEˀYbMt9۶Y ֫]'*[VQFN'ן~JwjxW1C7\__/\鮭0o^cwwYf_4 ~N>Q8wSΧ;qAKV\hT(꜡,hG&O/25/_\:G2-+.M=|LJn6Kfӧ[{/E&z-(z'kF@8T$Ex>զitгOVTUKpqyqhB:#MU1 #u[/ig^mvkgϞ-n6\]=$I3ڶ\75-s|x<wV5'Q̓'OQJQf0$Z.//چйtg8XL:]Q)CSVpzV(a,(qq3nފfچwy3NYqP0ie<$ Fۅ.W^6d̳4n(+)&]2\ 5U}*EoG$V .cC%mqy ~ /2no"Tm/F)+%n޶ IDAT!HLl]Q>f.8aGbYo/ g^zjM,J,ϙD?_Gk/_4ͲJ9}ߋt+iejWqzζy笸n9*ڶJxX6$@aApp'>;eum{Njj/ E^Ay;Yn5P /k&2 QJZ v7<ٻk})HжPTØ~鴜i2J sujּY% tw]#_Kdkɲ0)|ӏm O'똼玽|m U,TWIπyL&)?@!X\1mGuw]Tl-#wwʊ*ǫ/IreY+'`^3M/Ƒ( pe}mb!>x@F{t^A<n2if2|嗌j/;}vnE]mq_K- KfTN8H4QȓǏV!xyyI-,ˈ 8с^h\I 3 CY=FX:y~7mPUQ141,ϸ Q3#^"K}z1En勗4mC붮&NYqw_CMXl GӅS_fSUg~;H;,'ID4x.Y|W1W3DZtY8kN9~%of.L,/e%%H!]l1nA0GQpR^~M `!v뎶:.b0 m)E 49qRU2ZB*r1T5:o!ayIӶ$ߺ)˂ $Xo6"*{WM\$ F\,J622ΕגulX" ~frw[؊~_(k'~၌\5!t42m{ݚwe_ ]sfZ8!(`elWkVhXHk(=:zyhgڨaS1wwXk/!dF4#С^@ҌjGImS8D/X\=xp_5}gGn ˜n^xf#K7| n]ďsV+$#y&!c-;noc'/v"/K|sX8,Kv J9*# #\(h| 76sz^ 0 i4͖As⇈&,5(C$bY10www¬Ą<GL.qؑF!͎/1iuJ? O>|ȃ϶RG?de,h| c&KQr y=:DŽq4-__2.iRKi"2~R}1#۞/3J߃D4<|n\UL艡;<:PtTMCH4 ^b{OHUHjYu=yJ2$󯘦~IzV^=9`S)YR(XI_Ʊ qsuOgy)U%) p\PfgNlf~r" ..vr3E[0`p:~٭ V麆,N 54wJ|{Zg_Y7[iСNV3Y(7"Vn:kx.tsI& y~O'6ж-wwwN'>#\(Iе=y.fCuxwpĺ'Bkkq0Hg  ݹZcm ?9|}i_wg_~pbnnoGL ~^uXvZ8khwJ\\^bǞ* X0!p$Pp} 5VH3cZIapAeAAL~wޣ|` O˵ԪQDcnCYԣbXYl6k͕p~J׵c͈sP֊,̢r3 JؘTHg"2Χb'?-ۦ>劺軆c (K?nHXV)F@VWWEIeK_)>#AƎ'pN@Ng=zHÄcoqCiXy&̈́%)q.D:C b$X4}t5?]c-<-d u}|G]1ӄR8#i*[nooX/<减33W?- qi$P㉋K̎~]V+C 3^|5\GovZDJmɨ$MԢ9ϬbgGߧ(j\VJf!,ܫ_s>K^K,Be5wW9aѥ XPWWy5eZ8}kY? m%q3[- HCqĻ[9K0xozwѽBmZԏUR*lΥͳ^=#J<}"PO/?_zT[W N+-1iUxOȈ*IXYPhl,k-9nFC]4U=ǴQ^nZ4m>z|iJ1KfSiQ])Enu]s:9}%Ɂ7TEgIw)3}mKHF64>3~_WԯrWwf"[ki뎗_qhdY?zǏ8,a(GG 2=?|N]U{H{AJ+6JmQ-x0ޑ)(@UUyQJf*01vwOvSV_%4Nk%3o4V]{>9E%$y X-Fv9Wc)Rh+Iw/N,1Ӊ$?>h#$/8ϱa*k) 8q|=Ihff ӎ9ܢ]S7-H{ ב?Mr{4/&LXp)O4i*e4-+-=՚#>?gd{pxbͻwMOUS+zPfc)o߼SO=#:=kY6Xkno)81- ‹jEѵ-EbaU$w*H߷/h3h [)DWU 0hۊ0<A Vb /r\~iZV%}C) jTuEn R5}D3"GD1Zjsm9w~ؠk9)BWg<X担T4OE<{( ӧ*+lp5I4-bN`4~ 9Ju-LmgKwD~r'WOP5 ջ=|ZCs=A ʶ( VDIB?s@i',7k)'ʺo:4ja/[IjQf\JpQI52faZw飼 qD!dwTi!h ע!PC2NxZf荡S{9c{~:<Lh &ibmX#4)K6guX0K ^`xfs5>E^9َ,bI>~XyW l"T5][M|ZCno,NZB)74sWiۛkTMIvdiJ XNqU$T%Ir$MMuco5vJTh'Q3FBuv-X`BZYOcc'XsG0B k#OO(z+WU0ZuQ [ @&DX^ |8>|ZC`?ERaJ:N]Wp#9OQ U]Ep(ˆi/1 |e5E$dr4UM?& |#M euBz 4ď&e2@VI`B@i- \(9aZMӰZ.hZe)j'sW״CAĹr]8xN~4;Il)x:$U/çe> vMCVX#bi#c\9_+Gٞp iM'xkY"(xʘ/84yc5b) c`Ćڰs{@Hbѓ+ 0:f`%%4Ѧr&c␀8 VMq=Fں jfn "烜_X.$%$ENGR?q-u#OfŁaES*{CvlTZaiZ#(~̇~k }:UY 3pP}$LU)iuY> 'O/e4Z+蚊( jGdڞ)\# =g m2]ACcI \bN;7Ubb{m 3h Պ$F6 Jq~H}o0|k)ҥ3/hN2/Jr{Q;HEs ŰTc,A&N~)nGy}ࣿu<ǣTweH9}Ȓ)@{4#K3Y4,Z+<ÂVf:ꭕjYW?,<yv!̖0ׯ_G Ef3D,"`OJ?Kq?OO.'(nZEhۆ0L& 4k0. YLiEbdh=x6Ʃgib](QA<5{XFF؞ߔuMC1P' 踋xJ(%Om[iƏ?iDQ6ROPf-;ZjbAt<}7|'ыOZQěJ\h( 4E^yvȥi|:EM;B4TH=?-`?YV,]Pn'G|>$ﶖOKCӴNIJE01!"KMY~uQGѸF9(oyA@ocְL#zi"jk\^^pUyO;uU\^^Nb5QR"?)onn}Y#4o1=AM-kaVߔ5o]pW~(/($izapW|4Y~@' )70 :^1^QH2799m0+tق8:7 Y0d9tJi[1__\QEn]3Ң("aM4$"jPOcO $)IŨ5R -ІCNj^q4v*>JSlT^v-ဍf@v̒Hh+y޼yËJYAhڂrN}'t'K6yo}wׯ={d  "\1Zu-u( m47W̋i\klX#ip18r^%͖ n[:ez7rODPgfC%e=Gn{ fk~d&Oƨ{q=֨#mImb/'7"<Ɵ@ۇC ZlG4IaPh,'$sZ^}|1^<5 zNiHSV7Du,s>31\\\p}}G|)𣐲< {ᘡPE B(nR8xu1xc~Ư / DZ^*% @+?RYʼ$NbVJHScV5m1[.%LEQk960}f*{Mu|!b-;䟠g^}(֨kT_>G~eȖ8β|ݎ7t)2Ӊ,;r<rRR/W+.y*'}?6R0AeU9;%Q1;94)ӑ*6&*rhLO_-Iv{Q !;끮ewG[Q=asCjP3t#s#)#łgϞqsviGKoB?3icvhzLpd7Owhʫ[;uQ$Y>aM R;Ǻx jX,WS]kY.DQ񰥭+x3]@Fe{>N9^cFUdH}GP+?&h3HTU3$_ q|I߃]Z,X,i\.<)u[\`GگGga޽<./.Qt<`PR oArs'Ѷa2$펦,%H|!GjqJS"|qFHa$Xk'y2 J|kNFVmn@!%$K:f J5 !J:[MZJ)\5 \ǣ,#RaHFX+FXYhrh" Jz}]bwIQUlx7"qHdjG .;aQ,No Msww7IX4WfN1d] %4i1.@S(/TMEibjǑS޶ПL1b=sV~0md!-ĩ(9! xFYTty1eqQc[ںx8`fs̒woÀfM1|z,K%جc%,(JSzyX6<~lˋs{~~֖&-"i>:Z雚}A|kd|0*jjM)I ?>}wg>ècuc獜l=EQ8>]1 [r߾ayiwF!4 ]_ 2 q& |9Vk{6-يGWgݖ, 㐻=' Pl6Fۉ0ͰnP$3QӬV+" aM!OxUUe xwE/07X_ n|4M;1=Of"2h 7EZMӢ =eYF!H/Yg>_SW52%͒C!$!2'0!\ه\UZI7i=py\$[/jN#$B6&&@ZFS{PGC;HGlI4R"qw׃}([Ɋ|xEM]m+~sNz6ijLْخ' T'yr<9{|9vR)~_WDO90dKRP'>lN8'Ks{}Mt#zۯ8tOr'ӑ"YY.Vc\R65s'FgMUW idwB)Z$)%:քO;m(6ShՒTҼ5ഥ]GS%mWsqy{uԵp顜r('f"=|E!e+N8hOTg8ȋbR(LlFUw{\";dyN`Fnњ' dŴM' pa1Ȏ(O)aq MH]V&'cpF =泘?s2H9dY>u4S$ uQ)OZB , mQJs8KPe<_ mk;,;woH{ -q" ,C:s{GŜS kMW{>^g?3>Q 6}3_g|n6tNW=`$8K]7uGxgp86h+8RD qGcX/gc,?ѵŜ= _~%Q$k1QWu G1G2K{am`d!YvR<}q9aƼBf7k.//9RE~caZHvQ%,KQ<8&I"8"RIk?ϓ,;Owz|C+Ɖ̲ ? 1N. E= mE ъ9$yJ/8EzK\{ȲsT' 釺x8>hCH r}At!LSuҵc䘯p8'^Oݱlbr*,UU5}/5NuX-.~{K BL3鹿j[1yV}ngFGvoQZ:.H<{`C>k?6JÁ7eJDR:B/4VA\Q9A)H" `}~~CiE:?}Z,%M=N(&+Ρ$Y2_qqqjzR5F#kdd:{2\.ֲf# iJCH,P"!Rx -ioӒqc{f}"Sqagv{C(ʌ,xriO߶X\yx4C :$QG]Gg;+Mpvq5/^H|hz'_|'vAgMEt\]]}H |tprv~ɓIfsNWtml6'- =diF]V_e9UUXVEvKvXm9A()ؒM7<`Lq=Adp=ft8("}G[xOd[M(^zJg FsI'=^q3 coZ;榐N4sdYƢ?A?, ifEE7|ٷi֊9;;p8S?S|OqvqE%/~^| =yƟHDpZ7|ٷnw,+?~LD](7Ѯ<ǟ 傶.Y,$ȋ'RUp9[ѻ`eXaf(͕^0%4MMzJ␻kB?ďb }@^QZ1%cMRKѳ\.?lEݔǦ's X!>Q3PFM߰/Äc'px& g e3DӴDq,Mzs|!KO;T24hey2~jmZPfPB#U+0+n߲XKﮧwk4/> as'κd77[dNoO6-߾Īxw{/r{hlݻO}x6p8g7_,8۬q@<Qr<x'v[ʲ=5`$Z,gr |6gg-jjBC,l6TuE]Ҧ9Ck8;R" LfQUM7hel٬Ă`-r?7`RBt-LL֢c#lZ89syt]OX?`n׵-V~HFx㬺BUU(sз0mOJ񳟾G2KPڰZo}Zs}sGOh(\2_g4mw|%[ vepP_KQ0>N|?bsqqIkuYZ)j ]%gg'0 Ҍ,Kq{%f@:۞)(-!Ջ͕Xb~_ٌPFš={'檪ѳq>;?*+߼TUQ̣+_\[jooIs:@iVʲDo g (隆l=O<^UӔU3g8Li= o8I˜W_OoWXm8K^<#Ylh tw16 K|$eEB|L9o/Vs52-W/pΠUϻ[[-_. \S,bֆnE)kGS ̽&Ib =eݳX)>@ ,BJ[![ߣ3\_%G~K6t *AوbIZ67WkQ#}oUA$󄺒b&"hW3_l4z:͖8_|$,Ms< h;7þF1}]vO+j=DTE//@!J c-u'"DofZ(~w7c%77Vt< :P/pysa9ZhXf!aϳv(^=sWun2o DrA=u]I)+ Hs1:g0ZSK$sZVg"xgL1u VbZRi㐺( ?ۚ$\ky /k)K(B{C{ezN lGctTş]PCqqy5h`TM"i0VQאznxAε̗mqUav ם?Q9ZTmޡ Te2 }K[ie]CkKxuCM*]蛒P}OWhmi$ssʢBmCǷ3~V }ײo8;GwD^K5Uw7S ?PDcfOMOմ`qFAh ްzgG.0gĸևNl@` I2HvT>Q,ir5}CVbd|j>/VkkѣMzsݎGeCq:ʢp%̰<;ʏ-yQG{yу?7d-m]`fM]u;YDD봧A(N3mʣǗVE#/v<hsψն--iI ץUCq'Jݾ$ iL]Enwֆ O,(w^R'(U,% ?7J]U0dyAݶ]- ƮokNYE4ڗR~K'nOzJgK<}iՒvq (۲^/b(#ʕtyMoLOvOZf21qwѡEvڏʊg>;_Pde'᚞Uxn(-]h2 چmb2?s{q!oZ M۱4Ŗ^]eق0 Iqk%Z:-xmE(alSF}3H[B'X.lPFTuz_k0~P1wwak8I~A?2NiIx~#񸾹!0=ISՐzeR?sgoѝN\$SF̽Y筵A|ضhPʣj\G ]cˈZs`-w@a HW@L-Y܈ITD]}qtum[fe\8)g228PIL&ICnS"̆ eN_a%Jtፅ E4@s |x{`m(rgW( 2G@~Dx .u{]yHaH=&+ Έ߹.]*1ƤD~)S_K0!׵m>G"1F })_9G۶7CYThOvB "`,z;`4#^\3_¡)9^>(ljKxiFeum[9iUJ/Y7Be/yRgJ(G._>Ԫ?͐'y,a$OfxM3,9" ^\Zۄ%@H! 2Ju"L!$J":Ţe-Auk2Hk?32]DOfJN:jO-.vWƁ,RݎΛu]CM_jw @<(0S_c-8ȳpB0z*+C]W/V͛7zznզ$K?jbĦhHx)00 >,Sӌqx<Ȳ3[b%t1GB%|t"_f%)dYHt|^h!ƀuuk2>Wvۦ4:fGpx\~]Y3K'F4qSBpp1N!Fɬ&lq %tV $AQds{ އſ\49:XeUBs~? i qYo)^>@Si͔,^_׺-sqєYlTrfQ}#f!  =ab`L!02M:4$8$65fL4؀񈶧`߂B2h1x/^%eIQ\^^},#I,3uϘ}Zq1U:>K~pV5-F9JnO]dYFnv_]$IBUUiJ^ZP)ryQ]q!Ib({~[yUW%B.n'9,N0'JD4HhzABn뚺nP زnR],OR g.ݞa|o}]״;=RER 3K6~Uݑ>غIn<˲n}wYEw~[B# ۣdy@AaWU֚0 IӔ,:):ʲ$sR$pv=gBnls[YEbnxLe:n&]qA`Z|qD)4JJ~&xA瓴QXl6zR;뼯]qV}n9wX°=ڿ[#hqB{GE]~:㎏Bt~xn^}wy3oߙ`t}LGɏ% s?خԶG} c 9ItyF#FQHg{ڟOwZ_}W}9X°E* ,$ xڐn0̋gTc Mn8kwũV{VE^ctq#?0lW}}0b0Jv3R}עIO xW7-݉k} w)z}ݷlykmSn?:iNqkAI3oz뾪Bav\x5i~PuqtR1݉gZ}c+ξA{~3g6l5OJ.ҏ=  @{s!VwYZ^MO}|`68iф[9&"!QJmj\_ UTD͈=}͒m*BdwG.[ѿam_}Wolwq;:>턺WKvmc&n~jIoܷد7_vQ 0/'}AJ`DH-ItuidIo1>7]q~x>m1ެ)°KiN DK~׃Tܼ}Z?_e#m0]0aho^?m(jg͊axPv+)k-=cc% oZ k-]Խ\qRR"ZF wdwmM:akt Woqk_Qa˴_(T?;ҿg-펼yW75}c?yea`+OOE7E' FIF%H'8z(=8[3Mٮ./Ғ`sM uct؞gn> 8[}3G٦Q?/"#+rL)0&O޾]'9}wIYx!R~Ӌc'!Șwo.I%Z{XLG&wwRڿ>B8ug5A1;bܐ'<8<(K|4~ßcޝ%ygo<(t2ꊪI9N(>fd 1嚢peܲ7tFI/_nwj(.fCeLcL,>{rd2`w })5Aqqu`0"fA"Ղ0lV՝q)%xDYZDyJ1 PJG>g+Sj~=Nޞ)t(OE|錟 Ɉ}1_`~ :Nqt>$uIlx-s5Kj Śvnw}TUjhfool|ɣA]K`ۗfkZeC]c˒falk CAS5=9ăӁG%kV7ޢ(B=ڟu!%]߃=?$I bq&Ֆ<^fPU5QY88c]3Bחw~`NDQ()[r$|>3>}R+gӷdEzhO9{{3IOy\\\c߀}atȶX5˲> 1WK,5Ɉ??i$$IBdaW_d<^ogϞq~~lg|LdH Lz(U\lcc믿foovKV8!a)BLsʢf2w%< ƟR/WW GFѝq)ڇ*JfUUw~,LSʬt5q35XoSp,[^~5IHn(&ͪw]W/h|,axƫ(/4Ϩjv[1Ŝ2–c?~`0ݻwdYƧ~5uł*g4m/_G p8܈dۚ~eN= Cf{S.N<ӨWbW6r8Vk=\_x$ϟ}vÇG'~7y՗\3j_5{{3Rl5 >ٳ|! )5fCi Ahpg/!hr[clO4 aȦMyNMN3dYN<}qH4ȫ;s\]]!`oo n;Z/vre-,hgng ۮ6+C|((˒jn`H)~9 _ڲ\&6p{t6w7ZkB_o^,9?&%?O?kxڧ+VU'!b&XWX&k8`+-g<|շخZHdSٗy퓋7 ~}a%' m+q۷oͦEFQ쌢l>è㞪b:2X,]/ͳ~B'C)EEhZY7hek Thl}ja)/7R2OXWY H-UAQwഋhx>95QpP%H`Nvlp8rٴ`v9Q856H.?0;nnMRت Wծ_=jV>:².yh!WWWL(iCYN֢Gh:juPT(!!/K#zHt$g3’9NN[ՌS^z8,/aތvMV<{!*Jز7~0I?/Ovj-\sGG+ʲ$u 9ƊJ)wbɣGDQ1uU+K*irqHyAEh/@k>fMyƄ乥,ZIH)9=8 b&!|ͧߛ?i8 Ʉ˫kFɝqEm,&M_?}^"4V<Ͱexo>W '`8Q񔽽59pHn98% }.//ONNN~.J}*fߪZetrC<|A#5!͚4'0 H'm0!X,<~};f{ޜ(m($!F7G*uHGGG\_/@kt<׆fógHˌ%9˒co87aEʲ$2B?$CMY1r#?$IFf|ɧ(X6fe簎F#sL&#\8<^|⢾3vᶚ2c^yë`a>Ueyp8 NNN8qq]F-?9?a^s~zNQZJ?RD!Bi%Zj!<'_J4t,-H'pPci-fYȏ% ov9:±R:po8x&( /h$5-ʖ=b$lQ-( +4w}_wj;jł&ؙL449;BI E[Ӛt`"mh1yZgrumaHrDq}&U R*b \)|N4gc%zc d0z: YoVβ:+y_\ -AD7#> ^`xrfvj>gYXۺ62I|G8> ޽}CK?}B7OSD4߼jIOhD臜~D7'it6vkyk7 ꮚc ~ e-:MSc4=^| `^c:<}/|Whq}dŋzu``qu}l>)5$Ɉ㘲,d8dY5?E8۬SKw\\4Ϥ=WqŢ+9=]34WMoH{xϿmTTW? ~Ŀo|p8A*t:#޼GO Hb42pUloL}ëW ./ fiӓٮߧj~d{|Gr8R׎(𸾺$.x _YY)'*zܭ1+>ha?od?? IDATݙ. MǦ;;rZ"eH4Zi\UoA4<zE }(գگig"TxKb< |C4R%-I 8STG馑$5Fjt9l&A+mZ"u@Y0Uɝq%nSK=p(nIYZ.=MYT,z!JH}m0-`雄aq0A(·TG)BH*먜 sDa2JSG$CV IjWWj(ƏbڱY-/iKJ.>H)a?0Mxe yhgM]j*YBJ`JP; *ēÃqeSŝqǵti*8 PRML&!JT7>il"GuCUU3Rk80͡qG(n&2$ ;Sz0;n,15+ڀpN #ep {Զ $KStPƠM1wfx*S^X<,s0 1)9Z51,( :0 "nn彩ġ00/nuM˲Ė%4yӽfF#pRcZR'$47F(5%Y '|J[U Fl >ywZ|4U*r(aAkl[U8a!Ei¡!kH`j?>2X- ExCLY6P*qg@RM.0VZ)8C"Fk UYs>S<#1xSɩђmF9fyAi-WK.ސl, J0!4P[֛ xdHЊ9*U=/Jhl Gȋە)f+Ѹ"]Bsn[[c,.-?0gloi ֛MR9)O߱QJrh"d&k+i4Z Us5R Wo;=x>L/Yfq/^/ Nj.^.6K |/yx0'(cq<`{} f6,O?EJi58;}KUeRkQ$QP`hk@(p`mhHp c|gB[cl!7c ~uvYM1A>JiD \ +Ҥ,[m4Ay>hFoް,y%JڒfYa990@d hvf,+꺑SJM0e4(%J% <*[5l`jj[SU1C~i+,C c\c[ WkMQ()\eQ 36Q7XJ y]] 9jB@xwpزBCi%61NTUEdͷ I.{1hun)FH|zaIX\dxR4+@jAL~N8x F9oߞ0xPJbk 4R#@)Cl J5REF-շZ⃝`~`o"siv:(SՎ,) 'R{DѰq~i(m NJ, qxn1JD]SY#4kC.u?'Uhʱ^6S @{!zMVd$Ɋt$Ox0a4"i,~YդEAYפele4g*wj[\M$Զ,s`IU(R9UE$Q  [[Vn}O_CzgA?0KdzyhtXk,J"1QL][8H?ʒBvU5 €,!<`&rBM6 = h#-ʲ)oi?U! ΛӕҤ$٦R) |?B+&K|C;E->TMіHnQMT[clbs*ʒUCD a؊O^= -I!jGlT菟[veu?٬P({dY&OX\3X?$'NPWK/.tCqF1Ig`j|? l|/`X>`01( I% c"Dv[6=?[°'}.vHN@Rģ1yQTEsj`<jc(UݐR!%S Ud(J@AvM)ona-5=~bz61rlՒc@H(ɶMry`v@AfDwys%~c$-jBWvr*!ר"O% P,0Bcwo\kӷιl~GrYZlvY4iR͑cmI]4W3iq@{Q!7yeS>ŢR򼠰B]7_əVOdYUc0'YdDK1!R <~5JKVyFY,Kvyak|"/k;9`6Jh)(rPes;vP%.]l(Oe%Rh KެiAmѠR)p8!A [Qn |F+V:ҪQI%Z  BC~hR$k苩ihrn6V9F)ʛ}I{UhuPٌtȓE]9NޞQ` ,҄6g7d7"-N8Y?KG$v) i}tɓ'Gl "#Lm Ʉ~u m$̰u)9/Qy^>{^s>c8v-u[~>=rm)ZS&n]jm#tKx]M+3k(x8(2<'FB>ڿ3Λix1q7 )-$%4חNãvU%9B)ޝcxoBZeģ0 6oz]_m-1·Bpd8/^I6_ [CH9/_0hz7߼ $ Y F -2 BiMb+-傢*I'jv=˜P5ޔW_f:lg w.3$yFi+%R a||h!՝qJ>-$ mn0^Sk<- ,%x<C2' !O3~8 1>,Mo- &|/\޾ KۄCȾ&Kr0"K9I6Ԯ`0Xtf3I{8TY3fOwn9G7>9BzC!_KgN73Ԝ~bҵiPؕk^5 j?>͛7)zqWyD+8Qb;[ ~*渟5Ҵq?zy\α85m^7ScC{ضH&1Ղ((*U$˲g;&>H.Fcb0E!X.5v/??jYKH:.?lRfĶ,˭X-]Yvl+OA4MAoGGGXh4QH&+ie\ rraX[FU7 qq=6̕8L~jC9v,99S\pr@ 4;dnb&AMɊQlU#!Yp?f>;vUbv3Te,p1qsu=g>_"IeVG3#wYEcla$s4M6H $5+tU&b$j4Q&Xyt40i6+H dIFQ1A$YȲvoU軃Is{ڇmw)(PeUX-n7nk6 *@eD::Xc&Mom7!E y;YhZqr4rYlgS$ʼ@WTuLePH:%))E 5wSvj]pW|hwذSZrj-4֩ 0LLTAMMʼDdI'KJ|?= M<'hj7zrCVUュq0ݝSc{4mrp tl(98>h"XBuDg!HSr1YCѣI^*Ar,sחY2y>o\4/,蜜.B5,f5_9>:`A2 ATamd}.geTm>0l9u}6?Y">xK$yzɃ=C &+ar1B`rq˟9>5d:vDU$2'޸q6~uk >XUY; ~'MfU ӵEt14q#^~tp3c$L"ck HgqeH"xǏ?l`1 [|RPUW/'PM iRbG'o,<~tJe겦ZO?uz9=DAW ɲ8%}`ne&m^8.0piu8}qu5 aP}p2L:g6LZ²W;yҸnwSSȎlɬ̩Hh(L'X\Ox'膄Txj;E}>|m?P˥(JdI%"?:q1 IDATE 1_g>_Q(EێIqH4\̑%~|s1l׍(LjYCLyu7&U P?/΀(L,/ȳx(ʪ1S%nnB`ce|@Q4UqA]J}0.Ч6ă^t: >\б쭓b51T ]o5Hw]<8Eyλwoje~'޽0Øt Hϖ qru@4nn|jF}$Y Cz?UH9<2AJ{C ~yF#z}#lAյ[]]nc{> kt-a  #.8::AC"*.x/@סPmh$;NUUSM+, S,KѣGztSt:(.gg> C<{KW}ϼxpDm.1G HI `{ڻŪb&ՊS?8vS3#v- ܮIM%ve2 Ǩ@Z$̖nϡjA`Y֭]JnWg)܎$"ʜy ohzl fvt$owv;dY Tt>;NUxSEAFUEz>ӿϞ~t:Cz.uhkcХS}, ;TM 9ksvvk??0lmJ) H›!{b^b m)N*C!eQ $lG7A*jt~}mږ* U!| C j*}Fj.QUQ1MdAc8H‚8(Pe$mdM${cv۲utxL:.>!budd,PCkl[j X,ciuUݞ +EQC%틷]<ꫯN_},ͻ,+>"eߨ~rO?NSNN_^mlmW1~E>w/܏?9OVGg<׌{˲ ]W_^q|] 9w-$YRy5? UUq_+|ǿ˔8YvMTMJTM$DQIJ C[x> mcdiI>0lw;UUm5tv Ys#?x(U/wݯ+p0X52f~fyHJM-EME Ű 16VqRtnJz$J{ǹ;fr~~P', ^y/| /b|qӵXzsac(E0e^dHUI]wQet].B4.`F- j?ج|CQMUA* F=޾}qV2|Nf$q<~80 ˾58{ڜd7 `[)뺎a[fOCPby]!_}$OTc{}°qĆt}5c4'1mjÇ)\k8#q4uO<{$p.߿87d$q #&VGQ8Aܼĸ(0Ms 2eziajbDQ<+@B)bb9۳CzfatgrdYQj(naZցt<A]c`S0AԌ"2b]94(|3V$X}V4ʲ`0`4m䛣1(ER 49:# \͙\]Q5yR#*ŚlwD[S8]׷>(mQmAuP_S9O>veUy^pqqa4ۢ(2>=G7qNRPc[ض͏??S0T-bQkTUŲ,Iz&yu}m/~.`Kov۫ X uMαtJ 2 KH<+YVEuISdY&R$' "BWtQ@:+޴R[ͧJM(8:cnߣM!QUFe&P֥,${@Y҇1,E,zq8?2I4N MSɋf>CV! RҬ$bJjyCa3u2,3*qu<$DEt}TU'J bw-[5W?0T]o\vFB$dZOp.ӛ%qMiEQ>anZ~ p Y?u;4MIFmpwӉ]v_vȰ?n.(Q(zz MI>XW8au{Xj`puu6>;;Cpg A@1y9YEkz$)DQYjdGz¶/Kɶlxn^Dž!IhJSMv{*`w-d9w_Q9# "j\Ki eս\,fTEBX.^>AlNYIɓ]6Gm[ ES-IŬV+4Cg4>5-M]4 4MEB돽Op vWۡeo{`CQu$EVU$&#$Y&SDQ&Ir,gPdy&7X *w]or]uzifrt\6ᦅ[:a1՜٬@E1tJQqk(" |nnnx=Ǔ', 4Me6q:]N_^0)Exxzf4 rq#T$ $kdc$hBQ VՓno$lL$4= "rhD%Ib4EPkNOOk77o7E0 c_;LúPd$ihC5Du$xD%u]bWW -RdUBTD,ՙL.c~uIQ4#~.`X-~`fF: qEav] !I2(4Ȣ۔H₲0t M3lJwgR[D~o<탰mr>e9&(J[( :An[QQ)|Kv0cZϑq&qj&M@] AtԨ DjQ@@R`寰l(XkT]A4FE$uTp\O};8 ?z ovz7YEǴq.lTV1't{e;&"KYg1m(.,Y)˚ M%R.Nad1NFVAUVŶX8>>ד1F"BH ER14s0&IWIUe᰿Յkn(GH E)6,s|pȇw T^EE /^D?~LV4sEYcZU]yp[6Sݯ2vJmk1{{{,KzC! \Oo/CFC0q8#U"B?ٌCK1gqFOb,G dIa 1N$e(7.ڣU7:7lP ئet0ئ2,?0ԀnjreY˗8MJ0k֋5*wƇvŠ#|t]^̖X,EejQ ["ٻ.-bWS6?<^x^&k[8c-l] IX=\χwLf .u fpuuEߣB,D;6 2MN>UU`Ʀ4פHS('XǬkƃI0u W9U^`6K8߷c{ښU?08)m⮖`[c1J`<Sn?5$uՆ4{^)i5L7H} nM;諸vIwt{=(늇'L&ʒF5 [\tgWfIӜӇ@lQL(qX|ƣ=f7s6]\\D<}n? ]w̻w-l_z."_WTUz7>qZE&kiNYRP "?""i1ctpޝcl_!v~uC\ |nq#`ж,6aTێp8 %U] Ie[\xpp7=FQ4E& C}oY嗧^BQAiYɋ]|:>/̧-pc4n-uݍ"~b?cXX]Y2G>qv6AD$nT(0 nn$9w8;ke1LTUz2M0!c՜^wX7ǏǼY"c)h>0\4]3̓6 m_ugh%L"jL[#,ۡT"#LC~H`24;Eԙ|o[tC[.P宲Ӧ X,<:Ns hL(ř״t$KVZ`MA| ,MP%.޿C{n-)aAӱVK+tM# RΧ芁,($qqQ op=B|вMdD$;Ζ@ gGlUU,i# #LʭxE XSeI^e1[z vm\Lm2VYrvmw?vH =ϣȒ]>FSt^Yn~kL@8>|.J3/'B#Cw8wc~h4¶uo륇, 0Xt=DIdEɧ-fewlFmI\M4?b J:Ç1Mnu]/Eo)*˜z<~n_(yՆ?ꨘ{:2\R5$I&M?jyuʔE,> SdI@J,3Ok(˒٬/y,oslk .łteY}XAW_lOus"UΗafwQՍ;D(A9aeE%y(T)Q$ BͻwH@^n5K]{ 9)łDgϞqv1?@+.'gb5(ӇdC>7P IDAT;v]w#(7?u]4躺jCѿk}s !fWn<;ζ+$ ," бMήo|IY4yَy,|ݯկq-.Ǔ;[͡.~.`z>8>:+CTU0tm%𡱑0[**+`=ا&2].>nչk/ڦ ^(8ʛ7oΦ(xA@&u|ማ E]覉ft}(SNNepqeI^4~ WPyϸ8ŧ@A|RQ(X,KEeLwFx^FMmwp״9P5o4VkS-*e 5 h (Q5q !:8#/ۢB]Wf ZY 0X?|͝clQEѶke?0l0i󒶴4E=/X,Vk~,[xpzYKʲ"KNN凟~"+ tKrl^߹{~i}p]ŢxeQ 5,/ &k1~(ޠ1M4+qXF@4}[޽l(34FU圳k_GGϯV+5jS|vemw&LR6(i;UADVA.`OXۧ501+?Ej[fx#U1+8Nxn>WO%zQfzEU_}+s 45DQU>\"ly^]];"8ib7IWT;|/x ic9*b:Y/b$In7yznvU"JՊ/~v8eNU t{qr}3L&b(\\qx40 5u rs3CUuNcV> O6 _~u_nn[݁0lqO+}vC,C$,#}4E%"A%q3w$uQ eb b-B!"2^Ȳv+T^xWgwt-AFV$U)ʲ4M$EFA~D+h_<؎54 kMxt*q=]x^( yy\EY.-H--4AP79 0Ͷ]blGEQF%'y]B6խ*5sيQ^# !_㐃G\^^2NY.".E>\&uQa{Sw?vRQڎJYu}mv qlT]jr+CkJĺqE\2AI̖|կn 5N0BjңEc޿6xwy!KFhmHE 6QumխcE):mߵ- 4[&m,I/_$ {g"G.ݞah?WT޶yԑ ˰ɂ$9k2PTtRPTYH7$(0PT5E 躊ajxފ~zp8 Lu9.PCU+ MUA-2r}5wMS@uznx=ш@z6Ųm\\\`㡺Qr?,#Ib{eE?7.GB ;XWiguӖ\tlfj$Fe8q(z* =tMcPð?$¦$qO?ۧ|۷0.ߏD]V^,o% "MJh(\}@'pV7jeȊIˢV0ƧO'!Tȷ꒞ZX,z=4`4v̍A +Cg*KB%pyyţG-7JID( Ax\6߾BtK;DE(8ysl69(n]Iiaps=c4rmqj Hd)?x.b%c,mCE箩 \0m0na،GckUb0>EvgZmf8m비Apw̑!B$ 4\۶4ɓ'̇S4aZ1eY#ICGk?j;EO(˜s<=;"M(۔5!qT5ҿGv/RK8::bc7a^/?0 7/_e^ xN=CuX=tCf:b;O9 ]yf B*zd1Qa\{a`zF 7 kvC/1-aYf,3s,o MfPggfAM발O)D:4jZiΣ''q(L&f7J0MTC%b*D@uQn& zI(),a?#S/c13 *)`( zͨfJ]I,!:&MkLaS[L`[2 $wfs-5NI:4Αk"̱T-ERG$qNg$Y#>jee0Y" *,q~u}wڍ-v+IR~`ؖ틷m]P*:P5ћ#:Iw9;;c4""M MUe42| K~Gx^qmi*MNUP-N/e٘cD%rKg7WWrFyႫluzf @5_}䜢7U$?S%K ͠2\\2ı:A5+e8ۂ4!` I 6 .1yhA?0l8nrK{=r=$)q)c׿ntdnnx!I#Ky\t*7s1'''atl겢S$0;A^$iʈU*KuP7nlg5ʼ xWL&Uc P%ad :dQd%#jA*~yyoǟct4j <$5<iDQNis~zÃ[Nar{%Ix$IDnO麎,I~wd&I4]A\yq3hR5*iaX*8C,ki&.b9'=Lht6 [aj:7Y!Q hMGd;W=7Oi2 D}tHₛd nE#~zNjã=oƴ$TUCDEEy O9W65ڍjJuzCz\M7YNicYU#FV]_Oc+x궇*;T Oi#Knnqsjh@0h! BbO`WoXA n骓Ȉ]XGVkEJYf}k1Wh8&Io^ODZ%R6ENM. .PE&0FS R'͸( (eUW_9zuy ʹKC a9iZk)p|H%Xmv!đa!2l&2:5@ 1o;|fzKؿ0L̓ׯn%FkPQB dzaTZcqˮ ?ShZѵgF_1ؠLE" t~Cϡُ#ApF#Rcr#e2(1 94Bkla `Kҏ&k F*t=v~(dQ#kgSW GV 4=1~?A+T# > ^Cz+[2*aJ+zm@ S~H" $l8VƁs_(LVt$nD*\B}֢tFTiN{nfV Cw#|4ལ9=5mr+'4]u;R @F-,eUPիĢQ–\$ %!0tfvUUDxβK[3EJ͛1A(08QZVhoHSP+Sh[\_]#8ܼ~kպ(g389lvoXWUIJزĘt %88 RFle1jǖ#- 4@t<ƥSDFxt#UUas-Jha^DWWWD=ۆ8=wƁǧ3BHDH!RexG0HJ;K%Q5Ge/˼+S|v]):O(|=w\yKn@HrZHG65-C߳Yݯtw${!=88s?bJ [E–&%BN' VZX[}Bp<22?0sǑ%c0Zt W x8xFۂ㹣o >bl5Ā . ǁi0Dd' qw% ,T2={]7H85'TLSnxC@Op#H{lhږc2 2N(J 1 z) 7fȹlnZd); hڎ"0#{fsC6²zE&t:7Acp0)B.*!ޏa%y6#)(%>FVmC( @(I$Pim4B 66)1M^xc<*/\}>ygtHYV4T;=>Bu>R0BR*7[Soy_|E!ʍRSkڻ5̶365 `/0 QJCb*0L@P1R8JR8 SƘ躮$wwz,ͱ<~>ϳkP^9S紞D}@i nx VMJ~IX6(%/(ŘrB}"NQ|xNIp9+eplCK&9R+x:|o#uM޳l?@; !:ϩ9g]8W`u- 15 ƱGi N =C{fzB .$7t%)(m6>$q&!'1>hno{O KvwӁʖ3 btx@+#ƬjB%FѴ-\ƑH!Y%v #UU2cv7ɞkM"F*J:L0¦(M Q $$lnb7 g !qzn h*AzAvF24IDAT1N7IPks E RZ)N]Ka UU^O }* TkRF?"JR!1bSU\|m÷ߦQ*` "zv͙jk0)=}P(a-rbaDWÑBHB2zLap.Qbv^'xQe_::-}Dr/r.`8P(t]o~G6zatxŖ5V5T|;\T2/}A&ZK֖@ߝPZc -JS C?pA9GT5n8(mh39=Hx{o[>|-*v`ݣz0RrqӜۏ:")HJ+#ݞj1x,*BYqct)JNc~^^^md%=,OBs;[Vx:wF+~?|@B"Hwn&o1.ۏCtg{ g`%b`U1< p D!qQoSU;bUB[1`mX>6~N]www빾4 tbb Ee˔+qgψ^%h0 FqGB% $QLJGtQW)TQ0QXDI~BÏ VaM49gF89Ȁz]3t-Bj-TH)W?18醑hNʂfG׵"Bj k5ZC~Vg4r7֡z xnEa0--ZG@szdtoV%Rhv=?yo$U?rxl)W7K0/%`E@Itz+V")@*($EJL 4z?'4nqDB߳WpZCe %usT9]}mK+"-eWook 0~@x=LMv9[%J|pFn?7u]Sjw}?$<, -"W_Nǀ b #]j O.(Sh{O800%rapq9?DjPBȤ$(@Jn^!"<8BbA>Dr gRDr# ]ͷ;Zj"Zk?D:rl:ՆsiF(mp0J$1:Y جW`r>4Msf"pzyRAAڰk#D_}(a\bDp!;yYuk!@tx(x<ifJג#t *MO=ao[ڶ2q&#,ѤåP[ S}U3Y2w@F)u>~jV̡mxyt#2óR<)TJq>'䬔Wu]?y<]k[XJgkv-r~s`>3 Ip0 #};^4 -6Ҍ&5j/tk<4)mYțPyI^D1;kSpz:έΗ#O׳Π{G.4 ..p($Yt ud],#Q5`։\)1JDN.B$"^\k2|qu}safo&/nHBk;|>#sfU_[sͷuA= y!D).eZ^ˌcŒVZ*֫PVs^n=N %YsϤ|e?˼!ųVe9/,urz)/lC\d ˛ 6b p몚),y!kg {jNӟeGn!F?V`UU~ MDJ. 0nxvl0<;<7]UU1S e'|E4IMi B$i!/rIlj g"@jX6DtE(MtqVbYœT 4AL,#M4wWS9L9'\ve%q4Y9i(U\J`@$#x.+sM iR ǻcGM8{yiҠ$FC?PtaB 7N 3$ׯ.\.qL<D.$w*|(0%FD31]bVhULʘD 4r#ݷ^Ui n/Ʊ[ImӔ_ajQjy4swPST M?MWܼy=i$y3΅vwuq~/CrsMG0Pqts7N>J)0Fcvi|3s.R@ mL\?\ZƦ5Zv*h"ZKr@QX.Hߏ Ɗ?Yh: .A^\-Y~s$J#/(; 7,e`xY̼tK_!>[O<^Bȋ6 bj@/s庬xg("^Ze(s]&em<ٵZ;rۜ@5 5ӴSsHf(Qg"y+Z/.EpEL1~Zk/)8EcXryVZM ː35^t F$]cZ*c^/#V Ki]qhv]Gk!^γ6f\]]]<s’4 eY\ !9!uO֦ixu}3JOOODIj3ݥl6 kN~]W&_:-{Rz^9-ŭ^9˜kddǕ#p?ˈEs/g|pKǠ%#dv5+%$rz痗mua.wq}}=&Bʲ_,Y'-n\!*vǘsq!3ʐŏrJ:/C-+]!3VTQ_%/̋ qgl,,C|9Q=u^hJ92}#:(Wy.?Oz?w2X|>Yf0ϕz~fcjq>B32x!b*N ֟gǑwuݝ2>IENDB`mobf_core-2.5.1/barn/textures/barn_3d_filled_top.png000066400000000000000000001517431264104133000225030ustar00rootroot00000000000000PNG  IHDRg-sRGB pHYs  tIME y&tEXtCommentCreated with GIMPW IDATxڴIuzŬ\==Ž6RP⤁ 4~$Zt@H "!BLb N{9To]jf=Ơ1لtNkS9GY}(E!硵9G|*#[WiTy $a3n^[BpR-uݢR $9w1y4Ib,KQ[<GceR 5?{_BS}1Ơ!=AkM](?@Jc 8ZKLkk{g6=Ϟ=c>˯y_+aH ǟQW5] mۃ89y%}opq{{zb`٠d^!ٜ0o} WWW!c qe#$. PbWum <)F Z{G)E5HtصZKE{ʲ$McRH)nGX$Zk]4J PJ7S%(Kۜ7A9YcF1]oY>zqL=xdBgy澱_]]qrvJִM~u=Q=z1 麞0ضlzOo:FYF1T!9w︅,m]E]%:k(`>z1CQ}`B4 5w%m[LcEIUUl6Ǹ@8 |q$ 9Y(!%JLl, }4 mSG't\_]Q%QvZ"ooys{}M4M|>G!aḐ3M1;פIZ$ Zk0do}q;PjH}VGHL)HOe]7Ԯih8m;\Z]eY;v=u]S1]`@1)˒D ,xM =qLY( :Y!pߏ)ʖǏP;ڶ(pC?`\1N){!L63$Q89SF徱O$jh%qFS9PR@IvEyX'h5(1 ZJb44@:{8`☾\[k0 IӔv=]Ã*6;nÐۛ[zl=t6pEJ)"2EZAf7=mÐ(Xno'g8$LSst|nu[-?Yq:fZa"A8\kR n{ǽ$IB^]EZK۴l;&SU{#fhˋ|?`ۢFIu= +N&](Iq]MUqnA%XkZhh<ۆCaޕQ%ǜ<8A ů~}g= 0Fry17C^.5=ޟGUDa7 #ū7ok,7+ڦ#21.yk  Rهq,Jb:3_ yQ圜|<~d<9}RRA/JiA;>I;VܘR8,0?Ǵg×]5=B(<F{cz눣mꚶޣċR<'ﲀϿ~~-}aH-t] )6f~cMKԀXKx=_=z =>9\~{CZ5ggÁ4}b%@ hڊfC~vai]TJ)m"o?r,Iy%o޽NhDQ{HRR 9%RKs(!qZS9M}q@v-A R6N(ЪgX'qR #t]% 0]X ]עh{-$hh)`:QVUST0 )g (|g Id6u߹AlB+w+ ڶ=m[PX$ \R?E:)RkRH)B sVEyo-Ө0@!8JXzmP Һ8TgHf=MA[) JGfaF!R QJ3NaRC9HzJ"yB3v]5}c XMݑf= EQ!`2(bvzLpHo{ (4dw!~(]kbi }}bk%4N*LߠpQ'JDIɷXgtʧmjTa#0}VZG۵E|1K /8mIvU'}QRS+y!RzU~uUU84BJ5X7;وӻO}/@ qzz;풫sO+v[4! :B>ł0͈(M]5(pC}˻7o0[0 gEfS`gmA{>N }إ$Iidd2|{W,sZc/gi?LFɷL$8m;h3JIU7vO\!2oהe9_saBo#p[eI]iJ8[`C5b.箭#% Mנ)c#pH7xL:!p8cA+XcZ3j:Ah 9Ab_}Bg/pvymvXht ֶl9Jfa<3L9z\$7?Wo+݈<G Y5Mz3O &X34V{'3eL&#޾{E%l+>8'g}biOs}}h.o:˧_<- <@ߏhZ;85Fm#Jki)7-ɷ5_~ut3*g_Z~ןQs@YpfGw;b /,&1d\\\ks5'G-&8c:[ yr9j,7k9mi:ç|ƛ7l /ߜӔ'\_^k9zɂșfx ? ӌoܬ8{xD!#DGuq%Ů7^fy-~a>w'7)4OJ>rvlncI*tp% L1EĴum]s{qKʪJ =C+e:jX< 5^S75 T=z0YyR4e-W߾FJET87Ry8)ʅ@>BI͆6? Ig4n('el8,kV.'J"D1d%ml:cMÔ%aI@hnnyniB(d["$IŒO qQ6K̏<|L^,W!LOy4Md!ٓJMfDQ{O\߲/j=~}%Ōۛ>3[0d4Tb7x.8c6p<;o!Mݱ^mxlf_J\{$')] 5CoC!&wke}iET \ 4~Gl0mqbQV=2=`&<$aDd<!5(I:[,f1bNtUM%iFnPP~gxFGYU8i!B})moڎ0H|cfyE%W7( =[mk-nͳXQ%^t}u^b8 Aħ2aRi$hNuXv9>Yeqд{u;S-t7( Z7W&sFIK˛-P h738UU)=mxf ?T۶H)Ύ1VЄa~_#9`ΦS05Yq{F\n"<_f!eSqvl7{ml ԐS5>+! ϟ?EvCglku]G?1/x-'dw<~ik8+$ RjfI;^8ړ(b+H(3V7+._ɀ$,7n:@_(JK%N&PQ%MW~x>UtG$MS& 4rك<c l6bO]Cj!QLs@kokZ-Xrʺ-~}}-@Ы ``ixp s$E P呌&hߏC[={F(xxݶ #GyBW_h W!,yyӧUjшYH'yLפY(y$ J)`߳W WW(xx@4|gxf\;<痿ebJ,e] G|8͓'ȷ;޽;,K;GȐ"]ۢ D-yϘl7[:ӑfI2ex( w$Y>7+F٘ k mhFU>yNQl$$v8Lxb9cMqu|=|nWW C:4@8}%YHm YOR^=y'N)5 Y/wŐ ~ɂ0Rh^?D ,$ `vq6[-''d$I @Yּ|G0xOeA[dL)˒?C{r1[\_/J0XnքFInIqk~ ޽4iz<83 MO!4Q>Ͼ7~7yJK=YECϞAd)lF]7C3v7Xp]Y"DyKޝwknoonwFb~p6MCUUuEQIOoz>EA Di \]M5uExldqy+8{ِi 2EQu<nH?͆j{^- IDAT$!#&dӶ~(y aO>δwa}#W\_m8=Y0M+5QRU0M>]ovьg4n1c1+ O=DYt1(AQ(_֪0h QZQU{$ZgXpuyɛ7X,(5~1|૯vyCYLGc8⒓3%,zCf#-Wda0`hXLB2LQ G]ױkL6LEqGv O&Sfq8)qְ].ږiAJ47%}wqr2WW9O)ni~!OF#vmh4o|g7%AyrӧO?u})|?PU)rx6m<%hؗ3f"I%f0)퓦1ٌ|2ږ˫+޾}~?lֆ$$M2|$JI_g4/Yx`<6a-eYӵr_>UUD>a3φ/ouGɻ7I@!Pو i+NN3& '$$ق߿BIG~%UӐ1(jCÃ'TMtc+,fŘgO/߀NywHhc9(xjl |[Պ(n<Ǐ P<Mw|t]|>x!0ft /^]kN\6mI)y(%qcLw$8ut>9svzGdوdFp 7^aL!Gtlzt bCqw | fwYFYb"j'pg _E}C l:,:ʢ4'F?TEc!uc/ !AbIq耺h _[f?b_DQ nмtPU$P<3\. !KRf!Aa$IRMUo (pVY'k _ViJl6b듒ȉ!Ȳl( >x0IiM飼Kr]1wҌ"ߑDѡO& ٕ{4q>8s1"$$hꎾ7\_߰Zg,uӱeIQ7g'gF9}ES7[Ta`$_ zR_ж5>(vryq⧟Nsq~/$&>;O )4a_}{7K~)˚8ay!~/^펟C^xasBϧ*+qt|6_(N?>WW\ޖ<|9kz)H,X3=HKi#}ʢiB?d,8;à2_9[?F6Mmwģ,]XxA7UqfELc^v,ض:|># =Ngg}{N'2':KpVE y^pMg?z W[>)$b~wKihrf:dY-[~y$ɸ]x-7|,ت'H-R(JYV fшߓMb޼ <}F^ORVK&R6 u{1قgd4 hjW[=aRKc46wŎglwڶf:}JNy^9<^tAQgϞ GG'l%aӔ=o߾E!L>ϩTzDffC>kaǾ$$$uJ(?+[n5: <qH4 . d6 }ף@"hLOd8c`-N1uoZLSqd_+ )iZKGKckoo_RZ(K^Dqv+LcO:>zMl7/_[c^lE|+&aAqß٫k~s.oy B|o_Ŷ"mpΐ$ e ɈfEUIHo8>:m[(-q ~+bYVlEQxnf<=|iHksy~ʫW?py ݆р~x|GXDQN$QI1Mz#.EC/v&K*fԚ UUTEFGqh4iLF7mt` Qj궩44I m!**Ec9.ݎ矱|1ݎ8I|Fx5%',à(*2j)r}1nE\;b,GOjiPΐ5(lΓ'O6;8,OXoVP YVp9.hnϤ,sCULavPSU U*C8:>j"mP^.PA 0LHӔ h1ҊnFU5߾!B \W\޼zI#KxqFuhq(DQ F2WoP5A`aZ6D Nw͊]p?#5i*eYi y47W("īahنp7oap eQA%|eg9&j; HUCh4.9 !|IM{H$ 14fKtzOg4Y!JFl)N2h\ (6j9ɣm'>#(qzzBY,K:mz;39B%YMc[.I1ow!fӧO,7|7l G/;$² 2c0E[֛_ Y.׬V+棏^xVF1E^1:pv~_:wt]ʲmn&nG b>_:QE*mj0t4MGt8UYv=[B A@^$~?LDtsѩ7,SA_C]4%(Ķ-RFS+\^X.ʣ=$$MS|g99v-ZjNIaB?u ZavSeUZb&YSyz!Qצ2\O o#(%ZDBFC$DE-mLSDU4^ݻw覘Ia*RC:u@lV[a9=`J"IMch&YVPd9UA'Ibφ݊]]&Z:vO2I~{eoKC6Hg6eyHcsu=~dIJвx S^u}MM>K?_/,Y$qt:mTUCUTDtzlnV3o$ݲvXLC$B}*ej,X.hv fyXx+TE6Pt ~EY8*6F2j& %A&[! h~sOwM69A!i$nb"J,j~WsttD}$FUe)PLm[[DGl:Ƿ܎o(˜^䎺h]0,4$(3ʪ@7D]dUAN%hVJ2Q,k6K~5M6(P0LtCf4U c+Ȩ"kiBS7l, !YAnۉ?LD+PkF5F8 |&)OJfdIiz)#VW7~(~>Sg# Gl7{$yp3-œ!I1.ZbYk3v,+ZnrC#)4JdE9UWh4hL!IɖI]׼~M?<{vrɓc&c\c{<V[{:m[n GGOS4UͻwcOn6i"ʒ[q||Y8Mղ4 ;#QGӭ5!Nꆅ?{HF'yEYp)e!q,K<7 #at{pbٮCۄ=BSZ-Nؚ}0fU҆<) Afw=p<(whjzaLYDHJlю Æ#" a*nh{i| U(JU*(.ZDq}5e8l!E~"IwwSCviBt6@CQ$NO/) MQmwvmkx;|u)}o~a )Ug^ Ј 7P˲c,5*5 2+:$FmMF ]3$U1+,x斳G=Eb:g0)n99>͛k댺ᐲX,fB"sM##q1 IDAT;"Ꜫ`\w1L}ެY |Z-V -Zt8fϹ=m-J(w4ի(`Z§P%ɗItmʪb\rzz^a&ICG{(5{]ckz|kJ]/oTU3~=-$a*IWdB( DIL#dc=&e]SbY6冧rfL׸ UMU Yt:Ŷ]Nώh_p8o͒4O[ي:RVA4 1o6xYb*y )S<$N"\d&iI;` NvxsPq=7؇;Zq߾DU4nRMÁEMIg|sGQyȢ2<³=R²@U0fmdI%CWËz w8.MŰ2Llm99n "eO%K"6:̤M7RmP,^6Yy\4I+d:ض_yLt=7J^躎H8 f3Y/[hdfaXL&Z65*U)s~v(z>咣Qn|CdFśuQ2]ǶXzN]CՈ܍adYrH} qD f&+I^y5~!#gS89Q^)ßä=O4"߇qHQdضԵ`t m.NE㴪č9+jG}52Z~tnT2YVpyqDQt:=CDiB,W32b0l ՘&$IDU5Daz5lp{#\u:<0?UBu9 `[ EAQTi %OU?2͘O6,g 4NNVCVT ΧW¶m4`\>pV4NvtZ-65$FZ+v;l[p *O?͖p"^ >AU NΘ׋oo7lo🸎yi[y^&͎(xsGU^~ruugvg-(oY2<}5^f)`-i1؇[GݾKiXoRLn'R,I"tqW_-VlP ʼ.J d=F&Kr܃EнstxXwI3+tu$Ib2-HǴzYqw7u= $qu5ٳ}x__i{{:. &9$MQUvRMt 󒐫7H(]4MoH24Xf u77c3\FM lS 5777x >$I^~ UIE|ӥ3VGa7M[T0f<^yaRbfeiNt'O#h nPe[=?t܅gXArb>8NSt:"+j}%A @LJFLRM.v &V/~,+(KcA !5YQQj2ݮM^,K=:&Msq_lB>˥l!qeYlv"4M(°t]8~HNEA哏pmAihjN!2ha8h1asydmZbıp%yZ--Պ^K7DQtvۈk`u&o4t%_~nE=I~HyD,|e}rF.Yx6GQkټzt6R U^`;&*sr>H)낋GA~GW5lb?"O266$ LM 5Fsl$d ٮ7"BIC7VoaSI*!Yo GGrY LhË/l' 4TxKDxwhpVwt:"q{ݢ*eq}}-U9 VO Ø(˚(ӓi>a1c`YI $`'GN찺J]5$ Xh4RBRV9a4Qrd l91Il׬ tEJݞjT7qDYUY؞?Jx!v/7/Z[] QdU&1'c9ᨬvӹ>׋Tow7W|5o^`Zfbwp{+p *_o9>;icU]vv(D^dy%T"rCCI0̇c8C$VGؖO7jt2;٢oahyaJNf^r32:9-Pl6,[e4^oȲV03}čҌ0,r(vv;gg#Ӏi܏<{f^/S6 i%,\/Ϙ\]m(LMvX,VLS=wcaXs=:g:mwÏXBt{(hǵߕ7o_I'OÅWufCخe,7KL$Eắc2,yeݒ)iQϟm,K 2f9ww3ZA4Q!|)Ȳ ^i8V'I"5_?Nbɀnv+XAZe i'Z\f8 ;RWep]1fǝaX\^^X6777K |C~:l]Qjk6S,&K |ǥjʦDׅVMQKT]a:_Zv ,Q'#?V3T ۶1 I{Q$,;i I,"vֿ4ئI\l5Q$@P9*ݞxOpmN\ $Nڬǵ=y1t#i*LSg:ǰ,-~+=t~#jCJTUA lK|0 Qb͇ pLU 5?c. @QB,Kle?MX`:4 wmk_t>욟!%<5^[EuXbD7Ņ) tU3^^q/dfKs>_3N1MXbw,K`SᣙMHrحx0wzz|Nq(2р( 1;deJȪղ v _^(ﻴEj18;;uf42tt2g8iww5E8&_~5o__):*,~VDWUE ͎ѨEot 8:0,׍L s.튢(qitzکDXG3OQT9e-L^?ٳ'LIRfQ _5gǧ4d%dEJ^(~|&ih4B3t\c>jy8nl-WZኪ]50Lh޼yCVWɄs)ʒ}~O)21 nϯ-9Q*wIGp㗿5yů < ׷t>3OtDơ/9??!M ,#byx9R (MW( RcCsFE좐g)?tnntCtE*j޽Hk2/>|.خsT$aOS"C9POfeF~(iݘGk\9yAURU%J w;AC&L:tea&k)EBW+n_85+UM^xjYUCqwC5Pel ibʂnM> 2l[䍔 tPU$'P%AB5tz"lOÑbE9V]X/_~cq}Y3MU*guv9Y缾zK$F;7mg|wTNo໐jpt2MX-X l,dB4h^B̔5z(t],a۱V{f65р7o0舲PTn횸62Q%x4t$OF5-noE3xn2&I"=>u'{ ]#2d ʢ@%$CB, Z'YMޠ*yQ `aф䪬LZV0\r\vn7hc{Vdrw6&qBC6[̑d?/^( WW,Ѷm^x_|~9.XdL&Ƀ( ${EA\]])Qd9D=ysrr)E&$cQTXl0ttDFQB -~68,ү}4*aodo ߀( y1ggg%U]7x-6MS|ftZ]k8;9g=H$Ad- C4MgRIhw[؎nZ%jGǨt\'{L[!/3>[t;=dIիp>E#K"kTE5T2ZdiNf6aEt:mifIs2g2! mb645 武n\,oc4:ݏ1LFԔ?G__ :E#R aM]9GG}U(r(dnwwʊ; |e么MD S]1t,q- 0~笸<-X/XS]tT<w9Œ~uEU}Kӈ[ٜVX"vۄ,kvlȓsF\r] )24j tK;DV Q$,N!)os4i Ȓ)GH):^4)H4/%ʱ!K|g1[H:p;qṭɍAQ$14ѩFi֠5Ek6jqڬHjj( H ˊpByRẘ:q,NQ5;х$%#mIR1} UM&ALfHhBm{j,ǻ;2Gt*aZ8i\mkKYi$NzG8COK>Sh[fۢX/v(bqIhXbֽ kwmB|4?O˿ky ŗ<}vtp8`2r||,t-!STM\vaVB$Wo nYHLdpz:bS%y21MnG(.(˜|ע/s84Mv0t<ŲmǸv,JM`\`*!.sr3ۺ$NDrRs/wMհ-j~Wo_z0pmA7Ϩk1P$!JhUӰLAMXeݞ0Ȳ\UTM)_=lF3DZU5TN7YNYqt}5$qJzxBEEmrp0w zqr3 y{}E]gZ gλk\OEɳgflàa2*JE&3FVe^xU$ ^B q> "iwٲMf<C\}ոI0 ڛ<˱yOΙ5W:ufC_6I1EtEkg6^z?ƎE˃"jEl;b>srbt+2|yp<+ؓj͒vۮhQ$(؟6Qbqi 5BQ%4, ,zIdoUٸk P˹G!hYl6&K!f^x7t,NLt]CQdj5 IgE@ OE$$b4]@o슳M7ԭ-r5,KtZjb L7 #vH/%xqsrz4:&A,in,A%%!MCu P2$YqyFzVc{:ފ:A$K\EXN<ǶMY&!՜~(0LVj\0)@pq>iu8-fEd.q]q}f"en奨*/_"Mc9͒ӳ:W;rYU?^7vu]2OSt*z2 ,IT0>vyg-b[ A1HX$ˌjC/ꍞ1,z8<> =PЧ7n} âtzߓ a P*WwW:*nĢ:L&C9 ͊>H䔦(I0 0lx 7{n7ӜQ EѡpjEZl#d\o]<I& R"r(&|*qFc ɯf8C@ ="n2kxޖ WdiN˴LF\]X]6s&7" ?uk&"~i68GΥ^S՘L&l6޼0IGaYԑef\"-cHR\_oErFجV0 n˶b6hLAHlV쏺o0 }ɂ"SR˷GOιpS r8b`$ eI,wOI,ˑ%$\IPT4IÀX,IC € P?4HY(<.~#WlB-M3n1ki$f0Ω;u,<,tS6FDǻ]Vep[bU2Y,ah-vˡlruyGgoTK^ziհ,k&=e[ʢ@ $%rL$A,ĬՑ\bLSGd@:m[$]t]e^Gd֛i5Ȳ?V_rvPU(Ĭ,TlaXEI|9e(:AfGUu äjaY֮4>;veDBz|Ol6[t]uG9*)6venCX.EndUn=11L:>IUT.V$G!OJt0j<8{LzraH8Nvt"5M;Z,EimQuUk5 4U٬q^̧\\s3gR*e A\+II)댎N8<}`vG^$qUi6ȚF@RtTCS,(X>~25yfJף, |??{m\MEȲ*7{Lp!_evYudY0j,KEј]q+?ĈĮa|''4Miaoo@QF,((K@ѨMw(e( 0xs.`[Bx{3^iZWsϹ1'ᗗWj*1[wѻϾxbI%nQdY8ʚ$)cGs||mո3p{{ݰY63|C _{{ƣrvo{i r3l6+3ȪUa:%y#Kݺa ca |\<'I"DUe<# *p^fXl6[G9{{5$ծ#9Ŝ8 ÀiP\_ 6o_ٔ v ۮ-2p HMD +K*h4wh0Y:Lc(`:e˗/ ]Rjg.z>lC"D!A $?ѧaHfnv'Hrj"qdUVM_.f (;}<~JHejͫ4M޽$I\ESu>ӝgy\m}N]@l6YB5DbDAj ChwD I®TC7MUQr)0]"Z/bYQ/.0LBPu4]#b$E,7a| ݻ1*3_d1AbZ(n 8l,㏞2 #aZ"mmyf(1E-a20-bhŬ[+2<%Y,S<81(ޠvVrXT5()NKn D](@Tnnnȋr1yci*h 77dEz,WKnnQ$c ìxz׷:=0fLhE!FY* GS9Sg^k dyz,*Y *krw3FT4>{}Vvf"τMՑUUY(y^[c+Kj.XOϪ8XF< d9ꪲnkȊ{ʊD.iP.a|?xY8;;l;E0u$ -|u#Ʒ.{{m0a:]Y -'\_c,+vi6m)RLn^MD74ħq(˜~ QiAH#b9͆%ŊNCg(%eruq~lpy%@={7~Uh5A jjµu..uKY)sh:Md 0`? c 1 ?\|>IPTM6R{ߋY|# W| tU&,(%L@QԤ!+2NCZuۡbf!jE aYJ9`TVD1lGٟ9q, ~+Jpm Pmn&P$ z=\i(`22iVwe#UUwjte'%4Y%j?#W @Qe^e)=B!cdC! bN&;U$a@6<5\q OWUڷEf.{?MAjX^hbT/;1 0y5gP3u^Ws6KI^E|1E\P oIRdMEF͢ȁ$"2AulN^}y,7KZmS׹O:aH4u-?/Wqp226yw-6.iE u[ ~w+Eqw;ȊĻל #j"ݲ"!T2a[4J:WWWo+lP9Ye7MST2-W8WˮQs PG~#WJĉO0,EKXoBnVW? \4ʂA$KjN(sayk @RlCc 1:znBINxْ ɀaBX,\딓!ra;p1!)2Qj`6l滯""XUfKͩcL]a7#a%1I~!R>v z#G[ߥ2Xr?:C]'c,ӤqX6 2Zr?`&*^Lݐ1r 2jV$Ֆ:vn ⻗W+ykɲT!M a`- jzy,o)q! ES\#rY6IQH2# gg'139?!DEe |d0%V Ȳ=8dEahq6$/!+A,.nhmdYsp4ove>!I!%ߠݦcoNs )(RF0 l J)c2EBc8<IJD>]B'LB ?nX'Jpx& N (HYLw~!{lӢ!S(m[KL>/ߓ>[d[JPTD;5Jtp777t{vuL _bJtC(?IȫGI%U=$$F .S25 \T]& X5_drťi;W\ }Z xs1e,ĬIhV;~rtz)I1G'̖ 4CsC{i nSDtrEb鹌"AVJLK)lÿSwAdj5; A3tZ o= Cʟ>¥6jlK, ^M٬7L {tD.%,g\d2epwd:O>W_T,$ 9??=NjAY Ŝbq(؋(%.4 0,XR9gT;u۶ѫ7oֶ1L{nP  KXI<͉ E5PTQ4˲X.tJ,HU9/_V+wCjp8°L 0g&IUZD7dL.߽ 1[d?EsyyIzAT&kQFl\.)/^B75z!3-pM^~ŃMVG Z$U6b`&o繒tʳg®[,P 権q!s{ݔݥ@vbBzNQO>'?XTJY!CKMD 4¹YV`&&W栳")$0 vOS!ݿ.Ïva2HޠYuU\4˨Yo޾@[ðH;L_rzڥ4uf9T$ipX.VV?ЂW'"ZƘy8*qWI\Iwzr[UOb4޼y#"eI(EA%3KZ&CiZH3"XxAfQC.֋(CU <˨lV bUչʿΖuwkt-M4 .%?fn }ni64]f]aZajX.BI^Lp4[hQLզJ|wK[:Xi7rI%X5M< tzAU0 naS9ŚczTf _|/{,eb%*vy!Eᓓ z%Msd $ (J ]e:' #=j[.?+4neAV{77Wd6]#UA M8psuhorŷ_+@]gv?T54T(T4޾CW4(!<ꊫ7 F> ˦nXHU]jY@p-t4:M-v="5 c7@-rV̼,CUuLhA/Ȳ,+PvWl~Sǡ"yryrp04?ot,7WNȒkS4EgÄFb( ͎409v? HҔv6 C."`yq`X_V7,' Ľe:{{MTEboCVw:(2?$Gt|%Ex>& >zHX_0<x[懾'||nIgRPRJa!~ߊ>I!var?c{CtFcr?cl6qCIi7 / >&1_dZi6PJvJNa{&=`YQѣCBϣnAYʻ7XPO/HI@x!k%$&0cbܵYoijDQB,?eZgA[:/_2Otjg/x-ӬjnI"14+|l6!|\w2YoeH`oɆFa4[b!=[$QtE0G)$Q7x/_u=!ٔzSdKUFzw_B9yVEN0i6Ȳ[qHEId/{sSWY,ailB/$œT9rFPIL8V` l5,Cg0AGhrnt86Vj5?$2TI$ ]ҺYob&۵Gޖ/hLOI3LO>ua('b%6.-i4Zx(<_~MQ0 qFk誁O]'ڕSdXoJr8==$': 4nP9咢(89ާnSTdZo_b*a aUHb|Nݦ겘X-E4 K$c>z1u"PE;ll6lVf kEY眜hx-Ϟ=#< 7yQ5aYQ숳yhJӥ4MβBlAmo4k]dǬ[z<0 bAUunV}?/avw\o2O`~;Ai ~5GJNjMWWo)58!|rA@`r4C{][pӴO15~l@Uu-C_\,KѣǓʨY{jYVVWR^4i5E<.+L'n(% _.VkKg!3TK2)4޹?`29,F#a4E!`?Sob"^@W 8#DU0fO9>> dۛ8TDZc,C1d9Y)LF7oް\l5Ϩ[u$9=;I&\mih98,AP / >-S{/8tҢ*ĴtCq8p0m.(9~U2[N8_?ⳏ?NjÃ}b& :N#/l@b1Ohu&R<`6 ͦgOz,8EVr;%fmqwwCt;{^M8;>Gg=z$bBh؁݌\h>q?}mհ \#I2jaL4pP)z9{,)4mx!_2ŢrQѳ64bDae.Rq]X,Dl^t Øݓq4beqwlZ[&f N YZ˲b6i[|fv,R5_?N|ۅ~bj!8m8Nbވ^Og>k5 K7/99~1 Y{kTS&tSi;w+N¶xb{TUl?7o0uf<|e|(WIO^"ԛ<}<-*[˒L96>wwwK^`x55ӦniZ(LG; 4E|F2A0o}3z~٣ꆒ(~YWj\}jC;"bMKf%w"b^+ iB-hYZj#'fs޽DEL^nzya βNl{.QG*&wN<~x&X0qlBJ*y8JErwD)~-p}yg)*www\\\i?/ P9ŜbA Kvh} HbVOzN.waP=eA6 ؠm녏*{'H I*H^K< DQ"ȫiAU'/3̚l1Ǩd2e:"Kj7 (I)Qu1MwtD~ P fq 0:YY7YI {eH ,Vs$%$k^*birp!V[>'F$r04S"F.8kb-Zv"uw&Ǖl6ՈMdRl%e)!2R)½, X0jtnnT_0M z^`)˜$4&&$M}dڛZwZqq#3va 5f@%!׈f?(3Fvi;ݼFgkg86cbRhs[=i<|tri?b1Ç e+b[$8wai>Woq}Qzt(LeB%LwB4ǥ*jN__QѨp/~sc8!#^ Y060-.'6?\^rg}/ U;~ןryyI^dhbzxI,3AD#˯߼İ q勗;]9>=%PE=ULF_m|{isYؚ|w|۱x w)ag'eFYӶ iy.q.e%p}}M%ضϿ_|`0( Ld^;>?F fy Wt:"R0M')/}&6 ^~{T.I!I Fo߾`qC|Xfo k(>)'d\@+R>bH7|Aׯ_;kуՌk$'TU'M38@.sq0,9IvxH`:IU ر݄t!aF1s|_0  Kvm~0 ; $ޮ(0L q3 QРXv8(t"[dj,:=7qrrDDմ 39e`@ס7`{T#k ..Y.<߹S߻n_|(|hSW1L/^pzzg?+ȲJt:Ҷ-aiGd2i Cnċy\__QNj Qx$knoQdu"ٳg~4Zj(4I|Av̅czjXp ad2Ui=LYήd0g)WP9uUb.v"+Hevxᄦ|z-WWW$IJH S]F{k!u%NNu#ae0-Y[&r-1%=xzz%HQazgc*d.IPԖ&BDGUӌ#zYVUdElJ-nCh}ey_4*.б݆8h}׿ŋ_c6q3c;_bYYjxuݐW%_}u`}z!e"+I є8L j */~o8N`<bH4-E0ʂ.qbey㺷?|/a&,S,$^b*79eUa Ki &mU!i*Yq}S!B.E bdzLY4|+NNh:W($99=ijt$bnhrĶl5G#vM4%KrLàJpEUP6csrvO~לP75!qpx$E(Jon <ψY˜`S9cYј޲X9;;4j}t3bvJ ivM! SdIb9_ $ԳŜ$I%L#ڶa]7/ytkMeekTϟ?wl6+\%R0 u@J:"i*i1LaO-6Aq"KжԒ[R9U]C+#¨h21]3iMձuK3Zh EVP(qlmH8Tױv!vG R|Y }\' Ob=4ǴYϝW%(2ϿM4ÇKF(߷zKd8,+|snnvyhtDx3 w=X?R$'c4UESt4Eg?M3*u--%A޼zͻ7ao0GޛA+LǢؔPSsxrH/|sX뚋 I g',-ٌ8Ź2L988`/䟢-Ѵ>c:р8P5 k=6-GQ| &Stݤ,WY)`_̪lB&1Pi\M)BUe<}4 ,ج7 TYfv+B eEW|BLe0Sp/b*aas-~'wt]}xϽgDID.acU/阦.P5ׯxp'甕ë*faB]6 z,^q(n-ti[HŊgʿ{m( % ϱQ,MT`UǯXTU?C8yd02NXXbAHUU M'34nT:вZm쳧kfQ--yprچ$Ot:]<Um=NunTK}0WQ+˒b"+ޣnZmDqBk98J ?.I0HӂbC; b\1-f7+=GS ވzK7[,kz-^{M%(Nldydy)|PEYw|dEFUidW_S{] y7i Ij1-,KARⅪݿ|d q,v<OU44ڶeXhvVR8{5kve\_l-IVY,$I!yQWg 6~Oq<P풦rݲ# €mERA}ҧ0GzIȊNyɄ$IO$+X,6\^L&S8;ǛɊFN0 ۆã)&> -\7;>`.u-1bZۿjɛEul'F cx(JFLS~_0_Puw]nihIbK-ab{K/jpLfu~GUEs]&υi֋73fW׬K(7S| ~v\e -MW))ݎG/E*5MU蚄ah be[,ˢ*G;,P5.//,K]`KCՈߒnj:9<~ v;ʦd$.hzrppt}`ޢ*N[N}~m?Gc/~m:HnOE$9˗/fB hl7>'M|Ww7t:…1Ml"H4?e0 vd)Ϟ=vnc;Qa{2|kvᖲQdр-[ K [ ҴrڦFS\tMFU$!ɘt{H e:ȒvU(&ns48{4MGOh{ȪJMA۴HMcXۦHI /PӃ!`O~#2nHQlX-W>?0Tt. noLf)͚uH depϼhf{f, 7oޠ=]$a1b`ZMC8`4g)\\g<Rn)/) K1Wf41w*1HZUפ2;Vi׶nV4:4Eaݠp8q=L]S"CS^~+ iC<( ٌ`.Ǐdyg$iBQT[ĉx|(awpm]r YgH*YVqppae CEMCE{e"z0Ųu.\՗h'93J$lcIRl[>? ~DWv1„zi>nq>quyO8{p̫WL&b$_5Ϙ9>>1="CLGY,oafjhb!KvALͷM% 6wf-#+0>EW {dEE$Iΰl E44U0T\Adʢ#wcY/lh39>?a|p9{~RT>F$q&"!Q:BaǘnGgs+=zՕxۜNIӘwf־˻/~htx{p0lz!ɐ??4EmIAģGH]p}Q'öMZfFۄيdzރw"@޿!taeH`Oz~= j pћ78?ox=~UVyn ?Wh !#NY5fR''gQ`k>e K2Q65Y? "Jm (݁? vi{]]ԫ7P36m]:*UYQ7 ˜$)Q]ztx$.@'J h%|0lXZ{5l%IpzBjÇy{\]]3Q%0%0|Fb5iDJ,Ei/U]b`H`Y6MSEm#S#?IӈLmiD`l{tyR4yzvChIhBUMq$t>yqx豘9>9%VEYFpz} IߌJO°LEQY,-DqV<-yⅠ~[ d!|ڰOUm9\_IS2tиe)TuAƨLY HcMݒ$5Ն{deZt>H u G.g-a0޽Eu%%$ziQ5 Bgxmg?g81LE1 xϋbz0"R.1NwפtiÇ'whaE9ī6 V.$k$\BW5IwخwELK[UiXY`ncs48&N NONQ$I0ܡIrX '<_Ag0$bݎE۶0Mht2ݮ2TT]תo?@Q˜$ɼyg]dME gINUl^8+vN4zu'dqv1Mqce:py,VKLDQ3(RT E& CU]2QQ uޱ8FJHzy`<`^%ɓ(0 ǴvɲbOUh%؅[|ߥsk4Uu= GuNO* z^C1y]QuDa&e~ȇ(**NQլ#3$b@Suw=not|qSMom$Z4M}EWS됑on14aUTFu(NMne~p㈕QF{Nq LmKS4DۈOEݶdEIE8aشb}h@5TM,4Y"Ob],UE]AϦr(u)~oDQACZIK\dDIJQ_RKM+IefZDIJ&X *l7!7J~SSI}sMP w8qŖ M*g)a!cY@ni)t-,[&/T$|CCprhlF]JP ^ow(N mZxv-ސjKF 1T*oݽ=e8SYai+NxnF4bGX ѐeq3I@)|0MötAg뺾?\1O}mr/& #ʲViZ`ZnPL%Yu-Q(瘦$DaBS=q]-&vge)e&<,K:? e59Ml4 =u.L&<],>oެZj]<f4 a_!KS~翢qiTGG'lN^6\l8wԸ&weƻ7o1 !^,dv$ti(&LB7LVQi4Uv"l6vۀcG81MYi1 ۶X.Tex4LGt=VI_}''8kHf:A*TDY)Y1Mb. XP4qy=f2v?i )MӑUh 0)5U;ѶcYr-U4IRxn񈋋 ݆ }&wqlÕekO4$2nf;=~̛wU8MXmVt>.S^|rpppD74 _Ӱ5* b1] Vްpԣ(SK-?h*r|< I ]ߣZ,ݢ),Bj.*mfAjk&=`UKgw[*IcliE;,pJDZ9 2o$ᖶ,p"8u#!PT-Yw:жy*ph{=(=vA@]Հidqďw=%vLUmS,Fv!n$yLZZx~(+UEjZxi?u)IxERV5b[">mCՂvh[x=iE "d@U)?2UMAUdZij8I ys٠f ! R" {BCH-M]aXm#󩯽2/P,Kl ݰ)!m mpv2AQ DVA%" #"$kVxa|kW?~VQQZ ꖪˊ(@ 6), *p.f+uͰ:=2LBOm$t uE-"~UؖؔDYKި,t mOmˌ℺ h8JȊhH%ôm=CF|b=b>i^)A]'WEi}hZ @4d8=q{ ݰr;6-u͜(X% um U)f-ddG,ߝl)"32;;JBĆZP o5þłax$ A]nY\sj%UHHٵ;?"QrzUsZZ%Do9u9HY}N+ #Yx)xoJ?9#39kv<P@.iTuI]X}n eQ2GD VbL񲠼vC" ?'|;Ti8?qϐ\jU|m|elVk6+"\T%;wp1,ı"*+t(=Ϯ| R("q3 1 bUHJ+Վݳŵ/NT\R@Ƒ[hBQ55):RLA1 =nt 9qj{ )1Uڍl6 UUk㉦.X _Qu6q-:N(㚛P-js )5.%uw?F$"E#B\RI)s[,0Bvw'! ~,K_>wOP4(γ*'z;&iRlI!\Hn@7R \Hp)s,)li_C[SSDvi =J{$ݦfTD}vaTzqݡ-!RfXQEg=U%BZK9PW Bŵs/̔J?{_BrkdqΓRb{༽VG6gx񒻻;ztlne70ڑw׻lZYX67ExnNx|8|njUg߰9͖OJ|RmAQy9aXkY즢xˡL{!RĤTu9A)żz s^JH-]?2ϼz!>ep迉1p8 D)Kyh{zGv)[+R`y%R잽œC2| ;bԶ4MC;iZMWNAa> Yv̥u7RV)~åtD*cסTn ysv xa,]M햶m9G27)0tqֳ^gx4M<>=)/_$8Nd]< o]WuMK: 1rxI'\+cFSiV ֎]l6/ε/N(טrW$DrvU'E6S߱Ym1;4MyEA> Huϖ@0C9^K2aF~y!y9;HS0lTb (Qs8C!3,!/2?͛W)>=AHhcH "zST::<>>ru}0Tw5%^0l69lZ4 AiEY׌ߘ&;ͽq%Ǒ-P"@Ĕ5!x60V"ĔXVع|T)z6˗eNdƁrjgZӶ텖y~ZkLip1Zf|e6<=EͧH45;V:)f .by/ 1ږffnGJvEk?-MZִO#)Ea1=1˒&${~Y]7"e:V&y|̲3~A>̦qN)g.eUZݥ /*REh}eJ!9rβ#Zn{}{͊v4z;vL.|t*IV`c$!s:*CUߚ/_ ESHKk?ckS02f ]NeQkrnK]7IDDEuc.؞3=H c@ItĘ~3F8?!e?SO%՜(n.֮I|[B2ۆq<~D)ݖޡ6_qW<>>\>L_8(zR@۞r`.g#,KkO()w(%R(Lg!8q!r<3'zaH$RU.#qWfyW(%!w99Gd4ƹT"`'B*w*׾8-sH\milW+*ԒʑMzFiԚZp`F|HQ",V<><`G)K63}IUh֫tv9>@dsn(1bUq_SysW` !9YZ 9R.GO+9c ZPa@wpέz11:"w0`|0!ݼ7ϵ8馮/(H9. |ECGKV Ϳ'fHxooo=1ZCJnnUUJ)!ּU*1c~ZG ^dk-b֢fW9G4H3BE1_4>_#"ʺRj5̏7,q 39R.HL7)YMDZc{H)QqD's>}xJ)5 /LvS1i[YM+:!B+! fֽ@sBw !{80 &]L`kq6-iDiPժR" M@N(9Y" B;4eVu G4wc98kFu!l6xkE)Eq\O_%2k=a Eai6Y !@sǵ8|\1Ƙߢ(R,KґG~.FR"2`}XkS~iG2ƒ#Of;k<ۯ cdGQD1&SKGC)(0MO9.=nnnpssi{等d}^cK*!$p䀮OE]mhv2s⧸嘣 6 m8D8\uOK1JQLJVm!z>(@۶O(A(iMӔ,{Z䙄5-8 ZK1P/ˉ??)zSuZ#O<%9ytvns73g,^`Ro9NaND竔SJp8`ҭb9/ܝ?K?_8s5c#:<1xHyCOBK#KpBp)/|/* +0RpB 6a$p6kl[u"yɼ~1ߑF~FW^}}K0Gt\9CYTi<ٝȏA܊Q_̚kpcl=㽇|.a)n? R'Ԛ"oSi/}OGv^2R smܗb +c1dӄa'( p e"]q&p?\T}(NYi:V BĩBuwǥs l8KjKyQd!VGyȜ?/9(c~s-Okb< !%! s0/vkȟ bS76/8y{M x-d#%C0?DtKc{+vf7|Vvɢ+^Rg=G`׌R*}/K[ = *\j`Ĩgp#sߕn9@9U+pAK,K07=10y!W2qHʳ#&%[hGy#y^c!:oa1G\H.9I3•,kVD_3^b^q)\;&i ~xQ!eک9QfO8A%>DE k⥒&jﱐHC8|!:xG]hkyV"kfwMBZcnNK3w"Xu/Hn҉;ʹjCλb<ƮI)hJc^vb4KxXu%pn-M",ɿ'Z/Q})F {v$ s"JMӄiP{7T3>-?R;(]DQYJyvr Ƽ{Ãq#<0<7;Xkq{{:ֱQ~TNnUoMyCd XKpuk/K1G&anJsp8̥[YڼCgr=\D!3kऍUbE1BGa_tH?%Tܬu1ApR$ .W<$ %"K0R =?% svpuiɪǘRc&H]G<JKZ[|W"T`Xo\ %$u(^w_9XB?0\T<3Me]CR-`N8/EևD BiIkK]pl10jH/12ShJ )=h )}Np- ZR`}|.a)#bY<<r2W?+rµ,58<[k\1on A0ďUPZyFlvՏd H;u,#>8źdK1|.a/ebyk2咋r!w' six\37/r)Ƽ7(eλb/r'Z%HG_.捼gG~ O`>=Ct>O/p^@O8SvD91Ysn Υ8Ez h'k@$K@.ϛt"turRr@1#bPJ{ &|\C>NT!bfE}N&8tY8 UM}]Eo6ZΟKS5q\BJ Fyr/\0ݣ(j7Dف 9 SэP oW<޿PqҀDwο^yuAQh?⫟} \h-BKCs=6;9C{/x+#N>1|NLυ6- y~ͦg{\\Bk )$@6G܌1LÈrSc8Zࠗr&B -eSL"`Y(.=/*!C;<= .KؠPVI*S{l_bd `} ,2iG%q(KqiPZOPE p!ű7<' i9aHG^Dc)T&8`#t%cbD]5JRHQ OeYuݠ>qBɏ$!bAt,,|n@UUx-~&lx(%GQCq~?AwJ+QAs?ͮs5s\B+xM]Bpv Bp!PiBYh8L97bw ~n Bhp1Fv6O8Md kcs.P6sR*E!"2!8a@>DD) cC3NE ĊV1OyͬBkX;D_|sѶ"`Ҡ`[4֎%Cwa+8cMQoa?`|z?~*T_w<"6/?vr@tn_'0LEc1Ghy;a2F#x'_x)8!0EMLF8Ud$&D8k"9g`F."A֢, 8"!r..C ϔN1ǶSc @U(t 77C(RSGsû9CSmqs @8CFk!鿅ujeYa7B(ᡄ}?@in vD D c,8/Mn|Pؠ{5B!ƀ\1U˜Mg" sG<M9(GBqY J`h`LgU Dfw.bQZ<^jGƤa\- ơuwPJC jvFXJ* r -o08B < :p ,ξ{0JzuLBCK $5޼ZRa##go$e?~0oB(2Z o-W^HH0D.dfB $͈"cP`R|#Pw]%ɀ͉qhi]˜y@Jq89DL!2mSX"cB{r &=b / @%!xB)HEՁ~4*)g= ]4 (q88`a6`}Y3`N| g?80@H:p` J7F =Xqh ^yoݣܠTкge1b us fQV%(J͖v #Tzȃ(byO^K~J,a?= %1ƮP7)fs4( ԝχ,'cRF'ƈv{|aw}#c6%B8TQжlwv`[iݢ) og_|~j(QJJܿesfEYhTE&3sN1D  | B1 F,tQ`wO 1& "IPs<ǣlfA Y+췿i!$ 2f9  xp%!C%|egeDTHIg÷{:߿AkKooćx?,%qfgWBSf& waip77WpQh_}k87zc""ڶGS`PJBIH$LZ-E(U@i ۀx1ƼBz.a^O 6d'1`Ss`,4݈i`|*ʹ!uL}U8#NrnB R? -a~=*na$;} qf_,q8(t+0]*F.IA ""<8[=R\pxkiZB1F)(@3qh?*x.=% 7~)!DBC sh67f\ 01``#8=b8dl@`K;)] P5[HQ7IctAS3BhOEH`G ۯxCۻhn -v  8b4#<߼BYHp37ƈ*Y@#2:X=9RJC =ꪄngӂ >9{1F$ K0<J6gHǯ" L1ӈn+JQ7YuPB!9d`aA%rg:,ojRJ)Rr|\OS$ɢ,+~'P[Tq(^phѶ=?^yϿ94b %MW1Fhnp{[ UhDDrCw` 1B(A{!N1 Z3"pQOŘ<9E9 E,40Es0q]Bp!b9D&F]og3%BJ *^# {0)!B?Ȝݼ!SӇ n&qST͈1(6 -n[pߺ08oj*4 -p0=v@LH.ϣʢT/-b7bAG撬tedp]gskMvS拟m l?Sc?ㇷ-Ed0݇=޽CQ6EQaӔ(b10g^] (d0-PZ%f2hc `U( B?ty06\+0}0Ksf|K]L_t;HxNcKl'cE B OlvA,>Pޅt"0!9C {lϥrߖ6T>kXrׯCYoPwEdwwp>)0,nW  |[Lf 8縼{E@IT1n6`-r pc`M%!=bꪆ,|x1Ƽqҷ1.X竓< /H"m,ZUuG!LJGHQ75g((!71L|493o œ1U(df=$gP ?wYXiD>.5qB_*=aony5f IDAT>f e'H`&>wVPJ, era#0ƀ䨊sp&il% ϫerAJ"i)ED`CnY EЍn;@ )$BCD&65B'G`S)L'HQL!:Si:.gyj1-Bn 4w4܌s?1쿃B5F,rt' Bh nv]g>,5dQBk|E`mf'ѬS2wa[m>t;58ܵR/H4`a^EaE߰YIl6(uEȅ6i|G}-N:gc8WO^`ߗ-غ٧1˷.axLVWm6-s O]f5o58ɗjקa.82-FJ?FWBMܵy;|ޮI۹QT*΢Pޝ>u F:I)a~0$P; fC|4M"ǓfzpsI\SfM_ZZ)~]OŘZ_Krz1ȼCJESZk%I!J%gKyx4_b8J16##v7cSY# IJ2:4dI椬I,n*}8W.U758s-u{(YS12`&3Mvܕ{aH g5ʲ<6 ZZy0YLIbNv*1Q_~3p.õ89ab b\ %ntf4jxzz=`3QBXAe(6I>5݇kqLk:`TJiQE*tDGBR& JXD "<90t\NCrZ&Q;kZ?N"Z#%>aH# S3zT5MS~*(Pi6EeYcV]$S\Թ#\O/8M޾}DӬy6 9(R)9~s!'!%~'cC]קiǻc!δC%dmV>Կp.Im4lcu]'~vӥig_>0$_<C@\}.Ls_:{jUU-6p,1_ۏ'$#YKxP" )'Hiʲa^$/wJKJ[+.afW~-Ndns ƶmz::r>9qS |u'"3Sפa(*H6U;JŲףa9 ݗ`|u:YPzkyRҧC:BK,< %D+W͹)TV8oooqss D]_b'ޝ@ YMI(c,E^BJ v nv;W9>3޿[_8==b ШzCvm(Kq졵_߯G7|-b*{y4%8a0 o׎/H\'t rk3/"Hx)]$yEf\m@tݺ LƝȬb9T7JI\3WZRÀz= 8(@D,Q5iaD$lA.sW/!s{-Np^R;["1uC9*ZEHeQϓe)vT ! xb ( !>bbwٵ8bЗ /H{v;t飣HIo?B[dx)Ƽ2h>0$__ġV:h 9X9bdlQ,ߵyV"̃ .ĘؓARe^0y"U4mvW dVKf5uQcRo ]—!~ZP(Ǻc #N/4 KK9XJǭ-RNaQBufD $&>GDkQ<xc~v;e9d}" &D۶ swc 4 Gt:8!Τݘ3pǵ8sUsV1! ^sRrm"ҁ42IS>זRGNfB\E^f.d~hs CaHb:TOM\un6[DiQp]Fq(Zy`J ކUYOIUYaAqjQ %.qWn|sPRrP5`|qw#.gc9O'OgGt\kg.HV#es C*$ SaQp>|dɕ\|Tܥ8/lפ.H "˟C}"HUHt@>\;,yF> }57ϵ8,^`i*6aH|pY'HEм,!s8qk`J5"\q*xg%f_sm2w{.7~C]RD.CÐT~sS*;Ͳ6 s-NY.Ov]/Wk!pD ޼iB-µε?wmohgvRy۶)% jڹzd kPCsRs;#B)OkO4ctJk/ḧPtQ@YE?0_|0Mnj3;M`>`'۫JRdx&vt|EZkqI :(53*C, 7~ '*JT8f~8$D~*\­; [(1~of 0gɎ}J!/S&&QD%Y|8!Y5_S{4M"OX޿>`9'Oad8Cs^s'|HQ8)B#Z܄qӱ@X>v͉C^ >/x}/οFcDGA!ka. ÐzCC\)uqEudZkX>?H)?͒˦㙜|1Y Xtd8kybSj "Z\&|h*{$}ƛ\m#q(QԣA<.{aH!™Q4:N}LhӺ1wwsY,qE xQ9enj>UN'֟`BP_qc1 Ufpنsx!9.ڲ,QVeZBpTۛd&E`\J$FtHє>#.ރ\3(Ɏ1#[|oMKy$I@tI-dR"Ԏs(!!BD TeƎuy8dS/d)}Ju2̜i&Po\&8(ygNLusJjtVڜP)I"d l8vݑe$+;gȬ bh"i4M[ER'gGɇM+\Kٶmk@Y hȥHfkb]}40v~:7Mȧnk[IY`pjb<2,feDKWP\89noM= "g'wn򴌵q9$ BY%n7.LsfL8B ,6l6JP\1FќqvҢtdlhacRS;nEB0B(j mAVZ%iL9cЦ#\UBȲK>{l6ࡥNI2~[4 bB="nCDȰt D۶&%}6e2$3A"o4 v!:T?<%5+CpRsy) ع= Yz-: !yJOB2fQVxdbQ"tтiu ",<|t0AYWGwa3.^c R0 >Yr)% 't0ǠFUbL@JsO 9srRnOO-Ѡ_r;l6(\΋ aM}.@fSL815} ]bio^goRk<1ƴصָݥch2wW&a 4h] hB@0"3 `zl6ɚRYz v;t/'~וeʥ9ż,L1@:͓4RK. ^N,_da|_鈐RBU:BP<]41rOJv LpKx<ݞy7,kȟ> r:+DB̼RFQZ'ypZzAzOӄᐦEȇim 83&bzDQ&!,.uNrq{WSLI)qw/N$ mQBGXi8*ll ]0lŮD\cd#>~O xNgg:-mz~N9ϣDU6q٩c{~QѭT;0IDATDSB^=R0 !Xszng.{N'D$ὟkH(_9H!A\i<gU'=yO٤E@-ϤЍH8s<{Vh=6g-Vk y{‰_|DkdWq"1[6ZDw>&c e#B8YUvڴ]2:9p2 Ð&.P$nOzeYqM'Y#WҍUݙsHwt3ϻ#.G[ r$';+l6-XZUe=#n;S5B5ᥔC7DwJEd:Fm}b8c:B|5tdg"~x^uZ/8=|; IENDB`mobf_core-2.5.1/barn/textures/barn_3d_small_top.png000066400000000000000000001203311264104133000223410ustar00rootroot00000000000000PNG  IHDRzsRGB pHYsaa?itIME ++&: IDATx<=$Y%vνGfVUWU̐-@2(%jVYa5Aʂ_ 6vfg*+3##{@Xۻq_Wku֚m][k8Z)e]b8N}&N7o];~O.4GnӴ}my^nm$O>F{v/z篾6p{"ez>NuwӺioeϟ޽}y9^ǗO%")_ݷe Zޛ%<<?3jnH7~0 =W:rwyvz\,C)ӧO͛/z;h~R oxtj%ϯ?o6,o߾-撶Ӧ ץG(ԡ%5bZ/vR4MǗ[ISf #l4M˲eY2Sg&CJӢwX-á,%RnX}\N2nޖղYze<|{! us^֡y?==vqpyyy9m68N?'zL뼬=j>|yW_lN8frY0m7o޾l)"rnmqǟ?^w8k_Oϯ/2v3x)ϛi<n曯Z_pӧO4MCu a݌/k[qaŌaqڼ}| Ӻږeu$޼{k7^enw8Mn͌2_.w{y>lpq|tP矗e??OZ ϗPJ[f7ȏ?~2?JJn8v?Ӈ5zup?|~==Gð}}'Dvqo}e<>==>>pv;E,z9/Mw2~_W{w8vJquG,_错OO#IZz}kr׿6w3^RZo\&"<)I-e^ #=۴٬*izo8B 2|=H.˲n/$I)t^z}||tNfE_/^W?4 p0y#8ޏo2^>=۷owRu}4 0 >{z}x2Ek/݇6mqyƄ_k7~7_W/i7~+L|8q爸kkl6SǗpssڲYn"yN/|O_}mF;Rjv2O痗|zZ0Zf^zr9ۺELGD_7m(Zk]x<^:lѡ zvahƗK[Zf[zkk8c74N}u 2{Kwwg*J0nzk,C|>ߺ4MzA&IIGGu^ϯoL=oEOzd,k Ӯ Kq'mmp^fkok~?^/j. D۷zouܬ:w_lݮ 8N,woZpü=ץ+;k+ø?O?/>?m=>~|z~yֵ ^۲^z/?쥬 0 ?㲮8m6秧t<-vC߿9[ >|uG/ݛǷv)_H3/nݤ䥼Lj~nw+vtz;ܭ__ mmpqG{C`%Q:^iKӧ_~ݻwb F$)!jXNS!!ICwF IAbpv[5MRIf=֔z-$ܑ m %\e4#P{v z)B*z&DK挞XYJ7sx*a222 ݉,e702)EmnIb4Zd~_e32ZܪWCe.+M^!M,O#ZkDHD\"CfU##H3)o%htDgB!3hy1TӜ * -R!$4;.R= ]pga2d.$HD$# J@TtN#9R20+F:GtZ.,tztr~kk07o޴ih{~`=7+)nTJҋ.zϕ(3v9"Rg喑>QT60ȠIOWFnI/&-#W5$ IQ\ ꥐ=2 MDFXɈ¤ut3`FDѓNF $dHu !FkFP@T"8T2ʈPfA"O?݆7< |~]Z| j|P"rPj3ےZ`u^0C-uky Kh H!QAu@rKA=pbJȀT 1@R\Zi-$bmѥ,L IDATRDq5)EuRMfzyI RnB it nH4G&I %6”" pi#jƼq1Lz\o}ᥔR~n>|8 Yϟal6˩qelkWѢgʼ(H`eJ 2֞^tE21+%n90$@r<8c)%)RY2oWnH*T!)egMet]nY,!+tI -LR$P(/DD I@;YJDE4ETk)k,ŪL:aYWD-,񝻗bǯҐw"ϵ 7œހ`*I`jq ~@X,!: ̈́$7 Z{@@&+,ERa!㖷dv/ AeK kWO@M&'enbL-  $vklM%5 Ո@J0!w@V=iBJ@eO7nkh5B"jBڼ*21:(8oyf"F7>bޖ6nwxu% \i)Yy@/ `*iƴ$LeDBJtJ" SPjL&'fFRiD"M!+DR4˄06N.=Yш2Z7Lfn\J nPdMDJzfq 6@Dܦ "%y xɞ4DC rC[z@ZP Md'=~m-3rYzQy|srMt Fz?,ü^#(Nw)Kh0dG.k[) !8~E` 2@QTH* Pά^4v6d>T`3 `J/8t2@Dʐ E0zDgfȈ0um)%"i`AP  AΨqcm_kշ0N="JgJnLJO4q3:Y]Z2LR[%9CF~ DEB&Lڭ I)Tnm&gDP@# e @/0==ݔ*`Δ-aqKC&JiNPJ1\&ȁ34&ё4K)(%aJ)ftzA>^ fjښ)ɀx](>o+Kmmm~jfV?Hw_pOe(úk@{,@”/ФH7@f$0`03s YVTGTfZʈIV3H^G iX0$HYC'*i)¥id0e6WBdn1iYJ#p  ^Bn2H[`HH* R:k)#X[|[%121nG_nqD?x/v{zz~/]UR~Xsea$Ǣ}H#iLsFEn`CpժAi5>]V[ &%]$AQcӍ|;9elbNv[Pmk6@ 0B2V؂A,a xE +(SJ%R1#z F,Zu|> Њ5yT~念 ᗗvn֪uyG~a{U\KL׀Zkbឃކy{lLRݓB2 0W־]c4@2"kW -['2HS&Dwq&'2hێQQc6JO23vˈ&bϭv۸t1l" nIe9lr#f mfdd 2n,"JF^R&ot0+#j>Lu]j뺮|}{ˈ^f"KpFb]P!`.N%#A_k^(${e-Mup &y)ؒ8\Xf7c%HMnt;ݚVPYc-d;CV2z]-RBaAXq l[z+qt)#mΏqm=}֪~<ۧz[( D w4+Bg-yɷ[rpIF!s{bg#8˶A e"KB#O] Ȍ5KׅpG!ιZꎶI] 1nq/l|{ϩƒD2VD#6*&GՑó3}EP[0aݧ-R0ڙDCF`1v01pj!g2 }Xם莊L#C&(Sa0śv n`MqtMУzG&U"cgӬAŜ uT@nЎ1E<D{v1ڰj|*&uYkpFݎ6Z=k竵Gmgu[づ>ɄZB@#wI\a^ D.Б#HS@+`Dm5cbxۢN,9FŚˌ|YQ:A=Վ@r$DƑ^m@KX+ECZn6}YF,1J@Haf>.Vt57ۓ{^6M"0G_oGwjTfns vk>|?bE" uKĠ='L[dw `^&n1K0#t@Qn A0G#+ʵM"+" \pi̅2zgsc!=)B' ~NXB6 Xm,ijQxd,;8c.qqۺUs_>=?K}Χ|;_x}}wᪧp羾|~&Yf~Bj#]%``U6im(ZطX0<* >)*㍅Af».;ٱ[gB\iGN`-uX62+@t~b*2Q ܆(EkN! Lrj u FR;؋.C5875*eSr$~%iU}'6o~[fw7QGy]>ؠHLFZK3ƍfEcr,)D#=8n0Ġ7j!;3Rvl KMVXP2 _ Ki<.7j#Rؙ'-.t)d *}ZTrp>Yx<$1}:F 3X9"%q///qw@YA"\Dn{&5 f*sу(Wa:ЛJD&٦;l,HBa;|i{2@2ܭ]K[-ጘZo";(B"3 70LF37MfQ_^^~7?6///?~;mcܞqtNdDHZH'Ajfn9g.ˮ(䲛l.ͥfgL1Ёسz\"%c1䴤伶*Ǚp-<鉰dVЪ R'F'J!-6z 쎖;qN,AȈ!ߗlcqv~]z2a^w:nqU![sQ@|_n1r7|K/\K}.+ vYfc&A9L]-Te]2F`  wXJsΑb7؃P{ ()w3ʻn#2&oV /_+I< 䒢7zhV-遄Pc׻yIδ#8m3EZLCD;Q#^mMloPTQv"Ӱi:a9t5/?6`1?g7':>~߽|}Ryޭ8Fiy=ѾF3j%8%Ȍ$HevJ9l^WŤ[r˞`9ЎL:9Hi5fQ8HPUgQo RK#P*.Y DbrDfBb2"Nu/l8 H3/:V KC=O7[z?~GG?÷~&o{~~ s1Ah\K\v9sXcI)-kq'bKe+Bu͐D^XH[XӹVTNFK,xᑜ)445*ݚb4 7ި-#7w?)oxϳ&vDB}-E(w7 .X]9yN*ζq)4zyyy{{;g츝׷ٳھx_~̽i#H L6"0-m fH32Ks Kה[!NDw 2-N9xժm=uF~Je1#ˣ|0Ѩ =#6٘GVސ Ax{± \@L="C *f#z7vm-jw竹YXo:}^tۿkF|8_?=s&n۟#^St/] 3[lg״਴I0il=Ffy)taT0[$tH CKԊEmVN"{jzY󫽖41p|AD:2$A)AwY#ꡢ皀}1܊@50ypT53 qDFx{M=?(K_ecv_W//>|Xk_]ݶW_~?kCƇHX1" 0a|muvVٍF < IDATd6cc깶 :'#3 n0ZkO;umXa0zv4F(iGsܦUJ.-dVZB|-n#cUp9BftvNI!,Vm6 &|@O^o?}iF_g0><1o3ʾQF+ "ׂ!7 c`m ⚭ )trJ$C${-D5{l +cp$2[[*y{ʚ"%.r` @_^zFb9*f#``/hdW(7  8@4]yr;ۿ}?1n||sȧ1yk/<<݋yw= gM.m}2DHxVwg1*VLID} r#*fdy6k=Iru7[zg:bj:`oB FqDLmZ2>==kcD &Ԏ m{""S={N 3Kp͖7`8ll:H)#Bnd%)J{@?1D"-Nt5Z4L#4""A#pdF!n "-n[3`q]u88uH=P5,^ܚae$.8 Ax4z3W)eH~ctjֲZ@Og ?' vRGǏz}1Ɯ8b۳:eN90g[[vfDchI #Oyz6ǁ21sDD%ku |r8BW-I`M #RSӂ1+ldFw]a Y6Zs{:yṰ)G0@AE\Q;˅}{{TݝL¦fo7zlJpڽwivsϢмnX/nAqJƇ֚`([R@fR%v6ҚXZ|0{YTHm}`F Wmd ~>y&ǻ7I^S2[6cu-d !kmAZ2^D8cB"/`򵦤 1bn\,%-UҞKϛﻻzAz~~߼_:2~ eE#Ad $Z2fxgI@r)reYSFwܽ1=23ϲ E8཮n^` ;Y0LT+VA$DZL 4v@]K2pacmijڑϷo3|5"q <-TOh\>+D3\|G?|{{[5[>Y=׿mǹVGǏ~I1{y4F&Ih)\,6ss\mCL&i&WP]"Z5j8RlhI]liqcc.-a=EUnW?GqbNh)eQ`Dbu;nE+wG [t{{,@UR Ck@#m@x o[aM/[75~HZXP6WUu]"bnOZ8̜1j^>|x{U^kѭXX";YPts<$- !fpM]+dj'dAD} Zk9\deܪ4׶1ZPbOL acNw8I}R j|і羙$N{Al_`$Kp`+q^vUd(oQf7{wW WQ1.y[k!pvX]5RH܊s^J6ԫuk|{Y-C:3rU#rPyvfB1#ݵYaGL.Tӫ+ļTB/ށ]cY˾eD3(b^{M+1x\5tsf CRs6ۑ,PF0-ZruԴζkdlpHB},1bGL\}j'6 ܷ3΅`}Ç~OOz9q/$js-)g}r7e8޿4#< }%nbp&*)¬q$ځ 87JMtrCڶ)8kT6XU)rl*w~G'u,@ p,"0""ݓe4[m!\02hnlX)s$5["B.j42Mśp̡VYNAveśǧǧTUrb_E1vyzy" OdY ]9 =1T5nfWCi` ΢UÑ͢Χ%Dڻ'tS۲l7Č5{z~>:YZ9ruwVNӰ,=W$b,w\6HŜIr/qleBT!)jag!8 SyQuLسqD  ?Fb]&˅1HX\LzתDwy J?qn74VI~+"gVլqM/[6 2b@׎bB@1d~;BJ@V4*5hӫ 6f=KHb,lfؖMA/2n:acsNJb^#:#s *Az &cmd"cѧؙ]֮1< R2[ zn4t*pX\y7_9e8.Ǻ#_繧8P`Gnv{}T;vMkk**Y 2m 2. ?hwFP\ xibq=q6S+2V\(M?Ef5.k o6#Bss%[;)X0f!6/;D-+ٙ@)uqNerŅ}zǗjU1CoCR4^z|?9lE NPt&V*l!erj̘`PRd(2Pvq4"RC&DMt^5\ |ytl4R(;]\`zMHn2]Z&/ZZ `Y6 YAy"٩aqKC-k-kcLU,oxS~r;29З65=:(r7`J ޚ!тYT3 <0KEt|iA;}7 \2a2rhx6q8Ȅ+Jʘ`ۍU; "k6STAqB/@ȁ4LFjD>_)*=m<\lwπlNQWFluyA(0G*8hlCǮI?EWo޾}zz=ciq'PZ & lWHpQfeUjbO;-!;UdM>^Hag[w޹SR;pXy~Hcw^"k5Hh5R讚tobdC ԏJ,Brq!ܽ}B ІN,1TsADk¯͐Q *q{Vx.G,` aUܬGMPaY}/'` 1ױo71kOOt붸a1d<{5[- ?>~/yOn/~_'?Q|MOOo\/m/~y<<<>9g_z#Dh/W 6Y칺v6][@DKHm?6[u-)q T3G\m5;\EhtYg{2TtiTFs]ebpmz벫{5q/9WoGrI!bҵGl Qb5H~/޽{_/cf~r5k]1s7HJ;%e ]zhǾJ~-'`ڥM5j=̌Dv݆6vZܡ~5ѭ$&M\"tQPeg [C{` dP`ㄮZ롁y[;TEi'"F-hӔKN.ѻu sۈxyy>y7)^՗vvwuZm6MEɱM"Ґ2`yްjW:=& ԪR YvZZ לà<lF֜kWD#`v%͍(Bt+9 Vb VCj<|}XkjklH6p8 #D1:.)R#ЧOۯ6/͛7ݭq=B~xxY;Fk犺jw^ݵ;u,~ݐ Aw'JAGšiS@D7UPvv;AFGnsymq"!;\ճӊ=uc6xu:>ki].هz^ kZ?ΪyOΓ IDAT篯^__IZ/ϟT{0&9M RbCڑ~Y FW7!l/0HO#cqQsbz"Kg7vi2*;HI6suDG>{.T@-ur[\^(@.)abN:f.W B F潊8F.߷f"h}^qUq9 >=yz|̵߽xW_ob"a нw\`[T]+$f[^$m Q&WW7l5kZ&neZ^[*") 6N=I,9&W#ꮚ( Κ'. ".roݒU$Ntjm"&L^Ѡ䎞^h3#+"hݝ<[?y{}EyDdÇ_VӇ7ov-/|\ RlռFRad=@6@7 مf'V݊X(W7Ѳjߜ5fF׆ykCkɐZTU;zUmT-Vy. e%ry ^oPB>ƞ\%eD1>'}y^o>UZkٟ߾}] r֢Hg])HDubC#MtKMc}94$^pکJk->a~{ C$$3p rO1 r9vCհA"n6uNFJR@^X`&t h1? 1MF`kU2%暀:CvBwKi6]wPio>|?>!vݗqVgD|߿wO>}\.;y>lPBOjPYqh5Zg.":MU4;{'s!JrUӵվ64N27z=).(XfFwT&"Dӫr*@ .vpI]ܢ/"V rA<"U";.`\5Z@G|OӇuB_*3Wo7_㸬u"f=t7,b됵k^tFq&2ُhl4ʋ4rQDMbRquLI[{47 2I2MVݘ pd 4!HZkfGCZIJ`CfXG-jҎ,!SMf7uk`Yʘ}sheїiIı؂np?rs}߿:oyJv~U铤^V5׳@KDdn*t4Ts,4:7b'vuF`S <̬Z垒fkqK `Sg1(.ퟍPv+㎿+¾K][u^o4nG;lyVU0-n t-{4{NX^U[+`UGn\{qJp}q }{nr=ԆwhcC+]kO'3\N6DsUw_^nuI\#?}W?<==SyAʗ=5J[^-TND1pKOis ڌGhj"xU76y {*P½ݖ vj;X|x`O,t!mBee'YewwdJV 7륻~xNa0Kj(E,1^φmDصD"]z=^޿ǜp}8<1oC66ޢVۖIʹ .;=*uO0BQiĪ}nvDWv敬MܧVH@_^eG~@AQ {yPVӆnmF\62eHU-@aY:7([ Z-5۸jV5`w"RY}%7/g+hVe~ro>r\4$z9F)vѥ-cؼz%#Vݟ7 h/˘1׫#(vhpQ#@^#jNWXɳrIs><|v<\!l3v&=EtkݳevN*1nN*D% 1oP?ڔ׏zF9g?pN¨]@ c.HB4__\}r;D;՜#% aZ랶<=gq~x>|W_<>>f噒\:IFygz8 p2Ѷiʙ]ef)ZXsG`$ Tޢ>v*9{ﺙ8'l#)nX>I7:ݰZs\Ͳ6"#EW_\C5޾y|y0FHQ]]? )4n:ç^Zoe9:|iT5/ $EU!Yk6a;@.c:jmoVX6\!&,`fM`8_pu5a"Bm2Ը@XL"S[i悻FUF^!:KR&s7" {aAc?u*Dz }ѥ`MB-yj- q'؀\`ιVaq[?!9onH2F(zj!0 1^wCla__/9sVUתvyr(VbZ2lu{mfkdwndSĢ{_ FntͮY\+Q^lJ|" \m)5ܳ;-Nkպitnl"(&ZjK>p}aa{ٍa <@_x}| ^oq.8rmwE@W3'8z:j]!)B;RوnZR[!aBJXfPl`Ees^bX^ hkF44tcUWy-ձjϗy h׃C+/D|1[ z"y^rdȔzxx88~ӟ{<͛LFWه l r8cXܮ;`eiv4UtAXcb!ټFb*b-ڳz=ضfpV.D `GIe(H*QlĊI㬶C.'$.c}f۹u^ta{RX7w5rw}rU' {>0_ 2_&tU?~"zJzyy' {Ǐ/v ZHF^DFeZE.6"VcFҴhdwXOPP7:(Eh^OLbOC^E[IaE4zKSŔB#>'xBb`\MǐAF.½P`GR.[Ȫ.:$l9kIy냢p0ޒB.&EO仟̪gfr\$_/ᬗWT;t%*Bn^=Ve h(Z1X>4soVڞ~P:j^/{Y2FBewP{pn̝ۡ]IDlg6kAfq\ȳr-v>lpQ {٧3Dn .k-L9drΎR8Ps/,wlw쏼 o9 d]ꫯ˿OXq7"\9w!-skqy!C3C]Xb^B{/Ӝ RLbhT!6 H^YfǁKj͵è#d w هv\n7@[!O 8X呐 B{23x˫PyxUR@WwaCu kAW)Bvn}!Zk)>~zǏvȠ1޼}\F؜}dU9"rwW6+ lmRȸA@h)Rl@eGVm X.P 4Ks3T%~**sS .lc6)Zr)؉ȶ qAIU#۬ͽZADžn]kޫ>m@ ,nMj dl` -bྜྷ*v ٪lw,ٶƘsFG=< ]= 2iQ?@?A7$eѐ)˦A"@+=ݷ+3csʈ<T9;#bR.9ȤL~-<$5m&D2GS)m˹^k]k}an~[tӔ.z[׵?׿EfϤx͢=! 5"K)=E$J9SHDZ;mH1G&kNѠjFR4GtM3٩%1&hhb[ Y]!|V*f Lwd#f!t2FvoIa.*OEH=b E #T)Lb]b]U#I]€FD""޾キ8qx"z^K)?Rk=NcQ9ϥ䷿}z@KȂIiHvzdBM0I%F̅B=Df$E]swFʢewimy-oLLSk`NE }DoQ-0)(Zfa E+;D#fs j)v{$ 2K!Buao O'pDs23jv$)75ӓ̼;=X59˻wooӧ|w TG.#>fMsb-TWe I1"y' %E@sA| >&9\ 123k7uS1IJxh1?||]'p<&{xx|x'4" "ox|=_w}l}c ~ Ae쭏AJ 9 3 {BԱeYKҽ FN{MQMQ0֜s4K=h}P(`"P}T5"23$??}VUo~ozgŗ_{6)s6 9!PG_YG"cvC>M0 ώb 2`N ¯M|$!Ŕe0"#d"'gT`(1yB 5"Sb %EE|zL´Lt\Bd8D1p0]PH00*pTeB4"Eg_^TH2Y׎뱞1Ƅ><<(|~~z[G>~#|_/O@'cH3]&,2=-N70a!b! `NUt!FxI `T @dO(QLDr-椧Dl倅8HİaY)G!ч%6 qPG`&B%dd`C4S!E{\ 10ƜlnbKwJ9bez==>ܟgm\ɘppFޭ@Qp`SH"XZCT4_{l20}PB᠕b=F&='rY!#xib41<$yNJ "CSD=7LܱŎ9f0=CE@MGBVjRgAZtD.r(EMc #drt ٛ FDx[L{˶eY"p8u9|&t`?y=/!̇㇈46$;u"&tA :*șEZFdSv0V e 0 N|Xdd:GMW^[gВ*5dήb&5МL[t IDATA,h%ZD&"!w Yf<1 %#|P$ QY&vѽ{a=XOfET`keD`R*&"d|zg,RD}WoZJۺ>@OS#c8_2gBx\OyǩO0<ǢaDDcB S}.Zs u\lCBN 23jZw\Q5'$P ˾f-q.TĈi:,BxwL(Ě)ef s!4C-`zTVe]KhNIJhRG $阋)?TmBBEe-"LUDJ}Ovy8/m]RKk}GϦxD0gxbA!Δ`5Oz8"D{0fY|5 OW SuO aU)*3,# UL DkpxNVD,Ii f|MJΥ~)bx(9&/n1(EɭA9mx4McE-d҄l[ ǔr̋ s ) BE#{R߭3u9/iwwmo=eYʲ|ë@A{##$̠HɀO NO\@  1@z&T fPĆ{!R*Y̴$eQ0>+ 9FV"=iҧK'I'>lBOc }2cC1˒`y>͙pI=(TJPª-ae"ˢEaXT{`6G~HqA%Ɗ2ESpMT7L* %w#q$cD[ V5&N"aEtdFvw3 QU9@PUS&+(),sBdLX@Lf #-`V+bX11zuPʾ˺YMџ~zKYLzw:Η[]Rq9^^u9I$wA >|ol@G '5Մ! wbr ղ&Lf8cHHPU2I@a]m Rc@7E(LhP"B]jIowP S:8ﻑ MeYe E00-tE Z24vi)Z]*=|`td_ $5n)2R pAV{zPz:o[[e]_42 ž}Ү?~EŞeF&C8}P2ED"iܝz QF@`s"WFOτ+A0)P(0R H'U$JRLTP$)ZS{fƘ| db#aU tusbfjRED(f"4bxe UY${hY$L)QT/k3ĺV=o EmYR'Fŷ?|cw,_~_zͷ5]L!L S9zLTN];T,STB4A)uEm ((E9މH(UU"@v-y0QF02@UQ)UTZ͑#3. Z*T2'{'2ddBf 3GUU-itL,@PUeĜz*j==!藫/{ -vRVB#"m%#>2߾p.aNwmj|x~_7o?~}.˲m.k-2  dF'#TP B jzb"Pe'&tU1SeIG3 iHja4V5iA,ޣ7d 4RRjJ#."Nj 1?,bj!8DL@iJptw;)A4 mER#Z끶( A9. x$?=~oZۮ }<<<|xv[!ǻ_>mG@ oН: Q#P}'68K-26ciZ. ,1e)q]"Vx.|7_L"pMK+EcRD3hp"stĄ)Fx,ERH! 2b yeDV):hE}G]3pZ0Sګp–URlnEIФ O6P{vGX5GDbRs[p2CFjB÷-<}IDE4}:z@i:W^*Xj"~Dw`׿o=/ӗ?f[3 D%1f ۖjDQʬRM[G IR11E13BW#})RI ZRU AC2MK ϐPo`ݐ97AZ2S֊C("4ћ!VX5a E"X4[[6ϩ[)K)e cD) N @)iVЄ&fǗ?z1?ݶMR#<}h{$}75sT3B0J3zsYDj:ZF¤uDQ&B!Eu]@X1UjBgeGE%K1{/D'V„ZL V Jt G1VW7=պxGB™ \TߤI&U!jJTJDfCR3HD`6s:hk}-.&mkXFZ-'p{D҈$&DB"j="$9JTD00P"Ӊc)-AN uER%EU"ڡ@hNSAI{5AU@,FrcЄ #ʌXFNrYKG$0[)?ͻ1}|{gJ_~|~||ݧOa0Aj2)tl]%#UT`2z;=FDҊiAEfND-*:9Б@/Q M$DB\ŴH1D&- sމTjtP*d"ÒHXaw39 A!!Xm5{c̓Wg&|H. ]HX!d)04yU&DB֜EZID:>j]nɻvߝN׍f$"///;,˺6pޮ7bFHuIDfzpH-q)H& 1RdG[M8rB!bLF&Dlx {)#X5FzT"PH=ifI#E4I 7Rfj#U,;43Dԩ"Iq&2z4Ct!TeRtYmZo{ ýHH9j&1T8!PѹEQ)?,Y߾{Uͬx\{!vXpuHt[L+7zq}ϮUlwxD T1 ˬNP&$)j3zQ`:j,ZhIjtTP5nE:,….LrL UN0\RH-:R=<TB(TZǾJ:H4ZҊtDW=#H kP;4IR"RLS޼yfv8lZ*4ywpw,FH*dHzZ)RZRId0L|L)DPNs#VUKV!)hJh L<&2Q&FFfY@0K9)G ]T(#P fA!00DΝX$#CBzX)*bY M5IZK$hRJBD FF3UC: ֚Gf1ԴIѠO06Jj3ELJ NHA*"E"AITART  AU])3H7HRld+shŘ"6ͳ !A'=:4] |P5lWL@TRE F3JDƈy7Wh2T4(H(2#&}ScU LfN*ϥ˲wޭ>Ufc]H^n*KyYGoF.uuYgc\O~UR̞.Q^jӁ ".eqww<nmE1/Zu=eUyƺ>ݮǥ>gXJ^aY{D,}Zk)>֬9 fF}^ q\Zp7i=+"{&)"m;_DoTA#޿O2s}}dk"riedRJU6|Yw,Rzy^Rh)"ϷǏ|x8=fn_m 쭹.KI]v;}3e=>|C]DRn_ˆ8N.OeYFsQYEUE53k%[kmYq8̜!m Me&k鱔VU;cB-e6S"2 5"^.iko{" s, 2K齙?ǥEX-.ffr{zy1GnWRkApUK-˗n[މphoa]׈Y)T+2}')̗1j!.ޚm#5XL4{}v^ǣַ4ZkR"bjzo*tIo-EҜ"u)&w[_OYj:ݝ2 GoчhMH}L<ݵDDcx|mYׇwor݁ZSӉ^׃{wofrgf[8Z}H1{:yݷkko~Y}p D*߾G{|xXxv܇f7ã=??R8d>|| I﾿^׈;8qn^yfJkr]uPVj"Km2vGf/VE{oOUu޾st8 21FDct*o#bv</˲,\OS]N?ZJn&RdBS[7TίT)bZH3t@jkx,Kmuww/FvK[D,CŊ]ϗú\/G N jcNcxpVTzY) \.Ád]M ޶h塜C՜+bYf w+]7+FW%@VSUm+4tfHULf]̥idRjFh)jZ,pww 'zNm}͈!} .qwwH}(fn6x9}$.|/]zjr~:Ns~u]eoJF><m[17x-6Ŋvw:G3y<Ӻ.'+D֥e~^G5+t1b]Ӂ Dv 3ozbQz|ܮe]_J)omtz'F1$eUrt~oǻ1PZoOO",CUEb~*,O?Ͻݻֶ6 7U]Juafۥ>w/ävxc1a{\WU-(۶`#|_&~wwZNm'DT'f[m}j]kfٺF<3 v1<Ƨ+.ovݽ<>lz븻|q^n hY׵1@.auyqzoж=x^ͷK68חu-R~ݾA5ARr\vݶMU #Su*Тmx@lY{ļW}0ODZ0/_yx CrQ}Y+53|e)zrymȜp(BRSRH]$/$GF1FKs=5U@}<+콿}`p0+~Ӳ,,//˲l|]נp[Ƨk-^K}vNLJ/>z8?<ӧ~y./,}f{|˾ss=2߼9/OwZVV.ݷNnnUgry'ܷm{|Dúu=]Z[NblUTKv90<0} 1!:_ srw8p^u]{q(:Nw_}߼뺊豮eV$"Di9|~kW4LmUrۚ~"חRR~>^Ddxl}P曇c]鴲z}|>wwwjEzw/_|o[e?ܶVcb{6'>ƶW'D<", eѻ @`wY)xRJT'Tc-rsyWCjl{\ǣY;{lmvJ*-#ޏ)} Se.R,2\c(("-m\"](7Vk-Ko/~ (m߼y;)e-gwZ ovﭵ;|NNv Rj&, vy~zk)^DGZCoދ~ìoOZ*"sphmϏ׮k]}n8$meեu0?|0˲lF德w6D¬G[1J)k]J2//u)f֚R&^R(g>^mJ%53_G."̓t/IDATȌLOB9?~fjq~)ֺmLf%9IENDB`mobf_core-2.5.1/barn/textures/barn_small.png000066400000000000000000001301111264104133000210660ustar00rootroot00000000000000PNG  IHDRg-sRGB pHYs  tIME (HUtEXtCommentCreated with GIMPW IDATxڴImY[n>m^YYeWC\X ȞB&  f4$bbYBKBTVVe23E};nW`#kRx9{7?R]GJIUUeIJ1F#RJcZR{H3\Op3`R1 $eYs}}0"MEn$ 0L(ePJ!$|//H!HJaaU'ѬV 1Cp>|d#(1!nH)ZkXkk{Ѹ_GRhp:0F!ZK? JB@5֚aP@J,ͯ]{?ܿKw)p80O?Hͷ)o#,K"_3n*!8:޼!ZKxxx@Hs#{<=?Q%78)` l\ұg~;uMz9\jНAIΧ4Q6 e~`|Vh1m[#BDks)1JIT}L `9(R F$|Zc*ڶ:F)rӉHt)%MPQJ{|owEq8>P߿'88RLk6-⺪֢o_4 >R RJv.#Zk^:;޼{qmS%τ09OYi馑~hY!rBl$RJ/R`'|t(L%D%i]2 UUpssErR`%Ƙc6@|صq SXR4M1"{=D"p8kV)%|槿(eH%-WW->ܳ^Xk]5xƎ|ǫ7o9=18>1 MLPK~<y*Ǒ7.+\]#>x:1z aBp{{aR\~k j>Ty>G8X[s&>Éi8sleGՊwUUm6x[6ScֲZ*FH?qpn{Ru4!7XhKr۶H@8ø/wDU?메bZQ5~S҆sq{燯pS`9yg?]CO{bwa#1Fg_Uַy~z@n%m{D^EkMZL{l6J^p:^oPR!$q꺢}G^~M{GxqΡc e{GkMQtJ 4f) OT}ɸ7+q$%PR$c 1 FpS@<j)f.F)12&޾ƻ:Ƹz!^]{7M(@kKQ|\\3N,9cY#J͖'P4 8xGQ&V+,8[bLpbwLHcn端>d,^amt@i<) b:s۽xi6T%[jE1co1nr9㏾ DYKj%Ųޠ{xF#`z[!x||׼ȩ񑪮x|@۶hm=Z~*K)y1?g%'ѱ^npm4ehblnAw}a_v&PD$!egOl֔ )RkRH0E  ;q)0t= (c0V#hTY2±O] RB>x(-JDB=8(risvN|cDjCQkh=t-3(j63sYUO^/q@J$~Dr>xNHeV[UE8)d<8 eUTDh6h(xcLLn;w^3 ) @à }?2M#BąZQRӵ=0DJ_>3T"-,J s$)D24TUA ֔hCJhSQ%1&.O/V-$4/4Mt:)J+V"D>]dj1Zu((TMRH4Rϯ/{) &J"$Twos>~wwJ9Nx0!&<cۢ}li A qf)IZSì=(B!DYv2 RB6Oa RR$@LnF)(ƘyQ|c m5JkR38SʇH>rXc N߶ڠXx xx؋Rb$EV$2f@a8bቲzuҊUU ` 00i*+ /Ҋ1:,R(%PF!%QPJUa03VJm4}ߑB$J 7cnf{EvI17u4ի *j5c?bZjFST+R\%Ji!0eR"EP!%1b03P:{"&! .}"%q>FL9o[A"Z  Fs, ږزJbJLÈ&~йٟ'8Dqin?0%1y1bFj4DHČ I (bs͸xzѸ3vx'cmIC(%Ɖҙlw7v;H~r$}_ǩf3?@~@ 4iC ?BU( Lpza*ƾG*o|1|Ə qӀ4(lZ@׶E$q$ I]Z׷V*iV50PW Q?L`k1X)Z R*ؓR2$)fBUUb< 4KBd@(iqnx:1 LesOYHbnAXJ1 =y ;c\p/A[)B~9P)% _n4e!82\8Ɣ O )ܯJ}1,YQqa(˂0R3M#F9)#BI6 %ZiOyh[5ySz>Y,?JeŐ$D/xq.+SjlYНNw_O*14٘W4_|{sOzjU3eY8Zi@b C!iBgP.N-3.g,98'e>uhef{'K:¯I}J% d]it(e(z1 %df bL/˒i(HE`Z}mk~y2'LLÇ"(QFgnVD?P(ZQ}.ڒqUML.!eܓBEP*eNv16q6KDm !ɬ\v .!fSR.("aFK~hжgRT?V8X_]ўf֘a32g=H=X* !DOhmRBdGahQJB@YTD)%:lQH+gWZCgE[c!vH56!a98b^2o~= "SϿ;[,NOlwW>`z꺤jTYL g7 qDHB3BC+drTVZa@Д6sB@*ᰧ*lQdi\4~D kC.,8x@~3asu=֒fV`\t^|MIhZ"#i)Ef@V[bB EUy z&< 8N(l֚ua9Xce?\R4s C~UJQT%Jb^>X.vGb.L޵tm;OҚȫoFP< $ZiwLC2 U`A())|řqUQ b)Y"RB=?nk) d衛x|@d~Zu1% "c qJ3AKU"F,7qq'XRZQ-^,( EVu\RqZ*[2YX\ﶄHVJ)1yBJx_̟͞T{Q&#R\_ǜS+i0|)ͺ^UsÍ~NUSڬf( ,?  tAZ#D愇a`!Dp-8(3,1ƼAC R" QU5}עtGHq9N(ǹ=ڤ59v<:ܘ{q#U1@ (l}@ ~;* C#&)3BpXJeå,~s^h|my ҢMY-EHHRAJ͊"ZY̮a^hbo|1ڥSʥ 7R ''D>Pa>>|@LdEVe ]-J'.2iZ38f4eY"(1 |H!2 -8Xci\a[VvYYr:ޱ,kbsUWHg沬+lwh :T_ /;jo4Ka۰mM,_}4ד(J) E$U: $`%Ā&/&3C?R?LEْRX#͖=&0!.%HilY3t-JIڶ]nnL- CReO?0 ` YÇ%3yyxz$Gpġmd׏;gZnj>՜9ӬL=cZHR`+0l9'F1ɒ"!82p`suCYuH3` #mcgKmHnL IDAT"0N##uU dbϲm 9 z(zvw\B .0xih 4Vu}U9G]Yu+Ia5Ɨt`m)ƌ]{ˍvq: _K>IŪH1/KӶ-ۛ+RJy}Z $ePyZa ,9_Ow~L.g3 Qvϛ rLı0Fgg]5݆3uU5B "2y%YQ4͚z9liƫ0 |dYu3e\pn~h\Ǒ0C094(v_cag ^eYp>$" {@e?^7<>>. =eIQU .Zߟy~~&HU4M3oZ-HA^m{ֻQes9㞹 rfiF+D0903Vdzk[LQuŻGϐX]bDآ$DhRa@DZՎ=#eYp8G( j BgXh!OSU)?x/}}|Nҝ;-]#$"T{6C4:Yo=֔T EQn6\ZZK۶ @xzzZ 1~ >p<Zlp!z\nE|/S.jީ=WWW8疁V_ %H3^~K飼vVnyut?aIIps#n8aYmMQlRjEi̠tmE]737,f d#nP:B|\ʪPzcxz&&[S!8ցzW_}v:ܾzMןC+~0BáEM)xسxر]]stƚ,]1~e4x4*/FmȺ(8Obʖlor{[onf{]UIa B*[2⫯bm(,Mr5]?"mIY>!#EQ=*Xmf-k3xK'_yົfe e] KXmXV<== $4<(f<7o;RR?[mqv糂hG6 DxRdz\ OEy}uvb4.&jl'%Ohh䜣JDfgՐŻi6;]xH0 <{ۚ+$ƞOv蹾%*I,_nX3lSbFv={>Liۖ>dWٌJqb..eNyUUWNkv]p{nnn('6C)4-Sw(- 7)펺P|_ljFʀAYWbi.>KEif1"q&R^5L#Ha Uix{]?{GW2 16_[ₙf0z@KKx̓iQFRx|{X5 )Ϙ⇖i},MK=Bg@iK YA?̒^p8f.whG[VEjaY$%T\B߿_ˌ2gr>~v7mבHK(}z.BUUK4+>裬䙏-7}Gw(!x^vW_[֛O{G۞8GOʲi9hOτlr*zY$t_> Y&$!^\j͊ǯЅe&ie5GGԻ0g=y/9c{Z3z S>V͊0p?sq}})$E]QX'71<- f`Q4&K6/wKYmy~J,D)]_}j^#&LÀy*캎&4b5ؽt2]a«cj+Q.Du3<GQUK};BBJAUkUB~zR ^3VImc榯8OPvZ# Xj,y~K~~13u 5E_9M L{n Ļw>R"+b&3Ea*nVyRɗᠬlyV/Aib*9>?)y!Dhkq?}~38w=Zp(3L[J$V OO cL>3("u8eK.s=ϡc;,Sa hd8yP!e.t#g(!0e6mWpMT]V70E S"H)V9n9-]w^VU0ZW$i۞ҊE;N,M<>>3o43?3%9ĪbϾG' DYl`5ZlVv $@H10MFy_y詚 Ɂ.4kaE1Lc@0sL2eD&IR%)-(dJL1Y6N}VMFQe* 3X 0v^;jjŭHם\K*)%>%bpӱI]U@)EUivr3JJcm<̓- bI:C>eY=ef\Lu^qǎ80N#EY"s,BI6M) #a^k*Su"Z(1oȼݮ2$0EJ3Goe$^$Ęћ[||,YV?4 !%|X?yH( IE)˸!"ݼ8r0IdMFj %%1cHPͪPR" JY'DL*c$8O2ОN Mg5w 1ˤDE,.8>X(,ńOs,V3:?y'CudM-5qa۱~{Ö5\3Oœ` ɪ!Ơq]Gj8$+\sE#9NfW5Pc,]7%o׆j{=o#}*rOYkm) [/@?EE7Et٬W?)u=n\nRJF)+DT}q{zM{li)n޿ϛ7oDt}%5xX[ {(Kj>=~*0N\g%w#0o~_oRr\p,0{&޾b^S%üܧhhpVLL.En6WٴeA*tfG+ucR!JE‹"]?9RoP٠̼jOE%?)9O<>=wVk:S|,׷?!Ǎ7oBQ%Cb{*WL.6F#wh`;@1PUyG^A9ݟ02#uM,=5/~nr9 cH#RX\򧉯~Ӽ@S^pb"ν.rP.ܽ#}^a0{M5#FdiLքZ\dGaHF[A ,I6#HDrw'8@f~%ww{_heaHxCtYb g@1-U}¹(/8z"JF.W2,̄6b,Q& aa40(%bƁa4iO,ϱ֐bIRknd;Uk-q3Y_B gg15>Aԗ|vLyNaŒUJ\^buUQC8*6 (3=Zlq`{E2BwI CNeU$jzߌb+`e:qIɋ\QQ`Fg^^< [QLȌ4IՁqfDEaM*Zu= e-n1N Y*[kHXiM_wՀ ||_-6wB-7INJӝah;0"s!z,D&9|8N S* ٔ+w{"cU&G0,Qةe2mp<=TvWKR%Q[4MhQ8BjƢ,EW7qt}G; &0Ty}+@ټ$b~E}U=Y\ IJv\k<և(eZ1ar`bn Ņ7Qp:ێ_" c^޼~Wy9,!uԓ'O8NeN}>+u$"-7Ɛxi r˾yO -M:Ћd;rEM>ʖx<2K1n9v]ӧ ?IOYyoH0o෪k{gy ?tD':jaH84ǵF:?nj*Q,P X0Ñ@αH44cO eՃG|ٮq8qyyAF/{;)=Ad2>XzdZ;QUղ"u~~8 Co}.za&X90Ӕ8yX_jU2ݢ4>N ɓ'CߏQ >%IB<_ rlfD}>J2V㢙d%ُk HT12p%OW+ }Бg#noo9bbr 0 O>M;r}{zL%=Y͵⮛MqOI7tb[@CƀI1 @مж2:iY%#Y&kP%9ZHY6dd S˵'VP*elW8MzQO:щd*`JQ玦g<K@LSķ?ۻ;(;2>!:"4+,)p8LsyyGW%fڌx<2 3OZzqqVۘ3M "f3YoZH}Pg+"u*W+^ZN4M-Hm*q0~bx u}C 7" qVnITLd&֎im?$"P ~\C署v륞ȳ8f8`l[ZX0I"Ld,o?,qxÇMf ÀfC^$"4.}l 5e~f%,>E9ic,=A"-ۖjf`0`A@Y˜qSuh ]JE pUf!2Ra hn# WC|XxM0²KҔ$PJ)m ӌx{Ow]^z%_Bqy!/xFAeYzғy(&|+@F>.x4K-1IHl?-L۬ZcTpͺ5S(+7?䁏2z Xq_~%71,ba4ȀNqTQQgї?qN-|By Zh$YsYy忀0y%Ǐ3o-&i*_ )BivZsX+X۴T|@R`۾| 3wFF>0(JZ_Q;!IsxZel1׬oxQ3C%k9'M}/.a +r|M߶20Kqa:AGSZ%#$ 5J| a@pAi1 Ќni2i'Q%~m )W+-88ZFiDZCYdKjEhY0לqdFbOͿoR\J)Ngqqa!9ﭐ? "V"0h-q ʇ_wm%¬gey4F'4@^a~s&dx?Yk-\"xD,oHCo! 5e7#ILTdA@$8L}G*ZINE& q^IFdybu" 劚<&LPPGr:̧FQA,,Z2v0,˷N!Hܘ'{p>y͉sDZSH@$$VX;l2`2ʲ sf|4-Q6MSh%IqDm: 2Ҵl,Ck艚ښ ݳ}>u$bsuk96[$iE-ʇÁzz?oM9yɘn i=,uL .R~3jYa8 P @۴Dʽs[Bn2IJQ82 IDAT m#! xls8TmZ:2ohgcr:Y^>P{3~0/m>z ӛO8 n|1/tㄲ0L8 +Rdq^]7PBJfͧE[| >eN]*K ,+u(A7גm.\y5'}I s'o O&l~W,c} $~ka>'aUr)mߋF,PkU1/BȦyN Fi' 1T`e&"t$S7PV OK>Jz2->k,W~&{\I:$Zù #Zy!Q1Z7>~3I9~^4-7\.^(cTބ(e^~PPLڶaOGua~gV+قHKX?5|9~*~sOEiF]xbyiŽgaD=Ln#L(?$T3M}GgoK?kDhS2#uu\rwc5uhFl1lOvIQp8R,S rucBN}/.AFE-^>W/K<ߦh$jz`j2M=m[s恵4(bGr/:ϟ/|Ƙ\sS񛓓/o~)sPO4Ω$-͈9KM4ǫ۽ӄ}jV^* vQm}vK-foa*aZFy@4a.QIq΄.@O2ܴ@5q` ۬`4\)s4iJa,xwQ a*cݒ' j Mۓ$Þn"1z[]^4M< Šގq7H`EƊ_<,áIt϶MB0:ѧRT}'z:ѵg,'Rt((~k0Ɛ ֤~(E4cn.d8M^1M(|q?8vAHߏ$;+xaE` A٦8t 5 Yk9 Us$tWWW>(G{,>^}o8DNlϧq՚~5F%}5MHqd&+10xcA#fOJM549Q(h۞4Oʎ "L'$^g8 d/3(qւߊF1d( t>c|>U;vW4H+ǟ0Q_>zB׵tճZ?O+]Y)W(-04Á1DIG^ @D!: MW^䜣`lek* IN%D! #22ڞ楯?#SEi9N/Ć)*GTtҔ4IcjԨ9SW8\?],nG0*G*$/ ^%+ L ͒7EYAna%1NPո@h&C撷Vdf7LiF( 8BꝲGwwNGFuUa2'FtQ%y/ٺ>S=?_OOyBֻ ./ȵkZy`Mn%&~$|F9бKNo:'?CVqyu%YQkۓmc4!4^I)8?ɶ[ nN9( aR5:W/^ʰyMe4wU~xYBߴ?C CYT(5eV?ggYQlwkڪf=Ϳ5A(GFH!=Ǵ)ʒ;`wq''ۣuu$K3pwwGI Jx oGʲ\ #_ nZbTLitl:Xش)'ZVQtdx>m;6%Qq-㣶m ښժ$uUǑW;O>K뺦:ò %2۲f̛0Y~>OHLG\YqW‡8#,YK%ֲ wwwK֝"ϓsϿWQ:ר'q ؅>Ǵ*;o8JvKǤYN^lv`zzZ0XkUAN0CE~ApZ ̟ ~=gmkÐzMww|g( :mO'4|>!osz6Io6~;3`# I"('۴w !-LygWWWK4N>ZϞ v+#U"MV+Ii 1͆dPc'C R/EԵ,#M3JM("3d见 U uDE3n 3Jpd׵@Q\. agGQxijuz`otydi}QD6E6#Ax|ٵbyՙiD?O~yQh!Bb@Y$!{h*ˣi1ᬔd*-IAՊ/ _lo&z[ʑ):zp8xx J2JjItyE2$!W]+4s]/CoqƼjBY}k, "G}mDH뵼 9躎}z89Ţ(ZdhPDf"20(dfln8t<~%!jVtk<:©y< c$N'iYSV\'|BBBRu%e^,Q6T PdI&.wԵИ,xx4 goRs?0@~n6wVYFbm.[Q~t$ŲKd:T4ꭇe>$Y4fu8Ҹ @^Mx|>Ӷ"hq . @L䓿VfAQ@@^WzMUWd[[@OPՒlm!LBz83>}SH k2=gϧ t;Ya։Uqw#vDAP7Lm$yY13ѳJjL`3t2p,ׅpY|5*]kӴ@d2 6PVÛU$\"5(ŲuYJIz7*Y9w*IN$\^\yOR:y59[He|Q J%oˊ3vDI 隖,Xe5#IKJ% cTs}8 y^ :F8PGTjYwS8*mQӉBH[&HSaݱl c4MTj^ĦSu=8SISU"[7~vnj [2JkMӷLaDŽ4 c[%e9+.-Z\eaӎ$MBVj 3J79*4Ҝ) nQ_\\p>e4\ָu5A¨n"X CZ _g>d2<Ǒ˫+_xL/on~ f X (EiP$i&&oS'W^6&!5o^K )f++N!ä́dOӎ#uPf C8Mkqf[~uoϊ{p񀓇H7; Xt@i,4V /Qn8"saDa.8rXҼk$m;1AKS|(NA q$j;38R:#hl7DyAUWTw{vd|MvԴ8 քZ-qtH]5DQ XPT8!؎RbZk[ bx"SYb9{t͂7UU(8ZNoq_ f|n\vdĬ)f0'"BHLG(*K$(E &Mtm mC3P.//ʊlroZerpсE1$[C4-+ь1}#(fʢ1-EY|>U>+n&4{?HZi%h+u~7J ~۲m}ϟ3VfGY|(q5-m[s5 H(%ќbs첷nۆБ;78w#aQ;lݩUţrDG@'OLF.IpeD!adYQRqG>I76DZP8Cp:Fp-( ./(rqV+~ ?OAh?#׀-)yЍ㹡rlБm05-E{*bD&駉d w77(3+W:倀cLWq8|!kI٬8jXc9^t EL,͚ͪŋ\__?͏ \n=ƳXfNVW(-f(:\]-㔦m)(,,רּ.i%"&0RdYPLEϤcI\oL^q|<#L{ÄG<% ՔeAkY{@&\idUOs會<9|C&c/|ɓGr80ay"I=| ^_񭋍l6}FyNZ~|Jx"|@_矾SV\):V{y2->;M a[au>cY $kx:qV۾[!a7`A3Hg#kΙ#Q: ,B!*6l5,gἫ*IA/^ՉEˀYbMt9۶Y ֫]'*[VQFN'ן~JwjxW1C7\__/\鮭0o^cwwYf_4 ~N>Q8wSΧ;qAKV\hT(꜡,hG&O/25/_\:G2-+.M=|LJn6Kfӧ[{/E&z-(z'kF@8T$Ex>զitгOVTUKpqyqhB:#MU1 #u[/ig^mvkgϞ-n6\]=$I3ڶ\75-s|x<wV5'Q̓'OQJQf0$Z.//چйtg8XL:]Q)CSVpzV(a,(qq3nފfچwy3NYqP0ie<$ Fۅ.W^6d̳4n(+)&]2\ 5U}*EoG$V .cC%mqy ~ /2no"Tm/F)+%n޶ IDAT!HLl]Q>f.8aGbYo/ g^zjM,J,ϙD?_Gk/_4ͲJ9}ߋt+iejWqzζy笸n9*ڶJxX6$@aApp'>;eum{Njj/ E^Ay;Yn5P /k&2 QJZ v7<ٻk})HжPTØ~鴜i2J sujּY% tw]#_Kdkɲ0)|ӏm O'똼玽|m U,TWIπyL&)?@!X\1mGuw]Tl-#wwʊ*ǫ/IreY+'`^3M/Ƒ( pe}mb!>x@F{t^A<n2if2|嗌j/;}vnE]mq_K- KfTN8H4QȓǏV!xyyI-,ˈ 8с^h\I 3 CY=FX:y~7mPUQ141,ϸ Q3#^"K}z1En勗4mC붮&NYqw_CMXl GӅS_fSUg~;H;,'ID4x.Y|W1W3DZtY8kN9~%of.L,/e%%H!]l1nA0GQpR^~M `!v뎶:.b0 m)E 49qRU2ZB*r1T5:o!ayIӶ$ߺ)˂ $Xo6"*{WM\$ F\,J622ΕגulX" ~frw[؊~_(k'~၌\5!t42m{ݚwe_ ]sfZ8!(`elWkVhXHk(=:zyhgڨaS1wwXk/!dF4#С^@ҌjGImS8D/X\=xp_5}gGn ˜n^xf#K7| n]ďsV+$#y&!c-;noc'/v"/K|sX8,Kv J9*# #\(h| 76sz^ 0 i4͖As⇈&,5(C$bY10www¬Ą<GL.qؑF!͎/1iuJ? O>|ȃ϶RG?de,h| c&KQr y=:DŽq4-__2.iRKi"2~R}1#۞/3J߃D4<|n\UL艡;<:PtTMCH4 ^b{OHUHjYu=yJ2$󯘦~IzV^=9`S)YR(XI_Ʊ qsuOgy)U%) p\PfgNlf~r" ..vr3E[0`p:~٭ V麆,N 54wJ|{Zg_Y7[iСNV3Y(7"Vn:kx.tsI& y~O'6ж-wwwN'>#\(Iе=y.fCuxwpĺ'Bkkq0Hg  ݹZcm ?9|}i_wg_~pbnnoGL ~^uXvZ8khwJ\\^bǞ* X0!p$Pp} 5VH3cZIapAeAAL~wޣ|` O˵ԪQDcnCYԣbXYl6k͕p~J׵c͈sP֊,̢r3 JؘTHg"2Χb'?-ۦ>劺軆c (K?nHXV)F@VWWEIeK_)>#AƎ'pN@Ng=zHÄcoqCiXy&̈́%)q.D:C b$X4}t5?]c-<-d u}|G]1ӄR8#i*[nooX/<减33W?- qi$P㉋K̎~]V+C 3^|5\GovZDJmɨ$MԢ9ϬbgGߧ(j\VJf!,ܫ_s>K^K,Be5wW9aѥ XPWWy5eZ8}kY? m%q3[- HCqĻ[9K0xozwѽBmZԏUR*lΥͳ^=#J<}"PO/?_zT[W N+-1iUxOȈ*IXYPhl,k-9nFC]4U=ǴQ^nZ4m>z|iJ1KfSiQ])Enu]s:9}%Ɂ7TEgIw)3}mKHF64>3~_WԯrWwf"[ki뎗_qhdY?zǏ8,a(GG 2=?|N]U{H{AJ+6JmQ-x0ޑ)(@UUyQJf*01vwOvSV_%4Nk%3o4V]{>9E%$y X-Fv9Wc)Rh+Iw/N,1Ӊ$?>h#$/8ϱa*k) 8q|=Ihff ӎ9ܢ]S7-H{ ב?Mr{4/&LXp)O4i*e4-+-=՚#>?gd{pxbͻwMOUS+zPfc)o߼SO=#:=kY6Xkno)81- ‹jEѵ-EbaU$w*H߷/h3h [)DWU 0hۊ0<A Vb /r\~iZV%}C) jTuEn R5}D3"GD1Zjsm9w~ؠk9)BWg<X担T4OE<{( ӧ*+lp5I4-bN`4~ 9Ju-LmgKwD~r'WOP5 ջ=|ZCs=A ʶ( VDIB?s@i',7k)'ʺo:4ja/[IjQf\JpQI52faZw飼 qD!dwTi!h ע!PC2NxZf荡S{9c{~:<Lh &ibmX#4)K6guX0K ^`xfs5>E^9َ,bI>~XyW l"T5][M|ZCno,NZB)74sWiۛkTMIvdiJ XNqU$T%Ir$MMuco5vJTh'Q3FBuv-X`BZYOcc'XsG0B k#OO(z+WU0ZuQ [ @&DX^ |8>|ZC`?ERaJ:N]Wp#9OQ U]Ep(ˆi/1 |e5E$dr4UM?& |#M euBz 4ď&e2@VI`B@i- \(9aZMӰZ.hZe)j'sW״CAĹr]8xN~4;Il)x:$U/çe> vMCVX#bi#c\9_+Gٞp iM'xkY"(xʘ/84yc5b) c`Ćڰs{@Hbѓ+ 0:f`%%4Ѧr&c␀8 VMq=Fں jfn "烜_X.$%$ENGR?q-u#OfŁaES*{CvlTZaiZ#(~̇~k }:UY 3pP}$LU)iuY> 'O/e4Z+蚊( jGdڞ)\# =g m2]ACcI \bN;7Ubb{m 3h Պ$F6 Jq~H}o0|k)ҥ3/hN2/Jr{Q;HEs ŰTc,A&N~)nGy}ࣿu<ǣTweH9}Ȓ)@{4#K3Y4,Z+<ÂVf:ꭕjYW?,<yv!̖0ׯ_G Ef3D,"`OJ?Kq?OO.'(nZEhۆ0L& 4k0. YLiEbdh=x6Ʃgib](QA<5{XFF؞ߔuMC1P' 踋xJ(%Om[iƏ?iDQ6ROPf-;ZjbAt<}7|'ыOZQěJ\h( 4E^yvȥi|:EM;B4TH=?-`?YV,]Pn'G|>$ﶖOKCӴNIJE01!"KMY~uQGѸF9(oyA@ocְL#zi"jk\^^pUyO;uU\^^Nb5QR"?)onn}Y#4o1=AM-kaVߔ5o]pW~(/($izapW|4Y~@' )70 :^1^QH2799m0+tق8:7 Y0d9tJi[1__\QEn]3Ң("aM4$"jPOcO $)IŨ5R -ІCNj^q4v*>JSlT^v-ဍf@v̒Hh+y޼yËJYAhڂrN}'t'K6yo}wׯ={d  "\1Zu-u( m47W̋i\klX#ip18r^%͖ n[:ez7rODPgfC%e=Gn{ fk~d&Oƨ{q=֨#mImb/'7"<Ɵ@ۇC ZlG4IaPh,'$sZ^}|1^<5 zNiHSV7Du,s>31\\\p}}G|)𣐲< {ᘡPE B(nR8xu1xc~Ư / DZ^*% @+?RYʼ$NbVJHScV5m1[.%LEQk960}f*{Mu|!b-;䟠g^}(֨kT_>G~eȖ8β|ݎ7t)2Ӊ,;r<rRR/W+.y*'}?6R0AeU9;%Q1;94)ӑ*6&*rhLO_-Iv{Q !;끮ewG[Q=asCjP3t#s#)#łgϞqsviGKoB?3icvhzLpd7Owhʫ[;uQ$Y>aM R;Ǻx jX,WS]kY.DQ񰥭+x3]@Fe{>N9^cFUdH}GP+?&h3HTU3$_ q|I߃]Z,X,i\.<)u[\`GگGga޽<./.Qt<`PR oArs'Ѷa2$펦,%H|!GjqJS"|qFHa$Xk'y2 J|kNFVmn@!%$K:f J5 !J:[MZJ)\5 \ǣ,#RaHFX+FXYhrh" Jz}]bwIQUlx7"qHdjG .;aQ,No Msww7IX4WfN1d] %4i1.@S(/TMEibjǑS޶ПL1b=sV~0md!-ĩ(9! xFYTty1eqQc[ںx8`fs̒woÀfM1|z,K%جc%,(JSzyX6<~lˋs{~~֖&-"i>:Z雚}A|kd|0*jjM)I ?>}wg>ècuc獜l=EQ8>]1 [r߾ayiwF!4 ]_ 2 q& |9Vk{6-يGWgݖ, 㐻=' Pl6Fۉ0ͰnP$3QӬV+" aM!OxUUe xwE/07X_ n|4M;1=Of"2h 7EZMӢ =eYF!H/Yg>_SW52%͒C!$!2'0!\ه\UZI7i=py\$[/jN#$B6&&@ZFS{PGC;HGlI4R"qw׃}([Ɋ|xEM]m+~sNz6ijLْخ' T'yr<9{|9vR)~_WDO90dKRP'>lN8'Ks{}Mt#zۯ8tOr'ӑ"YY.Vc\R65s'FgMUW idwB)Z$)%:քO;m(6ShՒTҼ5ഥ]GS%mWsqy{uԵp顜r('f"=|E!e+N8hOTg8ȋbR(LlFUw{\";dyN`Fnњ' dŴM' pa1Ȏ(O)aq MH]V&'cpF =泘?s2H9dY>u4S$ uQ)OZB , mQJs8KPe<_ mk;,;woH{ -q" ,C:s{GŜS kMW{>^g?3>Q 6}3_g|n6tNW=`$8K]7uGxgp86h+8RD qGcX/gc,?ѵŜ= _~%Q$k1QWu G1G2K{am`d!YvR<}q9aƼBf7k.//9RE~caZHvQ%,KQ<8&I"8"RIk?ϓ,;Owz|C+Ɖ̲ ? 1N. E= mE ъ9$yJ/8EzK\{ȲsT' 釺x8>hCH r}At!LSuҵc䘯p8'^Oݱlbr*,UU5}/5NuX-.~{K BL3鹿j[1yV}ngFGvoQZ:.H<{`C>k?6JÁ7eJDR:B/4VA\Q9A)H" `}~~CiE:?}Z,%M=N(&+Ρ$Y2_qqqjzR5F#kdd:{2\.ֲf# iJCH,P"!Rx -ioӒqc{f}"Sqagv{C(ʌ,xriO߶X\yx4C :$QG]Gg;+Mpvq5/^H|hz'_|'vAgMEt\]]}H |tprv~ɓIfsNWtml6'- =diF]V_e9UUXVEvKvXm9A()ؒM7<`Lq=Adp=ft8("}G[xOd[M(^zJg FsI'=^q3 coZ;榐N4sdYƢ?A?, ifEE7|ٷi֊9;;p8S?S|OqvqE%/~^| =yƟHDpZ7|ٷnw,+?~LD](7Ѯ<ǟ 傶.Y,$ȋ'RUp9[ѻ`eXaf(͕^0%4MMzJ␻kB?ďb }@^QZ1%cMRKѳ\.?lEݔǦ's X!>Q3PFM߰/Äc'px& g e3DӴDq,Mzs|!KO;T24hey2~jmZPfPB#U+0+n߲XKﮧwk4/> as'κd77[dNoO6-߾Īxw{/r{hlݻO}x6p8g7_,8۬q@<Qr<x'v[ʲ=5`$Z,gr |6gg-jjBC,l6TuE]Ҧ9Ck8;R" LfQUM7hel٬Ă`-r?7`RBt-LL֢c#lZ89syt]OX?`n׵-V~HFx㬺BUU(sз0mOJ񳟾G2KPڰZo}Zs}sGOh(\2_g4mw|%[ vepP_KQ0>N|?bsqqIkuYZ)j ]%gg'0 Ҍ,Kq{%f@:۞)(-!Ջ͕Xb~_ٌPFš={'檪ѳq>;?*+߼TUQ̣+_\[jooIs:@iVʲDo g (隆l=O<^UӔU3g8Li= o8I˜W_OoWXm8K^<#Ylh tw16 K|$eEB|L9o/Vs52-W/pΠUϻ[[-_. \S,bֆnE)kGS ̽&Ib =eݳX)>@ ,BJ[![ߣ3\_%G~K6t *AوbIZ67WkQ#}oUA$󄺒b&"hW3_l4z:͖8_|$,Ms< h;7þF1}]vO+j=DTE//@!J c-u'"DofZ(~w7c%77Vt< :P/pysa9ZhXf!aϳv(^=sWun2o DrA=u]I)+ Hs1:g0ZSK$sZVg"xgL1u VbZRi㐺( ?ۚ$\ky /k)K(B{C{ezN lGctTş]PCqqy5h`TM"i0VQאznxAε̗mqUav ם?Q9ZTmޡ Te2 }K[ie]CkKxuCM*]蛒P}OWhmi$ssʢBmCǷ3~V }ײo8;GwD^K5Uw7S ?PDcfOMOմ`qFAh ްzgG.0gĸևNl@` I2HvT>Q,ir5}CVbd|j>/VkkѣMzsݎGeCq:ʢp%̰<;ʏ-yQG{yу?7d-m]`fM]u;YDD봧A(N3mʣǗVE#/v<hsψն--iI ץUCq'Jݾ$ iL]Enwֆ O,(w^R'(U,% ?7J]U0dyAݶ]- ƮokNYE4ڗR~K'nOzJgK<}iՒvq (۲^/b(#ʕtyMoLOvOZf21qwѡEvڏʊg>;_Pde'᚞Uxn(-]h2 چmb2?s{q!oZ M۱4Ŗ^]eق0 Iqk%Z:-xmE(alSF}3H[B'X.lPFTuz_k0~P1wwak8I~A?2NiIx~#񸾹!0=ISՐzeR?sgoѝN\$SF̽Y筵A|ضhPʣj\G ]cˈZs`-w@a HW@L-Y܈ITD]}qtum[fe\8)g228PIL&ICnS"̆ eN_a%Jtፅ E4@s |x{`m(rgW( 2G@~Dx .u{]yHaH=&+ Έ߹.]*1ƤD~)S_K0!׵m>G"1F })_9G۶7CYThOvB "`,z;`4#^\3_¡)9^>(ljKxiFeum[9iUJ/Y7Be/yRgJ(G._>Ԫ?͐'y,a$OfxM3,9" ^\Zۄ%@H! 2Ju"L!$J":Ţe-Auk2Hk?32]DOfJN:jO-.vWƁ,RݎΛu]CM_jw @<(0S_c-8ȳpB0z*+C]W/V͛7zznզ$K?jbĦhHx)00 >,Sӌqx<Ȳ3[b%t1GB%|t"_f%)dYHt|^h!ƀuuk2>Wvۦ4:fGpx\~]Y3K'F4qSBpp1N!Fɬ&lq %tV $AQds{ އſ\49:XeUBs~? i qYo)^>@Si͔,^_׺-sqєYlTrfQ}#f!  =ab`L!02M:4$8$65fL4؀񈶧`߂B2h1x/^%" .. ":" .. mob.modname .. ":"..mob.name .. "__default" .. "<") minetest.register_entity(":" .. mob.modname .. ":"..mob.name .. "__default", { replacement_name = mob.modname .. ":"..mob.name, on_activate = function(self,staticdata) local pos = self.object:getpos() if pos ~= nil then local newobject = minetest.add_entity(pos,self.replacement_name) local spawned_entity = mobf_find_entity(newobject) if spawned_entity ~= nil then spawned_entity.dynamic_data.initialized = false if (staticdata ~= "") then spawned_entity.dynamic_data.last_static_data = staticdata end end end self.object:remove() end, }) mobf.register_mob_item(mob.name,mob.modname,mob.generic.description, mob.generic.itemimage) --check if a movement pattern was specified if mobf_rtd.movement_patterns[mob.movement.pattern] == nil then minetest.log(LOGLEVEL_WARNING,"MOBF: no movement pattern specified!") end if mob.spawning ~= nil then minetest.log(LOGLEVEL_WARNING,"MOBF: \"" .. mob.name .. "\" is still using mob internal spawning," .. " this is DEPRECATED and going to be removed soon!") spawning.register_mob(mob) end --register factions required by mob mobf_factions.setupmob(mob.factions) if mob.generic.stepheight == nil then mob.generic.stepheight = 0 end --register mob name to internal data structures table.insert(mobf_rtd.registred_mob,mob.modname.. ":"..mob.name) mobf_rtd.registred_mob_data[mob.modname.. ":"..mob.name] = mob; return true end ------------------------------------------------------------------------------ -- @function mobf_is_known_mob(name) -- --! @brief check if mob of name is known --! @ingroup framework_mob -- --! @param name name to check if it's a mob --! @return true/false ------------------------------------------------------------------------------- function mobf_is_known_mob(name) for i,val in ipairs(mobf_rtd.registred_mob) do if name == val then return true end end return false end ------------------------------------------------------------------------------ -- @function mobf_register_environment(name,environment) -- --! @brief register an environment to mob framework --! @ingroup framework_mob -- --! @param name of environment --! @param environment specification --! @return true/false ------------------------------------------------------------------------------- function mobf_register_environment(name,environment) return environment.register(name,environment) end ------------------------------------------------------------------------------ -- @function mobf_environment_by_name(name) -- --! @brief get environment by name --! @ingroup framework_mob -- --! @param name of environment --! @return environment definition ------------------------------------------------------------------------------- function mobf_environment_by_name(name) if environment_list[name] ~= nil then return minetest.deserialize(minetest.serialize(environment_list[name])) else return nil end end ------------------------------------------------------------------------------ -- @function mobf_probab_movgen_register_pattern(pattern) -- --! @brief register an movement pattern for probabilistic movement gen --! @ingroup framework_mob -- --! @param pattern to register (see pattern specification) --! @return true/false ------------------------------------------------------------------------------- function mobf_probab_movgen_register_pattern(pattern) return movement_gen.register_pattern(pattern) end ------------------------------------------------------------------------------ -- @function mobf_spawner_register(name,spawndef) -- --! @brief register a spawndef to adv_spawning --! @ingroup framework_mob -- --! @param name of spawner --! @param mobname name of mob to register spawner for --! @param spawndef defintion of spawner --! @return true/false ------------------------------------------------------------------------------- function mobf_spawner_register(name,mobname,spawndef) --check if spawning is enabled if minetest.world_setting_get("mobf_disable_animal_spawning") then return false end --check if mob is blacklisted if mobf_contains(mobf_rtd.registred_mob,mobname) then minetest.log(LOGLEVEL_NOTICE,"MOBF: " .. mobname .. " is blacklisted, not adding spawner") return false end local customcheck = spawndef.custom_check spawndef.custom_check = function(pos,spawndef) local entities_around = spawndef.entities_around if entities_around ~= nil then for i=1,#entities_around,1 do --only do this check if relevant area is larger then activity range if entities_around[i].distance > adv_spawning.active_range then local count = spawning.count_deactivated_mobs( mobname, pos, entities_around[i].distance) local entity_active = minetest.get_objects_inside_radius(pos, entities_around[i].distance) for j=1,#entity_active,1 do local entity = entity_active[j]:get_luaentity() if entity ~= nil then if entity.name == entities_around[i].entityname then count = count +1 end if count + count > entities_around[i].threshold then break end end end if entities_around[i].type == "MIN" and count < entities_around[i].threshold then dbg_mobf.mobf_core_lvl3( "MOBF: MIN around not met: already: " .. count .. " relevant entities around") return false, "not enough entities around, only: " .. count .. " < " .. entities_around[i].threshold end if entities_around[i].type == "MAX" and count > entities_around[i].threshold then dbg_mobf.mobf_core_lvl3( "MOBF: MAX around not met: already: " .. count .. " relevant entities around") return false, "too many entities around, " .. count .. " > " .. entities_around[i].threshold end end end end if type(customcheck) == "function" and not customcheck(pos,spawndef) then return false, "customcheck failed" end return true, "entities around and customcheck ok" end --register adv_spawning.register(name,spawndef) end mobf_core-2.5.1/mobf/attention.lua000066400000000000000000000322071264104133000171030ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file attention.lua --! @brief component for calculating attention of mobs --! @copyright Sapier --! @author Sapier --! @date 2013-04-02 -- --! @defgroup attention Attention subcomponent --! @brief Component handling attention of a mob. This incudes aggression as --! well as initial attack handling. --! @ingroup framework_int --! @{ -- Contact: sapier a t gmx net ------------------------------------------------------------------------------- --! @class attention --!@} mobf_assert_backtrace(not core.global_exists("attention")) --! @brief attention handling class reference --TODO rename to fix documentation issues attention = {} ------------------------------------------------------------------------------- -- @function [parent=#attention] aggression(entity) -- --! @brief old agression handler to be used for legacy mobs --! @memberof attention -- --! @param entity mob to do action --! @param now current time ------------------------------------------------------------------------------- function attention.aggression(entity,now) --if no combat data is specified don't do anything if entity.data.combat == nil then return end local current_state = entity.dynamic_data.state.current --mob is specified as self attacking if entity.data.combat.starts_attack and entity.dynamic_data.combat.target == nil and current_state.state_mode ~= "combat" then dbg_mobf.fighting_lvl3("MOBF: ".. entity.data.name .. " " .. now .. " aggressive mob, is it time to attack?") if entity.dynamic_data.combat.ts_last_aggression_chance + 1 < now then dbg_mobf.fighting_lvl3("MOBF: ".. entity.data.name .. " " .. now .. " lazzy time over try to find an enemy") entity.dynamic_data.combat.ts_last_aggression_chance = now if entity.data.combat.angryness ~= nil and math.random() < entity.data.combat.angryness then dbg_mobf.fighting_lvl3("MOBF: ".. entity.data.name .. " " .. now .. " really is angry") local target = fighting.get_target(entity) if target ~= nil then if target ~= entity.dynamic_data.combat.target then entity.dynamic_data.combat.target = target fighting.switch_to_combat_state(entity,now,target) local targetname = fighting.get_target_name(target) dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. " " .. now .. " starting attack at player: " ..targetname) minetest.log(LOGLEVEL_INFO, "MOBF: starting attack at player "..targetname) end end end end end end ------------------------------------------------------------------------------- -- @function [parent=#attention] callback(entity) -- --! @brief calculate attenntion level for mob mob --! @memberof attention -- --! @param entity mob to do action --! @param now current time ------------------------------------------------------------------------------- function attention.callback(entity,now) --do legacy code if entity.data.attention == nil then attention.aggression(entity,now) return end local top_attention_object = nil local top_attention_value = 0 local top_attention_enemy = nil local top_attention_enemy_value = 0 local current_attention_value = 0 mobf_assert_backtrace(entity.dynamic_data.attention ~= nil) --set default values local reduction_value = 0.1 local attention_distance = 5 local target_switch_offset = 0.5 if entity.data.attention.attention_distance ~= nil then attention_distance = entity.data.attention.attention_distance end if entity.data.attention.target_switch_offset ~= nil then target_switch_offset = entity.data.attention.target_switch_offset end --reduce attention level for all objects for k,v in pairs(entity.dynamic_data.attention.watched_objects) do if v.value > reduction_value then v.value = v.value - reduction_value dbg_mobf.attention_lvl3("MOBF: preserving " .. k .. " for watchlist new value: " .. v.value) else entity.dynamic_data.attention.watched_objects[k] = nil dbg_mobf.attention_lvl3("MOBF: removing " .. k .. " from watchlist") end end local new_objecttable = entity.dynamic_data.attention.watched_objects entity.dynamic_data.attention.watched_objects = new_objecttable local own_pos = entity.object:getpos() --get list of all objects in attention range local objectlist = minetest.get_objects_inside_radius(own_pos,attention_distance) if #objectlist > 0 then for i = 1 , #objectlist, 1 do local continue = true dbg_mobf.attention_lvl3("MOBF: checking " .. tostring(objectlist[i])) if not objectlist[i]:is_player() then local lua_entity = objectlist[i]:get_luaentity() if lua_entity ~= nil and not lua_entity.draws_attention then continue = false end end if continue then local remote_pos = objectlist[i]:getpos() local hear_addon = false local own_view_addon = false local remote_view_addon = false --is in audible distance if entity.data.attention.hear_distance ~= nil then local distance = mobf_calc_distance(own_pos,remote_pos) if distance < entity.data.attention.hear_distance then hear_addon = true end end --does own view angle matter if entity.data.attention.view_angle ~= nil then local own_view = graphics.getyaw(entity) local min_yaw = own_view - entity.data.attention.view_angle/2 local max_yaw = own_view + entity.data.attention.view_angle/2 local direction = mobf_get_direction(own_pos,remote_pos) local yaw_to_target = mobf_calc_yaw(direction.x,direction.z) if yaw_to_target > min_yaw and yaw_to_target < max_yaw then own_view_addon = true end end local table_id = tostring(objectlist[i]) --does remote view angle matter if entity.data.attention.remote_view == true then local remote_view = objectlist[i]:getyaw() if objectlist[i]:is_player() then remote_view = objectlist[i]:get_look_yaw() end if remote_view ~= nil then local direction = mobf_get_direction(own_pos,remote_pos) local yaw_to_target = mobf_calc_yaw(direction.x,direction.z) --TODO check for overflows if remote_view > yaw_to_target - (math.pi/2) and remote_view < yaw_to_target + (math.pi/2) then remote_view_addon = true end else dbg_mobf.attention_lvl2( "MOBF: unable to get yaw for object: " ..table_id) end end --calculate new value local sum_values = 0; if hear_addon then dbg_mobf.attention_lvl3("MOBF: " .. table_id .. " within hear distance") sum_values = sum_values + entity.data.attention.hear_distance_value end if own_view_addon then dbg_mobf.attention_lvl3("MOBF: " .. table_id .. " in view") sum_values = sum_values + entity.data.attention.own_view_value end if remote_view_addon then dbg_mobf.attention_lvl3("MOBF: " .. table_id .. " looks towards mob") sum_values = sum_values + entity.data.attention.remote_view_value end sum_values = sum_values + entity.data.attention.attention_distance_value if new_objecttable[table_id] == nil then dbg_mobf.attention_lvl3("MOBF: " .. table_id .. " unknown adding new entry") new_objecttable[table_id] = { value = 0 } end new_objecttable[table_id].value = new_objecttable[table_id].value + sum_values if entity.data.attention.attention_max ~= nil and new_objecttable[table_id].value > entity.data.attention.attention_max then new_objecttable[table_id].value = entity.data.attention.attention_max end dbg_mobf.attention_lvl3("MOBF: adding " .. sum_values .. " to " .. table_id .. " new value " .. new_objecttable[table_id].value) --update overall atttention values if new_objecttable[table_id].value > top_attention_value then top_attention_value = new_objecttable[table_id].value top_attention_object = objectlist[i] end --update value of old most relevant target only if objectlist[i] == entity.dynamic_data.attention.most_relevant_target then current_attention_value = new_objecttable[table_id].value end --update enemy attention values if new_objecttable[table_id].value > top_attention_enemy_value and attention.is_enemy(entity,objectlist[i]) then top_attention_enemy_value = new_objecttable[table_id].value top_attention_enemy = objectlist[i] end end end end --check if top attention exceeds current + offset if top_attention_value > current_attention_value + target_switch_offset then --update top attention object entity.dynamic_data.attention.most_relevant_target = top_attention_object current_attention_value = top_attention_value end dbg_mobf.attention_lvl3("MOBF: value=" .. current_attention_value .. " attack_threshold=" .. dump(entity.data.attention.attack_threshold) .. " watch_threshold=" .. dump(entity.data.attention.watch_threshold)) local toattack = nil local attack_attention_value = nil if mobf_rtd.factions_available then if top_attention_enemy ~= nil then attack_attention_value = top_attention_enemy_value toattack = top_attention_enemy end --don't attack anyone if factions mod is available and no enemy is found else attack_attention_value = top_attention_value toattack = top_attention_object end if entity.data.attention.attack_threshold ~= nil and attack_attention_value ~= nil and attack_attention_value > entity.data.attention.attack_threshold then local current_state = mob_state.get_state_by_name(entity,entity.dynamic_data.state.current) if entity.data.combat.starts_attack then dbg_mobf.attention_lvl3("MOBF: attack threshold exceeded starting attack of " .. dump(entity.dynamic_data.attention.most_relevant_target)) entity.dynamic_data.attention.most_relevant_target = toattack current_attention_value = attack_attention_value fighting.set_target(entity,toattack) end else if entity.data.attention.watch_threshold ~= nil and current_attention_value > entity.data.attention.watch_threshold then dbg_mobf.attention_lvl2("MOBF: watch threshold exceeded: value=" .. current_attention_value .. " threshold=" .. entity.data.attention.watch_threshold ) if entity.data.attention.watch_callback ~= nil and type(entity.data.attention.watch_callback) == "function" then entity.data.attention.watch_callback(entity, entity.dynamic_data.attention.most_relevant_target) end end end entity.dynamic_data.attention.current_value = current_attention_value end ------------------------------------------------------------------------------- -- @function [parent=#attention] init_dynamic_data(entity) -- --! @brief initialize all dynamic data on activate --! @memberof attention -- --! @param entity mob to do action --! @param now current time ------------------------------------------------------------------------------- function attention.init_dynamic_data(entity,now) local data = { watched_objects = {}, most_relevant_target = nil, current_value = 0, } entity.dynamic_data.attention = data end ------------------------------------------------------------------------------- -- @function [parent=#attention] increase_attention_level(entity,source,value) -- --! @brief initialize all dynamic data on activate --! @memberof attention -- --! @param entity mob to do action --! @param source object causing this change --! @param value amount of change ------------------------------------------------------------------------------- function attention.increase_attention_level(entity,source,value) table_id = tostring(source) if entity.dynamic_data.attention ~= nil then if entity.dynamic_data.attention.watched_objects[table_id] == nil then entity.dynamic_data.attention.watched_objects[table_id] = { value = 0 } end entity.dynamic_data.attention.watched_objects[table_id].value = entity.dynamic_data.attention.watched_objects[table_id].value + value end end ------------------------------------------------------------------------------- -- @function [parent=#attention] is_enemy(entity,object) -- --! @brief initialize all dynamic data on activate --! @memberof attention -- --! @param entity mob to do action --! @param object to check if it's an enemy --! @return true/false ------------------------------------------------------------------------------- function attention.is_enemy(entity,object) mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(object ~= nil) if mobf_rtd.factions_available then if entity.object == object then return false end local remote_factions = factions.get_factions(object) if remote_factions == nil or #remote_factions < 1 then dbg_mobf.attention_lvl3("MOBF: " .. entity.data.name .. " no remote factions for: " .. tostring(object)) return false end for j=1, #remote_factions, 1 do local rep = factions.get_reputation(remote_factions[j],entity) if rep < 0 then dbg_mobf.attention_lvl3("MOBF: ".. remote_factions[j] .. " " .. tostring(object) .. " is enemy: " .. rep) return true end end end return false endmobf_core-2.5.1/mobf/compatibility.lua000066400000000000000000000054471264104133000177550ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file compatibility.lua --! @brief contains compatibility/transition code thats to be removed --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- minetest.register_abm({ nodenames = { "animalmaterials:wool_white" }, interval = 1, chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) minetest.remove_node(pos) minetest.add_node(pos,{name="wool:white"}) end }) minetest.register_abm({ nodenames = { "animalmaterials:wool_grey" }, interval = 1, chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) minetest.remove_node(pos) minetest.add_node(pos,{name="wool:grey"}) end }) minetest.register_abm({ nodenames = { "animalmaterials:wool_brown" }, interval = 1, chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) minetest.remove_node(pos) minetest.add_node(pos,{name="wool:brown"}) end }) minetest.register_abm({ nodenames = { "animalmaterials:wool_black" }, interval = 1, chance = 1, action = function(pos, node, active_object_count, active_object_count_wider) minetest.remove_node(pos) minetest.add_node(pos,{name="wool:black"}) end }) minetest.register_entity("mobf:compat_spawner", { collisionbox = {0,0,0,0,0,0}, physical = false, groups = { "immortal" }, on_activate = function(self,staticdata,dtime_s) local pos = self.object:getpos() local delta,y_offset = adv_spawning.get_spawner_density() local spawnerpos = { x = math.floor(pos.x/delta) * delta, y = math.floor((pos.y-y_offset)/delta) * delta + y_offset, z = math.floor(pos.x/delta) * delta } local objects_at = minetest.get_objects_inside_radius(spawnerpos, 0.5) local found = false for i=1,#objects_at,1 do local luaentity = objects_at[i]:get_luaentity() if luaentity ~= nil then if luaentity.name == "adv_spawning:spawn_seed" then found = true end end end if not found then minetest.add_entity(spawnerpos,"adv_spawning:spawn_seed") end self.object:remove() end, }) ------------------------------------------------------------------------------- -- compatibility functions to make transition to new name easier ------------------------------------------------------------------------------- function animals_add_animal(animal) mobf_add_mob(animal) endmobf_core-2.5.1/mobf/debug.lua000066400000000000000000000377221264104133000161730ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file debug.lua --! @brief contains debug functions for mob framework --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mobf_debug")) --! @defgroup debug_in_game In game debugging functions --! @brief debugging functions to be called from in game --! @ingroup framework_int --! @{ mobf_debug = {} ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] print_usage(player,command,toadd) -- --! @brief send errormessage to player -- --! @param player name of player to print usage --! @param command display usage for this command --! @param toadd additional information to transfer to player ------------------------------------------------------------------------------- function mobf_debug.print_usage(player, command, toadd) if toadd == nil then toadd = "" end if command == "spawnmob" then print("CMD: ".. player .."> ".. "Usage: /spawnmob " .. toadd) minetest.chat_send_player(player, "Usage: /spawnmob " .. toadd) end if command == "ukn_mob" then print("CMD: ".. player .."> ".. "Unknown mob name "..toadd) minetest.chat_send_player(player, "Unknown mob name "..toadd) end if command == "inv_pos" then print("CMD: ".. player .."> ".. "Invalid position "..toadd) minetest.chat_send_player(player, "Invalid position "..toadd) end if command == "mob_spawned" then print("CMD: ".. player .."> ".. "Mob successfully spawned "..toadd) minetest.chat_send_player(player, "Mob successfully spawned "..toadd) end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] spawn_mob(name,param) -- --! @brief handle a spawn mob command -- --! @param name name of player --! @param param parameters received ------------------------------------------------------------------------------ function mobf_debug.spawn_mob(name,param) local parameters = param:split(" ") if #parameters ~= 1 and #parameters ~= 2 then mobf_debug.print_usage(name,"spawnmob") return end if mobf_is_known_mob(parameters[1]) ~= true then mobf_debug.print_usage(name,"ukn_mob", ">"..parameters[1].."<") return true end if #parameters == 2 then local pos_strings = parameters[2]:split(",") if #pos_strings ~= 3 then mobf_debug.print_usage(name,"spawmob") return end local spawnpoint = { x=tonumber(pos_strings[1]), y=tonumber(pos_strings[2]), z=tonumber(pos_strings[3]) } if spawnpoint.x == nil or spawnpoint.y == nil or spawnpoint.z == nil then mobf_debug.print_usage(name,"spawnmob") return end spawning.spawn_and_check(parameters[1],spawnpoint,"mobf_debug_spawner") else --todo find random pos local player = minetest.get_player_by_name(name) if player == nil then return end local pos = player:getpos() if pos == nil then return end local found = false local maxtries = 10 while (found == false) and (maxtries > 0) do local toadd = {} toadd.x = pos.x + (math.random(20) -10) toadd.z = pos.z + (math.random(20) -10) local y = mobf_get_surface(toadd.x,toadd.z,pos.y-10,pos.y+10) if y ~= nil then toadd.y = y +2 if spawning.spawn_and_check(parameters[1],toadd,"mobf_debug_spawner") then found = true end end maxtries = maxtries -1 end end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] list_active_mobs(name,param) -- --! @brief print list of all current active mobs -- --! @param name name of player --! @param param parameters received ------------------------------------------------------------------------------ function mobf_debug.list_active_mobs(name,param) local count = 1 for index,value in pairs(minetest.luaentities) do if value.data ~= nil and value.data.name ~= nil then local tosend = count .. ": " .. value.data.name .. " at " .. printpos(value.object:getpos()) print(tosend) minetest.chat_send_player(name,tosend) count = count +1 end end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] list_spawners(name,param) -- --! @brief print list of all spawners around player -- --! @param name name of player --! @param param parameters received ------------------------------------------------------------------------------ function mobf_debug.list_spawners(name,param) for index,value in pairs(minetest.luaentities) do if value ~= nil and value.spawner_mob_name ~= nil then local resultline = "SPW: " .. mobf_fixed_size_string(value.spawner_mob_name,24) .. " " .. mobf_fixed_size_string(printpos(value.object:getpos()),16) .. " STATE: " .. mobf_fixed_size_string(dump(value.spawner_last_result),32) .. " TIME: " .. value.spawner_time_passed print(resultline) end end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] mob_count(name,param) -- --! @brief count active mobs -- --! @param name name of player --! @param param parameters received ------------------------------------------------------------------------------ function mobf_debug.mob_count(name,param) local count = 1 for index,value in pairs(minetest.luaentities) do if value.data ~= nil and value.data.name ~= nil then count = count +1 end end minetest.chat_send_player(name,"Active mobs: " .. count) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] add_tools(name,param) -- --! @brief add toolset for testing -- --! @param name name of player --! @param param parameters received ------------------------------------------------------------------------------ function mobf_debug.add_tools(name,param) local player = minetest.get_player_by_name(name) if player ~= nil then player:get_inventory():add_item("main", "animalmaterials:lasso 20") player:get_inventory():add_item("main", "animalmaterials:net 20") player:get_inventory():add_item("main", "animalmaterials:scissors 1") player:get_inventory():add_item("main", "vessels:drinking_glass 10") end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] list_defined_mobs(name,param) -- --! @brief list all registred mobs -- --! @param name name of player --! @param param parameters received ------------------------------------------------------------------------------ function mobf_debug.list_defined_mobs(name,param) local text = "" for i,val in ipairs(mobf_rtd.registred_mob) do text = text .. val .. " " end minetest.chat_send_player(name, "MOBF: "..text) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] init() -- --! @brief initialize debug commands chat handler -- ------------------------------------------------------------------------------ function mobf_debug.init() minetest.register_chatcommand("spawnmob", { params = " ", description = "spawn a mob at position(optional)" , privs = {mobfw_admin=true}, func = mobf_debug.spawn_mob }) minetest.register_chatcommand("listactivemobs", { params = "", description = "list all currently active mobs" , privs = {mobfw_admin=true}, func = mobf_debug.list_active_mobs }) minetest.register_chatcommand("listdefinedmobs", { params = "", description = "list all currently defined mobs" , privs = {mobfw_admin=true}, func = mobf_debug.list_defined_mobs }) minetest.register_chatcommand("mob_add_tools", { params = "", description = "add some mob specific tools to player" , privs = {mobfw_admin=true}, func = mobf_debug.add_tools }) minetest.register_chatcommand("mobf_version", { params = "", description = "show mobf version number" , privs = {}, func = function(name,param) minetest.chat_send_player(name,"MOBF version: " .. mobf_version) end }) minetest.register_chatcommand("listspawners", { params = "", description = "debug info about spawner entities" , privs = {mobfw_admin=true}, func = mobf_debug.list_spawners }) minetest.register_chatcommand("mobf_count", { params = "", description = "number of active mobs" , privs = {}, func = mobf_debug.mob_count }) minetest.register_chatcommand("mobf_mobs_offline", { params = "", description = "print offline mobs" , privs = {}, func = mobf_debug.print_offline_mobs }) if mobf_rtd.luatrace_enabled then minetest.register_chatcommand("traceon", { params = "", description = "start luatrace tracing" , privs = {mobfw_admin=true}, func = function() luatrace.tron(nil) end }) minetest.register_chatcommand("traceoff", { params = "", description = "stop luatrace tracing" , privs = {mobfw_admin=true}, func = function() luatrace.troff() end }) end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] print_offline_mobs(name,message) -- --! @brief spawn small house -- --! @param name name of player --! @param param parameters ------------------------------------------------------------------------------ function mobf_debug.print_offline_mobs(name,param) local count = 0 for key,value in pairs(spawning.mob_spawn_data) do for hash,enabled in pairs (value) do count = count +1 local mobpos = mobf_hash_to_pos(hash) print(string.format("%5d: ",count) .. key .. " " .. printpos(mobpos)) end end print("Total of " .. count .. " mobs stored as offline") end ------------------------------------------------------------------------------- -- @function [parent=#mobf_debug] rightclick_callback(entity,player) -- --! @brief show rightclick info -- --! @param entity entity rightclicked --! @param player player doing rightclick ------------------------------------------------------------------------------ function mobf_debug.rightclick_callback(entity,player) local basepos = entity.getbasepos(entity) local lifetime = mobf_get_current_time() - entity.dynamic_data.spawning.original_spawntime print("MOBF: " .. entity.data.name .. " " .. tostring(entity) .. " is alive for " .. lifetime .. " seconds") print("MOBF: \tAbsolute spawntime: " .. entity.dynamic_data.spawning.original_spawntime) print("MOBF: \tCurrent state: " .. entity.dynamic_data.state.current.name ) print("MOBF: \tCurrent movgen: " .. entity.dynamic_data.current_movement_gen.name ) print("MOBF: \tHP: " .. entity.object:get_hp()) if entity.dynamic_data.current_movement_gen.name == "follow_mov_gen" or entity.dynamic_data.current_movement_gen.name == "mgen_path" then local targetpos = entity.dynamic_data.spawning.spawnpoint if entity.dynamic_data.movement.target ~= nil then if not mobf_is_pos(entity.dynamic_data.movement.target) then targetpos = entity.dynamic_data.movement.target:getpos() else targetpos = entity.dynamic_data.movement.target end end if targetpos ~= nil then print("MOBF: \t\tmovement state: " .. mgen_follow.identify_movement_state(basepos,targetpos) ) else print("MOBF: \t\tmovement state: invalid") end print("MOBF: \t\tguard spawnpoint: " .. dump(entity.dynamic_data.movement.guardspawnpoint)) print("MOBF: \t\ttarget: " .. dump(entity.dynamic_data.movement.target)) end if entity.dynamic_data.current_movement_gen.name == "mgen_path" then print("MOBF: \t\tpath index: " .. entity.dynamic_data.p_movement.next_path_index) print("MOBF: \t\tpath: " .. dump(entity.dynamic_data.p_movement.path)) if entity.dynamic_data.p_movement.path ~= nil then for i,v in ipairs(entity.dynamic_data.p_movement.path) do local objects = minetest.get_objects_inside_radius(v,0.5) local found = false; for i=1,#objects,1 do local luaentity = objects[i]:get_luaentity() if luaentity ~= nil and luaentity.name == "mobf:path_marker_entity" then found = true break end end local node_at = minetest.get_node(v) if not found and node_at.name ~= nil and node_at.name ~= "ignore" then spawning.spawn_and_check("mobf:path_marker_entity",v,"mark_path") end end print("MOBF: \t\tdistance to next point: " .. p_mov_gen.distance_to_next_point(entity,entity.object:getpos())) end end local predicted_pos = movement_generic.predict_next_block( basepos, entity.object:getvelocity(), entity.object:getacceleration()) if not ( entity.data.movement.canfly == true) then predicted_pos.y = basepos.y end local pos_state = environment.pos_is_ok(predicted_pos,entity) local pos_quality = environment.pos_quality(basepos,entity) local predicted_quality = environment.pos_quality(predicted_pos,entity) print("MOBF: \tTime to state change: " .. entity.dynamic_data.state.time_to_next_change .. " seconds") print("MOBF: \tCurrent environmental state: " .. environment.pos_is_ok(entity.getbasepos(entity),entity)) if mobf_rtd.detailed_state then print("MOBF: \tCurrent detailed state: " .. pos_quality:shortstring()) end print("MOBF: \tCan fly: " .. dump(entity.data.movement.canfly)) print("MOBF: \tCurrent accel: " .. printpos(entity.object:getacceleration())) print("MOBF: \tDefault gravity: " .. dump(environment.get_default_gravity(basepos, entity.environment.media, entity.data.movement.canfly))) print("MOBF: \tCurrent speed: " .. printpos(entity.object:getvelocity())) print("MOBF: \tSpawnpoint: " .. printpos(entity.dynamic_data.spawning.spawnpoint)) print("MOBF: \tSpawner: " .. dump(entity.dynamic_data.spawning.spawner)) print("MOBF: \tCurrent pos: " .. printpos(basepos)) print("MOBF: \tPredicted pos: " .. printpos(predicted_pos)) print("MOBF: \tPredicted state: " .. pos_state) print("MOBF: \tMovement facedir offset: " .. dump(entity.automatic_face_movement_dir)) if entity.dynamic_data.sound ~= nil and entity.dynamic_data.sound.random_next ~= nil then print("MOBF: \tNext random sound: " .. (entity.dynamic_data.sound.random_next - mobf_get_current_time())) end if mobf_rtd.detailed_state then print("MOBF: \tPredicted detail: " .. predicted_quality:shortstring()) end if entity.dynamic_data.combat ~= nil then print("MOBF: \tCurrent combat target: " .. fighting.get_target_name(entity.dynamic_data.combat.target)) end if entity.dynamic_data.attention ~= nil then print("MOBF: \t Current attention table:") for k,v in pairs(entity.dynamic_data.attention.watched_objects) do print("MOBF: \t\t " .. k .. ": " .. v.value) end if entity.dynamic_data.attention.most_relevant_target ~= nil then local attention_name = tostring(entity.dynamic_data.attention.most_relevant_target) if (entity.dynamic_data.attention.most_relevant_target:is_player()) then attention_name = entity.dynamic_data.attention.most_relevant_target:get_player_name() end print("MOBF: \tTop attention object: " .. attention_name) end end if entity.dynamic_data.graphics.last_fps ~= nil then print("MOBF: Animating with: " .. entity.dynamic_data.graphics.last_fps .. " fps") end if entity.data.states ~= nil then print("MOBF: \tStatecount: " .. #entity.data.states) for i,v in ipairs(entity.data.states) do print("MOBF: \t\t" .. i .. ": " .. v.name .. " chance=" .. v.chance) end end return false end --!@} mobf_core-2.5.1/mobf/debug_trace.lua000066400000000000000000000063211264104133000173400ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file debug_trace.lua --! @brief contains switchable debug trace functions --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @defgroup debug_trace Debug trace functions --! @brief central configuration of trace functions --! @ingroup framework_int --lvl1 excessive output --lvl2 medium output --lvl3 less output --! @brief configuration of trace level to use for various components --! @ingroup debug_trace dbg_mobf = { generic_lvl1 = function () end, generic_lvl2 = function () end, generic_lvl3 = function () end, graphics_lvl1 = function () end, graphics_lvl2 = function () end, graphics_lvl3 = function () end, spawning_lvl1 = function () end, spawning_lvl2 = function () end, spawning_lvl3 = function () end, permanent_store_lvl1 = function () end, permanent_store_lvl2 = function () end, permanent_store_lvl3 = function () end, movement_lvl1 = function () end, movement_lvl2 = function () end, movement_lvl3 = function () end, pmovement_lvl1 = function () end, pmovement_lvl2 = function () end, pmovement_lvl3 = function () end, fmovement_lvl1 = function () end, fmovement_lvl2 = function () end, fmovement_lvl3 = function () end, flmovement_lvl1 = function () end, flmovement_lvl2 = function () end, flmovement_lvl3 = function () end, path_mov_lvl1 = function () end, path_mov_lvl2 = function () end, path_mov_lvl3 = function () end, fighting_lvl1 = function () end, fighting_lvl2 = function () end, fighting_lvl3 = function () end, environment_lvl1 = function () end, environment_lvl2 = function () end, environment_lvl3 = function () end, harvesting_lvl1 = function () end, harvesting_lvl2 = function () end, harvesting_lvl3 = function () end, sound_lvl1 = function () end, sound_lvl2 = function () end, sound_lvl3 = function () end, random_drop_lvl1 = function () end, random_drop_lvl2 = function () end, random_drop_lvl3 = function () end, mob_state_lvl1 = function () end, mob_state_lvl2 = function () end, mob_state_lvl3 = function () end, mobf_core_lvl1 = function () end, mobf_core_lvl2 = function () end, mobf_core_lvl3 = function () end, mobf_core_helper_lvl1 = function () end, mobf_core_helper_lvl2 = function () end, mobf_core_helper_lvl3 = function () end, trader_inv_lvl1 = function () end, trader_inv_lvl2 = function () end, trader_inv_lvl3 = function () end, ride_lvl1 = function () end, ride_lvl2 = function () end, ride_lvl3 = function () end, path_lvl1 = function () end, path_lvl2 = function () end, path_lvl3 = function () end, lifebar_lvl1 = function () end, lifebar_lvl2 = function () end, lifebar_lvl3 = function () end, attention_lvl1 = function () end, attention_lvl2 = function () end, attention_lvl3 = function () end, }mobf_core-2.5.1/mobf/depends.txt000066400000000000000000000000511264104133000165460ustar00rootroot00000000000000default adv_spawning? intllib? factions? mobf_core-2.5.1/mobf/doc/000077500000000000000000000000001264104133000151345ustar00rootroot00000000000000mobf_core-2.5.1/mobf/doc/Doxyfile000066400000000000000000002222431264104133000166470ustar00rootroot00000000000000# Doxyfile 1.7.6.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or sequence of words) that should # identify the project. Note that if you do not use Doxywizard you need # to put quotes around the project name if it contains spaces. PROJECT_NAME = "Mob Framework Minetest Mod" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not # exceed 55 pixels and the maximum width should not exceed 200 pixels. # Doxygen will copy the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = . # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = "The $name class" "The $name widget" "The $name file" is provides specifies contains represents a an the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful if your file system # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding # "class=itcl::class" will allow you to use the command class in the # itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this # tag. The format is ext=language, where ext is a file extension, and language # is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, # C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also makes the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and # unions are shown inside the group in which they are included (e.g. using # @ingroup) instead of on a separate page (for HTML and Man pages) or # section (for LaTeX and RTF). INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and # unions with only public data fields will be shown inline in the documentation # of the scope in which they are defined (i.e. file, namespace, or group # documentation), provided this scope is documented. If set to NO (the default), # structs, classes, and unions are shown on a separate page (for HTML and Man # pages) or section (for LaTeX and RTF). INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penalty. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. SYMBOL_CACHE_SIZE = 0 # Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be # set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given # their name and scope. Since this can be an expensive process and often the # same symbol appear multiple times in the code, doxygen keeps a cache of # pre-resolved symbols. If the cache is too small doxygen will become slower. # If the cache is too large, memory is wasted. The cache size is given by this # formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = YES # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespaces are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to # do proper type resolution of all parameters of a function it will reject a # match between the prototype and the implementation of a member function even # if there is only one candidate or it is obvious which candidate to choose # by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen # will still accept a match between prototype and implementation in such cases. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST = YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or macro consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and macros in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. The create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files # containing the references data. This must be a list of .bib files. The # .bib extension is automatically appended if omitted. Using this command # requires the bibtex tool to be installed. See also # http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style # of the bibliography can be controlled using LATEX_BIB_STYLE. To use this # feature you need bibtex and perl available in the search path. CITE_BIB_FILES = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = NO # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # The WARN_NO_PARAMDOC option can be enabled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = ../ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl #FILE_PATTERNS = *.c \ FILE_PATTERNS = *.lua # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = *.lua=/usr/local/bin/lua2dox # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) # and it is also possible to disable source filtering for a specific pattern # using *.ext= (so without naming a filter). This option only has effect when # FILTER_SOURCE_FILES is enabled. FILTER_SOURCE_PATTERNS = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. Note that when using a custom header you are responsible # for the proper inclusion of any scripts and style sheets that doxygen # needs, which is dependent on the configuration options used. # It is advised to generate a default header using "doxygen -w html # header.html footer.html stylesheet.css YourConfigFile" and then modify # that header. Note that the header is subject to change so you typically # have to redo this when upgrading to a newer version of doxygen or when # changing the value of configuration settings such as GENERATE_TREEVIEW! HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # style sheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that # the files will be copied as-is; there are no commands or markers available. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the style sheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = YES # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated # that can be used as input for Qt's qhelpgenerator to generate a # Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's # filter section matches. # # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before # the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) # at top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. Since the tabs have the same information as the # navigation tree you can set this option to NO if you already set # GENERATE_TREEVIEW to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. # Since the tree basically has the same information as the tab index you # could consider to set DISABLE_INDEX to NO when enabling this option. GENERATE_TREEVIEW = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values # (range [0,1..20]) that doxygen will group on one line in the generated HTML # documentation. Note that a value of 0 will completely suppress the enum # values from appearing in the overview section. ENUM_VALUES_PER_LINE = 1 # By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list. USE_INLINE_TREES = YES # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open # links to external symbols imported via tag files in a separate window. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax # (see http://www.mathjax.org) which uses client side Javascript for the # rendering instead of using prerendered bitmaps. Use this if you do not # have LaTeX installed or if you want to formulas look prettier in the HTML # output. When enabled you also need to install MathJax separately and # configure the path to it using the MATHJAX_RELPATH option. USE_MATHJAX = NO # When MathJax is enabled you need to specify the location relative to the # HTML output directory using the MATHJAX_RELPATH option. The destination # directory should contain the MathJax.js script. For instance, if the mathjax # directory is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the # mathjax.org site, so you can quickly see the result without installing # MathJax, but it is strongly recommended to install a local copy of MathJax # before deployment. MATHJAX_RELPATH = http://www.mathjax.org/mathjax # The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension # names that should be enabled during MathJax rendering. MATHJAX_EXTENSIONS = # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets # (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a PHP enabled web server instead of at the web client # using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server # based approach is that it scales better to large projects and allows # full text search. The disadvantages are that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for # the generated latex document. The footer should contain everything after # the last chapter. If it is left blank doxygen will generate a # standard footer. Notice: only use this tag if you know what you are doing! LATEX_FOOTER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See # http://en.wikipedia.org/wiki/BibTeX for more info. LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load style sheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # pointed to by INCLUDE_PATH will be searched when a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition that # overrules the definition found in the source code. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all references to function-like macros # that are alone on a line, have an all uppercase name, and do not end with a # semicolon, because these will confuse the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option also works with HAVE_DOT disabled, but it is recommended to # install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = NO # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will # base this on the number of processors available in the system. You can set it # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. DOT_NUM_THREADS = 0 # By default doxygen will use the Helvetica font for all dot files that # doxygen generates. When you want a differently looking font you can specify # the font name using DOT_FONTNAME. You need to make sure dot is able to find # the font, which can be done by putting it in a standard location or by setting # the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the # directory containing the font. DOT_FONTNAME = Helvetica # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the Helvetica font. # If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to # set the path where dot can find it. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are svg, png, jpg, or gif. # If left blank png will be used. If you choose svg you need to set # HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible in IE 9+ (other browsers do not have this requirement). DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. # Note that this requires a modern browser other than Internet Explorer. # Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you # need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible. Older versions of IE do not have SVG support. INTERACTIVE_SVG = NO # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the # \mscfile command). MSCFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES mobf_core-2.5.1/mobf/doc/Testcases.txt000066400000000000000000000007531264104133000176400ustar00rootroot00000000000000Date 2012 01 28 testcases to check before release: Features: -mobs moving -catching mobs with lasso -catching mobs with net -killing mobs -harvesting mobs -mobs doing meele attack -mobs transforming on harvest -mobs auto transform -mobs taking damage in sun -mobs self destruct -mobs honoring jump limitiations -mobs random dropping -mobs honoring movement medium -mobs doing random jumps -mobs level changing General: -version number correct? -readme correct? -debug output disabled? mobf_core-2.5.1/mobf/doc/mainpage_description.lua000066400000000000000000000037271264104133000220340ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file mainpage_description.lua --! @brief just a doc page --! @copyright Sapier --! @author Sapier --! @date 2013-04-28 -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @mainpage Mob Framework Mod 2.5.x --! --! This documentation uses doxygen created from lua code. As some of you --! probably know doxygen doesn't support lua on it's own so some of the --! shown descriptions arent perfectly correct.\n --! --! On this page some of the caveats are explained:\n --! Lua doesn't support classes and structs but tables which ain't supported by --! doxygen.\n --! --! Mapping of classes and structs: --! \li \b classes are used to group (sub)components containing primary functions\n --! \li \b structs are result of parsing tables and containing configuration or --! temporary data --! --! Datatypes shown in doxygen and it's meaning: --! \li \b function this is a return value of function and can be nil too --! \li \b var a variable --! \li \b struct some table element --! \li \b anonymous_value a table element specified without a name --! \li \b parameter is a parameter declared in a function definition --! --! --! Keywords used in config parameter description --! \li \b MANDATORY some parameter required --! \li \b OPTIONAL parameter may be nill without causinh an error --! \li \b MOV_GEN_DEPENDENT parameter is required dependent of selected movement gen --! \li \b 2D MANDATORY parameter is required in case of 2D mob --! \li \b 3D MANDATORY parameter is required in case of 3D mob --! \li \b ALGORITHM \b DEPENDENT is required dependent on selected algorithm --! \li \b UPPER_VALUE_DEPENDENT if you specify upper value you need to specify this too mobf_core-2.5.1/mobf/doc/mob_template.lua000066400000000000000000000357501264104133000203210ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file mob_template.lua --! @brief template for mob --! @copyright Sapier --! @author Sapier --! @date 2013-01-27 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -- WARNING this code might be not correct lua in order to get doxygen -- compatibility! --+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --! @brief Template for creating mobs --! --! This template trys to describe all available configuration options --! in mob framework. --! @ingroup framework_mob local mob_template = { --! @brief [MANDATORY] name of mob @b (alphanumeric and "_" only!!) name = "some name", --! @brief [MANDATORY] name of mod defining the mob modname = "name of mod", --! @brief [MANDATORY] generic parameters for mob generic = { --! @brief [MANDATORY] description to show on mouse over in inventory description="Some mob", --! @brief [MANDATORY] maximum health base_health=0, --! @brief [MANDATORY] environment of mob to be envid="some environment id", --! @brief [OPTIONAL] item description OR function all returning a item description of whats the result of a kill kill_result = nil, --! @brief [OPTIONAL] armor groups of mob armor_groups = nil, --! @brief [OPTIONAL] custom on_hit(entity,player) callback return true to skip normal fight callback on_hit_callback = nil, --! @brief [OPTIONAL] custom on_kill(entity,player) callback return true to skip normal on kill handling on_kill_callback = nil, --! @brief [OPTIONAL] custom on_place(entity, placer, pointed_thing) callback called after normal on place handling is done custom_on_place_handler = nil, --! @brief [OPTIONAL] custom on_activate(entity) callback called after normal on_activate handling is done custom_on_activate_handler = function(entity) end, --! @brief [OPTIONAL] custom on_step(entity) callback called after normal on_step handling is done custom_on_step_handler = nil, --! @brief [OPTIONAL] list of callbacks on_rightclick_callbacks = { { handler = function(entity, player) end, name = "internal name shown in debug info", visiblename = function(entity) end or "some label of rightclick button" } }, }, --! @brief [MANDATORY] configuration of movement generator movement = { --! @brief [MANDATORY] is this a flying mob canfly=false, --! @brief [MANDATORY] minumum acceleration of mob min_accel=0, --! @brief [MANDATORY] maximum acceleration of mob max_accel=0, --! @brief [MANDATORY] maximum absolute speed of mob max_speed=0, --! @brief [OPTIONAL] minimum speed a mob shall move (if moving at all) min_speed=0, --! @brief [MOV_GEN_DEPENDENT | MANDATORY] pattern based movement gen -> pattern to use for movement pattern="some pattern id", --! @brief [MOV_GEN_DEPENDENT | OPTIONAL] follow movement gen -> does this mob guard it's spawnpoint guardspawnpoint = false, --! @brief [MOV_GEN_DEPENDENT | OPTIONAL] follow movement gen -> time until this mob teleports to its target teleportdelay = 60, }, --! @brief [OPTIONAL] if mob is harvestable configure it here harvest = { --! @brief [OPTIONAL] tool required for harvesting tool=nil, --! @brief [OPTIONAL] is tool consuled by harvesting tool_consumed=false, --! @brief [MANDATORY] result of harvest result="", --! @brief [OPTIONAL] mob transforms to this mob on harvest transforms_to="", --! @brief [MANDATORY] minimum time between two harvests (in case of transform set this to -1) min_delay=-1, }, --! @brief [OPTIONAL] configuration how to catch the mob catching = { --! @brief [MANDATORY] tool required to wear to catch the mob tool = "some item", --! @brief [MANDATORY] is tool consumed by catching consumed = true, --! @brief [OPTIONAL] function to be called to check if a mob can be cought in current state can_be_cought = function(entity) end, }, --! @brief [OPTIONAL] does this mob do random drops random_drop = { --! @brief [MANDATORY] item to be dropped result="some_material", --! @brief [MANDATORY] minimum delay between two drops min_delay=60, --! @brief [MANDATORY] chance per second to drop after min_delay has passed chance=0.2 }, --! @brief [OPTIONAL] if this mob s intended to transform by its own configure it here auto_transform = { --! @brief [MANDATORY] mob to transform to result="some_mob", --! @brief [MANDATORY] time to transformation delay=1800 }, --! @brief [OPTIONAL] combat settings for mob combat = { --! @brief [MANDATORY] does mob start an attack on its own? starts_attack=true, --! @brief [MANDATORY] chance mob will attack (if starting attack on its own or beeing attacked) angryness=0.95, --! @brief [OPTIONAL] is mob sensitive to sun? sun_sensitive=true, --! @brief [OPTIONAL] attacks hostile mobs attack_hostile_mobs=false, --! @brief [UNUSED] attacks hostile mobs --attack_friendly_mobs=false, --! @brief [OPTIONAL] configuration of meele attack melee = { --! @brief [MANDATORY] maximum damage mob does per hit maxdamage=4, --! @brief [MANDATORY] range mob will hit range=2, --! @brief [MANDATORY] minimum time between two hits speed=2, --! @brief [OPTIONAL] list of groups damage is done to, if not specified "fleshy" is used weapon_damage_groups= { "fleshy" }, }, --! @brief [OPTIONAL] configuration of distance attack distance = { --! @brief [MANDATORY] distance projectile to issue attack="some_entity", --! @brief [MANDATORY] distance to issue an attack range=10, --! @brief [OPTIONAL] minimum range of distance attack (should be > range of melee attack) min_range = 3, --! @brief [MANDATORY] minimum time between two attacks speed=2, }, --! @brief [OPTIONAL] configuration for self destructing mob self_destruct = { --! [MANDATORY] maximum damage to be done on self destruct damage=15, --! [MANDATORY] maximum range to do damage range=5, --! [MANDATORY] range to destroy nodes on self destruction node_damage_range = 1.5, --! [MANDATORY] delay between self destruct triggering and explosion delay=5, }, }, --! @brief [MANDATORY] spawning configuration for mob spawning = { --! @brief [MANDATORY] rate this mob is spawned rate=0.01, --! @brief [MANDATORY] typical distance between two mobs of this type when spawend density=1000, --! @brief [MANDATORY] identifyer of spawn algorithm algorithm="some algorithm id", --! @brief [ALGORITHM DEPENDENT] shadows minimum number of air blocks above pos height = 1, }, --! @brief [OPTIONAL] sounds to be played by mob sound = { --! @brief [OPTIONAL] random sound to be played random = { --! @brief [MANDATORY] default interval between random sounds interval = 60, --! @brief [MANDATORY] maximum deviation from default interval max_interval_deviation = 10, --! @brief [MANDATORY] list of random sounds to play list = { { --! @brief [MANDATORY] basename of file name="random_1", --! @brief [MANDATORY] amplify the sound by this value gain = 1, --! @brief [MANDATORY] maximum distance sound is heared max_hear_distance = 75, }, { --! @brief [MANDATORY] basename of file name="random_2", --! @brief [MANDATORY] amplify the sound by this value gain = 1, --! @brief [MANDATORY] maximum distance sound is heared max_hear_distance = 75, }, { --! @brief [MANDATORY] basename of file name="random_x", --! @brief [MANDATORY] amplify the sound by this value gain = 1, --! @brief [MANDATORY] maximum distance sound is heared max_hear_distance = 75, }, }, }, --! @brief [OPTIONAL] sound played on self destruction self_destruct = { --! @brief [MANDATORY] basename of file name="bomb_explosion", --! @brief [MANDATORY] amplify the sound by this value gain = 2, --! @brief [MANDATORY] maximum distance sound is heared max_hear_distance = 1000, }, --! @brief [OPTIONAL] sound played on harvest harvest = { --! @brief [MANDATORY] basename of file name="harvest", --! @brief [MANDATORY] amplify the sound by this value gain = 0.8, --! @brief [MANDATORY] maximum distance sound is heared max_hear_distance = 5, }, --! @brief [OPTIONAL] sound played on distance attack distance = { --! @brief [MANDATORY] basename of file name="fireball", --! @brief [MANDATORY] amplify the sound by this value gain = 1, --! @brief [MANDATORY] maximum distance sound is heared max_hear_distance = 100, }, --! @brief [OPTIONAL] sound played if mob dies die = { --! @brief [MANDATORY] basename of file name="die", --! @brief [MANDATORY] amplify the sound by this value gain = 1, --! @brief [MANDATORY] maximum distance sound is heared max_hear_distance = 100, }, --! @brief [OPTIONAL] sound played if mob does meele attack melee = { --! @brief [MANDATORY] basename of file name="hit", --! @brief [MANDATORY] amplify the sound by this value gain = 1, --! @brief [MANDATORY] maximum distance sound is heared max_hear_distance = 100, }, }, --! @brief [OPTIONAL] parameters for rideable mobs ride = { --! @brief [OPTIONAL] speed when walking walkspeed = 7.8, --! @brief [OPTIONAL] speed when sneaking sneakspeed = 0.8, --! @brief [OPTIONAL] inital jumpspeed jumpspeed = 58, --! @brief [OPTIONAL] offset to center player is put to attacheoffset = { x=0,y=2,z=0}, --! @brief [OPTIONAL] texture modifier when player is attached texturemod = "^mob_ostrich_ostrich_saddle_mesh.png", --! @brief [OPTIONAL] animation to show when riding walk_anim = "walk", }, --! @brief [OPTIONAL] description of animations animation = { --! @brief [OPTIONAL] one or many animation descriptions animationname = { --! @brief [MANDATORY] start frame of animation start_frame = 1, --! @brief [MANDATORY] end frame of animation end_frame = 2, }, }, --! @brief [OPTIONAL] configuration for a trader mob trader_inventory = { --! @brief [MANDATORY] goodlist to be sold goods = { --! @brief [MANDOTORY] first element in list { "default:mese 1", "default:dirt 99", "default:cobble 50"}, --! @brief [OPTIONAL] any further element { "default:steel_ingot 1", "default:dirt 50", "default:cobble 20"}, }, --! @brief [MANDATORY] list of names randomly choosen for trader random_names = { "Name1","Name2","Name3"}, }, --! @brief [OPTIONAL] configuration for attention handling attention = { --! @brief [OPTIONAL] hear distance value hear_distance = 3, --! @brief [UPPER_VALUE_DEPENDENT | MANDATORY] value to add if within hear distance hear_distance_value = 0.5, --! @brief [OPTIONAL] view angle to consider view_angle = nil, --! @brief [UPPER_VALUE_DEPENDENT | MANDATORY] value to add if someone is within view angke own_view_value = 0, --! @brief [OPTIONAL] is remove view angle relevant? remote_view = false, --! @brief [UPPER_VALUE_DEPENDENT | MANDATORY] value to add if remote target looks at mob remote_view_value = 0, --! @brief [MANDATORY] value to add if within attention distance attention_distance_value = 0.2, --! @brief [MANDATORY] threshold to issue watch callback watch_threshold = 2, --! @brief [OPTIONAL] threshold to issue attack callback attack_threshold = nil, --! @brief [MANDATORY] maximum distance to consider objects for attantion attention_distance = 7.5, --! @brief [MANDATORY] maximum amount of attention any object can draw attention_max = 10, --! @brief [OPTIONAL] watch_callback(entity,target) a callback to be called once attention threshold is reached watch_callback = nil }, --! @brief [OPTIONAL] factions configuration factions = { --! @brief [OPTIONAL] define factions to be member in member = { "faction_1", "faction_2", "faction_3", "faction_4" }, }, --! @brief [OPTIONAL] used to specify different movement/model states, --! you may specify as many states as you like states = { { --! @brief [MANDATORY] (default state is MUST have!) name of state name = "default", --! @brief [MANDATORY] typical duration of this state typical_state_time = 180, --! @brief [MANDATORY] chance of state to be selected (SUM may not be > 1) chance = 0.5, --! @brief [MANDATORY] a special movement handler for this state movgen = "none", --! @brief [3D MANDATORY] a special model to be used for this state graphics_3d = { --! @brief [MANDATORY] this is the drawtype to use visual = "mesh", --! @brief [MANDATORY] this is the drawtype to use mesh = "mesh.x", --! @brief [MANDATORY] the model of the mob textures = {"some node declatation"}, --! @brief [MANDATORY] collisionbox to use collisionbox = { "" }, --! @brief [MANDATORY] xyz scale factors for the model visual_size = {x=1,y=1,z=1}, --! @brief [OPTIONAL] additional yaw offset used to fix models with broken orientation model_orientation_fix = -math.pi/2, }, --! @brief [2D MANDATORY] a special sprite to be used for this state graphics_2d = { --! @brief [MANDATORY] scale of sprite sprite_scale={x=0,y=0}, --! @brief [MANDATORY] description of multi dimensional sprites (e.g. containing burning/ differend view directions) sprite_div = {x=0,y=0}, --! @brief [MANDATORY] height of sprite visible_height = 3.2, }, --! @brief [MANDATORY] a animation to be played while this state is active animation = "walk", }, { --! @brief [MANDATORY] name of state name = "another_state_example", --! @brief [MANDATORY] typical duration of this state typical_state_time = 180, --! @brief [MANDATORY] chance of state to be selected (SUM may not be > 1) chance = 0.5, --! @brief [OPTIONAL] a function to check before switching to this state HANDLER_precondition = nil, --! @brief [OPTIONAL] a special movement handler for this state movgen = "none", --! @brief [OPTIONAL] a special model to be used for this state graphics_3d = "", --! @brief [OPTIONAL] a special sprite to be used for this state graphics_2d = "", --! @brief [OPTIONAL] a animation to be played while this state is active animation = "name", --! @brief [OPTIONAL] mode to differentiate different states e.g. "auto" for auto switched states "user_def" for user defined or "combat" for combat states state_mode = "auto", }, }, }mobf_core-2.5.1/mobf/env_constants.lua000066400000000000000000000067451264104133000177720ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- -- --! @file env_constants.lua --! @brief constants used for environmental functions --! @copyright Sapier --! @author Sapier --! @date 2013-08-17 -- --! @defgroup environment Environment subcomponent --! @brief Environment check functions used by different subcomponents --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- MQ_IN_MEDIA = 100 MQ_IN_WATER = 20 MQ_IN_AIR = 10 MQ_SOLID = 5 GQ_FULL = 100 GQ_PARTIAL = 60 GQ_NONE = 30 SQ_OK = 100 SQ_POSSIBLE = 60 SQ_WRONG = 30 SQ_WATER = 10 LQ_OK = 100 LQ_ABOVE = 60 LQ_BELOW = 30 Q_UNKNOWN = 0 --define some common limits for convenience --! @brief a position where mob is in media, has full surface contact and is at least on a possible surface LT_GOOD_POS = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=GQ_FULL, min_geom_center=nil, min_min_surface=SQ_POSSIBLE, min_max_surface=SQ_OK, min_center_surface=nil } LT_GOOD_FLY_POS = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=nil, min_geom_center=nil, min_min_surface=Q_UNKNOWN, min_max_surface=Q_UNKNOWN, min_center_surface=nil } --! @brief a position where mob is in media, has surface contact and is at least on a possible surface LT_SAFE_POS = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=GQ_PARTIAL, min_geom_center=nil, min_min_surface=nil, min_max_surface=SQ_POSSIBLE, min_center_surface=nil } --! @brief a position where mob is in media, it's center has surface contact and is at least on a possible surface LT_SAFE_EDGE_POS = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=nil, min_geom_center=GQ_FULL, min_min_surface=SQ_POSSIBLE, min_max_surface=SQ_OK, min_center_surface=nil } --! @brief a position where mob is in media, it's center has surface contact and is on a possible surface LT_SAFE_POSSIBLE_EDGE_POS = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=nil, min_geom_center=GQ_FULL, min_min_surface=SQ_POSSIBLE, min_max_surface=SQ_POSSIBLE, min_center_surface=nil } --! @brief a position where mob is in media, it's center has surface contact and it's center is at least on a possible surface LT_EDGE_POS_POSSIBLE_CENTER = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=nil, min_geom_center=GQ_FULL, min_min_surface=nil, min_max_surface=nil, min_center_surface=SQ_POSSIBLE } --! @brief a position where mob is in media, it's center has surface contact and it's center is at least on a good surface LT_EDGE_POS_GOOD_CENTER = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=nil, min_geom_center=GQ_FULL, min_min_surface=nil, min_max_surface=nil, min_center_surface=SQ_OK } --! @brief a position where mob is in media, has at least partial contact and is at least on possible surface LT_EDGE_POS = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=GQ_PARTIAL, min_geom_center=nil, min_min_surface=SQ_POSSIBLE, min_max_surface=SQ_OK, min_center_surface=nil } --! @brief a position where mob is in media, has at least partial contact but no contact at center LT_DROP_PENDING = { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=GQ_PARTIAL, min_geom_center=GQ_NONE, min_min_surface=nil, min_max_surface=nil, min_center_surface=nil }mobf_core-2.5.1/mobf/environment.lua000066400000000000000000001107431264104133000174440ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- -- --! @file environment.lua --! @brief component for environment related functions --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup environment Environment subcomponent --! @brief Environment check functions used by different subcomponents --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("environment")) environment = {} mobf_assert_backtrace(not core.global_exists("environment_list")) --! @brief list of known environments --! @memberof environment environment_list = {} ------------------------------------------------------------------------------- -- @function [parent=#environment] get_pos_same_level(pos_raw,maxsearcharea,entity,checkfunc) -- --! @brief find a position suitable around a specific position --! @ingroup environment -- --! @param pos_raw position to look at --! @param maxsearcharea max range to look for suitable position --! @param entity mob to look for position --! @param checkfunc function doing check if a pos is ok --! @return {x,y,z} position found or nil ------------------------------------------------------------------------------- function environment.get_pos_same_level(pos_raw,maxsearcharea,entity,checkfunc) dbg_mobf.environment_lvl2("MOBF: --> get_pos_same_level " .. printpos(pos_raw)) local pos = mobf_round_pos(pos_raw) dbg_mobf.environment_lvl3("MOBF: Starting pos is "..printpos(pos) .." max search area is "..maxsearcharea) local e1 = "|" local e2 = "|" local e3 = "|" local e4 = "|" local possible_targets = {} --search next position on solid ground for search=1, maxsearcharea,1 do --find along edge 1 for current=-search,search,1 do local pos_tocheck = { x= pos.x + current,y=pos.y,z=pos.z -search} local pos_quality = environment.pos_quality(pos_tocheck,entity) local pos_state = checkfunc(pos_quality) dbg_mobf.environment_lvl3("MOBF: state of "..printpos(pos_tocheck).." is " .. dump(pos_state)) if pos_state then dbg_mobf.environment_lvl3(" -->found good pos") table.insert(possible_targets, pos_tocheck) else e1 = e1.."-|" end end --find along edge 2 for current=-(search-1),(search-1),1 do local pos_tocheck = { x= pos.x + search,y=pos.y,z=pos.z + current} local pos_quality = environment.pos_quality(pos_tocheck,entity) local pos_state = checkfunc(pos_quality) dbg_mobf.environment_lvl3("MOBF: state of "..printpos(pos_tocheck).." is " .. dump(pos_state)) if pos_state then dbg_mobf.environment_lvl3(" -->found good pos") table.insert(possible_targets, pos_tocheck) else e2 = e2.. "-|" end end --find along edge 3 for current=search,-search,-1 do local pos_tocheck = { x= pos.x + current,y=pos.y,z=pos.z + search} local pos_quality = environment.pos_quality(pos_tocheck,entity) local pos_state = checkfunc(pos_quality) dbg_mobf.environment_lvl3("MOBF: state of "..printpos(pos_tocheck).." is " .. dump(pos_state)) if pos_state then dbg_mobf.environment_lvl3(" -->found good pos") table.insert(possible_targets, pos_tocheck) else e3 = e3.."-|" end end --find along edge 4 for current=(search-1),-(search-1),-1 do local pos_tocheck = { x= pos.x -search,y=pos.y,z=pos.z + current} local pos_quality = environment.pos_quality(pos_tocheck,entity) local pos_state = checkfunc(pos_quality) dbg_mobf.environment_lvl3("MOBF: state of "..printpos(pos_tocheck).." is " .. dump(pos_state)) if pos_state then dbg_mobf.environment_lvl3(" -->found good pos") table.insert(possible_targets, pos_tocheck) else e4 = e4.."-|" end end end -- print("MOBF: Bug !!! didn't find a suitable position to place mob") -- print("Surrounding of " .. printpos(pos_raw) .. "was:") -- print(e1) -- print(" " .. e2) -- print(e4) -- print(e3) if #possible_targets > 0 then local i = math.random(1, #possible_targets) dbg_mobf.environment_lvl1("Found " .. #possible_targets .. " possible positions, selected: " .. i .. ": " .. printpos(possible_targets[i])) return possible_targets[i] end return nil end ------------------------------------------------------------------------------- -- @function [parent=#environment] get_suitable_pos_same_level(pos_raw,maxsearcharea,entity) -- --! @brief find a position suitable around a specific position --! @ingroup environment -- --! @param pos_raw position to look at --! @param maxsearcharea max range to look for suitable position --! @param entity mob to look for position --! @param accept_possible return position thats possible only too --! @return {x,y,z} position found or nil ------------------------------------------------------------------------------- function environment.get_suitable_pos_same_level(pos_raw,maxsearcharea,entity,accept_possible) if accept_possible then return environment.get_pos_same_level(pos_raw,maxsearcharea,entity, function(quality) if entity.data.movement.canfly then return environment.evaluate_state(quality,LT_GOOD_FLY_POS) else return environment.evaluate_state(quality,LT_SAFE_POSSIBLE_EDGE_POS) end end) else return environment.get_pos_same_level(pos_raw,maxsearcharea,entity, function(quality) if entity.data.movement.canfly then return environment.evaluate_state(quality,LT_GOOD_FLY_POS) else return environment.evaluate_state(quality,LT_SAFE_EDGE_POS) end end) end end ------------------------------------------------------------------------------- -- @function [parent=#environment] is_media_element(nodename,environment) -- --! @brief check if nodename is in environment --! @ingroup environment -- --! @param nodename name to check --! @param media environment of mob --! @return true/false ------------------------------------------------------------------------------ function environment.is_media_element( nodename, media ) --security check if media == false then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: BUG!!!! no environment specified!") return false end for i,v in ipairs(media) do if v == nodename then return true end end dbg_mobf.environment_lvl2("MOBF: " .. nodename .. " is not within environment list:") for i,v in ipairs(media) do dbg_mobf.environment_lvl3("MOBF: \t" .. v) end return false end ------------------------------------------------------------------------------- -- @function [parent=#environment] get_absolute_min_max_pos(env, pos) -- --! @brief check if nodename is in environment --! @ingroup environment -- --! @param env environment mob should be --! @param pos position it is currently --! @return { minpos,maxpos } ------------------------------------------------------------------------------ function environment.get_absolute_min_max_pos(env,pos) local node = minetest.get_node(pos) --if is not within environment it should be return current position --as min max if environment.is_media_element(node.name,env.media) == false then return pos.y,pos.y end local min_y = env.min_height_above_ground local max_y = env.max_height_above_ground --a fully generic check isn't possible here so we need to use media --specific ways ... it's ugly but works if node.name == "air" then min_y = min_y + ( pos.y - mobf_surface_distance(pos)) max_y = max_y + ( pos.y - mobf_surface_distance(pos)) end if node.name == "default:water" or node.name == "defailt:water_flowing" then -- water mobs do use min/max directly end if node.name == "default:lava" or node.name == "default:lava_flowing" then --TODO e.g. lava fish --not implemented by now end return min_y,max_y end ------------------------------------------------------------------------------- -- @function [parent=#environment] is_jumpable_surface(name) -- --! @brief check if name is a surface an mob may jump onto --! @ingroup environment -- --! @param name name to check --! @return true/false ------------------------------------------------------------------------------- function environment.is_jumpable_surface(name) if name == "default:dirt" or name == "default:dirt_with_grass" or name == "default:stone" or name == "default:sand" or name == "default:clay" then return true end dbg_mobf.environment_lvl1("MOBF: is "..name.." a jumpable surface?") return false end ------------------------------------------------------------------------------- -- @function [parent=#environment] checksurface(pos,surfaces) -- --! @brief check if a position is suitable for an mob --! @ingroup environment -- --! @param pos position to check --! @param surface surfaces valid --! @return true on valid surface false if not ------------------------------------------------------------------------------- function environment.checksurface(pos,surface) local pos_below = {x=pos.x,y=pos.y-1,z=pos.z} local node_below = minetest.get_node(pos_below) --if we couldn't even get the node count as wrong if node_below == nil then return "wrong_surface" end --if no surfaces are specified any surface is treated as ok if surface == nil then if minetest.registered_nodes[node_below.name] ~= nil then if minetest.registered_nodes[node_below.name].walkable then return "ok",node_below.name else return "wrong_surface",node_below.name end else dbg_mobf.environment_lvl1("ENV surface check: " .. node_below.name.. " isn't even a registred node??") --not a registred nodename??? count as wrong surface return "wrong_surface",node_below.name end end for i,v in ipairs(surface.good) do if node_below.name == v then return "ok",node_below.name end end if surface.possible ~= nil then for i,v in ipairs(surface.possible) do if node_below.name == v then return "possible_surface",node_below.name end end end return "wrong_surface",node_below.name end ------------------------------------------------------------------------------- -- @function [parent=#environment] get_min_max_ground_dist(entity) -- --! @brief calculate absolute minimum and maximum height for a mob --! @ingroup environment -- --! @param entity mob to check --! @return min y val,max y val ------------------------------------------------------------------------------- function environment.get_min_max_ground_dist(entity) local min_ground_distance = 0 local max_ground_distance = 0 if entity.environment.max_height_above_ground ~= nil then max_ground_distance = entity.environment.max_height_above_ground end if entity.environment.min_height_above_ground ~= nil then min_ground_distance = entity.environment.min_height_above_ground end if entity.data.movement.canfly == nil or entity.data.movement.canfly == false then max_ground_distance = 1 end return min_ground_distance,max_ground_distance end ------------------------------------------------------------------------------- -- @function [parent=#environment] evaluate_pos_media(pos,media) -- --! @brief check position media quality --! @ingroup environment -- --! @param pos position to check --! @param media media description to check --! --! @return 100 = in media 10 collision 0 invalid, nodename ------------------------------------------------------------------------------- function environment.evaluate_pos_media(pos,media) local node_to_check = minetest.get_node(pos) if node_to_check == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: BUG!!!! checking position with invalid node") return 0,nil end if not environment.is_media_element(node_to_check.name,media) then return 10,node_to_check end return 100,node_to_check end ------------------------------------------------------------------------------- -- @function [parent=#environment] same_state(state1,state2) -- --! @brief compare two states --! @ingroup environment -- --! @param state1 --! @param state2 --! --! @return true/false ------------------------------------------------------------------------------- function environment.same_state(state1,state2) environment.is_state(state1) environment.is_state(state2) if state1.valid == false or state2.valid == false then return false end if state1.media_quality ~= state2.media_quality then return false end if state1.geometry_quality ~= state2.geometry_quality then return false end if state1.surface_quality_min ~= state2.surface_quality_min then return false end if state1.surface_quality_max ~= state2.surface_quality_max then return false end if state1.level_quality ~= state2.level_quality then return false end return true end ------------------------------------------------------------------------------- -- @function [parent=#environment] compare_state(state1,state2) -- --! @brief compare state1 and state2 --! @ingroup environment -- --! @param state1 --! @param state2 --! --! @return -1, 0, 1 ------------------------------------------------------------------------------- function environment.compare_state(state1,state2) environment.is_state(state1) environment.is_state(state2) local right_better = false local left_better = false if state1.valid == false and state2.valid == true then return 1 end if state1.valid == true and state2.valid == false then return -1 end if state1.media_quality < state2.media_quality or state1.geometry_quality < state2.geometry_quality or state1.surface_quality_min < state2.surface_quality_min or state1.surface_quality_max < state2.surface_quality_max or state1.level_quality < state2.level_quality then right_better = true end if state1.media_quality > state2.media_quality or state1.geometry_quality > state2.geometry_quality or state1.surface_quality_min > state2.surface_quality_min or state1.surface_quality_max > state2.surface_quality_max or state1.level_quality > state2.level_quality then left_better = true end if right_better and not left_better then return 1 end if left_better and not right_better then return -1 end return 0 end ------------------------------------------------------------------------------- -- @function [parent=#environment] is_state(state) -- -- --! @brief assert if state is something else than a state --! @ingroup environment ------------------------------------------------------------------------------- function environment.is_state(state) mobf_assert_backtrace(state ~= nil) mobf_assert_backtrace(type(state) == "table") mobf_assert_backtrace(state.valid ~= nil) mobf_assert_backtrace(state.media_quality ~= nil) mobf_assert_backtrace(state.geometry_quality ~= nil) mobf_assert_backtrace(state.center_geometry_quality ~= nil) mobf_assert_backtrace(state.surface_quality_min ~= nil) mobf_assert_backtrace(state.surface_quality_max ~= nil) mobf_assert_backtrace(state.level_quality ~= nil) mobf_assert_backtrace(state.surface_quality_center ~= nil) end ------------------------------------------------------------------------------- -- @function [parent=#environment] evaluate_state(state,old_state, min_media, -- min_geom,min_geom_center,min_min_surface,min_max_surface) -- --! @brief evaluate a position --! @ingroup environment -- -- @param state verify this state -- @param limits = { -- old_state if set new state needs to be better or equal to this state only -- min_media minimum media quality required -- min_geom minimum geometry quality required -- min_geom_center minimum geometry quality at center -- min_min_surface minimum value for minimal surface quality -- min_max_surface minimum value for maximal surface quality -- min_center_surface minimum value for surface quality at center -- } -- -- @return true/false ------------------------------------------------------------------------------- function environment.evaluate_state(state,limits) mobf_assert_backtrace(type(limits) == "table") environment.is_state(state) if state.valid == false then return false end if limits.min_media ~= nil and state.media_quality < limits.min_media then return false end if limits.min_geom_center ~= nil and state.center_geometry_quality < limits.min_geom_center then return false end if limits.old_state ~= nil then if environment.compare_state(state,limits.old_state) == -1 then return true end end local retval = true if limits.min_geom ~= nil and state.geometry_quality < limits.min_geom then retval = false end if limits.min_min_surface ~= nil and state.surface_quality_min < limits.min_min_surface then retval = false end if limits.min_max_surface ~= nil and state.surface_quality_max < limits.min_max_surface then retval = false end if limits.min_center_surface ~= nil and state.surface_quality_center < limits.min_center_surface then retval = false end return retval end ------------------------------------------------------------------------------- -- @function [parent=#environment] pos_quality(pos,entity) -- --! @brief check position quality --! @ingroup environment -- --! @param pos position to check --! @param entity mob to check --! @return position quality --! { --! media_quality 100 = in media --! 30 = collision (not passed outside) --! 20 = special in_water --! 10 = special in_air --! 5 = solid --! 0 = not evaluated --! geometry_quality = 100 = full contact --! 60 = partial contact --! 30 = no contact --! 0 = not evaluated --! center_geometry_quality = 100 = contact --! 30 = no contact --! 0 = not evaluated --! surface_quality_min = 100 = all ok --! 60 = worst is possible --! 30 = worst is wrong --! 10 = special "above_water" --! 0 = not evaluated --! surface_quality_max = 100 = all ok --! 60 = best is possible --! 30 = best is wrong --! 10 = special "above_water" --! 0 = not evaluated --! surface_quality_center = 100 = ok --! 60 = best is possible --! 30 = best is wrong --! 10 = special "above_water" --! 0 = not evaluated --! level_quality = 100 = ok --! 60 = above_limit --! 30 = below_limit --! 0 = not evaluated --! valid = true = data is valid --! false = no valid data ------------------------------------------------------------------------------- function environment.pos_quality(pos,entity) mobf_assert_backtrace(pos ~= nil) mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(entity.collisionbox ~= nil) mobf_assert_backtrace(entity.environment ~= nil) local retval = { media_quality = 100, geometry_quality = 0, center_geometry_quality = 0, surface_quality_min = 100, surface_quality_max = 0, surface_quality_center = 0, level_quality = 100, valid = true, tostring = function(state) environment.is_state(state) local retval = "\nState: ".. dump(state.valid) .. "\n" retval = retval .."\tmedia_quality: (" .. state.media_quality ..") " if state.media_quality == 100 then retval = retval .. "in media" end if state.media_quality == 30 then retval = retval .. "collision" end if state.media_quality == 20 then retval = retval .. "in water" end if state.media_quality == 10 then retval = retval .. "in air" end if state.media_quality == 5 then retval = retval .. "solid" end if state.media_quality == 0 then retval = retval .. "not evaluated" end retval = retval .."\n\tgeometry_quality: (" .. state.geometry_quality .. ") " if state.geometry_quality == 100 then retval = retval .. "full contact" end if state.geometry_quality == 60 then retval = retval .. "partial contact" end if state.geometry_quality == 30 then retval = retval .. "no contact" end if state.geometry_quality == 0 then retval = retval .. "not evaluated" end retval = retval .."\n\tcenter_geometry_quality: (" .. state.center_geometry_quality .. ") " if state.center_geometry_quality == 100 then retval = retval .. "contact" end if state.center_geometry_quality == 30 then retval = retval .. "no contact" end if state.center_geometry_quality == 0 then retval = retval .. "not evaluated" end retval = retval .."\n\tsurface_quality_min: (" .. state.surface_quality_min ..") " if state.surface_quality_min == 100 then retval = retval .. "ok" end if state.surface_quality_min == 60 then retval = retval .. "possible" end if state.surface_quality_min == 30 then retval = retval .. "wrong" end if state.surface_quality_min == 10 then retval = retval .. "above water" end if state.surface_quality_min == 0 then retval = retval .. "not evaluated" end retval = retval .."\n\tsurface_quality_max: (" .. state.surface_quality_max .. ") " if state.surface_quality_max == 100 then retval = retval .. "ok" end if state.surface_quality_max == 60 then retval = retval .. "possible" end if state.surface_quality_max == 30 then retval = retval .. "wrong" end if state.surface_quality_max == 10 then retval = retval .. "above water" end if state.surface_quality_max == 0 then retval = retval .. "not evaluated" end retval = retval .."\n\tsurface_quality_center: (" .. state.surface_quality_center .. ") " if state.surface_quality_center == 100 then retval = retval .. "ok" end if state.surface_quality_center == 60 then retval = retval .. "possible" end if state.surface_quality_center == 30 then retval = retval .. "wrong" end if state.surface_quality_center == 10 then retval = retval .. "above water" end if state.surface_quality_center == 0 then retval = retval .. "not evaluated" end retval = retval .."\n\tlevel_quality: (" .. state.level_quality .. ") " if state.level_quality == 100 then retval = retval .. "ok" end if state.level_quality == 60 then retval = retval .. "above limit" end if state.level_quality == 30 then retval = retval .. "below limit" end if state.level_quality == 0 then retval = retval .. "not evaluated" end retval = retval .. "\n" return retval end, shortstring = function(state) return dump(state.valid) .. ":" .. state.media_quality .. ";" .. state.geometry_quality .. "," .. state.center_geometry_quality .. ";" .. state.surface_quality_min .. "," .. state.surface_quality_center .. "," ..state.surface_quality_max .. ";" .. state.level_quality end } local cornerpositions = {} local lastpos = nil -- performance improvement to skip checking same pos multiple times table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) local min_ground_distance,max_ground_distance = environment.get_min_max_ground_dist(entity) --check if mob at pos will be in correct environment for i=1,#cornerpositions,1 do if not mobf_pos_is_same(lastpos,cornerpositions[i]) then local med_quality,node_to_check = environment.evaluate_pos_media(cornerpositions[i], entity.environment.media) --if current result is worse than old one if med_quality < retval.media_quality then if med_quality == 0 then retval.valid = false end if node_to_check.name == "default:water_source" or node_to_check.name == "default:water_flowing" then retval.media_quality = 20 end if node_to_check.name == "air" then retval.media_quality = 10 break end if med_quality < retval.media_quality then retval.media_quality = 5 end end end lastpos = cornerpositions[i] end --check height level for flying mobs if entity.data.movement.canfly == true then lastpos = nil for i=1,#cornerpositions,1 do if not mobf_pos_is_same(lastpos,cornerpositions[i]) then local miny,maxy = environment.get_absolute_min_max_pos(entity.environment,cornerpositions[i]) dbg_mobf.environment_lvl2("MOBF: \tflying mob detected, min: " .. miny .. " max: " .. maxy .. " current: " .. pos.y) if cornerpositions[i].y < miny then retval.level_quality = 30 end if cornerpositions[i].y > maxy then retval.level_quality = 60 end if retval.level_quality < 100 then break end end lastpos = cornerpositions[i] end else --check geometry and surface quality lastpos = nil local have_contact = false local have_no_contact = false table.insert(cornerpositions,pos) for i=1,#cornerpositions,1 do if not mobf_pos_is_same(lastpos,cornerpositions[i]) then local ground_distance = mobf_ground_distance(cornerpositions[i], entity.environment.media) --first check if on surface or not if ground_distance <= max_ground_distance then local is_center = false if mobf_pos_is_same(pos,cornerpositions[i]) then retval.center_geometry_quality = 100 is_center = true end have_contact = true local current_surface,belowname = environment.checksurface(cornerpositions[i],entity.environment.surfaces) if current_surface == "ok" then if retval.surface_quality_max < 100 then retval.surface_quality_max = 100 end if is_center then retval.surface_quality_center = 100 end end if current_surface == "possible_surface" then if retval.surface_quality_max < 60 then retval.surface_quality_max = 60 end if retval.surface_quality_min > 60 then retval.surface_quality_min = 60 end if is_center then retval.surface_quality_center = 60 end end if current_surface == "wrong_surface" then if belowname == "default:water_source" or belowname == "default:water_flowing" then if retval.surface_quality_max < 10 then retval.surface_quality_max = 10 end if retval.surface_quality_min > 10 then retval.surface_quality_min = 10 end if is_center then retval.surface_quality_center = 10 end else if retval.surface_quality_max < 30 then retval.surface_quality_max = 30 end if retval.surface_quality_min > 30 then retval.surface_quality_min = 30 end if is_center then retval.surface_quality_center = 30 end end end else if mobf_pos_is_same(pos,cornerpositions[i]) then retval.center_geometry_quality = 30 end have_no_contact = true end end lastpos = cornerpositions[i] end if have_contact and not have_no_contact then retval.geometry_quality = 100 end if have_contact and have_no_contact then retval.geometry_quality = 60 end if not have_contact and have_no_contact then retval.geometry_quality = 30 end end return retval end ------------------------------------------------------------------------------- -- @function [parent=#environment] pos_is_ok(pos,entity) DEPRECATED -- --! @brief check if a position is suitable for an mob --! @ingroup environment -- --! @param pos position to check --! @param entity mob to check --! @param dont_do_jumpcheck --! @return suitability of position for mob values: --! -ok -@>position is ok --! -collision -@>position is within a node --! -collision_jumpable -@>position is within a node that can be jumped onto --! -drop -@>position is a drop --! -drop_above_water -@>position is to far above water --! -above_water -@>position is right over water --! -in_water -@>position is within a water node(source or flow) --! -in_air -@>position is in air --! -above_limit -@>position is above level limit --! -below_limit -@>position is below level limit --! -wrong_surface -@>position is above surface mob shouldn't be --! -invalid -@>unable to check position ------------------------------------------------------------------------------- function environment.pos_is_ok(pos,entity,dont_do_jumpcheck) local min_ground_distance,max_ground_distance = environment.get_min_max_ground_dist(entity) local cornerpositions = {} table.insert(cornerpositions,pos) --read positions at corners table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) local lastpos = nil local retval = "temp_ok" local min_ground_distance = 33000 -- above max height --check if mob at pos will be in correct environment for i=1,#cornerpositions,1 do if not mobf_pos_is_same(lastpos,cornerpositions[i]) then local node_to_check = minetest.get_node(cornerpositions[i]) if node_to_check == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: BUG!!!! checking position with invalid node") retval = "invalid" break end if not environment.is_media_element(node_to_check.name,entity.environment.media) == true then dbg_mobf.environment_lvl3("MOBF: " .. i .. ": " .. printpos(cornerpositions[i]) .. " -- " .. printpos(pos) .. " not within environment") if mobf_pos_is_same(pos,cornerpositions[i]) then if node_to_check.name == "default:water_source" or node_to_check.name == "default:water_flowing" then retval = "in_water" break end if node_to_check.name == "air" then retval = "in_air" break end --TODO maybe replace by "invalid medium" else retval = "collision" end end min_ground_distance = MIN(min_ground_distance, mobf_ground_distance(cornerpositions[i],entity.environment.media)) end lastpos = cornerpositions[i] end -- if retval == "temp_ok" then dbg_mobf.environment_lvl3("MOBF: \tin environment") local ground_distance = mobf_ground_distance(pos,entity.environment.media) --following return codes are only usefull for non flying if entity.data.movement.canfly == nil or entity.data.movement.canfly == false then if mobf_above_water(pos) then if min_ground_distance > max_ground_distance then dbg_mobf.environment_lvl2("MOBF: \tdropping above water") retval = "drop_above_water" end dbg_mobf.environment_lvl2("MOBF: \tabove water") retval = "above_water" end if min_ground_distance > max_ground_distance then dbg_mobf.environment_lvl2("MOBF: \tdropping " .. ground_distance .. " / " .. max_ground_distance) retval = "drop" else dbg_mobf.environment_lvl2("MOBF: \tsurface dependent") retval = environment.checksurface(pos,entity.environment.surfaces) end else local miny,maxy = environment.get_absolute_min_max_pos(entity.environment,pos) dbg_mobf.environment_lvl2("MOBF: \tflying mob detected, min: " .. miny .. " max: " .. maxy .. " current: " .. pos.y) if pos.y < miny then retval = "below_limit" else if pos.y > maxy then retval = "above_limit" else retval = environment.checksurface(pos,entity.environment.surfaces) end end end end if retval == "collision" and not dont_do_jumpcheck then dbg_mobf.environment_lvl2("MOBF: check if pos is jumpable") local upper_pos_state = environment.pos_is_ok({x=pos.x, y=pos.y+1, z=pos.z}, entity,true) if upper_pos_state == "ok" then retval = "collision_jumpable" else dbg_mobf.environment_lvl2("MOBF: upper pos state was: " .. upper_pos_state) end end return retval end ------------------------------------------------------------------------------- -- @function [parent=#environment] get_default_gravity(pos,environment,canfly) -- --! @brief get default acceleration depending on mobs medium and pos --! @ingroup environment -- --! @param pos position where to check gravity --! @param media mobs movement medium --! @param canfly is mob capable of flying? --! @return y-acceleration ------------------------------------------------------------------------------ function environment.get_default_gravity(pos,media,canfly) if pos == nil then return nil end local node = minetest.get_node(pos) --if an mob can't fly or isn't within it's medium default acceleration -- for it's current medium is applied if canfly == nil or canfly == false or environment.is_media_element(node.name,media) == false then if (node.name == "air") then return -9.81 end if node.name == "default:water_source" or node.name == "default:water_flowing" then return -2.5 end if node.name == "default:lava" then return 0.1 end --mob is at invalid position thus returning default air acceleration return -9.81 end return 0 end ------------------------------------------------------------------------------- -- @function [parent=#environment] fix_base_pos(entity, middle_to_bottom) -- --! @brief fix the mobs y position according to model or sprite height --! @ingroup environment -- --! @param entity mob to fix base position --! @param center_to_bottom distance from center of mob to its bottom (absolute value) --! @return new position set by function ------------------------------------------------------------------------------ function environment.fix_base_pos(entity, center_to_bottom) if center_to_bottom > 0.5 then local pos = entity.object:getpos() local node_pos = minetest.get_node(pos) local pos_to_check = {x=pos.x,y=pos.y-center_to_bottom+0.1,z=pos.z} local node_pos_check = minetest.get_node(pos_to_check) if node_pos ~= nil and node_pos_check ~= nil then dbg_mobf.environment_lvl3("MOBF: fixing y position / base position required? " .. node_pos.name .. " " .. node_pos_check.name) if node_pos.name ~= node_pos_check.name then local distance_to_ground = mobf_surface_distance(pos) pos.y = pos.y + (center_to_bottom - distance_to_ground +0.5) dbg_mobf.environment_lvl2("MOBF: fixing y position of " .. entity.data.name .. " got distance " .. center_to_bottom .. " moving to " ..printpos(pos)) entity.object:moveto(pos) entity.dynamic_data.spawning.spawnpoint = pos end end end return entity.getbasepos(entity) end ------------------------------------------------------------------------------- -- @function [parent=#environment] register(name, environment) -- --! @brief register an environment to mob framework --! @ingroup environment -- --! @param name id of environment --! @param environment description of environment --! @return true/false succesfully registred environment ------------------------------------------------------------------------------- function environment.register(name, environment) if environment_list[name] ~= nil then return false end environment_list[name] = environment return true end ------------------------------------------------------------------------------- -- @function [parent=#environment] pos_state_is_impossible(entity,pos) -- --! @brief checks if a entity can be there (not if it would move there by its own) --! @ingroup environment -- --! @param entity entity to check --! @param pos position to check --! @return true entity may be there, entity can never be there ------------------------------------------------------------------------------- function environment.possible_pos(entity,pos) local state = environment.pos_is_ok(pos,entity) if state == "collision" or state == "collision_jumpable" or state == "invalid" then return false end return true end --!@} mobf_core-2.5.1/mobf/factions.lua000066400000000000000000000234651264104133000167120ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file factions.lua --! @brief contains factions adaption of mobf --! @copyright Sapier --! @author Sapier --! @date 2013-08-21 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mobf_factions")) --! @class mobf_factions --! @brief faction mod support for mobf mobf_factions = {} ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] init() -- --! @brief initialize mobf factions support --! @memberof mobf_factions -- ------------------------------------------------------------------------------- function mobf_factions.init() if minetest.get_modpath("factions")then mobf_rtd.factions_available = true end --need to catch my form events minetest.register_on_player_receive_fields(mobf_factions.button_handler) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] setupmob(factionsdata) -- --! @brief creat factions a mob may be member of --! @memberof mobf_factions -- --! @param factionsdata data to set for mob -- ------------------------------------------------------------------------------- function mobf_factions.setupmob(factionsdata) if mobf_rtd.factions_available then if factionsdata ~= nil and factionsdata.member ~= nil then for i=1,#factionsdata.member,1 do if not factions.exists(factionsdata.member[i]) then factions.add_faction(factionsdata.member[i]) end end end end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] setupentity(entity,preserved_data) -- --! @brief add a mob to it's factons --! @memberof mobf_factions -- --! @param entity to be added to it's factions --! @param preserved_data mob reputation data -- ------------------------------------------------------------------------------- function mobf_factions.setupentity(entity,preserved_data) if mobf_rtd.factions_available then if entity.data.factions ~= nil and entity.data.factions.member ~= nil then for i=1,#entity.data.factions.member,1 do factions.member_add(entity.data.factions.member[i],entity) end end if preserved_data ~= nil then for i=1,#preserved_data,1 do factions.member_add(preserved_data[i],entity) end end end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] cleanup_entity(entity) -- --! @brief called on deactivation of a mob --! @memberof mobf_factions -- --! @param entity to be cleant up -- ------------------------------------------------------------------------------- function mobf_factions.cleanupentity(entity) if mobf_rtd.factions_available then return factions.get_factions(entity.object) else return "" end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] mob_rightclick_callback(entity,player) -- --! @brief show factions rightclick menu --! @memberof mobf_factions -- --! @param entity to modify --! @param player issuing rightclick -- ------------------------------------------------------------------------------- function mobf_factions.mob_rightclick_callback(entity,player) local menu_data = {} local new_id = mobf_global_data_store(menu_data) menu_data.entity = entity menu_data.player = player mobf_factions.show_mob_factions_menu(new_id,menu_data) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] config_check(entity,player) -- --! @brief show factions rightclick menu --! @memberof mobf_factions -- --! @param entity clicked -- ------------------------------------------------------------------------------- function mobf_factions.config_check(entity) if not mobf_rtd.factions_available then return false end return false end ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] button_handler(entity,player) -- --! @brief show factions rightclick menu --! @memberof mobf_factions -- --! @param player issuing rightclick --! @param formname name of form being transmitted --! @param fields date for fields in current form -- ------------------------------------------------------------------------------- function mobf_factions.button_handler(player, formname, fields) local playername = player:get_player_name() mobf_assert_backtrace(playername ~= nil) if formname == "mobf:factions:main_menu" then dbg_mobf.path_lvl2("MOBF: factions menu opened") for k,v in pairs(fields) do local parts = string.split(k,":") if parts[1] == "mobf_factions" then local menu_data = mobf_factions.handle_rightclick(parts,fields,formname,player,k) if menu_data ~= nil then --push data back to store local new_id = mobf_global_data_store(menu_data) mobf_factions.show_mob_factions_menu(new_id,menu_data) break end end end return true end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] handle_rightclick(current_fields,fields,formname,player,fieldname) -- --! @brief show factions rightclick menu --! @memberof mobf_factions -- --! @param current_fields --! @param fields --! @param formname --! @param player --! @param fieldname name of field causingt the callback -- --! @return true/false show menu again ------------------------------------------------------------------------------- function mobf_factions.handle_rightclick(current_fields,fields,formname,player,fieldname) print("current_fields: " .. dump(current_fields)) --try to read data from global store local menu_data = mobf_global_data_get(current_fields[3]) if menu_data ~= nil then if current_fields[2] == "btn_add_mob_faction" then if menu_data.tl_owner_selected ~= nil then local factionlist_owner = factions.get_factions(menu_data.player) if factionlist_owner ~= nil and #factionlist_owner >= menu_data.tl_owner_selected then local toadd = factionlist_owner[menu_data.tl_owner_selected] factions.member_add(toadd,menu_data.entity.object) end end elseif current_fields[2] == "btn_drop_mob_faction" then if menu_data.tl_mob_selected ~= nil then local factionlist_mob = factions.get_factions(menu_data.entity.object) local todel = factionlist_mob[menu_data.tl_mob_selected] if factions.is_member(todel,player) then factions.member_remove(todel,menu_data.entity.object) else --TODO maybe send chat message print("MOBF Factions you're not member of " .. todel .. " so you can't remove a mob from it") end else print("MOBF Factions: trying to delete from faction but no faction selected") end elseif current_fields[2] == "tl_mob_factions" then local event = core.explode_textlist_event(fields[fieldname]) if event.typ ~= "INV" then menu_data.tl_mob_selected = event.index end elseif current_fields[2] == "tl_owner_factions" then local event = core.explode_textlist_event(fields[fieldname]) if event.typ ~= "INV" then menu_data.tl_owner_selected = event.index end end else dbg_mobf.path_lvl1("MOBF: factions menu failed to find menu_data") end return menu_data end ------------------------------------------------------------------------------- -- @function [parent=#mobf_factions] show_mob_factions_menu(new_id,menu_data) -- --! @brief show factions rightclick menu --! @memberof mobf_factions -- --! @param new_id --! @param menu_data -- --! @return true/false show menu again ------------------------------------------------------------------------------- function mobf_factions.show_mob_factions_menu(new_id,menu_data) mobf_assert_backtrace(menu_data.entity ~= nil) mobf_assert_backtrace(menu_data.player ~= nil) local playername = menu_data.player:get_player_name() local formspec = "" --check if menu creator is owner of the specific mob if menu_data.entity.dynamic_data.spawning.spawner == playername then if menu_data.tl_mob_selected == nil then menu_data.tl_mob_selected = 0 end if menu_data.tl_owner_selected == nil then menu_data.tl_owner_selected = 0 end formspec = formspec .. "size[8,8]" .."label[0,-0.25;Member of factions:]" .."textlist[0,0.3;3.7,7;mobf_factions:tl_mob_factions:" .. new_id .. ";" local factionlist_mob = factions.get_factions(menu_data.entity.object) if #factionlist_mob ~= 0 then for i=1,#factionlist_mob,1 do formspec = formspec .. factionlist_mob[i] .. "," end else formspec = formspec .. "not member of any faction" end formspec = formspec .. ";" .. menu_data.tl_mob_selected .. "]" local factionlist_player = factions.get_factions(menu_data.player) formspec = formspec .."label[4,-0.25;Owner factions:]" .."textlist[4,0.3;3.7,7;mobf_factions:tl_owner_factions: " .. new_id .. ";" if #factionlist_player ~= 0 then for i=1,#factionlist_player,1 do formspec = formspec .. factionlist_player[i] .. "," end else formspec = formspec .. "not member of any faction" end formspec = formspec .. ";" .. menu_data.tl_owner_selected .. "]" formspec = formspec .. "button[0,7.5;3.95,0.5;mobf_factions:btn_drop_mob_faction:" .. new_id .. ";Remove from faction]" .. "button[4,7.5;3.95,0.5;mobf_factions:btn_add_mob_faction:" .. new_id .. ";<-- Mob join faction]" else formspec = "size[4,1]label[0,0;This is not your mob keep away!]" .. "button_exit[1,0.75;2,0.5;btn_exit;Okay Okay!]" end --show formspec minetest.show_formspec(playername,"mobf:factions:main_menu",formspec) endmobf_core-2.5.1/mobf/fighting.lua000066400000000000000000001324271264104133000167020ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file fighting.lua --! @brief component for fighting related features --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup fighting Combat subcomponent --! @brief Component handling all fighting --! @ingroup framework_int --! @{ -- Contact: sapier a t gmx net ------------------------------------------------------------------------------- --! @class fighting --! @brief factor added to mob melee combat range to get its maximum agression radius MOBF_AGRESSION_FACTOR = 5 --!@} mobf_assert_backtrace(not core.global_exists("fighting")) --! @brief fighting class reference fighting = {} fighting.healdb = minetest.world_setting_get("fighting.healdb") --! @brief user defined on death callback --! @memberof fighting fighting.on_death_callbacks = {} ------------------------------------------------------------------------------- -- @function [parent=#fighting] register_on_death_callback(callback) -- --! @brief register an additional callback to be called on death of a mob --! @memberof fighting -- --! @param callback function to call --! @return true/false ------------------------------------------------------------------------------- function fighting.register_on_death_callback(callback) if type(callback) == "function" then table.insert(fighting.on_death_callbacks,callback) return true end return false end ------------------------------------------------------------------------------- -- @function [parent=#fighting] do_on_death_callback(entity,hitter) -- --! @brief call all registred on_death callbacks --! @memberof fighting -- --! @param entity to do callback for --! @param hitter object doing last punch ------------------------------------------------------------------------------- function fighting.do_on_death_callback(entity,hitter) for i,v in ipairs(fighting.on_death_callbacks) do v(entity.data.name,entity.getbasepos(),hitter) end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] push_back(entity,player) -- --! @brief move a mob backward if it's punched --! @memberof fighting --! @private -- --! @param entity mobbeing punched --! @param dir direction to push back ------------------------------------------------------------------------------- function fighting.push_back(entity,dir) --get some base information local mob_pos = entity.object:getpos() local mob_basepos = entity.getbasepos(entity) local dir_rad = mobf_calc_yaw(dir.x,dir.z) local posdelta = mobf_calc_vector_components(dir_rad,0.5) --push back mob local new_pos = { x=mob_basepos.x + posdelta.x, y=mob_basepos.y, z=mob_basepos.z + posdelta.z } local pos_valid = environment.possible_pos(entity,new_pos) new_pos.y = mob_pos.y local line_of_sight = mobf_line_of_sight(mob_pos,new_pos) dbg_mobf.fighting_lvl2("MOBF: trying to punch mob from " .. printpos(mob_pos) .. " to ".. printpos(new_pos)) if pos_valid and line_of_sight then dbg_mobf.fighting_lvl2("MOBF: punching back ") entity.object:moveto(new_pos) else dbg_mobf.fighting_lvl2("MOBF: not punching mob: " .. dump(pos_valid) .. " " ..dump(line_of_sight)) end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] dodamage(entity,attacker) -- --! @brief cause damage to be done to entity --! @memberof fighting -- --! @param entity mob being hit --! @param attacker player/object hitting the mob --! @param kill_reason reason to log for killing ------------------------------------------------------------------------------- function fighting.dodamage(entity,attacker, kill_reason) local mob_pos = entity.object:getpos() --update lifebar mobf_lifebar.set(entity.lifebar,entity.object:get_hp()/entity.hp_max) -- make it die if entity.object:get_hp() < 0.5 then mobf_lifebar.del(entity.lifebar) local result = entity.data.generic.kill_result if type(entity.data.generic.kill_result) == "function" then result = entity.data.generic.kill_result(entity, attacker) end --call on kill callback and superseed normal on kill handling if entity.data.generic.on_kill_callback == nil or entity.data.generic.on_kill_callback(entity,attacker) == false then if entity.data.sound ~= nil then sound.play(mob_pos,entity.data.sound.die); end if attacker:is_player() then if type(result) == "table" then for i=1,#result, 1 do if attacker:get_inventory():room_for_item("main", result[i]) then attacker:get_inventory():add_item("main", result[i]) end end else if attacker:get_inventory():room_for_item("main", result) then attacker:get_inventory():add_item("main", result) end end else --todo check if spawning a stack is possible minetest.add_item(mob_pos,result) end spawning.remove(entity, kill_reason) else dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. " custom on kill handler superseeds generic handling") end return end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] hit(entity,attacker) -- --! @brief handler for mob beeing hit --! @memberof fighting -- --! @param entity mob being hit --! @param attacker player/object hitting the mob ------------------------------------------------------------------------------- function fighting.hit(entity,attacker) mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(attacker ~= nil) --execute user defined on_hit_callback if entity.data.generic.on_hit_callback ~= nil and entity.data.generic.on_hit_callback(entity,attacker) == true then dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. " custom on hit handler superseeds generic handling") return end --get some base information local mob_pos = entity.object:getpos() local mob_basepos = entity.getbasepos(entity) local targetpos = attacker:getpos() local dir = mobf_get_direction(targetpos,mob_basepos) --don't attack spawner if entity.dynamic_data.spawning.spawner ~= nil and attacker:is_player() then local playername = attacker:get_player_name() if entity.dynamic_data.spawning.spawner == playername then if entity.dynamic_data.state.current ~= "combat" then local tool = attacker:get_wielded_item() -- rotation is only done if player punches using hand if tool:get_name() == "" then local current_yaw = graphics.getyaw(entity) graphics.setyaw(entity, current_yaw + math.pi/4) return end end end end --play hit sound if entity.data.sound ~= nil then sound.play(mob_pos,entity.data.sound.hit); end if entity.data.combat ~= nil and entity.data.combat.on_hit_overlay ~= nil then mobf_assert_backtrace( entity.data.combat.on_hit_overlay.texture ~= nil) mobf_assert_backtrace( entity.data.combat.on_hit_overlay.timer ~= nil) if entity.dynamic_data.combat.old_textures == nil then entity.dynamic_data.combat.old_textures = entity.textures end local new_props = { textures = { entity.dynamic_data.combat.old_textures[1] .. "^" .. entity.data.combat.on_hit_overlay.texture } } core.after(entity.data.combat.on_hit_overlay.timer,function() local restore_probs = { textures = entity.dynamic_data.combat.old_textures } entity.dynamic_data.combat.old_textures = nil entity.object:set_properties(restore_probs) end) entity.object:set_properties(new_props) end --push mob back fighting.push_back(entity,dir) --cause damage to be evaluated fighting.dodamage(entity, attacker, "killed") --dbg_mobf.fighting_lvl2("MOBF: attack chance is ".. entity.data.combat.angryness) -- fight back if entity.data.combat ~= nil and ( entity.data.combat.can_fight or (entity.data.combat.angryness ~= nil and entity.data.combat.angryness > 0) ) and entity.object:get_hp() > (entity.data.generic.base_health/3) then --face attacker if entity.mode ~= "3d" then graphics.setyaw(entity, mobf_calc_yaw(dir.x,dir.z)-math.pi) end dbg_mobf.fighting_lvl2("MOBF: mob with chance of fighting back attacked") --either the mob hasn't been attacked by now or a new player joined fight if math.random() < entity.data.combat.angryness then fighting.set_target(entity,attacker) end else --make non agressive animals run away fighting.run_away(entity,dir,attacker) end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] run_away(entity,dir_to_enemy,enemy) -- --! @brief make a mob run away --! @memberof fighting --! @private -- --! @param entity mob to run away --! @param dir_to_enemy direction towards enemy --! @param enemy the enemy to avoid ------------------------------------------------------------------------------- function fighting.run_away(entity,dir_to_enemy,enemy) local flee_state = mob_state.get_state_by_name(entity,"flee") if flee_state == nil then local new_state = mob_state.get_state_by_name(entity,"walking") local dir_rad = mobf_calc_yaw(dir_to_enemy.x,dir_to_enemy.z) local fleevelocity = mobf_calc_vector_components(dir_rad, entity.data.movement.max_accel*2) local current_accel = entity.object:getacceleration() local current_velocity = entity.object:getvelocity() mob_state.change_state(entity,new_state) entity.object:setvelocity({x=0,y=current_velocity.y,z=0}) entity.object:setacceleration({ x=fleevelocity.x, y=current_accel.y, z=fleevelocity.z} ) else mob_state.change_state(entity,flee_state) entity.dynamic_data.current_movement_gen.set_target(entity,enemy) end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] identify_combat_state(entity,target,distance) -- --! @brief identify combat state to use --! @memberof fighting --! @private -- --! @param entity mob to find state for --! @param distance distance to target -- --! @return state to use ------------------------------------------------------------------------------- function fighting.identify_combat_state(entity,distance) local target = entity.dynamic_data.combat.target mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(target ~= nil) local combat_melee = mob_state.get_state_by_name(entity,"combat_melee") local combat_distance = mob_state.get_state_by_name(entity,"combat_distance") local combat_generic = mob_state.get_state_by_name(entity,"combat") if distance == nil then local mob_pos = entity.object:getpos() local targetpos = target:getpos() distance = mobf_calc_distance(mob_pos,targetpos) end dbg_mobf.fighting_lvl2("MOBF: Identify combat state, mob: " .. entity.data.name .. " distance: " .. distance) if combat_melee ~= nil and distance < entity.data.combat.melee.range then return combat_melee end if combat_distance and entity.data.combat.distance ~= nil and distance < entity.data.combat.distance.range and (entity.data.combat.distance.min_range == nil or distance > entity.data.combat.distance.min_range) then -- distance within mele range return combat_distance end return combat_generic end ------------------------------------------------------------------------------- -- @function [parent=#fighting] switch_to_combat_state(entity,now,target) -- --! @brief switch to combat state --! @memberof fighting --! @private -- --! @param entity mob to switch state --! @param now current time in seconds --! @param target the target to attack ------------------------------------------------------------------------------- function fighting.switch_to_combat_state(entity,now,target) mobf_assert_backtrace(entity ~= nil) --precheck if target == nil then dbg_mobf.fighting_lvl2("MOBF: no target for combat state change specified") return end --set attack target entity.dynamic_data.combat.target = target local current_state = entity.dynamic_data.state.current mobf_assert_backtrace(current_state.state_mode ~= "combat") local combat_state = fighting.identify_combat_state(entity) if combat_state == nil then dbg_mobf.fighting_lvl2("MOBF: no special combat state") return end dbg_mobf.fighting_lvl2("MOBF: switching to combat state") --make sure state is locked mob_state.lock(entity,true) --backup dynamic movement data local backup = {} backup.movement = entity.dynamic_data.movement backup.p_movement = entity.dynamic_data.p_movement --create new movement data entity.dynamic_data.movement = {} entity.dynamic_data.p_movement = {} backup.current_state = entity.dynamic_data.state.current dbg_mobf.fighting_lvl2("MOBF: backing up state: " .. backup.current_state.name) --switch state mob_state.change_state(entity,combat_state) --save old movement data to use on switching back entity.dynamic_data.combat.movement_backup = backup --make sure a fighting mob ain't teleporting to target entity.dynamic_data.movement.teleportsupport = false --make sure we do follow our target entity.dynamic_data.movement.guardspawnpoint = false --set target entity.dynamic_data.current_movement_gen.set_target(entity,target) -- play start_attack sound if entity.data.sound ~= nil and entity.data.sound.start_attack ~= nil then sound.play(entity.object:getpos(),entity.data.sound.start_attack); end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] restore_previous_state(entity,now) -- --! @brief restore default movement generator of mob --! @memberof fighting --! @private -- --! @param entity mob to restore movement generator --! @param now current time in seconds ------------------------------------------------------------------------------- function fighting.restore_previous_state(entity,now) --check if ther is anything we can restore if entity.dynamic_data.combat.movement_backup ~= nil then local backup = entity.dynamic_data.combat.movement_backup mobf_assert_backtrace(backup.current_state ~= "combat") if backup.current_state ~= nil then dbg_mobf.fighting_lvl2("MOBF: restore state: " .. backup.current_state.name) mob_state.change_state(entity,backup.current_state) else minetest.log(LOGLEVEL_WARNING,"MOBF: unable to restore previous state switching to default") mob_state.change_state(entity,mob_state.get_state_by_name(entity,"default")) end backup.current_state = nil --restore old movement data entity.dynamic_data.movement = backup.movement entity.dynamic_data.p_movement = backup.p_movement --don't restore old movement target if not valid anymore if entity.dynamic_data.movement.target == nil or (not mobf_is_pos(entity.dynamic_data.movement.target) and entity.dynamic_data.movement.target:getpos() == nil) then entity.dynamic_data.movement.target = nil end --make sure all remaining data is deleted entity.dynamic_data.combat.movement_backup = nil end --make sure state is unlocked mob_state.lock(entity,false) end ------------------------------------------------------------------------------- -- @function [parent=#fighting] in_range(entity,now) -- --! @brief check if mob is within range of target --! @memberof fighting --! @private -- --! @param entity mob --! @param distance to target ------------------------------------------------------------------------------- function fighting.in_range(entity,distance) if (entity.data.combat.melee == nil or distance > entity.data.combat.melee.range) and (entity.data.combat.distance == nil or distance > entity.data.combat.distance.range) then if entity.data.combat.melee ~= nil or entity.data.combat.distance ~= nil then dbg_mobf.fighting_lvl2("MOBF: distance="..distance) if entity.data.combat.melee ~= nil then dbg_mobf.fighting_lvl2("MOBF: melee="..entity.data.combat.melee.range) end if entity.data.combat.distance ~= nil then dbg_mobf.fighting_lvl2("MOBF: distance="..entity.data.combat.distance.range) end end return false end return true end ------------------------------------------------------------------------------- -- @function [parent=#fighting] combat(entity,now) -- --! @brief periodic callback called to do mobs own combat related actions --! @memberof fighting -- --! @param entity mob to do action --! @param now current time --! @param dtime time fraction since last call -- --! @return continue callback execution or not ------------------------------------------------------------------------------- function fighting.combat(entity,now,dtime) --handle self destruct mobs if fighting.self_destruct_handler(entity,now) then return false end --fight against generic enemy "sun" if fighting.sun_damage_handler(entity,now) then return false end if entity.dynamic_data.combat ~= nil and entity.dynamic_data.combat.target ~= nil then --check if target is still valid if not entity.dynamic_data.combat.target:is_player() then local target_entity = entity.dynamic_data.combat.target:get_luaentity() local target_pos = entity.dynamic_data.combat.target:getpos() --print("MOBF: target is not player checking if stil valid: " -- .. dump(target_entity) .. " " .. dump(target_pos)) if target_entity == nil or target_entity.data == nil or target_pos == nil then -- switch back to default movement gen fighting.restore_previous_state(entity,now) --there is no player by that name, stop attack entity.dynamic_data.combat.target = nil dbg_mobf.fighting_lvl1("MOBF: not a valid target: " .. dump(entity.dynamic_data.combat.target)) return true end end --make mob run away if only 1/3 of health is left if entity.object:get_hp() < (entity.data.generic.base_health/3) then local old_target = fighting.get_target(entity) --restore state before attack fighting.restore_previous_state(entity,now) --stop attack entity.dynamic_data.combat.target = nil --make mob run away if old_target ~= nil then local dir = mobf_get_direction(old_target:getpos(),entity.object:getpos()) fighting.run_away(entity,dir,old_target) end return true end local targetname = fighting.get_target_name(entity.dynamic_data.combat.target) dbg_mobf.fighting_lvl1("MOBF: attacking player: " ..targetname) --calculate some basic data local mob_pos = entity.object:getpos() local targetpos = entity.dynamic_data.combat.target:getpos() local distance = mobf_calc_distance(mob_pos,targetpos) local dir = mobf_get_direction(targetpos,mob_pos) local target = entity.dynamic_data.combat.target --look towards target if entity.mode == "3d" then graphics.setyaw(entity, mobf_calc_yaw(dir.x,dir.z)+math.pi) else graphics.setyaw(entity, mobf_calc_yaw(dir.x,dir.z)-math.pi) end --initiate self destruct fighting.self_destruct_trigger(entity,distance,now) local range = entity.data.combat.melee.range * MOBF_AGRESSION_FACTOR if entity.data.combat.distance ~= nil and entity.data.combat.distance.range > range then range = entity.data.combat.distance.range end --find out if attacker is next to mob if distance > range then dbg_mobf.fighting_lvl2("MOBF: " .. entity.data.name .. " player >" .. targetname .. "< to far away " .. distance .. " > " .. range .. " stopping attack") --switch back to default movement gen fighting.restore_previous_state(entity,now) --there is no player by that name, stop attack entity.dynamic_data.combat.target = nil return true end --check if state needs to be switched local required_state = fighting.identify_combat_state(entity,distance) if required_state ~= nil and required_state.name ~= entity.dynamic_data.state.current then mob_state.change_state(entity,required_state) --reset current attack target as movement target after state switch entity.dynamic_data.current_movement_gen.set_target(entity,target) end --is mob near enough for any attack attack? if not fighting.in_range(entity,distance) then if entity.dynamic_data.combat.reset_path_counter > 1.5 then entity.dynamic_data.current_movement_gen.set_target(entity,target) entity.dynamic_data.combat.reset_path_counter = 0 end entity.dynamic_data.combat.reset_path_counter = entity.dynamic_data.combat.reset_path_counter + dtime return true end --check if melee attack can be done if fighting.melee_attack_handler(entity,now,distance) == false then --check if distance attack can be done if fighting.distance_attack_handler(entity, targetpos,mob_pos,now,distance) then -- mob did an attack so give chance to stop attack local rand_value = math.random() if entity.data.combat.angryness ~= nil and rand_value > entity.data.combat.angryness then dbg_mobf.fighting_lvl2("MOBF: rand=".. rand_value .. " angryness=" .. entity.data.combat.angryness) dbg_mobf.fighting_lvl2("MOBF: " .. entity.data.name .. " " .. now .. " random aborting attack at " ..targetname) -- switch back to default movement gen fighting.restore_previous_state(entity,now) entity.dynamic_data.combat.target = nil end end end end return true end ------------------------------------------------------------------------------- -- @function [parent=#fighting] get_target(entity) -- --! @brief find and possible target next to mob --! @memberof fighting --! @private -- --! @param entity mob to look around --! @return target ------------------------------------------------------------------------------- function fighting.get_target(entity) local possible_targets = {} if entity.data.combat.melee.range > 0 then local range = entity.data.combat.melee.range*MOBF_AGRESSION_FACTOR if entity.data.combat.distance ~= nil and entity.data.combat.distance.range > range then range = entity.data.combat.distance.range end local objectlist = minetest.get_objects_inside_radius( entity.object:getpos(),range) local count = 0 for i,v in ipairs(objectlist) do if v:is_player() then local playername = v:get_player_name() --don't attack spawner if entity.dynamic_data.spawning.spawner == nil or entity.dynamic_data.spawning.spawner ~= playername then count = count + 1 table.insert(possible_targets,v) dbg_mobf.fighting_lvl2("MOBF: " .. playername .. " is next to a mob of type ".. entity.data.name) else dbg_mobf.fighting_lvl2("MOBF: " .. entity.data.name .. " not attacking: " .. playername .. " is spawner") end else if entity.data.combat.attack_hostile_mobs then dbg_mobf.fighting_lvl2("MOBF: " .. entity.data.name .. " trying to attack hostile mobs too") local target_entity = v:get_luaentity() if target_entity ~= nil then local same_origin_protection = false if mobf_rtd.factions_available then same_origin_protection = not attention.is_enemy(entity,v) elseif target_entity.dynamic_data ~= nil and target_entity.dynamic_data.spawning ~= nil then same_origin_protection = target_entity.dynamic_data.spawning.spawner == entity.dynamic_data.spawning.spawner end if target_entity ~= entity and target_entity.data ~= nil and target_entity.data.combat ~= nil and target_entity.data.combat.starts_attack and not same_origin_protection then table.insert(possible_targets,v) dbg_mobf.fighting_lvl3(target_entity.data.name .. " is next to a mob of type " .. entity.data.name) end end end end end dbg_mobf.fighting_lvl2("MOBF: found ".. count .. " objects within" .. " attack range of " .. entity.data.name) end local targets_within_sight = {} for i,v in ipairs(possible_targets) do local entity_pos = entity.object:getpos() local target_pos = v:getpos() --is there a line of sight between mob and possible target --line of sight is calculated 1block above ground if mobf_line_of_sight({x=entity_pos.x,y=entity_pos.y+1,z=entity_pos.z}, {x=target_pos.x,y=target_pos.y+1,z=target_pos.z}) then table.insert(targets_within_sight,v) end end local nearest_target = nil local min_distance = -1 for i,v in ipairs(targets_within_sight) do local distance = mobf_calc_distance(entity.object:getpos(),v:getpos()) if min_distance < 0 or distance < min_distance then nearest_target = v min_distance = distance end end return nearest_target end ------------------------------------------------------------------------------- -- @function [parent=#fighting] self_destruct_trigger(entity,distance) -- --! @brief handle self destruct features --! @memberof fighting --! @private -- --! @param entity mob to do action --! @param distance current distance to target --! @param now current time --! @return true/false if handled or not ------------------------------------------------------------------------------- function fighting.self_destruct_trigger(entity,distance,now) if entity.data.combat ~= nil and entity.data.combat.self_destruct ~= nil then dbg_mobf.fighting_lvl1("MOBF: checking for self destruct trigger " .. distance .. " " .. entity.dynamic_data.combat.ts_self_destruct_triggered .. " " .. now) --trigger self destruct if distance <= entity.data.combat.self_destruct.range and entity.dynamic_data.combat.ts_self_destruct_triggered == -1 then dbg_mobf.fighting_lvl2("MOBF: self destruct triggered") entity.dynamic_data.combat.ts_self_destruct_triggered = now end end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] do_area_damage(pos,immune,damage_groups,range) -- --! @brief damage all objects within a certain range --! @memberof fighting --! @private -- --! @param pos cennter of damage area --! @param immune object immune to damage --! @param damage_groups list of damage groups to do damage to --! @param range range around pos ------------------------------------------------------------------------------- function fighting.do_area_damage(pos,immune,damage_groups,range) --damage objects within inner blast radius mobf_assert_backtrace(type(range) ~= "table") local objs = minetest.get_objects_inside_radius(pos, range) for k, obj in pairs(objs) do --don't do damage to issuer if obj ~= immune and obj ~= nil then --TODO as long as minetest still crashes without puncher use this workaround local worst_damage = 0 if type(damage_groups) == "table" then for k,v in pairs(damage_groups) do if v > worst_damage then worst_damage = v end end elseif type(damage_groups) == "number" then worst_damage = damage_groups else mobf_assert_backtrace("invalid damage_groups" == "selected") end local current_hp = obj:get_hp() obj:set_hp(current_hp - worst_damage) --punch --obj:punch(nil, 1.0, { -- full_punch_interval=1.0, -- damage_groups = damage_groups, --}, nil) end end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] do_node_damage(pos,immune_list,range,chance) -- --! @brief damage all objects within a certain range --! @memberof fighting --! @private -- --! @brief damage all nodes within a certain range -- --! @param pos center of area --! @param immune_list list of nodes immune to damage --! @param range range to do damage --! @param chance chance damage is done to a node ------------------------------------------------------------------------------- function fighting.do_node_damage(pos,immune_list,range,chance) --do node damage for i=pos.x-range, pos.x+range, 1 do for j=pos.y-range, pos.y+range, 1 do for k=pos.z-range,pos.z+range,1 do --TODO create a little bit more sophisticated blast resistance if math.random() < chance then local toremove = minetest.get_node({x=i,y=j,z=k}) if toremove ~= nil then local immune = false if immune_list ~= nil then for i,v in ipairs(immune_list) do if (toremove.name == v) then immune = true end end end if immune ~= true then minetest.remove_node({x=i,y=j,z=k}) end end end end end end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] self_destruct_handler(entity) -- --! @brief handle self destruct features --! @memberof fighting --! @private -- --! @param entity mob to do action --! @param now current time --! @return true/false if handled or not ------------------------------------------------------------------------------- function fighting.self_destruct_handler(entity,now) --self destructing mob? if entity.data.combat ~= nil and entity.data.combat.self_destruct ~= nil then local pos = entity.object:getpos() dbg_mobf.fighting_lvl1("MOBF: checking for self destruct imminent") --do self destruct if entity.dynamic_data.combat.ts_self_destruct_triggered > 0 and entity.dynamic_data.combat.ts_self_destruct_triggered + entity.data.combat.self_destruct.delay <= now then dbg_mobf.fighting_lvl2("MOBF: executing self destruct") if entity.data.sound ~= nil then sound.play(pos,entity.data.sound.self_destruct); end fighting.do_area_damage(pos,nil, entity.data.combat.self_destruct.damage, entity.data.combat.self_destruct.range) --TODO determine block removal by damage and remove blocks fighting.do_node_damage(pos,{}, entity.data.combat.self_destruct.node_damage_range, 1 - 1/entity.data.combat.self_destruct.node_damage_range) if mobf_rtd.fire_enabled then --Add fire for i=pos.x-entity.data.combat.self_destruct.range/2, pos.x+entity.data.combat.self_destruct.range/2, 1 do for j=pos.y-entity.data.combat.self_destruct.range/2, pos.y+entity.data.combat.self_destruct.range/2, 1 do for k=pos.z-entity.data.combat.self_destruct.range/2, pos.z+entity.data.combat.self_destruct.range/2, 1 do local current = minetest.get_node({x=i,y=j,z=k}) if (current.name == "air") then minetest.set_node({x=i,y=j,z=k}, {name="fire:basic_flame"}) end end end end else minetest.log(LOGLEVEL_NOTICE, "MOBF: self destruct without fire isn't really impressive!") end mobf_lifebar.del(entity.lifebar) spawning.remove(entity, "self destruct") return true end end return false end ------------------------------------------------------------------------------- -- @function [parent=#fighting] melee_attack_handler(entity,now) -- --! @brief handle melee attack --! @memberof fighting --! @private -- --! @param entity mob to do action --! @param now current time --! @param distance distance to player --! @return true/false if handled or not ------------------------------------------------------------------------------- function fighting.melee_attack_handler(entity,now,distance) if entity.data.combat.melee == nil then dbg_mobf.fighting_lvl2("MOBF: no meele attack specified") return false end local time_of_next_attack_chance = entity.dynamic_data.combat.ts_last_attack + entity.data.combat.melee.speed --check if mob is ready to attack if now < time_of_next_attack_chance then dbg_mobf.fighting_lvl1("MOBF: to early for meele attack " .. now .. " >= " .. time_of_next_attack_chance) return false end mobf_assert_backtrace( entity.dynamic_data.combat.target ~= nil) local ownpos = entity.object:getpos() local target_obj = entity.dynamic_data.combat.target.object if target_obj == nil then target_obj = entity.dynamic_data.combat.target end if distance <= entity.data.combat.melee.range and mobf_line_of_sight(ownpos,target_obj:getpos()) then --save time of attack entity.dynamic_data.combat.ts_last_attack = now if entity.data.sound ~= nil then sound.play(entity.object:getpos(),entity.data.sound.melee); end --calculate damage to be done local damage_done = math.floor(math.random(0,entity.data.combat.melee.maxdamage)) + 1 --TODO call punch instead of manually setting health for player too if target_obj:is_player() then local target_health = target_obj:get_hp() --do damage target_obj:set_hp(target_health -damage_done) else local damage_groups = nil if entity.data.combat.melee.weapon_groupcaps ~= nil then damage_groups = entity.data.combat.melee.weapon_damage_groups end if damage_groups == nil and entity.data.combat.melee.weapongroups ~= nil then damage_groups = {} for i=1, #entity.data.combat.melee.weapongroups, 1 do damage_groups[entity.data.combat.melee.weapon_damage_groups[i]] = damage_done end end if damage_groups == nil then damage_groups= { fleshy=damage_done } end target_obj:punch(entity.object, 1.0, { full_punch_interval=1.0, damage_groups = damage_groups, }, nil) end dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. " doing melee attack damage=" .. damage_done) return true end dbg_mobf.fighting_lvl1("MOBF: not within meele range " .. distance .. " > " .. entity.data.combat.melee.range) return false end ------------------------------------------------------------------------------- -- @function [parent=#fighting] distance_attack_handler(entity,now) -- --! @brief handle distance attack --! @memberof fighting --! @private -- --! @param entity mob to do action --! @param targetpos position of target --! @param mob_pos position of mob --! @param now current time --! @param distance distance between target and player --! @return true/false if handled or not ------------------------------------------------------------------------------- function fighting.distance_attack_handler(entity,targetpos,mob_pos,now,distance) if entity.data.combat.distance == nil then dbg_mobf.fighting_lvl2("MOBF: no distance attack specified") return false end local time_of_next_attack_chance = entity.dynamic_data.combat.ts_last_attack + entity.data.combat.distance.speed --check if mob is ready to attack if now < time_of_next_attack_chance then dbg_mobf.fighting_lvl1("MOBF: to early for distance attack " .. now .. " >= " .. time_of_next_attack_chance) return false end if distance <= entity.data.combat.distance.range and (entity.data.combat.distance.min_range == nil or distance > entity.data.combat.distance.min_range) then dbg_mobf.fighting_lvl2("MOBF: ".. entity.data.name .. " doing distance attack") --save time of attack entity.dynamic_data.combat.ts_last_attack = now local dir = mobf_get_direction({ x=mob_pos.x, y=mob_pos.y+1, z=mob_pos.z }, targetpos) if entity.data.sound ~= nil then sound.play(mob_pos,entity.data.sound.distance); end local newobject=minetest.add_entity({ x=mob_pos.x+dir.x, y=mob_pos.y+dir.y+1, z=mob_pos.z+dir.z }, entity.data.combat.distance.attack ) local thrown_entity = mobf_find_entity(newobject) if thrown_entity ~= nil then local vel_thrown = { x=dir.x*thrown_entity.velocity + math.random(0,0.05), y=dir.y*thrown_entity.velocity, z=dir.z*thrown_entity.velocity + math.random(0,0.05), } if entity.data.combat.distance.balistic == true then --this isn't an exact calculation but just something to make --it not too perfect local height_diff = targetpos.y - mob_pos.y local current_scalar_speed = mobf_calc_scalar_speed(vel_thrown.x,vel_thrown.z) local time_to_target = (distance/current_scalar_speed) local y_vel = mobf_balistic_start_speed( height_diff -1, time_to_target, -thrown_entity.gravity) vel_thrown.y = y_vel + math.random(0,0.25) end dbg_mobf.fighting_lvl2("MOBF: throwing with velocity: " .. printpos(vel_thrown)) newobject:setvelocity(vel_thrown) newobject:setacceleration({x=0, y=-thrown_entity.gravity, z=0}) thrown_entity.owner = entity.object if entity.data.sound ~= nil then sound.play(mob_pos,entity.data.sound.shoot_distance); end dbg_mobf.fighting_lvl2("MOBF: distance attack issued") else minetest.log(LOGLEVEL_ERROR, "MOBF: unable to find entity for distance attack") end return true end dbg_mobf.fighting_lvl1("MOBF: not within distance range " .. distance .. " > " .. entity.data.combat.distance.range) return false end ------------------------------------------------------------------------------- -- @function [parent=#fighting] sun_damage_handler(entity,now) -- --! @brief handle damage done by sun --! @memberof fighting --! @private -- --! @param entity mob to do action --! @param now current time -- --! @return true/false if killed or not ------------------------------------------------------------------------------- function fighting.sun_damage_handler(entity,now) if entity.data.combat ~= nil and entity.data.combat.sun_sensitive then mobf_assert_backtrace(entity.dynamic_data.combat ~= nil) local pos = entity.object:getpos() local current_state = entity.dynamic_data.state.current local current_light = minetest.get_node_light(pos) if current_light == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: Bug!!! didn't get a light value for " .. printpos(pos)) return false end --check if mob is in sunlight if ( current_light > LIGHT_MAX) then dbg_mobf.fighting_lvl1("MOBF: " .. entity.data.name .. " health at start:" .. entity.object:get_hp()) if current_state.animation ~= nil and entity.data.animation ~= nil and entity.data.animation[current_state.animation .. "__burning"] ~= nil then graphics.set_animation(entity,current_state.animation .. "burning") else graphics.set_animation(entity,"burning") end if entity.dynamic_data.combat.ts_last_sun_damage +1 < now then local damage = (1 + math.floor(entity.data.generic.base_health/15)) dbg_mobf.fighting_lvl1("Mob ".. entity.data.name .. " takes " ..damage .." damage because of sun") entity.object:set_hp(entity.object:get_hp() - damage) mobf_lifebar.set(entity.lifebar,entity.object:get_hp()/entity.hp_max) if entity.data.sound ~= nil then sound.play(pos,entity.data.sound.sun_damage); end if entity.object:get_hp() <= 0 then --if entity.dynamic_data.generic.health <= 0 then dbg_mobf.fighting_lvl2("Mob ".. entity.data.name .. " died of sun") mobf_lifebar.del(entity.lifebar) spawning.remove(entity,"died by sun") return true end entity.dynamic_data.combat.ts_last_sun_damage = now end else --use last sun damage to avoid setting animation over and over --even if nothing changed if entity.dynamic_data.combat.ts_last_sun_damage ~= -1 and current_state.animation ~= nil then graphics.set_animation(entity,current_state.animation) entity.dynamic_data.combat.ts_last_sun_damage = -1 end end end return false end ------------------------------------------------------------------------------- -- @function [parent=#fighting] get_target_name(target) -- --! @brief get name of target --! @memberof fighting --! @private -- --! @param target to get name for -- --! @return name of target ------------------------------------------------------------------------------- function fighting.get_target_name(target) if target == nil then return "invalid" end if target:is_player() then return target:get_player_name() else local target_entity = target:get_luaentity() if target_entity ~= nil and target_entity.data ~= nil and target_entity.data.name ~= nil then return "MOB: " .. target_entity.data.name end end return "unknown" end ------------------------------------------------------------------------------- -- @function [parent=#fighting] set_target(entity,target) -- --! @brief decide if only switching target or state --! @memberof fighting --! @public -- --! @param entity entity to update --! @param target to set ------------------------------------------------------------------------------- function fighting.set_target(entity,target) mobf_assert_backtrace(entity.dynamic_data ~= nil) if not fighting.is_valid_target(target) then return end if entity.dynamic_data.combat.target ~= nil then dbg_mobf.fighting_lvl2("MOBF: switching attack target") local target_distance = nil if entity.data.combat.melee ~= nil and entity.data.combat.melee.range ~= nil then target_distance = 0.75 * entity.data.combat.melee.range end --set movement target entity.dynamic_data.current_movement_gen.set_target(entity, target, true, target_distance) --set attack target entity.dynamic_data.combat.target = target else if entity.dynamic_data.combat.target ~= target then local attackername = fighting.get_target_name(target) dbg_mobf.fighting_lvl2("MOBF: initial attack at: ".. attackername) if entity.dynamic_data.combat.target == nil then fighting.switch_to_combat_state(entity,mobf_get_current_time(),target) end end end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] is_valid_target(target) -- --! @brief check if a target is a valid target --! @memberof fighting --! @public -- --! @param target to set ------------------------------------------------------------------------------- function fighting.is_valid_target(target) --remove target case if target == nil then return true end --valid if it's a player if target:is_player() then return true end --valid if it's a lua entity if target:get_luaentity() ~= nil then return true end --invalid any other case return false end ------------------------------------------------------------------------------- -- @function [parent=#fighting] init_dynamic_data() -- --! @brief initialize all dynamic data on activate --! @memberof fighting -- --! @param entity mob to do action --! @param now current time ------------------------------------------------------------------------------- function fighting.init_dynamic_data(entity,now) local data = { ts_last_sun_damage = now, ts_last_attack = now, ts_last_aggression_chance = now, ts_self_destruct_triggered = -1, target = nil, reset_path_counter = 0, } entity.dynamic_data.combat = data end ------------------------------------------------------------------------------- -- @function [parent=#fighting] heal() -- --! @brief heal a mob --! @memberof fighting -- --! @param entity mob to do action --! @param player the one doing the rightclick ------------------------------------------------------------------------------- function fighting.heal(entity,player) local health = entity.object:get_hp() if entity.data.generic.base_health == entity.object:get_hp() then return end if not player:is_player() then return end local tool = player:get_wielded_item() if tool == nil then return end tool = tool:get_name() if not fighting.healdb or not fighting.healdb[tool] then dbg_mobf.fighting_lvl1("MOBF: unknown heal item: " .. tool) return end local new_health = 0 print("healdb value: " .. dump(fighting.healdb[tool].value)) if fighting.healdb[tool].value >= 0 then new_health = MIN(entity.object:get_hp() + fighting.healdb[tool].value, entity.data.generic.base_health) else new_health = MAX(entity.object:get_hp() + fighting.healdb[tool].value, 0) end entity.object:set_hp(new_health) if new_health <= 0.5 then fighting.dodamage(entity, player, "poisoned") else mobf_lifebar.set(entity.lifebar,new_health/entity.hp_max) end player:get_inventory():remove_item("main",tool.." 1") if fighting.healdb[tool].replacement ~= nil then player:get_inventory():add_item("main", fighting.healdb[tool].replacement.." 1") end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] heal_caption() -- --! @brief get caption for heal button --! @memberof fighting -- --! @param entity mob to do action ------------------------------------------------------------------------------- function fighting.heal_caption(entity) if entity.data.generic.base_health ~= entity.object:get_hp() then return "heal" else return "nothing to heal" end end ------------------------------------------------------------------------------- -- @function [parent=#fighting] update_healdb() -- --! @brief update mobfs heal db --! @memberof fighting -- --! @param hp_change --! @param replace_with_item --! @param itemstack -- unused --! @param player player issuing the update --! @param pointed_thing -- unused ------------------------------------------------------------------------------- function fighting.update_healdb(hp_change, replace_with_item, itemstack, player, pointed_thing) if not player:is_player() then return end local tool = player:get_wielded_item() if tool == nil then return end tool = tool:get_name() local replacement = nil if replace_with_item ~= nil then if type(replace_with_item) ~= "string" then replacement = replace_with_item:get_name() end end if fighting.healdb == nil then fighting.healdb = {} end if fighting.healdb[tool] ~= nil and fighting.healdb[tool].value == hp_change and fighting.healdb[tool].replacement == replacement or hp_change == nil then return false end fighting.healdb[tool] = { value = hp_change, replacement = replacement } minetest.world_setting_set("fighting.healdb",fighting.healdb) return false end minetest.register_on_item_eat(fighting.update_healdb) mobf_core-2.5.1/mobf/graphics.lua000066400000000000000000000265071264104133000167040ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file graphics.lua --! @brief graphics related parts of mob --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("graphics")) --! @class graphics --! @brief graphic features graphics = {} ------------------------------------------------------------------------------- -- @function [parent=#graphics] init_dynamic_data(entity,velocity) -- --! @brief initialize values required by graphics --! @memberof graphics -- --! @param entity mob initialize --! @param now current time ------------------------------------------------------------------------------- function graphics.init_dynamic_data(entity,now) local data = { last_scalar_speed = nil, } entity.dynamic_data.graphics = data end ------------------------------------------------------------------------------- -- @function [parent=#graphics] update(entity,now,dtime) -- --! @brief callback for updating graphics of mob --! @memberof graphics -- --! @param entity mob to calculate direction --! @param now current time --! @param dtime current dtime ------------------------------------------------------------------------------- function graphics.update(entity,now,dtime) --update animation speed --replaced by core function (if ever merged) --graphics.update_animation(entity,now,dtime) --update attention if entity.dynamic_data ~= nil and entity.dynamic_data.attention ~= nil and entity.data.attention ~= nil and entity.dynamic_data.attention.most_relevant_target ~= nil and entity.data.attention.watch_threshold ~= nil and (entity.dynamic_data.attention.current_value > entity.data.attention.watch_threshold) then dbg_mobf.graphics_lvl3("MOBF: attention mode orientation update") local direction = mobf_get_direction(entity.object:getpos(), entity.dynamic_data.attention.most_relevant_target:getpos()) if entity.mode == "3d" then graphics.setyaw(entity, mobf_calc_yaw(direction.x,direction.z)) else graphics.setyaw(entity, mobf_calc_yaw(direction.x,direction.z)+math.pi/2) end end end ------------------------------------------------------------------------------- -- @function [parent=#graphics] update_animation(entity,now,dtime) -- --! @brief callback for updating graphics of mob --! @memberof graphics -- --! @param entity mob to calculate direction --! @param now current time --! @param dtime current dtime ------------------------------------------------------------------------------- function graphics.update_animation(entity,now,dtime) if entity.dynamic_data.animation ~= nil then local animdata = entity.data.animation[entity.dynamic_data.animation] if animdata ~= nil and animdata.basevelocity ~= nil then local current_velocity = entity.object:getvelocity() local scalar_velocity = mobf_calc_scalar_speed(current_velocity.x,current_velocity.z) if entity.dynamic_data.graphics.last_scalar_speed ~= nil then local speeddiff = DELTA(scalar_velocity, entity.dynamic_data.graphics.last_scalar_speed) if speeddiff > 0.05 then local current_fps = scalar_velocity/animdata.basevelocity * 15 entity.object:set_animation_speed(current_fps) entity.dynamic_data.graphics.last_scalar_speed = scalar_velocity entity.dynamic_data.graphics.last_fps = current_fps end else entity.dynamic_data.graphics.last_scalar_speed = scalar_velocity end end end end ------------------------------------------------------------------------------- -- @function [parent=#graphics] set_animation(entity,name) -- --! @brief set the drawmode for an mob entity --! @memberof graphics -- --! @param entity mob to set drawmode for --! @param name name of animation ------------------------------------------------------------------------------- function graphics.set_animation(entity,name) if name == nil then dbg_mobf.graphics_lvl2("MOBF: calling updating animation without name for " .. entity.data.name) return end if entity.mode == "2d" then if id == "stand" then entity.object:setsprite({x=0,y=0}, 1, 0, true) end if name == "burning" then entity.object:setsprite({x=0,y=1}, 1, 0, true) return end --fallback entity.object:setsprite({x=0,y=0}, 1, 0, true) return end if entity.mode == "3d" then --TODO change frame rate due to movement speed dbg_mobf.graphics_lvl2("MOBF: " .. entity.data.name .. " updating animation: " .. name) if entity.data.animation ~= nil and name ~= nil and entity.data.animation[name] ~= nil and entity.dynamic_data.animation ~= name then dbg_mobf.graphics_lvl2("MOBF:\tSetting animation to " .. name .. " start: " .. entity.data.animation[name].start_frame .. " end: " .. entity.data.animation[name].end_frame) entity.object:set_animation({ x=entity.data.animation[name].start_frame, y=entity.data.animation[name].end_frame }, entity.data.animation[name].anim_speed, nil, entity.data.animation[name].basevelocity) entity.dynamic_data.animation = name end return end mobf_bug_warning(LOGLEVEL_WARNING,"MOBF BUG!!: invalid graphics mode specified " .. dump(entity.mode)) end ------------------------------------------------------------------------------ -- @function [parent=#graphics] graphics_by_statename(graphics2d,graphics3d) -- --! @brief get graphics information --! @memberof graphics -- --! @param mob static data --! @param statename name of state -- --! @return grahphic information ------------------------------------------------------------------------------- function graphics.graphics_by_statename(mob,statename) local dummyentity = { data = mob } local default_state = mob_state.get_state_by_name(dummyentity,"default") local selected_state = nil if statename == "default" then selected_state = default_state else selected_state = mob_state.get_state_by_name(dummyentity,statename) end if selected_state == nil then selected_state = default_state end if selected_state.graphics_3d == nil then selected_state.graphics_3d = default_state.graphics_3d end if selected_state.graphics == nil then selected_state.graphics = default_state.graphics end local setgraphics = {} if (selected_state.graphics_3d == nil) or minetest.world_setting_get("mobf_disable_3d_mode") then if (selected_state.graphics == nil) then --no idea if there is any legitimate reason for this mobf_print("state: " .. dump(selected_state)) mobf_print("there ain't even 2d graphics available") return nil end local basename = modname .. name if statename ~= nil and statename ~= "default" then basename = basename .. "__" .. statename end setgraphics.collisionbox = {-0.5, -0.5 * selected_state.graphics.visible_height, -0.5, 0.5, 0.5 * selected_state.graphics.visible_height, 0.5} if selected_state.graphics.visual ~= nil then selected_state.graphics.visual = selected_state.graphics.visual else selected_state.graphics.visual = "sprite" end setgraphics.textures = { basename..".png^[makealpha:128,0,0^[makealpha:128,128,0" } setgraphics.visual_size = selected_state.graphics.sprite_scale setgraphics.spritediv = selected_state.graphics.sprite_div setgraphics.mode = "2d" else if selected_state.graphics_3d.visual == "mesh" then setgraphics.mesh = selected_state.graphics_3d.mesh end setgraphics.collisionbox = selected_state.graphics_3d.collisionbox setgraphics.visual = selected_state.graphics_3d.visual setgraphics.visual_size = selected_state.graphics_3d.visual_size setgraphics.textures = selected_state.graphics_3d.textures setgraphics.texturelist = selected_state.graphics_3d.texturelist setgraphics.mode = "3d" setgraphics.model_orientation_fix = selected_state.graphics_3d.model_orientation_fix end return setgraphics end ------------------------------------------------------------------------------ -- @function [parent=#graphics] prepare_graphic_info(graphics2d,graphics3d) -- --! @brief get graphics information --! @memberof graphics -- --! @param graphics2d --! @param graphics3d --! @param modname --! @param name --! @param statename --! @return grahpic information ------------------------------------------------------------------------------- function graphics.prepare_info(graphics2d,graphics3d,modname,name,statename) local setgraphics = {} if (graphics3d == nil) or minetest.world_setting_get("mobf_disable_3d_mode") then if (graphics2d == nil) then --this maybe correct if there's a state model requested! return nil end local basename = modname .. name if statename ~= nil and statename ~= "default" then basename = basename .. "__" .. statename end setgraphics.collisionbox = {-0.5, -0.5 * graphics2d.visible_height, -0.5, 0.5, 0.5 * graphics2d.visible_height, 0.5} if graphics2d.visual ~= nil then setgraphics.visual = graphics2d.visual else setgraphics.visual = "sprite" end setgraphics.textures = { basename..".png^[makealpha:128,0,0^[makealpha:128,128,0" } setgraphics.visual_size = graphics2d.sprite_scale setgraphics.spritediv = graphics2d.sprite_div setgraphics.mode = "2d" else if graphics3d.visual == "mesh" then setgraphics.mesh = graphics3d.mesh end setgraphics.collisionbox = graphics3d.collisionbox --todo is this required for mesh? setgraphics.visual = graphics3d.visual setgraphics.visual_size = graphics3d.visual_size setgraphics.textures = graphics3d.textures setgraphics.mode = "3d" end return setgraphics end ------------------------------------------------------------------------------ -- @function [parent=#graphics] setyaw(entity,value) -- --! @brief update yaw for a specific entity (overlay to workaround model bugs) --! @memberof graphics -- --! @param entity entity to set yaw for --! @param value yaw value to set ------------------------------------------------------------------------------- function graphics.setyaw(entity, value) local current_graphics = graphics.graphics_by_statename(entity.data, entity.dynamic_data.state.current.name) if current_graphics.model_orientation_fix ~= nil then value = value + current_graphics.model_orientation_fix end entity.object:setyaw(value) end ------------------------------------------------------------------------------ -- @function [parent=#graphics] getyaw(entity,value) -- --! @brief read yaw for this object, fix model offset --! @memberof graphics -- --! @param entity entity to set yaw for --! @param value yaw value to set ------------------------------------------------------------------------------- function graphics.getyaw(entity) local retval = 0 local current_graphics = graphics.graphics_by_statename(entity.data, entity.dynamic_data.state.current.name) if current_graphics.model_orientation_fix ~= nil then retval = retval - current_graphics.model_orientation_fix end retval = retval + entity.object:getyaw() return retval endmobf_core-2.5.1/mobf/harvesting.lua000066400000000000000000000200731264104133000172460ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file harvesting.lua --! @brief component for all harvesting related mob features --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup harvesting Harvesting subcomponent --! @brief Component handling harvesting --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("harvesting")) --! @class harvesting --! @brief harvesting features harvesting = {} --!@} ------------------------------------------------------------------------------- -- @function [parent=#harvesting] init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by harvesting --! @memberof harvesting -- --! @param entity mob to initialize harvest dynamic data --! @param now current time ------------------------------------------------------------------------------- function harvesting.init_dynamic_data(entity,now) dbg_mobf.harvesting_lvl1("MOBF: " .. entity.data.name .. " initializing harvesting dynamic data") local data = { ts_last = now, } entity.dynamic_data.harvesting = data end ------------------------------------------------------------------------------- -- @function [parent=#harvesting] callback(entity,player,now) -- --! @brief callback handler for harvest by player --! @memberof harvesting -- --! @param entity mob being harvested --! @param player player harvesting --! @param now the current time --! @return true/false if handled by harvesting or not ------------------------------------------------------------------------------- function harvesting.callback(entity,player,now) dbg_mobf.harvesting_lvl1("MOBF: harvest function called") local now = mobf_get_current_time() --handle catching of mob if entity.data.catching ~= nil and entity.data.catching.tool ~= "" then if (entity.dynamic_data.spawning.player_spawned and entity.dynamic_data.spawning.spawner == nil) then dbg_mobf.harvesting_lvl1("MOBF: mob flagged as player spanwned but no spawner set!") entity.dynamic_data.spawning.player_spawned = false end --grief protection if minetest.world_setting_get("mobf_grief_protection") and entity.dynamic_data.spawning.player_spawned and entity.dynamic_data.spawning.spawner ~= player:get_player_name() then dbg_mobf.harvesting_lvl1("MOBF: anti gief triggered catching aborted") return true end -- what's wielded by player local tool = player:get_wielded_item() if tool:get_name() == entity.data.catching.tool then dbg_mobf.harvesting_lvl1("MOBF: player wearing ".. entity.data.catching.tool) if type(entity.data.catching.can_be_cought) == "function" then if (not entity.data.catching.can_be_cought(entity)) then dbg_mobf.harvesting_lvl1("MOBF: entity denied catching") return true end end --check if player has enough room local inventory_add_result = nil if entity.data.generic.addoncatch ~= nil then inventory_add_result = player:get_inventory():add_item("main", entity.data.generic.addoncatch.." 1") dbg_mobf.harvesting_lvl2( "MOBF: adding specified oncatch item: " .. entity.data.generic.addoncatch) else inventory_add_result = player:get_inventory():add_item("main", entity.data.modname ..":"..entity.data.name.." 1") dbg_mobf.harvesting_lvl2( "MOBF: adding automatic oncatch item: " .. entity.data.modname ..":"..entity.data.name) end if not inventory_add_result:is_empty() then minetest.chat_send_player(player:get_player_name(), "You don't have any room left in inventory!") return true end --play catch sound if entity.data.sound ~= nil then sound.play(entity.object:getpos(),entity.data.sound.catch); end if entity.data.catching.consumed == true then if player:get_inventory():contains_item("main",entity.data.catching.tool.." 1") then dbg_mobf.harvesting_lvl2("MOBF: removing: " .. entity.data.catching.tool.." 1") player:get_inventory():remove_item("main", entity.data.catching.tool.." 1") else mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: BUG!!! player is" .. " wearing a item he doesn't have in inventory!!!") end end spawning.remove(entity, "cought") return true end end --handle harvestable mobs, check if player is wearing correct tool if entity.data.harvest ~= nil then dbg_mobf.harvesting_lvl1("MOBF: trying to harvest harvestable mob") if (entity.data.harvest.tool ~= "") then local tool = player:get_wielded_item() if tool ~= nil then dbg_mobf.harvesting_lvl1("MOBF: Player is wearing >" .. tool:get_name() .. "< required is >".. entity.data.harvest.tool .. "< wear: " .. tool:get_wear()) if (tool:get_name() ~= entity.data.harvest.tool) then --player is wearing wrong tool do an attack return false else --tool is completely consumed if entity.data.harvest.tool_consumed == true then if player:get_inventory():contains_item("main",entity.data.harvest.tool.." 1") == false then dbg_mobf.harvesting_lvl1("MOBF: Player doesn't have" .. " at least 1 of ".. entity.data.harvest.tool) --handled but not ok so don't attack return true end else --damage tool local tool_wear = tool:get_wear() dbg_mobf.harvesting_lvl1("MOBF: tool " .. tool:get_name() .. " wear: " .. tool_wear) -- damage used tool if tool_wear ~= nil and entity.data.harvest.max_tool_usage ~= nil then local todamage = (65535/entity.data.harvest.max_tool_usage) dbg_mobf.harvesting_lvl1("MOBF: tool damage calculated: " .. todamage); if tool:add_wear(todamage) ~= true then dbg_mobf.harvesting_lvl3("MOBF: Tried to damage non tool item " .. tool:get_name() .. "!"); end player:set_wielded_item(tool) end end end else --player isn't wearing a tool so this has to be an attack return false end else --no havest tool defined so this has to be an attack return false end --transformation and harvest delay is exclusive --harvest delay mode if entity.data.harvest.min_delay < 0 or entity.dynamic_data.harvesting.ts_last + entity.data.harvest.min_delay < now then --TODO check if player has enough room player:get_inventory():add_item("main", entity.data.harvest.result.." 1") --check if tool is consumed by action if entity.data.harvest.tool_consumed then dbg_mobf.harvesting_lvl2("MOBF: removing " ..entity.data.harvest.tool.." 1") player:get_inventory():remove_item("main",entity.data.harvest.tool.." 1") end else dbg_mobf.harvesting_lvl1("MOBF: " .. entity.data.name .. " not ready to be harvested") end -- check if mob is transformed by harvest if entity.data.harvest.transforms_to ~= nil and entity.data.harvest.transforms_to ~= "" then local transformed = spawning.replace_entity(entity, entity.data.harvest.transforms_to) else entity.dynamic_data.harvesting.ts_last = mobf_get_current_time() end --play harvest sound if entity.data.sound ~= nil then sound.play(entity.object:getpos(),entity.data.sound.harvest); end --harvest done return true end return false end ------------------------------------------------------------------------------- -- @function transform(entity) -- --! @brief self transform callback for mob --! @ingroup harvesting -- --! @param entity mob calling --! @param now current time ------------------------------------------------------------------------------- function transform(entity,now) --check if it's a transformable mob if entity.data.auto_transform ~= nil then if now - entity.dynamic_data.spawning.original_spawntime > entity.data.auto_transform.delay then spawning.replace_entity(entity,entity.data.auto_transform.result) return false end end end mobf_core-2.5.1/mobf/init.lua000066400000000000000000000255431264104133000160460ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file init.lua --! @brief main module file responsible for including all parts of mob framework mod --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup framework_int Internal framework subcomponent API --! @brief this functions are used to provide additional features to mob framework --! e.g. add additional spawn algorithms, movement generators, environments ... -- -- --! @defgroup framework_mob Mob Framework API --! @brief this functions are used to add a mob to mob framework -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- core.log("action","MOD: mobf loading ...") --! @brief runtime data required to be setup once on start mobf_rtd = { --!is mob running with fire support fire_enabled = false, --!do we have luatrace luatrace_enabled = false, --!do we have inventory plus support have_adv_spawning = false, --!do we have factions support factions_available = false, --!registry for movement patterns movement_patterns = {}, --!registry of mobs registred_mob = {}, --!registred mobs_data registred_mob_data = {}, --!timesource timesource = "os.clock() (10ms ONLY!)", --!total spawned mobs total_spawned = 0, --!detailed debug state detailed_state = false, } --!path of mod mobf_modpath = minetest.get_modpath("mobf") LOGLEVEL_INFO = "verbose" LOGLEVEL_NOTICE = "info" LOGLEVEL_WARNING = "action" LOGLEVEL_ERROR = "error" LOGLEVEL_CRITICAL = "error" -- initialize luatrace if necessary if mobf_rtd.luatrace_enabled then luatrace = require("luatrace") end -- minetest workarounds if not type(core.global_exists) ~= "function" then core.global_exists = function(name) return core[name] ~= nil end end --include debug trace functions dofile (mobf_modpath .. "/utils/text.lua") dofile (mobf_modpath .. "/debug_trace.lua") --include engine dofile (mobf_modpath .. "/utils/error_handling.lua") dofile (mobf_modpath .. "/utils/settings.lua") dofile (mobf_modpath .. "/utils/generic_functions.lua") dofile (mobf_modpath .. "/utils/data_storage.lua") dofile (mobf_modpath .. "/utils/tracing.lua") dofile (mobf_modpath .. "/utils/geometry.lua") dofile (mobf_modpath .. "/utils/permanent_data.lua") dofile (mobf_modpath .. "/lifebar.lua") dofile (mobf_modpath .. "/env_constants.lua") dofile (mobf_modpath .. "/environment.lua") dofile (mobf_modpath .. "/attention.lua") dofile (mobf_modpath .. "/movement_generic.lua") dofile (mobf_modpath .. "/graphics.lua") dofile (mobf_modpath .. "/movement_gen_registry.lua") dofile (mobf_modpath .. "/harvesting.lua") dofile (mobf_modpath .. "/fighting.lua") dofile (mobf_modpath .. "/random_drop.lua") dofile (mobf_modpath .. "/sound.lua") dofile (mobf_modpath .. "/ride.lua") dofile (mobf_modpath .. "/mobf.lua") dofile (mobf_modpath .. "/api.lua") dofile (mobf_modpath .. "/debug.lua") dofile (mobf_modpath .. "/mob_state.lua") dofile (mobf_modpath .. "/inventory.lua") dofile (mobf_modpath .. "/path.lua") dofile (mobf_modpath .. "/factions.lua") dofile (mobf_modpath .. "/step_quota.lua") --include spawning support dofile (mobf_modpath .. "/spawning.lua") --include movement generators dofile (mobf_modpath .. "/mgen_probab/main_probab.lua") dofile (mobf_modpath .. "/mgen_follow/main_follow.lua") dofile (mobf_modpath .. "/mgen_rasterized/mgen_raster.lua") dofile (mobf_modpath .. "/mgen_jordan4ibanez/mgen_jordan4ibanez.lua") dofile (mobf_modpath .. "/mgen_pathbased/main.lua") dofile (mobf_modpath .. "/mgen_flee/main_flee.lua") dofile (mobf_modpath .. "/mov_gen_none.lua") mobf_version = "2.5.1" --! @brief main initialization function function mobf_init_framework() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing mob framework") minetest.log(LOGLEVEL_NOTICE,"MOBF: Reading mob blacklist") local mobf_mob_blacklist_string = minetest.world_setting_get("mobf_blacklist") if mobf_mob_blacklist_string ~= nil then mobf_rtd.registred_mob = minetest.deserialize(mobf_mob_blacklist_string) if mobf_rtd.registred_mob == nil then minetest.log(LOGLEVEL_ERROR,"MOBF: Error on serializing blacklist!") mobf_rtd.registred_mob = {} end end minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize timesource...") mobf_init_timesource() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize statistics...") mobf_init_statistics() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize factions support...") mobf_factions.init() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize external mod dependencys...") mobf_init_mod_deps() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing probabilistic movement generator") movement_gen.initialize() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing debug hooks..") mobf_debug.init() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing mob preservation..") local preserved_mobs_raw = mobf_get_world_setting("mobf_preserve_mobs") if preserved_mobs_raw ~= nil then mobf.current_preserve_list = minetest.deserialize(preserved_mobs_raw) end if mobf.current_preserve_list == nil then mobf.current_preserve_list = {} end minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize path handling subsystem..") mobf_path.init() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize lifebar subsystem..") mobf_lifebar.init() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize spawning subsystem..") spawning.init() minetest.log(LOGLEVEL_NOTICE,"MOBF: Initialize mobf supplied modules..") mobf_init_modules() minetest.log(LOGLEVEL_NOTICE,"MOBF: Register rightclick button handler..") minetest.register_on_player_receive_fields(mobf.rightclick_button_handler) minetest.log(LOGLEVEL_NOTICE,"MOBF: Initializing step time quota .. ") mobf_step_quota.initialize() -- register privilege to change mobf settings minetest.register_privilege("mobfw_admin", { description = "Player may change mobf settings", give_to_singleplayer = true }) core.log("action","MOD: mob framework mod "..mobf_version.." loaded") end --! @brief initialize mod dependencys function mobf_init_mod_deps() local modlist = minetest.get_modnames() for i=1,#modlist,1 do if modlist[i] == "fire" then mobf_rtd.fire_enabled = true end if modlist[i] == "adv_spawning" then mobf_rtd.have_adv_spawning = true end end end --! @brief initialize mobf submodules function mobf_init_modules() --state change callback mobf.register_on_step_callback({ name = "state_change", handler = mob_state.callback, init = mob_state.initialize, configcheck = function(entity) if entity.data.states ~= nil then return true end return false end }) --auto transform hook mobf.register_on_step_callback({ name = "transform", handler = transform, init = nil, configcheck = function(entity) if entity.data.auto_transform ~= nil then return true end return false end }) --combat hook mobf.register_on_step_callback({ name = "combat", handler = fighting.combat, init = fighting.init_dynamic_data, configcheck = function(entity) if entity.data.combat ~= nil then return true end return false end }) --attention hook mobf.register_on_step_callback({ name = "attention", handler = attention.callback, init = attention.init_dynamic_data, configcheck = function(entity) if entity.data.attention ~= nil or entity.data.combat ~= nil then return true end return false end }) --workaround for shortcomings in spawn algorithm mobf.register_on_step_callback({ name = "check_pop_dense", handler = spawning.population_density_check, init = spawning.init_dynamic_data, configcheck = function(entity) if entity.data.generic.population_density ~= nil or entity.data.spawning ~= nil then return true else return false end end }) --random drop hook mobf.register_on_step_callback({ name = "random_drop", handler = random_drop.callback, init = random_drop.init_dynamic_data, configcheck = function(entity) if entity.data.random_drop ~= nil then return true end return false end }) --random sound hook mobf.register_on_step_callback({ name = "sound", handler = sound.play_random, init = sound.init_dynamic_data, configcheck = function(entity) if entity.data.sound ~= nil and entity.data.sound.random ~= nil then return true end return false end }) --visual change hook mobf.register_on_step_callback({ name = "update_graphics", handler = graphics.update, init = graphics.init_dynamic_data, configcheck = function(entity) return true end }) --custom hook mobf.register_on_step_callback({ name = "custom_hooks", handler = function(entity,now,dtime) if type(entity.data.generic.custom_on_step_handler) == "function" then entity.data.generic.custom_on_step_handler(entity,now,dtime) end end, configcheck = function(entity) return true end }) --on punch callbacks mobf.register_on_punch_callback({ name = "harvesting", handler = harvesting.callback, init = harvesting.init_dynamic_data, configcheck = function(entity) if (entity.data.catching ~= nil and entity.data.catching.tool ~= "" ) or entity.data.harvest ~= nil then return true end return false end }) mobf.register_on_punch_callback({ name = "riding", handler = mobf_ride.on_punch_callback, configcheck = mobf_ride.is_enabled }) mobf.register_on_punch_callback({ name = "punching", handler = fighting.hit, configcheck = function(entity) return true end }) --on rightclick callbacks mobf.register_on_rightclick_callback({ name = "tradercallback", visiblename = "Trade", handler = mob_inventory.trader_callback, configcheck = mob_inventory.config_check }) mobf.register_on_rightclick_callback({ name = "debugcallback", visiblename = "Show debuginfo", handler = mobf_debug.rightclick_callback, configcheck = function(entity) return true end, privs = {mobfw_admin=true} }) mobf.register_on_rightclick_callback({ name = "pathcallback", visiblename = mobf_path.buttontext, handler = mobf_path.mob_rightclick_callback, configcheck = mobf_path.config_check }) mobf.register_on_rightclick_callback({ name = "factions", visiblename = "Factions", handler = mobf_factions.mob_rightclick_callback, configcheck = mobf_factions.config_check }) mobf.register_on_rightclick_callback({ name = "heal", visiblename = fighting.heal_caption, handler = fighting.heal, configcheck = function(entity) return true end, }) end mobf_init_framework() dofile (mobf_modpath .. "/compatibility.lua") mobf_core-2.5.1/mobf/inventory.lua000066400000000000000000000502351264104133000171340ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file inventory.lua --! @brief component containing mob inventory related functions --! @copyright Sapier --! @author Sapier --! @date 2013-01-02 -- --! @defgroup Inventory Inventory subcomponent --! @brief Component handling mob inventory --! @ingroup framework_int --! @{ -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mob_inventory")) --! @class mob_inventory --! @brief inventory handling for trader like mobs --! @} mob_inventory = {} -- Boilerplate to support localized strings if intllib mod is installed. local S if core.global_exists("intllib") then S = intllib.Getter() else S = function(s) return s end end ------------------------------------------------------------------------------- mob_inventory.trader_inventories = {} mob_inventory.formspecs = {} ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] allow_move(inv, from_list, from_index, to_list, to_index, count, player) -- --! @brief check if there is enough at payroll --! @memberof mob_inventory --! @private -- --! @param inv inventory reference --! @param from_list name of list elements taken --! @param from_index index at list elements taken --! @param to_list list name of list elements being put --! @param to_index index at list elements being put --! @param count number of elements moved --! @param player doing changes -- --! @return number of elements allowed to move ------------------------------------------------------------------------------- function mob_inventory.allow_move(inv, from_list, from_index, to_list, to_index, count, player) dbg_mobf.trader_inv_lvl1("MOBF: move inv: " .. tostring(inv) .. " from:" .. dump(from_list) .. " to: " .. dump(to_list)) if to_list ~= "selection" or from_list == "price_1" or from_list == "price_2" or from_list == "pay" or from_list == "takeaway" or from_list == "identifier" then return 0 end -- forbid moving of parts of stacks local old_stack = inv.get_stack(inv, from_list, from_index) if count ~= old_stack.get_count(old_stack) then return 0; end return count end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] allow_put(inv, listname, index, stack, player) -- --! @brief check if there is enough at payroll --! @memberof mob_inventory --! @private -- --! @param inv inventory reference --! @param listname name of list changed --! @param index index in list changed --! @param stack moved --! @param player doing changes -- --! @return number of elements allowed to put ------------------------------------------------------------------------------- function mob_inventory.allow_put(inv, listname, index, stack, player) dbg_mobf.trader_inv_lvl1("MOBF: put inv: " .. tostring(inv) .. " to:" .. dump(listname)) if listname == "pay" then return 99 end return 0 end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] allow_take(inv, listname, index, stack, player) -- --! @brief check if there is enough at payroll --! @memberof mob_inventory --! @private -- --! @param inv inventory reference --! @param listname name of list changed --! @param index index in list changed --! @param stack moved --! @param player doing changes -- --! @return number of elements allowed to take ------------------------------------------------------------------------------- function mob_inventory.allow_take(inv, listname, index, stack, player) dbg_mobf.trader_inv_lvl1("MOBF: take inv: " .. tostring(inv) .. " to:" .. dump(listname)) if listname == "takeaway" or listname == "pay" then return 99 end return 0 end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] on_move(inv, from_list, from_index, to_list, to_index, count, player) -- --! @brief check if there is enough at payroll --! @memberof mob_inventory --! @private -- --! @param inv inventory reference --! @param from_list name of list elements taken --! @param from_index index at list elements taken --! @param to_list list name of list elements being put --! @param to_index index at list elements being put --! @param count number of elements moved --! @param player doing changes ------------------------------------------------------------------------------- function mob_inventory.on_move(inv, from_list, from_index, to_list, to_index, count, player) dbg_mobf.trader_inv_lvl1("MOBF: inv\"" .. tostring(inv) .. "\" moving " .. count .. " items from: " .. from_list .. ":" .. from_index .. " to: " .. to_list .. ":" .. to_index) if from_list == "goods" and to_list == "selection" then local moved = inv.get_stack(inv,to_list, to_index) local goodname = moved.get_name(moved) local elements = moved.get_count(moved) -- handle situations where diffrent amounts of the same good are offered- dbg_mobf.trader_inv_lvl2("MOBF: moving " .. dump(elements) .."/" .. dump(count) .. " from " .. dump(from_list).. " to ".. dump(from_list ).. " at index "..dump(from_index )) -- if it was the same type of good (and now contains a summed up number -- of offerings) put the old stack back into the traders inv if( elements > count ) then dbg_mobf.trader_inv_lvl2( "MOBF: ok, whe have to give back a stack of " ..tostring( elements - count ).." "..tostring( goodname ) ..". target stack: "..tostring( from_index )) -- remove the surplus parts inv.set_stack(inv,"selection", 1,goodname.." "..tostring( count )) -- the slot we took from is now free inv.set_stack(inv,"goods",from_index, goodname.." "..tostring( elements - count )) -- update the real amount of items in the slot now elements = count end local entity = mob_inventory.get_entity(inv) if entity == nil then dbg_mobf.trader_inv_lvl1("MOBF: move unable to find linked entity") return end dbg_mobf.trader_inv_lvl1("MOBF: good selected: " .. goodname) --get element put to selection mob_inventory.fill_prices(entity,inv,goodname,count) mob_inventory.update_takeaway(inv) end end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] on_put(inv, listname, index, stack, player) -- --! @brief check if there is enough at payroll --! @memberof mob_inventory --! @private -- --! @param inv inventory reference --! @param listname name of list changed --! @param index index in list changed --! @param stack moved --! @param player doing changes ------------------------------------------------------------------------------- function mob_inventory.on_put(inv, listname, index, stack, player) if listname == "pay" then local now_at_pay = inv.get_stack(inv,"pay",1) local playername = player.get_player_name(player) local count = now_at_pay.get_count(now_at_pay) local name = now_at_pay.get_name(now_at_pay) dbg_mobf.trader_inv_lvl1("MOBF: putpay player: " .. playername .. " pays now count=" .. count .. " of type=" ..name) mob_inventory.update_takeaway(inv) end end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] on_take(inv, listname, index, stack, player) -- --! @brief check if there is enough at payroll --! @memberof mob_inventory --! @private -- --! @param inv inventory reference --! @param listname name of list changed --! @param index index in list changed --! @param stack moved --! @param player doing changes ------------------------------------------------------------------------------- function mob_inventory.on_take(inv, listname, index, stack, player) if listname == "takeaway" then local now_at_pay = inv.get_stack(inv,"pay",index) local playername = player.get_player_name(player) local count = now_at_pay.get_count(now_at_pay) local name = now_at_pay.get_name(now_at_pay) dbg_mobf.trader_inv_lvl2("MOBF: takeaway player: " .. playername .. " pays now count=" .. count .. " of type=" ..name) if not mob_inventory.check_pay(inv,true) then dbg_mobf.trader_inv_lvl1("MOBF: error player hasn't payed enough!") end mob_inventory.update_takeaway(inv) end if listname == "pay" then if mob_inventory.check_pay(inv,false) then local selection = inv.get_stack(inv,"selection", 1) if selection ~= nil then inv.set_stack(inv,"takeaway",1,selection) else dbg_mobf.trader_inv_lvl1("MOBF: nothing selected to buy") end else inv.set_stack(inv,"takeaway",1,nil) end end end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] update_takeaway(inv) -- --! @brief update content of takeaway --! @memberof mob_inventory --! @private -- --! @param inv to update ------------------------------------------------------------------------------- function mob_inventory.update_takeaway(inv) if mob_inventory.check_pay(inv,false) then local selection = inv.get_stack(inv,"selection", 1) if selection ~= nil then inv.set_stack(inv,"takeaway",1,selection) else dbg_mobf.trader_inv_lvl1("MOBF: nothing selected to buy") end else inv.set_stack(inv,"takeaway",1,nil) end end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] check_pay(inv) -- --! @brief check if there is enough at payroll --! @memberof mob_inventory --! @private -- --! @param inv inventory to do check --! @param paynow true/false if it's called to pay or not -- --! @return true/false ------------------------------------------------------------------------------- function mob_inventory.check_pay(inv,paynow) local now_at_pay = inv.get_stack(inv,"pay",1) local count = now_at_pay.get_count(now_at_pay) local name = now_at_pay.get_name(now_at_pay) local price1 = inv.get_stack(inv,"price_1", 1) local price2 = inv.get_stack(inv,"price_2", 1) dbg_mobf.trader_inv_lvl1( "MOBF: p1 " .. dump(price1) .. " " .. dump(price1:get_name())) if price1:get_name() == name then local price = price1:get_count() if price > 0 and price <= count then if paynow then now_at_pay.take_item(now_at_pay,price) inv.set_stack(inv,"pay",1,now_at_pay) return true else return true end else if paynow then inv.set_stack(inv,"pay",1,nil) end end end dbg_mobf.trader_inv_lvl1( "MOBF: p2 " .. dump(price1) .. " " .. dump(price2:get_name())) if price2:get_name() == name then local price = price2:get_count() if price > 0 and price <= count then if paynow then now_at_pay.take_item(now_at_pay,price) inv.set_stack(inv,"pay",1,now_at_pay) return true else return true end else if paynow then inv.set_stack(inv,"pay",1,nil) end end end return false end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] init_detached_inventories(entity,now) -- --! @brief initialize dynamic data required by harvesting --! @memberof mob_inventory --! @public -- --! @param entity mob to initialize harvest dynamic data ------------------------------------------------------------------------------- function mob_inventory.init_trader_inventory(entity) --TODO find out why calling "tostring" is necessary?! local tradername = tostring( entity.data.trader_inventory.random_names[ math.random(1,#entity.data.trader_inventory.random_names)]) dbg_mobf.trader_inv_lvl3( "MOBF: randomly selected \"" .. tradername .. "\" as name") local unique_entity_id = string.gsub(tostring(entity),"table: ","") --local unique_entity_id = "testinv" local trader_inventory = minetest.create_detached_inventory(unique_entity_id, { allow_move = mob_inventory.allow_move, allow_put = mob_inventory.allow_put, allow_take = mob_inventory.allow_take, on_move = mob_inventory.on_move, on_put = mob_inventory.on_put, on_take = mob_inventory.on_take, }) trader_inventory.set_size(trader_inventory,"goods",16) trader_inventory.set_size(trader_inventory,"takeaway",1) trader_inventory.set_size(trader_inventory,"selection",1) trader_inventory.set_size(trader_inventory,"price_1",1) trader_inventory.set_size(trader_inventory,"price_2",1) trader_inventory.set_size(trader_inventory,"pay",1) mob_inventory.add_goods(entity,trader_inventory) --register to trader inventories table.insert(mob_inventory.trader_inventories, { identifier = unique_entity_id, inv_ref = trader_inventory, ent_ref = entity, }) dbg_mobf.trader_inv_lvl3( "MOBF: registering identifier: " .. unique_entity_id .. " invref \"" .. tostring(trader_inventory) .. "\" for entity \"" .. tostring(entity) .. "\"" ) local trader_formspec = "size[8,10;]" .. "label[2,0;"..S("Trader %s Inventory"):format(tradername).."]" .. "label[0,1;"..S("Selling:").."]" .. "list[detached:" .. unique_entity_id .. ";goods;0,1.5;8,2;]" .. "label[0,4.0;"..S("Selection").."]" .. "list[detached:" .. unique_entity_id .. ";selection;0,4.5;1,1;]" .. "label[1.25,4.75;-->]" .. "label[2,4.0;"..S("Price").."]" .. "list[detached:" .. unique_entity_id .. ";price_1;2,4.5;1,1;]" .. "label[3,4.0;"..S("or").."]" .. "list[detached:" .. unique_entity_id .. ";price_2;3,4.5;1,1;]" .. "label[4.25,4.75;-->]" .. "label[5,4.0;"..S("Pay").."]" .. "list[detached:" .. unique_entity_id .. ";pay;5,4.5;1,1;]" .. "label[6.25,4.75;-->]" .. "label[6.75,4.0;"..S("Takeaway").."]" .. "list[detached:" .. unique_entity_id .. ";takeaway;7,4.5;1,1;]" .. "list[current_player;main;0,6;8,4;]" if mob_inventory.register_formspec("formspec_" .. unique_entity_id,trader_formspec) == false then dbg_mobf.trader_inv_lvl1("MOBF: unable to create trader formspec") end end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] config_check(entity) -- --! @brief check if mob is configured as trader --! @memberof mob_inventory --! @public -- --! @param entity mob being checked --! @return true/false if trader or not ------------------------------------------------------------------------------- function mob_inventory.config_check(entity) if entity.data.trader_inventory ~= nil then return true end return false end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] register_formspec(name,formspec) -- --! @brief check if mob is configured as trader --! @memberof mob_inventory --! @public -- --! @param name name of formspec to register --! @param formspec formspec definition -- --! @return true/false if succesfull or not ------------------------------------------------------------------------------- function mob_inventory.register_formspec(name,formspec) if mob_inventory.formspecs[name] == nil then mob_inventory.formspecs[name] = formspec return true end return false end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] trader_callback(entity,player) -- --! @brief callback handler for inventory by rightclick --! @memberof mob_inventory --! @public -- --! @param entity mob being harvested --! @param player player harvesting -- --! @return true/false if handled by harvesting or not ------------------------------------------------------------------------------- function mob_inventory.trader_callback(entity, player) local unique_entity_id = string.gsub(tostring(entity),"table: ","") --local unique_entity_id = "testinv" local playername = player.get_player_name(player) if entity.data.sound ~= nil and entity.data.sound.inventory_open ~= nil then sound.play(playername, entity.data.sound.inventory_open) end if mob_inventory.formspecs["formspec_" .. unique_entity_id] ~= nil then local pos = entity.object:getpos() if pos == nil then dbg_mobf.trader_inv_lvl1("MOBF: unable to get trader pos") minetest.show_formspec(playername,"") return end --rotate mob to face player local direction = mobf_get_direction(pos, player:getpos()) if entity.mode == "3d" then graphics.setyaw(entity, mobf_calc_yaw(direction.x,direction.z)) else graphics.setyaw(entity, mobf_calc_yaw(direction.x,direction.z)+math.pi/2) end attention.increase_attention_level(entity,player,10) if minetest.show_formspec(playername, "formspec_" .. unique_entity_id, mob_inventory.formspecs["formspec_" .. unique_entity_id]) == false then dbg_mobf.trader_inv_lvl1("MOBF: unable to show trader formspec") end end end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] get_entity(inv) -- --! @brief find entity linked to inventory --! @memberof mob_inventory --! @private -- --! @param inv name of inventory ------------------------------------------------------------------------------- function mob_inventory.get_entity(inv) dbg_mobf.trader_inv_lvl3("MOBF: checking " .. #mob_inventory.trader_inventories .. " registred inventorys") local location = inv.get_location(inv) if location.type == "detached" then for i=1,#mob_inventory.trader_inventories,1 do dbg_mobf.trader_inv_lvl3("MOBF: comparing \"" .. location.name .. "\" to \"" .. mob_inventory.trader_inventories[i].identifier .. "\"") if mob_inventory.trader_inventories[i].identifier == location.name then return mob_inventory.trader_inventories[i].ent_ref end end end return nil end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] fill_prices(entity,inventory,goodname) -- --! @brief fill price fields --! @memberof mob_inventory --! @private -- --! @param entity to look for prices --! @param inventory to set prices --! @param goodname name of good to set prices for --! @param count number of elements ------------------------------------------------------------------------------- function mob_inventory.fill_prices(entity,inventory,goodname,count) --get price info from entity local good = nil for i=1,#entity.data.trader_inventory.goods,1 do local stackstring = goodname .." " .. count dbg_mobf.trader_inv_lvl3("MOBF: comparing \"" .. stackstring .. "\"" .. " to \"" .. entity.data.trader_inventory.goods[i][1] .. "\"") if entity.data.trader_inventory.goods[i][1] == stackstring then good = entity.data.trader_inventory.goods[i] end end if good ~= nil then inventory.set_stack(inventory,"price_1", 1, good[2]) inventory.set_stack(inventory,"price_2", 1, good[3]) else inventory.set_stack(inventory,"price_1", 1, nil) inventory.set_stack(inventory,"price_2", 1, nil) end end ------------------------------------------------------------------------------- -- @function [parent=#mob_inventory] add_goods(entity,trader_inventory) -- --! @brief fill inventory with mobs goods --! @memberof mob_inventory --! @private -- --! @param entity to look for prices --! @param trader_inventory to put goods ------------------------------------------------------------------------------- function mob_inventory.add_goods(entity,trader_inventory) local goods_to_add = nil if entity.data.trader_inventory.goodlist ~= nil then local total_sum_chances = 0 for i=1,#entity.data.trader_inventory.goodlist,1 do total_sum_chances = total_sum_chances + entity.data.trader_inventory.goodlist[i].chance end local rand_value = math.random(0,total_sum_chances) local selected_index=1 local runvalue = 0 for j=1,#entity.data.trader_inventory.goodlist,1 do runvalue = runvalue + entity.data.trader_inventory.goodlist[j].chance if runvalue < rand_value then selected_index=j break; end end goods_to_add = entity.data.trader_inventory.goodlist[selected_index].goods else goods_to_add = entity.data.trader_inventory.goods end dbg_mobf.trader_inv_lvl3("MOBF: adding " .. #goods_to_add .. " goods for trader") for i=1,#goods_to_add,1 do dbg_mobf.trader_inv_lvl3("MOBF:\tadding " .. goods_to_add[i][1]) trader_inventory.set_stack(trader_inventory,"goods", i, goods_to_add[i][1]) end end mobf_core-2.5.1/mobf/lifebar.lua000066400000000000000000000111051264104133000164740ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file lifebar.lua --! @brief mobf_lifebar implementation --! @copyright Sapier --! @author Sapier --! @date 2013-02-14 -- --! @defgroup mobf_lifebar --! @brief lifebar implements a visible lifebar showing health of a mob abov --! its head --! @ingroup framework_int --! @{ -- Contact: sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mobf_lifebar")) --! @class mobf_lifebar --! @brief a simple lifebar implementation --!@} mobf_lifebar = {} ------------------------------------------------------------------------------- -- @function [parent=#mobf_lifebar] init() -- --! @brief register lifebar entity --! @memberof mobf_lifebar ------------------------------------------------------------------------------- function mobf_lifebar.init() minetest.register_entity(":mobf:lifebar", { physical = false, collisionbox = { 0,0,0,0,0,0 }, visual = "sprite", textures = { "mobf_lb_64.png" }, visual_size = {x=1,y=0.2}, groups = { immortal=1, }, is_visible = true, initial_sprite_basepos = {x=0, y=0}, lifetime = 0, initialized = false, on_step = function (self,dtime) self.lifetime = self.lifetime + dtime if not self.initialized then if self.lifetime > 1 then dbg_mobf.lifebar_lvl3("MOBF: lifebar not attached deleting") self.object:remove() end end --parent will reset lifetime while it's active if self.lifetime > 5 then self.object:remove() end end }) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_lifebar] add(entity) -- --! @brief add a lifebat to an entity --! @memberof mobf_lifebar -- --! @param entity entity to add lifebar -- --! @return reference to lifebar added ------------------------------------------------------------------------------- function mobf_lifebar.add(entity) local pos = entity.object:getpos() local BS = 10 local lifebar_offset = (MAX(entity.collisionbox[4]-entity.collisionbox[1], entity.collisionbox[6]-entity.collisionbox[3]) / 0.5) * 0.4 lifebar_offset = lifebar_offset * lifebar_offset pos.y = pos.y + entity.collisionbox[5] + lifebar_offset local lifebar = minetest.add_entity(pos,"mobf:lifebar") if lifebar ~= nil then lifebar:set_attach(entity.object,"",{x=0,y=(entity.collisionbox[5] + 0.1) * BS,z=0},{x=0,y=-90,z=0}) local luaentity = lifebar:get_luaentity() if luaentity ~= nil then dbg_mobf.lifebar_lvl3("MOBF: marking lifebar as initialized") luaentity.initialized = true else dbg_mobf.lifebar_lvl3("MOBF: unable to create lifebar entity") end end return lifebar end ------------------------------------------------------------------------------- -- @function [parent=#mobf_lifebar] del(lifebar) -- --! @brief delete a lifebar --! @memberof mobf_lifebar -- --! @param lifebar lifebar do telete ------------------------------------------------------------------------------- function mobf_lifebar.del(lifebar) if lifebar ~= nil then lifebar:set_detach() lifebar:remove() end end ------------------------------------------------------------------------------- -- name: set(lifebar,value) -- --! @brief set value of a lifebar --! @memberof mobf_lifebar --! @private -- --! @param lifebar lifebar do update --! @param value (0-1) value of lifebar ------------------------------------------------------------------------------- function mobf_lifebar.set(lifebar,value) if lifebar ~= nil then local modifiername = mobf_lifebar.get_imagename(value) dbg_mobf.lifebar_lvl2("MOBF: got modifier " .. modifiername .. " for value " .. value) lifebar:settexturemod(modifiername) end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_lifebar] get_imagename(value) -- --! @brief calculate imagename from value --! @memberof mobf_lifebar --! @private -- --! @param value to get image for ------------------------------------------------------------------------------- function mobf_lifebar.get_imagename(value) local number = math.floor((value*32) +0.5) dbg_mobf.lifebar_lvl2("MOBF: calculated number: " .. number ) if number < 5 then return "^mobf_lb_0" .. number * 2 .. ".png" else return "^mobf_lb_" .. number * 2 .. ".png" end endmobf_core-2.5.1/mobf/locale/000077500000000000000000000000001264104133000156265ustar00rootroot00000000000000mobf_core-2.5.1/mobf/locale/de.txt000066400000000000000000000003411264104133000167550ustar00rootroot00000000000000# Translation by Xanthin Path marker tool = Pfadmarkierer ###Inventory.lua### Trader %s Inventory = Haendler %s Waren Selling: = Zu verkaufen: Selection = Auswahl Price = Preis or = oder Pay = Bezahlen Takeaway = Wegnehmen mobf_core-2.5.1/mobf/locale/es.txt000066400000000000000000000005241264104133000167770ustar00rootroot00000000000000# Spanish translation for Animals Modpack. # Traducción al español de Animals Modpack. # Author/Autor: Diego Martínez Path marker tool = Herramienta de marcado de camino ###Inventory.lua### Trader %s Inventory = Inventario de Mercader %s Selling: = Vende: Selection = Seleccion Price = Precio or = o Pay = Paga Takeaway = Toma mobf_core-2.5.1/mobf/locale/template.txt000066400000000000000000000002071264104133000202010ustar00rootroot00000000000000# Template Path marker tool = ###Inventory.lua### Trader %s Inventory = Selling: = Selection = Price = or = Pay = Takeaway = mobf_core-2.5.1/mobf/mgen_flee/000077500000000000000000000000001264104133000163105ustar00rootroot00000000000000mobf_core-2.5.1/mobf/mgen_flee/main_flee.lua000066400000000000000000000327021264104133000207360ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file main_flee.lua --! @brief component containing a movement generator trying to avoid a target --! @copyright Sapier --! @author Sapier --! @date 2013-09-10 -- --! @defgroup mgen_flee MGEN: flee movement generator --! @brief A movement generator creating movement that trys to avoid a moving --! target or get away as far as possible from a given point on map --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @class mgen_flee --! @brief a movement generator trying to get a way from a target --!@} mgen_flee = {} --! @brief movement generator identifier --! @memberof mgen_flee mgen_flee.name = "flee_mov_gen" ------------------------------------------------------------------------------- -- name: callback(entity,now) -- --! @brief main callback to make a mob flee from a target --! @memberof mgen_flee -- --! @param entity mob to generate movement for --! @param now current time ------------------------------------------------------------------------------- function mgen_flee.callback(entity,now) dbg_mobf.flmovement_lvl3("MOBF: Follow mgen callback called") if entity == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: called movement gen without entity!") return end if entity.dynamic_data == nil or entity.dynamic_data.movement == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: >" ..entity.data.name .. "< removed=" .. dump(entity.removed) .. " entity=" .. tostring(entity) .. " probab movement callback") return end local flee_speedup = {x=10,y=2,z=10 } if entity.data.movement.flee_speedup ~= nil then if type(entity.data.movement.flee_speedup) == "table" then flee_speedup = entity.data.movement.flee_speedup else flee_speedup.x= entity.data.movement.flee_speedup flee_speedup.z= entity.data.movement.flee_speedup end end --check max speed limit mgen_flee.checkspeed(entity) --check environment local basepos = entity.getbasepos(entity) local pos_quality = environment.pos_quality(basepos,entity) if environment.evaluate_state(pos_quality, LT_GOOD_POS) or (entity.data.movement.canfly and environment.evaluate_state(pos_quality,LT_GOOD_FLY_POS)) then local toset = { x= basepos.x, y= basepos.y - 0.5 - entity.collisionbox[2], z= basepos.z } --save known good position entity.dynamic_data.movement.last_pos_in_env = toset end if pos_quality.media_quality == MQ_IN_AIR or -- wrong media pos_quality.media_quality == MQ_IN_WATER or -- wrong media pos_quality.geometry_quality == GQ_NONE or -- no ground contact (TODO this was drop above water before) pos_quality.surface_quality_min == SQ_WATER then -- above water if entity.dynamic_data.movement.invalid_env_count == nil then entity.dynamic_data.movement.invalid_env_count = 0 end entity.dynamic_data.movement.invalid_env_count = entity.dynamic_data.movement.invalid_env_count + 1 --don't change at first invalid pos but give some steps to cleanup by --other less invasive mechanisms if entity.dynamic_data.movement.invalid_env_count > 10 then dbg_mobf.flmovement_lvl1("MOBF: fled to wrong place " .. pos_quality.tostring(pos_quality)) if entity.dynamic_data.movement.last_pos_in_env ~= nil then entity.object:moveto(entity.dynamic_data.movement.last_pos_in_env) basepos = entity.getbasepos(entity) else local newpos = environment.get_suitable_pos_same_level(basepos,1,entity,true) if newpos == nil then newpos = environment.get_suitable_pos_same_level( { x=basepos.x, y=basepos.y-1, z=basepos.z } ,1,entity,true) end if newpos == nil then newpos = environment.get_suitable_pos_same_level( { x=basepos.x, y=basepos.y+1, z=basepos.z } ,1,entity,true) end if newpos == nil then dbg_mobf.flmovement_lvl1("MOBF: no way to fix it removing mob") spawning.remove(entity,"mgen_flee poscheck") else newpos.y = newpos.y - (entity.collisionbox[2] + 0.49) entity.object:moveto(newpos) basepos = entity.getbasepos(entity) end end end else entity.dynamic_data.movement.invalid_env_count = 0 end if pos_quality.level_quality ~= LQ_OK and entity.data.movement.canfly then local current_accel = entity.object:getacceleration() if pos_quality.level_quality == LQ_ABOVE then if current_accel.y >= 0 then current_accel.y = - entity.data.movement.max_accel end end if pos_quality.level_quality == LQ_BELOW then local current_accel = entity.object:getacceleration() if current_accel.y <= 0 then current_accel.y = entity.data.movement.max_accel end end entity.object:setacceleration(current_accel) return end --fixup height local current_accel = entity.object:getacceleration() if entity.data.movement.canfly then if current_accel.y ~= 0 then current_accel.y = 0 entity.object:setacceleration(current_accel) end end if entity.dynamic_data.movement.target ~= nil then dbg_mobf.flmovement_lvl3("MOBF: Target available") --calculate distance to target local targetpos = nil if entity.dynamic_data.movement.target ~= nil then dbg_mobf.flmovement_lvl3("MOBF: have moving target") if not mobf_is_pos(entity.dynamic_data.movement.target) then targetpos = entity.dynamic_data.movement.target:getpos() else targetpos = entity.dynamic_data.movement.target end end if targetpos == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: flee " .. entity.data.name .. " don't have targetpos " .. " TGT: " .. dump(entity.dynamic_data.movement.target)) return end --TODO need to do something if not line of sight? if mobf_line_of_sight({x=basepos.x,y=basepos.y+1,z=basepos.z}, {x=targetpos.x,y=targetpos.y+1,z=targetpos.z}) == false then dbg_mobf.flmovement_lvl3("MOBF: no line of sight") --TODO teleport support? --TODO other ways to handle this? --return end local current_velocity = entity.object:getvelocity() local predicted_pos = movement_generic.predict_next_block(basepos,current_velocity,current_accel) local current_distance = mobf_calc_distance(targetpos,basepos) local predicted_distance = mobf_calc_distance(targetpos,predicted_pos) if not (predicted_distance > current_distance) then local current_rotation_addon = 0 local accel_to_set = nil local jump_required = false while accel_to_set == nil and current_rotation_addon < math.pi*(5/16) do accel_to_set,jump_required = mgen_flee.calc_accel(entity, targetpos, basepos, current_rotation_addon, current_velocity) if accel_to_set == nil then accel_to_set,jump_required = mgen_flee.calc_accel(entity, targetpos, basepos, -current_rotation_addon, current_velocity) end if accel_to_set == nil then current_rotation_addon = current_rotation_addon + math.pi/8 end end if accel_to_set ~= nil then mgen_flee.set_acceleration(entity,accel_to_set,flee_speedup,basepos) if jump_required then local jumpvel = current_velocity jumpvel.y = 5 entity.object:setvelocity(jumpvel) end else dbg_mobf.flmovement_lvl2("MOBF: didn't find a way to flee, stopping") end end else --TODO evaluate if this is an error case end end function mgen_flee.calc_accel(entity,targetpos,basepos,rot_addon,cur_vel) local dirxz,dirxy = mobf_calc_direction(targetpos,basepos) local dirxz = dirxz + rot_addon local accel_to_set = mobf_calc_vector_components(dirxz,entity.data.movement.max_speed) local yaccel = environment.get_default_gravity(basepos, entity.environment.media, entity.data.movement.canfly) accel_to_set.y = yaccel local predicted_pos = movement_generic.predict_next_block(basepos,cur_vel,accel_to_set) local pos_quality = environment.pos_quality(predicted_pos,entity) if pos_quality.media_quality == MQ_IN_MEDIA and pos_quality.surface_quality_min > SQ_WATER then return accel_to_set,false end predicted_pos.y = predicted_pos.y+1 pos_quality = environment.pos_quality(predicted_pos,entity) if pos_quality.media_quality == MQ_IN_MEDIA and pos_quality.surface_quality_min > SQ_WATER then return accel_to_set,true end return nil,false end ------------------------------------------------------------------------------- -- name: next_block_ok() -- --! @brief check quality of next block --! @memberof mgen_flee --! @public ------------------------------------------------------------------------------- function mgen_flee.next_block_ok(entity,pos,acceleration,velocity) local current_velocity = velocity if current_velocity == nil then current_velocity = entity.object:getvelocity() end local predicted_pos = movement_generic.predict_next_block(pos,current_velocity,acceleration) local quality = environment.pos_quality(predicted_pos,entity) return ( (quality.media_quality == MQ_IN_MEDIA) and (quality.level_quality == LQ_OK) and ( (quality.surface_quality_min == Q_UNKNOWN) or (quality.surface_quality_min >= SQ_WRONG) ) ) end ------------------------------------------------------------------------------- -- name: initialize() -- --! @brief initialize movement generator --! @memberof mgen_flee --! @public ------------------------------------------------------------------------------- function mgen_flee.initialize(entity,now) --intentionally empty end ------------------------------------------------------------------------------- -- name: init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by movement generator --! @memberof mgen_flee --! @public -- --! @param entity mob to initialize dynamic data --! @param now current time ------------------------------------------------------------------------------- function mgen_flee.init_dynamic_data(entity,now) local pos = entity.object:getpos() local data = { target = nil, invalid_env_count = 0, } entity.dynamic_data.movement = data end ------------------------------------------------------------------------------- -- name: checkspeed(entity) -- --! @brief check if mobs speed is within it's limits and correct if necessary --! @memberof mgen_flee --! @private -- --! @param entity mob to initialize dynamic data ------------------------------------------------------------------------------- function mgen_flee.checkspeed(entity) local current_velocity = entity.object:getvelocity() local xzspeed = mobf_calc_scalar_speed(current_velocity.x,current_velocity.z) if (xzspeed > (entity.data.movement.max_speed * 10)) then --preserver orientation when correcting speed local dir = mobf_calc_yaw(current_velocity.x,current_velocity.z) local velocity_to_set = mobf_calc_vector_components(dir,entity.data.movement.max_speed * 0.25) velocity_to_set.y=current_velocity.y entity.object:setvelocity(velocity_to_set) return true end return false end ------------------------------------------------------------------------------- -- name: set_acceleration(entity,accel,speedup) -- --! @brief apply acceleration to entity --! @memberof mgen_flee --! @private -- --! @param entity mob to apply to --! @param accel acceleration to set --! @param speedup speedup factor --! @param pos current position ------------------------------------------------------------------------------- function mgen_flee.set_acceleration(entity,accel,speedup,pos) mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(accel ~= nil) mobf_assert_backtrace(speedup ~= nil) accel.x = accel.x*speedup.x accel.z = accel.z*speedup.z if entity.data.movement.canfly then accel.y = accel.y*speedup.y end if mgen_flee.next_block_ok(entity,pos,accel) then dbg_mobf.flmovement_lvl3("MOBF: flee setting acceleration to: " .. printpos(accel)); entity.object:setacceleration(accel) return true elseif mgen_flee.next_block_ok(entity,pos,{x=0,y=0,z=0}) then accel = {x=0,y=0,z=0} dbg_mobf.flmovement_lvl3("MOBF: flee setting acceleration to: " .. printpos(accel)); entity.object:setacceleration(accel) return true else local current_velocity = entity.object:getvelocity() current_velocity.y = 0 if mgen_flee.next_block_ok(entity,pos,{x=0,y=0,z=0},current_velocity) then accel = {x=0,y=0,z=0} entity.object:setvelocity(current_velocity) entity.object:setacceleration(accel) return true end end dbg_mobf.flmovement_lvl1( "MOBF: \t acceleration " .. printpos(accel) .. " would result in invalid position not applying!") return false end ------------------------------------------------------------------------------- -- name: set_target(entity, target, follow_speedup, max_distance) -- --! @brief set target for movgen --! @memberof mgen_flee -- --! @param entity mob to apply to --! @param target to set --! @param follow_speedup --unused here --! @param max_distance --unused here ------------------------------------------------------------------------------- function mgen_flee.set_target(entity, target, follow_speedup, max_distance) mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(target ~= nil) entity.dynamic_data.movement.target = target return true end --register this movement generator registerMovementGen(mgen_flee.name,mgen_flee)mobf_core-2.5.1/mobf/mgen_follow/000077500000000000000000000000001264104133000166775ustar00rootroot00000000000000mobf_core-2.5.1/mobf/mgen_follow/main_follow.lua000066400000000000000000000504721264104133000217200ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file main_follow.lua --! @brief component containing a targeted movement generator --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup mgen_follow MGEN: follow movement generator --! @brief A movement generator creating movement that trys to follow a moving --! target or reach a given point on map --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @class mgen_follow --! @brief a movement generator trying to follow or reach a target --!@} mgen_follow = {} --! @brief movement generator identifier --! @memberof mgen_follow mgen_follow.name = "follow_mov_gen" ------------------------------------------------------------------------------- -- name: identify_movement_state(ownpos,targetpos) -- --! @brief check what situation we are --! @memberof mgen_follow --! @private -- --! @param ownpos position of entity --! @param targetpos position of target --! --! @return "below_los" --! "below_no_los" --! "same_height_los" --! "same_height_no_los" --! "above_los" --! "above_no_los" --! "unknown" ------------------------------------------------------------------------------- function mgen_follow.identify_movement_state(ownpos,targetpos) mobf_assert_backtrace(ownpos ~= nil) mobf_assert_backtrace(targetpos ~= nil) local same_height_delta = 0.1 local los = mobf_line_of_sight(ownpos,targetpos) if ownpos.y > targetpos.y - same_height_delta and ownpos.y < targetpos.y + same_height_delta then if los then return "same_height_los" else return "same_height_no_los" end end if ownpos.y < targetpos.y then if los then return "below_los" else return "below_no_los" end end if ownpos.y > targetpos.y then if los then return "above_los" else return "above_no_los" end end return "unknown" end ------------------------------------------------------------------------------- -- name: handleteleport(entity,now) -- --! @brief handle teleportsupport --! @memberof mgen_follow --! @private -- --! @param entity mob to check for teleport --! @param now current time --! @param targetpos position of target --! --! @return true/false finish processing ------------------------------------------------------------------------------- function mgen_follow.handleteleport(entity,now,targetpos) if (entity.dynamic_data.movement.last_next_to_target ~= nil ) then local time_since_next_to_target = now - entity.dynamic_data.movement.last_next_to_target dbg_mobf.fmovement_lvl3("MOBF: time since next to target: " .. time_since_next_to_target .. " delay: " .. dump(entity.data.movement.teleportdelay) .. " teleportsupport: " .. dump(entity.dynamic_data.movement.teleportsupport)) if (entity.dynamic_data.movement.teleportsupport) and time_since_next_to_target > entity.data.movement.teleportdelay then --check targetpos try to playe above if not valid local maxoffset = 5 local current_offset = 0 while (not environment.possible_pos(entity,{ x=targetpos.x, y=targetpos.y + current_offset, z=targetpos.z })) and current_offset < maxoffset do dbg_mobf.fmovement_lvl2( "MOBF: teleport target within block trying above: " .. current_offset) current_offset = current_offset +1 end targetpos.y = targetpos.y + current_offset --adjust to collisionbox of mob if entity.collisionbox[2] < -0.5 then targetpos.y = targetpos.y - (entity.collisionbox[2] + 0.49) end entity.object:setvelocity({x=0,y=0,z=0}) entity.object:setacceleration({x=0,y=0,z=0}) entity.object:moveto(targetpos) entity.dynamic_data.movement.last_next_to_target = now return true end end return false end ------------------------------------------------------------------------------- -- name: callback(entity,now) -- --! @brief main callback to make a mob follow its target --! @memberof mgen_follow -- --! @param entity mob to generate movement for --! @param now current time ------------------------------------------------------------------------------- function mgen_follow.callback(entity,now) dbg_mobf.fmovement_lvl3("MOBF: Follow mgen callback called") if entity == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: called movement gen without entity!") return end if entity.dynamic_data == nil or entity.dynamic_data.movement == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: >" ..entity.data.name .. "< removed=" .. dump(entity.removed) .. " entity=" .. tostring(entity) .. " probab movement callback") return end local follow_speedup = {x=10,y=2,z=10 } if entity.data.movement.follow_speedup ~= nil then if type(entity.data.movement.follow_speedup) == "table" then follow_speedup = entity.data.movement.follow_speedup else follow_speedup.x= entity.data.movement.follow_speedup follow_speedup.z= entity.data.movement.follow_speedup end end --if speedup is disabled reset if not entity.dynamic_data.movement.follow_speedup then follow_speedup = { x=1, y=1, z=1} end --check max speed limit mgen_follow.checkspeed(entity) --check environment local basepos = entity.getbasepos(entity) local pos_quality = environment.pos_quality(basepos,entity) if environment.evaluate_state(pos_quality, LT_GOOD_POS) or (entity.data.movement.canfly and environment.evaluate_state(pos_quality,LT_GOOD_FLY_POS)) then local toset = { x= basepos.x, y= basepos.y - 0.5 - entity.collisionbox[2], z= basepos.z } --save known good position entity.dynamic_data.movement.last_pos_in_env = toset end if pos_quality.media_quality == MQ_IN_AIR or -- wrong media pos_quality.media_quality == MQ_IN_WATER or -- wrong media pos_quality.geometry_quality == GQ_NONE or -- no ground contact (TODO this was drop above water before) pos_quality.surface_quality_min == SQ_WATER then -- above water if entity.dynamic_data.movement.invalid_env_count == nil then entity.dynamic_data.movement.invalid_env_count = 0 end entity.dynamic_data.movement.invalid_env_count = entity.dynamic_data.movement.invalid_env_count + 1 --don't change at first invalid pos but give some steps to cleanup by --other less invasive mechanisms if entity.dynamic_data.movement.invalid_env_count > 10 then dbg_mobf.fmovement_lvl1("MOBF: followed to wrong place " .. pos_quality.tostring(pos_quality)) if entity.dynamic_data.movement.last_pos_in_env ~= nil then entity.object:moveto(entity.dynamic_data.movement.last_pos_in_env) basepos = entity.getbasepos(entity) else local newpos = environment.get_suitable_pos_same_level(basepos,1,entity,true) if newpos == nil then newpos = environment.get_suitable_pos_same_level( { x=basepos.x, y=basepos.y-1, z=basepos.z } ,1,entity,true) end if newpos == nil then newpos = environment.get_suitable_pos_same_level( { x=basepos.x, y=basepos.y+1, z=basepos.z } ,1,entity,true) end if newpos == nil then dbg_mobf.fmovement_lvl1("MOBF: no way to fix it removing mob") spawning.remove(entity,"mgen_follow poscheck") else newpos.y = newpos.y - (entity.collisionbox[2] + 0.49) entity.object:moveto(newpos) basepos = entity.getbasepos(entity) end end end else entity.dynamic_data.movement.invalid_env_count = 0 end local current_accel = entity.object:getacceleration() if pos_quality.level_quality ~= LQ_OK and entity.data.movement.canfly then if pos_quality.level_quality == LQ_ABOVE then if current_accel.y >= 0 then current_accel.y = - entity.data.movement.max_accel end end if pos_quality.level_quality == LQ_BELOW then local current_accel = entity.object:getacceleration() if current_accel.y <= 0 then current_accel.y = entity.data.movement.max_accel end end entity.object:setacceleration(current_accel) return end --fixup height fixup if entity.data.movement.canfly then if current_accel.y ~= 0 then current_accel.y = 0 entity.object:setacceleration(current_accel) end end if entity.dynamic_data.movement.target ~= nil or entity.dynamic_data.movement.guardspawnpoint then dbg_mobf.fmovement_lvl3("MOBF: Target available") --calculate distance to target local targetpos = nil if entity.dynamic_data.movement.target ~= nil then dbg_mobf.fmovement_lvl3("MOBF: have moving target") if not mobf_is_pos(entity.dynamic_data.movement.target) then targetpos = entity.dynamic_data.movement.target:getpos() else targetpos = entity.dynamic_data.movement.target end end if targetpos == nil and entity.dynamic_data.movement.guardspawnpoint == true then dbg_mobf.fmovement_lvl3("MOBF: non target selected") targetpos = entity.dynamic_data.spawning.spawnpoint end if targetpos == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: " .. entity.data.name .. " don't have targetpos " .. "SP: " .. dump(entity.dynamic_data.spawning.spawnpoint) .. " TGT: " .. dump(entity.dynamic_data.movement.target)) return end local distance = nil local height_distance = nil if entity.data.movement.canfly then --real pos is relevant not basepos for flying mobs --target for flying mobs is always slightly above it's target distance = mobf_calc_distance(entity.object:getpos(), {x=targetpos.x, y=(targetpos.y+1), z=targetpos.z }) height_distance = entity.object:getpos().y - (targetpos.y+1) else distance = mobf_calc_distance_2d(basepos,targetpos) end if mobf_line_of_sight({x=basepos.x,y=basepos.y+1,z=basepos.z}, {x=targetpos.x,y=targetpos.y+1,z=targetpos.z}) == false then dbg_mobf.fmovement_lvl3("MOBF: no line of sight") --TODO teleport support? --TODO other ways to handle this? --return end --dbg_mobf.fmovement_lvl3("MOBF: line of sight") local max_distance = entity.dynamic_data.movement.max_distance if max_distance == nil then max_distance = 1 end --check if mob needs to move towards target dbg_mobf.fmovement_lvl3("MOBF: max distance is set to : " .. max_distance .. " dist: " .. distance) if distance > max_distance then entity.dynamic_data.movement.was_moving_last_step = true if mgen_follow.handleteleport(entity,now,targetpos) then return end dbg_mobf.fmovement_lvl3("MOBF: distance:" .. distance) local current_state = mgen_follow.identify_movement_state(basepos,targetpos) local handled = false if handled == false and (current_state == "same_height_los" or current_state == "above_los" or current_state == "above_no_los" ) then dbg_mobf.fmovement_lvl3("MOBF: \t Case 1: " .. current_state) local accel_to_set = movement_generic.get_accel_to(targetpos,entity,true) handled = mgen_follow.set_acceleration(entity, accel_to_set, follow_speedup, basepos) end if handled == false and (current_state == "below_los" or current_state == "below_no_los" or current_state == "same_height_no_los" ) then dbg_mobf.fmovement_lvl3("MOBF: \t Case 2: " .. current_state) local accel_to_set = movement_generic.get_accel_to(targetpos,entity) --seems to be a flying mob if (accel_to_set.y >0) then handled = mgen_follow.set_acceleration(entity, accel_to_set, follow_speedup, basepos) else local current_velocity = entity.object:getvelocity() local predicted_pos = movement_generic.predict_next_block(basepos, current_velocity, accel_to_set) --TODO replace by quality based mechanism!!!!!!!------------ local pos_state = environment.pos_is_ok(predicted_pos,entity) if pos_state == "collision_jumpable" then local pos_to_set = entity.object:getpos() pos_to_set.y = pos_to_set.y + 1.1 entity.object:moveto(pos_to_set) basepos.y=basepos.y+1.1 end ------------------------------------------------------------ dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: " .. printpos(accel_to_set) .. " predicted_state: " .. pos_state); handled = mgen_follow.set_acceleration(entity, accel_to_set, follow_speedup, basepos) end end if handled == false then dbg_mobf.fmovement_lvl1( "MOBF: \t Unexpected or unhandled movement state: " .. current_state) local yaccel = environment.get_default_gravity(basepos, entity.environment.media, entity.data.movement.canfly) --entity.object:setvelocity({x=0,y=0,z=0}) entity.object:setacceleration({x=0,y=yaccel,z=0}) end mgen_follow.update_animation(entity, "following") --nothing to do elseif height_distance ~= nil and math.abs(height_distance) > 0.1 then mgen_follow.set_acceleration(entity, { x=0,y=(height_distance*-0.2),z=0}, follow_speedup, basepos) mgen_follow.update_animation(entity, "following") --we're next to target stop movement else local yaccel = environment.get_default_gravity(basepos, entity.environment.media, entity.data.movement.canfly) if entity.dynamic_data.movement.was_moving_last_step == true or current_accel.Y ~= yaccel then dbg_mobf.fmovement_lvl3("MOBF: next to target") entity.object:setvelocity({x=0,y=0,z=0}) entity.object:setacceleration({x=0,y=yaccel,z=0}) entity.dynamic_data.movement.last_next_to_target = now mgen_follow.update_animation(entity, "ntt") end end else --TODO evaluate if this is an error case end end ------------------------------------------------------------------------------- -- name: update_animation() -- --! @brief update animation according to the follow movegen substate --! @memberof mgen_follow --! @public ------------------------------------------------------------------------------- function mgen_follow.update_animation(entity, anim_state) -- no need to change if anim_state == entity.dynamic_data.movement.anim_selected then return end -- check if there's a animation specified for stand in this state local statename, state = entity:get_state() if anim_state == "following" then entity.dynamic_data.movement.anim_selected = "following" if state.animation_walk ~= nil then graphics.set_animation(entity, state.animation_walk) elseif state.animation ~= nil then graphics.set_animation(entity, state.animation) end elseif anim_state == "ntt" then entity.dynamic_data.movement.anim_selected = "ntt" if state.animation_next_to_target ~= nil then graphics.set_animation(entity, state.animation_next_to_target) end end end ------------------------------------------------------------------------------- -- name: next_block_ok() -- --! @brief check quality of next block --! @memberof mgen_follow --! @public ------------------------------------------------------------------------------- function mgen_follow.next_block_ok(entity,pos,acceleration,velocity) local current_velocity = velocity if current_velocity == nil then current_velocity = entity.object:getvelocity() end local predicted_pos = movement_generic.predict_next_block(pos,current_velocity,acceleration) local quality = environment.pos_quality(predicted_pos,entity) return ( (quality.media_quality == MQ_IN_MEDIA) and (quality.level_quality == LQ_OK) and ( (quality.surface_quality_min == Q_UNKNOWN) or (quality.surface_quality_min >= SQ_WRONG) ) ) end ------------------------------------------------------------------------------- -- name: initialize() -- --! @brief initialize movement generator --! @memberof mgen_follow --! @public ------------------------------------------------------------------------------- function mgen_follow.initialize(entity,now) --intentionally empty end ------------------------------------------------------------------------------- -- name: init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by movement generator --! @memberof mgen_follow --! @public -- --! @param entity mob to initialize dynamic data --! @param now current time ------------------------------------------------------------------------------- function mgen_follow.init_dynamic_data(entity,now) local pos = entity.object:getpos() local data = { target = nil, guardspawnpoint = false, max_distance = entity.data.movement.max_distance, invalid_env_count = 0, follow_speedup = true, } if entity.data.movement.guardspawnpoint ~= nil and entity.data.movement.guardspawnpoint then dbg_mobf.fmovement_lvl3("MOBF: setting guard point to: " .. printpos(entity.dynamic_data.spawning.spawnpoint)) data.guardspawnpoint = true end if entity.data.movement.teleportdelay~= nil then data.last_next_to_target = now data.teleportsupport = true end entity.dynamic_data.movement = data end ------------------------------------------------------------------------------- -- name: checkspeed(entity) -- --! @brief check if mobs speed is within it's limits and correct if necessary --! @memberof mgen_follow --! @private -- --! @param entity mob to initialize dynamic data ------------------------------------------------------------------------------- function mgen_follow.checkspeed(entity) local current_velocity = entity.object:getvelocity() local xzspeed = mobf_calc_scalar_speed(current_velocity.x,current_velocity.z) if (xzspeed > entity.data.movement.max_speed) then local direction = mobf_calc_yaw(current_velocity.x, current_velocity.z) --reduce speed to 90% of current speed local new_speed = mobf_calc_vector_components(direction,xzspeed*0.9) local current_accel = entity.object:getacceleration() new_speed.y = current_velocity.y entity.object:setvelocity(new_speed) entity.object:setacceleration({x=0,y=current_accel.y,z=0}) return true end return false end ------------------------------------------------------------------------------- -- name: set_acceleration(entity,accel,speedup) -- --! @brief apply acceleration to entity --! @memberof mgen_follow --! @private -- --! @param entity mob to apply to --! @param accel acceleration to set --! @param speedup speedup factor --! @param pos current position ------------------------------------------------------------------------------- function mgen_follow.set_acceleration(entity,accel,speedup,pos) accel.x = accel.x*speedup.x accel.z = accel.z*speedup.z if entity.data.movement.canfly then accel.y = accel.y*speedup.y end if mgen_follow.next_block_ok(entity,pos,accel) then dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: " .. printpos(accel)); entity.object:setacceleration(accel) return true elseif mgen_follow.next_block_ok(entity,pos,{x=0,y=0,z=0}) then accel = {x=0,y=0,z=0} dbg_mobf.fmovement_lvl3("MOBF: setting acceleration to: " .. printpos(accel)); entity.object:setacceleration(accel) return true else local current_velocity = entity.object:getvelocity() current_velocity.y = 0 if mgen_follow.next_block_ok(entity,pos,{x=0,y=0,z=0},current_velocity) then accel = {x=0,y=0,z=0} entity.object:setvelocity(current_velocity) entity.object:setacceleration(accel) return true end end dbg_mobf.fmovement_lvl1( "MOBF: \t acceleration " .. printpos(accel) .. " would result in invalid position not applying!") return false end ------------------------------------------------------------------------------- -- name: set_target(entity, target, follow_speedup, max_distance) -- --! @brief set target for movgen --! @memberof mgen_follow -- --! @param entity mob to apply to --! @param target to set --! @param follow_speedup --unused here --! @param max_distance maximum distance to target to be tried to reach ------------------------------------------------------------------------------- function mgen_follow.set_target(entity,target, follow_speedup, max_distance) entity.dynamic_data.movement.target = target entity.dynamic_data.movement.max_distance = max_distance return true end --register this movement generator registerMovementGen(mgen_follow.name,mgen_follow)mobf_core-2.5.1/mobf/mgen_jordan4ibanez/000077500000000000000000000000001264104133000201275ustar00rootroot00000000000000mobf_core-2.5.1/mobf/mgen_jordan4ibanez/mgen_jordan4ibanez.lua000066400000000000000000000141621264104133000243760ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file main_follow.lua --! @brief component containing a movement generator based uppon jordan4ibanez code --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup mgen_jordan4ibanez MGEN: a velocity based movement generator --! @brief A movement generator creating simple random movement --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @class mgen_jordan4ibanez --! @brief a movement generator trying to follow or reach a target --!@} mgen_jordan4ibanez = {} --! @brief chillaxin_speed --! @memberof mgen_jordan4ibanez mgen_jordan4ibanez.chillaxin_speed = 0.1 --! @brief movement generator identifier --! @memberof mgen_jordan4ibanez mgen_jordan4ibanez.name = "jordan4ibanez_mov_gen" ------------------------------------------------------------------------------- -- name: callback(entity,now) -- --! @brief main callback to make a mob follow its target --! @memberof mgen_jordan4ibanez -- --! @param entity mob to generate movement for --! @param now current time ------------------------------------------------------------------------------- function mgen_jordan4ibanez.callback(entity,now) --update timers entity.dynamic_data.movement.timer = entity.dynamic_data.movement.timer + 0.01 entity.dynamic_data.movement.turn_timer = entity.dynamic_data.movement.turn_timer + 0.01 entity.dynamic_data.movement.jump_timer = entity.dynamic_data.movement.jump_timer + 0.01 entity.dynamic_data.movement.door_timer = entity.dynamic_data.movement.door_timer + 0.01 if entity.dynamic_data.movement.direction ~= nil then entity.object:setvelocity({x=entity.dynamic_data.movement.direction.x*mgen_jordan4ibanez.chillaxin_speed, y=entity.object:getvelocity().y, z=entity.dynamic_data.movement.direction.z*mgen_jordan4ibanez.chillaxin_speed}) end if entity.dynamic_data.movement.turn_timer > math.random(1,4) then entity.dynamic_data.movement.yaw = 360 * math.random() graphics.setyaw(entity, entity.dynamic_data.movement.yaw) entity.dynamic_data.movement.turn_timer = 0 entity.dynamic_data.movement.direction = {x = math.sin(entity.dynamic_data.movement.yaw)*-1, y = -10, z = math.cos(entity.dynamic_data.movement.yaw)} --entity.object:setvelocity({x=entity.dynamic_data.movement.direction.x,y=entity.object:getvelocity().y,z=entity.dynamic_data.movement.direction.z}) --entity.object:setacceleration(entity.dynamic_data.movement.direction) end --TODO update animation --open a door [alpha] if entity.dynamic_data.movement.direction ~= nil then if entity.dynamic_data.movement.door_timer > 2 then local is_a_door = minetest.get_node({x=entity.object:getpos().x + entity.dynamic_data.movement.direction.x, y=entity.object:getpos().y,z=entity.object:getpos(). z + entity.dynamic_data.movement.direction.z}).name if is_a_door == "doors:door_wood_t_1" then minetest.punch_node({x=entity.object:getpos().x + entity.dynamic_data.movement.direction.x, y=entity.object:getpos().y-1, z=entity.object:getpos().z + entity.dynamic_data.movement.direction.z}) entity.dynamic_data.movement.door_timer = 0 end local is_in_door = minetest.get_node(entity.object:getpos()).name if is_in_door == "doors:door_wood_t_1" then minetest.punch_node(entity.object:getpos()) end end end --jump if entity.dynamic_data.movement.direction ~= nil then if entity.dynamic_data.movement.jump_timer > 0.3 then if minetest.registered_nodes[minetest.get_node({x=entity.object:getpos().x + entity.dynamic_data.movement.direction.x, y=entity.object:getpos().y-1, z=entity.object:getpos().z + entity.dynamic_data.movement.direction.z}).name].walkable then entity.object:setvelocity({x=entity.object:getvelocity().x,y=5,z=entity.object:getvelocity().z}) entity.dynamic_data.movement.jump_timer = 0 end end end end ------------------------------------------------------------------------------- -- name: initialize() -- --! @brief initialize movement generator --! @memberof mgen_jordan4ibanez --! @public ------------------------------------------------------------------------------- function mgen_jordan4ibanez.initialize(entity,now) --intentionaly doing nothing this function is for symmetry reasons only end ------------------------------------------------------------------------------- -- name: init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by movement generator --! @memberof mgen_jordan4ibanez --! @public -- --! @param entity mob to initialize dynamic data --! @param now current time ------------------------------------------------------------------------------- function mgen_jordan4ibanez.init_dynamic_data(entity,now) local data = { timer = 0, turn_timer = 0, jump_timer = 0, door_timer = 0, direction = nil, yaw = nil, } entity.dynamic_data.movement = data end ------------------------------------------------------------------------------- -- name: set_target(entity, target, follow_speedup, max_distance) -- --! @brief set target for movgen --! @memberof mgen_jordan4ibanez -- --! @param entity mob to apply to --unused here --! @param target to set --unused here --! @param follow_speedup --unused here --! @param max_distance --unused here ------------------------------------------------------------------------------- function mgen_jordan4ibanez.set_target(entity,target) return false end --register this movement generator registerMovementGen(mgen_jordan4ibanez.name,mgen_jordan4ibanez)mobf_core-2.5.1/mobf/mgen_pathbased/000077500000000000000000000000001264104133000173305ustar00rootroot00000000000000mobf_core-2.5.1/mobf/mgen_pathbased/main.lua000066400000000000000000000262711264104133000207670ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file path_based_movement_gen.lua --! @brief component containing a path based movement generator --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup mgen_path_based MGEN: Path based movement generator --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @class p_mov_gen --! @brief a movement generator evaluating a path to a target and following it --!@} p_mov_gen = {} p_mov_gen.max_waypoint_distance = 0.5 --! @brief movement generator identifier --! @memberof p_mov_gen p_mov_gen.name = "mgen_path" ------------------------------------------------------------------------------- -- name: callback(entity,now) -- --! @brief path based movement generator callback --! @memberof p_mov_gen -- -- param1: mob to do movement -- param2: current time -- retval: - ------------------------------------------------------------------------------- function p_mov_gen.callback(entity,now,dstep) mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(entity.dynamic_data ~= nil) mobf_assert_backtrace(entity.dynamic_data.p_movement ~= nil) if entity.dynamic_data.p_movement.eta ~= nil then if now < entity.dynamic_data.p_movement.eta then return end end local current_pos = entity.object:getpos() local handled = false if entity.dynamic_data.p_movement.path == nil then dbg_mobf.path_mov_lvl1( "MOBF: path movement but mo path set!!") return end local max_distance = p_mov_gen.max_waypoint_distance mobf_assert_backtrace(entity.dynamic_data.p_movement.next_path_index ~= nil) mobf_assert_backtrace(max_distance ~= nil) --check if target is reached if p_mov_gen.distance_to_next_point(entity,current_pos) < max_distance then dbg_mobf.path_mov_lvl1("MOBF: pathmov next to next point switching target") local update_target = true if entity.dynamic_data.p_movement.waypoint_stop then entity.object:setvelocity({x=0,y=0,z=0}) end --return to begining of path if entity.dynamic_data.p_movement.next_path_index == #entity.dynamic_data.p_movement.path then if entity.dynamic_data.p_movement.cycle_path or (entity.dynamic_data.p_movement.cycle_path == nil and entity.data.patrol ~= nil and entity.data.patrol.cycle_path) then --0 is correct as it's incremented by one later entity.dynamic_data.p_movement.next_path_index = 0 else if entity.dynamic_data.p_movement.HANDLER_end_of_path ~= nil and type(entity.dynamic_data.p_movement.HANDLER_end_of_path) == "function" then entity.dynamic_data.p_movement.HANDLER_end_of_path(entity) end dbg_mobf.path_mov_lvl1("MOBF: cycle not set not updating point") update_target = false handled = true end end if update_target then mobf_assert_backtrace(entity.dynamic_data.p_movement.path ~= nil) entity.dynamic_data.p_movement.next_path_index = entity.dynamic_data.p_movement.next_path_index + 1 entity.dynamic_data.movement.target = entity.dynamic_data.p_movement.path [entity.dynamic_data.p_movement.next_path_index] dbg_mobf.path_mov_lvl1("MOBF: (1) setting new target to index: " .. entity.dynamic_data.p_movement.next_path_index .. " pos: " .. printpos(entity.dynamic_data.movement.target)) handled = true end end if not handled and entity.dynamic_data.movement.target == nil then mobf_assert_backtrace(entity.dynamic_data.p_movement.path ~= nil) entity.dynamic_data.movement.target = entity.dynamic_data.p_movement.path [entity.dynamic_data.p_movement.next_path_index] dbg_mobf.path_mov_lvl1("MOBF: (2) setting new target to index: " .. entity.dynamic_data.p_movement.next_path_index .. " pos: " .. printpos(entity.dynamic_data.movement.target)) end mgen_follow.callback(entity,now) end ------------------------------------------------------------------------------- -- name: distance_to_next_point(entity) -- --! @brief get distance to next target point (2d only) --! @memberof p_mov_gen --! @private -- --! @param entity mob to check --! @param current_pos position mob is atm -- --! @retval distance ------------------------------------------------------------------------------- function p_mov_gen.distance_to_next_point(entity,current_pos) local index = entity.dynamic_data.p_movement.next_path_index mobf_assert_backtrace(entity.dynamic_data.p_movement.path ~= nil) mobf_assert_backtrace(index <= #entity.dynamic_data.p_movement.path) return mobf_calc_distance_2d(current_pos, entity.dynamic_data.p_movement.path[index]) end ------------------------------------------------------------------------------- -- name: init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by movement generator --! @memberof p_mov_gen -- --! @param entity to initialize --! @param now current time --! @param restored_data data restored on activate ------------------------------------------------------------------------------- function p_mov_gen.init_dynamic_data(entity,now,restored_data) local pos = entity.object:getpos() local data = { path = nil, eta = nil, last_move_stop = now, next_path_index = 1, force_target = nil, pathowner = nil, pathname = nil, waypoint_stop = true, } if restored_data ~= nil and type(restored_data) == "table" then dbg_mobf.path_mov_lvl3( "MOBF: path movement reading stored data: " .. dump(restored_data)) if restored_data.pathowner ~= nil and restored_data.pathname ~= nil then data.pathowner = restored_data.pathowner data.pathname = restored_data.pathname data.path = mobf_path.getpoints(data.pathowner,data.pathname) dbg_mobf.path_mov_lvl3( "MOBF: path movement restored points: " .. dump(data.path)) end if restored_data.pathindex ~= nil and type(restored_data.pathindex) == "number" and restored_data.pathindex > 0 and data.path ~= nil and restored_data.pathindex < #data.path then data.next_path_index = restored_data.pathindex end end entity.dynamic_data.p_movement = data mgen_follow.init_dynamic_data(entity,now) entity.dynamic_data.movement.follow_speedup = false end ------------------------------------------------------------------------------- -- name: set_path(entity,path) -- --! @brief set target for movgen --! @memberof p_mov_gen --! @public -- --! @param entity mob to apply to --! @param path to set --! @param enable_speedup shall follow speedup be applied to path movement? ------------------------------------------------------------------------------- function p_mov_gen.set_path(entity,path, enable_speedup) mobf_assert_backtrace(entity.dynamic_data.p_movement ~= nil) if path ~= nil then entity.dynamic_data.p_movement.next_path_index = 1 entity.dynamic_data.movement.max_distance = p_mov_gen.max_waypoint_distance entity.dynamic_data.p_movement.path = path --a valid path has at least 2 positions mobf_assert_backtrace(#entity.dynamic_data.p_movement.path > 1) entity.dynamic_data.movement.target = entity.dynamic_data.p_movement.path[2] entity.dynamic_data.movement.follow_speedup = enable_speedup return true else entity.dynamic_data.p_movement.next_path_index = nil entity.dynamic_data.movement.max_distance = nil entity.dynamic_data.p_movement.path = nil entity.dynamic_data.movement.target = nil entity.dynamic_data.movement.follow_speedup = nil return false end end ------------------------------------------------------------------------------- -- name: set_cycle_path(entity,value) -- --! @brief set state of path cycle mechanism --! @memberof p_mov_gen --! @public -- --! @param entity mob to apply to --! @param value to set true/false/nil(mob global default) ------------------------------------------------------------------------------- function p_mov_gen.set_cycle_path(entity,value) mobf_assert_backtrace(entity.dynamic_data.p_movement ~= nil) entity.dynamic_data.p_movement.cycle_path = value end ------------------------------------------------------------------------------- -- name: set_end_of_path_handler(entity,handler) -- --! @brief set handler to call for non cyclic paths if final target is reached --! @memberof p_mov_gen --! @public -- --! @param entity mob to apply to --! @param handler to call at final target ------------------------------------------------------------------------------- function p_mov_gen.set_end_of_path_handler(entity,handler) entity.dynamic_data.p_movement.HANDLER_end_of_path = handler end ------------------------------------------------------------------------------- -- name: set_target(entity, target, follow_speedup, max_distance) -- --! @brief set target for movgen --! @memberof p_mov_gen --! @public -- --! @param entity mob to apply to --! @param target to set --! @param follow_speedup use follow speedup to reach target --! @param max_distance --unused here ------------------------------------------------------------------------------- function p_mov_gen.set_target(entity, target, follow_speedup, max_distance) mobf_assert_backtrace(target ~= nil) local current_pos = entity.getbasepos(entity) local targetpos = nil if not mobf_is_pos(target) then if target:is_player() then targetpos = target:getpos() targetpos.y = targetpos.y +0.5 else if type(target.getbasepos) == "function" then targetpos = target.getbasepos(target) else targetpos = target:getpos() end end else targetpos = target end if targetpos == nil then return false end if entity.dynamic_data.p_movement.lasttargetpos ~= nil then if mobf_pos_is_same(entity.dynamic_data.p_movement.lasttargetpos, targetpos) then return true end end entity.dynamic_data.p_movement.lasttargetpos = targetpos entity.dynamic_data.p_movement.path = nil entity.dynamic_data.p_movement.next_path_index = 1 --on target mode max distance is always 0.5 entity.dynamic_data.movement.max_distance = p_mov_gen.max_waypoint_distance --try to find path on our own if not mobf_get_world_setting("mobf_disable_pathfinding") then entity.dynamic_data.p_movement.path = mobf_path.find_path(current_pos,targetpos,5,1,1,nil) else entity.dynamic_data.p_movement.path = nil end if entity.dynamic_data.p_movement.path ~= nil then --a valid path has at least 2 positions mobf_assert_backtrace(#entity.dynamic_data.p_movement.path > 1) entity.dynamic_data.movement.target = entity.dynamic_data.p_movement.path[2] entity.dynamic_data.movement.follow_speedup = follow_speedup return true end if entity.dynamic_data.p_movement.path == nil then minetest.log(LOGLEVEL_INFO, "MOBF: no pathfinding support/ no path found directly setting targetpos as path") entity.dynamic_data.p_movement.path = {} table.insert(entity.dynamic_data.p_movement.path,targetpos) entity.dynamic_data.movement.target = entity.dynamic_data.p_movement.path[1] entity.dynamic_data.movement.follow_speedup = follow_speedup return true end return false end --register this movement generator registerMovementGen(p_mov_gen.name,p_mov_gen) mobf_core-2.5.1/mobf/mgen_probab/000077500000000000000000000000001264104133000166425ustar00rootroot00000000000000mobf_core-2.5.1/mobf/mgen_probab/direction_control.lua000066400000000000000000000445361264104133000231010ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file direction_control.lua --! @brief functions for direction control in probabilistic movement gen --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @ingroup mgen_probab --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @class direction_control --! @brief functions for direction control in probabilistic movement gen direction_control = {} --!@} ------------------------------------------------------------------------------- -- name: changeaccel(pos,entity,velocity) -- --! @brief find a suitable new acceleration for mob --! @memberof direction_control --! @private -- --! @param pos current position --! @param entity mob to get acceleration for --! @param current_velocity current velocity --! @return {{ x/y/z accel} + jump flag really? ------------------------------------------------------------------------------- function direction_control.changeaccel(pos,entity,current_velocity) local maxtries = 5 local old_quality = environment.pos_quality(pos,entity) local new_accel = direction_control.get_random_acceleration( entity.data.movement.min_accel, entity.data.movement.max_accel,graphics.getyaw(entity),0) local pos_predicted = movement_generic.predict_next_block(pos,current_velocity,new_accel) local new_quality = environment.pos_quality(pos_predicted,entity) local prefered_state = environment.evaluate_state( new_quality, old_quality, MQ_IN_MEDIA, nil, GQ_FULL, SQ_POSSIBLE, SQ_OK) while not prefered_state do dbg_mobf.pmovement_lvl1("MOBF: predicted pos " .. printpos(pos_predicted) .. " isn't perfect " .. maxtries .. " tries left, state: " .. new_quality.tostring(new_quality)) --don't loop forever get to save mode and try next time if maxtries <= 0 then dbg_mobf.pmovement_lvl1( "MOBF: Aborting acceleration finding for this cycle due to max retries") if state == "collision_jumpable" then dbg_mobf.movement_lvl1("Returning " ..printpos(new_accel).." as new accel as mob may jump") return new_accel end dbg_mobf.pmovement_lvl1( "MOBF: Didn't find a suitable acceleration stopping movement: " .. entity.data.name .. printpos(pos)) entity.object:setvelocity({x=0,y=0,z=0}) entity.dynamic_data.movement.started = false --don't slow down mob return { x=0, y=0, z=0 } end local probab = math.random() --accept possible surface in rare cases if probab < 0.3 then local acceptable_state = environment.evaluate_state(new_quality, nil, MQ_IN_MEDIA, GQ_PARTIAL, nil, SQ_WRONG, SQ_POSSIBLE) if acceptable_state then return new_accel end end --accept possible surface in rare cases if probab < 0.2 then local acceptable_state = environment.evaluate_state(new_quality, nil, MQ_IN_MEDIA, nil, GQ_FULL, SQ_WRONG, SQ_POSSIBLE) if acceptable_state then return new_accel end end --try another acceleration new_accel = direction_control.get_random_acceleration( entity.data.movement.min_accel, entity.data.movement.max_accel, graphics.getyaw(entity),1.57) pos_predicted = movement_generic.predict_next_block(pos,current_velocity,new_accel) local prefered_state = environment.evaluate_state( new_quality, old_quality, MQ_IN_MEDIA, nil, GQ_FULL, SQ_POSSIBLE, SQ_OK) maxtries = maxtries -1 end return new_accel end ------------------------------------------------------------------------------- -- name: get_random_acceleration(minaccel,maxaccel,current_yaw, minrotation) -- --! @brief get a random x/z acceleration within a specified acceleration range --! @memberof direction_control --! @private -- --! @param minaccel minimum acceleration to use --! @param maxaccel maximum acceleration --! @param current_yaw current orientation of mob --! @param minrotation minimum rotation to perform --! @return x/y/z acceleration ------------------------------------------------------------------------------- function direction_control.get_random_acceleration( minaccel,maxaccel,current_yaw, minrotation) local direction = 1 if math.random() < 0.5 then direction = -1 end --calc random absolute value local rand_accel = (math.random() * (maxaccel - minaccel)) + minaccel local orientation_delta = mobf_gauss(math.pi/6,1/2) --calculate new acceleration local new_direction = current_yaw + ((minrotation + orientation_delta) * direction) local new_accel = mobf_calc_vector_components(new_direction,rand_accel) dbg_mobf.pmovement_lvl3(" new direction: " .. new_direction .. " old direction: " .. current_yaw .. " new accel: " .. printpos(new_accel) .. " orientation_delta: " .. orientation_delta) return new_accel end ------------------------------------------------------------------------------- -- name: precheck_movement(entity,movement_state,pos_predicted,pos_predicted_quality) -- --! @brief check if x/z movement results in invalid position and change -- movement if required --! @memberof direction_control -- --! @param entity mob to generate movement --! @param movement_state current state of movement --! @param pos_predicted position mob will be next --! @param pos_predicted_quality quality of predicted position --! @return movement_state is changed! ------------------------------------------------------------------------------- function direction_control.precheck_movement( entity,movement_state,pos_predicted,pos_predicted_quality) if movement_state.changed then --someone already changed something return end local prefered_quality = environment.evaluate_state(pos_predicted_quality, LT_GOOD_POS) -- ok predicted pos isn't as good as we'd wanted it to be let's find out why if not prefered_quality then local mob_is_safe = environment.evaluate_state(pos_predicted_quality, LT_SAFE_POS) if movement_state.current_quality == nil then movement_state.current_quality = environment.pos_quality( movement_state.basepos, entity ) end if environment.compare_state( movement_state.current_quality, pos_predicted_quality) > 0 and pos_predicted_quality.media_quality == MQ_IN_MEDIA then --movement state is better than old one so we're fine return end local walking_at_edge = environment.evaluate_state(pos_predicted_quality, LT_SAFE_EDGE_POS) if walking_at_edge then --mob center still on ground but at worst walking at edge, do nothing return end if (pos_predicted_quality.geometry_quality == GQ_NONE) then dbg_mobf.pmovement_lvl2("MOBF: mob " .. entity.data.name .. " is dropping") return end local drop_pending = (pos_predicted_quality.geometry_quality <= GQ_PARTIAL and pos_predicted_quality.center_geometry_quality <= GQ_NONE) or pos_predicted_quality.surface_quality_min <= SQ_WATER if drop_pending then dbg_mobf.pmovement_lvl2( "MOBF: mob " .. entity.data.name .. " is going to walk on water or drop") local new_pos = environment.get_pos_same_level(movement_state.basepos,1,entity, function(quality) return environment.evaluate_state(quality,LT_SAFE_EDGE_POS) end ) if new_pos == nil then dbg_mobf.pmovement_lvl2( "MOBF: mob " .. entity.data.name .. " trying edge pos") new_pos = environment.get_pos_same_level(movement_state.basepos,1,entity, function(quality) return environment.evaluate_state(quality,LT_EDGE_POS) end ) end if new_pos == nil then dbg_mobf.pmovement_lvl2( "MOBF: mob " .. entity.data.name .. " trying relaxed surface") new_pos = environment.get_pos_same_level(movement_state.basepos,1,entity, function(quality) return environment.evaluate_state(quality, LT_EDGE_POS_GOOD_CENTER) end ) end if new_pos == nil then dbg_mobf.pmovement_lvl2( "MOBF: mob " .. entity.data.name .. " trying even more relaxed surface") new_pos = environment.get_pos_same_level(movement_state.basepos,1,entity, function(quality) return environment.evaluate_state(quality, LT_EDGE_POS_POSSIBLE_CENTER) end ) end if new_pos ~= nil then dbg_mobf.pmovement_lvl2( "MOBF: trying to redirect to safe position .. " .. printpos(new_pos)) local speedfactor = 0.1 local speed_found = false repeat movement_state.accel_to_set = movement_generic.get_accel_to(new_pos, entity, nil, entity.data.movement.max_accel*speedfactor) local next_pos = movement_generic.predict_next_block( movement_state.basepos, movement_state.current_velocity, movement_state.accel_to_set) local next_quality = environment.pos_quality( next_pos, entity ) if environment.evaluate_state(next_quality, LT_EDGE_POS_POSSIBLE_CENTER) then speed_found = true end speedfactor = speedfactor +0.1 until ( speedfactor > 1 or speed_found) -- try if our state would at least keep same if we walk towards -- the good pos if not speed_found then dbg_mobf.pmovement_lvl2("MOBF: trying min speed towards good pos") movement_state.accel_to_set = movement_generic.get_accel_to(new_pos, entity, nil, entity.data.movement.min_accel) local next_pos = movement_generic.predict_next_block( movement_state.basepos, movement_state.current_velocity, movement_state.accel_to_set) local next_quality = environment.pos_quality( next_pos, entity ) if ((mobf_calc_distance(next_pos,new_pos) < (mobf_calc_distance(movement_state.basepos,new_pos))) and environment.evaluate_state(next_quality, LT_DROP_PENDING)) then speed_found = true end end if speed_found then dbg_mobf.pmovement_lvl2("MOBF: redirecting to safe position .. " .. printpos(new_pos)) movement_state.changed = true return end end if new_pos == nil then dbg_mobf.pmovement_lvl2("MOBF: no suitable pos found") else dbg_mobf.pmovement_lvl2("MOBF: didn't find a way to suitable pos") end --no suitable pos found, if mob is safe atm just stop it if mob_is_safe then if movement_state.current_quality == GQ_FULL then local targetpos = {x= movement_state.basepos.x, y=movement_state.basepos.y, z=movement_state.basepos.z} targetpos.x = targetpos.x - movement_state.current_velocity.x targetpos.z = targetpos.z - movement_state.current_velocity.z movement_state.accel_to_set = movement_generic.get_accel_to(targetpos, entity, nil, entity.data.movement.min_accel) dbg_mobf.pmovement_lvl2( "MOBF: good pos, slowing down") movement_state.changed = true return else --stop immediatlely entity.object:setvelocity({x=0,y=0,z=0}) movement_state.accel_to_set = {x=0,y=nil,z=0} dbg_mobf.pmovement_lvl2( "MOBF: stopping at safe pos") movement_state.changed = true return end end dbg_mobf.pmovement_lvl2("MOBF: mob " .. entity.data.name .. " didn't find a way to fix drop trying random") --make mgen change direction randomly movement_state.force_change = true return end --check if mob is going to be somewhere where it can't be if pos_predicted_quality.media_quality ~= MQ_IN_MEDIA then dbg_mobf.pmovement_lvl2("MOBF: collision pending " .. printpos(movement_state.basepos) .. "-->" .. printpos(pos_predicted)) --try to find a better position at same level local new_pos = environment.get_suitable_pos_same_level(movement_state.basepos,1,entity) if new_pos == nil then new_pos = environment.get_suitable_pos_same_level( movement_state.basepos,1,entity,true) end --there is at least one direction to go if new_pos ~= nil then dbg_mobf.pmovement_lvl2("MOBF: mob " ..entity.data.name .. " redirecting to:" .. printpos(new_pos)) local new_predicted_state = nil local new_predicted_pos = nil for i=1,5,1 do movement_state.accel_to_set = movement_generic.get_accel_to(new_pos,entity) --TODO check if acceleration is enough new_predicted_pos = movement_generic.predict_enter_next_block( entity, movement_state.basepos, movement_state.current_velocity, movement_state.accel_to_set) new_predicted_state = environment.pos_quality( new_predicted_pos, entity ) if new_predicted_state.media_quality == MQ_IN_MEDIA then break end end if new_predicted_state.media_quality ~= MQ_IN_MEDIA then movement_state.accel_to_set = movement_state.current_acceleration dbg_mobf.pmovement_lvl2("MOBF: mob " ..entity.data.name .. " acceleration not enough to avoid collision try to jump") if math.random() < ( entity.dynamic_data.movement.mpattern.jump_up * PER_SECOND_CORRECTION_FACTOR) then local upper_pos = { x= pos_predicted.x, y= pos_predicted.y +1, z= pos_predicted.z } local upper_quality = environment.pos_quality( upper_pos, entity ) if environment.evaluate_state( upper_quality,LT_EDGE_POS) then entity.object:setvelocity( {x=movement_state.current_velocity.x, y=5, z=movement_state.current_velocity.z}) end end end movement_state.changed = true return end --try to find a better position above new_pos = environment.get_suitable_pos_same_level({ x=movement_state.basepos.x, y=movement_state.basepos.y+1, z=movement_state.basepos.z}, 1,entity) if new_pos == nil then new_pos = environment.get_suitable_pos_same_level({ x=movement_state.basepos.x, y=movement_state.basepos.y+1, z=movement_state.basepos.z}, 1,entity) end if new_pos ~= nil then dbg_mobf.pmovement_lvl2("MOBF: mob " ..entity.data.name .. " seems to be locked in, jumping to:" .. printpos(new_pos)) entity.object:setvelocity({x=0, y=5.5, z=0}) movement_state.accel_to_set = movement_generic.get_accel_to(new_pos,entity) movement_state.changed = true return end dbg_mobf.pmovement_lvl2("MOBF: mob " ..entity.data.name .. " unable to fix collision try random") --a collision is going to happen force change of direction movement_state.force_change = true return end local suboptimal_surface = environment.evaluate_state( pos_predicted_quality, { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=GQ_PARTIAL, min_geom_center=nil, min_min_surface=SQ_WRONG, min_max_surface=SQ_POSSIBLE, min_center_surface=nil }) if suboptimal_surface then dbg_mobf.pmovement_lvl2( "MOBF: suboptimal positiond detected trying to find better pos") --try to find a better position at same level local new_pos = environment.get_suitable_pos_same_level( movement_state.basepos,1,entity) if new_pos ~= nil then dbg_mobf.pmovement_lvl2( "MOBF: redirecting to better position .. " .. printpos(new_pos)) movement_state.accel_to_set = movement_generic.get_accel_to(new_pos,entity) movement_state.changed = true return else -- pos isn't critical don't do anything return end end local geom_ok = environment.evaluate_state( pos_predicted_quality, { old_state=nil, min_media=MQ_IN_MEDIA, min_geom=GQ_PARTIAL, min_geom_center=nil, min_min_surface=nil, min_max_surface=nil, min_center_surface=nil }) if geom_ok and pos_predicted_quality.surface_quality_max == SQ_WRONG then dbg_mobf.pmovement_lvl2( "MOBF: wrong surface detected trying to find better pos") local new_pos = environment.get_suitable_pos_same_level( movement_state.basepos,1,entity) if new_pos == nil then new_pos = environment.get_suitable_pos_same_level( movement_state.basepos,2,entity) end if new_pos ~= nil then dbg_mobf.pmovement_lvl2( "MOBF: redirecting to better position .. " .. printpos(new_pos)) movement_state.accel_to_set = movement_generic.get_accel_to(new_pos,entity) movement_state.changed = true return else --try generic movement_state.force_change = true return end end dbg_mobf.pmovement_lvl2("MOBF: Unhandled suboptimal state:" .. pos_predicted_quality.tostring(pos_predicted_quality)) movement_state.force_change = true end end ------------------------------------------------------------------------------- -- name: random_movement_handler(entity,movement_state) -- --! @brief generate a random y-movement --! @memberof direction_control -- --! @param entity mob to apply random jump --! @param movement_state current movement state --! @return movement_state is modified! ------------------------------------------------------------------------------- function direction_control.random_movement_handler(entity,movement_state) dbg_mobf.pmovement_lvl3("MOBF: random movement handler called") local rand_value = math.random() local max_value = entity.dynamic_data.movement.mpattern.random_acceleration_change * PER_SECOND_CORRECTION_FACTOR if movement_state.changed == false and (rand_value < max_value or movement_state.force_change) then movement_state.accel_to_set = direction_control.changeaccel(movement_state.basepos, entity,movement_state.current_velocity) if movement_state.accel_to_set ~= nil then --retain current y acceleration movement_state.accel_to_set.y = movement_state.current_acceleration.y movement_state.changed = true end dbg_mobf.pmovement_lvl1("MOBF: randomly changing speed from ".. printpos(movement_state.current_acceleration).." to ".. printpos(movement_state.accel_to_set)) else dbg_mobf.pmovement_lvl3("MOBF:" .. entity.data.name .. " not changing speed random: " .. rand_value .." >= " .. max_value) end endmobf_core-2.5.1/mobf/mgen_probab/height_level_control.lua000066400000000000000000000251161264104133000235510ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file height_level_control.lua --! @brief component containing random drop features --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @ingroup mgen_probab --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @class height_level_control --! @brief Subcomponent of probabilistic movement generator containing height --! level control and change functionality height_level_control = {} --!@} ------------------------------------------------------------------------------- -- name: calc_level_change_time(entity) -- --! @brief calculate time required to change one height level --! @memberof height_level_control --! @private -- --! @param entity mob to calculate change time --! @param default_accel default accel for mob --! @return time in seconds ------------------------------------------------------------------------------- function height_level_control.calc_level_change_time(entity,default_accel) local retval = 1 --default value --calculate a reasonable value to stop level change if entity.data.movement.canfly == nil or entity.data.movement.canfly == false then --case mob can't fly return 0 else -- if it's a flying mob and left it's normal medium if default_accel ~= 0 then retval = 0 else retval = math.sqrt(2/entity.data.movement.min_accel) end end return retval end ------------------------------------------------------------------------------- -- name: precheck_movement(entity,movement_state,pos_predicted,pos_predicted_quality) -- --! @brief check if there is a level change in progress that may -- need to be stopped --! @memberof height_level_control -- --! @param entity mob to check for level change --! @param movement_state current state of movement --! @param pos_predicted position the mob will be next --! @param pos_predicted_quality quality of the next position ------------------------------------------------------------------------------- function height_level_control.precheck_movement(entity,movement_state,pos_predicted,pos_predicted_quality) if entity.data.movement.canfly ~= nil and entity.data.movement.canfly == true and entity.dynamic_data.movement.changing_levels == true then local level_change_time = height_level_control.calc_level_change_time(entity,movement_state.default_y_accel) local time_completed = entity.dynamic_data.movement.ts_random_jump + level_change_time dbg_mobf.pmovement_lvl1("MOBF: ".. movement_state.now .. " " .. entity.data.name .. " check complete level change " .. time_completed) if entity.dynamic_data.movement.changing_levels and time_completed < movement_state.now then dbg_mobf.pmovement_lvl2("MOBF: ".. movement_state.now .. " " .. entity.data.name .. " level change complete reestablishing default y acceleration " .. movement_state.default_y_accel) entity.dynamic_data.movement.changing_levels = false movement_state.current_velocity.y = 0 entity.object:setvelocity(movement_state.current_velocity) movement_state.accel_to_set = movement_state.current_acceleration movement_state.accel_to_set.y = movement_state.default_y_accel movement_state.changed = true end end --mob would fly/swim into height it shouldn't be if movement_state.changed == false then local invalid_pos_handled = false --mob would fly/swim into height it shouldn't be if invalid_pos_handled == false and pos_predicted_quality.level_quality == LQ_ABOVE then local min_y,max_y = environment.get_absolute_min_max_pos(entity.environment,movement_state.basepos) if (pos_predicted.y - max_y) > 10 then movement_state.override_height_change_chance = 1 else movement_state.override_height_change_chance = (pos_predicted.y - max_y)/10 end invalid_pos_handled = true end --mob would fly/swim into height it shouldn't be if invalid_pos_handled == false and pos_predicted_quality.level_quality == LQ_BELOW then local min_y,max_y = environment.get_absolute_min_max_pos(entity.environment,movement_state.basepos) if (min_y - pos_predicted.y) > 10 then movement_state.override_height_change_chance = 1 else movement_state.override_height_change_chance = (min_y - pos_predicted.y)/10 end end end end ------------------------------------------------------------------------------- -- name: random_jump_fly(entity,movement_state) -- --! @brief apply random jump for flying mobs --! @memberof height_level_control --! @private -- --! @param entity mob to apply random jump --! @param movement_state current movement state --! @return movement_state is modified! ------------------------------------------------------------------------------- function height_level_control.random_jump_fly(entity,movement_state) --get some information local accel_to_set = movement_state.current_acceleration local current_state = environment.pos_is_ok(movement_state.basepos,entity) --find direction local ydirection = 1 if current_state == "ok" then if math.random() <= 0.5 then ydirection = -1 end end if current_state == "above_limit" then ydirection = -1 end --state "below_limit" is already handled by initialization value --prepare basic information about what may be afterwards local targetpos = {x=movement_state.basepos.x,y=movement_state.basepos.y+ydirection,z=movement_state.basepos.z} local target_state = environment.pos_is_ok(targetpos,entity); dbg_mobf.pmovement_lvl2("MOBF: " .. entity.data.name .." flying change y accel dir="..ydirection.." ypos="..movement_state.basepos.y.. " min="..entity.environment.min_height_above_ground.. " max="..entity.environment.max_height_above_ground.. " below="..target_state) --really do level change if (target_state == "ok") or target_state == current_state then local min_y, max_y = environment.get_absolute_min_max_pos(entity.environment,movement_state.basepos) dbg_mobf.pmovement_lvl2("MOBF: check level borders current=".. movement_state.basepos.y .. " min=" .. min_y .. " max=" .. max_y) movement_state.accel_to_set.y = ydirection * entity.data.movement.min_accel entity.dynamic_data.movement.ts_random_jump = movement_state.now entity.dynamic_data.movement.changing_levels = true dbg_mobf.pmovement_lvl2("MOBF: " .. entity.data.name .. " " .. movement_state.now .. " " .. " changing level within borders: " .. movement_state.accel_to_set.y) movement_state.changed = true end end ------------------------------------------------------------------------------- -- name: random_jump_ground(entity,movement_state) -- --! @brief apply random jump for ground mobs --! @memberof height_level_control --! @private -- --! @param entity mob to apply random jump --! @param movement_state current movement state --! @return movement_state is a modification! ------------------------------------------------------------------------------- function height_level_control.random_jump_ground(entity,movement_state) if movement_state.default_y_accel == 0 then minetest.log(LOGLEVEL_ERROR,"MOBF BUG!!! ground mob can't have zero default acceleration!!!") end local random_jump_time = 2 * (entity.dynamic_data.movement.mpattern.random_jump_initial_speed / math.abs(movement_state.default_y_accel)) local next_jump = entity.dynamic_data.movement.ts_random_jump + entity.dynamic_data.movement.mpattern.random_jump_delay + random_jump_time dbg_mobf.movement_lvl2("MOBF: now=" .. movement_state.now .. " time of next jump=" .. next_jump .. " | " .. random_jump_time .. " " .. entity.dynamic_data.movement.mpattern.random_jump_delay .. " " .. entity.dynamic_data.movement.ts_random_jump .. " " .. movement_state.default_y_accel .. " " .. entity.dynamic_data.movement.mpattern.random_jump_initial_speed) if movement_state.now > next_jump then local ground_distance = mobf_ground_distance(movement_state.basepos) --we shouldn't be moving in y direction at that time so probably we are just dropping --we can only jump while on solid ground! if ground_distance <= 1 then local current_speed = entity.object:getvelocity(); dbg_mobf.pmovement_lvl2("MOBF: " .. entity.data.name .." doing random jump ".. "speed: " .. entity.dynamic_data.movement.mpattern.random_jump_initial_speed .. ": ",entity) current_speed.y = entity.dynamic_data.movement.mpattern.random_jump_initial_speed entity.object:setvelocity(current_speed) entity.dynamic_data.movement.ts_random_jump = movement_state.now else dbg_mobf.pmovement_lvl2("MOBF: " .. entity.data.name .. " Random jump but ground distance was:" .. ground_distance .. " " ..movement_state.current_acceleration.y .. " " ..movement_state.default_y_accel) --this is a workaround for a bug --setting y acceleration to 0 for ground born mobs shouldn't be possible if movement_state.current_acceleration.y == 0 then movement_state.accel_to_set.x = movement_state.current_acceleration.x movement_state.accel_to_set.y = movement_state.default_y_accel movement_state.accel_to_set.z = movement_state.current_acceleration.z movement_state.changed = true end end end end ------------------------------------------------------------------------------- -- name: random_movement_handler(entity,movement_state) -- --! @brief generate a random y-movement --! @memberof height_level_control -- --! @param entity mob to apply random jump --! @param movement_state current movement state --! @return movement_state is a modified! ------------------------------------------------------------------------------- function height_level_control.random_movement_handler(entity,movement_state) --generate random y movement changes if movement_state.changed == false and entity.dynamic_data.movement.changing_levels == false and (math.random() < entity.dynamic_data.movement.mpattern.random_jump_chance or math.random() < movement_state.override_height_change_chance) then dbg_mobf.pmovement_lvl1("MOBF: Random jump chance for mob " .. entity.data.name .. " ".. entity.dynamic_data.movement.ts_random_jump .. " ".. entity.dynamic_data.movement.mpattern.random_jump_delay .. " " .. movement_state.now ) local to_set --flying/swiming mobs do level change on random jump chance if entity.data.movement.canfly then height_level_control.random_jump_fly(entity,movement_state) --ground mobs else height_level_control.random_jump_ground(entity,movement_state) end end end mobf_core-2.5.1/mobf/mgen_probab/main_probab.lua000066400000000000000000000551361264104133000216300ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file main_probab.lua --! @brief component containing a probabilistic movement generator --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- -- --! @defgroup mgen_probab MGEN: probabilistic movement generator --! @brief A movement generator trying to create a random movement --! @ingroup framework_int --! @{ -- --! @defgroup mpatterns Movement patterns --! @brief movement patterns used for probabilistic movement gen -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --factor used to adjust chances defined as x/per second to on_step frequency PER_SECOND_CORRECTION_FACTOR = 0.25 --! @class movement_gen --! @brief a probabilistic movement generator movement_gen = {} --! @brief movement patterns known to probabilistic movement gen mov_patterns_defined = {} --!@} dofile (mobf_modpath .. "/mgen_probab/movement_patterns/dont_move.lua") dofile (mobf_modpath .. "/mgen_probab/movement_patterns/run_and_jump_low.lua") dofile (mobf_modpath .. "/mgen_probab/movement_patterns/stop_and_go.lua") dofile (mobf_modpath .. "/mgen_probab/movement_patterns/swim_pattern1.lua") dofile (mobf_modpath .. "/mgen_probab/movement_patterns/swim_pattern2.lua") dofile (mobf_modpath .. "/mgen_probab/movement_patterns/flight_pattern1.lua") dofile (mobf_modpath .. "/mgen_probab/height_level_control.lua") dofile (mobf_modpath .. "/mgen_probab/direction_control.lua") --! @brief movement generator identifier --! @memberof movement_gen movement_gen.name = "probab_mov_gen" ------------------------------------------------------------------------------- -- name: register_pattern(pattern) -- --! @brief initialize movement generator --! @memberof movement_gen -- --! @param pattern pattern to register --! @return true/false ------------------------------------------------------------------------------- function movement_gen.register_pattern(pattern) core.log("action","\tregistering pattern "..pattern.name) if mobf_rtd.movement_patterns[pattern.name] == nil then mobf_rtd.movement_patterns[pattern.name] = pattern return true else return false end end ------------------------------------------------------------------------------- -- name: initialize(entity) -- --! @brief initialize movement generator --! @memberof movement_gen -- ------------------------------------------------------------------------------- function movement_gen.initialize() for index,value in ipairs(mov_patterns_defined) do movement_gen.register_pattern(value) end end ------------------------------------------------------------------------------- -- name: callback(entity) -- --! @brief main movement generation function --! @memberof movement_gen --! @private -- --! @param entity mob to generate movement for ------------------------------------------------------------------------------- function movement_gen.callback(entity) mobf_assert_backtrace(entity ~= nil) if entity.dynamic_data == nil or entity.dynamic_data.movement == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: >" ..entity.data.name .. "< removed=" .. dump(entity.removed) .. " entity=" .. tostring(entity) .. " probab movement callback") return end --initialize status datastructure local movement_state = { basepos = entity.getbasepos(entity), default_y_accel = nil, centerpos = entity.object:getpos(), current_acceleration = nil, current_velocity = entity.object:getvelocity(), now = nil, override_height_change_chance = 0, accel_to_set = nil, force_change = false, changed = false, } --------------------------------------------------------------------------- --------------------------------------------------------------------------- -- -- -- CHECK MOB POSITION -- -- -- --------------------------------------------------------------------------- --------------------------------------------------------------------------- dbg_mobf.pmovement_lvl3("MOBF: position check for mob ".. entity.data.name .. " "..printpos(movement_state.basepos)) movement_state.default_y_accel = environment.get_default_gravity(movement_state.basepos, entity.environment.media, entity.data.movement.canfly) mobf_assert_backtrace(movement_state.default_y_accel ~= nil) movement_state.now = mobf_get_current_time() --check current position --movement state is modified by this function local retval = movement_gen.fix_current_pos(entity, movement_state) --mob has been removed due to unfixable problems if retval.abort_processing then return end --read additional information required for further processing movement_state.current_acceleration = entity.object:getacceleration() if movement_state.changed then minetest.log(LOGLEVEL_WARNING, "MOBF:position got fixed ".. entity.data.name) end --------------------------------------------------------------------------- --------------------------------------------------------------------------- -- -- -- CHECK MOB MOVEMENT ITSELF -- -- -- --------------------------------------------------------------------------- --------------------------------------------------------------------------- dbg_mobf.pmovement_lvl3("MOBF: movement hard limits check for mob ".. entity.data.name .. " "..printpos(movement_state.basepos)) movement_gen.fix_runaway(entity,movement_state) --don't do slowness check each callback if entity.dynamic_data.movement.ts_last_slowcheck == nil or entity.dynamic_data.movement.ts_last_slowcheck +2 < movement_state.now then movement_gen.fix_to_slow(entity,movement_state) entity.dynamic_data.movement.ts_last_slowcheck = movement_state.now end --------------------------------------------------------------------------- --------------------------------------------------------------------------- -- -- -- DO / PREPARE CHANGES THAT NEED TO BE DONE BECAUSE OF MOB IS -- -- PREDICTED TO BE SOMEWHERE WHERE IT SHOULDN'T BE -- -- -- --------------------------------------------------------------------------- --------------------------------------------------------------------------- dbg_mobf.pmovement_lvl3("MOBF: movement check for mob ".. entity.data.name .. " "..printpos(movement_state.basepos)) --skip if movement already got changed if movement_state.changed == false then --predict next block mob will be local pos_predicted = movement_generic.predict_next_block( movement_state.basepos, movement_state.current_velocity, movement_state.current_acceleration) --if this isn't a flying mob ignore y prediction as it's not honoring --collisions if not entity.data.movement.canfly then pos_predicted.y = movement_state.basepos.y end --local pos_predicted = -- movement_generic.predict_enter_next_block(entity, movement_state.basepos, -- movement_state.current_velocity, -- movement_state.current_acceleration) local pos_predicted_quality = environment.pos_quality(pos_predicted,entity) dbg_mobf.pmovement_lvl3("MOBF: Pos pred quality: ".. entity.data.name .. ": " .. pos_predicted_quality:shortstring()) -- Y-Movement if movement_state.changed == false then height_level_control.precheck_movement(entity,movement_state, pos_predicted,pos_predicted_quality) end -- X/Z-Movement if movement_state.changed == false then direction_control.precheck_movement(entity,movement_state, pos_predicted,pos_predicted_quality) end end --------------------------------------------------------------------------- --------------------------------------------------------------------------- -- -- -- DO RANDOM CHANGES TO MOB MOVEMENT -- -- -- --------------------------------------------------------------------------- --------------------------------------------------------------------------- dbg_mobf.pmovement_lvl3("MOBF: randomized movement for mob ".. entity.data.name .. " "..printpos(movement_state.basepos)) --do randomized changes if not fighting height_level_control.random_movement_handler(entity,movement_state) direction_control.random_movement_handler(entity,movement_state) --------------------------------------------------------------------------- --------------------------------------------------------------------------- -- -- -- APPLY CHANGES CALCULATED BEFORE -- -- -- --------------------------------------------------------------------------- --------------------------------------------------------------------------- movement_gen.apply_movement_changes(entity,movement_state) end ------------------------------------------------------------------------------- -- name: apply_movement_changes(entity,movement_state) -- --! @brief check and apply the changes from movement_state --! @memberof movement_gen --! @private -- --! @param entity mob to apply changes --! @param movement_state changes to apply ------------------------------------------------------------------------------- function movement_gen.apply_movement_changes(entity,movement_state) if movement_state.changed then --check if there is a acceleration to set if movement_state.accel_to_set == nil then movement_state.accel_to_set = {x=0, y=movement_state.default_y_accel, z=0} entity.object:setvelocity({x=0,y=0,z=0}) minetest.log(LOGLEVEL_ERROR, "MOBF BUG!!!! set accel requested but no accel given!") end --check if gravity is set if movement_state.accel_to_set.y == nil then --print("fixing y movement: ".. printpos(movement_state.accel_to_set)) movement_state.accel_to_set.y = movement_state.current_acceleration.y end -- make sure non flying mobs have default acceleration if entity.data.movement.canfly == false then movement_state.accel_to_set.y = movement_state.default_y_accel if movement_state.default_y_accel == 0 then minetest.log(LOGLEVEL_ERROR,"MOBF BUG!!! " .. entity.data.name .. " mob that can't fly has acceleration 0!") end end dbg_mobf.pmovement_lvl2("MOBF: setting acceleration to " ..printpos(movement_state.accel_to_set)) -- todo check for harsh direction changes entity.dynamic_data.movement.acceleration = movement_state.accel_to_set entity.object:setacceleration(movement_state.accel_to_set) end end ------------------------------------------------------------------------------- -- name: init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by movement generator --! @memberof movement_gen -- --! @param entity mob to initialize --! @param now current time ------------------------------------------------------------------------------- function movement_gen.init_dynamic_data(entity,now) local accel_to_set = {x=0,y=9.81,z=0} local pos = entity.object:getpos() --initialize acceleration values accel_to_set.y = environment.get_default_gravity(pos, entity.environment.media, entity.data.movement.canfly) mobf_assert_backtrace(accel_to_set.y ~= nil) local data = { started = false, acceleration = accel_to_set, changing_levels = false, ts_random_jump = now, ts_orientation_upd = now, mpattern = mobf_rtd.movement_patterns[entity.data.movement.pattern], ts_last_slowcheck = now, } entity.dynamic_data.movement = data end ------------------------------------------------------------------------------- -- name: fix_runaway(entity,movement_state) -- --! @brief fix runaway speed mobs --! @memberof movement_gen --! @private -- --! @param entity mob to check speed limits --! @param movement_state current state of movement ------------------------------------------------------------------------------- function movement_gen.fix_runaway(entity,movement_state) --avoid mobs racing away local xzspeed = mobf_calc_scalar_speed(movement_state.current_velocity.x, movement_state.current_velocity.z) dbg_mobf.pmovement_lvl3("MOBF: checkrunaway x=" .. movement_state.current_velocity.x .. " z=" ..movement_state.current_velocity.z .. " xz=" .. xzspeed .. " maximum=" .. entity.data.movement.max_speed) if xzspeed > entity.data.movement.max_speed then dbg_mobf.pmovement_lvl3("MOBF: too fast! vxz=" .. xzspeed) dbg_mobf.pmovement_lvl3("MOBF: current acceleration:" .. printpos(movement_state.current_acceleration)) local direction = mobf_calc_yaw(movement_state.current_velocity.x, movement_state.current_velocity.z) --reduce speed to 90% of current speed local new_speed = mobf_calc_vector_components(direction,xzspeed*0.9) new_speed.y = movement_state.current_velocity.y entity.object:setvelocity(new_speed) movement_state.current_velocity = new_speed --don't accelerate any longer movement_state.accel_to_set = {x=0,z=0} movement_state.changed = true dbg_mobf.pmovement_lvl2("MOBF: fix runaway new acceleration:" .. printpos(movement_state.accel_to_set)) end end ------------------------------------------------------------------------------- -- name: fix_to_slow(entity,movement_state) -- --! @brief check if mobs motion is below its minimal speed (e.g. flying birds) --! @memberof movement_gen --! @private -- --! @param entity mob to check speed limits --! @param movement_state current state of movement ------------------------------------------------------------------------------- function movement_gen.fix_to_slow(entity,movement_state) local xzspeed = mobf_calc_scalar_speed(movement_state.current_velocity.x, movement_state.current_velocity.z) dbg_mobf.pmovement_lvl3("MOBF: checktoslow x=" .. movement_state.current_velocity.x .. " z=" ..movement_state.current_velocity.z .. " xz=" .. xzspeed .. " minimum=" .. dump(entity.data.movement.min_speed)) --this ain't perfect to avoid flying mobs standing in air --but it's a quick solution to fix most of the problems if (entity.data.movement.min_speed ~= nil and xzspeed < entity.data.movement.min_speed) or xzspeed == nil or xzspeed == 0 then dbg_mobf.pmovement_lvl2("MOBF: too slow! vxz=" .. xzspeed) --use normal speed change handling movement_state.force_change = true end end ------------------------------------------------------------------------------- -- name: fix_current_pos(entity,movement_state) -- --! @brief check current position for validity and try to fix --! @memberof movement_gen --! @private -- --! @param entity to fix position --! @param movement_state movement state of mob --! @return { speed_to_set = {x,y,z}, --! speed_changed = true/false, --! force_speed_change = true/false } ------------------------------------------------------------------------------- function movement_gen.fix_current_pos(entity,movement_state) --check if current pos is ok local current_state = environment.pos_is_ok(movement_state.basepos,entity) local handled = false dbg_mobf.pmovement_lvl3("MOBF: current state ".. entity.data.name .. ": " .. current_state) movement_state.accel_to_set = { x=0, y=movement_state.default_y_accel, z=0 } local abort_processing = false if current_state == "ok" or current_state == "possible_surface" then entity.dynamic_data.movement.last_pos_in_env = movement_state.centerpos handled = true end --states ok drop and wrong_surface don't require an imediate action if current_state ~= "ok" and current_state ~= "drop" and current_state ~= "wrong_surface" and current_state ~= "possible_surface" and current_state ~= "below_limit" and current_state ~= "above_limit" then dbg_mobf.movement_lvl1("MOBF: BUG !!! somehow your mob managed to get" .." where it shouldn't be (" .. current_state .. "), trying to fix") --stop mob from moving at all entity.object:setacceleration({x=0,y=movement_state.default_y_accel,z=0}) movement_state.force_change = true --mob is currently in water, --try to find a suitable position 1 level above current level if current_state == "in_water" or current_state == "above_water" then local targetpos = nil --if we don't have an old pos or old pos is to far away --try to finde another good pos around if entity.dynamic_data.movement.last_pos_in_env == nil or entity.dynamic_data.movement.last_pos_in_env.y > movement_state.centerpos.y + 2 then targetpos = environment.get_suitable_pos_same_level({ x=movement_state.basepos.x, y=movement_state.basepos.y+1, z=movement_state.basepos.z}, 1, entity) if targetpos ~= nil then targetpos.y = targetpos.y - entity.collisionbox[2] end else targetpos = entity.dynamic_data.movement.last_pos_in_env end if targetpos ~= nil then mobf_bug_warning(LOGLEVEL_INFO,"MOBF: BUG !!! didn't find a way" .. " out of water, for mob at: " .. printpos(movement_state.basepos) .. " using last known good position") if targetpos == nil then targetpos = entity.dynamic_data.movement.last_pos_in_env else targetpos.y = targetpos.y - entity.collisionbox[2] end minetest.log(LOGLEVEL_INFO,"MOBF: Your mob " .. entity.data.name .. " " .. tostring(entity) .. " dropt into water moving to ".. printpos(targetpos).." state: ".. environment.pos_is_ok(targetpos,entity)) entity.object:moveto(targetpos) movement_state.current_velocity.x = 0 --movement_state.current_velocity.x/10 movement_state.current_velocity.z = 0 --movement_state.current_velocity.z/10 entity.object:setvelocity(movement_state.current_velocity) movement_state.centerpos = targetpos movement_state.basepos = entity.getbasepos(entity) movement_state.accel_to_set.y = environment.get_default_gravity(targetpos, entity.environment.media, entity.data.movement.canfly) mobf_assert_backtrace(movement_state.accel_to_set.y ~= nil) else mobf_bug_warning(LOGLEVEL_WARNING,"MOBF: BUG !!! didn't find a way" .." out of water, for mob at: " .. printpos(movement_state.basepos) .. " drowning, last pos in env:" .. dump(entity.dynamic_data.movement.last_pos_in_env)) abort_processing = true spawning.remove(entity, "mgen probab watercheck") end handled = true end if current_state == "in_air" then --TODO die? handled = true end end local damagetime = 60 if entity.data.generic.wrong_surface_damage_time ~= nil then damagetime = entity.data.generic.wrong_surface_damage_time end if current_state == "wrong_surface" and entity.dynamic_data.good_surface ~= nil then if movement_state.now - entity.dynamic_data.good_surface > damagetime then entity.object:set_hp( entity.object:get_hp() - (entity.data.generic.base_health/25)) dbg_mobf.movement_lvl1("MOBF: mob is on wrong surface it will slowly take damage") --reset timer for next damage interval entity.dynamic_data.good_surface = movement_state.now if entity.object:get_hp() <= 0 then abort_processing = true spawning.remove(entity, "mgen probab surfacecheck") end movement_state.force_change = true end handled = true else entity.dynamic_data.good_surface = movement_state.now end if current_state == "collision" then local current_pos = mobf_round_pos(movement_state.basepos); local current_node = minetest.get_node( current_pos ); local walkable = false if minetest.registered_nodes[current_node.name] then walkable = minetest.registered_nodes[current_node.name].walkable end dbg_mobf.movement_lvl2( "MOBF: Mob collided with ".. tostring( current_pos.x )..":".. tostring( current_pos.y )..":".. tostring( current_pos.z ).." Nodename:".. tostring( current_node.name ).." walkable:".. tostring( walkable )) if not mobf_is_walkable(current_node) then local targetpos = entity.dynamic_data.movement.last_pos_in_env if targetpos == nil then targetpos = environment.get_suitable_pos_same_level({x=current_pos.x, y=current_pos.y, z=current_pos.z}, 1, entity) end if targetpos == nil then targetpos = environment.get_suitable_pos_same_level({x=current_pos.x, y=current_pos.y+1, z=current_pos.z}, 1, entity) end if targetpos ~= nil then minetest.log(LOGLEVEL_INFO, "MOBF: Your mob " .. entity.data.name .. " is within solid block moving to".. printpos(targetpos).." state: ".. environment.pos_is_ok(targetpos,entity)) entity.object:moveto(targetpos) movement_state.accel_to_set.y = environment.get_default_gravity(targetpos, entity.environment.media, entity.data.movement.canfly) mobf_assert_backtrace(movement_state.default_y_accel ~= nil) else minetest.log(LOGLEVEL_WARNING,"MOBF: mob " .. entity.data.name .. " was within solid block, removed") abort_processing = true spawning.remove(entity, "mgen probab solidblockcheck") end else dbg_mobf.movement_lvl2( "MOBF: mob is on walkable surface " .. "so no forced repositioning" ) end handled = true end if not handled then dbg_mobf.movement_lvl1("MOBF: ".. entity.data.name .. " state: ".. current_state .. " not handled!") end return { abort_processing=abort_processing, } end ------------------------------------------------------------------------------- -- name: set_target(entity,target) -- --! @brief set target for movgen --! @memberof movement_gen --! @private -- --! @param entity mob to apply to --! @param target to set ------------------------------------------------------------------------------- function movement_gen.set_target(entity,target) return false end --register this movement generator registerMovementGen(movement_gen.name,movement_gen)mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/000077500000000000000000000000001264104133000224145ustar00rootroot00000000000000mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/dont_move.lua000066400000000000000000000021771264104133000251200ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file dont_move.lua --! @brief movementpattern for immobile mob --! @copyright Sapier --! @author Sapier --! @date 2012-08-10 -- --! @addtogroup mpatterns --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @struct dont_move_prototype --! @brief a movement pattern resulting in a mob not moving at all local dont_move_prototype = { name ="dont_move", jump_up =0, random_jump_chance =0, random_jump_initial_speed =0, random_jump_delay =0, random_acceleration_change =0, -- -- --run towards player or run away? 1 <-> -1 -- player_attraction =0, -- --maximum distance a player has an effect -- player_attraction_range =-1, } --!@} table.insert(mov_patterns_defined,dont_move_prototype)mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/flight_pattern1.lua000066400000000000000000000017331264104133000262160ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file flight_pattern1.lua --! @brief movementpattern flight 1 --! @copyright Sapier --! @author Sapier --! @date 2012-08-10 -- --! @addtogroup mpatterns --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @struct flight_pattern1_prototype --! @brief a movement pattern used for flying mobs local flight_pattern1_prototype = { name ="flight_pattern1", jump_up =0, random_jump_chance =0.4, random_jump_initial_speed =0, random_jump_delay =10, random_acceleration_change =0.3, } --!@} table.insert(mov_patterns_defined,flight_pattern1_prototype)mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/run_and_jump_low.lua000066400000000000000000000021021264104133000264540ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file run_and_jump_low.lua --! @brief movementpattern running arround ad doing random low jumps --! @copyright Sapier --! @author Sapier --! @date 2012-08-10 -- --! @addtogroup mpatterns --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @struct run_and_jump_low_prototype --! @brief a movement pattern used for quick moving and direction changing mobs --! that jump every now and then local run_and_jump_low_prototype = { name ="run_and_jump_low", jump_up =0.2, random_jump_chance =0.3, random_jump_initial_speed =3.5, random_jump_delay =2, random_acceleration_change =0.6, } --!~@} table.insert(mov_patterns_defined,run_and_jump_low_prototype)mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/stop_and_go.lua000066400000000000000000000017721264104133000254220ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file stop_and_go.lua --! @brief movementpattern creating a random stop and go movement e.g. sheep/cow --! @copyright Sapier --! @author Sapier --! @date 2012-08-10 -- --! @addtogroup mpatterns --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @struct stop_and_go_prototype --! @brief movement pattern for mobs wandering around randomly local stop_and_go_prototype = { name ="stop_and_go", jump_up =0.4, random_jump_chance =0, random_jump_initial_speed =0, random_jump_delay =0, random_acceleration_change =0.05, } --!@} table.insert(mov_patterns_defined,stop_and_go_prototype)mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/swim_pattern1.lua000066400000000000000000000017611264104133000257210ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file swim_pattern1.lua --! @brief movementpattern for slow swimming mobs --! @copyright Sapier --! @author Sapier --! @date 2012-08-10 -- --! @addtogroup mpatterns --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @struct swim_pattern1_prototype --! @brief movement pattern for mobs swimming slow local swim_pattern1_prototype = { name ="swim_pattern1", jump_up =0, random_jump_chance =0.2, random_jump_initial_speed =0, random_jump_delay =10, random_acceleration_change =0.5, } --!@} table.insert(mov_patterns_defined,swim_pattern1_prototype)mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/swim_pattern2.lua000066400000000000000000000017521264104133000257220ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file swim_pattern2.lua --! @brief movementpattern for medium swimming mobs --! @copyright Sapier --! @author Sapier --! @date 2012-08-10 -- --! @addtogroup mpatterns --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @struct swim_pattern1_prototype --! @brief movement pattern for mobs swimming medium speeds local swim_pattern2_prototype = { name ="swim_pattern2", jump_up =0, random_jump_chance =0.4, random_jump_initial_speed =0, random_jump_delay =15, random_acceleration_change =0.7, } --!@} table.insert(mov_patterns_defined,swim_pattern2_prototype)mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/swim_pattern3.lua000066400000000000000000000020261264104133000257160ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file swim_pattern1.lua --! @brief movementpattern for slow swimming mobs --! @copyright Sapier --! @author Sapier --! @date 2012-08-10 -- --! @addtogroup mpatterns --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @struct swim_pattern1_prototype --! @brief movement pattern for mobs swimming slow local swim_pattern3_prototype = { name ="swim_pattern3", jump_up =0, random_jump_chance =0.2, random_jump_initial_speed =0, random_jump_delay =10, random_acceleration_change =0.5, min_height_above_ground = 3 } --!@} table.insert(mov_patterns_defined,swim_pattern3_prototype)mobf_core-2.5.1/mobf/mgen_probab/movement_patterns/template.lua000066400000000000000000000022111264104133000247260ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file template.lua --! @brief template file for movement patterns --! @copyright Sapier --! @author Sapier --! @date 2012-08-11 -- --! @addtogroup mpatterns --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --movement pattern for movement generator -- { -- --patternname -- name ="example" -- -- --chance to jump to higher level instead of turning -- jump_up =0, -- -- --chance an mob does random jumps -- random_jump_chance =0, -- --mobs jump speed (controling height of jump) -- random_jump_initial_speed =0, -- --minimum time between random jumps -- random_jump_delay =0, -- -- --chance an mob randomly changes its speed/direction -- random_acceleration_change =0, -- } --!@}mobf_core-2.5.1/mobf/mgen_rasterized/000077500000000000000000000000001264104133000175515ustar00rootroot00000000000000mobf_core-2.5.1/mobf/mgen_rasterized/mgen_raster.lua000066400000000000000000000140121264104133000225600ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file mgen_raster.lua --! @brief component containing a probabilistic movement generator (uses mgen follow) --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup mgen_raster MGEN: raster based movement generator --! @brief A movement generator creating movement that creates a rasterized --! random movement --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @class mgen_raster --! @brief a movement generator creating a rasterized probabilistic movement --!@} mgen_raster = {} --! @brief movement generator identifier --! @memberof mgen_follow mgen_raster.name = "probab_raster_mov_gen" ------------------------------------------------------------------------------- -- name: stop(entity) -- --! @brief stop this entity --! @memberof mgen_raster -- --! @param entity mob to stop ------------------------------------------------------------------------------- function mgen_raster.stop(entity) local defgrav = environment.get_default_gravity(entity.getbasepos(entity), entity.environment, entity.data.movement.canfly) entity.dynamic_data.movement.target = nil entity.object:setvelocity({x=0,y=0,z=0}) entity.object:setacceleration({x=0,y=defgrav,z=0}) end ------------------------------------------------------------------------------- -- name: callback(entity,now) -- --! @brief main callback to make a mob follow its target --! @memberof mgen_raster -- --! @param entity mob to generate movement for --! @param now current time ------------------------------------------------------------------------------- function mgen_raster.callback(entity,now) local basepos = entity.getbasepos(entity) local dtime = entity.current_dtime entity.dynamic_data.movement.time_travelled = entity.dynamic_data.movement.time_travelled + dtime --check environment local pos_state = environment.pos_is_ok(basepos,entity) if pos_state ~= "ok" and pos_state ~= "possible_surface" then mgen_raster.stop(entity) --try to find a good position around to move mob to local new_pos = environment.get_suitable_pos_same_level(basepos,1,entity,false) if (new_pos == nil ) then new_pos = environment.get_suitable_pos_same_level({x=basepos.x, y=basepos.y+1, z=basepos.z} ,1,entity,false) end if (new_pos == nil ) then new_pos = environment.get_suitable_pos_same_level({x=basepos.x, y=basepos.y+1, z=basepos.z} ,1,entity,false) end if (new_pos ~= nil ) then --TODO fix position according to model information! enity.object:moveto(new_pos) basepos = new_pos else --TODO error handling end end --check on ground if not mgen_raster.onground(entity,now,basepos) then mgen_raster.stop(entity) end --check how long we've been traveling if ( entity.dynamic_data.movement.time_travelled > entity.dynamic_data.movement.eta ) then mgen_raster.stop(entity) end --check distance to target (target reached or missed) local distance_to_target = mobf_calc_distance(basepos,entity.dynamic_data.movement.target) if distance_to_target > 2.0 or distance_to_target < 0.1 then mgen_raster.stop(entity) end --find next point if entity.dynamic_data.nextpoint == nil then entity.dynamic_data.nextpoint = environment.get_suitable_pos_same_level(basepos,1,entity,false) --TODO calc maximum eta + 10% end --call move to target mgen helper_mgen.callback(entity,now) end ------------------------------------------------------------------------------- -- name: initialize() -- --! @brief initialize movement generator --! @memberof mgen_raster --! @public ------------------------------------------------------------------------------- function mgen_raster.initialize(entity,now) --intentionaly doing nothing this function is for symmetry reasons only end ------------------------------------------------------------------------------- -- name: init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by movement generator --! @memberof mgen_raster --! @public -- --! @param entity mob to initialize dynamic data --! @param now current time ------------------------------------------------------------------------------- function mgen_raster.init_dynamic_data(entity,now) local pos = entity.object:getpos() local data = { target = nil, guardspawnpoint = true, time_travelled = 0, helper_mgen = getMovementGen(follow_mov_gen), max_distance = 0.1, } entity.dynamic_data.movement = data end ------------------------------------------------------------------------------- -- name: onground(entity,now) -- --! @brief check if mob is touching ground --! @memberof mgen_raster --! @public -- --! @param entity mob to initialize dynamic data --! @param now current time --! @param basepos position of feet ------------------------------------------------------------------------------- function mgen_raster.onground(entity,now,basepos) local posbelow = { x=basepos.x, y=basepos.y-1,z=basepos.z} for dx=-1,1 do for dz=-1,1 do local fp0 = posbelow local fp = { x= posbelow.x + (0.5*dx), y= posbelow.y, z= posbelow.z + (0.5*dz) } local n = minetest.get_node(fp) if mobf_is_walkable(n) then return true end end end end ------------------------------------------------------------------------------- -- name: set_target(entity,target) -- --! @brief set target for movgen --! @memberof mgen_raster --! @private -- --! @param entity mob to apply to --! @param target to set ------------------------------------------------------------------------------- function mgen_raster.set_target(entity,target) return false end --register this movement generator registerMovementGen(mgen_raster.name,mgen_raster)mobf_core-2.5.1/mobf/mob_state.lua000066400000000000000000000544061264104133000170600ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file mob_state.lua --! @brief component mob state transition handling --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup mob_state State handling functions --! @brief a component to do basic changes to mob on state change --! @ingroup framework_int --! @{ -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mob_state")) --! @class mob_state --! @brief state handling class --! @} mob_state = {} mob_state.default_state_time = 30 ------------------------------------------------------------------------------- -- @function [parent=#mob_state] initialize(entity,now) -- --! @brief initialize state dynamic data --! @memberof mob_state --! @public -- --! @param entity elemet to initialize state data --! @param now current time ------------------------------------------------------------------------------- function mob_state.initialize(entity,now) dbg_mobf.mob_state_lvl3("MOBF: " .. entity.data.name .. " initializing state dynamic data") local state = { current = "default", time_to_next_change = 30, locked = false, enabled = false, } local sum_chances = 0 local state_count = 0 if entity.data.states ~= nil then for s = 1, #entity.data.states , 1 do sum_chances = sum_chances + entity.data.states[s].chance if entity.data.states[s].name ~= "combat" and entity.data.states[s].name ~= "default" then state_count = state_count +1 end end end --sanity check for state chances if sum_chances > 1 then minetest.log(LOGLEVEL_WARNING,"MOBF: Warning sum of state chances for mob " .. entity.data.name .. " > 1") end --only enable state changeing if there is at least one state if state_count > 0 then state.enabled = true end entity.dynamic_data.state = state end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] get_state_by_name(entity,name) -- --! @brief get a state by its name --! @memberof mob_state --! @public -- --! @param entity elemet to look for state data --! @param name of state --! --! @return state data or nil ------------------------------------------------------------------------------- function mob_state.get_state_by_name(entity,name) mobf_assert_backtrace(entity ~= nil and entity.data ~= nil) for i=1, #entity.data.states, 1 do if entity.data.states[i].name == name then return entity.data.states[i] end end return nil end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] lock(entity,value) -- --! @brief disable random state changes for a mob --! @memberof mob_state --! @public -- --! @param entity elemet to lock --! @param value to set ------------------------------------------------------------------------------- function mob_state.lock(entity,value) if value ~= false and value ~= true then return end if entity.dynamic_data.state == nil then dbg_mobf.mob_state_lvl1("MOBF: unable to lock state for: " .. entity.data.name .. " no state dynamic data present") return end entity.dynamic_data.state.locked = value end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] callback(entity,now,dstep) -- --! @brief callback handling state changes --! @memberof mob_state --! @public -- --! @param entity elemet to look for state data --! @param now current time --! @param dstep time passed since last call -- --! @return ------------------------------------------------------------------------------- function mob_state.callback(entity,now,dstep) --TODO find out if this needs to be replaced -- if entity.dynamic_data.state == nil then -- minetest.log(LOGLEVEL_ERRROR,"MOBF BUG: " .. entity.data.name -- .. " mob state callback without mob dynamic data!") -- mob_state.initialize(entity,now) -- local default_state = mob_state.get_state_by_name(self,"default") -- entity.dynamic_data.current_movement_gen = getMovementGen(default_state.movgen) -- entity.dynamic_data.current_movement_gen.init_dynamic_data(entity,mobf_get_current_time()) -- entity = spawning.replace_entity(entity,entity.data.modname .. ":"..entity.data.name,true) -- return true -- end --abort state change if current state is locked if entity.dynamic_data.state.locked or entity.dynamic_data.state.enabled == false then dbg_mobf.mob_state_lvl3("MOBF: " .. entity.data.name .. " state locked or no custom states definded ") return true end entity.dynamic_data.state.time_to_next_change = entity.dynamic_data.state.time_to_next_change -dstep --do only change if last state timed out if entity.dynamic_data.state.time_to_next_change < 0 then dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " time to change state: " .. entity.dynamic_data.state.time_to_next_change .. " , " .. dstep .. " entity=" .. tostring(entity)) local rand = math.random() local maxvalue = 0 local state_table = {} --fill table with available states for i=1, #entity.data.states, 1 do if (entity.data.states[i].HANDLER_precondition == nil or entity.data.states[i].HANDLER_precondition(entity,entity.data.states[i])) and --ignore states that are not supposed to be switched to --by automatic state change handling e.g. fighting states or --manual set states ( entity.data.states[i].state_mode == nil or entity.data.states[i].state_mode == "auto") then table.insert(state_table,entity.data.states[i]) end end dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " " .. dump(#state_table) .. " states do pass pecondition check ") --try to get a random state to change to for i=1, #state_table, 1 do local rand_state = math.random(#state_table) local current_chance = 0 if type (state_table[rand_state].chance) == "function" then current_chance = state_table[rand_state].chance(entity,now,dstep) else if state_table[rand_state].chance ~= nil then current_chance = state_table[rand_state].chance end end local random_value = math.random() if random_value < current_chance then dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " switching to state " .. state_table[rand_state].name) mob_state.change_state(entity,state_table[rand_state]) return true else dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " not switching to state " .. state_table[rand_state].name .. " rand was: " .. random_value) table.remove(state_table,rand_state) end end dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " no specific state selected switching to default state ") --switch to default state (only reached if no change has been done mob_state.change_state(entity,mob_state.get_state_by_name(entity,"default")) else dbg_mobf.mob_state_lvl3("MOBF: " .. entity.data.name .. " is not ready for state change ") return true end return true end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] switch_switch_movgenentity(entity,state) -- --! @brief helper function to swich a movement based on new state --! @memberof mob_state --! @private -- --! @param entity to change movement gen --! @param state to take new entity ------------------------------------------------------------------------------- function mob_state.switch_movgen(entity,state) mobf_assert_backtrace(entity ~= nil) mobf_assert_backtrace(state ~= nil) local mov_to_set = nil --determine new movement gen if state.movgen ~= nil then mov_to_set = getMovementGen(state.movgen) else local default_state = mob_state.get_state_by_name(entity,"default") mov_to_set = getMovementGen(default_state.movgen) end --check if new mov gen differs from old one if mov_to_set ~= nil and mov_to_set ~= entity.dynamic_data.current_movement_gen then entity.dynamic_data.current_movement_gen = mov_to_set --TODO initialize new movement gen entity.dynamic_data.current_movement_gen.init_dynamic_data(entity,mobf_get_current_time()) end end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] change_state(entity,state) -- --! @brief change state for an entity --! @memberof mob_state --! @public -- --! @param entity to change state --! @param state to change to ------------------------------------------------------------------------------- function mob_state.change_state(entity,state) dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " state change called entity=" .. tostring(entity) .. " state:" .. dump(state)) --check if custom precondition handler tells us to stop state change if state ~= nil and type(state.HANDLER_precondition) == "function" then if not state.HANDLER_precondition(entity,state) then dbg_mobf.mob_state_lvl1("MOBF: " .. entity.data.name .. " custom precondition handler didn't meet ") --don't assert but switch to default in this case state = mob_state.get_state_by_name(entity,"default") end end --switch to default state if no state given if state == nil then dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " invalid state switch, switching to default instead of: " .. dump(state)) state = mob_state.get_state_by_name(entity,"default") end local entityname = entity.data.name local statename = state.name dbg_mobf.mob_state_lvl2("MOBF: " .. entityname .. " switching state to " .. statename) if entity.dynamic_data.state == nil then mobf_bug_warning(LOGLEVEL_WARNING,"MOBF BUG!!! mob_state no state dynamic data") end if entity.dynamic_data.state.current.name ~= state.name then --call leave state handler for old state if entity.dynamic_data.state.current.HANDLER_leave_state ~= nil and type(entity.dynamic_data.state.current.HANDLER_leave_state) == "function" then entity.dynamic_data.state.current.HANDLER_leave_state(entity,state) end dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " different states now really changeing to " .. state.name) mob_state.switch_movgen(entity,state) entity.dynamic_data.state.time_to_next_change = mob_state.getTimeToNextState(state.typical_state_time) entity.dynamic_data.state.current = state graphics.set_animation(entity,state.animation) if state.HANDLER_enter_state ~= nil and type(state.HANDLER_enter_state) == "function" then state.HANDLER_enter_state(entity) end else dbg_mobf.mob_state_lvl2("MOBF: " .. entity.data.name .. " switching to same state as before") entity.dynamic_data.state.time_to_next_change = mob_state.getTimeToNextState(state.typical_state_time) if state.HANDLER_enter_state ~= nil and type(state.HANDLER_enter_state) == "function" then state.HANDLER_enter_state(entity) end end --update model on each state change mob_state.switch_model(entity,state) dbg_mobf.mob_state_lvl2("MOBF: time to next change = " .. entity.dynamic_data.state.time_to_next_change) end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] getTimeToNextState(typical_state_time) -- --! @brief helper function to calculate a gauss distributed random value --! @memberof mob_state --! @private -- --! @param typical_state_time center of gauss --! --! @return a random value around typical_state_time ------------------------------------------------------------------------------- function mob_state.getTimeToNextState(typical_state_time) if typical_state_time == nil then mobf_bug_warning(LOGLEVEL_WARNING,"MOBF MOB BUG!!! missing typical state time!") typical_state_time = mob_state.default_state_time end local u1 = 2 * math.random() -1 local u2 = 2 * math.random() -1 local q = u1*u1 + u2*u2 local maxtries = 0 while (q == 0 or q >= 1) and maxtries < 10 do u1 = math.random() u2 = math.random() * -1 q = u1*u1 + u2*u2 maxtries = maxtries +1 end --abort random generation if maxtries >= 10 then return typical_state_time end local p = math.sqrt( (-2*math.log(q))/q ) local retval = 2 --calculate normalized state time with maximum error or half typical time up and down if math.random() < 0.5 then retval = typical_state_time + ( u1*p * (typical_state_time/2)) else retval = typical_state_time + ( u2*p * (typical_state_time/2)) end -- ensure minimum state time of 2 seconds if retval > 2 then return retval else return 2 end end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] prepare_states(mob) -- --! @brief prepare mobs states upon registration --! @memberof mob_state --! @public -- --! @param mob a mob declaration ------------------------------------------------------------------------------- function mob_state.prepare_states(mob) local builtin_state_overrides = {} --scan for states overriding mobf internal state definitions if mob.states ~= nil then for s = 1, #mob.states , 1 do if mob.states[s].name == "combat" then builtin_state_overrides["combat"] = true end --TODO patrol state --hunger state if mob.states[s].name == "RSVD_hunger" then builtin_state_overrides["RSVD_hunger"] = true end end else mob.states = {} end --add a default combat state if no custom state is defined if mob.combat ~= nil and builtin_state_overrides["combat"] ~= true then table.insert(mob.states, { name = "combat", HANDLER_precondition = nil, movgen = "follow_mov_gen", typical_state_time = -1, chance = 0, }) end if mob.hunger ~= nil and builtin_state_overrides["RSVD_hunger"] ~= true then table.insert(mob.states, { name = "RSVD_hunger", HANDLER_precondition = mob_state.BuiltinHungerPrecondition(mob), HANDLER_leave_state = mob_state.BuiltinHungerLeave(mob), HANDLER_enter_state = mob_state.BuiltinHungerEnter(mob), movgen = "mgen_path", typical_state_time = mob.hunger.typical_walk_time or 45, chance = mob.hunger.chance or 0.1, animation = mob.hunger.animation or "walk" }) end end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] switch_model(entity,state) -- --! @brief switch model used for a entity --! @memberof mob_state --! @public -- --! @param entity to switch model --! @param state to change to ------------------------------------------------------------------------------- function mob_state.switch_model(entity, state) local new_graphics = graphics.graphics_by_statename(entity.data, state.name) if type(new_graphics.texturelist) == "table" then if entity.dynamic_data.textureidx ~= nil and entity.dynamic_data.textureidx < #new_graphics.texturelist then new_graphics.textures = new_graphics.texturelist[entity.dynamic_data.textureidx] else new_graphics.textures = new_graphics.texturelist[1] end end local new_props = { automatic_face_movement_dir = true, textures = new_graphics.textures } if new_graphics.model_orientation_fix ~= nil then new_props.automatic_face_movement_dir = (new_graphics.model_orientation_fix / math.pi) * 360 + 90 end --TODO apply new model too entity.object:set_properties(new_props) end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] BuiltinHungerPrecondition(mob) -- --! @brief prepare builtin hunger precondition handler --! @memberof mob_state --! @public -- --! @param mob definition of mob --! @return function to be called as precondition handler ------------------------------------------------------------------------------- function mob_state.BuiltinHungerPrecondition(mob) mobf_assert_backtrace( (mob.hunger.target_nodes ~= nil) or (mob.hunger.target_entities ~= nil)) mobf_assert_backtrace(mob.hunger.range ~= nil) return function(entity,state) mobf_assert_backtrace(state ~= nil) mobf_assert_backtrace(state.name == "RSVD_hunger") local pos = entity.object:getpos() --mobf_print("MOBF: trying to find " .. -- dump(mob.hunger.target_nodes) .. " or " .. -- dump(mob.hunger.target_entities) .. -- " around: " .. printpos(pos)) local lower_pos = {x=pos.x-mob.hunger.range, y=pos.y-mob.hunger.range, z=pos.z-mob.hunger.range} local upper_pos = {x=pos.x+mob.hunger.range, y=pos.y+mob.hunger.range, z=pos.z+mob.hunger.range} local target_nodes = nil local target_entities = nil if mob.hunger.target_nodes ~= nil then target_nodes = minetest.find_nodes_in_area(lower_pos, upper_pos, mob.hunger.target_nodes) end if mob.hunger.target_entities ~= nil then local objectlist = minetest.get_objects_inside_radius(pos,mob.hunger.range) --mobf_print("MOBF: found: " .. #objectlist .. " objects around") if objectlist ~= nil and #objectlist > 0 then target_entities = {} for i=1,#objectlist,1 do local luaentity = objectlist[i]:get_luaentity() if luaentity ~= nil and mobf_contains(mob.hunger.target_entities,luaentity.name) then table.insert(target_entities,objectlist[i]) end end end end local targets = {} if target_nodes ~= nil then for i=1,#target_nodes,1 do table.insert(targets,target_nodes[i]) end end if target_entities ~= nil then for i=1,#target_entities,1 do table.insert(targets,target_entities[i]) end end if targets ~= nil then dbg_mobf.mob_state_lvl3("MOBF: Hunger found " .. #targets .. " targets") for i=1,5,1 do if #targets == 0 then break end local index = math.floor(math.random(1,#targets) + 0.5) local target = targets[index] table.remove(targets,index) --target is a entity if type(target) == "userdata" then entity.dynamic_data.hunger = {} entity.dynamic_data.hunger.target = target --mobf_print("MOBF: saving hungerdata: " .. dump(entity.dynamic_data.hunger)) return true else local targetpos = target targetpos.y = targetpos.y +1 --if mob is not in air try 1 above for pathfinding local current_node = minetest.get_node(pos) if current_node ~= nil and current_node.name ~= "air" then pos.y = pos.y+1 end local path = mobf_path.find_path(pos,targetpos,5,1,1,"A*_noprefetch") if path ~= nil then entity.dynamic_data.hunger = {} entity.dynamic_data.hunger.target = { x=targetpos.x,y=targetpos.y-1,z=targetpos.z} entity.dynamic_data.hunger.path = path --mobf_print("MOBF: Found new target: " .. printpos(targetpos) .. " Path: " .. dump(path)) --mobf_print("MOBF: saving hungerdata: " .. dump(entity.dynamic_data.hunger)) return true else dbg_mobf.mob_state_lvl2("MOBF: hunger no path to: " .. printpos(targetpos)) end end end end return false end --end of handler end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] BuiltinHungerLeave(mob) -- --! @brief prepare builtin hunger leave handler --! @memberof mob_state --! @public -- --! @param mob definition of mob --! @return function to be called as leave handler ------------------------------------------------------------------------------- function mob_state.BuiltinHungerLeave(mob) return function(entity,state) --restore old stepheight entity.object:set_properties({stepheight=entity.dynamic_data.hunger.old_stepheight}) entity.dynamic_data.hunger = nil p_mov_gen.set_cycle_path(entity,nil) p_mov_gen.set_path(entity,nil) p_mov_gen.set_end_of_path_handler(entity,nil) end end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] BuiltinHungerEnter(mob) -- --! @brief prepare builtin hunger enter handler --! @memberof mob_state --! @public -- --! @param mob definition of mob --! @return function to be called as enter handler ------------------------------------------------------------------------------- function mob_state.BuiltinHungerEnter(mob) return function(entity) mobf_assert_backtrace(entity.dynamic_data.state.current.name == "RSVD_hunger") mobf_assert_backtrace(entity.dynamic_data.hunger ~= nil) --use stepheight 1 as we did look for a path by using this entity.dynamic_data.hunger.old_stepheight = entity.stepheight if (type(entity.dynamic_data.hunger.target) == "userdata") then if not p_mov_gen.set_target(entity,entity.dynamic_data.hunger.target) then dbg_mobf.mob_state_lvl1("MOBF: EnterHungerState, failed to set target") end else entity.object:set_properties({stepheight=1}) p_mov_gen.set_path(entity,entity.dynamic_data.hunger.path) end --p_mov_gen.set_cycle_path(entity,handler) p_mov_gen.set_cycle_path(entity,false) p_mov_gen.set_end_of_path_handler(entity,mob_state.BuiltinHungerTargetReached) end end ------------------------------------------------------------------------------- -- @function [parent=#mob_state] BuiltinHungerTargetReached(entity) -- --! @brief handle target reached --! @memberof mob_state --! @public -- --! @param entity that reached the target ------------------------------------------------------------------------------- function mob_state.BuiltinHungerTargetReached(entity) --consume original target if (entity.dynamic_data.hunger.target ~= nil) and (entity.data.hunger.keep_food == nil or entity.data.hunger.keep_food == false) then if type(entity.dynamic_data.hunger.target) ~= "userdata" then dbg_mobf.mob_state_lvl2("MOBF: consuming targetnode: " .. printpos(entity.dynamic_data.hunger.target)) minetest.remove_node(entity.dynamic_data.hunger.target) else local targetentity = entity.dynamic_data.hunger.target:get_luaentity() if targetentity ~= nil and type(targetentity.mobf_hunger_interface) == "function" then targetentity.mobf_hunger_interface(entity,"HUNGER_CONSUME") dbg_mobf.mob_state_lvl2("MOBF: consuming targetentity") end end end local eating_state = mob_state.get_state_by_name(entity,"eating") if eating_state ~= nil then mob_state.change_state(entity,eating_state) else local default_state = mob_state.get_state_by_name(entity,"default") mob_state.change_state(entity,default_state) end endmobf_core-2.5.1/mobf/mobf.lua000066400000000000000000001153111264104133000160170ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file mobf.lua --! @brief class containing mob initialization functions --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- -- --! @defgroup mobf Basic mob entity functions --! @brief a component containing basic functions for mob handling and initialization --! @ingroup framework_int --! @{ -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mobf")) --! @class mobf --! @brief basic management component of mob functions --!@} mobf = {} mobf.on_step_callbacks = {} mobf.on_punch_callbacks = {} mobf.on_rightclick_callbacks = {} ------------------------------------------------------------------------------ -- @function [parent=#mobf] register_on_step_callback(callback) -- --! @brief make a new on_step callback known to mobf --! @memberof mobf --! @public -- --! @param callback to make known to mobf ------------------------------------------------------------------------------- function mobf.register_on_step_callback(callback) if callback.configcheck == nil or type(callback.configcheck) ~= "function" then return false end if callback.handler == nil or type(callback.configcheck) ~= "function" then return false end table.insert(mobf.on_step_callbacks,callback) end ------------------------------------------------------------------------------ -- @function [parent=#mobf] init_on_step_callbacks(entity,now) -- --! @brief initalize callbacks to be used on step --! @memberof mobf --! @private -- --! @param entity entity to initialize on_step handler --! @param now current time ------------------------------------------------------------------------------- function mobf.init_on_step_callbacks(entity,now) entity.on_step_hooks = {} if #mobf.on_step_callbacks > 32 then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: " .. #mobf.on_step_callbacks .. " incredible high number of onstep callbacks registred!") end dbg_mobf.mobf_core_lvl2("MOBF: initializing " .. #mobf.on_step_callbacks .. " on_step callbacks for " .. entity.data.name .. " entity=" .. tostring(entity)) for i = 1, #mobf.on_step_callbacks , 1 do if mobf.on_step_callbacks[i].configcheck(entity) then dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") enabling callback " .. mobf.on_step_callbacks[i].name) table.insert(entity.on_step_hooks,mobf.on_step_callbacks[i].handler) if type(mobf.on_step_callbacks[i].init) == "function" then dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" .." executing init function for " .. mobf.on_step_callbacks[i].name) mobf.on_step_callbacks[i].init(entity,now) else dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" .." no init function defined") end else dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") callback " .. mobf.on_step_callbacks[i].name .. " disabled due to config check") end end end ------------------------------------------------------------------------------ -- @function [parent=#mobf] register_on_rightclick_callback(callback) -- --! @brief make a new on_rightclick callback known to mobf --! @memberof mobf --! @public -- --! @param callback to make known to mobf ------------------------------------------------------------------------------- function mobf.register_on_rightclick_callback(callback) if callback.configcheck == nil or type(callback.configcheck) ~= "function" then return false end if callback.handler == nil or type(callback.configcheck) ~= "function" then return false end table.insert(mobf.on_rightclick_callbacks,callback) end ------------------------------------------------------------------------------ -- @function [parent=#mobf] register_on_punch_callback(callback) -- --! @brief make a new on_punch callback known to mobf --! @memberof mobf --! @public -- --! @param callback the callback to register in mobf ------------------------------------------------------------------------------- function mobf.register_on_punch_callback(callback) if callback.configcheck == nil or type(callback.configcheck) ~= "function" then return false end if callback.handler == nil or type(callback.configcheck) ~= "function" then return false end table.insert(mobf.on_punch_callbacks,callback) end ------------------------------------------------------------------------------ -- @function [parent=#mobf] init_on_punch_callbacks(entity,now) -- --! @brief initalize callbacks to be used on punch --! @memberof mobf --! @private -- --! @param entity entity to initialize on_punch handler --! @param now current time ------------------------------------------------------------------------------- function mobf.init_on_punch_callbacks(entity,now) entity.on_punch_hooks = {} dbg_mobf.mobf_core_lvl2("MOBF: initializing " .. #mobf.on_punch_callbacks .. " on_punch callbacks for " .. entity.data.name .. " entity=" .. tostring(entity)) for i = 1, #mobf.on_punch_callbacks , 1 do if mobf.on_punch_callbacks[i].configcheck(entity) and type(mobf.on_punch_callbacks[i].handler) == "function" then dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") enabling callback " .. mobf.on_punch_callbacks[i].name) table.insert(entity.on_punch_hooks,mobf.on_punch_callbacks[i].handler) if type(mobf.on_punch_callbacks[i].init) == "function" then dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" .." executing init function for " .. mobf.on_punch_callbacks[i].name) mobf.on_punch_callbacks[i].init(entity,now) else dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" .." no init function defined") end else dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") callback " .. mobf.on_punch_callbacks[i].name .. " disabled due to config check") end end end ------------------------------------------------------------------------------ -- @function [parent=#mobf] init_on_rightclick_callbacks(entity,now) -- --! @brief initalize callbacks to be used on punch --! @memberof mobf --! @private -- --! @param entity entity to initialize on_punch handler --! @param now current time ------------------------------------------------------------------------------- function mobf.init_on_rightclick_callbacks(entity,now) entity.on_rightclick_hooks = {} dbg_mobf.mobf_core_lvl2("MOBF: initializing " .. #mobf.on_rightclick_callbacks .. " on_rightclick callbacks for " .. entity.data.name .. " entity=" .. tostring(entity)) for i = 1, #mobf.on_rightclick_callbacks , 1 do if mobf.on_rightclick_callbacks[i].configcheck(entity) and type(mobf.on_rightclick_callbacks[i].handler) == "function" then dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") enabling callback " .. mobf.on_rightclick_callbacks[i].name) table.insert(entity.on_rightclick_hooks,mobf.on_rightclick_callbacks[i]) if type(mobf.on_rightclick_callbacks[i].init) == "function" then dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" .. " executing init function for " .. mobf.on_rightclick_callbacks[i].name) mobf.on_rightclick_callbacks[i].init(entity,now) else dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ")" .. "no init function defined") end else dbg_mobf.mobf_core_lvl2("MOBF: (" .. i .. ") callback " .. mobf.on_rightclick_callbacks[i].name .. " disabled due to config check") end end if entity.data.generic.on_rightclick_callbacks ~= nil then for i = 1, #entity.data.generic.on_rightclick_callbacks, 1 do if type(entity.data.generic.on_rightclick_callbacks[i].handler) == "function" and type(entity.data.generic.on_rightclick_callbacks[i].name) == "string" and (type(entity.data.generic.on_rightclick_callbacks[i].visiblename) == "string" or type(entity.data.generic.on_rightclick_callbacks[i].visiblename) == "function") then table.insert(entity.on_rightclick_hooks, entity.data.generic.on_rightclick_callbacks[i]) if type(entity.data.generic.on_rightclick_callbacks[i].init) == "function" then entity.data.generic.on_rightclick_callbacks[i].init(entity) end end end end end ------------------------------------------------------------------------------ -- @function [parent=#mobf] get_basepos(entity) -- --! @brief get basepos for an entity --! @memberof mobf --! @public -- --! @param entity entity to fetch basepos --! @return basepos of mob ------------------------------------------------------------------------------- function mobf.get_basepos(entity) local pos = entity.object:getpos() local nodeatpos = minetest.get_node(pos) dbg_mobf.mobf_core_helper_lvl3("MOBF: " .. entity.data.name .. " Center Position: " .. printpos(pos) .. " is: " .. nodeatpos.name) -- if visual height is more than one block the center of base block is -- below the entities center if (entity.collisionbox[2] < -0.5) then pos.y = pos.y + (entity.collisionbox[2] + 0.5) dbg_mobf.mobf_core_helper_lvl3("MOBF: collision box lower end: " .. entity.collisionbox[2]) end nodeatpos = minetest.get_node(pos) dbg_mobf.mobf_core_helper_lvl3("MOBF: Base Position: " .. printpos(pos) .. " is: " .. nodeatpos.name) return pos end ------------------------------------------------------------------------------- -- @function [parent=#mobf] get_persistent_data(entity) -- --! @brief get persistent data of a particular entity --! @memberof mobf --! @private -- --! @return persistent entity data ------------------------------------------------------------------------------- function mobf.get_persistent_data(entity) if (entity.dynamic_data.custom_persistent == nil) then entity.dynamic_data.custom_persistent = {} end return entity.dynamic_data.custom_persistent end ------------------------------------------------------------------------------ -- @function [parent=#mobf] mobf_activate_handler(self,staticdata) -- --! @brief hanlder called for basic mob initialization --! @memberof mobf --! @private -- --! @param self entity to initialize onstep handler --! @param staticdata data to use for initialization ------------------------------------------------------------------------------- function mobf.activate_handler(self,staticdata) local starttime = mobf_get_time_ms() if mobf_step_quota.is_exceeded() then --mobf_print("MOBF: step quota exceeded for mob: ".. -- self.data.name .." (" .. tostring(self) .. ")") return end --do some initial checks local pos = self.object:getpos() if pos == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: mob at nil pos!") end local current_node = minetest.get_node(pos) if current_node == nil then mobf_bug_warning(LOGLEVEL_ERROR, "MOBF: trying to activate mob in nil node! removing") spawning.remove_uninitialized(self,staticdata) mobf_step_quota.consume(starttime) return end --restore saved data local preserved_data = mobf_deserialize_permanent_entity_data(staticdata) self.dynamic_data.last_static_data = nil --check if position would collide with other entities if not spawning.check_activation_overlap(self,pos,preserved_data) then spawning.remove_uninitialized(self,staticdata) mobf_step_quota.consume(starttime) return end --check if position environment os ok if environment.is_media_element(current_node.name,self.environment.media) == false then minetest.log(LOGLEVEL_WARNING,"MOBF: trying to activate mob " .. self.data.name .. " at invalid position") minetest.log(LOGLEVEL_WARNING," Activation at: " .. printpos(pos) .. " " .. current_node.name .. " --> removing") ----------------------------- --TODO try to move 1 block up ----------------------------- spawning.remove_uninitialized(self,staticdata) mobf_step_quota.consume(starttime) return end --reset replaced marker self.replaced = nil ---------------------------------------------------------------------------- -- initialize environment <-> mob <-> player interaction ---------------------------------------------------------------------------- local now = mobf_get_current_time() spawning.init_dynamic_data(self,now) mobf.init_on_step_callbacks(self,now) mobf.init_on_punch_callbacks(self,now) --initialize ride support mobf_ride.init(self) --check if this was a replacement mob if self.dyndata_delayed ~= nil then minetest.log(LOGLEVEL_ERROR,"MOBF: delayed activation for replacement mob." .."This shouldn't happen at all but if we can fix it") self.dynamic_data = dyndata_delayed.data self.object:set_hp(dyndata_delayed.health) graphics.setyaw(self, dyndata_delayed.entity_orientation) self.dyndata_delayed = nil self.dynamic_data.initialized = true mobf_step_quota.consume(starttime) return end ---------------------------------------------------------------------------- -- initialize preserved data ---------------------------------------------------------------------------- if self.dynamic_data.spawning ~= nil then if mobf_pos_is_zero(preserved_data.spawnpoint) ~= true then self.dynamic_data.spawning.spawnpoint = preserved_data.spawnpoint else self.dynamic_data.spawning.spawnpoint = mobf_round_pos(pos) end self.dynamic_data.spawning.player_spawned = preserved_data.playerspawned if preserved_data.original_spawntime ~= -1 then self.dynamic_data.spawning.original_spawntime = preserved_data.original_spawntime end if preserved_data.spawner ~= nil then minetest.log(LOGLEVEL_INFO,"MOBF: setting spawner to: " .. preserved_data.spawner) self.dynamic_data.spawning.spawner = preserved_data.spawner end --only relevant if mob has different states if preserved_data.state ~= nil and self.dynamic_data.state ~= nil then minetest.log(LOGLEVEL_INFO,"MOBF: setting current state to: " .. preserved_data.state) self.dynamic_data.state.current = mob_state.get_state_by_name(self,preserved_data.state) end self.dynamic_data.textureidx = preserved_data.textureidx end self.dynamic_data.custom_persistent = preserved_data.custom_persistent ---------------------------------------------------------------------------- -- initialize mob state -- ------------------------------------------------------------------------- local default_state = mob_state.get_state_by_name(self,"default") if self.dynamic_data.state.current == nil or (self.dynamic_data.state.current.state_mode ~= "auto" and self.dynamic_data.state.current.state_mode ~= "user_def" and self.dynamic_data.state.current.state_mode ~= nil) or (self.dynamic_data.state.current.HANDLER_precondition ~= nil and (not self.dynamic_data.state.current.HANDLER_precondition(self,self.dynamic_data.state.current))) then self.dynamic_data.state.current = default_state end --user defined states are locked per definition if self.dynamic_data.state.current.state_mode == "user_def" then mob_state.lock(self,true) end dbg_mobf.mobf_core_lvl2("MOBF: " .. self.data.name .. " restoring state: " .. self.dynamic_data.state.current.name) -- if there is a texturelist specified for this mob select one and stay there if default_state.graphics_3d.texturelist ~= nil then if self.dynamic_data.textureidx == nil then self.dynamic_data.textureidx = math.random(1, #default_state.graphics_3d.texturelist) end mob_state.switch_model(self, self.dynamic_data.state.current) end ---------------------------------------------------------------------------- -- initializing movement engine ---------------------------------------------------------------------------- if self.dynamic_data.state.current.movgen ~= nil then dbg_mobf.mobf_core_lvl1( "MOBF: setting movegen to: " .. self.dynamic_data.state.current.movgen) self.dynamic_data.current_movement_gen = getMovementGen(self.dynamic_data.state.current.movgen) else dbg_mobf.mobf_core_lvl1( "MOBF: setting movegen to: " .. default_state.movgen) self.dynamic_data.current_movement_gen = getMovementGen(default_state.movgen) end if self.dynamic_data.state.current.animation ~= nil then dbg_mobf.mobf_core_lvl1( "MOBF: setting animation to: " .. self.dynamic_data.state.current.animation) graphics.set_animation(self,self.dynamic_data.state.current.animation) else dbg_mobf.mobf_core_lvl1( "MOBF: setting animation to: " .. dump(default_state.animation)) graphics.set_animation(self,default_state.animation) end mobf_assert_backtrace(self.dynamic_data.current_movement_gen ~= nil) --initialize movegen entity,current time, permanent data self.dynamic_data.current_movement_gen.init_dynamic_data(self,now,preserved_data) --call enter state fct if self.dynamic_data.state.current.HANDLER_enter_state ~= nil then self.dynamic_data.state.current.HANDLER_enter_state(self) end --initialize armor groups if self.data.generic.armor_groups ~= nil then self.object:set_armor_groups(self.data.generic.armor_groups) end --initialize factions mobf_factions.setupentity(self,preserved_data.factions) --initialize height level environment.fix_base_pos(self, self.collisionbox[2] * -1) --custom on activate handler if (self.data.generic.custom_on_activate_handler ~= nil) then self.data.generic.custom_on_activate_handler(self) end --check may need data present after initialization has completed mobf.init_on_rightclick_callbacks(self,now) --add lifebar if minetest.world_setting_get("mobf_lifebar") then self.lifebar = mobf_lifebar.add(self) mobf_lifebar.set(self.lifebar,self.object:get_hp()/self.hp_max) end ---------------------------------------------------------------------------- -- activation may have been delayed due to quota -- ------------------------------------------------------------------------- if self.dynamic_data.delayed_placement ~= nil then self.dynamic_data.spawning.player_spawned = self.dynamic_data.delayed_placement.player_spawned self.dynamic_data.spawning.spawner = self.dynamic_data.delayed_placement.spawner if self.dynamic_data.delayed_placement.callback ~= nil then self.dynamic_data.delayed_placement.callback(self, self.dynamic_data.delayed_placement.placer, self.dynamic_data.delayed_placement.pointed_thing) end self.dynamic_data.delayed_placement = nil end --mark as initialized now self.dynamic_data.initialized = true mobf_step_quota.consume(starttime) end ------------------------------------------------------------------------------ -- @function [parent=#mobf] init_factions(entityn) -- --! @brief register mob to factions nod --! @memberof mobf --! @private -- --! @param entity entity to initialize ------------------------------------------------------------------------------- function mobf.init_factions(entity) if not mobf_rtd.factions_available then return end end ------------------------------------------------------------------------------ -- @function [parent=#mobf] register_entity(entityname,graphics) -- --! @brief register an entity --! @memberof mobf --! @private -- --! @param name of entity to add --! @param graphics graphics to use for entity --! @param mob data to use ------------------------------------------------------------------------------- function mobf.register_entity(name, cur_graphics, mob) dbg_mobf.mobf_core_lvl1("MOBF: registering new entity: " .. name) minetest.log(LOGLEVEL_NOTICE,"MOBF: registering new entity: \"" .. name .. "\"") mobf_assert_backtrace(environment_list[mob.generic.envid] ~= nil) local face_movement_dir = true if cur_graphics.model_orientation_fix ~= nil then face_movement_dir = (cur_graphics.model_orientation_fix / math.pi) * 360 + 90 end local footstep_sounds = true if mob.generic.makes_footstep_sound ~= nil then footstep_sounds = mob.generic.makes_footstep_sound end minetest.register_entity(name, { physical = true, collisionbox = cur_graphics.collisionbox, visual = cur_graphics.visual, textures = cur_graphics.textures, visual_size = cur_graphics.visual_size, spritediv = cur_graphics.spritediv, mesh = cur_graphics.mesh, mode = cur_graphics.mode, initial_sprite_basepos = {x=0, y=0}, makes_footstep_sound = footstep_sounds, automatic_rotate = true, groups = mob.generic.groups, hp_max = mob.generic.base_health, stepheight = mob.generic.stepheight, automatic_face_movement_dir = face_movement_dir, automatic_face_movement_max_rotation_per_sec = 180, -- actions to be done by mob on its own on_step = function(self, dtime) local starttime = mobf_get_time_ms() if self.removed == true then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: on_step: " .. self.data.name .. " on_step for removed entity???? (" .. tostring(self) .. ")") mobf_warn_long_fct(starttime,"on_step_total_removed","on_step_total") return end if self.dynamic_data == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: on_step: " .. "no dynamic data available!") mobf_warn_long_fct(starttime,"on_step_total_no_dyn","on_step_total") return end if (self.dynamic_data.initialized ~= true) then if entity_at_loaded_pos(self.object:getpos(),self.data.name) then mobf.activate_handler(self,self.dynamic_data.last_static_data) --if quota is exceeded activation is delayed don't continue --until initialization is done if self.dynamic_data.initialized ~= true then return end self.dynamic_data.last_static_data = nil else mobf_warn_long_fct(starttime,"on_step_total_no_init","on_step_total") return end end --do special ride callback if mobf_ride.on_step_callback(self) then mobf_warn_long_fct(starttime,"on_step_total_ride_cb","on_step_total") return end self.current_dtime = self.current_dtime + dtime if self.lifebar ~= nil then local luaentity = self.lifebar:get_luaentity() if luaentity ~= nil then luaentity.lifetime = 0 else mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: on_step: " .. "trying to update lifebar but no luaentity present!") self.lifebar = nil end end if mobf_step_quota.is_exceeded() then return end local quotatime = mobf_get_time_ms() local now = mobf_get_current_time() if self.current_dtime < 0.25 then mobf_warn_long_fct(starttime,"on_step_total_pre_update","on_step_total") mobf_step_quota.consume(quotatime) return end --check lifetime if spawning.lifecycle_callback(self,now) == false then mobf_warn_long_fct(starttime,"on_step_total_lifecycle","on_step_total") mobf_step_quota.consume(quotatime) return end local movestart = mobf_get_time_ms() --movement generator self.dynamic_data.current_movement_gen.callback(self,now) mobf_warn_long_fct(movestart,"on_step_movement","movement") if self.on_step_hooks ~= nil then if #self.on_step_hooks > 32 then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!!: " .. tostring(self) .. " incredible high number of onstep hooks! " .. #self.on_step_hooks .. " ohlist: " .. tostring(self.on_step_hooks)) end --dynamic modules for i = 1, #self.on_step_hooks, 1 do local cb_starttime = mobf_get_time_ms() --check return value if on_step hook tells us to stop any other processing if self.on_step_hooks[i](self,now,self.current_dtime) == false then dbg_mobf.mobf_core_lvl1("MOBF: on_step: " .. self.data.name .. " aborting callback processing entity=" .. tostring(self)) break end mobf_warn_long_fct(cb_starttime,"callback_os_" .. self.data.name .. "_" .. i,"callback nr " .. i) end end mobf_warn_long_fct(starttime,"on_step_" .. self.data.name .. "_total","on_step_total") self.current_dtime = 0 mobf_step_quota.consume(quotatime) end, --player <-> mob interaction on_punch = function(self, hitter, time_from_last_punch, tool_capabilities, dir) local starttime = mobf_get_time_ms() local now = mobf_get_current_time() if self.dynamic_data.initialized ~= true then return end for i = 1, #self.on_punch_hooks, 1 do if self.on_punch_hooks[i](self,hitter,now, time_from_last_punch, tool_capabilities, dir) then mobf_warn_long_fct(starttime,"onpunch_total","onpunch_total") return end mobf_warn_long_fct(starttime,"callback nr " .. i, "callback_op_".. self.data.name .. "_" .. i) end mobf_warn_long_fct(starttime,"onpunch_total","onpunch_total") end, --rightclick handler on_rightclick = mobf.rightclick_handler, --do basic mob initialization on activation on_activate = function(self,staticdata) local starttime = mobf_get_time_ms() self.dynamic_data = {} self.dynamic_data.initialized = false --check for replace in progress marker and transfer to entity if spawning.replacing_NOW then self.replaced = true end --make sure entity is in loaded area at initialization local pos = self.object:getpos() --remove from mob offline storage spawning.activate_mob(self.data.modname .. ":" .. self.data.name,pos) if pos ~= nil and entity_at_loaded_pos(pos,self.data.name) then mobf.activate_handler(self,staticdata) end if self.dynamic_data.initialized ~= true then minetest.log(LOGLEVEL_INFO, "MOBF: delaying activation") if self.dynamic_data.last_static_data == nil and staticdata ~= "" then self.dynamic_data.last_static_data = staticdata end end mobf_warn_long_fct(starttime,"onactivate_total_" .. self.data.name,"onactivate_total") end, getbasepos = mobf.get_basepos, get_persistent_data = mobf.get_persistent_data, owner = function(entity) if (entity.dynamic_data.spawning.spawner) then return entity.dynamic_data.spawning.spawner else return nil end end, set_state = function(entity, statename) local state = mob_state.get_state_by_name(entity,statename) if (state == nil) then return false end mob_state.change_state(entity,state) return true end, get_state = function(entity) local statename = entity.dynamic_data.state.current.name local state = mob_state.get_state_by_name(entity,statename) return statename, state end, is_on_ground = function(entity) local basepos = entity.getbasepos(entity) local posbelow = { x=basepos.x, y=basepos.y-1,z=basepos.z} for dx=-1,1 do for dz=-1,1 do local fp0 = posbelow local fp = { x= posbelow.x + (0.5*dx), y= posbelow.y, z= posbelow.z + (0.5*dz) } local n = minetest.get_node(fp) if not mobf_is_walkable(n) then return true end end end return false end, --prepare permanent data --NOTE this isn't called if a object is deleted get_staticdata = function(self) --add to mob offline storage spawning.deactivate_mob(self.data.modname .. ":" .. self.data.name,self.object:getpos()) return mobf_serialize_permanent_entity_data(self) end, --custom variables for each mob data = mob, environment = environment_list[mob.generic.envid], current_dtime = 0, } ) end ------------------------------------------------------------------------------- -- @function [parent=#mobf] rightclick_handler(entity,clicker) -- --! @brief handle rightclick of mob --! @memberof mobf --! @private -- --! @param entity --! @param clicker -- --! @return true/false if handled or not ------------------------------------------------------------------------------- function mobf.rightclick_handler(entity,clicker) local starttime = mobf_get_time_ms() if entity.dynamic_data.initialized ~= true then return end if #entity.on_rightclick_hooks >= 1 then --get rightclick storage id local storage_id = mobf_global_data_store(entity) local y_pos = 0.25 local buttons = "" local playername = clicker:get_player_name() for i = 1, #entity.on_rightclick_hooks, 1 do if entity.on_rightclick_hooks[i].privs == nil or minetest.check_player_privs(playername, entity.on_rightclick_hooks[i].privs) or minetest.is_singleplayer() then local callback_storage_id = mobf_global_data_store(entity.on_rightclick_hooks[i].handler) buttons = buttons .. "button_exit[0," .. y_pos .. ";2.5,0.5;" .. "mobfrightclick_" .. storage_id .. "_" .. callback_storage_id .. ";" if type(entity.on_rightclick_hooks[i].visiblename) == "function" then buttons = buttons .. entity.on_rightclick_hooks[i].visiblename(entity, clicker) .. "]" else buttons = buttons .. entity.on_rightclick_hooks[i].visiblename .. "]" end y_pos = y_pos + 0.75 end end local y_size = y_pos local formspec = "size[2.5," .. y_size .. "]" .. buttons if playername ~= nil then --TODO start form close timer minetest.show_formspec(playername,"mobf_rightclick:main",formspec) end return true end if #entity.on_rightclick_hooks == 1 then entity.on_rightclick_hooks[1].handler(entity,clicker) return true end return false end ------------------------------------------------------------------------------- -- @function [parent=#mobf] rightclick_button_handler(player,formname,fields) -- --! @brief handle button clicks as result of rightclick of mob --! @memberof mobf --! @private -- --! @param player issuer of rightclick --! @param formname name of form that has been clicked --! @param fields fields passed to form -- --! @return true/false if handled or not ------------------------------------------------------------------------------- function mobf.rightclick_button_handler(player, formname, fields) if formname == "mobf_rightclick:main" then for k,v in pairs(fields) do local parts = string.split(k,"_") if parts[1] == "mobfrightclick" then local entity_store_id = parts[2] local callback_store_id = parts[3] dbg_mobf.mobf_core_lvl1("MOBF: rightclick button handler got: " .. dump(fields) .. " parted to: " .. dump(parts)) local entity = mobf_global_data_get(entity_store_id) local callback = mobf_global_data_get(callback_store_id) if entity ~= nil and callback ~= nil then callback(entity, player) else dbg_mobf.mobf_core_lvl1("MOBF: unable to do callback: " .. dump(entity) .. " " .. dump(callback)) end end end return true end return false end ------------------------------------------------------------------------------- -- @function [parent=#mobf] register_mob_item(mob) -- --! @brief add mob item for catchable mobs --! @memberof mobf --! @private -- --! @param name name of mob --! @param modname name of mod mob is defined in --! @param description description to use for mob --! @param imagename name of itemimage to use ------------------------------------------------------------------------------- function mobf.register_mob_item(name,modname,description, imagename) minetest.register_craftitem(modname..":"..name, { description = description, image = imagename or modname.."_"..name.."_item.png", on_place = function(item, placer, pointed_thing) if pointed_thing.type == "node" then local pos = pointed_thing.above local entity = spawning.spawn_and_check(modname..":"..name,pos,"item_spawner") if entity ~= nil then if entity.dynamic_data.spawning ~= nil then entity.dynamic_data.spawning.player_spawned = true if placer:is_player(placer) then minetest.log(LOGLEVEL_INFO,"MOBF: mob placed by " .. placer:get_player_name(placer)) entity.dynamic_data.spawning.spawner = placer:get_player_name(placer) end if entity.data.generic.custom_on_place_handler ~= nil then entity.data.generic.custom_on_place_handler(entity, placer, pointed_thing) end else -------------------------------------------------------- -- quota may have been exceeded make sure no data is lost -------------------------------------------------------- entity.dynamic_data.delayed_placement = { player_spawned = true, spawner = placer:get_player_name(placer), placer = placer, pointed_thing = pointed_thing, callback = entity.data.generic.custom_on_place_handler } end item:take_item() end return item end end }) end ------------------------------------------------------------------------------- -- @function [parent=#mobf] blacklisthandling(mob) -- --! @brief add mob item for catchable mobs --! @memberof mobf --! @public -- --! @param mob ------------------------------------------------------------------------------- function mobf.blacklisthandling(mob) dbg_mobf.mobf_core_lvl2("MOBF: blacklisthandling for " .. mob.modname .. ":" .. mob.name) local blacklisted = minetest.registered_entities[mob.modname.. ":"..mob.name] local on_activate_fct = nil --remove unknown animal objects if minetest.world_setting_get("mobf_delete_disabled_mobs") then on_activate_fct = function(self,staticdata) self.object:remove() end --cleanup spawners too if minetest.registered_entities[mob.modname.. ":"..mob.name] == nil and environment_list[mob.generic.envid] ~= nil then if type(mob.spawning.primary_algorithms) == "table" then for i=1 , #mob.spawning.primary_algorithms , 1 do local sp = mob.spawning.primary_algorithms[i] local cleanup = mobf_spawn_algorithms[sp.algorithm].initialize_cleanup dbg_mobf.mobf_core_lvl2("MOBF: blacklist cleanup for primary spawner " .. sp.algorithm) if type(cleanup) == "function" then cleanup(mob.modname.. ":" .. mob.name .. "_spawner_" .. sp.algorithm) else dbg_mobf.mobf_core_lvl2("MOBF: blacklist cleanup impossible - no cleanup function defined") end end end if type(mob.spawning.secondary_algorithms) == "table" then for i=1 , #mob.spawning.secondary_algorithms , 1 do local sp = mob.spawning.secondary_algorithms[i] local cleanup = mobf_spawn_algorithms[sp.algorithm].initialize_cleanup dbg_mobf.mobf_core_lvl2("MOBF: blacklist cleanup for secondary spawner " .. sp.algorithm) if type(cleanup) == "function" then cleanup(mob.modname.. ":" .. mob.name .. "_spawner_" .. sp.algorithm) else dbg_mobf.mobf_core_lvl2("MOBF: blacklist cleanup impossible - no cleanup function defined") end end end end else on_activate_fct = function(self,staticdata) self.object:setacceleration({x=0,y=0,z=0}) self.object:setvelocity({x=0,y=0,z=0}) end end if minetest.registered_entities[mob.modname.. ":"..mob.name] == nil then --cleanup mob entities minetest.register_entity(mob.modname.. ":"..mob.name, { on_activate = on_activate_fct }) end if blacklisted == nil then minetest.log(LOGLEVEL_INFO,"MOBF: " .. mob.modname.. ":"..mob.name .. " was blacklisted") else minetest.log(LOGLEVEL_ERROR,"MOBF: " .. mob.modname.. ":"..mob.name .. " already known not registering mob with same name!") end end ------------------------------------------------------------------------------- --- @function [parent=#mobf] preserve_removed(entity,reason) --- ---! @brief check if a mob needs to be preserved ---! @memberof mobf ---! @public --- ---! @param entity entity to check ---! @param reason reason for removal -------------------------------------------------------------------------------- function mobf.preserve_removed(entity,reason) if reason ~= "cought" and reason ~= "killed" and reason ~= "died by sun" and reason ~= "replaced" then if entity.dynamic_data.spawning.player_spawned then local toset = {} toset.modname = entity.data.modname toset.name = entity.data.name toset.owner = entity.dynamic_data.spawning.spawner toset.reason = reason if toset.owner ~= nil then dbg_mobf.mobf_core_lvl2("MOBF: preserving " .. toset.modname .. ":" .. toset.name .. " for player " .. toset.owner ) table.insert(mobf.current_preserve_list,toset) mobf_set_world_setting("mobf_preserve_mobs", minetest.serialize(mobf.current_preserve_list)) else dbg_mobf.mobf_core_lvl1("MOBF: unable to preserve mob") end else dbg_mobf.mobf_core_lvl2("MOBF: not preserving " .. entity.data.name .. " it's not playerspawned: " .. dump(entity.dynamic_data.spawning) ) end else dbg_mobf.mobf_core_lvl2("MOBF: not preserving " .. entity.data.name .. " removed by valid reason" ) end end ------------------------------------------------------------------------------- --- @function [parent=#mobf] check_definition(definition) -- --! @brief check if a mob definition contains obvious errors --! @memberof mobf --! @public -- --! @param entity entity to check --! @return true/false -------------------------------------------------------------------------------- function mobf.check_definition(definition) if definition.name == nil or definition.modname == nil then minetest.log(LOGLEVEL_ERROR,"MOBF: name and modname are mandatory for ALL mobs!") return false end local default_state = nil for i,v in ipairs(definition.states) do if v.name == "default" then default_state = v break end end if default_state == nil then minetest.log(LOGLEVEL_ERROR,"MOBF: default state is mandatory for ALL mobs!") return false end if default_state.movgen == nil then minetest.log(LOGLEVEL_ERROR,"MOBF: movgen has to be specified for default state!") return false end if definition.attention then if not definition.attention.attention_distance then minetest.log(LOGLEVEL_ERROR,"MOBF: \"attention\" requires " .. "\"attention.attention_distance\" to be present.") return false end if not definition.attention.attention_max then minetest.log(LOGLEVEL_ERROR,"MOBF: \"attention\" requires " .. "\"attention.attention_max\" to be present.") return false end if not definition.attention.attention_distance_value then minetest.log(LOGLEVEL_ERROR,"MOBF: \"attention\" requires " .. "\"attention.attention_distance_value\" to be present.") return false end if definition.attention.hear_distance and not definition.attention.hear_distance_value then minetest.log(LOGLEVEL_ERROR,"MOBF: \"attention.hear_distance\" requires " .. "\"attention.hear_distance_value\" to be present.") return false end end if definition.combat then if definition.combat.melee then if not definition.combat.melee.range then minetest.log(LOGLEVEL_ERROR,"MOBF: \"combat.melee\" requires " .. "\"combat.melee.range\" to be present.") return false end if not definition.combat.melee.maxdamage then minetest.log(LOGLEVEL_ERROR,"MOBF: \"combat.melee\" requires " .. "\"combat.melee.maxdamage\" to be present.") return false end if not definition.combat.melee.speed then minetest.log(LOGLEVEL_ERROR,"MOBF: \"combat.melee\" requires " .. "\"combat.melee.speed\" to be present.") return false end end end -- TODO check all mandatory definition elements! return true end mobf_core-2.5.1/mobf/models/000077500000000000000000000000001264104133000156525ustar00rootroot00000000000000mobf_core-2.5.1/mobf/models/mobf_path_marker.b3d000066400000000000000000000263631264104133000215560ustar00rootroot00000000000000BB3D,BRUS.Brush.001????NODE,Cube+nÿ@@,@@@?MESHt,VRTS&?_/uU:?p>0<=&@?_/?uU:?`>d?G>&@{?#i>_/?n6?ͮ>g?G>{?#i>_/n6?O7>Pd=G>{?#i>_/n6?O7>Pd=G>&@{?#i>_/?n6?ͮ>g?>&@_l?>_/?X',?պ>"?>_l?>_/X',?/@> =>_l?>_/X',?/@> =>&@_l?>_/?X',?պ>"?9?&@1T? >_/?6?>d?9?1T? >_/6?>(=9?1T? >_/6?>(=9?&@1T? >_/?6?>d?5?&@5??_/??>?5?5??_/?J>=5?5??_/?TvM>?5?&@5??_/??JP>=1T?&@9?6?_/? >=>=1T?9?6?_/ >T ;>b?1T?9?6?_/ >T ;>b?1T?&@9?6?_/? >=>=_l?&@>X',?_/?>z5(>=_l?>X',?_/>`%>!?_l?>X',?_/>`%>!?_l?&@>X',?_/?>z5(>={?&@G>n6?_/?#i>#>8d={?G>n6?_/#i>(O >g?{?G>n6?_/#i>(O >g?{?&@G>n6?_/?#i>#>8d=?&@i!3uU:?_/?,=<=?i!3uU:?_/ƒ=c??i!3uU:?_/ƒ=c??&@i!3uU:?_/?,=<={?&@Gn6?_/?#i=8={?Gn6?_/#ih=F?{?Gn6?_/#ih=F?{?&@Gn6?_/?#i=8=_l?&@þX',?_/?==_l?þX',?_/\N=A?_l?þX',?_/\N=A?_l?&@þX',?_/?==2T?&@96?_/? Ͼ+=@=2T?96?_/ ϾU =?2T?96?_/ ϾU =?2T?&@96?_/? Ͼ+=@=5?&@5?_/?<(=5?5?_/`_/?6$5;8=9?2T >_/6?9?2T >_/6T?8=9?&@2T >_/?6.??>&@_l>_/?X',l?&?>_l>_/X',!?@=>_l>_/X',!?@=>&@_l>_/?X',l?&?G>&@{#i>_/?n6p??G>{#i>_/n6&?=G>{#i>_/n6&?=G>&@{#i>_/?n6p??L﮴&@_/?uU:K$??L﮴_/uU:q%?00=L﮴_/uU:q%?00=L﮴&@_/?uU:K$??G&@{#i_/?n6W+??G{#i_/n6 ,? Y=G{#i_/n6 ,? Y=G&@{#i_/?n6W+??$þ&@\l_/?X',1??$þ\l_/X',p2?@=$þ\l_/X',p2?@=$þ&@\l_/?X',1??9&@,T Ͼ_/?6h7??9,T Ͼ_/6t8?=9,T Ͼ_/6t8?=9&@,T Ͼ_/?6h7??5&@5_/?)=??55_/=?=55_/M??5&@5_/?.?=8T&@96_/? Ͼҗ ?=8T96_/ Ͼ ??8T96_/ Ͼ ??8T&@96_/? Ͼҗ ?=dl&@þX',_/?b?0=dlþX',_/=??dlþX',_/=??dl&@þX',_/?b?0={&@Gn6_/?#i0?X={Gn6_/#iz??{Gn6_/#iz??{&@Gn6_/?#i0?X=&@5uU:_/?fY>0=5uU:_/>?5uU:_/>?&@5uU:_/?fY>0={&@G>n6_/?#i>Ē>={G>n6_/#i>{(>?{G>n6_/#i>{(>?{&@G>n6_/?#i>Ē>=Wl&@8>X',_/?>> =Wl8>X',_/>>&?Wl8>X',_/>>&?Wl&@8>X',_/?>> =&T&@9?6_/? >.>=&T9?6_/ >J>?&T9?6_/ >S>`=&T&@9?6_/? >JP>?5&@5?_/??b>F?55?_/?e>P=55?_/?e>P=5&@5?_/??b>F?9&@>T? Ͼ_/?6?X`x>?9>T? Ͼ_/6?4{>X=9>T? Ͼ_/6?4{>X=9&@>T? Ͼ_/?6?X`x>?þ&@hl?_/?X',? 9>@?þhl?_/X',?X>(=G>&@{?#i>_/?n6??t??&@?_/?uU:?n?t>]G&@{?#i_/?n6?sh?P>G>&@{?#i>_/?n6??t??]G&@{?#i_/?n6?sh?P>þ&@hl?_/?X',? 1b?{>G>&@{?#i>_/?n6??t??þ&@hl?_/?X',? 1b?{>>&@_l?>_/?X',?@x??þ&@hl?_/?X',? 1b?{>9&@>T? Ͼ_/?6?­[?{>>&@_l?>_/?X',?@x??9&@>T? Ͼ_/?6?­[?{>9?&@1T? >_/?6?<|?$ ?>&@_l?>_/?X',?@x??9&@>T? Ͼ_/?6?­[?{>5&@5?_/??nJU?N>9?&@1T? >_/?6?<|?$ ?5&@5?_/??nJU?N>5?&@5??_/??~?a)?9?&@1T? >_/?6?<|?$ ?5&@5?_/??nJU?N>&T&@9?6_/? >EO?r>5?&@5??_/??~?a)?&T&@9?6_/? >EO?r>1T?&@9?6?_/? >??5?&@5??_/??~?a)?&T&@9?6_/? >EO?r>Wl&@8>X',_/?>I??1T?&@9?6?_/? >??Wl&@8>X',_/?>I??_l?&@>X',?_/?>? ?1T?&@9?6?_/? >??Wl&@8>X',_/?>I??{&@G>n6_/?#i>@E??_l?&@>X',?_/?>? ?{&@G>n6_/?#i>@E??{?&@G>n6?_/?#i>~?fs&?_l?&@>X',?_/?>? ?{&@G>n6_/?#i>@E??&@5uU:_/?FA?$ ?{?&@G>n6?_/?#i>~?fs&?&@5uU:_/?FA?$ ??&@i!3uU:?_/?<|?w,?{?&@G>n6?_/?#i>~?fs&?&@5uU:_/?FA?$ ?{&@Gn6_/?#i3$??`)??&@i!3uU:?_/?<|?w,?{&@Gn6_/?#i3$??`)?{?&@Gn6?_/?#i@x?41??&@i!3uU:?_/?<|?w,?{&@Gn6_/?#i3$??`)?dl&@þX',_/?=??{?&@Gn6?_/?#i@x?41?dl&@þX',_/?=??_l?&@þX',?_/??t?6}6?{?&@Gn6?_/?#i@x?41?dl&@þX',_/?=??8T&@96_/? Ͼ=? ?_l?&@þX',?_/??t?6}6?8T&@96_/? Ͼ=? ?2T?&@96?_/? Ͼn?:?_l?&@þX',?_/??t?6}6?8T&@96_/? Ͼ=? ?5&@5_/?2$??ds&?2T?&@96?_/? Ͼn?:?5&@5_/?2$??ds&?5?&@5?_/?ph?_/?61b?=?5?&@5?_/?ph?_/?61b?=?$þ&@\l_/?X',@E?41?>&@_l>_/?X',[?=?9?&@2T >_/?61b?=?$þ&@\l_/?X',@E?41?G&@{#i_/?n6I?5}6?>&@_l>_/?X',[?=?G&@{#i_/?n6I?5}6?G>&@{#i>_/?n6kJU?&@_l>_/?X',[?=?G&@{#i_/?n6I?5}6?L﮴&@_/?uU:EO?:?G>&@{#i>_/?n6kJU?d??_/uU:?p>0<=]G{?#i_/n6?i>H=]G&@{?#i_/?n6?>G?þhl?_/X',?X>(=þ&@hl?_/?X',? 9>@?]G&@{?#i_/?n6?>G?]G{?#i_/n6?i>H=?_/uU:?*A?n?G>{?#i>_/n6?v@E?t?]G{?#i_/n6?%$??8h?G>{?#i>_/n6?v@E?t?þhl?_/X',?=?0b?]G{?#i_/n6?%$??8h?G>{?#i>_/n6?v@E?t?>_l?>_/X',?mI?x?þhl?_/X',?=?0b?>_l?>_/X',?mI?x?9>T? Ͼ_/6?=?[?þhl?_/X',?=?0b?>_l?>_/X',?mI?x?9?1T? >_/6?EO?|<|?9>T? Ͼ_/6?=?[?9?1T? >_/6?EO?|<|?55?_/?K$??2JU?9>T? Ͼ_/6?=?[?9?1T? >_/6?EO?|<|?5?5??_/?/JU?~?55?_/?K$??2JU?5?5??_/?/JU?~?&T9?6_/ >jA?EO?55?_/?K$??2JU?5?5??_/?/JU?~?1T?9?6?_/ >[??&T9?6_/ >jA?EO?1T?9?6?_/ >[??Wl8>X',_/>@E?pI?&T9?6_/ >jA?EO?1T?9?6?_/ >[??_l?>X',?_/>0b??Wl8>X',_/>@E?pI?_l?>X',?_/>0b??{G>n6_/#i>I?w@E?Wl8>X',_/>@E?pI?_l?>X',?_/>0b??{?G>n6?_/#i>4h?ĺ~?{G>n6_/#i>I?w@E?{?G>n6?_/#i>4h?ĺ~?5uU:_/;FO?,A?{G>n6_/#i>I?w@E?{?G>n6?_/#i>4h?ĺ~??i!3uU:?_/n?<|?5uU:_/;FO?,A??i!3uU:?_/n?<|?{Gn6_/#iJU?%$??5uU:_/;FO?,A??i!3uU:?_/n?<|?{?Gn6?_/#it?sx?{Gn6_/#iJU?%$??{?Gn6?_/#it?sx?dlþX',_/[?=?{Gn6_/#iJU?%$??{?Gn6?_/#it?sx?_l?þX',?_/x?{t?dlþX',_/[?=?_l?þX',?_/x?{t?8T96_/ Ͼf1b?=?dlþX',_/[?=?_l?þX',?_/x?{t?2T?96?_/ Ͼ|<|?%n?8T96_/ Ͼf1b?=?2T?96?_/ Ͼ|<|?%n?55_/h?L$??8T96_/ Ͼf1b?=?2T?96?_/ Ͼ|<|?%n?5?5?_/~?h?55_/h?L$??5?5?_/~?h?9,T Ͼ_/6$n?kA?55_/h?L$??5?5?_/~?h?9?2T >_/6?g1b?9,T Ͼ_/6$n?kA?9?2T >_/6?g1b?$þ\l_/X',zt?@E?9,T Ͼ_/6$n?kA?9?2T >_/6?g1b?>_l>_/X',? [?$þ\l_/X',zt?@E?>_l>_/X',? [?G{#i_/n6qx?I?$þ\l_/X',zt?@E?>_l>_/X',? [?G>{#i>_/n6ú~?JU?G{#i_/n6qx?I?G>{#i>_/n6ú~?JU?L﮴_/uU:<|?=<?><BA@CB@FEDGFDJIHKJHNMLONLRQPSRPVUTWVTZYX[ZX^]\_^\ba`cb`fedgfdjihkjhnmlonlrqpsrpvutwvtzyx}|{~     ! $#"'&%*)(-,+0/.321mobf_core-2.5.1/mobf/models/path_marker.blend000066400000000000000000015347101264104133000211700ustar00rootroot00000000000000BLENDER-v263RENDH 2SceneTEST^ ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `)))FFFFFFFFFDDDBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;666222222222222222222222../++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-6,-6,-6,-6,-612: ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `)))FFFFFFFFFFFFDDDBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;666222222222222222222222../++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-6,-6,-6,-6,-645@+,2 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `===FFFFFFFFFFFFCCCBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;666222222222222222222222../++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-6,-6,-6,-6,-612<78C%&) ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333GGGFFFFFFFFFFFFBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;222222222222222222222222../++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-5,-6,-6,-6,-612<78C78C%&) ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `)))GGGGGGFFFFFFFFFFFFBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;222222222222222222222222../++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-5,-6,-6,-6,-612<78C78C78C ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `===GGGGGGFFFFFFFFFFFFBBBBBBBBBBBBBBB@@@;;;;;;;;;;;;;;;;;;;;;222222222222222222222222../++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-5,-6,-6,-6,-612<78C78C78C017 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `)))GGGGGGGGGFFFFFFFFFDDDBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;222222222222222222222222++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-5,-6,-6,-6,-612<78C78C78C=?K ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333GGGGGGGGGFFFFFFFFFDDDBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;222222222222222222222222++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-5,-6,-6,-6,-6,-678C78C78C;=I017 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGFFFFFFFFFFFFDDDBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;222222222222222222222222++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-5,-6,-6,-6,-6,-678C78C78C;=I@AN ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `)))FFFGGGGGGFFFFFFFFFFFFCCCBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;999222222222222222222222222++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-5,-6,-6,-6,-6,-678C78C78C;=I@AN017 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGFFFFFFFFFFFFBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4,-5,-6,-6,-6,-6,-678C78C78C;=I@AN017 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `<<>>;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-6,-6,-6,-6,-612<78C78C78C@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGFFFFFFFFFFFFDDDBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-6,-6,-6,-6,-612<78C78C78C>?L@AN229 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `<<>>;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-6,-6,-6,-6,-612<78C78C78C;=I@AN9:E ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGFFFFFFFFFFFFBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;222222222222222222222222000++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-6,-6,-6,-6,-612<78C78C78C;=I@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGFFFFFFFFFFFFBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-678C78C78C;=I@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `)))FFFGGGGGGFFFFFFFFFFFFEEEBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-678C78C78C;=I@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-678C78C78C9;F@AN@AN ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;999222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-678C78C78C78C@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `<<>>;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-645@78C78C78C@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGFFFFFFFFFFFFBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0--3--4--4--4--4--4--4,-5,-6,-6,-6,-6,-612<78C78C78C@AN@AN229 ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGFFFFFFFFFFFFBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-5,-6,-6,-6,-6,-612<78C78C78C;=I@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4,-4,-6,-6,-6,-6,-612<78C78C78C;=I@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;444222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4,-6,-6,-6,-6,-612<78C78C78C;=I@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222../++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4,-6,-6,-6,-6,-612<78C78C78C;=I@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvdddddddddddddddVVXVVXVVXVVXWX\YZaYZaYZaYZaZ[hZ[hZ[hZ[hXZlXZl^`snqwz@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlnqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlnqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlnqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlnqnqnqnqdes@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZkXZlXZlXZlXZlXZlnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlceynqnqnqwz@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlceynqnqnqwz@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlceynqnqnqwz@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlceynqnqnqwzST[@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlceynqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvviiidddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlceynqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[iXZlXZlXZlXZlXZlXZlnqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@yyyvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlnqnqnqnqwz@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlnqnqnqnqwz@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqwzST[@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@SSS?vvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqsvghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZkXZlXZlXZlXZlXZlceynqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@yyy}}}vvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlceynqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZl^`snqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvviiiddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@SSS?vvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnq|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaZ[fZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnqwz@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnqwzST[@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@yyyvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[iXZlXZlXZlXZlXZlXZliknqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqsvghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@SSS?}}}vvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@yyyvvvvvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnq|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnqwzST[@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvviiiddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZkXZlXZlXZlXZlXZlXZlnqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlnqnqnqnqsvghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlceynqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlceynqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvrrrdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlceynqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlceynqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[iXZlXZlXZlXZlXZlXZlceynqnqnqnqwzST[A@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlceynqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddddddZZ[VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnqwzz|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZliknqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZkXZlXZlXZlXZlXZlXZlceynqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlceynqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlceynqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvrrrdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlceynqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@yyy}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaZ[fZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlceynqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZl^`snqnqnqnqwzz|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlXZlnqnqnqnqwz@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@SSS?}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddddddVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[jXZlXZlXZlXZlXZlXZlXZlnqnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvmmmddddddddddddddddddddddddddddddaaaVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hY[iXZlXZlXZlXZlXZlXZlXZlnqnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fffvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvmmmdddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@yyyvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlnqnqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZliknqnqnqnqghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlceynqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@SSS?}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlceynqnqnqnqwzghw@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@fff}}}vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvddddddddddddddddddddddddddddddddd]]^VVXVVXVVXVVXVVXVVXVVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaYZaYZaYZaYZaYZaYZdZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hZ[hXZlXZlXZlXZlXZlXZlXZlceynqnqnqnqwzz|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` `@@@@@@@@@@@@@@@@@@}}}vvvvvvvvvvvvvvvddddddddddddddd]]^VVXVVXVVXVVXVVXWX\YZaYZaYZaYZaYZaZ[hZ[hZ[hZ[hZ[hXZlXZlXZlceynqnq@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ` ` ` ` ` ` ` ` ` ` ` ` ` `<<>>;;;;;;;;;;;;;;;;;;;;;;;;;;;999222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-612<78C78C78C78C9;F@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `)))FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C@AN@ANACP34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C@AN@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGFFFFFFFFFFFFFFFFFFCCCBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C>?L@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `<<>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C;=I@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C;=I@AN@AN=>I ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4,-4,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C;=I@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C;=I@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB===;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C9;F@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGFFFFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C78C@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFFFFGGGGGGGGGFFFFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-6/0978C78C78C78C78C@AN@ANACP34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;999222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C@AN@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C@AN@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C>?L@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFEEEBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222222++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C;=I@AN@AN=>I ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C;=I@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFFFFGGGGGGGGGFFFFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--4--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-645@78C78C78C78C;=I@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB===;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;444222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0--3--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C;=I@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C9;F@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C78C@AN@ANACP34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C78C@AN@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFEEEBBBBBBBBBBBBBBBBBBBBBBBBBBB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-5,-6,-6,-6,-6,-6,-6,-612<78C78C78C78C78C@AN@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFFFFGGGGGGGGGFFFFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4,-4,-6,-6,-6,-6,-6,-6,-6/0978C78C78C78C78C@AN@AN@AN34; ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C;=I@AN@AN=>I ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `)))FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFDDDBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C;=I@AN@ANEFT ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFCCCBBBBBBBBBBBBBBBBBBBBBBBB>>>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C;=I@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `333FFFGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFBBBBBBBBBBBBBBBBBBBBBBBBBBB===;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;666222222222222222222222222222222222222../++,++,++,++,++,++,++,++,++,++,++,++,+,.,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-0,-2--4--4--4--4--4--4--4--4--4--4,-6,-6,-6,-6,-6,-6,-6,-678C78C78C78C78C;=I@AN@ANCDR ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `GLOB0p2 0#^ ^ /home/sapier/hobbys/workspace_20120207_minetest/mobf/models/path_marker.blendWMXxZ^WMWinMan\^\^\^\^( ^X^8t^^8t^(u^^8 ^8 ^8 ^DATA\^x^#^screenOH^C^8'^8'^P^HO^H^P^8F^(^SNh]^X^SRAnimation.001^(_^_^k^$^^ ^DATA ^^DATA ^x^^DATA x^$^^DATA $^^x^DATA ^hj^$^DATA hj^(b^^DATA (b^^hj^DATA ^X^(b^DATA X^ȁ^^<DATA ȁ^F^X^<DATA F^HS^ȁ^XDATA HS^H^F^XDATA H^ȋ^HS^XDATA ȋ^^H^DATA ^^ȋ^DATA ^^^^DATA ^^(_^^<DATA (_^^^<DATA(_^`^x^^DATA(`^x`^_^^^DATA(x`^`^`^hj^x^DATA(`^Xa^x`^hj^^DATA(Xa^a^`^^(b^DATA(a^8b^Xa^$^(b^DATA(8b^b^a^hj^^DATA(b^c^8b^X^(b^DATA(c^c^b^$^ȁ^DATA(c^c^c^X^ȁ^DATA(c^hd^c^^F^DATA(hd^d^c^HS^^DATA(d^He^hd^HS^(b^DATA(He^e^d^HS^F^DATA(e^(f^He^H^F^DATA((f^f^e^H^HS^DATA(f^g^(f^^ȋ^DATA(g^xg^f^^ȋ^DATA(xg^g^g^H^ȋ^DATA(g^Xh^xg^^F^DATA(Xh^h^g^^H^DATA(h^8i^Xh^^^DATA(8i^i^h^X^^^DATA(i^j^8i^^^^DATA(j^j^i^hj^(_^DATA(j^j^j^ȁ^(_^DATA(j^hk^j^^^(_^DATA(hk^k^j^^^DATA(k^hk^^ȋ^DATA$^(o^^^x^hj^(^(^Hl^m^DATA(Hl^m^DA DADADA?? DATA(m^Hl^mEmEpoo?? pDATA(o^^$^(b^X^ȁ^$^;<x^x^p^q^DATA(p^q^CAb'&6XCAWCAWCA?? ";DATA(q^p^XC=GC!>!?@ ""!"r^؄^DATAXr^t^BUTTONS_PT_contextBUTTONS_PT_contextContext$DATAXt^8v^r^RENDER_PT_renderRENDER_PT_renderRender=DATAX8v^w^t^RENDER_PT_layersRENDER_PT_layersLayersoDATAXw^xy^8v^RENDER_PT_dimensionsRENDER_PT_dimensionsDimensionsDATAXxy^{^w^RENDER_PT_antialiasingRENDER_PT_antialiasingAnti-Aliasing::DATAX{^|^xy^RENDER_PT_motion_blurRENDER_PT_motion_blurSampled Motion Blur"DATAX|^X~^{^RENDER_PT_shadingRENDER_PT_shadingShading DATAXX~^^|^RENDER_PT_performanceRENDER_PT_performancePerformanceDATAX^^X~^RENDER_PT_post_processingRENDER_PT_post_processingPost ProcessingDATAX^8^^RENDER_PT_stampRENDER_PT_stampStamp DATAX8^؄^^RENDER_PT_outputRENDER_PT_outputOutput( DATAX؄^8^RENDER_PT_bakeRENDER_PT_bakeBake DATAx^DATA^^(o^^F^HS^(b^WX^^^^DATA(^^ DA'7DADADA?? DATA(^^@~CHBpF}CHB=?HB|HHB= AH>W>DATA^DATA^^^X^^^(_^ȁ^=;x^x^^^DATA(^^CAb'&6XCAWCAWCA?? ";DATA(^^CGCS?? =!DATAx^^DATA^ؑ^DATAؑ^ ^ ^ ^ ^7^(@^^\^r^E^'^h:^b^ ^DATA^^^H^ȋ^^HS^YWj^^^^DATA(^h^@qDADAVDADA?? WWYrWDATA(h^ؖ^^C@FCF++?@ ,sPDATA(ؖ^H^h^CfCww?@ xfss"DATA(H^^ؖ^#C`#C`?@ sDATA(^H^sWP(^DATAp(^?ȧ? JLD>3;Q?Fwi?JF>#,TY!e?*=>o?E>Fwi?TY5;JF>!e?Q?#,+=>`DAoy@?>Ѣ0QQuZ? r[>?>#,>mi}?T*= lAoAc>ntU?@FR0ݹ,-3> O?8Clu·Ai>rB;B7@D>3;Q?Fwi?JF>#,TY!e?*=>o?>Ѣ0QQuZ? r[>?>#,>mi}?T*= lAoA [@ [@ [@?\>7?8˔oAoAH;? ! BVBq~BDATA`^333?? AL>h:^ B?=C DATA^H^^F^^^H^YH^H^^إ^DATA(^^@YDACACACA?? YrDATA(^h^^HCpHC?? sDATA(h^إ^^sDATA(إ^h^C@zC Ao:o:|HPCGisDATAH^^DATAp^% ^DATAH^^^^^ȋ^^ ^^8^^DATA(8^^^DACACACA?? DATA(^^8^7CHC??DATA(^^ hDH hD|H@F #<HBJDATAH^& ^DATA^H^^^^hj^(_^=^^^x^DATA(^x^fDAC@AICACA?? JJ==DATA(x^^=^DATAp^8 @d@AHMݕ/?V~'?3F:?>T8175e?4>Z& 4?ߕ/?7F:?81X~>75e?'?T3>ne@>N@?/?=I''? ??T?ļL@ l4}@~q11A 4AF>>֟ļG=->3xkBˇ֟&B|`eA(@ݕ/?V~'?3F:?>T8175e?4>Z& 4?/?=I''? ??T?ļL@ l4}@~q11A 4Afga@fga@fga@?H?N+Z# A;A??ABcZBvBDATA`^333?? AL>h:^ B? #<C SNX^#^h]^SRCompositingg.001^X^^^h^^ ^DATA ^^DATA ^^^DATA ^^^~DATA ^h^^~DATA h^ؼ^^DATA ؼ^H^h^~DATA H^^ؼ^ \DATA ^(^H^~\DATA (^^^ DATA ^^(^DATA ^x^^ DATA x^^^DATA ^X^x^DATA X^^ DATA(^8^^^DATA(8^^^^h^DATA(^^8^^ؼ^DATA(^^^h^ؼ^DATA(^^^^^DATA(^h^^H^^DATA(h^^^ؼ^(^DATA(^H^h^h^(^DATA(H^^^H^(^DATA(^(^H^ؼ^^DATA((^^^h^^DATA(^^(^(^^DATA(^x^^^^DATA(x^^^^x^DATA(^X^x^^x^DATA(X^^^^^DATA(^8^X^^X^DATA(8^^^^X^DATA(^^8^H^X^DATA(^^^x^^DATA(^^^^X^DATA(^^^^DATAh^8^h^^^ؼ^~8S^8S^X^^DATA(X^^ DADA~DADA?? ~DATA(^X^mEmEpoo?? pDATA8^(^h^X^H^^^!~[^\^^(^^DATA((^^sDACA]CACA?? ^^!~^DATA(^(^@~CHB23JуCHB]]A?HB|HHB= AH^B!~[^BDATA^DATA(^^8^H^(^ؼ^^!~]^x^x^^^DATA(^^CACA]CACA?? ^^!~^DATA(^^C\C\M^rRLr?@ ^sMs!~]^s^^DATAX^^BUTTONS_PT_contextBUTTONS_PT_contextContextL$DATAX^8^^RENDER_PT_renderRENDER_PT_renderRenderL=DATAX8^^^RENDER_PT_layersRENDER_PT_layersLayersoLDATAX^x^8^RENDER_PT_dimensionsRENDER_PT_dimensionsDimensionsLDATAXx^^^RENDER_PT_antialiasingRENDER_PT_antialiasingAnti-Aliasing:L:DATAX^^x^RENDER_PT_motion_blurRENDER_PT_motion_blurSampled Motion Blur"LDATAX^X^^RENDER_PT_shadingRENDER_PT_shadingShading LDATAXX^^^RENDER_PT_performanceRENDER_PT_performancePerformanceLDATAX^^X^RENDER_PT_post_processingRENDER_PT_post_processingPost ProcessingLDATAX^8^^RENDER_PT_stampRENDER_PT_stampStampL DATAX8^^^RENDER_PT_outputRENDER_PT_outputOutput(L DATAX^8^RENDER_PT_bakeRENDER_PT_bakeBakeL DATAx^DATA^H^(^^x^^X^^^^h^DATA(^^@qDAFDAFDAFDA?? DATA(^^^C@FCF++?@ ,rDATA(^^^CfCww?@ xf"DATA(^h^^#Cl#C?@ pDATA(h^^r^DATAp^]e?w@AHMݕ/?U~'?3F:?>T8165e?2>Z& 4?ߕ/?7F:?81W~>85e?'?T2>ne@>M@?*?c''Ӥ?7??T?']@l2zz11A 4A>>Ļ7v=m>3xB֟&BĭeA(@ݕ/?U~'?3F:?>T8165e?2>Z& 4?*?c''Ӥ?7??T?']@l2zz11A 4A>?>?>??H?N,Z#oAoAP1:A\>7?8˔?ABdZBvBDATA`^333?? AL>h:^ B?=C DATAH^^^^h^(^^ ]8$^8$^8^^DATA(8^^BDADADADA??   DATA(^8^ D DlD`@>C BB??FFQ= @  C1 CDATA08$^ @ ^ a?DATA^H^^^x^^X^X^^^DATA(^x^CAADA@DA@DA?? DATA(x^^^DATA(^x^CC@d?rrDATA(!X^dA>d>ddd?SN#^9^X^SRDefault%^+^,^H5^5^Hq^ ^hf5DATA %^%^DATA %^%^%^DATA %^h&^%^DATA h&^&^%^DATA &^H'^h&^DATA H'^'^&^DATA '^((^H'^4DATA ((^(^'^4DATA (^)^((^4,DATA )^x)^(^,DATA x)^)^)^TDATA )^X*^x)^4TDATA X*^*^)^<DATA *^8+^X*^4<DATA 8+^+^*^hTDATA +^8+^hDATA(,^,^%^%^DATA(,^,^,^%^&^DATA(,^h-^,^%^H'^DATA(h-^-^,^&^H'^DATA(-^H.^h-^%^'^DATA(H.^.^-^h&^'^DATA(.^(/^H.^H'^((^DATA((/^/^.^'^(^DATA(/^0^(/^((^(^DATA(0^x0^/^H'^)^DATA(x0^0^0^h&^)^DATA(0^X1^x0^(^)^DATA(X1^1^0^%^x)^DATA(1^82^X1^'^)^DATA(82^2^1^x)^)^DATA(2^3^82^&^x)^DATA(3^3^2^((^)^DATA(3^3^3^x)^8+^DATA(3^h4^3^)^8+^DATA(h4^4^3^&^+^DATA(4^H5^h4^((^+^DATA(H5^4^8+^+^DATA5^G^&^%^%^H'^X^^^6^8^'^'^DATA(6^8^DA`DA@DA@DA?? fh^^^H_^%^DATA(8^6^DA@DDgog?? hhx^^^DATA(^H^@PDA`DA@DA@DA?? hx^DATA(H^^^C5_C5gg?@ hhghX^Xp^ȵ'^DATA(^x^H^`DHB`DHB 1@DDB@DDB?? 2 26g 2h^DATA(x^^QC@ `D  5  5?? 6 $5 6^DATAh^^^x^H'^+^h^ DATAH'^B3D Export/home/sapier/hobbys/workspace_20120207_minetest/mobf/models/mobf_path_marker.b3d DATAG^p^5^'^(^)^h&^5+,^xo^xo^H^HJ^A^s^DATA(H^HJ^CAFCAECAECA?? 5+ih^^^'^'^DATA(HJ^H^C=5CJ?@ ^5j^h!^HW'^K^m^^H^DATAXK^XM^^BUTTONS_PT_contextBUTTONS_PT_contextContext$&DATAXXM^N^K^hM^RENDER_PT_renderRENDER_PT_renderRender= DATAXN^P^XM^؅M^RENDER_PT_layersRENDER_PT_layersLayerso DATAXP^8R^N^HM^RENDER_PT_dimensionsRENDER_PT_dimensionsDimensions DATAX8R^S^P^M^RENDER_PT_antialiasingRENDER_PT_antialiasingAnti-Aliasing::DATAXS^xU^8R^(M^RENDER_PT_motion_blurRENDER_PT_motion_blurSampled Motion Blur"DATAXxU^W^S^^RENDER_PT_shadingRENDER_PT_shadingShading DATAXW^X^xU^M^RENDER_PT_performanceRENDER_PT_performancePerformanceDATAXX^XZ^W^(M^RENDER_PT_post_processingRENDER_PT_post_processingPost ProcessingDATAXXZ^[^X^M^RENDER_PT_stampRENDER_PT_stampStampDATAX[^]^XZ^M^RENDER_PT_outputRENDER_PT_outputOutput$DATAX]^8_^[^M^RENDER_PT_bakeRENDER_PT_bakeBake DATAX8_^`^]^SCENE_PT_sceneSCENE_PT_sceneScene)=DATAX`^xb^8_^SCENE_PT_unitSCENE_PT_unitUnits)SDATAXxb^d^`^SCENE_PT_keying_setsSCENE_PT_keying_setsKeying Sets)EDATAXd^e^xb^SCENE_PT_physicsSCENE_PT_physicsGravity)$DATAXe^Xg^d^SCENE_PT_simplifySCENE_PT_simplifySimplify)P DATAXXg^h^e^SCENE_PT_custom_propsSCENE_PT_custom_propsCustom Properties)$ DATAXh^j^Xg^TEXTURE_PT_context_textureTEXTURE_PT_context_textureDATAXj^8l^h^TEXTURE_PT_mappingTEXTURE_PT_mappingMappingMDATAX8l^m^j^TEXTURE_PT_influenceTEXTURE_PT_influenceInfluenceVDATAXm^8l^OBJECT_PT_constraintsOBJECT_PT_constraintsObject Constraints$DATAxo^\^DATAp^u^G^%^x)^)^'^3S4Tȸ^t^t^q^s^8n^^DATA(q^s^`DADA3`DA`DA?? 4434kغ^#^#^U^H˓^DATA(s^q^@~CHBXg(CHB339?HB|HHB= AH4:3S4:l^^X^DATAt^?DATAu^(.^p^(^((^H'^)^5-Uȵ^xy^xy^v^x^XD^^DATA(v^x^CAFCAECAECA?? 5hmط^w^w^(^ғ^DATA(x^v^C65C::?? ;)5-g;n^^^^^DATAxy^Y^Y^^se SculptDATA^0 0 z^DATAz^0  ^ ^ ^ ^7^(@^^\^r^E^'^h:^b^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^  ^  ^  ^  ^  ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^  ^! ^" ^# ^$ ^% ^& ^' ^( ^) ^* ^+ ^, ^- ^. ^/ ^0 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^  ^  ^  ^  ^  ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^  ^! ^" ^# ^$ ^% ^& ^' ^( ^^^h!^*^2^(D^L^U^8^^f^o^Hx^^^X^^^h^^Ƚ^x^(^^^8^^^H^ ^^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^b^'^\^^h:^(@^E^h]^X^#^9^8^$^l^r^xZ^7^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^h!^h!^h!^h!^h!^h!^h!^h!^h!^ h!^ h!^ h!^ h!^ h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^h!^ h!^!h!^"h!^#h!^$h!^%h!^&h!^'h!^(h!^)h!^*h!^+h!^,h!^-h!^.h!^/h!^0h!^1h!^2h!^3h!^4h!^5h!^6h!^7h!^8h!^9h!^:h!^;h!^<h!^=h!^>h!^?h!^@h!^Ah!^Bh!^Ch!^Dh!^Eh!^*^*^*^*^*^*^*^*^*^ *^ *^ *^ *^ *^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^*^ *^!*^"*^#*^$*^%*^&*^'*^(*^)*^**^+*^,*^-*^.*^/*^0*^1*^2*^3*^4*^5*^6*^7*^8*^9*^:*^;*^<*^=*^>*^?*^@*^A*^B*^C*^D*^E*^2^2^2^2^2^2^2^2^2^ 2^ 2^ 2^ 2^ 2^2^2^2^2^2^2^2^2^2^2^2^2^2^2^2^2^2^2^ 2^!2^"2^#2^$2^%2^&2^'2^(2^)2^*2^+2^,2^-2^.2^/2^02^12^22^32^42^52^62^72^82^92^:2^;2^<2^=2^>2^?2^@2^A2^B2^C2^D2^E2^(D^(D^(D^(D^(D^(D^(D^(D^(D^ (D^ (D^ (D^ (D^ (D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^(D^ (D^!(D^"(D^#(D^$(D^%(D^&(D^'(D^((D^)(D^*(D^+(D^,(D^-(D^.(D^/(D^0(D^1(D^2(D^3(D^4(D^5(D^6(D^7(D^8(D^9(D^:(D^;(D^<(D^=(D^>(D^?(D^@(D^A(D^B(D^C(D^D(D^E(D^L^L^L^L^L^L^L^L^L^ L^ L^ L^ L^ L^L^L^L^L^L^L^L^L^L^L^L^L^L^L^L^L^L^L^ L^!L^"L^#L^$L^%L^&L^'L^(L^)L^*L^+L^,L^-L^.L^/L^0L^1L^2L^3L^4L^5L^6L^7L^8L^9L^:L^;L^<L^=L^>L^?L^@L^AL^BL^CL^DL^EL^U^U^U^U^U^U^U^U^U^ U^ U^ U^ U^ U^U^U^U^U^U^U^U^U^U^U^U^U^U^U^U^U^U^U^ U^!U^"U^#U^$U^%U^&U^'U^(U^)U^*U^+U^,U^-U^.U^/U^0U^1U^2U^3U^4U^5U^6U^7U^8U^9U^:U^;U^<U^=U^>U^?U^@U^AU^BU^CU^DU^EU^8^^8^^8^^8^^8^^8^^8^^8^^8^^ 8^^ 8^^ 8^^ 8^^ 8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^8^^ 8^^!8^^"8^^#8^^$8^^%8^^&8^^'8^^(8^^)8^^*8^^+8^^,8^^-8^^.8^^/8^^08^^18^^28^^38^^48^^58^^68^^78^^88^^98^^:8^^;8^^<8^^=8^^>8^^?8^^@8^^A8^^B8^^C8^^D8^^E8^^f^f^f^f^f^f^f^f^f^ f^ f^ f^ f^ f^f^f^f^f^f^f^f^f^f^f^f^f^f^f^f^f^f^f^ f^!f^"f^#f^$f^%f^&f^'f^(f^)f^*f^+f^,f^-f^.f^/f^0f^1f^2f^3f^4f^5f^6f^7f^8f^9f^:f^;f^<f^=f^>f^?f^@f^Af^Bf^Cf^Df^Ef^o^o^o^o^o^o^o^o^o^ o^ o^ o^ o^ o^o^o^o^o^o^o^o^o^o^o^o^o^o^o^o^o^o^o^ o^!o^"o^#o^$o^%o^&o^'o^(o^)o^*o^+o^,o^-o^.o^/o^0o^1o^2o^3o^4o^5o^6o^7o^8o^9o^:o^;o^<o^=o^>o^?o^@o^Ao^Bo^Co^Do^Eo^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^ Hx^ Hx^ Hx^ Hx^ Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^Hx^ Hx^!Hx^"Hx^#Hx^$Hx^%Hx^&Hx^'Hx^(Hx^)Hx^*Hx^+Hx^,Hx^-Hx^.Hx^/Hx^0Hx^1Hx^2Hx^3Hx^4Hx^5Hx^6Hx^7Hx^8Hx^9Hx^:Hx^;Hx^<Hx^=Hx^>Hx^?Hx^@Hx^AHx^BHx^CHx^DHx^EHx^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^X^X^X^X^X^X^X^X^X^ X^ X^ X^ X^ X^X^X^X^X^X^X^X^X^X^X^X^X^X^X^X^X^X^X^ X^!X^"X^#X^$X^%X^&X^'X^(X^)X^*X^+X^,X^-X^.X^/X^0X^1X^2X^3X^4X^5X^6X^7X^8X^9X^:X^;X^<X^=X^>X^?X^@X^AX^BX^CX^DX^EX^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^h^h^h^h^h^h^h^h^h^ h^ h^ h^ h^ h^h^h^h^h^h^h^h^h^h^h^h^h^h^h^h^h^h^h^ h^!h^"h^#h^$h^%h^&h^'h^(h^)h^*h^+h^,h^-h^.h^/h^0h^1h^2h^3h^4h^5h^6h^7h^8h^9h^:h^;h^<h^=h^>h^?h^@h^Ah^Bh^Ch^Dh^Eh^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^ Ƚ^ Ƚ^ Ƚ^ Ƚ^ Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^Ƚ^ Ƚ^!Ƚ^"Ƚ^#Ƚ^$Ƚ^%Ƚ^&Ƚ^'Ƚ^(Ƚ^)Ƚ^*Ƚ^+Ƚ^,Ƚ^-Ƚ^.Ƚ^/Ƚ^0Ƚ^1Ƚ^2Ƚ^3Ƚ^4Ƚ^5Ƚ^6Ƚ^7Ƚ^8Ƚ^9Ƚ^:Ƚ^;Ƚ^<Ƚ^=Ƚ^>Ƚ^?Ƚ^@Ƚ^AȽ^BȽ^CȽ^DȽ^EȽ^x^x^x^x^x^x^x^x^x^ x^ x^ x^ x^ x^x^x^x^x^x^x^x^x^x^x^x^x^x^x^x^x^x^x^ x^!x^"x^#x^$x^%x^&x^'x^(x^)x^*x^+x^,x^-x^.x^/x^0x^1x^2x^3x^4x^5x^6x^7x^8x^9x^:x^;x^<x^=x^>x^?x^@x^Ax^Bx^Cx^Dx^Ex^(^(^(^(^(^(^(^(^(^ (^ (^ (^ (^ (^(^(^(^(^(^(^(^(^(^(^(^(^(^(^(^(^(^(^ (^!(^"(^#(^$(^%(^&(^'(^((^)(^*(^+(^,(^-(^.(^/(^0(^1(^2(^3(^4(^5(^6(^7(^8(^9(^:(^;(^<(^=(^>(^?(^@(^A(^B(^C(^D(^E(^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^8^8^8^8^8^8^8^8^8^ 8^ 8^ 8^ 8^ 8^8^8^8^8^8^8^8^8^8^8^8^8^8^8^8^8^8^8^ 8^!8^"8^#8^$8^%8^&8^'8^(8^)8^*8^+8^,8^-8^.8^/8^08^18^28^38^48^58^68^78^88^98^:8^;8^<8^=8^>8^?8^@8^A8^B8^C8^D8^E8^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^H^H^H^H^H^H^H^H^H^ H^ H^ H^ H^ H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H^ H^!H^"H^#H^$H^%H^&H^'H^(H^)H^*H^+H^,H^-H^.H^/H^0H^1H^2H^3H^4H^5H^6H^7H^8H^9H^:H^;H^<H^=H^>H^?H^@H^AH^BH^CH^DH^EH^ ^ ^ ^ ^ ^ ^ ^ ^ ^  ^  ^  ^  ^  ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^  ^! ^" ^# ^$ ^% ^& ^' ^( ^) ^* ^+ ^, ^- ^. ^/ ^0 ^1 ^2 ^3 ^4 ^5 ^6 ^7 ^8 ^9 ^: ^; ^< ^= ^> ^? ^@ ^A ^B ^C ^D ^E ^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^4^5^6^7^8^9^:^;^<^=^>^?^@^A^B^C^D^E^b^b^b^b^b^b^b^b^b^ b^ b^ b^ b^ b^b^b^b^b^b^b^b^b^b^b^b^b^'^'^'^'^'^'^'^'^'^ '^ '^ '^ '^ '^'^'^'^'^'^'^'^'^'^'^'^'^'^'^'^'^'^\^\^\^\^\^\^\^\^\^ \^ \^ \^ \^ \^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^\^ \^!\^"\^#\^$\^%\^&\^'\^(\^)\^*\^+\^,\^-\^.\^/\^0\^1\^2\^3\^4\^5\^6\^7\^8\^9\^:\^;\^<\^=\^>\^?\^@\^A\^B\^C\^D\^E\^F\^G\^H\^I\^J\^K\^L\^M\^N\^O\^P\^Q\^R\^S\^^^^^^^^^^ ^ ^ ^ ^ ^^^^^^^^^^^^^^^^^^^ ^!^"^#^$^%^&^'^(^)^*^+^,^-^.^/^0^1^2^3^h:^h:^h:^h:^h:^h:^h:^h:^h:^ h:^ h:^ h:^ h:^ h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^h:^ h:^!h:^"h:^#h:^$h:^%h:^&h:^'h:^(h:^)h:^*h:^+h:^,h:^-h:^.h:^/h:^0h:^1h:^2h:^3h:^4h:^5h:^6h:^7h:^8h:^9h:^:h:^;h:^<h:^=h:^>h:^?h:^@h:^Ah:^Bh:^Ch:^Dh:^Eh:^Fh:^Gh:^Hh:^Ih:^Jh:^Kh:^Lh:^Mh:^Nh:^Oh:^Ph:^Qh:^Rh:^Sh:^Th:^Uh:^Vh:^Wh:^Xh:^Yh:^Zh:^[h:^\h:^]h:^^h:^_h:^`h:^(@^(@^(@^(@^(@^(@^(@^(@^(@^ (@^ (@^ (@^ (@^ (@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^(@^ (@^!(@^"(@^#(@^$(@^%(@^&(@^'(@^((@^)(@^*(@^+(@^,(@^-(@^.(@^/(@^0(@^1(@^2(@^3(@^4(@^5(@^6(@^7(@^8(@^9(@^:(@^;(@^<(@^=(@^>(@^?(@^@(@^A(@^B(@^C(@^D(@^E(@^F(@^G(@^H(@^I(@^J(@^K(@^L(@^M(@^N(@^O(@^P(@^Q(@^R(@^S(@^T(@^U(@^V(@^W(@^X(@^Y(@^Z(@^[(@^\(@^](@^^(@^_(@^`(@^E^E^E^E^E^E^E^E^E^ E^ E^ E^ E^ E^E^E^E^E^E^E^E^E^E^E^E^E^E^E^E^E^E^E^ E^!E^"E^#E^$E^%E^&E^'E^(E^)E^*E^+E^,E^-E^.E^/E^0E^1E^2E^3E^4E^5E^6E^7E^8E^9E^:E^;E^<E^=E^>E^?E^@E^AE^BE^CE^DE^EE^FE^GE^HE^IE^JE^KE^LE^ME^NE^OE^PE^QE^RE^SE^TE^UE^VE^WE^XE^YE^ZE^[E^\E^]E^^E^_E^`E^h]^h]^h]^h]^h]^h]^h]^h]^h]^ h]^X^X^X^X^X^X^X^X^X^ X^#^#^#^#^#^#^#^#^#^ #^9^9^9^9^9^9^9^9^9^ 9^8^8^8^8^8^8^8^8^8^ 8^$^$^$^$^$^$^$^$^$^ $^l^l^l^l^l^l^l^l^l^ l^r^r^r^r^r^r^r^r^r^ r^ r^ r^ r^ r^r^r^r^r^r^xZ^xZ^xZ^xZ^xZ^xZ^xZ^xZ^xZ^ xZ^ xZ^7^7^7^7^7^7^7^7^7^ 7^ 7^ 7^ 7^ 7^7^7^7^7^7^7^7^K^*^hQ^.^(W^2^DATA(.^Hq^u^8+^+^((^)^i3U-^4^o^/^h3^Xu^Xu^^x^DATA(/^0^DACACACA?? i3Uno^'^'^xM'^^DATA(0^1^/^iio^DATA(1^h3^0^33o^DATA(h3^1^DD@>`9?v>`B?i3op^8^1^DATA(!4^\^^ ^dA>d>ddd?DATA(HV^W^@PDA`DA@DA@DA?? hDATA(W^(Y^HV^C_Cgg?@ hhghDATA((Y^Z^W^`DHB`DHB 1@DDB@DDB?? 2 26g 2DATA(Z^(Y^hC@ `D  5  5?? 6 $5 6DATAh\^o^4^HV^Z^\^DATA\^Open Imageayout/home/sapier/hobbys/workspace_20120207_minetest/mobf/textures/mobf_path_marker.png 0DATA(d^f^^DACACACA?? i3UnDATA(f^g^d^CC::?@ ;;iG;2DATA(g^h^f^CZfCZ?@ ioF"DATA(h^hj^g^4C`#C`ã?@ 33oDATA(hj^h^ 3o+k^DATApk^?? L>$O1?}?=D?Q?!o?> }?!O=?1?@Q?AySDA?\z>9c1?\k==D=s ?iQQ!lA!oAP=?X%==g?3 +Χ@bu(B(rB @>$O1?}?=D?Q?!o?\z>9c1?\k==D=s ?iQQ!lA!oApQV@pQV@pQV@f34&@?c7?fd(O !!oA!oAa;?9DBqB:0%BDATA`o^\^d^hj^333?? AL>h:^ B?=zD DATAHq^(.^x)^&^+^8+^gUh-Ȼ^H^H^8r^^b^X^DATA(8r^s^lDADAgDADA?? hhgUnhq(^ؓ^ؓ^^(Z^DATA(s^^8r^C2C2Ï::?@ ;;G;r2^^^u^^^X^DATAXu^v^Q^VIEW3D_PT_tools_objectmodeVIEW3D_PT_tools_objectmodeObject Tools' DATAXv^Xx^u^VIEW3D_PT_tools_brushVIEW3D_PT_tools_brushBrushusDATAXXx^y^v^VIEW3D_PT_tools_brush_toolVIEW3D_PT_tools_brush_toolToolJ=DATAXy^{^Xx^VIEW3D_PT_tools_brush_strokeVIEW3D_PT_tools_brush_strokeStrokeEDATAX{^8}^y^VIEW3D_PT_tools_brush_curveVIEW3D_PT_tools_brush_curveCurve-DATAX8}^~^{^VIEW3D_PT_tools_brush_appearanceVIEW3D_PT_tools_brush_appearanceAppearance DATAX~^x^8}^VIEW3D_PT_tools_vertexpaintVIEW3D_PT_tools_vertexpaintOptions DATAXx^^~^VIEW3D_PT_tools_brush_textureVIEW3D_PT_tools_brush_textureTexture]DATAX^^x^VIEW3D_PT_sculpt_optionsVIEW3D_PT_sculpt_optionsOptionsDATAX^X^^VIEW3D_PT_sculpt_symmetryVIEW3D_PT_sculpt_symmetrySymmetryDATAXX^^^VIEW3D_PT_tools_mesheditVIEW3D_PT_tools_mesheditMesh ToolsDDATAX^X^VIEW3D_PT_tools_meshedit_optionsVIEW3D_PT_tools_meshedit_optionsMesh OptionsDATA(^^s^CZfCZ?@ oFs"^{^{^^^^A^DATAX^^VIEW3D_PT_last_operatorVIEW3D_PT_last_operatorOperatorObjectsn'DATA(^^^4C`#C`ã?@ ggoؽ^DATA(^^got^'^H'^^DATAp^?? L|mO.>%!>Qz?N=C}?P>?|%!>N=nQC}?P.>z?O>蠙@blAh@?Li6O.>M>ʯzzcq?EPAAzFP =3<>g?3m7?оA_,CAq@6=ŽvE>>zƾ7;@B+@f>zÐ<@?yq*sؽQvE?bپ<7;# Q;@pf`<ӿA@A]DA]DA]DA+nÿ?Wp="C$(?AAa;?B]Bl6BDATA`H^333?? AL>h:^ B?=zD SN9^8^#^SRGame Logic.001:^@^@^^(^H^ ^DATA :^H;^DATA H;^;^:^DATA ;^(<^H;^~DATA (<^<^;^~DATA <^=^(<^DATA =^x=^<^~DATA x=^=^=^DATA =^X>^x=^ DATA X>^>^=^ DATA >^8?^X>^~DATA 8?^?^>^@DATA ?^@^8?^@DATA @^@^?^DDATA @^@^DDATA(@^hA^H;^;^DATA(hA^A^@^H;^<^DATA(A^HB^hA^;^=^DATA(HB^B^A^<^=^DATA(B^(C^HB^<^x=^DATA((C^C^B^x=^=^DATA(C^D^(C^(<^X>^DATA(D^xD^C^=^X>^DATA(xD^D^D^:^x=^DATA(D^XE^xD^:^X>^DATA(XE^E^D^=^>^DATA(E^8F^XE^(<^>^DATA(8F^F^E^=^>^DATA(F^G^8F^8?^?^DATA(G^^F^=^?^DATA(^^G^>^8?^DATA(^h^^x=^@^DATA(h^ؕ^^8?^@^DATA(ؕ^H^h^<^@^DATA(H^^ؕ^?^@^DATA(^H^@^@^DATA(^^<^H;^;^=^~^^^^DATA(^^ DADA~DADA?? ~DATA(^^mED@poo?? pDATA^^(^X>^=^>^(<^!~^H^H^^X^DATA(^X^CACA]CACA?? ^^!~r^DATA(X^^C=CM^qNLq?@ ^rMr!~q^rȞ^^DATAXȞ^h^BUTTONS_PT_contextBUTTONS_PT_contextContextL$DATAXh^^Ȟ^RENDER_PT_renderRENDER_PT_renderRenderL=DATAX^^h^RENDER_PT_layersRENDER_PT_layersLayersoLDATAX^H^^RENDER_PT_dimensionsRENDER_PT_dimensionsDimensionsLDATAXH^^^RENDER_PT_antialiasingRENDER_PT_antialiasingAnti-Aliasing:L:DATAX^^H^RENDER_PT_motion_blurRENDER_PT_motion_blurSampled Motion Blur"LDATAX^(^^RENDER_PT_shadingRENDER_PT_shadingShading LDATAX(^ȫ^^RENDER_PT_performanceRENDER_PT_performancePerformanceLDATAXȫ^h^(^RENDER_PT_post_processingRENDER_PT_post_processingPost ProcessingLDATAXh^^ȫ^RENDER_PT_stampRENDER_PT_stampStampL DATAX^^h^RENDER_PT_outputRENDER_PT_outputOutput(L DATAX^^RENDER_PT_bakeRENDER_PT_bakeBakeL DATAH^DATA^^^:^x=^=^X>^ h^h^x^^DATA(x^^JCADADADA??   DATA(^^x^\CKCqq?@ rrrX^X^DATAXX^LOGIC_PT_propertiesLOGIC_PT_propertiesProperties$DATA(^^DpCPDƶ¸C3Dq22q??FF?H? Dr3`DrDATAHh^DATA^^^8?^?^=^>^A~ >]Ⱦ^Ⱦ^^X^DATA(^X^CADA=@DA@DA?? >>A~>DATA(X^^CCDVD=B #<zD >C>CA~>CDATAȾ^ DATA^H^^@^@^?^8?^E?]^^^h^DATA(^^@qDA~DA~DA~DA?? E?DATA(^^^C@FCF++?@ ,EECDATA(^^^CfCww?@ xfE?"DATA(^h^^4Cm#Cmã?@ ??DATA(h^^E?C^DATAp^#=K(=o?????????#=K(=o?5ApykA?????#=K(=o?t@t@t@??5AoA9P=A\>7?8˔?BBDATA`^333?? AL>h:^ B?=zD DATAH^^x=^<^@^@^CD]^^8^^DATA(8^^CACACCACA?? DDCDDATA(^8^CC@ 3DB22B?? DC31CDCDATA^t^DATAt^x^DATAx^ ^ ^ ^ ^7^(@^^\^r^E^'^h:^b^ ^SN8^$^9^SRScriptingg.001^8^^^H^^ ^DATA ^^DATA ^h^^DATA h^^^~DATA ^H^h^~DATA H^^^DATA ^(^H^~DATA (^^^DATA ^^(^DATA ^x^^hDATA x^^^hDATA ^X^x^hDATA X^^^DATA ^8^X^~DATA 8^^DATA(^^^h^DATA(^^^^H^DATA(^^^h^^DATA(^h^^H^^DATA(h^^^^(^DATA(^H^h^^^DATA(H^^^^^DATA(^(^H^H^^DATA((^^^(^x^DATA(^^(^^x^DATA(^x^^^^DATA(x^^^x^^DATA(^X^x^^X^DATA(X^^^(^X^DATA(^8^X^^^DATA(8^^^^^DATA(^^8^X^^DATA(^^^H^8^DATA(^^^(^8^DATA(^h^^^8^DATA(h^^^^x^DATA(^h^^^DATAH^^H^^h^^~]h$^h$^8^^DATA(8^^ DADA~DADA?? ~DATA(^8^DBDBnBomB?? CnC~CDATA^^H^^X^^^~h^h^^x^DATA(^x^CACACACA?? ~DATA(x^^C=C4}~|?@ }~^^DATAX^^BUTTONS_PT_contextBUTTONS_PT_contextContext|$DATAX^(^^RENDER_PT_renderRENDER_PT_renderRender|=DATAX(^^^RENDER_PT_layersRENDER_PT_layersLayerso|DATAX^h^(^RENDER_PT_dimensionsRENDER_PT_dimensionsDimensions|DATAXh^^^RENDER_PT_antialiasingRENDER_PT_antialiasingAnti-Aliasing:|:DATAX^^h^RENDER_PT_motion_blurRENDER_PT_motion_blurSampled Motion Blur"|DATAX^H^^RENDER_PT_shadingRENDER_PT_shadingShading |DATAXH^^^RENDER_PT_performanceRENDER_PT_performancePerformance|DATAX^^H^RENDER_PT_post_processingRENDER_PT_post_processingPost Processing|DATAX^(^^RENDER_PT_stampRENDER_PT_stampStamp| DATAX(^^^RENDER_PT_outputRENDER_PT_outputOutput(| DATAX^(^RENDER_PT_bakeRENDER_PT_bakeBake| DATAh^DATA^8^^^8^(^x^i?^^^X ^DATA(^^@qDA=DA=DA=DA?? iDATA(^x^^C@FCF++?@ ,%DATA(x^^^CfCww?@ xf"DATA(^X ^x^#C#Cyy?@ zhDATA(X ^^% ^DATAp ^?J?PףD>3;Q?Fwi?JF>#,TY!e?*=>o?E>Fwi?TY5;JF>!e?Q?#,+=>`DAoy@?>ޠQQuZ?> .>#,>m6uU?F +!>?`5hąC% ÈG6DWѦCGBD>3;Q?Fwi?JF>#,TY!e?*=>o?>ޠQQuZ?> .>#,>m7?8˔oAoAk;?! BVBt~BDATA`^333?? AL>h:^ B? #<C DATA8^H^^^^x^^ghx^x^(^^DATA((^^CADADADA?? DATA(^(^D3CDCMM?? NNgNDATA(^^DATA^DATAx^^^>>> pythonDATAH^^8^X^(^^^~^^8^^DATA(8^^CACACACA?? ~DATA(^8^CC}||?? }~DATA^8^DATA8^x^DATAx^ ^ ^ ^ ^7^(@^^\^r^E^'^h:^b^ ^DATA^H^^H^8^^i ?x!^x!^^ ^DATA(^ ^CA>DA=DA=DA?? iDATA( ^^DDD)dD.>CC$ #<zD %%%DATAx!^ =z||SN$^l^8^SRUV EditingH&^X)^)^(.^.^Z^ ^DATA H&^&^DATA &^('^H&^DATA ('^'^&^~DATA '^(^('^~DATA (^x(^'^DATA x(^(^(^~DATA (^X)^x(^DATA X)^(^DATA()^8*^&^('^DATA(8*^*^)^&^(^DATA(*^+^8*^('^x(^DATA(+^+^*^(^x(^DATA(+^+^+^(^(^DATA(+^h,^+^H&^X)^DATA(h,^,^+^H&^(^DATA(,^H-^h,^(^X)^DATA(H-^-^,^x(^(^DATA(-^(.^H-^'^X)^DATA((.^-^'^x(^DATA.^h2^(^&^('^x(^~k^k^/^0^DATA(/^0^ DADA~DADA?? ~DATA(0^/^mEmEpoo?? pDATAh2^Z^.^H&^(^(^X)^H9^H9^X3^7^DATA(X3^4^CArDAqDAqDA?? DATA(4^7^X3^[CsJCs?@ 86^86^DATAX86^IMAGE_PT_gpencilIMAGE_PT_gpencilGrease PencilPDATA(7^4^CC33+?33@DATA(!H9^dA>d>ddd?DATAZ^h2^X)^(^x(^'^~i^i^[^d^DATA([^]^@qDAmDA@mDA@mDA?? ~DATA(]^(`^[^ CVCVWW?@ XXhX^^^^DATAX^^VIEW3D_PT_tools_objectmodeVIEW3D_PT_tools_objectmodeObject ToolsDATA((`^8c^]^!CfC[Zww?@ xxhx"a^a^DATAXa^VIEW3D_PT_last_operatorVIEW3D_PT_last_operatorOperatorDATA(8c^d^(`^#C~#C~  ?@  ~~DATA(d^8c^i~f^DATApf^H?? JLD>3;Q?Fwi?JF>#,TY!e?*=>_?E>Fwi?TY4;JF>!e?Q?#,+=>6@_?? ?0QQ?X>?>#,>ՒΜz?T*=dbR@_@y\>,? (K5>}Q?sMd@JWA.Xj@-@D>3;Q?Fwi?JF>#,TY!e?*=>_? ?0QQ?X>?>#,>ՒΜz?T*=dbR@_@>>>?\>7?8˔_@_@r:?! BUBt~BDATA`i^333?? AL>h:^ B?=C SNl^$^SRVideo Editinghm^8r^r^z^z^^ ^DATA hm^m^DATA m^Hn^hm^DATA Hn^n^m^DATA n^(o^Hn^DATA (o^o^n^DATA o^p^(o^DATA p^xp^o^\DATA xp^p^p^DDATA p^Xq^xp^0DATA Xq^q^p^\DATA q^8r^Xq^0\DATA 8r^q^DDATA(r^s^m^Hn^DATA(s^s^r^m^(o^DATA(s^s^s^Hn^o^DATA(s^ht^s^(o^o^DATA(ht^t^s^o^p^DATA(t^Hu^ht^hm^xp^DATA(Hu^u^t^(o^p^DATA(u^(v^Hu^xp^Xq^DATA((v^v^u^Xq^q^DATA(v^w^(v^p^q^DATA(w^xw^v^p^8r^DATA(xw^w^w^n^8r^DATA(w^Xx^xw^xp^8r^DATA(Xx^x^w^hm^n^DATA(x^8y^Xx^o^p^DATA(8y^y^x^p^q^DATA(y^z^8y^(o^Xq^DATA(z^y^p^Xq^DATAz^X~^(o^m^Hn^o^؛^؛^x{^|^DATA(x{^|^DA DADADA?? DATA(|^x{^mEmEpoo?? pDATAX~^H^z^hm^xp^8r^n^CD(^(^H^^DATA(H^^ DA DADADA?? DATA(^H^@~CHBpF}CHB)?HB|HHB= AH*C*DATA(^DATAH^8^X~^xp^Xq^p^8r^E[^^8^^DATA(8^^DA DADADA?? E^DATA(^^8^\C}KC}?@ _[DATA(^^^ppDDppDD;F;F'7PG[[DATA(^^zCAzCA A?|HB #<Bi_[DATA^@DATA8^^H^Xq^(o^p^q^/]0f^^(^x^DATA((^^@YDAA7 DA/ DA DA?? 00/]v0DATA(^^(^HCpHCKK?? L:wLDATA(^x^^//wDATA(x^^C@zC AKVVKo:o:|HPCGiWL/wWLDATA^(^DATAp(^% ^DATA^8^q^p^o^p^1]f^^ؔ^(^DATA(ؔ^H^CA0DA/DA/DA?? 1]vDATA(H^^ؔ^wDATA(^(^H^CC!!DDK;F;F'7PGLL1wLDATA((^^zCAzCAKK A?|HB #<BiLDATA^@SC ^SCScenetageain/^h:^7^^^x^.A@+nÿ+nÿ+nÿ^^ZZD?dd??< 28ZQ! ????8^8^??????/tmp/ L?L?L??>??_??BLENDER_RENDERD?fC??^p^X^< ?=>L>I?fff?@?@Aff?AA@?A <@?y&DATA/^ DATA(^&^(W^DATA(&^z^^.hQ^DATA(z^x^&^.7K^DATA(x^ح^z^xW(@^DATA(ح^^x^.AE^DATA(^ح^.9h:^DATA^^^^?L?B ?o:= ??H^P2 HB2 B2 HB2 HB2 HB2 HB2 HB>? #<===ff??AHz?=???C#y??#y??DATA8^8^^DATA8^8^^DATA`^(^dd C@K?DATAx8^RenderLayerrIM0^8"^IMmobf_path_marker.png//../textures/mobf_path_marker.png207_minetest/mobf/textures/mobf_path_marker.pngh^h^0^<Q??DATA(0^ CAb^CACameraamera.001?=B B@?BALA'^$*^LALamp ?????AB>??^.?A4B?@@L=@ ???o:??????@?????x*^DATA@^w????C?55?55?*^??????DATA*^u??DATA(x*^ LA*^$.^'^LALamp.001 ?????AB>??^.?A4B?@@L=@ ???o:??????@?????-^DATA@^w????C?55?55?H-^??????DATAH-^u??DATA(-^ LA.^$2^*^LALamp.002 ?????AB>??x0^.?A4B?@@L=@ ???o:??????@?????h2^DATA@x0^w????C?55?55?2^??????DATA2^u??DATA(h2^ LA2^$.^LALamp.003 ?????AB>??85^.?A4B?@@L=@ ???o:??????@?????(7^DATA@85^w????C?55?55?6^??????DATA6^u??DATA((7^ WO7^WOWorldrcP=rcP=rcP=6$<6$<6$<??A @A@pA A?L= ף;>??9^DATA(9^ OBph:^(@^OBCameraamera.001 b^  ne@>N@??????*?91<"P?????ޕ/?5F:?81V~>75e?'?T3>ne@>N@??????33?3?5)?ݕ/?V~'?3F:?>T8175e?4>Z& 4?ޕ/?X~'?5F:?>T8195e?4>+>%?Z,?d8???>6 ?u=?????^DATA^??=L> ף<OBp(@^E^h:^OBCubephere8^^  ^ȃ^+nÿ@@@@,@???????@@@@,@+nÿ?????????>>0>c?????????Dd8?<?>">u=??????@???^a^X^y&DATA^DATAȃ^DATA^??=L> ף<HOBpE^K^(@^OBLamp '^  p@?p@??????{&?W+b=?????6씾t? bfE9L"?%?_>oK?p@?p@??????22?3?'4'?6씾fE&?t?9L_> b#?oK? ?Af ?6씾fE'?t?9L_> b"?oK?S?ؽ ?Dd8?<?>">u=??@??? ^DATA ^??=L> ף<OBpK^hQ^E^OBLamp.001 *^  ?䲷#@??????{&?W+b=?????6씾t? bfE9L"?%?_>oK??䲷#@??????22?3?'4'?@씾fE&?t? b"?oK?d@WY?6씾fE'?t?9L_> b"?oK?S?ؽ ?Dd8?<?>">u=??@???h^DATAh^??=L> ף<OBphQ^(W^K^OBLamp.002 .^  6P>??????{&?W+b=?????6씾t? bfE9L"?%?_>oK?6P>??????22?3?'4'?5씾fE%?t?7L_> b#?oK?$.@Fm0y@?6씾fE'?t?9L_> b"?oK?S?ؽ ?Dd8?<?>">u=??@???8^DATA8^??=L> ף<OBp(W^hQ^OBLamp.003 2^  XoH@jX@??????{&?W+b=?????6씾t? bfE9L"?%?_>oK?XoH@jX@??????22?3?'4'?/씾fE&?t?8L_> b"?oK?Mh?6씾fE'?t?9L_> b"?oK?S?ؽ ?Dd8?<?>">u=??@???X^DATAX^??=L> ף<MA\^'MAMaterialL?L?L???????????L??????????????? #<2L>??L>???2?? ף; ף;?C@C@ ?????????@?=?==???`^8b^????o?o?o?L==ff????DATA8`^r^??????????L>?????????????????????????????DATA(8b^  55b^DATAb^33ff33 f3  f !!!!!! f %%%(((******+++)))(((&&&$$$!!!  f%%%,,,000222333333222111000---+++)))%%%"""  f)))222666999:::;;;;;;:::999777555333000---)))&&&### f$$$555;;;???AAABBBBBBBBBAAA@@@>>><<<999666333000,,,)))%%%!!! 3f888@@@DDDGGGIIIJJJJJJIIIHHHGGGEEECCC@@@===:::666222///+++'''""" 666BBBHHHMMMOOOQQQQQQQQQPPPOOOMMMKKKIIIFFFCCC???<<<888444111,,,(((### ffCCCLLLQQQTTTWWWXXXXXXXXXWWWVVVTTTRRROOOLLLIIIEEEAAA>>>:::666111---(((### 111LLLTTTYYY\\\^^^_________^^^]]][[[XXXUUURRROOOKKKGGGCCC???;;;777222---(((""" fFFFUUU\\\aaadddffffffgggfffeeecccaaa^^^[[[XXXTTTQQQMMMHHHDDD@@@;;;777222,,,'''  ffPPP]]]dddhhhkkkmmmmmmmmmmmmkkkjjjgggdddaaa^^^ZZZVVVRRRMMMIIIEEE@@@;;;666111+++$$$ fYYYdddkkkppprrrtttttttttsssrrrpppnnnkkkhhhddd```\\\WWWSSSNNNIIIDDD???:::444...(((!!! ...___kkkrrrvvvyyyzzz{{{{{{zzzzzzxxxvvvrrrnnnjjjeeeaaa\\\WWWSSSNNNIIIDDD>>>888222,,,$$$ ///eeeqqqxxx}}}}}}xxxqqqlllgggaaa\\\WWWRRRMMMHHHBBB<<<666///''' 000jjjvvv~~~|||ssslllfffaaa\\\WWWQQQKKKEEE???999111***!!! fmmm{{{|||rrrkkkeee```ZZZUUUOOOHHHBBB;;;444,,,### 3nnnyyypppjjjccc^^^XXXRRRLLLEEE>>>666...%%%fkkkuuunnngggaaa[[[UUUNNNGGG@@@777///%%%3AAAyyyqqqjjjddd^^^WWWPPPIIIAAA999000&&&3|||uuunnnggg```YYYRRRJJJBBB999000### ]]]̍wwwpppiiibbbZZZSSSKKKBBB999...3yyyrrrkkkccc[[[SSSJJJAAA777)))3111fzzzssskkkccc[[[RRRIII???222NNN{{{sssjjjbbbYYYPPPFFF:::3PPPyyyqqqiii```VVVLLL???&&&3NNNwwwnnneee[[[PPPBBB 3zzzrrrhhh]]]QQQ666fIIIggg̉xxxqqqfff[[[@@@f3...fHHHHHH\\\XXXQQQ666###f3TEr^!TETex>@????????@@????? @??<t^DATA(t^  Xu^DATAXu^ME^2x^MECubephereH^^(^^8^^(^Ȕ^؊^x^ x^Hح^^@`"3???CDATAH^\^DATA؊^|(^DATA(^8@?*]<?&@*]WG>{?-`[<G>{?&@-`[W>_l?#V<>_l?&@#VW9?1T?3wM<9?1T?&@3wMW5?5?AA<5?5?&@AAW1T?9?wM3<1T?9?&@wM3W_l?>V#<_l?>&@V#W{?G>`[-<{?G>&@`[-W?i!3*]<?i!3&@*]W{?G`[<{?G&@`[W_l?þVY<_l?þ&@VYW2T?9wM><2T?9&@wM>W5?5A <5?5&@A W9?2T3<9?2T&@3W>_l#<>_l&@#WG>{-<G>{&@-WL﮴֢<L﮴&@֢WG{<G{&@W$þ\lY<$þ\l&@YW9,T>̉<9,T&@>̉W55 <55&@ W8T9><8T9&@>WdlþY<dlþ&@YW{G<{G&@W5֢<5&@֢W{G>-<{G>&@-WWl8>#<Wl8>&@#W&T9?3<&T9?&@3W55? A<55?&@ AW9>T?>wM<9>T?&@>wMWþhl?YV<þhl?&@YVW]G{?`[<]G{?&@`[WDATAx^|Ȕ^DATAȔ^5`########### # # # # # # # # # ########################## #!#! # "##!##"#"$#%##%$#$&#'%#'&#&(#)'#)(#(*#+)#+*#*,#-+#-,#,.#/-#/.#.0#1/#10#02#31#32#24#53#54#46#75#76#68#97#98#8:#;9#;:#:<#=;#=<#<>#?=#?>#>#?#DATAx^|H^UVMap^ D^DATA^|UVMap8^ NGon Face-Vertex^DATA 8^=p>zf?`>69ͮ>I9O7>vf?O7>vf?ͮ>I9պ>8/@>f?/@>f?պ>8>g8>{f?>{f?>g8>J>ݦf?TvM>JP>f?=>}f?T ;>g8T ;>g8=>}f?z5(>f?`%>8`%>8z5(>f?#>yf?(O >I9(O >I9#>yf?,=}f?ƒ=9ƒ=9,=}f?=f?h=ҵ9h=ҵ9=f?=f?\N=;9\N=;9=f?+=Xf?U =o:U =o:+=Xf?f?>q9>q9fY>f?Ē>f?{(>39{(>39Ē>f?>f?>9>9>f?.>f?J>l:S>4f?JP>:V!:b>:e>f?e>f?b>:X`x>H:4{>Uf?4{>Uf?X`x>H: 9>9X>f??t?>n?~?sh?? 1b?!B?­[?"B?nJU??EO?~?I?>@E?ߊ>FA?%>3$??A>=?>=?߿>2$??7>DA?S>@E?;>I?>EO?ȋ>kJU?̆>[?/B>1b?/B>ph?̆>n?ȋ>?t?>@x?;><|?R>~?5>?߿>?>~?>><|?">@x?܊>i>f?>>˵9`>69p>zf?X>f? 9>9>>˵9i>f?*A?:=v@E??=mI?<<EO?p</JU? ;[?\50b?4h? ;n?p<t?1<x?R?=|<|?6=~?;Z=?t=?G>ú~?3*><|?B>qx?X>zt?j>$n?Svy>h?h>f1b?B>[?/B>JU?>;FO?Rwy>I?#j>@E?BX>jA?B>K$??7*>=?I>=?x=%$??B^=DATA^;             !!#$"$&'%')*(*,-+-/!0 . 0!2#3"1"3#5%6$4$6%8'9&7&9';)<(:(<)>+?*=*?+A-B,@,B-D/E.C.E/G1H0F0H1J3K2I2K3M5N4L4N5P7Q6O6Q7S9T8R8T9V;W:U:W;Y=Z<X_?\=Y;V9S7P5M3J1G/D-A+>);'8%5#2!/,)&#    >]?_^<Z=\?]>[   "%(+. 1"4$7&:(=*@,C.F0I2L4O6R8U:X<[>^DATAح^|UVMap(^NGon Face^DATA (^<"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^DATA^:"  $(,048<@DHLPTX\`dhlptx  BRX^{^BRAddh.001?^??????????L>????????????????????????????? # Kfff?=???L>??>!>???DATA8^??????????L>?????????????????????????????DATA@^w????C?~6=~.=m^??????DATA0m^u?>k?@? ף=?BRX^{h!^^BRBlob001?X^??????????L>?????????????????????????????# Kfff?=??????>!>?>>>>?DATA8P^??????????L>?????????????????????????????DATA@X^w????C?._ra ^??????DATA0 ^u?>ףp?@?u=?BRXh!^{*^^BRBlur.004?(^??????????L>????????????????????????????? # Kfff?=???L>??>!>???DATA8"^??????????L>?????????????????????????????DATA@(^w????C?~6=~.=)^??????DATA0)^u?>k?@? ף=?BRX*^{2^h!^BRBrush?0^??????????L>?????????????????????????????# Kfff?=??????>!?>>>>?DATA8*^??????????L>?????????????????????????????DATA@0^w????C?._raH2^??????DATA0H2^u?>ףp?@?u=?BRX2^{x;^*^BRClay001?h9^??????????L>?????????????????????????????# Kfff?=??????>!>?>>>>?DATA8`3^??????????L>?????????????????????????????DATA@h9^w????C?._ra:^??????DATA0:^u?>ףp?@?u=?BRXx;^{(D^2^BRClay Strips01?B^??????????L>?????????????????????????????# Kfff?=??????>!wN??>>>>?DATA8<^??????????L>?????????????????????????????DATA@B^w????C?._raC^??????DATA0C^u?>ףp?@?u=?BRX(D^{L^x;^BRClone001?J^??????????L>?????????????????????????????# Kfff?=???333???>!>???DATA8D^??????????L>?????????????????????????????DATA@J^w????C?~6=~.=XL^??????DATA0XL^u?>k?@? ף=?BRXL^{U^(D^BRCrease001?xS^??????????L>?????????????????????????????# Kfff?=???>??>!>?>>>>?DATA8pM^??????????L>?????????????????????????????DATA@xS^w????C?a2p? U^??????DATA0U^u?>?@? #=?BRXU^{8^^L^BRDarken06?(\^??????????L>????????????????????????????? # Kfff?=???L>??>!>???DATA8 V^??????????L>?????????????????????????????DATA@(\^w????C?~6=~.=]^??????DATA0]^u?>k?@? ף=?BRX8^^{f^U^BRDraw.001?d^??????????L>?????????????????????????????# Kfff?=??????>!>?>>>>?DATA8^^??????????L>?????????????????????????????DATA@d^w????C?._rahf^??????DATA0hf^u?>ףp?@?u=?BRXf^{o^8^^BRFill/Deepen001?m^??????????L>?????????????????????????????# Kfff?=???? ??>!>??>>??DATA8g^??????????L>?????????????????????????????DATA@m^w????C?._rao^??????DATA0o^u?>ףp?@?u=?BRXo^{Hx^f^BRFlatten/Contrast001?8v^??????????L>?????????????????????????????# Kfff?=??????>!>??>>??DATA80p^??????????L>?????????????????????????????DATA@8v^w????C?._raw^??????DATA0w^u?>ףp?@?u=?BRXHx^{^o^BRGrab001?~^??????????L>?????????????????????????????K Kfff?=???L>??>!>>?>DATA8x^??????????L>?????????????????????????????DATA@~^w????C?._rax^??????DATA0x^u?>ףp?@?u=?BRX^{^Hx^BRInflate/Deflate001?^??????????L>?????????????????????????????# Kfff?=??????>!>@?@?@?>>>DATA8^??????????L>?????????????????????????????DATA@^w????C?._ra(^??????DATA0(^u?>ףp?@?u=?BRX^{X^^BRLayer001?H^??????????L>?????????????????????????????# Kfff?=??????>!>?>>DATA8@^??????????L>?????????????????????????????DATA@H^w????C?._raؑ^??????DATA0ؑ^u?>ףp?@?u=?BRXX^{^^BRLighten5?^??????????L>????????????????????????????? # Kfff?=???L>??>!>???DATA8^??????????L>?????????????????????????????DATA@^w????C?~6=~.=^??????DATA0^u?>k?@? ף=?BRX^{^X^BRMixh?^??????????L>????????????????????????????? # Kfff?=???333???>!>???DATA8^??????????L>?????????????????????????????DATA@^w????C?~6=~.=8^??????DATA08^u?>k?@? ף=?BRX^{h^^BRMultiply?X^??????????L>????????????????????????????? # Kfff?=???L>??>!>???DATA8P^??????????L>?????????????????????????????DATA@X^w????C?~6=~.=^??????DATA0^u?>k?@? ף=?BRXh^{^^BRNudge001?^??????????L>?????????????????????????????# Kfff?=???? ??>!>>?>DATA8^??????????L>?????????????????????????????DATA@^w????C?._ra^??????DATA0^u?>ףp?@?u=?BRX^{Ƚ^h^BRPinch/Magnify001?^??????????L>?????????????????????????????# Kfff?=??????>!>@?@?@?>>>DATA8^??????????L>?????????????????????????????DATA@^w????C?._raH^??????DATA0H^u?>ףp?@?u=?BRXȽ^{x^^BRPolish001?h^??????????L>?????????????????????????????# Kfff?=???????>!>??>>??DATA8`^??????????L>?????????????????????????????DATA@h^w????C?._ra^??????DATA0^u?>ףp?@?u=?BRXx^{(^Ƚ^BRScrape/Peaks001?^??????????L>?????????????????????????????# Kfff?=???? ??>!>??>>??DATA8^??????????L>?????????????????????????????DATA@^w????C?._ra^??????DATA0^u?>ףp?@?u=?BRX(^{^x^BRSculptDraw?^??????????L>?????????????????????????????# Kfff?=??????>!wN??>>>>?DATA8^??????????L>?????????????????????????????DATA@^w????C?._raX^??????DATA0X^u?>ףp?@?u=?BRX^{^(^BRSmear001?x^??????????L>?????????????????????????????# Kfff?=???L>??>!>???DATA8p^??????????L>?????????????????????????????DATA@x^w????C?~6=~.=^??????DATA0^u?>k?@? ף=?BRX^{8^^BRSmooth001?(^??????????L>?????????????????????????????#Kfff?=??????>!>@?@?@?DATA8 ^??????????L>?????????????????????????????DATA@(^w????C?._ra^??????DATA0^u?>ףp?@?u=?BRX8^{^^BRSnake Hook001?^??????????L>?????????????????????????????K Kfff?=???? ??>!>>?>DATA8^??????????L>?????????????????????????????DATA@^w????C?._rah^??????DATA0h^u?>ףp?@?u=?BRX^{^8^BRSoften01?^??????????L>?????????????????????????????# Kfff?=???L>??>!>???DATA8^??????????L>?????????????????????????????DATA@^w????C?~6=~.=^??????DATA0^u?>k?@? ף=?BRX^{H^^BRSubtract?8^??????????L>????????????????????????????? # Kfff?=???L>??>!>???DATA80^??????????L>?????????????????????????????DATA@8^w????C?~6=~.=^??????DATA0^u?>k?@? ף=?BRXH^{ ^^BRTexDraw? ^??????????L>?????????????????????????????# Kfff?=???333???>!>???>>?DATA8^??????????L>?????????????????????????????DATA@ ^w????C?._rax ^??????DATA0x ^u?>ףp?@?u=?BRX ^{^H^BRThumb001?^??????????L>?????????????????????????????K Kfff?=???? ??>!>>?>DATA8 ^??????????L>?????????????????????????????DATA@^w????C?._ra(^??????DATA0(^u?>ףp?@?u=?BRX^{ ^BRTwist001?H^??????????L>?????????????????????????????K Kfff?=??????>!>>?>DATA8@^??????????L>?????????????????????????????DATA@H^w????C?._ra^??????DATA0^u?>ףp?@?u=?DNA1X^SDNANAME: *next*prev*data*first*lastxyzxminxmaxyminymax*pointergroupvalval2typesubtypeflagname[64]saveddatalentotallen*newid*libname[66]padusicon_idpad2*propertiesid*idblock*filedataname[1024]filepath[1024]tot*parentw[2]h[2]changed[2]changed_timestamp[2]*rect[2]*obblocktypeadrcodename[128]*bp*beztmaxrcttotrctvartypetotvertipoextraprtbitmaskslide_minslide_maxcurval*drivercurvecurshowkeymuteipopospad1relativetotelemuid*weightsvgroup[64]sliderminslidermax*adt*refkeyelemstr[32]elemsizeblock*ipo*fromtotkeyslurphctimeuidgen*line*formatblenlinenostartendflagscolor[4]pad[4]*namenlineslines*curl*sellcurcselcmarkers*undo_bufundo_posundo_len*compiledmtimesizeseekdtxpassepartalphaclipstaclipendlensortho_scaledrawsizesensor_xsensor_yshiftxshiftyYF_dofdist*dof_obsensor_fitpad[7]*sceneframenrframesoffsetsfrafie_imacyclokmulti_indexlayerpassibufs*gputexture*anim*rr*renders[8]render_slotlast_render_slotsourcelastframetpageflagtotbindxrepyreptwstatwendbindcode*repbind*packedfile*previewlastupdatelastusedanimspeedgen_xgen_ygen_typegen_flagaspxaspytexcomaptomaptonegblendtype*object*texuvname[64]projxprojyprojzmappingofs[3]size[3]rottexflagcolormodelpmaptopmaptonegnormapspacewhich_outputbrush_map_modergbkdef_varcolfacvarfacnorfacdispfacwarpfaccolspecfacmirrfacalphafacdifffacspecfacemitfachardfacraymirrfactranslfacambfaccolemitfaccolreflfaccoltransfacdensfacscatterfacreflfactimefaclengthfacclumpfacdampfackinkfacroughfacpadensfacgravityfaclifefacsizefacivelfacfieldfacshadowfaczenupfaczendownfacblendfac*handle*pname*stnamesstypesvars*varstr*result*cfradata[32](*doit)()(*instance_init)()(*callback)()versionaipotype*ima*cube[6]imat[4][4]obimat[3][3]stypeviewscalenotlaycuberesdepthrecalclastsizefalloff_typefalloff_softnessradiuscolor_sourcetotpointspdpadpsyspsys_cache_spaceob_cache_space*point_tree*point_datanoise_sizenoise_depthnoise_influencenoise_basispdpad3[3]noise_facspeed_scalefalloff_speed_scalepdpad2*coba*falloff_curveresol[3]interp_typefile_formatextendsmoked_typeint_multiplierstill_framesource_path[1024]*datasetcachedframeoceanmod[64]outputnoisesizeturbulbrightcontrastsaturationrfacgfacbfacfiltersizemg_Hmg_lacunaritymg_octavesmg_offsetmg_gaindist_amountns_outscalevn_w1vn_w2vn_w3vn_w4vn_mexpvn_distmvn_coltypenoisedepthnoisetypenoisebasisnoisebasis2imaflagcropxmincropymincropxmaxcropymaxtexfilterafmaxxrepeatyrepeatcheckerdistnablaiuser*nodetree*plugin*env*pd*vd*otuse_nodesloc[3]rot[3]mat[4][4]min[3]max[3]cobablend_color[3]blend_factorblend_typepad[3]modetotexshdwrshdwgshdwbshdwpadenergydistspotsizespotblendhaintatt1att2*curfalloffshadspotsizebiassoftcompressthreshpad5[3]bufsizesampbuffersfiltertypebufflagbuftyperay_sampray_sampyray_sampzray_samp_typearea_shapearea_sizearea_sizeyarea_sizezadapt_threshray_samp_methodtexactshadhalostepsun_effect_typeskyblendtypehorizon_brightnessspreadsun_brightnesssun_sizebackscattered_lightsun_intensityatm_turbidityatm_inscattering_factoratm_extinction_factoratm_distance_factorskyblendfacsky_exposuresky_colorspacepad4[6]*mtex[18]pr_texturepad6[4]densityemissionscatteringreflectionemission_col[3]transmission_col[3]reflection_col[3]density_scaledepth_cutoffasymmetrystepsize_typeshadeflagshade_typeprecache_resolutionstepsizems_diffms_intensityms_spreadalpha_blendface_orientationmaterial_typespecrspecgspecbmirrmirgmirbambrambbambgambemitangspectraray_mirroralpharefspeczoffsaddtranslucencyvolgamefresnel_mirfresnel_mir_ifresnel_trafresnel_tra_ifiltertx_limittx_falloffray_depthray_depth_traharseed1seed2gloss_mirgloss_trasamp_gloss_mirsamp_gloss_traadapt_thresh_miradapt_thresh_traaniso_gloss_mirdist_mirfadeto_mirshade_flagmode_lflarecstarclinecringchasizeflaresizesubsizeflarebooststrand_stastrand_endstrand_easestrand_surfnorstrand_minstrand_widthfadestrand_uvname[64]sbiaslbiasshad_alphaseptexrgbselpr_typepr_backpr_lampml_flagdiff_shaderspec_shaderroughnessrefracparam[4]rmsdarkness*ramp_col*ramp_specrampin_colrampin_specrampblend_colrampblend_specramp_showpad3rampfac_colrampfac_spec*groupfrictionfhreflectfhdistxyfrictdynamodesss_radius[3]sss_col[3]sss_errorsss_scalesss_iorsss_colfacsss_texfacsss_frontsss_backsss_flagsss_presetmapto_texturedshadowonly_flagindexgpumaterial*bbselcol1selcol2quat[4]expxexpyexpzradrad2s*mat*imatelemsdisp*editelems**matflag2totcolwiresizerendersizethresh*lastelemvec[3][3]alfaweighth1h2f1f2f3hidevec[4]mat_nrpntsupntsvresoluresolvorderuordervflaguflagv*knotsu*knotsvtilt_interpradius_interpcharidxkernwhnurbs*keyindexshapenrnurb*editnurb*bevobj*taperobj*textoncurve*path*keybevdrawflagtwist_modetwist_smoothsmallcaps_scalepathlenbevresolwidthext1ext2resolu_renresolv_renactnu*lastselspacemodespacinglinedistshearfsizewordspaceulposulheightxofyoflinewidth*str*selboxes*editfontfamily[24]*vfont*vfontb*vfonti*vfontbisepchartotboxactbox*tbselstartselend*strinfocurinfo*mselect*mpoly*mtpoly*mloop*mloopuv*mloopcol*mface*mtface*tface*mvert*medge*dvert*mcol*msticky*texcomesh*edit_btmeshvdataedatafdatapdataldatatotedgetotfacetotselecttotpolytotloopact_facesmoothreshsubdivsubdivrsubsurftypeeditflag*mr*tpageuv[4][2]col[4]transptileunwrapv1v2v3v4edcodecreasebweightdef_nr*dwtotweightco[3]no[3]loopstartveuv[2]co[2]fis[256]totdisplevel(*disps)()*hiddenv[4]midpad[2]v[2]*faces*colfaces*edges*vertslevelslevel_countcurrentnewlvledgelvlpinlvlrenderlvluse_col*edge_flags*edge_creasesstackindex*errormodifier*texture*map_objectuvlayer_name[64]uvlayer_tmptexmappingsubdivTyperenderLevels*emCache*mCachestrengthdefaxispad[6]lengthrandomizeseed*ob_arm*start_cap*end_cap*curve_ob*offset_oboffset[3]scale[3]merge_distfit_typeoffset_typecountaxistolerance*mirror_obsplit_anglevalueresval_flagslim_flagse_flagsbevel_angledefgrp_name[64]*domain*flow*colltimedirectionmidlevel*projectors[10]*imagenum_projectorsaspectxaspectyscalexscaleypercentfaceCountfacrepeat*objectcenterstartxstartyheightnarrowspeeddampfallofftimeoffslifetimedeformflagmulti*prevCossubtarget[64]parentinv[4][4]cent[3]*indexartotindexforce*clothObject*sim_parms*coll_parms*point_cacheptcaches*x*xnew*xold*current_xnew*current_x*current_v*mfacesnumvertsnumfacestime_xtime_xnew*bvhtree*v*dmcfraoperationvertextotinfluencegridsize*bindinfluences*bindoffsets*bindcagecostotcagevert*dyngrid*dyninfluences*dynverts*pad2dyngridsizedyncellmin[3]dyncellwidthbindmat[4][4]*bindweights*bindcos(*bindfunc)()*psystotdmverttotdmedgetotdmfacepositionrandom_position*facepavgroupprotectlvlsculptlvltotlvlsimple*fss*target*auxTargetvgroup_name[64]keepDistshrinkTypeshrinkOptsprojAxissubsurfLevels*originfactorlimit[2]originOptsoffset_facoffset_fac_vgcrease_innercrease_outercrease_rimmat_ofsmat_ofs_rim*ob_axisstepsrender_stepsiterscrew_ofsangle*ocean*oceancacheresolutionspatial_sizewind_velocitysmallest_wavewave_alignmentwave_directionwave_scalechop_amountfoam_coveragebakestartbakeendcachepath[1024]foamlayername[64]cachedgeometry_moderefreshrepeat_xrepeat_yfoam_fade*object_from*object_tofalloff_radiusedit_flagsdefault_weight*cmap_curveadd_thresholdrem_thresholdmask_constantmask_defgrp_name[64]mask_tex_use_channel*mask_texture*mask_tex_map_objmask_tex_mappingmask_tex_uvlayer_name[64]pad_i1defgrp_name_a[64]defgrp_name_b[64]default_weight_adefault_weight_bmix_modemix_setpad_c1[6]proximity_modeproximity_flags*proximity_ob_targetmin_distmax_distpad_s1*canvas*brushthresholdscalehermite_num*lattpntswopntsuopntsvopntswtypeutypevtypewfufvfwdudvdw*def*latticedatalatmat[4][4]*editlattvec[8][3]*sculptpartypepar1par2par3parsubstr[64]*track*proxy*proxy_group*proxy_from*action*poselib*pose*gpdavs*mpathconstraintChannelseffectdefbasemodifiersrestore_mode*matbitsactcoldloc[3]orig[3]dsize[3]dscale[3]drot[3]dquat[4]rotAxis[3]drotAxis[3]rotAngledrotAngleobmat[4][4]constinv[4][4]imat_ren[4][4]laypad6colbitstransflagprotectflagtrackflagupflagnlaflagipoflagscaflagscavisflagpad5dupondupoffdupstadupendsfmassdampinginertiaformfactorrdampingmarginmax_velmin_velm_contactProcessingThresholdobstacleRadrotmodeboundtypecollision_boundtyperestrictflagdtempty_drawtypeempty_drawsizedupfacescapropsensorscontrollersactuatorsbbsize[3]actdefgameflaggameflag2*bsoftsoftflaganisotropicFriction[3]constraintsnlastripshooksparticlesystem*soft*dup_groupbody_typeshapeflag*fluidsimSettings*derivedDeform*derivedFinallastDataMaskcustomdata_maskstateinit_stategpulamppc_ids*duplilistima_ofs[2]curindexactiveoriglayomat[4][4]orco[3]no_drawanimateddeflectforcefieldshapetex_modekinkkink_axiszdirf_strengthf_dampf_flowf_sizef_powermaxdistmindistf_power_rmaxradminradpdef_damppdef_rdamppdef_permpdef_frictpdef_rfrictpdef_sticknessabsorptionpdef_sbdamppdef_sbiftpdef_sboftclump_facclump_powkink_freqkink_shapekink_ampfree_endtex_nabla*rngf_noiseweight[13]global_gravityrt[3]totdataframetotpointdata_types*data[8]*cur[8]extradatastepsimframestartframeendframeeditframelast_exactlast_validcompressionprev_name[64]info[64]path[1024]*cached_framesmem_cache*edit(*free_edit)()linStiffangStiffvolumeviterationspiterationsditerationsciterationskSRHR_CLkSKHR_CLkSSHR_CLkSR_SPLT_CLkSK_SPLT_CLkSS_SPLT_CLkVCFkDPkDGkLFkPRkVCkDFkMTkCHRkKHRkSHRkAHRcollisionflagsnumclusteriterationsweldingtotspring*bpoint*bspringmsg_lockmsg_valuenodemassnamedVG_Mass[64]gravmediafrictrklimitphysics_speedgoalspringgoalfrictmingoalmaxgoaldefgoalvertgroupnamedVG_Softgoal[64]fuzzynessinspringinfrictnamedVG_Spring_K[64]efraintervallocalsolverflags**keystotpointkeysecondspringcolballballdampballstiffsbc_modeaeroedgeminloopsmaxloopschokesolver_IDplasticspringpreload*scratchshearstiffinpush*pointcache*effector_weightslcom[3]lrot[3][3]lscale[3][3]last_framevel[3]*fmdshow_advancedoptionsresolutionxyzpreviewresxyzrealsizeguiDisplayModerenderDisplayModeviscosityValueviscosityModeviscosityExponentgrav[3]animStartanimEndbakeStartbakeEndframeOffsetgstarmaxRefineiniVelxiniVelyiniVelz*orgMesh*meshBBsurfdataPath[1024]bbStart[3]bbSize[3]typeFlagsdomainNovecgenvolumeInitTypepartSlipValuegenerateTracersgenerateParticlessurfaceSmoothingsurfaceSubdivsparticleInfSizeparticleInfAlphafarFieldSize*meshVelocitiescpsTimeStartcpsTimeEndcpsQualityattractforceStrengthattractforceRadiusvelocityforceStrengthvelocityforceRadiuslastgoodframeanimRatemistypehorrhorghorbzenrzengzenbfastcolexposureexprangelinfaclogfacgravityactivityBoxRadiusskytypeocclusionResphysicsEngineticratemaxlogicstepphysubstepmaxphystepmisimiststamistdistmisthistarrstargstarbstarkstarsizestarmindiststardiststarcolnoisedofstadofenddofmindofmaxaodistaodistfacaoenergyaobiasaomodeaosampaomixaocolorao_adapt_threshao_adapt_speed_facao_approx_errorao_approx_correctionao_indirect_energyao_env_energyao_pad2ao_indirect_bouncesao_padao_samp_methodao_gather_methodao_approx_passes*aosphere*aotablesselcolsxsy*lpFormat*lpParmscbFormatcbParmsfccTypefccHandlerdwKeyFrameEverydwQualitydwBytesPerSeconddwFlagsdwInterleaveEveryavicodecname[128]*cdParms*padcdSizeqtcodecname[128]codecTypecodecSpatialQualitycodeccodecFlagscolorDepthcodecTemporalQualityminSpatialQualityminTemporalQualitykeyFrameRatebitRateaudiocodecTypeaudioSampleRateaudioBitDepthaudioChannelsaudioCodecFlagsaudioBitRateaudio_codecvideo_bitrateaudio_bitrateaudio_mixrateaudio_channelsaudio_padaudio_volumegop_sizerc_min_raterc_max_raterc_buffer_sizemux_packet_sizemux_ratemixratemainspeed_of_sounddoppler_factordistance_model*mat_override*light_overridelay_zmasklayflagpassflagpass_xorimtypeplanesqualitycompressexr_codeccineon_flagcineon_whitecineon_blackcineon_gammajp2_flagim_format*avicodecdata*qtcodecdataqtcodecsettingsffcodecdatasubframepsfrapefraimagesframaptothreadsframelenblurfacedgeRedgeGedgeBfullscreenxplayyplayfreqplayattribframe_stepstereomodedimensionspresetmaximsizexschyschxpartsypartssubimtypedisplaymodescemoderaytrace_optionsraytrace_structureocrespad4alphamodeosafrs_secedgeintsafetyborderdisprectlayersactlaymblur_samplesxaspyaspfrs_sec_basegausscolor_mgt_flagpostgammaposthuepostsatdither_intensitybake_osabake_filterbake_modebake_flagbake_normal_spacebake_quad_splitbake_maxdistbake_biasdistbake_padpic[1024]stampstamp_font_idstamp_udata[768]fg_stamp[4]bg_stamp[4]seq_prev_typeseq_rend_typeseq_flagpad5[5]simplify_flagsimplify_subsurfsimplify_shadowsamplessimplify_particlessimplify_aossscineonwhitecineonblackcineongammajp2_presetjp2_depthrpad3domeresdomemodedomeangledometiltdomeresbuf*dometextengine[32]name[32]particle_percsubsurf_maxshadbufsample_maxao_errortiltresbuf*warptextcol[3]cellsizecellheightagentmaxslopeagentmaxclimbagentheightagentradiusedgemaxlenedgemaxerrorregionminsizeregionmergesizevertsperpolydetailsampledistdetailsamplemaxerrorframingplayerflagrt1rt2aasamplespad4[3]domestereoflageyeseparationrecastDatamatmodeexitkeyobstacleSimulationlevelHeight*camera*paint_cursorpaint_cursor_col[4]paintseam_bleednormal_anglescreen_grab_size[2]*paintcursorinverttotrekeytotaddkeybrushtypebrush[7]emitterdistselectmodeedittypedraw_stepfade_framesradial_symm[3]last_xlast_ylast_angledraw_anchoredanchored_sizeanchored_location[3]anchored_initial_mouse[2]draw_pressurepressure_valuespecial_rotation*vpaint_prev*wpaint_prevmat[3][3]unprojected_radius*vpaint*wpaint*uvsculptvgroup_weightcornertypejointrilimitdegrturnextr_offsdoublimitnormalsizeautomergesegmentsringsverticesunwrapperuvcalc_radiusuvcalc_cubesizeuvcalc_marginuvcalc_mapdiruvcalc_mapalignuvcalc_flaguv_flaguv_selectmodeuv_subsurf_levelgpencil_flagsautoik_chainlenimapaintparticleproportional_sizeselect_threshclean_threshautokey_modeautokey_flagmultires_subdiv_typepad2[5]skgen_resolutionskgen_threshold_internalskgen_threshold_externalskgen_length_ratioskgen_length_limitskgen_angle_limitskgen_correlation_limitskgen_symmetry_limitskgen_retarget_angle_weightskgen_retarget_length_weightskgen_retarget_distance_weightskgen_optionsskgen_postproskgen_postpro_passesskgen_subdivisions[3]skgen_multi_level*skgen_templatebone_sketchingbone_sketching_convertskgen_subdivision_numberskgen_retarget_optionsskgen_retarget_rollskgen_side_string[8]skgen_num_string[8]edge_modeedge_mode_live_unwrapsnap_modesnap_flagsnap_targetproportionalprop_modeproportional_objectspad[5]auto_normalizemultipaintuse_uv_sculptuv_sculpt_settingsuv_sculpt_tooluv_relax_methodsculpt_paint_settingssculpt_paint_unified_sizesculpt_paint_unified_unprojected_radiussculpt_paint_unified_alphaunified_paint_settingstotobjtotlamptotobjseltotcurvetotmeshtotarmaturescale_lengthsystemsystem_rotationgravity[3]quick_cache_step*world*setbase*basact*obeditcursor[3]twcent[3]twmin[3]twmax[3]layactlay_updated*ed*toolsettings*statsaudiotransform_spaces*sound_scene*sound_scene_handle*sound_scrub_handle*speaker_handles*fps_info*theDagdagisvaliddagflagsactive_keyingsetkeyingsetsgmunitphysics_settings*clipcustomdata_mask_modalcuserblendviewwinmat[4][4]viewmat[4][4]viewinv[4][4]persmat[4][4]persinv[4][4]viewmatob[4][4]persmatob[4][4]clip[6][4]clip_local[6][4]*clipbb*localvd*ri*render_engine*depths*sms*smooth_timertwmat[4][4]viewquat[4]zfaccamdxcamdypixsizecamzoomis_perspperspviewlocktwdrawflagrflaglviewquat[4]lpersplviewgridviewtwangle[3]rot_anglerot_axis[3]regionbasespacetypeblockscaleblockhandler[8]bundle_sizebundle_drawtypelay_used*ob_centrebgpicbase*bgpicob_centre_bone[64]drawtypeob_centre_cursorscenelockaroundgridnearfarmodeselectgridlinesgridsubdivgridflagtwtypetwmodetwflagpad2[2]afterdraw_transpafterdraw_xrayafterdraw_xraytranspzbufxraypad3[2]*properties_storageverthormaskmin[2]max[2]minzoommaxzoomscrollscroll_uikeeptotkeepzoomkeepofsalignwinxwinyoldwinxoldwiny*tab_offsettab_numtab_currpt_maskv2d*adsghostCurvesautosnapcursorValmainbmainbomainbuserre_alignpreviewtexture_contextpathflagdataicon*pinid*texuserrender_sizechanshownzebrazoomtitle[32]dir[1056]file[256]renamefile[256]renameedit[256]filter_glob[64]active_filesel_firstsel_lastsortdisplayf_fpfp_str[8]scroll_offset*params*files*folders_prev*folders_next*op*smoothscroll_timer*layoutrecentnrbookmarknrsystemnrtree*treestoresearch_string[32]search_tseoutlinevisstoreflagsearch_flags*cumapscopessample_line_histcursor[2]centxcentycurtilelockpindt_uvstickydt_uvstretch*texttopviewlinesmenunrlheightcwidthlinenrs_totleftshowlinenrstabnumbershowsyntaxline_hlightoverwritelive_editpix_per_linetxtscrolltxtbarwordwrapdopluginsfindstr[256]replacestr[256]margin_column*drawcache*py_draw*py_event*py_button*py_browsercallback*py_globaldictlastspacescriptname[1024]scriptarg[256]*script*but_refs*arraycachescache_display*idaspectpadfmxmy*edittreetreetypetexfromshaderfromlinkdraglen_alloccursorscrollbackhistoryprompt[256]language[32]sel_startsel_endfilter[64]xlockofylockofuserpath_lengthloc[2]stabmat[4][4]unistabmat[4][4]postproc_flagruntime_flagfilename[1024]blf_iduifont_idr_to_lpointskerningitalicboldshadowshadxshadyshadowalphashadowcolorpaneltitlegrouplabelwidgetlabelwidgetpanelzoomminlabelcharsminwidgetcharscolumnspacetemplatespaceboxspacebuttonspacexbuttonspaceypanelspacepanelouteroutline[4]inner[4]inner_sel[4]item[4]text[4]text_sel[4]shadedshadetopshadedownalpha_checkinner_anim[4]inner_anim_sel[4]inner_key[4]inner_key_sel[4]inner_driven[4]inner_driven_sel[4]header[4]show_headerwcol_regularwcol_toolwcol_textwcol_radiowcol_optionwcol_togglewcol_numwcol_numsliderwcol_menuwcol_pulldownwcol_menu_backwcol_menu_itemwcol_tooltipwcol_boxwcol_scrollwcol_progresswcol_list_itemwcol_statepaneliconfile[256]icon_alphaback[4]title[4]text_hi[4]header_title[4]header_text[4]header_text_hi[4]button[4]button_title[4]button_text[4]button_text_hi[4]list[4]list_title[4]list_text[4]list_text_hi[4]panel[4]panel_title[4]panel_text[4]panel_text_hi[4]shade1[4]shade2[4]hilite[4]grid[4]wire[4]select[4]lamp[4]speaker[4]empty[4]camera[4]pad[8]active[4]group[4]group_active[4]transform[4]vertex[4]vertex_select[4]edge[4]edge_select[4]edge_seam[4]edge_sharp[4]edge_facesel[4]edge_crease[4]face[4]face_select[4]face_dot[4]extra_edge_len[4]extra_face_angle[4]extra_face_area[4]pad3[4]normal[4]vertex_normal[4]bone_solid[4]bone_pose[4]strip[4]strip_select[4]cframe[4]nurb_uline[4]nurb_vline[4]act_spline[4]nurb_sel_uline[4]nurb_sel_vline[4]lastsel_point[4]handle_free[4]handle_auto[4]handle_vect[4]handle_align[4]handle_auto_clamped[4]handle_sel_free[4]handle_sel_auto[4]handle_sel_vect[4]handle_sel_align[4]handle_sel_auto_clamped[4]ds_channel[4]ds_subchannel[4]console_output[4]console_input[4]console_info[4]console_error[4]console_cursor[4]vertex_sizeoutline_widthfacedot_sizenoodle_curvingsyntaxl[4]syntaxn[4]syntaxb[4]syntaxv[4]syntaxc[4]movie[4]movieclip[4]image[4]scene[4]audio[4]effect[4]plugin[4]transition[4]meta[4]editmesh_active[4]handle_vertex[4]handle_vertex_select[4]handle_vertex_sizemarker_outline[4]marker[4]act_marker[4]sel_marker[4]dis_marker[4]lock_marker[4]bundle_solid[4]path_before[4]path_after[4]camera_path[4]hpad[7]preview_back[4]preview_stitch_face[4]preview_stitch_edge[4]preview_stitch_vert[4]preview_stitch_stitchable[4]preview_stitch_unstitchable[4]preview_stitch_active[4]match[4]selected_highlight[4]solid[4]tuitbutstv3dtfiletipotinfotacttnlatseqtimatexttoopsttimetnodetlogictuserpreftconsoletcliptarm[20]active_theme_areamodule[64]spec[4]dupflagsavetimetempdir[768]fontdir[768]renderdir[1024]textudir[768]plugtexdir[768]plugseqdir[768]pythondir[768]sounddir[768]image_editor[1024]anim_player[1024]anim_player_presetv2d_min_gridsizetimecode_styleversionsdbl_click_timegameflagswheellinescrolluiflaglanguageuserprefviewzoommixbufsizeaudiodeviceaudiorateaudioformataudiochannelsdpiencodingtransoptsmenuthreshold1menuthreshold2themesuifontsuistyleskeymapsuser_keymapsaddonskeyconfigstr[64]undostepsundomemorygp_manhattendistgp_euclideandistgp_erasergp_settingstb_leftmousetb_rightmouselight[3]tw_hotspottw_flagtw_handlesizetw_sizetextimeouttexcollectratewmdrawmethoddragthresholdmemcachelimitprefetchframesframeserverportpad_rot_angleobcenter_diarvisizervibrightrecent_filessmooth_viewtxglreslimitcurssizecolor_picker_typeipo_newkeyhandles_newscrcastfpsscrcastwaitwidget_unitanisotropic_filteruse_16bit_texturespad8ndof_sensitivityndof_flagglalphacliptext_renderpad9coba_weightsculpt_paint_overlay_col[3]tweak_thresholdauthor[80]compute_device_typecompute_device_idfcu_inactive_alphavertbaseedgebaseareabase*newsceneredraws_flagfulltempwiniddo_drawdo_refreshdo_draw_gesturedo_draw_paintcursordo_draw_dragswapmainwinsubwinactive*animtimer*contexthandler[8]*newvvec*v1*v2*typepanelname[64]tabname[64]drawname[64]ofsxofsysizexsizeylabelofscontrolsnapsortorder*paneltab*activedatalist_scrolllist_sizelist_last_lenlist_grip_sizelist_search[64]*v3*v4*fullbutspacetypeheadertypespacedatahandlersactionzoneswinrctdrawrctswinidregiontypealignmentdo_draw_overlayuiblockspanels*headerstr*regiondatasubvstr[4]subversionpadsminversionminsubversionwinpos*curscreen*curscenefileflagsglobalfrevisionname[256]orig_widthorig_heightbottomrightxofsyofslift[3]gamma[3]gain[3]dir[768]tcbuild_size_flagsbuild_tc_flagsdonestartstillendstill*stripdata*crop*transform*color_balance*instance_private_data**current_private_data*tmpstartofsendofsmachinestartdispenddispsatmulhandsizeanim_preseekstreamindexmulticam_sourceclip_flag*strip*scene_cameraeffect_faderspeed_fader*seq1*seq2*seq3seqbase*sound*scene_soundpitchpanstrobe*effectdataanim_startofsanim_endofsblend_modeblend_opacity*oldbasep*parseq*seqbasepmetastack*act_seqact_imagedir[1024]act_sounddir[1024]over_ofsover_cfraover_flagover_borderedgeWidthforwardwipetypefMinifClampfBoostdDistdQualitybNoCompScalexIniScaleyInixIniyInirotIniinterpolationuniform_scale*frameMapglobalSpeedlastValidFramebuttypeuserjitstatotpartnormfacobfacrandfactexfacrandlifeforce[3]vectsizemaxlendefvec[3]mult[4]life[4]child[4]mat[4]texmapcurmultstaticstepomattimetexspeedtexflag2negvertgroup_vvgroupname[64]vgroupname_v[64]*keysminfacnrusedusedelem*poinresetdistlastval*makeyqualqual2targetName[64]toggleName[64]value[64]maxvalue[64]delaydurationmaterialName[64]damptimerpropname[64]matname[64]axisflagposechannel[64]constraint[64]*fromObjectsubject[64]body[64]otypepulsefreqtotlinks**linkstapjoyindexaxis_singleaxisfbuttonhathatfprecisionstr[128]*mynewinputstotslinks**slinksvalostate_mask*actframeProp[64]blendinpriorityend_resetstrideaxisstridelengthlayer_weightmin_gainmax_gainreference_distancemax_distancerolloff_factorcone_inner_anglecone_outer_anglecone_outer_gainsndnrsound3Dpad6[1]*melinVelocity[3]angVelocity[3]localflagdyn_operationforceloc[3]forcerot[3]pad1[3]linearvelocity[3]angularvelocity[3]*referenceminmaxrotdampminloc[3]maxloc[3]minrot[3]maxrot[3]matprop[64]butstabutenddistributionint_arg_1int_arg_2float_arg_1float_arg_2toPropName[64]*toObjectbodyTypefilename[64]loadaniname[64]int_argfloat_arginfluence*subtargetfacingaxisvelocityaccelerationturnspeedupdateTime*navmeshgo*newpackedfileattenuationdistance*cache*waveform*playback_handle*lamprengobjectdupli_ofs[3]*propchildbaserollhead[3]tail[3]bone_mat[3][3]arm_head[3]arm_tail[3]arm_mat[4][4]arm_rollxwidthzwidthease1ease2rad_headrad_tailpad[1]bonebasechainbase*edbo*act_bone*act_edbone*sketchgevertdeformerlayer_usedlayer_protectedghostepghostsizeghosttypepathsizeghostsfghostefpathsfpathefpathbcpathac*pointsstart_frameend_frameghost_sfghost_efghost_bcghost_acghost_typeghost_stepghost_flagpath_typepath_steppath_viewflagpath_bakeflagpath_sfpath_efpath_bcpath_acikflagagrp_indexconstflagselectflagpad0[6]*bone*childiktreesiktree*custom*custom_txeul[3]chan_mat[4][4]pose_mat[4][4]pose_head[3]pose_tail[3]limitmin[3]limitmax[3]stiffness[3]ikstretchikrotweightiklinweight*tempchanbase*chanhashproxy_layerstride_offset[3]cyclic_offset[3]agroupsactive_groupiksolver*ikdata*ikparamproxy_act_bone[64]numiternumstepminstepmaxstepsolverfeedbackmaxveldampmaxdampepschannelscustomColcscurvesgroupsactive_markeridroot*source*filter_grpsearchstr[64]filterflagrenameIndexadstimeslide*grpname[30]ownspacetarspaceenforceheadtaillin_errorrot_error*tarmatrix[4][4]spacerotOrdertarnumtargetsiterationsrootbonemax_rootbone*poletarpolesubtarget[64]poleangleorientweightgrabtarget[3]numpointschainlenxzScaleModereserved1reserved2minmaxflagstuckcache[3]lockflagfollowflagvolmodeplaneorglengthbulgepivXpivYpivZaxXaxYaxZminLimit[6]maxLimit[6]extraFzinvmat[4][4]fromtomap[3]expofrom_min[3]from_max[3]to_min[3]to_max[3]rotAxiszminzmaxpad[9]track[64]object[64]*depth_obchannel[32]no_rot_axisstride_axiscurmodactstartactendactoffsstridelenblendoutstridechannel[32]offs_bone[32]hasinputhasoutputdatatypesockettypeis_copyexternal*new_sock*storagelimitstruct_typelocxlocy*default_valuestack_indexstack_typeown_indexto_index*groupsock*linkns*rectxsizeysize*new_nodelastyoutputsminiwidthupdatelabel[64]custom1custom2custom3custom4need_execexec*threaddatatotrbutrprvr*block*typeinfo*fromnode*tonode*fromsock*tosocknodeslinksinitcur_indexnodetype*execdata(*progress)()(*stats_draw)()(*test_break)()*tbh*prh*sdhvalue[3]value[4]cyclicmoviesamplesmaxspeedminspeedcurvedpercentxpercentybokehgammaimage_in_widthimage_in_heightcenter_xcenter_yspinwrapsigma_colorsigma_spacehuebase_path[1024]formatactive_inputuse_render_formatuse_node_formatt1t2t3fstrengthfalphakey[4]algorithmchannelx1x2y1y2fac_x1fac_x2fac_y1fac_y2colname[64]bktypepad_c1gamcono_zbuffstopmaxblurbthreshrotationpad_f1*dict*nodecolmodmixfadeangle_ofsmcjitprojfitslope[3]power[3]lift_lgg[3]gamma_inv[3]limchanunspilllimscaleuspillruspillguspillbtex_mappingcolor_mappingsun_direction[3]turbiditycolor_spaceprojectiongradient_typecoloringmusgrave_typewave_typeshortymintablemaxtableext_in[2]ext_out[2]*curve*table*premultablepresetchanged_timestampcurrcliprcm[4]black[3]white[3]bwmul[3]sample[3]x_resolutiondata_r[256]data_g[256]data_b[256]data_luma[256]sample_fullsample_linesaccuracywavefrm_modewavefrm_alphawavefrm_yfacwavefrm_heightvecscope_alphavecscope_heightminmax[3][2]hist*waveform_1*waveform_2*waveform_3*vecscopewaveform_totoffset[2]clonemtex*icon_imbuficon_filepath[1024]normal_weightob_modejittersmooth_stroke_radiussmooth_stroke_factorratergb[3]sculpt_planeplane_offsetsculpt_toolvertexpaint_toolimagepaint_toolpad3[5]autosmooth_factorcrease_pinch_factorplane_trimtexture_sample_biastexture_overlay_alphaadd_col[3]sub_col[3]active_rndactive_cloneactive_mask*layerstypemap[34]totlayermaxlayertotsize*pool*externalrot[4]ave[3]*groundwander[3]rest_lengthparticle_index[2]delete_flagnumparentpa[4]w[4]fuv[4]foffsetprev_state*hair*boiddietimenum_dmcachehair_indexalivespring_kplasticity_constantyield_ratioplasticity_balanceyield_balanceviscosity_omegaviscosity_betastiffness_kstiffness_knearrest_densitybuoyancyspring_frames*boids*fluiddistrphystypeavemodereacteventdrawdraw_asdraw_sizechildtyperen_assubframesdraw_colren_stephair_stepkeys_stepadapt_angleadapt_pixrotfromintegratorbb_alignbb_uv_splitbb_animbb_split_offsetbb_tiltbb_rand_tiltbb_offset[2]bb_size[2]bb_vel_headbb_vel_tailcolor_vec_maxsimplify_refsizesimplify_ratesimplify_transitionsimplify_viewporttimetweakcourant_targetjitfaceff_hairgrid_randps_offset[1]grid_reseffector_amounttime_flagtime_pad[3]partfactanfactanphasereactfacob_vel[3]avefacphasefacrandrotfacrandphasefacrandsizeacc[3]dragfacbrownfacrandlengthchild_nbrren_child_nbrparentschildsizechildrandsizechildradchildflatclumppowkink_flatkink_amp_clumprough1rough1_sizerough2rough2_sizerough2_thresrough_endrough_end_shapeclengthclength_thresparting_facparting_minparting_maxbranch_thresdraw_line[2]path_startpath_endtrail_countkeyed_loopsdupliweights*eff_group*dup_ob*bb_ob*pd2*part*particles**pathcache**childcachepathcachebufschildcachebufs*clmd*hair_in_dm*hair_out_dm*target_ob*latticetree_framebvhtree_framechild_seedtotunexisttotchildtotcachedtotchildcachetarget_psystotkeyedbakespacebb_uvname[3][64]vgroup[12]vg_negrt3*renderdata*effectors*fluid_springstot_fluidspringsalloc_fluidsprings*tree*pdd*franddt_frac_padCdisCvistructuralbendingmax_bendmax_structmax_shearavg_spring_lentimescaleeff_force_scaleeff_wind_scalesim_time_oldvelocity_smoothcollider_frictionvel_dampingstepsPerFrameprerollmaxspringlensolver_typevgroup_bendvgroup_massvgroup_structshapekey_restpresetsreset*collision_listepsilonself_frictionselfepsilonrepel_forcedistance_repelself_loop_countloop_countpressurethicknessstrokesframenum*actframegstepinfo[128]sbuffer_sizesbuffer_sflag*sbufferlistprintlevelstorelevel*reporttimer*windrawable*winactivewindowsinitializedfile_savedop_undo_depthoperatorsqueuereportsjobspaintcursorsdragskeyconfigs*defaultconf*addonconf*userconftimers*autosavetimer*ghostwingrabcursor*screen*newscreenscreenname[64]posxposywindowstatemonitorlastcursormodalcursoraddmousemove*eventstate*curswin*tweakdrawmethoddrawfail*drawdatamodalhandlerssubwindowsgestureidname[64]propvalue_str[64]propvalueshiftctrlaltoskeykeymodifiermaptype*ptr*remove_item*add_itemitemsdiff_itemsspaceidregionidkmi_id(*poll)()*modal_itemsbasename[64]actkeymap*customdata*py_instance*reportsmacro*opm*edata*coefficientsarraysizepoly_orderamplitudephase_multiplierphase_offsetvalue_offsetmidvalbefore_modeafter_modebefore_cyclesafter_cyclesrectphasemodificationstep_size*rna_pathpchan_name[32]transChanidtypetargets[8]num_targetsvariablesexpression[256]*expr_compvec[2]*fptarray_indexcolor_modecolor[3]from[128]to[128]mappingsstrips*remapfcurvesstrip_timeblendmodeextendmode*speaker_handlegroup[64]groupmodekeyingflagpathsdescription[240]typeinfo[64]active_path*tmpactnla_tracks*actstripdriversoverridesact_blendmodeact_extendmodeact_influenceruleoptionsfear_factorsignal_idlook_aheadoloc[3]queue_sizewanderflee_distancehealthstate_idrulesconditionsactionsruleset_typerule_fuzzinesslast_state_idlanding_smoothnessbankingaggressionair_min_speedair_max_speedair_max_accair_max_aveair_personal_spaceland_jump_speedland_max_speedland_max_accland_max_aveland_personal_spaceland_stick_forcestates*smd*fluid_group*coll_group*wt*tex_wt*tex_shadow*shadowp0[3]p1[3]dxomegatempAmbbetares[3]amplifymaxresviewsettingsnoisediss_percentdiss_speedres_wt[3]dx_wtv3dnumcache_compcache_high_comp*point_cache[2]ptcaches[2]border_collisionstime_scalevorticityvelocity[2]vel_multivgrp_heat_scale[2]vgroup_flowvgroup_densityvgroup_heat*points_old*velmat_old[4][4]volume_maxvolume_mindistance_maxdistance_referencecone_angle_outercone_angle_innercone_volume_outerrender_flagbuild_size_flagbuild_tc_flaglastsize[2]tracking*tracking_contextproxytrack_preview_height*track_previewtrack_pos[2]track_disabled*markerslide_scale[2]error*intrinsicssensor_widthpixel_aspectfocalunitsprincipal[2]k1k2k3pos[2]pat_min[2]pat_max[2]search_min[2]search_max[2]markersnrlast_marker*markersbundle_pos[3]pat_flagsearch_flagframes_limitpattern_matchtrackerpyramid_levelsminimum_correlationdefault_trackerdefault_pyramid_levelsdefault_minimum_correlationdefault_pattern_sizedefault_search_sizedefault_frames_limitdefault_margindefault_pattern_matchdefault_flagpodkeyframe1keyframe2refine_camera_intrinsicspad23clean_framesclean_actionclean_errorobject_distancetot_trackact_trackmaxscale*rot_tracklocinfscaleinfrotinf*scaleibuflast_cameracamnr*camerastracksreconstructionmessage[256]settingscamerastabilization*act_trackobjectsobjectnrtot_object*brush_groupcurrent_framedisp_typeimage_fileformateffect_uipreview_idinit_color_typepad_simage_resolutionsubstepsinit_color[4]*init_textureinit_layername[64]dry_speedcolor_dry_thresholddepth_clampdisp_factorspread_speedcolor_spread_speedshrink_speeddrip_veldrip_accinfluence_scaleradius_scalewave_dampingwave_speedwave_timescalewave_springimage_output_path[1024]output_name[64]output_name2[64]*pmdsurfacesactive_surerror[64]collisionwetnessparticle_radiusparticle_smoothpaint_distance*paint_ramp*vel_rampproximity_falloffray_dirwave_factorwave_clampmax_velocitysmudge_strengthTYPEcharucharshortushortintlongulongfloatdoubleint64_tuint64_tvoidLinkLinkDataListBasevec2svec2fvec3frctirctfIDPropertyDataIDPropertyIDLibraryFileDataPreviewImageIpoDriverObjectIpoCurveBPointBezTripleIpoKeyBlockKeyAnimDataTextLineTextMarkerTextPackedFileCameraImageUserSceneImageGPUTextureanimRenderResultMTexTexPluginTexCBDataColorBandEnvMapImBufPointDensityCurveMappingVoxelDataOceanTexbNodeTreeTexMappingColorMappingLampVolumeSettingsGameSettingsMaterialGroupVFontVFontDataMetaElemBoundBoxMetaBallNurbCharInfoTextBoxEditNurbGHashCurvePathSelBoxEditFontMeshMSelectMPolyMTexPolyMLoopMLoopUVMLoopColMFaceMTFaceTFaceMVertMEdgeMDeformVertMColMStickyBMEditMeshCustomDataMultiresMDeformWeightMFloatPropertyMIntPropertyMStringPropertyOrigSpaceFaceOrigSpaceLoopMDispsMultiresColMultiresColFaceMultiresFaceMultiresEdgeMultiresLevelMRecastModifierDataMappingInfoModifierDataSubsurfModifierDataLatticeModifierDataCurveModifierDataBuildModifierDataMaskModifierDataArrayModifierDataMirrorModifierDataEdgeSplitModifierDataBevelModifierDataBMeshModifierDataSmokeModifierDataSmokeDomainSettingsSmokeFlowSettingsSmokeCollSettingsDisplaceModifierDataUVProjectModifierDataDecimateModifierDataSmoothModifierDataCastModifierDataWaveModifierDataArmatureModifierDataHookModifierDataSoftbodyModifierDataClothModifierDataClothClothSimSettingsClothCollSettingsPointCacheCollisionModifierDataBVHTreeSurfaceModifierDataDerivedMeshBVHTreeFromMeshBooleanModifierDataMDefInfluenceMDefCellMeshDeformModifierDataParticleSystemModifierDataParticleSystemParticleInstanceModifierDataExplodeModifierDataMultiresModifierDataFluidsimModifierDataFluidsimSettingsShrinkwrapModifierDataSimpleDeformModifierDataShapeKeyModifierDataSolidifyModifierDataScrewModifierDataOceanModifierDataOceanOceanCacheWarpModifierDataWeightVGEditModifierDataWeightVGMixModifierDataWeightVGProximityModifierDataDynamicPaintModifierDataDynamicPaintCanvasSettingsDynamicPaintBrushSettingsRemeshModifierDataEditLattLatticebDeformGroupSculptSessionbActionbPosebGPdatabAnimVizSettingsbMotionPathBulletSoftBodyPartDeflectSoftBodyObHookDupliObjectRNGEffectorWeightsPTCacheExtraPTCacheMemPTCacheEditSBVertexBodyPointBodySpringSBScratchFluidVertexVelocityWorldBaseAviCodecDataQuicktimeCodecDataQuicktimeCodecSettingsFFMpegCodecDataAudioDataSceneRenderLayerImageFormatDataRenderDataRenderProfileGameDomeGameFramingRecastDataGameDataTimeMarkerPaintBrushImagePaintSettingsParticleBrushDataParticleEditSettingsSculptUvSculptVPaintTransformOrientationUnifiedPaintSettingsToolSettingsbStatsUnitSettingsPhysicsSettingsEditingSceneStatsDagForestMovieClipBGpicMovieClipUserRegionView3DRenderInfoRenderEngineViewDepthsSmoothViewStorewmTimerView3DSpaceLinkView2DSpaceInfoSpaceIpobDopeSheetSpaceButsSpaceSeqFileSelectParamsSpaceFileFileListwmOperatorFileLayoutSpaceOopsTreeStoreTreeStoreElemSpaceImageScopesHistogramSpaceNlaSpaceTextScriptSpaceScriptSpaceTimeCacheSpaceTimeSpaceNodeSpaceLogicConsoleLineSpaceConsoleSpaceUserPrefSpaceClipMovieClipScopesuiFontuiFontStyleuiStyleuiWidgetColorsuiWidgetStateColorsuiPanelColorsThemeUIThemeSpaceThemeWireColorbThemebAddonSolidLightUserDefbScreenScrVertScrEdgePanelPanelTypeuiLayoutScrAreaSpaceTypeARegionARegionTypeFileGlobalStripElemStripCropStripTransformStripColorBalanceStripProxyStripPluginSeqSequencebSoundMetaStackWipeVarsGlowVarsTransformVarsSolidColorVarsSpeedControlVarsEffectBuildEffPartEffParticleWaveEffbPropertybNearSensorbMouseSensorbTouchSensorbKeyboardSensorbPropertySensorbActuatorSensorbDelaySensorbCollisionSensorbRadarSensorbRandomSensorbRaySensorbArmatureSensorbMessageSensorbSensorbControllerbJoystickSensorbExpressionContbPythonContbActuatorbAddObjectActuatorbActionActuatorSound3DbSoundActuatorbEditObjectActuatorbSceneActuatorbPropertyActuatorbObjectActuatorbIpoActuatorbCameraActuatorbConstraintActuatorbGroupActuatorbRandomActuatorbMessageActuatorbGameActuatorbVisibilityActuatorbTwoDFilterActuatorbParentActuatorbStateActuatorbArmatureActuatorbSteeringActuatorGroupObjectBonebArmaturebMotionPathVertbPoseChannelbIKParambItascbActionGroupSpaceActionbActionChannelbConstraintChannelbConstraintbConstraintTargetbPythonConstraintbKinematicConstraintbSplineIKConstraintbTrackToConstraintbRotateLikeConstraintbLocateLikeConstraintbSizeLikeConstraintbSameVolumeConstraintbTransLikeConstraintbMinMaxConstraintbActionConstraintbLockTrackConstraintbDampTrackConstraintbFollowPathConstraintbStretchToConstraintbRigidBodyJointConstraintbClampToConstraintbChildOfConstraintbTransformConstraintbPivotConstraintbLocLimitConstraintbRotLimitConstraintbSizeLimitConstraintbDistLimitConstraintbShrinkwrapConstraintbFollowTrackConstraintbCameraSolverConstraintbObjectSolverConstraintbActionModifierbActionStripbNodeStackbNodeSocketbNodeLinkbNodePreviewbNodeuiBlockbNodeTypebNodeTreeExecbNodeSocketValueIntbNodeSocketValueFloatbNodeSocketValueBooleanbNodeSocketValueVectorbNodeSocketValueRGBANodeImageAnimNodeBlurDataNodeDBlurDataNodeBilateralBlurDataNodeHueSatNodeImageFileNodeImageMultiFileNodeImageMultiFileSocketNodeChromaNodeTwoXYsNodeTwoFloatsNodeGeometryNodeVertexColNodeDefocusNodeScriptDictNodeGlareNodeTonemapNodeLensDistNodeColorBalanceNodeColorspillNodeTexBaseNodeTexSkyNodeTexImageNodeTexCheckerNodeTexEnvironmentNodeTexGradientNodeTexNoiseNodeTexVoronoiNodeTexMusgraveNodeTexWaveNodeTexMagicNodeShaderAttributeTexNodeOutputCurveMapPointCurveMapBrushCloneCustomDataLayerCustomDataExternalHairKeyParticleKeyBoidParticleBoidDataParticleSpringChildParticleParticleTargetParticleDupliWeightParticleDataSPHFluidSettingsParticleSettingsBoidSettingsParticleCacheKeyKDTreeParticleDrawDataLinkNodebGPDspointbGPDstrokebGPDframebGPDlayerReportListwmWindowManagerwmWindowwmKeyConfigwmEventwmSubWindowwmGesturewmKeyMapItemPointerRNAwmKeyMapDiffItemwmKeyMapwmOperatorTypeFModifierFMod_GeneratorFMod_FunctionGeneratorFCM_EnvelopeDataFMod_EnvelopeFMod_CyclesFMod_PythonFMod_LimitsFMod_NoiseFMod_SteppedDriverTargetDriverVarChannelDriverFPointFCurveAnimMapPairAnimMapperNlaStripNlaTrackKS_PathKeyingSetAnimOverrideIdAdtTemplateBoidRuleBoidRuleGoalAvoidBoidRuleAvoidCollisionBoidRuleFollowLeaderBoidRuleAverageSpeedBoidRuleFightBoidStateFLUID_3DWTURBULENCESpeakerMovieClipProxyMovieClipCacheMovieTrackingMovieTrackingTrackMovieTrackingMarkerMovieReconstructedCameraMovieTrackingCameraMovieTrackingSettingsMovieTrackingStabilizationMovieTrackingReconstructionMovieTrackingObjectMovieTrackingStatsDynamicPaintSurfacePaintSurfaceDataTLEN  x(pp$8`(0( 08h@8P8XhhP   0@  @ @pxx8(x@Xp0(pxp0hhhp`X0xH  (@H x8 @8`X0`8x Php`8@ph(!@0H(h(8 P$P8p& (X(0,HP   @`PPHLH\Pp Hxp XX0(H (xHx8xpXPPPHhhPPXhXXH08X( ( ,@  `@@ 8h88@( <h ((X x8(h(hp 8P@hH@H0@8STRC                  !"#$%&'()*+,-./0123-.45678  9:;<= >?-@A  B<CDEF GHIJ! "K LMNOPQRSTU###VWX$ $$YZ[C \]^% _\`a#b#cdefghi jk&lm ' "KnopqrstuvwxyPz{|()}~*! #+,--&.A/|80#   1B?2%?13*4pq5      267 8/@ "K !"#$%&'()*+,-./0123456789:;<=>?@ABCDC(E9FP*0G23H5I7J8KL|: MNOPQ,; 2R!"#STUV<J "KWXYZ[\]^_`abc6dpqefghijklmnopqrstuvwxyCz{|}~P.L9F=>C?~ "K=>WzL22.9FP@     A #B&CCCDE "KDP? !MN"#$C% &'(6)*+,-./'(+.FFF0.123456789:01;<=G>0H?@IAJBC^KD "KDDIEFGHPLI!J?KMNLMNOP!QRST34UVW XaBYZ[\]^_`abcdMeNfgAhAiAjAklTmnHopqGrGsO/ "KDP!J?PtQuRvSwTxUyVzW{X|Y}Z~[\]O^_____5MNL!`X WV0Za([aY\Q0SR*WTU]PW*Wbcdefghihj0kl lljik5Y`Y__mn nnW)}on/pn\  qn^rnsnZtnHWu n\vnwn\x n\ynzn{|}~ n/ n*\nnnln/RCn   n   nn)} nYYYYYYV !nYY"#!$n%&('n(W)*+5,-./01'23456 7n8#9:;8n,8<=n>?@nABCD\nE nFGHIJKLM nNHOPWQn nRSTUVWX nYZ[\]^n_`abcdefghijklmnopqrlsn/tu6dvnwx6yz{|}~/n|}~/n|}~/nnWC^ "K12P!J[H|D "K&PD W?!MN TnI@C       & O  ,O( !"#$%&'()*+,-./0123456/78@9:;<  =>? @ ABCDEFGHI>J8KLMNOP Q/RSTUVWXYZ[\]^_`abcdefghijklm6>nopqrstuvwxyz{|}~R4OOP5N "KXzW  P.L9F        !"#$%&'()*+,-C ./012345\6789: ;<=>?T ?@@ABCDE FGHIJKLMNO|jPQRST$UVWXYZ[\]^_`abcdefgmlhijklGFmHnoWpqCrstuvwxyz{|}~% CW^%CC abcdfWC= \C lC 8)}\%[ lUC      !"#$%&'()*+,-./0123456789:;<C=>?@ABCDEF5GHIJK8). "KL)MNOPQRSTUVL9FWXYZf[ \ ] ^ _ `abcdefghi  j*(Eikabllm'nopqrstuvDwxyz{|}~^m3^mxU rQ %?|W I abm*(E6ab %\            \!"# $\C %& '()QC*ab+,-9F9./01V2    3V4  56789: ; ab<=>i Wm?@^ABCDEFGH GIJKLMNOPQRSTUVWXYZ[\]^_ `abcdefghijklmnolpqrstuvwxyz{|}~zdp     /_ !"#$%&'()*+,-./01523456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg2hijklmn opq)})rsCtuvwxyz{|}~ y D !!!3x4"###]w|V$ %n)E&' ()*,H+ ++&*'(), #     -2--  Z+P)},i,G---. T ///- -0^12       34  \ 555 8666 87-77 ~  [ R      D              ! " # $ 8% @999 R& ' ( )E) ::: * ;^+ , <C=?- ^>. / 0 1 2 ?3 4 @A5 6 B7 8 WC^D5 E9 : WC; F< = G> ? @ HHHA B C D C IE ,F J G H I J K L M KN L%WIIIIO P D A Q  ME HR S T N,OU  [V W X Y Z [ \ P] ^ _ ` a b c d Q e CT.Pf sg R ,Oh i j k l SC)}T3 ,U A m n Vo p q r V  [V CsW,s t CX Wu v w x y z Y  [V?{ | Z} ~    9 [  ?  C@ \ [ ]^^  %_,`a< = ( F b 5 ^    F M MMMO A   ,. #& & PT ] ^  \ ccc, @   ddd d&         ^(       e "K   d              ff                  g(gg   d g&g    g M            J  CT      h i  M          jjj       f    @    k  W lllj PummmP nnn    P  o oo    p%    q       (  W^r   ! " s # $ \ t # u # v # wTx y  % & C' z  Z[s t U { ( |  } R) ~ * + , -   . / 0 1 2 3 4 5 6 C   7   8 9 : ; < = > ?  @    A B     A B     A B   ^gWF^JLC iD E F iiE 7  G   H ,WI J PU Z[K L M N W O P Q  /s t R S T U V W X  Y Z [ \ ] ^ _ `  a b c d e f g h %i j _ ' P k &) Y \ ] Rl m n o p q r s t u v w x y z { | } ~ 9 "K    m  P k   s t s t V s t  '    D*   m       ^ \   \ #P         ]                         H\^ l     .                 : ; N  N NN  N NN N N N  >       6 ?                  W                * "  6 . 4   l l Z                     F E_ ! " # $  % & (' ( ) J* 8+ , - . / 0 1 2 3 8,W6 ,84 5 % 6 R7 . 8 2 3 l9 : ; + < = > ? @ A B C D E F  "KG H 88 I zJ K L M N O P Q R S T U V W X   Y Z [ \ ] ^ _ ` a b c d e f g h i  [ j k l m n o   p q r s    t u v w x y z { | l} ~            423                    .@ @   PI 4   P Q      & $                   !    & {  J \      |   }yz    \                \  @         ]  {                            v       4                  ! " # $  % & '  ( )  * + , - .  / :0   1 2 % 3 4 5 V   6  W O 7 8 9 W: ; < = s t +,5> s t ? @ A B %$ C lD E F   )G H I J K L <M N O < P j =1Q 5<R G S T U V FW X U Y Z  [ Z[K L W O \ ] C ^ X  )_ J ` G R a  b  c d a e G R " f Y g h i j k l m  "Kn ,o p q  n o r n ,Ms $ o t n u 8n  v w ~ x Wy z {  | } q  To ~   w               {(z H @ @ @  ++ +      u     \             | z 8u      \} z !#   O   "K.        To ~ H   "K# ,      4   ~  ~ O     C     ~            T                    ^             4                    Y0@         \     /             ! " # $ % & ' ( ) #* + \, ) #8?\- . / 0 1 22 23 4  5 6 7 8 9 ENDBmobf_core-2.5.1/mobf/mov_gen_none.lua000066400000000000000000000061441264104133000175500ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file mov_gen_none.lua --! @brief a dummy movement gen --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mgen_none")) --! @class mgen_none --! @brief a movement generator doing nothing mgen_none = {} --!@} --! @brief movement generator identifier --! @memberof mgen_none mgen_none.name = "none" ------------------------------------------------------------------------------- -- name: callback(entity,now) -- --! @brief main callback to do nothing --! @memberof mgen_none -- --! @param entity mob to generate movement for --! @param now current time ------------------------------------------------------------------------------- function mgen_none.callback(entity,now) mobf_assert_backtrace(entity ~= nil) local pos = entity.getbasepos(entity) local current_state = environment.pos_is_ok(pos,entity) if current_state == "in_water" or current_state == "in_air" or current_state == "above_water" then spawning.remove(entity, "mgen none envcheck") return end local speed = entity.object:getvelocity() local default_y_acceleration = environment.get_default_gravity(pos, entity.environment.media, entity.data.movement.canfly) entity.object:setacceleration({x=0,y=default_y_acceleration,z=0}) if default_y_acceleration ~= 0 then entity.object:setvelocity({x=0,y=speed.y,z=0}) else entity.object:setvelocity({x=0,y=0,z=0}) end end ------------------------------------------------------------------------------- -- name: initialize() -- --! @brief initialize movement generator --! @memberof mgen_none --! @public ------------------------------------------------------------------------------- function mgen_none.initialize(entity,now) end ------------------------------------------------------------------------------- -- name: init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by movement generator --! @memberof mgen_none --! @public -- --! @param entity mob to initialize dynamic data --! @param now current time ------------------------------------------------------------------------------- function mgen_none.init_dynamic_data(entity,now) local data = { moving = false, } entity.dynamic_data.movement = data end ------------------------------------------------------------------------------- -- name: set_target(entity,target) -- --! @brief set target for movgen --! @memberof mgen_none --! @public -- --! @param entity mob to apply to --! @param target to set ------------------------------------------------------------------------------- function mgen_none.set_target(entity,target) return false end --register this movement generator registerMovementGen(mgen_none.name,mgen_none)mobf_core-2.5.1/mobf/movement_gen_registry.lua000066400000000000000000000042001264104133000215010ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file movement_gen_registry.lua --! @brief registry for movement generators --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("movement_generators")) movement_generators = {} ------------------------------------------------------------------------------- -- name: getMovementGen(id) -- --! @brief get movement generator specified by id --! @ingroup framework_int -- --! @param id id of movementgenerator --! @return module pointer for movementgenerator ------------------------------------------------------------------------------- function getMovementGen(id) if movement_generators[id] == nil then local name = "" if id ~= nil then name = id end minetest.log(LOGLEVEL_ERROR,"MOBF: movement generator " .. dump(id) .. " not found!") return nil end return movement_generators[id] end ------------------------------------------------------------------------------- -- name: registerMovementGen(name,generator) -- --! @brief register a movement generator to mob framework --! @ingroup framework_mob -- --! @param name id to use for movement generator --! @param generator pointer to movement generator --! @return true/false successfully added generator ------------------------------------------------------------------------------- function registerMovementGen(name,generator) --some movement gen checks if generator.init_dynamic_data == nil then return false end if generator.callback == nil then return false end if movement_generators[name] == nil then movement_generators[name] = generator minetest.log(LOGLEVEL_NOTICE,"\tRegistering movement generator ".. name) if generator.initilize ~= nil then generator.initialize() end return true else return false end endmobf_core-2.5.1/mobf/movement_generic.lua000066400000000000000000000161271264104133000204270ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file movement_generic.lua --! @brief generic movement related functions --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup generic_movement Generic movement functions --! @brief Movement related functions used by different movement generators --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("movement_generic")) movement_generic = {} --!@} ------------------------------------------------------------------------------- -- @function [parent=#movement_generic] get_accel_to(new_pos,entity,ymovement,accel) -- --! @brief calculate a random speed directed to new_pos -- --! @param new_pos position to go to --! @param entity mob to move --! @param ymovement current movement in y direction --! @return { x,y,z } random speed directed to new_pos ------------------------------------------------------------------------------- -- function movement_generic.get_accel_to(new_pos, entity, ymovement, accel) if new_pos == nil or entity == nil then minetest.log(LOGLEVEL_CRITICAL, "MOBF: movement_generic.get_accel_to : Invalid parameters") end local old_pos = entity.object:getpos() local node = minetest.get_node(old_pos) local maxaccel = entity.data.movement.max_accel local minaccel = entity.data.movement.min_accel local yaccel = environment.get_default_gravity(old_pos, entity.environment.media, entity.data.movement.canfly) mobf_assert_backtrace(yaccel ~= nil) --calculate plane direction to target local xz_direction = mobf_calc_yaw(new_pos.x-old_pos.x,new_pos.z-old_pos.z) local absolute_accel = nil --find a new speed if not accel then absolute_accel = minaccel + (maxaccel - minaccel) * math.random() else absolute_accel = accel end local new_accel_vector = nil --flying mob calculate accel towards target if entity.data.movement.canfly and yaccel == 0 then local xz_direction,xy_direction = mobf_calc_direction(old_pos,new_pos) new_accel_vector = mobf_calc_vector_components_3d(xz_direction, xy_direction, absolute_accel) if (new_pos.y > old_pos.y) then mobf_assert_backtrace(new_accel_vector.y >= 0) end if (new_pos.y < old_pos.y) then mobf_assert_backtrace(new_accel_vector.y <= 0) end else new_accel_vector = mobf_calc_vector_components(xz_direction,absolute_accel) new_accel_vector.y = yaccel end return new_accel_vector end ------------------------------------------------------------------------------- -- @function [parent=#movement_generic] calc_new_pos(pos,acceleration,prediction_time) -- --! @brief calc the position a mob would be after a specified time -- this doesn't handle velocity changes due to colisions -- --! @param pos position --! @param acceleration acceleration to predict pos --! @param prediction_time time to predict pos --! @param current_velocity current velocity of mob --! @return { x,y,z } position after specified time ------------------------------------------------------------------------------- function movement_generic.calc_new_pos(pos,acceleration,prediction_time,current_velocity) local predicted_pos = {x=pos.x,y=pos.y,z=pos.z} predicted_pos.x = predicted_pos.x + current_velocity.x * prediction_time + (acceleration.x/2)*math.pow(prediction_time,2) predicted_pos.z = predicted_pos.z + current_velocity.z * prediction_time + (acceleration.z/2)*math.pow(prediction_time,2) return predicted_pos end ------------------------------------------------------------------------------- -- @function [parent=#movement_generic] predict_next_block(pos,velocity,acceleration) -- --! @brief predict next block based on pos velocity and acceleration -- --! @param pos current position --! @param velocity current velocity --! @param acceleration current acceleration --! @return { x,y,z } position of next block ------------------------------------------------------------------------------- function movement_generic.predict_next_block(pos,velocity,acceleration) local prediction_time = 2 local pos_predicted = movement_generic.calc_new_pos(pos, acceleration, prediction_time, velocity ) local count = 1 --check if after prediction time we would have traveled more than one block and adjust to not predict to far while mobf_calc_distance(pos,pos_predicted) > 1 do pos_predicted = movement_generic.calc_new_pos(pos, acceleration, prediction_time - (count*0.1), velocity ) if (prediction_time - (count*0.1)) < 0 then minetest.log(LOGLEVEL_WARNING,"MOBF: Bug!!!! didn't find a suitable prediction time. Mob will move more than one block within prediction period") break end count = count +1 end return pos_predicted end ------------------------------------------------------------------------------- -- @function [parent=#movement_generic] predict_enter_next_block(entity,pos,velocity,acceleration) -- --! @brief predict next block based on pos velocity and acceleration -- --! @param entity entitiy to do prediction for --! @param pos current position --! @param velocity current velocity --! @param acceleration current acceleration --! @return { x,y,z } position of next block ------------------------------------------------------------------------------- function movement_generic.predict_enter_next_block(entity,pos,velocity,acceleration) local cornerpositions = {} table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[4] -0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[6] -0.01}) table.insert(cornerpositions,{x=pos.x + entity.collisionbox[1] +0.01,y=pos.y,z=pos.z + entity.collisionbox[3] +0.01}) local sameblock = function(a,b) for i=1,#a,1 do if not mobf_pos_is_same( mobf_round_pos(a[i]), mobf_round_pos(b[i])) then return false end end return true end local prediction_time = 0.1 local predicted_corners = {} for i=1,#cornerpositions,1 do predicted_corners[i] = movement_generic.calc_new_pos(cornerpositions[i], acceleration, prediction_time, velocity ) end --check if any of the corners is in different block after prediction time while sameblock(cornerpositions,predicted_corners) and prediction_time < 2 do prediction_time = prediction_time + 0.1 for i=1,#cornerpositions,1 do predicted_corners[i] = movement_generic.calc_new_pos(cornerpositions[i], acceleration, prediction_time, velocity ) end end local pos_predicted = movement_generic.calc_new_pos(pos, acceleration, prediction_time, velocity ) return pos_predicted endmobf_core-2.5.1/mobf/path.lua000066400000000000000000000501061264104133000160300ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file path.lua --! @brief path support for mobf --! @copyright Sapier --! @author Sapier --! @date 2013-02-09 -- -- Contact sapier a t gmx net -- Boilerplate to support localized strings if intllib mod is installed. local S if (minetest.get_modpath("intllib")) then dofile(minetest.get_modpath("intllib").."/intllib.lua") S = intllib.Getter(minetest.get_current_modname()) else S = function ( s ) return s end end ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mobf_path")) mobf_path = {} ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] find_path() -- --! @brief workaround for broken mintest find_path function --! @ingroup mobf_path ------------------------------------------------------------------------------- function mobf_path.find_path(pos1,pos2,searchdistance,max_jump,max_drop,algorithm) local interim_path = minetest.find_path(pos1, pos2, searchdistance, max_jump, max_drop, algorithm) if interim_path == nil then return nil end local pos_n_minor_1 = nil local pos_n_minor_2 = nil local path_optimized = {} table.insert(path_optimized, interim_path[1]) for i,v in ipairs(interim_path) do if ( pos_n_minor_1 ~= nil ) and ( pos_n_minor_2 ~= nil) then local x_pitch = pos_n_minor_2.x - v.x local y_pitch = pos_n_minor_2.y - v.y local z_pitch = pos_n_minor_2.z - v.z local x_delta = (pos_n_minor_1.x - pos_n_minor_2.x) / x_pitch; local y_delta = (pos_n_minor_1.y - pos_n_minor_2.y) / y_pitch; local z_delta = (pos_n_minor_1.z - pos_n_minor_2.z) / z_pitch; if (x_pitch ~= 0) and (y_pitch ~= 0) and (x_delta ~= y_delta ) then table.insert(path_optimized, pos_n_minor_1) elseif (y_pitch ~= 0) and (z_pitch ~= 0) and (y_delta ~= z_delta) then table.insert(path_optimized, pos_n_minor_1) elseif (x_pitch ~= 0) and (z_pitch ~= 0) and (y_delta ~= z_delta) then table.insert(path_optimized, pos_n_minor_1) end end pos_n_minor_2 = pos_n_minor_1 pos_n_minor_1 = v end table.insert(path_optimized, interim_path[#interim_path]) return path_optimized end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] init() -- --! @brief initialize path subsystem --! @ingroup mobf_path ------------------------------------------------------------------------------- function mobf_path.init() mobf_path.load() if mobf_rtd.path_data == nil then mobf_rtd.path_data = {} end if mobf_rtd.path_data.users == nil then mobf_rtd.path_data.users = {} end --register path marker entity minetest.register_entity("mobf:path_marker_entity", { physical = false, collisionbox = {-0.5,-0.5,-0.5,0.5,1.5,0.5 }, visual = "mesh", textures = { "mobf_path_marker.png" }, mesh = "mobf_path_marker.b3d", initial_sprite_basepos = {x=0, y=0}, automatic_rotate = 2, on_step = function(self,dtime) if self.creationtime == nil then self.creationtime = 0 end self.creationtime = self.creationtime + dtime if self.creationtime > 30 then self.object:remove() end end }) minetest.register_craftitem(":mobf:path_marker", { description = S("Path marker tool"), image = "mobf_path_marker_item.png", on_place = function(item, placer, pointed_thing) if pointed_thing.type == "node" then local pos = pointed_thing.above local entity = spawning.spawn_and_check("mobf:path_marker_entity", pos,"path_marker_click") if entity ~= nil then mobf_path.handle_path_marker_place(placer,pos) end return item end end }) minetest.register_on_player_receive_fields(mobf_path.button_handler) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] save() -- --! @brief save all path data --! @ingroup mobf_path ------------------------------------------------------------------------------- function mobf_path.save() mobf_set_world_setting("mobf_path_data",minetest.serialize(mobf_rtd.path_data)) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] load() -- --! @brief save all path data --! @ingroup mobf_path ------------------------------------------------------------------------------- function mobf_path.load() local paths_raw = mobf_get_world_setting("mobf_path_data") if paths_raw ~= nil then mobf_rtd.path_data = minetest.deserialize(mobf_get_world_setting("mobf_path_data")) end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] handle_path_marker_place(placer,pos) -- --! @brief initialize path subsystem --! @ingroup mobf_path -- --! @param placer player object placing the path marker --! @param pos position placed at ------------------------------------------------------------------------------- function mobf_path.handle_path_marker_place(placer,pos) mobf_assert_backtrace(placer ~= nil) mobf_assert_backtrace(pos ~= nil) if placer:is_player() then local playername = placer:get_player_name() local player_paths = mobf_path.get_editable_path_names(playername) mobf_path.show_add_point_menu(playername,player_paths,pos) end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] get_editable_path_names(playername) -- --! @brief get list of pathnames for a player --! @ingroup mobf_path -- --! @param playername name of player to get paths for -- --! @return list of names ------------------------------------------------------------------------------- function mobf_path.get_editable_path_names(playername) if mobf_rtd.path_data.users[playername] == nil then return nil end if mobf_rtd.path_data.users[playername].paths == nil then return nil end local retval = {} for k,v in pairs(mobf_rtd.path_data.users[playername].paths) do if not v.locked then table.insert(retval,k) end end if #retval > 0 then return retval end return nil end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] add_point(playername,pathname,point) -- --! @brief add a point to a path --! @ingroup mobf_path -- --! @param playername name of player to store point --! @param pathname name of path to add point to --! @param point point to add ------------------------------------------------------------------------------- function mobf_path.add_point(playername,pathname,point) if mobf_rtd.path_data.users[playername] == nil then mobf_rtd.path_data.users[playername] = {} end if mobf_rtd.path_data.users[playername].paths == nil then mobf_rtd.path_data.users[playername].paths = {} end if mobf_rtd.path_data.users[playername].paths[pathname] == nil then mobf_rtd.path_data.users[playername].paths[pathname] = {} mobf_rtd.path_data.users[playername].paths[pathname].locked = false mobf_rtd.path_data.users[playername].paths[pathname].points = {} end table.insert(mobf_rtd.path_data.users[playername].paths[pathname].points,point) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] show_add_point_menu(pathnames) -- --! @brief show a menu containing all paths a point may be added to --! @ingroup mobf_path -- --! @param playername player to show menu --! @param pathnames names of paths --! @param point point to add ------------------------------------------------------------------------------- function mobf_path.show_add_point_menu(playername,pathnames,point) local buttons = "" local y_pos = 0.25 local storage_id = mobf_global_data_store(point) if pathnames ~= nil then for i = 1, #pathnames, 1 do buttons = buttons .. "button_exit[0," .. y_pos .. ";4.5,0.5;" .. "mobfpath:existing:" .. storage_id .. ":" .. pathnames[i] .. ";" .. pathnames[i] .. "]" y_pos = y_pos + 0.75 end end local y_size = y_pos + 3 * 0.75 - 0.25 --add new path element local formspec = "size[4.5," .. y_size .. "]" .. buttons .. "label[0," .. y_pos .. ";-----------------------------]" .. "field[0.25," .. (y_pos + 1) .. ";4.5,0.5;new_path_name;;]" .. "button_exit[1.5," .. (y_pos + 1.5) .. ";1.5,0.5;mobfpath:addnew:" .. storage_id .. ";new path]" --show formspec minetest.show_formspec(playername,"mobf:path:path_name_menu",formspec) end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] button_handler(player, formname, fields) -- --! @brief handle button click in mobf_path menu --! @ingroup mobf_path -- --! @param player player issuing click --! @param formname name of form beeing clicked --! @param fields data submitted to form -- --! @return true/false event has been handled by this handler ------------------------------------------------------------------------------- function mobf_path.button_handler(player, formname, fields) local playername = player:get_player_name() mobf_assert_backtrace(playername ~= nil) if formname == "mobf:path:path_name_menu" then dbg_mobf.path_lvl2("MOBF: Path marker rightclick path selected") for k,v in pairs(fields) do local parts = string.split(k,":") if parts[1] == "mobfpath" then local point = mobf_global_data_get(parts[3]) local pathname = parts[4] if parts[2] == "addnew" then pathname = fields.new_path_name end if point ~= nil and pathname ~= nil and pathname ~= "" then mobf_path.add_point(playername,pathname,point) mobf_path.save() end end end return true end if formname == "mobf:path:add_path_to_entity" then dbg_mobf.path_lvl2("MOBF: Adding path to an entity") for k,v in pairs(fields) do local parts = string.split(k,":") if parts[1] == "mobfpath" then local entity = mobf_global_data_get(parts[2]) local pathname = parts[3] if entity ~= nil and pathname ~= nil and mobf_rtd.path_data.users[playername].paths[pathname] ~= nil and entity.data.patrol ~= nil then --switch to guard state mobf_path.switch_patrol(entity,playername,pathname) end end end return true end --not handled by this callback return false end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] delete_path(ownername,pathname) -- --! @brief show path markers --! @ingroup mobf_path -- --! @param ownername name of path owner --! @param pathname name of path -- ------------------------------------------------------------------------------- function mobf_path.delete_path(ownername, pathname) dbg_mobf.path_lvl1("MOBF: delete path issued: " .. pathname .. " owner: " .. ownername) mobf_rtd.path_data.users[ownername].paths[pathname] = nil mobf_path.save() end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] show_pathmarkers(ownername,pathname) -- --! @brief show path markers --! @ingroup mobf_path -- --! @param ownername name of path owner --! @param pathname name of path -- ------------------------------------------------------------------------------- function mobf_path.show_pathmarkers(ownername,pathname) for i,v in ipairs(mobf_rtd.path_data.users[ownername].paths[pathname].points) do local objects = minetest.get_objects_inside_radius(v,0.5) dbg_mobf.path_lvl3("MOBF: got " .. #objects .. " around pos checking for marker") local found = false; for i=1,#objects,1 do local luaentity = objects[i]:get_luaentity() dbg_mobf.path_lvl3("MOBF: checking: " .. dump(luaentity)) if luaentity.name == "mobf:path_marker_entity" then found = true break end end local node_at = minetest.get_node(v) if not found and node_at.name ~= nil and node_at.name ~= "ignore" then spawning.spawn_and_check("mobf:path_marker_entity",v,"mark_path") end end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] get_pathlist(playername,isadmin) -- --! @brief get a list of paths for a specific player --! @ingroup mobf_path -- --! @param playername name of player to get paths --! @param isadmin does this player have admin rights? -- --! @return list of paths ------------------------------------------------------------------------------- function mobf_path.get_pathlist(playername,isadmin) local retval = {} if isadmin then for local_playername,userdata in pairs(mobf_rtd.path_data.users) do for pathname,path in pairs(userdata.paths) do dbg_mobf.path_lvl3("MOBF: Adding path: " .. pathname .. " data:" .. dump(path)) local toadd = { ownername = local_playername, pathname = pathname } dbg_mobf.path_lvl3("MOBF: Adding path entry: " .. dump(toadd)) table.insert(retval,toadd) end end else if playername ~= nil and mobf_rtd.path_data.users[playername] ~= nil then for pathname,path in pairs(mobf_rtd.path_data.users[playername].paths) do local toadd = { ownername = playername, pathname = pathname } table.insert(retval,toadd) end end end return retval end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] make_button_name(buttonid,data) -- --! @brief create a button name --! @ingroup mobf_path -- --! @param buttonid id to use for this button --! @param data information to add to this button -- --! @return string containing data ------------------------------------------------------------------------------- function mobf_path.make_button_name(buttonid,data) local retval = buttonid .. ":" if data.pathname ~= nil then retval = retval .. data.pathname .. ":" else retval = retval .. ":" end if data.ownername ~= nil then retval = retval .. data.ownername .. ":" else retval = retval .. ":" end return retval end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] parse_button_name(datastring) -- --! @brief get data from button name --! @ingroup mobf_path -- --! @param datastring name to parse -- --! @return parsed data ------------------------------------------------------------------------------- function mobf_path.parse_button_name(datastring) mobf_assert_backtrace(datastring ~= nil) local data = {} local parts = string.split(datastring,":") data.buttonid = parts[1] data.pathname = parts[2] data.ownername = parts[3] if data.pathname == "" then data.pathname = nil data.ownername = nil end return data end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] mob_rightclick_callback(entity,player) -- --! @brief do rightclick action --! @ingroup mobf_path -- --! @param entity mobf rightclicked --! @param player issuing rightclick ------------------------------------------------------------------------------- function mobf_path.mob_rightclick_callback(entity,player) local playername = player:get_player_name() if entity.dynamic_data.spawning.spawner ~= playername then core.show_formspec(playername,"mobf:path:add_path_to_entity", "size[4,1]label[0,0;This is not your mob keep away!]" .. "button_exit[1,0.75;2,0.5;btn_exit;Okay Okay!]") return end if entity.dynamic_data.patrol_state_before ~= nil then mobf_path.switch_patrol(entity,nil,nil) else local buttons = "" local y_pos = 0.25 local storage_id = mobf_global_data_store(entity) local playername = player:get_player_name() local pathlist = mobf_path.get_pathlist(playername,false) dbg_mobf.path_lvl2("MOBF: Pathlist contains: " .. dump(pathlist)) for i = 1, #pathlist, 1 do buttons = buttons .. "button_exit[0," .. y_pos .. ";4.5,0.5;" .. "mobfpath:" .. storage_id .. ":" .. pathlist[i].pathname .. ";" .. pathlist[i].pathname .. "]" y_pos = y_pos + 0.75 end local y_size = y_pos - 0.25 local formspec = "size[4.5," .. y_size .. "]" .. buttons --show formspec core.show_formspec(playername,"mobf:path:add_path_to_entity",formspec) end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] config_check(entity) -- --! @brief check if mob is configured as trader --! @ingroup mobf_path -- --! @param entity mob being checked --! @return true/false if trader or not ------------------------------------------------------------------------------- function mobf_path.config_check(entity) if entity.data.patrol ~= nil then return true end return false end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] buttontext(entity) -- --! @brief return text for rightclick button --! @ingroup mobf_path -- --! @param entity to get text for --! @return buttonname ------------------------------------------------------------------------------- function mobf_path.buttontext(entity) if entity.dynamic_data.patrol_state_before == nil then return "Select path" end return "Disable path" end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] switch_patrol(entity,points) -- --! @brief check if mob is configured as trader --! @ingroup mobf_path -- --! @param entity mob being switched --! @param pathowner owner of path to switch to --! @param pathname path to switch to ------------------------------------------------------------------------------- function mobf_path.switch_patrol(entity,pathowner,pathname) if pathowner ~= nil and pathname ~= nil and entity.data.patrol.state ~= nil then if entity.dynamic_data.patrol_state_before == nil then if entity.dynamic_data.state.current ~= entity.data.patrol.state then entity.dynamic_data.patrol_state_before = entity.dynamic_data.state.current else entity.dynamic_data.patrol_state_before = "default" end mob_state.lock(entity,true) end local new_state = mob_state.get_state_by_name(entity,entity.data.patrol.state) mobf_assert_backtrace(new_state ~= nil) mob_state.change_state(entity,new_state) entity.dynamic_data.p_movement.pathowner = pathowner entity.dynamic_data.p_movement.pathname = pathname entity.dynamic_data.p_movement.path = mobf_rtd.path_data.users[pathowner].paths[pathname].points entity.dynamic_data.p_movement.next_path_index = 1 else if entity.dynamic_data.patrol_state_before ~= nil then local new_state = mob_state.get_state_by_name(entity,entity.dynamic_data.patrol_state_before) if new_state == nil then new_state = mob_state.get_state_by_name(entity,"default") end mobf_assert_backtrace(new_state ~= nil) mob_state.change_state(entity,new_state) entity.dynamic_data.patrol_state_before = nil mob_state.lock(entity,false) end end end ------------------------------------------------------------------------------- -- @function [parent=#mobf_path] getpoints(owner,name) -- --! @brief get a path by owner and name --! @ingroup mobf_path -- --! @param pathowner player owning the path --! @param pathname name of path --! @return list of points ------------------------------------------------------------------------------- function mobf_path.getpoints(pathowner,pathname) if mobf_rtd.path_data.users[pathowner] == nil then dbg_mobf.path_lvl2("MOBF: no paths for " .. dump(pathowner) .. " found") return nil end if mobf_rtd.path_data.users[pathowner].paths[pathname] == nil then dbg_mobf.path_lvl2( "MOBF: no path " .. dump(pathname) .. " found for owner " .. pathowner .. " have paths: " .. dump(mobf_rtd.path_data.users[pathowner].paths)) return nil end return mobf_rtd.path_data.users[pathowner].paths[pathname].points end mobf_core-2.5.1/mobf/random_drop.lua000066400000000000000000000143731264104133000174060ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file random_drop.lua --! @brief component containing random drop features --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup randdrop Random Drop subcomponent --! @brief Component handling all random drops --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("random_drop")) --! @class random_drop --! @brief random drop features e.g lay eggs --!@} random_drop = {} ------------------------------------------------------------------------------- -- @function [parent=#random_drop] callback(entity) -- --! @brief random drop periodic callback --! @memberof random_drop -- --! @param entity mob calling it --! @param now current time ------------------------------------------------------------------------------- function random_drop.callback(entity,now) if entity.data.random_drop ~= nil and entity.dynamic_data.random_drop ~= nil and entity.data.random_drop.result ~= "" then dbg_mobf.random_drop_lvl3("MOBF: random drop for ".. entity.data.name .." is set") if entity.dynamic_data.random_drop.ts_last_drop + entity.data.random_drop.min_delay < now then dbg_mobf.random_drop_lvl3("MOBF: enough time passed give drop a chance") if math.random() < entity.data.random_drop.chance then entity.dynamic_data.random_drop.ts_last_drop = now local entitybasepos = entity.getbasepos(entity) --find pos around local toput = environment.get_suitable_pos_same_level(entitybasepos,1,entity) if toput ~= nil then minetest.add_entity(toput,entity.data.random_drop.result.."_ent") dbg_mobf.random_drop_lvl3("MOBF: adding random drop for "..entity.data.name .. ": "..entity.data.random_drop.result.."_ent" .. " at " .. printpos(toput)) if entity.data.sound ~= nil then sound.play(entitybasepos,entity.data.sound.random_drop) end else dbg_mobf.random_drop_lvl2("MOBF: didn't find a place to put random drop for ".. entity.data.name) end end end end end ------------------------------------------------------------------------------- -- @function [parent=#random_drop] register_random_drop(random_drop) -- --! @brief register random drop item and entity --! @memberof random_drop -- --! @param random_drop configuration for random drop feature ------------------------------------------------------------------------------- function random_drop.register(random_drop) --get basename from random drop item name local start_pos = 1 local end_pos = string.find(random_drop.result,":") if end_pos == nil then return end local drop_basename = string.sub(random_drop.result,start_pos,end_pos-1) local drop_itemname = string.sub(random_drop.result,end_pos+1) if drop_itemname == nil or drop_basename == nil then return end minetest.log(LOGLEVEL_INFO,"MOBF:\tregistering random drop entity: "..":"..random_drop.result.."_ent".. " item="..drop_itemname .. " basename=" .. drop_basename) local ent_graphics = {} local id = drop_basename .. "_" .. drop_itemname if minetest.world_setting_get("mobf_disable_3d_mode") or animalmaterialsdata[id] == nil or animalmaterialsdata[id].graphics_3d == nil then ent_graphics.visual = "sprite" ent_graphics.textures = {drop_basename .. "_"..drop_itemname..".png"} ent_graphics.collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5} else ent_graphics.visual = animalmaterialsdata[id].graphics_3d.visual ent_graphics.mesh = animalmaterialsdata[id].graphics_3d.mesh ent_graphics.textures = animalmaterialsdata[id].graphics_3d.textures ent_graphics.collisionbox = animalmaterialsdata[id].graphics_3d.collisionbox ent_graphics.visual_size = animalmaterialsdata[id].graphics_3d.visual_size end --Entity minetest.register_entity(":"..random_drop.result.."_ent", { physical = true, collisionbox = ent_graphics.collisionbox, visual = ent_graphics.visual, textures = ent_graphics.textures, mesh = ent_graphics.mesh, visual_size = ent_graphics.visual_size, on_drop_callback= random_drop.on_drop_callback, on_timeout_callback= random_drop.on_timeout_callback, on_activate = function(self,staticdata) self.object:setacceleration({x=0,y=-9.81,z=0}) local now = mobf_get_current_time() if staticdata == "" then self.dropped = now if type(self.on_drop_callback) == "function" then self:on_drop_callback() end else self.dropped = tonumber(staticdata) end if self.dropped + self.random_drop_max_life < now then dbg_mobf.random_drop_lvl2("MOBF: random drop entity timed out") self.object:remove() end end, on_punch = function(self, hitter) hitter:get_inventory():add_item("main", random_drop.result.." 1") self.object:remove() end, on_step = function(self,dtime) if self.dropped + self.random_drop_max_life < mobf_get_current_time() then dbg_mobf.random_drop_lvl2("MOBF: random drop entity timed out") if type(self.on_timeout_callback) == "function" then self:on_timeout_callback() end self.object:remove() end end, get_staticdata = function(self) return self.dropped end, random_drop_max_life = random_drop.min_delay/2, dropped = 0, }) end ------------------------------------------------------------------------------- -- @function [parent=#random_drop] init_dynamic_data(entity,now) -- --! @brief initialize dynamic data required by random drop --! @memberof random_drop -- --! @param entity mob to add data --! @param now current time ------------------------------------------------------------------------------- function random_drop.init_dynamic_data(entity,now) if entity.data.random_drop ~= nil and entity.data.random_drop.min_delay > 5 then entity.dynamic_data.random_drop = { ts_last_drop = now + math.random(5,entity.data.random_drop.min_delay) } else entity.dynamic_data.random_drop = { ts_last_drop = now } end end mobf_core-2.5.1/mobf/ride.lua000066400000000000000000000212451264104133000160210ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file ride.lua --! @brief class containing mobf functions for riding --! @copyright Sapier --! @author Sapier --! @date 2013-01-06 -- -- --! @defgroup mobf_ride Rideable mobs subcomponent --! @brief a component containing all functions required to ride a mob --! @ingroup framework_int --! @{ -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mobf_ride")) --! @class mobf_ride --! @brief contains all riding specific functions --! @} mobf_ride = {} ------------------------------------------------------------------------------ -- @function [parent=#mobf_ride] attach_player(entity,player) -- --! @brief make a player ride this mob --! @class mobf_ride --! @private -- --! @param entity entity to be ridden --! @param player player riding ------------------------------------------------------------------------------- function mobf_ride.attach_player(entity,player) entity.dynamic_data.ride.is_attached = true entity.dynamic_data.ride.player = player entity.object:setacceleration({x=0,y=-9.81,z=0}) entity.object:setvelocity({x=0,y=-9.81,z=0}) local attacheoffset = {x=0,y=0.5,z=0} if entity.data.ride ~= nil and entity.data.ride.attacheoffset ~= nil then attacheoffset = entity.data.ride.attacheoffset end player:set_attach(entity.object,"",attacheoffset, {x=0,y=90,z=0}) -- default always overrides animations even for attached players -- if type(default.player_set_animation) == "function" then -- default.player_set_animation(player, "sit") -- end if entity.data.ride.texturemod ~= nil then entity.object:settexturemod(entity.data.ride.texturemod); end end ------------------------------------------------------------------------------ -- @function [parent=#mobf_ride] dettach_player(entity,player) -- --! @brief make a player ride this mob --! @class mobf_ride --! @private -- --! @param entity entity to be ridden ------------------------------------------------------------------------------- function mobf_ride.dettach_player(entity) entity.dynamic_data.ride.is_attached = false entity.dynamic_data.ride.player:set_detach() entity.dynamic_data.ride.player = nil entity.object:settexturemod(""); end ------------------------------------------------------------------------------ -- @function [parent=#mobf_ride] on_step_callback(entity) -- --! @brief make a player ride this mob --! @class mobf_ride --! @public -- --! @param entity entity to be ridden ------------------------------------------------------------------------------- function mobf_ride.on_step_callback(entity) if entity.dynamic_data.ride.is_attached then dbg_mobf.ride_lvl3("MOBF: have attached player") local walkspeed = 3 local sneakspeed = 0.5 local jumpspeed = 30 local runspeed = walkspeed if entity.data.ride ~= nil then if entity.data.ride.walkspeed ~= nil then walkspeed = entity.data.ride.walkspeed end if entity.data.ride.runspeed ~= nil then runspeed = entity.data.ride.runspeed end if entity.data.ride.sneakspeed ~= nil then sneakspeed = entity.data.ride.sneakspeed end if entity.data.ride.jumpspeed ~= nil then jumpspeed = entity.data.ride.jumpspeed end end local dir = entity.dynamic_data.ride.player:get_look_yaw() local current_speed = entity.object:getacceleration() local speed_to_set = {x=0,y=current_speed.y,z=0} if dir ~= nil then local playerctrl = entity.dynamic_data.ride.player:get_player_control() if playerctrl ~= nil then local setspeed = false if playerctrl.jump and entity.is_on_ground(entity) then speed_to_set.y = jumpspeed setspeed = true end --just set speed to playerview direction if playerctrl.up then setspeed = true end --invert playerview direction if playerctrl.down then dir = dir + math.pi setspeed = true end if playerctrl.left then if playerctrl.up then dir = dir + math.pi/4 elseif playerctrl.down then dir = dir - math.pi/4 else dir = dir + math.pi/2 end setspeed = true end if playerctrl.right then if playerctrl.up then dir = dir - math.pi/4 elseif playerctrl.down then dir = dir + math.pi/4 else dir = dir - math.pi/2 end setspeed = true end local selected_speed = walkspeed if playerctrl.sneak then selected_speed = sneakspeed end if setspeed then local speed_to_set_xz = mobf_calc_vector_components(dir,selected_speed) speed_to_set.x = speed_to_set_xz.x speed_to_set.z = speed_to_set_xz.z if entity.data.ride.walk_anim ~= nil then graphics.set_animation(entity,entity.data.ride.walk_anim) end else if entity.data.ride.walk_anim ~= nil then if entity.data.ride.stand_anim ~= nil then graphics.set_animation(entity,entity.data.ride.stand_anim) mob_state.change_state(entity,mob_state.get_state_by_name(entity,entity.data.ride.state_stand)) else graphics.set_animation(entity,"stand") end end end entity.object:setvelocity(speed_to_set) --fix switched model orientation graphics.setyaw(entity,dir) end end return true else return false end end ------------------------------------------------------------------------------ -- @function [parent=#mobf_ride] on_punch_callback(entity,player) -- --! @brief make a player ride this mob --! @class mobf_ride --! @public -- --! @param entity entity to be ridden --! @param player player riding ------------------------------------------------------------------------------- function mobf_ride.on_punch_callback(entity,player) dbg_mobf.ride_lvl2("MOBF: ride on punch callback") print("MOBF: ride on punch callback") local saddle = "animalmaterials:saddle" if entity.data.ride.saddle ~= nil then saddle = entity.data.ride.saddle end --detache if entity.dynamic_data.ride.is_attached ~= false then dbg_mobf.ride_lvl2("MOBF: punched ridden mob") if entity.dynamic_data.ride.player == player then dbg_mobf.ride_lvl2("MOBF: detaching player") mobf_ride.dettach_player(entity) player:get_inventory():add_item("main",saddle .. " 1") return true end else --check if player has saddle dbg_mobf.ride_lvl2("MOBF: punched free mob") if player:get_wielded_item():get_name() == saddle then dbg_mobf.ride_lvl2("MOBF: punching with saddle") if player:get_inventory():contains_item("main",saddle .. " 1") then dbg_mobf.ride_lvl2("MOBF: have saddle") mobf_ride.attach_player(entity,player) player:get_inventory():remove_item("main",saddle .. " 1") return true end else dbg_mobf.ride_lvl2("MOBF: not punching with saddle but: " .. player:get_wielded_item():get_name()) end end return false end ------------------------------------------------------------------------------ -- @function [parent=#mobf_ride] is_enabled(entity) -- --! @brief check if riding is enabled for a mob --! @class mobf_ride --! @public -- --! @param entity entity to be ridden ------------------------------------------------------------------------------- function mobf_ride.is_enabled(entity) if entity.data.ride ~= nil then return true end dbg_mobf.ride_lvl2("riding of " .. entity.data.name .. " is disabled") return false end ------------------------------------------------------------------------------ -- @function [parent=#mobf_ride] init(entity) -- --! @brief initialize ride dynamic data --! @class mobf_ride --! @public -- --! @param entity entity to be ridden ------------------------------------------------------------------------------- function mobf_ride.init(entity) local data = { is_attached = false, player = nil, } entity.dynamic_data.ride = data end -- special handler on leave minetest.register_on_leaveplayer( function(player) if player ~= nil and player.object ~= nil then local pos = player.object:getpos() --print("MOBF: got player position: " ..printpos(pos) ) if pos ~= nil then local objects = get_objects_inside_radius(pos, 5) print("MOBF: found " .. dump(#objects) .. " objects around player") if objects ~= nil then for i=1,#objects,1 do local entity = objects[i]:get_luaentity() if entity ~= nil and entity.dynamic_data ~= nil and entity.dynamic_data.ride ~= nil and entity.dynamic_data.ride.player == player then print("MOBF: found player to be attached") ride.dettach_player(entity) break end end end end end end)mobf_core-2.5.1/mobf/sound.lua000066400000000000000000000145371264104133000162340ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file sound.lua --! @brief component containing sound related functions --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup grp_sound Sound subcomponent --! @brief Component handling all sound related actions --! @ingroup framework_int --! @{ -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("sound")) --! @class sound --! @brief sound selection and play functions --!@} sound = {} ------------------------------------------------------------------------------- -- @function [parent=#sound] play(entity) -- --! @brief play a sound at a specified position --! @memberof sound -- --! @param param1 position to play sound at or playername to play sound for --! @param soundspec sound to play ------------------------------------------------------------------------------- function sound.play(param1, soundspec) local pos = nil local playername = nil if type(param1) == "string" then playername = param1 else pos = param1 end if (soundspec ~= nil) then local toplay = { gain = soundspec.gain, pos = pos, to_player = playername, max_hear_distance = soundspec.max_hear_distance, loop = false, } minetest.sound_play(soundspec.name,toplay) else dbg_mobf.sound_lvl2("MOBF: no soundspec") --todo add log entry end end ------------------------------------------------------------------------------- -- @function [parent=#sound] play_random(entity,now) -- --! @brief play a random sound for mob --! @memberof sound -- --! @param entity mob to do action --! @param now current time ------------------------------------------------------------------------------- function sound.play_random(entity,now) if entity.dynamic_data == nil or entity.dynamic_data.sound == nil then mobf_bug_warning(LOGLEVEL_ERROR, "MOBF BUG!!!: >" ..entity.data.name .. "< removed=" .. dump(entity.removed) .. " entity=" .. tostring(entity) .. " sound callback without dynamic data") return end if entity.data.sound ~= nil and entity.data.sound.random ~= nil then -- check for old style random sound definition using chance + min_delta if entity.data.sound.random.min_delta ~= nil and entity.dynamic_data.sound.random_last + entity.data.sound.random.min_delta > now then return end -- check for old style random sound definition using chance if entity.data.sound.random.chance ~= nil and math.random() > entity.data.sound.random.chance then return end -- check for new style sounds done by gauss distribution if entity.dynamic_data.sound.random_next ~= nil and entity.dynamic_data.sound.random_next > now then return end -- init variable to be passed to play local toplay = nil -- sound list mode if entity.data.sound.random.list ~= nil then -- select random sound from list local current_random_sound = math.floor(math.random(1,#entity.data.sound.random.list) + 0.5) toplay = entity.data.sound.random.list[current_random_sound] dbg_mobf.sound_lvl3("MOBF: selected random sound: " .. current_random_sound .. "/" .. #entity.data.sound.random.list) -- single sound mode elseif entity.data.sound.random.name ~= nil then toplay = entity.data.sound.random else dbg_mobf.sound_lvl1("MOBF: invalid random sound configuration") end if toplay ~= nil then sound.play(entity.object:getpos(),toplay) entity.dynamic_data.sound.random_last = now dbg_mobf.sound_lvl3("MOBF: playing sound: " .. toplay.name) end if entity.dynamic_data.sound.random_next ~= nil then if entity.data.sound.random.interval == nil or entity.data.sound.random.max_interval_deviation == nil then dbg_mobf.sound_lvl1("MOBF: invalid random sound configuration," .. " missing \"interval\" or \"max_interval_deviation\"") return end local delta_next = sound.calc_random_sound_delay(entity) dbg_mobf.sound_lvl1("MOBF: next random sound in: " .. delta_next .. "s") entity.dynamic_data.sound.random_next = now + delta_next else entity.dynamic_data.sound.random_last = now end end end ------------------------------------------------------------------------------- -- @function [parent=#sound] sound.calc_random_sound_delay(entity) -- --! @brief calculate delay for random sound --! @memberof sound -- --! @param entity mob to calc for ------------------------------------------------------------------------------- function sound.calc_random_sound_delay(entity) local base_value = mobf_gauss(entity.data.sound.random.interval, entity.data.sound.random.max_interval_deviation/20) local delta_next = math.max(entity.data.sound.random.interval - entity.data.sound.random.max_interval_deviation, base_value) delta_next = math.min(delta_next, entity.data.sound.random.interval + entity.data.sound.random.max_interval_deviation) return delta_next end ------------------------------------------------------------------------------- -- @function [parent=#sound] sound.init_dynamic_data(entity, now) -- --! @brief initialize all dynamic data for sound on activate --! @memberof sound -- --! @param entity mob to initialize --! @param now current time ------------------------------------------------------------------------------- function sound.init_dynamic_data(entity, now) local data = { random_last = now, } if entity.data.sound.random ~= nil and entity.data.sound.random.interval ~= nil and entity.data.sound.random.max_interval_deviation then local delta_next = sound.calc_random_sound_delay(entity) data.random_next = delta_next + now dbg_mobf.sound_lvl2("MOBF: initalizing random_next to: " .. delta_next .. " Interval: " .. entity.data.sound.random.interval .. " Deviation: " .. entity.data.sound.random.max_interval_deviation) elseif entity.data.sound.random ~= nil and entity.data.sound.random.chance == nil then dbg_mobf.sound_lvl1("MOBF: invalid random sound definition for \"" .. entity.data.name .. "\" ... neither invterval nor probability mode configured") end entity.dynamic_data.sound = data endmobf_core-2.5.1/mobf/spawning.lua000066400000000000000000000511131264104133000167210ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file spawning.lua --! @brief component containing spawning features --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 -- --! @defgroup spawning Spawn mechanisms --! @brief all functions and variables required for automatic mob spawning --! @ingroup framework_int --! @{ -- --! @defgroup spawn_algorithms (DEPRECATED) Spawn algorithms --! @brief spawn algorithms provided by previous mob framework versions. New --! mobs are strongly encouraged to use advanced spawning mob as spawning will --! be removed from mobf as of version 2.5 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("spawning")) --! @class spawning --! @brief spawning features spawning = {} --!@} mobf_assert_backtrace(not core.global_exists("mobf_spawn_algorithms")) --! @brief registry for spawn algorithms --! @memberof spawning --! @private mobf_spawn_algorithms = {} ------------------------------------------------------------------------------- -- name: init() -- @function [parent=#spawning] init -- --! @brief initialize spawning data --! @memberof spawning -- ------------------------------------------------------------------------------- function spawning.init() --read from file local world_path = minetest.get_worldpath() local file,error = io.open(world_path .. "/mobf_spawning_data","r") if file ~= nil then local data_raw = file:read("*a") file:close() if data_raw ~= nil then spawning.mob_spawn_data = minetest.deserialize(data_raw) end end if spawning.mob_spawn_data == nil then spawning.mob_spawn_data = {} end --register spawndata persistent storer to globalstep minetest.after(300,spawning.preserve_spawn_data,true) --register cleanup handler minetest.register_on_shutdown(function(dstep) spawning.preserve_spawn_data(false) end) end ------------------------------------------------------------------------------- -- name: preserve_spawn_data() -- @function [parent=#spawning] preserve_spawn_data -- --! @brief save data on regular base --! @memberof spawning -- --! @param cyclic if this is true spawn data is saved in cyclic intervals ------------------------------------------------------------------------------- function spawning.preserve_spawn_data(cyclic) local world_path = minetest.get_worldpath() local file,error = io.open(world_path .. "/mobf_spawning_data","w") if error ~= nil then minetest.log(LOGLEVEL_ERROR,"MOBF: failed to spawning preserve file") end mobf_assert_backtrace(file ~= nil) local serialized_data = minetest.serialize(spawning.mob_spawn_data) file:write(serialized_data) if cyclic then minetest.after(300,spawning.preserve_spawn_data,cyclic) end end ------------------------------------------------------------------------------- -- name: total_offline_mobs() -- @function [parent=#spawning] total_offline_mobs -- --! @brief count total number of offline mobs --! @memberof spawning -- --! @return number of mobs ------------------------------------------------------------------------------- function spawning.total_offline_mobs() local count = 0 for key,value in pairs(spawning.mob_spawn_data) do for hash,v in pairs(value) do count = count +1 end end return count end ------------------------------------------------------------------------------- -- name: count_deactivated_mobs(name,pos,range) -- @function [parent=#spawning] count_deactivated_mobs -- --! @brief count number of mobs of specific type within a certain range --! @memberof spawning -- --! @param name name of mob to count --! @param pos to check distance to --! @param range to check -- --! @return number of mobs ------------------------------------------------------------------------------- function spawning.count_deactivated_mobs(name,pos,range) local count = 0 if spawning.mob_spawn_data[name] ~= nil then for hash,v in pairs(spawning.mob_spawn_data[name]) do local mobpos = mobf_hash_to_pos(hash) local distance = vector.distance(pos,mobpos) if distance < range then local node = core.get_node(mobpos) local notfound = true -- if we are within active object range and -- that position is loaded check if there's really a mob at that location if node.name ~= "ignore" and distance < 32 then local found = false local objects_around = core.get_objects_inside_radius(mobpos, 1) if objects_around and #objects_around > 0 then for i,v in ipairs(objects_around) do local luaentity = v:get_luaentity() if luaentity ~= nil then if luaentity.data ~= nil and luaentity.data.name == name then found = true break end end end end if not found then dbg_mobf.spawning_lvl2( "MOBF: clearing stall deactivated entry at: " .. core.pos_to_string(mobpos)) notfound = false spawning.mob_spawn_data[name][hash] = nil end end if notfound then count = count +1 end end end end return count end ------------------------------------------------------------------------------- -- name: deactivate_mob(entity) -- @function [parent=#spawning] deactivate_mob -- --! @brief add mob to deactivated list --! @memberof spawning -- --! @param name name of mob to be deactivated --! @param pos position to deactivate mob at ------------------------------------------------------------------------------- function spawning.deactivate_mob(name,pos) if spawning.mob_spawn_data[name] == nil then spawning.mob_spawn_data[name] = {} end local rounded_pos = vector.round(pos) local hash = minetest.hash_node_position(rounded_pos) --assert (mobf_pos_is_same(mobf_hash_to_pos(hash),rounded_pos)) spawning.mob_spawn_data[name][hash] = true end ------------------------------------------------------------------------------- -- name: activate_mob(name,pos) -- @function [parent=#spawning] preserve_spawn_data -- --! @brief save data on regular base --! @memberof spawning -- --! @param name name of mob to be activated --! @param pos position to activate mob at ------------------------------------------------------------------------------- function spawning.activate_mob(name,pos) if spawning.mob_spawn_data[name] ~= nil then local rounded_pos = vector.round(pos) local hash = minetest.hash_node_position(rounded_pos) --assert(mobf_pos_is_same(mobf_hash_to_pos(hash),rounded_pos)) spawning.mob_spawn_data[name][hash] = nil end end ------------------------------------------------------------------------------- -- name: remove_uninitialized(entity,staticdata) -- @function [parent=#spawning] remove_uninitialized -- --! @brief remove a spawn point based uppon staticdata supplied --! @memberof spawning -- --! @param entity to remove --! @param staticdata of mob ------------------------------------------------------------------------------- function spawning.remove_uninitialized(entity, staticdata) --entity may be known in spawnlist if staticdata ~= nil then local permanent_data = mobf_deserialize_permanent_entity_data(staticdata) if (permanent_data.spawnpoint ~= nil) then --prepare information required to remove entity entity.dynamic_data = {} entity.dynamic_data.spawning = {} entity.dynamic_data.spawning.spawnpoint = permanent_data.spawnpoint entity.dynamic_data.spawning.player_spawned = permanent_data.playerspawned entity.dynamic_data.spawning.spawner = permanent_data.spawner spawning.remove(entity,"remove uninitialized") end else dbg_mobf.spawning_lvl1("MOBF: remove uninitialized entity=" .. tostring(entity)) --directly remove it can't be known to spawnlist entity.object:remove() end end ------------------------------------------------------------------------------- -- name: remove(entity) -- @function [parent=#spawning] remove -- --! @brief remove a mob --! @memberof spawning -- --! @param entity mob to remove --! @param reason text to log as reason for removal ------------------------------------------------------------------------------- function spawning.remove(entity,reason) local pos = entity.object:getpos() dbg_mobf.spawning_lvl3("MOBF: --> remove " .. printpos(pos)) if entity ~= nil then entity.removed = true dbg_mobf.spawning_lvl1("MOBF: remove entity=" .. tostring(entity)) if minetest.world_setting_get("mobf_log_removed_entities") then if reason == nil then reason = "unknown" end minetest.log(LOGLEVEL_NOTICE,"MOBF: removing " .. entity.data.name .. " at " .. printpos(pos) .. " due to: " .. reason) end mobf.preserve_removed(entity,reason) if entity.lifebar ~= nil then mobf_lifebar.del(entity.lifebar) end entity.object:remove() else minetest.log(LOGLEVEL_ERROR,"Trying to delete an an non existant mob") end dbg_mobf.spawning_lvl3("MOBF: <-- remove") end ------------------------------------------------------------------------------- -- name: init_dynamic_data(entity) -- @function [parent=#spawning] init_dynamic_data -- --! @brief initialize dynamic data required for spawning --! @memberof spawning -- --! @param entity mob to initialize dynamic data --! @param now current time ------------------------------------------------------------------------------- function spawning.init_dynamic_data(entity,now) local player_spawned = false if entity.dynamic_data.spawning ~= nil and entity.dynamic_data.spawning.player_spawned then player_spawned = true end local data = { player_spawned = player_spawned, ts_dense_check = now, spawnpoint = entity.object:getpos(), original_spawntime = now, spawner = nil, density = spawning.population_density_get_min(entity), } entity.removed = false entity.dynamic_data.spawning = data end ------------------------------------------------------------------------------- -- name: population_density_check(mob) -- @function [parent=#spawning] population_density_check -- --! @brief check and fix if there are too many mobs within a specific range --! @memberof spawning -- --! @param entity mob to check --! @param now current time ------------------------------------------------------------------------------- function spawning.population_density_check(entity,now) if entity == nil or entity.dynamic_data == nil or entity.dynamic_data.spawning == nil then mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!! " .. entity.data.name .. " pop dense check called for entity with missing spawn data entity=" .. tostring(entity)) return false end --only check every 5 seconds if entity.dynamic_data.spawning.ts_dense_check + 5 > now then return true end -- don't check if mob is player spawned if entity.dynamic_data.spawning.player_spawned == true then dbg_mobf.spawning_lvl1("MOBF: mob is player spawned skipping pop dense check") return true end --don't do population check while fighting if entity.dynamic_data.combat ~= nil and entity.dynamic_data.combat.target ~= nil and entity.dynamic_data.combat.target ~= "" then dbg_mobf.spawning_lvl1( "MOBF: fighting right now skipping pop dense check: " .. dump(entity.dynamic_data.combat.target)) return true end entity.dynamic_data.spawning.ts_dense_check = now local entitypos = mobf_round_pos(entity.object:getpos()) --mob either not initialized completely or a bug if mobf_pos_is_zero(entitypos) then dbg_mobf.spawning_lvl1("MOBF: can't do a sane check") return true end local secondary_name = "" if entity.data.harvest ~= nil then secondary_name = entity.data.harvest.transform_to end local check_density = entity.dynamic_data.spawning.density if entity.data.generic.population_density ~= nil then check_density = entity.data.generic.population_density end mobf_assert_backtrace(check_density ~= nil) local mob_count = mobf_mob_around(entity.data.modname..":"..entity.data.name, secondary_name, entitypos, check_density, true) if mob_count > 5 then dbg_mobf.spawning_lvl1("MOBF: " .. entity.data.name .. mob_count .. " mobs of same type around") entity.removed = true minetest.log(LOGLEVEL_INFO,"MOBF: Too many ".. mob_count .. " ".. entity.data.name.." at one place dying: " .. tostring(entity.dynamic_data.spawning.player_spawned)) spawning.remove(entity, "population density check") return false else dbg_mobf.spawning_lvl2("MOBF: " .. entity.data.name .. " density ok only "..mob_count.." mobs around") return true end end ------------------------------------------------------------------------------- -- name: replace_entity(pos,name,spawnpos,health) -- @function [parent=#spawning] replace_entity -- --! @brief replace mob at a specific position by a new one --! @memberof spawning -- --! @param entity mob to replace --! @param name of the mob to add --! @param preserve preserve original spawntime --! @return entity added or nil on error ------------------------------------------------------------------------------- function spawning.replace_entity(entity,name,preserve) dbg_mobf.spawning_lvl3("MOBF: --> replace_entity(" .. entity.data.name .. "|" .. name .. ")") if minetest.registered_entities[name] == nil then minetest.log(LOGLEVEL_ERROR,"MOBF: replace_entity: Bug no " ..name.." is registred") return nil end -- avoid switching to same entity if entity.name == name then minetest.log(LOGLEVEL_INFO,"MOBF: not replacing " .. name .. " by entity of same type!") return nil end -- get data to be transfered to new entity local pos = mobf.get_basepos(entity) local health = entity.object:get_hp() local temporary_dynamic_data = entity.dynamic_data local entity_orientation = graphics.getyaw(entity) if preserve == nil or preserve == false then temporary_dynamic_data.spawning.original_spawntime = mobf_get_current_time() end --calculate new y pos if minetest.registered_entities[name].collisionbox ~= nil then pos.y = pos.y - minetest.registered_entities[name].collisionbox[2] end --delete current mob dbg_mobf.spawning_lvl2("MOBF: replace_entity: removing " .. entity.data.name) --unlink dynamic data (this should work but doesn't due to other bugs) entity.dynamic_data = nil --removing is done after exiting lua! spawning.remove(entity,"replaced") --set marker to true to make sure activate handler knows it's replacing right now spawning.replacing_NOW = true local newobject = minetest.add_entity(pos,name) spawning.replacing_NOW = false local newentity = mobf_find_entity(newobject) if newentity ~= nil then if newentity.dynamic_data ~= nil then dbg_mobf.spawning_lvl2("MOBF: replace_entity: " .. name) newentity.dynamic_data = temporary_dynamic_data newentity.object:set_hp(health) graphics.setyaw(newentity, entity_orientation) else minetest.log(LOGLEVEL_ERROR, "MOBF: replace_entity: dynamic data not set for "..name.. " maybe delayed activation?") newentity.dyndata_delayed = { data = temporary_dynamic_data, health = health, orientation = entity_orientation } end else minetest.log(LOGLEVEL_ERROR, "MOBF: replace_entity 4 : Bug no "..name.." has been created") end dbg_mobf.spawning_lvl3("MOBF: <-- replace_entity") return newentity end ------------------------------------------------------------------------------ -- name: lifecycle_callback() -- @function [parent=#spawning] lifecycle_callback -- --! @brief check mob lifecycle_callback --! @memberof spawning -- --! @return true/false still alive dead ------------------------------------------------------------------------------- function spawning.lifecycle_callback(entity,now) if entity.dynamic_data.spawning.original_spawntime ~= nil then if entity.data.spawning ~= nil and entity.data.spawning.lifetime ~= nil then local lifetime = entity.data.spawning.lifetime local current_age = now - entity.dynamic_data.spawning.original_spawntime if current_age > 0 and current_age > lifetime then dbg_mobf.spawning_lvl1("MOBF: removing animal due to limited lifetime") spawning.remove(entity," limited mob lifetime") return false end end else entity.dynamic_data.spawning.original_spawntime = now end return true end ------------------------------------------------------------------------------ -- name: spawn_and_check(name,pos,text) -- @function [parent=#spawning] spawn_and_check -- --! @brief spawn an entity and check for presence --! @memberof spawning --! @param name name of entity --! @param pos position to spawn mob at --! @param text message used for log messages -- --! @return spawned mob entity ------------------------------------------------------------------------------- function spawning.spawn_and_check(name,pos,text) mobf_assert_validpos(pos) mobf_assert_backtrace(name ~= nil) local newobject = minetest.add_entity(pos,name) if newobject then local newentity = mobf_find_entity(newobject) if newentity == nil then dbg_mobf.spawning_lvl3("MOBF BUG!!! no " .. name .. " entity has been created by " .. text .. "!") mobf_bug_warning(LOGLEVEL_ERROR,"BUG!!! no " .. name .. " entity has been created by " .. text .. "!") else dbg_mobf.spawning_lvl2("MOBF: spawning "..name .. " entity by " .. text .. " at position ".. printpos(pos)) minetest.log(LOGLEVEL_INFO,"MOBF: spawning "..name .. " entity by " .. text .. " at position ".. printpos(pos)) return newentity end else dbg_mobf.spawning_lvl3("MOBF BUG!!! no "..name.. " object has been created by " .. text .. "!") mobf_bug_warning(LOGLEVEL_ERROR,"MOBF BUG!!! no "..name.. " object has been created by " .. text .. "!") end return nil end ------------------------------------------------------------------------------ -- name: population_density_get_min(entity) -- @function [parent=#spawning] population_density_get_min -- --! @brief get minimum density for this mob --! @memberof spawning -- --! @param entity the mob itself -- --! @return minimum density over all spawners defined for this mob ------------------------------------------------------------------------------- function spawning.population_density_get_min(entity) if entity.data.spawning == nil then return entity.data.generic.population_density end -- legacy code if type(entity.data.spawning.primary_algorithms) == "table" then local density = nil for i=1 , #entity.data.spawning.primary_algorithms , 1 do if density == nil or entity.data.spawning.primary_algorithms[i].density < density then density = entity.data.spawning.primary_algorithms[i].density end end return density else return entity.data.spawning.density end end ------------------------------------------------------------------------------- -- name: check_activation_overlap(entity,pos,preserved_data) -- --! @brief check if a activating entity is spawned within some other entity -- --! @param entity entity to check --! @param pos position spawned at --! @param preserved_data data loaded for entity --! @return true ------------------------------------------------------------------------------- function spawning.check_activation_overlap(entity,pos,preserved_data) local cleaned_objectcount = mobf_objects_around(pos,0.25,{ "mobf:lifebar" }) --honor replaced marker if (entity.replaced ~= true and cleaned_objectcount > 1) or cleaned_objectcount > 2 then ------------------------------ -- debug output only -- --------------------------- local spawner = "unknown" if preserved_data ~= nil and preserved_data.spawner ~= nil then spawner = preserved_data.spawner mobf_bug_warning(LOGLEVEL_WARNING, "MOBF: trying to activate mob \"" ..entity.data.name .. " at " .. printpos(pos) .. " (" .. tostring(entity) .. ")".. "\" within something else!" .. " originaly spawned by: " .. spawner .. " --> removing") objectlist = minetest.get_objects_inside_radius(pos,0.25) for i=1,#objectlist,1 do local luaentity = objectlist[i]:get_luaentity() if luaentity ~= nil then if luaentity.data ~= nil and luaentity.data.name ~= nil then dbg_mobf.mobf_core_helper_lvl3( i .. " LE: " .. luaentity.name .. " (" .. tostring(luaentity) .. ") " .. luaentity.data.name .. " " .. printpos(objectlist[i]:getpos())) else dbg_mobf.mobf_core_helper_lvl3( i .. " LE: " .. luaentity.name .. " (" .. tostring(luaentity) .. ") " .. dump(luaentity)) end else dbg_mobf.mobf_core_helper_lvl3( i .. " " .. tostring(objectlist[i]) .. printpos(objectlist[i]:getpos())) end end ------------------------------ -- end debug output -- --------------------------- return false end end return true end mobf_core-2.5.1/mobf/step_quota.lua000066400000000000000000000060301264104133000172550ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file step_quota.lua --! @brief class containing mobf step quota handling --! @copyright Sapier --! @author Sapier --! @date 2013-11-30 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- mobf_assert_backtrace(not core.global_exists("mobf_step_quota")) --! @class mobf_step_quota --! @brief step_quota handling mobf_step_quota = {} ------------------------------------------------------------------------------ -- @function [parent=#mobf_step_quota] is_exceeded -- --! @brief check if quota is exceeded --! @memberof mobf_step_quota --! @public -- --! @return true == exceeded false == not exceeded ------------------------------------------------------------------------------- function mobf_step_quota.is_exceeded() return mobf_step_quota.remaining_quota <= 0 end ------------------------------------------------------------------------------ -- @function [parent=#mobf_step_quota] remaining() -- --! @brief get remaining time this quota --! @memberof mobf_step_quota --! @public -- --! @return time left ------------------------------------------------------------------------------- function mobf_step_quota.remaining() if mobf_step_quota.remaining_quota >= 0 then return mobf_step_quota.remaining_quota else return 0 end end ------------------------------------------------------------------------------ -- @function [parent=#mobf_step_quota] initialize() -- --! @brief initialize quota handling --! @memberof mobf_step_quota --! @public -- ------------------------------------------------------------------------------- function mobf_step_quota.initialize() --todo add setting mobf_step_quota.reload_value = 50 minetest.register_globalstep(mobf_step_quota.reload) end ------------------------------------------------------------------------------ -- @function [parent=#mobf_step_quota] reload() -- --! @brief reload current quota --! @memberof mobf_step_quota --! @public -- ------------------------------------------------------------------------------- function mobf_step_quota.reload() mobf_step_quota.remaining_quota = mobf_step_quota.reload_value end ------------------------------------------------------------------------------ -- @function [parent=#mobf_step_quota] cosume(starttime) -- --! @brief reduce remaining quota by time passed --! @memberof mobf_step_quota --! @public -- --! @param starttime time this operation started ------------------------------------------------------------------------------- function mobf_step_quota.consume(starttime) local now = mobf_get_time_ms() local passed = now - starttime if passed >= 0 then mobf_step_quota.remaining_quota = mobf_step_quota.remaining_quota - passed else --mobf_print("MOBF: error calculation consumed time: " .. starttime .. " --> " .. now) end endmobf_core-2.5.1/mobf/textures/000077500000000000000000000000001264104133000162525ustar00rootroot00000000000000mobf_core-2.5.1/mobf/textures/animals_spawn_marker.png000066400000000000000000000004611264104133000231560ustar00rootroot00000000000000PNG  IHDR`zsRGBbKGDm pHYs  tIME XYtEXtCommentCreated with GIMPWIDATX 0 C;zvpUغ5D̒L `3{xk{3GH^F^> e( 泷1 , h:lV KxĮbd(+5`1'xE,ߚIENDB`mobf_core-2.5.1/mobf/textures/invisible.png000066400000000000000000000002641264104133000207460ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME 54tEXtCommentCreated with GIMPWIDAT8c?%B0j c=AIENDB`mobf_core-2.5.1/mobf/textures/mobf_alignment_grid.png000066400000000000000000000005711264104133000227510ustar00rootroot00000000000000PNG  IHDRL\ pHYs  tIME (Y}tEXtCommentCreated with GIMPWIDATxٱ @ A?lj@Bw&^m "c vegCIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_12.png000066400000000000000000000004161264104133000206630ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME$/tEXtCommentCreated with GIMPWvIDATxA EA0sX싰#;IMnL+8>^e "c vG4IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_14.png000066400000000000000000000004161264104133000206650ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIMEv/tEXtCommentCreated with GIMPWvIDATxA EA0sXþ;2V נ/2F ]k v)`cIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_16.png000066400000000000000000000004161264104133000206670ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIMEkn tEXtCommentCreated with GIMPWvIDATxA EA0sX“þ;2V _נ/2F ] v\56IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_18.png000066400000000000000000000004161264104133000206710ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME%д.:tEXtCommentCreated with GIMPWvIDATxA EA0sX[‹þ;2V  A_dK vvIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_20.png000066400000000000000000000004161264104133000206620ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIMEڏtEXtCommentCreated with GIMPWvIDATxA EA0sX[‹þ;2V  A_dǻ v>G+IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_22.png000066400000000000000000000004171264104133000206650ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME}tEXtCommentCreated with GIMPWwIDATx EQm^SpHNR[5<S~rL/}1L+ v(ЂIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_24.png000066400000000000000000000004171264104133000206670ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME-BtEXtCommentCreated with GIMPWwIDATx EQm^SpHNR[5<S~zL/}1L vK(IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_26.png000066400000000000000000000004161264104133000206700ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIMEMPtEXtCommentCreated with GIMPWvIDATxQ EQ0sX¹qn3Ijr`J]O֠/2F ] v_-IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_28.png000066400000000000000000000004161264104133000206720ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME AtEXtCommentCreated with GIMPWvIDATxQ EQ0sX¹qn3Ijr`J]O_֠/2F ]{ voIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_30.png000066400000000000000000000004151264104133000206620ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME7;=tEXtCommentCreated with GIMPWuIDATxA EA0sX"pNR;<[9נ/2F }~ vd\IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_32.png000066400000000000000000000004151264104133000206640ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME7a3ntEXtCommentCreated with GIMPWuIDATxA EA0sX"pNR;<[=֠/2F }p[ v'IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_34.png000066400000000000000000000004161264104133000206670ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME'# tEXtCommentCreated with GIMPWvIDATxQ EQ0sX¹qn3Ijr`J]_ נ/2F ]a vPIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_36.png000066400000000000000000000004161264104133000206710ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIMEtEXtCommentCreated with GIMPWvIDATxQ EQ0sX¹qn3Ijr`J]_ ֠/2F ]S; v93IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_38.png000066400000000000000000000004171264104133000206740ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIMEjStEXtCommentCreated with GIMPWwIDATx EQm^SpHNR[5<S8>^x}1LD v >|IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_40.png000066400000000000000000000004171264104133000206650ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME.tEXtCommentCreated with GIMPWwIDATx EQm^SpHNR[5<S8>^x}1L6 v|IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_42.png000066400000000000000000000004161264104133000206660ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIMEϪtEXtCommentCreated with GIMPWvIDATxA EA0sX[‹þ;2V 8^A_d' vIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_44.png000066400000000000000000000004161264104133000206700ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME'tEXtCommentCreated with GIMPWvIDATxA EA0sX[‹þ;2V :^A_d vžڳIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_46.png000066400000000000000000000004161264104133000206720ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME 'AtEXtCommentCreated with GIMPWvIDATxA EA0sX“þ;2V <^נ/2F ] k vIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_48.png000066400000000000000000000004161264104133000206740ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME ;7tEXtCommentCreated with GIMPWvIDATxA EA0sXþ;2V >^נ/2F ] v1OIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_50.png000066400000000000000000000004161264104133000206650ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME &ltEXtCommentCreated with GIMPWvIDATxA EA0sX싰#;IMnL+`xu "c< vKIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_52.png000066400000000000000000000004161264104133000206670ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME 4nE1tEXtCommentCreated with GIMPWvIDATxA EA0sX[싰#;IMnL+`xu "cެ vһIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_54.png000066400000000000000000000004161264104133000206710ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME $s!stEXtCommentCreated with GIMPWvIDATxA EA0sX싰#;IMnL+`x}1L vV IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_56.png000066400000000000000000000004161264104133000206730ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME ",!ItEXtCommentCreated with GIMPWvIDATxQ DA0sXL`aG$5U0쯏E0 vQ\IENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_58.png000066400000000000000000000004161264104133000206750ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME HtEXtCommentCreated with GIMPWvIDATxA1A m=B d;SvU5苌 vIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_60.png000066400000000000000000000004161264104133000206660ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME * tEXtCommentCreated with GIMPWvIDATxA1A m^Q=B d;SvUEl vŇTIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_62.png000066400000000000000000000004121264104133000206640ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME *tEXtCommentCreated with GIMPWrIDATxQ DA0sX r"d;Svr+5苌 vy6sIENDB`mobf_core-2.5.1/mobf/textures/mobf_lb_64.png000066400000000000000000000004061264104133000206710ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME *tEXtCommentCreated with GIMPWnIDATxA 0A Y 64<-b@"ch{f)IENDB`mobf_core-2.5.1/mobf/textures/mobf_path_marker.png000066400000000000000000002346531264104133000222750ustar00rootroot00000000000000PNG  IHDR+bKGD pHYsgRtIME  O3 IDATxn#Y$ŭDEQl~ȗ`s=oǾ_7s0nKEI܊Ls1diC23$XU]GPZ+$25;1[$yf'?#ǮW ~[~]X},#eȿKF3"׏?^o?'OUOZ-O=omWj|p={l}/Sá~q0ooz%G>I>?Ri,@jW98H>?z}*a6/h63f9N yey{ۗ\,iADr?v^yMEd,@rW?XM# >X|ɼW "R;xN?G?>lV*~_(!'͏^%x<ױ<= d<~~ XpE9'=-6jUVukPCSx=O~K'/Rp*D;p Xd~`!N_,؃p/_/xˉH^2x^E®(}_zzvt4 t'W+ʶ/HcbQdoϓLF%lwWá{{?Fx^{6?W>22`sx}tۼyyy:eR]xxޓ7s,#y~`3޼j>>=Vrp0R/BOGlVnD䧈<Ȅ o2de8<+ne(RBASjoL"ɉZ TEiο ?r`Hvۓә3iޤ^LE/6_,_(b`}냎-uL22ee6X8Їfӿ,T,,~z^VfL ?^q{+&3 f2xW >8:ГJMJ }%OEBDxG'#H]Uy{i//2^O3k EКN`צspI63Z-x$Զ<e{[ nW[`1HVKP8;ԓcOuO 8~}T|\_l`Ppf||ćЕ>[}[[`򇫏#/psr=ľl@ *= ѓgOj>~h4Dj5,S'Ɍ$}LDDNDXDj"RzD>j˾cU/m_`OmJ֜iz{nAFE ﯣkl3/ax6w2UB:#埫aK ߉=j+Cñ/iW{dޏ3Qc^-2z@6[l,LEt`= 8m*77Z;)lB5wZi#:>#-B0?&M;iژ;h77zwlXP,>ZL|a)?vG?2}OR^%IWޞ'lV23+ʼ??n|*wz+/-?WUjei4kNAZޛb~K?fu%v6>|>G&y]Vw{oD^d e@秞VmʶAЖ~4;V(+>A9;^\9=i77P_]iò6_b|>||G毯uUtEJt%@X kT҂h,u˹I1'n*v{}^ -ۣfz=]-w'>|eA-G+_Ţh܂Xhlj!|GR}VH]D@UDD$'z*+RLQM@]4ZKD6=vSCi #9Ҳ`sz~4x~?ZT|>K>2E9eHmi L>Êb.^ hmJIh9ۜHdUuz==irt>|c|*рdR=XAY!l=Pv.ϡ pT^O4ZHjJmGu >|>vՇ `߯ oIh gg-rv&k5 v$MңiI=>.6i|>|_z{ua?Zj5駥fϡUd5?9]^\\h_zN2>|Ǯ8A@ &T~1l6DӜšTXsz}kmN77OO8,O>|Ǯ. kOvbp_MB5M?R|sXlN#i|>,1lZb&n151$=$ i<^lNu}4ŧ9>|@$ԊaX ;X\^ }$Gф%kO%oo>|>`]@$48ŖAezEU5nw9ٲ`sriQ7'|>|6Z - =?_ -QG[֜46֚SpYpLG>|>(aˠ$4 >3&,- >|V Gĵ9M&.9&9>|#=>`#UIhpSIiqnN>|H Xl.. LB:fU2ei>|H X1\uiܛS@eG$mO>|>6^ ?=9qmN>|H Z1\54N)-i|>@b@ePMBH|>|χ}H>fS_z]R ~@|>|c3f9@bl[ K%l9>|#>\'"z_`0{2>|adAKS.c1t aZ>|#>@f6/|.Sn(H>|#=>AߜL&~0?w>j|R`s=iiH|>aáx2*y* D1CKBWIŁ4>|H{ ԉo쿱`apO59Fki|>s7tj4x*HMɬ.U1Ȟ47v@|>|GAHOE R ?;%i>|#]>noSWcQnWbX,T*5Ɩٞ48>|>>V`z~3 Mi=i֜]>|cw0^^R{]y*HmPZ;fղ4>|>vnp[1@n4nQ477:>|X[XÃKRa6*X =P4&iA>|:|@'8OD bhIh9 MBH>|atnn“]Hsh*>|>>þo ) lg6+R(Gt:],m2?՞4kJv]v >|>Yb6I @̆TA%}큊rYZ^SikN!>|>,RbV(hh~☤4˚M-J">|G20@ Cu1HvB.06ZM>|>5bk5MC;MDmM?-*IiV5'kP$S&rt|>|$s#0t4(ZŰV4nˠ2&xLXsri4-O>|>0b4VK/[eIhˠ%eʲG `Z-mH6)|>|obg'up$tY1LeҬ9@#MfS>|> l@K &V,@}-EkGѸ{6Z >|`!(id(֘ w }mNTv{~ >|>?VlK>c1L%iL6)RcK >|>?rpxOlUԓOW%77Ihɴ-ItY Rf_V#>|>`Ri~x*,aP3eQ46֦϶Zڜd>|`?|;ۛ/n&}JkNTϟPB!^ >|ܢU! $@qL~v[4;P_|>|v4mN6VښS#rv JE\D; >| ~H(S/K;?;M|><@wYZ'ӧژ~SeO/4>|`?-RѺi~Xܗfip#>|>[/IHܓ&iN|> h1O{-@fs~YmNbxsv>|>`IM?@Z7v,- >|2nWՕj'q_j.KI0>|@bx}=?%G,KmYZ2LG >|>/{&jOZ@m>|l駗,KLs4|>|6R mûePV ePqlNaiڜ >|>+@ӋsO,͚S>ߜi>Ҝ\|>|V$4 iΡu,kNQ$>|l'gmزUƥ9>|@bKi=i|2}~.h2֜' >|]HlOZ@|>|0R@[@|>|0K IDATRq M=i>|GI& r1d ^qiN>|{̋bLx" D1ܥ4i|>d؏K%g>X wi Gi>|#i>Y5D`U1 +iHNmS>=i>|ǺWo";OFZ wa MxMpYۜ֕N/>|W}XޞHfHY#Vu(E*P~pX-P @q.9 >|JEZ{^CXy| /`Yt;Ʀn;^֨|>8<9:-\N@J'<ƚӲsh7ٜ>|>τ1''" `_?$p'X1N>O4vi߻>|g|>mpx(4=oqԲ Yoc kN>|g}t:RI(,UO'-LG՜š2>|w}t:^Jy) O0jWr84'[>|_q~t`&Q/gg<&pV^oy1L(wY;ݓׁ4>|W|XS*|^?t@+"G2(~(6{҂iGќK|>ha  E~j<|1a(n1\'͚MA9;>|ы?4~N"`*O?,-F,mLL>||?v ?e8y~OB/.{5lV/˗ŵ9MzYsuO>|>n6RM&rv""WWPL?eP86'kPn*mEe>|>R<=ia_]i r>L⑄Z1W벴G|>|$(z"ww~A\V gx${{Z 瞶Z$t(7W az=ǯ>|>7R pN@eP6De-X2(+3-'qiN0Oz|o >|>?n*zweP.ai @ePH/t(KBة wwz.|>$~|~B//O(aŰPFCOBų9 {hnn=|>b Hҗ(B] upԂxzadh)^ٿ|>8WWHp8?4eP*OBk5-|utK lZqijq{[4֜, |>~)jT0Bkq+!(vIhrYޞ_ 7|eɆ~4>:ƒ^߇6i0|>|Dv,ӊ Q2(feFCɉC+ͦ.rPa9Y2iFꫫ!4I>|Ƕ|)o(X h~'hql4(JCޟe~Op" =j^OYs}m>|6|bэ[+M:sNO2(KByT6|Iݧ՚X[|}k0_܋4>|H`:]|؏ei=w%GGP駛*֜liY9Yx~?Z9>|;D|\.jU|~q 6aW]eи{T:^|>ر Ţ a::%R6 apڱ4h]Mg8>|qy$XԄVԳ9;t՚_t1;&^]4ڤT4>|>V K%-BhWM?-?{2}daOmN"ڤnnןSiL|> z1<="_ M-ei96?כ'Z>|@b?'v}]6\ 86`Kšϟb9Fˏ>|>k-7I[ uML- u>`sŅ]_i}9fiN>|l$ bX/NG*mIyQ4aŅ,ߓf[Ci|> x1tAP, @eP٬^Qeͩ]lN,-؜d>f >|CKBϗ/rai8Hch9 t>|@b MB3iҒޜ|>>`k0xI\d2ߜ›SpmҚ>|>6R (n1\?a MpO]4F|>t%2($4iV%IX|>Uƽ9 YvM>|#}>`#{Ө@C|>UOԜ2>|)[$4>||7<) m6큪E* >|>6lƑk.˖AŰTb Ͷ>|>u"6Z '--i|>.>O&T<r8Ce >|> df3b+(=uᶊbq >|>dIX1(Q/ 6'ۓ4>|}][1'Xr"OÏK1$t՞]H|>t@+vYszyӪi6>||G:|z?>z~0\]T+P؜vy >|>V?u5@}<vA,ŢH[ilYI N@|>|3>lF0)4ݓfͩe >|>vg. |"<ΡhV-K9>|cW|\^7 HY]HEsssh|>u?5< 5@f{KiFt|>|Ç 2яD-6ˠ$4oN+>|>LzvHQ\.< N?ݥ4q:߯i|>|>;o~f"H~I?߅(a&QIdޞeG>|a.f3R ZlJzT(U0T.>|1 *-fɉQ @ +IH9$BAo$rp|>|$s#08|#Y V4D@R)>{4fUseKҬ1j"GG>|G20@ NGrY _5_ k5MM *)iτ5'(FCё4|>|#  oMCmePF XR,Oq4҆ji>|> 7-vv@_qHB$ @Y,͚ 9:$Οm6 |>|V@`j1blTxYvќjs|>R*ƩN&bpؽdNOj>| oͦTʮ=8ä @ [ᡟLj!5$͜>| /~˖QE=tUzs3yLےZM5ڐ,n6eiJ|v (Nُb6.9PEchmlͩO|>B(adNOE:YK5.՜|>؁-Q`1L9ɴGk9/Ksh ei>|o)QKidij5M9u:"ggLΠTDeMڜ|>n $I9Zsyёht>|`?- qU*~2}z95l\H|>>'u Mp;Ǐ}i6&G0>|@b4=in2}||C;d8\j2i6痥T,7m7)|>|6Z mJӛqϡui9ڜ|> Xk1 .v^]{}eiߜd^oNDz #>|>[/PxͲdږU*t>|@b_ߏo2lNU* f:|>|Ɗ @ ~zyJʲ48.K>|>`#X1_e]6i>|@bjM??4Ҭ9)8#)ei>|lJB0&Zw ͲEB|>|Ƌ(=P~rц-K[um\>|>k+4ݓV'"&a)N{|> X{1ܕ4u: >| ei >| @t:ٓ|>|d2/C|>|μ,Ʉ'"O]H91>|ITy~p|$f >|>#{j]OD V44v{9ߓ|>|oO|<&dpҴۋGi]ݏ|>||G>NE^ onq ;b%iV%^f>|XbQR ) @(|>TDUժ>W*zߋE)Owiile >|C#rQkP $H }i9-;v >|Lsr"jiP@Jwjpt@=PHSo7v~|>||V BAHQA-~@6|>||Gh+@ b@Y54i{҂tT))Y(|>||G[u.!=$X {ve -Cs+>|>UHg2rvS@jhM?$ @ rh=iqH|>||Ň1cM<*B(~-=PqbIh'-8foO|)$ >|v{~F>RXg{cKve{Ҭ9Ԩ{h|>hOC lt*2 {8#ŠaTdx|>||χ }n Ha0CPC$"Z#fra|YZ\t5P>|as E@d)g+r{+ru8ۍS+^ZN?cs֜^^X|>|c> ӓՕ($I|~.K kN^q4>|GR}@;'rwep6G0xiEMB @rY|sz}A}9>|q1 ./@Zt ([eCTY ݂.bX:;B,}DAG>|I1 ß廉ww_b[UZ2NGb$0ΟA{w~ >|Hqg-84"V ? m4DNN$ԊQ<:FÃ6G|>|#> 4)}y= @b)[ ݥPZM-Z[-V*jN6YBeKў >|H@puIOXyj{r$Vw|_GGD@_>ζ57@cB>|HPrASP+rbǟۚ6~1,p'^֜lGCh,}h3>|G>`^O=m/ra6;\>i4h1l2(; v_p,&OOڌnn4ZB3D|>|lhK &bpwrɉF4ԽY'`ۣ?tI5'׆|>|lx0/( a(RѤ==?T-$4AeWT} K\9ٌkPW%ihH|>tŇ?YhsZ"zti;ŝ~BlaɖMuO[>|>M@gXQ‹ap @V5 @ma0~}%hݮ^v 'M>| {Z,#]"Ihnw8Mp*K<Ք=6#h|>HA35 IDATKBEM8k5M=m3NG_[ePMÏ|#il"ՕMs}MqAEN>|c}xK~ [.Cnk!tԦJ۟|=>6K mR77OOܩQ>|e^S-?~ŰbxpMs kRaO9?=< >|g"2}f}-vɎ>O벩RƱ[J֜~oNi4Z~$ >|>&"cȄ`0C7 ua%'QlNbsk0M7,> >| DUD?' RjŰ^_,U,./>h’ Y'͒鷷>|c}w_Eu$*`]@$48ŖAezm)9uɖLnj9>|cW}X0]k@&! B1$|2(FmYs4ZkNeib2>|H g<D[ ÖAIh|f MpYZқ>|>c  @A1 }47F|>]`UIhpSIiqnN>|H Xl.. LB:fU2ei>|H X1\uiܛS@eG$mO>|>6^ ?=9qmN>|H Z1\54N)-i|>@b@ePMBH|>| v;|T.RO?e >|>~N&"|>[.IK@|>|LJ$ >Z u9T4 >|G:}=5&]eQD@blʲsObl iH|>tp?j(^%>j|Ҵ`s=iiH|>a^ө^;Cy* D1C+@|>|χ-Ec^2OD;X {Ҭ9i4]H|>tz} € 2ՅvP*ٓ&]H|>zzpf8@`6i4x"HUb(IlN<>|QP\N'"bX,T*5Ɩٞ48>|>~^V;Px?7 H@wO5n4>|H.͍c<_<ΡhV-K9>|#>./"~`G@hn7(xC|>|nv} X =Pbڥ4QO >|>p(2LCHiCKBePn@7'>|{?d'i u[r$48tZJ|>O}RlVP)Ï>tIHcCPi2?՞4kJv]v >|>Q@BA AVW/J@U0T.>|!4T 6"''"ggF5%8&i Ͳdh >|>_jt~] s9|1!LͩVi|>d `ZMNGQeOKJ@U-IT>|1 : e-||1452 ?֜ܣh GGj|>$ `槣6 ˖AY2(+`IH>rVKRM >|FHPiu:8_C&aʲei֜l ё&vlh>| oXԥOzP+Qe{>Ϣh=TS|>|0 xR?4Np2IkL;6'ۏvzW=?>|`|+h6RvGı&mJؒ}MdT %i>|`|98<]*ɧЛ$4PdږjԆdt/KTJ>|@4w~xmð(ww(Fkg[-mNvx2|>|RFY >%x 5'ۏvz*ZR}xt|>|nQaMm?Z|~YC[(/K>|>?|;NQ]H6'HSimͩ9;ejvm"R.k|>tQ?$ MUͩ򗥝ӎthE>|`m@\we ,RSmL?~ͩek岿'ݗ@|>|?ih݁4?~,K4q8>|ä Is4Q>|l'=P˖A} M9,\6b19mI>|@bhP$Ԧ {L]|>|ZapT蓸/Kkd%|s$c|> z1ģh%Ӷ,RO>|Cw=P~|`sZ'R߿l 6i|>|6V mJKTR}9qY>|>)6Ŋ2(+2864_mN>|U{lq^fͩ^oN4iNQ.K>|>`+pU4aкi5(j|>|6^ W @ 36lYڪshҜ|> X[1ܥ4z}>>?t4kNqړ|>|ڋ Y'a >|>) vu -Kc >|>8IΞ4|>|iS6|>|d2O]H91>|IzA OC,4#4i|>4"J"<*XUŠnҸhϯO>|>N٬^OC,0^<&,mNJ~|>|+>I>??W!7"nq ;b%iV%^f>|XBA] onZU m[ wq M^>|W}"U(?8P_HqxOHchL/kT>|W}T*"zj~| E`G'<ƚӲsh7ٜ>|>1FC}N'"T`gp:?>ilԯMﲟ>|_jͯ R,{eP˦2f)9>|aaLau OD ;eӤI &Q5d >|>sh+@`1 ڕ4ơ9J >|jsv7|^@hM?$ @q@h=iqH|>||ԇ]''"GG"ժH&?wv@+"G2(~(6{҂il"j)$ >|w%FyX)OC o{cKve{Ҭ9U*hNN>|>>g0T*o>D e NE&`qT~dYZڨS0dz<>|>]|xHe0CPC$"Z#fra|YZ\t5P>|a.YDSnWVjqJV~ d M9dV|>|>Vmð9I xJSPw{d$Ԋa>zT\5~_/8|>|>@ݝ_,Iޞ๧A7 Cei"UC^O|>|09jỻ%P77{l Qf1t ʊa:L a3?4NF3Ã^z->|>b?ME=Pá j0, ( ^iePEӸCk(vp8ݝ߫ 5|>|# > _(P>>(`X1,499P+GGlNv?<0mFڐ|>d`4ҤE/;48%Pn1tBhk6 j!lbji&>?krS[l)>|q !)p~iˠ3OmT.7jZHH+v1&&Ӷ֟`h9Y3>| R2` j0V.CPv[f/=n˚ h6}?u%ӽm4`>|XAePn1f?'Ó-VMC]uv?nse4I͍&WWChcah|>m }SuU!dQ ?Nn<9hP6ý?>DZLc{ǽ.Id|>mŢE! E V*tڹzj1eP>lvuJ Oa5'` `0$-mi|>Nt'Ѓ-zSKD4-uO7U #9Ҳ`sɳi ~`s>|>v02 +\x1 N?(ժ&m= &ӯˎqt>|c@sOEvutK5 m NcigvRFq >|>)oIh gg-rv&k5 bv$MңiI=>.6i|>|lJZ mSЮv[Q~Z*md2šϟڜ./E..I?=sFN>|@bxzE6Z -Xsz}kmN77OO8,O>|>`+p_O+l**ql5?bsI|>|DZ y-nn1tP뚘Z}4/6 ~sӜ|> HjŰ^_,U,./>h’ Y'͒鷷>|@b.rXbˠY*˚S؜lYZ9ɴ}̨>|>-/_(?p8?ƦZs .K(>|> 0lFg%9>|c}֊a蓸6d9݅74ڤ5'|>|lPb* .@4=i66 >|KBePaPIh\ҬJ, >|+={s H(I>|G|ƋG@ٹQс4?6 >|[+Ʃ9e >|>6R ( I(i|>nE* >|>D$i`Mp2(J Vs>|Gz}|+=iiH|>pDy? G.c1—4>|Hd,UP @Yv[ U $i|>wS e˶$_ @bP^lN'- i|>>a2k:k4;OE(bhp՞]H|>áxy<֫\`apO59Fki|>^">[0 U@&ЮcJT#{dݓi|>^OO f"OD R ?;%i>|#=>?>j0jˉ\]D@R EJeز4ۓF4>|ǯ|ߋuw6GiHI2>|qs忹W`k`9vͪei4'|>|$?|A(@HY]Esssh|>u-ݮCRa6*X =P4&iA>|w}E&]?iH) bhIh9 MBH>|q{}Lr< .pX.@8CkW4>|>QT?V@͊ "r'<ilJ9M&ړfMɮ=ˎ>|>>þoBHYP(h!!(ePIHs}@ ֜eC|>|c=>D4BAFCDlӨ$ Y֜lmT98>|>9b{U-|ίa.@6r9?)9j">|>__ih"j{li=PIH9Y%i֘j5#|>|# @@՚/q[4gš{MH_my>|>ػ֕4H")JqծrEExȳ{^u=:s^:PgQŃH|dn$!$?"Ң[HDxI~%4fӜ VBe$Ov4o 0ͦ!5&xxi `P[ IDAT0z|= Ф0 (ImidivvL%?kxxqpp|`;LSnh% @=%Ekq /v4洵xxqpp̡`3Q*p4 APn }ɮG;<4d xxxn*eO>I`uS +Ӈ&eCjlK5<<888>] O{6e'NZ4iےe֞JmiZ:xxx@2v~"ø$mEchmlinNi<<<ퟃߟHApaP؛]vx(J榩L9xxG 0:f9Ž9iܜNN&>d[xxx䟃h겎<Ҹ7'He*t|,65m&U֛xx'TB2lmi''a{Ύ [Ѹi<<<ퟃcnLH㶥jaeܘ~oNmZ פ<<:j M4 4H-<<ìDפ齽@eW<<vn7y TRTivw'NN7ӢoRxxxA[C鼭V+4>neڽ9mii9xx6{3k>I{[Zޜi+ӍͩXRa<<  /.&PMReڶjexxxACw 5PNz+ћӴ5i@EV<< 6%.,\ܜؖxxe0(v0lƛS\ goN><< @}OܖfoN)H3imixxxA`!JhiuinN˨Pxxo D@u:IkKmZnNxxxA`nai5ideD:>6鸛S֤xxy IZv|L xxqpp` 4-@<<<X*ggMxxxpppp|@ ۛSxxxDM<>><)f͉@<<aMJ\ϓ~E?Y)nUVwk}o<u{S@CoCղ]si,V\i<f!]mKsh5ii <<Y=칿/HRj癿wtČh>3iceAGAwwg UR]kks+Fa`4Izz' /z~~Qgz=I=A_SW:. e FV @EOb+kҢ46u7hKxxx|ĨT̺Rɼpl~D? v V/EAIejם}OFCIF>5'ۏӋ^^^ xQ`*i qQ`8KQ ·kp0 >(`&ޜjtܜM<<z [|~?͢}E?UpXښnoQnEO 5=k4'=II㏽w>FQ@1`A1Jv TOgiKCS*m+>xxx|ĈNM7w~Qͦ~$+k0hssMʚL?M Š9o 2xxxlLKi+iOATryWrCچu55k/ivv ZT*y}y^&;ׇ)&DG<^Q-1EpW<_ ݪU ۠=UCmmj4f3l:>6iZC{]=hǛcaxxYH_;_m/Ij*%vT(XܖoRYƆyڿࠨ<5vvMR~}L}{}x3d:-^%mϒ%+\p=.W mvw{j4:9y09X_ÁC3ަ.c -P;d=5a4nowwxxHKǓmIB)i_+VbѤW*kKj4|5x:>t|,5vwZ5v\IE@.l1bv$Hz%$L!RU*;P/-?sy's}O(hrC^7@le\|6\`nR s=>'xxx#6w?hO`G-iCº5JeU*j66 '{-Ox}hK(X yގLGƁ C *ZT.~FF۷:8/wl H!`\8?n`N7tmPv T8Y 2^xz|_l޲nܰ [yK-./üa/>[yy<<z{OI]WUbrJz֖3o6BLaU-?x|L/+Kj.|Oʑ[mlhsZ;;Wj4.h\k{^GGmj6;ioO!-ކsYmP*K%s1UaTDww0+{']|nҦ [<;~4!nlz=S<<exd{_QRyoNyK.3bWTT\P=<o43>r}Z( 򼢊E\jmmCڎ;ǏK]igZ7nU(ܩ^o4[h|aU"ېEAa0Z >ָ vPknjɋo7~Ľ/g-z`nF M<<Eyc5OBmyޖuy^EBI//\Tx=mn`w+6n~}*_emmUP߿έ*kU**ZK](@tD?~<ѣ' ml܋n awU}^~O4o58wwv۴؋Ϯ<<Exk (4' 1<$U(ƓMb}.#ZLJ*]=Uf, 9 [|~6dMt͢i<i˼Ft},ssmFcvp[AM\4lC7<JIr;.sSv'7lڦm72<<npCod<ҟxR ~*vXv.Yim7Y<<ȿb&S>{U[Iw*b=wF`KSu::;ӟ_2JsO vt듴ޜɛUMŇxxx,n}y=G\'Ʋ1n XS4mR7t{{kuZ_џ4Hs_8bO.f]scOw͍MLŇxxj{,n_VPҏS&faOƼшH{_VlxAPh+ z~CCww[yMWWzxEGS?Eػo<H3򖅶<<+f"maR;V*,xxz|72Gy*x:ݲ^_3x,/t0LU <-_ ,k0eMZ<xx:9Lq5H4N6;_x (Y99<<3ݝ)tP,Jٙ&`. my>L[ xK mkҢY <<Ks^]}}06i*-]f <<#yj@>8[\<⑚"0)C;&yps<<#ggaVi!`ߏOGfo.x^P㷢iҵ&xxx,-KL>O򈶙Gטg)`.Xz]($cmS Ͳ6xxxUkZ}39:;6/I'=hc#osi>"@542*L6mJJw$f><<qilc< +XJ'JmjZ_^i?y? Z岩loKmi_&mTz<<<ϹǓɶ':-p#~^(aŷ%5xxxvIi7<գZ5&coob ss}OrQꚪ ^ֽtpF?U(g-S9>6v mTҳ*K4Ӱ-m֖xxa[}=7Kjt|n0mî3iW>$KsxdFQUmn6uﵻ^G}F#I}" AVlN[["6D׈v6Ύho<`~z|= $4<milˍM5_<<y{ĥ^&o\x?3?~tywݭjg,l^WZGv_#h)@?o+=|Y x{mڭ6‹xxƧMoH:yvݿz=c{xllH<G<<]]hmA҃G672]3/3Q*5i mCij xmyp0rxxɴtL_?M__.H{Ov}(+@rQHz2ϴW2~]2tWd?ΣV3ao6ý淶̓N4N6|}Ж4RSpeAGì$aiܜNN&lF<<< d&igL[wlfPXVA&/sxǬK2b i%m89~oNmͦmxxxEo|*U5˺Y=bL<0*y`" n)Vk<@?ޮ4i؂<<ȻGR;I_.[X^`62mvˮxxg7 9_>=+Ƹ{٣ە:]xGt)p=.< Dž- xv@%y=!bG6xxG=d .s1h[[y}eO6r}ొq;{I2K.W鎭V}hʴ{srzs<<#Mg)yض<5Hlf .hJ I1RolFcXRa<<Ln@h'Fbc#Rm.[~jCwk摇J&-<<I)M7<<SneFx,feMFG*dvxe0SJܚ4C xxy 4}cu=̎AP+v yz};(xx-ٜ,̒fNxt UIz}mj8`_`,lxxNZ-9<ȢѨѨu64eW[\ 1gZnNxxxQ]ʌ9<Ȟh~M//z~[^|5P<<#fӶ^$`<P./Oi<<ȶ-,2}O{U xt:O%ܬovZ@}-1 xxx|&.,iFR16qG<0tOEZϑNO+y98xM{ޜ<<<דͨ-4g6*sx1Outyr ʹ[{xxHJ3w1Fh&O*ȬBx|)uzVf@6U xXuxxǼ<2c4|.p߻U驧VuA[eS`2t<<xop?!'q/~9fZwߜfxx_i^fۉ0, *00on0-Ixx_ 4[&n~n1~i<R'KLsN_㑖tGʃxxymeW00c<| iv0|b'XVC{ixxxGwn}z,{Jzz}5 ` ]Stڠ&<<lM6㶖Kc'+ C]q m_G S(3CRTZМ}[<<>|wg>>=M\rloKowv҉m=55wwxxxGcôaGVu./i[;<<I;^_34y3xx7cm-z0NX[ =}mG Ǐ{w`˹9%GoNa`i>Zz4xxG­豵e&|<<|Q*-<<<#nİ{G'߱<x,_* ٱEoIV4qӰ"\a+=<<Uyx,<2WVqPl%4.! $ض#VzQu<<UK#͓M05Pi G@1<<<рeL6=i0:Ӊa'r]<k}'Vu<<U]Ӝ9<<<<<-d#sx@Z'Y xpi~xҤa F<<WB M>I)M7<<k~د}#sx1/sp{$>;!V4Qik.ve<$AFxxypv>='ͮDz'xc׾gRҵWCi0^^ޮ)j{mPixFxxxG&q[˥yz}j8) Nҳ^/ vʂ&}-L`ImPi CsZnm<<??7UwM`J g` y<<z-ͽdգP%`<#p8ROIk|Kz*)uuUGtTB}`Guk<ikl09oo{xxx#nU88Hwy '2]pvf~`k^ʴZk lP,q۠,#}|^hVxnoywg[<<i4:2Nw5n$_k8P>>|wg>>=Mw\rloKowv҉m=55wwxxxUhժٙ8>6m'dZ_Ɠd:&'s}hpVz= y7Saڰmu:洭xx$Ƨ-x xy1syا;;Xt^x#P=KIJ۸p''Ð^ϼh;67Ѯ('+{o/a'' /$OF#h6ÉfaZ5>țk#T_E+9[yp{.#ZХdGnrpx0;0whΤ?MẎ^f<jIO<{}Ǥ'B_e2C9Shê}QJ6Pxöxlo<6{~Ľ/gضJÃZs~6ԡ76i<<<>Ov<1IKڑԐT`E{vly%橲Mݪ; w[&t܌}<7N'l~<ѓ'vJޞ3]pߟ׷MAvݝ6-ۮ<<y㉀m>O6_/]^GtrTYʻO<ܥy}౪fIO$_}Iˀ൚}OONyxhmJ!v0&)doN6c??a|yl񈮵YtxxysImI?+O_607& 286Y>-1kDd`8|;_I՞zݠ駶"cAn]!I(ŶIn (6xxx|e2k=I%.Rl࣓zJo=q-yHǪyżeVvЋ0~j66LŎ_,∾iv[w[I xxypO2ۣ&XTyRi6%[fsrt>-Da,jTcU<@R1v}jOо~$x}mΤpkd2oxxxm#~O(Ɠ-`p)@0 irepE8cm-Xw}w/`hww vS|Vz.. ϟceHC xx1O_ƸP癥&˵֖TZG6ɦ[w/m+=v}n`m' wXx`x}=}zK<<<8N2] EAI7ds==ьl sm_^.&<<Hfi89InreGKv'lŎyto+=8<<#h*z: tq#U>VZW va,46_|xxx ~RA-uz~^W]MQ~ra1i\N6WX=ok_L#ѭOzs &oNWWtǬ]|xxxG^4\𰡛Z :=tzZP#cفqkWX=`7/bO.cvt=561xx+y^i;~yyVxvvuZuvVٙ{ڒÞvy^xL$=IOPmIYVB xx-=nΪ:=⢬K*l S(𰴡o@uL1# IDAT <<_w/u׿V jo= /3s:10Z%?I-Im\/O[WinNHxxxRٻF-=Կ}KF%U4D 31t:L# Lֱ[I  +A@4icllXSܼ//EZhq*bq~Y?'[g> :`+Nc_2=AzЃoy>1e1ؘ,8xT In_F/fVG( h,h2=AzЃԩá$DZ8&Gs~s#:oQ?,~}_2oiDzЃ=Ab)!Շ{' I:#:mkqOڡЃ=Az#ru]ө.6mb~Mi<H7囓R =AzЃ=oЅGi|#,asA#6 @ x0oQb =AzЃ=ߐT?VJڤ+dLZ#V @u{< <>F =AzЃ=a>uc$`Usa>=Z*6>葨s m쁊˛IKc =AzЃ=CiLhl߀F\'Ͳ-f8ԓ1RlJxIJ߼Jޜ޼7ui =AzЃG=d4&0/Ӿ Is昬4Gl0G!s3\p=ia{<HCzЃ=Ak0-O& 50bs8L8lX$)́4=AzЃHpɌHGG_ljl;`.ci\Hd.!-42!{I ܠ=AzЃ=>ӓH72{oMCKG"!Ҙ{c =AzЃ=ͧ0}Z siXH|Hs Ck;y͉=AzЃIO̓n lr~ ր$8]> ,@^/(xIzЃ=Az|K2~{Gn~ T@@J1s$̥a}#Uſn-+[nGiN[Ѓ=AzЃ_C^>RȟFේP l[!W+~}}c]?_o"oq/W~l @1ǠHg~=AzЃ=q__tGGm_u(5RsXVC>o\PQ?iGi8<7LJM,Y?@8)4=AzЃ=豉5FNdvw7(5RX}|Rz=VɉٙZ #sI]9QE/q.u@Xb'#4ʏ=?՞Ytm}mÎ=AzЃ=>?Iw{WɩeSd2dސ P,vP7lqvŅ31sǺ45)w"(_7r9 %!(cI >p]7Y|"Pу=AzЃ_0l],7t!1ɼ_P,Q8EyEeqzjX^WK{]SEE__7J%ny}T7$mr @BzЃ=Az|c:~k rCd(^P7prRFGAd!W Ip۞y=pgp y pX@V5lx|f ¦cS0uݦ=AzЃ=0p0PQ.QzA ^霢lѨX,P"`Y J)my}z8>ެ9K7׋@$ƹ co^ݞ3.{ḍP$Ҭ[|eF^4=AzЃG2g('8:z:>:_lhTXlP*2P_.$}f}Tz_ևtfͱxl/![4ƋA8;ӈŢ7z]w6@A1[y?0C/7~]c TǡhvdI Hf=AzЃ]u]?qLffsBa~m@zYeZv? pH<_n@>?T*;)A߿M9jlќWӃ=AzЃ|\ul`~B8({Y>*6>*qtGkøxFO`R(,P" I\rY/>osr_ =AzЃ=葀4iE6_fcEqb4E#>iNru!tPO7 "/x3LZJM%nJ{lz#7bBzЃ=Az$ 鬒iuiܢmaiYr"FIYp|ꠞ/7j5dL$uNOPΛ^嘍JEġFzЃ=Az s@? x7 Pay[GʹsHwr❄Q \.^{.su4ā=_n {CaGD''% I۔tv[9u7zЃ=Az#Ec46FcF6_#o}r_y߁EavQSm/C1Nz ysh''ٙwtk5ݙ=AzЃ=i_oVԧ$RI7d}n!1bdLpwb6s0uG ?S&*h0(3;ӲoNci=AzЃ=X# d@OtOs(B ok@6Y].c;J/F_.~|xDF@s؍td\㝱Y*yݷ9у=Az="j`u@' @:$`u-ZLbԮy<]9 g u9t(u = = M:I @Y$hC:o2l9jNӃ=AzЃ4I#7 0= ( <PPؘ,L& BisLbqu}D \[OO9 1f!s]no5 ECgE3I{5R%9?..7VKIڦGzЃ=AQ4.9)yQ ,5\)c>c>u`#<_nĵOjύHsqFip#=AzЃ=FxQ=Aބ8t8m8NI XM>Zo)L$;7F=fືpݻExqROh C0nδ)/=AzЃ=葮Fz[@^ +]8Nq1^^xx8MEps>ͱdևYa8|dr%\ soo+1~?a4 6vΛiQnЃ=AzЃ)h,¶0>b>x|^ WWY\_gpscQ%b}GQׇGa5ӿq +jQK3Q7P:=x{ Z3m9ciq}s=AzЃ=K &xyy77Sظ* xzT?ZQՃaЁ3?;^? H;^_m&m,h,/LHzЃ=AzຮL\뫃^ox~v 6I)?dL昹>غ1z8gv1L-(?AzЃ=A~, A]%T\m ]#, =@; Bs`ݘ=PIR-hOZB=AzЃ=^?<0WT s٬ᰅ6޺xy9):oa2`4b8`8<```p(| [sl0[p\( 91 X: ` 8F`@9Ey=AzЃ=V f:f.L&N0UVV`P{n?yſ./XP2/% p(MES~sFmtsO<&oNǁ9Ey=AzЃ=TFu-]FO~~^2z<^^rx}1dna:U.ևbk+>}Bo@h62 =``zHvd7u$Ca9ũFzЃ=AHfoEFd2OO18o2 x|FgU]%bϿ}-W*\F@hBo 8=nm=x91 `{]JI;jcs6DzЃ=Az$/ olօq - Q+KL^ V#Y òt#@*l٬i?1VH_qys=AzЃ=U?p_0b>oa2b8,cjQk8Riu[ F#Fci9'@ fkT.Q⢇pcR* 6viֽ91=AzЃI Sœsf Fe EoF1O( F#|eTJaY%vlJZv5:= pq\Wg$`f@n0=AzЃq0]x:S?lVhTpˋGk!ͱ}䣯}6RPB&c#!+R^vNKw{xxXຉ^ C ;gS9v=AzЃ=MmqF]O]i`>/c2ˋ{-&9յt=>> R m+rYjyZ%UqrV52^9 &@x74tGߺ=7}s .=AzЃg=,+͗D]{O2,ΑS8N StZxphEA'q}CcG VB>o\V-vs8;+nf:*l\oo=T*E`l ?>@umɵ=AzЃ=kkK)_}S~\\:R^t 8NY㱍@d_P fj?hZk~U*)4 Bpvin%4U5u (UD>  Y⺛$#!YoN҃=AzЃ_I>,̀\N?OtQѢ?n @yi㱅@Sx|LOƬpIs y3 sk̡V+P(#@2,x=x@;#L?H.pc7*zЃ=Az+2f^.Ţ6 {0,^^8>b:m!;oYz?Ѐ9f_lm}|t.ׇYwvF:W ggzRV3(sd dpt`eu@@AH.pcoN.PzЃ=Az#R 5ՀJE|6s#f'p (\w@)up"'x0}>Ͽ5aNƸn3eBA3,,h\#` nM{e86n.fׁ߰=AzЃ=>lktWX\*Z.ﰬ.tT~86f3)i>:4&uIJYWgCKū&qk?/HPovzd0foLAoN=AzЃǦWO_>p2 IDATZp"\7 ׵0)j<}׶Ռ'Q(46-S^{IИǺtǴn3_f=AzЃ=YNgn6W* ӿWCp(+P*׵S`~Ȧ<ߵ29E Y#.0VMyG{<՛ zЃ=Az+Hs TeFlPNj)ufǾ7PWfQ砟Ŧ `/Ix0<n1=AzЃ\7:S(?}tYn|yy̜#944e|gh?OdC@`Z#.if׍=AzЃ4cbQ~џOOW~Aci0Pk$?zmW?|?M|Kib_]yؽ^L=Qn-`6z'121>摈&PS;z5׋O|4' {{,=AzЃg=mu,k+ oI)6ُaƯ[G6^^'Oua0ƣ#7M4AzЃ=Az|#lODQǺLe{l0acnظ}vxtBm[cu=7@7a}zz->zЃ=Az[,v㝉aA]x7G7.oR[Օ0i{=q{C;wޤ_KC=AzЃSſxRCMy(C[n(+xB ;o߼s1}cu{$A< Ơd;I0-A{??>j 9zЃ=Az#"O8YfLjŤ{rޓffLև/c_M ^_$(`'_(__?gm51 0l*v'ݻOO:lV/'AzЃ=Ai¼Ӄ=AzЃ{el:`4R?y4GbQ~ێG<f3 Qoo<=y_ocMOO~)՘#Gz#r3 [|fҦ-^].y}6nЃ=AzЃ{0,ҢgVgVi89&3Qx( z+хpp\d" h? ^*N{z~_''[<ӓzؖM+A)$oN 8ȍ;7AzЃ=qp22eCfẙzH|\ևuO7 A.9f g}00^4ml1QT4~*fSw3qW!a?ibKydƿAzЃ=AzLZps(o=붠T@7IfW=| lv07>Sl#+Nǘ}&z+nD BdoS xVu_ @ȸa1nqp=AzЃ!yx#g͢'(V=w>Z&=d|^q^MEv,C%t,W;=:w{=r,^Ow58b=AzЃa<"B8U,嘹4zru;pWE V;[u^OO3Y1}C:rßysџW`7zЃ=Aztz ˺,w,-C񐆌ƾͰƘ zCa>up\z 6؅ƖݮW?)v{GGyJ(gn><_S7у=AzMz$Cq,ZΡ'P/.oN*C[EqX|=AzЃ8$?pW-78E8Z'#Obbsgei}#V;Rl6c }$zGф e!, zЃ=Az~_-nkc60=Ibs\#i]X>c[ȱtGNOv-c~lE׍=AzЃ/#Ȫp F#]'#(]#8Hιv7CA7(aF(1h=AzЃ=C601AyĽ6 \Ǹ9у=Az#RG5p*)F"C{<>HB=㣭Kya[Ȱ@PiAzЃ=V) pSMn 9fce YzT<7``=wJ'4=AzЃw y߂R6|^lx\`ˋW%`X H+ɢhBlQJX>L?e =AzЃ=豍BAA -nyQx\[<>*ަ#`c΀9z$#h[)ū@NX wv3 2oiDzЃ=ACO ]qJF#A//<=YV [9zCd+@TxCH כaJ =AzЃ=uB׵8cP߷psR4]sHJB'ϰ>!W ? xf}>@zЃ=AzZcZx7/,asA豫 *P L |;*)'_n|6%,F~4=AzЃ8DP0w|6i&zM2Pjf/ޣX|D0?Ukm ;(t;>!鹼\Hj =AzЃ=4KGsA]~I^CA) .!kXv9!jlzЃ=Az=Vǚ')`ا.mZF\D.wl:P ʓ-+HSJ~ zЃ=Az<~у 22c(Նe5.7_\P1tGlLzЃ=Az#>c}!`(y e`Y (u n/2 s}ԙ=AzЃ=nFA{(d2(R5nmd/, OdGP:!ss.<$Ro `PP%aoN=AzЃؖc]î<gIüv0Gzmrs__aߌ~:f.;aoT=AzЃ.<>:}ag!3zЃxxЧ H.aoN=AzЃ=Ӽf9ẇ0Gzc0&+P`|LGú@]<~=AzЃGP>4c&yl0GzlcьOfgAƠH77'zЃ=AzAf(n̯mY aO699zccL ֥;-ai?@zЃ=Az#fdSуXmǟ| Q9z=AzЃ='t=a/_< =Ǻ8Ѓ=AzЃ*P=e>i.tqJzOd/V;==AzЃ=!f\3q6CaNuנ@^/>鎂јGXCP#^~|s=AzЃG<&cyOC1Gݮ0nؒ@Aͮݝ~h=AzЃ=kyfStD>^ͦTol3A:=[@&;=AzЃ=,tz4GbQ~ۦ=ba/ewy:]h4O]oCnAػaf-^]?~P޾X7fAzЃ=A}z][]H~"P}yؖCF<4vyHy.w`u'Ejd=$ّ=AzЃ؇:1,قQʒ0yT=#á7Bm;-N5 @UǼ { {>}=!زO=AzЃ_5d<"r9܊Azd?/t'G==?ׯ-cf=lZ&Q7'X [ }yÿc߁=AzЃ5dd8,Oz|ZGuCXRѨftDM 2Pt]}!,]Hc>h xt3NϾv^ʣK?۱]<(ؠ=AzЃ=>*nc]ȱfidy>rVq)ӓNO%;ggc:ttă?F??G}=AzЃzkHq㺇! 6 z#6؅ƖݮW?)v{GG x|xп>. ŅjiJ%70x`uznn4oGoAzЃ=AF)(4z뺨sd#9=葊@tzd ;tzPAo{szxXƾ\Ïx=AzЃ=7CC3HLf4b_^z/a8[GzЃ=AzMTl'1ly3A={ؑbͰ;f#i\;&syyx{<3EAzЃ=Azا =葸N"Р8tz°{Ulc(n=AzЃǡybm`11(f~0h9A?1vz=AzЃ=b}+aG;xu؛?$oN}0?1i=AzЃP<&1Ĥ=$^ jc=GzЃ=AzlhTzh=yF##sLcZcaI@YI =AzЃ=63`.c>i]l҃X|{;47à=AzЃHGzIcq =x{nJ4rsDzЃ=A(,MMb$!`4-k mPiAzЃ=qx0w~YFLl{K MPd 곝PЃ=AzЃ/n0gzۆ9z#չ t{sSЃ=AzЃ01g=#E}o62e ͉=AzЃ8u !y~UP|=iHCzЃ=Az?i<VZ 8:M^b`[Q iAzЃ=AC`w\:?zDjb8:B(a0*C =AzЃ=0w|6i&I zc͘Sh6?N`hP0iAzЃ=A̅Pjy #ؤ=v!2 m쁊q>@zЃ=Az# \Ya zCC G4ggz:c 4 {f ץ;-=AzЃHc\O5c#́Ca4@ {i;7u(Q 7#́4=AzЃGXlGtb m$f@zЃ=Az0fNzc i @1OwLJ=AzЃ=fу+iRJ{=Ѓ=AzЃQz)`cQòi@sOtǰ19у=AzЃHbd.OzDa_^/xHzЃ=AzG9$у1Weqci @:oAzЃ=Az?9zc s *,ݑ(6?Ӄ=AzЃϘ|iAzK7LfXAPt\:=AzЃG=mHr=lVȲ\(>=I@ Nj ˶ ;=AzЃHGAy/HJXAPnnx_+GEV=AzЃ=#h#&= \NwN8=x {-ɎPЃ=AzЃcG!`0Z_/=-hز&`@MzЃ=AzǣX&q|Y3sW=˱q|뺻pv%cB|@%)eˈ@@IzЃ=AzˣZc!#$)`ff6*^1s6v8:׽ԟe܃=AzЃHG_~Vk8LbHC& sƯzIlK: K')(aXJEyġFzЃ=Azl!NjHё.d$<üRO@sxy$%`/%oz gPXދ ?; J>~蠣$QmwzAzЃ=A$x[02|tyr{=xaߘENj[1&'Tҿ? { =AzЃa>-:*&P_h$4c䌙q}<ϋx?%(1i2FzЃ=A4{H0>aQ tZRjŭs(i7n_BOBOPP~yC18b|)=AzЃ=Åˢ0]Yɨ9zq]Yb3 n+iwilC//u󘇼95<|e=AzЃa&TvQgagq!{ac2YWl&y}Ѓh]ˢ g:=I :6,|sS=AzЃifУE VRe(UX rYz9dTrxi^8D ;6>1&'6,!c7=AzЃ8$iEPLΣPQ*Y-+f ͢*6Ӽ>qAg)G輏Bo@sOZ9?_#N{AzЃ=q2 a3(UD&SG6[C>_BCf8yӣ0=;avi ۓfvzHCzЃ=A{O#ök((h4h689pvpqz\4P=6!d |@zЃ=Az$CPJRzf''yظpvfUka0`nwf$]i xHQ=AzЃ8l3 !Q8:FZQ''5pv[Y4s{Zac ;Do@7'ˊϛ=AzЃ=XTǗvt8;䤈N'VBP*)a}JbG1<6@iֽ91=AzЃI**?Nn۽iNnQSy 0Qs5HՀЃ=Az#R9..qz5k8RY~q̷+,|s=7ѻYj_wxTgYU*U B*I/`G)uɓYCZA\@>ΎT*1 0'}m0űeG<$VZys_ui MܹlXTō=AzЃXG( ƧO.pp0@A|rB|L&t:qzMϽcm+DIDATc<,i ~㼺k 7a M5}M{8EzЃ=Azs\~yMZ 6J2vv٩qHR^򟂵M6{x[̅Cc=aa c`mƴ0ØH3247=AzЃ[<GLtR ?萱k Hp*R2 p\轏+lÀ?`]ke꿵'0+=F*@`֓@ٖ|q :=AzЃxG*%6K?~zgcVPPP1̛=\wr9]!xa\3fLzCJA.\ |_a)Q,?N?Y{=AzЃ=^=c-d,sy,d \oY@GiH 4㱴`\~}?0)Q؛x|qڸ᧟ƯMnyp] bw:1a@03=AzЃ=1GbR$=};[;W^vĿZ6/>0#oWǧʫ[' qңmPq9f/LQ/N=AzЃsRxi4JE&2>D|"@2 %ޙ3=!nYm0Iƌam~|xA.7}_5fy̚iYbp=AzЃ=]`_ڛuE [-֎!nAV3NK^Q`"@ɼ02]D-Øế˜sz?<=|FC bP*G췟pLrNm+ͅ$Ge{W;0oHt./(4}ytumPx$efFzЃ=AzPhF.'.!3̟E~l"@$hM: 3 ?=1~8+K`v:?~Ď㱪qhAzЃ=Ay=@J@>/ws"@pv #nGT|:Dz=f'@T?^d/mG`8Og%aF8]w2AzЃ=Axۚ+lVV ,lG8ٌ:Z.I1XG")<-ت_ ~-pqan2*`G{5!imi4HV+x.AzЃ=AG@3"%nOO ãJLIؚ_ ?~^`/.|ryhhA`b˓{<I pM+nWWrur CzЃ=AzcEd,fyhWҎܶXGb)U ljx6ZM86(ߡ.OA{q!;у=AzЃ x.4%\_gqu6k+Řuhuy$:ׯe'nWW՞zߟn4|zC_]ɰs + n=AzЃHGl@/W,m)G>/ƤLJgM6 y$>vh%ߗKu xXEG;Q*CjIG |:ד Vmզ/zЃ=Azc^EU?p~EƬ67bjzT*ɌPrMxE/SHà ONw\EGpH&+lW!-R|{g7jntaCzЃ=Az#SE+&.%d93kQ{{~YыI^o3#ֺ0&ɿRkUm*Bg2mIxa{W M A 3=AzЃ*<^Sxz"@#Em3KFf<wO?%\cK֯(XvGqwhG.~x6 =z?`iA*k77򄨕!?8=AzЃX[?p|1勿ϼݖdS?>(GInTdUY'<=c#k=r#Ir?,1GӇẒ;N)-/y-=nC>C+ͦ@yjO~Oׯ OەooFOЃ=AzЃXT>c\p_,>`m&z|p9#=#,ȢCcc]c?nØD'RT 蹧GGryh'gk+>UQ'8ɖ^eܠ=AzЃvz,/#|etoyu--Z)&Q:Y>)ٽ*Mu{ØߑJ[R)wJ~FCA(DW  >zЃ=Azco-.>O]ׅP"@@"@G&3Gyd2نU${I c$~'຿Øcd#>}JL/UVvS'SP.K'nY[+Ӄhs%=AzЃx*`/Jw2"YG07yBt0fc7A֎<\ƴ1`4 Mduy(xt,+=˪FpMxK>kS&N#6AzЃ=A*~?Ӏ!Mz`@ @ޫ%z/YQ?IK_+=;;RVɎ^m:tuFxٙa;у=Azc{=޻'V* [=v/ЂLxƲ< c[]Mq: )FE: [LW_cr;Z-֫ՒV{rO> `+Ny|?~z? 58us7zЃ=Az&P8{uD%z`屳{,;6#*_X䪿tt`+ +\if+$6_m'CRiOqi4vv&??o~CzЃ=Az,%XO`{n8jUJ8::147?ocuѫ@KeqBQ,c&9_] [+=Cv[+=Ve`܋dLC=AzЃ*=޷`EY" p\XL{u{ IOWWbg2mJǏصTVU)̪h%4jC*oqw}=|v`mU7zЃ=Az#!E &.Guylݪ yŷA W)|7tڦ_~gZzЃ=Az#Y7tdF1HzmX` [kvXqo4ᶛu>zЃ=AzlG ˜qJEZI⶘`-QX/'I}qz||qu >zЃ=AzlG sth!#dK@@Ɣ`LE:ΎbՉꁘQ{%>LcK{ Ư/0π JZnf_xύ^=7:m3GzЃ=AHhn$KaL 4JՑNQ,QlL1բ=>o]Zkqq+]BVCV7~ @ y xX4*ovCzЃ=AXB@L7:CscJ"C>_E\@Eё >~4hJs6#8/86xc}| _Ku5!i/NQ7X=7=AzЃ>5*`@ƴJ] B>j88Ç,hRh6T"͚D&Qy'luz_ѫWX(𰪷yҜfR_AzЃ=}kZuV$Aw%>#&Uq&xT*ʛh_3|0p=AzЃؖB`z}L 4h6h4P.Xl"!qJ0&c2R,?*>./%=>31Y J$KϽB&Oۖ/8ZdS.Lx<+ ﭅Q(&m+BfV'+٬@[-ۓU*W}$jvCzЃ=Azli!.>E>!*J+(ΐ@:@& c:7G.1qɿwKu)3jK{ommf|(S r!JO8Z+ʟ}HuW|=AzЃX?'# ;@.wR s׭!>rr0& _u'۴ب珏I?;o^ @q~LCoX H$QIENDB`mobf_core-2.5.1/mobf/textures/mobf_path_marker_item.png000066400000000000000000000066621264104133000233100ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYsgRtIME)# tEXtCommentCreated with GIMPW IDATxo\ו 5,(Q$A$S(K$K 78,Yw I Y&,;DEQ,DS qEsŚwo/E*WwwW뭋.,Q͇׹u6AwkkDCn]j =Є}۶Hd|UyG训Μ<*j-ϞPWR"76h@wnyN5!LQ) N:7}GXVNhf]_`v&?`mXXԚ[[oB,6[[%AL]o/Ps{y(CXyX8V[*,hh ==5dV8Cq44PijB #l0pC!])_q6%\q&b1a< vtP6MR)H@`|Ԅ bܻD44cru Lu4*A͡yDKˢ@89;Ds3\Q_mTP \eQ55N22EX֒t ʊ20chc`@{a|I|\{B :wqT.zd0ʾ`xp8}0J!y*lm@  BzehmE6,X_GR@jjK (p.d2H%d_Ufw7&`LN½{hL"ӄG F}N{_uQ +úscj e 6A._.k>qY!QOiz+? !i̩)\!N@BB\VVPAC044hݰH$bcUu`a|NC+ܿ0  T6À&r{{F~_ s]*a?#DHw~{^g~V>E.B;J(Pe9=2<|uiA)p&rnDq{MܹhkDB[!jkQN!PYU_/G8vm-kx`ۨHCPzzPlct/ֺ`i  eGG *F&^[blVexX ٳP 44 wMBc)ԻjY[( <U.J?Iu[,jT GZ Jauv:|?ATw7V'vwtN&/.j- ŢVuu)޸ )e2x">loQ4 t5h`@I?xmy"D6K1yMguקTҢ'T Q(@ "I,"Cb)—?eS(8 <A)Fq E"ýk,ɾ>(KI)Ix+] QN榮& c~{]Ǘ !bo{~G]e]]Y]$-j@*bU#U5=JRA?r>O0B`Y\&&toU{0%\g\#Q@Ŷv#6b~\҂8~tZ| *WR)JW/\FJ%*VWtMpf1|{x{pN>S," ߶MMڪݻ:B7oCc#Pcd: J! `*f}}rusǩ/ux="@H SS: 67áC;w \/`l CJ\4Q݈>X\t#t\(B)pv s)`&ޱcxPB48A,.",KwOJa*u k|q@tvb^$W.#De2/^!jjtҼ0>9|OX]A7hkʲP&DW>D0"yǗ,8tt%}=G*r]]DNScSSI15婃w._u? .0+;{z\ܿ (]_8wԤ/^ VW''K\-͛ GwjܛRoD4PTkn/_ɱ`PEe-\X@lnj1ѣ3H$337JopZ[W dzL?\xD#zzLNƆY!|>/C^Os-ziK&Wɣbe%ah7Xjv:Dbɞ}X<2Ғν^4OӁZl]! @77GG?tccc[o]W[=#/?{BIENDB`mobf_core-2.5.1/mobf/textures/old/000077500000000000000000000000001264104133000170305ustar00rootroot00000000000000mobf_core-2.5.1/mobf/textures/old/templates/000077500000000000000000000000001264104133000210265ustar00rootroot00000000000000mobf_core-2.5.1/mobf/textures/old/templates/animals_template_128_4.png000066400000000000000000000014541264104133000256740ustar00rootroot00000000000000PNG  IHDRsRGBbKGDH pHYs  tIME*%G:UtEXtCommentCreated with GIMPWIDATx S,:{PHą;sOվns @  @@ @ @  @@rywr` @ @ p~@ @ @ @  @@ @@7IENDB`mobf_core-2.5.1/mobf/textures/old/templates/animals_template_128_4_2levels.png000066400000000000000000000026251264104133000273310ustar00rootroot00000000000000PNG  IHDRsRGBbKGD pHYs  tIME!~tEXtCommentCreated with GIMPWIDATxщPyor|jSϧlo @@ @ @  @@ @ @o9~xa~ @@3w~З?o!~ @@ @ @ @ @wf3o @ @ @ @  @@ @ @x h~ Pv/  @@p~@o  @@ @ @  @?34elIENDB`mobf_core-2.5.1/mobf/textures/old/templates/animals_template_128_6.png000066400000000000000000000014561264104133000257000ustar00rootroot00000000000000PNG  IHDRsRGBbKGDm pHYs  tIME)»oAtEXtCommentCreated with GIMPWIDATxA C!DQ<\7ݺ*& ]uoaf @@ @ @ @ @ @ @ -@~_Rnl`~ @xY w~ o  @ @ @ @  @@o\?IENDB`mobf_core-2.5.1/mobf/textures/old/templates/animals_template_32.png000066400000000000000000000003501264104133000253550ustar00rootroot00000000000000PNG  IHDR sRGB pHYs  tIME~tEXtCommentCreated with GIMPWUIDATx10@KG Na @0@` 0a @a 0@ ā@ IENDB`mobf_core-2.5.1/mobf/utils/000077500000000000000000000000001264104133000155275ustar00rootroot00000000000000mobf_core-2.5.1/mobf/utils/data_storage.lua000066400000000000000000000055441264104133000206770ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file data_storage.lua --! @brief generic functions used in many different places --! @copyright Sapier --! @author Sapier --! @date 2013-02-04 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @defgroup gen_func Generic functions --! @brief functions for various tasks --! @ingroup framework_int --! @{ ------------------------------------------------------------------------------- -- name: mobf_global_data_store(value) -- --! @brief save data and return unique identifier -- --! @param value to save -- --! @return unique identifier ------------------------------------------------------------------------------- local mobf_global_data_identifier = 0 local mobf_global_data = {} mobf_global_data.cleanup_index = 0 mobf_global_data.last_cleanup = mobf_get_current_time() function mobf_global_data_store(value) local current_id = mobf_global_data_identifier mobf_global_data_identifier = mobf_global_data_identifier + 1 mobf_global_data[current_id] = { value = value, added = mobf_get_current_time(), } return current_id end ------------------------------------------------------------------------------- -- name: mobf_global_data_store(value) -- --! @brief pop data from global store -- --! @param id to pop -- --! @return stored value ------------------------------------------------------------------------------- function mobf_global_data_get(id) local dataid = tonumber(id) if dataid == nil or mobf_global_data[dataid] == nil then dbg_mobf.generic_lvl1("MOBF: data not found, id: " .. dump(dataid)) return nil end local retval = mobf_global_data[dataid].value mobf_global_data[dataid] = nil return retval end ------------------------------------------------------------------------------- -- name: mobf_global_data_cleanup() -- --! @brief periodic cleanup handler -- ------------------------------------------------------------------------------- function mobf_global_data_cleanup() if mobf_global_data.last_cleanup + 500 < mobf_get_current_time() then for i=1,50,1 do if mobf_global_data[mobf_global_data.cleanup_index] ~= nil then if mobf_global_data[mobf_global_data.cleanup_index].added < mobf_get_current_time() - 300 then mobf_global_data[mobf_global_data.cleanup_index] = nil end mobf_global_data.cleanup_index = mobf_global_data.cleanup_index +1 if mobf_global_data.cleanup_index > #mobf_global_data then mobf_global_data.cleanup_index = 0 break end end end mobf_global_data.last_cleanup = mobf_get_current_time() end end --!@}mobf_core-2.5.1/mobf/utils/error_handling.lua000066400000000000000000000030571264104133000212340ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file error_handling.lua --! @brief code required to do error handling --! @copyright Sapier --! @author Sapier --! @date 2013-05-010 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- -- ------------------------------------------------------------------------------- -- name: mobf_assert_backtrace(value) -- --! @brief assert in case value is false -- --! @param value to evaluate ------------------------------------------------------------------------------- function mobf_assert_backtrace(value) if value == false then print(debug.traceback("Current Callstack:\n")) assert(value) end end ------------------------------------------------------------------------------- -- name: mobf_assert_validpos(pos) -- --! @brief check if a pos is valid -- --! @param pos to evaluate ------------------------------------------------------------------------------- function mobf_assert_validpos(pos) mobf_assert_backtrace(pos ~= nil) mobf_assert_backtrace(type(pos) == "table") mobf_assert_backtrace(pos.x ~= nil) mobf_assert_backtrace(pos.y ~= nil) mobf_assert_backtrace(pos.z ~= nil) mobf_assert_backtrace(type(pos.x) == "number") mobf_assert_backtrace(type(pos.y) == "number") mobf_assert_backtrace(type(pos.z) == "number") endmobf_core-2.5.1/mobf/utils/generic_functions.lua000066400000000000000000000525071264104133000217470ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file generic_functions.lua --! @brief generic functions used in many different places --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @defgroup gen_func Generic functions --! @brief functions for various tasks --! @ingroup framework_int --! @{ ------------------------------------------------------------------------------- -- name: mobf_get_time_ms() -- --! @brief get current time in ms -- --! @return current time in ms ------------------------------------------------------------------------------- function mobf_get_time_ms() --this fct is overwritten on init with best timesource available atm return os.clock() * 1000 end ------------------------------------------------------------------------------- -- name: mobf_contains(cur_table,element) -- --! @brief check if element is in table -- --! @param cur_table table to look in --! @param element element to look for --! @return true/false ------------------------------------------------------------------------------- function mobf_contains(cur_table,element) if cur_table == nil then return false end for i,v in ipairs(cur_table) do if v == element then return true end end return false end ------------------------------------------------------------------------------- -- name: MIN(a,b) -- --! @brief minimum of two numbers -- --! @param a number 1 --! @param b number 2 --! @return minimum ------------------------------------------------------------------------------- function MIN(a,b) mobf_assert_backtrace(type(a) == "number") mobf_assert_backtrace(type(b) == "number") if a > b then return b else return a end end ------------------------------------------------------------------------------- -- name: MAX(a,b) -- --! @brief maximum of two numbers -- --! @param a number 1 --! @param b number 2 --! @return maximum ------------------------------------------------------------------------------- function MAX(a,b) mobf_assert_backtrace(type(a) == "number") mobf_assert_backtrace(type(b) == "number") if a > b then return a else return b end end ------------------------------------------------------------------------------- -- name: DELTA(a,b) -- --! @brief delta of two numbers -- --! @param a number 1 --! @param b number 2 --! @return delta ------------------------------------------------------------------------------- function DELTA(a,b) return math.abs(a-b) end ------------------------------------------------------------------------------- -- name: mobf_is_walkable(node) -- --! @brief check if walkable flag is set for a node -- --! @param node to check --! @return true/false ------------------------------------------------------------------------------- function mobf_is_walkable(node) return (node and node.name and minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].walkable == false) end ------------------------------------------------------------------------------- -- name: mobf_get_current_time() -- --! @brief alias to get current time -- --! @return current time in seconds ------------------------------------------------------------------------------- function mobf_get_current_time() if type(minetest.get_time) == "function" then return minetest.get_time() else return os.time(os.date('*t')) end end ------------------------------------------------------------------------------- -- name: mobf_round_pos(pos) -- --! @brief calculate integer position -- --! @param pos position to be rounded --! @return rounded position ------------------------------------------------------------------------------- function mobf_round_pos(pos) if pos == nil then return pos end return { x=math.floor(pos.x + 0.5), y=math.floor(pos.y + 0.5), z=math.floor(pos.z + 0.5) } end ------------------------------------------------------------------------------- -- name: mobf_find_entity(newobject) DEPRECATED -- --! @brief find entity by object reference -- --! @param newobject r object reference --! @return entity object reference points at or nil on error ------------------------------------------------------------------------------- function mobf_find_entity(newobject) return newobject:get_luaentity() end ------------------------------------------------------------------------------- -- name: mobf_max_light_around(pos,range,daytime) -- --! @brief get maximum light level around specified position -- --! @param pos center of area to search --! @param distance radius of area --! @param daytime time of day to check --! @return highest detected light level ------------------------------------------------------------------------------- function mobf_max_light_around(pos,distance,daytime) mobf_assert_validpos(pos) local max_light = 0 for y_run=pos.y-distance,pos.y+distance,1 do for z_run=pos.z-distance,pos.z+distance,1 do for x_run=pos.x-distance,pos.x+distance,1 do local current_pos = {x=x_run,y=y_run,z=z_run } local node = minetest.get_node(current_pos) if node.name == "air" then local current_light = minetest.get_node_light(current_pos,daytime) if current_light > max_light then max_light = current_light end end end end end return max_light end ------------------------------------------------------------------------------- -- name: mobf_min_light_around(pos,range,daytime) -- --! @brief get minimum light level around specified position -- --! @param pos center of area to search --! @param distance radius of area --! @param daytime time of day to check --! @return highest detected light level ------------------------------------------------------------------------------- function mobf_min_light_around(pos,distance,daytime) mobf_assert_validpos(pos) local min_light = LIGHT_MAX+1 for y_run=pos.y-distance,pos.y+distance,1 do for z_run=pos.z-distance,pos.z+distance,1 do for x_run=pos.x-distance,pos.x+distance,1 do local current_pos = {x=x_run,y=y_run,z=z_run } local node = minetest.get_node(current_pos) if node.name == "air" then local current_light = minetest.get_node_light(current_pos,daytime) if current_light < min_light then min_light = current_light end end end end end return min_light end ------------------------------------------------------------------------------- -- name: mobf_objects_around(pos,range,ignorelist) -- --! @brief get number of objects within a certain range -- --! @param pos position to look around --! @param range range to check --! @param ignorelist list of entitynames to ignore --! @return count of objects ------------------------------------------------------------------------------- function mobf_objects_around(pos,range,ignorelist) local objectlist = minetest.get_objects_inside_radius(pos,range) local cleaned_objectcount = 0 for i=1,#objectlist,1 do local luaentity = objectlist[i]:get_luaentity() if luaentity ~= nil then if not luaentity.mobf_spawner and not mobf_contains(ignorelist,luaentity.name) then cleaned_objectcount = cleaned_objectcount + 1 end else cleaned_objectcount = cleaned_objectcount + 1 end end return cleaned_objectcount end ------------------------------------------------------------------------------- -- name: mobf_mob_around(mob_name,mob_transform_name,pos,range,) -- --! @brief get number of mobs of specified type within range of pos -- --! @param mob_name basic name of mob --! @param mob_transform secondary name of mob --! @param pos position to check --! @param range range to check --! @param ignore_playerspawned ignore mob spawned by players for check --! @return number of mob found ------------------------------------------------------------------------------- function mobf_mob_around(mob_name,mob_transform,pos,range,ignore_playerspawned) local count = 0 local objectcount = 0 mobf_assert_backtrace(range ~= nil) mobf_assert_backtrace(pos ~= nil) local objectlist = minetest.get_objects_inside_radius(pos,range) if mob_transform == nil then mob_transform = "" end local objcount = 0 if objectlist ~= nil then objcount = #objectlist end dbg_mobf.generic_lvl1("MOBF: entity at "..printpos(pos).. " looking for: "..mob_name .. " or " .. mob_transform .. " within " .. objcount .. " objects" ) for index,value in pairs(objectlist) do local entity = mobf_find_entity(value) --any mob is required to have a name so we may use this to decide --if an entity is an mob or not if entity ~= nil and entity.data ~= nil and entity.dynamic_data ~= nil then if entity.removed == false then if entity.data.modname..":"..entity.data.name == mob_name or entity.data.modname..":"..entity.data.name == mob_transform then -- oops we don't yet know if this is playerspawned, -- for performance reasons assume it isn't if entity.dynamic_data.spawning == nil then count = count + 1 else if (ignore_playerspawned and entity.dynamic_data.spawning.player_spawned) or ignore_playerspawned ~= false then dbg_mobf.generic_lvl1("MOBF: Found "..mob_name.. " or " ..mob_transform .. " within specified range of "..range) count = count + 1 end end end end end objectcount = objectcount +1 end dbg_mobf.generic_lvl2("MOBF: found " .. objectcount .. " within range " .. count .. " of them are relevant mobs ") return count end ------------------------------------------------------------------------------- -- name: mobf_spawner_around(mob_name,pos,range) -- --! @brief get number of mobs of specified type within range of pos -- --! @param mob_name basic name of mob --! @param pos position to check --! @param range range to check --! @return number of mob found ------------------------------------------------------------------------------- function mobf_spawner_around(mob_name,pos,range) mobf_assert_validpos(pos) dbg_mobf.generic_lvl2("MOBF: mobf_spawner_around param: ".. dump(mob_name) .. " "..dump(pos).. " " .. dump(range)) local count = 0 local objectcount = 0 local objectlist = minetest.get_objects_inside_radius(pos,range) for index,value in pairs(objectlist) do local entity = value:get_luaentity() dbg_mobf.generic_lvl3("MOBF: entity at: "..dump(value:getpos()).. " looking for: "..mob_name .. " " .. dump(value) .. " " .. dump(entity)) --any mob is required to have a name so we may use this to decide --if an entity is an mob or not if entity ~= nil and entity.spawner_mob_name ~= nil then if entity.spawner_mob_name == mob_name then dbg_mobf.generic_lvl2("MOBF: Found "..mob_name .. " within specified range of "..range) count = count + 1 end end objectcount = objectcount +1 end dbg_mobf.generic_lvl2("MOBF: found " .. objectcount .. " within range " .. count .. " of them are relevant spawners ") return count end ------------------------------------------------------------------------------- -- name: mobf_line_of_sight(pos1,pos2) -- --! @brief is there a line of sight between two specified positions -- --! @param pos1 start position of los check --! @param pos2 end position of los check --! @return: true/false ------------------------------------------------------------------------------- function mobf_line_of_sight(pos1,pos2) --print("Checking line of sight between "..printpos(pos1).." and "..printpos(pos2)) local distance = mobf_calc_distance(pos1,pos2) local normalized_vector = { x=(pos2.x-pos1.x)/distance, y=(pos2.y-pos1.y)/distance, z=(pos2.z-pos1.z)/distance} local line_of_sight = true for i=1,distance, 1 do local tocheck = { x=pos1.x + (normalized_vector.x * i), y=pos1.y + (normalized_vector.y *i), z=pos1.z + (normalized_vector.z *i)} local node = minetest.env:get_node(tocheck) if minetest.registered_nodes[node.name] == nil or minetest.registered_nodes[node.name].sunlight_propagates ~= true then line_of_sight = false break end end return line_of_sight end function mobf_line_of_sightX(pos1,pos2) return minetest.line_of_sight(pos1,pos2) end ------------------------------------------------------------------------------- -- name: mobf_pos_is_zero(pos) -- --! @brief check if position is (0,0,0) -- --! @param pos position to check --! @return true/false ------------------------------------------------------------------------------- function mobf_pos_is_zero(pos) if pos.x ~= 0 then return false end if pos.y ~= 0 then return false end if pos.z ~= 0 then return false end return true end ------------------------------------------------------------------------------- -- name: mobf_air_above(pos,height) -- --! @brief check if theres at least height air abov pos -- --! @param pos position to check --! @param height min number of air to check --! @return true/false ------------------------------------------------------------------------------- function mobf_air_above(pos,height) mobf_assert_validpos(pos) for i=0, height, 1 do local pos_above = { x = pos.x, y = pos.y + 1, z = pos.z } local node_above = minetest.get_node(pos_above) if node_above.name ~= "air" then return false end end return true end ------------------------------------------------------------------------------- -- name: mobf_ground_distance(pos,media) -- --! @brief get number of blocks above solid ground -- --! @param pos_raw position to check --! @param media table of blocks not considered to be ground --! @param max_check_height abort looking for ground after this number of nodes -- --! @return number of blocks to ground ------------------------------------------------------------------------------- function mobf_ground_distance(pos_raw,media,max_check_height) local pos = { x=pos_raw.x, y=math.floor(pos_raw.y + 0.5), z=pos_raw.z } local node_to_check = minetest.get_node(pos) local count = 0 if max_check_height == nil then max_check_height = 32 end while node_to_check ~= nil and mobf_contains(media,node_to_check.name) and count < max_check_height do count = count +1 pos = {x=pos.x,y=pos.y-1,z=pos.z}; node_to_check = minetest.get_node(pos) end return count end ------------------------------------------------------------------------------- -- name: mobf_surface_distance(pos) -- --! @brief get number of blocks above surface (solid or fluid!) -- --! @param pos position to check --! @return number of blocks to ground ------------------------------------------------------------------------------- function mobf_surface_distance(pos) local node_to_check = minetest.get_node(pos) local count = 0 while node_to_check ~= nil and node_to_check.name == "air" and count < 32 do count = count +1 pos = {x=pos.x,y=pos.y-1,z=pos.z}; node_to_check = minetest.get_node(pos) end return count end ------------------------------------------------------------------------------- -- name: mobf_air_distance(pos) -- --! @brief get number of blocks below waterline -- --! @param pos position to check --! @return number of blocks to air ------------------------------------------------------------------------------- function mobf_air_distance(pos) mobf_assert_validpos(pos) local node_to_check = minetest.get_node(pos) local count = 0 while node_to_check ~= nil and ( node_to_check.name == "default:water_source" or node_to_check.name == "default:water_flowing") do count = count +1 pos = {x=pos.x,y=pos.y+1,z=pos.z}; node_to_check = minetest.get_node(pos) end if node_to_check.name == "air" then return count else return -1 end end ------------------------------------------------------------------------------- -- name: mobf_above_water(pos) -- --! @brief check if next non-air block below mob is a water block -- --! @param pos position to check --! @return true/false ------------------------------------------------------------------------------- function mobf_above_water(pos) local node_to_check = minetest.get_node(pos) while node_to_check ~= nil and node_to_check.name == "air" do pos = {x=pos.x,y=pos.y-1,z=pos.z}; node_to_check = minetest.get_node(pos) end if node_to_check.name == "default:water_source" or node_to_check.name == "default:water_flowing" then return true end return false end ------------------------------------------------------------------------------- -- name: get_sunlight_surface(x,z, min_y, max_y) -- --! @brief get surface for x/z coordinates -- --! @param x x-coordinate --! @param z z-coordinate --! @param min_y minimum y-coordinate to consider --! @param max_y maximum y-coordinate to consider --! @return y value of surface or nil ------------------------------------------------------------------------------- function mobf_get_sunlight_surface(x,z, min_y, max_y) for runy = min_y, max_y,1 do local pos = { x=x,y=runy, z=z } local node_to_check = minetest.get_node(pos) if node_to_check.name == "default:dirt_with_grass" then return pos.y end end return nil end ------------------------------------------------------------------------------- -- name: get_surface(x,z, min_y, max_y) -- --! @brief get surface for x/z coordinates -- --! @param x x-coordinate --! @param z z-coordinate --! @param min_y minimum y-coordinate to consider --! @param max_y maximum y-coordinate to consider --! @return y value of surface (first air node) or nil ------------------------------------------------------------------------------- function mobf_get_surface(x,z, min_y, max_y) mobf_assert_backtrace(min_y ~= nil) mobf_assert_backtrace(max_y ~= nil) mobf_assert_backtrace(x ~= nil) mobf_assert_backtrace(z ~= nil) if type(minetest.get_surface) == "function" then local basepos = {x=x,y=min_y,z=z} local offset = max_y-min_y local retval = minetest.get_surface(basepos,offset) return retval end local last_node = minetest.get_node({ x=x,y=min_y, z=z }) for runy = min_y+1, max_y,1 do local pos = { x=x,y=runy, z=z } local node_to_check = minetest.get_node(pos) if node_to_check.name == "air" and last_node.name ~= "air" and last_node.mame ~= "ignore" then return pos.y end last_node = node_to_check end return nil end ------------------------------------------------------------------------------- -- name: entity_at_loaded_pos(entity.mobname) -- --! @brief check if entity is activated at already loaded pos -- --! @param pos to check --! @param mobname name of mob --! @return true/false ------------------------------------------------------------------------------- function entity_at_loaded_pos(pos,mobname) local current_node = minetest.get_node(pos) if mobname == nil then mobname = "" end if current_node ~= nil then if current_node.name == "ignore" then minetest.log(LOGLEVEL_WARNING,"MOBF: " ..mobname .. " spawned at unloaded pos! : " .. dump(pos) .. " node: " .. dump(current_node)) return false else return true end end minetest.log(LOGLEVEL_WARNING,"MOBF: spawned at invalid pos!") return false end ------------------------------------------------------------------------------- -- name: mobf_random_direction() -- --! @brief get a random (blocked) 3d direction -- --! @return 3d dir value ------------------------------------------------------------------------------- function mobf_random_direction() local retval = {} retval.x=math.random(-1,1) retval.y=math.random(-1,1) retval.z=math.random(-1,1) return retval end ------------------------------------------------------------------------------- -- name: mobf_pos_is_same(pos1,pos2) -- --! @brief check if two positions are equal -- --! @param pos1 --! @param pos2 -- --! @return true/false ------------------------------------------------------------------------------- function mobf_pos_is_same(pos1,pos2) if pos1 == nil or pos2 == nil then return false end if pos1.x ~= pos2.x or pos1.y ~= pos2.y or pos1.z ~= pos2.z or pos1.x == nil or pos1.y == nil or pos1.z == nil or pos2.x == nil or pos2.y == nil or pos2.z == nil then return false end return true end ------------------------------------------------------------------------------- -- name: mobf_is_pos(value) -- --! @brief check if a given value is a position -- --! @param value to check -- --! @return true/false ------------------------------------------------------------------------------- function mobf_is_pos(value) if value == nil or type(value) ~= "table" then return false end if value.x == nil or tonumber(value.x) == nil then return false end if value.y == nil or tonumber(value.y) == nil then return false end if value.z == nil or tonumber(value.z) == nil then return false end return true end ------------------------------------------------------------------------------- -- name: mobf_hash_to_pos(hash) -- --! @brief restore a position from a pos hash value -- --! @param hash to restore pos from -- --! @return posistion reconstructed from hash ------------------------------------------------------------------------------- function mobf_hash_to_pos(hash) local retval = {} local raw_x = (hash % 65536) local raw_y = ((hash - raw_x) % (65536*65536)) / 65536 local raw_z = ((hash - raw_x - raw_y) / 65536) / 65536 local mobpos = {} retval.x = raw_x - 32768 retval.y = raw_y - 32768 retval.z = math.floor(raw_z - 32768) return retval end --!@} mobf_core-2.5.1/mobf/utils/geometry.lua000066400000000000000000000226631264104133000200760ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file geometry.lua --! @brief generic functions used in many different places --! @copyright Sapier --! @author Sapier --! @date 2013-02-04 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @defgroup gen_func Generic functions --! @brief functions for various tasks --! @ingroup framework_int --! @{ ------------------------------------------------------------------------------- -- name: mobf_calc_distance(pos1,pos2) -- --! @brief calculate 3d distance between to points -- --! @param pos1 first position --! @param pos2 second position --! @retval scalar value, distance ------------------------------------------------------------------------------- function mobf_calc_distance(pos1,pos2) mobf_assert_backtrace(pos1 ~= nil) mobf_assert_backtrace(pos2 ~= nil) return math.sqrt( math.pow(pos1.x-pos2.x,2) + math.pow(pos1.y-pos2.y,2) + math.pow(pos1.z-pos2.z,2)) end ------------------------------------------------------------------------------- -- name: mobf_calc_distance_2d(pos1,pos2) -- --! @brief calculate 2d distance between to points -- --! @param pos1 target position --! @param pos2 start position --! @return scalar value, distance ------------------------------------------------------------------------------- function mobf_calc_distance_2d(pos1,pos2) return math.sqrt( math.pow(pos1.x-pos2.x,2) + math.pow(pos1.z-pos2.z,2)) end ------------------------------------------------------------------------------- -- name: mobf_calc_direction(pos1,pos2) -- --! @brief calculate direction angles from pos2 to pos1 -- --! @param start starting position --! @param destination end position --! @return anglexz,anglexy ------------------------------------------------------------------------------- function mobf_calc_direction(start,destination) mobf_assert_backtrace(start ~= nil) mobf_assert_backtrace(destination ~= nil) local xdelta = destination.x - start.x local ydelta = destination.y - start.y local zdelta = destination.z - start.z local distance = math.sqrt( math.pow(xdelta,2) + math.pow(ydelta,2) + math.pow(zdelta,2) ) local anglexz = math.atan2(zdelta,xdelta) local anglexy = math.acos(ydelta/distance) return anglexz,anglexy end ------------------------------------------------------------------------------- -- name: mobf_calc_scalar_speed(speedx,speedz) -- --! @brief calculate scalar speed -- --! @param speedx speed in direction x --! @param speedz speed in direction z -- --! @return scalar speed value ------------------------------------------------------------------------------- function mobf_calc_scalar_speed(speedx,speedz) return math.sqrt(math.pow(speedx,2)+ math.pow(speedz,2)) end ------------------------------------------------------------------------------- -- name: mobf_calc_vector_components(dir_radians,absolute_speed) -- --! @brief calculate calculate x and z components of a directed speed -- --! @param dir_radians direction of movement radians --! @param absolute_speed speed in direction -- --! @return {x,z} ------------------------------------------------------------------------------- function mobf_calc_vector_components(dir_radians,absolute_speed) local retval = {x=0,z=0} retval.x = absolute_speed * math.cos(dir_radians) retval.z = absolute_speed * math.sin(dir_radians) return retval end ------------------------------------------------------------------------------- -- name: mobf_calc_vector_components_3d(xzplane_radians,xy_plane_radians,absolute_speed) -- --! @brief calculate calculate x and z components of a directed speed -- --! @param xz_plane_radians direction of movement within x-z plane radians --! @param xy_plane_radians direction of movement within x-y plane radians --! @param absolute_speed speed in direction -- --! @return {x,z} ------------------------------------------------------------------------------- function mobf_calc_vector_components_3d(xz_plane_radians,xy_plane_radians,absolute_speed) local retval = {x=0,z=0,y=0} retval.x= absolute_speed * math.sin(xy_plane_radians) * math.cos(xz_plane_radians) retval.z= absolute_speed * math.sin(xy_plane_radians) * math.sin(xz_plane_radians) retval.y= absolute_speed * math.cos(xy_plane_radians) return retval end ------------------------------------------------------------------------------- -- name: mobf_get_direction(pos1,pos2) -- --! @brief get normalized direction from pos1 to pos2 -- --! @param pos1 source point --! @param pos2 destination point --! @return xyz direction ------------------------------------------------------------------------------- function mobf_get_direction(pos1,pos2) local x_raw = pos2.x -pos1.x local y_raw = pos2.y -pos1.y local z_raw = pos2.z -pos1.z local x_abs = math.abs(x_raw) local y_abs = math.abs(y_raw) local z_abs = math.abs(z_raw) if x_abs >= y_abs and x_abs >= z_abs then y_raw = y_raw * (1/x_abs) z_raw = z_raw * (1/x_abs) x_raw = x_raw/x_abs end if y_abs >= x_abs and y_abs >= z_abs then x_raw = x_raw * (1/y_abs) z_raw = z_raw * (1/y_abs) y_raw = y_raw/y_abs end if z_abs >= y_abs and z_abs >= x_abs then x_raw = x_raw * (1/z_abs) y_raw = y_raw * (1/z_abs) z_raw = z_raw/z_abs end return {x=x_raw,y=y_raw,z=z_raw} end ------------------------------------------------------------------------------- -- name: mobf_calc_yaw(x,z) -- --! @brief calculate radians value of a 2 dimendional vector -- --! @param x vector component 1 --! @param z vector component 2 -- --! @return radians value ------------------------------------------------------------------------------- function mobf_calc_yaw(x,z) local direction = math.atan2(z,x) while direction < 0 do direction = direction + (2* math.pi) end while direction > (2*math.pi) do direction = direction - (2* math.pi) end return direction end ------------------------------------------------------------------------------- -- name: mobf_assert_backtrace(value) -- --! @brief assert in case value is false -- --! @param heightdiff height difference between shooter and target --! @param time time to reach target --! @param acceleration acceleration set for object -- --! @return y-velocity at start ------------------------------------------------------------------------------- function mobf_balistic_start_speed(heightdiff,time,acceleration) mobf_assert_backtrace(heightdiff ~= nil) mobf_assert_backtrace(time ~= nil) mobf_assert_backtrace(acceleration ~= nil) return (heightdiff - (acceleration/2) * (time*time)) / time end ------------------------------------------------------------------------------- -- name: mobf_calc_travel_time(distance,velocity,acceleration) -- --! @brief assert in case value is false -- --! @param distance distance to target --! @param velocity to start with --! @param acceleration acceleratio to use -- --! @return time to target ------------------------------------------------------------------------------- function mobf_calc_travel_time(distance,velocity,acceleration) mobf_assert_backtrace(distance ~= nil) mobf_assert_backtrace(velocity ~= nil) mobf_assert_backtrace(acceleration ~= nil) if acceleration == 0 then --print("no accel shortcut time calculation") return distance/velocity end local a = acceleration/2 local b = velocity local c = -distance --print("a=" .. a .. " b=" .. b .. " c=" .. c) local det = b*b - 4*a*c --print("det=" .. det) if det < 0 then return nil end local ret1 = (-b + math.sqrt(det))/(2*a) local ret2 = (-b - math.sqrt(det))/(2*a) --print("x1=" .. ret1 .. " x2=" .. ret2) if ret1 > 0 then return ret1 end return ret2 end ------------------------------------------------------------------------------- -- name: mobf_same_quadrant(x1,z1,x2,z2) -- --! @brief check if two points are in same quadrant -- --! @param x1 x of point1 --! @param z1 z of point1 --! @param x2 x of point2 --! @param z2 z of point2 -- --! @return true/false ------------------------------------------------------------------------------- function mobf_same_quadrant(x1,z1,x2,z2) if x1 > 0 and x2 < 0 or x1 < 0 and x2 >0 then return false end if z1 > 0 and z2 < 0 or z1 < 0 and z2 >0 then return false end return true end ------------------------------------------------------------------------------- -- name: mobf_gauss(center) -- --! @brief calc random value based uppon gauss distribution around a value -- --! @param center center value for distribution --! @param max_deviation maximum deviation from center -- --! @return gauss randomized value ------------------------------------------------------------------------------- function mobf_gauss(center,max_deviation) local u1 = 2 * math.random() -1 local u2 = 2 * math.random() -1 local q = u1*u1 + u2*u2 local maxtries = 0 while (q == 0 or q >= 1) and maxtries < 10 do u1 = math.random() u2 = math.random() * -1 q = u1*u1 + u2*u2 maxtries = maxtries +1 end --abort random generation if maxtries >= 10 then return center end local p = math.sqrt( (-2*math.log(q))/q ) local retval = nil --calculate normalized value of max deviation if math.random() < 0.5 then retval = center + ( u1*p * (center*max_deviation)) else retval = center + ( u2*p * (center*max_deviation)) end return retval end --!@}mobf_core-2.5.1/mobf/utils/permanent_data.lua000066400000000000000000000172701264104133000212230ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file permanent_data.lua --! @brief functions for storing required data permanently --! @copyright Sapier --! @author Sapier --! @date 2012-08-09 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @defgroup perm_data Permanent data handling --! @brief functions to deal with permanent data --! @ingroup framework_int --! @{ ------------------------------------------------------------------------------- -- name: mobf_deserialize_permanent_entity_data(datastring) -- --! @brief parse datastring and return table of data -- --! @param staticdata string to deserialize --! @return table containing data ------------------------------------------------------------------------------- function mobf_deserialize_permanent_entity_data(staticdata) --old style serialized static data local retval = {spawnpoint={x=0,y=0,z=0},playerspawned=false,original_spawntime=-1,state="default"} if staticdata == nil then return retval end local deserialized = minetest.deserialize(staticdata) if deserialized ~= nil and deserialized.version ~= nil then return deserialized end if staticdata ~= nil and staticdata ~= "" then local start_pos = 1 local end_pos = string.find(staticdata,";") if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as first element") if string.sub(staticdata,start_pos,end_pos-1) == "true" then retval.playerspawned = true end else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as second element") retval.spawnpoint.x = tonumber(string.sub(staticdata,start_pos,end_pos-1)) else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as third element") retval.spawnpoint.y = tonumber(string.sub(staticdata,start_pos,end_pos-1)) else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as fourth element") retval.spawnpoint.z = tonumber(string.sub(staticdata,start_pos,end_pos-1)) else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as fivth element") retval.original_spawntime = tonumber(string.sub(staticdata,start_pos,end_pos-1)) else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as sixth element") retval.spawner = string.sub(staticdata,start_pos,end_pos-1) if retval.spawner == "" then retval.spawner = nil end else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as seventh element") retval.state = string.sub(staticdata,start_pos,end_pos-1) if retval.state == "" then retval.state = nil end else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as eigth element") retval.pathindex_raw = string.sub(staticdata,start_pos,end_pos-1) retval.pathindex = tonumber(retval.pathindex_raw) else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as nineth element") retval.pathowner = string.sub(staticdata,start_pos,end_pos-1) if retval.pathowner == "" then retval.pathowner = nil end else return retval end start_pos = end_pos +1 end_pos = string.find(staticdata,";",start_pos) if end_pos ~= nil then dbg_mobf.permanent_store_lvl1("MOBF: Found: ".. string.sub(staticdata,start_pos,end_pos-1).. " as tenth element") retval.pathname = string.sub(staticdata,start_pos,end_pos-1) if retval.pathname == "" then retval.pathname = nil end else return retval end end return retval end ------------------------------------------------------------------------------- -- name: mobf_serialize_permanent_entity_data(entity) -- --! @brief return string containing all entity data to be preserved -- --! @param entity to get data from --! @return string containing entitys permanent data ------------------------------------------------------------------------------- function mobf_serialize_permanent_entity_data(entity) if entity.dynamic_data ~= nil and entity.dynamic_data.last_static_data ~= nil and entity.dynamic_data.last_static_data ~= "" then --mobf_print("MOBF: mob " .. entity.data.name .. "(" .. tostring(entity) -- .. ") wasn't even completely activated by now: " .. -- dump(entity.dynamic_data.last_static_data)) return entity.dynamic_data.last_static_data end if entity.dynamic_data ~= nil and entity.dynamic_data.spawning ~= nil then local state = "default" if entity.dynamic_data.state ~= nil and entity.dynamic_data.state.current ~= nil then state = entity.dynamic_data.state.current.name end if entity.dynamic_data.spawning.original_spawntime == nil then entity.dynamic_data.spawning.original_spawntime = mobf_get_current_time() minetest.log(LOGLEVEL_WARNING, "MOBF: saving entity without spawntime setting current time") end local pathowner = "" local pathname = "" local pathindex = "" if entity.dynamic_data.p_movement ~= nil then if entity.dynamic_data.p_movement.pathowner ~= nil then pathowner = entity.dynamic_data.p_movement.pathowner end if entity.dynamic_data.p_movement.pathname ~= nil then pathname = entity.dynamic_data.p_movement.pathname end if entity.dynamic_data.p_movement.next_path_index ~= nil then pathindex = "" .. entity.dynamic_data.p_movement.next_path_index end end local factions = mobf_factions.cleanupentity(entity) local toserialize = { spawnpoint = entity.dynamic_data.spawning.spawnpoint, playerspawned = entity.dynamic_data.spawning.player_spawned, original_spawntime = entity.dynamic_data.spawning.original_spawntime, spawner = entity.dynamic_data.spawning.spawner, version = 3, state = state, pathindex = pathindex, pathowner = pathowner, pathname = pathname, custom_persistent = entity.dynamic_data.custom_persistent, factions = factions, textureidx = entity.dynamic_data.textureidx } local serialized = minetest.serialize(toserialize) --mobf_print("DEBUG: serialized -> " .. serialized) return serialized else mobf_bug_warning(LOGLEVEL_ERROR,"MOBF: >" .. dump(entity.data.name) .. "< removed=" ..dump(entity.removed) .. " entity=" .. tostring(entity) .. " No spawning information available on saving mob") end return "" end --!@} mobf_core-2.5.1/mobf/utils/settings.lua000066400000000000000000000142121264104133000200720ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file settings.lua --! @brief generic functions used in many different places --! @copyright Sapier --! @author Sapier --! @date 2013-02-04 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @defgroup gen_func Generic functions --! @brief functions for various tasks --! @ingroup framework_int --! @{ ------------------------------------------------------------------------------- -- name: mobf_init_world_specific_settings(name,value) -- --! @brief check if world specific settings are supported by core if not do in lua -- ------------------------------------------------------------------------------- function mobf_init_world_specific_settings() local mobf_world_settings_data = nil if minetest.world_setting_set == nil or type(minetest.world_setting_set) ~= "function" then local mobf_world_path = minetest.get_worldpath() --initialy read settings file mobf_world_settings_data = nil local file,error = io.open(mobf_world_path .. "/mobf_settings.conf","r") if error ~= nil then minetest.log(LOGLEVEL_WARNING,"MOBF: failed to open world specific config file") mobf_world_settings_data = {} else local data_raw = file:read("*a") file:close() data_raw=data_raw:gsub("\n","") if data_raw ~= nil then mobf_world_settings_data = minetest.deserialize(data_raw) end end --set world settings function minetest.world_setting_set = function(name,value) mobf_world_settings_data[name] = value local file,error = io.open(mobf_world_path .. "/mobf_settings.conf.new","w") if error ~= nil then minetest.log(LOGLEVEL_ERROR,"MOBF: failed to open world specific config file") end mobf_assert_backtrace(file ~= nil) local serialized_data = minetest.serialize(mobf_world_settings_data) local prefix="" local lastwasnewline = false local commahandled = false local got_non_space_char = false for i = 1, #serialized_data do local c = serialized_data:sub(i,i) if (c == "{") then if not lastwasnewline then file:write("\n") end file:write(prefix .. c .. "\n") prefix = prefix .. " " lastwasnewline = true got_non_space_char = false elseif (c == "}") then if not lastwasnewline then file:write("\n") end prefix = prefix:sub(1,#prefix-4) local nextchar = serialized_data:sub(i+1,i+1) if nextchar == "," then file:write(prefix .. c ..nextchar .. "\n") commahandled = true else file:write(prefix .. c .. "\n") end got_non_space_char = false lastwasnewline = true elseif (c == ",") then if not commahandled then file:write(c .. "\n") end commahandled = false lastwasnewline = true got_non_space_char = false elseif (c == " ") then if got_non_space_char then file:write(c) end else if lastwasnewline then file:write(prefix) end file:write(c) lastwasnewline = false got_non_space_char = true end end file:write("\n") file:close() if not os.rename(mobf_world_path .. "/mobf_settings.conf.new", mobf_world_path .. "/mobf_settings.conf") then minetest.log(LOGLEVEL_ERROR,"MOBF: failed to swap old conf file to new one") end end minetest.world_setting_get = function(name) mobf_assert_backtrace(mobf_world_settings_data ~= nil) return mobf_world_settings_data[name] end end --initialize defaults if minetest.world_setting_get("vombie_3d_burn_animation_enabled") == nil then minetest.world_setting_set("vombie_3d_burn_animation_enabled",false) end if minetest.world_setting_get("mobf_log_removed_entities") == nil then minetest.world_setting_set("mobf_log_removed_entities",false) end if minetest.world_setting_get("mobf_disable_animal_spawning") == nil then minetest.world_setting_set("mobf_disable_animal_spawning",false) end if minetest.world_setting_get("mobf_disable_3d_mode") == nil then minetest.world_setting_set("mobf_disable_3d_mode",false) end if minetest.world_setting_get("mobf_disable_pathfinding") == nil then minetest.world_setting_set("mobf_disable_pathfinding",true) end if minetest.world_setting_get("mobf_delayed_spawning") == nil then minetest.world_setting_set("mobf_delayed_spawning",true) end if minetest.world_setting_get("mobf_log_bug_warnings") == nil then minetest.world_setting_set("mobf_log_bug_warnings",false) end if minetest.world_setting_get("mobf_show_spawners") == nil then minetest.world_setting_set("mobf_show_spawners",false) end if minetest.world_setting_get("mobf_lifebar") == nil then minetest.world_setting_set("mobf_lifebar",true) end if minetest.world_setting_get("mobf_enable_statistics") == nil then minetest.world_setting_set("mobf_enable_statistics",false) end if minetest.world_setting_get("mobf_animal_spawning_secondary") == nil then minetest.world_setting_set("mobf_animal_spawning_secondary",false) end if minetest.world_setting_get("mobf_grief_protection") == nil then minetest.world_setting_set("mobf_grief_protection",false) end end ------------------------------------------------------------------------------- -- name: mobf_set_world_setting(name,value) -- --! @brief save a setting dedicated to a single world only -- --! @param name key to use for storage --! @param value to save ------------------------------------------------------------------------------- function mobf_set_world_setting(name,value) minetest.world_setting_set(name,value) end ------------------------------------------------------------------------------- -- name: mobf_get_world_setting(name,value) -- --! @brief read a setting dedicated to a single world only -- --! @param name key to use for storage ------------------------------------------------------------------------------- function mobf_get_world_setting(name) return minetest.world_setting_get(name) end mobf_init_world_specific_settings() --!@}mobf_core-2.5.1/mobf/utils/text.lua000066400000000000000000000052401264104133000172170ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file text.lua --! @brief generic functions used in many different places --! @copyright Sapier --! @author Sapier --! @date 2013-02-04 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @defgroup gen_func Generic functions --! @brief functions for various tasks --! @ingroup framework_int --! @{ ------------------------------------------------------------------------------- -- name: printpos(pos) -- --! @brief convert pos to string of type "(X,Y,Z)" -- --! @param pos position to convert --! @return string with coordinates of pos ------------------------------------------------------------------------------- function printpos(pos) if pos ~= nil then if pos.y ~= nil then mobf_assert_backtrace(type(pos.x) == "number") mobf_assert_backtrace(type(pos.z) == "number") mobf_assert_backtrace(type(pos.y) == "number") return "("..string.format("%3f",pos.x).."," ..string.format("%3f",pos.y).."," ..string.format("%3f",pos.z)..")" else mobf_assert_backtrace(type(pos.x) == "number") mobf_assert_backtrace(type(pos.z) == "number") return "("..string.format("%3f",pos.x)..", ? ," ..string.format("%3f",pos.z)..")" end end return "" end ------------------------------------------------------------------------------- -- name: mobf_print(text) -- --! @brief print adding timestamp in front of text -- --! @param text to show ------------------------------------------------------------------------------- function mobf_print(text) print("[" .. string.format("%10f",os.clock()) .. "]" .. text) end ------------------------------------------------------------------------------- -- name: mobf_fixed_size_string(text,length) -- --! @brief make a text fixed length -- --! @param text text to enforce lenght --! @param length lenght to enforce --! --! @return text with exactly lenght characters ------------------------------------------------------------------------------- function mobf_fixed_size_string(text,length) mobf_assert_backtrace(length ~= nil) if text == nil then text="nil" end local current_length = string.len(text) if current_length == nil then current_length = 0 text = "" end if current_length < length then while current_length < length do text = text .. " " current_length = current_length +1 end return text else return string.sub(text,1,length) end end --!@}mobf_core-2.5.1/mobf/utils/tracing.lua000066400000000000000000000321011264104133000176560ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allow to pretend you have written it. -- --! @file tracing.lua --! @brief generic functions used in many different places --! @copyright Sapier --! @author Sapier --! @date 2013-02-04 --! -- Contact sapier a t gmx net ------------------------------------------------------------------------------- --! @defgroup gen_func Generic functions --! @brief functions for various tasks --! @ingroup framework_int --! @{ local callback_statistics = {} local statistics = {} statistics.total = 0 statistics.abms = 0 statistics.onstep = 0 statistics.mapgen = 0 statistics.lastcalc = 0 statistics.activate = 0 statistics.queue_load = 0 statistics.spawn_onstep = 0 statistics.user_1 = 0 statistics.user_2 = 0 statistics.user_3 = 0 statistics.data = {} statistics.data.total = { current=0,maxabs=0,max=0 } statistics.data.abm = { current=0,maxabs=0,max=0 } statistics.data.onstep = { current=0,maxabs=0,max=0 } statistics.data.mapgen = { current=0,maxabs=0,max=0 } statistics.data.activate = { current=0,maxabs=0,max=0 } statistics.data.queue_load = { current=0,maxabs=0,max=0 } statistics.data.mobs = { current=0,maxabs=" ",max=0 } statistics.data.queue = { current=0,maxabs=" ",max=0 } statistics.data.spawn_onstep = { current=0,maxabs=0,max=0 } statistics.data.user_1 = { current=0,maxabs=0,max=0 } statistics.data.user_2 = { current=0,maxabs=0,max=0 } statistics.data.user_3 = { current=0,maxabs=0,max=0 } ------------------------------------------------------------------------------- -- name: mobf_statistic_calc() -- --! @brief periodic update statistics -- ------------------------------------------------------------------------------- function mobf_statistic_calc(dtime) local refresh_interval = 50 -- in tenth of seconds local now = mobf_get_time_ms() if statistics.lastcalc == nil or now > statistics.lastcalc + (refresh_interval*100) then local delta = now - statistics.lastcalc local current_total = (statistics.total/delta)*100 local current_abm = (statistics.abms/delta)*100 local current_onstep = (statistics.onstep/delta)*100 local current_mapgen = (statistics.mapgen/delta)*100 local current_activate = (statistics.activate/delta)*100 local current_queue_load = (statistics.queue_load/delta)*100 local current_spawn_onstep = (statistics.spawn_onstep/delta)*100 local current_user_1 = (statistics.user_1/delta)*100 local current_user_2 = (statistics.user_2/delta)*100 local current_user_3 = (statistics.user_3/delta)*100 local active_mobs = 1 for index,value in pairs(minetest.luaentities) do if value.data ~= nil and value.data.name ~= nil then active_mobs = active_mobs +1 end end statistics.total = 0 statistics.abms = 0 statistics.onstep = 0 statistics.mapgen = 0 statistics.activate = 0 statistics.queue_load = 0 statistics.spawn_onstep = 0 statistics.user_1 = 0 statistics.user_2 = 0 statistics.user_3 = 0 statistics.data.total.current = current_total statistics.data.total.maxabs = MAX(statistics.data.total.maxabs, math.floor(current_total*refresh_interval)) statistics.data.total.max = MAX(statistics.data.total.max,current_total) statistics.data.abm.current = current_abm statistics.data.abm.maxabs = MAX(statistics.data.abm.maxabs, math.floor(current_abm*refresh_interval)) statistics.data.abm.max = MAX(statistics.data.abm.max,current_abm) statistics.data.onstep.current = current_onstep statistics.data.onstep.maxabs = MAX(statistics.data.onstep.maxabs, math.floor(current_onstep*refresh_interval)) statistics.data.onstep.max = MAX(statistics.data.onstep.max,current_onstep) statistics.data.mapgen.current = current_mapgen statistics.data.mapgen.maxabs = MAX(statistics.data.mapgen.maxabs, math.floor(current_mapgen*refresh_interval)) statistics.data.mapgen.max = MAX(statistics.data.mapgen.max,current_mapgen) statistics.data.activate.current = current_activate statistics.data.activate.maxabs = MAX(statistics.data.activate.maxabs, math.floor(current_activate*refresh_interval)) statistics.data.activate.max = MAX(statistics.data.activate.max,current_activate) statistics.data.queue_load.current = current_queue_load statistics.data.queue_load.maxabs = MAX(statistics.data.queue_load.maxabs, math.floor(current_queue_load*refresh_interval)) statistics.data.queue_load.max = MAX(statistics.data.queue_load.max,current_queue_load) statistics.data.spawn_onstep.current = current_spawn_onstep statistics.data.spawn_onstep.maxabs = MAX(statistics.data.spawn_onstep.maxabs, math.floor(current_spawn_onstep*refresh_interval)) statistics.data.spawn_onstep.max = MAX(statistics.data.spawn_onstep.max,current_spawn_onstep) statistics.data.mobs.current = active_mobs statistics.data.mobs.max = MAX(statistics.data.mobs.max,active_mobs) statistics.data.user_1.current = current_user_1 statistics.data.user_1.maxabs = MAX(statistics.data.user_1.maxabs, math.floor(current_user_1*refresh_interval)) statistics.data.user_1.max = MAX(statistics.data.user_1.max,current_user_1) statistics.data.user_2.current = current_user_2 statistics.data.user_2.maxabs = MAX(statistics.data.user_2.maxabs, math.floor(current_user_2*refresh_interval)) statistics.data.user_2.max = MAX(statistics.data.user_2.max,current_user_2) statistics.data.user_3.current = current_user_3 statistics.data.user_3.maxabs = MAX(statistics.data.user_3.maxabs, math.floor(current_user_3*refresh_interval)) statistics.data.user_3.max = MAX(statistics.data.user_3.max,current_user_3) statistics.lastcalc = now end end ------------------------------------------------------------------------------- -- name: mobf_get_statistics() -- --! @brief get mobf statistic information -- --! @return mobf statistics ------------------------------------------------------------------------------- function mobf_get_statistics() return statistics end ------------------------------------------------------------------------------- -- name: mobf_warn_long_fct(starttime,fctname,facility) -- --! @brief alias to get current time -- --! @param starttime time fct started --! @param fctname name of function --! @param facility name of facility to add time to -- --! @return current time in seconds ------------------------------------------------------------------------------- function mobf_warn_long_fct(starttime,fctname,facility) local currenttime = mobf_get_time_ms() mobf_assert_backtrace(starttime ~= nil) local delta = 0 if (starttime < currenttime) then delta = currenttime - starttime end if delta > 0 and minetest.world_setting_get("mobf_enable_statistics") then if facility == "abm" then statistics.abms = statistics.abms + delta statistics.total = statistics.total + delta end if facility == "on_step_total" then statistics.onstep = statistics.onstep + delta statistics.total = statistics.total + delta end if facility == "mapgen" then statistics.mapgen = statistics.mapgen + delta statistics.total = statistics.total + delta end if facility == "spawn_onstep" then statistics.spawn_onstep = statistics.spawn_onstep + delta statistics.total = statistics.total + delta end if facility == "onpunch_total" then statistics.onstep = statistics.onstep + delta statistics.total = statistics.total + delta end if facility == "delayed_processing" then statistics.queue_load = statistics.queue_load + delta statistics.total = statistics.total + delta end if facility == "onactivate_total" then statistics.activate = statistics.activate + delta statistics.total = statistics.total + delta end if facility == "user_1" then statistics.user_1 = statistics.user_1 + delta end if facility == "user_2" then statistics.user_2 = statistics.user_2 + delta end if facility == "user_3" then statistics.user_3 = statistics.user_3 + delta end end if minetest.world_setting_get("mobf_enable_callback_statistics") then if fctname == nil then fctname = "unknown" end if callback_statistics[fctname] == nil then callback_statistics[fctname] = { upto_005ms = 0, upto_010ms = 0, upto_020ms = 0, upto_050ms = 0, upto_100ms = 0, upto_200ms = 0, more = 0, valcount = 0, sum = 0, last_time = 0, } end callback_statistics[fctname].valcount = callback_statistics[fctname].valcount +1 callback_statistics[fctname].sum = callback_statistics[fctname].sum + delta if callback_statistics[fctname].valcount == 1000 then callback_statistics[fctname].valcount = 0 local deltatime = currenttime - callback_statistics[fctname].last_time callback_statistics[fctname].last_time = currenttime minetest.log(LOGLEVEL_ERROR,"Statistics for: " .. fctname .. ": " .. callback_statistics[fctname].upto_005ms .. "," .. callback_statistics[fctname].upto_010ms .. "," .. callback_statistics[fctname].upto_020ms .. "," .. callback_statistics[fctname].upto_050ms .. "," .. callback_statistics[fctname].upto_100ms .. "," .. callback_statistics[fctname].upto_200ms .. "," .. callback_statistics[fctname].more .. " (".. callback_statistics[fctname].sum .. " / " .. deltatime .. ") " .. tostring(math.floor((callback_statistics[fctname].sum/deltatime) * 100)) .. "%") callback_statistics[fctname].sum = 0 end if delta < 5 then callback_statistics[fctname].upto_005ms = callback_statistics[fctname].upto_005ms +1 return end if delta < 10 then callback_statistics[fctname].upto_010ms = callback_statistics[fctname].upto_010ms +1 return end if delta < 20 then callback_statistics[fctname].upto_020ms = callback_statistics[fctname].upto_020ms +1 return end if delta < 50 then callback_statistics[fctname].upto_050ms = callback_statistics[fctname].upto_050ms +1 return end if delta < 100 then callback_statistics[fctname].upto_100ms = callback_statistics[fctname].upto_100ms +1 return end if delta < 200 then callback_statistics[fctname].upto_200ms = callback_statistics[fctname].upto_200ms +1 return end callback_statistics[fctname].more = callback_statistics[fctname].more +1 end if delta >200 then minetest.log(LOGLEVEL_ERROR,"MOBF: function " .. fctname .. " took too long: " .. delta .. " ms") end end ------------------------------------------------------------------------------- -- name: mobf_bug_warning() -- --! @brief make bug warnings configurable -- --! @param level bug severity level to use for minetest.log --! @param text data to print to log ------------------------------------------------------------------------------- function mobf_bug_warning(level,text) if minetest.world_setting_get("mobf_log_bug_warnings") then minetest.log(level,text) end end ------------------------------------------------------------------------------- -- name: mobf_init_timesource() -- --! @brief select timesource to be used by mobf -- ------------------------------------------------------------------------------- function mobf_init_timesource() --try to get timesource with best accuracy if type(minetest.get_us_time) == "function" then mobf_get_time_ms = function() return minetest.get_us_time() / 1000 end mobf_rtd.timesource = "minetest.get_us_time()" else if socket == nil then local status, module = pcall(require, 'socket') if status and type(module.gettime) == "function" then mobf_get_time_ms = function() return socket.gettime()*1000 end mobf_rtd.timesource = "socket.gettime()" end else if type(socket.gettime) == "function" then mobf_get_time_ms = function() return socket.gettime()*1000 end mobf_rtd.timesource = "socket.gettime()" end end end end ------------------------------------------------------------------------------- -- name: mobf_init_statistics() -- --! @brief initialize profiling/statistics support -- ------------------------------------------------------------------------------- function mobf_init_statistics() --initialize statistics callback if minetest.world_setting_get("mobf_enable_statistics") then minetest.register_globalstep(mobf_statistic_calc) local abm_register_call = minetest.register_abm minetest.register_abm = function(spec) local modname = minetest.get_current_modname() local mob_start,mob_end = string.find(modname,"mob") local animal_start,animal_end = string.find(modname,"animal") local mobf_start,mobf_end = string.find(modname,"mobf") if mob_start == 1 or animal_start == 1 or mobf_start == 1 then local action = spec.action spec.action = function(pos, node, active_object_count, active_object_count_wider) local starttime = mobf_get_time_ms() local retval = action(pos, node, active_object_count, active_object_count_wider) mobf_warn_long_fct(starttime,"auto_abm","abm") return retval end minetest.log(LOGLEVEL_NOTICE,"MOBF: tracing enabled instrumenting abm for mod " .. modname) end abm_register_call(spec) end end end --!@}mobf_core-2.5.1/mobf_settings/000077500000000000000000000000001264104133000163075ustar00rootroot00000000000000mobf_core-2.5.1/mobf_settings/License.txt000066400000000000000000000002011264104133000204230ustar00rootroot00000000000000Licenses Everything not mentioned: CC-BY-SA 3.0, Author sapier URL: http://creativecommons.org/licenses/by-sa/3.0/de/legalcodemobf_core-2.5.1/mobf_settings/common.lua000066400000000000000000000050501264104133000203020ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file tab_feature_config.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- assert(not core.global_exists("mobf_settings")) mobf_settings = {} COLOR_RED = "#FF0000" COLOR_GREEN = "#00FF00" ------------------------------------------------------------------------------ -- name: setting_gettext(name) -- @function [parent=#mobf_settings] setting_gettext -- --! @brief convert bool to textual value --! @ingroup mobf_settings -- --! @param value string ------------------------------------------------------------------------------- function mobf_settings.setting_gettext(value) local value = mobf_get_world_setting(value) if value == nil then return "false" end if value then return "true" end return "false" end ------------------------------------------------------------------------------ -- name: printfac -- @function [parent=#mobf_settings] printfac -- --! @brief update formspec to tools tab --! @ingroup mobf_settings -- --! @param name of facility --! @param data data to add label --! @param yval ypos of label --! @param vs formatstring -- --! @return formspec label element string ------------------------------------------------------------------------------- function mobf_settings.printfac(name,data,yval,fs) return "label[0.75," .. yval .. ";" .. string.sub(name,1,20) .. "]" .. "label[2.75," .. yval .. ";" .. string.format("%10s",string.format(fs,data.current)).. "]" .. "label[4.25," .. yval .. ";" .. string.format("%10s",data.maxabs).. "]" .. "label[6," .. yval .. ";" .. string.format("%10s",string.format(fs,data.max)).. "]" end ------------------------------------------------------------------------------ -- name: contains -- --! @brief check if element is in table --! @ingroup mobf_settings -- --! @param cur_table table to check for element --! @param element element to find in table --! --! @return true/false ------------------------------------------------------------------------------- function contains(cur_table, element) if cur_table == nil then return false end for i,v in ipairs(cur_table) do if v == element then return true end end return false endmobf_core-2.5.1/mobf_settings/depends.txt000066400000000000000000000000041264104133000204640ustar00rootroot00000000000000mobfmobf_core-2.5.1/mobf_settings/fstk/000077500000000000000000000000001264104133000172565ustar00rootroot00000000000000mobf_core-2.5.1/mobf_settings/fstk/dialog.lua000066400000000000000000000043061264104133000212230ustar00rootroot00000000000000--Minetest --Copyright (C) 2014 sapier -- --self program is free software; you can redistribute it and/or modify --it under the terms of the GNU Lesser General Public License as published by --the Free Software Foundation; either version 2.1 of the License, or --(at your option) any later version. -- --self program is distributed in the hope that it will be useful, --but WITHOUT ANY WARRANTY; without even the implied warranty of --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --GNU Lesser General Public License for more details. -- --You should have received a copy of the GNU Lesser General Public License along --with self program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. local function dialog_event_handler(self,event) if self.user_eventhandler == nil or self.user_eventhandler(event) == false then --close dialog on esc if event == "MenuQuit" then self:delete() return true end end end local dialog_metatable = { eventhandler = dialog_event_handler, get_formspec = function(self) if not self.hidden then return self.formspec(self.data) end end, handle_buttons = function(self,fields) if not self.hidden then return self.buttonhandler(self,fields) end end, handle_events = function(self,event) if not self.hidden then return self.eventhandler(self,event) end end, hide = function(self) self.hidden = true end, show = function(self) self.hidden = false end, delete = function(self) if self.parent ~= nil then self.parent:show() end if self.parent_ui ~= nil then self.parent_ui:delete(self) end end, set_parent = function(self,parent) self.parent = parent end } dialog_metatable.__index = dialog_metatable function dialog_create(name, get_formspec, buttonhandler, eventhandler, parent_ui) local self = {} self.name = name self.type = "toplevel" self.hidden = true self.data = {} self.parent_ui = parent_ui self.formspec = get_formspec self.buttonhandler = buttonhandler self.user_eventhandler = eventhandler setmetatable(self,dialog_metatable) if self.parent_ui ~= nil then self.parent_ui:add(self) end return self end mobf_core-2.5.1/mobf_settings/fstk/tabview.lua000066400000000000000000000216111264104133000214230ustar00rootroot00000000000000--Minetest --Copyright (C) 2014 sapier -- --self program is free software; you can redistribute it and/or modify --it under the terms of the GNU Lesser General Public License as published by --the Free Software Foundation; either version 2.1 of the License, or --(at your option) any later version. -- --self program is distributed in the hope that it will be useful, --but WITHOUT ANY WARRANTY; without even the implied warranty of --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --GNU Lesser General Public License for more details. -- --You should have received a copy of the GNU Lesser General Public License along --with self program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -------------------------------------------------------------------------------- -- A tabview implementation -- -- Usage: -- -- tabview.create: returns initialized tabview raw element -- -- element.add(tab): add a tab declaration -- -- element.handle_buttons() -- -- element.handle_events() -- -- element.getFormspec() returns formspec of tabview -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- local function add_tab(self, tab, data) assert(tab.size == nil or (type(tab.size) == table and tab.size.x ~= nil and tab.size.y ~= nil)) assert(tab.cbf_formspec ~= nil and type(tab.cbf_formspec) == "function") assert(tab.cbf_button_handler == nil or type(tab.cbf_button_handler) == "function") assert(tab.cbf_events == nil or type(tab.cbf_events) == "function") local newtab = { name = tab.name, caption = tab.caption, button_handler = tab.cbf_button_handler, event_handler = tab.cbf_events, get_formspec = tab.cbf_formspec, tabsize = tab.tabsize, on_change = tab.on_change, tabdata = {}, } if data then newtab.tabdata = data end table.insert(self.tablist,newtab) if self.last_tab_index == #self.tablist then self.current_tab = tab.name if tab.on_activate ~= nil then tab.on_activate(nil,tab.name) end end end -------------------------------------------------------------------------------- local function get_formspec(self) local formspec = "" if not self.hidden and (self.parent == nil or not self.parent.hidden) then if self.parent == nil then local tsize = self.tablist[self.last_tab_index].tabsize or {width=self.width, height=self.height} formspec = formspec .. string.format("size[%f,%f,%s]",tsize.width,tsize.height, dump(self.fixed_size)) end formspec = formspec .. self:tab_header() formspec = formspec .. self.tablist[self.last_tab_index].get_formspec( self, self.tablist[self.last_tab_index].name, self.tablist[self.last_tab_index].tabdata, self.tablist[self.last_tab_index].tabsize ) end return formspec end -------------------------------------------------------------------------------- local function handle_buttons(self,fields) if self.hidden then return false end if self:handle_tab_buttons(fields) then return true end if self.glb_btn_handler ~= nil and self.glb_btn_handler(self,fields) then return true end if self.tablist[self.last_tab_index].button_handler ~= nil then return self.tablist[self.last_tab_index].button_handler( self, fields, self.tablist[self.last_tab_index].name, self.tablist[self.last_tab_index].tabdata ) end return false end -------------------------------------------------------------------------------- local function handle_events(self,event) if self.hidden then return false end if self.glb_evt_handler ~= nil and self.glb_evt_handler(self,event) then return true end if self.tablist[self.last_tab_index].evt_handler ~= nil then return self.tablist[self.last_tab_index].evt_handler( self, event, self.tablist[self.last_tab_index].name, self.tablist[self.last_tab_index].tabdata ) end return false end -------------------------------------------------------------------------------- local function tab_header(self) local toadd = "" for i=1,#self.tablist,1 do if toadd ~= "" then toadd = toadd .. "," end toadd = toadd .. self.tablist[i].caption end return string.format("tabheader[%f,%f;%s;%s;%i;true;false]", self.header_x, self.header_y, self.name, toadd, self.last_tab_index); end -------------------------------------------------------------------------------- local function switch_to_tab(self, index) --first call on_change for tab to leave if self.tablist[self.last_tab_index].on_change ~= nil then self.tablist[self.last_tab_index].on_change("LEAVE", self.current_tab, self.tablist[index].name, self) end --update tabview data self.last_tab_index = index local old_tab = self.current_tab self.current_tab = self.tablist[index].name if (self.autosave_tab) then core.setting_set(self.name .. "_LAST",self.current_tab) end -- call for tab to enter if self.tablist[index].on_change ~= nil then self.tablist[index].on_change("ENTER", old_tab,self.current_tab, self) end end -------------------------------------------------------------------------------- local function handle_tab_buttons(self,fields) --save tab selection to config file if fields[self.name] then local index = tonumber(fields[self.name]) switch_to_tab(self, index) return true end return false end -------------------------------------------------------------------------------- local function set_tab_by_name(self, name) for i=1,#self.tablist,1 do if self.tablist[i].name == name then switch_to_tab(self, i) return true end end return false end -------------------------------------------------------------------------------- local function hide_tabview(self) self.hidden=true -- if we don't have a current tab don't try to call on_change if not self.tablist[self.last_tab_index] then return end -- call on_change as we're not gonna show self tab any longer if self.tablist[self.last_tab_index].on_change ~= nil then self.tablist[self.last_tab_index].on_change("LEAVE", self.current_tab, nil, self) end end -------------------------------------------------------------------------------- local function show_tabview(self) self.hidden=false -- call for tab to enter if self.tablist[self.last_tab_index].on_change ~= nil then self.tablist[self.last_tab_index].on_change("ENTER", nil,self.current_tab, self) end end -------------------------------------------------------------------------------- local function delete(self) if self.parent_ui ~= nil then self.parent_ui:delete(self) end end -------------------------------------------------------------------------------- local function get_tabdata(self, name) assert(self ~= nil) for i=1, #self.tablist, 1 do if self.tablist[self.last_tab_index].name == name then return self.tablist[self.last_tab_index].tabdata end end return nil end -------------------------------------------------------------------------------- local function set_parent(self, parent) if parent == nil then self.type = "toplevel" else self.type = "addon" end self.parent = parent end local tabview_metatable = { add = add_tab, handle_buttons = handle_buttons, handle_events = handle_events, get_formspec = get_formspec, show = show_tabview, hide = hide_tabview, delete = delete, set_parent = set_parent, set_autosave_tab = function(self,value) self.autosave_tab = value end, set_tab = set_tab_by_name, get_tabdata = get_tabdata, set_global_button_handler = function(self,handler) self.glb_btn_handler = handler end, set_global_event_handler = function(self,handler) self.glb_evt_handler = handler end, set_fixed_size = function(self,state) self.fixed_size = state end, tab_header = tab_header, handle_tab_buttons = handle_tab_buttons } tabview_metatable.__index = tabview_metatable -------------------------------------------------------------------------------- function tabview_create(name, size, tabheaderpos, parent_ui) local self = {} self.name = name self.type = "toplevel" self.width = size.x self.height = size.y self.header_x = tabheaderpos.x self.header_y = tabheaderpos.y setmetatable(self, tabview_metatable) self.fixed_size = true self.hidden = true self.current_tab = nil self.last_tab_index = 1 self.tablist = {} self.parent_ui = parent_ui self.autosave_tab = false if parent_ui ~= nil then parent_ui:add(self) end return self end mobf_core-2.5.1/mobf_settings/fstk/ui_mod.lua000066400000000000000000000123641264104133000212430ustar00rootroot00000000000000--Minetest --Copyright (C) 2014 sapier -- --self program is free software; you can redistribute it and/or modify --it under the terms of the GNU Lesser General Public License as published by --the Free Software Foundation; either version 2.1 of the License, or --(at your option) any later version. -- --self program is distributed in the hope that it will be useful, --but WITHOUT ANY WARRANTY; without even the implied warranty of --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --GNU Lesser General Public License for more details. -- --You should have received a copy of the GNU Lesser General Public License along --with self program; if not, write to the Free Software Foundation, Inc., --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. local ui_registry = {} -------------------------------------------------------------------------------- local function add(self, child) assert(child ~= nil) if (self.childlist[child.name] ~= nil) then return false end self.childlist[child.name] = child return child.name end -------------------------------------------------------------------------------- local function delete(self, child) if self.childlist[child.name] == nil then return false end self.childlist[child.name] = nil return true end -------------------------------------------------------------------------------- local function set_default(self, name) self.default = name end -------------------------------------------------------------------------------- local function find_by_name(self, name) return self.childlist[name] end -------------------------------------------------------------------------------- local function hide(self) for key,value in pairs(self.childlist) do if type(value.hide) == "function" then value:hide() end end end -------------------------------------------------------------------------------- local function update(self) local formspec = "" local active_toplevel_ui_elements = 0 for key,value in pairs(self.childlist) do if (value.type == "toplevel") then local retval = value:get_formspec() if retval ~= nil and retval ~= "" then active_toplevel_ui_elements = active_toplevel_ui_elements +1 formspec = formspec .. retval end end end -- no need to show addons if there ain't a toplevel element if (active_toplevel_ui_elements > 0) then for key,value in pairs(self.childlist) do if (value.type == "addon") then local retval = value:get_formspec() if retval ~= nil and retval ~= "" then formspec = formspec .. retval end end end end if (active_toplevel_ui_elements == 0) then core.log("WARNING: not a single toplevel ui element active switching " .. "to default") self.childlist[self.default]:show() formspec = self.childlist[self.default]:get_formspec() end core.show_formspec(self.playername, self.formname, formspec) end -------------------------------------------------------------------------------- local ui_metatable = { add = add, delete = delete, set_default = set_default, find_by_name = find_by_name, hide = hide, update = update } ui_metatable.__index = ui_metatable -------------------------------------------------------------------------------- function create_ui(playername, unique_id) if (ui_registry[playername] == nil) then ui_registry[playername] = {} end if ui_registry[playername][unique_id] ~= nil then return nil end local self = {} setmetatable(self, ui_metatable) ui_registry[playername][unique_id] = self self.childlist = {} self.default = nil self.formname = unique_id self.playername = playername return self end -------------------------------------------------------------------------------- function get_ui_by_unique_id(playername, unique_id) if (ui_registry[playername] == nil) then return nil end return ui_registry[playername][unique_id] end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- Internal functions not to be called from user -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- local function handle_buttons(player, formname, fields) if not player:is_player() then return end local playername = player:get_player_name() if playername == nil or ui_registry[playername] == nil then return end if ui_registry[playername][formname] == nil then return end for key,value in pairs(ui_registry[playername][formname].childlist) do local retval = value:handle_buttons(fields) if retval then ui_registry[playername][formname]:update() return end end end -------------------------------------------------------------------------------- local function player_leave(player) if not player:is_player() then return end local playername = player:get_player_name() if playername == nil then return end ui_registry[playername] = nil end -------------------------------------------------------------------------------- minetest.register_on_player_receive_fields(handle_buttons) minetest.register_on_leaveplayer(player_leave) mobf_core-2.5.1/mobf_settings/init.lua000066400000000000000000000012441264104133000177560ustar00rootroot00000000000000------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file init.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net ------------------------------------------------------------------------------- function fgettext(text) return text end --!path of mod local modpath = minetest.get_modpath("mobf_settings") dofile (modpath .. DIR_DELIM .. "settings.lua") mobf_core-2.5.1/mobf_settings/settings.lua000066400000000000000000000061201264104133000206510ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file settings_v3.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- --!version local mobf_settings_version = "1.9.0" --!path of mod local modpath = minetest.get_modpath("mobf_settings") --!basepath of tools --TODO remove included fstk once minetest is updated --local basepath = core.get_builtin_path() local basepath = modpath --!unique id of ui local unique_id = "mobf_settings" dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "dialog.lua") dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "tabview.lua") dofile(basepath .. DIR_DELIM .. "fstk" .. DIR_DELIM .. "ui_mod.lua") dofile(modpath .. DIR_DELIM .. "common.lua") dofile(modpath .. DIR_DELIM .. "tab_main.lua") dofile(modpath .. DIR_DELIM .. "tab_info.lua") dofile(modpath .. DIR_DELIM .. "tab_feature_config.lua") dofile(modpath .. DIR_DELIM .. "tab_factions.lua") dofile(modpath .. DIR_DELIM .. "tab_mobs.lua") dofile(modpath .. DIR_DELIM .. "tab_restore_mobs.lua") dofile(modpath .. DIR_DELIM .. "tab_path_manager.lua") local function is_admin(playername) local privcheck = core.check_player_privs(playername, {mobfw_admin=true}) return privcheck or (playername == "singleplayer") end -------------------------------------------------------------------------------- local function init_player_ui(playername, param) local playerui = get_ui_by_unique_id(playername, unique_id) if not playerui then playerui = create_ui(playername, unique_id) assert( playerui ~= nil) local tv_main = tabview_create("mainview",{x=8,y=9},{x=0,y=0}, playerui) tv_main:add(mobf_settings_tab_main) if is_admin(playername) then tv_main:add(mobf_settings_tab_features, { is_admin=is_admin(playername) }) tv_main:add(mobf_settings_tab_mobs, { is_admin=is_admin(playername) }) end tv_main:add(mobf_settings_tab_info) if mobf_rtd.factions_available then tv_main:add(mobf_settings_tab_factions, { is_admin = is_admin(playername), playername = playername }) end tv_main:add(mobf_settings_tab_preserve, { is_admin = is_admin(playername), playername = playername }) tv_main:add(mobf_settings_tab_paths, { is_admin = is_admin(playername), playername = playername }) end playerui:hide() local main_tab = playerui:find_by_name("mainview") if not main_tab then return end main_tab:show() playerui:update() end -------------------------------------------------------------------------------- minetest.register_chatcommand("mobf", { params = "", description = "show mobf settings" , privs = {}, func = init_player_ui }) minetest.log("action","MOD: mobf_settings mod version "..mobf_settings_version.." loaded")mobf_core-2.5.1/mobf_settings/tab_factions.lua000066400000000000000000000157541264104133000214620ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file tab_feature_config.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- local function get_formspec(tabview, name, tabdata) if not tabdata.is_admin then return "label[0.75,0.25;" .. fgettext("Insufficient permissions to view this tab.") .. "]" end if tabdata.available_factions_selected == nil then tabdata.available_factions_selected = 0 end if tabdata.faction_reputation_selected == nil then tabdata.faction_reputation_selected = 0 end local retval = "" retval = retval .. "label[0.25,-0.25;Available factions:]" .. "textlist[0.25,0.25;3.5,7.5;tl_factions_available_factions;" local factionlist = factions.get_faction_list() local first_element = true if #factionlist ~= 0 then for i=1,#factionlist,1 do if not first_element then retval = retval .. "," else first_element = false end retval = retval .. factionlist[i] end else retval = retval .. "no factions available" end retval = retval .. ";" .. tabdata.available_factions_selected .. "]" if tabdata.is_admin then retval = retval .. "button[0.25,8;3.75,0.5;btn_factions_delete;Delete]" .. "field[4.3,0.75;4,0.5;te_factionname;New Faction;]" .. "button[4,1.25;4,0.25;btn_factions_create;Create]" end if core.check_player_privs(tabdata.playername, {faction_admin=true}) or core.check_player_privs(tabdata.playername, {faction_user=true}) or tabdata.playername == "singleplayer" then retval = retval .. "field[4.3,2.75;4,0.5;te_inviteename;Playername:;]" .. "button[4,3.25;4,0.25;btn_factions_invite;Invite]" end local selected_rep = "" retval = retval .. "label[4,3.75;Base reputation:]" .. "textlist[4,4.25;3.75,3.5;tl_factions_faction_reputation;" if tabdata.available_factions_selected > 0 and tabdata.available_factions_selected <= #factionlist then local first_rep = true for i=1,#factionlist,1 do local current_rep = factions.get_base_reputation( factionlist[i], factionlist[tabdata.available_factions_selected]) if not first_rep then retval = retval .. "," else first_rep = false end if tonumber(current_rep) > 0 then retval = retval .. COLOR_GREEN elseif tonumber(current_rep) < 0 then retval = retval .. COLOR_RED end retval = retval .. "(" .. current_rep .. ") " .. factionlist[i] end if tabdata.faction_reputation_selected > 0 and tabdata.faction_reputation_selected <= #factionlist then selected_rep = factions.get_base_reputation( factionlist[tabdata.faction_reputation_selected], factionlist[tabdata.available_factions_selected]) end end retval = retval .. ";" .. tabdata.faction_reputation_selected .."]" .. "label[4,7.9;New Baserep:]" .. "field[6.2,8.3;1.1,0.5;te_baserep;;" .. selected_rep .."]" .. "button[6.9,8;1,0.5;btn_factions_set_reputation;set]" if tabdata.errormessage then retval = retval .. "label[0.25,8.5;" .. tabdata.errormessage .. "]" end return retval end local function handle_settings_buttons(self, fields, tabname, tabdata) if not tabdata.is_admin then core.log("error", "MOBF_Settings: someone managed to press a button " .. "she/he shouldn't even see!") end if fields["btn_factions_delete"] then tabdata.errormessage = "delete faction is not implemented yet" return true end if fields["btn_factions_create"] then if fields["te_factionname"] ~= nil then if fields["te_factionname"] == "" then tabdata.errormessage ="Refusing to create faction with no name!" elseif not factions.exists(fields["te_factionname"]) then if not factions.add_faction(fields["te_factionname"]) then tabdata.errormessage = "Failed to add faction \"" .. fields["te_factionname"] .. "\"" else local player = minetest.get_player_by_name(tabdata.playername) if not player or not factions.member_add( fields["te_factionname"], player) then tabdata.errormessage = "Unable to add creator to faction!" elseif not factions.set_admin( fields["te_factionname"], tabdata.playername, true) then tabdata.errormessage = "Unable to give admin privileges to creator!" end end else tabdata.errormessage = "Faction \"" .. sender_data.fields["te_factionname"] .. "\" already exists" end end return true end if fields["btn_factions_invite"] then --get faction from faction list local factionlist = factions.get_faction_list() if tabdata.available_factions_selected > 0 and tabdata.available_factions_selected < #factionlist then local faction_to_invite = factionlist[tabdata.available_factions_selected] --check if player is in faction he wants to invite for --TODO privs check if factions.is_admin(faction_to_invite, tabdata.playername) or factions.is_free(faction_to_invite) then if fields["te_inviteename"] ~= nil and fields["te_inviteename"] ~= "" then factions.member_invite(faction_to_invite,fields["te_inviteename"]) else tabdata.errormessage = "You can't invite nobody!" end else tabdata.errormessage = "Not allowed to invite for faction " .. faction_to_invite end else tabdata.errormessage = "No faction selected to invite to" end return true end if fields["btn_factions_set_reputation"] then if tabdata.available_factions_selected == tabdata.faction_reputation_selected then tabdata.errormessage = "Can't set base reputation of faction to itself!" else local factionlist = factions.get_faction_list() local faction1 = factionlist[tabdata.available_factions_selected] local faction2 = factionlist[tabdata.faction_reputation_selected] if faction1 ~= nil and faction2 ~= nil and fields["te_baserep"] ~= nil and fields["te_baserep"] ~= "" then if not factions.set_base_reputation(faction1, faction2, fields["te_baserep"]) then tabdata.errormessage = "Failed to set base reputation" end else tabdata.errormessage = "Only one faction selected or no value given!" end end return true end if fields["tl_factions_available_factions"] then local event = core.explode_textlist_event( fields["tl_factions_available_factions"]) if event.typ ~= "INV" then tabdata.available_factions_selected = event.index end return true end if fields["tl_factions_faction_reputation"] then local event = core.explode_textlist_event( fields["tl_factions_faction_reputation"]) if event.typ ~= "INV" then tabdata.faction_reputation_selected = event.index end return true end return false end mobf_settings_tab_factions = { name = "factions", caption = fgettext("Factions"), cbf_formspec = get_formspec, cbf_button_handler = handle_settings_buttons }mobf_core-2.5.1/mobf_settings/tab_feature_config.lua000066400000000000000000000147571264104133000226360ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file tab_feature_config.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- local function get_formspec(tabview, name, tabdata) if not tabdata.is_admin then return "label[0.75,0.25;" .. fgettext("Insufficient permissions to view this tab.") .. "]" end local retval = "" local ypos = 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_disable_animal_spawning;" .. "Disable mob spawning;" .. mobf_settings.setting_gettext("mobf_disable_animal_spawning") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_disable_3d_mode;" .. "Disable 3D mobs;" .. mobf_settings.setting_gettext("mobf_disable_3d_mode") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_animal_spawning_secondary;" .. "Enable secondary spawning;" .. mobf_settings.setting_gettext("mobf_animal_spawning_secondary") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_delete_disabled_mobs;" .. "Delete disabled mobs+spawners;" .. mobf_settings.setting_gettext("mobf_delete_disabled_mobs") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_log_bug_warnings;" .. "Log MOBF bug warnings;" .. mobf_settings.setting_gettext("mobf_log_bug_warnings") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_vombie_3d_burn_animation_enabled;" .. "Vombie 3D burn animation;" .. mobf_settings.setting_gettext("vombie_3d_burn_animation_enabled") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_log_removed_entities;" .. "Log all removed mobs;" .. mobf_settings.setting_gettext("mobf_log_removed_entities") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_grief_protection;" .. "Enable grief protection;" .. mobf_settings.setting_gettext("mobf_grief_protection") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_lifebar;" .. "Show mob lifebar;" .. mobf_settings.setting_gettext("mobf_lifebar") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_enable_statistics;" .. "Enable statistics;" .. mobf_settings.setting_gettext("mobf_enable_statistics") .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_disable_pathfinding;" .. "Disable core pathfinding support;" .. mobf_settings.setting_gettext("mobf_disable_pathfinding") .."]" ypos = ypos + 0.5 local showspawner = core.setting_get("adv_spawning.debug") local spawner_setting_text = "false" if (showspawner) then spawner_setting_text = "true" end retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_features_show_spawners;" .. "Show spawner entities;" .. spawner_setting_text .."]" ypos = ypos + 0.5 retval = retval .. "checkbox[1," .. ypos .. ";" .. "cb_adv_spawning_refresh_spawners;" .. "Refresh spawners (spawn mobs in old maps);" .. (core.setting_get("adv_spawning_validate_spawners") or "false") .."]" ypos = ypos + 0.5 return retval end local function handle_settings_buttons(self, fields, tabname, tabdata) if not tabdata.is_admin then core.log("error", "MOBF_Settings: someone managed to press a button " .. "she/he shouldn't even see!") return false end if fields["cb_features_disable_animal_spawning"] then mobf_set_world_setting("mobf_disable_animal_spawning", core.is_yes(fields["cb_features_disable_animal_spawning"])) return true end if fields["cb_features_disable_3d_mode"] then mobf_set_world_setting("mobf_disable_3d_mode", core.is_yes(fields["cb_features_disable_3d_mode"])) return true end if fields["cb_features_animal_spawning_secondary"] then mobf_set_world_setting("mobf_animal_spawning_secondary", core.is_yes(fields["cb_features_animal_spawning_secondary"])) return true end if fields["cb_features_delete_disabled_mobs"] then mobf_set_world_setting("mobf_delete_disabled_mobs", core.is_yes(fields["cb_features_delete_disabled_mobs"])) return true end if fields["cb_features_log_bug_warnings"] then mobf_set_world_setting("mobf_log_bug_warnings", core.is_yes(fields["cb_features_log_bug_warnings"])) return true end if fields["cb_features_vombie_3d_burn_animation_enabled"] then mobf_set_world_setting("vombie_3d_burn_animation_enabled", core.is_yes(fields["cb_features_vombie_3d_burn_animation_enabled"])) return true end if fields["cb_features_log_removed_entities"] then mobf_set_world_setting("mobf_log_removed_entities", core.is_yes(fields["cb_features_log_removed_entities"])) return true end if fields["cb_features_grief_protection"] then mobf_set_world_setting("mobf_grief_protection", core.is_yes(fields["cb_features_grief_protection"])) return true end if fields["cb_features_lifebar"] then mobf_set_world_setting("mobf_lifebar", core.is_yes(fields["cb_features_lifebar"])) return true end if fields["cb_features_enable_statistics"] then mobf_set_world_setting("mobf_enable_statistics", core.is_yes(fields["cb_features_enable_statistics"])) return true end if fields["cb_features_delayed_spawning"] then mobf_set_world_setting("mobf_delayed_spawning", core.is_yes(fields["cb_features_delayed_spawning"])) return true end if fields["cb_features_disable_pathfinding"] then mobf_set_world_setting("mobf_disable_pathfinding", core.is_yes(fields["cb_features_disable_pathfinding"])) return true end if fields["cb_features_show_spawners"] then core.setting_set("adv_spawning.debug", fields["cb_features_show_spawners"]) return true end if fields["cb_adv_spawning_refresh_spawners"] then core.setting_set("adv_spawning_validate_spawners", fields["cb_adv_spawning_refresh_spawners"]) return true end return false end mobf_settings_tab_features = { name = "features", caption = fgettext("Features"), cbf_formspec = get_formspec, cbf_button_handler = handle_settings_buttons }mobf_core-2.5.1/mobf_settings/tab_info.lua000066400000000000000000000121301264104133000205700ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file tab_info.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- ------------------------------------------------------------------------------- local function get_formspec(tabview, name, tabdata) local adv_stats = nil if mobf_rtd.have_adv_spawning then adv_stats = adv_spawning.get_statistics() end local mobs_offline = spawning.total_offline_mobs() local statistics = mobf_get_statistics() local retval = "label[0.75,1.25;Timesource:]" .. "label[2.75,1.25;" .. mobf_fixed_size_string(mobf_rtd.timesource,30) .. "]" if mobf_rtd.have_adv_spawning then retval = retval .. "label[0.75,2.25;Mobs spawned by adv_spawning this session:]" .. "label[6,2.25;" .. string.format("%10d",adv_stats.session.entities_created) .. "]" end retval = retval .. mobf_settings.printfac("Type",{current="cur count",maxabs="",max="max count"},3,"%s") .. "box[0.75,3.5;6.75,0.05;#FFFFFF]" .. mobf_settings.printfac("Active mobs",statistics.data.mobs,3.5,"%6d") .. mobf_settings.printfac("Offline mobs",{current=mobs_offline,maxabs="",max=-1},4,"%6d") .. mobf_settings.printfac("Jobs in queue",statistics.data.queue,4.5,"%6d") .. "label[0.75,6.0;Daytime:]" .. "label[2.5,6.0;" .. string.format("%5d",minetest.get_timeofday()*24000) .. "]" return retval end ------------------------------------------------------------------------------- mobf_settings_tab_info_sub = { name = "info", caption = fgettext("Generic"), cbf_formspec = get_formspec } --------------------------------------------------------------------------------- local function get_formspec(tabview, name, tabdata) local adv_stats = nil if mobf_rtd.have_adv_spawning then adv_stats = adv_spawning.get_statistics() end local statistics = mobf_get_statistics() local retval = mobf_settings.printfac("Facility", { current = "Current", maxabs = "Abs.Max (ms)", max = "Maximum" }, "0.5","%s") .. "box[0.75,1;6.75,0.05;#FFFFFF]" .. mobf_settings.printfac("Total", statistics.data.total, "1", "%2.2f%%") .. mobf_settings.printfac("Onstep", statistics.data.onstep, "1.5", "%2.2f%%") .. mobf_settings.printfac("Job processing", statistics.data.queue_load, "2", "%2.2f%%") .. mobf_settings.printfac("ABM", statistics.data.abm, "2.5", "%.2f%%") .. mobf_settings.printfac("MapGen", statistics.data.mapgen, "3", "%2.2f%%") .. mobf_settings.printfac("Spawn onstep", statistics.data.spawn_onstep,"3.5", "%2.2f%%") .. mobf_settings.printfac("Activate", statistics.data.activate, "4", "%2.2f%%") .. mobf_settings.printfac("User 1", statistics.data.user_1, "7", "%2.2f%%") .. mobf_settings.printfac("User 2", statistics.data.user_2, "7.5", "%2.2f%%") .. mobf_settings.printfac("User 3", statistics.data.user_3, "8", "%2.2f%%") if mobf_rtd.have_adv_spawning then retval = retval .. mobf_settings.printfac("Adv.Spawning", { current = adv_stats.load.cur, maxabs = adv_stats.step.max, max = adv_stats.load.max }, "4.5","%2.2f%%") end return retval end ------------------------------------------------------------------------------- mobf_settings_tab_statistics = { name = "statistics", caption = fgettext("Statistics"), cbf_formspec = get_formspec } ------------------------------------------------------------------------------- local function init_tab(type, from, to, tabview) if (to == "info_top") then local tabdata = tabview:get_tabdata("info_top") assert(tabdata ~= nil) if tabdata.subtabview == nil then tabdata.subtabview = tabview_create("infoview", {x=8,y=8},{x=0,y=0.75}, tabview.parent_ui) tabdata.subtabview:add(mobf_settings_tab_info_sub) if core.world_setting_get("mobf_enable_statistics") then tabdata.subtabview:add(mobf_settings_tab_statistics) end tabdata.subtabview:set_parent(tabview) end tabdata.subtabview:show() elseif (from == "info_top") then local tabdata = tabview:get_tabdata("info_top") assert(tabdata ~= nil) if tabdata.subtabview ~= nil then tabdata.subtabview:hide() end end end ------------------------------------------------------------------------------- local function get_formspec_tab(tabview, name, tabdata) return "" end ------------------------------------------------------------------------------- local function btn_handler_tab(tabview, fields, tabname, tabdata) return false end ------------------------------------------------------------------------------- mobf_settings_tab_info = { name = "info_top", caption = fgettext("Info"), cbf_button_handler = btn_handler_tab, cbf_formspec = get_formspec_tab, on_change = init_tab }mobf_core-2.5.1/mobf_settings/tab_main.lua000066400000000000000000000126651264104133000205760ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file tab_main.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- local function get_formspec_cheat_leftclick(tabview, name, tabdata) return "box[0.25,2.90;7.25,6.05;#000000]" .. "label[0.5,3.2;" .. fgettext("target: own mob,") .. "]" .. "label[3,3.2;" .. fgettext("wielditem: hand") .. "]" .. "label[0.75,3.5;" .. fgettext("-->rotate mob by 45°") .. "]" .. "label[0.5,4;" .. fgettext("target: own mob,") .. "]" .. "label[3,4;" .. fgettext("wielditem: various weapons") .. "]" .. "label[0.75,4.3;" .. fgettext("-->attack") .. "]" .. "label[0.5,4.8;" .. fgettext("target: any mob,") .. "]" .. "label[3,4.8;" .. fgettext("wielditem: catching (net/lasso/...)") .. "]" .. "label[0.75,5.1;" .. fgettext("-->catch") .."]" .. "label[0.5,5.6;" .. fgettext("target: (small) barn,") .. "]" .. "label[3,5.6;" .. fgettext("wielditem: grass/leaves") .. "]" .. "label[0.75,5.9;" .. fgettext("-->fill barn for breeding") .. "]" .. "label[0.5,6.4;" .. fgettext("target: (small) barn,") .. "]" .. "label[3,6.4;" .. fgettext("wielditem: hand/tool") .. "]" .. "label[0.75,6.7;" .. fgettext("-->take barn") .. "]" .. "label[0.5,7.2;" .. fgettext("target: mob,") .. "]" .. "label[3,7.2;" .. fgettext("wielditem: harvest-tool") .. "]" .. "label[0.75,7.5;" .. fgettext("-->harvest e.g. gather wool or milk") .. "]" .. "label[0.5,8;" .. fgettext("target: ridable mob,") .. "]" .. "label[3,8;" .. fgettext("wielditem: saddle (mob specific)") .. "]" .. "label[0.75,8.3;" .. fgettext("--> mount a mob to ride") .. "]" end local function get_formspec_cheat_rightclick(tabview, name, tabdata) return "box[0.25,2.90;7.25,6.05;#000000]" .. "label[0.5,3.2;" .. fgettext("Rightclicking a mob opens a mob specific rightclick menu,") .. "]" .. "label[0.5,3.5;" .. fgettext("following menu elements are possible:") .. "]" .. "label[0.5,4;" .. fgettext("Show debuginfo") .. "]" .. "label[0.75,4.3;" .. fgettext("print debuginfo about this mob to console") .. "]" .. "label[0.5,4.8;" .. fgettext("Select path") .. "]" .. "label[0.75,5.1;" .. fgettext("Select a path to put mob in guard mode") .. "]" .. "label[0.5,5.6;" .. fgettext("Factions") .. "]" .. "label[0.75,5.9;" .. fgettext("configure factions for this mob") .. "]" .. "label[0.5,6.4;" .. fgettext("heal / nothing to heal(full health)") .. "]" .. "label[0.75,6.7;" .. fgettext("heal mob using the currently wielded food") .. "]" .. "label[0.5,7.2;" .. fgettext("Trade") .. "]" .. "label[0.75,7.5;" .. fgettext("open trade inventory") .. "]" end local function get_formspec_cheat_mixed(tabview, name, tabdata) return "box[0.25,2.90;7.25,6.05;#000000]" .. "label[0.5,3.2;" .. fgettext("Missing mobs") .. "]" .. "label[0.75,3.5;" .. fgettext("Case you're missing any of your mobs have a look at") .. "]" .. "label[0.75,3.8;" .. fgettext("\"Lost mobs\" in control panel, they may be preserved") .. "]" .. "label[0.5,4.3;" .. fgettext("Path configuration for guard mode") .. "]" .. "label[0.75,4.6;" .. fgettext("See \"Paths\" in control panel") .. "]" end local mobf_settings_tab_cheat_leftclick = { name = "cheat_left", caption = fgettext("Left click"), cbf_formspec = get_formspec_cheat_leftclick } local mobf_settings_tab_cheat_rightclick = { name = "cheat_left", caption = fgettext("Right click"), cbf_formspec = get_formspec_cheat_rightclick } local mobf_settings_tab_cheat_mixed = { name = "cheat_mixed", caption = fgettext("Other"), cbf_formspec = get_formspec_cheat_mixed } ------------------------------------------------------------------------------- local function init_tab(type, from, to, tabview) if (to == "main") then local tabdata = tabview:get_tabdata("main") assert(tabdata ~= nil) if tabdata.subtabview == nil then tabdata.subtabview = tabview_create("cheatsheets", {x=8,y=8},{x=0.5,y=3.25}, tabview.parent_ui) tabdata.subtabview:add(mobf_settings_tab_cheat_leftclick) tabdata.subtabview:add(mobf_settings_tab_cheat_rightclick) tabdata.subtabview:add(mobf_settings_tab_cheat_mixed) tabdata.subtabview:set_parent(tabview) end tabdata.subtabview:show() elseif (from == "main") then local tabdata = tabview:get_tabdata("main") assert(tabdata ~= nil) if tabdata.subtabview ~= nil then tabdata.subtabview:hide() end end end local function get_formspec(tabview, name, tabdata) local retval = "" retval = retval .. "label[0.25,0.25;" .. fgettext("Mob Framework") .. "]" .. "label[0.25,0.4;" .. "-------------------------" .. "]" .. "label[0.25,0.8;" .. fgettext("You're at mobf control panel various settings and tweeks") .. "]" .. "label[0.25,1.1;" .. fgettext("can be controled in here. Some options require \"mobf_admin\"") .."]" .. "label[0.25,1.4;" .. fgettext("privilege to be visible.") .. "]" .. "label[0.25,8.9;" .. fgettext("For more help see: http://github.com/sapier/animals_modpack") .."]" return retval end mobf_settings_tab_main = { name = "main", caption = fgettext("Main"), cbf_formspec = get_formspec, on_change = init_tab }mobf_core-2.5.1/mobf_settings/tab_mobs.lua000066400000000000000000000067461264104133000206150ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file tab_feature_config.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- local function get_formspec(tabview, name, tabdata) if not tabdata.is_admin then return "label[0.75,0.25;" .. fgettext("Insufficient permissions to view this tab.") .. "]" end local retval = "" if tabdata.lastselected ~= nil then local mobdef = minetest.registered_entities[tabdata.lastselected] if mobdef ~= nil and mobdef.data ~= nil then retval = retval .. "label[2.25,0.25;Name:]label[4,0.25;" .. mobdef.data.name .. "]" .. "label[2.25,0.75;Mod:]label[4,0.75;" .. mobdef.data.modname .. "]" .. "label[2.25,1.25;Description:]label[4,1.25;" .. mobdef.data.generic.description .. "]" .. "image[0.25,0.25;2,2;" .. mobdef.data.modname .. "_" .. mobdef.data.name .. "_item.png]" end end retval = retval .. "label[0.5,2;Mobs:]" .. "label[0.5,8.5;doubleclick to change!]" .. "label[4,8.5;green=enabled, red=disabled]" .. "textlist[0.5,2.5;7,6;tl_mobs_moblist;" local mobf_mob_blacklist_string = minetest.world_setting_get("mobf_blacklist") local mobf_mobs_blacklisted = nil if mobf_mob_blacklist_string ~= nil then mobf_mobs_blacklisted = core.deserialize(mobf_mob_blacklist_string) end local toadd = "" for i,val in ipairs(mobf_rtd.registred_mob) do if toadd ~= "" then toadd = toadd .. "," end if contains(mobf_mobs_blacklisted,val) then toadd = toadd .. COLOR_RED .. val else toadd = toadd .. COLOR_GREEN .. val end end retval = retval .. toadd .. ";]" return retval end local function handle_settings_buttons(self, fields, tabname, tabdata) if not tabdata.is_admin then core.log("error", "MOBF_Settings: someone managed to press a button " .. "she/he shouldn't even see!") return false end if fields["tl_mobs_moblist"] then local tl_event = core.explode_textlist_event(fields["tl_mobs_moblist"]) if tl_event.type == "DCL" and tl_event.index <= #mobf_rtd.registred_mob then local clicked_mob = mobf_rtd.registred_mob[tl_event.index] local mobf_mob_blacklist_string = minetest.world_setting_get("mobf_blacklist") local mobf_mobs_blacklisted = nil if mobf_mob_blacklist_string ~= nil then mobf_mobs_blacklisted = core.deserialize(mobf_mob_blacklist_string) else mobf_mobs_blacklisted = {} end local new_blacklist = {} if contains(mobf_mobs_blacklisted,clicked_mob) then for i=1,#mobf_mobs_blacklisted,1 do if mobf_mobs_blacklisted[i] ~= clicked_mob then table.insert(new_blacklist,mobf_mobs_blacklisted[i]) end end else new_blacklist = mobf_mobs_blacklisted table.insert(mobf_mobs_blacklisted,clicked_mob) end minetest.world_setting_set("mobf_blacklist",core.serialize(new_blacklist)) end if tl_event.type == "CHG" and tl_event.index <= #mobf_rtd.registred_mob then tabdata.lastselected = mobf_rtd.registred_mob[tl_event.index] end return true end return false end mobf_settings_tab_mobs = { name = "mobs", caption = fgettext("Mobs"), cbf_formspec = get_formspec, cbf_button_handler = handle_settings_buttons }mobf_core-2.5.1/mobf_settings/tab_path_manager.lua000066400000000000000000000106071264104133000222720ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file tab_restore_mobs.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- local function get_formspec(tabview, name, tabdata) if tabdata.selected_entry == nil then tabdata.selected_entry = 0 end if tabdata.selected_point_entry == nil then tabdata.selected_point_entry = 0 end local retval = "button[0.25,0.0;6,0.5;btn_give_pathmarker; Give pathmarker tool]" .. "label[0.25,0.5;Pathname]" .. "label[4,0.5;Owner]" local content = "" local all_paths = mobf_path.get_pathlist(tabdata.playername,tabdata.is_admin) if all_paths ~= nil then for i=1,#all_paths,1 do content = content .. all_paths[i].pathname .. ",(" .. all_paths[i].ownername .. ")" if i ~= #all_paths then content = content .. "," end end end retval = retval .. "tablecolumns[text,width=16;text]" .. "table[0.25,1;6,8;tbl_pathlist;" .. content .. ";" .. tabdata.selected_entry .. "]" if tabdata.selected_entry ~= 0 then local selected_path = all_paths[tabdata.selected_entry] if selected_path == nil then return retval end local path_data = mobf_rtd.path_data.users [selected_path.ownername].paths[selected_path.pathname] if path_data == nil then return retval end local point_content = "" local first = true for i,v in ipairs(path_data.points) do if not first then point_content = point_content .."," else first = false end point_content = point_content .. i .. ":," .. v.x .. "," .. v.y .. "," .. v.z end retval = retval .. "tablecolumns[text,width=5,align=right;".. "text,align=right;" .. "text,align=right;" .. "text,align=right]" .. "table[6.5,0;5.25,8;tbl_path_points;" .. point_content .. ";" .. tabdata.selected_point_entry .. "]" if path_data.locked then retval = retval .. "button[6.5,8.5;1.5,0.5;btn_unlock_path;unlock]" else retval = retval .. "button[6.5,8.5;1.5,0.5;btn_lock_path;lock]" end retval = retval .. "button[8,8.5;2,0.5;btn_show_points;show points]" .. "button[10,8.5;2,0.5;btn_delete_path;delete path]" end return retval end local function handle_settings_buttons(self, fields, tabname, tabdata) if fields["btn_give_pathmarker"] then local player = core.get_player_by_name(tabdata.playername) if not player then return true end player:get_inventory():add_item("main", "mobf:path_marker 1") return true end if fields["tbl_pathlist"] then local event = core.explode_table_event(fields["tbl_pathlist"]) if event.type == "CHG" then tabdata.selected_entry = event.row end return true; end if fields["btn_lock_path"] or fields["btn_unlock_path"] then local all_paths = mobf_path.get_pathlist(tabdata.playername,tabdata.is_admin) local selected_path = all_paths[tabdata.selected_entry] if selected_path == nil then return true end local path_data = mobf_rtd.path_data.users [selected_path.ownername].paths[selected_path.pathname] if path_data == nil then return true end if fields["btn_unlock_path"] then path_data.locked = false else path_data.locked = true end mobf_path.save() return true end if fields["btn_show_points"] then local all_paths = mobf_path.get_pathlist(tabdata.playername,tabdata.is_admin) local selected_path = all_paths[tabdata.selected_entry] if selected_path == nil then return true end mobf_path.show_pathmarkers(selected_path.ownername,selected_path.pathname) return true end if fields["btn_delete_path"] then local all_paths = mobf_path.get_pathlist(tabdata.playername,tabdata.is_admin) local selected_path = all_paths[tabdata.selected_entry] if selected_path == nil then return true end --TODO add confirmation dialog mobf_path.delete_path(selected_path.ownername,selected_path.pathname) return true end return false end mobf_settings_tab_paths = { name = "paths", caption = fgettext("Paths"), cbf_formspec = get_formspec, cbf_button_handler = handle_settings_buttons, tabsize = {width=12,height=9} }mobf_core-2.5.1/mobf_settings/tab_restore_mobs.lua000066400000000000000000000056531264104133000223540ustar00rootroot00000000000000-------------------------------------------------------------------------------- -- Mob Framework Settings Mod by Sapier -- -- You may copy, use, modify or do nearly anything except removing this -- copyright notice. -- And of course you are NOT allowed to pretend you have written it. -- --! @file tab_restore_mobs.lua --! @brief settings gui for mobf --! @copyright Sapier --! @author Sapier --! @date 2014-05-30 -- -- Contact sapier a t gmx net -------------------------------------------------------------------------------- local function get_formspec(tabview, name, tabdata) if tabdata.selected_entry == nil then tabdata.selected_entry = 0 end local tablehead = "Mobtype,Reason,Owner," local content = "" for n=1,#mobf.current_preserve_list, 1 do if mobf.current_preserve_list[n].owner == tabdata.playername or tabdata.is_admin then content = content .. mobf.current_preserve_list[n].modname .. ":" .. mobf.current_preserve_list[n].name .. "," .. mobf.current_preserve_list[n].reason .. "," .. mobf.current_preserve_list[n].owner if n ~= #mobf.current_preserve_list then content = content .. "," end end end local retval = "tablecolumns[text,width=16;text,width=25;text,width=6]" .. "table[0.25,0.25;11.25,8;tbl_lost_and_found;" .. tablehead .. content .. ";" .. tabdata.selected_entry .. "]" if tabdata.selected_entry ~= 0 then retval = retval .. "button[0.25,8.5;3.75,0.5;btn_restore_mob;" .. fgettext("Take") .. "]" end return retval end local function handle_settings_buttons(self, fields, tabname, tabdata) if fields["tbl_lost_and_found"] then local event = core.explode_table_event(fields["tbl_lost_and_found"]) if event.type == "CHG" then tabdata.selected_entry = event.row end return true; end if fields["btn_restore_mob"] then local elementcount = 0 local player = core.get_player_by_name(tabdata.playername) if not player then return true end for i=1,#mobf.current_preserve_list,1 do mobf_assert_backtrace(tabdata ~= nil) mobf_assert_backtrace(mobf.current_preserve_list[i] ~= nil) if mobf.current_preserve_list[i].owner == tabdata.playername or tabdata.isadmin then elementcount = elementcount +1 end if elementcount == (tabdata.selected_entry-1) then --ADD to inventory local inventory_add_result = player:get_inventory():add_item("main", mobf.current_preserve_list[i].modname ..":".. mobf.current_preserve_list[i].name.." 1") --remove from list if inventory_add_result:is_empty() then table.remove(mobf.current_preserve_list,i) mobf_set_world_setting("mobf_preserve_mobs", core.serialize(mobf.current_preserve_list)) end return true end end return true end return false end mobf_settings_tab_preserve = { name = "preserve", caption = fgettext("Lost mobs"), cbf_formspec = get_formspec, cbf_button_handler = handle_settings_buttons, tabsize = {width=12,height=9} }mobf_core-2.5.1/modpack.txt000066400000000000000000000000001264104133000156110ustar00rootroot00000000000000mobf_core-2.5.1/trap/000077500000000000000000000000001264104133000144125ustar00rootroot00000000000000mobf_core-2.5.1/trap/License.txt000066400000000000000000000002011264104133000165260ustar00rootroot00000000000000Licenses Everything not mentioned: CC-BY-SA 3.0, Author sapier URL: http://creativecommons.org/licenses/by-sa/3.0/de/legalcodemobf_core-2.5.1/trap/depends.txt000066400000000000000000000000641264104133000165750ustar00rootroot00000000000000default animal_vombie mobf animalmaterials intllib? mobf_core-2.5.1/trap/init.lua000066400000000000000000000047341264104133000160700ustar00rootroot00000000000000-- Boilerplate to support localized strings if intllib mod is installed. local S if (minetest.get_modpath("intllib")) then dofile(minetest.get_modpath("intllib").."/intllib.lua") S = intllib.Getter(minetest.get_current_modname()) else S = function ( s ) return s end end local version = "0.0.3" minetest.log("action","MOD: trap mod loading ...") minetest.register_craftitem("trap:undead", { description = S("Trap for undead mobs"), image = minetest.inventorycube("trap_undead.png","trap_undead.png","trap_undead.png"), on_place = function(item, placer, pointed_thing) if pointed_thing.type == "node" then local pos = pointed_thing.above local newobject = minetest.add_entity(pos,"trap:undead_ent") item:take_item() return item end end }) minetest.register_craft({ output = "trap:undead 1", recipe = { {'default:wood', 'default:wood','default:wood'}, {'default:wood', "animalmaterials:scale_golden",'default:wood'}, {'default:wood','default:wood','default:wood'}, } }) --Entity minetest.register_entity(":trap:undead_ent", { physical = true, collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, visual = "cube", textures = {"trap_undead.png","trap_undead.png","trap_undead.png","trap_undead.png","trap_undead.png","trap_undead.png"}, on_step = function(self,dtime) local now = os.time(os.date('*t')) if now ~= self.last_check_time then local pos = self.object:getpos() local objectlist = minetest.get_objects_inside_radius(pos,2) for index,value in pairs(objectlist) do --print("checking " .. index .. ": ",value) local luaentity = value:get_luaentity() --TODO check if mobf names are required to use here if luaentity ~= nil and luaentity.name == "animal_vombie:vombie" then spawning.remove(luaentity) self.object:remove() minetest.add_node(pos,{name="trap:cought_vombie"}) end end self.last_check_time = now end end, on_punch = function(self,player) player:get_inventory():add_item("main", "trap:undead 1") self.object:remove() end, last_check_time = -1, }) minetest.register_node("trap:cought_vombie", { description = S("Trap containing vombie"), tiles = {"trap_cought_vombie.png"}, drawtype = "normal", groups = { snappy=3 }, drop = "animal_vombie:vombie", light_source = 2, }) minetest.log("action","MOD: trap mod version " .. version .. " loaded") mobf_core-2.5.1/trap/locale/000077500000000000000000000000001264104133000156515ustar00rootroot00000000000000mobf_core-2.5.1/trap/locale/de.txt000066400000000000000000000001631264104133000170020ustar00rootroot00000000000000# Translation by Xanthin Trap for undead mobs = Falle fuer untote Wesen Trap containing vombie = Falle mit Vombie mobf_core-2.5.1/trap/locale/es.txt000066400000000000000000000003431264104133000170210ustar00rootroot00000000000000# Spanish translation for Animals Modpack. # Traducción al español de Animals Modpack. # Author/Autor: Diego Martínez Trap for undead mobs = Trampa para no muertos Trap containing vombie = Trampa conteniendo vombie mobf_core-2.5.1/trap/locale/template.txt000066400000000000000000000000761264104133000202300ustar00rootroot00000000000000# Template Trap for undead mobs = Trap containing vombie = mobf_core-2.5.1/trap/textures/000077500000000000000000000000001264104133000162755ustar00rootroot00000000000000mobf_core-2.5.1/trap/textures/trap_cought_vombie.png000066400000000000000000000030351264104133000226640ustar00rootroot00000000000000PNG  IHDR@@% sRGB pHYs  tIME(;^BBIDAThZjI=n XHN f;f&|$1# bFa\oUztSqW٤D]kޚ-re)̸ّO:Hpoʞ}NgyV'\JD*p:_Jb× {YWl8Ȑ'uHFul6;"}XT)Q]zG äj>K Irr9Veu:/v-Y#r=>2۷ZvvܽO~0Ɣ`fp;p}q \O_|ON(̃8p} |gaS2V>PYdR6Lp!L{;X;Ko{V9c~ ~>} 25> {;$-Y1-cIɄ>_-KO_WLߘϦe >`6leZQԴdQy tzbf[xb@Yl QAD0 >R:5N9<"&R6CL'@B l "᢭81 )k 9v%9!21wm}Qdd9 )6  ^3|Ň31ҍz_ue$a RYf"Hb_M򁤿sۗ2ǾT[L3G:d6'/:9J|NOo j<*PR[mѠcLXРEpL ZoLHlV666bO "V9R L 2V8ض9.k|9U~D4LJw :F{4s|8ZBֆ⅗9^;Ơ>N;'Z)qO4&Y88eD=̾I-~T|U,wlS'y{yxBWt1)̕Wo}f݃w??llE{,b+W.vRu؊eoqk+!5-bÜLU[V# z{p6) Q&1@Fȸ t|`SKIENDB`mobf_core-2.5.1/trap/textures/trap_undead.png000066400000000000000000000060501264104133000212720ustar00rootroot00000000000000PNG  IHDR@@% sRGB pHYs  tIME* IDAThZ[kdIr"2S7$ʹfz0t/Gc `Y0?dƦw{g-Tsˌ?uuJuZC'O|_\22g?븚7?pWf%#_ 9x;)R#v+I6ȻkcZb\}['~D͓}4?y{<Fy9Y=lPh$O"U܊&~y6xЛŹ;W\1gﮝ;tFys>~k4/?_k' %^j& sT*])0y-N*@`(ZH?l(l; Ьne/% V3踧%) pwfv@zHw  _عDXkՒ]ߩnAM`  $AMn F{Kp',` xldi?#q&ˁx0cq2`HԀD&G?Q3[}Q`GyՌ3SL0D %Ԃ`wD }c wG7X7McZjF[@Xǜ>1eK1:Zq6/22nKrSOڑHadn9e$޳1QAl;*RTuzzjf?'բ:a<AzT,x0-c0sdeNŝfu]̸fy ~}/] jXTUED_w"j۶rpmf777u5l#5j@pCTFiX_T`a?Fl6ۨxhrJAi^yM]֯͸g` @5`LUyB ݖ\,,XŸFbT{xiLDB@irYl(".pz}=IZ / `U7_8b{_/f|—7/yƜïWcݬ[?7ۣ,GTA`F؝⅜ۢ46fӾ{Vz}*AbQ;ĴdCܐԩz%:Ղ~,8HVSypG;hʗʡ5J߼fl 5"h: LdR4hmc Ÿ.3lq رtg/Bj GP %/YL޲ ~eԇ#_ G]C l `*j/E1)ovkUp(ErΜ:t2l>ۃ,~u}W]`F(eA۶](gze=m–L)0^,7 LRҶ5m'Z@ `λR $ :<̒< Ar>>}t.^P(2A?0G 9d6/6M9Hd A^&E6ݫxAWkCNY BlDnDU U(kiE d"Zq93Sv!}g&̣K.4hWIg1ys~z0JYWe kI|W܈IB-7kN}ŰNvz}?i\Z{c˛f\դž3 %p/ ]ֱ6Hq&^?:MSn^M‘d?-ivz?׮em7Kv:5}130˽sJhTRPi!j+OxP$|!d”UB65-)`tr7ҫi!9{tݵ#P>DIkC3O=e[7oKTZ@l.ބ]U˫^%^$i=]=ܧ=ʎU cɧ9(PVY6q&T),ccl^vuq"Ŭ!-LBTUԺfmș9!bt~UA#qk:7e00Zťu8ev5 JR&lb*48'P}pA>>tS q8ܮfرI+~l{dۿcw|ob}9u<+w߃w -F5.r?q1j$m?EP÷ WgWӎƅ=6nmCڸڽz#+j\ۣuf# r < d^;2.?ySC] i]S$)IENDB`