aboutsummaryrefslogtreecommitdiff
path: root/ch_core/data.lua
blob: 84a6bcc1988fca79a2f09cf62600d21779ad453a (plain)
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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
ch_core.open_submod("data")

-- POZICE A OBLASTI
-- ===========================================================================

local function setting_get_pos(name, setting_level)
	local result = minetest.setting_get_pos(name)
	if result ~= nil then
		minetest.log("action", "Position setting "..name.." read: "..minetest.pos_to_string(result))
	elseif setting_level == "required" then
		error("Required position setting "..name.." not set or have an invalid format!")
	elseif setting_level == "optional" then
		minetest.log("action", "Position setting "..name.." not set (optional).")
	else
		minetest.log("error", "Position setting "..name.." not set! Will be reset to zeroes.")
		result = vector.zero()
	end
	return result
end

local function setting_get_str(name, setting_level)
	local result = minetest.settings:get(name)
	if result ~= nil then
		minetest.log("action", "String setting "..name.." read: "..result)
	elseif setting_level == "required" then
		error("Required string setting "..name.." not set!")
	elseif setting_level == "optional" then
		minetest.log("action", "String setting "..name.." not set (optional).")
	else
		minetest.log("error", "String setting "..name.." not set! Will be reset to an empty string")
		result = ""
	end
	return result
end

ch_core.config = {
	povinne_vytisky_listname = setting_get_str("ch_povinne_vytisky_listname", "optional") or "main",
}

ch_core.positions = {
	zacatek_1 = setting_get_pos("static_spawnpoint") or vector.new(-70,9.5,40),
	zacatek_2 = setting_get_pos("ch_zacatek_2"),
	zacatek_3 = setting_get_pos("ch_zacatek_3"),
	vezeni_min = setting_get_pos("ch_vezeni_min"),
	vezeni_max = setting_get_pos("ch_vezeni_max"),
	vezeni_cil = setting_get_pos("ch_vezeni_cil"),
	vezeni_dvere = setting_get_pos("ch_vezeni_dvere", "optional"),
	povinne_vytisky = setting_get_pos("ch_povinne_vytisky", "optional"),
}

local zero_by_type = {
	int = 0,
	float = 0.0,
	-- nil = nil,
	string = "",
	vector = vector.zero(),
}

-- GLOBÁLNÍ DATA
-- ===========================================================================
local global_data_data_types = {
	posun_casu = "int",
	povinne_vytisky = "vector",
	povinne_vytisky_listname = "string",
	pristi_ick = "int",
}

local initial_global_data = {
	pristi_ick = 10000,
}

for k, t in pairs(global_data_data_types) do
	if initial_global_data[k] == nil then
		initial_global_data[k] = zero_by_type[t]
	end
end

-- DATA O POSTAVÁCH
-- ===========================================================================
-- key => "int|float", unknown keys are deserialized as strings
local offline_charinfo_data_types = {
	ap_level = "int", -- > 0
	ap_xp = "int", -- >= 0
	ap_version = "int", -- >= 0
	discard_drops = "int", -- 0 = nic, 1 = předměty házet do koše
	domov = "string",
	doslech = "int", -- >= 0
	extended_inventory = "int", -- 0 = normální velikost, 1 = rozšířený inventář
	last_ann_shown_date = "string", -- datum, kdy byla hráči/ce naposledy vypsána oznámení po přihlášení do hry (YYYY-MM-DD)
	last_login = "int", -- >= 0, in seconds since 1. 1. 2000 UTC; 0 is invalid value
	neshybat = "int", -- 0 = shýbat se při stisku Shift, 1 = neshýbat se
	no_ch_sky = "int", -- 0 = krásná obloha ano, 1 = ne
	past_ap_playtime = "float", -- in seconds
	past_playtime = "float", -- in seconds
	pending_registration_privs = "string",
	pending_registration_type = "string",
	rezim_plateb = "int", -- >= 0, význam podle módu ch_bank
	skryt_body = "int", -- 0 => zobrazit, 1 => skrýt
	skryt_hlad = "int", -- 0 => zobrazovat (výchozí), 1 => skrýt
	skryt_zbyv = "int", -- 0 => zobrazovat (výchozí), 1 => skrýt
	stavba = "string",
	ui_event_filter = "string",
	zacatek_kam = "int", -- 1 => Začátek, 2 => Masarykovo náměstí, 3 => Hlavní nádraží

	trest = "int",
}

local storage = ch_core.storage

ch_core.global_data = table.copy(initial_global_data)

