1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "common/system.h"
24 #include "common/tokenizer.h"
25 
26 #include "gui/message.h"
27 
28 #include "graphics/macgui/macwindowmanager.h"
29 #include "graphics/macgui/macmenu.h"
30 
31 #include "director/director.h"
32 #include "director/cast.h"
33 #include "director/castmember.h"
34 #include "director/frame.h"
35 #include "director/movie.h"
36 #include "director/score.h"
37 #include "director/sound.h"
38 #include "director/sprite.h"
39 #include "director/cursor.h"
40 #include "director/channel.h"
41 #include "director/window.h"
42 #include "director/stxt.h"
43 #include "director/util.h"
44 #include "director/lingo/lingo.h"
45 #include "director/lingo/lingo-builtins.h"
46 #include "director/lingo/lingo-code.h"
47 #include "director/lingo/lingo-codegen.h"
48 #include "director/lingo/lingo-object.h"
49 #include "director/lingo/lingo-utils.h"
50 
51 namespace Director {
52 
53 static BuiltinProto builtins[] = {
54 	// Math
55 	{ "abs",			LB::b_abs,			1, 1, 200, FBLTIN },	// D2 function
56 	{ "atan",			LB::b_atan,			1, 1, 400, FBLTIN },	//			D4 f
57 	{ "cos",			LB::b_cos,			1, 1, 400, FBLTIN },	//			D4 f
58 	{ "exp",			LB::b_exp,			1, 1, 400, FBLTIN },	//			D4 f
59 	{ "float",			LB::b_float,		1, 1, 400, FBLTIN },	//			D4 f
60 	{ "integer",		LB::b_integer,		1, 1, 300, FBLTIN },	//		D3 f
61 	{ "log",			LB::b_log,			1, 1, 400, FBLTIN },	//			D4 f
62 	{ "pi",				LB::b_pi,			0, 0, 400, FBLTIN },	//			D4 f
63 	{ "power",			LB::b_power,		2, 2, 400, FBLTIN },	//			D4 f
64 	{ "random",			LB::b_random,		1, 1, 200, FBLTIN },	// D2 f
65 	{ "sin",			LB::b_sin,			1, 1, 400, FBLTIN },	//			D4 f
66 	{ "sqrt",			LB::b_sqrt,			1, 1, 200, FBLTIN },	// D2 f
67 	{ "tan",			LB::b_tan,			1, 1, 400, FBLTIN },	//			D4 f
68 	// String
69 	{ "chars",			LB::b_chars,		3, 3, 200, FBLTIN },	// D2 f
70 	{ "charToNum",		LB::b_charToNum,	1, 1, 200, FBLTIN },	// D2 f
71 	{ "length",			LB::b_length,		1, 1, 200, FBLTIN },	// D2 f
72 	{ "numToChar",		LB::b_numToChar,	1, 1, 200, FBLTIN },	// D2 f
73 	{ "offset",			LB::b_offset,		2, 3, 200, FBLTIN },	// D2 f
74 	{ "string",			LB::b_string,		1, 1, 200, FBLTIN },	// D2 f
75 	{ "value",		 	LB::b_value,		1, 1, 200, FBLTIN },	// D2 f
76 	// Lists
77 	{ "add",			LB::b_add,			2, 2, 400, HBLTIN },	//			D4 handler
78 	{ "addAt",			LB::b_addAt,		3, 3, 400, HBLTIN },	//			D4 h
79 	{ "addProp",		LB::b_addProp,		3, 3, 400, HBLTIN },	//			D4 h
80 	{ "append",			LB::b_append,		2, 2, 400, HBLTIN },	//			D4 h
81 	{ "count",			LB::b_count,		1, 1, 400, FBLTIN },	//			D4 f
82 	{ "deleteAt",		LB::b_deleteAt,		2, 2, 400, HBLTIN },	//			D4 h
83 	{ "deleteProp",		LB::b_deleteProp,	2, 2, 400, HBLTIN },	//			D4 h
84 	{ "findPos",		LB::b_findPos,		2, 2, 400, FBLTIN },	//			D4 f
85 	{ "findPosNear",	LB::b_findPosNear,	2, 2, 400, FBLTIN },	//			D4 f
86 	{ "getaProp",		LB::b_getaProp,		2, 2, 400, FBLTIN },	//			D4 f
87 	{ "getAt",			LB::b_getAt,		2, 2, 400, FBLTIN },	//			D4 f
88 	{ "getLast",		LB::b_getLast,		1, 1, 400, FBLTIN },	//			D4 f
89 	{ "getOne",			LB::b_getOne,		2, 2, 400, FBLTIN },	//			D4 f
90 	{ "getPos",			LB::b_getPos,		2, 2, 400, FBLTIN },	//			D4 f
91 	{ "getProp",		LB::b_getProp,		2, 2, 400, FBLTIN },	//			D4 f
92 	{ "getPropAt",		LB::b_getPropAt,	2, 2, 400, FBLTIN },	//			D4 f
93 	{ "list",			LB::b_list,			-1, 0, 400, FBLTIN },	//			D4 f
94 	{ "listP",			LB::b_listP,		1, 1, 400, FBLTIN },	//			D4 f
95 	{ "max",			LB::b_max,			-1,0, 400, FBLTIN },	//			D4 f
96 	{ "min",			LB::b_min,			-1,0, 400, FBLTIN },	//			D4 f
97 	{ "setaProp",		LB::b_setaProp,		3, 3, 400, HBLTIN },	//			D4 h
98 	{ "setAt",			LB::b_setAt,		3, 3, 400, HBLTIN },	//			D4 h
99 	{ "setProp",		LB::b_setProp,		3, 3, 400, HBLTIN },	//			D4 h
100 	{ "sort",			LB::b_sort,			1, 1, 400, HBLTIN },	//			D4 h
101 	// Files
102 	{ "closeDA",	 	LB::b_closeDA, 		0, 0, 200, CBLTIN },	// D2 c
103 	{ "closeResFile",	LB::b_closeResFile,	0, 1, 200, CBLTIN },	// D2 c
104 	{ "closeXlib",		LB::b_closeXlib,	0, 1, 200, CBLTIN },	// D2 c
105 	{ "getNthFileNameInFolder",LB::b_getNthFileNameInFolder,2,2,400,FBLTIN }, //D4 f
106 	{ "open",			LB::b_open,			1, 2, 200, CBLTIN },	// D2 c
107 	{ "openDA",	 		LB::b_openDA, 		1, 1, 200, CBLTIN },	// D2 c
108 	{ "openResFile",	LB::b_openResFile,	1, 1, 200, CBLTIN },	// D2 c
109 	{ "openXlib",		LB::b_openXlib,		1, 1, 200, CBLTIN },	// D2 c
110 	{ "saveMovie",		LB::b_saveMovie,	1, 1, 400, CBLTIN },	//			D4 c
111 	{ "setCallBack",	LB::b_setCallBack,	2, 2, 200, CBLTIN },	// D2 c
112 	{ "showResFile",	LB::b_showResFile,	0, 1, 200, CBLTIN },	// D2 c
113 	{ "showXlib",		LB::b_showXlib,		0, 1, 200, CBLTIN },	// D2 c
114 	{ "xFactoryList",	LB::b_xFactoryList,	1, 1, 300, FBLTIN },	//		D3 f
115 	// Control
116 	{ "abort",			LB::b_abort,		0, 0, 400, CBLTIN },	//			D4 c
117 	{ "continue",		LB::b_continue,		0, 0, 200, CBLTIN },	// D2 c
118 	{ "dontPassEvent",	LB::b_dontPassEvent,0, 0, 200, CBLTIN },	// D2 c
119 	{ "delay",	 		LB::b_delay,		1, 1, 200, CBLTIN },	// D2 c
120 	{ "do",		 		LB::b_do,			1, 1, 200, CBLTIN },	// D2 c
121 	{ "go",		 		LB::b_go,			1, 2, 200, CBLTIN },	// D2 c
122 	{ "halt",	 		LB::b_halt,			0, 0, 400, CBLTIN },	//			D4 c
123 	{ "nothing",		LB::b_nothing,		0, 0, 200, CBLTIN },	// D2 c
124 	{ "pass",			LB::b_pass,			0, 0, 400, CBLTIN },	//			D4 c
125 	{ "pause",			LB::b_pause,		0, 0, 200, CBLTIN },	// D2 c
126 	{ "play",			LB::b_play,			0, 2, 200, CBLTIN },	// D2 c
127 	{ "playAccel",		LB::b_playAccel,	-1,0, 200, CBLTIN },	// D2
128 		// play done													// D2
129 	{ "preLoad",		LB::b_preLoad,		-1,0, 300, CBLTIN },	//		D3.1 c
130 	{ "preLoadCast",	LB::b_preLoadCast,	-1,0, 300, CBLTIN },	//		D3.1 c
131 	{ "quit",			LB::b_quit,			0, 0, 200, CBLTIN },	// D2 c
132 	{ "restart",		LB::b_restart,		0, 0, 200, CBLTIN },	// D2 c
133 	{ "return",			LB::b_return,		0, 1, 200, CBLTIN },	// D2 f
134 	{ "shutDown",		LB::b_shutDown,		0, 0, 200, CBLTIN },	// D2 c
135 	{ "startTimer",		LB::b_startTimer,	0, 0, 200, CBLTIN },	// D2 c
136 		// when keyDown													// D2
137 		// when mouseDown												// D2
138 		// when mouseUp													// D2
139 		// when timeOut													// D2
140 	// Types
141 	{ "factory",		LB::b_factory,		1, 1, 300, FBLTIN },	//		D3
142 	{ "floatP",			LB::b_floatP,		1, 1, 300, FBLTIN },	//		D3
143 	{ "ilk",	 		LB::b_ilk,			1, 2, 400, FBLTIN },	//			D4 f
144 	{ "integerp",		LB::b_integerp,		1, 1, 200, FBLTIN },	// D2 f
145 	{ "objectp",		LB::b_objectp,		1, 1, 200, FBLTIN },	// D2 f
146 	{ "pictureP",		LB::b_pictureP,		1, 1, 400, FBLTIN },	//			D4 f
147 	{ "stringp",		LB::b_stringp,		1, 1, 200, FBLTIN },	// D2 f
148 	{ "symbolp",		LB::b_symbolp,		1, 1, 200, FBLTIN },	// D2 f
149 	{ "voidP",			LB::b_voidP,		1, 1, 400, FBLTIN },	//			D4 f
150 	// Misc
151 	{ "alert",	 		LB::b_alert,		1, 1, 200, CBLTIN },	// D2 c
152 	{ "clearGlobals",	LB::b_clearGlobals,	0, 0, 300, CBLTIN },	//		D3.1 c
153 	{ "cursor",	 		LB::b_cursor,		1, 1, 200, CBLTIN },	// D2 c
154 	{ "framesToHMS",	LB::b_framesToHMS,	4, 4, 300, FBLTIN },	//		D3 f
155 	{ "HMStoFrames",	LB::b_HMStoFrames,	4, 4, 300, FBLTIN },	//		D3 f
156 	{ "param",	 		LB::b_param,		1, 1, 400, FBLTIN },	//			D4 f
157 	{ "printFrom",	 	LB::b_printFrom,	-1,0, 200, CBLTIN },	// D2 c
158 	{ "put",			LB::b_put,			-1,0, 200, CBLTIN },	// D2
159 		// set															// D2
160 	{ "showGlobals",	LB::b_showGlobals,	0, 0, 200, CBLTIN },	// D2 c
161 	{ "showLocals",		LB::b_showLocals,	0, 0, 200, CBLTIN },	// D2 c
162 	// Score
163 	{ "constrainH",		LB::b_constrainH,	2, 2, 200, FBLTIN },	// D2 f
164 	{ "constrainV",		LB::b_constrainV,	2, 2, 200, FBLTIN },	// D2 f
165 	{ "copyToClipBoard",LB::b_copyToClipBoard,1,1, 400, CBLTIN }, //			D4 c
166 	{ "duplicate",		LB::b_duplicate,	1, 2, 400, CBLTIN },	//			D4 c
167 	{ "editableText",	LB::b_editableText,	0, 0, 200, CBLTIN },	// D2, FIXME: the field in D4+
168 	{ "erase",			LB::b_erase,		1, 1, 400, CBLTIN },	//			D4 c
169 	{ "findEmpty",		LB::b_findEmpty,	1, 1, 400, FBLTIN },	//			D4 f
170 		// go															// D2
171 	{ "importFileInto",	LB::b_importFileInto,2, 2, 400, CBLTIN }, //			D4 c
172 	{ "installMenu",	LB::b_installMenu,	1, 1, 200, CBLTIN },	// D2 c
173 	{ "label",			LB::b_label,		1, 1, 200, FBLTIN },	// D2 f
174 	{ "marker",			LB::b_marker,		1, 1, 200, FBLTIN },	// D2 f
175 	{ "move",			LB::b_move,			1, 2, 400, CBLTIN },	//			D4 c
176 	{ "moveableSprite",	LB::b_moveableSprite,0, 0, 200, CBLTIN },// D2, FIXME: the field in D4+
177 	{ "pasteClipBoardInto",LB::b_pasteClipBoardInto,1,1,400,CBLTIN },//		D4 c
178 	{ "puppetPalette",	LB::b_puppetPalette, -1,0, 200, CBLTIN },// D2 c
179 	{ "puppetSound",	LB::b_puppetSound,	-1,0, 200, CBLTIN },	// D2 c
180 	{ "puppetSprite",	LB::b_puppetSprite,	-1,0, 200, CBLTIN },	// D2 c
181 	{ "puppetTempo",	LB::b_puppetTempo,	1, 1, 200, CBLTIN },	// D2 c
182 	{ "puppetTransition",LB::b_puppetTransition,-1,0,200, CBLTIN },// D2 c
183 	{ "ramNeeded",		LB::b_ramNeeded,	2, 2, 300, FBLTIN },	//		D3.1 f
184 	{ "rollOver",		LB::b_rollOver,		1, 1, 200, FBLTIN },	// D2 f
185 	{ "spriteBox",		LB::b_spriteBox,	5, 5, 200, CBLTIN },	// D2 c
186 	{ "unLoad",			LB::b_unLoad,		0, 2, 300, CBLTIN },	//		D3.1 c
187 	{ "unLoadCast",		LB::b_unLoadCast,	0, 2, 300, CBLTIN },	//		D3.1 c
188 	{ "updateStage",	LB::b_updateStage,	0, 0, 200, CBLTIN },	// D2 c
189 	{ "zoomBox",		LB::b_zoomBox,		-1,0, 200, CBLTIN },	// D2 c
190 	{"immediateSprite", LB::b_immediateSprite, -1, 0, 200, CBLTIN}, // D2 c
191 	// Point
192 	{ "point",			LB::b_point,		2, 2, 400, FBLTIN },	//			D4 f
193 	{ "inside",			LB::b_inside,		2, 2, 400, FBLTIN },	//			D4 f
194 	{ "intersect",		LB::b_intersect,	2, 2, 400, FBLTIN },	//			D4 f
195 	{ "map",			LB::b_map,			3, 3, 400, FBLTIN },	//			D4 f
196 	{ "rect",			LB::b_rect,			2, 4, 400, FBLTIN },	//			D4 f
197 	{ "union",			LB::b_union,		2, 2, 400, FBLTIN },	//			D4 f
198 	// Sound
199 	{ "beep",	 		LB::b_beep,			0, 1, 200, CBLTIN },	// D2
200 	{ "mci",	 		LB::b_mci,			1, 1, 300, CBLTIN },	//		D3.1 c
201 	{ "mciwait",		LB::b_mciwait,		1, 1, 400, CBLTIN },	//			D4 c
202 	{ "sound",			LB::b_sound,		2, 3, 300, CBLTIN },	//		D3 c
203 	{ "soundBusy",		LB::b_soundBusy,	1, 1, 300, FBLTIN },	//		D3 f
204 	// Constants
205 	{ "backspace",		LB::b_backspace,	0, 0, 200, KBLTIN },	// D2 konstant
206 	{ "empty",			LB::b_empty,		0, 0, 200, KBLTIN },	// D2 k
207 	{ "enter",			LB::b_enter,		0, 0, 200, KBLTIN },	// D2 k
208 	{ "false",			LB::b_false,		0, 0, 200, KBLTIN },	// D2 k
209 	{ "quote",			LB::b_quote,		0, 0, 200, KBLTIN },	// D2 k
210 	{ "return",			LB::b_returnconst,	0, 0, 200, KBLTIN },	// D2 k
211 	{ "tab",			LB::b_tab,			0, 0, 200, KBLTIN },	// D2 k
212 	{ "true",			LB::b_true,			0, 0, 200, KBLTIN },	// D2 k
213 	{ "version",		LB::b_version,		0, 0, 300, KBLTIN },	//		D3 k
214 	// References
215 	{ "cast",			LB::b_cast,			1, 1, 400, FBLTIN },	//			D4 f
216 	{ "script",			LB::b_script,		1, 1, 400, FBLTIN },	//			D4 f
217 	{ "window",			LB::b_window,		1, 1, 400, FBLTIN },	//			D4 f
218 	// Chunk operations
219 	{ "numberOfChars",	LB::b_numberofchars,1, 1, 300, FBLTIN },	//			D3 f
220 	{ "numberOfItems",	LB::b_numberofitems,1, 1, 300, FBLTIN },	//			D3 f
221 	{ "numberOfLines",	LB::b_numberoflines,1, 1, 300, FBLTIN },	//			D3 f
222 	{ "numberOfWords",	LB::b_numberofwords,1, 1, 300, FBLTIN },	//			D3 f
223 
224 	// ScummVM Asserts: Used for testing ScummVM's Lingo implementation
225 	{ "scummvmAssert",	LB::b_scummvmassert,1, 2, 200, HBLTIN },
226 	{ "scummvmAssertEqual",	LB::b_scummvmassertequal,2,3,200,HBLTIN },
227 
228 	// XCMD/XFCN (HyperCard), normally exposed
229 	{ "GetVolumes", LB::b_getVolumes, 0, 0, 400, FBLTIN },
230 
231 	{ 0, 0, 0, 0, 0, VOIDSYM }
232 };
233 
initBuiltIns()234 void Lingo::initBuiltIns() {
235 	initBuiltIns(builtins);
236 }
237 
initBuiltIns(BuiltinProto protos[])238 void Lingo::initBuiltIns(BuiltinProto protos[]) {
239 	for (BuiltinProto *blt = protos; blt->name; blt++) {
240 		if (blt->version > _vm->getVersion())
241 			continue;
242 
243 		Symbol sym;
244 
245 		sym.name = new Common::String(blt->name);
246 		sym.type = blt->type;
247 		sym.nargs = blt->minArgs;
248 		sym.maxArgs = blt->maxArgs;
249 
250 		sym.u.bltin = blt->func;
251 
252 		switch (blt->type) {
253 		case CBLTIN:
254 			_builtinCmds[blt->name] = sym;
255 			break;
256 		case FBLTIN:
257 			_builtinFuncs[blt->name] = sym;
258 			break;
259 		case HBLTIN:
260 			_builtinCmds[blt->name] = sym;
261 			_builtinFuncs[blt->name] = sym;
262 			break;
263 		case KBLTIN:
264 			_builtinConsts[blt->name] = sym;
265 		default:
266 			break;
267 		}
268 	}
269 }
270 
cleanupBuiltIns()271 void Lingo::cleanupBuiltIns() {
272 	_builtinCmds.clear();
273 	_builtinFuncs.clear();
274 	_builtinConsts.clear();
275 }
276 
cleanupBuiltIns(BuiltinProto protos[])277 void Lingo::cleanupBuiltIns(BuiltinProto protos[]) {
278 	for (BuiltinProto *blt = protos; blt->name; blt++) {
279 		switch (blt->type) {
280 		case CBLTIN:
281 			_builtinCmds.erase(blt->name);
282 			break;
283 		case FBLTIN:
284 			_builtinFuncs.erase(blt->name);
285 			break;
286 		case HBLTIN:
287 			_builtinCmds.erase(blt->name);
288 			_builtinFuncs.erase(blt->name);
289 			break;
290 		case KBLTIN:
291 			_builtinConsts.erase(blt->name);
292 		default:
293 			break;
294 		}
295 	}
296 }
297 
printSTUBWithArglist(const char * funcname,int nargs,const char * prefix)298 void Lingo::printSTUBWithArglist(const char *funcname, int nargs, const char *prefix) {
299 	Common::String s(funcname);
300 
301 	s += '(';
302 
303 	for (int i = 0; i < nargs; i++) {
304 		Datum d = _stack[_stack.size() - nargs + i];
305 
306 		s += d.asString(true);
307 
308 		if (i != nargs - 1)
309 			s += ", ";
310 	}
311 
312 	s += ")";
313 
314 	debug(5, "%s %s", prefix, s.c_str());
315 }
316 
convertVOIDtoString(int arg,int nargs)317 void Lingo::convertVOIDtoString(int arg, int nargs) {
318 	if (_stack[_stack.size() - nargs + arg].type == VOID) {
319 		if (_stack[_stack.size() - nargs + arg].u.s != NULL)
320 			g_lingo->_stack[_stack.size() - nargs + arg].type = STRING;
321 		else
322 			warning("Incorrect convertVOIDtoString for arg %d of %d", arg, nargs);
323 	}
324 }
325 
dropStack(int nargs)326 void Lingo::dropStack(int nargs) {
327 	for (int i = 0; i < nargs; i++)
328 		pop();
329 }
330 
drop(uint num)331 void Lingo::drop(uint num) {
332 	if (num > _stack.size() - 1) {
333 		warning("Incorrect number of elements to drop from stack: %d > %d", num, _stack.size() - 1);
334 		return;
335 	}
336 	_stack.remove_at(_stack.size() - 1 - num);
337 }
338 
339 
340 ///////////////////
341 // Math
342 ///////////////////
b_abs(int nargs)343 void LB::b_abs(int nargs) {
344 	Datum d = g_lingo->pop();
345 
346 	if (d.type == INT)
347 		d.u.i = ABS(d.u.i);
348 	else if (d.type == FLOAT)
349 		d.u.f = ABS(d.u.f);
350 
351 	g_lingo->push(d);
352 }
353 
b_atan(int nargs)354 void LB::b_atan(int nargs) {
355 	Datum d = g_lingo->pop();
356 	Datum res(atan(d.asFloat()));
357 	g_lingo->push(res);
358 }
359 
b_cos(int nargs)360 void LB::b_cos(int nargs) {
361 	Datum d = g_lingo->pop();
362 	Datum res(cos(d.asFloat()));
363 	g_lingo->push(res);
364 }
365 
b_exp(int nargs)366 void LB::b_exp(int nargs) {
367 	Datum d = g_lingo->pop();
368 	// Lingo uses int, so we're enforcing it
369 	Datum res((double)exp((double)d.asInt()));
370 	g_lingo->push(res);
371 }
372 
b_float(int nargs)373 void LB::b_float(int nargs) {
374 	Datum d = g_lingo->pop();
375 	Datum res(d.asFloat());
376 	g_lingo->push(res);
377 }
378 
b_integer(int nargs)379 void LB::b_integer(int nargs) {
380 	Datum d = g_lingo->pop();
381 	Datum res;
382 
383 	if (d.type == FLOAT) {
384 		if (g_director->getVersion() < 500) {	// Note that D4 behaves differently from asInt()
385 			res = (int)(d.u.f + 0.5);		// Yes, +0.5 even for negative numbers
386 		} else {
387 			res = (int)round(d.u.f);
388 		}
389 	} else {
390 		res = d.asInt();
391 	}
392 
393 	g_lingo->push(res);
394 }
395 
b_log(int nargs)396 void LB::b_log(int nargs) {
397 	Datum d = g_lingo->pop();
398 	Datum res(log(d.asFloat()));
399 	g_lingo->push(res);
400 }
401 
b_pi(int nargs)402 void LB::b_pi(int nargs) {
403 	Datum res((double)M_PI);
404 	g_lingo->push(res);
405 }
406 
b_power(int nargs)407 void LB::b_power(int nargs) {
408 	Datum d1 = g_lingo->pop();
409 	Datum d2 = g_lingo->pop();
410 	Datum res(pow(d2.asFloat(), d1.asFloat()));
411 	g_lingo->push(d1);
412 }
413 
b_random(int nargs)414 void LB::b_random(int nargs) {
415 	Datum max = g_lingo->pop();
416 	Datum res((int)(g_director->_rnd.getRandom(max.asInt()) + 1));
417 	g_lingo->push(res);
418 }
419 
b_sin(int nargs)420 void LB::b_sin(int nargs) {
421 	Datum d = g_lingo->pop();
422 	Datum res(sin(d.asFloat()));
423 	g_lingo->push(res);
424 }
425 
b_sqrt(int nargs)426 void LB::b_sqrt(int nargs) {
427 	Datum d = g_lingo->pop();
428 	Datum res(sqrt(d.asFloat()));
429 	g_lingo->push(res);
430 }
431 
b_tan(int nargs)432 void LB::b_tan(int nargs) {
433 	Datum d = g_lingo->pop();
434 	Datum res(tan(d.asFloat()));
435 	g_lingo->push(res);
436 }
437 
438 ///////////////////
439 // String
440 ///////////////////
b_chars(int nargs)441 void LB::b_chars(int nargs) {
442 	Datum d3 = g_lingo->pop();
443 	Datum d2 = g_lingo->pop();
444 	Datum s = g_lingo->pop();
445 	TYPECHECK(s, STRING);
446 
447 	if (g_director->getVersion() < 400 && (d2.type == FLOAT || d3.type == FLOAT)) {
448 		warning("LB::b_chars: Called with a float in Director 2 and 3 mode. chars' can't handle floats");
449 		g_lingo->push(Datum(0));
450 		return;
451 	}
452 
453 	int to = d3.asInt();
454 	int from = d2.asInt();
455 
456 	Common::U32String src = s.asString().decode(Common::kUtf8);
457 
458 	int len = src.size();
459 	int f = MAX(0, MIN(len, from - 1));
460 	int t = MAX(0, MIN(len, to));
461 
462 	Common::String res;
463 	if (f > t) {
464 		res = Common::String("");
465 	} else {
466 		res = src.substr(f, t - f).encode(Common::kUtf8);
467 	}
468 
469 	g_lingo->push(res);
470 }
471 
b_charToNum(int nargs)472 void LB::b_charToNum(int nargs) {
473 	Datum d = g_lingo->pop();
474 
475 	TYPECHECK(d, STRING);
476 
477 	Common::U32String str = d.asString().decode(Common::kUtf8);
478 	if (str.size() == 0) {
479 		g_lingo->push(0);
480 		return;
481 	}
482 
483 	g_lingo->push(charToNum(str[0]));
484 }
485 
b_length(int nargs)486 void LB::b_length(int nargs) {
487 	Datum d = g_lingo->pop();
488 	TYPECHECK(d, STRING);
489 
490 	Common::U32String src = d.asString().decode(Common::kUtf8);
491 	int res = src.size();
492 	g_lingo->push(res);
493 }
494 
b_numToChar(int nargs)495 void LB::b_numToChar(int nargs) {
496 	int num = g_lingo->pop().asInt();
497 	g_lingo->push(Common::U32String(numToChar(num)).encode(Common::kUtf8));
498 }
499 
b_offset(int nargs)500 void LB::b_offset(int nargs) {
501 	if (nargs == 3) {
502 		b_offsetRect(nargs);
503 		return;
504 	}
505 	Common::String source = g_lingo->pop().asString();
506 	Common::String target = g_lingo->pop().asString();
507 
508 	const char *str = strstr(source.c_str(), target.c_str());
509 
510 	if (str == nullptr)
511 		g_lingo->push(Datum(0));
512 	else
513 		g_lingo->push(Datum(int(str - source.c_str() + 1)));
514 }
515 
b_string(int nargs)516 void LB::b_string(int nargs) {
517 	Datum d = g_lingo->pop();
518 	Datum res(d.asString());
519 	g_lingo->push(res);
520 }
521 
b_value(int nargs)522 void LB::b_value(int nargs) {
523 	Datum d = g_lingo->pop();
524 	Common::String expr = d.asString();
525 	if (expr.empty()) {
526 		g_lingo->push(Datum(0));
527 		return;
528 	}
529 	Common::String code = "return " + expr;
530 	// Compile the code to an anonymous function and call it
531 	ScriptContext *sc = g_lingo->_compiler->compileAnonymous(code);
532 	Symbol sym = sc->_eventHandlers[kEventGeneric];
533 	LC::call(sym, 0, true);
534 }
535 
536 ///////////////////
537 // Lists
538 ///////////////////
b_add(int nargs)539 void LB::b_add(int nargs) {
540 	// FIXME: when a list is "sorted", add should insert based on
541 	// the current ordering. otherwise, append to the end.
542 	LB::b_append(nargs);
543 }
544 
b_addAt(int nargs)545 void LB::b_addAt(int nargs) {
546 	Datum value = g_lingo->pop();
547 	Datum indexD = g_lingo->pop();
548 	Datum list = g_lingo->pop();
549 
550 	TYPECHECK2(indexD, INT, FLOAT);
551 	int index = indexD.asInt();
552 	TYPECHECK(list, ARRAY);
553 
554 	int size = list.u.farr->arr.size();
555 	if (index > size) {
556 		for (int i = 0; i < index - size - 1; i++)
557 			list.u.farr->arr.push_back(Datum(0));
558 	}
559 	list.u.farr->arr.insert_at(index - 1, value);
560 }
561 
b_addProp(int nargs)562 void LB::b_addProp(int nargs) {
563 	Datum value = g_lingo->pop();
564 	Datum prop = g_lingo->pop();
565 	Datum list = g_lingo->pop();
566 
567 	TYPECHECK(list, PARRAY);
568 
569 	PCell cell = PCell(prop, value);
570 	list.u.parr->arr.push_back(cell);
571 
572 	if (list.u.parr->_sorted) {
573 		if (list.u.parr->arr.empty())
574 			list.u.parr->arr.push_back(cell);
575 		else {
576 			uint pos = list.u.parr->arr.size();
577 			for (uint i = 0; i < list.u.parr->arr.size(); i++) {
578 				if (list.u.parr->arr[i].p.asString() > cell.p.asString()) {
579 					pos = i;
580 					break;
581 				}
582 			}
583 			list.u.parr->arr.insert_at(pos, cell);
584 		}
585 	} else {
586 		list.u.parr->arr.push_back(cell);
587 	}
588 }
589 
b_append(int nargs)590 void LB::b_append(int nargs) {
591 	Datum value = g_lingo->pop();
592 	Datum list = g_lingo->pop();
593 
594 	TYPECHECK(list, ARRAY);
595 
596 	if (list.u.farr->_sorted) {
597 		if (list.u.farr->arr.empty())
598 			list.u.farr->arr.push_back(value);
599 		else {
600 			uint pos = list.u.farr->arr.size();
601 			for (uint i = 0; i < list.u.farr->arr.size(); i++) {
602 				if (list.u.farr->arr[i].asInt() > value.asInt()) {
603 					pos = i;
604 					break;
605 				}
606 			}
607 			list.u.farr->arr.insert_at(pos, value);
608 		}
609 	} else {
610 		list.u.farr->arr.push_back(value);
611 	}
612 }
613 
b_count(int nargs)614 void LB::b_count(int nargs) {
615 	Datum list = g_lingo->pop();
616 	Datum result;
617 	result.type = INT;
618 
619 	switch (list.type) {
620 	case ARRAY:
621 		result.u.i = list.u.farr->arr.size();
622 		break;
623 	case PARRAY:
624 		result.u.i = list.u.parr->arr.size();
625 		break;
626 	default:
627 		TYPECHECK2(list, ARRAY, PARRAY);
628 	}
629 
630 	g_lingo->push(result);
631 }
632 
b_deleteAt(int nargs)633 void LB::b_deleteAt(int nargs) {
634 	Datum indexD = g_lingo->pop();
635 	Datum list = g_lingo->pop();
636 	TYPECHECK2(indexD, INT, FLOAT);
637 	TYPECHECK2(list, ARRAY, PARRAY);
638 	int index = indexD.asInt();
639 
640 	switch (list.type) {
641 	case ARRAY:
642 		list.u.farr->arr.remove_at(index - 1);
643 		break;
644 	case PARRAY:
645 		list.u.parr->arr.remove_at(index - 1);
646 		break;
647 	default:
648 		break;
649 	}
650 }
651 
b_deleteProp(int nargs)652 void LB::b_deleteProp(int nargs) {
653 	Datum prop = g_lingo->pop();
654 	Datum list = g_lingo->pop();
655 	TYPECHECK2(list, ARRAY, PARRAY);
656 
657 	switch (list.type) {
658 	case ARRAY:
659 		g_lingo->push(list);
660 		g_lingo->push(prop);
661 		b_deleteAt(nargs);
662 		break;
663 	case PARRAY: {
664 		int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
665 		if (index > 0) {
666 			list.u.parr->arr.remove_at(index - 1);
667 		}
668 		break;
669 	}
670 	default:
671 		break;
672 	}
673 }
674 
b_findPos(int nargs)675 void LB::b_findPos(int nargs) {
676 	Datum prop = g_lingo->pop();
677 	Datum list = g_lingo->pop();
678 	Datum d(0);
679 	TYPECHECK(list, PARRAY);
680 
681 	int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
682 	if (index > 0) {
683 		d.type = INT;
684 		d.u.i = index;
685 	}
686 
687 	g_lingo->push(d);
688 }
689 
b_findPosNear(int nargs)690 void LB::b_findPosNear(int nargs) {
691 	Common::String prop = g_lingo->pop().asString();
692 	Datum list = g_lingo->pop();
693 	Datum res(0);
694 	TYPECHECK(list, PARRAY);
695 
696 	// FIXME: Integrate with compareTo framework
697 	prop.toLowercase();
698 
699 	for (uint i = 0; i < list.u.parr->arr.size(); i++) {
700 		Datum p = list.u.parr->arr[i].p;
701 		Common::String tgt = p.asString();
702 		tgt.toLowercase();
703 		if (tgt.find(prop.c_str()) == 0) {
704 			res.u.i = i + 1;
705 			break;
706 		}
707 	}
708 
709 	g_lingo->push(res);
710 }
711 
b_getaProp(int nargs)712 void LB::b_getaProp(int nargs) {
713 	Datum prop = g_lingo->pop();
714 	Datum list = g_lingo->pop();
715 
716 	switch (list.type) {
717 	case ARRAY:
718 		g_lingo->push(list);
719 		g_lingo->push(prop);
720 		b_getAt(nargs);
721 		break;
722 	case PARRAY: {
723 		Datum d;
724 		int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
725 		if (index > 0) {
726 			d = list.u.parr->arr[index - 1].v;
727 		}
728 		g_lingo->push(d);
729 		break;
730 	}
731 	default:
732 		TYPECHECK2(list, ARRAY, PARRAY);
733 	}
734 }
735 
b_getAt(int nargs)736 void LB::b_getAt(int nargs) {
737 	Datum indexD = g_lingo->pop();
738 	TYPECHECK2(indexD, INT, FLOAT);
739 	Datum list = g_lingo->pop();
740 	int index = indexD.asInt();
741 
742 	switch (list.type) {
743 	case ARRAY:
744 		ARRBOUNDSCHECK(index, list);
745 		g_lingo->push(list.u.farr->arr[index - 1]);
746 		break;
747 	case PARRAY:
748 		ARRBOUNDSCHECK(index, list);
749 		g_lingo->push(list.u.parr->arr[index - 1].v);
750 		break;
751 	default:
752 		TYPECHECK2(list, ARRAY, PARRAY);
753 	}
754 }
755 
b_getLast(int nargs)756 void LB::b_getLast(int nargs) {
757 	Datum list = g_lingo->pop();
758 	switch (list.type) {
759 	case ARRAY:
760 		g_lingo->push(list.u.farr->arr.back());
761 		break;
762 	case PARRAY:
763 		g_lingo->push(list.u.parr->arr.back().v);
764 		break;
765 	default:
766 		TYPECHECK(list, ARRAY);
767 	}
768 }
769 
b_getOne(int nargs)770 void LB::b_getOne(int nargs) {
771 	Datum val = g_lingo->pop();
772 	Datum list = g_lingo->pop();
773 
774 	switch (list.type) {
775 	case ARRAY:
776 		g_lingo->push(list);
777 		g_lingo->push(val);
778 		b_getPos(nargs);
779 		break;
780 	case PARRAY: {
781 		Datum d;
782 		int index = LC::compareArrays(LC::eqData, list, val, true, true).u.i;
783 		if (index > 0) {
784 			d = list.u.parr->arr[index - 1].p;
785 		}
786 		g_lingo->push(d);
787 		break;
788 	}
789 	default:
790 		TYPECHECK2(list, ARRAY, PARRAY);
791 	}
792 }
793 
b_getPos(int nargs)794 void LB::b_getPos(int nargs) {
795 	Datum val = g_lingo->pop();
796 	Datum list = g_lingo->pop();
797 	TYPECHECK2(list, ARRAY, PARRAY);
798 
799 	switch (list.type) {
800 	case ARRAY: {
801 		Datum d(0);
802 		int index = LC::compareArrays(LC::eqData, list, val, true).u.i;
803 		if (index > 0) {
804 			d.u.i = index;
805 		}
806 		g_lingo->push(d);
807 		break;
808 	}
809 	case PARRAY: {
810 		Datum d(0);
811 		int index = LC::compareArrays(LC::eqData, list, val, true, true).u.i;
812 		if (index > 0) {
813 			d.u.i = index;
814 		}
815 		g_lingo->push(d);
816 		break;
817 	}
818 	default:
819 		break;
820 	}
821 }
822 
b_getProp(int nargs)823 void LB::b_getProp(int nargs) {
824 	Datum prop = g_lingo->pop();
825 	Datum list = g_lingo->pop();
826 	TYPECHECK2(list, ARRAY, PARRAY);
827 
828 	switch (list.type) {
829 	case ARRAY:
830 		g_lingo->push(list);
831 		g_lingo->push(prop);
832 		b_getPos(nargs);
833 		break;
834 	case PARRAY: {
835 		int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
836 		if (index > 0) {
837 			g_lingo->push(list.u.parr->arr[index - 1].v);
838 		} else {
839 			error("b_getProp: Property %s not found", prop.asString().c_str());
840 		}
841 		break;
842 	}
843 	default:
844 		break;
845 	}
846 }
847 
b_getPropAt(int nargs)848 void LB::b_getPropAt(int nargs) {
849 	Datum indexD = g_lingo->pop();
850 	Datum list = g_lingo->pop();
851 	TYPECHECK2(indexD, INT, FLOAT);
852 	TYPECHECK(list, PARRAY);
853 	int index = indexD.asInt();
854 
855 	g_lingo->push(list.u.parr->arr[index - 1].p);
856 }
857 
b_list(int nargs)858 void LB::b_list(int nargs) {
859 	Datum result;
860 	result.type = ARRAY;
861 	result.u.farr = new FArray;
862 
863 	for (int i = 0; i < nargs; i++)
864 		result.u.farr->arr.insert_at(0, g_lingo->pop());
865 
866 	g_lingo->push(result);
867 }
868 
b_listP(int nargs)869 void LB::b_listP(int nargs) {
870 	Datum list = g_lingo->pop();
871 	Datum d(0);
872 	if (list.type == ARRAY || list.type == PARRAY) {
873 		d.u.i = 1;
874 	}
875 	g_lingo->push(d);
876 }
877 
b_max(int nargs)878 void LB::b_max(int nargs) {
879 	Datum max;
880 	max.type = INT;
881 	max.u.i = 0;
882 
883 	if (nargs == 1) {
884 		Datum d = g_lingo->pop();
885 		if (d.type == ARRAY) {
886 			uint arrsize = d.u.farr->arr.size();
887 			for (uint i = 0; i < arrsize; i++) {
888 				Datum item = d.u.farr->arr[i];
889 				if (i == 0 || item.compareTo(max) > 0) {
890 					max = item;
891 				}
892 			}
893 		} else {
894 			max = d;
895 		}
896 	} else if (nargs > 0) {
897 		for (int i = 0; i < nargs; i++) {
898 			Datum d = g_lingo->_stack[g_lingo->_stack.size() - nargs + i];
899 			if (d.type == ARRAY) {
900 				warning("b_max: undefined behavior: array mixed with other args");
901 			}
902 			if (i == 0 || d.compareTo(max) > 0) {
903 				max = d;
904 			}
905 		}
906 		g_lingo->dropStack(nargs);
907 	}
908 	g_lingo->push(max);
909 }
910 
b_min(int nargs)911 void LB::b_min(int nargs) {
912 	Datum min;
913 	min.type = INT;
914 	min.u.i = 0;
915 
916 	if (nargs == 1) {
917 		Datum d = g_lingo->pop();
918 		if (d.type == ARRAY) {
919 			uint arrsize = d.u.farr->arr.size();
920 			for (uint i = 0; i < arrsize; i++) {
921 				Datum item = d.u.farr->arr[i];
922 				if (i == 0 || item.compareTo(min) < 0) {
923 					min = item;
924 				}
925 			}
926 		} else {
927 			min = d;
928 		}
929 	} else if (nargs > 0) {
930 		for (int i = 0; i < nargs; i++) {
931 			Datum d = g_lingo->_stack[g_lingo->_stack.size() - nargs + i];
932 			if (d.type == ARRAY) {
933 				warning("b_min: undefined behavior: array mixed with other args");
934 			}
935 			if (i == 0 || d.compareTo(min) < 0) {
936 				min = d;
937 			}
938 		}
939 		g_lingo->dropStack(nargs);
940 	}
941 	g_lingo->push(min);
942 }
943 
b_setaProp(int nargs)944 void LB::b_setaProp(int nargs) {
945 	Datum value = g_lingo->pop();
946 	Datum prop = g_lingo->pop();
947 	Datum list = g_lingo->pop();
948 
949 	switch (list.type) {
950 	case ARRAY:
951 		g_lingo->push(list);
952 		g_lingo->push(prop);
953 		g_lingo->push(value);
954 		b_setAt(nargs);
955 		break;
956 	case PARRAY: {
957 		int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
958 		if (index > 0) {
959 			list.u.parr->arr[index - 1].v = value;
960 		} else {
961 			PCell cell = PCell(prop, value);
962 			list.u.parr->arr.push_back(cell);
963 		}
964 		break;
965 	}
966 	default:
967 		TYPECHECK2(list, ARRAY, PARRAY);
968 	}
969 }
970 
b_setAt(int nargs)971 void LB::b_setAt(int nargs) {
972 	Datum value = g_lingo->pop();
973 	Datum indexD = g_lingo->pop();
974 	Datum list = g_lingo->pop();
975 
976 	TYPECHECK2(indexD, INT, FLOAT);
977 	TYPECHECK2(list, ARRAY, PARRAY);
978 	int index = indexD.asInt();
979 
980 	switch (list.type) {
981 	case ARRAY:
982 		if ((uint)index <= list.u.farr->arr.size()) {
983 			list.u.farr->arr[index - 1] = value;
984 		} else {
985 			int inserts = index - list.u.farr->arr.size();
986 			while (--inserts)
987 				list.u.farr->arr.push_back(Datum(0));
988 			list.u.farr->arr.push_back(value);
989 		}
990 		break;
991 	case PARRAY:
992 		ARRBOUNDSCHECK(index, list);
993 		list.u.parr->arr[index - 1].v = value;
994 		break;
995 	default:
996 		break;
997 	}
998 }
999 
b_setProp(int nargs)1000 void LB::b_setProp(int nargs) {
1001 	Datum value = g_lingo->pop();
1002 	Datum prop = g_lingo->pop();
1003 	Datum list = g_lingo->pop();
1004 	TYPECHECK(list, PARRAY);
1005 
1006 	int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
1007 	if (index > 0) {
1008 		list.u.parr->arr[index - 1].v = value;
1009 	} else {
1010 		warning("b_setProp: Property not found");
1011 	}
1012 }
1013 
sortArrayHelper(const Datum & lhs,const Datum & rhs)1014 static bool sortArrayHelper(const Datum &lhs, const Datum &rhs) {
1015 	return lhs.asInt() < rhs.asInt();
1016 }
1017 
sortPArrayHelper(const PCell & lhs,const PCell & rhs)1018 static bool sortPArrayHelper(const PCell &lhs, const PCell &rhs) {
1019 	return lhs.p.asString() < rhs.p.asString();
1020 }
1021 
b_sort(int nargs)1022 void LB::b_sort(int nargs) {
1023 	// in D4 manual, p266. linear list is sorted by values
1024 	// property list is sorted alphabetically by properties
1025 	// once the list is sorted, it maintains it's sort order even when we add new variables using add command
1026 	// see b_append to get more details.
1027 	Datum list = g_lingo->pop();
1028 
1029 	if (list.type == ARRAY) {
1030 		Common::sort(list.u.farr->arr.begin(), list.u.farr->arr.end(), sortArrayHelper);
1031 		list.u.farr->_sorted = true;
1032 
1033 	} else if (list.type == PARRAY) {
1034 		Common::sort(list.u.parr->arr.begin(), list.u.parr->arr.end(), sortPArrayHelper);
1035 		list.u.parr->_sorted = true;
1036 
1037 	} else {
1038 		warning("LB::b_sort can not handle argument of type %s", list.type2str());
1039 	}
1040 }
1041 
1042 
1043 ///////////////////
1044 // Files
1045 ///////////////////
b_closeDA(int nargs)1046 void LB::b_closeDA(int nargs) {
1047 	warning("STUB: b_closeDA");
1048 }
1049 
b_closeResFile(int nargs)1050 void LB::b_closeResFile(int nargs) {
1051 	if (nargs == 0) { // Close all res files
1052 		g_director->_openResFiles.clear();
1053 		return;
1054 	}
1055 	Datum d = g_lingo->pop();
1056 	Common::String resFileName = g_director->getCurrentWindow()->getCurrentPath() + d.asString();
1057 
1058 	g_director->_openResFiles.erase(resFileName);
1059 }
1060 
b_closeXlib(int nargs)1061 void LB::b_closeXlib(int nargs) {
1062 	Datum d = g_lingo->pop();
1063 	Common::String xlibName = d.asString();
1064 	g_lingo->closeXLib(xlibName);
1065 }
1066 
b_getNthFileNameInFolder(int nargs)1067 void LB::b_getNthFileNameInFolder(int nargs) {
1068 	int fileNum = g_lingo->pop().asInt() - 1;
1069 	Common::String path = pathMakeRelative(g_lingo->pop().asString(), true, false, true);
1070 	// for directory, we either return the correct path, which we can access recursively.
1071 	// or we get a wrong path, which will lead us to a non-exist file node
1072 
1073 	Common::StringTokenizer directory_list(path, "/");
1074 	Common::FSNode d = Common::FSNode(*g_director->getGameDataDir());
1075 	while (d.exists() && !directory_list.empty()) {
1076 		d = d.getChild(directory_list.nextToken());
1077 	}
1078 
1079 	Datum r;
1080 	if (d.exists()) {
1081 		Common::FSList f;
1082 		if (!d.getChildren(f, Common::FSNode::kListAll)) {
1083 			warning("Cannot acces directory %s", path.c_str());
1084 		} else {
1085 			if ((uint)fileNum < f.size()) {
1086 				// here, we sort all the fileNames
1087 				Common::Array<Common::String> fileNameList;
1088 				for (uint i = 0; i < f.size(); i++)
1089 					fileNameList.push_back(f[i].getName());
1090 				Common::sort(fileNameList.begin(), fileNameList.end());
1091 				r = Datum(fileNameList[fileNum]);
1092 			}
1093 		}
1094 	}
1095 
1096 	g_lingo->push(r);
1097 }
1098 
b_open(int nargs)1099 void LB::b_open(int nargs) {
1100 	g_lingo->printSTUBWithArglist("b_open", nargs);
1101 
1102 	g_lingo->dropStack(nargs);
1103 }
1104 
b_openDA(int nargs)1105 void LB::b_openDA(int nargs) {
1106 	Datum d = g_lingo->pop();
1107 
1108 	warning("STUB: b_openDA(%s)", d.asString().c_str());
1109 }
1110 
b_openResFile(int nargs)1111 void LB::b_openResFile(int nargs) {
1112 	Datum d = g_lingo->pop();
1113 	Common::String resPath = g_director->getCurrentWindow()->getCurrentPath() + d.asString();
1114 
1115 	if (g_director->getPlatform() == Common::kPlatformWindows) {
1116 		warning("STUB: BUILDBOT: b_openResFile(%s) on Windows", d.asString().c_str());
1117 		return;
1118 	}
1119 
1120 	if (!g_director->_openResFiles.contains(resPath)) {
1121 		MacArchive *resFile = new MacArchive();
1122 
1123 		if (resFile->openFile(pathMakeRelative(resPath))) {
1124 			g_director->_openResFiles.setVal(resPath, resFile);
1125 		}
1126 	}
1127 }
1128 
b_openXlib(int nargs)1129 void LB::b_openXlib(int nargs) {
1130 	// TODO: When Xtras are implemented, determine whether to initialize
1131 	// the XObject or Xtra version of FileIO
1132 
1133 	Datum d = g_lingo->pop();
1134 	Common::String xlibName = d.asString();
1135 	g_lingo->openXLib(xlibName, kXObj);
1136 }
1137 
b_saveMovie(int nargs)1138 void LB::b_saveMovie(int nargs) {
1139 	g_lingo->printSTUBWithArglist("b_saveMovie", nargs);
1140 
1141 	g_lingo->dropStack(nargs);
1142 }
1143 
b_setCallBack(int nargs)1144 void LB::b_setCallBack(int nargs) {
1145 	for (int i = 0; i < nargs; i++)
1146 		g_lingo->pop();
1147 	warning("STUB: b_setCallBack");
1148 }
1149 
b_showResFile(int nargs)1150 void LB::b_showResFile(int nargs) {
1151 	Datum d = g_lingo->pop();
1152 
1153 	warning("STUB: b_showResFile(%s)", d.asString().c_str());
1154 }
1155 
b_showXlib(int nargs)1156 void LB::b_showXlib(int nargs) {
1157 	Datum d = g_lingo->pop();
1158 
1159 	warning("STUB: b_showXlib(%s)", d.asString().c_str());
1160 }
1161 
b_xFactoryList(int nargs)1162 void LB::b_xFactoryList(int nargs) {
1163 	Datum d = g_lingo->pop();
1164 
1165 	warning("STUB: b_xFactoryList(%s)", d.asString().c_str());
1166 }
1167 
1168 ///////////////////
1169 // Control
1170 ///////////////////
b_abort(int nargs)1171 void LB::b_abort(int nargs) {
1172 	warning("STUB: b_abort");
1173 }
1174 
b_continue(int nargs)1175 void LB::b_continue(int nargs) {
1176 	g_director->_playbackPaused = false;
1177 }
1178 
b_dontPassEvent(int nargs)1179 void LB::b_dontPassEvent(int nargs) {
1180 	g_lingo->_passEvent = false;
1181 	warning("dontPassEvent raised");
1182 }
1183 
b_nothing(int nargs)1184 void LB::b_nothing(int nargs) {
1185 	// Noop
1186 }
1187 
b_delay(int nargs)1188 void LB::b_delay(int nargs) {
1189 	Datum d = g_lingo->pop();
1190 
1191 	g_director->getCurrentMovie()->getScore()->_nextFrameTime = g_system->getMillis() + (float)d.asInt() / 60 * 1000;
1192 }
1193 
b_do(int nargs)1194 void LB::b_do(int nargs) {
1195 	Common::String code = g_lingo->pop().asString();
1196 	ScriptContext *sc = g_lingo->_compiler->compileAnonymous(code);
1197 	Symbol sym = sc->_eventHandlers[kEventGeneric];
1198 	LC::call(sym, 0, false);
1199 }
1200 
b_go(int nargs)1201 void LB::b_go(int nargs) {
1202 	// Builtin function for go as used by the Director bytecode engine.
1203 	//
1204 	// Accepted arguments:
1205 	// "loop"
1206 	// "next"
1207 	// "previous"
1208 	// (STRING|INT) frame
1209 	// STRING movie, (STRING|INT) frame
1210 
1211 	if (nargs >= 1 && nargs <= 2) {
1212 		Datum firstArg = g_lingo->pop();
1213 		nargs -= 1;
1214 		bool callSpecial = false;
1215 
1216 		if (firstArg.type == SYMBOL) {
1217 			if (*firstArg.u.s == "loop") {
1218 				g_lingo->func_gotoloop();
1219 				callSpecial = true;
1220 			} else if (*firstArg.u.s == "next") {
1221 				g_lingo->func_gotonext();
1222 				callSpecial = true;
1223 			} else if (*firstArg.u.s == "previous") {
1224 				g_lingo->func_gotoprevious();
1225 				callSpecial = true;
1226 			}
1227 		}
1228 
1229 		if (!callSpecial) {
1230 			Datum movie;
1231 			Datum frame;
1232 
1233 			if (nargs > 0) {
1234 				movie = firstArg;
1235 				TYPECHECK(movie, STRING);
1236 
1237 				frame = g_lingo->pop();
1238 				nargs -= 1;
1239 			} else {
1240 				frame = firstArg;
1241 			}
1242 
1243 			if (frame.type != STRING && frame.type != INT) {
1244 				warning("b_go: frame arg should be of type STRING or INT, not %s", frame.type2str());
1245 			}
1246 
1247 			g_lingo->func_goto(frame, movie);
1248 		}
1249 
1250 		if (nargs > 0) {
1251 			warning("b_go: ignoring %d extra args", nargs);
1252 			g_lingo->dropStack(nargs);
1253 		}
1254 
1255 	} else {
1256 		warning("b_go: expected 1 or 2 args, not %d", nargs);
1257 		g_lingo->dropStack(nargs);
1258 	}
1259 }
1260 
b_halt(int nargs)1261 void LB::b_halt(int nargs) {
1262 	b_quit(nargs);
1263 
1264 	warning("Movie halted");
1265 }
1266 
b_pass(int nargs)1267 void LB::b_pass(int nargs) {
1268 	g_lingo->_passEvent = true;
1269 	warning("pass raised");
1270 }
1271 
b_pause(int nargs)1272 void LB::b_pause(int nargs) {
1273 	g_director->_playbackPaused = true;
1274 }
1275 
b_play(int nargs)1276 void LB::b_play(int nargs) {
1277 	// Builtin function for play as used by the Director bytecode engine.
1278 	//
1279 	// Accepted arguments:
1280 	// 0  									# "play done"
1281 	// (STRING|INT) frame
1282 	// STRING movie, (STRING|INT) frame
1283 
1284 	Datum movie;
1285 	Datum frame;
1286 
1287 	switch (nargs) {
1288 	case 2:
1289 		movie = g_lingo->pop();
1290 		// fall through
1291 	case 1:
1292 		frame = g_lingo->pop();
1293 		if (!(frame.type == INT && frame.u.i == 0 && nargs == 1))
1294 			break;
1295 		// fall through
1296 	case 0:
1297 		frame.type = SYMBOL;
1298 		frame.u.s = new Common::String("done");
1299 		break;
1300 	default:
1301 		warning("b_play: expected 0, 1 or 2 args, not %d", nargs);
1302 		g_lingo->dropStack(nargs);
1303 
1304 		return;
1305 	}
1306 
1307 	g_lingo->func_play(frame, movie);
1308 }
1309 
b_playAccel(int nargs)1310 void LB::b_playAccel(int nargs) {
1311 	g_lingo->printSTUBWithArglist("b_playAccel", nargs);
1312 
1313 	g_lingo->dropStack(nargs);
1314 }
1315 
b_preLoad(int nargs)1316 void LB::b_preLoad(int nargs) {
1317 	// We always pretend we preloaded all frames
1318 	// Returning the number of the last frame successfully "loaded"
1319 	if (nargs == 0) {
1320 		g_lingo->_theResult = Datum((int)g_director->getCurrentMovie()->getScore()->_frames.size());
1321 		return;
1322 	}
1323 
1324 	g_lingo->_theResult = g_lingo->pop();
1325 
1326 	if (nargs == 2)
1327 		g_lingo->pop();
1328 }
1329 
b_preLoadCast(int nargs)1330 void LB::b_preLoadCast(int nargs) {
1331 	// We always pretend we preloaded all cast
1332 	// Returning the number of the last cast successfully "loaded"
1333 
1334 	g_lingo->_theResult = g_lingo->pop();
1335 
1336 	if (nargs == 2)
1337 		g_lingo->pop();
1338 }
1339 
b_framesToHMS(int nargs)1340 void LB::b_framesToHMS(int nargs) {
1341 	int fractionalSeconds = g_lingo->pop().asInt();
1342 	int dropFrame = g_lingo->pop().asInt();
1343 	int fps = g_lingo->pop().asInt();
1344 	int frames = g_lingo->pop().asInt();
1345 
1346 	if (fps <= 0)
1347 		fps = -fps;
1348 
1349 	bool negative = frames < 0;
1350 	if (negative)
1351 		frames = -frames;
1352 
1353 	int framesPerMin = 60 * fps;
1354 	int framesPerHour = 60 * framesPerMin;
1355 
1356 	if (dropFrame)
1357 		warning("STUB: b_framesToHMS: Unhandled dropFrame option");
1358 
1359 	int h = MIN(frames / framesPerHour, 99);
1360 	int m = (frames % framesPerHour) / framesPerMin;
1361 	int s = (frames % framesPerMin) / fps;
1362 
1363 	int residual;
1364 	if (fractionalSeconds) {
1365 		int ms = (1000 * (frames % fps)) / fps;
1366 		residual = (ms + 5) / 10; // round to nearest centisecond
1367 	} else {
1368 		residual = frames % fps;
1369 	}
1370 
1371 	Common::String hms = Common::String::format(
1372 		"%c%02d:%02d:%02d.%02d%c",
1373 		negative ? '-' : ' ',
1374 		h, m, s, residual,
1375 		dropFrame ? 'd' : ' '
1376 	);
1377 
1378 	g_lingo->push(hms);
1379 }
1380 
b_HMStoFrames(int nargs)1381 void LB::b_HMStoFrames(int nargs) {
1382 	// The original implementation of this accepts some really weird,
1383 	// seemingly malformed input strings.
1384 	// (Try, for example, "12345678901234567890")
1385 	// It's probably not worth supporting them all unless we need to,
1386 	// so only well-formed input is handled right now.
1387 
1388 	int fractionalSeconds = g_lingo->pop().asInt();
1389 	int dropFrame = g_lingo->pop().asInt();
1390 	int fps = g_lingo->pop().asInt();
1391 	Common::String hms = g_lingo->pop().asString();
1392 
1393 	if (fps <= 0)
1394 		fps = 1;
1395 
1396 	const char *ptr = hms.c_str();
1397 	while (Common::isSpace(*ptr))
1398 		ptr++;
1399 
1400 	// read sign
1401 	bool negative = false;
1402 	if (*ptr == '-') {
1403 		negative = true;
1404 		ptr++;
1405 	}
1406 
1407 	// read HH, MM, and SS
1408 	int secs = 0;
1409 	for (int i = 0; i < 3; i++) {
1410 		if (*ptr == ':' || Common::isSpace(*ptr))
1411 			ptr ++;
1412 
1413 		if (!Common::isDigit(*ptr))
1414 			break;
1415 
1416 		int part = 0;
1417 		for (int j = 0; j < 2 && Common::isDigit(*ptr); j++) {
1418 			part = (10 * part) + (*ptr - '0');
1419 			ptr++;
1420 		}
1421 		secs = (60 * secs) + part;
1422 	}
1423 	int frames = secs * fps;
1424 
1425 	// read FF
1426 	if (*ptr == '.') {
1427 		ptr++;
1428 
1429 		int part = 0;
1430 		for (int i = 0; i < 2 && Common::isDigit(*ptr); i++) {
1431 			part = (10 * part) + (*ptr - '0');
1432 			ptr++;
1433 		}
1434 		if (fractionalSeconds) {
1435 			frames += (part * fps + 50) / 100; // round to nearest frame
1436 		} else {
1437 			frames += part;
1438 		}
1439 	}
1440 
1441 	// read D
1442 	if (*ptr == 'd' || *ptr == 'D') {
1443 		ptr++;
1444 		dropFrame = 1;
1445 	}
1446 
1447 	while (Common::isSpace(*ptr))
1448 		ptr++;
1449 
1450 	if (*ptr != '\0')
1451 		warning("b_HMStoFrames: Unexpected character '%c'", *ptr);
1452 
1453 	if (dropFrame)
1454 		warning("STUB: b_HMStoFrames: Unhandled dropFrame option");
1455 
1456 	if (negative)
1457 		frames = -frames;
1458 
1459 	g_lingo->push(frames);
1460 }
1461 
b_param(int nargs)1462 void LB::b_param(int nargs) {
1463 	g_lingo->printSTUBWithArglist("b_param", nargs);
1464 
1465 	g_lingo->dropStack(nargs);
1466 }
1467 
b_printFrom(int nargs)1468 void LB::b_printFrom(int nargs) {
1469 	g_lingo->printSTUBWithArglist("b_printFrom", nargs);
1470 
1471 	g_lingo->dropStack(nargs);
1472 }
1473 
b_quit(int nargs)1474 void LB::b_quit(int nargs) {
1475 	if (g_director->getCurrentMovie())
1476 		g_director->getCurrentMovie()->getScore()->_playState = kPlayStopped;
1477 
1478 	g_lingo->pushVoid();
1479 }
1480 
b_return(int nargs)1481 void LB::b_return(int nargs) {
1482 	CFrame *fp = g_director->getCurrentWindow()->_callstack.back();
1483 
1484 	Datum retVal;
1485 	if (nargs > 0) {
1486 		retVal = g_lingo->pop();
1487 		g_lingo->_theResult = retVal;	// Store result for possible reference
1488 	}
1489 
1490 	// clear any temp values from loops
1491 	while (g_lingo->_stack.size() > fp->stackSizeBefore)
1492 		g_lingo->pop();
1493 
1494 	// Do not allow a factory's mNew method to return a value
1495 	if (nargs > 0 && !(g_lingo->_currentMe.type == OBJECT && g_lingo->_currentMe.u.obj->getObjType() == kFactoryObj
1496 			&& fp->sp.name->equalsIgnoreCase("mNew"))) {
1497 		g_lingo->push(retVal);
1498 	}
1499 
1500 	LC::c_procret();
1501 }
1502 
b_restart(int nargs)1503 void LB::b_restart(int nargs) {
1504 	b_quit(nargs);
1505 
1506 	warning("Computer restarts");
1507 }
1508 
b_shutDown(int nargs)1509 void LB::b_shutDown(int nargs) {
1510 	b_quit(nargs);
1511 
1512 	warning("Computer shuts down");
1513 }
1514 
b_startTimer(int nargs)1515 void LB::b_startTimer(int nargs) {
1516 	g_director->getCurrentMovie()->_lastTimerReset = g_director->getMacTicks();
1517 }
1518 
1519 ///////////////////
1520 // Types
1521 ///////////////////
b_factory(int nargs)1522 void LB::b_factory(int nargs) {
1523 	Datum factoryName = g_lingo->pop();
1524 	factoryName.type = GLOBALREF;
1525 	Datum o = g_lingo->varFetch(factoryName);
1526 	if (o.type == OBJECT && (o.u.obj->getObjType() & (kFactoryObj | kXObj))
1527 			&& o.u.obj->getName().equalsIgnoreCase(*factoryName.u.s) && o.u.obj->getInheritanceLevel() == 1) {
1528 		g_lingo->push(o);
1529 	} else {
1530 		g_lingo->push(Datum(0));
1531 	}
1532 }
1533 
b_floatP(int nargs)1534 void LB::b_floatP(int nargs) {
1535 	Datum d = g_lingo->pop();
1536 	Datum res((d.type == FLOAT) ? 1 : 0);
1537 	g_lingo->push(res);
1538 }
1539 
b_ilk(int nargs)1540 void LB::b_ilk(int nargs) {
1541 	Datum d = g_lingo->pop();
1542 	Datum res(Common::String(d.type2str(true)));
1543 	g_lingo->push(res);
1544 }
1545 
b_integerp(int nargs)1546 void LB::b_integerp(int nargs) {
1547 	Datum d = g_lingo->pop();
1548 	Datum res((d.type == INT) ? 1 : 0);
1549 	g_lingo->push(res);
1550 }
1551 
b_objectp(int nargs)1552 void LB::b_objectp(int nargs) {
1553 	Datum d = g_lingo->pop();
1554 	Datum res;
1555 	if (d.type == OBJECT) {
1556 		res = !d.u.obj->isDisposed();
1557 	} else {
1558 		res = 0;
1559 	}
1560 	g_lingo->push(res);
1561 }
1562 
b_pictureP(int nargs)1563 void LB::b_pictureP(int nargs) {
1564 	g_lingo->pop();
1565 	warning("STUB: b_pictureP");
1566 	g_lingo->push(Datum(0));
1567 }
1568 
b_stringp(int nargs)1569 void LB::b_stringp(int nargs) {
1570 	Datum d = g_lingo->pop();
1571 	Datum res((d.type == STRING) ? 1 : 0);
1572 	g_lingo->push(res);
1573 }
1574 
b_symbolp(int nargs)1575 void LB::b_symbolp(int nargs) {
1576 	Datum d = g_lingo->pop();
1577 	Datum res((d.type == SYMBOL) ? 1 : 0);
1578 	g_lingo->push(res);
1579 }
1580 
b_voidP(int nargs)1581 void LB::b_voidP(int nargs) {
1582 	Datum d = g_lingo->pop();
1583 	Datum res((d.type == VOID) ? 1 : 0);
1584 	g_lingo->push(res);
1585 }
1586 
1587 
1588 ///////////////////
1589 // Misc
1590 ///////////////////
b_alert(int nargs)1591 void LB::b_alert(int nargs) {
1592 	Datum d = g_lingo->pop();
1593 
1594 	Common::String alert = d.asString();
1595 	warning("b_alert(%s)", alert.c_str());
1596 
1597 	if (g_director->getGameGID() == GID_TEST) {
1598 		warning("b_alert: Skipping due to tests");
1599 
1600 		return;
1601 	}
1602 
1603 	if (!debugChannelSet(-1, kDebugFewFramesOnly)) {
1604 		g_director->_wm->clearHandlingWidgets();
1605 		GUI::MessageDialog dialog(alert.c_str(), "OK");
1606 		dialog.runModal();
1607 	}
1608 }
1609 
b_clearGlobals(int nargs)1610 void LB::b_clearGlobals(int nargs) {
1611 	g_lingo->printSTUBWithArglist("b_clearGlobals", nargs);
1612 
1613 	g_lingo->dropStack(nargs);
1614 }
1615 
b_cursor(int nargs)1616 void LB::b_cursor(int nargs) {
1617 	Datum d = g_lingo->pop();
1618 	g_lingo->func_cursor(d);
1619 }
1620 
b_put(int nargs)1621 void LB::b_put(int nargs) {
1622 	// Prints a statement to the Message window
1623 	Common::String output;
1624 	for (int i = nargs - 1; i >= 0; i--) {
1625 		output += g_lingo->peek(i).asString(true);
1626 		if (i > 0)
1627 			output += " ";
1628 	}
1629 	debug("-- %s", output.c_str());
1630 	g_lingo->dropStack(nargs);
1631 }
1632 
b_showGlobals(int nargs)1633 void LB::b_showGlobals(int nargs) {
1634 	warning("STUB: b_showGlobals");
1635 }
1636 
b_showLocals(int nargs)1637 void LB::b_showLocals(int nargs) {
1638 	warning("STUB: b_showLocals");
1639 }
1640 
1641 ///////////////////
1642 // Score
1643 ///////////////////
b_constrainH(int nargs)1644 void LB::b_constrainH(int nargs) {
1645 	Datum num = g_lingo->pop();
1646 	Datum sprite = g_lingo->pop();
1647 	Score *score = g_director->getCurrentMovie()->getScore();
1648 	int res = 0;
1649 	if (score) {
1650 		Channel *ch = score->getChannelById(sprite.asInt());
1651 		if (ch) {
1652 			res = CLIP<int> (num.asInt(), ch->getBbox().left, ch->getBbox().right);
1653 		} else {
1654 			warning("b_constrainH: cannot find channel %d", sprite.asInt());
1655 		}
1656 	} else {
1657 		warning("b_constrainH: no score");
1658 	}
1659 
1660 	g_lingo->push(Datum(res));
1661 }
1662 
b_constrainV(int nargs)1663 void LB::b_constrainV(int nargs) {
1664 	Datum num = g_lingo->pop();
1665 	Datum sprite = g_lingo->pop();
1666 
1667 	Score *score = g_director->getCurrentMovie()->getScore();
1668 	int res = 0;
1669 	if (score) {
1670 		Channel *ch = score->getChannelById(sprite.asInt());
1671 		if (ch) {
1672 			res = CLIP<int> (num.asInt(), ch->getBbox().top, ch->getBbox().bottom);
1673 		} else {
1674 			warning("b_constrainH: cannot find channel %d", sprite.asInt());
1675 		}
1676 	} else {
1677 		warning("b_constrainV: no score");
1678 	}
1679 
1680 	g_lingo->push(Datum(res));
1681 }
1682 
b_copyToClipBoard(int nargs)1683 void LB::b_copyToClipBoard(int nargs) {
1684 	g_lingo->printSTUBWithArglist("b_copyToClipBoard", nargs);
1685 
1686 	g_lingo->dropStack(nargs);
1687 }
1688 
b_duplicate(int nargs)1689 void LB::b_duplicate(int nargs) {
1690 	g_lingo->printSTUBWithArglist("b_duplicate", nargs);
1691 
1692 	g_lingo->dropStack(nargs);
1693 }
1694 
b_editableText(int nargs)1695 void LB::b_editableText(int nargs) {
1696 	Score *sc = g_director->getCurrentMovie()->getScore();
1697 	if (!sc) {
1698 		warning("b_editableText: no score");
1699 		g_lingo->dropStack(nargs);
1700 		return;
1701 	}
1702 
1703 	if (nargs == 2) {
1704 		Datum state = g_lingo->pop();
1705 		Datum sprite = g_lingo->pop();
1706 		if ((uint)sprite.asInt() < sc->_channels.size()) {
1707 			sc->getSpriteById(sprite.asInt())->_editable = state.asInt();
1708 			sc->getOriginalSpriteById(sprite.asInt())->_editable = state.asInt();
1709 		} else {
1710 			warning("b_editableText: sprite index out of bounds");
1711 		}
1712 	} else if (nargs == 0 && g_director->getVersion() < 400) {
1713 		g_lingo->dropStack(nargs);
1714 
1715 		if (g_lingo->_currentChannelId == -1) {
1716 			warning("b_editableText: channel Id is missing");
1717 			return;
1718 		}
1719 		sc->getSpriteById(g_lingo->_currentChannelId)->_editable = true;
1720 		sc->getOriginalSpriteById(g_lingo->_currentChannelId)->_editable = true;
1721 	} else {
1722 		warning("b_editableText: unexpectedly received %d arguments", nargs);
1723 		g_lingo->dropStack(nargs);
1724 	}
1725 }
1726 
b_erase(int nargs)1727 void LB::b_erase(int nargs) {
1728 	g_lingo->printSTUBWithArglist("b_erase", nargs);
1729 
1730 	g_lingo->dropStack(nargs);
1731 }
1732 
b_findEmpty(int nargs)1733 void LB::b_findEmpty(int nargs) {
1734 	g_lingo->printSTUBWithArglist("b_findEmpty", nargs);
1735 
1736 	g_lingo->dropStack(nargs);
1737 
1738 	g_lingo->push(Datum(0));
1739 }
1740 
b_importFileInto(int nargs)1741 void LB::b_importFileInto(int nargs) {
1742 	g_lingo->printSTUBWithArglist("b_importFileInto", nargs);
1743 
1744 	g_lingo->dropStack(nargs);
1745 }
1746 
menuCommandsCallback(int action,Common::String & text,void * data)1747 void menuCommandsCallback(int action, Common::String &text, void *data) {
1748 	g_director->getCurrentMovie()->queueUserEvent(kEventMenuCallback, action);
1749 }
1750 
b_installMenu(int nargs)1751 void LB::b_installMenu(int nargs) {
1752 	// installMenu castNum
1753 	Datum d = g_lingo->pop();
1754 
1755 	CastMemberID memberID = d.asMemberID();
1756 	if (memberID.member == 0) {
1757 		g_director->_wm->removeMenu();
1758 		return;
1759 	}
1760 
1761 	CastMember *member = g_director->getCurrentMovie()->getCastMember(memberID);
1762 	if (!member) {
1763 		g_lingo->lingoError("installMenu: Unknown %s", memberID.asString().c_str());
1764 		return;
1765 	}
1766 	if (member->_type != kCastText) {
1767 		g_lingo->lingoError("installMenu: %s is not a field", memberID.asString().c_str());
1768 		return;
1769 	}
1770 	TextCastMember *field = static_cast<TextCastMember *>(member);
1771 
1772 	// TODO: We should process the U32String instead of encoding it to Mac Roman first
1773 	Common::String menuStxt = g_lingo->_compiler->codePreprocessor(field->getText(), field->getCast()->_lingoArchive, kNoneScript, memberID, true).encode(Common::kMacRoman);
1774 	Common::String line;
1775 	int linenum = -1; // We increment it before processing
1776 
1777 	Graphics::MacMenu *menu = g_director->_wm->addMenu();
1778 	int submenu = -1;
1779 	Common::String submenuText;
1780 	Common::String command;
1781 	int commandId = 100;
1782 
1783 	menu->setCommandsCallback(menuCommandsCallback, g_director);
1784 
1785 	debugC(3, kDebugLingoExec, "installMenu: '%s'", Common::toPrintable(menuStxt).c_str());
1786 
1787 	LingoArchive *mainArchive = g_director->getCurrentMovie()->getMainLingoArch();
1788 
1789 	for (const byte *s = (const byte *)menuStxt.c_str(); *s; s++) {
1790 		// Get next line
1791 		line.clear();
1792 		while (*s && *s != '\n') { // If we see a whitespace
1793 			if (*s == (byte)'\xc2') {
1794 				s++;
1795 				if (*s == '\n') {
1796 					line += ' ';
1797 
1798 					s++;
1799 				}
1800 			} else {
1801 				line += *s++;
1802 			}
1803 		}
1804 
1805 		linenum++;
1806 
1807 		if (line.empty())
1808 			continue;
1809 
1810 		if (line.hasPrefixIgnoreCase("menu:")) {
1811 			const char *p = &line.c_str()[5];
1812 
1813 			while (*p && (*p == ' ' || *p == '\t'))
1814 				p++;
1815 
1816 			if (!submenuText.empty()) { // Adding submenu for previous menu
1817 				menu->createSubMenuFromString(submenu, submenuText.c_str(), 0);
1818 			}
1819 
1820 			if (!strcmp(p, "@"))
1821 				p = "\xf0";	// Apple symbol
1822 
1823 			submenu = menu->addMenuItem(nullptr, Common::String(p));
1824 
1825 			submenuText.clear();
1826 
1827 			continue;
1828 		}
1829 
1830 		// We have \xc5 as a separator
1831 		const char *p = strchr(line.c_str(), '\xc5');
1832 
1833 		Common::String text;
1834 
1835 		if (p) {
1836 			text = Common::String(line.c_str(), p);
1837 			command = Common::String(p + 1);
1838 		} else {
1839 			text = line;
1840 			command = "";
1841 		}
1842 
1843 		text.trim();
1844 		command.trim();
1845 
1846 		submenuText += text;
1847 
1848 		if (!submenuText.empty()) {
1849 			if (!command.empty()) {
1850 				while (mainArchive->getScriptContext(kEventScript, commandId)) {
1851 					commandId++;
1852 				}
1853 				mainArchive->replaceCode(command.decode(Common::kMacRoman), kEventScript, commandId);
1854 				submenuText += Common::String::format("[%d];", commandId);
1855 			} else {
1856 				submenuText += ';';
1857 			}
1858 		}
1859 
1860 		if (!*s) // if we reached end of string, do not increment it but break
1861 			break;
1862 	}
1863 
1864 	if (!submenuText.empty()) {
1865 		menu->createSubMenuFromString(submenu, submenuText.c_str(), 0);
1866 	}
1867 }
1868 
b_label(int nargs)1869 void LB::b_label(int nargs) {
1870 	Datum d = g_lingo->pop();
1871 	uint16 label = g_lingo->func_label(d);
1872 
1873 	g_lingo->push(label);
1874 }
1875 
b_marker(int nargs)1876 void LB::b_marker(int nargs) {
1877 	// marker functions as label when the input is a string
1878 	Datum d = g_lingo->pop();
1879 	int marker;
1880 	if (d.type == STRING) {
1881 		marker = g_lingo->func_label(d);
1882 	} else {
1883 		marker = g_lingo->func_marker(d.asInt());
1884 	}
1885 	g_lingo->push(marker);
1886 }
1887 
b_move(int nargs)1888 void LB::b_move(int nargs) {
1889 	g_lingo->printSTUBWithArglist("b_move", nargs);
1890 
1891 	g_lingo->dropStack(nargs);
1892 }
1893 
b_moveableSprite(int nargs)1894 void LB::b_moveableSprite(int nargs) {
1895 	Score *sc = g_director->getCurrentMovie()->getScore();
1896 	Frame *frame = sc->_frames[g_director->getCurrentMovie()->getScore()->getCurrentFrame()];
1897 
1898 	if (g_lingo->_currentChannelId == -1) {
1899 		warning("b_moveableSprite: channel Id is missing");
1900 		assert(0);
1901 		return;
1902 	}
1903 
1904 	// since we are using value copying, in order to make it taking effect immediately. we modify the sprites in channel
1905 	if (sc->_channels[g_lingo->_currentChannelId])
1906 		sc->_channels[g_lingo->_currentChannelId]->_sprite->_moveable = true;
1907 	frame->_sprites[g_lingo->_currentChannelId]->_moveable = true;
1908 }
1909 
b_pasteClipBoardInto(int nargs)1910 void LB::b_pasteClipBoardInto(int nargs) {
1911 	g_lingo->printSTUBWithArglist("b_pasteClipBoardInto", nargs);
1912 
1913 	g_lingo->dropStack(nargs);
1914 }
1915 
b_puppetPalette(int nargs)1916 void LB::b_puppetPalette(int nargs) {
1917 	g_lingo->convertVOIDtoString(0, nargs);
1918 	int numFrames = 0, speed = 0, palette = 0;
1919 	Datum d;
1920 
1921 	switch (nargs) {
1922 	case 3:
1923 		numFrames = g_lingo->pop().asInt();
1924 		// fall through
1925 	case 2:
1926 		speed = g_lingo->pop().asInt();
1927 		// fall through
1928 	case 1:
1929 		d = g_lingo->pop();
1930 
1931 		if (d.type == STRING) {
1932 			// TODO: It seems that there are not strings for Mac and Win system palette
1933 			Common::String palStr = d.asString();
1934 			if (palStr.equalsIgnoreCase("Rainbow")) {
1935 				palette = kClutRainbow;
1936 			} else if (palStr.equalsIgnoreCase("Grayscale")) {
1937 				palette = kClutGrayscale;
1938 			} else if (palStr.equalsIgnoreCase("Pastels")) {
1939 				palette = kClutPastels;
1940 			} else if (palStr.equalsIgnoreCase("Vivid")) {
1941 				palette = kClutVivid;
1942 			} else if (palStr.equalsIgnoreCase("NTSC")) {
1943 				palette = kClutNTSC;
1944 			} else if (palStr.equalsIgnoreCase("Metallic")) {
1945 				palette = kClutMetallic;
1946 			}
1947 		}
1948 		if (!palette) {
1949 			CastMember *member = g_director->getCurrentMovie()->getCastMember(d.asMemberID());
1950 
1951 			if (member && member->_type == kCastPalette)
1952 				palette = ((PaletteCastMember *)member)->getPaletteId();
1953 		}
1954 		break;
1955 	default:
1956 		ARGNUMCHECK(1);
1957 		return;
1958 	}
1959 
1960 	if (palette) {
1961 		g_director->setPalette(palette);
1962 		g_director->getCurrentMovie()->getScore()->_puppetPalette = true;
1963 	} else {
1964 		// Setting puppetPalette to 0 disables it (Lingo Dictionary, 226)
1965 		Score *sc = g_director->getCurrentMovie()->getScore();
1966 		g_director->getCurrentMovie()->getScore()->_puppetPalette = false;
1967 
1968 		// FIXME: set system palette decided by platform, should be fixed after windows palette is working.
1969 		// try to set mac system palette if lastPalette is 0.
1970 		if (sc->_lastPalette == 0)
1971 			g_director->setPalette(-1);
1972 		else
1973 			g_director->setPalette(sc->resolvePaletteId(sc->_lastPalette));
1974 	}
1975 
1976 	// TODO: Implement advanced features that use these.
1977 	if (numFrames || speed)
1978 		warning("b_puppetPalette: Skipping extra features");
1979 }
1980 
b_puppetSound(int nargs)1981 void LB::b_puppetSound(int nargs) {
1982 
1983 	if (nargs < 1 || nargs >= 3) {
1984 		warning("b_puppetSound(): needs 1 or 2 args");
1985 		return;
1986 	}
1987 
1988 	DirectorSound *sound = g_director->getCurrentWindow()->getSoundManager();
1989 	Score *score = g_director->getCurrentMovie()->getScore();
1990 
1991 	if (!score) {
1992 		warning("b_puppetSound(): no score");
1993 		return;
1994 	}
1995 
1996 	// Most variants of puppetSound don't actually play the sound
1997 	// until the playback head moves or updateStage is called.
1998 	// So we'll just queue it to be played later.
1999 
2000 	if (nargs == 1) {
2001 		CastMemberID castMember = g_lingo->pop().asMemberID();
2002 
2003 		// in D2 manual p206, puppetSound 0 will turn off the puppet status of sound
2004 		sound->setPuppetSound(castMember, 1);
2005 	} else {
2006 		if (g_director->getVersion() < 400) {
2007 			// in D2/3/3.1 interactivity manual, 2 args represent the menu and submenu sounds
2008 			int submenu = g_lingo->pop().asInt();
2009 			int menu = g_lingo->pop().asInt();
2010 
2011 			if (menu < kMinSampledMenu || menu > kMaxSampledMenu)
2012 				warning("LB::puppetSound: menu number is not available");
2013 
2014 			sound->setPuppetSound(SoundID(kSoundExternal, menu, submenu), 1);
2015 		} else {
2016 			// Two-argument puppetSound is undocumented in D4.
2017 			// It is however documented in the D5 Lingo dictionary.
2018 			CastMemberID castMember = g_lingo->pop().asMemberID();
2019 			int channel = g_lingo->pop().asInt();
2020 			sound->setPuppetSound(castMember, channel);
2021 
2022 			// The D4 two-arg variant of puppetSound plays
2023 			// immediately for some inexplicable reason.
2024 			sound->playPuppetSound(channel);
2025 		}
2026 	}
2027 }
2028 
b_immediateSprite(int nargs)2029 void LB::b_immediateSprite(int nargs) {
2030 	Score *sc = g_director->getCurrentMovie()->getScore();
2031 	if (!sc) {
2032 		warning("b_immediateSprite: no score");
2033 		g_lingo->dropStack(nargs);
2034 		return;
2035 	}
2036 
2037 	if (nargs == 2) {
2038 		Datum state = g_lingo->pop();
2039 		Datum sprite = g_lingo->pop();
2040 
2041 		Sprite *sp = sc->getSpriteById(sprite.asInt());
2042 		if ((uint)sprite.asInt() < sc->_channels.size()) {
2043 			if (sc->getNextFrame() && !sp->_immediate) {
2044 				// same as puppetSprite
2045 				Channel *channel = sc->getChannelById(sprite.asInt());
2046 
2047 				channel->replaceSprite(sc->_frames[sc->getNextFrame()]->_sprites[sprite.asInt()]);
2048 				channel->_dirty = true;
2049 			}
2050 
2051 			sc->getSpriteById(sprite.asInt())->_immediate = (bool)state.asInt();
2052 		} else {
2053 			warning("b_immediateSprite: sprite index out of bounds");
2054 		}
2055 	} else if (nargs == 0 && g_director->getVersion() < 400) {
2056 		g_lingo->dropStack(nargs);
2057 
2058 		if (g_lingo->_currentChannelId == -1) {
2059 			warning("b_immediateSprite: channel Id is missing");
2060 			return;
2061 		}
2062 		sc->getSpriteById(g_lingo->_currentChannelId)->_immediate = true;
2063 	} else {
2064 		warning("b_immediateSprite: unexpectedly received %d arguments", nargs);
2065 		g_lingo->dropStack(nargs);
2066 	}
2067 }
2068 
b_puppetSprite(int nargs)2069 void LB::b_puppetSprite(int nargs) {
2070 	Score *sc = g_director->getCurrentMovie()->getScore();
2071 	if (!sc) {
2072 		warning("b_puppetSprite: no score");
2073 		g_lingo->dropStack(nargs);
2074 		return;
2075 	}
2076 
2077 	if (nargs == 2) {
2078 		Datum state = g_lingo->pop();
2079 		Datum sprite = g_lingo->pop();
2080 
2081 		Sprite *sp = sc->getSpriteById(sprite.asInt());
2082 		if ((uint)sprite.asInt() < sc->_channels.size()) {
2083 			if (sc->getNextFrame() && !sp->_puppet) {
2084 				// WORKAROUND: If a frame update is queued, update the sprite to the
2085 				// sprite in new frame before setting puppet (Majestic).
2086 				Channel *channel = sc->getChannelById(sprite.asInt());
2087 
2088 				channel->replaceSprite(sc->_frames[sc->getNextFrame()]->_sprites[sprite.asInt()]);
2089 				channel->_dirty = true;
2090 			}
2091 
2092 			sc->getSpriteById(sprite.asInt())->_puppet = (bool)state.asInt();
2093 		} else {
2094 			warning("b_puppetSprite: sprite index out of bounds");
2095 		}
2096 	} else if (nargs == 0 && g_director->getVersion() < 400) {
2097 		g_lingo->dropStack(nargs);
2098 
2099 		if (g_lingo->_currentChannelId == -1) {
2100 			warning("b_puppetSprite: channel Id is missing");
2101 			return;
2102 		}
2103 		sc->getSpriteById(g_lingo->_currentChannelId)->_puppet = true;
2104 	} else {
2105 		warning("b_puppetSprite: unexpectedly received %d arguments", nargs);
2106 		g_lingo->dropStack(nargs);
2107 	}
2108 }
2109 
b_puppetTempo(int nargs)2110 void LB::b_puppetTempo(int nargs) {
2111 	g_director->getCurrentMovie()->getScore()->_puppetTempo = g_lingo->pop().asInt();
2112 }
2113 
b_puppetTransition(int nargs)2114 void LB::b_puppetTransition(int nargs) {
2115 	// puppetTransition whichTransition [, time] [, chunkSize] [, changeArea]
2116 	Window *stage = g_director->getCurrentWindow();
2117 	uint16 duration = 250, area = 1, chunkSize = 1, type = 0;
2118 
2119 	switch (nargs) {
2120 	case 4:
2121 		area = g_lingo->pop().asInt();
2122 		// fall through
2123 	case 3:
2124 		chunkSize = g_lingo->pop().asInt();
2125 		// fall through
2126 	case 2:
2127 		duration = g_lingo->pop().asInt();
2128 		// fall through
2129 	case 1:
2130 		type = ((TransitionType)(g_lingo->pop().asInt()));
2131 		break;
2132 	default:
2133 		ARGNUMCHECK(1);
2134 		g_lingo->dropStack(nargs);
2135 		return;
2136 	}
2137 
2138 	if (stage->_puppetTransition) {
2139 		warning("b_puppetTransition: Transition already queued");
2140 		return;
2141 	}
2142 
2143 	stage->_puppetTransition = new TransParams(duration, area, chunkSize, ((TransitionType)type));
2144 }
2145 
b_ramNeeded(int nargs)2146 void LB::b_ramNeeded(int nargs) {
2147 	Datum d = g_lingo->pop();
2148 	Datum d2 = g_lingo->pop();
2149 
2150 	// We do not need RAM, we have it all
2151 
2152 	g_lingo->push(Datum(0));
2153 }
2154 
b_rollOver(int nargs)2155 void LB::b_rollOver(int nargs) {
2156 	Datum d = g_lingo->pop();
2157 	Datum res(0);
2158 	int arg = d.asInt();
2159 
2160 	Score *score = g_director->getCurrentMovie()->getScore();
2161 
2162 	if (!score) {
2163 		warning("b_rollOver: Reference to an empty score");
2164 		return;
2165 	}
2166 
2167 	if (arg >= (int32) score->_channels.size()) {
2168 		g_lingo->push(res);
2169 		return;
2170 	}
2171 
2172 	Common::Point pos = g_director->getCurrentWindow()->getMousePos();
2173 
2174 	if (score->checkSpriteIntersection(arg, pos))
2175 		res.u.i = 1; // TRUE
2176 
2177 	g_lingo->push(res);
2178 }
2179 
b_spriteBox(int nargs)2180 void LB::b_spriteBox(int nargs) {
2181 	int b = g_lingo->pop().asInt();
2182 	int r = g_lingo->pop().asInt();
2183 	int t = g_lingo->pop().asInt();
2184 	int l = g_lingo->pop().asInt();
2185 	int spriteId = g_lingo->pop().asInt();
2186 	Channel *channel = g_director->getCurrentMovie()->getScore()->getChannelById(spriteId);
2187 
2188 	if (!channel)
2189 		return;
2190 
2191 	g_director->getCurrentWindow()->addDirtyRect(channel->getBbox());
2192 	channel->setBbox(l, t, r, b);
2193 	channel->_dirty = true;
2194 }
2195 
b_unLoad(int nargs)2196 void LB::b_unLoad(int nargs) {
2197 	// No op for us, we do not unload casts
2198 
2199 	g_lingo->dropStack(nargs);
2200 }
2201 
b_unLoadCast(int nargs)2202 void LB::b_unLoadCast(int nargs) {
2203 	// No op for us, we do not unload casts
2204 
2205 	g_lingo->dropStack(nargs);
2206 }
2207 
b_zoomBox(int nargs)2208 void LB::b_zoomBox(int nargs) {
2209 	// zoomBox startSprite, endSprite [, delatTicks]
2210 	//   ticks are in 1/60th, default 1
2211 	if (nargs < 2 || nargs > 3) {
2212 		warning("b_zoomBox: expected 2 or 3 arguments, got %d", nargs);
2213 
2214 		g_lingo->dropStack(nargs);
2215 
2216 		return;
2217 	}
2218 
2219 	int delayTicks = 1;
2220 	if (nargs > 2) {
2221 		Datum d = g_lingo->pop();
2222 		delayTicks = d.asInt();
2223 	}
2224 
2225 	int endSpriteId = g_lingo->pop().asInt();
2226 	int startSpriteId = g_lingo->pop().asInt();
2227 
2228 	Score *score = g_director->getCurrentMovie()->getScore();
2229 	uint16 curFrame = score->getCurrentFrame();
2230 
2231 	Common::Rect startRect = score->_channels[startSpriteId]->getBbox();
2232 	if (startRect.isEmpty()) {
2233 		warning("b_zoomBox: unknown start sprite #%d", startSpriteId);
2234 		return;
2235 	}
2236 
2237 	// Looks for endSprite in the current frame, otherwise
2238 	// Looks for endSprite in the next frame
2239 	Common::Rect endRect = score->_channels[endSpriteId]->getBbox();
2240 	if (endRect.isEmpty()) {
2241 		if ((uint)curFrame + 1 < score->_frames.size()) {
2242 			Channel endChannel(score->_frames[curFrame + 1]->_sprites[endSpriteId]);
2243 			endRect = endChannel.getBbox();
2244 		}
2245 	}
2246 
2247 	if (endRect.isEmpty()) {
2248 		if ((uint)curFrame - 1 > 0) {
2249 			Channel endChannel(score->_frames[curFrame - 1]->_sprites[endSpriteId]);
2250 			endRect = endChannel.getBbox();
2251 		}
2252 	}
2253 
2254 	if (endRect.isEmpty()) {
2255 		warning("b_zoomBox: unknown end sprite #%d", endSpriteId);
2256 		return;
2257 	}
2258 
2259 	Graphics::ZoomBox *box = new Graphics::ZoomBox;
2260 	box->start = startRect;
2261 	box->end = endRect;
2262 	box->delay = delayTicks;
2263 	box->step = 0;
2264 	box->startTime = g_system->getMillis();
2265 	box->nextTime  = g_system->getMillis() + 1000 * box->step / 60;
2266 
2267 	g_director->_wm->addZoomBox(box);
2268 }
2269 
b_updateStage(int nargs)2270 void LB::b_updateStage(int nargs) {
2271 	if (g_director->getGameGID() == GID_TEST) {
2272 		warning("b_updateStage: Skipping due to tests");
2273 
2274 		return;
2275 	}
2276 
2277 	Movie *movie = g_director->getCurrentMovie();
2278 	if (!movie) {
2279 		warning("b_updateStage: no movie");
2280 
2281 		return;
2282 	}
2283 
2284 	Score *score = movie->getScore();
2285 
2286 	score->updateWidgets(movie->_videoPlayback);
2287 	movie->getWindow()->render();
2288 
2289 	// play any puppet sounds that have been queued
2290 	score->playSoundChannel(score->getCurrentFrame());
2291 
2292 	if (score->_cursorDirty) {
2293 		score->renderCursor(movie->getWindow()->getMousePos());
2294 		score->_cursorDirty = false;
2295 	}
2296 
2297 	g_director->draw();
2298 
2299 	if (debugChannelSet(-1, kDebugFewFramesOnly)) {
2300 		score->_framesRan++;
2301 
2302 		if (score->_framesRan > 9) {
2303 			warning("b_updateStage(): exiting due to debug few frames only");
2304 			score->_playState = kPlayStopped;
2305 		}
2306 	}
2307 }
2308 
2309 
2310 ///////////////////
2311 // Point
2312 ///////////////////
b_point(int nargs)2313 void LB::b_point(int nargs) {
2314 	Datum y(g_lingo->pop().asFloat());
2315 	Datum x(g_lingo->pop().asFloat());
2316 	Datum d;
2317 
2318 	d.u.farr = new FArray;
2319 
2320 	d.u.farr->arr.push_back(x);
2321 	d.u.farr->arr.push_back(y);
2322 	d.type = POINT;
2323 
2324 	g_lingo->push(d);
2325 }
2326 
b_rect(int nargs)2327 void LB::b_rect(int nargs) {
2328 	Datum d(0);
2329 
2330 	if (nargs == 4) {
2331 		Datum bottom(g_lingo->pop().asInt());
2332 		Datum right(g_lingo->pop().asInt());
2333 		Datum top(g_lingo->pop().asInt());
2334 		Datum left(g_lingo->pop().asInt());
2335 
2336 		d.u.farr = new FArray;
2337 		d.u.farr->arr.push_back(left);
2338 		d.u.farr->arr.push_back(top);
2339 		d.u.farr->arr.push_back(right);
2340 		d.u.farr->arr.push_back(bottom);
2341 		d.type = RECT;
2342 	} else if (nargs == 2) {
2343 		Datum p2 = g_lingo->pop();
2344 		Datum p1 = g_lingo->pop();
2345 
2346 		if (p2.type == POINT && p1.type == POINT) {
2347 			d.u.farr = new FArray;
2348 			d.u.farr->arr.push_back(p1.u.farr->arr[0]);
2349 			d.u.farr->arr.push_back(p1.u.farr->arr[1]);
2350 			d.u.farr->arr.push_back(p2.u.farr->arr[0]);
2351 			d.u.farr->arr.push_back(p2.u.farr->arr[1]);
2352 			d.type = RECT;
2353 		} else
2354 			warning("LB::b_rect: Rect need 2 Point variable as argument");
2355 
2356 	} else {
2357 		warning("LB::b_rect: Rect doesn't support %d args", nargs);
2358 		g_lingo->dropStack(nargs);
2359 	}
2360 
2361 	g_lingo->push(d);
2362 }
2363 
2364 
b_intersect(int nargs)2365 void LB::b_intersect(int nargs) {
2366 	Datum d;
2367 	Datum r2 = g_lingo->pop();
2368 	Datum r1 = g_lingo->pop();
2369 	Common::Rect rect1(r1.u.farr->arr[0].asInt(), r1.u.farr->arr[1].asInt(), r1.u.farr->arr[2].asInt(), r1.u.farr->arr[3].asInt());
2370 	Common::Rect rect2(r2.u.farr->arr[0].asInt(), r2.u.farr->arr[1].asInt(), r2.u.farr->arr[2].asInt(), r2.u.farr->arr[3].asInt());
2371 
2372 	d.type = INT;
2373 	d.u.i = rect1.intersects(rect2);
2374 
2375 	g_lingo->push(d);
2376 }
2377 
b_inside(int nargs)2378 void LB::b_inside(int nargs) {
2379 	Datum d;
2380 	Datum r2 = g_lingo->pop();
2381 	Datum p1 = g_lingo->pop();
2382 	Common::Rect rect2(r2.u.farr->arr[0].asInt(), r2.u.farr->arr[1].asInt(), r2.u.farr->arr[2].asInt(), r2.u.farr->arr[3].asInt());
2383 	Common::Point point1(p1.u.farr->arr[0].asInt(), p1.u.farr->arr[1].asInt());
2384 
2385 	d.type = INT;
2386 	d.u.i = rect2.contains(point1);
2387 
2388 	g_lingo->push(d);
2389 }
2390 
b_map(int nargs)2391 void LB::b_map(int nargs) {
2392 	g_lingo->printSTUBWithArglist("b_map", nargs);
2393 
2394 	g_lingo->dropStack(nargs);
2395 
2396 	g_lingo->push(Datum(0));
2397 }
2398 
b_offsetRect(int nargs)2399 void LB::b_offsetRect(int nargs) {
2400 	g_lingo->printSTUBWithArglist("b_offsetRect", nargs);
2401 
2402 	g_lingo->dropStack(nargs);
2403 
2404 	g_lingo->push(Datum(0));
2405 }
2406 
b_union(int nargs)2407 void LB::b_union(int nargs) {
2408 	g_lingo->printSTUBWithArglist("b_union", nargs);
2409 
2410 	g_lingo->dropStack(nargs);
2411 
2412 	g_lingo->push(Datum(0));
2413 }
2414 
2415 
2416 ///////////////////
2417 // Sound
2418 ///////////////////
b_beep(int nargs)2419 void LB::b_beep(int nargs) {
2420 	int repeat = 1;
2421 	if (nargs == 1) {
2422 		Datum d = g_lingo->pop();
2423 		repeat = d.u.i;
2424 	}
2425 	g_lingo->func_beep(repeat);
2426 }
2427 
b_mci(int nargs)2428 void LB::b_mci(int nargs) {
2429 	Datum d = g_lingo->pop();
2430 
2431 	g_lingo->func_mci(d.asString());
2432 }
2433 
b_mciwait(int nargs)2434 void LB::b_mciwait(int nargs) {
2435 	Datum d = g_lingo->pop();
2436 
2437 	g_lingo->func_mciwait(d.asString());
2438 }
2439 
b_sound(int nargs)2440 void LB::b_sound(int nargs) {
2441 	// Builtin function for sound as used by the Director bytecode engine.
2442 	//
2443 	// Accepted arguments:
2444 	// "close", INT soundChannel
2445 	// "fadeIn", INT soundChannel(, INT ticks)
2446 	// "fadeOut", INT soundChannel(, INT ticks)
2447 	// "playFile", INT soundChannel, STRING fileName
2448 	// "stop", INT soundChannel
2449 
2450 	if (nargs < 2 || nargs > 3) {
2451 		warning("b_sound: expected 2 or 3 args, not %d", nargs);
2452 		g_lingo->dropStack(nargs);
2453 
2454 		return;
2455 	}
2456 
2457 	int ticks;
2458 	Datum secondArg = g_lingo->pop();
2459 	Datum firstArg = g_lingo->pop();
2460 	Datum verb;
2461 	if (nargs > 2) {
2462 		verb = g_lingo->pop();
2463 	} else {
2464 		verb = firstArg;
2465 		firstArg = secondArg;
2466 	}
2467 
2468 	if (verb.type != STRING && verb.type != SYMBOL) {
2469 		warning("b_sound: verb arg should be of type STRING, not %s", verb.type2str());
2470 		return;
2471 	}
2472 
2473 	DirectorSound *soundManager = g_director->getCurrentWindow()->getSoundManager();
2474 
2475 	if (verb.u.s->equalsIgnoreCase("close") || verb.u.s->equalsIgnoreCase("stop")) {
2476 		if (nargs != 2) {
2477 			warning("sound %s: expected 1 argument, got %d", verb.u.s->c_str(), nargs - 1);
2478 			return;
2479 		}
2480 
2481 		TYPECHECK(firstArg, INT);
2482 		soundManager->stopSound(firstArg.u.i);
2483 	} else if (verb.u.s->equalsIgnoreCase("fadeIn")) {
2484 		if (nargs > 2) {
2485 			TYPECHECK(secondArg, INT);
2486 			ticks = secondArg.u.i;
2487 		} else {
2488 			ticks = 15 * (60 / g_director->getCurrentMovie()->getScore()->_currentFrameRate);
2489 		}
2490 
2491 		TYPECHECK(firstArg, INT);
2492 		soundManager->registerFade(firstArg.u.i, true, ticks);
2493 		g_director->getCurrentMovie()->getScore()->_activeFade = firstArg.u.i;
2494 		return;
2495 	} else if (verb.u.s->equalsIgnoreCase("fadeOut")) {
2496 		if (nargs > 2) {
2497 			TYPECHECK(secondArg, INT);
2498 			ticks = secondArg.u.i;
2499 		} else {
2500 			ticks = 15 * (60 / g_director->getCurrentMovie()->getScore()->_currentFrameRate);
2501 		}
2502 
2503 		TYPECHECK(firstArg, INT);
2504 		soundManager->registerFade(firstArg.u.i, false, ticks);
2505 		g_director->getCurrentMovie()->getScore()->_activeFade = firstArg.u.i;
2506 		return;
2507 	} else if (verb.u.s->equalsIgnoreCase("playFile")) {
2508 		ARGNUMCHECK(3)
2509 
2510 		TYPECHECK(firstArg, INT);
2511 		TYPECHECK(secondArg, STRING);
2512 
2513 		soundManager->playFile(pathMakeRelative(*secondArg.u.s), firstArg.u.i);
2514 	} else {
2515 		warning("b_sound: unknown verb %s", verb.u.s->c_str());
2516 	}
2517 }
2518 
b_soundBusy(int nargs)2519 void LB::b_soundBusy(int nargs) {
2520 	DirectorSound *sound = g_director->getCurrentWindow()->getSoundManager();
2521 	Datum whichChannel = g_lingo->pop();
2522 
2523 	TYPECHECK(whichChannel, INT);
2524 
2525 	bool isBusy = sound->isChannelActive(whichChannel.u.i);
2526 	Datum result;
2527 	result.type = INT;
2528 	result.u.i = isBusy ? 1 : 0;
2529 	g_lingo->push(result);
2530 }
2531 
2532 ///////////////////
2533 // Constants
2534 ///////////////////
b_backspace(int nargs)2535 void LB::b_backspace(int nargs) {
2536 	g_lingo->push(Datum(Common::String("\b")));
2537 }
2538 
b_empty(int nargs)2539 void LB::b_empty(int nargs) {
2540 	g_lingo->push(Datum(Common::String("")));
2541 }
2542 
b_enter(int nargs)2543 void LB::b_enter(int nargs) {
2544 	g_lingo->push(Datum(Common::String("\03")));
2545 }
2546 
b_false(int nargs)2547 void LB::b_false(int nargs) {
2548 	g_lingo->push(Datum(0));
2549 }
2550 
b_quote(int nargs)2551 void LB::b_quote(int nargs) {
2552 	g_lingo->push(Datum(Common::String("\"")));
2553 }
2554 
b_returnconst(int nargs)2555 void LB::b_returnconst(int nargs) {
2556 	g_lingo->push(Datum(Common::String("\r")));
2557 }
2558 
b_tab(int nargs)2559 void LB::b_tab(int nargs) {
2560 	g_lingo->push(Datum(Common::String("\t")));
2561 }
2562 
b_true(int nargs)2563 void LB::b_true(int nargs) {
2564 	g_lingo->push(Datum(1));
2565 }
2566 
b_version(int nargs)2567 void LB::b_version(int nargs) {
2568 	int major = g_director->getVersion() / 100;
2569 	int minor = (g_director->getVersion() / 10) % 10;
2570 	int patch = g_director->getVersion() % 10;
2571 	Common::String res;
2572 	if (patch) {
2573 		res = Common::String::format("%d.%d.%d", major, minor, patch);
2574 	} else {
2575 		res = Common::String::format("%d.%d", major, minor);
2576 	}
2577 	g_lingo->push(res);
2578 }
2579 
2580 ///////////////////
2581 // References
2582 ///////////////////
b_cast(int nargs)2583 void LB::b_cast(int nargs) {
2584 	Datum d = g_lingo->pop();
2585 	Datum res = d.asMemberID();
2586 	res.type = CASTREF;
2587 	g_lingo->push(res);
2588 }
2589 
b_script(int nargs)2590 void LB::b_script(int nargs) {
2591 	Datum d = g_lingo->pop();
2592 	CastMemberID memberID = d.asMemberID();
2593 	CastMember *cast = g_director->getCurrentMovie()->getCastMember(memberID);
2594 
2595 	if (cast) {
2596 		ScriptContext *script = nullptr;
2597 
2598 		if (cast->_type == kCastLingoScript) {
2599 			// script cast can be either a movie script or score script
2600 			script = g_director->getCurrentMovie()->getScriptContext(kMovieScript, memberID);
2601 			if (!script)
2602 				script = g_director->getCurrentMovie()->getScriptContext(kScoreScript, memberID);
2603 		} else {
2604 			g_director->getCurrentMovie()->getScriptContext(kCastScript, memberID);
2605 		}
2606 
2607 		if (script) {
2608 			g_lingo->push(script);
2609 			return;
2610 		}
2611 	}
2612 
2613 	g_lingo->push(Datum());
2614 }
2615 
b_window(int nargs)2616 void LB::b_window(int nargs) {
2617 	Datum d = g_lingo->pop();
2618 	Common::String windowName = d.asString();
2619 	FArray *windowList = g_lingo->_windowList.u.farr;
2620 
2621 	for (uint i = 0; i < windowList->arr.size(); i++) {
2622 		if (windowList->arr[i].type != OBJECT || windowList->arr[i].u.obj->getObjType() != kWindowObj)
2623 			continue;
2624 
2625 		Window *window = static_cast<Window *>(windowList->arr[i].u.obj);
2626 		if (window->getName().equalsIgnoreCase(windowName)) {
2627 			g_lingo->push(window);
2628 			return;
2629 		}
2630 	}
2631 
2632 	Graphics::MacWindowManager *wm = g_director->getMacWindowManager();
2633 	Window *window = new Window(wm->getNextId(), false, false, false, wm, g_director, false);
2634 	window->setName(windowName);
2635 	window->setTitle(windowName);
2636 	window->resize(1, 1, true);
2637 	window->setVisible(false, true);
2638 	wm->addWindowInitialized(window);
2639 	windowList->arr.push_back(window);
2640 	g_lingo->push(window);
2641 }
2642 
b_numberofchars(int nargs)2643 void LB::b_numberofchars(int nargs) {
2644 	Datum d = g_lingo->pop();
2645 	Datum chunkRef = LC::lastChunk(kChunkChar, d);
2646 	g_lingo->push(chunkRef.u.cref->startChunk);
2647 }
2648 
b_numberofitems(int nargs)2649 void LB::b_numberofitems(int nargs) {
2650 	Datum d = g_lingo->pop();
2651 	Datum chunkRef = LC::lastChunk(kChunkItem, d);
2652 	g_lingo->push(chunkRef.u.cref->startChunk);
2653 }
2654 
b_numberoflines(int nargs)2655 void LB::b_numberoflines(int nargs) {
2656 	Datum d = g_lingo->pop();
2657 	Datum chunkRef = LC::lastChunk(kChunkLine, d);
2658 	g_lingo->push(chunkRef.u.cref->startChunk);
2659 }
2660 
b_numberofwords(int nargs)2661 void LB::b_numberofwords(int nargs) {
2662 	Datum d = g_lingo->pop();
2663 	Datum chunkRef = LC::lastChunk(kChunkWord, d);
2664 	g_lingo->push(chunkRef.u.cref->startChunk);
2665 }
2666 
b_scummvmassert(int nargs)2667 void LB::b_scummvmassert(int nargs) {
2668 	Datum line = g_lingo->pop();
2669 	Datum d = g_lingo->pop();
2670 
2671 	if (d.asInt() == 0) {
2672 		warning("LB::b_scummvmassert: is false at line %d", line.asInt());
2673 	}
2674 	assert(d.asInt() != 0);
2675 }
2676 
b_scummvmassertequal(int nargs)2677 void LB::b_scummvmassertequal(int nargs) {
2678 	Datum line = g_lingo->pop();
2679 	Datum d2 = g_lingo->pop();
2680 	Datum d1 = g_lingo->pop();
2681 
2682 	int result = d1.equalTo(d2);
2683 	if (!result) {
2684 		warning("LB::b_scummvmassertequals: %s is not equal %s at line %d", d1.asString().c_str(), d2.asString().c_str(), line.asInt());
2685 	}
2686 	assert(result == 1);
2687 }
2688 
b_getVolumes(int nargs)2689 void LB::b_getVolumes(int nargs) {
2690 	// Right now, only "Journeyman Project 2: Buried in Time" is known to check
2691 	// for its volume name.
2692 	Datum d;
2693 	d.type = ARRAY;
2694 	d.u.farr = new FArray;
2695 	d.u.farr->arr.push_back(Datum("Buried in Time\252 1"));
2696 
2697 	g_lingo->push(d);
2698 }
2699 
2700 } // End of namespace Director
2701