1 #include "pch.h"
2 #include "loader.h"
3 #include "GroupData.h"
4 #include "pb.h"
5 #include "pinball.h"
6 #include "Sound.h"
7 #include "zdrv.h"
8 #include <cmath> // of std::isnan()
9 using std::isnan;
10
11
12 errorMsg loader::loader_errors[] =
13 {
14 errorMsg{0, "Bad Handle"},
15 errorMsg{1, "No Type Field"},
16 errorMsg{2, "No Attributes Field"},
17 errorMsg{3, "Wrong Type: MATERIAL Expected"},
18 errorMsg{4, "Wrong Type: KICKER Expected"},
19 errorMsg{5, "Wrong Type: AN_OBJECT Expected"},
20 errorMsg{6, "Wrong Type: A_STATE Expected"},
21 errorMsg{7, "STATES (re)defined in a state"},
22 errorMsg{9, "Unrecognized Attribute"},
23 errorMsg{0x0A, "Unrecognized float Attribute"},
24 errorMsg{0x0B, "No float Attributes Field"},
25 errorMsg{0x0D, "float Attribute not found"},
26 errorMsg{0x0C, "state_index out of range"},
27 errorMsg{0x0F, "loader_material() reports failure"},
28 errorMsg{0x0E, "loader_kicker() reports failure"},
29 errorMsg{0x10, "loader_state_id() reports failure"},
30 errorMsg{0x8, "# walls doesn't match data size"},
31 errorMsg{0x11, "loader_query_visual_states()"},
32 errorMsg{0x12, "loader_query_visual()"},
33 errorMsg{0x15, "loader_material()"},
34 errorMsg{0x14, "loader_kicker()"},
35 errorMsg{0x16, "loader_query_attribute()"},
36 errorMsg{0x17, "loader_query_iattribute()"},
37 errorMsg{0x13, "loader_query_name()"},
38 errorMsg{0x18, "loader_state_id()"},
39 errorMsg{0x19, "loader_get_sound_id()"},
40 errorMsg{0x1A, "sound reference is not A_SOUND record"},
41 errorMsg{-1, "Unknown"},
42 };
43
44 int loader::sound_count = 1;
45 int loader::loader_sound_count;
46 DatFile* loader::loader_table;
47 DatFile* loader::sound_record_table;
48 soundListStruct loader::sound_list[65];
49
error(int errorCode,int captionCode)50 int loader::error(int errorCode, int captionCode)
51 {
52 auto curCode = loader_errors;
53 const char *errorText = nullptr, *errorCaption = nullptr;
54 auto index = 0;
55 while (curCode->Code >= 0)
56 {
57 if (errorCode == curCode->Code)
58 errorText = curCode->Message;
59 if (captionCode == curCode->Code)
60 errorCaption = curCode->Message;
61 curCode++;
62 index++;
63 }
64
65 if (!errorText)
66 errorText = loader_errors[index].Message;
67 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, errorCaption, errorText, nullptr);
68 return -1;
69 }
70
default_vsi(visualStruct * visual)71 void loader::default_vsi(visualStruct* visual)
72 {
73 visual->CollisionGroup = 0;
74 visual->Kicker.Threshold = 8.9999999e10f;
75 visual->Kicker.HardHitSoundId = 0;
76 visual->Smoothness = 0.94999999f;
77 visual->Elasticity = 0.60000002f;
78 visual->FloatArrCount = 0;
79 visual->SoftHitSoundId = 0;
80 visual->Bitmap = nullptr;
81 visual->ZMap = nullptr;
82 visual->SoundIndex3 = 0;
83 visual->SoundIndex4 = 0;
84 }
85
loadfrom(DatFile * datFile)86 void loader::loadfrom(DatFile* datFile)
87 {
88 loader_table = datFile;
89 sound_record_table = loader_table;
90
91 for (auto groupIndex = 0; groupIndex < static_cast<int>(datFile->Groups.size()); ++groupIndex)
92 {
93 auto value = reinterpret_cast<int16_t*>(datFile->field(groupIndex, FieldTypes::ShortValue));
94 if (value && *value == 202)
95 {
96 if (sound_count < 65)
97 {
98 sound_list[sound_count].WavePtr = nullptr;
99 sound_list[sound_count].GroupIndex = groupIndex;
100 sound_count++;
101 }
102 }
103 }
104 loader_sound_count = sound_count;
105 }
106
unload()107 void loader::unload()
108 {
109 for (int index = 1; index < sound_count; ++index)
110 {
111 Sound::FreeSound(sound_list[index].WavePtr);
112 sound_list[index].Loaded = 0;
113 sound_list[index].WavePtr = nullptr;
114 }
115
116 sound_count = 1;
117 }
118
get_sound_id(int groupIndex)119 int loader::get_sound_id(int groupIndex)
120 {
121 int16_t soundIndex = 1;
122 if (sound_count <= 1)
123 {
124 error(25, 26);
125 return -1;
126 }
127
128 while (sound_list[soundIndex].GroupIndex != groupIndex)
129 {
130 ++soundIndex;
131 if (soundIndex >= sound_count)
132 {
133 error(25, 26);
134 return -1;
135 }
136 }
137
138 if (!sound_list[soundIndex].Loaded && !sound_list[soundIndex].WavePtr)
139 {
140 WaveHeader wavHeader{};
141
142 int soundGroupId = sound_list[soundIndex].GroupIndex;
143 sound_list[soundIndex].Duration = 0.0;
144 if (soundGroupId > 0 && !pinball::quickFlag)
145 {
146 auto value = reinterpret_cast<int16_t*>(loader_table->field(soundGroupId,
147 FieldTypes::ShortValue));
148 if (value && *value == 202)
149 {
150 std::string fileName = loader_table->field(soundGroupId, FieldTypes::String);
151
152 // File name is in lower case, while game data is in upper case.
153 std::transform(fileName.begin(), fileName.end(), fileName.begin(),
154 [](unsigned char c) { return std::toupper(c); });
155 if (pb::FullTiltMode)
156 {
157 // FT sounds are in SOUND subfolder
158 fileName.insert(0, 1, PathSeparator);
159 fileName.insert(0, "SOUND");
160 }
161
162 auto filePath = pinball::make_path_name(fileName);
163 auto file = fopen(filePath.c_str(), "rb");
164 if (file)
165 {
166 fread(&wavHeader, 1, sizeof wavHeader, file);
167 fclose(file);
168 }
169
170 auto sampleCount = wavHeader.data_size / (wavHeader.channels * (wavHeader.bits_per_sample / 8.0));
171 sound_list[soundIndex].Duration = static_cast<float>(sampleCount / wavHeader.sample_rate);
172 sound_list[soundIndex].WavePtr = Sound::LoadWaveFile(filePath);
173 }
174 }
175 }
176
177 ++sound_list[soundIndex].Loaded;
178 return soundIndex;
179 }
180
181
query_handle(LPCSTR lpString)182 int loader::query_handle(LPCSTR lpString)
183 {
184 return loader_table->record_labeled(lpString);
185 }
186
query_visual_states(int groupIndex)187 short loader::query_visual_states(int groupIndex)
188 {
189 short result;
190 if (groupIndex < 0)
191 return error(0, 17);
192 auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortArray));
193 if (shortArr && *shortArr == 100)
194 result = shortArr[1];
195 else
196 result = 1;
197 return result;
198 }
199
query_name(int groupIndex)200 char* loader::query_name(int groupIndex)
201 {
202 if (groupIndex < 0)
203 {
204 error(0, 19);
205 return nullptr;
206 }
207
208 return loader_table->field(groupIndex, FieldTypes::GroupName);
209 }
210
query_iattribute(int groupIndex,int firstValue,int * arraySize)211 int16_t* loader::query_iattribute(int groupIndex, int firstValue, int* arraySize)
212 {
213 if (groupIndex < 0)
214 {
215 error(0, 22);
216 return nullptr;
217 }
218
219 for (auto skipIndex = 0;; ++skipIndex)
220 {
221 auto shortArr = reinterpret_cast<int16_t*>(loader_table->field_nth(groupIndex,
222 FieldTypes::ShortArray, skipIndex));
223 if (!shortArr)
224 break;
225 if (*shortArr == firstValue)
226 {
227 *arraySize = loader_table->field_size(groupIndex, FieldTypes::ShortArray) / 2 - 1;
228 return shortArr + 1;
229 }
230 }
231
232 error(2, 23);
233 *arraySize = 0;
234 return nullptr;
235 }
236
query_float_attribute(int groupIndex,int groupIndexOffset,int firstValue)237 float* loader::query_float_attribute(int groupIndex, int groupIndexOffset, int firstValue)
238 {
239 if (groupIndex < 0)
240 {
241 error(0, 22);
242 return nullptr;
243 }
244
245 int stateId = state_id(groupIndex, groupIndexOffset);
246 if (stateId < 0)
247 {
248 error(16, 22);
249 return nullptr;
250 }
251
252 for (auto skipIndex = 0;; ++skipIndex)
253 {
254 auto floatArr = reinterpret_cast<float*>(loader_table->field_nth(stateId, FieldTypes::FloatArray,
255 skipIndex));
256 if (!floatArr)
257 break;
258 if (static_cast<int16_t>(floor(*floatArr)) == firstValue)
259 return floatArr + 1;
260 }
261
262 error(13, 22);
263 return nullptr;
264 }
265
query_float_attribute(int groupIndex,int groupIndexOffset,int firstValue,float defVal)266 float loader::query_float_attribute(int groupIndex, int groupIndexOffset, int firstValue, float defVal)
267 {
268 if (groupIndex < 0)
269 {
270 error(0, 22);
271 return NAN;
272 }
273
274 int stateId = state_id(groupIndex, groupIndexOffset);
275 if (stateId < 0)
276 {
277 error(16, 22);
278 return NAN;
279 }
280
281 for (auto skipIndex = 0;; ++skipIndex)
282 {
283 auto floatArr = reinterpret_cast<float*>(loader_table->field_nth(stateId,
284 FieldTypes::FloatArray, skipIndex));
285 if (!floatArr)
286 break;
287 if (static_cast<int16_t>(floor(*floatArr)) == firstValue)
288 return floatArr[1];
289 }
290
291 if (!isnan(defVal))
292 return defVal;
293 error(13, 22);
294 return NAN;
295 }
296
material(int groupIndex,visualStruct * visual)297 int loader::material(int groupIndex, visualStruct* visual)
298 {
299 if (groupIndex < 0)
300 return error(0, 21);
301 auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortValue));
302 if (!shortArr)
303 return error(1, 21);
304 if (*shortArr != 300)
305 return error(3, 21);
306 auto floatArr = reinterpret_cast<float*>(loader_table->field(groupIndex, FieldTypes::FloatArray));
307 if (!floatArr)
308 return error(11, 21);
309
310 int floatArrLength = loader_table->field_size(groupIndex, FieldTypes::FloatArray) / 4;
311 for (auto index = 0; index < floatArrLength; index += 2)
312 {
313 switch (static_cast<int>(floor(floatArr[index])))
314 {
315 case 301:
316 visual->Smoothness = floatArr[index + 1];
317 break;
318 case 302:
319 visual->Elasticity = floatArr[index + 1];
320 break;
321 case 304:
322 visual->SoftHitSoundId = get_sound_id(static_cast<int>(floor(floatArr[index + 1])));
323 break;
324 default:
325 return error(9, 21);
326 }
327 }
328 return 0;
329 }
330
331
play_sound(int soundIndex)332 float loader::play_sound(int soundIndex)
333 {
334 if (soundIndex <= 0)
335 return 0.0;
336 Sound::PlaySound(sound_list[soundIndex].WavePtr, pb::time_ticks);
337 return sound_list[soundIndex].Duration;
338 }
339
state_id(int groupIndex,int groupIndexOffset)340 int loader::state_id(int groupIndex, int groupIndexOffset)
341 {
342 auto visualState = query_visual_states(groupIndex);
343 if (visualState <= 0)
344 return error(12, 24);
345 auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortValue));
346 if (!shortArr)
347 return error(1, 24);
348 if (*shortArr != 200)
349 return error(5, 24);
350 if (groupIndexOffset > visualState)
351 return error(12, 24);
352 if (!groupIndexOffset)
353 return groupIndex;
354
355 groupIndex += groupIndexOffset;
356 shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortValue));
357 if (!shortArr)
358 return error(1, 24);
359 if (*shortArr != 201)
360 return error(6, 24);
361 return groupIndex;
362 }
363
kicker(int groupIndex,visualKickerStruct * kicker)364 int loader::kicker(int groupIndex, visualKickerStruct* kicker)
365 {
366 if (groupIndex < 0)
367 return error(0, 20);
368 auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(groupIndex, FieldTypes::ShortValue));
369 if (!shortArr)
370 return error(1, 20);
371 if (*shortArr != 400)
372 return error(4, 20);
373 auto floatArr = reinterpret_cast<float*>(loader_table->field(groupIndex, FieldTypes::FloatArray));
374 if (!floatArr)
375 return error(11, 20);
376 int floatArrLength = loader_table->field_size(groupIndex, FieldTypes::FloatArray) / 4;
377 if (floatArrLength <= 0)
378 return 0;
379
380 for (auto index = 0; index < floatArrLength;)
381 {
382 int floorVal = static_cast<int>(floor(*floatArr++));
383 switch (floorVal)
384 {
385 case 401:
386 kicker->Threshold = *floatArr;
387 break;
388 case 402:
389 kicker->Boost = *floatArr;
390 break;
391 case 403:
392 kicker->ThrowBallMult = *floatArr;
393 break;
394 case 404:
395 kicker->ThrowBallAcceleration = *reinterpret_cast<vector_type*>(floatArr);
396 floatArr += 3;
397 index += 4;
398 break;
399 case 405:
400 kicker->ThrowBallAngleMult = *floatArr;
401 break;
402 case 406:
403 kicker->HardHitSoundId = get_sound_id(static_cast<int>(floor(*floatArr)));
404 break;
405 default:
406 return error(10, 20);
407 }
408 if (floorVal != 404)
409 {
410 floatArr++;
411 index += 2;
412 }
413 }
414 return 0;
415 }
416
417
query_visual(int groupIndex,int groupIndexOffset,visualStruct * visual)418 int loader::query_visual(int groupIndex, int groupIndexOffset, visualStruct* visual)
419 {
420 default_vsi(visual);
421 if (groupIndex < 0)
422 return error(0, 18);
423 auto stateId = state_id(groupIndex, groupIndexOffset);
424 if (stateId < 0)
425 return error(16, 18);
426
427 visual->Bitmap = loader_table->GetBitmap(stateId);
428 visual->ZMap = loader_table->GetZMap(stateId);
429
430 auto shortArr = reinterpret_cast<int16_t*>(loader_table->field(stateId, FieldTypes::ShortArray));
431 if (shortArr)
432 {
433 unsigned int shortArrSize = loader_table->field_size(stateId, FieldTypes::ShortArray);
434 for (auto index = 0u; index < shortArrSize / 2;)
435 {
436 switch (shortArr[0])
437 {
438 case 100:
439 if (groupIndexOffset)
440 return error(7, 18);
441 break;
442 case 300:
443 if (material(shortArr[1], visual))
444 return error(15, 18);
445 break;
446 case 304:
447 visual->SoftHitSoundId = get_sound_id(shortArr[1]);
448 break;
449 case 400:
450 if (kicker(shortArr[1], &visual->Kicker))
451 return error(14, 18);
452 break;
453 case 406:
454 visual->Kicker.HardHitSoundId = get_sound_id(shortArr[1]);
455 break;
456 case 602:
457 visual->CollisionGroup |= 1 << shortArr[1];
458 break;
459 case 1100:
460 visual->SoundIndex4 = get_sound_id(shortArr[1]);
461 break;
462 case 1101:
463 visual->SoundIndex3 = get_sound_id(shortArr[1]);
464 break;
465 case 1500:
466 shortArr += 7;
467 index += 7;
468 break;
469 default:
470 return error(9, 18);
471 }
472 shortArr += 2;
473 index += 2;
474 }
475 }
476
477 if (!visual->CollisionGroup)
478 visual->CollisionGroup = 1;
479 auto floatArr = reinterpret_cast<float*>(loader_table->field(stateId, FieldTypes::FloatArray));
480 if (!floatArr)
481 return 0;
482 if (*floatArr != 600.0f)
483 return 0;
484
485 visual->FloatArrCount = loader_table->field_size(stateId, FieldTypes::FloatArray) / 4 / 2 - 2;
486 auto floatVal = static_cast<int>(floor(floatArr[1]) - 1.0f);
487 switch (floatVal)
488 {
489 case 0:
490 visual->FloatArrCount = 1;
491 break;
492 case 1:
493 visual->FloatArrCount = 2;
494 break;
495 default:
496 if (floatVal != visual->FloatArrCount)
497 return error(8, 18);
498 break;
499 }
500
501 visual->FloatArr = floatArr + 2;
502 return 0;
503 }
504