/* 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 . * */ #include "common/config-manager.h" #include "common/fs.h" #include "common/platform.h" #include "director/types.h" #include "graphics/macgui/macbutton.h" #include "director/director.h" #include "director/cast.h" #include "director/cursor.h" #include "director/channel.h" #include "director/debugger.h" #include "director/frame.h" #include "director/movie.h" #include "director/sound.h" #include "director/sprite.h" #include "director/score.h" #include "director/window.h" #include "director/castmember/castmember.h" #include "director/castmember/digitalvideo.h" #include "director/castmember/text.h" #include "director/lingo/lingo-builtins.h" #include "director/lingo/lingo-code.h" #include "director/lingo/lingo-the.h" namespace Director { class Sprite; TheEntity entities[] = { // hasId ver. isFunction { kTheActiveWindow, "activeWindow", false, 500, false },// D5 property { kTheActorList, "actorList", false, 400, false },// D4 p { kTheAlertHook, "alertHook", false, 600, true }, // D6 p { kTheApplicationPath, "applicationPath", false, 600, true }, // D6 f { kTheBeepOn, "beepOn", false, 200, false },// D2 p { kTheButtonStyle, "buttonStyle", false, 200, false },// D2 p { kTheCast, "cast", true, 200, false },// D2 { kTheCastLibs, "castLibs", false, 500, false },// D5 p { kTheCastMembers, "castmembers", false, 300, false },// D3 { kTheCenterStage, "centerStage", false, 200, false },// D2 p { kTheCheckBoxAccess, "checkBoxAccess", false, 200, false },// D2 p { kTheCheckBoxType, "checkBoxType", false, 200, false },// D2 p { kTheChunk, "chunk", true, 300, false },// D3 { kTheClickLoc, "clickLoc", false, 400, true }, // D4 function { kTheClickOn, "clickOn", false, 200, true }, // D2 f { kTheColorDepth, "colorDepth", false, 200, false },// D2 p { kTheColorQD, "colorQD", false, 200, true }, // D2 f { kTheCommandDown, "commandDown", false, 200, true }, // D2 f { kTheControlDown, "controlDown", false, 200, true }, // D2 f { kTheCpuHogTicks, "cpuHogTicks", false, 400, true }, // D4 p, documented in D6 { kTheCurrentSpriteNum, "currentSpriteNum", false, 600, true }, // D6 p { kTheDate, "date", false, 300, true }, // D3 f { kTheDeskTopRectList, "deskTopRectList", false, 500, true }, // D5 p { kTheDigitalVideoTimeScale,"digitalVideoTimeScale",false, 500, false },// D5 p { kTheDoubleClick, "doubleClick", false, 200, true }, // D2 f { kTheEmulateMultiButtonMouse,"emulateMultiButtonMouse",false, 500, false },// D5 p { kTheExitLock, "exitLock", false, 200, false },// D2 p { kTheField, "field", true, 300, false },// D3 { kTheFixStageSize, "fixStageSize", false, 200, false },// D2 p { kTheFloatPrecision, "floatPrecision", false, 300, false },// D3 p { kTheFrame, "frame", false, 200, true }, // D2 f { kTheFrameLabel, "frameLabel", false, 400, false },// D4 p { kTheFramePalette, "framePalette", false, 400, false },// D4 p { kTheFrameScript, "frameScript", false, 400, false },// D4 p { kTheFrameSound1, "frameSound1", false, 500, false },// D5 p { kTheFrameSound2, "frameSound2", false, 500, false },// D5 p { kTheFrameTempo, "frameTempo", false, 400, false },// D4 p { kTheFrameTransition, "frameTransition", false, 500, false },// D5 p { kTheFreeBlock, "freeBlock", false, 200, true }, // D2 f { kTheFreeBytes, "freeBytes", false, 200, true }, // D2 f { kTheFrontWindow, "frontWindow", false, 500, false },// D5 p { kTheFullColorPermit, "fullColorPermit", false, 200, false },// D2 p { kTheIdleHandlerPeriod,"idleHandlerPeriod",false, 500, false },// D5 p { kTheIdleLoadMode, "idleLoadMode", false, 500, false },// D5 p { kTheIdleLoadPeriod, "idleLoadPeriod", false, 500, false },// D5 p { kTheIdleLoadTag, "idleLoadTag", false, 500, false },// D5 p { kTheIdleReadChunkSize,"idleReadChunkSize",false, 500, false },// D5 p { kTheImageDirect, "imageDirect", false, 200, false },// D2 p { kTheItemDelimiter, "itemDelimiter", false, 400, false },// D4 p { kTheKey, "key", false, 200, true }, // D2 f { kTheKeyCode, "keyCode", false, 200, true }, // D2 f { kTheKeyDownScript, "keyDownScript", false, 200, false },// D2 p { kTheKeyPressed, "keyPressed", false, 500, false },// D5 p { kTheKeyUpScript, "keyUpScript", false, 400, false },// D4 p { kTheLabelList, "labelList", false, 300, true }, // D3 f { kTheLastClick, "lastClick", false, 200, true }, // D2 f { kTheLastEvent, "lastEvent", false, 200, true }, // D2 f { kTheLastFrame, "lastFrame", false, 400, false },// D4 p { kTheLastKey, "lastKey", false, 200, true }, // D2 f { kTheLastRoll, "lastRoll", false, 200, true }, // D2 f { kTheMachineType, "machineType", false, 200, true }, // D2 f { kTheMaxInteger, "maxInteger", false, 300, true }, // D3.1 f { kTheMemorySize, "memorySize", false, 200, true }, // D2 f { kTheMenu, "menu", true, 300, false },// D3 p { kTheMenuItem, "menuitem", true, 300, false },// D3 p { kTheMenuItems, "menuitems", false, 300, true }, // D3 f { kTheMouseCast, "mouseCast", false, 300, true }, // D3 f { kTheMouseChar, "mouseChar", false, 300, true }, // D3 f { kTheMouseDown, "mouseDown", false, 200, true }, // D2 f { kTheMouseDownScript, "mouseDownScript", false, 200, false },// D2 p { kTheMouseH, "mouseH", false, 200, true }, // D2 f { kTheMouseItem, "mouseItem", false, 300, true }, // D3 f { kTheMouseLine, "mouseLine", false, 300, true }, // D3 f { kTheMouseMember, "mouseMember", false, 600, true }, // D6 f { kTheMouseUp, "mouseUp", false, 200, true }, // D2 f { kTheMouseUpScript, "mouseUpScript", false, 200, false },// D2 p { kTheMouseV, "mouseV", false, 200, true }, // D2 f { kTheMouseWord, "mouseWord", false, 300, true }, // D3 f { kTheMovie, "movie", false, 200, true }, // D2 f { kTheMovieFileFreeSize,"movieFileFreeSize",false, 400, true }, // D4 f { kTheMovieFileSize, "movieFileSize", false, 400, true }, // D4 f { kTheMovieName, "movieName", false, 400, true }, // D4 f { kTheMoviePath, "moviePath", false, 400, true }, // D4 f { kTheMultiSound, "multiSound", false, 300, true }, // D3.1 f { kTheNetThrottleTicks, "netThrottleTicks", false, 600, true }, // D6 f, documented in D7 { kTheOptionDown, "optionDown", false, 200, true }, // D2 f { kTheOrganizationName, "organizationName", false, 500, false },// D5 p, documented in D7 { kTheParamCount, "paramCount", false, 400, true }, // D4 f { kThePathName, "pathName", false, 200, true }, // D2 f { kThePauseState, "pauseState", false, 200, true }, // D2 f { kThePerFrameHook, "perFrameHook", false, 200, false },// D2 p { kThePi, "pi", false, 400, true }, // D4 f { kThePlatform, "platform", false, 500, false },// D5 p { kThePreloadEventAbort,"preloadEventAbort",false, 400, false },// D4 p { kThePreLoadRAM, "preLoadRAM", false, 400, false },// D4 p { kTheProductName, "productName", false, 500, false },// D5 p, undocumented { kTheProductVersion, "productVersion", false, 500, false },// D5 p, documented in D8 { kTheQuickTimePresent, "quickTimePresent", false, 300, true }, // D3.1 f { kTheRandomSeed, "randomSeed", false, 400, false },// D4 p { kTheResult, "result", false, 200, true }, // D2 f { kTheRightMouseDown, "rightMouseDown", false, 500, true }, // D5 f { kTheRightMouseUp, "rightMouseUp", false, 500, true }, // D5 f { kTheRollOver, "rollOver", false, 500, true }, // D5 f, undocumented { kTheRomanLingo, "romanLingo", false, 300, false },// D3.1 p { kTheRunMode, "runMode", false, 500, false },// D5 f, documented in D6 { kTheSafePlayer, "safePlayer", false, 600, false },// D6 p, documented in D7 { kTheScore, "score", false, 500, false },// D5 p { kTheScummvmVersion, "scummvmVersion", false, 200, true }, // ScummVM only { kTheSearchCurrentFolder,"searchCurrentFolder",false,400, true },// D4 f { kTheSearchPath, "searchPath", false, 400, true }, // D4 f { kTheSearchPaths, "searchPaths", false, 400, false },// D4 p, documented in D5 { kTheSelection, "selection", false, 200, true }, // D2 f { kTheSelEnd, "selEnd", false, 200, false },// D2 p { kTheSelStart, "selStart", false, 200, false },// D2 p { kTheSerialNumber, "serialNumber", false, 500, false },// D5 p, documnted in D7 { kTheShiftDown, "shiftDown", false, 200, true }, // D2 f { kTheSoundEnabled, "soundEnabled", false, 200, false },// D2 p { kTheSoundEntity, "sound", true, 300, false },// D3 p { kTheSoundKeepDevice, "soundKeepDevice", false, 600, false },// D6 p, documented in D7 { kTheSoundLevel, "soundLevel", false, 200, false },// D2 p { kTheSprite, "sprite", true, 200, false },// D4 p { kTheStage, "stage", false, 400, false },// D4 p { kTheStageBottom, "stageBottom", false, 200, true }, // D2 f { kTheStageColor, "stageColor", false, 300, false },// D3 p { kTheStageLeft, "stageLeft", false, 200, true }, // D2 f { kTheStageRight, "stageRight", false, 200, true }, // D2 f { kTheStageTop, "stageTop", false, 200, true }, // D2 f { kTheStillDown, "stillDown", false, 200, true }, // D2 f { kTheSwitchColorDepth, "switchColorDepth", false, 200, false },// D2 p { kTheTicks, "ticks", false, 200, true }, // D2 f { kTheTime, "time", false, 300, true }, // D3 f { kTheTimeoutKeyDown, "timeoutKeyDown", false, 200, false },// D2 p { kTheTimeoutLapsed, "timeoutLapsed", false, 200, false },// D2 p { kTheTimeoutLength, "timeoutLength", false, 200, false },// D2 p { kTheTimeoutMouse, "timeoutMouse", false, 200, false },// D2 p { kTheTimeoutPlay, "timeoutPlay", false, 200, false },// D2 p { kTheTimeoutScript, "timeoutScript", false, 200, false },// D2 p { kTheTimer, "timer", false, 200, false },// D2 p { kTheTrace, "trace", false, 400, false },// D4 p { kTheTraceLoad, "traceLoad", false, 400, false },// D4 p { kTheTraceLogFile, "traceLogFile", false, 400, false },// D4 p { kTheUpdateMovieEnabled,"updateMovieEnabled",false,400, false },// D4 p { kTheUserName, "userName", false, 500, false },// D5 p, documented in D7 { kTheVideoForWindowsPresent,"videoForWindowsPresent",false, 400, true },// D4 f { kTheWindow, "window", true, 400, false },// D4 { kTheWindowList, "windowList", false, 400, false },// D4 p { kTheXtras, "xtras", false, 500, false },// D5 p { kTheNOEntity, nullptr, false, 0, false } }; const TheEntityField fields[] = { { kTheSprite, "backColor", kTheBackColor, 200 },// D2 p { kTheSprite, "blend", kTheBlend, 400 },// D4 p { kTheSprite, "bottom", kTheBottom, 200 },// D2 p { kTheSprite, "castNum", kTheCastNum, 200 },// D2 p { kTheSprite, "castLibNum", kTheCastLibNum, 500 },// D5 p { kTheSprite, "constraint", kTheConstraint, 200 },// D2 p { kTheSprite, "currentTime", kTheCurrentTime,600 },// D6 p { kTheSprite, "cursor", kTheCursor, 200 },// D2 p { kTheSprite, "editableText", kTheEditableText,400 },// D4 p { kTheSprite, "flipH", kTheFlipH, 700 },// D7 p { kTheSprite, "flipV", kTheFlipV, 700 },// D7 p { kTheSprite, "foreColor", kTheForeColor, 200 },// D2 p { kTheSprite, "height", kTheHeight, 200 },// D2 p { kTheSprite, "immediate", kTheImmediate, 200 },// D2 p { kTheSprite, "ink", kTheInk, 200 },// D2 p { kTheSprite, "left", kTheLeft, 200 },// D2 p { kTheSprite, "lineSize", kTheLineSize, 200 },// D2 p { kTheSprite, "loc", kTheLoc, 400 },// D4 p ??? { kTheSprite, "locH", kTheLocH, 200 },// D2 p { kTheSprite, "locV", kTheLocV, 200 },// D2 p { kTheSprite, "member", kTheMember, 500 },// D5 p { kTheSprite, "memberNum", kTheMemberNum, 500 },// D5 p { kTheSprite, "moveableSprite",kTheMoveableSprite,400 },// D4 p { kTheSprite, "mostRecentCuePoint",kTheMostRecentCuePoint,600 },// D6 p { kTheSprite, "name", kTheName, 600 },// D6 p { kTheSprite, "pattern", kThePattern, 200 },// D2 p { kTheSprite, "puppet", kThePuppet, 200 },// D2 p { kTheSprite, "rect", kTheRect, 400 },// D4 p ??? { kTheSprite, "right", kTheRight, 200 },// D2 p { kTheSprite, "scoreColor", kTheScoreColor, 400 },// D4 p { kTheSprite, "scriptInstanceList",kTheScriptInstanceList,600 },// D6 p { kTheSprite, "scriptNum", kTheScriptNum, 400 },// D4 p { kTheSprite, "stretch", kTheStretch, 200 },// D2 p { kTheSprite, "top", kTheTop, 200 },// D2 p { kTheSprite, "trails", kTheTrails, 300 },// D3.1 p { kTheSprite, "tweened", kTheTweened, 600 },// D6 p { kTheSprite, "type", kTheType, 200 },// D2 p { kTheSprite, "visibility", kTheVisibility, 300 },// D3.1 p { kTheSprite, "visible", kTheVisible, 400 },// D4 p { kTheSprite, "width", kTheWidth, 200 },// D2 p // Cast library fields { kTheCastLib, "fileName", kTheFileName, 500 },// D5 p { kTheCastLib, "name", kTheName, 500 },// D5 p { kTheCastLib, "number", kTheNumber, 500 },// D5 p { kTheCastLib, "preLoadMode", kThePreLoadMode,500 },// D5 p { kTheCastLib, "selection", kTheSelectionField,500 },// D5 p // Common cast fields { kTheCast, "antiAlias", kTheAntiAlias, 500 },// D5 p (?), documented in D7 { kTheCast, "backColor", kTheBackColor, 400 },// D4 p { kTheCast, "castLibNum", kTheCastLibNum, 500 },// D5 p { kTheCast, "castType", kTheCastType, 400 },// D4 p { kTheCast, "cuePointNames",kTheCuePointNames,600 },// D6 p { kTheCast, "cuePointTimes",kTheCuePointTimes,600 },// D6 p { kTheCast, "filename", kTheFileName, 400 },// D4 p { kTheCast, "foreColor", kTheForeColor, 400 },// D4 p { kTheCast, "height", kTheHeight, 400 },// D4 p { kTheCast, "loaded", kTheLoaded, 400 },// D4 p { kTheCast, "media", kTheMedia, 500 },// D5 p { kTheCast, "mediaReady", kTheMediaReady, 600 },// D6 p { kTheCast, "memberNum", kTheMemberNum, 500 },// D5 p { kTheCast, "modified", kTheModified, 400 },// D4 p { kTheCast, "name", kTheName, 300 },// D3 p { kTheCast, "number", kTheNumber, 300 },// D3 p { kTheCast, "rect", kTheRect, 400 },// D4 p { kTheCast, "purgePriority",kThePurgePriority,400 },// D4 p // 0 Never purge, 1 Purge Last, 2 Purge next, 2 Purge normal { kTheCast, "scriptText", kTheScriptText, 400 },// D4 p { kTheCast, "size", kTheSize, 300 },// D3.1 p { kTheCast, "type", kTheType, 500 },// D5 p { kTheCast, "width", kTheWidth, 400 },// D4 p // Digital video fields { kTheCast, "center", kTheCenter, 400 },// D4 p { kTheCast, "controller", kTheController, 300 },// D3.1 p { kTheCast, "crop", kTheCrop, 400 },// D4 p { kTheCast, "digitalVideoType",kTheDigitalVideoType,500 },// D5 p { kTheCast, "directToStage",kTheDirectToStage,300 },// D3.1 p { kTheCast, "duration", kTheDuration, 300 },// D3.1 p { kTheCast, "frameRate", kTheFrameRate, 400 },// D4 p { kTheCast, "loop", kTheLoop, 300 },// D3.1 p { kTheSprite, "movieRate", kTheMovieRate, 300 },// D3.1 P { kTheSprite, "movieTime", kTheMovieTime, 300 },// D3.1 P { kTheCast, "pausedAtStart",kThePausedAtStart,400 },// D4 p { kTheCast, "preLoad", kThePreLoad, 300 },// D3.1 p { kTheSprite, "setTrackEnabled",kTheSetTrackEnabled, 500 },// D5 p { kTheCast, "sound", kTheSound, 300 },// D3.1 p // 0-1 off-on { kTheSprite, "startTime", kTheStartTime, 300 },// D3.1 p { kTheSprite, "stopTime", kTheStopTime, 300 },// D3.1 p { kTheCast, "timeScale", kTheTimeScale, 500 },// D5 p { kTheSprite, "trackEnabled", kTheTrackEnabled, 500 },// D5 p { kTheSprite, "trackNextKeyTime", kTheTrackNextKeyTime, 500 },// D5 p { kTheSprite, "trackNextSampleTime", kTheTrackNextSampleTime, 500 },// D5 p { kTheSprite, "trackPreviousKeyTime", kTheTrackPreviousKeyTime, 500 },// D5 p { kTheSprite, "trackPreviousSampleTime", kTheTrackPreviousKeyTime, 500 },//D5 p { kTheSprite, "trackText", kTheTrackText, 500 },// D5 p { kTheCast, "video", kTheVideo, 400 },// D4 p { kTheSprite, "volume", kTheVolume, 300 },// D3.1 p // track, scontains track type, seems to be unused // tracks, number of track, seems to be unused // Movie fields { kTheCast, "paletteMapping", kThePaletteMapping, 500 },// D5 p { kTheCast, "scriptsEnabled", kTheScriptsEnabled, 500 },// D5 p { kTheCast, "scoreSelection", kTheScoreSelection, 500 },// D5 p { kTheCast, "updateLock", kTheUpdateLock, 500 },// D5 p // Bitmap fields { kTheCast, "depth", kTheDepth, 400 },// D4 p { kTheCast, "regPoint", kTheRegPoint, 400 },// D4 p { kTheCast, "palette", kThePalette, 400 },// D4 p { kTheCast, "paletteRef", kThePaletteRef, 500 },// D4 p { kTheCast, "picture", kThePicture, 300 },// D3 p // TextCastMember fields { kTheCast, "alignment", kTheTextAlign, 500 },// D5 p { kTheCast, "autoTab", kTheAutoTab, 500 },// D5 p { kTheCast, "border", kTheBorder, 500 },// D5 p { kTheCast, "boxDropShadow",kTheBoxDropShadow, 500 },// D5 p { kTheCast, "boxType", kTheBoxType, 500 },// D5 p { kTheCast, "dropShadow", kTheDropShadow, 500 },// D5 p { kTheCast, "editable", kTheEditable, 500 },// D5 p { kTheCast, "font", kTheTextFont, 500 },// D5 p { kTheCast, "fontSize", kTheTextSize, 500 },// D5 p { kTheCast, "fontStyle", kTheTextStyle, 500 },// D5 p { kTheCast, "lineCount", kTheLineCount, 500 },// D5 p { kTheCast, "lineHeight", kTheTextHeight, 500 },// D5 p { kTheCast, "hilite", kTheHilite, 200 },// D2 p { kTheCast, "margin", kTheMargin, 500 },// D5 p { kTheCast, "pageHeight", kThePageHeight, 500 },// D5 p { kTheCast, "text", kTheText, 200 },// D2 p { kTheCast, "textAlign", kTheTextAlign, 300 },// D3 p { kTheCast, "textFont", kTheTextFont, 300 },// D3 p { kTheCast, "textHeight", kTheTextHeight, 300 },// D3 p { kTheCast, "textSize", kTheTextSize, 300 },// D3 p { kTheCast, "textStyle", kTheTextStyle, 300 },// D3 p { kTheCast, "scrollTop", kTheScrollTop, 500 },// D5 p { kTheCast, "wordWrap", kTheWordWrap, 500 },// D5 p // ButtonCastMember fields { kTheCast, "buttonType", kTheButtonType, 500 },// D5 p // ScriptCastMember fields { kTheCast, "scriptType", kTheScriptType, 500 },// D5 p // ShapeCastMember fields { kTheCast, "filled", kTheFilled, 500 },// D5 p { kTheCast, "lineSize", kTheLineSize, 500 },// D5 p { kTheCast, "pattern", kThePattern, 500 },// D5 p { kTheCast, "shapeType", kTheShapeType, 500 },// D5 p // SoundCastMember fields { kTheCast, "channelCount", kTheChannelCount,500 },// D5 p { kTheCast, "sampleRate", kTheSampleRate, 500 },// D5 p { kTheCast, "sampleSize", kTheSampleSize, 500 },// D5 p // TransitionCastMember fields { kTheCast, "changeArea", kTheChangeArea, 500 },// D5 p { kTheCast, "chunkSize", kTheChunkSize, 500 },// D5 p { kTheCast, "transitionType",kTheTransitionType,500 },// D5 p // XtrasCastMember fields { kTheCast, "interface", kTheInterface, 500 },// D5 p { kTheCast, "mediaBusy", kTheMediaBusy, 600 },// D6 p // Behavior (me) fields { kTheCast, "spriteNum", kTheSpriteNum, 600 },// D6 p // Field fields { kTheField, "alignment", kTheTextAlign, 500 },// D5 p { kTheField, "font", kTheTextFont, 500 },// D5 p { kTheField, "fontSize", kTheTextSize, 500 },// D5 p { kTheField, "fontStyle", kTheTextStyle, 500 },// D5 p { kTheField, "foreColor", kTheForeColor, 400 },// D4 p { kTheField, "hilite", kTheHilite, 200 },// D2 p { kTheField, "lineHeight", kTheTextHeight, 500 },// D5 p { kTheField, "name", kTheName, 300 },// D3 p { kTheField, "text", kTheText, 200 },// D2 p { kTheField, "textAlign", kTheTextAlign, 300 },// D3 p { kTheField, "textFont", kTheTextFont, 300 },// D3 p { kTheField, "textHeight", kTheTextHeight, 300 },// D3 p { kTheField, "textSize", kTheTextSize, 300 },// D3 p { kTheField, "textStyle", kTheTextStyle, 300 },// D3 p // Chunk fields { kTheChunk, "font", kTheTextFont, 500 },// D5 p { kTheChunk, "fontSize", kTheTextSize, 500 },// D5 p { kTheChunk, "fontStyle", kTheTextStyle, 500 },// D5 p { kTheChunk, "foreColor", kTheForeColor, 400 },// D4 p { kTheChunk, "lineHeight", kTheTextHeight, 500 },// D5 p { kTheChunk, "textFont", kTheTextFont, 300 },// D3 p { kTheChunk, "textHeight", kTheTextHeight, 300 },// D3 p { kTheChunk, "textSize", kTheTextSize, 300 },// D3 p { kTheChunk, "textStyle", kTheTextStyle, 300 },// D3 p { kTheWindow, "drawRect", kTheDrawRect, 400 },// D4 p { kTheWindow, "fileName", kTheFileName, 400 },// D4 p { kTheWindow, "modal", kTheModal, 400 },// D4 p { kTheWindow, "rect", kTheRect, 400 },// D4 p { kTheWindow, "title", kTheTitle, 400 },// D4 p { kTheWindow, "titleVisible", kTheTitleVisible,400 },// D4 p { kTheWindow, "sourceRect", kTheSourceRect, 400 },// D4 p { kTheWindow, "visible", kTheVisible, 400 },// D4 p { kTheWindow, "windowType", kTheWindowType, 400 },// D4 p { kTheMenuItem, "checkmark", kTheCheckMark, 300 },// D3 p { kTheMenuItem, "enabled", kTheEnabled, 300 },// D3 p { kTheMenuItem, "name", kTheName, 300 },// D3 p { kTheMenuItem, "script", kTheScript, 300 },// D3 p { kTheMenuItems,"number", kTheNumber, 300 },// D3 p // number of menuitems of menu { kTheMenu, "name", kTheName, 300 },// D3 p { kTheCastMembers, "number", kTheNumber, 300 },// D3 p { kTheCastLibs, "number", kTheNumber, 500 },// D5 p { kTheDate, "short", kTheShort, 300 },// D3 f { kTheDate, "long", kTheLong, 300 },// D3 f { kTheDate, "abbreviated", kTheAbbr, 300 },// D3 f { kTheDate, "abbrev", kTheAbbr, 300 },// D3 f { kTheDate, "abbr", kTheAbbr, 300 },// D3 f { kTheTime, "short", kTheShort, 300 },// D3 f { kTheTime, "long", kTheLong, 300 },// D3 f { kTheTime, "abbreviated", kTheAbbr, 300 },// D3 f { kTheTime, "abbrev", kTheAbbr, 300 },// D3 f { kTheTime, "abbr", kTheAbbr, 300 },// D3 f { kTheSoundEntity,"volume", kTheVolume, 300 },// D3 p { kTheNOEntity, nullptr, kTheNOField, 0 } }; void Lingo::initTheEntities() { _objectEntityId = kTheObject; const TheEntity *e = entities; _entityNames.resize(kTheMaxTheEntityType); while (e->entity != kTheNOEntity) { if (e->version <= _vm->getVersion()) { _theEntities[e->name] = e; _entityNames[e->entity] = e->name; } e++; } const TheEntityField *f = fields; _fieldNames.resize(kTheMaxTheFieldType); while (f->entity != kTheNOEntity) { if (f->version <= _vm->getVersion()) { _theEntityFields[Common::String::format("%d%s", f->entity, f->name)] = f; _fieldNames[f->field] = f->name; } // Store all fields for kTheObject _theEntityFields[Common::String::format("%d%s", _objectEntityId, f->name)] = f; f++; } } void Lingo::cleanUpTheEntities() { _entityNames.clear(); _fieldNames.clear(); } const char *Lingo::entity2str(int id) { static char buf[20]; if (id && id < kTheMaxTheEntityType && !_entityNames[id].empty()) return _entityNames[id].c_str(); snprintf(buf, 19, "#%d", id); return (const char *)buf; } const char *Lingo::field2str(int id) { static char buf[20]; if (id && id < kTheMaxTheFieldType && !_fieldNames[id].empty()) return _fieldNames[id].c_str(); snprintf(buf, 19, "#%d", id); return (const char *)buf; } #define getTheEntitySTUB(entity) \ warning("Lingo::getTheEntity(): Unprocessed getting entity %s", entity2str(entity)); Datum Lingo::getTheEntity(int entity, Datum &id, int field) { debugC(3, kDebugLingoThe, "Lingo::getTheEntity(%s, %s, %s)", entity2str(entity), id.asString(true).c_str(), field2str(field)); debugC(3, kDebugLingoExec, "Lingo::getTheEntity(%s, %s, %s)", entity2str(entity), id.asString(true).c_str(), field2str(field)); Datum d; Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::getTheEntity(): Movie is missing"); d.type = VOID; return d; } g_debugger->entityReadHook(entity, field); LingoArchive *mainArchive = movie->getMainLingoArch(); Score *score = movie->getScore(); switch (entity) { case kTheActiveWindow: { Window *win = (Window *)g_director->_wm->getWindow(g_director->_wm->getActiveWindow()); if (win) { d = Datum(win); } else { d.type = VOID; } } break; case kTheActorList: d = g_lingo->_actorList; break; case kTheAlertHook: warning("STUB: the alertHook"); break; case kTheApplicationPath: warning("STUB: the applicationPath"); break; case kTheBeepOn: d = (int)movie->_isBeepOn; break; case kTheButtonStyle: d = (int)(g_director->_wm->_mode & Graphics::kWMModeButtonDialogStyle); break; case kTheCast: d = getTheCast(id, field); break; case kTheCastLibs: // D5 d = getCastLibsNum(); break; case kTheCastMembers: { uint16 castLibID = 0; if (g_director->getVersion() >= 500) { LB::b_castLib(1); castLibID = (uint16)g_lingo->pop().u.i; } d = getMembersNum(castLibID); } break; case kTheCenterStage: d = g_director->_centerStage; break; case kTheCheckBoxAccess: d = movie->_checkBoxAccess; break; case kTheCheckBoxType: d = movie->_checkBoxType; break; case kTheChunk: d = getTheChunk(id, field); break; case kTheClickLoc: d.u.farr = new FArray; d.u.farr->arr.push_back(movie->_lastClickPos.x); d.u.farr->arr.push_back(movie->_lastClickPos.y); d.type = POINT; break; case kTheClickOn: // Even in D4, `the clickOn` uses the old "active" sprite instead of mouse sprite. d = (int)movie->_lastClickedSpriteId; break; case kTheColorDepth: // bpp. 1, 2, 4, 8, 32 d = _vm->_colorDepth; break; case kTheColorQD: d = 1; break; case kTheCommandDown: if (g_director->getPlatform() == Common::kPlatformWindows) d = (movie->_keyFlags & Common::KBD_CTRL) ? 1 : 0; else d = (movie->_keyFlags & Common::KBD_META) ? 1 : 0; break; case kTheControlDown: d = (movie->_keyFlags & Common::KBD_CTRL) ? 1 : 0; break; case kTheCpuHogTicks: // Mac-onlym specifies how often Director yeilds to other applications. // Default is 20 ticks (1/3 second) d = 20; break; case kTheCurrentSpriteNum: d = (int)movie->_currentSpriteNum; break; case kTheDate: d = getTheDate(field); break; case kTheDeskTopRectList: d = getTheDeskTopRectList(); break; case kTheDoubleClick: // Always measured against the last two clicks. // 25 ticks seems to be the threshold for a double click. d = (movie->_lastClickTime - movie->_lastClickTime2) <= 25 ? 1 : 0; break; case kTheEmulateMultiButtonMouse: d = g_director->_emulateMultiButtonMouse ? 1 : 0; break; case kTheExitLock: d = g_lingo->_exitLock; break; case kTheField: d = getTheField(id, field); break; case kTheFixStageSize: d = (int)g_director->_fixStageSize; break; case kTheFloatPrecision: d = _floatPrecision; break; case kTheFrame: d = (int)score->getCurrentFrameNum(); break; case kTheFrameLabel: d.type = STRING; d.u.s = score->getFrameLabel(score->getCurrentFrameNum()); break; case kTheFramePalette: d = score->getCurrentPalette().toMultiplex(); break; case kTheFrameScript: d = score->_currentFrame->_mainChannels.actionId.toMultiplex(); break; case kTheFrameSound1: d = score->_currentFrame->_mainChannels.sound1.toMultiplex(); break; case kTheFrameSound2: d = score->_currentFrame->_mainChannels.sound2.toMultiplex(); break; case kTheFrameTempo: d = score->_currentFrameRate; break; case kTheFrameTransition: d = score->_currentFrame->_mainChannels.trans.toMultiplex(); break; case kTheFreeBlock: case kTheFreeBytes: d = 32 * 1024 * 1024; // Let's have 32 Mbytes break; case kTheFullColorPermit: d = 1; // We always allow it in ScummVM break; case kTheImageDirect: d = 1; // We always allow it in ScummVM break; case kTheItemDelimiter: { Common::U32String ch(g_lingo->_itemDelimiter); d.type = STRING; d.u.s = new Common::String(ch, Common::kUtf8); } break; case kTheKey: if (movie->_key < 0x80) { d = Common::String::format("%c", (char)movie->_key); } else { d = Common::String(); } break; case kTheKeyCode: d = movie->_keyCode; break; case kTheKeyDownScript: d.type = STRING; if (mainArchive->primaryEventHandlers.contains(kEventKeyDown)) d.u.s = new Common::String(mainArchive->primaryEventHandlers[kEventKeyDown]); else d.u.s = new Common::String(); break; case kTheKeyPressed: { Common::U32String buf; buf.insertChar(movie->_key, 0); d = buf.encode(); } break; case kTheKeyUpScript: d.type = STRING; if (mainArchive->primaryEventHandlers.contains(kEventKeyUp)) d.u.s = new Common::String(mainArchive->primaryEventHandlers[kEventKeyUp]); else d.u.s = new Common::String(); break; case kTheLabelList: d.type = STRING; d.u.s = score->getLabelList(); break; case kTheLastClick: d = (int)(_vm->getMacTicks() - movie->_lastClickTime); break; case kTheLastEvent: d = (int)(_vm->getMacTicks() - movie->_lastEventTime); break; case kTheLastFrame: d = (int)score->getFramesNum(); break; case kTheLastKey: d = (int)(_vm->getMacTicks() - movie->_lastKeyTime); break; case kTheLastRoll: d = (int)(_vm->getMacTicks() - movie->_lastRollTime); break; case kTheMachineType: // These are actually coming from Gestalt machineType // // 1 - Macintosh 512Ke D2 // 2 - Macintosh Plus D2 // 3 - Macintosh SE D2 // 4 - Macintosh II D2 // 5 - Macintosh IIx D2 // 6 - Macintosh IIcx D2 // 7 - Macintosh SE/30 D2 // 8 - Macintosh Portable D2 // 9 - Macintosh IIci D2 // 11 - Macintosh IIfx D3 // 15 - Macintosh Classic D3 // 16 - Macintosh IIsi D3 // 17 - Macintosh LC D3 // 18 - Macintosh Quadra 900 D3 // 19 - PowerBook 170 D3 // 20 - Macintosh Quadra 700 D3 // 21 - Classic II D3 // 22 - PowerBook 100 D3 // 23 - PowerBook 140 D3 // 24 - Macintosh Quadra 950 D4 // 25 - PowerBook Duo 210 D4 // 27 - Macintosh LCIII D4 // 28 - Macintosh Centris 650 D4 // 30 - PowerBook Duo 230 D4 // 31 - PowerBook 180 D4 // 32 - PowerBook 160 D4 // 33 - Macintosh Quadra 800 D4 // 35 - Macintosh LC II D4 // 42 - Macintosh IIvi D4 // 45 - Power Macintosh 7100/70 D5 // 46 - Macintosh IIvx D4 // 47 - Macintosh Color Classic D4 // 48 - PowerBook 165c D4 // 50 - Macintosh Centris 610 D4 // 52 - PowerBook 145 D4 // 53 - PowerComputing 8100/100 D5 // 70 - PowerBook 540C D6 // "Director 6 Demystified" p.818 // 73 - Power Macintosh 6100/60 D5 // 76 - Macintosh Quadra 840av D4 // 256 - IBM PC-type machine D3 d = _vm->_machineType; break; case kTheMaxInteger: d = 2147483647; // (2^31)-1 [max 32bit signed integer] break; case kTheMemorySize: d = 32 * 1024 * 1024; // Let's have 32 Mbytes break; case kTheMenu: if (!g_director->_wm->getMenu()) { warning("Lingo::getTheEntity(): Menu does not exist!"); break; } d.type = STRING; Graphics::MacMenuItem *menuRef; menuRef = nullptr; if (id.u.menu->menuIdNum == -1) { menuRef = g_director->_wm->getMenu()->getMenuItem(*id.u.menu->menuIdStr); } else { menuRef = g_director->_wm->getMenu()->getMenuItem(id.u.menu->menuIdNum - 1); } d.u.s = new Common::String(); *d.u.s = g_director->_wm->getMenu()->getName(menuRef); break; case kTheMenuItem: if (!g_director->_wm->getMenu()) { warning("Lingo::getTheEntity(): Menu does not exist!"); break; } Graphics::MacMenuItem *menu, *menuItem; menu = nullptr, menuItem = nullptr; if (id.u.menu->menuIdNum == -1) { menu = g_director->_wm->getMenu()->getMenuItem(*id.u.menu->menuIdStr); } else { menu = g_director->_wm->getMenu()->getMenuItem(id.u.menu->menuIdNum - 1); } if (id.u.menu->menuItemIdNum == -1) { menuItem = g_director->_wm->getMenu()->getSubMenuItem(menu, *id.u.menu->menuItemIdStr); } else { menuItem = g_director->_wm->getMenu()->getSubMenuItem(menu, id.u.menu->menuItemIdNum - 1); } switch (field) { case kTheCheckMark: d = g_director->_wm->getMenuItemCheckMark(menuItem); break; case kTheEnabled: d = g_director->_wm->getMenuItemEnabled(menuItem); break; case kTheName: d.type = STRING; d.u.s = new Common::String; *(d.u.s) = g_director->_wm->getMenuItemName(menuItem); break; case kTheScript: d = g_director->_wm->getMenuItemAction(menuItem); break; default: warning("Lingo::getTheEntity(): Unprocessed setting field \"%s\" of entity %s", field2str(field), entity2str(entity)); break; } break; case kTheMenuItems: d = getMenuItemsNum(id); break; case kTheMenus: d = getMenuNum(); break; case kTheMouseCast: { // TODO: How is this handled with multiple casts in D5? Common::Point pos = g_director->getCurrentWindow()->getMousePos(); uint16 spriteId = score->getSpriteIDFromPos(pos); if (spriteId) { d = score->getSpriteById(spriteId)->_castId.toMultiplex(); } else { d = 0; } } break; case kTheMouseChar: { // maybe a better handling is iterate channels and check the text sprite that enclose the cursor Common::Point pos = g_director->getCurrentWindow()->getMousePos(); uint16 spriteId = score->getSpriteIDFromPos(pos); Channel *ch = score->getChannelById(spriteId); d = ch->getMouseChar(pos.x, pos.y); } break; case kTheMouseDown: d = g_system->getEventManager()->getButtonState() & (1 << Common::MOUSE_BUTTON_LEFT | 1 << Common::MOUSE_BUTTON_RIGHT) ? 1 : 0; break; case kTheMouseDownScript: d.type = STRING; if (mainArchive->primaryEventHandlers.contains(kEventMouseDown)) d.u.s = new Common::String(mainArchive->primaryEventHandlers[kEventMouseDown]); else d.u.s = new Common::String(); break; case kTheMouseH: d = g_director->getCurrentWindow()->getMousePos().x; break; case kTheMouseItem: { Common::Point pos = g_director->getCurrentWindow()->getMousePos(); uint16 spriteId = score->getSpriteIDFromPos(pos); Channel *ch = score->getChannelById(spriteId); d = ch->getMouseItem(pos.x, pos.y); } break; case kTheMouseLine: { Common::Point pos = g_director->getCurrentWindow()->getMousePos(); uint16 spriteId = score->getSpriteIDFromPos(pos); Channel *ch = score->getChannelById(spriteId); d = ch->getMouseLine(pos.x, pos.y); } break; case kTheMouseMember: { Common::Point pos = g_director->getCurrentWindow()->getMousePos(); uint16 spriteId = score->getSpriteIDFromPos(pos); if (spriteId) { d = score->getSpriteById(spriteId)->_cast; } else { d = getVoid(); } } break; case kTheMouseUp: d = g_system->getEventManager()->getButtonState() & (1 << Common::MOUSE_BUTTON_LEFT | 1 << Common::MOUSE_BUTTON_RIGHT) ? 0 : 1; break; case kTheMouseUpScript: d.type = STRING; if (mainArchive->primaryEventHandlers.contains(kEventMouseUp)) d.u.s = new Common::String(mainArchive->primaryEventHandlers[kEventMouseUp]); else d.u.s = new Common::String(); break; case kTheMouseV: d = g_director->getCurrentWindow()->getMousePos().y; break; case kTheMouseWord: { // same issue as MouseChar, check MouseChar above Common::Point pos = g_director->getCurrentWindow()->getMousePos(); uint16 spriteId = score->getSpriteIDFromPos(pos); Channel *ch = score->getChannelById(spriteId); d = ch->getMouseWord(pos.x, pos.y); } break; case kTheMovie: case kTheMovieName: d.type = STRING; d.u.s = new Common::String(movie->getMacName()); break; case kTheMovieFileFreeSize: d = 0; // Let's pretend the movie is compactified break; case kTheMovieFileSize: d = (int)movie->getArchive()->getFileSize(); break; case kTheMoviePath: case kThePathName: d.type = STRING; d.u.s = new Common::String(_vm->getCurrentAbsolutePath()); break; case kTheMultiSound: // We always support multiple sound channels! d = 1; break; case kTheNetThrottleTicks: // This is default in Mac Director. // Specifies the frequency of servicing to a network d = 15; break; case kTheOptionDown: d = (movie->_keyFlags & Common::KBD_ALT) ? 1 : 0; break; case kTheOrganizationName: d = Common::String("ScummVM Team"); break; case kTheParamCount: d = g_lingo->_state->callstack[g_lingo->_state->callstack.size() - 1]->paramCount; break; case kThePauseState: d = (int) g_director->_playbackPaused; break; case kThePerFrameHook: d = _perFrameHook; break; case kThePlatform: // D5 // ScummVM doesn't track different OS versions; // for now let's assume every game from D5 onwards was // x86-32 or PowerPC. if (g_director->getPlatform() == Common::kPlatformWindows) { if (g_director->getVersion() >= 500) { d = Datum("Windows,32"); } else { d = Datum("Windows,16"); } } else { // Macintosh or pippin if (g_director->getVersion() >= 500) { d = Datum("Macintosh,PowerPC"); } else { d = Datum("Macintosh,68k"); } } break; case kThePreloadEventAbort: d = g_lingo->_preLoadEventAbort; break; case kThePreLoadRAM: d = 0; // We always have unlimited RAM break; case kThePi: d = M_PI; break; case kTheProductName: d = Common::String("Director"); break; case kTheProductVersion: d = Common::String::format("%d.%d.%d", g_director->getVersion() / 100, (g_director->getVersion() / 10) % 10, g_director->getVersion() % 10); break; case kTheQuickTimePresent: // QuickTime is always present for ScummVM d = 1; break; case kTheRandomSeed: d = (int)g_director->_rnd.getSeed(); break; case kTheResult: d = g_lingo->_theResult; break; case kTheRightMouseDown: d = g_system->getEventManager()->getButtonState() & (1 << Common::MOUSE_BUTTON_RIGHT) ? 1 : 0; break; case kTheRightMouseUp: d = g_system->getEventManager()->getButtonState() & (1 << Common::MOUSE_BUTTON_RIGHT) ? 0 : 1; break; case kTheRollOver: d = score->getSpriteIDFromPos(g_director->getCurrentWindow()->getMousePos()); break; case kTheRomanLingo: d = g_lingo->_romanLingo; warning("BUILDBOT: the romanLingo is get, value is %d", g_lingo->_romanLingo); break; case kTheRunMode: if (ConfMan.hasKey("director_runMode")) d = Datum(ConfMan.get("director_runMode")); else { d = Datum("Projector"); } break; case kTheScummvmVersion: d = _vm->getVersion(); break; case kTheSearchCurrentFolder: //We always allow searching in current folder warning("BUILDBOT: SearchCurrentFolder is queried"); d = 1; break; case kTheSearchPath: case kTheSearchPaths: d = g_lingo->_searchPath; break; case kTheSelection: if (movie->_currentEditableTextChannel) { Channel *channel = score->_channels[movie->_currentEditableTextChannel]; if (channel->_widget) { d.type = STRING; d.u.s = new Common::String(Common::convertFromU32String(((Graphics::MacText *)channel->_widget)->getSelection(false, false))); } } break; case kTheSelEnd: case kTheSelStart: if (movie->_currentEditableTextChannel) { Channel *channel = score->_channels[movie->_currentEditableTextChannel]; if (channel->_widget) { d = (int)((Graphics::MacText *)channel->_widget)->getSelectionIndex(entity == kTheSelStart); } } break; case kTheSerialNumber: d = Common::String("DRW600-01234-56789-01234"); break; case kTheShiftDown: d = (movie->_keyFlags & Common::KBD_SHIFT) ? 1 : 0; break; case kTheSoundEnabled: d = _vm->getCurrentWindow()->getSoundManager()->getSoundEnabled(); break; case kTheSoundEntity: { switch (field) { case kTheVolume: d = _vm->getCurrentWindow()->getSoundManager()->getChannelVolume(id.asInt()); break; default: warning("Lingo::getTheEntity(): Unprocessed getting field \"%s\" of entity %s", field2str(field), entity2str(entity)); break; } } break; case kTheSoundKeepDevice: // System property; for Windows only, prevents the sound driver from unloading // and reloading each time a sound needs to play. The default value is TRUE. d = 1; break; case kTheSoundLevel: // getting sound level of channel 1, maybe need to be amended in higher version d = _vm->getCurrentWindow()->getSoundManager()->getChannelVolume(1) / 32; break; case kTheSprite: d = getTheSprite(id, field); break; case kTheStage: d = _vm->getStage(); break; case kTheStageBottom: { Window *window = _vm->getCurrentWindow(); d = window->_window->getInnerDimensions().bottom; } break; case kTheStageColor: // TODO: Provide proper reverse transform for non-indexed color d = (int)g_director->transformColor(g_director->getCurrentWindow()->getStageColor()); break; case kTheStageLeft: { Window *window = _vm->getCurrentWindow(); d = window->_window->getInnerDimensions().left; } break; case kTheStageRight: { Window *window = _vm->getCurrentWindow(); d = window->_window->getInnerDimensions().right; } break; case kTheStageTop: { Window *window = _vm->getCurrentWindow(); d = window->_window->getInnerDimensions().top; } break; case kTheStillDown: d = _vm->_wm->_mouseDown; break; case kTheSwitchColorDepth: getTheEntitySTUB(kTheSwitchColorDepth); break; case kTheTicks: d = (int)_vm->getMacTicks(); break; case kTheTime: d = getTheTime(field); break; case kTheTimeoutKeyDown: d= movie->_timeOutKeyDown; break; case kTheTimeoutLapsed: d = (int)(_vm->getMacTicks() - movie->_lastTimeOut); break; case kTheTimeoutLength: d = (int)movie->_timeOutLength; break; case kTheTimeoutMouse: d = movie->_timeOutMouse; break; case kTheTimeoutPlay: d = movie->_timeOutPlay; break; case kTheTimeoutScript: d.type = STRING; if (mainArchive->primaryEventHandlers.contains(kEventTimeout)) d.u.s = new Common::String(mainArchive->primaryEventHandlers[kEventTimeout]); else d.u.s = new Common::String(); break; case kTheTimer: d = (int)(_vm->getMacTicks() - movie->_lastTimerReset); break; case kTheTrace: d = (int) g_lingo->_trace; break; case kTheTraceLoad: d = g_lingo->_traceLoad; break; case kTheTraceLogFile: d.type = STRING; d.u.s = new Common::String(g_director->_traceLogFile.toString(Common::Path::kNativeSeparator)); break; case kTheUpdateMovieEnabled: d = g_lingo->_updateMovieEnabled; break; case kTheUserName: d = Common::String("ScummVM"); break; case kTheVideoForWindowsPresent: // Video For Windows is always present for ScummVM d = 1; break; case kTheWindow: g_lingo->push(id); LB::b_window(1); d = g_lingo->pop().u.obj->getField(field); break; case kTheWindowList: d = g_lingo->_windowList; break; case kTheXtras: // D5 d = getXtrasNum(); break; default: warning("Lingo::getTheEntity(): Unprocessed getting field \"%s\" of entity %s", field2str(field), entity2str(entity)); break; } return d; } #define setTheEntitySTUB(entity) \ warning("Lingo::setTheEntity(): Unprocessed setting entity %s", entity2str(entity)); #define setTheEntityReadOnly(entity) \ warning("Lingo::setTheEntity: Attempt to set read-only entity %s", entity2str(entity)); void Lingo::setTheEntity(int entity, Datum &id, int field, Datum &d) { debugC(3, kDebugLingoThe, "Lingo::setTheEntity(%s, %s, %s, %s)", entity2str(entity), id.asString(true).c_str(), field2str(field), d.asString(true).c_str()); debugC(3, kDebugLingoExec, "Lingo::setTheEntity(%s, %s, %s, %s)", entity2str(entity), id.asString(true).c_str(), field2str(field), d.asString(true).c_str()); Movie *movie = _vm->getCurrentMovie(); Score *score = movie->getScore(); switch (entity) { case kTheActorList: g_lingo->_actorList = d; break; case kTheAlertHook: warning("STUB: the alertHook"); break; case kTheBeepOn: movie->_isBeepOn = (bool)d.u.i; break; case kTheButtonStyle: if (d.asInt()) g_director->_wm->_mode = g_director->_wmMode | Graphics::kWMModeButtonDialogStyle; else g_director->_wm->_mode = g_director->_wmMode; break; case kTheCast: setTheCast(id, field, d); break; case kTheCenterStage: g_director->_centerStage = d.asInt(); break; case kTheCheckBoxAccess: movie->_checkBoxAccess = d.asInt(); break; case kTheCheckBoxType: movie->_checkBoxType = d.asInt(); break; case kTheChunk: setTheChunk(id, field, d); break; case kTheColorDepth: _vm->_colorDepth = d.asInt(); // bpp. 1, 2, 4, 8, 32 warning("STUB: Lingo::setTheEntity(): Set color depth to %d", _vm->_colorDepth); break; case kTheCpuHogTicks: // We do not need to do anything special to yield to other applications // so, ignore this setting break; case kTheExitLock: g_lingo->_exitLock = bool(d.asInt()); break; case kTheEmulateMultiButtonMouse: g_director->_emulateMultiButtonMouse = (bool)d.asInt(); break; case kTheFixStageSize: g_director->_fixStageSize = (bool)d.u.i; if (d.u.i) { g_director->_fixStageRect = movie->_movieRect; } break; case kTheField: setTheField(id, field, d); break; case kTheFloatPrecision: _floatPrecision = d.asInt(); _floatPrecision = MAX(0, MIN(_floatPrecision, 19)); // 0 to 19 _floatPrecisionFormat = Common::String::format("%%.%df", _floatPrecision); break; case kTheFrameLabel: // TODO: All of these frame properties are settable during a score recording session in D5+ setTheEntityReadOnly(kTheFrameLabel); break; case kTheFramePalette: setTheEntityReadOnly(kTheFramePalette); break; case kTheFrameScript: setTheEntityReadOnly(kTheFrameScript); break; case kTheFrameSound1: setTheEntityReadOnly(kTheFrameSound1); break; case kTheFrameSound2: setTheEntityReadOnly(kTheFrameSound2); break; case kTheFrameTempo: setTheEntityReadOnly(kTheFrameTempo); break; case kTheFrameTransition: setTheEntityReadOnly(kTheFrameTransition); break; case kTheFullColorPermit: // No op in ScummVM. We always allow it break; case kTheImageDirect: // No op in ScummVM. We always allow it break; case kTheItemDelimiter: if (d.asString().size() == 0) g_lingo->_itemDelimiter = 0; else g_lingo->_itemDelimiter = d.asString().decode(Common::kUtf8)[0]; break; case kTheKeyDownScript: movie->setPrimaryEventHandler(kEventKeyDown, d.asString()); break; case kTheKeyUpScript: movie->setPrimaryEventHandler(kEventKeyUp, d.asString()); break; case kTheMenu: setTheEntitySTUB(kTheMenu); break; case kTheMenuItem: Graphics::MacMenuItem *menu, *menuItem; menu = nullptr, menuItem = nullptr; if (!g_director->_wm->getMenu()) { warning("Lingo::setTheEntity(): Menu does not exist!"); break; } if (id.u.menu->menuIdNum == -1) { menu = g_director->_wm->getMenu()->getMenuItem(*id.u.menu->menuIdStr); } else { menu = g_director->_wm->getMenu()->getMenuItem(id.u.menu->menuIdNum - 1); } if (id.u.menu->menuItemIdNum == -1) { menuItem = g_director->_wm->getMenu()->getSubMenuItem(menu, *id.u.menu->menuItemIdStr); } else { menuItem = g_director->_wm->getMenu()->getSubMenuItem(menu, id.u.menu->menuItemIdNum - 1); } if (!menuItem) { warning("Wrong menuItem!"); break; } switch (field) { case kTheCheckMark: g_director->_wm->setMenuItemCheckMark(menuItem, d.asInt()); break; case kTheEnabled: g_director->_wm->setMenuItemEnabled(menuItem, d.asInt()); break; case kTheName: g_director->_wm->setMenuItemName(menuItem, d.asString()); break; case kTheScript: { LingoArchive *mainArchive = movie->getMainLingoArch(); int commandId = 100; while (mainArchive->getScriptContext(kEventScript, commandId)) commandId++; mainArchive->replaceCode(d.asString(), kEventScript, commandId); g_director->_wm->setMenuItemAction(menuItem, commandId); } break; default: warning("Lingo::setTheEntity(): Unprocessed setting field \"%s\" of entity %s", field2str(field), entity2str(entity)); break; } break; case kTheMouseDownScript: movie->setPrimaryEventHandler(kEventMouseDown, d.asString()); break; case kTheMouseUpScript: movie->setPrimaryEventHandler(kEventMouseUp, d.asString()); break; case kTheNetThrottleTicks: // No op, we always smooth on network operations break; case kThePerFrameHook: _perFrameHook = d; break; case kThePlatform: setTheEntityReadOnly(kThePlatform); break; case kThePreloadEventAbort: g_lingo->_preLoadEventAbort = bool(d.asInt()); break; case kThePreLoadRAM: // We always have the unlimited RAM, ignore break; case kTheRandomSeed: g_director->_rnd.setSeed(d.asInt()); break; case kTheRomanLingo: g_lingo->_romanLingo = bool(d.asInt()); // Catching when we set to Japanese if (!g_lingo->_romanLingo) { warning("BUILDBOT: the romanLingo is set to %d", g_lingo->_romanLingo); setTheEntitySTUB(kTheRomanLingo); } break; case kTheScummvmVersion: // Allow director version change: used for testing version differences via the lingo tests. _vm->setVersion(d.asInt()); break; case kTheSearchCurrentFolder: warning("BUILDBOT: Trying to set SearchCurrentFolder lingo property"); break; case kTheSearchPath: case kTheSearchPaths: g_lingo->_searchPath = d; break; case kTheSelEnd: movie->_selEnd = d.asInt(); if (movie->_currentEditableTextChannel != 0) { Channel *channel = score->getChannelById(movie->_currentEditableTextChannel); if (channel->_widget) (((Graphics::MacText *)channel->_widget)->setSelection(d.asInt(), false)); } break; case kTheSelStart: movie->_selStart = d.asInt(); if (movie->_currentEditableTextChannel != 0) { Channel *channel = score->getChannelById(movie->_currentEditableTextChannel); if (channel->_widget) (((Graphics::MacText *)channel->_widget)->setSelection(d.asInt(), true)); } break; case kTheSoundEnabled: _vm->getCurrentWindow()->getSoundManager()->setSoundEnabled((bool)d.asInt()); break; case kTheSoundEntity: { switch (field) { case kTheVolume: { _vm->getCurrentWindow()->getSoundManager()->setChannelVolume(id.asInt(), MAX(0, MIN(d.asInt(), 255))); } break; default: warning("Lingo::setTheEntity(): Unprocessed getting field \"%s\" of entity %s", field2str(field), entity2str(entity)); break; } } break; case kTheSoundKeepDevice: // We do not need to unload the sound driver, so just ignore this. break; case kTheSoundLevel: // setting all of the channel for now _vm->getCurrentWindow()->getSoundManager()->setChannelVolume(-1, MAX(0, MIN(d.asInt() * 32, 255))); break; case kTheSprite: setTheSprite(id, field, d); break; case kTheStage: setTheEntitySTUB(kTheStage); break; case kTheStageColor: g_director->getCurrentWindow()->setStageColor(g_director->transformColor(d.asInt())); // Redraw the stage score->updateSprites(kRenderForceUpdate); g_director->getCurrentWindow()->render(); break; case kTheSwitchColorDepth: setTheEntitySTUB(kTheSwitchColorDepth); break; case kTheTimeoutKeyDown: movie->_timeOutKeyDown = d.asInt(); break; case kTheTimeoutLapsed: // timeOutLapsed can be set in D4, but can't in D3. see D3.1 interactivity manual p312 and D4 dictionary p296. if (g_director->getVersion() >= 400 && (d.type == INT || d.type == FLOAT)) { g_director->_tickBaseline = (int)g_director->getMacTicks() - d.asInt(); } if (d.type != INT) { warning("Lingo::setTheEntity() : Wrong DatumType %d for setting of Lingo Property timeOutLapsed", d.type); } break; case kTheTimeoutLength: movie->_timeOutLength = d.asInt(); break; case kTheTimeoutMouse: movie->_timeOutMouse = d.asInt(); break; case kTheTimeoutPlay: movie->_timeOutPlay = d.asInt(); break; case kTheTimeoutScript: movie->setPrimaryEventHandler(kEventTimeout, d.asString()); break; case kTheTimer: // so value of the timer would be d.asInt() movie->_lastTimerReset = _vm->getMacTicks() - d.asInt(); break; case kTheTrace: g_lingo->_trace = (bool) d.asInt(); break; case kTheTraceLoad: g_lingo->_traceLoad = d.asInt(); break; case kTheTraceLogFile: { if (d.asString().size()) { Common::Path logPath = ConfMan.getPath("path").appendComponent(d.asString()); Common::FSNode out(logPath); if (!out.exists()) out.createWriteStream(false); if (out.isWritable()) g_director->_traceLogFile = logPath; else warning("traceLogFile '%s' is not writeable", logPath.toString(Common::Path::kNativeSeparator).c_str()); } else { g_director->_traceLogFile.clear(); } } break; case kTheUpdateMovieEnabled: g_lingo->_updateMovieEnabled = bool(d.asInt()); break; case kTheWindow: g_lingo->push(id); LB::b_window(1); g_lingo->pop().u.obj->setField(field, d); break; case kTheWindowList: if (d.type == ARRAY) { g_lingo->_windowList = d; } else { warning("Lingo::setTheEntity(): kTheWindowList must be a list"); } break; default: warning("Lingo::setTheEntity(): Unprocessed setting field \"%s\" of entity %s", field2str(field), entity2str(entity)); } g_debugger->entityWriteHook(entity, field); } int Lingo::getMenuNum() { if (!g_director->_wm->getMenu()) { warning("Lingo::getMenuNum(): Menu does not exist!"); return 0; } return g_director->_wm->getMenu()->numberOfMenus(); } int Lingo::getCastLibsNum() { return _vm->getCurrentMovie()->getCasts()->size(); } int Lingo::getMembersNum(uint16 castLibID) { Movie *movie = _vm->getCurrentMovie(); Cast *cast = movie->getCast(CastMemberID(0, castLibID)); if (cast) { return MAX(cast->getCastMaxID(), (movie->_sharedCast ? movie->_sharedCast->getCastMaxID() : 0)); } return 0; } int Lingo::getXtrasNum() { if (_openXtraObjects.size() < _openXtras.size()) { warning("Lingo::getXtrasNum(): Mismatch between openXtras (%d) and openXtraObjects (%d)!", _openXtras.size(), _openXtraObjects.size()); } return _openXtras.size(); } int Lingo::getMenuItemsNum(Datum &d) { if (d.type != MENUREF) { warning("Datum of wrong type: Expected MENUREF, got '%d'", d.type); return 0; } Graphics::MacMenuItem *menu = nullptr; if (!g_director->_wm->getMenu()) { warning("Lingo::getMenuItemsNum(): Menu does not exist!"); return 0; } if (d.u.menu->menuIdNum == -1) { menu = g_director->_wm->getMenu()->getMenuItem(*d.u.menu->menuIdStr); } else { menu = g_director->_wm->getMenu()->getMenuItem(d.u.menu->menuIdNum); } return g_director->_wm->getMenu()->numberOfMenuItems(menu); } Datum Lingo::getTheSprite(Datum &id1, int field) { Datum d; int id = 0; Score *score = _vm->getCurrentMovie()->getScore(); if (!score) { warning("Lingo::getTheSprite(): The sprite %d field \"%s\" setting over non-active score", id, field2str(field)); return d; } if ((id1.type == SPRITEREF) || (id1.type == INT)) { id = id1.u.i; } else { warning("Lingo::getTheSprite(): Unknown the sprite id type: %s", id1.type2str()); return d; } Channel *channel = score->getChannelById(id); if (!channel) return d; Sprite *sprite = channel->_sprite; if (!sprite) return d; switch (field) { case kTheBackColor: // TODO: Provide proper reverse transform for non-indexed color d = (int)g_director->transformColor(sprite->_backColor); break; case kTheBlend: d = (255 - sprite->_blendAmount) * 100 / 255; break; case kTheBottom: d = channel->getBbox().bottom; break; case kTheMember: d = sprite->_castId; break; case kTheCastNum: if (g_director->getVersion() >= 500) { // For the castNum, D5 will multiplex the castLib ID into the result. // the memberNum will just give you the member ID. d = sprite->_castId.toMultiplex(); } else { d = sprite->_castId.member; } break; case kTheCastLibNum: d = sprite->_castId.castLib; break; case kTheMemberNum: d = sprite->_castId.member; break; case kTheConstraint: d = (int)channel->_constraint; break; case kTheCursor: d = channel->_cursor._cursorResId; break; case kTheEditableText: d = sprite->_editable; break; case kTheFlipH: // D7 d = (sprite->_thickness & kTFlipH) ? 1 : 0; break; case kTheFlipV: // D7 d = (sprite->_thickness & kTFlipV) ? 1 : 0; break; case kTheForeColor: // TODO: Provide proper reverse transform for non-indexed color d = (int)g_director->transformColor(sprite->_foreColor); break; case kTheHeight: d = channel->getHeight(); break; case kTheImmediate: d = sprite->_immediate; break; case kTheInk: d = sprite->_ink; break; case kTheLeft: d = channel->getBbox().left; break; case kTheLineSize: d = (sprite->_thickness & kTThickness) - 1; break; case kTheLoc: { Common::Point position = channel->getPosition(); d.type = POINT; d.u.farr = new FArray; d.u.farr->arr.push_back(position.x); d.u.farr->arr.push_back(position.y); } break; case kTheLocH: d = channel->getPosition().x; break; case kTheLocV: d = channel->getPosition().y; break; case kTheMostRecentCuePoint: warning("STUB: the mostRecentCuePoint"); d = 0; break; case kTheMoveableSprite: d = sprite->_moveable; break; case kTheMovieRate: d = channel->_movieRate; if (debugChannelSet(-1, kDebugEndVideo)) d.u.f = 0.0; break; case kTheMovieTime: d = channel->_movieTime; break; case kThePattern: d = sprite->getPattern(); break; case kThePuppet: d = sprite->_puppet; break; case kTheRect: // let compiler to optimize this { Common::Rect bbox = channel->getBbox(); d.type = RECT; d.u.farr = new FArray; d.u.farr->arr.push_back(bbox.left); d.u.farr->arr.push_back(bbox.top); d.u.farr->arr.push_back(bbox.right); d.u.farr->arr.push_back(bbox.bottom); } break; case kTheRight: d = channel->getBbox().right; break; case kTheScoreColor: //Check the last 3 bits of the _colorcode byte as value lies in 0 to 5 d = (int)(sprite->_colorcode & 0x7); break; case kTheScriptInstanceList: warning("STUB: Getting the scriptInstanceList"); d.type = PARRAY; d.u.parr = new PArray; break; case kTheScriptNum: if (g_director->getVersion() >= 600) { if (sprite->_behaviors.size() > 0) d = sprite->_behaviors[0].memberID.toMultiplex(); // Return the first script in the list else d = 0; } else { d = sprite->_scriptId.member; } break; case kTheSpriteNum: warning("STUB: Getting the spriteNum"); d = 0; break; case kTheStartTime: d = channel->_startTime; break; case kTheStopTime: d = channel->_stopTime; break; case kTheStretch: d = (sprite->_stretch ? 1 : 0); break; case kTheTop: d = channel->getBbox().top; break; case kTheTrails: d = (sprite->_trails ? 1 : 0); break; case kTheType: d = sprite->_spriteType; break; case kTheTweened: // D6 d = (sprite->_thickness & kTTweened) ? 1 : 0; break; case kTheVisibility: case kTheVisible: d = (channel->_visible ? 1 : 0); break; case kTheVolume: d = sprite->_volume; break; case kTheWidth: d = channel->getWidth(); break; default: warning("Lingo::getTheSprite(): Unprocessed getting field \"%s\" of sprite", field2str(field)); d.type = VOID; } return d; } void Lingo::setTheSprite(Datum &id1, int field, Datum &d) { Movie *movie = _vm->getCurrentMovie(); Score *score = movie->getScore(); int id = 0; if (!score) { warning("Lingo::setTheSprite(): The sprite %d field \"%s\" setting over non-active score", id, field2str(field)); return; } if ((id1.type == SPRITEREF) || (id1.type == INT)) { id = id1.u.i; } else { warning("Lingo::setTheSprite(): Unknown the sprite id type: %s", id1.type2str()); return; } Channel *channel = score->getChannelById(id); if (!channel) return; Sprite *sprite = channel->_sprite; if (!sprite) return; if (!sprite->_enabled) sprite->_enabled = true; switch (field) { case kTheBackColor: { uint32 newColor = g_director->transformColor(d.asInt()); if (newColor != sprite->_backColor) { sprite->_backColor = newColor; channel->_dirty = true; // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPBackColor, true); } } break; case kTheBlend: { // Convert from (0, 100) range to (0xff, 0x00) int blend = (100 - CLIP(d.asInt(), 0, 100)) * 255 / 100; if (blend != sprite->_blendAmount) { sprite->_blendAmount = blend; channel->_dirty = true; } if (d.asInt() == 0) sprite->_thickness &= ~kTHasBlend; else sprite->_thickness |= kTHasBlend; // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPBlend, true); sprite->setAutoPuppet(kAPThickness, true); } break; case kTheMember: { CastMemberID targetMember = d.asMemberID(); if (targetMember != sprite->_castId) { movie->getWindow()->addDirtyRect(channel->getBbox()); channel->setCast(targetMember); // Ensure the new sprite, whether larger or smaller, appears correctly on the screen movie->getWindow()->addDirtyRect(channel->getBbox()); channel->_dirty = true; } } break; case kTheCastNum: case kTheCastLibNum: case kTheMemberNum: { CastMemberID castId = d.asMemberID(); if (field == kTheMemberNum) { // If the number is multiplexed with a castLib 2 or higher, the castLib will be used. // Otherwise the existing castLib will be preserved. if (castId.castLib == 1) { castId = CastMemberID(castId.member, sprite->_castId.castLib); } } else if (field == kTheCastLibNum) { castId = CastMemberID(sprite->_castId.member, d.asInt()); } CastMember *castMember = movie->getCastMember(castId); if (castMember && castMember->_type == kCastDigitalVideo) { if (((DigitalVideoCastMember *)castMember)->loadVideoFromCast()) { ((DigitalVideoCastMember *)castMember)->setChannel(channel); ((DigitalVideoCastMember *)castMember)->startVideo(); // b_updateStage needs to have _videoPlayback set to render video // in the regular case Score::updateSprites sets it. // However Score::updateSprites is not in the current code path. movie->_videoPlayback = true; } } // Since Digital Video dimensions get clarified after loading, // we enforce them here if (castId != sprite->_castId || (castMember && castMember->_type == kCastDigitalVideo)) { if (!sprite->_trails) { movie->getWindow()->addDirtyRect(channel->getBbox()); channel->_dirty = true; } channel->setCast(castId); channel->_dirty = true; } } break; case kTheConstraint: { int channelId = -1; if (d.type == CASTREF) { // Reference: CastMember ID // Find the first channel that uses this cast. CastMemberID memberID = *d.u.cast; for (uint i = 0; i < score->_channels.size(); i++) { if (score->_channels[i]->_sprite->_castId == memberID) { channelId = i; break; } } } else { channelId = d.asInt(); } if (channelId != -1 && channelId != (int)channel->_constraint) { channel->_constraint = d.u.i; channel->_dirty = true; } } break; case kTheCursor: if (d.type == INT) { channel->_cursor.readFromResource(d); } else { channel->_cursor.readFromCast(d); } score->_cursorDirty = true; break; case kTheEditableText: channel->_sprite->_editable = d.asInt(); break; case kTheFlipH: // D7 sprite->_thickness = (sprite->_thickness & ~kTFlipH) | ((d.asInt() ? kTFlipH : 0)); channel->_dirty = true; sprite->setAutoPuppet(kAPThickness, true); warning("STUB: Sprite flipH was set to %d", d.asInt()); break; case kTheFlipV: // D7 sprite->_thickness = (sprite->_thickness & ~kTFlipV) | ((d.asInt() ? kTFlipV : 0)); channel->_dirty = true; sprite->setAutoPuppet(kAPThickness, true); warning("STUB: Sprite flipV was set to %d", d.asInt()); break; case kTheForeColor: { uint32 newColor = g_director->transformColor(d.asInt()); if (newColor != sprite->_foreColor) { sprite->_foreColor = newColor; channel->_dirty = true; } // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPForeColor, true); } break; case kTheHeight: if (d.asInt() != channel->getHeight()) { g_director->getCurrentWindow()->addDirtyRect(channel->getBbox()); channel->setHeight(d.asInt()); channel->_dirty = true; } // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPHeight, true); break; case kTheImmediate: sprite->_immediate = (bool)d.asInt(); break; case kTheInk: if (d.asInt() != sprite->_ink) { sprite->_ink = static_cast(d.asInt()); channel->_dirty = true; } // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPInk, true); break; case kTheLineSize: sprite->_thickness = (sprite->_thickness & ~kTThickness) | ((d.asInt() + 1) & kTThickness); channel->_dirty = true; sprite->setAutoPuppet(kAPThickness, true); break; case kTheLoc: if (channel->getPosition() != d.asPoint()) { movie->getWindow()->addDirtyRect(channel->getBbox()); channel->_dirty = true; } channel->setPosition(d.asPoint().x, d.asPoint().y); break; case kTheLocH: if (d.asInt() != channel->getPosition().x) { // Only add a dirty rectangle for the original position if we're not rendering in trails mode. // Otherwise, it will erase the trail. if (!channel->_sprite->_trails) { movie->getWindow()->addDirtyRect(channel->getBbox()); } channel->_dirty = true; channel->setPosition(d.asInt(), channel->getPosition().y); } // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPLocH, true); break; case kTheLocV: if (d.asInt() != channel->getPosition().y) { if (!channel->_sprite->_trails) { movie->getWindow()->addDirtyRect(channel->getBbox()); } channel->_dirty = true; channel->setPosition(channel->getPosition().x, d.asInt()); } // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPLocV, true); break; case kTheMoveableSprite: sprite->_moveable = (bool)d.asInt(); // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPMoveable, true); break; case kTheMovieRate: channel->_movieRate = d.asFloat(); if (sprite->_cast && sprite->_cast->_type == kCastDigitalVideo) ((DigitalVideoCastMember *)sprite->_cast)->setMovieRate(channel->_movieRate); else warning("Setting movieTime for non-digital video"); break; case kTheMovieTime: channel->_movieTime = d.asInt(); if (sprite->_cast->_type == kCastDigitalVideo) ((DigitalVideoCastMember *)sprite->_cast)->seekMovie(channel->_movieTime); else warning("Setting movieTime for non-digital video"); break; case kThePattern: if (d.asInt() != sprite->getPattern()) { sprite->setPattern(d.asInt()); channel->_dirty = true; } break; case kThePuppet: sprite->_puppet = (bool)d.asInt(); if (!d.asInt()) { // TODO: Properly reset sprite properties after puppet disabled. sprite->_moveable = false; } break; case kTheRect: if (d.type == RECT || (d.type == ARRAY && d.u.farr->arr.size() >= 4)) { score->updateSprites(kRenderForceUpdate); channel->setBbox( d.u.farr->arr[0].u.i, d.u.farr->arr[1].u.i, d.u.farr->arr[2].u.i, d.u.farr->arr[3].u.i ); channel->_dirty = true; } // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPRect, true); break; case kTheScriptInstanceList: warning("STUB: Setting the scriptInstanceList"); break; case kTheStartTime: channel->_startTime = d.asInt(); if (sprite->_cast->_type == kCastDigitalVideo) ((DigitalVideoCastMember *)sprite->_cast)->seekMovie(channel->_startTime); else warning("Setting startTime for non-digital video"); break; case kTheStopTime: channel->_stopTime = d.asInt(); if (sprite->_cast->_type == kCastDigitalVideo) ((DigitalVideoCastMember *)sprite->_cast)->setStopTime(channel->_stopTime); else warning("Setting stopTime for non-digital video"); break; case kTheStretch: channel->setStretch(d.asInt() != 0); break; case kTheTrails: sprite->_trails = (d.asInt() ? true : false); break; case kTheType: if (d.asInt() != sprite->_spriteType) { sprite->_spriteType = static_cast(d.asInt()); channel->_dirty = true; } break; case kTheTweened: // D6 sprite->_thickness = (sprite->_thickness & ~kTTweened) | ((d.asInt() ? kTTweened : 0)); channel->_dirty = true; sprite->setAutoPuppet(kAPThickness, true); warning("STUB: Sprite tweened was set to %d", d.asInt()); break; case kTheVisibility: case kTheVisible: if ((bool)d.asInt() != channel->_visible) { channel->_visible = (bool)d.asInt(); channel->_dirty = true; } break; case kTheVolume: // TODO: Should changing digital video flags mark as dirty? sprite->_volume = d.asInt(); break; case kTheWidth: if (d.asInt() != channel->getWidth()) { g_director->getCurrentWindow()->addDirtyRect(channel->getBbox()); channel->setWidth(d.asInt()); channel->_dirty = true; } // Based on Director in a Nutshell, page 15 sprite->setAutoPuppet(kAPWidth, true); break; default: warning("Lingo::setTheSprite(): Unprocessed setting field \"%s\" of sprite", field2str(field)); } if (channel->_dirty) movie->getWindow()->addDirtyRect(channel->getBbox()); } Datum Lingo::getTheCast(Datum &id1, int field) { Datum d; Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::getTheCast(): No movie loaded"); return d; } CastMemberID id = id1.asMemberID(); CastMember *member = movie->getCastMember(id); if (!member) { if (field == kTheLoaded) { d = 0; } else if (field == kTheNumber) { d = -1; } else if (id.member <= getMembersNum(id.castLib)) { // If a cast member with the ID doesn't exist, // but the ID isn't greater than the biggest cast member ID, // Lingo will not crash, and instead return a VOID. return d; } else { g_lingo->lingoError("Lingo::getTheCast(): CastMember %s not found", id1.asString().c_str()); } return d; } if (field == kTheMedia) { // every time "the media" is invoked, a new copy is made. member->load(); d = Datum(member->duplicate(nullptr, 0)); return d; } if (!member->hasField(field)) { warning("Lingo::getTheCast(): %s has no property '%s'", id.asString().c_str(), field2str(field)); return d; } d = member->getField(field); return d; } void Lingo::setTheCast(Datum &id1, int field, Datum &d) { Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::setTheCast(): No movie loaded"); return; } CastMemberID id = id1.asMemberID(); CastMember *member = movie->getCastMember(id); if (!member) { g_lingo->lingoError("Lingo::setTheCast(): %s not found", id.asString().c_str()); return; } if (field == kTheMedia) { if (d.type != MEDIA) { warning("Lingo::setTheCast(): setting the media with a non-MEDIA object, ignoring"); return; } CastMember *replacement = (CastMember *)d.u.obj; Cast *cast = movie->getCast(id); cast->duplicateCastMember(replacement, nullptr, id.member); return; } if (!member->hasField(field)) { warning("Lingo::setTheCast(): %s has no property '%s'", id.asString().c_str(), field2str(field)); return; } member->setField(field, d); } Datum Lingo::getTheCastLib(Datum &id1, int field) { Datum d; if (id1.type != CASTLIBREF) { warning("Lingo::getTheCastLib(): Expected CASTLIBREF, not %s", id1.type2str()); return d; } Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::getTheCast(): No movie loaded"); return d; } Cast *cast = movie->getCast(CastMemberID(0, id1.u.i)); if (!cast) { g_lingo->lingoError("Lingo::getTheCastLib(): Cast lib %s not found", id1.asString().c_str()); return d; } switch (field) { case kTheFileName: d = cast->getArchive()->getPathName().toString(g_director->_dirSeparator); break; case kTheName: d = cast->getCastName(); break; case kTheNumber: d = cast->_castLibID; break; case kThePreLoadMode: warning("STUB: Lingo::getTheCastLib(): preLoadMode not implemented"); break; case kTheSelectionField: warning("STUB: Lingo::getTheCastLib(): selection not implemented"); d.type = ARRAY; d.u.farr = new FArray(); d.u.farr->arr.push_back(1); d.u.farr->arr.push_back(1); break; default: break; } return d; } void Lingo::setTheCastLib(Datum &id1, int field, Datum &d) { if (id1.type != CASTLIBREF) { warning("Lingo::setTheCastLib(): Expected CASTLIBREF, not %s", id1.type2str()); return; } Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::setTheCastLib(): No movie loaded"); return; } Cast *cast = movie->getCast(CastMemberID(0, id1.u.i)); if (!cast) { g_lingo->lingoError("Lingo::setTheCastLib(): Cast lib %s not found", id1.asString().c_str()); return; } switch (field) { case kTheFileName: { Common::Path castPath = findMoviePath(d.asString()); movie->loadCastLibFrom(id1.u.i, castPath); } break; case kTheName: movie->setCastLibName(d.asString(), id1.u.i); break; case kTheNumber: warning("Lingo::setTheCastLib(): number is read-only"); break; case kThePreLoadMode: warning("STUB: Lingo::setTheCastLib(): preLoadMode not implemented"); break; case kTheSelectionField: warning("STUB: Lingo::setTheCastLib(): selection not implemented"); break; default: warning("STUB: Lingo::setTheCastLib(): unknown property %d", field); break; } } Datum Lingo::getTheField(Datum &id1, int field) { Datum d; Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::getTheField(): No movie loaded"); return d; } CastMemberID id = id1.asMemberID(); CastMember *member = movie->getCastMember(id); if (!member) { if (field == kTheLoaded) { d = 0; } else { g_lingo->lingoError("Lingo::getTheField(): %s not found", id.asString().c_str()); } return d; } if (member->_type != kCastText) { g_lingo->lingoError("Lingo::getTheField(): %s is not a field", id.asString().c_str()); return d; } if (!member->hasField(field)) { warning("Lingo::getTheField(): %s has no property '%s'", id.asString().c_str(), field2str(field)); return d; } d = member->getField(field); return d; } void Lingo::setTheField(Datum &id1, int field, Datum &d) { Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::setTheField(): No movie loaded"); return; } CastMemberID id = id1.asMemberID(); CastMember *member = movie->getCastMember(id); if (!member) { g_lingo->lingoError("Lingo::setTheField(): %s not found", id.asString().c_str()); return; } if (member->_type != kCastText) { g_lingo->lingoError("Lingo::setTheField(): %s is not a field", id.asString().c_str()); return; } if (!member->hasField(field)) { warning("Lingo::setTheField(): %s has no property '%s'", id.asString().c_str(), field2str(field)); return; } member->setField(field, d); } Datum Lingo::getTheChunk(Datum &chunk, int field) { Datum d; Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::getTheChunk(): No movie loaded"); return d; } if (chunk.type != CHUNKREF) { warning("BUILDBOT: Lingo::getTheChunk(): bad chunk ref type: %s", chunk.type2str()); return d; } int start, end; start = chunk.u.cref->start; end = chunk.u.cref->end; Datum src = chunk.u.cref->source; while (src.type == CHUNKREF) { start += src.u.cref->start; end += src.u.cref->start; src = src.u.cref->source; } if (!src.isCastRef()) { warning("BUILDBOT: Lingo::getTheChunk(): bad chunk ref field type: %s", src.type2str()); return d; } CastMemberID memberID = *src.u.cast; CastMember *member = movie->getCastMember(memberID); if (!member) { g_lingo->lingoError("Lingo::getTheChunk(): %s not found", memberID.asString().c_str()); return d; } if (member->_type != kCastText) { g_lingo->lingoError("Lingo::getTheChunk(): %s is not a field", memberID.asString().c_str()); return d; } if (!((TextCastMember *)member)->hasChunkField(field)) { warning("Lingo::getTheChunk(): %s has no chunk property '%s'", memberID.asString().c_str(), field2str(field)); return d; } d = ((TextCastMember *)member)->getChunkField(field, start, end); return d; } void Lingo::setTheChunk(Datum &chunk, int field, Datum &d) { Movie *movie = _vm->getCurrentMovie(); if (!movie) { warning("Lingo::setTheChunk(): No movie loaded"); return; } if (chunk.type != CHUNKREF) { warning("BUILDBOT: Lingo::setTheChunk(): bad chunk ref type: %s", chunk.type2str()); return; } int start, end; start = chunk.u.cref->start; end = chunk.u.cref->end; Datum src = chunk.u.cref->source; while (src.type == CHUNKREF) { start += src.u.cref->start; end += src.u.cref->start; src = src.u.cref->source; } if (!src.isCastRef()) { warning("BUILDBOT: Lingo::setTheChunk(): bad chunk ref field type: %s", src.type2str()); return; } CastMemberID memberID = *src.u.cast; CastMember *member = movie->getCastMember(memberID); if (!member) { g_lingo->lingoError("Lingo::setTheChunk(): %s not found", memberID.asString().c_str()); return; } if (member->_type != kCastText) { g_lingo->lingoError("Lingo::setTheChunk(): %s is not a field", memberID.asString().c_str()); return; } if (!((TextCastMember *)member)->hasChunkField(field)) { warning("Lingo::setTheChunk(): %s has no chunk property '%s'", memberID.asString().c_str(), field2str(field)); return; } ((TextCastMember *)member)->setChunkField(field, start, end, d); } void Lingo::getObjectProp(Datum &obj, Common::String &propName) { Datum d; if (obj.type == OBJECT) { if (obj.u.obj->hasProp(propName)) { d = obj.u.obj->getProp(propName); } else { g_lingo->lingoError("Lingo::getObjectProp: Object <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str()); } g_lingo->push(d); g_debugger->propReadHook(propName); return; } else if (obj.type == PARRAY) { int index = LC::compareArrays(LC::eqData, obj, propName, true).u.i; if (index > 0) { d = obj.u.parr->arr[index - 1].v; } g_lingo->push(d); g_debugger->propReadHook(propName); return; } else if (obj.type == POINT) { if (propName.equalsIgnoreCase("locH")) { d = obj.u.farr->arr[0]; } else if (propName.equalsIgnoreCase("locV")) { d = obj.u.farr->arr[1]; } else { g_lingo->lingoError("Lingo::getObjectProp: Point <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str()); } g_lingo->push(d); g_debugger->propReadHook(propName); return; } else if (obj.type == RECT) { if (propName.equalsIgnoreCase("left")) { d = obj.u.farr->arr[0]; } else if (propName.equalsIgnoreCase("top")) { d = obj.u.farr->arr[1]; } else if (propName.equalsIgnoreCase("right")) { d = obj.u.farr->arr[2]; } else if (propName.equalsIgnoreCase("bottom")) { d = obj.u.farr->arr[3]; } else { g_lingo->lingoError("Lingo::getObjectProp: Rect <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str()); } g_lingo->push(d); g_debugger->propReadHook(propName); return; } else if (obj.type == CASTREF) { Movie *movie = _vm->getCurrentMovie(); if (!movie) { g_lingo->lingoError("Lingo::getObjectProp(): No movie loaded"); g_lingo->push(d); return; } CastMemberID id = *obj.u.cast; CastMember *member = movie->getCastMember(id); if (!member) { // No matching cast member. Many of the fields are accessible // to indicate the cast member is empty, however the // rest will throw a Lingo error. Common::String key = Common::String::format("%d%s", kTheCast, propName.c_str()); bool emptyAllowed = false; if (_theEntityFields.contains(key)) { emptyAllowed = true; switch (_theEntityFields[key]->field) { case kTheCastType: case kTheType: d = Datum("empty"); d.type = SYMBOL; break; case kTheFileName: case kTheScriptText: d = Datum(""); break; case kTheHeight: case kTheLoaded: case kTheModified: case kThePurgePriority: case kTheWidth: case kTheCenter: case kTheFrameRate: case kThePausedAtStart: case kThePreLoad: case kTheDepth: case kThePalette: d = Datum(0); break; case kTheCrop: case kTheVideo: d = Datum(1); break; case kTheRect: d = Datum(Common::Rect(0, 0, 0, 0)); break; case kTheRegPoint: d = Datum(Common::Point(0, 0)); break; case kTheNumber: if (g_director->getVersion() >= 500) { d = Datum(id.toMultiplex()); } else { d = Datum(id.member); } break; default: emptyAllowed = false; break; } } if (id.member <= getMembersNum(id.castLib)) { // Cast member ID is within range (i.e. less than max) // In real Director, accessing -any- of the properties will // be allowed, but return garbage. warning("Lingo::getObjectProp(): %s not found, but within cast ID range", id.asString().c_str()); } else if (!emptyAllowed) { // Cast member ID is out of range, throw a Lingo error g_lingo->lingoError("Lingo::getObjectProp(): %s not found and out of cast ID range", id.asString().c_str()); } g_lingo->push(d); return; } if (propName.equals("media")) { // every time "the media" is invoked, a new copy is made. member->load(); d = Datum(member->duplicate(nullptr, 0)); g_lingo->push(d); return; } if (member->hasProp(propName)) { d = member->getProp(propName); } else { g_lingo->lingoError("Lingo::getObjectProp(): %s has no property '%s'", id.asString().c_str(), propName.c_str()); } g_lingo->push(d); return; } else if (obj.type == CASTLIBREF) { Common::String key = Common::String::format("%d%s", kTheCastLib, propName.c_str()); if (_theEntityFields.contains(key)) { d = getTheCastLib(obj, _theEntityFields[key]->field); } g_lingo->push(d); return; } else if (obj.type == SPRITEREF) { Common::String key = Common::String::format("%d%s", kTheSprite, propName.c_str()); if (_theEntityFields.contains(key)) { d = getTheSprite(obj, _theEntityFields[key]->field); } g_lingo->push(d); return; } if (_builtinFuncs.contains(propName) && _builtinFuncs[propName].nargs == 1) { push(obj); LC::call(_builtinFuncs[propName], 1, true); return; } g_lingo->lingoError("Lingo::getObjectProp: Invalid object: %s", obj.asString(true).c_str()); g_lingo->push(d); } void Lingo::setObjectProp(Datum &obj, Common::String &propName, Datum &val) { if (obj.type == OBJECT) { if (obj.u.obj->hasProp(propName)) { obj.u.obj->setProp(propName, val); g_debugger->propWriteHook(propName); } else { g_lingo->lingoError("Lingo::setObjectProp: Object <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str()); } } else if (obj.type == PARRAY) { int index = LC::compareArrays(LC::eqData, obj, propName, true).u.i; if (index > 0) { obj.u.parr->arr[index - 1].v = val; } else { PCell cell = PCell(propName, val); obj.u.parr->arr.push_back(cell); } g_debugger->propWriteHook(propName); } else if (obj.type == POINT) { if (propName.equalsIgnoreCase("locH")) { obj.u.farr->arr[0] = val.asInt(); } else if (propName.equalsIgnoreCase("locV")) { obj.u.farr->arr[1] = val.asInt(); } else { g_lingo->lingoError("Lingo::setObjectProp: Point <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str()); } g_debugger->propWriteHook(propName); return; } else if (obj.type == RECT) { if (propName.equalsIgnoreCase("left")) { obj.u.farr->arr[0] = val.asInt(); } else if (propName.equalsIgnoreCase("top")) { obj.u.farr->arr[1] = val.asInt(); } else if (propName.equalsIgnoreCase("right")) { obj.u.farr->arr[2] = val.asInt(); } else if (propName.equalsIgnoreCase("bottom")) { obj.u.farr->arr[3] = val.asInt(); } else { g_lingo->lingoError("Lingo::setObjectProp: Rect <%s> has no property '%s'", obj.asString(true).c_str(), propName.c_str()); } g_debugger->propWriteHook(propName); return; } else if (obj.type == CASTREF) { Movie *movie = _vm->getCurrentMovie(); if (!movie) { g_lingo->lingoError("Lingo::setObjectProp(): No movie loaded"); return; } CastMemberID id = *obj.u.cast; CastMember *member = movie->getCastMember(id); if (!member) { Common::String key = Common::String::format("%d%s", kTheCast, propName.c_str()); bool emptyAllowed = false; if (_theEntityFields.contains(key)) { switch (_theEntityFields[key]->field) { case kTheFileName: case kTheScriptText: case kTheLoaded: case kThePurgePriority: case kTheCenter: case kTheFrameRate: case kThePausedAtStart: case kThePreLoad: case kThePalette: case kTheCrop: case kTheRegPoint: emptyAllowed = true; break; default: break; } } if (emptyAllowed) { return; } g_lingo->lingoError("Lingo::setObjectProp(): %s not found", id.asString().c_str()); return; } if (propName.equals("media")) { if (val.type != MEDIA) { warning("Lingo::setObjectProp(): setting the media with a non-MEDIA object, ignoring"); return; } CastMember *replacement = (CastMember *)val.u.obj; Cast *cast = movie->getCast(id); cast->duplicateCastMember(replacement, nullptr, id.member); return; } if (member->hasProp(propName)) { member->setProp(propName, val); } else { g_lingo->lingoError("Lingo::setObjectProp(): %s has no property '%s'", id.asString().c_str(), propName.c_str()); } } else if (obj.type == CASTLIBREF) { Common::String key = Common::String::format("%d%s", kTheCastLib, propName.c_str()); if (_theEntityFields.contains(key)) { setTheCastLib(obj, _theEntityFields[key]->field, val); } } else if (obj.type == SPRITEREF) { Common::String key = Common::String::format("%d%s", kTheSprite, propName.c_str()); if (_theEntityFields.contains(key)) { setTheSprite(obj, _theEntityFields[key]->field, val); } } else { g_lingo->lingoError("Lingo::setObjectProp: Invalid object: %s", obj.asString(true).c_str()); } } static const char *mfull[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static const char *wday[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; Datum Lingo::getTheDate(int field) { TimeDate t; g_system->getTimeAndDate(t); if (g_director->_forceDate.tm_year != -1) { // Override date portion t.tm_year = g_director->_forceDate.tm_year; t.tm_mon = g_director->_forceDate.tm_mon; t.tm_wday = g_director->_forceDate.tm_wday; t.tm_mday = g_director->_forceDate.tm_mday; } Common::String s; Datum d; d.type = STRING; const char *m = mfull[t.tm_mon]; const char *w = wday[t.tm_wday]; switch (field) { case kTheAbbr: // "Sat, Sep 7, 1991" s = Common::String::format("%c%c%c, %c%c%c %d, %d", w[0], w[1], w[2], m[0], m[1], m[2], t.tm_mday, t.tm_year + 1900); break; case kTheLong: // "Saturday, September 7, 1991" s = Common::String::format("%s, %s %d, %d", w, m, t.tm_mday, t.tm_year + 1900); break; default: // "9/7/91" s = Common::String::format("%d/%d/%02d", t.tm_mday, t.tm_mon, t.tm_year % 100); break; } d.u.s = new Common::String(s); return d; } Datum Lingo::getTheTime(int field) { TimeDate t; g_system->getTimeAndDate(t); Common::String s; Datum d; d.type = STRING; switch (field) { case kTheLong: s = Common::String::format("%d:%02d:%02d %s", t.tm_hour % 12, t.tm_min, t.tm_sec, t.tm_hour < 12 ? "AM" : "PM"); break; default: s = Common::String::format("%d:%02d %s", t.tm_hour % 12, t.tm_min, t.tm_hour < 12 ? "AM" : "PM"); break; } d.u.s = new Common::String(s); return d; } Datum Lingo::getTheDeskTopRectList() { // Returns dimensions of each monitor Datum monitorSize; monitorSize.type = RECT; monitorSize.u.farr = new FArray; monitorSize.u.farr->arr.push_back(0); monitorSize.u.farr->arr.push_back(0); monitorSize.u.farr->arr.push_back(g_director->getMacWindowManager()->getWidth()); monitorSize.u.farr->arr.push_back(g_director->getMacWindowManager()->getHeight()); Datum d; d.type = ARRAY; d.u.farr = new FArray; d.u.farr->arr.push_back(monitorSize); return d; } } // End of namespace Director