1 /*
2 * atanks - obliterate each other with oversize weapons
3 * Copyright (C) 2003 Thomas Hudson
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 * */
19
20 #include <time.h>
21 #include <cassert>
22 #include "player.h"
23 #include "globaldata.h"
24 #include "files.h"
25 #include "tank.h"
26 #include "sound.h"
27 #include "debris_pool.h"
28
29
GLOBALDATA()30 GLOBALDATA::GLOBALDATA()
31 {
32 // memset initialization, because Visual C++ 2013 can't do lists, yet.
33 memset(order, 0, sizeof(TANK*) * MAXPLAYERS);
34 memset(tank_status, 0, sizeof(char) * 128);
35 memset(heads, 0, sizeof(vobj_t*) * CLASS_COUNT);
36 memset(tails, 0, sizeof(vobj_t*) * CLASS_COUNT);
37 }
38
39
~GLOBALDATA()40 GLOBALDATA::~GLOBALDATA()
41 {
42 this->destroy();
43 }
44
45 /// @brief goes through the columns from @a left to @a right and sets slide type according to @a do_lock
addLandSlide(int32_t left,int32_t right,bool do_lock)46 void GLOBALDATA::addLandSlide(int32_t left, int32_t right, bool do_lock)
47 {
48 // Opt out soon if no landslide is to be done
49 if ( (SLIDE_NONE == env.landSlideType)
50 || (SLIDE_TANK_ONLY == env.landSlideType) )
51 return;
52
53 int32_t minX = std::min(left, right);
54 int32_t maxX = std::max(left, right);
55
56 if (minX < 1)
57 minX = 1;
58 if (minX > (env.screenWidth - 1) )
59 minX = env.screenWidth - 1;
60 if (maxX < 1)
61 maxX = 1;
62 if (maxX > (env.screenWidth - 1) )
63 maxX = env.screenWidth - 1;
64
65 if (do_lock)
66 memset(&done[minX], 3, sizeof(char) * (maxX - minX + 1) );
67 else
68 memset(&done[minX], 2, sizeof(char) * (maxX - minX + 1) );
69 }
70
71
addObject(vobj_t * object)72 void GLOBALDATA::addObject (vobj_t *object)
73 {
74 if (nullptr == object)
75 return;
76
77 eClasses class_ = object->getClass();
78
79 objLocks[class_].lock();
80
81 /// --- case 1: first of its kind ---
82 if (nullptr == tails[class_]) {
83 heads[class_] = object;
84 tails[class_] = object;
85 }
86
87 /// --- case 2: normal addition ---
88 else {
89 tails[class_]->next = object;
90 object->prev = tails[class_];
91 tails[class_] = object;
92 }
93
94 objLocks[class_].unlock();
95 }
96
97
98 // Combine both make_update and make_bgupdate with safety checks for
99 // the dimensions. This reduces code duplication.
addUpdate(int32_t x,int32_t y,int32_t w,int32_t h,BOX * target,int32_t & target_count)100 void GLOBALDATA::addUpdate(int32_t x, int32_t y, int32_t w, int32_t h,
101 BOX* target, int32_t &target_count)
102 {
103 assert (target && "ERROR: addUpdate called with nullptr target!");
104
105 bool combined = false;
106
107 assert ( (w > 0) && (h > 0) ); // No zero/negative updates, please!
108
109 int32_t left = std::max(x - 1, 0);
110 int32_t top = std::max(y - 1, 0);
111 int32_t right = std::min(x + w + 1, env.screenWidth);
112 int32_t bottom = std::min(y + h + 1, env.screenHeight);
113
114 // If the update is outside the screen, it is not needed:
115 if ( (bottom <= 0) /* most common case */
116 || (left >= env.screenWidth)
117 || (right <= 0)
118 || (top >= env.screenHeight) )
119 return;
120
121 assert( (left < right ) );
122 assert( (top < bottom) );
123
124 if ( combineUpdates && target_count
125 && (target_count < env.max_screen_updates)) {
126 // Re-purpose BOX::w as x2 and BOX::h as y2:
127 BOX prev(target[target_count - 1].x,
128 target[target_count - 1].y,
129 target[target_count - 1].x + target[target_count - 1].w,
130 target[target_count - 1].y + target[target_count - 1].h);
131 BOX next(left, top, right, bottom);
132
133 if ( (next.w > (prev.x - 3))
134 && (prev.w > (next.x - 3))
135 && (next.h > (prev.y - 3))
136 && (prev.h > (next.y - 3)) ) {
137 next.set(next.x < prev.x ? next.x : prev.x,
138 next.y < prev.y ? next.y : prev.y,
139 next.w > prev.w ? next.w : prev.w,
140 next.h > prev.h ? next.h : prev.h);
141 // recalculate x2/y2 back into w/h
142 target[target_count - 1].set(next.x, next.y,
143 next.w - next.x,
144 next.h - next.y);
145
146 // Make sure the target update is sane:
147 assert( (target[target_count - 1].w > 0)
148 && (target[target_count - 1].h > 0) );
149
150 combined = true;
151 }
152 }
153
154 if (!combined)
155 target[target_count++].set(left, top, right - left, bottom - top);
156
157 if (!stopwindow && (target_count <= env.max_screen_updates))
158 env.window_update(left, top, right - left, bottom - top);
159 }
160
161
162 // return true if any living tank is in the given box.
163 // left/right and top/bottom are determined automatically.
areTanksInBox(int32_t x1,int32_t y1,int32_t x2,int32_t y2)164 bool GLOBALDATA::areTanksInBox(int32_t x1, int32_t y1, int32_t x2, int32_t y2)
165 {
166 TANK* lt = static_cast<TANK*>(heads[CLASS_TANK]);
167
168 while (lt) {
169 // Tank found, is it in the box?
170 if ( (!lt->destroy) && lt->isInBox(x1, y1, x2, y2))
171 return true;
172 lt->getNext(<);
173 }
174
175 return false;
176 }
177
178
179
180 // This function checks to see if one full second has passed since the
181 // last time the function was called.
182 // The function returns true if time has passed. The function
183 // returns false if time hasn't passed or it was unable to tell
184 // how much time has passed.
check_time_changed()185 bool GLOBALDATA::check_time_changed()
186 {
187 volatile
188 static time_t last_second = 0;
189 static time_t current_second = 0;
190
191 time(¤t_second);
192
193 if ( current_second == last_second )
194 return false;
195
196 // time has changed
197 last_second = current_second;
198
199 return true;
200 }
201
202
203 /// @brief remove and delete *all* objects stored.
clear_objects()204 void GLOBALDATA::clear_objects()
205 {
206 int32_t class_ = 0;
207
208 while (class_ < CLASS_COUNT) {
209 while (tails[class_])
210 delete tails[class_];
211 ++class_;
212 }
213 }
214
215
216 // Call before calling allegro_exit()!
destroy()217 void GLOBALDATA::destroy()
218 {
219 clear_objects();
220
221 if (debris_pool) {
222 delete debris_pool;
223 debris_pool = nullptr;
224 }
225
226 if (canvas) destroy_bitmap(canvas); canvas = nullptr;
227 if (terrain) destroy_bitmap(terrain); terrain = nullptr;
228 if (done) delete [] done; done = nullptr;
229 if (fp) delete [] fp; fp = nullptr;
230 if (surface) delete [] surface; surface = nullptr;
231 if (dropTo) delete [] dropTo; dropTo = nullptr;
232 if (velocity) delete [] velocity; velocity = nullptr;
233 if (dropIncr) delete [] dropIncr; dropIncr = nullptr;
234 if (updates) delete [] updates; updates = nullptr;
235 if (lastUpdates) delete [] lastUpdates; lastUpdates = nullptr;
236 }
237
238
do_updates()239 void GLOBALDATA::do_updates ()
240 {
241 bool isBgUpdNeeded = lastUpdatesCount > 0;
242
243 acquire_bitmap(screen);
244 for (int32_t i = 0; i < updateCount; ++i) {
245 blit( canvas, screen,
246 updates[i].x, updates[i].y, updates[i].x, updates[i].y,
247 updates[i].w, updates[i].h);
248
249 if (isBgUpdNeeded)
250 make_bgupdate( updates[i].x, updates[i].y,
251 updates[i].w, updates[i].h);
252 }
253 release_bitmap(screen);
254 if (!isBgUpdNeeded) {
255 lastUpdatesCount = updateCount;
256 memcpy (lastUpdates, updates, sizeof (BOX) * updateCount);
257 }
258 updateCount = 0;
259 }
260
261
262 // Do what has to be done after the game starts
first_init()263 void GLOBALDATA::first_init()
264 {
265 // get memory for updates
266 try {
267 updates = new BOX[env.max_screen_updates];
268 } catch (std::bad_alloc &e) {
269 cerr << "globaldata.cpp:" << __LINE__ << ":first_init() : "
270 << "Failed to allocate memory for updates ["
271 << e.what() << "]" << endl;
272 exit(1);
273 }
274
275 // get memory for lastUpdates
276 try {
277 lastUpdates = new BOX[env.max_screen_updates];
278 } catch (std::bad_alloc &e) {
279 cerr << "globaldata.cpp:" << __LINE__ << ":first_init() : "
280 << "Failed to allocate memory for lastUpdates ["
281 << e.what() << "]" << endl;
282 exit(1);
283 }
284
285 canvas = create_bitmap (env.screenWidth, env.screenHeight);
286 if (!canvas) {
287 cout << "Failed to create canvas bitmap: " << allegro_error << endl;
288 exit(1);
289 }
290
291 terrain = create_bitmap (env.screenWidth, env.screenHeight);
292 if (!terrain) {
293 cout << "Failed to create terrain bitmap: " << allegro_error << endl;
294 exit(1);
295 }
296
297
298 // get memory for the debris pool
299 try {
300 debris_pool = new sDebrisPool(env.max_screen_updates);
301 } catch (std::bad_alloc &e) {
302 cerr << "globaldata.cpp:" << __LINE__ << ":first_init() : "
303 << "Failed to allocate memory for debris_pool ["
304 << e.what() << "]" << endl;
305 exit(1);
306 }
307
308
309 try {
310 done = new int8_t[env.screenWidth]{0};
311 fp = new int32_t[env.screenWidth]{0};
312 surface = new ai32_t[env.screenWidth]{ { 0 } };
313 dropTo = new int32_t[env.screenWidth]{0};
314 velocity = new double[env.screenWidth]{0};
315 dropIncr = new double[env.screenWidth]{0};
316 } catch (std::bad_alloc &e) {
317 cerr << "globaldata.cpp:" << __LINE__ << ":first_init() : "
318 << "Failed to allocate memory for base data arrays ["
319 << e.what() << "]" << endl;
320 exit(1);
321 }
322
323 initialise ();
324 }
325
326
327 /** @brief delegate freeing of a debris item to the debris pool.
328 *
329 * This delegating function, instead of making the debris pool public,
330 * exists as a point where locking, if it becomes necessary, can be
331 * added without having to rewrite a lot of code.
332 **/
free_debris_item(item_t * item)333 void GLOBALDATA::free_debris_item(item_t* item)
334 {
335 debris_pool->free_item(item);
336 }
337
338
339
get_avg_bgcolor(int32_t x1,int32_t y1,int32_t x2,int32_t y2,double xv,double yv)340 int32_t GLOBALDATA::get_avg_bgcolor(int32_t x1, int32_t y1,
341 int32_t x2, int32_t y2,
342 double xv, double yv)
343 {
344 // Movement
345 int32_t mvx = ROUND(10. * xv); // eliminate slow movement
346 int32_t mvy = ROUND(10. * yv); // eliminate slow movement
347 bool mv_left = mvx < 0;
348 bool mv_right = mvx > 0;
349 bool mv_up = mvy < 0;
350 bool mv_down = mvy > 0;
351
352 // Boundaries
353 int32_t min_x = 1;
354 int32_t max_x = env.screenWidth - 2;
355 int32_t min_y = env.isBoxed ? MENUHEIGHT + 1 : MENUHEIGHT;
356 int32_t max_y = env.screenHeight - 2;
357
358 // Coordinates
359 int32_t left = std::max(std::min(x1, x2), min_x);
360 int32_t right = std::min(std::max(x1, x2), max_x);
361 int32_t centre = (x1 + x2) / 2;
362 int32_t top = std::max(std::min(y1, y2), min_y);
363 int32_t bottom = std::min(std::max(y1, y2), max_y);
364 int32_t middle = (y1 + y2) / 2;
365
366
367 // Colors:
368 int32_t col_tl, col_tc, col_tr; // top row
369 int32_t col_ml, col_mc, col_mr; // middle row
370 int32_t col_bl, col_bc, col_br; // bottom row
371 int32_t r = 0, g = 0, b = 0;
372
373
374 // Get Sky or Terrain colour, whatever fits:
375 /*---------------------
376 --- Left side ---
377 ---------------------*/
378 if ( PINK == (col_tl = getpixel(terrain, left, top)) )
379 col_tl = getpixel(env.sky, left, top);
380 if ( PINK == (col_ml = getpixel(terrain, left, middle)) )
381 col_ml = getpixel(env.sky, left, middle);
382 if ( PINK == (col_bl = getpixel(terrain, left, bottom)) )
383 col_bl = getpixel(env.sky, left, bottom);
384
385 /*---------------------
386 --- The Center ---
387 ---------------------*/
388 if ( PINK == (col_tc = getpixel(terrain, centre, top)) )
389 col_tc = getpixel(env.sky, centre, top);
390 if ( PINK == (col_mc = getpixel(terrain, centre, middle)) )
391 col_mc = getpixel(env.sky, centre, middle);
392 if ( PINK == (col_bc = getpixel(terrain, centre, bottom)) )
393 col_bc = getpixel(env.sky, centre, bottom);
394
395 /*----------------------
396 --- Right side ---
397 ----------------------*/
398 if ( PINK == (col_tr = getpixel(terrain, right, top)) )
399 col_tr = getpixel(env.sky, right, top);
400 if ( PINK == (col_mr = getpixel(terrain, right, middle)) )
401 col_mr = getpixel(env.sky, right, middle);
402 if ( PINK == (col_br = getpixel(terrain, right, bottom)) )
403 col_br = getpixel(env.sky, right, bottom);
404
405
406 // Fetch the rgb parts, according to movement:
407
408 /* --- X-Movement --- */
409 if (mv_left) {
410 // Movement to the left, weight left side colour twice
411 r += (GET_R(col_tl) + GET_R(col_ml) + GET_R(col_bl)) * 2;
412 g += (GET_G(col_tl) + GET_G(col_ml) + GET_G(col_bl)) * 2;
413 b += (GET_B(col_tl) + GET_B(col_ml) + GET_B(col_bl)) * 2;
414 // The others are counted once
415 r += GET_R(col_tc) + GET_R(col_mc) + GET_R(col_bc)
416 + GET_R(col_tr) + GET_R(col_mr) + GET_R(col_br);
417 g += GET_G(col_tc) + GET_G(col_mc) + GET_G(col_bc)
418 + GET_G(col_tr) + GET_G(col_mr) + GET_G(col_br);
419 b += GET_B(col_tc) + GET_B(col_mc) + GET_B(col_bc)
420 + GET_B(col_tr) + GET_B(col_mr) + GET_B(col_br);
421 } else if (mv_right) {
422 // Movement to the right, weight right side colour twice
423 r += (GET_R(col_tr) + GET_R(col_mr) + GET_R(col_br)) * 2;
424 g += (GET_G(col_tr) + GET_G(col_mr) + GET_G(col_br)) * 2;
425 b += (GET_B(col_tr) + GET_B(col_mr) + GET_B(col_br)) * 2;
426 // The others are counted once
427 r += GET_R(col_tc) + GET_R(col_mc) + GET_R(col_bc)
428 + GET_R(col_tl) + GET_R(col_ml) + GET_R(col_bl);
429 g += GET_G(col_tc) + GET_G(col_mc) + GET_G(col_bc)
430 + GET_G(col_tl) + GET_G(col_ml) + GET_G(col_bl);
431 b += GET_B(col_tc) + GET_B(col_mc) + GET_B(col_bc)
432 + GET_B(col_tl) + GET_B(col_ml) + GET_B(col_bl);
433 } else {
434 // No x-movement, weight centre colour twice
435 r += (GET_R(col_tc) + GET_R(col_mc) + GET_R(col_bc)) * 2;
436 g += (GET_G(col_tc) + GET_G(col_mc) + GET_G(col_bc)) * 2;
437 b += (GET_B(col_tc) + GET_B(col_mc) + GET_B(col_bc)) * 2;
438 // The others are counted once
439 r += GET_R(col_tl) + GET_R(col_ml) + GET_R(col_bl)
440 + GET_R(col_tr) + GET_R(col_mr) + GET_R(col_br);
441 g += GET_G(col_tl) + GET_G(col_ml) + GET_G(col_bl)
442 + GET_G(col_tr) + GET_G(col_mr) + GET_G(col_br);
443 b += GET_B(col_tl) + GET_B(col_ml) + GET_B(col_bl)
444 + GET_B(col_tr) + GET_B(col_mr) + GET_B(col_br);
445 }
446
447 /* --- Y-Movement --- */
448 if (mv_up) {
449 // Movement upwards, weight top side colour twice
450 r += (GET_R(col_tl) + GET_R(col_tc) + GET_R(col_tr)) * 2;
451 g += (GET_G(col_tl) + GET_G(col_tc) + GET_G(col_tr)) * 2;
452 b += (GET_B(col_tl) + GET_B(col_tc) + GET_B(col_tr)) * 2;
453 // The others are counted once
454 r += GET_R(col_ml) + GET_R(col_mc) + GET_R(col_mr)
455 + GET_R(col_bl) + GET_R(col_bc) + GET_R(col_br);
456 g += GET_G(col_ml) + GET_G(col_mc) + GET_G(col_mr)
457 + GET_G(col_bl) + GET_G(col_bc) + GET_G(col_br);
458 b += GET_B(col_ml) + GET_B(col_mc) + GET_B(col_mr)
459 + GET_B(col_bl) + GET_B(col_bc) + GET_B(col_br);
460 } else if (mv_down) {
461 // Movement downwards, weight bottom side colour twice
462 r += (GET_R(col_bl) + GET_R(col_bc) + GET_R(col_br)) * 2;
463 g += (GET_G(col_bl) + GET_G(col_bc) + GET_G(col_br)) * 2;
464 b += (GET_B(col_bl) + GET_B(col_bc) + GET_B(col_br)) * 2;
465 // The others are counted once
466 r += GET_R(col_ml) + GET_R(col_mc) + GET_R(col_mr)
467 + GET_R(col_tl) + GET_R(col_tc) + GET_R(col_tr);
468 g += GET_G(col_ml) + GET_G(col_mc) + GET_G(col_mr)
469 + GET_G(col_tl) + GET_G(col_tc) + GET_G(col_tr);
470 b += GET_B(col_ml) + GET_B(col_mc) + GET_B(col_mr)
471 + GET_B(col_tl) + GET_B(col_tc) + GET_B(col_tr);
472 } else {
473 // No y-movement, weight middle colour twice
474 r += (GET_R(col_ml) + GET_R(col_mc) + GET_R(col_mr)) * 2;
475 g += (GET_G(col_ml) + GET_G(col_mc) + GET_G(col_mr)) * 2;
476 b += (GET_B(col_ml) + GET_B(col_mc) + GET_B(col_mr)) * 2;
477 // The others are counted once
478 r += GET_R(col_tl) + GET_R(col_tc) + GET_R(col_tr)
479 + GET_R(col_bl) + GET_R(col_bc) + GET_R(col_br);
480 g += GET_G(col_tl) + GET_G(col_tc) + GET_G(col_tr)
481 + GET_G(col_bl) + GET_G(col_bc) + GET_G(col_br);
482 b += GET_B(col_tl) + GET_B(col_tc) + GET_B(col_tr)
483 + GET_B(col_bl) + GET_B(col_bc) + GET_B(col_br);
484 }
485
486
487 /* I know this looks weird, but what we now have is some kind of summed
488 * matrix, which is always the same:
489 * Let's assume that xv and yv are both 0.0, so no movement is happening.
490 * The result is: (In counted times)
491 * 2|3|2 ( = 7)
492 * -+-+-
493 * 3|4|3 ( = 10)
494 * -+-+-
495 * 2|3|2 ( = 7)
496 * = 24
497 * And it is always 24, no matter which movement combination you try
498 */
499
500 r /= 24;
501 g /= 24;
502 b /= 24;
503
504 return makecol(r > 0xff ? 0xff : r,
505 g > 0xff ? 0xff : g,
506 b > 0xff ? 0xff : b);
507 }
508
509
510 // Locks global->command for reading, reads value, then unlocks the variable
511 // and returns the value.
get_command()512 int32_t GLOBALDATA::get_command()
513 {
514 cmdLock.lock();
515 int32_t c = command;
516 cmdLock.unlock();
517 return c;
518 }
519
520
get_curr_tank()521 TANK* GLOBALDATA::get_curr_tank()
522 {
523 return currTank;
524 }
525
526
527 /** @brief delegate getting a debris item to the debris pool.
528 *
529 * This delegating function, instead of making the debris pool public,
530 * exists as a point where locking, if it becomes necessary, can be
531 * added without having to rewrite a lot of code.
532 **/
get_debris_item(int32_t radius)533 sDebrisItem* GLOBALDATA::get_debris_item(int32_t radius)
534 {
535 return debris_pool->get_item(radius);
536 }
537
538
get_next_tank(bool * wrapped_around)539 TANK* GLOBALDATA::get_next_tank(bool *wrapped_around)
540 {
541 bool found = false;
542 int32_t index = tankindex + 1;
543 int32_t oldindex = tankindex;
544 int32_t wrapped = 0;
545
546 while (!found && (wrapped < 2)) {
547 if (index >= MAXPLAYERS) {
548 index = 0;
549 *wrapped_around = true;
550 wrapped++;
551 }
552
553 if ( order[index]
554 && (index != oldindex)
555 && !order[index]->destroy)
556 found = true;
557 else
558 ++index;
559 }
560
561 tankindex = index;
562
563 // If this tank is valid, the currently selected weapon must be checked
564 // first and changed if depleted
565 TANK* next_tank = order[index];
566 if (next_tank && next_tank->player)
567 next_tank->check_weapon();
568
569 // Whatever happened, the status bar needs an update:
570 if (oldindex != index)
571 updateMenu = true;
572
573 return next_tank;
574 }
575
576
initialise()577 void GLOBALDATA::initialise ()
578 {
579 clear_objects();
580 numTanks = 0;
581 clear_to_color (canvas, WHITE);
582 clear_to_color (terrain, PINK);
583
584 for (int32_t i = 0; i < env.screenWidth; ++i) {
585 done[i] = 0;
586 dropTo[i] = env.screenHeight - 1;
587 fp[i] = 0;
588 }
589 }
590
591
592 // return true if the dirt reaches into the given box.
593 // left/right and top/bottom are determined automatically.
isDirtInBox(int32_t x1,int32_t y1,int32_t x2,int32_t y2)594 bool GLOBALDATA::isDirtInBox(int32_t x1, int32_t y1, int32_t x2, int32_t y2)
595 {
596 int32_t top = std::max(std::min(y1, y2),
597 env.isBoxed ? MENUHEIGHT + 1 : MENUHEIGHT);
598 // Exit early if the box is below the playing area
599 if (top >= env.screenHeight)
600 return false;
601
602 int32_t bottom = std::min(std::max(y1, y2), env.screenHeight - 2);
603 // Exit early if the box is over the playing area
604 if (bottom <= MENUHEIGHT)
605 return false;
606
607 int32_t left = std::max(std::min(x1, x2), 1);
608 int32_t right = std::min(std::max(x1, x2), env.screenWidth - 2);
609
610 // If the box is outside the playing area, this loop won't do anything
611 for (int32_t x = left; x <= right; ++x) {
612 if (surface[x].load(ATOMIC_READ) <= bottom)
613 return true;
614 }
615
616 return false;
617 }
618
619
620 /// @return true if the close button was pressed
isCloseBtnPressed()621 bool GLOBALDATA::isCloseBtnPressed()
622 {
623 cbpLock.lock();
624 bool result = close_button_pressed;
625 cbpLock.unlock();
626
627 return result;
628 }
629
630
631 /** @brief load global data from a file
632 * This method is still present to provide backwards
633 * compatibility with configurations that were saved
634 * before the values were moved to ENVIRONMENT
635 **/
load_from_file(FILE * file)636 void GLOBALDATA::load_from_file (FILE* file)
637 {
638 char line[ MAX_CONFIG_LINE + 1] = { 0 };
639 char field[MAX_CONFIG_LINE + 1] = { 0 };
640 char value[MAX_CONFIG_LINE + 1] = { 0 };
641 char* result = nullptr;
642
643 setlocale(LC_NUMERIC, "C");
644
645 // read until we hit line "*GLOBAL*" or "***" or EOF
646 do {
647 result = fgets(line, MAX_CONFIG_LINE, file);
648 if ( !result
649 || !strncmp(line, "***", 3) )
650 // eof OR end of record
651 return;
652 } while ( strncmp(line, "*GLOBAL*", 8) );
653
654 bool done = false;
655
656 while (result && !done) {
657 // read a line
658 memset(line, '\0', MAX_CONFIG_LINE);
659 if ( ( result = fgets(line, MAX_CONFIG_LINE, file) ) ) {
660
661 // if we hit end of the record, stop
662 if (! strncmp(line, "***", 3) )
663 return;
664
665 // strip newline character
666 int32_t line_length = strlen(line);
667 while ( line[line_length - 1] == '\n') {
668 line[line_length - 1] = '\0';
669 line_length--;
670 }
671
672 // find equal sign
673 int32_t equal_position = 1;
674 while ( ( equal_position < line_length )
675 && ( line[equal_position] != '=' ) )
676 equal_position++;
677
678 // make sure the equal sign position is valid
679 if (line[equal_position] != '=')
680 continue; // Go to next line
681
682 // seperate field from value
683 memset(field, '\0', MAX_CONFIG_LINE);
684 memset(value, '\0', MAX_CONFIG_LINE);
685 strncpy(field, line, equal_position);
686 strncpy(value, &( line[equal_position + 1] ), MAX_CONFIG_LINE);
687
688
689 // Values that were moved to ENVIRONMENT:
690 // They are loaded, for compatibility, but the next
691 // save will put them into the correct section anyway.
692 // So these can eventually be removed.
693 if (!strcasecmp(field, "acceleratedai")) {
694 sscanf(value, "%d", &env.skipComputerPlay);
695 if (env.skipComputerPlay > SKIP_HUMANS_DEAD)
696 env.skipComputerPlay = SKIP_HUMANS_DEAD;
697 } else if (!strcasecmp(field, "checkupdates")) {
698 int32_t val = 0;
699 sscanf(value, "%d", &val);
700 env.check_for_updates = val > 0 ? true : false;
701 } else if (!strcasecmp(field, "colourtheme") ) {
702 sscanf(value, "%d", &env.colourTheme);
703 if (env.colourTheme < CT_REGULAR) env.colourTheme = CT_REGULAR;
704 if (env.colourTheme > CT_CRISPY) env.colourTheme = CT_CRISPY;
705 } else if (!strcasecmp(field, "debrislevel") )
706 sscanf(value, "%d", &env.debris_level);
707 else if (!strcasecmp(field, "detailedland")) {
708 int32_t val = 0;
709 sscanf(value, "%d", &val);
710 env.detailedLandscape = val > 0 ? true : false;
711 } else if (!strcasecmp(field, "detailedsky")) {
712 int32_t val = 0;
713 sscanf(value, "%d", &val);
714 env.detailedSky = val > 0 ? true : false;
715 } else if (!strcasecmp(field, "dither")) {
716 int32_t val = 0;
717 sscanf(value, "%d", &val);
718 env.ditherGradients = val > 0 ? true : false;
719 } else if (!strcasecmp(field, "dividemoney") ) {
720 int32_t val = 0;
721 sscanf(value, "%d", &val);
722 env.divide_money = val > 0 ? true : false;
723 } else if (!strcasecmp(field, "enablesound")) {
724 int32_t val = 0;
725 sscanf(value, "%d", &val);
726 env.sound_enabled = val > 0 ? true : false;
727 } else if (!strcasecmp(field, "frames") ) {
728 int32_t new_fps = 0;
729 sscanf(value, "%d", &new_fps);
730 env.set_fps(new_fps);
731 } else if (!strcasecmp(field, "fullscreen"))
732 sscanf(value, "%d", &env.full_screen);
733 else if (!strcasecmp(field, "interest"))
734 sscanf(value, "%lf", &env.interest);
735 else if (!strcasecmp(field, "language") ) {
736 uint32_t stored_lang = 0;
737 sscanf(value, "%u", &stored_lang);
738 env.language = static_cast<eLanguages>(stored_lang);
739 } else if (!strcasecmp(field, "listenport"))
740 sscanf(value, "%d", &env.network_port);
741 else if (!strcasecmp(field, "maxfiretime") )
742 sscanf(value, "%d", &env.maxFireTime);
743 else if (!strcasecmp(field, "networking")) {
744 int32_t val = 0;
745 sscanf(value, "%d", &val);
746 env.network_enabled = val > 0 ? true : false;
747 } else if (!strcasecmp(field, "numpermanentplayers"))
748 sscanf(value, "%d", &env.numPermanentPlayers);
749 else if (!strcasecmp(field, "OSMOUSE")) {
750 int32_t val = 0;
751 sscanf(value, "%d", &val);
752 env.osMouse = val > 0 ? true : false;
753 } else if (!strcasecmp(field, "playmusic")) {
754 int32_t val = 0;
755 sscanf(value, "%d", &val);
756 env.play_music = val > 0 ? true : false;
757 } else if (!strcasecmp(field, "rounds") )
758 sscanf(value, "%u", &env.rounds);
759 else if (!strcasecmp(field, "screenwidth")
760 && !env.temp_screenWidth) {
761 sscanf(value, "%d", &env.screenWidth);
762 env.halfWidth = env.screenWidth / 2;
763 env.temp_screenWidth = env.screenWidth;
764 } else if (!strcasecmp(field, "screenheight")
765 && !env.temp_screenHeight) {
766 sscanf(value, "%d", &env.screenHeight);
767 env.halfHeight = env.screenHeight / 2;
768 env.temp_screenHeight = env.screenHeight;
769 }
770 else if (!strcasecmp(field, "scorehitunit"))
771 sscanf(value, "%d", &env.scoreHitUnit);
772 else if (!strcasecmp(field, "scoreselfhit"))
773 sscanf(value, "%d", &env.scoreSelfHit);
774 else if (!strcasecmp(field, "scoreroundwinbonus"))
775 sscanf(value, "%d", &env.scoreRoundWinBonus);
776 else if (!strcasecmp(field, "scoreteamhit"))
777 sscanf(value, "%d", &env.scoreTeamHit);
778 else if (!strcasecmp(field, "scoreunitdestroybonus"))
779 sscanf(value, "%d", &env.scoreUnitDestroyBonus);
780 else if (!strcasecmp(field, "scoreunitselfdestroy"))
781 sscanf(value, "%d", &env.scoreUnitSelfDestroy);
782 else if (!strcasecmp(field, "sellpercent"))
783 sscanf(value, "%lf", &env.sellpercent);
784 else if (!strcasecmp(field, "sounddriver"))
785 sscanf(value, "%d", &env.sound_driver);
786 else if (!strcasecmp(field, "startmoney"))
787 sscanf(value, "%d", &env.startmoney);
788 else if (!strcasecmp(field, "turntype"))
789 sscanf(value, "%d", &env.turntype);
790 else if (!strcasecmp(field, "violentdeath") )
791 sscanf(value, "%d", &env.violent_death);
792 } // end of read a line properly
793 } // end of while not done
794 }
795
796
lockClass(eClasses class_)797 void GLOBALDATA::lockClass(eClasses class_)
798 {
799 objLocks[class_].lock();
800 }
801
802
lockLand()803 void GLOBALDATA::lockLand()
804 {
805 landLock.lock();
806 }
807
808
make_bgupdate(int32_t x,int32_t y,int32_t w,int32_t h)809 void GLOBALDATA::make_bgupdate (int32_t x, int32_t y, int32_t w, int32_t h)
810 {
811 if (lastUpdatesCount >= env.max_screen_updates) {
812 make_fullUpdate();
813 return;
814 }
815
816 assert( (w > 0) && (h > 0) );
817
818 if ( (w > 0) && (h > 0) )
819 addUpdate(x, y, w, h, lastUpdates, lastUpdatesCount);
820 }
821
822
make_fullUpdate()823 void GLOBALDATA::make_fullUpdate()
824 {
825 // Replace Updates with a full screen update:
826 combineUpdates = false;
827 updateCount = 0;
828 lastUpdatesCount = 0;
829
830 // They are split into 2 x 2 updates:
831 for (int32_t x = 0; x < 2; ++x) {
832 make_update( env.halfWidth * x, 0,
833 env.halfWidth, env.halfHeight);
834 make_bgupdate(env.halfWidth * x, 0,
835 env.halfWidth, env.halfHeight);
836 make_update( env.halfWidth * x, env.halfHeight,
837 env.halfWidth, env.halfHeight);
838 make_bgupdate(env.halfWidth * x, env.halfHeight,
839 env.halfWidth, env.halfHeight);
840 }
841
842 combineUpdates = true;
843 }
844
845
make_update(int32_t x,int32_t y,int32_t w,int32_t h)846 void GLOBALDATA::make_update (int32_t x, int32_t y, int32_t w, int32_t h)
847 {
848 if (updateCount >= env.max_screen_updates) {
849 make_fullUpdate();
850 return;
851 }
852
853 // These asserts should catch screwed updates that make no sense
854 assert( (h <= env.screenHeight) && (w <= env.screenWidth) );
855 assert( (w > 0) && (h > 0) );
856
857 if ( (h > 0) && (w > 0) )
858 addUpdate(x, y, w, h, updates, updateCount);
859 }
860
861
newRound()862 void GLOBALDATA::newRound()
863 {
864 if ( (currentround > 0) && (currentround-- < env.nextCampaignRound) )
865 env.nextCampaignRound -= env.campaign_rounds;
866
867 tankindex = 0;
868 naturals_activated = 0;
869 combineUpdates = true;
870
871 // clean all but texts and tanks
872 int32_t class_ = 0;
873 while (class_ < CLASS_COUNT) {
874 if ( (CLASS_FLOATTEXT != class_) && (CLASS_TANK != class_) ) {
875 while (tails[class_])
876 delete tails[class_];
877 }
878 ++class_;
879 }
880
881
882 // Re-init land slide
883 for (int32_t i = 0; i < env.screenWidth; ++i) {
884 done[i] = 2; // Check at once
885 dropTo[i] = env.screenHeight - 1;
886 fp[i] = 0;
887 }
888
889 // Init order array
890 for (int32_t i = 0; i < MAXPLAYERS; ++i)
891 order[i] = nullptr;
892 }
893
894
895 /// @brief Tell global that the close button was pressed
pressCloseButton()896 void GLOBALDATA::pressCloseButton()
897 {
898 cbpLock.lock();
899 close_button_pressed = true;
900 cbpLock.unlock();
901 set_command(GLOBAL_COMMAND_QUIT);
902 }
903
904
removeObject(vobj_t * object)905 void GLOBALDATA::removeObject (vobj_t *object)
906 {
907 if (nullptr == object)
908 return;
909
910 eClasses class_ = object->getClass();
911
912 /// --- 1: Is the list empty? ---
913 if (nullptr == heads[class_])
914 return;
915
916 objLocks[class_].lock();
917
918 /// --- 2: If the object is head, set it anew:
919 if (object == heads[class_])
920 heads[class_] = object->next;
921
922 /// --- 4: If the object is tail, set it anew:
923 if (object == tails[class_])
924 tails[class_] = object->prev;
925
926 /// --- 5: Take it out of the list:
927 if (object->prev)
928 object->prev->next = object->next;
929 if (object->next)
930 object->next->prev = object->prev;
931 object->prev = nullptr;
932 object->next = nullptr;
933
934 objLocks[class_].unlock();
935 }
936
937
removeTank(TANK * tank)938 void GLOBALDATA::removeTank(TANK* tank)
939 {
940 if (nullptr == tank)
941 return;
942
943 for (int32_t i = 0 ; i < MAXPLAYERS ; ++i) {
944 if (tank == order[i])
945 order[i] = nullptr;
946 }
947 }
948
949
replace_canvas()950 void GLOBALDATA::replace_canvas ()
951 {
952
953 for (int32_t i = 0; i < lastUpdatesCount; ++i) {
954 if ((lastUpdates[i].y + lastUpdates[i].h) > MENUHEIGHT) {
955 blit (env.sky, canvas, lastUpdates[i].x, lastUpdates[i].y - MENUHEIGHT,
956 lastUpdates[i].x, lastUpdates[i].y,
957 lastUpdates[i].w, lastUpdates[i].h);
958 masked_blit (terrain, canvas, lastUpdates[i].x, lastUpdates[i].y,
959 lastUpdates[i].x, lastUpdates[i].y,
960 lastUpdates[i].w, lastUpdates[i].h);
961 } // End of having an update below the top bar
962 }
963
964 int32_t l = 0;
965 int32_t r = env.screenWidth - 1;
966 int32_t t = MENUHEIGHT;
967 int32_t b = env.screenHeight - 1;
968
969 vline(canvas, l, t, b, env.wallColour); // Left edge
970 vline(canvas, l + 1, t, b, env.wallColour); // Left edge
971 vline(canvas, r, t, b, env.wallColour); // right edge
972 vline(canvas, r - 1, t, b, env.wallColour); // right edge
973 hline(canvas, l, b, r, env.wallColour); // bottom edge
974 if (env.isBoxed)
975 hline(canvas, l, t, r, env.wallColour); // top edge
976
977 lastUpdatesCount = 0;
978 }
979
980
981 // Set a new command, lock guarded
set_command(int32_t cmd)982 void GLOBALDATA::set_command(int32_t cmd)
983 {
984 cmdLock.lock();
985 command = cmd;
986 cmdLock.unlock();
987 }
988
989
set_curr_tank(TANK * tank_)990 void GLOBALDATA::set_curr_tank(TANK* tank_)
991 {
992 if (tank_ != currTank) {
993 if (currTank)
994 currTank->deactivate();
995 currTank = tank_;
996 if (currTank)
997 currTank->activate();
998 }
999 }
1000
1001
1002 /** @brief go through the land and slide what is to be slid and is not locked
1003 * Slide land basic control is done using the 'done[]' array.
1004 * done[x] == 0 : Nothing to do. All values assumed to be correct.
1005 * done[x] == 1 : This column is currently in sliding.
1006 * done[x] == 2 : This column is about to be slid, but the base values aren't set.
1007 * done[x] == 3 : This column is about to be slid but locked. (Explosion not done)
1008 **/
slideLand()1009 void GLOBALDATA::slideLand()
1010 {
1011 // Opt out soon if no landslide is to be done
1012 if ( (SLIDE_NONE == env.landSlideType)
1013 || (SLIDE_TANK_ONLY == env.landSlideType)
1014 || ( (SLIDE_CARTOON == env.landSlideType)
1015 && (env.time_to_fall > 0) ) )
1016 return;
1017
1018 for (int32_t col = 1; col < (env.screenWidth - 1); ++col) {
1019
1020 // Skip this column if it is done or locked
1021 if (!done[col] || (3 == done[col]))
1022 continue;
1023
1024 // Set base settings if this hasn't happen, yet
1025 if (2 == done[col]) {
1026 surface[col].store(0, ATOMIC_WRITE);
1027 dropTo[col] = env.screenHeight - 1;
1028 done[col] = 1;
1029
1030 // Calc the top and bottom of the column to slide
1031
1032 // Find top-most non-PINK pixel
1033 int32_t row = MENUHEIGHT + (env.isBoxed ? 1 : 0);
1034
1035 for ( ;(row < dropTo[col])
1036 && (PINK == getpixel(terrain, col, row));
1037 ++row) ;
1038 surface[col].store(row, ATOMIC_WRITE); // This is the top pixel with all gaps
1039
1040 // Find bottom-most PINK pixel
1041 int32_t top_row = row;
1042 for (row = dropTo[col];
1043 (row > top_row)
1044 && (PINK != getpixel(terrain, col, row));
1045 --row) ;
1046 dropTo[col] = row;
1047
1048 // Find bottom-most unsupported pixel
1049 for ( ;(row >= top_row)
1050 && (PINK == getpixel(terrain, col, row));
1051 --row) ;
1052
1053 // Check whether there is anything to do or not
1054 if ((row >= top_row) && (top_row < dropTo[col])) {
1055 fp[col] = row - top_row + 1;
1056 velocity[col] = 0; // Not yet
1057 done[col] = 1; // Can be processed
1058 }
1059
1060 // Otherwise this column is done
1061 else {
1062 if ( !skippingComputerPlay
1063 && (velocity[col] > .5)
1064 && (fp[col] > 1) )
1065 play_natural_sound(DIRT_FRAGMENT, col, 64,
1066 1000 - (fp[col] * 800 / env.screenHeight));
1067 done[col] = 0; // Nothing to do
1068 fp[col] = 0;
1069 }
1070 } // End of preparations
1071
1072 // Do the slide if possible
1073 if (1 == done[col]) {
1074
1075 // Only slide if no neighbours are locked
1076 bool can_slide = true;
1077 for (int32_t j = col - 1; can_slide && (j > 0) ; --j) {
1078 if (3 == done[j])
1079 can_slide = false;
1080 else if (!done[j])
1081 j = 0; // no further look needed.
1082 }
1083 for (int32_t j = col + 1; can_slide && (j < (env.screenWidth - 1)) ; ++j) {
1084 if (3 == done[j])
1085 can_slide = false;
1086 else if (!done[j])
1087 j = env.screenWidth; // no further look needed.
1088 }
1089
1090 if (can_slide) {
1091 // Do instant first, because only GRAVITY remains
1092 // which is the case if cartoon wait time is over.
1093 if ( (SLIDE_INSTANT == env.landSlideType) || skippingComputerPlay) {
1094 int32_t surf = surface[col].load(ATOMIC_READ);
1095 make_bgupdate (col, surf, 1, dropTo[col] - surf + 1);
1096 make_update (col, surf, 1, dropTo[col] - surf + 1);
1097 blit (terrain, terrain, col, surf, col,
1098 dropTo[col] - fp[col] + 1, 1, fp[col]);
1099 vline(terrain, col, surf, dropTo[col] - fp[col], PINK);
1100 velocity[col] = fp[col]; // Or no sound would be played if done
1101 done[col] = 2; // Recheck
1102 } else {
1103 velocity[col] += env.gravity;
1104 dropIncr[col] += velocity[col];
1105
1106 int32_t dropAdd = ROUND(dropIncr[col]);
1107 int32_t max_top = MENUHEIGHT + (env.isBoxed ? 1 : 0);
1108
1109 if (dropAdd > 0) {
1110
1111 int32_t top_row = surface[col].load(ATOMIC_READ);
1112
1113 assert( (top_row >= 0)
1114 && (top_row < terrain->h)
1115 && "ERROR: top_row out of range!");
1116
1117 // If the top pixel is not PINK, and the source is not
1118 // too high, increase dropAdd:
1119 int32_t over_top = top_row - dropAdd;
1120 while ( ( over_top <= max_top)
1121 && ( over_top > 0)
1122 && ( PINK != getpixel(terrain, col, over_top) ) ) {
1123 ++dropAdd;
1124 --over_top;
1125 }
1126
1127 if (dropAdd > (dropTo[col] - (top_row + fp[col])) ) {
1128 dropAdd = static_cast<int32_t>(dropTo[col]
1129 - (top_row + fp[col])
1130 + 1);
1131 dropIncr[col] = dropAdd;
1132 done[col] = 2; // Recheck
1133 over_top = top_row - dropAdd;
1134 }
1135
1136 int32_t slide_height = fp[col] + dropAdd;
1137
1138 assert( (over_top >= 0)
1139 && (over_top < terrain->h)
1140 && "ERROR: top_row - dropAdd out of range!");
1141 assert( (slide_height > 0)
1142 && (slide_height <= terrain->h)
1143 && "ERROR: slide_height out of range!");
1144 assert( ( (over_top + slide_height) <= terrain->h)
1145 && "ERROR: over_top + slide_height is out of range!");
1146 assert( ( (top_row + slide_height) <= terrain->h)
1147 && "ERROR: over_top + slide_height is out of range!");
1148
1149 blit (terrain, terrain,
1150 col, over_top,
1151 col, top_row, 1,
1152 slide_height);
1153 make_bgupdate(col, over_top, 1,
1154 slide_height + dropAdd + 1);
1155 make_update (col, over_top, 1,
1156 slide_height + dropAdd + 1);
1157 // If the top row reaches to the ceiling, there might
1158 // not be a PINK pixel to blit. In that case, one has
1159 // to be painted "by hand", or the slide will produce
1160 // nice long columns. (Happens with dirt balls when
1161 // "fixed" under the menubar.
1162 if (over_top <= max_top) {
1163 putpixel(terrain, col, max_top, PINK);
1164 putpixel(terrain, col, max_top + 1, PINK);
1165 }
1166
1167 surface[col].fetch_add(dropAdd);
1168 dropIncr[col] -= dropAdd;
1169 }
1170 }
1171 }
1172 } // End of actual slide
1173 } // End of looping columns
1174 }
1175
1176
unlockClass(eClasses class_)1177 void GLOBALDATA::unlockClass(eClasses class_)
1178 {
1179 objLocks[class_].unlock();
1180 }
1181
1182
unlockLand()1183 void GLOBALDATA::unlockLand()
1184 {
1185 landLock.unlock();
1186 }
1187
1188
1189 /// @brief goes through the columns from @a left to @a right and unlocks what is locked.
unlockLandSlide(int32_t left,int32_t right)1190 void GLOBALDATA::unlockLandSlide(int32_t left, int32_t right)
1191 {
1192 // Opt out soon if no landslide is to be done
1193 if ( (SLIDE_NONE == env.landSlideType)
1194 || (SLIDE_TANK_ONLY == env.landSlideType) )
1195 return;
1196
1197 int32_t minX = std::min(left, right);
1198 int32_t maxX = std::max(left, right);
1199
1200 if (minX < 1)
1201 minX = 1;
1202 if (maxX > (env.screenWidth - 1) )
1203 maxX = env.screenWidth - 1;
1204
1205 for (int32_t col = minX; col <= maxX; ++col) {
1206 if ((done[col] > 2) || !done[col])
1207 done[col] = 2;
1208 }
1209 }
1210
1211 #ifndef USE_MUTEX_INSTEAD_OF_SPINLOCK
1212
1213 /// === Spin Lock Implementations ===
1214
1215 /// @brief Default ctor
CSpinLock()1216 CSpinLock::CSpinLock() :
1217 is_destroyed(ATOMIC_VAR_INIT(false))
1218 {
1219 lock_flag.clear(); // Done this way, because VC++ can't do it normally.
1220 owner_id = std::thread::id();
1221 }
1222
1223
1224 /// @brief destructor - mark as destroyed, lock and go
~CSpinLock()1225 CSpinLock::~CSpinLock()
1226 {
1227 std::thread::id this_id = std::this_thread::get_id();
1228 bool need_lock = (owner_id != this_id);
1229
1230 if (need_lock)
1231 lock();
1232 is_destroyed.store(true);
1233 if (need_lock)
1234 unlock();
1235 }
1236
1237
1238 /// @brief return true if this thread has an active lock
hasLock()1239 bool CSpinLock::hasLock()
1240 {
1241 // This works, because unlock() sets the owner_id to -1.
1242 return (std::this_thread::get_id() == owner_id);
1243 }
1244
1245
1246 /** @brief Get a lock
1247 * Warning: No recursive locking possible! Only lock once!
1248 **/
lock()1249 void CSpinLock::lock()
1250 {
1251 std::thread::id this_id = std::this_thread::get_id();
1252 assert( (owner_id != this_id) && "ERROR: Lock already owned!");
1253
1254 if (false == is_destroyed.load(ATOMIC_READ)) {
1255 while (lock_flag.test_and_set()) {
1256 std::this_thread::yield();
1257 }
1258 owner_id = this_id;
1259 }
1260 }
1261
1262 /// @brief unlock if this thread owns the lock. Otherwise do nothing.
unlock()1263 void CSpinLock::unlock()
1264 {
1265 std::thread::id this_id = std::this_thread::get_id();
1266 assert( (owner_id == this_id) && "ERROR: Lock *NOT* owned!");
1267
1268 if (owner_id == this_id) {
1269 owner_id = std::thread::id();
1270 lock_flag.clear(std::memory_order_release);
1271 }
1272 }
1273
1274 #endif // USE_MUTEX_INSTEAD_OF_SPINLOCK
1275
1276