4 ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public License
8 as published by the Free Software Foundation; either version 2.1
9 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
24 boxes
.teleport_to_tutorial_exit
= {}
26 local modpath
= minetest
.get_modpath(minetest
.get_current_modname())
29 dofile(modpath
.. "/valloc.lua")
31 -- Handling the data that encodes boxes
32 dofile(modpath
.. "/data.lua")
34 -- Import/export boxes to files
35 dofile(modpath
.. "/io.lua")
37 -- Handle inventory-related callbacks (i.e. whether the itemstacks
38 -- should be decreased when placing nodes)
39 dofile(modpath
.. "/inv.lua")
41 -- Nodes that have a box-related effect
42 dofile(modpath
.. "/nodes.lua")
44 -- Box score recording code
45 dofile(modpath
.. "/score.lua")
47 local areas
= AreaStore("insidethebox")
49 function boxes
.find_box(pos
)
52 for k
, v
in pairs(areas
:get_areas_for_pos(pos
, false, true)) do
57 if boxes
.players_in_boxes
[name
] then
58 return boxes
.players_in_boxes
[name
]
59 elseif boxes
.players_editing_boxes
[name
] then
60 return boxes
.players_editing_boxes
[name
]
66 -- Set the region from minp to maxp to air
67 function boxes
.cleanup(minp
, maxp
)
68 -- Clear any leftover entities
69 local center
= vector
.divide(vector
.add(minp
, maxp
), 2)
70 local radius
= vector
.length(vector
.subtract(vector
.add(maxp
, 1), minp
)) / 2
71 for _
, obj
in ipairs(minetest
.get_objects_inside_radius(center
, radius
)) do
72 if not obj
:is_player() then
73 local pos
= obj
:get_pos()
74 if minp
.x
- 0.5 <= pos
.x
and pos
.x
<= maxp
.x
+ 0.5 and
75 minp
.y
- 0.5 <= pos
.y
and pos
.y
<= maxp
.y
+ 0.5 and
76 minp
.z
- 0.5 <= pos
.z
and pos
.z
<= maxp
.z
+ 0.5
83 local vm
= minetest
.get_voxel_manip(minp
, maxp
)
84 local emin
, emax
= vm
:get_emerged_area()
85 local va
= VoxelArea
:new
{MinEdge
=emin
,MaxEdge
=emax
}
86 local vmdata
= vm
:get_data()
87 local param2
= vm
:get_param2_data()
88 local cid
= minetest
.get_content_id("air")
90 for z
= minp
.z
, maxp
.z
do
91 for y
= minp
.y
, maxp
.y
do
92 local index
= va
:index(minp
.x
, y
, z
)
93 for x
= minp
.x
, maxp
.x
do
102 vm
:set_param2_data(param2
)
106 minetest
.after(0.1, minetest
.fix_light
, minp
, maxp
)
109 function vector
.min(a
, b
)
111 x
= math
.min(a
.x
, b
.x
),
112 y
= math
.min(a
.y
, b
.y
),
113 z
= math
.min(a
.z
, b
.z
),
117 function vector
.max(a
, b
)
119 x
= math
.max(a
.x
, b
.x
),
120 y
= math
.max(a
.y
, b
.y
),
121 z
= math
.max(a
.z
, b
.z
),
125 boxes
.players_in_boxes
= {}
127 local function open_door(player
, pos1
, pos2
, bminp
, bmaxp
)
128 local minp
= vector
.min(pos1
, pos2
)
129 local maxp
= vector
.max(pos1
, pos2
)
132 for x
= minp
.x
, maxp
.x
do
133 for y
= minp
.y
, maxp
.y
do
134 for z
= minp
.z
, maxp
.z
do
135 local door
= doors
.get({x
= x
, y
= y
, z
= z
})
138 drs
[i
] = {x
= x
, y
= y
, z
= z
}
144 local od
= boxes
.players_in_boxes
[player
:get_player_name()].open_doors
145 od
[#od
+ 1] = {minp
= bminp
, maxp
= bmaxp
, doors
= drs
, respawn
=
146 {x
= maxp
.x
+ 1, y
= minp
.y
, z
= (minp
.z
+ maxp
.z
) / 2}}
149 function boxes
.player_success(player
)
150 local name
= player
:get_player_name()
151 local box
= boxes
.players_in_boxes
[name
]
152 local id
= box
.box_id
153 local time_taken
= minetest
.get_gametime() - box
.start_time
154 local deaths
= box
.deaths
155 local damage
= box
.damage
156 local bmeta
= db
.box_get_meta(id
)
158 boxes
.score(name
, id
, "time", {time_taken
})
159 boxes
.score(name
, id
, "damage", {damage
})
160 boxes
.score(name
, id
, "deaths", {deaths
})
162 bmeta
.meta
.num_completed_players
= db
.box_get_num_completed_players(id
)
164 db
.box_set_meta(id
, bmeta
)
165 for _
, p
in ipairs(box
.signs_to_update
) do
166 local node
= minetest
.get_node(p
)
167 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_exit_update
then
168 minetest
.registered_nodes
[node
.name
].on_exit_update(p
, player
, {
169 time_taken
= {format = "time", data
= time_taken
},
170 damage_taken
= {format = "text", data
= tostring(damage
)},
171 num_deaths
= {format = "text", data
= tostring(deaths
)},
176 local sid
= box
.in_series
178 local pmeta
= db
.player_get_meta(name
)
179 local index
= pmeta
.series_progress
[sid
] or 1
180 pmeta
.series_progress
[sid
] = index
+ 1
181 db
.player_set_meta(name
, pmeta
)
183 -- check if player completed tutorial series
184 local bxs
= db
.series_get_boxes(sid
)
185 if bxs
[index
+ 1] then
188 -- omit check if tutorial is actually required here
189 if sid
== conf
.tutorial
.series
then
190 player
:set_attribute("tutorial_completed", 1)
191 minetest
.log("action", name
.. " completed the tutorial")
192 minetest
.chat_send_all(name
.. " completed the tutorial!")
194 irc
.say(name
.. " has completed the tutorial!")
196 -- reward: create priv
197 local privs
= minetest
.get_player_privs(name
)
199 minetest
.set_player_privs(name
, privs
)
200 boxes
.teleport_to_tutorial_exit
[name
] = true
205 function boxes
.open_exit(player
)
206 local name
= player
:get_player_name()
207 local box
= boxes
.players_in_boxes
[name
]
208 local exds
= box
.exit_doors
210 if n
== 0 then return end
213 open_door(player
, ex
.door_pos
, vector
.add(ex
.door_pos
, {x
= -1, y
= 0, z
= 1}), ex
.box_minp
, ex
.box_maxp
)
216 function boxes
.increase_items(player
)
217 local name
= player
:get_player_name()
218 if not boxes
.players_in_boxes
[name
] then
221 local exds
= boxes
.players_in_boxes
[name
].exit_doors
223 if n
== 0 then return end
225 ex
.door_items
= ex
.door_items
- 1
226 if ex
.door_items
== 0 then
227 boxes
.open_exit(player
)
231 -- Close doors behind players
232 minetest
.register_globalstep(function(dtime
)
233 for playername
, info
in pairs(boxes
.players_in_boxes
) do
235 local player
= minetest
.get_player_by_name(playername
)
236 local pos
= player
:get_pos()
237 local odoors
= info
.open_doors
241 if odoors
[i
].minp
.x
+ off
<= pos
.x
and pos
.x
<= odoors
[i
].maxp
.x
- off
and
242 odoors
[i
].minp
.y
<= pos
.y
and pos
.y
<= odoors
[i
].maxp
.y
and
243 odoors
[i
].minp
.z
+ off
<= pos
.z
and pos
.z
<= odoors
[i
].maxp
.z
- off
245 for _
, dpos
in ipairs(odoors
[i
].doors
) do
246 local door
= doors
.get(dpos
)
251 info
.respawn
= odoors
[i
].respawn
252 odoors
[i
] = odoors
[n
]
255 if not info
.start_time
then
256 info
.start_time
= minetest
.get_gametime()
259 music
.start(player
, info
, "box")
261 -- We closed the last door: the player got out!
262 boxes
.player_success(player
)
263 minetest
.log("action", playername
.. " completed box " .. info
.box_id
)
264 minetest
.chat_send_all(playername
.. " completed box " .. info
.box_id
.. "!")
265 music
.start(player
, info
, "exit")
274 local function translate_all(l
, offset
)
276 for i
, p
in ipairs(l
) do
277 result
[i
] = vector
.add(p
, offset
)
282 -- Although this functions takes a list as an argument,
283 -- several parts of the code rely on it having length exactly
284 -- 3 and being entry lobby, box, exit
285 function boxes
.open_box(player
, box_id_list
)
286 local name
= player
:get_player_name()
287 if boxes
.players_in_boxes
[name
] ~= nil or boxes
.players_editing_boxes
[name
] ~= nil then
291 minetest
.log("action", name
.. " entered box " .. box_id_list
[2])
293 local offset
= {x
= 0, y
= 0, z
= 0}
294 local pmin
= {x
= 0, y
= 0, z
= 0}
295 local pmax
= {x
= 0, y
= 0, z
= 0}
298 for i
, box
in ipairs(box_id_list
) do
299 local meta
= db
.box_get_meta(box
).meta
301 offset
= vector
.subtract(offset
, meta
.entry
)
302 pmin
= vector
.min(pmin
, offset
)
303 pmax
= vector
.max(pmax
, vector
.add(offset
, meta
.size
))
305 offset
= vector
.add(offset
, meta
.exit)
307 for i
, off
in ipairs(offsets
) do
308 offsets
[i
] = vector
.subtract(off
, pmin
)
310 pmax
= vector
.subtract(pmax
, pmin
)
312 local size
= math
.max(pmax
.x
, pmax
.z
)
313 local minp
= boxes
.valloc(size
)
314 local maxp
= vector
.add(minp
, vector
.subtract(pmax
, 1))
316 local exit_doors
= {}
317 local n
= #box_id_list
319 local exd
= vector
.add(minp
, vector
.add(metas
[n
- i
].exit, offsets
[n
- i
]))
321 door_items
= metas
[n
- i
].num_items
,
323 box_minp
= vector
.add(minp
, offsets
[n
- i
+ 1]),
324 box_maxp
= vector
.add(minp
, vector
.add(offsets
[n
- i
+ 1], vector
.subtract(metas
[n
- i
+ 1].size
, 1))),
328 local spawn_pos
= vector
.add(minp
, vector
.add(metas
[1].entry
, offsets
[1]))
329 spawn_pos
.y
= spawn_pos
.y
- 0.5
331 local base_exit
= vector
.add(offsets
[3], minp
)
332 local signs_to_update
= translate_all(metas
[3].signs_to_update
, base_exit
)
333 local star_positions
= translate_all(metas
[3].star_positions
, base_exit
)
334 local category_positions
= translate_all(metas
[3].category_positions
, base_exit
)
336 local areas_id
= areas
:insert_area(vector
.add(minp
, 1), vector
.subtract(maxp
, 1), name
)
337 boxes
.players_in_boxes
[name
] = {
339 box_id
= box_id_list
[2],
343 exit_doors
= exit_doors
,
344 exit = vector
.add(minp
, vector
.add(metas
[n
].exit, offsets
[n
])),
347 signs_to_update
= signs_to_update
,
348 star_positions
= star_positions
,
349 category_positions
= category_positions
,
353 for i
, box
in ipairs(box_id_list
) do
354 boxes
.load(vector
.add(minp
, offsets
[i
]), box
, player
)
357 player
:set_physics_override({gravity
= 0, jump
= 0})
358 spawn_pos
.y
= spawn_pos
.y
+ 0.5
360 -- the `send_mapblock` method is not yet merged, requiring this compat check
361 if player
.send_mapblock
then
362 local tv
= vector
.floor((vector
.divide(spawn_pos
, 16)))
363 local ret
= player
:send_mapblock(tv
)
365 minetest
.log("error", "send_mapblock failed for " .. player
:get_player_name() .. " to " .. minetest
.pos_to_string(tv
))
369 player
:set_pos(spawn_pos
)
370 minetest
.after(0.5, function()
371 player
:set_physics_override({gravity
= 1, jump
= 1})
373 player
:set_look_horizontal(3 * math
.pi
/ 2)
374 player
:set_look_vertical(0)
375 players
.give_box_inventory(player
)
376 skybox
.set(player
, metas
[n
- 1].skybox
)
378 boxes
.open_exit(player
)
380 minetest
.after(1.0, function()
385 local p
= player
:get_pos()
386 if p
and p
.y
< spawn_pos
.y
- 0.95 and boxes
.players_in_boxes
[name
] then
387 minetest
.log("error", name
.. " fell from an entrance lobby")
388 if (math
.abs(spawn_pos
.x
- p
.x
) < 2) and (math
.abs(spawn_pos
.z
- p
.z
) < 2) then
389 player
:set_pos({x
= p
.x
, y
= spawn_pos
.y
+ 0.5, z
= p
.z
})
391 player
:set_pos(spawn_pos
)
397 function boxes
.close_box(player
)
398 local name
= player
:get_player_name()
399 if boxes
.players_in_boxes
[name
] == nil then
403 local bx
= boxes
.players_in_boxes
[name
]
405 minetest
.log("action", name
.. " left box " .. bx
.box_id
)
407 boxes
.cleanup(bx
.minp
, bx
.maxp
)
409 areas
:remove_area(boxes
.players_in_boxes
[name
].areas_id
)
410 boxes
.players_in_boxes
[name
] = nil
413 function boxes
.next_series(player
, sid
, is_entering
)
414 local name
= player
:get_player_name()
415 local pmeta
= db
.player_get_meta(name
)
416 local index
= pmeta
.series_progress
[sid
] or 1
417 local bxs
= db
.series_get_boxes(sid
)
418 if not bxs
[index
] then
419 if not is_entering
then
420 players
.return_to_lobby(player
)
424 if sid
== conf
.tutorial
.series
and
425 conf
.tutorial
.required
and
426 player
:get_attribute("tutorial_completed") ~= "1" then
427 boxes
.open_box(player
, {conf
.tutorial
.entry_lobby
, bxs
[index
], conf
.tutorial
.exit_lobby
})
429 boxes
.open_box(player
, {0, bxs
[index
], 1})
431 boxes
.players_in_boxes
[name
].in_series
= sid
436 function boxes
.validate_pos(player
, pos
, withborder
)
437 local name
= player
:get_player_name()
438 local box
= boxes
.players_in_boxes
[name
] or boxes
.players_editing_boxes
[name
]
440 if not minetest
.check_player_privs(player
, "server") then
444 minp
= { x
= -32768, y
= -32768, z
= -32768 },
445 maxp
= { x
= 32768, y
= 32768, z
= 32768 },
449 if pos
.x
< box
.minp
.x
or pos
.x
> box
.maxp
.x
or
450 pos
.y
< box
.minp
.y
or pos
.y
> box
.maxp
.y
or
451 pos
.z
< box
.minp
.z
or pos
.z
> box
.maxp
.z
then
455 if pos
.x
< box
.minp
.x
+ 1 or pos
.x
> box
.maxp
.x
- 1 or
456 pos
.y
< box
.minp
.y
+ 1 or pos
.y
> box
.maxp
.y
- 1 or
457 pos
.z
< box
.minp
.z
+ 1 or pos
.z
> box
.maxp
.z
- 1 then
465 boxes
.players_editing_boxes
= {}
467 minetest
.register_chatcommand("enter", {
469 description
= "Enter box with this id",
470 -- privs = {server = true},
471 func
= function(name
, param
)
472 local player
= minetest
.get_player_by_name(name
)
473 if boxes
.players_in_boxes
[name
] or boxes
.players_editing_boxes
[name
] then
474 minetest
.chat_send_player(name
, "You are already in a box!")
477 local id
= tonumber(param
)
478 if not id
or id
~= math
.floor(id
) or id
< 0 then
479 minetest
.chat_send_player(name
, "The id you supplied is not a nonnegative interger.")
482 local meta
= db
.box_get_meta(id
)
483 if not meta
or meta
.type ~= db
.BOX_TYPE
then
484 minetest
.chat_send_player(name
, "The id you supplied does not correspond to any box.")
487 -- only server admins may enter any box
488 -- normal players may not enter anything but published boxes or their own boxes
489 if minetest
.check_player_privs(player
, "server") or
490 meta
.meta
.status
== db
.STATUS_ACCEPTED
or
491 meta
.meta
.builder
== name
then
492 boxes
.open_box(player
, {0, id
, 1})
494 minetest
.chat_send_player(name
, "You do not have permissions to enter that box.")
499 minetest
.register_chatcommand("series_create", {
501 description
= "Create a new series",
502 privs
= {server
= true},
503 func
= function(name
, param
)
505 minetest
.chat_send_player(name
, "Please privide a name for the series")
508 local id
= db
.series_create(param
)
510 minetest
.chat_send_player(name
, "Series successfully created with id " .. id
)
512 minetest
.chat_send_player(name
, "Series failed to create")
517 minetest
.register_chatcommand("series_destroy", {
518 params
= "<series_id>",
519 description
= "Destroy a series",
520 privs
= {server
= true},
521 func
= function(name
, param
)
523 minetest
.chat_send_player(name
, "Please privide a series id to destroy")
526 local id
= tonumber(param
)
527 if not db
.series_destroy(id
) then
528 minetest
.chat_send_player(name
, "Failed to destroy series " .. id
)
530 minetest
.chat_send_player(name
, "Destroyed series " .. id
)
535 minetest
.register_chatcommand("series_add", {
536 params
= "<series_id> <box_id>",
537 description
= "Add a box a series",
538 privs
= {server
= true},
539 func
= function(name
, param
)
540 local sid
, bid
= string.match(param
, "^([^ ]+) +(.+)$")
543 if not sid
or not bid
or not db
.series_get_meta(sid
) or not db
.box_get_meta(bid
) then
544 minetest
.chat_send_player(name
, "Box or series doesn't exist.")
547 db
.series_add_at_end(sid
, bid
)
548 minetest
.chat_send_player(name
, "Done")
552 minetest
.register_chatcommand("series_list", {
554 description
= "List defined series",
555 privs
= {server
= true},
556 func
= function(name
, param
)
558 -- list boxes in this series.
559 local sid
= tonumber(param
)
560 if not sid
or not db
.series_get_meta(sid
) then
561 minetest
.chat_send_player(name
, "Series doesn't exist.")
564 for _
, v
in ipairs(db
.series_get_boxes(sid
)) do
565 minetest
.chat_send_player(name
, v
)
569 for k
, v
in ipairs(db
.series_get_series()) do
570 minetest
.chat_send_player(name
, v
.id
.. " - " .. v
.name
)
576 minetest
.register_chatcommand("series_remove", {
577 params
= "<series_id> <box_id>",
578 description
= "Remove a box from specified series",
579 privs
= {server
= true},
580 func
= function(name
, param
)
581 local sid
, bid
= string.match(param
, "^([^ ]+) +(.+)$")
584 if not sid
or not bid
or not db
.series_get_meta(sid
) or not db
.box_get_meta(bid
) then
585 minetest
.chat_send_player(name
, "Box or series doesn't exist.")
588 db
.series_delete_box(sid
, bid
)
589 minetest
.chat_send_player(name
, "Done")
594 minetest
.register_chatcommand("leave", {
596 description
= "Leave the current box",
597 privs
= {server
= true},
598 func
= function(name
, param
)
599 if not boxes
.players_in_boxes
[name
] then
600 minetest
.chat_send_player(name
, "You are not in a box!")
603 local player
= minetest
.get_player_by_name(name
)
604 boxes
.close_box(player
)
606 players
.return_to_lobby(player
)
610 minetest
.register_chatcommand("open", {
612 description
= "Open the current exit.",
613 privs
= {server
= true},
614 func
= function(name
, param
)
615 local box
= boxes
.players_in_boxes
[name
]
617 minetest
.chat_send_player(name
, "You are not in a box!")
620 if box
.exit_doors
[1] == nil then
621 minetest
.chat_send_player(name
, "There are no more exits to open!")
624 if box
.open_doors
[1] ~= nil then
625 minetest
.chat_send_player(name
, "This box already has an open door!")
628 local player
= minetest
.get_player_by_name(name
)
629 boxes
.open_exit(player
)
633 -- This is only still needed if we want to change the size, the entry, or the
634 -- exit of the lobbies; changing the contents can be done by /edite.
636 local lobby_updates
= {}
637 minetest
.register_chatcommand("update_lobby", {
638 params
= "entry|exit <box id>",
639 description
= "Set corresponding lobby. Use without parameter to start \
640 updating a lobby, then punch both corners of the lobby and the bottom node \
641 of the exit. In the case of the entry lobby, stand at its spawnpoint to run \
643 privs
= {server
= true},
644 func
= function(name
, param
)
645 local update_type
, box_id
= string.match(param
, "^([^ ]+) +(.+)$")
647 if update_type
~= "entry" and update_type
~= "exit" and param
~= "" then
652 lobby_updates
[name
] = {}
653 elseif not lobby_updates
[name
] then
654 minetest
.chat_send_player(name
, "Not all positions have been set.")
657 box_id
= tonumber(box_id
)
658 local pos1
= lobby_updates
[name
].pos1
659 local pos2
= lobby_updates
[name
].pos2
660 local pos3
= lobby_updates
[name
].pos3
661 if not pos1
or not pos2
or not pos3
then
662 minetest
.chat_send_player(name
, "Not all positions have been set.")
665 local minp
= vector
.min(pos1
, pos2
)
666 local maxp
= vector
.max(pos1
, pos2
)
667 local data
= boxes
.save(minp
, maxp
)
668 local p3
= vector
.subtract(pos3
, minp
)
669 if update_type
== "exit" then
671 local player
= minetest
.get_player_by_name(name
)
672 local exit = vector
.subtract(vector
.round(player
:get_pos()), minp
)
673 db
.box_set_data(box_id
, data
)
674 db
.box_set_meta(box_id
, {
677 size
= vector
.add(vector
.subtract(maxp
, minp
), 1),
684 local player
= minetest
.get_player_by_name(name
)
685 local entry
= vector
.subtract(vector
.round(player
:get_pos()), minp
)
687 db
.box_set_data(box_id
, data
)
688 db
.box_set_meta(box_id
, {
689 type = db
.ENTRY_TYPE
,
691 size
= vector
.add(vector
.subtract(maxp
, minp
), 1),
697 minetest
.chat_send_player(name
, "Updated.")
698 lobby_updates
[name
] = nil
703 minetest
.register_on_punchnode(function(pos
, node
, puncher
, pointed_thing
)
704 if not puncher
then return end
705 local name
= puncher
:get_player_name()
706 if not lobby_updates
[name
] then return end
707 if not lobby_updates
[name
].pos1
then
708 lobby_updates
[name
].pos1
= pos
709 minetest
.chat_send_player(name
, "Position 1 set to " .. dump(pos
) .. ".")
710 elseif not lobby_updates
[name
].pos2
then
711 lobby_updates
[name
].pos2
= pos
712 minetest
.chat_send_player(name
, "Position 2 set to " .. dump(pos
) .. ".")
713 elseif not lobby_updates
[name
].pos3
then
714 lobby_updates
[name
].pos3
= pos
715 minetest
.chat_send_player(name
, "Position 3 set to " .. dump(pos
) .. ".")
720 local digits
= {[0] =
783 function boxes
.make_new(player
, size
)
784 local minp
= boxes
.valloc(size
+ 2)
785 local maxp
= vector
.add(minp
, size
+ 1)
787 -- Clear existing metadata
788 local meta_positions
= minetest
.find_nodes_with_meta(minp
, maxp
)
789 for _
, pos
in ipairs(meta_positions
) do
790 minetest
.get_meta(pos
):from_table()
794 local cid_air
= minetest
.get_content_id("air")
795 local cid_wall
= minetest
.get_content_id("nodes:marbleb")
796 local cid_digit
= minetest
.get_content_id("nodes:bronzeb")
797 local cid_barrier
= minetest
.get_content_id("nodes:barrier")
799 local vm
= minetest
.get_voxel_manip(minp
, maxp
)
800 local emin
, emax
= vm
:get_emerged_area()
801 local va
= VoxelArea
:new
{MinEdge
=emin
,MaxEdge
=emax
}
802 local vmdata
= vm
:get_data()
803 local param2
= vm
:get_param2_data()
806 for z
= minp
.z
, maxp
.z
do
807 for y
= minp
.y
, maxp
.y
do
808 local index
= va
:index(minp
.x
, y
, z
)
809 for x
= minp
.x
, maxp
.x
do
810 vmdata
[index
] = cid_air
817 -- Add stone for walls and barrier at the top
818 for z
= minp
.z
, maxp
.z
do
819 local index
= va
:index(minp
.x
, minp
.y
, z
)
820 local index2
= va
:index(minp
.x
, maxp
.y
, z
)
821 for x
= minp
.x
, maxp
.x
do
822 vmdata
[index
] = cid_wall
823 vmdata
[index2
] = cid_barrier
828 for y
= minp
.y
, maxp
.y
- 1 do
829 local index
= va
:index(minp
.x
, y
, minp
.z
)
830 local index2
= va
:index(minp
.x
, y
, maxp
.z
)
831 for x
= minp
.x
, maxp
.x
do
832 vmdata
[index
] = cid_wall
833 vmdata
[index2
] = cid_wall
838 local ystride
= emax
.x
- emin
.x
+ 1
839 for z
= minp
.z
, maxp
.z
do
840 local index
= va
:index(minp
.x
, minp
.y
, z
)
841 local index2
= va
:index(maxp
.x
, minp
.y
, z
)
842 for y
= minp
.y
, maxp
.y
- 1 do
843 vmdata
[index
] = cid_wall
844 vmdata
[index2
] = cid_wall
845 index
= index
+ ystride
846 index2
= index2
+ ystride
850 local box_id
= db
.get_last_box_id() + 1
853 local id_string
= tostring(box_id
)
854 local id_sz
= 4 * string.len(id_string
) - 1
855 if size
< 6 or size
< id_sz
+ 2 then
856 minetest
.log("error", "boxes.make_new(" .. size
.. "): box size too small")
858 local xoff
= minp
.x
+ math
.floor((size
+ 2 - id_sz
) / 2)
859 local yoff
= minp
.y
+ math
.floor((size
+ 2 - 5 + 1) / 2)
860 local n
= string.len(id_string
)
861 for i
= 1, string.len(id_string
) do
864 if digits
[string.byte(id_string
, n
- i
+ 1) - 48][3-dx
+3*(4-dy
)] then
865 local index
= va
:index(xoff
+ dx
, yoff
+ dy
, minp
.z
)
866 vmdata
[index
] = cid_digit
874 x
= math
.floor((size
+ 2 - id_sz
) / 2),
875 y
= math
.floor((size
+ 2 - 5 + 1) / 2),
880 vm
:set_param2_data(param2
)
884 minetest
.after(0.1, minetest
.fix_light
, minp
, maxp
)
886 local s2
= math
.floor(size
/ 2)
887 local entry
= {x
= 0, y
= 1, z
= s2
+ 1}
888 local exit = {x
= size
+ 2, y
= 1, z
= s2
+ 1}
889 local sz
= {x
= size
+ 2, y
= size
+ 2, z
= size
+ 2}
897 box_name
= "(No name)",
898 builder
= player
:get_player_name(),
901 digit_pos
= digit_pos
,
905 player
:set_pos(vector
.add(minp
, {x
= 1, y
= 1, z
= s2
+ 1}))
906 players
.give_edit_inventory(player
)
907 db
.box_set_meta(box_id
, meta
)
908 db
.box_set_data(box_id
, boxes
.save(minp
, maxp
))
910 local name
= player
:get_player_name()
911 local areas_id
= areas
:insert_area(vector
.add(minp
, 1), vector
.subtract(maxp
, 1), name
)
912 boxes
.players_editing_boxes
[name
] = {
919 entry
= vector
.add(minp
, entry
),
920 exit = vector
.add(minp
, exit),
921 start_edit_time
= minetest
.get_gametime(),
927 local function get_digit_pos(player
)
928 local name
= player
:get_player_name()
929 local box
= boxes
.players_editing_boxes
[name
]
933 local id
= box
.box_id
934 local meta
= db
.box_get_meta(id
)
935 if meta
.meta
.digit_pos
then
936 return meta
.meta
.digit_pos
939 -- Digit position is not specified in meta (old boxes)
940 -- We have to infer it.
941 local minp
= box
.minp
942 local maxp
= {x
= box
.maxp
.x
, y
= box
.maxp
.y
, z
= box
.minp
.z
}
943 local vm
= minetest
.get_voxel_manip(minp
, maxp
)
944 local emin
, emax
= vm
:get_emerged_area()
945 local va
= VoxelArea
:new
{MinEdge
=emin
, MaxEdge
=emax
}
946 local vmdata
= vm
:get_data()
947 local cid_digit
= minetest
.get_content_id("nodes:bronzeb")
950 for y
= minp
.y
, maxp
.y
do
951 local index
= va
:index(minp
.x
, y
, minp
.z
)
952 for x
= minp
.x
, maxp
.x
do
953 if vmdata
[index
] == cid_digit
then
964 local id_string
= tostring(id
)
965 local id_sz
= 4 * string.len(id_string
) - 1
966 local digit_pos
= {x
= maxx
- minp
.x
- id_sz
+ 1, y
= maxy
- minp
.y
- 4, z
= 0}
967 meta
.meta
.digit_pos
= digit_pos
968 db
.box_set_meta(id
, meta
)
972 local function paint_digits(pos
, node
, id
)
973 local id_string
= tostring(id
)
974 local n
= string.len(id_string
)
978 if digits
[string.byte(id_string
, n
- i
+ 1) - 48][3-dx
+3*(4-dy
)] then
979 local p
= {x
= pos
.x
+ 4 * i
- 4 + dx
, y
= pos
.y
+ dy
, z
= pos
.z
}
980 minetest
.set_node(p
, node
)
987 function boxes
.move_digits(player
, pos
)
988 local name
= player
:get_player_name()
989 local box
= boxes
.players_editing_boxes
[name
]
993 local id
= box
.box_id
994 local old_pos
= get_digit_pos(player
)
996 minetest
.log("boxes.move_digits: error: old digit pos is nil")
999 paint_digits(vector
.add(box
.minp
, old_pos
), {name
= "nodes:marbleb"}, id
)
1000 paint_digits(pos
, {name
= "nodes:bronzeb"}, id
)
1001 local meta
= db
.box_get_meta(id
)
1002 meta
.meta
.digit_pos
= vector
.subtract(pos
, box
.minp
)
1003 db
.box_set_meta(id
, meta
)
1006 minetest
.register_privilege("create", "Can create new boxes")
1008 minetest
.register_chatcommand("create", {
1009 params
= "<box_size>",
1010 description
= "Start editing a new box.",
1011 privs
= {server
= true},
1012 func
= function(name
, param
)
1013 if boxes
.players_editing_boxes
[name
] or boxes
.players_in_boxes
[name
] then
1014 minetest
.chat_send_player(name
, "You are already editing a box!")
1018 local size
= tonumber(param
)
1019 if not size
or size
~= math
.floor(size
) or size
<= 0 then
1020 minetest
.chat_send_player(name
, "Please specify a size.")
1024 if size
< 20 or size
> 40 then
1025 minetest
.chat_send_player(name
, "Size is not in the allowed range [20, 40]")
1029 boxes
.make_new(minetest
.get_player_by_name(name
), size
)
1033 minetest
.register_chatcommand("edit", {
1035 description
= "DEPRECATED",
1036 privs
= {server
= true},
1037 func
= function(name
, param
)
1038 minetest
.chat_send_player(name
, "Did you mean /create or /edite?")
1043 minetest
.register_chatcommand("edite", {
1044 params
= "<box_id>",
1045 description
= "Edit an existing box.",
1046 privs
= {create
= true},
1047 func
= function(name
, param
)
1048 if boxes
.players_editing_boxes
[name
] or boxes
.players_in_boxes
[name
] then
1049 minetest
.chat_send_player(name
, "You are already editing a box!")
1053 local id
= tonumber(param
)
1054 if not id
or id
~= math
.floor(id
) or id
< 0 then
1055 minetest
.chat_send_player(name
, "The id you supplied is not a nonnegative integer.")
1059 local meta
= db
.box_get_meta(id
)
1061 minetest
.chat_send_player(name
, "The id you supplied is not in the database.")
1065 if not minetest
.check_player_privs(name
, "server") then
1066 if meta
.meta
.builder
~= name
or meta
.meta
.status
~= db
.STATUS_EDITING
then
1067 minetest
.chat_send_player(name
, "You are not allowed to edit this box.")
1072 local player
= minetest
.get_player_by_name(name
)
1073 local minp
= boxes
.valloc(math
.max(meta
.meta
.size
.x
, meta
.meta
.size
.z
))
1074 local maxp
= vector
.add(minp
, vector
.subtract(meta
.meta
.size
, 1))
1076 local areas_id
= areas
:insert_area(vector
.add(minp
, 1), vector
.subtract(maxp
, 1), name
)
1077 boxes
.players_editing_boxes
[name
] = {
1082 areas_id
= areas_id
,
1083 num_items
= meta
.meta
.num_items
,
1084 entry
= vector
.add(minp
, meta
.meta
.entry
),
1085 exit = vector
.add(minp
, meta
.meta
.exit),
1086 start_edit_time
= minetest
.get_gametime(),
1087 box_name
= meta
.meta
.box_name
,
1088 skybox
= meta
.meta
.skybox
,
1090 boxes
.load(minp
, id
, player
)
1091 local spawnpoint
= vector
.add({x
= 1, y
= 0, z
= 0}, vector
.add(minp
, meta
.meta
.entry
))
1092 player
:set_pos(spawnpoint
)
1093 players
.give_edit_inventory(player
)
1094 music
.start(player
, nil, "create")
1095 -- skybox may not exist for lobbies
1096 if meta
.meta
.skybox
then
1097 skybox
.set(player
, meta
.meta
.skybox
)
1102 function boxes
.save_edit(player
, id
)
1103 local name
= player
:get_player_name()
1104 local box
= boxes
.players_editing_boxes
[name
]
1105 if not box
then return end
1107 local bmeta
= db
.box_get_meta(box
.box_id
)
1112 db
.box_set_data(id
, boxes
.save(box
.minp
, box
.maxp
))
1113 bmeta
.meta
.num_items
= box
.num_items
1114 bmeta
.meta
.entry
= vector
.subtract(box
.entry
, box
.minp
)
1115 bmeta
.meta
.exit = vector
.subtract(box
.exit, box
.minp
)
1116 bmeta
.meta
.box_name
= box
.box_name
1117 if box
.box_name
== "" then
1118 bmeta
.meta
.box_name
= "(No name)"
1120 bmeta
.meta
.skybox
= box
.skybox
1121 local t
= minetest
.get_gametime()
1122 if name
== bmeta
.meta
.builder
then -- Do not count admin edit times
1123 bmeta
.meta
.build_time
= bmeta
.meta
.build_time
+ t
- box
.start_edit_time
1124 box
.start_edit_time
= t
1126 db
.box_set_meta(id
, bmeta
)
1128 minetest
.log("action", name
.. " saved box " .. id
)
1129 minetest
.chat_send_player(name
, "Box " .. id
.. " saved successfully")
1132 function boxes
.stop_edit(player
)
1133 local name
= player
:get_player_name()
1134 local box
= boxes
.players_editing_boxes
[name
]
1135 if not box
then return end
1137 boxes
.cleanup(box
.minp
, box
.maxp
)
1138 boxes
.vfree(box
.minp
)
1139 areas
:remove_area(boxes
.players_editing_boxes
[name
].areas_id
)
1140 boxes
.players_editing_boxes
[name
] = nil
1143 minetest
.register_chatcommand("setbuilder", {
1144 params
= "<id> <name>",
1145 description
= "Set the builder of the box <id> to <name>.",
1146 privs
= {server
= true},
1147 func
= function(name
, param
)
1148 local box_id
, nname
= string.match(param
, "^([^ ]+) +(.+)$")
1149 box_id
= tonumber(box_id
)
1150 if not box_id
or not nname
then
1151 minetest
.chat_send_player(name
, "Supplied box id/name missing or incorrect format!")
1154 local bmeta
= db
.box_get_meta(box_id
)
1156 minetest
.chat_send_player(name
, "Supplied box id does not exist!")
1159 if bmeta
.type ~= db
.BOX_TYPE
then
1160 minetest
.chat_send_player(name
, "This id does not correspond to a box.")
1163 bmeta
.meta
.builder
= nname
1164 db
.box_set_meta(box_id
, bmeta
)
1165 minetest
.chat_send_player(name
, "Done.")
1169 minetest
.register_chatcommand("save", {
1171 description
= "Save the box you are currently editing. If id is supplied, a copy is" ..
1172 "instead saved to the box numbered id.",
1173 privs
= {server
= true},
1174 func
= function(name
, param
)
1175 if not boxes
.players_editing_boxes
[name
] then
1176 minetest
.chat_send_player(name
, "You are not currently editing a box!")
1182 local id
= tonumber(param
)
1183 if not id
or id
~= math
.floor(id
) or id
< 0 then
1184 minetest
.chat_send_player(name
, "The id you supplied is not a non-negative number.")
1190 boxes
.save_edit(minetest
.get_player_by_name(name
), box_id
)
1194 minetest
.register_chatcommand("stopedit", {
1196 description
= "Stop editing a box.",
1197 privs
= {server
= true},
1198 func
= function(name
, param
)
1199 if not boxes
.players_editing_boxes
[name
] then
1200 minetest
.chat_send_player(name
, "You are not currently editing a box!")
1204 local player
= minetest
.get_player_by_name(name
)
1205 if param
~= "false" then
1206 boxes
.save_edit(player
)
1208 boxes
.stop_edit(player
)
1210 players
.return_to_lobby(player
)
1214 local function on_leaveplayer(player
)
1215 minetest
.log("action", player
:get_player_name() .. " left the game")
1217 boxes
.close_box(player
)
1218 boxes
.save_edit(player
)
1219 boxes
.stop_edit(player
)
1222 minetest
.register_on_leaveplayer(on_leaveplayer
)
1223 minetest
.register_on_shutdown(function()
1224 for _
, player
in ipairs(minetest
.get_connected_players()) do
1225 on_leaveplayer(player
)
1230 minetest
.register_on_respawnplayer(function(player
)
1231 local name
= player
:get_player_name()
1232 if boxes
.players_in_boxes
[name
] then
1233 local box
= boxes
.players_in_boxes
[name
]
1234 player
:setpos(box
.respawn
)
1235 elseif boxes
.players_editing_boxes
[name
] then
1236 local box
= boxes
.players_editing_boxes
[name
]
1237 player
:setpos(vector
.add(box
.entry
, {x
= 1, y
= 0, z
= 0}))
1239 players
.return_to_lobby(player
)
1244 minetest
.register_on_dieplayer(function(player
)
1245 local name
= player
:get_player_name()
1246 local box
= boxes
.players_in_boxes
[name
]
1247 if box
and box
.deaths
then
1248 box
.deaths
= box
.deaths
+ 1
1252 minetest
.register_on_player_hpchange(function(player
, hp_change
)
1253 local name
= player
:get_player_name()
1254 local box
= boxes
.players_in_boxes
[name
]
1255 if box
and box
.damage
and hp_change
< 0 then
1256 box
.damage
= box
.damage
- hp_change
1260 function boxes
.can_edit(player
)
1261 local name
= player
:get_player_name()
1262 if boxes
.players_editing_boxes
[name
] then
1265 if boxes
.players_in_boxes
[name
] then
1268 return minetest
.check_player_privs(name
, "server")
1272 local metadata_updates
= {}
1273 minetest
.register_on_punchnode(function(pos
, node
, puncher
, pointed_thing
)
1274 if not puncher
then return end
1275 local name
= puncher
:get_player_name()
1276 if not metadata_updates
[name
] then return end
1277 if not boxes
.players_editing_boxes
[name
] then return end
1278 local box
= boxes
.players_editing_boxes
[name
]
1279 local p
= vector
.subtract(pos
, box
.minp
)
1280 local upd
= metadata_updates
[name
]
1281 local key
= upd
.update_key
1282 local bmeta
= db
.box_get_meta(box
.box_id
)
1283 if upd
.update_type
== "set" then
1285 db
.box_set_meta(box
.box_id
, bmeta
)
1286 metadata_updates
[name
] = nil
1287 sfinv
.set_player_inventory_formspec(puncher
)
1288 elseif upd
.update_type
== "add" then
1289 bmeta
.meta
[key
][#bmeta
.meta
[key
] + 1] = p
1290 db
.box_set_meta(box
.box_id
, bmeta
)
1291 sfinv
.set_player_inventory_formspec(puncher
)
1295 local meta_types
= {
1297 {key_name
= "builder", type = "text"},
1298 {key_name
= "status", type = "number"},
1301 {key_name
= "status", type = "number"},
1304 {key_name
= "signs_to_update", type = "poslist"},
1305 {key_name
= "star_positions", type = "poslist"},
1306 {key_name
= "category_positions", type = "poslist"},
1307 {key_name
= "status", type = "number"},
1311 sfinv
.register_page("boxes:admin", {
1313 is_in_nav
= function(self
, player
, context
)
1314 return minetest
.check_player_privs(player
, "server") and
1315 boxes
.players_editing_boxes
[player
:get_player_name()]
1317 get
= function(self
, player
, context
)
1318 local name
= player
:get_player_name()
1319 local box
= boxes
.players_editing_boxes
[name
]
1320 local upd
= metadata_updates
[name
]
1321 local bmeta
= db
.box_get_meta(box
.box_id
)
1324 for i
, setting
in ipairs(meta_types
[bmeta
.type]) do
1325 local current_value
= bmeta
.meta
[setting
.key_name
]
1326 if setting
.type == "text" then
1327 form
= form
.. "field[0.3," .. (y
+ 0.8) .. ";5,1;" ..
1328 tostring(i
) .. ";" .. setting
.key_name
.. ";" ..
1329 minetest
.formspec_escape(current_value
) .. "]" ..
1330 "field_close_on_enter[" .. tostring(i
) .. ";false]"
1332 elseif setting
.type == "number" then
1333 form
= form
.. "field[0.3," .. (y
+ 0.8) .. ";5,1;" ..
1334 tostring(i
) .. ";" .. setting
.key_name
.. ";" ..
1335 tostring(current_value
) .. "]" ..
1336 "field_close_on_enter[" .. tostring(i
) .. ";false]"
1338 elseif setting
.type == "pos" then
1340 if upd
and upd
.update_key
== setting
.key_name
then
1341 blab
= "Cancel edit"
1343 form
= form
.. "label[0," .. (y
+ 0.3) .. ";" ..
1344 minetest
.formspec_escape(setting
.key_name
.. ": " .. minetest
.pos_to_string(current_value
)) .. "]"
1345 form
= form
.. "button[0," .. (y
+ 0.7) .. ";3,1;" ..
1346 tostring(i
) .. ";" .. blab
.. "]"
1348 elseif setting
.type == "poslist" then
1350 if upd
and upd
.update_key
== setting
.key_name
then
1354 for j
, pos
in ipairs(current_value
) do
1358 cvs
= cvs
.. minetest
.pos_to_string(pos
)
1361 form
= form
.. "label[0," .. (y
+ 0.3) .. ";" ..
1362 minetest
.formspec_escape(setting
.key_name
.. ": " .. cvs
) .. "]"
1363 form
= form
.. "button[0," .. (y
+ 0.7) .. ";3,1;" ..
1364 tostring(i
) .. ";" .. blab
.. "]"
1368 return sfinv
.make_formspec(player
, context
, form
, false)
1370 on_player_receive_fields
= function(self
, player
, context
, fields
)
1371 if not minetest
.check_player_privs(player
, "server") then
1375 local update
= false
1377 local name
= player
:get_player_name()
1378 local box
= boxes
.players_editing_boxes
[name
]
1379 local bmeta
= db
.box_get_meta(box
.box_id
)
1380 local settings
= meta_types
[bmeta
.type]
1381 for index
, value
in pairs(fields
) do
1382 local i
= tonumber(index
)
1384 local setting
= settings
[i
]
1385 if setting
.type == "number" then
1386 value
= tonumber(value
)
1389 if value
~= nil and setting
.type ~= "pos" and setting
.type ~= "poslist" then
1390 bmeta
.meta
[setting
.key_name
] = value
1391 elseif setting
.type == "pos" or setting
.type == "poslist" then
1392 if metadata_updates
[name
] and metadata_updates
[name
].update_key
== setting
.key_name
then
1393 metadata_updates
[name
] = nil
1395 if setting
.type == "poslist" then
1396 bmeta
.meta
[setting
.key_name
] = {}
1398 metadata_updates
[name
] = {
1399 update_type
= (setting
.type == "pos" and "set" or "add"),
1400 update_key
= setting
.key_name
1407 db
.box_set_meta(box
.box_id
, bmeta
)
1410 sfinv
.set_page(player
, "boxes:admin")
1418 -- Handle box expositions
1419 -- These are boxes whose inside can be seen by the players before entering them
1420 -- Proof of concept for now, so only one such box.
1422 local box_expo_minp = {x = 5, y = 0, z = 5}
1423 local box_expo_size = {x = 22, y = 22, z = 22}
1426 minetest.register_chatcommand("expo", {
1427 params = "<box_id>",
1428 description = "Select the chosen box for exposition",
1429 privs = {server = true},
1430 func = function(name, param)
1431 local id = tonumber(param)
1432 if not id or id ~= math.floor(id) or id < 0 then
1433 minetest.chat_send_player(name, "The id you supplied is not a nonnegative integer.")
1437 local meta = db.box_get_meta(id)
1439 minetest.chat_send_player(name, "The id you supplied is not in the database.")
1442 if meta.type ~= db.BOX_TYPE then
1443 minetest.chat_send_player(name, "The id you supplied does not correspond to a box.")
1447 local size = meta.meta.size
1448 if not vector.equals(size, box_expo_size) then
1449 minetest.chat_send_player(name, "The box you chose does not have the correct size.")
1453 boxes.load(box_expo_minp, id, nil)
1456 local minp = vector.add(box_expo_minp, {x = 1, y = 1, z = size.z - 1})
1457 local maxp = vector.add(box_expo_minp, {x = size.x - 2, y = size.y - 1, z = size.z - 1})
1459 local cid_barrier = minetest.get_content_id("nodes:barrier")
1461 local vm = minetest.get_voxel_manip(minp, maxp)
1462 local emin, emax = vm:get_emerged_area()
1463 local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax}
1464 local vmdata = vm:get_data()
1466 for y = minp.y, maxp.y do
1467 local index = va:index(minp.x, y, maxp.z)
1468 for x = minp.x, maxp.x do
1469 vmdata[index] = cid_barrier