Introduction to Macro Writing With Javascript/ja: Difference between revisions
No edit summary |
No edit summary |
(No difference)
|
Latest revision as of 23:59, 18 February 2024
BEGINNER
THIS IS A BEGINNER ARTICLE
はじめに
MapTool 1.10 より、新しい JavaScript 環境が導入され、JavaScript を使ってマクロを書くことができるようになった。これは MapTool で Javascript を使ったことがない人のための手引である。この手引では Javascript、プログラミング、マクロの書き方全般についての予備知識がないことを前提としている。マクロを書く魅力については はじめてのマクロ作成 の冒頭を参照のこと。
MapTool 環境
MapTool 用の JavaScript を書きたい人が最初に知っておくべきことは、MapTool には2つの異なる Javascript 環境があるということだ。1つはマクロ用のもので、一連の js.eval() 関数に関連しており、もう1つの Javascript 環境は HTML の一部としてフレームやオーバーレイの中に存在している。混乱を避けるために、前者の JavaScript 環境を『GraalVM JavaScript(グラールブイエム)』と呼び、後者を『フレームJavaScript』と呼ぶ事にする。この手引では、GraalVM Javascript に焦点を当てているため、Javascript とだけ言及している場合は GraalVM Javascript を指しているものとする。
マクロ環境の準備
Javascript を js.eval() で評価される文字列の中に記述するだけでも良い。1行よりも少し複雑なことをしたい場合、この方法はすぐに扱いにくくなるので、複雑なマクロを開発するための領域を設定する方が良いだろう。この手引では、これを以下の手順で行う;
- create a Library Token
- set allows URI acess to true
- create a macro with Javascript
- execute it.
- ライブラリートークンを作成する。
- URIでのアクセスを有効にする
- Javascript でマクロを作成する。
- 実行する。
この手順のやり方がわからない場合は、以下に手順を逐次的にに説明する;
- pick a token and change its name to start with lib: like lib:javascript-tutorial
- still inside the edit token window go on the "Lib:Token properties" tab and click the checkbox so that allows URI access has a checkmark besides it
- to create a macro on the lib token first make sure you have a token related panel
- to show a token panel on the top of the window on the menu bar click on window and then select either impersonate or selected (if you selected the impersonate panel make sure to impersonate the library token)
- to add a new macro, right click on the panel and select "Add New Macro" from the context menu
- to edit the macro, right click it and select "Edit.." from the context menu
- in the second tab you can change its name from (new)
- To execute the text inside a macro on a lib token create another macro and inside it call the js.evalURI() function
- to call the function write
[r:js.evalURI("javascript-tutorial",lib URI)]
- a lib URI to a macro is a link of the form lib://libraryTokenName/macro/macroName so if you lib token is lib:javascript-tutorial and your macro is js then to refer to it the link is lib://javascript-tutorial/macro/js
- the first argument is the namespace try to use a unique one or it may cause problems with other macros written in javascript
- to call the function write
- トークンを選択し、lib:javascript-tutorial のように lib: で始まる名前に変更する。
- トークンの編集ウィンドウで『Lib:トークンのプロパティ』タブを開き、その何かある『URIによる接続を許可』のチェックボックスをクリックする。
- ライブラリートークン上にマクロを作成するため、トークン関連のパネルを表示させておく。
- トークン用パネルを表示させるために、メニューバーの『ウィンドウ』をクリックし、『発言対象トークン』または『選択トークン』のいずれかを選択する(『発言対象トークン』パネルを選択した場合は、ライブラリートークンを『発言対象に設定』しておく)。
- 新規マクロを追加するには、パネル上で右クリックし、右クリックメニューから『新規マクロを追加』を選択する。
- マクロを編集するには、そのマクロを右クリックし、右クリックメニューから『編集』を選択する。
- 2つ目のタブで、名前を (new) から別のものに変更することができる。
- ライブラリートークン上のマクロ内のテキストを実行するには、別のマクロを作成し、その中で js.evalURI() 関数を呼び出す.
- この関数を呼び出すには、と書く。
[r:js.evalURI("javascript-tutorial",lib URI)]
- したがって、ライブラリートークンが lib:javascript-tutorial で、マクロ名が js の場合、それを参照するためのリンクは lib://javascript-tutorial/macro/js となる。
- 1つ目の引数は名前空間である。一意なものを使うようにしないと、javascript で書かれた他のマクロとの間で問題が発生する可能性がある。
- この関数を呼び出すには、
MapTool 内蔵のマクロエディターを使う代わりに、Javascript IDE やテキストエディターを使うことができ、これは大いに役立つだろう。MapTool の外で編集し、コードをコピー/貼り付けする場合は、Creating A MapTool Addon を検討すると良いだろう。
最初の Javascript プログラム
This is the part where you start to learn Javascript, if you already know Javascript you can read the following articles to get started js:MapTool , js:MapTool.chat ,js:MapTool.tokens , js:MapTool.clientInfo and last but most important js:MTScript. Javascript is a multi-paradigm programming language the exact meaning of this isn't important what is important is that we can treat it as a imperative programming language. An imperative program is just a sequence of commands. Insert the following in your macro holding javascript.
これはJavascriptを学び始める部分である。もし既に Javascript を知っているならば、以下の記事を読んで始めることが出来る。js:MapTool、js:MapTool、js:MapTool.chat、js:MapTool.tokens、js:MapTool.clientInfo、そして最後に最も重要なのは js:MTScript だ。 Javascript はマルチパラダイムプログラミング言語であるが、その正確な意味は重要ではなく、重要なのは、Javascript を命令型プログラミング言語として扱えるということである。命令型プログラミングとは、単なるコマンドの羅列である。 Javascript を格納するマクロに以下のコードを挿入する。
"use strict";
try {
let message = "hello world";
MapTool.chat.broadcast(message);
} catch(e) {
MapTool.chat.broadcast(""+e+"\n"+e.stack);
}
if you inserted the text correctly, when executed hello world appeared in the chat, if you got something else check the text carefully, code is very fragile and a single misplaced { or } could break it.
this snippet contains a lot of things which need to be explained, most of them will be explained now but the try and the catch will be explained next section
もしテキストが正しく挿入されていれば、実行されたときに hello world がチャットに表示される。もし他のものが表示されたら、テキストを注意深くチェックしよう。コードは非常に壊れやすく、たった1文字の誤字が全体を壊してしまう事になりかねない。
このコード断片には説明が必要なものがたくさん含まれている。その大半については今から説明するが、try と catch については次の節で説明する。
Javascript Statements
Firstly look at the end of each line besides the second and the and fifth one, notice that there is a semicolon. The semicolon denotes the end of a statement. In this instance is optional as each statement is in a separate line but it is good practise to include it. If you don't an automated program inserts it automatically and it may not get it right, which you certainly don't want. a statement is basically a command, if you rolled on MapTool before you probably used a command, this is the same thing, a statement does something.
Javascript Strict Mode
on the first line there is a statement which causes Javascript to enter strict mode, on strict mode Javascript turns things which are probably mistakes into errors which if you are inside a try catch block will give what is wrong and where, the full list of what happens can be found here but just to talk about a example say you mistyped something Javascript normally won't complain and continue using a value called undefined, creating subtle bugs, compare that to getting "on line x column y you asked for a value which doesn't exist."
Javascript variables
On the third line we have a statement containing let message = "hello world";
what this does is create a variable. A variable stores a changeable value so instead of using "hello world" we can use message, you can declare a variable with var, let or const. A variable name must start with a letter or underline and may contain numbers, letters and underline, a variable name can't be any the Javascript keywords like let, const, var and more.
In the last paragraph it was mentioned that a variable can hold a value, this value can be one of the basic types in Javascript they are
- string: a string is a fragment of text enclosed in either quotes or double quotes, a string enclosed with backticks is called a [template literal] which is special.
- number: a number is a number they can be either integer like -1, 3 or it can be fractional number like 0.2, 0.1 3.5 they are called floating point numbers do note that because of their representation in the computer using floats does have some imprecision 0.1 + 0.2 isn't equal to 0.3 but rather 0.30000000000000004. this also happens for really big integers as they are also floating point numbers
- objects: a object is a group of properties with associated values, they are created with curly braces enclosing the properties and pairs of propertyName:propertyValue like this
{property1:value1,property2:2}
, only strings, a special type called Symbol and numbers can be properties but anything can be a value in an object,this includes other objects and functions. To access a object properties you use ["property"] or .property after the object, there is a lot more to know about objects but for now this is enough. - arrays: a array is a collection of values, the literal form of a array is braces with values like this
[value1,value2,etc]
there are special functions to working with arrays like objects there is a lot more to them. - functions: they will be talked about more later but note that a function can be assigned to a variable and later called by that name.
Javascript Functions
On the fourth line there is MapTool.chat.broadcast(message);
which is a function call. A function is basically a group of statements which can optionally receive arguments. when you call a function you supply the arguments and it performs the statements it contains. In this case we are calling the broadcast function of the chat object which is contained in the MapTool object. This function calls java code to output a string to chat, nb: if you give as argument something other than a string like a number or a object you will get an error. To declare a function use
function functionName (argument1,argument2,etc){
insert your statements here;
}
Javascript Control Structures
You may have noticed that with the tools we have currently it isn't possible to build anything complex. For instance: What if we want check if a roll is higher than the targets DC and if so roll an attack? What if we want to deal damage to all characters in the map? Even worse, what if we make an error like dividing by 0? Control structures solve this problem, see the following example of implementing a fireball spell for d&d
"use strict";
function roll(dice,sides){
let string = MTScript.execMacro(`[r:roll(${dice},${sides})]`);
return Number(string);
}
var checkNumber = (value => Number.isNaN(value)||value == undefined);
function makeSave(token,spellLevel,spellDC){
const dex = Number(token.getProperty("dexterity"));
let message = token.getName()+":"
let dexModifier = (dex-10)/2;
if(checkNumber(dexModifier)){
dexModifier = 0;
}
const result = dexModifier + roll(1,20);
let previousLife = Number(token.getProperty("HP"));
if(checkNumber(previousLife)){
previousLife = 0;
}
let newLife;
const damageRoll = (roll(8,6)+roll(spellLevel-4,6))
if(result >=spellDC){
message = message +"succeded on its dex save with "+result;
newLife = Math.ceil(previousLife-damageRoll/2);
}
else{
message = message + "failed on its dex save with "+result;
newLife = previousLife -damageRoll;
}
token.setProperty("HP",""+newLife);
MapTool.chat.broadcast(message + " and is now at "+newLife+"HP");
return token;
}
function getInput(names){
let commandStart = "[h:input(";
let commandEnd = `[h:value = "[]"]`;
const argNum = names.length -1;
for(let i = 0; i<argNum;i++){
commandStart = commandStart +`"${names[i]}||${names[i]}|TEXT|",`
commandEnd = commandEnd + `\n[h:value = json.append(value,${names[i]})]`
}
commandStart = commandStart +`"${names[argNum]}||${names[argNum]}|TEXT|")]`;
commandEnd = commandEnd + `\n[r:json.append(value,${names[argNum]})]`;
return JSON.parse(MTScript.execMacro(commandStart+commandEnd));
}
try{
const tokens = MapTool.tokens.getMapTokens();
const [spellLevel,spellDC] = getInput(["spellLevel","spellDC"]);
for(const token of tokens){
makeSave(token,spellLevel,spellDC);
}
}
catch(e){
MapTool.chat.broadcast(""+e+"\n"+e.stack);
}
Javascript try and catch
Like the previous example this code uses a try catch block. In a try block if any part of the code throws a exception then the catch block is executed. The variable inside parenthesis near the catch keyword represents the error. In MapTool and most browsers Error objects contain a [stack] property that indicates from where it was thrown. When programming in Javascript try to always have at least a try catch block so you get useful errors. If you don't then you will get a generic there is something wrong in this code.
Javascript If
In the makeSave function we have various ifs keywords, when the result inside the parenthesis of the if is truthy it executes the code otherwise it executes the else if present. A truthy result, is either true, a number other than 0, a non empty string or any object including a empty object. A falsy result is the empty string, 0, null and undefined. related to the if keyword there are comparison operators == checks for equality but converts types 0 == "0" evaluates to true. to truly check for equality use the === operator the opposite is the not equal operator != like the basic equality operator it converts types so 0 != "0" is false to check for inequality without conversion use !== there also are comparison operators for numbers like greater than >, greater or equal than >= and others.
Javascript For
On the getInput function there is a use of a for keyword, in the basic for block you define a variable, declare an exit condition, and finally declare a statement to be executed at the end of the loop, it will execute the code block inside the for loop until the end condition is reached. Besides that there is a for of and a for in block. The difference is that the for of loops over the elements of an iterable meaning array like objects, and the for in loops over enumerable properties meaning visible properties of a object.
Complete Explanation of Code Example
This code block:
- defines some functions, then
- in a try block gets all tokens on the map from MapTool.token.getMapTokens,
- information of the cast fireball from the get input function
- it then loops over all tokens with the information of the fireball spell.
Roll and Check Number Function
The roll function calls some MapTool Script to roll dice, via the MTScript.execMacro function. The checkNumber function checks if the entered number is either undefined or the special number NaN, it uses the "or" || operator there also is the "and" && logical operator. The check number function is a example of a arrow function, an alternative syntax for defining functions, as the checkNumber function is only a single statement it does not require curly braces due to the arrow notation.
MakeSave Function
The makeSave function implements the logic of a fireball spell, it shows an example of building a message by parts to later use in a call to MapTool.chat.broadcast.
GetInput Function
The getInput function builds a complex macro expression to call the input function at the end, it uses a for loop to do so, this get input function could be reused for any number of inputs, it also is an example of using the JSON builtin object to transform a textual json object into a javascript object.
Javascript Macro Writing Tips
Always Use Try Catch Blocks
if you don't you will get cryptic error messages that give no information
Calling A Previously Defined Function In A Namespace
When you define a global variable it lives in the Javascript namespace until the campaign is reloaded. you can just add MTScript.registerMacro(macroName, function)
to define a UDF named js.macroName that calls the function, note however that as of 1.11.5 you can't access json objects received from Maptool