]> git.lizzy.rs Git - Crafter.git/blob - mods/itemhandling/init.lua
Stop items from crashing in extremely specific cases
[Crafter.git] / mods / itemhandling / init.lua
1 collection = {}
2 collection.collection_height = 0.5 -- the height of the collection based off the player's origin y height
3 collection.magnet_radius = 2 -- the radius of the item magnet
4 collection.allow_lower = false -- false = items below origin y of player will not be collected --true = player will collect all objects in radius --false = minecraft style --true = pilzadam style
5 collection.collection_time = 2.5 --the time which the item will be collected
6
7 local path = minetest.get_modpath("itemhandling")
8 dofile(path.."/magnet.lua")
9
10
11 local creative_mode = minetest.settings:get_bool("creative_mode")
12
13 --handle node drops
14 --survival
15 if not creative_mode then
16         function minetest.handle_node_drops(pos, drops, digger)
17                 local meta = digger:get_wielded_item():get_meta()
18                 local slippery =  meta:get_int("slippery")
19                 local careful = meta:get_int("careful")
20                 local fortune = meta:get_int("fortune") + 1
21                 local autorepair = meta:get_int("autorepair")
22                 local spiky = meta:get_int("spiky")
23                 if careful > 0 then
24                         drops = {minetest.get_node(pos).name}
25                 end
26                 for i = 1,fortune do
27                         for _,item in ipairs(drops) do
28                                 local count, name
29                                 if type(item) == "string" then
30                                         count = 1
31                                         name = item
32                                 else
33                                         count = item:get_count()
34                                         name = item:get_name()
35                                 end
36                                 for i=1,count do
37                                         local obj = minetest.add_item(pos, name)
38                                         if obj ~= nil then
39                                                 local x=math.random(-2,2)*math.random()
40                                                 local y=math.random(2,5)
41                                                 local z=math.random(-2,2)*math.random()
42                                                 obj:set_velocity({x=x, y=y, z=z})
43                                         end
44                                 end
45                         end
46                 local experience_amount = minetest.get_item_group(minetest.get_node(pos).name,"experience")
47                 if experience_amount > 0 then
48                     minetest.throw_experience(pos, experience_amount)
49                 end
50                 end
51                 --make the player drop their "slippery" item
52                 if slippery > 0 and math.random(0,1000) < slippery then
53                         minetest.item_drop(digger:get_wielded_item(), digger, digger:get_pos())
54                         digger:set_wielded_item("")
55                 end
56                 
57                 --auto repair the item
58                 if autorepair > 0 and math.random(0,1000) < autorepair then
59                         local itemstack = digger:get_wielded_item()
60                         itemstack:add_wear(autorepair*-100)
61                         digger:set_wielded_item(itemstack)
62                 end
63                 
64                 --hurt the player randomly
65                 if spiky > 0 and math.random(0,1000) < spiky then
66                         digger:set_hp(digger:get_hp()-spiky)
67                 end
68         end
69 --creative
70 else
71         function minetest.handle_node_drops(pos, drops, digger)
72         end
73     minetest.register_on_dignode(function(pos, oldnode, digger)
74                 if digger and digger:is_player() then
75                         local inv = digger:get_inventory()
76                         if inv and not inv:contains_item("main", oldnode) and inv:room_for_item("main", oldnode) then
77                                 inv:add_item("main", oldnode)
78                         end
79                 end
80         end)
81         minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
82                 return(itemstack:get_name())
83         end)
84 end
85
86 -- Minetest: builtin/item_entity.lua
87
88 function minetest.spawn_item(pos, item)
89         -- Take item in any format
90         local stack = ItemStack(item)
91         local obj = minetest.add_entity(pos, "__builtin:item")
92         -- Don't use obj if it couldn't be added to the map.
93         if obj then
94                 obj:get_luaentity():set_item(stack:to_string())
95         end
96         return obj
97 end
98
99 function minetest.throw_item(pos, item)
100         -- Take item in any format
101         local stack = ItemStack(item)
102         local obj = minetest.add_entity(pos, "__builtin:item")
103         -- Don't use obj if it couldn't be added to the map.
104         if obj then
105                 obj:get_luaentity():set_item(stack:to_string())
106                 local x=math.random(-2,2)*math.random()
107                 local y=math.random(2,5)
108                 local z=math.random(-2,2)*math.random()
109                 obj:set_velocity({x=x, y=y, z=z})
110         end
111         return obj
112 end
113
114
115 function minetest.throw_experience(pos, amount)
116     for i = 1,amount do
117         local obj = minetest.add_entity(pos, "experience:orb")
118         -- Don't use obj if it couldn't be added to the map.
119         if obj then
120             local x=math.random(-2,2)*math.random()
121             local y=math.random(2,5)
122             local z=math.random(-2,2)*math.random()
123             obj:set_velocity({x=x, y=y, z=z})
124         end
125     end
126         --return obj
127 end
128
129 --override drops
130 function minetest.item_drop(itemstack, dropper, pos)
131         local dropper_is_player = dropper and dropper:is_player()
132         local p = table.copy(pos)
133         local cnt
134         if dropper_is_player then
135                 local sneak = dropper:get_player_control().sneak
136                 p.y = p.y + 1.2
137                 if not sneak then
138                         cnt = itemstack:get_count()
139                 else
140                         cnt = 1
141                 end
142         else
143                 cnt = itemstack:get_count()
144         end
145         local item = itemstack:take_item(cnt)
146         local obj = minetest.add_item(p, item)
147         if obj then
148                 if dropper_is_player then
149                         local dir = dropper:get_look_dir()
150                         dir.x = dir.x * 2.9
151                         dir.y = dir.y * 2.9 + 2
152                         dir.z = dir.z * 2.9
153                         dir = vector.add(dir,dropper:get_player_velocity())
154                         obj:set_velocity(dir)
155                         obj:get_luaentity().dropped_by = dropper:get_player_name()
156                         obj:get_luaentity().collection_timer = 0
157                 end
158                 return itemstack
159         end
160         -- If we reach this, adding the object to the
161         -- environment failed
162 end
163
164 -- If item_entity_ttl is not set, enity will have default life time
165 -- Setting it to -1 disables the feature
166
167 local time_to_live = tonumber(minetest.settings:get("item_entity_ttl")) or 300
168 local gravity = tonumber(minetest.settings:get("movement_gravity")) or 9.81
169
170
171 minetest.register_entity(":__builtin:item", {
172         initial_properties = {
173                 hp_max = 1,
174                 physical = true,
175                 collide_with_objects = false,
176                 collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3},
177                 visual = "wielditem",
178                 visual_size = {x = 0.4, y = 0.4},
179                 textures = {""},
180                 spritediv = {x = 1, y = 1},
181                 initial_sprite_basepos = {x = 0, y = 0},
182                 is_visible = false,
183                 pointable = false,
184         },
185
186         itemstring = "",
187         moving_state = true,
188         slippery_state = false,
189         physical_state = true,
190         -- Item expiry
191         age = 0,
192         -- Pushing item out of solid nodes
193         force_out = nil,
194         force_out_start = nil,
195         --Collection Variables
196         collection_timer = 2,
197         collection_timer_goal = collection.collection_time,
198         collection_height = collection.collection_height,
199         collectable = false,
200         try_timer = 0,
201         collected = false,
202         delete_timer = 0,
203         radius = collection.magnet_radius,
204         time_to_live = time_to_live,
205
206         set_item = function(self, item)
207                 local stack = ItemStack(item or self.itemstring)
208                 self.itemstring = stack:to_string()
209                 if self.itemstring == "" then
210                         -- item not yet known
211                         return
212                 end
213
214                 -- Backwards compatibility: old clients use the texture
215                 -- to get the type of the item
216                 local itemname = stack:is_known() and stack:get_name() or "unknown"
217
218                 local max_count = stack:get_stack_max()
219                 local count = math.min(stack:get_count(), max_count)
220
221                 local size = 0.21
222                 local coll_height = size * 0.75
223                 local def = minetest.registered_nodes[itemname]
224                 local glow = def and def.light_source
225
226                 self.object:set_properties({
227                         is_visible = true,
228                         visual = "wielditem",
229                         textures = {itemname},
230                         visual_size = {x = size, y = size},
231                         collisionbox = {-size, -0.21, -size,
232                                 size, coll_height, size},
233                         selectionbox = {-size, -size, -size, size, size, size},
234                         automatic_rotate = math.pi * 0.5 * 0.2 / size,
235                         wield_item = self.itemstring,
236                         glow = glow,
237                 })
238
239         end,
240
241         get_staticdata = function(self)
242                 return minetest.serialize({
243                         itemstring = self.itemstring,
244                         age = self.age,
245                         dropped_by = self.dropped_by,
246                         collection_timer = self.collection_timer,
247                         collectable = self.collectable,
248                         try_timer = self.try_timer,
249                         collected = self.collected,
250                         delete_timer = self.delete_timer,
251                         collector = self.collector,
252                         magnet_timer = self.magnet_timer,
253                 })
254         end,
255
256         on_activate = function(self, staticdata, dtime_s)
257                 if string.sub(staticdata, 1, string.len("return")) == "return" then
258                         local data = minetest.deserialize(staticdata)
259                         if data and type(data) == "table" then
260                                 self.itemstring = data.itemstring
261                                 self.age = (data.age or 0) + dtime_s
262                                 self.dropped_by = data.dropped_by
263                                 self.magnet_timer = data.magnet_timer
264                                 self.collection_timer = data.collection_timer
265                                 self.collectable = data.collectable
266                                 self.try_timer = data.try_timer
267                                 self.collected = data.collected
268                                 self.delete_timer = data.delete_timer
269                                 self.collector = data.collector
270                                 --print("restored timer: "..self.collection_timer)
271                         end
272                 else
273                         self.itemstring = staticdata
274                         
275                         local x=math.random(-2,2)*math.random()
276                         local y=math.random(2,5)
277                         local z=math.random(-2,2)*math.random()
278                         self.object:set_velocity(vector.new(x,y,z))
279                      -- print(self.collection_timer)
280                 end
281                 self.object:set_armor_groups({immortal = 1})
282                 self.object:set_velocity({x = 0, y = 2, z = 0})
283                 self.object:set_acceleration({x = 0, y = -gravity, z = 0})
284                 self:set_item()
285         end,
286
287         enable_physics = function(self)
288                 if not self.physical_state then
289                         self.physical_state = true
290                         self.object:set_properties({physical = true})
291                         self.object:set_velocity({x=0, y=0, z=0})
292                         self.object:set_acceleration({x=0, y=-gravity, z=0})
293                 end
294         end,
295
296         disable_physics = function(self)
297                 if self.physical_state then
298                         self.physical_state = false
299                         self.object:set_properties({physical = false})
300                         self.object:set_velocity({x=0, y=0, z=0})
301                         self.object:set_acceleration({x=0, y=0, z=0})
302                 end
303         end,
304         magnet_timer = 0,
305         on_step = function(self, dtime,moveresult)
306                 --if item set to be collected then only execute go to player
307                 if self.collected == true then
308                         if not self.collector then
309                                 self.object:remove()
310                                 return
311                         end
312                         local collector = minetest.get_player_by_name(self.collector)
313                         if collector then
314                                 self.magnet_timer = self.magnet_timer + dtime
315                                 self.object:set_acceleration(vector.new(0,0,0))
316                                 self.disable_physics(self)
317                                 --get the variables
318                                 local pos = self.object:get_pos()
319                                 local pos2 = collector:get_pos()
320                                 local player_velocity = collector:get_player_velocity()
321                                 pos2.y = pos2.y + self.collection_height
322                                                                 
323                                 local direction = vector.normalize(vector.subtract(pos2,pos))
324                                 local distance = vector.distance(pos2,pos)
325                                                                 
326                                 --remove if too far away
327                                 if distance > self.radius then
328                                         distance = 0
329                                 end
330                                                                 
331                                 local multiplier = (self.radius*5) - distance
332                                 local velocity = vector.multiply(direction,multiplier)
333                                 
334                                 local velocity = vector.add(player_velocity,velocity)
335                                 
336                                 self.object:set_velocity(velocity)
337                                 
338                                 if distance < 0.3 or self.magnet_timer > 0.2 or (self.old_magnet_distance and self.old_magnet_distance < distance) then
339                                         self.object:remove()
340                                 end
341                                 
342                                 self.old_magnet_distance = distance
343                                 --self.delete_timer = self.delete_timer + dtime
344                                 --this is where the item gets removed from world
345                                 --if self.delete_timer > 1 then
346                                 --      self.object:remove()
347                                 --end
348                                 return
349                         else
350                                 --print(self.collector.." does not exist")
351                                 self.object:remove()
352                         end
353                 end
354                 
355                 --allow entity to be collected after timer
356                 if self.collectable == false and self.collection_timer >= self.collection_timer_goal then
357                         self.collectable = true
358                 elseif self.collectable == false then
359                         self.collection_timer = self.collection_timer + dtime
360                 end
361                                 
362                 self.age = self.age + dtime
363                 if self.time_to_live > 0 and self.age > self.time_to_live then
364                         self.itemstring = ""
365                         self.object:remove()
366                         return
367                 end
368
369                 local pos = self.object:get_pos()
370                 --stop crashing if this ever fails
371                 if not pos then
372                         return
373                 end
374                 local node = minetest.get_node_or_nil({
375                         x = pos.x,
376                         y = pos.y + self.object:get_properties().collisionbox[2] - 0.05,
377                         z = pos.z
378                 })
379                 
380
381                 -- Remove nodes in 'ignore'
382                 if node and node.name == "ignore" then
383                         self.itemstring = ""
384                         self.object:remove()
385                         return
386                 end
387
388                 --burn inside fire nodes
389                 local node_inside = minetest.get_node_or_nil(pos)
390                 if node_inside and (node_inside.name == "fire:fire" or node_inside.name == "nether:lava" or node_inside.name == "nether:lavaflow" or node_inside.name == "main:lava" or node_inside.name == "main:lavaflow") then
391                         minetest.add_particlespawner({
392                                 amount = 6,
393                                 time = 0.001,
394                                 minpos = pos,
395                                 maxpos = pos,
396                                 minvel = vector.new(-1,0.5,-1),
397                                 maxvel = vector.new(1,1,1),
398                                 minacc = {x=0, y=1, z=0},
399                                 maxacc = {x=0, y=2, z=0},
400                                 minexptime = 1.1,
401                                 maxexptime = 1.5,
402                                 minsize = 1,
403                                 maxsize = 2,
404                                 collisiondetection = false,
405                                 vertical = false,
406                                 texture = "smoke.png",
407                         })
408                         minetest.sound_play("fire_extinguish", {pos=pos,gain=0.3,pitch=math.random(80,100)/100})
409                         self.itemstring = ""
410                         self.object:remove()
411                         return
412                 end
413
414
415                 local is_stuck = false
416                 local snode = minetest.get_node_or_nil(pos)
417                 if snode then
418                         local sdef = minetest.registered_nodes[snode.name] or {}
419                         is_stuck = (sdef.walkable == nil or sdef.walkable == true)
420                                 and (sdef.collision_box == nil or sdef.collision_box.type == "regular")
421                                 and (sdef.node_box == nil or sdef.node_box.type == "regular")
422                 end
423
424                 -- Push item out when stuck inside solid node
425                 if is_stuck then
426                         local shootdir
427                         local order = {
428                                 {x=1, y=0, z=0}, {x=-1, y=0, z= 0},
429                                 {x=0, y=0, z=1}, {x= 0, y=0, z=-1},
430                         }
431
432                         -- Check which one of the 4 sides is free
433                         for o = 1, #order do
434                                 local cnode = minetest.get_node(vector.add(pos, order[o])).name
435                                 local cdef = minetest.registered_nodes[cnode] or {}
436                                 if cnode ~= "ignore" and cdef.walkable == false then
437                                         shootdir = order[o]
438                                         break
439                                 end
440                         end
441                         -- If none of the 4 sides is free, check upwards
442                         if not shootdir then
443                                 shootdir = {x=0, y=1, z=0}
444                                 local cnode = minetest.get_node(vector.add(pos, shootdir)).name
445                                 if cnode == "ignore" then
446                                         shootdir = nil -- Do not push into ignore
447                                 end
448                         end
449
450                         if shootdir then
451                                 -- Set new item moving speed accordingly
452                                 local newv = vector.multiply(shootdir, 3)
453                                 self:disable_physics()
454                                 self.object:set_velocity(newv)
455
456                                 self.force_out = newv
457                                 self.force_out_start = vector.round(pos)
458                                 return
459                         end
460                 elseif self.force_out then
461                         -- This code runs after the entity got a push from the above code.
462                         -- It makes sure the entity is entirely outside the solid node
463                         local c = self.object:get_properties().collisionbox
464                         local s = self.force_out_start
465                         local f = self.force_out
466                         local ok = (f.x > 0 and pos.x + c[1] > s.x + 0.5) or
467                                 (f.y > 0 and pos.y + c[2] > s.y + 0.5) or
468                                 (f.z > 0 and pos.z + c[3] > s.z + 0.5) or
469                                 (f.x < 0 and pos.x + c[4] < s.x - 0.5) or
470                                 (f.z < 0 and pos.z + c[6] < s.z - 0.5)
471                         if ok then
472                                 -- Item was successfully forced out
473                                 self.force_out = nil
474                                 self:enable_physics()
475                         end
476                 end
477
478                 if not self.physical_state then
479                         return -- Don't do anything
480                 end
481
482                 -- Slide on slippery nodes
483                 local vel = self.object:get_velocity()
484                 local def = node and minetest.registered_nodes[node.name]
485                 local is_moving = (def and not def.walkable) or
486                         vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0
487                 local is_slippery = false
488
489                 if def and def.walkable then
490                         local slippery = minetest.get_item_group(node.name, "slippery")
491                         is_slippery = slippery ~= 0
492                         if is_slippery and (math.abs(vel.x) > 0.2 or math.abs(vel.z) > 0.2) then
493                                 -- Horizontal deceleration
494                                 local slip_factor = 4.0 / (slippery + 4)
495                                 self.object:set_acceleration({
496                                         x = -vel.x * slip_factor,
497                                         y = 0,
498                                         z = -vel.z * slip_factor
499                                 })
500                         elseif vel.y == 0 then
501                                 is_moving = false
502                                 --[[
503                                 local collisionbox = self.object:get_properties().collisionbox
504                                 local move_y = collisionbox[2]
505                                 if self.move_up == nil then
506                                         self.move_up = true
507                                 end
508                                 local addition = 0
509                                 if self.move_up == true then
510                                         move_y = move_y + (dtime/10)
511                                         addition = (dtime/8)
512                                         if move_y > -0.21 then
513                                                 self.move_up = false
514                                         end
515                                 elseif self.move_up == false then
516                                         move_y = move_y - (dtime/10)
517                                         if move_y < -0.5 then
518                                                 self.move_up = true
519                                         end
520                                 end
521                                 collisionbox[2] = move_y
522                                 self.object:set_properties({collisionbox=collisionbox,physical = true})
523                                 ]]--
524                         end
525                 end
526
527                 if self.moving_state == is_moving and self.slippery_state == is_slippery then
528                         -- Do not update anything until the moving state changes
529                         return
530                 end
531
532                 self.moving_state = is_moving
533                 self.slippery_state = is_slippery
534                 
535                 if is_moving then
536                         self.object:set_acceleration({x = 0, y = -gravity, z = 0})
537                 else
538                         self.object:set_acceleration({x = 0, y = 0, z = 0})
539                         self.object:set_velocity({x = 0, y = 0, z = 0})
540                 end
541         end,
542 })