/* 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/util.h" #include "mediastation/actor.h" #include "mediastation/actors/camera.h" #include "mediastation/actors/stage.h" #include "mediastation/debugchannels.h" #include "mediastation/mediascript/scriptconstants.h" #include "mediastation/mediastation.h" namespace MediaStation { Actor::~Actor() { for (auto it = _eventHandlers.begin(); it != _eventHandlers.end(); ++it) { Common::Array &handlersForType = it->_value; for (EventHandler *handler : handlersForType) { delete handler; } handlersForType.clear(); } _eventHandlers.clear(); } void Actor::initFromParameterStream(Chunk &chunk) { ActorHeaderSectionType paramType = kActorHeaderEmptySection; while (true) { paramType = static_cast(chunk.readTypedUint16()); if (paramType == 0) { break; } else { readParameter(chunk, paramType); } } } void Actor::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) { switch (paramType) { case kActorHeaderEventHandler: { EventHandler *eventHandler = new EventHandler(chunk); Common::Array &eventHandlersForType = _eventHandlers.getOrCreateVal(eventHandler->_type); // This is not a hashmap because we don't want to have to hash ScriptValues. for (EventHandler *existingEventHandler : eventHandlersForType) { if (existingEventHandler->_argumentValue == eventHandler->_argumentValue) { error("%s: Event handler for %s (%s) already exists", __func__, eventTypeToStr(eventHandler->_type), eventHandler->_argumentValue.getDebugString().c_str()); } } eventHandlersForType.push_back(eventHandler); break; } default: error("Got unimplemented actor parameter 0x%x", static_cast(paramType)); } } void Actor::loadIsComplete() { if (_loadIsComplete) { warning("%s: Called more than once for actor %d", __func__, _id); } _loadIsComplete = true; } ScriptValue Actor::callMethod(BuiltInMethod methodId, Common::Array &args) { warning("%s: Got unimplemented method call 0x%x (%s)", __func__, static_cast(methodId), builtInMethodToStr(methodId)); return ScriptValue(); } void Actor::processTimeEventHandlers() { // TODO: Replace with a queue. uint currentTime = g_system->getMillis(); const Common::Array &_timeHandlers = _eventHandlers.getValOrDefault(kTimerEvent); for (EventHandler *timeEvent : _timeHandlers) { // Indeed float, not time. double timeEventInFractionalSeconds = timeEvent->_argumentValue.asFloat(); uint timeEventInMilliseconds = timeEventInFractionalSeconds * 1000; bool timeEventAlreadyProcessed = timeEventInMilliseconds < _lastProcessedTime; bool timeEventNeedsToBeProcessed = timeEventInMilliseconds <= currentTime - _startTime; if (!timeEventAlreadyProcessed && timeEventNeedsToBeProcessed) { debugC(5, kDebugScript, "Actor::processTimeEventHandlers(): Running On Time handler for time %d ms", timeEventInMilliseconds); timeEvent->execute(_id); } } _lastProcessedTime = currentTime - _startTime; } void Actor::runEventHandlerIfExists(EventType eventType, const ScriptValue &arg) { const Common::Array &eventHandlers = _eventHandlers.getValOrDefault(eventType); for (EventHandler *eventHandler : eventHandlers) { const ScriptValue &argToCheck = eventHandler->_argumentValue; if (arg.getType() != argToCheck.getType()) { warning("Got event handler arg type %s, expected %s", scriptValueTypeToStr(arg.getType()), scriptValueTypeToStr(argToCheck.getType())); continue; } if (arg == argToCheck) { debugC(5, kDebugScript, "Executing handler for event type %s on actor %d", eventTypeToStr(eventType), _id); eventHandler->execute(_id); return; } } debugC(5, kDebugScript, "No event handler for event type %s on actor %d", eventTypeToStr(eventType), _id); } void Actor::runEventHandlerIfExists(EventType eventType) { ScriptValue scriptValue; runEventHandlerIfExists(eventType, scriptValue); } SpatialEntity::~SpatialEntity() { if (_parentStage != nullptr) { _parentStage->removeChildSpatialEntity(this); } } ScriptValue SpatialEntity::callMethod(BuiltInMethod methodId, Common::Array &args) { ScriptValue returnValue; switch (methodId) { case kSpatialMoveToMethod: { assert(args.size() == 2); int16 x = static_cast(args[0].asFloat()); int16 y = static_cast(args[1].asFloat()); moveTo(x, y); break; } case kSpatialMoveToByOffsetMethod: { assert(args.size() == 2); int16 dx = static_cast(args[0].asFloat()); int16 dy = static_cast(args[1].asFloat()); int16 newX = _boundingBox.left + dx; int16 newY = _boundingBox.top + dy; moveTo(newX, newY); break; } case kSpatialZMoveToMethod: { assert(args.size() == 1); int zIndex = static_cast(args[0].asFloat()); setZIndex(zIndex); break; } case kSpatialCenterMoveToMethod: { assert(args.size() == 2); int16 x = static_cast(args[0].asFloat()); int16 y = static_cast(args[1].asFloat()); moveToCentered(x, y); break; } case kGetLeftXMethod: assert(args.empty()); returnValue.setToFloat(_boundingBox.left); break; case kGetTopYMethod: assert(args.empty()); returnValue.setToFloat(_boundingBox.top); break; case kGetWidthMethod: assert(args.empty()); returnValue.setToFloat(_boundingBox.width()); break; case kGetHeightMethod: assert(args.empty()); returnValue.setToFloat(_boundingBox.height()); break; case kGetCenterXMethod: { assert(args.empty()); int centerX = _boundingBox.left + (_boundingBox.width() / 2); returnValue.setToFloat(centerX); break; } case kGetCenterYMethod: { assert(args.empty()); int centerY = _boundingBox.top + (_boundingBox.height() / 2); returnValue.setToFloat(centerY); break; } case kGetZCoordinateMethod: assert(args.empty()); returnValue.setToFloat(_zIndex); break; case kSetDissolveFactorMethod: { assert(args.size() == 1); double dissolveFactor = args[0].asFloat(); setDissolveFactor(dissolveFactor); break; } case kIsVisibleMethod: assert(args.empty()); returnValue.setToBool(isVisible()); break; case kSetMousePositionMethod: { assert(args.size() == 2); int16 x = static_cast(args[0].asFloat()); int16 y = static_cast(args[1].asFloat()); setMousePosition(x, y); break; } case kGetXScaleMethod1: case kGetXScaleMethod2: assert(args.empty()); returnValue.setToFloat(_scaleX); break; case kSetScaleMethod: assert(args.size() == 1); invalidateLocalBounds(); _scaleX = _scaleY = args[0].asFloat(); invalidateLocalBounds(); break; case kSetXScaleMethod: assert(args.size() == 1); invalidateLocalBounds(); _scaleX = args[0].asFloat(); invalidateLocalBounds(); break; case kGetYScaleMethod: assert(args.empty()); returnValue.setToFloat(_scaleY); break; case kSetYScaleMethod: assert(args.size() == 1); invalidateLocalBounds(); _scaleY = args[0].asFloat(); invalidateLocalBounds(); break; default: Actor::callMethod(methodId, args); } return returnValue; } void SpatialEntity::readParameter(Chunk &chunk, ActorHeaderSectionType paramType) { switch (paramType) { case kActorHeaderBoundingBox: _originalBoundingBox = chunk.readTypedRect(); setAdjustedBounds(kWrapNone); break; case kActorHeaderDissolveFactor: _dissolveFactor = chunk.readTypedDouble(); break; case kActorHeaderZIndex: _zIndex = chunk.readTypedGraphicUnit(); break; case kActorHeaderTransparency: _hasTransparency = static_cast(chunk.readTypedByte()); break; case kActorHeaderChildActorId: _stageId = chunk.readTypedUint16(); break; case kActorHeaderScaleXAndY: _scaleX = _scaleY = chunk.readTypedDouble(); break; case kActorHeaderScaleX: _scaleX = chunk.readTypedDouble(); break; case kActorHeaderScaleY: _scaleY = chunk.readTypedDouble(); break; default: Actor::readParameter(chunk, paramType); } } void SpatialEntity::loadIsComplete() { Actor::loadIsComplete(); if (_stageId != 0) { Actor *pendingParentStageActor = g_engine->getActorById(_stageId); if (pendingParentStageActor == nullptr) { error("%s: Actor %d doesn't exist", __func__, _stageId); } else if (pendingParentStageActor->type() != kActorTypeStage) { error("%s: Requested parent stage %d is not a stage", __func__, _stageId); } StageActor *pendingParentStage = static_cast(pendingParentStageActor); pendingParentStage->addChildSpatialEntity(this); } } void SpatialEntity::invalidateMouse() { // TODO: Invalidate the mouse properly when we have custom events. // For now, we simulate the mouse update event with a mouse moved event. Common::Event mouseEvent; mouseEvent.type = Common::EVENT_MOUSEMOVE; mouseEvent.mouse = g_system->getEventManager()->getMousePos(); g_system->getEventManager()->pushEvent(mouseEvent); } void SpatialEntity::moveTo(int16 x, int16 y) { Common::Point dest(x, y); if (dest == _boundingBox.origin()) { // We aren't actually moving anywhere. return; } if (isVisible()) { invalidateLocalBounds(); } _originalBoundingBox.moveTo(dest); setAdjustedBounds(kWrapNone); if (isVisible()) { invalidateLocalBounds(); } if (interactsWithMouse()) { invalidateMouse(); } } void SpatialEntity::moveToCentered(int16 x, int16 y) { int16 targetX = x - (_boundingBox.width() / 2); int16 targetY = y - (_boundingBox.height() / 2); moveTo(targetX, targetY); } void SpatialEntity::setBounds(const Common::Rect &bounds) { if (_boundingBox == bounds) { // We aren't actually moving anywhere. return; } if (isVisible()) { invalidateLocalBounds(); } _originalBoundingBox = bounds; setAdjustedBounds(kWrapNone); if (isVisible()) { invalidateLocalBounds(); } if (interactsWithMouse()) { invalidateMouse(); } } void SpatialEntity::setZIndex(int zIndex) { if (_zIndex == zIndex) { // We aren't actually moving anywhere. return; } _zIndex = zIndex; invalidateLocalZIndex(); if (interactsWithMouse()) { invalidateMouse(); } } void SpatialEntity::setMousePosition(int16 x, int16 y) { if (_parentStage) { _parentStage->setMousePosition(x, y); } } void SpatialEntity::setDissolveFactor(double dissolveFactor) { dissolveFactor = CLIP(dissolveFactor, 0.0, 1.0); if (dissolveFactor != _dissolveFactor) { _dissolveFactor = dissolveFactor; invalidateLocalBounds(); } } void SpatialEntity::invalidateLocalBounds() { if (_parentStage != nullptr) { _parentStage->setAdjustedBounds(kWrapNone); _parentStage->invalidateRect(getBbox()); } else { error("%s: No parent stage for entity %d", __func__, _id); } } void SpatialEntity::invalidateLocalZIndex() { warning("STUB: %s", __func__); } void SpatialEntity::setAdjustedBounds(CylindricalWrapMode alignmentMode) { _boundingBox = _originalBoundingBox; if (_parentStage == nullptr) { return; } Common::Point offset(0, 0); Common::Point stageExtent = _parentStage->extent(); switch (alignmentMode) { case kWrapRight: { offset.x = stageExtent.x; offset.y = 0; break; } case kWrapLeft: { offset.x = -stageExtent.x; offset.y = 0; break; } case kWrapBottom: { offset.x = 0; offset.y = stageExtent.y; break; } case kWrapLeftTop: { offset.x = 0; offset.y = -stageExtent.y; break; } case kWrapTop: { offset.x = stageExtent.x; offset.y = stageExtent.y; break; } case kWrapRightBottom: { offset.x = -stageExtent.x; offset.y = -stageExtent.y; break; } case kWrapRightTop: { offset.x = -stageExtent.x; offset.y = stageExtent.y; break; } case kWrapLeftBottom: { offset.x = stageExtent.x; offset.y = -stageExtent.y; break; } case kWrapNone: default: // No offset adjustment. break; } if (alignmentMode != kWrapNone) { // TODO: Implement this once we have a title that actually uses it. warning("%s: Actor %d: Wrapping mode %d not handled yet: (%d, %d, %d, %d) -= (%d, %d)", __func__, _id, static_cast(alignmentMode), PRINT_RECT(_boundingBox), offset.x, offset.y); } if (_scaleX != 0.0 || _scaleY != 0.0) { // TODO: Implement this once we have a title that actually uses it. warning("%s: Scale not handled yet (scaleX: %f, scaleY: %f)", __func__, _scaleX, _scaleY); } } } // End of namespace MediaStation