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