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