1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "graphics/macgui/macwidget.h"
24 
25 #include "director/director.h"
26 #include "director/castmember.h"
27 #include "director/frame.h"
28 #include "director/movie.h"
29 #include "director/score.h"
30 #include "director/sprite.h"
31 #include "director/lingo/lingo.h"
32 #include "director/lingo/lingo-object.h"
33 
34 namespace Director {
35 
Sprite(Frame * frame)36 Sprite::Sprite(Frame *frame) {
37 	_frame = frame;
38 	_score = _frame->getScore();
39 	_movie = _score->getMovie();
40 
41 	_scriptId = CastMemberID(0, 0);
42 	_colorcode = 0;
43 	_blendAmount = 0;
44 	_unk3 = 0;
45 
46 	_enabled = false;
47 	_castId = CastMemberID(0, 0);
48 	_pattern = 0;
49 
50 	_spriteType = kInactiveSprite;
51 	_inkData = 0;
52 	_ink = kInkTypeCopy;
53 	_trails = 0;
54 
55 	_matte = nullptr;
56 	_cast = nullptr;
57 
58 	_thickness = 0;
59 	_width = 0;
60 	_height = 0;
61 	_moveable = false;
62 	_editable = false;
63 	_puppet = false;
64 	_immediate = false;
65 	_backColor = g_director->_wm->_colorWhite;
66 	_foreColor = g_director->_wm->_colorBlack;
67 
68 	_blend = 0;
69 
70 	_volume = 0;
71 	_stretch = 0;
72 }
73 
~Sprite()74 Sprite::~Sprite() {
75 	delete _matte;
76 }
77 
isQDShape()78 bool Sprite::isQDShape() {
79 	return _spriteType == kRectangleSprite ||
80 		_spriteType == kRoundedRectangleSprite ||
81 		_spriteType == kOvalSprite ||
82 		_spriteType == kLineTopBottomSprite ||
83 		_spriteType == kLineBottomTopSprite ||
84 		_spriteType == kOutlinedRectangleSprite ||
85 		_spriteType == kOutlinedRoundedRectangleSprite ||
86 		_spriteType == kOutlinedOvalSprite ||
87 		_spriteType == kThickLineSprite;
88 }
89 
createQDMatte()90 void Sprite::createQDMatte() {
91 	Graphics::ManagedSurface tmp;
92 	tmp.create(_width, _height, g_director->_pixelformat);
93 	tmp.clear(g_director->_wm->_colorWhite);
94 
95 	Common::Rect srcRect(_width, _height);
96 
97 	Common::Rect fillAreaRect((int)srcRect.width(), (int)srcRect.height());
98 	Graphics::MacPlotData plotFill(&tmp, nullptr, &g_director->getPatterns(), getPattern(), 0, 0, 1, g_director->_wm->_colorBlack);
99 
100 	// it's the same for filled and outlined qd shape when we are using floodfill, so we use filled rect directly since it won't be affected by line size.
101 	switch (_spriteType) {
102 	case kOutlinedRectangleSprite:
103 	case kRectangleSprite:
104 		Graphics::drawFilledRect(fillAreaRect, g_director->_wm->_colorBlack, g_director->_wm->getDrawPixel(), &plotFill);
105 		break;
106 	case kOutlinedRoundedRectangleSprite:
107 	case kRoundedRectangleSprite:
108 		Graphics::drawRoundRect(fillAreaRect, 12, g_director->_wm->_colorBlack, true, g_director->_wm->getDrawPixel(), &plotFill);
109 		break;
110 	case kOutlinedOvalSprite:
111 	case kOvalSprite:
112 		Graphics::drawEllipse(fillAreaRect.left, fillAreaRect.top, fillAreaRect.right, fillAreaRect.bottom, g_director->_wm->_colorBlack, true, g_director->_wm->getDrawPixel(), &plotFill);
113 		break;
114 	case kLineBottomTopSprite:
115 	case kLineTopBottomSprite:
116 		warning("Sprite::createQDMatte doesn't support creating matte for type %d", _spriteType);
117 		break;
118 	default:
119 		warning("Sprite::createQDMatte Expected shape type but got type %d", _spriteType);
120 	}
121 
122 	Graphics::Surface surface;
123 	surface.create(_width, _height, g_director->_pixelformat);
124 	surface.copyFrom(tmp);
125 
126 	_matte = new Graphics::FloodFill(&surface, g_director->_wm->_colorWhite, 0, true);
127 
128 	for (int yy = 0; yy < surface.h; yy++) {
129 		_matte->addSeed(0, yy);
130 		_matte->addSeed(surface.w - 1, yy);
131 	}
132 
133 	for (int xx = 0; xx < surface.w; xx++) {
134 		_matte->addSeed(xx, 0);
135 		_matte->addSeed(xx, surface.h - 1);
136 	}
137 
138 	_matte->fillMask();
139 	tmp.free();
140 	surface.free();
141 }
142 
getQDMatte()143 Graphics::Surface *Sprite::getQDMatte() {
144 	if (!isQDShape() || _ink != kInkTypeMatte)
145 		return nullptr;
146 	if (!_matte)
147 		createQDMatte();
148 	return _matte ? _matte->getMask() : nullptr;
149 }
150 
updateEditable()151 void Sprite::updateEditable() {
152 	if (!_cast)
153 		return;
154 
155 	if (!_puppet)
156 		_editable = _editable || _cast->isEditable();
157 }
158 
respondsToMouse()159 bool Sprite::respondsToMouse() {
160 	if (_moveable)
161 		return true;
162 
163 	if (_cast && _cast->_type == kCastButton)
164 		return true;
165 
166 	ScriptContext *spriteScript = _movie->getScriptContext(kScoreScript, _scriptId);
167 	if (spriteScript && (spriteScript->_eventHandlers.contains(kEventGeneric)
168 					  || spriteScript->_eventHandlers.contains(kEventMouseDown)
169 					  || spriteScript->_eventHandlers.contains(kEventMouseUp)))
170 		return true;
171 
172 	ScriptContext *castScript = _movie->getScriptContext(kCastScript, _castId);
173 	if (castScript && (castScript->_eventHandlers.contains(kEventMouseDown)
174 					|| castScript->_eventHandlers.contains(kEventMouseUp)))
175 		return true;
176 
177 	return false;
178 }
179 
isActive()180 bool Sprite::isActive() {
181 	if (_cast && _cast->_type == kCastButton)
182 		return true;
183 
184 	return _movie->getScriptContext(kScoreScript, _scriptId) != nullptr
185 			|| _movie->getScriptContext(kCastScript, _castId) != nullptr;
186 }
187 
shouldHilite()188 bool Sprite::shouldHilite() {
189 	if (!isActive())
190 		return false;
191 
192 	if (_moveable)
193 		return false;
194 
195 	if (_puppet)
196 		return false;
197 
198 	if (_cast) {
199 		// Restrict to bitmap cast members.
200 		// Buttons also hilite on click, but they have their own check.
201 		if (_cast->_type != kCastBitmap)
202 			return false;
203 
204 		if (g_director->getVersion() >= 300) {
205 			// The Auto Hilite flag was introduced in D3.
206 
207 			CastMemberInfo *castInfo = _cast->getInfo();
208 			if (castInfo)
209 				return castInfo->autoHilite;
210 
211 			// If there's no cast info, fall back to the old matte check.
212 			// In D4 or above, there should always be a cast info,
213 			// but in D3, it is not present unless you set a cast member's
214 			// name, script, etc.
215 		}
216 	} else {
217 		// QuickDraw shapes may also hilite on click.
218 		if (!isQDShape())
219 			return false;
220 	}
221 
222 	return _ink == kInkTypeMatte;
223 }
224 
getPattern()225 uint16 Sprite::getPattern() {
226 	if (!_cast) {
227 		if (isQDShape())
228 			return _pattern;
229 	} else if (_cast->_type == kCastShape) {
230 		return ((ShapeCastMember *)_cast)->_pattern;
231 	}
232 
233 	return 0;
234 }
235 
setPattern(uint16 pattern)236 void Sprite::setPattern(uint16 pattern) {
237 	switch (_spriteType) {
238 	case kRectangleSprite:
239 	case kRoundedRectangleSprite:
240 	case kOvalSprite:
241 	case kLineTopBottomSprite:
242 	case kLineBottomTopSprite:
243 	case kOutlinedRectangleSprite:
244 	case kOutlinedRoundedRectangleSprite:
245 	case kOutlinedOvalSprite:
246 		_pattern = pattern;
247 		break;
248 
249 	case kCastMemberSprite:
250 		// TODO
251 		warning("Sprite::setPattern(): kCastMemberSprite");
252 		return;
253 
254 	default:
255 		return;
256 	}
257 }
258 
checkSpriteType()259 bool Sprite::checkSpriteType() {
260 	// check whether the sprite type match the cast type
261 	// if it doesn't match, then we treat it as transparent
262 	// this happens in warlock-mac data/stambul/c up
263 	if (_spriteType == kBitmapSprite && _cast->_type != kCastBitmap) {
264 		if (debugChannelSet(2, kDebugImages))
265 			warning("Sprite::checkSpriteType: Didn't render sprite due to the sprite type mismatch with cast type");
266 		return false;
267 	}
268 	return true;
269 }
270 
setCast(CastMemberID memberID)271 void Sprite::setCast(CastMemberID memberID) {
272 	/**
273 	 * There are two things we need to take into account here:
274 	 *   1. The cast member's type
275 	 *   2. The sprite's type
276 	 * If the two types do not align, the sprite should not render.
277 	 *
278 	 * Before D4, you needed to manually set a sprite's type along
279 	 * with its castNum.
280 	 *
281 	 * Starting in D4, setting a sprite's castNum also set its type
282 	 * to an appropriate default.
283 	 */
284 
285 	_castId = memberID;
286 	_cast = _movie->getCastMember(_castId);
287 	if (g_director->getVersion() >= 400)
288 		_spriteType = kCastMemberSprite;
289 
290 	if (_cast) {
291 		if (g_director->getVersion() >= 400) {
292 			// Set the sprite type to be more specific ONLY for bitmap or text.
293 			// Others just use the generic kCastMemberSprite in D4.
294 			switch (_cast->_type) {
295 			case kCastBitmap:
296 				_spriteType = kBitmapSprite;
297 				break;
298 			case kCastText:
299 				_spriteType = kTextSprite;
300 				break;
301 			default:
302 				break;
303 			}
304 		}
305 
306 		// TODO: Respect sprite width/height settings. Need to determine how to read
307 		// them properly.
308 		Common::Rect dims = _cast->getInitialRect();
309 		// strange logic here, need to be fixed
310 		if (_cast->_type == kCastBitmap) {
311 			// for the stretched sprites, we need the original size to get the correct bbox offset.
312 			// there are two stretch situation here.
313 			// 1. stretch happened when creating the widget, there is no lingo participated. we will use the original sprite size to create widget. check copyStretchImg
314 			// 2. stretch set by lingo. this time we need to store the original dims because we will create the original sprite and stretch it when bliting. check inkBlitStretchSurface
315 			if (!(_inkData & 0x80) || _stretch) {
316 				_width = dims.width();
317 				_height = dims.height();
318 			}
319 		} else if (_cast->_type != kCastShape && _cast->_type != kCastText) {
320 			_width = dims.width();
321 			_height = dims.height();
322 		}
323 
324 	} else {
325 		if (_castId.member != 0 && debugChannelSet(kDebugImages, 2))
326 			warning("Sprite::setCast(): %s is null", memberID.asString().c_str());
327 	}
328 }
329 
330 } // End of namespace Director
331