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