Compare commits
374 Commits
trade_list
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
0548172db0 | ||
|
38081f8f17 | ||
|
cce17c7c23 | ||
|
94906c221b | ||
|
7896d53dd6 | ||
|
6b6c31e4aa | ||
|
b371f15801 | ||
|
3855a9b393 | ||
|
9d2fb157d0 | ||
|
2b7db390c5 | ||
|
208cafc725 | ||
|
de001c3539 | ||
|
e00be2fc43 | ||
|
710c8e1928 | ||
|
1b9df50376 | ||
|
52e04dff2f | ||
a1a5d5c243 | |||
|
67fe90c984 | ||
|
9077feb1a9 | ||
|
2e1732644f | ||
|
9af492c07a | ||
|
a85f0629c5 | ||
|
9b2aae7fe1 | ||
|
799233c8a2 | ||
|
7e5fc745c0 | ||
|
40e7fc2087 | ||
|
8e0f53fdb5 | ||
|
d5b5832b02 | ||
|
7c801291f9 | ||
|
133a8ccf8d | ||
|
543c767dd5 | ||
|
7ce56ca733 | ||
|
0cd28dcb54 | ||
|
d2fdb6da61 | ||
|
51ab9e1ecb | ||
|
000978ef50 | ||
|
0d76f7492a | ||
|
6370396fea | ||
|
16df2f9155 | ||
|
31434e69ce | ||
|
829683f750 | ||
|
76f7e37566 | ||
|
d9d10112cf | ||
|
dc223d99f5 | ||
|
c0ed2cc148 | ||
|
06507880b6 | ||
|
8570fea8ba | ||
|
40314125a8 | ||
|
e5183813d7 | ||
|
c0fcbecd63 | ||
|
10e90e412a | ||
|
4cf06da1e4 | ||
|
c4ebef21f0 | ||
|
7e3ea18653 | ||
|
e1f13f7bff | ||
|
293df54dac | ||
|
a7f5f3c8b0 | ||
|
8456a10107 | ||
|
28c3e7eed9 | ||
|
5cd9a9aab6 | ||
|
e3094962c3 | ||
|
68e37e24b8 | ||
|
1b45fc7792 | ||
|
95e721b8f9 | ||
|
349d140d09 | ||
|
8af9a995a0 | ||
|
9797ada402 | ||
|
0316afdfa9 | ||
|
bfeed37767 | ||
|
a54c395e91 | ||
|
6e604b05d0 | ||
|
5f6f488788 | ||
|
769b4046b4 | ||
|
3cf9da85ca | ||
|
b9bebe82bd | ||
|
3d21489d49 | ||
|
1323f3d8fc | ||
|
7b8e98534f | ||
|
f029f5d21b | ||
|
23762a141a | ||
|
5f2917df01 | ||
|
e262b53a04 | ||
|
a9d5adb3a9 | ||
|
fab5316837 | ||
|
fe8755a260 | ||
|
ebfc068694 | ||
|
30dfa9c378 | ||
|
507123660f | ||
|
cfb673993f | ||
|
7343366d2f | ||
|
8236c9826e | ||
|
bbfbb20896 | ||
|
96806d3c66 | ||
|
c26068fd48 | ||
|
443d6568b1 | ||
|
86e4d9a3fa | ||
|
7471691d55 | ||
|
1e2981fc38 | ||
|
5f7f769d87 | ||
|
e306fe3466 | ||
|
70e1d4144c | ||
|
7905d9937f | ||
|
f5ecb06ce6 | ||
|
13de73ecde | ||
|
1c89bd7dcf | ||
|
9781ea9b43 | ||
|
f503d436dc | ||
|
a8b92a754f | ||
|
189165dd22 | ||
|
6381ea59b9 | ||
|
c2107d0bf3 | ||
|
b076e6a2f5 | ||
|
2d677127dd | ||
|
24d3a05ce0 | ||
|
5c7ffe3739 | ||
|
3eab65d2ee | ||
|
2329724ef2 | ||
|
20735916c1 | ||
|
48243f2ddc | ||
|
db2abf76fe | ||
|
d58789e703 | ||
|
6f51ddd10d | ||
|
cc15431cb2 | ||
|
d588e38308 | ||
|
7b14fb6668 | ||
|
691301019b | ||
|
774b076f3b | ||
|
e3e03ee990 | ||
|
eaf84cfd31 | ||
|
55b180544f | ||
|
d52aa2e5cb | ||
|
71e08eac08 | ||
|
94a1d1092a | ||
|
6de16720e0 | ||
|
35fa4e9774 | ||
|
42c3751966 | ||
|
0b7078e3e9 | ||
|
c7fbdb84e4 | ||
|
b93186d3c2 | ||
|
c1deea695c | ||
|
b04d37c963 | ||
|
e2722d7c60 | ||
|
f70bc72f85 | ||
|
0a3764fe84 | ||
|
6450417637 | ||
|
9968adca25 | ||
|
10e8cef102 | ||
|
1748a3308b | ||
|
7c72e4215b | ||
|
dfc88b4895 | ||
|
b1f5a3ee9e | ||
|
61d2fc1679 | ||
|
cb0869f53c | ||
|
a7d6d07785 | ||
|
7482061868 | ||
|
c0c3cae460 | ||
|
4e5e3d9093 | ||
|
2dae28a655 | ||
|
24022c98b6 | ||
|
db2f401322 | ||
|
7ddf97d49d | ||
|
d7440369b5 | ||
|
77e6906f0e | ||
|
9f44bb1961 | ||
|
e27809d4d7 | ||
|
b2fb83d046 | ||
|
0490f8183c | ||
|
d6338abeca | ||
|
82616c85b1 | ||
|
2a8b621606 | ||
|
7cbc610866 | ||
|
21f77baae1 | ||
|
61b5799247 | ||
|
063aa7e297 | ||
|
92d3622464 | ||
|
f6bcb2e6c6 | ||
|
928675a3c1 | ||
|
8838307d16 | ||
|
7408d8d286 | ||
|
20056cb1e9 | ||
|
9fe6713bd9 | ||
|
711febcb4c | ||
|
9d26267e67 | ||
|
8c27f02c39 | ||
|
12fc39b3d0 | ||
|
03da9151c4 | ||
|
04b67da670 | ||
|
1cc4f2e742 | ||
|
0f6962e0a8 | ||
|
1659aed8ab | ||
|
e7369f3075 | ||
|
d10b394204 | ||
|
567decb7bd | ||
|
763a7e27c5 | ||
|
21005455ab | ||
|
21c8f1149d | ||
|
c2728f853b | ||
|
e33e1326fd | ||
|
596d521345 | ||
|
8dbaaf21d5 | ||
|
aa707c40ac | ||
|
b6f5cd9029 | ||
|
abf063975e | ||
|
630287d99a | ||
|
682cc3bccd | ||
|
89bd9ea546 | ||
|
8b3fb10faf | ||
|
4d2da67f9d | ||
|
3dab37dae7 | ||
|
24a6edcd53 | ||
|
4cfab4c1d8 | ||
|
79aae18038 | ||
|
c7f9fc81c3 | ||
|
99e2b4c2ec | ||
|
bfbc7c238b | ||
|
478849812d | ||
|
aee256938b | ||
|
7fc2d89974 | ||
|
753d3f3eec | ||
|
936cb08e71 | ||
|
34e639f68f | ||
|
c8d5293df0 | ||
|
920fbcf062 | ||
|
964fce04e3 | ||
|
49b370b7bb | ||
|
44e8683e61 | ||
|
8110442b2a | ||
|
f224222d7c | ||
|
eb6cf0f031 | ||
|
067744981b | ||
|
922c2af88f | ||
|
3847dda6ee | ||
|
8396464fcf | ||
|
fe1910c88f | ||
|
3ae305dcc9 | ||
|
536ad045f6 | ||
|
2cbac5ca7e | ||
|
0f0593100e | ||
|
afe47c3908 | ||
|
e9ee295db5 | ||
|
4f15503dfe | ||
|
e8082faf75 | ||
|
fad536a0a4 | ||
|
d2e5362cf9 | ||
|
361bcd9dd1 | ||
|
c368538c28 | ||
|
d57ac190eb | ||
|
031abad71b | ||
|
259a79e359 | ||
|
eb6d75c8fc | ||
|
abbb8cf123 | ||
|
f37f07c722 | ||
|
2c2b3d173d | ||
|
e73a0cf35c | ||
|
2e3bc1ddea | ||
|
9f7349146d | ||
|
a1963d6b52 | ||
|
f3b0f10603 | ||
|
e3762b9c4c | ||
|
40a72949eb | ||
|
992f653534 | ||
077b7e2425 | |||
30d605f8f0 | |||
81fb3dd999 | |||
6425b580dd | |||
56e2863b76 | |||
|
556c24f782 | ||
|
f1668176ad | ||
|
9b7e08f2b0 | ||
|
f60aaa8bd0 | ||
|
1416ab39d3 | ||
|
dc278123d6 | ||
|
c513e0b8e4 | ||
|
62d267cfe7 | ||
|
aa3fd4580c | ||
|
8888373210 | ||
|
a66340ef8a | ||
|
58976ec35b | ||
|
696a36a145 | ||
|
d57afa434c | ||
|
a33b52f0d8 | ||
|
a735a9a86f | ||
|
cf868e0761 | ||
|
6346a747e0 | ||
|
1dd6c587c7 | ||
|
6f8dde7e61 | ||
|
3c4f5bd3ea | ||
|
53c9318cd4 | ||
|
7e4bd07109 | ||
|
2e40e236af | ||
|
c4e5c65773 | ||
|
2d2afdfb26 | ||
|
4993656940 | ||
|
861c44024a | ||
|
b7423b0d81 | ||
|
ce067bf18e | ||
|
78f26d42a1 | ||
|
13abe1687a | ||
|
94df301302 | ||
|
f12f778b06 | ||
|
36ed71ae56 | ||
|
2183c1af32 | ||
|
2c28a0fc26 | ||
|
feae914e85 | ||
|
a3c1c5ccfd | ||
|
b82a985ca1 | ||
|
7a9ab3867d | ||
|
9e3b98d09e | ||
|
ba81ac2f63 | ||
|
3f9da138e0 | ||
|
b621fe7b12 | ||
|
234a18efff | ||
|
314c637aae | ||
|
c3d97233dd | ||
|
d267560d2a | ||
|
32510ad974 | ||
|
8d0377572d | ||
|
e2ed6263ac | ||
|
5e8e31876c | ||
|
1347fb1bdb | ||
|
fd35e172d8 | ||
|
f201d0a244 | ||
|
52b280035e | ||
|
8b8fbfa4aa | ||
|
e8053e25e9 | ||
|
0ec51adf74 | ||
|
af43cc467c | ||
|
58fe04cd49 | ||
|
10b3ac6aff | ||
|
c744ce6aff | ||
|
7d602fccf2 | ||
|
6a24488ac2 | ||
|
b451432b84 | ||
|
eabafafe17 | ||
|
9313ef3bef | ||
|
4bcbf8ee0a | ||
|
83f92f1a49 | ||
|
fd525a950f | ||
|
030f75be15 | ||
|
0204f38a46 | ||
|
d9208a1365 | ||
|
35506731bd | ||
|
6ecf800a60 | ||
|
decae6977e | ||
|
1c72d714e7 | ||
|
352a0322f4 | ||
|
5ba53ba637 | ||
|
1e4e3cbaef | ||
|
e879e1e67a | ||
|
31f29beecd | ||
|
49c264193c | ||
|
4ce6101bca | ||
|
263ec1ac86 | ||
|
f00fe9ea84 | ||
|
1e7a4a4ad1 | ||
|
d2f92ced9a | ||
|
698472c16f | ||
|
e69e2f4c02 | ||
|
28a1fc8b9a | ||
|
8b5c45701a | ||
|
8db72aad9d | ||
|
08bb8d3ea5 | ||
|
1b492e50f8 | ||
|
17792a28d1 | ||
|
bf8627d48b | ||
|
21480b3bdb | ||
|
04debcb096 | ||
|
3bc3b70bc4 | ||
|
c6fef779ac | ||
|
5b1035a19e | ||
|
d8c6394dc0 | ||
|
b2148d3e91 | ||
5372cfdec1 | |||
31d86bc058 |
9
LICENSE
Normal 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
@ -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.
|
||||
|
@ -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
@ -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
|
45
addons/effect_send_coordinates.lua
Normal 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,
|
||||
}
|
94
addons/effect_send_mail.lua
Normal 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
@ -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
@ -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.
|
@ -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
@ -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
|
||||
|
||||
|
@ -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
@ -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
|
@ -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
@ -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
@ -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
@ -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
@ -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
|
@ -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
|
||||
-----------------------------------------------------------------------------
|
@ -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
|
||||
|
252
config.lua
@ -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
|
||||
------------------------------------------------------------------------------
|
||||
|
@ -1 +0,0 @@
|
||||
mobs
|
107
dev/example.json
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -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" } } } } } } }
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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" } } } } } } }
|
@ -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
@ -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
|
@ -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
|
445
exec_actions.lua
@ -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
|
||||
|
@ -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).." "..
|
||||
|
@ -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
@ -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
|
@ -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
@ -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
@ -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
@ -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
|
||||
)
|
67
fs/fs_action_npc_wants.lua
Normal 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
@ -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
@ -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
@ -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
|
||||
)
|
@ -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
@ -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
@ -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
@ -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
@ -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()
|
||||
|
@ -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
|
||||
)
|
@ -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
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
2506
fs_edit_general.lua
@ -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
|
@ -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
|
624
fs_fashion.lua
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
1073
fs_talkdialog.lua
732
functions.lua
@ -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
@ -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
|
112
functions_save_restore_dialogs.lua
Normal 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
@ -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
102
init.lua
@ -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
@ -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
@ -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
|
||||
``
|
||||
|
@ -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
|
||||
|
92
mobs.lua
@ -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
|
3
mod.conf
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
})
|
@ -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
|
474
quest_api.lua
@ -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
@ -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
|
@ -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', {
|
||||
|
415
show_fs.lua
@ -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 "..
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 165 B |
Before Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 114 B |
Before Width: | Height: | Size: 139 B |
Before Width: | Height: | Size: 118 B |
Before Width: | Height: | Size: 118 B |
Before Width: | Height: | Size: 207 B |
Before Width: | Height: | Size: 191 B |