1 /////////////////////////////////////////
2 //
3 //                  OpenLieroX
4 //
5 // code under LGPL, based on JasonBs work,
6 // enhanced by Dark Charlie and Albert Zeyer
7 //
8 //
9 /////////////////////////////////////////
10 
11 
12 // Game script class
13 // Created 7/2/02
14 // Jason Boettcher
15 
16 /*
17  reference for original Compile* sources:
18  http://openlierox.svn.sourceforge.net/viewvc/openlierox/src/common/CGameScript.cpp?revision=3781&view=markup
19  */
20 
21 #include <cstdarg>
22 
23 #include "EndianSwap.h"
24 #include "LieroX.h"
25 #include "Debug.h"
26 #include "FindFile.h"
27 #include "StringUtils.h"
28 #include "CGameScript.h"
29 #include "Error.h"
30 #include "ConfigHandler.h"
31 #include "ProjectileDesc.h"
32 #include "WeaponDesc.h"
33 #include "IniReader.h"
34 
35 
36 
initNewWeapons(int num)37 void CGameScript::initNewWeapons(int num) {
38 	if(Weapons) delete[] Weapons;
39 	SetNumWeapons(num);
40 	Weapons = new weapon_t[num];
41 }
42 
43 
44 
45 ///////////////////
46 // Write a string in pascal format
writeString(const std::string & szString,FILE * fp)47 static void writeString(const std::string& szString, FILE *fp)
48 {
49     if(szString == "") return;
50 
51 	size_t length = szString.size();
52 	if(length > 255) {
53 		warnings << "i will cut the following string for writing: " << szString << endl;
54 		length = 255;
55 	}
56 	uchar len = (uchar)length;
57 
58     fwrite( &len, sizeof(uchar), 1, fp );
59     fwrite( szString.c_str(),sizeof(char), length, fp );
60 }
61 
62 
63 ///////////////////
64 // Read a string in pascal format
readString(FILE * fp)65 static std::string readString(FILE *fp)
66 {
67 	char buf[256];
68 
69 	uchar length;
70 	if(fread( &length, sizeof(uchar), 1, fp ) == 0) return "";
71 	if(fread( buf,sizeof(char), length, fp ) < length) return "";
72 
73 	buf[length] = '\0';
74 
75 	return buf;
76 }
77 
78 
79 
80 ///////////////////
81 // Save the script (compiler)
Save(const std::string & filename)82 int CGameScript::Save(const std::string& filename)
83 {
84 	FILE *fp;
85 	int n;
86 
87 
88 	// Open it
89 	fp = OpenGameFile(filename,"wb");
90 	if(fp == NULL) {
91 		errors << "CGameScript::Save: Could not open " << filename << " for writing" << endl;
92 		return false;
93 	}
94 
95 	Header.Version = GS_VERSION;
96 	strcpy(Header.ID,"Liero Game Script");
97 
98 
99 	// Header
100 	gs_header_t tmpheader = Header;
101 	EndianSwap(tmpheader.Version);
102 	fwrite(&tmpheader,sizeof(gs_header_t),1,fp);
103 
104 	fwrite_endian_compat((NumWeapons),sizeof(int),1,fp);
105 
106 	savedProjs.clear();
107 
108 	// Weapons
109 	weapon_t *wpn = Weapons;
110 
111 	for(n=0;n<NumWeapons;n++,wpn++) {
112 
113         writeString(wpn->Name, fp);
114 		fwrite_endian_compat((wpn->Type),          sizeof(int),    1, fp);
115 
116 		// Special
117 		if(wpn->Type == WPN_SPECIAL) {
118 			fwrite_endian_compat((wpn->Special),   sizeof(int),    1, fp);
119 			fwrite_endian_compat((wpn->tSpecial),  sizeof(gs_special_t), 1, fp); // WARNING: this works, because it contains only one int
120 			fwrite_endian_compat((wpn->Recharge*10.f),  sizeof(float),  1, fp);
121 			fwrite_endian_compat((wpn->Drain),     sizeof(float),  1, fp);
122 			fwrite_endian_compat((wpn->ROF*1000.0f),       sizeof(float),  1, fp);
123 			fwrite_endian<int>(fp, wpn->LaserSight);
124 		}
125 
126 		// Beam
127 		if(wpn->Type == WPN_BEAM) {
128 			fwrite_endian_compat((wpn->Recoil),    sizeof(int),    1, fp);
129 			fwrite_endian_compat((wpn->Recharge*10.f),  sizeof(float),  1, fp);
130 			fwrite_endian_compat((wpn->Drain),     sizeof(float),  1, fp);
131 			fwrite_endian_compat((wpn->ROF*1000.0f),       sizeof(float),  1, fp);
132 			fwrite_endian<int>(fp, wpn->LaserSight);
133 
134 			wpn->Bm.write(this, fp);
135 		}
136 
137 		// Projectile
138 		if(wpn->Type == WPN_PROJECTILE) {
139 			fwrite_endian_compat((wpn->Class),     sizeof(int),    1, fp);
140 			fwrite_endian_compat((wpn->Recoil),    sizeof(int),    1, fp);
141 			fwrite_endian_compat((wpn->Recharge*10.f),  sizeof(float),  1, fp);
142 			fwrite_endian_compat((wpn->Drain),     sizeof(float),  1, fp);
143 			fwrite_endian_compat((wpn->ROF*1000.0f),       sizeof(float),  1, fp);
144 
145 			if(Header.Version <= GS_LX56_VERSION) {
146 				fwrite_endian_compat((wpn->Proj.Speed), sizeof(int),    1, fp);
147 				fwrite_endian_compat((wpn->Proj.SpeedVar),sizeof(float),1, fp);
148 				fwrite_endian_compat((wpn->Proj.Spread),sizeof(float),  1, fp);
149 				fwrite_endian_compat((wpn->Proj.Amount),sizeof(int),    1, fp);
150 				fwrite_endian<int>(fp, wpn->LaserSight);
151 
152 				fwrite_endian<int>(fp, wpn->UseSound);
153 				if(wpn->UseSound)
154 					writeString(wpn->SndFilename, fp);
155 
156 				// Recursively save the projectile id's
157 				SaveProjectile(wpn->Proj.Proj,fp);
158 			}
159 			else { // newer GS versions
160 				fwrite_endian<char>(fp, wpn->LaserSight);
161 
162 				fwrite_endian<char>(fp, wpn->UseSound);
163 				if(wpn->UseSound)
164 					writeString(wpn->SndFilename, fp);
165 
166 				// Recursively save the projectile id's
167 				wpn->Proj.write(this, fp);
168 			}
169 		}
170 
171 		if(Header.Version > GS_LX56_VERSION)
172 			wpn->FinalProj.write(this, fp);
173 	}
174 
175 	// Extra stuff
176 
177 
178 	// Ninja Rope
179 	fwrite_endian_compat((RopeLength),sizeof(int),1,fp);
180 	fwrite_endian_compat((RestLength),sizeof(int),1,fp);
181 	fwrite_endian_compat((Strength),sizeof(float),1,fp);
182 
183 	// Worm
184 	gs_worm_t tmpworm = Worm;
185 	EndianSwap(tmpworm.AngleSpeed);
186 	EndianSwap(tmpworm.GroundSpeed);
187 	EndianSwap(tmpworm.AirSpeed);
188 	EndianSwap(tmpworm.Gravity);
189 	EndianSwap(tmpworm.JumpForce);
190 	EndianSwap(tmpworm.AirFriction);
191 	EndianSwap(tmpworm.GroundFriction);
192 	fwrite(&tmpworm, sizeof(gs_worm_t), 1, fp);
193 
194 
195 	fclose(fp);
196 
197 	savedProjs.clear();
198 
199 	return true;
200 }
201 
202 
203 ///////////////////
204 // Save a projectile
SaveProjectile(proj_t * proj,FILE * fp)205 bool CGameScript::SaveProjectile(proj_t *proj, FILE *fp)
206 {
207 	if(!proj)
208 		return false;
209 
210 	if(Header.Version > GS_LX56_VERSION) {
211 		// well, that's slow but it's not worth to create a reverted-map just for saving
212 		int index = -1;
213 		for(Projectiles::iterator it = projectiles.begin(); it != projectiles.end(); ++it) {
214 			if(it->second == proj) {
215 				index = it->first;
216 				break;
217 			}
218 		}
219 		fwrite_endian<int>(fp, index);
220 		if(savedProjs.find(proj) != savedProjs.end()) return false;
221 		savedProjs.insert(proj);
222 	}
223 
224 	fwrite_endian_compat((proj->Type),			sizeof(int),1,fp);
225 	fwrite_endian_compat((proj->Timer.Time),	sizeof(float),1,fp);
226 	fwrite_endian_compat((proj->Timer.TimeVar),sizeof(float),1,fp);
227 	fwrite_endian_compat((proj->Trail.Type),		sizeof(int),1,fp);
228 	//fwrite_endian_compat(proj->UseCustomGravity, sizeof(int), 1, fp); // use this to test static assert (should fail if sizeof(bool)!=sizeof(int))
229 	fwrite_endian<int>(fp, proj->UseCustomGravity);
230 
231 	if(proj->UseCustomGravity)
232 		fwrite_endian_compat((proj->Gravity),	sizeof(int), 1, fp);
233 
234 	fwrite_endian_compat((proj->Dampening),	sizeof(int), 1, fp);
235 
236 	if(Header.Version > GS_LX56_VERSION) {
237 		fwrite_endian<int>(fp, proj->AttractiveForce);
238 		fwrite_endian<int>(fp, proj->AttractiveForceType);
239 		fwrite_endian<int>(fp, proj->AttractiveForceObjects);
240 		fwrite_endian<int>(fp, proj->AttractiveForceClasses);
241 		fwrite_endian<int>(fp, proj->AttractiveForceRadius);
242 		fwrite_endian<bool>(fp, proj->AttractiveForceThroughWalls);
243 		fwrite_endian<int>(fp, proj->Width);
244 		fwrite_endian<int>(fp, proj->Height);
245 	}
246 
247     // Pixel type
248 	switch(proj->Type) {
249 		case PRJ_POLYGON:
250 			fwrite_endian<Uint32>(fp, (Uint32)proj->polygon.getPoints().size());
251 			for(Polygon2D::Points::const_iterator p = proj->polygon.getPoints().begin(); p != proj->polygon.getPoints().end(); ++p) {
252 				fwrite_endian<int>(fp, p->x);
253 				fwrite_endian<int>(fp, p->y);
254 			}
255 
256 			// fallthrough to save color
257 		case PRJ_RECT:
258 		case PRJ_CIRCLE:
259 		case PRJ_PIXEL:
260 			fwrite_endian_compat(((int)proj->Colour.size()), sizeof(int), 1, fp);
261 			for(size_t i = 0; i < proj->Colour.size(); ++i) {
262 				if(Header.Version <= GS_LX56_VERSION && i >= 2) {
263 					warnings << "SaveProjectile: projectile has more than 2 colors, other colors are ignored for LX56 format" << endl;
264 					break;
265 				}
266 				if(Header.Version <= GS_LX56_VERSION) {
267 					fwrite_endian_compat(((int)proj->Colour[i].r),   sizeof(int),1,fp);
268 					fwrite_endian_compat(((int)proj->Colour[i].g),   sizeof(int),1,fp);
269 					fwrite_endian_compat(((int)proj->Colour[i].b),   sizeof(int),1,fp);
270 				} else {
271 					fwrite_endian_compat((proj->Colour[i].r), 1,1,fp);
272 					fwrite_endian_compat((proj->Colour[i].g), 1,1,fp);
273 					fwrite_endian_compat((proj->Colour[i].b), 1,1,fp);
274 					fwrite_endian_compat((proj->Colour[i].a), 1,1,fp);
275 				}
276 			}
277 			break;
278 
279 		case PRJ_IMAGE:
280 			writeString(proj->ImgFilename, fp);
281 			fwrite_endian<int>(fp, proj->Rotating);
282 			fwrite_endian_compat((proj->RotIncrement), sizeof(int), 1, fp);
283 			fwrite_endian_compat((proj->RotSpeed), sizeof(int),1,fp);
284 			fwrite_endian<int>(fp, proj->UseAngle);
285 			fwrite_endian<int>(fp, proj->UseSpecAngle);
286 			if(proj->UseAngle || proj->UseSpecAngle)
287 				fwrite_endian_compat((proj->AngleImages),sizeof(int),1,fp);
288 			fwrite_endian<int>(fp, proj->Animating);
289 			if(proj->Animating) {
290 				fwrite_endian_compat((proj->AnimRate),sizeof(float),1,fp);
291 				fwrite_endian_compat((proj->AnimType),sizeof(int),1,fp);
292 			}
293 			break;
294 
295 		case __PRJ_LBOUND: case __PRJ_UBOUND: errors << "SaveProjectile: PRJ BOUND" << endl;
296 	}
297 
298 
299 
300 
301 	//
302 	// Hit
303 	//
304 	if(Header.Version <= GS_LX56_VERSION) {
305 		fwrite_endian_compat((proj->Hit.Type),sizeof(int),1,fp);
306 
307 		// Hit::Explode
308 		if(proj->Hit.Type == PJ_EXPLODE) {
309 			fwrite_endian_compat((proj->Hit.Damage),		sizeof(int),1,fp);
310 			fwrite_endian<int>(fp, proj->Hit.Projectiles);
311 			fwrite_endian<int>(fp, proj->Hit.UseSound);
312 			fwrite_endian_compat((proj->Hit.Shake),		sizeof(int),1,fp);
313 			if(proj->Hit.UseSound)
314 				writeString(proj->Hit.SndFilename, fp);
315 		}
316 
317 		// Hit::Bounce
318 		if(proj->Hit.Type == PJ_BOUNCE) {
319 			fwrite_endian_compat((proj->Hit.BounceCoeff),	sizeof(float),	1,fp);
320 			fwrite_endian_compat((proj->Hit.BounceExplode),sizeof(int),	1,fp);
321 		}
322 
323 		// Hit::Carve
324 		if(proj->Hit.Type == PJ_CARVE) {
325 			fwrite_endian_compat((proj->Hit.Damage),		sizeof(int),1,fp);
326 		}
327 	}
328 	else { // newer GS version
329 		proj->Hit.write(this, fp);
330 	}
331 
332 
333 
334 
335 	//
336 	// Timer
337 	//
338 	if(proj->Timer.Time > 0) {
339 		if(Header.Version <= GS_LX56_VERSION) {
340 			fwrite_endian_compat((proj->Timer.Type),sizeof(int),1,fp);
341 			if(proj->Timer.Type == PJ_EXPLODE) {
342 				fwrite_endian_compat((proj->Timer.Damage),		sizeof(int),1,fp);
343 				fwrite_endian<int>(fp, proj->Timer.Projectiles);
344 				fwrite_endian_compat((proj->Timer.Shake),sizeof(int),1,fp);
345 			}
346 		}
347 		else {
348 			proj->Timer.write(this, fp);
349 		}
350 	}
351 
352 
353 	//
354 	// Player hit
355 	//
356 	if(Header.Version <= GS_LX56_VERSION) {
357 		fwrite_endian_compat((proj->PlyHit.Type),sizeof(int),1,fp);
358 
359 		// PlyHit::Explode || PlyHit::Injure
360 		if(proj->PlyHit.Type == PJ_EXPLODE || proj->PlyHit.Type == PJ_INJURE) {
361 			fwrite_endian_compat((proj->PlyHit.Damage),sizeof(int),1,fp);
362 			fwrite_endian<int>(fp, proj->PlyHit.Projectiles);
363 		}
364 
365 		// PlyHit::Bounce
366 		if(proj->PlyHit.Type == PJ_BOUNCE) {
367 			fwrite_endian_compat((proj->PlyHit.BounceCoeff),sizeof(float),1,fp);
368 		}
369 	}
370 	else { // newer GS version
371 		proj->PlyHit.write(this, fp);
372 	}
373 
374 
375 	if(Header.Version <= GS_LX56_VERSION) {
376 		// NOTE: this is obsolete, they are not used
377 
378 		//
379 		// Explode
380 		//
381 		fwrite_endian_compat((proj->Exp.Type),     sizeof(int), 1, fp);
382 		fwrite_endian_compat((proj->Exp.Damage),   sizeof(int), 1, fp);
383 		fwrite_endian<int>(fp, proj->Exp.Projectiles);
384 		fwrite_endian<int>(fp, proj->Exp.UseSound);
385 		if(proj->Exp.UseSound)
386 			writeString(proj->Exp.SndFilename, fp);
387 
388 
389 		//
390 		// Touch
391 		//
392 		fwrite_endian_compat((proj->Tch.Type),     sizeof(int), 1, fp);
393 		fwrite_endian_compat((proj->Tch.Damage),   sizeof(int), 1, fp);
394 		fwrite_endian<int>(fp, proj->Tch.Projectiles);
395 		fwrite_endian<int>(fp, proj->Tch.UseSound);
396 		if(proj->Tch.UseSound)
397 			writeString(proj->Tch.SndFilename, fp);
398 	}
399 
400 	if(Header.Version > GS_LX56_VERSION) {
401 		fwrite_endian<Uint32>(fp, (Uint32)proj->actions.size());
402 		for(Uint32 i = 0; i < proj->actions.size(); ++i) {
403 			proj->actions[i].write(this, fp);
404 		}
405 	}
406 
407 	if(Header.Version > GS_LX56_VERSION) {
408 		// always save, there are many cases where we could possibly need it
409 		proj->GeneralSpawnInfo.write(this, fp);
410 	}
411 	else if(proj->Timer.Projectiles || proj->Hit.Projectiles || proj->PlyHit.Projectiles || proj->Exp.Projectiles ||
412        proj->Tch.Projectiles) {
413 		fwrite_endian<int>(fp, proj->GeneralSpawnInfo.Useangle);
414 		fwrite_endian_compat((proj->GeneralSpawnInfo.Angle),	sizeof(int),	1, fp);
415 
416 		fwrite_endian_compat((proj->GeneralSpawnInfo.Amount),	sizeof(int),	1, fp);
417 		fwrite_endian_compat((proj->GeneralSpawnInfo.Spread),	sizeof(float),	1, fp);
418 		fwrite_endian_compat((proj->GeneralSpawnInfo.Speed),	sizeof(int),	1, fp);
419 		fwrite_endian_compat((proj->GeneralSpawnInfo.SpeedVar),	sizeof(float),	1, fp);
420 
421 		SaveProjectile(proj->GeneralSpawnInfo.Proj,fp);
422 	}
423 
424 
425 	// Projectile trail
426 	if(proj->Trail.Type == TRL_PROJECTILE) {
427 
428 		fwrite_endian<int>(fp, proj->Trail.Proj.UseParentVelocityForSpread);
429 		fwrite_endian_compat((proj->Trail.Delay*1000.0f),				sizeof(float),	1, fp);
430 
431 		if(Header.Version <= GS_LX56_VERSION) {
432 			fwrite_endian_compat((proj->Trail.Proj.Amount),			sizeof(int),	1, fp);
433 			fwrite_endian_compat((proj->Trail.Proj.Speed),				sizeof(int),	1, fp);
434 			fwrite_endian_compat((proj->Trail.Proj.SpeedVar),			sizeof(float),	1, fp);
435 			fwrite_endian_compat((proj->Trail.Proj.Spread),			sizeof(float),	1, fp);
436 
437 			SaveProjectile(proj->Trail.Proj.Proj, fp);
438 		}
439 		else { // newer GS versions
440 			proj->Trail.Proj.write(this, fp);
441 		}
442 
443 	}
444 
445 	return true;
446 }
447 
448 
449 ///////////////////
450 // Load the game script from a file (game)
Load(const std::string & dir)451 int CGameScript::Load(const std::string& dir)
452 {
453 	// TODO: remove that as soon as we do the gamescript loading in a seperate thread
454 	ScopedBackgroundLoadingAni backgroundLoadingAni(320, 280, 50, 50, Color(128,128,128), Color(64,64,64));
455 
456 	// Already cached externally
457 	/*
458 	// Try cache first
459 	CGameScript *cached = cCache.GetMod(dir);
460 	if (cached != NULL)  {
461 		CopyFrom(cached);
462 		return GSE_OK;
463 	}
464 	*/
465 	Shutdown();
466 
467 	int n;
468 	std::string filename = dir + "/script.lgs";
469 	sDirectory = dir;
470 
471 	// Open it
472 	FILE* fp = OpenGameFile(filename,"rb");
473 	if(fp == NULL) {
474 		if(IsFileAvailable(dir + "/main.txt")) {
475 			hints << "GameScript: '" << dir << "': loading from gamescript source" << endl;
476 			if(Compile(dir))
477 				return GSE_OK;
478 			else {
479 				warnings << "GameScript::Load(): could not compile source gamescript '" << dir << "'" << endl;
480 				return GSE_BAD;
481 			}
482 		}
483 
484 		warnings << "CGameScript::Load(): Could not load file " << filename << endl;
485 		return GSE_FILE;
486 	}
487 
488 	// Header
489 	fread_compat(Header,sizeof(gs_header_t),1,fp);
490 	EndianSwap(Header.Version);
491 	// for security
492 	fix_markend(Header.ID);
493 	fix_markend(Header.ModName);
494 
495 	// Check ID
496 	if(strcmp(Header.ID,"Liero Game Script") != 0) {
497 		fclose(fp);
498 		SetError("CGameScript::Load(): Bad script id");
499 		return GSE_BAD;
500 	}
501 
502 	// Check version
503 	if(Header.Version < GS_FIRST_SUPPORTED_VERSION || Header.Version > GS_VERSION) {
504 		warnings << "GS:CheckFile: WARNING: " << dir << "/script.lgs has a wrong version";
505 		warnings << " (" << (unsigned)Header.Version << ", required is in the range ";
506 		warnings << "[" << GS_FIRST_SUPPORTED_VERSION << "," << GS_VERSION << "])" << endl;
507 		fclose(fp);
508 		SetError("CGameScript::Load(): Bad script version");
509 		return GSE_VERSION;
510 	}
511 
512     // Clear an old mod file
513     modLog("Loading game mod file " + filename);
514 	//modLog("  ID = %s", Header.ID);
515 	//modLog("  Version = %i", Header.Version);
516 
517 	fread_compat(NumWeapons,sizeof(int),1,fp);
518 	EndianSwap(NumWeapons);
519 	//modLog("  NumWeapons = %i", NumWeapons);
520 
521 	// Do Allocations
522 	Weapons = new weapon_t[NumWeapons];
523 	if(Weapons == NULL) {
524 		SetError("SGameScript::Load(): Out of memory");
525 		return GSE_MEM;
526 	}
527 
528 	// Weapons
529 	weapon_t *wpn;
530 	for(n=0;n<NumWeapons;n++) {
531 		wpn = &Weapons[n];
532 
533 		wpn->ID = n;
534 		wpn->Proj.Proj = NULL;
535 
536         wpn->Name = readString(fp);
537 		fread_compat(wpn->Type,           sizeof(int),    1,fp);
538 		EndianSwap(wpn->Type);
539 
540 		// Special
541 		if(wpn->Type == WPN_SPECIAL) {
542 			fread_compat(wpn->Special,    sizeof(int),    1, fp);
543 			EndianSwap(wpn->Special);
544 			fread_compat(wpn->tSpecial,   sizeof(gs_special_t), 1, fp);
545 			EndianSwap(wpn->tSpecial.Thrust);
546 			fread_compat(wpn->Recharge,   sizeof(float),  1, fp);
547 			EndianSwap(wpn->Recharge);
548 			fread_compat(wpn->Drain,      sizeof(float),  1, fp);
549 			EndianSwap(wpn->Drain);
550 			fread_compat(wpn->ROF,        sizeof(float),  1, fp);
551 			EndianSwap(wpn->ROF);
552 			fread_endian<int>(fp, wpn->LaserSight);
553 		}
554 
555 		// Beam
556 		if(wpn->Type == WPN_BEAM) {
557 			fread_compat(wpn->Recoil,     sizeof(int),    1, fp);
558 			EndianSwap(wpn->Recoil);
559 			fread_compat(wpn->Recharge,   sizeof(float),  1, fp);
560 			EndianSwap(wpn->Recharge);
561 			fread_compat(wpn->Drain,      sizeof(float),  1, fp);
562 			EndianSwap(wpn->Drain);
563 			fread_compat(wpn->ROF,        sizeof(float),  1, fp);
564 			EndianSwap(wpn->ROF);
565 			fread_endian<int>(fp, wpn->LaserSight);
566 
567 			wpn->Bm.read(this, fp);
568 		}
569 
570 		// Projectile
571 		if(wpn->Type == WPN_PROJECTILE) {
572 			fread_compat(wpn->Class,sizeof(int),1,fp);
573 			EndianSwap(wpn->Class);
574 			fread_compat(wpn->Recoil,sizeof(int),1,fp);
575 			EndianSwap(wpn->Recoil);
576 			fread_compat(wpn->Recharge,sizeof(float),1,fp);
577 			EndianSwap(wpn->Recharge);
578 			fread_compat(wpn->Drain,sizeof(float),1,fp);
579 			EndianSwap(wpn->Drain);
580 			fread_compat(wpn->ROF,sizeof(float),1,fp);
581 			EndianSwap(wpn->ROF);
582 
583 			if(Header.Version <= GS_LX56_VERSION) {
584 				fread_compat(wpn->Proj.Speed,sizeof(int),1,fp);
585 				EndianSwap(wpn->Proj.Speed);
586 				fread_compat(wpn->Proj.SpeedVar,sizeof(float),1,fp);
587 				EndianSwap(wpn->Proj.SpeedVar);
588 				fread_compat(wpn->Proj.Spread,sizeof(float),1,fp);
589 				EndianSwap(wpn->Proj.Spread);
590 				fread_compat(wpn->Proj.Amount,sizeof(int),1,fp);
591 				EndianSwap(wpn->Proj.Amount);
592 				fread_endian<int>(fp, wpn->LaserSight);
593 
594 				fread_endian<int>(fp, wpn->UseSound);
595 				if(wpn->UseSound)
596 					wpn->SndFilename = readString(fp);
597 
598 				wpn->Proj.Proj = LoadProjectile(fp);
599 			}
600 			else { // newer GS versions
601 				fread_endian<char>(fp, wpn->LaserSight);
602 
603 				fread_endian<char>(fp, wpn->UseSound);
604 				if(wpn->UseSound)
605 					wpn->SndFilename = readString(fp);
606 
607 				wpn->Proj.read(this, fp);
608 			}
609 
610 			if(!bDedicated && wpn->UseSound) {
611 				// Load the sample
612 				wpn->smpSample = LoadGSSample(dir,wpn->SndFilename);
613 
614 				if(wpn->smpSample == NULL)
615 					wpn->UseSound = false;
616 			}
617 		}
618 
619 		wpn->ROF /= 1000.0f;
620 		wpn->Recharge /= 10.0f;
621 
622 		if(Header.Version > GS_LX56_VERSION)
623 			wpn->FinalProj.read(this, fp);
624 	}
625 
626 
627 	// Extra stuff
628 
629 
630 	// Ninja Rope
631 	fread_compat(RopeLength,sizeof(int),1,fp);
632 	EndianSwap(RopeLength);
633 	fread_compat(RestLength,sizeof(int),1,fp);
634 	EndianSwap(RestLength);
635 	fread_compat(Strength,sizeof(float),1,fp);
636 	EndianSwap(Strength);
637 
638 	// Worm
639 	fread_compat(Worm, sizeof(gs_worm_t), 1, fp);
640 	EndianSwap(Worm.AngleSpeed);
641 	EndianSwap(Worm.GroundSpeed);
642 	EndianSwap(Worm.AirSpeed);
643 	EndianSwap(Worm.Gravity);
644 	EndianSwap(Worm.JumpForce);
645 	EndianSwap(Worm.AirFriction);
646 	EndianSwap(Worm.GroundFriction);
647 
648 
649 	fclose(fp);
650 
651 	// Already cached externally
652 	// Save to cache
653 	//cCache.SaveMod(dir, this);
654 
655 	loaded = true;
656 
657 	return GSE_OK;
658 }
659 
660 
661 ///////////////////
662 // Load a projectile
LoadProjectile(FILE * fp)663 proj_t *CGameScript::LoadProjectile(FILE *fp)
664 {
665 	int projIndex = -1;
666 	if(Header.Version > GS_LX56_VERSION) {
667 		fread_endian<int>(fp, projIndex);
668 		std::map<int, proj_t*>::iterator f = projectiles.find(projIndex);
669 		if(f != projectiles.end())
670 			return f->second;
671 	}
672 	else
673 		projIndex = (int)projectiles.size();
674 
675 	proj_t *proj = new proj_t;
676 	if(proj == NULL)
677 		return NULL;
678 	projectiles[projIndex] = proj;
679 
680 	fread_compat(proj->Type,			sizeof(int),  1,fp);
681 	EndianSwap(proj->Type);
682 	fread_compat(proj->Timer.Time,	sizeof(float),1,fp);
683 	EndianSwap(proj->Timer.Time);
684 	fread_compat(proj->Timer.TimeVar,	sizeof(float),1,fp);
685 	EndianSwap(proj->Timer.TimeVar);
686 	fread_compat(proj->Trail.Type,			sizeof(int),  1,fp);
687 	EndianSwap(proj->Trail.Type);
688 	fread_endian<int>(fp, proj->UseCustomGravity);
689 	if(proj->UseCustomGravity)
690 	{
691 		fread_compat(proj->Gravity,	sizeof(int), 1, fp);
692 		EndianSwap(proj->Gravity);
693 	}
694 	fread_compat(proj->Dampening,		sizeof(int),  1, fp);
695 	EndianSwap(proj->Dampening);
696 
697 	if(Header.Version > GS_LX56_VERSION) {
698 		fread_endian<int>(fp, proj->AttractiveForce);
699 		fread_endian<int>(fp, proj->AttractiveForceType);
700 		fread_endian<int>(fp, proj->AttractiveForceObjects);
701 		fread_endian<int>(fp, proj->AttractiveForceClasses);
702 		fread_endian<int>(fp, proj->AttractiveForceRadius);
703 		fread_endian<bool>(fp, proj->AttractiveForceThroughWalls);
704 
705 		fread_endian<int>(fp, proj->Width);
706 		fread_endian<int>(fp, proj->Height);
707 	}
708 	else {
709 		proj->AttractiveForce = 0;
710 		proj->AttractiveForceType = ATT_GRAVITY;
711 		proj->AttractiveForceObjects = ATO_NONE;
712 		proj->AttractiveForceClasses = 0;
713 		proj->AttractiveForceRadius = 0;
714 		proj->AttractiveForceThroughWalls = true;
715 
716 		if(proj->Type == PRJ_PIXEL)
717 			proj->Width = proj->Height = 2;
718 		else
719 			proj->Width = proj->Height = 4;
720 	}
721 
722 	proj->Trail.Proj.Proj = NULL;
723 	proj->GeneralSpawnInfo.Proj = NULL;
724 	proj->Hit.Projectiles = false;
725 	proj->Timer.Projectiles = false;
726 	proj->PlyHit.Projectiles = false;
727 	proj->Animating = false;
728 	proj->Rotating = false;
729 	proj->RotIncrement = 0;
730 	proj->Timer.Shake = 0;
731 
732 	switch(proj->Type) {
733 		case PRJ_POLYGON: {
734 			proj->polygon.clear();
735 			Uint32 NumPoints = 0;
736 			fread_endian<Uint32>(fp, NumPoints);
737 			proj->polygon.startPointAdding();
738 			for(Uint32 i = 0; i < NumPoints; ++i) {
739 				VectorD2<int> p;
740 				fread_endian<int>(fp, p.x);
741 				fread_endian<int>(fp, p.y);
742 				proj->polygon.addPoint(p);
743 			}
744 			proj->polygon.endPointAdding();
745 		}
746 			// fallthrough to read color
747 		case PRJ_RECT:
748 		case PRJ_CIRCLE:
749 		case PRJ_PIXEL:
750 		{
751 			Uint32 NumColours = 0;
752 			fread_endian<int>(fp, NumColours);
753 			proj->Colour.resize(NumColours);
754 
755 			for(size_t i = 0; i < NumColours; ++i) {
756 				if(Header.Version <= GS_LX56_VERSION) {
757 					int color[3] = {0,0,0};
758 					fread_endian<int>(fp, color[0]);
759 					fread_endian<int>(fp, color[1]);
760 					fread_endian<int>(fp, color[2]);
761 					proj->Colour[i] = Color(color[0],color[1],color[2]);
762 				} else {
763 					fread_compat(proj->Colour[i].r,1,1,fp);
764 					fread_compat(proj->Colour[i].g,1,1,fp);
765 					fread_compat(proj->Colour[i].b,1,1,fp);
766 					fread_compat(proj->Colour[i].a,1,1,fp);
767 				}
768 			}
769 			break;
770 		}
771 		case PRJ_IMAGE:
772 			proj->ImgFilename = readString(fp);
773 
774 			if(!bDedicated) {
775 				proj->bmpImage = LoadGSImage(sDirectory, proj->ImgFilename);
776 				if(!proj->bmpImage)
777 					modLog("Could not open image '" + proj->ImgFilename + "'");
778 				else
779 					proj->bmpShadow = GenerateShadowSurface(proj->bmpImage);
780 			}
781 
782 			fread_endian<int>(fp, proj->Rotating);
783 			fread_compat(proj->RotIncrement, sizeof(int), 1, fp);
784 			EndianSwap(proj->RotIncrement);
785 			fread_compat(proj->RotSpeed,sizeof(int),1,fp);
786 			EndianSwap(proj->RotSpeed);
787 			fread_endian<int>(fp, proj->UseAngle);
788 			fread_endian<int>(fp, proj->UseSpecAngle);
789 			if(proj->UseAngle || proj->UseSpecAngle)
790 			{
791 				fread_compat(proj->AngleImages,sizeof(int),1,fp);
792 				EndianSwap(proj->AngleImages);
793 			}
794 			fread_endian<int>(fp, proj->Animating);
795 			if(proj->Animating) {
796 				fread_compat(proj->AnimRate,sizeof(float),1,fp);
797 				EndianSwap(proj->AnimRate);
798 				fread_compat(proj->AnimType,sizeof(int),1,fp);
799 				EndianSwap(proj->AnimType);
800 			}
801 			break;
802 
803 		case __PRJ_LBOUND: case __PRJ_UBOUND: errors << "LoadProjectile: PRJ BOUND" << endl;
804 	}
805 
806 
807 	//
808 	// Hit
809 	//
810 	if(Header.Version <= GS_LX56_VERSION) {
811 		fread_compat(proj->Hit.Type,sizeof(int),1,fp);
812 		EndianSwap(proj->Hit.Type);
813 
814 		// Hit::Explode
815 		if(proj->Hit.Type == PJ_EXPLODE) {
816 			fread_compat(proj->Hit.Damage,		sizeof(int),1,fp);
817 			EndianSwap(proj->Hit.Damage);
818 			fread_endian<int>(fp, proj->Hit.Projectiles);
819 			fread_endian<int>(fp, proj->Hit.UseSound);
820 			fread_compat(proj->Hit.Shake,			sizeof(int),1,fp);
821 			EndianSwap(proj->Hit.Shake);
822 
823 			if(proj->Hit.UseSound) {
824 				proj->Hit.SndFilename = readString(fp);
825 			}
826 		}
827 
828 		// Hit::Bounce
829 		if(proj->Hit.Type == PJ_BOUNCE) {
830 			fread_compat(proj->Hit.BounceCoeff,	sizeof(float), 1, fp);
831 			EndianSwap(proj->Hit.BounceCoeff);
832 			fread_compat(proj->Hit.BounceExplode, sizeof(int),   1, fp);
833 			EndianSwap(proj->Hit.BounceExplode);
834 		}
835 
836 		// Hit::Carve
837 		if(proj->Hit.Type == PJ_CARVE) {
838 			fread_compat(proj->Hit.Damage,		sizeof(int),1,fp);
839 			EndianSwap(proj->Hit.Damage);
840 		}
841 
842 		if(!bDedicated && proj->Hit.UseSound) {
843 			// Load the sample
844 			proj->Hit.Sound = LoadGSSample(sDirectory,proj->Hit.SndFilename);
845 
846 			if(proj->Hit.Sound == NULL) {
847 				proj->Hit.UseSound = false;
848 				modLog("Could not open sound '" + proj->Hit.SndFilename + "'");
849 			}
850 		}
851 	}
852 	else { // newer GS version
853 		proj->Hit.read(this, fp);
854 	}
855 
856 
857 
858 
859 	//
860 	// Timer
861 	//
862 	if(proj->Timer.Time > 0) {
863 		if(Header.Version <= GS_LX56_VERSION) {
864 			fread_compat(proj->Timer.Type,sizeof(int),1,fp);
865 			EndianSwap(proj->Timer.Type);
866 			if(proj->Timer.Type == PJ_EXPLODE) {
867 				fread_compat(proj->Timer.Damage,sizeof(int),1,fp);
868 				EndianSwap(proj->Timer.Damage);
869 				fread_endian<int>(fp, proj->Timer.Projectiles);
870 				fread_compat(proj->Timer.Shake,sizeof(int),1,fp);
871 				EndianSwap(proj->Timer.Shake);
872 			}
873 		}
874 		else {
875 			proj->Timer.read(this, fp);
876 		}
877 	}
878 
879 
880 
881 	//
882 	// Player hit
883 	//
884 	if(Header.Version <= GS_LX56_VERSION) {
885 		fread_compat(proj->PlyHit.Type,sizeof(int),1,fp);
886 		EndianSwap(proj->PlyHit.Type);
887 
888 		// PlyHit::Explode || PlyHit::Injure
889 		if(proj->PlyHit.Type == PJ_INJURE || proj->PlyHit.Type == PJ_EXPLODE) {
890 			fread_endian<int>(fp, proj->PlyHit.Damage);
891 			fread_endian<int>(fp, proj->PlyHit.Projectiles);
892 		}
893 
894 		// PlyHit::Bounce
895 		if(proj->PlyHit.Type == PJ_BOUNCE) {
896 			fread_compat(proj->PlyHit.BounceCoeff, sizeof(float), 1, fp);
897 			EndianSwap(proj->PlyHit.BounceCoeff);
898 		}
899 	}
900 	else {
901 		proj->PlyHit.read(this, fp);
902 	}
903 
904 	if(Header.Version <= GS_LX56_VERSION) {
905 		// NOTE: this is obsolete, it is used nowhere
906 
907 		//
908 		// Explode
909 		//
910 		fread_compat(proj->Exp.Type,     sizeof(int), 1, fp);
911 		EndianSwap(proj->Exp.Type);
912 		fread_compat(proj->Exp.Damage,   sizeof(int), 1, fp);
913 		EndianSwap(proj->Exp.Damage);
914 		fread_endian<int>(fp, proj->Exp.Projectiles);
915 		fread_endian<int>(fp, proj->Exp.UseSound);
916 		if(proj->Exp.UseSound) {
917 			proj->Exp.SndFilename = readString(fp);
918 		}
919 
920 		//
921 		// Touch
922 		//
923 		fread_compat(proj->Tch.Type,     sizeof(int), 1, fp);
924 		EndianSwap(proj->Tch.Type);
925 		fread_compat(proj->Tch.Damage,   sizeof(int), 1, fp);
926 		EndianSwap(proj->Tch.Damage);
927 		fread_endian<int>(fp, proj->Tch.Projectiles);
928 		fread_endian<int>(fp, proj->Tch.UseSound);
929 		if(proj->Tch.UseSound) {
930 			proj->Tch.SndFilename = readString(fp);
931 		}
932 	}
933 
934 	if(Header.Version > GS_LX56_VERSION) {
935 		Uint32 projHitC = 0;
936 		fread_endian<Uint32>(fp, projHitC);
937 		proj->actions.resize(projHitC);
938 		for(Uint32 i = 0; i < projHitC; ++i) {
939 			proj->actions[i].read(this, fp);
940 		}
941 	}
942 
943 	if(Header.Version > GS_LX56_VERSION) {
944 		proj->GeneralSpawnInfo.read(this, fp);
945 	}
946 	else if(proj->Timer.Projectiles || proj->Hit.Projectiles || proj->PlyHit.Projectiles || proj->Exp.Projectiles ||
947        proj->Tch.Projectiles) {
948 		fread_endian<int>(fp, proj->GeneralSpawnInfo.Useangle);
949 		fread_compat(proj->GeneralSpawnInfo.Angle,		sizeof(int),	1, fp);
950 		EndianSwap(proj->GeneralSpawnInfo.Angle);
951 		fread_compat(proj->GeneralSpawnInfo.Amount,	sizeof(int),	1, fp);
952 		EndianSwap(proj->GeneralSpawnInfo.Amount);
953 		fread_compat(proj->GeneralSpawnInfo.Spread,	sizeof(float),	1, fp);
954 		EndianSwap(proj->GeneralSpawnInfo.Spread);
955 		fread_compat(proj->GeneralSpawnInfo.Speed,		sizeof(int),	1, fp);
956 		EndianSwap(proj->GeneralSpawnInfo.Speed);
957 		fread_compat(proj->GeneralSpawnInfo.SpeedVar,	sizeof(float),	1, fp);
958 		EndianSwap(proj->GeneralSpawnInfo.SpeedVar);
959 
960 		proj->GeneralSpawnInfo.Proj = LoadProjectile(fp);
961 	}
962 
963 
964 	// Projectile trail
965 	if(proj->Trail.Type == TRL_PROJECTILE) {
966 
967 		fread_endian<int>(fp, proj->Trail.Proj.UseParentVelocityForSpread);
968 		fread_endian<float>(fp, proj->Trail.Delay);
969 		// Change from milli-seconds to seconds
970 		proj->Trail.Delay /= 1000.0f;
971 
972 		if(Header.Version <= GS_LX56_VERSION) {
973 			fread_compat(proj->Trail.Proj.Amount,			sizeof(int),	1, fp);
974 			EndianSwap(proj->Trail.Proj.Amount);
975 			fread_compat(proj->Trail.Proj.Speed,			sizeof(int),	1, fp);
976 			EndianSwap(proj->Trail.Proj.Speed);
977 			fread_compat(proj->Trail.Proj.SpeedVar,		sizeof(float),	1, fp);
978 			EndianSwap(proj->Trail.Proj.SpeedVar);
979 			fread_compat(proj->Trail.Proj.Spread,			sizeof(float),	1, fp);
980 			EndianSwap(proj->Trail.Proj.Spread);
981 
982 			proj->Trail.Proj.Proj = LoadProjectile(fp);
983 		}
984 		else { // new GS versions
985 			proj->Trail.Proj.read(this, fp);
986 		}
987 
988 	}
989 
990 	return proj;
991 }
992 
993 ///////////////////
994 // Load an image
LoadGSImage(const std::string & dir,const std::string & filename)995 SDL_Surface * CGameScript::LoadGSImage(const std::string& dir, const std::string& filename)
996 {
997 	if(bDedicated) return NULL;
998 
999 	SmartPointer<SDL_Surface> img = NULL;
1000 
1001 	// First, check the gfx directory in the mod dir
1002 	img = LoadGameImage(dir + "/gfx/" + filename, true);
1003 	if(img.get())  {
1004 		SetColorKey(img.get());
1005 		CachedImages.push_back(img);
1006 		return img.get();
1007 	}
1008 
1009 	// Check the gfx directory in the data dir
1010 	img = LoadGameImage("data/gfx/" + filename, true);
1011 	if(img.get())
1012 	{
1013 		SetColorKey(img.get());
1014 		CachedImages.push_back(img);
1015 	}
1016 	return img.get();
1017 }
1018 
1019 ///////////////////
1020 // Load a sample
LoadGSSample(const std::string & dir,const std::string & filename)1021 SoundSample * CGameScript::LoadGSSample(const std::string& dir, const std::string& filename)
1022 {
1023 	if(bDedicated) return NULL;
1024 
1025 	SmartPointer<SoundSample> smp = NULL;
1026 
1027 	// First, check the sfx directory in the mod dir
1028 	smp = LoadSample(dir + "/sfx/" + filename, 10);
1029 
1030 	if(smp.get())
1031 	{
1032 		CachedSamples.push_back(smp);
1033 		return smp.get();
1034 	}
1035 
1036 	// Check the sounds directory in the data dir
1037 	smp = LoadSample("data/sounds/" + filename, 10);
1038 	if(smp.get())
1039 		CachedSamples.push_back(smp);
1040 	return smp.get();
1041 }
1042 
1043 
1044 
1045 ///////////////////
1046 // Find a weapon based on its name
FindWeapon(const std::string & name)1047 const weapon_t *CGameScript::FindWeapon(const std::string& name)
1048 {
1049 	if(!Weapons) return NULL;
1050 	if(name == "") return NULL;
1051 
1052 	// Go through each weapon
1053 	weapon_t *wpn = Weapons;
1054 	for(int n=0;n<NumWeapons;n++,wpn++) {
1055 
1056 		if(stringcaseequal(wpn->Name, name))
1057 			return wpn;
1058 	}
1059 
1060 	return NULL;
1061 }
1062 
1063 
1064 ///////////////////
1065 // Returns true if the weapon is in the game script
weaponExists(const std::string & szName)1066 bool CGameScript::weaponExists(const std::string& szName)
1067 {
1068     // Go through each weapon
1069 	weapon_t *wpn = Weapons;
1070 	for(int n=0;n<NumWeapons;n++,wpn++) {
1071 
1072 		if(stringcasecmp(wpn->Name, szName) == 0)
1073 			return true;
1074 	}
1075 
1076     // Not found
1077     return false;
1078 }
1079 
1080 
1081 
1082 // Helper function
GetProjSize(proj_t * prj)1083 static size_t GetProjSize(proj_t *prj)
1084 {
1085 	if (prj)
1086 		return 	prj->Exp.SndFilename.size() + prj->filename.size() +
1087 				prj->Hit.SndFilename.size() + prj->ImgFilename.size() +
1088 				prj->Tch.SndFilename.size() +
1089 				/*GetSurfaceMemorySize(prj->bmpImage.get()) + */
1090 				sizeof(proj_t);
1091 	else
1092 		return 0;
1093 }
1094 
1095 //////////////////
1096 // Returns the memory occupied by this gamescript
GetMemorySize()1097 size_t CGameScript::GetMemorySize()
1098 {
1099 	size_t res = sizeof(CGameScript) + sDirectory.size();
1100 	weapon_t *it = Weapons;
1101 	for (int i = 0; i < NumWeapons; i++, it++)  {
1102 		res += sizeof(weapon_t) + sizeof(SoundSample);
1103 		res += it->SndFilename.size();
1104 	}
1105 	for(Projectiles::iterator i = projectiles.begin(); i != projectiles.end(); ++i) {
1106 		res += GetProjSize(i->second);
1107 	}
1108 
1109 	return res;
1110 }
1111 
1112 
1113 ///////////////////
1114 // Shutdown the game script
Shutdown()1115 void CGameScript::Shutdown()
1116 {
1117 	loaded = false;
1118 	needCollisionInfo = false;
1119 
1120     // Close the log file
1121     if(pModLog) {
1122         fclose(pModLog);
1123         pModLog = NULL;
1124     }
1125 
1126 	for(Projectiles::iterator i = projectiles.begin(); i != projectiles.end(); ++i) {
1127 		ShutdownProjectile(i->second);
1128 	}
1129 	projectiles.clear();
1130 	savedProjs.clear();
1131 	projFileIndexes.clear();
1132 
1133 	if(Weapons)
1134 		delete[] Weapons;
1135 	Weapons = NULL;
1136 }
1137 
1138 
1139 ///////////////////
1140 // Shutdown a projectile
ShutdownProjectile(proj_t * prj)1141 void CGameScript::ShutdownProjectile(proj_t *prj)
1142 {
1143 	if(prj) {
1144 		delete prj;
1145 	}
1146 }
1147 
1148 
1149 ///////////////////
1150 // Check if a file is a valid liero game script
CheckFile(const std::string & dir,std::string & name,bool abs_filename)1151 bool CGameScript::CheckFile(const std::string& dir, std::string& name, bool abs_filename)
1152 {
1153 	name = "";
1154 
1155 	// Open it
1156 	FILE *fp = NULL;
1157 	if(abs_filename) {
1158 		std::string filename;
1159 		// we still need to add "/script.lgs" and then do an exact filename search
1160 		if(GetExactFileName(dir + "/script.lgs", filename))
1161 	 		fp = fopen(Utf8ToSystemNative(filename).c_str(), "rb");
1162 	} else
1163 		fp = OpenGameFile(dir + "/script.lgs", "rb");
1164 
1165 	if(fp == NULL) {
1166 		// try source gamescript
1167 
1168 		std::string filename = dir + "/main.txt";
1169 		if(abs_filename) {
1170 			if(!GetExactFileName(dir + "/main.txt", filename))
1171 				return false;
1172 		} else {
1173 			if(!IsFileAvailable(filename))
1174 				return false;
1175 		}
1176 
1177 		ReadString(filename,"General","ModName", name,"untitled", abs_filename);
1178 		return true;
1179 	}
1180 
1181 	// Header
1182 	gs_header_t head;
1183 	memset(&head,0,sizeof(gs_header_t));
1184 	fread_compat(head,sizeof(gs_header_t),1,fp);
1185 	fclose(fp);
1186 
1187 	EndianSwap(head.Version);
1188 	// for security
1189 	fix_markend(head.ID);
1190 	fix_markend(head.ModName);
1191 
1192 	// Check ID
1193 	if(strcmp(head.ID,"Liero Game Script") != 0) {
1194 		warnings << "GS:CheckFile: WARNING: " << dir << "/script.lgs is not a Liero game script";
1195 		warnings << " (but \"" << head.ID << "\" instead)" << endl;
1196 		return false;
1197 	}
1198 
1199 	// Check version
1200 	if(head.Version < GS_FIRST_SUPPORTED_VERSION || head.Version > GS_VERSION) {
1201 		warnings << "GS:CheckFile: WARNING: " << dir << "/script.lgs has the wrong version";
1202 		warnings << " (" << (unsigned)head.Version << ", required is in the range ";
1203 		warnings << "[" << GS_FIRST_SUPPORTED_VERSION << "," << GS_VERSION << "])" << endl;
1204 		return false;
1205 	}
1206 
1207 	name = head.ModName;
1208 	return true;
1209 }
1210 
1211 
1212 ///////////////////
1213 // Return an error message based on code
getError(int code)1214 std::string CGameScript::getError(int code)
1215 {
1216 	std::string text = "Undefined error";
1217 
1218 	switch(code) {
1219 
1220 		case GSE_MEM:
1221 			text = "Out of memory";
1222 			break;
1223 
1224 		case GSE_VERSION:
1225 			text = "Incorrect version";
1226 			break;
1227 
1228 		case GSE_FILE:
1229 			text = "Could not open file";
1230 			break;
1231 
1232 		case GSE_BAD:
1233 			text = "Bad file format";
1234 			break;
1235 	}
1236 
1237 	return text;
1238 }
1239 
1240 
1241 ///////////////////
1242 // Write info to a mod log file
modLog(const std::string & text)1243 void CGameScript::modLog(const std::string& text)
1244 {
1245 	notes << "modLog: " << text << endl;
1246 
1247 	if(!pModLog) {
1248 		pModLog = OpenGameFile("modlog.txt","wt");
1249 		if(!pModLog)
1250 			return;
1251 		fprintf(pModLog,"Log file for mod:\n%s\n--------------------------------\n",Header.ModName);
1252 	}
1253 
1254 	if (text.size() != 0)
1255 		fprintf(pModLog,"%s\n", text.c_str());
1256 }
1257 
1258 // Not needed with new caching system - game script won't ever change it's state during game
1259 /*
1260 ///////////////////
1261 // Copies infor from anothe gamescript
1262 void CGameScript::CopyFrom(CGameScript *cg)
1263 {
1264 	sDirectory = cg->sDirectory;
1265 	Header = cg->Header;
1266 	NumWeapons = cg->NumWeapons;
1267 
1268 	// HINT: only a pointer is copied here, because the weapon info does not change
1269 	// so it would be wasting of memory if we copied the whole list
1270 	Weapons = cg->Weapons;
1271 
1272 	Worm = cg->Worm;
1273 	RopeLength = cg->RopeLength;
1274 	RestLength = cg->RestLength;
1275 	Strength = cg->Strength;
1276 }
1277 */
1278 
SmartPointer_ObjectDeinit(CGameScript * obj)1279 template <> void SmartPointer_ObjectDeinit<CGameScript> ( CGameScript * obj )
1280 {
1281 	delete obj;
1282 }
1283 
1284 
1285 
1286 
1287 
1288 
1289 static IniReader::KeywordList compilerKeywords;
1290 
InitDefaultCompilerKeywords()1291 void CGameScript::InitDefaultCompilerKeywords()
1292 {
1293 	if (compilerKeywords.empty())  {
1294 		// Add some keywords
1295 		// IMPORTANT: Every keyword (key) need to be different.
1296 		compilerKeywords["WPN_PROJECTILE"] = WPN_PROJECTILE;
1297 		compilerKeywords["WPN_SPECIAL"] = WPN_SPECIAL;
1298 		compilerKeywords["WPN_BEAM"] = WPN_BEAM;
1299 
1300 		compilerKeywords["WCL_AUTOMATIC"] = WCL_AUTOMATIC;
1301 		compilerKeywords["WCL_POWERGUN"] = WCL_POWERGUN;
1302 		compilerKeywords["WCL_GRENADE"] = WCL_GRENADE;
1303 		compilerKeywords["WCL_MISSILE"] = WCL_MISSILE;
1304 		compilerKeywords["WCL_CLOSERANGE"] = WCL_CLOSERANGE;
1305 
1306 		compilerKeywords["PRJ_PIXEL"] = PRJ_PIXEL;
1307 		compilerKeywords["PRJ_IMAGE"] = PRJ_IMAGE;
1308 		compilerKeywords["PRJ_CIRCLE"] = PRJ_CIRCLE;
1309 		compilerKeywords["PRJ_POLYGON"] = PRJ_POLYGON;
1310 		compilerKeywords["PRJ_RECT"] =  PRJ_RECT;
1311 
1312 		// action types
1313 		compilerKeywords["Bounce"] = PJ_BOUNCE;
1314 		compilerKeywords["Explode"] = PJ_EXPLODE;
1315 		compilerKeywords["Injure"] = PJ_INJURE;
1316 		compilerKeywords["InjureProj"] = PJ_INJUREPROJ;
1317 		compilerKeywords["Carve"] = PJ_CARVE;
1318 		compilerKeywords["Dirt"] = PJ_DIRT;
1319 		compilerKeywords["GreenDirt"] = PJ_GREENDIRT;
1320 		compilerKeywords["Disappear"] = PJ_DISAPPEAR;
1321 		compilerKeywords["Nothing"] = PJ_NOTHING;
1322 		compilerKeywords["Disappear2"] =  PJ_DISAPPEAR2;
1323 		compilerKeywords["GoThrough"] =  PJ_GOTHROUGH;
1324 		compilerKeywords["PlaySound"] =  PJ_PLAYSOUND;
1325 		compilerKeywords["InjureWorm"] =  PJ_INJUREWORM;
1326 		compilerKeywords["ChangeRadius"] = PJ_ChangeRadius;
1327 		compilerKeywords["OverwriteOwnSpeed"] =  PJ_OverwriteOwnSpeed;
1328 		compilerKeywords["MultiplyOwnSpeed"] =  PJ_MultiplyOwnSpeed;
1329 		compilerKeywords["DiffOwnSpeed"] =  PJ_DiffOwnSpeed;
1330 		compilerKeywords["OverwriteTargetSpeed"] =  PJ_OverwriteTargetSpeed;
1331 		compilerKeywords["MultiplyTargetSpeed"] =  PJ_MultiplyTargetSpeed;
1332 		compilerKeywords["DiffTargetSpeed"] =  PJ_DiffTargetSpeed;
1333 		compilerKeywords["HeadingToNextWorm"] =  PJ_HeadingToNextWorm;
1334 		compilerKeywords["HeadingToOwner"] =  PJ_HeadingToOwner;
1335 		compilerKeywords["HeadingToNextOtherWorm"] =  PJ_HeadingToNextOtherWorm;
1336 		compilerKeywords["HeadingToNextEnemyWorm"] =  PJ_HeadingToNextEnemyWorm;
1337 		compilerKeywords["HeadingToNextTeamMate"] =  PJ_HeadingToNextTeamMate;
1338 		compilerKeywords["HeadTargetToUs"] =  PJ_HeadTargetToUs;
1339 
1340 		// event types
1341 		compilerKeywords["Timer"] =  Proj_Event::PET_TIMER;
1342 		compilerKeywords["ProjHit"] =  Proj_Event::PET_PROJHIT;
1343 		compilerKeywords["WormHit"] =  Proj_Event::PET_WORMHIT;
1344 		compilerKeywords["TerrainHit"] =  Proj_Event::PET_TERRAINHIT;
1345 		compilerKeywords["Death"] =  Proj_Event::PET_DEATH;
1346 		compilerKeywords["Fallback"] =  Proj_Event::PET_FALLBACK;
1347 
1348 		// trail types
1349 		compilerKeywords["TRL_NONE"] = TRL_NONE;
1350 		compilerKeywords["TRL_SMOKE"] = TRL_SMOKE;
1351 		compilerKeywords["TRL_CHEMSMOKE"] = TRL_CHEMSMOKE;
1352 		compilerKeywords["TRL_PROJECTILE"] = TRL_PROJECTILE;
1353 		compilerKeywords["TRL_DOOMSDAY"] = TRL_DOOMSDAY;
1354 		compilerKeywords["TRL_EXPLOSIVE"] = TRL_EXPLOSIVE;
1355 
1356 		compilerKeywords["SPC_JETPACK"] = SPC_JETPACK;
1357 
1358 		compilerKeywords["ANI_ONCE"] = ANI_ONCE;
1359 		compilerKeywords["ANI_LOOP"] = ANI_LOOP;
1360 		compilerKeywords["ANI_PINGPONG"] = ANI_PINGPONG;
1361 
1362 		compilerKeywords["ATO_NONE"] = ATO_NONE;
1363 		compilerKeywords["ATO_PLAYERS"] = ATO_PLAYERS;
1364 		compilerKeywords["ATO_PROJECTILES"] = ATO_PROJECTILES;
1365 		compilerKeywords["ATO_ROPE"] = ATO_ROPE;
1366 		compilerKeywords["ATO_BONUSES"] = ATO_BONUSES;
1367 		compilerKeywords["ATO_ALL"] = ATO_ALL;
1368 
1369 		compilerKeywords["ATC_NONE"] = ATC_NONE;
1370 		compilerKeywords["ATC_OWNER"] = ATC_OWNER;
1371 		compilerKeywords["ATC_ENEMY"] = ATC_ENEMY;
1372 		compilerKeywords["ATC_TEAMMATE"] = ATC_TEAMMATE;
1373 		compilerKeywords["ATC_ALL"] = ATC_ALL;
1374 
1375 		compilerKeywords["ATT_GRAVITY"] = ATT_GRAVITY;
1376 		compilerKeywords["ATT_CONSTANT"] = ATT_CONSTANT;
1377 		compilerKeywords["ATT_LINEAR"] = ATT_LINEAR;
1378 		compilerKeywords["ATT_QUADRATIC"] = ATT_QUADRATIC;
1379 
1380 		compilerKeywords["true"] = true;
1381 		compilerKeywords["false"] = false;
1382 	}
1383 }
1384 
1385 ///////////////////
1386 // Compile
Compile(const std::string & dir)1387 bool CGameScript::Compile(const std::string& dir)
1388 {
1389 	Shutdown();
1390 
1391 	CGameScript* Game = this;
1392 
1393 	InitDefaultCompilerKeywords();
1394 	IniReader ini(dir + "/Main.txt", compilerKeywords);
1395 
1396 	if (!ini.Parse())  {
1397 		errors << "Error while parsing the gamescript " << dir << endl;
1398 		return false;
1399 	}
1400 
1401 
1402 
1403 	sDirectory = dir;
1404 
1405 	int num,n;
1406 
1407 	std::string modname;
1408 	ini.ReadString("General","ModName", modname,"untitled");
1409 	fix_strncpy(Header.ModName, modname.c_str());
1410 
1411 	notes << "Compiling '" << modName() << "'" << endl;
1412 
1413 	ini.ReadInteger("Weapons","NumWeapons",&num,0);
1414 
1415 
1416 	// Weapons
1417 	Game->initNewWeapons(num);
1418 
1419 
1420 	// Compile the weapons
1421 	for(n=0;n<Game->GetNumWeapons();n++) {
1422 		std::string wpn = "Weapon" + itoa(n+1);
1423 
1424 		std::string weap;
1425 		ini.ReadString("Weapons", wpn, weap, "");
1426 
1427 		if(!CompileWeapon(dir, weap, n))
1428 			return false;
1429 	}
1430 
1431 	// Compile the extra stuff
1432 	CompileExtra(ini);
1433 
1434 	loaded = true;
1435 
1436 	return true;
1437 }
1438 
1439 
1440 ///////////////////
1441 // Compile a weapon
CompileWeapon(const std::string & dir,const std::string & weapon,int id)1442 bool CGameScript::CompileWeapon(const std::string& dir, const std::string& weapon, int id)
1443 {
1444 	CGameScript* Game = this;
1445 
1446 	weapon_t *Weap = Game->Weapons+id;
1447 	IniReader ini(dir + "/" + weapon, compilerKeywords);
1448 	if (!ini.Parse())  {
1449 		errors << "Error while parsing weapon file " << weapon << endl;
1450 		return false;
1451 	}
1452 
1453 	Weap->ID = id;
1454 	Weap->Proj.Proj = NULL;
1455 	Weap->UseSound = false;
1456 	Weap->Special = SPC_NONE;
1457 	Weap->Type = WPN_PROJECTILE;
1458 	Weap->Proj.Proj = NULL;
1459 	Weap->LaserSight = false;
1460 
1461 	ini.ReadString("General", "Name", Weap->Name, "");
1462 	notes << "  Compiling Weapon '" << Weap->Name << "'" << endl;
1463 
1464 	ini.ReadKeyword("General", "Type", (int *)&Weap->Type, WPN_PROJECTILE);
1465 
1466 
1467 
1468 	// Special Weapons
1469 	if(Weap->Type == WPN_SPECIAL) {
1470 
1471 		ini.ReadKeyword("General", "Special", (int*)&Weap->Special, SPC_NONE);
1472 
1473 		// If it is a special weapon, read the values depending on the special weapon
1474 		// We don't bother with the 'normal' values
1475 		switch(Weap->Special) {
1476 			// Jetpack
1477 			case SPC_JETPACK:
1478 				CompileJetpack(ini, Weap);
1479 				break;
1480 
1481 			default:
1482 				notes << "   Error: Unknown special type" << endl;
1483 		}
1484 		return true;
1485 	}
1486 
1487 
1488 	// Beam Weapons
1489 	if(Weap->Type == WPN_BEAM) {
1490 
1491 		CompileBeam(ini, Weap);
1492 		return true;
1493 	}
1494 
1495 
1496 	// Projectile Weapons
1497 	ini.ReadKeyword("General","Class",(int*)&Weap->Class,WCL_AUTOMATIC);
1498 	ini.ReadInteger("General","Recoil",&Weap->Recoil,0);
1499 	ini.ReadFloat("General","Recharge",&Weap->Recharge,0); Weap->Recharge /= 10.0f;
1500 	ini.ReadFloat("General","Drain",&Weap->Drain,0);
1501 	ini.ReadFloat("General","ROF",&Weap->ROF,0); Weap->ROF /= 1000.0f;
1502 	ini.ReadKeyword("General", "LaserSight", &Weap->LaserSight, false);
1503 	if(ini.ReadString("General","Sound",Weap->SndFilename,"")) {
1504 		Weap->UseSound = true;
1505 
1506 		if(!bDedicated) {
1507 			// Load the sample
1508 			Weap->smpSample = LoadGSSample(dir,Weap->SndFilename);
1509 		}
1510 	}
1511 
1512 	Weap->Proj.readFromIni(this, dir, ini, "Projectile");
1513 
1514 	if(Weap->Proj.UseParentVelocityForSpread) {
1515 		warnings << "UseProjVelocity is set in Projectile-section (" << weapon << "); this was not supported in LX56 thus we ignore it" << endl;
1516 		Weap->Proj.UseParentVelocityForSpread = false;
1517 	}
1518 
1519 	if(!Weap->Proj.Useangle) {
1520 		warnings << "Useangle is set in Projectile-section (" << weapon << "); this was not supported in LX56 thus we ignore it" << endl;
1521 		Weap->Proj.Useangle = true;
1522 	}
1523 
1524 	if(Weap->Proj.Angle != 0) {
1525 		warnings << "Angle is set in Projectile-section (" << weapon << "); this was not supported in LX56 thus we ignore it" << endl;
1526 		Weap->Proj.Angle = 0;
1527 	}
1528 
1529 	if(Weap->Proj.Proj == NULL) {
1530 		warnings << "projectile not set for weapon " << weapon << endl;
1531 		return false;
1532 	}
1533 
1534 	Weap->FinalProj.readFromIni(this, dir, ini, "FinalProj");
1535 
1536 	return true;
1537 }
1538 
1539 
1540 ///////////////////
1541 // Compile a beam weapon
CompileBeam(const IniReader & ini,weapon_t * Weap)1542 void CGameScript::CompileBeam(const IniReader& ini, weapon_t *Weap)
1543 {
1544 	ini.ReadInteger("General","Recoil",&Weap->Recoil,0);
1545 	ini.ReadFloat("General","Recharge",&Weap->Recharge,0); Weap->Recharge /= 10.0f;
1546 	ini.ReadFloat("General","Drain",&Weap->Drain,0);
1547 	ini.ReadFloat("General","ROF",&Weap->ROF,0); Weap->ROF /= 1000.0f;
1548 	if(ini.ReadString("General","Sound",Weap->SndFilename,""))
1549 		Weap->UseSound = true;
1550 
1551 	Weap->Bm.readFromIni(ini, "Beam");
1552 }
1553 
1554 
1555 ///////////////////
1556 // Compile a projectile
CompileProjectile(const std::string & dir,const std::string & pfile)1557 proj_t *CGameScript::CompileProjectile(const std::string& dir, const std::string& pfile)
1558 {
1559 	ProjFileMap::iterator f = projFileIndexes.find(pfile);
1560 	if(f != projFileIndexes.end()) {
1561 		notes << "    Reuse already compiled Projectile '" << pfile << "'" << endl;
1562 		return projectiles[f->second];
1563 	}
1564 
1565 	proj_t *proj = new proj_t;
1566 	if(proj == NULL)
1567 		return NULL;
1568 
1569 	int projIndex = (int)projectiles.size();
1570 	projectiles[projIndex] = proj;
1571 	projFileIndexes[pfile] = projIndex;
1572 
1573 	// Load the projectile
1574 	IniReader ini(dir + "/" + pfile, compilerKeywords);
1575 	notes << "    Compiling Projectile '" << pfile << "'" << endl;
1576 
1577 	proj->filename = pfile;
1578 
1579 	proj->Timer.Projectiles = false;
1580 	proj->Hit.Projectiles = false;
1581 	proj->PlyHit.Projectiles = false;
1582 	proj->Exp.Projectiles = false;
1583 	proj->Tch.Projectiles = false;
1584 	proj->GeneralSpawnInfo.Proj = NULL;
1585 	proj->Trail.Proj.Proj = NULL;
1586 	proj->Animating = false;
1587 	proj->UseCustomGravity = false;
1588 
1589 	proj->AttractiveForce = 0;
1590 	proj->AttractiveForceType = ATT_GRAVITY;
1591 	proj->AttractiveForceObjects = ATO_NONE;
1592 	proj->AttractiveForceClasses = 0;
1593 	proj->AttractiveForceRadius = 0;
1594 	proj->AttractiveForceThroughWalls = true;
1595 
1596 	if (!ini.Parse())  {
1597 		warnings << "projectile file " << pfile << " could not be parsed, using defaults..." << endl;
1598 		return proj;
1599 	}
1600 
1601 	ini.ReadKeyword("General","Type",(int*)&proj->Type,PRJ_PIXEL);
1602 	ini.ReadFloat("General","Timer",&proj->Timer.Time,0);
1603 	ini.ReadFloat("General", "TimerVar", &proj->Timer.TimeVar, 0);
1604 	ini.ReadKeyword("General","Trail",(int*)&proj->Trail.Type,TRL_NONE);
1605 
1606 	ini.ReadInteger("General","AttractiveForce", &proj->AttractiveForce, 0);
1607 	ini.ReadInteger("General","AttractiveForceRadius", &proj->AttractiveForceRadius, 0);
1608 	ini.ReadKeyword("General","AttractiveForceType",(int*)&proj->AttractiveForceType,ATT_GRAVITY);
1609 	ini.ReadKeywordList("General","AttractiveForceObjects",&proj->AttractiveForceObjects,0);
1610 	ini.ReadKeywordList("General","AttractiveForceClasses",&proj->AttractiveForceClasses,0);
1611 	ini.ReadKeyword("General","AttractiveForceThroughWalls",&proj->AttractiveForceThroughWalls,true);
1612 
1613 	if (ini.ReadInteger("General","Gravity",&proj->Gravity, 0))
1614 		proj->UseCustomGravity = true;
1615 
1616 	ini.ReadFloat("General","Dampening",&proj->Dampening,1.0f);
1617 
1618 	if(proj->Type == PRJ_PIXEL)
1619 		proj->Width = proj->Height = 2;
1620 	else
1621 		proj->Width = proj->Height = 4;
1622 
1623 	ini.ReadInteger("General", "Width", &proj->Width, proj->Width);
1624 	ini.ReadInteger("General", "Height", &proj->Height, proj->Height);
1625 
1626 	proj->Colour.clear();
1627 	proj->polygon.clear();
1628 	switch(proj->Type) {
1629 		case PRJ_POLYGON:
1630 			proj->polygon.startPointAdding();
1631 			for(size_t i = 0; ; ++i) {
1632 				VectorD2<int> p;
1633 				if(ini.ReadVectorD2("General", "P" + itoa((unsigned)i+1), p) ) {
1634 					proj->polygon.addPoint(p);
1635 				} else
1636 					break;
1637 			}
1638 			proj->polygon.endPointAdding();
1639 
1640 			if(proj->polygon.getPoints().empty()) {
1641 				warnings << "no points specified for PRJ_POLYGON projectile " << pfile << "; fallback to PRJ_PIXEL" << endl;
1642 				proj->Type = PRJ_PIXEL;
1643 			}
1644 
1645 			// fallthrough to read color
1646 		case PRJ_RECT:
1647 		case PRJ_CIRCLE:
1648 		case PRJ_PIXEL:
1649 			for(size_t i = 0; ; ++i) {
1650 				Color col;
1651 				if(ini.ReadColour("General","Colour" + itoa((unsigned)i+1), col, Color()) ) {
1652 					proj->Colour.push_back(col);
1653 				} else
1654 					break;
1655 			}
1656 			if(proj->Colour.size() == 0) {
1657 				warnings << "no colors specified for projectile " << pfile << ", using black" << endl;
1658 				proj->Colour.push_back(Color());
1659 			}
1660 			break;
1661 
1662 		case PRJ_IMAGE:
1663 			ini.ReadString("General","Image",proj->ImgFilename,"");
1664 			ini.ReadKeyword("General","Rotating",&proj->Rotating,false);
1665 			ini.ReadInteger("General","RotIncrement",&proj->RotIncrement,0);
1666 			ini.ReadInteger("General","RotSpeed",&proj->RotSpeed,0);
1667 			ini.ReadKeyword("General","UseAngle",&proj->UseAngle,0);
1668 			ini.ReadKeyword("General","UseSpecAngle",&proj->UseSpecAngle,0);
1669 			if(proj->UseAngle || proj->UseSpecAngle)
1670 				ini.ReadInteger("General","AngleImages",&proj->AngleImages,0);
1671 
1672 			ini.ReadKeyword("General","Animating",&proj->Animating,0);
1673 			if(proj->Animating) {
1674 				ini.ReadFloat("General","AnimRate",&proj->AnimRate,0);
1675 				ini.ReadKeyword("General","AnimType",(int*)&proj->AnimType,ANI_ONCE);
1676 			}
1677 
1678 			if(!bDedicated) {
1679 				proj->bmpImage = LoadGSImage(dir, proj->ImgFilename);
1680 				if(!proj->bmpImage)
1681 					modLog("Could not open image '" + proj->ImgFilename + "'");
1682 				else
1683 					proj->bmpShadow = GenerateShadowSurface(proj->bmpImage);
1684 			}
1685 			break;
1686 
1687 		case __PRJ_LBOUND: case __PRJ_UBOUND: errors << "PRJ BOUND err" << endl;
1688 	}
1689 
1690 	// general Projectile spawn info
1691 	{
1692 		proj->GeneralSpawnInfo.readFromIni(this, dir, ini, "Projectile");
1693 
1694 		if(proj->GeneralSpawnInfo.UseParentVelocityForSpread) {
1695 			warnings << "UseProjVelocity is set in Projectile-section (" << pfile << "); this was not supported in LX56 thus we ignore it" << endl;
1696 			proj->GeneralSpawnInfo.UseParentVelocityForSpread = false;
1697 		}
1698 	}
1699 
1700 	proj->Hit.readFromIni(this, dir, ini, "Hit");
1701 
1702 	if(proj->Hit.needGeneralSpawnInfo() && !proj->GeneralSpawnInfo.isSet()) {
1703 		warnings << dir << "/" << pfile << ": Hit section wants to spawn projectiles but there is no spawning information" << endl;
1704 		proj->Hit.Projectiles = false;
1705 	}
1706 
1707 	// Timer
1708 	if(proj->Timer.Time > 0) {
1709 		proj->Timer.readFromIni(this, dir, ini, "Time");
1710 
1711 		if(proj->Timer.needGeneralSpawnInfo() && !proj->GeneralSpawnInfo.isSet()) {
1712 			warnings << dir << "/" << pfile << ": Timer section wants to spawn projectiles but there is no spawning information" << endl;
1713 			proj->Timer.Projectiles = false;
1714 		}
1715 	}
1716 
1717 	// Player hit
1718 	proj->PlyHit.Type = PJ_INJURE;
1719 	proj->PlyHit.readFromIni(this, dir, ini, "PlayerHit");
1720 
1721 	if(proj->PlyHit.Shake != 0) {
1722 		warnings << "projectile " << ini.getFileName() << " has PlayerHit.Shake != 0 which was not supported earlier. this is ignored" << endl;
1723 		proj->PlyHit.Shake = 0;
1724 	}
1725 
1726 	if(proj->PlyHit.BounceExplode != 0) {
1727 		warnings << "projectile " << ini.getFileName() << " has PlayerHit.BounceExplode != 0 which was not supported earlier. this is ignored" << endl;
1728 		proj->PlyHit.BounceExplode = 0;
1729 	}
1730 
1731 	// If PJ_PLAYSOUND is set, we support it because PJ_PLAYSOUND didn't existed earlier.
1732 	// But you could also just use a custom action to play the sound.
1733 	if(proj->PlyHit.UseSound && proj->PlyHit.Type != PJ_PLAYSOUND) {
1734 		warnings << "projectile " << ini.getFileName() << " has sound set, which was not supported earlier, thus it's ignored now" << endl;
1735 		proj->PlyHit.UseSound = false;
1736 	}
1737 
1738 	if(proj->PlyHit.needGeneralSpawnInfo() && !proj->GeneralSpawnInfo.isSet()) {
1739 		warnings << dir << "/" << pfile << ": PlayerHit section wants to spawn projectiles but there is no spawning information" << endl;
1740 		proj->PlyHit.Projectiles = false;
1741 	}
1742 
1743 	/*
1744     // OnExplode
1745 	ReadKeyword( file, "Explode", "Type",   (int*)&proj->Exp.Type, PJ_NOTHING );
1746 	ReadInteger( file, "Explode", "Damage",     &proj->Exp.Damage, 0 );
1747 	ReadKeyword( file, "Explode", "Projectiles",&proj->Exp.Projectiles, false );
1748 	ReadInteger( file, "Explode", "Shake",      &proj->Exp.Shake, 0 );
1749 	proj->Exp.UseSound = false;
1750 	if( ReadString(file, "Explode", "Sound", proj->Exp.SndFilename,"") ) {
1751 		proj->Exp.UseSound = true;
1752 	}
1753 
1754     // Touch
1755 	ReadKeyword( file, "Touch", "Type",   (int*)&proj->Tch.Type, PJ_NOTHING );
1756 	ReadInteger( file, "Touch", "Damage",     &proj->Tch.Damage, 0 );
1757 	ReadKeyword( file, "Touch", "Projectiles",&proj->Tch.Projectiles, false );
1758 	ReadInteger( file, "Touch", "Shake",      &proj->Tch.Shake, 0 );
1759 	proj->Tch.UseSound = false;
1760 	if( ReadString(file, "Touch", "Sound", proj->Tch.SndFilename,"") )
1761 		proj->Tch.UseSound = true;
1762 	 */
1763 
1764 	{
1765 		int projHitC = 0;
1766 		ini.ReadInteger("General", "ActionNum", &projHitC, 0);
1767 		for(int i = 0; i < projHitC; ++i) {
1768 			Proj_EventAndAction act;
1769 			if(!act.readFromIni(this, dir, ini, "Action" + itoa(i+1))) continue;
1770 
1771 			if(act.needGeneralSpawnInfo() && !proj->GeneralSpawnInfo.isSet()) {
1772 				warnings << dir << "/" << pfile << ": Action" << (i+1) << " section wants to spawn projectiles but there is no spawning information" << endl;
1773 				act.Projectiles = false;
1774 			}
1775 
1776 			if(!act.hasAction()) {
1777 				warnings << "section Action" << (i+1) << " (" << pfile << ") doesn't have any effect" << endl;
1778 				continue;
1779 			}
1780 
1781 			proj->actions.push_back(act);
1782 		}
1783 	}
1784 
1785 	// Projectile trail
1786 	if(proj->Trail.Type == TRL_PROJECTILE) {
1787 		ini.ReadFloat("ProjectileTrail", "Delay",  &proj->Trail.Delay, 100); proj->Trail.Delay /= 1000.0f;
1788 
1789 		// we have some other default values here
1790 		proj->Trail.Proj.Amount = 1;
1791 		proj->Trail.Proj.Speed = 100;
1792 
1793 		proj->Trail.Proj.readFromIni(this, dir, ini, "ProjectileTrail");
1794 
1795 		if(proj->Trail.Proj.Useangle) {
1796 			warnings << "Useangle is set in ProjectileTrail-section (" << pfile << "); this was not supported in LX56 thus we ignore it" << endl;
1797 			proj->Trail.Proj.Useangle = false;
1798 		}
1799 
1800 		if(proj->Trail.Proj.Angle != 0) {
1801 			warnings << "Angle is set in ProjectileTrail-section (" << pfile << "); this was not supported in LX56 thus we ignore it" << endl;
1802 			proj->Trail.Proj.Useangle = false;
1803 		}
1804 
1805 		if(proj->Trail.Type == TRL_PROJECTILE && !proj->Trail.Proj.isSet()) {
1806 			warnings << dir << "/" << pfile << ": ProjectileTrail section wants to spawn projectiles but there is no spawning information" << endl;
1807 			proj->Trail.Type = TRL_NONE;
1808 		}
1809 	}
1810 
1811 	return proj;
1812 }
1813 
1814 
1815 ///////////////////
1816 // Compile the extra stuff
CompileExtra(const IniReader & ini)1817 bool CGameScript::CompileExtra(const IniReader& ini)
1818 {
1819 	CGameScript* Game = this;
1820 
1821 	notes << "   Compiling Extras" << endl;
1822 
1823 	// Ninja Rope
1824 	notes << "  Compiling Ninja Rope" << endl;
1825 
1826 	int ropel, restl;
1827 	float strength;
1828 
1829 	ini.ReadInteger("NinjaRope","RopeLength",&ropel,0);
1830 	ini.ReadInteger("NinjaRope","RestLength",&restl,0);
1831 	ini.ReadFloat("NinjaRope","Strength",&strength,0);
1832 
1833 	Game->SetRopeLength(ropel);
1834 	Game->SetRestLength(restl);
1835 	Game->SetStrength(strength);
1836 
1837 
1838 	// Worm
1839 	notes << "  Compiling Worm" << endl;
1840 	gs_worm_t *wrm = &Game->Worm;
1841 
1842 	ini.ReadFloat( "Worm", "AngleSpeed",		&wrm->AngleSpeed, 150);
1843 	ini.ReadFloat( "Worm", "GroundSpeed",		&wrm->GroundSpeed, 8);
1844 	ini.ReadFloat( "Worm", "AirSpeed",		&wrm->AirSpeed, 1);
1845 	ini.ReadFloat( "Worm", "Gravity",			&wrm->Gravity, 175);
1846 	ini.ReadFloat( "Worm", "JumpForce",		&wrm->JumpForce, -140);
1847 	ini.ReadFloat( "Worm", "AirFriction",		&wrm->AirFriction, 0);
1848 	ini.ReadFloat( "Worm", "GroundFriction",	&wrm->GroundFriction, 0.6f);
1849 
1850 
1851 
1852 
1853 
1854 	return true;
1855 }
1856 
1857 
1858 /*
1859 ===============================
1860 
1861 		Special items
1862 
1863 ===============================
1864 */
1865 
1866 
1867 ///////////////////
1868 // Compile the jetpack
CompileJetpack(const IniReader & ini,weapon_t * Weap)1869 bool CGameScript::CompileJetpack(const IniReader& ini, weapon_t *Weap)
1870 {
1871 	Weap->Proj.Proj = NULL;
1872 
1873 	ini.ReadInteger("JetPack", "Thrust", (int*)&Weap->tSpecial.Thrust, 0);
1874 	ini.ReadFloat("JetPack", "Drain", &Weap->Drain, 0);
1875 	ini.ReadFloat("JetPack", "Recharge", &Weap->Recharge, 0);	Weap->Recharge /= 10.0f;
1876 
1877 	Weap->ROF = 1.0f / 1000.0f;
1878 
1879 	return true;
1880 }
1881 
1882 
readFromIni(CGameScript * gs,const std::string & dir,const IniReader & ini,const std::string & section)1883 bool Proj_SpawnInfo::readFromIni(CGameScript* gs, const std::string& dir, const IniReader& ini, const std::string& section) {
1884 	ini.ReadKeyword(section, "AddParentVel", &AddParentVel, AddParentVel); // new in OLX beta9
1885 	ini.ReadMatrixD2(section, "ParentVelFactor", ParentVelFactor, ParentVelFactor); // new in OLX beta9
1886 	ini.ReadVectorD2(section, "PosDiff", PosDiff, PosDiff); // new in OLX beta9
1887 	ini.ReadVectorD2(section, "SnapToGrid", SnapToGrid, SnapToGrid); // new in OLX beta9
1888 
1889 	ini.ReadKeyword(section, "Useangle", &Useangle, Useangle);
1890 	ini.ReadInteger(section, "Angle", &Angle, Angle);
1891 
1892 	ini.ReadKeyword(section, "UseProjVelocity", &UseParentVelocityForSpread, UseParentVelocityForSpread);
1893 
1894 	ini.ReadInteger(section, "Amount", &Amount, Amount);
1895 	ini.ReadInteger(section, "Speed",  &Speed, Speed);
1896 	ini.ReadFloat(section, "SpeedVar",  &SpeedVar, SpeedVar);
1897 	ini.ReadFloat(section, "Spread", &Spread, Spread);
1898 
1899 	std::string prjfile;
1900 	ini.ReadString(section, "Projectile", prjfile, "");
1901 	if (prjfile.size())
1902 		Proj = gs->CompileProjectile(dir, prjfile);
1903 	else
1904 		Proj = NULL;
1905 
1906 	return true;
1907 }
1908 
read(CGameScript * gs,FILE * fp)1909 bool Proj_SpawnInfo::read(CGameScript* gs, FILE* fp) {
1910 	if(gs->GetHeader()->Version <= GS_LX56_VERSION) {
1911 		errors << "Proj_SpawnInfo::read called for old GS version" << endl;
1912 		return false;
1913 	}
1914 
1915 	fread_endian<char>(fp, AddParentVel);
1916 	fread_endian_M<float>(fp, ParentVelFactor);
1917 	fread_endian_V<int>(fp, PosDiff);
1918 	fread_endian_V<int>(fp, SnapToGrid);
1919 	fread_endian<char>(fp, Useangle);
1920 	fread_endian<int>(fp, Angle);
1921 	fread_endian<int>(fp, Amount);
1922 	fread_endian<int>(fp, Speed);
1923 	fread_endian<float>(fp, SpeedVar);
1924 	fread_endian<float>(fp, Spread);
1925 	Proj = gs->LoadProjectile(fp);
1926 	return Proj != NULL;
1927 }
1928 
write(CGameScript * gs,FILE * fp)1929 bool Proj_SpawnInfo::write(CGameScript* gs, FILE* fp) {
1930 	if(gs->GetHeader()->Version <= GS_LX56_VERSION) {
1931 		errors << "Proj_SpawnInfo::write called for old GS version" << endl;
1932 		return false;
1933 	}
1934 
1935 	fwrite_endian<char>(fp, AddParentVel);
1936 	fwrite_endian_M<float>(fp, ParentVelFactor);
1937 	fwrite_endian_V<int>(fp, PosDiff);
1938 	fwrite_endian_V<int>(fp, SnapToGrid);
1939 	fwrite_endian<char>(fp, Useangle);
1940 	fwrite_endian<int>(fp, Angle);
1941 	fwrite_endian<int>(fp, Amount);
1942 	fwrite_endian<int>(fp, Speed);
1943 	fwrite_endian<float>(fp, SpeedVar);
1944 	fwrite_endian<float>(fp, Spread);
1945 	return gs->SaveProjectile(Proj, fp);
1946 }
1947 
1948 
readFromIni(const IniReader & ini,const std::string & section)1949 bool Wpn_Beam::readFromIni(const IniReader& ini, const std::string& section) {
1950 	ini.ReadInteger(section, "Damage", &Damage, Damage);
1951 	ini.ReadInteger(section, "Length", &Length, Length);
1952 	ini.ReadInteger(section, "PlayerDamage", &PlyDamage, PlyDamage);
1953 	ini.ReadColour(section, "Colour", Colour, Colour);
1954 	ini.ReadInteger(section, "InitWidth", &InitWidth, InitWidth);
1955 	ini.ReadFloat(section, "WidthIncrease", &WidthIncrease, WidthIncrease);
1956 	ini.ReadKeyword(section, "DistributeDamageOverWidth", &DistributeDamageOverWidth, DistributeDamageOverWidth);
1957 	return true;
1958 }
1959 
read(CGameScript * gs,FILE * fp)1960 bool Wpn_Beam::read(CGameScript* gs, FILE* fp) {
1961 	if(gs->GetHeader()->Version <= GS_LX56_VERSION) {
1962 		int r=0,g=0,b=0;
1963 		fread_endian<int>(fp, r);
1964 		fread_endian<int>(fp, g);
1965 		fread_endian<int>(fp, b);
1966 		Colour = Color(r,g,b);
1967 	}
1968 	else {
1969 		fread_compat(Colour.r, 1,  1, fp);
1970 		fread_compat(Colour.g, 1,  1, fp);
1971 		fread_compat(Colour.b, 1,  1, fp);
1972 		fread_compat(Colour.a, 1,  1, fp);
1973 	}
1974 
1975 	fread_endian<int>(fp, Damage);
1976 	fread_endian<int>(fp, PlyDamage);
1977 	fread_endian<int>(fp, Length);
1978 
1979 	if(gs->GetHeader()->Version > GS_LX56_VERSION) {
1980 		fread_endian<int>(fp, InitWidth);
1981 		fread_endian<float>(fp, WidthIncrease);
1982 		fread_endian<char>(fp, DistributeDamageOverWidth);
1983 	}
1984 
1985 	return true;
1986 }
1987 
write(CGameScript * gs,FILE * fp)1988 bool Wpn_Beam::write(CGameScript* gs, FILE* fp) {
1989 	if(gs->GetHeader()->Version <= GS_LX56_VERSION) {
1990 		fwrite_endian<int>(fp, Colour.r);
1991 		fwrite_endian<int>(fp, Colour.g);
1992 		fwrite_endian<int>(fp, Colour.b);
1993 	}
1994 	else {
1995 		fwrite_endian<Uint8>(fp, Colour.r);
1996 		fwrite_endian<Uint8>(fp, Colour.g);
1997 		fwrite_endian<Uint8>(fp, Colour.b);
1998 		fwrite_endian<Uint8>(fp, Colour.a);
1999 	}
2000 
2001 	fwrite_endian<int>(fp, Damage);
2002 	fwrite_endian<int>(fp, PlyDamage);
2003 	fwrite_endian<int>(fp, Length);
2004 
2005 	if(gs->GetHeader()->Version > GS_LX56_VERSION) {
2006 		fwrite_endian<int>(fp, InitWidth);
2007 		fwrite_endian<float>(fp, WidthIncrease);
2008 		fwrite_endian<char>(fp, DistributeDamageOverWidth);
2009 	}
2010 
2011 	return true;
2012 }
2013 
2014 
readFromIni(CGameScript * gs,const std::string & dir,const IniReader & ini,const std::string & section,int deepCounter)2015 bool Proj_Action::readFromIni(CGameScript* gs, const std::string& dir, const IniReader& ini, const std::string& section, int deepCounter) {
2016 	ini.ReadKeyword(section, "Type", (int*)&Type, Type);
2017 	ini.ReadKeyword(section,"Projectiles",&Projectiles,false);
2018 	ini.ReadInteger(section,"Damage",&Damage,Damage);
2019 	ini.ReadInteger(section,"Shake",&Shake,Shake);
2020 
2021 	UseSound = false;
2022 	if(ini.ReadString(section,"Sound",SndFilename,"")) {
2023 		UseSound = true;
2024 
2025 		if(!bDedicated) {
2026 			// Load the sample
2027 			Sound = gs->LoadGSSample(dir, SndFilename);
2028 
2029 			if(Sound == NULL)
2030 				gs->modLog(ini.getFileName() + ":" + section + ": Could not open sound '" + SndFilename + "'");
2031 		}
2032 	}
2033 
2034 	ini.ReadFloat(section,"BounceCoeff",&BounceCoeff,BounceCoeff);
2035 	ini.ReadInteger(section,"BounceExplode",&BounceExplode,BounceExplode);
2036 
2037 	ini.ReadFloat(section,"GoThroughSpeed",&GoThroughSpeed,GoThroughSpeed);
2038 	ini.ReadVectorD2(section, "ChangeRadius", ChangeRadius, ChangeRadius);
2039 
2040 	switch(Type) {
2041 		case PJ_OverwriteOwnSpeed:
2042 		case PJ_DiffOwnSpeed:
2043 		case PJ_OverwriteTargetSpeed:
2044 		case PJ_DiffTargetSpeed:
2045 			if(!ini.ReadVectorD2(section, "Speed", Speed, Speed)) {
2046 				warnings << "Speed attribute missing in " << dir << "/" << ini.getFileName() << ":" << section << endl;
2047 			}
2048 		default: break;
2049 	}
2050 	switch(Type) {
2051 		case PJ_MultiplyOwnSpeed:
2052 		case PJ_MultiplyTargetSpeed:
2053 		case PJ_HeadingToNextWorm:
2054 		case PJ_HeadingToOwner:
2055 		case PJ_HeadingToNextOtherWorm:
2056 		case PJ_HeadingToNextEnemyWorm:
2057 		case PJ_HeadingToNextTeamMate:
2058 		case PJ_HeadTargetToUs:
2059 			if(!ini.ReadMatrixD2(section, "SpeedMult", SpeedMult, SpeedMult)) {
2060 				warnings << "SpeedMult attribute missing in " << dir << "/" << ini.getFileName() << ":" << section << endl;
2061 			}
2062 		default:
2063 			// Read it as well but don't complain when missing
2064 			ini.ReadMatrixD2(section, "SpeedMult", SpeedMult, SpeedMult);
2065 		break;
2066 	}
2067 
2068 	if(Projectiles)
2069 		Proj.readFromIni(gs, dir, ini, section + ".Projectile");
2070 
2071 	std::string addActionSection;
2072 	ini.ReadString(section, "Additional", addActionSection, "");
2073 	TrimSpaces(addActionSection);
2074 	if(addActionSection != "") {
2075 		if(deepCounter > 1000) {
2076 			warnings << "There is probably an additional action definition loop in " << ini.getFileName() << ":" << section << endl;
2077 			return false;
2078 		}
2079 
2080 		additionalAction = new Proj_Action();
2081 		additionalAction->Type = PJ_NOTHING;
2082 		if(!additionalAction->readFromIni(gs, dir, ini, addActionSection, deepCounter + 1)) {
2083 			delete additionalAction; additionalAction = NULL;
2084 			return false;
2085 		}
2086 		else if(!additionalAction->hasAction()) {
2087 			warnings << "additional action " << addActionSection << "(in " << ini.getFileName() << ":" << section << ") does not have any effect" << endl;
2088 			delete additionalAction; additionalAction = NULL;
2089 		}
2090 	}
2091 	else
2092 		additionalAction = NULL;
2093 
2094 	return true;
2095 }
2096 
read(CGameScript * gs,FILE * fp)2097 bool Proj_Action::read(CGameScript* gs, FILE* fp) {
2098 	if(gs->GetHeader()->Version <= GS_LX56_VERSION) {
2099 		errors << "Proj_Action::read called for old GS version" << endl;
2100 		return false;
2101 	}
2102 
2103 	fread_endian<int>(fp, Type);
2104 	fread_endian<char>(fp, Projectiles);
2105 	fread_endian<int>(fp, Damage);
2106 	fread_endian<int>(fp, Shake);
2107 	fread_endian<char>(fp, UseSound);
2108 	if(UseSound) {
2109 		SndFilename = readString(fp);
2110 
2111 		if(!bDedicated) {
2112 			// Load the sample
2113 			Sound = gs->LoadGSSample(gs->sDirectory, SndFilename);
2114 
2115 			if(Sound == NULL)
2116 				gs->modLog("Could not open sound '" + SndFilename + "'");
2117 		}
2118 	}
2119 	fread_endian<float>(fp, BounceCoeff);
2120 	fread_endian<int>(fp, BounceExplode);
2121 	fread_endian<float>(fp, GoThroughSpeed);
2122 	fread_endian_V<int>(fp, ChangeRadius);
2123 	fread_endian_V<float>(fp, Speed);
2124 	fread_endian_M<float>(fp, SpeedMult);
2125 	if(Projectiles) Proj.read(gs, fp);
2126 
2127 	bool haveAddAction = false;
2128 	fread_endian<char>(fp, haveAddAction);
2129 	if(haveAddAction) {
2130 		additionalAction = new Proj_Action();
2131 		return additionalAction->read(gs, fp);
2132 	}
2133 	return true;
2134 }
2135 
write(CGameScript * gs,FILE * fp)2136 bool Proj_Action::write(CGameScript* gs, FILE* fp) {
2137 	if(gs->GetHeader()->Version <= GS_LX56_VERSION) {
2138 		errors << "Proj_Action::write called for old GS version" << endl;
2139 		return false;
2140 	}
2141 
2142 	fwrite_endian<int>(fp, Type);
2143 	fwrite_endian<char>(fp, Projectiles);
2144 	fwrite_endian<int>(fp, Damage);
2145 	fwrite_endian<int>(fp, Shake);
2146 	fwrite_endian<char>(fp, UseSound);
2147 	if(UseSound) writeString(SndFilename, fp);
2148 	fwrite_endian<float>(fp, BounceCoeff);
2149 	fwrite_endian<int>(fp, BounceExplode);
2150 	fwrite_endian<float>(fp, GoThroughSpeed);
2151 	fwrite_endian_V<int>(fp, ChangeRadius);
2152 	fwrite_endian_V<float>(fp, Speed);
2153 	fwrite_endian_M<float>(fp, SpeedMult);
2154 
2155 	if(Projectiles) Proj.write(gs, fp);
2156 
2157 	if(!additionalAction)
2158 		fwrite_endian<char>(fp, 0);
2159 	else {
2160 		fwrite_endian<char>(fp, 1);
2161 		additionalAction->write(gs, fp);
2162 	}
2163 	return true;
2164 }
2165 
readFromIni(CGameScript * gs,const std::string & dir,const IniReader & ini,const std::string & section)2166 bool Proj_Event::readFromIni(CGameScript* gs, const std::string& dir, const IniReader& ini, const std::string& section) {
2167 	if(!ini.ReadKeyword(section, "Type", (int*)&type, type)) {
2168 		warnings << ini.getFileName() << ":" << section << ": Type attribute isn't set for event" << endl;
2169 		return false;
2170 	}
2171 	if(get() == NULL) {
2172 		warnings << ini.getFileName() << ":" << section << ": Type attribute is invalid for event" << endl;
2173 		return false;
2174 	}
2175 	if(!get()->readFromIni(gs, dir, ini, section))
2176 		return false;
2177 
2178 	if(!get()->canMatch()) {
2179 		warnings << ini.getFileName() << ":" << section << ": Event can never occur with these settings" << endl;
2180 		return false;
2181 	}
2182 	return true;
2183 }
2184 
read(CGameScript * gs,FILE * fp)2185 bool Proj_Event::read(CGameScript* gs, FILE* fp) {
2186 	fread_endian<int>(fp, type);
2187 	if(get() == NULL) return false;
2188 	return get()->read(gs, fp);
2189 }
2190 
write(CGameScript * gs,FILE * fp)2191 bool Proj_Event::write(CGameScript* gs, FILE* fp) {
2192 	fwrite_endian<int>(fp, type);
2193 	if(get() == NULL) return false;
2194 	return get()->write(gs, fp);
2195 }
2196 
readFromIni(CGameScript * gs,const std::string & dir,const IniReader & ini,const std::string & section)2197 bool Proj_EventAndAction::readFromIni(CGameScript* gs, const std::string& dir, const IniReader& ini, const std::string& section) {
2198 	std::string eventSection;
2199 	ini.ReadString(section, "Event", eventSection, eventSection);
2200 	if(eventSection == "") {
2201 		warnings << ini.getFileName() << ":" << section << ": Event not set" << endl;
2202 		return false;
2203 	}
2204 
2205 	while(eventSection != "") {
2206 		if(events.size() > 1000) {
2207 			warnings << ini.getFileName() << ":" << section << ": Probably there is a loop definition for Event" << endl;
2208 			events.clear();
2209 			return false;
2210 		}
2211 		Proj_Event ev;
2212 		if(!ev.readFromIni(gs, dir, ini, eventSection)) return events.size() > 0;
2213 		events.push_back(ev);
2214 		if(!ini.ReadString(std::string(eventSection), "AndEvent", eventSection, eventSection)) break;
2215 	}
2216 
2217 	return Proj_Action::readFromIni(gs, dir, ini, section);
2218 }
2219 
read(CGameScript * gs,FILE * fp)2220 bool Proj_EventAndAction::read(CGameScript* gs, FILE* fp) {
2221 	Uint32 eventNum = 0;
2222 	fread_endian<Uint32>(fp, eventNum);
2223 	events.resize(eventNum);
2224 
2225 	for(Uint32 i = 0; i < eventNum; ++i)
2226 		if(!events[i].read(gs, fp)) {
2227 			errors << "Proj_EventAndAction: error while reading game script projectile actions" << endl;
2228 			events.clear(); // would crash otherwise
2229 			return false;
2230 		}
2231 
2232 	return Proj_Action::read(gs, fp);
2233 }
2234 
write(CGameScript * gs,FILE * fp)2235 bool Proj_EventAndAction::write(CGameScript* gs, FILE* fp) {
2236 	fwrite_endian<Uint32>(fp, (Uint32)events.size());
2237 
2238 	for(Uint32 i = 0; i < events.size(); ++i)
2239 		events[i].write(gs, fp);
2240 
2241 	return Proj_Action::write(gs, fp);
2242 }
2243 
2244 
readFromIni(CGameScript * gs,const std::string & dir,const IniReader & ini,const std::string & section)2245 bool Proj_TimerEvent::readFromIni(CGameScript* gs, const std::string& dir, const IniReader& ini, const std::string& section) {
2246 	ini.ReadFloat(section, "Delay", &Delay, Delay);
2247 	ini.ReadKeyword(section, "Repeat", &Repeat, Repeat);
2248 	ini.ReadKeyword(section, "UseGlobalTime", &UseGlobalTime, UseGlobalTime);
2249 	ini.ReadInteger(section, "PermanentMode", &PermanentMode, PermanentMode);
2250 	return true;
2251 }
2252 
read(CGameScript * gs,FILE * fp)2253 bool Proj_TimerEvent::read(CGameScript* gs, FILE* fp) {
2254 	fread_endian<float>(fp, Delay);
2255 	fread_endian<char>(fp, Repeat);
2256 	fread_endian<char>(fp, UseGlobalTime);
2257 	fread_endian<int>(fp, PermanentMode);
2258 	return true;
2259 }
2260 
write(CGameScript * gs,FILE * fp)2261 bool Proj_TimerEvent::write(CGameScript* gs, FILE* fp) {
2262 	fwrite_endian<float>(fp, Delay);
2263 	fwrite_endian<char>(fp, Repeat);
2264 	fwrite_endian<char>(fp, UseGlobalTime);
2265 	fwrite_endian<int>(fp, PermanentMode);
2266 	return true;
2267 }
2268 
readFromIni(CGameScript * gs,const std::string & dir,const IniReader & ini,const std::string & section)2269 bool Proj_ProjHitEvent::readFromIni(CGameScript* gs, const std::string& dir, const IniReader& ini, const std::string& section) {
2270 	gs->needCollisionInfo = true;
2271 
2272 	ownerWorm.readFromIni(gs, dir, ini, section);
2273 
2274 	ini.ReadInteger(section, "MinHitCount", &MinHitCount, MinHitCount);
2275 	ini.ReadInteger(section, "MaxHitCount", &MaxHitCount, MaxHitCount);
2276 
2277 	if(MaxHitCount >= 0 && MaxHitCount < MinHitCount) {
2278 		warnings << ini.getFileName() << ":" << section << ": ignoring MaxHitCount because MaxHitCount < MinHitCount" << endl;
2279 		MaxHitCount = -1;
2280 	}
2281 
2282 	ini.ReadInteger(section, "Width", &Width, Width);
2283 	ini.ReadInteger(section, "Height", &Height, Height);
2284 
2285 	ini.ReadKeyword(section, "TargetHealthIsMore", &TargetHealthIsMore, TargetHealthIsMore);
2286 	ini.ReadKeyword(section, "TargetHealthIsLess", &TargetHealthIsLess, TargetHealthIsLess);
2287 	ini.ReadKeyword(section, "TargetTimeIsMore", &TargetTimeIsMore, TargetTimeIsMore);
2288 	ini.ReadKeyword(section, "TargetTimeIsLess", &TargetTimeIsLess, TargetTimeIsLess);
2289 
2290 	std::string prjfile;
2291 	ini.ReadString(section, "Target", prjfile, "");
2292 	if(prjfile != "")
2293 		Target = gs->CompileProjectile(dir, prjfile);
2294 	else
2295 		Target = NULL;
2296 	return true;
2297 }
2298 
read(CGameScript * gs,FILE * fp)2299 bool Proj_ProjHitEvent::read(CGameScript* gs, FILE* fp) {
2300 	if(gs->GetHeader()->Version <= GS_LX56_VERSION) {
2301 		errors << "Proj_ProjHitEvent::read called for old GS version" << endl;
2302 		return false;
2303 	}
2304 
2305 	ownerWorm.read(gs, fp);
2306 
2307 	gs->needCollisionInfo = true;
2308 	fread_endian<int>(fp, MinHitCount);
2309 	fread_endian<int>(fp, MaxHitCount);
2310 	fread_endian<int>(fp, Width);
2311 	fread_endian<int>(fp, Height);
2312 	fread_endian<char>(fp, TargetHealthIsMore);
2313 	fread_endian<char>(fp, TargetHealthIsLess);
2314 	fread_endian<char>(fp, TargetTimeIsMore);
2315 	fread_endian<char>(fp, TargetTimeIsLess);
2316 
2317 	bool hasSpecificTarget = false;
2318 	fread_endian<char>(fp, hasSpecificTarget);
2319 	if(hasSpecificTarget)
2320 		Target = gs->LoadProjectile(fp);
2321 	else
2322 		Target = NULL;
2323 	return !hasSpecificTarget || Target != NULL;
2324 }
2325 
write(CGameScript * gs,FILE * fp)2326 bool Proj_ProjHitEvent::write(CGameScript* gs, FILE* fp) {
2327 	if(gs->GetHeader()->Version <= GS_LX56_VERSION) {
2328 		errors << "Proj_ProjHitEvent::write called for old GS version" << endl;
2329 		return false;
2330 	}
2331 
2332 	ownerWorm.write(gs, fp);
2333 
2334 	fwrite_endian<int>(fp, MinHitCount);
2335 	fwrite_endian<int>(fp, MaxHitCount);
2336 	fwrite_endian<int>(fp, Width);
2337 	fwrite_endian<int>(fp, Height);
2338 	fwrite_endian<char>(fp, TargetHealthIsMore);
2339 	fwrite_endian<char>(fp, TargetHealthIsLess);
2340 	fwrite_endian<char>(fp, TargetTimeIsMore);
2341 	fwrite_endian<char>(fp, TargetTimeIsLess);
2342 
2343 	fwrite_endian<char>(fp, Target != NULL);
2344 	if(Target) return gs->SaveProjectile(Target, fp);
2345 	return true;
2346 }
2347 
2348 
2349 
readFromIni(CGameScript * gs,const std::string & dir,const IniReader & ini,const std::string & section)2350 bool Proj_WormHitEvent::readFromIni(CGameScript* gs, const std::string& dir, const IniReader& ini, const std::string& section) {
2351 	ini.ReadKeyword(section, "SameWormAsProjOwner", &SameWormAsProjOwner, SameWormAsProjOwner);
2352 	ini.ReadKeyword(section, "SameTeamAsProjOwner", &SameTeamAsProjOwner, SameTeamAsProjOwner);
2353 	ini.ReadKeyword(section, "DiffWormAsProjOwner", &DiffWormAsProjOwner, DiffWormAsProjOwner);
2354 	ini.ReadKeyword(section, "DiffTeamAsProjOwner", &DiffTeamAsProjOwner, DiffTeamAsProjOwner);
2355 	ini.ReadKeyword(section, "TeamMateOfProjOwner", &TeamMateOfProjOwner, TeamMateOfProjOwner);
2356 	ini.ReadKeyword(section, "EnemyOfProjOwner", &EnemyOfProjOwner, EnemyOfProjOwner);
2357 	return true;
2358 }
2359 
read(CGameScript * gs,FILE * fp)2360 bool Proj_WormHitEvent::read(CGameScript* gs, FILE* fp) {
2361 	fread_endian<char>(fp, SameWormAsProjOwner);
2362 	fread_endian<char>(fp, SameTeamAsProjOwner);
2363 	fread_endian<char>(fp, DiffWormAsProjOwner);
2364 	fread_endian<char>(fp, DiffTeamAsProjOwner);
2365 	fread_endian<char>(fp, TeamMateOfProjOwner);
2366 	fread_endian<char>(fp, EnemyOfProjOwner);
2367 	return true;
2368 }
2369 
write(CGameScript * gs,FILE * fp)2370 bool Proj_WormHitEvent::write(CGameScript* gs, FILE* fp) {
2371 	fwrite_endian<char>(fp, SameWormAsProjOwner);
2372 	fwrite_endian<char>(fp, SameTeamAsProjOwner);
2373 	fwrite_endian<char>(fp, DiffWormAsProjOwner);
2374 	fwrite_endian<char>(fp, DiffTeamAsProjOwner);
2375 	fwrite_endian<char>(fp, TeamMateOfProjOwner);
2376 	fwrite_endian<char>(fp, EnemyOfProjOwner);
2377 	return true;
2378 }
2379 
2380 
2381 
readFromIni(CGameScript * gs,const std::string & dir,const IniReader & ini,const std::string & section)2382 bool Proj_TerrainHitEvent::readFromIni(CGameScript* gs, const std::string& dir, const IniReader& ini, const std::string& section) {
2383 	ini.ReadKeyword(section, "MapBound", &MapBound, MapBound);
2384 	ini.ReadKeyword(section, "Dirt", &Dirt, Dirt);
2385 	ini.ReadKeyword(section, "Rock", &Rock, Rock);
2386 	return true;
2387 }
2388 
read(CGameScript * gs,FILE * fp)2389 bool Proj_TerrainHitEvent::read(CGameScript* gs, FILE* fp) {
2390 	fread_endian<char>(fp, MapBound);
2391 	fread_endian<char>(fp, Dirt);
2392 	fread_endian<char>(fp, Rock);
2393 	return true;
2394 }
2395 
write(CGameScript * gs,FILE * fp)2396 bool Proj_TerrainHitEvent::write(CGameScript* gs, FILE* fp) {
2397 	fwrite_endian<char>(fp, MapBound);
2398 	fwrite_endian<char>(fp, Dirt);
2399 	fwrite_endian<char>(fp, Rock);
2400 	return true;
2401 }
2402