4284 lines
114 KiB
C++
4284 lines
114 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
||
*
|
||
* ScummVM is the legal property of its developers, whose names
|
||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||
* file distributed with this source distribution.
|
||
*
|
||
* This program is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation, either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*
|
||
*/
|
||
|
||
#include "common/system.h"
|
||
#include "common/translation.h"
|
||
|
||
#include "director/types.h"
|
||
#include "gui/message.h"
|
||
|
||
#include "graphics/macgui/macwindowmanager.h"
|
||
|
||
#include "director/director.h"
|
||
#include "director/cast.h"
|
||
#include "director/channel.h"
|
||
#include "director/debugger.h"
|
||
#include "director/frame.h"
|
||
#include "director/movie.h"
|
||
#include "director/score.h"
|
||
#include "director/sound.h"
|
||
#include "director/sprite.h"
|
||
#include "director/stxt.h"
|
||
#include "director/util.h"
|
||
#include "director/window.h"
|
||
#include "director/castmember/castmember.h"
|
||
#include "director/castmember/bitmap.h"
|
||
#include "director/castmember/palette.h"
|
||
#include "director/castmember/text.h"
|
||
#include "director/castmember/transition.h"
|
||
#include "director/lingo/lingo-builtins.h"
|
||
#include "director/lingo/lingo-code.h"
|
||
#include "director/lingo/lingo-codegen.h"
|
||
#include "director/lingo/lingo-utils.h"
|
||
|
||
#include "image/pict.h"
|
||
|
||
namespace Director {
|
||
|
||
static const BuiltinProto builtins[] = {
|
||
// Math
|
||
{ "abs", LB::b_abs, 1, 1, 200, FBLTIN }, // D2 function
|
||
{ "atan", LB::b_atan, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "cos", LB::b_cos, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "exp", LB::b_exp, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "float", LB::b_float, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "integer", LB::b_integer, 1, 1, 300, FBLTIN }, // D3 f
|
||
{ "log", LB::b_log, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "pi", LB::b_pi, 0, 0, 400, FBLTIN }, // D4 f
|
||
{ "power", LB::b_power, 2, 2, 400, FBLTIN }, // D4 f
|
||
{ "random", LB::b_random, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "sin", LB::b_sin, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "sqrt", LB::b_sqrt, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "tan", LB::b_tan, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "void", LB::b_void, 0, 0, 600, FBLTIN }, // D6 f
|
||
// String
|
||
{ "chars", LB::b_chars, 3, 3, 200, FBLTIN }, // D2 f
|
||
{ "charToNum", LB::b_charToNum, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "length", LB::b_length, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "numToChar", LB::b_numToChar, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "offset", LB::b_offset, 2, 3, 200, FBLTIN }, // D2 f
|
||
{ "string", LB::b_string, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "value", LB::b_value, 1, 1, 200, FBLTIN }, // D2 f
|
||
// Lists
|
||
{ "add", LB::b_add, 2, 2, 400, HBLTIN_LIST }, // D4 handler
|
||
{ "addAt", LB::b_addAt, 3, 3, 400, HBLTIN_LIST }, // D4 h
|
||
{ "addProp", LB::b_addProp, 3, 3, 400, HBLTIN_LIST }, // D4 h
|
||
{ "append", LB::b_append, 2, 2, 400, HBLTIN_LIST }, // D4 h
|
||
{ "count", LB::b_count, 1, 1, 400, FBLTIN_LIST }, // D4 f
|
||
{ "deleteAt", LB::b_deleteAt, 2, 2, 400, HBLTIN_LIST }, // D4 h
|
||
{ "deleteOne", LB::b_deleteOne, 2, 2, 400, HBLTIN_LIST }, // D4 h, documented in D5
|
||
{ "deleteProp", LB::b_deleteProp, 2, 2, 400, HBLTIN_LIST }, // D4 h
|
||
{ "duplicate", LB::b_duplicateList,1, 1, 500, FBLTIN_LIST }, // D5 f
|
||
{ "findPos", LB::b_findPos, 2, 2, 400, FBLTIN_LIST }, // D4 f
|
||
{ "findPosNear", LB::b_findPosNear, 2, 2, 400, FBLTIN_LIST }, // D4 f
|
||
{ "getaProp", LB::b_getaProp, 2, 2, 400, FBLTIN_LIST }, // D4 f
|
||
{ "getAt", LB::b_getAt, 2, 2, 400, FBLTIN_LIST }, // D4 f
|
||
{ "getLast", LB::b_getLast, 1, 1, 400, FBLTIN_LIST }, // D4 f
|
||
{ "getOne", LB::b_getOne, 2, 2, 400, FBLTIN_LIST }, // D4 f
|
||
{ "getPos", LB::b_getPos, 2, 2, 400, FBLTIN_LIST }, // D4 f
|
||
{ "getProp", LB::b_getProp, 2, 2, 400, FBLTIN_LIST }, // D4 f
|
||
{ "getPropAt", LB::b_getPropAt, 2, 2, 400, FBLTIN_LIST }, // D4 f
|
||
{ "list", LB::b_list, -1,0, 400, FBLTIN_LIST }, // D4 f
|
||
{ "listP", LB::b_listP, 1, 1, 400, FBLTIN_LIST }, // D4 f
|
||
{ "max", LB::b_max, -1,0, 400, FBLTIN_LIST }, // D4 f
|
||
{ "min", LB::b_min, -1,0, 400, FBLTIN_LIST }, // D4 f
|
||
{ "setaProp", LB::b_setaProp, 3, 3, 400, HBLTIN_LIST }, // D4 h
|
||
{ "setAt", LB::b_setAt, 3, 3, 400, HBLTIN_LIST }, // D4 h
|
||
{ "setProp", LB::b_setProp, 3, 3, 400, HBLTIN_LIST }, // D4 h
|
||
{ "sort", LB::b_sort, 1, 1, 400, HBLTIN_LIST }, // D4 h
|
||
// Files
|
||
{ "closeDA", LB::b_closeDA, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "closeResFile", LB::b_closeResFile, 0, 1, 200, CBLTIN }, // D2 c
|
||
{ "closeXlib", LB::b_closeXlib, 0, 1, 200, CBLTIN }, // D2 c
|
||
{ "getNthFileNameInFolder",LB::b_getNthFileNameInFolder,2,2,400,FBLTIN }, //D4 f
|
||
{ "open", LB::b_open, 1, 2, 200, CBLTIN }, // D2 c
|
||
{ "openDA", LB::b_openDA, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "openResFile", LB::b_openResFile, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "openXlib", LB::b_openXlib, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "save", LB::b_save, 1, 1, 500, CBLTIN }, // D5 c
|
||
{ "saveMovie", LB::b_saveMovie, 0, 1, 400, CBLTIN }, // D4 c
|
||
{ "setCallBack", LB::b_setCallBack, 2, 2, 200, CBLTIN }, // D2 c
|
||
{ "showResFile", LB::b_showResFile, 0, 1, 200, CBLTIN }, // D2 c
|
||
{ "showXlib", LB::b_showXlib, 0, 1, 200, CBLTIN }, // D2 c
|
||
{ "xFactoryList", LB::b_xFactoryList, 1, 1, 300, FBLTIN }, // D3 f
|
||
{ "xtra", LB::b_xtra, 1, 1, 500, FBLTIN }, // D5 f
|
||
// Control
|
||
{ "abort", LB::b_abort, 0, 0, 400, CBLTIN }, // D4 c
|
||
{ "cancelIdleLoad", LB::b_cancelIdleLoad,1,1, 500, CBLTIN }, // D5 c
|
||
{ "call", LB::b_call, -1,0, 600, CBLTIN }, // D6 c
|
||
{ "callAncestor", LB::b_callAncestor, -1,0, 600, CBLTIN }, // D6 c
|
||
{ "continue", LB::b_continue, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "dontPassEvent", LB::b_dontPassEvent,0, 0, 200, CBLTIN }, // D2 c
|
||
{ "delay", LB::b_delay, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "do", LB::b_do, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "finishIdleLoad", LB::b_finishIdleLoad,1,1, 500, CBLTIN }, // D5 c
|
||
{ "go", LB::b_go, 1, 2, 200, CBLTIN }, // D2 c
|
||
{ "halt", LB::b_halt, 0, 0, 400, CBLTIN }, // D4 c
|
||
{ "idleLoadDone", LB::b_idleLoadDone, 1, 1, 500, FBLTIN }, // D5 f
|
||
{ "nothing", LB::b_nothing, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "pass", LB::b_pass, 0, 0, 400, CBLTIN }, // D4 c
|
||
{ "pause", LB::b_pause, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "play", LB::b_play, 0, 2, 200, CBLTIN }, // D2 c
|
||
{ "playAccel", LB::b_playAccel, -1,0, 200, CBLTIN }, // D2
|
||
// play done // D2
|
||
{ "preLoad", LB::b_preLoad, -1,0, 300, CBLTIN }, // D3.1 c
|
||
{ "preLoadCast", LB::b_preLoadCast, -1,0, 300, CBLTIN }, // D3.1 c
|
||
{ "preLoadMember", LB::b_preLoadCast, -1,0, 500, CBLTIN }, // D5 c
|
||
{ "preLoadMovie", LB::b_preLoadMovie, 1, 1, 500, CBLTIN }, // D5 c
|
||
{ "quit", LB::b_quit, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "restart", LB::b_restart, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "return", LB::b_return, 0, 1, 200, CBLTIN }, // D2 f
|
||
{ "send", LB::b_call, -1,0, 400, CBLTIN }, // D4 c, undocumented
|
||
{ "sendAncestor", LB::b_callAncestor, -1,0, 400, CBLTIN }, // D4 c, undocumented
|
||
{ "shutDown", LB::b_shutDown, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "startTimer", LB::b_startTimer, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "stopEvent", LB::b_stopEvent, 0, 0, 600, CBLTIN }, // D6 c
|
||
// when keyDown // D2
|
||
// when mouseDown // D2
|
||
// when mouseUp // D2
|
||
// when timeOut // D2
|
||
// Types
|
||
{ "factory", LB::b_factory, 1, 1, 300, FBLTIN }, // D3
|
||
{ "floatP", LB::b_floatP, 1, 1, 300, FBLTIN }, // D3
|
||
{ "ilk", LB::b_ilk, 1, 2, 400, FBLTIN }, // D4 f
|
||
{ "integerp", LB::b_integerp, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "objectp", LB::b_objectp, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "pictureP", LB::b_pictureP, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "stringp", LB::b_stringp, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "symbolp", LB::b_symbolp, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "symbol", LB::b_symbol, 1, 1, 600, FBLTIN }, // D6 f
|
||
{ "voidP", LB::b_voidP, 1, 1, 400, FBLTIN }, // D4 f
|
||
// Misc
|
||
{ "alert", LB::b_alert, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "clearGlobals", LB::b_clearGlobals, 0, 0, 300, CBLTIN }, // D3.1 c
|
||
{ "cursor", LB::b_cursor, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "framesToHMS", LB::b_framesToHMS, 4, 4, 300, FBLTIN }, // D3 f
|
||
{ "HMStoFrames", LB::b_HMStoFrames, 4, 4, 300, FBLTIN }, // D3 f
|
||
{ "param", LB::b_param, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "printFrom", LB::b_printFrom, -1,0, 200, CBLTIN }, // D2 c
|
||
{ "put", LB::b_put, -1,0, 200, CBLTIN }, // D2
|
||
// set // D2
|
||
{ "setPref", LB::b_setPref, 2, 2, 600, CBLTIN }, // D6 c
|
||
{ "showGlobals", LB::b_showGlobals, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "showLocals", LB::b_showLocals, 0, 0, 200, CBLTIN }, // D2 c
|
||
// Score
|
||
{ "constrainH", LB::b_constrainH, 2, 2, 200, FBLTIN }, // D2 f
|
||
{ "constrainV", LB::b_constrainV, 2, 2, 200, FBLTIN }, // D2 f
|
||
{ "copyToClipBoard",LB::b_copyToClipBoard,1,1,400, CBLTIN }, // D4 c
|
||
{ "duplicate", LB::b_duplicate, 1, 2, 400, CBLTIN }, // D4 c
|
||
{ "editableText", LB::b_editableText, 0, 0, 200, CBLTIN }, // D2
|
||
{ "erase", LB::b_erase, 1, 1, 400, CBLTIN }, // D4 c
|
||
{ "findEmpty", LB::b_findEmpty, 1, 1, 400, FBLTIN }, // D4 f
|
||
// go // D2
|
||
{ "importFileInto", LB::b_importFileInto,2,2, 400, CBLTIN }, // D4 c
|
||
{ "installMenu", LB::b_installMenu, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "label", LB::b_label, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "marker", LB::b_marker, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "move", LB::b_move, 1, 2, 400, CBLTIN }, // D4 c
|
||
{ "moveableSprite", LB::b_moveableSprite,0, 0, 200, CBLTIN }, // D2, FIXME: the field in D4+
|
||
{ "pasteClipBoardInto",LB::b_pasteClipBoardInto,1,1,400,CBLTIN },// D4 c
|
||
{ "puppetPalette", LB::b_puppetPalette, -1,0, 200, CBLTIN }, // D2 c
|
||
{ "puppetSound", LB::b_puppetSound, -1,0, 200, CBLTIN }, // D2 c
|
||
{ "puppetSprite", LB::b_puppetSprite, -1,0, 200, CBLTIN }, // D2 c
|
||
{ "puppetTempo", LB::b_puppetTempo, 1, 1, 200, CBLTIN }, // D2 c
|
||
{ "puppetTransition",LB::b_puppetTransition,-1,0,200, CBLTIN }, // D2 c
|
||
{ "ramNeeded", LB::b_ramNeeded, 2, 2, 300, FBLTIN }, // D3.1 f
|
||
{ "rollOver", LB::b_rollOver, 1, 1, 200, FBLTIN }, // D2 f
|
||
{ "sendAllSprites", LB::b_sendAllSprites,-1,0,600, CBLTIN }, // D6 c
|
||
{ "sendSprite", LB::b_sendSprite, -1,0, 600, CBLTIN }, // D6 c
|
||
{ "spriteBox", LB::b_spriteBox, 5, 5, 200, CBLTIN }, // D2 c
|
||
{ "unLoad", LB::b_unLoad, 0, 2, 300, CBLTIN }, // D3.1 c
|
||
{ "unLoadCast", LB::b_unLoadCast, 0, 2, 300, CBLTIN }, // D3.1 c
|
||
{ "unLoadMember", LB::b_unLoadCast, 0, 2, 500, CBLTIN }, // D5 c
|
||
{ "unLoadMovie", LB::b_unLoadMovie, 1, 1, 500, CBLTIN }, // D5 c
|
||
{ "updateStage", LB::b_updateStage, 0, 0, 200, CBLTIN }, // D2 c
|
||
{ "zoomBox", LB::b_zoomBox, -1,0, 200, CBLTIN }, // D2 c
|
||
{"immediateSprite", LB::b_immediateSprite,-1,0,200,CBLTIN }, // D2 c
|
||
// Score recording
|
||
{ "clearFrame", LB::b_clearFrame, 0, 0, 500, CBLTIN }, // D5 c
|
||
{ "deleteFrame", LB::b_deleteFrame, 0, 0, 500, CBLTIN }, // D5 c
|
||
{ "duplicateFrame", LB::b_duplicateFrame,0,0, 500, CBLTIN }, // D5 c
|
||
{ "insertFrame", LB::b_insertFrame, 0, 0, 500, CBLTIN }, // D5 c
|
||
{ "updateFrame", LB::b_updateFrame, 0, 0, 500, CBLTIN }, // D5 c
|
||
// Point
|
||
{ "point", LB::b_point, 2, 2, 400, FBLTIN }, // D4 f
|
||
// Rect
|
||
{ "inflate", LB::b_inflate, 2, 3, 400, FBLTIN }, // D4 f
|
||
{ "inside", LB::b_inside, 2, 2, 400, FBLTIN }, // D4 f
|
||
{ "intersect", LB::b_intersect, 2, 2, 400, FBLTIN }, // D4 f
|
||
{ "map", LB::b_map, 3, 3, 400, FBLTIN }, // D4 f
|
||
{ "rect", LB::b_rect, 2, 4, 400, FBLTIN }, // D4 f
|
||
{ "union", LB::b_union, 2, 2, 400, FBLTIN }, // D4 f
|
||
// Sound
|
||
{ "beep", LB::b_beep, 0, 1, 200, CBLTIN }, // D2
|
||
{ "isPastCuePoint",LB::b_isPastCuePoint,2, 2, 600, FBLTIN }, // D6 f
|
||
{ "mci", LB::b_mci, 1, 1, 300, CBLTIN }, // D3.1 c
|
||
{ "mciwait", LB::b_mciwait, 1, 1, 300, CBLTIN }, // D3.1 c
|
||
{ "sound", LB::b_sound, 2, 3, 300, CBLTIN }, // D3 c
|
||
{ "soundBusy", LB::b_soundBusy, 1, 1, 300, FBLTIN }, // D3 f
|
||
// Constants
|
||
{ "backspace", LB::b_backspace, 0, 0, 200, KBLTIN }, // D2 konstant
|
||
{ "empty", LB::b_empty, 0, 0, 200, KBLTIN }, // D2 k
|
||
{ "enter", LB::b_enter, 0, 0, 200, KBLTIN }, // D2 k
|
||
{ "false", LB::b_false, 0, 0, 200, KBLTIN }, // D2 k
|
||
{ "quote", LB::b_quote, 0, 0, 200, KBLTIN }, // D2 k
|
||
{ "return", LB::b_returnconst, 0, 0, 200, KBLTIN }, // D2 k
|
||
{ "tab", LB::b_tab, 0, 0, 200, KBLTIN }, // D2 k
|
||
{ "true", LB::b_true, 0, 0, 200, KBLTIN }, // D2 k
|
||
{ "version", LB::b_version, 0, 0, 300, KBLTIN }, // D3 k
|
||
// References
|
||
{ "cast", LB::b_cast, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "castLib", LB::b_castLib, 1, 1, 500, FBLTIN }, // D5 f
|
||
{ "member", LB::b_member, 1, 2, 500, FBLTIN }, // D5 f
|
||
{ "script", LB::b_script, 1, 2, 400, FBLTIN }, // D4 f
|
||
{ "sprite", LB::b_sprite, 1, 1, 500, FBLTIN }, // D5 f
|
||
{ "window", LB::b_window, 1, 1, 400, FBLTIN }, // D4 f
|
||
{ "windowPresent", LB::b_windowPresent,1, 1, 500, FBLTIN }, // D5 f
|
||
// Field operations
|
||
{ "charPosToLoc", LB::b_charPosToLoc, 2, 2, 500, FBLTIN }, // D5 f
|
||
{ "lineHeight", LB::b_lineHeight, 2, 2, 500, FBLTIN }, // D5 f
|
||
{ "linePosToLocV", LB::b_linePosToLocV,2, 2, 500, FBLTIN }, // D5 f
|
||
{ "locToCharPos", LB::b_locToCharPos, 2, 2, 500, FBLTIN }, // D5 f
|
||
{ "locVToLinePos", LB::b_locVToLinePos,2, 2, 500, FBLTIN }, // D5 f
|
||
{ "scrollByLine", LB::b_scrollByLine, 2, 2, 500, CBLTIN }, // D5 c
|
||
{ "scrollByPage", LB::b_scrollByPage, 2, 2, 500, CBLTIN }, // D5 c
|
||
// Chunk operations
|
||
{ "numberOfChars", LB::b_numberofchars,1, 1, 300, FBLTIN }, // D3 f
|
||
{ "numberOfItems", LB::b_numberofitems,1, 1, 300, FBLTIN }, // D3 f
|
||
{ "numberOfLines", LB::b_numberoflines,1, 1, 300, FBLTIN }, // D3 f
|
||
{ "numberOfWords", LB::b_numberofwords,1, 1, 300, FBLTIN }, // D3 f
|
||
// Digital video operations
|
||
{ "trackCount", LB::b_trackCount, 1, 1, 500, FBLTIN }, // D5 f
|
||
{ "trackStartTime", LB::b_trackStartTime,1,1, 500, FBLTIN }, // D5 f
|
||
{ "trackStopTime", LB::b_trackStopTime,1, 1, 500, FBLTIN }, // D5 f
|
||
{ "trackType", LB::b_trackType, 1, 1, 500, FBLTIN }, // D5 f
|
||
|
||
// Save session
|
||
{ "beginRecording", LB::b_beginRecording,0, 1, 500, CBLTIN }, // D5 c
|
||
{ "endRecording", LB::b_endRecording, 0, 0, 500, CBLTIN }, // D5 c
|
||
|
||
// Shockwave
|
||
{ "externalParamCount", LB::b_externalParamCount, 0, 0, 600, FBLTIN }, // D6 f
|
||
{ "externalParamName", LB::b_externalParamName, 1, 1, 600, FBLTIN }, // D6 f
|
||
{ "externalParamValue", LB::b_externalParamValue, 1, 1, 600, FBLTIN }, // D6 f
|
||
{ "frameReady", LB::b_frameReady, 0, 2, 600, FBLTIN }, // D6 f
|
||
{ "getPref", LB::b_getPref, 1, 1, 600, FBLTIN }, // D6 f
|
||
|
||
// ScummVM Asserts: Used for testing ScummVM's Lingo implementation
|
||
{ "scummvmAssert", LB::b_scummvmassert,1, 2, 200, HBLTIN },
|
||
{ "scummvmAssertEqual", LB::b_scummvmassertequal,2,3,200,HBLTIN },
|
||
{ "scummvmNoFatalError", LB::b_scummvmNoFatalError,1,1,200,HBLTIN },
|
||
|
||
// XCMD/XFCN (HyperCard), normally exposed
|
||
{ "GetVolumes", LB::b_getVolumes, 0, 0, 400, FBLTIN },
|
||
|
||
{ nullptr, nullptr, 0, 0, 0, VOIDSYM }
|
||
};
|
||
|
||
/* These are related to Director Services API, used by Xtras
|
||
to talk to Director. Unused in ScummVM. Leaving here for reference.
|
||
|
||
Media Info:
|
||
composite // D5
|
||
editableMedia // D7
|
||
image // D5
|
||
palette // D5
|
||
sound // D5
|
||
score // D5
|
||
scriptStyles // D6
|
||
text // D5
|
||
textSyles // D5
|
||
|
||
Media Format:
|
||
macColorTable // D5
|
||
macGWorld // D5
|
||
macPICT // D5
|
||
macSnd // D5
|
||
macTEStyles // D5
|
||
moaHandle // D5
|
||
moaPixels // D5
|
||
moaSound // D5
|
||
moaTEStyles // D5
|
||
winDIB // D5
|
||
winPALETTE // D5
|
||
winPICT // D5
|
||
winWAVE // D5
|
||
|
||
Frame properties:
|
||
palette // D5
|
||
paletteFrames // D5
|
||
paletteOverTime // D5
|
||
paletteRef // D5
|
||
paletteSpeed // D5
|
||
paletteTransitionType// D5
|
||
script // D5
|
||
tempo // D5
|
||
transition // D5
|
||
waitClick // D5
|
||
waitDigitalVideo // D5
|
||
waitSeconds // D5
|
||
waitSound // D5
|
||
|
||
Palette properties:
|
||
fadeToBlack // D5
|
||
fadeToWhite // D5
|
||
normal // D5
|
||
|
||
Sound properties:
|
||
member // D5
|
||
scoreColor // D5
|
||
|
||
Sprite properties:
|
||
member // D5
|
||
scoreColor // D5
|
||
script // D5
|
||
scriptNum // D5
|
||
size // D5
|
||
loc // D5
|
||
foreColor // D5
|
||
color // D5
|
||
backColor // D5
|
||
bgColor // D7
|
||
ink // D5
|
||
trails // D5
|
||
moveableSprite // D5
|
||
editableText // D5
|
||
blend // D5
|
||
stretch // D5
|
||
tweened // D6
|
||
|
||
General properties:
|
||
authorMode // D5
|
||
folderName // D5
|
||
maxMember // D5
|
||
minMember // D5
|
||
memberCount // D5
|
||
modified // D5
|
||
name // D5
|
||
pathName // D5
|
||
preloadMode // D8
|
||
selectiomn // D5
|
||
soundDevice // D7
|
||
version // D5
|
||
|
||
Movie properties:
|
||
active3dRenderer // D8.5
|
||
activeCast // D5
|
||
activeCastLib // D5
|
||
bgStageColor // D7
|
||
castCount // D5
|
||
createName // D5
|
||
defaultColorDepth // D5
|
||
defaultPalette // D5
|
||
defaultStageRect // D5
|
||
editShortcutsEnabled // D8
|
||
enableFlashLingo // D8.5
|
||
enableInkmodeLimitations// D8
|
||
frame // D5
|
||
instance // D5
|
||
lastChannel // D7
|
||
modified // D5
|
||
modifyName // D5
|
||
movieAboutInfo // D7
|
||
movieCopyrightInfo // D7
|
||
movieFileVersion // D8
|
||
movieImageCompression// D8
|
||
movieImageQuality // D8
|
||
name // D5
|
||
okToQueryKeyboard // D8.5
|
||
pathName // D5
|
||
playing // D5
|
||
preferred3dRenderer // D8.5
|
||
remapPalettes // D5
|
||
scriptExecutionStyle // D10
|
||
safePlayer // D6
|
||
scoreSelection // D5
|
||
stageColor // D5
|
||
tempo // D5
|
||
tempoScaleFactor // D7
|
||
urlAdmin // D7
|
||
version // D5
|
||
|
||
Time Frame Prop:
|
||
label // D5
|
||
palette // D5
|
||
script // D5
|
||
tempo // D5
|
||
transition // D5
|
||
|
||
Host Info:
|
||
appFileSpec // D5
|
||
|
||
Types:
|
||
bitmap
|
||
button
|
||
digitalVideo
|
||
field
|
||
filmLoop
|
||
movie
|
||
ole
|
||
palette
|
||
richText
|
||
shape
|
||
script
|
||
sound
|
||
transition
|
||
*/
|
||
|
||
void Lingo::initBuiltIns() {
|
||
initBuiltIns(builtins);
|
||
}
|
||
|
||
void Lingo::initBuiltIns(const BuiltinProto protos[]) {
|
||
for (const BuiltinProto *blt = protos; blt->name; blt++) {
|
||
if (blt->version > _vm->getVersion())
|
||
continue;
|
||
|
||
Symbol sym;
|
||
|
||
sym.name = new Common::String(blt->name);
|
||
sym.type = blt->type;
|
||
sym.nargs = blt->minArgs;
|
||
sym.maxArgs = blt->maxArgs;
|
||
|
||
sym.u.bltin = blt->func;
|
||
|
||
switch (blt->type) {
|
||
case CBLTIN:
|
||
_builtinCmds[blt->name] = sym;
|
||
break;
|
||
case FBLTIN_LIST:
|
||
_builtinListHandlers[blt->name] = sym; // fall-through
|
||
case FBLTIN:
|
||
_builtinFuncs[blt->name] = sym;
|
||
break;
|
||
case HBLTIN_LIST:
|
||
_builtinListHandlers[blt->name] = sym; // fall-through
|
||
case HBLTIN:
|
||
_builtinCmds[blt->name] = sym;
|
||
_builtinFuncs[blt->name] = sym;
|
||
break;
|
||
case KBLTIN:
|
||
_builtinConsts[blt->name] = sym;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
void Lingo::cleanupBuiltIns() {
|
||
_builtinCmds.clear();
|
||
_builtinFuncs.clear();
|
||
_builtinConsts.clear();
|
||
}
|
||
|
||
void Lingo::cleanupBuiltIns(const BuiltinProto protos[]) {
|
||
for (const BuiltinProto *blt = protos; blt->name; blt++) {
|
||
switch (blt->type) {
|
||
case CBLTIN:
|
||
_builtinCmds.erase(blt->name);
|
||
break;
|
||
case FBLTIN:
|
||
_builtinFuncs.erase(blt->name);
|
||
break;
|
||
case HBLTIN:
|
||
_builtinCmds.erase(blt->name);
|
||
_builtinFuncs.erase(blt->name);
|
||
break;
|
||
case KBLTIN:
|
||
_builtinConsts.erase(blt->name);
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
void Lingo::printArgs(const char *funcname, int nargs, const char *prefix) {
|
||
Common::String s;
|
||
if (prefix)
|
||
s += Common::String(prefix);
|
||
|
||
s += Common::String(funcname);
|
||
s += '(';
|
||
|
||
for (int i = 0; i < nargs; i++) {
|
||
Datum d = _state->stack[_state->stack.size() - nargs + i];
|
||
|
||
s += d.asString(true);
|
||
|
||
if (i != nargs - 1)
|
||
s += ", ";
|
||
}
|
||
|
||
s += ")";
|
||
|
||
debug(3, "%s", s.c_str());
|
||
}
|
||
|
||
void Lingo::convertVOIDtoString(int arg, int nargs) {
|
||
if (_state->stack[_state->stack.size() - nargs + arg].type == VOID) {
|
||
if (_state->stack[_state->stack.size() - nargs + arg].u.s != nullptr)
|
||
g_lingo->_state->stack[_state->stack.size() - nargs + arg].type = STRING;
|
||
else
|
||
warning("Incorrect convertVOIDtoString for arg %d of %d", arg, nargs);
|
||
}
|
||
}
|
||
|
||
void Lingo::dropStack(int nargs) {
|
||
for (int i = 0; i < nargs; i++)
|
||
pop();
|
||
}
|
||
|
||
void Lingo::drop(uint num) {
|
||
if (num > _state->stack.size() - 1) {
|
||
warning("Incorrect number of elements to drop from stack: %d > %d", num, _state->stack.size() - 1);
|
||
return;
|
||
}
|
||
_state->stack.remove_at(_state->stack.size() - 1 - num);
|
||
}
|
||
|
||
|
||
///////////////////
|
||
// Math
|
||
///////////////////
|
||
void LB::b_abs(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(0);
|
||
|
||
if (d.type == INT)
|
||
res = Datum(ABS(d.u.i));
|
||
else if (d.type == FLOAT)
|
||
res = Datum(ABS(d.u.f));
|
||
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_atan(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(atan(d.asFloat()));
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_cos(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(cos(d.asFloat()));
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_exp(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
// Lingo uses int, so we're enforcing it
|
||
Datum res((double)exp((double)d.asInt()));
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_float(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res;
|
||
|
||
if (d.type == STRING) {
|
||
Common::String src = d.asString();
|
||
char *endPtr = nullptr;
|
||
double result = strtod(src.c_str(), &endPtr);
|
||
if (*endPtr == 0) {
|
||
res = result;
|
||
} else {
|
||
// for some reason, float(str) will return str if it doesn't work
|
||
res = d;
|
||
}
|
||
} else if (d.type == INT || d.type == FLOAT) {
|
||
res = d.asFloat();
|
||
} else {
|
||
warning("b_float: Attempted to process invalid type %s, returning same value", d.type2str());
|
||
res = d;
|
||
}
|
||
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_integer(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res;
|
||
|
||
if (d.type == FLOAT) {
|
||
if (g_director->getVersion() < 500) { // Note that D4 behaves differently from asInt()
|
||
res = (int)(d.u.f + 0.5); // Yes, +0.5 even for negative numbers
|
||
} else {
|
||
res = (int)round(d.u.f);
|
||
}
|
||
} else if (d.type == STRING) {
|
||
Common::String src = d.asString();
|
||
char *endPtr = nullptr;
|
||
int result = (int)strtol(src.c_str(), &endPtr, 10);
|
||
// Stop conditions found by probing D4 for windows:
|
||
// repeat with i = 0 to 255
|
||
// put i & " = " & integer("12345" & numToChar(i))
|
||
// end repeat
|
||
if (endPtr && endPtr != src.c_str() && (
|
||
(*endPtr >= 0 && *endPtr < 45) ||
|
||
(*endPtr == 47) ||
|
||
(*endPtr >= 58 && *endPtr < 65) ||
|
||
(*endPtr >= 91 && *endPtr < 95) ||
|
||
(*endPtr == 96) ||
|
||
(*endPtr >= 123))
|
||
) {
|
||
res = result;
|
||
}
|
||
} else {
|
||
res = d.asInt();
|
||
}
|
||
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_log(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(log(d.asFloat()));
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_pi(int nargs) {
|
||
Datum res((double)M_PI);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_power(int nargs) {
|
||
Datum d1 = g_lingo->pop();
|
||
Datum d2 = g_lingo->pop();
|
||
Datum res(pow(d2.asFloat(), d1.asFloat()));
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_random(int nargs) {
|
||
int max = g_lingo->pop().asInt();
|
||
Datum res;
|
||
// Output in D4/D5 seems to be bounded from 1-65535, regardless of input.
|
||
if (max <= 0) {
|
||
res = Datum((int)(g_director->_rnd.getRandom(65535) + 1));
|
||
} else {
|
||
max = MIN(max, 65535);
|
||
res = Datum((int)(g_director->_rnd.getRandom(max) + 1));
|
||
}
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_sin(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(sin(d.asFloat()));
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_sqrt(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res;
|
||
if (d.type == INT) {
|
||
// integer input returns the sqrt rounded to the nearest int
|
||
res = Datum((int)round(sqrt(d.asInt())));
|
||
} else if (d.type == FLOAT) {
|
||
// float input returns float output
|
||
res = Datum(sqrt(d.asFloat()));
|
||
} else if (d.type == STRING) {
|
||
// string input attempts to coerce to float, else crash
|
||
Common::String src = d.asString();
|
||
char *endPtr = nullptr;
|
||
double result = strtod(src.c_str(), &endPtr);
|
||
if (*endPtr == 0) {
|
||
res = Datum(sqrt(result));
|
||
} else {
|
||
g_lingo->lingoError("b_sqrt: Invalid string");
|
||
}
|
||
} else if (d.type == VOID) {
|
||
// void input returns float 0.0
|
||
res = Datum(0.0);
|
||
} else {
|
||
g_lingo->lingoError("b_sqrt: Invalid type");
|
||
}
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_tan(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(tan(d.asFloat()));
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_void(int nargs) {
|
||
g_lingo->pushVoid();
|
||
}
|
||
|
||
///////////////////
|
||
// String
|
||
///////////////////
|
||
void LB::b_chars(int nargs) {
|
||
Datum d3 = g_lingo->pop();
|
||
Datum d2 = g_lingo->pop();
|
||
Datum s = g_lingo->pop();
|
||
TYPECHECK(s, STRING);
|
||
|
||
if (g_director->getVersion() < 400 && (d2.type == FLOAT || d3.type == FLOAT)) {
|
||
warning("LB::b_chars: Called with a float in Director 2 and 3 mode. chars' can't handle floats");
|
||
g_lingo->push(Datum(0));
|
||
return;
|
||
}
|
||
|
||
int to = d3.asInt();
|
||
int from = d2.asInt();
|
||
|
||
Common::U32String src = s.asString().decode(Common::kUtf8);
|
||
|
||
int len = src.size();
|
||
int f = MAX(0, MIN(len, from - 1));
|
||
int t = MAX(0, MIN(len, to));
|
||
|
||
Common::String res;
|
||
if (f > t) {
|
||
res = Common::String("");
|
||
} else {
|
||
res = src.substr(f, t - f).encode(Common::kUtf8);
|
||
}
|
||
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_charToNum(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
TYPECHECK(d, STRING);
|
||
|
||
Common::U32String str = d.asString().decode(Common::kUtf8);
|
||
if (str.size() == 0) {
|
||
g_lingo->push(0);
|
||
return;
|
||
}
|
||
|
||
g_lingo->push(charToNum(str[0]));
|
||
}
|
||
|
||
void LB::b_length(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
if (d.type == INT || d.type == FLOAT || d.type == VOID) {
|
||
g_lingo->push(0);
|
||
return;
|
||
}
|
||
TYPECHECK(d, STRING);
|
||
|
||
Common::U32String src = d.asString().decode(Common::kUtf8);
|
||
int res = src.size();
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_numToChar(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
if (g_director->getVersion() < 400) {
|
||
TYPECHECK(d, INT);
|
||
} else if (d.type != INT) {
|
||
warning("BUILDBOT: b_numToChar: Unimplemented behaviour for arg of type %s", (d).type2str());
|
||
return;
|
||
}
|
||
|
||
int num = d.asInt();
|
||
g_lingo->push(Common::U32String(numToChar(num)).encode(Common::kUtf8));
|
||
}
|
||
|
||
void LB::b_offset(int nargs) {
|
||
if (nargs == 3) {
|
||
b_offsetRect(nargs);
|
||
return;
|
||
}
|
||
Common::String source = g_lingo->pop().asString();
|
||
Common::String target = g_lingo->pop().asString();
|
||
|
||
const char *str = d_strstr(source.c_str(), target.c_str());
|
||
|
||
if (str == nullptr)
|
||
g_lingo->push(Datum(0));
|
||
else
|
||
g_lingo->push(Datum(int(str - source.c_str() + 1)));
|
||
}
|
||
|
||
void LB::b_string(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(d.asString());
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_value(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
if (d.type != STRING) {
|
||
g_lingo->push(d);
|
||
return;
|
||
}
|
||
|
||
Common::String expr = d.asString();
|
||
expr.trim();
|
||
if (expr.empty()) {
|
||
g_lingo->push(Datum(0));
|
||
return;
|
||
}
|
||
Common::String code = "return " + expr;
|
||
// Compile the code to an anonymous function and call it
|
||
ScriptContext *sc = g_lingo->_compiler->compileAnonymous(code, kLPPTrimGarbage);
|
||
if (!sc) {
|
||
warning("b_value(): Failed to parse expression \"%s\", returning void", expr.c_str());
|
||
g_lingo->pushVoid();
|
||
return;
|
||
}
|
||
Symbol sym = sc->_eventHandlers[kEventGeneric];
|
||
LC::call(sym, 0, true);
|
||
}
|
||
|
||
///////////////////
|
||
// Lists
|
||
///////////////////
|
||
void LB::b_add(int nargs) {
|
||
Datum value = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
TYPECHECK(list, ARRAY);
|
||
|
||
// If the list is sorted, keep the sort
|
||
if (list.u.farr->_sorted) {
|
||
if (list.u.farr->arr.empty()) {
|
||
list.u.farr->arr.push_back(value);
|
||
} else {
|
||
// TODO: We'd better do a binary search here
|
||
uint pos = list.u.farr->arr.size();
|
||
for (uint i = 0; i < list.u.farr->arr.size(); i++) {
|
||
if (list.u.farr->arr[i] > value) { // We are using Datum::compareTo() here
|
||
pos = i;
|
||
break;
|
||
}
|
||
}
|
||
list.u.farr->arr.insert_at(pos, value);
|
||
}
|
||
} else {
|
||
list.u.farr->arr.push_back(value);
|
||
list.u.farr->_sorted = false; // Drop the sorted flag
|
||
}
|
||
}
|
||
|
||
void LB::b_addAt(int nargs) {
|
||
Datum value = g_lingo->pop();
|
||
Datum indexD = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
TYPECHECK2(indexD, INT, FLOAT);
|
||
int index = indexD.asInt();
|
||
TYPECHECK(list, ARRAY);
|
||
|
||
int size = list.u.farr->arr.size();
|
||
if (index > size) {
|
||
for (int i = 0; i < index - size - 1; i++)
|
||
list.u.farr->arr.push_back(Datum(0));
|
||
}
|
||
list.u.farr->arr.insert_at(index - 1, value);
|
||
}
|
||
|
||
void LB::b_addProp(int nargs) {
|
||
Datum value = g_lingo->pop();
|
||
Datum prop = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
TYPECHECK(list, PARRAY);
|
||
|
||
PCell cell = PCell(prop, value);
|
||
|
||
if (list.u.parr->_sorted) {
|
||
if (list.u.parr->arr.empty())
|
||
list.u.parr->arr.push_back(cell);
|
||
else {
|
||
uint pos = list.u.parr->arr.size();
|
||
for (uint i = 0; i < list.u.parr->arr.size(); i++) {
|
||
if (list.u.parr->arr[i].p > cell.p) {
|
||
pos = i;
|
||
break;
|
||
}
|
||
}
|
||
list.u.parr->arr.insert_at(pos, cell);
|
||
}
|
||
} else {
|
||
list.u.parr->arr.push_back(cell);
|
||
}
|
||
}
|
||
|
||
void LB::b_append(int nargs) {
|
||
Datum value = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
TYPECHECK(list, ARRAY);
|
||
|
||
list.u.farr->arr.push_back(value);
|
||
list.u.farr->_sorted = false; // Drop the sorted flag
|
||
}
|
||
|
||
void LB::b_count(int nargs) {
|
||
Datum list = g_lingo->pop();
|
||
Datum result;
|
||
result.type = INT;
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
case RECT:
|
||
case POINT:
|
||
result.u.i = list.u.farr->arr.size();
|
||
break;
|
||
case PARRAY:
|
||
result.u.i = list.u.parr->arr.size();
|
||
break;
|
||
case OBJECT:
|
||
result.u.i = list.u.obj->getPropCount();
|
||
break;
|
||
default:
|
||
TYPECHECK3(list, ARRAY, PARRAY, OBJECT);
|
||
}
|
||
|
||
g_lingo->push(result);
|
||
}
|
||
|
||
void LB::b_deleteAt(int nargs) {
|
||
Datum indexD = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
TYPECHECK2(indexD, INT, FLOAT);
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
int index = indexD.asInt();
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
list.u.farr->arr.remove_at(index - 1);
|
||
break;
|
||
case PARRAY:
|
||
list.u.parr->arr.remove_at(index - 1);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void LB::b_deleteOne(int nargs) {
|
||
Datum val = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
|
||
switch (list.type) {
|
||
case ARRAY: {
|
||
g_lingo->push(list);
|
||
g_lingo->push(val);
|
||
b_getPos(nargs);
|
||
int index = g_lingo->pop().asInt();
|
||
if (index > 0) {
|
||
list.u.farr->arr.remove_at(index - 1);
|
||
}
|
||
break;
|
||
}
|
||
case PARRAY: {
|
||
Datum d;
|
||
int index = LC::compareArrays(LC::eqData, list, val, true, true).u.i;
|
||
if (index > 0) {
|
||
list.u.parr->arr.remove_at(index - 1);
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
}
|
||
}
|
||
|
||
|
||
void LB::b_deleteProp(int nargs) {
|
||
Datum prop = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
g_lingo->push(list);
|
||
g_lingo->push(prop);
|
||
b_deleteAt(nargs);
|
||
break;
|
||
case PARRAY: {
|
||
int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
|
||
if (index > 0) {
|
||
list.u.parr->arr.remove_at(index - 1);
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
void LB::b_duplicateList(int nargs) {
|
||
Datum list = g_lingo->pop();
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
g_lingo->push(list.clone());
|
||
}
|
||
|
||
|
||
void LB::b_findPos(int nargs) {
|
||
Datum prop = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
Datum d(g_lingo->getVoid());
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
|
||
if (list.type == ARRAY) {
|
||
if (list.u.farr->_sorted) {
|
||
int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
|
||
if (index > 0)
|
||
d = index;
|
||
else
|
||
d = 0;
|
||
} else {
|
||
if (prop.asInt() > 0 && prop.asInt() <= (int)list.u.farr->arr.size())
|
||
d = prop.asInt();
|
||
else
|
||
d = 0;
|
||
}
|
||
} else {
|
||
int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
|
||
if (index > 0) {
|
||
d = index;
|
||
}
|
||
}
|
||
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
void LB::b_findPosNear(int nargs) {
|
||
Datum prop = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
Datum res;
|
||
TYPECHECK2(list, PARRAY, ARRAY);
|
||
|
||
// This requires more testing, but D4-D6 test show that it does not work as described.
|
||
// The example:
|
||
// findPosNear([#Nile:2, #Pharaoh:4, #Raja:0], #Ni) supposed to return 1, for #Nile, but it returns 4
|
||
if (list.type == PARRAY) {
|
||
res = Datum((int)list.u.parr->arr.size() + 1); // Set it to the end of array by default
|
||
int index = LC::compareArrays(list.u.parr->_sorted ? LC::geData : LC::eqData, list, prop, true).u.i;
|
||
if (index != 0) {
|
||
res = index;
|
||
}
|
||
} else if (list.type == ARRAY) {
|
||
res = prop; // set it to the returned value
|
||
int index = LC::compareArrays(list.u.farr->_sorted ? LC::geData : LC::eqData, list, prop, true).u.i;
|
||
if (index != 0) {
|
||
res = index;
|
||
}
|
||
}
|
||
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_getaProp(int nargs) {
|
||
Datum prop = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
g_lingo->push(list);
|
||
g_lingo->push(prop);
|
||
b_getAt(nargs);
|
||
break;
|
||
case PARRAY: {
|
||
Datum d;
|
||
int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
|
||
if (index > 0) {
|
||
d = list.u.parr->arr[index - 1].v;
|
||
}
|
||
g_lingo->push(d);
|
||
break;
|
||
}
|
||
case OBJECT:
|
||
{
|
||
if (prop.type != SYMBOL) {
|
||
g_lingo->lingoError("b_getaProp(): symbol expected");
|
||
return;
|
||
}
|
||
Datum d;
|
||
if (list.u.obj->hasProp(*prop.u.s))
|
||
d = list.u.obj->getProp(*prop.u.s);
|
||
g_lingo->push(d);
|
||
}
|
||
break;
|
||
default:
|
||
TYPECHECK3(list, ARRAY, PARRAY, OBJECT);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void LB::b_getAt(int nargs) {
|
||
Datum indexD = g_lingo->pop();
|
||
TYPECHECK2(indexD, INT, FLOAT);
|
||
Datum list = g_lingo->pop();
|
||
int index = indexD.asInt();
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
case POINT:
|
||
case RECT:
|
||
ARRBOUNDSCHECK(index, list);
|
||
g_lingo->push(list.u.farr->arr[index - 1]);
|
||
break;
|
||
case PARRAY:
|
||
ARRBOUNDSCHECK(index, list);
|
||
g_lingo->push(list.u.parr->arr[index - 1].v);
|
||
break;
|
||
default:
|
||
TYPECHECK4(list, ARRAY, PARRAY, POINT, RECT);
|
||
}
|
||
}
|
||
|
||
void LB::b_getLast(int nargs) {
|
||
Datum list = g_lingo->pop();
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
if (list.u.farr->arr.empty()) {
|
||
g_lingo->pushVoid();
|
||
} else {
|
||
g_lingo->push(list.u.farr->arr.back());
|
||
}
|
||
break;
|
||
case PARRAY:
|
||
if (list.u.farr->arr.empty()) {
|
||
g_lingo->pushVoid();
|
||
} else {
|
||
g_lingo->push(list.u.parr->arr.back().v);
|
||
}
|
||
break;
|
||
default:
|
||
TYPECHECK(list, ARRAY);
|
||
}
|
||
}
|
||
|
||
void LB::b_getOne(int nargs) {
|
||
Datum val = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
g_lingo->push(list);
|
||
g_lingo->push(val);
|
||
b_getPos(nargs);
|
||
break;
|
||
case PARRAY: {
|
||
Datum d;
|
||
int index = LC::compareArrays(LC::eqData, list, val, true, true).u.i;
|
||
if (index > 0) {
|
||
d = list.u.parr->arr[index - 1].p;
|
||
}
|
||
g_lingo->push(d);
|
||
break;
|
||
}
|
||
default:
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
}
|
||
}
|
||
|
||
void LB::b_getPos(int nargs) {
|
||
Datum val = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
|
||
switch (list.type) {
|
||
case ARRAY: {
|
||
Datum d(0);
|
||
int index = LC::compareArrays(LC::eqDataStrict, list, val, true).u.i;
|
||
if (index > 0) {
|
||
d.u.i = index;
|
||
}
|
||
g_lingo->push(d);
|
||
break;
|
||
}
|
||
case PARRAY: {
|
||
Datum d(0);
|
||
int index = LC::compareArrays(LC::eqDataStrict, list, val, true, true).u.i;
|
||
if (index > 0) {
|
||
d.u.i = index;
|
||
}
|
||
g_lingo->push(d);
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void LB::b_getProp(int nargs) {
|
||
Datum prop = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
if (g_director->getVersion() < 500) {
|
||
// D4 allows getProp to be called on ARRAYs
|
||
g_lingo->push(list);
|
||
g_lingo->push(prop);
|
||
b_getAt(nargs);
|
||
} else {
|
||
g_lingo->lingoError("BUILDBOT: b_getProp: Attempted to call on an ARRAY");
|
||
}
|
||
break;
|
||
case PARRAY: {
|
||
int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
|
||
if (index > 0) {
|
||
g_lingo->push(list.u.parr->arr[index - 1].v);
|
||
} else {
|
||
g_lingo->lingoError("BUILDBOT: b_getProp: Property %s not found", prop.asString().c_str());
|
||
}
|
||
break;
|
||
}
|
||
case OBJECT:
|
||
{
|
||
if (prop.type != SYMBOL) {
|
||
g_lingo->lingoError("BUILDBOT: b_getProp(): symbol expected, got %s", prop.type2str());
|
||
return;
|
||
}
|
||
Datum d;
|
||
if (list.u.obj->hasProp(*prop.u.s))
|
||
d = list.u.obj->getProp(*prop.u.s);
|
||
g_lingo->push(d);
|
||
}
|
||
break;
|
||
default:
|
||
TYPECHECK3(list, ARRAY, PARRAY, OBJECT);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void LB::b_getPropAt(int nargs) {
|
||
Datum indexD = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
TYPECHECK2(indexD, INT, FLOAT);
|
||
int index = indexD.asInt();
|
||
switch (list.type) {
|
||
case PARRAY:
|
||
{
|
||
if ((index <= 0) || (index > (int)list.u.parr->arr.size())) {
|
||
g_lingo->lingoError("b_getPropAt(): index out of range");
|
||
return;
|
||
}
|
||
g_lingo->push(list.u.parr->arr[index - 1].p);
|
||
}
|
||
break;
|
||
case OBJECT:
|
||
{
|
||
if ((index <= 0) || (index > (int)list.u.obj->getPropCount())) {
|
||
g_lingo->lingoError("b_getPropAt(): index out of range");
|
||
return;
|
||
}
|
||
Common::String key = list.u.obj->getPropAt(index);
|
||
Datum result(key);
|
||
result.type = SYMBOL;
|
||
g_lingo->push(result);
|
||
}
|
||
break;
|
||
default:
|
||
TYPECHECK2(list, PARRAY, OBJECT);
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
void LB::b_list(int nargs) {
|
||
Datum result;
|
||
result.type = ARRAY;
|
||
result.u.farr = new FArray;
|
||
|
||
for (int i = 0; i < nargs; i++)
|
||
result.u.farr->arr.insert_at(0, g_lingo->pop());
|
||
|
||
g_lingo->push(result);
|
||
}
|
||
|
||
void LB::b_listP(int nargs) {
|
||
Datum list = g_lingo->pop();
|
||
Datum d(0);
|
||
if (list.type == ARRAY || list.type == PARRAY) {
|
||
d.u.i = 1;
|
||
}
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
void LB::b_max(int nargs) {
|
||
Datum max;
|
||
max.type = INT;
|
||
max.u.i = 0;
|
||
|
||
bool hasVoidQuirk = g_director->getVersion() < 500;
|
||
|
||
Common::Array<Datum> testArr;
|
||
|
||
if (nargs == 1) {
|
||
Datum d = g_lingo->pop();
|
||
if (d.type == ARRAY) {
|
||
for (auto &it : d.u.farr->arr) {
|
||
testArr.push_back(it);
|
||
}
|
||
} else {
|
||
max = d;
|
||
}
|
||
} else if (nargs > 0) {
|
||
for (int i = 0; i < nargs; i++) {
|
||
Datum d = g_lingo->peek(nargs - i - 1);
|
||
if (d.type == ARRAY) {
|
||
warning("b_max: undefined behavior: array mixed with other args");
|
||
}
|
||
testArr.push_back(d);
|
||
}
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
if (!testArr.empty()) {
|
||
// D4: if there is a VOID for the first arg of max, return VOID
|
||
if (hasVoidQuirk && testArr[0].type == VOID) {
|
||
g_lingo->pushVoid();
|
||
return;
|
||
}
|
||
// The trick seems to be to compare each item in sequence.
|
||
// D4: If we encounter a VOID, treat it as the smallest value possible.
|
||
// D5+: If we encounter a string, and the current maximum is a VOID, it gets ignored.
|
||
// D5+: If we encounter a VOID, and the current maximum is a STRING, set it to VOID.
|
||
|
||
max = testArr[0];
|
||
for (int i = 1; i < (int)testArr.size(); i++) {
|
||
if (!hasVoidQuirk) {
|
||
if ((max.type == VOID) && (testArr[i].type == STRING)) {
|
||
continue;
|
||
} else if ((testArr[i].type == VOID) && (max.type == STRING)) {
|
||
max = Datum();
|
||
continue;
|
||
}
|
||
}
|
||
if (testArr[i] > max) {
|
||
max = testArr[i];
|
||
}
|
||
}
|
||
}
|
||
|
||
g_lingo->push(max);
|
||
}
|
||
|
||
void LB::b_min(int nargs) {
|
||
Datum min;
|
||
min.type = INT;
|
||
min.u.i = 0;
|
||
|
||
bool hasVoidQuirk = g_director->getVersion() < 500;
|
||
|
||
Common::Array<Datum> testArr;
|
||
|
||
if (nargs == 1) {
|
||
Datum d = g_lingo->pop();
|
||
if (d.type == ARRAY) {
|
||
for (auto &it : d.u.farr->arr) {
|
||
testArr.push_back(it);
|
||
}
|
||
} else {
|
||
min = d;
|
||
}
|
||
} else if (nargs > 0) {
|
||
for (int i = 0; i < nargs; i++) {
|
||
Datum d = g_lingo->peek(nargs - i - 1);
|
||
if (d.type == ARRAY) {
|
||
warning("b_min: undefined behavior: array mixed with other args");
|
||
}
|
||
testArr.push_back(d);
|
||
}
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
if (!testArr.empty()) {
|
||
// D4: if there is a VOID for the last arg of min, return VOID
|
||
if (hasVoidQuirk && testArr[testArr.size()-1].type == VOID) {
|
||
g_lingo->pushVoid();
|
||
return;
|
||
}
|
||
// The trick seems to be to compare each item in sequence.
|
||
// If we encounter a VOID, and the current minimum is a number or a symbol, it gets converted to VOID.
|
||
// If we encounter a VOID, and the current minimum is a string, we ignore it.
|
||
// If the current minimum is VOID, and we encounter a string, the current minimum is set to the string.
|
||
|
||
min = testArr[0];
|
||
for (int i = 1; i < (int)testArr.size(); i++) {
|
||
if (testArr[i].type == VOID) {
|
||
if (min.type != STRING) {
|
||
min = Datum();
|
||
}
|
||
continue;
|
||
// D4: if the current minimum is VOID, set the next value as the new minimum
|
||
// D5+: if the current minimum is VOID, and the next value is a string (coercable or not), set it as the new minimum
|
||
} else if ((min.type == VOID) && (hasVoidQuirk || (testArr[i].type == STRING))) {
|
||
min = testArr[i];
|
||
continue;
|
||
}
|
||
if (testArr[i] < min) {
|
||
min = testArr[i];
|
||
}
|
||
}
|
||
}
|
||
g_lingo->push(min);
|
||
}
|
||
|
||
void LB::b_setaProp(int nargs) {
|
||
Datum value = g_lingo->pop();
|
||
Datum prop = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
g_lingo->push(list);
|
||
g_lingo->push(prop);
|
||
g_lingo->push(value);
|
||
b_setAt(nargs);
|
||
break;
|
||
case PARRAY: {
|
||
int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
|
||
if (index > 0) {
|
||
list.u.parr->arr[index - 1].v = value;
|
||
} else {
|
||
PCell cell = PCell(prop, value);
|
||
list.u.parr->arr.push_back(cell);
|
||
}
|
||
break;
|
||
}
|
||
case OBJECT:
|
||
{
|
||
if (prop.type != SYMBOL) {
|
||
g_lingo->lingoError("b_setaProp(): symbol expected");
|
||
return;
|
||
}
|
||
list.u.obj->setProp(*prop.u.s, value);
|
||
}
|
||
break;
|
||
default:
|
||
TYPECHECK2(list, ARRAY, PARRAY);
|
||
}
|
||
}
|
||
|
||
void LB::b_setAt(int nargs) {
|
||
Datum value = g_lingo->pop();
|
||
Datum indexD = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
TYPECHECK2(indexD, INT, FLOAT);
|
||
TYPECHECK3(list, ARRAY, PARRAY, RECT);
|
||
int index = indexD.asInt();
|
||
|
||
switch (list.type) {
|
||
case ARRAY:
|
||
if ((uint)index <= list.u.farr->arr.size()) {
|
||
list.u.farr->arr[index - 1] = value;
|
||
} else {
|
||
int inserts = index - list.u.farr->arr.size();
|
||
while (--inserts)
|
||
list.u.farr->arr.push_back(Datum(0));
|
||
list.u.farr->arr.push_back(value);
|
||
}
|
||
break;
|
||
case PARRAY:
|
||
ARRBOUNDSCHECK(index, list);
|
||
list.u.parr->arr[index - 1].v = value;
|
||
break;
|
||
case RECT:
|
||
ARRBOUNDSCHECK(index, list);
|
||
list.u.farr->arr[index-1] = value;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void LB::b_setProp(int nargs) {
|
||
Datum value = g_lingo->pop();
|
||
Datum prop = g_lingo->pop();
|
||
Datum list = g_lingo->pop();
|
||
|
||
switch (list.type) {
|
||
case PARRAY:
|
||
{
|
||
int index = LC::compareArrays(LC::eqData, list, prop, true).u.i;
|
||
if (index > 0) {
|
||
list.u.parr->arr[index - 1].v = value;
|
||
} else {
|
||
warning("b_setProp: Property not found");
|
||
}
|
||
}
|
||
break;
|
||
case OBJECT:
|
||
{
|
||
if (prop.type != SYMBOL) {
|
||
g_lingo->lingoError("BUILDBOT: b_setProp(): symbol expected, got %s", prop.type2str());
|
||
return;
|
||
}
|
||
// unlike the PARRAY case, OBJECT seems to create
|
||
// new properties without throwing an error
|
||
list.u.obj->setProp(*prop.u.s, value);
|
||
}
|
||
break;
|
||
default:
|
||
TYPECHECK2(list, PARRAY, OBJECT);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static bool sortArrayHelper(const Datum &lhs, const Datum &rhs) {
|
||
return lhs < rhs;
|
||
}
|
||
|
||
static bool sortNumericArrayHelper(const Datum &lhs, const Datum &rhs) {
|
||
return lhs.asFloat() < rhs.asFloat();
|
||
}
|
||
|
||
static bool sortPArrayHelper(const PCell &lhs, const PCell &rhs) {
|
||
return lhs.p < rhs.p;
|
||
}
|
||
|
||
static bool sortNumericPArrayHelper(const PCell &lhs, const PCell &rhs) {
|
||
return lhs.p.asFloat() < rhs.p.asFloat();
|
||
}
|
||
|
||
void LB::b_sort(int nargs) {
|
||
// in D4 manual, p266. linear list is sorted by values
|
||
// property list is sorted alphabetically by properties
|
||
// once the list is sorted, it maintains its sort order even when we add new variables using add command
|
||
// see b_append to get more details.
|
||
Datum list = g_lingo->pop();
|
||
|
||
if (list.type == ARRAY) {
|
||
if (list.u.farr->_sorted)
|
||
return;
|
||
|
||
// Check to see if the array is full of numbers
|
||
bool isNumeric = true;
|
||
for (const auto &it : list.u.farr->arr) {
|
||
isNumeric &= it.isNumeric();
|
||
}
|
||
if (isNumeric) {
|
||
// Sorting an array of numbers will use numeric sort order.
|
||
Common::sort(list.u.farr->arr.begin(), list.u.farr->arr.end(), sortNumericArrayHelper);
|
||
} else {
|
||
// Sorting an array of strings will use the string sort order.
|
||
// Sorting an array of mixed types is undefined behaviour; sometimes the interpreter
|
||
// will give an answer nearly the same as the string sort order, other times
|
||
// the interpreter will softlock.
|
||
Common::sort(list.u.farr->arr.begin(), list.u.farr->arr.end(), sortArrayHelper);
|
||
}
|
||
list.u.farr->_sorted = true;
|
||
|
||
} else if (list.type == PARRAY) {
|
||
if (list.u.parr->_sorted)
|
||
return;
|
||
|
||
// Check to see if the array is full of numbers
|
||
bool isNumeric = true;
|
||
for (const auto &it : list.u.parr->arr) {
|
||
isNumeric &= it.p.isNumeric();
|
||
}
|
||
if (isNumeric) {
|
||
Common::sort(list.u.parr->arr.begin(), list.u.parr->arr.end(), sortNumericPArrayHelper);
|
||
} else {
|
||
Common::sort(list.u.parr->arr.begin(), list.u.parr->arr.end(), sortPArrayHelper);
|
||
}
|
||
list.u.parr->_sorted = true;
|
||
|
||
} else {
|
||
warning("LB::b_sort can not handle argument of type %s", list.type2str());
|
||
}
|
||
}
|
||
|
||
|
||
///////////////////
|
||
// Files
|
||
///////////////////
|
||
void LB::b_closeDA(int nargs) {
|
||
warning("BUILDBOT: closeDA is not supported in ScummVM");
|
||
}
|
||
|
||
void LB::b_closeResFile(int nargs) {
|
||
// closeResFile closes only resource files that were opened with openResFile.
|
||
|
||
if (nargs == 0) { // Close all open resource files
|
||
for (auto &it : g_director->_openResFiles)
|
||
g_director->_allOpenResFiles.remove(it._key);
|
||
|
||
g_director->_openResFiles.clear();
|
||
return;
|
||
}
|
||
|
||
Datum d = g_lingo->pop();
|
||
Common::Path resFileName(g_director->getCurrentWindow()->getCurrentPath() + d.asString(), g_director->_dirSeparator);
|
||
|
||
if (g_director->_openResFiles.contains(resFileName)) {
|
||
g_director->_openResFiles.erase(resFileName);
|
||
|
||
g_director->_allOpenResFiles.remove(resFileName);
|
||
}
|
||
}
|
||
|
||
void LB::b_closeXlib(int nargs) {
|
||
if (nargs ==0) { // Close all Xlibs
|
||
g_lingo->closeOpenXLibs();
|
||
return;
|
||
}
|
||
|
||
Datum d = g_lingo->pop();
|
||
Common::String xlibName = getFileName(d.asString());
|
||
g_lingo->closeXLib(xlibName);
|
||
}
|
||
|
||
void LB::b_getNthFileNameInFolder(int nargs) {
|
||
int fileNum = g_lingo->pop().asInt() - 1;
|
||
Common::String pathRaw = g_lingo->pop().asString();
|
||
if (pathRaw.empty()) {
|
||
// If we receive a blank string as a path, it shouldn't match anything.
|
||
g_lingo->push(Datum(""));
|
||
return;
|
||
}
|
||
|
||
// getNthFileNameInFolder requires an absolute path as an input.
|
||
// relative paths will not match anything.
|
||
Common::Path path = findAbsolutePath(pathRaw, true);
|
||
// for directory, we either return the correct path, which we can access recursively.
|
||
// or we get a wrong path, which will lead us to a non-exist file node
|
||
|
||
Common::StringArray directory_list = path.splitComponents();
|
||
Common::FSNode d = Common::FSNode(*g_director->getGameDataDir());
|
||
for (auto &it : directory_list) {
|
||
d = d.getChild(it);
|
||
if (!d.exists())
|
||
break;
|
||
}
|
||
|
||
Datum r("");
|
||
Common::Array<Common::String> fileNameList;
|
||
|
||
// Update the game quirks archive in case our save state has changed.
|
||
// This is necessary because we may save a game and then try to open a game in the same session.
|
||
g_director->gameQuirks(g_director->getGameId(), g_director->getPlatform());
|
||
|
||
// First, mix in any files injected from the quirks
|
||
Common::Archive *cache = SearchMan.getArchive(kQuirksCacheArchive);
|
||
if (cache) {
|
||
Common::ArchiveMemberList files;
|
||
|
||
cache->listMatchingMembers(files, path.append(path.empty() ? "*" : "/*", '/'), true);
|
||
|
||
for (auto &fi : files) {
|
||
fileNameList.push_back(Common::lastPathComponent(fi->getName(), '/'));
|
||
}
|
||
}
|
||
|
||
// Next, mix in files from the game filesystem (if they exist)
|
||
if (d.exists()) {
|
||
Common::FSList f;
|
||
if (!d.getChildren(f, Common::FSNode::kListAll)) {
|
||
warning("Cannot access directory %s", path.toString(Common::Path::kNativeSeparator).c_str());
|
||
} else {
|
||
for (uint i = 0; i < f.size(); i++)
|
||
fileNameList.push_back(f[i].getName());
|
||
}
|
||
}
|
||
|
||
if (!fileNameList.empty() && (uint)fileNum < fileNameList.size()) {
|
||
// Sort files alphabetically
|
||
Common::sort(fileNameList.begin(), fileNameList.end());
|
||
r = Datum(fileNameList[fileNum]);
|
||
}
|
||
|
||
g_lingo->push(r);
|
||
}
|
||
|
||
void LB::b_open(int nargs) {
|
||
|
||
Datum ex = g_lingo->pop();
|
||
Datum d;
|
||
if (nargs == 2)
|
||
d = g_lingo->pop();
|
||
warning("LB::b_open(): Unsupported command open encountered -> The movie tried to open %s %s", ex.asString().c_str(), d.type != VOID ? d.asString().c_str() : "");
|
||
|
||
if (!debugChannelSet(-1, kDebugFewFramesOnly) &&
|
||
!(g_director->getGameGID() == GID_TEST || g_director->getGameGID() == GID_TESTALL)) {
|
||
Common::U32String message = Common::String::format("Unsupported command open encountered -> The movie tried to execute open %s %s!", ex.asString().c_str(), d.type != VOID ? d.asString().c_str() : "");
|
||
GUI::MessageDialog dialog(message);
|
||
dialog.runModal();
|
||
}
|
||
}
|
||
|
||
void LB::b_openDA(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
|
||
warning("BUILDBOT: openDA is not supported in ScummVM");
|
||
}
|
||
|
||
void LB::b_openResFile(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Common::Path resPath(g_director->getCurrentWindow()->getCurrentPath() + d.asString(), g_director->_dirSeparator);
|
||
|
||
if (g_director->getPlatform() == Common::kPlatformWindows) {
|
||
warning("STUB: BUILDBOT: b_openResFile(%s) on Windows", d.asString().c_str());
|
||
return;
|
||
}
|
||
|
||
if (!g_director->_allSeenResFiles.contains(resPath)) {
|
||
MacArchive *arch = new MacArchive();
|
||
if (arch->openFile(findPath(resPath))) {
|
||
g_director->_openResFiles.setVal(resPath, arch);
|
||
g_director->_allSeenResFiles.setVal(resPath, arch);
|
||
g_director->addArchiveToOpenList(resPath);
|
||
} else {
|
||
delete arch;
|
||
}
|
||
}
|
||
}
|
||
|
||
void LB::b_openXlib(int nargs) {
|
||
Common::String xlibName;
|
||
|
||
Datum d = g_lingo->pop();
|
||
|
||
Common::Path xlibPath = findXLibPath(d.asString(), true, false);
|
||
if (g_director->getPlatform() == Common::kPlatformMacintosh) {
|
||
// try opening the file as a Macintosh resource fork
|
||
MacArchive *resFile = new MacArchive();
|
||
if (resFile->openFile(xlibPath)) {
|
||
uint32 XCOD = MKTAG('X', 'C', 'O', 'D');
|
||
uint32 XCMD = MKTAG('X', 'C', 'M', 'D');
|
||
uint32 XFCN = MKTAG('X', 'F', 'C', 'N');
|
||
|
||
Common::Array<uint16> rsrcList = resFile->getResourceIDList(XCOD);
|
||
|
||
for (uint i = 0; i < rsrcList.size(); i++) {
|
||
xlibName = resFile->getResourceDetail(XCOD, rsrcList[i]).name.c_str();
|
||
g_lingo->openXLib(xlibName, kXObj, xlibPath);
|
||
}
|
||
|
||
rsrcList = resFile->getResourceIDList(XCMD);
|
||
for (uint i = 0; i < rsrcList.size(); i++) {
|
||
xlibName = resFile->getResourceDetail(XCMD, rsrcList[i]).name.c_str();
|
||
g_lingo->openXLib(xlibName, kXObj, xlibPath);
|
||
}
|
||
|
||
rsrcList = resFile->getResourceIDList(XFCN);
|
||
for (uint i = 0; i < rsrcList.size(); i++) {
|
||
xlibName = resFile->getResourceDetail(XFCN, rsrcList[i]).name.c_str();
|
||
g_lingo->openXLib(xlibName, kXObj, xlibPath);
|
||
}
|
||
delete resFile;
|
||
return;
|
||
}
|
||
delete resFile;
|
||
}
|
||
|
||
xlibName = getFileName(d.asString());
|
||
|
||
g_lingo->openXLib(xlibName, kNoneObj, xlibPath);
|
||
}
|
||
|
||
void LB::b_save(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_save", nargs);
|
||
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_saveMovie(int nargs) {
|
||
Common::String filename;
|
||
if (nargs) {
|
||
filename = g_lingo->pop().asString();
|
||
}
|
||
g_director->getCurrentMovie()->getArchive()->writeToFile(filename, g_director->getCurrentMovie());
|
||
}
|
||
|
||
void LB::b_setCallBack(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_setCallBack", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_showResFile(int nargs) {
|
||
if (nargs)
|
||
g_lingo->pop();
|
||
Common::String out;
|
||
for (auto &it : g_director->_allOpenResFiles)
|
||
out += it.toString(g_director->_dirSeparator) + "\n";
|
||
g_debugger->debugLogFile(out, false);
|
||
}
|
||
|
||
void LB::b_showXlib(int nargs) {
|
||
if (nargs)
|
||
g_lingo->pop();
|
||
Common::String out;
|
||
for (auto &it : g_lingo->_openXLibs)
|
||
out += it._key + "\n";
|
||
g_debugger->debugLogFile(out, false);
|
||
}
|
||
|
||
void LB::b_xFactoryList(int nargs) {
|
||
g_lingo->pop();
|
||
Datum d("");
|
||
|
||
for (auto &it : g_lingo->_openXLibs)
|
||
*d.u.s += it._key + "\n";
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
void LB::b_xtra(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
if (d.type == INT) {
|
||
int i = d.asInt() - 1; // Lingo index for XTRAs start at 1
|
||
if (i >= 0 && (uint)i < g_lingo->_openXtraObjects.size()) {
|
||
Datum var = g_lingo->_openXtraObjects[i];
|
||
g_lingo->push(var);
|
||
return;
|
||
}
|
||
} else {
|
||
Common::String name = d.asString();
|
||
for (uint i = 0; i < g_lingo->_openXtras.size(); i++) {
|
||
if (name.equalsIgnoreCase(g_lingo->_openXtras[i])) {
|
||
Datum var = g_lingo->_openXtraObjects[i];
|
||
g_lingo->push(var);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
g_lingo->lingoError("Xtra not found: %s", d.asString().c_str());
|
||
}
|
||
|
||
///////////////////
|
||
// Control
|
||
///////////////////
|
||
void LB::b_abort(int nargs) {
|
||
g_lingo->_abort = true;
|
||
}
|
||
|
||
void LB::b_call(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_call", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_callAncestor(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_callAncestor", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_cancelIdleLoad(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_cancelIdleLoad", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_continue(int nargs) {
|
||
g_director->_playbackPaused = false;
|
||
}
|
||
|
||
void LB::b_dontPassEvent(int nargs) {
|
||
g_lingo->_passEvent = false;
|
||
}
|
||
|
||
void LB::b_nothing(int nargs) {
|
||
// Noop
|
||
}
|
||
|
||
void LB::b_delay(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
g_director->getCurrentMovie()->getScore()->setDelay(d.asInt());
|
||
}
|
||
|
||
void LB::b_do(int nargs) {
|
||
Common::String code = g_lingo->pop().asString();
|
||
|
||
if (code.empty())
|
||
return;
|
||
|
||
ScriptContext *sc = g_lingo->_compiler->compileAnonymous(code);
|
||
if (!sc) {
|
||
warning("b_do(): compilation failed, ignoring");
|
||
return;
|
||
} else if (!sc->_eventHandlers.contains(kEventGeneric)) {
|
||
warning("b_do(): compiled code did not return handler, ignoring");
|
||
return;
|
||
}
|
||
Symbol sym = sc->_eventHandlers[kEventGeneric];
|
||
|
||
// Check if we have anything to execute
|
||
if (sym.type == VOIDSYM)
|
||
return;
|
||
|
||
LC::call(sym, 0, false);
|
||
}
|
||
|
||
void LB::b_finishIdleLoad(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_finishIdleLoad", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_go(int nargs) {
|
||
// Builtin function for go as used by the Director bytecode engine.
|
||
//
|
||
// Accepted arguments:
|
||
// "loop"
|
||
// "next"
|
||
// "previous"
|
||
// (STRING|INT) frame
|
||
// STRING movie, (STRING|INT) frame
|
||
|
||
if (nargs >= 1 && nargs <= 2) {
|
||
Datum firstArg = g_lingo->pop();
|
||
nargs -= 1;
|
||
bool callSpecial = false;
|
||
|
||
if (firstArg.type == SYMBOL) {
|
||
if (*firstArg.u.s == "loop") {
|
||
g_lingo->func_gotoloop();
|
||
callSpecial = true;
|
||
} else if (*firstArg.u.s == "next") {
|
||
g_lingo->func_gotonext();
|
||
callSpecial = true;
|
||
} else if (*firstArg.u.s == "previous") {
|
||
g_lingo->func_gotoprevious();
|
||
callSpecial = true;
|
||
}
|
||
}
|
||
|
||
if (!callSpecial) {
|
||
Datum movie;
|
||
Datum frame;
|
||
|
||
if (nargs > 0 && firstArg.type == STRING) {
|
||
movie = firstArg;
|
||
TYPECHECK(movie, STRING);
|
||
|
||
frame = g_lingo->pop();
|
||
nargs -= 1;
|
||
// Even if there's more than one argument, if the first
|
||
// arg is an int, Director discards the remainder and
|
||
// treats it as the frame.
|
||
} else if (nargs > 0 && firstArg.type == INT) {
|
||
frame = g_lingo->pop();
|
||
nargs -= 1;
|
||
} else {
|
||
frame = firstArg;
|
||
}
|
||
|
||
if (frame.type != STRING && frame.type != INT) {
|
||
warning("b_go: frame arg should be of type STRING or INT, not %s", frame.type2str());
|
||
}
|
||
|
||
g_lingo->func_goto(frame, movie, true);
|
||
}
|
||
|
||
if (nargs > 0) {
|
||
warning("b_go: ignoring %d extra args", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
} else {
|
||
warning("b_go: expected 1 or 2 args, not %d", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
}
|
||
|
||
void LB::b_halt(int nargs) {
|
||
b_quit(nargs);
|
||
|
||
warning("Movie halted");
|
||
}
|
||
|
||
void LB::b_idleLoadDone(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_idleLoadDone", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
Datum res(1);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_pass(int nargs) {
|
||
g_lingo->_passEvent = true;
|
||
}
|
||
|
||
void LB::b_pause(int nargs) {
|
||
g_director->_playbackPaused = true;
|
||
}
|
||
|
||
void LB::b_play(int nargs) {
|
||
// Builtin function for play as used by the Director bytecode engine.
|
||
//
|
||
// Accepted arguments:
|
||
// 0 # "play done"
|
||
// (STRING|INT) frame
|
||
// STRING movie, (STRING|INT) frame
|
||
|
||
Datum movie;
|
||
Datum frame;
|
||
|
||
switch (nargs) {
|
||
case 2:
|
||
movie = g_lingo->pop();
|
||
// fall through
|
||
case 1:
|
||
frame = g_lingo->pop();
|
||
if (!(frame.type == INT && frame.u.i == 0 && nargs == 1))
|
||
break;
|
||
// fall through
|
||
case 0:
|
||
frame.type = SYMBOL;
|
||
frame.u.s = new Common::String("done");
|
||
break;
|
||
default:
|
||
warning("b_play: expected 0, 1 or 2 args, not %d", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
|
||
return;
|
||
}
|
||
|
||
g_lingo->func_play(frame, movie);
|
||
}
|
||
|
||
void LB::b_playAccel(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_playAccel", nargs);
|
||
|
||
/*
|
||
byFrame Read one frame at a time from disk.
|
||
click Stop and pass on mouse event.
|
||
clickStop Stop and don't pass on mouse event.
|
||
loop Play movie continuously.
|
||
noFlush Prevents the current interactive movie from being
|
||
removed from memory when the Accelerator
|
||
document is loaded.
|
||
noSound Don’t play sound.
|
||
noUpdate Don’t update screen at end of movie.
|
||
playRect, l, t, r, b
|
||
Stop the movie when the pointer is moved outside the coordinates (left, top, right, bottom).
|
||
repeat, n Number of times to repeat.
|
||
sync Attempt to play in sync with scan rate of monitor.
|
||
whatFits Play only what fits into memory.
|
||
tempo, n Set the tempo of the movie.
|
||
*/
|
||
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_preLoad(int nargs) {
|
||
// We always pretend we preloaded all frames
|
||
// Returning the number of the last frame successfully "loaded"
|
||
if (nargs == 0) {
|
||
g_lingo->_theResult = Datum((int)g_director->getCurrentMovie()->getScore()->getFramesNum());
|
||
return;
|
||
}
|
||
|
||
g_lingo->_theResult = g_lingo->pop();
|
||
|
||
if (nargs > 1)
|
||
g_lingo->dropStack(nargs - 1);
|
||
}
|
||
|
||
void LB::b_preLoadCast(int nargs) {
|
||
// We always pretend we preloaded all cast
|
||
// Returning the number of the last cast successfully "loaded"
|
||
|
||
if (nargs > 1) {
|
||
g_lingo->_theResult = g_lingo->pop();
|
||
} else {
|
||
g_lingo->_theResult = 1;
|
||
}
|
||
|
||
if (nargs == 2)
|
||
g_lingo->pop();
|
||
}
|
||
|
||
void LB::b_preLoadMovie(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_preLoadMovie", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_framesToHMS(int nargs) {
|
||
int fractionalSeconds = g_lingo->pop().asInt();
|
||
int dropFrame = g_lingo->pop().asInt();
|
||
int fps = g_lingo->pop().asInt();
|
||
int frames = g_lingo->pop().asInt();
|
||
|
||
fps = MAX(1, fps);
|
||
|
||
bool negative = frames < 0;
|
||
if (negative)
|
||
frames = -frames;
|
||
|
||
int framesPerMin = 60 * fps;
|
||
int framesPerHour = 60 * framesPerMin;
|
||
|
||
if (dropFrame)
|
||
warning("STUB: b_framesToHMS: Unhandled dropFrame option");
|
||
|
||
int h = MIN(frames / framesPerHour, 99);
|
||
int m = (frames % framesPerHour) / framesPerMin;
|
||
int s = (frames % framesPerMin) / fps;
|
||
|
||
int residual;
|
||
if (fractionalSeconds) {
|
||
int ms = (1000 * (frames % fps)) / fps;
|
||
residual = (ms + 5) / 10; // round to nearest centisecond
|
||
} else {
|
||
residual = frames % fps;
|
||
}
|
||
|
||
Common::String hms = Common::String::format(
|
||
"%c%02d:%02d:%02d.%02d%c",
|
||
negative ? '-' : ' ',
|
||
h, m, s, residual,
|
||
dropFrame ? 'd' : ' '
|
||
);
|
||
|
||
g_lingo->push(hms);
|
||
}
|
||
|
||
void LB::b_HMStoFrames(int nargs) {
|
||
// The original implementation of this accepts some really weird,
|
||
// seemingly malformed input strings.
|
||
// (Try, for example, "12345678901234567890")
|
||
// It's probably not worth supporting them all unless we need to,
|
||
// so only well-formed input is handled right now.
|
||
|
||
int fractionalSeconds = g_lingo->pop().asInt();
|
||
int dropFrame = g_lingo->pop().asInt();
|
||
int fps = g_lingo->pop().asInt();
|
||
Common::String hms = g_lingo->pop().asString();
|
||
|
||
if (fps <= 0)
|
||
fps = 1;
|
||
|
||
const char *ptr = hms.c_str();
|
||
while (Common::isSpace(*ptr))
|
||
ptr++;
|
||
|
||
// read sign
|
||
bool negative = false;
|
||
if (*ptr == '-') {
|
||
negative = true;
|
||
ptr++;
|
||
}
|
||
|
||
// read HH, MM, and SS
|
||
int secs = 0;
|
||
for (int i = 0; i < 3; i++) {
|
||
if (*ptr == ':' || Common::isSpace(*ptr))
|
||
ptr ++;
|
||
|
||
if (!Common::isDigit(*ptr))
|
||
break;
|
||
|
||
int part = 0;
|
||
for (int j = 0; j < 2 && Common::isDigit(*ptr); j++) {
|
||
part = (10 * part) + (*ptr - '0');
|
||
ptr++;
|
||
}
|
||
secs = (60 * secs) + part;
|
||
}
|
||
int frames = secs * fps;
|
||
|
||
// read FF
|
||
if (*ptr == '.') {
|
||
ptr++;
|
||
|
||
int part = 0;
|
||
for (int i = 0; i < 2 && Common::isDigit(*ptr); i++) {
|
||
part = (10 * part) + (*ptr - '0');
|
||
ptr++;
|
||
}
|
||
if (fractionalSeconds) {
|
||
frames += (part * fps + 50) / 100; // round to nearest frame
|
||
} else {
|
||
frames += part;
|
||
}
|
||
}
|
||
|
||
// read D
|
||
if (*ptr == 'd' || *ptr == 'D') {
|
||
ptr++;
|
||
dropFrame = 1;
|
||
}
|
||
|
||
while (Common::isSpace(*ptr))
|
||
ptr++;
|
||
|
||
if (*ptr != '\0')
|
||
warning("b_HMStoFrames: Unexpected character '%c'", *ptr);
|
||
|
||
if (dropFrame)
|
||
warning("STUB: b_HMStoFrames: Unhandled dropFrame option");
|
||
|
||
if (negative)
|
||
frames = -frames;
|
||
|
||
g_lingo->push(frames);
|
||
}
|
||
|
||
void LB::b_param(int nargs) {
|
||
int pos = g_lingo->pop().asInt();
|
||
Datum result;
|
||
CFrame *cf = g_lingo->_state->callstack[g_lingo->_state->callstack.size() - 1];
|
||
// for named parameters, b_param must return what the current value is (i.e.
|
||
// if the handler has changed it, return that)
|
||
if (pos > 0 && cf->sp.argNames && pos <= (int)cf->sp.argNames->size()) {
|
||
Datum func((*cf->sp.argNames)[pos - 1]);
|
||
func.type = LOCALREF;
|
||
result = g_lingo->varFetch(func);
|
||
} else if (pos > 0 && pos <= (int)cf->paramList.size()) {
|
||
// otherwise, if a function was called with extra unnamed arguments,
|
||
// return that.
|
||
result = cf->paramList[pos - 1];
|
||
} else {
|
||
warning("Invalid argument position %d", pos);
|
||
}
|
||
g_lingo->push(result);
|
||
}
|
||
|
||
void LB::b_printFrom(int nargs) {
|
||
warning("BUILDBOT: printFrom is not supported in ScummVM");
|
||
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_quit(int nargs) {
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
if (movie)
|
||
movie->getScore()->_playState = kPlayStopped;
|
||
|
||
g_lingo->pushVoid();
|
||
}
|
||
|
||
void LB::b_return(int nargs) {
|
||
CFrame *fp = g_lingo->_state->callstack.back();
|
||
|
||
Datum retVal;
|
||
if (nargs > 0) {
|
||
retVal = g_lingo->pop();
|
||
}
|
||
|
||
// clear any temp values from loops
|
||
while (g_lingo->_state->stack.size() > fp->stackSizeBefore)
|
||
g_lingo->pop();
|
||
|
||
// Do not allow a factory's mNew method to return a value
|
||
if (nargs > 0 && !(g_lingo->_state->me.type == OBJECT && g_lingo->_state->me.u.obj->getObjType() == kFactoryObj
|
||
&& fp->sp.name->equalsIgnoreCase("mNew"))) {
|
||
g_lingo->push(retVal);
|
||
}
|
||
|
||
LC::procret();
|
||
}
|
||
|
||
void LB::b_restart(int nargs) {
|
||
b_quit(nargs);
|
||
|
||
warning("Computer restarts");
|
||
}
|
||
|
||
void LB::b_shutDown(int nargs) {
|
||
b_quit(nargs);
|
||
|
||
warning("Computer shuts down");
|
||
}
|
||
|
||
void LB::b_startTimer(int nargs) {
|
||
g_director->getCurrentMovie()->_lastTimerReset = g_director->getMacTicks();
|
||
}
|
||
|
||
void LB::b_stopEvent(int nargs) {
|
||
warning("STUB: b_stopEvent");
|
||
// TEquivalent to the dontPassEvent command used in earlier
|
||
//versions of Director, this command also applies to sprite scripts.
|
||
g_lingo->_passEvent = false;
|
||
}
|
||
|
||
///////////////////
|
||
// Types
|
||
///////////////////
|
||
void LB::b_factory(int nargs) {
|
||
Datum factoryName = g_lingo->pop();
|
||
factoryName.type = GLOBALREF;
|
||
Datum o = g_lingo->varFetch(factoryName);
|
||
if (o.type == OBJECT && (o.u.obj->getObjType() & (kFactoryObj | kXObj))
|
||
&& o.u.obj->getName().equalsIgnoreCase(*factoryName.u.s) && o.u.obj->getInheritanceLevel() == 1) {
|
||
g_lingo->push(o);
|
||
} else {
|
||
g_lingo->push(Datum(0));
|
||
}
|
||
}
|
||
|
||
void LB::b_floatP(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res((d.type == FLOAT) ? 1 : 0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_ilk(int nargs) {
|
||
Datum res(0);
|
||
if (nargs == 1) {
|
||
// Single-argument mode returns the type of the item as a symbol.
|
||
// D4 is inconsistent about what types this variant is allowed to work with; e.g. #integer is fine,
|
||
// but #proplist is not. For now, give a response for all types.
|
||
Datum item = g_lingo->pop();
|
||
res = Datum(Common::String(item.type2str(true)));
|
||
res.type = SYMBOL;
|
||
g_lingo->push(res);
|
||
return;
|
||
}
|
||
|
||
if (nargs > 2) {
|
||
warning("b_ilk: dropping %d extra args", nargs - 2);
|
||
g_lingo->dropStack(nargs - 2);
|
||
}
|
||
|
||
// Two argument mode checks the type of the item against a symbol.
|
||
Datum type = g_lingo->pop();
|
||
Datum item = g_lingo->pop();
|
||
if (type.type != SYMBOL) {
|
||
warning("b_ilk: expected a symbol for second arg");
|
||
} else {
|
||
Common::String typeCopy = type.asString();
|
||
|
||
// A special case is #list, which is the equivalent of checking the item type is one of #linearlist,
|
||
// #proplist, #point and #rect.
|
||
if (typeCopy.equalsIgnoreCase("list")) {
|
||
res.u.i = item.type == ARRAY ? 1 : 0;
|
||
res.u.i |= item.type == PARRAY ? 1 : 0;
|
||
res.u.i |= item.type == POINT ? 1 : 0;
|
||
res.u.i |= item.type == RECT ? 1 : 0;
|
||
} else {
|
||
res.u.i = typeCopy.equalsIgnoreCase(item.type2str(true)) ? 1 : 0;
|
||
}
|
||
}
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_integerp(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res((d.type == INT) ? 1 : 0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_objectp(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res;
|
||
if (d.type == OBJECT) {
|
||
res = !d.u.obj->isDisposed();
|
||
} else if (d.type == ARRAY || d.type == PARRAY) {
|
||
res = 1;
|
||
} else {
|
||
res = 0;
|
||
}
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_pictureP(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res((d.type == PICTUREREF) ? 1 : 0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_stringp(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res((d.type == STRING) ? 1 : 0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_symbol(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
switch (d.type) {
|
||
case SYMBOL:
|
||
g_lingo->push(d);
|
||
break;
|
||
case STRING:
|
||
{
|
||
Common::String payload = d.asString();
|
||
if ((payload.size() == 0) || ((payload.size() == 1) && (payload[0] == ' '))) {
|
||
payload = "";
|
||
} else if (payload.size() == 1) {
|
||
// if the string is one character, allow it
|
||
// unless it's space, in which case return a zero-length symbol
|
||
} else {
|
||
// if the string is more than one character, cut it at the first non [a-zA-Z0-9_] character
|
||
for (unsigned int i = 0; i < payload.size(); i++) {
|
||
if (!Common::isAlnum(payload[i]) && (payload[i] != '_')) {
|
||
payload = payload.substr(0, i);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
Datum result(payload);
|
||
result.type = SYMBOL;
|
||
g_lingo->push(result);
|
||
}
|
||
break;
|
||
default:
|
||
g_lingo->pushVoid();
|
||
break;
|
||
}
|
||
}
|
||
|
||
void LB::b_symbolp(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res((d.type == SYMBOL) ? 1 : 0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
|
||
void LB::b_voidP(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res((d.type == VOID) ? 1 : 0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
|
||
///////////////////
|
||
// Misc
|
||
///////////////////
|
||
void LB::b_alert(int nargs) {
|
||
// Let the movie know not to record mouse and key events
|
||
// While there is an GUI alert box being shown
|
||
// It may happen the user clicks on the button on the GUI message and
|
||
// due to it getting recorded as a movie event, causes unpredictable changes
|
||
g_director->getCurrentMovie()->_inGuiMessageBox = true;
|
||
|
||
Datum d = g_lingo->pop();
|
||
|
||
Common::String alert = d.asString();
|
||
warning("b_alert(%s)", alert.c_str());
|
||
|
||
if (g_director->getGameGID() == GID_TEST) {
|
||
warning("b_alert: Skipping due to tests");
|
||
|
||
return;
|
||
}
|
||
|
||
if (!debugChannelSet(-1, kDebugFewFramesOnly)) {
|
||
g_director->_wm->clearHandlingWidgets();
|
||
GUI::MessageDialog dialog(alert.c_str());
|
||
dialog.runModal();
|
||
}
|
||
|
||
// Movie can process events as normal now
|
||
g_director->getCurrentMovie()->_inGuiMessageBox = false;
|
||
}
|
||
|
||
void LB::b_clearGlobals(int nargs) {
|
||
for (auto &it : g_lingo->_globalvars) {
|
||
if (!it._value.ignoreGlobal) {
|
||
// For some reason, factory objects are not removed
|
||
// by this command.
|
||
if (it._value.type == OBJECT && it._value.u.obj->getObjType() & (kFactoryObj | kScriptObj))
|
||
continue;
|
||
g_lingo->_globalvars.erase(it._key);
|
||
}
|
||
}
|
||
}
|
||
|
||
void LB::b_cursor(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
g_lingo->func_cursor(d);
|
||
}
|
||
|
||
void LB::b_put(int nargs) {
|
||
// Prints a statement to the Message window
|
||
Common::String output;
|
||
for (int i = nargs - 1; i >= 0; i--) {
|
||
output += g_lingo->peek(i).asString(true);
|
||
if (i > 0)
|
||
output += " ";
|
||
}
|
||
if (g_debugger->isActive()) {
|
||
g_debugger->debugLogFile(output, true);
|
||
} else {
|
||
debug("-- %s", output.c_str());
|
||
}
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_setPref(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_setPref", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_showGlobals(int nargs) {
|
||
b_version(0);
|
||
Datum ver = g_lingo->pop();
|
||
Common::String global_out = "-- Global Variables --\nversion = ";
|
||
global_out += ver.asString() + "\n";
|
||
if (g_lingo->_globalvars.size()) {
|
||
for (auto it = g_lingo->_globalvars.begin(); it != g_lingo->_globalvars.end(); it++) {
|
||
if (!it->_value.ignoreGlobal) {
|
||
global_out += it->_key + " = " + it->_value.asString() + "\n";
|
||
}
|
||
}
|
||
}
|
||
g_debugger->debugLogFile(global_out, false);
|
||
}
|
||
|
||
void LB::b_showLocals(int nargs) {
|
||
Common::String local_out = "-- Local Variables --\n";
|
||
if (g_lingo->_state->localVars) {
|
||
for (auto it = g_lingo->_state->localVars->begin(); it != g_lingo->_state->localVars->end(); it++) {
|
||
local_out += it->_key + " = " + it->_value.asString() + "\n";
|
||
}
|
||
}
|
||
g_debugger->debugLogFile(local_out, false);
|
||
}
|
||
|
||
///////////////////
|
||
// Score
|
||
///////////////////
|
||
void LB::b_constrainH(int nargs) {
|
||
Datum num = g_lingo->pop();
|
||
Datum sprite = g_lingo->pop();
|
||
Score *score = g_director->getCurrentMovie()->getScore();
|
||
int res = 0;
|
||
if (score) {
|
||
Channel *ch = score->getChannelById(sprite.asInt());
|
||
if (ch) {
|
||
res = CLIP<int> (num.asInt(), ch->getBbox().left, ch->getBbox().right);
|
||
} else {
|
||
warning("b_constrainH: cannot find channel %d", sprite.asInt());
|
||
}
|
||
} else {
|
||
warning("b_constrainH: no score");
|
||
}
|
||
|
||
g_lingo->push(Datum(res));
|
||
}
|
||
|
||
void LB::b_constrainV(int nargs) {
|
||
Datum num = g_lingo->pop();
|
||
Datum sprite = g_lingo->pop();
|
||
|
||
Score *score = g_director->getCurrentMovie()->getScore();
|
||
int res = 0;
|
||
if (score) {
|
||
Channel *ch = score->getChannelById(sprite.asInt());
|
||
if (ch) {
|
||
res = CLIP<int> (num.asInt(), ch->getBbox().top, ch->getBbox().bottom);
|
||
} else {
|
||
warning("b_constrainH: cannot find channel %d", sprite.asInt());
|
||
}
|
||
} else {
|
||
warning("b_constrainV: no score");
|
||
}
|
||
|
||
g_lingo->push(Datum(res));
|
||
}
|
||
|
||
void LB::b_copyToClipBoard(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
g_director->_clipBoard = new CastMemberID(d.asMemberID());
|
||
}
|
||
|
||
void LB::b_duplicate(int nargs) {
|
||
Datum to;
|
||
Datum from;
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
|
||
if (nargs >= 2) {
|
||
nargs -= 2;
|
||
g_lingo->dropStack(nargs);
|
||
to = g_lingo->pop();
|
||
from = g_lingo->pop();
|
||
if (from.type != CASTREF)
|
||
error("b_duplicate(): expected CASTREF for from, got %s", from.type2str());
|
||
if (to.type == INT)
|
||
to = Datum(CastMemberID(to.asInt(), DEFAULT_CAST_LIB));
|
||
else if (to.type != CASTREF)
|
||
error("b_duplicate(): expected CASTREF or INT for to, got %s", to.type2str());
|
||
} else if (nargs == 1) {
|
||
// use next available slot in the same cast library
|
||
from = g_lingo->pop();
|
||
if (from.type != CASTREF)
|
||
error("b_duplicate(): expected CASTREF for from, got %s", from.type2str());
|
||
if (!movie->getCasts()->contains(from.u.cast->castLib))
|
||
error("b_duplicate(): couldn't find cast lib %d", from.u.cast->castLib);
|
||
|
||
Cast *cast = movie->getCasts()->getVal(from.u.cast->castLib);
|
||
to = Datum(CastMemberID(cast->getNextUnusedID(), from.u.cast->castLib));
|
||
} else {
|
||
error("b_duplicate(): expected at least 1 argument");
|
||
}
|
||
|
||
if (!movie->duplicateCastMember(*from.u.cast, *to.u.cast)) {
|
||
warning("b_duplicate(): failed to copy cast member %s to %s", from.u.cast->asString().c_str(), to.u.cast->asString().c_str());
|
||
}
|
||
|
||
Score *score = movie->getScore();
|
||
// force redraw any sprites
|
||
score->refreshPointersForCastMemberID(*to.u.cast);
|
||
b_updateStage(0);
|
||
g_lingo->push(Datum(to.u.cast->member));
|
||
}
|
||
|
||
void LB::b_editableText(int nargs) {
|
||
// editableText is deprecated in D4+ with the addition of "the editableText",
|
||
// but is still a valid function call.
|
||
Score *sc = g_director->getCurrentMovie()->getScore();
|
||
if (!sc) {
|
||
warning("b_editableText: no score");
|
||
g_lingo->dropStack(nargs);
|
||
return;
|
||
}
|
||
|
||
if (nargs == 2) {
|
||
Datum state = g_lingo->pop();
|
||
Datum sprite = g_lingo->pop();
|
||
if ((uint)sprite.asInt() < sc->_channels.size()) {
|
||
sc->getSpriteById(sprite.asInt())->_editable = state.asInt();
|
||
sc->getOriginalSpriteById(sprite.asInt())->_editable = state.asInt();
|
||
} else {
|
||
warning("b_editableText: sprite index out of bounds");
|
||
}
|
||
} else if (nargs == 0) {
|
||
g_lingo->dropStack(nargs);
|
||
|
||
if (g_lingo->_currentChannelId == -1) {
|
||
warning("b_editableText: channel Id is missing");
|
||
return;
|
||
}
|
||
sc->getSpriteById(g_lingo->_currentChannelId)->_editable = true;
|
||
sc->getOriginalSpriteById(g_lingo->_currentChannelId)->_editable = true;
|
||
} else {
|
||
warning("b_editableText: unexpectedly received %d arguments", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
}
|
||
|
||
void LB::b_erase(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
CastMember *eraseCast = movie->getCastMember(d.asMemberID());
|
||
|
||
if (eraseCast) {
|
||
eraseCast->_erase = true;
|
||
Common::Array<Channel *> channels = movie->getScore()->_channels;
|
||
|
||
for (uint i = 0; i < channels.size(); i++) {
|
||
if (channels[i]->_sprite->_castId == d.asMemberID()) {
|
||
channels[i]->_dirty = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void LB::b_findEmpty(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Cast *cast = g_director->getCurrentMovie()->getCast();
|
||
uint16 c_start = cast->_castArrayStart;
|
||
uint16 c_end = cast->_castArrayEnd;
|
||
|
||
if (d.type != CASTREF) {
|
||
warning("Incorrect argument type for findEmpty");
|
||
return;
|
||
}
|
||
|
||
Datum res;
|
||
|
||
if (d.u.cast->member > c_end) {
|
||
res = d.u.cast->member;
|
||
g_lingo->push(res);
|
||
return;
|
||
}
|
||
|
||
if (d.u.cast->member > c_start) {
|
||
c_start = (uint16) d.u.cast->member;
|
||
}
|
||
|
||
for (uint16 i = c_start; i <= c_end; i++) {
|
||
if (!(cast->getCastMember(i) && cast->getCastMember(i)->_type != kCastTypeNull)) {
|
||
res = i;
|
||
g_lingo->push(res);
|
||
return;
|
||
}
|
||
}
|
||
|
||
res = (int) c_end + 1;
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_importFileInto(int nargs) {
|
||
|
||
Common::String file = g_lingo->pop().asString();
|
||
Datum dst = g_lingo->pop();
|
||
|
||
if (!dst.isCastRef()) {
|
||
warning("b_importFileInto(): bad cast ref field type: %s", dst.type2str());
|
||
return;
|
||
}
|
||
|
||
CastMemberID memberID = *dst.u.cast;
|
||
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
Score *score = movie->getScore();
|
||
Cast *cast = movie->getCast(memberID);
|
||
if (!cast) {
|
||
return;
|
||
}
|
||
|
||
Common::Path path = findPath(file);
|
||
if (path.empty()) {
|
||
warning("b_importFileInto(): couldn't find target file %s", file.c_str());
|
||
return;
|
||
}
|
||
|
||
cast->importFileInto(memberID.member, path);
|
||
|
||
score->refreshPointersForCastMemberID(dst.asMemberID());
|
||
}
|
||
|
||
void menuCommandsCallback(int action, Common::String &text, void *data) {
|
||
g_director->getCurrentMovie()->queueInputEvent(kEventMenuCallback, action);
|
||
}
|
||
|
||
void LB::b_installMenu(int nargs) {
|
||
// installMenu castNum
|
||
Datum d = g_lingo->pop();
|
||
|
||
CastMemberID memberID = d.asMemberID(kCastText);
|
||
if (memberID.member == 0) {
|
||
debugC(3, kDebugLoading, "LB::b_installMenu(): removing menu");
|
||
g_director->_wm->removeMenu();
|
||
return;
|
||
}
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
CastMember *member = movie->getCastMember(memberID);
|
||
if (!member) {
|
||
g_lingo->lingoError("installMenu: Unknown %s", memberID.asString().c_str());
|
||
return;
|
||
}
|
||
if (member->_type != kCastText) {
|
||
g_lingo->lingoError("installMenu: %s is not a field", memberID.asString().c_str());
|
||
return;
|
||
}
|
||
TextCastMember *field = static_cast<TextCastMember *>(member);
|
||
|
||
Common::String menuStxt = field->getRawText();
|
||
// clang reports linenum variable is unused
|
||
// int linenum = -1; // We increment it before processing
|
||
|
||
Graphics::MacMenu *menu = g_director->_wm->addMenu();
|
||
int submenu = -1;
|
||
Common::String submenuText;
|
||
Common::String command;
|
||
int commandId = 100;
|
||
|
||
menu->setCommandsCallback(menuCommandsCallback, g_director);
|
||
|
||
debugC(3, kDebugLoading, "LB::b_installMenu(): installing menu - '%s'", Common::toPrintable(menuStxt).c_str());
|
||
|
||
LingoArchive *mainArchive = movie->getMainLingoArch();
|
||
|
||
// STXT sections use Mac-style carriage returns for line breaks.
|
||
const char LINE_BREAK_CHAR = '\x0D';
|
||
// Menu definitions use the character 0xc5 to denote a code separator.
|
||
// For Mac D4 and below, this is ≈. For Windows D4 and below, this is Å.
|
||
char CODE_SEPARATOR_CHAR = '\xC5';
|
||
char CODE_SEPARATOR_CHAR_2 = '\xC5';
|
||
if (g_director->getVersion() >= 500) {
|
||
// D5 changed this to be the pipe | character, the same in Windows and Mac.
|
||
CODE_SEPARATOR_CHAR = '\x7C';
|
||
// FIXME: For some reason there are games which use º (Mac) or ¼ (Win) and it works too?
|
||
CODE_SEPARATOR_CHAR_2 = '\xBC';
|
||
}
|
||
// Continuation character is 0xac to denote a line running over.
|
||
// For Mac, this is ¨. For Windows, this is ¬.
|
||
const char CONTINUATION_CHAR = '\xAC';
|
||
// Menu definitions use the character 0xc3 to denote a checkmark.
|
||
// For Mac, this is √. For Windows, this is Ã.
|
||
// This is used by MacMenu::createSubMenuFromString.
|
||
|
||
Common::String line;
|
||
|
||
for (auto it = menuStxt.begin(); it != menuStxt.end(); ++it) {
|
||
line.clear();
|
||
while (it != menuStxt.end() && *it != LINE_BREAK_CHAR) {
|
||
if (*it == '-') {
|
||
it++;
|
||
if (it != menuStxt.end() && *it == '-') { // rest of the line is a comment
|
||
while (it != menuStxt.end() && *it != LINE_BREAK_CHAR) {
|
||
it++;
|
||
}
|
||
break;
|
||
}
|
||
line += '-';
|
||
} else if (*it == CONTINUATION_CHAR) { // fast forward to the next line
|
||
it++;
|
||
if (*it == LINE_BREAK_CHAR) {
|
||
line += ' ';
|
||
it++;
|
||
}
|
||
} else if (*it == LINE_BREAK_CHAR) {
|
||
break;
|
||
} else {
|
||
line += *it++;
|
||
}
|
||
}
|
||
// clang reports linenum variable is unused
|
||
// linenum++;
|
||
|
||
if (line.empty())
|
||
continue;
|
||
|
||
if (line.hasPrefixIgnoreCase("menu:")) {
|
||
const char *p = &line.c_str()[5];
|
||
|
||
while (*p && (*p == ' ' || *p == '\t'))
|
||
p++;
|
||
|
||
if (!submenuText.empty()) { // Adding submenu for previous menu
|
||
menu->createSubMenuFromString(submenu, submenuText.c_str(), 0);
|
||
}
|
||
|
||
if (!strcmp(p, "@"))
|
||
p = "\xf0"; // Apple symbol
|
||
|
||
submenu = menu->addMenuItem(nullptr, Common::String(p));
|
||
|
||
submenuText.clear();
|
||
|
||
continue;
|
||
}
|
||
|
||
// Split the line at the code separator
|
||
size_t sepOffset = line.find(CODE_SEPARATOR_CHAR);
|
||
if (sepOffset == Common::String::npos)
|
||
sepOffset = line.find(CODE_SEPARATOR_CHAR_2);
|
||
|
||
Common::String text;
|
||
|
||
if (sepOffset != Common::String::npos) {
|
||
text = Common::String(line.c_str(), line.c_str() + sepOffset);
|
||
command = Common::String(line.c_str() + sepOffset + 1);
|
||
} else {
|
||
text = line;
|
||
command = "";
|
||
}
|
||
|
||
text.trim();
|
||
command.trim();
|
||
|
||
submenuText += text;
|
||
|
||
if (!submenuText.empty()) {
|
||
if (!command.empty()) {
|
||
while (mainArchive->getScriptContext(kEventScript, commandId)) {
|
||
commandId++;
|
||
}
|
||
mainArchive->replaceCode(command.decode(Common::kMacRoman), kEventScript, commandId);
|
||
submenuText += Common::String::format("[%d];", commandId);
|
||
} else {
|
||
submenuText += ';';
|
||
}
|
||
}
|
||
|
||
if (it == menuStxt.end()) // if we reached end of string, do not increment it but break
|
||
break;
|
||
}
|
||
|
||
if (!submenuText.empty()) {
|
||
menu->createSubMenuFromString(submenu, submenuText.c_str(), 0);
|
||
}
|
||
}
|
||
|
||
void LB::b_label(int nargs) {
|
||
// label functions as marker when the input is an int
|
||
Datum d = g_lingo->pop();
|
||
int marker;
|
||
if (d.type == STRING) {
|
||
marker = g_lingo->func_label(d);
|
||
} else {
|
||
marker = g_lingo->func_marker(d.asInt());
|
||
}
|
||
g_lingo->push(marker);
|
||
}
|
||
|
||
void LB::b_marker(int nargs) {
|
||
// marker functions as label when the input is a string
|
||
Datum d = g_lingo->pop();
|
||
int marker;
|
||
if (d.type == STRING) {
|
||
marker = g_lingo->func_label(d);
|
||
} else {
|
||
marker = g_lingo->func_marker(d.asInt());
|
||
}
|
||
g_lingo->push(marker);
|
||
}
|
||
|
||
void LB::b_move(int nargs) {
|
||
Datum src, dest;
|
||
|
||
if (nargs == 1) {
|
||
int id = (int) g_director->getCurrentMovie()->getCast()->_castArrayStart;
|
||
CastMemberID castId(id, DEFAULT_CAST_LIB);
|
||
Datum d = Datum(castId);
|
||
g_lingo->push(d);
|
||
b_findEmpty(1);
|
||
} else if (nargs == 2) {
|
||
// pass
|
||
} else {
|
||
ARGNUMCHECK(2);
|
||
g_lingo->dropStack(nargs);
|
||
return;
|
||
}
|
||
dest = g_lingo->pop();
|
||
src = g_lingo->pop();
|
||
|
||
// Convert dest datum to type CASTREF if it is INT.
|
||
// Confirmed to always move to DEFAULT_CAST_LIB in D5
|
||
if (dest.type == INT) {
|
||
dest = Datum(CastMemberID(dest.asInt(), DEFAULT_CAST_LIB));
|
||
}
|
||
|
||
// No need to move if src and dest are same
|
||
if (src == dest) {
|
||
return;
|
||
}
|
||
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
CastMember *toMove = movie->getCastMember(src.asMemberID());
|
||
|
||
if (!toMove) {
|
||
warning("b_move: Source CastMember doesn't exist");
|
||
return;
|
||
}
|
||
|
||
g_lingo->push(dest);
|
||
Score *score = movie->getScore();
|
||
uint16 frame = score->getCurrentFrameNum();
|
||
|
||
score->renderFrame(frame, kRenderForceUpdate);
|
||
|
||
movie->duplicateCastMember(src.asMemberID(), dest.asMemberID());
|
||
movie->eraseCastMember(src.asMemberID());
|
||
|
||
score->refreshPointersForCastMemberID(dest.asMemberID());
|
||
score->refreshPointersForCastMemberID(src.asMemberID());
|
||
|
||
score->renderFrame(frame, kRenderForceUpdate);
|
||
}
|
||
|
||
void LB::b_moveableSprite(int nargs) {
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
Score *score = movie->getScore();
|
||
Frame *frame = score->_currentFrame;
|
||
|
||
if (g_lingo->_currentChannelId == -1) {
|
||
warning("b_moveableSprite: channel Id is missing");
|
||
assert(0);
|
||
return;
|
||
}
|
||
|
||
// since we are using value copying, in order to make it taking effect immediately. we modify the sprites in channel
|
||
if (score->_channels[g_lingo->_currentChannelId])
|
||
score->_channels[g_lingo->_currentChannelId]->_sprite->_moveable = true;
|
||
frame->_sprites[g_lingo->_currentChannelId]->_moveable = true;
|
||
}
|
||
|
||
void LB::b_pasteClipBoardInto(int nargs) {
|
||
Datum to = g_lingo->pop();
|
||
if (!g_director->_clipBoard) {
|
||
warning("LB::b_pasteClipBoardInto(): Nothing to paste from clipboard, skipping paste..");
|
||
return;
|
||
}
|
||
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
|
||
CastMember *castMember = movie->getCastMember(*g_director->_clipBoard);
|
||
if (!castMember) {
|
||
warning("LB:B_pasteClipBoardInto(): castMember not found");
|
||
return;
|
||
}
|
||
|
||
Score *score = movie->getScore();
|
||
castMember->setModified(true);
|
||
movie->duplicateCastMember(*g_director->_clipBoard, *to.u.cast);
|
||
score->refreshPointersForCastMemberID(to.asMemberID());
|
||
}
|
||
|
||
static const struct PaletteNames {
|
||
const char *name;
|
||
PaletteType type;
|
||
} paletteNames[] = {
|
||
{ "System", kClutSystemMac },
|
||
{ "System - Mac", kClutSystemMac },
|
||
{ "Rainbow", kClutRainbow },
|
||
{ "Grayscale", kClutGrayscale },
|
||
{ "Pastels", kClutPastels },
|
||
{ "Vivid", kClutVivid },
|
||
{ "NTSC", kClutNTSC },
|
||
{ "Metallic", kClutMetallic },
|
||
//{ "Web 216", },
|
||
//{ "VGA", },
|
||
{ "System - Win", kClutSystemWinD5 },
|
||
{ "SYSTEM - WIN (DIR 4)", kClutSystemWin },
|
||
|
||
// Japanese palette names.
|
||
// TODO: Check encoding. Original is SJIS
|
||
{ "\x83V\x83X\x83""e\x83\x80 - Mac", kClutSystemMac }, // システム - Mac
|
||
{ "\x83\x8C>\x83""C\x83\x93\x83{\x81[", kClutRainbow }, // レインボー
|
||
{ "\x83O\x83>\x8C\x81[\x83X\x83P\x81[\x83\x8B", kClutGrayscale }, // グレースケール
|
||
{ "\x83p\x83>X\x83""e\x83\x8B", kClutPastels }, // パステル
|
||
{ "\x83r\x83>r\x83""b\x83h", kClutVivid }, // ビビッド
|
||
{ "\x83\x81\x83^\x83\x8A\x83""b\x83N", kClutMetallic }, // メタリック
|
||
{ "\x83V\x83X\x83""e\x83\x80 - Win", kClutSystemWinD5 }, // システム - Win
|
||
{ "\x83V\x83X\x83""e\x83\x80 - Win (Dir 4)", kClutSystemWin }, // システム - Win (Dir 4)
|
||
};
|
||
|
||
void LB::b_puppetPalette(int nargs) {
|
||
g_lingo->convertVOIDtoString(0, nargs);
|
||
int numFrames = 0, speed = 0;
|
||
CastMemberID palette(0, 0);
|
||
Datum d;
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
|
||
switch (nargs) {
|
||
case 3:
|
||
numFrames = g_lingo->pop().asInt();
|
||
// fall through
|
||
case 2:
|
||
speed = g_lingo->pop().asInt();
|
||
// fall through
|
||
case 1:
|
||
d = g_lingo->pop();
|
||
|
||
if (d.type == STRING) {
|
||
// TODO: It seems that there are not strings for Mac and Win system palette
|
||
Common::String palStr = d.asString();
|
||
|
||
for (int i = 0; i < ARRAYSIZE(paletteNames); i++) {
|
||
if (palStr.equalsIgnoreCase(paletteNames[i].name)) {
|
||
palette = CastMemberID(paletteNames[i].type, -1);
|
||
if (g_director->getVersion() < 500 && paletteNames[i].type == kClutSystemWinD5)
|
||
palette.member = kClutSystemWin;
|
||
break;
|
||
}
|
||
}
|
||
|
||
}
|
||
if (palette.isNull()) {
|
||
CastMember *member = movie->getCastMember(d.asMemberID());
|
||
|
||
if (member && member->_type == kCastPalette)
|
||
palette = ((PaletteCastMember *)member)->getPaletteId();
|
||
}
|
||
break;
|
||
default:
|
||
ARGNUMCHECK(1);
|
||
return;
|
||
}
|
||
|
||
Score *score = movie->getScore();
|
||
if (!palette.isNull()) {
|
||
g_director->setPalette(palette);
|
||
score->_puppetPalette = true;
|
||
} else {
|
||
// Setting puppetPalette to 0 disables it (Lingo Dictionary, 226)
|
||
|
||
score->_puppetPalette = false;
|
||
|
||
// FIXME: set system palette decided by platform, should be fixed after windows palette is working.
|
||
// try to set mac system palette if lastPalette is 0.
|
||
if (g_director->_lastPalette.isNull())
|
||
g_director->setPalette(CastMemberID(kClutSystemMac, -1));
|
||
else
|
||
g_director->setPalette(g_director->_lastPalette);
|
||
}
|
||
|
||
// TODO: Implement advanced features that use these.
|
||
if (numFrames || speed)
|
||
warning("b_puppetPalette: Skipping extra features");
|
||
}
|
||
|
||
void LB::b_puppetSound(int nargs) {
|
||
|
||
if (nargs < 1 || nargs >= 3) {
|
||
warning("b_puppetSound(): needs 1 or 2 args, got %d", nargs);
|
||
if (nargs < 1)
|
||
return;
|
||
g_lingo->dropStack(nargs - 2);
|
||
}
|
||
|
||
// TODO
|
||
// Midi variant is similar to playAccel
|
||
//
|
||
// and contains these commands:
|
||
//
|
||
// midiBeat
|
||
// midiContinue
|
||
// midiSong
|
||
// midiSongpointer
|
||
// midiStart
|
||
// midiStop
|
||
|
||
DirectorSound *sound = g_director->getCurrentWindow()->getSoundManager();
|
||
Score *score = g_director->getCurrentMovie()->getScore();
|
||
|
||
if (!score) {
|
||
warning("b_puppetSound(): no score");
|
||
return;
|
||
}
|
||
|
||
// Most variants of puppetSound don't actually play the sound
|
||
// until the playback head moves or updateStage is called.
|
||
// So we'll just queue it to be played later.
|
||
|
||
if (nargs == 1) {
|
||
CastMemberID castMember = g_lingo->pop().asMemberID(kCastSound);
|
||
|
||
// in D2 manual p206, puppetSound 0 will turn off the puppet status of sound
|
||
sound->setPuppetSound(castMember, 1);
|
||
} else {
|
||
if (g_director->getVersion() < 400) {
|
||
// in D2/3/3.1 interactivity manual, 2 args represent the menu and submenu sounds
|
||
int submenu = g_lingo->pop().asInt();
|
||
int menu = g_lingo->pop().asInt();
|
||
|
||
if (menu < kMinSampledMenu || menu > kMaxSampledMenu)
|
||
warning("LB::puppetSound: menu number is not available");
|
||
|
||
sound->setPuppetSound(SoundID(kSoundExternal, menu, submenu), 1);
|
||
} else {
|
||
// Two-argument puppetSound is undocumented in D4.
|
||
// It is however documented in the D5 Lingo dictionary.
|
||
Datum arg2 = g_lingo->pop();
|
||
Datum arg1 = g_lingo->pop();
|
||
int channel = 1;
|
||
CastMemberID castMember;
|
||
if (arg1.type == STRING) {
|
||
// Apparently if the first argument is a string, it will be evaluated as per the 1-arg case
|
||
castMember = arg1.asMemberID(kCastSound);
|
||
} else {
|
||
// FIXME: Figure out how to deal with multilib in D5+
|
||
castMember = arg2.asMemberID(kCastSound);
|
||
channel = arg1.asInt();
|
||
}
|
||
sound->setPuppetSound(castMember, channel);
|
||
|
||
// The D4 two-arg variant of puppetSound plays
|
||
// immediately for some inexplicable reason.
|
||
sound->playPuppetSound(channel);
|
||
}
|
||
}
|
||
}
|
||
|
||
void LB::b_immediateSprite(int nargs) {
|
||
Score *sc = g_director->getCurrentMovie()->getScore();
|
||
if (!sc) {
|
||
warning("b_immediateSprite: no score");
|
||
g_lingo->dropStack(nargs);
|
||
return;
|
||
}
|
||
|
||
if (nargs == 2) {
|
||
Datum state = g_lingo->pop();
|
||
Datum sprite = g_lingo->pop();
|
||
|
||
if ((uint)sprite.asInt() < sc->_channels.size()) {
|
||
sc->getSpriteById(sprite.asInt())->_immediate = (bool)state.asInt();
|
||
} else {
|
||
warning("b_immediateSprite: sprite index out of bounds");
|
||
}
|
||
} else if (nargs == 0 && g_director->getVersion() < 400) {
|
||
g_lingo->dropStack(nargs);
|
||
|
||
if (g_lingo->_currentChannelId == -1) {
|
||
warning("b_immediateSprite: channel Id is missing");
|
||
return;
|
||
}
|
||
sc->getSpriteById(g_lingo->_currentChannelId)->_immediate = true;
|
||
} else {
|
||
warning("b_immediateSprite: unexpectedly received %d arguments", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
}
|
||
|
||
void LB::b_puppetSprite(int nargs) {
|
||
Score *sc = g_director->getCurrentMovie()->getScore();
|
||
if (!sc) {
|
||
warning("b_puppetSprite: no score");
|
||
g_lingo->dropStack(nargs);
|
||
return;
|
||
}
|
||
|
||
if (nargs == 2) {
|
||
Datum state = g_lingo->pop();
|
||
Datum sprite = g_lingo->pop();
|
||
|
||
if ((uint)sprite.asInt() < sc->_channels.size()) {
|
||
sc->getSpriteById(sprite.asInt())->_puppet = (bool)state.asInt();
|
||
} else {
|
||
warning("b_puppetSprite: sprite index out of bounds");
|
||
}
|
||
} else if (nargs == 0 && g_director->getVersion() < 400) {
|
||
g_lingo->dropStack(nargs);
|
||
|
||
if (g_lingo->_currentChannelId == -1) {
|
||
warning("b_puppetSprite: channel Id is missing");
|
||
return;
|
||
}
|
||
sc->getSpriteById(g_lingo->_currentChannelId)->_puppet = true;
|
||
} else {
|
||
warning("b_puppetSprite: unexpectedly received %d arguments", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
}
|
||
|
||
void LB::b_puppetTempo(int nargs) {
|
||
g_director->getCurrentMovie()->getScore()->setPuppetTempo(g_lingo->pop().asInt());
|
||
}
|
||
|
||
void LB::b_puppetTransition(int nargs) {
|
||
// puppetTransition whichTransition [, time] [, chunkSize] [, changeArea]
|
||
Window *stage = g_director->getCurrentWindow();
|
||
uint16 duration = 250, area = 1, chunkSize = 1, type = 0;
|
||
|
||
TransitionCastMember *tcast = nullptr;
|
||
|
||
switch (nargs) {
|
||
case 4:
|
||
area = g_lingo->pop().asInt();
|
||
// fall through
|
||
case 3:
|
||
chunkSize = g_lingo->pop().asInt();
|
||
// fall through
|
||
case 2:
|
||
// units are quarter-seconds
|
||
duration = g_lingo->pop().asInt() * 250;
|
||
// fall through
|
||
case 1:
|
||
{
|
||
Datum d = g_lingo->pop();
|
||
if (d.type == CASTREF) {
|
||
CastMemberID castId = d.asMemberID();
|
||
CastMember *cast = stage->getCurrentMovie()->getCastMember(castId);
|
||
if (cast->_type == kCastTransition) {
|
||
tcast = (TransitionCastMember *)cast;
|
||
} else {
|
||
warning("b_puppetTransition: expected transition cast member");
|
||
return;
|
||
}
|
||
} else {
|
||
type = ((TransitionType)(d.asInt()));
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
ARGNUMCHECK(1);
|
||
g_lingo->dropStack(nargs);
|
||
return;
|
||
}
|
||
|
||
if (stage->_puppetTransition) {
|
||
warning("b_puppetTransition: Transition already queued");
|
||
delete stage->_puppetTransition;
|
||
stage->_puppetTransition = nullptr;
|
||
}
|
||
if (tcast) {
|
||
stage->_puppetTransition = new TransParams(tcast->_durationMillis, tcast->_area, tcast->_chunkSize, tcast->_transType);
|
||
} else {
|
||
stage->_puppetTransition = new TransParams(duration, area, chunkSize, ((TransitionType)type));
|
||
}
|
||
debugC(3, kDebugImages, "b_puppetTransition(): type: %d, duration: %d, chunkSize: %d, area: %d",
|
||
stage->_puppetTransition->type, stage->_puppetTransition->duration, stage->_puppetTransition->chunkSize, stage->_puppetTransition->area);
|
||
|
||
}
|
||
|
||
void LB::b_ramNeeded(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum d2 = g_lingo->pop();
|
||
|
||
// We do not need RAM, we have it all
|
||
|
||
g_lingo->push(Datum(0));
|
||
}
|
||
|
||
void LB::b_rollOver(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(0);
|
||
int arg = 0;
|
||
if (d.type == SPRITEREF) {
|
||
arg = d.u.i;
|
||
} else {
|
||
arg = d.asInt();
|
||
}
|
||
|
||
Score *score = g_director->getCurrentMovie()->getScore();
|
||
|
||
if (!score) {
|
||
warning("b_rollOver: Reference to an empty score");
|
||
return;
|
||
}
|
||
|
||
if (arg >= (int32) score->_channels.size()) {
|
||
g_lingo->push(res);
|
||
return;
|
||
}
|
||
|
||
Common::Point pos = g_director->getCurrentWindow()->getMousePos();
|
||
|
||
if (score->checkSpriteRollOver(arg, pos))
|
||
res.u.i = 1; // TRUE
|
||
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_sendAllSprites(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_sendAllSprites", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_sendSprite(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_sendSprite", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_spriteBox(int nargs) {
|
||
int b = g_lingo->pop().asInt();
|
||
int r = g_lingo->pop().asInt();
|
||
int t = g_lingo->pop().asInt();
|
||
int l = g_lingo->pop().asInt();
|
||
int spriteId = g_lingo->pop().asInt();
|
||
Channel *channel = g_director->getCurrentMovie()->getScore()->getChannelById(spriteId);
|
||
|
||
if (!channel)
|
||
return;
|
||
|
||
// This automatically sets the stretch mode
|
||
channel->_sprite->_stretch = true;
|
||
|
||
g_director->getCurrentWindow()->addDirtyRect(channel->getBbox());
|
||
channel->setBbox(
|
||
l < r ? l : r,
|
||
t < b ? t : b,
|
||
r > l ? r : l,
|
||
b > t ? b : t
|
||
);
|
||
if (channel->_sprite->_cast)
|
||
channel->_sprite->_cast->setModified(true);
|
||
channel->_dirty = true;
|
||
}
|
||
|
||
void LB::b_unLoad(int nargs) {
|
||
// No op for us, we do not unload casts
|
||
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_unLoadCast(int nargs) {
|
||
// No op for us, we do not unload casts
|
||
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_unLoadMovie(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_unLoadMovie", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_zoomBox(int nargs) {
|
||
// zoomBox startSprite, endSprite [, delatTicks]
|
||
// ticks are in 1/60th, default 1
|
||
if (nargs < 2 || nargs > 3) {
|
||
warning("b_zoomBox: expected 2 or 3 arguments, got %d", nargs);
|
||
|
||
g_lingo->dropStack(nargs);
|
||
|
||
return;
|
||
}
|
||
|
||
int delayTicks = 1;
|
||
if (nargs > 2) {
|
||
Datum d = g_lingo->pop();
|
||
delayTicks = d.asInt();
|
||
}
|
||
|
||
int endSpriteId = g_lingo->pop().asInt();
|
||
int startSpriteId = g_lingo->pop().asInt();
|
||
|
||
Score *score = g_director->getCurrentMovie()->getScore();
|
||
uint16 curFrame = score->getCurrentFrameNum();
|
||
|
||
Common::Rect startRect = score->_channels[startSpriteId]->getBbox();
|
||
if (startRect.isEmpty()) {
|
||
warning("b_zoomBox: unknown start sprite #%d", startSpriteId);
|
||
return;
|
||
}
|
||
|
||
// Looks for endSprite in the current frame, otherwise
|
||
// Looks for endSprite in the next frame
|
||
Common::Rect endRect = score->_channels[endSpriteId]->getBbox();
|
||
if (endRect.isEmpty()) {
|
||
if ((uint)curFrame + 1 < score->getFramesNum()) {
|
||
Frame *nextFrame = score->getFrameData(curFrame + 1);
|
||
Channel endChannel(nullptr, nextFrame->_sprites[endSpriteId]);
|
||
endRect = endChannel.getBbox();
|
||
delete nextFrame;
|
||
}
|
||
}
|
||
|
||
if (endRect.isEmpty()) {
|
||
if ((uint)curFrame - 1 > 0) {
|
||
Frame *prevFrame = score->getFrameData(curFrame - 1);
|
||
Channel endChannel(nullptr, prevFrame->_sprites[endSpriteId]);
|
||
endRect = endChannel.getBbox();
|
||
delete prevFrame;
|
||
}
|
||
}
|
||
|
||
if (endRect.isEmpty()) {
|
||
warning("b_zoomBox: unknown end sprite #%d", endSpriteId);
|
||
return;
|
||
}
|
||
|
||
if (Director::g_director->desktopEnabled()) {
|
||
Director::Datum stageRect = Director::g_director->getStage()->getStageRect();
|
||
Common::Point stageOffset(stageRect.u.farr->arr[0].asInt(), stageRect.u.farr->arr[1].asInt());
|
||
startRect.translate(stageOffset.x, stageOffset.y);
|
||
endRect.translate(stageOffset.x, stageOffset.y);
|
||
}
|
||
|
||
Graphics::ZoomBox *box = new Graphics::ZoomBox;
|
||
box->start = startRect;
|
||
box->end = endRect;
|
||
box->delay = delayTicks;
|
||
box->step = 0;
|
||
box->startTime = g_system->getMillis();
|
||
box->nextTime = g_system->getMillis() + 1000 * box->step / 60;
|
||
|
||
g_director->_wm->addZoomBox(box);
|
||
}
|
||
|
||
void LB::b_updateStage(int nargs) {
|
||
if (g_director->getGameGID() == GID_TEST) {
|
||
warning("b_updateStage: Skipping due to tests");
|
||
|
||
return;
|
||
}
|
||
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
if (!movie) {
|
||
warning("b_updateStage: no movie");
|
||
|
||
return;
|
||
}
|
||
|
||
Score *score = movie->getScore();
|
||
Window *window = movie->getWindow();
|
||
|
||
if (score->_disableGoPlayUpdateStage) {
|
||
warning("Lingo::b_updateStage(): ignoring updateStage due to disableGoPlayUpdateStage flag");
|
||
return;
|
||
}
|
||
|
||
score->updateWidgets(movie->_videoPlayback);
|
||
if (window->_puppetTransition) {
|
||
window->playTransition(score->getCurrentFrameNum(), kRenderModeNormal, window->_puppetTransition->duration, window->_puppetTransition->area, window->_puppetTransition->chunkSize, window->_puppetTransition->type, score->_currentFrame->_mainChannels.scoreCachedPaletteId);
|
||
delete window->_puppetTransition;
|
||
window->_puppetTransition = nullptr;
|
||
} else {
|
||
movie->getWindow()->render();
|
||
}
|
||
|
||
// play any puppet sounds that have been queued
|
||
score->playSoundChannel(true);
|
||
|
||
if (score->_cursorDirty) {
|
||
score->renderCursor(movie->getWindow()->getMousePos());
|
||
score->_cursorDirty = false;
|
||
}
|
||
|
||
g_director->draw();
|
||
|
||
if (debugChannelSet(-1, kDebugFewFramesOnly)) {
|
||
g_director->_framesRan++;
|
||
warning("LB::b_updateStage(): ran frame %0d", g_director->_framesRan);
|
||
|
||
if (g_director->_framesRan > kFewFamesMaxCounter) {
|
||
warning("b_updateStage(): exiting due to debug few frames only");
|
||
score->_playState = kPlayStopped;
|
||
}
|
||
}
|
||
}
|
||
|
||
///////////////////
|
||
// Score recording
|
||
///////////////////
|
||
|
||
void LB::b_clearFrame(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_clearFrame", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_deleteFrame(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_deleteFrame", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_duplicateFrame(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_duplicateFrame", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_insertFrame(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_insertFrame", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_updateFrame(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_updateFrame", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
///////////////////
|
||
// Point
|
||
///////////////////
|
||
void LB::b_point(int nargs) {
|
||
Datum y(g_lingo->pop().asInt());
|
||
Datum x(g_lingo->pop().asInt());
|
||
Datum d;
|
||
|
||
d.u.farr = new FArray;
|
||
|
||
d.u.farr->arr.push_back(x);
|
||
d.u.farr->arr.push_back(y);
|
||
d.type = POINT;
|
||
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
void LB::b_rect(int nargs) {
|
||
Datum d(0);
|
||
|
||
if (nargs == 4) {
|
||
Datum bottom(g_lingo->pop().asInt());
|
||
Datum right(g_lingo->pop().asInt());
|
||
Datum top(g_lingo->pop().asInt());
|
||
Datum left(g_lingo->pop().asInt());
|
||
|
||
d.u.farr = new FArray;
|
||
d.u.farr->arr.push_back(left);
|
||
d.u.farr->arr.push_back(top);
|
||
d.u.farr->arr.push_back(right);
|
||
d.u.farr->arr.push_back(bottom);
|
||
d.type = RECT;
|
||
} else if (nargs == 2) {
|
||
Datum p2 = g_lingo->pop();
|
||
Datum p1 = g_lingo->pop();
|
||
|
||
if (p2.type == POINT && p1.type == POINT) {
|
||
d.u.farr = new FArray;
|
||
d.u.farr->arr.push_back(p1.u.farr->arr[0]);
|
||
d.u.farr->arr.push_back(p1.u.farr->arr[1]);
|
||
d.u.farr->arr.push_back(p2.u.farr->arr[0]);
|
||
d.u.farr->arr.push_back(p2.u.farr->arr[1]);
|
||
d.type = RECT;
|
||
} else
|
||
warning("LB::b_rect: Rect need 2 Point variable as argument");
|
||
|
||
} else {
|
||
warning("LB::b_rect: Rect doesn't support %d args", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
|
||
void LB::b_intersect(int nargs) {
|
||
Datum d;
|
||
Datum r2 = g_lingo->pop();
|
||
Datum r1 = g_lingo->pop();
|
||
TYPECHECK(r1, RECT);
|
||
TYPECHECK(r2, RECT);
|
||
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());
|
||
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());
|
||
|
||
d = rect1.intersects(rect2);
|
||
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
void LB::b_inflate(int nargs) {
|
||
int inflateHeight = g_lingo->pop().asInt();
|
||
int inflateWidth = g_lingo->pop().asInt();
|
||
Datum sR = g_lingo->pop();
|
||
|
||
TYPECHECK(sR, RECT);
|
||
|
||
Common::Rect sourceRect(sR.u.farr->arr[0].asInt(),
|
||
sR.u.farr->arr[1].asInt(),
|
||
sR.u.farr->arr[2].asInt(),
|
||
sR.u.farr->arr[3].asInt());
|
||
|
||
int inflatedLeft = sourceRect.left - inflateWidth;
|
||
int inflatedTop = sourceRect.top - inflateHeight;
|
||
int inflatedRight = sourceRect.right + inflateWidth;
|
||
int inflatedBottom = sourceRect.bottom + inflateHeight;
|
||
|
||
Datum inflatedRect;
|
||
inflatedRect.type = RECT;
|
||
|
||
inflatedRect.u.farr = new FArray();
|
||
inflatedRect.u.farr->arr.push_back(Datum(inflatedLeft));
|
||
inflatedRect.u.farr->arr.push_back(Datum(inflatedTop));
|
||
inflatedRect.u.farr->arr.push_back(Datum(inflatedRight));
|
||
inflatedRect.u.farr->arr.push_back(Datum(inflatedBottom));
|
||
|
||
g_lingo->push(inflatedRect);
|
||
}
|
||
|
||
void LB::b_inside(int nargs) {
|
||
Datum d;
|
||
Datum r2 = g_lingo->pop();
|
||
Datum p1 = g_lingo->pop();
|
||
TYPECHECK(r2, RECT);
|
||
TYPECHECK(p1, POINT);
|
||
|
||
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());
|
||
Common::Point point1(p1.u.farr->arr[0].asInt(), p1.u.farr->arr[1].asInt());
|
||
|
||
d = rect2.contains(point1);
|
||
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
void LB::b_map(int nargs) {
|
||
Datum toRect = g_lingo->pop();
|
||
Datum fromRect = g_lingo->pop();
|
||
Datum srcArr = g_lingo->pop();
|
||
|
||
if (!(toRect.type == RECT || (toRect.type == ARRAY && toRect.u.farr->arr.size() == 4)) ||
|
||
!(fromRect.type == RECT || (fromRect.type == ARRAY && fromRect.u.farr->arr.size() == 4))) {
|
||
warning("LB::b_map(): Invalid Datum Type of source and destination Rects");
|
||
return;
|
||
}
|
||
|
||
int toWidth = toRect.u.farr->arr[2].u.i - toRect.u.farr->arr[0].u.i;
|
||
int toHeight = toRect.u.farr->arr[3].u.i - toRect.u.farr->arr[1].u.i;
|
||
int fromWidth = fromRect.u.farr->arr[2].u.i - fromRect.u.farr->arr[0].u.i;
|
||
int fromHeight = fromRect.u.farr->arr[3].u.i - fromRect.u.farr->arr[1].u.i;
|
||
|
||
if (!(srcArr.type == POINT ||
|
||
srcArr.type == RECT ||
|
||
(srcArr.type == ARRAY && (srcArr.u.farr->arr.size() == 2 || srcArr.u.farr->arr.size() == 4)))) {
|
||
warning("LB::b_map(): Invalid Datum type of input Point / Rect");
|
||
return;
|
||
}
|
||
|
||
Datum d;
|
||
d.type = POINT;
|
||
d.u.farr = new FArray();
|
||
d.u.farr->arr.push_back((srcArr.u.farr->arr[0].u.i - fromRect.u.farr->arr[0].u.i) * (toWidth / fromWidth) + toRect.u.farr->arr[0].u.i);
|
||
d.u.farr->arr.push_back((srcArr.u.farr->arr[1].u.i - fromRect.u.farr->arr[1].u.i) * (toHeight / fromHeight) + toRect.u.farr->arr[1].u.i);
|
||
|
||
if (srcArr.type == RECT ||
|
||
(srcArr.type == ARRAY && srcArr.u.farr->arr.size() == 4)) {
|
||
d.type = RECT;
|
||
d.u.farr->arr.push_back((srcArr.u.farr->arr[2].u.i - srcArr.u.farr->arr[0].u.i) * (toWidth / fromWidth) + d.u.farr->arr[0].u.i);
|
||
d.u.farr->arr.push_back((srcArr.u.farr->arr[3].u.i - srcArr.u.farr->arr[1].u.i) * (toWidth / fromWidth) + d.u.farr->arr[1].u.i);
|
||
}
|
||
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
void LB::b_offsetRect(int nargs) {
|
||
Datum vert = g_lingo->pop();
|
||
Datum hori = g_lingo->pop();
|
||
Datum rect = g_lingo->pop();
|
||
|
||
if (vert.type != INT ||
|
||
hori.type != INT ||
|
||
!(rect.type == RECT || (rect.type == ARRAY && rect.u.farr->arr.size() >= 4))) {
|
||
warning(" LB::b_offsetRect(): Invalid DatumType of inputs");
|
||
g_lingo->push(rect);
|
||
}
|
||
|
||
//When vert is positive, rect moves higher
|
||
//When hori is positive, rect moves towards right
|
||
|
||
rect.u.farr->arr[0].u.i += hori.u.i;
|
||
rect.u.farr->arr[2].u.i += hori.u.i;
|
||
rect.u.farr->arr[1].u.i -= vert.u.i;
|
||
rect.u.farr->arr[3].u.i -= vert.u.i;
|
||
|
||
|
||
g_lingo->push(rect);
|
||
}
|
||
|
||
void LB::b_union(int nargs) {
|
||
if (nargs != 2) {
|
||
warning("Wrong number of arguments for b_union: Expected 2, got %d", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum(0));
|
||
return;
|
||
}
|
||
|
||
Datum rect1 = g_lingo->pop();
|
||
Datum rect2 = g_lingo->pop();
|
||
|
||
if (rect1.type != RECT || rect2.type != RECT) {
|
||
warning("Wrong type of arguments for b_union");
|
||
g_lingo->push(Datum(0));
|
||
return;
|
||
}
|
||
|
||
Datum res;
|
||
res.type = RECT;
|
||
|
||
res.u.farr = new FArray();
|
||
res.u.farr->arr.push_back(Datum(MIN(rect1.u.farr->arr[0].u.i, rect2.u.farr->arr[0].u.i)));
|
||
res.u.farr->arr.push_back(Datum(MIN(rect1.u.farr->arr[1].u.i, rect2.u.farr->arr[1].u.i)));
|
||
res.u.farr->arr.push_back(Datum(MAX(rect1.u.farr->arr[2].u.i, rect2.u.farr->arr[2].u.i)));
|
||
res.u.farr->arr.push_back(Datum(MAX(rect1.u.farr->arr[3].u.i, rect2.u.farr->arr[3].u.i)));
|
||
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
|
||
///////////////////
|
||
// Sound
|
||
///////////////////
|
||
void LB::b_beep(int nargs) {
|
||
int repeat = 1;
|
||
if (nargs == 1) {
|
||
Datum d = g_lingo->pop();
|
||
repeat = d.u.i;
|
||
}
|
||
g_lingo->func_beep(repeat);
|
||
}
|
||
|
||
void LB::b_isPastCuePoint(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_isPastCuePoint", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum());
|
||
}
|
||
|
||
void LB::b_mci(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
|
||
g_lingo->func_mci(d.asString());
|
||
}
|
||
|
||
void LB::b_mciwait(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
|
||
g_lingo->func_mciwait(d.asString());
|
||
}
|
||
|
||
void LB::b_sound(int nargs) {
|
||
// Builtin function for sound as used by the Director bytecode engine.
|
||
//
|
||
// Accepted arguments:
|
||
// "close", INT soundChannel
|
||
// "fadeIn", INT soundChannel(, INT ticks)
|
||
// "fadeOut", INT soundChannel(, INT ticks)
|
||
// "playFile", INT soundChannel, STRING fileName
|
||
// "stop", INT soundChannel
|
||
|
||
if (nargs < 2 || nargs > 3) {
|
||
warning("b_sound: expected 2 or 3 args, not %d", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
|
||
return;
|
||
}
|
||
|
||
int ticks;
|
||
Datum secondArg = g_lingo->pop();
|
||
Datum firstArg = g_lingo->pop();
|
||
Datum verb;
|
||
if (nargs > 2) {
|
||
verb = g_lingo->pop();
|
||
} else {
|
||
verb = firstArg;
|
||
firstArg = secondArg;
|
||
}
|
||
|
||
if (verb.type != STRING && verb.type != SYMBOL) {
|
||
warning("b_sound: verb arg should be of type STRING, not %s", verb.type2str());
|
||
return;
|
||
}
|
||
|
||
DirectorSound *soundManager = g_director->getCurrentWindow()->getSoundManager();
|
||
Score *score = g_director->getCurrentMovie()->getScore();
|
||
|
||
if (verb.u.s->equalsIgnoreCase("close") || verb.u.s->equalsIgnoreCase("stop")) {
|
||
if (nargs != 2) {
|
||
warning("sound %s: expected 1 argument, got %d", verb.u.s->c_str(), nargs - 1);
|
||
return;
|
||
}
|
||
|
||
TYPECHECK(firstArg, INT);
|
||
soundManager->stopSound(firstArg.u.i);
|
||
} else if (verb.u.s->equalsIgnoreCase("fadeIn")) {
|
||
if (nargs > 2) {
|
||
TYPECHECK2(secondArg, INT, FLOAT);
|
||
ticks = secondArg.asInt();
|
||
} else {
|
||
ticks = 15 * (60 / score->_currentFrameRate);
|
||
}
|
||
|
||
TYPECHECK(firstArg, INT);
|
||
soundManager->registerFade(firstArg.u.i, true, ticks);
|
||
score->_activeFade = true;
|
||
return;
|
||
} else if (verb.u.s->equalsIgnoreCase("fadeOut")) {
|
||
if (nargs > 2) {
|
||
TYPECHECK2(secondArg, INT, FLOAT);
|
||
ticks = secondArg.asInt();
|
||
} else {
|
||
ticks = 15 * (60 / score->_currentFrameRate);
|
||
}
|
||
|
||
TYPECHECK2(firstArg, INT, FLOAT);
|
||
soundManager->registerFade(firstArg.asInt(), false, ticks);
|
||
score->_activeFade = true;
|
||
return;
|
||
} else if (verb.u.s->equalsIgnoreCase("playFile")) {
|
||
ARGNUMCHECK(3)
|
||
|
||
TYPECHECK(firstArg, INT);
|
||
TYPECHECK(secondArg, STRING);
|
||
|
||
soundManager->playFile(*secondArg.u.s, firstArg.u.i);
|
||
} else {
|
||
warning("b_sound: unknown verb %s", verb.u.s->c_str());
|
||
}
|
||
}
|
||
|
||
void LB::b_soundBusy(int nargs) {
|
||
DirectorSound *sound = g_director->getCurrentWindow()->getSoundManager();
|
||
Datum whichChannel = g_lingo->pop();
|
||
|
||
// Horror Tour 2 calls this with a void argument
|
||
TYPECHECK2(whichChannel, INT, VOID);
|
||
int channel = whichChannel.u.i;
|
||
if (whichChannel.type == VOID) {
|
||
channel = 1;
|
||
}
|
||
|
||
bool isBusy = sound->isChannelActive(channel);
|
||
Datum result;
|
||
result.type = INT;
|
||
result.u.i = isBusy ? 1 : 0;
|
||
g_lingo->push(result);
|
||
}
|
||
|
||
///////////////////
|
||
// Constants
|
||
///////////////////
|
||
void LB::b_backspace(int nargs) {
|
||
g_lingo->push(Datum(Common::String("\b")));
|
||
}
|
||
|
||
void LB::b_empty(int nargs) {
|
||
g_lingo->push(Datum(Common::String("")));
|
||
}
|
||
|
||
void LB::b_enter(int nargs) {
|
||
g_lingo->push(Datum(Common::String("\03")));
|
||
}
|
||
|
||
void LB::b_false(int nargs) {
|
||
g_lingo->push(Datum(0));
|
||
}
|
||
|
||
void LB::b_quote(int nargs) {
|
||
g_lingo->push(Datum(Common::String("\"")));
|
||
}
|
||
|
||
void LB::b_returnconst(int nargs) {
|
||
g_lingo->push(Datum(Common::String("\r")));
|
||
}
|
||
|
||
void LB::b_tab(int nargs) {
|
||
g_lingo->push(Datum(Common::String("\t")));
|
||
}
|
||
|
||
void LB::b_true(int nargs) {
|
||
g_lingo->push(Datum(1));
|
||
}
|
||
|
||
void LB::b_version(int nargs) {
|
||
int major = g_director->getVersion() / 100;
|
||
int minor = (g_director->getVersion() / 10) % 10;
|
||
int patch = g_director->getVersion() % 10;
|
||
Common::String res;
|
||
if (patch) {
|
||
res = Common::String::format("%d.%d.%d", major, minor, patch);
|
||
} else {
|
||
res = Common::String::format("%d.%d", major, minor);
|
||
}
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
///////////////////
|
||
// References
|
||
///////////////////
|
||
void LB::b_cast(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res = d.asMemberID();
|
||
res.type = CASTREF;
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_castLib(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(0);
|
||
if (d.type == STRING) {
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
res.u.i = movie->getCastLibIDByName(*d.u.s);
|
||
} else {
|
||
res.u.i = d.asInt();
|
||
}
|
||
res.type = CASTLIBREF;
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_member(int nargs) {
|
||
CastMemberID res;
|
||
if (nargs == 1) {
|
||
Datum member = g_lingo->pop();
|
||
res = member.asMemberID();
|
||
} else if (nargs == 2) {
|
||
Datum library = g_lingo->pop();
|
||
Datum member = g_lingo->pop();
|
||
res = g_lingo->toCastMemberID(member, library);
|
||
}
|
||
|
||
if (res.member > g_lingo->getMembersNum(res.castLib)) {
|
||
if (g_director->getVersion() < 600) {
|
||
g_lingo->lingoError("b_member: Cast member ID out of range");
|
||
return;
|
||
}
|
||
}
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_script(int nargs) {
|
||
CastMemberID memberID;
|
||
if (nargs == 1) {
|
||
Datum member = g_lingo->pop();
|
||
memberID = member.asMemberID();
|
||
} else if (nargs == 2) {
|
||
Datum library = g_lingo->pop();
|
||
Datum member = g_lingo->pop();
|
||
memberID = g_lingo->toCastMemberID(member, library);
|
||
}
|
||
CastMember *cast = g_director->getCurrentMovie()->getCastMember(memberID);
|
||
|
||
if (cast) {
|
||
ScriptContext *script = nullptr;
|
||
|
||
if (cast->_type == kCastLingoScript) {
|
||
// script cast can be either a movie script, score script, or parent script (D5+)
|
||
script = g_director->getCurrentMovie()->getScriptContext(kMovieScript, memberID);
|
||
if (!script)
|
||
script = g_director->getCurrentMovie()->getScriptContext(kScoreScript, memberID);
|
||
if (!script)
|
||
script = g_director->getCurrentMovie()->getScriptContext(kParentScript, memberID);
|
||
} else {
|
||
script = g_director->getCurrentMovie()->getScriptContext(kCastScript, memberID);
|
||
}
|
||
|
||
if (script) {
|
||
g_lingo->push(script);
|
||
return;
|
||
}
|
||
}
|
||
warning("b_script(): No script context found for '%s'", memberID.asString().c_str());
|
||
g_lingo->push(Datum());
|
||
}
|
||
|
||
void LB::b_sprite(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum res(d.asInt());
|
||
res.type = SPRITEREF;
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_window(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
FArray *windowList = g_lingo->_windowList.u.farr;
|
||
|
||
// Refer window by-indexing, lingo can request using "window #index" where #index is the index of window that is previously
|
||
// created, in tutorial workshop `rect of window`, a window is created using 'open(window "ball")' and the same window is
|
||
// referenced by 'window 1', ie 'put the rect of window 1 into field 9'
|
||
if (d.type == INT || d.type == FLOAT) {
|
||
int windowIndex = d.asInt() - 1;
|
||
|
||
if (windowIndex >= 0 && windowIndex < (int)windowList->arr.size()) {
|
||
if (windowList->arr[windowIndex].type == OBJECT && windowList->arr[windowIndex].u.obj->getObjType() == kWindowObj) {
|
||
Window *window = static_cast<Window *>(windowList->arr[windowIndex].u.obj);
|
||
g_lingo->push(window);
|
||
return;
|
||
}
|
||
} else {
|
||
warning("LB::b_window: Window referenced by index %d, out of bounds.", windowIndex);
|
||
}
|
||
}
|
||
|
||
Common::String windowName = d.asString();
|
||
Window *window = g_director->getOrCreateWindow(windowName);
|
||
bool isNewWindow = true;
|
||
for (auto &it : windowList->arr) {
|
||
if (it.type == OBJECT && it.u.obj == window) {
|
||
isNewWindow = false;
|
||
break;
|
||
}
|
||
}
|
||
if (isNewWindow)
|
||
windowList->arr.push_back(Datum(window));
|
||
|
||
g_lingo->push(window);
|
||
}
|
||
|
||
void LB::b_windowPresent(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_windowPresent", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum(1));
|
||
}
|
||
|
||
void LB::b_charPosToLoc(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_charPosToLoc", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
Datum res(Common::Point(0, 0));
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_linePosToLocV(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_linePosToLocV", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
Datum res(0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_locToCharPos(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_locToCharPos", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
Datum res(0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_locVToLinePos(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_locVToLinePos", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
Datum res(0);
|
||
g_lingo->push(res);
|
||
}
|
||
|
||
void LB::b_scrollByLine(int nargs) {
|
||
ARGNUMCHECK(2);
|
||
Datum count = g_lingo->pop();
|
||
Datum member = g_lingo->pop();
|
||
CastMemberID id = member.asMemberID();
|
||
Movie *movie = g_director->getCurrentMovie();
|
||
CastMember *cast = movie->getCastMember(id);
|
||
if (!cast) {
|
||
g_lingo->lingoError("b_scrollByLine: Could not resolve cast member %s", id.asString().c_str());
|
||
} else if (cast->_type != kCastText) {
|
||
g_lingo->lingoError("b_scrollByLine: Expected cast member %s to be text, got %s", id.asString().c_str(), castType2str(cast->_type));
|
||
} else {
|
||
((TextCastMember *)cast)->scrollByLine(count.asInt());
|
||
}
|
||
|
||
}
|
||
|
||
void LB::b_scrollByPage(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_scrollByPage", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_lineHeight(int nargs) {
|
||
Datum lineNum = g_lingo->pop();
|
||
Datum castRef = g_lingo->pop();
|
||
|
||
if (castRef.type != CASTREF) {
|
||
g_lingo->lingoError("Incorrect argument type for lineHeight");
|
||
g_lingo->push(1);
|
||
return;
|
||
}
|
||
|
||
CastMember *member = g_director->getCurrentMovie()->getCastMember(*castRef.u.cast);
|
||
|
||
if (member->_type != kCastText) {
|
||
g_lingo->lingoError("Incorrect member type for lineHeight");
|
||
g_lingo->push(1);
|
||
return;
|
||
}
|
||
|
||
// MacText::getLineHeigt() is zero-indexed, we are one-indexed
|
||
int lineHeight = ((TextCastMember *)member)->getLineHeight(lineNum.u.i - 1);
|
||
g_lingo->push(lineHeight);
|
||
}
|
||
|
||
void LB::b_numberofchars(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum chunkRef = LC::lastChunk(kChunkChar, d);
|
||
g_lingo->push(chunkRef.u.cref->startChunk);
|
||
}
|
||
|
||
void LB::b_numberofitems(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum chunkRef = LC::lastChunk(kChunkItem, d);
|
||
g_lingo->push(chunkRef.u.cref->startChunk);
|
||
}
|
||
|
||
void LB::b_numberoflines(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum chunkRef = LC::lastChunk(kChunkLine, d);
|
||
g_lingo->push(chunkRef.u.cref->startChunk);
|
||
}
|
||
|
||
void LB::b_numberofwords(int nargs) {
|
||
Datum d = g_lingo->pop();
|
||
Datum chunkRef = LC::lastChunk(kChunkWord, d);
|
||
g_lingo->push(chunkRef.u.cref->startChunk);
|
||
}
|
||
|
||
void LB::b_trackCount(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_trackCount", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(1);
|
||
}
|
||
|
||
void LB::b_trackStartTime(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_trackStartTime", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(0);
|
||
}
|
||
|
||
void LB::b_trackStopTime(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_trackStopTime", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(0);
|
||
}
|
||
|
||
void LB::b_trackType(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_trackType", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
// Possible values are #video, #sound, #text, and #music
|
||
// also #timeCode is present but seemingly not used
|
||
Datum result("video");
|
||
result.type = SYMBOL;
|
||
g_lingo->push(result);
|
||
}
|
||
|
||
void LB::b_scummvmassert(int nargs) {
|
||
Datum line = g_lingo->pop();
|
||
Datum d = g_lingo->pop();
|
||
|
||
if (d.asInt() == 0) {
|
||
warning("BUILDBOT: LB::b_scummvmassert: is false at line %d", line.asInt());
|
||
}
|
||
if (debugChannelSet(-1, kDebugLingoStrict)) {
|
||
assert(d.asInt() != 0);
|
||
}
|
||
}
|
||
|
||
void LB::b_scummvmassertequal(int nargs) {
|
||
Datum line = g_lingo->pop();
|
||
Datum d2 = g_lingo->pop();
|
||
Datum d1 = g_lingo->pop();
|
||
|
||
int result;
|
||
|
||
if (d1.type == ARRAY && d2.type == ARRAY) {
|
||
result = LC::eqData(d1, d2).u.i;
|
||
} else if (d1.type == PARRAY && d2.type == PARRAY) {
|
||
result = LC::eqData(d1, d2).u.i;
|
||
} else {
|
||
result = (d1 == d2);
|
||
}
|
||
|
||
if (!result) {
|
||
warning("BUILDBOT: LB::b_scummvmassertequal: %s is not equal %s at line %d", formatStringForDump(d1.asString()).c_str(), formatStringForDump(d2.asString()).c_str(), line.asInt());
|
||
}
|
||
if (debugChannelSet(-1, kDebugLingoStrict)) {
|
||
assert(result == 1);
|
||
}
|
||
}
|
||
|
||
void LB::b_scummvmNoFatalError(int nargs) {
|
||
Datum flag = g_lingo->pop();
|
||
|
||
g_director->_noFatalLingoError = (flag.asInt() != 0);
|
||
|
||
debug("> scummvmNoFatalEror is set to %d", g_director->_noFatalLingoError);
|
||
}
|
||
|
||
void LB::b_getVolumes(int nargs) {
|
||
// Right now, only "Journeyman Project 2: Buried in Time" is known to check
|
||
// for its volume name.
|
||
Datum d;
|
||
d.type = ARRAY;
|
||
d.u.farr = new FArray;
|
||
d.u.farr->arr.push_back(Datum("Buried in Time\252 1"));
|
||
|
||
g_lingo->push(d);
|
||
}
|
||
|
||
void LB::b_beginRecording(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_beginRecording", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
void LB::b_endRecording(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_endRecording", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
}
|
||
|
||
// Shockwave D6
|
||
void LB::b_externalParamCount(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_externalParamCount", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum());
|
||
}
|
||
|
||
void LB::b_externalParamName(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_externalParamName", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum());
|
||
}
|
||
|
||
void LB::b_externalParamValue(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_externalParamValue", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum());
|
||
}
|
||
|
||
void LB::b_frameReady(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_frameReady", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum());
|
||
}
|
||
|
||
void LB::b_getPref(int nargs) {
|
||
g_lingo->printSTUBWithArglist("b_getPref", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum());
|
||
}
|
||
|
||
void LB::b_netPresent(int nargs) {
|
||
// Once NETLINGO.X32 is implmemented, this should return 1
|
||
g_lingo->printSTUBWithArglist("b_netPresent", nargs);
|
||
g_lingo->dropStack(nargs);
|
||
g_lingo->push(Datum());
|
||
}
|
||
|
||
} // End of namespace Director
|