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 "engines/nancy/nancy.h"
24 #include "engines/nancy/graphics.h"
25 #include "engines/nancy/resource.h"
26 #include "engines/nancy/cursor.h"
27 #include "engines/nancy/sound.h"
28 #include "engines/nancy/input.h"
29 #include "engines/nancy/util.h"
30 #include "engines/nancy/constants.h"
31 
32 #include "engines/nancy/ui/inventorybox.h"
33 
34 #include "engines/nancy/state/scene.h"
35 
36 #include "engines/nancy/ui/scrollbar.h"
37 
38 namespace Nancy {
39 namespace UI {
40 
InventoryBox(RenderObject & redrawFrom)41 InventoryBox::InventoryBox(RenderObject &redrawFrom) :
42 		RenderObject(redrawFrom, 6),
43 		_scrollbar(nullptr),
44 		_curtains(*this, this),
45 		_scrollbarPos(0),
46 		_curtainsFrameTime(0) {}
47 
~InventoryBox()48 InventoryBox::~InventoryBox() {
49 	_fullInventorySurface.free();
50 	_iconsSurface.free(); delete _scrollbar;
51 }
52 
init()53 void InventoryBox::init() {
54 	Common::SeekableReadStream &stream = *g_nancy->getBootChunkStream("INV");
55 	stream.seek(0, SEEK_SET);
56 
57 	_order.clear();
58 
59 	Common::Rect scrollbarSrcBounds;
60 	readRect(stream, scrollbarSrcBounds);
61 	Common::Point scrollbarDefaultPos;
62 	scrollbarDefaultPos.x = stream.readUint16LE();
63 	scrollbarDefaultPos.y = stream.readUint16LE();
64 	uint16 scrollbarMaxScroll = stream.readUint16LE();
65 
66 	stream.seek(0xD6, SEEK_SET);
67 
68 	uint numFrames = g_nancy->getConstants().numCurtainAnimationFrames;
69 	_curtainsSrc.resize(numFrames * 2);
70 	for (uint i = 0; i < numFrames * 2; ++i) {
71 		readRect(stream, _curtainsSrc[i]);
72 	}
73 
74 	readRect(stream, _screenPosition);
75 	_curtainsFrameTime = stream.readUint16LE();
76 
77 	Common::String inventoryBoxIconsImageName;
78 	readFilename(stream, inventoryBoxIconsImageName);
79 	readFilename(stream, _inventoryCursorsImageName);
80 
81 	stream.skip(8);
82 	readRect(stream, _emptySpace);
83 
84 	char itemName[20];
85 	uint itemNameLength = g_nancy->getGameType() == kGameTypeVampire ? 15 : 20;
86 
87 	_itemDescriptions.reserve(g_nancy->getConstants().numItems);
88 	for (uint i = 0; i < g_nancy->getConstants().numItems; ++i) {
89 		stream.read(itemName, itemNameLength);
90 		itemName[itemNameLength - 1] = '\0';
91 		_itemDescriptions.push_back(ItemDescription());
92 		ItemDescription &desc = _itemDescriptions.back();
93 		desc.name = Common::String(itemName);
94 		desc.oneTimeUse = stream.readUint16LE();
95 		readRect(stream, desc.sourceRect);
96 	}
97 
98 	g_nancy->_resource->loadImage(inventoryBoxIconsImageName, _iconsSurface);
99 
100 	_fullInventorySurface.create(_screenPosition.width(), _screenPosition.height() * ((g_nancy->getConstants().numItems / 4) + 1), g_nancy->_graphicsManager->getScreenPixelFormat());
101 	Common::Rect sourceRect = _screenPosition;
102 	sourceRect.moveTo(0, 0);
103 	_drawSurface.create(_fullInventorySurface, sourceRect);
104 
105 	for (uint i = 0; i < 4; ++i) {
106 		Common::Rect &r = _itemHotspots[i].hotspot;
107 		r = _screenPosition;
108 		r.setWidth(r.width() / 2);
109 		r.setHeight(r.height() / 2);
110 		r.translate((i % 2) * r.width(), (i / 2) * r.height());
111 	}
112 
113 	RenderObject::init();
114 
115 	_scrollbar = new Scrollbar(NancySceneState.getFrame(), 9, scrollbarSrcBounds, scrollbarDefaultPos, scrollbarMaxScroll - scrollbarDefaultPos.y);
116 	_scrollbar->init();
117 	_curtains.init();
118 }
119 
updateGraphics()120 void InventoryBox::updateGraphics() {
121 	if (_scrollbarPos != _scrollbar->getPos()) {
122 		_scrollbarPos = _scrollbar->getPos();
123 
124 		onScrollbarMove();
125 	}
126 }
127 
registerGraphics()128 void InventoryBox::registerGraphics() {
129 	RenderObject::registerGraphics();
130 	_scrollbar->registerGraphics();
131 	_curtains.registerGraphics();
132 }
133 
handleInput(NancyInput & input)134 void InventoryBox::handleInput(NancyInput &input) {
135 	if (_order.size()) {
136 		_scrollbar->handleInput(input);
137 	}
138 
139 	for (uint i = 0; i < 4; ++i) {
140 		if (_itemHotspots[i].hotspot.contains(input.mousePos)) {
141 			if (NancySceneState.getHeldItem() != -1) {
142 				g_nancy->_cursorManager->setCursorType(CursorManager::kHotspotArrow);
143 				if (input.input & NancyInput::kLeftMouseButtonUp) {
144 					NancySceneState.addItemToInventory(NancySceneState.getHeldItem());
145 					g_nancy->_sound->playSound("BULS");
146 				}
147 			} else if (_itemHotspots[i].itemID != -1) {
148 				g_nancy->_cursorManager->setCursorType(CursorManager::kHotspotArrow);
149 				if (input.input & NancyInput::kLeftMouseButtonUp) {
150 					NancySceneState.removeItemFromInventory(_itemHotspots[i].itemID);
151 					g_nancy->_sound->playSound("GLOB");
152 				}
153 			}
154 			break;
155 		}
156 	}
157 }
158 
addItem(int16 itemID)159 void InventoryBox::addItem(int16 itemID) {
160 	if (_order.size() == 0) {
161 		// Adds first item, start curtains animation
162 		_curtains.setOpen(true);
163 	}
164 	Common::Array<int16> back = _order;
165 	_order.clear();
166 	_order.push_back(itemID);
167 	_order.push_back(back);
168 
169 	onReorder();
170 }
171 
removeItem(int16 itemID)172 void InventoryBox::removeItem(int16 itemID) {
173 	for (auto &i : _order) {
174 		if (i == itemID) {
175 			_order.erase(&i);
176 			onReorder();
177 			break;
178 		}
179 	}
180 }
181 
onReorder()182 void InventoryBox::onReorder() {
183 	onScrollbarMove();
184 
185 	_fullInventorySurface.clear();
186 	for (uint i = 0; i < _order.size(); ++i) {
187 		Common::Rect dest;
188 		dest.setWidth(_screenPosition.width() / 2);
189 		dest.setHeight(_screenPosition.height() / 2);
190 		dest.moveTo((i % 2) * dest.width(), (i / 2) * dest.height());
191 		Common::Point destPoint = Common::Point (dest.left, dest.top);
192 
193 		_fullInventorySurface.blitFrom(_iconsSurface, _itemDescriptions[_order[i]].sourceRect, destPoint);
194 	}
195 
196 	if (_order.size() > 0) {
197 		_curtains.setOpen(true);
198 	} else {
199 		_curtains.setOpen(false);
200 	}
201 
202 	_needsRedraw = true;
203 }
204 
setHotspots(uint pageNr)205 void InventoryBox::setHotspots(uint pageNr) {
206 	for (uint i = 0; i < 4; ++i) {
207 		if (i + pageNr * 4 < _order.size()) {
208 			_itemHotspots[i].itemID = _order[i +  pageNr * 4];
209 		} else {
210 			_itemHotspots[i].itemID = -1;
211 		}
212 	}
213 }
214 
onScrollbarMove()215 void InventoryBox::onScrollbarMove() {
216 	float scrollPos = _scrollbar->getPos();
217 
218 	float numPages = (_order.size() - 1) / 4 + 1;
219 	float pageFrac = 1 / numPages;
220 	uint curPage = MIN<uint>(scrollPos / pageFrac, numPages - 1);
221 
222 	Common::Rect sourceRect = _screenPosition;
223 	sourceRect.moveTo(0, curPage * sourceRect.height());
224 	_drawSurface.create(_fullInventorySurface, sourceRect);
225 
226 	setHotspots(curPage);
227 
228 	_needsRedraw = true;
229 }
230 
init()231 void InventoryBox::Curtains::init() {
232 	Common::Rect bounds = _parent->getBounds();
233 	_drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphicsManager->getInputPixelFormat());
234 
235 	if (g_nancy->getGameType() == kGameTypeVampire) {
236 		_drawSurface.setPalette(g_nancy->_graphicsManager->_object0.getPalette(), 0, 256);
237 	}
238 
239 	_screenPosition = _parent->getScreenPosition();
240 	_nextFrameTime = 0;
241 	setAnimationFrame(_curFrame);
242 
243 	setTransparent(true);
244 
245 	RenderObject::init();
246 }
247 
updateGraphics()248 void InventoryBox::Curtains::updateGraphics() {
249 	Time time = g_nancy->getTotalPlayTime();
250 	if (_areOpen) {
251 		if (_curFrame < g_nancy->getConstants().numCurtainAnimationFrames && time > _nextFrameTime) {
252 			setAnimationFrame(++_curFrame);
253 			_nextFrameTime = time + _parent->_curtainsFrameTime;
254 
255 			if (!_soundTriggered) {
256 				_soundTriggered = true;
257 				g_nancy->_sound->playSound("CURT");
258 			}
259 		}
260 	} else {
261 		if (_curFrame > 0 && time > _nextFrameTime) {
262 			setAnimationFrame(--_curFrame);
263 			_nextFrameTime = time + _parent->_curtainsFrameTime;
264 
265 			if (!_soundTriggered) {
266 				_soundTriggered = true;
267 				g_nancy->_sound->playSound("CURT");
268 			}
269 		}
270 	}
271 
272 	if (_curFrame == 0 || _curFrame == g_nancy->getConstants().numCurtainAnimationFrames) {
273 		_soundTriggered = false;
274 	}
275 }
276 
setAnimationFrame(uint frame)277 void InventoryBox::Curtains::setAnimationFrame(uint frame) {
278 	Graphics::ManagedSurface &_object0 = g_nancy->_graphicsManager->_object0;
279 	Common::Rect srcRect;
280 	Common::Point destPoint;
281 
282 	if (frame > g_nancy->getConstants().numCurtainAnimationFrames - 1) {
283 		setVisible(false);
284 		return;
285 	} else {
286 		setVisible(true);
287 	}
288 
289 	_drawSurface.clear(g_nancy->_graphicsManager->getTransColor());
290 
291 	// Draw left shade
292 	srcRect = _parent->_curtainsSrc[frame * 2];
293 	_drawSurface.blitFrom(_object0, srcRect, destPoint);
294 
295 	// Draw right shade
296 	srcRect = _parent->_curtainsSrc[frame * 2 + 1];
297 	destPoint.x = getBounds().width() - srcRect.width();
298 	_drawSurface.blitFrom(_object0, srcRect, destPoint);
299 
300 	_needsRedraw = true;
301 }
302 
303 } // End of namespace UI
304 } // End of namespace Nancy
305