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 "ultima/ultima8/world/camera_process.h"
24 #include "ultima/ultima8/world/world.h"
25 #include "ultima/ultima8/world/current_map.h"
26 #include "ultima/ultima8/world/actors/actor.h"
27 #include "ultima/ultima8/kernel/kernel.h"
28 #include "ultima/ultima8/ultima8.h"
29 #include "ultima/ultima8/world/get_object.h"
30 
31 namespace Ultima {
32 namespace Ultima8 {
33 
34 DEFINE_RUNTIME_CLASSTYPE_CODE(CameraProcess)
35 
36 //
37 // Statics
38 //
39 CameraProcess *CameraProcess::_camera = nullptr;
40 int32 CameraProcess::_earthquake = 0;
41 int32 CameraProcess::_eqX = 0;
42 int32 CameraProcess::_eqY = 0;
43 
CameraProcess()44 CameraProcess::CameraProcess() : Process(), _sx(0), _sy(0), _sz(0),
45 	_ex(0), _ey(0), _ez(0), _time(0), _elapsed(0),
46 	_itemNum(0), _lastFrameNum(0) {
47 }
48 
~CameraProcess()49 CameraProcess::~CameraProcess() {
50 	if (_camera == this)
51 		_camera = nullptr;
52 }
53 
SetCameraProcess(CameraProcess * cam)54 uint16 CameraProcess::SetCameraProcess(CameraProcess *cam) {
55 	if (!cam) cam = new CameraProcess(0);
56 	if (_camera) _camera->terminate();
57 	_camera = cam;
58 	return Kernel::get_instance()->addProcess(_camera);
59 }
60 
ResetCameraProcess()61 void CameraProcess::ResetCameraProcess() {
62 	if (_camera) _camera->terminate();
63 	_camera = nullptr;
64 }
65 
moveToLocation(int32 x,int32 y,int32 z)66 void CameraProcess::moveToLocation(int32 x, int32 y, int32 z) {
67 	if (_itemNum) {
68 		Item *item = getItem(_itemNum);
69 		if (item) item->clearExtFlag(Item::EXT_CAMERA);
70 	}
71 
72 	_sx = _sy = _sz = _time = _elapsed = _lastFrameNum = _itemNum = 0;
73 	_eqX = _eqY = _earthquake = 0;
74 	_ex = x;
75 	_ey = y;
76 	_ez = z;
77 	GetCameraLocation(_sx, _sy, _sz);
78 }
79 
GetCameraLocation(int32 & x,int32 & y,int32 & z)80 void CameraProcess::GetCameraLocation(int32 &x, int32 &y, int32 &z) {
81 	if (!_camera) {
82 		World *world = World::get_instance();
83 		CurrentMap *map = world->getCurrentMap();
84 		int map_num = map->getNum();
85 		Actor *av = getActor(1);
86 
87 		if (!av || av->getMapNum() != map_num) {
88 			x = 8192;
89 			y = 8192;
90 			z = 64;
91 		} else
92 			av->getLocation(x, y, z);
93 
94 		if (_earthquake) {
95 			x += 2 * _eqX + 4 * _eqY;
96 			y += -2 * _eqX + 4 * _eqY;
97 		}
98 	} else {
99 		_camera->GetLerped(x, y, z, 256, true);
100 	}
101 }
102 
103 //
104 // Constructors
105 //
106 
107 // Track item, do nothing
CameraProcess(uint16 _itemnum)108 CameraProcess::CameraProcess(uint16 _itemnum) :
109 	_time(0), _elapsed(0), _itemNum(_itemnum), _lastFrameNum(0) {
110 	GetCameraLocation(_sx, _sy, _sz);
111 
112 	if (_itemNum) {
113 		Item *item = getItem(_itemNum);
114 		if (item) {
115 			item->setExtFlag(Item::EXT_CAMERA);
116 			item->getLocation(_ex, _ey, _ez);
117 			_ez += 20; //!!constant
118 		} else {
119 			_ex = 0;
120 			_ey = 0;
121 			_ez = 0;
122 		}
123 		return;
124 	}
125 
126 	// No item
127 	_itemNum = 0;
128 	_ex = _sx;
129 	_ey = _sy;
130 	_ez = _sz;
131 }
132 
133 // Stay over point
CameraProcess(int32 x,int32 y,int32 z)134 CameraProcess::CameraProcess(int32 x, int32 y, int32 z) :
135 	_ex(x), _ey(y), _ez(z), _time(0), _elapsed(0), _itemNum(0), _lastFrameNum(0) {
136 	GetCameraLocation(_sx, _sy, _sz);
137 }
138 
139 // Scroll
CameraProcess(int32 x,int32 y,int32 z,int32 time)140 CameraProcess::CameraProcess(int32 x, int32 y, int32 z, int32 time) :
141 	_ex(x), _ey(y), _ez(z), _time(time), _elapsed(0), _itemNum(0), _lastFrameNum(0) {
142 	GetCameraLocation(_sx, _sy, _sz);
143 	//pout << "Scrolling from (" << sx << "," << sy << "," << sz << ") to (" <<
144 	//  ex << "," << ey << "," << ez << ") in " << _time << " frames" << Std::endl;
145 }
146 
terminate()147 void CameraProcess::terminate() {
148 	if (_itemNum) {
149 		Item *item = getItem(_itemNum);
150 		if (item) item->clearExtFlag(Item::EXT_CAMERA);
151 	}
152 
153 	Process::terminate();
154 }
155 
run()156 void CameraProcess::run() {
157 	if (_earthquake) {
158 		_eqX = (getRandom() % (_earthquake * 2 + 1)) - _earthquake;
159 		_eqY = (getRandom() % (_earthquake * 2 + 1)) - _earthquake;
160 	} else {
161 		_eqX = 0;
162 		_eqY = 0;
163 	}
164 
165 	if (_time && _elapsed > _time) {
166 		_result = 0; // do we need this
167 		CameraProcess::SetCameraProcess(0); // This will terminate us
168 		return;
169 	}
170 
171 	_elapsed++;
172 }
173 
itemMoved()174 void CameraProcess::itemMoved() {
175 	if (!_itemNum)
176 		return;
177 
178 	Item *item = getItem(_itemNum);
179 
180 	// We only update for now if lerping has been disabled
181 	if (!item || !item->hasExtFlags(Item::EXT_LERP_NOPREV))
182 		return;
183 
184 	int32 ix, iy, iz;
185 	item->getLocation(ix, iy, iz);
186 
187 	int32 maxdist = MAX(MAX(abs(_ex - iz), abs(_ey - iy)), abs(_ez - iz));
188 
189 	if (GAME_IS_U8 || (GAME_IS_CRUSADER && maxdist > 0x40)) {
190 		_sx = _ex = ix;
191 		_sy = _ey = iy;
192 		_ez = iz;
193 		_sz = _ez += 20;
194 		World::get_instance()->getCurrentMap()->updateFastArea(_sx, _sy, _sz, _ex, _ey, _ez);
195 	}
196 }
197 
GetLerped(int32 & x,int32 & y,int32 & z,int32 factor,bool noupdate)198 void CameraProcess::GetLerped(int32 &x, int32 &y, int32 &z, int32 factor, bool noupdate) {
199 	if (_time == 0) {
200 		if (!noupdate) {
201 
202 			bool inBetween = true;
203 
204 			if (_lastFrameNum != _elapsed) {
205 				// No lerping if we missed a frame
206 				if ((_elapsed - _lastFrameNum) > 1) factor = 256;
207 				_lastFrameNum = _elapsed;
208 				inBetween = false;
209 			}
210 
211 			if (!inBetween) {
212 				_sx = _ex;
213 				_sy = _ey;
214 				_sz = _ez;
215 
216 				if (_itemNum) {
217 					Item *item = getItem(_itemNum);
218 					// Got it
219 					if (item) {
220 						_sx = _ex;
221 						_sy = _ey;
222 						_sz = _ez;
223 						item->getLocation(_ex, _ey, _ez);
224 						_ez += 20; //!!constant
225 					}
226 				}
227 				// Update the fast area
228 				World::get_instance()->getCurrentMap()->updateFastArea(_sx, _sy, _sz, _ex, _ey, _ez);
229 			}
230 		}
231 
232 		if (factor == 256) {
233 			x = _ex;
234 			y = _ey;
235 			z = _ez;
236 		} else if (factor == 0) {
237 			x = _sx;
238 			y = _sy;
239 			z = _sz;
240 		} else {
241 			// This way while possibly slower is more accurate
242 			x = ((_sx * (256 - factor) + _ex * factor) >> 8);
243 			y = ((_sy * (256 - factor) + _ey * factor) >> 8);
244 			z = ((_sz * (256 - factor) + _ez * factor) >> 8);
245 		}
246 	} else {
247 		// Do a quadratic interpolation here of velocity (maybe), but not yet
248 		int32 sfactor = _elapsed;
249 		int32 efactor = _elapsed + 1;
250 
251 		if (sfactor > _time) sfactor = _time;
252 		if (efactor > _time) efactor = _time;
253 
254 		int32 lsx = ((_sx * (_time - sfactor) + _ex * sfactor) / _time);
255 		int32 lsy = ((_sy * (_time - sfactor) + _ey * sfactor) / _time);
256 		int32 lsz = ((_sz * (_time - sfactor) + _ez * sfactor) / _time);
257 
258 		int32 lex = ((_sx * (_time - efactor) + _ex * efactor) / _time);
259 		int32 ley = ((_sy * (_time - efactor) + _ey * efactor) / _time);
260 		int32 lez = ((_sz * (_time - efactor) + _ez * efactor) / _time);
261 
262 		// Update the fast area
263 		if (!noupdate) World::get_instance()->getCurrentMap()->updateFastArea(lsx, lsy, lsz, lex, ley, lez);
264 
265 		// This way while possibly slower is more accurate
266 		x = ((lsx * (256 - factor) + lex * factor) >> 8);
267 		y = ((lsy * (256 - factor) + ley * factor) >> 8);
268 		z = ((lsz * (256 - factor) + lez * factor) >> 8);
269 	}
270 
271 	if (_earthquake) {
272 		x += 2 * _eqX + 4 * _eqY;
273 		y += -2 * _eqX + 4 * _eqY;
274 	}
275 }
276 
findRoof(int32 factor)277 uint16 CameraProcess::findRoof(int32 factor) {
278 	int32 x, y, z;
279 	int32 earthquake_old = _earthquake;
280 	_earthquake = 0;
281 	GetLerped(x, y, z, factor);
282 	_earthquake = earthquake_old;
283 	Item *avatar = getItem(1);
284 	if (!avatar) // avatar gone?
285 		return 0;
286 
287 	int32 dx, dy, dz;
288 	avatar->getFootpadWorld(dx, dy, dz);
289 	uint16 roofid;
290 	World::get_instance()->getCurrentMap()->isValidPosition(x, y, z - 10, dx / 2, dy / 2, dz / 2, 0, 1, 0, &roofid);
291 	return roofid;
292 }
293 
saveData(Common::WriteStream * ws)294 void CameraProcess::saveData(Common::WriteStream *ws) {
295 	Process::saveData(ws);
296 
297 	ws->writeUint32LE(static_cast<uint32>(_sx));
298 	ws->writeUint32LE(static_cast<uint32>(_sy));
299 	ws->writeUint32LE(static_cast<uint32>(_sz));
300 	ws->writeUint32LE(static_cast<uint32>(_ex));
301 	ws->writeUint32LE(static_cast<uint32>(_ey));
302 	ws->writeUint32LE(static_cast<uint32>(_ez));
303 	ws->writeUint32LE(static_cast<uint32>(_time));
304 	ws->writeUint32LE(static_cast<uint32>(_elapsed));
305 	ws->writeUint16LE(_itemNum);
306 	ws->writeUint32LE(_lastFrameNum);
307 	ws->writeUint32LE(static_cast<uint32>(_earthquake));
308 	ws->writeUint32LE(static_cast<uint32>(_eqX));
309 	ws->writeUint32LE(static_cast<uint32>(_eqY));
310 }
311 
loadData(Common::ReadStream * rs,uint32 version)312 bool CameraProcess::loadData(Common::ReadStream *rs, uint32 version) {
313 	if (!Process::loadData(rs, version)) return false;
314 
315 	_sx = static_cast<int32>(rs->readUint32LE());
316 	_sy = static_cast<int32>(rs->readUint32LE());
317 	_sz = static_cast<int32>(rs->readUint32LE());
318 	_ex = static_cast<int32>(rs->readUint32LE());
319 	_ey = static_cast<int32>(rs->readUint32LE());
320 	_ez = static_cast<int32>(rs->readUint32LE());
321 	_time = static_cast<int32>(rs->readUint32LE());
322 	_elapsed = static_cast<int32>(rs->readUint32LE());
323 	_itemNum = rs->readUint16LE();
324 	_lastFrameNum = rs->readUint32LE();
325 	_earthquake = static_cast<int32>(rs->readUint32LE()); //static
326 	_eqX = static_cast<int32>(rs->readUint32LE()); //static
327 	_eqY = static_cast<int32>(rs->readUint32LE()); //static
328 
329 	_camera = this; //static
330 
331 	return true;
332 }
333 
334 //	"Camera::move_to(uword, uword, ubyte, word)",
I_moveTo(const uint8 * args,unsigned int argsize)335 uint32 CameraProcess::I_moveTo(const uint8 *args, unsigned int argsize) {
336 	ARG_UINT16(x);
337 	ARG_UINT16(y);
338 	ARG_UINT8(z);
339 	if (argsize > 6) {
340 		ARG_SINT16(unk);
341 	}
342 
343 	if (GAME_IS_CRUSADER) {
344 		x *= 2;
345 		y *= 2;
346 	}
347 	CameraProcess::SetCameraProcess(new CameraProcess(x, y, z));
348 	return 0;
349 }
350 
351 //	"Camera::setCenterOn(uword)",
I_setCenterOn(const uint8 * args,unsigned int)352 uint32 CameraProcess::I_setCenterOn(const uint8 *args, unsigned int /*argsize*/) {
353 	ARG_OBJID(itemNum);
354 	CameraProcess::SetCameraProcess(new CameraProcess(itemNum));
355 	return 0;
356 }
357 
358 //	Camera::scrollTo(uword, uword, ubyte, word)
I_scrollTo(const uint8 * args,unsigned int)359 uint32 CameraProcess::I_scrollTo(const uint8 *args, unsigned int /*argsize*/) {
360 	ARG_UINT16(x);
361 	ARG_UINT16(y);
362 	ARG_UINT8(z);
363 	ARG_SINT16(unk);
364 
365 	if (GAME_IS_CRUSADER) {
366 		x *= 2;
367 		y *= 2;
368 	}
369 
370 	return CameraProcess::SetCameraProcess(new CameraProcess(x, y, z, 25));
371 }
372 
373 //	Camera::startQuake(word)
I_startQuake(const uint8 * args,unsigned int)374 uint32 CameraProcess::I_startQuake(const uint8 *args, unsigned int /*argsize*/) {
375 	ARG_UINT16(strength);
376 	SetEarthquake(strength);
377 	return 0;
378 }
379 
380 //	Camera::stopQuake()
I_stopQuake(const uint8 *,unsigned int)381 uint32 CameraProcess::I_stopQuake(const uint8 * /*args*/, unsigned int /*argsize*/) {
382 	SetEarthquake(0);
383 	return 0;
384 }
385 
I_getCameraX(const uint8 * args,unsigned int argsize)386 uint32 CameraProcess::I_getCameraX(const uint8 *args, unsigned int argsize) {
387 	int32 x, y, z;
388 	assert(GAME_IS_CRUSADER);
389 	GetCameraLocation(x, y, z);
390 	return x / 2;
391 }
392 
I_getCameraY(const uint8 * args,unsigned int argsize)393 uint32 CameraProcess::I_getCameraY(const uint8 *args, unsigned int argsize) {
394 	int32 x, y, z;
395 	assert(GAME_IS_CRUSADER);
396 	GetCameraLocation(x, y, z);
397 	return y / 2;
398 }
399 
I_getCameraZ(const uint8 * args,unsigned int argsize)400 uint32 CameraProcess::I_getCameraZ(const uint8 *args, unsigned int argsize) {
401 	int32 x, y, z;
402 	GetCameraLocation(x, y, z);
403 	return z;
404 }
405 
406 } // End of namespace Ultima8
407 } // End of namespace Ultima
408