Use TRUE ephemeral sounds
[minetest_flying_carpet.git] / init.lua
blob768b2265de53fce332a36998ef98c031e62be560
1 local S = minetest.get_translator("flying_carpet")
3 --
4 -- Helper functions
5 --
7 local function get_sign(i)
8 if i == 0 then
9 return 0
10 else
11 return i/math.abs(i)
12 end
13 end
15 local function get_velocity(v, yaw, y)
16 local x = math.cos(yaw)*v
17 local z = math.sin(yaw)*v
18 return {x=x, y=y, z=z}
19 end
21 local function get_v(v)
22 return math.sqrt(v.x^2+v.z^2)
23 end
25 local function get_v3(v)
26 return math.sqrt(v.x^2+v.y^2+v.z^2)
27 end
29 local function magic_particle(object, texture)
30 local src = object:get_pos()
31 local velo = object:get_velocity()
32 velo = vector.multiply(velo, 0.1)
33 if texture == nil then texture = "flying_carpet_magic_smoke.png" end
34 return minetest.add_particlespawner({
35 amount = 50,
36 time = 1,
37 minpos = {x=src.x-1.5, y=src.y-0.03, z=src.z-1.5},
38 maxpos = {x=src.x+1.5, y=src.y+0.03, z=src.z+1.},
39 minvel = {x=velo.x-0.4, y=velo.y-0.1, z=velo.z-0.4},
40 maxvel = {x=velo.x+0.4, y=velo.y+0.1, z=velo.z+0.4},
41 minexptime=4.5,
42 maxexptime=6,
43 minsize=1,
44 maxsize=1.25,
45 texture = texture,
47 end
49 local function death_particles(object)
50 local src = object:get_pos()
51 local yaw = object:get_yaw()
52 minetest.add_particlespawner({
53 amount = 50,
54 time = 0.1,
55 minpos = {x=src.x-1, y=src.y-0.02, z=src.z-1},
56 maxpos = {x=src.x+1, y=src.y+0.02, z=src.z+1},
57 minvel = {x=-0.01, y=0, z=0.01},
58 maxvel = {x=0.01, y=0, z=0.01},
59 minexptime=4.5,
60 maxexptime=6,
61 minsize=1.5,
62 maxsize=1.5,
63 texture = "flying_carpet_death.png",
65 end
68 -- Initialization
71 local mana_regen_cost = 1.5
73 local mod_model = minetest.get_modpath("player_api") ~= nil
74 local mod_mana = minetest.get_modpath("mana") ~= nil
75 local mod_craft = minetest.get_modpath("default") ~= nil and minetest.get_modpath("wool")
76 local mod_craft_mcl2 = minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_wool") and minetest.get_modpath("mesecons_torch")
77 local mod_doc_items = minetest.get_modpath("doc_items") ~= nil
78 local mod_doc_identifier = minetest.get_modpath("doc_identifier") ~= nil
81 -- Carpet entity
84 local init_velocities = function()
85 local ret = {}
86 for i=1,50 do
87 table.insert(ret,{x=0,y=0,z=0})
88 end
89 return ret
90 end
92 local update_velocities = function(velocities, new_value)
93 table.insert(velocities, 1, new_value)
94 table.remove(velocities, 51)
95 return velocities
96 end
98 local carpet = {
99 physical = true,
100 collide_with_objects = false,
101 collisionbox = {-1,-0.02,-1, 1,0.02,1},
102 visual = "mesh",
103 mesh = "flying_carpet_model.obj",
104 textures = {"flying_carpet_surface.png"},
106 driver = nil, -- Attached player object
107 v = 0, -- Velocity on the horizontal plane
108 h = 0, -- Velocity on the vertical plane (height speed)
109 falling = false, -- Fall mode: Carpet falls down and can't be controlled
110 flying = false, -- If it is in fly mode
111 prefly = true, -- Prefly mode: Carpet has been placed and waits to be boarded
112 starttimer = 0, -- Time in seconds since the flight has been started
113 weartimer = 0, -- Timer to apply wear for flying
114 viscosity = 0, -- Viscosity of node in which the carpet is in (no liquid = 0)
115 wear = 0, -- Damage / wear of carpet, corresponds to tool wear
116 sound_slide = nil, -- Sound handle for sliding loop
117 sound_flight = nil, -- Sound handle for flight loop
118 sound_death_warning=nil, -- Sound handle for flight loop with high damage
119 slidepos = nil, -- Position of nearby node which has been checked for sliding
120 slidenode = nil, -- Node definition of nearby node which has been checked for sliding
121 liquidpos = nil, -- Position of nearby node which has been checked for being a liquid
122 liquidnode = nil, -- Node definition of nearby node which has been checked for being a liquid
123 past_velocities = init_velocities(), -- List of recent velocities
124 past_diff = 0, -- Difference of the past two velocities, used to deal collision damage to player
125 last_damage = nil, -- How much damage has been dealt to the player in the last step
126 last_particle = 1, -- Particle spawning timer
129 -- Rightclick allows to attach and detach to/from carpet
130 function carpet:on_rightclick(clicker)
131 if not clicker or not clicker:is_player() then
132 return
134 local name = clicker:get_player_name()
135 -- Detach attached player
136 if self.driver and clicker == self.driver then
137 self.driver = nil
138 clicker:set_detach()
139 if mod_model then
140 player_api.player_attached[name] = false
141 player_api.set_animation(clicker, "stand", 30)
143 if mod_mana and self.flying then
144 mana.setregen(clicker:get_player_name(), mana.getregen(clicker:get_player_name())+mana_regen_cost)
145 clicker:get_meta():set_int("flying_carpet:mana_cost", 0)
147 -- Attach player (must be unoccupied and slow)
148 elseif not self.driver and clicker:get_attach() == nil and ((self.v < 0.5 and math.abs(self.h) < 1) or self.prefly) then
149 self.driver = clicker
150 clicker:set_look_horizontal(self.object:get_yaw()-math.pi/2)
151 clicker:set_attach(self.object, "", {x=-4,y=0.3,z=0}, {x=0,y=90,z=0})
152 if mod_model then
153 player_api.player_attached[name] = true
154 minetest.after(0.2, function(player)
155 if player and player:is_player() then
156 player_api.set_animation(player, "sit", 30)
158 end, clicker)
160 if mod_mana and (self.flying or self.prefly) then
161 mana.setregen(clicker:get_player_name(), mana.getregen(clicker:get_player_name())-mana_regen_cost)
162 clicker:get_meta():set_int("flying_carpet:mana_cost", 1)
164 if self.prefly then
165 self.flying = true
166 self.falling = false
167 self.prefly = false
168 self.object:set_velocity(get_velocity(4, self.object:get_yaw(), 0))
173 function carpet:on_activate(staticdata, dtime_s)
174 self.object:set_armor_groups({fabric=100})
175 if staticdata ~= nil and staticdata ~= "" then
176 local data = minetest.deserialize(staticdata)
177 if data then
178 self.v = data.v
179 self.h = data.h
180 self.falling = data.falling
181 self.flying = data.flying
182 self.prefly = data.prefly
183 self.viscosity = data.viscosity
184 self.wear = data.wear
185 else
186 minetest.log("error", "[flying_carpet] carpet:on_activate: deserialized data was nil!")
187 return
192 function carpet:get_staticdata()
193 if self.sound_flight then
194 minetest.sound_stop(self.sound_flight)
195 self.sound_flight = nil
197 if self.sound_slide then
198 minetest.sound_stop(self.sound_slide)
199 self.sound_slide = nil
201 if self.sound_death_warning then
202 minetest.sound_stop(self.sound_death_warning)
203 self.sound_death_warning = nil
205 local data = {}
206 data.v = self.v
207 data.h = self.h
208 data.falling = self.falling
209 data.flying = self.flying
210 data.prefly = self.prefly
211 data.viscosity = self.viscosity
212 data.wear = self.wear
213 return minetest.serialize(data)
216 -- Stop flying or collect carpet on punching it
217 function carpet:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
218 if puncher and puncher:is_player() then
219 -- Owner of flying carpet punches
220 if (self.driver == puncher and self.flying) then
221 -- Go into falling mode immediately
222 minetest.sound_play("flying_carpet_out_of_energy", {pos = self.object:get_pos(), gain=0.7}, true)
223 magic_particle(self.object)
224 if mod_mana then
225 mana.setregen(puncher:get_player_name(), mana.getregen(puncher:get_player_name())+mana_regen_cost)
226 puncher:get_meta():set_int("flying_carpet:mana_cost", 0)
228 self.falling = true
229 self.flying = false
230 if self.sound_flight then
231 minetest.sound_stop(self.sound_flight)
232 self.sound_flight = nil
234 if self.sound_death_warning then
235 minetest.sound_stop(self.sound_death_warning)
236 self.sound_death_warning = nil
238 -- Someone punches slow non-flying carpet
239 elseif (self.driver == nil or self.driver == puncher) and (self.v < 1 and math.abs(self.h) < 1) and not self.flying then
240 -- Collect carpet
241 local carpetitem = ItemStack("flying_carpet:carpet")
242 carpetitem:set_wear(self.wear)
243 local inv = puncher:get_inventory()
244 if not minetest.settings:get_bool("creative_mode") or not inv:contains_item("main", "flying_carpet:carpet") then
245 local leftover = inv:add_item("main", carpetitem)
246 -- Put item into world if no space in inventory
247 if not leftover:is_empty() then
248 minetest.add_item(self.object:get_pos(), leftover)
251 minetest.sound_play("flying_carpet_take", {pos=self.object:get_pos(), gain=0.3}, true)
252 puncher:set_detach()
253 if mod_model then
254 player_api.player_attached[puncher:get_player_name()] = false
256 if self.sound_flight then
257 minetest.sound_stop(self.sound_flight)
258 self.sound_flight = nil
260 if self.sound_death_warning then
261 minetest.sound_stop(self.sound_death_warning)
262 self.sound_death_warning = nil
264 if self.sound_slide then
265 minetest.sound_stop(self.sound_slide)
266 self.sound_slide = nil
268 self.object:remove()
273 function carpet:on_step(dtime)
274 if not self.prefly then
276 self.v = get_v(self.object:get_velocity())*get_sign(self.v)
277 self.h = self.object:get_velocity().y
279 local spawn_particles
280 self.last_particle = self.last_particle + 0.1
281 if self.last_particle > 0.1 then
282 spawn_particles = true
283 self.last_particle = 0
284 else
285 spawn_particles = false
288 -- check for big recent changes in speed, (likely caused by collisions)
289 self.past_velocities = update_velocities(self.past_velocities, self.object:get_velocity())
290 local avg = {x=0,y=0,z=0}
291 for i=1,#self.past_velocities do
292 avg.x = avg.x + self.past_velocities[i].x
293 avg.y = avg.y + self.past_velocities[i].y
294 avg.z = avg.z + self.past_velocities[i].z
296 avg.x = avg.x/#self.past_velocities
297 avg.y = avg.y/#self.past_velocities
298 avg.z = avg.z/#self.past_velocities
300 local v3_avg = get_v3(avg)
301 local v3 = get_v3(self.object:get_velocity())
303 local diff = math.abs(v3_avg - v3)
305 if not minetest.settings:get_bool("creative_mode") then
306 if self.last_damage ~= nil then
307 self.last_damage = self.last_damage + dtime
310 if self.last_damage == nil or self.last_damage > 1 then
311 -- hurt the driver if there was a large recent speed change
312 if self.driver and diff >= 9 and diff > self.past_diff then
313 -- ... but don't hurt the driver too many times in a row accidentally
314 self.driver:set_hp(self.driver:get_hp() - math.floor(diff - 9))
315 self.last_damage = 0
317 -- damage the carpet if there was a large recent speed change
318 if diff >= 6 and diff > self.past_diff then
319 -- ... but don't damage the carpet too many times in a row accidentally
320 self.wear = self.wear + math.floor((diff - 6)*700)
321 self.last_damage = 0
326 self.past_diff = diff
328 if self.flying then
329 if not minetest.settings:get_bool("creative_mode") then
330 -- Add wear for flying
331 self.weartimer = self.weartimer + dtime
332 -- This allows a flight of roughly 12 hours until the maximum wear of 65535 is reached
333 local add_wear_at = 0.32959
334 if(self.weartimer > add_wear_at) then
335 self.wear = self.wear + math.floor((self.weartimer / add_wear_at))
336 self.weartimer = self.weartimer % add_wear_at
340 if self.driver then
341 local ctrl = self.driver:get_player_control()
342 if ctrl.up then
343 -- Viscosity penalty
344 local vpenalty = 2 + self.viscosity
345 self.v = self.v + 0.1 / vpenalty
347 if ctrl.down then
348 self.v = self.v-0.08
350 if ctrl.left then
351 self.object:set_yaw(self.object:get_yaw()+math.pi/600+dtime*math.pi/600)
353 if ctrl.right then
354 self.object:set_yaw(self.object:get_yaw()-math.pi/600-dtime*math.pi/600)
356 if ctrl.jump then
357 if self.v >= 6 then
358 self.h = self.h+0.02
361 if ctrl.sneak then
362 if self.v >= 6 then
363 self.h = self.h-0.03
367 if mod_mana then
368 if mana.getregen(self.driver:get_player_name()) < 0 and mana.get(self.driver:get_player_name()) == 0 then
369 minetest.sound_play("flying_carpet_out_of_energy", {pos = self.object:get_pos(), gain=0.7}, true)
370 magic_particle(self.object)
371 self.falling = true
372 self.flying = false
373 mana.setregen(self.driver:get_player_name(), mana.getregen(self.driver:get_player_name())+mana_regen_cost)
374 self.driver:get_meta():set_int("flying_carpet:mana_cost", 0)
375 if self.sound_flight then
376 minetest.sound_stop(self.sound_flight)
377 self.sound_flight = nil
379 if self.sound_death_warning then
380 minetest.sound_stop(self.sound_death_warning)
381 self.sound_death_warning = nil
386 -- Harsh speed penalty for being in a liquid with viscosity > 0
387 local vpenalty = 0.09 * self.viscosity
388 if self.v > 15 then
389 self.v = 15
390 self.v = self.v - 0.02 - vpenalty
391 elseif self.v > 12 then
392 self.v = self.v - 0.02 - vpenalty
393 elseif self.v < 6 then
394 self.v = self.v + 0.06 - vpenalty
395 elseif self.v < 9 then
396 self.v = self.v + 0.04 - vpenalty
397 elseif self.v < 12 then
398 self.v = self.v + 0.02 - vpenalty
401 else
402 -- Harsh speed penalty for being in a liquid with viscosity > 0
403 local vpenalty = 0.09 * self.viscosity
404 -- Slow down flying carpet if no player is attached
405 self.v = self.v - 0.025 - vpenalty
407 if self.v > 3 and self.flying then
408 if spawn_particles then
409 local star
410 if self.v > 7.5 then
411 star = "flying_carpet_star.png"
412 else
413 star = "flying_carpet_star_warning.png"
415 local src = self.object:get_pos()
416 local velo = self.object:get_velocity()
417 -- Speed indicator particles
418 minetest.add_particlespawner({
419 amount = math.ceil(math.min(3,math.max(0,self.v+3))),
420 time = 0.1,
421 minpos = {x=src.x-0.6, y=src.y-0.02, z=src.z-0.6},
422 maxpos = {x=src.x+0.6, y=src.y-0.02, z=src.z+0.6},
423 minvel = {x=velo.x*0.01,y=velo.y*0.01,z=velo.z*0.01},
424 maxvel = {x=velo.x*0.01,y=velo.y*0.01,z=velo.z*0.01},
425 minexptime=1.8,
426 maxexptime=2.2,
427 minsize=1,
428 maxsize=1.25,
429 texture = star,
431 -- Death warning particles
432 if not minetest.settings:get_bool("creative_mode") and self.wear > 58499 then
433 minetest.add_particlespawner({
434 --[[ More speed and more wear = more particles.
435 Carefully adjusted to roughly match the number of speed indicator particles
436 At near full wear, the number of death warning particles should be equal
437 to the number of speed indicator particles ]]
438 amount = math.ceil(math.min(3,math.max(0,(self.v+3)/(18-(self.wear-58499)/391)))),
439 time = 0.1,
440 minpos = {x=src.x-0.6, y=src.y-0.06, z=src.z-0.6},
441 maxpos = {x=src.x+0.6, y=src.y-0.06, z=src.z+0.6},
442 minvel = {x=velo.x*0.005,y=velo.y*0.005,z=velo.z*0.005},
443 maxvel = {x=velo.x*0.005,y=velo.y*0.005,z=velo.z*0.005},
444 minexptime=5,
445 maxexptime=7,
446 minsize=1,
447 maxsize=1.25,
448 texture = "flying_carpet_star_death_warning.png"
452 if not self.sound_flight then
453 self.sound_flight = minetest.sound_play("flying_carpet_flight", {object = self.object, gain = 0.6, max_hear_distance = 16, loop = true })
455 if not minetest.settings:get_bool("creative_mode") and self.wear > 64624 and not self.sound_death_warning then
456 self.sound_death_warning = minetest.sound_play("flying_carpet_death_warning", {object = self.object, gain = 0.2, max_hear_distance = 24, loop = true })
458 else
459 if self.sound_flight then
460 minetest.sound_stop(self.sound_flight)
461 self.sound_flight = nil
463 if self.sound_death_warning then
464 minetest.sound_stop(self.sound_death_warning)
465 self.sound_death_warning = nil
470 local sh = get_sign(self.h)
472 local op = self.object:get_pos()
473 local ps = table.copy(op)
474 ps.y = ps.y-1
475 local n
476 if self.slidepos ~= nil and vector.equals(vector.round(ps), self.slidepos) then
477 n = self.slidenode
478 else
479 n = minetest.get_node(ps)
480 self.slidepos = vector.round(ps)
481 self.slidenode = n
483 if n.name ~= "ignore" and n.name ~= "air" and n.name ~= nil then
484 local comma = ps.y - math.floor(ps.y)
485 if minetest.registered_nodes[n.name].walkable and self.h < 0.001 and comma > 0.519 and comma < 0.53 then
486 self.v = self.v - 0.2
487 if minetest.settings:get_bool("creative_mode") and self.v > 6 then
488 -- Apply a small wear for sliding
489 self.wear = self.wear + math.floor(self.v)
492 if spawn_particles then
493 local src = self.object:get_pos()
494 local velo = self.object:get_velocity()
495 minetest.add_particlespawner({
496 amount = math.ceil(math.min(10,math.max(0,self.v))),
497 time = 1,
498 minpos = {x=src.x-0.6, y=src.y-0.02, z=src.z-0.6},
499 maxpos = {x=src.x+0.6, y=src.y-0.02, z=src.z+0.6},
500 minvel = {x=-velo.x*0.01-0.1, y=velo.y*0.05+0.01, z=-velo.z*0.01-0.1},
501 maxvel = {x=-velo.x*0.01+0.1, y=velo.y*0.05+0.05, z=-velo.z*0.01+0.1},
502 minexptime=10,
503 maxexptime=15,
504 minsize=1,
505 maxsize=1.25,
506 texture = "flying_carpet_smoke.png",
509 if self.v > 1 and not self.sound_slide then
510 self.sound_slide = minetest.sound_play("flying_carpet_slide", {object = self.object, gain = 0.5, max_hear_distance = 24, loop = true })
511 elseif self.v <= 1 and self.sound_slide then
512 minetest.sound_stop(self.sound_slide)
513 self.sound_slide = nil
515 elseif self.sound_slide then
516 minetest.sound_stop(self.sound_slide)
517 self.sound_slide = nil
519 elseif self.sound_slide then
520 minetest.sound_stop(self.sound_slide)
521 self.sound_slide = nil
524 local pl = table.copy(op)
525 pl.y = pl.y-0.3
526 if self.liquidpos ~= nil and vector.equals(vector.round(pl), self.liquidpos) then
527 n = self.liquidnode
528 else
529 n = minetest.get_node(pl)
530 self.liquidpos = vector.round(pl)
531 self.liquidnode = n
534 local ndef = minetest.registered_nodes[n.name]
535 if ndef.liquidtype ~= "none" then
536 if self.h < 0.1 then
537 self.h = self.h + 0.05
538 self.viscosity = ndef.liquid_viscosity
540 else
541 self.viscosity = 0
544 self.starttimer = self.starttimer + dtime
546 if (self.v < 6 and self.starttimer >= 5) or (self.v < 0.5 and self.starttimer >= 0.5) then
547 if self.falling == false then
548 minetest.sound_play("flying_carpet_out_of_energy", {pos = self.object:get_pos(), gain=0.7}, true)
549 self.falling = true
550 self.flying = false
551 if mod_mana and self.driver ~= nil then
552 mana.setregen(self.driver:get_player_name(), mana.getregen(self.driver:get_player_name())+mana_regen_cost)
553 self.driver:get_meta():set_int("flying_carpet:mana_cost", 0)
555 magic_particle(self.object)
556 if self.sound_flight then
557 self.sound_flight = minetest.sound_stop(self.sound_flight)
558 self.sound_flight = nil
560 if self.sound_death_warning then
561 self.sound_death_warning = minetest.sound_stop(self.sound_death_warning)
562 self.sound_death_warning = nil
564 self.v = self.v - 0.04
566 else
567 if self.h > 0.01 then
568 self.h = self.h - 0.005
569 elseif self.h < 0.01 then
570 self.h = self.h + 0.005
572 if math.abs(self.h) > 1 then
573 self.h = 1*sh
575 if math.abs(self.h) < 0.01 then
576 self.h = 0
581 if self.v < 0 then
582 self.v = 0
585 local y
586 if self.falling then
587 y = self.object:get_velocity().y
588 if self.viscosity > 0 then
589 local vpenalty = 0.09 * self.viscosity
590 self.v = self.v - 0.06 - vpenalty
592 self.object:set_acceleration({x=0,y=0,z=0})
593 local v_ref = -0.4 / self.viscosity
594 local dampen = 1.2
595 if(math.abs(y-v_ref) < (dampen+0.01)*self.viscosity) then
596 y = v_ref
597 else
598 if(y>v_ref) then
599 y = y - dampen*self.viscosity
600 else
601 y = y + dampen*self.viscosity
604 else
605 self.object:set_acceleration({x=0,y=-tonumber(minetest.settings:get("movement_gravity")),z=0})
607 else
608 y = self.h
609 self.object:set_acceleration({x=0,y=0,z=0})
611 self.object:set_velocity(get_velocity(self.v, self.object:get_yaw(), y))
615 -- Check wear
616 if minetest.settings:get_bool("creative_mode") and self.wear >= 65535 then
617 -- Destroy carpet if it took to much damage
618 death_particles(self.object)
619 if self.sound_flight then
620 minetest.sound_stop(self.sound_flight)
621 self.sound_flight = nil
623 if self.sound_death_warning then
624 minetest.sound_stop(self.sound_death_warning)
625 self.sound_death_warning = nil
627 if self.sound_slide then
628 minetest.sound_stop(self.sound_slide)
629 self.sound_slide = nil
631 -- Detach driver and refund mana
632 if self.driver ~= nil then
633 self.driver:set_detach()
634 if mod_model then
635 player_api.player_attached[self.driver:get_player_name()] = false
636 player_api.set_animation(self.driver, "stand", 30)
638 if mod_mana then
639 mana.setregen(self.driver:get_player_name(), mana.getregen(self.driver:get_player_name())+mana_regen_cost)
640 self.driver:get_meta():set_int("flying_carpet:mana_cost", 0)
643 minetest.sound_play("flying_carpet_out_of_energy", {pos = self.object:get_pos(), gain=0.7}, true)
644 self.object:remove()
645 return
649 minetest.register_entity("flying_carpet:carpet", carpet)
651 local longdesc, usagehelp, durability
652 if mod_doc_items then
653 longdesc = S("Quickly explore the vast terrain with the magical flying carpet. But only the skilled users can master its speed, the fools will crash and hurt themselves. The carpet flies fast horizontally, but is only slowly able to change its rotation or height.")
654 if mod_mana then
655 if minetest.settings:get_bool("creative_mode") then
656 longdesc = longdesc .. S(" It constantly reduces your mana resources and will wear out over time.")
657 else
658 longdesc = longdesc .. S(" It constantly reduces your mana resources.")
660 elseif not minetest.settings:get_bool("creative_mode") then
661 longdesc = longdesc .. S(" It will wear out over time.")
663 usagehelp = ""
664 usagehelp = usagehelp .. S("Look to the desired initial flight direction and place the carpet on any flat open surface. Make sure it has enough space (3×2×3), then place it. Right-click the carpet to sit on it and depart. You can only enter the flying carpet if it is not moving itself and you're not in another vehicle.") .. "\n\n"
666 usagehelp = usagehelp .. S("Flight is controlled with the movement keys: Up to speed up, down to slow down, left and right to rotate slowly. Jump to rise, sneak to sink.") .. "\n\n"
668 usagehelp = usagehelp .. S("The flying carpet has a basic movement speed which it is aiming to reach; thus, it flies without your intervention. Speed changes apply only for as long as you hold down the keys. As soon as you release the keys, the carpet will go back to its basic speed again. When the carpet is under a certain critical speed, it loses its magic and just drops on the ground. There's a short “grace period” at the beginning of the flight where this minimum speed limit does not apply. Watch the particles emitted by the carpet carefully: If they are yellow, everything is okay, but if they become red this means the carpet is going dangerously slow and you should speed up.") .. "\n\n"
670 usagehelp = usagehelp .. S("The flying carpet also fails immediately if you crash into the landscape, which may even hurt you. You can also also take fall damage, but it is greatly reduced.")
672 if mod_mana then
673 usagehelp = usagehelp .. S(" Your carpet will also stop working if your mana resources are depleted.")
675 usagehelp = usagehelp .. S(" When your carpet stopped working, you have to collect the carpet (punch it) and place it again.") .. "\n\n"
677 usagehelp = usagehelp .. S("If you fly directly down onto a flat solid ground, your flying carpet will come to a halt quickly due to friction. Your carpet can not fly into liquids from above, but it can fly into them from the side or even below. But the speed in liquids is greatly reduced and your carpet will likely fail.") .. "\n\n"
679 if not minetest.settings:get_bool("creative_mode") then
680 usagehelp = usagehelp .. S("Your carpet is not indestructible! Long flights, scratching and crashes will wear out the carpet over time and it might get destroyed eventually. Crashes deal major damage, sliding deals a very minor wear and flying deals a tiny amount of wear.") .. "\n\n"
681 usagehelp = usagehelp .. S("On high wear levels, the carpet will emit black particles which will increase in number with its wear. At this point, you should get a replacement soon. On a critical wear level, the carpet will emit a very annoying loud noise while flying. If you hear this, you will have roughly five minutes worth of flight until the carpet finally disintegrates under your feet! Additionally, a flying carpet will disintegrate and is lost forever if it stands still and has no user for 1 minute.")
683 durability = S("Ca. 12 hours worth of flight time, if you fly without accidents.")
687 minetest.register_tool("flying_carpet:carpet", {
688 description = S("Flying carpet"),
689 _doc_items_longdesc = longdesc,
690 _doc_items_usagehelp = usagehelp,
691 _doc_items_durability = durability,
692 inventory_image = "flying_carpet_inventory.png",
693 wield_image = "flying_carpet_wield.png",
694 wield_scale = {x=1.374, y=2, z=0.2},
696 on_place = function(itemstack, placer, pointed_thing)
697 if pointed_thing.type ~= "node" then
698 return
700 local place_pos = pointed_thing.under
701 place_pos.y = place_pos.y+1.5
702 -- check if there is enough free space for the carpet
703 local check_pos = vector.round(place_pos)
704 for x=-1,1 do
705 for y=-1,0 do
706 for z=-1,1 do
707 local node = minetest.get_node({x=check_pos.x+x, y=check_pos.y+y, z=check_pos.z+z})
708 local nodedef = minetest.registered_nodes[node.name]
709 if not (not nodedef.walkable and nodedef.liquidtype == "none") then
710 return
716 -- check if carpet wouldn't collide with any nearby carpet
717 local conflict_objects = minetest.get_objects_inside_radius(place_pos, math.sqrt(2)*1.5)
718 for i=1, #conflict_objects do
719 local le = conflict_objects[i]:get_luaentity()
720 if le ~= nil and le.name == "flying_carpet:carpet" then
721 return
725 local ent = minetest.add_entity(place_pos, "flying_carpet:carpet")
726 ent:set_yaw(placer:get_look_horizontal()+math.pi/2)
727 ent:get_luaentity().wear = itemstack:get_wear()
729 if not minetest.settings:get_bool("creative_mode") then
730 itemstack:take_item()
732 minetest.sound_play("flying_carpet_place", {pos = place_pos, gain = 1}, true)
733 return itemstack
734 end,
737 if mod_mana then
738 -- Restore mana regeneration when players leave
739 minetest.register_on_joinplayer(function(player)
740 local name = player:get_player_name()
741 local meta = player:get_meta()
742 local mana_cost = meta:get_int("flying_carpet:mana_cost") == 1
743 if mana_cost then
744 mana.setregen(name, mana.getregen(name)+mana_regen_cost)
745 minetest.log("info", "[flying_carpet] Restoring mana regen for "..name..", who was previously attached to a carpet")
747 end)
750 if mod_craft then
751 minetest.register_craft({
752 output = "flying_carpet:carpet",
753 recipe = { { "wool:red", "wool:yellow", "wool:red" },
754 { "wool:yellow", "wool:red", "wool:yellow" },
755 { "default:mese_crystal", "default:goldblock", "default:mese_crystal" } }
758 if mod_craft_mcl2 then
759 minetest.register_craft({
760 output = "flying_carpet:carpet",
761 recipe = { { "mcl_wool:red", "mcl_wool:yellow", "mcl_wool:red" },
762 { "mcl_wool:yellow", "mcl_wool:red", "mcl_wool:yellow" },
763 { "mesecons_torch:redstoneblock", "mcl_core:goldblock", "mesecons_torch:redstoneblock" } }
767 if mod_doc_identifier then
768 doc.sub.identifier.register_object("flying_carpet:carpet", "tools", "flying_carpet:carpet")