local function is_acceptable_name(player_name)
	local types = {}
	for i = 1, #player_name do
		local b = string.byte(player_name, i)
		if b == 0x2d or b == 0x5f then
			types[i] = '_'
		elseif 0x30 <= b and b <= 0x39 then
			types[i] = '0'
		elseif b < 0x61 then
			types[i] = 'A'
		else
			types[i] = 'a'
		end
	end
	local digits, dashes = 0, 0
	for i = 1, #player_name do
		if types[i] == '0' then
			digits = digits + 1
		elseif digits > 0 then
			-- číslice jsou dovoleny jen na konci jména
			return false
		elseif types[i] == '_' then
			dashes = dashes + 1
			-- pomlčky a podtržítka jsou dovoleny jen mezi písmeny
			if i == 1 or i == #player_name or string.lower(types[i - 1]) ~= 'a' or string.lower(types[i + 1]) ~= 'a' then
				return false
			end
		end
	end
	return digits <= 4 and dashes <= 5 -- omezení počtu
end
ch_data.is_acceptable_name = is_acceptable_name

local function is_invalid_player_name(player_name)
	if type(player_name) == "number" then
		player_name = tostring(player_name)
	elseif type(player_name) ~= "string" then
		return "Invalid player_name type "..type(player_name).."!"
	end
	if #player_name == 0 then
		return "Empty player_name!"
	end
	if #player_name > 19 then
		return "Player name "..player_name.." too long!"
	end
	if string.find(player_name, "[^_%w-]") then
		return "Player name '"..player_name.."' contains an invalid character!"
	end
	return false
end

--[[
local function verify_valid_player_name(player_name)
	local message = is_invalid_player_name(player_name)
	if message then
		error(message)
	else
		return tostring(player_name)
	end
end
]]

function ch_core.get_offline_charinfo(player_name)
	core.log("warning", "Obsolete function ch_core.get_offline_charinfo() called!")
	return ch_data.get_offline_charinfo(player_name)
end

function ch_core.save_global_data(keys)
	local ax = type(keys)
	if ax == "table" then
		ax = ch_core.save_global_data
		for _, key in ipairs(keys) do
			ax(key)
		end
		return
	elseif ax ~= "string" and ax ~= "number" then
		error("save_global_data() called with an argument of invalid type "..ax.."!")
	end

	local data_type = global_data_data_types[keys]
	if data_type == nil then
		minetest.log("warning", "save_global_data() called for unknown key '"..keys.."', ignored.")
		return false
	end
	local full_key = "/"..keys
	local value = ch_core.global_data[keys]
	if data_type == "int" then
		storage:set_int(full_key, value or 0)
	elseif data_type == "float" then
		storage:set_float(full_key, value or 0.0)
	elseif data_type == "vector" then
		storage:set_string(full_key, minetest.pos_to_string(vector.round(value)))
	else
		storage:set_string(full_key, value or "")
	end
	return true
end

-- datatype = "string"|"int"|"float"|"vector"|"nil"
function ch_core.save_offline_charinfo(player_name, keys)
	core.log("warning", "Obsolete function ch_core.save_offline_charinfo() called!")
	return ch_data.save_offline_charinfo(player_name)
end

function ch_core.set_titul(player_name, titul)
	local offline_charinfo = ch_data.get_offline_charinfo(player_name)
	offline_charinfo.titul = titul
	ch_data.save_offline_charinfo(player_name)
	local online_charinfo = ch_data.online_charinfo[player_name]
	local player = core.get_player_by_name(player_name)
	if player and online_charinfo and ch_core.compute_player_nametag then
		player:set_nametag_attributes(ch_core.compute_player_nametag(online_charinfo, offline_charinfo))
	end
	return true
end

-- ch_core.set_temporary_titul() -- Nastaví či zruší dočasný titul postavy.
--
function ch_core.set_temporary_titul(player_name, titul, titul_enabled)
	if type(player_name) ~= "string" then
		error("ch_core.set_temporary_titul(): Invalid player_name type: "..type(player_name).."!")
	end
	local online_charinfo = ch_data.online_charinfo[player_name]
	if not online_charinfo or not titul or titul == "" then return false end
	local dtituly = ch_core.get_or_add(online_charinfo, "docasne_tituly")
	if titul_enabled then
		dtituly[titul] = 1
	else
		dtituly[titul] = nil
	end
	local player = core.get_player_by_name(player_name)
	if player and ch_core.compute_player_nametag then
		player:set_nametag_attributes(ch_core.compute_player_nametag(online_charinfo, ch_data.get_offline_charinfo(player_name)))
		return true
	else
		return false
	end
end

local function restore_value_by_type(data_type, value, value_description)
	if data_type == "int" then
		return math.round(tonumber(value))
	elseif data_type == "float" then
		return tonumber(value)
	elseif data_type == "string" then
		return value
	elseif data_type == "vector" then
		local result = minetest.string_to_pos(value)
		if result == nil then
			minetest.log("warning", "Invalid value ignored on restore! (description = "..(value_description or "nil")..")")
			return zero_by_type["vector"]
		end
		return result
	elseif data_type == "nil" then
		return nil
	else
		error("restore_value_by_type() called with invalid data_type "..dump2(data_type).."!")
	end
end

