From The Mana World
m (User:Kess/eAthena scripting moved to User:Kess/Scripting: I'm going to rework this)
m (→‎eAthena: Some quick thoughts)
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
This is an attempt to standardize the eAthena scripting. Any suggestions are welcome to be posted on the discussion page before editing.
In my mind a scripting language should be simple to both understand and use. You should not need to be a C++/Lua hacker to be able to create a dialogue menu where some replies depends on certain circumstances. Here follows a few suggestions how the tmwserv Lua scripting could look.


==Conventions==
== Example script ==
In current eAthena and tmwserv script as well as a proposal of a more scripter comfortable tmwserv syntax.


===Typographical style===
=== eAthena ===
Typographical conventions vary depending on situation. The following are recommendations for an international English on the eAthena client (which does not support Unicode at this writing).


'''Punctuation'''
  // Example of NPC script
* Use only one space ( ) after the punctuation ending a sentence.
* Use three dots (...) for an ellipsis, which marks something cut off.
  012-3.gat,43,21,0 script NPC Name 123,{
* Use two dashes (--) for wider dashes, which are used in several constructions, among them denoting ranges and offsetting parts of a text.
  mes "[NPC Name]";
** Please add spaces around the dashes when they are used parenthetically.
  mes "\"Yah! This is my first spoken line. What do you want?\"";
 
  // Punctuation example
mes "[Luca the Hunter]";
mes "\"Luckily I had a camera with me! Here's a picture of it<span style="color:green;">...</span>\"";
mes "A liquid<span style="color:green;"> -- </span>smelling strangely of fermented apples<span style="color:green;"> -- </span>is applied.";
 
'''Quotes'''
* Use \" for direct speech.
** For the double quotation mark not to interfere with the scripting code, a backslash needs to be prepended it.
* Contractions and quotes within the direct speech use the single quote '.
 
  // Quote example
mes "<span style="color:green;">\"</span>Ah, Agostine! The <span style="color:green;">'legendary tailor'</span>!<span style="color:green;">\"</span>";
mes "<span style="color:green;">\"</span>Aw, you <span style="color:green;">don't</span> have enough gold on you!<span style="color:green;">\"</span>";
 
'''Formatting text'''
* Item and monster names, please use the names in the way shown by the client (e.g., Black Scorpion, Bottle of Sand) outside direct speech in the dialogues.
* Emphasis may be formatted with capital letters, use it sparingly.
* Actions may be put inside asterisks, use it sparingly.
 
// Formatting example
  mes "\"I was fighting <span style="color:green;">scorpions</span> for experience and I bumped into a <span style="color:green;">RED</span> one.\"";
  mes "\"I fought, of course! It hit me here <span style="color:green;">*points at a bruise at his shoulder*</span>.\"";
 
===Coding conventions===
'''Comments'''
* Each file should begin with a comment block describing the scope and use of the file.
** <span style="color:red;">Put a template here?</span>
 
'''Indentation'''
* All indentations are made with tabs, one for each indentation level.
* Labels use no indentations at all.
* Menus have all their options on their own lines, and indented one level.
* <span style="color:red;">If clauses...</span>
 
// Indentation example
L_label:
mes "What do you want to do?";
  next;
  next;
L_main_menu:
  menu
  menu
  "Spend money", -,
  "A fancier menu.", L_fancy_menu,
  "Script a fun quest", L_good_choice;
"An even fancier menu.", L_fancier_menu,
"Do stuff!", L_change_things,
  "Nothing.", -;
mes "[NPC Name]";
mes "\"That means the end. Bye!\"";
  close;
  close;
L_fancy_menu:
if (!(UNSET_GLOBAL_FLAGS & PARTICULAR_ONE) && !(UNSET_GLOBAL_FLAGS & PARTICULAR_TWO))
menu
"Back to main menu.", L_main_menu,
"Close.", -;
if ((UNSET_GLOBAL_FLAGS & PARTICULAR_ONE) && !(UNSET_GLOBAL_FLAGS & PARTICULAR_TWO))
menu
"Back to main menu.", L_main_menu,
"Particular option one.", L_particular_option_one,
"Close.", -;
if (!(UNSET_GLOBAL_FLAGS & PARTICULAR_ONE) && (UNSET_GLOBAL_FLAGS & PARTICULAR_TWO))
menu
"Back to main menu.", L_main_menu,
"Particular option two.", L_particular_option_two,
"Close.", -;
if ((UNSET_GLOBAL_FLAGS & PARTICULAR_ONE) && (UNSET_GLOBAL_FLAGS & PARTICULAR_TWO))
menu
"Back to main menu.", L_main_menu,
"Particular option one.", L_particular_option_one,
"Particular option two.", L_particular_option_two,
"Close.", -;
close;
L_fancier_menu:
// Credit goes to fate for this (relatively) handy one
set @CHOICE_MAIN_MENU,      0;
set @CHOICE_PARTICULAR_ONE, 1;
set @CHOICE_PARTICULAR_TWO, 2;
set @CHOICE_CLOSE,          9;
setarray @menu_options$, "", "", "", "";
set @menu_option, 0;
set @menu_options$[@menu_option], "Back to main menu.";
set @menu_choice[@menu_option], @CHOICE_MAIN_MENU;
set @menu_option, @menu_option + 1;
if (UNSET_GLOBAL_FLAGS & PARTICULAR_ONE)
goto L_fancier_menu_one;
goto L_fancier_menu_end;
L_fancier_menu_one:
set @menu_options$[@menu_option], "Particular option one.";
set @menu_choice[@menu_option], @CHOICE_PARTICULAR_ONE;
set @menu_option, @menu_option + 1;
if (UNSET_GLOBAL_FLAGS & PARTICULAR_TWO)
goto L_fancier_menu_two;
goto L_fancier_menu_end;
L_fancier_menu_two:
set @menu_options$[@menu_option], "Particular option two.";
set @menu_choice[@menu_option], @CHOICE_PARTICULAR_TWO;
set @menu_option, @menu_option + 1;
L_fancier_menu_end:
set @menu_options$[@menu_option], "Close.";
set @menu_choice[@menu_option], @CHOICE_CLOSE;
menu
@menu_options$[0], -,
@menu_options$[1], -,
@menu_options$[2], -,
@menu_options$[3], -;
set @menu, @menu - 1;
if (@menu_choice[@menu] == @CHOICE_MAIN_MENU) goto L_main_menu;
if (@menu_choice[@menu] == @CHOICE_PARTICULAR_ONE) goto L_particular_option_one;
if (@menu_choice[@menu] == @CHOICE_PARTICULAR_TWO) goto L_particular_option_two;
close;
L_change_things:
heal 500, 100;
if (countitem("Iten") < 2) goto L_not_enough_items;
if (zeny < 12) goto L_not_enough_money;
delitem "Iten", 2;
getitem "Gr8t Iten", 1;
set zeny, zeny - 12;
getexp 42, 0;
mes "[42 experience points]";
close;
}


'''Various'''
A few good things:
* Script files should end with a newline (a empty line).
* Relatively straight-forward language
 
** Including label <tt>goto</tt>s
 
'''Code blocks'''
* Code blocks should be separated from each other in a dinstinct way; the best way of doing it is to insert a blank line between code blocks.
* Do not add blank lines within individual blocks.
* The opening brackets should be at the end of the parent line, not in a new line; the closing ones should be on a line of their own.
 
* In creating an NPC, remember to follow the rules stated in the [[NPC development]] section.
 
=== Example ===
An example is given by a quest to get a key for a chest:
 
{| border=0
|
<pre>
// A treasure chest. You need three keys to open it.
new_5-1.gat,93,37,0    script  Treasure        111,{
mes "Would you try to open it?";
next;
menu
"Yup", L_Sure,
"Nope", close;
 
L_Sure:
if (countitem(537) < 3) goto L_WrongKey;
delitem 537, 3;
getitem 536, 1;
mes "You opened it and found a short sword!";
close;
 
L_WrongKey:
mes "It seems that this is not the right key...";
close;
}
</pre>
|}
 
The above script starts with describing the NPC (the chest, NPC sprite 111) and its location. Then follows what happens on activation. A message is shown, then an option dialog is displayed. Then, when the player has less than 3 keys (item id 537), he is told not to have the right key. If he does, the player looses three keys and receives a short sword (item id 536). This quest in particular is questionable as the explanation given to the player doesn't match the implemented behavior (possibly to make the quest more challenging), but I've put it here because it is short and shows one basic way to use the scripting system.
 
For more examples of the current system, check out the current scripts in use by the server. Beware though, this could affect your enjoyment of the game as it does spoil some of the mystery. Here's a link to SVN so you can view them in your browser:
 
http://themanaworld.svn.sourceforge.net/viewvc/themanaworld/server-data/trunk/npc/
 
Note that anything said by an NPC should be put in double quotes ("). You can do that like this: "'''\"'''Hello!'''\"''' she said."
 
==Map objects==
{|border="0" cellspacing="0" cellpadding="2" style="float: right;"
!colspan="2"| Key
|-
| <TAB>
| a tab
|-
| text
| static text, do not change
|-
| '''text'''
| obligatory value, change this
|-
| '''''text'''''
| optional value, may be skipped
|}
 
These sections describe how to define map objects.
 
=== Warps ===
Warps are what move players between maps. They can also be used to move players around a single map, if needed. Warps are defined like this:
 
'''map1''','''startX''','''startY'''<TAB>warp<TAB>'''name'''<TAB>'''width''','''height''','''map2''','''endX''','''endY'''
 
Where
* '''map1''' is the map which the player will warp from
* '''startX''' is the x-coordinate of the tile the player will warp from
* '''startY''' is the y-coordinate of the tile the player will warp from
* '''name''' is the name of the warp, unused but must be defined
* '''width''' is the width of the warp
* '''height''' is the height of the warp
* '''map2''' is the map which the player will warp to
* '''endX''' is the x-coordinate of the tile the player will end up on
* '''endY''' is the y-coordinate of the tile the player will end up on
 
Width and height are described in detail in [[EAthena Scripting Standards/Warp Details|Warp Details]].
 
<pre>// Warp example (npc/new_10-1-xmas/passages.txt)
 
new_10-1.gat,69,19 warp tovillage 4,1,new_11-1.gat,69,127</pre>
 
=== Monsters ===
Monsters are defined like this:
 
'''map''','''x''','''y''','''width''','''height'''<TAB>monster<TAB>'''name'''<TAB>'''mobID''','''count''','''spawn1''','''spawn2''','''event'''
 
Where
 
* '''map''' is the map the monsters should appear on
* '''x''' is the x-coordinate of the spawn tile
* '''y''' is the y-coordinate of the spawn tile
* '''width''' is the tile width of the spawn area
* '''height''' is the tile height of the spawn area
* '''name''' is the name of the mob, unused but must be defined
* '''mobID''' is the mob identifier of the desired monster (in the monster database [http://themanaworld.svn.sourceforge.net/viewvc/themanaworld/server-data/trunk/db/mob_db.txt?view=markup])
* '''count''' is the number to spawn
* '''spawn1''' is the minimum delay between successive spawns (per individual)
* '''spawn2''' is the minimum delay between death and respawn (per individual)
* '''event''' is the script event to fire upon death
 
A detailed description of position and area can be found in [[EAthena Scripting Standards/Mob Details|Mob Details]].
 
<pre>// Monster example (npc/new_9-1-woodland/monsters.txt)


new_9-1.gat,0,0,0,0 monster EvilMushroom 1013,25,0,0,0
A few things that could have been easier:
new_9-1.gat,0,0,0,0 monster SleepFlower 1014,40,0,0,0
* Contextual <tt>menu</tt>s
new_9-1.gat,0,0,0,0 monster Alzarin 1032,1,2700000,1800000,0</pre>
* Client side:
** Experience and gold should be announced (per client settings) just like recieved items, such messages should not need to be entered in the script itself
** NPC names should be displayed by the client, not hard coded in place in the script itself


=== NPCs ===
=== tmwserv (currently) ===
NPCs are defined like


'''map''','''x''','''y''','''direction'''<TAB>script<TAB>'''name'''<TAB>'''npcID''','''''area1''''','''''area2''''',{
-- TODO


:'''''script'''''
A few good things:
}
* ...


Where
A few things that could be made easier:
* More straight-forward language
** Especially with regards to function naming and scope


* '''map''' is the map the NPC is located in
=== tmwserv (proposal) ===
* '''x''' is the x-coordinate of the NPC
* '''y''' is the y-coordinate of the NPC
* '''direction''' is the direction the NPC faces, unused but must be defined
* '''name''' is the name of the NPC, to hide (the latter part of) a name to the client begin (that part of) the name with a hash (#)
* '''npcID''' is the NPC identifier (in the NPC database [http://themanaworld.svn.sourceforge.net/viewvc/themanaworld/tmwdata/trunk/npcs.xml?view=markup])
* '''''area1''''' and '''''area2''''' describe the map area which activates the NPC, optional
* '''''script''''' is the NPC script, see the below


The name of the NPC can be hidden to the player, part of the name or the whole. Begin the name with a hash (#) to hide the whole name, or add it before the part which should be truncated (it will be truncated to the end).
-- TODO


<pre>// NPC example with activation area (npc/new_37-1-woodland-mine/miners.txt)


new_37-1.gat,78,59,0 script Miner 109,1,1,{
<!--
mes "[Miner]";
=== NPC dialogue ===
mes "\"I'm sorry, but this area is closed off.\"";
Currently on eAthena:
}</pre>


=== Shops ===
// A comment!
Shops are defined like
mes "[Maji Chan]";
mes "\"How do you do my dear quester?\"";
next;


'''map''','''x''','''y''','''direction'''<TAB>shop<TAB>'''name'''<TAB>'''npcID''','''inventory'''
Currently on tmwserv:


Where
do_message(npc, char, "“How do you do my dear quester?”")


* '''map''' is the map the shop is located in
Which is quite unwieldy. When scripting you most often only have the NPC say something to the player, so this could be simplified a fair bit to something like:
* '''x''' is the x-coordinate of the shop
* '''y''' is the y-coordinate of the shop
* '''direction''' is the direction the shop faces, unused but must be defined
* '''name''' is the name of the shop, to hide (the latter part of) a name to the client begin (that part of) the name with a hash (#)
* '''npcID''' is the NPC identifier for the shop
* '''inventory''' is a comma separated inventory list of items to sale


The inventory is defined like
message("“How do you do my dear quester?”")


'''itemID''':'''price'''[,'''itemID''':'''price''']*
I would actually propose to <tt>gettext</tt>-ize it:


Where [...]* means that part can be repeated as necessary, and
message(_("“How do you do my dear quester?”"))


* '''itemID''' is the item identifier (in the item database [http://themanaworld.svn.sourceforge.net/viewvc/themanaworld/server-data/trunk/db/item_db.txt?view=markup])
==== Using variables ====
* '''price''' is the price the item is sold for
Currently on eAthena:


You add an item:price pair for any item that should be sold by the shop.
set @variable_1 = 42; // numeral variable for the local script
set @variable_2$ = "Bottle of Sand"; // string variable for the local script
mes "\"Did you bring " + @variable_1 + " [" + @variable_2$ + "]?\"";


<pre>// Shop example (npc/new_23-1-dimonds-cove/dimonds.txt)
-->


new_23-1.gat,24,27,0 shop Bartender 107,539:175,567:500,568:500</pre>
== See also ==
tmwserv:
* [[Scripting|Script bindings]] — things we currently can access through scripts
* [[User:Crush/Scripting|Script bindings to do]] — things we should be able to access through scripts... in time
* <tt>[http://gitorious.org/tmwserv-data/mainline/blobs/raw/master/scripts/test.lua <tmwserv-data>/scripts/test.lua]</tt> — current script example


== Script Functions ==
eAthena:
* [[eAthena Scripting Standards|eAthena scripting standards]]

Latest revision as of 09:55, 28 September 2009

In my mind a scripting language should be simple to both understand and use. You should not need to be a C++/Lua hacker to be able to create a dialogue menu where some replies depends on certain circumstances. Here follows a few suggestions how the tmwserv Lua scripting could look.

Example script

In current eAthena and tmwserv script as well as a proposal of a more scripter comfortable tmwserv syntax.

eAthena

// Example of NPC script

012-3.gat,43,21,0	script	NPC Name	123,{
	mes "[NPC Name]";
	mes "\"Yah! This is my first spoken line. What do you want?\"";
	next;

L_main_menu:
	menu
		"A fancier menu.", L_fancy_menu,
		"An even fancier menu.", L_fancier_menu,
		"Do stuff!", L_change_things,
		"Nothing.", -;

	mes "[NPC Name]";
	mes "\"That means the end. Bye!\"";
	close;

L_fancy_menu:
	if (!(UNSET_GLOBAL_FLAGS & PARTICULAR_ONE) && !(UNSET_GLOBAL_FLAGS & PARTICULAR_TWO))
		menu
			"Back to main menu.", L_main_menu,
			"Close.", -;

	if ((UNSET_GLOBAL_FLAGS & PARTICULAR_ONE) && !(UNSET_GLOBAL_FLAGS & PARTICULAR_TWO))
		menu
			"Back to main menu.", L_main_menu,
			"Particular option one.", L_particular_option_one,
			"Close.", -;

	if (!(UNSET_GLOBAL_FLAGS & PARTICULAR_ONE) && (UNSET_GLOBAL_FLAGS & PARTICULAR_TWO))
		menu
			"Back to main menu.", L_main_menu,
			"Particular option two.", L_particular_option_two,
			"Close.", -;

	if ((UNSET_GLOBAL_FLAGS & PARTICULAR_ONE) && (UNSET_GLOBAL_FLAGS & PARTICULAR_TWO))
		menu
			"Back to main menu.", L_main_menu,
			"Particular option one.", L_particular_option_one,
			"Particular option two.", L_particular_option_two,
			"Close.", -;

	close;

L_fancier_menu:
	// Credit goes to fate for this (relatively) handy one
	set @CHOICE_MAIN_MENU,      0;
	set @CHOICE_PARTICULAR_ONE, 1;
	set @CHOICE_PARTICULAR_TWO, 2;
	set @CHOICE_CLOSE,          9;

	setarray @menu_options$, "", "", "", "";
	set @menu_option, 0;

	set @menu_options$[@menu_option], "Back to main menu.";
	set @menu_choice[@menu_option], @CHOICE_MAIN_MENU;
	set @menu_option, @menu_option + 1;

	if (UNSET_GLOBAL_FLAGS & PARTICULAR_ONE)
		goto L_fancier_menu_one;
	goto L_fancier_menu_end;

L_fancier_menu_one:
	set @menu_options$[@menu_option], "Particular option one.";
	set @menu_choice[@menu_option], @CHOICE_PARTICULAR_ONE;
	set @menu_option, @menu_option + 1;

	if (UNSET_GLOBAL_FLAGS & PARTICULAR_TWO)
		goto L_fancier_menu_two;
	goto L_fancier_menu_end;

L_fancier_menu_two:
	set @menu_options$[@menu_option], "Particular option two.";
	set @menu_choice[@menu_option], @CHOICE_PARTICULAR_TWO;
	set @menu_option, @menu_option + 1;

L_fancier_menu_end:
	set @menu_options$[@menu_option], "Close.";
	set @menu_choice[@menu_option], @CHOICE_CLOSE;

	menu
		@menu_options$[0], -,
		@menu_options$[1], -,
		@menu_options$[2], -,
		@menu_options$[3], -;

	set @menu, @menu - 1;

	if (@menu_choice[@menu] == @CHOICE_MAIN_MENU) goto L_main_menu;
	if (@menu_choice[@menu] == @CHOICE_PARTICULAR_ONE) goto L_particular_option_one;
	if (@menu_choice[@menu] == @CHOICE_PARTICULAR_TWO) goto L_particular_option_two;

	close;

L_change_things:
	heal 500, 100;

	if (countitem("Iten") < 2) goto L_not_enough_items;
	if (zeny < 12) goto L_not_enough_money;
	delitem "Iten", 2;
	getitem "Gr8t Iten", 1;
	set zeny, zeny - 12;
	getexp 42, 0;
	mes "[42 experience points]";

	close;

}

A few good things:

  • Relatively straight-forward language
    • Including label gotos

A few things that could have been easier:

  • Contextual menus
  • Client side:
    • Experience and gold should be announced (per client settings) just like recieved items, such messages should not need to be entered in the script itself
    • NPC names should be displayed by the client, not hard coded in place in the script itself

tmwserv (currently)

-- TODO

A few good things:

  • ...

A few things that could be made easier:

  • More straight-forward language
    • Especially with regards to function naming and scope

tmwserv (proposal)

-- TODO


See also

tmwserv:

eAthena: