mob_pickup.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. -----------------------------------------------------------------------------------------------------
  2. -- Allows to pick up a mob, carry him around in the player's inventory, and place the mob back
  3. -----------------------------------------------------------------------------------------------------
  4. -- uses mob_basics.find_mob_by_id and mob_basics.update_visual_size when available
  5. -- TODO: add after_place, after_pickup?
  6. -- the privilege allows admins/moderators to pick up and remove mobs which are undesired for whatever reason
  7. minetest.register_privilege("mob_pickup", { description = "allows to pick up mobs (which use mob_pickup) that are not your own", give_to_singleplayer = false});
  8. -- namespace used for functions and variables of this part of the mod
  9. mob_pickup = {}
  10. -- find the right object for an entity so that a mob can be stored in the player's inventory after pickup
  11. mob_pickup.entity_to_object_name = {}
  12. mob_pickup.pickup_success_msg = {}
  13. mob_pickup.place_success_msg = {}
  14. -- this can hold functions (in a table) which deny the pickup of a mob; structure:
  15. -- key: entity name
  16. -- value: function( self, player ) that gets an entity as parameter, returns '' on success; returns error message on failure
  17. mob_pickup.deny_pickup = {}
  18. -- if you want to deny placement - i.e. if there are already too many around or something like that; structure:
  19. -- key: entity name
  20. -- value: function( data, pos, player ) that gets an entity name as parameter, returns '' on success; returns error message on failure
  21. -- data is minetest.deserialize( item[ "metadata" ]); pos is the position where the mob is to be placed
  22. mob_pickup.deny_place = {}
  23. -----------------------------------------------------------------------------------------------------
  24. -- We need to know something about the mobs and their corresponding items
  25. -----------------------------------------------------------------------------------------------------
  26. -- the "functions" parameter may contain the deny_* functions
  27. mob_pickup.register_mob_for_pickup = function( entity_name, item_name, data )
  28. if( not( entity_name ) or not( item_name )) then
  29. return false;
  30. end
  31. -- has the mob been registered already?
  32. if( mob_pickup.entity_to_object_name[ entity_name ] ) then
  33. return false;
  34. end
  35. -- store which item the player will get when picking up the mob
  36. mob_pickup.entity_to_object_name[ entity_name ] = item_name;
  37. -- for animals, pickup might be denied if the player does not wield the appropriate lasso/cage/watever
  38. if( data and data.deny_pickup ) then
  39. mob_pickup.deny_pickup[ entity_name ] = data.deny_pickup;
  40. end
  41. if( data and data.deny_place ) then
  42. mob_pickup.deny_place[ entity_name ] = data.deny_place;
  43. end
  44. if( data and data.pickup_success_msg ) then
  45. mob_pickup.pickup_success_msg[ entity_name ] = data.pickup_success_msg;
  46. else
  47. mob_pickup.pickup_success_msg[ entity_name ] =
  48. 'Mob picked up. In order to use him again, just wield him and place him somewhere.';
  49. end
  50. if( data and data.place_success_msg ) then
  51. mob_pickup.place_success_msg[ entity_name ] = data.place_success_msg;
  52. else
  53. mob_pickup.place_success_msg[ entity_name ] =
  54. 'Mob placed.';
  55. end
  56. end
  57. -----------------------------------------------------------------------------------------------------
  58. -- Log when someone picks up a mob or places one (in case there's debate of who removed or placed a mob)
  59. -----------------------------------------------------------------------------------------------------
  60. -- The position of the mob is important here; also who picked him up (might not always be the owner)
  61. -- This is more or less a copy from mob_basics.log
  62. mob_pickup.log = function( msg, self, prefix )
  63. if( self==nil ) then
  64. minetest.log("action", '[mob_pickup] '..tostring( msg ) );
  65. else
  66. minetest.log("action", '[mob_pickup] '..tostring( msg )..
  67. ' id:'..tostring( self[ prefix..'_id'] )..
  68. ' typ:'..tostring( self[ prefix..'_typ'] or '?' )..
  69. ' prefix:'..tostring( prefix or '?' )..
  70. ' at:'..minetest.pos_to_string( self.object:get_pos() )..
  71. ' by:'..tostring( self[ prefix..'_owner'] )..'.');
  72. end
  73. end
  74. -----------------------------------------------------------------------------------------------------
  75. -- pick the mob up and store in the players inventory;
  76. -----------------------------------------------------------------------------------------------------
  77. -- the mob's data will be saved and he can be placed at another location
  78. -- NOTE: only mobs which are owned by the player can be picked up (unless the player has the mob_pickup priv)
  79. mob_pickup.pick_mob_up = function( self, player, menu_path, prefix, is_personalized, stored_data )
  80. if( not( self ) or not( player ) or not(self.name)) then
  81. return;
  82. end
  83. local pname = player:get_player_name();
  84. -- check the privs again to be sure that there's no maliscious client input
  85. if( not( (self[ prefix..'_owner'] and self[ prefix..'_owner'] == pname)
  86. or minetest.check_player_privs( pname, {mob_pickup=true}))) then
  87. minetest.chat_send_player( pname,
  88. 'Error: You do not own this mob and do not have the mob_pickup priv. Taking failed.');
  89. return;
  90. end
  91. local staticdata = {};
  92. if( not( stored_data )) then
  93. staticdata = self:get_staticdata();
  94. else
  95. staticdata = stored_data;
  96. end
  97. -- deserialize to do some tests
  98. local staticdata_table = minetest.deserialize( staticdata );
  99. if( not( staticdata_table[ prefix..'_name'] )
  100. or not( staticdata_table[ prefix..'_id' ] )
  101. or not( staticdata_table[ prefix..'_typ' ] )) then
  102. minetest.chat_send_player( pname,
  103. 'Error: This mob is misconfigured. Name, id or typ are missing. Please punch him in order to remove him.');
  104. return;
  105. end
  106. -- is picking this mob up allowed?
  107. local deny = mob_pickup.deny_pickup[ self.name ];
  108. if( not( stored_data ) and deny ) then
  109. local deny_msg = deny( self, player );
  110. if( deny_msg ~= '') then
  111. minetest.chat_send_player( pname,
  112. deny_msg );
  113. return;
  114. end
  115. end
  116. local mob_object_name = mob_pickup.entity_to_object_name[ self.name ];
  117. if( not( mob_object_name )) then
  118. minetest.chat_send_player( pname,
  119. 'Error: This mob is unknown by mob_pickup. Taking failed.');
  120. return;
  121. end
  122. local player_inv = player:get_inventory();
  123. -- no point in doing more if the player can't take the mob due to too few space
  124. if( not( player_inv:room_for_item("main", mob_object_name ))) then
  125. minetest.chat_send_player( pname,
  126. 'Error: You do not have a free inventory slot for the mob. Taking failed.');
  127. return;
  128. end
  129. -- create a stack with a general mob item
  130. local mob_as_item = ItemStack( mob_object_name );
  131. -- turn that stack data into a form we can manipulate
  132. local item = mob_as_item:to_table();
  133. -- the metadata field became available - it now stores the real data
  134. item[ "metadata" ] = staticdata;
  135. -- save the changed table
  136. mob_as_item:replace( item );
  137. if( stored_data ) then
  138. minetest.chat_send_player( pname,
  139. 'A copy of the mob has been dropped into your inventory.');
  140. -- put the copy of the mob into the players inventory
  141. player_inv:add_item( "main", mob_as_item );
  142. -- do not remove the mob
  143. return;
  144. end
  145. minetest.chat_send_player( pname,
  146. mob_pickup.pickup_success_msg[ self.name ] );
  147. mob_pickup.log( pname..' picked up', self, prefix );
  148. -- put the mob into the players inventory
  149. player_inv:add_item( "main", mob_as_item );
  150. -- remove the now obsolete mob
  151. self.object:remove();
  152. -- remove the mob from the stored list
  153. mob_basics.forget_mob( staticdata_table[ prefix..'_id' ] );
  154. end
  155. -----------------------------------------------------------------------------------------------------
  156. -- place the mob back into the world
  157. -----------------------------------------------------------------------------------------------------
  158. -- called from on_place: on_place = function( itemstack, placer, pointed_thing )
  159. -- is_personalized has to be true for all mobs that carry metadata
  160. mob_pickup.place_mob = function( itemstack, placer, pointed_thing, prefix, entity_name, is_personalized )
  161. if( not( placer )) then
  162. return itemstack;
  163. end
  164. local pname = placer:get_player_name();
  165. if( not( pointed_thing ) or pointed_thing.type ~= "node" ) then
  166. minetest.chat_send_player( pname,
  167. 'Error: No node selected for mob to spawn on. Cannot spawn him.');
  168. return itemstack;
  169. end
  170. local data = {};
  171. -- some mobs carry individual data (i.e. traders owned by players), wile others (i.e. animals) do not
  172. if( is_personalized ) then
  173. local item = itemstack:to_table();
  174. if( not( item[ "metadata"] ) or item["metadata"]=="" ) then
  175. minetest.chat_send_player( pname,
  176. 'Error: Mob is not properly configured. Cannot spawn him.');
  177. return itemstack;
  178. end
  179. data = minetest.deserialize( item[ "metadata" ]);
  180. if( not( data ) or data[ prefix..'_id'] == '') then
  181. minetest.chat_send_player( pname,
  182. 'Error: Mob is misconfigured. Cannot spawn him.');
  183. return itemstack;
  184. end
  185. -- if there is already a mob with the same id, do not create a new one
  186. if( mob_basics and mob_basics.find_mob_by_id( data[ prefix..'_id'], prefix )) then
  187. minetest.chat_send_player( pname,
  188. 'Error: A mob with that ID exists already. Please destroy this duplicate!');
  189. return itemstack;
  190. end
  191. end
  192. local pos = minetest.get_pointed_thing_position( pointed_thing, false );
  193. -- does this particular mob want to be placed there?
  194. local deny = mob_pickup.deny_place[ entity_name ];
  195. if( deny ) then
  196. local deny_msg = deny( data, pos, placer );
  197. if( deny_msg ~= '' ) then
  198. minetest.chat_send_player( pname,
  199. deny_msg );
  200. return itemstack;
  201. end
  202. end
  203. -- spawn a mob
  204. local object = minetest.add_entity( {x=pos.x, y=(pos.y+1.5), z=pos.z}, entity_name );
  205. if( not( object )) then
  206. minetest.chat_send_player( pname,
  207. 'Error: Spawning of mob failed.');
  208. return itemstack;
  209. end
  210. object:set_yaw( -1.14 );
  211. local self = object:get_luaentity();
  212. local tmp_id = self[ prefix..'_id'];
  213. -- transfer the data to the mob object
  214. for k,v in pairs( data ) do
  215. if( type( k )=='string' ) then
  216. local help = k:split( '_');
  217. if( help and #help>1 and help[1]==prefix ) then
  218. self[ k ] = v;
  219. end
  220. end
  221. end
  222. self[ prefix..'_animation' ] = 'stand';
  223. self.object:set_properties( { textures = { data[ prefix..'_texture'] }});
  224. if( mob_basics and mob_basics.update_visual_size ) then
  225. mob_basics.update_visual_size( self, data[ prefix..'_vsize'], false, prefix );
  226. end
  227. -- the mob was placed at a new location
  228. self[ prefix..'_pos'] = pos;
  229. minetest.chat_send_player( pname, mob_pickup.place_success_msg[ entity_name ]);
  230. mob_pickup.log( pname..' placed', self, prefix );
  231. mob_basics.forget_mob( tmp_id );
  232. mob_basics.update( self, prefix ); -- store data about this placed mob
  233. return '';
  234. end