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 "sci/engine/state.h"
24 #include "sci/engine/kernel.h"
25 
26 namespace Sci {
27 
28 // Following variations existed:
29 // up until including 0.530 (Hoyle 1): will always get 2 parameters, even if 2 parameters were not passed
30 //                                     it seems if range is 0, it will return the seed.
31 // 0.566 (Hero's Quest) to SCI1MID: check for 2 parameters, if not 2 parameters get seed.
32 // SCI1LATE+: 2 parameters -> get random number within range
33 //            1 parameter -> set seed
34 //            any other amount of parameters -> get seed
35 //
36 // Right now, the weird SCI0 behavior (up until 0.530) for getting parameters and getting the seed is not implemented.
37 // We also do not let through more than 2 parameters to kRandom via signatures. In case there is a game doing this,
38 // a workaround should be added.
kRandom(EngineState * s,int argc,reg_t * argv)39 reg_t kRandom(EngineState *s, int argc, reg_t *argv) {
40 	Common::RandomSource &rng = g_sci->getRNG();
41 
42 	if (argc == 2) {
43 		// get random number
44 		// numbers are definitely unsigned, for example lsl5 door code in k rap radio is random
45 		//  and 5-digit - we get called kRandom(10000, 65000)
46 		//  some codes in sq4 are also random and 5 digit (if i remember correctly)
47 		const uint16 fromNumber = argv[0].toUint16();
48 		const uint16 toNumber = argv[1].toUint16();
49 		// Some scripts may request a range in the reverse order (from largest
50 		// to smallest). An example can be found in Longbow, room 710, where a
51 		// random number is requested from 119 to 83. In this case, we're
52 		// supposed to return toNumber (determined by the KQ5CD disasm).
53 		// Fixes bug #3413020.
54 		if (fromNumber > toNumber)
55 			return make_reg(0, toNumber);
56 
57 		uint16 range = toNumber - fromNumber + 1;
58 		// calculating range is exactly how sierra sci did it and is required for hoyle 4
59 		//  where we get called with kRandom(0, -1) and we are supposed to give back values from 0 to 0
60 		//  the returned value will be used as displace-offset for a background cel
61 		//  note: i assume that the hoyle4 code is actually buggy and it was never fixed because of
62 		//         the way sierra sci handled it - "it just worked". It should have called kRandom(0, 0)
63 		if (range)
64 			range--; // the range value was never returned, our random generator gets 0->range, so fix it
65 
66 		const int randomNumber = fromNumber + (int)rng.getRandomNumber(range);
67 		return make_reg(0, randomNumber);
68 	}
69 
70 	// for other amounts of arguments
71 	if (getSciVersion() >= SCI_VERSION_1_LATE) {
72 		if (argc == 1) {
73 			// 1 single argument is for setting the seed
74 			// right now we do not change the Common RNG seed.
75 			// It should never be required unless a game reuses the same seed to get the same combination of numbers multiple times.
76 			// And in such a case, we would have to add code for such a feature (ScummVM RNG uses a UINT32 seed).
77 			warning("kRandom: caller requested to set the RNG seed");
78 			return NULL_REG;
79 		}
80 	}
81 
82 	// treat anything else as if caller wants the seed
83 	warning("kRandom: caller requested to get the RNG seed");
84 	return make_reg(0, rng.getSeed());
85 }
86 
kAbs(EngineState * s,int argc,reg_t * argv)87 reg_t kAbs(EngineState *s, int argc, reg_t *argv) {
88 	return make_reg(0, ABS(argv[0].toSint16()));
89 }
90 
kSqrt(EngineState * s,int argc,reg_t * argv)91 reg_t kSqrt(EngineState *s, int argc, reg_t *argv) {
92 	return make_reg(0, (int16) sqrt((float) ABS(argv[0].toSint16())));
93 }
94 
kGetAngle_SCI0(int16 x1,int16 y1,int16 x2,int16 y2)95 uint16 kGetAngle_SCI0(int16 x1, int16 y1, int16 x2, int16 y2) {
96 	int16 xRel = x2 - x1;
97 	int16 yRel = y1 - y2; // y-axis is mirrored.
98 	int16 angle;
99 
100 	// Move (xrel, yrel) to first quadrant.
101 	if (y1 < y2)
102 		yRel = -yRel;
103 	if (x2 < x1)
104 		xRel = -xRel;
105 
106 	// Compute angle in grads.
107 	if (yRel == 0 && xRel == 0)
108 		return 0;
109 	else
110 		angle = 100 * xRel / (xRel + yRel);
111 
112 	// Fix up angle for actual quadrant of (xRel, yRel).
113 	if (y1 < y2)
114 		angle = 200 - angle;
115 	if (x2 < x1)
116 		angle = 400 - angle;
117 
118 	// Convert from grads to degrees by merging grad 0 with grad 1,
119 	// grad 10 with grad 11, grad 20 with grad 21, etc. This leads to
120 	// "degrees" that equal either one or two grads.
121 	angle -= (angle + 9) / 10;
122 	return angle;
123 }
124 
125 // atan2 for first octant, x >= y >= 0. Returns [0,45] (inclusive)
kGetAngle_SCI1_atan2_base(int y,int x)126 int kGetAngle_SCI1_atan2_base(int y, int x) {
127 	if (x == 0)
128 		return 0;
129 
130 	// fixed point tan(a)
131 	int tan_fp = 10000 * y / x;
132 
133 	if ( tan_fp >= 1000 ) {
134 		// For tan(a) >= 0.1, interpolate between multiples of 5 degrees
135 
136 		// 10000 * tan([5, 10, 15, 20, 25, 30, 35, 40, 45])
137 		const int tan_table[] = { 875, 1763, 2679, 3640, 4663, 5774,
138 		                          7002, 8391, 10000 };
139 
140 		// Look up tan(a) in our table
141 		int i = 1;
142 		while (tan_fp > tan_table[i]) ++i;
143 
144 		// The angle a is between 5*i and 5*(i+1). We linearly interpolate.
145 		int dist = tan_table[i] - tan_table[i-1];
146 		int interp = (5 * (tan_fp - tan_table[i-1]) + dist/2) / dist;
147 		return 5*i + interp;
148 	} else {
149 		// for tan(a) < 0.1, tan(a) is approximately linear in a.
150 		// tan'(0) = 1, so in degrees the slope of atan is 180/pi = 57.29...
151 		return (57 * y + x/2) / x;
152 	}
153 }
154 
kGetAngle_SCI1_atan2(int y,int x)155 int kGetAngle_SCI1_atan2(int y, int x) {
156 	if (y < 0) {
157 		int a = kGetAngle_SCI1_atan2(-y, -x);
158 		if (a == 180)
159 			return 0;
160 		else
161 			return 180 + a;
162 	}
163 	if (x < 0)
164 		return 90 + kGetAngle_SCI1_atan2(-x, y);
165 	if (y > x)
166 		return 90 - kGetAngle_SCI1_atan2_base(x, y);
167 	else
168 		return kGetAngle_SCI1_atan2_base(y, x);
169 
170 }
171 
kGetAngle_SCI1(int16 x1,int16 y1,int16 x2,int16 y2)172 uint16 kGetAngle_SCI1(int16 x1, int16 y1, int16 x2, int16 y2) {
173 	// We flip things around to get into the standard atan2 coordinate system
174 	return kGetAngle_SCI1_atan2(x2 - x1, y1 - y2);
175 
176 }
177 
178 /**
179  * Returns the angle (in degrees) between the two points determined by (x1, y1)
180  * and (x2, y2). The angle ranges from 0 to 359 degrees.
181  * What this function does is pretty simple but apparently the original is not
182  * accurate.
183  */
184 
kGetAngleWorker(int16 x1,int16 y1,int16 x2,int16 y2)185 uint16 kGetAngleWorker(int16 x1, int16 y1, int16 x2, int16 y2) {
186 	if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY)
187 		return kGetAngle_SCI1(x1, y1, x2, y2);
188 	else
189 		return kGetAngle_SCI0(x1, y1, x2, y2);
190 }
191 
192 
193 
kGetAngle(EngineState * s,int argc,reg_t * argv)194 reg_t kGetAngle(EngineState *s, int argc, reg_t *argv) {
195 	// Based on behavior observed with a test program created with
196 	// SCI Studio.
197 	int x1 = argv[0].toSint16();
198 	int y1 = argv[1].toSint16();
199 	int x2 = argv[2].toSint16();
200 	int y2 = argv[3].toSint16();
201 
202 	return make_reg(0, kGetAngleWorker(x1, y1, x2, y2));
203 }
204 
kGetDistance(EngineState * s,int argc,reg_t * argv)205 reg_t kGetDistance(EngineState *s, int argc, reg_t *argv) {
206 	int xdiff = (argc > 3) ? argv[3].toSint16() : 0;
207 	int ydiff = (argc > 2) ? argv[2].toSint16() : 0;
208 	int angle = (argc > 5) ? argv[5].toSint16() : 0;
209 	int xrel = (int)(((float) argv[1].toSint16() - xdiff) / cos(angle * M_PI / 180.0)); // This works because cos(0)==1
210 	int yrel = argv[0].toSint16() - ydiff;
211 	return make_reg(0, (int16)sqrt((float) xrel*xrel + yrel*yrel));
212 }
213 
kTimesSin(EngineState * s,int argc,reg_t * argv)214 reg_t kTimesSin(EngineState *s, int argc, reg_t *argv) {
215 	int angle = argv[0].toSint16();
216 	int factor = argv[1].toSint16();
217 
218 	return make_reg(0, (int16)(factor * sin(angle * M_PI / 180.0)));
219 }
220 
kTimesCos(EngineState * s,int argc,reg_t * argv)221 reg_t kTimesCos(EngineState *s, int argc, reg_t *argv) {
222 	int angle = argv[0].toSint16();
223 	int factor = argv[1].toSint16();
224 
225 	return make_reg(0, (int16)(factor * cos(angle * M_PI / 180.0)));
226 }
227 
kCosDiv(EngineState * s,int argc,reg_t * argv)228 reg_t kCosDiv(EngineState *s, int argc, reg_t *argv) {
229 	int angle = argv[0].toSint16();
230 	int value = argv[1].toSint16();
231 	double cosval = cos(angle * M_PI / 180.0);
232 
233 	if ((cosval < 0.0001) && (cosval > -0.0001)) {
234 		error("kCosDiv: Attempted division by zero");
235 		return SIGNAL_REG;
236 	} else
237 		return make_reg(0, (int16)(value / cosval));
238 }
239 
kSinDiv(EngineState * s,int argc,reg_t * argv)240 reg_t kSinDiv(EngineState *s, int argc, reg_t *argv) {
241 	int angle = argv[0].toSint16();
242 	int value = argv[1].toSint16();
243 	double sinval = sin(angle * M_PI / 180.0);
244 
245 	if ((sinval < 0.0001) && (sinval > -0.0001)) {
246 		error("kSinDiv: Attempted division by zero");
247 		return SIGNAL_REG;
248 	} else
249 		return make_reg(0, (int16)(value / sinval));
250 }
251 
kTimesTan(EngineState * s,int argc,reg_t * argv)252 reg_t kTimesTan(EngineState *s, int argc, reg_t *argv) {
253 	int param = argv[0].toSint16();
254 	int scale = (argc > 1) ? argv[1].toSint16() : 1;
255 
256 	param -= 90;
257 	if ((param % 90) == 0) {
258 		error("kTimesTan: Attempted tan(pi/2)");
259 		return SIGNAL_REG;
260 	} else
261 		return make_reg(0, (int16) - (tan(param * M_PI / 180.0) * scale));
262 }
263 
kTimesCot(EngineState * s,int argc,reg_t * argv)264 reg_t kTimesCot(EngineState *s, int argc, reg_t *argv) {
265 	int param = argv[0].toSint16();
266 	int scale = (argc > 1) ? argv[1].toSint16() : 1;
267 
268 	if ((param % 90) == 0) {
269 		error("kTimesCot: Attempted tan(pi/2)");
270 		return SIGNAL_REG;
271 	} else
272 		return make_reg(0, (int16)(tan(param * M_PI / 180.0) * scale));
273 }
274 
275 #ifdef ENABLE_SCI32
276 
kMulDiv(EngineState * s,int argc,reg_t * argv)277 reg_t kMulDiv(EngineState *s, int argc, reg_t *argv) {
278 	int16 multiplicant = argv[0].toSint16();
279 	int16 multiplier = argv[1].toSint16();
280 	int16 denominator = argv[2].toSint16();
281 
282 	// Sanity check...
283 	if (!denominator) {
284 		error("kMulDiv: attempt to divide by zero (%d * %d / %d", multiplicant, multiplier, denominator);
285 		return NULL_REG;
286 	}
287 
288 	return make_reg(0, multiplicant * multiplier / denominator);
289 }
290 
291 #endif
292 
293 } // End of namespace Sci
294