-- restore offline data from the storage
local player_counter, player_field_counter, global_counter, delete_counter = 0, 0, 0, 0
local player_set = {}
local storage_table = (storage:to_table() or {}).fields or {}
for full_key, value in pairs(storage_table) do
	local player_name, key = full_key:match("^([^/]*)/(.+)$")
	if player_name == "" and key ~= "" then
		local data_type = global_data_data_types[key]
		ch_core.global_data[key] = restore_value_by_type(data_type, value, "global property "..key)
		global_counter = global_counter + 1
	--[[elseif player_name and not is_invalid_player_name(player_name) and (player_set[player_name] or ch_data.offline_charinfo[player_name] == nil) then
		local ch_data_offline_charinfo = ch_data.get_or_add_offline_charinfo(player_name)
		local data_type = offline_charinfo_data_types[key]
		if data_type == "int" then
			ch_data_offline_charinfo[key] = math.round(tonumber(value))
			core.log("warning", "Offline charinfo upgraded from storage: "..player_name.."/"..key.."=(int)"..tostring(ch_data_offline_charinfo[key]))
		elseif data_type == "float" then
			ch_data_offline_charinfo[key] = tonumber(value)
			core.log("warning", "Offline charinfo upgraded from storage: "..player_name.."/"..key.."=(float)"..tostring(ch_data_offline_charinfo[key]))
		else
			ch_data_offline_charinfo[key] = value
			core.log("warning", "Offline charinfo upgraded from storage: "..player_name.."/"..key.."=(string)\""..tostring(ch_data_offline_charinfo[key]).."\"")
		end
		player_field_counter = player_field_counter + 1
		if player_set[player_name] == nil then
			player_set[player_name] = true
			player_counter = player_counter + 1
		end]]
	else
		storage:set_string(full_key, "")
		core.log("warning", "Invalid key '"..full_key.."' (value "..value..") removed from mod storage!")
		delete_counter = delete_counter + 1
	end
end
print("[ch_core] Restored "..player_field_counter.." data pairs of "..player_counter.." players and "..global_counter.." global pairs from the mod storage. "..delete_counter.." was deleted.")

for player_name, _ in pairs(player_set) do
	ch_data.save_offline_charinfo(player_name)
end

-- Check and update keys
for key, data_type in pairs(global_data_data_types) do
	if ch_core.global_data[key] == nil and data_type ~= "nil" then
		ch_core.global_data[key] = zero_by_type[data_type]
	end
end

def = {
	description = "Zaznamená do příslušného souboru co nejvíc údajů o aktuálním stavu hráčské postavy. Pouze pro Administraci.",
	params = "<Jmeno_postavy>",
	privs = {server = true},
	func = function(admin_name, player_name)
		local player = core.get_player_by_name(player_name)
		if player == nil then
			return false, "Postava neexistuje!"
		end
		player_name = player:get_player_name()
		local inv = player:get_inventory()
		local result = {
			player_name = player_name,
			pos = player:get_pos(),
			velocity = player:get_velocity(),
			hp = player:get_hp(),
			inv_main = inv:get_list("main"),
			inv_craft = inv:get_list("craft"),
			wield_index = player:get_wield_index(),
			armor_groups = player:get_armor_groups(),
			animation = player:get_animation(),
			attachment = {player:get_attach()},
			children = player:get_children(),
			bone_overrides = player:get_bone_overrides(),
			properties = player:get_properties(),
			is_player = player:is_player(),
			nametag_attributes = player:get_nametag_attributes(),
			look_dir = player:get_look_dir(),
			look_vertical = player:get_look_vertical(),
			look_horizontal = player:get_look_horizontal(),
			breath = player:get_breath(),
			fov = {player:get_fov()},
			meta = player:get_meta():to_table(),
			player_control = player:get_player_control(),
			player_control_bits = player:get_player_control_bits(),
			physics_override = player:get_physics_override(),
			huds = player:hud_get_all(),
			hud_flags = player:hud_get_flags(),
			hotbar_size = player:hud_get_hotbar_itemcount(),
			hotbar_image = player:hud_get_hotbar_image(),
			hotbar_selected_image = player:hud_get_hotbar_selected_image(),
			sky = player:get_sky(),
			sun = player:get_sun(),
			moon = player:get_moon(),
			stars = player:get_stars(),
			clouds = player:get_clouds(),
			day_night_ratio_override = player:get_day_night_ratio(),
			local_animation = {player:get_local_animation()},
			eye_offset = {player:get_eye_offset()},
			lighting = player:get_lighting(),
			flags = player:get_flags(),
			online_charinfo = ch_data.online_charinfo[player_name] or "nil",
			offline_charinfo = ch_data.offline_charinfo[player_name] or "nil",
		}
		result = dump2(result)
		local parts = string.split(result, "\n_[")
		table.sort(parts)
		result = table.concat(parts, "\n_[")
		local path = core.get_worldpath().."/_dump_"..player_name..".txt"
		core.safe_file_write(path, result)
		return true, "Zaznamenáno do: "..path
	end,
}

core.register_chatcommand("dumpplayer", def)

ch_core.close_submod("data")