Introduction to Macro Writing With Javascript/ja

From RPTools Wiki
Revision as of 18:08, 18 February 2024 by Fourwoods (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Languages:  English  • Deutsch  • 日本語

BEGINNER
THIS IS A BEGINNER ARTICLE

はじめに

Since MapTool 1.10 there is a new JavaScript environment that allows the use of JavaScript to write macros. This is a tutorial for anybody that hasn't used Javascript in MapTool before. This tutorial assumes no prior knowledge of Javascript, programming or macro writing in general. For reasons why you would want to write a macro see the start of Introduction to Macro Writing

MapTool 1.10 より、新しい JavaScript 環境が導入され、JavaScript を使ってマクロを書くことができるようになった。これは MapTool で Javascript を使ったことがない人のための手引である。この手引では Javascript、プログラミング、マクロの書き方全般についての予備知識がないことを前提としている。マクロを書く魅力については はじめてのマクロ作成 の冒頭を参照のこと。

MapTool 環境

The first thing that any person wanting to write JavaScript for MapTool should know is that there are 2 different Javascript environments in MapTool. The first is for macros, related to the js.eval() family of functions, the other Javascript environment lives inside the frames and overlays as part of HTML. To avoid confusion the first JavaScript environment is referred to as "GraalVM JavaScript" and the second "Frame JavaScript". This tutorial focuses on GraalVM Javascript so assume any reference to only Javascript is a reference to GraalVM Javascript.

MapTool 用の JavaScript を書きたい人が最初に知っておくべきことは、MapTool には2つの異なる Javascript 環境があるということだ。1つはマクロ用のもので、一連の js.eval() 関数に関連しており、もう1つの Javascript 環境は HTML の一部としてフレームやオーバーレイの中に存在している。混乱を避けるために、前者の JavaScript 環境を『GraalVM JavaScript(グラールブイエム)』と呼び、後者を『フレームJavaScript』と呼ぶ事にする。この手引では、GraalVM Javascript に焦点を当てているため、Javascript とだけ言及している場合は GraalVM Javascript を指しているものとする。

マクロ環境の準備

Although you can just write Javascript inside a string to be evaluated by js.eval(). If you want to do anything slightly more complex than a single line this approach quickly becomes unwieldy, so setting up a area to develop complex macros is a good idea. For this tutorial, do this by following these steps;

Javascript を js.eval() で評価される文字列の中に記述するだけでも良い。1行よりも少し複雑なことをしたい場合、この方法はすぐに扱いにくくなるので、複雑なマクロを開発するための領域を設定する方が良いだろう。この手引では、これを以下の手順で行う;

  1. create a Library Token
  2. set allows URI acess to true
  3. create a macro with Javascript
  4. execute it.
  1. ライブラリートークンを作成する。
  2. URIでのアクセスを有効にする
  3. Javascript でマクロを作成する。
  4. 実行する。
If you are unsure of how to do any of these steps here is a verbose description of the procedure;

この手順のやり方がわからない場合は、以下に手順を逐次的にに説明する;

  1. pick a token and change its name to start with lib: like lib:javascript-tutorial
  2. 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
  3. to create a macro on the lib token first make sure you have a token related panel
    1. 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)
    2. to add a new macro, right click on the panel and select "Add New Macro" from the context menu
    3. to edit the macro, right click it and select "Edit.." from the context menu
    4. in the second tab you can change its name from (new)
  4. To execute the text inside a macro on a lib token create another macro and inside it call the js.evalURI() function
    1. to call the function write
      [r:js.evalURI("javascript-tutorial",lib URI)]
      1. 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
      2. the first argument is the namespace try to use a unique one or it may cause problems with other macros written in javascript
  1. トークンを選択し、lib:javascript-tutorial のように lib: で始まる名前に変更する。
  2. トークンの編集ウィンドウで『Lib:トークンのプロパティ』タブを開き、その何かある『URIによる接続を許可』のチェックボックスをクリックする。
  3. ライブラリートークン上にマクロを作成するため、トークン関連のパネルを表示させておく。
    1. トークン用パネルを表示させるために、メニューバーの『ウィンドウ』をクリックし、『発言対象トークン』または『選択トークン』のいずれかを選択する(『発言対象トークン』パネルを選択した場合は、ライブラリートークンを『発言対象に設定』しておく)。
    2. 新規マクロを追加するには、パネル上で右クリックし、右クリックメニューから『新規マクロを追加』を選択する。
    3. マクロを編集するには、そのマクロを右クリックし、右クリックメニューから『編集』を選択する。
    4. 2つ目のタブで、名前を (new) から別のものに変更することができる。
  4. ライブラリートークン上のマクロ内のテキストを実行するには、別のマクロを作成し、その中で js.evalURI() 関数を呼び出す.
    1. この関数を呼び出すには、
      [r:js.evalURI("javascript-tutorial",lib URI)]
      と書く。
      1. したがって、ライブラリートークンが lib:javascript-tutorial で、マクロ名が js の場合、それを参照するためのリンクは lib://javascript-tutorial/macro/js となる。
      2. 1つ目の引数は名前空間である。一意なものを使うようにしないと、javascript で書かれた他のマクロとの間で問題が発生する可能性がある。
As an alternative to using the built-in macro editor you can use a Javascript IDE or a text editor, they can be an immense help. If you are editing outside MapTool and then copy pasting your code, consider Creating A MapTool Addon.

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:MapTooljs:MapTooljs:MapTool.chatjs:MapTool.tokensjs: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

See also