Compare commits

...

374 Commits

Author SHA1 Message Date
Sokomine
0548172db0 fixed bug #8663 if meta field is missing 2025-06-18 04:06:00 +02:00
Sokomine
38081f8f17 fixed operation with two variables 2025-06-09 04:04:46 +02:00
Sokomine
cce17c7c23 fixed undeclared globals 2025-06-09 03:41:34 +02:00
Sokomine
94906c221b allow to combine values of variables via operator 2025-06-09 02:52:39 +02:00
Sokomine
7896d53dd6 check parameters in set_variable_to_random_number better 2025-06-09 02:19:58 +02:00
Sokomine
6b6c31e4aa adjust real_owner when changing owner as well 2025-06-09 01:53:18 +02:00
Sokomine
b371f15801 supported "at least" for accepting items 2025-06-08 16:33:44 +02:00
Sokomine
3855a9b393 allow to take parts of a stack in d_got_item if take_as_wanted is set 2025-06-08 16:32:57 +02:00
Sokomine
9d2fb157d0 fixed two bugs in replacing vars in text 2025-06-08 14:00:03 +02:00
Sokomine
2b7db390c5 made action trade work again 2025-06-01 05:22:17 +02:00
Sokomine
208cafc725 fixed local variables 2025-05-31 19:28:16 +02:00
Sokomine
de001c3539 save quest vars only if something actually changed 2025-04-20 21:38:15 +02:00
Sokomine
e00be2fc43 added effect to play a sound 2025-04-05 20:42:13 +02:00
Sokomine
710c8e1928 fixed bug with empty list in ipairs issue #8378 2025-04-05 17:48:49 +02:00
Sokomine
1b9df50376 fixed bug if no textures are defined 2025-03-22 23:25:17 +01:00
Sokomine
52e04dff2f added ink language support docu 2025-03-22 18:36:21 +01:00
a1a5d5c243 Merge pull request '#25 / your-land/bugtracker#6618 Fixes crash when using protection rune on NPC' (#26) from AliasAlreadyTaken/yl_speak_up:master into master
Reviewed-on: Sokomine/yl_speak_up#26
2025-03-21 18:52:27 +01:00
Sokomine
67fe90c984 variables can now have a default value other than nil 2025-02-09 00:26:24 +01:00
Sokomine
9077feb1a9 addons: added effect send_coordinates (useful in combination with a waypoint_compass but can be used manually as well) 2025-01-16 21:21:32 +01:00
Sokomine
2e1732644f added addons effect send_mail 2025-01-16 03:02:55 +01:00
Sokomine
9af492c07a added addons folder; added addon action send_mail 2025-01-16 00:52:19 +01:00
Sokomine
a85f0629c5 ink import: sort dialogs according to order given in ink; sort existing but not imported dialogs in after that 2025-01-12 22:28:19 +01:00
Sokomine
9b2aae7fe1 disable options that are not included in the current import by adding a 'false' precondition 2025-01-12 20:40:46 +01:00
Sokomine
799233c8a2 partial ink import implemented 2025-01-09 23:20:49 +01:00
Sokomine
7e5fc745c0 fixed bug in ink import due to a wrong reimplementation of split command in test environment 2025-01-09 23:19:09 +01:00
Sokomine
40e7fc2087 do not change d_sort of other dialogs when changing the start dialog 2025-01-09 18:01:28 +01:00
Sokomine
8e0f53fdb5 added import_from_ink.lua - but no input for that yet 2025-01-07 21:04:37 +01:00
Sokomine
d5b5832b02 fixed bug in update_dialog_option regarding automaticly selected and random options 2025-01-07 20:02:23 +01:00
Sokomine
7c801291f9 update_dialog_option now can update d_trade and d_got_item dialogs (to a degree) 2025-01-05 20:29:47 +01:00
Sokomine
133a8ccf8d allow to export to ink without _ prefix 2025-01-05 11:46:59 +01:00
Sokomine
543c767dd5 added update_start_dialog for ink import 2025-01-05 11:31:30 +01:00
Sokomine
7ce56ca733 export to ink: add effect list to the action knot success option where it belongs 2025-01-04 19:50:18 +01:00
Sokomine
0cd28dcb54 better loggin for update_dialog_option 2025-01-04 19:21:10 +01:00
Sokomine
d2fdb6da61 the dialog is randomly selected; not the option (typo) 2025-01-04 18:26:23 +01:00
Sokomine
51ab9e1ecb update_dialog now properly ignores special dialogs 2025-01-04 18:25:05 +01:00
Sokomine
000978ef50 try to perserve d_<nr> dialog names in update_dialog 2025-01-04 18:14:48 +01:00
Sokomine
0d76f7492a implemented update_dialog_options_completed for options that were not updated 2025-01-04 18:06:58 +01:00
Sokomine
6370396fea o_random is part of a dialog - not of an option 2025-01-03 22:25:53 +01:00
Sokomine
16df2f9155 fixed bug in get_dialog_list_for_export 2025-01-03 22:03:14 +01:00
Sokomine
31434e69ce less spammy logs for update_dialog* 2025-01-03 21:51:36 +01:00
Sokomine
829683f750 pass o_text_when_prerequisites_not_met on to update_dialog_option 2025-01-03 21:03:55 +01:00
Sokomine
76f7e37566 sort dialogs in export according to d_sort instead of d_<nr> alphabethicly 2025-01-02 20:16:40 +01:00
Sokomine
d9d10112cf allow to add sort_oder to update_dialog_option 2025-01-02 18:14:22 +01:00
Sokomine
dc223d99f5 fixed logging of option changes in update option 2025-01-02 17:46:18 +01:00
Sokomine
c0ed2cc148 moved function get_dialog_list_for_export 2025-01-02 17:30:00 +01:00
Sokomine
06507880b6 properly split option names for ink import 2025-01-02 00:43:16 +01:00
Sokomine
8570fea8ba corrected obvious errors in a new function 2025-01-02 00:12:49 +01:00
Sokomine
40314125a8 moved yl_speak_up.get_start_dialog_id into functions_dialog 2025-01-01 23:00:38 +01:00
Sokomine
e5183813d7 moved a function from _dialogs to _talk 2025-01-01 22:41:09 +01:00
Sokomine
c0fcbecd63 added functions for updating and importing dialogs and options for future ink import 2025-01-01 22:37:59 +01:00
Sokomine
10e90e412a prefix options in ink export with automaticly_ or randomly_ if necessary 2025-01-01 18:53:56 +01:00
Sokomine
4cf06da1e4 made functions in functions_dialog.lua only optionally dependant on pname 2025-01-01 18:24:38 +01:00
Sokomine
c4ebef21f0 split up functions.lua into diffrent files 2025-01-01 18:00:13 +01:00
Sokomine
7e3ea18653 export to ink: support autoanswer, random and grey out answer 2024-12-27 17:40:51 +01:00
Sokomine
e1f13f7bff ink export: use d.d_name for actions and effects as well 2024-12-26 20:02:30 +01:00
Sokomine
293df54dac use specified prefix for all knots in ink export 2024-12-26 18:15:23 +01:00
Sokomine
a7f5f3c8b0 export d_got_item and d_trade to ink language as well 2024-12-26 16:27:14 +01:00
Sokomine
8456a10107 renamed main ink loop to postfix _d_end instead of main to make its point more obvious 2024-12-26 16:11:13 +01:00
Sokomine
28c3e7eed9 removed surplus line in export to ink before farewell message 2024-12-26 16:07:41 +01:00
Sokomine
5cd9a9aab6 fixed crash in quest_api when saving 2024-12-26 16:01:39 +01:00
Sokomine
e3094962c3 search texts for variable (display) usage when storing npc data 2024-12-25 22:50:33 +01:00
Sokomine
68e37e24b8 allow to show value of variables and properties in dialog text and option text 2024-12-21 22:53:09 +01:00
Sokomine
1b45fc7792 #5501 allow to configure setting of npc_privs 2024-12-19 22:12:08 +01:00
Sokomine
95e721b8f9 #5809 NPC show name, descr, owner, last pos when picked up 2024-12-15 21:04:10 +01:00
Sokomine
349d140d09 2076 mobs_redo mobs can be picked up with lasso by those who can edit them and not just their owner 2024-12-15 19:58:03 +01:00
Sokomine
8af9a995a0 #5268 mention missing npc privs in debug mode 2024-12-15 17:09:01 +01:00
Sokomine
9797ada402 #5266 check if npc for npc_talk_privs exists 2024-12-15 16:57:27 +01:00
Sokomine
0316afdfa9 take items back into npc inventory when npc_gives failed instead of forgetting them 2024-12-15 14:36:19 +01:00
Sokomine
bfeed37767 allow d_got_item and d_trade to be used as target dialogs and as target for failed actions 2024-12-15 13:50:58 +01:00
Sokomine
a54c395e91 show error message instead of crash if no formspec text supplied 2024-12-15 12:47:45 +01:00
Sokomine
6e604b05d0 fixed crash in simple dialogs export 2024-12-13 17:27:29 +01:00
Sokomine
5f6f488788 typo 2024-12-05 23:11:31 +01:00
Sokomine
769b4046b4 ask for initial_config at first talk 2024-12-05 19:59:19 +01:00
Sokomine
3cf9da85ca fixed #7788 (hp_max in update_tag has no value) 2024-12-02 23:42:29 +01:00
Sokomine
b9bebe82bd allow NPC to use tools when punching and right-clicking blocks 2024-07-20 01:20:08 +02:00
AliasAlreadyTaken
3d21489d49 #25 / your-land/bugtracker#6618 Fixes crash when using protection rune on NPC 2024-04-30 19:55:58 +02:00
Sokomine
1323f3d8fc removed GPLv3, added MIT license 2024-04-02 22:04:35 +02:00
Sokomine
7b8e98534f removed release entry from mod.conf for contentdb 2024-03-26 20:46:00 +01:00
Sokomine
f029f5d21b Merge branch 'master' of https://gitea.your-land.de/Sokomine/yl_speak_up 2024-03-26 19:53:17 +01:00
Sokomine
23762a141a mod (minus editor part) now also available under MIT license 2024-03-26 19:51:32 +01:00
Sokomine
5f2917df01 split mod into core (this mod) and editor (npc_talk_edit mod) 2024-03-22 22:33:27 +01:00
Sokomine
e262b53a04 fixed initial config 2024-03-22 22:17:31 +01:00
Sokomine
a9d5adb3a9 corrected wrong bracket in count_dialogs 2024-03-22 21:42:08 +01:00
Sokomine
fab5316837 fixed error message when no dialog is found 2024-03-22 19:47:27 +01:00
Sokomine
fe8755a260 added yl_speak_up.count_dialogs function 2024-03-22 19:26:23 +01:00
Sokomine
ebfc068694 export_to_simple_dialogs: use d_name where possible 2024-03-22 17:26:28 +01:00
Sokomine
30dfa9c378 added yl_speak_up.d_id_to_d_name 2024-03-22 17:26:05 +01:00
Sokomine
507123660f turned into function: yl_speak_up.export_to_simple_dialogs_language( 2024-03-22 17:20:02 +01:00
Sokomine
cfb673993f added yl_speak_up.get_dialog_list_for_export 2024-03-22 17:15:13 +01:00
Sokomine
7343366d2f export to simple_dialogs: don't export special or generic dialogs 2024-03-22 16:23:50 +01:00
Sokomine
8236c9826e now really mostly fixed links in readme.md 2024-03-16 17:05:14 +01:00
Sokomine
bbfbb20896 fixed links in readme.md 2024-03-16 16:51:44 +01:00
Sokomine
96806d3c66 try to fix links in readme.md 2024-03-16 16:46:54 +01:00
Sokomine
c26068fd48 fixed typo and bug #6478 2024-03-14 17:17:06 +01:00
Sokomine
443d6568b1 support for d_name in export to ink 2024-03-10 20:54:05 +01:00
Sokomine
86e4d9a3fa d_name supported for on_failure effects 2024-03-10 18:34:28 +01:00
Sokomine
7471691d55 properly formspec_escape player-provided list entries 2024-03-10 17:10:14 +01:00
Sokomine
1e2981fc38 support for d_name in edit_actions 2024-03-10 17:01:45 +01:00
Sokomine
5f7f769d87 added yl_speak_up.get_sorted_dialog_name_list 2024-03-10 17:00:34 +01:00
Sokomine
e306fe3466 d_name support for edit_options dialog 2024-03-10 16:10:46 +01:00
Sokomine
70e1d4144c added more checks for dialog names 2024-03-09 19:32:29 +01:00
Sokomine
7905d9937f allow to name and rename dialogs 2024-03-09 19:25:51 +01:00
Sokomine
f5ecb06ce6 export to ink: support for some limited effects (setting variables to values) 2024-03-03 02:18:32 +01:00
Sokomine
13de73ecde export to ink: support for variables (to a degree) and precondtions (to a degree) 2024-03-02 19:54:21 +01:00
Sokomine
1c89bd7dcf export_to_ink: added labels for options 2024-03-02 15:48:13 +01:00
Sokomine
9781ea9b43 support o_visit_only_once (non-sticky choices) in export_to_ink 2024-03-02 15:40:18 +01:00
Sokomine
f503d436dc added visit counters to readme.md and added custom preconditions for reading them out 2024-02-29 19:20:21 +01:00
Sokomine
a8b92a754f options can now be marked as Done when having been visited once 2024-02-29 16:10:58 +01:00
Sokomine
189165dd22 count visits to options 2024-02-29 12:40:59 +01:00
Sokomine
6381ea59b9 count visits to dialogs 2024-02-29 12:18:23 +01:00
Sokomine
c2107d0bf3 added d_dynamic (dynamic dialogs) to readme.md 2024-02-29 11:46:33 +01:00
Sokomine
b076e6a2f5 export_to_ink: works apart from preconditions and variables 2024-02-29 11:43:39 +01:00
Sokomine
2d677127dd export to ink: added alternate_text for dialogs and actions 2024-02-28 18:46:43 +01:00
Sokomine
24d3a05ce0 improved export to ink: diverts 2024-02-28 16:43:15 +01:00
Sokomine
5c7ffe3739 added limited export to ink language 2024-02-23 03:23:46 +01:00
Sokomine
3eab65d2ee added entity_type precondition to readme.md 2024-02-17 19:52:39 +01:00
Sokomine
2329724ef2 do not count d_dynamic when counting amount of dialogs for initial setup or dialog deletion 2024-02-17 04:13:09 +01:00
Sokomine
20735916c1 store cape and shield texture without the mask (apply that in on_activate) 2024-02-16 17:00:49 +01:00
Sokomine
48243f2ddc added topics to dynamic dialogs as demonstration; also added common replacements like $ as demo 2024-02-16 16:36:56 +01:00
Sokomine
db2abf76fe typo 2024-02-15 22:29:20 +01:00
Sokomine
d58789e703 only load detached npc inventories when they are really needed - either by the npc dialog or in edit_mode 2024-02-15 22:19:08 +01:00
Sokomine
6f51ddd10d automaticly remove unused detached npc inventories that havn't been changed or loaded for more than 600 seconds 2024-02-15 21:18:10 +01:00
Sokomine
cc15431cb2 added yl_speak_up.npc_is_in_conversation / npc_is_in_conversation_with 2024-02-15 21:16:22 +01:00
Sokomine
d588e38308 make detached trade_inv beeing sent only to the player it belongs to and delete it when the player leaves 2024-02-15 00:48:44 +01:00
Sokomine
7b14fb6668 let cape and shield texture deal with empty textures 2024-02-15 00:10:59 +01:00
Sokomine
691301019b created yl_speak_up.generate_next_dynamic_dialog_simple 2024-02-14 05:26:18 +01:00
Sokomine
774b076f3b first implementation of actual d_dynamic execution 2024-02-14 04:10:08 +01:00
Sokomine
e3e03ee990 preparations for d_dynamic dialog 2024-02-14 01:58:18 +01:00
Sokomine
eaf84cfd31 handle simple_dialogs export when no dialogs are available 2024-02-13 05:41:01 +01:00
Sokomine
55b180544f allow d_trade as dialog target 2024-02-13 04:01:28 +01:00
Sokomine
d52aa2e5cb allowed export/import without editor/ 2024-02-13 03:04:57 +01:00
Sokomine
71e08eac08 broke long formspec strings into table.concat lists (editor/fs/fs_edit_options_dialog.lua) 2024-02-13 02:24:43 +01:00
Sokomine
94a1d1092a broke long formspec strings into table.concat lists (editor/fs) 2024-02-12 13:05:36 +01:00
Sokomine
6de16720e0 added missing _wrapper postfix in fs_trade_limit 2024-02-12 12:35:21 +01:00
Sokomine
35fa4e9774 broke long formspec strings into table.concat lists 2024-02-12 12:33:58 +01:00
Sokomine
42c3751966 cleaned up editor/edit_mode.lua by removing unused files 2024-02-12 12:06:38 +01:00
Sokomine
0b7078e3e9 cleaned up init.lua by removing editor/ files 2024-02-12 12:03:21 +01:00
Sokomine
c7fbdb84e4 removed old_staff_tool_compatibility.lua 2024-02-12 02:42:57 +01:00
Sokomine
b93186d3c2 allow to edit name, description and owner even without editor installed 2024-02-12 02:10:46 +01:00
Sokomine
c1deea695c allowed reloading when modname is not present 2024-02-12 00:29:06 +01:00
Sokomine
b04d37c963 made initial_config work better in edit_mode 2024-02-11 13:26:44 +01:00
Sokomine
e2722d7c60 created initial_config.lua so that overriding start dialogs becomes easier 2024-02-10 22:33:00 +01:00
Sokomine
f70bc72f85 prepared initial_config fs for working without api/formspec_helpers.lua 2024-02-10 22:03:52 +01:00
Sokomine
0a3764fe84 adjusted the move of api_quest_steps.lua in init.lua and editor/ 2024-02-10 19:52:18 +01:00
Sokomine
6450417637 moved api_quest_steps.lua to editor/api/ as it is only needed in edit_mode 2024-02-10 19:38:05 +01:00
Sokomine
9968adca25 removed obsolete files 2024-02-10 19:15:26 +01:00
Sokomine
10e8cef102 provide basic show_precondition/action/effect for debug mode when editor/ is not available 2024-02-10 19:11:26 +01:00
Sokomine
1748a3308b fixed typo in fs_trade_via_buy_button 2024-02-10 18:55:11 +01:00
Sokomine
7c72e4215b split show_fs.lua into editor/ and normal function 2024-02-10 18:51:36 +01:00
Sokomine
dfc88b4895 fixed some typos in function names 2024-02-10 00:53:25 +01:00
Sokomine
b1f5a3ee9e adding missing postfix for _wrapper in show_log 2024-02-10 00:33:48 +01:00
Sokomine
61d2fc1679 actually added the new files to init.lua 2024-02-10 00:28:42 +01:00
Sokomine
cb0869f53c moved formspecs out of exec_actions.lua and into fs/fs_action_*.lua 2024-02-10 00:23:35 +01:00
Sokomine
a7d6d07785 show_fs: talk in edit mode 2024-02-10 00:11:20 +01:00
Sokomine
7482061868 moved add_trade_simple in editor/ into fs/ subfolder 2024-02-10 00:00:33 +01:00
Sokomine
c0c3cae460 fixed some input_fs_ names in editor/ 2024-02-09 23:58:46 +01:00
Sokomine
4e5e3d9093 show_fs: initial_config 2024-02-09 23:35:23 +01:00
Sokomine
2dae28a655 show_fs: optiondialog/setdialog (only in yl_npc now) 2024-02-09 22:15:34 +01:00
Sokomine
24022c98b6 moved fs_show_log because show_fs needs to be loaded first 2024-02-09 22:14:15 +01:00
Sokomine
db2f401322 show_fs: talk 2024-02-09 21:59:41 +01:00
Sokomine
7ddf97d49d show_fs: save_or_discard_changes 2024-02-09 21:52:11 +01:00
Sokomine
d7440369b5 show_fs: player_offers_item 2024-02-09 21:47:11 +01:00
Sokomine
77e6906f0e show_fs: trade_via_buy_button 2024-02-09 21:45:17 +01:00
Sokomine
9f44bb1961 show_fs: add/do_trade_simple 2024-02-09 21:43:48 +01:00
Sokomine
e27809d4d7 show_fs: npc_list 2024-02-09 21:31:30 +01:00
Sokomine
b2fb83d046 show_fs: trade_list 2024-02-09 21:29:24 +01:00
Sokomine
0490f8183c fixed get_fs_show_log wrapper parameter 2024-02-09 21:10:24 +01:00
Sokomine
d6338abeca show_fs: edit_trade_limit 2024-02-09 21:03:22 +01:00
Sokomine
82616c85b1 show_fs: trade_limit 2024-02-09 21:01:00 +01:00
Sokomine
2a8b621606 show_fs: show_log 2024-02-09 20:56:26 +01:00
Sokomine
7cbc610866 show_fs: inventory 2024-02-09 20:53:33 +01:00
Sokomine
21f77baae1 show_fs: edit_options_dialog 2024-02-09 20:50:38 +01:00
Sokomine
61b5799247 show_fs: fashion/fashion_extended 2024-02-09 20:43:27 +01:00
Sokomine
063aa7e297 show_fs: assign_quest_step 2024-02-09 20:38:33 +01:00
Sokomine
92d3622464 show_fs: manage_(quests/variables) 2024-02-09 20:36:48 +01:00
Sokomine
f6bcb2e6c6 show_fs: show_what_points_to_this_dialog 2024-02-09 20:33:07 +01:00
Sokomine
928675a3c1 show_fs: quest_gui 2024-02-09 20:31:35 +01:00
Sokomine
8838307d16 show_fs: export 2024-02-09 20:30:09 +01:00
Sokomine
7408d8d286 show_fs: action_(npc_gives/npc_wants/text_input/evaluate) 2024-02-09 20:02:22 +01:00
Sokomine
20056cb1e9 show_fs: edit_(preconditions/actions/effects) 2024-02-09 19:55:21 +01:00
Sokomine
9fe6713bd9 show_fs: notes 2024-02-09 19:48:51 +01:00
Sokomine
711febcb4c show_fs: properties 2024-02-09 19:46:37 +01:00
Sokomine
9d26267e67 added force_fs_ver to register_fs 2024-02-09 19:45:34 +01:00
Sokomine
8c27f02c39 show_fs: add_quest_steps 2024-02-09 19:27:50 +01:00
Sokomine
12fc39b3d0 prepared show_fs.lua for beeing more universal by adding register_fs function 2024-02-09 19:25:59 +01:00
Sokomine
03da9151c4 catch missing yl_speak_up.edit_mode in show_fs (which is not yet split up properly) 2024-02-09 06:28:36 +01:00
Sokomine
04b67da670 added exception for npc_was_changed in functions.lua 2024-02-09 06:22:53 +01:00
Sokomine
1cc4f2e742 log add_trade_simple in npc_was_changed as an exception without having to resort to edit_mode directly 2024-02-09 06:20:18 +01:00
Sokomine
0f6962e0a8 split fs_add_trade_simple into editor/ and rest 2024-02-09 06:09:40 +01:00
Sokomine
1659aed8ab took care of delete_trade_simple regarding edit_mode 2024-02-09 05:44:30 +01:00
Sokomine
e7369f3075 split api_trade_inv.lua into edit_mode and normal mode 2024-02-09 05:35:34 +01:00
Sokomine
d10b394204 adjusted fs_do_trade_simple so that it works with edit_mode beeing only in editor/ 2024-02-07 23:36:12 +01:00
Sokomine
567decb7bd stop_talking now uses reset_vars_for_player instead of doing that manually 2024-02-07 22:55:15 +01:00
Sokomine
763a7e27c5 moved edit_mode out of functions.lua 2024-02-07 22:53:11 +01:00
Sokomine
21005455ab added some missing local variables in fs/fs_talkdialog.lua 2024-02-07 22:18:15 +01:00
Sokomine
21c8f1149d added dry_run_no_exec parameter to execute_all_relevant_effects so that the function can work properly in edit_mode 2024-02-07 22:12:18 +01:00
Sokomine
c2728f853b split action_inv_changed function into editor/ and interpreter functionality 2024-02-07 21:44:39 +01:00
Sokomine
e33e1326fd moved edit_mode comment to editor/ 2024-02-07 20:30:50 +01:00
Sokomine
596d521345 moved '/npc_talk force_edit' into editor/ part 2024-02-07 20:28:25 +01:00
Sokomine
8dbaaf21d5 moved etitor parts into edtior/ folder 2024-02-07 18:34:33 +01:00
Sokomine
aa707c40ac moved edit_mode.lua to editor/fs/* and added new edit_mode.lua as future init.lua file for editor mod 2024-02-07 06:19:20 +01:00
Sokomine
b6f5cd9029 show 'I want to give you something' option only when it fits 2024-02-05 23:35:24 +01:00
Sokomine
abf063975e fixed exit button not working in initial config 2024-02-05 23:28:02 +01:00
Sokomine
630287d99a split up input function of fs/fs_talkdialog.lua 2024-02-05 06:37:00 +01:00
Sokomine
682cc3bccd finished get_fs_talkdialog split into normal and edit mode 2024-02-04 01:38:44 +01:00
Sokomine
89bd9ea546 renamed add_buttons function to get_fs_talkdialog_add_edit_and_command_buttons and placed commands like walk/follow/stand/inventory there 2024-02-02 21:19:43 +01:00
Sokomine
8b3fb10faf moved yl_speak_up.get_fs_talkdialog_add_edit_buttons to edit_mode.lua 2024-02-02 20:57:23 +01:00
Sokomine
4d2da67f9d turned yl_speak_up.get_fs_talkdialog_add_player_offers_item into extra function 2024-02-02 19:44:51 +01:00
Sokomine
3dab37dae7 moved yl_speak_up.get_fs_talkdialog_line_in_edit_mode to edit_mode.lua and shortened to get_fs_talkdialog_line 2024-02-02 19:27:16 +01:00
Sokomine
24a6edcd53 moved get_fs_talkdialog_main_text_in_edit_mode to edit_mode.lua and renamed to get_fs_talkdialog_main_text 2024-02-02 19:11:13 +01:00
Sokomine
4cfab4c1d8 created yl_speak_up.apply_autoanswer_and_random_and_d_got_item and adjusted yl_speak_up.calculate_displayable_options; created edit_mode.lua for overrides 2024-02-02 18:58:03 +01:00
Sokomine
79aae18038 added back item to error message when npc refuses an item 2024-02-02 18:25:29 +01:00
Sokomine
c7f9fc81c3 passing on newname parameter to mobs.mob_class.update_tag 2024-01-30 23:43:28 +01:00
Sokomine
99e2b4c2ec removed obsolete depends.txt 2024-01-27 04:35:50 +01:00
Sokomine
bfbc7c238b updated mod.conf 2024-01-27 04:33:16 +01:00
Sokomine
478849812d license of textures added 2024-01-25 18:17:15 +01:00
Sokomine
aee256938b moved cape texture to yl_npc 2024-01-25 18:03:22 +01:00
Sokomine
7fc2d89974 removed surplus textures 2024-01-25 17:56:38 +01:00
Sokomine
753d3f3eec removed obsolete _mask_ textures and test textures 2024-01-24 22:02:03 +01:00
Sokomine
936cb08e71 added missing fs_inventory.lua file 2024-01-20 22:01:21 +01:00
Sokomine
34e639f68f typos in function names corrected 2024-01-17 04:15:24 +01:00
Sokomine
c8d5293df0 prevent NPC from mobs_redo to take environmental dammage 2024-01-17 03:59:03 +01:00
Sokomine
920fbcf062 adjusted to initial_properties in minetest.registered_entities 2023-12-19 03:23:18 +01:00
Sokomine
964fce04e3 fixed bug in local function 2023-12-19 03:21:06 +01:00
Sokomine
49b370b7bb explained meaning of api/ folder 2023-12-18 06:16:10 +01:00
Sokomine
44e8683e61 renamed function input_fs_manage_general to handle_* 2023-12-18 06:13:27 +01:00
Sokomine
8110442b2a renamed input_routing_show_a_quest_step function to handle_input_routing_show_a_quest_step 2023-12-18 06:04:30 +01:00
Sokomine
f224222d7c moved formspec_helpers.lua into api folder 2023-12-18 05:58:39 +01:00
Sokomine
eb6cf0f031 renamed get_fs_quest_edit_error function to build_fs_quest_edit_error as that is more appropriate 2023-12-18 05:52:11 +01:00
Sokomine
067744981b renamed function input_fs_manage_general to more appropriate handle_input_fs_manage_general 2023-12-18 05:41:41 +01:00
Sokomine
922c2af88f renamed get_fs_manage_general function to build_fs_manage_general as that describes it better 2023-12-18 05:35:19 +01:00
Sokomine
3847dda6ee renamed some get_fs_ functions to better fitting get_sub_fs_ functions 2023-12-18 05:31:48 +01:00
Sokomine
8396464fcf completed last commit 2023-12-18 05:25:59 +01:00
Sokomine
fe1910c88f split inventory.lua into api/ and fs/ 2023-12-18 05:25:26 +01:00
Sokomine
3ae305dcc9 moved fs_edit_general.lua to api/; renamed most get_fs_ functions there to get_sub_fs_ and main function to build_fs_ for more clarity 2023-12-18 05:15:04 +01:00
Sokomine
536ad045f6 renamed get_fs_ function in alternate_text because it does something diffrent from other get_fs_ functions 2023-12-18 04:47:53 +01:00
Sokomine
2cbac5ca7e composing string via concate now 2023-12-18 04:46:01 +01:00
Sokomine
0f0593100e used table.concat to insert yl_speak_up.show_fs_simple_deco instead of just string concatination 2023-12-18 03:02:56 +01:00
Sokomine
afe47c3908 moved fs_decorated to api/api_decorated because it does not show its own formspec 2023-12-18 02:53:03 +01:00
Sokomine
e9ee295db5 split up fs_talkdialog.lua into fs/ and api/ 2023-12-18 02:44:43 +01:00
Sokomine
4f15503dfe split fs_fashion.lua up into api/ and fs/ (two files) 2023-12-14 04:35:37 +01:00
Sokomine
e8082faf75 added forgotten file 2023-12-14 04:30:20 +01:00
Sokomine
fad536a0a4 split fs_npc_list.lua into api/ and fs/ 2023-12-12 03:01:58 +01:00
Sokomine
d2e5362cf9 split quest related fs_*.lua into fs/ and api/ 2023-12-11 21:48:31 +01:00
Sokomine
361bcd9dd1 moved some more fs_*.lua to fs/ 2023-12-11 19:41:33 +01:00
Sokomine
c368538c28 moved fs_edit_*.lua to fs/ 2023-12-11 19:24:19 +01:00
Sokomine
d57ac190eb split trade_simple.lua and other trading into fs/ and api/ part 2023-12-11 19:05:33 +01:00
Sokomine
031abad71b split fs_trade_via_buy_button.lua in fs/ and api/ 2023-12-10 05:13:11 +01:00
Sokomine
259a79e359 split fs_trade_limit.lua into fs/ and api/ part 2023-12-10 04:56:51 +01:00
Sokomine
eb6d75c8fc explained what files in fs/ folder are for 2023-12-08 22:43:39 +01:00
Sokomine
abbb8cf123 manage variables/quests: made local functions with fun_ prefix distinct from the input_ function 2023-12-08 22:43:06 +01:00
Sokomine
f37f07c722 moved some files into fs/ and api/ folders to clean up 2023-12-08 22:41:27 +01:00
Sokomine
2c2b3d173d extracted fs_show_what_points_to_this_dialog.lua from fs_alternate_text.lua 2023-12-08 20:39:28 +01:00
Sokomine
e73a0cf35c split fs_properties.lua into fs and api part 2023-12-08 19:32:59 +01:00
Sokomine
2e3bc1ddea removed api_logging.lua functions from fs_show_log.lua 2023-12-08 19:31:22 +01:00
Sokomine
9f7349146d split fs_show_log.lua into api and fs 2023-12-08 19:27:17 +01:00
Sokomine
a1963d6b52 import and export textures, infotext, animation and properties 2023-11-26 00:15:57 +01:00
Sokomine
f3b0f10603 improved readme.md: typo, link to bugtracker, mentionned markup language for the text the npc says 2023-11-25 22:56:56 +01:00
Sokomine
e3762b9c4c do not crash when action leads to d_end in case of success 2023-11-25 20:59:16 +01:00
Sokomine
40a72949eb fixed table of contents in readme.md 2023-11-16 05:39:02 +01:00
Sokomine
992f653534 fixed readme.md 2023-11-16 05:37:26 +01:00
077b7e2425 revert 30d605f8f0
revert readme experiment 2
2023-11-16 04:35:26 +00:00
30d605f8f0 readme experiment 2 2023-11-16 04:33:43 +00:00
81fb3dd999 readme experiment 2023-11-16 04:32:21 +00:00
6425b580dd experiment with link in readme 2023-11-16 04:31:00 +00:00
56e2863b76 updated link in readme.md 2023-11-16 04:07:05 +00:00
Sokomine
556c24f782 made scrollbar for options easier to operate 2023-11-15 22:58:32 +01:00
Sokomine
f1668176ad #16 Show option text even when automatic or random selection is active 2023-11-15 22:31:47 +01:00
Sokomine
9b7e08f2b0 #3 made mute message less misleading 2023-11-14 02:29:43 +01:00
Sokomine
f60aaa8bd0 #5383 mobs_redo now uses get_properties() for hp_max; adjusted update_tag 2023-11-08 22:45:43 +01:00
Sokomine
1416ab39d3 added loging of capturing mobs (at least those using the mobs_redo interface) 2023-11-08 21:46:47 +01:00
Sokomine
dc278123d6 added /npc_talk version command 2023-11-05 18:10:49 +01:00
Sokomine
c513e0b8e4 allow to unset cape 2023-11-05 13:05:42 +01:00
Sokomine
62d267cfe7 show only npc in npc list - not blocks 2023-11-05 12:24:20 +01:00
Sokomine
aa3fd4580c fixed bug 2023-11-03 23:12:12 +01:00
Sokomine
8888373210 make back button show edit_options_dialog in manage_quest_steps 2023-11-03 22:00:26 +01:00
Sokomine
a66340ef8a fixed bug in assigning new quest step after deleting old 2023-11-03 19:44:15 +01:00
Sokomine
58976ec35b adding and deleting quest step assignments to poptions is now logged 2023-11-03 15:33:01 +01:00
Sokomine
696a36a145 typo 2023-11-01 21:41:21 +01:00
Sokomine
d57afa434c added export and notes to readme.md 2023-11-01 21:40:31 +01:00
Sokomine
a33b52f0d8 #5295 allow to write internal notes 2023-11-01 21:34:07 +01:00
Sokomine
a735a9a86f allow to import dialogs (without checking) with privs priv 2023-10-31 22:05:15 +01:00
Sokomine
cf868e0761 added export (show .json file structure) 2023-10-29 15:47:56 +01:00
Sokomine
6346a747e0 improved fs_assign_quest_step; deleting is possible 2023-10-28 17:34:43 +02:00
Sokomine
1dd6c587c7 moved adding npc/locations to quest steps into quest_api 2023-10-28 17:32:07 +02:00
Sokomine
6f8dde7e61 #17 made buttons for owner colored diffrently 2023-10-07 13:50:43 +02:00
Sokomine
3c4f5bd3ea scrollbar finetuning 2023-10-02 04:04:47 +02:00
Sokomine
53c9318cd4 better visualization in quest step view over where quest steps can be set 2023-10-02 03:48:48 +02:00
Sokomine
7e4bd07109 added yl_speak_up.quest_step_show_where_set 2023-10-01 21:42:17 +02:00
Sokomine
2e40e236af used check_if_dialog_has_option 2023-10-01 20:22:08 +02:00
Sokomine
c4e5c65773 make sure npc/location is part of the lists 2023-10-01 20:19:46 +02:00
Sokomine
2d2afdfb26 added yl_speak_up.check_if_dialog_has_option for easier check if option is available 2023-10-01 20:18:51 +02:00
Sokomine
4993656940 actually store assigned quest steps in npc savefile 2023-10-01 14:22:06 +02:00
Sokomine
861c44024a further prepared connection of npc and quest steps 2023-10-01 12:21:31 +02:00
Sokomine
b7423b0d81 prepared better assignment of quest steps to npc 2023-10-01 00:59:40 +02:00
Sokomine
ce067bf18e store quest.var_name as quest id in npc 2023-09-30 21:43:41 +02:00
Sokomine
78f26d42a1 add npcs and locations via add field to lists 2023-09-30 21:03:44 +02:00
Sokomine
13abe1687a make sure quest.npcs and quest.locations exist 2023-09-30 19:38:19 +02:00
Sokomine
94df301302 check if npcs and locations can be removed from quests 2023-09-30 19:14:49 +02:00
Sokomine
f12f778b06 allow to add or delete locations for quests 2023-09-30 13:21:13 +02:00
Sokomine
36ed71ae56 display adding quest locations 2023-09-30 13:16:30 +02:00
Sokomine
2183c1af32 added helper function yl_speak_up.count_used_in_quest_steps 2023-09-30 12:57:49 +02:00
Sokomine
2c28a0fc26 local function grey_if_zero 2023-09-30 12:51:18 +02:00
Sokomine
feae914e85 preparations for adding locations to quests 2023-09-30 12:48:53 +02:00
Sokomine
a3c1c5ccfd added way to add npcs as potential contributors to quests 2023-09-30 11:34:30 +02:00
Sokomine
b82a985ca1 link edit npc contributors in quest overview 2023-09-30 11:23:14 +02:00
Sokomine
7a9ab3867d show names of npc in quest overview 2023-09-30 10:51:11 +02:00
Sokomine
9e3b98d09e got rid of x_add in add quest step 2023-09-29 16:21:30 +02:00
Sokomine
ba81ac2f63 use container for fs_add_quest_steps 2023-09-29 16:08:20 +02:00
Sokomine
3f9da138e0 quest steps are clickable from quest overview page 2023-09-25 05:22:38 +02:00
Sokomine
b621fe7b12 show npcs, locations and items in manage_quests 2023-09-25 01:37:16 +02:00
Sokomine
234a18efff show some quest steps in quest overview 2023-09-25 00:19:47 +02:00
Sokomine
314c637aae added show all quest steps button 2023-09-24 23:28:34 +02:00
Sokomine
c3d97233dd allow to click on start/end/unconnected steps in list 2023-09-24 23:05:38 +02:00
Sokomine
d267560d2a fixed bug in fashion if mesh not defined 2023-09-24 00:24:14 +02:00
Sokomine
32510ad974 yl_speak_up.quest_step_show_table_decorated renamed to yl_speak_up.get_fs_show_list_in_box and moved to formspec_helpers.lua 2023-09-23 23:45:31 +02:00
Sokomine
8d0377572d make color of table lines configurable 2023-09-23 23:36:32 +02:00
Sokomine
e2ed6263ac make more use of yl_speak_up.quest_step_show_table_decorated 2023-09-23 23:14:43 +02:00
Sokomine
5e8e31876c added yl_speak_up.quest_step_show_table_decorated 2023-09-23 22:28:33 +02:00
Sokomine
1347fb1bdb show start, end and unconnected quest steps 2023-09-23 21:28:46 +02:00
Sokomine
fd35e172d8 re-enabled quests (which are under development) 2023-09-23 20:03:57 +02:00
Sokomine
f201d0a244 temporally disabled unfinished quest system for yl 2023-09-22 07:25:15 +02:00
Sokomine
52b280035e fs improvements 2023-09-22 07:17:14 +02:00
Sokomine
8b8fbfa4aa improved back button 2023-09-22 07:00:18 +02:00
Sokomine
e8053e25e9 forward quest_step input for embedded_select mode 2023-09-22 06:56:32 +02:00
Sokomine
0ec51adf74 show step list when adding quest step 2023-09-22 06:17:30 +02:00
Sokomine
af43cc467c added get_location_id for quests 2023-09-22 02:10:28 +02:00
Sokomine
58fe04cd49 show in add_quest_steps how many locations *set* a quest step 2023-09-22 00:29:31 +02:00
Sokomine
10b3ac6aff removed unused texture 2023-09-22 00:07:05 +02:00
Sokomine
c744ce6aff put more into containers in fs_manage_quest_steps 2023-09-21 22:35:38 +02:00
Sokomine
7d602fccf2 make sure all relevant tables exist inside quest step data inside quest_api.lua 2023-09-21 22:05:51 +02:00
Sokomine
6a24488ac2 slightly less color in tooltips in fs_manage_quest_steps 2023-09-21 20:36:43 +02:00
Sokomine
b451432b84 make </> buttons work in manage quest steps 2023-09-21 20:20:54 +02:00
Sokomine
eabafafe17 improved tooltips in manage quest steps 2023-09-21 20:07:22 +02:00
Sokomine
9313ef3bef rearranged buttons in manage quest steps 2023-09-21 19:52:19 +02:00
Sokomine
4bcbf8ee0a improved quest step management 2023-09-20 22:43:06 +02:00
Sokomine
83f92f1a49 make quest step in quest step list clickable 2023-09-20 22:26:38 +02:00
Sokomine
fd525a950f do not offer the quest step itself as dependency 2023-09-20 04:45:10 +02:00
Sokomine
030f75be15 improved inserting quest steps before/after others 2023-09-20 00:33:46 +02:00
Sokomine
0204f38a46 allow to remove quest steps from the lists of requirements 2023-09-19 19:47:00 +02:00
Sokomine
d9208a1365 added first temporary storing of previous quest steps 2023-09-19 05:23:16 +02:00
Sokomine
35506731bd fixed bug in manage_quest_steps 2023-09-19 05:20:52 +02:00
Sokomine
6ecf800a60 prepared adding quest steps 2023-09-19 04:16:38 +02:00
Sokomine
decae6977e changed formspec size for add quest steps 2023-09-19 02:17:17 +02:00
Sokomine
1c72d714e7 removed surplus edit button 2023-09-19 00:35:28 +02:00
Sokomine
352a0322f4 passing mode of how/where to add new quest steps differntly now 2023-09-19 00:32:32 +02:00
Sokomine
5ba53ba637 added formspec for adding quest steps 2023-09-18 23:33:10 +02:00
Sokomine
1e4e3cbaef improved manage_quest_steps formspec 2023-09-18 21:51:03 +02:00
Sokomine
e879e1e67a manage only really existing quests; preparations for deleting quests 2023-09-16 05:38:33 +02:00
Sokomine
31f29beecd removed debug message 2023-09-15 00:52:51 +02:00
Sokomine
49c264193c removed overlapping of text in add quest steps formspec 2023-09-15 00:51:23 +02:00
Sokomine
4ce6101bca added first basic creation/delition of quest steps 2023-09-15 00:40:34 +02:00
Sokomine
263ec1ac86 at least show *some* data about a quest 2023-09-15 00:07:49 +02:00
Sokomine
f00fe9ea84 fixed selecting "Add ..:"-entry in formspec_helpers 2023-09-14 23:54:22 +02:00
Sokomine
1e7a4a4ad1 allow to show error messages in input_fs_manage_general 2023-09-14 22:24:11 +02:00
Sokomine
d2f92ced9a fixed bug editing quest values for players 2023-09-12 21:27:46 +02:00
Sokomine
698472c16f added hint that players with npc_talk_admin priv can set names of npc to those of players 2023-09-12 03:58:40 +02:00
Sokomine
e69e2f4c02 added yl_speak_up.add_on_rightclick_entry for picking up mobs 2023-09-10 22:05:04 +02:00
Sokomine
28a1fc8b9a entity.mesh is no loner needed (better mob mod compatibility) 2023-09-10 00:28:32 +02:00
Sokomine
8b5c45701a changed order of config options in config.lua to give it more structure 2023-09-09 03:57:16 +02:00
Sokomine
8db72aad9d moved mesh and skin definition to yl_npc/npc_talk 2023-09-09 02:55:43 +02:00
Sokomine
08bb8d3ea5 removed files that are yl-specific and belong to yl_npc or npc_talk and not here 2023-09-09 02:02:10 +02:00
Sokomine
1b492e50f8 minor changes in readme.md 2023-09-08 21:37:34 +02:00
Sokomine
17792a28d1 removed very old examples in dev/ folder 2023-09-08 21:36:00 +02:00
Sokomine
bf8627d48b added LICENSE and installation instructions 2023-09-04 01:07:29 +02:00
Sokomine
21480b3bdb only do after_activate for those mobs we're responsible for 2023-09-04 00:58:03 +02:00
Sokomine
04debcb096 restructured and improved readme.md 2023-09-03 01:25:23 +02:00
Sokomine
3bc3b70bc4 renamed README.md to readme.md 2023-09-02 04:12:04 +02:00
Sokomine
c6fef779ac further improved README.md 2023-09-02 04:11:28 +02:00
Sokomine
5b1035a19e bug in directory of README.md fixed 2023-09-02 03:31:58 +02:00
Sokomine
d8c6394dc0 removed formatting error in README.md 2023-09-02 03:09:11 +02:00
Sokomine
b2148d3e91 removed obsolete usage.md 2023-09-02 03:02:59 +02:00
5372cfdec1 further improvement of README.md 2023-09-02 00:55:58 +00:00
31d86bc058 reformatted and improved README.md 2023-09-02 00:23:51 +00:00
103 changed files with 9899 additions and 13731 deletions

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2024 Sokomine
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

586
README.md
View File

@ -1,586 +0,0 @@
# Let NPC talk in Minetest (like in RPGs/point&click) [yl_speak_up]
This mod allows to set RPG-like texts for NPC where the NPC "says" something
to the player and the player can reply with a selection of given replies -
which most of the time lead to the next dialog.
Original author: AliasAlreadyTaken/Bastrabun
Massive rewrite/extension: Sokomine
## Table of Content
1. [Tutorial](#tutorial)
2. [Chat Commands](#chat-commands)
3. [Terminology] (#terminology)
4. [Special dialogs] (#special-dialogs")
5. [How to configure NPC and add dialogs] (#how-to-configure)
6. [The privs] (#privs)
7. [Tools] (#tools)
8. [Mute] (#mute)
9. [Skin] (#skin)
10. [Simple variables] (#simple-variables)
11. [Trading (simple)] (#trading-simple)
12. [Logging] (#logging)
13. [Quest items] (#quest-items)
14. [Entering Passwords] (#quest-passwords)
15. [Custom Preconditions, Actions and Effects] (#precon_action_effect)
16. [Alternate Text] (#alternate_text)
17. [Autoselect/Autoanswer] (#autoselect_autoanswer)
18. [Random Dialogs] (#random_dialogs)
19. [Maximum recursion depth] (#max_recursion_depth)
20. [Giving things to the NPC] (#npc_wants)
21. [Configuration] (#configuration)
22. [Adding local overrides for your server] (#server_overrides)
23. [Data saved in modstorage] (#modstorage)
24. [Files generated in world folder] (#files_generated)
25. [Properties] (#properties)
26. [Generic behaviour] (#generic_behaviour)
27. [Integration into your own NPC/mob mods] (#integration)
### 1. Tutorial
<a name="tutorial"></a>
There is an NPC that explains a few things and adds as a tutuor.
His savefile is: `n_1.json`
Copy that file to the folder
<your wold folder>/yl_speak_up_dialogs/n_1.json
(replace the 1 with a higher number if you already have some NPC defined)
The first NPC in your world will become a tutor (after you spawned it
and set its name).
### 2. Chat commands
<a name="chat-commands"></a>
#### `/npc_talk style` Allows to select the formspec version:
* 1: Very rudamentary. Not recommended.
* 2: Shows additional buttons for up and down.
* 3: Default (recommended) version. Scroll via mouse wheel.
#### `/npc_talk list` Shows a list of all your NPC and where they are.
#### `/npc_talk debug` Allows to debug dialogs.
#### `/npc_talk force_edit` Toggles edit mode.
From now on (until you issue this command again), all NPC you
talk to will be in edit mode (provided you are allowed to
edit them). This is useful if something's wrong with your NPC
like i.e. you made it select a dialog automaticly and let
that dialog lead to d_end.
#### `/npc_talk privs` grant, revoke or list the privs of NPC. NPC need privs for
some dangerous things like executing lua code. Examples:
`/npc_talk privs list` lists the privs of all NPC
`/npc_talk privs grant n_3 effect_exec_lua`
grants NPC n_3 the right to execute
lua code as an effect/result
Note: If a precondition or effect originates from a generic
NPC, the priv will be considered granted if either the
executing NPC or the the generic NPC has the priv.
#### `/npc_talk force_restore_npc <id> [<copy_from_id>]` Restore an NPC that got lost.
It may have got lost due to someone having misplaced its egg.
Or it might have been killed somehow.
The optional parameter `<copy_from_id>` is only used when the NPC
is *not* listed in `/npc_talk list`. You won't need it. It's for legacy NPC.
WARNING: If the egg or the NPC turns up elsewhere, be sure to have only
*ONE* NPC with that ID standing around! Else you'll get chaos.
#### `/npc_talk generic` Add or remove NPC from the list of generic dialog providers.
`/npc_talk generic list` Lists all generic NPC
`/npc_talk generic add n_3` Adds the dialog from NPC as a
generic dialog.
`/npc_talk generic add n_3` Removes the dialog from NPC as a
generic dialog.
Requires the npc_talk_admin priv.
#### `/npc_talk_reload` Reload almost all of the code of this mod.
When you add custom new functions to this mod or change a
custom function - or even code of the mod itself! -, you can
reload this mod without having to restart the server.
If you made an error and the files can't load then your
server will crash - so please test on a test server first!
Requires the privs priv.
### 3. Terminology
<a name="terminology"></a>
dialog A text said by the NPC, with diffrent replys the player can
select from.
option A reply/answer to the text the NPC said.
precondition/ All listed preconditions have to be true in order for the NPC
prerequirement to offer this option.
action An action the player may (or may not) take, i.e. trading,
taking an item from the NPC, giving the NPC something, entering
the right password etc.
effect/result Further effects (like setting variables, handing out items)
that take place after the action was successful.
alternate text Text shown instead of the normal dialog text. This is useful
when you have a dialog with a lot of questions and want the
player to be able to easily select the next question without
having to create a new dialog for each option.
### 4. Special dialogs
<a name="special-dialogs"></a>
In general, dialogs follow the naming schem "d_<nr>". However, some few have a
special meaning:
`d_end` End the conversation (i.e. after teleporting the player).
`d_got_item` The NPC got something and is trying to decide what to do with it.
### 5. How to configure NPC and add dialogs
<a name="how-to-configure"></a>
Just talk to them and click on the "I am your owner"-Dialog. This opens up
a menu where you can edit most things.
hint: The command `/npc_talk debug <npc_id>` allows you to get debug
information regarding preconditions and effects. You can turn it
off with `/npc_talk debug off`. The NPC ID can be seen in the
setup dialog for preconditions and effects.
### 6. The privs
<a name="privs"></a>
`npc_talk_owner` will allow players to edit their *own* NPC by talking to them.
Ought to be given to all players.
`npc_talk_master` allows players to edit *any* NPC supported by this mod.
Ought to be given to selected trusted players who want to
help others with their NPC configuration and/or support
NPCs owned by the server.
`npc_talk_admin` Generic maintenance of NPC. Necessary for the command
`/npc_talk generic` - add or remove an NPC from the list of
generic dialog providers
Also allows to set and change NPC properties starting with
the prefix "server".
`npc_master` allows players to edit *any* NPC supported by this mod.
*Does* include usage of the staffs (now part of `yl_npc`).
This is very powerful and allows to enter and execute lua
code without restrictions.
Only grant this to staff members you really trust.
`privs` Necessary for the commands
`/npc_talk privs` - grant NPC privs like e.g. execute lua
`/npc_talk_reload` - reload code of this mod
NPC can have privs as well. The NPC...
`precon_exec_lua` ..is allowed to excecute lua code as a precondition
`effect_exec_lua` ..is allowed to execute lua code as an effect
`effect_give_item` ..can give items to the player, created out of thin air
`effect_take_item` ..can accept and destroy items given to it by a player
`effect_move_player` ..can move the player to another position
### 7. Tools
<a name="tools"></a>
There are no more tools (=staffs) provided. You can do all you could do
with them by just talking to the NPC.
### 8. Mute
<a name="mute"></a>
When you edit an NPC, you might want to stop it from talking to other players
and spoiling unifinished texts/options to the player.
For this case, the NPC can be muted. This works by selecting the appropriate
option in the talk menu after having started edit mode by claiming to be the
NPC's owner.
### 9. Skin
<a name="skin"></a>
The skin and what the NPC wields can be changed via the "Edit Skin" button.
### 10. Simple variables
<a name="simple-variables"></a>
If you want to let your NPC greet the player by name, you can do so. Some
variables/texts are replaced appropriately in the text the NPC says and
the player can reply:
`$MY_NAME$` will be replaced by the name of the NPC
`$NPC_NAME$` same as above
`$OWNER_NAME$` will be replaced by the name of the owner of the NPC
`$PLAYER_NAME$` will be replaced by the name of the player talking to the NPC
`$GOOD_DAY$` will be replaced by "Good morning", "Good afternoon" or
"Good evening" - depending on the ingame time of day
`$good_DAY$` same as above, but starts with a lowercase letter (i.e.
"good morning")
Note: If you want to extend this, you can do the following in your own mod:
```
local old_function = yl_speak_up.replace_vars_in_text
yl_speak_up.replace_vars_in_text = function(text, dialog, pname)
-- do not forget to call the old function
text = old_function(text, dialog, pname)
-- do your own replacements
text = string.gsub(text, "$TEXT_TO_REPLACE$", "new text")
-- do not forget to return the new text
return text
end
```
The replacements will not be applied in edit mode.
### 11. Trading (simple)
<a name="trading-simple"></a>
The NPC can trade item(stacks) with other players.
Only undammaged items can be traded.
Items that contain metadata (i.e. written books, petz, ..) cannot be traded.
Dammaged items and items containing metadata cannot be given to the NPC.
Trades can either be attached to dialog options (and show up as results there)
via the edit options dialog or just be trades that are shown in a trade list.
The trade list can be accessed from the NPC's inventory.
If there are trades that ought to show up in the general trade list (i.e. not
only be attached to dialog options), then a button "Let's trade" will be shown
as option for the first dialog.
Trades that are attached to the trade list (and not dialog options) can be
added and deleted without entering edit mode ("I am your owner. ...").
If unsure where to put your trades: If your NPC wants to tell players a story
about what he sells (or if it is i.e. a barkeeper), put your trades in the
options of dialogs. If you just want to sell surplus items to other players
and have the NPC act like a shop, then use the trade list.
### 12. Logging
<a name="logging"></a>
The trade list view provides access to a log file where all changes to the
NPC, inventory movements and purchases are logged.
The log shows the date but not the time of the action. Players can view the
logs of their own NPC.
If you want to keep an NPC from logging, set the property
server_nolog_effects to i.e "true"
That way, the NPC will no longer log or send debug messages when executing
effects.
### 13. Quest items
<a name="quest-items"></a>
Quest items can be *created* to some degree as part of the *action* of an
*option* through the edit options menu.
MineTest does not allow to create truely new items on a running server on
the fly. What can be done is giving specific items (i.e. *that* one apple,
*that* piece of paper, *that* stack of wood, ..) a new description that
makes it diffrent from all other items of the same type (i.e. all other
apples).
A new description alone may also be set by the player with the engraving
table (provided that mod is installed):
<a href="https://forum.minetest.net/viewtopic.php?t=17482">See Minetest Forum Engraving Table Topic</a>
In order to distinguish items created by your NPC and those created through
the engraving table or other mods, you can set a special ID. That ID will
also contain the name of the player to which the NPC gave the item. Thus,
players can't just take the quest items of other players from their bones
and solve quests that way.
The actions *npc_gives* and *npc_wants* are responsible for handling of
quest items. They can of course also handle regular items.
If an NPC creates a special quest item for a player in the *npc_gives*
action, it takes the item out of its inventory from a stack of ordinary
items of that type and applies the necessary modifications (change
description, set special quest ID, set information which player got it).
If the NPC gets such a quest item in an *npc_wants* action, it will check
the given parameters. If all is correct, it will strip those special
parameters from the item, call the action a success and store the item
in its inventory without wasting space (the item will likely stack if it
is *not* a quest item).
### 14. Entering Passwords
<a name="quest-passwords"></a>
Another useful method for quests is the *text_input* action. It allows the
NPC to ask for a passwort or the answer to a question the NPC just asked.
The player's answer is checked against the *expected answer* that you give
when you set up this action.
### 15. Custom Preconditions, Actions and Effects
<a name="precon_action_effect"></a>
You can define custom actions and provide up to ten parameters. The file
`custom_functions_you_can_override.lua`
holds examplexs. Please do not edit that file directly. Just take a look
there and override functions as needed in your own files! That way it is
much easier to update.
In general, the table
`yl_speak_up.custom_functions_p_[ descriptive_name ]`
holds information about the parameters for display in the formspec (when
setting up a precondition, action or effect) and contains the function
that shall be executed.
### 16. Alternate Text
<a name="alternate_text"></a>
Sometimes you may encounter a situation where your NPC ought to answer to
several questions and the player likely wanting an answer to each. In such
a situation, you might create a dialog text for each possible option/answer
and add an option to each of these new dialogs like "I have more questions.".
That is sometimes impractical. Therefore, you can add alternate texts.
These alternate texts can be shown instead of the normal dialog text when the
player selected an option/answer. Further alternate texts can be shown if
the action (provided there is one defined for the option) or an effect failed.
The alternate text will override the text of the dialog (what the NPC says)
but offer the same options/answers as the dialog normally would.
Alternate texts can be converted to normal dialogs, and normal dialogs can
vice versa be converted to alternate texts if only one option/answer points
to them.
### 17. Autoselect/Autoanswer
<a name="autoselect_autoanswer"></a>
Sometimes you may wish to i.e. greet the player who has been sent on a mission
or who is well known to the NPC in a diffrent way.
For that purpose, you can use the option in the edit options menu right next to
"..the player may answer with this text [dialog option "o_<nr>"]:" and switch that
from "by clicking on it" to "automaticly".
When the NPC shows a dialog, it will evaluate all preconditions of all options. But
once it hits an option where selecting has been set to "automaticly" and all other
preconditions are true, it will abort processing the current dialog and move on to
the dialog stated in the automaticly selected option/answer and display that one.
### 18. Random Dialogs
<a name="random_dialogs"></a>
If you want the NPC to answer with one of several texts (randomly selected), then
add a new dialog with options/answers that lead to your random texts and edit one
of these options so that it is "randomly" selected.
Note that random selection affects the entire dialog! That dialogs' text isn't shown
anymore, and neither are the texts of the options/answers shown. Whenever your NPC
ends up at this dialog, he'll automaticly choose one of the options/answers randomly
and continue there!
### 19. Maximum recursion depth
<a name="max_recursion_depth"></a>
Autoselect/autoanswer and random dialogs may both lead to further dialogs with further
autoanswers and/or random selections. As this might create infinite loops, there's a
maximum number of "redirections" through autoanswer and random selection that your NPC
can take. It is configured in config.lua as
`yl_speak_up.max_allowed_recursion_depth`
and usually set to 5.
### 20. Giving things to the NPC
<a name="npc_wants"></a>
There are several ways of giving items to the NPC: trade, using an action where the
NPC wants a more or less special item, or an effect/result where the NPC just removes
the item from the player's inventory and thrashes it (requires `npc_talk_admin` priv
- or whichever priv you set in config.lua as `yl_speak_up.npc_privs_priv`).
Using an action might work in many situations. There may be situations where it
would be far more convenient for the player to just give the items to the NPC and let
it deal with it.
In order to activate this functionality, just enter edit mode and select the option
"I want to give you something.".
A new dialog named `d_got_item` will be created and an option shown to players in the
very first dialog where they can tell the NPC that they want to give it something.
The dialog `d_got_item` can have options like any other dialog - except that autoselect
will be activated for each option. If there are no options/answers to this dialog or
none fits, the NPC will offer the items back automaticly.
Please make sure each option of the dialog `d_got_item` has a precondition that
inspects what was offered:
"an item the player offered/gave to the NPC" (precondition)
and an effect/result that deals with the item:
"an item the player offered to the NPC" (effect)
Else the items will just be offered back to the player.
### 21. Configuration
<a name="configuration"></a>
Please make sure that the tables
yl_speak_up.blacklist_effect_on_block_<type> with <type>:
<interact|place|dig|punch|right_click|put|take>
contain all the blocks which do not allow the NPCs this kind of
interaction.
You may i.e. set the put and take tables for blocks that do extensive
checks on the player object which the NPC simply can't provide.
Note: The best way to deal with local adjustments may be to create your
own mod, i.e. yl_speak_up_addons, and let that mod depend on this
one, yl_speak_up, and do the necessary calls. This is very useful
for i.e. adding your own textures or doing configuration. You can
then still update the mod without loosing local configuration.
### 22. Adding local overrides for your server
<a name="server_overrides"></a>
You can override and add config values by creating and adding a file
local_server_config.lua
in the mod folder of this mod. It will be executed after the file
config.lua
has been executed. This happens at startup and each time after the command
/npc_talk_reload has been given.
If you want to add or override existing functions (i.e. functions from/for
custom_functions_you_can_override.lua), you can create a file named
local_server_do_on_reload.lua
in the mod folder of this mod. It will be executed at startup and each time
/npc_talk_reload is executed.
Note: If you want to register things (call minetest.register_-functions),
you have to do that in the file
local_server_do_once_on_startup.lua
which will be executed only *once* after server start and *not* when
/npc_talk_reload is executed.
### 23. Data saved in modstorage
<a name="modstorage"></a>
status Set this to 2 to globally deactivate all NPC.
amount Number of NPCs generated in this world. This is
needed to create a uniqe ID for each NPC.
generic_npc_list List of NPC ids whose dialogs shall be used as
generic dialogs.
### 24. Files generated in world folder
<a name="files_generated"></a>
yl_speak_up.path/ Folder containing the JSON files containing the
stored dialogs of the NPC.
yl_speak_up.inventory_path/ Folder containing the detatched inventories of
the NPC.
yl_speak_up_npc_privs.data File containing the privs of the NPC.
yl_speak_up.player_vars_save_file JSON file containing information about
quest progress and quest data for individual
players.
### 25. Properties
<a name="properties"></a>
NPC may have properties. A property is a value a particular NPC has. It
does not depend on any player and will remain the same until you change
it for this NPC. You can view and change properties via the "Edit" button
and then clicking on "Edit properties". There are preconditions for
checking properties and effects for changing them.
Properties prefixed by the text "self." originate from the NPC itself,
i.e. self.order (as used by many mobs_redo NPC for either following their
owner, standing around or wandering randomly). They usually cannot be
changed - unless you write a function for them. See
yl_speak_up.custom_property_handler
and
custom_functions_you_can_override.lua
You can also react to or limit normal property changes this way.
Properties starting with "server" can only be changed by players who have
the `npc_talk_admin` priv.
Example for a property: mood of the NPC (raises when treated well, gets
lowered when treated badly).
Properties are also extremyl important for generic behaviour. Depending
on the properties of the NPC, a particular generic behaviour might fit
or not fit to the NPC.
### 26. Generic behaviour
<a name="generic_behaviour"></a>
Sometimes you may have a group of NPC that ought to show a common behaviour
- like for example guards, smiths, bakers, or inhabitants of a town, or other
NPC that have something in common. Not each NPC may warrant its own, individual
dialogs.
The Tutoial (TODO!) is another example: Not each NPC needs to present the player
with a tutorial, but those that are owned and where the owner tries to
program them ought to offer some help.
That's where generic dialogs come in. You can create a new type of generic
dialog with any NPC. That NPC can from then on only be used for this one
purpose and ought not to be found in the "normal" world! Multiple such generic
dialogs and NPC for their creation can exist.
Generic dialogs have to start with a dialog with just one option. This option
has to be set to "automaticly" (see Autoanswer). The preconditions of this
option are very important: They determine if this particular generic dialog
fits to this particular NPC or not. If it fits, all dialogs that are part of
this NPC that provides the generic dialog will be added to the "normal"
dialogs the importing actual NPC offers. If it doesn't fit, these generic
dialogs will be ignored here.
The creator of a generic dialog can't know all situations where NPC may want
to use his dialogs and where those NPC will be standing and by whom they
are owned. Therefore only a limited amount of types of preconditions are
allowed for the preconditions of this first automatic option: state,
property, player_inv and custom.
The other dialogs that follow after this first automatic dialog may contain
a few more types of preconditions: player_offered_item, function and other
are allowed here as well, while block, trade, npc_inv and block_inv make no
sense and are not available.
All types of actions are allowed.
Regarding effects/results, the types block, put_into_block_inv,
take_from_block_inv and craft are not supported.
The "automaticly" selected only option from the start dialog leads
via the usual "dialog" effect to the actual start dialog for the
imported dialogs from that NPC. The options found there will be
added into the target NPC and the dialog text will be appended to
its dialog text.
The chat command `/npc_talk generic` (requires npc_talk_admin priv) is
used to list, add or remove NPC from the list of generic dialog/behaviour
providers.
### 27. Integration into your own NPC/mob mods
<a name="integration"></a>
In order to talk to NPC, you need to call
if(minetest.global_exists("yl_speak_up") and yl_speak_up.talk) then
yl_speak_up.talk(self, clicker)
return
end
in the function that your NPC executes in on_rightclick.
Note that capturing and placing of your NPC is *not* handled by yl_speak_up!
Use i.e. the lasso that came with your NPC mod.

View File

@ -172,6 +172,8 @@ end
yl_speak_up.check_and_add_as_generic_dialog = function(dialog, n_id)
yl_speak_up.generic_dialogs[n_id] = nil
yl_speak_up.generic_dialog_conditions[n_id] = nil
-- we do *not* want d_dynamic in generic dialogs (each NPC will get its own anyway):
dialog.n_dialogs["d_dynamic"] = nil
-- get the start dialog
local d_id = yl_speak_up.get_start_dialog_id(dialog)
if(not(d_id)
@ -443,6 +445,19 @@ end
yl_speak_up.add_generic_dialogs = function(dialog, current_n_id, player)
dialog = yl_speak_up.strip_generic_dialogs(dialog)
-- make sure we can add d_dynamic dialog:
if(not(dialog)) then
dialog = {}
end
if(not(dialog.n_dialogs)) then
dialog.n_dialogs = {}
end
-- make sure the dynamic dialog exists (as an empty dialog):
-- (initial_dialog looks for dialog.n_npc in order to determine if it's a new npc;
-- so we are safe here with an initialized dialog)
dialog.n_dialogs["d_dynamic"] = {}
dialog.n_dialogs["d_dynamic"].d_options = {}
if(not(player) or not(current_n_id)) then
return dialog
end
@ -453,12 +468,6 @@ yl_speak_up.add_generic_dialogs = function(dialog, current_n_id, player)
start_dialog_current = "d_1"
end
-- unconfigured NPC are in special need of generic dialogs
if(not(dialog)) then
dialog = {}
end
if(not(dialog.n_dialogs)) then
dialog.n_dialogs = {}
end
if(not(dialog.n_dialogs[start_dialog_current])) then
dialog.n_dialogs[start_dialog_current] = {}
end

188
addons/action_send_mail.lua Normal file
View File

@ -0,0 +1,188 @@
-- requires mail.send from the mail mod
-- sending a mail allows to give feedback - and for players to ask the NPC owner to add more texts
-- and let the NPC answer to further questions
-- define the custom action named "send_mail"
local action_send_mail = {
-- this information is necessary for allowing to add this as an action to an option
description = "Send a mail via the mail_mod mod for feedback/ideas/etc.",
-- define the parameters that can be set when the action is added
param1_text = "To:",
param1_desc = "Who shall receive this mail? Default: $OWNER_NAME$."..
"\nNote: Leave fields empty for the default values."..
"\nNote: All parameters allow to use the usual replacements like $NPC_NAME$,"..
"\n\t$OWNER_NAME$, $PLAYER_NAME$, $VAR name_of_your_var$, $PROP name_of_prop$.",
param2_text = "From:",
param2_desc = "Who shall be listed as the sender of this mail?\n"..
"The player talking to the NPC might be best as it makes answering easier.\n"..
"Default: $PLAYER_NAME$. Also allowed: $OWNER_NAME$.",
param3_text = "cc:",
param3_desc = "(optional) Whom to send a carbon copy to?"..
"\nThis is useful if multiple players may edit this NPC."..
"\nIt is also possible to send a copy to $PLAYER_NAME$.",
param4_text = "bcc:",
param4_desc = "(optional) Who gets set in the bcc?",
param5_text = "Subject:",
param5_desc = "The subject of the mail. The player talking to the NPC\n"..
"will provide the actual text for the body of the mail.\n"..
"Default: \"$NPC_NAME$: regarding $PLAYER_NAME$\"",
}
-- this function will show a formspec whenever our custom action "send_mail" is executed
--yl_speak_up.custom_functions_a_[ "send_mail" ].code = function(player, n_id, a)
action_send_mail.code = function(player, n_id, a)
local pname = player:get_player_name()
-- sending the mail can either succeed or fail; pdata.tmp_mail_* variables store the result
local pdata = yl_speak_up.speak_to[pname]
-- just the normal dialog data from the NPC (contains name of the NPC and owner)
local dialog = yl_speak_up.speak_to[pname].dialog
local npc_name = "- (this NPC) -"
local owner_name = "- (his owner) -"
if(dialog) then
-- the NPC is the one "forwarding" the message (so that the receiver will know
-- *which* NPC was talked to)
npc_name = minetest.formspec_escape(dialog.n_npc or "- ? -")
-- usually the owner is the receiver
owner_name = minetest.formspec_escape(a.a_param1 or dialog.npc_owner or "- ? ")
end
-- the mail was already sent successful; we still return once to this formspec so that
-- the player gets this information and can finish the action successfully
if(pdata and pdata.tmp_mail_to and pdata.tmp_mail_success) then
local mail_to = minetest.formspec_escape(pdata.tmp_mail_to or "?")
-- unset temporary variables that are no longer needed
pdata.tmp_mail_success = nil
pdata.tmp_mail_error = nil
pdata.tmp_mail_to = nil
return table.concat({
"size[20,3]label[0.2,0.7;",
npc_name,
" has sent a mail containing your text to ",
mail_to,
" and awaits further instructions."..
"\nPlease be patient and wait for a reply. This may take some time as ",
mail_to,
" has to receive, read and answer the mail.]",
-- offer a button to finally complete the action successfully
"button[4,2;6.8,0.9;finished_action;Ok. I'll wait.]",
}, "")
-- we tried to send the mail - and an error occoured
elseif(pdata and pdata.tmp_mail_to and pdata.tmp_mail_error) then
local mail_to = minetest.formspec_escape(pdata.tmp_mail_to or "?")
local error_msg = minetest.formspec_escape(pdata.tmp_mail_error or "?")
-- unset temporary variables that are no longer needed
pdata.tmp_mail_success = nil
pdata.tmp_mail_error = nil
pdata.tmp_mail_to = nil
return table.concat({
"size[20,8]label[0.2,0.7;",
npc_name,
" FAILED to sent a mail containing your text to ",
mail_to,
" in order to get help!]",
"textarea[0.2,1.8;19.6,5;;The following error(s) occourd:;",
error_msg,
"]",
-- the action can no longer be completed successfully; best to back to talk
"button[7,7.0;6.0,0.9;back_to_talk;Back to talk]",
}, "")
end
-- the mail has not been sent yet; show the normal formspec asking for text input
return table.concat({"size[20,8.5]label[4,0.7;Send a message to ",
npc_name,
"]",
"button[17.8,0.2;2.0,0.9;back_to_talk;Back]",
"label[0.2,7.0;Note: ",
npc_name,
" will send a mail to ",
minetest.formspec_escape(a.a_param1 or dialog.npc_owner or "- ? "),
", requesting instructions how to respond. This may take a while.]",
"button[3.6,7.5;6.0,0.9;back_to_talk;Abort and go back]",
"button[10.2,7.5;6.0,0.9;send_mail;Send this message]",
-- read-only
"textarea[0.2,1.8;19.6,5;message_text;Write your message for ",
npc_name,
" here, and then click on \"Send this message\":;",
"",
"]",
})
end
-- whenever our formspec above for the custom action "send_mail" received input (player clicked
-- on a button), this function is called
action_send_mail.code_input_handler = function(player, n_id, a, formname, fields)
local pname = player:get_player_name()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return fields
end
-- sending was aborted or there was no text to send
if( not(fields.message_text) or fields.message_text == ""
or not(fields.send_mail) or fields.send_mail == "") then
return fields
end
local dialog = yl_speak_up.speak_to[pname].dialog
-- prepare data/parameters for the mail we want to send
-- $PLAYER_NAME$, $OWNER_NAME$, $NPC_NAME$, $VAR *$ $PROP *$ are allowed replacements!!
local mail_to = yl_speak_up.replace_vars_in_text(a.a_param1, dialog, pname)
local mail_from = yl_speak_up.replace_vars_in_text(a.a_param2, dialog, pname)
local mail_cc = yl_speak_up.replace_vars_in_text(a.a_param3, dialog, pname)
local mail_bcc = yl_speak_up.replace_vars_in_text(a.a_param4, dialog, pname)
local mail_subject = yl_speak_up.replace_vars_in_text(a.a_param5, dialog, pname)
if(not(mail_to) or mail_to == "") then
mail_to = dialog.npc_owner
end
-- make sure the sender is not forged; we allow EITHER the name of the owner of the NPC
-- OR the name of the player currently talking to the npc
if(not(mail_from) or mail_from == "" or mail_from ~= dialog.npc_owner) then
mail_from = pname
end
if(not(mail_cc) or mail_cc == "") then
mail_cc = nil
end
if(not(mail_bcc) or mail_bcc == "") then
mail_bcc = nil
end
if(not(mail_subject) or mail_subject == "") then
mail_subject = (dialog.n_npc or "- ? -")..": regarding "..pname
end
-- actually send the mail via the mail_mod mod
local success, error_msg = mail.send({
from = mail_from,
to = mail_to,
cc = mail_cc,
bcc = mail_bcc,
subject = mail_subject,
body = "Dear "..tostring(dialog.npc_owner)..",\n\n"..tostring(pname)..
" asked me something I don't know the answer to. Hope you can help? "..
"This is the request:\n\n"..
tostring(fields.message_text or "- no message -")
})
-- Sending this mail was either successful or not. We want to display this to the player.
-- Therefore, we set fields.back_from_error_msg. This tells the calling function that it
-- needs to display the formspec generated by the function
-- yl_speak_up.custom_functions_a_[ "send_mail" ].code
-- again.
fields.back_from_error_msg = true
-- The function displaying the formspec needs to know that it has to display the result
-- of sending the mail now. We need to store these variables somewhere.
local pdata = yl_speak_up.speak_to[pname]
pdata.tmp_mail_success = success
pdata.tmp_mail_error = error_msg
pdata.tmp_mail_to = mail_to
-- the function has to return fields
return fields
end
if(minetest.global_exists("mail")
and type(mail) == "table"
and type(mail.send) == "function") then
-- only add this action if the mail mod and the mail.send function exist
yl_speak_up.custom_functions_a_[ "send_mail" ] = action_send_mail
end

View File

@ -0,0 +1,45 @@
-- hand out a preconfigured waypoint compass to the player
yl_speak_up.custom_functions_r_[ "send_coordinates" ] = {
description = "Send a chat message to the player with coordinates.",
param1_text = "X coordinate:",
param1_desc = "The target x coordinate.",
param2_text = "Y coordinate:",
param2_desc = "The target y coordinate.",
param3_text = "Z coordinate:",
param3_desc = "The target z coordinate.",
param4_text = "Name of target location:",
param4_desc = "This is how the target location is called, i.e. \"Hidden treasure chest\".",
-- the color cannot be set this way
-- param5_text = "Color code in Hex:",
-- param5_desc = "Give the color for the compass here. Example: \"FFD700\".\n"..
-- "Needs to be 6 characters long, with each character ranging\n"..
-- "from 0-9 or beeing A, B, C, D, E or F.\n"..
-- "Or just write something like yellow, orange etc.",
code = function(player, n_id, r)
local pname = player:get_player_name()
local coords = core.string_to_pos((r.r_param1 or "0")..","..
(r.r_param2 or "0")..","..
(r.r_param3 or "0"))
local town = (r.r_param4 or "- some place somewhere -")
if(not(coords)) then
minetest.chat_send_player(pname, "Sorry. There was an internal error with the "..
"coordinates. Please inform whoever is responsible for this NPC.")
return false
end
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return false
end
local dialog = yl_speak_up.speak_to[pname].dialog
minetest.chat_send_player(pname,
(dialog.n_npc or "- ? -")..": \""..
tostring(town).."\" can be found at "..core.pos_to_string(coords, 0)..".")
if(minetest.get_modpath("waypoint_compass")) then
minetest.chat_send_player(pname, "If you have a waypoint compass, right-click "..
"while wielding it. Select \"copy:\" to copy the location above into "..
"your compass.")
end
-- the function was successful (effects only return true or false)
return true
end,
}

View File

@ -0,0 +1,94 @@
-- requires mail.send from the mail mod
-- NPC can send out mails in order to sum up a quest state or complex step
-- - or just to inform their owner that they ran out of stock
-- There is also a similar action defined in another file. The action
-- allows the player that talks to the NPC to enter his/her own mailtext.
-- The *effect* here requires that the text has been configured in advance.
-- define the custom effect named "send_mail"
local effect_send_mail = {
-- this information is necessary for allowing to add this as an effect to an option
description = "Send a preconfigured mail via the mail_mod mod for quest state etc.",
-- define the parameters that can be set when the action is added
param1_text = "To:",
param1_desc = "Who shall receive this mail? Default: $PLAYER_NAME$."..
"\nNote: Leave fields empty for the default values."..
"\nNote: All parameters allow to use the usual replacements like $NPC_NAME$,"..
"\n\t$OWNER_NAME$, $PLAYER_NAME$, $VAR name_of_your_var$, $PROP name_of_prop$.",
-- the "From:" field will always be the name of the owner of the NPC
-- param2_text = "From:",
-- param2_desc = "Who shall be listed as the sender of this mail?\n"..
-- "The player talking to the NPC might be best as it makes answering easier.\n"..
-- "Default: $PLAYER_NAME$. Also allowed: $OWNER_NAME$.",
param3_text = "cc:",
param3_desc = "(optional) Whom to send a carbon copy to?"..
"\nThis is useful if multiple players may edit this NPC."..
"\nIt is also possible to send a copy to $PLAYER_NAME$.",
param4_text = "bcc:",
param4_desc = "(optional) Who gets set in the bcc?",
param5_text = "Subject:",
param5_desc = "The subject of the mail. Ought to give the player information\n"..
"which NPC sent this mail and why.\n"..
"Default: \"$NPC_NAME$ has a message from $OWNER_NAME$\"",
param6_text = "Mail text:",
param6_desc = "The actual text of the mail. Use the usual replacements to make the mail\n"..
"meaningful! You may want to use $VAR name_of_your_var$.\n"..
"Note: Use \\n to create a newline!",
}
-- the actual implementation of the function - run when the effect is executed
effect_send_mail.code = function(player, n_id, r)
local pname = player:get_player_name()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return fields
end
local dialog = yl_speak_up.speak_to[pname].dialog
-- prepare data/parameters for the mail we want to send
-- $PLAYER_NAME$, $OWNER_NAME$, $NPC_NAME$, $VAR *$ $PROP *$ are allowed replacements!
local mail_to = yl_speak_up.replace_vars_in_text(r.r_param1, dialog, pname)
local mail_from = yl_speak_up.replace_vars_in_text(r.r_param2, dialog, pname)
local mail_cc = yl_speak_up.replace_vars_in_text(r.r_param3, dialog, pname)
local mail_bcc = yl_speak_up.replace_vars_in_text(r.r_param4, dialog, pname)
local mail_subject = yl_speak_up.replace_vars_in_text(r.r_param5, dialog, pname)
local mail_text = yl_speak_up.replace_vars_in_text(r.r_param6, dialog, pname)
-- this is in reverse of the actions: the mail is usually sent to the player the
-- NPC is talking with - e.g. as a reminder of a quest status
if(not(mail_to) or mail_to == "") then
mail_to = pname
end
-- the mail always originates from the owner of the NPC
mail_from = dialog.npc_owner
if(not(mail_cc) or mail_cc == "") then
mail_cc = nil
end
if(not(mail_bcc) or mail_bcc == "") then
mail_bcc = nil
end
if(not(mail_subject) or mail_subject == "") then
mail_subject = (dialog.n_npc or "- ? -").." has a message from "..(dialog.npc_owner or "- ? -")
end
-- actually send the mail via the mail_mod mod
local success, error_msg = mail.send({
from = mail_from,
to = mail_to,
cc = mail_cc,
bcc = mail_bcc,
subject = mail_subject,
body = "Message from "..tostring(dialog.n_npc or "- ? -")..":\n\n"..
table.concat(string.split(mail_text or "- no message -", "\\n"), "\n")
})
return success
end
if(minetest.global_exists("mail")
and type(mail) == "table"
and type(mail.send) == "function") then
-- only add this effect if the mail mod and the mail.send function exist
yl_speak_up.custom_functions_r_[ "send_mail" ] = effect_send_mail
end

20
addons/load_addons.lua Normal file
View File

@ -0,0 +1,20 @@
-- this file lods addons - actions, preconditions, effects and other things
-- - which may not be of intrest to all games
-- - and which usually require other mods to be installed in order to work
local path_addons = yl_speak_up.modpath..DIR_DELIM.."addons"..DIR_DELIM
-- the action "send_mail" requires the "mail" mod and allows to send
-- ingame mails via actions
if(minetest.global_exists("mail")
and type(mail) == "table"
and type(mail.send) == "function") then
dofile(path_addons .. "action_send_mail.lua")
dofile(path_addons .. "effect_send_mail.lua")
end
-- makes mostly sense if the waypoint_compass mod is installed
dofile(path_addons.."effect_send_coordinates.lua")

3
api/README.md Normal file
View File

@ -0,0 +1,3 @@
This folder - while *named* `api/` - does not necessarily contain the API
of this mod. It's for now a collection of functions used by more than one
file in the `fs/fs_*.lua` folder.

View File

@ -178,7 +178,39 @@ yl_speak_up.get_pname_for_old_fs = function(pname)
end
yl_speak_up.show_fs_decorated = function(pname, edit_mode, h,
-- display the window with the text the NPC is saying
-- Note: In edit mode, and if there is a dialog selected, the necessary
-- elements for editing said text are done in the calling function.
yl_speak_up.show_fs_npc_text = function(pname, formspec, dialog, alternate_text, active_dialog, fs_version)
if(alternate_text and active_dialog and active_dialog.d_text) then
alternate_text = string.gsub(alternate_text, "%$TEXT%$", active_dialog.d_text)
elseif(active_dialog and active_dialog.d_text) then
alternate_text = active_dialog.d_text
end
-- replace $NPC_NAME$ etc.
local t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text(
alternate_text, dialog, pname))
-- t = "Visits to this dialog: "..tostring(active_dialog.visits).."\n"..t
if(fs_version > 2) then
yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
"hypertext", "0.2,5;19.6,17.8", "d_text",
"<normal>"..t.."\n</normal>",
t:trim()..";#000000;#FFFFFF",
true)
else
yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
"textarea", "0.2,5;19.6,17.8", "",
";"..t.."\n",
t:trim(),
true)
end
return formspec
end
yl_speak_up.show_fs_decorated = function(pname, npc_text_already_printed, h,
alternate_text,
add_this_to_left_window,
add_this_to_bottom_window,
@ -217,7 +249,7 @@ yl_speak_up.show_fs_decorated = function(pname, edit_mode, h,
end
formspec = {
"size[57,33]",
"size[58,33]",
"position[0,0.45]",
"anchor[0,0.45]",
"no_prepend[]",
@ -228,7 +260,7 @@ yl_speak_up.show_fs_decorated = function(pname, edit_mode, h,
-- Background
"background[0,0;20,23;yl_speak_up_bg_dialog.png;false]",
"background[0,24;54.5,7.5;yl_speak_up_bg_dialog.png;false]",
"background[0,24;55.6,7.5;yl_speak_up_bg_dialog.png;false]",
-- Frame Dialog
"image[-0.25,-0.25;1,1;yl_speak_up_bg_dialog_tl.png]",
@ -243,12 +275,12 @@ yl_speak_up.show_fs_decorated = function(pname, edit_mode, h,
"image[-0.25,23.75;1,1;yl_speak_up_bg_dialog_tl.png]",
"image[-0.25,30.75;1,1;yl_speak_up_bg_dialog_bl.png]",
"image[53.75,23.75;1,1;yl_speak_up_bg_dialog_tr.png]",
"image[53.75,30.75;1,1;yl_speak_up_bg_dialog_br.png]",
"image[54.75,23.75;1,1;yl_speak_up_bg_dialog_tr.png]",
"image[54.75,30.75;1,1;yl_speak_up_bg_dialog_br.png]",
"image[-0.25,24.75;1,6;yl_speak_up_bg_dialog_hl.png]",
"image[53.75,24.75;1,6;yl_speak_up_bg_dialog_hr.png]",
"image[0.75,23.75;53,1;yl_speak_up_bg_dialog_vt.png]",
"image[0.75,30.75;53,1;yl_speak_up_bg_dialog_vb.png]",
"image[54.75,24.75;1,6;yl_speak_up_bg_dialog_hr.png]",
"image[0.75,23.75;54,1;yl_speak_up_bg_dialog_vt.png]",
"image[0.75,30.75;54,1;yl_speak_up_bg_dialog_vb.png]",
"label[0.3,0.6;",
minetest.formspec_escape(dialog.n_npc),
@ -265,6 +297,13 @@ yl_speak_up.show_fs_decorated = function(pname, edit_mode, h,
if(fs_version > 2) then
table.insert(formspec, "style_type[button;bgcolor=#a37e45]")
table.insert(formspec, "style_type[button_exit;bgcolor=#a37e45]") -- Dialog
table.insert(formspec, "style[button_start_edit_mode,show_log,add_option,"..
"delete_this_empty_dialog,show_what_points_to_this_dialog,"..
"make_first_option,turn_into_a_start_dialog,mute_npc,"..
"un_mute_npc,button_end_edit_mode,show_inventory,order_follow,"..
"button_edit_basics,"..
"order_stand,order_wander,order_custom;"..
"bgcolor=#FF4444;textcolor=white]")
-- table.insert(formspec, "background[-1,-1;22,25;yl_speak_up_bg_dialog2.png;false]")
-- table.insert(formspec, "background[-1,23;58,10;yl_speak_up_bg_dialog2.png;false]")
-- table.insert(formspec, "style_type[button;bgcolor=#a37e45]")
@ -273,28 +312,8 @@ yl_speak_up.show_fs_decorated = function(pname, edit_mode, h,
-- display the window with the text the NPC is saying
-- Note: In edit mode, and if there is a dialog selected, the necessary
-- elements for editing said text are done in the calling function.
if(not(edit_mode) or not(dialog) or not(dialog.n_dialogs)) then
if(alternate_text and active_dialog and active_dialog.d_text) then
alternate_text = string.gsub(alternate_text, "%$TEXT%$", active_dialog.d_text)
elseif(active_dialog and active_dialog.d_text) then
alternate_text = active_dialog.d_text
end
-- replace $NPC_NAME$ etc.
local t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text(
alternate_text, dialog, pname))
if(fs_version > 2) then
yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
"hypertext", "0.2,5;19.6,17.8", "d_text",
"<normal>"..t.."\n</normal>",
t:trim()..";#000000;#FFFFFF",
true)
else
yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
"textarea", "0.2,5;19.6,17.8", "",
";"..t.."\n",
t:trim(),
true)
end
if(not(npc_text_already_printed) or not(dialog) or not(dialog.n_dialogs)) then
yl_speak_up.show_fs_npc_text(pname, formspec, dialog, alternate_text, active_dialog, fs_version)
end
-- add custom things (mostly for editing a dialog) to the window shown left
@ -321,11 +340,20 @@ yl_speak_up.show_fs_decorated = function(pname, edit_mode, h,
if(allow_scrolling and fs_version > 2) then
local max_scroll = math.ceil(h - yl_speak_up.max_number_of_buttons) + 1
table.insert(formspec, "scrollbaroptions[min=0;max="..tostring(max_scroll)..
";smallstep=1;largestep=2;arrows=show".. --]")
";thumbsize="..tostring(math.ceil(h/yl_speak_up.max_number_of_buttons)).."]")
table.insert(formspec, "scrollbar[0.2,24.2;0.2,7;vertical;scr0;0]")
table.insert(formspec, "scroll_container[0,24;56,7;scr0;vertical;1]")
-- table.insert(formspec, "scrollbar[0.2,24.2;0.2,7;vertical;scr0;0]")
table.insert(formspec, "scrollbaroptions[min=0;max=")
table.insert(formspec, tostring(max_scroll*10))
table.insert(formspec, ";thumbsize=")
table.insert(formspec, tostring(math.ceil(
yl_speak_up.max_number_of_buttons /
(yl_speak_up.max_number_of_buttons + max_scroll)*max_scroll*10)))
table.insert(formspec, ";smallstep=10")
table.insert(formspec, ";largestep=")
table.insert(formspec, tostring(yl_speak_up.max_number_of_buttons*10))
table.insert(formspec, "]")
table.insert(formspec, "scrollbar[54.2,24.2;1.2,7.2;vertical;scr0;1]")
table.insert(formspec, "scroll_container[-0.2,24;54.2,7;scr0;vertical;0.1]")
elseif(allow_scrolling) then
if(fs_version < 2) then
-- if the player has an older formspec version

181
api/api_fashion.lua Normal file
View File

@ -0,0 +1,181 @@
-- some meshes use more than one texture, and which texture is the main skin
-- texture can only be derived from the name of the mesh
yl_speak_up.get_mesh = function(pname)
if(not(pname)) then
return "error"
end
local obj = yl_speak_up.speak_to[pname].obj
if(not(obj)) then
return "error"
end
local entity = obj:get_luaentity()
if(not(entity)) then
return "error"
end
-- mobs_redo stores it extra; other mob mods may not
if(not(entity.mesh) and entity.name
and minetest.registered_entities[entity.name]) then
if(minetest.registered_entities[entity.name].initial_properties
and minetest.registered_entities[entity.name].initial_properties.mesh) then
return minetest.registered_entities[entity.name].initial_properties.mesh
end
return minetest.registered_entities[entity.name].mesh
end
return entity.mesh
end
-- diffrent mobs (distinguished by self.name) may want to wear diffrent skins
-- even if they share the same model; find out which mob we're dealing with
yl_speak_up.get_mob_type = function(pname)
if(not(pname)) then
return "error"
end
local obj = yl_speak_up.speak_to[pname].obj
if(not(obj)) then
return "error"
end
local entity = obj:get_luaentity()
if(not(entity)) then
return "error"
end
return entity.name
end
-- this makes use of the "model" option of formspecs
yl_speak_up.skin_preview_3d = function(mesh, textures, where_front, where_back)
local tstr = ""
for i, t in ipairs(textures or {}) do
tstr = tstr..minetest.formspec_escape(t)..","
end
local backside = ""
if(where_back) then
backside = ""..
"model["..where_back..";skin_show_back;"..mesh..";"..tstr..";0,0;false;true;;]"
end
return "model["..where_front..";skin_show_front;"..mesh..";"..tstr..";0,180;false;true;;]"..--"0,300;9]".. -- ;]"..
backside
end
-- TODO: this function is obsolete now
-- this is a suitable version for most models/meshes that use normal player skins
-- (i.e. mobs_redo) with skins in either 64 x 32 or 64 x 64 MC skin format
yl_speak_up.skin_preview_normal = function(skin, with_backside)
local backside = ""
if(with_backside) then
backside = ""..
"image[8,0.7;2,2;[combine:8x8:-24,-8="..skin.."]".. -- back head
"image[7.85,0.55;2.3,2.3;[combine:8x8:-56,-8="..skin.."]".. -- head, beard
"image[8,2.75;2,3;[combine:8x12:-32,-20="..skin..":-32,-36="..skin.."]".. -- body back
"image[8,5.75;1,3;[combine:4x12:-12,-20="..skin.."]".. -- left leg back
"image[8,5.75;1,3;[combine:4x12:-28,-52="..skin..":-12,-52="..skin.."]".. -- r. leg back ov
"image[9,5.75;1,3;[combine:4x12:-12,-20="..skin.."^[transformFX]".. -- right leg back
"image[9,5.75;1,3;[combine:4x12:-12,-36="..skin.."]".. -- right leg back ov
"image[7,2.75;1,3;[combine:4x12:-52,-20="..skin..":-40,-52="..skin..":-60,-52="..skin.."]".. -- l. hand back ov
"image[10,2.75;1,3;[combine:4x12:-52,-20="..skin.."^[transformFX]".. -- right hand back
"image[10,2.75;1,3;[combine:4x12:-52,-20="..skin..":-52,-36="..skin.."]" -- left hand back
end
return "image[3,0.7;2,2;[combine:8x8:-8,-8="..skin.."]"..
"image[2.85,0.55;2.3,2.3;[combine:8x8:-40,-8="..skin.."]".. -- head, beard
"image[3,2.75;2,3;[combine:8x12:-20,-20="..skin..":-20,-36="..skin.."]".. -- body
"image[3,5.75;1,3;[combine:4x12:-4,-20="..skin..":-4,-36="..skin.."]".. -- left leg + ov
"image[4,5.75;1,3;[combine:4x12:-4,-20="..skin.."^[transformFX]".. -- right leg
"image[4,5.75;1,3;[combine:4x12:-20,-52="..skin..":-4,-52="..skin.."]".. -- right leg ov
"image[2.0,2.75;1,3;[combine:4x12:-44,-20="..skin..":-44,-36="..skin.."]".. -- left hand
"image[5.0,2.75;1,3;[combine:4x12:-44,-20="..skin.."^[transformFX]".. -- right hand
"image[5.0,2.75;1,3;[combine:4x12:-36,-52="..skin..":-52,-52="..skin.."]".. -- right hand ov
backside
--local legs_back = "[combine:4x12:-12,-20="..skins.skins[name]..".png"
end
yl_speak_up.cape2texture = function(t)
if(not(t) or t=="") then
t = "blank.png"
end
-- same texture mask as the shield
return "yl_speak_up_mask_shield.png^[combine:32x64:56,20=" .. tostring(t)
end
yl_speak_up.shield2texture = function(t)
if(not(t) or t=="") then
t = "3d_armor_trans.png"
end
return "yl_speak_up_mask_shield.png^[combine:32x64:0,0=(" .. tostring(t) .. ")"
end
yl_speak_up.textures2skin = function(textures)
local temp = {}
-- Cape
local cape = yl_speak_up.cape2texture(textures[1])
-- Main
local main = textures[2]
-- left (Shield)
local left = yl_speak_up.shield2texture(textures[3])
-- right (Sword)
local right = textures[4]
temp = {cape, main, left, right}
return temp
end
yl_speak_up.mesh_update_textures = function(pname, textures)
-- actually make sure that the NPC updates its texture
local obj = yl_speak_up.speak_to[pname].obj
if(not(obj) or not(textures)) then
return
end
-- store the textures without added masks for cape and shield:
yl_speak_up.speak_to[pname].skins = textures
local entity = obj:get_luaentity()
if(entity) then
entity.yl_speak_up.skin = textures
end
-- the skins with wielded items need some conversion,
-- while simpler models may just apply the texture
local mesh = yl_speak_up.get_mesh(pname)
if(mesh and yl_speak_up.mesh_data[mesh].textures_to_skin) then
textures = yl_speak_up.textures2skin(textures)
end
obj:set_properties({ textures = textures })
-- scrolling through the diffrent skins updates the skin; avoid spam in the log
-- yl_speak_up.log_change(pname, n_id,
-- "(fashion) skin changed to "..tostring(new_skin)..".")
end
yl_speak_up.update_nametag = function(self)
if(self.yl_speak_up.hide_nametag) then
self.object:set_nametag_attributes({text=nil})
return
end
if self.yl_speak_up.npc_name then
-- the nametag is normal (cyan by default)
if(self.yl_speak_up.talk) then
self.force_nametag_color = yl_speak_up.nametag_color_when_not_muted
self.object:set_nametag_attributes({color=self.force_nametag_color, text=self.yl_speak_up.npc_name})
-- the nametag has the addition "[muted]" and is magenta when muted
else
self.force_nametag_color = yl_speak_up.nametag_color_when_muted
self.object:set_nametag_attributes({color=self.force_nametag_color, text=self.yl_speak_up.npc_name.." [muted]"})
end
end
end

View File

@ -3,6 +3,9 @@
-- cache the inventory of NPCs for easier access
yl_speak_up.npc_inventory = {}
-- used so that unused inventories are not immediately discarded:
yl_speak_up.npc_inventory_last_used = {}
-- where are they stored on the disk?
yl_speak_up.get_inventory_save_path = function(n_id)
return yl_speak_up.worldpath .. yl_speak_up.inventory_path .. DIR_DELIM .. "inv_" .. n_id .. ".json"
@ -28,64 +31,6 @@ yl_speak_up.check_stack_has_meta = function(player, stack)
end
-- the player has closed the inventory formspec of the NPC - save it
yl_speak_up.input_inventory = function(player, formname, fields)
local pname = player:get_player_name()
local d_id = yl_speak_up.speak_to[pname].d_id
local n_id = yl_speak_up.speak_to[pname].n_id
-- after closing the inventory formspec:
-- ..save the (very probably) modified inventory
yl_speak_up.save_npc_inventory(n_id)
-- show inventory again?
if(fields.back_from_error_msg) then
yl_speak_up.show_fs(player, "inventory")
return
end
-- show the trade list?
if(fields.inventory_show_tradelist) then
yl_speak_up.show_fs(player, "trade_list")
return
end
-- ..and go back to the normal talk formspec
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
end
-- access the inventory of the NPC (only possible for players with the right priv)
yl_speak_up.get_fs_inventory = function(player)
if(not(player)) then
return ""
end
local pname = player:get_player_name()
-- which NPC is the player talking to?
local n_id = yl_speak_up.speak_to[pname].n_id
local dialog = yl_speak_up.speak_to[pname].dialog
-- do we have all the necessary data?
if(not(n_id) or not(dialog.n_npc)) then
return "size[6,2]"..
"label[0.2,0.5;Ups! This NPC lacks ID or name.]"..
"button_exit[2,1.5;1,0.9;exit;Exit]"
end
-- only players which can edit this npc can see its inventory
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
return "size[6,2]"..
"label[0.2,0.5;Sorry. You lack the privileges.]"..
"button_exit[2,1.5;1,0.9;exit;Exit]"
end
return "size[12,11]" ..
"label[2,-0.2;Inventory of "..minetest.formspec_escape(dialog.n_npc)..
" (ID: "..tostring(n_id).."):]"..
"list[detached:yl_speak_up_npc_"..tostring(n_id)..";npc_main;0,0.3;12,6;]" ..
"list[current_player;main;2,7.05;8,1;]" ..
"list[current_player;main;2,8.28;8,3;8]" ..
"listring[detached:yl_speak_up_npc_"..tostring(n_id)..";npc_main]" ..
"listring[current_player;main]" ..
"button[3.5,6.35;5,0.6;inventory_show_tradelist;Show trade list trades (player view)]"..
"button[10.0,10.4;2,0.9;back_from_inventory;Back]"
end
-- save the inventory of the NPC with the id n_id
yl_speak_up.save_npc_inventory = function( n_id )
@ -93,6 +38,8 @@ yl_speak_up.save_npc_inventory = function( n_id )
if(not(n_id) or not(yl_speak_up.npc_inventory[ n_id ])) then
return
end
-- the inv was just saved - make sure it is kept in memory a bit
yl_speak_up.npc_inventory_last_used[ n_id ] = os.time()
-- convert the inventory data to something we can actually store
local inv = yl_speak_up.npc_inventory[ n_id ]
local inv_as_table = {}
@ -151,17 +98,87 @@ yl_speak_up.inventory_allow_item = function(player, stack, input_to)
end
-- checks dialog and tries to find out if this dialog needs a detached inventory for the NPC;
-- returns true if the NPC needs one; else false
yl_speak_up.dialog_requires_inventory = function(dialog)
if(not(dialog)) then
return false
end
for t, t_data in pairs(dialog.trades or {}) do
if(t and t ~= "limits") then
return true
end
end
for d_id, d_data in pairs(dialog.n_dialogs or {}) do
for o_id, o_data in pairs(d_data.d_options or {}) do
-- check preconditions:
for p_id, p_data in pairs(o_data.o_prerequisites or {}) do
local t = p_data.p_type or "?"
if(t == "trade" or t == "npc_inv" or t == "player_offered_item") then
return true
end
end
-- check actions:
for a_id, a_data in pairs(o_data.actions or {}) do
local t = a_data.a_type or "?"
if(t == "trade" or t == "npc_gives" or t == "npc_wants") then
return true
end
end
-- check effects:
for r_id, r_data in pairs(o_data.o_results or {}) do
local t = r_data.r_type or "?"
if(t == "block" or t == "craft" or t == "put_into_block_inv"
or t == "take_from_block_inv" or t == "deal_with_offered_item") then
return true
end
end
end
end
-- nothing found that actually uses the NPC's inventory - so don't load it
return false
end
-- create and load the detached inventory in yl_speak_up.after_activate;
-- direct access to this inventory is only possible for players with the right privs
-- (this is an inventory for the *NPC*, which is stored to disk sometimes)
yl_speak_up.load_npc_inventory = function(n_id)
-- if force_load is true, the inventory will be loaded even if the NPC doesn't usually
-- need one (i.e. in edit_mode, or with "show me your inventory").
yl_speak_up.load_npc_inventory = function(n_id, force_load, dialog)
if(not(n_id)) then
return
end
-- clean up no longer needed detached inventories
local recently_used = os.time() - 600 -- used in the last 10 minutes
for id, data in pairs(yl_speak_up.npc_inventory) do
-- not the one for this particular NPC,
if(id and id ~= n_id and data
-- and has not been used recently:
and yl_speak_up.npc_inventory_last_used[id]
and yl_speak_up.npc_inventory_last_used[id] < recently_used
-- and not if anyone is talking to it
and not(yl_speak_up.npc_is_in_conversation(id))) then
-- actually remove that detached inventory:
minetest.remove_detached_inventory("yl_speak_up_npc_"..tostring(id))
-- delete it here as well:
yl_speak_up.npc_inventory[id] = nil
end
end
yl_speak_up.npc_inventory_last_used[ n_id ] = os.time()
-- the inventory is already loaded
if( yl_speak_up.npc_inventory[ n_id ]) then
if(yl_speak_up.npc_inventory[ n_id ]) then
return
end
-- check if the NPC actually needs an inventory - else don't load it
if(not(force_load) and dialog
and not(yl_speak_up.dialog_requires_inventory(dialog))) then
return
end
-- create the detached inventory (it is empty for now)
local npc_inv = minetest.create_detached_inventory("yl_speak_up_npc_"..tostring(n_id), {
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)

69
api/api_logging.lua Normal file
View File

@ -0,0 +1,69 @@
-- handle logging
-- log changes done by players or admins to NPCs
yl_speak_up.log_change = function(pname, n_id, text, log_level)
-- make sure all variables are defined
if(not(pname)) then
pname = "- unkown player -"
end
if(not(n_id)) then
n_id = "- unknown NPC -"
end
if(not(text)) then
text = "- no text given -"
end
if(not(log_level)) then
log_level = "info"
end
-- we don't want newlines in the texts
text = string.gsub(text, "\n", "\\n")
-- log in debug.txt
local log_text = "<"..tostring(n_id).."> ["..tostring(pname).."]: "..text
minetest.log(log_level, "[MOD] yl_speak_up "..log_text)
-- log in a file for each npc so that it can be shown when needed
-- date needs to be inserted manually (minetest.log does it automaticly);
-- each file logs just one npc, so n_id is not important
log_text = tostring(os.date("%Y-%m-%d %H:%M:%S ")..tostring(pname).." "..text.."\n")
n_id = tostring(n_id)
if(n_id and n_id ~= "" and n_id ~= "n_" and n_id ~= "- unkown NPC -") then
-- actually append to the logfile
local file, err = io.open(yl_speak_up.worldpath..yl_speak_up.log_path..DIR_DELIM..
"log_"..tostring(n_id)..".txt", "a")
if err then
minetest.log("error", "[MOD] yl_speak_up Error saving NPC logfile: "..minetest.serialize(err))
return
end
file:write(log_text)
file:close()
end
-- log into a general all-npc-file as well
local file, err = io.open(yl_speak_up.worldpath..yl_speak_up.log_path..DIR_DELIM..
"log_ALL.txt", "a")
if err then
minetest.log("error","[MOD] yl_speak_up Error saving NPC logfile: "..minetest.serialize(err))
return
end
file:write(tostring(n_id).." "..log_text)
file:close()
end
-- this is used by yl_speak_up.eval_and_execute_function(..) in fs_edit_general.lua
yl_speak_up.log_with_position = function(pname, n_id, text, log_level)
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
yl_speak_up.log_change(pname, n_id,
"error: -npc not found- "..tostring(text))
return
end
local obj = yl_speak_up.speak_to[pname].obj
local n_id = yl_speak_up.speak_to[pname].n_id
local pos_str = "-unknown-"
if obj:get_luaentity() and tonumber(npc) then
pos_str = minetest.pos_to_string(obj:get_pos(),0)
end
yl_speak_up.log_change(pname, n_id,
"NPC at position "..pos_str.." "..tostring(text), log_level)
end

View File

@ -120,6 +120,93 @@ yl_speak_up.npc_list_store = function()
end
-- the entries for the "/npc_talk list" NPC list are generally the same for all
-- - except that not all lines are shown to each player and that some
-- lines might be colored diffrently
yl_speak_up.build_cache_general_npc_list_lines = function()
-- small helper function to suppress the display of zeros
local show_if_bigger_null = function(value, do_count)
if(do_count and value) then
local anz = 0
for k, v in pairs(value) do
anz = anz + 1
end
value = anz
end
if(value and value > 0) then
return tostring(value)
else
return ""
end
end
-- the real priv names would be far too long
local short_priv_name = {
precon_exec_lua = 'pX',
effect_exec_lua = 'eX',
effect_give_item = 'eG',
effect_take_item = 'eT',
effect_move_player = 'eM',
}
yl_speak_up.cache_general_npc_list_lines = {}
for k, data in pairs(yl_speak_up.npc_list) do
local data = yl_speak_up.npc_list[k]
local n = (data.name or "- ? -")
if(data.desc and data.desc ~= "") then
n = n..', '..(data.desc or "")
end
-- is the NPC muted?
local npc_color = (yl_speak_up.nametag_color_when_not_muted or '#FFFFFF')
if(data.muted ~= nil and data.muted == false) then
npc_color = (yl_speak_up.nametag_color_when_muted or '#FFFFFF')
end
-- is the NPC loaded?
local is_loaded_color = '#777777'
if(yl_speak_up.npc_list_objects[k]) then
is_loaded_color = '#FFFFFF'
end
-- is it a generic NPC?
local n_id = 'n_'..tostring(k)
local is_generic = ''
if(yl_speak_up.generic_dialogs[n_id]) then
is_generic = 'G'
end
-- does the NPC have extra privs?
local priv_list = ''
if(yl_speak_up.npc_priv_table[n_id]) then
for priv, has_it in pairs(yl_speak_up.npc_priv_table[n_id]) do
priv_list = priv_list..tostring(short_priv_name[priv])..' '
end
end
-- fallback if something went wrong with the position (or it's unknown)
local pos_str = '- unknown -'
if(not(data.pos) or not(data.pos.x) or not(data.pos.y) or not(data.pos.z)) then
data.pos = {x=0, y=0, z=0}
end
pos_str = minetest.formspec_escape(minetest.pos_to_string(data.pos))
yl_speak_up.cache_general_npc_list_lines[k] = {
id = k, -- keep for sorting
is_loaded_color = is_loaded_color,
n_id = n_id,
is_generic = is_generic,
npc_color = npc_color, -- muted or not
-- npc_color is diffrent for each player
n_name = minetest.formspec_escape(n),
owner = minetest.formspec_escape(data.owner or '- ? -'),
is_loaded_color = is_loaded_color,
anz_trades = show_if_bigger_null(#data.trades),
anz_properties = show_if_bigger_null(data.properties, true),
anz_editors = show_if_bigger_null(data.may_edit, true),
pos = pos_str,
priv_list = priv_list,
}
end
end
-- emergency restore NPC that got lost (egg deleted, killed, ...)
yl_speak_up.command_npc_force_restore_npc = function(pname, rest)
if(not(pname)) then
@ -291,356 +378,3 @@ yl_speak_up.command_npc_talk_list = function(pname, rest)
end
-- allow to sort the npc list, display more info on one NPC etc.
yl_speak_up.input_show_npc_list = function(player, formname, fields)
local pname = player:get_player_name()
-- teleport to NPC
if(fields.teleport
and fields.selected_id
and yl_speak_up.cache_npc_list_per_player[pname]
and minetest.check_player_privs(pname, {teleport=true})) then
local id = tonumber(fields.selected_id)
if(not(id) or id < 0
or not(yl_speak_up.npc_list[id])
or table.indexof(yl_speak_up.cache_npc_list_per_player[pname], id) < 1) then
minetest.chat_send_player(pname, "Sorry. Cannot find that NPC.")
return
end
-- try cached position
local pos = yl_speak_up.npc_list[id].pos
local obj = yl_speak_up.npc_list_objects[id]
if(obj) then
pos = obj:get_pos()
end
if(not(pos) or not(pos.x) or not(pos.y) or not(pos.z)) then
pos = yl_speak_up.npc_list[id].pos
end
if(not(pos) or not(pos.x) or not(pos.y) or not(pos.z)) then
minetest.chat_send_player(pname, "Sorry. Cannot find position of that NPC.")
return
end
player:set_pos(pos)
minetest.chat_send_player(pname, "Teleporting to NPC with ID "..
tostring(fields.selected_id)..': '..
tostring(yl_speak_up.npc_list[id].name)..'.')
return
end
-- sort by column or select an NPC
if(fields.show_npc_list) then
local selected = minetest.explode_table_event(fields.show_npc_list)
-- sort by column
if(selected.row == 1) then
local old_sort = yl_speak_up.sort_npc_list_per_player[pname] or 0
-- reverse sort
if(old_sort == selected.column) then
yl_speak_up.sort_npc_list_per_player[pname] = -1 * selected.column
else -- sort by new col
yl_speak_up.sort_npc_list_per_player[pname] = selected.column
end
-- show the update
yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_npc_list",
yl_speak_up.get_fs_show_npc_list(pname, nil))
return
else
-- show details about a specific NPC
yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_npc_list",
yl_speak_up.get_fs_show_npc_list(pname, selected.row))
return
end
end
return
end
-- the entries for the "/npc_talk list" NPC list are generally the same for all
-- - except that not all lines are shown to each player and that some
-- lines might be colored diffrently
yl_speak_up.build_cache_general_npc_list_lines = function()
-- small helper function to suppress the display of zeros
local show_if_bigger_null = function(value, do_count)
if(do_count and value) then
local anz = 0
for k, v in pairs(value) do
anz = anz + 1
end
value = anz
end
if(value and value > 0) then
return tostring(value)
else
return ""
end
end
-- the real priv names would be far too long
local short_priv_name = {
precon_exec_lua = 'pX',
effect_exec_lua = 'eX',
effect_give_item = 'eG',
effect_take_item = 'eT',
effect_move_player = 'eM',
}
yl_speak_up.cache_general_npc_list_lines = {}
for k, data in pairs(yl_speak_up.npc_list) do
local data = yl_speak_up.npc_list[k]
local n = (data.name or "- ? -")
if(data.desc and data.desc ~= "") then
n = n..', '..(data.desc or "")
end
-- is the NPC muted?
local npc_color = (yl_speak_up.nametag_color_when_not_muted or '#FFFFFF')
if(data.muted ~= nil and data.muted == false) then
npc_color = (yl_speak_up.nametag_color_when_muted or '#FFFFFF')
end
-- is the NPC loaded?
local is_loaded_color = '#777777'
if(yl_speak_up.npc_list_objects[k]) then
is_loaded_color = '#FFFFFF'
end
-- is it a generic NPC?
local n_id = 'n_'..tostring(k)
local is_generic = ''
if(yl_speak_up.generic_dialogs[n_id]) then
is_generic = 'G'
end
-- does the NPC have extra privs?
local priv_list = ''
if(yl_speak_up.npc_priv_table[n_id]) then
for priv, has_it in pairs(yl_speak_up.npc_priv_table[n_id]) do
priv_list = priv_list..tostring(short_priv_name[priv])..' '
end
end
-- fallback if something went wrong with the position (or it's unknown)
local pos_str = '- unknown -'
if(not(data.pos) or not(data.pos.x) or not(data.pos.y) or not(data.pos.z)) then
data.pos = {x=0, y=0, z=0}
end
pos_str = minetest.formspec_escape(minetest.pos_to_string(data.pos))
yl_speak_up.cache_general_npc_list_lines[k] = {
id = k, -- keep for sorting
is_loaded_color = is_loaded_color,
n_id = n_id,
is_generic = is_generic,
npc_color = npc_color, -- muted or not
-- npc_color is diffrent for each player
n_name = minetest.formspec_escape(n),
owner = minetest.formspec_escape(data.owner or '- ? -'),
is_loaded_color = is_loaded_color,
anz_trades = show_if_bigger_null(#data.trades),
anz_properties = show_if_bigger_null(data.properties, true),
anz_editors = show_if_bigger_null(data.may_edit, true),
pos = pos_str,
priv_list = priv_list,
}
end
end
-- allow to toggle between trade entries and full log
-- Note: takes pname instead of player(object) as first parameter
yl_speak_up.get_fs_show_npc_list = function(pname, selected_row)
-- which NPC can the player edit?
local level = 0
if( minetest.check_player_privs(pname, {npc_master=true})
or minetest.check_player_privs(pname, {npc_talk_master=true})
or minetest.check_player_privs(pname, {npc_talk_admin=true})) then
level = 2
elseif(minetest.check_player_privs(pname, {npc_talk_owner=true})) then
level = 1
end
if(level < 1) then
return "size[5,1]label[0,0;Error: You do not have the npc_talk_owner priv.]"
end
local formspec_start = 'size[18,14.7]'..
'label[4.5,0.5;List of all NPC (that you can edit)]'..
'tablecolumns[' ..
'color;text,align=right;'.. -- the ID
'color;text,align=center;'.. -- is the NPC a generic one?
'color;text,align=left;'.. -- the name of the NPC
'color;text,align=center;'.. -- the name of the owner of the NPC
'color;text,align=right;'.. -- number of trades offered
'color;text,align=right;'.. -- number of properties set
'color;text,align=right;'.. -- number of people who can edit NPC
'color;text,align=center;'.. -- last known position
'color;text,align=center]'.. -- does he have extra privs?
'table[0.1,1.0;17.8,9.8;show_npc_list;'
-- add information about a specific NPC (selected row)
local info_current_row = ''
if(selected_row
and selected_row > 1
and yl_speak_up.cache_npc_list_per_player[pname]
and yl_speak_up.cache_npc_list_per_player[pname][selected_row-1]) then
local k = yl_speak_up.cache_npc_list_per_player[pname][selected_row-1]
local data = yl_speak_up.npc_list[k]
local line = yl_speak_up.cache_general_npc_list_lines[k]
if(data) then
local edit_list = {data.owner}
if(data.may_edit) then
for e, t in pairs(data.may_edit or {}) do
table.insert(edit_list, e)
end
end
local n_id = 'n_'..tostring(k)
local priv_list = {}
if(yl_speak_up.npc_priv_table[n_id]) then
for priv, has_it in pairs(yl_speak_up.npc_priv_table[n_id]) do
table.insert(priv_list, priv)
end
else
priv_list = {'- none -'}
end
local prop_text = 'label[3.0,2.0;- none -]'
if(data.properties) then
local prop_list = {}
for k, v in pairs(data.properties) do
table.insert(prop_list, minetest.formspec_escape(
tostring(k)..' = '..tostring(v)))
end
if(#prop_list > 0) then
prop_text = 'dropdown[3.0,1.8;8,0.6;properties;'..
table.concat(prop_list, ',')..';;]'
end
end
local first_seen_at = '- unknown -'
if(data.created_at and data.created_at ~= "") then
first_seen_at = minetest.formspec_escape(os.date("%m/%d/%y", data.created_at))
end
-- allow those with teleport priv to easily visit their NPC
local teleport_button = ''
if(minetest.check_player_privs(pname, {teleport=true})) then
-- the ID of the NPC we want to visit is hidden in a field; this is unsafe,
-- but the actual check needs to happen when the teleport button is pressed
-- anyway
teleport_button = 'field[40,40;0,0;selected_id;;'..tostring(k)..']'..
'button_exit[12.1,1.8;5,0.6;teleport;Teleport to this NPC]'
end
info_current_row =
'container[0.1,11.2]'..
'label[0.1,0.0;Name, Desc:]'..
'label[3.0,0.0;'..tostring(line.n_name)..']'..
'label[0.1,0.5;Typ:]'..
'label[3.0,0.5;'..
minetest.formspec_escape(tostring(data.typ or '- ? -'))..']'..
'label[12.1,0.5;First seen at:]'..
'label[14.4,0.5;'..
first_seen_at..']'..
'label[0.1,1.0;Can be edited by:]'..
'label[3.0,1.0;'..
minetest.formspec_escape(table.concat(edit_list, ', '))..']'..
'label[0.1,1.5;Has the privs:]'..
'label[3.0,1.5;'..
minetest.formspec_escape(table.concat(priv_list, ', '))..']'..
'label[0.1,2.0;Properties:]'..
prop_text..
teleport_button..
'container_end[]'
end
else
selected_row = 1
info_current_row = 'label[0.1,11.2;Click on a column name/header in order to sort by '..
'that column. Click it again in order to reverse sort order.\n'..
'Click on a row to get more information about a specific NPC.\n'..
'Only NPC that can be edited by you are shown.\n'..
'Legend: \"G\": is generic NPC. '..
'\"#Tr\", \"#Pr\": Number of trades or properties the NPC offers.\n'..
' \"#Ed\": Number of players that can edit the NPC. '..
'\"Privs\": List of abbreviated names of privs the NPC has.]'
end
local formspec = {}
-- TODO: blocks may also be talked to
local tmp_liste = {}
for k, v in pairs(yl_speak_up.npc_list) do
if(level == 2
or (v.owner and v.owner == pname)
or (v.may_edit and v.may_edit[pname])) then
table.insert(tmp_liste, k)
end
end
-- the columns with the colors count as well even though they can't be selected
-- (don't sort the first column by n_<id> STRING - sort by <id> NUMBER)
local col_names = {"id", "id", "is_generic", "is_generic", "n_name", "n_name",
"owner", "owner", "anz_trades", "anz_trades",
"anz_properties", "anz_properties", "anz_editors", "anz_editors",
"pos", "pos", "priv_list", "priv_list"}
local sort_col = yl_speak_up.sort_npc_list_per_player[pname]
if(not(sort_col) or sort_col == 0) then
table.sort(tmp_liste)
elseif(sort_col > 0) then
-- it is often more helpful to sort in descending order
local col_name = col_names[sort_col]
table.sort(tmp_liste, function(a, b)
return yl_speak_up.cache_general_npc_list_lines[a][col_name]
> yl_speak_up.cache_general_npc_list_lines[b][col_name]
end)
else
local col_name = col_names[sort_col * -1]
table.sort(tmp_liste, function(a, b)
return yl_speak_up.cache_general_npc_list_lines[a][col_name]
< yl_speak_up.cache_general_npc_list_lines[b][col_name]
end)
end
local col_headers = {'n_id', 'G', 'Name', 'Owner', '#Tr', '#Pr', '#Ed', 'Position', 'Privs'}
for i, k in ipairs(col_headers) do
if( sort_col and sort_col == (i * 2)) then
table.insert(formspec, 'yellow')
table.insert(formspec, 'v '..k..' v')
elseif(sort_col and sort_col == (i * -2)) then
table.insert(formspec, 'yellow')
table.insert(formspec, '^ '..k..' ^')
else
table.insert(formspec, '#FFFFFF')
table.insert(formspec, k)
end
end
yl_speak_up.cache_npc_list_per_player[pname] = tmp_liste
for i, k in ipairs(tmp_liste) do
local data = yl_speak_up.npc_list[k]
local line = yl_speak_up.cache_general_npc_list_lines[k]
-- own NPC are colored green, others white
local owner_color = '#FFFFFF'
if(data.owner == pname) then
owner_color = '#00FF00'
elseif (data.may_edit and data.may_edit[pname]) then
owner_color = '#FFFF00'
end
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.n_id)
table.insert(formspec, 'orange')
table.insert(formspec, line.is_generic)
table.insert(formspec, line.npc_color)
table.insert(formspec, line.n_name)
table.insert(formspec, owner_color) -- diffrent for each player
table.insert(formspec, line.owner)
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.anz_trades)
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.anz_properties)
table.insert(formspec, owner_color) -- diffrent for each player
table.insert(formspec, line.anz_editors)
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.pos)
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.priv_list)
end
table.insert(formspec, ";"..selected_row.."]")
return table.concat({formspec_start,
table.concat(formspec, ','),
info_current_row,
'button_exit[0.1,14;19.6,0.6;exit;Exit]'}, '')
end
-- at load/reload of the mod: read the list of existing NPC
yl_speak_up.npc_list_load()

109
api/api_properties.lua Normal file
View File

@ -0,0 +1,109 @@
-- handle properties
-- Properties for NPC --
-- This is used when an NPC doesn't have a specific dialog but still wants to
-- make use of a (or some) generic dialog(es)
-- helper function:
-- get one property value of the NPC
yl_speak_up.get_one_npc_property = function(pname, property_name)
if(not(pname)) then
return nil
end
-- get just the property data
return yl_speak_up.get_npc_properties(pname, false)[property_name]
end
-- helper function;
-- adds "normal" properties of the npc with a self.<property_name> prefix as well
-- if long_version is not set, a table containing all properties is returned;
-- if long_version *is* set, a table containing the table above plus additional entries is returned
yl_speak_up.get_npc_properties_long_version = function(pname, long_version)
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return {}
end
local obj = yl_speak_up.speak_to[pname].obj
if(not(obj)) then
return {}
end
local entity = obj:get_luaentity()
if(not(entity)) then
return {}
end
if(not(entity.yl_speak_up)) then
return {}
end
local properties = entity.yl_speak_up.properties
if(not(properties)) then
properties = {}
entity.yl_speak_up.properties = properties
end
-- copy other property data that is stored under self.* over as well (like i.e. self.order for mobs_redo)
for k, v in pairs(entity) do
local t = type(v)
if(t == "string" or t == "number" or t == "boolean") then
properties["self."..tostring(k)] = tostring(v)
end
end
properties["self.name"] = tostring(entity.name)
if(not(long_version)) then
return properties
end
-- the long version contains additional information
local prop_names = {}
for k, v in pairs(properties) do
table.insert(prop_names, k)
end
table.sort(prop_names)
return {obj = obj, entity = entity, properties = properties, prop_names = prop_names}
end
-- most of the time we don't need object, entity or a list of the names of properties;
-- this returns just the properties themshelves
yl_speak_up.get_npc_properties = function(pname)
return yl_speak_up.get_npc_properties_long_version(pname, false)
end
yl_speak_up.set_npc_property = function(pname, property_name, property_value, reason)
if(not(pname) or not(property_name) or property_name == "") then
return "No player name or property name given. Cannot load property data."
end
-- here we want a table with additional information
local property_data = yl_speak_up.get_npc_properties_long_version(pname, true)
if(not(property_data)) then
return "Failed to load property data of NPC."
end
-- it is possible to react to property changes with special custom handlers
if(yl_speak_up.custom_property_handler[property_name]) then
-- the table contains the pointer to a fucntion
local fun = yl_speak_up.custom_property_handler[property_name]
-- call that function with the current values
return fun(pname, property_name, property_value, property_data)
end
-- properties of type self. are not set directly
if(string.sub(property_name, 1, 5) == "self.") then
return "Properties of the type \"self.\" cannot be modified."
end
-- properites starting with "server" can only be changed or added manually by
-- players with the npc_talk_admin priv
if(string.sub(property_name, 1, 6) == "server") then
if(not(reason) or reason ~= "manually" or not(pname)
or not(minetest.check_player_privs(pname, {npc_talk_admin=true}))) then
return "Properties starting with \"server\" can only be changed by players "..
"who have the \"npc_talk_admin\" priv."
end
end
-- store it
if(property_data.entity) then
property_data.entity.yl_speak_up.properties[property_name] = property_value
local n_id = yl_speak_up.speak_to[pname].n_id
yl_speak_up.log_change(pname, n_id, "Property \""..tostring(property_name)..
"\" set to \""..tostring(property_value).."\".")
end
-- TODO: handle non-npc (blocks etc)
return "OK"
end

55
api/api_talk.lua Normal file
View File

@ -0,0 +1,55 @@
yl_speak_up.stop_talking = function(pname)
if(not(pname)) then
return
end
yl_speak_up.reset_vars_for_player(pname, nil)
minetest.close_formspec(pname, "yl_speak_up:talk")
end
-- count visits to this dialog - but *not* for generic dialogs as those are just linked and not
-- copied for each player; also not in edit_mode as it makes no sense there
yl_speak_up.count_visits_to_dialog = function(pname)
if(not(pname)) then
return
end
local d_id = yl_speak_up.speak_to[pname].d_id
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(d_id) or not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])) then
return
end
if(not(dialog.n_dialogs[d_id].is_generic)) then
if(not(dialog.n_dialogs[d_id].visits)) then
dialog.n_dialogs[d_id].visits = 0
end
dialog.n_dialogs[d_id].visits = dialog.n_dialogs[d_id].visits + 1
end
end
-- count visits to options - but *not* for generic dialogs as those are just linked and not
-- copied for each player;
-- called after all effects have been executed successfully
-- not called in edit_mode because effects are not executed there
yl_speak_up.count_visits_to_option = function(pname, o_id)
if(not(pname)) then
return
end
local d_id = yl_speak_up.speak_to[pname].d_id
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(d_id) or not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])
or not(o_id)
or not(dialog.n_dialogs[d_id].d_options)
or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return
end
local o_data = dialog.n_dialogs[d_id].d_options[o_id]
if(not(o_data.is_generic)) then
if(not(o_data.visits)) then
o_data.visits = 0
end
o_data.visits = o_data.visits + 1
end
end

195
api/api_trade.lua Normal file
View File

@ -0,0 +1,195 @@
-----------------------------------------------------------------------------
-- limits for trading: maximum and minimum stock to keep
-----------------------------------------------------------------------------
-- sometimes players may not want the NPC to sell *all* of their stock,
-- or not let the NPC buy endless amounts of something when only a limited
-- amount is needed
-----------------------------------------------------------------------------
-- helper function: make sure all necessary entries in the trades table exist
yl_speak_up.setup_trade_limits = function(dialog)
if(not(dialog)) then
dialog = {}
end
if(not(dialog.trades)) then
dialog.trades = {}
end
if(not(dialog.trades.limits)) then
dialog.trades.limits = {}
end
if(not(dialog.trades.limits.sell_if_more)) then
dialog.trades.limits.sell_if_more = {}
end
if(not(dialog.trades.limits.buy_if_less)) then
dialog.trades.limits.buy_if_less = {}
end
return dialog
end
-- helper function: count how many items the NPC has in his inventory
-- empty stacks are counted under the key "";
-- for other items, the amount of items of each type is counted
yl_speak_up.count_npc_inv = function(n_id)
if(not(n_id)) then
return {}
end
-- the NPC's inventory
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)})
if(not(npc_inv)) then
return {}
end
local anz = npc_inv:get_size('npc_main')
local stored = {}
for i=1, anz do
local stack = npc_inv:get_stack('npc_main', i )
local name = stack:get_name()
local count = stack:get_count()
-- count empty stacks
if(name=="") then
count = 1
end
-- count how much of each item is there
if(not(stored[ name ])) then
stored[ name ] = count
else
stored[ name ] = stored[ name ] + count
end
end
return stored
end
-- helper function: update the items table so that it reflects a limitation
-- items is a table (list) with these entries:
-- [1] 0 in stock;
-- [2] sell if more than 0;
-- [3] buy if less than 10000;
-- [4] item is part of a trade offer
yl_speak_up.insert_trade_item_limitation = function( items, k, i, v )
if( i<1 or i>4) then
return;
end
if( not( items[ k ] )) then
-- 0 in stock; sell if more than 0; buy if less than 10000; item is part of a trade offer
items[ k ] = { 0, 0, 10000, false, #items }
end
items[ k ][ i ] = v
end
-- helper function; returns how often a trade can be done
-- stock_buy how much of the buy stack does the NPC have in storage?
-- stock_pay how much of the price stack does the NPC have in storage?
-- buy_stack stack containing the item the NPC sells
-- pay_stack stack containing the price for said item
-- min_storage how many items of the buy stack items shall the NPC keep?
-- max_storage how many items of the pay stack items can the NPC accept?
-- used in fs_trade_via_buy_button.lua and fs_trade_list.lua
yl_speak_up.get_trade_amount_available = function(stock_buy, stock_pay, buy_stack, pay_stack, min_storage, max_storage)
local stock = 0
-- the NPC shall not sell more than this
if(min_storage and min_storage > 0) then
stock_buy = math.max(0, stock_buy - min_storage)
end
stock = math.floor(stock_buy / buy_stack:get_count())
-- the NPC shall not buy more than this
if(max_storage and max_storage < 10000) then
stock_pay = math.min(max_storage - stock_pay, 10000)
stock = math.min(stock, math.floor(stock_pay / pay_stack:get_count()))
end
return stock
end
-- helper function; also used by fs_trade_list.lua
yl_speak_up.get_sorted_trade_id_list = function(dialog, show_dialog_option_trades)
-- make sure all fields exist
yl_speak_up.setup_trade_limits(dialog)
local keys = {}
if(show_dialog_option_trades) then
for k, v in pairs(dialog.trades) do
if(k ~= "limits" and k ~= "" and v.d_id) then
table.insert(keys, k)
end
end
else
for k, v in pairs(dialog.trades) do
if(k ~= "limits" and k ~= "") then
-- structure of the indices: sell name amount for name amount
local parts = string.split(k, " ")
if(parts and #parts == 6 and parts[4] == "for"
and v.pay and v.pay[1] ~= "" and v.pay[1] == parts[5].." "..parts[6]
and v.buy and v.buy[1] ~= "" and v.buy[1] == parts[2].." "..parts[3]
and minetest.registered_items[parts[5]]
and minetest.registered_items[parts[2]]
and tonumber(parts[6]) > 0
and tonumber(parts[3]) > 0) then
table.insert(keys, k)
end
end
end
end
table.sort(keys)
return keys
end
-- taken from trade_simple.lua:
-- helper function for
-- yl_speak_up.input_do_trade_simple (here) and
-- yl_speak_up.input_trade_via_buy_button (in fs_trade_via_buy_button.lua)
--
-- delete a trade; this can be done here only if..
-- * it is a trade from the trade list (not an effect of a dialog option)
-- * it is a trade associated with a dialog option and the player is in
-- edit mode
-- * the player has the necessary privs
-- This option is available without having to enter edit mode first.
yl_speak_up.delete_trade_simple = function(player, trade_id)
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
-- not a really helpful message - but then, this should never happen (player probably cheated)
return yl_speak_up.trade_fail_msg
end
-- get the necessary dialog data
local dialog = yl_speak_up.speak_to[pname].dialog
-- store d_id and o_id in order to be able to return to the right
-- edit options dialog
local back_to_d_id = nil
local back_to_o_id = nil
if(dialog and dialog.trades and trade_id
and dialog.trades[ trade_id ] and n_id) then
-- Note: That the trade cannot be deleted outside edit mode if it is the action
-- belonging to an option is checked in editor/trade_*.lua
if( dialog.trades[ trade_id ].d_id ) then
back_to_d_id = dialog.trades[ trade_id ].d_id
back_to_o_id = dialog.trades[ trade_id ].o_id
end
-- log the change
yl_speak_up.log_change(pname, n_id,
"Trade: Deleted offer "..tostring(trade_id)..".")
-- delete this particular trade
dialog.trades[ trade_id ] = nil
-- actually save the dialog to disk
yl_speak_up.save_dialog(n_id, dialog)
-- we are done with this trade
yl_speak_up.trade[pname] = nil
yl_speak_up.speak_to[pname].trade_id = nil
yl_speak_up.speak_to[pname].trade_done = nil
end
-- always return to edit options dialog if deleting a trade that belonged to one
if(back_to_d_id and back_to_o_id) then
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = back_to_d_id, o_id = back_to_o_id})
return
end
-- go back showing the trade list (since we deleted this trade)
yl_speak_up.show_fs(player, "trade_list")
return
end

245
api/api_trade_inv.lua Normal file
View File

@ -0,0 +1,245 @@
-- functions for handling the detached trade inventory of players
-- these functions exist as extra functions so that they can be changed with /npc_talk_reload
-- can this trade be made? called in allow_take
yl_speak_up.can_trade_simple = function(player, count)
if(not(player)) then
return 0
end
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
-- do we have all the necessary data?
if(not(trade) or trade.trade_type ~= "trade_simple") then
return 0
end
-- the player tries to take *less* items than what his payment is;
-- avoid this confusion!
if(ItemStack(trade.npc_gives):get_count() ~= count) then
return 0
end
-- buy, sell and config items need to be placed somewhere
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
-- the players' inventory
local player_inv = player:get_inventory()
-- the NPCs' inventory
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(trade.n_id)})
-- is the payment in the payment slot?
if( not(trade_inv:contains_item("pay", trade.player_gives))
-- is the item to be sold in the buy slot?
or not(trade_inv:contains_item("buy", trade.npc_gives))
-- has the NPC room for the payment?
or not(npc_inv:room_for_item("npc_main", trade.player_gives))
-- has the player room for the sold item?
or not(player_inv:room_for_item("main", trade.npc_gives))) then
-- trade not possible
return 0
end
-- used items cannot be sold as there is no fair way to indicate how
-- much they are used
if( trade_inv:get_stack("pay", 1):get_wear() > 0) then
return 0
end
-- all ok; all items that are to be sold can be taken
return ItemStack(trade.npc_gives):get_count()
end
-- actually execute the trade
yl_speak_up.do_trade_simple = function(player, count)
-- can the trade be made?
if(not(yl_speak_up.can_trade_simple(player, count))) then
return
end
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
-- buy, sell and config items need to be placed somewhere
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
-- the NPCs' inventory
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(trade.n_id)})
-- the NPC sells these items right now, and the player is moving it to his inventory
npc_inv:remove_item("npc_main", trade.npc_gives)
-- move price items to the NPC
local stack = trade_inv:remove_item("pay", trade.player_gives)
npc_inv:add_item("npc_main", stack)
-- save the inventory of the npc so that the payment does not get lost
yl_speak_up.save_npc_inventory( trade.n_id )
-- store for statistics how many times the player has executed this trade
-- (this is also necessary to switch to the right target dialog when
-- dealing with dialog options trades)
yl_speak_up.trade[pname].trade_done = yl_speak_up.trade[pname].trade_done + 1
-- log the trade
yl_speak_up.log_change(pname, trade.n_id,
"bought "..tostring(trade.npc_gives)..
" for "..tostring(trade.player_gives))
end
-- moving of items between diffrent lists is not allowed
yl_speak_up.trade_inv_allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
if(not(player)) then
return 0
end
if(from_list ~= to_list) then
return 0
end
return count
end
-- these all require calling special functions, depending on context
yl_speak_up.trade_inv_allow_put = function(inv, listname, index, stack, player)
if(not(player)) then
return 0
end
-- the "buy" slot is managed by the NPC; the player only takes from it
if(listname == "buy") then
return 0
end
-- do not allow used items or items with metadata in the setup slots
-- (they can't really be traded later on anyway)
if(listname == "setup") then
-- check if player can edit NPC, item is undammaged and contains no metadata
return yl_speak_up.inventory_allow_item(player, stack,
"yl_speak_up:add_trade_simple")
end
-- allow putting something in in edit mode - but not otherwise
if(listname == "npc_gives") then
return 0
end
return stack:get_count()
end
yl_speak_up.trade_inv_allow_take = function(inv, listname, index, stack, player)
if(not(player)) then
return 0
end
-- can the trade be made?
if(listname == "buy") then
return yl_speak_up.can_trade_simple(player, stack:get_count())
end
return stack:get_count()
end
yl_speak_up.trade_inv_on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
end
yl_speak_up.trade_inv_on_put = function(inv, listname, index, stack, player)
if(listname == "pay") then
local pname = player:get_player_name()
-- show formspec with updated information (perhaps sale is now possible)
yl_speak_up.show_fs(player, "trade_simple")
elseif(listname == "npc_gives"
or listname == "npc_wants") then
-- monitor changes in order to adjust the formspec
yl_speak_up.action_inv_changed(inv, listname, index, stack, player, "put")
end
end
yl_speak_up.trade_inv_on_take = function(inv, listname, index, stack, player)
-- the player may have put something wrong in the payment slot
-- -> show updated formspec
if(listname == "pay") then
local pname = player:get_player_name()
-- show formspec with updated information (perhaps sale is now possible)
yl_speak_up.show_fs(player, "trade_simple")
elseif(listname == "buy") then
-- do the exchange
yl_speak_up.do_trade_simple(player, stack:get_count())
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
-- when the player traded once inside an action: that action was a success;
-- execute next action
-- but only if not in edit mode
if(trade and trade.trade_done > 0
and not(trade.trade_is_trade_list)
and not(trade.dry_run_no_exec)) then
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
-- return surplus items from the pay slot
local pay = trade_inv:get_stack("pay", 1)
local player_inv = player:get_inventory()
if( pay and player_inv:room_for_item("main", pay)) then
player_inv:add_item("main", pay)
trade_inv:set_stack("pay", 1, "")
end
-- done trading
yl_speak_up.speak_to[pname].target_d_id = nil
yl_speak_up.speak_to[pname].trade_id = nil
-- execute the next action
yl_speak_up.execute_next_action(player, trade.a_id, true, "yl_speak_up:trade_simple")
return
end
-- information may require an update (NPC might now be out of stock), or
-- the player can do the trade a second time
yl_speak_up.show_fs(player, "trade_simple")
elseif(listname == "npc_gives"
or listname == "npc_wants") then
-- monitor changes in order to adjust the formspec
yl_speak_up.action_inv_changed(inv, listname, index, stack, player, "take")
end
end
-- create a detached inventory for the *player* for trading with the npcs
-- (called in minetest.register_on_joinplayer)
yl_speak_up.player_joined_add_trade_inv = function(player, last_login)
local pname = player:get_player_name()
-- create the detached inventory;
-- the functions for monitoring changes will be important later on
-- only the the player owning this detached inventory may access it
local trade_inv = minetest.create_detached_inventory("yl_speak_up_player_"..tostring(pname), {
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return yl_speak_up.trade_inv_allow_move(inv, from_list, from_index, to_list,
to_index, count, player)
end,
allow_put = function(inv, listname, index, stack, player)
return yl_speak_up.trade_inv_allow_put(inv, listname, index, stack, player)
end,
allow_take = function(inv, listname, index, stack, player)
return yl_speak_up.trade_inv_allow_take(inv, listname, index, stack, player)
end,
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
return yl_speak_up.trade_inv_on_move(inv, from_list, from_index, to_list,
to_index, count, player)
end,
on_put = function(inv, listname, index, stack, player)
return yl_speak_up.trade_inv_on_put(inv, listname, index, stack, player)
end,
on_take = function(inv, listname, index, stack, player)
return yl_speak_up.trade_inv_on_take(inv, listname, index, stack, player)
end,
-- create the detached inventory only for that player (don't spam other clients with it):
}, tostring(pname))
-- prepare the actual inventories
trade_inv:set_size("pay", 1)
trade_inv:set_size("buy", 1)
-- for setting up new simple trades
trade_inv:set_size("setup", 2*1)
-- for setting up actions
trade_inv:set_size("npc_gives", 1)
trade_inv:set_size("npc_wants", 1)
-- for setting wielded items (left and right)
trade_inv:set_size("wield", 2)
end
yl_speak_up.player_left_remove_trade_inv = function(player)
if(not(player)) then
return
end
local pname = player:get_player_name()
minetest.remove_detached_inventory("yl_speak_up_player_"..tostring(pname))
end

View File

@ -24,6 +24,33 @@ yl_speak_up.replace_vars_in_text = function(text, dialog, pname)
PLAYER_NAME = pname,
}
if(not(text) or text == "") then
return ""
end
-- only try to replace variables if there are variables inside the text
if(string.find(text, "$VAR ")) then
local varlist = yl_speak_up.get_quest_variables(dialog.npc_owner, true)
for i,v in ipairs(varlist) do
local v_name = string.sub(v, 3)
-- only allow to replace unproblematic variable names
if(not(string.find(v_name, "[^%w^%s^_^%-^%.]"))) then
-- remove leading $ from $ var_owner_name var_name
subs["VAR "..v_name] = yl_speak_up.get_quest_variable_value(pname, v) or "- not set -"
end
end
end
-- only replace properties if any properties are used inside the text
if(string.find(text, "$PROP ")) then
local properties = yl_speak_up.get_npc_properties(pname)
for k,v in pairs(properties) do
-- only allow to replace unproblematic property names
if(not(string.find(k, "[^%w^%s^_^%-^%.]"))) then
subs["PROP "..k] = v
end
end
end
local day_time_name = "day"
local day_time = minetest.get_timeofday()
if(day_time < 0.5) then
@ -41,7 +68,9 @@ yl_speak_up.replace_vars_in_text = function(text, dialog, pname)
-- substitutions in it using substring captured by "()" in
-- pattern. "[%a_]+" means one or more letter or underscore.
-- If lookup returns nil, then no substitution is made.
text = string.gsub(text or "", "%$([%a_]+)%$", subs)
-- Note: Names of variables may contain alphanumeric signs, spaces, "_", "-" and ".".
-- Variables with other names cannot be replaced.
text = string.gsub(text or "", "%$([%w%s_%-%.]+)%$", subs)
return text
end
@ -254,6 +283,59 @@ yl_speak_up.custom_functions_p_[ "text_contained_these_words" ] = {
}
yl_speak_up.custom_functions_p_[ "counted_visits_to_dialog" ] = {
description = "counted dialog visits: "..
"How many times has the player visited/seen this dialog during this talk?",
param1_text = "Name of dialog (i.e. \"d_1\"):",
param1_desc = "Enter the dialog ID of the dialog for which you want to get the amount of "..
"visits. If the dialog does not exist, -1 is returned.",
code = function(player, n_id, p)
local pname = player:get_player_name()
if(not(pname)) then
return -1
end
local dialog = yl_speak_up.speak_to[pname].dialog
local d_id = p["p_param1"]
if(not(yl_speak_up.check_if_dialog_exists(dialog, d_id))) then
return -1
end
local visits = dialog.n_dialogs[d_id].visits
if(not(visits)) then
return 0
end
return visits
end,
}
yl_speak_up.custom_functions_p_[ "counted_visits_to_option" ] = {
description = "counted dialog option/answers visits: "..
"How many times has the player visited/seen this dialog *option* during this talk?",
param1_text = "Name of dialog (i.e. \"d_1\"):",
param1_desc = "Enter the dialog ID of the dialog the option belongs to.",
param2_text = "Name of option (i.e. \"o_2\"):",
param2_desc = "Enter the option ID of the dialog for which you want to get the amount of "..
"visits. If the option does not exist, -1 is returned.",
code = function(player, n_id, p)
local pname = player:get_player_name()
if(not(pname)) then
return -1
end
local dialog = yl_speak_up.speak_to[pname].dialog
local d_id = p["p_param1"]
local o_id = p["p_param2"]
if(not(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o_id))) then
return -1
end
local visits = dialog.n_dialogs[d_id].d_options[o_id].visits
if(not(visits)) then
return 0
end
return visits
end,
}
-----------------------------------------------------------------------------
-- Custom actions (of type "evaluate")
-----------------------------------------------------------------------------
@ -432,6 +514,9 @@ yl_speak_up.custom_functions_r_[ "set_variable_to_random_number" ] = {
-- set the value of the variable
local n1 = tonumber(r.r_param2)
local n2 = tonumber(r.r_param3)
if(n1 == nil or n2 == nil) then
return false
end
if(n2 < n1) then
local tmp = n1
n1 = n2
@ -451,6 +536,7 @@ yl_speak_up.custom_functions_r_[ "set_variable_to_random_number" ] = {
end
}
yl_speak_up.custom_functions_r_[ "set_variable_to_random_value_from_list" ] = {
description = "Set a variable to a random value from a list.",
param1_text = "Name of the variable:",
@ -481,6 +567,136 @@ yl_speak_up.custom_functions_r_[ "set_variable_to_random_value_from_list" ] = {
end
}
yl_speak_up.custom_functions_r_[ "var0_eq_var1_op_var2" ] = {
description = "Set a <variable0> to the value of <variable1> <operator> <variable2>.",
param1_text = "Name of the variable0 to set:",
param1_desc = "Which variable do you want to set to the new value?",
param2_text = "Name of variable1:",
param2_desc = "Which variable holds the value of the first operand?",
param3_text = "Operand (+ - * / % ^ and or):",
param3_desc = "Choose operand: Add, subtract, multiply, divide, modulo, exponent, and, or.",
param4_text = "Name of variable2:",
param4_desc = "Which variable holds the value of the second operand?",
code = function(player, n_id, r)
if(not(r) or not(r.r_param1) or r.r_param1 == ""
or not(r.r_param2) or r.r_param2 == ""
or not(r.r_param3) or r.r_param3 == ""
or not(r.r_param4) or r.r_param4 == "") then
return false
end
-- the owner is already encoded in the variable name
local pname = player:get_player_name()
local owner = yl_speak_up.npc_owner[ n_id ]
local prefix = "$ "..tostring(owner).." "
local v_1 = yl_speak_up.get_quest_variable_value(pname, prefix..r.r_param2)
local v_2 = yl_speak_up.get_quest_variable_value(pname, prefix..r.r_param4)
local new_value = nil
local op = r.r_param3
if(op == 'and') then
new_value = v_1 and v_2
elseif(op == 'or') then
new_value = v_1 or v_2
else
v_1 = tonumber(v_1)
v_2 = tonumber(v_2)
if(v_1 == nil or v_2 == nil) then
new_value = nil
elseif(op == '+') then
new_value = v_1 + v_2
elseif(op == '-') then
new_value = v_1 - v_2
elseif(op == '*') then
new_value = v_1 * v_2
elseif(op == '/') then
if(v_2 ~= 0) then
new_value = v_1 / v_2
else
new_value = nil
end
elseif(op == '%') then
if(v_2 ~= 0) then
new_value = v_1 % v_2
else
new_value = nil
end
elseif(op == '^') then
new_value = v_1 ^ v_2
end
end
if(new_value == nil) then
return false
end
local ret = yl_speak_up.set_quest_variable_value(pname, prefix..r.r_param1, new_value)
local o_id = yl_speak_up.speak_to[pname].o_id or "?"
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
"state: Success: "..tostring(ret).." for setting "..tostring(r.r_param1).." to "..
tostring(new_value)..".")
return ret
end
}
yl_speak_up.custom_functions_r_[ "play_sound" ] = {
description = "Plays a sound.",
param1_text = "Name of the sound(file):",
param1_desc = "How is the sound(file) called?\n"..
"It has to be provided by another mod.\n"..
"Example: default_dirt_footstep",
param2_text = "Gain:",
param2_desc = "Number between 1 and 10. Default: 10.",
param3_text = "Pitch:",
param3_desc = "Applies a pitch-shift to the sound.\n"..
"Each factor of 20 results in a pitch-shift of +12 semitones.\n"..
"Default is 10.",
param4_text = "Max hear distance:",
param4_desc = "Default: 32 m. Can be set to a value between 3 and 64.",
code = function(player, n_id, r)
if(not(r) or not(r.r_param1) or r.r_param1 == "") then
return false
end
if(not(player)) then
return false
end
local pname = player:get_player_name()
sound_param = {}
if(r.r_param2 and r.r_param2 ~= "") then
sound_param.gain = math.floor(tonumber(r.r_param2) or 0)
if(sound_param.gain < 1 or sound_param.gain > 10) then
sound_param.gain = 10
end
sound_param.gain = sound_param.gain / 10
end
if(r.r_param3 and r.r_param3 ~= "") then
sound_param.pitch = math.floor(tonumber(r.r_pitch) or 0)
if(sound_param.pitch < 1) then
sound_param.pitch = 10
end
sound_param.pitch = sound_param.pitch / 10
end
if(r.r_param4 and r.r_param4 ~= "") then
sound_param.max_hear_distance = math.min(tonumber(r.r_pitch) or 1, 32)
if(sound_param.max_hear_distance < 3) then
sound_param.max_hear_distance = 3
end
if(sound_param.max_hear_distance > 64) then
sound_param.max_hear_distance = 64
end
end
sound_param.loop = false
-- located at the NPC
sound_param.object = yl_speak_up.speak_to[pname].obj
if(not(sound_param.object)) then
return false
end
-- actually play the sound
core.sound_play(r.r_param1, sound_param, true)
return true
end
}
-----------------------------------------------------------------------------
-- Custom handling of special properties
-----------------------------------------------------------------------------

View File

@ -1,5 +1,8 @@
-- Implementation of chat commands that were registered in register_once
-- this is a way to provide additional help if a mod adds further commands (like the editor)
yl_speak_up.add_to_command_help_text = ""
-- general function for handling talking to the NPC
yl_speak_up.command_npc_talk = function(pname, param)
if(not(pname)) then
@ -12,16 +15,17 @@ yl_speak_up.command_npc_talk = function(pname, param)
if( cmd and cmd == "style") then
-- implemented in fs_decorated.lua:
return yl_speak_up.command_npc_talk_style(pname, rest)
-- show formspec with list of NPC controlled by the player
elseif(cmd and cmd == "list") then
return yl_speak_up.command_npc_talk_list(pname, rest)
-- show the version of the mod
elseif(cmd and cmd == "version") then
minetest.chat_send_player(pname, "Version of yl_speak_up: "..tostring(yl_speak_up.version))
return
-- debug mode only makes sense if the player can edit that NPC; the command checks for this
elseif(cmd and cmd == "debug") then
-- implemented in npc_talk_debug.lua:
return yl_speak_up.command_npc_talk_debug(pname, rest)
-- activates edit mode when talking to an NPC; but only if the player can edit that NPC
elseif(cmd and cmd == "force_edit") then
-- implemented in functions.lua:
return yl_speak_up.command_npc_talk_force_edit(pname, rest)
-- managing generic NPC requires npc_talk_admin priv
elseif(cmd and cmd == "generic") then
@ -44,14 +48,7 @@ yl_speak_up.command_npc_talk = function(pname, param)
-- implemented in fs_npc_list.lua:
return yl_speak_up.command_npc_force_restore_npc(pname, rest)
elseif(cmd and cmd == "privs") then
-- TODO: make this available for npc_talk_admin?
if(not(minetest.check_player_privs(pname, {privs = true}))) then
minetest.chat_send_player(pname, "This command is used for managing "..
"privs (like execute lua, teleportation, giving items...) for NPC. "..
"You lack the \"privs\" priv required to "..
"run this command.")
return
end
-- the command now checks for player privs
-- implemented in npc_privs.lua:
return yl_speak_up.command_npc_talk_privs(pname, rest)
end
@ -61,13 +58,14 @@ yl_speak_up.command_npc_talk = function(pname, param)
"Usage: \"/npc_talk <command>\" with <command> beeing:\n"..
" help this help here\n"..
" style display talk menu in a way better suited for very old versions of MT\n"..
" version show human-readable version information\n"..
" list shows a list of NPC that you can edit\n"..
" debug debug a particular NPC\n"..
" force_edit forces edit mode for any NPC you talk to\n"..
" generic [requores npc_talk_admin priv] list, add or remove NPC as generic NPC\n"..
" privs list, grant or revoke privs for your NPC\n"..
" generic [requires npc_talk_admin priv] list, add or remove NPC as generic NPC\n"..
" force_restore_npc [requires npc_talk_admin priv] restore NPC that got lost\n"..
" privs [requires privs priv] list, grant or revoke privs for an NPC\n"..
-- reload is fully handled in register_once
"Note: /npc_talk_reload [requires privs priv] reloads the code of the mod without server "..
"restart.")
"restart."..
yl_speak_up.add_to_command_help_text)
end

View File

@ -9,13 +9,58 @@
--
-- So please use a seperate config mod!
-- Do the NPCs talk right after they spawned?
-- Do the NPCs talk right after they spawned? Change this only if you
-- want to globally prevent NPC from talking.
yl_speak_up.talk_after_spawn = true
------------------------------------------------------------------------------
-- Config values you can adjust
------------------------------------------------------------------------------
-- how many buttons will be shown simultaneously without having to scroll?
-- Changing this value might not be a too good idea as it might break
-- formspecs.
yl_speak_up.max_number_of_buttons = 7
-- how many buttons can be added to one dialog?
yl_speak_up.max_number_of_options_per_dialog = 15
-- how many rows and cols shall be used for the trade overview list?
yl_speak_up.trade_max_rows = 10
-- change the rows above as needed, but do not increase the cols
-- above 12 (there is no room for more)
yl_speak_up.trade_max_cols = 12
-- how many prerequirements can the player define per dialog option?
yl_speak_up.max_prerequirements = 12
-- how many actions can there be per dialog option?
-- for now, more than one doesn't make sense
yl_speak_up.max_actions = 1
-- how many effects can the player define per dialog option?
yl_speak_up.max_result_effects = 6
-- An option may be choosen automaticly without the player having to click if all of its
-- preconditions are true and the mode is set to automatic. Now, if the choosen target
-- dialog has an option that also uses this automatic mode, infinite loops might be
-- created. This option exists to avoid them. Any small value will do.
yl_speak_up.max_allowed_recursion_depth = 5
-- nametag colors based on the NPC's health are a bit too colorful
-- (and besides, NPC should not get hurt anyway) - so use fixed colors
yl_speak_up.nametag_color_when_not_muted = "#00DDDD"
yl_speak_up.nametag_color_when_muted = "#FF00FF"
-- NPC can send a message to all players as an effect;
-- this text will be put in front of this message so that you and your players
-- know that it originated from an NPC (just make sure this returns a string)
yl_speak_up.chat_all_prefix = minetest.colorize("#0000FF", "[NPC] ")
-- the NPC will use this color when sending a chat message
yl_speak_up.chat_all_color = "#AAAAFF"
------------------------------------------------------------------------------
-- Skin and mesh definition - will be extended in i.e. npc_talk
-- Don't edit here.
------------------------------------------------------------------------------
-- diffrent NPC may use diffrent models
-- IMPORTANT: If you want to support an NPC with a diffrent model, provide
-- an entry in this array! Else setting its skin will fail horribly.
@ -24,52 +69,6 @@ yl_speak_up.mesh_data["error"] = {
texture_index = 1,
can_show_wielded_items = false,
}
-- this model is used by mobs_npc
yl_speak_up.mesh_data["mobs_character.b3d"] = {
-- the first texture is the skin
texture_index = 1,
-- there is no support for capes or wielded items
can_show_wielded_items = false,
-- textures are applied directly
textures_to_skin = false,
animation = {
-- {x = start_frame, y = end_frame, collisionbox}
stand_still = {x = 0, y = 0, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
stand = {x = 0, y = 79, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
sit = {x = 81, y = 160, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.0, 0.3}},
lay = {x = 162, y = 166, collisionbox = {-0.6, 0.0, -0.6, 0.6, 0.3, 0.6}},
walk = {x = 168, y = 187, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
mine = {x = 189, y = 198, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
walk_mine = {x = 200, y = 219, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
},
}
yl_speak_up.mesh_data["skinsdb_3d_armor_character_5.b3d"] = {
-- the second texture is the skin
texture_index = 2,
-- they can wear and show capes and wield items
can_show_wielded_items = true,
-- call textures2skin in order to convert the textures (wielded items)
textures_to_skin = true,
animation = {
-- {x = start_frame, y = end_frame, collisionbox}
stand_still = {x = 0, y = 0, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
stand = {x = 0, y = 79, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
sit = {x = 81, y = 160, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.0, 0.3}},
lay = {x = 162, y = 166, collisionbox = {-0.6, 0.0, -0.6, 0.6, 0.3, 0.6}},
walk = {x = 168, y = 187, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
mine = {x = 189, y = 198, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
walk_mine = {x = 200, y = 219, collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3}},
},
}
yl_speak_up.mesh_data["mobs_sheep.b3d"] = {
texture_index = 1,
}
yl_speak_up.mesh_data["mobs_cow.b3d"] = {
texture_index = 1,
}
-- diffrent mob types may want to wear diffrent skins - even if they share the
@ -78,38 +77,24 @@ yl_speak_up.mob_skins = {}
-- some models support capes
yl_speak_up.mob_capes = {}
-- mobs_redo usually uses 64 x 32 textures:
yl_speak_up.mob_skins["mobs_npc:npc"] = {
"mobs_npc.png", "mobs_npc2.png", "mobs_npc3.png", "mobs_npc4.png", "mobs_npc_baby.png"}
yl_speak_up.mob_skins["mobs_npc:igor"] = {
"mobs_igor.png", "mobs_igor2.png", "mobs_igor3.png", "mobs_igor4.png",
"mobs_igor5.png", "mobs_igor6.png", "mobs_igor7.png", "mobs_igor8.png"}
yl_speak_up.mob_skins["mobs_npc:trader"] = {
"mobs_trader.png", "mobs_trader2.png", "mobs_trader3.png"}
yl_speak_up.mob_skins["mobs_animal:sheep_white"] = {
"mobs_sheep_base.png^(mobs_sheep_wool.png^[colorize:#abababc0)"}
yl_speak_up.mob_skins["mobs_animal:sheep_red"] = {
"mobs_sheep_base.png^(mobs_sheep_wool.png^[colorize:#ff0000a0)"}
yl_speak_up.mob_skins["mobs_animal:cow"] = {
"mobs_cow.png", "mobs_cow2.png"}
-- this here uses 64 x 64 textures:
yl_speak_up.mob_skins["yl_speak_up:human"] = {
"yl_speak_up_main_default.png"}
-- which capes can an NPC wear?
yl_speak_up.mob_capes["yl_speak_up:human"] = {
"yl_npc_cape_default.png"}
-- some mobs (in particular from mobs_redo) can switch between follow (their owner),
-- stand and walking when they're right-clicked; emulate this behaviour for NPC in
-- this list
yl_speak_up.emulate_orders_on_rightclick = {
"mobs_npc:npc", "mobs_npc:igor", "mobs_npc:trader",
"mobs_animal:sheep_white", "mobs_animal:sheep_red", "mobs_animal:cow"}
yl_speak_up.emulate_orders_on_rightclick = {}
-- add a special line for a particluar mob (i.e. picking mob up via menu entry
-- instead of lasso)
-- index: entity name; values: table with indices...
-- condition a condition (i.e. a function)
-- text_if_true text shown on button if the condition is true
-- text_if_false text shown on button if the condition is false
-- execute_function function to call when this button is selected
yl_speak_up.add_on_rightclick_entry = {}
------------------------------------------------------------------------------
-- Extend this in your own mod or i.e. in npc_talk
------------------------------------------------------------------------------
-- some properties from external NPC can be edited and changed (they have the self. prefix),
-- and it is possible to react to property changes with handlers;
-- key: name of the property (i.e. self.order);
@ -118,8 +103,10 @@ yl_speak_up.emulate_orders_on_rightclick = {
yl_speak_up.custom_property_handler = {}
------------------------------------------------------------------------------
-- Path definitions (usually there is no need to change this)
------------------------------------------------------------------------------
-- What shall we call the folder all the dialogs will reside in?
yl_speak_up.path = "yl_speak_up_dialogs"
-- What shall we call the folder all the inventories of the NPC will reside in?
@ -138,24 +125,68 @@ yl_speak_up.quest_path = "yl_speak_up_quests"
-- (more time can pass if no variable is changed)
yl_speak_up.player_vars_min_save_time = 60
-- An option may be choosen automaticly without the player having to click if all of its
-- preconditions are true and the mode is set to automatic. Now, if the choosen target
-- dialog has an option that also uses this automatic mode, infinite loops might be
-- created. This option exists to avoid them. Any small value will do.
yl_speak_up.max_allowed_recursion_depth = 5
-- * set the name of the priv that allows to add, edit and change preconditions, actions and
-- effects listed in yl_speak_up.npc_priv_names in npc_privs.lua
-- * this also allows the player to use the "/npc_talk privs" command to assign these privs
-- to NPC
-- * it does *NOT* include the "precon_exec_lua" and "effect_exec_lua" priv - just
-- "effect_give_item", "effect_take_item" and "effect_move_player"
------------------------------------------------------------------------------
-- Privs - usually no need to change
------------------------------------------------------------------------------
-- NPC need npc privs in order to use some preconditions, actions and effects.
--
-- Plaers need privs in order to add, edit and change preconditions, actions and
-- effects listed in yl_speak_up.npc_priv_names in npc_privs.lua.
--
-- The following player priv allows the player to use the "/npc_talk privs" command to
-- grant/revoke/see these npc privs for *all NPC - not only for those the player can edit!
-- * default: "npc_talk_admin" (but can also be set to "npc_master" or "privs" if you want)
yl_speak_up.npc_privs_priv = "npc_talk_admin"
-- depending on your server, you might want to allow /npc_talk privs to be used by players
-- who *don't* have the privs priv;
-- WANRING: "precon_exec_lua" and "effect_exec_lua" are dangerous npc privs. Only players
-- with the privs priv ought to be able to use those!
-- The privs priv is the fallback if nothing is specified here.
yl_speak_up.npc_priv_needs_player_priv = {}
-- these privs allow to create items out of thin air - similar to the "give" priv
yl_speak_up.npc_priv_needs_player_priv["effect_give_item"] = "give"
yl_speak_up.npc_priv_needs_player_priv["effect_take_item"] = "give"
-- on servers with travelnets and/or teleporters, you'd most likely want to allow every
-- player to let NPC teleport players around; the "interact" priv covers that
--yl_speak_up.npc_priv_needs_player_priv["effect_move_player"] = "interact"
-- on YourLand, travel is very restricted; only those who can teleport players around can
-- do it with NPC as well; for backward compatibility, this is set for all servers
yl_speak_up.npc_priv_needs_player_priv["effect_move_player"] = "bring"
------------------------------------------------------------------------------
-- Blacklists - not all blocks may be suitable for all effects NPC can do
------------------------------------------------------------------------------
-- these blacklists forbid NPC to use effects on blocks; format:
-- yl_speak_up.blacklist_effect_on_block_interact[ node_name ] = true
-- forbids all interactions;
-- use this if a node isn't prepared for a type of interaction with
-- an NPC and cannot be changed easily;
-- Example: yl_speak_up.blacklist_effect_on_block_right_click["default:chest"] = true
yl_speak_up.blacklist_effect_on_block_interact = {}
-- blocks the NPC shall not be able to place:
yl_speak_up.blacklist_effect_on_block_place = {}
-- blocks the NPC shall not be able to dig:
yl_speak_up.blacklist_effect_on_block_dig = {}
-- blocks the NPC shall not be able to punch:
yl_speak_up.blacklist_effect_on_block_punch = {}
-- blocks the NPC shall not be able to right-click:
yl_speak_up.blacklist_effect_on_block_right_click = {}
-- taking something out of the inventory of a block or putting something in
yl_speak_up.blacklist_effect_on_block_put = {}
yl_speak_up.blacklist_effect_on_block_take = {}
-- tools the NPC shall not be able to use (covers both punching and right-click):
yl_speak_up.blacklist_effect_tool_use = {}
-- If some items are for some reasons not at all acceptable as quest items,
-- blacklist them here. The data structure is the same as for the tables above.
yl_speak_up.blacklist_action_quest_item = {}
------------------------------------------------------------------------------
-- Texts
------------------------------------------------------------------------------
yl_speak_up.message_button_option_exit = "Farewell!"
yl_speak_up.message_button_option_prerequisites_not_met_default = "Locked answer"
yl_speak_up.message_tool_taken_because_of_lacking_priv = "We took the tool from you and logged this event. You used an admin item while lacking the neccessary priv npc_master"
@ -165,51 +196,6 @@ yl_speak_up.text_new_prerequisite_id = "New prerequisite"
yl_speak_up.text_new_result_id = "New result"
yl_speak_up.text_version_warning = "You are using an outdated Minetest version!\nI will have a hard time talking to you properly, but I will try my best.\nYou can help me by upgrading to at least 5.3.0!\nGet it at https://minetest.net/downloads"
yl_speak_up.infotext = "Rightclick to talk"
-- how many buttons will be shown simultaneously without having to scroll?
yl_speak_up.max_number_of_buttons = 7
-- how many buttons can be added to one dialog?
yl_speak_up.max_number_of_options_per_dialog = 15
-- how many rows and cols shall be used for the trade overview list?
yl_speak_up.trade_max_rows = 10
yl_speak_up.trade_max_cols = 12
-- how many prerequirements can the player define per dialog option?
yl_speak_up.max_prerequirements = 12
-- how many actions can there be per dialog option?
-- for now, more than one doesn't make sense
yl_speak_up.max_actions = 1
-- how many effects can the player define per dialog option?
yl_speak_up.max_result_effects = 6
-- these blacklists forbid NPC to use effects on blocks; format:
-- yl_speak_up.blacklist_effect_on_block_interact[ node_name ] = true
-- forbids all interactions;
-- use this if a node isn't prepared for a type of interaction with
-- an NPC and cannot be changed easily;
-- Example: yl_speak_up.blacklist_effect_on_block_right_click["default:chest"] = true
yl_speak_up.blacklist_effect_on_block_interact = {}
yl_speak_up.blacklist_effect_on_block_place = {}
yl_speak_up.blacklist_effect_on_block_dig = {}
yl_speak_up.blacklist_effect_on_block_punch = {}
yl_speak_up.blacklist_effect_on_block_right_click = {}
-- taking something out of the inventory of a block or putting something in
yl_speak_up.blacklist_effect_on_block_put = {}
yl_speak_up.blacklist_effect_on_block_take = {}
-- If some items are for some reasons not at all acceptable as quest items,
-- blacklist them here. The data structure is the same as for the tables above.
yl_speak_up.blacklist_action_quest_item = {}
-- NPC can send a message to all players as an effect;
-- this text will be put in front of this message so that you and your players
-- know that it originated from an NPC (just make sure this returns a string)
yl_speak_up.chat_all_prefix = minetest.colorize("#0000FF", "[NPC] ")
-- the NPC will use this color when sending a chat message
yl_speak_up.chat_all_color = "#AAAAFF"
-- it's possible to prevent players from trying actions (i.e. npc_gives, text_input, ..) too often;
-- if no special text is set, this one will be shown (tab "Limit guessing:" in edit options menu)
yl_speak_up.standard_text_if_action_failed_too_often = "You have tried so many times. I'm tired! "..
@ -219,3 +205,7 @@ yl_speak_up.standard_text_if_action_failed_too_often = "You have tried so many t
-- shown by default (tab "Limit repeating:" in edit options menu)
yl_speak_up.standard_text_if_action_repeated_too_soon = "I don't have infinite ressources. If you lost "..
"something I gave you - come back later and we may talk again.\n$TEXT$"
------------------------------------------------------------------------------
-- End of config.lua
------------------------------------------------------------------------------

View File

@ -1 +0,0 @@
mobs

View File

@ -1,107 +0,0 @@
{
"n_id": "n_1",
"n_npc": "Mayor",
"n_description": "The Mayor of Haven",
"n_dialogs": {
"d_1": {
"d_id": "d_1",
"d_type": "text",
"d_text": "Hello Adventurer, I am the Mayor of Haven.",
"d_options": {
"o_1": {
"o_id": "o_1",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "",
"o_text_when_prerequisites_met": "",
"o_prerequisites": {
"p_1": {
"p_id": "p_1",
"p_type": "auto",
"p_value": "5"
}
},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "2"
}
}
}
}
},
"d_2": {
"d_id": "d_2",
"d_text": "What can I do for you?",
"d_options": {
"o_1": {
"o_id": "o_1",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "This option is not available.",
"o_text_when_prerequisites_met": "Let's talk about the plots in the city!",
"o_prerequisites": {},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "3"
}
}
},
"o_2": {
"o_id": "o_2",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "This option is not available.",
"o_text_when_prerequisites_met": "Let's talk about Airports!",
"o_prerequisites": {},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "4"
}
}
}
}
},
"d_3": {
"d_id": "d_3",
"d_text": "Text3",
"d_options": {
"o_1": {
"o_id": "o_1",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "This option is not available.",
"o_text_when_prerequisites_met": "Let's talk about the plots in the city!",
"o_prerequisites": {},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "3"
}
}
},
"o_2": {
"o_id": "o_2",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "This option is not available.",
"o_text_when_prerequisites_met": "Let's talk about Airports!",
"o_prerequisites": {},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "4"
}
}
}
}
}
}
}

View File

@ -1,80 +0,0 @@
{
"n_id": "n_12",
"n_npc": "Mayor",
"n_description": "The Mayor of Haven",
"n_dialogs": {
"d_1": {
"d_id": "d_1",
"d_type": "text",
"d_text": "Hello Adventurer, I am the Mayor of Haven.",
"d_options": {
"o_1": {
"o_id": "o_1",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "o_1 notmet",
"o_text_when_prerequisites_met": "o_1 met",
"o_prerequisites": {
"p_1": {
"p_id": "p_1",
"p_type": "auto",
"p_value": "5"
}
},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "2"
}
}
},
"o_2": {
"o_id": "o_2",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "o_2 notmet",
"o_text_when_prerequisites_met": "o_2 met",
"o_prerequisites": {
"p_1": {
"p_id": "p_1",
"p_type": "auto",
"p_value": "5"
}
},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "2"
}
}
},
"o_3": {
"o_id": "o_3",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "o3 notmet",
"o_text_when_prerequisites_met": "o3 met",
"o_prerequisites": {
"p_1": {
"p_id": "p_1",
"p_type": "auto",
"p_value": "5"
}
},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "2"
}
}
}
}
}
}
}

View File

@ -1 +0,0 @@
{ "n_id": "n_1", "n_npc": "Mayor", "n_description": "The Mayor of Haven", "n_dialogs": { "d_1": { "d_id": "d_1", "d_type": "text", "d_text": "Hello Adventurer, I am the Mayor of Haven.", "d_options": { "o_1": { "o_id": "o_1", "o_hide_when_prerequisites_not_met": "false", "o_grey_when_prerequisites_not_met": "true", "o_text_when_prerequisites_not_met": "", "o_text_when_prerequisites_met": "", "o_prerequisites": { "p_1": { "p_id": "p_1", "p_type": "auto", "p_value": "5" } }, "o_results": { "r_1": { "r_id": "r_1", "r_type": "dialog", "r_value": "2" } } } } }, "d_2": { "d_id": "d_2", "d_text": "What can I do for you?", "d_options": { "o_1": { "o_id": "o_1", "o_hide_when_prerequisites_not_met": "false", "o_grey_when_prerequisites_not_met": "true", "o_text_when_prerequisites_not_met": "This option is not available.", "o_text_when_prerequisites_met": "Let's talk about the plots in the city!", "o_prerequisites": {}, "o_results": { "r_1": { "r_id": "r_1", "r_type": "dialog", "r_value": "3" } } }, "o_2": { "o_id": "o_2", "o_hide_when_prerequisites_not_met": "false", "o_grey_when_prerequisites_not_met": "true", "o_text_when_prerequisites_not_met": "This option is not available.", "o_text_when_prerequisites_met": "Let's talk about Airports!", "o_prerequisites": {}, "o_results": { "r_1": { "r_id": "r_1", "r_type": "dialog", "r_value": "4" } } } } }, "d_3": { "d_id": "d_3", "d_text": "Text3", "d_options": { "o_1": { "o_id": "o_1", "o_hide_when_prerequisites_not_met": "false", "o_grey_when_prerequisites_not_met": "true", "o_text_when_prerequisites_not_met": "This option is not available.", "o_text_when_prerequisites_met": "Let's talk about the plots in the city!", "o_prerequisites": {}, "o_results": { "r_1": { "r_id": "r_1", "r_type": "dialog", "r_value": "3" } } }, "o_2": { "o_id": "o_2", "o_hide_when_prerequisites_not_met": "false", "o_grey_when_prerequisites_not_met": "true", "o_text_when_prerequisites_not_met": "This option is not available.", "o_text_when_prerequisites_met": "Let's talk about Airports!", "o_prerequisites": {}, "o_results": { "r_1": { "r_id": "r_1", "r_type": "dialog", "r_value": "4" } } } } } } }

View File

@ -1,77 +0,0 @@
{
"n_id": "n_12",
"n_npc": "Mayor",
"n_description": "The Mayor of Haven",
"n_dialogs": {
"d_1": {
"d_id": "d_1",
"d_type": "text",
"d_text": "Hello Adventurer, I am the Mayor of Haven.",
"d_options": {
"o_1": {
"o_id": "o_1",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "o_1 notmet",
"o_text_when_prerequisites_met": "o_1 met",
"o_prerequisites": {
"p_1": {
"p_id": "p_1",
"p_type": "auto",
"p_value": "5"
}
},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "2"
}
}
},
"o_2": {
"o_id": "o_2",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "o_2 notmet",
"o_text_when_prerequisites_met": "o_2 met",
"o_prerequisites": {
"p_1": {
"p_id": "p_1",
"p_type": "auto",
"p_value": "5"
}
},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "2"
}
}
},
"o_3": {
"o_id": "o_3",
"o_hide_when_prerequisites_not_met": "false",
"o_grey_when_prerequisites_not_met": "true",
"o_text_when_prerequisites_not_met": "o3 notmet",
"o_text_when_prerequisites_met": "o3 met",
"o_prerequisites": {
"p_1": {
"p_id": "p_1",
"p_type": "auto",
"p_value": "5"
}
},
"o_results": {
"r_1": {
"r_id": "r_1",
"r_type": "dialog",
"r_value": "2"
}
}
}
}
}
}
}

View File

@ -1 +0,0 @@
{ "n_id": "n_12", "n_npc": "Mayor", "n_description": "The Mayor of Haven", "n_dialogs": { "d_1": { "d_id": "d_1", "d_type": "text", "d_text": "Hello Adventurer, I am the Mayor of Haven.", "d_options": { "o_1": { "o_id": "o_1", "o_hide_when_prerequisites_not_met": "false", "o_grey_when_prerequisites_not_met": "true", "o_text_when_prerequisites_not_met": "o_1 notmet", "o_text_when_prerequisites_met": "o_1 met", "o_prerequisites": { "p_1": { "p_id": "p_1", "p_type": "auto", "p_value": "5" } }, "o_results": { "r_1": { "r_id": "r_1", "r_type": "dialog", "r_value": "2" } } }, "o_2": { "o_id": "o_2", "o_hide_when_prerequisites_not_met": "false", "o_grey_when_prerequisites_not_met": "true", "o_text_when_prerequisites_not_met": "o_2 notmet", "o_text_when_prerequisites_met": "o_2 met", "o_prerequisites": { "p_1": { "p_id": "p_1", "p_type": "auto", "p_value": "5" } }, "o_results": { "r_1": { "r_id": "r_1", "r_type": "dialog", "r_value": "2" } } }, "o_3": { "o_id": "o_3", "o_hide_when_prerequisites_not_met": "false", "o_grey_when_prerequisites_not_met": "true", "o_text_when_prerequisites_not_met": "o3 notmet", "o_text_when_prerequisites_met": "o3 met", "o_prerequisites": { "p_1": { "p_id": "p_1", "p_type": "auto", "p_value": "5" } }, "o_results": { "r_1": { "r_id": "r_1", "r_type": "dialog", "r_value": "2" } } } } } } }

View File

@ -1,16 +0,0 @@
Next:
Bugfixes and executing functions when talked to
###
https://codeberg.org/Hamlet/mobs_humans/src/branch/master/init.lua
https://github.com/minetest-mods/areas/blob/master/internal.lua
https://forum.minetest.net/viewtopic.php?t=20379
https://forum.minetest.net/viewtopic.php?t=19108
###
Make flat:
echo $(cat example.json) > n_12.json

149
dynamic_dialog.lua Normal file
View File

@ -0,0 +1,149 @@
-- This is a quick way to generate a simple d_dynamic dialog with
-- displayed text new_text and options/answers from the table
-- (list) answers.
-- The strings topics are added as parameters to the dialog options.
-- TODO: do the common replacements like $PLAYER_NAME$, $NPC_NAME$ etc?
yl_speak_up.generate_next_dynamic_dialog_simple = function(
player, n_id, d_id, alternate_text, recursion_depth,
new_text, answers, topics,
back_option_o_id, back_option_target_dialog)
if(not(player)) then
return
end
local pname = player:get_player_name()
if(not(yl_speak_up.speak_to[pname])) then
return
end
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog)) then
return
end
local dyn_dialog = dialog.n_dialogs["d_dynamic"]
if(not(dyn_dialog)) then
return
end
-- which dialog did the player come from?
local prev_d_id = yl_speak_up.speak_to[pname].d_id
-- the dialog d_dynamic is modified directly; we do not return anything
-- set the new text:
dialog.n_dialogs["d_dynamic"].d_text = new_text
-- add all the answers:
dialog.n_dialogs["d_dynamic"].d_options = {}
if(not(topics)) then
topics = {}
end
for i, text in ipairs(answers) do
local future_o_id = "o_" .. tostring(i)
-- add the dialog option as such:
dialog.n_dialogs["d_dynamic"].d_options[future_o_id] = {
o_id = future_o_id,
o_hide_when_prerequisites_not_met = "false",
o_grey_when_prerequisites_not_met = "false",
o_sort = i,
o_text_when_prerequisites_not_met = "",
o_text_when_prerequisites_met = (text or ""),
-- some additional information to make it easier to
-- react to a selected answer:
tmp_topic = topics[i],
}
-- create a fitting dialog result automaticly:
-- give this new dialog a dialog result that leads back to this dialog
-- (this can be changed later on if needed):
local future_r_id = "r_1"
-- actually store the new result
dialog.n_dialogs["d_dynamic"].d_options[future_o_id].o_results = {}
dialog.n_dialogs["d_dynamic"].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "dialog",
r_value = "d_dynamic"}
end
-- go back to back_option_target_dialog:
if(back_option_o_id
and dialog.n_dialogs["d_dynamic"].d_options[back_option_o_id]) then
dialog.n_dialogs["d_dynamic"].d_options[back_option_o_id].o_results["r_1"].r_value =
back_option_target_dialog
end
end
-- the dialog will be modified for this player only:
-- (pass on all the known parameters in case they're relevant):
-- called from yl_speak_up.get_fs_talkdialog(..):
yl_speak_up.generate_next_dynamic_dialog = function(player, n_id, d_id, alternate_text, recursion_depth)
if(not(player)) then
return
end
local pname = player:get_player_name()
if(not(yl_speak_up.speak_to[pname])) then
return
end
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog.n_dialogs["d_dynamic"])) then
dialog.n_dialogs["d_dynamic"] = {}
end
-- which dialog did the player come from?
local prev_d_id = yl_speak_up.speak_to[pname].d_id
local selected_o_id = yl_speak_up.speak_to[pname].selected_o_id
-- the text the NPC shall say:
local prev_answer = "- unknown -"
local tmp_topic = "- none -"
if(dialog.n_dialogs[prev_d_id]
and dialog.n_dialogs[prev_d_id].d_options
and dialog.n_dialogs[prev_d_id].d_options[selected_o_id]
and dialog.n_dialogs[prev_d_id].d_options[selected_o_id].o_text_when_prerequisites_met) then
prev_answer = dialog.n_dialogs[prev_d_id].d_options[selected_o_id].o_text_when_prerequisites_met
tmp_topic = dialog.n_dialogs[prev_d_id].d_options[selected_o_id].tmp_topic
end
-- pname is the name of the player; d_id is "d_dynamic"
local new_text = "Hello $PLAYER_NAME$,\n".. -- also: pname
"you're talking to me, $NPC_NAME$, who has the NPC ID "..tostring(n_id)..".\n"..
"Previous dialog: "..tostring(prev_d_id)..".\n"..
"Selected option: "..tostring(selected_o_id).." with the text:\n"..
"\t\""..tostring(prev_answer).."\".\n"..
"We have shared "..tostring(dialog.n_dialogs["d_dynamic"].tmp_count or 0)..
" such continous dynamic dialogs this time.\n"
-- the answers/options the player can choose from:
local answers = {"$GOOD_DAY$! My name is $PLAYER_NAME$.",
"Can I help you, $NPC_NAME$?",
"What is your name? I'm called $PLAYER_NAME$.", "Who is your employer?",
"What are you doing here?", "Help me, please!", "This is just a test.",
"That's too boring. Let's talk normal again!"}
-- store a topic for each answer so that the NPC can reply accordingly:
local topics = {"my_name", "help_offered", "your_name", "your_employer", "your_job",
"help_me", "test", "back"}
-- react to the previously selected topic (usually you'd want a diffrent new_text,
-- answers and topics based on what the player last selected; this here is just for
-- demonstration):
if(tmp_topic == "my_name") then
new_text = new_text.."Pleased to meet you, $PLAYER_NAME$!"
elseif(tmp_topic == "help_offered") then
new_text = new_text.."Thanks! But I don't need any help right now."
elseif(tmp_topic == "your_name") then
new_text = new_text.."Thank you for asking for my name! It is $NPC_NAME$."
elseif(tmp_topic == "your_employer") then
new_text = new_text.."I work for $OWNER_NAME$."
elseif(tmp_topic == "your_job") then
new_text = new_text.."My job is to answer questions from adventurers like yourself."
elseif(tmp_topic == "help_me") then
new_text = new_text.."I'm afraid I'm unable to help you."
elseif(tmp_topic == "test") then
new_text = new_text.."Your test was successful. We're talking."
else
new_text = new_text.."Feel free to talk to me! Just choose an answer or question."
end
-- With this answer/option, the player can leave the d_dynamic dialog and return..
local back_option_o_id = "o_"..tostring(#answers)
-- ..back to dialog d_1 (usually the start dialog):
local back_option_target_dialog = "d_1"
-- store some additional values:
if(d_id ~= "d_dynamic" or not(dialog.n_dialogs["d_dynamic"].tmp_count)) then
dialog.n_dialogs["d_dynamic"].tmp_count = 0
end
dialog.n_dialogs["d_dynamic"].tmp_count = dialog.n_dialogs["d_dynamic"].tmp_count + 1
-- actually update the d_dynamic dialog
return yl_speak_up.generate_next_dynamic_dialog_simple(
player, n_id, d_id, alternate_text, recursion_depth,
new_text, answers, topics, back_option_o_id, back_option_target_dialog)
end

View File

@ -1,493 +0,0 @@
-- helper function for yl_speak_up.edit_mode_apply_changes;
-- makes sure the new dialog (and a result/effect "dialog" for each option) exist
yl_speak_up.prepare_new_dialog_for_option = function(dialog, pname, n_id, d_id, o_id,target_dialog,o_results)
-- this may also point to a new dialog
if(target_dialog == yl_speak_up.text_new_dialog_id) then
-- create a new dialog and show it as new target dialog - but do not display
-- this dialog directly (the player may follow the -> button)
target_dialog = yl_speak_up.add_new_dialog(dialog, pname, nil)
end
-- is there a result/effect of the type "dialog" already? else use a fallback
local result = {} --{r_value = "-default-"}
if(o_results) then
for kr, vr in pairs(o_results) do
if( vr.r_type == "dialog" ) then
result = vr
-- no problem - the right dialog is set already
if(result.r_value and result.r_value == target_dialog) then
return target_dialog
else
-- no need to search any further
break
end
end
end
end
-- store that a new option has been added to this dialog
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The target dialog for option "..
tostring(o_id).." was changed from "..
tostring(result.r_value or "-default-").." to "..
tostring(target_dialog)..".")
-- does the result/effect of type "dialog" exist already? then we're done
if(result.r_type and result.r_type == "dialog") then
-- actually change the target dialog
result.r_value = target_dialog
return target_dialog
end
-- create a new result (first the id, then the actual result)
local future_r_id = yl_speak_up.add_new_result(dialog, d_id, o_id)
-- actually store the new result
dialog.n_dialogs[d_id].d_options[o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "dialog",
r_value = target_dialog}
return target_dialog
end
-- helper function for formspec "yl_speak_up:talk" *and* formspec "yl_speak_up:edit_option_dialog"
-- when a parameter was changed in edit mode;
-- this is called when the player is in edit_mode (editing the NPC);
-- the function checks if the player has changed any parameters
-- Parameters:
-- pname player name
-- fields the fields returned from the formspec
-- Returns:
-- result table with information about what was added
-- (for now, only result.show_next_option is of intrest in the option edit menu)
yl_speak_up.edit_mode_apply_changes = function(pname, fields)
local n_id = yl_speak_up.edit_mode[pname]
local d_id = yl_speak_up.speak_to[pname].d_id
local dialog = yl_speak_up.speak_to[pname].dialog
-- check if the player is allowed to edit this NPC
if(not(yl_speak_up.may_edit_npc(minetest.get_player_by_name(pname), n_id))) then
return
end
-- this way we can store the actual changes and present them to the player for saving
if(not(yl_speak_up.npc_was_changed[ n_id ])) then
yl_speak_up.npc_was_changed[ n_id ] = {}
end
-- nothing to do if that dialog does not exist
if(not(d_id) or not(dialog.n_dialogs) or not(dialog.n_dialogs[ d_id ])) then
return
end
-- allow owner to mute/unmute npc (would be bad if players can already see what is going
-- to happen while the owner creates a long quest)
-- mute/unmute gets logged in the function and does not need extra log entries
local obj = yl_speak_up.speak_to[pname].obj
if(fields.mute_npc and obj) then
yl_speak_up.set_muted(pname, obj, true)
elseif(fields.un_mute_npc and obj) then
yl_speak_up.set_muted(pname, obj, false)
end
-- new options etc. may be added; store these IDs so that we can switch to the right target
local result = {}
-- make this the first dialog shown when starting a conversation
if(fields.make_first_option) then
-- check which dialog(s) previously had the highest priority and change thsoe
for k, v in pairs(dialog.n_dialogs) do
if(v and v.d_sort and (v.d_sort=="0" or v.d_sort==0)) then
-- try to derive a sensible future sort priority from the key:
-- here we make use of the d_<nr> pattern; but even if that fails to yield
-- a number, the sort function will later be able to deal with it anyway
local new_priority = string.sub(k, 3)
dialog.n_dialogs[ k ].d_sort = new_priority
end
end
-- actually make this the chat with the highest priority
dialog.n_dialogs[ d_id ].d_sort = "0"
-- this is not immediately saved, even though the changes to the previous dialog with
-- the highest priority cannot be automaticly undone (but as long as it is not saved,
-- it really does not matter; and when saving, the player has to take some care)
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Turned into new start dialog.")
end
-- if it is *a* start dialog: buttons like give item to npc/trade/etc. will be shown
if(fields.turn_into_a_start_dialog) then
if(dialog.n_dialogs[ d_id ].is_a_start_dialog) then
-- no need to waste space...
dialog.n_dialogs[ d_id ].is_a_start_dialog = nil
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Is no longer *a* start dialog (regarding buttons).")
else
dialog.n_dialogs[ d_id ].is_a_start_dialog = true
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Turned into *a* start dialog (regarding buttons).")
end
end
-- detect changes to d_text: text of the dialog (what the npc is saying)
-- (only happens in dialog edit menu)
if(fields.d_text and dialog.n_dialogs[ d_id ].d_text ~= fields.d_text) then
-- store that there have been changes to this npc
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": d_text (what the NPC says) was changed from \""..
tostring( dialog.n_dialogs[ d_id ].d_text)..
"\" to \""..tostring(fields.d_text).."\".")
-- actually change the text - but do not save to disk yet
dialog.n_dialogs[ d_id ].d_text = fields.d_text
end
-- add a new option/answer
if(fields[ "add_option"]) then
local future_o_id = yl_speak_up.add_new_option(dialog, pname, nil, d_id, "", d_id)
if(not(future_o_id)) then
-- this is already checked earlier on and the button only shown if
-- options can be added; so this can reamin a chat message
minetest.chat_send_player(pname, "Sorry. Only "..
tostring(yl_speak_up.max_number_of_options_per_dialog)..
" options/answers are allowed per dialog.")
fields.add_option = nil
else
-- add_new_option has added a dialog result for us already - no need to do that again
-- if this is selected in the options edit menu, we want to move straight on to the new option
result["show_next_option"] = future_o_id
end
end
-- delete an option directly from the main fs_talkdialog
if(dialog.n_dialogs[d_id].d_options) then
for o_id, o_v in pairs(dialog.n_dialogs[d_id].d_options) do
if(o_id and fields["delete_option_"..o_id]) then
fields["del_option"] = true
fields.o_id = o_id
-- ..or move an option up by one in the list
elseif(o_id and fields["option_move_up_"..o_id]) then
fields["option_move_up"] = true
fields.o_id = o_id
-- ..or move an option down by one in the list
elseif(o_id and fields["option_move_down_"..o_id]) then
fields["option_move_down"] = true
fields.o_id = o_id
end
end
end
if(fields[ "del_option"] and fields.o_id and dialog.n_dialogs[d_id].d_options[fields.o_id]) then
local o_id = fields.o_id
-- which dialog to show instead of the deleted one?
local next_o_id = o_id
local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort")
for i, o in ipairs(sorted_list) do
if(o == o_id and sorted_list[ i+1 ]) then
next_o_id = sorted_list[ i+1 ]
elseif(o == o_id and sorted_list[ i-1 ]) then
next_o_id = sorted_list[ i-1 ]
end
end
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Option "..tostring(o_id).." deleted.")
-- actually delete the dialog
dialog.n_dialogs[d_id].d_options[o_id] = nil
-- the current dialog is deleted; we need to show another one
result["show_next_option"] = next_o_id
-- after deleting the entry, all previous/further changes to it are kind of unintresting
return result
end
-- move an option up by one
local d_options = dialog.n_dialogs[d_id].d_options
if(fields[ "option_move_up"] and fields.o_id and d_options[fields.o_id]) then
local sorted_o_list = yl_speak_up.get_sorted_options(d_options, "o_sort")
local idx = table.indexof(sorted_o_list, fields.o_id)
if(idx > 1) then
-- swap the two positions
local tmp = dialog.n_dialogs[d_id].d_options[fields.o_id].o_sort
dialog.n_dialogs[d_id].d_options[fields.o_id].o_sort =
dialog.n_dialogs[d_id].d_options[sorted_o_list[idx - 1]].o_sort
dialog.n_dialogs[d_id].d_options[sorted_o_list[idx - 1]].o_sort = tmp
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Option "..tostring(fields.o_id).." was moved up by one.")
end
-- ..or move the option down by one
elseif(fields[ "option_move_down"] and fields.o_id and d_options[fields.o_id]) then
local sorted_o_list = yl_speak_up.get_sorted_options(d_options, "o_sort")
local idx = table.indexof(sorted_o_list, fields.o_id)
if(idx > 0 and idx < #sorted_o_list) then
local tmp = dialog.n_dialogs[d_id].d_options[fields.o_id].o_sort
dialog.n_dialogs[d_id].d_options[fields.o_id].o_sort =
dialog.n_dialogs[d_id].d_options[sorted_o_list[idx + 1]].o_sort
dialog.n_dialogs[d_id].d_options[sorted_o_list[idx + 1]].o_sort = tmp
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Option "..tostring(fields.o_id).." was moved down by one.")
end
end
-- ignore entries to o_sort if they are not a number
if(fields[ "edit_option_o_sort"]
and tonumber(fields[ "edit_option_o_sort"])
and fields.o_id and dialog.n_dialogs[d_id].d_options[fields.o_id]) then
local o_id = fields.o_id
local new_nr = tonumber(fields[ "edit_option_o_sort"])
local old_nr = tonumber(dialog.n_dialogs[d_id].d_options[o_id].o_sort)
-- if the nr is -1 (do not show) then we are done already: nothing to do
if(old_nr == new_nr) then
-- -1: do not list as option/answer (but still store and keep it)
elseif(new_nr == -1 and old_nr ~= -1) then
dialog.n_dialogs[d_id].d_options[o_id].o_sort = "-1"
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Option "..tostring(o_id).." was set to -1 (do not list).")
else
-- get the old sorted list
local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort")
-- negative numbers are not shown
local entries_shown_list = {}
for i, o in ipairs(sorted_list) do
local n = tonumber(dialog.n_dialogs[d_id].d_options[o].o_sort)
if(n and n > 0 and o ~= o_id) then
table.insert(entries_shown_list, o)
end
end
-- insert the entry at the new position and let lua do the job
table.insert(entries_shown_list, new_nr, o_id)
-- take the indices from that new list as new sort values and store them;
-- this has the side effect that duplicate entries get sorted out as well
for i, o in ipairs(entries_shown_list) do
dialog.n_dialogs[d_id].d_options[o].o_sort = tostring(i)
end
-- store that there was a cahnge
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Option "..tostring(o_id).." was moved to position "..
tostring(new_nr)..".")
end
end
-- changes to options are not possible if there are none
if(dialog.n_dialogs[ d_id ].d_options) then
-- detect changes to text_option_<o_id>: text for option <o_id>
for k, v in pairs(dialog.n_dialogs[ d_id ].d_options) do
if( fields[ "text_option_"..k ]
and fields[ "text_option_"..k ] ~= v.o_text_when_prerequisites_met ) then
-- store that there have been changes to this npc
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The text for option "..tostring(k)..
" was changed from \""..tostring(v.o_text_when_prerequisites_met)..
"\" to \""..tostring(fields[ "text_option_"..k]).."\".")
-- actually change the text of the option
dialog.n_dialogs[ d_id ].d_options[ k ].o_text_when_prerequisites_met = fields[ "text_option_"..k ]
end
end
-- detect changes to d_id_<o_id>: target dialog for option <o_id>
for k, v in pairs(dialog.n_dialogs[ d_id ].d_options) do
if(fields[ "d_id_"..k ]) then
local new_target_dialog = yl_speak_up.prepare_new_dialog_for_option(
dialog, pname, n_id, d_id, k, fields[ "d_id_"..k ], v.o_results)
if(new_target_dialog ~= fields[ "d_id_"..k ]) then
fields[ "d_id_"..k ] = new_target_dialog
-- in options edit menu: show this update
result["show_next_option"] = k
end
end
end
end
-- add a new dialog; either via "+" button or "New dialog" in dialog dropdown menu
-- this has to be done after all the other changes because those (text changes etc.) still
-- apply to the *old* dialog
if(fields.show_new_dialog
or(fields["d_id"] and fields["d_id"] == yl_speak_up.text_new_dialog_id)) then
-- create the new dialog and make sure it gets shown
local d_id = yl_speak_up.add_new_dialog(dialog, pname, nil)
-- actually show the new dialog
fields["d_id"] = d_id
fields["show_new_dialog"] = nil
end
-- delete one empty dialog
if(fields.delete_this_empty_dialog) then
local anz_options = 0
-- we need to show a new dialog after this one was deleted
local new_dialog = d_id
local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs, "d_sort")
for i, k in ipairs(sorted_list) do
-- count the options of this dialog
if(k == d_id) then
if(dialog.n_dialogs[d_id].d_options) then
for o, w in pairs(dialog.n_dialogs[d_id].d_options) do
anz_options = anz_options + 1
end
end
if(sorted_list[i+1]) then
new_dialog = sorted_list[i+1]
elseif(sorted_list[i-1]) then
new_dialog = sorted_list[i-1]
end
end
end
-- there needs to be one dialog left after deleting this one,
if(#sorted_list > 1
-- this dialog isn't allowed to hold any more options/answers
and anz_options == 0
-- we really found a new dialog to show
and new_dialog ~= d_id
-- and the text needs to be empty
and dialog.n_dialogs[ d_id ].d_text == "") then
-- actually delete this dialog
dialog.n_dialogs[ d_id ] = nil
-- ..and store it to disk
yl_speak_up.delete_dialog(n_id, d_id)
yl_speak_up.log_change(pname, n_id,
"Deleted dialog "..tostring(d_id)..".")
-- switch to another dialog (this one was deleted after all)
fields["d_id"] = new_dialog
fields["show_new_dialog"] = nil
else
-- deleting is only possible from the talk menu, and there the delete
-- button is only shown if the dialog can be deleted; so this can remain
-- a chat message
minetest.chat_send_player(pname, "Sorry. This dialog cannot be deleted (yet). "..
"It is either the only dialog left or has a non-empty text or has at "..
"least on remaining option/answer.")
end
end
-- not in options edit menu?
local o_id = fields.o_id
if(not(o_id)) then
return result
end
local d_option = dialog.n_dialogs[ d_id ].d_options[ o_id ]
-- change alternate text when preconditions are not met
-- (only happens in options edit menu)
if(fields.option_text_not_met and d_option
and d_option.o_text_when_prerequisites_not_met ~= fields.option_text_not_met) then
-- add change to changelog
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The alternate text for option "..tostring(o_id)..
" was changed from \""..
tostring(d_option.o_text_when_prerequisites_not_met).."\" to \""..
tostring(fields.option_text_not_met).."\".")
-- actually change the text of the option
d_option.o_text_when_prerequisites_not_met = fields.option_text_not_met
end
-- toggle autoselection/autoclick of an option
if(d_option and fields.option_autoanswer and fields.option_autoanswer ~= "") then
local old_answer_mode = "by clicking on it"
if(dialog.n_dialogs[ d_id ].o_random) then
old_answer_mode = "randomly"
elseif(d_option.o_autoanswer and d_option.o_autoanswer == 1) then
old_answer_mode = "automaticly"
end
if(fields.otpion_autoanswer ~= old_answer_mode) then
local new_answer_mode = ""
if(fields.option_autoanswer == "by clicking on it") then
d_option.o_autoanswer = nil
-- the dialog is no longer random
dialog.n_dialogs[ d_id ].o_random = nil
new_answer_mode = fields.option_autoanswer
elseif(fields.option_autoanswer == "automaticly") then
d_option.o_autoanswer = 1
-- the dialog is no longer random
dialog.n_dialogs[ d_id ].o_random = nil
new_answer_mode = fields.option_autoanswer
elseif(fields.option_autoanswer == "randomly") then
d_option.o_autoanswer = nil
-- the entire dialog needs to be set to randomly - not just this option
dialog.n_dialogs[ d_id ].o_random = 1
new_answer_mode = fields.option_autoanswer
end
if(new_answer_mode ~= "" and new_answer_mode ~= old_answer_mode) then
local random_was_changed = ""
if(new_answer_mode == "randomly" or old_answer_mode == "randomly") then
random_was_changed = " Note that changes to/from \"randomly\" "..
"affect the entire dialog!"
end
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The modus for option "..tostring(o_id)..
" was changed from \""..old_answer_mode.."\" to \""..
new_answer_mode.."\"."..random_was_changed)
end
end
end
-- handle hide/grey out/show alternate answer
-- (only happens in options edit menu)
if(fields.hide_or_grey_or_alternate_answer and d_option) then
if(fields.hide_or_grey_or_alternate_answer == "..hide this answer."
and d_option.o_hide_when_prerequisites_not_met ~= "true") then
d_option.o_hide_when_prerequisites_not_met = "true"
d_option.o_grey_when_prerequisites_not_met = "false"
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": If precondition for option "..tostring(o_id)..
" is not met, hide option/answer.")
-- make sure we show this options update next
result["show_next_option"] = o_id
elseif(fields.hide_or_grey_or_alternate_answer == "..grey out the following answer:"
and d_option.o_grey_when_prerequisites_not_met ~= "true") then
d_option.o_hide_when_prerequisites_not_met = "false"
d_option.o_grey_when_prerequisites_not_met = "true"
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": If precondition for option "..tostring(o_id)..
" is not met, grey out option/answer.")
result["show_next_option"] = o_id
elseif(fields.hide_or_grey_or_alternate_answer == "..display the following alternate answer:"
and (d_option.o_hide_when_prerequisites_not_met ~= "false"
or d_option.o_grey_when_prerequisites_not_met) ~= "false") then
d_option.o_hide_when_prerequisites_not_met = "false"
d_option.o_grey_when_prerequisites_not_met = "false"
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": If precondition for option "..tostring(o_id)..
" is not met, show alternate option/answer.")
result["show_next_option"] = o_id
end
end
-- how many times can the player fail to execute the action successfully?
if(fields[ "timer_max_attempts_on_failure"]) then
local field_name = "timer_max_attempts_on_failure"
local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
if(not(tonumber(fields[ field_name ]))) then
fields[ field_name ] = 0
end
-- make sure the variable exists
if(yl_speak_up.add_time_based_variable(timer_name)) then
yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "max_attempts",
fields[ field_name ])
end
end
-- ..and how long has the player to wait in order to try again?
if(fields[ "timer_max_seconds_on_failure"]) then
local field_name = "timer_max_seconds_on_failure"
local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
if(not(tonumber(fields[ field_name ]))) then
fields[ field_name ] = 0
end
-- make sure the variable exists
if(yl_speak_up.add_time_based_variable(timer_name)) then
yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "duration",
fields[ field_name ])
end
end
if(fields[ "timer_max_seconds_on_success"]) then
local field_name = "timer_max_seconds_on_success"
local timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id)
if(not(tonumber(fields[ field_name ]))) then
fields[ field_name ] = 0
end
-- make sure the variable exists
if(yl_speak_up.add_time_based_variable(timer_name)) then
yl_speak_up.set_variable_metadata(timer_name, nil, "parameter", "duration",
fields[ field_name ])
end
end
-- currently only contains result["show_new_option"] (which is needed for options edit menu)
return result
end
-- end of yl_speak_up.edit_mode_apply_changes

View File

@ -7,66 +7,23 @@ yl_speak_up.action_inv_changed = function(inv, listname, index, stack, player, h
return
end
local pname = player:get_player_name()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
local n_id = yl_speak_up.speak_to[pname].n_id
-- if not in edit mode: the player may just be normally interacting with the NPC;
-- nothing to do for us here (wait for the player to click on "save")
if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then
if(listname and listname == "npc_gives") then
yl_speak_up.input_fs_action_npc_gives(player, "action_npc_gives", {})
return
end
if(listname and listname == "npc_gives") then
yl_speak_up.input_fs_action_npc_gives(player, "action_npc_gives", {})
return
end
-- is the player in the process of editing an action of the npc_gives/npc_wants type?
local target_fs = "edit_actions"
local data = yl_speak_up.speak_to[pname][ "tmp_action" ]
if(not(data) or (data.what ~= 4 and data.what ~= 5)) then
-- we are editing an action
if(data) then
return
end
-- it might be a precondition
data = yl_speak_up.speak_to[pname][ "tmp_prereq" ]
if(not(data) or (data.what ~= 8)) then
return
end
target_fs = "edit_preconditions"
end
-- "The NPC gives something to the player (i.e. a quest item).", -- 4
-- "The player is expected to give something to the NPC (i.e. a quest item).", -- 5
if(how == "put") then
data.item_node_name = stack:get_name().." "..stack:get_count()
local meta = stack:get_meta()
if(meta and meta:get_string("description")) then
-- try to reconstruct $PLAYER_NAME$ (may not always work)
local item_was_for = meta:get_string("yl_speak_up:quest_item_for")
local new_desc = meta:get_string("description")
if(item_was_for and item_was_for ~= "") then
new_desc = string.gsub(new_desc, item_was_for, "$PLAYER_NAME$")
end
data.item_desc = new_desc
end
if(meta and meta:get_string("yl_speak_up:quest_id")) then
data.item_quest_id = meta:get_string("yl_speak_up:quest_id")
end
elseif(how == "take" and data.what == 4) then
data.item_desc = "- no item set -"
data.item_node_name = ""
elseif(how == "take" and data.what == 5) then
data.item_desc = "- no item set -"
data.item_node_name = ""
end
-- show the updated formspec to the player
yl_speak_up.show_fs(player, target_fs, nil)
-- no need to check anything more here; the real checks need to be done
-- when the player presses the save/store/execute button
end
-- actions - in contrast to preconditions and effects - may take time
-- because the player usually gets presented a formspec and needs to
-- react to that; thus, we can't just execute all actions simultaneously
yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id)
yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id, formname)
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
local d_id = yl_speak_up.speak_to[pname].d_id
@ -220,9 +177,10 @@ yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id)
yl_speak_up.speak_to[pname].d_id = this_action.a_on_failure
yl_speak_up.speak_to[pname].o_id = nil
yl_speak_up.speak_to[pname].a_id = nil
yl_speak_up.show_fs(player, "talk", {n_id = n_id,
d_id = this_action.a_on_failure,
alternate_text = this_action.alternate_text})
-- allow d_end, d_trade, d_got_item etc. to work as a_on_failure
yl_speak_up.show_next_talk_fs_after_action(player, pname,
this_action.a_on_failure, formname,
dialog, d_id, n_id, this_action.alternate_text)
return
else
local this_action = actions[ sorted_key_list[ nr ]]
@ -263,19 +221,45 @@ yl_speak_up.execute_next_action = function(player, a_id, result_of_a_id)
local target_dialog = res.next_dialog
yl_speak_up.speak_to[pname].o_id = nil
yl_speak_up.speak_to[pname].a_id = nil
-- the function above returns a target dialog; show that to the player
yl_speak_up.show_next_talk_fs_after_action(player, pname, target_dialog, formname,
dialog, target_dialog, n_id, res.alternate_text)
end
-- after completing the action - either successfully or if it failed:
yl_speak_up.show_next_talk_fs_after_action = function(player, pname, target_dialog, formname,
dialog, d_id, n_id, alternate_text)
-- allow to switch to d_trade from any dialog
if(target_dialog and target_dialog == "d_trade") then
yl_speak_up.show_fs(player, "trade_list")
return
end
-- allow to switch to d_got_item from any dialog
if(target_dialog and target_dialog == "d_got_item") then
yl_speak_up.show_fs(player, "player_offers_item")
return
end
-- end conversation
if(target_dialog and target_dialog == "d_end") then
yl_speak_up.stop_talking(pname)
-- we are done with this; close any open forms
if(formname) then
minetest.close_formspec(pname, formname)
end
return
end
-- the special dialogs d_trade and d_got_item have no actions or effects - thus
-- d_id cannot become d_trade or d_got_item
if(not(target_dialog)
or target_dialog == ""
or not(dialog.n_dialogs[target_dialog])) then
target_dialog = d_id
end
-- the function above returns a target dialog; show that to the player
-- actually show the next dialog to the player
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = target_dialog,
alternate_text = res.alternate_text})
alternate_text = alternate_text})
end
@ -328,6 +312,22 @@ yl_speak_up.get_action_by_player = function(player)
end
-- did the NPC try to give something to the player already - and the player didn't take it?
-- then give that old item back to the NPC
yl_speak_up.action_take_back_failed_npc_gives = function(trade_inv, npc_inv)
if(not(trade_inv) or not(npc_inv)) then
return
end
local last_stack = trade_inv:get_stack("npc_gives", 1)
if(not(last_stack:is_empty())) then
-- strip any metadata to avoid stacking problems
npc_inv:add_item("npc_main", last_stack:get_name().." "..last_stack:get_count())
-- clear the stack
trade_inv:set_stack("npc_gives", 1, "")
end
end
-- Create the quest item by taking a raw item (i.e. a general piece of paper) out
-- of the NPC's inventory, applying a description (if given) and quest id (if
-- given); place the quest item in the trade inv of the player in the npc_gives slot.
@ -347,6 +347,10 @@ yl_speak_up.action_quest_item_prepare = function(player)
local stack = ItemStack(a.a_value)
-- get the inventory of the NPC
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)})
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
yl_speak_up.action_take_back_failed_npc_gives(trade_inv, npc_inv)
-- does the NPC have the item we are looking for?
if(not(npc_inv:contains_item("npc_main", stack))) then
local o_id = yl_speak_up.speak_to[pname].o_id
@ -376,7 +380,6 @@ yl_speak_up.action_quest_item_prepare = function(player)
-- put the stack in the npc_gives-slot of the trade inventory of the player
-- (as that slot is managed by the NPC alone we don't have to worry about
-- anything else in the slot)
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
-- actually put the stack in there
trade_inv:set_stack("npc_gives", 1, new_stack)
return true
@ -510,334 +513,4 @@ end
-- show the diffrent action-related formspecs and handle input to them
-- (Note: trade is handled in trade_simple.lua)
yl_speak_up.input_fs_action_npc_gives = function(player, formname, fields)
-- back from error_msg? then show the formspec again
if(fields.back_from_error_msg) then
-- do not create a new item
yl_speak_up.show_fs(player, "action_npc_gives", nil)
return
end
local pname = player:get_player_name()
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
if(not(yl_speak_up.speak_to[pname])) then
return
end
local a_id = yl_speak_up.speak_to[pname].a_id
if(fields.npc_does_not_have_item) then
-- the NPC can't supply the item - abort the action
yl_speak_up.execute_next_action(player, a_id, nil)
return
end
-- is the npc_gives inv empty? then all went as expected.
-- (it does not really matter which button the player pressed in this case)
if(trade_inv:is_empty("npc_gives")) then
-- the NPC has given the item to the player; save the NPCs inventory
local n_id = yl_speak_up.speak_to[pname].n_id
yl_speak_up.save_npc_inventory(n_id)
-- the action was a success; the NPC managed to give the item to the player
yl_speak_up.execute_next_action(player, a_id, true)
return
end
-- the npc_gives slot does not accept input - so we don't have to check for any misplaced items
-- but if the player aborts, give the item back to the NPC
if(fields.back_to_talk) then
-- strip the quest item info from the stack (so that it may stack again)
-- and give that (hopefully) stackable stack back to the NPC
yl_speak_up.action_quest_item_take_back(player)
-- the action failed
yl_speak_up.execute_next_action(player, a_id, nil)
return
end
-- else show a message to the player that he ought to take the item
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:action_npc_gives",
formspec = "size[7,1.5]"..
"label[0.2,-0.2;"..
"Please take the offered item and click on \"Done\"!\n"..
"If you can't take it, click on \"Back to talk\".]"..
"button[2,1.0;1.5,0.9;back_from_error_msg;Back]"})
end
yl_speak_up.get_fs_action_npc_gives = function(player, param)
-- called for the first time; create the item the NPC wants to give
if(param) then
if(not(yl_speak_up.action_quest_item_prepare(player))) then
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
-- it's not the fault of the player that the NPC doesn't have the item;
-- so tell him that (the action will still fail)
return "size[7,2.0]"..
"label[0.2,-0.2;"..
minetest.formspec_escape(dialog.n_npc or "- ? -")..
" is very sorry:\n"..
"The item intended for you is currently unavailable.\n"..
"Please come back later!]"..
"button[2,1.5;1.5,0.9;npc_does_not_have_item;Back]"
end
end
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
return "size[8.5,8]"..
"list[current_player;main;0.2,3.85;8,1;]"..
"list[current_player;main;0.2,5.08;8,3;8]"..
"button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]"..
"button[4.75,1.6;1.5,0.9;finished_action;Done]"..
"tooltip[back_to_talk;Click here if you don't want to (or can't)\n"..
"take the offered item.]"..
"tooltip[finished_action;Click here once you have taken the item and\n"..
"stored it in your inventory.]"..
"label[1.5,0.7;"..minetest.formspec_escape(dialog.n_npc or "- ? -")..
" offers to you:]"..
-- unlike the npc_gives slot - which is used for setting up the NPC - the
-- npc_gives slot does not allow putting something in
"list[detached:yl_speak_up_player_"..pname..";npc_gives;3.25,1.5;1,1;]" ..
"label[1.5,2.7;Take the offered item and click on \"Done\" to proceed.]"
end
yl_speak_up.input_fs_action_npc_wants = function(player, formname, fields)
-- back from error_msg? then show the formspec again
if(fields.back_from_error_msg) then
yl_speak_up.show_fs(player, "action_npc_wants", nil)
return
end
local pname = player:get_player_name()
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
local a_id = yl_speak_up.speak_to[pname].a_id
-- is the npc_wants inv empty and the player pressed the back to talk button? then the action failed.
if(trade_inv:is_empty("npc_wants") and fields.back_to_talk) then
-- the action was aborted
yl_speak_up.execute_next_action(player, a_id, nil)
return
end
-- the player tried to give something; check if it is the right thing
if(not(trade_inv:is_empty("npc_wants"))) then
local stack = trade_inv:get_stack("npc_wants", 1)
-- check if it really is the item the NPC wanted; let the NPC take it
local is_correct_item = yl_speak_up.action_quest_item_take_back(player)
-- the action may have been a success or failure
yl_speak_up.execute_next_action(player, a_id, is_correct_item)
return
end
-- else show a message to the player
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:action_npc_wants",
formspec = "size[7,1.5]"..
"label[0.2,-0.2;"..
"Please insert the item for the npc and click on \"Done\"!\n"..
"If you don't have what he wants, click on \"Back to talk\".]"..
"button[2,1.0;1.5,0.9;back_from_error_msg;Back]"})
end
yl_speak_up.get_fs_action_npc_wants = function(player, param)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
return "size[8.5,8]"..
"list[current_player;main;0.2,3.85;8,1;]"..
"list[current_player;main;0.2,5.08;8,3;8]"..
"button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]"..
"button[4.75,1.6;1.5,0.9;finished_action;Done]"..
"tooltip[back_to_talk;Click here if you don't know what item the\n"..
"NPC wants or don't have the desired item.]"..
"tooltip[finished_action;Click here once you have placed the item in\n"..
"the waiting slot.]"..
"label[1.5,0.7;"..minetest.formspec_escape(dialog.n_npc or "- ? -")..
" expects something from you:]"..
"list[detached:yl_speak_up_player_"..pname..";npc_wants;3.25,1.5;1,1;]" ..
"label[1.5,2.7;Insert the right item and click on \"Done\" to proceed.]"
end
yl_speak_up.input_fs_action_text_input = function(player, formname, fields)
-- back from error_msg? then show the formspec again
if(fields.back_from_error_msg) then
-- the error message is only shown if the input was empty
yl_speak_up.show_fs(player, "action_text_input", "")
return
end
local pname = player:get_player_name()
local a_id = yl_speak_up.speak_to[pname].a_id
local a = yl_speak_up.get_action_by_player(player)
if(fields.back_to_talk) then
-- the action was aborted
yl_speak_up.execute_next_action(player, a_id, nil)
return
end
if(fields.finished_action and fields.quest_answer and fields.quest_answer ~= "") then
local n_id = yl_speak_up.speak_to[pname].n_id
-- is the answer correct?
-- strip leading and tailing blanks
local success = not(not(fields.quest_answer and a.a_value
and fields.quest_answer:trim() == a.a_value:trim()))
if(not(success)) then
yl_speak_up.log_change(pname, n_id,
"Action "..tostring(a_id)..
" "..tostring(yl_speak_up.speak_to[pname].o_id)..
" "..tostring(yl_speak_up.speak_to[pname].d_id)..
": Player answered with \""..tostring(fields.quest_answer:trim())..
"\", but we expected: \""..tostring(a.a_value:trim()).."\".")
else
yl_speak_up.log_change(pname, n_id,
"Action "..tostring(a_id)..
" "..tostring(yl_speak_up.speak_to[pname].o_id)..
" "..tostring(yl_speak_up.speak_to[pname].d_id)..
": Answer is correct.")
end
-- store what the player entered so that it can be examined by other functions
yl_speak_up.last_text_input[pname] = fields.quest_answer:trim()
-- the action was a either a success or failure
yl_speak_up.execute_next_action(player, a_id, success)
return
end
-- no scrolling desired
fields.button_up = nil
fields.button_down = nil
--[[ this is too disruptive; it's better to just let the player select a button
-- else show a message to the player
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:action_text_input",
formspec = "size[7,1.5]"..
"label[0.2,-0.2;"..
"Please answer the question and click on \"Send answer\"!\n"..
"If you don't know the answer, click on \"Back to talk\".]"..
"button[2,1.0;1.5,0.9;back_from_error_msg;Back]"})
--]]
end
yl_speak_up.get_fs_action_text_input = function(player, param)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
local a = yl_speak_up.get_action_by_player(player)
if(not(a)) then
return ""
end
local alternate_text =
(a.a_question or "Your answer:").."\n\n"..
(dialog.n_npc or "- ? -").." looks expectantly at you.]"
local formspec = {}
table.insert(formspec, "label[0.7,1.8;Answer:]")
table.insert(formspec, "button[45,1.0;9,1.8;finished_action;Send this answer]")
-- show the actual text for the option
yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
"field", "4.0,1.0;40,1.5",
"quest_answer",
";", --..minetest.formspec_escape("<your answer>"),
"Enter your answer here.",
true)
local h = 2.0
local pname_for_old_fs = nil
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"finished_action",
"Please enter your answer in the input field above and click here to "..
"send it.",
minetest.formspec_escape("[Send this answer]"),
true, nil, nil, pname_for_old_fs)
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"back_to_talk",
"If you don't know the answer or don't want to answer right now, "..
"choose this option to get back to the previous dialog.",
"I give up. Let's talk about something diffrent.",
true, nil, nil, pname_for_old_fs)
-- do not offer edit_mode in the trade formspec because it makes no sense there;
return yl_speak_up.show_fs_decorated(pname, nil, h, alternate_text, "",
table.concat(formspec, "\n"), nil, h)
--[[ old version with extra formspec
return --"size[12.0,4.5]"..
yl_speak_up.show_fs_simple_deco(12.0, 4.5)..
"button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]"..
"button[2.0,3.7;3.0,0.9;finished_action;Send answer]"..
"tooltip[back_to_talk;Click here if you don't know the answer.]"..
"tooltip[finished_action;Click here once you've entered the answer.]"..
"label[0.2,1.2;"..minetest.formspec_escape(a.a_question or "Your answer:").."]"..
"label[0.2,1.9;Answer:]"..
"field[1.6,2.2;10.0,0.6;quest_answer;;"..tostring(param or "").."]"..
"label[0.2,2.8;"..minetest.formspec_escape(
"["..(dialog.n_npc or "- ? -").." looks expectantly at you.]").."]"
--]]
end
-- action of the type "evaluate"
yl_speak_up.input_fs_action_evaluate = function(player, formname, fields)
local pname = player:get_player_name()
local a_id = yl_speak_up.speak_to[pname].a_id
-- the custom input_handler may have something to say here as well
local a = yl_speak_up.get_action_by_player(player)
if(player and a and a.a_value) then
local custom_data = yl_speak_up.custom_functions_a_[a.a_value]
if(custom_data and custom_data.code_input_handler) then
local n_id = yl_speak_up.speak_to[pname].n_id
local fun = custom_data.code_input_handler
-- actually call the function (which may change the value of fields)
fields = fun(player, n_id, a, formname, fields)
end
end
-- back from error_msg? then show the formspec again
if(fields.back_from_error_msg) then
yl_speak_up.show_fs(player, "action_evaluate", nil)
return
end
if(fields.back_to_talk) then
-- the action was aborted
yl_speak_up.execute_next_action(player, a_id, nil)
return
end
if(fields.failed_action) then
-- the action failed
yl_speak_up.execute_next_action(player, a_id, false)
return
end
if(fields.finished_action) then
-- the action was a success
yl_speak_up.execute_next_action(player, a_id, true)
return
end
if(fields.quit) then
return
end
-- else show a message to the player that he ought to decide
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:action_evaluate",
formspec = "size[7,1.5]"..
"label[0.2,-0.2;"..
"Please click on one of the offered options\nor select \"Back to talk\"!]"..
"button[2,1.0;1.5,0.9;back_from_error_msg;Back]"})
end
yl_speak_up.get_fs_action_evaluate = function(player, param)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
local n_id = yl_speak_up.speak_to[pname].n_id
local a = yl_speak_up.get_action_by_player(player)
if(not(a)) then
return ""
end
if(not(player) or not(a.a_value)) then
return "label[0.2,0.5;Ups! An internal error occoured. Please tell your "..
"local admin to check the brain of this lifeform here.]"..
"button[1.5,1.5;2,0.9;back_to_talk;Back]"
end
local custom_data = yl_speak_up.custom_functions_a_[a.a_value]
if(not(custom_data) or not(custom_data.code)) then
return "label[0.2,0.5;Ups! An internal error occoured. Please tell your "..
"local admin that the internal function "..
minetest.formspec_escape(tostring(a.a_value))..
"somehow got lost/broken.]"..
"button[1.5,1.5;2,0.9;back_to_talk;Back]"
end
local fun = custom_data.code
-- actually call the function
return fun(player, n_id, a)
end
-- -> now moved to fs/fs_action_*.lua

View File

@ -19,7 +19,11 @@ yl_speak_up.dropdown_values_deal_with_offered_item = {
-- how how to interact with the node
-- node_name the node to place
-- node_there the node that can currently be found at that position
yl_speak_up.check_blacklisted = function(how, node_name, node_there)
-- tool_name the name of the tool the NPC wants to use (punch or right-click with)
yl_speak_up.check_blacklisted = function(how, node_name, node_there, tool_name)
if(tool_name) then
return yl_speak_up.blacklist_effect_tool_use[ tool_name ]
end
return yl_speak_up.blacklist_effect_on_block_interact[ node_name ]
or yl_speak_up.blacklist_effect_on_block_interact[ node_there ]
or (how == "place" and yl_speak_up.blacklist_effect_on_block_place[ node_name ])
@ -31,6 +35,79 @@ yl_speak_up.check_blacklisted = function(how, node_name, node_there)
end
-- create fake playerdata so that the NPC can interact with inventories, punch and right-click blocks
yl_speak_up.get_fake_player = function(owner_name, wielded_item)
return {
get_player_name = function()
return owner_name
end,
is_player = function()
return true
end,
is_fake_player = true,
get_wielded_item = function(self, item)
return ItemStack(wielded_item)
end,
get_player_control = function()
-- NPC is not sneaking
return {}
end,
}
end
-- shall the NPC wield and use a tool? if so that tools' on_use or on_place
-- function takes precedence over the block it's used on
yl_speak_up.use_tool_on_block = function(r, fun_name, player, n_id, o_id)
if(not(r.r_wielded) or r.r_wielded == "") then
return false
end
-- we need the owner_name for creating the fake player
local owner_name = yl_speak_up.npc_owner[ n_id ]
if(not(owner_name) or owner_name == "") then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
r.r_type..": NPC does not have an owner. Aborting.")
return false
end
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)})
-- can the tool be used?
local tool_err_msg = nil
if(not(minetest.registered_items[r.r_wielded])) then
tool_err_msg = "Tool not defined"
elseif(not(minetest.registered_items[r.r_wielded][fun_name])) then
tool_err_msg = "Tool does not support "..tostring(r.r_value).."ing"
-- do not use forbidden tools
elseif(yl_speak_up.check_blacklisted(nil, nil, nil, r.r_wielded)) then
tool_err_msg = "NPC are not allowed to use this tool"
-- does the NPC have the item he's supposed to wield?
elseif(not(npc_inv:contains_item("npc_main", r.r_wielded, false))) then
tool_err_msg = "NPC lacks tool"
end
if(tool_err_msg) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id)..
" block: "..tostring(r.r_value).." - "..tool_err_msg.. ": \""..tostring(r.r_wielded).."\".")
return false
end
-- act in the name of the owner when accessing inventories
local fake_player = yl_speak_up.get_fake_player(owner_name, r.r_wielded)
local itemstack = fake_player:get_wielded_item()
local pointed_thing = {
type = "node",
under = r.r_pos,
above = {x=r.r_pos.x, y=r.r_pos.y+1, z=r.r_pos.z}
}
local new_itemstack = minetest.registered_items[r.r_wielded][fun_name](
itemstack, fake_player, pointed_thing)
minetest.chat_send_player("singleplayer", "Did the rightclicking. Result: "..new_itemstack:get_name().." fun_name: "..tostring(fun_name))
if(new_itemstack) then
-- apply any itemstack changes
npc_inv:remove_item("npc_main", itemstack)
npc_inv:add_item("npc_main", new_itemstack)
end
return true
end
-- called by yl_speak_up.input_talk(..)
-- and also by yl_speak_up.get_fs_trade_list(..)
--
@ -44,7 +121,8 @@ end
-- was encountered after an unsuccessful action *or* right after an
-- effect that returned false.
-- Note: In edit mode, effects will *not* be executed.
yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, action_was_successful, d_option)
yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, action_was_successful, d_option,
dry_run_no_exec) -- dry_run_no_exec for edit_mode
local target_dialog = ""
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
@ -59,16 +137,17 @@ yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, actio
n_id, d_id, o_id)
end
yl_speak_up.debug_msg(player, n_id, o_id, "No effects given.")
-- the player has visited this option successfully
yl_speak_up.count_visits_to_option(pname, o_id)
-- no effects? Then...return to the start dialog
return {next_dialog = "", alternate_text = nil}
end
local edit_mode = (yl_speak_up.edit_mode[pname] == n_id)
-- Important: the list of effects is *sorted* here. The order remains constant!
local sorted_key_list = yl_speak_up.sort_keys(effects)
if(not(sorted_key_list) or #sorted_key_list < 1) then
yl_speak_up.debug_msg(player, n_id, o_id, "Error: No effects found. At least one of "..
"type \"dialog\" is necessary.")
elseif(not(edit_mode)) then
elseif(not(dry_run_no_exec)) then
yl_speak_up.debug_msg(player, n_id, o_id, "Executing effects: "..
table.concat(sorted_key_list, ", ")..".")
else
@ -86,7 +165,7 @@ yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, actio
yl_speak_up.debug_msg(player, n_id, o_id, "..executing "..
tostring(r.r_id)..": "..yl_speak_up.show_effect(r, pname))
-- do not execute effects in edit mode
if(not(edit_mode)) then
if(not(dry_run_no_exec)) then
if(not(no_log)) then
yl_speak_up.debug_msg(player, n_id, o_id,
"Executing effect "..tostring(r.r_id)..".")
@ -118,7 +197,7 @@ yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, actio
end
if(r and r.r_type and r.r_type == "deal_with_offered_item") then
refuse_items = true
if(not(r.r_value) or r.r_value == "do_nothing") then
if(not(r.r_value) or r.r_value == "do_nothing" or r.r_value == "take_as_wanted") then
refuse_items = false
end
end
@ -137,6 +216,7 @@ yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, actio
tostring(r.r_id)..". New target dialog: "..tostring(r.r_value)..".")
-- we also stop execution here
-- any quest step is NOT set (because effects and/or action weren't successful)
-- the visit counter for this option is not incresed - after all the visit failed
return {next_dialog = r.r_value, alternate_text = r.alternate_text}
end
last_result = res
@ -161,6 +241,8 @@ yl_speak_up.execute_all_relevant_effects = function(player, effects, o_id, actio
yl_speak_up.quest_step_reached(player, d_option.quest_step, d_option.quest_id,
n_id, d_id, o_id)
end
-- the player has visited this option successfully
yl_speak_up.count_visits_to_option(pname, o_id)
return {next_dialog = target_dialog, alternate_text = alternate_text}
end
@ -313,7 +395,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
return false
end
-- do not interact with nodes on the blacklist
if(yl_speak_up.check_blacklisted(how_to_interact, node.name, node.name)) then
-- (this here is inventory interaction, so no need to check for tools)
if(yl_speak_up.check_blacklisted(how_to_interact, node.name, node.name, nil)) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
r.r_type..": Blocks of type \""..tostring(node.name).."\" do not allow "..
"interaction of type \""..tostring(r.r_value).."\" for NPC.")
@ -327,18 +410,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
return false
end
-- act in the name of the owner when accessing inventories
local fake_player = {
get_player_name = function() return owner_name end,
is_player = function() return true end,
is_fake_player = true,
get_wielded_item = function(self, item)
if(self._inventory and def.wield_list) then
return self._inventory:get_stack(def.wield_list, self._wield_index)
end
return ItemStack(self._wielded_item)
end,
}
-- TODO: get the fake player from pipeworks?
local fake_player = yl_speak_up.get_fake_player(owner_name, "")
local def = minetest.registered_nodes[ node.name ]
if(def and def[ "allow_metadata_inventory_"..how_to_interact ]) then
local res = def[ "allow_metadata_inventory_"..how_to_interact ](
@ -384,7 +456,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
local amount = 0
if(r.r_value == "take_all") then
amount = stack_got:get_count()
elseif(r.r_value == "takeas_wanted") then
elseif(r.r_value == "take_as_wanted") then
amount = stack_wanted:get_count()
-- the NPC didn't get enough
if(amount > stack_got:get_count()) then
@ -393,6 +465,16 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
"is smaller than what the NPC wanted: \""..
tostring(stack_wanted).."\".")
return false
elseif(amount < stack_got:get_count()) then
local rest = stack_got:get_count() - amount
local player_inv = player:get_inventory()
if(player_inv) then
local rest_stack = ItemStack(stack_got:to_table())
rest_stack:set_count(rest)
player_inv:add_item("main", rest_stack)
stack_got:set_count(amount)
inv:set_stack("npc_wants", 1, stack_got)
end
end
end
local take_stack = stack_got:get_name().." "..tostring(amount)
@ -417,6 +499,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
elseif(r.r_type == "function") then
-- this can only be set and edited with the staff
if(not(yl_speak_up.npc_has_priv(n_id, "effect_exec_lua", r.r_is_generic))) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
r.r_type..": The NPC does not have the \"effect_exec_lua\" priv.")
return false
end
return yl_speak_up.eval_and_execute_function(player, r, "r_")
@ -426,6 +510,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
return false
end
if(not(yl_speak_up.npc_has_priv(n_id, "effect_give_item", r.r_is_generic))) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
r.r_type..": The NPC does not have the \"effect_give_item\" priv.")
return false
end
local item = ItemStack(r.r_value)
@ -446,6 +532,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
-- this can only be set and edited with the staff
elseif(r.r_type == "take_item") then
if(not(yl_speak_up.npc_has_priv(n_id, "effect_take_item", r.r_is_generic))) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
r.r_type..": The NPC does not have the \"effect_take_item\" priv.")
return false
end
if(not(r.r_value)) then
@ -467,6 +555,8 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
-- this can only be set and edited with the staff
elseif(r.r_type == "move") then
if(not(yl_speak_up.npc_has_priv(n_id, "effect_move_player", r.r_is_generic))) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
r.r_type..": The NPC does not have the \"effect_move_player\" priv.")
return false
end
-- copeid/moved here from AliasAlreadyTakens code in functions.lua
@ -613,7 +703,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
return false
end
-- do not interact with nodes on the blacklist
if(yl_speak_up.check_blacklisted(r.r_value, r.r_node, node.name)) then
if(yl_speak_up.check_blacklisted(r.r_value, r.r_node, node.name, nil)) then
-- construct the right text for the error message
local nname = node.name
if(r.r_value == "place") then
@ -635,6 +725,14 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
" and thus cannot interact with it.")
return false
end
-- create a fake player and a suitable itemstack
local owner_name = yl_speak_up.npc_owner[ n_id ]
if(not(owner_name) or owner_name == "") then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
r.r_type..": NPC does not have an owner. Aborting.")
return false
end
-- "If there is air: Place a block so that it looks like now.", -- 2
if(r.r_value and r.r_value == "place") then
if(is_protected) then
@ -692,7 +790,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
local npc_inv = minetest.get_inventory({type="detached",
name="yl_speak_up_npc_"..tostring(n_id)})
-- put the drops into the inventory of the NPC
for i, d in ipairs(drop_list) do
for i, d in ipairs(drop_list or {}) do
local rest = npc_inv:add_item("npc_main", ItemStack(d))
if(rest and not(rest:is_empty()) and rest:get_count()>0) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id)..
@ -705,11 +803,23 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
return true
-- "Punch the block.", -- 4
elseif(r.r_value and r.r_value == "punch") then
-- shall the NPC wield and use an item? if so that items' on_use function takes
-- precedence
if(r.r_wielded and r.r_wielded ~= "") then
return yl_speak_up.use_tool_on_block(r, "on_use", player, n_id, o_id)
end
-- even air can be punched - even if that is pretty pointless
minetest.punch_node(r.r_pos)
-- TODO: some blocks may define their own functions and care for what the player wields (i.e. cheese mod)
minetest.punch_node(r.r_pos, nil)
return true
-- "Right-click the block.", -- 5
elseif(r.r_value and r.r_value == "right-click") then
-- shall the NPC wield and use an item? if so that items' on_use function takes
-- precedence
if(r.r_wielded and r.r_wielded ~= "") then
return yl_speak_up.use_tool_on_block(r, "on_place", player, n_id, o_id)
end
-- with a tool, clicking on air might make sense; without a tool it doesn't
if(not(node) or not(node.name) or not(minetest.registered_nodes[node.name])) then
return false
end
@ -743,14 +853,18 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
"block: Opened/closed trapdoor at "..pos_str..".")
elseif(minetest.registered_nodes[node.name]
and minetest.registered_nodes[node.name].on_rightclick
and minetest.registered_nodes[node.name].on_rightclick(r.r_pos, node, nil)) then
minetest.registered_nodes[node.name].on_rightclick(r.r_pos, node, nil)
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
"block: right-clicked at at pos "..pos_str..".")
else
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
"block: right-click at at pos "..pos_str.." had no effect.")
and minetest.registered_nodes[node.name].on_rightclick) then
local fake_player = yl_speak_up.get_fake_player(owner_name, "")
local itemstack = ItemStack("")
local pointed_thing = nil -- TODO
if(minetest.registered_nodes[node.name].on_rightclick(
r.r_pos, node, fake_player, itemstack, pointed_thing)) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
"block: right-clicked at at pos "..pos_str..".")
else
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
"block: right-click at at pos "..pos_str.." had no effect.")
end
end
end
return false
@ -763,7 +877,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
input.items = {}
-- multiple slots in the craft grid may contain the same item
local sum_up = {}
for i, v in ipairs(r.r_craft_grid) do
for i, v in ipairs(r.r_craft_grid or {}) do
if(v and v ~= "") then
local stack = ItemStack(v)
-- store this for later crafting
@ -809,7 +923,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..
"Great: Crafting is possible!")
-- actually consume the items required, return the ones in decremented_input
for i, v in ipairs(r.r_craft_grid) do
for i, v in ipairs(r.r_craft_grid or {}) do
if(v and v ~= "") then
npc_inv:remove_item("npc_main", ItemStack(v))
end
@ -821,7 +935,7 @@ yl_speak_up.execute_effect = function(player, n_id, o_id, r)
end
npc_inv:add_item("npc_main", output.item)
-- add the decremented_inputs
for k,v in pairs(decremented_input.items) do
for k,v in pairs(decremented_input.items or {}) do
if(k and not(v:is_empty())) then
if(not(npc_inv:room_for_item("npc_main", v))) then
yl_speak_up.debug_msg(player, n_id, o_id, tostring(r.r_id).." "..

View File

@ -6,7 +6,7 @@
-- this is called directly in yl_speak_up.get_fs_talkdialog
-- it returns a list of options whose preconditions are fulfilled
-- allow_recursion may be false - we need to avoid infinite loops
yl_speak_up.calculate_displayable_options = function(pname, d_options, in_edit_mode, allow_recursion)
yl_speak_up.calculate_displayable_options = function(pname, d_options, allow_recursion)
-- Let's go through all the options and see if we need to display them to the user
local retval = {}
@ -21,7 +21,6 @@ yl_speak_up.calculate_displayable_options = function(pname, d_options, in_edit_m
-- list can work without causing loops or the like
local sorted_list = yl_speak_up.get_sorted_options(d_options, "o_sort")
for i, o_k in ipairs(sorted_list) do
if(not(in_edit_mode)) then
local o_v = d_options[ o_k ]
-- Can we display this option?
retval[o_k] = yl_speak_up.eval_all_preconditions(player, o_v.o_prerequisites, o_k, retval,o_v)
@ -32,10 +31,6 @@ yl_speak_up.calculate_displayable_options = function(pname, d_options, in_edit_m
retval["autoanswer"] = o_k
return retval
end
-- if in edit mode: no need to evaluate preconditions
else
retval[o_k ] = true
end
end
return retval
end
@ -79,7 +74,7 @@ yl_speak_up.eval_all_preconditions = function(player, prereq, o_id, other_option
-- during this call, so we can cache them
local properties = yl_speak_up.get_npc_properties(pname)
for k, p in pairs(prereq) do
for k, p in pairs(prereq or {}) do
yl_speak_up.debug_msg(player, n_id, o_id, "..checking "..
tostring(p.p_id)..": "..yl_speak_up.show_precondition(p, pname))
if(not(yl_speak_up.eval_precondition(player, n_id, p, other_options_true_or_false, properties))) then
@ -387,8 +382,11 @@ yl_speak_up.eval_precondition = function(player, n_id, p, other_options_true_or_
if(p.p_match_stack_size and p.p_match_stack_size ~= "") then
local c_got = stack_got:get_count()
local c_wanted = stack_wanted:get_count()
if( p.p_match_stack_size == "exactly" and c_got ~= c_wanted) then
return false -- not exactly the same amount as the wanted one
elseif(p.p_match_stack_size == "at_least" and c_got < c_wanted) then
return false -- didn't get at least the wanted amount
elseif(p.p_match_stack_size == "less" and c_got >= c_wanted) then
return false -- didn't get less than the given number
elseif(p.p_match_stack_size == "more" and c_got <= c_wanted) then
@ -418,6 +416,13 @@ yl_speak_up.eval_precondition = function(player, n_id, p, other_options_true_or_
and other_options_true_or_false
and other_options_true_or_false[ p.p_value ] ~= nil
and tostring(other_options_true_or_false[ p.p_value ]) == tostring(p.p_fulfilled))
elseif(p.p_type == "entity_type") then
local pname = player:get_player_name()
-- is the NPC type the same as the requested entity_type?
return (p.p_value
and yl_speak_up.speak_to[pname]._self
and yl_speak_up.speak_to[pname]._self.name
and yl_speak_up.speak_to[pname]._self.name == p.p_value)
end
-- fallback - unknown type
return false
@ -463,7 +468,7 @@ yl_speak_up.eval_trade_list_preconditions = function(player)
for i, s_o_id in ipairs(sorted_o_list) do
local prereq = options[s_o_id].o_prerequisites
local all_ok = true
for k, p in pairs(prereq) do
for k, p in pairs(prereq or {}) do
if(not(yl_speak_up.eval_precondition_npc_inv(p, inv, inv_name))) then
all_ok = false
break

566
export_to_ink.lua Normal file
View File

@ -0,0 +1,566 @@
-- helper functions for export_to_ink_language:
-- this table will hold the functions for exporting to ink so that we don't fill that namespace too much
yl_speak_up.export_to_ink = {}
-- an abbreviation
local ink_export = yl_speak_up.export_to_ink
-- use d_name field (name of dialog) instead of n_<id>_d_<id>
local use_d_name = true
-- in order to be able to deal with multiple NPC in ink, we use the NPC id n_id
-- plus the dialog id d_id as a name prefix; o_id, a_id and r_id are appended
-- as needed
yl_speak_up.export_to_ink.print_knot_name = function(lines, knot_name, use_prefix, dialog_names)
if(knot_name and dialog_names[knot_name]) then
knot_name = dialog_names[knot_name]
end
knot_name = use_prefix..tostring(knot_name or "ERROR")
table.insert(lines, "\n\n=== ")
table.insert(lines, knot_name)
table.insert(lines, " ===")
return knot_name
end
-- execution of effects ends if an on_failure effect is reached; for ink to be able to
-- display effects (as tags) correctly, we need to add them at the right place - some
-- tags come after the option/choice, some after the last action (if there is an action),
-- some between on_failure actions (if they exist)
yl_speak_up.export_to_ink.add_effect_tags = function(text, sorted_e_list, effects, start_at_effect)
if(not(text)) then
text = ""
end
if(not(start_at_effect) or start_at_effect > #sorted_e_list) then
return text
end
for i = start_at_effect, #sorted_e_list do
local r_id = sorted_e_list[i]
if(effects and effects[r_id]) then
local r = effects[r_id]
if(r and r.r_type and r.r_type == "on_failure") then
-- end as soon as we reach the next on_failure dialog
return text
end
if(r and r.r_type and r.r_type ~= "dialog") then
if(text ~= "") then
text = text.."\n "
end
-- the dialog effect is something diffrent
text = text.."# effect "..tostring(r_id).." "..tostring(yl_speak_up.show_effect(r))
end
end
end
return text
end
-- choices are a bit complicated as they may contain alternate_text that is to be
-- displayed instead (in yl_speak_up) and before (in ink) shwoing the target dialog text;
-- also, the divert_to target dialog may need to be rewritten
yl_speak_up.export_to_ink.print_choice = function(lines, choice_text, use_prefix, start_dialog,
alternate_text, divert_to, only_once, label,
precondition_list, effect_list,
dialog_names)
-- usually, options/answers/choices can be selected multiple times;
-- we support the default ink way of "*" as well (but only until the player stops talking,
-- not persistently stored)
if(not(only_once)) then
table.insert(lines, "\n+ ")
else
table.insert(lines, "\n* ")
end
-- helps to regcognize what has been changed how when importing again
if(label and label ~= "") then
table.insert(lines, "(")
table.insert(lines, tostring(label))
table.insert(lines, ") ")
end
-- are there any preconditions which can be handled by ink? most can not as they can
-- only be determined ingame (i.e. state of a block); even the value of variables may
-- have been changed externally
if(precondition_list and #precondition_list > 0) then
for _, p_text in ipairs(precondition_list) do
if(p_text ~= "") then
table.insert(lines, "{ ")
table.insert(lines, p_text)
table.insert(lines, " } ")
end
end
end
-- don't repeat the text of the choice in the output when running ink
table.insert(lines, "[")
table.insert(lines, choice_text)
table.insert(lines, "]")
-- dialogs, actions and effects can have an alternate_text with which they override the
-- text of the target_dialog/divert_to;
-- this isn't perfect as alternate_text supports $TEXT$ for inserting the text of the
-- target dialog anywhere in the alternate_text - while ink will print out this alternate_text
-- first and then that of the target dialog/divert_to
if(alternate_text and alternate_text ~= "") then
-- a new line and some indentation makes this more readable
table.insert(lines, "\n ")
table.insert(lines, alternate_text)
-- write the divert into a new line as well
table.insert(lines, "\n ")
end
-- setting a variable to a value is something we can model in ink as well
if(effect_list and #effect_list > 0) then
for _, e_text in ipairs(effect_list) do
table.insert(lines, "\n ~ ")
table.insert(lines, e_text)
end
-- the divert needs to be put into a new line
table.insert(lines, "\n")
end
-- actually go to the dialog this option leads to
table.insert(lines, " -> "..use_prefix)
if(not(start_dialog) or start_dialog == "") then
start_dialog = "d_1"
end
if(not(divert_to) or divert_to == "") then
-- go back to the start dialog (the start dialog may have been changed)
divert_to = tostring(start_dialog)
elseif(divert_to == "d_end" or divert_to == use_prefix.."d_end") then
-- go back to choosing between talking to NPC and end
divert_to = "d_end"
else
divert_to = tostring(divert_to)
end
if(dialog_names and dialog_names[divert_to]) then
divert_to = dialog_names[divert_to]
end
table.insert(lines, divert_to)
end
-- this prints the dialog as a knot - but without choices (those are added to the lines table later)
-- d: dialog
yl_speak_up.export_to_ink.print_dialog_knot = function(lines, use_prefix, d_id, d, dialog_names)
local knot_name = ink_export.print_knot_name(lines, d_id, use_prefix, dialog_names)
-- many characters at the start of a line have a special meaning;
-- hopefully they will not be obstrusive later on;
-- TODO: in order to be on the safe side: add a ":" in front of each line?
local t = d.d_text or ""
if(t == "") then
-- entirely empty text for knots does not work
t = "No text."
end
-- t = string.gsub(t, "\n([:>=])", "\n %1")
table.insert(lines, "\n")
table.insert(lines, t)
return knot_name
end
-- actions can fail *and* be aborted by the player; in order to model that in ink, we add
-- a knot for each action
-- Parameter:
-- a action
yl_speak_up.export_to_ink.print_action_knot = function(lines, use_prefix, d_id, o_id, start_dialog,
a, alternate_text_on_success, next_target, dialog_names,
e_list_on_success)
local action_prefix = use_prefix.."action_"..tostring(a.a_id).."_"..tostring(o_id).."_"
local knot_name = ink_export.print_knot_name(lines, d_id, action_prefix, dialog_names)
table.insert(lines, "\n:action: ")
table.insert(lines, a.a_id)
table.insert(lines, " ")
table.insert(lines, yl_speak_up.show_action(a))
table.insert(lines, "A: "..minetest.serialize(a or {})..".")
ink_export.print_choice(lines, "Action was successful", use_prefix, start_dialog,
alternate_text_on_success, next_target, false, nil,
nil, e_list_on_success, dialog_names)
ink_export.print_choice(lines, "Action failed", use_prefix, start_dialog,
a.alternate_text, a.a_on_failure, false, nil,
nil, nil, dialog_names)
ink_export.print_choice(lines, "Back", use_prefix, start_dialog,
nil, tostring(d_id), false, nil,
nil, nil, dialog_names)
return string.sub(knot_name, string.len(use_prefix)+1)
end
-- there is a special on_failure effect that can lead to a diffrent target dialog and print
-- out a diffrent alternate_text if the *previous* effect failed; in order to model that in
-- ink, we add a knot for such on_failure effects
-- Parameter:
-- r effect/result
-- r_prev previous effect
yl_speak_up.export_to_ink.print_effect_knot = function(lines, use_prefix, d_id, o_id, start_dialog,
r, r_prev, alternate_text_on_success, next_target,
dialog_names)
local effect_prefix = use_prefix.."effect_"..tostring(r.r_id).."_"..tostring(o_id).."_"
local knot_name = ink_export.print_knot_name(lines, d_id, effect_prefix, dialog_names)
table.insert(lines, "\n:effect: ")
table.insert(lines, r.r_id)
table.insert(lines, " ")
-- show text of the *previous effect* - because that is the one which may have failed:
table.insert(lines, yl_speak_up.show_effect(r))
table.insert(lines, "\nThe previous effect was: ")
table.insert(lines, r_prev.r_id)
table.insert(lines, " ")
-- show text of the *previous effect* - because that is the one which may have failed:
table.insert(lines, yl_speak_up.show_effect(r_prev))
ink_export.print_choice(lines, "Effect was successful", use_prefix, start_dialog,
alternate_text_on_success, next_target, false, nil,
nil, nil, dialog_names)
ink_export.print_choice(lines, "Effect failed", use_prefix, start_dialog,
r.alternate_text, r.r_value, false, nil,
nil, nil, dialog_names)
return string.sub(knot_name, string.len(use_prefix)+1)
end
-- which variables are used by this NPC?
yl_speak_up.export_to_ink.print_variables_used = function(lines, dialog)
if(not(dialog) or not(dialog.n_dialogs)) then
return
end
local vars_used = {}
for d_id, d_data in pairs(dialog.n_dialogs or {}) do
for o_id, o_data in pairs(d_data.d_options or {}) do
-- variables may be used in preconditions
for p_id, p in pairs(o_data.o_prerequisites or {}) do
-- we are checking the state of a variable
if(p and p.p_type and p.p_type == "state") then
-- store as key in order to avoid duplicates
vars_used[ p.p_variable ] = true
-- properties are comparable to variables
elseif(p and p.p_type and p.p_type == "property") then
vars_used[ "property "..p.p_value ] = true
end
end
for r_id, r in pairs(o_data.o_results or {}) do
if(r and r.r_type and r.r_type == "state") then
vars_used[ r.r_variable ] = true
elseif(r and r.r_type and r.r_type == "property") then
vars_used[ "property "..r.r_value ] = true
end
end
end
end
table.insert(lines, "\n")
-- we stored as key/value in order to avoid duplicates
for var_name, _ in pairs(vars_used) do
-- replace blanks with an underscore in an attempt to turn it into a legal var name
-- (this is not really sufficient as var names in yl_speak_up are just strings,
-- while the ink language expects sane var names like other lanugages)
-- TODO: this is not necessarily a legitimate var name!
local parts = string.split(var_name, " ")
table.remove(parts, 1)
local v_name = table.concat(parts, "_")
-- stor it for later use
vars_used[var_name] = v_name
-- add the variable as a variable to INK
table.insert(lines, "\nVAR ")
table.insert(lines, v_name)
table.insert(lines, " = false") -- start with undefined/nil (we don't know the stored value)
end
table.insert(lines, "\n")
return vars_used
end
-- which preconditions and effects can be modelled in ink?
--
-- in singleplayer adventures, properties can be relevant as well;
-- in multiplayer, other players may affect the state of the property
--
-- *some* functions may be relevant here:
-- (but not for variables)
-- * compare a variable with a variable
-- * counted dialog option visits
-- * counted option visits
--
-- types "true" and "false" can be relevant later on
-- small helper function
local var_with_operator = function(liste, var_name, op, var_cmp_value, vars_used)
-- visits are not stored as variables in ink
if(not(vars_used[var_name])) then
vars_used[var_name] = var_name
end
if(op == "~=") then
op = "!="
end
if(op=="==" or op=="!=" or op==">=" or op==">" or op=="<=" or op==">") then
table.insert(liste, tostring(vars_used[var_name]).." ".. op.." "..tostring(var_cmp_value))
elseif(op=="not") then
table.insert(liste, "not "..tostring(vars_used[var_name]))
elseif(op=="is_set") then
table.insert(liste, tostring(vars_used[var_name]))
elseif(op=="is_unset") then
table.insert(liste, tostring(vars_used[var_name]).." == false")
end
-- the following values for op cannot really be checked here and are not printed:
-- "more_than_x_seconds_ago","less_than_x_seconds_ago",
-- "quest_step_done", "quest_step_not_done"
end
yl_speak_up.export_to_ink.translate_precondition_list = function(dialog, preconditions, vars_used, use_prefix,
dialog_names)
-- collect preconditions that may work in ink
local liste = {}
-- variables may be used in preconditions
for p_id, p in pairs(preconditions or {}) do
if(p and p.p_type and p.p_type == "state") then
-- state changes of variables may mostly work in ink as well
var_with_operator(liste, p.p_variable, p.p_operator, p.p_var_cmp_value, vars_used)
elseif(p and p.p_type and p.p_type == "property") then
-- same with properties
var_with_operator(liste, p.p_value, p.p_operator, p.p_var_cmp_value, vars_used)
elseif(p and p.p_type and p.p_type == "evaluate" and p.p_value == "counted_visits_to_option") then
-- simulate the visit counter that ink has in yl_speak_up
local tmp_var_name = use_prefix..p.p_param1
if(dialog_names[tmp_var_name]) then
tmp_var_name = use_prefix..dialog_names[tmp_var_name].."."..tostring(p.p_param2)
else
tmp_var_name = tmp_var_name.. "_"..tostring(p.p_param2)
end
var_with_operator(liste, tmp_var_name, p.p_operator, p.p_var_cmp_value, vars_used)
elseif(p and p.p_type and p.p_type == "true") then
table.insert(liste, p.p_type)
elseif(p and p.p_type and p.p_type == "false") then
table.insert(liste, p.p_type)
end
end
return liste
end
-- small helper function
local set_var_to_value = function(liste, var_name_full, op, val, vars_used)
if(not(vars_used[var_name_full])) then
vars_used[var_name_full] = var_name_full
end
local var_name = vars_used[var_name_full]
if(op == "set_to") then
table.insert(liste, tostring(var_name).." = "..tostring(val))
elseif(op == "unset") then
-- TODO: there does not seem to be a none/nil type in the Ink language
table.insert(liste, tostring(var_name).." = false ")
elseif(op == "maximum") then
table.insert(liste, tostring(var_name).." = max("..tostring(var_name)..", "..tostring(val))
elseif(op == "minimum") then
table.insert(liste, tostring(var_name).." = min("..tostring(var_name)..", "..tostring(val))
elseif(op == "increment") then
table.insert(liste, tostring(var_name).." = "..tostring(var_name).." + "..tostring(val))
elseif(op == "decrement") then
table.insert(liste, tostring(var_name).." = "..tostring(var_name).." - "..tostring(val))
-- not supported: "set_to_current_time", "quest_step"
end
end
yl_speak_up.export_to_ink.translate_effect_list = function(dialog, effects, vars_used)
-- collect effects that may work in ink
local liste = {}
-- variables may be set in effects
for r_id, r in pairs(effects or {}) do
if(r and r.r_type and r.r_type == "state") then
-- state changes of variables may mostly work in ink as well
set_var_to_value(liste, r.r_variable, r.r_operator, r.r_var_cmp_value, vars_used)
elseif(p and p.p_type and p.p_type == "property") then
-- same with properties
set_var_to_value(liste, r.r_value, r.r_operator, r.r_var_cmp_value, vars_used)
end
end
return liste
end
-- Note: use_prefix ought to be tostring(n_id).."_" or ""
yl_speak_up.export_to_ink_language = function(dialog, use_prefix)
local start_dialog = yl_speak_up.get_start_dialog_id(dialog)
if(not(start_dialog)) then
start_dialog = "d_1"
end
if(use_d_name
and dialog.n_dialogs
and dialog.n_dialogs[start_dialog]
and dialog.n_dialogs[start_dialog].d_name) then
start_dialog = dialog.n_dialogs[start_dialog].d_name
else
start_dialog = tostring(start_dialog)
end
-- prefix all dialog names with this;
-- advantage: several NPC dialog exports can be combined into one inc game
-- where the player can talk to diffrent NPC (which can have the
-- same dialog names without conflict thanks to the prefix)
-- use_prefix = tostring(n_id).."_"
if(not(use_prefix)) then
use_prefix = ""
end
-- go to the main loop whenever the player ends the conversation with the NPC;
-- this allows to create an additional dialog in INK where the player can then
-- decide to talk to multiple NPC - or to continue his conversation with the
-- same NPC
local main_loop = use_prefix.."d_end"
local tmp = {"-> ", main_loop,
"\n=== ", main_loop, " ===",
"\nWhat do you wish to do?",
"\n+ Talk to ", tostring(dialog.n_npc or prefix or "-unknown-"), " -> ", use_prefix..tostring(start_dialog),
"\n+ End -> END"}
local vars_used = ink_export.print_variables_used(tmp, dialog)
local sorted_d_list = yl_speak_up.get_dialog_list_for_export(dialog)
-- d_got_item may contain alternate texts - so it is of intrest here
-- (also links to other dialogs)
if(dialog.n_dialogs["d_got_item"]) then
table.insert(sorted_d_list, "d_got_item")
end
-- maybe not that useful to set up this one in inK; add it for completeness
if(dialog.n_dialogs["d_trade"]) then
table.insert(sorted_d_list, "d_trade")
end
-- make use of dialog names if wanted
local dialog_names = {}
for i, d_id in ipairs(sorted_d_list) do
if(use_d_name) then
local n = tostring(d_id)
local d = dialog.n_dialogs[d_id]
dialog_names[n] = (d.d_name or n)
end
end
for i, d_id in ipairs(sorted_d_list) do
-- store the knots for actions and effects here:
local tmp2 = {}
local d = dialog.n_dialogs[d_id]
-- print the dialog knot, but without choices (those we add in the following loop)
local this_knot_name = ink_export.print_dialog_knot(tmp, use_prefix, d_id, d, dialog_names)
-- iterate over all options
local sorted_o_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options or {}, "o_sort")
for j, o_id in ipairs(sorted_o_list) do
local o_data = d.d_options[o_id]
local sorted_a_list = yl_speak_up.sort_keys(o_data.actions or {})
local sorted_e_list = yl_speak_up.sort_keys(o_data.o_results or {})
-- we will get alternate_text from the dialog result later on
local alternate_text_on_success = ""
local target_dialog = nil
-- what is the normal target dialog/divert (in ink language) of this dialog?
for k, r_id in ipairs(sorted_e_list) do
local r = o_data.o_results[r_id]
if(r and r.r_type and r.r_type == "dialog") then
target_dialog = tostring(r.r_value)
alternate_text_on_success = r.alternate_text or ""
end
end
-- iterate backwards through the effects and serach for on_failure;
-- the first effect cannot be an on_failure effect because on_failure effects
-- decide on failure/success of the *previous* effect
for k = #sorted_e_list, 2, -1 do
local r_id = sorted_e_list[k]
local r = o_data.o_results[r_id]
if(r and r.r_type and r.r_type == "on_failure") then
local r_prev = o_data.o_results[sorted_e_list[k-1]]
-- *after* this effect we still need to execute all the other
-- remaining effects (read: add them as tag)
alternate_text_on_success = ink_export.add_effect_tags(
alternate_text_on_success,
sorted_e_list, o_data.o_results, k)
-- whatever dialog comes previously - the dialog, an action, or
-- another on_failure dialog - needs to lead to this dialog
target_dialog = ink_export.print_effect_knot(tmp2,
use_prefix, d_id, o_id, start_dialog,
r, r_prev,
alternate_text_on_success, target_dialog,
dialog_names)
-- we have dealt with the alternate text (it will only be shown
-- in the last on_failure dialog before we go to the target)
alternate_text_on_success = ""
end
end
-- add the remaining effects
alternate_text_on_success = ink_export.add_effect_tags(
alternate_text_on_success,
sorted_e_list, o_data.o_results, 1)
-- if it is an action knot then the effects have to go to the action knot
local e_list = ink_export.translate_effect_list(dialog, o_data.o_results,
vars_used)
-- iterate backwards through the actions (though usually only one is supported)
for k = #sorted_a_list, 1, -1 do
local a_id = sorted_a_list[k]
local a = o_data.actions[a_id]
target_dialog = ink_export.print_action_knot(tmp2,
use_prefix, d_id, o_id, start_dialog,
a,
alternate_text_on_success, target_dialog, dialog_names,
e_list)
-- has been dealt with
alternate_text_on_success = ""
end
-- which preconditions can be translated to ink?
local p_list = ink_export.translate_precondition_list(dialog, o_data.o_prerequisites,
vars_used, use_prefix, dialog_names)
-- what remains is to print the option/choice itself
local o_text = o_data.o_text_when_prerequisites_met
local o_prefix = ""
if(d.o_random) then
o_text = "[One of these options is randomly selected]"
o_prefix = "randomly_"
elseif(o_data.o_autoanswer) then
o_text = "[Automaticly selected if preconditions are met]"
o_prefix = "automaticly_"
end
-- if the target is an action knot: do not print the effect list as that belongs
-- to the action knot!
if(#sorted_a_list > 0) then
e_list = {}
end
ink_export.print_choice(tmp,
o_text, use_prefix, start_dialog,
alternate_text_on_success, target_dialog,
o_data.o_visit_only_once, -- print + (often) or * (only once)
o_prefix..o_id, p_list, e_list, dialog_names)
-- deal with o_grey_when_prerequisites_not_met (grey out this answer)
if( o_data.o_text_when_prerequisites_not_met
and o_data.o_text_when_prerequisites_not_met ~= ""
and o_data.o_grey_when_prerequisites_not_met
and o_data.o_grey_when_prerequisites_not_met == "true") then
o_text = o_data.o_text_when_prerequisites_not_met
-- this option cannot be selected - so choose d_end as target dialog
ink_export.print_choice(tmp,
o_text, use_prefix, start_dialog,
alternate_text_on_success, "d_end",
o_data.o_visit_only_once, -- print + (often) or * (only once)
"grey_out_"..o_id, p_list, e_list, dialog_names)
end
-- Note: Showing an alternate text if the preconditions are not met is not
-- covered here. It makes little sense for the NPC as the option appears
-- but cannot be clicked. It exists for backward compatibility of old NPC
-- on the Your Land server.
end -- dealt with the option
-- add way to end talking to the NPC
ink_export.print_choice(tmp, "Farewell!", use_prefix, start_dialog,
nil, "d_end", false, nil, dialog_names)
-- add the knots for actions and effects for this dialog and all its options:
for _, line in ipairs(tmp2) do
table.insert(tmp, line)
end
end
return table.concat(tmp, "")
end

View File

@ -1,248 +0,0 @@
-- helper function:
-- create a formspec dropdown list with player names (first entry: Add player) and
-- an option to delete players from that list
-- Note: With the what_is_the_list_about-parameter, it is possible to handle i.e. variables as well
yl_speak_up.create_dropdown_playerlist = function(player, pname,
table_of_names, index_selected,
start_x, start_y, stretch_x, h, dropdown_name, what_is_the_list_about, delete_button_text,
field_name_for_adding_player, explain_add_player,
field_name_for_deleting_player, explain_delete_player)
local text = "dropdown["..tostring(start_x)..","..tostring(start_y)..";"..
tostring(3.8 + stretch_x)..","..tostring(h)..";"..
tostring(dropdown_name)..";Add "..tostring(what_is_the_list_about)..":"
-- table_of_names is a table with the playernames as keys
-- we want to work with indices later on; in order to be able to do that reliably, we
-- need a defined order of names
local tmp_list = yl_speak_up.sort_keys(table_of_names, true)
for i, p in ipairs(tmp_list) do
text = text..","..minetest.formspec_escape(p)
end
-- has an entry been selected?
if(not(index_selected) or index_selected < 0 or index_selected > #tmp_list+1) then
index_selected = 1
end
text = text..";"..tostring(index_selected)..";]"
if(index_selected == 1) then
-- first index "Add player" selected? Then offer a field for entering the name
text = text.."field["..tostring(start_x + 4.0 + stretch_x)..","..tostring(start_y)..
";"..tostring(3.5 + stretch_x)..","..tostring(h)..";"..
tostring(field_name_for_adding_player)..";;]"..
"tooltip["..tostring(field_name_for_adding_player)..";"..
tostring(explain_add_player).."]"
else
text = text.."button["..tostring(start_x + 3.8 + stretch_x)..","..tostring(start_y)..
";"..tostring(3.4 + stretch_x)..","..tostring(h)..";"..
tostring(field_name_for_deleting_player)..";"..
tostring(delete_button_text).."]"..
"tooltip["..tostring(field_name_for_deleting_player)..";"..
tostring(explain_delete_player).."]"
end
return text
end
-- manages back, exit, prev, next, add_list_entry, del_entry_general
--
-- if a new entry is to be added, the following function that is passed as a parmeter
-- is called:
-- function_add_new_entry(pname, fields.add_entry_general)
-- expected return value: index of fields.add_entry_general in the new list
--
-- if an entry is to be deleted, the following function that is passed as a parameter
-- is called:
-- function_del_old_entry(pname, entry_name)
-- expected return value: text describing weather the removal worked or not
--
-- if any other fields are set that this function does not process, the following
-- function that is passed on as a parameter can be used:
-- function_input_check_fields(player, formname, fields, entry_name, list_of_entries)
-- expected return value: nil if the function found work; else entry_name
--
yl_speak_up.input_fs_manage_general = function(player, formname, fields,
what_is_the_list_about, min_length, max_length, function_add_new_entry,
list_of_entries, function_del_old_entry, function_input_check_fields)
local pname = player:get_player_name()
local what = minetest.formspec_escape(what_is_the_list_about or "?")
local fs_name = formname
if(formname and string.sub(formname, 0, 12) == "yl_speak_up:") then
formname = string.sub(formname, 13)
end
if(fields and fields.back_from_msg) then
yl_speak_up.show_fs(player, formname, fields.stored_value_for_player)
return
end
-- leave this formspec
if(fields and (fields.quit or fields.exit or fields.back)) then
local last_fs = yl_speak_up.speak_to[pname][ "working_at" ]
local last_params = yl_speak_up.speak_to[pname][ "working_at_params" ]
yl_speak_up.speak_to[pname].tmp_index_general = nil
yl_speak_up.show_fs(player, last_fs, last_params)
return
-- add a new entry?
elseif(fields and fields.add_list_entry) then
local error_msg = ""
if(not(fields.add_entry_general) or fields.add_entry_general == ""
or fields.add_entry_general:trim() == "") then
error_msg = "Please enter the name of the "..what.." you want to create!"
-- limit names to something more sensible
elseif(string.len(fields.add_entry_general) > max_length) then
error_msg = "The name of your new "..what.." is too long.\n"..
"Only up to "..tostring(max_length).." characters are allowed."
elseif(string.len(fields.add_entry_general:trim()) < min_length) then
error_msg = "The name of your new "..what.." is too short.\n"..
"It has to be at least "..tostring(min_length).." characters long."
elseif(table.indexof(list_of_entries, fields.list_of_entries:trim()) > 0) then
error_msg = "A "..what.." with that name exists already."
else
fields.add_entry_general = fields.add_entry_general:trim()
-- this depends on what is created
local res = function_add_new_entry(pname, fields.add_entry_general)
-- not really an error msg here - but fascilitates output
error_msg = "A new "..what.." named\n \""..
minetest.formspec_escape(fields.add_entry_general)..
"\"\nhas been created."
if(not(res) or res == -1) then
error_msg = "Failed to create "..what.." named\n \""..
minetest.formspec_escape(fields.add_entry_general).."\"."
else
-- select this new entry (add 1 because the first entry of our
-- list is adding a new entry)
yl_speak_up.speak_to[pname].tmp_index_general = res + 1
end
end
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..formname,
formspec = "size[10,2]"..
"label[0.2,0.0;"..error_msg.."]"..
"button[1.5,1.5;2,0.9;back_from_msg;Back]"})
return
-- scroll through the variables with prev/next buttons
elseif(fields and (fields["prev"] or fields["next"])) then
local index = yl_speak_up.speak_to[pname].tmp_index_general
if(not(index)) then
yl_speak_up.speak_to[pname].tmp_index_general = 1
elseif(fields["prev"] and index > 1) then
yl_speak_up.speak_to[pname].tmp_index_general = index - 1
elseif(fields["next"] and index <= #list_of_entries) then
yl_speak_up.speak_to[pname].tmp_index_general = index + 1
end
yl_speak_up.show_fs(player, formname, fields.stored_value_for_player)
return
end
-- an entry was selected in the dropdown list
if(fields and fields.list_of_entries and fields.list_of_entries ~= "") then
local index = table.indexof(list_of_entries, fields.list_of_entries)
if(fields.list_of_entries == "Add "..what..":") then
index = 0
end
if(index and index > -1) then
yl_speak_up.speak_to[pname].tmp_index_general = index + 1
end
end
local entry_name = list_of_entries[ yl_speak_up.speak_to[pname].tmp_index_general - 1]
-- delete entry
if(fields and ((fields.del_entry_general and fields.del_entry_general ~= ""))
and entry_name and entry_name ~= "") then
local text = function_del_old_entry(pname, entry_name)
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..formname,
formspec = "size[10,2]"..
"label[0.2,0.0;Trying to delete "..what.." \""..
minetest.formspec_escape(tostring(entry_name))..
"\":\n"..text.."]"..
"button[1.5,1.5;2,0.9;back_from_msg;Back]"})
return
-- maybe the custom function knows what to do with this
elseif(fields
and not(function_input_check_fields(player, formname, fields, entry_name, list_of_entries))) then
-- the function_input_check_fields managed to handle this input
return
-- an entry was selected in the dropdown list
elseif(entry_name and entry_name ~= "") then
-- show the same formspec again, with a diffrent variable selected
yl_speak_up.show_fs(player, formname)
return
end
-- try to go back to the last formspec shown before this one
if(not(yl_speak_up.speak_to[pname])) then
return
end
local last_fs = yl_speak_up.speak_to[pname][ "working_at" ]
local last_params = yl_speak_up.speak_to[pname][ "working_at_params" ]
yl_speak_up.show_fs(player, last_fs, last_params)
end
-- inserts buttons into formspec which allow to select previous/next entry, to go back,
-- create new entries, delete entries and select entries from a dropdown menu;
-- returns the currently selected entry or nil (=create new entry)
-- Note: Designed for a formspec of size "size[18,12]"
yl_speak_up.get_fs_manage_general = function(player, param,
formspec, list_of_entries,
text_add_new, tooltip_add_new,
what_is_the_list_about,
tooltip_add_entry_general, tooltip_del_entry_general)
local selected = nil
local pname = player:get_player_name()
-- the yl_speak_up.create_dropdown_playerlist function needs a table - not a list
local table_of_entries = {}
for i, k in ipairs(list_of_entries) do
table_of_entries[ k ] = true
end
-- "Add variable:" is currently selected
if(not(yl_speak_up.speak_to[pname].tmp_index_general)
or yl_speak_up.speak_to[pname].tmp_index_general == 1
or not(list_of_entries[ yl_speak_up.speak_to[pname].tmp_index_general - 1])) then
yl_speak_up.speak_to[pname].tmp_index_general = 1
table.insert(formspec, "button[12.2,2.15;2.5,0.6;add_list_entry;")
table.insert(formspec, minetest.formspec_escape(text_add_new))
table.insert(formspec, "]")
table.insert(formspec, "tooltip[add_list_entry;")
table.insert(formspec, minetest.formspec_escape(tooltip_add_new))
table.insert(formspec, "]")
else
-- index 1 is "Add variable:"
selected = list_of_entries[ yl_speak_up.speak_to[pname].tmp_index_general - 1]
end
if(yl_speak_up.speak_to[pname].tmp_index_general > 1) then
table.insert(formspec, "button[4.0,0.2;2.0,0.6;prev;< Prev]"..
"button[4.0,11.0;2.0,0.6;prev;< Prev]")
end
if(yl_speak_up.speak_to[pname].tmp_index_general <= #list_of_entries) then
table.insert(formspec, "button[12.0,0.2;2.0,0.6;next;Next >]"..
"button[12.0,11.0;2.0,0.6;next;Next >]")
end
table.insert(formspec, "button[0.0,0.2;2.0,0.6;back;Back]"..
"button[8.0,11.0;2.0,0.6;back;Back]")
local what = minetest.formspec_escape(what_is_the_list_about)
table.insert(formspec, "label[7.0,0.4;* Manage your ")
table.insert(formspec, what)
table.insert(formspec, "s *]")
table.insert(formspec, "label[0.2,2.45;Your ")
table.insert(formspec, what)
table.insert(formspec, ":]")
-- offer a dropdown list and a text input field for new varialbe names for adding
table.insert(formspec, yl_speak_up.create_dropdown_playerlist(
player, pname,
table_of_entries,
yl_speak_up.speak_to[pname].tmp_index_general,
2.6, 2.15, 1.0, 0.6,
"list_of_entries",
what,
"Delete selected "..what,
"add_entry_general",
minetest.formspec_escape(tooltip_add_entry_general),
"del_entry_general",
minetest.formspec_escape(tooltip_del_entry_general)
))
-- either nil or the text of the selected entry
return selected
end

43
fs/README.md Normal file
View File

@ -0,0 +1,43 @@
In general, files in here ought to provide exactly tow functions:
```
yl_speak_up.input_fs_<FILE_NAME> = function(player, formname, fields)
-- react to whatever input the player supplied;
-- usually show another formspec
return
end
```
and:
```
yl_speak_up.get_fs_<FILE_NAME> = function(player, param)
-- return a formspec string
return formspec_string
end
```
The actual displaying of the formspecs and calling of these functions happens
in `show_fs.lua`.
There may be no function for handling input if the formspec only displays
something and only offers a back button or buttons to other formspecs.
Additional (local) helper functions may be included if they are only used
by those two functions above and not used elsewhere in the mod.
That is not a technical requirement but mostly for keeping things clean.
These functions are usually *not* declared as local and may be overridden
from outside if needed.
An exception to the above rule are the functions
```
yl_speak_up.show_precondition
yl_speak_up.show_action
yl_speak_up.show_effect
```
because in those cases it was better for readability and context to have them
in their respective `fs/fs_edit_<precondition|action|effect>.lua` files.
These functions are called in debug mode when excecuting the preconditions,
actions and effects as well.
Another exception is `exec_actions.lua` as the respective `get_fs_*` and
`input_*` functions there are only called when an action is executed and not
via `show_fs.lua` at all.

82
fs/fs_action_evaluate.lua Normal file
View File

@ -0,0 +1,82 @@
-- action of the type "evaluate"
yl_speak_up.input_fs_action_evaluate = function(player, formname, fields)
local pname = player:get_player_name()
local a_id = yl_speak_up.speak_to[pname].a_id
-- the custom input_handler may have something to say here as well
local a = yl_speak_up.get_action_by_player(player)
if(player and a and a.a_value) then
local custom_data = yl_speak_up.custom_functions_a_[a.a_value]
if(custom_data and custom_data.code_input_handler) then
local n_id = yl_speak_up.speak_to[pname].n_id
local fun = custom_data.code_input_handler
-- actually call the function (which may change the value of fields)
fields = fun(player, n_id, a, formname, fields)
end
end
-- back from error_msg? then show the formspec again
if(fields.back_from_error_msg) then
yl_speak_up.show_fs(player, "action_evaluate", nil)
return
end
if(fields.back_to_talk) then
-- the action was aborted
yl_speak_up.execute_next_action(player, a_id, nil, formame)
return
end
if(fields.failed_action) then
-- the action failed
yl_speak_up.execute_next_action(player, a_id, false, formame)
return
end
if(fields.finished_action) then
-- the action was a success
yl_speak_up.execute_next_action(player, a_id, true, formame)
return
end
if(fields.quit) then
return
end
-- else show a message to the player that he ought to decide
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:action_evaluate",
formspec = "size[7,1.5]"..
"label[0.2,-0.2;"..
"Please click on one of the offered options\nor select \"Back to talk\"!]"..
"button[2,1.0;1.5,0.9;back_from_error_msg;Back]"})
end
yl_speak_up.get_fs_action_evaluate = function(player, param)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
local n_id = yl_speak_up.speak_to[pname].n_id
local a = yl_speak_up.get_action_by_player(player)
if(not(a)) then
return ""
end
if(not(player) or not(a.a_value)) then
return "label[0.2,0.5;Ups! An internal error occoured. Please tell your "..
"local admin to check the brain of this lifeform here.]"..
"button[1.5,1.5;2,0.9;back_to_talk;Back]"
end
local custom_data = yl_speak_up.custom_functions_a_[a.a_value]
if(not(custom_data) or not(custom_data.code)) then
return "label[0.2,0.5;Ups! An internal error occoured. Please tell your "..
"local admin that the internal function "..
minetest.formspec_escape(tostring(a.a_value))..
"somehow got lost/broken.]"..
"button[1.5,1.5;2,0.9;back_to_talk;Back]"
end
local fun = custom_data.code
-- actually call the function
return fun(player, n_id, a)
end
yl_speak_up.register_fs("action_evaluate",
yl_speak_up.input_fs_action_evaluate,
yl_speak_up.get_fs_action_evaluate,
-- no special formspec version required:
nil
)

103
fs/fs_action_npc_gives.lua Normal file
View File

@ -0,0 +1,103 @@
-- show the diffrent action-related formspecs and handle input to them
-- (Note: trade is handled in trade_simple.lua)
yl_speak_up.input_fs_action_npc_gives = function(player, formname, fields)
-- back from error_msg? then show the formspec again
if(fields.back_from_error_msg) then
-- do not create a new item
yl_speak_up.show_fs(player, "action_npc_gives", nil)
return
end
local pname = player:get_player_name()
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
if(not(yl_speak_up.speak_to[pname])) then
return
end
local a_id = yl_speak_up.speak_to[pname].a_id
if(fields.npc_does_not_have_item) then
-- the NPC can't supply the item - abort the action
yl_speak_up.execute_next_action(player, a_id, nil, formname)
return
end
-- is the npc_gives inv empty? then all went as expected.
-- (it does not really matter which button the player pressed in this case)
if(trade_inv:is_empty("npc_gives")) then
-- the NPC has given the item to the player; save the NPCs inventory
local n_id = yl_speak_up.speak_to[pname].n_id
yl_speak_up.save_npc_inventory(n_id)
-- the action was a success; the NPC managed to give the item to the player
yl_speak_up.execute_next_action(player, a_id, true, formname)
return
end
-- the npc_gives slot does not accept input - so we don't have to check for any misplaced items
-- but if the player aborts, give the item back to the NPC
if(fields.back_to_talk) then
-- actually take the item back into the NPC's inventory
local n_id = yl_speak_up.speak_to[pname].n_id
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)})
yl_speak_up.action_take_back_failed_npc_gives(trade_inv, npc_inv)
-- strip the quest item info from the stack (so that it may stack again)
-- and give that (hopefully) stackable stack back to the NPC
yl_speak_up.action_quest_item_take_back(player)
-- the action failed
yl_speak_up.execute_next_action(player, a_id, nil, formname)
return
end
-- else show a message to the player that he ought to take the item
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:action_npc_gives",
formspec = "size[7,1.5]"..
"label[0.2,-0.2;"..
"Please take the offered item and click on \"Done\"!\n"..
"If you can't take it, click on \"Back to talk\".]"..
"button[2,1.0;1.5,0.9;back_from_error_msg;Back]"})
end
yl_speak_up.get_fs_action_npc_gives = function(player, param)
-- called for the first time; create the item the NPC wants to give
if(param) then
if(not(yl_speak_up.action_quest_item_prepare(player))) then
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
-- it's not the fault of the player that the NPC doesn't have the item;
-- so tell him that (the action will still fail)
return table.concat({"size[7,2.0]"..
"label[0.2,-0.2;",
minetest.formspec_escape(dialog.n_npc or "- ? -"),
" is very sorry:\n"..
"The item intended for you is currently unavailable.\n"..
"Please come back later!]"..
"button[2,1.5;1.5,0.9;npc_does_not_have_item;Back]"}, "")
end
end
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
return table.concat({"size[8.5,8]",
"list[current_player;main;0.2,3.85;8,1;]",
"list[current_player;main;0.2,5.08;8,3;8]",
"button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]",
"button[4.75,1.6;1.5,0.9;finished_action;Done]",
"tooltip[back_to_talk;Click here if you don't want to (or can't)\n",
"take the offered item.]",
"tooltip[finished_action;Click here once you have taken the item and\n",
"stored it in your inventory.]",
"label[1.5,0.7;",
minetest.formspec_escape(dialog.n_npc or "- ? -"),
" offers to you:]",
-- unlike the npc_gives slot - which is used for setting up the NPC - the
-- npc_gives slot does not allow putting something in
"list[detached:yl_speak_up_player_"..pname..";npc_gives;3.25,1.5;1,1;]" ,
"label[1.5,2.7;Take the offered item and click on \"Done\" to proceed.]"
}, "")
end
yl_speak_up.register_fs("action_npc_gives",
yl_speak_up.input_fs_action_npc_gives,
yl_speak_up.get_fs_action_npc_gives,
-- force formspec version 1 for this:
1
)

View File

@ -0,0 +1,67 @@
yl_speak_up.input_fs_action_npc_wants = function(player, formname, fields)
-- back from error_msg? then show the formspec again
if(fields.back_from_error_msg) then
yl_speak_up.show_fs(player, "action_npc_wants", nil)
return
end
local pname = player:get_player_name()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
local a_id = yl_speak_up.speak_to[pname].a_id
-- is the npc_wants inv empty and the player pressed the back to talk button? then the action failed.
if(trade_inv:is_empty("npc_wants") and fields.back_to_talk) then
-- the action was aborted
yl_speak_up.execute_next_action(player, a_id, nil, formname)
return
end
-- the player tried to give something; check if it is the right thing
if(not(trade_inv:is_empty("npc_wants"))) then
local stack = trade_inv:get_stack("npc_wants", 1)
-- check if it really is the item the NPC wanted; let the NPC take it
local is_correct_item = yl_speak_up.action_quest_item_take_back(player)
-- the action may have been a success or failure
yl_speak_up.execute_next_action(player, a_id, is_correct_item, formname)
return
end
-- else show a message to the player
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:action_npc_wants",
formspec = "size[7,1.5]"..
"label[0.2,-0.2;"..
"Please insert the item for the npc and click on \"Done\"!\n"..
"If you don't have what he wants, click on \"Back to talk\".]"..
"button[2,1.0;1.5,0.9;back_from_error_msg;Back]"})
end
yl_speak_up.get_fs_action_npc_wants = function(player, param)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
return table.concat({"size[8.5,8]",
"list[current_player;main;0.2,3.85;8,1;]",
"list[current_player;main;0.2,5.08;8,3;8]",
"button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]",
"button[4.75,1.6;1.5,0.9;finished_action;Done]",
"tooltip[back_to_talk;Click here if you don't know what item the\n",
"NPC wants or don't have the desired item.]",
"tooltip[finished_action;Click here once you have placed the item in\n",
"the waiting slot.]",
"label[1.5,0.7;",
minetest.formspec_escape(dialog.n_npc or "- ? -"),
" expects something from you:]",
"list[detached:yl_speak_up_player_",
pname,
";npc_wants;3.25,1.5;1,1;]",
"label[1.5,2.7;Insert the right item and click on \"Done\" to proceed.]"
}, "")
end
yl_speak_up.register_fs("action_npc_wants",
yl_speak_up.input_fs_action_npc_wants,
yl_speak_up.get_fs_action_npc_wants,
-- force formspec version 1 for this:
1
)

125
fs/fs_action_text_input.lua Normal file
View File

@ -0,0 +1,125 @@
yl_speak_up.input_fs_action_text_input = function(player, formname, fields)
-- back from error_msg? then show the formspec again
if(fields.back_from_error_msg) then
-- the error message is only shown if the input was empty
yl_speak_up.show_fs(player, "action_text_input", "")
return
end
local pname = player:get_player_name()
-- the player is no longer talking to the NPC
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
local a_id = yl_speak_up.speak_to[pname].a_id
local a = yl_speak_up.get_action_by_player(player)
if(fields.back_to_talk) then
-- the action was aborted
yl_speak_up.execute_next_action(player, a_id, nil, formname)
return
end
if(fields.finished_action and fields.quest_answer and fields.quest_answer ~= "") then
local n_id = yl_speak_up.speak_to[pname].n_id
-- is the answer correct?
-- strip leading and tailing blanks
local success = not(not(fields.quest_answer and a.a_value
and fields.quest_answer:trim() == a.a_value:trim()))
if(not(success)) then
yl_speak_up.log_change(pname, n_id,
"Action "..tostring(a_id)..
" "..tostring(yl_speak_up.speak_to[pname].o_id)..
" "..tostring(yl_speak_up.speak_to[pname].d_id)..
": Player answered with \""..tostring(fields.quest_answer:trim())..
"\", but we expected: \""..tostring(a.a_value:trim()).."\".")
else
yl_speak_up.log_change(pname, n_id,
"Action "..tostring(a_id)..
" "..tostring(yl_speak_up.speak_to[pname].o_id)..
" "..tostring(yl_speak_up.speak_to[pname].d_id)..
": Answer is correct.")
end
-- store what the player entered so that it can be examined by other functions
yl_speak_up.last_text_input[pname] = fields.quest_answer:trim()
-- the action was a either a success or failure
yl_speak_up.execute_next_action(player, a_id, success, formname)
return
end
-- no scrolling desired
fields.button_up = nil
fields.button_down = nil
--[[ this is too disruptive; it's better to just let the player select a button
-- else show a message to the player
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:action_text_input",
formspec = "size[7,1.5]"..
"label[0.2,-0.2;"..
"Please answer the question and click on \"Send answer\"!\n"..
"If you don't know the answer, click on \"Back to talk\".]"..
"button[2,1.0;1.5,0.9;back_from_error_msg;Back]"})
--]]
end
yl_speak_up.get_fs_action_text_input = function(player, param)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
local a = yl_speak_up.get_action_by_player(player)
if(not(a)) then
return ""
end
local alternate_text =
(a.a_question or "Your answer:").."\n\n"..
(dialog.n_npc or "- ? -").." looks expectantly at you.]"
local formspec = {}
table.insert(formspec, "label[0.7,1.8;Answer:]")
table.insert(formspec, "button[45,1.0;9,1.8;finished_action;Send this answer]")
-- show the actual text for the option
yl_speak_up.add_formspec_element_with_tooltip_if(formspec,
"field", "4.0,1.0;40,1.5",
"quest_answer",
";", --..minetest.formspec_escape("<your answer>"),
"Enter your answer here.",
true)
local h = 2.0
local pname_for_old_fs = nil
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"finished_action",
"Please enter your answer in the input field above and click here to "..
"send it.",
minetest.formspec_escape("[Send this answer]"),
true, nil, nil, pname_for_old_fs)
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"back_to_talk",
"If you don't know the answer or don't want to answer right now, "..
"choose this option to get back to the previous dialog.",
"I give up. Let's talk about something diffrent.",
true, nil, nil, pname_for_old_fs)
-- do not offer edit_mode in the trade formspec because it makes no sense there;
return yl_speak_up.show_fs_decorated(pname, nil, h, alternate_text, "",
table.concat(formspec, "\n"), nil, h)
--[[ old version with extra formspec
return --"size[12.0,4.5]"..
yl_speak_up.show_fs_simple_deco(12.0, 4.5)..
"button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]"..
"button[2.0,3.7;3.0,0.9;finished_action;Send answer]"..
"tooltip[back_to_talk;Click here if you don't know the answer.]"..
"tooltip[finished_action;Click here once you've entered the answer.]"..
"label[0.2,1.2;"..minetest.formspec_escape(a.a_question or "Your answer:").."]"..
"label[0.2,1.9;Answer:]"..
"field[1.6,2.2;10.0,0.6;quest_answer;;"..tostring(param or "").."]"..
"label[0.2,2.8;"..minetest.formspec_escape(
"["..(dialog.n_npc or "- ? -").." looks expectantly at you.]").."]"
--]]
end
yl_speak_up.register_fs("action_text_input",
yl_speak_up.input_fs_action_text_input,
yl_speak_up.get_fs_action_text_input,
-- no special formspec version required:
nil
)

313
fs/fs_add_trade_simple.lua Normal file
View File

@ -0,0 +1,313 @@
-- when closing the yl_speak_up.get_fs_add_trade_simple formspec:
-- give the items back to the player (he took them from his inventory and
-- had no real chance to put them elsewhere - so there really ought to be
-- room enough)
yl_speak_up.add_trade_simple_return_items = function(player, trade_inv, pay, buy)
local player_inv = player:get_inventory()
if( pay and player_inv:room_for_item("main", pay)) then
player_inv:add_item("main", pay)
trade_inv:set_stack("setup", 1, "")
end
if( buy and player_inv:room_for_item("main", buy)) then
player_inv:add_item("main", buy)
trade_inv:set_stack("setup", 2, "")
end
end
-- simple trade: add a new trade or edit existing one (by storing a new one);
-- set trade_id to "new" if it shall be a new trade added to the trade list;
-- set trade_id to "<d_id> <o_id>" if it shall be a result/effect of a dialog option;
yl_speak_up.get_fs_add_trade_simple = function(player, trade_id)
if(not(player)) then
return yl_speak_up.trade_fail_fs
end
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
local dialog = yl_speak_up.speak_to[pname].dialog
-- is this player allowed to edit the NPC and his trades? If not abort.
if(not(yl_speak_up.may_edit_npc(player, n_id)) or not(dialog) or not(dialog.n_npc)) then
return "size[9,2]"..
"label[2.0,1.8;Ups! Something went wrong.]"..
"button[6.2,1.6;2.0,0.9;abort_trade_simple;Back]"
end
-- store the trade_id (so that it doesn't have to be transfered in a hidden field)
yl_speak_up.speak_to[pname].trade_id = trade_id
local delete_button =
"button[0.2,2.6;1.0,0.9;delete_trade_simple;Delete]"..
"tooltip[delete_trade_simple;Delete this trade.]"
-- no point in deleting a new trade - it doesn't exist yet
if(trade_id and trade_id == "new") then
delete_button = ""
end
return table.concat({"size[8.5,9]",
"label[4.35,0.8;",
minetest.formspec_escape(dialog.n_npc),
" sells:]",
"list[current_player;main;0.2,4.85;8,1;]",
"list[current_player;main;0.2,6.08;8,3;8]",
-- show the second slot of the setup inventory in the detached player's inv
"list[detached:yl_speak_up_player_",
pname,
";setup;2,1.5;1,1;]",
-- show the second slot of said inventory
"list[detached:yl_speak_up_player_",
pname,
";setup;5,1.5;1,1;1]",
"label[0.5,0.0;Configure trade with ",
minetest.formspec_escape(dialog.n_npc),
":]",
"label[1.5,0.8;The customer pays:]",
"label[1.5,3.8;Put items in the two slots and click on \"Store trade\".]",
"label[1.5,4.2;You will get your items back when storing the trade.]",
-- annoyingly, the height value no longer works :-(
"label[0.2,2.5;Item\nname:]",
"field[1.5,3.2;3,0.2;item_name_price;;]",
"label[4.35,2.5;If you don't have the item you\n",
"want to buy, then enter its item\n",
"name (i.e. default:diamond) here.]",
"button[0.2,1.6;1.0,0.9;abort_trade_simple;Abort]",
delete_button,
"button[6.2,1.6;2.0,0.9;store_trade_simple;Store trade]",
"tooltip[store_trade_simple;Click here to store this as a new trade. Your\n",
"items will be returned to you and the trade will\n",
"will be shown the way the customer can see it.]",
"tooltip[abort_trade_simple;Abort setting up this new trade.]"
}, "")
end
-- the player wants to add a simple trade; handle formspec input
-- possible inputs:
-- fields.back_from_error_msg show this formspec here again
-- fields.store_trade_simple store this trade as a result and
-- go on to showing the do_trade_simple formspec
-- fields.delete_trade_simple delete this trade
-- go back to edit options dialog
-- abort_trade_simple, ESC go back to edit options dialog
-- The rest is inventory item movement.
yl_speak_up.input_add_trade_simple = function(player, formname, fields, input_to)
if(not(player)) then
return 0
end
local pname = player:get_player_name()
if(not(input_to)) then
input_to = "add_trade_simple"
end
-- we return from showing an error message (the player may not have noticed
-- a chat message while viewing a formspec; thus, we showed a formspec message)
if(fields.back_from_error_msg) then
yl_speak_up.show_fs(player, input_to)
return
end
-- which trade are we talking about?
local trade_id = yl_speak_up.speak_to[pname].trade_id
-- this also contains the inventory list "setup" where the player placed the items
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
-- fields.abort_trade_simple can be ignored as it is similar to ESC
local pay = trade_inv:get_stack("setup", 1)
local buy = trade_inv:get_stack("setup", 2)
-- clicking on abort here when adding a new trade via the trade list
-- goes back to the trade list (does not require special privs)
if(fields.abort_trade_simple and trade_id == "new") then
-- we are no longer doing a particular trade
yl_speak_up.speak_to[pname].trade_id = nil
-- return the items (setting up the trade was aborted)
yl_speak_up.add_trade_simple_return_items(player, trade_inv, pay, buy)
-- ..else go back to the edit options formspec
yl_speak_up.show_fs(player, "trade_list")
return
end
-- adding a new trade via the trade list?
if(not(trade_id) and fields.store_trade_simple) then
trade_id = "new"
end
local n_id = yl_speak_up.speak_to[pname].n_id
local d_id = yl_speak_up.speak_to[pname].d_id
local o_id = yl_speak_up.speak_to[pname].o_id
-- the trade can only be changed in edit mode
if((input_to == "add_trade_simple")
-- exception: when adding a new trade via the trade list
-- (that is allowed without having to be in edit mode)
and not(trade_id == "new" and yl_speak_up.may_edit_npc(player, n_id))) then
-- return the items (setting up the trade was aborted)
yl_speak_up.add_trade_simple_return_items(player, trade_inv, pay, buy)
return
end
-- store the new trade
if(fields.store_trade_simple) then
local error_msg = ""
local simulated_pay = false
if(pay:is_empty() and fields.item_name_price and fields.item_name_price ~= "") then
pay = ItemStack(fields.item_name_price)
simulated_pay = true
end
-- check for error conditions
if(pay:is_empty()) then
error_msg = "What shall the customer pay?\nWe don't give away stuff for free here!"
elseif(buy:is_empty()) then
error_msg = "What shall your NPC sell?\nCustomers won't pay for nothing!"
elseif(pay:get_wear() > 0 or buy:get_wear() > 0) then
error_msg = "Selling used items is not possible."
elseif(not(minetest.registered_items[ pay:get_name() ])
or not(minetest.registered_items[ buy:get_name() ])) then
error_msg = "Unkown items cannot be traded."
elseif(pay:get_name() == buy:get_name()) then
error_msg = "Selling *and* buying the same item\nat the same time makes no sense."
else
-- get the necessary dialog data
local dialog = yl_speak_up.speak_to[pname].dialog
-- player_gives (pay stack):
local ps = pay:get_name().." "..tostring(pay:get_count())
-- npc_gives (buy stack):
local bs = buy:get_name().." "..tostring(buy:get_count())
local r_id = "?"
if(not(dialog.trades)) then
dialog.trades = {}
end
-- is this a trade attached to the trade list?
-- or do we have to create a new trade ID?
if(trade_id == "new") then
-- if the player adds the same trade again, the ID is reused; other
-- than that, the ID is uniq
-- (the ID is formed so that we can later easily sort the offers by
-- the name of the buy stack - which is more helpful for the player
-- than sorting by the pay stack)
trade_id = "sell "..bs.." for "..ps
-- log the change
yl_speak_up.log_change(pname, n_id,
"Trade: Added offer "..tostring(trade_id)..".")
-- add this new trade
dialog.trades[ trade_id ] = {pay={ps},buy={bs}}
-- actually save the dialog to disk
yl_speak_up.save_dialog(n_id, dialog)
-- store the newly created trade_id
yl_speak_up.speak_to[pname].trade_id = trade_id
-- all ok so far
error_msg = nil
-- storing trades that are associated with particular dialogs and options
-- requires d_id and o_id to be set
elseif(trade_id ~= "new" and (not(d_id) or not(o_id))) then
error_msg = "Internal error. o_id was not set."
else
-- would be too complicated to handle exceptions; this is for edit_mode:
if(yl_speak_up.npc_was_changed
and yl_speak_up.npc_was_changed[n_id]) then
-- record this as a change, but do not save do disk yet
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Trade "..tostring(trade_id)..
" added to option "..tostring(o_id)..".")
end
-- add this new trade - complete with information to which dialog and
-- to which option the trade belongs
dialog.trades[ trade_id ] = {pay={ps},buy={bs}, d_id = d_id, o_id = o_id}
-- all ok so far
error_msg = nil
end
-- do not return yet - the items still need to be given back!
end
-- make sure we don't create items here out of thin air
if(simulated_pay) then
pay = ItemStack("")
end
-- show error message (that leads back to this formspec)
if(error_msg) then
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..input_to,
formspec =
"size[6,2]"..
"label[0.2,0.5;"..error_msg.."]"..
"button[2,1.5;1,0.9;back_from_error_msg;Back]"})
return
end
-- we need a way of deleting trades as well;
-- this affects only trades that are associated with dialogs and options;
-- trades from the trade list are deleted more directly
elseif(fields.delete_trade_simple) then
-- delete this result (if it exists)
-- get the necessary dialog data
local dialog = yl_speak_up.speak_to[pname].dialog
-- would be too complicated to handle exceptions; this is for edit_mode:
if(yl_speak_up.npc_was_changed
and yl_speak_up.npc_was_changed[n_id]) then
-- record this as a change
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Trade "..tostring(trade_id)..
" deleted from option "..tostring(o_id)..".")
end
if(not(dialog.trades)) then
dialog.trades = {}
end
-- delete the trade type result
if(trade_id) then
dialog.trades[ trade_id ] = nil
end
-- do not return yet - the items still need to be given back!
end
-- return the items after successful setup
yl_speak_up.add_trade_simple_return_items(player, trade_inv, pay, buy)
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog.trades)) then
dialog.trades = {}
end
if(dialog.trades[ trade_id ] and dialog.trades[ trade_id ].d_id
and input_to == "add_trade_simple") then
yl_speak_up.speak_to[pname].trade_id = trade_id
-- tell the player that the new trade has been added
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:do_trade_simple",
formspec =
"size[6,2]"..
"label[0.2,0.5;The new trade has been configured successfully.]"..
"button[1.5,1.5;2,0.9;trade_simple_stored;Show trade]"})
-- return back to trade list
elseif(not(o_id)) then
-- we are no longer trading
yl_speak_up.speak_to[pname].trade_id = nil
-- ..else go back to the edit options formspec
yl_speak_up.show_fs(player, "trade_list")
else
-- we are no longer trading
yl_speak_up.speak_to[pname].trade_id = nil
-- the trade has been stored or deleted successfully
return true
-- -- ..else go back to the edit options formspec (obsolete)
-- yl_speak_up.show_fs(player, "edit_option_dialog",
-- {n_id = n_id, d_id = d_id, o_id = o_id})
end
end
yl_speak_up.get_fs_add_trade_simple_wrapper = function(player, param)
local pname = player:get_player_name()
-- the optional parameter param is the trade_id
if(not(param) and yl_speak_up.speak_to[pname]) then
param = yl_speak_up.speak_to[pname].trade_id
end
return yl_speak_up.get_fs_add_trade_simple(player, param)
end
yl_speak_up.register_fs("add_trade_simple",
yl_speak_up.input_add_trade_simple,
yl_speak_up.get_fs_add_trade_simple_wrapper,
-- force formspec version 1:
1
)

444
fs/fs_do_trade_simple.lua Normal file
View File

@ -0,0 +1,444 @@
-- spimple trading: one item(stack) for another item(stack)
-- fallback message if something went wrong
yl_speak_up.trade_fail_fs = "size[6,2]"..
"label[0.2,0.5;Ups! The trade is not possible.\nPlease notify an admin.]"..
"button_exit[2,1.5;1,0.9;exit;Exit]"
-- possible inputs:
-- fields.edit_trade_simple go on to showing the add_trade_simple formspec
-- fields.abort_trade_simple, ESC, depends on context
-- fields.delete_trade_simple delete this trade
-- fields.finished_trading
-- if in edit_mode: go back to edit options dialog (handled by editor/)
-- if traded at least once: go on to the target dialog
-- if not traded: go back to the original dialog
yl_speak_up.input_do_trade_simple = function(player, formname, fields)
if(not(player)) then
return 0
end
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
-- show the trade list
if(fields.back_to_trade_list) then
yl_speak_up.show_fs(player, "trade_list")
return
end
-- get from a dialog option trade back to the list of all these trades
if(fields.show_trade_list_dialog_options) then
yl_speak_up.show_fs(player, "trade_list", true)
return
end
-- a new trade has been stored - show it
if(fields.trade_simple_stored) then
yl_speak_up.show_fs(player, "trade_simple", yl_speak_up.speak_to[pname].trade_id)
return
end
if(fields.buy_directly) then
local error_msg = yl_speak_up.do_trade_direct(player)
if(error_msg ~= "") then
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:do_trade_simple",
formspec = "size[6,2]"..
"label[0.2,-0.2;"..error_msg.."]"..
"button[2,1.5;1,0.9;back_from_error_msg;Back]"})
return
end
yl_speak_up.show_fs(player, "trade_simple", yl_speak_up.speak_to[pname].trade_id)
return
end
if(fields.delete_trade_simple) then
yl_speak_up.delete_trade_simple(player, trade.trade_id)
return
end
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
local player_inv = player:get_inventory()
-- give the items from the pay slot back
local pay = trade_inv:get_stack("pay", 1)
if( player_inv:room_for_item("main", pay)) then
player_inv:add_item("main", pay)
trade_inv:set_stack("pay", 1, "")
end
-- clear the buy slot as well
trade_inv:set_stack("buy", 1, "")
-- show the edit trade formspec
if(fields.edit_trade_simple) then
yl_speak_up.show_fs(player, "add_trade_simple", trade.trade_id)
return
end
-- go back to the main dialog
if(fields.abort_trade_simple or fields.quit or fields.finished_trading) then
-- was the action a success?
local success = not(not(trade and trade.trade_done and trade.trade_done > 0))
local a_id = trade.a_id
local o_id = trade.o_id
local n_id = yl_speak_up.speak_to[pname].n_id
yl_speak_up.debug_msg(player, n_id, o_id, "Ending trade.")
-- done trading
yl_speak_up.speak_to[pname].target_d_id = nil
yl_speak_up.speak_to[pname].trade_id = nil
-- execute the next action
yl_speak_up.execute_next_action(player, a_id, success, formname)
return
end
-- show this formspec again
yl_speak_up.show_fs(player, "trade_simple")
end
-- try to do the trade directly - without moving items in the buy/sell inventory slot
-- returns error_msg or "" when successful
yl_speak_up.do_trade_direct = function(player)
if(not(player)) then
return "Player, where are you?"
end
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
-- do we have all the necessary data?
if(not(trade) or trade.trade_type ~= "trade_simple") then
return "No trade found!"
end
-- the players' inventory
local player_inv = player:get_inventory()
-- the NPCs' inventory
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(trade.n_id)})
-- has the NPC the item he wants to sell?
if( not(npc_inv:contains_item("npc_main", trade.npc_gives))) then
return "Sorry. This item is sold out!"
-- has the NPC room for the payment?
elseif(not(npc_inv:room_for_item("npc_main", trade.player_gives))) then
return "Sorry. No room to store your payment!\n"..
"Please try again later."
-- can the player pay the price?
elseif(not(player_inv:contains_item("main", trade.player_gives))) then
return "You can't pay the price!"
-- has the player room for the sold item?
elseif(not(player_inv:room_for_item("main", trade.npc_gives))) then
return "You don't have enough free inventory space.\n"..
"Trade aborted."
end
local payment = player_inv:remove_item("main", trade.player_gives)
local sold = npc_inv:remove_item("npc_main", trade.npc_gives)
-- used items cannot be sold as there is no fair way to indicate how
-- much they are used
if(payment:get_wear() > 0 or sold:get_wear() > 0) then
-- revert the trade
player_inv:add_item("main", payment)
npc_inv:add_item("npc_main", sold)
return "At least one of the items that shall be traded\n"..
"is dammaged. Trade aborted."
end
player_inv:add_item("main", sold)
npc_inv:add_item("npc_main", payment)
-- save the inventory of the npc so that the payment does not get lost
yl_speak_up.save_npc_inventory( trade.n_id )
-- store for statistics how many times the player has executed this trade
-- (this is also necessary to switch to the right target dialog when
-- dealing with dialog options trades)
yl_speak_up.trade[pname].trade_done = yl_speak_up.trade[pname].trade_done + 1
-- log the trade
yl_speak_up.log_change(pname, trade.n_id,
"bought "..tostring(trade.npc_gives)..
" for "..tostring(trade.player_gives))
return ""
end
-- simple trade: one item(stack) for another
-- handles configuration of new trades and showing the formspec for trades;
-- checks if payment and buying is possible
yl_speak_up.get_fs_do_trade_simple = function(player, trade_id)
if(not(player)) then
return yl_speak_up.trade_fail_fs
end
local pname = player:get_player_name()
-- which trade are we talking about?
local trade = yl_speak_up.trade[pname]
if(trade and trade.trade_id and trade_id and trade.trade_id == trade_id) then
-- nothing to do; trade is already loaded and stored
elseif(trade_id) then
local d_id = yl_speak_up.speak_to[pname].d_id
local n_id = yl_speak_up.speak_to[pname].n_id
local dialog = yl_speak_up.speak_to[pname].dialog
yl_speak_up.setup_trade_limits(dialog)
trade = {
-- we start with the simple trade
trade_type = "trade_simple",
-- can be determined from other variables, but it is easier to store it here
n_id = n_id,
npc_name = dialog.n_npc,
-- for statistics and in order to determine which dialog to show next
trade_done = 0,
-- we need to know which option this is
target_dialog = d_id,
trade_is_trade_list = true,
trade_id = trade_id
}
if(dialog.trades[ trade_id ]) then
trade.player_gives = dialog.trades[ trade_id ].pay[1]
trade.npc_gives = dialog.trades[ trade_id ].buy[1]
trade.trade_is_trade_list = not(dialog.trades[ trade_id ].d_id)
yl_speak_up.speak_to[pname].trade_id = trade_id
-- copy the limits
local stack = ItemStack(trade.npc_gives)
trade.npc_gives_name = stack:get_name()
trade.npc_gives_amount = stack:get_count()
trade.min_storage = dialog.trades.limits.sell_if_more[ trade.npc_gives_name ]
stack = ItemStack(trade.player_gives)
trade.player_gives_name = stack:get_name()
trade.player_gives_amount = stack:get_count()
trade.max_storage = dialog.trades.limits.buy_if_less[ trade.player_gives_name ]
else
trade.edit_trade = true
end
yl_speak_up.trade[pname] = trade
-- store which action we are working at
trade.a_id = yl_speak_up.speak_to[pname].a_id
else
trade_id = yl_speak_up.speak_to[pname].trade_id
trade.trade_id = trade_id
end
-- do we have all the necessary data?
if(not(trade) or trade.trade_type ~= "trade_simple") then
return yl_speak_up.trade_fail_fs
end
-- the common formspec, shared by actual trade and configuration
-- no listring here as that would make things more complicated
local formspec = table.concat({ -- "size[8.5,8]"..
yl_speak_up.show_fs_simple_deco(8.5, 8),
"label[4.35,0.7;", minetest.formspec_escape(trade.npc_name), " sells:]",
"list[current_player;main;0.2,3.85;8,1;]",
"list[current_player;main;0.2,5.08;8,3;8]"
}, "")
-- configuration of a new trade happens here
if(not(trade.player_gives) or not(trade.npc_gives) or trade.edit_trade) then
return yl_speak_up.get_fs_add_trade_simple(player, trade_id)
end
-- view for the customer when actually trading
-- buy, sell and config items need to be placed somewhere
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
-- the players' inventory
local player_inv = player:get_inventory()
-- the NPCs' inventory
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(trade.n_id)})
-- show edit button for the owner if the owner can edit the npc
if(yl_speak_up.may_edit_npc(player, trade.n_id)) then
-- for trades in trade list: allow delete (new trades can easily be added)
-- allow delete for trades in trade list even if not in edit mode
-- (entering edit mode for that would be too much work)
formspec = formspec..
"button[0.2,2.0;1.2,0.9;delete_trade_simple;Delete]"..
"tooltip[delete_trade_simple;"..
"Delete this trade. You can do so only if\n"..
"you can edit the NPC as such (i.e. own it).]"
if(not(trade.trade_is_trade_list)) then
-- normal back button will lead to the talk dialog or edit option dialog;
-- add this second back button to go back to the list of all dialog option trades
formspec = formspec..
"button[0.2,1.0;2.0,0.9;show_trade_list_dialog_options;Back to list]"..
"tooltip[show_trade_list_dialog_options;"..
"Click here to get back to the list of all trades\n"..
"associated with dialog options (like this one).\n"..
"This button is only shown if you can edit this NPC.]"
local dialog = yl_speak_up.speak_to[pname].dialog
local tr = dialog.trades[ trade_id ]
if( tr and tr.d_id and tr.o_id) then
formspec = formspec..
"label[0.2,-0.3;This trade belongs to dialog "..
minetest.formspec_escape(tostring(tr.d_id)).." option "..
minetest.formspec_escape(tostring(tr.o_id))..".]"
end
end
end
-- the functionality of the back button depends on context
if(not(trade.trade_is_trade_list)) then
-- go back to the right dialog (or forward to the next one)
formspec = formspec..
-- "button[6.2,1.6;2.0,0.9;finished_trading;Back to talk]"..
"button[0.2,0.0;2.0,0.9;finished_trading;Back to talk]"..
"tooltip[finished_trading;Click here once you've traded enough with this "..
"NPC and want to get back to talking.]"
else
-- go back to the trade list
formspec = formspec.. "button[0.2,0.0;2.0,0.9;back_to_trade_list;Back to list]"..
"tooltip[back_to_trade_list;Click here once you've traded enough with this "..
"NPC and want to get back to the trade list.]"
end
local trade_possible_msg = "Status of trade: Unknown."
local can_trade = false
-- find out how much the npc has stoerd
local stock_pay = 0
local stock_buy = 0
-- only count the inv if there actually are any mins or max
if(trade.min_storage or trade.max_storage) then
local n_id = yl_speak_up.speak_to[pname].n_id
local counted_npc_inv = {}
counted_npc_inv = yl_speak_up.count_npc_inv(n_id)
stock_pay = counted_npc_inv[trade.player_gives_name] or 0
stock_buy = counted_npc_inv[trade.npc_gives_name] or 0
end
-- can the NPC provide his part?
if(not(npc_inv:contains_item("npc_main", trade.npc_gives))) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" ran out of stock.\nPlease come back later."
-- has the NPC room for the payment?
elseif(not(npc_inv:room_for_item("npc_main", trade.player_gives))) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" ran out of inventory space.\nThere is no room to store your payment!"
-- trade limit: is enough left after the player buys the item?
elseif(trade.min_storage and trade.min_storage > stock_buy - trade.npc_gives_amount) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" currently does not want to\nsell that much."..
" Current stock: "..tostring(stock_buy)..
" (min: "..tostring(trade.min_storage)..
"). Perhaps later?"
-- trade limit: make sure the bought amount does not exceed the desired maximum
elseif(trade.max_storage and trade.max_storage < stock_pay + trade.player_gives_amount) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" currently does not want to\nbuy that much."..
" Current stock: "..tostring(stock_pay)..
" (max: "..tostring(trade.max_storage)..
"). Perhaps later?"
-- trade as an action
elseif(not(trade.trade_is_trade_list)) then
if(trade_inv:contains_item("pay", trade.player_gives)) then
-- all good so far; move the price stack to the pay slot
-- move price item to the price slot
local stack = player_inv:remove_item("main", trade.player_gives)
trade_inv:add_item("pay", stack)
trade_possible_msg = "Please take your purchase!"
can_trade = true
elseif(trade_inv:is_empty("pay")) then
trade_possible_msg = "Please insert the right payment in the pay slot\n"..
"and then take your purchase."
can_trade = false
else
trade_possible_msg = "This is not what "..minetest.formspec_escape(trade.npc_name)..
" wants.\nPlease insert the right payment!"
can_trade = false
end
-- can the player pay?
elseif(not(player_inv:contains_item("main", trade.player_gives))) then
-- both slots will remain empty
trade_possible_msg = "You cannot pay the price."
-- is the slot for the payment empty?
elseif not(trade_inv:is_empty("pay")) then
-- both slots will remain empty
-- (the slot may already contain the right things; we'll find that out later on)
trade_possible_msg = "This is not what "..minetest.formspec_escape(trade.npc_name)..
" wants.\nPlease insert the right payment!"
else
trade_possible_msg = "Please insert the right payment in the pay slot\n"..
"or click on \"buy\"."..
"]button[6.5,2.0;1.2,0.9;buy_directly;Buy]"..
"tooltip[buy_directly;"..
"Click here in order to buy directly without having to insert\n"..
"your payment manually into the pay slot."
can_trade = true
end
-- make sure the sale slot is empty (we will fill it if the trade is possible)
trade_inv:set_stack("buy", 1, "")
-- after all this: does the payment slot contain the right things?
if(can_trade and trade_inv:contains_item("pay", trade.player_gives)) then
trade_possible_msg = "Take the offered item(s) in order to buy them."
-- only new/undammaged tools, weapons and armor are accepted
if(trade_inv:get_stack("pay", 1):get_wear() > 0) then
trade_possible_msg = "Sorry. "..minetest.formspec_escape(trade.npc_name)..
" accepts only undammaged items."
else
-- put a *copy* of the item(stack) that is to be sold in the sale slot
trade_inv:add_item("buy", trade.npc_gives)
end
end
if(can_trade and not(player_inv:room_for_item("main", trade.npc_gives))) then
-- the player has no room for the sold item; give a warning
trade_possible_msg = "Careful! You do not seem to have enough\n"..
"free inventory space to store your purchase."
end
local trades_done = "Not yet traded."
if(yl_speak_up.trade[pname].trade_done > 0) then
trades_done = "Traded: "..tostring(yl_speak_up.trade[pname].trade_done).." time(s)"
end
return table.concat({formspec,
"label[2.5,0.0;Trading with ",
minetest.formspec_escape(trade.npc_name),
"]",
"label[1.5,0.7;You pay:]",
-- show images of price and what is sold so that the player knows what
-- it costs and what he will get even if the trade is not possible at
-- that moment
"item_image[2.1,1.2;0.8,0.8;",
tostring(trade.player_gives),
"]",
"item_image[5.1,1.2;0.8,0.8;",
tostring(trade.npc_gives),
"]",
"image[3.5,2.0;1,1;gui_furnace_arrow_bg.png^[transformR270]",
-- show the pay slot from the detached player's trade inventory
"list[detached:yl_speak_up_player_",
pname,
";pay;2,2.0;1,1;]",
-- show the buy slot from the same inventory
"list[detached:yl_speak_up_player_",
pname,
";buy;5,2.0;1,1;]",
"label[1.5,3.0;",
trade_possible_msg,
"]",
"label[6.0,1.5;",
trades_done,
"]"
}, "")
end
yl_speak_up.get_fs_do_trade_simple_wrapper = function(player, param)
local pname = player:get_player_name()
-- the optional parameter param is the trade_id
if(not(param) and yl_speak_up.speak_to[pname]) then
param = yl_speak_up.speak_to[pname].trade_id
end
return yl_speak_up.get_fs_do_trade_simple(player, param)
end
yl_speak_up.register_fs("do_trade_simple",
yl_speak_up.input_do_trade_simple,
yl_speak_up.get_fs_do_trade_simple_wrapper,
-- force formspec version 1:
1
)

View File

@ -116,3 +116,19 @@ yl_speak_up.get_fs_edit_trade_limit = function(player, selected_row)
}
return table.concat(formspec, '')
end
yl_speak_up.get_fs_edit_trade_limit = function(player, param)
if(not(param)) then
param = {}
end
return yl_speak_up.get_fs_edit_trade_limit(player, param.selected_row)
end
yl_speak_up.register_fs("edit_trade_limit",
yl_speak_up.input_edit_trade_limit,
yl_speak_up.get_fs_edit_trade_limit_wrapper,
-- force formspec version 1:
1
)

303
fs/fs_export.lua Normal file
View File

@ -0,0 +1,303 @@
yl_speak_up.export_to_simple_dialogs_language = function(dialog, n_id)
local d_liste = yl_speak_up.get_dialog_list_for_export(dialog)
local tmp = {}
for i, d_id in ipairs(d_liste) do
table.insert(tmp, "===")
-- TODO: use labels here when available
table.insert(tmp, tostring(yl_speak_up.d_id_to_d_name(dialog, d_id)))
table.insert(tmp, "\n")
-- :, > and = are not allowed as line start in simple dialogs
-- just add a leading blank so that any :, > and = at the start are covered
table.insert(tmp, " ")
local t = dialog.n_dialogs[d_id].d_text or ""
t = string.gsub(t, "\n([:>=])", "\n %1")
table.insert(tmp, t)
table.insert(tmp, "\n")
for o_id, o_data in pairs(dialog.n_dialogs[d_id].d_options or {}) do
local target_dialog = nil
for r_id, r_data in pairs(o_data.o_results or {}) do
if(r_data.r_type and r_data.r_type == "dialog") then
target_dialog = r_data.r_value
end
end
table.insert(tmp, ">")
table.insert(tmp, yl_speak_up.d_id_to_d_name(dialog, target_dialog or "d_1"))
table.insert(tmp, ":")
table.insert(tmp, o_data.o_text_when_prerequisites_met)
table.insert(tmp, "\n")
end
table.insert(tmp, "\n")
end
return table.concat(tmp, "")
end
yl_speak_up.input_export = function(player, formname, fields)
if(fields and fields.back) then
return yl_speak_up.show_fs(player, "talk")
elseif(fields and fields.show_readable) then
return yl_speak_up.show_fs(player, "export", "show_readable")
elseif(fields and (fields.back_to_export or fields.show_export)) then
return yl_speak_up.show_fs(player, "export", "show_export")
elseif(fields and fields.show_ink_export) then
return yl_speak_up.show_fs(player, "export", "show_ink_export")
elseif(fields and fields.show_simple_dialogs) then
return yl_speak_up.show_fs(player, "export", "show_simple_dialogs")
elseif(fields and (fields.import or fields.back_from_error_msg)) then
return yl_speak_up.show_fs(player, "export", "import")
elseif(fields and fields.really_import and fields.new_dialog_input
and string.sub(fields.new_dialog_input, 1, 3) == "-> ") then
local pname = player:get_player_name()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
local n_id = yl_speak_up.speak_to[pname].n_id
-- can the player edit this npc?
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
return yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:export",
formspec = yl_speak_up.build_fs_quest_edit_error(
"You do not own this NPC and are not allowed to edit it!",
"back_from_error_msg")})
end
-- import in ink format
local dialog = yl_speak_up.speak_to[pname].dialog
local log = {}
local log_level = 1
yl_speak_up.parse_ink.import_from_ink(dialog, fields.new_dialog_input, log_level, log)
-- save the changed dialog
yl_speak_up.save_dialog(n_id, dialog)
for i_, t_ in ipairs(log) do
minetest.chat_send_player(pname, t_)
end
-- log the change
return yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:export",
formspec = "size[10,3]"..
"label[0.5,1.0;Partially imported dialog data in ink format "..
" successfully.]"..
"button[3.5,2.0;2,0.9;back_from_error_msg;Back]"
})
elseif(fields and fields.really_import and fields.new_dialog_input) then
-- can that possibly be json format?
if(not(string.sub(fields.new_dialog_input, 1, 1) == "{")) then
return yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:export",
formspec = yl_speak_up.build_fs_quest_edit_error(
"This does not seem to be in .json format. Please make sure "..
"your import starts with a \"{\"!",
"back_from_error_msg")})
end
-- importing in .json format requires the "privs" priv
-- and it imports more information like npc name
if(not(minetest.check_player_privs(player, {privs=true}))) then
return yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:export",
formspec = yl_speak_up.build_fs_quest_edit_error(
"You need the \"privs\" priv in order to import NPC data.",
"back_from_error_msg")})
end
local pname = player:get_player_name()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
local n_id = yl_speak_up.speak_to[pname].n_id
-- actually import the dialog
local new_dialog = minetest.parse_json(fields.new_dialog_input or "")
if(not(new_dialog)) then
return yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:export",
formspec = yl_speak_up.build_fs_quest_edit_error(
"Failed to parse the .json data.",
"back_from_error_msg")})
end
-- TODO: the dialog has to be checked if it is a valid one (very big TODO!)
-- the ID has to be adjusted to this NPC
new_dialog.n_id = n_id
-- update the entity with name, description and owner
if yl_speak_up.speak_to[pname].obj then
local obj = yl_speak_up.speak_to[pname].obj
local ent = obj:get_luaentity()
if ent ~= nil then
ent.yl_speak_up.npc_name = new_dialog.n_npc
ent.yl_speak_up.npc_description = new_dialog.n_description
ent.owner = new_dialog.npc_owner
local i_text = new_dialog.n_npc .. "\n" ..
new_dialog.n_description .. "\n" ..
yl_speak_up.infotext
obj:set_properties({infotext = i_text})
yl_speak_up.update_nametag(ent)
end
end
-- import self.yl_speak_up (contains skin, animation, properties and the like)
local obj = yl_speak_up.speak_to[pname].obj
if(obj and new_dialog.entity_yl_speak_up) then
local entity = obj:get_luaentity()
if(entity) then
-- not all staticdata is changed
local staticdata = entity:get_staticdata()
-- we need to take the ID of this *current* NPC - not of the savedone!
local old_id = entity.yl_speak_up.id
new_dialog.entity_yl_speak_up.id = old_id
-- provide the entity with the new data
entity.yl_speak_up = new_dialog.entity_yl_speak_up
-- textures and infotext may depend on the mod
if(entity.yl_speak_up.entity_textures) then
entity.textures = entity.yl_speak_up.entity_textures
end
if(entity.yl_speak_up.entity_infotext) then
entity.infotext = entity.yl_speak_up.entity_infotext
end
-- update the entity
local dtime_s = 1
entity:on_activate(new_staticdata, dtime_s)
-- apply the changes
entity.object:set_properties(entity)
if(entity.yl_speak_up.animation) then
entity.object:set_animation(entity.yl_speak_up.animation)
end
-- get the updated staticdata (with new yl_speak_up values)
local new_staticdata = entity:get_staticdata()
-- update the nametag if possible
yl_speak_up.update_nametag(entity)
end
end
-- update the stored dialog
yl_speak_up.speak_to[pname].dialog = new_dialog
-- save it
yl_speak_up.save_dialog(n_id, new_dialog)
-- log the change
yl_speak_up.log_change(pname, n_id, "Imported new dialog in .json format (complete).")
return yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:export",
formspec = "size[10,3]"..
"label[0.5,1.0;Data successfully imported.]"..
"button[3.5,2.0;2,0.9;back_from_error_msg;Back]"
})
end
end
yl_speak_up.get_fs_export = function(player, param)
local pname = player:get_player_name()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
local n_id = yl_speak_up.speak_to[pname].n_id
-- generic dialogs are not part of the NPC
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
return ""
end
local text = ""
if(not(minetest.check_player_privs(pname, {privs=true}))) then
text = "You lack the \"privs\" priv that is required in order to import NPC data."
end
if(param and param == "import") then
return table.concat({"size[20,20]label[4,0.5;IMPORT for NPC ",
minetest.formspec_escape(n_id or "- ? -"),
"dialog data in .json format]",
"button[17.8,0.2;2.0,0.9;back_to_export;Back]",
"button[3.6,17.2;6.0,0.9;really_import;Yes, I'm sure. Import it!]",
"button[10.2,17.2;6.0,0.9;back_to_export;No. Go back, please.]",
-- read-only
"textarea[0.2,2;19.6,15;new_dialog_input;NEW dialog for ",
minetest.formspec_escape(n_id or "- ? -"),
":;",
text,
"]",
"textarea[0.2,18.2;19.6,1.8;;;",
"WARNING: This is highly experimental and requires the \"privs\" priv. "..
"Use in singleplayer on a new NPC - but not on a live server!]",
})
end
local content = ""
local explanation = ""
local b1 = "button[0.2,17.2;4.0,0.9;show_readable;Human readable]"..
"tooltip[show_readable;Shows the raw dialog data format formatted diffrently so\n"..
"that it is easier to read.]"
local b2 = "button[5.0,17.2;4.0,0.9;show_export;Export in .json]"..
"tooltip[show_export;Shows the raw dialog data format (.json).]"
local b3 = "button[9.8,17.2;4.0,0.9;show_simple_dialogs;Simple dialogs]"..
"tooltip[show_simple_dialogs;Show the dialog data structure in the format used by\n"..
"the mod \"simple_dialogs\".]"
local b4 = "button[14.6,17.2;4.0,0.9;show_ink_export;Ink language]"..
"tooltip[show_ink_export;Export the dialog data structure to the\n"..
"Ink markup language.]"
-- include self.yl_speak_up (contains skin, animation, properties and the like)
local obj = yl_speak_up.speak_to[pname].obj
if(obj) then
local entity = obj:get_luaentity()
if(entity) then
dialog.entity_yl_speak_up = entity.yl_speak_up
-- these data values vary too much depending on mod used for the NPC
if(entity.textures) then
dialog.entity_yl_speak_up.entity_textures = entity.textures
end
if(entity.infotext) then
dialog.entity_yl_speak_up.entity_infotext = entity.infotext
end
end
end
if(param and param == "show_readable") then
b1 = "label[0.2,17.6;This is human readable format]"
explanation = "This is like the raw dialog data format - except that it's written a bit "..
"diffrent so that it is easier to read."
content = minetest.write_json(dialog, true)
elseif(param and param == "show_ink_export") then
b4 = "label[14.6,17.6;Ink lanugage]"
-- TODO
explanation = "This is the format used by the \"Ink\" scripting language. "..
"TODO: The export is not complete yet."
content = yl_speak_up.export_to_ink_language(dialog, tostring(n_id).."_")
elseif(param and param == "show_simple_dialogs") then
b3 = "label[9.8,17.6;Simple dialogs format]"
explanation = "This is the format used by the \"simple_dialogs\" mod. "..
"It does not cover preconditions, actions, effects and other specialities of "..
"this mod here. It only covers the raw dialogs. If a dialog line starts with "..
"\":\", \">\" or \"=\", a \" \" is added before that letter because such a "..
"line start would not be allowed in \"simple_dialogs\"."
content = yl_speak_up.export_to_simple_dialogs_language(dialog, n_id)
else
b2 = "label[5.0,17.6;This is export in .json format]"
explanation = "Mark the text in the above window with your mouse and paste it into "..
"a local file on your disk. Save it as n_<id>.json (i.e. n_123.json) "..
"in the folder containing your dialog data. Then the NPCs in your "..
"local world can use this dialog."
-- TODO: import?
content = minetest.write_json(dialog, false)
end
return table.concat({"size[20,20]label[4,0.5;Export of NPC ",
minetest.formspec_escape(n_id or "- ? -"),
" dialog data in .json format]",
"button[17.8,0.2;2.0,0.9;back;Back]",
"button[15.4,0.2;2.0,0.9;import;Import]",
"tooltip[import;WARNING: This is highly experimental and requires the \"privs\" priv.\n"..
"Use in singleplayer on a new NPC - but not on a live server!]",
b1, b2, b3, b4,
-- background color for the textarea below
"box[0.2,2;19.6,15;#AAAAAA]",
-- read-only
"textarea[0.2,2;19.6,15;;Current dialog of ",
minetest.formspec_escape(n_id or "- ? -"),
":;",
minetest.formspec_escape(content),
"]",
"textarea[0.2,18.2;19.6,1.8;;;",
explanation,
"]",
})
end
yl_speak_up.register_fs("export",
yl_speak_up.input_export,
yl_speak_up.get_fs_export,
-- no special formspec required:
nil
)

270
fs/fs_initial_config.lua Normal file
View File

@ -0,0 +1,270 @@
-- set name, description and owner of the NPC
-- (owner can only be set if the player has the npc_talk_master
-- priv - not with npc_talk_owner priv alone)
yl_speak_up.input_fs_initial_config = function(player, formname, fields)
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
if(fields.back_from_error_msg) then
-- no point in showing the formspec or error message again if we did so already
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
return
end
-- show this formspec again
yl_speak_up.show_fs(player, "initial_config",
{n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
return
end
if(fields.button_export_dialog) then
yl_speak_up.show_fs(player, "export")
return
end
if((not(fields.save_initial_config)
and not(fields.show_nametag)
) or (fields and fields.exit)) then
local dialog = yl_speak_up.speak_to[pname].dialog
-- unconfigured NPC
if(fields and fields.exit and not(dialog) or not(dialog.n_dialogs)) then
minetest.chat_send_player(pname, "Aborting initial configuration.")
return
end
-- else we can quit here
return
end
local error_msg = nil
-- remove leading and tailing spaces from the potential new NPC name in order to avoid
-- confusing names where a player's name (or that of another NPC) is beginning/ending
-- with blanks
if(fields.n_npc) then
fields.n_npc = fields.n_npc:match("^%s*(.-)%s*$")
end
local dialog = yl_speak_up.speak_to[pname].dialog
-- the player is trying to save the initial configuration
-- is the player allowed to initialize this npc?
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
error_msg = "You are not allowed to edit this NPC."
elseif(not(fields.n_npc) or string.len(fields.n_npc) < 2) then
error_msg = "The name of your NPC needs to be\nat least two characters long."
elseif(minetest.check_player_privs(fields.n_npc, {interact=true})
and not(minetest.check_player_privs(player, {npc_talk_master=true}))
and not(minetest.check_player_privs(player, {npc_talk_admin=true}))) then
error_msg = "You cannot name your NPC after an existing player.\n"..
"Only those with the npc_talk_admin priv can do so."
elseif(not(fields.n_description) or string.len(fields.n_description) < 2) then
error_msg = "Please provide a description of your NPC!"
-- sensible length limit
elseif(string.len(fields.n_npc)>40 or string.len(fields.n_description)>40) then
error_msg = "The name and description of your NPC\ncannot be longer than 40 characters."
-- want to change the owner?
elseif(fields.n_owner and fields.n_owner ~= yl_speak_up.npc_owner[ n_id ]) then
if( not(minetest.check_player_privs(player, {npc_talk_master=true}))) then
error_msg = "You need the \"npc_talk_master\" priv\nin order to change the owner."
elseif(not(minetest.check_player_privs(fields.n_owner, {npc_talk_owner=true}))) then
error_msg = "The NPC can only be owned by players that\n"..
"have the \"npc_talk_owner\" priv. Else the\n"..
"new owner could not edit his own NPC."
end
end
if(error_msg) then
yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:initial_config",
formspec = "size[6,2]"..
"label[0.2,0.0;"..tostring(error_msg).."]"..
"button[2,1.5;1,0.9;back_from_error_msg;Back]"})
return
end
-- warn players with npc_talk_master priv if the name of an npc is used by a player already
if(minetest.check_player_privs(fields.n_npc, {interact=true})) then
minetest.chat_send_player(pname, "WARNING: A player named \'"..tostring(fields.n_npc)..
"\' exists. This NPC got assigned the same name!")
end
-- we checked earlier if the player doing this change and the
-- player getting the NPC have appropriate privs
if(fields.n_owner ~= yl_speak_up.npc_owner[ n_id ]) then
yl_speak_up.log_change(pname, n_id,
"Owner changed from "..tostring(yl_speak_up.npc_owner[ n_id ])..
" to "..tostring(fields.n_owner).." for "..
"NPC name: \""..tostring(fields.n_npc))
-- the owner will actually be changed further down, at the end of this function
yl_speak_up.npc_owner[ n_id ] = fields.n_owner
end
-- give the NPC its first dialog
if(not(dialog)
or not(dialog.created_at)
or not(dialog.n_npc)
or not(dialog.npc_owner)) then
-- TODO: pname == yl_speak_up.npc_owner[ n_id ] required
-- initialize the NPC with first dialog, name, description and owner:
yl_speak_up.initialize_npc_dialog_once(pname, dialog, n_id, fields.n_npc, fields.n_description)
end
-- initializing the dialog in the code above may have changed it
dialog = yl_speak_up.speak_to[pname].dialog
-- just change name and description
if((fields.n_npc and fields.n_npc ~= "")
and (fields.n_description and fields.n_description ~= "")) then
-- we checked that these fields contain values; are they diffrent from the existing ones?
if(dialog.n_npc ~= fields.n_npc
or dialog.n_description ~= fields.n_description) then
dialog.n_npc = fields.n_npc
dialog.n_description = fields.n_description
yl_speak_up.save_dialog(n_id, dialog)
yl_speak_up.log_change(pname, n_id,
"Name and/or description changed. "..
"NPC name: \""..tostring(fields.n_npc)..
"\" Description: \""..tostring(fields.n_description)..
"\" May be edited by: \""..
table.concat(yl_speak_up.sort_keys(dialog.n_may_edit or {}, true), " ").."\".")
end
end
-- show nametag etc.
if yl_speak_up.speak_to[pname].obj then
local obj = yl_speak_up.speak_to[pname].obj
local ent = obj:get_luaentity()
if ent ~= nil then
if(fields.show_nametag) then
local new_nametag_state = "- UNDEFINED -"
if(fields.show_nametag == "false") then
ent.yl_speak_up.hide_nametag = true
dialog.hide_nametag = true
new_nametag_state = "HIDE"
-- update_nametag else will only work on reload
obj:set_nametag_attributes({text=""})
elseif(fields.show_nametag == "true") then
ent.yl_speak_up.hide_nametag = nil
dialog.hide_nametag = nil
new_nametag_state = "SHOW"
end
yl_speak_up.save_dialog(n_id, dialog)
yl_speak_up.log_change(pname, n_id,
tostring(new_nametag_state).." nametag.")
minetest.chat_send_player(pname,
tostring(dialog.n_npc)..": I will "..
tostring(new_nametag_state).." my nametag.")
end
ent.yl_speak_up.npc_name = dialog.n_npc
ent.yl_speak_up.npc_description = dialog.n_description
ent.owner = yl_speak_up.npc_owner[ n_id ] or dialog.npc_owner
ent.yl_speak_up.real_owner = ent.owner
local i_text = dialog.n_npc .. "\n" ..
dialog.n_description .. "\n" ..
yl_speak_up.infotext
obj:set_properties({infotext = i_text})
yl_speak_up.update_nametag(ent)
end
end
-- the dialog id may be new due to the dialog having been initialized
local d_id = yl_speak_up.speak_to[pname].d_id
if(not(fields.save_initial_config)) then
yl_speak_up.show_fs(player, "initial_config",
{n_id = n_id, d_id = d_id, false})
return
end
if((fields.add_may_edit and fields.add_may_edit ~= "")
or (fields.delete_may_edit and fields.delete_may_edit ~= "")) then
-- show this formspec again
yl_speak_up.show_fs(player, "initial_config",
{n_id = n_id, d_id = d_id, false})
else
-- actually start a chat with our new npc
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
end
end
-- initialize the npc without having to use a staff;
-- returns true when initialization possible
-- the entries from add_formspec are added to the output
yl_speak_up.get_fs_initial_config = function(player, n_id, d_id, is_initial_config, add_formspec)
local pname = player:get_player_name()
-- is the player allowed to edit this npc?
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
return "size[6,2]"..
"label[0.2,0.0;Sorry. You are not authorized\nto edit this NPC.]"..
"button_exit[2,1.5;1,0.9;back_from_error_msg;Exit]"
end
local tmp_show_nametag = "true"
local tmp_name = n_id
local tmp_descr = "A new NPC without description"
local tmp_text = "Please provide your new NPC with a name and description!"
local tmp_owner = (yl_speak_up.npc_owner[ n_id ] or "- none -")
-- use existing name and description as presets when just editing
if(not(is_initial_config)) then
local dialog = yl_speak_up.speak_to[pname].dialog
tmp_show_nametag = not(dialog.hide_nametag)
tmp_name = (dialog.n_npc or tmp_name)
tmp_descr = (dialog.n_description or tmp_descr)
tmp_text = "You can change the name and description of your NPC."
end
local formspec = {"size[11,8.0]",
"label[0.2,0.5;",
tmp_text,
"]",
"button[9.0,0.2;1.8,0.9;button_export_dialog;Export]",
"tooltip[button_export_dialog;"..
"Export: Show the dialog in .json format which you can"..
"\n\tcopy and store on your computer.]",
-- name of the npc
"checkbox[2.2,0.9;show_nametag;Show nametag;",
tostring(tmp_show_nametag),
"]",
"label[0.2,1.65;Name:]",
"field[2.2,1.2;4,0.9;n_npc;;",
minetest.formspec_escape(tmp_name),
"]",
"label[7.0,1.65;NPC ID: ",
minetest.colorize("#FFFF00",tostring(n_id)),
"]",
"tooltip[n_npc;n_npc: The name of the NPC;#FFFFFF;#000000]",
-- description of the npc
"label[0.2,2.65;Description:]",
"field[2.2,2.2;8,0.9;n_description;;",
minetest.formspec_escape(tmp_descr),
"]",
"tooltip[n_description;n_description: A description for the NPC;#FFFFFF;#000000]",
-- the owner of the NPC
"label[0.2,3.65;Owner:]",
"field[2.2,3.2;8,0.9;n_owner;;",
minetest.formspec_escape(tmp_owner),
"]",
"tooltip[n_owner;The owner of the NPC. This can only be changed\n"..
"if you have the npc_talk_master priv.;#FFFFFF;#000000]",
-- save and exit buttons
"button[3.2,7.0;2,0.9;save_initial_config;Save]",
"button_exit[5.4,7.0;2,0.9;exit;Exit]"
}
-- add some entries in edit mode
if(add_formspec) then
for _, v in ipairs(add_formspec) do
table.insert(formspec, v)
end
elseif(not(is_initial_config)) then
-- TODO: add import/export/show texture?
end
-- show the formspec to the player
return table.concat(formspec, "")
end
yl_speak_up.get_fs_initial_config_wrapper = function(player, param)
if(not(param)) then
param = {}
end
return yl_speak_up.get_fs_initial_config(player, param.n_id, param.d_id, param.is_initial_config, nil)
end
yl_speak_up.register_fs("initial_config",
yl_speak_up.input_fs_initial_config,
yl_speak_up.get_fs_initial_config_wrapper,
-- no special formspec required:
nil
)

78
fs/fs_inventory.lua Normal file
View File

@ -0,0 +1,78 @@
-- the player has closed the inventory formspec of the NPC - save it
yl_speak_up.input_inventory = function(player, formname, fields)
local pname = player:get_player_name()
local d_id = yl_speak_up.speak_to[pname].d_id
local n_id = yl_speak_up.speak_to[pname].n_id
-- after closing the inventory formspec:
-- ..save the (very probably) modified inventory
yl_speak_up.save_npc_inventory(n_id)
-- show inventory again?
if(fields.back_from_error_msg) then
yl_speak_up.show_fs(player, "inventory")
return
end
-- show the trade list?
if(fields.inventory_show_tradelist) then
yl_speak_up.show_fs(player, "trade_list")
return
end
-- ..and go back to the normal talk formspec
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
end
-- access the inventory of the NPC (only possible for players with the right priv)
yl_speak_up.get_fs_inventory = function(player)
if(not(player)) then
return ""
end
local pname = player:get_player_name()
-- which NPC is the player talking to?
local n_id = yl_speak_up.speak_to[pname].n_id
local dialog = yl_speak_up.speak_to[pname].dialog
-- do we have all the necessary data?
if(not(n_id) or not(dialog.n_npc)) then
return "size[6,2]"..
"label[0.2,0.5;Ups! This NPC lacks ID or name.]"..
"button_exit[2,1.5;1,0.9;exit;Exit]"
end
-- only players which can edit this npc can see its inventory
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
return "size[6,2]"..
"label[0.2,0.5;Sorry. You lack the privileges.]"..
"button_exit[2,1.5;1,0.9;exit;Exit]"
end
-- make sure the inventory of the NPC is loaded
yl_speak_up.load_npc_inventory(n_id, true, nil)
return table.concat({"size[12,11]",
"label[2,-0.2;Inventory of ",
minetest.formspec_escape(dialog.n_npc),
" (ID: ",
tostring(n_id),
"):]",
"list[detached:yl_speak_up_npc_",
tostring(n_id),
";npc_main;0,0.3;12,6;]",
"list[current_player;main;2,7.05;8,1;]",
"list[current_player;main;2,8.28;8,3;8]",
"listring[detached:yl_speak_up_npc_",
tostring(n_id),
";npc_main]",
"listring[current_player;main]",
"button[3.5,6.35;5,0.6;inventory_show_tradelist;Show trade list trades (player view)]",
"button[10.0,10.4;2,0.9;back_from_inventory;Back]"
}, "")
end
yl_speak_up.register_fs("inventory",
yl_speak_up.input_inventory,
yl_speak_up.get_fs_inventory,
-- this is a very classical formspec; it works far better with OLD fs;
-- force formspec version 1:
1
)

274
fs/fs_npc_list.lua Normal file
View File

@ -0,0 +1,274 @@
-- allow to sort the npc list, display more info on one NPC etc.
yl_speak_up.input_show_npc_list = function(player, formname, fields)
local pname = player:get_player_name()
-- teleport to NPC
if(fields.teleport
and fields.selected_id
and yl_speak_up.cache_npc_list_per_player[pname]
and minetest.check_player_privs(pname, {teleport=true})) then
local id = tonumber(fields.selected_id)
if(not(id) or id < 0
or not(yl_speak_up.npc_list[id])
or table.indexof(yl_speak_up.cache_npc_list_per_player[pname], id) < 1) then
minetest.chat_send_player(pname, "Sorry. Cannot find that NPC.")
return
end
-- try cached position
local pos = yl_speak_up.npc_list[id].pos
local obj = yl_speak_up.npc_list_objects[id]
if(obj) then
pos = obj:get_pos()
end
if(not(pos) or not(pos.x) or not(pos.y) or not(pos.z)) then
pos = yl_speak_up.npc_list[id].pos
end
if(not(pos) or not(pos.x) or not(pos.y) or not(pos.z)) then
minetest.chat_send_player(pname, "Sorry. Cannot find position of that NPC.")
return
end
player:set_pos(pos)
minetest.chat_send_player(pname, "Teleporting to NPC with ID "..
tostring(fields.selected_id)..': '..
tostring(yl_speak_up.npc_list[id].name)..'.')
return
end
-- sort by column or select an NPC
if(fields.show_npc_list) then
local selected = minetest.explode_table_event(fields.show_npc_list)
-- sort by column
if(selected.row == 1) then
local old_sort = yl_speak_up.sort_npc_list_per_player[pname] or 0
-- reverse sort
if(old_sort == selected.column) then
yl_speak_up.sort_npc_list_per_player[pname] = -1 * selected.column
else -- sort by new col
yl_speak_up.sort_npc_list_per_player[pname] = selected.column
end
-- show the update
yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_npc_list",
yl_speak_up.get_fs_show_npc_list(pname, nil))
return
else
-- show details about a specific NPC
yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_npc_list",
yl_speak_up.get_fs_show_npc_list(pname, selected.row))
return
end
end
return
end
-- allow to toggle between trade entries and full log
-- Note: takes pname instead of player(object) as first parameter
yl_speak_up.get_fs_show_npc_list = function(pname, selected_row)
-- which NPC can the player edit?
local level = 0
if( minetest.check_player_privs(pname, {npc_master=true})
or minetest.check_player_privs(pname, {npc_talk_master=true})
or minetest.check_player_privs(pname, {npc_talk_admin=true})) then
level = 2
elseif(minetest.check_player_privs(pname, {npc_talk_owner=true})) then
level = 1
end
if(level < 1) then
return "size[5,1]label[0,0;Error: You do not have the npc_talk_owner priv.]"
end
local formspec_start = 'size[18,14.7]'..
'label[4.5,0.5;List of all NPC (that you can edit)]'..
'tablecolumns[' ..
'color;text,align=right;'.. -- the ID
'color;text,align=center;'.. -- is the NPC a generic one?
'color;text,align=left;'.. -- the name of the NPC
'color;text,align=center;'.. -- the name of the owner of the NPC
'color;text,align=right;'.. -- number of trades offered
'color;text,align=right;'.. -- number of properties set
'color;text,align=right;'.. -- number of people who can edit NPC
'color;text,align=center;'.. -- last known position
'color;text,align=center]'.. -- does he have extra privs?
'table[0.1,1.0;17.8,9.8;show_npc_list;'
-- add information about a specific NPC (selected row)
local info_current_row = ''
if(selected_row
and selected_row > 1
and yl_speak_up.cache_npc_list_per_player[pname]
and yl_speak_up.cache_npc_list_per_player[pname][selected_row-1]) then
local k = yl_speak_up.cache_npc_list_per_player[pname][selected_row-1]
local data = yl_speak_up.npc_list[k]
local line = yl_speak_up.cache_general_npc_list_lines[k]
if(data) then
local edit_list = {data.owner}
if(data.may_edit) then
for e, t in pairs(data.may_edit or {}) do
table.insert(edit_list, e)
end
end
local n_id = 'n_'..tostring(k)
local priv_list = {}
if(yl_speak_up.npc_priv_table[n_id]) then
for priv, has_it in pairs(yl_speak_up.npc_priv_table[n_id]) do
table.insert(priv_list, priv)
end
else
priv_list = {'- none -'}
end
local prop_text = 'label[3.0,2.0;- none -]'
if(data.properties) then
local prop_list = {}
for k, v in pairs(data.properties) do
table.insert(prop_list, minetest.formspec_escape(
tostring(k)..' = '..tostring(v)))
end
if(#prop_list > 0) then
prop_text = 'dropdown[3.0,1.8;8,0.6;properties;'..
table.concat(prop_list, ',')..';;]'
end
end
local first_seen_at = '- unknown -'
if(data.created_at and data.created_at ~= "") then
first_seen_at = minetest.formspec_escape(os.date("%m/%d/%y", data.created_at))
end
-- allow those with teleport priv to easily visit their NPC
local teleport_button = ''
if(minetest.check_player_privs(pname, {teleport=true})) then
-- the ID of the NPC we want to visit is hidden in a field; this is unsafe,
-- but the actual check needs to happen when the teleport button is pressed
-- anyway
teleport_button = 'field[40,40;0,0;selected_id;;'..tostring(k)..']'..
'button_exit[12.1,1.8;5,0.6;teleport;Teleport to this NPC]'
end
info_current_row =
'container[0.1,11.2]'..
'label[0.1,0.0;Name, Desc:]'..
'label[3.0,0.0;'..tostring(line.n_name)..']'..
'label[0.1,0.5;Typ:]'..
'label[3.0,0.5;'..
minetest.formspec_escape(tostring(data.typ or '- ? -'))..']'..
'label[12.1,0.5;First seen at:]'..
'label[14.4,0.5;'..
first_seen_at..']'..
'label[0.1,1.0;Can be edited by:]'..
'label[3.0,1.0;'..
minetest.formspec_escape(table.concat(edit_list, ', '))..']'..
'label[0.1,1.5;Has the privs:]'..
'label[3.0,1.5;'..
minetest.formspec_escape(table.concat(priv_list, ', '))..']'..
'label[0.1,2.0;Properties:]'..
prop_text..
teleport_button..
'container_end[]'
end
else
selected_row = 1
info_current_row = 'label[0.1,11.2;Click on a column name/header in order to sort by '..
'that column. Click it again in order to reverse sort order.\n'..
'Click on a row to get more information about a specific NPC.\n'..
'Only NPC that can be edited by you are shown.\n'..
'Legend: \"G\": is generic NPC. '..
'\"#Tr\", \"#Pr\": Number of trades or properties the NPC offers.\n'..
' \"#Ed\": Number of players that can edit the NPC. '..
'\"Privs\": List of abbreviated names of privs the NPC has.]'
end
local formspec = {}
-- TODO: blocks may also be talked to
local tmp_liste = {}
for k, v in pairs(yl_speak_up.npc_list) do
-- show only NPC - not blocks
if(type(k) == "number" and (level == 2
or (v.owner and v.owner == pname)
or (v.may_edit and v.may_edit[pname]))) then
table.insert(tmp_liste, k)
end
end
-- the columns with the colors count as well even though they can't be selected
-- (don't sort the first column by n_<id> STRING - sort by <id> NUMBER)
local col_names = {"id", "id", "is_generic", "is_generic", "n_name", "n_name",
"owner", "owner", "anz_trades", "anz_trades",
"anz_properties", "anz_properties", "anz_editors", "anz_editors",
"pos", "pos", "priv_list", "priv_list"}
local sort_col = yl_speak_up.sort_npc_list_per_player[pname]
if(not(sort_col) or sort_col == 0) then
table.sort(tmp_liste)
elseif(sort_col > 0) then
-- it is often more helpful to sort in descending order
local col_name = col_names[sort_col]
table.sort(tmp_liste, function(a, b)
return yl_speak_up.cache_general_npc_list_lines[a][col_name]
> yl_speak_up.cache_general_npc_list_lines[b][col_name]
end)
else
local col_name = col_names[sort_col * -1]
table.sort(tmp_liste, function(a, b)
return yl_speak_up.cache_general_npc_list_lines[a][col_name]
< yl_speak_up.cache_general_npc_list_lines[b][col_name]
end)
end
local col_headers = {'n_id', 'G', 'Name', 'Owner', '#Tr', '#Pr', '#Ed', 'Position', 'Privs'}
for i, k in ipairs(col_headers) do
if( sort_col and sort_col == (i * 2)) then
table.insert(formspec, 'yellow')
table.insert(formspec, 'v '..k..' v')
elseif(sort_col and sort_col == (i * -2)) then
table.insert(formspec, 'yellow')
table.insert(formspec, '^ '..k..' ^')
else
table.insert(formspec, '#FFFFFF')
table.insert(formspec, k)
end
end
yl_speak_up.cache_npc_list_per_player[pname] = tmp_liste
for i, k in ipairs(tmp_liste) do
local data = yl_speak_up.npc_list[k]
local line = yl_speak_up.cache_general_npc_list_lines[k]
-- own NPC are colored green, others white
local owner_color = '#FFFFFF'
if(data.owner == pname) then
owner_color = '#00FF00'
elseif (data.may_edit and data.may_edit[pname]) then
owner_color = '#FFFF00'
end
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.n_id)
table.insert(formspec, 'orange')
table.insert(formspec, line.is_generic)
table.insert(formspec, line.npc_color)
table.insert(formspec, line.n_name)
table.insert(formspec, owner_color) -- diffrent for each player
table.insert(formspec, line.owner)
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.anz_trades)
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.anz_properties)
table.insert(formspec, owner_color) -- diffrent for each player
table.insert(formspec, line.anz_editors)
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.pos)
table.insert(formspec, line.is_loaded_color)
table.insert(formspec, line.priv_list)
end
table.insert(formspec, ";"..selected_row.."]")
return table.concat({formspec_start,
table.concat(formspec, ','),
info_current_row,
'button_exit[0.1,14;19.6,0.6;exit;Exit]'}, '')
end
yl_speak_up.register_fs("npc_list",
yl_speak_up.input_npc_list,
yl_speak_up.get_fs_npc_list,
-- no special formspec required:
nil
)
-- at load/reload of the mod: read the list of existing NPC
yl_speak_up.npc_list_load()

View File

@ -2,21 +2,22 @@
yl_speak_up.get_fs_player_offers_item = function(player, param)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
return yl_speak_up.show_fs_simple_deco(8.5, 8)..
"list[current_player;main;0.2,3.85;8,1;]"..
"list[current_player;main;0.2,5.08;8,3;8]"..
"button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]"..
"button[4.75,1.6;1.5,0.9;finished_action;Give]"..
return table.concat({yl_speak_up.show_fs_simple_deco(8.5, 8),
"list[current_player;main;0.2,3.85;8,1;]",
"list[current_player;main;0.2,5.08;8,3;8]",
"button[0.2,0.0;2.0,0.9;back_to_talk;Back to talk]",
"button[4.75,1.6;1.5,0.9;finished_action;Give]",
"tooltip[back_to_talk;Click here if you're finished with giving\n"..
"items to the NPC.]"..
"items to the NPC.]",
"tooltip[finished_action;Click here once you have placed the item in\n"..
"the waiting slot.]"..
"label[1.5,0.7;What do you want to give to "..
minetest.formspec_escape(dialog.n_npc or "- ? -").."?]"..
"the waiting slot.]",
"label[1.5,0.7;What do you want to give to ",
minetest.formspec_escape(dialog.n_npc or "- ? -"), "?]",
-- the npc_wants inventory slot can be used here as well
"list[detached:yl_speak_up_player_"..pname..";npc_wants;3.25,1.5;1,1;]" ..
"list[detached:yl_speak_up_player_", pname, ";npc_wants;3.25,1.5;1,1;]",
"label[1.5,2.7;Insert the item here and click on \"Give\" to proceed.]"
}, "")
end
@ -63,8 +64,17 @@ yl_speak_up.input_player_offers_item = function(player, formname, fields)
-- show a message to the player
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:player_offers_item",
formspec = yl_speak_up.show_fs_simple_deco(8, 2.5)..
"label[0.5,0.5;"..
error_msg.."]"..
"button[3.5,1.5;1.5,1.0;back_from_error_msg;Back]"})
formspec = table.concat({yl_speak_up.show_fs_simple_deco(8, 2.5),
"label[0.5,0.5;",
error_msg, "]"..
"button[3.5,1.5;1.5,1.0;back_from_error_msg;Back]"}, ""),
""})
end
yl_speak_up.register_fs("player_offers_item",
yl_speak_up.input_player_offers_item,
yl_speak_up.get_fs_player_offers_item,
-- force formspec version 1:
1
)

View File

@ -1,74 +1,4 @@
-- handle logging
-- log changes done by players or admins to NPCs
yl_speak_up.log_change = function(pname, n_id, text, log_level)
-- make sure all variables are defined
if(not(pname)) then
pname = "- unkown player -"
end
if(not(n_id)) then
n_id = "- unknown NPC -"
end
if(not(text)) then
text = "- no text given -"
end
if(not(log_level)) then
log_level = "info"
end
-- we don't want newlines in the texts
text = string.gsub(text, "\n", "\\n")
-- log in debug.txt
local log_text = "<"..tostring(n_id).."> ["..tostring(pname).."]: "..text
minetest.log(log_level, "[MOD] yl_speak_up "..log_text)
-- log in a file for each npc so that it can be shown when needed
-- date needs to be inserted manually (minetest.log does it automaticly);
-- each file logs just one npc, so n_id is not important
log_text = tostring(os.date("%Y-%m-%d %H:%M:%S ")..tostring(pname).." "..text.."\n")
n_id = tostring(n_id)
if(n_id and n_id ~= "" and n_id ~= "n_" and n_id ~= "- unkown NPC -") then
-- actually append to the logfile
local file, err = io.open(yl_speak_up.worldpath..yl_speak_up.log_path..DIR_DELIM..
"log_"..tostring(n_id)..".txt", "a")
if err then
minetest.log("error", "[MOD] yl_speak_up Error saving NPC logfile: "..minetest.serialize(err))
return
end
file:write(log_text)
file:close()
end
-- log into a general all-npc-file as well
local file, err = io.open(yl_speak_up.worldpath..yl_speak_up.log_path..DIR_DELIM..
"log_ALL.txt", "a")
if err then
minetest.log("error","[MOD] yl_speak_up Error saving NPC logfile: "..minetest.serialize(err))
return
end
file:write(tostring(n_id).." "..log_text)
file:close()
end
-- this is used by yl_speak_up.eval_and_execute_function(..) in fs_edit_general.lua
yl_speak_up.log_with_position = function(pname, n_id, text, log_level)
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
yl_speak_up.log_change(pname, n_id,
"error: -npc not found- "..tostring(text))
return
end
local obj = yl_speak_up.speak_to[pname].obj
local n_id = yl_speak_up.speak_to[pname].n_id
local pos_str = "-unknown-"
if obj:get_luaentity() and tonumber(npc) then
pos_str = minetest.pos_to_string(obj:get_pos(),0)
end
yl_speak_up.log_change(pname, n_id,
"NPC at position "..pos_str.." "..tostring(text), log_level)
end
-- show logfile of the NPC to the player
yl_speak_up.input_show_log = function(player, formname, fields)
local pname = player:get_player_name()
@ -232,3 +162,17 @@ yl_speak_up.get_fs_show_log = function(player, log_type)
return table.concat(formspec, '')
end
yl_speak_up.get_fs_show_log_wrapper = function(player, param)
if(not(param)) then
param = {}
end
return yl_speak_up.get_fs_show_log(player, param.log_type)
end
yl_speak_up.register_fs("show_log",
yl_speak_up.input_show_log,
yl_speak_up.get_fs_show_log_wrapper,
-- no special formspec required:
nil
)

635
fs/fs_talkdialog.lua Normal file
View File

@ -0,0 +1,635 @@
-- This is the main talkdialog the NPC shows when right-clicked.
-- Returns o (selected dialog option) if fields.just_return_selected_option
-- is set (useful for edit_mode).
yl_speak_up.input_talk = function(player, formname, fields)
if formname ~= "yl_speak_up:talk" then
return
end
local pname = player:get_player_name()
-- error: not talking?
if(not(yl_speak_up.speak_to[pname])) then
return
end
local n_id = yl_speak_up.speak_to[pname].n_id
local d_id = yl_speak_up.speak_to[pname].d_id
local dialog = yl_speak_up.speak_to[pname].dialog
-- the NPC needs to be configured first; route input to the configuration dialog
if(not(dialog)
or not(dialog.n_npc)
or not(d_id)) then
yl_speak_up.input_fs_initial_config(player, formname, fields)
return
end
if(fields.show_log) then
-- show a log
yl_speak_up.show_fs(player, "show_log", {log_type = "full"})
return
end
-- mobs_redo based NPC may follow their owner, stand or wander around
local new_move_order = ""
if(fields.order_stand) then
new_move_order = "stand"
elseif(fields.order_follow) then
new_move_order = "follow"
elseif(fields.order_wander) then
new_move_order = "wander"
end
if(new_move_order ~= "") then
local dialog = yl_speak_up.speak_to[pname].dialog
yl_speak_up.set_npc_property(pname, "self.order", new_move_order, "move_order")
minetest.chat_send_player(pname,
tostring(dialog.n_npc or "NPC").." tells you: "..
"Ok. I will "..tostring(new_move_order)..".")
yl_speak_up.stop_talking(pname)
return
end
-- useful for i.e. picking up the mob
if(fields.order_custom) then
local obj = yl_speak_up.speak_to[pname].obj
-- some precautions - someone else might have eliminated the NPC in the meantime
local luaentity = nil
if(obj) then
luaentity = obj:get_luaentity()
end
if(luaentity and luaentity.name and yl_speak_up.add_on_rightclick_entry[luaentity.name]) then
local m = yl_speak_up.add_on_rightclick_entry[luaentity.name]
local ret = m.execute_function(luaentity, player)
end
yl_speak_up.stop_talking(pname)
return
end
-- normal mode + edit_mode (not exclusive to edit_mode):
if fields.quit or fields.button_exit then
-- if there are any changes done: ask first and don't quit yet
yl_speak_up.show_fs(player, "quit", nil)
return
end
-- allow the player to take the item back
if(fields.show_player_offers_item and fields.show_player_offers_item ~= "") then
yl_speak_up.show_fs(player, "player_offers_item", nil)
return
end
-- the player wants to give something to the NPC
-- (less complex outside edit mode)
if(fields.player_offers_item) then
-- normal mode: take the item the player wants to offer
yl_speak_up.show_fs(player, "player_offers_item", nil)
return
end
-- the player wants to access the inventory of the NPC
if(fields.show_inventory and yl_speak_up.may_edit_npc(player, n_id)) then
-- the inventory is just an inventory with a back button; come back to this dialog here
yl_speak_up.show_fs(player, "inventory")
return
end
-- the player wants to see the trade list
if(fields.show_trade_list) then
yl_speak_up.show_fs(player, "trade_list", nil)
return
end
-- allow some basic editing (name, description, owner) even without editor mod installed:
if(fields.button_edit_basics) then
yl_speak_up.show_fs(player, "initial_config",
{n_id = n_id, d_id = d_id, false})
return
end
if fields.button_up then
yl_speak_up.speak_to[pname].option_index =
yl_speak_up.speak_to[pname].option_index + yl_speak_up.max_number_of_buttons
yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id,
d_id = yl_speak_up.speak_to[pname].d_id})
return
elseif fields.button_down then --and yl_speak_up.speak_to[pname].option_index > yl_speak_up.max_number_of_buttons then
yl_speak_up.speak_to[pname].option_index =
yl_speak_up.speak_to[pname].option_index - yl_speak_up.max_number_of_buttons
if yl_speak_up.speak_to[pname].option_index < 0 then
yl_speak_up.speak_to[pname].option_index = 1
end
yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id,
d_id = yl_speak_up.speak_to[pname].d_id})
return
else
yl_speak_up.speak_to[pname].option_index = 1
end
-- has an option/answer been selected?
local o = ""
for k, v in pairs(fields) do
-- only split into 2 parts at max
local s = string.split(k, "_", false, 2)
if(s[1] == "button"
and s[2] ~= nil and s[2] ~= "" and s[2] ~= "exit" and s[2] ~= "back" and s[3] ~= nil
and s[2] ~= "up" and s[2] ~= "down") then
o = s[2] .. "_" .. s[3]
end
end
-- this is for edit mode - we need a diffrent reaction there, not executing actions
if(fields.just_return_selected_option) then
return o
end
-- nothing selected
if(o == "") then
return
end
-- Let's check if the button was among the "allowed buttons". Only those may be executed
if(not(yl_speak_up.speak_to[pname].allowed) or not(yl_speak_up.speak_to[pname].allowed[o])) then
return
end
-- we may soon need actions and o_results from the selected_option
local selected_option = {}
if(yl_speak_up.check_if_dialog_has_option(dialog, d_id, o)) then
selected_option = dialog.n_dialogs[d_id].d_options[o]
end
-- abort if the option does not exist
if(not(selected_option)) then
return
end
yl_speak_up.speak_to[pname].o_id = o
-- store this a bit longer than o_id above (for yl_speak_up.generate_next_dynamic_dialog):
yl_speak_up.speak_to[pname].selected_o_id = o
-- start with executing the first action
yl_speak_up.execute_next_action(player, nil, true, formname)
return
end
-- helper function for yl_speak_up.get_fs_talkdialog:
-- shows the text the NPC "speaks"
-- (this is pretty boring; the more intresting stuff happens in edit_mode)
yl_speak_up.get_fs_talkdialog_main_text = function(pname, formspec, h, dialog, dialog_list, c_d_id, active_dialog, alternate_text)
local fs_version = yl_speak_up.fs_version[pname]
formspec = yl_speak_up.show_fs_npc_text(pname, formspec, dialog, alternate_text, active_dialog, fs_version)
return {h = h, formspec = formspec, d_id_to_dropdown_index = {}, dialog_list = dialog_list}
end
-- helper function for yl_speak_up.get_fs_talkdialog:
-- prints one entry (option/answer) in normal mode - not in edit_mode
yl_speak_up.get_fs_talkdialog_line = function(
formspec, h, pname_for_old_fs, oid, sb_v,
dialog, allowed, pname,
-- these additional parameters are needed in edit_mode and not used here
active_dialog, dialog_list, d_id_to_dropdown_index,
current_index, anz_options)
local t = "- no text given -"
local t_alt = nil
-- the preconditions are fulfilled; showe the option
if(allowed[sb_v.o_id] == true) then
-- replace $NPC_NAME$ etc.
t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text(
sb_v.o_text_when_prerequisites_met, dialog, pname))
-- precondition not fulfilled? the option shall be hidden
elseif(sb_v.o_hide_when_prerequisites_not_met == "true") then
-- show nothing; t_alt remains nil
t = nil
-- precondition not fulfilled, and autoanswer active? Then hide this option.
elseif(sb_v.o_autoanswer) then
-- show nothing; t_alt remains nil
t = nil
-- precondition not fulfilled? the option shall be greyed out
-- default to greyed out (this option cannot be selected)
elseif(sb_v.o_grey_when_prerequisites_not_met == "true") then
local text = sb_v.o_text_when_prerequisites_not_met
if(not(text) or text == "") then
text = t or yl_speak_up.message_button_option_prerequisites_not_met_default
end
t = nil
-- replace $NPC_NAME$ etc.
t_alt = minetest.formspec_escape(yl_speak_up.replace_vars_in_text(
text, dialog, pname))
elseif(sb_v.o_grey_when_prerequisites_not_met == "false"
and sb_v.o_text_when_prerequisites_not_met ~= "") then
-- show in normal coor
t = minetest.formspec_escape(yl_speak_up.replace_vars_in_text(
sb_v.o_text_when_prerequisites_not_met, dialog, pname))
end
if(t or t_alt) then
-- some options can be visited only once; talking to the NPC anew resets that
-- (not stored persistently)
if(t and sb_v.visits and sb_v.visits > 0
and sb_v.o_visit_only_once and sb_v.o_visit_only_once == 1) then
t_alt = minetest.formspec_escape("[Done] ")..t
t = nil
end
-- actually show the button
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"button_" .. oid,
t,
t,
(t and not(t_alt)),
t_alt,
nil, pname_for_old_fs)
end
return {h = h, formspec = formspec}
end
-- at least allow editing name, description and owner - even without the editor mod installed
yl_speak_up.get_fs_talkdialog_add_basic_edit = function(
pname, formspec, h, pname_for_old_fs, is_a_start_dialog,
active_dialog, luaentity, may_edit_npc, anz_options)
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"button_edit_basics",
"Edit name, description and owner of your NPC.\n"..
"For more editing, install npc_talk_edit!",
-- chat option: "[Edit name, description and owner.]"
minetest.formspec_escape("[Edit name, description and owner.]"),
true, nil, false, pname_for_old_fs) -- *not* an exit button
return {h = h, formspec = formspec}
end
-- helper function for yl_speak_up.get_fs_talkdialog:
-- if the player can edit the NPC,
-- either add a button for entering edit mode
-- or add the buttons needed to edit the dialog when in *edit mode*
yl_speak_up.get_fs_talkdialog_add_edit_and_command_buttons = function(
pname, formspec, h, pname_for_old_fs, is_a_start_dialog,
active_dialog, luaentity, may_edit_npc, anz_options)
-- outside edit mode: nothing to add here
if(not(may_edit_npc)) then
return {h = h, formspec = formspec}
end
if(is_a_start_dialog) then
-- show the "show your inventory"-button even when not in edit mode
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"show_inventory",
"Access and manage the inventory of the NPC. This is used for adding trade "..
"items, getting collected payments and managing quest items.",
"Show your inventory (only accessible to owner)!",
true, nil, nil, pname_for_old_fs)
end
-- mobs_redo based NPC can follow, stand or wander around
if(luaentity and luaentity.order and may_edit_npc
-- not all mobs need or support this feature
and table.indexof(yl_speak_up.emulate_orders_on_rightclick, luaentity.name) > -1) then
if(luaentity.order ~= "follow") then
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"order_follow",
"The NPC will follow you.",
"New order: Follow me!",
((luaentity.owner == pname) and (luaentity.order ~= "follow")),
"New order: Follow me. (Only available for owner).",
nil, pname_for_old_fs)
end
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"order_stand",
"The NPC will wait here.",
"New order: Stand here.",
(luaentity.order ~= "stand"), nil, nil, pname_for_old_fs)
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"order_wander",
"The NPC will wander around randomly.",
"New order: Wander around a bit on your own.",
(luaentity.order ~= "walking"), nil, nil, pname_for_old_fs)
end
-- some mobs may need additional things in on_rightclick (i.e. beeing picked up)
if(luaentity and may_edit_npc
and yl_speak_up.add_on_rightclick_entry[luaentity.name]) then
local m = yl_speak_up.add_on_rightclick_entry[luaentity.name]
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"order_custom",
minetest.formspec_escape(m.text_if_false),
minetest.formspec_escape(m.text_if_true),
(m.condition), nil, nil, pname_for_old_fs)
end
local res = yl_speak_up.get_fs_talkdialog_add_basic_edit(
pname, formspec, h, pname_for_old_fs, is_a_start_dialog,
active_dialog, luaentity, may_edit_npc, anz_options)
h = res.h
return {h = h, formspec = formspec}
end
-- apply autoanswer, random dialog switching and d_got_item if needed;
-- returns the new formspec if any automatic switching happened
-- (this will not be done if in edit_mode)
yl_speak_up.apply_autoanswer_and_random_and_d_got_item = function(player, pname, d_id, dialog, allowed, active_dialog, recursion_depth)
-- autoanswer or o_random may force to select a particular dialog
local go_to_next_dialog = nil
-- abort here if needed - the autoanswer/autoselection did choose an option for us alread
if(allowed and allowed["autoanswer"] and allowed["autoanswer"] ~= "") then
go_to_next_dialog = allowed["autoanswer"]
-- randomly select an answer
elseif(allowed and active_dialog.o_random
and (recursion_depth < yl_speak_up.max_allowed_recursion_depth)) then
local liste = {}
-- only allowed options can be randomly selected from
for o_id, v in pairs(allowed) do
if(v) then
table.insert(liste, o_id)
end
end
-- randomly select one of the possible dialogs
if(#liste > 0) then
go_to_next_dialog = liste[math.random(1, #liste)]
end
end
if(go_to_next_dialog and go_to_next_dialog ~= "") then
-- no actions shall be executed
local o_id = go_to_next_dialog
local effects = active_dialog.d_options[o_id].o_results
local d_option = active_dialog.d_options[o_id]
-- execute all effects/results
local res = yl_speak_up.execute_all_relevant_effects(player, effects, o_id, true, d_option)
local target_dialog = res.next_dialog
yl_speak_up.speak_to[pname].o_id = nil
yl_speak_up.speak_to[pname].a_id = nil
-- end the conversation?
if(target_dialog and target_dialog == "d_end") then
yl_speak_up.stop_talking(pname)
-- a formspec is expected here; provide one that has an exit button only
return "size[2,1]"..
"button_exit[0,0;1,1;Exit;exit]"
end
if(not(target_dialog)
or target_dialog == ""
or not(dialog.n_dialogs[target_dialog])) then
target_dialog = yl_speak_up.speak_to[pname].d_id
end
-- show the new target dialog and exit
-- the recursion_depth will be increased by one (we did autoselect here and need to
-- avoid infinite loops)
return yl_speak_up.get_fs_talkdialog(player,
yl_speak_up.speak_to[pname].n_id, target_dialog, res.alternate_text,
recursion_depth + 1)
end
-- is the player comming back from trying to offer something to the NPC?
-- And is the NPC trying to return the item?
if(d_id == "d_got_item") then
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
if(not(trade_inv:is_empty("npc_wants"))) then
return table.concat({"formspec_version[1]",
yl_speak_up.show_fs_simple_deco(8, 2.5),
"label[0.5,0.5;",
minetest.formspec_escape(dialog.n_npc or "- ? -"),
" does not seem to be intrested in that.\n"..
"Please take your item back and try something else.]"..
"button[3.5,1.5;1.5,1.0;show_player_offers_item;Ok]"
}, "")
end
end
-- no automatic switching happened
return nil
end
yl_speak_up.get_fs_talkdialog_add_player_offers_item = function(pname, formspec, h, dialog, add_text, pname_for_old_fs)
return yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"player_offers_item",
"If you want to give something (items) to this NPC\n"..
"- either because he requested it or as a present -\n"..
"click here. The NPC will return items he doesn't want.",
(add_text or "").."I want to give you something.",
-- show this in edit mode and when the NPC actually accepts items
(add_text or dialog.n_dialogs["d_got_item"]), nil, nil, pname_for_old_fs)
end
-- recursion_depth is increased each time autoanswer is automaticly selected
yl_speak_up.get_fs_talkdialog = function(player, n_id, d_id, alternate_text, recursion_depth)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
local context_d_id = yl_speak_up.speak_to[pname].d_id
local active_dialog
if(not(dialog)) then
yl_speak_up.log_change(pname, n_id,
"unconfigured NPC beeing talked to at "..
minetest.pos_to_string(player:get_pos()), "action")
return yl_speak_up.get_error_message()
end
-- currently no trade running (we're editing options)
yl_speak_up.trade[pname] = nil
yl_speak_up.speak_to[pname].trade_id = nil
-- add a d_trade dialog if necessary
if(dialog and dialog.trades and dialog.n_dialogs and not(dialog.n_dialogs["d_trade"])) then
yl_speak_up.add_new_dialog(dialog, pname, "trade", "[no text]")
end
--[[ If we have an explicit call for a certain d_id, we grab it from parameters.
If not, we grab in from context.
When neither are present, we grab it from d_sort
]]--
local c_d_id
-- the generic start dialog contains only those options that are generic;
-- choose the right start dialog of the NPC
if(d_id ~= nil and d_id ~= "d_generic_start_dialog") then
active_dialog = dialog.n_dialogs[d_id]
c_d_id = d_id
elseif(d_id and d_id ~= "d_generic_start_dialog" and yl_speak_up.speak_to[pname].d_id ~= nil) then
c_d_id = yl_speak_up.speak_to[pname].d_id
active_dialog = dialog.n_dialogs[c_d_id]
-- do this only if the dialog is already configured/created_at:
elseif dialog.n_dialogs ~= nil and dialog.created_at then
-- Find the dialog with d_sort = 0
c_d_id = yl_speak_up.get_start_dialog_id(dialog)
if(c_d_id) then
active_dialog = dialog.n_dialogs[c_d_id]
end
else
-- it may be possible that this player can initialize this npc
yl_speak_up.log_change(pname, n_id,
"unconfigured NPC beeing talked to at "..
minetest.pos_to_string(player:get_pos()), "action")
-- this is the initial config
-- (input ends up at yl_speak_up.input_talk and needs to be rerouted)
return yl_speak_up.get_fs_initial_config(player, n_id, d_id, true)
end
if c_d_id == nil then return yl_speak_up.get_error_message() end
-- show the player a dynamic dialog text:
if(c_d_id == "d_dynamic") then
-- the dialog will be modified for this player only:
-- (pass on all the known parameters in case they're relevant):
yl_speak_up.generate_next_dynamic_dialog(player, n_id, d_id, alternate_text, recursion_depth)
-- just to make sure that the right dialog is loaded:
active_dialog = dialog.n_dialogs[c_d_id]
end
yl_speak_up.speak_to[pname].d_id = c_d_id
-- Now we have a dialog to display to the user
-- do not crash in case of error
if(not(active_dialog)) then
return "size[10,3]"..
"label[0.2,0.5;Ups! Something went wrong. No dialog found.\n"..
"Please talk to the NPC by right-clicking again.]"..
"button_exit[4.5,1.6;1,0.9;exit;Exit]"
end
-- how often has the player visted this dialog?
yl_speak_up.count_visits_to_dialog(pname)
-- evaluate the preconditions of each option and check if the option can be offered
local allowed = yl_speak_up.calculate_displayable_options(pname, active_dialog.d_options,
-- avoid loops by limiting max recoursion depths for autoanswers
(recursion_depth < yl_speak_up.max_allowed_recursion_depth))
-- apply autoanswer, random dialog switching and d_got_item if needed
local show_other_dialog_fs = yl_speak_up.apply_autoanswer_and_random_and_d_got_item(
player, pname, d_id, dialog, allowed, active_dialog, recursion_depth)
if(show_other_dialog_fs) then
return show_other_dialog_fs
end
yl_speak_up.speak_to[pname].allowed = allowed
local pname_for_old_fs = yl_speak_up.get_pname_for_old_fs(pname)
local fs_version = yl_speak_up.fs_version[pname]
local formspec = {}
local h
-- this is used to build a list of all available dialogs for a dropdown menu in edit mode
-- (only relevant in edit mode)
local dialog_list = yl_speak_up.text_new_dialog_id
-- allow to change skin, wielded items etc.
-- display the window with the text the NPC is saying (diffrent in *edit_mode*)
local res_edit_top = yl_speak_up.get_fs_talkdialog_main_text(
pname, formspec, h, dialog, dialog_list, c_d_id, active_dialog,
alternate_text)
-- we are finished with adding buttons and text etc. to the left side of the formspec
local left_window_fs = table.concat(res_edit_top.formspec, "")
dialog_list = res_edit_top.dialog_list
-- find the right index for the dialog_list dropdown above
local d_id_to_dropdown_index = res_edit_top.d_id_to_dropdown_index
-- empty formspec for the bottom part
formspec = {}
h = -0.8
-- allow to delete entries that have no options later on
local anz_options = 0
-- Let's sort the options by o_sort
if active_dialog ~= nil and active_dialog.d_options ~= nil then
local sorted_o_list = yl_speak_up.get_sorted_options(active_dialog.d_options, "o_sort")
for _, sb_v in ipairs(sorted_o_list) do
anz_options = anz_options + 1
end
for i, s_o_id in ipairs(sorted_o_list) do
local sb_v = active_dialog.d_options[s_o_id]
local oid = minetest.formspec_escape(sb_v.o_id)
local res = {}
-- normal mode: show an option if the prerequirements (if any are defined) are met
res = yl_speak_up.get_fs_talkdialog_line(
formspec, h, pname_for_old_fs, oid, sb_v,
dialog, allowed, pname,
-- these additional parameters are needed in edit_mode
active_dialog, dialog_list, d_id_to_dropdown_index, i, #sorted_o_list)
formspec = res.formspec
h = res.h
end
end
-- with automatic selection from the start dialog, it is possible that the
-- real start dialog is never shown; thus, add those buttons which need to
-- be shown just once to all dialogs with is_a_start_dialog set
local is_a_start_dialog = (active_dialog and active_dialog.d_sort
and (tonumber(active_dialog.d_sort) == 0
or active_dialog.is_a_start_dialog))
-- add a "I want to give you something" button to the first dialog if the NPC accepts items
if(is_a_start_dialog) then
h = yl_speak_up.get_fs_talkdialog_add_player_offers_item(pname, formspec, h,
dialog, nil, pname_for_old_fs)
end
-- add a Let's trade button to the first dialog if the NPC has trades
local has_trades = nil
if(is_a_start_dialog and dialog.trades) then
for k, v in pairs(dialog.trades) do
-- has the NPC any *public* trades that are not effects/results?
if(not(v.hide) and not(v.d_id)) then
has_trades = true
break
end
end
end
if(has_trades) then
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"show_trade_list",
"Show a list of trades the NPC has to offer.",
"Let's trade!",
(has_trades), nil, nil, pname_for_old_fs)
end
-- can the player edit this NPC?
local may_edit_npc = yl_speak_up.may_edit_npc(player, n_id)
-- for muting and for checking the owner/order, the luaentity is needed
local obj = yl_speak_up.speak_to[pname].obj
-- some precautions - someone else might have eliminated the NPC in the meantime
local luaentity = nil
if(obj) then
luaentity = obj:get_luaentity()
end
-- If in edit mode, add new menu entries: "add new options", "end edit mode" and what else is needed.
-- Else allow to enter edit mode
-- also covers commands for mobs_npc (walk, stand, follow), custom commands and inventory access
local res = yl_speak_up.get_fs_talkdialog_add_edit_and_command_buttons(
pname, formspec, h, pname_for_old_fs, is_a_start_dialog,
active_dialog, luaentity, may_edit_npc, anz_options)
formspec = res.formspec
h = res.h
-- we are finished with adding buttons to the bottom of the formspec
local bottom_window_fs = table.concat(formspec, "\n")
return yl_speak_up.show_fs_decorated(pname, true, h, alternate_text,
left_window_fs, bottom_window_fs,
active_dialog, h)
end
yl_speak_up.get_fs_talk_wrapper = function(player, param)
if(not(param)) then
param = {}
end
-- recursion depth from autoanswer: 0 (the player selected manually)
return yl_speak_up.get_fs_talkdialog(player, param.n_id, param.d_id, param.alternate_text,0)
end
yl_speak_up.register_fs("talk",
yl_speak_up.input_talk,
yl_speak_up.get_fs_talk_wrapper,
-- no special formspec required:
nil
)

View File

@ -1,85 +1,3 @@
-----------------------------------------------------------------------------
-- limits for trading: maximum and minimum stock to keep
-----------------------------------------------------------------------------
-- sometimes players may not want the NPC to sell *all* of their stock,
-- or not let the NPC buy endless amounts of something when only a limited
-- amount is needed
-----------------------------------------------------------------------------
-- helper function: make sure all necessary entries in the trades table exist
yl_speak_up.setup_trade_limits = function(dialog)
if(not(dialog)) then
dialog = {}
end
if(not(dialog.trades)) then
dialog.trades = {}
end
if(not(dialog.trades.limits)) then
dialog.trades.limits = {}
end
if(not(dialog.trades.limits.sell_if_more)) then
dialog.trades.limits.sell_if_more = {}
end
if(not(dialog.trades.limits.buy_if_less)) then
dialog.trades.limits.buy_if_less = {}
end
return dialog
end
-- helper function: count how many items the NPC has in his inventory
-- empty stacks are counted under the key "";
-- for other items, the amount of items of each type is counted
yl_speak_up.count_npc_inv = function(n_id)
if(not(n_id)) then
return {}
end
-- the NPC's inventory
local npc_inv = minetest.get_inventory({type="detached", name="yl_speak_up_npc_"..tostring(n_id)})
if(not(npc_inv)) then
return {}
end
local anz = npc_inv:get_size('npc_main')
local stored = {}
for i=1, anz do
local stack = npc_inv:get_stack('npc_main', i )
local name = stack:get_name()
local count = stack:get_count()
-- count empty stacks
if(name=="") then
count = 1
end
-- count how much of each item is there
if(not(stored[ name ])) then
stored[ name ] = count
else
stored[ name ] = stored[ name ] + count
end
end
return stored
end
-- helper function: update the items table so that it reflects a limitation
-- items is a table (list) with these entries:
-- [1] 0 in stock;
-- [2] sell if more than 0;
-- [3] buy if less than 10000;
-- [4] item is part of a trade offer
yl_speak_up.insert_trade_item_limitation = function( items, k, i, v )
if( i<1 or i>4) then
return;
end
if( not( items[ k ] )) then
-- 0 in stock; sell if more than 0; buy if less than 10000; item is part of a trade offer
items[ k ] = { 0, 0, 10000, false, #items }
end
items[ k ][ i ] = v
end
-- handle input to the formspec
yl_speak_up.input_trade_limit = function(player, formname, fields)
local pname = player:get_player_name()
@ -158,29 +76,29 @@ yl_speak_up.get_fs_trade_limit = function(player, selected)
-- all items for which limitations might possibly be needed have been collected;
-- now display them
local formspec = {'size[18,12]'..
'button[0.5,11.1;17,0.8;back;Back]'..
'label[7.0,0.5;List of trade limits]'..
local formspec = {'size[18,12]',
'button[0.5,11.1;17,0.8;back;Back]',
'label[7.0,0.5;List of trade limits]',
'label[0.5,1.0;If you do not set any limits, your NPC will buy and sell as many '..
'items as his inventory allows.\n'..
'items as his inventory allows.\n',
'If you set \'Will sell if more than this\', your NPC '..
'will only sell if he will have enough left after the trade,\n'..
'will only sell if he will have enough left after the trade,\n',
'and if you set \'Will buy if less than this\', he will '..
'only buy items as long as he will not end up with more than '..
'this.]'..
'tablecolumns[' ..
'text,align=left;'..
'color;text,align=right;'..
'color;text,align=center;'..
'text,align=right;'..
'color;text,align=center;'..
'text,align=right;'..
'color;text,align=left]'..
'table[0.1,2.3;17.8,8.5;edit_trade_limit;'..
'Description:,'..
'#FFFFFF,NPC has:,'..
'#FFFFFF,Will sell if more than this:,,'..
'#FFFFFF,Will buy if less than this:,,'..
'this.]',
'tablecolumns[',
'text,align=left;',
'color;text,align=right;',
'color;text,align=center;',
'text,align=right;',
'color;text,align=center;',
'text,align=right;',
'color;text,align=left]',
'table[0.1,2.3;17.8,8.5;edit_trade_limit;',
'Description:,',
'#FFFFFF,NPC has:,',
'#FFFFFF,Will sell if more than this:,,',
'#FFFFFF,Will buy if less than this:,,',
'#EEEEEE,Item string:,'
}
@ -237,3 +155,19 @@ yl_speak_up.get_fs_trade_limit = function(player, selected)
table.insert(formspec, ";"..selected_row.."]")
return table.concat(formspec, '')
end
yl_speak_up.get_fs_trade_limit_wrapper = function(player, param)
if(not(param)) then
param = {}
end
return yl_speak_up.get_fs_trade_limit(player, param.selected)
end
yl_speak_up.register_fs("trade_limit",
yl_speak_up.input_trade_limit,
yl_speak_up.get_fs_trade_limit_wrapper,
-- no special formspec required:
nil
)

View File

@ -304,3 +304,11 @@ yl_speak_up.get_fs_trade_list = function(player, show_dialog_option_trades)
return yl_speak_up.show_fs_decorated(pname, nil, h, alternate_text, "",
table.concat(formspec, "\n"), nil, h)
end
yl_speak_up.register_fs("trade_list",
yl_speak_up.input_trade_list,
yl_speak_up.get_fs_trade_list,
-- no special formspec required:
nil
)

View File

@ -12,63 +12,6 @@ yl_speak_up.get_trade_item_desc = function(item)
end
-- helper function; returns how often a trade can be done
-- stock_buy how much of the buy stack does the NPC have in storage?
-- stock_pay how much of the price stack does the NPC have in storage?
-- buy_stack stack containing the item the NPC sells
-- pay_stack stack containing the price for said item
-- min_storage how many items of the buy stack items shall the NPC keep?
-- max_storage how many items of the pay stack items can the NPC accept?
yl_speak_up.get_trade_amount_available = function(stock_buy, stock_pay, buy_stack, pay_stack, min_storage, max_storage)
local stock = 0
-- the NPC shall not sell more than this
if(min_storage and min_storage > 0) then
stock_buy = math.max(0, stock_buy - min_storage)
end
stock = math.floor(stock_buy / buy_stack:get_count())
-- the NPC shall not buy more than this
if(max_storage and max_storage < 10000) then
stock_pay = math.min(max_storage - stock_pay, 10000)
stock = math.min(stock, math.floor(stock_pay / pay_stack:get_count()))
end
return stock
end
-- helper function; also used by fs_trade_list.lua
yl_speak_up.get_sorted_trade_id_list = function(dialog, show_dialog_option_trades)
-- make sure all fields exist
yl_speak_up.setup_trade_limits(dialog)
local keys = {}
if(show_dialog_option_trades) then
for k, v in pairs(dialog.trades) do
if(k ~= "limits" and k ~= "" and v.d_id) then
table.insert(keys, k)
end
end
else
for k, v in pairs(dialog.trades) do
if(k ~= "limits" and k ~= "") then
-- structure of the indices: sell name amount for name amount
local parts = string.split(k, " ")
if(parts and #parts == 6 and parts[4] == "for"
and v.pay and v.pay[1] ~= "" and v.pay[1] == parts[5].." "..parts[6]
and v.buy and v.buy[1] ~= "" and v.buy[1] == parts[2].." "..parts[3]
and minetest.registered_items[parts[5]]
and minetest.registered_items[parts[2]]
and tonumber(parts[6]) > 0
and tonumber(parts[3]) > 0) then
table.insert(keys, k)
end
end
end
end
table.sort(keys)
return keys
end
yl_speak_up.input_trade_via_buy_button = function(player, formname, fields)
local pname = player:get_player_name()
@ -297,8 +240,8 @@ yl_speak_up.get_fs_trade_via_buy_button = function(player, trade_id)
-- the common formspec, shared by actual trade and configuration
-- no listring here as that would make things more complicated
local formspec = { -- "size[8.5,8]"..
yl_speak_up.show_fs_simple_deco(10, 8.8)..
"container[0.75,0]"..
yl_speak_up.show_fs_simple_deco(10, 8.8),
"container[0.75,0]",
"label[4.35,1.4;", npc_name, " sells:]",
"list[current_player;main;0.2,4.55;8,1;]",
"list[current_player;main;0.2,5.78;8,3;8]",
@ -363,7 +306,7 @@ yl_speak_up.get_fs_trade_via_buy_button = function(player, trade_id)
"NPC and want to get back to talking with the NPC.]")
end
-- show edit button for the owner if in edit_mode
-- show edit button for the owner if the player can edit the NPC
if(yl_speak_up.may_edit_npc(player, n_id)) then
-- for trades in trade list: allow delete (new trades can easily be added)
-- allow delete for trades in trade list even if not in edit mode
@ -414,3 +357,21 @@ yl_speak_up.get_fs_trade_via_buy_button = function(player, trade_id)
"tooltip[next_trade;Show next trade offer]")
return table.concat(formspec, '')
end
yl_speak_up.get_fs_trade_via_buy_button_wrapper = function(player, param)
local pname = player:get_player_name()
-- the optional parameter param is the trade_id
if(not(param) and yl_speak_up.speak_to[pname]) then
param = yl_speak_up.speak_to[pname].trade_id
end
return yl_speak_up.get_fs_trade_via_buy_button(player, param)
end
yl_speak_up.register_fs("trade_via_buy_button",
yl_speak_up.input_trade_via_buy_button,
yl_speak_up.get_fs_trade_via_buy_button_wrapper,
-- force formspec version 1:
1
)

View File

@ -1,679 +0,0 @@
-- helper function for yl_speak_up.input_fs_edit_option_related
-- (handle editing of alternate texts that are shown instead of the normal dialog)
yl_speak_up.handle_edit_actions_alternate_text = function(
player, pname, n_id, d_id, o_id, x_id, id_prefix,
formspec_input_to, data, fields, tmp_data_cache)
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog)
or not(dialog.n_dialogs)
or not(dialog.n_dialogs[ d_id ])
or not(dialog.n_dialogs[ d_id ].d_options)
or not(dialog.n_dialogs[ d_id ].d_options[ o_id ])) then
return
end
-- edit_dialog_options: these first two buttons can only be pressed in this dialog
-- action failed: want to edit the text that is shown when switching to the next dialog?
if(fields.button_edit_action_failed_dialog) then
-- the target effect is the (failed) action
local target_action = {}
local actions = dialog.n_dialogs[ d_id ].d_options[ o_id ].actions
if(actions) then
for a_id, a in pairs(actions) do
if(a and a.a_id) then
target_action = a
end
end
end
if(not(target_action)) then
return
end
-- remember what we're working at
yl_speak_up.speak_to[pname].edit_alternate_text_for = target_action
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..formspec_input_to,
formspec = yl_speak_up.get_fs_edit_dialog_modification(
dialog, target_action.a_on_failure, target_action.alternate_text,
"if the action \""..tostring(target_action.a_id)..
"\" of option \""..tostring(o_id)..
"\" of dialog \""..tostring(d_id)..
"\" failed because the player did something wrong")
})
-- showing new formspec - the calling function shall return as well
return true
-- action was successful: want to edit the text that is shown when switching to the next dialog?
elseif(fields.button_edit_action_success_dialog) then
-- the target effect is the "dialog" effect
local target_effect = {}
local results = dialog.n_dialogs[ d_id ].d_options[ o_id ].o_results
if(results) then
for r_id, r in pairs(results) do
if(r and r.r_type and r.r_type == "dialog") then
target_effect = r
end
end
end
if(not(target_effect)) then
return
end
-- remember what we're working at
yl_speak_up.speak_to[pname].edit_alternate_text_for = target_effect
-- this only happens in edit_options_dialog; log it directly
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..formspec_input_to,
formspec = yl_speak_up.get_fs_edit_dialog_modification(
dialog, target_effect.r_value, target_effect.alternate_text,
"if the action "..
"of option \""..tostring(o_id)..
"\" of dialog \""..tostring(d_id)..
"\" was successful - or if there was no action")
})
-- showing new formspec - the calling function shall return as well
return true
-- in edit action dialog: edit alternate text for a failed action
elseif(fields.button_edit_action_on_failure_text_change) then
local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs)
local failure_id = ""
-- action is beeing edited; data.action_failure_dialog points to an index
if(data and data.action_failure_dialog) then
failure_id = sorted_dialog_list[ data.action_failure_dialog ]
end
-- remember what we edit
data.x_id = x_id
data.id_prefix = id_prefix
yl_speak_up.speak_to[pname].edit_alternate_text_for = data
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..formspec_input_to,
formspec = yl_speak_up.get_fs_edit_dialog_modification(
dialog, failure_id, data.alternate_text,
"if the action \""..tostring(x_id)..
"\" of option \""..tostring(o_id)..
"\" of dialog \""..tostring(d_id)..
"\" failed because the player did something wrong")
})
-- showing new formspec - the calling function shall return as well
return true
-- edit alternate text for an on_failure effect
elseif(fields.button_edit_effect_on_failure_text_change) then
-- remember what we edit
data.x_id = x_id
data.id_prefix = id_prefix
yl_speak_up.speak_to[pname].edit_alternate_text_for = data
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..formspec_input_to,
formspec = yl_speak_up.get_fs_edit_dialog_modification(
dialog, data.on_failure, data.alternate_text,
"if the effect \""..tostring(x_id)..
"\" of option \""..tostring(o_id)..
"\" of dialog \""..tostring(d_id)..
"\" failed to execute correctly")
})
-- showing new formspec - the calling function shall return as well
return true
-- edit alternate text for when the player has failed to do the action too many times
elseif(fields.button_edit_limit_action_failed_repeat) then
local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
local timer_data = yl_speak_up.get_variable_metadata( timer_name, "parameter", true)
local alternate_text = yl_speak_up.standard_text_if_action_failed_too_often
if(timer_data and timer_data["alternate_text"]) then
alternate_text = timer_data["alternate_text"]
end
-- remember what we're working at
yl_speak_up.speak_to[pname].edit_alternate_text_for = "timer_on_failure"
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..formspec_input_to,
formspec = yl_speak_up.get_fs_edit_dialog_modification(
dialog, d_id, alternate_text,
"if the player failed to complete the action "..
"\" of option \""..tostring(o_id)..
"\" of dialog \""..tostring(d_id)..
"\" too many times",
true) -- forbid_turn_into_new_dialog
})
-- showing new formspec - the calling function shall return as well
return true
-- edit alternate text whent he player has to wait a bit until he's allowed to repeat the
-- action (to avoid i.e. unlimited quest item handout)
elseif(fields.button_edit_limit_action_success_repeat) then
local timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id)
local timer_data = yl_speak_up.get_variable_metadata( timer_name, "parameter", true)
local alternate_text = yl_speak_up.standard_text_if_action_repeated_too_soon
if(timer_data and timer_data["alternate_text"]) then
alternate_text = timer_data["alternate_text"]
end
-- remember what we're working at
yl_speak_up.speak_to[pname].edit_alternate_text_for = "timer_on_success"
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:"..formspec_input_to,
formspec = yl_speak_up.get_fs_edit_dialog_modification(
dialog, d_id, alternate_text,
"if the player has complete the action "..
"\" of option \""..tostring(o_id)..
"\" of dialog \""..tostring(d_id)..
"\" just not long enough ago",
true) -- forbid_turn_into_new_dialog
})
-- showing new formspec - the calling function shall return as well
return true
-- save the changes
elseif(fields.save_dialog_modification) then
local old_text = "-none-"
local target_element = yl_speak_up.speak_to[pname].edit_alternate_text_for
if(target_element
and (target_element == "timer_on_failure" or target_element == "timer_on_success")) then
-- we're changing a timer (both can be handled the same way here)
local timer_name = target_element.."_"..tostring(d_id).."_"..tostring(o_id)
local timer_data = yl_speak_up.get_variable_metadata( timer_name, "parameter", true)
local alternate_text = yl_speak_up.standard_text_if_action_failed_too_often
if(target_element == "timer_on_success") then
alternate_text = yl_speak_up.standard_text_if_action_repeated_too_soon
end
if(timer_data and timer_data["alternate_text"]) then
alternate_text = timer_data["alternate_text"]
end
-- store the modified alternate text
if(fields.d_text_new and fields.d_text_new ~= ""
and fields.d_text_new ~= alternate_text) then
-- make sure the variable exists
if(yl_speak_up.add_time_based_variable(timer_name)) then
yl_speak_up.set_variable_metadata(timer_name, nil, "parameter",
"alternate_text", fields.d_text_new)
-- log the change
yl_speak_up.log_change(pname, n_id,
"Dialog "..d_id..", option "..tostring(o_id)..
": The text displayed for "..tostring(target_element)..
" was changed from "..
"["..tostring(alternate_text).."] to ["..
tostring(fields.d_text_new).."].")
end
end
elseif(target_element) then
data = target_element
id_prefix = "a_"
if(target_element.r_id) then
id_prefix = "r_"
end
old_text = target_element.alternate_text
else
old_text = data.alternate_text
end
if(data and fields.d_text_new and fields.d_text_new ~= "$TEXT$"
and fields.d_text_new ~= data.alternate_text) then
-- store modification
-- not necessary for edit_option_dialog
if(tmp_data_cache) then
data.alternate_text = fields.d_text_new
yl_speak_up.speak_to[pname][ tmp_data_cache ] = data
else
target_element.alternate_text = fields.d_text_new
end
if(id_prefix == "r_") then
local failure_id = data.on_failure
-- effect is beeing edited; data.on_failure contains the dialog name
if(data and data.on_failure) then
failure_id = data.on_failure
-- edit_option_dialog: data.r_value contains the dialog name
elseif(target_element and target_element.r_value) then
failure_id = target_element.r_value
end
-- record the change
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The text displayed for dialog "..
tostring(failure_id).." when selecting option "..
tostring(o_id).." in dialog "..tostring( d_id )..
" and effect "..tostring(x_id).." failed "..
" was changed from "..
"["..tostring(old_text).."] to ["..tostring(fields.d_text_new).."].")
elseif(id_prefix == "a_") then
local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs)
local failure_id = ""
-- action is beeing edited; data.action_failure_dialog points to an index
if(data and data.action_failure_dialog) then
failure_id = sorted_dialog_list[ data.action_failure_dialog ]
-- edit_option_dialog: data.a_on_failure contains the dialog name
elseif(target_element and target_element.a_on_failure) then
failure_id = target_element.a_on_failure
end
-- record the change
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The text displayed for dialog "..
tostring(failure_id).." when the action "..
tostring(x_id).." of option "..
tostring( o_id ).." in dialog "..tostring( d_id )..
" failed, was changed from "..
"["..tostring(old_text).."] to ["..tostring(fields.d_text_new).."].")
end
-- saved; finished editing
yl_speak_up.speak_to[pname].edit_alternate_text_for = nil
end
-- turn this alternate answer into a new dialog
elseif(fields.turn_alternate_text_into_new_dialog) then
local target_element = yl_speak_up.speak_to[pname].edit_alternate_text_for
if(target_element) then
data = target_element
if(data.id_prefix and data.x_id) then
id_prefix = data.id_prefix
x_id = data.x_id
else
id_prefix = "a_"
x_id = target_element.a_id
if(target_element.r_id) then
id_prefix = "r_"
x_id = target_element.r_id
end
end
end
-- create the new dialog
local new_dialog_id = yl_speak_up.add_new_dialog(dialog, pname, nil)
-- set the text (the previous alternate text)
dialog.n_dialogs[ new_dialog_id ].d_text = data.alternate_text
-- edit option: effect dialog - this is the normal progression from this dialog to the next
if( data.r_id and data.r_type and data.r_type == "dialog") then
data.r_value = new_dialog_id
data.alternate_text = nil
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The alternate text for effect "..tostring(x_id)..
" (dialog) of option "..tostring(o_id).." was turned into the new dialog "..
tostring(new_dialog_id).." (edit option).")
-- edit option: the action failed
elseif(data.a_id and data.a_on_failure) then
data.a_on_failure = new_dialog_id
data.alternate_text = nil
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The alternate text for action "..tostring(data.a_id)..
" of option "..tostring(o_id).." was turned into the new dialog "..
tostring(new_dialog_id).." (edit option).")
-- edit action: the action failed
elseif(data.what and data.what == 6 and data.action_failure_dialog) then
local sorted_dialog_list = yl_speak_up.sort_keys(dialog.n_dialogs)
data.action_failure_dialog = math.max(1,
table.indexof(sorted_dialog_list, new_dialog_id))
data.a_on_failure = new_dialog_id
data.alternate_text = nil
-- make sure its stored correctly
dialog.n_dialogs[d_id].d_options[o_id].actions[x_id].a_on_failure = new_dialog_id
dialog.n_dialogs[d_id].d_options[o_id].actions[x_id].alternate_text = nil
yl_speak_up.speak_to[pname][ tmp_data_cache ] = data
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The alternate text for action "..tostring(x_id)..
" of option "..tostring(o_id).." was turned into the new dialog "..
tostring(new_dialog_id).." (edit action).")
-- edit effect: on_failure - the previous effect failed
elseif(data.what and data.what == 5 and data.on_failure) then
data.on_failure = new_dialog_id
data.alternate_text = nil
-- make sure its stored correctly
dialog.n_dialogs[d_id].d_options[o_id].o_results[x_id].on_failure = new_dialog_id
dialog.n_dialogs[d_id].d_options[o_id].o_results[x_id].alternate_text = nil
yl_speak_up.speak_to[pname][ tmp_data_cache ] = data
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": The alternate text for effect "..tostring(x_id)..
" of option "..tostring(o_id).." was turned into the new dialog "..
tostring(new_dialog_id).." (edit effect).")
end
end
end
yl_speak_up.show_colored_dialog_text = function(dialog, data, d_id, hypertext_pos,
alternate_label_text, postfix, button_name)
if(not(data)) then
return ""
end
-- if(math.random(1,2)==1) then data.alternate_text = "This is an alternate text.\n$TEXT$" end
-- slightly red in order to indicate that this is an on_failure dialog
local color = "776666"
-- ..except for normal redirecting to the next dialog with the dialog effect
-- (slightly yellow there)
if(data.r_id and data.r_type and data.r_type == "dialog") then
color = "777766"
end
local add_info_alternate_text = ""
local text = ""
if(dialog and dialog.n_dialogs and dialog.n_dialogs[ d_id ]) then
text = dialog.n_dialogs[ d_id ].d_text
end
if(d_id == "d_got_item") then
color = "777777"
text = "[This dialog shall only have automatic options. The text is therefore irrelevant.]"
end
if(d_id == "d_end") then
color = "777777"
text = "[The NPC will end this conversation.]"
end
if(not(text)) then
text = "[ERROR: No text!]"
end
if(data and data.alternate_text and data.alternate_text ~= "") then
add_info_alternate_text = alternate_label_text
-- replace $TEXT$ with the normal dialog text and make the new text yellow
text = "<style color=#FFFF00>"..
string.gsub(
data.alternate_text,
"%$TEXT%$",
"<style color=#FFFFFF>"..text.."</style>")..
"</style>"
-- slightly blue in order to indicate that this is a modified text
color = "333366"
end
-- fallback
if(not(text)) then
text = "ERROR: No dialog text found for dialog \""..tostring(d_id).."\"!"
end
-- display the variables in orange
text = yl_speak_up.replace_vars_in_text(text,
-- fake dialog; just adds the colors
-- also MY_NAME..but we can easily replace just one
{ n_npc = "<style color=#FF8800>$NPC_NAME$</style>",
npc_owner = "<style color=#FF8800>$OWNER_NAME$</style>"},
-- pname
"<style color=#FF8800>$PLAYER_NAME$</style>")
local edit_button = ""
-- if there is the possibility that an alternate text may be displayed: allow to edit it
-- and calculate the position of the button from the hypertext_pos position and size
if(button_name and button_name ~= "") then
local parts = string.split(hypertext_pos, ";")
local start = string.split(parts[1], ",")
local size = string.split(parts[2], ",")
edit_button = "button_exit["..
tostring(tonumber(start[1]) + tonumber(size[1]) - 3.5)..","..
tostring(tonumber(start[2]) + tonumber(size[2]) - 0.9)..";"..
"3.0,0.7;"..button_name..";Edit this text]"
end
return add_info_alternate_text..
postfix..
"hypertext["..hypertext_pos..";<global background=#"..color.."><normal>"..
minetest.formspec_escape(text or "?")..
"\n</normal>]"..
-- display the edit button *inside*/on top of the hypertext field
edit_button
end
-- this allows to edit modifications of a dialog that are applied when a given option
-- is choosen - i.e. when the NPC wants to answer some questions - but those answers
-- do not warrant their own dialog
yl_speak_up.get_fs_edit_dialog_modification = function(dialog, d_id, alternate_dialog_text, explanation,
forbid_turn_into_new_dialog)
local nd = "button[9.0,12.3;6,0.7;turn_alternate_text_into_new_dialog;Turn this into a new dialog]"
if(forbid_turn_into_new_dialog) then
nd = ""
end
return "size[20,13.5]"..
"label[6.0,0.5;Edit alternate text]"..
"label[0.2,1.0;The alternate text which you can edit here will be shown instead of "..
"the normal text of the dialog \""..tostring(d_id).."\" - but *only*\n"..
tostring(explanation or "- missing explanation -")..".]"..
"label[0.2,2.3;This is the normal text of dialog \""..
minetest.formspec_escape(tostring(d_id)).."\", shown for reference:]"..
yl_speak_up.show_colored_dialog_text(
dialog,
{r_id = "", r_type = "dialog"},
d_id,
"1.2,2.6;18.0,4.0;d_text_orig",
"", -- no modifications possible at this step
"",
"").. -- no edit button here as this text cannot be changed here
"label[0.2,7.3;Enter the alternate text here. $TEXT$ will be replaced with the normal "..
"dialog text above:]"..
"textarea[1.2,7.6;18.0,4.5;d_text_new;;"..
minetest.formspec_escape(alternate_dialog_text or "$TEXT$").."]"..
"button[3.0,12.3;1,0.7;back_from_edit_dialog_modification;Abort]"..
"button[6.0,12.3;1,0.7;save_dialog_modification;Save]"..
nd
end
-- show which dialogs point/lead to this_dialog
yl_speak_up.show_what_points_to_this_dialog = function(player, this_dialog)
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog)
or not(dialog.n_dialogs)
or not(this_dialog)
or not(dialog.n_dialogs[ this_dialog ])) then
return ""
end
-- only show this information when editing this npc
if(yl_speak_up.edit_mode[pname] ~= yl_speak_up.speak_to[pname].n_id) then
return ""
end
local found = {}
-- colored lines for the table showing the results
local res = {}
-- iterate over all dialogs
for d_id, d in pairs(dialog.n_dialogs) do
-- the dialog itself may have options that point back to the dialog itself
if(d.d_options) then
-- iterate over all options
for o_id, o in pairs(d.d_options) do
local r_text = ""
local p_text = ""
local alternate_dialog = nil
local alternate_text = nil
-- preconditions are not relevant;
-- effects are (dialog and on_failure)
if(o.o_results) then
for r_id, r in pairs(o.o_results) do
if(r and r.r_type and r.r_type == "dialog"
and r.r_value == this_dialog) then
r_text = r_text..yl_speak_up.print_as_table_effect(
r, pname)
table.insert(found, {d_id, o_id, r_id,
"option was selected"})
alternate_dialog = r.r_value
if(r.alternate_text) then
alternate_text =
"Show alternate text: "..
tostring(r.alternate_text)
end
elseif(r and r.r_type and r.r_type == "on_failure"
and r.r_value == this_dialog) then
r_text = r_text..yl_speak_up.print_as_table_effect(
r, pname)
table.insert(found, {d_id, o_id, r_id,
"the previous effect failed"})
alternate_dialog = r.r_value
alternate_text = "The previous effect failed. "
if(r.alternate_text) then
alternate_text = alternate_text..
"Show alternate text: "..
tostring(r.alternate_text)
else
alternate_text = alternate_text..
"Show this dialog here."
end
end
end
end
-- actions may be relevant
if(o.actions) then
for a_id, a in pairs(o.actions) do
if(a and a.a_on_failure
and a.a_on_failure == this_dialog) then
p_text = p_text..yl_speak_up.print_as_table_action(
a, pname)
table.insert(found, {d_id, o_id, a_id,
"action failed"})
alternate_dialog = a.a_on_failure
alternate_text = "The action failed. "
if(a.alternate_text) then
alternate_text = alternate_text..
"Show this alternate text: "..
tostring(a.alternate_text)
else
alternate_text = alternate_text..
"Show this dialog here."
end
end
end
end
yl_speak_up.print_as_table_dialog(p_text, r_text, dialog,
dialog.n_id, d_id, o_id,
-- sort value: formed by dialog and option id (not perfect but
-- good enough)
res, o, tostring(d_id).." "..tostring(o_id),
alternate_dialog, alternate_text)
end
end
end
local d_id = this_dialog
local formspec = yl_speak_up.print_as_table_prepare_formspec(res, "table_of_dialog_uses",
"back_from_show_what_points_here", "Back to dialog \""..tostring(d_id).."\"")
table.insert(formspec,
"label[20.0,1.8;Dialog \""..minetest.formspec_escape(this_dialog)..
"\" is referenced here:]")
if(#found ~= 1) then
table.insert(formspec,
"label[16.0,31.0;"..
minetest.formspec_escape("This dialog \""..tostring(d_id)..
"\" can be reached from "..
minetest.colorize("#FFFF00", tostring(#found))..
" options/actions/results.").."]")
else
table.insert(formspec,
"button[0.2,30.6;56.6,1.2;turn_dialog_into_alternate_text;"..
minetest.formspec_escape("Turn this dialog \""..
tostring(d_id)).."\" into an alternate text.]")
end
return table.concat(formspec, "\n")
end
yl_speak_up.input_fs_show_what_points_to_this_dialog = function(player, formname, fields)
local pname = player:get_player_name()
if(fields.back_from_show_what_points_here
or not(fields.turn_dialog_into_alternate_text)) then
-- back to the dialog
yl_speak_up.show_fs(player, "talk",
{n_id = yl_speak_up.speak_to[pname].n_id,
d_id = yl_speak_up.speak_to[pname].d_id})
return
end
-- fields.turn_dialog_into_alternate_text is set
local n_id = yl_speak_up.speak_to[pname].n_id
local this_dialog = yl_speak_up.speak_to[pname].d_id
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog)
or not(dialog.n_dialogs)
or not(this_dialog)
or not(dialog.n_dialogs[ this_dialog ])) then
return
end
-- only show this information when editing this npc
if(yl_speak_up.edit_mode[pname] ~= yl_speak_up.speak_to[pname].n_id) then
return
end
-- find out what needs to be turned into an alternate dialog
local found = {}
-- how many options does the current dialog have?
local count_options = 0
-- iterate over all dialogs
for d_id, d in pairs(dialog.n_dialogs) do
-- the dialog itself may have options that point back to the dialog itself
if(d.d_options) then
-- iterate over all options
for o_id, o in pairs(d.d_options) do
-- this is only possible if there are no options for this dialog
if(d_id == this_dialog) then
count_options = count_options + 1
end
-- preconditions are not relevant;
-- effects are (dialog and on_failure)
if(o.o_results) then
for r_id, r in pairs(o.o_results) do
if(r and r.r_type and r.r_type == "dialog"
and r.r_value == this_dialog) then
table.insert(found, {d_id=d_id, o_id=o_id, id=r_id,
element=r, text="option was selected"})
elseif(r and r.r_type and r.r_type == "on_failure"
and r.r_value == this_dialog) then
table.insert(found, {d_id=d_id, o_id=o_id, id=r_id,
element=r, text="the previous effect failed"})
end
end
end
-- actions may be relevant
if(o.actions) then
for a_id, a in pairs(o.actions) do
if(a and a.a_on_failure
and a.a_on_failure == this_dialog) then
table.insert(found, {d_id=d_id, o_id=o_id, id=a_id,
element=a, text="action failed"})
end
end
end
end
end
end
local error_msg = ""
if(count_options > 0) then
error_msg = "This dialog still has dialog options.\nConversion not possible."
elseif(#found < 1) then
error_msg = "Found no option, action or effect\nthat points to this dialog."
elseif(#found > 1) then
error_msg = "Found more than one option, action\nor effect that points to this dialog."
end
if(error_msg ~= "") then
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:show_what_points_to_this_dialog",
formspec = "size[8,2]"..
"label[0.2,0.5;Error: "..error_msg.."]"..
"button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
return
end
-- all fine so far; this is the text we want to set as alternate text
local d_text = dialog.n_dialogs[ this_dialog ].d_text
local f = found[1]
-- are we dealing with a result/effect or an action?
t = "o_results"
if(f.element.a_id) then
t = "actions"
end
-- there may already be an alternate text stored there
local alternate_text = dialog.n_dialogs[ f.d_id ].d_options[ f.o_id ][ t ][ f.id ].alternate_text
if(not(alternate_text)) then
-- no old text found
alternate_text = d_text
else
-- the old text may reference this d_text
alternate_text = string.gsub(alternate_text, "%$TEXT%$", d_text)
end
-- log the change
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..tostring(this_dialog)..": Deleted this dialog and turned it into an "..
"alternate text for dialog \""..tostring(f.d_id).."\" option \""..tostring(f.o_id)..
"\" element \""..tostring(f.id).."\".")
-- actually set the new alternate_text
dialog.n_dialogs[ f.d_id ].d_options[ f.o_id ][ t ][ f.id ].alternate_text = alternate_text
-- delete this dialog
dialog.n_dialogs[ this_dialog ] = nil
-- we need to show a new/diffrent dialog to the player now - because the old one was deleted
yl_speak_up.speak_to[pname].d_id = f.d_id
yl_speak_up.speak_to[pname].o_id = f.o_id
if(t == "o_results") then
-- we can't really know where this ought to point to - the old dialog is gone, so
-- let's point to the current dialog to avoid errors
dialog.n_dialogs[ f.d_id ].d_options[ f.o_id ][ t ][ f.id ].r_value = f.d_id
-- dialog - normal switching to the next dialog or on_failure - the previous effect failed:
yl_speak_up.show_fs(player, "edit_effects", f.id)
else
-- the player may have to change this manually; we really can't know what would fit
-- (but the old dialog is gone)
dialog.n_dialogs[ f.d_id ].d_options[ f.o_id ][ t ][ f.id ].a_on_failure = f.d_id
-- an action failed:
yl_speak_up.show_fs(player, "edit_actions", f.id)
end
end

View File

@ -1,143 +0,0 @@
-- assign a quest step to a dialog option/answe
-- This is the formspec where this is handled.
yl_speak_up.input_fs_assign_quest_step = function(player, formname, fields)
if(not(player)) then
return ""
end
local pname = player:get_player_name()
-- what are we talking about?
local n_id = yl_speak_up.speak_to[pname].n_id
local d_id = yl_speak_up.speak_to[pname].d_id
local o_id = yl_speak_up.speak_to[pname].o_id
if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then
return
end
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog) or not(dialog.n_dialogs)
or not(d_id) or not(o_id)
or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_options)
or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return
end
-- go back to edit options field
if((fields and fields.quit)
or (fields and fields.back and fields.back ~= "")) then
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id})
return
elseif(fields and fields.back_from_error_msg) then
yl_speak_up.show_fs(player, "assign_quest_step", nil)
return
-- show manage quests formspec
elseif(fields and fields.manage_quests) then
-- store information so that the back button can work
yl_speak_up.speak_to[pname][ "working_at" ] = "assign_quest_step"
yl_speak_up.show_fs(player, "manage_quests", nil)
return
elseif(fields.delete and fields.delete ~= "") then
dialog.n_dialogs[d_id].d_options[o_id].quest_id = nil
dialog.n_dialogs[d_id].d_options[o_id].quest_step = nil
if(not(yl_speak_up.npc_was_changed[ n_id ])) then
yl_speak_up.npc_was_changed[ n_id ] = {}
end
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Option "..tostring(o_id)..
" no longer provides quest step \""..
tostring(fields.quest_step).."\" for quest \""..tostring(fields.quest_id).."\".")
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id})
return
elseif(not(fields) or not(fields.save)) then
return
end
-- actually store the data
local error_msg = ""
-- check if the quest exists
local quest_list = yl_speak_up.get_sorted_quest_list(pname)
local idx = table.indexof(quest_list, fields.quest_id or "")
if(not(fields.quest_id) or fields.quest_id == "" or idx < 1) then
error_msg = "Quest not found."
elseif(not(fields.quest_step)
or string.len(fields.quest_step) < 1
or string.len(fields.quest_step) > 80) then
error_msg = "The name of the quest step has to be between\n"..
"1 and 80 characters long."
end
if(error_msg ~= "") then
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:assign_quest_step",
formspec = "size[9,2]"..
"label[0.2,0.5;Error: "..minetest.formspec_escape(error_msg).."]"..
"button[1.5,1.5;2,0.9;back_from_error_msg;Back]"})
return
end
dialog.n_dialogs[d_id].d_options[o_id].quest_id = fields.quest_id
dialog.n_dialogs[d_id].d_options[o_id].quest_step = fields.quest_step
if(not(yl_speak_up.npc_was_changed[ n_id ])) then
yl_speak_up.npc_was_changed[ n_id ] = {}
end
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Option "..tostring(o_id)..
" has been set as quest step \""..
tostring(fields.quest_step).."\" for quest \""..tostring(fields.quest_id).."\".")
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id})
end
yl_speak_up.get_fs_assign_quest_step = function(player, param)
if(not(player)) then
return ""
end
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
local d_id = yl_speak_up.speak_to[pname].d_id
local o_id = yl_speak_up.speak_to[pname].o_id
-- this only works in edit mode
if(not(n_id) or yl_speak_up.edit_mode[pname] ~= n_id) then
return "size[1,1]label[0,0;You cannot edit this NPC.]"
end
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog) or not(dialog.n_dialogs)
or not(d_id) or not(o_id)
or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_options)
or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return "size[4,1]label[0,0;Dialog option does not exist.]"
end
local d_option = dialog.n_dialogs[d_id].d_options[o_id]
local quest_id = d_option.quest_id or ""
local quest_step = d_option.quest_step or ""
local selected = 1
local quest_list = yl_speak_up.get_sorted_quest_list(pname)
for i, v in ipairs(quest_list) do
quest_list[i] = minetest.formspec_escape(v)
if(quest_id and v == quest_id) then
selected = i
end
end
local delete_button = ""
if(quest_step and quest_step ~= "" and quest_id and quest_id ~= "") then
delete_button = "button[4.0,3.0;2.0,0.8;delete;Delete]"
end
return "size[14,4]"..
"label[4.0,0.5;Using this option/answer shall be a quest step.]"..
"label[0.2,1.4;Select a quest:]"..
"dropdown[4.0,1.0;9.5,0.8;quest_id;"..
table.concat(quest_list, ',')..";"..
tostring(selected)..",]"..
"label[0.2,2.4;Name of the quest step:]"..
"field[4.0,2.0;9.5,0.8;quest_step;;"..
minetest.formspec_escape(quest_step).."]"..
"button[0.2,3.0;3.6,0.8;manage_quests;Manage Quests]"..
delete_button..
"button[6.5,3.0;2.0,0.8;save;Save]"..
"button[9.0,3.0;2.0,0.8;back;Abort]"..
"button[11.5,3.0;2.0,0.8;back;Back]"
end

View File

@ -1,125 +0,0 @@
-- This file contains what is necessary to add/edit an action.
--
-- Which diffrent types of actions are available?
-- -> The following fields are part of an action:
-- a_id the ID/key of the action
-- a_type selected from values_what
-- a_value used to store the subtype of a_type
--
-- no action (none): nothing to do.
--
-- a trade ("trade"):
-- a_buy what the NPC sells (itemstack)
-- a_pay what the NPC wants as payment (itemstack)
--
-- giving and taking of items ("npc_gives" and "npc_wants"):
-- a_on_failure if the action fails, go to this dialog
-- a_value itemstack of the given/wanted item in string form
-- a_item_desc the description the NPC shall set for that itemstack
-- (so that the player can distinguish it from other
-- itemstacks with the same items)
-- a_item_quest_id Special ID to make sure that it is really the *right*
-- item and not just something the player faked with an
-- engraving table or something similar
--
-- the player has to enter text ("text_input"):
-- a_value the expected answer the player has to enter
-- a_question the question the NPC shall ask the player (so that the
-- player can know which answer is expected here)
--
-- Show something custom (has to be provided by the server). (=call a function) ("evaluate"):
-- a_value the name of the function that is to be called
-- a_param1 the first paramter (optional; depends on function)
-- ..
-- a_param9 the 9th parameter (optional; depends on function)
--
-- a general, more complex formspec-basted puzzle ("puzzle"): not supported
-- (custom may be more helpful)
--
--
-- Note: Trades are not stored as actions - they are stored in
-- dialog.trades[ trade_id ] with <trade_id> == "<d_id> <o_id>"
--
-- some helper lists for creating the formspecs and evaulating
-- the player's answers:
-- general direction of what could make up an action
local check_what = {
"- please select -",
"No action (default).", -- 2
"Normal trade - one item(stack) for another item(stack).", -- 3
"The NPC gives something to the player (i.e. a quest item).", -- 4
"The player is expected to give something to the NPC (i.e. a quest item).", -- 5
"The player has to manually enter a password or passphrase or some other text.", -- 6
"Show something custom (has to be provided by the server).",
-- "The player has to move virtual items in a virtual inventory to the right position.", -- 8
}
-- how to store these as a_type in the action:
local values_what = {"", "none", "trade", "npc_gives", "npc_wants", "text_input", "evaluate", "puzzle"}
-- returns a human-readable text as description of the action
-- (as shown in the edit options dialog and in the edit effect formspec)
yl_speak_up.show_action = function(a)
if(not(a.a_type) or a.a_type == "" or a.a_type == "none") then
return "(nothing): Nothing to do. No action."
elseif(a.a_type == "trade") then
return "NPC sells \""..table.concat(a.a_buy, ";").."\" for \""..
table.concat(a.a_pay, ";").."\"."
elseif(a.a_type == "npc_gives") then
return "The NPC gives \""..tostring(a.a_item_desc or "- default description -")..
"\" (\""..tostring(a.a_value or "- ? -").."\") "..
"with ID \""..tostring(a.a_item_quest_id or "- no special ID -").."\"."
elseif(a.a_type == "npc_wants") then
return "The NPC wants \""..tostring(a.a_item_desc or "- default description -")..
"\" (\""..tostring(a.a_value or "- ? -").."\") "..
"with ID \""..tostring(a.a_item_quest_id or "- no special ID -").."\"."
elseif(a.a_type == "text_input") then
return "Q: \""..tostring(a.a_question).."\" A:\""..tostring(a.a_value).."\"."
elseif(a.a_type == "evaluate") then
local str = ""
for i = 1, 9 do
str = str..tostring(a["a_param"..tostring(i)])
if(i < 9) then
str = str..","
end
end
return "FUNCTION["..tostring(a.a_value).."]("..str..")"
-- puzzle is unused; better do that via custom
-- elseif(a.a_type == "puzzle") then
-- return "puzzle:"
end
-- fallback
return tostring(a.a_value)
end
-- these are only wrapper functions for those in fs_edit_general.lua
yl_speak_up.input_fs_edit_actions = function(player, formname, fields)
return yl_speak_up.input_fs_edit_option_related(player, formname, fields,
"a_", "actions", yl_speak_up.max_actions,
"(A)ctions", "tmp_action",
nil, -- unused - no block operations
values_what, {}, {}, {}, {},
check_what, {}, {}, {}, {},
nil, -- no variables
"edit_actions"
)
end
yl_speak_up.get_fs_edit_actions = function(player, table_click_result)
return yl_speak_up.get_fs_edit_option_related(player, table_click_result,
"a_", "actions", yl_speak_up.max_actions,
"(A)ctions", "tmp_action",
"What do you want to happen in this (A)ction?",
values_what, {}, {}, {}, {},
check_what, {}, {}, {}, {},
nil, -- no variables
yl_speak_up.show_action,
"table_of_elements",
nil, nil, nil, -- no variable handling here
nil -- nothing block-related to do here
)
end

View File

@ -1,344 +0,0 @@
-- This file contains what is necessary to add/edit an effect.
--
-- Which diffrent types of effects are available?
-- -> The following fields are part of an effect/result:
-- r_id the ID/key of the effect/result
-- r_type selected from values_what; the staffs allow to use other
-- types like "function" or "give_item" etc. - but that is not
-- supported here (cannot be edited or created; only be shown)
-- r_value used to store the subtype of r_type
--
-- a state/variable ("state"):
-- r_variable name of a variable the player has *write* access to;
-- dropdown list with allowed options
-- r_operator selected from values_operator
-- r_var_cmp_value can be set freely by the player (the variable will be
-- set to this value)
--
-- the value of a property of the NPC (for generic NPC) ("property"):
-- r_value name of the property that is to be changed
-- r_operator how shall the property be changed?
-- r_var_cmp_value the new value (or increment/decrement) for this property
--
-- something that has to be calculated or evaluated (=call a function) ("evaluate"):
-- r_value the name of the function that is to be called
-- r_param1 the first paramter (optional; depends on function)
-- ..
-- r_param9 the 9th parameter (optional; depends on function)
--
-- a block in the world ("block"):
-- r_pos a position in the world; determined by asking the player
-- to punch the block
-- r_node (follows from r_pos)
-- r_param2 (follows from r_pos)
--
-- place an item into the inventory of a block (i.e. a chest; "put_into_block_inv"):
-- r_pos the position of the target block
-- r_inv_list_name the inventory list where the item shall be moved to (often "main")
-- r_itemstack the itemstack that is to be moved
--
-- take item out of the inventory of a block (i.e. a chest; "take_from_block_inv");
-- same as "put_into_block_inv"
--
-- accept items the player has given to the NPC ("deal_with_offered_item"):
-- r_value subtype; one of yl_speak_up.dropdown_values_deal_with_offered_item
--
-- a craft receipe ("craft"):
-- r_value the expected craft result
-- r_craft_grid array containing the stacks in the 9 craft grid fields in string form
--
-- on_failure ("on_failure"):
-- r_value alternate target dialog if the previous *effect* failed
--
-- chat_all ("chat_all"):
-- r_value chat message sent to all players
--
--
-- give item to player ("give_item"): requires yl_speak_up.npc_privs_priv priv
-- r_value the itemstack that shall be added to the player's inventory
--
-- take item from player's inventory ("take_item"): requires yl_speak_up.npc_privs_priv priv
-- r_value the itemstack that will be removed from the player's inventory
--
-- move the player to a position ("move"): requires yl_speak_up.npc_privs_priv priv
-- r_value the position where the player shall be moved to
--
-- execute lua code ("function"): requires npc_master priv
-- r_value the lua code that shall be executed
--
-- Unlike in preconditions, trade (the trade action already happened) and
-- inventory actions are not supported as effects.
--
-- some helper lists for creating the formspecs and evaulating
-- the player's answers:
-- general direction of what could make up an effect
local check_what = {
"- please select -",
"an internal state (i.e. of a quest)", -- 2
"the value of a property of the NPC (for generic NPC)", -- property
"something that has to be calculated or evaluated (=call a function)", -- evaluate
"a block somewhere", -- 3
"put item from the NPC's inventory into a chest etc.", -- 4
"take item from a chest etc. and put it into the NPC's inventory",
-- 5
"an item the player offered to the NPC",
"NPC crafts something", -- 6
"go to other dialog if the previous effect failed", -- 7
"send a chat message to all players", -- 8
"give item (created out of thin air) to player (requires "..
tostring(yl_speak_up.npc_privs_priv).." priv)", -- 9
"take item from player and destroy it (requires "..
tostring(yl_speak_up.npc_privs_priv).." priv)", -- 10
"move the player to a given position (requires "..
tostring(yl_speak_up.npc_privs_priv).." priv)", -- 11
"execute Lua code (requires npc_master priv)", -- 12
}
-- how to store these as r_type in the precondition:
local values_what = {"", "state",
"property", "evaluate", "block",
-- interact with the inventory of blocks on the map
"put_into_block_inv", "take_from_block_inv",
-- the player gave an item to the NPC; now deal with it somehow
"deal_with_offered_item",
-- crafting, handling failure, send chat message to all
"craft", "on_failure", "chat_all",
-- the following require the yl_speak_up.npc_privs_priv priv:
"give_item", "take_item", "move",
-- the following require the npc_master priv:
"function",
}
-- unlike in the preconditions, the "I cannot punch it" option is
-- not offered here - because the player (and later the NPC) needs
-- to be able to build at this position
local check_block = {
"- please select -", -- 1
"If there is air: Place a block so that it looks like now.", -- 2
"If there is a block: Dig it.", -- 3
"Punch the block.", -- 4
"Right-click the block.", -- 5
}
-- how to store these as p_value (the actual node data gets stored as p_node, p_param2 and p_pos):
local values_block = {"", "place", "dig", "punch", "right-click"}
-- comparison operators for variables
local check_operator = {
"- please select -", -- 1
"new value:", -- 2
"discard/unset/forget", -- 3
"current time", -- 4
"quest step completed:", -- 5
minetest.formspec_escape("max(current, new_value)"), -- 6
minetest.formspec_escape("min(current, new_value)"), -- 7
"increment by:", -- 8
"decrement by:", -- 9
}
-- how to store these as r_value (the actual variable is stored in r_variable, and the value in r_new_value):
local values_operator = {"", "set_to", "unset", "set_to_current_time",
"quest_step", "maximum", "minimum", "increment", "decrement"}
-- get the list of variables the player has *write* access to
yl_speak_up.get_sorted_player_var_list_write_access = function(pname)
local var_list = {}
-- some values - like hour of day or HP of the player - can be read in
-- a precondition but not be modified
-- get the list of variables the player can *write*
local tmp = yl_speak_up.get_quest_variables_with_write_access(pname)
-- sort that list (the dropdown formspec element returns just an index)
table.sort(tmp)
for i, v in ipairs(tmp) do
table.insert(var_list, v)
end
return var_list
end
-- helper function for yl_speak_up.show_effect
-- used by "state" and "property"
yl_speak_up.show_effect_with_operator = function(r, var_name)
if(not(r.r_operator)) then
return "Error: Operator not defined."
elseif(r.r_operator == "set_to") then
return "set "..var_name.." to value \""..
tostring(r.r_var_cmp_value).."\""
elseif(r.r_operator == "unset") then
return "discard "..var_name.." (unset)"
elseif(r.r_operator == "set_to_current_time") then
return "set "..var_name.." to the current time"
elseif(r.r_operator == "quest_step") then
return "store that the player has completed quest step \""..
tostring(r.r_var_cmp_value).."\""
elseif(r.r_operator == "maximum") then
return "set "..var_name.." to value \""..
tostring(r.r_var_cmp_value).."\" if its current value is larger than that"
elseif(r.r_operator == "minimum") then
return "set "..var_name.." to value \""..
tostring(r.r_var_cmp_value).."\" if its current value is lower than that"
elseif(r.r_operator == "increment") then
return "increment the value of "..var_name.." by \""..
tostring(r.r_var_cmp_value).."\""
elseif(r.r_operator == "decrement") then
return "decrement the value of "..var_name.." by \""..
tostring(r.r_var_cmp_value).."\""
else
return "ERROR: Wrong operator \""..tostring(r.r_operator).."\" for "..var_name
end
end
-- returns a human-readable text as description of the effects
-- (as shown in the edit options dialog and in the edit effect formspec)
yl_speak_up.show_effect = function(r, pname)
if(not(r.r_type) or r.r_type == "") then
return "(nothing): Nothing to do. No effect."
elseif(r.r_type == "give_item") then
return "give_item: Add \""..tostring(r.r_value).."\" to the player's inventory."
elseif(r.r_type == "take_item") then
return "take_item: Take \""..tostring(r.r_value).."\" from the player's inventory."
elseif(r.r_type == "move") then
return "move: Move the player to "..tostring(r.r_value).."."
elseif(r.r_type == "function") then
return "function: execute \""..tostring(r.r_value).."\"."
elseif(r.r_type == "trade") then
return "trade: obsolete (now defined as an action)"
elseif(r.r_type == "dialog") then
return "Switch to dialog \""..tostring(r.r_value).."\"."
elseif(r.r_type == "state") then
local var_name = "VARIABLE[ - ? - ]"
if(r.r_variable) then
var_name = "VARIABLE[ "..tostring(
yl_speak_up.strip_pname_from_var(r.r_variable, pname)).." ]"
end
return yl_speak_up.show_effect_with_operator(r, var_name)
-- the value of a property of the NPC (for generic NPC) ("property"):
elseif(r.r_type == "property") then
local var_name = "PROPERTY[ "..tostring(r.r_value or "- ? -").." ]"
return yl_speak_up.show_effect_with_operator(r, var_name)
-- something that has to be calculated or evaluated (=call a function) ("evaluate"):
elseif(r.r_type == "evaluate") then
local str = ""
for i = 1, 9 do
str = str..tostring(r["r_param"..tostring(i)])
if(i < 9) then
str = str..","
end
end
return "FUNCTION["..tostring(r.r_value).."]("..str..")"
elseif(r.r_type == "block") then
if(not(r.r_pos) or type(r.r_pos) ~= "table"
or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then
return "ERROR: r.r_pos is "..minetest.serialize(r.r_pos)
-- we don't check here yet which node is actually there - that will be done upon execution
elseif(yl_speak_up.check_blacklisted(r.r_value, r.r_node, r.r_node)) then
return "ERROR: Blocks of type \""..tostring(r.r_node).."\" do not allow "..
"interaction of type \""..tostring(r.r_value).."\" for NPC."
elseif(r.r_value == "place") then
return "Place \""..tostring(r.r_node).."\" with param2: "..tostring(r.r_param2)..
" at "..minetest.pos_to_string(r.r_pos).."."
elseif(r.r_value == "dig") then
return "Dig the block at "..minetest.pos_to_string(r.r_pos).."."
elseif(r.r_value == "punch") then
return "Punch the block at "..minetest.pos_to_string(r.r_pos).."."
elseif(r.r_value == "right-click") then
return "Right-click the block at "..minetest.pos_to_string(r.r_pos).."."
else
return "ERROR: Don't know what to do with the block at "..
minetest.pos_to_string(r.r_pos)..": \""..tostring(r.r_value).."\"?"
end
elseif(r.r_type == "craft") then
-- this is only shown in the edit options menu and when editing an effect;
-- we can afford a bit of calculation here (it's not a precondtion...)
if(not(r.r_value) or not(r.r_craft_grid)) then
return "ERROR: Crafting not configured correctly."
end
local craft_str = "Craft \""..tostring(r.r_value).."\" from "..
table.concat(r.r_craft_grid, ", ").."."
-- check here if the craft receipe is broken
local input = {}
input.items = {}
for i, v in ipairs(r.r_craft_grid) do
input.items[ i ] = ItemStack(v or "")
end
input.method = "normal" -- normal crafting; no cooking or fuel or the like
input.width = 3
local output, decremented_input = minetest.get_craft_result(input)
if(output.item:is_empty()) then
return "Error: Recipe changed! No output for "..craft_str
end
-- the craft receipe may have changed in the meantime and yield a diffrent result
local expected_stack = ItemStack(r.r_value)
if(output.item:get_name() ~= expected_stack:get_name()
or output.item:get_count() ~= expected_stack:get_count()) then
return "Error: Amount of output changed! "..craft_str
end
return craft_str
elseif(r.r_type == "on_failure") then
return "If the *previous* effect failed, go to dialog \""..tostring(r.r_value).. "\"."
elseif(r.r_type == "chat_all") then
return "Send chat message: \""..tostring(r.r_value).."\""
elseif(r.r_type == "put_into_block_inv") then
if(not(r.r_pos) or type(r.r_pos) ~= "table"
or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then
return "ERROR: r.r_pos is "..minetest.serialize(r.r_pos)
end
return "Put item \""..tostring(r.r_itemstack).."\" from NPC inv into block at "..
minetest.pos_to_string(r.r_pos)..
" in inventory list \""..tostring(r.r_inv_list_name).."\"."
elseif(r.r_type == "take_from_block_inv") then
if(not(r.r_pos) or type(r.r_pos) ~= "table"
or not(r.r_pos.x) or not(r.r_pos.y) or not(r.r_pos.z)) then
return "ERROR: r.r_pos is "..minetest.serialize(r.r_pos)
end
return "Take item \""..tostring(r.r_itemstack).."\" from block at "..
minetest.pos_to_string(r.r_pos)..
" out of inventory list \""..tostring(r.r_inv_list_name)..
"\" and put it into the NPC's inventory."
elseif(r.r_type == "deal_with_offered_item") then
local nr = 1
if(r.r_value) then
nr = math.max(1, table.indexof(yl_speak_up.dropdown_values_deal_with_offered_item,
r.r_value))
return yl_speak_up.dropdown_list_deal_with_offered_item[ nr ]
end
return "ERROR: Missing subtype r.r_value: \""..tostring(r.r_value).."\""
end
-- fallback
return tostring(r.r_value)
end
-- these are only wrapper functions for those in fs_edit_general.lua
yl_speak_up.input_fs_edit_effects = function(player, formname, fields)
return yl_speak_up.input_fs_edit_option_related(player, formname, fields,
"r_", "o_results", yl_speak_up.max_result_effects,
"(Ef)fect", "tmp_result",
"Please punch the block you want to manipulate in your effect!",
values_what, values_operator, values_block, {}, {},
check_what, check_operator, check_block, {}, {},
-- player variables with write access
yl_speak_up.get_sorted_player_var_list_write_access,
"edit_effects"
)
end
yl_speak_up.get_fs_edit_effects = function(player, table_click_result)
return yl_speak_up.get_fs_edit_option_related(player, table_click_result,
"r_", "o_results", yl_speak_up.max_result_effects,
"(Ef)fect", "tmp_result",
"What do you want to change with this effect?",
values_what, values_operator, values_block, {}, {},
check_what, check_operator, check_block, {}, {},
-- player variables with write access
yl_speak_up.get_sorted_player_var_list_write_access,
yl_speak_up.show_effect,
"table_of_elements",
"Change the value of the following variable:", "Set variable to:", "New value:",
"The NPC shall do something to the block at the following position:"
)
end

File diff suppressed because it is too large Load Diff

View File

@ -1,766 +0,0 @@
-- helper function; used by
-- * yl_speak_up.get_fs_edit_option_dialog and
-- * yl_speak_up.get_fs_edit_trade_limit
yl_speak_up.get_list_of_effects_and_target_dialog_and_effect = function(dialog, results, pname, target_dialog, target_effect)
local list_of_effects = ""
local count_effects = 0
if(results) then
local sorted_key_list = yl_speak_up.sort_keys(results)
for i, k in ipairs(sorted_key_list) do
local v = results[ k ]
if v.r_type == "dialog" and (dialog.n_dialogs[v.r_value] ~= nil or v.r_value == "d_end" or v.r_value == "d_got_item") then
list_of_effects = list_of_effects..
minetest.formspec_escape(v.r_id)..",#999999,"..
minetest.formspec_escape(v.r_type)..","..
minetest.formspec_escape(
yl_speak_up.show_effect(v, pname))..","
-- there may be more than one in the data structure
target_dialog = v.r_value
target_effect = v
elseif v.r_type ~= "dialog" then
list_of_effects = list_of_effects..
minetest.formspec_escape(v.r_id)..",#FFFF00,"..
minetest.formspec_escape(v.r_type)..","..
minetest.formspec_escape(
yl_speak_up.show_effect(v, pname))..","
end
count_effects = count_effects + 1
end
end
if(count_effects < yl_speak_up.max_result_effects) then
list_of_effects = list_of_effects..",#00FF00,add,Add a new (Ef)fect"
else
list_of_effects = list_of_effects..",#AAAAAA,-,"..
"Maximum amount of allowed (Ef)fects per option reached!"
end
return {list = list_of_effects, target_dialog = target_dialog, target_effect = target_effect}
end
-- process input from formspec created in get_fs_edit_option_dialog(..)
yl_speak_up.input_edit_option_dialog = function(player, formname, fields)
if formname ~= "yl_speak_up:edit_option_dialog" then
return
end
local pname = player:get_player_name()
-- Is the player working on this particular npc?
local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id)
if(not(edit_mode)) then
return
end
local n_id = yl_speak_up.speak_to[pname].n_id
local d_id = yl_speak_up.speak_to[pname].d_id
local o_id = yl_speak_up.speak_to[pname].o_id
local dialog = yl_speak_up.speak_to[pname].dialog
local n_dialog = dialog.n_dialogs[d_id]
if(not(n_dialog) or not(n_dialog.d_options)) then
return
end
local d_option = n_dialog.d_options[o_id]
if(not(d_option)) then
return
end
if(fields.assign_quest_step and fields.assign_quest_step ~= "") then
yl_speak_up.show_fs(player, "assign_quest_step",
{n_id = n_id, d_id = d_id, o_id = o_id})
return
end
if(fields.switch_tab and fields.switch_tab == "2") then
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id,
caller="show_if_action_failed"})
return
elseif(fields.switch_tab and fields.switch_tab == "1") then
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id,
caller="show_if_action_succeeded"})
return
elseif(fields.switch_tab and fields.switch_tab == "3") then
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id,
caller="show_tab_limit_guessing"})
return
elseif(fields.switch_tab and fields.switch_tab == "4") then
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id,
caller="show_tab_limit_repeating"})
return
end
-- this menu is specific to an option for a dialog; if no dialog is selected, we really
-- can't know what to do
if(not(o_id) and d_id) then
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
elseif(not(d_id)) then
return
end
-- backwards compatibility to when this was a hidden field
fields.o_id = o_id
-- handles changes to o_text_when_prerequisites_met, target dialog, adding of a new dialog
local result = yl_speak_up.edit_mode_apply_changes(pname, fields)
-- if a new option was added or the target dialog of this one changed, display the right new option
if(result and result["show_next_option"] and n_dialog.d_options[result["show_next_option"]]) then
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = result["show_next_option"],
caller="show_next_option"})
return
end
if(fields.save_option) then
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id, caller="save_option"})
return
end
-- want to edit the text that is shown when switching to the next dialog?
if(fields.button_edit_action_failed_dialog
or fields.button_edit_action_success_dialog
or fields.save_dialog_modification
or fields.button_edit_limit_action_failed_repeat
or fields.button_edit_limit_action_success_repeat
or fields.turn_alternate_text_into_new_dialog) then
if( yl_speak_up.handle_edit_actions_alternate_text(
-- x_id, id_prefix, target_element and tmp_data_cache are nil here
player, pname, n_id, d_id, o_id, nil, nil,
"edit_option_dialog", nil, fields, nil)) then
-- the function above showed a formspec already
return
else
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id,
caller="back_from_edit_dialog_modifications"})
return
end
elseif(fields.back_from_edit_dialog_modification) then
-- no longer working on an alternate text
yl_speak_up.speak_to[pname].edit_alternate_text_for = nil
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_id, caller="back_from_edit_dialog_modifications"})
return
end
-- back to the main dialog window?
-- (this also happens when the last option was deleted)
if(fields.show_current_dialog or fields.quit or fields.button_exit or not(d_option) or fields.del_option) then
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
return
end
-- the player wants to see the previous option/answer
if(fields.edit_option_prev) then
-- sort all options by o_sort
local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort")
local o_found = o_id
for i, o in ipairs(sorted_list) do
if(o == o_id and sorted_list[ i-1]) then
o_found = sorted_list[ i-1 ]
end
end
-- show that dialog; fallback: show the same (o_id) again
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_found, caller="prev option"})
return
-- the player wants to see the next option/answer
elseif(fields.edit_option_next) then
-- sort all options by o_sort
local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort")
local o_found = o_id
for i, o in ipairs(sorted_list) do
if(o == o_id and sorted_list[ i+1 ]) then
o_found = sorted_list[ i+1 ]
end
end
-- show that dialog; fallback: show the same (o_id) again
yl_speak_up.show_fs(player, "edit_option_dialog",
{n_id = n_id, d_id = d_id, o_id = o_found, caller="next option"})
return
-- the player clicked on a precondition
elseif(fields.table_of_preconditions) then
yl_speak_up.show_fs(player, "edit_preconditions", fields.table_of_preconditions)
return
-- the player clicked on an action
elseif(fields.table_of_actions) then
yl_speak_up.show_fs(player, "edit_actions", fields.table_of_actions)
return
-- the player clicked on an effect
elseif(fields.table_of_effects) then
yl_speak_up.show_fs(player, "edit_effects", fields.table_of_effects)
return
end
-- if ESC is pressed or anything else unpredicted happens: go back to the main dialog edit window
-- reason: don't loose any unsaved changes to the dialog
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
end
-- edit options (not via staff but via the "I am your owner" dialog)
yl_speak_up.get_fs_edit_option_dialog = function(player, n_id, d_id, o_id, caller)
-- n_id, d_id and o_id have already been checked when this function is called
local pname = player:get_player_name()
local dialog = yl_speak_up.speak_to[pname].dialog
local n_dialog = dialog.n_dialogs[d_id]
-- currently no trade running (we're editing options)
yl_speak_up.trade[pname] = nil
yl_speak_up.speak_to[pname].trade_id = nil
if(not(n_dialog) or not(n_dialog.d_options) or not(n_dialog.d_options[o_id])) then
return "size[6,2]"..
"label[0.2,0.5;Ups! Option "..minetest.formspec_escape(tostring(o_id))..
" does not exist.]"..
"button_exit[2,1.5;1,0.9;exit;Exit]"
end
local d_option = n_dialog.d_options[o_id]
-- if it is a quest step, then show that; else allow creating a quest step
local quest_step_text = "button[15.4,0.1;6.0,0.9;assign_quest_step;Turn this into a quest step]"
if( d_option.quest_id and d_option.quest_id ~= ""
and d_option.quest_step and d_option.quest_step ~= "") then
quest_step_text = "box[4.9,0.0;14.0,1.1;#BB77BB]"..
"label[0.2,0.3;This is quest step:]"..
"label[5.0,0.3;"..
minetest.colorize("#00FFFF",
minetest.formspec_escape(d_option.quest_step)).."]"..
"label[0.2,0.8;of the quest:]"..
"label[5.0,0.8;"..
minetest.colorize("#CCCCFF",
minetest.formspec_escape(d_option.quest_id)).."]"..
"button[19.4,0.1;2.0,0.9;assign_quest_step;Change]"
end
-- offer the correct preselection for hidden/grey/show text
local alternate_answer_option = "3"
if(d_option.o_hide_when_prerequisites_not_met == "true") then
alternate_answer_option = "1"
elseif(d_option.o_grey_when_prerequisites_not_met == "true") then
alternate_answer_option = "2"
end
local answer_mode = 0
-- shall this option be choosen automaticly?
if(d_option.o_autoanswer and d_option.o_autoanswer == 1) then
answer_mode = 1
-- or randomly?
elseif(n_dialog.o_random) then
answer_mode = 2
end
local answer_text =
-- answer of the player (the actual option)
"container[0.0,8.3]"..
"label[0.2,0.0;..the player may answer with this text"..
minetest.formspec_escape(" [dialog option \""..tostring(o_id).."\"]:").."]"..
"dropdown[16.0,-0.4;5.3,0.7;option_autoanswer;"..
"by clicking on it,automaticly,randomly;"..tostring(answer_mode+1)..";]"
-- (automaticly *by fulfilling the prerequirements*)
if(d_id == "d_got_item" or d_id == "d_trade") then
answer_mode = 1
d_option.o_autoanswer = 1
answer_text =
"container[0.0,8.3]"..
"label[0.2,0.0;..this option will be selected automaticly.]"
end
if(answer_mode == 0 and (d_id ~= "d_got_item" and d_id ~= "d_trade")) then
answer_text = answer_text..
"label[1.2,0.8;A:]"..
"field[1.7,0.3;19.6,0.9;text_option_"..minetest.formspec_escape(o_id)..";;"..
minetest.formspec_escape(d_option.o_text_when_prerequisites_met).."]"..
"tooltip[option_text_met;This is the answer the player may choose if the "..
"preconditions are all fulfilled.]"..
-- dropdown for selecting weather to show the alternate answer or not
"label[0.2,1.7;..but if at least one pre(C)ondition is not fulfilled, then...]"..
"dropdown[12.0,1.3;9.3,0.7;hide_or_grey_or_alternate_answer;"..
"..hide this answer.,"..
"..grey out the following answer:,"..
"..display the following alternate answer:;"..
alternate_answer_option..";]"..
-- alternate answer
"label[1.2,2.5;A:]"..
"field[1.7,2.0;19.6,0.9;option_text_not_met;;"..
minetest.formspec_escape(d_option.o_text_when_prerequisites_not_met).."]"..
"tooltip[option_text_not_met;This is the answer the player may choose if the "..
"preconditions are NOT all fulfilled.]"..
"container_end[]"
elseif(answer_mode == 1) then
answer_text = answer_text..
"label[1.2,0.8;This option will not be shown but will be selected automaticly if all "..
"prerequirements are fullfilled.]"..
"label[1.2,1.4;The remaining options of this dialog will in this case not be evaluated.]"..
"label[1.2,2.0;The NPC will proceed as if this option was choosen manually.]"..
"label[1.2,2.6;"
if(d_id == "d_got_item") then
answer_text = answer_text..
"Note: This is used here to process items that the player gave to the NPC."
elseif(d_id == "d_trade") then
answer_text = answer_text..
"Note: This is useful for refilling stock by crafting new things when "..
"necessary, or for getting\nsupplies from a storage, or for storing "..
"traded goods in external storage chests."
else
answer_text = answer_text..
"This is i.e. useful for offering a diffrent start dialog depending on the "..
"player's progress in a quest."
end
answer_text = answer_text .. "]container_end[]"
elseif(answer_mode == 2) then
answer_text = answer_text..
"label[1.2,0.8;One option of the dialog - for example this one - will be selected randomly.]"..
"label[1.2,1.4;The other options of this dialog will be set to random as well.]"..
"label[1.2,2.0;The NPC will proceed as if this dialog was choosen manually.]"..
"label[1.2,2.6;Useful for small talk for generic NPC but usually not for quests.]"..
"container_end[]"
end
-- remember which option we are working at (better than a hidden field)
yl_speak_up.speak_to[pname].o_id = o_id
-- are there any preconditions?
local list_of_preconditions = ""
local prereq = d_option.o_prerequisites
local count_prereq = 0
if(prereq) then
local sorted_key_list = yl_speak_up.sort_keys(prereq)
for i, k in ipairs(sorted_key_list) do
local v = prereq[ k ]
list_of_preconditions = list_of_preconditions..
minetest.formspec_escape(v.p_id)..",#FFFF00,"..
minetest.formspec_escape(v.p_type)..","..
minetest.formspec_escape(
yl_speak_up.show_precondition(v, pname))..","
count_prereq = count_prereq + 1
end
end
if(count_prereq < yl_speak_up.max_prerequirements) then
list_of_preconditions = list_of_preconditions..",#00FF00,add,Add a new pre(C)ondition"
else
list_of_preconditions = list_of_preconditions..",#AAAAAA,-,"..
"Maximum amount of pre(C)onditions per option reached!"
end
-- build action list the same way as list of preconditions and effects
local list_of_actions = ""
local actions = d_option.actions
local count_actions = 0
local action_data = nil
-- if autoanswer or random is choosen, then there can be no action
if(answer_mode == 1 or answer_mode == 2) then
actions = nil
count_actions = 0
caller = ""
end
if(actions) then
local sorted_key_list = yl_speak_up.sort_keys(actions)
for i, k in ipairs(sorted_key_list) do
local v = actions[ k ]
list_of_actions = list_of_actions..
minetest.formspec_escape(v.a_id)..",#FFFF00,"..
minetest.formspec_escape(v.a_type)..","..
minetest.formspec_escape(
yl_speak_up.show_action(v))..","
count_actions = count_actions + 1
action_data = v
end
end
if(count_actions < yl_speak_up.max_actions) then
list_of_actions = list_of_actions..",#00FF00,add,Add a new (A)ction"
else
list_of_actions = list_of_actions..",#AAAAAA,-,"..
"Maximum amount of (A)ctions per option reached!"
end
-- list of (A)ctions (there can only be one per option; i.e. a trade)
local action_list_text =
"container[0.0,12.0]"..
"label[0.2,0.0;When this answer has been selected, start the following (A)ction:]"..
"tablecolumns[text;color,span=1;text;text]"
if(answer_mode == 1) then
action_list_text = action_list_text..
"label[1.2,0.6;No actions are executed because this option here is automaticly selected.]"..
"container_end[]"
elseif(answer_mode == 2) then
action_list_text = action_list_text..
"label[1.2,0.6;No actions are executed because this option here is selected randomly.]"..
"container_end[]"
else
action_list_text = action_list_text..
"table[1.2,0.3;20.2,0.7;table_of_actions;"..
list_of_actions..";0]"..
"container_end[]"
end
-- find the right target dialog for this option (if it exists)
local target_dialog = nil
-- which effect holds the information about the target dialog?
-- set this to a fallback for yl_speak_up.show_colored_dialog_text
local target_effect = {r_id = "-?-", r_type = "dialog"}
-- and build the list of effects
local results = d_option.o_results
-- create a new dialog type option if needed
if(not(results) or not(next(results))) then
target_dialog = yl_speak_up.prepare_new_dialog_for_option(
dialog, pname, n_id, d_id, o_id,
yl_speak_up.text_new_dialog_id,
results)
-- make sure we are up to date (a new option was inserted)
results = d_option.o_results
end
-- constructs the list_of_effects; may also update target_dialog and target_effect
local res = yl_speak_up.get_list_of_effects_and_target_dialog_and_effect(dialog, results, pname,
target_dialog, target_effect)
local list_of_effects = res.list
target_dialog = res.target_dialog
target_effect = res.target_effect
-- if no target dialog has been selected: default is to go to the dialog with d_sort 0
if(not(target_dialog) or target_dialog == "" or
(not(dialog.n_dialogs[target_dialog])
and target_dialog ~= "d_end"
and target_dialog ~= "d_got_item")) then
for d, v in pairs(dialog.n_dialogs) do
if(v.d_sort and tonumber(v.d_sort) == 0) then
target_dialog = d
end
end
end
-- build the list of available dialogs for the dropdown list(s)
local dialog_list = yl_speak_up.text_new_dialog_id
local dialog_selected = "1"
-- if there are dialogs defined
if(dialog and dialog.n_dialogs) then
-- the first entry will be "New dialog"
local n = 1
for k, v in pairs(dialog.n_dialogs) do
dialog_list = dialog_list .. "," .. minetest.formspec_escape(v.d_id or "?")
-- which one is the current dialog?
n = n + 1
if(v.d_id == target_dialog) then
dialog_selected = tostring(n)
end
end
if(target_dialog == "d_end") then
dialog_selected = tostring(n + 1)
end
end
dialog_list = dialog_list..",d_end"
if(not(target_dialog)) then
target_dialog = "- none -"
end
-- can the button "prev(ious)" be shown?
local button_prev = ""
-- can the button "next" be shown?
local button_next = ""
-- sort all options by o_sort
local sorted_list = yl_speak_up.get_sorted_options(n_dialog.d_options, "o_sort")
local o_found = o_id
local anz_options = 0
for i, o in ipairs(sorted_list) do
-- the buttons are inside a container; thus, Y is 0.0
if(o == o_id and sorted_list[ i-1 ]) then
button_prev = ""..
"button[7.9,0.0;2.0,0.9;edit_option_prev;Prev]"..
"tooltip[edit_option_prev;Go to previous option/answer "..
"(according to o_sort).]"
end
if(o == o_id and sorted_list[ i+1 ]) then
button_next = ""..
"button[12.5,0.0;2.0,0.9;edit_option_next;Next]"..
"tooltip[edit_option_next;Go to next option/answer "..
"(according to o_sort).]"
end
anz_options = anz_options + 1
end
-- less than yl_speak_up.max_number_of_options_per_dialog options?
local button_add = ""..
-- the buttons are inside a container; thus, Y is 0.0
"button[2.4,0.0;2.0,0.9;add_option;Add]"..
"tooltip[add_option;Add a new option/answer to this dialog.]"
if(anz_options >= yl_speak_up.max_number_of_options_per_dialog
or target_dialog == "d_end") then
button_add = ""
end
-- make all following coordinates relative
local action_text = "container[0.2,14.0]"..
"box[0.25,0.0;21.0,6.7;#555555]"
local tab_list = "tabheader[0.2,0.0;switch_tab;"..
"If the action was successful:,"..
"If the action failed:,"..
"Limit guessing:,"..
"Limit repeating:"
-- show what happens if the action fails
if(caller == "show_if_action_failed") then
-- allow to switch between successful and failed actions
action_text = action_text..tab_list..";2;true;true]"..
"label[0.4,0.6;"..
"If the player *failed* to complete the above action correctly,]"
if(action_data and action_data.a_on_failure
and dialog.n_dialogs and dialog.n_dialogs[ action_data.a_on_failure]) then
action_text = action_text..
-- ..and what the NPC will reply to that answer
"tooltip[1.2,3.9;19.6,2.5;This is what the NPC will say next when "..
"the player has failed to complete the action.]"..
"container[0.0,3.2]"..
"label[0.4,0.4;..the NPC will react to this failed action with the "..
"following dialog \""..tostring(action_data.a_on_failure)..
"\""..
yl_speak_up.show_colored_dialog_text(
dialog,
action_data,
action_data.a_on_failure,
"1.2,0.7;19.6,2.5;d_text_next",
"with the *modified* text",
":]",
"button_edit_action_failed_dialog")..
"container_end[]"
else
action_text = action_text..
"label[0.4,3.6;..go back to the initial dialog.]"
end
-- show time-based restrictions (max guesses per time);
-- the values will be saved in function yl_speak_up.edit_mode_apply_changes
elseif( caller == "show_tab_limit_guessing") then
local timer_name = "timer_on_failure_"..tostring(d_id).."_"..tostring(o_id)
local timer_data = yl_speak_up.get_variable_metadata(timer_name, "parameter", true)
if(not(timer_data)) then
timer_data = {}
end
action_text = action_text..tab_list..";3;true;true]"..
-- allow to switch between successful and failed actions
"label[0.4,0.6;"..
"Apply the following time-based restrictions to limit wild guessing:]"..
-- timer for failed actions
"label[0.4,1.6;The player can make]"..
"field[4.9,1.0;1.5,0.9;timer_max_attempts_on_failure;;"..
tostring(timer_data[ "max_attempts" ] or 0).."]"..
"label[6.7,1.6;attempts to complete this action successfully each]"..
"field[17.5,1.0;1.5,0.9;timer_max_seconds_on_failure;;"..
tostring(timer_data[ "duration" ] or 0).."]"..
"label[19.2,1.6;seconds.]"..
"label[0.4,2.2;Hint: 3 attempts per 1200 seconds (=20 minutes or one MineTest day)"..
" may be good values to\navoid wild guessing while not making the player "..
"having to wait too long to try again.]"..
"tooltip[timer_max_attempts_on_failure;How many tries shall the player have?"..
"\nA value of 0 disables this restriction.]"..
"tooltip[timer_max_seconds_on_failure;After which time can the player try again?"..
"\nA value of 0 disables this restriction.]"..
-- ..and what the NPC will explain in such a case
"tooltip[1.2,3.9;19.6,2.5;This is what the NPC will say next when "..
"\nthe player has failed to complete the action too"..
"\nmany times for the NPC's patience and the player"..
"\nhas to wait some time before guessing again.]"..
"container[0.0,3.2]"..
"label[0.4,0.4;The NPC will explain his unwillingness to accept more "..
"guesses "..
yl_speak_up.show_colored_dialog_text(
dialog,
{alternate_text = (timer_data[ "alternate_text" ]
or yl_speak_up.standard_text_if_action_failed_too_often)},
d_id, -- show the same dialog again
"1.2,0.7;19.6,2.5;d_text_next",
"with the following text",
":]",
"button_edit_limit_action_failed_repeat")..
"container_end[]"
-- show time-based restrictions (time between repeating this action successfully)
elseif( caller == "show_tab_limit_repeating") then
local timer_name = "timer_on_success_"..tostring(d_id).."_"..tostring(o_id)
local timer_data = yl_speak_up.get_variable_metadata(timer_name, "parameter", true)
if(not(timer_data)) then
timer_data = {}
end
action_text = action_text..tab_list..";4;true;true]"..
"label[0.4,0.6;"..
"Apply the following time-based restrictions to limit too quick repeating:]"..
-- timer for successful actions
"label[0.4,1.6;If the player completed the action successfully, he shall have to"..
" wait]"..
"field[15.0,1.0;1.5,0.9;timer_max_seconds_on_success;;"..
tostring(timer_data[ "duration" ] or 0).."]"..
"label[16.7,1.6;seconds until he]"..
"label[0.4,2.1;can repeat the action. Hint: 1200 seconds (=20 minutes or one "..
"MineTest day) may be a good value.]"..
"tooltip[timer_max_seconds_on_success;"..minetest.formspec_escape(
"If you hand out a quest item, you may not want the player"..
"\nto immediately repeat the action countless times, thus"..
"\nemptying the NPC's storage and using the quest item for"..
"\nother purposes. On the other hand, quest items may get "..
"\nlost, so the player needs a way to repeat each step."..
"\n1200 seconds may be a good value here as well.").."]"..
-- ..and what the NPC will explain in such a case
"tooltip[1.2,3.9;19.6,2.5;"..minetest.formspec_escape(
"This is what the NPC will say next when the player"..
"\nwants to repeat the action too soon for the NPC's"..
"\ntaste - after all the NPC does not have infinite "..
"\ninventory ressources, and the player may abuse the "..
"\nquest item for entirely diffrent purposes..").."]"..
"container[0.0,3.2]"..
-- this will lead back to the same dialog
"label[0.4,0.4;The NPC will explain his unwillingness to repeat the "..
"action so soon "..
yl_speak_up.show_colored_dialog_text(
dialog,
{alternate_text = (timer_data[ "alternate_text" ]
or yl_speak_up.standard_text_if_action_repeated_too_soon)},
d_id, -- show the same dialog again
"1.2,0.7;19.6,2.5;d_text_next",
"with the following text",
":]",
"button_edit_limit_action_success_repeat")..
"container_end[]"
-- show what happens if the action was successful
else
-- no action defined
if(count_actions == 0) then
-- do not show tabheader
action_text = action_text..
"label[0.4,0.6;"..
"There is no (A)ction defined. Directly apply the following (Ef)fects:]"
else
-- allow to switch between successful and failed actions
action_text = action_text..tab_list..";1;true;true]"..
"label[0.4,0.6;"..
"If the player completed the above action successfully, "..
"apply the following (Ef)fects:]"
end
action_text = action_text..
-- list of effects
"tablecolumns[text;color,span=1;text;text]"..
"table[1.2,0.9;19.6,2.0;table_of_effects;"..
list_of_effects..";0]"..
"tooltip[1.2,0.9;19.6,2.0;"..
"*All* (Ef)fects are executed after the action (if there is\n"..
"one defined in this option) has been completed successfully\n"..
"by the player. If there is no action defined, then the\n"..
"(Ef)fects will always be executed when this option here is\n"..
"selected.\n"..
"Please click on an (Ef)fect in order to edit or delete it!]"..
"container[0.0,3.2]"..
"label[0.4,0.4;The NPC will react to this answer with dialog:]"
if(d_id == "d_trade") then
action_text = action_text..
"label[13.5,0.4;..by showing his trade list.]"..
"container_end[]"
else
action_text = action_text..
-- allow to change the target dialog via a dropdown menu
"dropdown[10.2,0.0;3.0,0.7;d_id_"..minetest.formspec_escape(o_id)..";"..
dialog_list..";"..dialog_selected..",]"..
"tooltip[10.2,0.0;3.0,0.7;Select the target dialog with which the NPC shall react "..
"to this answer. Currently, dialog \""..
minetest.formspec_escape(target_dialog)..
"\" is beeing displayed.;#FFFFFF;#000000]"..
-- ..and what the NPC will reply to that answer
"tooltip[1.2,0.7;19.6,2.5;This is what the NPC will say next when the player has "..
"selected this answer here.]"..
yl_speak_up.show_colored_dialog_text(
dialog,
-- this is either the "dialog" effect or an empty fallback
target_effect,
-- this is the text the NPC will say in reaction to this answer
target_dialog,
"1.2,0.7;19.6,2.5;d_text",
"label[13.5,0.4;with the following *modified* text:]",
"",
"button_edit_action_success_dialog")..
"container_end[]"
end
end
action_text = action_text.."container_end[]"
-- build up the formspec
local formspec = ""..
"size[22,22]"..
"bgcolor[#00000000;false]"..
-- button back to the current dialog (of which this is an option)
"button[16.4,0.2;5.0,0.9;show_current_dialog;Back to dialog "..
minetest.formspec_escape(d_id).."]"..
"tooltip[show_current_dialog;Go back to dialog "..
minetest.formspec_escape(d_id).." and continue editing that dialog.]"..
-- tell the player what this formspec is about
"label[6.5,0.4;You are editing dialog option \""..tostring(o_id).."\":]"..
-- the text the NPC says
"container[0.0,0.9]"..
"label[0.2,0.0;NPC says "..
minetest.formspec_escape("[dialog \""..tostring(d_id).."\"]:").."]"..
yl_speak_up.show_colored_dialog_text(
dialog,
{r_id = "", r_type = "dialog"},
d_id,
"1.2,0.3;20.2,2.5;d_text",
"", -- no modifications possible at this step
"",
"").. -- no edit button here as this text cannot be changed here
"tooltip[1.2,0.3;20.2,3.0;This is what the NPC says to the player.]"..
"container_end[]"..
"container[0.0,3.9]"..
quest_step_text..
"container_end[]"..
-- list the preconditions
"container[0.0,5.4]"..
"label[0.2,0.0;If all of the following pre(C)onditions are fulfilled:]"..
"tablecolumns[text;color,span=1;text;text]"..
"table[1.2,0.3;20.2,2.0;table_of_preconditions;"..
list_of_preconditions..";0]"..
"tooltip[1.2,0.3;20.2,2.0;"..
"*All* pre(C)onditions need to be true in order\n"..
"for the option to be offered to the player.\n"..
"Please click on a pre(C)ondition in order\n"..
"to edit or delete it!]"..
"container_end[]"..
-- answer of the player (the actual option)
answer_text..
-- list of (A)ctions (there can only be one per option; i.e. a trade)
action_list_text..
-- list effects and target dialog for successful - and target dialog for unsuccessful
-- actions (including a toggle button)
action_text..
-- container for the buttons/footer
"container[0.0,20.9]"..
-- button: delete
"button[0.2,0.0;2.0,0.9;del_option;Delete]"..
"tooltip[del_option;Delete this option/answer.]"..
-- button: add new
button_add..
-- button: save
"button[4.6,0.0;2.0,0.9;save_option;Save]"..
"tooltip[save_option;Save what you canged (or discard it).]"..
-- button: prev/next
button_prev..
button_next..
-- button: go back to dialog (repeated from top of the page)
"button[15.8,0.0;5.0,0.9;show_current_dialog;Back to dialog "..
minetest.formspec_escape(d_id).."]"..
"tooltip[show_current_dialog;Go back to dialog "..
minetest.formspec_escape(d_id).." and continue editing that dialog.]"..
-- allow to enter o_sort
"label[10.1,0.5;Sort:]"..
"field[11.1,0.0;1.0,0.9;edit_option_o_sort;;"..
minetest.formspec_escape(d_option.o_sort).."]"..
"tooltip[edit_option_o_sort;o_sort: The lower the number, the higher up in the "..
"list this option goes\nNegative values are ignored;#FFFFFF;#000000]"..
"container_end[]"
return formspec
end

View File

@ -1,362 +0,0 @@
-- This file contains what is necessary to add/edit a precondition.
--
-- Which diffrent types of preconditions are available?
-- -> The following fields are part of a precondition:
-- p_id the ID/key of the precondition/prerequirement
-- p_type selected from values_what
-- p_value used to store the subtype of p_type
--
-- a state/variable ("state"):
-- p_variable name of a variable the player has read access to;
-- dropdown list with allowed options
-- p_operator selected from values_operator
-- p_var_cmp_value can be set freely by the player
--
-- the value of a property of the NPC (for generic NPC) ("property"):
-- p_value name of the property that shall be checked
-- p_operator operator for cheking the property against p_expected_val
-- p_var_cmp_value the expected value of the property
--
-- something that has to be calculated or evaluated (=call a function) ("evaluate"):
-- p_value the name of the function that is to be called
-- p_param1 the first paramter (optional; depends on function)
-- ..
-- p_param9 the 9th parameter (optional; depends on function)
-- p_operator operator for checking the result
-- p_var_cmp_value compare the result of the function with this value
--
-- a block in the world ("block"):
-- p_pos a position in the world; determined by asking the player
-- to punch the block
-- p_node (follows from p_pos)
-- p_param2 (follows from p_pos)
--
-- a trade defined as an action ("trade"): no variables needed (buy and pay stack
-- follow from the trade set as action)
--
-- an inventory: ("player_inv", "npc_inv" or "block_inv")
-- p_itemstack an itemstack; needs to be a minetest.registered_item[..];
-- size/count is also checked
--
-- the inventory of a block on the map: ("block_inv", in addition to the ones above)
-- p_pos a position in the world; determined by asking the player
-- to punch the block
-- p_inv_list_name name of the inventory list of the block
--
-- the player offered/gave the NPC an item: ("player_offered_item"):
-- p_value an itemstack; needs to be a minetest.registered_item[..];
-- size/count is checked for some subtypes
-- p_match_stack_size does the NPC expect exactly one stack size - or is
-- more or less etc. also ok?
-- p_item_group are items of this group instead of the exact item name
-- also acceptable?
-- p_item_desc the description of the itemstack (set by another quest NPC
-- so that the player can distinguish it from other itemstacks
-- with the same item name; see action "npc_gives")
-- p_item_quest_id Special ID to make sure that it is really the *right*
-- item and not just something the player faked with an
-- engraving table or something similar
--
-- a function ("function"): requires npc_master to create and edit
-- p_value the lua code to execute and evaulate
--
-- depends on another option:
-- p_value name of the other option of this dialog that is considered
-- p_fulfilled shall option p_value be true or false?
-- some helper lists for creating the formspecs and evaulating
-- the player's answers:
-- general direction of what a prerequirement may be about
local check_what = {
"- please select -",
"an internal state (i.e. of a quest)", -- 2
"the value of a property of the NPC (for generic NPC)",
"something that has to be calculated or evaluated (=call a function)",
"a block somewhere", -- 3
"a trade", -- 4
"the inventory of the player", -- 5
"the inventory of the NPC", -- 6
"the inventory of a block somewhere", -- 7
"an item the player offered/gave to the NPC", -- 8
"execute Lua code (requires npc_master priv)", -- 7 -> 9
"The preconditions of another dialog option are fulfilled/not fulfilled.", -- 9 -> 11
"nothing - always true (useful for generic dialogs)",
"nothing - always false (useful for temporally deactivating an option)",
}
-- how to store these as p_type in the precondition:
local values_what = {"", "state", "property", "evaluate", "block", "trade",
"player_inv", "npc_inv", "block_inv",
"player_offered_item",
-- "function" requires npc_master priv:
"function",
-- depends on the preconditions of another option
"other",
"true", "false"}
-- options for "a trade"
local check_trade = {
"- please select -",
"The NPC has the item(s) he wants to sell in his inventory.", -- 2
"The player has the item(s) needed to pay the price.", -- 3
"The NPC ran out of stock.", -- 4
"The player cannot afford the price.", -- 5
}
-- how to store these as p_value:
local values_trade = {"", "npc_can_sell", "player_can_buy", "npc_is_out_of_stock", "player_has_not_enough"}
-- options for "the inventory of " (either player or NPC; perhaps blocks later on)
local check_inv = {
"- please select -",
"The inventory contains the following item:",
"The inventory *does not* contain the following item:",
"There is room for the following item in the inventory:",
"The inventory is empty.",
}
-- how to store these as p_value (the actual itemstack gets stored as p_itemstack):
local values_inv = {"", "inv_contains", "inv_does_not_contain", "has_room_for", "inv_is_empty"}
local check_block = {
"- please select -",
"The block is as it is now.",
"There shall be air instead of this block.",
"The block is diffrent from how it is now.",
"I can't punch it. The block is as the block *above* the one I punched.",
}
-- how to store these as p_value (the actual node data gets stored as p_node, p_param2 and p_pos):
-- Note: "node_is_like" occours twice because it is used to cover blocks that
-- cannot be punched as well as normal blocks.
local values_block = {"", "node_is_like", "node_is_air", "node_is_diffrent_from", "node_is_like"}
-- comparison operators for variables
local check_operator = {
"- please select -", -- 1
"== (is equal)", -- 2
"~= (is not equal)", -- 3
">= (is greater or equal)", -- 4
"> (is greater)", -- 5
"<= (is smaller or equal)", -- 6
"< (is smaller)", -- 7
"not (logically invert)", -- 8
"is_set (has a value)", -- 9
"is_unset (has no value)", -- 10
"more than x seconds ago", -- 11
"less than x seconds ago", -- 12
"has completed quest step", -- 13
"quest step *not* completed", -- 14
}
-- how to store these as p_value (the actual variable is stored in p_variable, and the value in p_cmp_value):
local values_operator = {"", "==", "~=", ">=", ">", "<=", "<", "not", "is_set", "is_unset",
"more_than_x_seconds_ago","less_than_x_seconds_ago",
"quest_step_done", "quest_step_not_done"}
-- get the list of variables the player has read access to
yl_speak_up.get_sorted_player_var_list_read_access = function(pname)
local var_list = {}
-- copy the values that are server-specific
for i, v in ipairs(yl_speak_up.custom_server_functions.precondition_descriptions) do
table.insert(var_list, v)
end
-- get the list of variables the player can read
local tmp = yl_speak_up.get_quest_variables_with_read_access(pname)
-- sort that list (the dropdown formspec element returns just an index)
table.sort(tmp)
for i, v in ipairs(tmp) do
table.insert(var_list, v)
end
return var_list
end
-- returns a human-readable text as description of the precondition
-- (as shown in the edit options dialog and in the edit precondition formspec)
yl_speak_up.show_precondition = function(p, pname)
if(not(p.p_type) or p.p_type == "") then
return "(nothing): Always true."
elseif(p.p_type == "item") then
return "item: The player has \""..tostring(p.p_value).."\" in his inventory."
elseif(p.p_type == "quest") then
return "quest: Always false."
elseif(p.p_type == "auto") then
return "auto: Always true."
elseif(p.p_type == "true") then
return "true: Always true."
elseif(p.p_type == "false") then
return "false: Always false."
elseif(p.p_type == "function") then
return "function: evaluate "..tostring(p.p_value)
elseif(p.p_type == "state") then
local var_name = "VALUE_OF[ - ? - ]"
if(p.p_variable) then
var_name = "VALUE_OF[ "..tostring(
yl_speak_up.strip_pname_from_var(p.p_variable, pname)).." ]"
end
if(not(p.p_operator)) then
return "Error: Operator not defined."
elseif(p.p_operator == "not") then
return "not( "..var_name.." )"
elseif(p.p_operator == "is_set") then
return var_name.." ~= nil (is_set)"
elseif(p.p_operator == "is_unset") then
return var_name.." == nil (is_unset)"
elseif(p.p_operator == "more_than_x_seconds_ago") then
return var_name.." was set to current time "..
"*more* than "..tostring(p.p_var_cmp_value).." seconds ago"
elseif(p.p_operator == "less_than_x_seconds_ago") then
return var_name.." was set to current time "..
"*less* than "..tostring(p.p_var_cmp_value).." seconds ago"
elseif(p.p_operator == "quest_step_done") then
return var_name.." shows: player completed quest step \""..
tostring(p.p_var_cmp_value).."\" successfully"
elseif(p.p_operator == "quest_step_not_done") then
return var_name.." shows: player has not yet completed quest step \""..
tostring(p.p_var_cmp_value).."\""
end
if(p.p_var_cmp_value == "") then
return var_name.." "..tostring(p.p_operator).." \"\""
end
return var_name.." "..tostring(p.p_operator).." "..
tostring(p.p_var_cmp_value)
elseif(p.p_type == "property") then
local i = math.max(1,table.indexof(values_operator, p.p_operator))
return tostring(p.p_value)..
" "..tostring(check_operator[i])..
" "..tostring(p.p_var_cmp_value)
elseif(p.p_type == "evaluate") then
local str = ""
for i = 1, 9 do
str = str..tostring(p["p_param"..tostring(i)])
if(i < 9) then
str = str..","
end
end
local i_op = math.max(1,table.indexof(values_operator, p.p_operator))
return "FUNCTION["..tostring(p.p_value).."]"..
"("..str..") "..tostring(check_operator[i_op])..
" "..tostring(p.p_var_cmp_value)
elseif(p.p_type == "block") then
if(not(p.p_pos) or type(p.p_pos) ~= "table"
or not(p.p_pos.x) or not(p.p_pos.y) or not(p.p_pos.z)) then
return "ERROR: p.p_pos is "..minetest.serialize(p.p_pos)
elseif(p.p_value == "node_is_like") then
return "The block at "..minetest.pos_to_string(p.p_pos).." is \""..
tostring(p.p_node).."\" with param2: "..tostring(p.p_param2).."."
elseif(p.p_value == "node_is_air") then
return "There is no block at "..minetest.pos_to_string(p.p_pos).."."
elseif(p.p_value == "node_is_diffrent_from") then
return "There is another block than \""..tostring(p.p_node).."\" at "..
minetest.pos_to_string(p.p_pos)..", or it is at least "..
"rotated diffrently (param2 is not "..tostring(p.p_param2)..")."
end
elseif(p.p_type == "trade") then
local nr = table.indexof(values_trade, p.p_value)
if(nr and check_trade[ nr ]) then
return check_trade[ nr ]
end
elseif(p.p_type == "player_inv" or p.p_type == "npc_inv" or p.p_type == "block_inv") then
local who = "The player"
local what = "\""..tostring(p.p_itemstack).."\" in his inventory."
if(p.p_type == "npc_inv") then
who = "The NPC"
elseif(p.p_type == "block_inv") then
if(not(p.p_pos) or type(p.p_pos) ~= "table"
or not(p.p_pos.x) or not(p.p_pos.y) or not(p.p_pos.z)) then
return "ERROR: p.p_pos is "..minetest.serialize(p.p_pos)
end
who = "The block at "..minetest.pos_to_string(p.p_pos)
what = "\""..tostring(p.p_itemstack).."\" in inventory list \""..
tostring(p.p_inv_list_name).."\"."
end
if(p.p_value == "inv_contains") then
return who.." has "..what
elseif(p.p_value == "inv_does_not_contain") then
return who.." does not have "..what
elseif(p.p_value == "has_room_for") then
return who.." has room for "..what
elseif(p.p_value == "inv_is_empty") then
if(p.p_type == "block_inv") then
return who.." has an empty inventory list \""..
tostring(p.p_inv_list_name).."\"."
end
return who.." has an empty inventory."
end
elseif(p.p_type == "player_offered_item") then
local item = tostring(p.p_value:split(" ")[1])
local amount = tostring(p.p_value:split(" ")[2])
local match = "any amount"
if(p.p_match_stack_size == "any") then
match = "any amount"
elseif(p.p_match_stack_size == "exactly") then
match = "exactly "..tostring(amount)
elseif(p.p_match_stack_size == "less"
or p.p_match_stack_size == "more") then
match = p.p_match_stack_size.." than "..tostring(amount)
elseif(p.p_match_stack_size == "another") then
match = "another amount than " ..tostring(amount)
end
if(p.p_item_group and p.p_item_group ~= "") then
return "The player offered "..tostring(match).." item(s) of the group \""..
tostring(item).."\"."
elseif((p.p_item_quest_id and p.p_item_quest_id ~= "")
or (p.p_item_desc and p.p_item_desc ~= "")) then
return "The player offered "..tostring(match).." of \""..
tostring(p.p_item_desc or "- default description -")..
"\" (\""..tostring(item or "- ? -").."\") "..
"with ID \""..tostring(p.p_item_quest_id or "- no special ID -").."\"."
else
return "The player offered "..tostring(match).." of \""..tostring(item).."\"."
end
elseif(p.p_type == "other") then
local fulfilled = "fulfilled"
if(not(p.p_fulfilled) or p.p_fulfilled ~= "true") then
fulfilled = "*not* fulfilled"
end
return "The preconditions for dialog option \""..tostring(p.p_value).."\" are "..
fulfilled.."."
end
-- fallback
return tostring(p.p_value)
end
-- these are only wrapper functions for those in fs_edit_general.lua
yl_speak_up.input_fs_edit_preconditions = function(player, formname, fields)
return yl_speak_up.input_fs_edit_option_related(player, formname, fields,
"p_", "o_prerequisites", yl_speak_up.max_prerequirements,
"pre(C)ondition", "tmp_prereq",
"Please punch the block you want to check in your precondition!",
values_what, values_operator, values_block, values_trade, values_inv,
check_what, check_operator, check_block, check_trade, check_inv,
-- player variables with read access
yl_speak_up.get_sorted_player_var_list_read_access,
"edit_preconditions"
)
end
yl_speak_up.get_fs_edit_preconditions = function(player, table_click_result)
return yl_speak_up.get_fs_edit_option_related(player, table_click_result,
"p_", "o_prerequisites", yl_speak_up.max_prerequirements,
"pre(C)ondition", "tmp_prereq",
"What do you want to check in this precondition?",
values_what, values_operator, values_block, values_trade, values_inv,
check_what, check_operator, check_block, check_trade, check_inv,
-- player variables with read access
yl_speak_up.get_sorted_player_var_list_read_access,
-- show one precondition element
yl_speak_up.show_precondition,
"table_of_preconditions",
"The following expression shall be true:", "Operator:",
"Value to compare with (in some cases parameter):",
"The following shall be true about the block:"
)
end

View File

@ -1,624 +0,0 @@
-- ###
-- Fashion
-- ###
-- some meshes use more than one texture, and which texture is the main skin
-- texture can only be derived from the name of the mesh
yl_speak_up.get_mesh = function(pname)
if(not(pname)) then
return "error"
end
local obj = yl_speak_up.speak_to[pname].obj
if(not(obj)) then
return "error"
end
local entity = obj:get_luaentity()
if(not(entity)) then
return "error"
end
return entity.mesh
end
-- diffrent mobs (distinguished by self.name) may want to wear diffrent skins
-- even if they share the same model; find out which mob we're dealing with
yl_speak_up.get_mob_type = function(pname)
if(not(pname)) then
return "error"
end
local obj = yl_speak_up.speak_to[pname].obj
if(not(obj)) then
return "error"
end
local entity = obj:get_luaentity()
if(not(entity)) then
return "error"
end
return entity.name
end
-- this makes use of the "model" option of formspecs
yl_speak_up.skin_preview_3d = function(mesh, textures, where_front, where_back)
local tstr = ""
for i, t in ipairs(textures) do
tstr = tstr..minetest.formspec_escape(t)..","
end
local backside = ""
if(where_back) then
backside = ""..
"model["..where_back..";skin_show_back;"..mesh..";"..tstr..";0,0;false;true;;]"
end
return "model["..where_front..";skin_show_front;"..mesh..";"..tstr..";0,180;false;true;;]"..--"0,300;9]".. -- ;]"..
backside
end
-- this is a suitable version for most models/meshes that use normal player skins
-- (i.e. mobs_redo) with skins in either 64 x 32 or 64 x 64 MC skin format
yl_speak_up.skin_preview_normal = function(skin, with_backside)
local backside = ""
if(with_backside) then
backside = ""..
"image[8,0.7;2,2;[combine:8x8:-24,-8="..skin.."]".. -- back head
"image[7.85,0.55;2.3,2.3;[combine:8x8:-56,-8="..skin.."]".. -- head, beard
"image[8,2.75;2,3;[combine:8x12:-32,-20="..skin..":-32,-36="..skin.."]".. -- body back
"image[8,5.75;1,3;[combine:4x12:-12,-20="..skin.."]".. -- left leg back
"image[8,5.75;1,3;[combine:4x12:-28,-52="..skin..":-12,-52="..skin.."]".. -- r. leg back ov
"image[9,5.75;1,3;[combine:4x12:-12,-20="..skin.."^[transformFX]".. -- right leg back
"image[9,5.75;1,3;[combine:4x12:-12,-36="..skin.."]".. -- right leg back ov
"image[7,2.75;1,3;[combine:4x12:-52,-20="..skin..":-40,-52="..skin..":-60,-52="..skin.."]".. -- l. hand back ov
"image[10,2.75;1,3;[combine:4x12:-52,-20="..skin.."^[transformFX]".. -- right hand back
"image[10,2.75;1,3;[combine:4x12:-52,-20="..skin..":-52,-36="..skin.."]" -- left hand back
end
return "image[3,0.7;2,2;[combine:8x8:-8,-8="..skin.."]"..
"image[2.85,0.55;2.3,2.3;[combine:8x8:-40,-8="..skin.."]".. -- head, beard
"image[3,2.75;2,3;[combine:8x12:-20,-20="..skin..":-20,-36="..skin.."]".. -- body
"image[3,5.75;1,3;[combine:4x12:-4,-20="..skin..":-4,-36="..skin.."]".. -- left leg + ov
"image[4,5.75;1,3;[combine:4x12:-4,-20="..skin.."^[transformFX]".. -- right leg
"image[4,5.75;1,3;[combine:4x12:-20,-52="..skin..":-4,-52="..skin.."]".. -- right leg ov
"image[2.0,2.75;1,3;[combine:4x12:-44,-20="..skin..":-44,-36="..skin.."]".. -- left hand
"image[5.0,2.75;1,3;[combine:4x12:-44,-20="..skin.."^[transformFX]".. -- right hand
"image[5.0,2.75;1,3;[combine:4x12:-36,-52="..skin..":-52,-52="..skin.."]".. -- right hand ov
backside
--local legs_back = "[combine:4x12:-12,-20="..skins.skins[name]..".png"
end
local function cape2texture(t)
return "yl_speak_up_mask_cape.png^[combine:32x64:56,20=" .. t
end
local function shield2texture(t)
return "yl_speak_up_mask_shield.png^[combine:32x64:0,0=(" .. t .. ")"
end
local function textures2skin(textures)
local temp = {}
-- Cape
local cape = cape2texture(textures[1])
-- Main
local main = textures[2]
-- left (Shield)
local left = shield2texture(textures[3])
-- right (Sword)
local right = textures[4]
temp = {cape, main, left, right}
return temp
end
yl_speak_up.mesh_update_textures = function(pname, textures)
-- actually make sure that the NPC updates its texture
local obj = yl_speak_up.speak_to[pname].obj
if(not(obj) or not(textures)) then
return
end
-- the skins with wielded items need some conversion,
-- while simpler models may just apply the texture
local mesh = yl_speak_up.get_mesh(pname)
if(mesh and yl_speak_up.mesh_data[mesh].textures_to_skin) then
textures = textures2skin(textures)
end
obj:set_properties({ textures = textures })
yl_speak_up.speak_to[pname].skins = textures
local entity = obj:get_luaentity()
if(entity) then
entity.yl_speak_up.skin = textures
end
-- scrolling through the diffrent skins updates the skin; avoid spam in the log
-- yl_speak_up.log_change(pname, n_id,
-- "(fashion) skin changed to "..tostring(new_skin)..".")
end
yl_speak_up.get_fs_fashion_extended = function(pname)
-- which texture from the textures list are we talking about?
-- this depends on the model!
local mesh = yl_speak_up.get_mesh(pname)
local texture_index = yl_speak_up.mesh_data[mesh].texture_index
if(not(texture_index)) then
texture_index = 1
end
local textures = yl_speak_up.speak_to[pname].textures
local skin = (textures[texture_index] or "")
-- which skins are available? this depends on mob_type
local mob_type = yl_speak_up.get_mob_type(pname)
local skins = yl_speak_up.mob_skins[mob_type] or {skin}
local capes = yl_speak_up.mob_capes[mob_type] or {}
local cape = "" -- TODO
-- is this player editing this particular NPC? then rename the button
if(not(yl_speak_up.edit_mode[pname])
or yl_speak_up.edit_mode[pname] ~= yl_speak_up.speak_to[pname].n_id) then
return "label[Error. Not in Edit mode!]"
end
local cape_list = table.concat(capes, ",")
local cape_index = table.indexof(capes, cape)
if(cape_index == -1) then
cape_index = ""
end
local tmp_textures = textures
if(texture_index ~= 1) then
tmp_textures = textures2skin(textures)
end
local preview = yl_speak_up.skin_preview_3d(mesh, tmp_textures, "4.7,0.5;5,10", nil)
local button_cancel = "Cancel"
-- is this player editing this particular NPC? then rename the button
if( yl_speak_up.edit_mode[pname]
and yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) then
button_cancel = "Back"
end
local formspec = {
"size[13.4,15]",
"label[0.3,0.2;Skin: ",
minetest.formspec_escape(skin),
"]",
"label[4.6,0.65;",
yl_speak_up.speak_to[pname].n_id,
"]",
"label[6,0.65;",
(yl_speak_up.speak_to[pname].n_npc or "- nameless -"),
"]",
"dropdown[9.1,0.2;4,0.75;set_cape;",
cape_list, ";", cape_index, "]",
"label[0.3,4.2;Left:]",
"label[9.1,4.2;Right:]",
"field_close_on_enter[set_sword;false]",
"field_close_on_enter[set_shield;false]",
"image[9.1,1;4,2;",
textures[1] or "",
"]", -- Cape
"image[0.3,4.2;4,4;",
textures[4] or "",
"]", -- Sword
"image[9.1,4.2;4,4;",
textures[3] or "",
"]", --textures[3],"]", -- Shield
"tooltip[0.3,4.2;4,4;This is: ",
minetest.formspec_escape(textures[4]),
"]",
"tooltip[9.1,4.2;4,4;This is: ",
minetest.formspec_escape(textures[3]),
"]",
preview or "",
"button[0.3,8.4;3,0.75;button_cancel;"..button_cancel.."]",
"button[10.1,8.4;3,0.75;button_save;Save]",
"list[current_player;main;1.8,10;8,4;]",
-- set wielded items
"label[0.3,9.7;Wield\nleft:]",
"label[12.0,9.7;Wield\nright:]",
"list[detached:yl_speak_up_player_"..tostring(pname)..";wield;0.3,10.5;1,1;]",
"list[detached:yl_speak_up_player_"..tostring(pname)..";wield;12.0,10.5;1,1;1]",
"button[0.3,11.7;1,0.6;button_wield_left;Set]",
"button[12.0,11.7;1,0.6;button_wield_right;Set]",
"tooltip[button_wield_left;Set and store what your NPC shall wield in its left hand.]",
"tooltip[button_wield_right;Set and store what your NPC shall wield in its right hand.]",
}
return table.concat(formspec, "")
end
yl_speak_up.fashion_wield_give_items_back = function(player, pname)
-- move the item back to the player's inventory (if possible)
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
local player_inv = player:get_inventory()
local left_stack = trade_inv:get_stack("wield", 1)
local right_stack = trade_inv:get_stack("wield", 2)
if(left_stack and not(left_stack:is_empty())
and player_inv:add_item("main", left_stack)) then
trade_inv:set_stack("wield", 1, "")
end
if(right_stack and not(right_stack:is_empty())
and player_inv:add_item("main", right_stack)) then
trade_inv:set_stack("wield", 2, "")
end
end
-- normal skins for NPC - without wielded items or capes etc.
yl_speak_up.input_fashion = function(player, formname, fields)
if formname ~= "yl_speak_up:fashion" then
return
end
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
-- is the player editing this npc? if not: abort
if(not(yl_speak_up.edit_mode[pname])
or (yl_speak_up.edit_mode[pname] ~= n_id)) then
return ""
end
-- catch ESC as well
if(not(fields) or (fields.quit or fields.button_cancel or fields.button_exit)) then
yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id,
d_id = yl_speak_up.speak_to[pname].d_id})
return
end
-- which texture from the textures list are we talking about?
-- this depends on the model!
local mesh = yl_speak_up.get_mesh(pname)
local texture_index = yl_speak_up.mesh_data[mesh].texture_index
if(not(texture_index)) then
texture_index = 1
end
-- show extra formspec with wielded item configuration and cape setup
if(fields.button_config_wielded_items
and yl_speak_up.mesh_data[mesh].can_show_wielded_items) then
yl_speak_up.show_fs(player, "fashion_extended")
return
end
-- which skins are available? this depends on mob_type
local mob_type = yl_speak_up.get_mob_type(pname)
local skins = yl_speak_up.mob_skins[mob_type]
local textures = yl_speak_up.speak_to[pname].textures
-- fallback if something went wrong (i.e. unkown NPC)
local skin = (textures[texture_index] or "")
if(not(skins)) then
skins = {skin}
end
local skin_index = table.indexof(skins, skin)
if(skin_index == -1) then
skin_index = 1
end
local new_skin = skin
-- switch back to the stored old skin
if(fields.button_old_skin) then
local old_texture = yl_speak_up.speak_to[pname].old_texture
if(old_texture) then
new_skin = old_texture
end
-- store the new skin
elseif(fields.button_store_new_skin) then
yl_speak_up.speak_to[pname].old_texture = skin
-- show previous skin
elseif(fields.button_prev_skin) then
if(skin_index > 1) then
new_skin = skins[skin_index - 1]
else
new_skin = skins[#skins]
end
-- show next skin
elseif(fields.button_next_skin) then
if(skin_index < #skins) then
new_skin = skins[skin_index + 1]
else
new_skin = skins[1]
end
-- set directly via list
elseif(fields.set_skin_normal) then
local new_index = table.indexof(skins, fields.set_skin_normal)
if(new_index ~= -1) then
new_skin = skins[new_index]
end
end
-- if there is a new skin to consider
if(textures[texture_index] ~= new_skin) then
textures[texture_index] = new_skin
yl_speak_up.mesh_update_textures(pname, textures)
end
if(fields.set_animation
and yl_speak_up.mesh_data[mesh]
and yl_speak_up.mesh_data[mesh].animation
and yl_speak_up.mesh_data[mesh].animation[fields.set_animation]
and yl_speak_up.speak_to[pname]
and yl_speak_up.speak_to[pname].obj) then
local obj = yl_speak_up.speak_to[pname].obj
obj:set_animation(yl_speak_up.mesh_data[mesh].animation[fields.set_animation])
-- store the animation so that it can be restored on reload
local entity = obj:get_luaentity()
if(entity) then
entity.yl_speak_up.animation = yl_speak_up.mesh_data[mesh].animation[fields.set_animation]
end
end
if(fields.button_old_skin or fields.button_store_new_skin) then
yl_speak_up.speak_to[pname].old_texture = nil
yl_speak_up.show_fs(player, "talk", {n_id = yl_speak_up.speak_to[pname].n_id,
d_id = yl_speak_up.speak_to[pname].d_id})
return
end
yl_speak_up.show_fs(player, "fashion")
end
-- set what the NPC shall wield and which cape to wear
yl_speak_up.input_fashion_extended = function(player, formname, fields)
if formname ~= "yl_speak_up:fashion_extended" then
return
end
local pname = player:get_player_name()
local textures = yl_speak_up.speak_to[pname].textures
local n_id = yl_speak_up.speak_to[pname].n_id
-- is the player editing this npc? if not: abort
if(not(yl_speak_up.edit_mode[pname])
or (yl_speak_up.edit_mode[pname] ~= n_id)) then
return ""
end
-- catch ESC as well
if(not(fields)
or (fields.quit or fields.button_cancel or fields.button_exit or fields.button_save)) then
yl_speak_up.fashion_wield_give_items_back(player, pname)
yl_speak_up.show_fs(player, "fashion")
return
elseif(fields.button_wield_left
or fields.button_wield_right) then
local trade_inv = minetest.get_inventory({type="detached", name="yl_speak_up_player_"..pname})
local player_inv = player:get_inventory()
local left_stack = trade_inv:get_stack("wield", 1)
local right_stack = trade_inv:get_stack("wield", 2)
if(left_stack and left_stack:get_name() and fields.button_wield_left) then
textures[4] = yl_speak_up.get_wield_texture(left_stack:get_name())
yl_speak_up.log_change(pname, n_id,
"(fashion) sword changed to "..tostring(fields.set_sword)..".")
end
if(right_stack and right_stack:get_name() and fields.button_wield_right) then
textures[3] = yl_speak_up.get_wield_texture(right_stack:get_name())
yl_speak_up.log_change(pname, n_id,
"(fashion) shield changed to "..tostring(fields.set_shield)..".")
end
yl_speak_up.fashion_wield_give_items_back(player, pname)
-- only change cape if there really is a diffrent one selected
elseif(fields.set_cape and fields.set_cape ~= textures[1]) then
local mob_type = yl_speak_up.get_mob_type(pname)
local capes = yl_speak_up.mob_capes[mob_type] or {}
-- only set the cape if it is part of the list of allowed capes
if(table.indexof(capes, fields.set_cape) ~= -1) then
textures[1] = fields.set_cape
yl_speak_up.log_change(pname, n_id,
"(fashion) cape changed to "..tostring(fields.set_cape)..".")
end
end
if(fields.button_wield_left or fields.button_wield_right or fields.set_cape or fields.button_sve) then
yl_speak_up.fashion_wield_give_items_back(player, pname)
yl_speak_up.mesh_update_textures(pname, textures)
yl_speak_up.show_fs(player, "fashion_extended")
return
end
yl_speak_up.show_fs(player, "fashion")
end
function yl_speak_up.fashion(player, obj)
local luaentity = obj:get_luaentity()
local pname = player:get_player_name()
local npc_id = luaentity.yl_speak_up.id
local skins = luaentity.yl_speak_up.skins
local n_id = "n_" .. npc_id
local t = luaentity.textures
yl_speak_up.speak_to[pname] = {}
yl_speak_up.speak_to[pname].n_id = n_id
yl_speak_up.speak_to[pname].obj = obj
yl_speak_up.speak_to[pname].textures = t
yl_speak_up.speak_to[pname].skins = textures2skin(t)
local dialog = yl_speak_up.load_dialog(n_id, false)
if next(dialog) then
yl_speak_up.speak_to[pname].n_npc = dialog.n_npc
else
yl_speak_up.speak_to[pname].n_npc = "Unknown"
end
yl_speak_up.show_fs(player, "fashion")
end
yl_speak_up.update_nametag = function(self)
if(self.yl_speak_up.hide_nametag) then
self.object:set_nametag_attributes({text=nil})
return
end
if self.yl_speak_up.npc_name then
-- the nametag is normal (cyan by default)
if(self.yl_speak_up.talk) then
self.force_nametag_color = yl_speak_up.nametag_color_when_not_muted
self.object:set_nametag_attributes({color=self.force_nametag_color, text=self.yl_speak_up.npc_name})
-- the nametag has the addition "[muted]" and is magenta when muted
else
self.force_nametag_color = yl_speak_up.nametag_color_when_muted
self.object:set_nametag_attributes({color=self.force_nametag_color, text=self.yl_speak_up.npc_name.." [muted]"})
end
end
end
-- inspired/derived from the wieldview mod in 3darmor
yl_speak_up.get_wield_texture = function(item)
if(not(item) or not(minetest.registered_items[ item ])) then
return "3d_armor_trans.png"
end
local def = minetest.registered_items[ item ]
if(def.inventory_image ~= "") then
return def.inventory_image
elseif(def.tiles and type(def.tiles[1]) == "string" and def.tiles[1] ~= "") then
return minetest.inventorycube(def.tiles[1])
end
return "3d_armor_trans.png"
end
-- this only sets the *skin*, depending on the mesh of the NPC;
-- capes and wielded items are supported by an extended formspec for those
-- NPC that can handle them
yl_speak_up.get_fs_fashion = function(pname)
-- which texture from the textures list are we talking about?
-- this depends on the model!
local mesh = yl_speak_up.get_mesh(pname)
local texture_index = yl_speak_up.mesh_data[mesh].texture_index
if(not(texture_index)) then
texture_index = 1
end
-- which skins are available? this depends on mob_type
local mob_type = yl_speak_up.get_mob_type(pname)
local skins = yl_speak_up.mob_skins[mob_type]
local textures = yl_speak_up.speak_to[pname].textures
local skin = ""
if(textures and textures[texture_index]) then
skin = (textures[texture_index] or "")
end
-- store the old texture so that we can go back to it
local old_texture = yl_speak_up.speak_to[pname].old_texture
if(not(old_texture)) then
yl_speak_up.speak_to[pname].old_texture = skin
old_texture = skin
end
-- fallback if something went wrong
if(not(skins)) then
skins = {old_texture}
end
local button_cancel = "Cancel"
-- is this player editing this particular NPC? then rename the button
if( yl_speak_up.edit_mode[pname]
and yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id) then
button_cancel = "Back"
end
local skin_list = table.concat(skins, ",")
local skin_index = table.indexof(skins, skin)
if(skin_index == -1) then
skin_index = ""
end
local tmp_textures = textures
if(texture_index ~= 1) then
tmp_textures = textures2skin(textures)
end
local preview = yl_speak_up.skin_preview_3d(mesh, tmp_textures, "2,1;6,12", "8,1;6,12")
-- local preview = yl_speak_up.mesh_data[mesh].skin_preview(skin)
local formspec = {
"container[0.5,4.0]",
"dropdown[0.75,14.1;16.25,1.5;set_skin_normal;",
skin_list or "",
";",
tostring(skin_index) or "",
"]",
"label[0.75,13.6;The name of this skin is:]",
"button[0.75,0.75;1.2,12;button_prev_skin;<]",
"button[15.75,0.75;1.2,12;button_next_skin;>]",
"tooltip[button_prev_skin;Select previous skin in list.]",
"tooltip[button_next_skin;Select next skin in list.]",
"tooltip[set_skin_normal;Select a skin from the list.]",
preview,
-- we add a special button for setting the skin in the player answer/reply window
}
if(yl_speak_up.mesh_data[mesh].animation
and yl_speak_up.speak_to[pname]
and yl_speak_up.speak_to[pname].obj) then
local anim_list = {}
for k, v in pairs(yl_speak_up.mesh_data[mesh].animation) do
table.insert(anim_list, k)
end
table.sort(anim_list)
-- which animation is the NPC currently running?
local obj = yl_speak_up.speak_to[pname].obj
local curr_anim = obj:get_animation(pname)
local anim = ""
-- does the current animation match any stored one?
for k, v in pairs(yl_speak_up.mesh_data[mesh].animation) do
if(v.x and v.y and curr_anim and curr_anim.x and curr_anim.y
and v.x == curr_anim.x and v.y == curr_anim.y) then
anim = k
end
end
local anim_index = table.indexof(anim_list, anim)
if(anim_index == -1) then
anim_index = "1"
end
table.insert(formspec, "label[0.75,16.4;Do the following animation:]")
table.insert(formspec, "dropdown[0.75,16.9;16.25,1.5;set_animation;")
table.insert(formspec, table.concat(anim_list, ','))
table.insert(formspec, ";")
table.insert(formspec, tostring(anim_index) or "")
table.insert(formspec, "]")
end
table.insert(formspec, "container_end[]")
local left_window = table.concat(formspec, "")
formspec = {}
local h = -0.8
local button_text = "This shall be your new skin. Wear it proudly!"
if(skin == old_texture) then
button_text = "This is your old skin. It is fine. Keep it!"
end
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"button_store_new_skin",
"The NPC will wear the currently selected skin.",
button_text,
true, nil, nil, nil)
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"button_old_skin",
"The NPC will wear the skin he wore before you started changing it.",
"On a second throught - Keep your old skin. It was fine.",
(skin ~= old_texture), nil, nil, nil)
h = yl_speak_up.add_edit_button_fs_talkdialog(formspec, h,
"button_config_wielded_items",
"What shall the NPC wield, and which cape shall he wear?",
"I'll tell you what you shall wield.",
(yl_speak_up.mesh_data[mesh].can_show_wielded_items),
"You don't know how to show wielded items. Thus, we can't configure them.",
nil, nil)
return yl_speak_up.show_fs_decorated(pname, true, h,
"",
left_window,
table.concat(formspec, ""),
nil,
h)
end

View File

@ -1,93 +0,0 @@
-- this function is called in fs_edit_general.lua when creating preconditions/effects
-- and from fs_manage_variables.lua when the player clicks on a button;
-- input to this formspec is sent to the respective calling functions
-- find out where this variable is used in NPCs
yl_speak_up.fs_get_list_of_usage_of_variable = function(var_name, pname, check_preconditions,
back_button_name, back_button_text, is_internal_var)
-- TODO: check if the player really has read access to this variable
if(not(is_internal_var)) then
var_name = yl_speak_up.restore_complete_var_name(var_name, pname)
end
-- which NPC (might be several) is using this variable?
-- TODO: ..or if the player at least is owner of these NPC or has extended privs
local npc_list = yl_speak_up.get_variable_metadata(var_name, "used_by_npc")
-- list of all relevant preconditions, actions and effects
local res = {}
local count_read = 0
local count_changed = 0
for i, n_id in ipairs(npc_list) do
-- the NPC may not even be loaded
local dialog = yl_speak_up.load_dialog(n_id, false)
if(dialog and dialog.n_dialogs) then
for d_id, d in pairs(dialog.n_dialogs) do
if(d and d.d_options) then
for o_id, o in pairs(d.d_options) do
local p_text = ""
local r_text = ""
local sort_value = 0
if(o and o.o_prerequisites and check_preconditions) then
for p_id, p in pairs(o.o_prerequisites) do
if(p and p.p_type and p.p_type == "state"
and p.p_variable and p.p_variable == var_name) then
p_text = p_text..yl_speak_up.print_as_table_precon(p,pname)
sort_value = (p.p_var_cmp_value or 0)
count_read = count_read + 1
end
end
end
if(o and o.o_results) then
for r_id, r in pairs(o.o_results) do
if(r and r.r_type and r.r_type == "state"
and r.r_variable and r.r_variable == var_name) then
r_text = r_text..yl_speak_up.print_as_table_effect(r,pname)
-- values set in the results are more important than
-- those set in preconditions
sort_value = (r.r_var_cmp_value or 0)
count_changed = count_changed + 1
end
end
end
-- if preconditions or effects apply: show the action as well
if(o and o.actions and (p_text ~= "" or r_text ~= "")) then
for a_id, a in pairs(o.actions) do
-- no need to introduce an a_text; this will follow
-- directly after p_text, and p_text is finished
p_text = p_text..yl_speak_up.print_as_table_action(a, pname)
end
end
yl_speak_up.print_as_table_dialog(p_text, r_text, dialog,
n_id, d_id, o_id, res, o, sort_value)
end
end
end
end
end
local formspec = yl_speak_up.print_as_table_prepare_formspec(res, "table_of_variable_uses",
back_button_name, back_button_text)
table.insert(formspec,
"label[20.0,1.8;"..
minetest.formspec_escape("Variable \""..
minetest.colorize("#FFFF00", tostring(var_name or "- ? -"))..
"\" is used here:").."]")
if(count_read > 0 or count_changed > 0) then
table.insert(formspec,
"label[16.0,31.0;The variable is accessed in "..
minetest.colorize("#FFFF00", tostring(count_read).." pre(C)onditions")..
" and changed in "..
minetest.colorize("#55FF55", tostring(count_changed).." (Ef)fects")..
".]")
elseif(not(is_internal_var)) then
table.insert(formspec,
"button[0.2,30.6;56.6,1.2;delete_unused_variable;"..
minetest.formspec_escape("Delete this unused variable \""..
tostring(var_name or "- ? -")).."\".]")
else
table.insert(formspec,
"label[16.0,31.0;This is an internal variable and cannot be deleted.]")
end
return table.concat(formspec, "\n")
end

View File

@ -1,376 +0,0 @@
-- set name, description and owner of the NPC
-- (owner can only be set if the player has the npc_talk_master
-- priv - not with npc_talk_owner priv alone)
yl_speak_up.input_fs_initial_config = function(player, formname, fields)
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
if(fields.back_from_error_msg) then
-- no point in showing the formspec or error message again if we did so already
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
return
end
-- show this formspec again
yl_speak_up.show_fs(player, "initial_config",
{n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
return
end
if(fields.edit_skin and fields.edit_skin ~= "") then
yl_speak_up.show_fs(player, "fashion")
return
end
if(fields.edit_properties and fields.edit_properties ~= "") then
yl_speak_up.show_fs(player, "properties")
return
end
if((not(fields.save_initial_config)
and not(fields.show_nametag)
and not(fields.list_may_edit)
and not(fields.add_may_edit)
) or (fields and fields.Exit)) then
local dialog = yl_speak_up.speak_to[pname].dialog
-- unconfigured NPC
if(fields and fields.Exit and not(dialog) or not(dialog.n_dialogs)) then
minetest.chat_send_player(pname, "Aborting initial configuration.")
return
end
-- is the player editing the npc? then leaving this config
-- dialog has to lead back to the talk dialog
if(yl_speak_up.edit_mode[pname] == n_id and n_id) then
yl_speak_up.show_fs(player, "talk",
{n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id})
end
-- else we can quit here
return
end
local error_msg = nil
-- remove leading and tailing spaces from the potential new NPC name in order to avoid
-- confusing names where a player's name (or that of another NPC) is beginning/ending
-- with blanks
if(fields.n_npc) then
fields.n_npc = fields.n_npc:match("^%s*(.-)%s*$")
end
local dialog = yl_speak_up.speak_to[pname].dialog
-- the player is trying to save the initial configuration
-- is the player allowed to initialize this npc?
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
error_msg = "You are not allowed to edit this NPC."
elseif(not(fields.n_npc) or string.len(fields.n_npc) < 2) then
error_msg = "The name of your NPC needs to be\nat least two characters long."
elseif(minetest.check_player_privs(fields.n_npc, {interact=true})
and not(minetest.check_player_privs(player, {npc_talk_master=true}))) then
error_msg = "You cannot name your NPC\nafter an existing player."
elseif(not(fields.n_description) or string.len(fields.n_description) < 2) then
error_msg = "Please provide a description of your NPC!"
-- sensible length limit
elseif(string.len(fields.n_npc)>40 or string.len(fields.n_description)>40) then
error_msg = "The name and description of your NPC\ncannot be longer than 40 characters."
-- want to change the owner?
elseif(fields.n_owner and fields.n_owner ~= yl_speak_up.npc_owner[ n_id ]) then
if( not(minetest.check_player_privs(player, {npc_talk_master=true}))) then
error_msg = "You need the \"npc_talk_master\" priv\nin order to change the owner."
elseif(not(minetest.check_player_privs(fields.n_owner, {npc_talk_owner=true}))) then
error_msg = "The NPC can only be owned by players that\n"..
"have the \"npc_talk_owner\" priv. Else the\n"..
"new owner could not edit his own NPC."
end
-- want to change who may edit this npc?
-- delete a player from the list of those allowed to edit the NPC
elseif(fields.delete_may_edit and fields.delete_may_edit ~= ""
and fields.list_may_edit and fields.list_may_edit ~= "") then
if(pname ~= yl_speak_up.npc_owner[ n_id ]) then
error_msg = "Only the owner of the NPC\ncan change this."
elseif(not(dialog)) then
error_msg = "Please set a name for your NPC first!"
else
-- actually delete the player from the list
dialog.n_may_edit[ fields.list_may_edit ] = nil
-- show the next entry
yl_speak_up.speak_to[pname].tmp_index = math.max(1,
yl_speak_up.speak_to[pname].tmp_index-1)
end
-- add a player who may edit this NPC
elseif(fields.add_may_edit and fields.add_may_edit ~= "") then
if(pname ~= yl_speak_up.npc_owner[ n_id ]) then
error_msg = "Only the owner of the NPC\ncan change this."
elseif(fields.add_may_edit == pname) then
error_msg = "You are already the owner of this NPC!\nNo need to add you extra here."
elseif(not(minetest.check_player_privs(fields.add_may_edit, {interact=true}))) then
error_msg = "Player \""..minetest.formspec_escape(fields.add_may_edit)..
"\" not found."
elseif(not(dialog)) then
error_msg = "Please set a name for the NPC first!"
else
if(not(dialog.n_may_edit)) then
dialog.n_may_edit = {}
end
dialog.n_may_edit[ fields.add_may_edit ] = true
-- jump to the index with this player so that the player sees that he has been added
local tmp_list = yl_speak_up.sort_keys(dialog.n_may_edit, true)
local index = table.indexof(tmp_list, fields.add_may_edit)
if(index and index > 0) then
-- "Add player:" is added before all other names, so +1
yl_speak_up.speak_to[pname].tmp_index = index + 1
end
end
-- selected a player name in the why may edit this NPC dropdown?
elseif(fields.list_may_edit and fields.list_may_edit ~= "") then
local tmp_list = yl_speak_up.sort_keys(dialog.n_may_edit, true)
local index = table.indexof(tmp_list, fields.list_may_edit)
if(fields.list_may_edit == "Add player:") then
index = 0
end
if(index and index > -1) then
yl_speak_up.speak_to[pname].tmp_index = index + 1
end
end
if(error_msg) then
yl_speak_up.show_fs(player, "msg", { input_to = "yl_speak_up:initial_config",
formspec = "size[6,2]"..
"label[0.2,0.0;"..tostring(error_msg).."]"..
"button[2,1.5;1,0.9;back_from_error_msg;Back]"})
return
end
-- warn players with npc_talk_master priv if the name of an npc is used by a player already
if(minetest.check_player_privs(fields.n_npc, {interact=true})) then
minetest.chat_send_player(pname, "WARNING: A player named \'"..tostring(fields.n_npc)..
"\' exists. This NPC got assigned the same name!")
end
local d_id = yl_speak_up.speak_to[pname].d_id
local count = 0
if(dialog and dialog.n_dialogs) then
for k,v in pairs(dialog.n_dialogs) do
count = count + 1
end
end
-- we checked earlier if the player doing this change and the
-- player getting the NPC have appropriate privs
if(fields.n_owner ~= yl_speak_up.npc_owner[ n_id ]) then
yl_speak_up.log_change(pname, n_id,
"Owner changed from "..tostring(yl_speak_up.npc_owner[ n_id ])..
" to "..tostring(fields.n_owner).." for "..
"NPC name: \""..tostring(fields.n_npc))
-- the owner will actually be changed further down, at the end of this function
yl_speak_up.npc_owner[ n_id ] = fields.n_owner
end
-- give the NPC its first dialog
if(not(dialog) or count==0) then
local f = {}
-- create a new dialog
f.d_id = yl_speak_up.text_new_dialog_id
-- ...with this text
f.d_text = "$GOOD_DAY$ $PLAYER_NAME$,\n"..
"I am $NPC_NAME$. I don't know much yet.\n"..
"Hopefully $OWNER_NAME$ will teach me to talk soon."
-- it is the first, initial dialog
f.d_sort = "0"
f.n_npc = fields.n_npc
f.n_description = fields.n_description
f.npc_owner = yl_speak_up.npc_owner[ n_id ]
-- create and save the first dialog for this npc
local dialog = yl_speak_up.fields_to_dialog(pname, f)
-- add example answers and dialogs; they may be irritating for experienced
-- users, but help new users a lot in figuring out how the system works
--
-- first, we create three example dialogs as such:
-- (the first one has already been created in the creation of the dialog;
-- yl_speak_up.fields_to_dialog stores d_id in yl_speak_up.speak_to[pname].d_id)
local new_d_1 = "d_1"
local new_d_2 = yl_speak_up.add_new_dialog(dialog, pname, "2",
"I'm glad that you wish me to talk to you.\n\n"..
"I hope we can talk more soon!")
local new_d_3 = yl_speak_up.add_new_dialog(dialog, pname, "3",
"Oh! Do you really think so?\n\n"..
"To me, talking is important.")
-- we are dealing with a new NPC, so there are no options yet
-- options added to dialog f.d_id:
local new_d_1_o_1 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_1", "I hope so as well!", new_d_2)
local new_d_1_o_2 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_1", "No. Talking is overrated.", new_d_3)
-- options added to dialog new_d_2:
local new_d_2_o_1 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_2", "Glad that we agree.", new_d_1)
-- options added to dialog new_d_3:
local new_d_3_o_1 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_3", "No. I was just joking. I want to talk to you!", new_d_2)
local new_d_3_o_2 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_3", "Yes. Why am I talking to you anyway?", "d_end")
-- save our new dialog
yl_speak_up.save_dialog(n_id, dialog)
yl_speak_up.speak_to[pname].dialog = dialog
dialog.n_may_edit = {}
-- now connect the dialogs via results
yl_speak_up.log_change(pname, n_id,
"Initial config saved. "..
"NPC name: \""..tostring(fields.n_npc)..
"\" Description: \""..tostring(fields.n_description).."\".")
elseif(fields.add_may_edit and fields.add_may_edit ~= "") then
yl_speak_up.save_dialog(n_id, dialog)
yl_speak_up.log_change(pname, n_id,
"Added to \"may be edited by\": "..tostring(fields.add_may_edit))
elseif(fields.delete_may_edit and fields.delete_may_edit ~= ""
and fields.list_may_edit and fields.list_may_edit ~= "") then
yl_speak_up.save_dialog(n_id, dialog)
yl_speak_up.log_change(pname, n_id,
"Removed from \"may be edited by\": "..tostring(fields.list_may_edit))
-- just change name and description
elseif((fields.n_npc and fields.n_npc ~= "")
or (fields.n_description and fields.n_description ~= "")) then
dialog = yl_speak_up.speak_to[pname].dialog
if(dialog.n_npc ~= fields.n_npc
or dialog.n_description ~= fields.n_description) then
dialog.n_npc = fields.n_npc
dialog.n_description = fields.n_description
yl_speak_up.save_dialog(n_id, dialog)
yl_speak_up.log_change(pname, n_id,
"Name and/or description changed. "..
"NPC name: \""..tostring(fields.n_npc)..
"\" Description: \""..tostring(fields.n_description)..
"\" May be edited by: \""..
table.concat(yl_speak_up.sort_keys(dialog.n_may_edit, true), " ").."\".")
end
end
dialog = yl_speak_up.speak_to[pname].dialog
-- show nametag etc.
if yl_speak_up.speak_to[pname].obj then
local obj = yl_speak_up.speak_to[pname].obj
local ent = obj:get_luaentity()
if ent ~= nil then
if(fields.show_nametag) then
local new_nametag_state = "- UNDEFINED -"
if(fields.show_nametag == "false") then
ent.yl_speak_up.hide_nametag = true
dialog.hide_nametag = true
new_nametag_state = "HIDE"
-- update_nametag else will only work on reload
obj:set_nametag_attributes({text=""})
elseif(fields.show_nametag == "true") then
ent.yl_speak_up.hide_nametag = nil
dialog.hide_nametag = nil
new_nametag_state = "SHOW"
end
yl_speak_up.save_dialog(n_id, dialog)
yl_speak_up.log_change(pname, n_id,
tostring(new_nametag_state).." nametag.")
minetest.chat_send_player(pname,
tostring(dialog.n_npc)..": I will "..
tostring(new_nametag_state).." my nametag.")
end
ent.yl_speak_up.npc_name = dialog.n_npc
ent.yl_speak_up.npc_description = dialog.n_description
ent.owner = yl_speak_up.npc_owner[ n_id ] or dialog.npc_owner
local i_text = dialog.n_npc .. "\n" ..
dialog.n_description .. "\n" ..
yl_speak_up.infotext
obj:set_properties({infotext = i_text})
yl_speak_up.update_nametag(ent)
end
end
if(not(fields.save_initial_config)) then
yl_speak_up.show_fs(player, "initial_config",
{n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
return
end
if((fields.add_may_edit and fields.add_may_edit ~= "")
or (fields.delete_may_edit and fields.delete_may_edit ~= "")) then
-- show this formspec again
yl_speak_up.show_fs(player, "initial_config",
{n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
else
-- actually start a chat with our new npc
yl_speak_up.show_fs(player, "talk", {n_id = n_id, d_id = d_id})
end
end
-- initialize the npc without having to use a staff;
-- returns true when initialization possible
yl_speak_up.get_fs_initial_config = function(player, n_id, d_id, is_initial_config)
local pname = player:get_player_name()
-- is the player allowed to edit this npc?
if(not(yl_speak_up.may_edit_npc(player, n_id))) then
return "size[6,2]"..
"label[0.2,0.0;Sorry. You are not authorized\nto edit this NPC.]"..
"button_exit[2,1.5;1,0.9;back_from_error_msg;Exit]"
end
local tmp_show_nametag = "true"
local tmp_name = n_id
local tmp_descr = "A new NPC without description"
local tmp_text = "Please provide your new NPC with a name and description!"
local tmp_owner = (yl_speak_up.npc_owner[ n_id ] or "- none -")
local table_of_names = {}
local may_be_edited_by = ""
-- use existing name and description as presets when just editing
if(not(is_initial_config)) then
local dialog = yl_speak_up.speak_to[pname].dialog
tmp_show_nametag = not(dialog.hide_nametag)
tmp_name = (dialog.n_npc or tmp_name)
tmp_descr = (dialog.n_description or tmp_descr)
tmp_text = "You can change the name and description of your NPC."
-- dialog.n_may_edit was a string for a short time in development
if(not(dialog.n_may_edit) or type(dialog.n_may_edit) ~= "table") then
dialog.n_may_edit = {}
end
table_of_names = dialog.n_may_edit
may_be_edited_by =
-- who can edit this NPC?
"label[0.2,4.75;May be edited by:]"..
-- offer a dropdown list and a text input field for player names for adding
yl_speak_up.create_dropdown_playerlist(player, pname,
table_of_names, yl_speak_up.speak_to[pname].tmp_index,
3.0, 4.3, 0.0, 1.0, "list_may_edit", "player",
"Remove selected\nplayer from list",
"add_may_edit",
"Enter the name of the player whom you\n"..
"want to grant the right to edit your NPC.\n"..
"The player needs at least the npc_talk_owner priv\n"..
"in order to actually edit the NPC.\n"..
"Click on \"Save\" to add the new player.",
"delete_may_edit",
"If you click here, the player will no\n"..
"longer be able to edit your NPC."
)
end
local formspec = "size[11,8.0]"..
"label[0.2,0.5;"..tmp_text.."]"..
-- name of the npc
"checkbox[2.2,0.9;show_nametag;;"..tostring(tmp_show_nametag).."]"..
"label[2.7,0.9;Show nametag]"..
"label[0.2,1.65;Name:]"..
"field[2.2,1.2;4,0.9;n_npc;;"..minetest.formspec_escape(tmp_name).."]"..
"label[7.0,1.65;NPC ID: "..minetest.colorize("#FFFF00",tostring(n_id)).."]"..
"tooltip[n_npc;n_npc: The name of the NPC;#FFFFFF;#000000]"..
-- description of the npc
"label[0.2,2.65;Description:]"..
"field[2.2,2.2;8,0.9;n_description;;"..minetest.formspec_escape(tmp_descr).."]"..
"tooltip[n_description;n_description: A description for the NPC;#FFFFFF;#000000]"..
-- the owner of the NPC
"label[0.2,3.65;Owner:]"..
"field[2.2,3.2;8,0.9;n_owner;;"..minetest.formspec_escape(tmp_owner).."]"..
"tooltip[n_owner;The owner of the NPC. This can only be changed\n"..
"if you have the npc_talk_master priv.;#FFFFFF;#000000]"..
may_be_edited_by..
"button[1.0,5.5;4,0.9;edit_skin;Edit Skin]"..
"button[6.0,5.5;4,0.9;edit_properties;Edit Properties]"..
-- save and exit buttons
"button[3.2,7.0;2,0.9;save_initial_config;Save]"..
"button_exit[5.4,7.0;2,0.9;exit;Exit]"
-- show the formspec to the player
return formspec
end

View File

@ -1,88 +0,0 @@
-- helper functions for yl_speak_up.input_fs_manage_quests(..)
-- returns the index of the new quest
yl_speak_up.input_fs_manage_quests_add_new_entry = function(pname, entry_name)
local res = yl_speak_up.add_quest(pname, entry_name,
"Name of your quest",
"Enter a longer description here for describing the quest "..
"to players who search for one.",
"Enter a short description here describing what the quest is about.",
"Room for comments/notes")
-- TODO: might make sense to show the error message somewhere
if(res ~= "OK") then
return -1
end
local quest_list = yl_speak_up.get_sorted_quest_list(pname)
return table.indexof(quest_list, entry_name)
end
-- helper functions for yl_speak_up.input_fs_manage_quests(..)
-- returns a text describing if deleting the quest worked
yl_speak_up.input_fs_manage_quests_del_old_entry = function(pname, entry_name)
return "NOT IMPLEMENTED YET"
-- delete (empty) variable
-- return yl_speak_up.del_quest_variable(pname, entry_name, nil)
end
-- helper functions for yl_speak_up.input_fs_manage_quests(..)
-- implements all the functions that are specific to managing quests and not part of
-- general item management
yl_speak_up.input_fs_manage_quests_check_fields = function(player, formname, fields, quest_name, list_of_entries)
local pname = player:get_player_name()
if(not(quest_name)) then
quest_name = ""
end
if(fields and fields.show_variable) then
yl_speak_up.show_fs(player, "manage_variables", quest_name)
return
end
-- this function didn't have anything to do
return "NOTHING FOUND"
end
-- makes use of yl_speak_up.input_fs_manage_general and is thus pretty short
yl_speak_up.input_fs_manage_quests = function(player, formname, fields)
local pname = player:get_player_name()
local quest_list = yl_speak_up.get_sorted_quest_list(pname)
local res = yl_speak_up.input_fs_manage_general(player, formname, fields,
-- what_is_the_list_about, min_length, max_length, function_add_new_entry,
"quest", 2, 80,
yl_speak_up.input_fs_manage_quests_add_new_entry,
quest_list,
yl_speak_up.input_fs_manage_quests_del_old_entry,
yl_speak_up.input_fs_manage_quests_check_fields)
return true
end
yl_speak_up.get_fs_manage_quests = function(player, param)
local pname = player:get_player_name()
local quest_list = yl_speak_up.get_sorted_quest_list(pname)
local formspec = {}
if(param and param ~= "") then
local index = table.indexof(quest_list, param)
yl_speak_up.speak_to[pname].tmp_index_general = index + 1
end
table.insert(formspec, "size[18,12]"..
"label[0.2,1.2;A quest is a linear sequence of quest steps. Quests can "..
"depend on and influence other quests.\n"..
"Progress for each player is stored in a variable. The name of "..
"that variable cannot be changed after creation.]")
local selected = yl_speak_up.get_fs_manage_general(player, param,
formspec, quest_list,
"Create quest",
"Create a new varialbe with the name\n"..
"you entered in the field to the left.",
"quest",
"Enter the name of the new quest you want to create.\n"..
"You can't change this name afterwards. But you *can*\n"..
"add and change a human readable description later on.",
"If you click here, the selected quest will be deleted.\n"..
"This will only be possible if it's not used anywhere.")
if(selected and selected ~= "") then
local k = selected
-- index 1 is "Add variable:"
table.insert(formspec, "button[12,2.15;4.5,0.6;show_variable;Show and edit this variable]")
end
return table.concat(formspec, "")
end

View File

@ -1,471 +0,0 @@
-- helper functions for yl_speak_up.input_fs_manage_variables(..)
-- returns the index of the new variable
yl_speak_up.input_fs_manage_variables_add_new_entry = function(pname, entry_name)
local res = yl_speak_up.add_quest_variable(pname, entry_name)
if(not(res)) then
return -1
end
local var_list = yl_speak_up.get_quest_variables(pname, true)
-- make names of own variables shorter
yl_speak_up.strip_pname_from_varlist(var_list, pname)
table.sort(var_list)
return table.indexof(var_list, entry_name)
end
-- helper functions for yl_speak_up.input_fs_manage_variables(..)
-- returns a text describing if deleting the variable worked
yl_speak_up.input_fs_manage_variables_del_old_entry = function(pname, entry_name)
-- delete (empty) variable
return yl_speak_up.del_quest_variable(pname, entry_name, nil)
end
-- helper functions for yl_speak_up.input_fs_manage_variables(..)
-- implements all the functions that are specific to managing variables and not part of
-- general item management
yl_speak_up.input_fs_manage_variables_check_fields = function(player, formname, fields, var_name, list_of_entries)
local pname = player:get_player_name()
if(not(var_name)) then
var_name = ""
end
local var_name_with_prefix = yl_speak_up.restore_complete_var_name(var_name, pname)
-- show all stored values for a variable in a table
if(fields and fields.show_stored_values_for_var and var_name) then
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = yl_speak_up.fs_show_all_var_values(player, pname, var_name)
})
return
-- show details about a quest (if it is a quest variable)
elseif(fields and fields.show_quest) then
yl_speak_up.show_fs(player, "manage_quests", var_name)
return
-- show where this variable is used
elseif(fields and fields.show_var_usage and fields.show_var_usage ~= "") then
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = yl_speak_up.fs_get_list_of_usage_of_variable(
fields.list_of_entries, pname, true,
"back_from_msg",
"Back to manage variables",
-- not an internal variable
false)
})
return
-- enable, disable and list variables in debug mode
elseif(fields and fields.enable_debug_mode and var_name) then
yl_speak_up.set_variable_metadata(var_name, pname, "debug", pname, true)
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = "size[10,2]"..
"label[0.2,0.0;Activating debug mode for variable \""..
minetest.colorize("#FFFF00",
minetest.formspec_escape(tostring(var_name)))..
"\".\nYou will now receive a chat message whenever the "..
"variable changes.]"..
"button[1.5,1.5;2,0.9;back_from_msg;Back]"})
return
elseif(fields and fields.disable_debug_mode and var_name) then
yl_speak_up.set_variable_metadata(var_name, pname, "debug", pname, nil)
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = "size[10,2]"..
"label[0.2,0.0;Deactivating debug mode for variable \""..
minetest.colorize("#FFFF00",
minetest.formspec_escape(tostring(var_name)))..
"\".\nYou will no longer receive a chat message whenever the "..
"variable changes.]"..
"button[1.5,1.5;2,0.9;back_from_msg;Back]"})
return
elseif(fields and fields.list_debug_mode) then
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = "size[10,6]"..
"label[0.2,0.0;You are currently receiving debug information for the "..
"following variables:]"..
"tablecolumns[text]"..
"table[0.8,0.8;8.8,4.0;list_of_variables_in_debug_mode;"..
-- the table entries will already be formspec_escaped
table.concat(yl_speak_up.get_list_of_debugged_variables(pname), ",").."]"..
"button[1.5,5.5;2,0.9;back_from_msg;Back]"})
return
-- a player name was given; the value for that player shall be shown
elseif(fields and fields.show_stored_value_for_player and var_name
and fields.stored_value_for_player and fields.stored_value_for_player ~= "") then
yl_speak_up.show_fs(player, "manage_variables", fields.stored_value_for_player)
return
-- change the value for a player (possibly to nil)
elseif(fields and fields.store_new_value_for_player and var_name
and fields.stored_value_for_player and fields.stored_value_for_player ~= "") then
local old_value = yl_speak_up.get_quest_variable_value(
fields.stored_value_for_player, var_name_with_prefix)
yl_speak_up.set_quest_variable_value(fields.stored_value_for_player, var_name_with_prefix,
fields.current_value_for_player)
local new_value = yl_speak_up.get_quest_variable_value(
fields.stored_value_for_player, var_name_with_prefix)
local success_msg = minetest.colorize("#00FF00", "Successfully set variable")
if(new_value ~= fields.current_value_for_player) then
success_msg = minetest.colorize("#FF0000", "FAILED TO set variable")
end
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = "size[10,2.5]"..
"label[0.2,0.0;"..success_msg.." \""..
minetest.colorize("#FFFF00",
minetest.formspec_escape(tostring(var_name)))..
"\"\nfor player "..
minetest.formspec_escape(fields.stored_value_for_player)..
"\n(old value: "..
minetest.colorize("#AAAAAA", old_value)..
")\nto new value "..
minetest.colorize("#FFFF00", fields.current_value_for_player)..".]"..
"button[1.5,2.0;2,0.9;back_from_msg;Back]"})
return
-- remove the value for a player (set to nil)
elseif(fields and fields.unset_value_for_player and var_name
and fields.stored_value_for_player and fields.stored_value_for_player ~= "") then
local old_value = yl_speak_up.get_quest_variable_value(
fields.stored_value_for_player, var_name_with_prefix)
yl_speak_up.set_quest_variable_value(fields.stored_value_for_player, var_name_with_prefix, nil)
local new_value = yl_speak_up.get_quest_variable_value(
fields.stored_value_for_player, var_name_with_prefix)
local success_msg = minetest.colorize("#00FF00", "Unset variable")
if(new_value) then
success_msg = minetest.colorize("#FF0000", "FAILED TO unset variable")
end
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = "size[10,2]"..
"label[0.2,0.0;"..success_msg.." \""..
minetest.colorize("#FFFF00",
minetest.formspec_escape(tostring(var_name)))..
"\"\nfor player "..
minetest.formspec_escape(fields.stored_value_for_player)..
"\n(old value: "..
minetest.colorize("#AAAAAA", old_value)..
").]"..
"button[1.5,1.5;2,0.9;back_from_msg;Back]"})
return
-- revoke read or write access to a variable
elseif(fields
and ((fields.revoke_player_var_read_access and fields.revoke_player_var_read_access ~= "")
or (fields.revoke_player_var_write_access and fields.revoke_player_var_write_access ~= ""))
and var_name) then
local right = "read"
if(fields.revoke_player_var_write_access and fields.revoke_player_var_write_access ~= "") then
right = "write"
end
-- which player are we talking about?
local selected = yl_speak_up.speak_to[pname]["tmp_index_var_"..right.."_access"]
local pl_with_access = yl_speak_up.get_access_list_for_var(var_name, pname, right.."_access")
local tmp_list = {}
for k, v in pairs(pl_with_access) do
table.insert(tmp_list, k)
end
table.sort(tmp_list)
local grant_to = ""
if(selected > 1) then
grant_to = tmp_list[ selected-1 ]
end
local error_msg = ""
local pl_with_access = yl_speak_up.get_access_list_for_var(var_name, pname, right.."_access")
if(not(grant_to) or grant_to == "") then
error_msg = "For which player do you want to revoke "..right.." access?"
elseif(pname ~= yl_speak_up.npc_owner[ n_id ]
and not(minetest.check_player_privs(pname, {npc_talk_master=true}))) then
error_msg = "Only the owner of the NPC or players with\n"..
"the npc_talk_master priv can change this."
elseif(not(pl_with_access[ grant_to ])) then
error_msg = minetest.formspec_escape(grant_to).." doesn't have "..right..
" access\nto this variable. Nothing changed."
-- try to revoke access
elseif(not(yl_speak_up.manage_access_to_quest_variable(var_name, pname, grant_to,
right.."_access", nil))) then
error_msg = "An internal error occoured."
else
-- not really an error message here...rather a success message
error_msg = "Revoked "..right.." access to variable\n\""..
minetest.formspec_escape(var_name)..
"\"\nfor player "..minetest.formspec_escape(grant_to)..".\n"..
"Note: This will *not* affect existing preconditions/effects!"
end
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = "size[6,2]"..
"label[0.2,0.0;"..error_msg.."]"..
"button[1.5,1.5;2,0.9;back_from_msg;Back]"})
return
-- grant read or write access to a variable
elseif(fields
and ((fields.grant_player_var_read_access and fields.grant_player_var_read_access ~= "")
or (fields.grant_player_var_write_access and fields.grant_player_var_write_access ~= ""))
and var_name) then
local right = "read"
if(fields.grant_player_var_write_access and fields.grant_player_var_write_access ~= "") then
right = "write"
end
local grant_to = fields[ "grant_player_var_"..right.."_access"]
local error_msg = ""
local pl_with_access = yl_speak_up.get_access_list_for_var(var_name, pname, right.."_access")
if(pname ~= yl_speak_up.npc_owner[ n_id ]
and not(minetest.check_player_privs(pname, {npc_talk_master=true}))) then
error_msg = "Only the owner of the NPC or players with\n"..
"the npc_talk_master priv can change this."
elseif(grant_to == pname) then
error_msg = "You already have "..right.." access to this variable."
elseif(pl_with_access[ grant_to ]) then
error_msg = minetest.formspec_escape(grant_to).." already has "..right..
" access\nto this variable."
elseif(not(minetest.check_player_privs(grant_to, {interact=true}))) then
error_msg = "Player \""..minetest.formspec_escape(grant_to).."\" not found."
-- try to grant access
elseif(not(yl_speak_up.manage_access_to_quest_variable(var_name, pname, grant_to,
right.."_access", true))) then
error_msg = "An internal error occoured."
else
-- not really an error message here...rather a success message
error_msg = "Granted "..right.." access to variable\n\""..
minetest.formspec_escape(var_name)..
"\"\nto player "..minetest.formspec_escape(grant_to).."."
end
yl_speak_up.show_fs(player, "msg", {
input_to = "yl_speak_up:manage_variables",
formspec = "size[6,2]"..
"label[0.2,0.0;"..error_msg.."]"..
"button[1.5,1.5;2,0.9;back_from_msg;Back]"})
return
-- the player clicked on a name in the list of players with read or write access
elseif(fields
and ((fields.list_var_read_access and fields.list_var_read_access ~= "")
or (fields.list_var_write_access and fields.list_var_write_access ~= ""))
and var_name) then
local right = "read"
if(fields.list_var_write_access and fields.list_var_write_access ~= "") then
right = "write"
end
local selected = fields[ "list_var_"..right.."_access"]
local pl_with_access = yl_speak_up.get_access_list_for_var(var_name, pname, right.."_access")
local tmp_list = {}
for k, v in pairs(pl_with_access) do
table.insert(tmp_list, k)
end
table.sort(tmp_list)
local index = table.indexof(tmp_list, selected)
if(selected == "Add player:") then
index = 0
end
if(index and index > -1) then
yl_speak_up.speak_to[pname]["tmp_index_var_"..right.."_access"] = index + 1
end
yl_speak_up.show_fs(player, "manage_variables")
return
end
-- this function didn't have anything to do
return "NOTHING FOUND"
end
-- makes use of yl_speak_up.input_fs_manage_general and is thus pretty short
yl_speak_up.input_fs_manage_variables = function(player, formname, fields)
local pname = player:get_player_name()
local list_of_entries = yl_speak_up.get_quest_variables(pname, true)
-- make names of own variables shorter
yl_speak_up.strip_pname_from_varlist(list_of_entries, pname)
table.sort(list_of_entries)
local res = yl_speak_up.input_fs_manage_general(player, formname, fields,
-- what_is_the_list_about, min_length, max_length, function_add_new_entry,
"variable", 2, 30,
yl_speak_up.input_fs_manage_variables_add_new_entry,
list_of_entries,
yl_speak_up.input_fs_manage_variables_del_old_entry,
yl_speak_up.input_fs_manage_variables_check_fields)
end
yl_speak_up.get_fs_manage_variables = function(player, param)
local pname = player:get_player_name()
-- variables owned by the player - including those with write access
local var_list = yl_speak_up.get_quest_variables(pname, true)
-- make names of own variables shorter
yl_speak_up.strip_pname_from_varlist(var_list, pname)
local formspec = {}
if(param and param ~= "") then
local index = table.indexof(var_list, param)
yl_speak_up.speak_to[pname].tmp_index_general = index + 1
end
table.insert(formspec, "size[18,12]"..
"label[0.2,1.2;Note: Each variable will store a diffrent value for each "..
"player who interacts with the NPC.\n"..
"You can grant read and write access to other players for your "..
"variables so that they can also use them as well.]")
local selected = yl_speak_up.get_fs_manage_general(player, param,
formspec, var_list,
"Create variable",
"Create a new varialbe with the name\n"..
"you entered in the field to the left.",
"variable",
"Enter the name of the new variable you\n"..
"want to create.",
"If you click here, the selected variable\n"..
"will be deleted.")
if(selected and selected ~= "") then
local k = selected
-- index 1 is "Add variable:"
local pl_with_read_access = yl_speak_up.get_access_list_for_var(k, pname, "read_access")
local pl_with_write_access = yl_speak_up.get_access_list_for_var(k, pname, "write_access")
if(not(yl_speak_up.speak_to[pname].tmp_index_var_read_access)
or yl_speak_up.speak_to[pname].tmp_index_var_read_access == 1) then
yl_speak_up.speak_to[pname].tmp_index_var_read_access = 1
table.insert(formspec, "button[14.6,2.95;1.0,0.6;add_read_access;Add]"..
"tooltip[add_read_access;Grant the player whose name you entered\n"..
"you entered in the field to the left read access\n"..
"to your variable.]")
end
if(not(yl_speak_up.speak_to[pname].tmp_index_var_write_access)
or yl_speak_up.speak_to[pname].tmp_index_var_write_access == 1) then
yl_speak_up.speak_to[pname].tmp_index_var_write_access = 1
table.insert(formspec, "button[14.6,3.95;1.0,0.6;add_write_access;Add]"..
"tooltip[add_write_access;Grant the player whose name you entered\n"..
"you entered in the field to the left *write* access\n"..
"to your variable.]")
end
local list_of_npc_users = "- none -"
local list_of_node_pos_users = "- none -"
-- expand name of variable k again
local k_long = yl_speak_up.add_pname_to_var(k, pname)
-- which npc and which node_pos use this variable? create a list for display
local c1 = 0
local c2 = 0
if(k_long
and yl_speak_up.player_vars[ k_long ]
and yl_speak_up.player_vars[ k_long ][ "$META$"]) then
local npc_users = yl_speak_up.get_variable_metadata(k_long, "used_by_npc")
c1 = #npc_users
if(npc_users and c1 > 0) then
list_of_npc_users = minetest.formspec_escape(table.concat(npc_users, ", "))
end
local node_pos_users = yl_speak_up.get_variable_metadata(k_long, "used_by_node_pos")
c2 = #node_pos_users
if(node_pos_users and c2 > 0) then
list_of_node_pos_users = minetest.formspec_escape(table.concat(
node_pos_users, ", "))
end
end
table.insert(formspec, "button[10.0,10.05;4.0,0.6;list_debug_mode;What am I debugging?]"..
"tooltip[list_debug_mode;"..
"Show for which variables you currently have "..
"\nactivated the debug mode.]")
local debuggers = yl_speak_up.get_variable_metadata(k_long, "debug")
local i = table.indexof(debuggers, pname)
if(i and i > 0) then
table.insert(formspec,
"button[5.0,10.05;4.0,0.6;disable_debug_mode;Deactivate debug mode]"..
"tooltip[disable_debug_mode;"..
"You will no longer receive a chat message "..
"\nwhen this value changes for a player.]")
else
table.insert(formspec,
"button[5.0,10.05;4.0,0.6;enable_debug_mode;Activate debug mode]"..
"tooltip[enable_debug_mode;"..
"You will receive a chat message whenever the value "..
"\nof this variable changes for one player. The debug\n"..
"messages will be sent even after relogin.]")
end
-- checking/changing debug value for one specific player
if(not(param)) then
param = ""
end
table.insert(formspec,
"label[0.2,8.05;Show stored value for player:]"..
"field[4.9,7.75;4.0,0.6;stored_value_for_player;;")
table.insert(formspec, minetest.formspec_escape(param))
table.insert(formspec, "]")
table.insert(formspec,
"button[9.0,7.75;4.5,0.6;show_stored_value_for_player;Show value for this player]"..
"tooltip[stored_value_for_player;Enter the name of the player for which you\n"..
"want to check (or change) the stored value.]"..
"tooltip[show_stored_value_for_player;Click here to read and the current value"..
"\nstored for this player.]")
if(param and param ~= "") then
local v = yl_speak_up.get_quest_variable_value(param, k_long) or ""
table.insert(formspec,
"label[0.2,9.05;Found stored value:]"..
"field[4.9,8.75;4.0,0.6;current_value_for_player;;")
table.insert(formspec, minetest.formspec_escape(v))
table.insert(formspec, "]"..
"tooltip[current_value_for_player;You can see and change the current "..
"value here.]"..
"button[9.0,8.75;4.5,0.6;store_new_value_for_player;"..
"Store this as new value]"..
"tooltip[store_new_value_for_player;"..
"Click here to update the stored value for this player."..
"\nWARNING: Be very careful here and never do this without"..
"\n informing the player about this change!]"..
"button[13.9,8.75;3.0,0.6;unset_value_for_player;"..
"Remove this entry]"..
"tooltip[unset_value_for_player;Click here to delete the entry for this "..
"player.\nSetting the entry to an empty string would not be "..
"the same!]")
end
table.insert(formspec, "button[12.2,2.15;3.0,0.6;show_var_usage;Where is it used?]"..
"tooltip[show_var_usage;Show which NPC use this variable in which context.]"..
-- offer a dropdown list and a text input field for new varialbe names for adding
"label[0.2,3.25;Players with read access to this variable:]")
table.insert(formspec, yl_speak_up.create_dropdown_playerlist(player, pname,
pl_with_read_access,
yl_speak_up.speak_to[pname].tmp_index_var_read_access,
6.9, 2.95, 0.0, 0.6, "list_var_read_access", "player",
"Remove player from list",
"grant_player_var_read_access",
"Enter the name of the player that shall\n"..
"have read access to this variable.",
"revoke_player_var_read_access",
"If you click here, the selected player\n"..
"will no longer be able to add new\n"..
"pre(C)onditions which read your variable."))
table.insert(formspec, "label[0.2,4.25;Players with *write* access to this variable:]")
table.insert(formspec, yl_speak_up.create_dropdown_playerlist(player, pname,
pl_with_write_access,
yl_speak_up.speak_to[pname].tmp_index_var_write_access,
6.9, 3.95, 0.0, 0.6,
"list_var_write_access", "player", "Remove player from list",
"grant_player_var_write_access",
"Enter the name of the player that shall\n"..
"have *write* access to this variable.",
"revoke_player_var_write_access",
"If you click here, the selected player\n"..
"will no longer be able to *write* new\n"..
"values into this variable."))
local var_type = (yl_speak_up.get_variable_metadata(k_long, "var_type")
or "String/text or numerical value, depending on how you use it")
table.insert(formspec, "label[0.2,5.05;Type of variable: ")
table.insert(formspec, minetest.colorize("#FFFF00", var_type)) -- show variable type
table.insert(formspec, ".]")
if(var_type == "quest") then
table.insert(formspec, "button[4.2,4.75;4.5,0.6;show_quest;Show and edit this quest]")
end
table.insert(formspec, "label[0.2,6.05;This variable is used by the following ")
table.insert(formspec,
minetest.colorize("#FFFF00", tostring(c1)).." NPC:\n\t"..
-- those are only n_id - no need to formspec_escape that
minetest.colorize("#FFFF00", list_of_npc_users))
table.insert(formspec, ".]")
table.insert(formspec, "label[0.2,7.05;This variable is used by the following ")
table.insert(formspec,
minetest.colorize("#FFFF00", tostring(c2)).." node positions:\n\t"..
-- those are only pos_to_string(..) - no need to formspec_escape that
minetest.colorize("#FFFF00", list_of_node_pos_users))
table.insert(formspec, ".]")
table.insert(formspec,
"button[0.2,10.05;4.0,0.6;show_stored_values_for_var;Show all stored values]"..
"tooltip[show_stored_values_for_var;A diffrent value can be stored for each "..
"player.\nShow these values in a table.]")
end
return table.concat(formspec, "")
end

View File

@ -1,221 +0,0 @@
-- Properties for NPC --
-- This is used when an NPC doesn't have a specific dialog but still wants to
-- make use of a (or some) generic dialog(es)
-- helper function:
-- get one property value of the NPC
yl_speak_up.get_one_npc_property = function(pname, property_name)
if(not(pname)) then
return nil
end
-- get just the property data
return yl_speak_up.get_npc_properties(pname, false)[property_name]
end
-- helper function;
-- adds "normal" properties of the npc with a self.<property_name> prefix as well
-- if long_version is not set, a table containing all properties is returned;
-- if long_version *is* set, a table containing the table above plus additional entries is returned
yl_speak_up.get_npc_properties_long_version = function(pname, long_version)
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return {}
end
local obj = yl_speak_up.speak_to[pname].obj
if(not(obj)) then
return {}
end
local entity = obj:get_luaentity()
if(not(entity)) then
return {}
end
if(not(entity.yl_speak_up)) then
return {}
end
local properties = entity.yl_speak_up.properties
if(not(properties)) then
properties = {}
entity.yl_speak_up.properties = properties
end
-- copy other property data that is stored under self.* over as well (like i.e. self.order for mobs_redo)
for k, v in pairs(entity) do
local t = type(v)
if(t == "string" or t == "number" or t == "boolean") then
properties["self."..tostring(k)] = tostring(v)
end
end
properties["self.name"] = tostring(entity.name)
if(not(long_version)) then
return properties
end
-- the long version contains additional information
local prop_names = {}
for k, v in pairs(properties) do
table.insert(prop_names, k)
end
table.sort(prop_names)
return {obj = obj, entity = entity, properties = properties, prop_names = prop_names}
end
-- most of the time we don't need object, entity or a list of the names of properties;
-- this returns just the properties themshelves
yl_speak_up.get_npc_properties = function(pname)
return yl_speak_up.get_npc_properties_long_version(pname, false)
end
yl_speak_up.set_npc_property = function(pname, property_name, property_value, reason)
if(not(pname) or not(property_name) or property_name == "") then
return "No player name or property name given. Cannot load property data."
end
-- here we want a table with additional information
local property_data = yl_speak_up.get_npc_properties_long_version(pname, true)
if(not(property_data)) then
return "Failed to load property data of NPC."
end
-- it is possible to react to property changes with special custom handlers
if(yl_speak_up.custom_property_handler[property_name]) then
-- the table contains the pointer to a fucntion
local fun = yl_speak_up.custom_property_handler[property_name]
-- call that function with the current values
return fun(pname, property_name, property_value, property_data)
end
-- properties of type self. are not set directly
if(string.sub(property_name, 1, 5) == "self.") then
return "Properties of the type \"self.\" cannot be modified."
end
-- properites starting with "server" can only be changed or added manually by
-- players with the npc_talk_admin priv
if(string.sub(property_name, 1, 6) == "server") then
if(not(reason) or reason ~= "manually" or not(pname)
or not(minetest.check_player_privs(pname, {npc_talk_admin=true}))) then
return "Properties starting with \"server\" can only be changed by players "..
"who have the \"npc_talk_admin\" priv."
end
end
-- store it
if(property_data.entity) then
property_data.entity.yl_speak_up.properties[property_name] = property_value
local n_id = yl_speak_up.speak_to[pname].n_id
yl_speak_up.log_change(pname, n_id, "Property \""..tostring(property_name)..
"\" set to \""..tostring(property_value).."\".")
end
-- TODO: handle non-npc (blocks etc)
return "OK"
end
yl_speak_up.input_properties = function(player, formname, fields)
local pname = player:get_player_name()
if(not(pname) or not(yl_speak_up.speak_to[pname])) then
return
end
local n_id = yl_speak_up.speak_to[pname].n_id
if(fields and fields.back and fields.back ~= "") then
yl_speak_up.show_fs(player, "initial_config",
{n_id = n_id, d_id = yl_speak_up.speak_to[pname].d_id, false})
return
end
local selected_row = nil
if(fields and fields.store_new_val and fields.store_new_val ~= "" and fields.prop_val) then
yl_speak_up.set_npc_property(pname, fields.prop_name, fields.prop_val, "manually")
elseif(fields and fields.delete_prop and fields.delete_prop ~= "") then
-- delete the old property
yl_speak_up.set_npc_property(pname, fields.prop_name, nil, "manually")
elseif(fields and fields.table_of_properties) then
local selected = minetest.explode_table_event(fields.table_of_properties)
if(selected.type == "CHG" or selected.type == "DLC") then
selected_row = selected.row
end
end
yl_speak_up.show_fs(player, "properties", {selected = selected_row})
end
yl_speak_up.get_fs_properties = function(pname, selected)
if(not(pname)) then
return ""
end
local n_id = yl_speak_up.speak_to[pname].n_id
-- is the player editing this npc? if not: abort
if(not(yl_speak_up.edit_mode[pname])
or (yl_speak_up.edit_mode[pname] ~= n_id)) then
return ""
end
-- we want the long version with additional information
local property_data = yl_speak_up.get_npc_properties_long_version(pname, true)
if(not(property_data)) then
-- something went wrong - there really is nothing useful we can do
-- if the NPC we want to interact with doesn't exist or is broken
return
end
local s = ""
if(not(property_data.prop_names)) then
property_data.prop_names = {}
end
local anz_prop = #property_data.prop_names
for i, k in ipairs(property_data.prop_names) do
local v = property_data.properties[k]
s = s.."#BBBBFF,"..minetest.formspec_escape(k)..","..minetest.formspec_escape(v)..","
end
s = s.."#00FF00,add,Add a new property"
if(not(selected) or selected == "") then
selected = -1
end
local add_selected = "label[3.5,6.5;No property selected.]"
selected = tonumber(selected)
if(selected > anz_prop + 1 or selected < 1) then
selected = -1
elseif(selected > anz_prop) then
add_selected = "label[0.2,6.5;Add new property:]"..
"field[3.0,6.5;3.5,1.0;prop_name;;]"..
"label[6.5,6.5;with value:]"..
"field[8.2,6.5;4.5,1.0;prop_val;;]"..
"button[8.2,7.8;2.0,1.0;store_new_val;Store]"
-- external properties can usually only be read but not be changed
elseif(string.sub(property_data.prop_names[selected], 1, 5) == "self."
and not(yl_speak_up.custom_property_handler[property_data.prop_names[selected]])) then
add_selected = "label[3.5,6.5;Properties of the type \"self.\" usually cannot be modified.]"
elseif(string.sub(property_data.prop_names[selected], 1, 6) == "server"
and not(minetest.check_player_privs(pname, {npc_talk_admin=true}))) then
add_selected = "label[3.5,6.5;Properties starting with \"server\" can only be "..
"changed by players\nwho have the \"npc_talk_admin\" priv."
else
local name = property_data.prop_names[selected]
local val = minetest.formspec_escape(property_data.properties[name])
local name_esc = minetest.formspec_escape(name)
add_selected = "label[0.2,6.5;Change property:]"..
"field[3.0,6.5;3.5,1.0;prop_name;;"..name_esc.."]"..
"label[6.5,6.5;to value:]"..
"field[8.2,6.5;4.5,1.0;prop_val;;"..val.."]"..
"button[8.2,7.8;2.0,1.0;store_new_val;Store]"..
"button[10.4,7.8;2.0,1.0;delete_prop;Delete]"
end
if(selected < 1) then
selected = ""
end
local dialog = yl_speak_up.speak_to[pname].dialog
local npc_name = minetest.formspec_escape(dialog.n_npc or "- nameless -")
return "size[12.5,8.5]" ..
"label[2,0;Properties of "..npc_name.." (ID: "..tostring(n_id).."):]"..
"tablecolumns[color,span=1;text;text]"..
"table[0.2,0.5;12,4.0;table_of_properties;"..s..";"..tostring(selected).."]"..
"tooltip[0.2,0.5;12,4.0;"..
"Click on \"add\" to add a new property.\n"..
"Click on the name of a property to change or delete it.]"..
"label[2.0,4.5;"..
"Properties are important for NPC that want to make use of generic dialogs.\n"..
"Properties can be used to determine which generic dialog(s) shall apply to\n"..
"this particular NPC and how they shall be configured. You need the\n"..
"\"npc_talk_admin\" priv to edit properties starting with the text \"server\".]"..
"button[5.0,7.8;2.0,0.9;back;Back]"..
add_selected
end

View File

@ -1,33 +0,0 @@
yl_speak_up.input_quest_gui = function(player, formname, fields)
-- this return value is necessary for custom actions
local ret = {quit = true}
local pname = player:get_player_name()
if(fields and fields.back_from_msg) then
yl_speak_up.show_fs(player, "quest_gui")
return ret
end
-- new variables have to be added (and deleted) somewhere after all
if(fields.manage_variables) then
-- remember which formspec we are comming from
yl_speak_up.speak_to[pname][ "working_at" ] = "quest_gui"
yl_speak_up.show_fs(player, "manage_variables")
return ret
elseif(fields.manage_quests) then
yl_speak_up.speak_to[pname][ "working_at" ] = "quest_gui"
yl_speak_up.show_fs(player, "manage_quests")
return ret
end
-- the calling NPC shall no longer do anything
return ret
end
yl_speak_up.get_fs_quest_gui = function(player, param)
local pname = player:get_player_name()
return "size[24,20]"..
"label[0,0.5;Hi. This is a quest admin gui.]"..
"button[0.2,1.0;4.0,0.6;manage_variables;Manage variables]"..
"button[6.2,1.0;4.0,0.6;manage_quests;Manage quests]"
end

View File

@ -1,123 +0,0 @@
-- when the player is editing the NPC and has changed it without having
-- saved the changes yet: ask what shall be done (save? discard? back?)
yl_speak_up.input_save_or_discard_changes = function(player, formname, fields)
local pname = player:get_player_name()
-- if the player is not even talking to this particular npc
if(not(yl_speak_up.speak_to[pname])) then
return
end
local target_dialog = yl_speak_up.speak_to[pname].target_dialog
if(not(target_dialog)) then
target_dialog = ""
end
local edit_mode = (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id)
local d_id = yl_speak_up.speak_to[pname].d_id
local n_id = yl_speak_up.speak_to[pname].n_id
local o_id = yl_speak_up.speak_to[pname].o_id
-- the player decided to go back and continue editing the current dialog
if(edit_mode and fields.back_to_dialog_changes) then
-- do NOT clear the list of changes; just show the old dialog again
yl_speak_up.show_fs(player, "show_last_fs", {})
return
-- save changes and continue on to the next dialog
elseif(edit_mode and fields.save_dialog_changes) then
-- actually save the dialog (the one the NPC currently has)
yl_speak_up.save_dialog(n_id, yl_speak_up.speak_to[pname].dialog)
-- log all the changes
for i, c in ipairs(yl_speak_up.npc_was_changed[ n_id ]) do
yl_speak_up.log_change(pname, n_id, c)
end
-- clear list of changes
yl_speak_up.npc_was_changed[ n_id ] = {}
-- discard changes and continue on to the next dialog
elseif(edit_mode and fields.discard_dialog_changes) then
-- the current dialog and the one we want to show next may both be new dialogs;
-- if we just reload the old state, they would both get lost
local target_dialog_data = yl_speak_up.speak_to[pname].dialog.n_dialogs[ target_dialog ]
-- actually restore the old state and discard the changes by loading the dialog anew
yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, false)
-- clear list of changes
yl_speak_up.npc_was_changed[ n_id ] = {}
local dialog = yl_speak_up.speak_to[pname].dialog
-- do we have to save again after restoring current and target dialog?
local need_to_save = false
-- if the current dialog was a new one, it will be gone now - restore it
if(d_id and d_id ~= "" and not(dialog.n_dialogs[ d_id ])) then
-- we can't just restore the current dialog - after all the player wanted
-- to discard the changes; but we can recreate the current dialog so that it
-- is in the "new dialog" state again
local next_id = tonumber(string.sub( d_id, 3))
yl_speak_up.add_new_dialog(dialog, pname, next_id)
yl_speak_up.log_change(pname, n_id, "Saved new dialog "..tostring( d_id )..".")
need_to_save = true
end
if(target_dialog and target_dialog ~= "" and not(dialog.n_dialogs[ target_dialog ])) then
-- restore the new target dialog
dialog.n_dialogs[ target_dialog ] = target_dialog_data
yl_speak_up.log_change(pname, n_id, "Saved new dialog "..tostring( target_dialog )..".")
need_to_save = true
end
if(need_to_save) then
yl_speak_up.save_dialog(n_id, dialog)
end
end
-- are there any changes which might be saved or discarded?
if(edit_mode
and yl_speak_up.npc_was_changed[ n_id ]
and #yl_speak_up.npc_was_changed[ n_id ] > 0) then
yl_speak_up.show_fs(player, "save_or_discard_changes", {})
return
end
yl_speak_up.show_fs(player, "proceed_after_save", {})
end
yl_speak_up.get_fs_save_or_discard_changes = function(player, param)
local pname = player:get_player_name()
local n_id = yl_speak_up.speak_to[pname].n_id
-- TODO
local target_name = "quit"
local target_dialog = nil -- TODO
if(target_dialog and target_dialog ~= "") then
target_name = "go on to dialog "..minetest.formspec_escape(target_dialog)
if(target_dialog == "edit_option_dialog") then
target_name = "edit option \""..
minetest.formspec_escape(tostring(o_id)).."\" of dialog \""..
minetest.formspec_escape(tostring(d_id)).."\""
end
end
yl_speak_up.speak_to[pname].target_dialog = target_dialog
local d_id = yl_speak_up.speak_to[pname].d_id
-- reverse the order of the changes in the log so that newest are topmost
local text = ""
for i,t in ipairs(yl_speak_up.npc_was_changed[ n_id ]) do
text = minetest.formspec_escape(t).."\n"..text
end
-- build a formspec showing the changes to this dialog and ask for save
return "size[14,6.2]"..
"bgcolor[#00000000;false]"..
-- TODO: make this more flexible
"label[0.2,0.2;You are about to leave dialog "..minetest.formspec_escape(d_id)..
" and "..target_name..".]"..
"label[0.2,0.65;These changes have been applied to dialog "..
minetest.formspec_escape(d_id)..":]"..
"hypertext[0.2,1;13.5,4;list_of_changes;<normal>"..
minetest.formspec_escape(text) .. "\n</normal>".."]"..
"button_exit[1.2,5.2;3,0.9;discard_dialog_changes;Discard changes]"..
"button[5.7,5.2;3,0.9;back_to_dialog_changes;Back]"..
"button_exit[10.2,5.2;3,0.9;save_dialog_changes;Save changes]"..
"tooltip[save_dialog_changes;Save all changes to this dialog and "..target_name..".]"..
"tooltip[discard_dialog_changes;Undo all changes and "..target_name..".]"..
"tooltip[back_to_dialog_changes;Go back to dialog "..
minetest.formspec_escape(d_id).." and continue editing it.]"
end

View File

@ -1,64 +0,0 @@
-- called only by mange_variables formspec
yl_speak_up.fs_show_all_var_values = function(player, pname, var_name)
-- wrong parameters? no need to show an error message here
if(not(var_name) or not(pname) or not(player)) then
return ""
end
-- TODO: check if the player really has read access to this variable
var_name = yl_speak_up.restore_complete_var_name(var_name, pname)
-- player names with values as key; normally the player name is the key and
-- the value the value - but that would be a too long list to display, and
-- so we rearrange the array for display here
local players_with_value = {}
-- the diffrent values that exist
local values = {}
local var_data = yl_speak_up.player_vars[ var_name ]
local count_players = 0
for player_name, v in pairs(var_data) do
-- metadata is diffrent and not of relevance here
if(player_name and player_name ~= "$META$" and v) then
if(not(players_with_value[ v ])) then
players_with_value[ v ] = {}
table.insert(values, v)
end
table.insert(players_with_value[ v ], player_name)
count_players = count_players + 1
end
end
-- the values ought to be shown in a sorted way
table.sort(values)
-- construct the lines that shall form the table
local lines = {"#FFFFFF,Value:,#FFFFFF,Players for which this value is stored:"}
for i, v in ipairs(values) do
table.insert(lines,
"#FFFF00,"..minetest.formspec_escape(v)..",#CCCCCC,"..
-- text, prefix, line_length, max_lines
yl_speak_up.wrap_long_lines_for_table(
table.concat(players_with_value[ v ], ", "),
",,,#CCCCCC,", 80, 8))
end
-- true here means: lines are already sorted;
-- ",": don't insert blank lines between entries
local formspec = yl_speak_up.print_as_table_prepare_formspec(lines, "table_of_variable_values",
"back_from_msg", "Back", true, ",",
"color,span=1;text;color,span=1;text") -- the table columns
table.insert(formspec,
"label[18.0,1.8;"..
minetest.formspec_escape("For variable \""..
minetest.colorize("#FFFF00", tostring(var_name or "- ? -"))..
"\", these values are stored:").."]")
if(values and #values > 0) then
table.insert(formspec,
"label[18.0,31.0;The variable holds "..
minetest.colorize("#FFFF00", tostring(#values)).." diffrent values for "..
minetest.colorize("#FFFF00", tostring(count_players)).." diffrent players.]")
else
table.insert(formspec,
"label[18.0,31.0;The variable does not currently hold any stored values.]")
end
return table.concat(formspec, "\n")
end

File diff suppressed because it is too large Load Diff

View File

@ -1,732 +0,0 @@
-- if player has npc_talk_owner priv AND is owner of this particular npc:
-- chat option: "I am your owner. I have new orders for you.
-- -> enters edit mode
-- when edit_mode has been enabled, the following chat options are added to the options:
-- chat option: "Add new answer/option to this dialog."
-- -> adds a new aswer/option
-- chat option: "That was all. I'm finished with giving you new orders. Remember them!"
-- -> ends edit mode
--
--###
-- Init
--###
-- store if the player is editing a particular NPC; format: yl_speak_up.edit_mode[pname] = npc_id
yl_speak_up.edit_mode = {}
-- changes applied in edit_mode are applied immediately - but not immediately stored to disk
-- (this gives the players a chance to back off in case of unwanted changes)
yl_speak_up.npc_was_changed = {}
-- self (the npc as such) is rarely passed on to any functions; in order to be able to check if
-- the player really owns the npc, we need to have that data available;
-- format: yl_speak_up.npc_owner[ npc_id ] = owner_name
yl_speak_up.npc_owner = {}
-- store the current trade between player and npc in case it gets edited in the meantime
yl_speak_up.trade = {}
-- store what the player last entered in an text_input action
yl_speak_up.last_text_input = {}
yl_speak_up.reset_vars_for_player = function(pname, reset_fs_version)
yl_speak_up.speak_to[pname] = nil
yl_speak_up.edit_mode[pname] = nil
yl_speak_up.last_text_input[pname] = nil
-- when just stopping editing: don't reset the fs_version
if(reset_fs_version) then
yl_speak_up.fs_version[pname] = nil
end
end
--###
-- Debug
--###
yl_speak_up.debug = true
--###
-- Helpers
--###
yl_speak_up.get_number_from_id = function(any_id)
if(not(any_id) or any_id == "d_got_item" or any_id == "d_end") then
return "0"
end
return string.split(any_id, "_")[2]
end
local function save_path(n_id)
return yl_speak_up.worldpath .. yl_speak_up.path .. DIR_DELIM .. n_id .. ".json"
end
yl_speak_up.get_error_message = function()
local formspec = {
"size[13.4,8.5]",
"bgcolor[#FF0000]",
"label[0.2,0.35;Please save a NPC file first]",
"button_exit[0.2,7.7;3,0.75;button_back;Back]"
}
return table.concat(formspec, "")
end
yl_speak_up.find_next_id = function(t)
local start_id = 1
if t == nil then
return start_id
end
local keynum = 1
for k, _ in pairs(t) do
local keynum = tonumber(yl_speak_up.get_number_from_id(k))
if keynum and keynum >= start_id then
start_id = keynum + 1
end
end
return start_id
end
yl_speak_up.sanitize_sort = function(options, value)
local retval = value
if value == "" or value == nil or tonumber(value) == nil then
local temp = 0
for k, v in pairs(options) do
if v.o_sort ~= nil then
if tonumber(v.o_sort) > temp then
temp = tonumber(v.o_sort)
end
end
end
retval = tostring(temp + 1)
end
return retval
end
--###
--Load and Save
--###
-- we can't really log changes here in this function because we don't know *what* has been changed
yl_speak_up.save_dialog = function(n_id, dialog)
if type(n_id) ~= "string" or type(dialog) ~= "table" then
return false
end
local p = save_path(n_id)
-- save some data (in particular usage of quest variables)
yl_speak_up.update_stored_npc_data(n_id, dialog)
-- make sure we never store any automaticly added generic dialogs
dialog = yl_speak_up.strip_generic_dialogs(dialog)
local content = minetest.write_json(dialog)
return minetest.safe_file_write(p, content)
end
-- if a player is supplied: include generic dialogs
yl_speak_up.load_dialog = function(n_id, player) -- returns the saved dialog
local p = save_path(n_id)
local file, err = io.open(p, "r")
if err then
return yl_speak_up.add_generic_dialogs({}, n_id, player)
end
io.input(file)
local content = io.read()
local dialog = minetest.parse_json(content)
io.close(file)
if type(dialog) ~= "table" then
dialog = {}
end
return yl_speak_up.add_generic_dialogs(dialog, n_id, player)
end
-- used by staff and input_inital_config
yl_speak_up.fields_to_dialog = function(pname, fields)
local n_id = yl_speak_up.speak_to[pname].n_id
local dialog = yl_speak_up.load_dialog(n_id, false)
local save_d_id = ""
if next(dialog) == nil then -- No file found. Let's create the basic values
dialog = {}
dialog.n_dialogs = {}
end
if dialog.n_dialogs == nil or next(dialog.n_dialogs) == nil then --No dialogs found. Let's make a table
dialog.n_dialogs = {}
end
if fields.d_text ~= "" then -- If there is dialog text, then save new or old dialog
if fields.d_id == yl_speak_up.text_new_dialog_id then --New dialog --
-- Find highest d_id and increase by 1
save_d_id = "d_" .. yl_speak_up.find_next_id(dialog.n_dialogs)
-- Initialize empty dialog
dialog.n_dialogs[save_d_id] = {}
else -- Already existing dialog
save_d_id = fields.d_id
end
-- Change dialog
dialog.n_dialogs[save_d_id].d_id = save_d_id
dialog.n_dialogs[save_d_id].d_type = "text"
dialog.n_dialogs[save_d_id].d_text = fields.d_text
dialog.n_dialogs[save_d_id].d_sort = fields.d_sort
end
--Context
yl_speak_up.speak_to[pname].d_id = save_d_id
-- Just in case the NPC vlaues where changed or set
dialog.n_id = n_id
dialog.n_description = fields.n_description
dialog.n_npc = fields.n_npc
dialog.npc_owner = fields.npc_owner
return dialog
end
yl_speak_up.delete_dialog = function(n_id, d_id)
if d_id == yl_speak_up.text_new_dialog_id then
return false
end -- We don't delete "New dialog"
local dialog = yl_speak_up.load_dialog(n_id, false)
dialog.n_dialogs[d_id] = nil
yl_speak_up.save_dialog(n_id, dialog)
end
--###
--Formspecs
--###
-- get formspecs
-- talk
-- receive fields
-- talk
-- helper function
-- the option to override next_id and provide a value is needed when a new dialog was
-- added, then edited, and then discarded; it's still needed after that, but has to
-- be reset to empty state (wasn't stored before)
yl_speak_up.add_new_dialog = function(dialog, pname, next_id, dialog_text)
if(not(next_id)) then
next_id = yl_speak_up.find_next_id(dialog.n_dialogs)
end
local future_d_id = "d_" .. next_id
-- Initialize empty dialog
dialog.n_dialogs[future_d_id] = {
d_id = future_d_id,
d_type = "text",
d_text = (dialog_text or ""),
d_sort = next_id
}
-- store that there have been changes to this npc
-- (better ask only when the new dialog is changed)
-- table.insert(yl_speak_up.npc_was_changed[ yl_speak_up.edit_mode[pname] ],
-- "Dialog "..future_d_id..": New dialog added.")
-- add an option for going back to the start of the dialog;
-- this is an option which the player can delete and change according to needs,
-- not a fixed button which may not always fit
if(not(dialog_text)) then
-- we want to go back to the start from here
local target_dialog = yl_speak_up.get_start_dialog_id(dialog)
-- this text will be used for the button
local option_text = "Let's go back to the start of our talk."
-- we just created this dialog - this will be the first option
yl_speak_up.add_new_option(dialog, pname, "1", future_d_id, option_text, target_dialog)
end
return future_d_id
end
-- add a new option/answer to dialog d_id with option_text (or default "")
-- option_text (optional) the text that shall be shown as option/answer
-- target_dialog (optional) the target dialog where the player will end up when choosing
-- this option/answer
yl_speak_up.add_new_option = function(dialog, pname, next_id, d_id, option_text, target_dialog)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])) then
return nil
end
if dialog.n_dialogs[d_id].d_options == nil then
-- make sure d_options exists
dialog.n_dialogs[d_id].d_options = {}
else
-- we don't want an infinite amount of answers per dialog
local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort")
local anz_options = #sorted_list
if(anz_options >= yl_speak_up.max_number_of_options_per_dialog) then
-- nothing added
return nil
end
end
if(not(next_id)) then
next_id = yl_speak_up.find_next_id(dialog.n_dialogs[d_id].d_options)
end
local future_o_id = "o_" .. next_id
dialog.n_dialogs[d_id].d_options[future_o_id] = {
o_id = future_o_id,
o_hide_when_prerequisites_not_met = "false",
o_grey_when_prerequisites_not_met = "false",
o_sort = -1,
o_text_when_prerequisites_not_met = "",
o_text_when_prerequisites_met = (option_text or ""),
}
-- necessary in order for it to work
local s = yl_speak_up.sanitize_sort(dialog.n_dialogs[d_id].d_options, yl_speak_up.speak_to[pname].o_sort)
dialog.n_dialogs[d_id].d_options[future_o_id].o_sort = s
-- log only in edit mode
local n_id = yl_speak_up.speak_to[pname].n_id
if(yl_speak_up.npc_was_changed[ n_id ]) then
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Added new option/answer "..future_o_id..".")
end
-- letting d_got_item point back to itself is not a good idea because the
-- NPC will then end up in a loop; plus the d_got_item dialog is intended for
-- automatic processing, not for showing to the player
if(d_id == "d_got_item") then
-- unless the player specifies something better, we go back to the start dialog
-- (that is where d_got_item got called from anyway)
target_dialog = yl_speak_up.get_start_dialog_id(dialog)
-- ...and this option needs to be selected automaticly
dialog.n_dialogs[d_id].d_options[future_o_id].o_autoanswer = 1
elseif(d_id == "d_trade") then
-- we really don't want to go to another dialog from here
target_dialog = "d_trade"
-- ...and this option needs to be selected automaticly
dialog.n_dialogs[d_id].d_options[future_o_id].o_autoanswer = 1
end
local future_r_id = nil
-- create a fitting dialog result automaticly if possible:
-- give this new dialog a dialog result that leads back to this dialog
-- (which is more helpful than creating tons of empty dialogs)
if(target_dialog and (dialog.n_dialogs[target_dialog] or target_dialog == "d_end")) then
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
-- actually store the new result
dialog.n_dialogs[d_id].d_options[future_o_id].o_results = {}
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "dialog",
r_value = target_dialog}
end
-- the d_got_item dialog is special; players can easily forget to add the
-- necessary preconditions and effects, so we do that manually here
if(d_id == "d_got_item") then
-- we also need a precondition so that the o_autoanswer can actually get called
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites = {}
-- we just added this option; this is the first and for now only precondition for it;
-- the player still has to adjust it, but at least it is a reasonable default
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites["p_1"] = {
p_id = "p_1",
p_type = "player_offered_item",
p_item_stack_size = tostring(next_id),
p_match_stack_size = "exactly",
-- this is just a simple example item and ought to be changed after adding
p_value = "default:stick "..tostring(next_id)}
-- we need to show the player that his action was successful
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id].alternate_text =
"Thank you for the "..tostring(next_id).." stick(s)! "..
"Never can't have enough sticks.\n$TEXT$"
-- we need an effect for accepting the item;
-- taking all that was offered and putting it into the NPC's inventory is a good default
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "deal_with_offered_item",
r_value = "take_all"}
-- the trade dialog is equally special
elseif(d_id == "d_trade") then
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites = {}
-- this is just an example
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites["p_1"] = {
p_id = "p_1",
p_type = "npc_inv",
p_value = "inv_does_not_contain",
p_inv_list_name = "npc_main",
p_itemstack = "default:stick "..tostring(100-next_id)}
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
-- example craft
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "craft",
r_value = "default:stick 4",
o_sort = "1",
r_craft_grid = {"default:wood", "", "", "", "", "", "", "", ""}}
end
return future_o_id
end
-- add a new result to option o_id of dialog d_id
yl_speak_up.add_new_result = function(dialog, d_id, o_id)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return
end
-- create a new result (first the id, then the actual result)
local future_r_id = "r_" .. yl_speak_up.find_next_id(dialog.n_dialogs[d_id].d_options[o_id].o_results)
if future_r_id == "r_1" then
dialog.n_dialogs[d_id].d_options[o_id].o_results = {}
end
dialog.n_dialogs[d_id].d_options[o_id].o_results[future_r_id] = {}
return future_r_id
end
-- this is useful for result types that can exist only once per option
-- (apart from editing with the staff);
-- examples: "dialog" and "trade";
-- returns tue r_id or nil if no result of that type has been found
yl_speak_up.get_result_id_by_type = function(dialog, d_id, o_id, result_type)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return
end
local results = dialog.n_dialogs[d_id].d_options[o_id].o_results
if(not(results)) then
return
end
for k, v in pairs(results) do
if(v.r_type == result_type) then
return k
end
end
end
-- helper function for sorting options/answers using options[o_id].o_sort
-- (or dialogs by d_sort)
yl_speak_up.get_sorted_options = function(options, sort_by)
local sorted_list = {}
for k,v in pairs(options) do
table.insert(sorted_list, k)
end
table.sort(sorted_list,
function(a,b)
if(not(options[a][sort_by])) then
return false
elseif(not(options[b][sort_by])) then
return true
-- sadly not all entries are numeric
elseif(tonumber(options[a][sort_by]) and tonumber(options[b][sort_by])) then
return (tonumber(options[a][sort_by]) < tonumber(options[b][sort_by]))
-- numbers have a higher priority
elseif(tonumber(options[a][sort_by])) then
return true
elseif(tonumber(options[b][sort_by])) then
return false
-- if the value is the same: sort by index
elseif(options[a][sort_by] == options[b][sort_by]) then
return (a < b)
else
return (options[a][sort_by] < options[b][sort_by])
end
end
)
return sorted_list
end
-- simple sort of keys of a table numericly;
-- this is not efficient - but that doesn't matter: the lists are small and
-- it is only executed when configuring an NPC
-- simple: if the parameter is true, the keys will just be sorted (i.e. player names) - which is
-- not enough for d_<nr>, o_<nr> etc. (which need more care when sorting)
yl_speak_up.sort_keys = function(t, simple)
local keys = {}
for k, v in pairs(t) do
-- add a prefix so that p_2 ends up before p_10
if(not(simple) and string.len(k) == 3) then
k = "a"..k
end
table.insert(keys, k)
end
table.sort(keys)
if(simple) then
return keys
end
for i,k in ipairs(keys) do
-- avoid cutting the single a from a_1 (action 1)
if(k and string.sub(k, 1, 1) == "a" and string.sub(k, 2, 2) ~= "_") then
-- remove the leading blank
keys[i] = string.sub(k, 2)
end
end
return keys
end
-- identify multiple results that lead to target dialogs
yl_speak_up.check_for_disambigous_results = function(n_id, pname)
local errors_found = false
-- this is only checked when trying to edit this npc;
-- let's stick to check the dialogs of this one without generic dialogs
local dialog = yl_speak_up.load_dialog(n_id, false)
-- nothing defined yet - nothing to repair
if(not(dialog.n_dialogs)) then
return
end
-- iterate over all dialogs
for d_id, d in pairs(dialog.n_dialogs) do
if(d_id and d and d.d_options) then
-- iterate over all options
for o_id, o in pairs(d.d_options) do
if(o_id and o and o.o_results) then
local dialog_results = {}
-- iterate over all results
for r_id, r in pairs(o.o_results) do
if(r.r_type == "dialog") then
table.insert(dialog_results, r_id)
end
end
if(#dialog_results>1) then
local msg = "ERROR: Dialog "..
tostring(d_id)..", option "..tostring(o_id)..
", has multiple results of type dialog: "..
minetest.serialize(dialog_results)..". Please "..
"let someone with npc_master priv fix that first!"
yl_speak_up.log_change(pname, n_id, msg, "error")
if(pname) then
minetest.chat_send_player(pname, msg)
end
errors_found = true
end
end
end
end
end
return errors_found
end
-- allow to enter force edit mode (useful when an NPC was broken)
yl_speak_up.force_edit_mode = {}
-- command to enter force edit mode
yl_speak_up.command_npc_talk_force_edit = function(pname, param)
if(not(pname)) then
return
end
if(yl_speak_up.force_edit_mode[pname]) then
yl_speak_up.force_edit_mode[pname] = nil
minetest.chat_send_player(pname,
"Ending force edit mode for NPC. From now on talks "..
"will no longer start in edit mode.")
else
yl_speak_up.force_edit_mode[pname] = true
minetest.chat_send_player(pname,
"STARTING force edit mode for NPC. From now on talks "..
"with NPC will always start in edit mode provided "..
"you are allowed to edit this NPC.\n"..
"In order to end force edit mode, give the command "..
"/npc_talk_force_edit a second time.")
end
end
-- Make the NPC talk
-- assign n_ID
-- usually this happens when talking to the NPC for the first time;
-- but if you want to you can call this function earlier (on spawn)
-- so that logging of spawning with the ID is possible
yl_speak_up.initialize_npc = function(self)
-- already configured?
if(not(self) or (self.yl_speak_up and self.yl_speak_up.id)) then
return self
end
local m_talk = yl_speak_up.talk_after_spawn or true
local m_id = yl_speak_up.number_of_npcs + 1
yl_speak_up.number_of_npcs = m_id
yl_speak_up.modstorage:set_int("amount", m_id)
self.yl_speak_up = {
talk = m_talk,
id = m_id,
textures = self.textures
}
return self
end
function yl_speak_up.talk(self, clicker)
if not clicker and not clicker:is_player() then
return
end
if not self then
return
end
local id_prefix = "n"
-- we are not dealing with an NPC but with a position/block on the map
if(self.is_block) then
id_prefix = "p"
local owner = "- unknown -"
local talk_name = "- unknown -"
if(self.pos and self.pos and self.pos.x) then
local meta = minetest.get_meta(self.pos)
if(meta) then
owner = meta:get_string("owner") or ""
talk_name = meta:get_string("talk_name") or ""
end
end
self.yl_speak_up = {
is_block = true,
talk = true,
id = minetest.pos_to_string(self.pos, 0),
textures = {},
owner = owner,
npc_name = talk_name,
object = nil, -- blocks don't have an object
}
-- TODO: remember somewhere that this block is relevant
-- initialize the mob if necessary; this happens at the time of first talk, not at spawn time!
elseif(not(self.yl_speak_up) or not(self.yl_speak_up.id)) then
self = yl_speak_up.initialize_npc(self)
end
-- create a detached inventory for the npc and load its inventory
yl_speak_up.load_npc_inventory(id_prefix.."_"..tostring(self.yl_speak_up.id))
local npc_id = self.yl_speak_up.id
local n_id = id_prefix.."_" .. npc_id
-- remember whom the npc belongs to (as long as we still have self.owner available for easy access)
yl_speak_up.npc_owner[ n_id ] = self.owner
local pname = clicker:get_player_name()
if not self.yl_speak_up or not self.yl_speak_up.talk or self.yl_speak_up.talk~=true then
local was = "This NPC"
if(id_prefix ~= "n") then
was = "This block"
end
-- show a formspec to other players that this NPC is busy
if(not(yl_speak_up.may_edit_npc(clicker, n_id))) then
-- show a formspec so that the player knows that he may come back later
yl_speak_up.show_fs(player, "msg", {input_to = "yl_spaek_up:ignore", formspec =
"size[6,2]"..
"label[1.2,0.0;"..minetest.formspec_escape((self.yl_speak_up.npc_name or was)..
" [muted]").."]"..
"label[0.2,0.5;Sorry! I'm currently busy learning new things.]"..
"label[0.2,1.0;Please come back later.]"..
"button_exit[2.5,1.5;1,0.9;ok;Ok]"})
return
end
-- allow the owner to edit (and subsequently unmute) the npc
minetest.chat_send_player(pname, was.." is muted. It will only talk to you.")
end
yl_speak_up.speak_to[pname] = {}
yl_speak_up.speak_to[pname].n_id = n_id -- Memorize which player talks to which NPC
yl_speak_up.speak_to[pname].textures = self.yl_speak_up.textures
yl_speak_up.speak_to[pname].option_index = 1
-- the object itself may be needed in load_dialog for adding generic dialogs
yl_speak_up.speak_to[pname].obj = self.object
-- Load the dialog and see what we can do with it
-- this inculdes generic dialog parts;
-- make sure this is never true in edit mode (because in edit mode we want to
-- edit this particular NPC without generic parts)
local player = clicker
if(yl_speak_up.edit_mode[pname] == n_id) then
player = false
end
yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, player)
-- is this player explicitly allowed to edit this npc?
if(yl_speak_up.speak_to[pname].dialog
and yl_speak_up.speak_to[pname].dialog.n_may_edit
and yl_speak_up.speak_to[pname].dialog.n_may_edit[pname]
and minetest.check_player_privs(clicker, {npc_talk_owner=true})) then
yl_speak_up.speak_to[pname].may_edit_this_npc = true
end
-- are we in force edit mode, and can the player edit this NPC?
if(yl_speak_up.force_edit_mode[pname]
and yl_speak_up.may_edit_npc(clicker, n_id)) then
yl_speak_up.edit_mode[pname] = n_id
end
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog.trades)) then
dialog.trades = {}
end
-- some NPC may have reset the animation; at least set it to the desired
-- value whenever we talk to the NPC
if self.yl_speak_up and self.yl_speak_up.animation then
self.object:set_animation(self.yl_speak_up.animation)
end
-- maintain a list of existing NPC, but do not force saving
yl_speak_up.update_npc_data(self, dialog, false)
yl_speak_up.show_fs(clicker, "talk", {n_id = n_id})
end
-- mute the npc; either via the appropriate staff or via talking to him
yl_speak_up.set_muted = function(p_name, obj, set_muted)
if(not(obj)) then
return
end
local luaentity = obj:get_luaentity()
if(not(luaentity)) then
return
end
local npc = luaentity.yl_speak_up.id
local npc_name = luaentity.yl_speak_up.npc_name
-- fallback
if(not(npc_name)) then
npc_name = npc
end
if(set_muted and luaentity.yl_speak_up.talk) then
-- the npc is willing to talk
luaentity.yl_speak_up.talk = false
yl_speak_up.update_nametag(luaentity)
minetest.chat_send_player(p_name,"NPC with ID n_"..npc.." will shut up at pos "..
minetest.pos_to_string(obj:get_pos(),0).." on command of "..p_name)
yl_speak_up.log_change(p_name, "n_"..npc, "muted - NPC stops talking")
elseif(not(set_muted) and not(luaentity.yl_speak_up.talk)) then
-- mute the npc
luaentity.yl_speak_up.talk = true
yl_speak_up.update_nametag(luaentity)
minetest.chat_send_player(p_name,"NPC with ID n_"..npc.." will resume speech at pos "..
minetest.pos_to_string(obj:get_pos(),0).." on command of "..p_name)
yl_speak_up.log_change(p_name, "n_"..npc, "unmuted - NPC talks again")
end
end
-- has the player the right privs?
-- this is used for the "I am your master" talk based configuration; *NOT* for the staffs!
yl_speak_up.may_edit_npc = function(player, n_id)
if(not(player)) then
return false
end
local pname = player:get_player_name()
-- is the player allowed to edit this npc?
return ((yl_speak_up.npc_owner[ n_id ] == pname
and minetest.check_player_privs(player, {npc_talk_owner=true}))
or minetest.check_player_privs(player, {npc_talk_master=true})
or minetest.check_player_privs(player, {npc_master=true})
or (yl_speak_up.speak_to[pname]
and yl_speak_up.speak_to[pname].may_edit_this_npc))
end

858
functions_dialogs.lua Normal file
View File

@ -0,0 +1,858 @@
--
-- These functions here access and manipulate the "dialogs" data structure.
-- It is loaded for each player whenever the player talks to an NPC. Each
-- talking player gets *a copy* of that data structure.
--
-- As this mod is about this "dialogs" data structure and its editing, this
-- isn't the only place in this mod where the data structure is accessed
-- and/or manipulated. This here just contains some common functions.
--
--###
-- Helpers
--###
yl_speak_up.string_starts_with = function(str, starts_with)
return (string.sub(str, 1, string.len(starts_with)) == starts_with)
end
yl_speak_up.get_number_from_id = function(any_id)
if(not(any_id) or any_id == "d_got_item" or any_id == "d_end" or any_id == "d_dynamic") then
return "0"
end
return string.split(any_id, "_")[2]
end
yl_speak_up.find_next_id = function(t)
local start_id = 1
if t == nil then
return start_id
end
local keynum = 1
for k, _ in pairs(t) do
local keynum = tonumber(yl_speak_up.get_number_from_id(k))
if keynum and keynum >= start_id then
start_id = keynum + 1
end
end
return start_id
end
yl_speak_up.sanitize_sort = function(options, value)
local retval = value
if value == "" or value == nil or tonumber(value) == nil then
local temp = 0
for k, v in pairs(options) do
if v.o_sort ~= nil then
if tonumber(v.o_sort) > temp then
temp = tonumber(v.o_sort)
end
end
end
retval = tostring(temp + 1)
end
return retval
end
-- helper function for
-- yl_speak_up.get_fs_talkdialog and
-- yl_speak_up.check_and_add_as_generic_dialog
-- find the dialog with d_sort == 0 or lowest number
yl_speak_up.get_start_dialog_id = function(dialog)
if(not(dialog) or not(dialog.n_dialogs)) then
return nil
end
-- Find the dialog with d_sort = 0 or alternatively with the lowest number
local lowest_sort = nil
local d_id = nil
for k, v in pairs(dialog.n_dialogs) do
local nr = tonumber(v.d_sort)
if(not(lowest_sort) or (nr and nr >= 0 and nr < lowest_sort)) then
lowest_sort = nr
d_id = k
end
end
return d_id
end
-- helper function that is also used by export_to_ink.lua
-- returns a sorted dialog list without special or generic dialogs
yl_speak_up.get_dialog_list_for_export = function(dialog)
local liste = {}
if(not(dialog) or not(dialog.n_dialogs)) then
return liste
end
-- sort the list of dialogs by d_id
local liste_sorted = yl_speak_up.sort_keys(dialog.n_dialogs or {}, true)
for _, d_id in ipairs(liste_sorted) do
-- only normal dialogs - no d_trade, d_got_item, d_dynamic etc;
if(not(yl_speak_up.is_special_dialog(d_id))
-- also no generic dialogs (they do not come from this NPC)
and not(dialog.n_dialogs[d_id].is_generic)) then
table.insert(liste, d_id)
end
end
-- now that the list contains only normal dialogs, we can sort by d_sort
-- (thus allowing d_9 to be listed earlier than d_10 etc.)
table.sort(liste, function(a, b)
return dialog and dialog.n_dialogs and dialog.n_dialogs[a] and dialog.n_dialogs[b]
and ((tonumber(dialog.n_dialogs[a].d_sort or "") or 0)
< (tonumber(dialog.n_dialogs[b].d_sort or "") or 0)) end)
return liste
end
--###
--Formspecs
--###
-- helper function
-- the option to override next_id and provide a value is needed when a new dialog was
-- added, then edited, and then discarded; it's still needed after that, but has to
-- be reset to empty state (wasn't stored before)
-- Note: pname is only passed to yl_speak_up.add_new_option - which is only used if
-- dialog_text is empty (and only for logging)
yl_speak_up.add_new_dialog = function(dialog, pname, next_id, dialog_text)
if(not(next_id)) then
next_id = yl_speak_up.find_next_id(dialog.n_dialogs)
end
local future_d_id = "d_" .. next_id
-- Initialize empty dialog
dialog.n_dialogs[future_d_id] = {
d_id = future_d_id,
d_type = "text",
d_text = (dialog_text or ""),
d_sort = next_id
}
-- store that there have been changes to this npc
-- (better ask only when the new dialog is changed)
-- table.insert(yl_speak_up.npc_was_changed[ yl_speak_up.edit_mode[pname] ],
-- "Dialog "..future_d_id..": New dialog added.")
-- add an option for going back to the start of the dialog;
-- this is an option which the player can delete and change according to needs,
-- not a fixed button which may not always fit
if(not(dialog_text)) then
-- we want to go back to the start from here
local target_dialog = yl_speak_up.get_start_dialog_id(dialog)
-- this text will be used for the button
local option_text = "Let's go back to the start of our talk."
-- we just created this dialog - this will be the first option
yl_speak_up.add_new_option(dialog, pname, "1", future_d_id, option_text, target_dialog)
end
return future_d_id
end
-- update existing or create a new dialog named d_name with d_text
-- (useful for import from ink and likewise functionality)
-- this also prepares the dialog for options update
yl_speak_up.update_dialog = function(log, dialog, dialog_name, dialog_text)
if(dialog_name and yl_speak_up.is_special_dialog(dialog_name)) then
-- d_trade, d_got_item, d_dynamic and d_end are not imported because they need to be handled diffrently
table.insert(log, "Note: Not importing dialog text for \""..tostring(dialog_name).."\" because it is a special dialog.")
-- the options of thes special dialogs are still relevant
return dialog_name
end
-- does a dialog with name d_name already exist?
local d_id = yl_speak_up.d_name_to_d_id(dialog, dialog_name)
-- name the thing for logging purposes
local log_str = "Dialog "..tostring(d_id)
if(dialog_name and dialog_name ~= d_id) then
log_str = log_str.." ["..tostring(dialog_name).."]:"
else
log_str = log_str..": "
end
local is_new = false
if(not(d_id)) then
local next_id = nil
-- if dialog_name matches the d_<nr> pattern but d_<nr> does not exist,
-- then try to create *that* dialog
if(dialog_name and string.sub(dialog_name, 1, 2) == "d_") then
next_id = tonumber(string.sub(dialog_name, 3))
end
-- pname is nil - thus no logging and no adding of a back to start option
-- next_id is also usually nil - so just add a new dialog
d_id = yl_speak_up.add_new_dialog(dialog, nil, next_id, dialog_text)
if(not(d_id)) then
-- the creation may have failed (i.e. dialog not beeing a dialog,
-- or too many dialogs in dialog already)
table.insert(log, log_str.."FAILED to create new dialog.")
return nil
end
-- we got a new name for the log
log_str = "New dialog "..tostring(d_id).." ["..tostring(dialog_name).."]: "
is_new = true
table.insert(log, log_str.." Created successfully.")
elseif(dialog.n_dialogs[d_id].d_text ~= dialog_text) then
-- else update the text
table.insert(log, log_str.." Changed dialog text from \""..
tostring(dialog.n_dialogs[d_id].d_text).."\" to \""..tostring(dialog_text).."\".")
-- actually change the dialog text
dialog.n_dialogs[d_id].d_text = dialog_text
end
local d_data = dialog.n_dialogs[d_id]
-- set d_name if it differs from d_id
if(d_id ~= dialog_name
and (not(d_data.d_name)
or(d_data.d_name ~= dialog_name))) then
if(not(is_new)) then
-- log only if it's not a new dialog
table.insert(log, log_str.."Changed dialog name from \""..
tostring(d_data.d_name).."\" to \""..tostring(dialog_name).."\".")
end
-- actually change the dialog name
d_data.d_name = dialog_name
end
-- the random option is set for the dialog entire; we will have to process the individual
-- options in order to find out if this dialog is o_random; the first option that is sets
-- it for the dialog -> keep the old value
--d_data.o_random = nil
-- there may be existing options that won't get updated; deal with them:
-- remember which options the dialog has and which sort order they had
d_data.d_tmp_sorted_option_list = yl_speak_up.get_sorted_options(d_data.d_options or {}, "o_sort") or {}
-- this value is increased whenever an option gets updated - so that we can have options
-- that don't get an update sorted in after those options that did
d_data.d_tmp_sort_value = 1
-- mark all existing options as requirilng an update
for i, o_id in ipairs(d_data.d_tmp_sorted_option_list or {}) do
d_data.d_options[o_id].o_tmp_needs_update = true
end
-- mark this dialog as having received an update (meaning we won't have to update d_sort after
-- all dialogs have been updated)
d_data.d_tmp_has_been_updated = true
return d_id
end
-- helper function for update_dialog_options_completed;
-- adds a precondition of p_type "false" to the option so that the option is no longer displayed
-- if disable_option is false, then all preconditions of p_type "false" will be changed to p_type "true"
-- and thus the option will be shown to the player again
yl_speak_up.update_disable_dialog_option = function(o_data, disable_option)
-- is this otpion already deactivated?
local is_deactivated = false
for p_id, p in pairs(o_data.o_prerequisites or {}) do
if(p and p_id and p.p_type == "false") then
is_deactivated = true
-- if we want to re-enable the option, then this here is the place
if(not(disable_option)) then
-- change the type from false to true - this particular precondition
-- will now always be true
p.p_type = "true"
-- we continue work here because the player may have created multiple
-- options of this type
end
end
end
-- if not: add a precondition of type "false"
if(not(is_deactivated) and disable_option) then
-- we need to add a new precondition of type "false"
-- make sure we can add the prereq:
if(not(o_data.o_prerequisites)) then
o_data.o_prerequisites = {}
end
local future_p_id = "p_"..tostring(yl_speak_up.find_next_id(o_data.o_prerequisites))
-- we just added this option; this is the first and for now only precondition for it;
-- the player still has to adjust it, but at least it is a reasonable default
o_data.o_prerequisites[future_p_id] = { p_id = future_p_id, p_type = "false"}
end
end
-- call this *after* all dialog options have been updated for dialog_name
yl_speak_up.update_dialog_options_completed = function(log, dialog, d_id)
local d_data = dialog.n_dialogs[d_id]
if(not(d_data)) then
return
end
for i, o_id in ipairs(d_data.d_tmp_sorted_option_list or {}) do
local o_data = d_data.d_options[o_id]
if(o_data.o_tmp_needs_update) then
-- update the sort value so that this option will be listed *after* those
-- options that actually did get updated
o_data.o_sort = d_data.d_tmp_sort_value
d_data.d_tmp_sort_value = d_data.d_tmp_sort_value + 1
-- this option has now been processed
o_data.o_tmp_needs_update = nil
-- name the thing for logging purposes
local log_str = "Dialog "..tostring(d_id)
if(dialog_name and dialog_name ~= d_id) then
log_str = log_str.." ["..tostring(d_id).."]"
end
table.insert(log, log_str..", option <"..tostring(o_id)..">: "..
"Option exists in old dialog but not in import. Keeping option.")
-- add a precondition of p_type "false" to the option so that the option
-- is no longer displayed
yl_speak_up.update_disable_dialog_option(o_data, true)
end
end
-- clean up the dialog
d_data.d_tmp_sorted_option_list = nil
d_data.d_tmp_sort_value = nil
end
-- make sure only one dialog has d_sort set to 0 (and is thus the start dialog)
yl_speak_up.update_start_dialog = function(log, dialog, start_dialog_name, start_with_d_sort)
local start_d_id = yl_speak_up.d_name_to_d_id(dialog, start_dialog_name)
if(not(start_d_id)) then
return
end
for d_id, d in pairs(dialog.n_dialogs) do
if(d_id == start_d_id) then
if(not(d.d_sort) or d.d_sort ~= 0) then
table.insert(log, "Setting start dialog to "..tostring(start_dialog_name)..".")
end
d.d_sort = 0
-- the start dialog certainly is *a* start dialog (with the buttons)
d.is_a_start_dialog = true
elseif(not(d.d_tmp_has_been_updated)) then
-- sort this dialog behind the others
d.d_sort = start_with_d_sort
start_with_d_sort = start_with_d_sort + 1
end
d.d_tmp_has_been_updated = nil
end
end
-- add a new option/answer to dialog d_id with option_text (or default "")
-- option_text (optional) the text that shall be shown as option/answer
-- target_dialog (optional) the target dialog where the player will end up when choosing
-- this option/answer
-- Note: pname is only used for logging (and for changing o_sort)
yl_speak_up.add_new_option = function(dialog, pname, next_id, d_id, option_text, target_dialog)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])) then
return nil
end
if dialog.n_dialogs[d_id].d_options == nil then
-- make sure d_options exists
dialog.n_dialogs[d_id].d_options = {}
else
-- we don't want an infinite amount of answers per dialog
local sorted_list = yl_speak_up.get_sorted_options(dialog.n_dialogs[d_id].d_options, "o_sort")
local anz_options = #sorted_list
if(anz_options >= yl_speak_up.max_number_of_options_per_dialog) then
-- nothing added
return nil
end
end
if(not(next_id)) then
next_id = yl_speak_up.find_next_id(dialog.n_dialogs[d_id].d_options)
end
local future_o_id = "o_" .. next_id
dialog.n_dialogs[d_id].d_options[future_o_id] = {
o_id = future_o_id,
o_hide_when_prerequisites_not_met = "false",
o_grey_when_prerequisites_not_met = "false",
o_sort = -1,
o_text_when_prerequisites_not_met = "",
o_text_when_prerequisites_met = (option_text or ""),
}
local start_with_o_sort = nil
if(pname and pname ~= "") then
-- log only in edit mode
local n_id = yl_speak_up.speak_to[pname].n_id
-- would be too difficult to add an exception for edit_mode here; thus, we do it directly here:
if(yl_speak_up.npc_was_changed
and yl_speak_up.npc_was_changed[n_id]) then
table.insert(yl_speak_up.npc_was_changed[ n_id ],
"Dialog "..d_id..": Added new option/answer "..future_o_id..".")
end
start_with_o_sort = yl_speak_up.speak_to[pname].o_sort
end
-- necessary in order for it to work
local new_o_sort = yl_speak_up.sanitize_sort(dialog.n_dialogs[d_id].d_options, start_with_o_sort)
dialog.n_dialogs[d_id].d_options[future_o_id].o_sort = new_o_sort
-- letting d_got_item point back to itself is not a good idea because the
-- NPC will then end up in a loop; plus the d_got_item dialog is intended for
-- automatic processing, not for showing to the player
if(d_id == "d_got_item") then
-- unless the player specifies something better, we go back to the start dialog
-- (that is where d_got_item got called from anyway)
target_dialog = yl_speak_up.get_start_dialog_id(dialog)
-- ...and this option needs to be selected automaticly
dialog.n_dialogs[d_id].d_options[future_o_id].o_autoanswer = 1
elseif(d_id == "d_trade") then
-- we really don't want to go to another dialog from here
target_dialog = "d_trade"
-- ...and this option needs to be selected automaticly
dialog.n_dialogs[d_id].d_options[future_o_id].o_autoanswer = 1
end
local future_r_id = nil
-- create a fitting dialog result automaticly if possible:
-- give this new dialog a dialog result that leads back to this dialog
-- (which is more helpful than creating tons of empty dialogs)
if(target_dialog and (dialog.n_dialogs[target_dialog] or target_dialog == "d_end")) then
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
-- actually store the new result
dialog.n_dialogs[d_id].d_options[future_o_id].o_results = {}
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "dialog",
r_value = target_dialog}
end
-- the d_got_item dialog is special; players can easily forget to add the
-- necessary preconditions and effects, so we do that manually here
if(d_id == "d_got_item") then
-- we also need a precondition so that the o_autoanswer can actually get called
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites = {}
-- we just added this option; this is the first and for now only precondition for it;
-- the player still has to adjust it, but at least it is a reasonable default
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites["p_1"] = {
p_id = "p_1",
p_type = "player_offered_item",
p_item_stack_size = tostring(next_id),
p_match_stack_size = "exactly",
-- this is just a simple example item and ought to be changed after adding
p_value = "default:stick "..tostring(next_id)}
-- we need to show the player that his action was successful
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id].alternate_text =
"Thank you for the "..tostring(next_id).." stick(s)! "..
"Never can't have enough sticks.\n$TEXT$"
-- we need an effect for accepting the item;
-- taking all that was offered and putting it into the NPC's inventory is a good default
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "deal_with_offered_item",
r_value = "take_all"}
-- the trade dialog is equally special
elseif(d_id == "d_trade") then
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites = {}
-- this is just an example
dialog.n_dialogs[d_id].d_options[future_o_id].o_prerequisites["p_1"] = {
p_id = "p_1",
p_type = "npc_inv",
p_value = "inv_does_not_contain",
p_inv_list_name = "npc_main",
p_itemstack = "default:stick "..tostring(100-next_id)}
future_r_id = yl_speak_up.add_new_result(dialog, d_id, future_o_id)
-- example craft
dialog.n_dialogs[d_id].d_options[future_o_id].o_results[future_r_id] = {
r_id = future_r_id,
r_type = "craft",
r_value = "default:stick 4",
o_sort = "1",
r_craft_grid = {"default:wood", "", "", "", "", "", "", "", ""}}
end
return future_o_id
end
-- update existing or create a new option named option_name for dialog dialog_name
-- If option_name starts with..
-- new_ create a new option (discard the rest of option_name)
-- automaticly_ set o_autoanswer
-- randomly_ set o_random *for the dialog*
-- grey_out_ set o_text_when_prerequisites_not_met
-- ..and take what remains as option_name.
-- (useful for import from ink and likewise functionality)
--
-- TODO: these notes need to be taken care of in the calling function
-- Note: The calling function may need to adjust o_sort according to its needs.
-- Note: Preconditions, actions and effects are not handled here (apart from the "dialog"
-- effect/result for the redirection to the target dialog)
yl_speak_up.update_dialog_option = function(log, dialog, dialog_name, option_name,
option_text, option_text_if_preconditions_false,
target_dialog, alternate_text, visit_only_once, sort_order)
-- does the dialog we want to add to exist?
local d_id = yl_speak_up.d_name_to_d_id(dialog, dialog_name)
if(not(d_id)) then
if(not(yl_speak_up.is_special_dialog(dialog_name))) then
-- the dialog does not exist - we cannot add an option to a nonexistant dialog
return nil
end
-- options for special dialogs have to start with "automaticly_"
local parts = string.split(option_name or "", "_")
if(not(parts) or not(parts[1]) or parts[1] ~= "automaticly") then
option_name = "automaticly_"..table.concat(parts[2], "_")
end
-- for d_trade and d_got_item effects and preconditions are created WITH DEFAULT VALUES TODO
d_id = dialog_name
-- make sure the relevant dialog and fields exist
dialog.n_dialogs[d_id] = dialog.n_dialogs[d_id] or {}
dialog.n_dialogs[d_id].d_options = dialog.n_dialogs[d_id].d_options or {}
end
-- name the thing for logging purposes
local log_str = "Dialog "..tostring(d_id)
if(dialog_name and dialog_name ~= d_id) then
log_str = log_str.." ["..tostring(dialog_name).."]"
end
log_str = log_str..", option <"..tostring(option_name)..">: "
local is_new = false
-- translate the name of the target_dialog if needed
if(target_dialog and not(yl_speak_up.is_special_dialog(target_dialog))) then
target_dialog = yl_speak_up.d_name_to_d_id(dialog, target_dialog)
end
-- TODO: dialogs d_got_item and d_trade are special
local o_id = option_name
local mode = 0
local text_when_prerequisites_not_met = ""
local parts = string.split(o_id, "_")
if(not(parts) or not(parts[1]) or not(parts[2])) then
table.insert(log, log_str.."FAILED to create unknown option \""..tostring(o_id).."\".")
return nil
elseif(o_id and parts[1] == "new") then
-- we are asked to create a *new* option
o_id = nil
elseif(o_id and parts[1] == "automaticly") then
-- this option will be automaticly selected if its preconditions are true
mode = 1
option_name = parts[2]
o_id = option_name
elseif(o_id and parts[1] == "randomly") then
-- this option will be randomly selected if its preconditions are true;
-- (that means all other options of this dialog will have to be randomly as well;
-- something which cannot be done here as there is no guarantee that all options
-- *exist* at this point)
mode = 2
option_name = parts[2]
o_id = option_name
elseif(o_id and parts[1] ~= "o") then
table.insert(log, log_str.."FAILED to create unknown option \""..tostring(o_id).."\".")
return nil
end
-- if the option does not exist: create it
if( not(dialog.n_dialogs[d_id].d_options)
or not(o_id) or o_id == ""
or not(dialog.n_dialogs[d_id].d_options[o_id])) then
local next_id = nil
-- get the id part (number) from o_id - because we may be creating a new option here -
-- but said option may have a diffrent *name* than what a new option would get by
-- default
if(o_id) then
next_id = string.sub(o_id, 3)
if(next_id == "" or not(tonumber(next_id))) then
next_id = nil
table.insert(log, log_str.."FAILED to create new option \""..tostring(o_id).."\".")
return
end
end
-- pname is nil - thus no logging here
o_id = yl_speak_up.add_new_option(dialog, nil, next_id, d_id, option_text, target_dialog)
if(not(o_id)) then
return nil
end
is_new = true
end
-- abbreviate that
local o_data = dialog.n_dialogs[d_id].d_options[o_id]
-- cchnage option_text if needed
if(o_data.o_text_when_prerequisites_met ~= option_text) then
table.insert(log, log_str.."Changed option text from \""..
tostring(o_data.o_text_when_prerequisites_met)..
"\" to \""..tostring(option_text).."\" for option \""..tostring(o_id).."\".")
end
-- actually update the text
o_data.o_text_when_prerequisites_met = option_text
-- chnage greyed out text if needed
if(o_data.o_text_when_prerequisites_not_met ~= option_text_if_preconditions_false
and option_text_if_preconditions_false) then
table.insert(log, log_str.."Changed greyed out text when prerequisites not met from \""..
tostring(o_data.o_text_when_prerequisites_not_met)..
"\" to \""..tostring(option_text_if_preconditions_false or "")..
"\" for option \""..tostring(o_id).."\".")
-- make sure the greyed out text gets shown (or not shown)
o_data.o_text_when_prerequisites_not_met = option_text_if_preconditions_false or ""
end
-- make grey_out_ text visible if necessary
if(o_data.o_text_when_prerequisites_not_met and o_data.o_text_when_prerequisites_not_met ~= ""
and option_text_if_preconditions_false and option_text_if_preconditions_false ~= "") then
-- make sure this text is really shown - and greyed out
-- (resetting this can only happen through editing the NPC directly; not through import)
o_data.o_hide_when_prerequisites_not_met = "false"
o_data.o_grey_when_prerequisites_not_met = "true"
else
-- if this were not set to true, then the player would see a clickable button for
-- the option - but that button would do nothing
o_data.o_hide_when_prerequisites_not_met = "true"
o_data.o_grey_when_prerequisites_not_met = "false"
end
local r_found = false
-- the target_dialog may have been changed
for r_id, r in pairs(o_data.o_results or {}) do
-- we found the right result/effect that holds the (current) target_dialog
if(r and r.r_type and r.r_type == "dialog") then
r_found = true
if(not(r.r_value) or r.r_value ~= target_dialog) then
if(is_new) then
table.insert(log, log_str.."Successfully created new option \""..
tostring(o_id).."\" with target dialog \""..
tostring(target_dialog).."\".")
else
table.insert(log, log_str.."Changed target dialog from \""..
tostring(r.r_value).."\" to \""..tostring(target_dialog)..
"\" for option \""..tostring(o_id).."\".")
end
-- actually change the target dialog
r.r_value = target_dialog
end
-- the alternate_text may have been changed
if(r.alternate_text ~= alternate_text) then
table.insert(log, log_str.."Changed alternate text from \""..
tostring(r.r_alternate_text).."\" to \""..tostring(alternate_text)..
"\" for option \""..tostring(o_id).."\".")
r.alternate_text = alternate_text
end
end
end
-- for some reason the effect pointing to the target dialog got lost!
if(r_found and is_new) then
table.insert(log, log_str.."Set target dialog to "..tostring(target_dialog)..
" for new option \""..tostring(o_id).."\".")
end
if(not(r_found)) then
-- create the result/effect that points to the target_dialog
local r_id = yl_speak_up.add_new_result(dialog, d_id, o_id)
if(r_id) then
o_data.o_results[r_id].r_type = "dialog"
o_data.o_results[r_id].r_value = target_dialog
o_data.o_results[r_id].alternate_text = alternate_text
table.insert(log, log_str.."Set target dialog to "..tostring(target_dialog)..
" for option \""..tostring(o_id).."\".")
end
end
-- "randomly selected" applies to the *dialog* - it is set there and not in the individual option
local d_data = dialog.n_dialogs[d_id]
-- is this option selected randomly?
if( mode == 2 and not(d_data.o_random)) then
table.insert(log, log_str.."Changed DIALOG \""..tostring(d_id).."\" to RANDOMLY SELECTED.")
d_data.o_random = 1
end
-- is this option selected automaticly if all preconditions are met?
if(mode == 1 and not(o_data.o_autoanswer)) then
o_data.o_autoanswer = 1
table.insert(log, log_str.."Changed option \""..tostring(o_id).."\" to AUTOMATICLY SELECTED.")
-- mode is 0 - that means everything is normal for this option
elseif(mode ~= 1 and o_data.o_autoanswer) then
o_data.o_autoanswer = nil
table.insert(log, log_str.."Removed AUTOMATICLY SELECTED from option \""..tostring(o_id).."\".")
end
-- the visit_only_once option is handled without logging as it might create too many
-- entries in the log without adding any helpful information
if(visit_only_once
and (not(o_data.o_visit_only_once)
or o_data.o_visit_only_once ~= 1)) then
o_data.o_visit_only_once = 1
elseif(not(visit_only_once)
and o_data.o_visit_only_once and o_data.o_visit_only_once == 1) then
o_data.o_visit_only_once = nil
end
-- set sort order of options (no logging because that might get too spammy)
if(sort_order) then
o_data.o_sort = sort_order
end
-- this option has been updated
o_data.o_tmp_needs_update = false
if(o_data.o_sort and d_data.d_tmp_sort_value and o_data.o_sort >= d_data.d_tmp_sort_value) then
-- make sure this stores the highest o_sort value we found
d_data.d_tmp_sort_value = o_data.o_sort + 1
end
return o_id
end
-- add a new result to option o_id of dialog d_id
yl_speak_up.add_new_result = function(dialog, d_id, o_id)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return
end
-- create a new result (first the id, then the actual result)
local future_r_id = "r_" .. yl_speak_up.find_next_id(dialog.n_dialogs[d_id].d_options[o_id].o_results)
if future_r_id == "r_1" then
dialog.n_dialogs[d_id].d_options[o_id].o_results = {}
end
dialog.n_dialogs[d_id].d_options[o_id].o_results[future_r_id] = {}
dialog.n_dialogs[d_id].d_options[o_id].o_results[future_r_id].r_id = future_r_id
return future_r_id
end
-- TODO: we need yl_speak_up.update_dialog_option_result as well
-- this is useful for result types that can exist only once per option
-- (apart from editing with the staff);
-- examples: "dialog" and "trade";
-- returns tue r_id or nil if no result of that type has been found
yl_speak_up.get_result_id_by_type = function(dialog, d_id, o_id, result_type)
if(not(dialog) or not(dialog.n_dialogs) or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_options) or not(dialog.n_dialogs[d_id].d_options[o_id])) then
return
end
local results = dialog.n_dialogs[d_id].d_options[o_id].o_results
if(not(results)) then
return
end
for k, v in pairs(results) do
if(v.r_type == result_type) then
return k
end
end
end
-- helper function for sorting options/answers using options[o_id].o_sort
-- (or dialogs by d_sort)
yl_speak_up.get_sorted_options = function(options, sort_by)
local sorted_list = {}
for k,v in pairs(options) do
table.insert(sorted_list, k)
end
table.sort(sorted_list,
function(a,b)
if(not(options[a][sort_by])) then
return false
elseif(not(options[b][sort_by])) then
return true
-- sadly not all entries are numeric
elseif(tonumber(options[a][sort_by]) and tonumber(options[b][sort_by])) then
return (tonumber(options[a][sort_by]) < tonumber(options[b][sort_by]))
-- numbers have a higher priority
elseif(tonumber(options[a][sort_by])) then
return true
elseif(tonumber(options[b][sort_by])) then
return false
-- if the value is the same: sort by index
elseif(options[a][sort_by] == options[b][sort_by]) then
return (a < b)
else
return (options[a][sort_by] < options[b][sort_by])
end
end
)
return sorted_list
end
-- simple sort of keys of a table numericly;
-- this is not efficient - but that doesn't matter: the lists are small and
-- it is only executed when configuring an NPC
-- simple: if the parameter is true, the keys will just be sorted (i.e. player names) - which is
-- not enough for d_<nr>, o_<nr> etc. (which need more care when sorting)
yl_speak_up.sort_keys = function(t, simple)
local keys = {}
for k, v in pairs(t) do
-- add a prefix so that p_2 ends up before p_10
if(not(simple) and string.len(k) == 3) then
k = "a"..k
end
table.insert(keys, k)
end
table.sort(keys)
if(simple) then
return keys
end
for i,k in ipairs(keys) do
-- avoid cutting the single a from a_1 (action 1)
if(k and string.sub(k, 1, 1) == "a" and string.sub(k, 2, 2) ~= "_") then
-- remove the leading blank
keys[i] = string.sub(k, 2)
end
end
return keys
end
-- checks if dialog contains d_id and o_id
yl_speak_up.check_if_dialog_has_option = function(dialog, d_id, o_id)
return (dialog and d_id and o_id
and dialog.n_dialogs
and dialog.n_dialogs[d_id]
and dialog.n_dialogs[d_id].d_options
and dialog.n_dialogs[d_id].d_options[o_id])
end
-- checks if dialog exists
yl_speak_up.check_if_dialog_exists = function(dialog, d_id)
return (dialog and d_id
and dialog.n_dialogs
and dialog.n_dialogs[d_id])
end
yl_speak_up.is_special_dialog = function(d_id)
if(not(d_id)) then
return false
end
return (d_id == "d_trade" or d_id == "d_got_item" or d_id == "d_dynamic" or d_id == "d_end")
end
yl_speak_up.d_name_to_d_id = function(dialog, d_name)
if(not(dialog) or not(dialog.n_dialogs) or not(d_name) or d_name == "") then
return nil
end
-- it is already the ID of an existing dialog
if(dialog.n_dialogs[d_name]) then
return d_name
end
-- search all dialogs for one with a fitting d_name
for k,v in pairs(dialog.n_dialogs) do
if(v and v.d_name and v.d_name == d_name) then
return k
end
end
return nil
end
-- get the name of a dialog (reverse of above)
yl_speak_up.d_id_to_d_name = function(dialog, d_id)
if(not(dialog) or not(dialog.n_dialogs) or not(d_id) or d_id == ""
or not(dialog.n_dialogs[d_id])
or not(dialog.n_dialogs[d_id].d_name)
or dialog.n_dialogs[d_id].d_name == "") then
return d_id
end
return dialog.n_dialogs[d_id].d_name
end
-- how many own (not special, not generic) dialogs does the NPC have?
yl_speak_up.count_dialogs = function(dialog)
local count = 0
if(not(dialog) or not(dialog.n_dialogs)) then
return 0
end
for d_id, v in pairs(dialog.n_dialogs) do
if(d_id
and not(yl_speak_up.is_special_dialog(d_id))
and not(dialog.n_dialogs[d_id].is_generic)) then
count = count + 1
end
end
return count
end

View File

@ -0,0 +1,112 @@
--###
--Load and Save
--###
local function save_path(n_id)
return yl_speak_up.worldpath .. yl_speak_up.path .. DIR_DELIM .. n_id .. ".json"
end
-- we can't really log changes here in this function because we don't know *what* has been changed
yl_speak_up.save_dialog = function(n_id, dialog)
if type(n_id) ~= "string" or type(dialog) ~= "table" then
return false
end
local p = save_path(n_id)
-- save some data (in particular usage of quest variables)
yl_speak_up.update_stored_npc_data(n_id, dialog)
-- make sure we never store any automaticly added generic dialogs
dialog = yl_speak_up.strip_generic_dialogs(dialog)
-- never store d_dynamic dialogs
if(dialog.n_dialogs and dialog.n_dialogs["d_dynamic"]) then
dialog.n_dialogs["d_dynamic"] = nil
end
local content = minetest.write_json(dialog)
return minetest.safe_file_write(p, content)
end
-- if a player is supplied: include generic dialogs
yl_speak_up.load_dialog = function(n_id, player) -- returns the saved dialog
local p = save_path(n_id)
-- note: add_generic_dialogs will also add an empty d_dynamic dialog
local file, err = io.open(p, "r")
if err then
return yl_speak_up.add_generic_dialogs({}, n_id, player)
end
io.input(file)
local content = io.read()
local dialog = minetest.parse_json(content)
io.close(file)
if type(dialog) ~= "table" then
dialog = {}
end
return yl_speak_up.add_generic_dialogs(dialog, n_id, player)
end
-- this deletes the dialog with id d_id from the npc n_id's dialogs;
-- it loads the dialogs from the npc's savefile, deletes dialog d_id,
-- and then saves the dialogs back to the npc's savefile in order to
-- keep things consistent
yl_speak_up.delete_dialog = function(n_id, d_id)
if d_id == yl_speak_up.text_new_dialog_id then
return false
end -- We don't delete "New dialog"
local dialog = yl_speak_up.load_dialog(n_id, false)
dialog.n_dialogs[d_id] = nil
yl_speak_up.save_dialog(n_id, dialog)
end
-- used by staff and input_inital_config
yl_speak_up.fields_to_dialog = function(pname, fields)
local n_id = yl_speak_up.speak_to[pname].n_id
local dialog = yl_speak_up.load_dialog(n_id, false)
local save_d_id = ""
if next(dialog) == nil then -- No file found. Let's create the basic values
dialog = {}
dialog.n_dialogs = {}
end
if dialog.n_dialogs == nil or next(dialog.n_dialogs) == nil then --No dialogs found. Let's make a table
dialog.n_dialogs = {}
end
if fields.d_text ~= "" then -- If there is dialog text, then save new or old dialog
if fields.d_id == yl_speak_up.text_new_dialog_id then --New dialog --
-- Find highest d_id and increase by 1
save_d_id = "d_" .. yl_speak_up.find_next_id(dialog.n_dialogs)
-- Initialize empty dialog
dialog.n_dialogs[save_d_id] = {}
else -- Already existing dialog
save_d_id = fields.d_id
end
-- Change dialog
dialog.n_dialogs[save_d_id].d_id = save_d_id
dialog.n_dialogs[save_d_id].d_type = "text"
dialog.n_dialogs[save_d_id].d_text = fields.d_text
dialog.n_dialogs[save_d_id].d_sort = fields.d_sort
end
--Context
yl_speak_up.speak_to[pname].d_id = save_d_id
-- Just in case the NPC vlaues where changed or set
dialog.n_id = n_id
dialog.n_description = fields.n_description
dialog.n_npc = fields.n_npc
dialog.npc_owner = fields.npc_owner
return dialog
end

328
functions_talk.lua Normal file
View File

@ -0,0 +1,328 @@
--###
-- Init
--###
-- self (the npc as such) is rarely passed on to any functions; in order to be able to check if
-- the player really owns the npc, we need to have that data available;
-- format: yl_speak_up.npc_owner[ npc_id ] = owner_name
yl_speak_up.npc_owner = {}
-- store the current trade between player and npc in case it gets edited in the meantime
yl_speak_up.trade = {}
-- store what the player last entered in an text_input action
yl_speak_up.last_text_input = {}
--###
-- Debug
--###
yl_speak_up.debug = true
---###
-- general formpsec
---###
yl_speak_up.get_error_message = function()
local formspec = {
"size[13.4,8.5]",
"bgcolor[#FF0000]",
"label[0.2,0.35;Please save a NPC file first]",
"button_exit[0.2,7.7;3,0.75;button_back;Back]"
}
return table.concat(formspec, "")
end
yl_speak_up.get_sorted_dialog_name_list = function(dialog)
local liste = {}
if(dialog and dialog.n_dialogs) then
for k, v in pairs(dialog.n_dialogs) do
-- this will be used for dropdown lists - so we use formspec_escape
table.insert(liste, minetest.formspec_escape(v.d_name or k or "?"))
end
-- sort alphabethicly
table.sort(liste)
end
return liste
end
---###
-- player related
---###
yl_speak_up.reset_vars_for_player = function(pname, reset_fs_version)
yl_speak_up.speak_to[pname] = nil
yl_speak_up.last_text_input[pname] = nil
-- when just stopping editing: don't reset the fs_version
if(reset_fs_version) then
yl_speak_up.fs_version[pname] = nil
end
end
---###
-- player and npc related
---###
-- identify multiple results that lead to target dialogs
yl_speak_up.check_for_disambigous_results = function(n_id, pname)
local errors_found = false
-- this is only checked when trying to edit this npc;
-- let's stick to check the dialogs of this one without generic dialogs
local dialog = yl_speak_up.load_dialog(n_id, false)
-- nothing defined yet - nothing to repair
if(not(dialog.n_dialogs)) then
return
end
-- iterate over all dialogs
for d_id, d in pairs(dialog.n_dialogs) do
if(d_id and d and d.d_options) then
-- iterate over all options
for o_id, o in pairs(d.d_options) do
if(o_id and o and o.o_results) then
local dialog_results = {}
-- iterate over all results
for r_id, r in pairs(o.o_results) do
if(r.r_type == "dialog") then
table.insert(dialog_results, r_id)
end
end
if(#dialog_results>1) then
local msg = "ERROR: Dialog "..
tostring(d_id)..", option "..tostring(o_id)..
", has multiple results of type dialog: "..
minetest.serialize(dialog_results)..". Please "..
"let someone with npc_master priv fix that first!"
yl_speak_up.log_change(pname, n_id, msg, "error")
if(pname) then
minetest.chat_send_player(pname, msg)
end
errors_found = true
end
end
end
end
end
return errors_found
end
-- returns true if someone is speaking to the NPC
yl_speak_up.npc_is_in_conversation = function(n_id)
for name, data in pairs(yl_speak_up.speak_to) do
if(data and data.n_id and data.n_id == n_id) then
return true
end
end
return false
end
-- returns a list of players that are in conversation with this NPC
yl_speak_up.npc_is_in_conversation_with = function(n_id)
local liste = {}
for name, data in pairs(yl_speak_up.speak_to) do
if(data and data.n_id and data.n_id == n_id) then
table.insert(liste, name)
end
end
return liste
end
-- Make the NPC talk
-- assign n_ID
-- usually this happens when talking to the NPC for the first time;
-- but if you want to you can call this function earlier (on spawn)
-- so that logging of spawning with the ID is possible
yl_speak_up.initialize_npc = function(self)
-- already configured?
if(not(self) or (self.yl_speak_up and self.yl_speak_up.id)) then
return self
end
local m_talk = yl_speak_up.talk_after_spawn or true
local m_id = yl_speak_up.number_of_npcs + 1
yl_speak_up.number_of_npcs = m_id
yl_speak_up.modstorage:set_int("amount", m_id)
self.yl_speak_up = {
talk = m_talk,
id = m_id,
textures = self.textures
}
return self
end
function yl_speak_up.talk(self, clicker)
if not clicker and not clicker:is_player() then
return
end
if not self then
return
end
local id_prefix = "n"
-- we are not dealing with an NPC but with a position/block on the map
if(self.is_block) then
id_prefix = "p"
local owner = "- unknown -"
local talk_name = "- unknown -"
if(self.pos and self.pos and self.pos.x) then
local meta = minetest.get_meta(self.pos)
if(meta) then
owner = meta:get_string("owner") or ""
talk_name = meta:get_string("talk_name") or ""
end
end
self.yl_speak_up = {
is_block = true,
talk = true,
id = minetest.pos_to_string(self.pos, 0),
textures = {},
owner = owner,
npc_name = talk_name,
object = nil, -- blocks don't have an object
}
-- TODO: remember somewhere that this block is relevant
-- initialize the mob if necessary; this happens at the time of first talk, not at spawn time!
elseif(not(self.yl_speak_up) or not(self.yl_speak_up.id)) then
self = yl_speak_up.initialize_npc(self)
end
local npc_id = self.yl_speak_up.id
local n_id = id_prefix.."_" .. npc_id
-- remember whom the npc belongs to (as long as we still have self.owner available for easy access)
yl_speak_up.npc_owner[ n_id ] = self.owner
local pname = clicker:get_player_name()
if not self.yl_speak_up or not self.yl_speak_up.talk or self.yl_speak_up.talk~=true then
local was = "This NPC"
if(id_prefix ~= "n") then
was = "This block"
end
-- show a formspec to other players that this NPC is busy
if(not(yl_speak_up.may_edit_npc(clicker, n_id))) then
-- show a formspec so that the player knows that he may come back later
yl_speak_up.show_fs(player, "msg", {input_to = "yl_spaek_up:ignore", formspec =
"size[6,2]"..
"label[1.2,0.0;"..minetest.formspec_escape((self.yl_speak_up.npc_name or was)..
" [muted]").."]"..
"label[0.2,0.5;Sorry! I'm currently busy learning new things.]"..
"label[0.2,1.0;Please come back later.]"..
"button_exit[2.5,1.5;1,0.9;ok;Ok]"})
return
end
-- allow the owner to edit (and subsequently unmute) the npc
minetest.chat_send_player(pname, was.." is muted. It will only talk to you.")
end
yl_speak_up.speak_to[pname] = {}
yl_speak_up.speak_to[pname].n_id = n_id -- Memorize which player talks to which NPC
yl_speak_up.speak_to[pname].textures = self.yl_speak_up.textures
yl_speak_up.speak_to[pname].option_index = 1
-- the object itself may be needed in load_dialog for adding generic dialogs
yl_speak_up.speak_to[pname].obj = self.object
-- this makes it a bit easier to access some values later on:
yl_speak_up.speak_to[pname]._self = self
-- Load the dialog and see what we can do with it
-- this inculdes generic dialog parts;
yl_speak_up.speak_to[pname].dialog = yl_speak_up.load_dialog(n_id, clicker)
-- is this player explicitly allowed to edit this npc?
if(yl_speak_up.speak_to[pname].dialog
and yl_speak_up.speak_to[pname].dialog.n_may_edit
and yl_speak_up.speak_to[pname].dialog.n_may_edit[pname]
and minetest.check_player_privs(clicker, {npc_talk_owner=true})) then
yl_speak_up.speak_to[pname].may_edit_this_npc = true
end
local dialog = yl_speak_up.speak_to[pname].dialog
if(not(dialog.trades)) then
dialog.trades = {}
end
-- create a detached inventory for the npc and load its inventory
yl_speak_up.load_npc_inventory(id_prefix.."_"..tostring(self.yl_speak_up.id), false, dialog)
-- some NPC may have reset the animation; at least set it to the desired
-- value whenever we talk to the NPC
if self.yl_speak_up and self.yl_speak_up.animation then
self.object:set_animation(self.yl_speak_up.animation)
end
-- maintain a list of existing NPC, but do not force saving
yl_speak_up.update_npc_data(self, dialog, false)
yl_speak_up.show_fs(clicker, "talk", {n_id = n_id})
end
-- mute the npc; either via the appropriate staff or via talking to him
yl_speak_up.set_muted = function(p_name, obj, set_muted)
if(not(obj)) then
return
end
local luaentity = obj:get_luaentity()
if(not(luaentity)) then
return
end
local npc = luaentity.yl_speak_up.id
local npc_name = luaentity.yl_speak_up.npc_name
-- fallback
if(not(npc_name)) then
npc_name = npc
end
if(set_muted and luaentity.yl_speak_up.talk) then
-- the npc is willing to talk
luaentity.yl_speak_up.talk = false
yl_speak_up.update_nametag(luaentity)
-- minetest.chat_send_player(p_name,"NPC with ID n_"..npc.." will shut up at pos "..
-- minetest.pos_to_string(obj:get_pos(),0).." on command of "..p_name)
minetest.chat_send_player(p_name, "NPC n_"..tostring(npc).." is now muted and will "..
"only talk to those who can edit the NPC.")
yl_speak_up.log_change(p_name, "n_"..npc, "muted - NPC stops talking")
elseif(not(set_muted) and not(luaentity.yl_speak_up.talk)) then
-- mute the npc
luaentity.yl_speak_up.talk = true
yl_speak_up.update_nametag(luaentity)
minetest.chat_send_player(p_name, "NPC n_"..tostring(npc).." is no longer muted and "..
"will talk with any player who right-clicks the NPC.")
-- minetest.chat_send_player(p_name,"NPC with ID n_"..npc.." will resume speech at pos "..
-- minetest.pos_to_string(obj:get_pos(),0).." on command of "..p_name)
yl_speak_up.log_change(p_name, "n_"..npc, "unmuted - NPC talks again")
end
end
-- has the player the right privs?
-- this is used for the "I am your master" talk based configuration; *NOT* for the staffs!
yl_speak_up.may_edit_npc = function(player, n_id)
if(not(player)) then
return false
end
local pname = player:get_player_name()
-- is the player allowed to edit this npc?
return ((yl_speak_up.npc_owner[ n_id ] == pname
and minetest.check_player_privs(player, {npc_talk_owner=true}))
or minetest.check_player_privs(player, {npc_talk_master=true})
or minetest.check_player_privs(player, {npc_master=true})
or (yl_speak_up.speak_to[pname]
and yl_speak_up.speak_to[pname].may_edit_this_npc))
end

1039
import_from_ink.lua Normal file

File diff suppressed because it is too large Load Diff

102
init.lua
View File

@ -1,5 +1,8 @@
yl_speak_up = {}
-- human-readable version
yl_speak_up.version = "05.11.23 first publication"
-- some mods (i.e. yl_speak_up_addons) need to be called again when
-- this mod is reloaded via chat command "/npc_talk_reload";
-- this is a list of such functions
@ -141,21 +144,22 @@ yl_speak_up.reload = function(modpath, log_entry)
minetest.mkdir(yl_speak_up.worldpath..yl_speak_up.quest_path)
-- logging and showing the log
dofile(modpath .. "fs_show_log.lua")
dofile(modpath .. "api/api_logging.lua")
-- players *and* npc need privs for certain things; this here handles the NPC side of things
dofile(modpath .. "npc_privs.lua")
-- add generic dialogs
dofile(modpath .. "add_generic_dialogs.lua")
-- handle on_player_receive_fields and showing of formspecs
dofile(modpath .. "show_fs.lua")
-- needs to be registered after show_fs.lua so that it can register its formspecs:
dofile(modpath .. "fs/fs_show_log.lua")
-- general decoration part for main formspec, trade window etc.
dofile(modpath .. "fs_decorated.lua")
dofile(modpath .. "api/api_decorated.lua")
-- change dialog d_dynamic via an extra function on the fly when the player talks to the NPC:
dofile(modpath .. "dynamic_dialog.lua")
-- the formspec and input handling for the main dialog
dofile(modpath .. "fs_talkdialog.lua")
-- ask if the player wants to save, discard or go back in edit mode
dofile(modpath .. "fs_save_or_discard_or_back.lua")
-- the player wants to change something regarding the dialog
dofile(modpath .. "edit_mode_apply_changes.lua")
dofile(modpath .. "api/api_talk.lua")
dofile(modpath .. "fs/fs_talkdialog.lua")
-- As the name says: a collection of custom functions that you can
-- override on your server or in your game to suit your needs;
@ -164,80 +168,78 @@ yl_speak_up.reload = function(modpath, log_entry)
-- the server's file system or can execute lua code.
-- Note: Please do not edit this file. Instead, create and edit the
-- file "local_server_do_on_reload.lua"!
dofile(modpath .. "custom_functions_you_can_override.lua")
-- assign a quest step to a dialog option/answer
dofile(modpath .. "fs_assign_quest_step.lua")
dofile(modpath .. "api/custom_functions_you_can_override.lua")
-- execute preconditions, actions and effects
dofile(modpath .. "exec_eval_preconditions.lua")
dofile(modpath .. "exec_actions.lua")
-- the formspecs for the actions:
dofile(modpath .. "fs/fs_action_npc_wants.lua")
dofile(modpath .. "fs/fs_action_npc_gives.lua")
dofile(modpath .. "fs/fs_action_text_input.lua")
dofile(modpath .. "fs/fs_action_evaluate.lua")
-- execute and apply effects:
dofile(modpath .. "exec_apply_effects.lua")
-- some helper functions for formatting text for a formspec talbe
dofile(modpath .. "print_as_table.lua")
-- create i.e. a dropdown list of player names
dofile(modpath .. "formspec_helpers.lua")
-- handle alternate text for dialogs
dofile(modpath .. "fs_alternate_text.lua")
-- debugging - extended information about what (your own) NPC does
dofile(modpath .. "npc_talk_debug.lua")
-- execute lua code directly (preconditions and effects) - requires priv
dofile(modpath .. "eval_and_execute_function.lua")
-- common functions for editing preconditions and effects
dofile(modpath .. "fs_edit_general.lua")
-- edit preconditions (can be reached through edit options dialog)
dofile(modpath .. "fs_edit_preconditions.lua")
-- edit actions (can be reached through edit options dialog)
dofile(modpath .. "fs_edit_actions.lua")
-- edit effects (can be reached through edit options dialog)
dofile(modpath .. "fs_edit_effects.lua")
-- edit options dialog (detailed configuration of options in edit mode)
dofile(modpath .. "fs_edit_options_dialog.lua")
-- provide the NPC with an initial (example) dialog and store name, descr and owner:
dofile(modpath .. "initial_config.lua")
-- set name, description and owner (owner only with npc_talk_master priv)
dofile(modpath .. "fs_initial_config.lua")
dofile(modpath .. "fs/fs_initial_config.lua")
-- inspect and accept items the player gave to the NPC
dofile(modpath .. "fs_player_offers_item.lua")
dofile(modpath .. "fs/fs_player_offers_item.lua")
-- inventory management, trading and handling of quest items:
dofile(modpath .. "inventory.lua")
dofile(modpath .. "api/api_inventory.lua")
dofile(modpath .. "fs/fs_inventory.lua")
-- limit how much the NPC shall buy and sell
dofile(modpath .. "fs_trade_limit.lua")
dofile(modpath .. "fs_edit_trade_limit.lua")
dofile(modpath .. "api/api_trade.lua")
dofile(modpath .. "fs/fs_trade_limit.lua")
dofile(modpath .. "fs/fs_edit_trade_limit.lua")
-- trade one item(stack) against one other item(stack)
dofile(modpath .. "trade_simple.lua")
dofile(modpath .. "api/api_trade_inv.lua")
dofile(modpath .. "fs/fs_do_trade_simple.lua")
dofile(modpath .. "fs/fs_add_trade_simple.lua")
-- just click on a button to buy items from the trade list
dofile(modpath .. "fs_trade_via_buy_button.lua")
dofile(modpath .. "fs/fs_trade_via_buy_button.lua")
-- easily accessible list of all trades the NPC offers
dofile(modpath .. "fs_trade_list.lua")
-- as the name says: list which npc acesses a variable how and in which context
dofile(modpath .. "fs_get_list_of_usage_of_variable.lua")
-- show which values are stored for which player in a quest variable
dofile(modpath .. "fs_show_all_var_values.lua")
-- manage quest variables: add, delete, manage access rights etc.
dofile(modpath .. "fs_manage_variables.lua")
dofile(modpath .. "fs/fs_trade_list.lua")
-- handle variables for quests for player-owned NPC
dofile(modpath .. "quest_api.lua")
-- GUI for adding/editing quests
dofile(modpath .. "fs_manage_quests.lua")
-- setting skin, wielded item etc.
dofile(modpath .. "fs_fashion.lua")
dofile(modpath .. "api/api_fashion.lua")
-- properties for NPC without specific dialogs that want to make use of
-- some generic dialogs
dofile(modpath .. "fs_properties.lua")
dofile(modpath .. "api/api_properties.lua")
-- the main functionality of the mod
dofile(modpath .. "functions.lua")
dofile(modpath .. "functions_dialogs.lua")
dofile(modpath .. "functions_save_restore_dialogs.lua")
dofile(modpath .. "functions_talk.lua")
-- implementation of the chat commands registered in register_once.lua:
dofile(modpath .. "chat_commands.lua")
-- creating and maintaining quests
dofile(modpath .. "fs_quest_gui.lua")
-- show a list of all NPC the player can edit
dofile(modpath .. "fs_npc_list.lua")
dofile(modpath .. "api/api_npc_list.lua")
dofile(modpath .. "fs/fs_npc_list.lua")
-- this may load custom things like preconditions, actions, effects etc.
-- which may depend on the existance of other mods
dofile(modpath .. "addons/load_addons.lua")
-- some general functions that are useful for mobs_redo
-- (react to right-click, nametag color etc.)
-- only gets loaded if mobs_redo (mobs) exists as mod
dofile(modpath .. "interface_mobs_api.lua")
-- export dialog for cut&paste in .json format
dofile(modpath .. "export_to_ink.lua")
dofile(modpath .. "fs/fs_export.lua")
dofile(modpath .. "import_from_ink.lua")
-- edit_mode.lua has been moved to the mod npc_talk_edit:
-- dofile(modpath .. "editor/edit_mode.lua")
-- initialize and load all registered generic dialogs
yl_speak_up.load_generic_dialogs()

82
initial_config.lua Normal file
View File

@ -0,0 +1,82 @@
-- add example answers and dialogs; they may be irritating for experienced
-- users, but help new users a lot in figuring out how the system works
--
-- first, we create three example dialogs as such:
-- (the first one has already been created in the creation of the dialog;
-- yl_speak_up.fields_to_dialog stores d_id in yl_speak_up.speak_to[pname].d_id)
--
-- Important: Overwrite this function if you want something diffrent here
-- (i.e. texts in a language your players speak).
yl_speak_up.setup_initial_dialog = function(dialog, pname)
local new_d_1 = "d_1"
local new_d_2 = yl_speak_up.add_new_dialog(dialog, pname, "2",
"I'm glad that you wish me to talk to you.\n\n"..
"I hope we can talk more soon!")
local new_d_3 = yl_speak_up.add_new_dialog(dialog, pname, "3",
"Oh! Do you really think so?\n\n"..
"To me, talking is important.")
-- we are dealing with a new NPC, so there are no options yet
-- options added to dialog f.d_id:
local new_d_1_o_1 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_1", "I hope so as well!", new_d_2)
local new_d_1_o_2 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_1", "No. Talking is overrated.", new_d_3)
-- options added to dialog new_d_2:
local new_d_2_o_1 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_2", "Glad that we agree.", new_d_1)
-- options added to dialog new_d_3:
local new_d_3_o_1 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_3", "No. I was just joking. I want to talk to you!", new_d_2)
local new_d_3_o_2 = yl_speak_up.add_new_option(dialog, pname, nil,
"d_3", "Yes. Why am I talking to you anyway?", "d_end")
end
-- supply the NPC with its first (initial) dialog;
-- pname will become the owner of the NPC;
-- example call:
-- yl_speak_up.initialize_npc_dialog_once(pname, nil, n_id, fields.n_npc, fields.n_description)
yl_speak_up.initialize_npc_dialog_once = function(pname, dialog, n_id, npc_name, npc_description)
-- the NPC already has a dialog - do not overwrite it!
if(dialog.created_at) then
return dialog
end
if(yl_speak_up.count_dialogs(dialog) > 0) then
-- add the created_at flag if the dialog is already set up
-- (this affects only NPC created before this time)
dialog.created_at = os.time()
return dialog
end
-- give the NPC its first dialog
local f = {}
-- create a new dialog
f.d_id = yl_speak_up.text_new_dialog_id
-- ...with this text
f.d_text = "$GOOD_DAY$ $PLAYER_NAME$,\n"..
"I am $NPC_NAME$. I don't know much yet.\n"..
"Hopefully $OWNER_NAME$ will teach me to talk soon."
-- it is the first, initial dialog
f.d_sort = "0"
f.n_npc = npc_name -- old: fields.n_npc
f.n_description = npc_description -- old: fields.n_description
f.npc_owner = pname -- old: yl_speak_up.npc_owner[ n_id ] (make sure to call it with right pname!)
-- create and save the first dialog for this npc
local dialog = yl_speak_up.fields_to_dialog(pname, f)
-- overwrite this function if you want something diffrent added:
yl_speak_up.setup_initial_dialog(dialog, pname)
dialog.created_at = os.time()
-- save our new dialog
yl_speak_up.save_dialog(n_id, dialog)
dialog.n_may_edit = {}
-- update the dialog for the player
yl_speak_up.speak_to[pname].dialog = dialog
yl_speak_up.speak_to[pname].d_id = yl_speak_up.get_start_dialog_id(dialog)
-- now connect the dialogs via results
yl_speak_up.log_change(pname, n_id,
"Initial config saved. "..
"NPC name: \""..tostring(dialog.n_npc)..
"\" Description: \""..tostring(dialog.n_description).."\".")
return dialog
end

292
ink_language_support.md Normal file
View File

@ -0,0 +1,292 @@
# Limited ink support
Please don't hope for too much yet. This here is mostly an outline
of what is *planned* - not what is available yet!
The export works already. Import of knots works partially (no
preconditions, actions or effects).
Complex things will take their time.
In short: Take your NPC, use the "Export to ink" button, edit
the text of dialogs and options, add new knots or options,
rearrange the order of the lines of the options to affect
sorting - and then import it back and see how well it worked.
## Table of Content
1. [What is ink](#1-what-is-ink)
2. [Export to ink](#2-export-to-ink)
3. [Limitations](#3-limitations)
3. [Import from ink](#3-import-from-ink)
1. [3.1 Supported elements](#31-supported-elements)
1. [3.2 Supported inline elements](#32-supported-inline-elements)
1. [3.3 Escape character](#33-escape-character)
1. [3.4 Labels](#34-labels)
1. [3.5 Structure of a choice](#35-structure-of-a-choice)
1. [3.6 Special dialogs](#36-special-dialogs)
## 1. What is Ink
This mod has limited support for the
<a href="https://github.com/inkle/ink/blob/master/Documentation/WritingWithInk.md">ink lanugage</a>.
That scripting language is intended for writing stories and quests
and help you write dialogs for NPC. You can use your favorite text
editor offline instead of clicking through the GUI of the NPC ingame.
You can also write, run and test those dialogs with an ink
interpreter like Inky instead of running a MineTest server.
## 2. Export to ink
NPC can export their dialogs. This is a good start in order to see
how things work. You can get there by clicking on "I am your owner"
in the NPC dialog, then on the "Export" button in the top row, and
then on "Ink language".
In theory, this export ought to work with interpreters for the ink
language, i.e. with inky. In practice, some small errors cannot
always be avoided. You may have to edit the export in such a case
until the ink interpreter is happy.
Currently, the visit counter (precondition, "something that has to
be calculated or evaluated", "counted dialog option/answers visits:"
tries its best to come up with a suitable name - but will most
likely fail. Please adjust manually! Often, the name of the
option/answer will do.
### 3. Limitations
Please note that the whole MineTest environment - the world, the
players, the mobs, the inventories etc - is *not* emulated in ink.
Most preconditions and effects therefore cannot be translated
to ink in a meaningful way. There's just no way to tell if e.g.
the player has a particular item in his inventory, or which block
sits at a given location. Those things don't exist in ink. They
could theoreticly be emulated, but - not in any feasible way in
a realistic amount of time.
The things that preconditions check and effects change in the
MineTest world could be simulated in ink via variables. That would
work - but it would also be pretty complex and make reading your
ink code and writing things for import not particulary easy.
Likewise, ink is a language of its own. The NPC support only a
very limited and restricted subset of the ink language and
expect certain special handling and knots.
The export function addas a wrapper dialog (in ink terminolgy:
a knot) to the ink script so that you can continue to run the
program even after you hit a d\_end in its dialogs.
See 3.6 Special Dialogs for more information.
## 3. Import from ink
The ink language is not as well defined as one might hope from the
documentation. As a user you might not run into these problems.
### 3.1 Supported elements
In general, most ink elements will end up becomming a dialog for the
NPC (so, in effect a knot) - no matter if the ink element is a knot,
stitch, gather or weave. NPC are only aware of dialogs and options.
The ink elements are nevertheless helpful for writing such dialogs.
So far, only *knots* are supported - not weaving.
In general, each ink language element has to start at the start of a
line in order for import to work - even if the ink language doesn't
demand this!
Supported (at least to a degree) elements are:
| Ink symbol | Meaning of the smybol |
| ---------- | ------------------------------ |
| == | knot |
| * | choice (can be selected only once) |
| + | sticky choice (can be selected multiple times) |
Partially supported (recognized but not imported):
| Ink symbol | Meaning of the smybol |
| ---------- | ------------------------------ |
| // | Start of inline comment |
| \/* | Start of multiline comment |
| \*/ | End of multiline comment |
| = | stitch (sub-knot of a knot) |
| - | gather |
| { | start of multiline alternative |
| } | end of multiline alternative |
| - | gather |
| ~ | code |
| INCLUDE | include file (not supported) |
| VAR | declaration of a variable |
| CONST | declaration of a constant |
Not supported:
| Ink symbol | Meaning of the smybol |
| ---------- | ------------------------------ |
| <> | glue |
| # | tag |
### 3.2 Supported inline elements
*Note*: None of this is implemented in the import yet. For this mod,
the text will for now just be printed as-is.
There is one main inline element that the ink language has: Text in
curly brackets: {inline}. The meaning of that inline element depends
a lot on context:
If it's a variable: The value of the variable is printed.
If it's something like {A|B|C|D}, then that's an alternative - the
first time the knot is visited, A is printed. The second time B,
third time C and so on.
| Inline | Meaning of inline text |
| ---------- | ---------------------------------------------------- |
| {variable} | print the *value* of a varialbe |
| {A|B|C|D} | alternative: show A on first visit, B on second etc. |
| {&A|B|C|D} | cycle: start with A again after showing D |
| {!A|B|C|D} | once-only alternatives: display nothing after D |
| {~A|B|C|D} | shuffle:randomly A, B, C or D |
| {cond:A|B} | if(cond) then A else B |
Note that shuffle will not just shuffle the elements and use them in
a random order but will decide randomly each time what to display.
This may be diffrent from what ink does (which might - or might not -
really show *all* shuffle elements, just in a random order).
### 3.2 Escape character
The escape character is the backslash "\". With that, you can use
the special characters above and have them appear literally in
your text.
### 3.4 Labels
Choices *may* and *ought to* have *labels* in order for the import to
work satisfactorily. In ink, they are optional. For the import, you
kind of need them. Else the import function has no idea if you want to
add a new option/choice to a dialog, want to change the text of an
existing one or just re-arranged their order.
Knots and stitches already have names and can thus be mapped to
existing dialogs.
If a choice or gather doesn't have a label when importing, it's seen
as a *new* choice or gather.
If you create a new dialog and/or option, you don't have to add a
label to the option/choice yourself. You can write it without in ink,
then use the import function, then let the NPC export that again -
and use that processed export as the base for further editing.
Missing labels will be generated automaticly that way and bear the
right name.
### 3.4 Structure of a choice
By default, NPC use *sticky choices* (starting with a plus, "+") and
not the choices (starting with a star, "\*") ink prefers in general.
The difference is that the ink choices can be used *only once* in a
conversation. The NPCs have small memories and many people may
want to talk to them on a multiplayer server - so they don't remember
what has been used between conversations. Even non-sticky choices
will become available again if the player closes the dialog and talks
to the NPC again.
In ink, a choice may look as minimal as this:
\* Hello!
For an NPC, it may end up looking more like this:
\+ (o\_3) {cond1} {cond2} {cond3} [Hello!] Oh, nice that you're greeting me! -> start
The brackets [] around the text mean that ink won't repeat that text
when it outputs the reaction to the text. NPC by default don't repeat
such texts. The brackets [] are therefore there mostly so that the
ink output is halfway consistent.
(o\_3) is the label. It will be assigned automaticly by the NPC. When
you edit a choice, or move it around in the list of answers, the
import function will thus know which choice/option you refer to.
The label is not shown to the player talking to the player. It
does not affect the order in which options are shown. Always keep
the label the same - unless you want to add a *new* option!
{cond1} {cond2} {cond3} are conditions in ink. They all need to be
true in order for the choice/option to be available.
What is available between ink and the NPC is quite limited. It's
mostly accessing values of variables and the visit counter that
you may use here. *Note*: This isn't supported yet.
The ink export also puts setting of variables and effects as # tags
somewhere after the choice and before the divert.
The text after the closing square bracket "]" is interpreted as
alternate text by the NPC.
### 3.6 Special dialogs
NPC have some special dialogs:
| Name | Meaning of dialog name |
| ----------- | ------------------------------------------------------ |
| d\_end | end the conversation and close the formspec |
| d\_trade | a special trading dialog with diffrent buttons |
| d\_dynamic | dynamic dialog texts provided by server-side functions |
| d\_got\_item| react to items the player gave to the NPC |
The dialog target d\_end would normally end the conversation. That might
be pretty inconvenient when you're constructing a quest with many NPC
participating - or just want to test its dialogs without having to restart
it all the time. Therefore, a wrapper dialog is added by the export to ink
function. Example (for NPC n\_134, with start dialog START):
``
-> n_134_d_end
=== n_134_d_end ===
What do you wish to do?
+ Talk to n_134 -> n_134_START
+ End -> END
``
Actions in the NPC way of speaking are actions that have to be done by the
player. The player may succeed or fail at completing the action - or simply
click on the "Back" button. Ink has no way to know about such thing as
player inventories or the Minetest world around the player. Therefore,
actions are modelled as an additional dialog in *ink*. Example:
``
=== n_134_action_a_1_o_2_did_you_get_bone ===
:action: a_1 The NPC wants "- default description -" ("default:chest") with ID "- no special ID -".
+ [Action was successful] -> n_134_thanks_for_bone
+ [Action failed] -> n_134_no_bone
+ [Back] -> n_134_did_you_get_bone
``
The only thing that will vary in your actions is the name of the action, the
parameters in the text and the target dialogs for each possible outcome.
The same applies to effects. While there is no way to choose a back button
there, effects may still be either successful or fail:
``
=== n_134_effect_r_2_o_1_START ===
:effect: r_2 If the *previous* effect failed, go to dialog "d_1".
The previous effect was: r_1 Switch to dialog "d_2".
+ [Effect was successful] -> n_134_guarding_both
+ [Effect failed]
Oh! Effect r_2 failed. Seems I ran out of bones. Please come back later!
$TEXT$
-> n_134_START
``

View File

@ -31,15 +31,69 @@ function yl_speak_up.do_mobs_on_rightclick(self, clicker)
--local item = clicker:get_wielded_item()
local name = clicker:get_player_name()
-- Take the mob only with net or lasso
if self.owner and self.owner == name then
if mobs:capture_mob(self, clicker, nil, 100, 100, true, nil) then
return
end
end
local n_id = "?"
if(self and self.yl_speak_up and self.yl_speak_up.id) then
n_id = "n_"..tostring(self.yl_speak_up.id)
-- if someone other than the owner placed the mob, then we need to
-- adjust the owner back from placer to real_owner
if(self.yl_speak_up.real_owner and self.yl_speak_up.real_owner ~= self.owner) then
self.owner = self.yl_speak_up.real_owner
end
end
-- Take the mob only with net or lasso
if self.owner and (self.owner == name or yl_speak_up.may_edit_npc(clicker, n_id)) then
local pos = self.object:get_pos()
self.yl_speak_up.last_pos = minetest.pos_to_string(pos, 0)
-- the mob can be picked up by someone who can just *edit* it but is not *the* owner
if(self.owner ~= name) then
self.yl_speak_up.real_owner = self.owner
end
-- try to capture the mob
local egg_stack = mobs:capture_mob(self, clicker, nil, 100, 100, true, nil)
if(egg_stack and self.yl_speak_up) then
minetest.log("action","[MOD] yl_speak_up "..
" NPC n_"..tostring(self.yl_speak_up.id)..
" named "..tostring(self.yl_speak_up.npc_name)..
" (owned by "..tostring(self.owner)..
") picked up by "..tostring(clicker:get_player_name())..
" at pos "..minetest.pos_to_string(pos, 0)..".")
-- players want to know *which* NPC will "hatch" from this egg;
-- sadly there is no point in modifying egg_data as that has already
-- been put into the inventory of the player and is just a copy now
local player_inv = clicker:get_inventory()
for i, v in ipairs(player_inv:get_list("main") or {}) do
local m = v:get_meta()
local d = minetest.deserialize(m:get_string("") or {})
-- adjust the description text of the NPC in the inventory
if(d and d.yl_speak_up and d.yl_speak_up.id) then
local d2 = d.yl_speak_up
local text = (d2.npc_name or "- nameless -").. ", "..
(d2.npc_description or "-").."\n"..
"(n_"..tostring(d2.id)..", owned by "..
tostring(d.owner).."),\n"..
"picked up at "..tostring(d2.last_pos or "?").."."
m:set_string("description", text)
player_inv:set_stack("main", i, v)
end
end
return
end
end
-- protect npc with mobs:protector
if mobs:protect(self, clicker) then
if(self.yl_speak_up) then
local pos = self.object:get_pos()
minetest.log("action","[MOD] yl_speak_up "..
" NPC n_"..tostring(self.yl_speak_up.id)..
" named "..tostring(self.yl_speak_up.npc_name)..
" (owned by "..tostring(self.owner)..
") protected with protector by "..tostring(clicker:get_player_name())..
" at pos "..minetest.pos_to_string(pos, 0)..".")
end
return
end
@ -56,6 +110,11 @@ function yl_speak_up.do_mobs_after_activate(self, staticdata, def, dtime)
-- yl_speak_up.log_change("-", "n_"..self.yl_speak_up.id,
-- "activated at "..minetest.pos_to_string(self.object:get_pos()), "action")
-- we are not (yet?) responsible for this mob
if(not(self.yl_speak_up)) then
return true
end
if yl_speak_up.status == 2 then
self.object:remove()
return true
@ -64,6 +123,34 @@ function yl_speak_up.do_mobs_after_activate(self, staticdata, def, dtime)
-- load the texture/skin of the NPC
if self.yl_speak_up and self.yl_speak_up.skin then
local tex = self.yl_speak_up.skin
-- only use the cape as such:
if(tex[1]) then
local p = string.split(tex[1], "=")
if(#p > 1) then
tex[1] = p[#p]
end
end
-- the shield:
if(tex[3]) then
local p = string.split(tex[3], "=")
if(#p > 1) then
tex[3] = p[#p]
local start = 1
local ende = string.len(tex[3])
if(string.sub(tex[3], 1, 1)=="(") then
start = 2
end
if(string.sub(tex[3], ende)==")") then
ende = ende - 1
end
tex[3] = string.sub(tex[3], start, ende)
end
end
-- store only the basic texture names without shield and text mask:
self.yl_speak_up.skin = tex
-- add back cape and shield mask:
tex[1] = yl_speak_up.cape2texture(tex[1])
tex[3] = yl_speak_up.shield2texture(tex[3])
self.object:set_properties({textures = {tex[1], tex[2], tex[3], tex[4]}})
end
@ -90,6 +177,21 @@ function yl_speak_up.do_mobs_after_activate(self, staticdata, def, dtime)
end
-- prevent NPC from getting hurt by special nodes
-- This has another positive side-effect: update_tag doesn't get called constantly
if(not(yl_speak_up.orig_mobs_do_env_damage)) then
yl_speak_up.orig_mobs_do_env_damage = mobs.mob_class.do_env_damage
end
mobs.mob_class.do_env_damage = function(self)
-- we are only responsible for talking NPC
if(not(self) or not(self.yl_speak_up)) then
return yl_speak_up.orig_mobs_do_env_damage(self)
end
-- *no* env dammage for NPC
return
end
-- we need to override this function from mobs_redo mod so that color
-- changes to the name tag color are possible
-- BUT: Only do this once. NOT at each reset!
@ -97,45 +199,58 @@ if(not(yl_speak_up.orig_mobs_update_tag)) then
yl_speak_up.orig_mobs_update_tag = mobs.mob_class.update_tag
end
-- update nametag and infotext
mobs.mob_class.update_tag = function(self)
mobs.mob_class.update_tag = function(self, newname)
-- we are only responsible for talking NPC
if(not(self) or not(self.yl_speak_up)) then
return yl_speak_up.orig_mobs_update_tag(self)
return yl_speak_up.orig_mobs_update_tag(self, newname)
end
local qua = 0
local floor = math.floor
local col = "#00FF00"
local qua = self.hp_max / 4
local prop = self.object:get_properties()
local hp_max = 0
if(prop) then
hp_max = prop.hp_max
end
if(not(hp_max)) then
hp_max = self.hp_max
end
if(not(hp_max)) then
hp_max = self.health
end
local qua = hp_max / 6
if(self.force_nametag_color) then
col = self.force_nametag_color
elseif self.health <= floor(qua) then
elseif self.health <= qua then
col = "#FF0000"
elseif self.health <= floor(qua * 2) then
col = "#FF6600"
elseif self.health <= floor(qua * 3) then
elseif self.health <= (qua * 2) then
col = "#FF7A00"
elseif self.health <= (qua * 3) then
col = "#FFB500"
elseif self.health <= (qua * 4) then
col = "#FFFF00"
elseif self.health <= (qua * 5) then
col = "#B4FF00"
elseif self.health > (qua * 5) then
col = "#00FF00"
end
local text = ""
if self.horny == true then
local HORNY_TIME = 30
local HORNY_AGAIN_TIME = 60 * 5 -- 5 minutes
text = "\nLoving: " .. (self.hornytimer - (HORNY_TIME + HORNY_AGAIN_TIME))
elseif self.child == true then
local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
text = "\nGrowing: " .. (self.hornytimer - CHILD_GROW_TIME)
elseif self._breed_countdown then
text = "\nBreeding: " .. self._breed_countdown
end
if self.protected then
@ -153,7 +268,7 @@ mobs.mob_class.update_tag = function(self)
add_info = add_info..", "..tostring(self.yl_speak_up.npc_description)
end
end
self.infotext = "Health: " .. self.health .. " / " .. self.hp_max
self.infotext = "Health: " .. self.health .. " / " .. hp_max
.. add_info
.. (self.owner == "" and "" or "\nOwner: " .. self.owner)
.. text

View File

@ -1,92 +0,0 @@
-- assing ID at spawning (not at first talk) and log it
function yl_speak_up.on_spawn(self)
--Let's protect it
self.protected = true
self.tamed = true
self.object:set_armor_groups({immortal = 100})
--Let's assign an ID (usually this happens at first talk)
self = yl_speak_up.initialize_npc(self)
if(not(self) or not(self.yl_speak_up) or not(self.yl_speak_up.id)) then
-- something went very wrong
return false
end
yl_speak_up.log_change("-", "n_"..self.yl_speak_up.id,
"spawned at "..minetest.pos_to_string(self.object:get_pos()), "action")
--Let's do it only once
return true
end
-- thankyou flux for helping with this
function yl_speak_up.alias_mob(old_mob, new_mob)
minetest.register_entity(
":" .. old_mob,
{
initial_properties = {
physical = false,
-- pointable = false,
collide_with_objects = false,
-- is_visible = false,
glow = -1, -- disable light's effect on texture to make more visible in dark
shaded = false,
nametag = "Please /bug me",
infotext = "I should have been an NPC\nbut something went wrong\nplease report to staff",
static_save = true,
},
on_activate = function(self, staticdata)
local new_entity
local pos = self.object:get_pos()
if pos and minetest.features.add_entity_with_staticdata then
-- if pos is nil then we don't know where we are and we can't add entity.
new_entity = minetest.add_entity(pos, new_mob, staticdata)
end
local data = minetest.deserialize(staticdata) or {}
local pos_str = pos and minetest.pos_to_string(pos) or "NO_POS"
local owner = data.owner or "NO_OWNER"
local id = (data.yl_speak_up or {}).id or "NO_ID"
if new_entity then
self.object:remove()
minetest.log("action", string.format("[yl_speak_up] alias_mob: converted %s ID:%s owned by %s to %s @ %s",
old_mob, id, owner, new_mob, pos_str)
)
else
self.staticdata = staticdata
self.object:set_armor_groups({immortal = 1})
minetest.log("error", string.format("[yl_speak_up] alias_mob: error initializing %s ID:%s owned by %s @ %s, keeping %s for safe keeping",
new_mob, id, owner, pos_str, old_mob)
)
end
end,
get_staticdata = function(self)
return self.staticdata
end,
on_blast = function()
return false, false, {}
end,
-- TODO allow STAFF to pick up with lasso and move?
})
end
do
local races = {'human', 'elf', 'dwarf', 'goblin', 'orc', 'npc'}
for _, race in pairs(races) do
-- alias entities
yl_speak_up.alias_mob("yl_speak_up:" .. race, "yl_npc:" .. race)
-- alias eggs for spawning new NPCs
minetest.register_alias("yl_speak_up:" .. race, "yl_npc:" .. race)
-- alias eggs that store NPCs picked up with a lasso
minetest.register_alias("yl_speak_up:" .. race .. "_set", "yl_npc:" .. race .. "_set")
end
end

View File

@ -1,6 +1,5 @@
author = Alias
author = Bastrabun/AliasAlreadyTaken, Sokomine
description = NPCs deliver speeches
release = 202009231753
title = Yourland Speak up
name = yl_speak_up
optional_depends = mobs

View File

@ -13,6 +13,17 @@ yl_speak_up.npc_priv_names = {
"effect_exec_lua", "effect_give_item", "effect_take_item", "effect_move_player",
}
-- make sure this table exists
if(not(yl_speak_up.npc_priv_needs_player_priv)) then
yl_speak_up.npc_priv_needs_player_priv = {}
end
-- and set it to privs if nothing is specified (because the *_lua are extremly dangerous npc_privs!)
for i, p in ipairs(yl_speak_up.npc_priv_names) do
if(not(yl_speak_up.npc_priv_needs_player_priv[p])) then
yl_speak_up.npc_priv_needs_player_priv[p] = "privs"
end
end
-- either the npc with n_id *or* if generic_npc_id is set the generic npc with the
-- id generic_npc_id needs to have been granted priv_name
yl_speak_up.npc_has_priv = function(n_id, priv_name, generic_npc_id)
@ -74,27 +85,43 @@ end
-- "If called with parameter [list], all granted privs for all NPC are shown.",
-- privs = {privs = true},
yl_speak_up.command_npc_talk_privs = function(pname, param)
-- can the player see the privs for all NPC? or just for those he can edit?
local list_all = false
local ptmp = {}
ptmp[yl_speak_up.npc_privs_priv] = true
if(minetest.check_player_privs(pname, ptmp)) then
list_all = true
end
if(not(param) or param == "") then
-- if the npc priv has a player priv as requirement, then list that
local tmp = {}
for i, p in ipairs(yl_speak_up.npc_priv_names) do
table.insert(tmp, tostring(p)..
" ["..tostring(yl_speak_up.npc_priv_needs_player_priv[p]).."]")
end
minetest.chat_send_player(pname,
"Usage: [grant|revoke|list] <n_id> <priv>\n"..
"The following privilege exist:\n\t"..
table.concat(yl_speak_up.npc_priv_names, ", ")..".")
"The following privilege exist [and require you to have this priv to set]:\n\t"..
table.concat(tmp, ", ")..".")
return
end
local player = minetest.get_player_by_name(pname)
local parts = string.split(param, " ")
if(parts[1] == "list") then
local text = "This list contains the privs of each NPC in the form of "..
"<npc_name>: <list of privs>"
local text = "This list contains the privs of each NPC you can edit "..
"in the form of <npc_name>: <list of privs>"
-- create list of all existing extra privs for npc
for n_id, v in pairs(yl_speak_up.npc_priv_table) do
text = text..".\n"..tostring(n_id)..":"
local found = false
for priv, w in pairs(v) do
text = text.." "..tostring(priv)
found = true
end
if(not(found)) then
text = text.." <none>"
if(list_all or yl_speak_up.may_edit_npc(player, n_id)) then
text = text..".\n"..tostring(n_id)..":"
local found = false
for priv, w in pairs(v) do
text = text.." "..tostring(priv)
found = true
end
if(not(found)) then
text = text.." <none>"
end
end
end
minetest.chat_send_player(pname, text..".")
@ -114,6 +141,31 @@ yl_speak_up.command_npc_talk_privs = function(pname, param)
table.concat(yl_speak_up.npc_priv_names, ", ")..".")
return
end
-- does the player have the necessary player priv to grant or revoke this npc priv?
local ptmp = {}
ptmp[yl_speak_up.npc_priv_needs_player_priv[priv]] = true
if(not(minetest.check_player_privs(pname, ptmp))) then
minetest.chat_send_player(pname, "You lack the \""..
tostring(yl_speak_up.npc_priv_needs_player_priv[priv])..
"\" priv required to grant or revoke this NPC priv!")
return
end
-- does the player have the right to edit/change this npc?
if(not(list_all) and not(yl_speak_up.may_edit_npc(player, n_id))) then
minetest.chat_send_player(pname, "You can only set privs for NPC which you can edit. \""..
tostring(n_id).." cannot be edited by you.")
return
end
-- revoking privs of nonexistant NPC is allowed - but not granting them privs
local id = tonumber(string.sub(n_id, 3)) or 0
if(command == "grant" and not(yl_speak_up.npc_list[id])) then
minetest.chat_send_player(pname,
"Unknown NPC \""..tostring(n_id).."\".\n")
return
end
if(command == "grant" and not(yl_speak_up.npc_priv_table[n_id])) then
yl_speak_up.npc_priv_table[n_id] = {}
end

View File

@ -1,4 +1,19 @@
-- full version found in fs_edit_preconditions.lua:
yl_speak_up.show_precondition = function(p, pname)
return "P: "..minetest.serialize(p or {}).."."
end
-- full version found in fs_edit_actions.lua:
yl_speak_up.show_action = function(a)
return "A: "..minetest.serialize(a or {}).."."
end
-- full version found in fs_edit_effects.lua:
yl_speak_up.show_effect = function(r, pname)
return "E: "..minetest.serialize(r or {}).."."
end
-- store which player is monitoring the NPC (for preconditions and
-- effects)

View File

@ -1,164 +0,0 @@
-- Important: This is only of relevance for the Server YourLand -
-- which was running an older version of this mod and nees
-- adjustments for the newer version.
--
-- And even on that server the file was relevant only once.
-- It is mainly included here as an example of how to iterate
-- over all existing NPC (at least those who have dialogs!).
---
-- Rename this file to
-- local_server_do_on_reload.lua
-- and execute
-- /npc_talk_migrate
-- in order to automaticly add the necessary privs for all existing old NPC.
--
-- NPC dialogs are checked and reported if broken.
yl_speak_up.migration_npc_needs_priv = function(n_id, pname, counter, priv_name)
if(counter < 1) then
return
end
-- does the NPC have the priv already?
if(yl_speak_up.npc_has_priv(n_id, priv_name, nil)) then
minetest.chat_send_player(pname, " OK: "..tostring(n_id).." has priv "..priv_name)
return
end
-- grant the NPC the priv
minetest.chat_send_player(pname, " GRANTING "..tostring(n_id).." priv "..priv_name)
if(not(yl_speak_up.npc_priv_table[n_id])) then
yl_speak_up.npc_priv_table[n_id] = {}
end
yl_speak_up.npc_priv_table[n_id][priv_name] = true
end
yl_speak_up.check_one_npc_for_migration = function(dialog, pname, n_id, fname)
minetest.chat_send_player(pname, " Checking NPC "..tostring(fname)..
" \""..tostring(dialog.n_npc or "- nameless -").."\"...")
-- nothing defined yet - nothing to repair
if(not(dialog.n_dialogs)) then
minetest.chat_send_player(pname, " NPC "..tostring(n_id).." has nothing to say.")
return
end
local c_effect_exec_lua = 0
local c_effect_give_item = 0
local c_effect_take_item = 0
local c_effect_move_player = 0
local c_precon_exec_lua = 0
-- iterate over all dialogs
for d_id, d in pairs(dialog.n_dialogs) do
if(d_id and d and d.d_options) then
-- iterate over all options
for o_id, o in pairs(d.d_options) do
if(o_id and o and o.o_results) then
local has_close_formspec = false
local dialog_results = {}
-- iterate over all results
for r_id, r in pairs(o.o_results) do
if(r.r_type == "dialog") then
table.insert(dialog_results, r_id)
elseif(r.r_type == "function") then
c_effect_exec_lua = c_effect_exec_lua + 1
-- if the formspec is closed, we need to use the
-- target dialog d_end
if(string.find(r.r_value , "minetest.close_formspec")) then
has_close_formspec = true
end
elseif(r.r_type == "move") then
c_effect_move_player = c_effect_move_player + 1
elseif(r.r_type == "give_item") then
c_effect_give_item = c_effect_give_item + 1
elseif(r.r_type == "take_item") then
c_effect_take_item = c_effect_take_item + 1
end
end
if(#dialog_results>1) then
local msg = "ERROR: Dialog "..
tostring(d_id)..", option "..tostring(o_id)..
", has multiple results of type dialog: "..
minetest.serialize(dialog_results)..". Please "..
"let someone with npc_master priv fix that first!"
yl_speak_up.log_change(pname, n_id, msg, "error")
if(pname) then
minetest.chat_send_player(pname, msg)
end
-- sometimes we need d_end because minetest.close_formspec in
-- an effect of type "function" is not enough
elseif(has_close_formspec) then
local found = false
local d_old = ""
for r_id, r in pairs(o.o_results) do
if(r.r_type == "dialog" and not(found)) then
d_old = r.r_value
r.r_value = "d_end"
found = true
end
end
-- if there is no dialog option yet: create one
if(not(found)) then
local future_r_id = yl_speak_up.add_new_result(
dialog, d_id, o_id)
o.o_results[future_r_id] = {
r_id = future_r_id,
r_type = "dialog",
r_value = "d_end"}
end
if(d_old ~= "d_end") then
yl_speak_up.save_dialog(n_id, dialog)
local msg = "ERROR: Dialog "..
tostring(d_id)..", option "..tostring(o_id)..
", uses minetest.close_formspec in its "..
"lua code but "..tostring(d_old)..
" instead of d_end as target dialog. Fixing."
minetest.chat_send_player(pname, msg)
end
end
end
if(o_id and o and o.o_prerequisites) then
for p_id, p in pairs(o.o_prerequisites) do
if(p.p_type == "function") then
c_precon_exec_lua = c_precon_exec_lua + 1
end
end
end
end
end
end
yl_speak_up.migration_npc_needs_priv(n_id, pname, c_effect_exec_lua, "effect_exec_lua")
yl_speak_up.migration_npc_needs_priv(n_id, pname, c_effect_give_item, "effect_give_item")
yl_speak_up.migration_npc_needs_priv(n_id, pname, c_effect_take_item, "effect_take_item")
yl_speak_up.migration_npc_needs_priv(n_id, pname, c_effect_move_player, "effect_move_player")
yl_speak_up.migration_npc_needs_priv(n_id, pname, c_precon_exec_lua, "precon_exec_lua")
end
yl_speak_up.check_all_npc_for_migration = function(pname)
local path = yl_speak_up.worldpath..yl_speak_up.path.."/"
local file_names = minetest.get_dir_list(path, false)
minetest.chat_send_player(pname, "There are "..tostring(#file_names).." NPCs with stored dialogs "..
"in folder "..path..".")
for i, fname in ipairs(file_names) do
local file, err = io.open(path..fname, "r")
io.input(file)
local content = io.read()
local dialog = minetest.parse_json(content)
io.close(file)
if type(dialog) ~= "table" then
dialog = {}
end
yl_speak_up.check_one_npc_for_migration(dialog, pname, string.sub(fname, 0, -6), fname)
end
minetest.chat_send_player(pname, "Done with checking all NPC files for migration.")
-- store the changed privs
yl_speak_up.npc_privs_store()
return true
end
minetest.register_chatcommand( 'npc_talk_migrate', {
description = "Checks NPC from old yl_speak_up for migration to new version of the mod.",
privs = {npc_master = true},
func = function(pname, param)
return yl_speak_up.check_all_npc_for_migration(pname)
end
})

View File

@ -1,124 +0,0 @@
-- helper function
yl_speak_up.wrap_long_lines_for_table = function(text, prefix, line_length, max_lines)
-- show newlines as <\n> in order to save space
local text = (text or "?")
text = string.gsub(text, "\n", minetest.formspec_escape("<br>"))
-- break the text up into lines of length x
local parts = minetest.wrap_text(text, line_length, true)
if(not(parts) or #parts < 2) then
return minetest.formspec_escape(text)
end
local show_parts = {}
-- only show the first two lines (we don't have infinite room)
for i, p in ipairs(parts) do
if(i <= max_lines) then
table.insert(show_parts, minetest.formspec_escape(p))
end
end
if(#parts > max_lines) then
return table.concat(show_parts, prefix)..minetest.formspec_escape(" [...]")
end
return table.concat(show_parts, prefix)
end
-- helper functions for yl_speak_up.fs_get_list_of_usage_of_variable
-- and yl_speak_up.show_what_points_to_this_dialog
yl_speak_up.print_as_table_precon = function(p, pname)
return ",#FFFF00,"..
minetest.formspec_escape(tostring(p.p_id))..
",#FFFF00,pre(C)ondition,#FFFF00,"..
minetest.formspec_escape(p.p_type)..",#FFFF00,"..
minetest.formspec_escape(yl_speak_up.show_precondition(p, pname))
end
yl_speak_up.print_as_table_effect = function(r, pname)
return ",#55FF55,"..
minetest.formspec_escape(tostring(r.r_id))..
",#55FF55,(Ef)fect,#55FF55,"..
minetest.formspec_escape(r.r_type)..",#55FF55,"..
minetest.formspec_escape(yl_speak_up.show_effect(r, pname))
end
yl_speak_up.print_as_table_action = function(a, pname)
return ",#FF9900,"..
minetest.formspec_escape(tostring(a.a_id))..
",#FF9900,(A)ction,#FF9900,"..
minetest.formspec_escape(a.a_type)..",#FF9900,"..
-- these lines can get pretty long when a description for a quest item is set
yl_speak_up.wrap_long_lines_for_table(
yl_speak_up.show_action(a, pname),
",#FFFFFF,,#FFFFFF,,#FFFFFF,,#FF9900,",
80, 4)
end
yl_speak_up.print_as_table_dialog = function(p_text, r_text, dialog, n_id, d_id, o_id, res, o, sort_value,
alternate_dialog, alternate_text)
if(p_text == "" and r_text == "" ) then
return
end
local d_text = yl_speak_up.wrap_long_lines_for_table(
dialog.n_dialogs[ d_id ].d_text or "?",
",#FFFFFF,,#FFFFFF,,#FFFFFF,,#BBBBFF,",
80, 3)
if(not(alternate_dialog) or not(alternate_text)) then
alternate_text = ""
else
alternate_text = ",#BBBBFF,"..minetest.formspec_escape(tostring(alternate_dialog))..
-- show alternate text in a diffrent color
",#BBBBFF,Dialog,#BBBBFF,says next:,#FFBBBB,"..
yl_speak_up.wrap_long_lines_for_table(
alternate_text,
",#FFFFFF,,#FFFFFF,,#FFFFFF,,#FFBBBB,",
80, 3)
end
res[ tostring(n_id).." "..tostring(d_id).." "..tostring(o_id) ] = {
text = "#6666FF,"..
tostring(n_id)..",#6666FF,NPC,#6666FF,named:,#6666FF,"..
minetest.formspec_escape(dialog.n_npc or "?")..","..
"#BBBBFF,"..
tostring(d_id)..",#BBBBFF,Dialog,#BBBBFF,says:,#BBBBFF,"..
d_text..","..
"#FFFFFF,"..
tostring(o_id)..",#FFFFFF,Option,#FFFFFF,A:,#FFFFFF,"..
minetest.formspec_escape(tostring(o.o_text_when_prerequisites_met or "?"))..
p_text..r_text..
alternate_text,
sort_value = sort_value}
end
yl_speak_up.print_as_table_prepare_formspec = function(res, table_name, back_button_name, back_button_text,
is_already_sorted, concat_with, table_columns)
local sorted_res = {}
-- this is the default for "show where a variable is used"
if(not(is_already_sorted)) then
local sorted_list = yl_speak_up.get_sorted_options(res, "sort_value")
for i, k in pairs(sorted_list) do
table.insert(sorted_res, res[ k ].text)
end
table_columns = "color,span=1;text;color,span=1;text;color,span=1;text;color,span=1;text"
else
sorted_res = res
end
if(not(concat_with)) then
-- insert blank lines between lines belonging together
concat_with = ",#FFFFFF,,#FFFFFF,,#FFFFFF,,#FFFFFF,,"
end
local formspec = {
"size[57,33]",
-- back to the list with that one precondition or effect
"button[0.2,0.2;56.6,1.2;"..back_button_name..";"..
minetest.formspec_escape(back_button_text).."]",
"button[0.2,31.6;56.6,1.2;"..back_button_name..";"..
minetest.formspec_escape(back_button_text).."]",
"tablecolumns["..tostring(table_columns).."]",
}
table.insert(formspec,
"table[1.2,2.4;55.0,28.0;"..tostring(table_name)..";"..
table.concat(sorted_res, concat_with).."]")
return formspec
end

View File

@ -1,4 +1,18 @@
-- general helper functions
-- TODO: not yet used
yl_speak_up.count_table_elements = function(table)
if(not(table) or type(table) ~= "table") then
return -1
end
local c = 0
for k, v in pairs(table) do
c = c + 1
end
return c
end
-- just some handling of variables
-- TODO: mark some vars as "need to be saved" while others are less important (i.e. timestamps)
@ -12,19 +26,46 @@ yl_speak_up.player_vars = {}
-- store when player_vars was last saved to disc
yl_speak_up.player_vars_last_save_time = 0
-- do not save this data every time it's changed - reduce disk writes
yl_speak_up.quest_var_saving_pending = 0
-- save the data to disc; either if force_save is set or enough time has passed
yl_speak_up.save_quest_variables = function(force_save)
if(not(force_save)
and (yl_speak_up.player_vars_last_save_time + yl_speak_up.player_vars_min_save_time >
math.floor(minetest.get_us_time()/1000000))) then
if(not(force_save)) then
if(yl_speak_up.quest_var_saving_pending > minetest.get_us_time()) then
-- we are waiting a bit, hoping to catch more changes before next write
return
end
if(yl_speak_up.player_vars_min_save_time < 10) then
yl_speak_up.player_vars_min_save_time = 10
end
-- save in yl_speak_up.player_vars_min_save_time seconds
yl_speak_up.quest_var_saving_pending = minetest.get_us_time() +
yl_speak_up.player_vars_min_save_time * 1000000
core.after(yl_speak_up.player_vars_min_save_time, yl_speak_up.save_quest_variables, true)
return
end
yl_speak_up.quest_var_saving_pending = 0
local json = minetest.write_json( yl_speak_up.player_vars )
-- actually store it on disk
minetest.safe_file_write(yl_speak_up.worldpath..yl_speak_up.player_vars_save_file..".json", json)
end
yl_speak_up.handle_json_nil_values = function(data)
if(data and type(data) == "table") then
for k,v in pairs(data) do
if( type(v) == "string" and v == "$NIL_VALUE$") then
data[ k ] = {}
elseif(type(v) == "table") then
data[ k ] = yl_speak_up.handle_json_nil_values(v)
end
end
end
return data
end
-- load the data from disc
yl_speak_up.load_quest_variables = function()
-- load the data from the file
@ -41,12 +82,7 @@ yl_speak_up.load_quest_variables = function()
if(type(data) ~= "table") then
return
end
for k,v in pairs(data) do
if(v == "$NIL_VALUE$") then
data[ k ] = {}
end
end
yl_speak_up.player_vars = data
yl_speak_up.player_vars = yl_speak_up.handle_json_nil_values(data)
if(not(yl_speak_up.player_vars.meta)) then
yl_speak_up.player_vars["meta"] = {}
end
@ -162,6 +198,10 @@ yl_speak_up.set_quest_variable_value = function(player_name, variable_name, new_
if(new_value ~= nil) then
new_value = tostring(new_value)
end
-- no need to change anything if the value is unchanged
if(yl_speak_up.player_vars[ k ][ player_name ] == new_value) then
return true
end
yl_speak_up.player_vars[ k ][ player_name ] = new_value
-- a quest variable was changed - save that to disc (but no need to force it)
yl_speak_up.save_quest_variables(false)
@ -175,8 +215,17 @@ yl_speak_up.get_quest_variable_value = function(player_name, variable_name)
-- the owner name is alrady encoded in the variable name
local k = tostring(variable_name)
if(not(variable_name) or not(player_name) or not(yl_speak_up.player_vars[ k ])) then
local k_long = yl_speak_up.add_pname_to_var(k, player_name or "")
yl_speak_up.get_variable_metadata(k_long, "default_value", true)
return nil
end
-- return default value if no value is set
if(yl_speak_up.player_vars[ k ][ player_name ] == nil
and yl_speak_up.player_vars[ k ]["$META$"]
and type(yl_speak_up.player_vars[ k ][ "$META$"]) == "table") then
return yl_speak_up.player_vars[ k ][ "$META$"][ "default_value" ]
end
-- return stored value
return yl_speak_up.player_vars[ k ][ player_name ]
end
@ -195,7 +244,8 @@ yl_speak_up.get_quest_variables = function(pname, has_write_access)
end
-- if the player has the right privs: allow to access all other variables as well
if( minetest.check_player_privs(pname, {npc_master=true})
or minetest.check_player_privs(pname, {npc_talk_master=true})) then
or minetest.check_player_privs(pname, {npc_talk_master=true})
or minetest.check_player_privs(pname, {npc_talk_admin=true})) then
for k, v in pairs(yl_speak_up.player_vars) do
local parts = string.split(k, " ")
-- variables owned by *other* players
@ -263,7 +313,7 @@ yl_speak_up.add_pname_to_var = function(var_name, pname)
end
-- helper function for yl_speak_up.input_fs_edit_option_related
-- helper function for yl_speak_up.handle_input_fs_edit_option_related
-- and yl_speak_up.get_fs_edit_option_p_and_e_state
yl_speak_up.strip_pname_from_varlist = function(var_list, pname)
local var_list_text = ""
@ -336,22 +386,42 @@ yl_speak_up.set_variable_metadata = function(k, pname, meta_name, entry_name, ne
if(not(yl_speak_up.player_vars[ k ])) then
return false
end
local changed = nil
-- make sure all the necessary tables exist
if( not(yl_speak_up.player_vars[ k ][ "$META$" ])) then
yl_speak_up.player_vars[ k ][ "$META$" ] = { meta_name = {} }
changed = true
end
-- var_type (the type of the variable) is a single string
if(meta_name == "var_type") then
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ] = new_value
if(yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ] ~= new_value) then
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ] = new_value
changed = true
end
elseif(meta_name == "default_value") then
-- reset default value to nil with empty string:
if(new_value == "") then
new_value = nil
end
if(yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ] ~= new_value) then
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ] = new_value
changed = true
end
else
if( not(yl_speak_up.player_vars[ k ][ "$META$" ][ meta_name ])
or type(yl_speak_up.player_vars[ k ][ "$META$" ][ meta_name ]) ~= "table") then
yl_speak_up.player_vars[ k ][ "$META$" ][ meta_name ] = {}
changed = true
end
if(yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ][ entry_name ] ~= new_value) then
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ][ entry_name ] = new_value
changed = true
end
yl_speak_up.player_vars[ k ][ "$META$"][ meta_name ][ entry_name ] = new_value
end
yl_speak_up.save_quest_variables(true)
if(changed) then
yl_speak_up.save_quest_variables(true)
end
return true
end
@ -370,6 +440,22 @@ yl_speak_up.get_access_list_for_var = function(k, pname, access_what)
end
-- helper function that searces for variables that will be replaced with their
-- values in text when displayed; helper function for yl_speak_up.update_stored_npc_data
-- (for keeping track of which NPC uses which variables)
-- changes table vars_used
yl_speak_up.find_player_vars_in_text = function(vars_used, text)
if(not(text) or text == "") then
return vars_used
end
for v in string.gmatch(text, "%$VAR ([%w%s_%-%.]+)%$") do
-- add the $ prefix again
vars_used["$ "..tostring(v)] = true
end
return vars_used
end
-- the dialog data of an NPC is saved - use this to save some statistical data
-- plus store which variables are used by this NPC
-- TODO: show this data in a formspec to admins for maintenance
@ -390,26 +476,36 @@ yl_speak_up.update_stored_npc_data = function(n_id, dialog)
local anz_actions = 0
local anz_effects = 0
local anz_trades = 0
local variables_p = {}
local variables_e = {}
-- used in d.d_text dialog texts,
-- o.o_text_when_prerequisites_met, o.o_text_when_prerequisites_not_met,
-- preconditions and effects
local variables_used = {}
if(dialog and dialog.n_dialogs) then
for d_id, d in pairs(dialog.n_dialogs) do
anz_dialogs = anz_dialogs + 1
if(d) then
-- find all variables used in the text
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, d.d_text)
end
if(d and d.d_options) then
for o_id, o in pairs(d.d_options) do
anz_options = anz_options + 1
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, o.o_text_when_prerequisites_met)
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, o.o_text_when_prerequisites_not_met)
if(o and o.o_prerequisites) then
for p_id, p in pairs(o.o_prerequisites) do
anz_preconditions = anz_preconditions + 1
if(p and p.p_type and p.p_type == "state"
and p.p_variable and p.p_variable ~= "") then
variables_p[ p.p_variable ] = true
variables_used[ p.p_variable ] = true
end
end
end
if(o and o.actions) then
for a_id, a_data in pairs(o.actions) do
anz_actions = anz_actions + 1
-- actions can have alternate_text
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, a_data.alternate_text)
end
end
if(o and o.o_results) then
@ -417,8 +513,10 @@ yl_speak_up.update_stored_npc_data = function(n_id, dialog)
anz_effects = anz_effects + 1
if(r and r.r_type and r.r_type == "state"
and r.r_variable and r.r_variable ~= "") then
variables_e[ r.r_variable ] = true
variables_used[ r.r_variable ] = true
end
-- effects can have alternate_text
variables_used = yl_speak_up.find_player_vars_in_text(variables_used, r.alternate_text)
end
end
end
@ -454,17 +552,14 @@ yl_speak_up.update_stored_npc_data = function(n_id, dialog)
-- delete all old entries that are not longer needed
for k, v in pairs(yl_speak_up.player_vars) do
if(not(variables_p[ k ]) and not(variables_e[ k ])) then
if(not(variables_used[ k ])) then
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, false)
end
end
-- save in the variables' metadata which NPC uses it
-- (this is what we're mostly after - know which variable is used in which NPC)
for k, v in pairs(variables_p) do
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, true)
end
for k, v in pairs(variables_e) do
for k, v in pairs(variables_used) do
yl_speak_up.set_variable_metadata(k, pname, "used_by_npc", n_id, true)
end
-- force writing the data
@ -475,7 +570,7 @@ end
-- which NPC do use this variable?
yl_speak_up.get_variable_metadata = function(var_name, meta_name, get_as_is)
-- var_type (the type of the variable) is a single string
if(meta_name and var_name and meta_name == "var_type") then
if(meta_name and var_name and (meta_name == "var_type" or meta_name == "default_value")) then
if( not(yl_speak_up.player_vars[ var_name ])
or not(yl_speak_up.player_vars[ var_name ][ "$META$"])) then
return nil
@ -546,6 +641,7 @@ yl_speak_up.save_quest = function(q_id)
end
-- read quest q_id from disc
yl_speak_up.load_quest = function(q_id)
-- load the data from the file
@ -563,13 +659,33 @@ yl_speak_up.load_quest = function(q_id)
if(type(data) ~= "table") then
return
end
for k,v in pairs(data) do
if(v == "$NIL_VALUE$") then
data[ k ] = {}
yl_speak_up.quests[q_id] = yl_speak_up.handle_json_nil_values(data)
-- make sure all required fields exist
local quest = yl_speak_up.quests[q_id]
if(quest and not(quest.step_data)) then
quest.step_data = {}
end
if(quest and not(quest.npcs)) then
quest.npcs = {}
end
if(quest and not(quest.locations)) then
quest.locations = {}
end
if(quest) then
for s, d in pairs(quest.step_data) do
if(not(d.where)) then
quest.step_data[s].where = {}
end
if(not(d.one_step_required)) then
quest.step_data[s].one_step_required = {}
end
if(not(d.all_steps_required)) then
quest.step_data[s].all_steps_required = {}
end
end
end
yl_speak_up.quests[q_id] = data
return
yl_speak_up.quests[q_id] = quest
return yl_speak_up.quests[q_id]
end
@ -627,6 +743,7 @@ yl_speak_up.add_quest = function(owner_name, variable_name, quest_name, descr_lo
-- the list of quest steps is stored in the variables' metadata for quicker access
-- (this way we won't have to load the quest file if we want to check a precondition
-- or update the variable value to the next quest step)
-- TODO: store those in the quest file
yl_speak_up.set_variable_metadata(var_name, owner_name, "quest_data", "steps", {"start","finish"})
-- create the quest data structure
@ -658,8 +775,10 @@ yl_speak_up.add_quest = function(owner_name, variable_name, quest_name, descr_lo
-- -> determined from quests.npcs and quests.locations
quest.npcs = {} -- list of NPC that contribute to this quest
-- -> derived from quest.var_name
-- --> or derived from quest.step_data.where
quest.locations = {} -- list of locations that contribute to this quest
-- -> derived from quest.var_name
-- --> or derived from quest.step_data.where
quest.items = {} -- data of quest items created and accepted
-- -> derived from the quest steps
quest.rewards = {} -- list of rewards (item stacks) for this ques
@ -684,11 +803,17 @@ end
-- delete a quest if possible
yl_speak_up.del_quest = function(q_id, pname)
if(not(q_id)) then
return "No quest ID given. Quest not found."
end
local quest = yl_speak_up.load_quest(q_id)
if(not(data)) then
if(not(quest)) then
return "Quest "..tostring(q_id).." does not exist."
end
if(quest.owner ~= pname) then
if(quest.owner ~= pname
and not(minetest.check_player_privs(pname, {npc_master=true}))
and not(minetest.check_player_privs(pname, {npc_talk_master=true}))
and not(minetest.check_player_privs(pname, {npc_talk_admin=true}))) then
return "Quest "..tostring(q_id).." is owned by "..tostring(quest.owner)..
".\n You can't delete it."
end
@ -701,20 +826,23 @@ yl_speak_up.del_quest = function(q_id, pname)
table.concat(quest.subquests, ", ")..
".\nPlease remove the subquests first!"
end
local quest_data = yl_speak_up.get_variable_metadata(k_long, "quest_data", true) or {}
if(quest_data and quest_data.steps and #quest_data.steps > 2) then
return "Quest "..tostring(q_id).." contains more than two steps:\n"..
table.concat(quest_data.steps, ", ")..
".\nPlease remove all steps apart from \"start\" and \"finish\" first!"
for k, v in pairs(quest.step_data) do
if(v) then
return "Quest "..tostring(q_id).." contains at least one remaining quest step.\n"..
"Please remove all steps first!"
end
end
-- TODO: actually delete the file?
-- TODO: set the quest variable back to no type
-- TODO: delete quest variable?
-- TODO: delete (empty?) quest variable? yl_speak_up.del_quest_variable(pname, entry_name, nil)
return "OK"
end
-- returns a list of all quest IDs to which the player has write access
-- TODO: function is unused
yl_speak_up.get_quest_owner_list = function(pname)
local var_list = yl_speak_up.get_quest_variables_with_write_access(pname)
local quest_id_list = {}
@ -723,7 +851,12 @@ yl_speak_up.get_quest_owner_list = function(pname)
if(t and t == "quest") then
local data = yl_speak_up.get_variable_metadata(var_name, "quest_data", true)
if(data and data["quest_nr"]) then
table.insert(quest_id_list, "q_"..tostring(data["quest_nr"]))
local q_id = "q_"..tostring(data["quest_nr"])
yl_speak_up.load_quest(q_id)
-- offer the quest only if it was loaded successfully
if(yl_speak_up.quests[q_id]) then
table.insert(quest_id_list, q_id)
end
end
end
end
@ -732,12 +865,19 @@ end
yl_speak_up.get_sorted_quest_list = function(pname)
local var_list = yl_speak_up.get_quest_variables_with_write_access(pname)
local quest_list = {}
for i, var_name in ipairs(var_list) do
local t = yl_speak_up.get_variable_metadata(var_name, "var_type")
if(t and t == "quest") then
table.insert(quest_list, var_name)
local has_privs = (minetest.check_player_privs(pname, {npc_master=true})
or minetest.check_player_privs(pname, {npc_talk_master=true})
or minetest.check_player_privs(pname, {npc_talk_admin=true}))
for q_id, data in pairs(yl_speak_up.quests) do
if(data and data.var_name) then
if(has_privs
or(data.owner and data.owner == pname)
or(table.indexof(
yl_speak_up.get_access_list_for_var(
data.var_name, pname, "write_access") or {}) ~= -1)) then
table.insert(quest_list, data.var_name)
end
end
end
yl_speak_up.strip_pname_from_varlist(quest_list, pname)
@ -746,6 +886,236 @@ yl_speak_up.get_sorted_quest_list = function(pname)
end
-- quests have a name and a variable which stores their data
-- this returns the q_id (index in the quest table) based on the variable name
yl_speak_up.get_quest_id_by_var_name = function(var_name, owner_name)
local var_name = yl_speak_up.add_pname_to_var(var_name, owner_name)
-- find out which quest we're talking about
for q_id, quest in pairs(yl_speak_up.quests) do
if(quest.var_name == var_name) then
return q_id
end
end
-- TODO or we may have a leftover variable with no quest information stored
-- local var_type = yl_speak_up.get_variable_metadata(var_name, "var_type")
-- if("var_type" == "quest") then
-- -- this is no longer a quest variable - the quest is long gone
-- yl_speak_up.set_variable_metadata(var_name, owner_name, "var_type", nil, "string")
-- yl_speak_up.set_variable_metadata(var_name, owner_name, "quest_data", "quest_nr", nil)
-- yl_speak_up.set_variable_metadata(var_name, owner_name, "quest_data", "steps", nil)
-- end
return nil
end
-- quests can also be identified by their name
-- this returns the q_id (index in the quest table) based on the quest name
yl_speak_up.get_quest_id_by_quest_name = function(quest_name)
-- find out which quest we're talking about
for q_id, quest in pairs(yl_speak_up.quests) do
if(quest.name == quest_name) then
return q_id
end
end
return nil
end
-- finds out if player pname is allowed to view (read_only is true)
-- or edit (read_only is false) the quest q_id
yl_speak_up.quest_allow_access = function(q_id, pname, read_only)
-- no quest with that variable as base found
if(not(q_id) or not(yl_speak_up.quests[q_id])) then
return "Quest not found (id: "..tostring(q_id)..")."
end
local quest = yl_speak_up.quests[q_id]
-- check if the player has access rights to that quest
if(quest.owner ~= pname
and not(minetest.check_player_privs(pname, {npc_master=true}))
and not(minetest.check_player_privs(pname, {npc_talk_master=true}))
and not(minetest.check_player_privs(pname, {npc_talk_admin=true}))) then
-- the player may have suitable privs (check access to the variable)
local access_what = "write_access"
if(read_only) then
access_what = "read_access"
end
local allowed = yl_speak_up.get_access_list_for_var(quest.var_name, "", access_what)
if(table.indexof(allowed, pname) == -1) then
return "Sorry. You have no write access to quest \""..tostring(quest.name).."\" "..
"["..tostring(k).."]."
end
end
-- quests that are already open to the public cannot be changed anymore
-- as that would cause chaos; only in "created" and "testing" stage it's
-- possible to change quest steps
if(not(read_only) and (quest.state == "open" or quest.state == "official")) then
return "The quest is in state \""..tostring(quest.state).."\". Quests in such "..
"a state cannot be changed/extended as that would confuse players. "..
"Reset quest state first if changes are unavoidable."
end
return "OK"
end
-- add a quest step to a quest
yl_speak_up.quest_step_add_quest_step = function(pname, q_id, quest_step_name)
local error_msg = yl_speak_up.quest_allow_access(q_id, pname, false)
if(error_msg ~= "OK") then
return error_msg
end
if(not(quest_step_name) or quest_step_name == ""
or string.len(quest_step_name) < 2 or string.len(quest_step_name) > 70) then
return "No name for this quest step given or too long (>70) or too short (<2 characters)."
end
if(not(yl_speak_up.quests[q_id].step_data)) then
yl_speak_up.quests[q_id].step_data = {}
end
-- create an entry for the quest step if needed
if(not(yl_speak_up.quests[q_id].step_data[quest_step_name])) then
yl_speak_up.quests[q_id].step_data[quest_step_name] = {
-- where (NPCs, locations) can this quest step be set?
where = {},
-- at least one of this quest steps has to be achieved before this one is possible
one_step_required = {},
-- all of these quest steps have to be achieved before this one is possible
all_steps_required = {}
}
yl_speak_up.save_quest(q_id)
end
-- return OK even if the quest step existed already
return "OK"
end
-- delete a quest step - but only if it's not used
yl_speak_up.quest_step_del_quest_step = function(pname, q_id, quest_step_name)
local error_msg = yl_speak_up.quest_allow_access(q_id, pname, false)
if(error_msg ~= "OK") then
return error_msg
end
if(not(quest_step_name)
or not(yl_speak_up.quests[q_id].step_data)
or not(yl_speak_up.quests[q_id].step_data[quest_step_name])) then
return "OK"
end
-- the quest step exists; can we delete it?
local quest_step = yl_speak_up.quests[q_id].step_data[quest_step_name]
local anz_where = 0
for k, _ in pairs(quest_step.where or {}) do
anz_where = anz_where + 1
end
if(anz_where > 0) then
return "This quest step is used/set by "..tostring(anz_where)..
" NPCs and/or locations.\nRemove them from this quest step first!"
end
-- is this the previous quest step of another step?
for sn, step_data in pairs(yl_speak_up.quests[q_id].step_data) do
if(step_data and step_data.previous_step and step_data.previous_step == quest_step_name) then
return "Quest step \""..tostring(sn).."\" names this quest step that you want "..
"to delete as its previous step. Please remove that requirement first "..
"for quest step \""..tostring(sn).."\" before deleting this step here."
end
if(step_data and step_data.further_required_steps
and table.indexof(step_data.further_required_steps, quest_step_name) ~= -1) then
return "Quest step \""..tostring(sn).."\" gives this quest step that you want "..
"to delete as one of the further steps required to reach it. Please "..
"remove that requirement first "..
"for quest step \""..tostring(sn).."\" before deleting this step here."
end
-- offered_until_quest_step_reached would be no problem/hinderance and doesn't need checking
end
yl_speak_up.quests[q_id].step_data[quest_step_name] = nil
yl_speak_up.save_quest(q_id)
return "OK"
end
-- turn a location {n_id=.., d_id=.., c_id=..} or position into a uniq string
yl_speak_up.get_location_id = function(loc)
if(not(loc) or type(loc) ~= "table") then
return nil
end
if(loc.is_block and loc.n_id and loc.d_id and loc.o_id) then
return "POS "..tostring(loc.n_id).." "..tostring(loc.d_id).." "..tostring(loc.o_id)
-- if it's an NPC:
elseif(loc.n_id and string.sub(loc.n_id, 1, 2) == "n_" and loc.d_id and loc.o_id) then
return "NPC "..tostring(loc.n_id).." "..tostring(loc.d_id).." "..tostring(loc.o_id)
else
return nil
end
end
-- add an NPC or location to a quest step (quest_step.where = list of such locations)
-- Note: This is for NPC and locations that SET this very quest step. They ought to be listed here.
-- new_location has to be a table, and new_loc_id an ID to avoid duplicates
-- for NPC, new_loc_id ought to look like this: "NPC <n_id> <d_id> <o_id>"
yl_speak_up.quest_step_add_where = function(pname, q_id, quest_step_name, new_location)
local error_msg = yl_speak_up.quest_allow_access(q_id, pname, false)
if(error_msg ~= "OK") then
return error_msg
end
local step_data = yl_speak_up.quests[q_id].step_data[quest_step_name]
if(not(step_data)) then
return "Quest step \""..tostring(quest_step_name).."\" does not exist."
end
if(not(step_data.where)) then
step_data.where = {}
end
local new_loc_id = yl_speak_up.get_location_id(new_location)
if(not(new_loc_id)) then
return "Failed to create location ID for this location/NPC."
end
-- overwrite existing/old entries
yl_speak_up.quests[q_id].step_data[quest_step_name].where[new_loc_id] = new_location
-- make sure quest.npcs or quest.locations contains this entry
local n_id = new_location.n_id or "?"
if(string.sub(n_id, 1, 2) == "n_") then
-- only npcs that are not yet added (and we store IDs without n_ prefix)
local id = tonumber(string.sub(n_id, 3))
if(id and table.indexof(yl_speak_up.quests[q_id].npcs or {}, id) == -1) then
table.insert(yl_speak_up.quests[q_id].npcs, id)
end
elseif(string.sub(n_id, 1, 1) == "p"
and table.indexof(yl_speak_up.quests[q_id].locations or {}, n_id) == -1) then
table.insert(yl_speak_up.quests[q_id].locations, n_id)
end
yl_speak_up.save_quest(q_id)
-- return OK even if the quest step existed already
return "OK"
end
-- delete a quest step location with the id location_id
yl_speak_up.quest_step_del_where = function(pname, q_id, quest_step_name, old_location)
local error_msg = yl_speak_up.quest_allow_access(q_id, pname, false)
if(error_msg ~= "OK") then
return error_msg
end
local quest_step = yl_speak_up.quests[q_id].step_data[quest_step_name]
if(not(quest_step)) then
return "Quest step \""..tostring(quest_step_name).."\" does not exist."
end
local loc_id = yl_speak_up.get_location_id(old_location)
if(not(loc_id)) then
return "Failed to create location ID for this location/NPC."
end
if(not(yl_speak_up.quests[q_id].step_data[quest_step_name])) then
yl_speak_up.quests[q_id].step_data[quest_step_name].where = {}
end
-- delete the quest step location
yl_speak_up.quests[q_id].step_data[quest_step_name].where[loc_id] = nil
yl_speak_up.save_quest(q_id)
return "OK"
end
-- TODO: quest_step: previous_step -> one_step_required
-- TODO: quest_step: further_required_steps -> all_steps_required
-- TODO: quest_step: offered_until_quest_step_reached
-- called for example by yl_speak_up.eval_all_preconditions to see if the player
-- can reach quest_step in quest quest_id
yl_speak_up.quest_step_possible = function(player, quest_step, quest_id, n_id, d_id, o_id)
@ -766,3 +1136,21 @@ yl_speak_up.quest_step_reached = function(player, quest_step, quest_id, n_id, d_
-- TODO: actually store the quest progress
-- minetest.chat_send_player("singleplayer", "SETTING quest step "..tostring(quest_step).." for quest "..tostring(quest_id))
end
-- load all known quests
yl_speak_up.load_all_quests = function()
for var_name, var_data in pairs(yl_speak_up.player_vars) do
local var_type = yl_speak_up.get_variable_metadata(var_name, "var_type")
if(var_type == "quest") then
local data = yl_speak_up.get_variable_metadata(var_name, "quest_data", true)
if(data and data["quest_nr"]) then
yl_speak_up.load_quest("q_"..tostring(data["quest_nr"]))
end
end
end
end
-- do so on startup and reload
yl_speak_up.load_all_quests()

871
readme.md
View File

@ -1,53 +1,870 @@
## Version
# Let NPC talk in Minetest (like in RPGs/point&click) [yl_speak_up]
Current version: 202009231753
This mod allows to set RPG-like texts for NPC where the NPC "says" something
to the player and the player can reply with a selection of given replies -
which most of the time lead to the next dialog.
## Dependencies
Original author: AliasAlreadyTaken/aka Bastrabun
Massive rewrite/extension: Sokomine
- mobs_redo
## Quick installation
## Optional dependencies
License: From now (24.03.2024) on (but not past versions), this mod (minus the editor,
which is now a seperate mod) is available under the MIT license.
License of textures (`yl_speak_up_bg_dialog*.png` plus masks): AliasAlreadyTaken/aka Bastrabun CC0
None so far, but there is a command in another package that let's you conveniently upload new skins
Reporting bugs: Please report issues <a href="https://gitea.your-land.de/Sokomine/yl_speak_up">here</a>.
## Requirements
Clone via i.e. `git clone https://gitea.your-land.de/Sokomine/yl_speak_up`
or install via <a href="https://content.minetest.net/">ContentDB</a>.
202009231753 for 5.3.0
In order to be able to edit the dialogs of the NPC ingame, please install
<a href="https://gitea.your-land.de/Sokomine/npc_talk_edit">npc_talk_edit</a>.
The only situation where you might not need this extra mod is when you
design your own adventure game and your players are not expected to edit
the NPC.
## License
Optional dependency:
<a href="https://content.minetest.net/packages/TenPlus1/mobs/">mobs_redo</a>
is highly recommended. You may be able to use other mob mods with
manual adjustments as well (This is for very advanced users).
None yet. This may change in the future. Whoever has access to the repo can use it for their own purpose. Also, licensing of third party stuff is questionable at best, so I can't offer a proper license at the moment. I'm no lawyer
This mod here does not provide any NPC as such. You need a mod that does so.
The mod <a href="https://gitea.your-land.de/Sokomine/npc_talk">npc_talk</a> adds the actual NPC.
It is highly recommended to use `npc_talk` or to create your own according to your needs.
## Installation
You might also wish to use <a href="https://content.minetest.net/packages/TenPlus1/mobs_npc/">mobs_npc</a>
from TenPlus1 as that mod provides very useful actual NPC and will be used by
<a href="https://gitea.your-land.de/Sokomine/npc_talk">npc_talk</a> if installed.
Enter your mod repository, something like /home/yourname/games/minetest/mods/
To get started, best install `yl_speak_up`, `npc_talk_edit`, `npc_talk`, `mobs_redo` and `mobs_npc` in your world.
`git clone https://gitea.your-land.de/your-land/yl_speak_up.git`
Look at the config.lua and alter the values as you like.
## Table of Content
If you wish to to upload skins or textures on runtime, you need to create a second mod in the worldfolder and have worldedit installed.
1. [For players: How to use it as a player](#1-for-players-how-to-use-it-as-a--player)
1. [1.1 Terminology](#11-terminology)
1. [1.2 How it works](#12-how-it-works)
1. [1.3 How to configure NPC and add dialogs](#13-how-to-configure-npc-and-add_dialogs)
1. [1.4 Skin](#14-skin)
1. [1.5 Mute](#15-mute)
1. [1.6 Chat Commands for players](#16-chat-commands-for-players)
1. [1.7 Simple replacements (NPC name, player name etc)](#17-simple-replacements-npc-name-player-name-etc)
1. [1.8 Alternate Text](#18-alternate-text)
1. [1.9 Autoselect/Autoanswer](#19-autoselectautoanswer)
1. [1.10 Random Dialogs](#110-random-dialogs)
1. [1.11 Maximum recursion depth](#111-max-recursion-depth)
1. [1.12 Changing the start dialog](#112-changing-the-start-dialog)
1. [1.13 Special dialogs](#113-special-dialogs)
1. [1.14 Trading (simple)](#114-trading-simple)
1. [1.15 Quest items](#115-quest-items)
1. [1.16 Entering Passwords](#116-entering-passwords)
1. [1.17 Giving things to the NPC](#117-giving-things-to-the-npc)
1. [1.18 Quests](#118-quests)
1. [1.19 Properties](#119-properties)
1. [1.20 Logging](#120-logging)
1. [1.21 Export/Import](#121-exportimport)
1. [1.22 Storing internal notes](#122-storing-internal-notes)
1. [1.23 Counting visits to dialogs and options](#123-counting-visits-to-dialog-and-options)
## Support mod
2. [For moderators: Generic NPCs](#2-for-moderators-generic-npcs)
1. [2.1 Generic behaviour](#21-generic-behaviour)
1. [2.2 Chat Commands for moderators](#22-chat-commands-for-moderators)
To create this mod you need go to your world folder
3. [For server owners: What to consider as a server owner](#3-server-owners-what-to-consider-as-a-server-owner)
1. [3.1 Tutorial](#31-tutorial)
1. [3.2 The privs](#32-the-privs)
1. [3.3 Chat Commands for server owners](#33-chat-commands-for-server-owners)
1. [3.4 Reload mod without server restart](#34-reload-mod-without-server-restart)
1. [3.5 Restoring a lost NPC](#35-restoring-a-lost-npc)
1. [3.6 Tools](#36-tools)
1. [3.7 Configuration](#37-configuration)
1. [3.8 Adding local overrides for your server](#38-adding-local-overrides-for-your-server)
1. [3.9 Data saved in modstorage](#39-data-saved-in-modstorage)
1. [3.10 Files generated in world folder](#310-files-generated-in-world-folder)
1. [3.11 Additional custom replacements (in addition to NPC name, player name etc)](#311-additional-custom-replacements-in-addition-to-npc-name-player-name-etc)
1. [3.12 Custom Preconditions, Actions and Effects](#312-custom-preconditions-actions-and-effects)
1. [3.13 Integration into your own NPC/mob mods](#313-integration-into-your-own-npcmob-mods)
1. [3.14 Dynamic dialog](#314-dynamic-dialog)
Create a folder and name it yl_npc
4. [Future](#4-future)
Inside this folder create an empty init.lua file
Create a textures folder, where all the skins go that you upload after server start
## 1. For players: How to use it as a player
<a name="for-players"></a>
How to actually upload a skin during runtime, see usage.md/Upload a skin
You need:
* the `npc_talk_owner` [priv](#privs)
* an actual NPC of which you are owner
* to be able to right-click your NPC
## Usage
### 1.1 Terminology
<a name="terminology"></a>
See usage.md
| Word used | What it means [and how it's called; names and numbers are assigned automaticly] |
| -------------- | ------------------------------------------------------------------------------- |
| dialog | A text said by the NPC, with diffrent replys the player can select from. [`d_<nr>`] |
| option | A reply/answer to the text the NPC said. The player selects one by clicking on it. [`o_<nr>`] |
| precondition/<br>prerequirement | All listed preconditions have to be true in order for the NPC to offer an option. [`p_<nr>`] |
| action | An action the player may (or may not) take, i.e. trading, taking an item from the NPC, giving the NPC something, entering the right password etc. The action happens after the option is selected by the player. [`a_<nr>`] |
| effect/result | Further effects (like setting variables, handing out items) that take place after the action was successful. If there was no action, the effects/results will be executed directly after selecting the option. [`r_<nr>`]|
| alternate text | Text shown instead of the normal dialog text. This is useful when you have a dialog with a lot of questions and want the player to be able to easily select the next question without having to create a new dialog for each option. [-] |
## Storage
Dialogs are stored in JSON inside the world directory in the folder specified in yl_speak_up.path
### 1.2 How it works
<a name="how-it-works"></a>
When someone right-clicks your NPC, the NPC will show a _dialog_. Remember: The _dialog_ is the text the
NPC says to the player plus the _options_ (or answers, choices) offered to the player.
Some texts, like i.e. the name of the NPC or its owner, can be [automaticly replaced](#simple-variables) in the dialog text and the text of the options/answers.
The dialog that is to be shown is selected this way:
* At first, the NPC will usually show the [start dialog](#start_dialog).
* If the player selected an option, the target dialog of that option is shown.
* If the NPC inherits some [generic behaviour](#generic_behaviour), it may show another dialog or additional options.<br>*Note:* As a player, you have no influence on this.
* [Autoselect/Autoanswer](#autoselect_autoanswer) may select an option automaticly if all preconditions are met and switch to a diffrent dialog.
* If the options are set to [random Dialog](#random_dialogs), then an option is choosen randomly and the appropriate dialog will be shown.
Not all options of a dialog will be shown in all situations. An option/answer can have one or more _preconditions_. _All preconditions of an option have to be true_ in order for the option to be offered to the player. A _precondition_ is something that can be either true or false. This can be:
* _check_ an internal state (i.e. of a [quest](#quests)) - based on a system of [variables](#variables)
* _check_ the value of a [property of the NPC](#properties) (for [generic NPC](#generic_behaviour))
* something that has to be calculated or evaluated (=call a function); this can be extended with [custom functions](#precon_action_effect) on the server
* a block somewhere (i.e. that block is cobble, or not dirt, or air, or...)
* a [trade](#trading-simple) - Is the trade possible?
* the inventory of the player (contains an item or does not, has room for item etc.)
* the inventory of the NPC (similar to the player's inventory above)
* the inventory of a block somewhere (for chests, furnaces etc; similar to the player's inventory above)
* an item the player offered/gave to the NPC (react to [the last thing the player gave to the NPC](#npc_wants))
* execute Lua code (requires `npc_master` [priv](#privs)) - extremly powerful and dangerous
* The preconditions of another dialog option are fulfilled/not fulfilled.
* nothing - always true (useful for generic dialogs)
* nothing - always false (useful for temporally deactivating an option)
If the preconditions of the option the player clicked on are all true, operation proceeds with that option.
If the preconditions are not fullfilled, the NPC can show an alternate text or a greyed out text. That option cannot be selected by the player then but is visible.
There may be _one_ _action_ defined. Actions are situations where the NPC shows a formspec to the player and expects some reaction. When the player reacts, the NPC evaluates the action as either successful (i.e. gave the right thing, right password) or a failure. Possible actions are:
* No action (default)
* Normal [trade](#trading-simple) - one item(stack) for another item(stack)
* The NPC gives something to the player (i.e. a [quest item](#quest-items)) out of its inventory.
* The player is expected to give something to the NPC (i.e. a [quest item](#quest-items)). The NPC will store it in his inventory.
* The player has to manually [enter a password or passphrase or some other text](#quest-passwords).
* Show something [custom (has to be provided by the server)](#precon_action_effect).
The NPC may react diffrently when the action failed and show a diffrent dialog. It's also possible to limit how often a player may guess wrongly withhin a given timespan and how often an action may be repeated withhin a given timespan.
If the action was successful (or there was no action at all), then the _effects_/results are executed. Possible effects are:
* _change_ an internal state (i.e. of a [quest](#quests)) - based on a system of [variables](#variables)
* _change_ the value of a [property of the NPC](#properties) (for [generic NPC](#generic_behaviour))
* something that has to be calculated or evaluated (=call a function); this can be extended with [custom functions](#precon_action_effect) on the server; Example: Set a variable to a random number.
* _place_, _dig_, _punch_ or _right-click_ a block somewhere
* put item from the NPC's inventory into a chest etc.
* take item from a chest etc. and put it into the NPC's inventory
* _deal with_ (accept or refuse) an item the player offered to the NPC - [the last thing the player gave to the NPC](#npc_wants))
* NPC crafts something with the things he has in its inventory
* go to other dialog if the previous _effect_ failed
* send a chat message to all players
* give item (created out of thin air) to player (requires extra [privs](#privs) for you _and_ the NPC)
* take item from player and destroy it (requires extra [privs](#privs) for you _and_ the NPC)
* move the player to a given position (requires extra [privs](#privs) for you _and_ the NPC)
* execute any Lua code (requires extra [privs](#privs) for you _and_ the NPC)
The _effects_ also contain a special effect named _dialog_. This effect determines which dialog will be shown next if the player selected this option and all went well. The target _dialog_ is usually set in other parts of the formspec and not set manually as an _effect_.
It is possible to show an [alternate text](#alternate_text) instead of the normal dialog text when the target _dialog_ is shown. This may help in situations where the NPC may have to answer a lot of questions and an extra dialog for each answer seems impractical.
### 1.3 How to configure NPC and add dialogs
<a name="how-to-configure"></a>
Just talk to the NPC and click on the `"I am your owner"`-Dialog. This opens up
a menu where you can edit most things.
Most of the basic configuration and adding of new dialogs and options can be done
in the menu that pops up on rightclick. However, more advanced things such as
_preconditions_, _actions_ and _effects_ can only be set in the _Edit options_
menu. You can reach it by clicking on the button of the option, i.e. on `o_1`.
*Hint*: The command `/npc_talk debug <npc_id>` allows you to get debug
information regarding preconditions and effects. You can turn it
off with `/npc_talk debug off`. The NPC ID can be seen in the
setup dialog for preconditions and effects.
The text the NPC says is written in the markup language Minetest uses. See
<a href="https://github.com/minetest/minetest/blob/master/doc/lua_api.md">lua_api.md</a>
for more details. You can use the elements of that markup language for the
text that the NPC says, i.e.
`<img name=default_cobble.png width=100 height=100>`
will display the image `default_cobble.png`.
### 1.4 Skin
<a name="skin"></a>
The skin and what the NPC wields can be changed via the `"Edit Skin"` button.
It is also possible to set the animation the NPC shows (sitting, lying down,
walking, mining etc.) if that is [configured for this NPC](#integration).
### 1.5 Mute
<a name="mute"></a>
When you edit an NPC, you might want to stop it from talking to other players
and spoiling unifinished texts/options to the player.
For this case, the NPC can be muted. This works by selecting the appropriate
option in the talk menu after having started edit mode by claiming to be the
NPC's owner.
### 1.6 Chat Commands for players
<a name="chat-commands-players"></a>
In general, chat commands used by this mod have the form `/npc_talk <command> [<optional parameters>]`.
| Command | Description |
| ------- | ----------- |
| `style <nr>` | Allows to select the formspec version with `<nr>` beeing 1, 2 or 3. Important for very old clients.|
| `list` | Shows a list of all your NPC and where they are.|
| `debug [<npc_id>]` | Toggle debug mode for NPC `<npc_id>`. Warning: May scroll a lot.|
| `debug off` | Turn debug mode off again.|
| `force_edit` | Toggles edit mode for you.<br> From now on (until you issue this command again), all NPC you talk to will be in edit mode (provided you are allowed to edit them). This is useful if something's wrong with your NPC like i.e. you made it select a dialog automaticly and let that dialog lead to `d_end`. |
There are [additional commands](#chat-commands-server) that require [further privs](#privs) for some special functionality (i.e. [generic NPC behaviour](#generic_behaviour)).
### 1.7 Simple replacements (NPC name, player name etc)
<a name="simple-variables"></a>
If you want to let your NPC greet the player by name, you can do so. Some
variables/texts are replaced appropriately in the text the NPC says and
in the options the player can select from:
| Variable | will be replaced with.. |
| --------------- | ----------------------- |
| `$MY_NAME$` | ..the name of the NPC|
| `$NPC_NAME$` | ..same as above (name of the NPC)|
| `$OWNER_NAME$` | ..the name of the owner of the NPC|
| `$PLAYER_NAME$` | ..the name of the player talking to the NPC|
| `$GOOD_DAY$` | ..`"Good morning"`, `"Good afternoon"` or `"Good evening"` - depending on the ingame time of day|
| `$good_DAY$` | ..same as above, but starts with a lowercase letter (i.e. `"good morning"`)|
The replacements will not be applied in edit mode.
Servers can define [additional custom replacements](#add-simple-variables).
It is also possible to insert the *value* of variables into the text. Only variables the owner of the NPC has read access to can be replaced. Example: The variable "Example Variable Nr. 3" (without blanks would be a better name, i.e. "example\_variable\_nr\_3"), created by "exampleplayer", could be inserted by inserting the following text into dialog texts and options:
$VAR exampleplayer Example Variable Nr. 3$
It will be replaced by the *value* that variable has for the player that is talking to the NPC.
Properties can be replaced in a similar way. The value of the property "job" of the NPC for example could thus be shown:
$PROP job$
Variables and properties can only be replaced if their *name* contains only alphanumeric signs (a-z, A-Z, 0-9), spaces, "\_", "-" and/or ".".
### 1.8 Alternate Text
<a name="alternate_text"></a>
Sometimes you may encounter a situation where your NPC ought to answer to
several questions and the player likely wanting an answer to each. In such
a situation, you might create a dialog text for each possible option/answer
and add an option to each of these new dialogs like "I have more questions.".
That is sometimes impractical. Therefore, you can add alternate texts.
These alternate texts can be shown instead of the normal dialog text when the
player selected an option/answer. Further alternate texts can be shown if
the action (provided there is one defined for the option) or an effect failed.
The alternate text will override the text of the dialog (what the NPC says)
but offer the same options/answers as the dialog normally would.
Alternate texts can be converted to normal dialogs, and normal dialogs can
vice versa be converted to alternate texts if only one option/answer points
to them.
### 1.9 Autoselect/Autoanswer
<a name="autoselect_autoanswer"></a>
Sometimes you may wish to i.e. greet the player who has been sent on a mission
or who is well known to the NPC in a diffrent way.
For that purpose, you can use the option in the edit options menu right next to
`"..the player may answer with this text [dialog option "o_<nr>"]:"` and switch that
from `"by clicking on it"` to `"automaticly"`.
When the NPC shows a dialog, it will evaluate all preconditions of all options. But
once it hits an option where selecting has been set to `"automaticly"` and all other
preconditions are true, it will abort processing the current dialog and move on to
the dialog stated in the automaticly selected option/answer and display that one.
### 1.10 Random Dialogs
<a name="random_dialogs"></a>
If you want the NPC to answer with one of several texts (randomly selected), then
add a new dialog with options/answers that lead to your random texts and edit one
of these options so that it is `"randomly"` selected.
Note that random selection affects the entire dialog! That dialogs' text isn't shown
anymore, and neither are the texts of the options/answers shown. Whenever your NPC
ends up at this dialog, he'll automaticly choose one of the options/answers randomly
and continue there!
There is no way to assign weights to the options. All are choosen with equal probability.
### 1.11 Maximum recursion depth
<a name="max_recursion_depth"></a>
Autoselect/autoanswer and random dialogs may both lead to further dialogs with further
autoanswers and/or random selections. As this might create infinite loops, there's a
maximum number of "redirections" through autoanswer and random selection that your NPC
can take. It is configured in config.lua as
`yl_speak_up.max_allowed_recursion_depth`
and usually set to 5.
### 1.12 Changing the start dialog]
<a name="start_dialog"></a>
The _start dialog_ is usually the _first_ dialog of the NPC: `d_1`. It may sometimes become
necessary to change that when your NPC gets a new (temporary?) job or when you're designing
a [generic NPC](#generic_behaviour). You can change the _start dialog_ easily with the
options the NPC offers you when you talk to it as owner.
### 1.13 Special dialogs
<a name="special-dialogs"></a>
In general, dialogs follow the naming schem `d_<nr>`. However, some few have a
special meaning:
| Dialog | Meaning |
| ---------------- | ------- |
| `d_<nr>` | Normal dialog. They are automaticly numbered. |
| `d_end` | End the conversation (i.e. after teleporting the player). |
| `d_got_item` | The NPC [got something](#npc_wants) and is trying to decide what to do with it. |
| `d_trade` | [Trade](#trading-simple)-specific options. I.e. crafting new items when stock is low.|
| `d_dynamic` | [Dynamic dialog](#dynamic-dialog) that is changed on the fly. Each NPC has exactly one. |
### 1.14 Trading (simple)
<a name="trading-simple"></a>
The NPC can trade item(stacks) with other players.
Only undammaged items can be traded.
Items that contain metadata (i.e. written books, petz, ..) cannot be traded.
Dammaged items and items containing metadata cannot be given to the NPC.
Trades can either be attached to dialog options (and show up as results there)
via the edit options dialog or just be trades that are shown in a trade list.
The trade list can be accessed from the NPC's inventory.
If there are trades that ought to show up in the general trade list (i.e. not
only be attached to dialog options), then a button "Let's trade" will be shown
as option for the first dialog.
Trades that are attached to the trade list (and not dialog options) can be
added and deleted without entering edit mode (`"I am your owner. ..."`).
If unsure where to put your trades: If your NPC wants to tell players a story
about what he sells (or if it is i.e. a barkeeper), put your trades in the
options of dialogs. If you just want to sell surplus items to other players
and have the NPC act like a shop, then use the trade list.
### 1.15 Quest items
<a name="quest-items"></a>
[Quest](#quests) items can be _created_ to some degree as part of the _action_ of an
_option_ through the edit options menu.
<a href="https://github.com/minetest/minetest">MineTest</a>
does not allow to create truely new items on a running server on
the fly. What can be done is giving specific items (i.e. _that_ one apple,
_that_ piece of paper, _that_ stack of wood, ..) a new description that
makes it diffrent from all other items of the same type (i.e. all other
apples).
A new description alone may also be set by the player with the engraving
table (provided that mod is installed):
<a href="https://forum.minetest.net/viewtopic.php?t=17482">See Minetest Forum Engraving Table Topic</a>
In order to distinguish items created by your NPC and those created through
the engraving table or other mods, you can set a special ID. That ID will
also contain the name of the player to which the NPC gave the item. Thus,
players can't just take the quest items of other players from their bones
and solve quests that way.
The actions _npc_gives_ and _npc_wants_ are responsible for handling of
quest items. They can of course also handle regular items.
If an NPC creates a special quest item for a player in the _npc_gives_
action, it takes the item out of its inventory from a stack of ordinary
items of that type and applies the necessary modifications (change
description, set special quest ID, set information which player got it).
If the NPC gets such a quest item in an _npc_wants_ action, it will check
the given parameters. If all is correct, it will strip those special
parameters from the item, call the action a success and store the item
in its inventory without wasting space (the item will likely stack if it
is _not_ a quest item).
### 1.16 Entering Passwords
<a name="quest-passwords"></a>
Another useful method for [quests](#quests) is the _text_input_ action. It allows the
NPC to ask for a passwort or the answer to a question the NPC just asked.
The player's answer is checked against the _expected answer_ that you give
when you set up this action.
### 1.17 Giving things to the NPC
<a name="npc_wants"></a>
There are several ways of giving items to the NPC:
* selecting `"Show me your inventory"` and putting it directly into the NPC's inventory (as owner)
* [trading](#trading-simple)
* using an action where the NPC wants a more or less special item
* or an effect/result where the NPC just removes the item from the player's inventory and thrashes it (requires `npc_talk_admin` [priv](#privs) - or whichever priv you set in config.lua as `yl_speak_up.npc_privs_priv`).
Using an action might work in many situations. There may be situations where it
would be far more convenient for the player to just give the items to the NPC and let
it deal with it. This is what the special dialog `d_got_item` is for.
In order to activate this functionality, just enter edit mode and select the option
`"I want to give you something.".`
A new dialog named `d_got_item` will be created and an option shown to players in the
very first dialog where they can tell the NPC that they want to give it something.
The dialog `d_got_item` can have options like any other dialog - except that [autoselect](#autoselect_autoanswer)
will be activated for each option. If there are no options/answers to this dialog or
none fits, the NPC will offer the items back automaticly.
Please make sure each option of the dialog `d_got_item` has..
* a precondition that inspects what was offered:<br>`"an item the player offered/gave to the NPC" (precondition)`
* and an effect/result that deals with the item:<br>`"an item the player offered to the NPC" (effect)`
Else the items will just be offered back to the player.
It is also possible to examine the item the NPC got using the precondition `"an item the player offered/gave to the NPC"`.
### 1.18 Quests
<a name="quests"></a>
NPC love handing out quests! Especially if they got any complex quests.
A simple "bring me 10 dead rats" may not excite players or NPC much.
But a true riddle, something that involves some thinking and puzzling - that's great!
Every NPC which got such a quest will be proud (just search for "Epic NPC man" on youtube).
Quest handling is far from optimal yet and will be improved in the future.
In order to manage a quest, you need to store information about a player.
You can _create_ _variables_ that will hold data for each player. That way
you can remember which _quest step_ the player has already completed.
Variables can be created and managed by adding and editing _preconditions_
and _effects_ of the type `"an internal state (i.e. of a quest)"`.
Inside a _precondition_, you can _check_ the _value of a variable_ (it's always
evaluated for the current player that clicked on your NPC), and in an _effect_
you can _change_ the _value of the variable_ for that player.
You can also grant access to a variable you've created to other players so that
they may use it in their quests and NPC.
You can also check the values for all players (regarding your own variables)
and edit those values if needed.
Checking and setting quest progress currently has to be done manually.
It's ongoing work to move that to the _edit options menu_ (only partially
implemented so far).
### 1.19 Properties
<a name="properties"></a>
NPC may have _properties_. A _property_ is a _value a particular NPC has_. It
does not depend on any player and will remain the same until you change
it for this NPC. You can view and change properties via the `"Edit"` button
and then clicking on `"Edit properties"`. There are preconditions for
checking properties and effects for changing them.
Properties prefixed by the text `"self."` originate from the NPC itself,
i.e. `self.order` (as used by many `mobs_redo` NPC for either following their
owner, standing around or wandering randomly). They usually cannot be
changed - unless you write a function for them. See
`yl_speak_up.custom_property_handler`
and
`custom_functions_you_can_override.lua`
You can also react to or limit normal property changes this way.
Properties starting with `"server"` can only be changed by players who have
the `npc_talk_admin` priv.
Example for a property: Mood of the NPC (raises when treated well, gets
lowered when treated badly).
Properties are also extremly important for generic behaviour. Depending
on the properties of the NPC, a particular [generic behaviour](#generic_behaviour) might fit
or not fit to the NPC.
### 1.20 Logging
<a name="logging"></a>
The trade list view provides access to a log file where all changes to the
NPC, inventory movements and purchases are logged.
The log shows the date but not the time of the action. Players can view the
logs of their own NPC.
Admins can keep an NPC from logging by setting the property
`server_nolog_effects` to i.e `true`
That way, the NPC will no longer log or send debug messages when executing
effects.
### 1.21 Export/Import
<a name="export"></a>
It's possible to export the full dialog data of an NPC and to view it in more
human-readable form. It's also possible to export it in some degree in the
format the mod simple\_dialogs uses (minus preconditions, actions, effects and
all other special things).
Players with the `privs` priv can also import those dialogs into an NPC in
their local singleplayer game. But be warned: The dialog is *not* checked for
consistency or anything yet! That is why it requires the `privs` priv. Don't
just import any NPC data someone sends to you!
It is planned to check the dialog (.json format) more fully in the future so
that players can import data on a server as well. However, we are not that far
yet.
### 1.22 Storing internal notes
<a name="notes"></a>
It's possible to take internal notes on your NPC by clicking on the "Notes"
button. You can help your memory and store information about your plans with
this NPC - its character, how it behaves and talks, who his friends are etc.
These internal notes are only shown to players who can edit this NPC.
### 1.23 Counting visits to dialogs and options
<a name="visit-counter"></a>
Whenever a dialog text is displayed to the player and whenever the player
selects an option which is successful (no aborted actions or `on_failure`
effects inbetween), a counter called `visits` is increased by one for that
dialog or option.
This information is *not* persistent! When the player leaves the talk by
choosing `Farewell!` or pressing ESC all visit information is lost.
The feature can be used to help players see which options they've tried
and which path they've followed. If an option is set to `*once*` instead
of the default `often` in the edit options dialog, that option can only
be selected *once* each time the player talks to this NPC. After that the
option gets greyed out and displays "[Done]" followed by the normal option
text.
Visits are not counted in edit mode and not counted for generic dialogs.
There are custom preconditions for checking the number of visits to a dialog
and/or option.
## 2. For moderators: Generic NPCs
<a name="for-moderators"></a>
You need:
* the `npc_talk_admin` [priv](#privs)
With this priv you can edit and maintain NPC that show [generic behaviuor](#geeric_behaviour)
and which may influence or override all other NPC on the server.
### 2.1 Generic behaviour
<a name="generic_behaviour"></a>
Sometimes you may have a group of NPC that ought to show a common behaviour - like for example guards, smiths, bakers, or inhabitants of a town, or other NPC that have something in common. Not each NPC may warrant its own, individual dialogs.
The [Tutoial](#tutorial) is another example: Not each NPC needs to present the player with a tutorial, but those that are owned and where the owner tries to program them ought to offer some help.
That's where _generic dialogs_ come in. You can create a new type of generic dialog with any NPC. That NPC can from then on only be used for this one purpose and ought not to be found in the _"normal"_ world! Multiple such generic dialogs and NPC for their creation can exist.
[Properties](#properties) are very helpful for determining if a particular NPC ought
to offer a generic dialog.
The _`entity_type`_ precondition can also be helpful in this case. It allows to add dialogs for specific types of mobs only (i.e. `npc_talk:talking_npc`).
Requirements for a generic dialog to work:
* _Generic dialogs_ have to [start with a dialog](#start_dialog) with _just one option_.
* This _option_ has to be set to _"automaticly"_ (see [Autoanswer](#autoselect_autoanswer)).
* The _preconditions_ of this option are very important: They determine if this particular generic dialog fits to this particular NPC or not. If it fits, all dialogs that are part of this NPC that provides the generic dialog will be added to the _"normal"_ dialogs the importing actual NPC offers. If it doesn't fit, these generic dialogs will be ignored here.
* The creator of a generic dialog can't know all situations where NPC may want to use his dialogs and where those NPC will be standing and by whom they are owned. Therefore only a limited amount of types of preconditions are allowed for the preconditions of this first automatic option: `state`, `property`, `player_inv` and `custom`.
* The other dialogs that follow after this first automatic dialog may contain a few more types of preconditions: `player_offered_item`, `function` and `other` are allowed here as well, while `block`, `trade`, `npc_inv` and `block_inv` make no sense and are not available.
* All types of actions are allowed.
* Regarding effects/results, the types `block`, `put_into_block_inv`, `take_from_block_inv` and `craft` are not supported.
The `"automaticly"` selected only option from the [start dialog](#start_dialog) leads via the usual `"dialog"` effect to the actual [start dialog](#start_dialog) for the imported dialogs from that NPC. The options found there will be added into the target NPC and the dialog text will be appended to its dialog text.
The chat command `/npc_talk generic` (requires `npc_talk_admin` [priv](#privs)) is used to list, add or remove NPC from the list of generic dialog/behaviour providers.
### 2.2 Chat Commands for moderators
<a name="chat-commands-moderators"></a>
In general, chat commands used by this mod have the form `/npc_talk <command> [<optional parameters>]`.
The [chat commands for players](#chat-commands-players) are still important and valid. In addition,
commands for managing [generic NPC](#generic_behaviour) become available:
| Command | Description |
| ------- | ----------- |
| `generic list` | list all NPC that provide generic dialogs|
| `generic add <npc_id>` | add NPC `<npc_id>` as a new provider of generic dialogs|
| `generic remove <npc_id>` | remove NPC `<npc_id>` as a provider of generic dialogs|
| `generic reload` | reload data regarding generic dialogs|
## 3. For server owners: What to consider as a server owner
<a name="server-owners"></a>
### 3.1 Tutorial
<a name="tutorial"></a>
There is an NPC that explains a few things and adds as a tutuor.
The savefile is: `n_1.json`
Copy that file to the folder
`<your world folder>/yl_speak_up_dialogs/n_1.json`
(replace the 1 with a higher number if you already have some NPC defined).
The first NPC in your world will now become a tutor (after you spawned it
and set its name).
### 3.2 The privs
<a name="privs"></a>
| Minetest priv | what this priv grants the player who has it |
| ----------------- | ------------------------------------------- |
| `npc_talk_owner` | will allow players to edit their *own* NPC by talking to them. Ought to be given to all players.|
| `npc_talk_master` | allows players to edit *any* NPC supported by this mod.<br>Ought to be given to selected trusted players who want to help others with their NPC configuration and/or support NPCs owned by the server.|
| `npc_talk_admin` | Generic maintenance of NPC.<br>Allows to use the the command `/npc_talk generic` and add or remove an NPC from the list of generic dialog providers.<br>Also allows to set and change NPC properties starting with the prefix "server".|
| `npc_master` | allows players to edit *any* NPC supported by this mod. *Does* include usage of the staffs (now part of `yl_npc`).<br>This is very powerful and allows to enter and execute lua code without restrictions.<br>Only grant this to staff members you ***really*** trust. The priv is usually not needed.|
| `privs` | Necessary for the commands<br>`/npc_talk privs` - grant NPC privs like e.g. execute lua and <br>`/npc_talk_reload` - reload code of this mod|
NPC can have privs as well:
| NPC priv | The NPC.. |
| -------------------- | --------- |
| `precon_exec_lua` | ..is allowed to excecute lua code as a precondition|
| `effect_exec_lua` | ..is allowed to execute lua code as an effect|
| `effect_give_item` | ..can give items to the player, created out of thin air|
| `effect_take_item` | ..can accept and destroy items given to it by a player|
| `effect_move_player` | ..can move the player to another position|
### 3.3 Chat Commands for server owners
<a name="chat-commands-owners"></a>
The [chat commands for players](#chat-commands-players) and the [chat commands for moderators](#chat-commands-moderators) are still important and valid. In addition, commands for managing [privs](#privs) become available:
In general, chat commands used by this mod have the form `/npc_talk <command> [<optional parameters>]`.
| Command | Description |
| ------- | ----------- |
| `privs list` | List the privs of all NPC. NPC need privs for some dangerous things like executing lua code.|
| `privs grant <npc_id> <priv>` | grant NPC `<npc_id>` the priv `<priv>`|
| `privs revoke <npc_id> <priv>` | revoke priv `<priv>` for NPC `<npc_id>`|
* NPC need privs for some dangerous things like executing lua code.<br>Example: `/npc_talk privs grant n_3 effect_exec_lua`<br>grants NPC n_3 the right to execute lua code as an effect/result.<br>Note: If a precondition or effect originates from a generic NPC, the priv will be considered granted if either the executing NPC or the the generic NPC has the priv.
### 3.4 Reload mod without server restart
<a name="hotreload"></a>
The mod doesn't define any items or NPC itself. It is designed in a way that you can
_reload the code of this mod without having to restart the server_.
The command `/npc_talk_reload` reloads almost all of the code of this mod.
When you add new [custom functions](#precon_action_effect) to this mod or change a custom function - or even code of the mod itself! -, you can reload this mod without having to restart the server. If you made an error and the files can't load then your server will crash - so please _test on a test server_ first! Requires the _privs_ priv.
### 3.5 Restoring a lost NPC
<a name="npc_restore"></a>
Sometimes (hopefully rarely!) an NPC entity and its egg may get lost. It may have got lost due to someone having misplaced its egg (happens). Or it might have been killed somehow.
In that case, you can run `/npc_talk force_restore_npc <id> [<copy_from_id>]`
The optional parameter `<copy_from_id>` is only used when the NPC is _not_ listed in `/npc_talk list`. You won't need it. It's for legacy NPC.
*WARNING*: If the egg or the NPC turns up elsewhere, be sure to have only *one* NPC with that ID standing around! Else you'll get chaos.
### 3.6 Tools
<a name="tools"></a>
There are no more tools (=staffs) provided. You can do all you could do
with them with the staffs by just talking to the NPC. The staffs are deprecated.
Use `/npc_talk force_edit` if the NPC is broken and cannot be talked to normally anymore.
### 3.7 Configuration
<a name="configuration"></a>
Please make sure that the tables
```
yl_speak_up.blacklist_effect_on_block_<type> with <type>:<interact|place|dig|punch|right_click|put|take>
```
contain all the blocks which do not allow the NPCs this kind of interaction.
<br>You may i.e. set the `put` and `take` tables for blocks that do extensive
checks on the player object which the NPC simply can't provide.
```
yl_speak_up.blacklist_effect_tool_use
```
is a similar table. Please let it contain a list of all the tool item names that NPC are _not_ allowed to u se.
_Note_: The best way to deal with local adjustments may be to create your
own mod, i.e. `yl_speak_up_addons`, and let that mod depend on this
one, `yl_speak_up`, and do the necessary calls. This is very useful
for i.e. adding your own textures or doing configuration. You can
then still update the mod without loosing local configuration.
### 3.8 Adding local overrides for your server
<a name="server_overrides"></a>
* You can override and add config values by creating and adding a file
```
local_server_config.lua
```
in the mod folder of this mod. It will be executed after the file `config.lua` has been executed. This happens at startup and each time after the command `/npc_talk_reload` has been given.
* If you want to add or override existing functions (i.e. functions from/for `custom_functions_you_can_override.lua`), you can create a file named
```
local_server_do_on_reload.lua
```
in the mod folder of this mod. It will be executed at startup and each time `/npc_talk_reload` is executed.
* Note: If you want to register things (call minetest.register_-functions), you have to do that in the file
```
local_server_do_once_on_startup.lua
```
which will be executed only _once_ after server start and _not_ when `/npc_talk_reload` is executed.
### 3.9 Data saved in modstorage
<a name="modstorage"></a>
| Variable | Usage |
| ------------------ | ------- |
| `status` | Set this to 2 to globally deactivate all NPC. |
| `amount` | Number of NPCs generated in this world. This is needed to create a uniqe ID for each NPC. |
| `generic_npc_list` | List of NPC ids whose dialogs shall be used as generic dialogs. |
### 3.10 Files generated in world folder
<a name="files_generated"></a>
| Path/File name | Content |
| ----------------------------------- | ------- |
| `yl_speak_up.path` | Directory containing the JSON files containing the stored dialogs of the NPC. |
| `yl_speak_up.inventory_path` | Directory containing the detatched inventories of the NPC. |
| `yl_speak_up.log_path` | Directory containing the logfiles of the NPC. |
| `yl_speak_up.quest_path` | Directory containing information about quests. |
| `yl_speak_up_npc_privs.data` | File containing the privs of the NPC. |
| `yl_speak_up.player_vars_save_file` | JSON file containing information about quest progress and quest data for individual players. |
### 3.11 Additional custom replacements (in addition to NPC name, player name etc)
<a name="simple-variables"></a>
In addition to the [simple replacements (NPC name, player name etc)](#simple-variables),
a server owner can add server-specific replacements as needed.
In order to do this, add the following in your own mod:
```lua
local old_function = yl_speak_up.replace_vars_in_text
yl_speak_up.replace_vars_in_text = function(text, dialog, pname)
-- do not forget to call the old function
text = old_function(text, dialog, pname)
-- do your own replacements
text = string.gsub(text, "$TEXT_TO_REPLACE$", "new text")
-- do not forget to return the new text
return text
end
```
### 3.12 Custom Preconditions, Actions and Effects
<a name="precon_action_effect"></a>
You can define custom actions and provide up to ten parameters. The file
`custom_functions_you_can_override.lua`
holds examplexs. Please do not edit that file directly. Just take a look
there and override functions as needed in your own files! That way it is
much easier to update.
In general, the table
`yl_speak_up.custom_functions_p_[ descriptive_name ]`
holds information about the parameters for display in the formspec (when
setting up a precondition, action or effect) and contains the function
that shall be executed.
### 3.13 Integration into your own NPC/mob mods
<a name="integration"></a>
In order to talk to NPC, you need to call
```lua
if(minetest.global_exists("yl_speak_up") and yl_speak_up.talk) then
yl_speak_up.talk(self, clicker)
return
end
```
in the function that your NPC executes in `on_rightclick`.
Note that capturing and placing of your NPC is *not* handled by `yl_speak_up`!
Use i.e. the lasso that came with your NPC mod.
You also need to make sure that the textures of your mob can be edited. In order to do so,
* add an entry in `yl_speak_up.mesh_data[<model.b3d>]` for your model
* add an entry in `yl_speak_up.mob_skins[<entity_name>] = {"skin1.png", "skin2.png", "another_skin.png"}`
* call `table.insert(yl_speak_up.emulate_orders_on_rightclick, <entity_name>)` if your mob is a `mobs_redo` one and can stand, follow (its owner) and walk around randomly. As you override `on_rightclick`, this setting will make sure to add buttons to emulate previous behaviour shown when clicking on the NPC.
### 3.14 Dynamic dialogs
<a name="dynamic-dialog"></a>
Sometimes you may have to generate a dialog on the fly and/or wish for more dynamic texts and answers.
Each NPC has a `d_dynamic` dialog that will never be saved with the NPC data and that can be changed each time the player selected an option.
This is particulary useful for using external functions for generating dialog texts and options/answers plus their reactions.
Warning: This feature is not finished yet and undergoing heavy changes. Do not rely on its functionality yet!
## 4. Future
<a name="future"></a>
Creating quests is possible but not very convincing yet. It is too tedious and errorprone. I'm working on a better mechanism.
## Bugs, suggestions, features & bugfixes
Report bugs here: https://gitea.your-land.de/your-land/yl_speak_up/issues

View File

@ -53,6 +53,7 @@ minetest.register_privilege("npc_talk_admin", npc_talk_admin_priv_definition)
minetest.register_on_leaveplayer(
function(player)
yl_speak_up.reset_vars_for_player(player:get_player_name(), true)
yl_speak_up.player_left_remove_trade_inv(player)
end
)
@ -159,17 +160,6 @@ minetest.register_chatcommand( 'npc_talk_reload', {
-- most of the files of this mod can be reloaded without the server having to
-- be restarted;
-- handled in init.lua
minetest.register_chatcommand( 'npc_talk_force_edit', {
description = "Toggles force edit mode. This is helpful if you cut yourself out "..
"of editing an NPC by breaking it. From now on all NPC you will talk to "..
"will already be in edit mode (provided you are allowed to edit them)."..
"\nIssuing the command again ends force edit mode.",
privs = {npc_talk_owner = true},
func = function(pname, param)
return yl_speak_up.command_npc_talk_force_edit(pname, param)
end,
})
-- a general command that may branch off and/or offer help
minetest.register_chatcommand( 'npc_talk', {

View File

@ -1,120 +1,33 @@
-- allow show_fs to be extended more easily;
-- key: formname without yl_speak_up: prefix
yl_speak_up.registered_forms_get_fs = {}
yl_speak_up.registered_forms_input_handler = {}
-- force_fs_ver can be nil if no special formspec version is required
yl_speak_up.registered_forms_force_fs_ver = {}
yl_speak_up.register_fs = function(formname, fun_input_handler, fun_get_fs, force_fs_ver)
yl_speak_up.registered_forms_input_handler[formname] = fun_input_handler
yl_speak_up.registered_forms_get_fs[formname] = fun_get_fs
yl_speak_up.registered_forms_force_fs_ver[formname] = force_fs_ver
end
-- route player input to the right functions;
-- return true when the right function has been found
-- called in minetest.register_on_player_receive_fields
yl_speak_up.input_handler = function(player, formname, fields)
if formname == "yl_speak_up:optiondialog" then
yl_speak_up.input_optiondialog(player, formname, fields)
return true
elseif formname == "yl_speak_up:setdialog" then
yl_speak_up.input_setdialog(player, formname, fields)
return true
-- handled in fs_save_or_discard_or_back.lua
elseif formname == "yl_speak_up:save_or_discard_changes" then
yl_speak_up.input_save_or_discard_changes(player, formname, fields)
return true
-- handled in fs_edit_options_dialog.lua
elseif formname == "yl_speak_up:edit_option_dialog" then
yl_speak_up.input_edit_option_dialog(player, formname, fields)
return true
elseif formname == "yl_speak_up:talk" then
yl_speak_up.input_talk(player, formname, fields)
return true
elseif formname == "yl_speak_up:fashion" then
yl_speak_up.input_fashion(player, formname, fields)
return true
elseif formname == "yl_speak_up:fashion_extended" then
yl_speak_up.input_fashion_extended(player, formname, fields)
return true
-- handled in fs_properties.lua
elseif formname == "yl_speak_up:properties" then
yl_speak_up.input_properties(player, formname, fields)
return true
-- handled in inventory.lua
elseif formname == "yl_speak_up:inventory" then
yl_speak_up.input_inventory(player, formname, fields)
return true
-- handled in fs_trade_list.lua
elseif formname == "yl_speak_up:trade_list" then
yl_speak_up.input_trade_list(player, formname, fields)
return true
-- handled in fs_player_offers_item.lua
elseif formname == "yl_speak_up:player_offers_item" then
yl_speak_up.input_player_offers_item(player, formname, fields)
return true
-- handled in trade_simple.lua
elseif formname == "yl_speak_up:do_trade_simple" then
yl_speak_up.input_do_trade_simple(player, formname, fields)
return true
-- handled in fs_trade_via_buy_button.lua
elseif formname == "yl_speak_up:trade_via_buy_button" then
yl_speak_up.input_trade_via_buy_button(player, formname, fields)
return true
-- (as above - also handled in trade_simple.lua)
elseif formname == "yl_speak_up:add_trade_simple" then
yl_speak_up.input_add_trade_simple(player, formname, fields)
return true
elseif formname == "yl_speak_up:trade_limit" then
yl_speak_up.input_trade_limit(player, formname, fields)
return true
elseif formname == "yl_speak_up:show_log" then
yl_speak_up.input_show_log(player, formname, fields)
return true
elseif formname == "yl_speak_up:show_npc_list" then
yl_speak_up.input_show_npc_list(player, formname, fields)
return true
elseif formname == "yl_speak_up:edit_trade_limit" then
yl_speak_up.input_edit_trade_limit(player, formname, fields)
return true
-- handled in fs_initial_config.lua
elseif formname == "yl_speak_up:initial_config" then
yl_speak_up.input_fs_initial_config(player, formname, fields)
return true
-- handled in fs_assign_quest_step.lua
elseif formname == "yl_speak_up:assign_quest_step" then
yl_speak_up.input_fs_assign_quest_step(player, formname, fields)
return true
-- handled in fs_edit_preconditions.lua
elseif formname == "yl_speak_up:edit_preconditions" then
yl_speak_up.input_fs_edit_preconditions(player, formname, fields)
return true
-- handled in fs_edit_actions.lua
elseif formname == "yl_speak_up:edit_actions" then
yl_speak_up.input_fs_edit_actions(player, formname, fields)
return true
-- handled in fs_edit_actions.lua
elseif formname == "yl_speak_up:action_npc_gives" then
yl_speak_up.input_fs_action_npc_gives(player, formname, fields)
return true
elseif formname == "yl_speak_up:action_npc_wants" then
yl_speak_up.input_fs_action_npc_wants(player, formname, fields)
return true
elseif formname == "yl_speak_up:action_text_input" then
yl_speak_up.input_fs_action_text_input(player, formname, fields)
return true
elseif formname == "yl_speak_up:action_evaluate" then
yl_speak_up.input_fs_action_evaluate(player, formname, fields)
return true
-- handled in fs_edit_effects.lua
elseif formname == "yl_speak_up:edit_effects" then
yl_speak_up.input_fs_edit_effects(player, formname, fields)
return true
-- handled in fs_manage_variables.lua
elseif formname == "yl_speak_up:manage_variables" then
yl_speak_up.input_fs_manage_variables(player, formname, fields)
return true
-- handled in fs_manage_quests.lua
elseif formname == "yl_speak_up:manage_quests" then
yl_speak_up.input_fs_manage_quests(player, formname, fields)
return true
-- handled in fs_alternate_text.lua
elseif formname == "yl_speak_up:show_what_points_to_this_dialog" then
yl_speak_up.input_fs_show_what_points_to_this_dialog(player, formname, fields)
return true
-- create and manage quests
elseif formname == "yl_speak_up:quest_gui" then
yl_speak_up.input_quest_gui(player, formname, fields)
return true
if(not(formname)) then
return false
end
-- cut off the leading "yl_speak_up:" prefix
local fs_name = string.sub(formname, 13)
if(fs_name and fs_name ~= "") then
local fun = yl_speak_up.registered_forms_input_handler[fs_name]
if(fun) then
fun(player, formname, fields)
return true
end
end
end
@ -122,6 +35,13 @@ end
-- show formspec with highest possible version information for the player
-- force_version: optional parameter
yl_speak_up.show_fs_ver = function(pname, formname, formspec, force_version)
-- catch errors
if(not(formspec)) then
force_version = "1"
formspec = "size[4,2]label[0,0;Error: No text found for form\n\""..
minetest.formspec_escape(formname).."\"]"..
"button_exit[1.5,1.5;1,0.5;exit;Exit]"
end
-- if the formspec already calls for a specific formspec version: use that one
if(string.sub(formspec, 1, 17) == "formspec_version[") then
minetest.show_formspec(pname, formname, formspec)
@ -148,92 +68,26 @@ yl_speak_up.show_fs = function(player, fs_name, param)
return
end
local last_fs = yl_speak_up.speak_to[pname].last_fs
-- show the save or discard changes dialog
if(fs_name and fs_name == "save_or_discard_changes") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:save_or_discard_changes",
yl_speak_up.get_fs_save_or_discard_changes(player, param))
-- abort talk if we hit d_end
if(fs_name == "talk" and param and param.d_id and param.d_id == "d_end") then
yl_speak_up.stop_talking(pname)
return
-- the player either saved or discarded; we may proceed now
elseif(fs_name and fs_name == "proceed_after_save") then
fs_name = yl_speak_up.speak_to[pname].next_fs
param = yl_speak_up.speak_to[pname].next_fs_param
yl_speak_up.speak_to[pname].next_fs = nil
yl_speak_up.speak_to[pname].next_fs_param = nil
yl_speak_up.speak_to[pname].last_fs = fs_name
yl_speak_up.speak_to[pname].last_fs_param = param
if(not(fs_name) or fs_name == "quit") then
yl_speak_up.reset_vars_for_player(pname, false)
return
end
-- the player clicked on "back" in the above dialog
elseif(fs_name and fs_name == "show_last_fs") then
-- call the last formspec again - and with the same parameters
fs_name = yl_speak_up.speak_to[pname].last_fs
param = yl_speak_up.speak_to[pname].last_fs_param
-- do we need to check if there is something that needs saving?
elseif(fs_name
-- msg is just a loop for displaying (mostly error) messages
and fs_name ~= "msg"
and fs_name ~= "player_offers_item"
-- is the player editing the NPC? that is: might there be any changes?
and (yl_speak_up.edit_mode[pname] == yl_speak_up.speak_to[pname].n_id)) then
local last_fs = yl_speak_up.speak_to[pname].last_fs
local d_id = yl_speak_up.speak_to[pname].d_id
local o_id = yl_speak_up.speak_to[pname].o_id
-- only these two formspecs need to ask specificly if the data ought to be saved
if(last_fs == "talk" or last_fs == "edit_option_dialog" or fs_name == "quit") then
local last_param = yl_speak_up.speak_to[pname].last_fs_param
local show_save_fs = false
if(not(param)) then
param = {}
end
-- set the target dialog
yl_speak_up.speak_to[pname].target_dialog = param.d_id
-- if we are switching from one dialog to another: is it the same?
if(last_fs == "talk" and fs_name == last_fs
and param and param.d_id and param.d_id ~= d_id) then
-- diffrent parameters: save (if needed)
show_save_fs = true
elseif(fs_name == "talk" and param and param.do_save) then
-- player clicked on save button
show_save_fs = true
-- leaving a dialog: save!
elseif(last_fs == "talk" and fs_name ~= last_fs) then
show_save_fs = true
-- clicking on "save" in an edit option dialog: save!
elseif(last_fs == "edit_option_dialog" and fs_name == last_fs
and param and param.caller and param.caller == "save_option") then
show_save_fs = true
-- leaving editing an option: save!
elseif(last_fs == "edit_option_dialog" and fs_name ~= last_fs) then
show_save_fs = true
-- quitting: save!
elseif(fs_name == "quit") then
yl_speak_up.speak_to[pname].target_dialog = nil
show_save_fs = true
end
-- show the save or discard dialog
if(show_save_fs) then
yl_speak_up.speak_to[pname].next_fs = fs_name
yl_speak_up.speak_to[pname].next_fs_param = param
-- check first if it's necessary to ask for save or discard
yl_speak_up.input_save_or_discard_changes(player, "", {})
return
end
end
-- store the new formspec
yl_speak_up.speak_to[pname].last_fs = fs_name
-- and its parameter
yl_speak_up.speak_to[pname].last_fs_param = param
end
-- this formspec was renamed; make it usable again
if(fs_name == "trade_simple") then
fs_name = "do_trade_simple"
end
local fun = yl_speak_up.registered_forms_get_fs[fs_name]
if(fun) then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:"..fs_name,
fun(player, param),
yl_speak_up.registered_forms_force_fs_ver[fs_name])
return true
-- this is here mostly to fascilitate debugging - so that really all calls to
-- minetest.show_formspec are routed through here
if(fs_name == "msg") then
elseif(fs_name == "msg") then
if(not(param)) then
param = {}
end
@ -243,181 +97,6 @@ yl_speak_up.show_fs = function(player, fs_name, param)
elseif(fs_name == "quit") then
return
-- staff-based editing
elseif(fs_name == "optiondialog") then
if(not(param)) then
param = {}
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:optiondialog",
yl_speak_up.get_fs_optiondialog(player, param.n_id, param.d_id, param.o_id, param.p_id, param.r_id))
elseif(fs_name == "setdialog") then
if(not(param)) then
param = {}
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:setdialog",
yl_speak_up.get_fs_setdialog(player, param.n_id, param.d_id))
elseif(fs_name == "edit_option_dialog") then
-- the optional "caller" parameter can be used for debugging
if(not(param)) then
param = {}
end
yl_speak_up.speak_to[pname].o_id = param.o_id
yl_speak_up.show_fs_ver(pname, "yl_speak_up:edit_option_dialog",
yl_speak_up.get_fs_edit_option_dialog(player, param.n_id, param.d_id, param.o_id,
param.caller))
elseif(fs_name == "talk") then
if(not(param)) then
param = {}
end
if(param.d_id and param.d_id == "d_end") then
yl_speak_up.stop_talking(pname)
return
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:talk",
-- recursion depth from autoanswer: 0 (the player selected manually)
yl_speak_up.get_fs_talkdialog(player, param.n_id, param.d_id, param.alternate_text,0))
elseif(fs_name == "fashion") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:fashion",
yl_speak_up.get_fs_fashion(pname))
elseif(fs_name == "fashion_extended") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:fashion_extended",
yl_speak_up.get_fs_fashion_extended(pname))
elseif(fs_name == "properties") then
if(not(param)) then
param = {}
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:properties",
yl_speak_up.get_fs_properties(pname, param.selected),
1)
elseif(fs_name == "inventory") then
-- this is a very classical formspec; it works far better with OLD fs;
-- force formspec version 1
yl_speak_up.show_fs_ver(pname, "yl_speak_up:inventory",
yl_speak_up.get_fs_inventory(player),
1)
elseif(fs_name == "trade_list") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:trade_list",
yl_speak_up.get_fs_trade_list(player, param))
elseif(fs_name == "player_offers_item") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:player_offers_item",
yl_speak_up.get_fs_player_offers_item(player, param), 1)
elseif(fs_name == "trade_simple") then
-- the optional parameter param is the trade_id
if(not(param) and yl_speak_up.speak_to[pname]) then
param = yl_speak_up.speak_to[pname].trade_id
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:do_trade_simple",
yl_speak_up.get_fs_trade_simple(player, param), 1)
elseif(fs_name == "trade_via_buy_button") then
-- the optional parameter param is the trade_id
if(not(param) and yl_speak_up.speak_to[pname]) then
param = yl_speak_up.speak_to[pname].trade_id
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:trade_via_buy_button",
yl_speak_up.get_fs_trade_via_buy_button(player, param), 1)
elseif(fs_name == "add_trade_simple") then
-- the optional parameter param is the trade_id
if(not(param) and yl_speak_up.speak_to[pname]) then
param = yl_speak_up.speak_to[pname].trade_id
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:add_trade_simple",
yl_speak_up.get_fs_add_trade_simple(player, param), 1)
elseif(fs_name == "trade_limit") then
if(not(param)) then
param = {}
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:trade_limit",
yl_speak_up.get_fs_trade_limit(player, param.selected))
elseif(fs_name == "show_log") then
if(not(param)) then
param = {}
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_log",
yl_speak_up.get_fs_show_log(player, param.log_type))
elseif(fs_name == "show_npc_list") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_npc_list",
yl_speak_up.get_fs_show_npc_list(player, nil))
elseif(fs_name == "edit_trade_limit") then
if(not(param)) then
param = {}
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:edit_trade_limit",
yl_speak_up.get_fs_edit_trade_limit(player, param.selected_row), 1)
elseif(fs_name == "initial_config") then
if(not(param)) then
param = {}
end
yl_speak_up.show_fs_ver(pname, "yl_speak_up:initial_config",
yl_speak_up.get_fs_initial_config(player, param.n_id, param.d_id,
param.is_initial_config))
elseif(fs_name == "assign_quest_step") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:assign_quest_step",
yl_speak_up.get_fs_assign_quest_step(player, param))
elseif(fs_name == "edit_preconditions") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:edit_preconditions",
yl_speak_up.get_fs_edit_preconditions(player, param))
elseif(fs_name == "edit_actions") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:edit_actions",
yl_speak_up.get_fs_edit_actions(player, param))
elseif(fs_name == "edit_effects") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:edit_effects",
yl_speak_up.get_fs_edit_effects(player, param))
-- action related
elseif(fs_name == "action_npc_gives") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:action_npc_gives",
yl_speak_up.get_fs_action_npc_gives(player, param), 1)
elseif(fs_name == "action_npc_wants") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:action_npc_wants",
yl_speak_up.get_fs_action_npc_wants(player, param), 1)
elseif(fs_name == "action_text_input") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:action_text_input",
yl_speak_up.get_fs_action_text_input(player, param))
elseif(fs_name == "action_evaluate") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:action_evaluate",
yl_speak_up.get_fs_action_evaluate(player, param))
elseif(fs_name == "manage_variables") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:manage_variables",
yl_speak_up.get_fs_manage_variables(player, param))
elseif(fs_name == "manage_quests") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:manage_quests",
yl_speak_up.get_fs_manage_quests(player, param))
elseif(fs_name == "show_what_points_to_this_dialog") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:show_what_points_to_this_dialog",
yl_speak_up.show_what_points_to_this_dialog(player, param))
elseif(fs_name == "quest_gui") then
yl_speak_up.show_fs_ver(pname, "yl_speak_up:quest_gui",
yl_speak_up.get_fs_quest_gui(player, param))
-- fallback in case of wrong call
else
minetest.chat_send_player(pname, "Error: Trying to show wrong "..

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 B

Some files were not shown because too many files have changed in this diff Show More