aboutsummaryrefslogtreecommitdiff
path: root/ch_data/init.lua
blob: 63734612c72534274bc37020e500f619424daf70 (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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
ch_base.open_mod(core.get_current_modname())

local worldpath = core.get_worldpath()
local datapath = worldpath.."/ch_playerdata"
local playerlist_path = worldpath.."/ch_data_players"
local storage = core.get_mod_storage()
local old_online_charinfo = {} -- uchovává online_charinfo[] po odpojení postavy
local players_list, players_set = {}, {} -- seznam/množina všech známých hráčských postav (pro offline_charinfo)
local lc_to_player_name = {} -- pro jména existujících postav lowercase => loginname
local current_format_version = 2

ch_data = {
	online_charinfo = {},
	offline_charinfo = {},
	supported_lang_codes = {cs = true, sk = true},
	-- Tato funkce může být přepsána. Rozhoduje, zda zadané jméno postavy je přijatelné.
	is_acceptable_name = function(player_name) return true end,
	initial_offline_charinfo = {
		-- int (> 0) -- pro [ch_core/ap], udává aktuální úroveň postavy
		ap_level = 1, -- musí být > 0
		-- int (> 0) -- pro [ch_core/ap], udává verzi systému AP (pro upgrade)
		ap_version = 1,
		-- int (>= 0) -- pro [ch_core/ap], udává celkový počet bodů aktivity postavy
		ap_xp = 0,
		-- int {0, 1} -- 0 = nic, 1 = předměty házet do koše
		discard_drops = 0,
		-- string -- pro [ch_core/teleport], pozice uložená příkazem /domů
		domov = "",
		-- int (>= 0) -- pro [ch_core/chat] udává aktuální doslech
		doslech = 50,
		-- int {0, 1} -- pro [ch_core] 0 = normální velikost, 1 = rozšířený inventář
		extended_inventory = 0,
		-- string -- pole příznaků
		flags = "",
		-- string YYYY-MM-DD nebo "" -- datum, kdy byla hráči/ce naposledy vypsána oznámení po přihlášení do hry (YYYY-MM-DD)
		last_ann_shown_date = "1970-01-01",
		-- int (>= 0) -- v sekundách od 1. 1. 2000 UTC; 0 značí neplatnou hodnotu
		last_login = 0,
		-- int {0, 1} -- 0 = shýbat se při stisku Shift; 1 = neshýbat se
		neshybat = 0,
		-- int {0, 1} -- 0 = krásná obloha ano, 1 ne
		no_ch_sky = 0,
		-- float -- v sekundách
		past_ap_playtime = 0.0,
		-- float -- v sekundách
		past_playtime = 0.0,
		-- string -- výčet dodatečných práv naplánovaných pro registraci postavy
		pending_registration_privs = "",
		-- string -- typ naplánované registrace postavy
		pending_registration_type = "",
		-- int -- pro [ch_bank]
		rezim_plateb = 0,
		-- int {0, 1} -- 0 => zobrazit, 1 => skrýt
		skryt_body = 0,
		-- int {0, 1} -- 0 => zobrazovat (výchozí), 1 => skrýt
		skryt_hlad = 0,
		-- int {0, 1} -- 0 => zobrazovat (výchozí), 1 => skrýt
		skryt_zbyv = 0,
		-- string -- pro [ch_core/teleport], pozice uložená příkazem /stavím
		stavba = "",
		-- string -- nastavení filtru událostí
		ui_event_filter = "",
		-- int (>= 1) -- číslo verze uložených dat (umožňuje upgrade)
		version = current_format_version,
		-- int {1, 2, 3} -- volba cíle pro /začátek: 1 => Začátek, 2 => Masarykovo náměstí, 3 => Hlavní nádraží
		zacatek_kam = 1,
	},
	initial_offline_playerinfo = {
		-- int -- výše uloženého trestu (může být záporná)
		trest = 0,
	}
}

-- POZNÁMKA: protože příznaky z následujícího pole se mapují na znaky v řetězci 'flags', nesmí se mazat ani zakomentovat!
-- Je však možno je přejmenovat při zachování pozice.
local flag_ids = {
	"discard_drops",
	"extended_inventory",
	"neshybat",
	"no_ch_sky",
	"skryt_body",
	"skryt_hlad",
	"skryt_zbyv",
}
local flag_name_to_id = {}

for i, flag in ipairs(flag_ids) do
	local old_id = flag_name_to_id[flag]
	if old_id ~= nil then
		error("Flag '"..flag.."' has multiple IDs: "..old_id..", "..i.."!")
	end
	flag_name_to_id[flag] = i
end
ch_data.initial_offline_charinfo.flags = string.rep(" ", #flag_ids)

local function add_player(player_name, new_offline_charinfo, player_info)
	if players_set[player_name] then
		return false
	end
	assert(new_offline_charinfo)
	local lcase = string.lower(player_name)
	-- add player to players_list (persistently):
	table.insert(players_list, player_name)
	core.safe_file_write(playerlist_path, assert(core.serialize(players_list)))
	-- add player to players_set and lc_to_player_name:
	players_set[player_name] = true
	lc_to_player_name[lcase] = player_name
	-- add new offline_charinfo:
	ch_data.offline_charinfo[player_name] = new_offline_charinfo
	-- add new offline_charinfo[].player:
	new_offline_charinfo.player = player_info or table.copy(ch_data.initial_offline_playerinfo)
	if new_offline_charinfo.player.name == nil then
		new_offline_charinfo.player.name = player_name
	end
	return true
end

local function delete_player(player_name)
	-- check if the player exists:
	if not players_set[player_name] then
		return false
	end
	-- detach offline_playerinfo:
	local offline_player_info = assert(ch_data.offline_charinfo[player_name].player)
	local aliases = {}
	for alias, offline_charinfo in pairs(ch_data.offline_charinfo) do
		if alias ~= player_name and offline_charinfo.player.name == player_name then
			offline_player_info = offline_charinfo.player
			table.insert(aliases, alias)
		end
	end
	if #aliases > 0 then
		offline_player_info.name = aliases[1]
		core.log("debug", "delete_player(): dotplayer of "..table.concat(aliases, ",").." corrected to "..player_info.name)
		for _, alias in ipairs(aliases) do
			ch_core.save_offline_playerinfo(alias)
		end
	end
	-- remove player from players_list:
	local lcase = string.lower(player_name)
	for i, pname in ipairs(players_list) do
		if pname == player_name then
			table.remove(players_list, i)
			break
		end
	end
	-- save the player_list:
	core.safe_file_write(playerlist_path, assert(core.serialize(players_list)))
	-- remove player from players_set:
	players_set[player_name] = nil
	-- remove player from lc_to_player_name:
	lc_to_player_name[lcase] = nil
	-- remove player from offline_charinfo:
	ch_data.offline_charinfo[player_name] = nil
	return true
end

function ch_data.get_flag(charinfo, flag_name, default_result)
	local id = flag_name_to_id[flag_name]
	if id ~= nil then
		local result = (charinfo.flags or ""):sub(id, id)
		if result ~= "" then
			return result
		end
	end
	return default_result or " "
end

function ch_data.get_flags(charinfo)
	local flags = charinfo.flags or ""
	local result = {}
	for id, name in ipairs(flag_ids) do
		local value = flags:sub(id, id)
		if value == "" then
			value = " "
		end
		result[name] = value
	end
	return result
end

function ch_data.set_flag(charinfo, flag_name, value)
	local id = flag_name_to_id[flag_name]
	if id == nil then
		return false
	end
	value = (tostring(value).." "):sub(1,1)
	local flags = charinfo.flags or ""
	if flags:len() < id then
		flags = flags..string.rep(" ", id - flags:len() - 1)..value
	else
		flags = flags:sub(1, id - 1)..value..flags:sub(id + 1, -1)
	end
	charinfo.flags = flags
	return true
end

function ch_data.correct_player_name_casing(name)
	return lc_to_player_name[string.lower(name)]
end

function ch_data.get_joining_online_charinfo(player)
	assert(core.is_player(player))
	local player_name = player:get_player_name()
	local result = ch_data.online_charinfo[player_name]
	if result ~= nil then
		return result
	end
	local player_info = core.get_player_information(player_name)
	local now = core.get_us_time()
	result = {
		areas = {{
			id = 0,
			name = "Český hvozd",
			type = 1,
		}},
		-- časová známka vytvoření online_charinfo (vstupu postavy do hry)
		join_timestamp = now,
		-- jazykový kód (obvykle "cs")
		lang_code = player_info.lang_code or "",
		-- úroveň osvětlení postavy
		light_level = 0,
		-- časová známka pro úroveň osvětlení postavy
		light_level_timestamp = now,
		-- verze protokolu
		protocol_version = player_info.protocol_version or 0,
		-- tabulka již zobrazených nápověd (bude deserializována níže)
		navody = {},
		-- co udělat těsně po připojení
		news_role = "new_player",
		-- přihlašovací jméno
		player_name = player_name,
	}

	-- news_role:
	--[[
		5.5.x => formspec_version = 5, protocol_version = 40
		5.6.x => formspec_version = 6, protocol_version = 41
		5.7.x => formspec_version = 6, protocol_version = 42
		5.8.0 => formspec_version = 7, protocol_version = 43
		5.9.0 => formspec_version = ?, protocol_version = ?
		5.10.0 => formspec_version = 8, protocol_version = 46
	]]
	if result.protocol_version < 42 then
		result.news_role = "disconnect"
	elseif not ch_data.supported_lang_codes[result.lang_code] and not core.check_player_privs(player, "server") then
			result.news_role = "invalid_locale"
	elseif core.check_player_privs(player, "ch_registered_player") then
		result.news_role = "player"
	elseif not ch_data.is_acceptable_name(player_name) then
		result.news_role = "invalid_name"
	else
		result.news_role = "new_player"
	end

	ch_data.online_charinfo[player_name] = result
	local prev_online_charinfo = old_online_charinfo[player_name]
	if prev_online_charinfo ~= nil then
		old_online_charinfo[player_name] = nil
		if prev_online_charinfo.leave_timestamp ~= nil then
			result.prev_leave_timestamp = prev_online_charinfo.leave_timestamp
		end
	end
	core.log("action", "JOIN PLAYER(" .. player_name ..") at "..now.." with lang_code \""..result.lang_code..
		"\", formspec_version = "..tostring(player_info.formspec_version)..", protocol_version = "..
		result.protocol_version..", news_role = "..result.news_role..", ip_address = "..tostring(player_info.address))

	-- deserializovat návody:
	local meta = player:get_meta()
	local s = meta:get_string("navody")
	if s and s ~= "" then
		result.navody = core.deserialize(s, true) or result.navody
	end

	if result.news_role ~= "invalid_name" then
		ch_data.get_or_add_offline_charinfo(player_name)
	end
	--[[
	TODO:
	if core.is_creative_enabled(player_name) then
		result.is_creative = true
		if ch_core.set_immortal then
			ch_core.set_immortal(player, true)
				end
			end
		else
			core.log("error", "Player object not available for "..player_name.." in get_joining_online_charinfo()!")
		end
		]]
	return result
end

function ch_data.get_leaving_online_charinfo(player)
	assert(core.is_player(player))
	local player_name = player:get_player_name()
	local result = ch_data.online_charinfo[player_name]
	if result ~= nil then
		result.leave_timestamp = core.get_us_time()
		old_online_charinfo[player_name] = result
		ch_data.online_charinfo[player_name] = nil
		return result
	else
		return old_online_charinfo[player_name]
	end
end

function ch_data.delete_offline_charinfo(player_name)
	if ch_data.online_charinfo[player_name] ~= nil then
		return false, "Postava je ve hře!"
	end
	if not delete_player(player_name) then
		return false, "Mazání selhalo."
	end
	local success, errmsg = os.remove(worldpath.."/ch_playerdata/"..player_name)
	if success then
		return true, "Úspěšně smazáno."
	else
		return false, "Mazání souboru selhalo: "..(errmsg or "nil")
	end
end

function ch_data.get_offline_charinfo(player_name)
	local result = ch_data.offline_charinfo[player_name]
	if result == nil then
		error("Offline charinfo not found for player '"..player_name.."'!")
	end
	return result
end

function ch_data.get_or_add_offline_charinfo(player_name)
	local result = ch_data.offline_charinfo[player_name]
	if result == nil then
		add_player(player_name, table.copy(ch_data.initial_offline_charinfo))
		result = assert(ch_data.offline_charinfo[player_name])
		core.log("action", "[ch_data] Offline charinfo initialized for "..player_name)
		ch_data.save_offline_charinfo(player_name)
	end
	return result
end

local debug_flag = false

function ch_data.save_offline_charinfo(player_name, include_playerinfo)
	if players_set[player_name] == nil then
		return false
	end
	local data = ch_data.offline_charinfo[player_name]
	if data == nil then
		return false
	end
	core.safe_file_write(datapath.."/"..player_name, assert(core.serialize(data)))
	if include_playerinfo and data.player.name ~= player_name then
		local dotplayer_name = data.player.name
		assert(ch_data.offline_charinfo[dotplayer_name])
		assert(ch_data.offline_charinfo[dotplayer_name].player.name == dotplayer_name)
		return ch_data.save_offline_charinfo(dotplayer_name)
	end
	return true
end

function ch_data.save_offline_playerinfo(player_name)
	if players_set[player_name] == nil then
		return false
	end
	local data = ch_data.offline_charinfo[player_name]
	if data == nil then
		return false
	end
	local dotplayer_name = assert(data.player.name)
	assert(ch_data.offline_charinfo[dotplayer_name])
	assert(ch_data.offline_charinfo[dotplayer_name].player.name == dotplayer_name)
	return ch_data.save_offline_charinfo(dotplayer_name)
end

local function on_joinplayer(player, last_login)
	ch_data.get_joining_online_charinfo(player)
end

local function on_leaveplayer(player)
	ch_data.get_leaving_online_charinfo(player)
end

core.register_on_joinplayer(on_joinplayer)
core.register_on_leaveplayer(on_leaveplayer)

local function upgrade_offline_charinfo(player_name, data)
	local old_version = data.version
	if data.version <= 1 then
		data.player.trest = data.trest or 0
		data.trest = nil
	end
	data.version = current_format_version
	core.log("info", "Offline_charinfo["..player_name.."] upgraded from version "..old_version.." to the current version "..data.version..".")
	return true
end

-- Load and initialize:

core.mkdir(datapath)
local function initialize()
	local f = io.open(playerlist_path)
	if f then
		local text = f:read("*a")
		f:close()
		if text ~= nil and text ~= "" then
			local new_players = core.deserialize(text, true)
			if type(new_players) == "table" then
				core.log("action", "[ch_data] "..#new_players.." known players.")
				players_list = new_players
				players_set = {}
				for _, player_name in ipairs(players_list) do
					players_set[player_name] = true
				end
			end
		end
	end
	for _, player_name in ipairs(players_list) do
		f = io.open(datapath.."/"..player_name)
		if f then
			local text = f:read("*a")
			f:close()
			if text ~= nil and text ~= "" then
				local data = core.deserialize(text, true)
				if type(data) == "table" then
					ch_data.offline_charinfo[player_name] = data
					lc_to_player_name[string.lower(player_name)] = player_name
				end
			end
		end
		if ch_data.offline_charinfo[player_name] == nil then
			core.log("error", "[ch_data] deserialization of offline_charinfo["..player_name.."] failed!")
		end
	end
	for player_name, poc in pairs(ch_data.offline_charinfo) do
		-- vivify/upgrade/correct offline_charinfo:
		for key, value in pairs(ch_data.initial_offline_charinfo) do
			if poc[key] == nil then
				poc[key] = value
				core.log("warning", "Missing offline_charinfo key "..player_name.."/"..key.." vivified.")
			end
		end
		-- correct invalid past_playtime:
		if poc.past_playtime < 0 then
			core.log("warning", "Invalid past_playtime for "..player_name.." ("..poc.past_playtime..") corrected to zero!")
			poc.past_playtime = 0 -- correction of invalid data
		end
		-- vivify .player table (including .player.name)
		if poc.player == nil or poc.player.name == nil then
			poc.player = {name = player_name}
		end
	end
	-- link .player tables according to .player.name:
	for player_name, poc in pairs(ch_data.offline_charinfo) do
		local dotplayer_name = assert(poc.player.name)
		if dotplayer_name ~= player_name then
			if players_set[dotplayer_name] and ch_data.offline_charinfo[dotplayer_name] then
				poc.player = assert(ch_data.offline_charinfo[dotplayer_name].player)
			else
				error("offline_charinfo["..player_name.."] looks for player data of '"..dotplayer_name.."', but it doesn't exist!")
			end
		end
	end
	-- upgrade old data:
	for player_name, poc in pairs(ch_data.offline_charinfo) do
		if poc.version < current_format_version then
			upgrade_offline_charinfo(player_name, poc)
		end
	end
end

initialize()
initialize = nil

-- Obsluhy událostí:
-- ======================================================================================================================
local function on_joinplayer(player, last_login)
	local player_name = player:get_player_name()
	local online_charinfo = ch_data.get_joining_online_charinfo(player)
	local offline_charinfo = ch_data.get_offline_charinfo(player_name)
	online_charinfo.doslech = offline_charinfo.doslech

	if offline_charinfo ~= nil then
		offline_charinfo.last_login = os.time() - 946684800
		ch_data.save_offline_charinfo(player_name)
		-- lc_to_player_name[string.lower(player_name)] = player_name
	end

	return true
end

local function save_playtime(online_charinfo, offline_charinfo)
	if offline_charinfo == nil then
		return 0, 0, 0
	end
	local now = core.get_us_time()
	local past_playtime = offline_charinfo.past_playtime or 0
	local current_playtime = math.max(0, 1.0e-6 * (now - online_charinfo.join_timestamp))
	local total_playtime = past_playtime + current_playtime

	offline_charinfo.past_playtime = total_playtime
	ch_data.save_offline_charinfo(online_charinfo.player_name)
	online_charinfo.join_timestamp = nil
	return past_playtime, current_playtime, total_playtime
end

local function on_leaveplayer(player, timedout)
	local player_name = player:get_player_name()
	local online_info = ch_data.get_leaving_online_charinfo(player)

	if online_info.join_timestamp then
		local past_playtime, current_playtime, total_playtime = save_playtime(online_info, ch_data.offline_charinfo[player_name])
		print("PLAYER(" .. player_name .."): played seconds: " .. current_playtime .. " / " .. total_playtime)
	end
end

local function on_shutdown()
	for player_name, online_info in pairs(table.copy(ch_data.online_charinfo)) do
		if online_info.join_timestamp then
			local past_playtime, current_playtime, total_playtime
			past_playtime, current_playtime, total_playtime = save_playtime(online_info, ch_data.offline_charinfo[player_name])
			print("PLAYER(" .. player_name .."): played seconds: " .. current_playtime .. " / " .. total_playtime)
		end
	end
end

local function on_placenode(pos, newnode, placer, oldnode, itemstack, pointed_thing)
	if core.is_player(placer) then
		local player_name = placer:get_player_name()
		local online_charinfo = ch_data.online_charinfo[player_name]
		if online_charinfo ~= nil then
			online_charinfo.last_placenode_ustime = core.get_us_time()
		end
	end
end

local function on_dignode(pos, oldnode, digger)
	if core.is_player(digger) then
		local player_name = digger:get_player_name()
		local online_charinfo = ch_data.online_charinfo[player_name]
		if online_charinfo ~= nil then
			online_charinfo.last_dignode_ustime = core.get_us_time()
		end
	end
end

function ch_data.clear_help(player)
	local player_name = player:get_player_name()
	local online_charinfo = ch_data.online_charinfo[player_name]
	if online_charinfo then
		online_charinfo.navody = {}
		player:get_meta():set_string("navody", core.serialize(online_charinfo.navody))
		return true
	else
		return false
	end
end

--[[
	Otestuje, zda podle online_charinfo má dané postavě být zobrazený
	v četu návod k položce daného názvu. Pokud ano, nastaví příznak, aby se
	to znovu již nestalo, a vrátí definici daného předmětu,
	z níž lze z položek description a _ch_help sestavit text k zobrazení.
]]
function ch_data.should_show_help(player, online_charinfo, item_name)
	local def = core.registered_items[item_name]
	if def and def._ch_help then
		if def._ch_help_group then
			item_name = def._ch_help_group
		end
		local navody = online_charinfo.navody
		if not navody then
			navody = {[item_name] = 1}
			online_charinfo.navody = navody
			player:get_meta():set_string("navody", core.serialize(navody))
			return def.description ~= nil and def
		end
		if not navody[item_name] then
			navody[item_name] = 1
			player:get_meta():set_string("navody", core.serialize(navody))
			return def.description ~= nil and def
		end
	end
	return nil
end

function ch_data.nastavit_shybani(player_name, shybat)
	local offline_charinfo = ch_data.offline_charinfo[player_name]
	if offline_charinfo == nil then
		core.log("error", "ch_data.nastavit_shybani(): Expected offline charinfo for player "..player_name.." not found!")
		return false
	end
	local new_state
	if shybat then
		new_state = 0
	else
		new_state = 1
	end
	offline_charinfo.neshybat = new_state
	ch_data.save_offline_charinfo(player_name)
	return true
end

core.register_on_joinplayer(on_joinplayer)
core.register_on_leaveplayer(on_leaveplayer)
core.register_on_shutdown(on_shutdown)
core.register_on_dignode(on_dignode)
core.register_on_placenode(on_placenode)

local def = {
	description = "Smaže údaje o tom, ke kterým předmětům již byly postavě zobrazeny nápovědy, takže budou znovu zobrazovány nápovědy ke všem předmětům.",
	func = function(player_name, param)
		if ch_data.clear_help(core.get_player_by_name(player_name)) then
			return true, "Údaje smazány."
		else
			core.log("error", "/návodyznovu: vnitřní chyba serveru ("..player_name..")!")
			return false, "Vnitřní chyba serveru"
		end
	end,
}
core.register_chatcommand("návodyznovu", def)
core.register_chatcommand("navodyznovu", def)

def = {
	description = "Odstraní údaje o postavě uložené v systému ch_data. Postava nesmí být ve hře.",
	privs = {server = true},
	func = function(player_name, param)
		local offline_charinfo = ch_data.offline_charinfo[param]
		if not offline_charinfo then
			return false, "Data o "..param.." nenalezena!"
		end
		if ch_data.delete_offline_charinfo(param) then
			return true, "Data o "..param.." smazána."
		else
			return false, "Při odstraňování nastala chyba."
		end
	end,
}

core.register_chatcommand("delete_offline_charinfo", def)

def = {
	description = "Trvale vypne či zapne shýbání postavy při držení Shiftu.",
	params = "<ano|ne>",
	func = function(player_name, param)
		if param ~= "ano" and param ~= "ne" then
			return false, "Chybná syntaxe."
		end
		if ch_data.nastavit_shybani(player_name, param == "ano") then
			core.chat_send_player(player_name, "*** Shýbání postavy "..(param == "ne" and "vypnuto" or "zapnuto")..".")
			return true
		else
			core.log("error", "/shybat: Expected offline charinfo for player "..player_name.." not found!")
			return false, "Vnitřní chyba serveru: Data postavy nenalezena."
		end
	end,
}

core.register_chatcommand("shýbat", def)
core.register_chatcommand("shybat", def)

local function merge_playerinfos(player_name_a, player_name_b)
	if player_name_a == player_name_b then
		return false, "'"..player_name_a.."' a '"..player_name_b.."' reprezentují tutéž postavu!"
	end
	local oci_a = ch_data.offline_charinfo[player_name_a]
	local oci_b = ch_data.offline_charinfo[player_name_b]
	if oci_a == nil then
		return false, "Postava '"..player_name_a.."' neexistuje!"
	end
	if oci_b == nil then
		return false, "Postava '"..player_name_b.."' neexistuje!"
	end
	local dotplayer_a = assert(oci_a.player.name)
	local dotplayer_b = assert(oci_b.player.name)
	if dotplayer_a == dotplayer_b then
		return false, "Postavy '"..player_name_a.."' a '"..player_name_b.."' již patří stejné/mu hráči/ce '"..dotplayer_a.."'."
	end
	local aliases = {dotplayer_b}
	for alias, offline_charinfo in pairs(ch_data.offline_charinfo) do
		if alias ~= dotplayer_b and offline_charinfo.player.name == dotplayer_b then
			table.insert(aliases, alias)
		end
	end
	ch_data.save_offline_charinfo(dotplayer_b)
	for _, alias in ipairs(aliases) do
		ch_data.offline_charinfo[alias].player = oci_a.player
		ch_data.save_offline_charinfo(alias)
		core.log("action", "[MERGE] Player info of .player "..dotplayer_a.." assigned to player "..alias..".")
	end
	return true, "Postavy "..table.concat(aliases, ",").." přiřazeny hráči/ce "..dotplayer_a.."."
end

local function set_main_player(player_name)
	local oci_a = ch_data.offline_charinfo[player_name]
	if oci_a == nil then
		return false, "Postava '"..player_name.."' neexistuje!"
	end
	local old_main = assert(oci_a.player.name)
	if old_main == player_name then
		return false, "Postava '"..player_name.."' již je hlavní."
	end
	local aliases = {}
	for alias, offline_charinfo in pairs(ch_data.offline_charinfo) do
		if alias ~= old_main and offline_charinfo.player.name == old_main then
			table.insert(aliases, alias)
		end
	end
	oci_a.player.name = player_name
	ch_data.save_offline_charinfo(old_main)
	for _, alias in ipairs(aliases) do
		ch_data.save_offline_charinfo(alias)
	end
	return true, "Postava "..player_name.." nastavena jako hlavní (původní hlavní postava: "..old_main..")."
end

function ch_data.get_player_characters(player_name)
	local offline_charinfo = ch_data.offline_charinfo[player_name]
	if offline_charinfo == nil then
		return nil
	end
	local main_name = offline_charinfo.player.name
	local result = {}
	for name, oci in pairs(ch_data.offline_charinfo) do
		if oci.player.name == main_name then
			table.insert(result, name)
		end
	end
	if #result > 1 then
		table.sort(result, function(a, b) return (a == main_name and b ~= main_name) or a < b end) -- TODO: better sorting
	end
	return result, main_name
end

def = {
	description = "Sloučí hráčská data odpovídající postavě B s hráčskými daty odpovídajícími postavě A",
	params = "<Jmeno_postavy_hlavni_A> <Jmeno_postavy_vedlejsi_B>",
	privs = {server = true},
	func = function(admin_name, param)
		local a, b = string.match(param, "^([^ ]+) +([^ ]+)$")
		if a == nil or b == nil then
			return false, "Chybné zadání!"
		end
		local result, message = merge_playerinfos(a, b)
		return result, message
	end,
}

core.register_chatcommand("připojit_postavu", def)
core.register_chatcommand("pripojit_postavu", def)

def = {
	description = "Změní hlavní postavu hráče/ky",
	params = "<Nova_hlavni_postava>",
	privs = {server = true},
	func = function(admin_name, param)
		local result, message = set_main_player(param)
		return result, message
	end,
}

core.register_chatcommand("hlavní_postava", def)
core.register_chatcommand("hlavni_postava", def)

ch_base.close_mod(core.get_current_modname())