-
Notifications
You must be signed in to change notification settings - Fork 0
/
spawnusher.lua
324 lines (264 loc) · 10.4 KB
/
spawnusher.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
--[[
Copyright (c) 2015, Robert 'Bobby' Zenz
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--]]
--- Spawn usher is a system that allows to correct the spawn position of players
-- without knowing anything about the mapgen.
--
-- The system will register callbacks for newplayer and respawnplayer and will
-- try to find an air bubble, either upwards or downwards, which the player can
-- fit into. If an air bubble is found, the player will be moved there. If
-- the block is not loaded, it will be tried again after a certain amount of
-- time.
--
-- The only function that should be called from clients is activate.
spawnusher = {
--- If the system should be activated automatically.
activate_automatically = settings.get_bool("spawnusher_activate", true),
--- If the system is active/has been activated.
active = false,
--- The list of callbacks that are invoked after the player has been placed.
after_spawn_callbacks = List:new(),
--- The physics override that is set to make the player inmovable.
physics_override = {
speed = 0,
jump = 0,
gravity = 0,
sneak = false,
sneak_glitch = false
},
--- The original physics of the payers.
player_physics = {},
--- The list of players that need to be placed.
players = List:new(),
--- If the player should be facing a random direction after spawning.
random_direction = settings.get_bool("spawnusher_random_direction", true),
--- The placement radius around the spawn.
random_placement_radius = settings.get_number("spawnusher_placement_radius", 40),
--- The required air bubble size.
required_bubble_size = settings.get_number("spawnusher_bubble_size", 2),
--- The retry time.
retry_time = settings.get_number("spawnusher_retry_time", 0.5),
--- If the system is currently scheduled for execution.
scheduled = false,
--- The spawnpoint that is configured.
spawnpoint = settings.get_pos("static_spawnpoint", {
x = 0,
y = 0,
z = 0
}),
--- The registered spawnpoint providers.
spawnpoint_providers = List:new()
}
--- Activates the spawn usher system, if it has not been deactivated by
-- a seeting in the configuration.
function spawnusher.activate()
if spawnusher.activate_automatically then
spawnusher.activate_internal()
end
end
--- Activates the system, without checking the configuration. Multiple
-- invokations have no effect.
function spawnusher.activate_internal()
if not spawnusher.active then
minetest.register_on_newplayer(spawnusher.on_spawn_player)
minetest.register_on_respawnplayer(spawnusher.on_spawn_player)
spawnusher.active = true
end
end
--- Tests if the given position is an air bubble big enough.
--
-- @param start_pos The position at which to check.
-- @return true if at the given position is an air bubble big enough.
function spawnusher.is_air_bubble(start_pos)
local pos = {
x = start_pos.x,
y = start_pos.y,
z = start_pos.z
}
for counter = 1, spawnusher.required_bubble_size, 1 do
pos.y = pos.y + 1
if minetest.get_node(pos).name ~= "air" then
return false
end
end
return true
end
--- Schedules the player to be moved later. Also moves the player to the given
-- position.
--
-- @param player The player object.
-- @param current_pos The current position to which the player will be moved.
function spawnusher.move_later(player, current_pos)
player:setpos(current_pos)
spawnusher.players:add(player)
if not spawnusher.scheduled then
spawnusher.scheduled = true
minetest.after(spawnusher.retry_time, spawnusher.move_players)
end
end
--- Moves the player randomly on the x and z plane.
--
-- @param player The Player object to move.
function spawnusher.move_random(player)
local move_x = random.next_int(-spawnusher.random_placement_radius, spawnusher.random_placement_radius)
local move_z = random.next_int(-spawnusher.random_placement_radius, spawnusher.random_placement_radius)
local pos = player:getpos()
pos.x = pos.x + move_x
pos.z = pos.z + move_z
player:setpos(pos)
end
--- Moves the player to a safe location.
--
-- @param player The player object.
function spawnusher.move_player(player)
local pos = player:getpos()
-- Could be while true, but at least this is halfway sane.
while mathutil.in_range(pos.y, -31000, 31000) do
local current = minetest.get_node(pos).name
if current ~= "air" and current ~= "ignore" then
-- The current node is neither air nor ignore, that means it
-- is "solid", so we walk upwards looking for air.
pos.y = pos.y + 1
elseif current == "air" then
-- The current node is air, now we will check if the node below it
-- is also air, if yes we will move downwards, if not we will check
-- if here is an air bubble.
local beneath_pos = {
x = pos.x,
y = pos.y - 1,
z = pos.z
}
local beneath_node = minetest.get_node(beneath_pos).name
if beneath_node == "air" then
-- The node below is air, move two downwards looking for
-- a "solid" node.
pos.y = pos.y - 2
elseif beneath_node == "ignore" then
-- The node below is ignore, means we will have to try again
-- later.
spawnusher.move_later(player, pos)
return
elseif spawnusher.is_air_bubble(pos) then
-- Awesome! Place the user here.
player:setpos(pos)
if spawnusher.random_direction then
-- Randomize the direction in which the player looks.
player:set_look_yaw(math.rad(random.next_int(0, 360)))
end
-- Reset the physics override.
player:set_physics_override(spawnusher.player_physics[player:get_player_name()])
-- Remove the saved one.
spawnusher.player_physics[player:get_player_name()] = nil
-- Invoke the callbacks.
spawnusher.after_spawn_callbacks:invoke(player, pos)
return
else
-- The node beneath is neither air nor ignore and there is no
-- air bubble big enough, lets go upwards and see if that
-- helps.
while mathutil.in_range(pos.y, -31000, 31000) do
pos.y = pos.y + 1
local upward_node = minetest.get_node(pos).name
if upward_node == "ignore" then
spawnusher.move_later(player, pos)
return
elseif upward_node ~= "air" then
break
end
end
end
elseif current == "ignore" then
-- The current node is ignore, which means we need to retry later.
spawnusher.move_later(player, pos)
return
end
end
end
--- Move all players that could not be placed so far.
function spawnusher.move_players()
-- Copy the list to make sure that no one adds a player while we iterate
-- over it. Though, I'm not sure if that is actually possible, but the Java
-- programmer does not stop to scream "race condition" without this.
local to_move_players = spawnusher.players
spawnusher.players = List:new()
to_move_players:foreach(function(player, index)
spawnusher.move_player(player)
end)
-- If there are still players that could not be placed, schedule it again.
if spawnusher.players:size() > 0 then
minetest.after(spawnusher.retry_time, spawnusher.move_players)
else
spawnusher.scheduled = false
end
end
--- Callback for if a player spawns.
--
-- @param player The Player that spawned.
-- @return true, to disable default placement.
function spawnusher.on_spawn_player(player)
-- Override the physics of the player to make sure that the player does
-- not fall while we wait and also does not move around.
spawnusher.player_physics[player:get_player_name()] = player:get_physics_override()
player:set_physics_override(spawnusher.physics_override)
-- Move the player to the set/default spawn point.
player:setpos(spawnusher.spawnpoint)
-- Move the player randomly afterwards.
spawnusher.move_random(player)
local exact_pos = false
local spawn_pos = player:getpos()
-- Run the position through the providers.
spawnusher.spawnpoint_providers:foreach(function(provider, index)
local provided_pos, provided_exact_pos = provider(player, spawn_pos)
if provided_pos ~= nil then
spawn_pos = provided_pos
end
if provided_exact_pos ~= nil then
exact_pos = provided_exact_pos
end
end)
player:setpos(spawn_pos)
if not exact_pos then
-- Now find a nice spawn place for the player.
spawnusher.move_player(player)
end
return true
end
--- Allows to register callbacks after a player has been spawned by spawn usher.
--
-- @param callback The callback to invoke, a function that accepts the player
-- object as first parameter and the spawn point as second.
function spawnusher.register_after_spawn_callback(callback)
spawnusher.after_spawn_callbacks:add(callback)
end
--- Allows to register providers that are called after the final position of
-- the player has been determined. The provider can return a different position,
-- or nil if it is happy with the given position, and if it is is the exact
-- position or not.
--
-- @param provider The provider. A function that accepts two parameters,
-- the Player object and the spawn position that the system
-- calculated. It can return a new position, a table with
-- x y z values, or nil if the position should not be changed.
-- The second return value can be true to make the system use
-- the provided position without looking for an air bubble.
function spawnusher.register_spawnpoint_provider(provider)
spawnusher.spawnpoint_providers:add(provider)
end