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