1-- Auto-create instance of NPCs binding
2-- (This avoid the need to explicitely it in the dialog scripts)
3-- The dialog's name of the NPC is used to create the Lua instance.
4-- It must univoquely identify one single NPC. Failing to respect
5-- that constraint can lead to some strange behaviors...
6-- You were warned !
7setmetatable(_G, {
8	__index = function(t, name)
9		if rawget(_G, name) == nil then
10			-- script_helpers.lua is also loaded in the 'config'
11			-- context, without the FDrpg module being loaded.
12			if rawget(_G, "FDrpg") == nil then
13				return nil
14			end
15			local rtn, npc = pcall(FDrpg.get_npc, name)
16			if not rtn then
17				return nil
18			end
19			-- print("Creating " .. name)
20			rawset(_G, name, npc)
21			return npc
22		end
23		return rawget(_G, name)
24	end,
25})
26
27-- set the random seed
28math.randomseed(os.time())
29
30-- aliases for dialogs
31get_program = get_program_revision
32
33-- "_" is used as a gettext marker in dialogs as well as in config files.
34--
35-- For dialog files, it is bound to a C function which translates text using
36-- the freedroidrpg-dialog domain.
37--
38-- For config files, "_" is just used to declare i18n texts, but does not
39-- involve immediat translation. This thus has to be a 'void' function.
40--
41-- So, if _() was not defined when the lua thread was created (config files use
42-- case), it is set to a 'void' function.
43_ = _ or function(t) return t end
44
45-- compute the "town score" to determine whether the player can become
46-- a red guard
47function get_town_score()
48	local town_score = 0
49
50	if (has_quest("Deliverance")) then
51		if (data_cube_lost) then
52			town_score = town_score - 5
53		elseif (done_quest("Deliverance")) then
54			town_score = town_score + 5
55		end
56	end
57
58	if (done_quest("Bender's problem")) then
59		town_score = town_score + 10
60	end
61
62	if (done_quest("The yellow toolkit")) then
63		town_score = town_score + 15
64	end
65
66	if (done_quest("Anything but the army snacks, please!")) then
67		town_score = town_score + 10
68	end
69
70	if (done_quest("Novice Arena")) then
71		town_score = town_score + 10
72	end
73
74	if (done_quest("Time to say goodnight")) then
75		town_score = town_score + 20
76	end
77
78	if (done_quest("Opening a can of bots...")) then
79		town_score = town_score + 15
80	end
81
82	if (KevinMurderCongratulation) then
83		town_score = town_score + 20
84	end
85
86    return town_score
87end
88
89-- this table describes obstacle states
90obstacle_states = {
91	  [3] = {["opened"] = -1, ["closed"] = 3,},
92	  [4] = {["opened"] = -1, ["closed"] = 4,},
93	  [6] = {["opened"] =  6, ["closed"] = 26,},
94	  [7] = {["opened"] =  6, ["closed"] = 26,},
95	  [8] = {["opened"] =  6, ["closed"] = 26,},
96	  [9] = {["opened"] =  6, ["closed"] = 26,},
97	 [10] = {["opened"] =  6, ["closed"] = 26,},
98
99	 [11] = {["opened"] = 11, ["closed"] = 27,},
100	 [12] = {["opened"] = 11, ["closed"] = 27,},
101	 [13] = {["opened"] = 11, ["closed"] = 27,},
102	 [14] = {["opened"] = 11, ["closed"] = 27,},
103	 [15] = {["opened"] = 11, ["closed"] = 27,},
104
105	 [26] = {["closed"] = 26, ["opened"] = 6,},
106	 [27] = {["closed"] = 27, ["opened"] = 11,},
107
108	 [32] = {["enabled"] = 32, ["disabled"] = 324,},
109	 [33] = {["enabled"] = 33, ["disabled"] = 325,},
110	 [34] = {["enabled"] = 34, ["disabled"] = 326,},
111	 [35] = {["enabled"] = 35, ["disabled"] = 327,},
112
113	[181] = {["opened"] = 181, ["closed"] = 191,},
114	[182] = {["opened"] = 181, ["closed"] = 191,},
115	[183] = {["opened"] = 181, ["closed"] = 191,},
116	[184] = {["opened"] = 181, ["closed"] = 191,},
117	[185] = {["opened"] = 181, ["closed"] = 191,},
118
119	[186] = {["opened"] = 186, ["closed"] = 192,},
120	[187] = {["opened"] = 186, ["closed"] = 192,},
121	[188] = {["opened"] = 186, ["closed"] = 192,},
122	[189] = {["opened"] = 186, ["closed"] = 192,},
123	[190] = {["opened"] = 186, ["closed"] = 192,},
124
125	[191] = {["closed"] = 191, ["opened"] = 181,},
126	[192] = {["closed"] = 192, ["opened"] = 186,},
127
128	[235] = {["broken"] = 237,},
129	[236] = {["broken"] = 237,},
130	[237] = {["broken"] = 237,},
131	[323] = {["broken"] = 237,},
132
133	[324] = {["disabled"] = 324, ["enabled"] = 32,},
134	[325] = {["disabled"] = 325, ["enabled"] = 33,},
135	[326] = {["disabled"] = 326, ["enabled"] = 34,},
136	[327] = {["disabled"] = 327, ["enabled"] = 35,},
137
138	[351] = {["closed"] = 351, ["opened"] = 353,},
139	[352] = {["closed"] = 352, ["opened"] = 358,},
140
141	[353] = {["opened"] = 353, ["closed"] = 351,},
142	[354] = {["opened"] = 353, ["closed"] = 351,},
143	[355] = {["opened"] = 353, ["closed"] = 351,},
144	[356] = {["opened"] = 353, ["closed"] = 351,},
145	[357] = {["opened"] = 353, ["closed"] = 351,},
146
147	[358] = {["opened"] = 358, ["closed"] = 352,},
148	[359] = {["opened"] = 358, ["closed"] = 352,},
149	[360] = {["opened"] = 358, ["closed"] = 352,},
150	[361] = {["opened"] = 358, ["closed"] = 352,},
151	[362] = {["opened"] = 358, ["closed"] = 352,},
152
153	[388] = {["enabled"] = 388, ["disabled"] = 407,},
154	[407] = {["disabled"] = 407, ["enabled"] = 388,},
155
156	-- glass walls
157	[281] = {["intact"] = 281, ["broken"] = 348,},
158	[282] = {["intact"] = 282, ["broken"] = 446,},
159
160	[348] = {["broken"] = 348, ["intact"] = 281,},
161	[446] = {["broken"] = 446, ["intact"] = 282,},
162
163	-- chests
164	[28] = {["locked"] = 28, ["unlocked"] = 30,},
165	[29] = {["locked"] = 29, ["unlocked"] = 31,},
166
167	[30] = {["unlocked"] = 30, ["locked"] = 28,},
168	[31] = {["unlocked"] = 31, ["locked"] = 29,},
169
170	[372] = {["locked"] = 372, ["unlocked"] = 374,},
171	[373] = {["locked"] = 373, ["unlocked"] = 375,},
172
173	[374] = {["unlocked"] = 374, ["locked"] = 372,},
174	[375] = {["unlocked"] = 375, ["locked"] = 373,},
175
176
177	[376] = {["locked"] = 376, ["unlocked"] = 378,},
178	[377] = {["locked"] = 377, ["unlocked"] = 379,},
179
180	[378] = {["unlocked"] = 378, ["locked"] = 376,},
181	[379] = {["unlocked"] = 379, ["locked"] = 377,},
182
183	-- lootable shelves
184	[466] = {["unlooted"] = 466, ["looted"] = 467,},
185	[467] = {["looted"] = 467, ["unlooted"] = 466,},
186
187	[468] = {["unlooted"] = 468, ["looted"] = 469,},
188	[469] = {["looted"] = 469, ["unlooted"] = 468,},
189
190	[470] = {["unlooted"] = 470, ["looted"] = 471,},
191	[471] = {["looted"] = 471, ["unlooted"] = 470,},
192
193	[472] = {["unlooted"] = 472, ["looted"] = 473,},
194	[473] = {["looted"] = 473, ["unlooted"] = 472,},
195
196	[474] = {["unlooted"] = 474, ["looted"] = 475,},
197	[475] = {["looted"] = 475, ["unlooted"] = 474,},
198
199	[476] = {["unlooted"] = 476, ["looted"] = 477,},
200	[477] = {["looted"] = 477, ["unlooted"] = 476,},
201
202	[478] = {["unlooted"] = 478, ["looted"] = 479,},
203	[479] = {["looted"] = 479, ["unlooted"] = 478,},
204
205	[480] = {["unlooted"] = 480, ["looted"] = 481,},
206	[481] = {["looted"] = 481, ["unlooted"] = 480,},
207
208	-- trapdoors
209	[150] = {["opened"] = 150, ["closed"] = 482,},
210	[482] = {["closed"] = 482, ["opened"] = 150,},
211
212	[149] = {["opened"] = 149, ["closed"] = 483,},
213	[483] = {["closed"] = 483, ["opened"] = 149,},
214
215	[24] = {["opened"] = 24, ["closed"] = 61,},
216	[61] = {["closed"] = 61, ["opened"] = 24,},
217
218	[23] = {["opened"] = 23, ["closed"] = 62,},
219	[62] = {["closed"] = 62, ["opened"] = 23,},
220
221	-- teleporter
222	[16] = {["purple"] = 16, ["blue"] = 17,},
223	[17] = {["blue"] = 17, ["purple"] = 16,},
224};
225
226function get_obstacle_state_id(id, state)
227	local a = rawget(obstacle_states, id);
228	if (a == nil) then
229		error("Obstacle number " .. id .. " does not have any states defined.", 2);
230	end
231
232	a = rawget(a, state)
233	if (a == nil) then
234		error("State \"" .. state .. "\" is not defined for obstacle type " .. id .. ".", 2);
235	end
236
237	return a
238end
239
240function change_obstacle_state(label, state)
241	local id = get_obstacle_type(label);
242
243	change_obstacle_type(label, get_obstacle_state_id(id, state));
244end
245
246function cmp_obstacle_state(label, state)
247	local id = get_obstacle_type(label);
248
249	return (id == get_obstacle_state_id(id, state));
250end
251
252-- Quest functions
253function add_quest(quest, text) -- FDtux:add_quest
254	if (not running_benchmark()) then
255		if done_quest(quest) or
256		   has_quest(quest) then
257			if (not running_benchmark()) then -- don't spam the validator
258				print(FDutils.text.highlight("\n\tSEVERE ERROR", "red"))
259				print(FDutils.text.highlight("\tTried to assign already assigned quest!", "red"))
260				print(FDutils.text.highlight("\tWe will continue execution, quest is:", "red"))
261				print(FDutils.text.highlight(quest, "red"))
262			end
263		end
264	end
265	assign_quest(quest, text)
266	play_sound("effects/Mission_Status_Change_Sound_0.ogg")
267	if (run_from_dialog()) then
268		cli_says("   "..S_"New Quest assigned: " .. D_(quest),"NO_WAIT")
269		npc_says("")
270	else
271		display_big_message("   "..S_"New Quest assigned: " .. D_(quest))
272	end
273end
274
275function update_quest(quest, text) -- FDtux:update_quest
276	if (has_quest(quest)) then
277		add_diary_entry(quest, text)
278		play_sound("effects/Mission_Status_Change_Sound_0.ogg")
279		if (run_from_dialog()) then
280			cli_says("   ".._"Quest log updated: " .. D_(quest), "NO_WAIT")
281			npc_says("")
282		else
283			display_big_message("   "..S_"Quest log updated: " .. D_(quest))
284		end
285	else -- we don't have the quest, wtf?
286		if (not running_benchmark()) then -- don't spam the validator
287			print(FDutils.text.highlight("\n\tSEVERE ERROR", "red"))
288			print(FDutils.text.highlight("\tTried to update quest that was never assigned!", "red"))
289			print(FDutils.text.highlight("\tWe will continue execution, quest is:", "red"))
290			print(FDutils.text.highlight(quest, "red"))
291		end
292	end
293end
294
295function end_quest(quest, text) -- FDtux:end_quest
296	if (not running_benchmark()) then -- don't spam the validator
297		if (done_quest(quest)) then
298				print(FDutils.text.highlight("\n\tERROR", "red"))
299				print(FDutils.text.highlight("\tTried to end already done quest!", "red"))
300				print(FDutils.text.highlight("\tWe will continue execution, quest is:", "red"))
301				print(FDutils.text.highlight(quest, "red"))
302		elseif (not has_quest(quest)) then
303				print(FDutils.text.highlight("\n\tSEVERE ERROR", "red"))
304				print(FDutils.text.highlight("\tTried to end never assigned quest!", "red"))
305				print(FDutils.text.highlight("\tWe will continue execution, quest is:", "red"))
306				print(FDutils.text.highlight(quest, "red"))
307		end
308	end
309
310	complete_quest(quest, text)
311	play_sound("effects/Mission_Status_Change_Sound_0.ogg")
312	if (run_from_dialog()) then
313		cli_says("   "..S_"Quest completed: " .. D_(quest),"NO_WAIT")
314		npc_says("")
315	else
316		display_big_message("   "..S_"Quest completed: " .. D_(quest))
317	end
318end
319
320-- Set faction and name
321function npc_faction(faction, name)
322	set_npc_faction(faction)
323	if (name == nil) then
324	else
325		set_bot_name(name)
326	end
327end
328
329function chat_says_format(text, ...)
330	local arg = {...}
331	local no_wait = "WAIT"
332	if (arg[#arg] == "NO_WAIT") then
333		no_wait = "NO_WAIT"
334		table.remove(arg)
335	end
336	text = string.format(text, table.unpack(arg))
337	return text, no_wait
338end
339
340function tux_says(text, ...) -- FDtux:says
341	local text, no_wait = chat_says_format('\1- ' .. text .. '\n', ...)
342	chat_says(text, no_wait)
343end
344
345function npc_says(text, ...) -- FDnpc:says
346	local text, no_wait = chat_says_format('\2' .. text .. '\n', ...)
347	chat_says(apply_bbcode(text,"\3","\2"), no_wait)
348end
349
350function cli_says(text, ...)
351	local text, no_wait = chat_says_format('\3' .. text, ...)
352	chat_says(text, no_wait)
353end
354
355function display_console_message(text)
356	event_display_console_message(apply_bbcode(text,"\4","\5"))
357end
358
359-- Magic font-numbers table:
360-- number  font face         default
361-- '\1'    medium orange    tux_says()
362-- '\2'    medium yellow    npc_says()
363-- '\3'    medium blue      cli_cays()
364-- '\4'    small blue
365-- '\5'    small yellow   display_console_message()
366function apply_bbcode(text,magic_num_b,magic_num_nrm)
367	local text = string.gsub(text,  '%[b%]', magic_num_b)
368	text = string.gsub(text, '%[/b%]', magic_num_nrm)
369	return text
370end
371
372function npc_says_random(...) -- FDnpc:says_random
373	arg = {...}
374	if (arg[#arg] == "NO_WAIT") then
375		npc_says(arg[math.random(#arg-1)],"NO_WAIT")
376	else
377		npc_says(arg[math.random(#arg)])
378	end
379end
380
381function tux_says_random(...) -- FDtux:says_random
382	arg = {...}
383	if (arg[#arg] == "NO_WAIT") then
384		tux_says(arg[math.random(#arg-1)],"NO_WAIT")
385	else
386		tux_says(arg[math.random(#arg)])
387	end
388end
389
390function get_random(...)
391	arg = {...}
392	return arg[math.random(#arg)]
393end
394
395function del_gold(gold_amount) -- FDtux:del_gold
396	if (gold_amount <= get_gold()) then
397		add_gold(-gold_amount)
398		return true
399	else
400		return false
401	end
402end
403
404function count_item(item_name) -- FDtux:count_item
405	local number = count_item_backpack(item_name)
406	if has_item_equipped(item_name) then
407		return number + 1
408	else
409		return number
410	end
411end
412
413function has_item_backpack(item_name) -- FDtux:has_item_backpack
414	return (count_item_backpack(item_name) > 0)
415end
416
417function del_item(item_name) -- FDtux:del_item
418	if (count_item_backpack(item_name) > 0) then
419		del_item_backpack(item_name)
420		return true
421	else
422		return false
423	end
424end
425
426function del_points(num_points) -- FDtux:del_points
427	if (get_training_points() >= num_points) then
428		del_training_points(num_points)
429		return true
430	else
431		return false
432	end
433end
434
435function can_tux_train(gold_amount, num_points) -- FDtux:can_train
436	if (get_gold() < gold_amount) then
437		return false
438	end
439
440	if (get_training_points() < num_points) then
441		return false
442	end
443
444	return true
445end
446
447function train_skill(gold_amount, num_points, skill) -- FDtux:train_skill
448	if (get_gold() < gold_amount) then
449		return false
450	end
451
452	if (get_training_points() < num_points) then
453		return false
454	end
455
456	add_gold(-gold_amount)
457	del_training_points(num_points)
458	improve_skill(skill)
459	return true
460end
461
462function train_program(gold_amount, num_points, program) -- FDtux:train_program
463	if (get_gold() < gold_amount) then
464		return false
465	end
466
467	if (get_training_points() < num_points) then
468		return false
469	end
470
471	add_gold(-gold_amount)
472	del_training_points(num_points)
473	improve_program(program)
474	return true
475end
476
477function tux_hp_ratio() -- FDtux:get_hp_ratio
478	return get_tux_hp()/get_tux_max_hp()
479end
480
481function npc_damage_ratio() -- FDnpc:get_damage_ratio
482	return npc_damage_amount()/npc_max_health()
483end
484
485function del_health(num_points) -- FDtux:del_health
486	if (num_points < get_tux_hp()) then
487		hurt_tux(num_points)
488		return true
489	else
490		return false
491	end
492end
493
494function drain_bot() -- FDnpc:drain_health
495--[[ npc_damage_amount - npc_max_health   will return the HPs of the droid
496	 for example:  max_HP = 20, DMG_amount = 10 ; 10-20 = -10
497					 hurt_tux(-10) will heal tux by 10 HP
498	 to have the difficulty lvl influence the HP tux gets we divide the
499	 heal-HP by the difficulty_lvl+1 (to prevent division by zero)
500	 -10/2 (difficulty_lvl = 3/hard) = -5.  Tux will be healed by 5 HP ]]--
501	hurt_tux((npc_damage_amount() - npc_max_health())/(difficulty_level()+1))
502	drop_dead()
503end
504
505function difficulty()
506	local levels = {"easy", "normal", "hard"}
507	return levels[difficulty_level()+1]
508end
509
510function get_random_bot_59()
511	local bot_59 = get_random(123, 139, 247, 249, 296, 296, 302, 302, 329, 329, 420,
512			  420, 476, 476, 493, 493, 516, 516, 516, 571, 571, 571, 598,
513			  598, 598, 614, 614, 614, 615, 615, 615, 629, 629, 629, 711,
514			  711, 711, 742, 742, 742, 751, 751, 751, 821, 821, 821, 834,
515			  834, 834, 883, 883, 883, 999, 999, 999, 543,
516			  543, 543, 603) -- "GUN")
517	return bot_59
518end
519
520function has_item(...) -- FDtux:has_item
521	for i,item in ipairs({...}) do
522		if (not (count_item(item) > 0)) then
523			return false
524		end
525	end
526	return true
527end
528
529--[[
530>>>>>>>>>>>>>>>>>>                                  <<<<<<<<<<<<<<<<<<<<
531                  24 / Debug Level  below this line:
532~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
533]]--
534
535function closelvl24doors()
536	change_obstacle_state("24door1", "closed")
537	change_obstacle_state("24door2", "closed")
538	change_obstacle_state("24door3", "closed")
539	change_obstacle_state("24door4", "closed")
540end
541
542function openlvl24doors()
543	change_obstacle_state("24door1", "opened")
544	change_obstacle_state("24door2", "opened")
545	change_obstacle_state("24door3", "opened")
546	change_obstacle_state("24door4", "opened")
547end
548
549function level24obstacles()
550	local randobstacletype
551	function level24newid()
552		randobstacletype = math.random(0,465) -- 466 obstacles
553	end
554	function level24idcheck()
555		if (randobstacletype == 25) then
556		   -- cat ./map/obstacle_specs.lua | grep "image_filenames" | nl -v 0| grep "DUMMY"     (note: skip the last one)
557			display_big_message(randobstacletype.." getting new id")
558			level24newid()
559			level24idcheck()
560		end
561	end
562	level24newid()
563	level24idcheck()
564	display_big_message(randobstacletype)
565	change_obstacle_type("24randobst", randobstacletype)
566end
567
568
569